1 /* 2 * A Scrollable Text Output Window 3 * 4 * David Harrison 5 * University of California, Berkeley 6 * 1986 7 * 8 * The following is an implementation for a scrollable text output 9 * system. It handles exposure events only (other interactions are 10 * under user control). For scrolling, a always present scroll bar 11 * is implemented. It detects size changes and compensates accordingly. 12 */ 13 14 #include <X11/X.h> 15 #include <X11/Xlib.h> 16 #include <X11/X10.h> 17 #include <sys/types.h> 18 #include "scrollText.h" 19 20 extern char *malloc(); 21 extern char *realloc(); 22 #define alloc(type) (type *) malloc(sizeof(type)) 23 #define numalloc(type, num) (type *) malloc((unsigned) (num * sizeof(type))) 24 #define MAXINT 2147483647 25 26 extern XAssocTable *XCreateAssocTable(); 27 extern caddr_t XLookUpAssoc(); 28 29 static XAssocTable *textWindows = (XAssocTable *) 0; 30 31 #define NOOPTION -1 /* Option hasn't been set yet */ 32 #define NORMSCROLL 0 /* Smooth scroll on LineToTop and TopToHere */ 33 #define JUMPSCROLL 1 /* Jump scrolling on LineToTop and TopToHere */ 34 35 static int ScrollOption = NOOPTION; 36 37 typedef char *Generic; 38 39 #define DEFAULT_GC textInfo->fontGC[textInfo->curFont] 40 41 #define BARSIZE 15 42 #define BARBORDER 1 43 #define MAXFONTS 8 44 #define INITBUFSIZE 1024 45 #define INITLINES 50 46 #define INITEXPARY 50 47 #define XPADDING 2 48 #define YPADDING 2 49 #define INTERLINE 5 50 #define INTERSPACE 1 51 #define CURSORWIDTH 2 52 #define EXPANDPERCENT 40 53 #define BUFSIZE 1024 54 #define CUROFFSET 1 55 #define MAXFOREIGN 250 56 #define NOINDEX -1 57 58 /* The wrap line indicator */ 59 #define WRAPINDSIZE 7 60 #define STEMOFFSET 5 61 #define arrow_width 7 62 #define arrow_height 5 63 static char arrow_bits[] = { 64 0x24, 0x26, 0x3f, 0x06, 0x04}; 65 66 #define NEWLINE '\n' 67 #define BACKSPACE '\010' 68 #define NEWFONT '\006' 69 #define LOWCHAR '\040' 70 #define HIGHCHAR '\176' 71 72 #define CHARMASK 0x00ff /* Character mask */ 73 #define FONTMASK 0x0700 /* Character font */ 74 #define FONTSHIFT 8 /* Shift amount */ 75 76 #define WRAPFLAG 0x01 /* Line wrap flag */ 77 78 /* 79 * Lines are represented by a pointer into the overall array of 80 * 16-bit characters. The lower eight bits is used to indicate the character 81 * (in ASCII), and the next two bits are used to indicate the font 82 * the character should be drawn in. 83 */ 84 85 typedef struct txtLine { 86 int lineLength; /* Current line length */ 87 int lineHeight; /* Full height of line in pixels */ 88 int lineBaseLine; /* Current baseline of the line */ 89 int lineWidth; /* Drawing position at end of line */ 90 int lineText; /* Offset into master buffer */ 91 int lineFlags; /* Line wrap flag is here */ 92 }; 93 94 95 /* 96 * For ExposeCopy events, we queue up the redraw requests collapsing 97 * them into line redraw requests until the CopyExpose event arrives. 98 * The queue is represented as a dynamic array of the following 99 * structure: 100 */ 101 102 typedef struct expEvent { 103 int lineIndex; /* Index of line to redraw */ 104 int ypos; /* Drawing position of line */ 105 }; 106 107 108 /* 109 * The text buffer is represented using a dynamic counted array 110 * of 16-bit quantities. This array expands as needed. 111 * For the screen representation, a dynamic counted array 112 * of line structures is used. This array points into the 113 * text buffer to denote the start of each line and its parameters. 114 * The windows are configured as one overall window which contains 115 * the scroll bar as a sub-window along its right edge. Thus, 116 * the text drawing space is actually w-BARSIZE. 117 */ 118 119 #define NOTATBOTTOM 0x01 /* Need to scroll to bottom before appending */ 120 #define FONTNUMWAIT 0x02 /* Waiting for font number */ 121 #define COPYEXPOSE 0x04 /* Need to process a copy expose event */ 122 #define SCREENWRONG 0x08 /* TxtJamStr has invalidated screen contents */ 123 124 typedef struct txtWin { 125 /* Basic text buffer */ 126 int bufAlloc; /* Allocated size of buffer */ 127 int bufSpot; /* Current writing position in buffer */ 128 short *mainBuffer; /* Main buffer of text */ 129 130 /* Line information */ 131 int numLines; /* Number of display lines in buffer */ 132 int allocLines; /* Number of lines allocated */ 133 struct txtLine **txtBuffer; /* Dynamic array of lines */ 134 135 /* Current Window display information */ 136 Window mainWindow; /* Text display window */ 137 Window scrollBar; /* Subwindow for scroll bar */ 138 Pixmap arrowMap; /* line wrap indicator */ 139 int bgPix, fgPix; /* Background and cursor */ 140 GC CursorGC; /* gc for the cursor */ 141 GC bgGC; /* gc for erasing things */ 142 GC fontGC[MAXFONTS]; /* gc for doing fonts */ 143 XFontStruct theFonts[MAXFONTS];/* Display fonts */ 144 int theColors[MAXFONTS]; /* foregrounds of the fonts */ 145 int curFont; /* current font for tracking */ 146 int w, h; /* Current size */ 147 int startLine; /* Top line in display */ 148 int endLine; /* Bottom line in display */ 149 int bottomSpace; /* Space at bottom of screen */ 150 int flagWord; /* If non-zero, not at end */ 151 152 /* For handling ExposeCopy events */ 153 int exposeSize; /* Current size of array */ 154 int exposeAlloc; /* Allocated size */ 155 struct expEvent **exposeAry;/* Array of line indices */ 156 157 /* Drawing position information */ 158 int curLine; /* Current line in buffer */ 159 int curX; /* Current horizontal positi */ 160 int curY; /* Current vertical drawing */ 161 }; 162 163 /* Flags for the various basic character handling functions */ 164 165 #define DODISP 0x01 /* Update the display */ 166 #define NONEWLINE 0x02 /* Dont append newline */ 167 168 169 170 static int InitLine(newLine) 171 struct txtLine *newLine; /* Newly created line structure */ 172 /* 173 * This routine initializes a newly created line structure. 174 */ 175 { 176 newLine->lineLength = 0; 177 newLine->lineHeight = 0; 178 newLine->lineBaseLine = 0; 179 newLine->lineWidth = XPADDING; 180 newLine->lineText = NOINDEX; 181 newLine->lineFlags = 0; 182 return 1; 183 } 184 185 186 187 188 int TxtGrab(display, txtWin, program, mainFont, bg, fg, cur) 189 Display *display; /* display window is on */ 190 Window txtWin; /* Window to take over as scrollable text */ 191 char *program; /* Program name for Xdefaults */ 192 XFontStruct *mainFont; /* Primary text font */ 193 int bg, fg, cur; /* Background, foreground, and cursor colors */ 194 /* 195 * This routine takes control of 'txtWin' and makes it into a scrollable 196 * text output window. It will create a sub-window for the scroll bar 197 * with a background of 'bg' and an bar with color 'fg'. Both fixed width 198 * and variable width fonts are supported. Additional fonts can be loaded 199 * using 'TxtAddFont'. Returns 0 if there were problems, non-zero if 200 * everything went ok. 201 */ 202 { 203 struct txtWin *newWin; /* Text package specific information */ 204 XWindowAttributes winInfo; /* Window information */ 205 int index; 206 XGCValues gc_val; 207 208 if (textWindows == (XAssocTable *) 0) { 209 textWindows = XCreateAssocTable(32); 210 if (textWindows == (XAssocTable *) 0) return(0); 211 } 212 if (XGetWindowAttributes(display, txtWin, &winInfo) == 0) return 0; 213 214 if (ScrollOption == NOOPTION) { 215 /* Read to see if the user wants jump scrolling or not */ 216 if (XGetDefault(display, program, "JumpScroll")) { 217 ScrollOption = JUMPSCROLL; 218 } else { 219 ScrollOption = NORMSCROLL; 220 } 221 } 222 223 /* Initialize local structure */ 224 newWin = alloc(struct txtWin); 225 226 /* Initialize arrow pixmap */ 227 newWin->arrowMap = XCreatePixmapFromBitmapData(display, txtWin, 228 arrow_bits, 229 arrow_width, arrow_height, 230 cur, bg, 231 DisplayPlanes(display, 0)); 232 233 newWin->bufAlloc = INITBUFSIZE; 234 newWin->bufSpot = 0; 235 newWin->mainBuffer = numalloc(short, INITBUFSIZE); 236 237 newWin->numLines = 1; 238 newWin->allocLines = INITLINES; 239 newWin->txtBuffer = numalloc(struct txtLine *, INITLINES); 240 for (index = 0; index < INITLINES; index++) { 241 newWin->txtBuffer[index] = alloc(struct txtLine); 242 InitLine(newWin->txtBuffer[index]); 243 } 244 245 /* Window display information */ 246 newWin->mainWindow = txtWin; 247 newWin->w = winInfo.width; 248 newWin->h = winInfo.height; 249 newWin->startLine = 0; 250 newWin->endLine = 0; 251 newWin->bottomSpace = winInfo.height 252 - YPADDING - mainFont->ascent - mainFont->descent - INTERLINE; 253 newWin->flagWord = 0; 254 newWin->bgPix = bg; 255 newWin->fgPix = fg; 256 257 /* Scroll Bar Creation */ 258 newWin->scrollBar = XCreateSimpleWindow(display, txtWin, 259 winInfo.width - BARSIZE, 260 0, BARSIZE - (2*BARBORDER), 261 winInfo.height - (2*BARBORDER), 262 BARBORDER, 263 fg, bg); 264 XSelectInput(display, newWin->scrollBar, ExposureMask|ButtonReleaseMask); 265 XMapRaised(display, newWin->scrollBar); 266 267 /* Font and Color Initialization */ 268 newWin->theFonts[0] = *mainFont; 269 newWin->theColors[0] = fg; 270 gc_val.function = GXcopy; 271 gc_val.plane_mask = AllPlanes; 272 gc_val.foreground = fg; 273 gc_val.background = bg; 274 gc_val.graphics_exposures = 1; 275 gc_val.font = mainFont->fid; 276 gc_val.line_width = 1; 277 gc_val.line_style = LineSolid; 278 279 newWin->fontGC[0] = XCreateGC(display, txtWin, 280 GCFunction | GCPlaneMask | 281 GCForeground | GCBackground | 282 GCGraphicsExposures | GCFont, 283 &gc_val); 284 285 gc_val.foreground = cur; 286 newWin->CursorGC = XCreateGC(display, txtWin, 287 GCFunction | GCPlaneMask | 288 GCForeground | GCBackground | 289 GCLineStyle | GCLineWidth, 290 &gc_val); 291 292 gc_val.foreground = bg; 293 newWin->bgGC = XCreateGC(display, txtWin, 294 GCFunction | GCPlaneMask | 295 GCForeground | GCBackground | 296 GCGraphicsExposures | GCFont, 297 &gc_val); 298 299 300 for (index = 1; index < MAXFONTS; index++) { 301 newWin->theFonts[index].fid = 0; 302 newWin->fontGC[index] = 0; 303 } 304 305 306 /* Initialize size of first line */ 307 newWin->txtBuffer[0]->lineHeight = newWin->theFonts[0].ascent + 308 newWin->theFonts[0].descent; 309 newWin->txtBuffer[0]->lineText = 0; 310 311 /* ExposeCopy array initialization */ 312 newWin->exposeSize = 0; 313 newWin->exposeAlloc = INITEXPARY; 314 newWin->exposeAry = numalloc(struct expEvent *, INITEXPARY); 315 for (index = 0; index < newWin->exposeAlloc; index++) 316 newWin->exposeAry[index] = alloc(struct expEvent); 317 /* Put plus infinity in last slot for sorting purposes */ 318 newWin->exposeAry[0]->lineIndex = MAXINT; 319 320 /* Drawing Position Information */ 321 newWin->curLine = 0; 322 newWin->curX = 0; 323 newWin->curY = YPADDING + mainFont->ascent + mainFont->descent; 324 325 /* Attach it to both windows */ 326 XMakeAssoc(display, textWindows, (XID) txtWin, (caddr_t) newWin); 327 XMakeAssoc(display, textWindows, (XID) newWin->scrollBar, (caddr_t) newWin); 328 return 1; 329 } 330 331 332 int TxtRelease(display, w) 333 Display *display; 334 Window w; /* Window to release */ 335 /* 336 * This routine releases all resources associated with the 337 * specified window which are consumed by the text 338 * window package. This includes the entire text buffer, line start 339 * array, and the scroll bar window. However, the window 340 * itself is NOT destroyed. The routine will return zero if 341 * the window is not owned by the text window package. 342 */ 343 { 344 struct txtWin *textInfo; 345 int index; 346 347 if ((textInfo = (struct txtWin *) XLookUpAssoc(display, 348 textWindows, (XID) w)) == 0) 349 return 0; 350 351 for (index = 0; index < MAXFONTS; index++) 352 if (textInfo->fontGC[index] != 0) 353 XFreeGC(display, textInfo->fontGC[index]); 354 355 free((Generic) textInfo->mainBuffer); 356 for (index = 0; index < textInfo->numLines; index++) { 357 free((Generic) textInfo->txtBuffer[index]); 358 } 359 free((Generic) textInfo->txtBuffer); 360 XDestroyWindow(display, textInfo->scrollBar); 361 for (index = 0; index < textInfo->exposeSize; index++) { 362 free((Generic) textInfo->exposeAry[index]); 363 } 364 free((Generic) textInfo->exposeAry); 365 XDeleteAssoc(display, textWindows, (XID) w); 366 free((Generic) textInfo); 367 return 1; 368 } 369 370 371 372 static int RecompBuffer(textInfo) 373 struct txtWin *textInfo; /* Text window information */ 374 /* 375 * This routine recomputes all line breaks in a buffer after 376 * a change in window size or font. This is done by throwing 377 * away the old line start array and recomputing it. Although 378 * a lot of this work is also done elsewhere, it has been included 379 * inline here for efficiency. 380 */ 381 { 382 int startPos, endSize, linenum; 383 register int index, chsize, curfont; 384 register short *bufptr; 385 register XFontStruct *fontptr; 386 register struct txtLine *lineptr; 387 char theChar; 388 389 /* Record the old position so we can come back to it */ 390 for (startPos = textInfo->txtBuffer[textInfo->startLine]->lineText; 391 (startPos > 0) && (textInfo->mainBuffer[startPos] != '\n'); 392 startPos--) 393 /* null loop body */; 394 395 /* Clear out the old line start array */ 396 for (index = 0; index < textInfo->numLines; index++) { 397 InitLine(textInfo->txtBuffer[index]); 398 } 399 400 /* Initialize first line */ 401 textInfo->txtBuffer[0]->lineHeight = 402 textInfo->theFonts[0].ascent + textInfo->theFonts[0].descent; 403 textInfo->txtBuffer[0]->lineText = 0; 404 405 /* Process the text back into lines */ 406 endSize = textInfo->w - BARSIZE - WRAPINDSIZE; 407 bufptr = textInfo->mainBuffer; 408 lineptr = textInfo->txtBuffer[0]; 409 linenum = 0; 410 fontptr = &(textInfo->theFonts[0]); 411 curfont = 0; 412 for (index = 0; index < textInfo->bufSpot; index++) { 413 theChar = bufptr[index] & CHARMASK; 414 415 if ((bufptr[index] & FONTMASK) != curfont) { 416 int newFontNum, heightDiff; 417 418 /* Switch fonts */ 419 newFontNum = (bufptr[index] & FONTMASK) >> FONTSHIFT; 420 if (textInfo->theFonts[newFontNum].fid != 0) { 421 /* Valid font */ 422 curfont = bufptr[index] & FONTMASK; 423 fontptr = &(textInfo->theFonts[newFontNum]); 424 heightDiff = (fontptr->ascent + fontptr->descent) - 425 lineptr->lineHeight; 426 if (heightDiff < 0) heightDiff = 0; 427 lineptr->lineHeight += heightDiff; 428 } 429 } 430 if (theChar == '\n') { 431 /* Handle new line */ 432 if (linenum >= textInfo->allocLines-1) 433 /* Expand number of lines */ 434 ExpandLines(textInfo); 435 linenum++; 436 lineptr = textInfo->txtBuffer[linenum]; 437 /* Initialize next line */ 438 lineptr->lineHeight = fontptr->ascent + fontptr->descent; 439 lineptr->lineText = index+1; 440 /* Check to see if its the starting line */ 441 if (index == startPos) textInfo->startLine = linenum; 442 } else { 443 /* Handle normal character */ 444 chsize = CharSize(textInfo, linenum, index); 445 if (lineptr->lineWidth + chsize > endSize) { 446 /* Handle line wrap */ 447 lineptr->lineFlags |= WRAPFLAG; 448 if (linenum >= textInfo->allocLines-1) 449 /* Expand number of lines */ 450 ExpandLines(textInfo); 451 linenum++; 452 lineptr = textInfo->txtBuffer[linenum]; 453 /* Initialize next line */ 454 lineptr->lineHeight = fontptr->ascent + fontptr->descent; 455 lineptr->lineText = index; 456 lineptr->lineLength = 1; 457 lineptr->lineWidth += chsize; 458 } else { 459 /* Handle normal addition of character */ 460 lineptr->lineLength += 1; 461 lineptr->lineWidth += chsize; 462 } 463 } 464 } 465 /* We now have a valid line array. Let's clean up some other fields. */ 466 textInfo->numLines = linenum+1; 467 if (startPos == 0) { 468 textInfo->startLine = 0; 469 } 470 textInfo->endLine = FindEndLine(textInfo, &(textInfo->bottomSpace)); 471 textInfo->curLine = linenum; 472 /* Check to see if we are at the bottom */ 473 if (textInfo->endLine >= textInfo->numLines-1) { 474 textInfo->curY = textInfo->h - textInfo->bottomSpace - 475 lineptr->lineHeight; 476 textInfo->flagWord &= (~NOTATBOTTOM); 477 } else { 478 textInfo->flagWord |= NOTATBOTTOM; 479 } 480 return 1; 481 } 482 483 484 485 486 int TxtAddFont(display, textWin, fontNumber, newFont, newColor) 487 Display *display; 488 Window textWin; /* Scrollable text window */ 489 int fontNumber; /* Place to add font (0-7) */ 490 XFontStruct *newFont; /* Font to add */ 491 int newColor; /* Color of font */ 492 /* 493 * This routine loads a new font so that it can be used in a previously 494 * created text window. There are eight font slots numbered 0 through 7. 495 * If there is already a font in the specified slot, it will be replaced 496 * and an automatic redraw of the window will take place. See TxtWriteStr 497 * for details on using alternate fonts. The color specifies the foreground 498 * color of the text. The default foreground color is used if this 499 * parameter is TXT_NO_COLOR. Returns a non-zero value if 500 * everything went well. 501 */ 502 { 503 struct txtWin *textInfo; 504 int redrawFlag; 505 XGCValues gc_val; 506 507 if ((fontNumber < 0) || (fontNumber >= MAXFONTS)) return 0; 508 if ((textInfo = (struct txtWin *) 509 XLookUpAssoc(display, textWindows, (XID) textWin)) == 0) 510 return 0; 511 if (newColor == TXT_NO_COLOR) { 512 newColor = textInfo->fgPix; 513 } 514 515 gc_val.font = newFont->fid; 516 gc_val.foreground = newColor; 517 gc_val.background = textInfo->bgPix; 518 gc_val.plane_mask = AllPlanes; 519 gc_val.graphics_exposures = 1; 520 gc_val.function = GXcopy; 521 522 if (textInfo->fontGC[fontNumber] != 0) 523 { 524 XChangeGC(display, textInfo->fontGC[fontNumber], 525 GCFont | GCForeground, &gc_val); 526 } 527 else 528 textInfo->fontGC[fontNumber] = XCreateGC(display, textWin, 529 GCFont | 530 GCForeground | 531 GCBackground | 532 GCFunction | 533 GCPlaneMask | 534 GCGraphicsExposures, 535 &gc_val); 536 537 538 redrawFlag = (textInfo->theFonts[fontNumber].fid != 0) && 539 (((newFont) && (newFont->fid != textInfo->theFonts[fontNumber].fid)) || 540 (newColor != textInfo->theColors[fontNumber])); 541 if (newFont) { 542 textInfo->theFonts[fontNumber] = *newFont; 543 } 544 textInfo->theColors[fontNumber] = newColor; 545 546 if (redrawFlag) { 547 RecompBuffer(textInfo); 548 XClearWindow(display, textWin); 549 TxtRepaint(display, textWin); 550 } 551 return 1; 552 } 553 554 555 556 int TxtWinP(display, w) 557 Display *display; 558 Window w; 559 /* 560 * Returns a non-zero value if the window has been previously grabbed 561 * using TxtGrab and 0 if it has not. 562 */ 563 { 564 if (XLookUpAssoc(display, textWindows, (XID) w)) 565 return(1); 566 else return(0); 567 } 568 569 570 571 static int FindEndLine(textInfo, botSpace) 572 struct txtWin *textInfo; 573 int *botSpace; 574 /* 575 * Given the starting line in 'textInfo->startLine', this routine 576 * determines the index of the last line that can be drawn given the 577 * current size of the screen. If there are not enough lines to 578 * fill the screen, the index of the last line will be returned. 579 * The amount of empty bottom space is returned in 'botSpace'. 580 */ 581 { 582 int index, height, lineHeight; 583 584 height = YPADDING; 585 index = textInfo->startLine; 586 while (index < textInfo->numLines) { 587 lineHeight = textInfo->txtBuffer[index]->lineHeight + INTERLINE; 588 if (height + lineHeight > textInfo->h) break; 589 height += lineHeight; 590 index++; 591 } 592 if (botSpace) { 593 *botSpace = textInfo->h - height; 594 } 595 return index - 1; 596 } 597 598 599 600 static int UpdateScroll(display, textInfo) 601 Display *display; 602 struct txtWin *textInfo; /* Text window information */ 603 /* 604 * This routine computes the current extent of the scroll bar 605 * indicator and repaints the bar with the correct information. 606 */ 607 { 608 int top, bottom; 609 610 if (textInfo->numLines > 1) { 611 top = textInfo->startLine * (textInfo->h - 2*BARBORDER) / 612 (textInfo->numLines - 1); 613 bottom = textInfo->endLine * (textInfo->h - 2*BARBORDER) / 614 (textInfo->numLines - 1); 615 } else { 616 top = 0; 617 bottom = textInfo->h - (2*BARBORDER); 618 } 619 620 /* Draw it - make sure there is a little padding */ 621 if (top == 0) top++; 622 if (bottom == textInfo->h-(2*BARBORDER)) bottom--; 623 624 XFillRectangle(display, textInfo->scrollBar, 625 textInfo->bgGC, 626 0, 0, BARSIZE, top-1); 627 XFillRectangle(display, textInfo->scrollBar, 628 DEFAULT_GC, top, BARSIZE - (2*BARBORDER) - 2, 629 bottom - top); 630 XFillRectangle(display, textInfo->scrollBar, DEFAULT_GC, 631 0, bottom+1, BARSIZE, 632 textInfo->h - (2 * BARBORDER) - bottom); 633 634 return 1; 635 } 636 637 638 639 640 int TxtClear(display, w) 641 Display *display; 642 Window w; 643 /* 644 * This routine clears a scrollable text window. It resets the current 645 * writing position to the upper left hand corner of the screen. 646 * NOTE: THIS ALSO CLEARS THE CONTENTS OF THE TEXT WINDOW BUFFER AND 647 * RESETS THE SCROLL BAR. Returns 0 if the window is not a text window. 648 * This should be used *instead* of XClear. 649 */ 650 { 651 struct txtWin *textInfo; 652 int index; 653 654 if ((textInfo = (struct txtWin *) XLookUpAssoc(display, textWindows, (XID) w)) == 0) 655 return 0; 656 657 /* Zero out the arrays */ 658 textInfo->bufSpot = 0; 659 for (index = 0; index < textInfo->numLines; index++) { 660 InitLine(textInfo->txtBuffer[index]); 661 } 662 textInfo->txtBuffer[0]->lineHeight = 663 textInfo->theFonts[textInfo->curFont].ascent + 664 textInfo->theFonts[textInfo->curFont].descent; 665 666 textInfo->numLines = 1; 667 textInfo->startLine = 0; 668 textInfo->endLine = 0; 669 textInfo->curLine = 0; 670 textInfo->curX = 0; 671 textInfo->curY = YPADDING + textInfo->theFonts[textInfo->curFont].ascent 672 + textInfo->theFonts[textInfo->curFont].descent; 673 674 textInfo->bottomSpace = textInfo->h - YPADDING - 675 textInfo->theFonts[textInfo->curFont].ascent - INTERLINE - 676 textInfo->theFonts[textInfo->curFont].descent; 677 /* Actually clear the window */ 678 XClearWindow(display, w); 679 680 /* Draw the current cursor */ 681 XFillRectangle(display, w, textInfo->CursorGC, 682 XPADDING + CUROFFSET, textInfo->curY, 683 CURSORWIDTH, 684 textInfo->theFonts[textInfo->curFont].ascent + 685 textInfo->theFonts[textInfo->curFont].descent); 686 687 /* Update the scroll bar */ 688 UpdateScroll(display, textInfo); 689 return 1; 690 } 691 692 693 static int WarpToBottom(display, textInfo) 694 Display *display; 695 struct txtWin *textInfo; /* Text Information */ 696 /* 697 * This routine causes the specified text window to display its 698 * last screen of information. It updates the scroll bar 699 * to the appropriate spot. The implementation scans backward 700 * through the buffer to find an appropriate starting spot for 701 * the window. 702 */ 703 { 704 int index, height, lineHeight; 705 706 index = textInfo->numLines-1; 707 height = 0; 708 while (index >= 0) { 709 lineHeight = textInfo->txtBuffer[index]->lineHeight + INTERLINE; 710 if (height + lineHeight > textInfo->h) break; 711 height += lineHeight; 712 index--; 713 } 714 textInfo->startLine = index + 1; 715 textInfo->endLine = FindEndLine(textInfo, &(textInfo->bottomSpace)); 716 textInfo->curY = textInfo->h - textInfo->bottomSpace - 717 textInfo->txtBuffer[textInfo->endLine]->lineHeight; 718 XClearWindow(display, textInfo->mainWindow); 719 TxtRepaint(display, textInfo->mainWindow); 720 return 1; 721 } 722 723 724 725 static int UpdateExposures(display, textInfo) 726 Display *display; 727 struct txtWin *textInfo; /* Text window information */ 728 /* 729 * Before a new scrolling action occurs, the text window package 730 * must handle all COPYEXPOSE events generated by the last scrolling 731 * action. This routine is called to do this. Foreign events (those 732 * not handled by TxtFilter) are queued up and replaced on the queue 733 * after the processing of the exposure events is complete. 734 */ 735 { 736 #if 0 737 XEvent foreignQueue[MAXFOREIGN]; 738 int index, lastItem = 0; 739 740 while (textInfo->flagWord & COPYEXPOSE) { 741 XNextEvent(display, &(foreignQueue[lastItem])); 742 if (!TxtFilter(display, &(foreignQueue[lastItem]))) 743 lastItem++; 744 if (lastItem >= MAXFOREIGN) { 745 printf("Too many foreign events to queue!\n"); 746 textInfo->flagWord &= (~COPYEXPOSE); 747 } 748 } 749 for (index = 0; index < lastItem; index++) { 750 XPutBackEvent(display, &(foreignQueue[index])); 751 } 752 #endif 753 return 1; 754 } 755 756 757 static int ScrollDown(display,textInfo) 758 Display *display; 759 struct txtWin *textInfo; /* Text window information */ 760 /* 761 * This routine scrolls the indicated text window down by one 762 * line. The line below the current line must exist. The window 763 * is scrolled so that the line below the last line is fully 764 * displayed. This may cause many lines to scroll off the top. 765 * Scrolling is done using XCopyArea. The exposure events should 766 * be caught using ExposeCopy. 767 */ 768 { 769 int lineSum, index, targetSpace, freeSpace, updateFlag; 770 771 lineSum = 0; 772 if (textInfo->endLine + 1 >= textInfo->numLines) return 0; 773 targetSpace = textInfo->txtBuffer[textInfo->endLine+1]->lineHeight + 774 INTERLINE; 775 if (textInfo->bottomSpace < targetSpace) { 776 index = textInfo->startLine; 777 while (index < textInfo->endLine) { 778 lineSum += (textInfo->txtBuffer[index]->lineHeight + INTERLINE); 779 if (textInfo->bottomSpace + lineSum >= targetSpace) break; 780 index++; 781 } 782 783 /* Must move upward by 'lineSum' pixels */ 784 XCopyArea(display, textInfo->mainWindow, textInfo->mainWindow, 785 DEFAULT_GC, 0, lineSum, 786 textInfo->w - BARSIZE, textInfo->h, 787 0, 0); 788 789 textInfo->flagWord |= COPYEXPOSE; 790 /* Repair the damage to the structures */ 791 textInfo->startLine = index + 1; 792 updateFlag = 1; 793 } else { 794 updateFlag = 0; 795 } 796 /* More lines might be able to fit. Let's check. */ 797 freeSpace = textInfo->bottomSpace + lineSum - targetSpace; 798 index = textInfo->endLine + 1; 799 while (index < textInfo->numLines-1) { 800 if (freeSpace - textInfo->txtBuffer[index+1]->lineHeight - INTERLINE < 0) 801 break; 802 freeSpace -= (textInfo->txtBuffer[index+1]->lineHeight + INTERLINE); 803 index++; 804 } 805 textInfo->endLine = index; 806 textInfo->bottomSpace = freeSpace; 807 if (updateFlag) { 808 UpdateExposures(display, textInfo); 809 } 810 UpdateScroll(display, textInfo); 811 return 1; 812 } 813 814 815 816 817 static int ExpandLines(textInfo) 818 struct txtWin *textInfo; /* Text Information */ 819 /* 820 * This routine allocates and initializes additional space in 821 * the line start array (txtBuffer). The new space 822 * is allocated using realloc. The expansion factor is a percentage 823 * given by EXPANDPERCENT. 824 */ 825 { 826 int newSize, index; 827 828 newSize = textInfo->allocLines; 829 newSize += (newSize * EXPANDPERCENT) / 100; 830 831 textInfo->txtBuffer = (struct txtLine **) 832 realloc((char *) textInfo->txtBuffer, 833 (unsigned) (newSize * sizeof(struct txtLine *))); 834 for (index = textInfo->allocLines; index < newSize; index++) { 835 textInfo->txtBuffer[index] = alloc(struct txtLine); 836 InitLine(textInfo->txtBuffer[index]); 837 } 838 textInfo->allocLines = newSize; 839 return 1; 840 } 841 842 static int ExpandBuffer(textInfo) 843 struct txtWin *textInfo; /* Text information */ 844 /* 845 * Expands the basic character buffer using realloc. The expansion 846 * factor is a percentage given by EXPANDPERCENT. 847 */ 848 { 849 int newSize; 850 851 newSize = textInfo->bufAlloc + (textInfo->bufAlloc * EXPANDPERCENT) / 100; 852 textInfo->mainBuffer = (short *) 853 realloc((char *) textInfo->mainBuffer, (unsigned) newSize * sizeof(short)); 854 textInfo->bufAlloc = newSize; 855 return 1; 856 } 857 858 859 860 static int HandleNewLine(display, textInfo, flagWord) 861 Display *display; 862 struct txtWin *textInfo; /* Text Information */ 863 int flagWord; /* DODISP or NONEWLINE or both */ 864 /* 865 * This routine initializes the next line for drawing by setting 866 * its height to the current font height, scrolls the screen down 867 * one line, and updates the current drawing position to the 868 * left edge of the newly cleared line. If DODISP is specified, 869 * the screen will be updated (otherwise not). If NONEWLINE is 870 * specified, no newline character will be added to the text buffer 871 * (this is for line wrap). 872 */ 873 { 874 struct txtLine *curLine, *nextLine; 875 876 /* Check to see if a new line must be allocated */ 877 if (textInfo->curLine >= textInfo->allocLines-1) 878 /* Expand the number of lines */ 879 ExpandLines(textInfo); 880 textInfo->numLines += 1; 881 882 /* Then we initialize the next line */ 883 nextLine = textInfo->txtBuffer[textInfo->numLines-1]; 884 nextLine->lineHeight = 885 textInfo->theFonts[textInfo->curFont].ascent + 886 textInfo->theFonts[textInfo->curFont].descent; 887 888 curLine = textInfo->txtBuffer[textInfo->curLine]; 889 if (flagWord & DODISP) { 890 /* Scroll down a line if required */ 891 if ((textInfo->curY + curLine->lineHeight + 892 nextLine->lineHeight + (INTERLINE * 2)) > textInfo->h) 893 { 894 ScrollDown(display, textInfo); 895 } 896 else 897 { 898 /* Update the bottom space appropriately */ 899 textInfo->bottomSpace -= (nextLine->lineHeight + INTERLINE); 900 textInfo->endLine += 1; 901 } 902 /* Update drawing position */ 903 textInfo->curY = textInfo->h - 904 (textInfo->bottomSpace + nextLine->lineHeight); 905 } 906 907 /* Move down a line */ 908 textInfo->curLine += 1; 909 if (!(flagWord & NONEWLINE)) { 910 /* Append end-of-line to text buffer */ 911 if (textInfo->bufSpot >= textInfo->bufAlloc) { 912 /* Allocate more space in main text buffer */ 913 ExpandBuffer(textInfo); 914 } 915 textInfo->mainBuffer[(textInfo->bufSpot)++] = 916 (textInfo->curFont << FONTSHIFT) | '\n'; 917 } 918 nextLine->lineText = textInfo->bufSpot; 919 textInfo->curX = 0; 920 return 1; 921 } 922 923 924 925 static int CharSize(textInfo, lineNum, charNum) 926 struct txtWin *textInfo; /* Current Text Information */ 927 int lineNum; /* Line in buffer */ 928 int charNum; /* Character in line */ 929 /* 930 * This routine determines the size of the specified character. 931 * It takes in account the font of the character and whether its 932 * fixed or variable. The size includes INTERSPACE spacing between 933 * the characters. 934 */ 935 { 936 register XFontStruct *charFont; 937 register short *theLine; 938 register short theChar; 939 940 theLine = &(textInfo->mainBuffer[textInfo->txtBuffer[lineNum]->lineText]); 941 theChar = theLine[charNum] & CHARMASK; 942 charFont = &(textInfo->theFonts[(theChar & FONTMASK) >> FONTSHIFT]); 943 if (theChar <= charFont->min_char_or_byte2 || 944 theChar >= charFont->max_char_or_byte2 || 945 charFont->per_char == 0) 946 return charFont->max_bounds.width + 1; 947 else 948 return charFont->per_char[theChar].width + 1; 949 } 950 951 952 953 954 955 static int HandleBackspace(display, textInfo, flagWord) 956 Display *display; 957 struct txtWin *textInfo; /* Text Information */ 958 int flagWord; /* DODISP or nothing */ 959 /* 960 * This routine handles a backspace found in the input stream. The 961 * character before the current writing position will be erased and 962 * the drawing position will move back one character. If the writing 963 * position is at the left margin, the drawing position will move 964 * up to the previous line. If it is a line that has been wrapped, 965 * the character at the end of the previous line will be erased. 966 */ 967 { 968 struct txtLine *thisLine, *prevLine; 969 int chSize; 970 971 thisLine = textInfo->txtBuffer[textInfo->curLine]; 972 /* First, determine whether we need to go back a line */ 973 if (thisLine->lineLength == 0) { 974 /* Bleep if at top of buffer */ 975 if (textInfo->curLine == 0) { 976 XBell(display, 50); 977 return 0; 978 } 979 980 /* See if we have to scroll in the other direction */ 981 if ((flagWord & DODISP) && (textInfo->curY <= YPADDING)) { 982 /* This will display the last lines of the buffer */ 983 WarpToBottom(display, textInfo); 984 } 985 986 /* Set drawing position at end of previous line */ 987 textInfo->curLine -= 1; 988 prevLine = textInfo->txtBuffer[textInfo->curLine]; 989 textInfo->numLines -= 1; 990 if (flagWord & DODISP) { 991 textInfo->curY -= (prevLine->lineHeight + INTERLINE); 992 textInfo->bottomSpace += (thisLine->lineHeight + INTERLINE); 993 textInfo->endLine -= 1; 994 } 995 996 /* We are unlinewrapping if the previous line has flag set */ 997 if (prevLine->lineFlags & WRAPFLAG) { 998 /* Get rid of line wrap indicator */ 999 if (flagWord & DODISP) { 1000 XFillRectangle(display, textInfo->mainWindow, 1001 textInfo->bgGC, 1002 textInfo->w - BARSIZE - WRAPINDSIZE, 1003 textInfo->curY, WRAPINDSIZE, 1004 prevLine->lineHeight); 1005 } 1006 prevLine->lineFlags &= (~WRAPFLAG); 1007 /* Call recursively to wipe out the ending character */ 1008 HandleBackspace(display, textInfo, flagWord); 1009 } else { 1010 /* Delete the end-of-line in the primary buffer */ 1011 textInfo->bufSpot -= 1; 1012 } 1013 } else { 1014 /* Normal deletion of character */ 1015 chSize = 1016 CharSize(textInfo, textInfo->curLine, 1017 textInfo->txtBuffer[textInfo->curLine]->lineLength - 1); 1018 /* Move back appropriate amount and wipe it out */ 1019 thisLine->lineWidth -= chSize; 1020 if (flagWord & DODISP) { 1021 XFillRectangle(display, textInfo->mainWindow, 1022 textInfo->bgGC, 1023 thisLine->lineWidth, textInfo->curY, 1024 chSize, thisLine->lineHeight); 1025 } 1026 /* Delete from buffer */ 1027 textInfo->txtBuffer[textInfo->curLine]->lineLength -= 1; 1028 textInfo->bufSpot -= 1; 1029 } 1030 return 1; 1031 } 1032 1033 1034 1035 static int DrawLineWrap(display, win, x, y, h, col) 1036 Display *display; 1037 Window win; /* What window to draw it in */ 1038 int x, y; /* Position of upper left corner */ 1039 int h; /* Height of indicator */ 1040 int col; /* Color of indicator */ 1041 /* 1042 * This routine draws a line wrap indicator at the end of a line. 1043 * Visually, it is an arrow of the specified height directly against 1044 * the scroll bar border. The bitmap used for the arrow is stored 1045 * in 'arrowMap' with size 'arrow_width' and 'arrow_height'. 1046 */ 1047 { 1048 struct txtWin *textInfo; 1049 1050 textInfo = (struct txtWin *)XLookUpAssoc(display, textWindows, 1051 (XID) win); 1052 1053 /* First, draw the arrow */ 1054 XCopyArea(display, textInfo->arrowMap, textInfo->mainWindow, 1055 textInfo->CursorGC, 1056 0, 0, arrow_width, arrow_height, 1057 x, y + h - arrow_height, 1); 1058 1059 /* Then draw the stem */ 1060 XDrawLine(display, textInfo->mainWindow, textInfo->CursorGC, 1061 x + STEMOFFSET, y, 1062 x + STEMOFFSET, y + h - arrow_height); 1063 return 1; 1064 } 1065 1066 1067 1068 1069 static int DrawLine(display, textInfo, lineIndex, ypos) 1070 Display *display; 1071 struct txtWin *textInfo; /* Text window information */ 1072 int lineIndex; /* Index of line to draw */ 1073 int ypos; /* Y position for line */ 1074 /* 1075 * This routine destructively draws the indicated line in the 1076 * indicated window at the indicated position. It does not 1077 * clear to end of line however. It draws a line wrap indicator 1078 * if needed but does not draw a cursor. 1079 */ 1080 { 1081 int index, startPos, curFont, theColor, curX, saveX, fontIndex; 1082 struct txtLine *someLine; 1083 char lineBuffer[BUFSIZE], *glyph; 1084 short *linePointer; 1085 XFontStruct *theFont; 1086 XGCValues gc; 1087 1088 /* First, we draw the text */ 1089 index = 0; 1090 curX = XPADDING; 1091 someLine = textInfo->txtBuffer[lineIndex]; 1092 linePointer = &(textInfo->mainBuffer[someLine->lineText]); 1093 while (index < someLine->lineLength) { 1094 startPos = index; 1095 saveX = curX; 1096 curFont = linePointer[index] & FONTMASK; 1097 fontIndex = curFont >> FONTSHIFT; 1098 theFont = &(textInfo->theFonts[fontIndex]); 1099 theColor = textInfo->theColors[fontIndex]; 1100 glyph = &(lineBuffer[0]); 1101 while ((index < someLine->lineLength) && 1102 ((linePointer[index] & FONTMASK) == curFont)) 1103 { 1104 *glyph = linePointer[index] & CHARMASK; 1105 index++; 1106 curX += CharSize(textInfo, lineIndex, index); 1107 glyph++; 1108 } 1109 1110 /* Flush out the glyphs */ 1111 XFillRectangle(display, textInfo->mainWindow, 1112 textInfo->bgGC, 1113 saveX, ypos, 1114 textInfo->w - BARSIZE, 1115 someLine->lineHeight + YPADDING + INTERLINE); 1116 1117 XDrawString(display, textInfo->mainWindow, 1118 textInfo->fontGC[fontIndex], 1119 saveX, ypos, 1120 lineBuffer, someLine->lineLength); 1121 } 1122 /* Then the line wrap indicator (if needed) */ 1123 if (someLine->lineFlags & WRAPFLAG) { 1124 DrawLineWrap(display, textInfo->mainWindow, 1125 textInfo->w - BARSIZE - WRAPINDSIZE, 1126 ypos, someLine->lineHeight, 1127 textInfo->fgPix); 1128 } 1129 return 1; 1130 } 1131 1132 1133 1134 1135 static int HandleNewFont(display, fontNum, textInfo, flagWord) 1136 Display *display; 1137 int fontNum; /* Font number */ 1138 struct txtWin *textInfo; /* Text information */ 1139 int flagWord; /* DODISP or nothing */ 1140 /* 1141 * This routine handles a new font request. These requests take 1142 * the form "^F<digit>". The parsing is done in TxtWriteStr. 1143 * This routine is called only if the form is valid. It may return 1144 * a failure (0 status) if the requested font is not loaded. 1145 * If the new font is larger than any of the current 1146 * fonts on the line, it will change the line height and redisplay 1147 * the line. 1148 */ 1149 { 1150 struct txtLine *thisLine; 1151 int heightDiff, baseDiff, redrawFlag; 1152 1153 if (textInfo->theFonts[fontNum].fid == 0) { 1154 return 0; 1155 } else { 1156 thisLine = textInfo->txtBuffer[textInfo->curLine]; 1157 textInfo->curFont = fontNum; 1158 redrawFlag = 0; 1159 heightDiff = textInfo->theFonts[fontNum].ascent + 1160 textInfo->theFonts[fontNum].descent - 1161 thisLine->lineHeight; 1162 1163 if (heightDiff > 0) { 1164 redrawFlag = 1; 1165 } else { 1166 heightDiff = 0; 1167 } 1168 1169 if (redrawFlag) { 1170 if (flagWord & DODISP) { 1171 /* Clear current line */ 1172 XFillRectangle(display, textInfo->mainWindow, 1173 textInfo->bgGC, 1174 0, textInfo->curY, textInfo->w, 1175 thisLine->lineHeight); 1176 1177 /* Check to see if it requires scrolling */ 1178 if ((textInfo->curY + thisLine->lineHeight + heightDiff + 1179 INTERLINE) > textInfo->h) 1180 { 1181 /* 1182 * General approach: "unscroll" the last line up 1183 * and then call ScrollDown to do the right thing. 1184 */ 1185 textInfo->endLine -= 1; 1186 textInfo->bottomSpace += thisLine->lineHeight + 1187 INTERLINE; 1188 1189 XFillRectangle(display, textInfo->mainWindow, 1190 textInfo->bgGC, 1191 0, textInfo->h - textInfo->bottomSpace, 1192 textInfo->w, textInfo->bottomSpace); 1193 1194 thisLine->lineHeight += heightDiff; 1195 ScrollDown(display, textInfo); 1196 textInfo->curY = textInfo->h - 1197 (textInfo->bottomSpace + INTERLINE + 1198 thisLine->lineHeight); 1199 } 1200 else 1201 { 1202 /* Just update bottom space */ 1203 textInfo->bottomSpace -= heightDiff; 1204 thisLine->lineHeight += heightDiff; 1205 } 1206 /* Redraw the current line */ 1207 DrawLine(display, textInfo, textInfo->curLine, textInfo->curY); 1208 } else { 1209 /* Just update line height */ 1210 thisLine->lineHeight += heightDiff; 1211 } 1212 } 1213 return 1; 1214 } 1215 } 1216 1217 1218 1219 int TxtWriteStr(display, w, str) 1220 Display *display; 1221 Window w; /* Text window */ 1222 register char *str; /* 0 terminated string */ 1223 /* 1224 * This routine writes a string to the specified text window. 1225 * The following notes apply: 1226 * - Text is always appended to the end of the text buffer. 1227 * - If the scroll bar is positioned such that the end of the 1228 * text is not visible, an automatic scroll to the bottom 1229 * will be done before the appending of text. 1230 * - Non-printable ASCII characters are not displayed. 1231 * - The '\n' character causes the current text position to 1232 * advance one line and start at the left. 1233 * - Tabs are not supported. 1234 * - Lines too long for the screen will be wrapped and a line wrap 1235 * indication will be drawn. 1236 * - Backspace clears the previous character. It will do the right 1237 * thing if asked to backspace past a wrapped line. 1238 * - A new font can be chosen using the sequence '^F<digit>' where 1239 * <digit> is 0-7. The directive will be ignored if 1240 * there is no font in the specified slot. 1241 * Returns 0 if something went wrong. 1242 */ 1243 { 1244 register int fontIndex; 1245 register struct txtWin *textInfo; 1246 register struct txtLine *thisLine; 1247 1248 if ((textInfo = (struct txtWin *) XLookUpAssoc(display, textWindows, (XID) w)) == 0) 1249 return 0; 1250 1251 /* See if screen needs to be updated */ 1252 if (textInfo->flagWord & SCREENWRONG) { 1253 TxtRepaint(display, textInfo->mainWindow); 1254 } 1255 1256 /* See if we have to scroll down to the bottom */ 1257 if (textInfo->flagWord & NOTATBOTTOM) { 1258 WarpToBottom(display, textInfo); 1259 textInfo->flagWord &= (~NOTATBOTTOM); 1260 } 1261 1262 /* Undraw the current cursor */ 1263 thisLine = textInfo->txtBuffer[textInfo->curLine]; 1264 1265 XFillRectangle(display, w, textInfo->bgGC, 1266 thisLine->lineWidth + CUROFFSET, 1267 textInfo->curY, 1268 CURSORWIDTH, 1269 thisLine->lineHeight); 1270 1271 for ( /* str is ok */ ; (*str != 0) ; str++) { 1272 /* Check to see if we are waiting on a font */ 1273 if (textInfo->flagWord & FONTNUMWAIT) { 1274 textInfo->flagWord &= (~FONTNUMWAIT); 1275 fontIndex = *str - '0'; 1276 if ((fontIndex >= 0) && (fontIndex < MAXFONTS)) { 1277 /* Handle font -- go get next character */ 1278 if (HandleNewFont(display, fontIndex, textInfo, DODISP)) 1279 continue; 1280 } 1281 } 1282 1283 /* Inline code for handling normal character case */ 1284 if ((*str >= LOWCHAR) && (*str <= HIGHCHAR)) { 1285 register XFontStruct *thisFont; 1286 register struct txtLine *thisLine; 1287 register int charWidth; 1288 int thisColor; 1289 1290 /* Determine size of character */ 1291 thisFont = &(textInfo->theFonts[textInfo->curFont]); 1292 thisColor = textInfo->theColors[textInfo->curFont]; 1293 if (*str <= thisFont->min_char_or_byte2 || 1294 *str >= thisFont->max_char_or_byte2 || 1295 thisFont->per_char == 0) 1296 charWidth = thisFont->max_bounds.width + 1; 1297 else 1298 charWidth = thisFont->per_char[*str].width + 1; 1299 1300 /* Check to see if line wrap is required */ 1301 thisLine = textInfo->txtBuffer[textInfo->curLine]; 1302 if (thisLine->lineWidth + charWidth > 1303 (textInfo->w-BARSIZE-WRAPINDSIZE)) 1304 { 1305 DrawLineWrap(display, textInfo->mainWindow, 1306 textInfo->w-BARSIZE-WRAPINDSIZE, 1307 textInfo->curY, thisLine->lineHeight, 1308 textInfo->fgPix); 1309 thisLine->lineFlags |= WRAPFLAG; 1310 /* Handle the spacing problem the same way as a newline */ 1311 HandleNewLine(display, textInfo, DODISP | NONEWLINE); 1312 thisLine = textInfo->txtBuffer[textInfo->curLine]; 1313 } 1314 1315 /* Ready to draw character */ 1316 XDrawString(display, textInfo->mainWindow, 1317 DEFAULT_GC, 1318 textInfo->curX += charWidth, 1319 textInfo->curY + thisLine->lineHeight, 1320 str, 1); 1321 1322 /* Append character onto main buffer */ 1323 if (textInfo->bufSpot >= textInfo->bufAlloc) 1324 /* Make room for more characters */ 1325 ExpandBuffer(textInfo); 1326 textInfo->mainBuffer[(textInfo->bufSpot)++] = 1327 (textInfo->curFont << FONTSHIFT) | (*str); 1328 1329 /* Update the line start array */ 1330 thisLine->lineLength += 1; 1331 thisLine->lineWidth += charWidth; 1332 } else if (*str == NEWLINE) { 1333 HandleNewLine(display, textInfo, DODISP); 1334 } else if (*str == NEWFONT) { 1335 /* Go into waiting for font number mode */ 1336 textInfo->flagWord |= FONTNUMWAIT; 1337 } else if (*str == BACKSPACE) { 1338 HandleBackspace(display, textInfo, DODISP); 1339 } else { 1340 /* Ignore all others */ 1341 } 1342 } 1343 /* Draw the cursor in its new position */ 1344 thisLine = textInfo->txtBuffer[textInfo->curLine]; 1345 1346 XFillRectangle(display, w, textInfo->CursorGC, 1347 thisLine->lineWidth + CUROFFSET, 1348 textInfo->curY /* + thisLine->lineHeight */, 1349 CURSORWIDTH, thisLine->lineHeight); 1350 1351 return 1; 1352 } 1353 1354 1355 1356 int TxtJamStr(display, w, str) 1357 Display *display; 1358 Window w; /* Text window */ 1359 register char *str; /* NULL terminated string */ 1360 /* 1361 * This is the same as TxtWriteStr except the screen is NOT updated. 1362 * After a call to this routine, TxtRepaint should be called to 1363 * update the screen. This routine is meant to be used to load 1364 * a text buffer with information and then allow the user to 1365 * scroll through it at will. 1366 */ 1367 { 1368 register int fontIndex; 1369 register struct txtWin *textInfo; 1370 1371 if ((textInfo = (struct txtWin *) XLookUpAssoc(display, textWindows, (XID) w) 1372 ) == 0) 1373 return 0; 1374 1375 for ( /* str is ok */ ; (*str != 0) ; str++) { 1376 /* Check to see if we are waiting on a font */ 1377 if (textInfo->flagWord & FONTNUMWAIT) { 1378 textInfo->flagWord &= (~FONTNUMWAIT); 1379 fontIndex = *str - '0'; 1380 if ((fontIndex >= 0) && (fontIndex < MAXFONTS)) { 1381 if (HandleNewFont(display, fontIndex, textInfo, 0)) { 1382 /* Handled font -- go get next character */ 1383 continue; 1384 } 1385 } 1386 } 1387 /* Inline code for handling normal character case */ 1388 if ((*str >= LOWCHAR) && (*str <= HIGHCHAR)) { 1389 register XFontStruct *thisFont; 1390 register struct txtLine *thisLine; 1391 register int charWidth; 1392 1393 /* Determine size of character */ 1394 thisFont = &(textInfo->theFonts[textInfo->curFont]); 1395 1396 if (*str <= thisFont->min_char_or_byte2 || 1397 *str >= thisFont->max_char_or_byte2 || 1398 thisFont->per_char == 0) 1399 charWidth = thisFont->max_bounds.width + 1; 1400 else 1401 charWidth = thisFont->per_char[*str].width + 1; 1402 1403 /* Check to see if line wrap is required */ 1404 thisLine = textInfo->txtBuffer[textInfo->curLine]; 1405 if (thisLine->lineWidth + charWidth > 1406 (textInfo->w-BARSIZE-WRAPINDSIZE)) 1407 { 1408 thisLine->lineFlags |= WRAPFLAG; 1409 /* Handle the spacing problem the same way as a newline */ 1410 HandleNewLine(display, textInfo, NONEWLINE); 1411 thisLine = textInfo->txtBuffer[textInfo->curLine]; 1412 } 1413 /* Append character onto main buffer */ 1414 if (textInfo->bufSpot >= textInfo->bufAlloc) 1415 /* Make room for more characters */ 1416 ExpandBuffer(textInfo); 1417 textInfo->mainBuffer[(textInfo->bufSpot)++] = 1418 (textInfo->curFont << FONTSHIFT) | (*str); 1419 1420 /* Update the line start array */ 1421 thisLine->lineLength += 1; 1422 thisLine->lineWidth += charWidth; 1423 } else if (*str == NEWLINE) { 1424 HandleNewLine(display, textInfo, 0); 1425 } else if (*str == NEWFONT) { 1426 /* Go into waiting for font number mode */ 1427 textInfo->flagWord |= FONTNUMWAIT; 1428 } else if (*str == BACKSPACE) { 1429 HandleBackspace(display, textInfo, 0); 1430 } else { 1431 /* Ignore all others */ 1432 } 1433 } 1434 textInfo->flagWord |= SCREENWRONG; 1435 return 1; 1436 } 1437 1438 1439 1440 int TxtRepaint(display,w) 1441 Display *display; 1442 Window w; 1443 /* 1444 * Repaints the given scrollable text window. The routine repaints 1445 * the entire window. For handling exposure events, the TxtFilter 1446 * routine should be used. 1447 */ 1448 { 1449 struct txtWin *textInfo; 1450 int index, ypos; 1451 1452 if ((textInfo = (struct txtWin *) XLookUpAssoc(display, textWindows, (XID) w) 1453 ) == 0) 1454 return 0; 1455 1456 /* Check to see if the screen is up to date */ 1457 if (textInfo->flagWord & SCREENWRONG) { 1458 textInfo->endLine = FindEndLine(textInfo, &(textInfo->bottomSpace)); 1459 textInfo->flagWord &= (~SCREENWRONG); 1460 } 1461 1462 ypos = YPADDING; 1463 index = textInfo->startLine; 1464 for (;;) { 1465 DrawLine(display, textInfo, index, ypos); 1466 if (index >= textInfo->endLine) break; 1467 ypos += (textInfo->txtBuffer[index]->lineHeight + INTERLINE); 1468 index++; 1469 } 1470 /* Draw the cursor (if on screen) */ 1471 if (textInfo->endLine == textInfo->curLine) { 1472 XFillRectangle(display, w, textInfo->CursorGC, 1473 textInfo->txtBuffer[index]->lineWidth + CUROFFSET, 1474 ypos /* + textInfo->txtBuffer[index]->lineHeight */, 1475 CURSORWIDTH, textInfo->txtBuffer[index]->lineHeight); 1476 1477 } 1478 /* Update the scroll bar */ 1479 UpdateScroll(display, textInfo); 1480 return 1; 1481 } 1482 1483 1484 1485 static int InsertIndex(textInfo, thisIndex, ypos) 1486 struct txtWin *textInfo; /* Text Window Information */ 1487 int thisIndex; /* Line index of exposed line */ 1488 int ypos; /* Drawing position of line */ 1489 /* 1490 * This routine inserts the supplied line index into the copy 1491 * exposure array for 'textInfo'. The array is kept sorted 1492 * from lowest to highest using insertion sort. The array 1493 * is dynamically expanded if needed. 1494 */ 1495 { 1496 struct expEvent *newItem; 1497 int newSize, index, downIndex; 1498 1499 /* Check to see if we need to expand it */ 1500 if ((textInfo->exposeSize + 3) >= textInfo->exposeAlloc) { 1501 newSize = textInfo->exposeAlloc + 1502 (textInfo->exposeAlloc * EXPANDPERCENT / 100); 1503 textInfo->exposeAry = (struct expEvent **) 1504 realloc((char *) textInfo->exposeAry, 1505 (unsigned) (newSize * sizeof(struct expEvent *))); 1506 for (index = textInfo->exposeAlloc; index < newSize; index++) 1507 textInfo->exposeAry[index] = alloc(struct expEvent); 1508 textInfo->exposeAlloc = newSize; 1509 } 1510 /* Find spot for insertion. NOTE: last spot has big number */ 1511 for (index = 0; index <= textInfo->exposeSize; index++) { 1512 if (textInfo->exposeAry[index]->lineIndex >= thisIndex) { 1513 if (textInfo->exposeAry[index]->lineIndex > thisIndex) { 1514 /* Insert before this entry */ 1515 newItem = textInfo->exposeAry[textInfo->exposeSize+1]; 1516 for (downIndex = textInfo->exposeSize; 1517 downIndex >= index; 1518 downIndex--) 1519 { 1520 textInfo->exposeAry[downIndex+1] = 1521 textInfo->exposeAry[downIndex]; 1522 } 1523 /* Put a free structure at this spot */ 1524 textInfo->exposeAry[index] = newItem; 1525 /* Fill it in */ 1526 textInfo->exposeAry[index]->lineIndex = thisIndex; 1527 textInfo->exposeAry[index]->ypos = ypos; 1528 /* Break out of loop */ 1529 textInfo->exposeSize += 1; 1530 } 1531 break; 1532 } 1533 } 1534 return 1; 1535 } 1536 1537 1538 1539 static int ScrollUp(display, textInfo) 1540 Display *display; 1541 struct txtWin *textInfo; /* Text window information */ 1542 /* 1543 * This routine scrolls the indicated text window up by one 1544 * line. The line above the current line must exist. The 1545 * window is scrolled so that the line above the start line 1546 * is displayed at the top of the screen. This may cause 1547 * many lines to scroll off the bottom. The scrolling is 1548 * done using XCopyArea. The exposure events should be caught 1549 * by ExposeCopy. 1550 */ 1551 { 1552 int targetSpace; 1553 1554 /* Make sure all exposures have been handled by now */ 1555 if (textInfo->startLine == 0) return 0; 1556 targetSpace = textInfo->txtBuffer[textInfo->startLine-1]->lineHeight + 1557 INTERLINE; 1558 /* Move the area downward by the target amount */ 1559 XCopyArea(display, textInfo->mainWindow, textInfo->mainWindow, 1560 DEFAULT_GC, 1561 0, YPADDING, textInfo->w - BARSIZE, 1562 textInfo->h, 0, targetSpace); 1563 1564 textInfo->flagWord |= COPYEXPOSE; 1565 /* Update the text window parameters */ 1566 textInfo->startLine -= 1; 1567 textInfo->endLine = FindEndLine(textInfo, &(textInfo->bottomSpace)); 1568 1569 /* Clear out bottom space region */ 1570 XClearArea(display, textInfo->mainWindow, 1571 0, textInfo->h - textInfo->bottomSpace, 1572 textInfo->w, textInfo->bottomSpace); 1573 1574 UpdateExposures(display, textInfo); 1575 UpdateScroll(display, textInfo); 1576 1577 return 1; 1578 } 1579 1580 1581 static int ScrollToSpot(display, textInfo, ySpot) 1582 Display *display; 1583 struct txtWin *textInfo; /* Text window information */ 1584 int ySpot; /* Button position in scroll window */ 1585 /* 1586 * This routine scrolls the specified text window relative to the 1587 * position of the mouse in the scroll bar. The center of the screen 1588 * will be positioned to correspond to the mouse position. 1589 */ 1590 { 1591 int targetLine, aboveLines; 1592 1593 targetLine = textInfo->numLines * ySpot / textInfo->h; 1594 textInfo->startLine = targetLine; 1595 textInfo->endLine = FindEndLine(textInfo, &(textInfo->bottomSpace)); 1596 aboveLines = 0; 1597 /* Make the target line the *center* of the window */ 1598 while ((textInfo->startLine > 0) && 1599 (aboveLines < textInfo->endLine - targetLine)) 1600 { 1601 textInfo->startLine -= 1; 1602 textInfo->endLine = FindEndLine(textInfo, &(textInfo->bottomSpace)); 1603 aboveLines++; 1604 } 1605 if (textInfo->endLine == textInfo->numLines-1) { 1606 WarpToBottom(display, textInfo); 1607 } else { 1608 XClearWindow(display, textInfo->mainWindow); 1609 TxtRepaint(display, textInfo->mainWindow); 1610 } 1611 return 1; 1612 } 1613 1614 1615 1616 static int LineToTop(display, textInfo, pos) 1617 Display *display; 1618 struct txtWin *textInfo; /* Text window information */ 1619 int pos; /* Y position of mouse */ 1620 /* 1621 * This routine scrolls the screen down until the line at the 1622 * mouse position is at the top of the screen. It stops 1623 * if it can't scroll the buffer down that far. If the 1624 * global 'ScrollOption' is NORMSCROLL, a smooth scroll 1625 * is used. Otherwise, it jumps to the right position 1626 * and repaints the screen. 1627 */ 1628 { 1629 int index, sum; 1630 1631 /* First, we find the current line */ 1632 sum = 0; 1633 for (index = textInfo->startLine; index <= textInfo->endLine; index++) { 1634 if (sum + textInfo->txtBuffer[index]->lineHeight + INTERLINE> pos) break; 1635 sum += textInfo->txtBuffer[index]->lineHeight + INTERLINE; 1636 } 1637 /* We always want to scroll down at least one line */ 1638 if (index == textInfo->startLine) index++; 1639 if (ScrollOption == NORMSCROLL) { 1640 /* Scroll down until 'index' is the starting line */ 1641 while ((textInfo->startLine < index) && ScrollDown(display, textInfo)) 1642 { 1643 /* Empty Loop Body */ 1644 } 1645 } else { 1646 /* Immediately jump to correct spot */ 1647 textInfo->startLine = index; 1648 textInfo->endLine = FindEndLine(textInfo, &(textInfo->bottomSpace)); 1649 if (textInfo->endLine == textInfo->numLines-1) { 1650 WarpToBottom(display, textInfo); 1651 } else { 1652 XClearWindow(display, textInfo->mainWindow); 1653 TxtRepaint(display, textInfo->mainWindow); 1654 } 1655 } 1656 /* Check to see if at end of buffer */ 1657 if (textInfo->endLine >= textInfo->numLines-1) { 1658 textInfo->flagWord &= (~NOTATBOTTOM); 1659 } 1660 return 1; 1661 } 1662 1663 1664 1665 static int TopToHere(display, textInfo, pos) 1666 Display *display; 1667 struct txtWin *textInfo; /* Text window information */ 1668 int pos; /* Y position of mouse */ 1669 /* 1670 * This routine scrolls the screen up until the top line of 1671 * the screen is at the current Y position of the mouse. Again, 1672 * it will stop if it can't scroll that far. If the global 1673 * 'ScrollOption' is NORMSCROLL, a smooth scroll is used. 1674 * If it's not, it will simply redraw the screen at the 1675 * correct spot. 1676 */ 1677 { 1678 int sum, target, linesup, index; 1679 1680 target = pos - textInfo->txtBuffer[textInfo->startLine]->lineHeight; 1681 /* We always want to scroll up at least one line */ 1682 if (target <= 0) target = 1; 1683 sum = 0; 1684 linesup = 0; 1685 /* Check to see if we are at the top anyway */ 1686 if (textInfo->startLine == 0) return 0; 1687 if (ScrollOption == NORMSCROLL) { 1688 /* Scroll up until sum of new top lines greater than target */ 1689 while ((sum < target) && ScrollUp(display, textInfo)) { 1690 sum += textInfo->txtBuffer[textInfo->startLine]->lineHeight; 1691 linesup++; 1692 } 1693 } else { 1694 /* Search backward to find index */ 1695 index = textInfo->startLine - 1; 1696 while ((index > 0) && (sum < target)) { 1697 sum += textInfo->txtBuffer[index]->lineHeight; 1698 linesup++; 1699 index--; 1700 } 1701 /* Go directly to the index */ 1702 textInfo->startLine = index; 1703 textInfo->endLine = FindEndLine(textInfo, &(textInfo->bottomSpace)); 1704 XClearWindow(display, textInfo->mainWindow); 1705 TxtRepaint(display, textInfo->mainWindow); 1706 } 1707 /* If we scrolled, assert we are not at bottom of buffer */ 1708 if (linesup > 0) { 1709 textInfo->flagWord |= NOTATBOTTOM; 1710 } 1711 return 1; 1712 } 1713 1714 1715 1716 int TxtFilter(display, evt) 1717 Display *display; 1718 XEvent *evt; 1719 /* 1720 * This routine handles events associated with scrollable text windows. 1721 * It will handle all exposure events and any button released events 1722 * in the scroll bar of a text window. It does NOT handle any other 1723 * events. If it cannot handle the event, it will return 0. 1724 */ 1725 { 1726 XExposeEvent *expose = &evt->xexpose; 1727 XButtonEvent *btEvt = &evt->xbutton; 1728 XGraphicsExposeEvent *gexpose = &evt->xgraphicsexpose; 1729 XNoExposeEvent *noexpose = &evt->xnoexpose; 1730 struct txtWin *textInfo; 1731 int index, ypos; 1732 Window w, sw; 1733 1734 if (textWindows == (XAssocTable *) 0) { 1735 textWindows = XCreateAssocTable(32); 1736 if (textWindows == (XAssocTable *) 0) return(0); 1737 } 1738 if (evt->type == Expose) { 1739 w = expose->window; 1740 sw = 0; 1741 } 1742 else if (evt->type == GraphicsExpose) { 1743 w = gexpose->drawable; 1744 sw = 0; 1745 } 1746 else if (evt->type == NoExpose) { 1747 w = noexpose->drawable; 1748 sw = 0; 1749 } 1750 else if (evt->type == ButtonRelease) { 1751 w = btEvt->window; 1752 sw = btEvt->subwindow; 1753 } 1754 else 1755 return 0; 1756 1757 if ((textInfo = (struct txtWin *) 1758 XLookUpAssoc(display, textWindows, (XID) w)) == 0) 1759 return 0; 1760 1761 /* Determine whether it's main window or not */ 1762 if ((w == textInfo->mainWindow) && (sw == 0)) { 1763 /* Main Window - handle exposures */ 1764 switch (evt->type) { 1765 case Expose: 1766 ypos = 0 /*YPADDING*/; 1767 for (index = textInfo->startLine; 1768 index <= textInfo->endLine; 1769 index++) 1770 { 1771 int lh = textInfo->txtBuffer[index]->lineHeight; 1772 1773 if (((ypos + lh) >= expose->y) && 1774 (ypos <= (expose->y + expose->height))) 1775 { 1776 /* Intersection region */ 1777 /* Draw line immediately */ 1778 DrawLine(display, textInfo, index, ypos); 1779 /* And possibly draw cursor */ 1780 if (textInfo->curLine == index) { 1781 XFillRectangle(display, w, textInfo->CursorGC, 1782 textInfo->txtBuffer[index]->lineWidth + 1783 CUROFFSET, 1784 ypos, 1785 CURSORWIDTH, 1786 lh); 1787 } 1788 } 1789 ypos += lh + INTERLINE; 1790 } 1791 break; 1792 case GraphicsExpose: 1793 ypos = 0 /*YPADDING*/; 1794 for (index = textInfo->startLine; 1795 index <= textInfo->endLine; 1796 index++) 1797 { 1798 int lh = textInfo->txtBuffer[index]->lineHeight; 1799 1800 if (((ypos + lh) >= gexpose->y) && 1801 (ypos <= (gexpose->y + gexpose->height))) 1802 { 1803 /* Intersection region */ 1804 /* Draw line immediately */ 1805 DrawLine(display, textInfo, index, ypos); 1806 /* And possibly draw cursor */ 1807 if (textInfo->curLine == index) { 1808 XFillRectangle(display, w, textInfo->CursorGC, 1809 textInfo->txtBuffer[index]->lineWidth + 1810 CUROFFSET, 1811 ypos, 1812 CURSORWIDTH, 1813 lh); 1814 } 1815 } 1816 ypos += lh + INTERLINE; 1817 } 1818 break; 1819 case NoExpose: 1820 break; 1821 default: 1822 /* Not one of our events */ 1823 return 0; 1824 } 1825 } else { 1826 switch (evt->type) { 1827 case Expose: 1828 UpdateScroll(display, textInfo); 1829 break; 1830 case ButtonRelease: 1831 /* Find out which button */ 1832 switch (btEvt->button) { 1833 case Button1: 1834 /* Scroll up until top line is at mouse position */ 1835 TopToHere(display, textInfo, btEvt->y); 1836 break; 1837 case Button2: 1838 /* Scroll to spot relative to position */ 1839 ScrollToSpot(display, textInfo, btEvt->y); 1840 if (textInfo->endLine >= textInfo->numLines-1) { 1841 textInfo->flagWord &= (~NOTATBOTTOM); 1842 } else { 1843 textInfo->flagWord |= NOTATBOTTOM; 1844 } 1845 break; 1846 case Button3: 1847 /* Scroll down until pointed line is at top */ 1848 LineToTop(display, textInfo, btEvt->y); 1849 break; 1850 } 1851 break; 1852 default: 1853 /* Not one of our events */ 1854 return 0; 1855 } 1856 } 1857 return 1; 1858 } 1859