1: /* $Header: Xtextlib.c,v 10.4 86/02/01 15:42:32 tony Rel $ */ 2: /* Library of routines for creating a simple text output window. 3: * 4: * Routines in the library are: 5: * 6: * TextCreate Creates a new instance of a text window 7: * TextDestroy Destroys the window 8: * TextClear Clears a text window 9: * TextRedisplay Redisplays the window 10: * TextEvent Handles exposure and unmapping events 11: * TextPutString Displays a string in a text window 12: * TextPutChar Displays a character in a text window 13: * TextPrintf Does a printf in a text window 14: * 15: * All these routines pass around a pointer to a TextWindow data structure: 16: * 17: * typedef struct _TextWindow { 18: * Window w; Window to use 19: * FontInfo *font; Font to use for text 20: * short num_lines; Number of lines in the window 21: * short num_chars; The length of each line 22: * short mapped; Whether or not the window is mapped 23: * short height; Height of window in pixels 24: * short width; Width of window in pixels 25: * short first_line; The index of the first line 26: * char **lines; Ptr to array of text lines 27: * short *line_length; Ptr to array of line lengths (in pixels) 28: * short *line_chars; Ptr to array of line lengths in chars 29: * short last_line; Which line is the last 30: * short last_char; Length of the last line 31: * short next_x; X-coord for next character 32: * short next_y; Y-coord for next character 33: * unsigned int eventmask; List of events we're interested in 34: * char *scroll_history; Ptr to list of scroll amounts 35: * short scroll_count; Number of outstanding scrolls 36: * short scroll_start; Where in the history the history starts 37: * short old_scrolls; Number of ignorable outstanding scrolls 38: * short fastscroll; Whether or not to use fast scrolling 39: * } TextWindow; 40: * 41: * Applications should not modify anything in this data structure, obviously! 42: * They may, however, have reason to get information out of it. (Such as the 43: * window id for mapping). 44: * 45: * Information about the first line of the window is stored in the array 46: * entries subscripted by [first_line]; the arrays wrap back up at the end. 47: * Last_char should always be the same as line_chars[last_line]. 48: * Similarly, next_x should always be the same as line_length[last_line]; 49: * 50: * The only complicated thing about these procedures is the way they keep 51: * track of scrolling. When a scroll is done, X sends ExposeRegions for 52: * every region that needs to be patched up and then an ExposeCopy event. 53: * The ExposeCopy comes even if there were no regions. The only problem 54: * is that more scrolls may have been done in the meantime. So we keep a 55: * history of how much cumulative scrolling has been done in the 56: * scroll_history list. scroll_start tells which one to start with, and 57: * scroll_count tells how many there are (they wrap around). The list is 58: * num_lines long since anything that's scrolled away longer ago than that 59: * has scrolled off the screen. The old_scrolls field gets set whenever the 60: * screen is fully updated for some reason or other; it means that that 61: * many ExposeCopy events can be completely ignored since the screen has 62: * been fully updated. 63: */ 64: 65: #include <stdio.h> 66: #include "Xlib.h" 67: #include "Xtext.h" 68: 69: #ifndef TRUE 70: #define TRUE 1 71: #define FALSE 0 72: #endif 73: 74: /* Define the width of the left margin */ 75: 76: #define mar_width 2 77: 78: char *calloc(), *malloc(), *realloc(); 79: 80: /* The following variable is sometimes set by TextPutString to temporarily 81: disable screen updating. */ 82: 83: static int dont_update = FALSE; 84: 85: /* TextCreate creates a new window which will use the 86: * specified font. The window is height lines high and width 87: * characters wide. Note that since a variable-width font may be 88: * used, the width is calculated using the average width of the font. 89: * Colors are used as specified. 90: */ 91: 92: TextWindow *TextCreate (width, height, x, y, parent, fontname, 93: bwidth, fgpixel, bgpixel, bordercolor, fastscroll) 94: int height, width, x, y, bwidth, fastscroll; 95: Window parent; 96: char *fontname; 97: int fgpixel, bgpixel; 98: Pixmap bordercolor; 99: { 100: register TextWindow *t; 101: register int i; 102: register FontInfo *f; 103: Window XCreateWindow(); 104: Pixmap bgpixmap; 105: 106: if ((t = (TextWindow *) malloc(sizeof(TextWindow))) == 107: NULL) return NULL; 108: 109: if ((f = t->font = XOpenFont(fontname)) == NULL) { 110: TextDestroy(t); 111: return NULL; 112: } 113: 114: t->fgpixel = fgpixel; 115: t->bgpixel = bgpixel; 116: 117: if ((bgpixmap = XMakeTile(bgpixel)) == NULL) { 118: TextDestroy(t); 119: return NULL; 120: } 121: 122: t->width = width * f->width + mar_width; 123: t->height = height * f->height; 124: 125: t->w = XCreateWindow (parent, x, y, t->width, t->height, 126: bwidth, bordercolor, bgpixmap); 127: if (t->w == NULL) { 128: TextDestroy(t); 129: XFreePixmap(bgpixmap); 130: return NULL; 131: } 132: 133: XFreePixmap(bgpixmap); 134: 135: t->eventmask = ExposeRegion | ExposeCopy | UnmapWindow; 136: /* (ExposeRegion automatically selects ExposeWindow) */ 137: 138: XSelectInput (t->w, t->eventmask); 139: 140: XSetResizeHint (t->w, mar_width, 0, f->width, f->height); 141: t->fastscroll = fastscroll; 142: t->mapped = FALSE; 143: t->num_lines = height; 144: t->num_chars = width; 145: 146: t->first_line = 0; 147: 148: if ((t->lines = (char **) 149: calloc (height, sizeof (char *))) == NULL) { 150: TextDestroy(t); 151: return NULL; 152: } 153: 154: if ((t->line_length = (short *) 155: calloc (height, sizeof (short))) == NULL) { 156: TextDestroy(t); 157: return NULL; 158: } 159: 160: if ((t->line_chars = (short *) 161: calloc (height, sizeof (short))) == NULL) { 162: TextDestroy(t); 163: return NULL; 164: } 165: 166: for (i = 0; i < height; i++) { 167: if ((t->lines[i] = (char *) 168: calloc (width+1, sizeof (char))) == NULL) { 169: TextDestroy(t); 170: return NULL; 171: } 172: } 173: 174: if ((t->scroll_history = calloc(height, sizeof (char))) == NULL) { 175: TextDestroy(t); 176: return NULL; 177: } 178: 179: t->scroll_count = t->scroll_start = t->old_scrolls = 0; 180: TextClear(t); 181: return t; 182: } 183: 184: /* Free all the storage associated with a textwindow */ 185: 186: TextDestroy(t) 187: register TextWindow *t; 188: { 189: register int i; 190: 191: /* Free things in the order we allocated them. If something doesn't 192: exist, don't free it!) */ 193: 194: if (t->font) { 195: if (t->font->fixedwidth == 0) free(t->font->widths); 196: free(t->font); 197: } 198: 199: if (t->w) XDestroyWindow(t->w); 200: 201: if (t->lines) { 202: for (i = 0; i < t->num_lines; i++) { 203: if (t->lines[i]) free(t->lines[i]); 204: } 205: free(t->lines); 206: } 207: 208: if (t->line_length) free (t->line_length); 209: if (t->line_chars) free (t->line_chars); 210: if (t->scroll_history) free (t->scroll_history); 211: 212: /* And finally the data structure itself! */ 213: 214: free (t); 215: } 216: 217: /* Clear out a text window and redisplay */ 218: 219: TextClear(t) 220: register TextWindow *t; 221: { 222: register int i; 223: 224: for (i = 0; i < t->num_lines; i++) { 225: t->lines[i][0] = '\0'; 226: t->line_chars[i] = 0; 227: t->line_length[i] = mar_width; /* Allow a left margin */ 228: } 229: t->last_line = 0; 230: t->last_char = 0; 231: t->next_x = mar_width; /* Allow a left margin */ 232: t->next_y = 0; 233: t->first_line = 0; 234: 235: TextRedisplay(t); 236: } 237: 238: /* Redisplays a text window */ 239: 240: TextRedisplay (t) 241: register TextWindow *t; 242: { 243: if (!t->mapped) return; 244: 245: /* Clear the border area */ 246: 247: XPixSet(t->w, 0, 0, mar_width, t->height, t->bgpixel); 248: 249: Redisplay_lines(t, 0, t->num_lines - 1); 250: 251: /* Any outstanding copies from scrolls can now be ignored */ 252: 253: t->old_scrolls = t->scroll_count; 254: t->scroll_count = t->scroll_start = 0; 255: } 256: 257: Redisplay_lines(t, start, finish) 258: register TextWindow *t; 259: int start, finish; 260: { 261: register int i, j, y, height = t->font->height, x, width; 262: 263: if (finish < 0) return; 264: if (start < 0) start = 0; 265: 266: y = start * height; 267: j = start + t->first_line; 268: 269: for (i = start; i <= finish; i++) { 270: if (j >= t->num_lines) j = 0; 271: 272: if (t->line_chars[j]) { 273: XText (t->w, mar_width, y, t->lines[j], t->line_chars[j], 274: t->font->id, t->fgpixel, t->bgpixel); 275: } 276: 277: x = t->line_length[j]; 278: width = t->width - x; 279: 280: if (width > 0) XPixSet(t->w, x, y, width, height, t->bgpixel); 281: y += height; 282: j++; 283: } 284: } 285: 286: /* Handles an event. If it's not an event it knows how to deal with, 287: returns TRUE, otherwise FALSE. */ 288: 289: int TextEvent(t, e) 290: register TextWindow *t; 291: XEvent *e; 292: { 293: XExposeEvent *ee = (XExposeEvent *) e; 294: int offset; 295: 296: switch (e->type) { 297: case ExposeWindow: 298: if (ee->height != t->height || ee->width != t->width) { 299: Change_text_window_size(t, ee->height / t->font->height, 300: ee->width / t->font->width); 301: } 302: t->mapped = TRUE; 303: TextRedisplay(t); 304: break; 305: 306: case ExposeRegion: 307: /* If there have been more scrolls than there are lines, 308: this stuff has already scrolled off! */ 309: 310: if (t->scroll_count > t->num_lines) return FALSE; 311: 312: /* If this is for an old scroll, ignore it */ 313: 314: if (ee->detail == ExposeCopy && t->old_scrolls) return FALSE; 315: 316: if (t->scroll_count > 0) { 317: offset = t->scroll_history[t->scroll_start]; 318: } else offset = 0; 319: Redisplay_lines(t, ee->y / t->font->height - offset, 320: (ee->y + ee->height - 1) / t->font->height - offset); 321: break; 322: 323: case UnmapWindow: 324: t->mapped = FALSE; 325: break; 326: 327: case ExposeCopy: /* We've finished the events for one scroll */ 328: /* If there are old scrolls, just decrement the count and 329: return */ 330: 331: if (t->old_scrolls) { 332: t->old_scrolls--; 333: return FALSE; 334: } 335: t->scroll_count--; 336: if (t->scroll_count < t->num_lines) { 337: t->scroll_start++; 338: if (t->scroll_start >= t->num_lines) t->scroll_start = 0; 339: } 340: break; 341: 342: default: 343: return TRUE; 344: } 345: return FALSE; 346: } 347: 348: Change_text_window_size (t, new_h, new_w) 349: register TextWindow *t; 350: register int new_h, new_w; 351: { 352: register int i; 353: register char *curline; 354: 355: Normalize(t); /* Rearrange lines so that first_line = 0 */ 356: 357: /* First free up any now extraneous lines */ 358: 359: for (i = new_h; i < t->num_lines; i++) free(t->lines[i]); 360: 361: if ((t->lines = (char **) 362: realloc(t->lines, new_h * sizeof (char *))) == NULL) { 363: return; 364: } 365: 366: if ((t->line_length = (short *) 367: realloc(t->line_length, new_h * sizeof (short))) == NULL) { 368: return; 369: } 370: 371: if ((t->line_chars = (short *) 372: realloc(t->line_chars, new_h * sizeof (short))) == NULL) { 373: return; 374: } 375: 376: if ((t->scroll_history = realloc(t->scroll_history, new_h)) == NULL) { 377: return; 378: } 379: 380: for (i = 0; i < new_h; i++) { 381: if (i < t->num_lines) { 382: if ((curline = t->lines[i] = 383: realloc(t->lines[i], new_w + 1)) == NULL) { 384: return; 385: } 386: 387: if (t->line_chars[i] > new_w) { 388: t->line_chars[i] = new_w; 389: curline[new_w] = '\0'; /* Truncate the line */ 390: t->line_length[i] = mar_width + 391: XStringWidth (curline, t->font, 0, 0); 392: } 393: } else { 394: if ((t->lines[i] = malloc(new_w+1)) == NULL) { 395: return; 396: } 397: t->lines[i][0] = '\0'; 398: t->line_chars[i] = 0; 399: t->line_length[i] = mar_width; 400: } 401: } 402: 403: if (t->last_line >= new_h) { 404: t->last_line = new_h - 1; 405: t->last_char = t->line_chars[t->last_line]; 406: t->next_x = t->line_length[t->last_line]; 407: t->next_y = t->last_line * t->font->height; 408: 409: } else if (t->last_char > new_w) { 410: t->last_char = t->line_chars[t->last_line]; 411: t->next_x = t->line_length[t->last_line]; 412: } 413: 414: t->num_lines = new_h; 415: t->num_chars = new_w; 416: t->height = new_h * t->font->height; 417: t->width = new_w * t->font->width + mar_width; 418: } 419: 420: /* Routine to re-arrange the lines in a window structure so that first_line 421: is equal to 0. */ 422: 423: Normalize(t) 424: register TextWindow *t; 425: { 426: if (t->first_line == 0) return; 427: 428: t->last_line -= t->first_line; 429: if (t->last_line < 0) t->last_line += t->num_lines; 430: 431: Spin_lines(t, 0, t->num_lines-1, t->first_line); 432: 433: t->first_line = 0; 434: } 435: 436: /* Spin lines rotates the m through n lines of the arrays 437: forward offset places. For example, 012345 spun forward 2 is 234501. 438: It's straightforward to spin the first part of the arrays; and we 439: call Spin_lines recursively to do the last offset elements */ 440: 441: /* Actually, it's tail-recursive, so I just use a loop. But I can 442: pretend, can't I? */ 443: 444: Spin_lines(t, m, n, offset) 445: register TextWindow *t; 446: int m, n; 447: register int offset; 448: { 449: register int i; 450: register int temp; /* Temporaries */ 451: register char *tempc; 452: 453: while (1) { 454: if (offset == 0 || offset > n-m) return; 455: 456: for (i = m; i <= n-offset; i++) { 457: temp = t->line_length[i]; 458: t->line_length[i] = t->line_length[offset+i]; 459: t->line_length[offset+i] = temp; 460: 461: temp = t->line_chars[i]; 462: t->line_chars[i] = t->line_chars[offset+i]; 463: t->line_chars[offset+i] = temp; 464: 465: tempc = t->lines[i]; 466: t->lines[i] = t->lines[offset+i]; 467: t->lines[offset+i] = tempc; 468: } 469: 470: /* Spin_lines(t, n-offset+1, n, offset - ((n-m+1) % offset)); */ 471: 472: temp = m; 473: m = n - offset + 1; 474: offset -= (n - temp + 1) % offset; 475: } 476: } 477: 478: /* Routine to put a string in a text window. If fastscroll is 479: set in the TextWindow structure, a single block scroll is done instead 480: of scrolling at each newline. */ 481: 482: #define verybig 10000 /* Amount to scroll if we should refresh instead */ 483: 484: TextPutString (t, str) 485: register TextWindow *t; 486: register char *str; 487: { 488: register char *ch = str; 489: register char oldch; 490: int jump = t->fastscroll; /* Whether to do jump scrolling */ 491: int newlines, scroll; 492: 493: if (jump) jump = Count_lines (t, str, &newlines, &scroll); 494: 495: while (1) { 496: while (*ch != '\0' && *ch != '\n') ch++; 497: if (ch != str) { 498: oldch = *ch; 499: *ch = '\0'; 500: Do_text_string (t, str); 501: *ch = oldch; 502: } 503: if (*ch == '\0') break; 504: if (jump && newlines == scroll) { 505: Clear_lines (t, newlines); 506: dont_update = TRUE; /* Stop updating now */ 507: } 508: newlines--; 509: TextPutChar (t, *ch); 510: str = ++ch; 511: } 512: if (t->mapped && jump) { 513: if (scroll != verybig) Scroll_text_window (t, scroll); 514: else TextRedisplay (t); 515: } 516: dont_update = FALSE; 517: } 518: 519: /* Count the number of lines in str, calculate how much scrolling 520: will be needed, and return whether this amount is positive */ 521: 522: int Count_lines (t, str, newlines, scroll) 523: register TextWindow *t; 524: register char *str; 525: int *newlines, *scroll; 526: { 527: register int num_lines = 0; 528: register int lines_left, height = t->num_lines; 529: 530: *scroll = 0; 531: 532: while (*str) { 533: if (*str++ == '\n') num_lines++; 534: } 535: 536: *newlines = num_lines; 537: 538: if (num_lines <= 1) return FALSE; /* Don't bother jump scrolling */ 539: 540: /* Would this fill the screen? */ 541: 542: if (num_lines >= height) { 543: *scroll = verybig; 544: return TRUE; 545: } 546: 547: /* Calculate the number of lines left in the window */ 548: 549: lines_left = height - (t->last_line - t->first_line + 1); 550: if (lines_left >= height) lines_left -= height; 551: 552: /* Figure out how many lines to scroll */ 553: 554: num_lines -= lines_left; 555: 556: if (num_lines <= 0) return FALSE; /* Enough room already */ 557: 558: *scroll = num_lines; 559: return TRUE; 560: } 561: 562: /* Clear a number of lines in the window data structure */ 563: 564: Clear_lines (t, scroll) 565: register TextWindow *t; 566: register int scroll; 567: { 568: register int i, start = t->first_line; 569: register int height = t->num_lines; 570: 571: /* If this would fill the screen, clear it instead */ 572: 573: if (scroll >= t->height ) { 574: TextClear (t); 575: return; 576: } 577: 578: /* Shift the contents */ 579: 580: t->first_line += scroll; 581: if (t->first_line >= height) t->first_line -= height; 582: 583: /* Now clear the blank lines */ 584: 585: for (i = 0; i < scroll; i++) { 586: t->lines[start][0] = '\0'; 587: t->line_chars[start] = 0; 588: t->line_length[start] = mar_width; /* Allow a left margin */ 589: start++; 590: if (start >= height) start = 0; 591: } 592: } 593: 594: /* Store the characters of a string in the window and update the screen, 595: but only if dont_update isn't set */ 596: 597: Do_text_string (t, str) 598: register TextWindow *t; 599: char *str; 600: { 601: register char *ch = str; 602: register char *curline = t->lines[t->last_line]; 603: register int curchar = t->last_char; 604: register int x = t->next_x; 605: register FontInfo *f = t->font; 606: int start_x = t->next_x, start = curchar, 607: minch = f->firstchar, maxch = f->lastchar; 608: 609: /* First store the characters in the line */ 610: 611: while (*ch != '\0' && curchar < t->num_chars) { 612: curline[curchar] = *ch; 613: if (*ch >= minch && *ch <= maxch) { 614: x += f->fixedwidth ? f->width : f->widths[*ch - minch]; 615: } 616: curchar++; 617: ch++; 618: } 619: 620: curline[curchar] = '\0'; 621: t->line_chars[t->last_line] = t->last_char = curchar; 622: t->line_length[t->last_line] = t->next_x = x; 623: 624: if (dont_update || !t->mapped) return; 625: 626: /* And then update the screen */ 627: 628: if (start < t->num_chars) { 629: XText (t->w, start_x, t->next_y, str, curchar-start, 630: f->id, t->fgpixel, t->bgpixel); 631: } 632: } 633: 634: /* Textputchar displays a character in the text window. It 635: * responds to \n as a special character and just displays anything else. 636: */ 637: 638: TextPutChar (t, ch) 639: register TextWindow *t; 640: char ch; 641: { 642: register int i, height = t->num_lines; 643: register char *curline = t->lines[t->last_line]; 644: register FontInfo *f = t->font; 645: 646: switch (ch) { 647: case '\0': /* NULL */ 648: break; 649: 650: case '\n': /* newline */ 651: if (t->last_line == t->first_line - 1 || 652: (t->last_line == height - 1 && t->first_line == 0)) { 653: 654: /* The screen is full...clear out the first line */ 655: 656: t->lines[t->first_line][0] = '\0'; 657: t->line_chars[t->first_line] = 0; 658: t->line_length[t->first_line] = mar_width; 659: 660: t->first_line++; /* And advance it */ 661: if (t->first_line == height) t->first_line = 0; 662: 663: if (!dont_update && t->mapped) Scroll_text_window (t, 1); 664: 665: } else if (!dont_update) t->next_y += f->height; 666: 667: t->last_line++; 668: if (t->last_line == height) t->last_line = 0; 669: 670: t->last_char = 0; 671: t->next_x = mar_width; 672: break; 673: 674: default: /* Just insert the character */ 675: t->last_char++; 676: t->line_chars[t->last_line]++; 677: if (t->last_char > t->num_chars) break; 678: 679: curline[t->last_char] = ch; 680: curline[t->last_char+1] = '\0'; 681: 682: if (!dont_update && t->mapped) { 683: XText(t->w, t->next_x, t->next_y, &ch, 1, 684: f->id, t->fgpixel, t->bgpixel); 685: } 686: if (ch <= f->firstchar && ch >= f->lastchar) { 687: t->line_length[t->last_line] = t->next_x += 688: (f->fixedwidth ? f->width : 689: f->widths[ch - f->lastchar]); 690: } 691: break; 692: } 693: } 694: 695: /* This procedure moves the contents of a text window up n lines. 696: */ 697: 698: Scroll_text_window (t, n) 699: register TextWindow *t; 700: register int n; 701: { 702: register int i, y, x, width, j; 703: int height = t->font->height; 704: int scrollsize = n * height; 705: 706: /* First shift up the contents */ 707: 708: XMoveArea(t->w, 0, scrollsize, 0, 0, t->width, t->height-scrollsize); 709: 710: /* Now redisplay the bottom n lines */ 711: 712: y = height * (t->num_lines - n); 713: i = t->first_line - n; 714: if (i < 0) i += t->num_lines; 715: 716: for (j = 0; j < n; j++) { 717: if (t->line_chars[i]) { 718: XText (t->w, mar_width, y, t->lines[i], t->line_chars[i], 719: t->font->id, t->fgpixel, t->bgpixel); 720: } 721: x = t->line_length[i]; 722: width = t->width - x; 723: 724: if (width > 0) XPixSet(t->w, x, y, width, height, t->bgpixel); 725: y += height; 726: i++; 727: if (i == t->num_lines) i = 0; 728: } 729: 730: /* Add the current scroll to all values in the scroll history, 731: then add a new entry at the end (the history wraps!) */ 732: 733: i = t->scroll_start; 734: 735: for (j = 0; j < t->scroll_count; j++) { 736: t->scroll_history[i] += n; 737: i++; 738: if (i >= t->num_lines) i = 0; 739: } 740: t->scroll_count++; 741: t->scroll_history[i] = n; 742: 743: if (t->scroll_count > t->num_lines) t->scroll_start++; /* trash one */ 744: } 745: 746: #define TEXT_BUFSIZE 2048 747: 748: TextPrintf(t, format, args) 749: TextWindow *t; 750: char *format; 751: { 752: char buffer[TEXT_BUFSIZE+1]; 753: struct _iobuf _strbuf; 754: 755: _strbuf._flag = _IOWRT+_IOSTRG; 756: _strbuf._ptr = buffer; 757: _strbuf._cnt = TEXT_BUFSIZE; 758: _doprnt(format, &args, &_strbuf); 759: _strbuf._cnt++; /* Be sure there's room for the \0 */ 760: putc('\0', &_strbuf); 761: TextPutString(t, buffer); 762: }