Added OpenMotif 2.2.3 and Lesstif 0.93.94 to the "known good" lists.
[nedit.git] / source / textDisp.c
blob04fd56b3a48afef61e1fff0607c3514d159eb448
1 static const char CVSID[] = "$Id: textDisp.c,v 1.56 2004/04/01 02:50:29 tksoh Exp $";
2 /*******************************************************************************
3 * *
4 * textDisp.c - Display text from a text buffer *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. *
12 * *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
20 * Place, Suite 330, Boston, MA 02111-1307 USA *
21 * *
22 * Nirvana Text Editor *
23 * June 15, 1995 *
24 * *
25 * Written by Mark Edel *
26 * *
27 *******************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
33 #include "textDisp.h"
34 #include "textBuf.h"
35 #include "text.h"
36 #include "textP.h"
37 #include "nedit.h"
38 #include "calltips.h"
39 #include "highlight.h"
40 #include "rangeset.h"
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <limits.h>
46 #ifdef VMS
47 #include "../util/VMSparam.h"
48 #else
49 #ifndef __MVS__
50 #include <sys/param.h>
51 #endif
52 #endif /*VMS*/
54 #include <Xm/Xm.h>
55 #include <Xm/ScrolledW.h>
56 #include <Xm/ScrollBar.h>
57 #include <Xm/Label.h>
58 #include <X11/Shell.h>
60 #ifdef HAVE_DEBUG_H
61 #include "../debug.h"
62 #endif
64 #define TOP_MARGIN 1
65 #define BOTTOM_MARGIN 1
66 #define LEFT_MARGIN 3
67 #define RIGHT_MARGIN 3
69 /* Masks for text drawing methods. These are or'd together to form an
70 integer which describes what drawing calls to use to draw a string */
71 #define FILL_SHIFT 8
72 #define SECONDARY_SHIFT 9
73 #define PRIMARY_SHIFT 10
74 #define HIGHLIGHT_SHIFT 11
75 #define STYLE_LOOKUP_SHIFT 0
76 #define BACKLIGHT_SHIFT 12
78 #define FILL_MASK (1 << FILL_SHIFT)
79 #define SECONDARY_MASK (1 << SECONDARY_SHIFT)
80 #define PRIMARY_MASK (1 << PRIMARY_SHIFT)
81 #define HIGHLIGHT_MASK (1 << HIGHLIGHT_SHIFT)
82 #define STYLE_LOOKUP_MASK (0xff << STYLE_LOOKUP_SHIFT)
83 #define BACKLIGHT_MASK (0xff << BACKLIGHT_SHIFT)
85 #define RANGESET_SHIFT (20)
86 #define RANGESET_MASK (0x3F << RANGESET_SHIFT)
88 /* If you use both 32-Bit Style mask layout:
89 Bits +----------------+----------------+----------------+----------------+
90 hex |1F1E1D1C1B1A1918|1716151413121110| F E D C B A 9 8| 7 6 5 4 3 2 1 0|
91 dec |3130292827262524|2322212019181716|151413121110 9 8| 7 6 5 4 3 2 1 0|
92 +----------------+----------------+----------------+----------------+
93 Type | r r| r r r r b b b b| b b b b H 1 2 F| s s s s s s s s|
94 +----------------+----------------+----------------+----------------+
95 where: s - style lookup value (8 bits)
96 F - fill (1 bit)
97 2 - secondary selection (1 bit)
98 1 - primary selection (1 bit)
99 H - highlight (1 bit)
100 b - backlighting index (8 bits)
101 r - rangeset index (6 bits)
102 This leaves 6 "unused" bits */
104 /* Maximum displayable line length (how many characters will fit across the
105 widest window). This amount of memory is temporarily allocated from the
106 stack in the redisplayLine routine for drawing strings */
107 #define MAX_DISP_LINE_LEN 1000
109 /* Macro for getting the TextPart from a textD */
110 #define TEXT_OF_TEXTD(t) (((TextWidget)((t)->w))->text)
112 enum positionTypes {CURSOR_POS, CHARACTER_POS};
114 static void updateLineStarts(textDisp *textD, int pos, int charsInserted,
115 int charsDeleted, int linesInserted, int linesDeleted, int *scrolled);
116 static void offsetLineStarts(textDisp *textD, int newTopLineNum);
117 static void calcLineStarts(textDisp *textD, int startLine, int endLine);
118 static void calcLastChar(textDisp *textD);
119 static int posToVisibleLineNum(textDisp *textD, int pos, int *lineNum);
120 static void redisplayLine(textDisp *textD, int visLineNum, int leftClip,
121 int rightClip, int leftCharIndex, int rightCharIndex);
122 static void drawString(textDisp *textD, int style, int x, int y, int toX,
123 char *string, int nChars);
124 static void clearRect(textDisp *textD, GC gc, int x, int y,
125 int width, int height);
126 static void drawCursor(textDisp *textD, int x, int y);
127 static int styleOfPos(textDisp *textD, int lineStartPos,
128 int lineLen, int lineIndex, int dispIndex, int thisChar);
129 static int stringWidth(textDisp *textD, char *string, int length, int style);
130 static int inSelection(selection *sel, int pos, int lineStartPos,
131 int dispIndex);
132 static int xyToPos(textDisp *textD, int x, int y, int posType);
133 static void xyToUnconstrainedPos(textDisp *textD, int x, int y, int *row,
134 int *column, int posType);
135 static void bufPreDeleteCB(int pos, int nDeleted, void *cbArg);
136 static void bufModifiedCB(int pos, int nInserted, int nDeleted,
137 int nRestyled, char *deletedText, void *cbArg);
138 static void setScroll(textDisp *textD, int topLineNum, int horizOffset,
139 int updateVScrollBar, int updateHScrollBar);
140 static void hScrollCB(Widget w, XtPointer clientData, XtPointer callData);
141 static void vScrollCB(Widget w, XtPointer clientData, XtPointer callData);
142 static void visibilityEH(Widget w, XtPointer data, XEvent *event,
143 Boolean *continueDispatch);
144 static void redrawLineNumbers(textDisp *textD, int clearAll);
145 static void updateVScrollBarRange(textDisp *textD);
146 static int updateHScrollBarRange(textDisp *textD);
147 static int max(int i1, int i2);
148 static int min(int i1, int i2);
149 static int countLines(char *string);
150 static int measureVisLine(textDisp *textD, int visLineNum);
151 static int emptyLinesVisible(textDisp *textD);
152 static void blankCursorProtrusions(textDisp *textD);
153 static void allocateFixedFontGCs(textDisp *textD, XFontStruct *fontStruct,
154 Pixel bgPixel, Pixel fgPixel, Pixel selectFGPixel, Pixel selectBGPixel,
155 Pixel highlightFGPixel, Pixel highlightBGPixel, Pixel lineNumFGPixel);
156 static GC allocateGC(Widget w, unsigned long valueMask,
157 unsigned long foreground, unsigned long background, Font font,
158 unsigned long dynamicMask, unsigned long dontCareMask);
159 static void releaseGC(Widget w, GC gc);
160 static void resetClipRectangles(textDisp *textD);
161 static int visLineLength(textDisp *textD, int visLineNum);
162 static void measureDeletedLines(textDisp *textD, int pos, int nDeleted);
163 static void findWrapRange(textDisp *textD, char *deletedText, int pos,
164 int nInserted, int nDeleted, int *modRangeStart, int *modRangeEnd,
165 int *linesInserted, int *linesDeleted);
166 static void wrappedLineCounter(textDisp *textD, textBuffer *buf, int startPos,
167 int maxPos, int maxLines, int startPosIsLineStart, int styleBufOffset,
168 int *retPos, int *retLines, int *retLineStart, int *retLineEnd);
169 static void findLineEnd(textDisp *textD, int startPos, int startPosIsLineStart,
170 int *lineEnd, int *nextLineStart);
171 static int wrapUsesCharacter(textDisp *textD, int lineEndPos);
172 static void hideOrShowHScrollBar(textDisp *textD);
173 static int rangeTouchesRectSel(selection *sel, int rangeStart, int rangeEnd);
174 static void extendRangeForStyleMods(textDisp *textD, int *start, int *end);
175 static int getAbsTopLineNum(textDisp *textD);
176 static void offsetAbsLineNum(textDisp *textD, int oldFirstChar);
177 static int maintainingAbsTopLineNum(textDisp *textD);
178 static void resetAbsLineNum(textDisp *textD);
179 static int measurePropChar(textDisp *textD, char c, int colNum, int pos);
180 static Pixel allocBGColor(Widget w, char *colorName, int *ok);
181 Pixel getRangesetColor(textDisp *textD, int ind, Pixel bground);
183 textDisp *TextDCreate(Widget widget, Widget hScrollBar, Widget vScrollBar,
184 Position left, Position top, Position width, Position height,
185 Position lineNumLeft, Position lineNumWidth, textBuffer *buffer,
186 XFontStruct *fontStruct, Pixel bgPixel, Pixel fgPixel,
187 Pixel selectFGPixel, Pixel selectBGPixel, Pixel highlightFGPixel,
188 Pixel highlightBGPixel, Pixel cursorFGPixel, Pixel lineNumFGPixel,
189 int continuousWrap, int wrapMargin, XmString bgClassString,
190 Pixel calltipFGPixel, Pixel calltipBGPixel)
192 textDisp *textD;
193 XGCValues gcValues;
194 int i;
196 textD = (textDisp *)XtMalloc(sizeof(textDisp));
197 textD->w = widget;
198 textD->top = top;
199 textD->left = left;
200 textD->width = width;
201 textD->height = height;
202 textD->cursorOn = True;
203 textD->cursorPos = 0;
204 textD->cursorX = -100;
205 textD->cursorY = -100;
206 textD->cursorToHint = NO_HINT;
207 textD->cursorStyle = NORMAL_CURSOR;
208 textD->cursorPreferredCol = -1;
209 textD->buffer = buffer;
210 textD->firstChar = 0;
211 textD->lastChar = 0;
212 textD->nBufferLines = 0;
213 textD->topLineNum = 1;
214 textD->absTopLineNum = 1;
215 textD->needAbsTopLineNum = False;
216 textD->horizOffset = 0;
217 textD->visibility = VisibilityUnobscured;
218 textD->hScrollBar = hScrollBar;
219 textD->vScrollBar = vScrollBar;
220 textD->fontStruct = fontStruct;
221 textD->ascent = fontStruct->ascent;
222 textD->descent = fontStruct->descent;
223 textD->fixedFontWidth = fontStruct->min_bounds.width ==
224 fontStruct->max_bounds.width ? fontStruct->min_bounds.width : -1;
225 textD->styleBuffer = NULL;
226 textD->styleTable = NULL;
227 textD->nStyles = 0;
228 textD->bgPixel = bgPixel;
229 textD->fgPixel = fgPixel;
230 textD->selectFGPixel = selectFGPixel;
231 textD->highlightFGPixel = highlightFGPixel;
232 textD->selectBGPixel = selectBGPixel;
233 textD->highlightBGPixel = highlightBGPixel;
234 textD->lineNumFGPixel = lineNumFGPixel;
235 textD->cursorFGPixel = cursorFGPixel;
236 textD->wrapMargin = wrapMargin;
237 textD->continuousWrap = continuousWrap;
238 allocateFixedFontGCs(textD, fontStruct, bgPixel, fgPixel, selectFGPixel,
239 selectBGPixel, highlightFGPixel, highlightBGPixel, lineNumFGPixel);
240 textD->styleGC = allocateGC(textD->w, 0, 0, 0, fontStruct->fid,
241 GCClipMask|GCForeground|GCBackground, GCArcMode);
242 textD->lineNumLeft = lineNumLeft;
243 textD->lineNumWidth = lineNumWidth;
244 textD->nVisibleLines = (height - 1) / (textD->ascent + textD->descent) + 1;
245 gcValues.foreground = cursorFGPixel;
246 textD->cursorFGGC = XtGetGC(widget, GCForeground, &gcValues);
247 textD->lineStarts = (int *)XtMalloc(sizeof(int) * textD->nVisibleLines);
248 textD->lineStarts[0] = 0;
249 textD->calltipW = NULL;
250 textD->calltipShell = NULL;
251 textD->calltip.ID = 0;
252 textD->calltipFGPixel = calltipFGPixel;
253 textD->calltipBGPixel = calltipBGPixel;
254 for (i=1; i<textD->nVisibleLines; i++)
255 textD->lineStarts[i] = -1;
256 textD->bgClassPixel = NULL;
257 textD->bgClass = NULL;
258 TextDSetupBGClasses(widget, bgClassString, &textD->bgClassPixel,
259 &textD->bgClass, bgPixel);
260 textD->suppressResync = 0;
261 textD->nLinesDeleted = 0;
262 textD->modifyingTabDist = 0;
263 textD->pointerHidden = False;
264 textD->graphicsExposeQueue = NULL;
266 /* Attach an event handler to the widget so we can know the visibility
267 (used for choosing the fastest drawing method) */
268 XtAddEventHandler(widget, VisibilityChangeMask, False,
269 visibilityEH, textD);
271 /* Attach the callback to the text buffer for receiving modification
272 information */
273 if (buffer != NULL) {
274 BufAddModifyCB(buffer, bufModifiedCB, textD);
275 BufAddPreDeleteCB(buffer, bufPreDeleteCB, textD);
278 /* Initialize the scroll bars and attach movement callbacks */
279 if (vScrollBar != NULL) {
280 XtVaSetValues(vScrollBar, XmNminimum, 1, XmNmaximum, 2,
281 XmNsliderSize, 1, XmNrepeatDelay, 10, XmNvalue, 1, NULL);
282 XtAddCallback(vScrollBar, XmNdragCallback, vScrollCB, (XtPointer)textD);
283 XtAddCallback(vScrollBar, XmNvalueChangedCallback, vScrollCB,
284 (XtPointer)textD);
286 if (hScrollBar != NULL) {
287 XtVaSetValues(hScrollBar, XmNminimum, 0, XmNmaximum, 1,
288 XmNsliderSize, 1, XmNrepeatDelay, 10, XmNvalue, 0,
289 XmNincrement, fontStruct->max_bounds.width, NULL);
290 XtAddCallback(hScrollBar, XmNdragCallback, hScrollCB, (XtPointer)textD);
291 XtAddCallback(hScrollBar, XmNvalueChangedCallback, hScrollCB,
292 (XtPointer)textD);
295 /* Update the display to reflect the contents of the buffer */
296 if (buffer != NULL)
297 bufModifiedCB(0, buffer->length, 0, 0, NULL, textD);
299 /* Decide if the horizontal scroll bar needs to be visible */
300 hideOrShowHScrollBar(textD);
302 return textD;
306 ** Free a text display and release its associated memory. Note, the text
307 ** BUFFER that the text display displays is a separate entity and is not
308 ** freed, nor are the style buffer or style table.
310 void TextDFree(textDisp *textD)
312 BufRemoveModifyCB(textD->buffer, bufModifiedCB, textD);
313 BufRemovePreDeleteCB(textD->buffer, bufPreDeleteCB, textD);
314 releaseGC(textD->w, textD->gc);
315 releaseGC(textD->w, textD->selectGC);
316 releaseGC(textD->w, textD->highlightGC);
317 releaseGC(textD->w, textD->selectBGGC);
318 releaseGC(textD->w, textD->highlightBGGC);
319 releaseGC(textD->w, textD->styleGC);
320 releaseGC(textD->w, textD->lineNumGC);
321 XtFree((char *)textD->lineStarts);
322 while (TextDPopGraphicExposeQueueEntry(textD)) {
324 XtFree((char *)textD->bgClassPixel);
325 XtFree((char *)textD->bgClass);
326 XtFree((char *)textD);
330 ** Attach a text buffer to display, replacing the current buffer (if any)
332 void TextDSetBuffer(textDisp *textD, textBuffer *buffer)
334 /* If the text display is already displaying a buffer, clear it off
335 of the display and remove our callback from it */
336 if (textD->buffer != NULL) {
337 bufModifiedCB(0, 0, textD->buffer->length, 0, NULL, textD);
338 BufRemoveModifyCB(textD->buffer, bufModifiedCB, textD);
339 BufRemovePreDeleteCB(textD->buffer, bufPreDeleteCB, textD);
342 /* Add the buffer to the display, and attach a callback to the buffer for
343 receiving modification information when the buffer contents change */
344 textD->buffer = buffer;
345 BufAddModifyCB(buffer, bufModifiedCB, textD);
346 BufAddPreDeleteCB(buffer, bufPreDeleteCB, textD);
348 /* Update the display */
349 bufModifiedCB(0, buffer->length, 0, 0, NULL, textD);
353 ** return the displayed text buffer
355 textBuffer *TextDGetBuffer(textDisp *textD)
357 return textD->buffer;
361 ** Attach (or remove) highlight information in text display and redisplay.
362 ** Highlighting information consists of a style buffer which parallels the
363 ** normal text buffer, but codes font and color information for the display;
364 ** a style table which translates style buffer codes (indexed by buffer
365 ** character - 65 (ASCII code for 'A')) into fonts and colors; and a callback
366 ** mechanism for as-needed highlighting, triggered by a style buffer entry of
367 ** "unfinishedStyle". Style buffer can trigger additional redisplay during
368 ** a normal buffer modification if the buffer contains a primary selection
369 ** (see extendRangeForStyleMods for more information on this protocol).
371 ** Style buffers, tables and their associated memory are managed by the caller.
373 void TextDAttachHighlightData(textDisp *textD, textBuffer *styleBuffer,
374 styleTableEntry *styleTable, int nStyles, char unfinishedStyle,
375 unfinishedStyleCBProc unfinishedHighlightCB, void *cbArg)
377 textD->styleBuffer = styleBuffer;
378 textD->styleTable = styleTable;
379 textD->nStyles = nStyles;
380 textD->unfinishedStyle = unfinishedStyle;
381 textD->unfinishedHighlightCB = unfinishedHighlightCB;
382 textD->highlightCBArg = cbArg;
384 /* Call TextDSetFont to combine font information from style table and
385 primary font, adjust font-related parameters, and then redisplay */
386 TextDSetFont(textD, textD->fontStruct);
390 /* Change the (non syntax-highlit) colors */
391 void TextDSetColors(textDisp *textD, Pixel textFgP, Pixel textBgP,
392 Pixel selectFgP, Pixel selectBgP, Pixel hiliteFgP, Pixel hiliteBgP,
393 Pixel lineNoFgP, Pixel cursorFgP)
395 XGCValues values;
396 Display *d = XtDisplay(textD->w);
398 /* Update the stored pixels */
399 textD->fgPixel = textFgP;
400 textD->bgPixel = textBgP;
401 textD->selectFGPixel = selectFgP;
402 textD->selectBGPixel = selectBgP;
403 textD->highlightFGPixel = hiliteFgP;
404 textD->highlightBGPixel = hiliteBgP;
405 textD->lineNumFGPixel = lineNoFgP;
406 textD->cursorFGPixel = cursorFgP;
408 releaseGC(textD->w, textD->gc);
409 releaseGC(textD->w, textD->selectGC);
410 releaseGC(textD->w, textD->selectBGGC);
411 releaseGC(textD->w, textD->highlightGC);
412 releaseGC(textD->w, textD->highlightBGGC);
413 releaseGC(textD->w, textD->lineNumGC);
414 allocateFixedFontGCs(textD, textD->fontStruct, textBgP, textFgP, selectFgP,
415 selectBgP, hiliteFgP, hiliteBgP, lineNoFgP);
417 /* Change the cursor GC (the cursor GC is not shared). */
418 values.foreground = cursorFgP;
419 XChangeGC( d, textD->cursorFGGC, GCForeground, &values );
421 /* Redisplay */
422 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
423 textD->height);
424 redrawLineNumbers(textD, True);
428 ** Change the (non highlight) font
430 void TextDSetFont(textDisp *textD, XFontStruct *fontStruct)
432 Display *display = XtDisplay(textD->w);
433 int i, maxAscent = fontStruct->ascent, maxDescent = fontStruct->descent;
434 int width, height, fontWidth;
435 Pixel bgPixel, fgPixel, selectFGPixel, selectBGPixel;
436 Pixel highlightFGPixel, highlightBGPixel, lineNumFGPixel;
437 XGCValues values;
438 XFontStruct *styleFont;
440 /* If font size changes, cursor will be redrawn in a new position */
441 blankCursorProtrusions(textD);
443 /* If there is a (syntax highlighting) style table in use, find the new
444 maximum font height for this text display */
445 for (i=0; i<textD->nStyles; i++) {
446 styleFont = textD->styleTable[i].font;
447 if (styleFont != NULL && styleFont->ascent > maxAscent)
448 maxAscent = styleFont->ascent;
449 if (styleFont != NULL && styleFont->descent > maxDescent)
450 maxDescent = styleFont->descent;
452 textD->ascent = maxAscent;
453 textD->descent = maxDescent;
455 /* If all of the current fonts are fixed and match in width, compute */
456 fontWidth = fontStruct->max_bounds.width;
457 if (fontWidth != fontStruct->min_bounds.width)
458 fontWidth = -1;
459 else {
460 for (i=0; i<textD->nStyles; i++) {
461 styleFont = textD->styleTable[i].font;
462 if (styleFont != NULL &&
463 (styleFont->max_bounds.width != fontWidth ||
464 styleFont->max_bounds.width != styleFont->min_bounds.width))
465 fontWidth = -1;
468 textD->fixedFontWidth = fontWidth;
470 /* Don't let the height dip below one line, or bad things can happen */
471 if (textD->height < maxAscent + maxDescent)
472 textD->height = maxAscent + maxDescent;
474 /* Change the font. In most cases, this means re-allocating the
475 affected GCs (they are shared with other widgets, and if the primary
476 font changes, must be re-allocated to change it). Unfortunately,
477 this requres recovering all of the colors from the existing GCs */
478 textD->fontStruct = fontStruct;
479 XGetGCValues(display, textD->gc, GCForeground|GCBackground, &values);
480 fgPixel = values.foreground;
481 bgPixel = values.background;
482 XGetGCValues(display, textD->selectGC, GCForeground|GCBackground, &values);
483 selectFGPixel = values.foreground;
484 selectBGPixel = values.background;
485 XGetGCValues(display, textD->highlightGC,GCForeground|GCBackground,&values);
486 highlightFGPixel = values.foreground;
487 highlightBGPixel = values.background;
488 XGetGCValues(display, textD->lineNumGC, GCForeground, &values);
489 lineNumFGPixel = values.foreground;
490 releaseGC(textD->w, textD->gc);
491 releaseGC(textD->w, textD->selectGC);
492 releaseGC(textD->w, textD->highlightGC);
493 releaseGC(textD->w, textD->selectBGGC);
494 releaseGC(textD->w, textD->highlightBGGC);
495 releaseGC(textD->w, textD->lineNumGC);
496 allocateFixedFontGCs(textD, fontStruct, bgPixel, fgPixel, selectFGPixel,
497 selectBGPixel, highlightFGPixel, highlightBGPixel, lineNumFGPixel);
498 XSetFont(display, textD->styleGC, fontStruct->fid);
500 /* Do a full resize to force recalculation of font related parameters */
501 width = textD->width;
502 height = textD->height;
503 textD->width = textD->height = 0;
504 TextDResize(textD, width, height);
506 /* if the shell window doesn't get resized, and the new fonts are
507 of smaller sizes, sometime we get some residual text on the
508 blank space at the bottom part of text area. Clear it here. */
509 clearRect(textD, textD->gc, textD->left,
510 textD->top + textD->height - maxAscent - maxDescent,
511 textD->width, maxAscent + maxDescent);
513 /* Redisplay */
514 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
515 textD->height);
517 /* Clean up line number area in case spacing has changed */
518 redrawLineNumbers(textD, True);
521 int TextDMinFontWidth(textDisp *textD, Boolean considerStyles)
523 int fontWidth = textD->fontStruct->max_bounds.width;
524 int i;
526 if (considerStyles) {
527 for (i = 0; i < textD->nStyles; ++i) {
528 int thisWidth = (textD->styleTable[i].font)->min_bounds.width;
529 if (thisWidth < fontWidth) {
530 fontWidth = thisWidth;
534 return(fontWidth);
537 int TextDMaxFontWidth(textDisp *textD, Boolean considerStyles)
539 int fontWidth = textD->fontStruct->max_bounds.width;
540 int i;
542 if (considerStyles) {
543 for (i = 0; i < textD->nStyles; ++i) {
544 int thisWidth = (textD->styleTable[i].font)->max_bounds.width;
545 if (thisWidth > fontWidth) {
546 fontWidth = thisWidth;
550 return(fontWidth);
554 ** Change the size of the displayed text area
556 void TextDResize(textDisp *textD, int width, int height)
558 int oldVisibleLines = textD->nVisibleLines;
559 int canRedraw = XtWindow(textD->w) != 0;
560 int newVisibleLines = height / (textD->ascent + textD->descent);
561 int redrawAll = False;
562 int oldWidth = textD->width;
563 int exactHeight = height - height % (textD->ascent + textD->descent);
565 textD->width = width;
566 textD->height = height;
568 /* In continuous wrap mode, a change in width affects the total number of
569 lines in the buffer, and can leave the top line number incorrect, and
570 the top character no longer pointing at a valid line start */
571 if (textD->continuousWrap && textD->wrapMargin==0 && width!=oldWidth) {
572 int oldFirstChar = textD->firstChar;
573 textD->nBufferLines = TextDCountLines(textD, 0, textD->buffer->length,
574 True);
575 textD->firstChar = TextDStartOfLine(textD, textD->firstChar);
576 textD->topLineNum = TextDCountLines(textD, 0, textD->firstChar, True)+1;
577 redrawAll = True;
578 offsetAbsLineNum(textD, oldFirstChar);
581 /* reallocate and update the line starts array, which may have changed
582 size and/or contents. (contents can change in continuous wrap mode
583 when the width changes, even without a change in height) */
584 if (oldVisibleLines < newVisibleLines) {
585 XtFree((char *)textD->lineStarts);
586 textD->lineStarts = (int *)XtMalloc(sizeof(int) * newVisibleLines);
588 textD->nVisibleLines = newVisibleLines;
589 calcLineStarts(textD, 0, newVisibleLines);
590 calcLastChar(textD);
592 /* if the window became shorter, there may be partially drawn
593 text left at the bottom edge, which must be cleaned up */
594 if (canRedraw && oldVisibleLines>newVisibleLines && exactHeight!=height)
595 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->left,
596 textD->top + exactHeight, textD->width,
597 height - exactHeight, False);
599 /* if the window became taller, there may be an opportunity to display
600 more text by scrolling down */
601 if (canRedraw && oldVisibleLines < newVisibleLines && textD->topLineNum +
602 textD->nVisibleLines > textD->nBufferLines)
603 setScroll(textD, max(1, textD->nBufferLines - textD->nVisibleLines +
604 2 + TEXT_OF_TEXTD(textD).cursorVPadding),
605 textD->horizOffset, False, False);
607 /* Update the scroll bar page increment size (as well as other scroll
608 bar parameters. If updating the horizontal range caused scrolling,
609 redraw */
610 updateVScrollBarRange(textD);
611 if (updateHScrollBarRange(textD))
612 redrawAll = True;
614 /* If a full redraw is needed */
615 if (redrawAll && canRedraw)
616 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
617 textD->height);
619 /* Decide if the horizontal scroll bar needs to be visible */
620 hideOrShowHScrollBar(textD);
622 /* Refresh the line number display to draw more line numbers, or
623 erase extras */
624 redrawLineNumbers(textD, True);
626 /* Redraw the calltip */
627 TextDRedrawCalltip(textD, 0);
631 ** Refresh a rectangle of the text display. left and top are in coordinates of
632 ** the text drawing window
634 void TextDRedisplayRect(textDisp *textD, int left, int top, int width,
635 int height)
637 int fontHeight, firstLine, lastLine, line;
639 /* find the line number range of the display */
640 fontHeight = textD->ascent + textD->descent;
641 firstLine = (top - textD->top - fontHeight + 1) / fontHeight;
642 lastLine = (top + height - textD->top) / fontHeight;
644 /* If the graphics contexts are shared using XtAllocateGC, their
645 clipping rectangles may have changed since the last use */
646 resetClipRectangles(textD);
648 /* draw the lines of text */
649 for (line=firstLine; line<=lastLine; line++)
650 redisplayLine(textD, line, left, left+width, 0, INT_MAX);
652 /* draw the line numbers if exposed area includes them */
653 if (textD->lineNumWidth != 0 && left <= textD->lineNumLeft + textD->lineNumWidth)
654 redrawLineNumbers(textD, False);
658 ** Refresh all of the text between buffer positions "start" and "end"
659 ** not including the character at the position "end".
660 ** If end points beyond the end of the buffer, refresh the whole display
661 ** after pos, including blank lines which are not technically part of
662 ** any range of characters.
664 void TextDRedisplayRange(textDisp *textD, int start, int end)
666 int i, startLine, lastLine, startIndex, endIndex;
668 /* If the range is outside of the displayed text, just return */
669 if (end < textD->firstChar || (start > textD->lastChar &&
670 !emptyLinesVisible(textD)))
671 return;
673 /* Clean up the starting and ending values */
674 if (start < 0) start = 0;
675 if (start > textD->buffer->length) start = textD->buffer->length;
676 if (end < 0) end = 0;
677 if (end > textD->buffer->length) end = textD->buffer->length;
679 /* Get the starting and ending lines */
680 if (start < textD->firstChar)
681 start = textD->firstChar;
682 if (!posToVisibleLineNum(textD, start, &startLine))
683 startLine = textD->nVisibleLines - 1;
684 if (end >= textD->lastChar) {
685 lastLine = textD->nVisibleLines - 1;
686 } else {
687 if (!posToVisibleLineNum(textD, end, &lastLine)) {
688 /* shouldn't happen */
689 lastLine = textD->nVisibleLines - 1;
693 /* Get the starting and ending positions within the lines */
694 startIndex = textD->lineStarts[startLine] == -1 ? 0 :
695 start - textD->lineStarts[startLine];
696 if (end >= textD->lastChar)
697 endIndex = INT_MAX;
698 else if (textD->lineStarts[lastLine] == -1)
699 endIndex = 0;
700 else
701 endIndex = end - textD->lineStarts[lastLine];
703 /* Reset the clipping rectangles for the drawing GCs which are shared
704 using XtAllocateGC, and may have changed since the last use */
705 resetClipRectangles(textD);
707 /* If the starting and ending lines are the same, redisplay the single
708 line between "start" and "end" */
709 if (startLine == lastLine) {
710 redisplayLine(textD, startLine, 0, INT_MAX, startIndex, endIndex);
711 return;
714 /* Redisplay the first line from "start" */
715 redisplayLine(textD, startLine, 0, INT_MAX, startIndex, INT_MAX);
717 /* Redisplay the lines in between at their full width */
718 for (i=startLine+1; i<lastLine; i++)
719 redisplayLine(textD, i, 0, INT_MAX, 0, INT_MAX);
721 /* Redisplay the last line to "end" */
722 redisplayLine(textD, lastLine, 0, INT_MAX, 0, endIndex);
726 ** Set the scroll position of the text display vertically by line number and
727 ** horizontally by pixel offset from the left margin
729 void TextDSetScroll(textDisp *textD, int topLineNum, int horizOffset)
731 int sliderSize, sliderMax;
732 int vPadding = (int)(TEXT_OF_TEXTD(textD).cursorVPadding);
734 /* Limit the requested scroll position to allowable values */
735 if (topLineNum < 1)
736 topLineNum = 1;
737 else if ((topLineNum > textD->topLineNum) &&
738 (topLineNum > (textD->nBufferLines + 2 - textD->nVisibleLines +
739 vPadding)))
740 topLineNum = max(textD->topLineNum,
741 textD->nBufferLines + 2 - textD->nVisibleLines + vPadding);
742 XtVaGetValues(textD->hScrollBar, XmNmaximum, &sliderMax,
743 XmNsliderSize, &sliderSize, NULL);
744 if (horizOffset < 0)
745 horizOffset = 0;
746 if (horizOffset > sliderMax - sliderSize)
747 horizOffset = sliderMax - sliderSize;
749 setScroll(textD, topLineNum, horizOffset, True, True);
753 ** Get the current scroll position for the text display, in terms of line
754 ** number of the top line and horizontal pixel offset from the left margin
756 void TextDGetScroll(textDisp *textD, int *topLineNum, int *horizOffset)
758 *topLineNum = textD->topLineNum;
759 *horizOffset = textD->horizOffset;
763 ** Set the position of the text insertion cursor for text display "textD"
765 void TextDSetInsertPosition(textDisp *textD, int newPos)
767 /* make sure new position is ok, do nothing if it hasn't changed */
768 if (newPos == textD->cursorPos)
769 return;
770 if (newPos < 0) newPos = 0;
771 if (newPos > textD->buffer->length) newPos = textD->buffer->length;
773 /* cursor movement cancels vertical cursor motion column */
774 textD->cursorPreferredCol = -1;
776 /* erase the cursor at it's previous position */
777 TextDBlankCursor(textD);
779 /* draw it at its new position */
780 textD->cursorPos = newPos;
781 textD->cursorOn = True;
782 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos + 1);
785 void TextDBlankCursor(textDisp *textD)
787 if (!textD->cursorOn)
788 return;
790 blankCursorProtrusions(textD);
791 textD->cursorOn = False;
792 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
795 void TextDUnblankCursor(textDisp *textD)
797 if (!textD->cursorOn) {
798 textD->cursorOn = True;
799 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
803 void TextDSetCursorStyle(textDisp *textD, int style)
805 textD->cursorStyle = style;
806 blankCursorProtrusions(textD);
807 if (textD->cursorOn)
808 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos + 1);
811 void TextDSetWrapMode(textDisp *textD, int wrap, int wrapMargin)
813 textD->wrapMargin = wrapMargin;
814 textD->continuousWrap = wrap;
816 /* wrapping can change change the total number of lines, re-count */
817 textD->nBufferLines = TextDCountLines(textD, 0, textD->buffer->length,True);
819 /* changing wrap margins wrap or changing from wrapped mode to non-wrapped
820 can leave the character at the top no longer at a line start, and/or
821 change the line number */
822 textD->firstChar = TextDStartOfLine(textD, textD->firstChar);
823 textD->topLineNum = TextDCountLines(textD, 0, textD->firstChar, True) + 1;
824 resetAbsLineNum(textD);
826 /* update the line starts array */
827 calcLineStarts(textD, 0, textD->nVisibleLines);
828 calcLastChar(textD);
830 /* Update the scroll bar page increment size (as well as other scroll
831 bar parameters) */
832 updateVScrollBarRange(textD);
833 updateHScrollBarRange(textD);
835 /* Decide if the horizontal scroll bar needs to be visible */
836 hideOrShowHScrollBar(textD);
838 /* Do a full redraw */
839 TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left,
840 textD->height);
843 int TextDGetInsertPosition(textDisp *textD)
845 return textD->cursorPos;
849 ** Insert "text" at the current cursor location. This has the same
850 ** effect as inserting the text into the buffer using BufInsert and
851 ** then moving the insert position after the newly inserted text, except
852 ** that it's optimized to do less redrawing.
854 void TextDInsert(textDisp *textD, char *text)
856 int pos = textD->cursorPos;
858 textD->cursorToHint = pos + strlen(text);
859 BufInsert(textD->buffer, pos, text);
860 textD->cursorToHint = NO_HINT;
864 ** Insert "text" (which must not contain newlines), overstriking the current
865 ** cursor location.
867 void TextDOverstrike(textDisp *textD, char *text)
869 int startPos = textD->cursorPos;
870 textBuffer *buf = textD->buffer;
871 int lineStart = BufStartOfLine(buf, startPos);
872 int textLen = strlen(text);
873 int i, p, endPos, indent, startIndent, endIndent;
874 char *c, ch, *paddedText = NULL;
876 /* determine how many displayed character positions are covered */
877 startIndent = BufCountDispChars(textD->buffer, lineStart, startPos);
878 indent = startIndent;
879 for (c=text; *c!='\0'; c++)
880 indent += BufCharWidth(*c, indent, buf->tabDist, buf->nullSubsChar);
881 endIndent = indent;
883 /* find which characters to remove, and if necessary generate additional
884 padding to make up for removed control characters at the end */
885 indent=startIndent;
886 for (p=startPos; ; p++) {
887 if (p == buf->length)
888 break;
889 ch = BufGetCharacter(buf, p);
890 if (ch == '\n')
891 break;
892 indent += BufCharWidth(ch, indent, buf->tabDist, buf->nullSubsChar);
893 if (indent == endIndent) {
894 p++;
895 break;
896 } else if (indent > endIndent) {
897 if (ch != '\t') {
898 p++;
899 paddedText = XtMalloc(textLen + MAX_EXP_CHAR_LEN + 1);
900 strcpy(paddedText, text);
901 for (i=0; i<indent-endIndent; i++)
902 paddedText[textLen+i] = ' ';
903 paddedText[textLen+i] = '\0';
905 break;
908 endPos = p;
910 textD->cursorToHint = startPos + textLen;
911 BufReplace(buf, startPos, endPos, paddedText == NULL ? text : paddedText);
912 textD->cursorToHint = NO_HINT;
913 if (paddedText != NULL)
914 XtFree(paddedText);
918 ** Translate window coordinates to the nearest text cursor position.
920 int TextDXYToPosition(textDisp *textD, int x, int y)
922 return xyToPos(textD, x, y, CURSOR_POS);
926 ** Translate window coordinates to the nearest character cell.
928 int TextDXYToCharPos(textDisp *textD, int x, int y)
930 return xyToPos(textD, x, y, CHARACTER_POS);
934 ** Translate window coordinates to the nearest row and column number for
935 ** positioning the cursor. This, of course, makes no sense when the font
936 ** is proportional, since there are no absolute columns.
938 void TextDXYToUnconstrainedPosition(textDisp *textD, int x, int y, int *row,
939 int *column)
941 xyToUnconstrainedPos(textD, x, y, row, column, CURSOR_POS);
945 ** Translate line and column to the nearest row and column number for
946 ** positioning the cursor. This, of course, makes no sense when the font
947 ** is proportional, since there are no absolute columns.
949 int TextDLineAndColToPos(textDisp *textD, int lineNum, int column)
951 int i, lineEnd, charIndex, outIndex;
952 int lineStart=0, charLen=0;
953 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
955 /* Count lines */
956 if (lineNum < 1)
957 lineNum = 1;
958 lineEnd = -1;
959 for (i=1; i<=lineNum && lineEnd<textD->buffer->length; i++) {
960 lineStart = lineEnd + 1;
961 lineEnd = BufEndOfLine(textD->buffer, lineStart);
964 /* If line is beyond end of buffer, position at last character in buffer */
965 if ( lineNum >= i ) {
966 return lineEnd;
969 /* Start character index at zero */
970 charIndex=0;
972 /* Only have to count columns if column isn't zero (or negative) */
973 if (column > 0) {
974 /* Count columns, expanding each character */
975 lineStr = BufGetRange(textD->buffer, lineStart, lineEnd);
976 outIndex = 0;
977 for(i=lineStart; i<lineEnd; i++, charIndex++) {
978 charLen = BufExpandCharacter(lineStr[charIndex], outIndex,
979 expandedChar, textD->buffer->tabDist,
980 textD->buffer->nullSubsChar);
981 if ( outIndex+charLen >= column ) break;
982 outIndex+=charLen;
985 /* If the column is in the middle of an expanded character, put cursor
986 * in front of character if in first half of character, and behind
987 * character if in last half of character
989 if (column >= outIndex + ( charLen / 2 ))
990 charIndex++;
992 /* If we are beyond the end of the line, back up one space */
993 if ((i>=lineEnd)&&(charIndex>0)) charIndex--;
996 /* Position is the start of the line plus the index into line buffer */
997 return lineStart + charIndex;
1001 ** Translate a buffer text position to the XY location where the center
1002 ** of the cursor would be positioned to point to that character. Returns
1003 ** False if the position is not displayed because it is VERTICALLY out
1004 ** of view. If the position is horizontally out of view, returns the
1005 ** x coordinate where the position would be if it were visible.
1007 int TextDPositionToXY(textDisp *textD, int pos, int *x, int *y)
1009 int charIndex, lineStartPos, fontHeight, lineLen;
1010 int visLineNum, charLen, outIndex, xStep, charStyle;
1011 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
1013 /* If position is not displayed, return false */
1014 if (pos < textD->firstChar ||
1015 (pos > textD->lastChar && !emptyLinesVisible(textD)))
1016 return False;
1018 /* Calculate y coordinate */
1019 if (!posToVisibleLineNum(textD, pos, &visLineNum))
1020 return False;
1021 fontHeight = textD->ascent + textD->descent;
1022 *y = textD->top + visLineNum*fontHeight + fontHeight/2;
1024 /* Get the text, length, and buffer position of the line. If the position
1025 is beyond the end of the buffer and should be at the first position on
1026 the first empty line, don't try to get or scan the text */
1027 lineStartPos = textD->lineStarts[visLineNum];
1028 if (lineStartPos == -1) {
1029 *x = textD->left - textD->horizOffset;
1030 return True;
1032 lineLen = visLineLength(textD, visLineNum);
1033 lineStr = BufGetRange(textD->buffer, lineStartPos, lineStartPos + lineLen);
1035 /* Step through character positions from the beginning of the line
1036 to "pos" to calculate the x coordinate */
1037 xStep = textD->left - textD->horizOffset;
1038 outIndex = 0;
1039 for(charIndex=0; charIndex<pos-lineStartPos; charIndex++) {
1040 charLen = BufExpandCharacter(lineStr[charIndex], outIndex, expandedChar,
1041 textD->buffer->tabDist, textD->buffer->nullSubsChar);
1042 charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1043 outIndex, lineStr[charIndex]);
1044 xStep += stringWidth(textD, expandedChar, charLen, charStyle);
1045 outIndex += charLen;
1047 *x = xStep;
1048 XtFree(lineStr);
1049 return True;
1053 ** If the text widget is maintaining a line number count appropriate to "pos"
1054 ** return the line and column numbers of pos, otherwise return False. If
1055 ** continuous wrap mode is on, returns the absolute line number (as opposed to
1056 ** the wrapped line number which is used for scrolling). THIS ROUTINE ONLY
1057 ** WORKS FOR DISPLAYED LINES AND, IN CONTINUOUS WRAP MODE, ONLY WHEN THE
1058 ** ABSOLUTE LINE NUMBER IS BEING MAINTAINED. Otherwise, it returns False.
1060 int TextDPosToLineAndCol(textDisp *textD, int pos, int *lineNum, int *column)
1062 textBuffer *buf = textD->buffer;
1064 /* In continuous wrap mode, the absolute (non-wrapped) line count is
1065 maintained separately, as needed. Only return it if we're actually
1066 keeping track of it and pos is in the displayed text */
1067 if (textD->continuousWrap) {
1068 if (!maintainingAbsTopLineNum(textD) || pos < textD->firstChar ||
1069 pos > textD->lastChar)
1070 return False;
1071 *lineNum = textD->absTopLineNum + BufCountLines(buf,
1072 textD->firstChar, pos);
1073 *column = BufCountDispChars(buf, BufStartOfLine(buf, pos), pos);
1074 return True;
1077 /* Only return the data if pos is within the displayed text */
1078 if (!posToVisibleLineNum(textD, pos, lineNum))
1079 return False;
1080 *column = BufCountDispChars(buf, textD->lineStarts[*lineNum], pos);
1081 *lineNum += textD->topLineNum;
1082 return True;
1086 ** Return True if position (x, y) is inside of the primary selection
1088 int TextDInSelection(textDisp *textD, int x, int y)
1090 int row, column, pos = xyToPos(textD, x, y, CHARACTER_POS);
1091 textBuffer *buf = textD->buffer;
1093 xyToUnconstrainedPos(textD, x, y, &row, &column, CHARACTER_POS);
1094 if (rangeTouchesRectSel(&buf->primary, textD->firstChar, textD->lastChar))
1095 column = TextDOffsetWrappedColumn(textD, row, column);
1096 return inSelection(&buf->primary, pos, BufStartOfLine(buf, pos), column);
1100 ** Correct a column number based on an unconstrained position (as returned by
1101 ** TextDXYToUnconstrainedPosition) to be relative to the last actual newline
1102 ** in the buffer before the row and column position given, rather than the
1103 ** last line start created by line wrapping. This is an adapter
1104 ** for rectangular selections and code written before continuous wrap mode,
1105 ** which thinks that the unconstrained column is the number of characters
1106 ** from the last newline. Obviously this is time consuming, because it
1107 ** invloves character re-counting.
1109 int TextDOffsetWrappedColumn(textDisp *textD, int row, int column)
1111 int lineStart, dispLineStart;
1113 if (!textD->continuousWrap || row < 0 || row > textD->nVisibleLines)
1114 return column;
1115 dispLineStart = textD->lineStarts[row];
1116 if (dispLineStart == -1)
1117 return column;
1118 lineStart = BufStartOfLine(textD->buffer, dispLineStart);
1119 return column + BufCountDispChars(textD->buffer, lineStart, dispLineStart);
1123 ** Correct a row number from an unconstrained position (as returned by
1124 ** TextDXYToUnconstrainedPosition) to a straight number of newlines from the
1125 ** top line of the display. Because rectangular selections are based on
1126 ** newlines, rather than display wrapping, and anywhere a rectangular selection
1127 ** needs a row, it needs it in terms of un-wrapped lines.
1129 int TextDOffsetWrappedRow(textDisp *textD, int row)
1131 if (!textD->continuousWrap || row < 0 || row > textD->nVisibleLines)
1132 return row;
1133 return BufCountLines(textD->buffer, textD->firstChar,
1134 textD->lineStarts[row]);
1138 ** Scroll the display to bring insertion cursor into view.
1140 ** Note: it would be nice to be able to do this without counting lines twice
1141 ** (setScroll counts them too) and/or to count from the most efficient
1142 ** starting point, but the efficiency of this routine is not as important to
1143 ** the overall performance of the text display.
1145 void TextDMakeInsertPosVisible(textDisp *textD)
1147 int hOffset, topLine, x, y;
1148 int cursorPos = textD->cursorPos;
1149 int linesFromTop = 0, do_padding = 1;
1150 int cursorVPadding = (int)TEXT_OF_TEXTD(textD).cursorVPadding;
1152 hOffset = textD->horizOffset;
1153 topLine = textD->topLineNum;
1155 /* Don't do padding if this is a mouse operation */
1156 do_padding = ((TEXT_OF_TEXTD(textD).dragState == NOT_CLICKED) &&
1157 (cursorVPadding > 0));
1159 /* Find the new top line number */
1160 if (cursorPos < textD->firstChar) {
1161 topLine -= TextDCountLines(textD, cursorPos, textD->firstChar, False);
1162 /* linesFromTop = 0; */
1163 } else if (cursorPos > textD->lastChar && !emptyLinesVisible(textD)) {
1164 topLine += TextDCountLines(textD, textD->lastChar -
1165 (wrapUsesCharacter(textD, textD->lastChar) ? 0 : 1),
1166 cursorPos, False);
1167 linesFromTop = textD->nVisibleLines-1;
1168 } else if (cursorPos == textD->lastChar && !emptyLinesVisible(textD) &&
1169 !wrapUsesCharacter(textD, textD->lastChar)) {
1170 topLine++;
1171 linesFromTop = textD->nVisibleLines-1;
1172 } else {
1173 /* Avoid extra counting if cursorVPadding is disabled */
1174 if (do_padding)
1175 linesFromTop = TextDCountLines(textD, textD->firstChar,
1176 cursorPos, True);
1178 if (topLine < 1) {
1179 fprintf(stderr, "internal consistency check tl1 failed\n");
1180 topLine = 1;
1183 if (do_padding) {
1184 /* Keep the cursor away from the top or bottom of screen. */
1185 if (textD->nVisibleLines <= 2*(int)cursorVPadding) {
1186 topLine += (linesFromTop - textD->nVisibleLines/2);
1187 topLine = max(topLine, 1);
1188 } else if (linesFromTop < (int)cursorVPadding) {
1189 topLine -= (cursorVPadding - linesFromTop);
1190 topLine = max(topLine, 1);
1191 } else if (linesFromTop > textD->nVisibleLines-(int)cursorVPadding-1) {
1192 topLine += (linesFromTop - (textD->nVisibleLines-cursorVPadding-1));
1196 /* Find the new setting for horizontal offset (this is a bit ungraceful).
1197 If the line is visible, just use TextDPositionToXY to get the position
1198 to scroll to, otherwise, do the vertical scrolling first, then the
1199 horizontal */
1200 if (!TextDPositionToXY(textD, cursorPos, &x, &y)) {
1201 setScroll(textD, topLine, hOffset, True, True);
1202 if (!TextDPositionToXY(textD, cursorPos, &x, &y))
1203 return; /* Give up, it's not worth it (but why does it fail?) */
1205 if (x > textD->left + textD->width)
1206 hOffset += x - (textD->left + textD->width);
1207 else if (x < textD->left)
1208 hOffset += x - textD->left;
1210 /* Do the scroll */
1211 setScroll(textD, topLine, hOffset, True, True);
1215 ** Return the current preferred column along with the current
1216 ** visible line index (-1 if not visible) and the lineStartPos
1217 ** of the current insert position.
1219 int TextDPreferredColumn(textDisp *textD, int *visLineNum, int *lineStartPos)
1221 int column;
1223 /* Find the position of the start of the line. Use the line starts array
1224 if possible, to avoid unbounded line-counting in continuous wrap mode */
1225 if (posToVisibleLineNum(textD, textD->cursorPos, visLineNum)) {
1226 *lineStartPos = textD->lineStarts[*visLineNum];
1228 else {
1229 *lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1230 *visLineNum = -1;
1233 /* Decide what column to move to, if there's a preferred column use that */
1234 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1235 BufCountDispChars(textD->buffer, *lineStartPos, textD->cursorPos);
1236 return(column);
1240 ** Return the insert position of the requested column given
1241 ** the lineStartPos.
1243 int TextDPosOfPreferredCol(textDisp *textD, int column, int lineStartPos)
1245 int newPos;
1247 newPos = BufCountForwardDispChars(textD->buffer, lineStartPos, column);
1248 if (textD->continuousWrap) {
1249 newPos = min(newPos, TextDEndOfLine(textD, lineStartPos, True));
1251 return(newPos);
1255 ** Cursor movement functions
1257 int TextDMoveRight(textDisp *textD)
1259 if (textD->cursorPos >= textD->buffer->length)
1260 return False;
1261 TextDSetInsertPosition(textD, textD->cursorPos + 1);
1262 return True;
1265 int TextDMoveLeft(textDisp *textD)
1267 if (textD->cursorPos <= 0)
1268 return False;
1269 TextDSetInsertPosition(textD, textD->cursorPos - 1);
1270 return True;
1273 int TextDMoveUp(textDisp *textD, int absolute)
1275 int lineStartPos, column, prevLineStartPos, newPos, visLineNum;
1277 /* Find the position of the start of the line. Use the line starts array
1278 if possible, to avoid unbounded line-counting in continuous wrap mode */
1279 if (absolute) {
1280 lineStartPos = BufStartOfLine(textD->buffer, textD->cursorPos);
1281 visLineNum = -1;
1282 } else if (posToVisibleLineNum(textD, textD->cursorPos, &visLineNum))
1283 lineStartPos = textD->lineStarts[visLineNum];
1284 else {
1285 lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1286 visLineNum = -1;
1288 if (lineStartPos == 0)
1289 return False;
1291 /* Decide what column to move to, if there's a preferred column use that */
1292 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1293 BufCountDispChars(textD->buffer, lineStartPos, textD->cursorPos);
1295 /* count forward from the start of the previous line to reach the column */
1296 if (absolute)
1297 prevLineStartPos = BufCountBackwardNLines(textD->buffer, lineStartPos, 1);
1298 else if (visLineNum != -1 && visLineNum != 0)
1299 prevLineStartPos = textD->lineStarts[visLineNum-1];
1300 else
1301 prevLineStartPos = TextDCountBackwardNLines(textD, lineStartPos, 1);
1302 newPos = BufCountForwardDispChars(textD->buffer, prevLineStartPos, column);
1303 if (textD->continuousWrap && !absolute)
1304 newPos = min(newPos, TextDEndOfLine(textD, prevLineStartPos, True));
1306 /* move the cursor */
1307 TextDSetInsertPosition(textD, newPos);
1309 /* if a preferred column wasn't aleady established, establish it */
1310 textD->cursorPreferredCol = column;
1312 return True;
1314 int TextDMoveDown(textDisp *textD, int absolute)
1316 int lineStartPos, column, nextLineStartPos, newPos, visLineNum;
1318 if (textD->cursorPos == textD->buffer->length)
1319 return False;
1320 if (absolute) {
1321 lineStartPos = BufStartOfLine(textD->buffer, textD->cursorPos);
1322 visLineNum = -1;
1323 } else if (posToVisibleLineNum(textD, textD->cursorPos, &visLineNum))
1324 lineStartPos = textD->lineStarts[visLineNum];
1325 else {
1326 lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1327 visLineNum = -1;
1329 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1330 BufCountDispChars(textD->buffer, lineStartPos, textD->cursorPos);
1331 if (absolute)
1332 nextLineStartPos = BufCountForwardNLines(textD->buffer, lineStartPos, 1);
1333 else
1334 nextLineStartPos = TextDCountForwardNLines(textD, lineStartPos, 1, True);
1335 newPos = BufCountForwardDispChars(textD->buffer, nextLineStartPos, column);
1336 if (textD->continuousWrap && !absolute)
1337 newPos = min(newPos, TextDEndOfLine(textD, nextLineStartPos, True));
1338 TextDSetInsertPosition(textD, newPos);
1339 textD->cursorPreferredCol = column;
1341 return True;
1345 ** Same as BufCountLines, but takes in to account wrapping if wrapping is
1346 ** turned on. If the caller knows that startPos is at a line start, it
1347 ** can pass "startPosIsLineStart" as True to make the call more efficient
1348 ** by avoiding the additional step of scanning back to the last newline.
1350 int TextDCountLines(textDisp *textD, int startPos, int endPos,
1351 int startPosIsLineStart)
1353 int retLines, retPos, retLineStart, retLineEnd;
1355 /* If we're not wrapping use simple (and more efficient) BufCountLines */
1356 if (!textD->continuousWrap)
1357 return BufCountLines(textD->buffer, startPos, endPos);
1359 wrappedLineCounter(textD, textD->buffer, startPos, endPos, INT_MAX,
1360 startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1361 &retLineEnd);
1362 return retLines;
1366 ** Same as BufCountForwardNLines, but takes in to account line breaks when
1367 ** wrapping is turned on. If the caller knows that startPos is at a line start,
1368 ** it can pass "startPosIsLineStart" as True to make the call more efficient
1369 ** by avoiding the additional step of scanning back to the last newline.
1371 int TextDCountForwardNLines(textDisp *textD, int startPos, int nLines,
1372 int startPosIsLineStart)
1374 int retLines, retPos, retLineStart, retLineEnd;
1376 /* if we're not wrapping use more efficient BufCountForwardNLines */
1377 if (!textD->continuousWrap)
1378 return BufCountForwardNLines(textD->buffer, startPos, nLines);
1380 /* wrappedLineCounter can't handle the 0 lines case */
1381 if (nLines == 0)
1382 return startPos;
1384 /* use the common line counting routine to count forward */
1385 wrappedLineCounter(textD, textD->buffer, startPos, textD->buffer->length,
1386 nLines, startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1387 &retLineEnd);
1388 return retPos;
1392 ** Same as BufEndOfLine, but takes in to account line breaks when wrapping
1393 ** is turned on. If the caller knows that startPos is at a line start, it
1394 ** can pass "startPosIsLineStart" as True to make the call more efficient
1395 ** by avoiding the additional step of scanning back to the last newline.
1397 ** Note that the definition of the end of a line is less clear when continuous
1398 ** wrap is on. With continuous wrap off, it's just a pointer to the newline
1399 ** that ends the line. When it's on, it's the character beyond the last
1400 ** DISPLAYABLE character on the line, where a whitespace character which has
1401 ** been "converted" to a newline for wrapping is not considered displayable.
1402 ** Also note that, a line can be wrapped at a non-whitespace character if the
1403 ** line had no whitespace. In this case, this routine returns a pointer to
1404 ** the start of the next line. This is also consistent with the model used by
1405 ** visLineLength.
1407 int TextDEndOfLine(textDisp *textD, int pos, int startPosIsLineStart)
1409 int retLines, retPos, retLineStart, retLineEnd;
1411 /* If we're not wrapping use more efficien BufEndOfLine */
1412 if (!textD->continuousWrap)
1413 return BufEndOfLine(textD->buffer, pos);
1415 if (pos == textD->buffer->length)
1416 return pos;
1417 wrappedLineCounter(textD, textD->buffer, pos, textD->buffer->length, 1,
1418 startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1419 &retLineEnd);
1420 return retLineEnd;
1424 ** Same as BufStartOfLine, but returns the character after last wrap point
1425 ** rather than the last newline.
1427 int TextDStartOfLine(textDisp *textD, int pos)
1429 int retLines, retPos, retLineStart, retLineEnd;
1431 /* If we're not wrapping, use the more efficient BufStartOfLine */
1432 if (!textD->continuousWrap)
1433 return BufStartOfLine(textD->buffer, pos);
1435 wrappedLineCounter(textD, textD->buffer, BufStartOfLine(textD->buffer, pos),
1436 pos, INT_MAX, True, 0, &retPos, &retLines, &retLineStart,
1437 &retLineEnd);
1438 return retLineStart;
1442 ** Same as BufCountBackwardNLines, but takes in to account line breaks when
1443 ** wrapping is turned on.
1445 int TextDCountBackwardNLines(textDisp *textD, int startPos, int nLines)
1447 textBuffer *buf = textD->buffer;
1448 int pos, lineStart, retLines, retPos, retLineStart, retLineEnd;
1450 /* If we're not wrapping, use the more efficient BufCountBackwardNLines */
1451 if (!textD->continuousWrap)
1452 return BufCountBackwardNLines(textD->buffer, startPos, nLines);
1454 pos = startPos;
1455 while (True) {
1456 lineStart = BufStartOfLine(buf, pos);
1457 wrappedLineCounter(textD, textD->buffer, lineStart, pos, INT_MAX,
1458 True, 0, &retPos, &retLines, &retLineStart, &retLineEnd);
1459 if (retLines > nLines)
1460 return TextDCountForwardNLines(textD, lineStart, retLines-nLines,
1461 True);
1462 nLines -= retLines;
1463 pos = lineStart - 1;
1464 if (pos < 0)
1465 return 0;
1466 nLines -= 1;
1471 ** Callback attached to the text buffer to receive delete information before
1472 ** the modifications are actually made.
1474 static void bufPreDeleteCB(int pos, int nDeleted, void *cbArg)
1476 textDisp *textD = (textDisp *)cbArg;
1477 if (textD->continuousWrap &&
1478 (textD->fixedFontWidth == -1 || textD->modifyingTabDist))
1479 /* Note: we must perform this measurement, even if there is not a
1480 single character deleted; the number of "deleted" lines is the
1481 number of visual lines spanned by the real line in which the
1482 modification takes place.
1483 Also, a modification of the tab distance requires the same
1484 kind of calculations in advance, even if the font width is "fixed",
1485 because when the width of the tab characters changes, the layout
1486 of the text may be completely different. */
1487 measureDeletedLines(textD, pos, nDeleted);
1488 else
1489 textD->suppressResync = 0; /* Probably not needed, but just in case */
1493 ** Callback attached to the text buffer to receive modification information
1495 static void bufModifiedCB(int pos, int nInserted, int nDeleted,
1496 int nRestyled, char *deletedText, void *cbArg)
1498 int linesInserted, linesDeleted, startDispPos, endDispPos;
1499 textDisp *textD = (textDisp *)cbArg;
1500 textBuffer *buf = textD->buffer;
1501 int oldFirstChar = textD->firstChar;
1502 int scrolled, origCursorPos = textD->cursorPos;
1503 int wrapModStart, wrapModEnd;
1505 /* buffer modification cancels vertical cursor motion column */
1506 if (nInserted != 0 || nDeleted != 0)
1507 textD->cursorPreferredCol = -1;
1509 /* Count the number of lines inserted and deleted, and in the case
1510 of continuous wrap mode, how much has changed */
1511 if (textD->continuousWrap) {
1512 findWrapRange(textD, deletedText, pos, nInserted, nDeleted,
1513 &wrapModStart, &wrapModEnd, &linesInserted, &linesDeleted);
1514 } else {
1515 linesInserted = nInserted == 0 ? 0 :
1516 BufCountLines(buf, pos, pos + nInserted);
1517 linesDeleted = nDeleted == 0 ? 0 : countLines(deletedText);
1520 /* Update the line starts and topLineNum */
1521 if (nInserted != 0 || nDeleted != 0) {
1522 if (textD->continuousWrap) {
1523 updateLineStarts(textD, wrapModStart, wrapModEnd-wrapModStart,
1524 nDeleted + pos-wrapModStart + (wrapModEnd-(pos+nInserted)),
1525 linesInserted, linesDeleted, &scrolled);
1526 } else {
1527 updateLineStarts(textD, pos, nInserted, nDeleted, linesInserted,
1528 linesDeleted, &scrolled);
1530 } else
1531 scrolled = False;
1533 /* If we're counting non-wrapped lines as well, maintain the absolute
1534 (non-wrapped) line number of the text displayed */
1535 if (maintainingAbsTopLineNum(textD) && (nInserted != 0 || nDeleted != 0)) {
1536 if (pos + nDeleted < oldFirstChar)
1537 textD->absTopLineNum += BufCountLines(buf, pos, pos + nInserted) -
1538 countLines(deletedText);
1539 else if (pos < oldFirstChar)
1540 resetAbsLineNum(textD);
1543 /* Update the line count for the whole buffer */
1544 textD->nBufferLines += linesInserted - linesDeleted;
1546 /* Update the scroll bar ranges (and value if the value changed). Note
1547 that updating the horizontal scroll bar range requires scanning the
1548 entire displayed text, however, it doesn't seem to hurt performance
1549 much. Note also, that the horizontal scroll bar update routine is
1550 allowed to re-adjust horizOffset if there is blank space to the right
1551 of all lines of text. */
1552 updateVScrollBarRange(textD);
1553 scrolled |= updateHScrollBarRange(textD);
1555 /* Update the cursor position */
1556 if (textD->cursorToHint != NO_HINT) {
1557 textD->cursorPos = textD->cursorToHint;
1558 textD->cursorToHint = NO_HINT;
1559 } else if (textD->cursorPos > pos) {
1560 if (textD->cursorPos < pos + nDeleted)
1561 textD->cursorPos = pos;
1562 else
1563 textD->cursorPos += nInserted - nDeleted;
1566 /* If the changes caused scrolling, re-paint everything and we're done. */
1567 if (scrolled) {
1568 blankCursorProtrusions(textD);
1569 TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left,
1570 textD->height);
1571 if (textD->styleBuffer) {/* See comments in extendRangeForStyleMods */
1572 textD->styleBuffer->primary.selected = False;
1573 textD->styleBuffer->primary.zeroWidth = False;
1575 return;
1578 /* If the changes didn't cause scrolling, decide the range of characters
1579 that need to be re-painted. Also if the cursor position moved, be
1580 sure that the redisplay range covers the old cursor position so the
1581 old cursor gets erased, and erase the bits of the cursor which extend
1582 beyond the left and right edges of the text. */
1583 startDispPos = textD->continuousWrap ? wrapModStart : pos;
1584 if (origCursorPos == startDispPos && textD->cursorPos != startDispPos)
1585 startDispPos = min(startDispPos, origCursorPos-1);
1586 if (linesInserted == linesDeleted) {
1587 if (nInserted == 0 && nDeleted == 0)
1588 endDispPos = pos + nRestyled;
1589 else {
1590 endDispPos = textD->continuousWrap ? wrapModEnd :
1591 BufEndOfLine(buf, pos + nInserted) + 1;
1592 if (origCursorPos >= startDispPos &&
1593 (origCursorPos <= endDispPos || endDispPos == buf->length))
1594 blankCursorProtrusions(textD);
1596 /* If more than one line is inserted/deleted, a line break may have
1597 been inserted or removed in between, and the line numbers may
1598 have changed. If only one line is altered, line numbers cannot
1599 be affected (the insertion or removal of a line break always
1600 results in at least two lines being redrawn). */
1601 if (linesInserted > 1) redrawLineNumbers(textD, False);
1602 } else { /* linesInserted != linesDeleted */
1603 endDispPos = textD->lastChar + 1;
1604 if (origCursorPos >= pos)
1605 blankCursorProtrusions(textD);
1606 redrawLineNumbers(textD, False);
1609 /* If there is a style buffer, check if the modification caused additional
1610 changes that need to be redisplayed. (Redisplaying separately would
1611 cause double-redraw on almost every modification involving styled
1612 text). Extend the redraw range to incorporate style changes */
1613 if (textD->styleBuffer)
1614 extendRangeForStyleMods(textD, &startDispPos, &endDispPos);
1616 /* Redisplay computed range */
1617 TextDRedisplayRange(textD, startDispPos, endDispPos);
1621 ** In continuous wrap mode, internal line numbers are calculated after
1622 ** wrapping. A separate non-wrapped line count is maintained when line
1623 ** numbering is turned on. There is some performance cost to maintaining this
1624 ** line count, so normally absolute line numbers are not tracked if line
1625 ** numbering is off. This routine allows callers to specify that they still
1626 ** want this line count maintained (for use via TextDPosToLineAndCol).
1627 ** More specifically, this allows the line number reported in the statistics
1628 ** line to be calibrated in absolute lines, rather than post-wrapped lines.
1630 void TextDMaintainAbsLineNum(textDisp *textD, int state)
1632 textD->needAbsTopLineNum = state;
1633 resetAbsLineNum(textD);
1637 ** Returns the absolute (non-wrapped) line number of the first line displayed.
1638 ** Returns 0 if the absolute top line number is not being maintained.
1640 static int getAbsTopLineNum(textDisp *textD)
1642 if (!textD->continuousWrap)
1643 return textD->topLineNum;
1644 if (maintainingAbsTopLineNum(textD))
1645 return textD->absTopLineNum;
1646 return 0;
1650 ** Re-calculate absolute top line number for a change in scroll position.
1652 static void offsetAbsLineNum(textDisp *textD, int oldFirstChar)
1654 if (maintainingAbsTopLineNum(textD)) {
1655 if (textD->firstChar < oldFirstChar)
1656 textD->absTopLineNum -= BufCountLines(textD->buffer,
1657 textD->firstChar, oldFirstChar);
1658 else
1659 textD->absTopLineNum += BufCountLines(textD->buffer,
1660 oldFirstChar, textD->firstChar);
1665 ** Return true if a separate absolute top line number is being maintained
1666 ** (for displaying line numbers or showing in the statistics line).
1668 static int maintainingAbsTopLineNum(textDisp *textD)
1670 return textD->continuousWrap &&
1671 (textD->lineNumWidth != 0 || textD->needAbsTopLineNum);
1675 ** Count lines from the beginning of the buffer to reestablish the
1676 ** absolute (non-wrapped) top line number. If mode is not continuous wrap,
1677 ** or the number is not being maintained, does nothing.
1679 static void resetAbsLineNum(textDisp *textD)
1681 textD->absTopLineNum = 1;
1682 offsetAbsLineNum(textD, 0);
1686 ** Find the line number of position "pos" relative to the first line of
1687 ** displayed text. Returns False if the line is not displayed.
1689 static int posToVisibleLineNum(textDisp *textD, int pos, int *lineNum)
1691 int i;
1693 if (pos < textD->firstChar)
1694 return False;
1695 if (pos > textD->lastChar) {
1696 if (emptyLinesVisible(textD)) {
1697 if (textD->lastChar < textD->buffer->length) {
1698 if (!posToVisibleLineNum(textD, textD->lastChar, lineNum)) {
1699 fprintf(stderr, "Consistency check ptvl failed\n");
1700 return False;
1702 return ++(*lineNum) <= textD->nVisibleLines-1;
1703 } else {
1704 posToVisibleLineNum(textD, max(textD->lastChar-1, 0), lineNum);
1705 return True;
1708 return False;
1711 for (i=textD->nVisibleLines-1; i>=0; i--) {
1712 if (textD->lineStarts[i] != -1 && pos >= textD->lineStarts[i]) {
1713 *lineNum = i;
1714 return True;
1717 return False; /* probably never be reached */
1721 ** Redisplay the text on a single line represented by "visLineNum" (the
1722 ** number of lines down from the top of the display), limited by
1723 ** "leftClip" and "rightClip" window coordinates and "leftCharIndex" and
1724 ** "rightCharIndex" character positions (not including the character at
1725 ** position "rightCharIndex").
1727 ** The cursor is also drawn if it appears on the line.
1729 static void redisplayLine(textDisp *textD, int visLineNum, int leftClip,
1730 int rightClip, int leftCharIndex, int rightCharIndex)
1732 textBuffer *buf = textD->buffer;
1733 int i, x, y, startX, charIndex, lineStartPos, lineLen, fontHeight;
1734 int stdCharWidth, charWidth, startIndex, charStyle, style;
1735 int charLen, outStartIndex, outIndex, cursorX = 0, hasCursor = False;
1736 int dispIndexOffset, cursorPos = textD->cursorPos, y_orig;
1737 char expandedChar[MAX_EXP_CHAR_LEN], outStr[MAX_DISP_LINE_LEN];
1738 char *lineStr, *outPtr;
1739 char baseChar;
1741 /* If line is not displayed, skip it */
1742 if (visLineNum < 0 || visLineNum >= textD->nVisibleLines)
1743 return;
1745 /* Shrink the clipping range to the active display area */
1746 leftClip = max(textD->left, leftClip);
1747 rightClip = min(rightClip, textD->left + textD->width);
1749 if (leftClip > rightClip) {
1750 return;
1753 /* Calculate y coordinate of the string to draw */
1754 fontHeight = textD->ascent + textD->descent;
1755 y = textD->top + visLineNum * fontHeight;
1757 /* Get the text, length, and buffer position of the line to display */
1758 lineStartPos = textD->lineStarts[visLineNum];
1759 if (lineStartPos == -1) {
1760 lineLen = 0;
1761 lineStr = NULL;
1762 } else {
1763 lineLen = visLineLength(textD, visLineNum);
1764 lineStr = BufGetRange(buf, lineStartPos, lineStartPos + lineLen);
1767 /* Space beyond the end of the line is still counted in units of characters
1768 of a standardized character width (this is done mostly because style
1769 changes based on character position can still occur in this region due
1770 to rectangular selections). stdCharWidth must be non-zero to prevent a
1771 potential infinite loop if x does not advance */
1772 stdCharWidth = textD->fontStruct->max_bounds.width;
1773 if (stdCharWidth <= 0) {
1774 fprintf(stderr, "Internal Error, bad font measurement\n");
1775 XtFree(lineStr);
1776 return;
1779 /* Rectangular selections are based on "real" line starts (after a newline
1780 or start of buffer). Calculate the difference between the last newline
1781 position and the line start we're using. Since scanning back to find a
1782 newline is expensive, only do so if there's actually a rectangular
1783 selection which needs it */
1784 if (textD->continuousWrap && (rangeTouchesRectSel(&buf->primary,
1785 lineStartPos, lineStartPos + lineLen) || rangeTouchesRectSel(
1786 &buf->secondary, lineStartPos, lineStartPos + lineLen) ||
1787 rangeTouchesRectSel(&buf->highlight, lineStartPos,
1788 lineStartPos + lineLen))) {
1789 dispIndexOffset = BufCountDispChars(buf,
1790 BufStartOfLine(buf, lineStartPos), lineStartPos);
1791 } else
1792 dispIndexOffset = 0;
1794 /* Step through character positions from the beginning of the line (even if
1795 that's off the left edge of the displayed area) to find the first
1796 character position that's not clipped, and the x coordinate for drawing
1797 that character */
1798 x = textD->left - textD->horizOffset;
1799 outIndex = 0;
1800 for(charIndex=0; ; charIndex++) {
1801 baseChar = '\0';
1802 charLen = charIndex >= lineLen ? 1 :
1803 BufExpandCharacter(baseChar = lineStr[charIndex], outIndex,
1804 expandedChar, buf->tabDist, buf->nullSubsChar);
1805 style = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1806 outIndex + dispIndexOffset, baseChar);
1807 charWidth = charIndex >= lineLen ? stdCharWidth :
1808 stringWidth(textD, expandedChar, charLen, style);
1809 if (x + charWidth >= leftClip && charIndex >= leftCharIndex) {
1810 startIndex = charIndex;
1811 outStartIndex = outIndex;
1812 startX = x;
1813 break;
1815 x += charWidth;
1816 outIndex += charLen;
1819 /* Scan character positions from the beginning of the clipping range, and
1820 draw parts whenever the style changes (also note if the cursor is on
1821 this line, and where it should be drawn to take advantage of the x
1822 position which we've gone to so much trouble to calculate) */
1823 outPtr = outStr;
1824 outIndex = outStartIndex;
1825 x = startX;
1826 for(charIndex=startIndex; charIndex<rightCharIndex; charIndex++) {
1827 if (lineStartPos+charIndex == cursorPos) {
1828 if (charIndex < lineLen || (charIndex == lineLen &&
1829 cursorPos >= buf->length)) {
1830 hasCursor = True;
1831 cursorX = x - 1;
1832 } else if (charIndex == lineLen) {
1833 if (wrapUsesCharacter(textD, cursorPos)) {
1834 hasCursor = True;
1835 cursorX = x - 1;
1839 baseChar = '\0';
1840 charLen = charIndex >= lineLen ? 1 :
1841 BufExpandCharacter(baseChar = lineStr[charIndex], outIndex,
1842 expandedChar, buf->tabDist, buf->nullSubsChar);
1843 charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1844 outIndex + dispIndexOffset, baseChar);
1845 for (i=0; i<charLen; i++) {
1846 if (i != 0 && charIndex < lineLen && lineStr[charIndex] == '\t')
1847 charStyle = styleOfPos(textD, lineStartPos, lineLen,
1848 charIndex, outIndex + dispIndexOffset, '\t');
1849 if (charStyle != style) {
1850 drawString(textD, style, startX, y, x, outStr, outPtr - outStr);
1851 outPtr = outStr;
1852 startX = x;
1853 style = charStyle;
1855 if (charIndex < lineLen) {
1856 *outPtr = expandedChar[i];
1857 charWidth = stringWidth(textD, &expandedChar[i], 1, charStyle);
1858 } else
1859 charWidth = stdCharWidth;
1860 outPtr++;
1861 x += charWidth;
1862 outIndex++;
1864 if (outPtr-outStr+MAX_EXP_CHAR_LEN>=MAX_DISP_LINE_LEN || x>=rightClip)
1865 break;
1868 /* Draw the remaining style segment */
1869 drawString(textD, style, startX, y, x, outStr, outPtr - outStr);
1871 /* Draw the cursor if part of it appeared on the redisplayed part of
1872 this line. Also check for the cases which are not caught as the
1873 line is scanned above: when the cursor appears at the very end
1874 of the redisplayed section. */
1875 y_orig = textD->cursorY;
1876 if (textD->cursorOn) {
1877 if (hasCursor)
1878 drawCursor(textD, cursorX, y);
1879 else if (charIndex<lineLen && (lineStartPos+charIndex+1 == cursorPos)
1880 && x == rightClip) {
1881 if (cursorPos >= buf->length)
1882 drawCursor(textD, x - 1, y);
1883 else {
1884 if (wrapUsesCharacter(textD, cursorPos))
1885 drawCursor(textD, x - 1, y);
1890 /* If the y position of the cursor has changed, redraw the calltip */
1891 if (hasCursor && (y_orig != textD->cursorY || y_orig != y))
1892 TextDRedrawCalltip(textD, 0);
1894 if (lineStr != NULL)
1895 XtFree(lineStr);
1899 ** Draw a string or blank area according to parameter "style", using the
1900 ** appropriate colors and drawing method for that style, with top left
1901 ** corner at x, y. If style says to draw text, use "string" as source of
1902 ** characters, and draw "nChars", if style is FILL, erase
1903 ** rectangle where text would have drawn from x to toX and from y to
1904 ** the maximum y extent of the current font(s).
1906 static void drawString(textDisp *textD, int style, int x, int y, int toX,
1907 char *string, int nChars)
1909 GC gc, bgGC;
1910 XGCValues gcValues;
1911 XFontStruct *fs = textD->fontStruct;
1912 Pixel bground = textD->bgPixel;
1913 Pixel fground = textD->fgPixel;
1914 int underlineStyle = FALSE;
1916 /* Don't draw if widget isn't realized */
1917 if (XtWindow(textD->w) == 0)
1918 return;
1920 /* select a GC */
1921 if (style & (STYLE_LOOKUP_MASK | BACKLIGHT_MASK | RANGESET_MASK)) {
1922 gc = bgGC = textD->styleGC;
1924 else if (style & HIGHLIGHT_MASK) {
1925 gc = textD->highlightGC;
1926 bgGC = textD->highlightBGGC;
1928 else if (style & PRIMARY_MASK) {
1929 gc = textD->selectGC;
1930 bgGC = textD->selectBGGC;
1932 else {
1933 gc = bgGC = textD->gc;
1936 if (gc == textD->styleGC) {
1937 /* we have work to do */
1938 styleTableEntry *styleRec;
1939 /* Set font, color, and gc depending on style. For normal text, GCs
1940 for normal drawing, or drawing within a selection or highlight are
1941 pre-allocated and pre-configured. For syntax highlighting, GCs are
1942 configured here, on the fly. */
1943 if (style & STYLE_LOOKUP_MASK) {
1944 styleRec = &textD->styleTable[(style & STYLE_LOOKUP_MASK) - ASCII_A];
1945 underlineStyle = styleRec->underline;
1946 fs = styleRec->font;
1947 gcValues.font = fs->fid;
1948 fground = styleRec->color;
1949 /* here you could pick up specific select and highlight fground */
1951 else {
1952 styleRec = NULL;
1953 gcValues.font = fs->fid;
1954 fground = textD->fgPixel;
1956 /* Background color priority order is:
1957 1 Primary(Selection), 2 Highlight(Parens),
1958 3 Rangeset, 4 SyntaxHighlightStyle,
1959 5 Backlight (if NOT fill), 6 DefaultBackground */
1960 bground =
1961 style & PRIMARY_MASK ? textD->selectBGPixel :
1962 style & HIGHLIGHT_MASK ? textD->highlightBGPixel :
1963 style & RANGESET_MASK ?
1964 getRangesetColor(textD,
1965 (style&RANGESET_MASK)>>RANGESET_SHIFT,
1966 bground) :
1967 styleRec && styleRec->bgColorName ? styleRec->bgColor :
1968 (style & BACKLIGHT_MASK) && !(style & FILL_MASK) ?
1969 textD->bgClassPixel[(style>>BACKLIGHT_SHIFT) & 0xff] :
1970 textD->bgPixel;
1971 if (fground == bground) /* B&W kludge */
1972 fground = textD->bgPixel;
1973 /* set up gc for clearing using the foreground color entry */
1974 gcValues.foreground = gcValues.background = bground;
1975 XChangeGC(XtDisplay(textD->w), gc,
1976 GCFont | GCForeground | GCBackground, &gcValues);
1979 /* Draw blank area rather than text, if that was the request */
1980 if (style & FILL_MASK) {
1981 /* wipes out to right hand edge of widget */
1982 if (toX >= textD->left)
1983 clearRect(textD, bgGC, max(x, textD->left), y,
1984 toX - max(x, textD->left), textD->ascent + textD->descent);
1985 return;
1988 /* If any space around the character remains unfilled (due to use of
1989 different sized fonts for highlighting), fill in above or below
1990 to erase previously drawn characters */
1991 if (fs->ascent < textD->ascent)
1992 clearRect(textD, bgGC, x, y, toX - x, textD->ascent - fs->ascent);
1993 if (fs->descent < textD->descent)
1994 clearRect(textD, bgGC, x, y + textD->ascent + fs->descent, toX - x,
1995 textD->descent - fs->descent);
1997 /* set up gc for writing text (set foreground properly) */
1998 if (bgGC == textD->styleGC) {
1999 gcValues.foreground = fground;
2000 XChangeGC(XtDisplay(textD->w), gc, GCForeground, &gcValues);
2003 /* Draw the string using gc and font set above */
2004 XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w), gc, x,
2005 y + textD->ascent, string, nChars);
2007 /* Underline if style is secondary selection */
2008 if (style & SECONDARY_MASK || underlineStyle)
2010 /* restore foreground in GC (was set to background by clearRect()) */
2011 gcValues.foreground = fground;
2012 XChangeGC(XtDisplay(textD->w), gc,
2013 GCForeground, &gcValues);
2014 /* draw underline */
2015 XDrawLine(XtDisplay(textD->w), XtWindow(textD->w), gc, x,
2016 y + textD->ascent, toX - 1, y + textD->ascent);
2021 ** Clear a rectangle with the appropriate background color for "style"
2023 static void clearRect(textDisp *textD, GC gc, int x, int y,
2024 int width, int height)
2026 /* A width of zero means "clear to end of window" to XClearArea */
2027 if (width == 0 || XtWindow(textD->w) == 0)
2028 return;
2030 if (gc == textD->gc) {
2031 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), x, y,
2032 width, height, False);
2034 else {
2035 XFillRectangle(XtDisplay(textD->w), XtWindow(textD->w),
2036 gc, x, y, width, height);
2041 ** Draw a cursor with top center at x, y.
2043 static void drawCursor(textDisp *textD, int x, int y)
2045 XSegment segs[5];
2046 int left, right, cursorWidth, midY;
2047 int fontWidth = textD->fontStruct->min_bounds.width, nSegs = 0;
2048 int fontHeight = textD->ascent + textD->descent;
2049 int bot = y + fontHeight - 1;
2051 if (XtWindow(textD->w) == 0 || x < textD->left-1 ||
2052 x > textD->left + textD->width)
2053 return;
2055 /* For cursors other than the block, make them around 2/3 of a character
2056 width, rounded to an even number of pixels so that X will draw an
2057 odd number centered on the stem at x. */
2058 cursorWidth = (fontWidth/3) * 2;
2059 left = x - cursorWidth/2;
2060 right = left + cursorWidth;
2062 /* Create segments and draw cursor */
2063 if (textD->cursorStyle == CARET_CURSOR) {
2064 midY = bot - fontHeight/5;
2065 segs[0].x1 = left; segs[0].y1 = bot; segs[0].x2 = x; segs[0].y2 = midY;
2066 segs[1].x1 = x; segs[1].y1 = midY; segs[1].x2 = right; segs[1].y2 = bot;
2067 segs[2].x1 = left; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2=midY-1;
2068 segs[3].x1 = x; segs[3].y1=midY-1; segs[3].x2 = right; segs[3].y2 = bot;
2069 nSegs = 4;
2070 } else if (textD->cursorStyle == NORMAL_CURSOR) {
2071 segs[0].x1 = left; segs[0].y1 = y; segs[0].x2 = right; segs[0].y2 = y;
2072 segs[1].x1 = x; segs[1].y1 = y; segs[1].x2 = x; segs[1].y2 = bot;
2073 segs[2].x1 = left; segs[2].y1 = bot; segs[2].x2 = right; segs[2].y2=bot;
2074 nSegs = 3;
2075 } else if (textD->cursorStyle == HEAVY_CURSOR) {
2076 segs[0].x1 = x-1; segs[0].y1 = y; segs[0].x2 = x-1; segs[0].y2 = bot;
2077 segs[1].x1 = x; segs[1].y1 = y; segs[1].x2 = x; segs[1].y2 = bot;
2078 segs[2].x1 = x+1; segs[2].y1 = y; segs[2].x2 = x+1; segs[2].y2 = bot;
2079 segs[3].x1 = left; segs[3].y1 = y; segs[3].x2 = right; segs[3].y2 = y;
2080 segs[4].x1 = left; segs[4].y1 = bot; segs[4].x2 = right; segs[4].y2=bot;
2081 nSegs = 5;
2082 } else if (textD->cursorStyle == DIM_CURSOR) {
2083 midY = y + fontHeight/2;
2084 segs[0].x1 = x; segs[0].y1 = y; segs[0].x2 = x; segs[0].y2 = y;
2085 segs[1].x1 = x; segs[1].y1 = midY; segs[1].x2 = x; segs[1].y2 = midY;
2086 segs[2].x1 = x; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2 = bot;
2087 nSegs = 3;
2088 } else if (textD->cursorStyle == BLOCK_CURSOR) {
2089 right = x + fontWidth;
2090 segs[0].x1 = x; segs[0].y1 = y; segs[0].x2 = right; segs[0].y2 = y;
2091 segs[1].x1 = right; segs[1].y1 = y; segs[1].x2 = right; segs[1].y2=bot;
2092 segs[2].x1 = right; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2 = bot;
2093 segs[3].x1 = x; segs[3].y1 = bot; segs[3].x2 = x; segs[3].y2 = y;
2094 nSegs = 4;
2096 XDrawSegments(XtDisplay(textD->w), XtWindow(textD->w),
2097 textD->cursorFGGC, segs, nSegs);
2099 /* Save the last position drawn */
2100 textD->cursorX = x;
2101 textD->cursorY = y;
2105 ** Determine the drawing method to use to draw a specific character from "buf".
2106 ** "lineStartPos" gives the character index where the line begins, "lineIndex",
2107 ** the number of characters past the beginning of the line, and "dispIndex",
2108 ** the number of displayed characters past the beginning of the line. Passing
2109 ** lineStartPos of -1 returns the drawing style for "no text".
2111 ** Why not just: styleOfPos(textD, pos)? Because style applies to blank areas
2112 ** of the window beyond the text boundaries, and because this routine must also
2113 ** decide whether a position is inside of a rectangular selection, and do so
2114 ** efficiently, without re-counting character positions from the start of the
2115 ** line.
2117 ** Note that style is a somewhat incorrect name, drawing method would
2118 ** be more appropriate.
2120 static int styleOfPos(textDisp *textD, int lineStartPos,
2121 int lineLen, int lineIndex, int dispIndex, int thisChar)
2123 textBuffer *buf = textD->buffer;
2124 textBuffer *styleBuf = textD->styleBuffer;
2125 int pos, style = 0;
2127 if (lineStartPos == -1 || buf == NULL)
2128 return FILL_MASK;
2130 pos = lineStartPos + min(lineIndex, lineLen);
2132 if (lineIndex >= lineLen)
2133 style = FILL_MASK;
2134 else if (styleBuf != NULL) {
2135 style = (unsigned char)BufGetCharacter(styleBuf, pos);
2136 if (style == textD->unfinishedStyle) {
2137 /* encountered "unfinished" style, trigger parsing */
2138 (textD->unfinishedHighlightCB)(textD, pos, textD->highlightCBArg);
2139 style = (unsigned char)BufGetCharacter(styleBuf, pos);
2142 if (inSelection(&buf->primary, pos, lineStartPos, dispIndex))
2143 style |= PRIMARY_MASK;
2144 if (inSelection(&buf->highlight, pos, lineStartPos, dispIndex))
2145 style |= HIGHLIGHT_MASK;
2146 if (inSelection(&buf->secondary, pos, lineStartPos, dispIndex))
2147 style |= SECONDARY_MASK;
2148 /* store in the RANGESET_MASK portion of style the rangeset index for pos */
2149 if (buf->rangesetTable) {
2150 int rangesetIndex = RangesetIndex1ofPos(buf->rangesetTable, pos, True);
2151 style |= ((rangesetIndex << RANGESET_SHIFT) & RANGESET_MASK);
2153 /* store in the BACKLIGHT_MASK portion of style the background color class
2154 of the character thisChar */
2155 if (textD->bgClass)
2157 style |= (textD->bgClass[(unsigned char)thisChar]<<BACKLIGHT_SHIFT);
2159 return style;
2163 ** Find the width of a string in the font of a particular style
2165 static int stringWidth(textDisp *textD, char *string, int length, int style)
2167 XFontStruct *fs;
2169 if (style & STYLE_LOOKUP_MASK)
2170 fs = textD->styleTable[(style & STYLE_LOOKUP_MASK) - ASCII_A].font;
2171 else
2172 fs = textD->fontStruct;
2173 return XTextWidth(fs, string, length);
2177 ** Return true if position "pos" with indentation "dispIndex" is in
2178 ** selection "sel"
2180 static int inSelection(selection *sel, int pos, int lineStartPos, int dispIndex)
2182 return sel->selected &&
2183 ((!sel->rectangular &&
2184 pos >= sel->start && pos < sel->end) ||
2185 (sel->rectangular &&
2186 pos >= sel->start && lineStartPos <= sel->end &&
2187 dispIndex >= sel->rectStart && dispIndex < sel->rectEnd));
2191 ** Translate window coordinates to the nearest (insert cursor or character
2192 ** cell) text position. The parameter posType specifies how to interpret the
2193 ** position: CURSOR_POS means translate the coordinates to the nearest cursor
2194 ** position, and CHARACTER_POS means return the position of the character
2195 ** closest to (x, y).
2197 static int xyToPos(textDisp *textD, int x, int y, int posType)
2199 int charIndex, lineStart, lineLen, fontHeight;
2200 int charWidth, charLen, charStyle, visLineNum, xStep, outIndex;
2201 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
2203 /* Find the visible line number corresponding to the y coordinate */
2204 fontHeight = textD->ascent + textD->descent;
2205 visLineNum = (y - textD->top) / fontHeight;
2206 if (visLineNum < 0)
2207 return textD->firstChar;
2208 if (visLineNum >= textD->nVisibleLines)
2209 visLineNum = textD->nVisibleLines - 1;
2211 /* Find the position at the start of the line */
2212 lineStart = textD->lineStarts[visLineNum];
2214 /* If the line start was empty, return the last position in the buffer */
2215 if (lineStart == -1)
2216 return textD->buffer->length;
2218 /* Get the line text and its length */
2219 lineLen = visLineLength(textD, visLineNum);
2220 lineStr = BufGetRange(textD->buffer, lineStart, lineStart + lineLen);
2222 /* Step through character positions from the beginning of the line
2223 to find the character position corresponding to the x coordinate */
2224 xStep = textD->left - textD->horizOffset;
2225 outIndex = 0;
2226 for(charIndex=0; charIndex<lineLen; charIndex++) {
2227 charLen = BufExpandCharacter(lineStr[charIndex], outIndex, expandedChar,
2228 textD->buffer->tabDist, textD->buffer->nullSubsChar);
2229 charStyle = styleOfPos(textD, lineStart, lineLen, charIndex, outIndex,
2230 lineStr[charIndex]);
2231 charWidth = stringWidth(textD, expandedChar, charLen, charStyle);
2232 if (x < xStep + (posType == CURSOR_POS ? charWidth/2 : charWidth)) {
2233 XtFree(lineStr);
2234 return lineStart + charIndex;
2236 xStep += charWidth;
2237 outIndex += charLen;
2240 /* If the x position was beyond the end of the line, return the position
2241 of the newline at the end of the line */
2242 XtFree(lineStr);
2243 return lineStart + lineLen;
2247 ** Translate window coordinates to the nearest row and column number for
2248 ** positioning the cursor. This, of course, makes no sense when the font is
2249 ** proportional, since there are no absolute columns. The parameter posType
2250 ** specifies how to interpret the position: CURSOR_POS means translate the
2251 ** coordinates to the nearest position between characters, and CHARACTER_POS
2252 ** means translate the position to the nearest character cell.
2254 static void xyToUnconstrainedPos(textDisp *textD, int x, int y, int *row,
2255 int *column, int posType)
2257 int fontHeight = textD->ascent + textD->descent;
2258 int fontWidth = textD->fontStruct->max_bounds.width;
2260 /* Find the visible line number corresponding to the y coordinate */
2261 *row = (y - textD->top) / fontHeight;
2262 if (*row < 0) *row = 0;
2263 if (*row >= textD->nVisibleLines) *row = textD->nVisibleLines - 1;
2264 *column = ((x-textD->left) + textD->horizOffset +
2265 (posType == CURSOR_POS ? fontWidth/2 : 0)) / fontWidth;
2266 if (*column < 0) *column = 0;
2270 ** Offset the line starts array, topLineNum, firstChar and lastChar, for a new
2271 ** vertical scroll position given by newTopLineNum. If any currently displayed
2272 ** lines will still be visible, salvage the line starts values, otherwise,
2273 ** count lines from the nearest known line start (start or end of buffer, or
2274 ** the closest value in the lineStarts array)
2276 static void offsetLineStarts(textDisp *textD, int newTopLineNum)
2278 int oldTopLineNum = textD->topLineNum;
2279 int oldFirstChar = textD->firstChar;
2280 int lineDelta = newTopLineNum - oldTopLineNum;
2281 int nVisLines = textD->nVisibleLines;
2282 int *lineStarts = textD->lineStarts;
2283 int i, lastLineNum;
2284 textBuffer *buf = textD->buffer;
2286 /* If there was no offset, nothing needs to be changed */
2287 if (lineDelta == 0)
2288 return;
2290 /* { int i;
2291 printf("Scroll, lineDelta %d\n", lineDelta);
2292 printf("lineStarts Before: ");
2293 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2294 printf("\n");
2295 } */
2297 /* Find the new value for firstChar by counting lines from the nearest
2298 known line start (start or end of buffer, or the closest value in the
2299 lineStarts array) */
2300 lastLineNum = oldTopLineNum + nVisLines - 1;
2301 if (newTopLineNum < oldTopLineNum && newTopLineNum < -lineDelta) {
2302 textD->firstChar = TextDCountForwardNLines(textD, 0, newTopLineNum-1,
2303 True);
2304 /* printf("counting forward %d lines from start\n", newTopLineNum-1);*/
2305 } else if (newTopLineNum < oldTopLineNum) {
2306 textD->firstChar = TextDCountBackwardNLines(textD, textD->firstChar,
2307 -lineDelta);
2308 /* printf("counting backward %d lines from firstChar\n", -lineDelta);*/
2309 } else if (newTopLineNum < lastLineNum) {
2310 textD->firstChar = lineStarts[newTopLineNum - oldTopLineNum];
2311 /* printf("taking new start from lineStarts[%d]\n",
2312 newTopLineNum - oldTopLineNum); */
2313 } else if (newTopLineNum-lastLineNum < textD->nBufferLines-newTopLineNum) {
2314 textD->firstChar = TextDCountForwardNLines(textD,
2315 lineStarts[nVisLines-1], newTopLineNum - lastLineNum, True);
2316 /* printf("counting forward %d lines from start of last line\n",
2317 newTopLineNum - lastLineNum); */
2318 } else {
2319 textD->firstChar = TextDCountBackwardNLines(textD, buf->length,
2320 textD->nBufferLines - newTopLineNum + 1);
2321 /* printf("counting backward %d lines from end\n",
2322 textD->nBufferLines - newTopLineNum + 1); */
2325 /* Fill in the line starts array */
2326 if (lineDelta < 0 && -lineDelta < nVisLines) {
2327 for (i=nVisLines-1; i >= -lineDelta; i--)
2328 lineStarts[i] = lineStarts[i+lineDelta];
2329 calcLineStarts(textD, 0, -lineDelta);
2330 } else if (lineDelta > 0 && lineDelta < nVisLines) {
2331 for (i=0; i<nVisLines-lineDelta; i++)
2332 lineStarts[i] = lineStarts[i+lineDelta];
2333 calcLineStarts(textD, nVisLines-lineDelta, nVisLines-1);
2334 } else
2335 calcLineStarts(textD, 0, nVisLines);
2337 /* Set lastChar and topLineNum */
2338 calcLastChar(textD);
2339 textD->topLineNum = newTopLineNum;
2341 /* If we're numbering lines or being asked to maintain an absolute line
2342 number, re-calculate the absolute line number */
2343 offsetAbsLineNum(textD, oldFirstChar);
2345 /* { int i;
2346 printf("lineStarts After: ");
2347 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2348 printf("\n");
2349 } */
2353 ** Update the line starts array, topLineNum, firstChar and lastChar for text
2354 ** display "textD" after a modification to the text buffer, given by the
2355 ** position where the change began "pos", and the nmubers of characters
2356 ** and lines inserted and deleted.
2358 static void updateLineStarts(textDisp *textD, int pos, int charsInserted,
2359 int charsDeleted, int linesInserted, int linesDeleted, int *scrolled)
2361 int *lineStarts = textD->lineStarts;
2362 int i, lineOfPos, lineOfEnd, nVisLines = textD->nVisibleLines;
2363 int charDelta = charsInserted - charsDeleted;
2364 int lineDelta = linesInserted - linesDeleted;
2366 /* { int i;
2367 printf("linesDeleted %d, linesInserted %d, charsInserted %d, charsDeleted %d\n",
2368 linesDeleted, linesInserted, charsInserted, charsDeleted);
2369 printf("lineStarts Before: ");
2370 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2371 printf("\n");
2372 } */
2373 /* If all of the changes were before the displayed text, the display
2374 doesn't change, just update the top line num and offset the line
2375 start entries and first and last characters */
2376 if (pos + charsDeleted < textD->firstChar) {
2377 textD->topLineNum += lineDelta;
2378 for (i=0; i<nVisLines && lineStarts[i] != -1; i++)
2379 lineStarts[i] += charDelta;
2380 /* { int i;
2381 printf("lineStarts after delete doesn't touch: ");
2382 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2383 printf("\n");
2384 } */
2385 textD->firstChar += charDelta;
2386 textD->lastChar += charDelta;
2387 *scrolled = False;
2388 return;
2391 /* The change began before the beginning of the displayed text, but
2392 part or all of the displayed text was deleted */
2393 if (pos < textD->firstChar) {
2394 /* If some text remains in the window, anchor on that */
2395 if (posToVisibleLineNum(textD, pos + charsDeleted, &lineOfEnd) &&
2396 ++lineOfEnd < nVisLines && lineStarts[lineOfEnd] != -1) {
2397 textD->topLineNum = max(1, textD->topLineNum + lineDelta);
2398 textD->firstChar = TextDCountBackwardNLines(textD,
2399 lineStarts[lineOfEnd] + charDelta, lineOfEnd);
2400 /* Otherwise anchor on original line number and recount everything */
2401 } else {
2402 if (textD->topLineNum > textD->nBufferLines + lineDelta) {
2403 textD->topLineNum = 1;
2404 textD->firstChar = 0;
2405 } else
2406 textD->firstChar = TextDCountForwardNLines(textD, 0,
2407 textD->topLineNum - 1, True);
2409 calcLineStarts(textD, 0, nVisLines-1);
2410 /* { int i;
2411 printf("lineStarts after delete encroaches: ");
2412 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2413 printf("\n");
2414 } */
2415 /* calculate lastChar by finding the end of the last displayed line */
2416 calcLastChar(textD);
2417 *scrolled = True;
2418 return;
2421 /* If the change was in the middle of the displayed text (it usually is),
2422 salvage as much of the line starts array as possible by moving and
2423 offsetting the entries after the changed area, and re-counting the
2424 added lines or the lines beyond the salvaged part of the line starts
2425 array */
2426 if (pos <= textD->lastChar) {
2427 /* find line on which the change began */
2428 posToVisibleLineNum(textD, pos, &lineOfPos);
2429 /* salvage line starts after the changed area */
2430 if (lineDelta == 0) {
2431 for (i=lineOfPos+1; i<nVisLines && lineStarts[i]!= -1; i++)
2432 lineStarts[i] += charDelta;
2433 } else if (lineDelta > 0) {
2434 for (i=nVisLines-1; i>=lineOfPos+lineDelta+1; i--)
2435 lineStarts[i] = lineStarts[i-lineDelta] +
2436 (lineStarts[i-lineDelta] == -1 ? 0 : charDelta);
2437 } else /* (lineDelta < 0) */ {
2438 for (i=max(0,lineOfPos+1); i<nVisLines+lineDelta; i++)
2439 lineStarts[i] = lineStarts[i-lineDelta] +
2440 (lineStarts[i-lineDelta] == -1 ? 0 : charDelta);
2442 /* { int i;
2443 printf("lineStarts after salvage: ");
2444 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2445 printf("\n");
2446 } */
2447 /* fill in the missing line starts */
2448 if (linesInserted >= 0)
2449 calcLineStarts(textD, lineOfPos + 1, lineOfPos + linesInserted);
2450 if (lineDelta < 0)
2451 calcLineStarts(textD, nVisLines+lineDelta, nVisLines);
2452 /* { int i;
2453 printf("lineStarts after recalculation: ");
2454 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2455 printf("\n");
2456 } */
2457 /* calculate lastChar by finding the end of the last displayed line */
2458 calcLastChar(textD);
2459 *scrolled = False;
2460 return;
2463 /* Change was past the end of the displayed text, but displayable by virtue
2464 of being an insert at the end of the buffer into visible blank lines */
2465 if (emptyLinesVisible(textD)) {
2466 posToVisibleLineNum(textD, pos, &lineOfPos);
2467 calcLineStarts(textD, lineOfPos, lineOfPos+linesInserted);
2468 calcLastChar(textD);
2469 /* { int i;
2470 printf("lineStarts after insert at end: ");
2471 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2472 printf("\n");
2473 } */
2474 *scrolled = False;
2475 return;
2478 /* Change was beyond the end of the buffer and not visible, do nothing */
2479 *scrolled = False;
2483 ** Scan through the text in the "textD"'s buffer and recalculate the line
2484 ** starts array values beginning at index "startLine" and continuing through
2485 ** (including) "endLine". It assumes that the line starts entry preceding
2486 ** "startLine" (or textD->firstChar if startLine is 0) is good, and re-counts
2487 ** newlines to fill in the requested entries. Out of range values for
2488 ** "startLine" and "endLine" are acceptable.
2490 static void calcLineStarts(textDisp *textD, int startLine, int endLine)
2492 int startPos, bufLen = textD->buffer->length;
2493 int line, lineEnd, nextLineStart, nVis = textD->nVisibleLines;
2494 int *lineStarts = textD->lineStarts;
2496 /* Clean up (possibly) messy input parameters */
2497 if (nVis == 0) return;
2498 if (endLine < 0) endLine = 0;
2499 if (endLine >= nVis) endLine = nVis - 1;
2500 if (startLine < 0) startLine = 0;
2501 if (startLine >=nVis) startLine = nVis - 1;
2502 if (startLine > endLine)
2503 return;
2505 /* Find the last known good line number -> position mapping */
2506 if (startLine == 0) {
2507 lineStarts[0] = textD->firstChar;
2508 startLine = 1;
2510 startPos = lineStarts[startLine-1];
2512 /* If the starting position is already past the end of the text,
2513 fill in -1's (means no text on line) and return */
2514 if (startPos == -1) {
2515 for (line=startLine; line<=endLine; line++)
2516 lineStarts[line] = -1;
2517 return;
2520 /* Loop searching for ends of lines and storing the positions of the
2521 start of the next line in lineStarts */
2522 for (line=startLine; line<=endLine; line++) {
2523 findLineEnd(textD, startPos, True, &lineEnd, &nextLineStart);
2524 startPos = nextLineStart;
2525 if (startPos >= bufLen) {
2526 /* If the buffer ends with a newline or line break, put
2527 buf->length in the next line start position (instead of
2528 a -1 which is the normal marker for an empty line) to
2529 indicate that the cursor may safely be displayed there */
2530 if (line == 0 || (lineStarts[line-1] != bufLen &&
2531 lineEnd != nextLineStart)) {
2532 lineStarts[line] = bufLen;
2533 line++;
2535 break;
2537 lineStarts[line] = startPos;
2540 /* Set any entries beyond the end of the text to -1 */
2541 for (; line<=endLine; line++)
2542 lineStarts[line] = -1;
2546 ** Given a textDisp with a complete, up-to-date lineStarts array, update
2547 ** the lastChar entry to point to the last buffer position displayed.
2549 static void calcLastChar(textDisp *textD)
2551 int i;
2553 for (i=textD->nVisibleLines-1; i>0 && textD->lineStarts[i]== -1; i--);
2554 textD->lastChar = i < 0 ? 0 :
2555 TextDEndOfLine(textD, textD->lineStarts[i], True);
2558 void TextDImposeGraphicsExposeTranslation(textDisp *textD, int *xOffset, int *yOffset)
2560 if (textD->graphicsExposeQueue) {
2561 graphicExposeTranslationEntry *thisGEQEntry = textD->graphicsExposeQueue->next;
2562 if (thisGEQEntry) {
2563 *xOffset += thisGEQEntry->horizontal;
2564 *yOffset += thisGEQEntry->vertical;
2569 Boolean TextDPopGraphicExposeQueueEntry(textDisp *textD)
2571 graphicExposeTranslationEntry *removedGEQEntry = textD->graphicsExposeQueue;
2573 if (removedGEQEntry) {
2574 textD->graphicsExposeQueue = removedGEQEntry->next;
2575 XtFree((char *)removedGEQEntry);
2577 return(removedGEQEntry?True:False);
2580 void TextDTranlateGraphicExposeQueue(textDisp *textD, int xOffset, int yOffset, Boolean appendEntry)
2582 graphicExposeTranslationEntry *newGEQEntry = NULL;
2583 if (appendEntry) {
2584 newGEQEntry = (graphicExposeTranslationEntry *)XtMalloc(sizeof(graphicExposeTranslationEntry));
2585 newGEQEntry->next = NULL;
2586 newGEQEntry->horizontal = xOffset;
2587 newGEQEntry->vertical = yOffset;
2589 if (textD->graphicsExposeQueue) {
2590 graphicExposeTranslationEntry *iter = textD->graphicsExposeQueue;
2591 while (iter->next) {
2592 iter->next->horizontal += xOffset;
2593 iter->next->vertical += yOffset;
2594 iter = iter->next;
2596 if (appendEntry) {
2597 iter->next = (struct graphicExposeTranslationEntry *)newGEQEntry;
2600 else {
2601 if (appendEntry) {
2602 textD->graphicsExposeQueue = newGEQEntry;
2607 static void setScroll(textDisp *textD, int topLineNum, int horizOffset,
2608 int updateVScrollBar, int updateHScrollBar)
2610 int fontHeight = textD->ascent + textD->descent;
2611 int origHOffset = textD->horizOffset;
2612 int lineDelta = textD->topLineNum - topLineNum;
2613 int xOffset, yOffset, srcX, srcY, dstX, dstY, width, height;
2614 int exactHeight = textD->height - textD->height %
2615 (textD->ascent + textD->descent);
2617 /* Do nothing if scroll position hasn't actually changed or there's no
2618 window to draw in yet */
2619 if (XtWindow(textD->w) == 0 || (textD->horizOffset == horizOffset &&
2620 textD->topLineNum == topLineNum))
2621 return;
2623 /* If part of the cursor is protruding beyond the text clipping region,
2624 clear it off */
2625 blankCursorProtrusions(textD);
2627 /* If the vertical scroll position has changed, update the line
2628 starts array and related counters in the text display */
2629 offsetLineStarts(textD, topLineNum);
2631 /* Just setting textD->horizOffset is enough information for redisplay */
2632 textD->horizOffset = horizOffset;
2634 /* Update the scroll bar positions if requested, note: updating the
2635 horizontal scroll bars can have the further side-effect of changing
2636 the horizontal scroll position, textD->horizOffset */
2637 if (updateVScrollBar && textD->vScrollBar != NULL) {
2638 updateVScrollBarRange(textD);
2640 if (updateHScrollBar && textD->hScrollBar != NULL) {
2641 updateHScrollBarRange(textD);
2644 /* Redisplay everything if the window is partially obscured (since
2645 it's too hard to tell what displayed areas are salvageable) or
2646 if there's nothing to recover because the scroll distance is large */
2647 xOffset = origHOffset - textD->horizOffset;
2648 yOffset = lineDelta * fontHeight;
2649 if (textD->visibility != VisibilityUnobscured ||
2650 abs(xOffset) > textD->width || abs(yOffset) > exactHeight) {
2651 TextDTranlateGraphicExposeQueue(textD, xOffset, yOffset, False);
2652 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
2653 textD->height);
2654 } else {
2655 /* If the window is not obscured, paint most of the window using XCopyArea
2656 from existing displayed text, and redraw only what's necessary */
2657 /* Recover the useable window areas by moving to the proper location */
2658 srcX = textD->left + (xOffset >= 0 ? 0 : -xOffset);
2659 dstX = textD->left + (xOffset >= 0 ? xOffset : 0);
2660 width = textD->width - abs(xOffset);
2661 srcY = textD->top + (yOffset >= 0 ? 0 : -yOffset);
2662 dstY = textD->top + (yOffset >= 0 ? yOffset : 0);
2663 height = exactHeight - abs(yOffset);
2664 resetClipRectangles(textD);
2665 TextDTranlateGraphicExposeQueue(textD, xOffset, yOffset, True);
2666 XCopyArea(XtDisplay(textD->w), XtWindow(textD->w), XtWindow(textD->w),
2667 textD->gc, srcX, srcY, width, height, dstX, dstY);
2668 /* redraw the un-recoverable parts */
2669 if (yOffset > 0) {
2670 TextDRedisplayRect(textD, textD->left, textD->top,
2671 textD->width, yOffset);
2673 else if (yOffset < 0) {
2674 TextDRedisplayRect(textD, textD->left, textD->top +
2675 textD->height + yOffset, textD->width, -yOffset);
2677 if (xOffset > 0) {
2678 TextDRedisplayRect(textD, textD->left, textD->top,
2679 xOffset, textD->height);
2681 else if (xOffset < 0) {
2682 TextDRedisplayRect(textD, textD->left + textD->width + xOffset,
2683 textD->top, -xOffset, textD->height);
2685 /* Restore protruding parts of the cursor */
2686 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
2689 /* Refresh line number/calltip display if its up and we've scrolled
2690 vertically */
2691 if (lineDelta != 0) {
2692 redrawLineNumbers(textD, False);
2693 TextDRedrawCalltip(textD, 0);
2696 HandleAllPendingGraphicsExposeNoExposeEvents((TextWidget)textD->w, NULL);
2700 ** Update the minimum, maximum, slider size, page increment, and value
2701 ** for vertical scroll bar.
2703 static void updateVScrollBarRange(textDisp *textD)
2705 int sliderSize, sliderMax, sliderValue;
2707 if (textD->vScrollBar == NULL)
2708 return;
2710 /* The Vert. scroll bar value and slider size directly represent the top
2711 line number, and the number of visible lines respectively. The scroll
2712 bar maximum value is chosen to generally represent the size of the whole
2713 buffer, with minor adjustments to keep the scroll bar widget happy */
2714 sliderSize = max(textD->nVisibleLines, 1); /* Avoid X warning (size < 1) */
2715 sliderValue = textD->topLineNum;
2716 sliderMax = max(textD->nBufferLines + 2 +
2717 TEXT_OF_TEXTD(textD).cursorVPadding,
2718 sliderSize + sliderValue);
2719 XtVaSetValues(textD->vScrollBar,
2720 XmNmaximum, sliderMax,
2721 XmNsliderSize, sliderSize,
2722 XmNpageIncrement, max(1, textD->nVisibleLines - 1),
2723 XmNvalue, sliderValue, NULL);
2727 ** Update the minimum, maximum, slider size, page increment, and value
2728 ** for the horizontal scroll bar. If scroll position is such that there
2729 ** is blank space to the right of all lines of text, scroll back (adjust
2730 ** horizOffset but don't redraw) to take up the slack and position the
2731 ** right edge of the text at the right edge of the display.
2733 ** Note, there is some cost to this routine, since it scans the whole range
2734 ** of displayed text, particularly since it's usually called for each typed
2735 ** character!
2737 static int updateHScrollBarRange(textDisp *textD)
2739 int i, maxWidth = 0, sliderMax, sliderWidth;
2740 int origHOffset = textD->horizOffset;
2742 if (textD->hScrollBar == NULL || !XtIsManaged(textD->hScrollBar))
2743 return False;
2745 /* Scan all the displayed lines to find the width of the longest line */
2746 for (i=0; i<textD->nVisibleLines && textD->lineStarts[i]!= -1; i++)
2747 maxWidth = max(measureVisLine(textD, i), maxWidth);
2749 /* If the scroll position is beyond what's necessary to keep all lines
2750 in view, scroll to the left to bring the end of the longest line to
2751 the right margin */
2752 if (maxWidth < textD->width + textD->horizOffset && textD->horizOffset > 0)
2753 textD->horizOffset = max(0, maxWidth - textD->width);
2755 /* Readjust the scroll bar */
2756 sliderWidth = textD->width;
2757 sliderMax = max(maxWidth, sliderWidth + textD->horizOffset);
2758 XtVaSetValues(textD->hScrollBar,
2759 XmNmaximum, sliderMax,
2760 XmNsliderSize, sliderWidth,
2761 XmNpageIncrement, max(textD->width - 100, 10),
2762 XmNvalue, textD->horizOffset, NULL);
2764 /* Return True if scroll position was changed */
2765 return origHOffset != textD->horizOffset;
2769 ** Define area for drawing line numbers. A width of 0 disables line
2770 ** number drawing.
2772 void TextDSetLineNumberArea(textDisp *textD, int lineNumLeft, int lineNumWidth,
2773 int textLeft)
2775 int newWidth = textD->width + textD->left - textLeft;
2776 textD->lineNumLeft = lineNumLeft;
2777 textD->lineNumWidth = lineNumWidth;
2778 textD->left = textLeft;
2779 XClearWindow(XtDisplay(textD->w), XtWindow(textD->w));
2780 resetAbsLineNum(textD);
2781 TextDResize(textD, newWidth, textD->height);
2782 TextDRedisplayRect(textD, 0, textD->top, INT_MAX, textD->height);
2786 ** Refresh the line number area. If clearAll is False, writes only over
2787 ** the character cell areas. Setting clearAll to True will clear out any
2788 ** stray marks outside of the character cell area, which might have been
2789 ** left from before a resize or font change.
2791 static void redrawLineNumbers(textDisp *textD, int clearAll)
2793 int y, line, visLine, nCols, lineStart;
2794 char lineNumString[12];
2795 int lineHeight = textD->ascent + textD->descent;
2796 int charWidth = textD->fontStruct->max_bounds.width;
2797 XRectangle clipRect;
2798 Display *display = XtDisplay(textD->w);
2800 /* Don't draw if lineNumWidth == 0 (line numbers are hidden), or widget is
2801 not yet realized */
2802 if (textD->lineNumWidth == 0 || XtWindow(textD->w) == 0)
2803 return;
2805 /* Make sure we reset the clipping range for the line numbers GC, because
2806 the GC may be shared (eg, if the line numbers and text have the same
2807 color) and therefore the clipping ranges may be invalid. */
2808 clipRect.x = textD->lineNumLeft;
2809 clipRect.y = textD->top;
2810 clipRect.width = textD->lineNumWidth;
2811 clipRect.height = textD->height;
2812 XSetClipRectangles(display, textD->lineNumGC, 0, 0,
2813 &clipRect, 1, Unsorted);
2815 /* Erase the previous contents of the line number area, if requested */
2816 if (clearAll)
2817 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->lineNumLeft,
2818 textD->top, textD->lineNumWidth, textD->height, False);
2820 /* Draw the line numbers, aligned to the text */
2821 nCols = min(11, textD->lineNumWidth / charWidth);
2822 y = textD->top;
2823 line = getAbsTopLineNum(textD);
2824 for (visLine=0; visLine < textD->nVisibleLines; visLine++) {
2825 lineStart = textD->lineStarts[visLine];
2826 if (lineStart != -1 && (lineStart==0 ||
2827 BufGetCharacter(textD->buffer, lineStart-1)=='\n')) {
2828 sprintf(lineNumString, "%*d", nCols, line);
2829 XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w),
2830 textD->lineNumGC, textD->lineNumLeft, y + textD->ascent,
2831 lineNumString, strlen(lineNumString));
2832 line++;
2833 } else {
2834 XClearArea(XtDisplay(textD->w), XtWindow(textD->w),
2835 textD->lineNumLeft, y, textD->lineNumWidth,
2836 textD->ascent + textD->descent, False);
2837 if (visLine == 0)
2838 line++;
2840 y += lineHeight;
2845 ** Callbacks for drag or valueChanged on scroll bars
2847 static void vScrollCB(Widget w, XtPointer clientData, XtPointer callData)
2849 textDisp *textD = (textDisp *)clientData;
2850 int newValue = ((XmScrollBarCallbackStruct *)callData)->value;
2851 int lineDelta = newValue - textD->topLineNum;
2853 if (lineDelta == 0)
2854 return;
2855 setScroll(textD, newValue, textD->horizOffset, False, True);
2857 static void hScrollCB(Widget w, XtPointer clientData, XtPointer callData)
2859 textDisp *textD = (textDisp *)clientData;
2860 int newValue = ((XmScrollBarCallbackStruct *)callData)->value;
2862 if (newValue == textD->horizOffset)
2863 return;
2864 setScroll(textD, textD->topLineNum, newValue, False, False);
2867 static void visibilityEH(Widget w, XtPointer data, XEvent *event,
2868 Boolean *continueDispatch)
2870 /* Record whether the window is fully visible or not. This information
2871 is used for choosing the scrolling methodology for optimal performance,
2872 if the window is partially obscured, XCopyArea may not work */
2873 ((textDisp *)data)->visibility = ((XVisibilityEvent *)event)->state;
2876 static int max(int i1, int i2)
2878 return i1 >= i2 ? i1 : i2;
2881 static int min(int i1, int i2)
2883 return i1 <= i2 ? i1 : i2;
2887 ** Count the number of newlines in a null-terminated text string;
2889 static int countLines(char *string)
2891 char *c;
2892 int lineCount = 0;
2894 if (string == NULL)
2895 return 0;
2896 for (c=string; *c!='\0'; c++)
2897 if (*c == '\n') lineCount++;
2898 return lineCount;
2902 ** Return the width in pixels of the displayed line pointed to by "visLineNum"
2904 static int measureVisLine(textDisp *textD, int visLineNum)
2906 int i, width = 0, len, style, lineLen = visLineLength(textD, visLineNum);
2907 int charCount = 0, lineStartPos = textD->lineStarts[visLineNum];
2908 char expandedChar[MAX_EXP_CHAR_LEN];
2910 if (textD->styleBuffer == NULL) {
2911 for (i=0; i<lineLen; i++) {
2912 len = BufGetExpandedChar(textD->buffer, lineStartPos + i,
2913 charCount, expandedChar);
2914 width += XTextWidth(textD->fontStruct, expandedChar, len);
2915 charCount += len;
2917 } else {
2918 for (i=0; i<lineLen; i++) {
2919 len = BufGetExpandedChar(textD->buffer, lineStartPos+i,
2920 charCount, expandedChar);
2921 style = (unsigned char)BufGetCharacter(textD->styleBuffer,
2922 lineStartPos+i) - ASCII_A;
2923 width += XTextWidth(textD->styleTable[style].font, expandedChar,
2924 len);
2925 charCount += len;
2928 return width;
2932 ** Return true if there are lines visible with no corresponding buffer text
2934 static int emptyLinesVisible(textDisp *textD)
2936 return textD->nVisibleLines > 0 &&
2937 textD->lineStarts[textD->nVisibleLines-1] == -1;
2941 ** When the cursor is at the left or right edge of the text, part of it
2942 ** sticks off into the clipped region beyond the text. Normal redrawing
2943 ** can not overwrite this protruding part of the cursor, so it must be
2944 ** erased independently by calling this routine.
2946 static void blankCursorProtrusions(textDisp *textD)
2948 int x, width, cursorX = textD->cursorX, cursorY = textD->cursorY;
2949 int fontWidth = textD->fontStruct->max_bounds.width;
2950 int fontHeight = textD->ascent + textD->descent;
2951 int cursorWidth, left = textD->left, right = left + textD->width;
2953 cursorWidth = (fontWidth/3) * 2;
2954 if (cursorX >= left-1 && cursorX <= left + cursorWidth/2 - 1) {
2955 x = cursorX - cursorWidth/2;
2956 width = left - x;
2957 } else if (cursorX >= right - cursorWidth/2 && cursorX <= right) {
2958 x = right;
2959 width = cursorX + cursorWidth/2 + 2 - right;
2960 } else
2961 return;
2963 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), x, cursorY,
2964 width, fontHeight, False);
2968 ** Allocate shared graphics contexts used by the widget, which must be
2969 ** re-allocated on a font change.
2971 static void allocateFixedFontGCs(textDisp *textD, XFontStruct *fontStruct,
2972 Pixel bgPixel, Pixel fgPixel, Pixel selectFGPixel, Pixel selectBGPixel,
2973 Pixel highlightFGPixel, Pixel highlightBGPixel, Pixel lineNumFGPixel)
2975 textD->gc = allocateGC(textD->w, GCFont | GCForeground | GCBackground,
2976 fgPixel, bgPixel, fontStruct->fid, GCClipMask, GCArcMode);
2977 textD->selectGC = allocateGC(textD->w, GCFont | GCForeground | GCBackground,
2978 selectFGPixel, selectBGPixel, fontStruct->fid, GCClipMask,
2979 GCArcMode);
2980 textD->selectBGGC = allocateGC(textD->w, GCForeground, selectBGPixel, 0,
2981 fontStruct->fid, GCClipMask, GCArcMode);
2982 textD->highlightGC = allocateGC(textD->w, GCFont|GCForeground|GCBackground,
2983 highlightFGPixel, highlightBGPixel, fontStruct->fid, GCClipMask,
2984 GCArcMode);
2985 textD->highlightBGGC = allocateGC(textD->w, GCForeground, highlightBGPixel,
2986 0, fontStruct->fid, GCClipMask, GCArcMode);
2987 textD->lineNumGC = allocateGC(textD->w, GCFont | GCForeground |
2988 GCBackground, lineNumFGPixel, bgPixel, fontStruct->fid,
2989 GCClipMask, GCArcMode);
2993 ** X11R4 does not have the XtAllocateGC function for sharing graphics contexts
2994 ** with changeable fields. Unfortunately the R4 call for creating shared
2995 ** graphics contexts (XtGetGC) is rarely useful because most widgets need
2996 ** to be able to set and change clipping, and that makes the GC unshareable.
2998 ** This function allocates and returns a gc, using XtAllocateGC if possible,
2999 ** or XCreateGC on X11R4 systems where XtAllocateGC is not available.
3001 static GC allocateGC(Widget w, unsigned long valueMask,
3002 unsigned long foreground, unsigned long background, Font font,
3003 unsigned long dynamicMask, unsigned long dontCareMask)
3005 XGCValues gcValues;
3007 gcValues.font = font;
3008 gcValues.background = background;
3009 gcValues.foreground = foreground;
3010 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4
3011 return XtAllocateGC(w, 0, valueMask, &gcValues, dynamicMask,
3012 dontCareMask);
3013 #else
3014 return XCreateGC(XtDisplay(w), RootWindowOfScreen(XtScreen(w)),
3015 valueMask, &gcValues);
3016 #endif
3020 ** Release a gc allocated with allocateGC above
3022 static void releaseGC(Widget w, GC gc)
3024 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4
3025 XtReleaseGC(w, gc);
3026 #else
3027 XFreeGC(XtDisplay(w), gc);
3028 #endif
3032 ** resetClipRectangles sets the clipping rectangles for GCs which clip
3033 ** at the text boundary (as opposed to the window boundary). These GCs
3034 ** are shared such that the drawing styles are constant, but the clipping
3035 ** rectangles are allowed to change among different users of the GCs (the
3036 ** GCs were created with XtAllocGC). This routine resets them so the clipping
3037 ** rectangles are correct for this text display.
3039 static void resetClipRectangles(textDisp *textD)
3041 XRectangle clipRect;
3042 Display *display = XtDisplay(textD->w);
3044 clipRect.x = textD->left;
3045 clipRect.y = textD->top;
3046 clipRect.width = textD->width;
3047 clipRect.height = textD->height - textD->height %
3048 (textD->ascent + textD->descent);
3050 XSetClipRectangles(display, textD->gc, 0, 0,
3051 &clipRect, 1, Unsorted);
3052 XSetClipRectangles(display, textD->selectGC, 0, 0,
3053 &clipRect, 1, Unsorted);
3054 XSetClipRectangles(display, textD->highlightGC, 0, 0,
3055 &clipRect, 1, Unsorted);
3056 XSetClipRectangles(display, textD->selectBGGC, 0, 0,
3057 &clipRect, 1, Unsorted);
3058 XSetClipRectangles(display, textD->highlightBGGC, 0, 0,
3059 &clipRect, 1, Unsorted);
3060 XSetClipRectangles(display, textD->styleGC, 0, 0,
3061 &clipRect, 1, Unsorted);
3065 ** Return the length of a line (number of displayable characters) by examining
3066 ** entries in the line starts array rather than by scanning for newlines
3068 static int visLineLength(textDisp *textD, int visLineNum)
3070 int nextLineStart, lineStartPos = textD->lineStarts[visLineNum];
3072 if (lineStartPos == -1)
3073 return 0;
3074 if (visLineNum+1 >= textD->nVisibleLines)
3075 return textD->lastChar - lineStartPos;
3076 nextLineStart = textD->lineStarts[visLineNum+1];
3077 if (nextLineStart == -1)
3078 return textD->lastChar - lineStartPos;
3079 if (wrapUsesCharacter(textD, nextLineStart-1))
3080 return nextLineStart-1 - lineStartPos;
3081 return nextLineStart - lineStartPos;
3085 ** When continuous wrap is on, and the user inserts or deletes characters,
3086 ** wrapping can happen before and beyond the changed position. This routine
3087 ** finds the extent of the changes, and counts the deleted and inserted lines
3088 ** over that range. It also attempts to minimize the size of the range to
3089 ** what has to be counted and re-displayed, so the results can be useful
3090 ** both for delimiting where the line starts need to be recalculated, and
3091 ** for deciding what part of the text to redisplay.
3093 static void findWrapRange(textDisp *textD, char *deletedText, int pos,
3094 int nInserted, int nDeleted, int *modRangeStart, int *modRangeEnd,
3095 int *linesInserted, int *linesDeleted)
3097 int length, retPos, retLines, retLineStart, retLineEnd;
3098 textBuffer *deletedTextBuf, *buf = textD->buffer;
3099 int nVisLines = textD->nVisibleLines;
3100 int *lineStarts = textD->lineStarts;
3101 int countFrom, countTo, lineStart, adjLineStart, i;
3102 int visLineNum = 0, nLines = 0;
3105 ** Determine where to begin searching: either the previous newline, or
3106 ** if possible, limit to the start of the (original) previous displayed
3107 ** line, using information from the existing line starts array
3109 if (pos >= textD->firstChar && pos <= textD->lastChar) {
3110 for (i=nVisLines-1; i>0; i--)
3111 if (lineStarts[i] != -1 && pos >= lineStarts[i])
3112 break;
3113 if (i > 0) {
3114 countFrom = lineStarts[i-1];
3115 visLineNum = i-1;
3116 } else
3117 countFrom = BufStartOfLine(buf, pos);
3118 } else
3119 countFrom = BufStartOfLine(buf, pos);
3123 ** Move forward through the (new) text one line at a time, counting
3124 ** displayed lines, and looking for either a real newline, or for the
3125 ** line starts to re-sync with the original line starts array
3127 lineStart = countFrom;
3128 *modRangeStart = countFrom;
3129 while (True) {
3131 /* advance to the next line. If the line ended in a real newline
3132 or the end of the buffer, that's far enough */
3133 wrappedLineCounter(textD, buf, lineStart, buf->length, 1, True, 0,
3134 &retPos, &retLines, &retLineStart, &retLineEnd);
3135 if (retPos >= buf->length) {
3136 countTo = buf->length;
3137 *modRangeEnd = countTo;
3138 if (retPos != retLineEnd)
3139 nLines++;
3140 break;
3141 } else
3142 lineStart = retPos;
3143 nLines++;
3144 if (lineStart > pos + nInserted &&
3145 BufGetCharacter(buf, lineStart-1) == '\n') {
3146 countTo = lineStart;
3147 *modRangeEnd = lineStart;
3148 break;
3151 /* Don't try to resync in continuous wrap mode with non-fixed font
3152 sizes; it would result in a chicken-and-egg dependency between
3153 the calculations for the inserted and the deleted lines.
3154 If we're in that mode, the number of deleted lines is calculated in
3155 advance, without resynchronization, so we shouldn't resynchronize
3156 for the inserted lines either. */
3157 if (textD->suppressResync)
3158 continue;
3160 /* check for synchronization with the original line starts array
3161 before pos, if so, the modified range can begin later */
3162 if (lineStart <= pos) {
3163 while (visLineNum<nVisLines && lineStarts[visLineNum] < lineStart)
3164 visLineNum++;
3165 if (visLineNum < nVisLines && lineStarts[visLineNum] == lineStart) {
3166 countFrom = lineStart;
3167 nLines = 0;
3168 if (visLineNum+1 < nVisLines && lineStarts[visLineNum+1] != -1)
3169 *modRangeStart = min(pos, lineStarts[visLineNum+1]-1);
3170 else
3171 *modRangeStart = countFrom;
3172 } else
3173 *modRangeStart = min(*modRangeStart, lineStart-1);
3176 /* check for synchronization with the original line starts array
3177 after pos, if so, the modified range can end early */
3178 else if (lineStart > pos + nInserted) {
3179 adjLineStart = lineStart - nInserted + nDeleted;
3180 while (visLineNum<nVisLines && lineStarts[visLineNum]<adjLineStart)
3181 visLineNum++;
3182 if (visLineNum < nVisLines && lineStarts[visLineNum] != -1 &&
3183 lineStarts[visLineNum] == adjLineStart) {
3184 countTo = TextDEndOfLine(textD, lineStart, True);
3185 *modRangeEnd = lineStart;
3186 break;
3190 *linesInserted = nLines;
3193 /* Count deleted lines between countFrom and countTo as the text existed
3194 before the modification (that is, as if the text between pos and
3195 pos+nInserted were replaced by "deletedText"). This extra context is
3196 necessary because wrapping can occur outside of the modified region
3197 as a result of adding or deleting text in the region. This is done by
3198 creating a textBuffer containing the deleted text and the necessary
3199 additional context, and calling the wrappedLineCounter on it.
3201 NOTE: This must not be done in continuous wrap mode when the font
3202 width is not fixed. In that case, the calculation would try
3203 to access style information that is no longer available (deleted
3204 text), or out of date (updated highlighting), possibly leading
3205 to completely wrong calculations and/or even crashes eventually.
3206 (This is not theoretical; it really happened.)
3208 In that case, the calculation of the number of deleted lines
3209 has happened before the buffer was modified (only in that case,
3210 because resynchronization of the line starts is impossible
3211 in that case, which makes the whole calculation less efficient).
3213 if (textD->suppressResync) {
3214 *linesDeleted = textD->nLinesDeleted;
3215 textD->suppressResync = 0;
3216 return;
3219 length = (pos-countFrom) + nDeleted +(countTo-(pos+nInserted));
3220 deletedTextBuf = BufCreatePreallocated(length);
3221 if (pos > countFrom)
3222 BufCopyFromBuf(textD->buffer, deletedTextBuf, countFrom, pos, 0);
3223 if (nDeleted != 0)
3224 BufInsert(deletedTextBuf, pos-countFrom, deletedText);
3225 if (countTo > pos+nInserted)
3226 BufCopyFromBuf(textD->buffer, deletedTextBuf,
3227 pos+nInserted, countTo, pos-countFrom+nDeleted);
3228 /* Note that we need to take into account an offset for the style buffer:
3229 the deletedTextBuf can be out of sync with the style buffer. */
3230 wrappedLineCounter(textD, deletedTextBuf, 0, length, INT_MAX, True,
3231 countFrom, &retPos, &retLines, &retLineStart, &retLineEnd);
3232 BufFree(deletedTextBuf);
3233 *linesDeleted = retLines;
3234 textD->suppressResync = 0;
3238 ** This is a stripped-down version of the findWrapRange() function above,
3239 ** intended to be used to calculate the number of "deleted" lines during
3240 ** a buffer modification. It is called _before_ the modification takes place.
3242 ** This function should only be called in continuous wrap mode with a
3243 ** non-fixed font width. In that case, it is impossible to calculate
3244 ** the number of deleted lines, because the necessary style information
3245 ** is no longer available _after_ the modification. In other cases, we
3246 ** can still perform the calculation afterwards (possibly even more
3247 ** efficiently).
3249 static void measureDeletedLines(textDisp *textD, int pos, int nDeleted)
3251 int retPos, retLines, retLineStart, retLineEnd;
3252 textBuffer *buf = textD->buffer;
3253 int nVisLines = textD->nVisibleLines;
3254 int *lineStarts = textD->lineStarts;
3255 int countFrom, lineStart;
3256 int nLines = 0, i;
3258 ** Determine where to begin searching: either the previous newline, or
3259 ** if possible, limit to the start of the (original) previous displayed
3260 ** line, using information from the existing line starts array
3262 if (pos >= textD->firstChar && pos <= textD->lastChar) {
3263 for (i=nVisLines-1; i>0; i--)
3264 if (lineStarts[i] != -1 && pos >= lineStarts[i])
3265 break;
3266 if (i > 0) {
3267 countFrom = lineStarts[i-1];
3268 } else
3269 countFrom = BufStartOfLine(buf, pos);
3270 } else
3271 countFrom = BufStartOfLine(buf, pos);
3274 ** Move forward through the (new) text one line at a time, counting
3275 ** displayed lines, and looking for either a real newline, or for the
3276 ** line starts to re-sync with the original line starts array
3278 lineStart = countFrom;
3279 while (True) {
3280 /* advance to the next line. If the line ended in a real newline
3281 or the end of the buffer, that's far enough */
3282 wrappedLineCounter(textD, buf, lineStart, buf->length, 1, True, 0,
3283 &retPos, &retLines, &retLineStart, &retLineEnd);
3284 if (retPos >= buf->length) {
3285 if (retPos != retLineEnd)
3286 nLines++;
3287 break;
3288 } else
3289 lineStart = retPos;
3290 nLines++;
3291 if (lineStart > pos + nDeleted &&
3292 BufGetCharacter(buf, lineStart-1) == '\n') {
3293 break;
3296 /* Unlike in the findWrapRange() function above, we don't try to
3297 resync with the line starts, because we don't know the length
3298 of the inserted text yet, nor the updated style information.
3300 Because of that, we also shouldn't resync with the line starts
3301 after the modification either, because we must perform the
3302 calculations for the deleted and inserted lines in the same way.
3304 This can result in some unnecessary recalculation and redrawing
3305 overhead, and therefore we should only use this two-phase mode
3306 of calculation when it's really needed (continuous wrap + variable
3307 font width). */
3309 textD->nLinesDeleted = nLines;
3310 textD->suppressResync = 1;
3314 ** Count forward from startPos to either maxPos or maxLines (whichever is
3315 ** reached first), and return all relevant positions and line count.
3316 ** The provided textBuffer may differ from the actual text buffer of the
3317 ** widget. In that case it must be a (partial) copy of the actual text buffer
3318 ** and the styleBufOffset argument must indicate the starting position of the
3319 ** copy, to take into account the correct style information.
3321 ** Returned values:
3323 ** retPos: Position where counting ended. When counting lines, the
3324 ** position returned is the start of the line "maxLines"
3325 ** lines beyond "startPos".
3326 ** retLines: Number of line breaks counted
3327 ** retLineStart: Start of the line where counting ended
3328 ** retLineEnd: End position of the last line traversed
3330 static void wrappedLineCounter(textDisp *textD, textBuffer *buf, int startPos,
3331 int maxPos, int maxLines, int startPosIsLineStart, int styleBufOffset,
3332 int *retPos, int *retLines, int *retLineStart, int *retLineEnd)
3334 int lineStart, newLineStart = 0, b, p, colNum, wrapMargin;
3335 int maxWidth, width, countPixels, i, foundBreak;
3336 int nLines = 0, tabDist = textD->buffer->tabDist;
3337 unsigned char c;
3338 char nullSubsChar = textD->buffer->nullSubsChar;
3340 /* If the font is fixed, or there's a wrap margin set, it's more efficient
3341 to measure in columns, than to count pixels. Determine if we can count
3342 in columns (countPixels == False) or must count pixels (countPixels ==
3343 True), and set the wrap target for either pixels or columns */
3344 if (textD->fixedFontWidth != -1 || textD->wrapMargin != 0) {
3345 countPixels = False;
3346 wrapMargin = textD->wrapMargin != 0 ? textD->wrapMargin :
3347 textD->width / textD->fixedFontWidth;
3348 maxWidth = INT_MAX;
3349 } else {
3350 countPixels = True;
3351 wrapMargin = INT_MAX;
3352 maxWidth = textD->width;
3355 /* Find the start of the line if the start pos is not marked as a
3356 line start. */
3357 if (startPosIsLineStart)
3358 lineStart = startPos;
3359 else
3360 lineStart = TextDStartOfLine(textD, startPos);
3363 ** Loop until position exceeds maxPos or line count exceeds maxLines.
3364 ** (actually, contines beyond maxPos to end of line containing maxPos,
3365 ** in case later characters cause a word wrap back before maxPos)
3367 colNum = 0;
3368 width = 0;
3369 for (p=lineStart; p<buf->length; p++) {
3370 c = BufGetCharacter(buf, p);
3372 /* If the character was a newline, count the line and start over,
3373 otherwise, add it to the width and column counts */
3374 if (c == '\n') {
3375 if (p >= maxPos) {
3376 *retPos = maxPos;
3377 *retLines = nLines;
3378 *retLineStart = lineStart;
3379 *retLineEnd = maxPos;
3380 return;
3382 nLines++;
3383 if (nLines >= maxLines) {
3384 *retPos = p + 1;
3385 *retLines = nLines;
3386 *retLineStart = p + 1;
3387 *retLineEnd = p;
3388 return;
3390 lineStart = p + 1;
3391 colNum = 0;
3392 width = 0;
3393 } else {
3394 colNum += BufCharWidth(c, colNum, tabDist, nullSubsChar);
3395 if (countPixels)
3396 width += measurePropChar(textD, c, colNum, p+styleBufOffset);
3399 /* If character exceeded wrap margin, find the break point
3400 and wrap there */
3401 if (colNum > wrapMargin || width > maxWidth) {
3402 foundBreak = False;
3403 for (b=p; b>=lineStart; b--) {
3404 c = BufGetCharacter(buf, b);
3405 if (c == '\t' || c == ' ') {
3406 newLineStart = b + 1;
3407 if (countPixels) {
3408 colNum = 0;
3409 width = 0;
3410 for (i=b+1; i<p+1; i++) {
3411 width += measurePropChar(textD,
3412 BufGetCharacter(buf, i), colNum,
3413 i+styleBufOffset);
3414 colNum++;
3416 } else
3417 colNum = BufCountDispChars(buf, b+1, p+1);
3418 foundBreak = True;
3419 break;
3422 if (!foundBreak) { /* no whitespace, just break at margin */
3423 newLineStart = max(p, lineStart+1);
3424 colNum = BufCharWidth(c, colNum, tabDist, nullSubsChar);
3425 if (countPixels)
3426 width = measurePropChar(textD, c, colNum, p+styleBufOffset);
3428 if (p >= maxPos) {
3429 *retPos = maxPos;
3430 *retLines = maxPos < newLineStart ? nLines : nLines + 1;
3431 *retLineStart = maxPos < newLineStart ? lineStart :
3432 newLineStart;
3433 *retLineEnd = maxPos;
3434 return;
3436 nLines++;
3437 if (nLines >= maxLines) {
3438 *retPos = foundBreak ? b + 1 : max(p, lineStart+1);
3439 *retLines = nLines;
3440 *retLineStart = lineStart;
3441 *retLineEnd = foundBreak ? b : p;
3442 return;
3444 lineStart = newLineStart;
3448 /* reached end of buffer before reaching pos or line target */
3449 *retPos = buf->length;
3450 *retLines = nLines;
3451 *retLineStart = lineStart;
3452 *retLineEnd = buf->length;
3456 ** Measure the width in pixels of a character "c" at a particular column
3457 ** "colNum" and buffer position "pos". This is for measuring characters in
3458 ** proportional or mixed-width highlighting fonts.
3460 ** A note about proportional and mixed-width fonts: the mixed width and
3461 ** proportional font code in nedit does not get much use in general editing,
3462 ** because nedit doesn't allow per-language-mode fonts, and editing programs
3463 ** in a proportional font is usually a bad idea, so very few users would
3464 ** choose a proportional font as a default. There are still probably mixed-
3465 ** width syntax highlighting cases where things don't redraw properly for
3466 ** insertion/deletion, though static display and wrapping and resizing
3467 ** should now be solid because they are now used for online help display.
3469 static int measurePropChar(textDisp *textD, char c, int colNum, int pos)
3471 int charLen, style;
3472 char expChar[MAX_EXP_CHAR_LEN];
3473 textBuffer *styleBuf = textD->styleBuffer;
3475 charLen = BufExpandCharacter(c, colNum, expChar,
3476 textD->buffer->tabDist, textD->buffer->nullSubsChar);
3477 if (styleBuf == NULL) {
3478 style = 0;
3479 } else {
3480 style = (unsigned char)BufGetCharacter(styleBuf, pos);
3481 if (style == textD->unfinishedStyle) {
3482 /* encountered "unfinished" style, trigger parsing */
3483 (textD->unfinishedHighlightCB)(textD, pos, textD->highlightCBArg);
3484 style = (unsigned char)BufGetCharacter(styleBuf, pos);
3487 return stringWidth(textD, expChar, charLen, style);
3491 ** Finds both the end of the current line and the start of the next line. Why?
3492 ** In continuous wrap mode, if you need to know both, figuring out one from the
3493 ** other can be expensive or error prone. The problem comes when there's a
3494 ** trailing space or tab just before the end of the buffer. To translate an
3495 ** end of line value to or from the next lines start value, you need to know
3496 ** whether the trailing space or tab is being used as a line break or just a
3497 ** normal character, and to find that out would otherwise require counting all
3498 ** the way back to the beginning of the line.
3500 static void findLineEnd(textDisp *textD, int startPos, int startPosIsLineStart,
3501 int *lineEnd, int *nextLineStart)
3503 int retLines, retLineStart;
3505 /* if we're not wrapping use more efficient BufEndOfLine */
3506 if (!textD->continuousWrap) {
3507 *lineEnd = BufEndOfLine(textD->buffer, startPos);
3508 *nextLineStart = min(textD->buffer->length, *lineEnd + 1);
3509 return;
3512 /* use the wrapped line counter routine to count forward one line */
3513 wrappedLineCounter(textD, textD->buffer, startPos, textD->buffer->length,
3514 1, startPosIsLineStart, 0, nextLineStart, &retLines,
3515 &retLineStart, lineEnd);
3516 return;
3520 ** Line breaks in continuous wrap mode usually happen at newlines or
3521 ** whitespace. This line-terminating character is not included in line
3522 ** width measurements and has a special status as a non-visible character.
3523 ** However, lines with no whitespace are wrapped without the benefit of a
3524 ** line terminating character, and this distinction causes endless trouble
3525 ** with all of the text display code which was originally written without
3526 ** continuous wrap mode and always expects to wrap at a newline character.
3528 ** Given the position of the end of the line, as returned by TextDEndOfLine
3529 ** or BufEndOfLine, this returns true if there is a line terminating
3530 ** character, and false if there's not. On the last character in the
3531 ** buffer, this function can't tell for certain whether a trailing space was
3532 ** used as a wrap point, and just guesses that it wasn't. So if an exact
3533 ** accounting is necessary, don't use this function.
3535 static int wrapUsesCharacter(textDisp *textD, int lineEndPos)
3537 char c;
3539 if (!textD->continuousWrap || lineEndPos == textD->buffer->length)
3540 return True;
3542 c = BufGetCharacter(textD->buffer, lineEndPos);
3543 return c == '\n' || ((c == '\t' || c == ' ') &&
3544 lineEndPos + 1 != textD->buffer->length);
3548 ** Decide whether the user needs (or may need) a horizontal scroll bar,
3549 ** and manage or unmanage the scroll bar widget accordingly. The H.
3550 ** scroll bar is only hidden in continuous wrap mode when it's absolutely
3551 ** certain that the user will not need it: when wrapping is set
3552 ** to the window edge, or when the wrap margin is strictly less than
3553 ** the longest possible line.
3555 static void hideOrShowHScrollBar(textDisp *textD)
3557 if (textD->continuousWrap && (textD->wrapMargin == 0 || textD->wrapMargin *
3558 textD->fontStruct->max_bounds.width < textD->width))
3559 XtUnmanageChild(textD->hScrollBar);
3560 else
3561 XtManageChild(textD->hScrollBar);
3565 ** Return true if the selection "sel" is rectangular, and touches a
3566 ** buffer position withing "rangeStart" to "rangeEnd"
3568 static int rangeTouchesRectSel(selection *sel, int rangeStart, int rangeEnd)
3570 return sel->selected && sel->rectangular && sel->end >= rangeStart &&
3571 sel->start <= rangeEnd;
3575 ** Extend the range of a redraw request (from *start to *end) with additional
3576 ** redraw requests resulting from changes to the attached style buffer (which
3577 ** contains auxiliary information for coloring or styling text).
3579 static void extendRangeForStyleMods(textDisp *textD, int *start, int *end)
3581 selection *sel = &textD->styleBuffer->primary;
3582 int extended = False;
3584 /* The peculiar protocol used here is that modifications to the style
3585 buffer are marked by selecting them with the buffer's primary selection.
3586 The style buffer is usually modified in response to a modify callback on
3587 the text buffer BEFORE textDisp.c's modify callback, so that it can keep
3588 the style buffer in step with the text buffer. The style-update
3589 callback can't just call for a redraw, because textDisp hasn't processed
3590 the original text changes yet. Anyhow, to minimize redrawing and to
3591 avoid the complexity of scheduling redraws later, this simple protocol
3592 tells the text display's buffer modify callback to extend it's redraw
3593 range to show the text color/and font changes as well. */
3594 if (sel->selected) {
3595 if (sel->start < *start) {
3596 *start = sel->start;
3597 extended = True;
3599 if (sel->end > *end) {
3600 *end = sel->end;
3601 extended = True;
3605 /* If the selection was extended due to a style change, and some of the
3606 fonts don't match in spacing, extend redraw area to end of line to
3607 redraw characters exposed by possible font size changes */
3608 if (textD->fixedFontWidth == -1 && extended)
3609 *end = BufEndOfLine(textD->buffer, *end) + 1;
3612 /********************** Backlight Functions ******************************/
3614 ** Allocate a read-only (shareable) colormap cell for a named color, from the
3615 ** the default colormap of the screen on which the widget (w) is displayed. If
3616 ** the colormap is full and there's no suitable substitute, print an error on
3617 ** stderr, and return the widget's background color as a backup.
3619 static Pixel allocBGColor(Widget w, char *colorName, int *ok)
3621 int r,g,b;
3622 *ok = 1;
3623 return AllocColor(w, colorName, &r, &g, &b);
3626 Pixel getRangesetColor(textDisp *textD, int ind, Pixel bground)
3628 textBuffer *buf;
3629 RangesetTable *tab;
3630 Pixel color;
3631 char *color_name;
3632 int valid;
3634 if (ind > 0) {
3635 ind--;
3636 buf = textD->buffer;
3637 tab = buf->rangesetTable;
3639 valid = RangesetTableGetColorValid(tab, ind, &color);
3640 if (valid == 0) {
3641 color_name = RangesetTableGetColorName(tab, ind);
3642 if (color_name)
3643 color = allocBGColor(textD->w, color_name, &valid);
3644 RangesetTableAssignColorPixel(tab, ind, color, valid);
3646 if (valid > 0) {
3647 return color;
3650 return bground;
3654 ** Read the background color class specification string in str, allocating the
3655 ** necessary colors, and allocating and setting up the character->class_no and
3656 ** class_no->pixel map arrays, returned via *pp_bgClass and *pp_bgClassPixel
3657 ** respectively.
3658 ** Note: the allocation of class numbers could be more intelligent: there can
3659 ** never be more than 256 of these (one per character); but I don't think
3660 ** there'll be a pressing need. I suppose the scanning of the specification
3661 ** could be better too, but then, who cares!
3663 void TextDSetupBGClasses(Widget w, XmString str, Pixel **pp_bgClassPixel,
3664 unsigned char **pp_bgClass, Pixel bgPixelDefault)
3666 unsigned char bgClass[256];
3667 Pixel bgClassPixel[256];
3668 int class_no = 0;
3669 char *semicol;
3670 char *s = (char *)str;
3671 size_t was_semicol;
3672 int lo, hi, dummy;
3673 char *pos;
3674 Boolean is_good = True;
3676 XtFree((char *)*pp_bgClass);
3677 XtFree((char *)*pp_bgClassPixel);
3679 *pp_bgClassPixel = NULL;
3680 *pp_bgClass = NULL;
3682 if (!s)
3683 return;
3685 /* default for all chars is class number zero, for standard background */
3686 memset(bgClassPixel, 0, sizeof bgClassPixel);
3687 memset(bgClass, 0, sizeof bgClass);
3688 bgClassPixel[0] = bgPixelDefault;
3689 /* since class no == 0 in a "style" has no set bits in BACKLIGHT_MASK
3690 (see styleOfPos()), when drawString() is called for text with a
3691 backlight class no of zero, bgClassPixel[0] is never consulted, and
3692 the default background color is chosen. */
3694 /* The format of the class string s is:
3695 low[-high]{,low[-high]}:color{;low-high{,low[-high]}:color}
3697 32-255:#f0f0f0;1-31,127:red;128-159:orange;9-13:#e5e5e5
3698 where low and high represent a character range between ordinal
3699 ASCII values. Using strtol() allows automatic octal, dec and hex
3700 reading of low and high. The example format sets backgrounds as follows:
3701 char 1 - 8 colored red (control characters)
3702 char 9 - 13 colored #e5e5e5 (isspace() control characters)
3703 char 14 - 31 colored red (control characters)
3704 char 32 - 126 colored #f0f0f0
3705 char 127 colored red (delete character)
3706 char 128 - 159 colored orange ("shifted" control characters)
3707 char 160 - 255 colored #f0f0f0
3708 Notice that some of the later ranges overwrite the class values defined
3709 for earlier ones (eg the first clause, 32-255:#f0f0f0 sets the DEL
3710 character background color to #f0f0f0; it is then set to red by the
3711 clause 1-31,127:red). */
3713 while (s && class_no < 255) {
3714 class_no++; /* simple class alloc scheme */
3715 was_semicol = 0;
3716 is_good = True;
3717 if ((semicol = (char *)strchr(s, ';'))) {
3718 *semicol = '\0'; /* null-terminate low[-high]:color clause */
3719 was_semicol = 1;
3722 /* loop over ranges before the color spec, assigning the characters
3723 in the ranges to the current class number */
3724 for (lo = hi = strtol(s, &pos, 0);
3725 is_good;
3726 lo = hi = strtol(pos + 1, &pos, 0)) {
3727 if (pos && *pos == '-')
3728 hi = strtol(pos + 1, &pos, 0); /* get end of range */
3729 is_good = (pos && 0 <= lo && lo <= hi && hi <= 255);
3730 if (is_good)
3731 while (lo <= hi)
3732 bgClass[lo++] = (unsigned char)class_no;
3733 if (*pos != ',')
3734 break;
3736 if ((is_good = (is_good && *pos == ':'))) {
3737 is_good = (*pos++ != '\0'); /* pos now points to color */
3738 bgClassPixel[class_no] = allocBGColor(w, pos, &dummy);
3740 if (!is_good) {
3741 /* complain? this class spec clause (in string s) was faulty */
3744 /* end of loop iterator clauses */
3745 if (was_semicol)
3746 *semicol = ';'; /* un-null-terminate low[-high]:color clause */
3747 s = semicol + was_semicol;
3749 /* when we get here, we've set up our class table and class-to-pixel table
3750 in local variables: now put them into the "real thing" */
3751 if (class_no) {
3752 class_no++; /* bigger than all valid class_nos */
3753 *pp_bgClass = (unsigned char *)XtMalloc(256);
3754 *pp_bgClassPixel = (Pixel *)XtMalloc(class_no * sizeof (Pixel));
3755 if (!*pp_bgClass || !*pp_bgClassPixel) {
3756 XtFree((char *)*pp_bgClass);
3757 XtFree((char *)*pp_bgClassPixel);
3758 return;
3760 memcpy(*pp_bgClass, bgClass, 256);
3761 memcpy(*pp_bgClassPixel, bgClassPixel, class_no * sizeof (Pixel));