static char rcsid[] = "$Id: display.c,v 1.4 1992/10/13 01:22:10 mike Exp $"; /* $Log: display.c,v $ * Revision 1.4 1992/10/13 01:22:10 mike * - No need to #include `gem.h'. * * Revision 1.3 1992/10/02 22:31:40 mike * - Some changes for the Atari ST/TT version. * * Revision 1.2 1992/09/14 18:03:42 mike * - First set of changes for the Atari ST/TT version. * * Revision 1.1 1992/09/05 01:13:32 mike * Initial revision * */ /* * DISPLAY.C * * The functions in this file handle redisplay. There are two halves, the * ones that update the virtual display screen, and the ones that make the * physical display screen the same as the virtual display screen. These * functions use hints that are left in the windows by the commands. * * REVISION HISTORY: * ? Steve Wilhite, 1-Dec-85 - massive cleanup on code. * Craig Durland - more cleanup on code. */ /* Copyright 1990, 1991, 1992 Craig Durland * Distributed under the terms of the GNU General Public License. * Distributed "as is", without warranties of any kind, but comments, * suggestions and bug reports are welcome. */ #include #include "me2.h" #include "config.h" #ifdef atarist #include "stio.h" #endif #define HUGE 1000 /* Huge number */ extern char *strcpy(), *memset(); /* next are in terminal code so can set nice defaults */ extern int t_nrow, t_ncol, tcolor, mcolor; static char *vttext; /* pointer to current line on virtual screen */ static char *vscreen = NULL; /* Virtual screen */ #if !FASTVIDEO static char *pscreen; /* Physical screen */ #endif static int vrow_size, vtrow = 0, /* Row location of SW cursor */ vtcol = 0, /* Column location of SW cursor */ ttrow = HUGE, /* Row location of HW cursor */ ttcol = HUGE; /* Column location of HW cursor */ int curcol, /* current window cursor column */ currow, /* screen cursor row */ screen_is_garbage = TRUE, /* TRUE if screen is garbage */ mline_dirty = FALSE, /* !0 if message in last line */ hstep = 16, /* horizontal scroll step size */ od_hook, cd_hook; /* open-display-hook, close-display-hook */ void movecursor(); static void updateline(); /* video flags */ #define VFCHG 0x01 /* Line changed */ #define MLINE 0x02 /* this line is a mode line */ typedef struct { char flags; /* Video flags */ char text[1]; /* Screen data - actually t_ncol characters */ } Video; #define VSCREEN(row) ((Video *)(vscreen +(row)*vrow_size)) #define VFLAGS(row) VSCREEN(row)->flags /* vscreen[row].flags */ #define VTEXT(row) VSCREEN(row)->text /* vscreen[row].text */ #define PSCREEN(row) ((Video *)(pscreen +(row)*vrow_size)) #define PFLAGS(row) PSCREEN(row)->flags /* pscreen[row].flags */ #define PTEXT(row) PSCREEN(row)->text /* pscreen[row].text */ /* * Create and initialize the video data structures used by the display code * to maintain the display screen. vscreen and pscreen contain copies of * the screen. vscreen is the internal (virtual) copy that changes are * made to and pscreen is a copy of the physical (what the user sees) * screen. To make the physical screen look like the virtual screen, * "diff vscreen pscreen | patch terminal". * WARNING: * This rouine uses t_nrow and t_ncol. Before calling alloc_display(), * make sure they are set (by t_open() or open_display()). * You may need to update the window hints. * This whole mess depends on Video being a bunch of chars. If that * changes, expect a lot of other stuff to also change. * Data structures: * There is one Video per screen line. Each Video holds t_ncol characters. * The virtual and physical screens each have a Video array. * There is extra memory after the arrays for two more screen lines that * Led uses. * Have to malloc() 'cause I don't know the screen size until runtime * and may want to change the screen size on the fly. Strango * code since I want one malloc() to setup all the arrays. * Video macros above turn this block of memory into the Video arrays. */ void alloc_display() { char *ptr; int bytes, screen_size; if (vscreen) free((char *)vscreen); /* we've been here before */ vrow_size = t_ncol +1; /* bytes per Video */ bytes = screen_size = t_nrow *vrow_size; /* a screen full of Video */ #if !FASTVIDEO bytes *= 2; /* plus the physical screen */ #endif #if LED bytes += 2*t_ncol; /* plus Leds video lines */ #endif #if 0 /* ??? I should probably do this */ if (old_screen_size <= screen_size) { dont_malloc(); ptr = old_bytes } else { old_screen_size = screen_size; malloc() } #endif if ((ptr = malloc(bytes)) == NULL) { mlwrite("Cannot allocate Video!"); exit(1); } vscreen = ptr; #if !FASTVIDEO ptr += screen_size; pscreen = ptr; #endif #if LED { extern char *Lpline, *Lvline; extern int Lncol; Lvline = ptr +screen_size; Lpline = Lvline +t_ncol; Lncol = t_ncol -1; } #endif screen_is_garbage = TRUE; /* Probably need to redraw the screen */ } /* display_resized: Call this routine if the display has or will change * size. * Input: * Rows: The number of rows on the resized screen. * Columns: The number of columns on the resized screen. * Notes: * Some bounds checking is made to avoid a screen so small ME blows up * or a screen so big it uses up too much memory. * It might be a better idea to reject a resize request (but do the * redisplay) if the values are goofy. * Blech - I got some knowledge of window system guts in here. * It might be a good idea to do nothing in the case where the screen * size doesn't change but this routine is probably called about * never and I don't think this case will pop up. * I set the minimum screen size values bigger than necessary because * its just about impossible to edit at the smaller sizes. * WARNING * Only call this after ME has been fully initialized!?! * I might call this before the window system is initialized (so I can * sanity check what the display sez the rows and columns are) but * thats OK because curwp is NULL if the window system has not been * initialized (and onlywind() and wmunge() both no-op in that case * also). This check could be very important on windowing systems * where a wrong button press will create a 1x1 window and nuke ME. */ void display_resized(rows, columns) int rows, columns; { rows = imax(rows, 6); /* min: 3 */ columns = imax(columns, 39); /* min: ~10 */ rows = imin(rows, 400); columns = imin(columns, 500); rows--; /* leave a row for the message line */ #if 0 /* ??? do I care? */ if (vscreen && /* then the display system has been initialized */ (rows == t_nrow && columns == t_ncol)) return; #endif if (rows != t_nrow) /* screen length changed */ { if (curwp) /* only do this if the window system is up */ { onlywind(FALSE,1); curwp->w_ntrows = rows -1; /* new window length (-1 for modeline) */ } t_nrow = rows; /* new screen length */ } t_ncol = columns; /* set screen width */ alloc_display(); wmunge(WFHARD | WFMODE); } /* display_open is a state variable that is used to prevent calling * t_open() or t_close() multiple time in a row. This might happen * when we close the display (eg to fork a shell) and then get a * signal to terminate ME. */ static int display_open = FALSE; /* Make the display system ready. * WARNING! * This better be the only routine that calls t_open()! */ void open_display() { if (display_open) return; display_open = TRUE; t_open(); /* open the terminal (OS dependent) */ screen_is_garbage = TRUE; /* Screeen is probably garbage */ if (od_hook != -1) MMrun_pgm(od_hook); } /* * Clean up the virtual terminal system, in anticipation for a return to * the operating system. * If tidy_up, move down to the last line and clear it out (the next * system prompt will be written in the line). * Shut down the channel to the terminal. * WARNING! * This better be the only routine that calls t_close()! */ void close_display(tidy_up) { if (!display_open) return; display_open = FALSE; if (tidy_up) { movecursor(t_nrow,0,TRUE); t_eeol(); t_flush(); } if (cd_hook != -1) MMrun_pgm(cd_hook); t_close(); /* close the terminal (OS dependent) */ } /* The current line has been changed and is now a mode line */ void munged_mline() { VFLAGS(vtrow) |= (VFCHG | MLINE); } /* * Set the virtual cursor to the specified row and column on the virtual * screen. There is no checking for nonsense values; this might be a good * idea during the early stages. */ void vtmove(row,col) { vtrow = row; vtcol = col; vttext = VTEXT(row); } /* Write a character to the virtual screen. The virtual row and column are * updated. * Only printable characters are expected. * Returns: TRUE if all OK, FALSE if tried to print past margin. */ vtputc(c) register char c; { if (vtcol < t_ncol) { vttext[vtcol++] = c; return TRUE; } return FALSE; } void vtputs(str) register char *str; { register char c; while (c = *str++) vtputc(c); } /* Format a screen line: convert tabs to spaces, control characters to * printable. Right pad with blanks. Shift text left as needed to fit it * between the screen margins. You have to look at the text from the * beginning to ensure correct tab and control character expansion. */ #define ADD_CHAR(c) if (lmargin <= col++) *qtr++ = c static void vblast(ptr,len,lmargin) unsigned char *ptr; register int lmargin; { register unsigned char *qtr = (unsigned char *)vttext; register int col = 0; register unsigned char c; register int rmargin = lmargin +t_ncol; while (col < rmargin && len--) { c = *ptr++; if (iscntrl(c)) { if (c == '\t') /* expand tab */ { register int n = imin(rmargin, NEXT_TAB_COLUMN(col)); do { ADD_CHAR(' '); } while (col < n); continue; } ADD_CHAR('^'); /* LineFeed => "^J" */ c ^= 0x40; /* Convert ASCII LineFeed to "J" */ if (col == rmargin) continue; /* no room for the "J" */ /* fall though and put the "J" in the video */ } ADD_CHAR(c); } if (0 < len) *(qtr-1) = '$'; /* hit rmargin before wrote all of row */ else /* clear to end of line */ memset(qtr,' ',(col <= lmargin) ? t_ncol : rmargin -col); if (0 < lmargin) vttext[0] = '$'; } /* Clear the line containing the software cursor. */ void vtclear_line() { memset(vttext,' ',t_ncol); } /* Put a string to the physical screen and tell the virtual screen the line * has changed. */ void vpputs(row,col,str) int row, col; char *str; { t_move(row,col); t_eeol(); t_puts(str); VFLAGS(row) |= VFCHG; #if !FASTVIDEO strcpy(memset(&PTEXT(row)[col],' ',t_ncol-col),str); PFLAGS(row) = 0; #endif } /* * Make sure that the display is right. This is a three part process: * First, scan through all of the windows looking for dirty ones. Check * the framing, and refresh the screen. * Second, make sure that "currow" and "curcol" are correct for the * current window. * Third, make the virtual and physical screens the same. */ void update() { register Line *lp; register Window *wp; register int i, j, k; if (pending()) return; /* check to see if dot has moved beyond left or right screen edge */ /* find the cursor column */ curcol = getcol(curwp->dot.line,curwp->dot.offset); if (hstep != 0) /* check to see if have to horizontal scroll */ { k = curwp->lmargin; if (curcol < k) /* cursor is left of window margin */ { j = ((k -curcol +hstep -1)/hstep)*hstep; if ((k -= j) < 0) k = 0; curwp->w_flag |= WFHARD; } else if (curcol >= k+t_ncol) /* cursor is right of screen edge */ { j = ((curcol -(k +t_ncol) +hstep)/hstep)*hstep; k += j; curwp->w_flag |= WFHARD; } curwp->lmargin = k; } /* look at each window to see if it sez it needs updating */ for (wp = first_window; wp; wp = wp->nextw) { if (wp->w_flag) /* Look at windows with update flag(s) set */ { register Line *header_line = BUFFER_HEADER_LINE(wp->wbuffer); /* If not force reframe, check the framing */ if (!(wp->w_flag & WFFORCE)) { for (lp = wp->top_line, i = wp->w_ntrows; i--; lp = lforw(lp)) { if (lp == wp->dot.line) goto out; /* frame OK if dot on screen */ if (lp == header_line) break; /* EoB */ } } /* Dot is not visible - not acceptable. Better compute a new value * for the line at the top of the window. Then set the WFHARD flag * to force full redraw. */ i = wp->w_force; /* window line to put dot on */ if (i == 0) i = wp->w_ntrows/2; /* center dot */ else if (i > 0) i = imin(i -1, wp->w_ntrows -1); else i = imax(i +wp->w_ntrows, 0); lp = wp->dot.line; while (i != 0 && lback(lp) != header_line) { i--; lp = lback(lp); } wp->top_line = lp; wp->w_flag |= WFHARD; /* Force full redraw */ out: lp = wp->top_line; j = (i = wp->w_toprow) +wp->w_ntrows; /* windows screen lines */ k = wp->lmargin; /* If only EDIT is set, then only one line in the buffer has changed * and so the most we have to do is change is one screen line. If * it is not visible, we don't have to do nothin. */ if ((wp->w_flag & (WFEDIT | WFHARD)) == WFEDIT) { while (lp != header_line && lp != wp->elp && i < j) { i++; lp = lforw(lp); } if (i < j) /* changed line is on screen */ { VFLAGS(i) = VFCHG; /* !!! sleaze */ /*vscreen[i]->v_flag |= VFCHG; vscreen[i]->v_flag &= ~MLINE;*/ vtmove(i,0); vblast(lp->l_text,llength(lp),k); } } else if (wp->w_flag & (WFEDIT | WFHARD)) /* redraw the entire frame */ { while (i < j) { VFLAGS(i) = VFCHG; /* !!! sleaze */ /*vscreen[i]->v_flag |= VFCHG; vscreen[i]->v_flag &= ~MLINE;*/ vtmove(i,0); if (lp != header_line) { vblast(lp->l_text,llength(lp),k); lp = lforw(lp); } else vtclear_line(); ++i; } } #if !WFDEBUG if (wp->w_flag & WFMODE) modeline(wp); wp->w_flag = 0; wp->w_force = 0; #endif } /* end if */ #if WFDEBUG modeline(wp); wp->w_flag = 0; wp->w_force = 0; #endif } /* end for */ /* Always recompute the row and column number of the hardware cursor. * This is the only update for simple moves. */ lp = curwp->top_line; currow = curwp->w_toprow; while (lp != curwp->dot.line) { ++currow; lp = lforw(lp); } /* Special hacking if the screen is garbage. Clear the hardware screen, * and update your copy to agree with it. Set all the virtual screen * change bits, to force a full update. */ if (screen_is_garbage) { for (i = t_nrow; i--; ) { VFLAGS(i) |= VFCHG; #if !FASTVIDEO PFLAGS(i) = 0; memset(PTEXT(i),' ',t_ncol); #endif } movecursor(0,0,TRUE); t_eeop(); /* Erase the screen */ /* Erase-page clears the message area */ screen_is_garbage = mline_dirty = FALSE; } /* Make sure that the physical and virtual displays agree. * If a line is different between the two, send a new copy to the * screen. */ for (i = 0; i < t_nrow; ++i) { register Video *vline, *pline; vline = VSCREEN(i); if (vline->flags & VFCHG) { if (pending()) return; vline->flags &= ~VFCHG; #if !FASTVIDEO pline = PSCREEN(i); updateline(i, vline->text,vline->flags, pline->text,pline->flags); pline->flags = vline->flags; #else putline(i,vline->text,(vline->flags & MLINE) ? mcolor : tcolor); #endif } } #if !FASTVIDEO setcolor(tcolor,2); /* restore tcolor in case of minibuf write */ #endif /* Finally, update the hardware cursor and flush out buffers */ j = curcol -curwp->lmargin; if (t_ncol <= j) j = t_ncol -1; /* cursor off right side of screen */ else if (j < 0) j = 0; /* cursor off left side of screen */ movecursor(currow,j,FALSE); t_flush(); } #if !FASTVIDEO /* * Update a single line. This does not know how to use insert or delete * character sequences; we are using VT52 functionality. Update the * physical row and column variables. It does try to exploit erase to end * of line. */ static void updateline(row, vline,vflags, pline,pflags) int row, vflags, pflags; char *vline, *pline; { register char *cp1 = vline, *cp2 = pline, *cp4, *cp3 = &vline[t_ncol], *cp5 = cp3; register int i; int no_blanks, color = tcolor; if (tcolor != mcolor) /* mode line and text are different colors */ { if (vflags & MLINE) color = mcolor; /* this is a mode line */ /* if vline & pline diff color, gotta redraw entire line to recolor */ if ((vflags & MLINE) != (pflags & MLINE)) { movecursor(row,0,FALSE); setcolor(color,3); goto writeit; } } while (cp1 != cp5 && *cp1 == *cp2) { ++cp1; ++cp2; }/* Compute left match */ /* This can still happen, even though we only call this routine on * changed lines. A hard update is always done when a line splits, a * massive change is done, or a buffer is displayed twice. This * optimizes out most of the excess updating. A lot of computes are * used, but these tend to be hard operations that do a lot of update, * so I don't really care. */ if (cp1 == cp5) return; /* lines are equal */ /* Compute right match */ no_blanks = FALSE; cp4 = &pline[t_ncol]; while (cp3[-1] == cp4[-1]) { --cp3; --cp4; if (*cp3 != ' ') /* Note if any nonblank in right match */ no_blanks = TRUE; } cp5 = cp3; if (!no_blanks) /* Check to see if can use Erase to EoL */ { while (cp5 != cp1 && cp5[-1] == ' ') --cp5; if (cp3 -cp5 <= 3) cp5 = cp3; /* Use only if erase is fewer characters */ } movecursor(row,cp1-vline,FALSE); setcolor(color,2); writeit: #ifdef atarist t_putstr(cp1,cp5); i = cp5 - cp1; memcpy(cp2,cp1,i); cp2 += i; ttcol += i; cp1 = cp5; #else while (cp1 != cp5) /* left part of line */ { t_putchar(*cp1); ++ttcol; *cp2++ = *cp1++; } #endif if (cp5 != cp3) /* Erase */ { t_eeol(); while (cp1 != cp3) *cp2++ = *cp1++; } } #endif /* * Send a command to the terminal to move the hardware cursor to * (row,column). * The row and column arguments are origin 0. * Optimize out random calls. * Update "ttrow" and "ttcol". * Note: You should probably always force the cursor when moving around in * the minibuffer (last line on screen) because minibuffer routines write * there and don't keep us informed where the cursor is. */ void movecursor(row,col,force) int row, col, force; { if (force || row != ttrow || col != ttcol) { ttrow = row; ttcol = col; t_move(row, col); } }