Fix for SF bug #564782: Show Path in Windows Menu
[nedit.git] / source / textDisp.c
blob7294e27b5c125804e5a30535bd46f50f2630956a
1 static const char CVSID[] = "$Id: textDisp.c,v 1.44 2003/03/21 18:51:01 n8gray 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_fn.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 (0x1F << 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 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 (5 bits - actual range is limited to 0-26)
102 This leaves 7 "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 redrawLineNumbers(textDisp *textD, int clearAll);
143 static void updateVScrollBarRange(textDisp *textD);
144 static int updateHScrollBarRange(textDisp *textD);
145 static int max(int i1, int i2);
146 static int min(int i1, int i2);
147 static int countLines(char *string);
148 static int measureVisLine(textDisp *textD, int visLineNum);
149 static int emptyLinesVisible(textDisp *textD);
150 static void blankCursorProtrusions(textDisp *textD);
151 static void allocateFixedFontGCs(textDisp *textD, XFontStruct *fontStruct,
152 Pixel bgPixel, Pixel fgPixel, Pixel selectFGPixel, Pixel selectBGPixel,
153 Pixel highlightFGPixel, Pixel highlightBGPixel, Pixel lineNumFGPixel);
154 static GC allocateGC(Widget w, unsigned long valueMask,
155 unsigned long foreground, unsigned long background, Font font,
156 unsigned long dynamicMask, unsigned long dontCareMask);
157 static void releaseGC(Widget w, GC gc);
158 static void resetClipRectangles(textDisp *textD);
159 static int visLineLength(textDisp *textD, int visLineNum);
160 static void measureDeletedLines(textDisp *textD, int pos, int nDeleted);
161 static void findWrapRange(textDisp *textD, char *deletedText, int pos,
162 int nInserted, int nDeleted, int *modRangeStart, int *modRangeEnd,
163 int *linesInserted, int *linesDeleted);
164 static void wrappedLineCounter(textDisp *textD, textBuffer *buf, int startPos,
165 int maxPos, int maxLines, int startPosIsLineStart, int styleBufOffset,
166 int *retPos, int *retLines, int *retLineStart, int *retLineEnd);
167 static void findLineEnd(textDisp *textD, int startPos, int startPosIsLineStart,
168 int *lineEnd, int *nextLineStart);
169 static int wrapUsesCharacter(textDisp *textD, int lineEndPos);
170 static void hideOrShowHScrollBar(textDisp *textD);
171 static int rangeTouchesRectSel(selection *sel, int rangeStart, int rangeEnd);
172 static void extendRangeForStyleMods(textDisp *textD, int *start, int *end);
173 static int getAbsTopLineNum(textDisp *textD);
174 static void offsetAbsLineNum(textDisp *textD, int oldFirstChar);
175 static int maintainingAbsTopLineNum(textDisp *textD);
176 static void resetAbsLineNum(textDisp *textD);
177 static int measurePropChar(textDisp *textD, char c, int colNum, int pos);
178 static Pixel allocBGColor(Widget w, char *colorName, int *ok);
179 Pixel getRangesetColor(textDisp *textD, int ind, Pixel bground);
181 textDisp *TextDCreate(Widget widget, Widget hScrollBar, Widget vScrollBar,
182 Position left, Position top, Position width, Position height,
183 Position lineNumLeft, Position lineNumWidth, textBuffer *buffer,
184 XFontStruct *fontStruct, Pixel bgPixel, Pixel fgPixel,
185 Pixel selectFGPixel, Pixel selectBGPixel, Pixel highlightFGPixel,
186 Pixel highlightBGPixel, Pixel cursorFGPixel, Pixel lineNumFGPixel,
187 int continuousWrap, int wrapMargin, XmString bgClassString)
189 textDisp *textD;
190 XGCValues gcValues;
191 int i;
193 textD = (textDisp *)XtMalloc(sizeof(textDisp));
194 textD->w = widget;
195 textD->top = top;
196 textD->left = left;
197 textD->width = width;
198 textD->height = height;
199 textD->cursorOn = True;
200 textD->cursorPos = 0;
201 textD->cursorX = -100;
202 textD->cursorY = -100;
203 textD->cursorToHint = NO_HINT;
204 textD->cursorStyle = NORMAL_CURSOR;
205 textD->cursorPreferredCol = -1;
206 textD->buffer = buffer;
207 textD->firstChar = 0;
208 textD->lastChar = 0;
209 textD->nBufferLines = 0;
210 textD->topLineNum = 1;
211 textD->absTopLineNum = 1;
212 textD->needAbsTopLineNum = False;
213 textD->horizOffset = 0;
214 textD->hScrollBar = hScrollBar;
215 textD->vScrollBar = vScrollBar;
216 textD->fontStruct = fontStruct;
217 textD->ascent = fontStruct->ascent;
218 textD->descent = fontStruct->descent;
219 textD->fixedFontWidth = fontStruct->min_bounds.width ==
220 fontStruct->max_bounds.width ? fontStruct->min_bounds.width : -1;
221 textD->styleBuffer = NULL;
222 textD->styleTable = NULL;
223 textD->nStyles = 0;
224 textD->bgPixel = bgPixel;
225 textD->fgPixel = fgPixel;
226 textD->selectFGPixel = selectFGPixel;
227 textD->highlightFGPixel = highlightFGPixel;
228 textD->selectBGPixel = selectBGPixel;
229 textD->highlightBGPixel = highlightBGPixel;
230 textD->lineNumFGPixel = lineNumFGPixel;
231 textD->wrapMargin = wrapMargin;
232 textD->continuousWrap = continuousWrap;
233 allocateFixedFontGCs(textD, fontStruct, bgPixel, fgPixel, selectFGPixel,
234 selectBGPixel, highlightFGPixel, highlightBGPixel, lineNumFGPixel);
235 textD->styleGC = allocateGC(textD->w, 0, 0, 0, fontStruct->fid,
236 GCClipMask|GCForeground|GCBackground, GCArcMode);
237 textD->lineNumLeft = lineNumLeft;
238 textD->lineNumWidth = lineNumWidth;
239 textD->nVisibleLines = (height - 1) / (textD->ascent + textD->descent) + 1;
240 gcValues.foreground = cursorFGPixel;
241 textD->cursorFGGC = XtGetGC(widget, GCForeground, &gcValues);
242 textD->lineStarts = (int *)XtMalloc(sizeof(int) * textD->nVisibleLines);
243 textD->lineStarts[0] = 0;
244 textD->calltipW = NULL;
245 textD->calltipShell = NULL;
246 textD->calltip.ID = 0;
247 for (i=1; i<textD->nVisibleLines; i++)
248 textD->lineStarts[i] = -1;
249 textD->bgClassPixel = NULL;
250 textD->bgClass = NULL;
251 TextDSetupBGClasses(widget, bgClassString, &textD->bgClassPixel,
252 &textD->bgClass, bgPixel);
253 textD->suppressResync = 0;
254 textD->nLinesDeleted = 0;
255 textD->modifyingTabDist = 0;
256 textD->pointerHidden = False;
257 textD->graphicsExposeQueue = NULL;
259 /* Attach the callback to the text buffer for receiving modification
260 information */
261 if (buffer != NULL) {
262 BufAddModifyCB(buffer, bufModifiedCB, textD);
263 BufAddPreDeleteCB(buffer, bufPreDeleteCB, textD);
266 /* Initialize the scroll bars and attach movement callbacks */
267 if (vScrollBar != NULL) {
268 XtVaSetValues(vScrollBar, XmNminimum, 1, XmNmaximum, 2,
269 XmNsliderSize, 1, XmNrepeatDelay, 10, XmNvalue, 1, NULL);
270 XtAddCallback(vScrollBar, XmNdragCallback, vScrollCB, (XtPointer)textD);
271 XtAddCallback(vScrollBar, XmNvalueChangedCallback, vScrollCB,
272 (XtPointer)textD);
274 if (hScrollBar != NULL) {
275 XtVaSetValues(hScrollBar, XmNminimum, 0, XmNmaximum, 1,
276 XmNsliderSize, 1, XmNrepeatDelay, 10, XmNvalue, 0,
277 XmNincrement, fontStruct->max_bounds.width, NULL);
278 XtAddCallback(hScrollBar, XmNdragCallback, hScrollCB, (XtPointer)textD);
279 XtAddCallback(hScrollBar, XmNvalueChangedCallback, hScrollCB,
280 (XtPointer)textD);
283 /* Update the display to reflect the contents of the buffer */
284 if (buffer != NULL)
285 bufModifiedCB(0, buffer->length, 0, 0, NULL, textD);
287 /* Decide if the horizontal scroll bar needs to be visible */
288 hideOrShowHScrollBar(textD);
290 return textD;
294 ** Free a text display and release its associated memory. Note, the text
295 ** BUFFER that the text display displays is a separate entity and is not
296 ** freed, nor are the style buffer or style table.
298 void TextDFree(textDisp *textD)
300 BufRemoveModifyCB(textD->buffer, bufModifiedCB, textD);
301 BufRemovePreDeleteCB(textD->buffer, bufPreDeleteCB, textD);
302 releaseGC(textD->w, textD->gc);
303 releaseGC(textD->w, textD->selectGC);
304 releaseGC(textD->w, textD->highlightGC);
305 releaseGC(textD->w, textD->selectBGGC);
306 releaseGC(textD->w, textD->highlightBGGC);
307 releaseGC(textD->w, textD->styleGC);
308 releaseGC(textD->w, textD->lineNumGC);
309 XtFree((char *)textD->lineStarts);
310 while (TextDPopGraphicExposeQueueEntry(textD)) {
312 XtFree((char *)textD->bgClassPixel);
313 XtFree((char *)textD->bgClass);
314 XtFree((char *)textD);
318 ** Attach a text buffer to display, replacing the current buffer (if any)
320 void TextDSetBuffer(textDisp *textD, textBuffer *buffer)
322 /* If the text display is already displaying a buffer, clear it off
323 of the display and remove our callback from it */
324 if (textD->buffer != NULL) {
325 bufModifiedCB(0, 0, textD->buffer->length, 0, NULL, textD);
326 BufRemoveModifyCB(textD->buffer, bufModifiedCB, textD);
327 BufRemovePreDeleteCB(textD->buffer, bufPreDeleteCB, textD);
330 /* Add the buffer to the display, and attach a callback to the buffer for
331 receiving modification information when the buffer contents change */
332 textD->buffer = buffer;
333 BufAddModifyCB(buffer, bufModifiedCB, textD);
334 BufAddPreDeleteCB(buffer, bufPreDeleteCB, textD);
336 /* Update the display */
337 bufModifiedCB(0, buffer->length, 0, 0, NULL, textD);
341 ** return the displayed text buffer
343 textBuffer *TextDGetBuffer(textDisp *textD)
345 return textD->buffer;
349 ** Attach (or remove) highlight information in text display and redisplay.
350 ** Highlighting information consists of a style buffer which parallels the
351 ** normal text buffer, but codes font and color information for the display;
352 ** a style table which translates style buffer codes (indexed by buffer
353 ** character - 65 (ASCII code for 'A')) into fonts and colors; and a callback
354 ** mechanism for as-needed highlighting, triggered by a style buffer entry of
355 ** "unfinishedStyle". Style buffer can trigger additional redisplay during
356 ** a normal buffer modification if the buffer contains a primary selection
357 ** (see extendRangeForStyleMods for more information on this protocol).
359 ** Style buffers, tables and their associated memory are managed by the caller.
361 void TextDAttachHighlightData(textDisp *textD, textBuffer *styleBuffer,
362 styleTableEntry *styleTable, int nStyles, char unfinishedStyle,
363 unfinishedStyleCBProc unfinishedHighlightCB, void *cbArg)
365 textD->styleBuffer = styleBuffer;
366 textD->styleTable = styleTable;
367 textD->nStyles = nStyles;
368 textD->unfinishedStyle = unfinishedStyle;
369 textD->unfinishedHighlightCB = unfinishedHighlightCB;
370 textD->highlightCBArg = cbArg;
372 /* Call TextDSetFont to combine font information from style table and
373 primary font, adjust font-related parameters, and then redisplay */
374 TextDSetFont(textD, textD->fontStruct);
378 /* Change the (non syntax-highlit) colors */
379 void TextDSetColors(textDisp *textD, Pixel textFgP, Pixel textBgP,
380 Pixel selectFgP, Pixel selectBgP, Pixel hiliteFgP, Pixel hiliteBgP,
381 Pixel lineNoFgP, Pixel cursorFgP)
383 XGCValues values;
384 Display *d = XtDisplay(textD->w);
386 /* Update the stored pixels */
387 textD->lineNumFGPixel = lineNoFgP;
388 textD->bgPixel = textBgP;
389 textD->selectBGPixel = selectBgP;
390 textD->highlightBGPixel = hiliteBgP;
392 /* Change the main gc */
393 values.foreground = textFgP;
394 values.background = textBgP;
395 XChangeGC( d, textD->gc, GCForeground | GCBackground, &values );
397 /* Change the select GC */
398 values.foreground = selectFgP;
399 values.background = selectBgP;
400 XChangeGC( d, textD->selectGC, GCForeground | GCBackground, &values );
402 /* Change the select BGGC */
403 values.foreground = selectBgP;
404 XChangeGC( d, textD->selectBGGC, GCForeground, &values );
406 /* Change the highlight GC */
407 values.foreground = hiliteFgP;
408 values.background = hiliteBgP;
409 XChangeGC( d, textD->highlightGC, GCForeground | GCBackground, &values );
411 /* Change the highlight BGGC */
412 values.foreground = hiliteBgP;
413 XChangeGC( d, textD->highlightBGGC, GCForeground, &values );
415 /* Change the line number GC */
416 values.foreground = lineNoFgP;
417 values.background = textBgP;
418 XChangeGC( d, textD->lineNumGC, GCForeground | GCBackground, &values );
420 /* Change the cursor GC */
421 values.foreground = cursorFgP;
422 XChangeGC( d, textD->cursorFGGC, GCForeground, &values );
424 /* Redisplay */
425 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
426 textD->height);
427 redrawLineNumbers(textD, True);
431 ** Change the (non highlight) font
433 void TextDSetFont(textDisp *textD, XFontStruct *fontStruct)
435 Display *display = XtDisplay(textD->w);
436 int i, maxAscent = fontStruct->ascent, maxDescent = fontStruct->descent;
437 int width, height, fontWidth;
438 Pixel bgPixel, fgPixel, selectFGPixel, selectBGPixel;
439 Pixel highlightFGPixel, highlightBGPixel, lineNumFGPixel;
440 XGCValues values;
441 XFontStruct *styleFont;
443 /* If font size changes, cursor will be redrawn in a new position */
444 blankCursorProtrusions(textD);
446 /* If there is a (syntax highlighting) style table in use, find the new
447 maximum font height for this text display */
448 for (i=0; i<textD->nStyles; i++) {
449 styleFont = textD->styleTable[i].font;
450 if (styleFont != NULL && styleFont->ascent > maxAscent)
451 maxAscent = styleFont->ascent;
452 if (styleFont != NULL && styleFont->descent > maxDescent)
453 maxDescent = styleFont->descent;
455 textD->ascent = maxAscent;
456 textD->descent = maxDescent;
458 /* If all of the current fonts are fixed and match in width, compute */
459 fontWidth = fontStruct->max_bounds.width;
460 if (fontWidth != fontStruct->min_bounds.width)
461 fontWidth = -1;
462 else {
463 for (i=0; i<textD->nStyles; i++) {
464 styleFont = textD->styleTable[i].font;
465 if (styleFont != NULL &&
466 (styleFont->max_bounds.width != fontWidth ||
467 styleFont->max_bounds.width != styleFont->min_bounds.width))
468 fontWidth = -1;
471 textD->fixedFontWidth = fontWidth;
473 /* Don't let the height dip below one line, or bad things can happen */
474 if (textD->height < maxAscent + maxDescent)
475 textD->height = maxAscent + maxDescent;
477 /* Change the font. In most cases, this means re-allocating the
478 affected GCs (they are shared with other widgets, and if the primary
479 font changes, must be re-allocated to change it). Unfortunately,
480 this requres recovering all of the colors from the existing GCs */
481 textD->fontStruct = fontStruct;
482 XGetGCValues(display, textD->gc, GCForeground|GCBackground, &values);
483 fgPixel = values.foreground;
484 bgPixel = values.background;
485 XGetGCValues(display, textD->selectGC, GCForeground|GCBackground, &values);
486 selectFGPixel = values.foreground;
487 selectBGPixel = values.background;
488 XGetGCValues(display, textD->highlightGC,GCForeground|GCBackground,&values);
489 highlightFGPixel = values.foreground;
490 highlightBGPixel = values.background;
491 XGetGCValues(display, textD->highlightGC,GCForeground,&values);
492 lineNumFGPixel = values.foreground;
493 releaseGC(textD->w, textD->gc);
494 releaseGC(textD->w, textD->selectGC);
495 releaseGC(textD->w, textD->highlightGC);
496 releaseGC(textD->w, textD->selectBGGC);
497 releaseGC(textD->w, textD->highlightBGGC);
498 releaseGC(textD->w, textD->lineNumGC);
499 allocateFixedFontGCs(textD, fontStruct, bgPixel, fgPixel, selectFGPixel,
500 selectBGPixel, highlightFGPixel, highlightBGPixel, lineNumFGPixel);
501 XSetFont(display, textD->styleGC, fontStruct->fid);
503 /* Do a full resize to force recalculation of font related parameters */
504 width = textD->width;
505 height = textD->height;
506 textD->width = textD->height = 0;
507 TextDResize(textD, width, height);
509 /* Redisplay */
510 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
511 textD->height);
513 /* Clean up line number area in case spacing has changed */
514 redrawLineNumbers(textD, True);
517 int TextDMinFontWidth(textDisp *textD, Boolean considerStyles)
519 int fontWidth = textD->fontStruct->max_bounds.width;
520 int i;
522 if (considerStyles) {
523 for (i = 0; i < textD->nStyles; ++i) {
524 int thisWidth = (textD->styleTable[i].font)->min_bounds.width;
525 if (thisWidth < fontWidth) {
526 fontWidth = thisWidth;
530 return(fontWidth);
533 int TextDMaxFontWidth(textDisp *textD, Boolean considerStyles)
535 int fontWidth = textD->fontStruct->max_bounds.width;
536 int i;
538 if (considerStyles) {
539 for (i = 0; i < textD->nStyles; ++i) {
540 int thisWidth = (textD->styleTable[i].font)->max_bounds.width;
541 if (thisWidth > fontWidth) {
542 fontWidth = thisWidth;
546 return(fontWidth);
550 ** Change the size of the displayed text area
552 void TextDResize(textDisp *textD, int width, int height)
554 int oldVisibleLines = textD->nVisibleLines;
555 int canRedraw = XtWindow(textD->w) != 0;
556 int newVisibleLines = height / (textD->ascent + textD->descent);
557 int redrawAll = False;
558 int oldWidth = textD->width;
559 int exactHeight = height - height % (textD->ascent + textD->descent);
561 textD->width = width;
562 textD->height = height;
564 /* In continuous wrap mode, a change in width affects the total number of
565 lines in the buffer, and can leave the top line number incorrect, and
566 the top character no longer pointing at a valid line start */
567 if (textD->continuousWrap && textD->wrapMargin==0 && width!=oldWidth) {
568 int oldFirstChar = textD->firstChar;
569 textD->nBufferLines = TextDCountLines(textD, 0, textD->buffer->length,
570 True);
571 textD->firstChar = TextDStartOfLine(textD, textD->firstChar);
572 textD->topLineNum = TextDCountLines(textD, 0, textD->firstChar, True)+1;
573 redrawAll = True;
574 offsetAbsLineNum(textD, oldFirstChar);
577 /* reallocate and update the line starts array, which may have changed
578 size and/or contents. (contents can change in continuous wrap mode
579 when the width changes, even without a change in height) */
580 if (oldVisibleLines < newVisibleLines) {
581 XtFree((char *)textD->lineStarts);
582 textD->lineStarts = (int *)XtMalloc(sizeof(int) * newVisibleLines);
584 textD->nVisibleLines = newVisibleLines;
585 calcLineStarts(textD, 0, newVisibleLines);
586 calcLastChar(textD);
588 /* if the window became shorter, there may be partially drawn
589 text left at the bottom edge, which must be cleaned up */
590 if (canRedraw && oldVisibleLines>newVisibleLines && exactHeight!=height)
591 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->left,
592 textD->top + exactHeight, textD->width,
593 height - exactHeight, False);
595 /* if the window became taller, there may be an opportunity to display
596 more text by scrolling down */
597 if (canRedraw && oldVisibleLines < newVisibleLines && textD->topLineNum +
598 textD->nVisibleLines > textD->nBufferLines)
599 setScroll(textD, max(1, textD->nBufferLines - textD->nVisibleLines +
600 2 + TEXT_OF_TEXTD(textD).cursorVPadding),
601 textD->horizOffset, False, False);
603 /* Update the scroll bar page increment size (as well as other scroll
604 bar parameters. If updating the horizontal range caused scrolling,
605 redraw */
606 updateVScrollBarRange(textD);
607 if (updateHScrollBarRange(textD))
608 redrawAll = True;
610 /* If a full redraw is needed */
611 if (redrawAll && canRedraw)
612 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
613 textD->height);
615 /* Decide if the horizontal scroll bar needs to be visible */
616 hideOrShowHScrollBar(textD);
618 /* Refresh the line number display to draw more line numbers, or
619 erase extras */
620 redrawLineNumbers(textD, True);
622 /* Redraw the calltip */
623 TextDRedrawCalltip(textD, 0);
627 ** Refresh a rectangle of the text display. left and top are in coordinates of
628 ** the text drawing window
630 void TextDRedisplayRect(textDisp *textD, int left, int top, int width,
631 int height)
633 int fontHeight, firstLine, lastLine, line;
635 /* find the line number range of the display */
636 fontHeight = textD->ascent + textD->descent;
637 firstLine = (top - textD->top - fontHeight + 1) / fontHeight;
638 lastLine = (top + height - textD->top) / fontHeight;
640 /* If the graphics contexts are shared using XtAllocateGC, their
641 clipping rectangles may have changed since the last use */
642 resetClipRectangles(textD);
644 /* draw the lines of text */
645 for (line=firstLine; line<=lastLine; line++)
646 redisplayLine(textD, line, left, left+width, 0, INT_MAX);
648 /* draw the line numbers if exposed area includes them */
649 if (textD->lineNumWidth != 0 && left <= textD->lineNumLeft + textD->lineNumWidth)
650 redrawLineNumbers(textD, False);
654 ** Refresh all of the text between buffer positions "start" and "end"
655 ** not including the character at the position "end".
656 ** If end points beyond the end of the buffer, refresh the whole display
657 ** after pos, including blank lines which are not technically part of
658 ** any range of characters.
660 void TextDRedisplayRange(textDisp *textD, int start, int end)
662 int i, startLine, lastLine, startIndex, endIndex;
664 /* If the range is outside of the displayed text, just return */
665 if (end < textD->firstChar || (start > textD->lastChar &&
666 !emptyLinesVisible(textD)))
667 return;
669 /* Clean up the starting and ending values */
670 if (start < 0) start = 0;
671 if (start > textD->buffer->length) start = textD->buffer->length;
672 if (end < 0) end = 0;
673 if (end > textD->buffer->length) end = textD->buffer->length;
675 /* Get the starting and ending lines */
676 if (start < textD->firstChar)
677 start = textD->firstChar;
678 if (!posToVisibleLineNum(textD, start, &startLine))
679 startLine = textD->nVisibleLines - 1;
680 if (end >= textD->lastChar) {
681 lastLine = textD->nVisibleLines - 1;
682 } else {
683 if (!posToVisibleLineNum(textD, end, &lastLine)) {
684 /* shouldn't happen */
685 lastLine = textD->nVisibleLines - 1;
689 /* Get the starting and ending positions within the lines */
690 startIndex = textD->lineStarts[startLine] == -1 ? 0 :
691 start - textD->lineStarts[startLine];
692 if (end >= textD->lastChar)
693 endIndex = INT_MAX;
694 else if (textD->lineStarts[lastLine] == -1)
695 endIndex = 0;
696 else
697 endIndex = end - textD->lineStarts[lastLine];
699 /* Reset the clipping rectangles for the drawing GCs which are shared
700 using XtAllocateGC, and may have changed since the last use */
701 resetClipRectangles(textD);
703 /* If the starting and ending lines are the same, redisplay the single
704 line between "start" and "end" */
705 if (startLine == lastLine) {
706 redisplayLine(textD, startLine, 0, INT_MAX, startIndex, endIndex);
707 return;
710 /* Redisplay the first line from "start" */
711 redisplayLine(textD, startLine, 0, INT_MAX, startIndex, INT_MAX);
713 /* Redisplay the lines in between at their full width */
714 for (i=startLine+1; i<lastLine; i++)
715 redisplayLine(textD, i, 0, INT_MAX, 0, INT_MAX);
717 /* Redisplay the last line to "end" */
718 redisplayLine(textD, lastLine, 0, INT_MAX, 0, endIndex);
722 ** Set the scroll position of the text display vertically by line number and
723 ** horizontally by pixel offset from the left margin
725 void TextDSetScroll(textDisp *textD, int topLineNum, int horizOffset)
727 int sliderSize, sliderMax;
728 int vPadding = (int)(TEXT_OF_TEXTD(textD).cursorVPadding);
730 /* Limit the requested scroll position to allowable values */
731 if (topLineNum < 1)
732 topLineNum = 1;
733 else if ((topLineNum > textD->topLineNum) &&
734 (topLineNum > (textD->nBufferLines + 2 - textD->nVisibleLines +
735 vPadding)))
736 topLineNum = max(textD->topLineNum,
737 textD->nBufferLines + 2 - textD->nVisibleLines + vPadding);
738 XtVaGetValues(textD->hScrollBar, XmNmaximum, &sliderMax,
739 XmNsliderSize, &sliderSize, NULL);
740 if (horizOffset < 0)
741 horizOffset = 0;
742 if (horizOffset > sliderMax - sliderSize)
743 horizOffset = sliderMax - sliderSize;
745 setScroll(textD, topLineNum, horizOffset, True, True);
749 ** Get the current scroll position for the text display, in terms of line
750 ** number of the top line and horizontal pixel offset from the left margin
752 void TextDGetScroll(textDisp *textD, int *topLineNum, int *horizOffset)
754 *topLineNum = textD->topLineNum;
755 *horizOffset = textD->horizOffset;
759 ** Set the position of the text insertion cursor for text display "textD"
761 void TextDSetInsertPosition(textDisp *textD, int newPos)
763 /* make sure new position is ok, do nothing if it hasn't changed */
764 if (newPos == textD->cursorPos)
765 return;
766 if (newPos < 0) newPos = 0;
767 if (newPos > textD->buffer->length) newPos = textD->buffer->length;
769 /* cursor movement cancels vertical cursor motion column */
770 textD->cursorPreferredCol = -1;
772 /* erase the cursor at it's previous position */
773 TextDBlankCursor(textD);
775 /* draw it at its new position */
776 textD->cursorPos = newPos;
777 textD->cursorOn = True;
778 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos + 1);
781 void TextDBlankCursor(textDisp *textD)
783 if (!textD->cursorOn)
784 return;
786 blankCursorProtrusions(textD);
787 textD->cursorOn = False;
788 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
791 void TextDUnblankCursor(textDisp *textD)
793 if (!textD->cursorOn) {
794 textD->cursorOn = True;
795 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
799 void TextDSetCursorStyle(textDisp *textD, int style)
801 textD->cursorStyle = style;
802 blankCursorProtrusions(textD);
803 if (textD->cursorOn)
804 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos + 1);
807 void TextDSetWrapMode(textDisp *textD, int wrap, int wrapMargin)
809 textD->wrapMargin = wrapMargin;
810 textD->continuousWrap = wrap;
812 /* wrapping can change change the total number of lines, re-count */
813 textD->nBufferLines = TextDCountLines(textD, 0, textD->buffer->length,True);
815 /* changing wrap margins wrap or changing from wrapped mode to non-wrapped
816 can leave the character at the top no longer at a line start, and/or
817 change the line number */
818 textD->firstChar = TextDStartOfLine(textD, textD->firstChar);
819 textD->topLineNum = TextDCountLines(textD, 0, textD->firstChar, True) + 1;
820 resetAbsLineNum(textD);
822 /* update the line starts array */
823 calcLineStarts(textD, 0, textD->nVisibleLines);
824 calcLastChar(textD);
826 /* Update the scroll bar page increment size (as well as other scroll
827 bar parameters) */
828 updateVScrollBarRange(textD);
829 updateHScrollBarRange(textD);
831 /* Decide if the horizontal scroll bar needs to be visible */
832 hideOrShowHScrollBar(textD);
834 /* Do a full redraw */
835 TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left,
836 textD->height);
839 int TextDGetInsertPosition(textDisp *textD)
841 return textD->cursorPos;
845 ** Insert "text" at the current cursor location. This has the same
846 ** effect as inserting the text into the buffer using BufInsert and
847 ** then moving the insert position after the newly inserted text, except
848 ** that it's optimized to do less redrawing.
850 void TextDInsert(textDisp *textD, char *text)
852 int pos = textD->cursorPos;
854 textD->cursorToHint = pos + strlen(text);
855 BufInsert(textD->buffer, pos, text);
856 textD->cursorToHint = NO_HINT;
860 ** Insert "text" (which must not contain newlines), overstriking the current
861 ** cursor location.
863 void TextDOverstrike(textDisp *textD, char *text)
865 int startPos = textD->cursorPos;
866 textBuffer *buf = textD->buffer;
867 int lineStart = BufStartOfLine(buf, startPos);
868 int textLen = strlen(text);
869 int i, p, endPos, indent, startIndent, endIndent;
870 char *c, ch, *paddedText = NULL;
872 /* determine how many displayed character positions are covered */
873 startIndent = BufCountDispChars(textD->buffer, lineStart, startPos);
874 indent = startIndent;
875 for (c=text; *c!='\0'; c++)
876 indent += BufCharWidth(*c, indent, buf->tabDist, buf->nullSubsChar);
877 endIndent = indent;
879 /* find which characters to remove, and if necessary generate additional
880 padding to make up for removed control characters at the end */
881 indent=startIndent;
882 for (p=startPos; ; p++) {
883 if (p == buf->length)
884 break;
885 ch = BufGetCharacter(buf, p);
886 if (ch == '\n')
887 break;
888 indent += BufCharWidth(ch, indent, buf->tabDist, buf->nullSubsChar);
889 if (indent == endIndent) {
890 p++;
891 break;
892 } else if (indent > endIndent) {
893 if (ch != '\t') {
894 p++;
895 paddedText = XtMalloc(textLen + MAX_EXP_CHAR_LEN + 1);
896 strcpy(paddedText, text);
897 for (i=0; i<indent-endIndent; i++)
898 paddedText[textLen+i] = ' ';
899 paddedText[textLen+i] = '\0';
901 break;
904 endPos = p;
906 textD->cursorToHint = startPos + textLen;
907 BufReplace(buf, startPos, endPos, paddedText == NULL ? text : paddedText);
908 textD->cursorToHint = NO_HINT;
909 if (paddedText != NULL)
910 XtFree(paddedText);
914 ** Translate window coordinates to the nearest text cursor position.
916 int TextDXYToPosition(textDisp *textD, int x, int y)
918 return xyToPos(textD, x, y, CURSOR_POS);
922 ** Translate window coordinates to the nearest character cell.
924 int TextDXYToCharPos(textDisp *textD, int x, int y)
926 return xyToPos(textD, x, y, CHARACTER_POS);
930 ** Translate window coordinates to the nearest row and column number for
931 ** positioning the cursor. This, of course, makes no sense when the font
932 ** is proportional, since there are no absolute columns.
934 void TextDXYToUnconstrainedPosition(textDisp *textD, int x, int y, int *row,
935 int *column)
937 xyToUnconstrainedPos(textD, x, y, row, column, CURSOR_POS);
941 ** Translate line and column to the nearest row and column number for
942 ** positioning the cursor. This, of course, makes no sense when the font
943 ** is proportional, since there are no absolute columns.
945 int TextDLineAndColToPos(textDisp *textD, int lineNum, int column)
947 int i, lineEnd, charIndex, outIndex;
948 int lineStart=0, charLen=0;
949 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
951 /* Count lines */
952 if (lineNum < 1)
953 lineNum = 1;
954 lineEnd = -1;
955 for (i=1; i<=lineNum && lineEnd<textD->buffer->length; i++) {
956 lineStart = lineEnd + 1;
957 lineEnd = BufEndOfLine(textD->buffer, lineStart);
960 /* If line is beyond end of buffer, position at last character in buffer */
961 if ( lineNum >= i ) {
962 return lineEnd;
965 /* Start character index at zero */
966 charIndex=0;
968 /* Only have to count columns if column isn't zero (or negative) */
969 if (column > 0) {
970 /* Count columns, expanding each character */
971 lineStr = BufGetRange(textD->buffer, lineStart, lineEnd);
972 outIndex = 0;
973 for(i=lineStart; i<lineEnd; i++, charIndex++) {
974 charLen = BufExpandCharacter(lineStr[charIndex], outIndex,
975 expandedChar, textD->buffer->tabDist,
976 textD->buffer->nullSubsChar);
977 if ( outIndex+charLen >= column ) break;
978 outIndex+=charLen;
981 /* If the column is in the middle of an expanded character, put cursor
982 * in front of character if in first half of character, and behind
983 * character if in last half of character
985 if (column >= outIndex + ( charLen / 2 ))
986 charIndex++;
988 /* If we are beyond the end of the line, back up one space */
989 if ((i>=lineEnd)&&(charIndex>0)) charIndex--;
992 /* Position is the start of the line plus the index into line buffer */
993 return lineStart + charIndex;
997 ** Translate a buffer text position to the XY location where the center
998 ** of the cursor would be positioned to point to that character. Returns
999 ** False if the position is not displayed because it is VERTICALLY out
1000 ** of view. If the position is horizontally out of view, returns the
1001 ** x coordinate where the position would be if it were visible.
1003 int TextDPositionToXY(textDisp *textD, int pos, int *x, int *y)
1005 int charIndex, lineStartPos, fontHeight, lineLen;
1006 int visLineNum, charLen, outIndex, xStep, charStyle;
1007 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
1009 /* If position is not displayed, return false */
1010 if (pos < textD->firstChar ||
1011 (pos > textD->lastChar && !emptyLinesVisible(textD)))
1012 return False;
1014 /* Calculate y coordinate */
1015 if (!posToVisibleLineNum(textD, pos, &visLineNum))
1016 return False;
1017 fontHeight = textD->ascent + textD->descent;
1018 *y = textD->top + visLineNum*fontHeight + fontHeight/2;
1020 /* Get the text, length, and buffer position of the line. If the position
1021 is beyond the end of the buffer and should be at the first position on
1022 the first empty line, don't try to get or scan the text */
1023 lineStartPos = textD->lineStarts[visLineNum];
1024 if (lineStartPos == -1) {
1025 *x = textD->left - textD->horizOffset;
1026 return True;
1028 lineLen = visLineLength(textD, visLineNum);
1029 lineStr = BufGetRange(textD->buffer, lineStartPos, lineStartPos + lineLen);
1031 /* Step through character positions from the beginning of the line
1032 to "pos" to calculate the x coordinate */
1033 xStep = textD->left - textD->horizOffset;
1034 outIndex = 0;
1035 for(charIndex=0; charIndex<pos-lineStartPos; charIndex++) {
1036 charLen = BufExpandCharacter(lineStr[charIndex], outIndex, expandedChar,
1037 textD->buffer->tabDist, textD->buffer->nullSubsChar);
1038 charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1039 outIndex, lineStr[charIndex]);
1040 xStep += stringWidth(textD, expandedChar, charLen, charStyle);
1041 outIndex += charLen;
1043 *x = xStep;
1044 XtFree(lineStr);
1045 return True;
1049 ** If the text widget is maintaining a line number count appropriate to "pos"
1050 ** return the line and column numbers of pos, otherwise return False. If
1051 ** continuous wrap mode is on, returns the absolute line number (as opposed to
1052 ** the wrapped line number which is used for scrolling). THIS ROUTINE ONLY
1053 ** WORKS FOR DISPLAYED LINES AND, IN CONTINUOUS WRAP MODE, ONLY WHEN THE
1054 ** ABSOLUTE LINE NUMBER IS BEING MAINTAINED. Otherwise, it returns False.
1056 int TextDPosToLineAndCol(textDisp *textD, int pos, int *lineNum, int *column)
1058 textBuffer *buf = textD->buffer;
1060 /* In continuous wrap mode, the absolute (non-wrapped) line count is
1061 maintained separately, as needed. Only return it if we're actually
1062 keeping track of it and pos is in the displayed text */
1063 if (textD->continuousWrap) {
1064 if (!maintainingAbsTopLineNum(textD) || pos < textD->firstChar ||
1065 pos > textD->lastChar)
1066 return False;
1067 *lineNum = textD->absTopLineNum + BufCountLines(buf,
1068 textD->firstChar, pos);
1069 *column = BufCountDispChars(buf, BufStartOfLine(buf, pos), pos);
1070 return True;
1073 /* Only return the data if pos is within the displayed text */
1074 if (!posToVisibleLineNum(textD, pos, lineNum))
1075 return False;
1076 *column = BufCountDispChars(buf, textD->lineStarts[*lineNum], pos);
1077 *lineNum += textD->topLineNum;
1078 return True;
1082 ** Return True if position (x, y) is inside of the primary selection
1084 int TextDInSelection(textDisp *textD, int x, int y)
1086 int row, column, pos = xyToPos(textD, x, y, CHARACTER_POS);
1087 textBuffer *buf = textD->buffer;
1089 xyToUnconstrainedPos(textD, x, y, &row, &column, CHARACTER_POS);
1090 if (rangeTouchesRectSel(&buf->primary, textD->firstChar, textD->lastChar))
1091 column = TextDOffsetWrappedColumn(textD, row, column);
1092 return inSelection(&buf->primary, pos, BufStartOfLine(buf, pos), column);
1096 ** Correct a column number based on an unconstrained position (as returned by
1097 ** TextDXYToUnconstrainedPosition) to be relative to the last actual newline
1098 ** in the buffer before the row and column position given, rather than the
1099 ** last line start created by line wrapping. This is an adapter
1100 ** for rectangular selections and code written before continuous wrap mode,
1101 ** which thinks that the unconstrained column is the number of characters
1102 ** from the last newline. Obviously this is time consuming, because it
1103 ** invloves character re-counting.
1105 int TextDOffsetWrappedColumn(textDisp *textD, int row, int column)
1107 int lineStart, dispLineStart;
1109 if (!textD->continuousWrap || row < 0 || row > textD->nVisibleLines)
1110 return column;
1111 dispLineStart = textD->lineStarts[row];
1112 if (dispLineStart == -1)
1113 return column;
1114 lineStart = BufStartOfLine(textD->buffer, dispLineStart);
1115 return column + BufCountDispChars(textD->buffer, lineStart, dispLineStart);
1119 ** Correct a row number from an unconstrained position (as returned by
1120 ** TextDXYToUnconstrainedPosition) to a straight number of newlines from the
1121 ** top line of the display. Because rectangular selections are based on
1122 ** newlines, rather than display wrapping, and anywhere a rectangular selection
1123 ** needs a row, it needs it in terms of un-wrapped lines.
1125 int TextDOffsetWrappedRow(textDisp *textD, int row)
1127 if (!textD->continuousWrap || row < 0 || row > textD->nVisibleLines)
1128 return row;
1129 return BufCountLines(textD->buffer, textD->firstChar,
1130 textD->lineStarts[row]);
1134 ** Scroll the display to bring insertion cursor into view.
1136 ** Note: it would be nice to be able to do this without counting lines twice
1137 ** (setScroll counts them too) and/or to count from the most efficient
1138 ** starting point, but the efficiency of this routine is not as important to
1139 ** the overall performance of the text display.
1141 void TextDMakeInsertPosVisible(textDisp *textD)
1143 int hOffset, topLine, x, y;
1144 int cursorPos = textD->cursorPos;
1145 int linesFromTop = 0, do_padding = 1;
1146 int cursorVPadding = (int)TEXT_OF_TEXTD(textD).cursorVPadding;
1148 hOffset = textD->horizOffset;
1149 topLine = textD->topLineNum;
1151 /* Don't do padding if this is a mouse operation */
1152 do_padding = ((TEXT_OF_TEXTD(textD).dragState == NOT_CLICKED) &&
1153 (cursorVPadding > 0));
1155 /* Find the new top line number */
1156 if (cursorPos < textD->firstChar) {
1157 topLine -= TextDCountLines(textD, cursorPos, textD->firstChar, False);
1158 /* linesFromTop = 0; */
1159 } else if (cursorPos > textD->lastChar && !emptyLinesVisible(textD)) {
1160 topLine += TextDCountLines(textD, textD->lastChar -
1161 (wrapUsesCharacter(textD, textD->lastChar) ? 0 : 1),
1162 cursorPos, False);
1163 linesFromTop = textD->nVisibleLines-1;
1164 } else if (cursorPos == textD->lastChar && !emptyLinesVisible(textD) &&
1165 !wrapUsesCharacter(textD, textD->lastChar)) {
1166 topLine++;
1167 linesFromTop = textD->nVisibleLines-1;
1168 } else {
1169 /* Avoid extra counting if cursorVPadding is disabled */
1170 if (do_padding)
1171 linesFromTop = TextDCountLines(textD, textD->firstChar,
1172 cursorPos, True);
1174 if (topLine < 1) {
1175 fprintf(stderr, "internal consistency check tl1 failed\n");
1176 topLine = 1;
1179 if (do_padding) {
1180 /* Keep the cursor away from the top or bottom of screen. */
1181 if (textD->nVisibleLines <= 2*(int)cursorVPadding) {
1182 topLine += (linesFromTop - textD->nVisibleLines/2);
1183 topLine = max(topLine, 1);
1184 } else if (linesFromTop < (int)cursorVPadding) {
1185 topLine -= (cursorVPadding - linesFromTop);
1186 topLine = max(topLine, 1);
1187 } else if (linesFromTop > textD->nVisibleLines-(int)cursorVPadding-1) {
1188 topLine += (linesFromTop - (textD->nVisibleLines-cursorVPadding-1));
1192 /* Find the new setting for horizontal offset (this is a bit ungraceful).
1193 If the line is visible, just use TextDPositionToXY to get the position
1194 to scroll to, otherwise, do the vertical scrolling first, then the
1195 horizontal */
1196 if (!TextDPositionToXY(textD, cursorPos, &x, &y)) {
1197 setScroll(textD, topLine, hOffset, True, True);
1198 if (!TextDPositionToXY(textD, cursorPos, &x, &y))
1199 return; /* Give up, it's not worth it (but why does it fail?) */
1201 if (x > textD->left + textD->width)
1202 hOffset += x - (textD->left + textD->width);
1203 else if (x < textD->left)
1204 hOffset += x - textD->left;
1206 /* Do the scroll */
1207 setScroll(textD, topLine, hOffset, True, True);
1211 ** Return the current preferred column along with the current
1212 ** visible line index (-1 if not visible) and the lineStartPos
1213 ** of the current insert position.
1215 int TextDPreferredColumn(textDisp *textD, int *visLineNum, int *lineStartPos)
1217 int column;
1219 /* Find the position of the start of the line. Use the line starts array
1220 if possible, to avoid unbounded line-counting in continuous wrap mode */
1221 if (posToVisibleLineNum(textD, textD->cursorPos, visLineNum)) {
1222 *lineStartPos = textD->lineStarts[*visLineNum];
1224 else {
1225 *lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1226 *visLineNum = -1;
1229 /* Decide what column to move to, if there's a preferred column use that */
1230 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1231 BufCountDispChars(textD->buffer, *lineStartPos, textD->cursorPos);
1232 return(column);
1236 ** Return the insert position of the requested column given
1237 ** the lineStartPos.
1239 int TextDPosOfPreferredCol(textDisp *textD, int column, int lineStartPos)
1241 int newPos;
1243 newPos = BufCountForwardDispChars(textD->buffer, lineStartPos, column);
1244 if (textD->continuousWrap) {
1245 newPos = min(newPos, TextDEndOfLine(textD, lineStartPos, True));
1247 return(newPos);
1251 ** Cursor movement functions
1253 int TextDMoveRight(textDisp *textD)
1255 if (textD->cursorPos >= textD->buffer->length)
1256 return False;
1257 TextDSetInsertPosition(textD, textD->cursorPos + 1);
1258 return True;
1261 int TextDMoveLeft(textDisp *textD)
1263 if (textD->cursorPos <= 0)
1264 return False;
1265 TextDSetInsertPosition(textD, textD->cursorPos - 1);
1266 return True;
1269 int TextDMoveUp(textDisp *textD, int absolute)
1271 int lineStartPos, column, prevLineStartPos, newPos, visLineNum;
1273 /* Find the position of the start of the line. Use the line starts array
1274 if possible, to avoid unbounded line-counting in continuous wrap mode */
1275 if (absolute) {
1276 lineStartPos = BufStartOfLine(textD->buffer, textD->cursorPos);
1277 visLineNum = -1;
1278 } else if (posToVisibleLineNum(textD, textD->cursorPos, &visLineNum))
1279 lineStartPos = textD->lineStarts[visLineNum];
1280 else {
1281 lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1282 visLineNum = -1;
1284 if (lineStartPos == 0)
1285 return False;
1287 /* Decide what column to move to, if there's a preferred column use that */
1288 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1289 BufCountDispChars(textD->buffer, lineStartPos, textD->cursorPos);
1291 /* count forward from the start of the previous line to reach the column */
1292 if (absolute)
1293 prevLineStartPos = BufCountBackwardNLines(textD->buffer, lineStartPos, 1);
1294 else if (visLineNum != -1 && visLineNum != 0)
1295 prevLineStartPos = textD->lineStarts[visLineNum-1];
1296 else
1297 prevLineStartPos = TextDCountBackwardNLines(textD, lineStartPos, 1);
1298 newPos = BufCountForwardDispChars(textD->buffer, prevLineStartPos, column);
1299 if (textD->continuousWrap && !absolute)
1300 newPos = min(newPos, TextDEndOfLine(textD, prevLineStartPos, True));
1302 /* move the cursor */
1303 TextDSetInsertPosition(textD, newPos);
1305 /* if a preferred column wasn't aleady established, establish it */
1306 textD->cursorPreferredCol = column;
1308 return True;
1310 int TextDMoveDown(textDisp *textD, int absolute)
1312 int lineStartPos, column, nextLineStartPos, newPos, visLineNum;
1314 if (textD->cursorPos == textD->buffer->length)
1315 return False;
1316 if (absolute) {
1317 lineStartPos = BufStartOfLine(textD->buffer, textD->cursorPos);
1318 visLineNum = -1;
1319 } else if (posToVisibleLineNum(textD, textD->cursorPos, &visLineNum))
1320 lineStartPos = textD->lineStarts[visLineNum];
1321 else {
1322 lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1323 visLineNum = -1;
1325 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1326 BufCountDispChars(textD->buffer, lineStartPos, textD->cursorPos);
1327 if (absolute)
1328 nextLineStartPos = BufCountForwardNLines(textD->buffer, lineStartPos, 1);
1329 else
1330 nextLineStartPos = TextDCountForwardNLines(textD, lineStartPos, 1, True);
1331 newPos = BufCountForwardDispChars(textD->buffer, nextLineStartPos, column);
1332 if (textD->continuousWrap && !absolute)
1333 newPos = min(newPos, TextDEndOfLine(textD, nextLineStartPos, True));
1334 TextDSetInsertPosition(textD, newPos);
1335 textD->cursorPreferredCol = column;
1337 return True;
1341 ** Same as BufCountLines, but takes in to account wrapping if wrapping is
1342 ** turned on. If the caller knows that startPos is at a line start, it
1343 ** can pass "startPosIsLineStart" as True to make the call more efficient
1344 ** by avoiding the additional step of scanning back to the last newline.
1346 int TextDCountLines(textDisp *textD, int startPos, int endPos,
1347 int startPosIsLineStart)
1349 int retLines, retPos, retLineStart, retLineEnd;
1351 /* If we're not wrapping use simple (and more efficient) BufCountLines */
1352 if (!textD->continuousWrap)
1353 return BufCountLines(textD->buffer, startPos, endPos);
1355 wrappedLineCounter(textD, textD->buffer, startPos, endPos, INT_MAX,
1356 startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1357 &retLineEnd);
1358 return retLines;
1362 ** Same as BufCountForwardNLines, but takes in to account line breaks when
1363 ** wrapping is turned on. If the caller knows that startPos is at a line start,
1364 ** it can pass "startPosIsLineStart" as True to make the call more efficient
1365 ** by avoiding the additional step of scanning back to the last newline.
1367 int TextDCountForwardNLines(textDisp *textD, int startPos, int nLines,
1368 int startPosIsLineStart)
1370 int retLines, retPos, retLineStart, retLineEnd;
1372 /* if we're not wrapping use more efficient BufCountForwardNLines */
1373 if (!textD->continuousWrap)
1374 return BufCountForwardNLines(textD->buffer, startPos, nLines);
1376 /* wrappedLineCounter can't handle the 0 lines case */
1377 if (nLines == 0)
1378 return startPos;
1380 /* use the common line counting routine to count forward */
1381 wrappedLineCounter(textD, textD->buffer, startPos, textD->buffer->length,
1382 nLines, startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1383 &retLineEnd);
1384 return retPos;
1388 ** Same as BufEndOfLine, but takes in to account line breaks when wrapping
1389 ** is turned on. If the caller knows that startPos is at a line start, it
1390 ** can pass "startPosIsLineStart" as True to make the call more efficient
1391 ** by avoiding the additional step of scanning back to the last newline.
1393 ** Note that the definition of the end of a line is less clear when continuous
1394 ** wrap is on. With continuous wrap off, it's just a pointer to the newline
1395 ** that ends the line. When it's on, it's the character beyond the last
1396 ** DISPLAYABLE character on the line, where a whitespace character which has
1397 ** been "converted" to a newline for wrapping is not considered displayable.
1398 ** Also note that, a line can be wrapped at a non-whitespace character if the
1399 ** line had no whitespace. In this case, this routine returns a pointer to
1400 ** the start of the next line. This is also consistent with the model used by
1401 ** visLineLength.
1403 int TextDEndOfLine(textDisp *textD, int pos, int startPosIsLineStart)
1405 int retLines, retPos, retLineStart, retLineEnd;
1407 /* If we're not wrapping use more efficien BufEndOfLine */
1408 if (!textD->continuousWrap)
1409 return BufEndOfLine(textD->buffer, pos);
1411 if (pos == textD->buffer->length)
1412 return pos;
1413 wrappedLineCounter(textD, textD->buffer, pos, textD->buffer->length, 1,
1414 startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1415 &retLineEnd);
1416 return retLineEnd;
1420 ** Same as BufStartOfLine, but returns the character after last wrap point
1421 ** rather than the last newline.
1423 int TextDStartOfLine(textDisp *textD, int pos)
1425 int retLines, retPos, retLineStart, retLineEnd;
1427 /* If we're not wrapping, use the more efficient BufStartOfLine */
1428 if (!textD->continuousWrap)
1429 return BufStartOfLine(textD->buffer, pos);
1431 wrappedLineCounter(textD, textD->buffer, BufStartOfLine(textD->buffer, pos),
1432 pos, INT_MAX, True, 0, &retPos, &retLines, &retLineStart,
1433 &retLineEnd);
1434 return retLineStart;
1438 ** Same as BufCountBackwardNLines, but takes in to account line breaks when
1439 ** wrapping is turned on.
1441 int TextDCountBackwardNLines(textDisp *textD, int startPos, int nLines)
1443 textBuffer *buf = textD->buffer;
1444 int pos, lineStart, retLines, retPos, retLineStart, retLineEnd;
1446 /* If we're not wrapping, use the more efficient BufCountBackwardNLines */
1447 if (!textD->continuousWrap)
1448 return BufCountBackwardNLines(textD->buffer, startPos, nLines);
1450 pos = startPos;
1451 while (True) {
1452 lineStart = BufStartOfLine(buf, pos);
1453 wrappedLineCounter(textD, textD->buffer, lineStart, pos, INT_MAX,
1454 True, 0, &retPos, &retLines, &retLineStart, &retLineEnd);
1455 if (retLines > nLines)
1456 return TextDCountForwardNLines(textD, lineStart, retLines-nLines,
1457 True);
1458 nLines -= retLines;
1459 pos = lineStart - 1;
1460 if (pos < 0)
1461 return 0;
1462 nLines -= 1;
1467 ** Callback attached to the text buffer to receive delete information before
1468 ** the modifications are actually made.
1470 static void bufPreDeleteCB(int pos, int nDeleted, void *cbArg)
1472 textDisp *textD = (textDisp *)cbArg;
1473 if (textD->continuousWrap &&
1474 (textD->fixedFontWidth == -1 || textD->modifyingTabDist))
1475 /* Note: we must perform this measurement, even if there is not a
1476 single character deleted; the number of "deleted" lines is the
1477 number of visual lines spanned by the real line in which the
1478 modification takes place.
1479 Also, a modification of the tab distance requires the same
1480 kind of calculations in advance, even if the font width is "fixed",
1481 because when the width of the tab characters changes, the layout
1482 of the text may be completely different. */
1483 measureDeletedLines(textD, pos, nDeleted);
1484 else
1485 textD->suppressResync = 0; /* Probably not needed, but just in case */
1489 ** Callback attached to the text buffer to receive modification information
1491 static void bufModifiedCB(int pos, int nInserted, int nDeleted,
1492 int nRestyled, char *deletedText, void *cbArg)
1494 int linesInserted, linesDeleted, startDispPos, endDispPos;
1495 textDisp *textD = (textDisp *)cbArg;
1496 textBuffer *buf = textD->buffer;
1497 int oldFirstChar = textD->firstChar;
1498 int scrolled, origCursorPos = textD->cursorPos;
1499 int wrapModStart, wrapModEnd;
1501 /* buffer modification cancels vertical cursor motion column */
1502 if (nInserted != 0 || nDeleted != 0)
1503 textD->cursorPreferredCol = -1;
1505 /* Count the number of lines inserted and deleted, and in the case
1506 of continuous wrap mode, how much has changed */
1507 if (textD->continuousWrap) {
1508 findWrapRange(textD, deletedText, pos, nInserted, nDeleted,
1509 &wrapModStart, &wrapModEnd, &linesInserted, &linesDeleted);
1510 } else {
1511 linesInserted = nInserted == 0 ? 0 :
1512 BufCountLines(buf, pos, pos + nInserted);
1513 linesDeleted = nDeleted == 0 ? 0 : countLines(deletedText);
1516 /* Update the line starts and topLineNum */
1517 if (nInserted != 0 || nDeleted != 0) {
1518 if (textD->continuousWrap) {
1519 updateLineStarts(textD, wrapModStart, wrapModEnd-wrapModStart,
1520 nDeleted + pos-wrapModStart + (wrapModEnd-(pos+nInserted)),
1521 linesInserted, linesDeleted, &scrolled);
1522 } else {
1523 updateLineStarts(textD, pos, nInserted, nDeleted, linesInserted,
1524 linesDeleted, &scrolled);
1526 } else
1527 scrolled = False;
1529 /* If we're counting non-wrapped lines as well, maintain the absolute
1530 (non-wrapped) line number of the text displayed */
1531 if (maintainingAbsTopLineNum(textD) && (nInserted != 0 || nDeleted != 0)) {
1532 if (pos + nDeleted < oldFirstChar)
1533 textD->absTopLineNum += BufCountLines(buf, pos, pos + nInserted) -
1534 countLines(deletedText);
1535 else if (pos < oldFirstChar)
1536 resetAbsLineNum(textD);
1539 /* Update the line count for the whole buffer */
1540 textD->nBufferLines += linesInserted - linesDeleted;
1542 /* Update the scroll bar ranges (and value if the value changed). Note
1543 that updating the horizontal scroll bar range requires scanning the
1544 entire displayed text, however, it doesn't seem to hurt performance
1545 much. Note also, that the horizontal scroll bar update routine is
1546 allowed to re-adjust horizOffset if there is blank space to the right
1547 of all lines of text. */
1548 updateVScrollBarRange(textD);
1549 scrolled |= updateHScrollBarRange(textD);
1551 /* Update the cursor position */
1552 if (textD->cursorToHint != NO_HINT) {
1553 textD->cursorPos = textD->cursorToHint;
1554 textD->cursorToHint = NO_HINT;
1555 } else if (textD->cursorPos > pos) {
1556 if (textD->cursorPos < pos + nDeleted)
1557 textD->cursorPos = pos;
1558 else
1559 textD->cursorPos += nInserted - nDeleted;
1562 /* If the changes caused scrolling, re-paint everything and we're done. */
1563 if (scrolled) {
1564 blankCursorProtrusions(textD);
1565 TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left,
1566 textD->height);
1567 if (textD->styleBuffer) {/* See comments in extendRangeForStyleMods */
1568 textD->styleBuffer->primary.selected = False;
1569 textD->styleBuffer->primary.zeroWidth = False;
1571 return;
1574 /* If the changes didn't cause scrolling, decide the range of characters
1575 that need to be re-painted. Also if the cursor position moved, be
1576 sure that the redisplay range covers the old cursor position so the
1577 old cursor gets erased, and erase the bits of the cursor which extend
1578 beyond the left and right edges of the text. */
1579 startDispPos = textD->continuousWrap ? wrapModStart : pos;
1580 if (origCursorPos == startDispPos && textD->cursorPos != startDispPos)
1581 startDispPos = min(startDispPos, origCursorPos-1);
1582 if (linesInserted == linesDeleted) {
1583 if (nInserted == 0 && nDeleted == 0)
1584 endDispPos = pos + nRestyled;
1585 else {
1586 endDispPos = textD->continuousWrap ? wrapModEnd :
1587 BufEndOfLine(buf, pos + nInserted) + 1;
1588 if (origCursorPos >= startDispPos &&
1589 (origCursorPos <= endDispPos || endDispPos == buf->length))
1590 blankCursorProtrusions(textD);
1592 /* If more than one line is inserted/deleted, a line break may have
1593 been inserted or removed in between, and the line numbers may
1594 have changed. If only one line is altered, line numbers cannot
1595 be affected (the insertion or removal of a line break always
1596 results in at least two lines being redrawn). */
1597 if (linesInserted > 1) redrawLineNumbers(textD, False);
1598 } else { /* linesInserted != linesDeleted */
1599 endDispPos = textD->lastChar + 1;
1600 if (origCursorPos >= pos)
1601 blankCursorProtrusions(textD);
1602 redrawLineNumbers(textD, False);
1605 /* If there is a style buffer, check if the modification caused additional
1606 changes that need to be redisplayed. (Redisplaying separately would
1607 cause double-redraw on almost every modification involving styled
1608 text). Extend the redraw range to incorporate style changes */
1609 if (textD->styleBuffer)
1610 extendRangeForStyleMods(textD, &startDispPos, &endDispPos);
1612 /* Redisplay computed range */
1613 TextDRedisplayRange(textD, startDispPos, endDispPos);
1617 ** In continuous wrap mode, internal line numbers are calculated after
1618 ** wrapping. A separate non-wrapped line count is maintained when line
1619 ** numbering is turned on. There is some performance cost to maintaining this
1620 ** line count, so normally absolute line numbers are not tracked if line
1621 ** numbering is off. This routine allows callers to specify that they still
1622 ** want this line count maintained (for use via TextDPosToLineAndCol).
1623 ** More specifically, this allows the line number reported in the statistics
1624 ** line to be calibrated in absolute lines, rather than post-wrapped lines.
1626 void TextDMaintainAbsLineNum(textDisp *textD, int state)
1628 textD->needAbsTopLineNum = state;
1629 resetAbsLineNum(textD);
1633 ** Returns the absolute (non-wrapped) line number of the first line displayed.
1634 ** Returns 0 if the absolute top line number is not being maintained.
1636 static int getAbsTopLineNum(textDisp *textD)
1638 if (!textD->continuousWrap)
1639 return textD->topLineNum;
1640 if (maintainingAbsTopLineNum(textD))
1641 return textD->absTopLineNum;
1642 return 0;
1646 ** Re-calculate absolute top line number for a change in scroll position.
1648 static void offsetAbsLineNum(textDisp *textD, int oldFirstChar)
1650 if (maintainingAbsTopLineNum(textD)) {
1651 if (textD->firstChar < oldFirstChar)
1652 textD->absTopLineNum -= BufCountLines(textD->buffer,
1653 textD->firstChar, oldFirstChar);
1654 else
1655 textD->absTopLineNum += BufCountLines(textD->buffer,
1656 oldFirstChar, textD->firstChar);
1661 ** Return true if a separate absolute top line number is being maintained
1662 ** (for displaying line numbers or showing in the statistics line).
1664 static int maintainingAbsTopLineNum(textDisp *textD)
1666 return textD->continuousWrap &&
1667 (textD->lineNumWidth != 0 || textD->needAbsTopLineNum);
1671 ** Count lines from the beginning of the buffer to reestablish the
1672 ** absolute (non-wrapped) top line number. If mode is not continuous wrap,
1673 ** or the number is not being maintained, does nothing.
1675 static void resetAbsLineNum(textDisp *textD)
1677 textD->absTopLineNum = 1;
1678 offsetAbsLineNum(textD, 0);
1682 ** Find the line number of position "pos" relative to the first line of
1683 ** displayed text. Returns False if the line is not displayed.
1685 static int posToVisibleLineNum(textDisp *textD, int pos, int *lineNum)
1687 int i;
1689 if (pos < textD->firstChar)
1690 return False;
1691 if (pos > textD->lastChar) {
1692 if (emptyLinesVisible(textD)) {
1693 if (textD->lastChar < textD->buffer->length) {
1694 if (!posToVisibleLineNum(textD, textD->lastChar, lineNum)) {
1695 fprintf(stderr, "Consistency check ptvl failed\n");
1696 return False;
1698 return ++(*lineNum) <= textD->nVisibleLines-1;
1699 } else {
1700 posToVisibleLineNum(textD, max(textD->lastChar-1, 0), lineNum);
1701 return True;
1704 return False;
1707 for (i=textD->nVisibleLines-1; i>=0; i--) {
1708 if (textD->lineStarts[i] != -1 && pos >= textD->lineStarts[i]) {
1709 *lineNum = i;
1710 return True;
1713 return False; /* probably never be reached */
1717 ** Redisplay the text on a single line represented by "visLineNum" (the
1718 ** number of lines down from the top of the display), limited by
1719 ** "leftClip" and "rightClip" window coordinates and "leftCharIndex" and
1720 ** "rightCharIndex" character positions (not including the character at
1721 ** position "rightCharIndex").
1723 ** The cursor is also drawn if it appears on the line.
1725 static void redisplayLine(textDisp *textD, int visLineNum, int leftClip,
1726 int rightClip, int leftCharIndex, int rightCharIndex)
1728 textBuffer *buf = textD->buffer;
1729 int i, x, y, startX, charIndex, lineStartPos, lineLen, fontHeight;
1730 int stdCharWidth, charWidth, startIndex, charStyle, style;
1731 int charLen, outStartIndex, outIndex, cursorX = 0, hasCursor = False;
1732 int dispIndexOffset, cursorPos = textD->cursorPos, y_orig;
1733 char expandedChar[MAX_EXP_CHAR_LEN], outStr[MAX_DISP_LINE_LEN];
1734 char *lineStr, *outPtr;
1735 char baseChar;
1737 /* If line is not displayed, skip it */
1738 if (visLineNum < 0 || visLineNum >= textD->nVisibleLines)
1739 return;
1741 /* Shrink the clipping range to the active display area */
1742 leftClip = max(textD->left, leftClip);
1743 rightClip = min(rightClip, textD->left + textD->width);
1745 if (leftClip > rightClip) {
1746 return;
1749 /* Calculate y coordinate of the string to draw */
1750 fontHeight = textD->ascent + textD->descent;
1751 y = textD->top + visLineNum * fontHeight;
1753 /* Get the text, length, and buffer position of the line to display */
1754 lineStartPos = textD->lineStarts[visLineNum];
1755 if (lineStartPos == -1) {
1756 lineLen = 0;
1757 lineStr = NULL;
1758 } else {
1759 lineLen = visLineLength(textD, visLineNum);
1760 lineStr = BufGetRange(buf, lineStartPos, lineStartPos + lineLen);
1763 /* Space beyond the end of the line is still counted in units of characters
1764 of a standardized character width (this is done mostly because style
1765 changes based on character position can still occur in this region due
1766 to rectangular selections). stdCharWidth must be non-zero to prevent a
1767 potential infinite loop if x does not advance */
1768 stdCharWidth = textD->fontStruct->max_bounds.width;
1769 if (stdCharWidth <= 0) {
1770 fprintf(stderr, "Internal Error, bad font measurement\n");
1771 XtFree(lineStr);
1772 return;
1775 /* Rectangular selections are based on "real" line starts (after a newline
1776 or start of buffer). Calculate the difference between the last newline
1777 position and the line start we're using. Since scanning back to find a
1778 newline is expensive, only do so if there's actually a rectangular
1779 selection which needs it */
1780 if (textD->continuousWrap && (rangeTouchesRectSel(&buf->primary,
1781 lineStartPos, lineStartPos + lineLen) || rangeTouchesRectSel(
1782 &buf->secondary, lineStartPos, lineStartPos + lineLen) ||
1783 rangeTouchesRectSel(&buf->highlight, lineStartPos,
1784 lineStartPos + lineLen))) {
1785 dispIndexOffset = BufCountDispChars(buf,
1786 BufStartOfLine(buf, lineStartPos), lineStartPos);
1787 } else
1788 dispIndexOffset = 0;
1790 /* Step through character positions from the beginning of the line (even if
1791 that's off the left edge of the displayed area) to find the first
1792 character position that's not clipped, and the x coordinate for drawing
1793 that character */
1794 x = textD->left - textD->horizOffset;
1795 outIndex = 0;
1796 for(charIndex=0; ; charIndex++) {
1797 baseChar = '\0';
1798 charLen = charIndex >= lineLen ? 1 :
1799 BufExpandCharacter(baseChar = lineStr[charIndex], outIndex,
1800 expandedChar, buf->tabDist, buf->nullSubsChar);
1801 style = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1802 outIndex + dispIndexOffset, baseChar);
1803 charWidth = charIndex >= lineLen ? stdCharWidth :
1804 stringWidth(textD, expandedChar, charLen, style);
1805 if (x + charWidth >= leftClip && charIndex >= leftCharIndex) {
1806 startIndex = charIndex;
1807 outStartIndex = outIndex;
1808 startX = x;
1809 break;
1811 x += charWidth;
1812 outIndex += charLen;
1815 /* Scan character positions from the beginning of the clipping range, and
1816 draw parts whenever the style changes (also note if the cursor is on
1817 this line, and where it should be drawn to take advantage of the x
1818 position which we've gone to so much trouble to calculate) */
1819 outPtr = outStr;
1820 outIndex = outStartIndex;
1821 x = startX;
1822 for(charIndex=startIndex; charIndex<rightCharIndex; charIndex++) {
1823 if (lineStartPos+charIndex == cursorPos) {
1824 if (charIndex < lineLen || (charIndex == lineLen &&
1825 cursorPos >= buf->length)) {
1826 hasCursor = True;
1827 cursorX = x - 1;
1828 } else if (charIndex == lineLen) {
1829 if (wrapUsesCharacter(textD, cursorPos)) {
1830 hasCursor = True;
1831 cursorX = x - 1;
1835 baseChar = '\0';
1836 charLen = charIndex >= lineLen ? 1 :
1837 BufExpandCharacter(baseChar = lineStr[charIndex], outIndex,
1838 expandedChar, buf->tabDist, buf->nullSubsChar);
1839 charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1840 outIndex + dispIndexOffset, baseChar);
1841 for (i=0; i<charLen; i++) {
1842 if (i != 0 && charIndex < lineLen && lineStr[charIndex] == '\t')
1843 charStyle = styleOfPos(textD, lineStartPos, lineLen,
1844 charIndex, outIndex + dispIndexOffset, '\t');
1845 if (charStyle != style) {
1846 drawString(textD, style, startX, y, x, outStr, outPtr - outStr);
1847 outPtr = outStr;
1848 startX = x;
1849 style = charStyle;
1851 if (charIndex < lineLen) {
1852 *outPtr = expandedChar[i];
1853 charWidth = stringWidth(textD, &expandedChar[i], 1, charStyle);
1854 } else
1855 charWidth = stdCharWidth;
1856 outPtr++;
1857 x += charWidth;
1858 outIndex++;
1860 if (outPtr-outStr+MAX_EXP_CHAR_LEN>=MAX_DISP_LINE_LEN || x>=rightClip)
1861 break;
1864 /* Draw the remaining style segment */
1865 drawString(textD, style, startX, y, x, outStr, outPtr - outStr);
1867 /* Draw the cursor if part of it appeared on the redisplayed part of
1868 this line. Also check for the cases which are not caught as the
1869 line is scanned above: when the cursor appears at the very end
1870 of the redisplayed section. */
1871 y_orig = textD->cursorY;
1872 if (textD->cursorOn) {
1873 if (hasCursor)
1874 drawCursor(textD, cursorX, y);
1875 else if (charIndex<lineLen && (lineStartPos+charIndex+1 == cursorPos)
1876 && x == rightClip) {
1877 if (cursorPos >= buf->length)
1878 drawCursor(textD, x - 1, y);
1879 else {
1880 if (wrapUsesCharacter(textD, cursorPos))
1881 drawCursor(textD, x - 1, y);
1886 /* If the y position of the cursor has changed, redraw the calltip */
1887 if (hasCursor && (y_orig != textD->cursorY || y_orig != y))
1888 TextDRedrawCalltip(textD, 0);
1890 if (lineStr != NULL)
1891 XtFree(lineStr);
1895 ** Draw a string or blank area according to parameter "style", using the
1896 ** appropriate colors and drawing method for that style, with top left
1897 ** corner at x, y. If style says to draw text, use "string" as source of
1898 ** characters, and draw "nChars", if style is FILL, erase
1899 ** rectangle where text would have drawn from x to toX and from y to
1900 ** the maximum y extent of the current font(s).
1902 static void drawString(textDisp *textD, int style, int x, int y, int toX,
1903 char *string, int nChars)
1905 GC gc, bgGC;
1906 XGCValues gcValues;
1907 XFontStruct *fs = textD->fontStruct;
1908 Pixel bground = textD->bgPixel;
1909 Pixel fground = textD->fgPixel;
1910 int underlineStyle = FALSE;
1912 /* Don't draw if widget isn't realized */
1913 if (XtWindow(textD->w) == 0)
1914 return;
1916 /* select a GC */
1917 if (style & (STYLE_LOOKUP_MASK | BACKLIGHT_MASK | RANGESET_MASK)) {
1918 gc = bgGC = textD->styleGC;
1920 else if (style & HIGHLIGHT_MASK) {
1921 gc = textD->highlightGC;
1922 bgGC = textD->highlightBGGC;
1924 else if (style & PRIMARY_MASK) {
1925 gc = textD->selectGC;
1926 bgGC = textD->selectBGGC;
1928 else {
1929 gc = bgGC = textD->gc;
1932 if (gc == textD->styleGC) {
1933 /* we have work to do */
1934 styleTableEntry *styleRec;
1935 /* Set font, color, and gc depending on style. For normal text, GCs
1936 for normal drawing, or drawing within a selection or highlight are
1937 pre-allocated and pre-configured. For syntax highlighting, GCs are
1938 configured here, on the fly. */
1939 if (style & STYLE_LOOKUP_MASK) {
1940 styleRec = &textD->styleTable[(style & STYLE_LOOKUP_MASK) - ASCII_A];
1941 underlineStyle = styleRec->underline;
1942 fs = styleRec->font;
1943 gcValues.font = fs->fid;
1944 fground = styleRec->color;
1945 /* here you could pick up specific select and highlight fground */
1947 else {
1948 styleRec = NULL;
1949 gcValues.font = fs->fid;
1950 fground = textD->fgPixel;
1952 /* Background color priority order is:
1953 1 Primary(Selection), 2 Highlight(Parens),
1954 3 Rangeset, 4 SyntaxHighlightStyle,
1955 5 Backlight (if NOT fill), 6 DefaultBackground */
1956 bground =
1957 style & PRIMARY_MASK ? textD->selectBGPixel :
1958 style & HIGHLIGHT_MASK ? textD->highlightBGPixel :
1959 style & RANGESET_MASK ?
1960 getRangesetColor(textD,
1961 (style&RANGESET_MASK)>>RANGESET_SHIFT,
1962 bground) :
1963 styleRec && styleRec->bgColorName ? styleRec->bgColor :
1964 (style & BACKLIGHT_MASK) && !(style & FILL_MASK) ?
1965 textD->bgClassPixel[(style>>BACKLIGHT_SHIFT) & 0xff] :
1966 textD->bgPixel;
1967 if (fground == bground) /* B&W kludge */
1968 fground = textD->bgPixel;
1969 /* set up gc for clearing using the foreground color entry */
1970 gcValues.foreground = gcValues.background = bground;
1971 XChangeGC(XtDisplay(textD->w), gc,
1972 GCFont | GCForeground | GCBackground, &gcValues);
1975 /* Draw blank area rather than text, if that was the request */
1976 if (style & FILL_MASK) {
1977 /* wipes out to right hand edge of widget */
1978 if (toX >= textD->left)
1979 clearRect(textD, bgGC, max(x, textD->left), y,
1980 toX - max(x, textD->left), textD->ascent + textD->descent);
1981 return;
1984 /* If any space around the character remains unfilled (due to use of
1985 different sized fonts for highlighting), fill in above or below
1986 to erase previously drawn characters */
1987 if (fs->ascent < textD->ascent)
1988 clearRect(textD, bgGC, x, y, toX - x, textD->ascent - fs->ascent);
1989 if (fs->descent < textD->descent)
1990 clearRect(textD, bgGC, x, y + textD->ascent + fs->descent, toX - x,
1991 textD->descent - fs->descent);
1993 /* set up gc for writing text (set foreground properly) */
1994 if (bgGC == textD->styleGC) {
1995 gcValues.foreground = fground;
1996 XChangeGC(XtDisplay(textD->w), gc, GCForeground, &gcValues);
1999 /* Draw the string using gc and font set above */
2000 XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w), gc, x,
2001 y + textD->ascent, string, nChars);
2003 /* Underline if style is secondary selection */
2004 if (style & SECONDARY_MASK || underlineStyle)
2006 /* restore foreground in GC (was set to background by clearRect()) */
2007 gcValues.foreground = fground;
2008 XChangeGC(XtDisplay(textD->w), gc,
2009 GCForeground, &gcValues);
2010 /* draw underline */
2011 XDrawLine(XtDisplay(textD->w), XtWindow(textD->w), gc, x,
2012 y + textD->ascent, toX - 1, y + textD->ascent);
2017 ** Clear a rectangle with the appropriate background color for "style"
2019 static void clearRect(textDisp *textD, GC gc, int x, int y,
2020 int width, int height)
2022 /* A width of zero means "clear to end of window" to XClearArea */
2023 if (width == 0)
2024 return;
2026 if (gc == textD->gc) {
2027 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), x, y,
2028 width, height, False);
2030 else {
2031 XFillRectangle(XtDisplay(textD->w), XtWindow(textD->w),
2032 gc, x, y, width, height);
2037 ** Draw a cursor with top center at x, y.
2039 static void drawCursor(textDisp *textD, int x, int y)
2041 XSegment segs[5];
2042 int left, right, cursorWidth, midY;
2043 int fontWidth = textD->fontStruct->min_bounds.width, nSegs = 0;
2044 int fontHeight = textD->ascent + textD->descent;
2045 int bot = y + fontHeight - 1;
2047 if (XtWindow(textD->w) == 0 || x < textD->left-1 ||
2048 x > textD->left + textD->width)
2049 return;
2051 /* For cursors other than the block, make them around 2/3 of a character
2052 width, rounded to an even number of pixels so that X will draw an
2053 odd number centered on the stem at x. */
2054 cursorWidth = (fontWidth/3) * 2;
2055 left = x - cursorWidth/2;
2056 right = left + cursorWidth;
2058 /* Create segments and draw cursor */
2059 if (textD->cursorStyle == CARET_CURSOR) {
2060 midY = bot - fontHeight/5;
2061 segs[0].x1 = left; segs[0].y1 = bot; segs[0].x2 = x; segs[0].y2 = midY;
2062 segs[1].x1 = x; segs[1].y1 = midY; segs[1].x2 = right; segs[1].y2 = bot;
2063 segs[2].x1 = left; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2=midY-1;
2064 segs[3].x1 = x; segs[3].y1=midY-1; segs[3].x2 = right; segs[3].y2 = bot;
2065 nSegs = 4;
2066 } else if (textD->cursorStyle == NORMAL_CURSOR) {
2067 segs[0].x1 = left; segs[0].y1 = y; segs[0].x2 = right; segs[0].y2 = y;
2068 segs[1].x1 = x; segs[1].y1 = y; segs[1].x2 = x; segs[1].y2 = bot;
2069 segs[2].x1 = left; segs[2].y1 = bot; segs[2].x2 = right; segs[2].y2=bot;
2070 nSegs = 3;
2071 } else if (textD->cursorStyle == HEAVY_CURSOR) {
2072 segs[0].x1 = x-1; segs[0].y1 = y; segs[0].x2 = x-1; segs[0].y2 = bot;
2073 segs[1].x1 = x; segs[1].y1 = y; segs[1].x2 = x; segs[1].y2 = bot;
2074 segs[2].x1 = x+1; segs[2].y1 = y; segs[2].x2 = x+1; segs[2].y2 = bot;
2075 segs[3].x1 = left; segs[3].y1 = y; segs[3].x2 = right; segs[3].y2 = y;
2076 segs[4].x1 = left; segs[4].y1 = bot; segs[4].x2 = right; segs[4].y2=bot;
2077 nSegs = 5;
2078 } else if (textD->cursorStyle == DIM_CURSOR) {
2079 midY = y + fontHeight/2;
2080 segs[0].x1 = x; segs[0].y1 = y; segs[0].x2 = x; segs[0].y2 = y;
2081 segs[1].x1 = x; segs[1].y1 = midY; segs[1].x2 = x; segs[1].y2 = midY;
2082 segs[2].x1 = x; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2 = bot;
2083 nSegs = 3;
2084 } else if (textD->cursorStyle == BLOCK_CURSOR) {
2085 right = x + fontWidth;
2086 segs[0].x1 = x; segs[0].y1 = y; segs[0].x2 = right; segs[0].y2 = y;
2087 segs[1].x1 = right; segs[1].y1 = y; segs[1].x2 = right; segs[1].y2=bot;
2088 segs[2].x1 = right; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2 = bot;
2089 segs[3].x1 = x; segs[3].y1 = bot; segs[3].x2 = x; segs[3].y2 = y;
2090 nSegs = 4;
2092 XDrawSegments(XtDisplay(textD->w), XtWindow(textD->w),
2093 textD->cursorFGGC, segs, nSegs);
2095 /* Save the last position drawn */
2096 textD->cursorX = x;
2097 textD->cursorY = y;
2101 ** Determine the drawing method to use to draw a specific character from "buf".
2102 ** "lineStartPos" gives the character index where the line begins, "lineIndex",
2103 ** the number of characters past the beginning of the line, and "dispIndex",
2104 ** the number of displayed characters past the beginning of the line. Passing
2105 ** lineStartPos of -1 returns the drawing style for "no text".
2107 ** Why not just: styleOfPos(textD, pos)? Because style applies to blank areas
2108 ** of the window beyond the text boundaries, and because this routine must also
2109 ** decide whether a position is inside of a rectangular selection, and do so
2110 ** efficiently, without re-counting character positions from the start of the
2111 ** line.
2113 ** Note that style is a somewhat incorrect name, drawing method would
2114 ** be more appropriate.
2116 static int styleOfPos(textDisp *textD, int lineStartPos,
2117 int lineLen, int lineIndex, int dispIndex, int thisChar)
2119 textBuffer *buf = textD->buffer;
2120 textBuffer *styleBuf = textD->styleBuffer;
2121 int pos, style = 0;
2123 if (lineStartPos == -1 || buf == NULL)
2124 return FILL_MASK;
2126 pos = lineStartPos + min(lineIndex, lineLen);
2128 if (lineIndex >= lineLen)
2129 style = FILL_MASK;
2130 else if (styleBuf != NULL) {
2131 style = (unsigned char)BufGetCharacter(styleBuf, pos);
2132 if (style == textD->unfinishedStyle) {
2133 /* encountered "unfinished" style, trigger parsing */
2134 (textD->unfinishedHighlightCB)(textD, pos, textD->highlightCBArg);
2135 style = (unsigned char)BufGetCharacter(styleBuf, pos);
2138 if (inSelection(&buf->primary, pos, lineStartPos, dispIndex))
2139 style |= PRIMARY_MASK;
2140 if (inSelection(&buf->highlight, pos, lineStartPos, dispIndex))
2141 style |= HIGHLIGHT_MASK;
2142 if (inSelection(&buf->secondary, pos, lineStartPos, dispIndex))
2143 style |= SECONDARY_MASK;
2144 /* store in the RANGESET_MASK portion of style the rangeset index for pos */
2145 if (buf->rangesetTable) {
2146 int rangesetIndex = RangesetIndex1ofPos(buf->rangesetTable, pos, True);
2147 style |= ((rangesetIndex << RANGESET_SHIFT) & RANGESET_MASK);
2149 /* store in the BACKLIGHT_MASK portion of style the background color class
2150 of the character thisChar */
2151 if (textD->bgClass)
2153 style |= (textD->bgClass[(unsigned char)thisChar]<<BACKLIGHT_SHIFT);
2155 return style;
2159 ** Find the width of a string in the font of a particular style
2161 static int stringWidth(textDisp *textD, char *string, int length, int style)
2163 XFontStruct *fs;
2165 if (style & STYLE_LOOKUP_MASK)
2166 fs = textD->styleTable[(style & STYLE_LOOKUP_MASK) - ASCII_A].font;
2167 else
2168 fs = textD->fontStruct;
2169 return XTextWidth(fs, string, length);
2173 ** Return true if position "pos" with indentation "dispIndex" is in
2174 ** selection "sel"
2176 static int inSelection(selection *sel, int pos, int lineStartPos, int dispIndex)
2178 return sel->selected &&
2179 ((!sel->rectangular &&
2180 pos >= sel->start && pos < sel->end) ||
2181 (sel->rectangular &&
2182 pos >= sel->start && lineStartPos <= sel->end &&
2183 dispIndex >= sel->rectStart && dispIndex < sel->rectEnd));
2187 ** Translate window coordinates to the nearest (insert cursor or character
2188 ** cell) text position. The parameter posType specifies how to interpret the
2189 ** position: CURSOR_POS means translate the coordinates to the nearest cursor
2190 ** position, and CHARACTER_POS means return the position of the character
2191 ** closest to (x, y).
2193 static int xyToPos(textDisp *textD, int x, int y, int posType)
2195 int charIndex, lineStart, lineLen, fontHeight;
2196 int charWidth, charLen, charStyle, visLineNum, xStep, outIndex;
2197 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
2199 /* Find the visible line number corresponding to the y coordinate */
2200 fontHeight = textD->ascent + textD->descent;
2201 visLineNum = (y - textD->top) / fontHeight;
2202 if (visLineNum < 0)
2203 return textD->firstChar;
2204 if (visLineNum >= textD->nVisibleLines)
2205 visLineNum = textD->nVisibleLines - 1;
2207 /* Find the position at the start of the line */
2208 lineStart = textD->lineStarts[visLineNum];
2210 /* If the line start was empty, return the last position in the buffer */
2211 if (lineStart == -1)
2212 return textD->buffer->length;
2214 /* Get the line text and its length */
2215 lineLen = visLineLength(textD, visLineNum);
2216 lineStr = BufGetRange(textD->buffer, lineStart, lineStart + lineLen);
2218 /* Step through character positions from the beginning of the line
2219 to find the character position corresponding to the x coordinate */
2220 xStep = textD->left - textD->horizOffset;
2221 outIndex = 0;
2222 for(charIndex=0; charIndex<lineLen; charIndex++) {
2223 charLen = BufExpandCharacter(lineStr[charIndex], outIndex, expandedChar,
2224 textD->buffer->tabDist, textD->buffer->nullSubsChar);
2225 charStyle = styleOfPos(textD, lineStart, lineLen, charIndex, outIndex,
2226 lineStr[charIndex]);
2227 charWidth = stringWidth(textD, expandedChar, charLen, charStyle);
2228 if (x < xStep + (posType == CURSOR_POS ? charWidth/2 : charWidth)) {
2229 XtFree(lineStr);
2230 return lineStart + charIndex;
2232 xStep += charWidth;
2233 outIndex += charLen;
2236 /* If the x position was beyond the end of the line, return the position
2237 of the newline at the end of the line */
2238 XtFree(lineStr);
2239 return lineStart + lineLen;
2243 ** Translate window coordinates to the nearest row and column number for
2244 ** positioning the cursor. This, of course, makes no sense when the font is
2245 ** proportional, since there are no absolute columns. The parameter posType
2246 ** specifies how to interpret the position: CURSOR_POS means translate the
2247 ** coordinates to the nearest position between characters, and CHARACTER_POS
2248 ** means translate the position to the nearest character cell.
2250 static void xyToUnconstrainedPos(textDisp *textD, int x, int y, int *row,
2251 int *column, int posType)
2253 int fontHeight = textD->ascent + textD->descent;
2254 int fontWidth = textD->fontStruct->max_bounds.width;
2256 /* Find the visible line number corresponding to the y coordinate */
2257 *row = (y - textD->top) / fontHeight;
2258 if (*row < 0) *row = 0;
2259 if (*row >= textD->nVisibleLines) *row = textD->nVisibleLines - 1;
2260 *column = ((x-textD->left) + textD->horizOffset +
2261 (posType == CURSOR_POS ? fontWidth/2 : 0)) / fontWidth;
2262 if (*column < 0) *column = 0;
2266 ** Offset the line starts array, topLineNum, firstChar and lastChar, for a new
2267 ** vertical scroll position given by newTopLineNum. If any currently displayed
2268 ** lines will still be visible, salvage the line starts values, otherwise,
2269 ** count lines from the nearest known line start (start or end of buffer, or
2270 ** the closest value in the lineStarts array)
2272 static void offsetLineStarts(textDisp *textD, int newTopLineNum)
2274 int oldTopLineNum = textD->topLineNum;
2275 int oldFirstChar = textD->firstChar;
2276 int lineDelta = newTopLineNum - oldTopLineNum;
2277 int nVisLines = textD->nVisibleLines;
2278 int *lineStarts = textD->lineStarts;
2279 int i, lastLineNum;
2280 textBuffer *buf = textD->buffer;
2282 /* If there was no offset, nothing needs to be changed */
2283 if (lineDelta == 0)
2284 return;
2286 /* { int i;
2287 printf("Scroll, lineDelta %d\n", lineDelta);
2288 printf("lineStarts Before: ");
2289 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2290 printf("\n");
2291 } */
2293 /* Find the new value for firstChar by counting lines from the nearest
2294 known line start (start or end of buffer, or the closest value in the
2295 lineStarts array) */
2296 lastLineNum = oldTopLineNum + nVisLines - 1;
2297 if (newTopLineNum < oldTopLineNum && newTopLineNum < -lineDelta) {
2298 textD->firstChar = TextDCountForwardNLines(textD, 0, newTopLineNum-1,
2299 True);
2300 /* printf("counting forward %d lines from start\n", newTopLineNum-1);*/
2301 } else if (newTopLineNum < oldTopLineNum) {
2302 textD->firstChar = TextDCountBackwardNLines(textD, textD->firstChar,
2303 -lineDelta);
2304 /* printf("counting backward %d lines from firstChar\n", -lineDelta);*/
2305 } else if (newTopLineNum < lastLineNum) {
2306 textD->firstChar = lineStarts[newTopLineNum - oldTopLineNum];
2307 /* printf("taking new start from lineStarts[%d]\n",
2308 newTopLineNum - oldTopLineNum); */
2309 } else if (newTopLineNum-lastLineNum < textD->nBufferLines-newTopLineNum) {
2310 textD->firstChar = TextDCountForwardNLines(textD,
2311 lineStarts[nVisLines-1], newTopLineNum - lastLineNum, True);
2312 /* printf("counting forward %d lines from start of last line\n",
2313 newTopLineNum - lastLineNum); */
2314 } else {
2315 textD->firstChar = TextDCountBackwardNLines(textD, buf->length,
2316 textD->nBufferLines - newTopLineNum + 1);
2317 /* printf("counting backward %d lines from end\n",
2318 textD->nBufferLines - newTopLineNum + 1); */
2321 /* Fill in the line starts array */
2322 if (lineDelta < 0 && -lineDelta < nVisLines) {
2323 for (i=nVisLines-1; i >= -lineDelta; i--)
2324 lineStarts[i] = lineStarts[i+lineDelta];
2325 calcLineStarts(textD, 0, -lineDelta);
2326 } else if (lineDelta > 0 && lineDelta < nVisLines) {
2327 for (i=0; i<nVisLines-lineDelta; i++)
2328 lineStarts[i] = lineStarts[i+lineDelta];
2329 calcLineStarts(textD, nVisLines-lineDelta, nVisLines-1);
2330 } else
2331 calcLineStarts(textD, 0, nVisLines);
2333 /* Set lastChar and topLineNum */
2334 calcLastChar(textD);
2335 textD->topLineNum = newTopLineNum;
2337 /* If we're numbering lines or being asked to maintain an absolute line
2338 number, re-calculate the absolute line number */
2339 offsetAbsLineNum(textD, oldFirstChar);
2341 /* { int i;
2342 printf("lineStarts After: ");
2343 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2344 printf("\n");
2345 } */
2349 ** Update the line starts array, topLineNum, firstChar and lastChar for text
2350 ** display "textD" after a modification to the text buffer, given by the
2351 ** position where the change began "pos", and the nmubers of characters
2352 ** and lines inserted and deleted.
2354 static void updateLineStarts(textDisp *textD, int pos, int charsInserted,
2355 int charsDeleted, int linesInserted, int linesDeleted, int *scrolled)
2357 int *lineStarts = textD->lineStarts;
2358 int i, lineOfPos, lineOfEnd, nVisLines = textD->nVisibleLines;
2359 int charDelta = charsInserted - charsDeleted;
2360 int lineDelta = linesInserted - linesDeleted;
2362 /* { int i;
2363 printf("linesDeleted %d, linesInserted %d, charsInserted %d, charsDeleted %d\n",
2364 linesDeleted, linesInserted, charsInserted, charsDeleted);
2365 printf("lineStarts Before: ");
2366 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2367 printf("\n");
2368 } */
2369 /* If all of the changes were before the displayed text, the display
2370 doesn't change, just update the top line num and offset the line
2371 start entries and first and last characters */
2372 if (pos + charsDeleted < textD->firstChar) {
2373 textD->topLineNum += lineDelta;
2374 for (i=0; i<nVisLines && lineStarts[i] != -1; i++)
2375 lineStarts[i] += charDelta;
2376 /* { int i;
2377 printf("lineStarts after delete doesn't touch: ");
2378 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2379 printf("\n");
2380 } */
2381 textD->firstChar += charDelta;
2382 textD->lastChar += charDelta;
2383 *scrolled = False;
2384 return;
2387 /* The change began before the beginning of the displayed text, but
2388 part or all of the displayed text was deleted */
2389 if (pos < textD->firstChar) {
2390 /* If some text remains in the window, anchor on that */
2391 if (posToVisibleLineNum(textD, pos + charsDeleted, &lineOfEnd) &&
2392 ++lineOfEnd < nVisLines && lineStarts[lineOfEnd] != -1) {
2393 textD->topLineNum = max(1, textD->topLineNum + lineDelta);
2394 textD->firstChar = TextDCountBackwardNLines(textD,
2395 lineStarts[lineOfEnd] + charDelta, lineOfEnd);
2396 /* Otherwise anchor on original line number and recount everything */
2397 } else {
2398 if (textD->topLineNum > textD->nBufferLines + lineDelta) {
2399 textD->topLineNum = 1;
2400 textD->firstChar = 0;
2401 } else
2402 textD->firstChar = TextDCountForwardNLines(textD, 0,
2403 textD->topLineNum - 1, True);
2405 calcLineStarts(textD, 0, nVisLines-1);
2406 /* { int i;
2407 printf("lineStarts after delete encroaches: ");
2408 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2409 printf("\n");
2410 } */
2411 /* calculate lastChar by finding the end of the last displayed line */
2412 calcLastChar(textD);
2413 *scrolled = True;
2414 return;
2417 /* If the change was in the middle of the displayed text (it usually is),
2418 salvage as much of the line starts array as possible by moving and
2419 offsetting the entries after the changed area, and re-counting the
2420 added lines or the lines beyond the salvaged part of the line starts
2421 array */
2422 if (pos <= textD->lastChar) {
2423 /* find line on which the change began */
2424 posToVisibleLineNum(textD, pos, &lineOfPos);
2425 /* salvage line starts after the changed area */
2426 if (lineDelta == 0) {
2427 for (i=lineOfPos+1; i<nVisLines && lineStarts[i]!= -1; i++)
2428 lineStarts[i] += charDelta;
2429 } else if (lineDelta > 0) {
2430 for (i=nVisLines-1; i>=lineOfPos+lineDelta+1; i--)
2431 lineStarts[i] = lineStarts[i-lineDelta] +
2432 (lineStarts[i-lineDelta] == -1 ? 0 : charDelta);
2433 } else /* (lineDelta < 0) */ {
2434 for (i=max(0,lineOfPos+1); i<nVisLines+lineDelta; i++)
2435 lineStarts[i] = lineStarts[i-lineDelta] +
2436 (lineStarts[i-lineDelta] == -1 ? 0 : charDelta);
2438 /* { int i;
2439 printf("lineStarts after salvage: ");
2440 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2441 printf("\n");
2442 } */
2443 /* fill in the missing line starts */
2444 if (linesInserted >= 0)
2445 calcLineStarts(textD, lineOfPos + 1, lineOfPos + linesInserted);
2446 if (lineDelta < 0)
2447 calcLineStarts(textD, nVisLines+lineDelta, nVisLines);
2448 /* { int i;
2449 printf("lineStarts after recalculation: ");
2450 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2451 printf("\n");
2452 } */
2453 /* calculate lastChar by finding the end of the last displayed line */
2454 calcLastChar(textD);
2455 *scrolled = False;
2456 return;
2459 /* Change was past the end of the displayed text, but displayable by virtue
2460 of being an insert at the end of the buffer into visible blank lines */
2461 if (emptyLinesVisible(textD)) {
2462 posToVisibleLineNum(textD, pos, &lineOfPos);
2463 calcLineStarts(textD, lineOfPos, lineOfPos+linesInserted);
2464 calcLastChar(textD);
2465 /* { int i;
2466 printf("lineStarts after insert at end: ");
2467 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2468 printf("\n");
2469 } */
2470 *scrolled = False;
2471 return;
2474 /* Change was beyond the end of the buffer and not visible, do nothing */
2475 *scrolled = False;
2479 ** Scan through the text in the "textD"'s buffer and recalculate the line
2480 ** starts array values beginning at index "startLine" and continuing through
2481 ** (including) "endLine". It assumes that the line starts entry preceding
2482 ** "startLine" (or textD->firstChar if startLine is 0) is good, and re-counts
2483 ** newlines to fill in the requested entries. Out of range values for
2484 ** "startLine" and "endLine" are acceptable.
2486 static void calcLineStarts(textDisp *textD, int startLine, int endLine)
2488 int startPos, bufLen = textD->buffer->length;
2489 int line, lineEnd, nextLineStart, nVis = textD->nVisibleLines;
2490 int *lineStarts = textD->lineStarts;
2492 /* Clean up (possibly) messy input parameters */
2493 if (nVis == 0) return;
2494 if (endLine < 0) endLine = 0;
2495 if (endLine >= nVis) endLine = nVis - 1;
2496 if (startLine < 0) startLine = 0;
2497 if (startLine >=nVis) startLine = nVis - 1;
2498 if (startLine > endLine)
2499 return;
2501 /* Find the last known good line number -> position mapping */
2502 if (startLine == 0) {
2503 lineStarts[0] = textD->firstChar;
2504 startLine = 1;
2506 startPos = lineStarts[startLine-1];
2508 /* If the starting position is already past the end of the text,
2509 fill in -1's (means no text on line) and return */
2510 if (startPos == -1) {
2511 for (line=startLine; line<=endLine; line++)
2512 lineStarts[line] = -1;
2513 return;
2516 /* Loop searching for ends of lines and storing the positions of the
2517 start of the next line in lineStarts */
2518 for (line=startLine; line<=endLine; line++) {
2519 findLineEnd(textD, startPos, True, &lineEnd, &nextLineStart);
2520 startPos = nextLineStart;
2521 if (startPos >= bufLen) {
2522 /* If the buffer ends with a newline or line break, put
2523 buf->length in the next line start position (instead of
2524 a -1 which is the normal marker for an empty line) to
2525 indicate that the cursor may safely be displayed there */
2526 if (line == 0 || (lineStarts[line-1] != bufLen &&
2527 lineEnd != nextLineStart)) {
2528 lineStarts[line] = bufLen;
2529 line++;
2531 break;
2533 lineStarts[line] = startPos;
2536 /* Set any entries beyond the end of the text to -1 */
2537 for (; line<=endLine; line++)
2538 lineStarts[line] = -1;
2542 ** Given a textDisp with a complete, up-to-date lineStarts array, update
2543 ** the lastChar entry to point to the last buffer position displayed.
2545 static void calcLastChar(textDisp *textD)
2547 int i;
2549 for (i=textD->nVisibleLines-1; i>0 && textD->lineStarts[i]== -1; i--);
2550 textD->lastChar = i < 0 ? 0 :
2551 TextDEndOfLine(textD, textD->lineStarts[i], True);
2554 void TextDImposeGraphicsExposeTranslation(textDisp *textD, int *xOffset, int *yOffset)
2556 if (textD->graphicsExposeQueue) {
2557 graphicExposeTranslationEntry *thisGEQEntry = textD->graphicsExposeQueue->next;
2558 if (thisGEQEntry) {
2559 *xOffset += thisGEQEntry->horizontal;
2560 *yOffset += thisGEQEntry->vertical;
2565 Boolean TextDPopGraphicExposeQueueEntry(textDisp *textD)
2567 graphicExposeTranslationEntry *removedGEQEntry = textD->graphicsExposeQueue;
2569 if (removedGEQEntry) {
2570 textD->graphicsExposeQueue = removedGEQEntry->next;
2571 XtFree((char *)removedGEQEntry);
2573 return(removedGEQEntry?True:False);
2576 void TextDTranlateGraphicExposeQueue(textDisp *textD, int xOffset, int yOffset, Boolean appendEntry)
2578 graphicExposeTranslationEntry *newGEQEntry = NULL;
2579 if (appendEntry) {
2580 newGEQEntry = (graphicExposeTranslationEntry *)XtMalloc(sizeof(graphicExposeTranslationEntry));
2581 newGEQEntry->next = NULL;
2582 newGEQEntry->horizontal = xOffset;
2583 newGEQEntry->vertical = yOffset;
2585 if (textD->graphicsExposeQueue) {
2586 graphicExposeTranslationEntry *iter = textD->graphicsExposeQueue;
2587 while (iter->next) {
2588 iter->next->horizontal += xOffset;
2589 iter->next->vertical += yOffset;
2590 iter = iter->next;
2592 if (appendEntry) {
2593 iter->next = (struct graphicExposeTranslationEntry *)newGEQEntry;
2596 else {
2597 if (appendEntry) {
2598 textD->graphicsExposeQueue = newGEQEntry;
2603 static void setScroll(textDisp *textD, int topLineNum, int horizOffset,
2604 int updateVScrollBar, int updateHScrollBar)
2606 int fontHeight = textD->ascent + textD->descent;
2607 int origHOffset = textD->horizOffset;
2608 int lineDelta = textD->topLineNum - topLineNum;
2609 int xOffset, yOffset, srcX, srcY, dstX, dstY, width, height;
2610 int exactHeight = textD->height - textD->height %
2611 (textD->ascent + textD->descent);
2613 /* Do nothing if scroll position hasn't actually changed or there's no
2614 window to draw in yet */
2615 if (XtWindow(textD->w) == 0 || (textD->horizOffset == horizOffset &&
2616 textD->topLineNum == topLineNum))
2617 return;
2619 /* If part of the cursor is protruding beyond the text clipping region,
2620 clear it off */
2621 blankCursorProtrusions(textD);
2623 /* If the vertical scroll position has changed, update the line
2624 starts array and related counters in the text display */
2625 offsetLineStarts(textD, topLineNum);
2627 /* Just setting textD->horizOffset is enough information for redisplay */
2628 textD->horizOffset = horizOffset;
2630 /* Update the scroll bar positions if requested, note: updating the
2631 horizontal scroll bars can have the further side-effect of changing
2632 the horizontal scroll position, textD->horizOffset */
2633 if (updateVScrollBar && textD->vScrollBar != NULL)
2634 updateVScrollBarRange(textD);
2635 if (updateHScrollBar && textD->hScrollBar != NULL) {
2636 updateHScrollBarRange(textD);
2639 /* Redisplay everything if the window is partially obscured (since
2640 it's too hard to tell what displayed areas are salvageable) or
2641 if there's nothing to recover because the scroll distance is large */
2642 xOffset = origHOffset - textD->horizOffset;
2643 yOffset = lineDelta * fontHeight;
2644 if (abs(xOffset) > textD->width || abs(yOffset) > exactHeight) {
2645 TextDTranlateGraphicExposeQueue(textD, xOffset, yOffset, False);
2646 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
2647 textD->height);
2648 } else {
2649 /* Recover the useable window areas by moving to the proper location */
2650 srcX = textD->left + (xOffset >= 0 ? 0 : -xOffset);
2651 dstX = textD->left + (xOffset >= 0 ? xOffset : 0);
2652 width = textD->width - abs(xOffset);
2653 srcY = textD->top + (yOffset >= 0 ? 0 : -yOffset);
2654 dstY = textD->top + (yOffset >= 0 ? yOffset : 0);
2655 height = exactHeight - abs(yOffset);
2656 resetClipRectangles(textD);
2657 TextDTranlateGraphicExposeQueue(textD, xOffset, yOffset, True);
2658 XCopyArea(XtDisplay(textD->w), XtWindow(textD->w), XtWindow(textD->w),
2659 textD->gc, srcX, srcY, width, height, dstX, dstY);
2660 /* redraw the un-recoverable parts */
2661 if (yOffset > 0)
2662 TextDRedisplayRect(textD, textD->left, textD->top,
2663 textD->width, yOffset);
2664 else if (yOffset < 0)
2665 TextDRedisplayRect(textD, textD->left, textD->top +
2666 textD->height + yOffset, textD->width, -yOffset);
2667 if (xOffset > 0)
2668 TextDRedisplayRect(textD, textD->left, textD->top,
2669 xOffset, textD->height);
2670 else if (xOffset < 0)
2671 TextDRedisplayRect(textD, textD->left + textD->width + xOffset,
2672 textD->top, -xOffset, textD->height);
2673 /* Restore protruding parts of the cursor */
2674 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
2677 /* Refresh line number/calltip display if its up and we've scrolled
2678 vertically */
2679 if (lineDelta != 0) {
2680 redrawLineNumbers(textD, False);
2681 TextDRedrawCalltip(textD, 0);
2684 HandleAllPendingGraphicsExposeNoExposeEvents((TextWidget)textD->w, NULL);
2688 ** Update the minimum, maximum, slider size, page increment, and value
2689 ** for vertical scroll bar.
2691 static void updateVScrollBarRange(textDisp *textD)
2693 int sliderSize, sliderMax, sliderValue;
2695 if (textD->vScrollBar == NULL)
2696 return;
2698 /* The Vert. scroll bar value and slider size directly represent the top
2699 line number, and the number of visible lines respectively. The scroll
2700 bar maximum value is chosen to generally represent the size of the whole
2701 buffer, with minor adjustments to keep the scroll bar widget happy */
2702 sliderSize = max(textD->nVisibleLines, 1); /* Avoid X warning (size < 1) */
2703 sliderValue = textD->topLineNum;
2704 sliderMax = max(textD->nBufferLines + 2 +
2705 TEXT_OF_TEXTD(textD).cursorVPadding,
2706 sliderSize + sliderValue);
2707 XtVaSetValues(textD->vScrollBar,
2708 XmNmaximum, sliderMax,
2709 XmNsliderSize, sliderSize,
2710 XmNpageIncrement, max(1, textD->nVisibleLines - 1),
2711 XmNvalue, sliderValue, NULL);
2715 ** Update the minimum, maximum, slider size, page increment, and value
2716 ** for the horizontal scroll bar. If scroll position is such that there
2717 ** is blank space to the right of all lines of text, scroll back (adjust
2718 ** horizOffset but don't redraw) to take up the slack and position the
2719 ** right edge of the text at the right edge of the display.
2721 ** Note, there is some cost to this routine, since it scans the whole range
2722 ** of displayed text, particularly since it's usually called for each typed
2723 ** character!
2725 static int updateHScrollBarRange(textDisp *textD)
2727 int i, maxWidth = 0, sliderMax, sliderWidth;
2728 int origHOffset = textD->horizOffset;
2730 if (textD->hScrollBar == NULL || !XtIsManaged(textD->hScrollBar))
2731 return False;
2733 /* Scan all the displayed lines to find the width of the longest line */
2734 for (i=0; i<textD->nVisibleLines && textD->lineStarts[i]!= -1; i++)
2735 maxWidth = max(measureVisLine(textD, i), maxWidth);
2737 /* If the scroll position is beyond what's necessary to keep all lines
2738 in view, scroll to the left to bring the end of the longest line to
2739 the right margin */
2740 if (maxWidth < textD->width + textD->horizOffset && textD->horizOffset > 0)
2741 textD->horizOffset = max(0, maxWidth - textD->width);
2743 /* Readjust the scroll bar */
2744 sliderWidth = textD->width;
2745 sliderMax = max(maxWidth, sliderWidth + textD->horizOffset);
2746 XtVaSetValues(textD->hScrollBar,
2747 XmNmaximum, sliderMax,
2748 XmNsliderSize, sliderWidth,
2749 XmNpageIncrement, max(textD->width - 100, 10),
2750 XmNvalue, textD->horizOffset, NULL);
2752 /* Return True if scroll position was changed */
2753 return origHOffset != textD->horizOffset;
2757 ** Define area for drawing line numbers. A width of 0 disables line
2758 ** number drawing.
2760 void TextDSetLineNumberArea(textDisp *textD, int lineNumLeft, int lineNumWidth,
2761 int textLeft)
2763 int newWidth = textD->width + textD->left - textLeft;
2764 textD->lineNumLeft = lineNumLeft;
2765 textD->lineNumWidth = lineNumWidth;
2766 textD->left = textLeft;
2767 XClearWindow(XtDisplay(textD->w), XtWindow(textD->w));
2768 resetAbsLineNum(textD);
2769 TextDResize(textD, newWidth, textD->height);
2770 TextDRedisplayRect(textD, 0, textD->top, INT_MAX, textD->height);
2774 ** Refresh the line number area. If clearAll is False, writes only over
2775 ** the character cell areas. Setting clearAll to True will clear out any
2776 ** stray marks outside of the character cell area, which might have been
2777 ** left from before a resize or font change.
2779 static void redrawLineNumbers(textDisp *textD, int clearAll)
2781 int y, line, visLine, nCols, lineStart;
2782 char lineNumString[12];
2783 int lineHeight = textD->ascent + textD->descent;
2784 int charWidth = textD->fontStruct->max_bounds.width;
2786 /* Don't draw if lineNumWidth == 0 (line numbers are hidden), or widget is
2787 not yet realized */
2788 if (textD->lineNumWidth == 0 || XtWindow(textD->w) == 0)
2789 return;
2791 /* Erase the previous contents of the line number area, if requested */
2792 if (clearAll)
2793 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->lineNumLeft,
2794 textD->top, textD->lineNumWidth, textD->height, False);
2796 /* Draw the line numbers, aligned to the text */
2797 nCols = min(11, textD->lineNumWidth / charWidth);
2798 y = textD->top;
2799 line = getAbsTopLineNum(textD);
2800 for (visLine=0; visLine < textD->nVisibleLines; visLine++) {
2801 lineStart = textD->lineStarts[visLine];
2802 if (lineStart != -1 && (lineStart==0 ||
2803 BufGetCharacter(textD->buffer, lineStart-1)=='\n')) {
2804 sprintf(lineNumString, "%*d", nCols, line);
2805 XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w),
2806 textD->lineNumGC, textD->lineNumLeft, y + textD->ascent,
2807 lineNumString, strlen(lineNumString));
2808 line++;
2809 } else {
2810 XClearArea(XtDisplay(textD->w), XtWindow(textD->w),
2811 textD->lineNumLeft, y, textD->lineNumWidth,
2812 textD->ascent + textD->descent, False);
2813 if (visLine == 0)
2814 line++;
2816 y += lineHeight;
2821 ** Callbacks for drag or valueChanged on scroll bars
2823 static void vScrollCB(Widget w, XtPointer clientData, XtPointer callData)
2825 textDisp *textD = (textDisp *)clientData;
2826 int newValue = ((XmScrollBarCallbackStruct *)callData)->value;
2827 int lineDelta = newValue - textD->topLineNum;
2829 if (lineDelta == 0)
2830 return;
2831 setScroll(textD, newValue, textD->horizOffset, False, True);
2833 static void hScrollCB(Widget w, XtPointer clientData, XtPointer callData)
2835 textDisp *textD = (textDisp *)clientData;
2836 int newValue = ((XmScrollBarCallbackStruct *)callData)->value;
2838 if (newValue == textD->horizOffset)
2839 return;
2840 setScroll(textD, textD->topLineNum, newValue, False, False);
2843 static int max(int i1, int i2)
2845 return i1 >= i2 ? i1 : i2;
2848 static int min(int i1, int i2)
2850 return i1 <= i2 ? i1 : i2;
2854 ** Count the number of newlines in a null-terminated text string;
2856 static int countLines(char *string)
2858 char *c;
2859 int lineCount = 0;
2861 if (string == NULL)
2862 return 0;
2863 for (c=string; *c!='\0'; c++)
2864 if (*c == '\n') lineCount++;
2865 return lineCount;
2869 ** Return the width in pixels of the displayed line pointed to by "visLineNum"
2871 static int measureVisLine(textDisp *textD, int visLineNum)
2873 int i, width = 0, len, style, lineLen = visLineLength(textD, visLineNum);
2874 int charCount = 0, lineStartPos = textD->lineStarts[visLineNum];
2875 char expandedChar[MAX_EXP_CHAR_LEN];
2877 if (textD->styleBuffer == NULL) {
2878 for (i=0; i<lineLen; i++) {
2879 len = BufGetExpandedChar(textD->buffer, lineStartPos + i,
2880 charCount, expandedChar);
2881 width += XTextWidth(textD->fontStruct, expandedChar, len);
2882 charCount += len;
2884 } else {
2885 for (i=0; i<lineLen; i++) {
2886 len = BufGetExpandedChar(textD->buffer, lineStartPos+i,
2887 charCount, expandedChar);
2888 style = (unsigned char)BufGetCharacter(textD->styleBuffer,
2889 lineStartPos+i) - ASCII_A;
2890 width += XTextWidth(textD->styleTable[style].font, expandedChar,
2891 len);
2892 charCount += len;
2895 return width;
2899 ** Return true if there are lines visible with no corresponding buffer text
2901 static int emptyLinesVisible(textDisp *textD)
2903 return textD->nVisibleLines > 0 &&
2904 textD->lineStarts[textD->nVisibleLines-1] == -1;
2908 ** When the cursor is at the left or right edge of the text, part of it
2909 ** sticks off into the clipped region beyond the text. Normal redrawing
2910 ** can not overwrite this protruding part of the cursor, so it must be
2911 ** erased independently by calling this routine.
2913 static void blankCursorProtrusions(textDisp *textD)
2915 int x, width, cursorX = textD->cursorX, cursorY = textD->cursorY;
2916 int fontWidth = textD->fontStruct->max_bounds.width;
2917 int fontHeight = textD->ascent + textD->descent;
2918 int cursorWidth, left = textD->left, right = left + textD->width;
2920 cursorWidth = (fontWidth/3) * 2;
2921 if (cursorX >= left-1 && cursorX <= left + cursorWidth/2 - 1) {
2922 x = cursorX - cursorWidth/2;
2923 width = left - x;
2924 } else if (cursorX >= right - cursorWidth/2 && cursorX <= right) {
2925 x = right;
2926 width = cursorX + cursorWidth/2 + 2 - right;
2927 } else
2928 return;
2930 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), x, cursorY,
2931 width, fontHeight, False);
2935 ** Allocate shared graphics contexts used by the widget, which must be
2936 ** re-allocated on a font change.
2938 static void allocateFixedFontGCs(textDisp *textD, XFontStruct *fontStruct,
2939 Pixel bgPixel, Pixel fgPixel, Pixel selectFGPixel, Pixel selectBGPixel,
2940 Pixel highlightFGPixel, Pixel highlightBGPixel, Pixel lineNumFGPixel)
2942 textD->gc = allocateGC(textD->w, GCFont | GCForeground | GCBackground,
2943 fgPixel, bgPixel, fontStruct->fid, GCClipMask, GCArcMode);
2944 textD->selectGC = allocateGC(textD->w, GCFont | GCForeground | GCBackground,
2945 selectFGPixel, selectBGPixel, fontStruct->fid, GCClipMask,
2946 GCArcMode);
2947 textD->selectBGGC = allocateGC(textD->w, GCForeground, selectBGPixel, 0,
2948 fontStruct->fid, GCClipMask, GCArcMode);
2949 textD->highlightGC = allocateGC(textD->w, GCFont|GCForeground|GCBackground,
2950 highlightFGPixel, highlightBGPixel, fontStruct->fid, GCClipMask,
2951 GCArcMode);
2952 textD->highlightBGGC = allocateGC(textD->w, GCForeground, highlightBGPixel,
2953 0, fontStruct->fid, GCClipMask, GCArcMode);
2954 textD->lineNumGC = allocateGC(textD->w, GCFont | GCForeground |
2955 GCBackground, lineNumFGPixel, bgPixel, fontStruct->fid,
2956 GCClipMask, GCArcMode);
2960 ** X11R4 does not have the XtAllocateGC function for sharing graphics contexts
2961 ** with changeable fields. Unfortunately the R4 call for creating shared
2962 ** graphics contexts (XtGetGC) is rarely useful because most widgets need
2963 ** to be able to set and change clipping, and that makes the GC unshareable.
2965 ** This function allocates and returns a gc, using XtAllocateGC if possible,
2966 ** or XCreateGC on X11R4 systems where XtAllocateGC is not available.
2968 static GC allocateGC(Widget w, unsigned long valueMask,
2969 unsigned long foreground, unsigned long background, Font font,
2970 unsigned long dynamicMask, unsigned long dontCareMask)
2972 XGCValues gcValues;
2974 gcValues.font = font;
2975 gcValues.background = background;
2976 gcValues.foreground = foreground;
2977 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4
2978 return XtAllocateGC(w, 0, valueMask, &gcValues, dynamicMask,
2979 dontCareMask);
2980 #else
2981 return XCreateGC(XtDisplay(w), RootWindowOfScreen(XtScreen(w)),
2982 valueMask, &gcValues);
2983 #endif
2987 ** Release a gc allocated with allocateGC above
2989 static void releaseGC(Widget w, GC gc)
2991 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4
2992 XtReleaseGC(w, gc);
2993 #else
2994 XFreeGC(XtDisplay(w), gc);
2995 #endif
2999 ** resetClipRectangles sets the clipping rectangles for GCs which clip
3000 ** at the text boundary (as opposed to the window boundary). These GCs
3001 ** are shared such that the drawing styles are constant, but the clipping
3002 ** rectangles are allowed to change among different users of the GCs (the
3003 ** GCs were created with XtAllocGC). This routine resets them so the clipping
3004 ** rectangles are correct for this text display.
3006 static void resetClipRectangles(textDisp *textD)
3008 XRectangle clipRect;
3009 Display *display = XtDisplay(textD->w);
3011 clipRect.x = textD->left;
3012 clipRect.y = textD->top;
3013 clipRect.width = textD->width;
3014 clipRect.height = textD->height - textD->height %
3015 (textD->ascent + textD->descent);
3017 XSetClipRectangles(display, textD->gc, 0, 0,
3018 &clipRect, 1, Unsorted);
3019 XSetClipRectangles(display, textD->selectGC, 0, 0,
3020 &clipRect, 1, Unsorted);
3021 XSetClipRectangles(display, textD->highlightGC, 0, 0,
3022 &clipRect, 1, Unsorted);
3023 XSetClipRectangles(display, textD->selectBGGC, 0, 0,
3024 &clipRect, 1, Unsorted);
3025 XSetClipRectangles(display, textD->highlightBGGC, 0, 0,
3026 &clipRect, 1, Unsorted);
3027 XSetClipRectangles(display, textD->styleGC, 0, 0,
3028 &clipRect, 1, Unsorted);
3032 ** Return the length of a line (number of displayable characters) by examining
3033 ** entries in the line starts array rather than by scanning for newlines
3035 static int visLineLength(textDisp *textD, int visLineNum)
3037 int nextLineStart, lineStartPos = textD->lineStarts[visLineNum];
3039 if (lineStartPos == -1)
3040 return 0;
3041 if (visLineNum+1 >= textD->nVisibleLines)
3042 return textD->lastChar - lineStartPos;
3043 nextLineStart = textD->lineStarts[visLineNum+1];
3044 if (nextLineStart == -1)
3045 return textD->lastChar - lineStartPos;
3046 if (wrapUsesCharacter(textD, nextLineStart-1))
3047 return nextLineStart-1 - lineStartPos;
3048 return nextLineStart - lineStartPos;
3052 ** When continuous wrap is on, and the user inserts or deletes characters,
3053 ** wrapping can happen before and beyond the changed position. This routine
3054 ** finds the extent of the changes, and counts the deleted and inserted lines
3055 ** over that range. It also attempts to minimize the size of the range to
3056 ** what has to be counted and re-displayed, so the results can be useful
3057 ** both for delimiting where the line starts need to be recalculated, and
3058 ** for deciding what part of the text to redisplay.
3060 static void findWrapRange(textDisp *textD, char *deletedText, int pos,
3061 int nInserted, int nDeleted, int *modRangeStart, int *modRangeEnd,
3062 int *linesInserted, int *linesDeleted)
3064 int length, retPos, retLines, retLineStart, retLineEnd;
3065 textBuffer *deletedTextBuf, *buf = textD->buffer;
3066 int nVisLines = textD->nVisibleLines;
3067 int *lineStarts = textD->lineStarts;
3068 int countFrom, countTo, lineStart, adjLineStart, i;
3069 int visLineNum = 0, nLines = 0;
3072 ** Determine where to begin searching: either the previous newline, or
3073 ** if possible, limit to the start of the (original) previous displayed
3074 ** line, using information from the existing line starts array
3076 if (pos >= textD->firstChar && pos <= textD->lastChar) {
3077 for (i=nVisLines-1; i>0; i--)
3078 if (lineStarts[i] != -1 && pos >= lineStarts[i])
3079 break;
3080 if (i > 0) {
3081 countFrom = lineStarts[i-1];
3082 visLineNum = i-1;
3083 } else
3084 countFrom = BufStartOfLine(buf, pos);
3085 } else
3086 countFrom = BufStartOfLine(buf, pos);
3090 ** Move forward through the (new) text one line at a time, counting
3091 ** displayed lines, and looking for either a real newline, or for the
3092 ** line starts to re-sync with the original line starts array
3094 lineStart = countFrom;
3095 *modRangeStart = countFrom;
3096 while (True) {
3098 /* advance to the next line. If the line ended in a real newline
3099 or the end of the buffer, that's far enough */
3100 wrappedLineCounter(textD, buf, lineStart, buf->length, 1, True, 0,
3101 &retPos, &retLines, &retLineStart, &retLineEnd);
3102 if (retPos >= buf->length) {
3103 countTo = buf->length;
3104 *modRangeEnd = countTo;
3105 if (retPos != retLineEnd)
3106 nLines++;
3107 break;
3108 } else
3109 lineStart = retPos;
3110 nLines++;
3111 if (lineStart > pos + nInserted &&
3112 BufGetCharacter(buf, lineStart-1) == '\n') {
3113 countTo = lineStart;
3114 *modRangeEnd = lineStart;
3115 break;
3118 /* Don't try to resync in continuous wrap mode with non-fixed font
3119 sizes; it would result in a chicken-and-egg dependency between
3120 the calculations for the inserted and the deleted lines.
3121 If we're in that mode, the number of deleted lines is calculated in
3122 advance, without resynchronization, so we shouldn't resynchronize
3123 for the inserted lines either. */
3124 if (textD->suppressResync)
3125 continue;
3127 /* check for synchronization with the original line starts array
3128 before pos, if so, the modified range can begin later */
3129 if (lineStart <= pos) {
3130 while (visLineNum<nVisLines && lineStarts[visLineNum] < lineStart)
3131 visLineNum++;
3132 if (visLineNum < nVisLines && lineStarts[visLineNum] == lineStart) {
3133 countFrom = lineStart;
3134 nLines = 0;
3135 if (visLineNum+1 < nVisLines && lineStarts[visLineNum+1] != -1)
3136 *modRangeStart = min(pos, lineStarts[visLineNum+1]-1);
3137 else
3138 *modRangeStart = countFrom;
3139 } else
3140 *modRangeStart = min(*modRangeStart, lineStart-1);
3143 /* check for synchronization with the original line starts array
3144 after pos, if so, the modified range can end early */
3145 else if (lineStart > pos + nInserted) {
3146 adjLineStart = lineStart - nInserted + nDeleted;
3147 while (visLineNum<nVisLines && lineStarts[visLineNum]<adjLineStart)
3148 visLineNum++;
3149 if (visLineNum < nVisLines && lineStarts[visLineNum] != -1 &&
3150 lineStarts[visLineNum] == adjLineStart) {
3151 countTo = TextDEndOfLine(textD, lineStart, True);
3152 *modRangeEnd = lineStart;
3153 break;
3157 *linesInserted = nLines;
3160 /* Count deleted lines between countFrom and countTo as the text existed
3161 before the modification (that is, as if the text between pos and
3162 pos+nInserted were replaced by "deletedText"). This extra context is
3163 necessary because wrapping can occur outside of the modified region
3164 as a result of adding or deleting text in the region. This is done by
3165 creating a textBuffer containing the deleted text and the necessary
3166 additional context, and calling the wrappedLineCounter on it.
3168 NOTE: This must not be done in continuous wrap mode when the font
3169 width is not fixed. In that case, the calculation would try
3170 to access style information that is no longer available (deleted
3171 text), or out of date (updated highlighting), possibly leading
3172 to completely wrong calculations and/or even crashes eventually.
3173 (This is not theoretical; it really happened.)
3175 In that case, the calculation of the number of deleted lines
3176 has happened before the buffer was modified (only in that case,
3177 because resynchronization of the line starts is impossible
3178 in that case, which makes the whole calculation less efficient).
3180 if (textD->suppressResync) {
3181 *linesDeleted = textD->nLinesDeleted;
3182 textD->suppressResync = 0;
3183 return;
3186 length = (pos-countFrom) + nDeleted +(countTo-(pos+nInserted));
3187 deletedTextBuf = BufCreatePreallocated(length);
3188 if (pos > countFrom)
3189 BufCopyFromBuf(textD->buffer, deletedTextBuf, countFrom, pos, 0);
3190 if (nDeleted != 0)
3191 BufInsert(deletedTextBuf, pos-countFrom, deletedText);
3192 if (countTo > pos+nInserted)
3193 BufCopyFromBuf(textD->buffer, deletedTextBuf,
3194 pos+nInserted, countTo, pos-countFrom+nDeleted);
3195 /* Note that we need to take into account an offset for the style buffer:
3196 the deletedTextBuf can be out of sync with the style buffer. */
3197 wrappedLineCounter(textD, deletedTextBuf, 0, length, INT_MAX, True,
3198 countFrom, &retPos, &retLines, &retLineStart, &retLineEnd);
3199 BufFree(deletedTextBuf);
3200 *linesDeleted = retLines;
3201 textD->suppressResync = 0;
3205 ** This is a stripped-down version of the findWrapRange() function above,
3206 ** intended to be used to calculate the number of "deleted" lines during
3207 ** a buffer modification. It is called _before_ the modification takes place.
3209 ** This function should only be called in continuous wrap mode with a
3210 ** non-fixed font width. In that case, it is impossible to calculate
3211 ** the number of deleted lines, because the necessary style information
3212 ** is no longer available _after_ the modification. In other cases, we
3213 ** can still perform the calculation afterwards (possibly even more
3214 ** efficiently).
3216 static void measureDeletedLines(textDisp *textD, int pos, int nDeleted)
3218 int retPos, retLines, retLineStart, retLineEnd;
3219 textBuffer *buf = textD->buffer;
3220 int nVisLines = textD->nVisibleLines;
3221 int *lineStarts = textD->lineStarts;
3222 int countFrom, lineStart;
3223 int visLineNum = 0, nLines = 0, i;
3225 ** Determine where to begin searching: either the previous newline, or
3226 ** if possible, limit to the start of the (original) previous displayed
3227 ** line, using information from the existing line starts array
3229 if (pos >= textD->firstChar && pos <= textD->lastChar) {
3230 for (i=nVisLines-1; i>0; i--)
3231 if (lineStarts[i] != -1 && pos >= lineStarts[i])
3232 break;
3233 if (i > 0) {
3234 countFrom = lineStarts[i-1];
3235 visLineNum = i-1;
3236 } else
3237 countFrom = BufStartOfLine(buf, pos);
3238 } else
3239 countFrom = BufStartOfLine(buf, pos);
3242 ** Move forward through the (new) text one line at a time, counting
3243 ** displayed lines, and looking for either a real newline, or for the
3244 ** line starts to re-sync with the original line starts array
3246 lineStart = countFrom;
3247 while (True) {
3248 /* advance to the next line. If the line ended in a real newline
3249 or the end of the buffer, that's far enough */
3250 wrappedLineCounter(textD, buf, lineStart, buf->length, 1, True, 0,
3251 &retPos, &retLines, &retLineStart, &retLineEnd);
3252 if (retPos >= buf->length) {
3253 if (retPos != retLineEnd)
3254 nLines++;
3255 break;
3256 } else
3257 lineStart = retPos;
3258 nLines++;
3259 if (lineStart > pos + nDeleted &&
3260 BufGetCharacter(buf, lineStart-1) == '\n') {
3261 break;
3264 /* Unlike in the findWrapRange() function above, we don't try to
3265 resync with the line starts, because we don't know the length
3266 of the inserted text yet, nor the updated style information.
3268 Because of that, we also shouldn't resync with the line starts
3269 after the modification either, because we must perform the
3270 calculations for the deleted and inserted lines in the same way.
3272 This can result in some unnecessary recalculation and redrawing
3273 overhead, and therefore we should only use this two-phase mode
3274 of calculation when it's really needed (continuous wrap + variable
3275 font width). */
3277 textD->nLinesDeleted = nLines;
3278 textD->suppressResync = 1;
3282 ** Count forward from startPos to either maxPos or maxLines (whichever is
3283 ** reached first), and return all relevant positions and line count.
3284 ** The provided textBuffer may differ from the actual text buffer of the
3285 ** widget. In that case it must be a (partial) copy of the actual text buffer
3286 ** and the styleBufOffset argument must indicate the starting position of the
3287 ** copy, to take into account the correct style information.
3289 ** Returned values:
3291 ** retPos: Position where counting ended. When counting lines, the
3292 ** position returned is the start of the line "maxLines"
3293 ** lines beyond "startPos".
3294 ** retLines: Number of line breaks counted
3295 ** retLineStart: Start of the line where counting ended
3296 ** retLineEnd: End position of the last line traversed
3298 static void wrappedLineCounter(textDisp *textD, textBuffer *buf, int startPos,
3299 int maxPos, int maxLines, int startPosIsLineStart, int styleBufOffset,
3300 int *retPos, int *retLines, int *retLineStart, int *retLineEnd)
3302 int lineStart, newLineStart = 0, b, p, colNum, wrapMargin;
3303 int maxWidth, width, countPixels, i, foundBreak;
3304 int nLines = 0, tabDist = textD->buffer->tabDist;
3305 unsigned char c;
3306 char nullSubsChar = textD->buffer->nullSubsChar;
3308 /* If the font is fixed, or there's a wrap margin set, it's more efficient
3309 to measure in columns, than to count pixels. Determine if we can count
3310 in columns (countPixels == False) or must count pixels (countPixels ==
3311 True), and set the wrap target for either pixels or columns */
3312 if (textD->fixedFontWidth != -1 || textD->wrapMargin != 0) {
3313 countPixels = False;
3314 wrapMargin = textD->wrapMargin != 0 ? textD->wrapMargin :
3315 textD->width / textD->fixedFontWidth;
3316 maxWidth = INT_MAX;
3317 } else {
3318 countPixels = True;
3319 wrapMargin = INT_MAX;
3320 maxWidth = textD->width;
3323 /* Find the start of the line if the start pos is not marked as a
3324 line start. */
3325 if (startPosIsLineStart)
3326 lineStart = startPos;
3327 else
3328 lineStart = TextDStartOfLine(textD, startPos);
3331 ** Loop until position exceeds maxPos or line count exceeds maxLines.
3332 ** (actually, contines beyond maxPos to end of line containing maxPos,
3333 ** in case later characters cause a word wrap back before maxPos)
3335 colNum = 0;
3336 width = 0;
3337 for (p=lineStart; p<buf->length; p++) {
3338 c = BufGetCharacter(buf, p);
3340 /* If the character was a newline, count the line and start over,
3341 otherwise, add it to the width and column counts */
3342 if (c == '\n') {
3343 if (p >= maxPos) {
3344 *retPos = maxPos;
3345 *retLines = nLines;
3346 *retLineStart = lineStart;
3347 *retLineEnd = maxPos;
3348 return;
3350 nLines++;
3351 if (nLines >= maxLines) {
3352 *retPos = p + 1;
3353 *retLines = nLines;
3354 *retLineStart = p + 1;
3355 *retLineEnd = p;
3356 return;
3358 lineStart = p + 1;
3359 colNum = 0;
3360 width = 0;
3361 } else {
3362 colNum += BufCharWidth(c, colNum, tabDist, nullSubsChar);
3363 if (countPixels)
3364 width += measurePropChar(textD, c, colNum, p+styleBufOffset);
3367 /* If character exceeded wrap margin, find the break point
3368 and wrap there */
3369 if (colNum > wrapMargin || width > maxWidth) {
3370 foundBreak = False;
3371 for (b=p; b>=lineStart; b--) {
3372 c = BufGetCharacter(buf, b);
3373 if (c == '\t' || c == ' ') {
3374 newLineStart = b + 1;
3375 if (countPixels) {
3376 colNum = 0;
3377 width = 0;
3378 for (i=b+1; i<p+1; i++) {
3379 width += measurePropChar(textD,
3380 BufGetCharacter(buf, i), colNum,
3381 i+styleBufOffset);
3382 colNum++;
3384 } else
3385 colNum = BufCountDispChars(buf, b+1, p+1);
3386 foundBreak = True;
3387 break;
3390 if (!foundBreak) { /* no whitespace, just break at margin */
3391 newLineStart = max(p, lineStart+1);
3392 colNum = BufCharWidth(c, colNum, tabDist, nullSubsChar);
3393 if (countPixels)
3394 width = measurePropChar(textD, c, colNum, p+styleBufOffset);
3396 if (p >= maxPos) {
3397 *retPos = maxPos;
3398 *retLines = maxPos < newLineStart ? nLines : nLines + 1;
3399 *retLineStart = maxPos < newLineStart ? lineStart :
3400 newLineStart;
3401 *retLineEnd = maxPos;
3402 return;
3404 nLines++;
3405 if (nLines >= maxLines) {
3406 *retPos = foundBreak ? b + 1 : max(p, lineStart+1);
3407 *retLines = nLines;
3408 *retLineStart = lineStart;
3409 *retLineEnd = foundBreak ? b : p;
3410 return;
3412 lineStart = newLineStart;
3416 /* reached end of buffer before reaching pos or line target */
3417 *retPos = buf->length;
3418 *retLines = nLines;
3419 *retLineStart = lineStart;
3420 *retLineEnd = buf->length;
3424 ** Measure the width in pixels of a character "c" at a particular column
3425 ** "colNum" and buffer position "pos". This is for measuring characters in
3426 ** proportional or mixed-width highlighting fonts.
3428 ** A note about proportional and mixed-width fonts: the mixed width and
3429 ** proportional font code in nedit does not get much use in general editing,
3430 ** because nedit doesn't allow per-language-mode fonts, and editing programs
3431 ** in a proportional font is usually a bad idea, so very few users would
3432 ** choose a proportional font as a default. There are still probably mixed-
3433 ** width syntax highlighting cases where things don't redraw properly for
3434 ** insertion/deletion, though static display and wrapping and resizing
3435 ** should now be solid because they are now used for online help display.
3437 static int measurePropChar(textDisp *textD, char c, int colNum, int pos)
3439 int charLen, style;
3440 char expChar[MAX_EXP_CHAR_LEN];
3441 textBuffer *styleBuf = textD->styleBuffer;
3443 charLen = BufExpandCharacter(c, colNum, expChar,
3444 textD->buffer->tabDist, textD->buffer->nullSubsChar);
3445 if (styleBuf == NULL) {
3446 style = 0;
3447 } else {
3448 style = (unsigned char)BufGetCharacter(styleBuf, pos);
3449 if (style == textD->unfinishedStyle) {
3450 /* encountered "unfinished" style, trigger parsing */
3451 (textD->unfinishedHighlightCB)(textD, pos, textD->highlightCBArg);
3452 style = (unsigned char)BufGetCharacter(styleBuf, pos);
3455 return stringWidth(textD, expChar, charLen, style);
3459 ** Finds both the end of the current line and the start of the next line. Why?
3460 ** In continuous wrap mode, if you need to know both, figuring out one from the
3461 ** other can be expensive or error prone. The problem comes when there's a
3462 ** trailing space or tab just before the end of the buffer. To translate an
3463 ** end of line value to or from the next lines start value, you need to know
3464 ** whether the trailing space or tab is being used as a line break or just a
3465 ** normal character, and to find that out would otherwise require counting all
3466 ** the way back to the beginning of the line.
3468 static void findLineEnd(textDisp *textD, int startPos, int startPosIsLineStart,
3469 int *lineEnd, int *nextLineStart)
3471 int retLines, retLineStart;
3473 /* if we're not wrapping use more efficient BufEndOfLine */
3474 if (!textD->continuousWrap) {
3475 *lineEnd = BufEndOfLine(textD->buffer, startPos);
3476 *nextLineStart = min(textD->buffer->length, *lineEnd + 1);
3477 return;
3480 /* use the wrapped line counter routine to count forward one line */
3481 wrappedLineCounter(textD, textD->buffer, startPos, textD->buffer->length,
3482 1, startPosIsLineStart, 0, nextLineStart, &retLines,
3483 &retLineStart, lineEnd);
3484 return;
3488 ** Line breaks in continuous wrap mode usually happen at newlines or
3489 ** whitespace. This line-terminating character is not included in line
3490 ** width measurements and has a special status as a non-visible character.
3491 ** However, lines with no whitespace are wrapped without the benefit of a
3492 ** line terminating character, and this distinction causes endless trouble
3493 ** with all of the text display code which was originally written without
3494 ** continuous wrap mode and always expects to wrap at a newline character.
3496 ** Given the position of the end of the line, as returned by TextDEndOfLine
3497 ** or BufEndOfLine, this returns true if there is a line terminating
3498 ** character, and false if there's not. On the last character in the
3499 ** buffer, this function can't tell for certain whether a trailing space was
3500 ** used as a wrap point, and just guesses that it wasn't. So if an exact
3501 ** accounting is necessary, don't use this function.
3503 static int wrapUsesCharacter(textDisp *textD, int lineEndPos)
3505 char c;
3507 if (!textD->continuousWrap || lineEndPos == textD->buffer->length)
3508 return True;
3510 c = BufGetCharacter(textD->buffer, lineEndPos);
3511 return c == '\n' || ((c == '\t' || c == ' ') &&
3512 lineEndPos + 1 != textD->buffer->length);
3516 ** Decide whether the user needs (or may need) a horizontal scroll bar,
3517 ** and manage or unmanage the scroll bar widget accordingly. The H.
3518 ** scroll bar is only hidden in continuous wrap mode when it's absolutely
3519 ** certain that the user will not need it: when wrapping is set
3520 ** to the window edge, or when the wrap margin is strictly less than
3521 ** the longest possible line.
3523 static void hideOrShowHScrollBar(textDisp *textD)
3525 if (textD->continuousWrap && (textD->wrapMargin == 0 || textD->wrapMargin *
3526 textD->fontStruct->max_bounds.width < textD->width))
3527 XtUnmanageChild(textD->hScrollBar);
3528 else
3529 XtManageChild(textD->hScrollBar);
3533 ** Return true if the selection "sel" is rectangular, and touches a
3534 ** buffer position withing "rangeStart" to "rangeEnd"
3536 static int rangeTouchesRectSel(selection *sel, int rangeStart, int rangeEnd)
3538 return sel->selected && sel->rectangular && sel->end >= rangeStart &&
3539 sel->start <= rangeEnd;
3543 ** Extend the range of a redraw request (from *start to *end) with additional
3544 ** redraw requests resulting from changes to the attached style buffer (which
3545 ** contains auxiliary information for coloring or styling text).
3547 static void extendRangeForStyleMods(textDisp *textD, int *start, int *end)
3549 selection *sel = &textD->styleBuffer->primary;
3550 int extended = False;
3552 /* The peculiar protocol used here is that modifications to the style
3553 buffer are marked by selecting them with the buffer's primary selection.
3554 The style buffer is usually modified in response to a modify callback on
3555 the text buffer BEFORE textDisp.c's modify callback, so that it can keep
3556 the style buffer in step with the text buffer. The style-update
3557 callback can't just call for a redraw, because textDisp hasn't processed
3558 the original text changes yet. Anyhow, to minimize redrawing and to
3559 avoid the complexity of scheduling redraws later, this simple protocol
3560 tells the text display's buffer modify callback to extend it's redraw
3561 range to show the text color/and font changes as well. */
3562 if (sel->selected) {
3563 if (sel->start < *start) {
3564 *start = sel->start;
3565 extended = True;
3567 if (sel->end > *end) {
3568 *end = sel->end;
3569 extended = True;
3573 /* If the selection was extended due to a style change, and some of the
3574 fonts don't match in spacing, extend redraw area to end of line to
3575 redraw characters exposed by possible font size changes */
3576 if (textD->fixedFontWidth == -1 && extended)
3577 *end = BufEndOfLine(textD->buffer, *end) + 1;
3580 /********************** Backlight Functions ******************************/
3582 ** Allocate a read-only (shareable) colormap cell for a named color, from the
3583 ** the default colormap of the screen on which the widget (w) is displayed. If
3584 ** the colormap is full and there's no suitable substitute, print an error on
3585 ** stderr, and return the widget's background color as a backup.
3587 static Pixel allocBGColor(Widget w, char *colorName, int *ok)
3589 int r,g,b;
3590 *ok = 1;
3591 return AllocColor(w, colorName, &r, &g, &b);
3594 Pixel getRangesetColor(textDisp *textD, int ind, Pixel bground)
3596 textBuffer *buf;
3597 RangesetTable *tab;
3598 Pixel color;
3599 char *color_name;
3600 int valid;
3602 if (ind > 0) {
3603 ind--;
3604 buf = textD->buffer;
3605 tab = buf->rangesetTable;
3607 valid = RangesetTableGetColorValid(tab, ind, &color);
3608 if (valid == 0) {
3609 color_name = RangesetTableGetColorName(tab, ind);
3610 if (color_name)
3611 color = allocBGColor(textD->w, color_name, &valid);
3612 RangesetTableAssignColorPixel(tab, ind, color, valid);
3614 if (valid > 0) {
3615 return color;
3618 return bground;
3622 ** Read the background color class specification string in str, allocating the
3623 ** necessary colors, and allocating and setting up the character->class_no and
3624 ** class_no->pixel map arrays, returned via *pp_bgClass and *pp_bgClassPixel
3625 ** respectively.
3626 ** Note: the allocation of class numbers could be more intelligent: there can
3627 ** never be more than 256 of these (one per character); but I don't think
3628 ** there'll be a pressing need. I suppose the scanning of the specification
3629 ** could be better too, but then, who cares!
3631 void TextDSetupBGClasses(Widget w, XmString str, Pixel **pp_bgClassPixel,
3632 unsigned char **pp_bgClass, Pixel bgPixelDefault)
3634 unsigned char bgClass[256];
3635 Pixel bgClassPixel[256];
3636 int class_no = 0;
3637 char *semicol;
3638 char *s = (char *)str;
3639 size_t was_semicol;
3640 int lo, hi, dummy;
3641 char *pos;
3642 Boolean is_good = True;
3644 XtFree((char *)*pp_bgClass);
3645 XtFree((char *)*pp_bgClassPixel);
3647 *pp_bgClassPixel = NULL;
3648 *pp_bgClass = NULL;
3650 if (!s)
3651 return;
3653 /* default for all chars is class number zero, for standard background */
3654 memset(bgClassPixel, 0, sizeof bgClassPixel);
3655 memset(bgClass, 0, sizeof bgClass);
3656 bgClassPixel[0] = bgPixelDefault;
3657 /* since class no == 0 in a "style" has no set bits in BACKLIGHT_MASK
3658 (see styleOfPos()), when drawString() is called for text with a
3659 backlight class no of zero, bgClassPixel[0] is never consulted, and
3660 the default background color is chosen. */
3662 /* The format of the class string s is:
3663 low[-high]{,low[-high]}:color{;low-high{,low[-high]}:color}
3665 32-255:#f0f0f0;1-31,127:red;128-159:orange;9-13:#e5e5e5
3666 where low and high represent a character range between ordinal
3667 ASCII values. Using strtol() allows automatic octal, dec and hex
3668 reading of low and high. The example format sets backgrounds as follows:
3669 char 1 - 8 colored red (control characters)
3670 char 9 - 13 colored #e5e5e5 (isspace() control characters)
3671 char 14 - 31 colored red (control characters)
3672 char 32 - 126 colored #f0f0f0
3673 char 127 colored red (delete character)
3674 char 128 - 159 colored orange ("shifted" control characters)
3675 char 160 - 255 colored #f0f0f0
3676 Notice that some of the later ranges overwrite the class values defined
3677 for earlier ones (eg the first clause, 32-255:#f0f0f0 sets the DEL
3678 character background color to #f0f0f0; it is then set to red by the
3679 clause 1-31,127:red). */
3681 while (s && class_no < 255) {
3682 class_no++; /* simple class alloc scheme */
3683 was_semicol = 0;
3684 is_good = True;
3685 if ((semicol = (char *)strchr(s, ';'))) {
3686 *semicol = '\0'; /* null-terminate low[-high]:color clause */
3687 was_semicol = 1;
3690 /* loop over ranges before the color spec, assigning the characters
3691 in the ranges to the current class number */
3692 for (lo = hi = strtol(s, &pos, 0);
3693 is_good;
3694 lo = hi = strtol(pos + 1, &pos, 0)) {
3695 if (pos && *pos == '-')
3696 hi = strtol(pos + 1, &pos, 0); /* get end of range */
3697 is_good = (pos && 0 <= lo && lo <= hi && hi <= 255);
3698 if (is_good)
3699 while (lo <= hi)
3700 bgClass[lo++] = (unsigned char)class_no;
3701 if (*pos != ',')
3702 break;
3704 if ((is_good = (is_good && *pos == ':'))) {
3705 is_good = (*pos++ != '\0'); /* pos now points to color */
3706 bgClassPixel[class_no] = allocBGColor(w, pos, &dummy);
3708 if (!is_good) {
3709 /* complain? this class spec clause (in string s) was faulty */
3712 /* end of loop iterator clauses */
3713 if (was_semicol)
3714 *semicol = ';'; /* un-null-terminate low[-high]:color clause */
3715 s = semicol + was_semicol;
3717 /* when we get here, we've set up our class table and class-to-pixel table
3718 in local variables: now put them into the "real thing" */
3719 if (class_no) {
3720 class_no++; /* bigger than all valid class_nos */
3721 *pp_bgClass = (unsigned char *)XtMalloc(256);
3722 *pp_bgClassPixel = (Pixel *)XtMalloc(class_no * sizeof (Pixel));
3723 if (!*pp_bgClass || !*pp_bgClassPixel) {
3724 XtFree((char *)*pp_bgClass);
3725 XtFree((char *)*pp_bgClassPixel);
3726 return;
3728 memcpy(*pp_bgClass, bgClass, 256);
3729 memcpy(*pp_bgClassPixel, bgClassPixel, class_no * sizeof (Pixel));