/* update.c - Zed's Virtual Terminal * Copyright (C) 1998 Michael Zucchi * * A virtual terminal emulator. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* This module handles the update of the 'on-screen' virtual terminal from the 'off-screen' buffer. It also handles selections, and making them visible, etc. */ #include #include #include #include #include #include #include #include #include #include #include "lists.h" #include "vt.h" #include "vtx.h" #define d(x) /* 'update' line debug */ #define u(x) /* update line 'line' (node 'l') of the vt always==1 assumes all line data is stale start/end columns to update */ static void vt_line_update(struct _vtx *vx, struct vt_line *l, struct vt_line *bl, int line, int always, int start, int end) { int i; int run, commonrun; int runstart; char *p; uint32 attr, newattr, oldattr, oldchar, newchar, oldback, lastchar; /* struct vt_line *bl;*/ int sx, ex; /* start/end selection */ int force; d(printf("updating line %d: ", line)); d(fwrite(l->data, l->width, 1, stdout)); d(printf("\n")); u(printf("updating line from (%d-%d) ->", start, end)); /* for 'scrollback' where the window has changed size - create a proper-length line (this is easier than special case code below */ /* bl = (struct vt_line *)vt_list_index(&vx->vt.lines_back, line); */ /* some sanity checks */ g_return_if_fail (bl != NULL); g_return_if_fail (bl->next != NULL); /* work out if selections are being rendered */ if (vx->selected && (((line >= (vx->selstarty - vx->vt.scrollbackoffset)) && /* normal order select */ (line <= (vx->selendy - vx->vt.scrollbackoffset))) || ((line <= (vx->selstarty - vx->vt.scrollbackoffset)) && /* start= (vx->selendy - vx->vt.scrollbackoffset)))) ) { /* work out range of selections */ sx = 0; ex = l->width; if (vx->selstarty<=vx->selendy) { if (line == (vx->selstarty-vx->vt.scrollbackoffset)) sx = vx->selstartx; if (line == (vx->selendy-vx->vt.scrollbackoffset)) ex = vx->selendx; } else { if (line == (vx->selendy-vx->vt.scrollbackoffset)) sx = vx->selendx; if (line == (vx->selstarty-vx->vt.scrollbackoffset)) { ex = vx->selstartx; } } /* check startxex) && (line == vx->selstarty-vx->vt.scrollbackoffset) && (line == vx->selendy-vx->vt.scrollbackoffset) ) { int tmp; tmp=sx; sx=ex; ex=tmp; } } else { sx = -1; ex = -1; } /* ^^ at this point sx -- ex needs to be inverted (it is selected) */ /* scan line, checking back vs front, if there are differences, then look to render */ run = 0; attr = 0; runstart = 0; p = NULL; oldback = 0; commonrun = 0; lastchar = 0; /* start out optimistic */ vx->back_match = 1; force = always; /* actual size to use is bl->width */ if (end>bl->width) end=bl->width; if (start>bl->width) start=bl->width; for (i=start;idata[i]; /* handle smaller line size, from scrollback */ if (iwidth) newchar = l->data[i]; else newchar = lastchar; lastchar = newchar & VTATTR_MASK; /* check for selected block */ if (i >= sx && i < ex) { newchar ^= VTATTR_REVERSE; } /* if there are no changes, quit right here ... */ if (oldchar != newchar || force) { bl->data[i] = newchar; newattr = newchar & VTATTR_MASK; oldattr = oldchar & VTATTR_MASK; if (run) { if (newattr == attr) { if (vx->back_match) { if (!VT_BLANK(oldchar & 0xffff) || (VT_BMASK(newattr) != VT_BMASK(oldattr))) vx->back_match = 0; } #ifdef VT_THRESHHOLD if (commonrun) { run += commonrun; commonrun=0; } #endif run++; } else { /* render 'run so far' */ vx->draw_text(vx->vt.user_data, bl, line, runstart, run, attr); vx->back_match = always?0: ((VT_BMASK(newattr) == VT_BMASK(oldattr)) && VT_BLANK(oldchar&0xffff) && (newattr&VTATTR_REVERSE)==0); run = 1; runstart = i; attr = newattr; commonrun = 0; } } else { vx->back_match = always?0: ((VT_BMASK(newattr) == VT_BMASK(oldattr)) && VT_BLANK(oldchar&0xffff) && (newattr&VTATTR_REVERSE)==0); runstart = i; attr = newattr; run=1; commonrun = 0; } } else { if (run) { #ifdef VT_THRESHHOLD /* check for runs of common characters, if they are short, then use them */ if (commonrun>VT_THRESHHOLD) { vx->draw_text(vx->vt.user_data, bl, line, runstart, run, attr); run=0; commonrun=0; } else { commonrun++; } #else vx->draw_text(vx->vt.user_data, bl, line, runstart, run, attr); run=0; #endif } } } if (run) { vx->draw_text(vx->vt.user_data, bl, line, runstart, run, attr); } l->modcount = 0; bl->line = line; l->line = line; } int vt_get_attr_at (struct _vtx *vx, int col, int row) { struct vt_line *line; line = (struct vt_line *) vt_list_index (&vx->vt.lines_back, row); return line->data [col]; } /* scroll/update a section of lines from firstline (fn), scroll count lines 'offset' lines */ static int vt_scroll_update(struct _vtx *vx, struct vt_line *fn, int firstline, int count, int offset, int scrolled) { struct vt_line *tn, *bn, *dn, *nn, *bl; /* top/bottom of scroll area */ int i, fill; int force; int top,bottom; int index; /* if we are not scrolling, then the background doesn't need rendering! */ if (vx->scroll_type == VT_SCROLL_SOMETIMES) force=1; else force=0; d(printf("scrolling %d lines from %d, by %d\n", count, firstline, offset)); d({ struct vt_line *wn; printf("before:\n"); wn = &vx->vt.lines_back.head; while(wn) { printf("node %p: n: %p p:%p -> %d\n", wn, wn->next, wn->prev, wn->line); wn = wn->next; } }); /* if we can optimise scrolling - do so */ if (vx->scroll_type == VT_SCROLL_ALWAYS || (vx->scroll_type == VT_SCROLL_SOMETIMES && count>(vx->vt.height/2))) { /* find start/end of scroll area */ if (offset > 0) { /* grab n lines at bottom of scroll area, and move them to above it */ tn = (struct vt_line *)vt_list_index(&vx->vt.lines_back, firstline); nn = (struct vt_line *)vt_list_index(&vx->vt.lines, firstline); bn = (struct vt_line *)vt_list_index(&vx->vt.lines_back, firstline+offset-1); dn = (struct vt_line *)vt_list_index(&vx->vt.lines_back, firstline+count+offset); } else { /* grab n lines at top of scroll area, and move them to below it */ tn = (struct vt_line *)vt_list_index(&vx->vt.lines_back, firstline+count+offset); nn = (struct vt_line *)vt_list_index(&vx->vt.lines, firstline+count+offset); bn = (struct vt_line *)vt_list_index(&vx->vt.lines_back, firstline+count-1); dn = (struct vt_line *)vt_list_index(&vx->vt.lines_back, firstline+offset); } if (!tn || !nn || !bn || !dn) { g_error("vt_scroll_update tn=%p nn=%p bn=%p dn=%p\n", tn, nn, bn, dn); } /* 0->dn->4->tn->1->2->bn->5 0->dn->4->5 0->tn->1->2->bn->dn->4->5 */ /* remove the scroll segment */ tn->prev->next = bn->next; bn->next->prev = tn->prev; /* insert it again at the destination */ tn->prev = dn->prev; dn->prev->next = tn; bn->next = dn; dn->prev = bn; d({ struct vt_line *wn; int line; printf("After\n"); wn = &vx->vt.lines_back.head; line=0; while(wn && linevt.height) { printf("node %p: n: %p p:%p -> %d\n", wn, wn->next, wn->prev, wn->line); wn = wn->next; line++; } }); /* this 'clears' the rendered data, to properly reflect what just happened. SPEEDUP: I suppose we could just leave it as is if we dont have a pixmap ... */ fill = nn->data[0] & VTATTR_MASK; do { d(printf("clearning line %d\n", tn->line)); for (i = 0; i < tn->width; i++) { tn->data[i] = fill; } } while ((tn!=bn) && (tn=tn->next)); /* find out what colour the new lines is - make it match (use * first character as a guess), and perform the visual scroll */ d(printf("firstline = %d, count = %d, offset = %d\n", firstline, count, offset)); /* idea: if scrolling 'most' of the screen, always scroll. then fix up the other areas by redrawing them, so the background repairs. */ d(printf("scrolling ...\n")); fill = (nn->data[0] & VTATTR_BACKCOLOURM) >> VTATTR_BACKCOLOURB; vx->scroll_area(vx->vt.user_data, firstline, count, offset, fill); /* force update of every other line */ /* get the top line */ index = vx->vt.scrollbackoffset; if (index<0) { nn = (struct vt_line *)vt_list_index(&vx->vt.scrollback, index); if (!nn) { /* check for error condition */ printf("LINE UNDERFLOW!\n"); nn = (struct vt_line *)vx->vt.scrollback.head; } } else { nn = (struct vt_line *)vx->vt.lines.head; } bl = (struct vt_line *)vx->vt.lines_back.head; top=firstline;bottom=firstline+count-1; d(printf("fixing up [force=%d] from %d-%d\n", force, top, bottom)); for (i=0;nn->next && ivt.height;i++) { d(printf("looking at line %d [%p] ", i, nn)); if (ibottom) { d(printf("forcing update of %d [force=%d]\n", i, force)); vt_line_update(vx, nn, bl, i, force, 0, bl->width); } else { d(printf(" leaving\n")); /* umm, force this line not updated */ nn->line = i; } nn->line = i; d(printf("%p: line %d, was %d\n", nn, i, nn->line)); if (nn==(struct vt_line *)vx->vt.scrollback.tailpred) { d(printf("++ skipping to real lines at line %d\n", nn->line)); nn = (struct vt_line *)vx->vt.lines.head; } else { d(printf("++ going to next line ...\n")); nn = nn->next; } bl = bl->next; } scrolled=1; } else { nn = fn; /* otherwise, scroll the area by redrawing what we have ... */ /* we only have to force if we've scrolled this time */ if (!scrolled && force) force=0; d(printf("fake scrolling ... [force=%d]\n", force)); if (offset>0) { bl = (struct vt_line *)vt_list_index(&vx->vt.lines_back, firstline); for (i=firstline;nn->next && i<(firstline+count+offset);i++) { d(printf("updating line %d\n", i)); vt_line_update(vx, nn, bl, i, force, 0, bl->width); if (nn==(struct vt_line *)vx->vt.scrollback.tailpred) { d(printf("++ skipping to real lines at line %d\n", nn->line)); nn = (struct vt_line *)vx->vt.lines.head; } else { d(printf("++ going to next line ...\n")); nn = nn->next; } bl = bl->next; } } else { index = vx->vt.scrollbackoffset + offset + firstline; if (index<0) { nn = (struct vt_line *)vt_list_index(&vx->vt.scrollback, index); if (!nn) { /* check for error condition */ printf("LINE UNDERFLOW!\n"); nn = (struct vt_line *)vx->vt.scrollback.head; } } else { nn = (struct vt_line *)vt_list_index(&vx->vt.lines, index); } bl = (struct vt_line *)vt_list_index(&vx->vt.lines_back, offset+firstline); d(printf("updating %d to %d\n", firstline+offset, firstline+count)); d(printf("negative offset - ooops\n")); for (i=firstline+offset;nn->next && iwidth); if (nn==(struct vt_line *)vx->vt.scrollback.tailpred) { d(printf("++ skipping to real lines at line %d\n", nn->line)); nn = (struct vt_line *)vx->vt.lines.head; } else { d(printf("++ going to next line ...\n")); nn = nn->next; } bl = bl->next; } } } return scrolled; } /* do an optimised update of the screen performed in 3 passes - first a scroll update pass, for down scrolling then a scroll update pass, for up scrolling finally a character update pass if state==0, perform optimised update otherwise perform refresh update (update whole screen) */ void vt_update(struct _vtx *vx, int update_state) { int line=0; int offset; int oldoffset=0; struct vt_line *wn, *nn, *fn, *bl; int firstline; int old_state; int update_start=-1; /* where to start/stop update */ int update_end=-1; int scrolled=0, force; d(printf("updating screen\n")); wn = NULL; nn = NULL; fn = NULL; old_state = vx->cursor_state(vx->vt.user_data, 0); /* find first line of visible screen, take into account scrollback */ offset = vx->vt.scrollbackoffset; if (offset<0) { wn = (struct vt_line *)vt_list_index(&vx->vt.scrollback, offset); if (!wn) { /* check for error condition */ printf("LINE UNDERFLOW!\n"); wn = (struct vt_line *)vx->vt.scrollback.head; } } else { wn = (struct vt_line *)vx->vt.lines.head; } d({ struct vt_line *dn; printf("INPUT before:\n"); dn = &vx->vt.lines; while(dn) { printf(" node %p: n: %p p:%p -> %d\n", dn, dn->next, dn->prev, dn->line); dn = dn->next; } dn = wn; while(dn) { printf(" node %p: n: %p p:%p -> %d\n", dn, dn->next, dn->prev, dn->line); dn = dn->next; } }); /* updated scrollback if we can ... otherwise dont */ if (!(update_state&UPDATE_REFRESH)) { /* if scrollback happening, then force update of newly exposed areas - calculate here */ offset = vx->vt.scrollbackoffset-vx->vt.scrollbackold; if (offset>0) { d(printf("scrolling up, by %d\n", offset)); update_start = vx->vt.height-offset-1; update_end = vx->vt.height; } else { d(printf("scrolling down, by %d\n", -offset)); update_start = -1; update_end = -offset; } d(printf("forced updated from %d - %d\n", update_start, update_end)); nn = wn->next; firstline = 0; /* this isn't really necessary (quietens compiler) */ fn = wn; while (nn && linevt.height) { int oldline; /* enforce full update for 'scrollback' areas */ if (line>update_start && lineline; } d(printf("%p: scanning line %d, offset=%d : line = %d\n ", wn, line, (wn->line-line), wn->line)); d(printf("(%d - %d)\n", line, wn->line)); if ((oldline!=-1) && ((offset = oldline-line) >0)) { wn->line = line; if (offset == oldoffset) { /* accumulate "update" lines */ } else { if (oldoffset != 0) { d(printf("scrolling (1.1)\n")); scrolled = vt_scroll_update(vx, fn, firstline, line-firstline, oldoffset, scrolled); /* scroll/update a line */ } d(printf("found a scrolled line\n")); firstline = line; /* first line to scroll */ fn = wn; } } else { if (oldoffset != 0) { d(printf("scrolling (1.2)\n")); scrolled = vt_scroll_update(vx, fn, firstline, line-firstline, oldoffset, scrolled); /* scroll/update a line */ } offset=0; } /* goto next logical line */ if (wn==(struct vt_line *)vx->vt.scrollback.tailpred) { d(printf("skipping to real lines at line %d\n", line)); wn = (struct vt_line *)vx->vt.lines.head; } else { d(printf("going to next line ...\n")); wn = nn; } oldoffset = offset; line ++; nn = wn->next; } if (oldoffset != 0) { d(printf("scrolling (1.3)\n")); d(printf("oldoffset = %d, must scroll\n", oldoffset)); scrolled = vt_scroll_update(vx, fn, firstline, line-firstline, oldoffset, scrolled); /* scroll/update a line */ } /* now scan backwards ! */ d(printf("scanning backwards now\n")); /* does this need checking for overflow? */ if (wn == (struct vt_line *)vx->vt.lines.head) { wn = (struct vt_line *)vx->vt.scrollback.tailpred; nn = wn->prev; } else { wn = wn->prev; nn = wn->prev; } line = vx->vt.height; oldoffset = 0; while (nn && line) { int oldline; line--; /* enforce full update for 'scrollback' areas */ if (line>update_start && lineline; } d(printf("%p: scanning line %d, offset=%d : line = %d, oldoffset=%d\n ", wn, line, (wn->line-line), wn->line, oldoffset)); if ((oldline !=-1) && ((offset = oldline-line) < 0)) { wn->line = line; if (offset == oldoffset) { /* accumulate "update" lines */ } else { if (oldoffset != 0) { d(printf("scrolling (2.1)\n")); scrolled = vt_scroll_update(vx, fn, line, firstline-line+1, oldoffset, scrolled); /* scroll/update a line */ } d(printf("found a scrolled line\n")); firstline = line; /* first line to scroll */ fn = wn; } } else { if (oldoffset != 0) { d(printf("scrolling (2.2)\n")); scrolled = vt_scroll_update(vx, fn, line+1, firstline-line, oldoffset, scrolled); /* scroll/update a line */ } offset=0; } /* goto previous logical line */ if (wn==(struct vt_line *)vx->vt.lines.head) { wn = (struct vt_line *)vx->vt.scrollback.tailpred; d(printf("going to scrollback buffer line ...\n")); } else { d(printf("going to prev line ...\n")); wn = nn; } nn = wn->prev; oldoffset = offset; d(printf("nn = %p\n", nn)); } if (oldoffset != 0) { d(printf("scrolling (2.3)\n")); d(printf("oldoffset = %d, must scroll 2\n", oldoffset)); scrolled = vt_scroll_update(vx, fn, 0, firstline-line+1, oldoffset, scrolled); /* scroll/update a line */ } /* have to align the pointer properly for the last pass */ if (wn==(struct vt_line *)vx->vt.scrollback.tailpred) { d(printf("++ skipping to real lines at line %d\n", line)); wn = (struct vt_line *)vx->vt.lines.head; } else { d(printf("++ going to next line ...\n")); wn = wn->next; } } /* now, re-scan, since all lines should be at the right position now, and update as required */ d(printf("scanning from top again\n")); /* see if we have to force an update */ if (scrolled && vx->scroll_type == VT_SCROLL_SOMETIMES) force = 1; else force = 0; nn = wn->next; firstline = 0; /* this isn't really necessary */ fn = wn; line=0; bl = (struct vt_line *)vx->vt.lines_back.head; offset = vx->vt.scrollbackoffset; while (nn && linevt.height) { d(printf("%p: scanning line %d, was %d\n", wn, line, wn->line)); if (wn->line==-1) { vt_line_update(vx, wn, bl, line, 0, 0, bl->width); d(printf("manual: updating line %d\n", line)); } else if (wn->modcount || update_state) { vt_line_update(vx, wn, bl, line, force, 0, bl->width); d(printf("manual, forced: updating line %d\n", line)); } wn->line = line; /* make sure line is reset */ line++; /* goto next logical line */ if (wn==(struct vt_line *)vx->vt.scrollback.tailpred) { d(printf("skipping to real lines at line %d\n", line)); wn = (struct vt_line *)vx->vt.lines.head; } else { wn = nn; } nn = wn->next; bl = bl->next; d(printf(" -- wn = %p, wn->next = %p\n", wn, wn->next)); } vx->vt.scrollbackold = vx->vt.scrollbackoffset; /* some debug */ #if 0 { struct vt_line *wb, *nb; int i; wb = (struct vt_line *)vx->vt.lines_back.head; nb = wb->next; printf("on-screen buffer contains:\n"); while (nb) { printf("%d: ", wb->line); for (i=0;iwidth;i++) { (printf("%c", (wb->data[i]&VTATTR_DATAMASK))); /*>=32?(wb->data[i]&0xffff):' '));*/ } (printf("\n")); wb=nb; nb=nb->next; } } #endif vx->cursor_state(vx->vt.user_data, old_state); } /* updates only a rectangle of the screen pass in rectangle of screen to draw (in pixel coordinates) - assumes screen order is up-to-date. fill is the 'colour' the rectangle is currently. use a fill colour of -1 to indicate that the contents of the screen is unknown. */ void vt_update_rect(struct _vtx *vx, int fill, int csx, int csy, int cex, int cey) { int lines; struct vt_line *wn, *nn; int old_state; int i; struct vt_line *bl; uint32 fillin; old_state = vx->cursor_state(vx->vt.user_data, 0); /* ensure cursor is really off */ d(printf("updating (%d,%d) - (%d,%d)\n", csx, csy, cex, cey)); /* bounds check */ if (cex>vx->vt.width) cex = vx->vt.width; if (csx>vx->vt.width) csx = vx->vt.width; if (cey>=vx->vt.height) cey = vx->vt.height-1; if (csy>=vx->vt.height) csy = vx->vt.height-1; lines = cey-csy; /* check scrollback for current line */ if ((vx->vt.scrollbackoffset+csy)<0) { wn = (struct vt_line *)vt_list_index(&vx->vt.scrollback, vx->vt.scrollbackoffset+csy); } else { wn = (struct vt_line *)vt_list_index(&vx->vt.lines, vx->vt.scrollbackoffset+csy); } bl = (struct vt_line *)vt_list_index(&vx->vt.lines_back, csy); fillin = (fill<next; while ((csy<=cey) && nn) { d(printf("updating line %d\n", csy)); /* make the back buffer match the screen state */ for (i=csx;iwidth;i++) { bl->data[i] = fillin; } vt_line_update(vx, wn, bl, csy, 0, csx, cex); csy++; /* skip out of scrollback buffer if need be */ if (wn==(struct vt_line *)vx->vt.scrollback.tailpred) { wn = (struct vt_line *)vx->vt.lines.head; } else { wn = nn; } nn = wn->next; bl = bl->next; } } vx->cursor_state(vx->vt.user_data, old_state); } /* returns true if 'c' is in a 'wordclass' (for selections) */ static int vt_in_wordclass(struct _vtx *vx, uint32 c) { int ch; c &= VTATTR_DATAMASK; if (c>=256) return 1; ch = c&0xff; return (vx->wordclass[ch>>3]&(1<<(ch&7)))!=0; } /* set the match wordclass. is a string of characters which constitute word characters which are selected when match by word is active. The syntax is similar to character classes in regec, but without the []'s. Ranges ban be set using A-Z, or 0-9. Use a leading - to include a hyphen. */ void vt_set_wordclass(struct _vtx *vx, unsigned char *s) { int start, end, i; memset(vx->wordclass, 0, sizeof(vx->wordclass)); if (s) { while ( (start = *s) ) { if (s[1]=='-' && s[2]) { end = s[2]; s+=3; } else { end = start; s++; } for (i=start;i<=end;i++) { vx->wordclass[i>>3] |= 1<<(i&7); } } } else { for(i=0;i<256;i++) if (isalnum(i) || i=='_') { vx->wordclass[i>>3] |= 1<<(i&7); } } d( printf("wordclass: "); for(i=0;iwordclass);i++) printf("%2.2x", vx->wordclass[i]); printf("\n") ); } /* fixes up the selection boundaries made by the mouse, so they match the screen data (tabs, newlines, etc) should this also fix the up/down ordering? */ void vt_fix_selection(struct _vtx *vx) { struct vt_line *s, *e; int sx, sy, ex, ey; /* range check vertical limits */ if (vx->selendy >= vx->vt.height) vx->selendy = vx->vt.height-1; if (vx->selstarty >= vx->vt.height) vx->selstarty = vx->vt.height-1; if (vx->selendy < (-vx->vt.scrollbacklines)) vx->selendy = -vx->vt.scrollbacklines; if (vx->selstarty < (-vx->vt.scrollbacklines)) vx->selstarty = -vx->vt.scrollbacklines; /* range check horizontal */ if (vx->selstartx<0) vx->selstartx=0; if (vx->selendx<0) vx->selendx=0; /* make sure 'start' is at the top/left */ if ( ((vx->selstarty == vx->selendy) && (vx->selstartx > vx->selendx)) || (vx->selstarty > vx->selendy) ) { ex = vx->selstartx; /* swap start/end */ ey = vx->selstarty; sx = vx->selendx; sy = vx->selendy; } else { /* keep start/end same */ sx = vx->selstartx; sy = vx->selstarty; ex = vx->selendx; ey = vx->selendy; } d(printf("fixing selection, starting at (%d,%d) (%d,%d)\n", sx, sy, ex, ey)); /* check if it is 'on screen' or in the scroll back memory */ s = (struct vt_line *)vt_list_index(sy<0?(&vx->vt.scrollback):(&vx->vt.lines), sy); e = (struct vt_line *)vt_list_index(ey<0?(&vx->vt.scrollback):(&vx->vt.lines), ey); /* if we didn't find it ... umm? FIXME: do something? */ switch(vx->selectiontype & VT_SELTYPE_MASK) { case VT_SELTYPE_LINE: d(printf("selecting by line\n")); sx=0; ex=e->width; break; case VT_SELTYPE_WORD: d(printf("selecting by word\n")); /* scan back over word chars */ d(printf("startx = %d %p-> \n", sx, s->data)); if (ex==sx && exwidth && sy==ey) ex++; if ((s->data[sx]&VTATTR_DATAMASK)==0 || (s->data[sx]&VTATTR_DATAMASK)==9) { while ((sx>0) && ((s->data[sx]&VTATTR_DATAMASK) == 0)) sx--; if (sx && ((s->data[sx])&VTATTR_DATAMASK)!=0x09) /* 'compress' tabs */ sx++; } else { while ((sx>0) && (( (vt_in_wordclass(vx, s->data[sx]))))) sx--; if (!vt_in_wordclass(vx, s->data[sx])) sx++; } d(printf("%d\n", sx)); /* scan forward over word chars */ /* special cases for tabs and 'blank' character select */ if ( !((ex >0) && ((e->data[ex-1]&VTATTR_DATAMASK) != 0)) ) while ((exwidth) && ((e->data[ex]&VTATTR_DATAMASK) == 0)) ex++; if ( !((ex >0) && (!vt_in_wordclass(vx, e->data[ex-1]))) ) while ((exwidth) && ( (vt_in_wordclass(vx, e->data[ex])))) ex++; break; case VT_SELTYPE_CHAR: default: d(printf("selecting by char\n")); if (ex==sx && exwidth && sy==ey) ex++; if ((s->data[sx]&VTATTR_DATAMASK)==0) { while ((sx>0) && ((s->data[sx]&VTATTR_DATAMASK) == 0)) sx--; if (sx && (( ((s->data[sx])&VTATTR_DATAMASK)!=0x09))) /* 'compress' tabs */ sx++; } /* special cases for tabs and 'blank' character select */ if ( !((ex >0) && ((e->data[ex-1]&VTATTR_DATAMASK) != 0)) ) while ((exwidth) && ((e->data[ex]&VTATTR_DATAMASK) == 0)) ex++; } if ( ((vx->selstarty == vx->selendy) && (vx->selstartx > vx->selendx)) || (vx->selstarty > vx->selendy) ) { vx->selstartx = ex; /* swap end/start values */ vx->selendx = sx; } else { vx->selstartx = sx; /* swap end/start values */ vx->selendx = ex; } d(printf("fixed selection, now at (%d,%d) (%d,%d)\n", sx, sy, ex, ey)); } /* convert columns start to column end of line l, into chars of size 'size'. */ static char *vt_expand_line(struct vt_line *l, int size, int start, int end, char *out) { int i; unsigned int c=0; int state=0; int dataend=0; int lf=0; /* scan from the end of the line to the start, looking for the actual end of screen data on that line */ for(dataend=l->width;dataend>-1;) { dataend--; if (l->data[dataend] & VTATTR_DATAMASK) { dataend++; break; } } if (end>dataend) { lf = 1; /* we selected past the end of the line */ end = dataend; } if (start<0) start=0; /* YUCK! oh well, this seems easiest way to do this! */ switch(size) { case 2: { unsigned short *o = (unsigned short *)out; for (i=start;idata[i] & VTATTR_DATAMASK; if (state==0) { if (c==0x09) state=1; else if (c<32) c=' '; else if (c>0xffff) c='?'; *o++ = c; } else { if (c) { /* in tab, keep skipping null bytes */ if (c!=0x09) state=0; *o++ = c; } } d(printf("%02x", c)); } if (lf) *o++='\n'; out = (char *)o; } break; case 4: { unsigned int *o = (unsigned int *)out; for (i=start;idata[i] & VTATTR_DATAMASK; if (state==0) { if (c==0x09) state=1; else if (c<32) c=' '; *o++ = c; } else { if (c) { /* in tab, keep skipping null bytes */ if (c!=0x09) state=0; *o++ = c; } } d(printf("%02x", c)); } if (lf) *o++='\n'; out = (char *)o; } break; case 1: default: { unsigned char *o = out; for (i=start;idata[i] & VTATTR_DATAMASK; if (state==0) { if (c==0x09) state=1; else if (c<32) c=' '; else if (c>0xff) c='?'; *o++ = c; } else { if (c) { /* in tab, keep skipping null bytes */ if (c!=0x09) state=0; *o++ = c; } } d(printf("%02x", c)); } if (lf) *o++='\n'; out = o; } break; } return out; } /* select block (sx,sy)-(ex,ey) from the buffer. Line '0' is the top of the screen, negative lines are part of the scrollback, positive lines are part of the visible screen. size is the size of each character. size = 1 -> char size = 2 -> 16 bit utf size = 4 -> 32 bit utf */ static char * vt_select_block(struct _vtx *vx, int size, int sx, int sy, int ex, int ey, int *len) { struct vt_line *wn, *nn; int line; char *out, *data; int tmp; if ( ((sy == ey) && (sx > ex)) || (sy > ey) ) { /* swap start/end values */ tmp = sx; sx=ex; ex=tmp; tmp = sy; sy=ey; ey=tmp; } /* makes a rough assumption about the buffer size needed */ if ( (data = g_malloc(size*(ey-sy+1)*(vx->vt.width+20)+1)) == 0 ) { *len = 0; printf("ERROR: Cannot g_malloc selection buffer\n"); return 0; } out = data; line = sy; wn = (struct vt_line *)vt_list_index((line<0)?(&vx->vt.scrollback):(&vx->vt.lines), line); if (wn) nn = wn->next; else { *len = 0; strcpy(data, ""); return data; } /* FIXME: check 'wn' exists ... */ /* only a single line selected? */ if (sy == ey) { out = vt_expand_line(wn, size, sx, ex, out); } else { /* scan lines */ while (nn && (linewidth, out); /* first line */ } else { out = vt_expand_line(wn, size, 0, wn->width, out); } line++; if (line==0) { /* wrapped into 'on screen' area? */ wn = (struct vt_line *)vt_list_index(&vx->vt.lines, 0); nn = wn->next; } else { wn = nn; nn = nn->next; } } /* last line (if it exists - shouldn't happen?) */ if (nn) out = vt_expand_line(wn, size, 0, ex, out); } *len = (out-data)/size; *out = 0; d(printf("selected text = \n"); fwrite(data, out-data, 1, stdout); printf("\n")); return data; } /* return currently selected text */ char *vt_get_selection(struct _vtx *vx, int size, int *len) { if (vx->selection_data) g_free(vx->selection_data); vx->selection_data = (uint32 *)vt_select_block(vx, size, vx->selstartx, vx->selstarty, vx->selendx, vx->selendy, &vx->selection_size); if (len) *len = vx->selection_size; return (char *)vx->selection_data; } /* clear the selection */ void vt_clear_selection(struct _vtx *vx) { if (vx->selection_data) { g_free(vx->selection_data); vx->selection_data = 0; vx->selection_size = 0; } } /* Draw part of the selection Called by vt_draw_selection */ static void vt_draw_selection_part(struct _vtx *vx, int sx, int sy, int ex, int ey) { int tmp; struct vt_line *l, *bl; int line; /* always draw top->bottom */ if (sy>ey) { tmp = sy;sy=ey;ey=tmp; tmp = sx;sx=ex;ex=tmp; } d(printf("selecting from (%d,%d) to (%d,%d)\n", sx, sy, ex, ey)); line = sy; if (line<0) { l = (struct vt_line *)vt_list_index(&vx->vt.scrollback, line); } else { l = (struct vt_line *)vt_list_index(&vx->vt.lines, line); } if ((line-vx->vt.scrollbackoffset)>=0) bl = (struct vt_line *)vt_list_index(&vx->vt.lines_back, line-vx->vt.scrollbackoffset); else bl = (struct vt_line *)vx->vt.lines_back.head; d(printf("selecting from (%d,%d) to (%d,%d)\n", sx, sy-vx->vt.scrollbackoffset, ex, ey-vx->vt.scrollbackoffset)); while ((line<=ey) && (l->next) && ((line-vx->vt.scrollbackoffset)vt.height)) { d(printf("line %d = %p ->next = %p\n", line, l, l->next)); if ((line-vx->vt.scrollbackoffset)>=0) { vt_line_update(vx, l, bl, line-vx->vt.scrollbackoffset, 0, 0, bl->width); bl=bl->next; if (bl->next==0) return; } line++; if (line==0) { l = (struct vt_line *)vt_list_index(&vx->vt.lines, 0); } else { l=l->next; } } } /* draws selection from vt->selstartx,vt->selstarty to vt->selendx,vt->selendy */ void vt_draw_selection(struct _vtx *vx) { /* draw in 2 parts difference between old start and new start difference between old end and new end */ /* dont draw nothin if we dont have to! */ if (vx->selendxold == vx->selstartxold && vx->selendx == vx->selstartx && vx->selendyold == vx->selstartyold && vx->selendy == vx->selstarty) return; d(printf("start ... \n")); vt_draw_selection_part(vx, vx->selstartx, vx->selstarty, vx->selstartxold, vx->selstartyold); d(printf("end ..\n")); vt_draw_selection_part(vx, vx->selendx, vx->selendy, vx->selendxold, vx->selendyold); vx->selendxold = vx->selendx; vx->selendyold = vx->selendy; vx->selstartxold = vx->selstartx; vx->selstartyold = vx->selstarty; } /* draw/undraw the cursor */ void vt_draw_cursor(struct _vtx *vx, int state) { uint32 attr; if (vx->vt.scrollbackold == 0 && vx->vt.cursorxvt.width) { attr = vx->vt.this_line->data[vx->vt.cursorx]; if (state) { /* must swap fore/background colour */ attr = (((attr & VTATTR_FORECOLOURM) >> VTATTR_FORECOLOURB) << VTATTR_BACKCOLOURB) | (((attr & VTATTR_BACKCOLOURM) >> VTATTR_BACKCOLOURB) << VTATTR_FORECOLOURB) | ( attr & ~(VTATTR_FORECOLOURM|VTATTR_BACKCOLOURM)); } vx->back_match=0; /* forces re-draw? */ vx->draw_text(vx->vt.user_data, vx->vt.this_line, vx->vt.cursory, vx->vt.cursorx, 1, attr); } } /* dummy rendering callbacks */ static void dummy_draw_text(void *user_data, struct vt_line *line, int row, int col, int len, int attr) { } static void dummy_scroll_area(void *user_data, int firstrow, int count, int offset, int fill) { } static int dummy_cursor_state(void *user_data, int state) { return 0; } struct _vtx *vtx_new(int width, int height, void *user_data) { struct _vtx *vx; int i; vx = g_malloc0(sizeof(struct _vtx)); /* initial settings */ vt_init(&vx->vt, width, height); vx->selection_data = 0; vx->selection_size = 0; vx->selected = 0; vx->selectiontype = VT_SELTYPE_NONE; vx->scroll_type = VT_SCROLL_ALWAYS; vx->scroll_area = dummy_scroll_area; vx->draw_text = dummy_draw_text; vx->cursor_state = dummy_cursor_state; /* other parameters initialised to 0 by calloc */ vx->vt.user_data = user_data; /* set '1' bits for all characters considered a 'word' */ for(i=0;i<256;i++) if (isalnum(i) || i=='_') vx->wordclass[i>>3] |= 1<<(i&7); return vx; } void vtx_destroy(struct _vtx *vx) { if (vx) { vt_destroy(&vx->vt); if (vx->selection_data) g_free(vx->selection_data); g_free(vx); } }