Commit | Line | Data |
---|---|---|
cb290b32 C |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | \f | |
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 | } |