/* * A Scrollable Text Output Window * * David Harrison * University of California, Berkeley * 1986 * * The following is an implementation for a scrollable text output * system. It handles exposure events only (other interactions are * under user control). For scrolling, a always present scroll bar * is implemented. It detects size changes and compensates accordingly. */ #include #include #include #include #include "scrollText.h" extern char *malloc(); extern char *realloc(); #define alloc(type) (type *) malloc(sizeof(type)) #define numalloc(type, num) (type *) malloc((unsigned) (num * sizeof(type))) #define MAXINT 2147483647 extern XAssocTable *XCreateAssocTable(); extern caddr_t XLookUpAssoc(); static XAssocTable *textWindows = (XAssocTable *) 0; #define NOOPTION -1 /* Option hasn't been set yet */ #define NORMSCROLL 0 /* Smooth scroll on LineToTop and TopToHere */ #define JUMPSCROLL 1 /* Jump scrolling on LineToTop and TopToHere */ static int ScrollOption = NOOPTION; typedef char *Generic; #define DEFAULT_GC textInfo->fontGC[textInfo->curFont] #define BARSIZE 15 #define BARBORDER 1 #define MAXFONTS 8 #define INITBUFSIZE 1024 #define INITLINES 50 #define INITEXPARY 50 #define XPADDING 2 #define YPADDING 2 #define INTERLINE 5 #define INTERSPACE 1 #define CURSORWIDTH 2 #define EXPANDPERCENT 40 #define BUFSIZE 1024 #define CUROFFSET 1 #define MAXFOREIGN 250 #define NOINDEX -1 /* The wrap line indicator */ #define WRAPINDSIZE 7 #define STEMOFFSET 5 #define arrow_width 7 #define arrow_height 5 static char arrow_bits[] = { 0x24, 0x26, 0x3f, 0x06, 0x04}; #define NEWLINE '\n' #define BACKSPACE '\010' #define NEWFONT '\006' #define LOWCHAR '\040' #define HIGHCHAR '\176' #define CHARMASK 0x00ff /* Character mask */ #define FONTMASK 0x0700 /* Character font */ #define FONTSHIFT 8 /* Shift amount */ #define WRAPFLAG 0x01 /* Line wrap flag */ /* * Lines are represented by a pointer into the overall array of * 16-bit characters. The lower eight bits is used to indicate the character * (in ASCII), and the next two bits are used to indicate the font * the character should be drawn in. */ typedef struct txtLine { int lineLength; /* Current line length */ int lineHeight; /* Full height of line in pixels */ int lineBaseLine; /* Current baseline of the line */ int lineWidth; /* Drawing position at end of line */ int lineText; /* Offset into master buffer */ int lineFlags; /* Line wrap flag is here */ }; /* * For ExposeCopy events, we queue up the redraw requests collapsing * them into line redraw requests until the CopyExpose event arrives. * The queue is represented as a dynamic array of the following * structure: */ typedef struct expEvent { int lineIndex; /* Index of line to redraw */ int ypos; /* Drawing position of line */ }; /* * The text buffer is represented using a dynamic counted array * of 16-bit quantities. This array expands as needed. * For the screen representation, a dynamic counted array * of line structures is used. This array points into the * text buffer to denote the start of each line and its parameters. * The windows are configured as one overall window which contains * the scroll bar as a sub-window along its right edge. Thus, * the text drawing space is actually w-BARSIZE. */ #define NOTATBOTTOM 0x01 /* Need to scroll to bottom before appending */ #define FONTNUMWAIT 0x02 /* Waiting for font number */ #define COPYEXPOSE 0x04 /* Need to process a copy expose event */ #define SCREENWRONG 0x08 /* TxtJamStr has invalidated screen contents */ typedef struct txtWin { /* Basic text buffer */ int bufAlloc; /* Allocated size of buffer */ int bufSpot; /* Current writing position in buffer */ short *mainBuffer; /* Main buffer of text */ /* Line information */ int numLines; /* Number of display lines in buffer */ int allocLines; /* Number of lines allocated */ struct txtLine **txtBuffer; /* Dynamic array of lines */ /* Current Window display information */ Window mainWindow; /* Text display window */ Window scrollBar; /* Subwindow for scroll bar */ Pixmap arrowMap; /* line wrap indicator */ int bgPix, fgPix; /* Background and cursor */ GC CursorGC; /* gc for the cursor */ GC bgGC; /* gc for erasing things */ GC fontGC[MAXFONTS]; /* gc for doing fonts */ XFontStruct theFonts[MAXFONTS];/* Display fonts */ int theColors[MAXFONTS]; /* foregrounds of the fonts */ int curFont; /* current font for tracking */ int w, h; /* Current size */ int startLine; /* Top line in display */ int endLine; /* Bottom line in display */ int bottomSpace; /* Space at bottom of screen */ int flagWord; /* If non-zero, not at end */ /* For handling ExposeCopy events */ int exposeSize; /* Current size of array */ int exposeAlloc; /* Allocated size */ struct expEvent **exposeAry;/* Array of line indices */ /* Drawing position information */ int curLine; /* Current line in buffer */ int curX; /* Current horizontal positi */ int curY; /* Current vertical drawing */ }; /* Flags for the various basic character handling functions */ #define DODISP 0x01 /* Update the display */ #define NONEWLINE 0x02 /* Dont append newline */ static int InitLine(newLine) struct txtLine *newLine; /* Newly created line structure */ /* * This routine initializes a newly created line structure. */ { newLine->lineLength = 0; newLine->lineHeight = 0; newLine->lineBaseLine = 0; newLine->lineWidth = XPADDING; newLine->lineText = NOINDEX; newLine->lineFlags = 0; return 1; } int TxtGrab(display, txtWin, program, mainFont, bg, fg, cur) Display *display; /* display window is on */ Window txtWin; /* Window to take over as scrollable text */ char *program; /* Program name for Xdefaults */ XFontStruct *mainFont; /* Primary text font */ int bg, fg, cur; /* Background, foreground, and cursor colors */ /* * This routine takes control of 'txtWin' and makes it into a scrollable * text output window. It will create a sub-window for the scroll bar * with a background of 'bg' and an bar with color 'fg'. Both fixed width * and variable width fonts are supported. Additional fonts can be loaded * using 'TxtAddFont'. Returns 0 if there were problems, non-zero if * everything went ok. */ { struct txtWin *newWin; /* Text package specific information */ XWindowAttributes winInfo; /* Window information */ int index; XGCValues gc_val; if (textWindows == (XAssocTable *) 0) { textWindows = XCreateAssocTable(32); if (textWindows == (XAssocTable *) 0) return(0); } if (XGetWindowAttributes(display, txtWin, &winInfo) == 0) return 0; if (ScrollOption == NOOPTION) { /* Read to see if the user wants jump scrolling or not */ if (XGetDefault(display, program, "JumpScroll")) { ScrollOption = JUMPSCROLL; } else { ScrollOption = NORMSCROLL; } } /* Initialize local structure */ newWin = alloc(struct txtWin); /* Initialize arrow pixmap */ newWin->arrowMap = XCreatePixmapFromBitmapData(display, txtWin, arrow_bits, arrow_width, arrow_height, cur, bg, DisplayPlanes(display, 0)); newWin->bufAlloc = INITBUFSIZE; newWin->bufSpot = 0; newWin->mainBuffer = numalloc(short, INITBUFSIZE); newWin->numLines = 1; newWin->allocLines = INITLINES; newWin->txtBuffer = numalloc(struct txtLine *, INITLINES); for (index = 0; index < INITLINES; index++) { newWin->txtBuffer[index] = alloc(struct txtLine); InitLine(newWin->txtBuffer[index]); } /* Window display information */ newWin->mainWindow = txtWin; newWin->w = winInfo.width; newWin->h = winInfo.height; newWin->startLine = 0; newWin->endLine = 0; newWin->bottomSpace = winInfo.height - YPADDING - mainFont->ascent - mainFont->descent - INTERLINE; newWin->flagWord = 0; newWin->bgPix = bg; newWin->fgPix = fg; /* Scroll Bar Creation */ newWin->scrollBar = XCreateSimpleWindow(display, txtWin, winInfo.width - BARSIZE, 0, BARSIZE - (2*BARBORDER), winInfo.height - (2*BARBORDER), BARBORDER, fg, bg); XSelectInput(display, newWin->scrollBar, ExposureMask|ButtonReleaseMask); XMapRaised(display, newWin->scrollBar); /* Font and Color Initialization */ newWin->theFonts[0] = *mainFont; newWin->theColors[0] = fg; gc_val.function = GXcopy; gc_val.plane_mask = AllPlanes; gc_val.foreground = fg; gc_val.background = bg; gc_val.graphics_exposures = 1; gc_val.font = mainFont->fid; gc_val.line_width = 1; gc_val.line_style = LineSolid; newWin->fontGC[0] = XCreateGC(display, txtWin, GCFunction | GCPlaneMask | GCForeground | GCBackground | GCGraphicsExposures | GCFont, &gc_val); gc_val.foreground = cur; newWin->CursorGC = XCreateGC(display, txtWin, GCFunction | GCPlaneMask | GCForeground | GCBackground | GCLineStyle | GCLineWidth, &gc_val); gc_val.foreground = bg; newWin->bgGC = XCreateGC(display, txtWin, GCFunction | GCPlaneMask | GCForeground | GCBackground | GCGraphicsExposures | GCFont, &gc_val); for (index = 1; index < MAXFONTS; index++) { newWin->theFonts[index].fid = 0; newWin->fontGC[index] = 0; } /* Initialize size of first line */ newWin->txtBuffer[0]->lineHeight = newWin->theFonts[0].ascent + newWin->theFonts[0].descent; newWin->txtBuffer[0]->lineText = 0; /* ExposeCopy array initialization */ newWin->exposeSize = 0; newWin->exposeAlloc = INITEXPARY; newWin->exposeAry = numalloc(struct expEvent *, INITEXPARY); for (index = 0; index < newWin->exposeAlloc; index++) newWin->exposeAry[index] = alloc(struct expEvent); /* Put plus infinity in last slot for sorting purposes */ newWin->exposeAry[0]->lineIndex = MAXINT; /* Drawing Position Information */ newWin->curLine = 0; newWin->curX = 0; newWin->curY = YPADDING + mainFont->ascent + mainFont->descent; /* Attach it to both windows */ XMakeAssoc(display, textWindows, (XID) txtWin, (caddr_t) newWin); XMakeAssoc(display, textWindows, (XID) newWin->scrollBar, (caddr_t) newWin); return 1; } int TxtRelease(display, w) Display *display; Window w; /* Window to release */ /* * This routine releases all resources associated with the * specified window which are consumed by the text * window package. This includes the entire text buffer, line start * array, and the scroll bar window. However, the window * itself is NOT destroyed. The routine will return zero if * the window is not owned by the text window package. */ { struct txtWin *textInfo; int index; if ((textInfo = (struct txtWin *) XLookUpAssoc(display, textWindows, (XID) w)) == 0) return 0; for (index = 0; index < MAXFONTS; index++) if (textInfo->fontGC[index] != 0) XFreeGC(display, textInfo->fontGC[index]); free((Generic) textInfo->mainBuffer); for (index = 0; index < textInfo->numLines; index++) { free((Generic) textInfo->txtBuffer[index]); } free((Generic) textInfo->txtBuffer); XDestroyWindow(display, textInfo->scrollBar); for (index = 0; index < textInfo->exposeSize; index++) { free((Generic) textInfo->exposeAry[index]); } free((Generic) textInfo->exposeAry); XDeleteAssoc(display, textWindows, (XID) w); free((Generic) textInfo); return 1; } static int RecompBuffer(textInfo) struct txtWin *textInfo; /* Text window information */ /* * This routine recomputes all line breaks in a buffer after * a change in window size or font. This is done by throwing * away the old line start array and recomputing it. Although * a lot of this work is also done elsewhere, it has been included * inline here for efficiency. */ { int startPos, endSize, linenum; register int index, chsize, curfont; register short *bufptr; register XFontStruct *fontptr; register struct txtLine *lineptr; char theChar; /* Record the old position so we can come back to it */ for (startPos = textInfo->txtBuffer[textInfo->startLine]->lineText; (startPos > 0) && (textInfo->mainBuffer[startPos] != '\n'); startPos--) /* null loop body */; /* Clear out the old line start array */ for (index = 0; index < textInfo->numLines; index++) { InitLine(textInfo->txtBuffer[index]); } /* Initialize first line */ textInfo->txtBuffer[0]->lineHeight = textInfo->theFonts[0].ascent + textInfo->theFonts[0].descent; textInfo->txtBuffer[0]->lineText = 0; /* Process the text back into lines */ endSize = textInfo->w - BARSIZE - WRAPINDSIZE; bufptr = textInfo->mainBuffer; lineptr = textInfo->txtBuffer[0]; linenum = 0; fontptr = &(textInfo->theFonts[0]); curfont = 0; for (index = 0; index < textInfo->bufSpot; index++) { theChar = bufptr[index] & CHARMASK; if ((bufptr[index] & FONTMASK) != curfont) { int newFontNum, heightDiff; /* Switch fonts */ newFontNum = (bufptr[index] & FONTMASK) >> FONTSHIFT; if (textInfo->theFonts[newFontNum].fid != 0) { /* Valid font */ curfont = bufptr[index] & FONTMASK; fontptr = &(textInfo->theFonts[newFontNum]); heightDiff = (fontptr->ascent + fontptr->descent) - lineptr->lineHeight; if (heightDiff < 0) heightDiff = 0; lineptr->lineHeight += heightDiff; } } if (theChar == '\n') { /* Handle new line */ if (linenum >= textInfo->allocLines-1) /* Expand number of lines */ ExpandLines(textInfo); linenum++; lineptr = textInfo->txtBuffer[linenum]; /* Initialize next line */ lineptr->lineHeight = fontptr->ascent + fontptr->descent; lineptr->lineText = index+1; /* Check to see if its the starting line */ if (index == startPos) textInfo->startLine = linenum; } else { /* Handle normal character */ chsize = CharSize(textInfo, linenum, index); if (lineptr->lineWidth + chsize > endSize) { /* Handle line wrap */ lineptr->lineFlags |= WRAPFLAG; if (linenum >= textInfo->allocLines-1) /* Expand number of lines */ ExpandLines(textInfo); linenum++; lineptr = textInfo->txtBuffer[linenum]; /* Initialize next line */ lineptr->lineHeight = fontptr->ascent + fontptr->descent; lineptr->lineText = index; lineptr->lineLength = 1; lineptr->lineWidth += chsize; } else { /* Handle normal addition of character */ lineptr->lineLength += 1; lineptr->lineWidth += chsize; } } } /* We now have a valid line array. Let's clean up some other fields. */ textInfo->numLines = linenum+1; if (startPos == 0) { textInfo->startLine = 0; } textInfo->endLine = FindEndLine(textInfo, &(textInfo->bottomSpace)); textInfo->curLine = linenum; /* Check to see if we are at the bottom */ if (textInfo->endLine >= textInfo->numLines-1) { textInfo->curY = textInfo->h - textInfo->bottomSpace - lineptr->lineHeight; textInfo->flagWord &= (~NOTATBOTTOM); } else { textInfo->flagWord |= NOTATBOTTOM; } return 1; } int TxtAddFont(display, textWin, fontNumber, newFont, newColor) Display *display; Window textWin; /* Scrollable text window */ int fontNumber; /* Place to add font (0-7) */ XFontStruct *newFont; /* Font to add */ int newColor; /* Color of font */ /* * This routine loads a new font so that it can be used in a previously * created text window. There are eight font slots numbered 0 through 7. * If there is already a font in the specified slot, it will be replaced * and an automatic redraw of the window will take place. See TxtWriteStr * for details on using alternate fonts. The color specifies the foreground * color of the text. The default foreground color is used if this * parameter is TXT_NO_COLOR. Returns a non-zero value if * everything went well. */ { struct txtWin *textInfo; int redrawFlag; XGCValues gc_val; if ((fontNumber < 0) || (fontNumber >= MAXFONTS)) return 0; if ((textInfo = (struct txtWin *) XLookUpAssoc(display, textWindows, (XID) textWin)) == 0) return 0; if (newColor == TXT_NO_COLOR) { newColor = textInfo->fgPix; } gc_val.font = newFont->fid; gc_val.foreground = newColor; gc_val.background = textInfo->bgPix; gc_val.plane_mask = AllPlanes; gc_val.graphics_exposures = 1; gc_val.function = GXcopy; if (textInfo->fontGC[fontNumber] != 0) { XChangeGC(display, textInfo->fontGC[fontNumber], GCFont | GCForeground, &gc_val); } else textInfo->fontGC[fontNumber] = XCreateGC(display, textWin, GCFont | GCForeground | GCBackground | GCFunction | GCPlaneMask | GCGraphicsExposures, &gc_val); redrawFlag = (textInfo->theFonts[fontNumber].fid != 0) && (((newFont) && (newFont->fid != textInfo->theFonts[fontNumber].fid)) || (newColor != textInfo->theColors[fontNumber])); if (newFont) { textInfo->theFonts[fontNumber] = *newFont; } textInfo->theColors[fontNumber] = newColor; if (redrawFlag) { RecompBuffer(textInfo); XClearWindow(display, textWin); TxtRepaint(display, textWin); } return 1; } int TxtWinP(display, w) Display *display; Window w; /* * Returns a non-zero value if the window has been previously grabbed * using TxtGrab and 0 if it has not. */ { if (XLookUpAssoc(display, textWindows, (XID) w)) return(1); else return(0); } static int FindEndLine(textInfo, botSpace) struct txtWin *textInfo; int *botSpace; /* * Given the starting line in 'textInfo->startLine', this routine * determines the index of the last line that can be drawn given the * current size of the screen. If there are not enough lines to * fill the screen, the index of the last line will be returned. * The amount of empty bottom space is returned in 'botSpace'. */ { int index, height, lineHeight; height = YPADDING; index = textInfo->startLine; while (index < textInfo->numLines) { lineHeight = textInfo->txtBuffer[index]->lineHeight + INTERLINE; if (height + lineHeight > textInfo->h) break; height += lineHeight; index++; } if (botSpace) { *botSpace = textInfo->h - height; } return index - 1; } static int UpdateScroll(display, textInfo) Display *display; struct txtWin *textInfo; /* Text window information */ /* * This routine computes the current extent of the scroll bar * indicator and repaints the bar with the correct information. */ { int top, bottom; if (textInfo->numLines > 1) { top = textInfo->startLine * (textInfo->h - 2*BARBORDER) / (textInfo->numLines - 1); bottom = textInfo->endLine * (textInfo->h - 2*BARBORDER) / (textInfo->numLines - 1); } else { top = 0; bottom = textInfo->h - (2*BARBORDER); } /* Draw it - make sure there is a little padding */ if (top == 0) top++; if (bottom == textInfo->h-(2*BARBORDER)) bottom--; XFillRectangle(display, textInfo->scrollBar, textInfo->bgGC, 0, 0, BARSIZE, top-1); XFillRectangle(display, textInfo->scrollBar, DEFAULT_GC, top, BARSIZE - (2*BARBORDER) - 2, bottom - top); XFillRectangle(display, textInfo->scrollBar, DEFAULT_GC, 0, bottom+1, BARSIZE, textInfo->h - (2 * BARBORDER) - bottom); return 1; } int TxtClear(display, w) Display *display; Window w; /* * This routine clears a scrollable text window. It resets the current * writing position to the upper left hand corner of the screen. * NOTE: THIS ALSO CLEARS THE CONTENTS OF THE TEXT WINDOW BUFFER AND * RESETS THE SCROLL BAR. Returns 0 if the window is not a text window. * This should be used *instead* of XClear. */ { struct txtWin *textInfo; int index; if ((textInfo = (struct txtWin *) XLookUpAssoc(display, textWindows, (XID) w)) == 0) return 0; /* Zero out the arrays */ textInfo->bufSpot = 0; for (index = 0; index < textInfo->numLines; index++) { InitLine(textInfo->txtBuffer[index]); } textInfo->txtBuffer[0]->lineHeight = textInfo->theFonts[textInfo->curFont].ascent + textInfo->theFonts[textInfo->curFont].descent; textInfo->numLines = 1; textInfo->startLine = 0; textInfo->endLine = 0; textInfo->curLine = 0; textInfo->curX = 0; textInfo->curY = YPADDING + textInfo->theFonts[textInfo->curFont].ascent + textInfo->theFonts[textInfo->curFont].descent; textInfo->bottomSpace = textInfo->h - YPADDING - textInfo->theFonts[textInfo->curFont].ascent - INTERLINE - textInfo->theFonts[textInfo->curFont].descent; /* Actually clear the window */ XClearWindow(display, w); /* Draw the current cursor */ XFillRectangle(display, w, textInfo->CursorGC, XPADDING + CUROFFSET, textInfo->curY, CURSORWIDTH, textInfo->theFonts[textInfo->curFont].ascent + textInfo->theFonts[textInfo->curFont].descent); /* Update the scroll bar */ UpdateScroll(display, textInfo); return 1; } static int WarpToBottom(display, textInfo) Display *display; struct txtWin *textInfo; /* Text Information */ /* * This routine causes the specified text window to display its * last screen of information. It updates the scroll bar * to the appropriate spot. The implementation scans backward * through the buffer to find an appropriate starting spot for * the window. */ { int index, height, lineHeight; index = textInfo->numLines-1; height = 0; while (index >= 0) { lineHeight = textInfo->txtBuffer[index]->lineHeight + INTERLINE; if (height + lineHeight > textInfo->h) break; height += lineHeight; index--; } textInfo->startLine = index + 1; textInfo->endLine = FindEndLine(textInfo, &(textInfo->bottomSpace)); textInfo->curY = textInfo->h - textInfo->bottomSpace - textInfo->txtBuffer[textInfo->endLine]->lineHeight; XClearWindow(display, textInfo->mainWindow); TxtRepaint(display, textInfo->mainWindow); return 1; } static int UpdateExposures(display, textInfo) Display *display; struct txtWin *textInfo; /* Text window information */ /* * Before a new scrolling action occurs, the text window package * must handle all COPYEXPOSE events generated by the last scrolling * action. This routine is called to do this. Foreign events (those * not handled by TxtFilter) are queued up and replaced on the queue * after the processing of the exposure events is complete. */ { #if 0 XEvent foreignQueue[MAXFOREIGN]; int index, lastItem = 0; while (textInfo->flagWord & COPYEXPOSE) { XNextEvent(display, &(foreignQueue[lastItem])); if (!TxtFilter(display, &(foreignQueue[lastItem]))) lastItem++; if (lastItem >= MAXFOREIGN) { printf("Too many foreign events to queue!\n"); textInfo->flagWord &= (~COPYEXPOSE); } } for (index = 0; index < lastItem; index++) { XPutBackEvent(display, &(foreignQueue[index])); } #endif return 1; } static int ScrollDown(display,textInfo) Display *display; struct txtWin *textInfo; /* Text window information */ /* * This routine scrolls the indicated text window down by one * line. The line below the current line must exist. The window * is scrolled so that the line below the last line is fully * displayed. This may cause many lines to scroll off the top. * Scrolling is done using XCopyArea. The exposure events should * be caught using ExposeCopy. */ { int lineSum, index, targetSpace, freeSpace, updateFlag; lineSum = 0; if (textInfo->endLine + 1 >= textInfo->numLines) return 0; targetSpace = textInfo->txtBuffer[textInfo->endLine+1]->lineHeight + INTERLINE; if (textInfo->bottomSpace < targetSpace) { index = textInfo->startLine; while (index < textInfo->endLine) { lineSum += (textInfo->txtBuffer[index]->lineHeight + INTERLINE); if (textInfo->bottomSpace + lineSum >= targetSpace) break; index++; } /* Must move upward by 'lineSum' pixels */ XCopyArea(display, textInfo->mainWindow, textInfo->mainWindow, DEFAULT_GC, 0, lineSum, textInfo->w - BARSIZE, textInfo->h, 0, 0); textInfo->flagWord |= COPYEXPOSE; /* Repair the damage to the structures */ textInfo->startLine = index + 1; updateFlag = 1; } else { updateFlag = 0; } /* More lines might be able to fit. Let's check. */ freeSpace = textInfo->bottomSpace + lineSum - targetSpace; index = textInfo->endLine + 1; while (index < textInfo->numLines-1) { if (freeSpace - textInfo->txtBuffer[index+1]->lineHeight - INTERLINE < 0) break; freeSpace -= (textInfo->txtBuffer[index+1]->lineHeight + INTERLINE); index++; } textInfo->endLine = index; textInfo->bottomSpace = freeSpace; if (updateFlag) { UpdateExposures(display, textInfo); } UpdateScroll(display, textInfo); return 1; } static int ExpandLines(textInfo) struct txtWin *textInfo; /* Text Information */ /* * This routine allocates and initializes additional space in * the line start array (txtBuffer). The new space * is allocated using realloc. The expansion factor is a percentage * given by EXPANDPERCENT. */ { int newSize, index; newSize = textInfo->allocLines; newSize += (newSize * EXPANDPERCENT) / 100; textInfo->txtBuffer = (struct txtLine **) realloc((char *) textInfo->txtBuffer, (unsigned) (newSize * sizeof(struct txtLine *))); for (index = textInfo->allocLines; index < newSize; index++) { textInfo->txtBuffer[index] = alloc(struct txtLine); InitLine(textInfo->txtBuffer[index]); } textInfo->allocLines = newSize; return 1; } static int ExpandBuffer(textInfo) struct txtWin *textInfo; /* Text information */ /* * Expands the basic character buffer using realloc. The expansion * factor is a percentage given by EXPANDPERCENT. */ { int newSize; newSize = textInfo->bufAlloc + (textInfo->bufAlloc * EXPANDPERCENT) / 100; textInfo->mainBuffer = (short *) realloc((char *) textInfo->mainBuffer, (unsigned) newSize * sizeof(short)); textInfo->bufAlloc = newSize; return 1; } static int HandleNewLine(display, textInfo, flagWord) Display *display; struct txtWin *textInfo; /* Text Information */ int flagWord; /* DODISP or NONEWLINE or both */ /* * This routine initializes the next line for drawing by setting * its height to the current font height, scrolls the screen down * one line, and updates the current drawing position to the * left edge of the newly cleared line. If DODISP is specified, * the screen will be updated (otherwise not). If NONEWLINE is * specified, no newline character will be added to the text buffer * (this is for line wrap). */ { struct txtLine *curLine, *nextLine; /* Check to see if a new line must be allocated */ if (textInfo->curLine >= textInfo->allocLines-1) /* Expand the number of lines */ ExpandLines(textInfo); textInfo->numLines += 1; /* Then we initialize the next line */ nextLine = textInfo->txtBuffer[textInfo->numLines-1]; nextLine->lineHeight = textInfo->theFonts[textInfo->curFont].ascent + textInfo->theFonts[textInfo->curFont].descent; curLine = textInfo->txtBuffer[textInfo->curLine]; if (flagWord & DODISP) { /* Scroll down a line if required */ if ((textInfo->curY + curLine->lineHeight + nextLine->lineHeight + (INTERLINE * 2)) > textInfo->h) { ScrollDown(display, textInfo); } else { /* Update the bottom space appropriately */ textInfo->bottomSpace -= (nextLine->lineHeight + INTERLINE); textInfo->endLine += 1; } /* Update drawing position */ textInfo->curY = textInfo->h - (textInfo->bottomSpace + nextLine->lineHeight); } /* Move down a line */ textInfo->curLine += 1; if (!(flagWord & NONEWLINE)) { /* Append end-of-line to text buffer */ if (textInfo->bufSpot >= textInfo->bufAlloc) { /* Allocate more space in main text buffer */ ExpandBuffer(textInfo); } textInfo->mainBuffer[(textInfo->bufSpot)++] = (textInfo->curFont << FONTSHIFT) | '\n'; } nextLine->lineText = textInfo->bufSpot; textInfo->curX = 0; return 1; } static int CharSize(textInfo, lineNum, charNum) struct txtWin *textInfo; /* Current Text Information */ int lineNum; /* Line in buffer */ int charNum; /* Character in line */ /* * This routine determines the size of the specified character. * It takes in account the font of the character and whether its * fixed or variable. The size includes INTERSPACE spacing between * the characters. */ { register XFontStruct *charFont; register short *theLine; register short theChar; theLine = &(textInfo->mainBuffer[textInfo->txtBuffer[lineNum]->lineText]); theChar = theLine[charNum] & CHARMASK; charFont = &(textInfo->theFonts[(theChar & FONTMASK) >> FONTSHIFT]); if (theChar <= charFont->min_char_or_byte2 || theChar >= charFont->max_char_or_byte2 || charFont->per_char == 0) return charFont->max_bounds.width + 1; else return charFont->per_char[theChar].width + 1; } static int HandleBackspace(display, textInfo, flagWord) Display *display; struct txtWin *textInfo; /* Text Information */ int flagWord; /* DODISP or nothing */ /* * This routine handles a backspace found in the input stream. The * character before the current writing position will be erased and * the drawing position will move back one character. If the writing * position is at the left margin, the drawing position will move * up to the previous line. If it is a line that has been wrapped, * the character at the end of the previous line will be erased. */ { struct txtLine *thisLine, *prevLine; int chSize; thisLine = textInfo->txtBuffer[textInfo->curLine]; /* First, determine whether we need to go back a line */ if (thisLine->lineLength == 0) { /* Bleep if at top of buffer */ if (textInfo->curLine == 0) { XBell(display, 50); return 0; } /* See if we have to scroll in the other direction */ if ((flagWord & DODISP) && (textInfo->curY <= YPADDING)) { /* This will display the last lines of the buffer */ WarpToBottom(display, textInfo); } /* Set drawing position at end of previous line */ textInfo->curLine -= 1; prevLine = textInfo->txtBuffer[textInfo->curLine]; textInfo->numLines -= 1; if (flagWord & DODISP) { textInfo->curY -= (prevLine->lineHeight + INTERLINE); textInfo->bottomSpace += (thisLine->lineHeight + INTERLINE); textInfo->endLine -= 1; } /* We are unlinewrapping if the previous line has flag set */ if (prevLine->lineFlags & WRAPFLAG) { /* Get rid of line wrap indicator */ if (flagWord & DODISP) { XFillRectangle(display, textInfo->mainWindow, textInfo->bgGC, textInfo->w - BARSIZE - WRAPINDSIZE, textInfo->curY, WRAPINDSIZE, prevLine->lineHeight); } prevLine->lineFlags &= (~WRAPFLAG); /* Call recursively to wipe out the ending character */ HandleBackspace(display, textInfo, flagWord); } else { /* Delete the end-of-line in the primary buffer */ textInfo->bufSpot -= 1; } } else { /* Normal deletion of character */ chSize = CharSize(textInfo, textInfo->curLine, textInfo->txtBuffer[textInfo->curLine]->lineLength - 1); /* Move back appropriate amount and wipe it out */ thisLine->lineWidth -= chSize; if (flagWord & DODISP) { XFillRectangle(display, textInfo->mainWindow, textInfo->bgGC, thisLine->lineWidth, textInfo->curY, chSize, thisLine->lineHeight); } /* Delete from buffer */ textInfo->txtBuffer[textInfo->curLine]->lineLength -= 1; textInfo->bufSpot -= 1; } return 1; } static int DrawLineWrap(display, win, x, y, h, col) Display *display; Window win; /* What window to draw it in */ int x, y; /* Position of upper left corner */ int h; /* Height of indicator */ int col; /* Color of indicator */ /* * This routine draws a line wrap indicator at the end of a line. * Visually, it is an arrow of the specified height directly against * the scroll bar border. The bitmap used for the arrow is stored * in 'arrowMap' with size 'arrow_width' and 'arrow_height'. */ { struct txtWin *textInfo; textInfo = (struct txtWin *)XLookUpAssoc(display, textWindows, (XID) win); /* First, draw the arrow */ XCopyArea(display, textInfo->arrowMap, textInfo->mainWindow, textInfo->CursorGC, 0, 0, arrow_width, arrow_height, x, y + h - arrow_height, 1); /* Then draw the stem */ XDrawLine(display, textInfo->mainWindow, textInfo->CursorGC, x + STEMOFFSET, y, x + STEMOFFSET, y + h - arrow_height); return 1; } static int DrawLine(display, textInfo, lineIndex, ypos) Display *display; struct txtWin *textInfo; /* Text window information */ int lineIndex; /* Index of line to draw */ int ypos; /* Y position for line */ /* * This routine destructively draws the indicated line in the * indicated window at the indicated position. It does not * clear to end of line however. It draws a line wrap indicator * if needed but does not draw a cursor. */ { int index, startPos, curFont, theColor, curX, saveX, fontIndex; struct txtLine *someLine; char lineBuffer[BUFSIZE], *glyph; short *linePointer; XFontStruct *theFont; XGCValues gc; /* First, we draw the text */ index = 0; curX = XPADDING; someLine = textInfo->txtBuffer[lineIndex]; linePointer = &(textInfo->mainBuffer[someLine->lineText]); while (index < someLine->lineLength) { startPos = index; saveX = curX; curFont = linePointer[index] & FONTMASK; fontIndex = curFont >> FONTSHIFT; theFont = &(textInfo->theFonts[fontIndex]); theColor = textInfo->theColors[fontIndex]; glyph = &(lineBuffer[0]); while ((index < someLine->lineLength) && ((linePointer[index] & FONTMASK) == curFont)) { *glyph = linePointer[index] & CHARMASK; index++; curX += CharSize(textInfo, lineIndex, index); glyph++; } /* Flush out the glyphs */ XFillRectangle(display, textInfo->mainWindow, textInfo->bgGC, saveX, ypos, textInfo->w - BARSIZE, someLine->lineHeight + YPADDING + INTERLINE); XDrawString(display, textInfo->mainWindow, textInfo->fontGC[fontIndex], saveX, ypos, lineBuffer, someLine->lineLength); } /* Then the line wrap indicator (if needed) */ if (someLine->lineFlags & WRAPFLAG) { DrawLineWrap(display, textInfo->mainWindow, textInfo->w - BARSIZE - WRAPINDSIZE, ypos, someLine->lineHeight, textInfo->fgPix); } return 1; } static int HandleNewFont(display, fontNum, textInfo, flagWord) Display *display; int fontNum; /* Font number */ struct txtWin *textInfo; /* Text information */ int flagWord; /* DODISP or nothing */ /* * This routine handles a new font request. These requests take * the form "^F". The parsing is done in TxtWriteStr. * This routine is called only if the form is valid. It may return * a failure (0 status) if the requested font is not loaded. * If the new font is larger than any of the current * fonts on the line, it will change the line height and redisplay * the line. */ { struct txtLine *thisLine; int heightDiff, baseDiff, redrawFlag; if (textInfo->theFonts[fontNum].fid == 0) { return 0; } else { thisLine = textInfo->txtBuffer[textInfo->curLine]; textInfo->curFont = fontNum; redrawFlag = 0; heightDiff = textInfo->theFonts[fontNum].ascent + textInfo->theFonts[fontNum].descent - thisLine->lineHeight; if (heightDiff > 0) { redrawFlag = 1; } else { heightDiff = 0; } if (redrawFlag) { if (flagWord & DODISP) { /* Clear current line */ XFillRectangle(display, textInfo->mainWindow, textInfo->bgGC, 0, textInfo->curY, textInfo->w, thisLine->lineHeight); /* Check to see if it requires scrolling */ if ((textInfo->curY + thisLine->lineHeight + heightDiff + INTERLINE) > textInfo->h) { /* * General approach: "unscroll" the last line up * and then call ScrollDown to do the right thing. */ textInfo->endLine -= 1; textInfo->bottomSpace += thisLine->lineHeight + INTERLINE; XFillRectangle(display, textInfo->mainWindow, textInfo->bgGC, 0, textInfo->h - textInfo->bottomSpace, textInfo->w, textInfo->bottomSpace); thisLine->lineHeight += heightDiff; ScrollDown(display, textInfo); textInfo->curY = textInfo->h - (textInfo->bottomSpace + INTERLINE + thisLine->lineHeight); } else { /* Just update bottom space */ textInfo->bottomSpace -= heightDiff; thisLine->lineHeight += heightDiff; } /* Redraw the current line */ DrawLine(display, textInfo, textInfo->curLine, textInfo->curY); } else { /* Just update line height */ thisLine->lineHeight += heightDiff; } } return 1; } } int TxtWriteStr(display, w, str) Display *display; Window w; /* Text window */ register char *str; /* 0 terminated string */ /* * This routine writes a string to the specified text window. * The following notes apply: * - Text is always appended to the end of the text buffer. * - If the scroll bar is positioned such that the end of the * text is not visible, an automatic scroll to the bottom * will be done before the appending of text. * - Non-printable ASCII characters are not displayed. * - The '\n' character causes the current text position to * advance one line and start at the left. * - Tabs are not supported. * - Lines too long for the screen will be wrapped and a line wrap * indication will be drawn. * - Backspace clears the previous character. It will do the right * thing if asked to backspace past a wrapped line. * - A new font can be chosen using the sequence '^F' where * is 0-7. The directive will be ignored if * there is no font in the specified slot. * Returns 0 if something went wrong. */ { register int fontIndex; register struct txtWin *textInfo; register struct txtLine *thisLine; if ((textInfo = (struct txtWin *) XLookUpAssoc(display, textWindows, (XID) w)) == 0) return 0; /* See if screen needs to be updated */ if (textInfo->flagWord & SCREENWRONG) { TxtRepaint(display, textInfo->mainWindow); } /* See if we have to scroll down to the bottom */ if (textInfo->flagWord & NOTATBOTTOM) { WarpToBottom(display, textInfo); textInfo->flagWord &= (~NOTATBOTTOM); } /* Undraw the current cursor */ thisLine = textInfo->txtBuffer[textInfo->curLine]; XFillRectangle(display, w, textInfo->bgGC, thisLine->lineWidth + CUROFFSET, textInfo->curY, CURSORWIDTH, thisLine->lineHeight); for ( /* str is ok */ ; (*str != 0) ; str++) { /* Check to see if we are waiting on a font */ if (textInfo->flagWord & FONTNUMWAIT) { textInfo->flagWord &= (~FONTNUMWAIT); fontIndex = *str - '0'; if ((fontIndex >= 0) && (fontIndex < MAXFONTS)) { /* Handle font -- go get next character */ if (HandleNewFont(display, fontIndex, textInfo, DODISP)) continue; } } /* Inline code for handling normal character case */ if ((*str >= LOWCHAR) && (*str <= HIGHCHAR)) { register XFontStruct *thisFont; register struct txtLine *thisLine; register int charWidth; int thisColor; /* Determine size of character */ thisFont = &(textInfo->theFonts[textInfo->curFont]); thisColor = textInfo->theColors[textInfo->curFont]; if (*str <= thisFont->min_char_or_byte2 || *str >= thisFont->max_char_or_byte2 || thisFont->per_char == 0) charWidth = thisFont->max_bounds.width + 1; else charWidth = thisFont->per_char[*str].width + 1; /* Check to see if line wrap is required */ thisLine = textInfo->txtBuffer[textInfo->curLine]; if (thisLine->lineWidth + charWidth > (textInfo->w-BARSIZE-WRAPINDSIZE)) { DrawLineWrap(display, textInfo->mainWindow, textInfo->w-BARSIZE-WRAPINDSIZE, textInfo->curY, thisLine->lineHeight, textInfo->fgPix); thisLine->lineFlags |= WRAPFLAG; /* Handle the spacing problem the same way as a newline */ HandleNewLine(display, textInfo, DODISP | NONEWLINE); thisLine = textInfo->txtBuffer[textInfo->curLine]; } /* Ready to draw character */ XDrawString(display, textInfo->mainWindow, DEFAULT_GC, textInfo->curX += charWidth, textInfo->curY + thisLine->lineHeight, str, 1); /* Append character onto main buffer */ if (textInfo->bufSpot >= textInfo->bufAlloc) /* Make room for more characters */ ExpandBuffer(textInfo); textInfo->mainBuffer[(textInfo->bufSpot)++] = (textInfo->curFont << FONTSHIFT) | (*str); /* Update the line start array */ thisLine->lineLength += 1; thisLine->lineWidth += charWidth; } else if (*str == NEWLINE) { HandleNewLine(display, textInfo, DODISP); } else if (*str == NEWFONT) { /* Go into waiting for font number mode */ textInfo->flagWord |= FONTNUMWAIT; } else if (*str == BACKSPACE) { HandleBackspace(display, textInfo, DODISP); } else { /* Ignore all others */ } } /* Draw the cursor in its new position */ thisLine = textInfo->txtBuffer[textInfo->curLine]; XFillRectangle(display, w, textInfo->CursorGC, thisLine->lineWidth + CUROFFSET, textInfo->curY /* + thisLine->lineHeight */, CURSORWIDTH, thisLine->lineHeight); return 1; } int TxtJamStr(display, w, str) Display *display; Window w; /* Text window */ register char *str; /* NULL terminated string */ /* * This is the same as TxtWriteStr except the screen is NOT updated. * After a call to this routine, TxtRepaint should be called to * update the screen. This routine is meant to be used to load * a text buffer with information and then allow the user to * scroll through it at will. */ { register int fontIndex; register struct txtWin *textInfo; if ((textInfo = (struct txtWin *) XLookUpAssoc(display, textWindows, (XID) w) ) == 0) return 0; for ( /* str is ok */ ; (*str != 0) ; str++) { /* Check to see if we are waiting on a font */ if (textInfo->flagWord & FONTNUMWAIT) { textInfo->flagWord &= (~FONTNUMWAIT); fontIndex = *str - '0'; if ((fontIndex >= 0) && (fontIndex < MAXFONTS)) { if (HandleNewFont(display, fontIndex, textInfo, 0)) { /* Handled font -- go get next character */ continue; } } } /* Inline code for handling normal character case */ if ((*str >= LOWCHAR) && (*str <= HIGHCHAR)) { register XFontStruct *thisFont; register struct txtLine *thisLine; register int charWidth; /* Determine size of character */ thisFont = &(textInfo->theFonts[textInfo->curFont]); if (*str <= thisFont->min_char_or_byte2 || *str >= thisFont->max_char_or_byte2 || thisFont->per_char == 0) charWidth = thisFont->max_bounds.width + 1; else charWidth = thisFont->per_char[*str].width + 1; /* Check to see if line wrap is required */ thisLine = textInfo->txtBuffer[textInfo->curLine]; if (thisLine->lineWidth + charWidth > (textInfo->w-BARSIZE-WRAPINDSIZE)) { thisLine->lineFlags |= WRAPFLAG; /* Handle the spacing problem the same way as a newline */ HandleNewLine(display, textInfo, NONEWLINE); thisLine = textInfo->txtBuffer[textInfo->curLine]; } /* Append character onto main buffer */ if (textInfo->bufSpot >= textInfo->bufAlloc) /* Make room for more characters */ ExpandBuffer(textInfo); textInfo->mainBuffer[(textInfo->bufSpot)++] = (textInfo->curFont << FONTSHIFT) | (*str); /* Update the line start array */ thisLine->lineLength += 1; thisLine->lineWidth += charWidth; } else if (*str == NEWLINE) { HandleNewLine(display, textInfo, 0); } else if (*str == NEWFONT) { /* Go into waiting for font number mode */ textInfo->flagWord |= FONTNUMWAIT; } else if (*str == BACKSPACE) { HandleBackspace(display, textInfo, 0); } else { /* Ignore all others */ } } textInfo->flagWord |= SCREENWRONG; return 1; } int TxtRepaint(display,w) Display *display; Window w; /* * Repaints the given scrollable text window. The routine repaints * the entire window. For handling exposure events, the TxtFilter * routine should be used. */ { struct txtWin *textInfo; int index, ypos; if ((textInfo = (struct txtWin *) XLookUpAssoc(display, textWindows, (XID) w) ) == 0) return 0; /* Check to see if the screen is up to date */ if (textInfo->flagWord & SCREENWRONG) { textInfo->endLine = FindEndLine(textInfo, &(textInfo->bottomSpace)); textInfo->flagWord &= (~SCREENWRONG); } ypos = YPADDING; index = textInfo->startLine; for (;;) { DrawLine(display, textInfo, index, ypos); if (index >= textInfo->endLine) break; ypos += (textInfo->txtBuffer[index]->lineHeight + INTERLINE); index++; } /* Draw the cursor (if on screen) */ if (textInfo->endLine == textInfo->curLine) { XFillRectangle(display, w, textInfo->CursorGC, textInfo->txtBuffer[index]->lineWidth + CUROFFSET, ypos /* + textInfo->txtBuffer[index]->lineHeight */, CURSORWIDTH, textInfo->txtBuffer[index]->lineHeight); } /* Update the scroll bar */ UpdateScroll(display, textInfo); return 1; } static int InsertIndex(textInfo, thisIndex, ypos) struct txtWin *textInfo; /* Text Window Information */ int thisIndex; /* Line index of exposed line */ int ypos; /* Drawing position of line */ /* * This routine inserts the supplied line index into the copy * exposure array for 'textInfo'. The array is kept sorted * from lowest to highest using insertion sort. The array * is dynamically expanded if needed. */ { struct expEvent *newItem; int newSize, index, downIndex; /* Check to see if we need to expand it */ if ((textInfo->exposeSize + 3) >= textInfo->exposeAlloc) { newSize = textInfo->exposeAlloc + (textInfo->exposeAlloc * EXPANDPERCENT / 100); textInfo->exposeAry = (struct expEvent **) realloc((char *) textInfo->exposeAry, (unsigned) (newSize * sizeof(struct expEvent *))); for (index = textInfo->exposeAlloc; index < newSize; index++) textInfo->exposeAry[index] = alloc(struct expEvent); textInfo->exposeAlloc = newSize; } /* Find spot for insertion. NOTE: last spot has big number */ for (index = 0; index <= textInfo->exposeSize; index++) { if (textInfo->exposeAry[index]->lineIndex >= thisIndex) { if (textInfo->exposeAry[index]->lineIndex > thisIndex) { /* Insert before this entry */ newItem = textInfo->exposeAry[textInfo->exposeSize+1]; for (downIndex = textInfo->exposeSize; downIndex >= index; downIndex--) { textInfo->exposeAry[downIndex+1] = textInfo->exposeAry[downIndex]; } /* Put a free structure at this spot */ textInfo->exposeAry[index] = newItem; /* Fill it in */ textInfo->exposeAry[index]->lineIndex = thisIndex; textInfo->exposeAry[index]->ypos = ypos; /* Break out of loop */ textInfo->exposeSize += 1; } break; } } return 1; } static int ScrollUp(display, textInfo) Display *display; struct txtWin *textInfo; /* Text window information */ /* * This routine scrolls the indicated text window up by one * line. The line above the current line must exist. The * window is scrolled so that the line above the start line * is displayed at the top of the screen. This may cause * many lines to scroll off the bottom. The scrolling is * done using XCopyArea. The exposure events should be caught * by ExposeCopy. */ { int targetSpace; /* Make sure all exposures have been handled by now */ if (textInfo->startLine == 0) return 0; targetSpace = textInfo->txtBuffer[textInfo->startLine-1]->lineHeight + INTERLINE; /* Move the area downward by the target amount */ XCopyArea(display, textInfo->mainWindow, textInfo->mainWindow, DEFAULT_GC, 0, YPADDING, textInfo->w - BARSIZE, textInfo->h, 0, targetSpace); textInfo->flagWord |= COPYEXPOSE; /* Update the text window parameters */ textInfo->startLine -= 1; textInfo->endLine = FindEndLine(textInfo, &(textInfo->bottomSpace)); /* Clear out bottom space region */ XClearArea(display, textInfo->mainWindow, 0, textInfo->h - textInfo->bottomSpace, textInfo->w, textInfo->bottomSpace); UpdateExposures(display, textInfo); UpdateScroll(display, textInfo); return 1; } static int ScrollToSpot(display, textInfo, ySpot) Display *display; struct txtWin *textInfo; /* Text window information */ int ySpot; /* Button position in scroll window */ /* * This routine scrolls the specified text window relative to the * position of the mouse in the scroll bar. The center of the screen * will be positioned to correspond to the mouse position. */ { int targetLine, aboveLines; targetLine = textInfo->numLines * ySpot / textInfo->h; textInfo->startLine = targetLine; textInfo->endLine = FindEndLine(textInfo, &(textInfo->bottomSpace)); aboveLines = 0; /* Make the target line the *center* of the window */ while ((textInfo->startLine > 0) && (aboveLines < textInfo->endLine - targetLine)) { textInfo->startLine -= 1; textInfo->endLine = FindEndLine(textInfo, &(textInfo->bottomSpace)); aboveLines++; } if (textInfo->endLine == textInfo->numLines-1) { WarpToBottom(display, textInfo); } else { XClearWindow(display, textInfo->mainWindow); TxtRepaint(display, textInfo->mainWindow); } return 1; } static int LineToTop(display, textInfo, pos) Display *display; struct txtWin *textInfo; /* Text window information */ int pos; /* Y position of mouse */ /* * This routine scrolls the screen down until the line at the * mouse position is at the top of the screen. It stops * if it can't scroll the buffer down that far. If the * global 'ScrollOption' is NORMSCROLL, a smooth scroll * is used. Otherwise, it jumps to the right position * and repaints the screen. */ { int index, sum; /* First, we find the current line */ sum = 0; for (index = textInfo->startLine; index <= textInfo->endLine; index++) { if (sum + textInfo->txtBuffer[index]->lineHeight + INTERLINE> pos) break; sum += textInfo->txtBuffer[index]->lineHeight + INTERLINE; } /* We always want to scroll down at least one line */ if (index == textInfo->startLine) index++; if (ScrollOption == NORMSCROLL) { /* Scroll down until 'index' is the starting line */ while ((textInfo->startLine < index) && ScrollDown(display, textInfo)) { /* Empty Loop Body */ } } else { /* Immediately jump to correct spot */ textInfo->startLine = index; textInfo->endLine = FindEndLine(textInfo, &(textInfo->bottomSpace)); if (textInfo->endLine == textInfo->numLines-1) { WarpToBottom(display, textInfo); } else { XClearWindow(display, textInfo->mainWindow); TxtRepaint(display, textInfo->mainWindow); } } /* Check to see if at end of buffer */ if (textInfo->endLine >= textInfo->numLines-1) { textInfo->flagWord &= (~NOTATBOTTOM); } return 1; } static int TopToHere(display, textInfo, pos) Display *display; struct txtWin *textInfo; /* Text window information */ int pos; /* Y position of mouse */ /* * This routine scrolls the screen up until the top line of * the screen is at the current Y position of the mouse. Again, * it will stop if it can't scroll that far. If the global * 'ScrollOption' is NORMSCROLL, a smooth scroll is used. * If it's not, it will simply redraw the screen at the * correct spot. */ { int sum, target, linesup, index; target = pos - textInfo->txtBuffer[textInfo->startLine]->lineHeight; /* We always want to scroll up at least one line */ if (target <= 0) target = 1; sum = 0; linesup = 0; /* Check to see if we are at the top anyway */ if (textInfo->startLine == 0) return 0; if (ScrollOption == NORMSCROLL) { /* Scroll up until sum of new top lines greater than target */ while ((sum < target) && ScrollUp(display, textInfo)) { sum += textInfo->txtBuffer[textInfo->startLine]->lineHeight; linesup++; } } else { /* Search backward to find index */ index = textInfo->startLine - 1; while ((index > 0) && (sum < target)) { sum += textInfo->txtBuffer[index]->lineHeight; linesup++; index--; } /* Go directly to the index */ textInfo->startLine = index; textInfo->endLine = FindEndLine(textInfo, &(textInfo->bottomSpace)); XClearWindow(display, textInfo->mainWindow); TxtRepaint(display, textInfo->mainWindow); } /* If we scrolled, assert we are not at bottom of buffer */ if (linesup > 0) { textInfo->flagWord |= NOTATBOTTOM; } return 1; } int TxtFilter(display, evt) Display *display; XEvent *evt; /* * This routine handles events associated with scrollable text windows. * It will handle all exposure events and any button released events * in the scroll bar of a text window. It does NOT handle any other * events. If it cannot handle the event, it will return 0. */ { XExposeEvent *expose = &evt->xexpose; XButtonEvent *btEvt = &evt->xbutton; XGraphicsExposeEvent *gexpose = &evt->xgraphicsexpose; XNoExposeEvent *noexpose = &evt->xnoexpose; struct txtWin *textInfo; int index, ypos; Window w, sw; if (textWindows == (XAssocTable *) 0) { textWindows = XCreateAssocTable(32); if (textWindows == (XAssocTable *) 0) return(0); } if (evt->type == Expose) { w = expose->window; sw = 0; } else if (evt->type == GraphicsExpose) { w = gexpose->drawable; sw = 0; } else if (evt->type == NoExpose) { w = noexpose->drawable; sw = 0; } else if (evt->type == ButtonRelease) { w = btEvt->window; sw = btEvt->subwindow; } else return 0; if ((textInfo = (struct txtWin *) XLookUpAssoc(display, textWindows, (XID) w)) == 0) return 0; /* Determine whether it's main window or not */ if ((w == textInfo->mainWindow) && (sw == 0)) { /* Main Window - handle exposures */ switch (evt->type) { case Expose: ypos = 0 /*YPADDING*/; for (index = textInfo->startLine; index <= textInfo->endLine; index++) { int lh = textInfo->txtBuffer[index]->lineHeight; if (((ypos + lh) >= expose->y) && (ypos <= (expose->y + expose->height))) { /* Intersection region */ /* Draw line immediately */ DrawLine(display, textInfo, index, ypos); /* And possibly draw cursor */ if (textInfo->curLine == index) { XFillRectangle(display, w, textInfo->CursorGC, textInfo->txtBuffer[index]->lineWidth + CUROFFSET, ypos, CURSORWIDTH, lh); } } ypos += lh + INTERLINE; } break; case GraphicsExpose: ypos = 0 /*YPADDING*/; for (index = textInfo->startLine; index <= textInfo->endLine; index++) { int lh = textInfo->txtBuffer[index]->lineHeight; if (((ypos + lh) >= gexpose->y) && (ypos <= (gexpose->y + gexpose->height))) { /* Intersection region */ /* Draw line immediately */ DrawLine(display, textInfo, index, ypos); /* And possibly draw cursor */ if (textInfo->curLine == index) { XFillRectangle(display, w, textInfo->CursorGC, textInfo->txtBuffer[index]->lineWidth + CUROFFSET, ypos, CURSORWIDTH, lh); } } ypos += lh + INTERLINE; } break; case NoExpose: break; default: /* Not one of our events */ return 0; } } else { switch (evt->type) { case Expose: UpdateScroll(display, textInfo); break; case ButtonRelease: /* Find out which button */ switch (btEvt->button) { case Button1: /* Scroll up until top line is at mouse position */ TopToHere(display, textInfo, btEvt->y); break; case Button2: /* Scroll to spot relative to position */ ScrollToSpot(display, textInfo, btEvt->y); if (textInfo->endLine >= textInfo->numLines-1) { textInfo->flagWord &= (~NOTATBOTTOM); } else { textInfo->flagWord |= NOTATBOTTOM; } break; case Button3: /* Scroll down until pointed line is at top */ LineToTop(display, textInfo, btEvt->y); break; } break; default: /* Not one of our events */ return 0; } } return 1; }