Merged 5.4 release branch with main trunk.
[nedit.git] / source / textDisp.c
blob6aec42efadee30d896fa1a630c359c82848f6c4b
1 static const char CVSID[] = "$Id: textDisp.c,v 1.54 2003/11/22 13:03:40 edg 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 /* Redisplay */
507 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
508 textD->height);
510 /* Clean up line number area in case spacing has changed */
511 redrawLineNumbers(textD, True);
514 int TextDMinFontWidth(textDisp *textD, Boolean considerStyles)
516 int fontWidth = textD->fontStruct->max_bounds.width;
517 int i;
519 if (considerStyles) {
520 for (i = 0; i < textD->nStyles; ++i) {
521 int thisWidth = (textD->styleTable[i].font)->min_bounds.width;
522 if (thisWidth < fontWidth) {
523 fontWidth = thisWidth;
527 return(fontWidth);
530 int TextDMaxFontWidth(textDisp *textD, Boolean considerStyles)
532 int fontWidth = textD->fontStruct->max_bounds.width;
533 int i;
535 if (considerStyles) {
536 for (i = 0; i < textD->nStyles; ++i) {
537 int thisWidth = (textD->styleTable[i].font)->max_bounds.width;
538 if (thisWidth > fontWidth) {
539 fontWidth = thisWidth;
543 return(fontWidth);
547 ** Change the size of the displayed text area
549 void TextDResize(textDisp *textD, int width, int height)
551 int oldVisibleLines = textD->nVisibleLines;
552 int canRedraw = XtWindow(textD->w) != 0;
553 int newVisibleLines = height / (textD->ascent + textD->descent);
554 int redrawAll = False;
555 int oldWidth = textD->width;
556 int exactHeight = height - height % (textD->ascent + textD->descent);
558 textD->width = width;
559 textD->height = height;
561 /* In continuous wrap mode, a change in width affects the total number of
562 lines in the buffer, and can leave the top line number incorrect, and
563 the top character no longer pointing at a valid line start */
564 if (textD->continuousWrap && textD->wrapMargin==0 && width!=oldWidth) {
565 int oldFirstChar = textD->firstChar;
566 textD->nBufferLines = TextDCountLines(textD, 0, textD->buffer->length,
567 True);
568 textD->firstChar = TextDStartOfLine(textD, textD->firstChar);
569 textD->topLineNum = TextDCountLines(textD, 0, textD->firstChar, True)+1;
570 redrawAll = True;
571 offsetAbsLineNum(textD, oldFirstChar);
574 /* reallocate and update the line starts array, which may have changed
575 size and/or contents. (contents can change in continuous wrap mode
576 when the width changes, even without a change in height) */
577 if (oldVisibleLines < newVisibleLines) {
578 XtFree((char *)textD->lineStarts);
579 textD->lineStarts = (int *)XtMalloc(sizeof(int) * newVisibleLines);
581 textD->nVisibleLines = newVisibleLines;
582 calcLineStarts(textD, 0, newVisibleLines);
583 calcLastChar(textD);
585 /* if the window became shorter, there may be partially drawn
586 text left at the bottom edge, which must be cleaned up */
587 if (canRedraw && oldVisibleLines>newVisibleLines && exactHeight!=height)
588 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->left,
589 textD->top + exactHeight, textD->width,
590 height - exactHeight, False);
592 /* if the window became taller, there may be an opportunity to display
593 more text by scrolling down */
594 if (canRedraw && oldVisibleLines < newVisibleLines && textD->topLineNum +
595 textD->nVisibleLines > textD->nBufferLines)
596 setScroll(textD, max(1, textD->nBufferLines - textD->nVisibleLines +
597 2 + TEXT_OF_TEXTD(textD).cursorVPadding),
598 textD->horizOffset, False, False);
600 /* Update the scroll bar page increment size (as well as other scroll
601 bar parameters. If updating the horizontal range caused scrolling,
602 redraw */
603 updateVScrollBarRange(textD);
604 if (updateHScrollBarRange(textD))
605 redrawAll = True;
607 /* If a full redraw is needed */
608 if (redrawAll && canRedraw)
609 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
610 textD->height);
612 /* Decide if the horizontal scroll bar needs to be visible */
613 hideOrShowHScrollBar(textD);
615 /* Refresh the line number display to draw more line numbers, or
616 erase extras */
617 redrawLineNumbers(textD, True);
619 /* Redraw the calltip */
620 TextDRedrawCalltip(textD, 0);
624 ** Refresh a rectangle of the text display. left and top are in coordinates of
625 ** the text drawing window
627 void TextDRedisplayRect(textDisp *textD, int left, int top, int width,
628 int height)
630 int fontHeight, firstLine, lastLine, line;
632 /* find the line number range of the display */
633 fontHeight = textD->ascent + textD->descent;
634 firstLine = (top - textD->top - fontHeight + 1) / fontHeight;
635 lastLine = (top + height - textD->top) / fontHeight;
637 /* If the graphics contexts are shared using XtAllocateGC, their
638 clipping rectangles may have changed since the last use */
639 resetClipRectangles(textD);
641 /* draw the lines of text */
642 for (line=firstLine; line<=lastLine; line++)
643 redisplayLine(textD, line, left, left+width, 0, INT_MAX);
645 /* draw the line numbers if exposed area includes them */
646 if (textD->lineNumWidth != 0 && left <= textD->lineNumLeft + textD->lineNumWidth)
647 redrawLineNumbers(textD, False);
651 ** Refresh all of the text between buffer positions "start" and "end"
652 ** not including the character at the position "end".
653 ** If end points beyond the end of the buffer, refresh the whole display
654 ** after pos, including blank lines which are not technically part of
655 ** any range of characters.
657 void TextDRedisplayRange(textDisp *textD, int start, int end)
659 int i, startLine, lastLine, startIndex, endIndex;
661 /* If the range is outside of the displayed text, just return */
662 if (end < textD->firstChar || (start > textD->lastChar &&
663 !emptyLinesVisible(textD)))
664 return;
666 /* Clean up the starting and ending values */
667 if (start < 0) start = 0;
668 if (start > textD->buffer->length) start = textD->buffer->length;
669 if (end < 0) end = 0;
670 if (end > textD->buffer->length) end = textD->buffer->length;
672 /* Get the starting and ending lines */
673 if (start < textD->firstChar)
674 start = textD->firstChar;
675 if (!posToVisibleLineNum(textD, start, &startLine))
676 startLine = textD->nVisibleLines - 1;
677 if (end >= textD->lastChar) {
678 lastLine = textD->nVisibleLines - 1;
679 } else {
680 if (!posToVisibleLineNum(textD, end, &lastLine)) {
681 /* shouldn't happen */
682 lastLine = textD->nVisibleLines - 1;
686 /* Get the starting and ending positions within the lines */
687 startIndex = textD->lineStarts[startLine] == -1 ? 0 :
688 start - textD->lineStarts[startLine];
689 if (end >= textD->lastChar)
690 endIndex = INT_MAX;
691 else if (textD->lineStarts[lastLine] == -1)
692 endIndex = 0;
693 else
694 endIndex = end - textD->lineStarts[lastLine];
696 /* Reset the clipping rectangles for the drawing GCs which are shared
697 using XtAllocateGC, and may have changed since the last use */
698 resetClipRectangles(textD);
700 /* If the starting and ending lines are the same, redisplay the single
701 line between "start" and "end" */
702 if (startLine == lastLine) {
703 redisplayLine(textD, startLine, 0, INT_MAX, startIndex, endIndex);
704 return;
707 /* Redisplay the first line from "start" */
708 redisplayLine(textD, startLine, 0, INT_MAX, startIndex, INT_MAX);
710 /* Redisplay the lines in between at their full width */
711 for (i=startLine+1; i<lastLine; i++)
712 redisplayLine(textD, i, 0, INT_MAX, 0, INT_MAX);
714 /* Redisplay the last line to "end" */
715 redisplayLine(textD, lastLine, 0, INT_MAX, 0, endIndex);
719 ** Set the scroll position of the text display vertically by line number and
720 ** horizontally by pixel offset from the left margin
722 void TextDSetScroll(textDisp *textD, int topLineNum, int horizOffset)
724 int sliderSize, sliderMax;
725 int vPadding = (int)(TEXT_OF_TEXTD(textD).cursorVPadding);
727 /* Limit the requested scroll position to allowable values */
728 if (topLineNum < 1)
729 topLineNum = 1;
730 else if ((topLineNum > textD->topLineNum) &&
731 (topLineNum > (textD->nBufferLines + 2 - textD->nVisibleLines +
732 vPadding)))
733 topLineNum = max(textD->topLineNum,
734 textD->nBufferLines + 2 - textD->nVisibleLines + vPadding);
735 XtVaGetValues(textD->hScrollBar, XmNmaximum, &sliderMax,
736 XmNsliderSize, &sliderSize, NULL);
737 if (horizOffset < 0)
738 horizOffset = 0;
739 if (horizOffset > sliderMax - sliderSize)
740 horizOffset = sliderMax - sliderSize;
742 setScroll(textD, topLineNum, horizOffset, True, True);
746 ** Get the current scroll position for the text display, in terms of line
747 ** number of the top line and horizontal pixel offset from the left margin
749 void TextDGetScroll(textDisp *textD, int *topLineNum, int *horizOffset)
751 *topLineNum = textD->topLineNum;
752 *horizOffset = textD->horizOffset;
756 ** Set the position of the text insertion cursor for text display "textD"
758 void TextDSetInsertPosition(textDisp *textD, int newPos)
760 /* make sure new position is ok, do nothing if it hasn't changed */
761 if (newPos == textD->cursorPos)
762 return;
763 if (newPos < 0) newPos = 0;
764 if (newPos > textD->buffer->length) newPos = textD->buffer->length;
766 /* cursor movement cancels vertical cursor motion column */
767 textD->cursorPreferredCol = -1;
769 /* erase the cursor at it's previous position */
770 TextDBlankCursor(textD);
772 /* draw it at its new position */
773 textD->cursorPos = newPos;
774 textD->cursorOn = True;
775 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos + 1);
778 void TextDBlankCursor(textDisp *textD)
780 if (!textD->cursorOn)
781 return;
783 blankCursorProtrusions(textD);
784 textD->cursorOn = False;
785 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
788 void TextDUnblankCursor(textDisp *textD)
790 if (!textD->cursorOn) {
791 textD->cursorOn = True;
792 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
796 void TextDSetCursorStyle(textDisp *textD, int style)
798 textD->cursorStyle = style;
799 blankCursorProtrusions(textD);
800 if (textD->cursorOn)
801 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos + 1);
804 void TextDSetWrapMode(textDisp *textD, int wrap, int wrapMargin)
806 textD->wrapMargin = wrapMargin;
807 textD->continuousWrap = wrap;
809 /* wrapping can change change the total number of lines, re-count */
810 textD->nBufferLines = TextDCountLines(textD, 0, textD->buffer->length,True);
812 /* changing wrap margins wrap or changing from wrapped mode to non-wrapped
813 can leave the character at the top no longer at a line start, and/or
814 change the line number */
815 textD->firstChar = TextDStartOfLine(textD, textD->firstChar);
816 textD->topLineNum = TextDCountLines(textD, 0, textD->firstChar, True) + 1;
817 resetAbsLineNum(textD);
819 /* update the line starts array */
820 calcLineStarts(textD, 0, textD->nVisibleLines);
821 calcLastChar(textD);
823 /* Update the scroll bar page increment size (as well as other scroll
824 bar parameters) */
825 updateVScrollBarRange(textD);
826 updateHScrollBarRange(textD);
828 /* Decide if the horizontal scroll bar needs to be visible */
829 hideOrShowHScrollBar(textD);
831 /* Do a full redraw */
832 TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left,
833 textD->height);
836 int TextDGetInsertPosition(textDisp *textD)
838 return textD->cursorPos;
842 ** Insert "text" at the current cursor location. This has the same
843 ** effect as inserting the text into the buffer using BufInsert and
844 ** then moving the insert position after the newly inserted text, except
845 ** that it's optimized to do less redrawing.
847 void TextDInsert(textDisp *textD, char *text)
849 int pos = textD->cursorPos;
851 textD->cursorToHint = pos + strlen(text);
852 BufInsert(textD->buffer, pos, text);
853 textD->cursorToHint = NO_HINT;
857 ** Insert "text" (which must not contain newlines), overstriking the current
858 ** cursor location.
860 void TextDOverstrike(textDisp *textD, char *text)
862 int startPos = textD->cursorPos;
863 textBuffer *buf = textD->buffer;
864 int lineStart = BufStartOfLine(buf, startPos);
865 int textLen = strlen(text);
866 int i, p, endPos, indent, startIndent, endIndent;
867 char *c, ch, *paddedText = NULL;
869 /* determine how many displayed character positions are covered */
870 startIndent = BufCountDispChars(textD->buffer, lineStart, startPos);
871 indent = startIndent;
872 for (c=text; *c!='\0'; c++)
873 indent += BufCharWidth(*c, indent, buf->tabDist, buf->nullSubsChar);
874 endIndent = indent;
876 /* find which characters to remove, and if necessary generate additional
877 padding to make up for removed control characters at the end */
878 indent=startIndent;
879 for (p=startPos; ; p++) {
880 if (p == buf->length)
881 break;
882 ch = BufGetCharacter(buf, p);
883 if (ch == '\n')
884 break;
885 indent += BufCharWidth(ch, indent, buf->tabDist, buf->nullSubsChar);
886 if (indent == endIndent) {
887 p++;
888 break;
889 } else if (indent > endIndent) {
890 if (ch != '\t') {
891 p++;
892 paddedText = XtMalloc(textLen + MAX_EXP_CHAR_LEN + 1);
893 strcpy(paddedText, text);
894 for (i=0; i<indent-endIndent; i++)
895 paddedText[textLen+i] = ' ';
896 paddedText[textLen+i] = '\0';
898 break;
901 endPos = p;
903 textD->cursorToHint = startPos + textLen;
904 BufReplace(buf, startPos, endPos, paddedText == NULL ? text : paddedText);
905 textD->cursorToHint = NO_HINT;
906 if (paddedText != NULL)
907 XtFree(paddedText);
911 ** Translate window coordinates to the nearest text cursor position.
913 int TextDXYToPosition(textDisp *textD, int x, int y)
915 return xyToPos(textD, x, y, CURSOR_POS);
919 ** Translate window coordinates to the nearest character cell.
921 int TextDXYToCharPos(textDisp *textD, int x, int y)
923 return xyToPos(textD, x, y, CHARACTER_POS);
927 ** Translate window coordinates to the nearest row and column number for
928 ** positioning the cursor. This, of course, makes no sense when the font
929 ** is proportional, since there are no absolute columns.
931 void TextDXYToUnconstrainedPosition(textDisp *textD, int x, int y, int *row,
932 int *column)
934 xyToUnconstrainedPos(textD, x, y, row, column, CURSOR_POS);
938 ** Translate line and column to the nearest row and column number for
939 ** positioning the cursor. This, of course, makes no sense when the font
940 ** is proportional, since there are no absolute columns.
942 int TextDLineAndColToPos(textDisp *textD, int lineNum, int column)
944 int i, lineEnd, charIndex, outIndex;
945 int lineStart=0, charLen=0;
946 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
948 /* Count lines */
949 if (lineNum < 1)
950 lineNum = 1;
951 lineEnd = -1;
952 for (i=1; i<=lineNum && lineEnd<textD->buffer->length; i++) {
953 lineStart = lineEnd + 1;
954 lineEnd = BufEndOfLine(textD->buffer, lineStart);
957 /* If line is beyond end of buffer, position at last character in buffer */
958 if ( lineNum >= i ) {
959 return lineEnd;
962 /* Start character index at zero */
963 charIndex=0;
965 /* Only have to count columns if column isn't zero (or negative) */
966 if (column > 0) {
967 /* Count columns, expanding each character */
968 lineStr = BufGetRange(textD->buffer, lineStart, lineEnd);
969 outIndex = 0;
970 for(i=lineStart; i<lineEnd; i++, charIndex++) {
971 charLen = BufExpandCharacter(lineStr[charIndex], outIndex,
972 expandedChar, textD->buffer->tabDist,
973 textD->buffer->nullSubsChar);
974 if ( outIndex+charLen >= column ) break;
975 outIndex+=charLen;
978 /* If the column is in the middle of an expanded character, put cursor
979 * in front of character if in first half of character, and behind
980 * character if in last half of character
982 if (column >= outIndex + ( charLen / 2 ))
983 charIndex++;
985 /* If we are beyond the end of the line, back up one space */
986 if ((i>=lineEnd)&&(charIndex>0)) charIndex--;
989 /* Position is the start of the line plus the index into line buffer */
990 return lineStart + charIndex;
994 ** Translate a buffer text position to the XY location where the center
995 ** of the cursor would be positioned to point to that character. Returns
996 ** False if the position is not displayed because it is VERTICALLY out
997 ** of view. If the position is horizontally out of view, returns the
998 ** x coordinate where the position would be if it were visible.
1000 int TextDPositionToXY(textDisp *textD, int pos, int *x, int *y)
1002 int charIndex, lineStartPos, fontHeight, lineLen;
1003 int visLineNum, charLen, outIndex, xStep, charStyle;
1004 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
1006 /* If position is not displayed, return false */
1007 if (pos < textD->firstChar ||
1008 (pos > textD->lastChar && !emptyLinesVisible(textD)))
1009 return False;
1011 /* Calculate y coordinate */
1012 if (!posToVisibleLineNum(textD, pos, &visLineNum))
1013 return False;
1014 fontHeight = textD->ascent + textD->descent;
1015 *y = textD->top + visLineNum*fontHeight + fontHeight/2;
1017 /* Get the text, length, and buffer position of the line. If the position
1018 is beyond the end of the buffer and should be at the first position on
1019 the first empty line, don't try to get or scan the text */
1020 lineStartPos = textD->lineStarts[visLineNum];
1021 if (lineStartPos == -1) {
1022 *x = textD->left - textD->horizOffset;
1023 return True;
1025 lineLen = visLineLength(textD, visLineNum);
1026 lineStr = BufGetRange(textD->buffer, lineStartPos, lineStartPos + lineLen);
1028 /* Step through character positions from the beginning of the line
1029 to "pos" to calculate the x coordinate */
1030 xStep = textD->left - textD->horizOffset;
1031 outIndex = 0;
1032 for(charIndex=0; charIndex<pos-lineStartPos; charIndex++) {
1033 charLen = BufExpandCharacter(lineStr[charIndex], outIndex, expandedChar,
1034 textD->buffer->tabDist, textD->buffer->nullSubsChar);
1035 charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1036 outIndex, lineStr[charIndex]);
1037 xStep += stringWidth(textD, expandedChar, charLen, charStyle);
1038 outIndex += charLen;
1040 *x = xStep;
1041 XtFree(lineStr);
1042 return True;
1046 ** If the text widget is maintaining a line number count appropriate to "pos"
1047 ** return the line and column numbers of pos, otherwise return False. If
1048 ** continuous wrap mode is on, returns the absolute line number (as opposed to
1049 ** the wrapped line number which is used for scrolling). THIS ROUTINE ONLY
1050 ** WORKS FOR DISPLAYED LINES AND, IN CONTINUOUS WRAP MODE, ONLY WHEN THE
1051 ** ABSOLUTE LINE NUMBER IS BEING MAINTAINED. Otherwise, it returns False.
1053 int TextDPosToLineAndCol(textDisp *textD, int pos, int *lineNum, int *column)
1055 textBuffer *buf = textD->buffer;
1057 /* In continuous wrap mode, the absolute (non-wrapped) line count is
1058 maintained separately, as needed. Only return it if we're actually
1059 keeping track of it and pos is in the displayed text */
1060 if (textD->continuousWrap) {
1061 if (!maintainingAbsTopLineNum(textD) || pos < textD->firstChar ||
1062 pos > textD->lastChar)
1063 return False;
1064 *lineNum = textD->absTopLineNum + BufCountLines(buf,
1065 textD->firstChar, pos);
1066 *column = BufCountDispChars(buf, BufStartOfLine(buf, pos), pos);
1067 return True;
1070 /* Only return the data if pos is within the displayed text */
1071 if (!posToVisibleLineNum(textD, pos, lineNum))
1072 return False;
1073 *column = BufCountDispChars(buf, textD->lineStarts[*lineNum], pos);
1074 *lineNum += textD->topLineNum;
1075 return True;
1079 ** Return True if position (x, y) is inside of the primary selection
1081 int TextDInSelection(textDisp *textD, int x, int y)
1083 int row, column, pos = xyToPos(textD, x, y, CHARACTER_POS);
1084 textBuffer *buf = textD->buffer;
1086 xyToUnconstrainedPos(textD, x, y, &row, &column, CHARACTER_POS);
1087 if (rangeTouchesRectSel(&buf->primary, textD->firstChar, textD->lastChar))
1088 column = TextDOffsetWrappedColumn(textD, row, column);
1089 return inSelection(&buf->primary, pos, BufStartOfLine(buf, pos), column);
1093 ** Correct a column number based on an unconstrained position (as returned by
1094 ** TextDXYToUnconstrainedPosition) to be relative to the last actual newline
1095 ** in the buffer before the row and column position given, rather than the
1096 ** last line start created by line wrapping. This is an adapter
1097 ** for rectangular selections and code written before continuous wrap mode,
1098 ** which thinks that the unconstrained column is the number of characters
1099 ** from the last newline. Obviously this is time consuming, because it
1100 ** invloves character re-counting.
1102 int TextDOffsetWrappedColumn(textDisp *textD, int row, int column)
1104 int lineStart, dispLineStart;
1106 if (!textD->continuousWrap || row < 0 || row > textD->nVisibleLines)
1107 return column;
1108 dispLineStart = textD->lineStarts[row];
1109 if (dispLineStart == -1)
1110 return column;
1111 lineStart = BufStartOfLine(textD->buffer, dispLineStart);
1112 return column + BufCountDispChars(textD->buffer, lineStart, dispLineStart);
1116 ** Correct a row number from an unconstrained position (as returned by
1117 ** TextDXYToUnconstrainedPosition) to a straight number of newlines from the
1118 ** top line of the display. Because rectangular selections are based on
1119 ** newlines, rather than display wrapping, and anywhere a rectangular selection
1120 ** needs a row, it needs it in terms of un-wrapped lines.
1122 int TextDOffsetWrappedRow(textDisp *textD, int row)
1124 if (!textD->continuousWrap || row < 0 || row > textD->nVisibleLines)
1125 return row;
1126 return BufCountLines(textD->buffer, textD->firstChar,
1127 textD->lineStarts[row]);
1131 ** Scroll the display to bring insertion cursor into view.
1133 ** Note: it would be nice to be able to do this without counting lines twice
1134 ** (setScroll counts them too) and/or to count from the most efficient
1135 ** starting point, but the efficiency of this routine is not as important to
1136 ** the overall performance of the text display.
1138 void TextDMakeInsertPosVisible(textDisp *textD)
1140 int hOffset, topLine, x, y;
1141 int cursorPos = textD->cursorPos;
1142 int linesFromTop = 0, do_padding = 1;
1143 int cursorVPadding = (int)TEXT_OF_TEXTD(textD).cursorVPadding;
1145 hOffset = textD->horizOffset;
1146 topLine = textD->topLineNum;
1148 /* Don't do padding if this is a mouse operation */
1149 do_padding = ((TEXT_OF_TEXTD(textD).dragState == NOT_CLICKED) &&
1150 (cursorVPadding > 0));
1152 /* Find the new top line number */
1153 if (cursorPos < textD->firstChar) {
1154 topLine -= TextDCountLines(textD, cursorPos, textD->firstChar, False);
1155 /* linesFromTop = 0; */
1156 } else if (cursorPos > textD->lastChar && !emptyLinesVisible(textD)) {
1157 topLine += TextDCountLines(textD, textD->lastChar -
1158 (wrapUsesCharacter(textD, textD->lastChar) ? 0 : 1),
1159 cursorPos, False);
1160 linesFromTop = textD->nVisibleLines-1;
1161 } else if (cursorPos == textD->lastChar && !emptyLinesVisible(textD) &&
1162 !wrapUsesCharacter(textD, textD->lastChar)) {
1163 topLine++;
1164 linesFromTop = textD->nVisibleLines-1;
1165 } else {
1166 /* Avoid extra counting if cursorVPadding is disabled */
1167 if (do_padding)
1168 linesFromTop = TextDCountLines(textD, textD->firstChar,
1169 cursorPos, True);
1171 if (topLine < 1) {
1172 fprintf(stderr, "internal consistency check tl1 failed\n");
1173 topLine = 1;
1176 if (do_padding) {
1177 /* Keep the cursor away from the top or bottom of screen. */
1178 if (textD->nVisibleLines <= 2*(int)cursorVPadding) {
1179 topLine += (linesFromTop - textD->nVisibleLines/2);
1180 topLine = max(topLine, 1);
1181 } else if (linesFromTop < (int)cursorVPadding) {
1182 topLine -= (cursorVPadding - linesFromTop);
1183 topLine = max(topLine, 1);
1184 } else if (linesFromTop > textD->nVisibleLines-(int)cursorVPadding-1) {
1185 topLine += (linesFromTop - (textD->nVisibleLines-cursorVPadding-1));
1189 /* Find the new setting for horizontal offset (this is a bit ungraceful).
1190 If the line is visible, just use TextDPositionToXY to get the position
1191 to scroll to, otherwise, do the vertical scrolling first, then the
1192 horizontal */
1193 if (!TextDPositionToXY(textD, cursorPos, &x, &y)) {
1194 setScroll(textD, topLine, hOffset, True, True);
1195 if (!TextDPositionToXY(textD, cursorPos, &x, &y))
1196 return; /* Give up, it's not worth it (but why does it fail?) */
1198 if (x > textD->left + textD->width)
1199 hOffset += x - (textD->left + textD->width);
1200 else if (x < textD->left)
1201 hOffset += x - textD->left;
1203 /* Do the scroll */
1204 setScroll(textD, topLine, hOffset, True, True);
1208 ** Return the current preferred column along with the current
1209 ** visible line index (-1 if not visible) and the lineStartPos
1210 ** of the current insert position.
1212 int TextDPreferredColumn(textDisp *textD, int *visLineNum, int *lineStartPos)
1214 int column;
1216 /* Find the position of the start of the line. Use the line starts array
1217 if possible, to avoid unbounded line-counting in continuous wrap mode */
1218 if (posToVisibleLineNum(textD, textD->cursorPos, visLineNum)) {
1219 *lineStartPos = textD->lineStarts[*visLineNum];
1221 else {
1222 *lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1223 *visLineNum = -1;
1226 /* Decide what column to move to, if there's a preferred column use that */
1227 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1228 BufCountDispChars(textD->buffer, *lineStartPos, textD->cursorPos);
1229 return(column);
1233 ** Return the insert position of the requested column given
1234 ** the lineStartPos.
1236 int TextDPosOfPreferredCol(textDisp *textD, int column, int lineStartPos)
1238 int newPos;
1240 newPos = BufCountForwardDispChars(textD->buffer, lineStartPos, column);
1241 if (textD->continuousWrap) {
1242 newPos = min(newPos, TextDEndOfLine(textD, lineStartPos, True));
1244 return(newPos);
1248 ** Cursor movement functions
1250 int TextDMoveRight(textDisp *textD)
1252 if (textD->cursorPos >= textD->buffer->length)
1253 return False;
1254 TextDSetInsertPosition(textD, textD->cursorPos + 1);
1255 return True;
1258 int TextDMoveLeft(textDisp *textD)
1260 if (textD->cursorPos <= 0)
1261 return False;
1262 TextDSetInsertPosition(textD, textD->cursorPos - 1);
1263 return True;
1266 int TextDMoveUp(textDisp *textD, int absolute)
1268 int lineStartPos, column, prevLineStartPos, newPos, visLineNum;
1270 /* Find the position of the start of the line. Use the line starts array
1271 if possible, to avoid unbounded line-counting in continuous wrap mode */
1272 if (absolute) {
1273 lineStartPos = BufStartOfLine(textD->buffer, textD->cursorPos);
1274 visLineNum = -1;
1275 } else if (posToVisibleLineNum(textD, textD->cursorPos, &visLineNum))
1276 lineStartPos = textD->lineStarts[visLineNum];
1277 else {
1278 lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1279 visLineNum = -1;
1281 if (lineStartPos == 0)
1282 return False;
1284 /* Decide what column to move to, if there's a preferred column use that */
1285 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1286 BufCountDispChars(textD->buffer, lineStartPos, textD->cursorPos);
1288 /* count forward from the start of the previous line to reach the column */
1289 if (absolute)
1290 prevLineStartPos = BufCountBackwardNLines(textD->buffer, lineStartPos, 1);
1291 else if (visLineNum != -1 && visLineNum != 0)
1292 prevLineStartPos = textD->lineStarts[visLineNum-1];
1293 else
1294 prevLineStartPos = TextDCountBackwardNLines(textD, lineStartPos, 1);
1295 newPos = BufCountForwardDispChars(textD->buffer, prevLineStartPos, column);
1296 if (textD->continuousWrap && !absolute)
1297 newPos = min(newPos, TextDEndOfLine(textD, prevLineStartPos, True));
1299 /* move the cursor */
1300 TextDSetInsertPosition(textD, newPos);
1302 /* if a preferred column wasn't aleady established, establish it */
1303 textD->cursorPreferredCol = column;
1305 return True;
1307 int TextDMoveDown(textDisp *textD, int absolute)
1309 int lineStartPos, column, nextLineStartPos, newPos, visLineNum;
1311 if (textD->cursorPos == textD->buffer->length)
1312 return False;
1313 if (absolute) {
1314 lineStartPos = BufStartOfLine(textD->buffer, textD->cursorPos);
1315 visLineNum = -1;
1316 } else if (posToVisibleLineNum(textD, textD->cursorPos, &visLineNum))
1317 lineStartPos = textD->lineStarts[visLineNum];
1318 else {
1319 lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1320 visLineNum = -1;
1322 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1323 BufCountDispChars(textD->buffer, lineStartPos, textD->cursorPos);
1324 if (absolute)
1325 nextLineStartPos = BufCountForwardNLines(textD->buffer, lineStartPos, 1);
1326 else
1327 nextLineStartPos = TextDCountForwardNLines(textD, lineStartPos, 1, True);
1328 newPos = BufCountForwardDispChars(textD->buffer, nextLineStartPos, column);
1329 if (textD->continuousWrap && !absolute)
1330 newPos = min(newPos, TextDEndOfLine(textD, nextLineStartPos, True));
1331 TextDSetInsertPosition(textD, newPos);
1332 textD->cursorPreferredCol = column;
1334 return True;
1338 ** Same as BufCountLines, but takes in to account wrapping if wrapping is
1339 ** turned on. If the caller knows that startPos is at a line start, it
1340 ** can pass "startPosIsLineStart" as True to make the call more efficient
1341 ** by avoiding the additional step of scanning back to the last newline.
1343 int TextDCountLines(textDisp *textD, int startPos, int endPos,
1344 int startPosIsLineStart)
1346 int retLines, retPos, retLineStart, retLineEnd;
1348 /* If we're not wrapping use simple (and more efficient) BufCountLines */
1349 if (!textD->continuousWrap)
1350 return BufCountLines(textD->buffer, startPos, endPos);
1352 wrappedLineCounter(textD, textD->buffer, startPos, endPos, INT_MAX,
1353 startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1354 &retLineEnd);
1355 return retLines;
1359 ** Same as BufCountForwardNLines, but takes in to account line breaks when
1360 ** wrapping is turned on. If the caller knows that startPos is at a line start,
1361 ** it can pass "startPosIsLineStart" as True to make the call more efficient
1362 ** by avoiding the additional step of scanning back to the last newline.
1364 int TextDCountForwardNLines(textDisp *textD, int startPos, int nLines,
1365 int startPosIsLineStart)
1367 int retLines, retPos, retLineStart, retLineEnd;
1369 /* if we're not wrapping use more efficient BufCountForwardNLines */
1370 if (!textD->continuousWrap)
1371 return BufCountForwardNLines(textD->buffer, startPos, nLines);
1373 /* wrappedLineCounter can't handle the 0 lines case */
1374 if (nLines == 0)
1375 return startPos;
1377 /* use the common line counting routine to count forward */
1378 wrappedLineCounter(textD, textD->buffer, startPos, textD->buffer->length,
1379 nLines, startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1380 &retLineEnd);
1381 return retPos;
1385 ** Same as BufEndOfLine, but takes in to account line breaks when wrapping
1386 ** is turned on. If the caller knows that startPos is at a line start, it
1387 ** can pass "startPosIsLineStart" as True to make the call more efficient
1388 ** by avoiding the additional step of scanning back to the last newline.
1390 ** Note that the definition of the end of a line is less clear when continuous
1391 ** wrap is on. With continuous wrap off, it's just a pointer to the newline
1392 ** that ends the line. When it's on, it's the character beyond the last
1393 ** DISPLAYABLE character on the line, where a whitespace character which has
1394 ** been "converted" to a newline for wrapping is not considered displayable.
1395 ** Also note that, a line can be wrapped at a non-whitespace character if the
1396 ** line had no whitespace. In this case, this routine returns a pointer to
1397 ** the start of the next line. This is also consistent with the model used by
1398 ** visLineLength.
1400 int TextDEndOfLine(textDisp *textD, int pos, int startPosIsLineStart)
1402 int retLines, retPos, retLineStart, retLineEnd;
1404 /* If we're not wrapping use more efficien BufEndOfLine */
1405 if (!textD->continuousWrap)
1406 return BufEndOfLine(textD->buffer, pos);
1408 if (pos == textD->buffer->length)
1409 return pos;
1410 wrappedLineCounter(textD, textD->buffer, pos, textD->buffer->length, 1,
1411 startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1412 &retLineEnd);
1413 return retLineEnd;
1417 ** Same as BufStartOfLine, but returns the character after last wrap point
1418 ** rather than the last newline.
1420 int TextDStartOfLine(textDisp *textD, int pos)
1422 int retLines, retPos, retLineStart, retLineEnd;
1424 /* If we're not wrapping, use the more efficient BufStartOfLine */
1425 if (!textD->continuousWrap)
1426 return BufStartOfLine(textD->buffer, pos);
1428 wrappedLineCounter(textD, textD->buffer, BufStartOfLine(textD->buffer, pos),
1429 pos, INT_MAX, True, 0, &retPos, &retLines, &retLineStart,
1430 &retLineEnd);
1431 return retLineStart;
1435 ** Same as BufCountBackwardNLines, but takes in to account line breaks when
1436 ** wrapping is turned on.
1438 int TextDCountBackwardNLines(textDisp *textD, int startPos, int nLines)
1440 textBuffer *buf = textD->buffer;
1441 int pos, lineStart, retLines, retPos, retLineStart, retLineEnd;
1443 /* If we're not wrapping, use the more efficient BufCountBackwardNLines */
1444 if (!textD->continuousWrap)
1445 return BufCountBackwardNLines(textD->buffer, startPos, nLines);
1447 pos = startPos;
1448 while (True) {
1449 lineStart = BufStartOfLine(buf, pos);
1450 wrappedLineCounter(textD, textD->buffer, lineStart, pos, INT_MAX,
1451 True, 0, &retPos, &retLines, &retLineStart, &retLineEnd);
1452 if (retLines > nLines)
1453 return TextDCountForwardNLines(textD, lineStart, retLines-nLines,
1454 True);
1455 nLines -= retLines;
1456 pos = lineStart - 1;
1457 if (pos < 0)
1458 return 0;
1459 nLines -= 1;
1464 ** Callback attached to the text buffer to receive delete information before
1465 ** the modifications are actually made.
1467 static void bufPreDeleteCB(int pos, int nDeleted, void *cbArg)
1469 textDisp *textD = (textDisp *)cbArg;
1470 if (textD->continuousWrap &&
1471 (textD->fixedFontWidth == -1 || textD->modifyingTabDist))
1472 /* Note: we must perform this measurement, even if there is not a
1473 single character deleted; the number of "deleted" lines is the
1474 number of visual lines spanned by the real line in which the
1475 modification takes place.
1476 Also, a modification of the tab distance requires the same
1477 kind of calculations in advance, even if the font width is "fixed",
1478 because when the width of the tab characters changes, the layout
1479 of the text may be completely different. */
1480 measureDeletedLines(textD, pos, nDeleted);
1481 else
1482 textD->suppressResync = 0; /* Probably not needed, but just in case */
1486 ** Callback attached to the text buffer to receive modification information
1488 static void bufModifiedCB(int pos, int nInserted, int nDeleted,
1489 int nRestyled, char *deletedText, void *cbArg)
1491 int linesInserted, linesDeleted, startDispPos, endDispPos;
1492 textDisp *textD = (textDisp *)cbArg;
1493 textBuffer *buf = textD->buffer;
1494 int oldFirstChar = textD->firstChar;
1495 int scrolled, origCursorPos = textD->cursorPos;
1496 int wrapModStart, wrapModEnd;
1498 /* buffer modification cancels vertical cursor motion column */
1499 if (nInserted != 0 || nDeleted != 0)
1500 textD->cursorPreferredCol = -1;
1502 /* Count the number of lines inserted and deleted, and in the case
1503 of continuous wrap mode, how much has changed */
1504 if (textD->continuousWrap) {
1505 findWrapRange(textD, deletedText, pos, nInserted, nDeleted,
1506 &wrapModStart, &wrapModEnd, &linesInserted, &linesDeleted);
1507 } else {
1508 linesInserted = nInserted == 0 ? 0 :
1509 BufCountLines(buf, pos, pos + nInserted);
1510 linesDeleted = nDeleted == 0 ? 0 : countLines(deletedText);
1513 /* Update the line starts and topLineNum */
1514 if (nInserted != 0 || nDeleted != 0) {
1515 if (textD->continuousWrap) {
1516 updateLineStarts(textD, wrapModStart, wrapModEnd-wrapModStart,
1517 nDeleted + pos-wrapModStart + (wrapModEnd-(pos+nInserted)),
1518 linesInserted, linesDeleted, &scrolled);
1519 } else {
1520 updateLineStarts(textD, pos, nInserted, nDeleted, linesInserted,
1521 linesDeleted, &scrolled);
1523 } else
1524 scrolled = False;
1526 /* If we're counting non-wrapped lines as well, maintain the absolute
1527 (non-wrapped) line number of the text displayed */
1528 if (maintainingAbsTopLineNum(textD) && (nInserted != 0 || nDeleted != 0)) {
1529 if (pos + nDeleted < oldFirstChar)
1530 textD->absTopLineNum += BufCountLines(buf, pos, pos + nInserted) -
1531 countLines(deletedText);
1532 else if (pos < oldFirstChar)
1533 resetAbsLineNum(textD);
1536 /* Update the line count for the whole buffer */
1537 textD->nBufferLines += linesInserted - linesDeleted;
1539 /* Update the scroll bar ranges (and value if the value changed). Note
1540 that updating the horizontal scroll bar range requires scanning the
1541 entire displayed text, however, it doesn't seem to hurt performance
1542 much. Note also, that the horizontal scroll bar update routine is
1543 allowed to re-adjust horizOffset if there is blank space to the right
1544 of all lines of text. */
1545 updateVScrollBarRange(textD);
1546 scrolled |= updateHScrollBarRange(textD);
1548 /* Update the cursor position */
1549 if (textD->cursorToHint != NO_HINT) {
1550 textD->cursorPos = textD->cursorToHint;
1551 textD->cursorToHint = NO_HINT;
1552 } else if (textD->cursorPos > pos) {
1553 if (textD->cursorPos < pos + nDeleted)
1554 textD->cursorPos = pos;
1555 else
1556 textD->cursorPos += nInserted - nDeleted;
1559 /* If the changes caused scrolling, re-paint everything and we're done. */
1560 if (scrolled) {
1561 blankCursorProtrusions(textD);
1562 TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left,
1563 textD->height);
1564 if (textD->styleBuffer) {/* See comments in extendRangeForStyleMods */
1565 textD->styleBuffer->primary.selected = False;
1566 textD->styleBuffer->primary.zeroWidth = False;
1568 return;
1571 /* If the changes didn't cause scrolling, decide the range of characters
1572 that need to be re-painted. Also if the cursor position moved, be
1573 sure that the redisplay range covers the old cursor position so the
1574 old cursor gets erased, and erase the bits of the cursor which extend
1575 beyond the left and right edges of the text. */
1576 startDispPos = textD->continuousWrap ? wrapModStart : pos;
1577 if (origCursorPos == startDispPos && textD->cursorPos != startDispPos)
1578 startDispPos = min(startDispPos, origCursorPos-1);
1579 if (linesInserted == linesDeleted) {
1580 if (nInserted == 0 && nDeleted == 0)
1581 endDispPos = pos + nRestyled;
1582 else {
1583 endDispPos = textD->continuousWrap ? wrapModEnd :
1584 BufEndOfLine(buf, pos + nInserted) + 1;
1585 if (origCursorPos >= startDispPos &&
1586 (origCursorPos <= endDispPos || endDispPos == buf->length))
1587 blankCursorProtrusions(textD);
1589 /* If more than one line is inserted/deleted, a line break may have
1590 been inserted or removed in between, and the line numbers may
1591 have changed. If only one line is altered, line numbers cannot
1592 be affected (the insertion or removal of a line break always
1593 results in at least two lines being redrawn). */
1594 if (linesInserted > 1) redrawLineNumbers(textD, False);
1595 } else { /* linesInserted != linesDeleted */
1596 endDispPos = textD->lastChar + 1;
1597 if (origCursorPos >= pos)
1598 blankCursorProtrusions(textD);
1599 redrawLineNumbers(textD, False);
1602 /* If there is a style buffer, check if the modification caused additional
1603 changes that need to be redisplayed. (Redisplaying separately would
1604 cause double-redraw on almost every modification involving styled
1605 text). Extend the redraw range to incorporate style changes */
1606 if (textD->styleBuffer)
1607 extendRangeForStyleMods(textD, &startDispPos, &endDispPos);
1609 /* Redisplay computed range */
1610 TextDRedisplayRange(textD, startDispPos, endDispPos);
1614 ** In continuous wrap mode, internal line numbers are calculated after
1615 ** wrapping. A separate non-wrapped line count is maintained when line
1616 ** numbering is turned on. There is some performance cost to maintaining this
1617 ** line count, so normally absolute line numbers are not tracked if line
1618 ** numbering is off. This routine allows callers to specify that they still
1619 ** want this line count maintained (for use via TextDPosToLineAndCol).
1620 ** More specifically, this allows the line number reported in the statistics
1621 ** line to be calibrated in absolute lines, rather than post-wrapped lines.
1623 void TextDMaintainAbsLineNum(textDisp *textD, int state)
1625 textD->needAbsTopLineNum = state;
1626 resetAbsLineNum(textD);
1630 ** Returns the absolute (non-wrapped) line number of the first line displayed.
1631 ** Returns 0 if the absolute top line number is not being maintained.
1633 static int getAbsTopLineNum(textDisp *textD)
1635 if (!textD->continuousWrap)
1636 return textD->topLineNum;
1637 if (maintainingAbsTopLineNum(textD))
1638 return textD->absTopLineNum;
1639 return 0;
1643 ** Re-calculate absolute top line number for a change in scroll position.
1645 static void offsetAbsLineNum(textDisp *textD, int oldFirstChar)
1647 if (maintainingAbsTopLineNum(textD)) {
1648 if (textD->firstChar < oldFirstChar)
1649 textD->absTopLineNum -= BufCountLines(textD->buffer,
1650 textD->firstChar, oldFirstChar);
1651 else
1652 textD->absTopLineNum += BufCountLines(textD->buffer,
1653 oldFirstChar, textD->firstChar);
1658 ** Return true if a separate absolute top line number is being maintained
1659 ** (for displaying line numbers or showing in the statistics line).
1661 static int maintainingAbsTopLineNum(textDisp *textD)
1663 return textD->continuousWrap &&
1664 (textD->lineNumWidth != 0 || textD->needAbsTopLineNum);
1668 ** Count lines from the beginning of the buffer to reestablish the
1669 ** absolute (non-wrapped) top line number. If mode is not continuous wrap,
1670 ** or the number is not being maintained, does nothing.
1672 static void resetAbsLineNum(textDisp *textD)
1674 textD->absTopLineNum = 1;
1675 offsetAbsLineNum(textD, 0);
1679 ** Find the line number of position "pos" relative to the first line of
1680 ** displayed text. Returns False if the line is not displayed.
1682 static int posToVisibleLineNum(textDisp *textD, int pos, int *lineNum)
1684 int i;
1686 if (pos < textD->firstChar)
1687 return False;
1688 if (pos > textD->lastChar) {
1689 if (emptyLinesVisible(textD)) {
1690 if (textD->lastChar < textD->buffer->length) {
1691 if (!posToVisibleLineNum(textD, textD->lastChar, lineNum)) {
1692 fprintf(stderr, "Consistency check ptvl failed\n");
1693 return False;
1695 return ++(*lineNum) <= textD->nVisibleLines-1;
1696 } else {
1697 posToVisibleLineNum(textD, max(textD->lastChar-1, 0), lineNum);
1698 return True;
1701 return False;
1704 for (i=textD->nVisibleLines-1; i>=0; i--) {
1705 if (textD->lineStarts[i] != -1 && pos >= textD->lineStarts[i]) {
1706 *lineNum = i;
1707 return True;
1710 return False; /* probably never be reached */
1714 ** Redisplay the text on a single line represented by "visLineNum" (the
1715 ** number of lines down from the top of the display), limited by
1716 ** "leftClip" and "rightClip" window coordinates and "leftCharIndex" and
1717 ** "rightCharIndex" character positions (not including the character at
1718 ** position "rightCharIndex").
1720 ** The cursor is also drawn if it appears on the line.
1722 static void redisplayLine(textDisp *textD, int visLineNum, int leftClip,
1723 int rightClip, int leftCharIndex, int rightCharIndex)
1725 textBuffer *buf = textD->buffer;
1726 int i, x, y, startX, charIndex, lineStartPos, lineLen, fontHeight;
1727 int stdCharWidth, charWidth, startIndex, charStyle, style;
1728 int charLen, outStartIndex, outIndex, cursorX = 0, hasCursor = False;
1729 int dispIndexOffset, cursorPos = textD->cursorPos, y_orig;
1730 char expandedChar[MAX_EXP_CHAR_LEN], outStr[MAX_DISP_LINE_LEN];
1731 char *lineStr, *outPtr;
1732 char baseChar;
1734 /* If line is not displayed, skip it */
1735 if (visLineNum < 0 || visLineNum >= textD->nVisibleLines)
1736 return;
1738 /* Shrink the clipping range to the active display area */
1739 leftClip = max(textD->left, leftClip);
1740 rightClip = min(rightClip, textD->left + textD->width);
1742 if (leftClip > rightClip) {
1743 return;
1746 /* Calculate y coordinate of the string to draw */
1747 fontHeight = textD->ascent + textD->descent;
1748 y = textD->top + visLineNum * fontHeight;
1750 /* Get the text, length, and buffer position of the line to display */
1751 lineStartPos = textD->lineStarts[visLineNum];
1752 if (lineStartPos == -1) {
1753 lineLen = 0;
1754 lineStr = NULL;
1755 } else {
1756 lineLen = visLineLength(textD, visLineNum);
1757 lineStr = BufGetRange(buf, lineStartPos, lineStartPos + lineLen);
1760 /* Space beyond the end of the line is still counted in units of characters
1761 of a standardized character width (this is done mostly because style
1762 changes based on character position can still occur in this region due
1763 to rectangular selections). stdCharWidth must be non-zero to prevent a
1764 potential infinite loop if x does not advance */
1765 stdCharWidth = textD->fontStruct->max_bounds.width;
1766 if (stdCharWidth <= 0) {
1767 fprintf(stderr, "Internal Error, bad font measurement\n");
1768 XtFree(lineStr);
1769 return;
1772 /* Rectangular selections are based on "real" line starts (after a newline
1773 or start of buffer). Calculate the difference between the last newline
1774 position and the line start we're using. Since scanning back to find a
1775 newline is expensive, only do so if there's actually a rectangular
1776 selection which needs it */
1777 if (textD->continuousWrap && (rangeTouchesRectSel(&buf->primary,
1778 lineStartPos, lineStartPos + lineLen) || rangeTouchesRectSel(
1779 &buf->secondary, lineStartPos, lineStartPos + lineLen) ||
1780 rangeTouchesRectSel(&buf->highlight, lineStartPos,
1781 lineStartPos + lineLen))) {
1782 dispIndexOffset = BufCountDispChars(buf,
1783 BufStartOfLine(buf, lineStartPos), lineStartPos);
1784 } else
1785 dispIndexOffset = 0;
1787 /* Step through character positions from the beginning of the line (even if
1788 that's off the left edge of the displayed area) to find the first
1789 character position that's not clipped, and the x coordinate for drawing
1790 that character */
1791 x = textD->left - textD->horizOffset;
1792 outIndex = 0;
1793 for(charIndex=0; ; charIndex++) {
1794 baseChar = '\0';
1795 charLen = charIndex >= lineLen ? 1 :
1796 BufExpandCharacter(baseChar = lineStr[charIndex], outIndex,
1797 expandedChar, buf->tabDist, buf->nullSubsChar);
1798 style = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1799 outIndex + dispIndexOffset, baseChar);
1800 charWidth = charIndex >= lineLen ? stdCharWidth :
1801 stringWidth(textD, expandedChar, charLen, style);
1802 if (x + charWidth >= leftClip && charIndex >= leftCharIndex) {
1803 startIndex = charIndex;
1804 outStartIndex = outIndex;
1805 startX = x;
1806 break;
1808 x += charWidth;
1809 outIndex += charLen;
1812 /* Scan character positions from the beginning of the clipping range, and
1813 draw parts whenever the style changes (also note if the cursor is on
1814 this line, and where it should be drawn to take advantage of the x
1815 position which we've gone to so much trouble to calculate) */
1816 outPtr = outStr;
1817 outIndex = outStartIndex;
1818 x = startX;
1819 for(charIndex=startIndex; charIndex<rightCharIndex; charIndex++) {
1820 if (lineStartPos+charIndex == cursorPos) {
1821 if (charIndex < lineLen || (charIndex == lineLen &&
1822 cursorPos >= buf->length)) {
1823 hasCursor = True;
1824 cursorX = x - 1;
1825 } else if (charIndex == lineLen) {
1826 if (wrapUsesCharacter(textD, cursorPos)) {
1827 hasCursor = True;
1828 cursorX = x - 1;
1832 baseChar = '\0';
1833 charLen = charIndex >= lineLen ? 1 :
1834 BufExpandCharacter(baseChar = lineStr[charIndex], outIndex,
1835 expandedChar, buf->tabDist, buf->nullSubsChar);
1836 charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1837 outIndex + dispIndexOffset, baseChar);
1838 for (i=0; i<charLen; i++) {
1839 if (i != 0 && charIndex < lineLen && lineStr[charIndex] == '\t')
1840 charStyle = styleOfPos(textD, lineStartPos, lineLen,
1841 charIndex, outIndex + dispIndexOffset, '\t');
1842 if (charStyle != style) {
1843 drawString(textD, style, startX, y, x, outStr, outPtr - outStr);
1844 outPtr = outStr;
1845 startX = x;
1846 style = charStyle;
1848 if (charIndex < lineLen) {
1849 *outPtr = expandedChar[i];
1850 charWidth = stringWidth(textD, &expandedChar[i], 1, charStyle);
1851 } else
1852 charWidth = stdCharWidth;
1853 outPtr++;
1854 x += charWidth;
1855 outIndex++;
1857 if (outPtr-outStr+MAX_EXP_CHAR_LEN>=MAX_DISP_LINE_LEN || x>=rightClip)
1858 break;
1861 /* Draw the remaining style segment */
1862 drawString(textD, style, startX, y, x, outStr, outPtr - outStr);
1864 /* Draw the cursor if part of it appeared on the redisplayed part of
1865 this line. Also check for the cases which are not caught as the
1866 line is scanned above: when the cursor appears at the very end
1867 of the redisplayed section. */
1868 y_orig = textD->cursorY;
1869 if (textD->cursorOn) {
1870 if (hasCursor)
1871 drawCursor(textD, cursorX, y);
1872 else if (charIndex<lineLen && (lineStartPos+charIndex+1 == cursorPos)
1873 && x == rightClip) {
1874 if (cursorPos >= buf->length)
1875 drawCursor(textD, x - 1, y);
1876 else {
1877 if (wrapUsesCharacter(textD, cursorPos))
1878 drawCursor(textD, x - 1, y);
1883 /* If the y position of the cursor has changed, redraw the calltip */
1884 if (hasCursor && (y_orig != textD->cursorY || y_orig != y))
1885 TextDRedrawCalltip(textD, 0);
1887 if (lineStr != NULL)
1888 XtFree(lineStr);
1892 ** Draw a string or blank area according to parameter "style", using the
1893 ** appropriate colors and drawing method for that style, with top left
1894 ** corner at x, y. If style says to draw text, use "string" as source of
1895 ** characters, and draw "nChars", if style is FILL, erase
1896 ** rectangle where text would have drawn from x to toX and from y to
1897 ** the maximum y extent of the current font(s).
1899 static void drawString(textDisp *textD, int style, int x, int y, int toX,
1900 char *string, int nChars)
1902 GC gc, bgGC;
1903 XGCValues gcValues;
1904 XFontStruct *fs = textD->fontStruct;
1905 Pixel bground = textD->bgPixel;
1906 Pixel fground = textD->fgPixel;
1907 int underlineStyle = FALSE;
1909 /* Don't draw if widget isn't realized */
1910 if (XtWindow(textD->w) == 0)
1911 return;
1913 /* select a GC */
1914 if (style & (STYLE_LOOKUP_MASK | BACKLIGHT_MASK | RANGESET_MASK)) {
1915 gc = bgGC = textD->styleGC;
1917 else if (style & HIGHLIGHT_MASK) {
1918 gc = textD->highlightGC;
1919 bgGC = textD->highlightBGGC;
1921 else if (style & PRIMARY_MASK) {
1922 gc = textD->selectGC;
1923 bgGC = textD->selectBGGC;
1925 else {
1926 gc = bgGC = textD->gc;
1929 if (gc == textD->styleGC) {
1930 /* we have work to do */
1931 styleTableEntry *styleRec;
1932 /* Set font, color, and gc depending on style. For normal text, GCs
1933 for normal drawing, or drawing within a selection or highlight are
1934 pre-allocated and pre-configured. For syntax highlighting, GCs are
1935 configured here, on the fly. */
1936 if (style & STYLE_LOOKUP_MASK) {
1937 styleRec = &textD->styleTable[(style & STYLE_LOOKUP_MASK) - ASCII_A];
1938 underlineStyle = styleRec->underline;
1939 fs = styleRec->font;
1940 gcValues.font = fs->fid;
1941 fground = styleRec->color;
1942 /* here you could pick up specific select and highlight fground */
1944 else {
1945 styleRec = NULL;
1946 gcValues.font = fs->fid;
1947 fground = textD->fgPixel;
1949 /* Background color priority order is:
1950 1 Primary(Selection), 2 Highlight(Parens),
1951 3 Rangeset, 4 SyntaxHighlightStyle,
1952 5 Backlight (if NOT fill), 6 DefaultBackground */
1953 bground =
1954 style & PRIMARY_MASK ? textD->selectBGPixel :
1955 style & HIGHLIGHT_MASK ? textD->highlightBGPixel :
1956 style & RANGESET_MASK ?
1957 getRangesetColor(textD,
1958 (style&RANGESET_MASK)>>RANGESET_SHIFT,
1959 bground) :
1960 styleRec && styleRec->bgColorName ? styleRec->bgColor :
1961 (style & BACKLIGHT_MASK) && !(style & FILL_MASK) ?
1962 textD->bgClassPixel[(style>>BACKLIGHT_SHIFT) & 0xff] :
1963 textD->bgPixel;
1964 if (fground == bground) /* B&W kludge */
1965 fground = textD->bgPixel;
1966 /* set up gc for clearing using the foreground color entry */
1967 gcValues.foreground = gcValues.background = bground;
1968 XChangeGC(XtDisplay(textD->w), gc,
1969 GCFont | GCForeground | GCBackground, &gcValues);
1972 /* Draw blank area rather than text, if that was the request */
1973 if (style & FILL_MASK) {
1974 /* wipes out to right hand edge of widget */
1975 if (toX >= textD->left)
1976 clearRect(textD, bgGC, max(x, textD->left), y,
1977 toX - max(x, textD->left), textD->ascent + textD->descent);
1978 return;
1981 /* If any space around the character remains unfilled (due to use of
1982 different sized fonts for highlighting), fill in above or below
1983 to erase previously drawn characters */
1984 if (fs->ascent < textD->ascent)
1985 clearRect(textD, bgGC, x, y, toX - x, textD->ascent - fs->ascent);
1986 if (fs->descent < textD->descent)
1987 clearRect(textD, bgGC, x, y + textD->ascent + fs->descent, toX - x,
1988 textD->descent - fs->descent);
1990 /* set up gc for writing text (set foreground properly) */
1991 if (bgGC == textD->styleGC) {
1992 gcValues.foreground = fground;
1993 XChangeGC(XtDisplay(textD->w), gc, GCForeground, &gcValues);
1996 /* Draw the string using gc and font set above */
1997 XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w), gc, x,
1998 y + textD->ascent, string, nChars);
2000 /* Underline if style is secondary selection */
2001 if (style & SECONDARY_MASK || underlineStyle)
2003 /* restore foreground in GC (was set to background by clearRect()) */
2004 gcValues.foreground = fground;
2005 XChangeGC(XtDisplay(textD->w), gc,
2006 GCForeground, &gcValues);
2007 /* draw underline */
2008 XDrawLine(XtDisplay(textD->w), XtWindow(textD->w), gc, x,
2009 y + textD->ascent, toX - 1, y + textD->ascent);
2014 ** Clear a rectangle with the appropriate background color for "style"
2016 static void clearRect(textDisp *textD, GC gc, int x, int y,
2017 int width, int height)
2019 /* A width of zero means "clear to end of window" to XClearArea */
2020 if (width == 0)
2021 return;
2023 if (gc == textD->gc) {
2024 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), x, y,
2025 width, height, False);
2027 else {
2028 XFillRectangle(XtDisplay(textD->w), XtWindow(textD->w),
2029 gc, x, y, width, height);
2034 ** Draw a cursor with top center at x, y.
2036 static void drawCursor(textDisp *textD, int x, int y)
2038 XSegment segs[5];
2039 int left, right, cursorWidth, midY;
2040 int fontWidth = textD->fontStruct->min_bounds.width, nSegs = 0;
2041 int fontHeight = textD->ascent + textD->descent;
2042 int bot = y + fontHeight - 1;
2044 if (XtWindow(textD->w) == 0 || x < textD->left-1 ||
2045 x > textD->left + textD->width)
2046 return;
2048 /* For cursors other than the block, make them around 2/3 of a character
2049 width, rounded to an even number of pixels so that X will draw an
2050 odd number centered on the stem at x. */
2051 cursorWidth = (fontWidth/3) * 2;
2052 left = x - cursorWidth/2;
2053 right = left + cursorWidth;
2055 /* Create segments and draw cursor */
2056 if (textD->cursorStyle == CARET_CURSOR) {
2057 midY = bot - fontHeight/5;
2058 segs[0].x1 = left; segs[0].y1 = bot; segs[0].x2 = x; segs[0].y2 = midY;
2059 segs[1].x1 = x; segs[1].y1 = midY; segs[1].x2 = right; segs[1].y2 = bot;
2060 segs[2].x1 = left; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2=midY-1;
2061 segs[3].x1 = x; segs[3].y1=midY-1; segs[3].x2 = right; segs[3].y2 = bot;
2062 nSegs = 4;
2063 } else if (textD->cursorStyle == NORMAL_CURSOR) {
2064 segs[0].x1 = left; segs[0].y1 = y; segs[0].x2 = right; segs[0].y2 = y;
2065 segs[1].x1 = x; segs[1].y1 = y; segs[1].x2 = x; segs[1].y2 = bot;
2066 segs[2].x1 = left; segs[2].y1 = bot; segs[2].x2 = right; segs[2].y2=bot;
2067 nSegs = 3;
2068 } else if (textD->cursorStyle == HEAVY_CURSOR) {
2069 segs[0].x1 = x-1; segs[0].y1 = y; segs[0].x2 = x-1; segs[0].y2 = bot;
2070 segs[1].x1 = x; segs[1].y1 = y; segs[1].x2 = x; segs[1].y2 = bot;
2071 segs[2].x1 = x+1; segs[2].y1 = y; segs[2].x2 = x+1; segs[2].y2 = bot;
2072 segs[3].x1 = left; segs[3].y1 = y; segs[3].x2 = right; segs[3].y2 = y;
2073 segs[4].x1 = left; segs[4].y1 = bot; segs[4].x2 = right; segs[4].y2=bot;
2074 nSegs = 5;
2075 } else if (textD->cursorStyle == DIM_CURSOR) {
2076 midY = y + fontHeight/2;
2077 segs[0].x1 = x; segs[0].y1 = y; segs[0].x2 = x; segs[0].y2 = y;
2078 segs[1].x1 = x; segs[1].y1 = midY; segs[1].x2 = x; segs[1].y2 = midY;
2079 segs[2].x1 = x; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2 = bot;
2080 nSegs = 3;
2081 } else if (textD->cursorStyle == BLOCK_CURSOR) {
2082 right = x + fontWidth;
2083 segs[0].x1 = x; segs[0].y1 = y; segs[0].x2 = right; segs[0].y2 = y;
2084 segs[1].x1 = right; segs[1].y1 = y; segs[1].x2 = right; segs[1].y2=bot;
2085 segs[2].x1 = right; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2 = bot;
2086 segs[3].x1 = x; segs[3].y1 = bot; segs[3].x2 = x; segs[3].y2 = y;
2087 nSegs = 4;
2089 XDrawSegments(XtDisplay(textD->w), XtWindow(textD->w),
2090 textD->cursorFGGC, segs, nSegs);
2092 /* Save the last position drawn */
2093 textD->cursorX = x;
2094 textD->cursorY = y;
2098 ** Determine the drawing method to use to draw a specific character from "buf".
2099 ** "lineStartPos" gives the character index where the line begins, "lineIndex",
2100 ** the number of characters past the beginning of the line, and "dispIndex",
2101 ** the number of displayed characters past the beginning of the line. Passing
2102 ** lineStartPos of -1 returns the drawing style for "no text".
2104 ** Why not just: styleOfPos(textD, pos)? Because style applies to blank areas
2105 ** of the window beyond the text boundaries, and because this routine must also
2106 ** decide whether a position is inside of a rectangular selection, and do so
2107 ** efficiently, without re-counting character positions from the start of the
2108 ** line.
2110 ** Note that style is a somewhat incorrect name, drawing method would
2111 ** be more appropriate.
2113 static int styleOfPos(textDisp *textD, int lineStartPos,
2114 int lineLen, int lineIndex, int dispIndex, int thisChar)
2116 textBuffer *buf = textD->buffer;
2117 textBuffer *styleBuf = textD->styleBuffer;
2118 int pos, style = 0;
2120 if (lineStartPos == -1 || buf == NULL)
2121 return FILL_MASK;
2123 pos = lineStartPos + min(lineIndex, lineLen);
2125 if (lineIndex >= lineLen)
2126 style = FILL_MASK;
2127 else if (styleBuf != NULL) {
2128 style = (unsigned char)BufGetCharacter(styleBuf, pos);
2129 if (style == textD->unfinishedStyle) {
2130 /* encountered "unfinished" style, trigger parsing */
2131 (textD->unfinishedHighlightCB)(textD, pos, textD->highlightCBArg);
2132 style = (unsigned char)BufGetCharacter(styleBuf, pos);
2135 if (inSelection(&buf->primary, pos, lineStartPos, dispIndex))
2136 style |= PRIMARY_MASK;
2137 if (inSelection(&buf->highlight, pos, lineStartPos, dispIndex))
2138 style |= HIGHLIGHT_MASK;
2139 if (inSelection(&buf->secondary, pos, lineStartPos, dispIndex))
2140 style |= SECONDARY_MASK;
2141 /* store in the RANGESET_MASK portion of style the rangeset index for pos */
2142 if (buf->rangesetTable) {
2143 int rangesetIndex = RangesetIndex1ofPos(buf->rangesetTable, pos, True);
2144 style |= ((rangesetIndex << RANGESET_SHIFT) & RANGESET_MASK);
2146 /* store in the BACKLIGHT_MASK portion of style the background color class
2147 of the character thisChar */
2148 if (textD->bgClass)
2150 style |= (textD->bgClass[(unsigned char)thisChar]<<BACKLIGHT_SHIFT);
2152 return style;
2156 ** Find the width of a string in the font of a particular style
2158 static int stringWidth(textDisp *textD, char *string, int length, int style)
2160 XFontStruct *fs;
2162 if (style & STYLE_LOOKUP_MASK)
2163 fs = textD->styleTable[(style & STYLE_LOOKUP_MASK) - ASCII_A].font;
2164 else
2165 fs = textD->fontStruct;
2166 return XTextWidth(fs, string, length);
2170 ** Return true if position "pos" with indentation "dispIndex" is in
2171 ** selection "sel"
2173 static int inSelection(selection *sel, int pos, int lineStartPos, int dispIndex)
2175 return sel->selected &&
2176 ((!sel->rectangular &&
2177 pos >= sel->start && pos < sel->end) ||
2178 (sel->rectangular &&
2179 pos >= sel->start && lineStartPos <= sel->end &&
2180 dispIndex >= sel->rectStart && dispIndex < sel->rectEnd));
2184 ** Translate window coordinates to the nearest (insert cursor or character
2185 ** cell) text position. The parameter posType specifies how to interpret the
2186 ** position: CURSOR_POS means translate the coordinates to the nearest cursor
2187 ** position, and CHARACTER_POS means return the position of the character
2188 ** closest to (x, y).
2190 static int xyToPos(textDisp *textD, int x, int y, int posType)
2192 int charIndex, lineStart, lineLen, fontHeight;
2193 int charWidth, charLen, charStyle, visLineNum, xStep, outIndex;
2194 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
2196 /* Find the visible line number corresponding to the y coordinate */
2197 fontHeight = textD->ascent + textD->descent;
2198 visLineNum = (y - textD->top) / fontHeight;
2199 if (visLineNum < 0)
2200 return textD->firstChar;
2201 if (visLineNum >= textD->nVisibleLines)
2202 visLineNum = textD->nVisibleLines - 1;
2204 /* Find the position at the start of the line */
2205 lineStart = textD->lineStarts[visLineNum];
2207 /* If the line start was empty, return the last position in the buffer */
2208 if (lineStart == -1)
2209 return textD->buffer->length;
2211 /* Get the line text and its length */
2212 lineLen = visLineLength(textD, visLineNum);
2213 lineStr = BufGetRange(textD->buffer, lineStart, lineStart + lineLen);
2215 /* Step through character positions from the beginning of the line
2216 to find the character position corresponding to the x coordinate */
2217 xStep = textD->left - textD->horizOffset;
2218 outIndex = 0;
2219 for(charIndex=0; charIndex<lineLen; charIndex++) {
2220 charLen = BufExpandCharacter(lineStr[charIndex], outIndex, expandedChar,
2221 textD->buffer->tabDist, textD->buffer->nullSubsChar);
2222 charStyle = styleOfPos(textD, lineStart, lineLen, charIndex, outIndex,
2223 lineStr[charIndex]);
2224 charWidth = stringWidth(textD, expandedChar, charLen, charStyle);
2225 if (x < xStep + (posType == CURSOR_POS ? charWidth/2 : charWidth)) {
2226 XtFree(lineStr);
2227 return lineStart + charIndex;
2229 xStep += charWidth;
2230 outIndex += charLen;
2233 /* If the x position was beyond the end of the line, return the position
2234 of the newline at the end of the line */
2235 XtFree(lineStr);
2236 return lineStart + lineLen;
2240 ** Translate window coordinates to the nearest row and column number for
2241 ** positioning the cursor. This, of course, makes no sense when the font is
2242 ** proportional, since there are no absolute columns. The parameter posType
2243 ** specifies how to interpret the position: CURSOR_POS means translate the
2244 ** coordinates to the nearest position between characters, and CHARACTER_POS
2245 ** means translate the position to the nearest character cell.
2247 static void xyToUnconstrainedPos(textDisp *textD, int x, int y, int *row,
2248 int *column, int posType)
2250 int fontHeight = textD->ascent + textD->descent;
2251 int fontWidth = textD->fontStruct->max_bounds.width;
2253 /* Find the visible line number corresponding to the y coordinate */
2254 *row = (y - textD->top) / fontHeight;
2255 if (*row < 0) *row = 0;
2256 if (*row >= textD->nVisibleLines) *row = textD->nVisibleLines - 1;
2257 *column = ((x-textD->left) + textD->horizOffset +
2258 (posType == CURSOR_POS ? fontWidth/2 : 0)) / fontWidth;
2259 if (*column < 0) *column = 0;
2263 ** Offset the line starts array, topLineNum, firstChar and lastChar, for a new
2264 ** vertical scroll position given by newTopLineNum. If any currently displayed
2265 ** lines will still be visible, salvage the line starts values, otherwise,
2266 ** count lines from the nearest known line start (start or end of buffer, or
2267 ** the closest value in the lineStarts array)
2269 static void offsetLineStarts(textDisp *textD, int newTopLineNum)
2271 int oldTopLineNum = textD->topLineNum;
2272 int oldFirstChar = textD->firstChar;
2273 int lineDelta = newTopLineNum - oldTopLineNum;
2274 int nVisLines = textD->nVisibleLines;
2275 int *lineStarts = textD->lineStarts;
2276 int i, lastLineNum;
2277 textBuffer *buf = textD->buffer;
2279 /* If there was no offset, nothing needs to be changed */
2280 if (lineDelta == 0)
2281 return;
2283 /* { int i;
2284 printf("Scroll, lineDelta %d\n", lineDelta);
2285 printf("lineStarts Before: ");
2286 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2287 printf("\n");
2288 } */
2290 /* Find the new value for firstChar by counting lines from the nearest
2291 known line start (start or end of buffer, or the closest value in the
2292 lineStarts array) */
2293 lastLineNum = oldTopLineNum + nVisLines - 1;
2294 if (newTopLineNum < oldTopLineNum && newTopLineNum < -lineDelta) {
2295 textD->firstChar = TextDCountForwardNLines(textD, 0, newTopLineNum-1,
2296 True);
2297 /* printf("counting forward %d lines from start\n", newTopLineNum-1);*/
2298 } else if (newTopLineNum < oldTopLineNum) {
2299 textD->firstChar = TextDCountBackwardNLines(textD, textD->firstChar,
2300 -lineDelta);
2301 /* printf("counting backward %d lines from firstChar\n", -lineDelta);*/
2302 } else if (newTopLineNum < lastLineNum) {
2303 textD->firstChar = lineStarts[newTopLineNum - oldTopLineNum];
2304 /* printf("taking new start from lineStarts[%d]\n",
2305 newTopLineNum - oldTopLineNum); */
2306 } else if (newTopLineNum-lastLineNum < textD->nBufferLines-newTopLineNum) {
2307 textD->firstChar = TextDCountForwardNLines(textD,
2308 lineStarts[nVisLines-1], newTopLineNum - lastLineNum, True);
2309 /* printf("counting forward %d lines from start of last line\n",
2310 newTopLineNum - lastLineNum); */
2311 } else {
2312 textD->firstChar = TextDCountBackwardNLines(textD, buf->length,
2313 textD->nBufferLines - newTopLineNum + 1);
2314 /* printf("counting backward %d lines from end\n",
2315 textD->nBufferLines - newTopLineNum + 1); */
2318 /* Fill in the line starts array */
2319 if (lineDelta < 0 && -lineDelta < nVisLines) {
2320 for (i=nVisLines-1; i >= -lineDelta; i--)
2321 lineStarts[i] = lineStarts[i+lineDelta];
2322 calcLineStarts(textD, 0, -lineDelta);
2323 } else if (lineDelta > 0 && lineDelta < nVisLines) {
2324 for (i=0; i<nVisLines-lineDelta; i++)
2325 lineStarts[i] = lineStarts[i+lineDelta];
2326 calcLineStarts(textD, nVisLines-lineDelta, nVisLines-1);
2327 } else
2328 calcLineStarts(textD, 0, nVisLines);
2330 /* Set lastChar and topLineNum */
2331 calcLastChar(textD);
2332 textD->topLineNum = newTopLineNum;
2334 /* If we're numbering lines or being asked to maintain an absolute line
2335 number, re-calculate the absolute line number */
2336 offsetAbsLineNum(textD, oldFirstChar);
2338 /* { int i;
2339 printf("lineStarts After: ");
2340 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2341 printf("\n");
2342 } */
2346 ** Update the line starts array, topLineNum, firstChar and lastChar for text
2347 ** display "textD" after a modification to the text buffer, given by the
2348 ** position where the change began "pos", and the nmubers of characters
2349 ** and lines inserted and deleted.
2351 static void updateLineStarts(textDisp *textD, int pos, int charsInserted,
2352 int charsDeleted, int linesInserted, int linesDeleted, int *scrolled)
2354 int *lineStarts = textD->lineStarts;
2355 int i, lineOfPos, lineOfEnd, nVisLines = textD->nVisibleLines;
2356 int charDelta = charsInserted - charsDeleted;
2357 int lineDelta = linesInserted - linesDeleted;
2359 /* { int i;
2360 printf("linesDeleted %d, linesInserted %d, charsInserted %d, charsDeleted %d\n",
2361 linesDeleted, linesInserted, charsInserted, charsDeleted);
2362 printf("lineStarts Before: ");
2363 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2364 printf("\n");
2365 } */
2366 /* If all of the changes were before the displayed text, the display
2367 doesn't change, just update the top line num and offset the line
2368 start entries and first and last characters */
2369 if (pos + charsDeleted < textD->firstChar) {
2370 textD->topLineNum += lineDelta;
2371 for (i=0; i<nVisLines && lineStarts[i] != -1; i++)
2372 lineStarts[i] += charDelta;
2373 /* { int i;
2374 printf("lineStarts after delete doesn't touch: ");
2375 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2376 printf("\n");
2377 } */
2378 textD->firstChar += charDelta;
2379 textD->lastChar += charDelta;
2380 *scrolled = False;
2381 return;
2384 /* The change began before the beginning of the displayed text, but
2385 part or all of the displayed text was deleted */
2386 if (pos < textD->firstChar) {
2387 /* If some text remains in the window, anchor on that */
2388 if (posToVisibleLineNum(textD, pos + charsDeleted, &lineOfEnd) &&
2389 ++lineOfEnd < nVisLines && lineStarts[lineOfEnd] != -1) {
2390 textD->topLineNum = max(1, textD->topLineNum + lineDelta);
2391 textD->firstChar = TextDCountBackwardNLines(textD,
2392 lineStarts[lineOfEnd] + charDelta, lineOfEnd);
2393 /* Otherwise anchor on original line number and recount everything */
2394 } else {
2395 if (textD->topLineNum > textD->nBufferLines + lineDelta) {
2396 textD->topLineNum = 1;
2397 textD->firstChar = 0;
2398 } else
2399 textD->firstChar = TextDCountForwardNLines(textD, 0,
2400 textD->topLineNum - 1, True);
2402 calcLineStarts(textD, 0, nVisLines-1);
2403 /* { int i;
2404 printf("lineStarts after delete encroaches: ");
2405 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2406 printf("\n");
2407 } */
2408 /* calculate lastChar by finding the end of the last displayed line */
2409 calcLastChar(textD);
2410 *scrolled = True;
2411 return;
2414 /* If the change was in the middle of the displayed text (it usually is),
2415 salvage as much of the line starts array as possible by moving and
2416 offsetting the entries after the changed area, and re-counting the
2417 added lines or the lines beyond the salvaged part of the line starts
2418 array */
2419 if (pos <= textD->lastChar) {
2420 /* find line on which the change began */
2421 posToVisibleLineNum(textD, pos, &lineOfPos);
2422 /* salvage line starts after the changed area */
2423 if (lineDelta == 0) {
2424 for (i=lineOfPos+1; i<nVisLines && lineStarts[i]!= -1; i++)
2425 lineStarts[i] += charDelta;
2426 } else if (lineDelta > 0) {
2427 for (i=nVisLines-1; i>=lineOfPos+lineDelta+1; i--)
2428 lineStarts[i] = lineStarts[i-lineDelta] +
2429 (lineStarts[i-lineDelta] == -1 ? 0 : charDelta);
2430 } else /* (lineDelta < 0) */ {
2431 for (i=max(0,lineOfPos+1); i<nVisLines+lineDelta; i++)
2432 lineStarts[i] = lineStarts[i-lineDelta] +
2433 (lineStarts[i-lineDelta] == -1 ? 0 : charDelta);
2435 /* { int i;
2436 printf("lineStarts after salvage: ");
2437 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2438 printf("\n");
2439 } */
2440 /* fill in the missing line starts */
2441 if (linesInserted >= 0)
2442 calcLineStarts(textD, lineOfPos + 1, lineOfPos + linesInserted);
2443 if (lineDelta < 0)
2444 calcLineStarts(textD, nVisLines+lineDelta, nVisLines);
2445 /* { int i;
2446 printf("lineStarts after recalculation: ");
2447 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2448 printf("\n");
2449 } */
2450 /* calculate lastChar by finding the end of the last displayed line */
2451 calcLastChar(textD);
2452 *scrolled = False;
2453 return;
2456 /* Change was past the end of the displayed text, but displayable by virtue
2457 of being an insert at the end of the buffer into visible blank lines */
2458 if (emptyLinesVisible(textD)) {
2459 posToVisibleLineNum(textD, pos, &lineOfPos);
2460 calcLineStarts(textD, lineOfPos, lineOfPos+linesInserted);
2461 calcLastChar(textD);
2462 /* { int i;
2463 printf("lineStarts after insert at end: ");
2464 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2465 printf("\n");
2466 } */
2467 *scrolled = False;
2468 return;
2471 /* Change was beyond the end of the buffer and not visible, do nothing */
2472 *scrolled = False;
2476 ** Scan through the text in the "textD"'s buffer and recalculate the line
2477 ** starts array values beginning at index "startLine" and continuing through
2478 ** (including) "endLine". It assumes that the line starts entry preceding
2479 ** "startLine" (or textD->firstChar if startLine is 0) is good, and re-counts
2480 ** newlines to fill in the requested entries. Out of range values for
2481 ** "startLine" and "endLine" are acceptable.
2483 static void calcLineStarts(textDisp *textD, int startLine, int endLine)
2485 int startPos, bufLen = textD->buffer->length;
2486 int line, lineEnd, nextLineStart, nVis = textD->nVisibleLines;
2487 int *lineStarts = textD->lineStarts;
2489 /* Clean up (possibly) messy input parameters */
2490 if (nVis == 0) return;
2491 if (endLine < 0) endLine = 0;
2492 if (endLine >= nVis) endLine = nVis - 1;
2493 if (startLine < 0) startLine = 0;
2494 if (startLine >=nVis) startLine = nVis - 1;
2495 if (startLine > endLine)
2496 return;
2498 /* Find the last known good line number -> position mapping */
2499 if (startLine == 0) {
2500 lineStarts[0] = textD->firstChar;
2501 startLine = 1;
2503 startPos = lineStarts[startLine-1];
2505 /* If the starting position is already past the end of the text,
2506 fill in -1's (means no text on line) and return */
2507 if (startPos == -1) {
2508 for (line=startLine; line<=endLine; line++)
2509 lineStarts[line] = -1;
2510 return;
2513 /* Loop searching for ends of lines and storing the positions of the
2514 start of the next line in lineStarts */
2515 for (line=startLine; line<=endLine; line++) {
2516 findLineEnd(textD, startPos, True, &lineEnd, &nextLineStart);
2517 startPos = nextLineStart;
2518 if (startPos >= bufLen) {
2519 /* If the buffer ends with a newline or line break, put
2520 buf->length in the next line start position (instead of
2521 a -1 which is the normal marker for an empty line) to
2522 indicate that the cursor may safely be displayed there */
2523 if (line == 0 || (lineStarts[line-1] != bufLen &&
2524 lineEnd != nextLineStart)) {
2525 lineStarts[line] = bufLen;
2526 line++;
2528 break;
2530 lineStarts[line] = startPos;
2533 /* Set any entries beyond the end of the text to -1 */
2534 for (; line<=endLine; line++)
2535 lineStarts[line] = -1;
2539 ** Given a textDisp with a complete, up-to-date lineStarts array, update
2540 ** the lastChar entry to point to the last buffer position displayed.
2542 static void calcLastChar(textDisp *textD)
2544 int i;
2546 for (i=textD->nVisibleLines-1; i>0 && textD->lineStarts[i]== -1; i--);
2547 textD->lastChar = i < 0 ? 0 :
2548 TextDEndOfLine(textD, textD->lineStarts[i], True);
2551 void TextDImposeGraphicsExposeTranslation(textDisp *textD, int *xOffset, int *yOffset)
2553 if (textD->graphicsExposeQueue) {
2554 graphicExposeTranslationEntry *thisGEQEntry = textD->graphicsExposeQueue->next;
2555 if (thisGEQEntry) {
2556 *xOffset += thisGEQEntry->horizontal;
2557 *yOffset += thisGEQEntry->vertical;
2562 Boolean TextDPopGraphicExposeQueueEntry(textDisp *textD)
2564 graphicExposeTranslationEntry *removedGEQEntry = textD->graphicsExposeQueue;
2566 if (removedGEQEntry) {
2567 textD->graphicsExposeQueue = removedGEQEntry->next;
2568 XtFree((char *)removedGEQEntry);
2570 return(removedGEQEntry?True:False);
2573 void TextDTranlateGraphicExposeQueue(textDisp *textD, int xOffset, int yOffset, Boolean appendEntry)
2575 graphicExposeTranslationEntry *newGEQEntry = NULL;
2576 if (appendEntry) {
2577 newGEQEntry = (graphicExposeTranslationEntry *)XtMalloc(sizeof(graphicExposeTranslationEntry));
2578 newGEQEntry->next = NULL;
2579 newGEQEntry->horizontal = xOffset;
2580 newGEQEntry->vertical = yOffset;
2582 if (textD->graphicsExposeQueue) {
2583 graphicExposeTranslationEntry *iter = textD->graphicsExposeQueue;
2584 while (iter->next) {
2585 iter->next->horizontal += xOffset;
2586 iter->next->vertical += yOffset;
2587 iter = iter->next;
2589 if (appendEntry) {
2590 iter->next = (struct graphicExposeTranslationEntry *)newGEQEntry;
2593 else {
2594 if (appendEntry) {
2595 textD->graphicsExposeQueue = newGEQEntry;
2600 static void setScroll(textDisp *textD, int topLineNum, int horizOffset,
2601 int updateVScrollBar, int updateHScrollBar)
2603 int fontHeight = textD->ascent + textD->descent;
2604 int origHOffset = textD->horizOffset;
2605 int lineDelta = textD->topLineNum - topLineNum;
2606 int xOffset, yOffset, srcX, srcY, dstX, dstY, width, height;
2607 int exactHeight = textD->height - textD->height %
2608 (textD->ascent + textD->descent);
2610 /* Do nothing if scroll position hasn't actually changed or there's no
2611 window to draw in yet */
2612 if (XtWindow(textD->w) == 0 || (textD->horizOffset == horizOffset &&
2613 textD->topLineNum == topLineNum))
2614 return;
2616 /* If part of the cursor is protruding beyond the text clipping region,
2617 clear it off */
2618 blankCursorProtrusions(textD);
2620 /* If the vertical scroll position has changed, update the line
2621 starts array and related counters in the text display */
2622 offsetLineStarts(textD, topLineNum);
2624 /* Just setting textD->horizOffset is enough information for redisplay */
2625 textD->horizOffset = horizOffset;
2627 /* Update the scroll bar positions if requested, note: updating the
2628 horizontal scroll bars can have the further side-effect of changing
2629 the horizontal scroll position, textD->horizOffset */
2630 if (updateVScrollBar && textD->vScrollBar != NULL) {
2631 updateVScrollBarRange(textD);
2633 if (updateHScrollBar && textD->hScrollBar != NULL) {
2634 updateHScrollBarRange(textD);
2637 /* Redisplay everything if the window is partially obscured (since
2638 it's too hard to tell what displayed areas are salvageable) or
2639 if there's nothing to recover because the scroll distance is large */
2640 xOffset = origHOffset - textD->horizOffset;
2641 yOffset = lineDelta * fontHeight;
2642 if (textD->visibility != VisibilityUnobscured ||
2643 abs(xOffset) > textD->width || abs(yOffset) > exactHeight) {
2644 TextDTranlateGraphicExposeQueue(textD, xOffset, yOffset, False);
2645 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
2646 textD->height);
2647 } else {
2648 /* If the window is not obscured, paint most of the window using XCopyArea
2649 from existing displayed text, and redraw only what's necessary */
2650 /* Recover the useable window areas by moving to the proper location */
2651 srcX = textD->left + (xOffset >= 0 ? 0 : -xOffset);
2652 dstX = textD->left + (xOffset >= 0 ? xOffset : 0);
2653 width = textD->width - abs(xOffset);
2654 srcY = textD->top + (yOffset >= 0 ? 0 : -yOffset);
2655 dstY = textD->top + (yOffset >= 0 ? yOffset : 0);
2656 height = exactHeight - abs(yOffset);
2657 resetClipRectangles(textD);
2658 TextDTranlateGraphicExposeQueue(textD, xOffset, yOffset, True);
2659 XCopyArea(XtDisplay(textD->w), XtWindow(textD->w), XtWindow(textD->w),
2660 textD->gc, srcX, srcY, width, height, dstX, dstY);
2661 /* redraw the un-recoverable parts */
2662 if (yOffset > 0) {
2663 TextDRedisplayRect(textD, textD->left, textD->top,
2664 textD->width, yOffset);
2666 else if (yOffset < 0) {
2667 TextDRedisplayRect(textD, textD->left, textD->top +
2668 textD->height + yOffset, textD->width, -yOffset);
2670 if (xOffset > 0) {
2671 TextDRedisplayRect(textD, textD->left, textD->top,
2672 xOffset, textD->height);
2674 else if (xOffset < 0) {
2675 TextDRedisplayRect(textD, textD->left + textD->width + xOffset,
2676 textD->top, -xOffset, textD->height);
2678 /* Restore protruding parts of the cursor */
2679 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
2682 /* Refresh line number/calltip display if its up and we've scrolled
2683 vertically */
2684 if (lineDelta != 0) {
2685 redrawLineNumbers(textD, False);
2686 TextDRedrawCalltip(textD, 0);
2689 HandleAllPendingGraphicsExposeNoExposeEvents((TextWidget)textD->w, NULL);
2693 ** Update the minimum, maximum, slider size, page increment, and value
2694 ** for vertical scroll bar.
2696 static void updateVScrollBarRange(textDisp *textD)
2698 int sliderSize, sliderMax, sliderValue;
2700 if (textD->vScrollBar == NULL)
2701 return;
2703 /* The Vert. scroll bar value and slider size directly represent the top
2704 line number, and the number of visible lines respectively. The scroll
2705 bar maximum value is chosen to generally represent the size of the whole
2706 buffer, with minor adjustments to keep the scroll bar widget happy */
2707 sliderSize = max(textD->nVisibleLines, 1); /* Avoid X warning (size < 1) */
2708 sliderValue = textD->topLineNum;
2709 sliderMax = max(textD->nBufferLines + 2 +
2710 TEXT_OF_TEXTD(textD).cursorVPadding,
2711 sliderSize + sliderValue);
2712 XtVaSetValues(textD->vScrollBar,
2713 XmNmaximum, sliderMax,
2714 XmNsliderSize, sliderSize,
2715 XmNpageIncrement, max(1, textD->nVisibleLines - 1),
2716 XmNvalue, sliderValue, NULL);
2720 ** Update the minimum, maximum, slider size, page increment, and value
2721 ** for the horizontal scroll bar. If scroll position is such that there
2722 ** is blank space to the right of all lines of text, scroll back (adjust
2723 ** horizOffset but don't redraw) to take up the slack and position the
2724 ** right edge of the text at the right edge of the display.
2726 ** Note, there is some cost to this routine, since it scans the whole range
2727 ** of displayed text, particularly since it's usually called for each typed
2728 ** character!
2730 static int updateHScrollBarRange(textDisp *textD)
2732 int i, maxWidth = 0, sliderMax, sliderWidth;
2733 int origHOffset = textD->horizOffset;
2735 if (textD->hScrollBar == NULL || !XtIsManaged(textD->hScrollBar))
2736 return False;
2738 /* Scan all the displayed lines to find the width of the longest line */
2739 for (i=0; i<textD->nVisibleLines && textD->lineStarts[i]!= -1; i++)
2740 maxWidth = max(measureVisLine(textD, i), maxWidth);
2742 /* If the scroll position is beyond what's necessary to keep all lines
2743 in view, scroll to the left to bring the end of the longest line to
2744 the right margin */
2745 if (maxWidth < textD->width + textD->horizOffset && textD->horizOffset > 0)
2746 textD->horizOffset = max(0, maxWidth - textD->width);
2748 /* Readjust the scroll bar */
2749 sliderWidth = textD->width;
2750 sliderMax = max(maxWidth, sliderWidth + textD->horizOffset);
2751 XtVaSetValues(textD->hScrollBar,
2752 XmNmaximum, sliderMax,
2753 XmNsliderSize, sliderWidth,
2754 XmNpageIncrement, max(textD->width - 100, 10),
2755 XmNvalue, textD->horizOffset, NULL);
2757 /* Return True if scroll position was changed */
2758 return origHOffset != textD->horizOffset;
2762 ** Define area for drawing line numbers. A width of 0 disables line
2763 ** number drawing.
2765 void TextDSetLineNumberArea(textDisp *textD, int lineNumLeft, int lineNumWidth,
2766 int textLeft)
2768 int newWidth = textD->width + textD->left - textLeft;
2769 textD->lineNumLeft = lineNumLeft;
2770 textD->lineNumWidth = lineNumWidth;
2771 textD->left = textLeft;
2772 XClearWindow(XtDisplay(textD->w), XtWindow(textD->w));
2773 resetAbsLineNum(textD);
2774 TextDResize(textD, newWidth, textD->height);
2775 TextDRedisplayRect(textD, 0, textD->top, INT_MAX, textD->height);
2779 ** Refresh the line number area. If clearAll is False, writes only over
2780 ** the character cell areas. Setting clearAll to True will clear out any
2781 ** stray marks outside of the character cell area, which might have been
2782 ** left from before a resize or font change.
2784 static void redrawLineNumbers(textDisp *textD, int clearAll)
2786 int y, line, visLine, nCols, lineStart;
2787 char lineNumString[12];
2788 int lineHeight = textD->ascent + textD->descent;
2789 int charWidth = textD->fontStruct->max_bounds.width;
2790 XRectangle clipRect;
2791 Display *display = XtDisplay(textD->w);
2793 /* Don't draw if lineNumWidth == 0 (line numbers are hidden), or widget is
2794 not yet realized */
2795 if (textD->lineNumWidth == 0 || XtWindow(textD->w) == 0)
2796 return;
2798 /* Make sure we reset the clipping range for the line numbers GC, because
2799 the GC may be shared (eg, if the line numbers and text have the same
2800 color) and therefore the clipping ranges may be invalid. */
2801 clipRect.x = textD->lineNumLeft;
2802 clipRect.y = textD->top;
2803 clipRect.width = textD->lineNumWidth;
2804 clipRect.height = textD->height;
2805 XSetClipRectangles(display, textD->lineNumGC, 0, 0,
2806 &clipRect, 1, Unsorted);
2808 /* Erase the previous contents of the line number area, if requested */
2809 if (clearAll)
2810 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->lineNumLeft,
2811 textD->top, textD->lineNumWidth, textD->height, False);
2813 /* Draw the line numbers, aligned to the text */
2814 nCols = min(11, textD->lineNumWidth / charWidth);
2815 y = textD->top;
2816 line = getAbsTopLineNum(textD);
2817 for (visLine=0; visLine < textD->nVisibleLines; visLine++) {
2818 lineStart = textD->lineStarts[visLine];
2819 if (lineStart != -1 && (lineStart==0 ||
2820 BufGetCharacter(textD->buffer, lineStart-1)=='\n')) {
2821 sprintf(lineNumString, "%*d", nCols, line);
2822 XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w),
2823 textD->lineNumGC, textD->lineNumLeft, y + textD->ascent,
2824 lineNumString, strlen(lineNumString));
2825 line++;
2826 } else {
2827 XClearArea(XtDisplay(textD->w), XtWindow(textD->w),
2828 textD->lineNumLeft, y, textD->lineNumWidth,
2829 textD->ascent + textD->descent, False);
2830 if (visLine == 0)
2831 line++;
2833 y += lineHeight;
2838 ** Callbacks for drag or valueChanged on scroll bars
2840 static void vScrollCB(Widget w, XtPointer clientData, XtPointer callData)
2842 textDisp *textD = (textDisp *)clientData;
2843 int newValue = ((XmScrollBarCallbackStruct *)callData)->value;
2844 int lineDelta = newValue - textD->topLineNum;
2846 if (lineDelta == 0)
2847 return;
2848 setScroll(textD, newValue, textD->horizOffset, False, True);
2850 static void hScrollCB(Widget w, XtPointer clientData, XtPointer callData)
2852 textDisp *textD = (textDisp *)clientData;
2853 int newValue = ((XmScrollBarCallbackStruct *)callData)->value;
2855 if (newValue == textD->horizOffset)
2856 return;
2857 setScroll(textD, textD->topLineNum, newValue, False, False);
2860 static void visibilityEH(Widget w, XtPointer data, XEvent *event,
2861 Boolean *continueDispatch)
2863 /* Record whether the window is fully visible or not. This information
2864 is used for choosing the scrolling methodology for optimal performance,
2865 if the window is partially obscured, XCopyArea may not work */
2866 ((textDisp *)data)->visibility = ((XVisibilityEvent *)event)->state;
2869 static int max(int i1, int i2)
2871 return i1 >= i2 ? i1 : i2;
2874 static int min(int i1, int i2)
2876 return i1 <= i2 ? i1 : i2;
2880 ** Count the number of newlines in a null-terminated text string;
2882 static int countLines(char *string)
2884 char *c;
2885 int lineCount = 0;
2887 if (string == NULL)
2888 return 0;
2889 for (c=string; *c!='\0'; c++)
2890 if (*c == '\n') lineCount++;
2891 return lineCount;
2895 ** Return the width in pixels of the displayed line pointed to by "visLineNum"
2897 static int measureVisLine(textDisp *textD, int visLineNum)
2899 int i, width = 0, len, style, lineLen = visLineLength(textD, visLineNum);
2900 int charCount = 0, lineStartPos = textD->lineStarts[visLineNum];
2901 char expandedChar[MAX_EXP_CHAR_LEN];
2903 if (textD->styleBuffer == NULL) {
2904 for (i=0; i<lineLen; i++) {
2905 len = BufGetExpandedChar(textD->buffer, lineStartPos + i,
2906 charCount, expandedChar);
2907 width += XTextWidth(textD->fontStruct, expandedChar, len);
2908 charCount += len;
2910 } else {
2911 for (i=0; i<lineLen; i++) {
2912 len = BufGetExpandedChar(textD->buffer, lineStartPos+i,
2913 charCount, expandedChar);
2914 style = (unsigned char)BufGetCharacter(textD->styleBuffer,
2915 lineStartPos+i) - ASCII_A;
2916 width += XTextWidth(textD->styleTable[style].font, expandedChar,
2917 len);
2918 charCount += len;
2921 return width;
2925 ** Return true if there are lines visible with no corresponding buffer text
2927 static int emptyLinesVisible(textDisp *textD)
2929 return textD->nVisibleLines > 0 &&
2930 textD->lineStarts[textD->nVisibleLines-1] == -1;
2934 ** When the cursor is at the left or right edge of the text, part of it
2935 ** sticks off into the clipped region beyond the text. Normal redrawing
2936 ** can not overwrite this protruding part of the cursor, so it must be
2937 ** erased independently by calling this routine.
2939 static void blankCursorProtrusions(textDisp *textD)
2941 int x, width, cursorX = textD->cursorX, cursorY = textD->cursorY;
2942 int fontWidth = textD->fontStruct->max_bounds.width;
2943 int fontHeight = textD->ascent + textD->descent;
2944 int cursorWidth, left = textD->left, right = left + textD->width;
2946 cursorWidth = (fontWidth/3) * 2;
2947 if (cursorX >= left-1 && cursorX <= left + cursorWidth/2 - 1) {
2948 x = cursorX - cursorWidth/2;
2949 width = left - x;
2950 } else if (cursorX >= right - cursorWidth/2 && cursorX <= right) {
2951 x = right;
2952 width = cursorX + cursorWidth/2 + 2 - right;
2953 } else
2954 return;
2956 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), x, cursorY,
2957 width, fontHeight, False);
2961 ** Allocate shared graphics contexts used by the widget, which must be
2962 ** re-allocated on a font change.
2964 static void allocateFixedFontGCs(textDisp *textD, XFontStruct *fontStruct,
2965 Pixel bgPixel, Pixel fgPixel, Pixel selectFGPixel, Pixel selectBGPixel,
2966 Pixel highlightFGPixel, Pixel highlightBGPixel, Pixel lineNumFGPixel)
2968 textD->gc = allocateGC(textD->w, GCFont | GCForeground | GCBackground,
2969 fgPixel, bgPixel, fontStruct->fid, GCClipMask, GCArcMode);
2970 textD->selectGC = allocateGC(textD->w, GCFont | GCForeground | GCBackground,
2971 selectFGPixel, selectBGPixel, fontStruct->fid, GCClipMask,
2972 GCArcMode);
2973 textD->selectBGGC = allocateGC(textD->w, GCForeground, selectBGPixel, 0,
2974 fontStruct->fid, GCClipMask, GCArcMode);
2975 textD->highlightGC = allocateGC(textD->w, GCFont|GCForeground|GCBackground,
2976 highlightFGPixel, highlightBGPixel, fontStruct->fid, GCClipMask,
2977 GCArcMode);
2978 textD->highlightBGGC = allocateGC(textD->w, GCForeground, highlightBGPixel,
2979 0, fontStruct->fid, GCClipMask, GCArcMode);
2980 textD->lineNumGC = allocateGC(textD->w, GCFont | GCForeground |
2981 GCBackground, lineNumFGPixel, bgPixel, fontStruct->fid,
2982 GCClipMask, GCArcMode);
2986 ** X11R4 does not have the XtAllocateGC function for sharing graphics contexts
2987 ** with changeable fields. Unfortunately the R4 call for creating shared
2988 ** graphics contexts (XtGetGC) is rarely useful because most widgets need
2989 ** to be able to set and change clipping, and that makes the GC unshareable.
2991 ** This function allocates and returns a gc, using XtAllocateGC if possible,
2992 ** or XCreateGC on X11R4 systems where XtAllocateGC is not available.
2994 static GC allocateGC(Widget w, unsigned long valueMask,
2995 unsigned long foreground, unsigned long background, Font font,
2996 unsigned long dynamicMask, unsigned long dontCareMask)
2998 XGCValues gcValues;
3000 gcValues.font = font;
3001 gcValues.background = background;
3002 gcValues.foreground = foreground;
3003 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4
3004 return XtAllocateGC(w, 0, valueMask, &gcValues, dynamicMask,
3005 dontCareMask);
3006 #else
3007 return XCreateGC(XtDisplay(w), RootWindowOfScreen(XtScreen(w)),
3008 valueMask, &gcValues);
3009 #endif
3013 ** Release a gc allocated with allocateGC above
3015 static void releaseGC(Widget w, GC gc)
3017 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4
3018 XtReleaseGC(w, gc);
3019 #else
3020 XFreeGC(XtDisplay(w), gc);
3021 #endif
3025 ** resetClipRectangles sets the clipping rectangles for GCs which clip
3026 ** at the text boundary (as opposed to the window boundary). These GCs
3027 ** are shared such that the drawing styles are constant, but the clipping
3028 ** rectangles are allowed to change among different users of the GCs (the
3029 ** GCs were created with XtAllocGC). This routine resets them so the clipping
3030 ** rectangles are correct for this text display.
3032 static void resetClipRectangles(textDisp *textD)
3034 XRectangle clipRect;
3035 Display *display = XtDisplay(textD->w);
3037 clipRect.x = textD->left;
3038 clipRect.y = textD->top;
3039 clipRect.width = textD->width;
3040 clipRect.height = textD->height - textD->height %
3041 (textD->ascent + textD->descent);
3043 XSetClipRectangles(display, textD->gc, 0, 0,
3044 &clipRect, 1, Unsorted);
3045 XSetClipRectangles(display, textD->selectGC, 0, 0,
3046 &clipRect, 1, Unsorted);
3047 XSetClipRectangles(display, textD->highlightGC, 0, 0,
3048 &clipRect, 1, Unsorted);
3049 XSetClipRectangles(display, textD->selectBGGC, 0, 0,
3050 &clipRect, 1, Unsorted);
3051 XSetClipRectangles(display, textD->highlightBGGC, 0, 0,
3052 &clipRect, 1, Unsorted);
3053 XSetClipRectangles(display, textD->styleGC, 0, 0,
3054 &clipRect, 1, Unsorted);
3058 ** Return the length of a line (number of displayable characters) by examining
3059 ** entries in the line starts array rather than by scanning for newlines
3061 static int visLineLength(textDisp *textD, int visLineNum)
3063 int nextLineStart, lineStartPos = textD->lineStarts[visLineNum];
3065 if (lineStartPos == -1)
3066 return 0;
3067 if (visLineNum+1 >= textD->nVisibleLines)
3068 return textD->lastChar - lineStartPos;
3069 nextLineStart = textD->lineStarts[visLineNum+1];
3070 if (nextLineStart == -1)
3071 return textD->lastChar - lineStartPos;
3072 if (wrapUsesCharacter(textD, nextLineStart-1))
3073 return nextLineStart-1 - lineStartPos;
3074 return nextLineStart - lineStartPos;
3078 ** When continuous wrap is on, and the user inserts or deletes characters,
3079 ** wrapping can happen before and beyond the changed position. This routine
3080 ** finds the extent of the changes, and counts the deleted and inserted lines
3081 ** over that range. It also attempts to minimize the size of the range to
3082 ** what has to be counted and re-displayed, so the results can be useful
3083 ** both for delimiting where the line starts need to be recalculated, and
3084 ** for deciding what part of the text to redisplay.
3086 static void findWrapRange(textDisp *textD, char *deletedText, int pos,
3087 int nInserted, int nDeleted, int *modRangeStart, int *modRangeEnd,
3088 int *linesInserted, int *linesDeleted)
3090 int length, retPos, retLines, retLineStart, retLineEnd;
3091 textBuffer *deletedTextBuf, *buf = textD->buffer;
3092 int nVisLines = textD->nVisibleLines;
3093 int *lineStarts = textD->lineStarts;
3094 int countFrom, countTo, lineStart, adjLineStart, i;
3095 int visLineNum = 0, nLines = 0;
3098 ** Determine where to begin searching: either the previous newline, or
3099 ** if possible, limit to the start of the (original) previous displayed
3100 ** line, using information from the existing line starts array
3102 if (pos >= textD->firstChar && pos <= textD->lastChar) {
3103 for (i=nVisLines-1; i>0; i--)
3104 if (lineStarts[i] != -1 && pos >= lineStarts[i])
3105 break;
3106 if (i > 0) {
3107 countFrom = lineStarts[i-1];
3108 visLineNum = i-1;
3109 } else
3110 countFrom = BufStartOfLine(buf, pos);
3111 } else
3112 countFrom = BufStartOfLine(buf, pos);
3116 ** Move forward through the (new) text one line at a time, counting
3117 ** displayed lines, and looking for either a real newline, or for the
3118 ** line starts to re-sync with the original line starts array
3120 lineStart = countFrom;
3121 *modRangeStart = countFrom;
3122 while (True) {
3124 /* advance to the next line. If the line ended in a real newline
3125 or the end of the buffer, that's far enough */
3126 wrappedLineCounter(textD, buf, lineStart, buf->length, 1, True, 0,
3127 &retPos, &retLines, &retLineStart, &retLineEnd);
3128 if (retPos >= buf->length) {
3129 countTo = buf->length;
3130 *modRangeEnd = countTo;
3131 if (retPos != retLineEnd)
3132 nLines++;
3133 break;
3134 } else
3135 lineStart = retPos;
3136 nLines++;
3137 if (lineStart > pos + nInserted &&
3138 BufGetCharacter(buf, lineStart-1) == '\n') {
3139 countTo = lineStart;
3140 *modRangeEnd = lineStart;
3141 break;
3144 /* Don't try to resync in continuous wrap mode with non-fixed font
3145 sizes; it would result in a chicken-and-egg dependency between
3146 the calculations for the inserted and the deleted lines.
3147 If we're in that mode, the number of deleted lines is calculated in
3148 advance, without resynchronization, so we shouldn't resynchronize
3149 for the inserted lines either. */
3150 if (textD->suppressResync)
3151 continue;
3153 /* check for synchronization with the original line starts array
3154 before pos, if so, the modified range can begin later */
3155 if (lineStart <= pos) {
3156 while (visLineNum<nVisLines && lineStarts[visLineNum] < lineStart)
3157 visLineNum++;
3158 if (visLineNum < nVisLines && lineStarts[visLineNum] == lineStart) {
3159 countFrom = lineStart;
3160 nLines = 0;
3161 if (visLineNum+1 < nVisLines && lineStarts[visLineNum+1] != -1)
3162 *modRangeStart = min(pos, lineStarts[visLineNum+1]-1);
3163 else
3164 *modRangeStart = countFrom;
3165 } else
3166 *modRangeStart = min(*modRangeStart, lineStart-1);
3169 /* check for synchronization with the original line starts array
3170 after pos, if so, the modified range can end early */
3171 else if (lineStart > pos + nInserted) {
3172 adjLineStart = lineStart - nInserted + nDeleted;
3173 while (visLineNum<nVisLines && lineStarts[visLineNum]<adjLineStart)
3174 visLineNum++;
3175 if (visLineNum < nVisLines && lineStarts[visLineNum] != -1 &&
3176 lineStarts[visLineNum] == adjLineStart) {
3177 countTo = TextDEndOfLine(textD, lineStart, True);
3178 *modRangeEnd = lineStart;
3179 break;
3183 *linesInserted = nLines;
3186 /* Count deleted lines between countFrom and countTo as the text existed
3187 before the modification (that is, as if the text between pos and
3188 pos+nInserted were replaced by "deletedText"). This extra context is
3189 necessary because wrapping can occur outside of the modified region
3190 as a result of adding or deleting text in the region. This is done by
3191 creating a textBuffer containing the deleted text and the necessary
3192 additional context, and calling the wrappedLineCounter on it.
3194 NOTE: This must not be done in continuous wrap mode when the font
3195 width is not fixed. In that case, the calculation would try
3196 to access style information that is no longer available (deleted
3197 text), or out of date (updated highlighting), possibly leading
3198 to completely wrong calculations and/or even crashes eventually.
3199 (This is not theoretical; it really happened.)
3201 In that case, the calculation of the number of deleted lines
3202 has happened before the buffer was modified (only in that case,
3203 because resynchronization of the line starts is impossible
3204 in that case, which makes the whole calculation less efficient).
3206 if (textD->suppressResync) {
3207 *linesDeleted = textD->nLinesDeleted;
3208 textD->suppressResync = 0;
3209 return;
3212 length = (pos-countFrom) + nDeleted +(countTo-(pos+nInserted));
3213 deletedTextBuf = BufCreatePreallocated(length);
3214 if (pos > countFrom)
3215 BufCopyFromBuf(textD->buffer, deletedTextBuf, countFrom, pos, 0);
3216 if (nDeleted != 0)
3217 BufInsert(deletedTextBuf, pos-countFrom, deletedText);
3218 if (countTo > pos+nInserted)
3219 BufCopyFromBuf(textD->buffer, deletedTextBuf,
3220 pos+nInserted, countTo, pos-countFrom+nDeleted);
3221 /* Note that we need to take into account an offset for the style buffer:
3222 the deletedTextBuf can be out of sync with the style buffer. */
3223 wrappedLineCounter(textD, deletedTextBuf, 0, length, INT_MAX, True,
3224 countFrom, &retPos, &retLines, &retLineStart, &retLineEnd);
3225 BufFree(deletedTextBuf);
3226 *linesDeleted = retLines;
3227 textD->suppressResync = 0;
3231 ** This is a stripped-down version of the findWrapRange() function above,
3232 ** intended to be used to calculate the number of "deleted" lines during
3233 ** a buffer modification. It is called _before_ the modification takes place.
3235 ** This function should only be called in continuous wrap mode with a
3236 ** non-fixed font width. In that case, it is impossible to calculate
3237 ** the number of deleted lines, because the necessary style information
3238 ** is no longer available _after_ the modification. In other cases, we
3239 ** can still perform the calculation afterwards (possibly even more
3240 ** efficiently).
3242 static void measureDeletedLines(textDisp *textD, int pos, int nDeleted)
3244 int retPos, retLines, retLineStart, retLineEnd;
3245 textBuffer *buf = textD->buffer;
3246 int nVisLines = textD->nVisibleLines;
3247 int *lineStarts = textD->lineStarts;
3248 int countFrom, lineStart;
3249 int nLines = 0, i;
3251 ** Determine where to begin searching: either the previous newline, or
3252 ** if possible, limit to the start of the (original) previous displayed
3253 ** line, using information from the existing line starts array
3255 if (pos >= textD->firstChar && pos <= textD->lastChar) {
3256 for (i=nVisLines-1; i>0; i--)
3257 if (lineStarts[i] != -1 && pos >= lineStarts[i])
3258 break;
3259 if (i > 0) {
3260 countFrom = lineStarts[i-1];
3261 } else
3262 countFrom = BufStartOfLine(buf, pos);
3263 } else
3264 countFrom = BufStartOfLine(buf, pos);
3267 ** Move forward through the (new) text one line at a time, counting
3268 ** displayed lines, and looking for either a real newline, or for the
3269 ** line starts to re-sync with the original line starts array
3271 lineStart = countFrom;
3272 while (True) {
3273 /* advance to the next line. If the line ended in a real newline
3274 or the end of the buffer, that's far enough */
3275 wrappedLineCounter(textD, buf, lineStart, buf->length, 1, True, 0,
3276 &retPos, &retLines, &retLineStart, &retLineEnd);
3277 if (retPos >= buf->length) {
3278 if (retPos != retLineEnd)
3279 nLines++;
3280 break;
3281 } else
3282 lineStart = retPos;
3283 nLines++;
3284 if (lineStart > pos + nDeleted &&
3285 BufGetCharacter(buf, lineStart-1) == '\n') {
3286 break;
3289 /* Unlike in the findWrapRange() function above, we don't try to
3290 resync with the line starts, because we don't know the length
3291 of the inserted text yet, nor the updated style information.
3293 Because of that, we also shouldn't resync with the line starts
3294 after the modification either, because we must perform the
3295 calculations for the deleted and inserted lines in the same way.
3297 This can result in some unnecessary recalculation and redrawing
3298 overhead, and therefore we should only use this two-phase mode
3299 of calculation when it's really needed (continuous wrap + variable
3300 font width). */
3302 textD->nLinesDeleted = nLines;
3303 textD->suppressResync = 1;
3307 ** Count forward from startPos to either maxPos or maxLines (whichever is
3308 ** reached first), and return all relevant positions and line count.
3309 ** The provided textBuffer may differ from the actual text buffer of the
3310 ** widget. In that case it must be a (partial) copy of the actual text buffer
3311 ** and the styleBufOffset argument must indicate the starting position of the
3312 ** copy, to take into account the correct style information.
3314 ** Returned values:
3316 ** retPos: Position where counting ended. When counting lines, the
3317 ** position returned is the start of the line "maxLines"
3318 ** lines beyond "startPos".
3319 ** retLines: Number of line breaks counted
3320 ** retLineStart: Start of the line where counting ended
3321 ** retLineEnd: End position of the last line traversed
3323 static void wrappedLineCounter(textDisp *textD, textBuffer *buf, int startPos,
3324 int maxPos, int maxLines, int startPosIsLineStart, int styleBufOffset,
3325 int *retPos, int *retLines, int *retLineStart, int *retLineEnd)
3327 int lineStart, newLineStart = 0, b, p, colNum, wrapMargin;
3328 int maxWidth, width, countPixels, i, foundBreak;
3329 int nLines = 0, tabDist = textD->buffer->tabDist;
3330 unsigned char c;
3331 char nullSubsChar = textD->buffer->nullSubsChar;
3333 /* If the font is fixed, or there's a wrap margin set, it's more efficient
3334 to measure in columns, than to count pixels. Determine if we can count
3335 in columns (countPixels == False) or must count pixels (countPixels ==
3336 True), and set the wrap target for either pixels or columns */
3337 if (textD->fixedFontWidth != -1 || textD->wrapMargin != 0) {
3338 countPixels = False;
3339 wrapMargin = textD->wrapMargin != 0 ? textD->wrapMargin :
3340 textD->width / textD->fixedFontWidth;
3341 maxWidth = INT_MAX;
3342 } else {
3343 countPixels = True;
3344 wrapMargin = INT_MAX;
3345 maxWidth = textD->width;
3348 /* Find the start of the line if the start pos is not marked as a
3349 line start. */
3350 if (startPosIsLineStart)
3351 lineStart = startPos;
3352 else
3353 lineStart = TextDStartOfLine(textD, startPos);
3356 ** Loop until position exceeds maxPos or line count exceeds maxLines.
3357 ** (actually, contines beyond maxPos to end of line containing maxPos,
3358 ** in case later characters cause a word wrap back before maxPos)
3360 colNum = 0;
3361 width = 0;
3362 for (p=lineStart; p<buf->length; p++) {
3363 c = BufGetCharacter(buf, p);
3365 /* If the character was a newline, count the line and start over,
3366 otherwise, add it to the width and column counts */
3367 if (c == '\n') {
3368 if (p >= maxPos) {
3369 *retPos = maxPos;
3370 *retLines = nLines;
3371 *retLineStart = lineStart;
3372 *retLineEnd = maxPos;
3373 return;
3375 nLines++;
3376 if (nLines >= maxLines) {
3377 *retPos = p + 1;
3378 *retLines = nLines;
3379 *retLineStart = p + 1;
3380 *retLineEnd = p;
3381 return;
3383 lineStart = p + 1;
3384 colNum = 0;
3385 width = 0;
3386 } else {
3387 colNum += BufCharWidth(c, colNum, tabDist, nullSubsChar);
3388 if (countPixels)
3389 width += measurePropChar(textD, c, colNum, p+styleBufOffset);
3392 /* If character exceeded wrap margin, find the break point
3393 and wrap there */
3394 if (colNum > wrapMargin || width > maxWidth) {
3395 foundBreak = False;
3396 for (b=p; b>=lineStart; b--) {
3397 c = BufGetCharacter(buf, b);
3398 if (c == '\t' || c == ' ') {
3399 newLineStart = b + 1;
3400 if (countPixels) {
3401 colNum = 0;
3402 width = 0;
3403 for (i=b+1; i<p+1; i++) {
3404 width += measurePropChar(textD,
3405 BufGetCharacter(buf, i), colNum,
3406 i+styleBufOffset);
3407 colNum++;
3409 } else
3410 colNum = BufCountDispChars(buf, b+1, p+1);
3411 foundBreak = True;
3412 break;
3415 if (!foundBreak) { /* no whitespace, just break at margin */
3416 newLineStart = max(p, lineStart+1);
3417 colNum = BufCharWidth(c, colNum, tabDist, nullSubsChar);
3418 if (countPixels)
3419 width = measurePropChar(textD, c, colNum, p+styleBufOffset);
3421 if (p >= maxPos) {
3422 *retPos = maxPos;
3423 *retLines = maxPos < newLineStart ? nLines : nLines + 1;
3424 *retLineStart = maxPos < newLineStart ? lineStart :
3425 newLineStart;
3426 *retLineEnd = maxPos;
3427 return;
3429 nLines++;
3430 if (nLines >= maxLines) {
3431 *retPos = foundBreak ? b + 1 : max(p, lineStart+1);
3432 *retLines = nLines;
3433 *retLineStart = lineStart;
3434 *retLineEnd = foundBreak ? b : p;
3435 return;
3437 lineStart = newLineStart;
3441 /* reached end of buffer before reaching pos or line target */
3442 *retPos = buf->length;
3443 *retLines = nLines;
3444 *retLineStart = lineStart;
3445 *retLineEnd = buf->length;
3449 ** Measure the width in pixels of a character "c" at a particular column
3450 ** "colNum" and buffer position "pos". This is for measuring characters in
3451 ** proportional or mixed-width highlighting fonts.
3453 ** A note about proportional and mixed-width fonts: the mixed width and
3454 ** proportional font code in nedit does not get much use in general editing,
3455 ** because nedit doesn't allow per-language-mode fonts, and editing programs
3456 ** in a proportional font is usually a bad idea, so very few users would
3457 ** choose a proportional font as a default. There are still probably mixed-
3458 ** width syntax highlighting cases where things don't redraw properly for
3459 ** insertion/deletion, though static display and wrapping and resizing
3460 ** should now be solid because they are now used for online help display.
3462 static int measurePropChar(textDisp *textD, char c, int colNum, int pos)
3464 int charLen, style;
3465 char expChar[MAX_EXP_CHAR_LEN];
3466 textBuffer *styleBuf = textD->styleBuffer;
3468 charLen = BufExpandCharacter(c, colNum, expChar,
3469 textD->buffer->tabDist, textD->buffer->nullSubsChar);
3470 if (styleBuf == NULL) {
3471 style = 0;
3472 } else {
3473 style = (unsigned char)BufGetCharacter(styleBuf, pos);
3474 if (style == textD->unfinishedStyle) {
3475 /* encountered "unfinished" style, trigger parsing */
3476 (textD->unfinishedHighlightCB)(textD, pos, textD->highlightCBArg);
3477 style = (unsigned char)BufGetCharacter(styleBuf, pos);
3480 return stringWidth(textD, expChar, charLen, style);
3484 ** Finds both the end of the current line and the start of the next line. Why?
3485 ** In continuous wrap mode, if you need to know both, figuring out one from the
3486 ** other can be expensive or error prone. The problem comes when there's a
3487 ** trailing space or tab just before the end of the buffer. To translate an
3488 ** end of line value to or from the next lines start value, you need to know
3489 ** whether the trailing space or tab is being used as a line break or just a
3490 ** normal character, and to find that out would otherwise require counting all
3491 ** the way back to the beginning of the line.
3493 static void findLineEnd(textDisp *textD, int startPos, int startPosIsLineStart,
3494 int *lineEnd, int *nextLineStart)
3496 int retLines, retLineStart;
3498 /* if we're not wrapping use more efficient BufEndOfLine */
3499 if (!textD->continuousWrap) {
3500 *lineEnd = BufEndOfLine(textD->buffer, startPos);
3501 *nextLineStart = min(textD->buffer->length, *lineEnd + 1);
3502 return;
3505 /* use the wrapped line counter routine to count forward one line */
3506 wrappedLineCounter(textD, textD->buffer, startPos, textD->buffer->length,
3507 1, startPosIsLineStart, 0, nextLineStart, &retLines,
3508 &retLineStart, lineEnd);
3509 return;
3513 ** Line breaks in continuous wrap mode usually happen at newlines or
3514 ** whitespace. This line-terminating character is not included in line
3515 ** width measurements and has a special status as a non-visible character.
3516 ** However, lines with no whitespace are wrapped without the benefit of a
3517 ** line terminating character, and this distinction causes endless trouble
3518 ** with all of the text display code which was originally written without
3519 ** continuous wrap mode and always expects to wrap at a newline character.
3521 ** Given the position of the end of the line, as returned by TextDEndOfLine
3522 ** or BufEndOfLine, this returns true if there is a line terminating
3523 ** character, and false if there's not. On the last character in the
3524 ** buffer, this function can't tell for certain whether a trailing space was
3525 ** used as a wrap point, and just guesses that it wasn't. So if an exact
3526 ** accounting is necessary, don't use this function.
3528 static int wrapUsesCharacter(textDisp *textD, int lineEndPos)
3530 char c;
3532 if (!textD->continuousWrap || lineEndPos == textD->buffer->length)
3533 return True;
3535 c = BufGetCharacter(textD->buffer, lineEndPos);
3536 return c == '\n' || ((c == '\t' || c == ' ') &&
3537 lineEndPos + 1 != textD->buffer->length);
3541 ** Decide whether the user needs (or may need) a horizontal scroll bar,
3542 ** and manage or unmanage the scroll bar widget accordingly. The H.
3543 ** scroll bar is only hidden in continuous wrap mode when it's absolutely
3544 ** certain that the user will not need it: when wrapping is set
3545 ** to the window edge, or when the wrap margin is strictly less than
3546 ** the longest possible line.
3548 static void hideOrShowHScrollBar(textDisp *textD)
3550 if (textD->continuousWrap && (textD->wrapMargin == 0 || textD->wrapMargin *
3551 textD->fontStruct->max_bounds.width < textD->width))
3552 XtUnmanageChild(textD->hScrollBar);
3553 else
3554 XtManageChild(textD->hScrollBar);
3558 ** Return true if the selection "sel" is rectangular, and touches a
3559 ** buffer position withing "rangeStart" to "rangeEnd"
3561 static int rangeTouchesRectSel(selection *sel, int rangeStart, int rangeEnd)
3563 return sel->selected && sel->rectangular && sel->end >= rangeStart &&
3564 sel->start <= rangeEnd;
3568 ** Extend the range of a redraw request (from *start to *end) with additional
3569 ** redraw requests resulting from changes to the attached style buffer (which
3570 ** contains auxiliary information for coloring or styling text).
3572 static void extendRangeForStyleMods(textDisp *textD, int *start, int *end)
3574 selection *sel = &textD->styleBuffer->primary;
3575 int extended = False;
3577 /* The peculiar protocol used here is that modifications to the style
3578 buffer are marked by selecting them with the buffer's primary selection.
3579 The style buffer is usually modified in response to a modify callback on
3580 the text buffer BEFORE textDisp.c's modify callback, so that it can keep
3581 the style buffer in step with the text buffer. The style-update
3582 callback can't just call for a redraw, because textDisp hasn't processed
3583 the original text changes yet. Anyhow, to minimize redrawing and to
3584 avoid the complexity of scheduling redraws later, this simple protocol
3585 tells the text display's buffer modify callback to extend it's redraw
3586 range to show the text color/and font changes as well. */
3587 if (sel->selected) {
3588 if (sel->start < *start) {
3589 *start = sel->start;
3590 extended = True;
3592 if (sel->end > *end) {
3593 *end = sel->end;
3594 extended = True;
3598 /* If the selection was extended due to a style change, and some of the
3599 fonts don't match in spacing, extend redraw area to end of line to
3600 redraw characters exposed by possible font size changes */
3601 if (textD->fixedFontWidth == -1 && extended)
3602 *end = BufEndOfLine(textD->buffer, *end) + 1;
3605 /********************** Backlight Functions ******************************/
3607 ** Allocate a read-only (shareable) colormap cell for a named color, from the
3608 ** the default colormap of the screen on which the widget (w) is displayed. If
3609 ** the colormap is full and there's no suitable substitute, print an error on
3610 ** stderr, and return the widget's background color as a backup.
3612 static Pixel allocBGColor(Widget w, char *colorName, int *ok)
3614 int r,g,b;
3615 *ok = 1;
3616 return AllocColor(w, colorName, &r, &g, &b);
3619 Pixel getRangesetColor(textDisp *textD, int ind, Pixel bground)
3621 textBuffer *buf;
3622 RangesetTable *tab;
3623 Pixel color;
3624 char *color_name;
3625 int valid;
3627 if (ind > 0) {
3628 ind--;
3629 buf = textD->buffer;
3630 tab = buf->rangesetTable;
3632 valid = RangesetTableGetColorValid(tab, ind, &color);
3633 if (valid == 0) {
3634 color_name = RangesetTableGetColorName(tab, ind);
3635 if (color_name)
3636 color = allocBGColor(textD->w, color_name, &valid);
3637 RangesetTableAssignColorPixel(tab, ind, color, valid);
3639 if (valid > 0) {
3640 return color;
3643 return bground;
3647 ** Read the background color class specification string in str, allocating the
3648 ** necessary colors, and allocating and setting up the character->class_no and
3649 ** class_no->pixel map arrays, returned via *pp_bgClass and *pp_bgClassPixel
3650 ** respectively.
3651 ** Note: the allocation of class numbers could be more intelligent: there can
3652 ** never be more than 256 of these (one per character); but I don't think
3653 ** there'll be a pressing need. I suppose the scanning of the specification
3654 ** could be better too, but then, who cares!
3656 void TextDSetupBGClasses(Widget w, XmString str, Pixel **pp_bgClassPixel,
3657 unsigned char **pp_bgClass, Pixel bgPixelDefault)
3659 unsigned char bgClass[256];
3660 Pixel bgClassPixel[256];
3661 int class_no = 0;
3662 char *semicol;
3663 char *s = (char *)str;
3664 size_t was_semicol;
3665 int lo, hi, dummy;
3666 char *pos;
3667 Boolean is_good = True;
3669 XtFree((char *)*pp_bgClass);
3670 XtFree((char *)*pp_bgClassPixel);
3672 *pp_bgClassPixel = NULL;
3673 *pp_bgClass = NULL;
3675 if (!s)
3676 return;
3678 /* default for all chars is class number zero, for standard background */
3679 memset(bgClassPixel, 0, sizeof bgClassPixel);
3680 memset(bgClass, 0, sizeof bgClass);
3681 bgClassPixel[0] = bgPixelDefault;
3682 /* since class no == 0 in a "style" has no set bits in BACKLIGHT_MASK
3683 (see styleOfPos()), when drawString() is called for text with a
3684 backlight class no of zero, bgClassPixel[0] is never consulted, and
3685 the default background color is chosen. */
3687 /* The format of the class string s is:
3688 low[-high]{,low[-high]}:color{;low-high{,low[-high]}:color}
3690 32-255:#f0f0f0;1-31,127:red;128-159:orange;9-13:#e5e5e5
3691 where low and high represent a character range between ordinal
3692 ASCII values. Using strtol() allows automatic octal, dec and hex
3693 reading of low and high. The example format sets backgrounds as follows:
3694 char 1 - 8 colored red (control characters)
3695 char 9 - 13 colored #e5e5e5 (isspace() control characters)
3696 char 14 - 31 colored red (control characters)
3697 char 32 - 126 colored #f0f0f0
3698 char 127 colored red (delete character)
3699 char 128 - 159 colored orange ("shifted" control characters)
3700 char 160 - 255 colored #f0f0f0
3701 Notice that some of the later ranges overwrite the class values defined
3702 for earlier ones (eg the first clause, 32-255:#f0f0f0 sets the DEL
3703 character background color to #f0f0f0; it is then set to red by the
3704 clause 1-31,127:red). */
3706 while (s && class_no < 255) {
3707 class_no++; /* simple class alloc scheme */
3708 was_semicol = 0;
3709 is_good = True;
3710 if ((semicol = (char *)strchr(s, ';'))) {
3711 *semicol = '\0'; /* null-terminate low[-high]:color clause */
3712 was_semicol = 1;
3715 /* loop over ranges before the color spec, assigning the characters
3716 in the ranges to the current class number */
3717 for (lo = hi = strtol(s, &pos, 0);
3718 is_good;
3719 lo = hi = strtol(pos + 1, &pos, 0)) {
3720 if (pos && *pos == '-')
3721 hi = strtol(pos + 1, &pos, 0); /* get end of range */
3722 is_good = (pos && 0 <= lo && lo <= hi && hi <= 255);
3723 if (is_good)
3724 while (lo <= hi)
3725 bgClass[lo++] = (unsigned char)class_no;
3726 if (*pos != ',')
3727 break;
3729 if ((is_good = (is_good && *pos == ':'))) {
3730 is_good = (*pos++ != '\0'); /* pos now points to color */
3731 bgClassPixel[class_no] = allocBGColor(w, pos, &dummy);
3733 if (!is_good) {
3734 /* complain? this class spec clause (in string s) was faulty */
3737 /* end of loop iterator clauses */
3738 if (was_semicol)
3739 *semicol = ';'; /* un-null-terminate low[-high]:color clause */
3740 s = semicol + was_semicol;
3742 /* when we get here, we've set up our class table and class-to-pixel table
3743 in local variables: now put them into the "real thing" */
3744 if (class_no) {
3745 class_no++; /* bigger than all valid class_nos */
3746 *pp_bgClass = (unsigned char *)XtMalloc(256);
3747 *pp_bgClassPixel = (Pixel *)XtMalloc(class_no * sizeof (Pixel));
3748 if (!*pp_bgClass || !*pp_bgClassPixel) {
3749 XtFree((char *)*pp_bgClass);
3750 XtFree((char *)*pp_bgClassPixel);
3751 return;
3753 memcpy(*pp_bgClass, bgClass, 256);
3754 memcpy(*pp_bgClassPixel, bgClassPixel, class_no * sizeof (Pixel));