Merged patch #734202: Warnings Removal (Thorsten).
[nedit.git] / source / textDisp.c
blobcecabbff9447c4237575684115b412e8b96c5af0
1 static const char CVSID[] = "$Id: textDisp.c,v 1.51 2003/05/05 16:25:56 edg Exp $";
2 /*******************************************************************************
3 * *
4 * textDisp.c - Display text from a text buffer *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. *
12 * *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
20 * Place, Suite 330, Boston, MA 02111-1307 USA *
21 * *
22 * Nirvana Text Editor *
23 * June 15, 1995 *
24 * *
25 * Written by Mark Edel *
26 * *
27 *******************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
33 #include "textDisp.h"
34 #include "textBuf.h"
35 #include "text.h"
36 #include "textP.h"
37 #include "nedit.h"
38 #include "calltips.h"
39 #include "highlight.h"
40 #include "rangeset.h"
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <limits.h>
46 #ifdef VMS
47 #include "../util/VMSparam.h"
48 #else
49 #ifndef __MVS__
50 #include <sys/param.h>
51 #endif
52 #endif /*VMS*/
54 #include <Xm/Xm.h>
55 #include <Xm/ScrolledW.h>
56 #include <Xm/ScrollBar.h>
57 #include <Xm/Label.h>
58 #include <X11/Shell.h>
60 #ifdef HAVE_DEBUG_H
61 #include "../debug.h"
62 #endif
64 #define TOP_MARGIN 1
65 #define BOTTOM_MARGIN 1
66 #define LEFT_MARGIN 3
67 #define RIGHT_MARGIN 3
69 /* Masks for text drawing methods. These are or'd together to form an
70 integer which describes what drawing calls to use to draw a string */
71 #define FILL_SHIFT 8
72 #define SECONDARY_SHIFT 9
73 #define PRIMARY_SHIFT 10
74 #define HIGHLIGHT_SHIFT 11
75 #define STYLE_LOOKUP_SHIFT 0
76 #define BACKLIGHT_SHIFT 12
78 #define FILL_MASK (1 << FILL_SHIFT)
79 #define SECONDARY_MASK (1 << SECONDARY_SHIFT)
80 #define PRIMARY_MASK (1 << PRIMARY_SHIFT)
81 #define HIGHLIGHT_MASK (1 << HIGHLIGHT_SHIFT)
82 #define STYLE_LOOKUP_MASK (0xff << STYLE_LOOKUP_SHIFT)
83 #define BACKLIGHT_MASK (0xff << BACKLIGHT_SHIFT)
85 #define RANGESET_SHIFT (20)
86 #define RANGESET_MASK (0x3F << RANGESET_SHIFT)
88 /* If you use both 32-Bit Style mask layout:
89 Bits +----------------+----------------+----------------+----------------+
90 hex |1F1E1D1C1B1A1918|1716151413121110| F E D C B A 9 8| 7 6 5 4 3 2 1 0|
91 dec |3130292827262524|2322212019181716|151413121110 9 8| 7 6 5 4 3 2 1 0|
92 +----------------+----------------+----------------+----------------+
93 Type | r r| r r r r b b b b| b b b b H 1 2 F| s s s s s s s s|
94 +----------------+----------------+----------------+----------------+
95 where: s - style lookup value (8 bits)
96 F - fill (1 bit)
97 2 - secondary selection (1 bit)
98 1 - primary selection (1 bit)
99 H - highlight (1 bit)
100 b - backlighting index (8 bits)
101 r - rangeset index (6 bits)
102 This leaves 6 "unused" bits */
104 /* Maximum displayable line length (how many characters will fit across the
105 widest window). This amount of memory is temporarily allocated from the
106 stack in the redisplayLine routine for drawing strings */
107 #define MAX_DISP_LINE_LEN 1000
109 /* Macro for getting the TextPart from a textD */
110 #define TEXT_OF_TEXTD(t) (((TextWidget)((t)->w))->text)
112 enum positionTypes {CURSOR_POS, CHARACTER_POS};
114 static void updateLineStarts(textDisp *textD, int pos, int charsInserted,
115 int charsDeleted, int linesInserted, int linesDeleted, int *scrolled);
116 static void offsetLineStarts(textDisp *textD, int newTopLineNum);
117 static void calcLineStarts(textDisp *textD, int startLine, int endLine);
118 static void calcLastChar(textDisp *textD);
119 static int posToVisibleLineNum(textDisp *textD, int pos, int *lineNum);
120 static void redisplayLine(textDisp *textD, int visLineNum, int leftClip,
121 int rightClip, int leftCharIndex, int rightCharIndex);
122 static void drawString(textDisp *textD, int style, int x, int y, int toX,
123 char *string, int nChars);
124 static void clearRect(textDisp *textD, GC gc, int x, int y,
125 int width, int height);
126 static void drawCursor(textDisp *textD, int x, int y);
127 static int styleOfPos(textDisp *textD, int lineStartPos,
128 int lineLen, int lineIndex, int dispIndex, int thisChar);
129 static int stringWidth(textDisp *textD, char *string, int length, int style);
130 static int inSelection(selection *sel, int pos, int lineStartPos,
131 int dispIndex);
132 static int xyToPos(textDisp *textD, int x, int y, int posType);
133 static void xyToUnconstrainedPos(textDisp *textD, int x, int y, int *row,
134 int *column, int posType);
135 static void bufPreDeleteCB(int pos, int nDeleted, void *cbArg);
136 static void bufModifiedCB(int pos, int nInserted, int nDeleted,
137 int nRestyled, char *deletedText, void *cbArg);
138 static void setScroll(textDisp *textD, int topLineNum, int horizOffset,
139 int updateVScrollBar, int updateHScrollBar);
140 static void hScrollCB(Widget w, XtPointer clientData, XtPointer callData);
141 static void vScrollCB(Widget w, XtPointer clientData, XtPointer callData);
142 static void 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,
188 Pixel calltipFGPixel, Pixel calltipBGPixel)
190 textDisp *textD;
191 XGCValues gcValues;
192 int i;
194 textD = (textDisp *)XtMalloc(sizeof(textDisp));
195 textD->w = widget;
196 textD->top = top;
197 textD->left = left;
198 textD->width = width;
199 textD->height = height;
200 textD->cursorOn = True;
201 textD->cursorPos = 0;
202 textD->cursorX = -100;
203 textD->cursorY = -100;
204 textD->cursorToHint = NO_HINT;
205 textD->cursorStyle = NORMAL_CURSOR;
206 textD->cursorPreferredCol = -1;
207 textD->buffer = buffer;
208 textD->firstChar = 0;
209 textD->lastChar = 0;
210 textD->nBufferLines = 0;
211 textD->topLineNum = 1;
212 textD->absTopLineNum = 1;
213 textD->needAbsTopLineNum = False;
214 textD->horizOffset = 0;
215 textD->hScrollBar = hScrollBar;
216 textD->vScrollBar = vScrollBar;
217 textD->fontStruct = fontStruct;
218 textD->ascent = fontStruct->ascent;
219 textD->descent = fontStruct->descent;
220 textD->fixedFontWidth = fontStruct->min_bounds.width ==
221 fontStruct->max_bounds.width ? fontStruct->min_bounds.width : -1;
222 textD->styleBuffer = NULL;
223 textD->styleTable = NULL;
224 textD->nStyles = 0;
225 textD->bgPixel = bgPixel;
226 textD->fgPixel = fgPixel;
227 textD->selectFGPixel = selectFGPixel;
228 textD->highlightFGPixel = highlightFGPixel;
229 textD->selectBGPixel = selectBGPixel;
230 textD->highlightBGPixel = highlightBGPixel;
231 textD->lineNumFGPixel = lineNumFGPixel;
232 textD->wrapMargin = wrapMargin;
233 textD->continuousWrap = continuousWrap;
234 allocateFixedFontGCs(textD, fontStruct, bgPixel, fgPixel, selectFGPixel,
235 selectBGPixel, highlightFGPixel, highlightBGPixel, lineNumFGPixel);
236 textD->styleGC = allocateGC(textD->w, 0, 0, 0, fontStruct->fid,
237 GCClipMask|GCForeground|GCBackground, GCArcMode);
238 textD->lineNumLeft = lineNumLeft;
239 textD->lineNumWidth = lineNumWidth;
240 textD->nVisibleLines = (height - 1) / (textD->ascent + textD->descent) + 1;
241 gcValues.foreground = cursorFGPixel;
242 textD->cursorFGGC = XtGetGC(widget, GCForeground, &gcValues);
243 textD->lineStarts = (int *)XtMalloc(sizeof(int) * textD->nVisibleLines);
244 textD->lineStarts[0] = 0;
245 textD->calltipW = NULL;
246 textD->calltipShell = NULL;
247 textD->calltip.ID = 0;
248 textD->calltipFGPixel = calltipFGPixel;
249 textD->calltipBGPixel = calltipBGPixel;
250 for (i=1; i<textD->nVisibleLines; i++)
251 textD->lineStarts[i] = -1;
252 textD->bgClassPixel = NULL;
253 textD->bgClass = NULL;
254 TextDSetupBGClasses(widget, bgClassString, &textD->bgClassPixel,
255 &textD->bgClass, bgPixel);
256 textD->suppressResync = 0;
257 textD->nLinesDeleted = 0;
258 textD->modifyingTabDist = 0;
259 textD->pointerHidden = False;
260 textD->graphicsExposeQueue = NULL;
262 /* Attach the callback to the text buffer for receiving modification
263 information */
264 if (buffer != NULL) {
265 BufAddModifyCB(buffer, bufModifiedCB, textD);
266 BufAddPreDeleteCB(buffer, bufPreDeleteCB, textD);
269 /* Initialize the scroll bars and attach movement callbacks */
270 if (vScrollBar != NULL) {
271 XtVaSetValues(vScrollBar, XmNminimum, 1, XmNmaximum, 2,
272 XmNsliderSize, 1, XmNrepeatDelay, 10, XmNvalue, 1, NULL);
273 XtAddCallback(vScrollBar, XmNdragCallback, vScrollCB, (XtPointer)textD);
274 XtAddCallback(vScrollBar, XmNvalueChangedCallback, vScrollCB,
275 (XtPointer)textD);
277 if (hScrollBar != NULL) {
278 XtVaSetValues(hScrollBar, XmNminimum, 0, XmNmaximum, 1,
279 XmNsliderSize, 1, XmNrepeatDelay, 10, XmNvalue, 0,
280 XmNincrement, fontStruct->max_bounds.width, NULL);
281 XtAddCallback(hScrollBar, XmNdragCallback, hScrollCB, (XtPointer)textD);
282 XtAddCallback(hScrollBar, XmNvalueChangedCallback, hScrollCB,
283 (XtPointer)textD);
286 /* Update the display to reflect the contents of the buffer */
287 if (buffer != NULL)
288 bufModifiedCB(0, buffer->length, 0, 0, NULL, textD);
290 /* Decide if the horizontal scroll bar needs to be visible */
291 hideOrShowHScrollBar(textD);
293 return textD;
297 ** Free a text display and release its associated memory. Note, the text
298 ** BUFFER that the text display displays is a separate entity and is not
299 ** freed, nor are the style buffer or style table.
301 void TextDFree(textDisp *textD)
303 BufRemoveModifyCB(textD->buffer, bufModifiedCB, textD);
304 BufRemovePreDeleteCB(textD->buffer, bufPreDeleteCB, textD);
305 releaseGC(textD->w, textD->gc);
306 releaseGC(textD->w, textD->selectGC);
307 releaseGC(textD->w, textD->highlightGC);
308 releaseGC(textD->w, textD->selectBGGC);
309 releaseGC(textD->w, textD->highlightBGGC);
310 releaseGC(textD->w, textD->styleGC);
311 releaseGC(textD->w, textD->lineNumGC);
312 XtFree((char *)textD->lineStarts);
313 while (TextDPopGraphicExposeQueueEntry(textD)) {
315 XtFree((char *)textD->bgClassPixel);
316 XtFree((char *)textD->bgClass);
317 XtFree((char *)textD);
321 ** Attach a text buffer to display, replacing the current buffer (if any)
323 void TextDSetBuffer(textDisp *textD, textBuffer *buffer)
325 /* If the text display is already displaying a buffer, clear it off
326 of the display and remove our callback from it */
327 if (textD->buffer != NULL) {
328 bufModifiedCB(0, 0, textD->buffer->length, 0, NULL, textD);
329 BufRemoveModifyCB(textD->buffer, bufModifiedCB, textD);
330 BufRemovePreDeleteCB(textD->buffer, bufPreDeleteCB, textD);
333 /* Add the buffer to the display, and attach a callback to the buffer for
334 receiving modification information when the buffer contents change */
335 textD->buffer = buffer;
336 BufAddModifyCB(buffer, bufModifiedCB, textD);
337 BufAddPreDeleteCB(buffer, bufPreDeleteCB, textD);
339 /* Update the display */
340 bufModifiedCB(0, buffer->length, 0, 0, NULL, textD);
344 ** return the displayed text buffer
346 textBuffer *TextDGetBuffer(textDisp *textD)
348 return textD->buffer;
352 ** Attach (or remove) highlight information in text display and redisplay.
353 ** Highlighting information consists of a style buffer which parallels the
354 ** normal text buffer, but codes font and color information for the display;
355 ** a style table which translates style buffer codes (indexed by buffer
356 ** character - 65 (ASCII code for 'A')) into fonts and colors; and a callback
357 ** mechanism for as-needed highlighting, triggered by a style buffer entry of
358 ** "unfinishedStyle". Style buffer can trigger additional redisplay during
359 ** a normal buffer modification if the buffer contains a primary selection
360 ** (see extendRangeForStyleMods for more information on this protocol).
362 ** Style buffers, tables and their associated memory are managed by the caller.
364 void TextDAttachHighlightData(textDisp *textD, textBuffer *styleBuffer,
365 styleTableEntry *styleTable, int nStyles, char unfinishedStyle,
366 unfinishedStyleCBProc unfinishedHighlightCB, void *cbArg)
368 textD->styleBuffer = styleBuffer;
369 textD->styleTable = styleTable;
370 textD->nStyles = nStyles;
371 textD->unfinishedStyle = unfinishedStyle;
372 textD->unfinishedHighlightCB = unfinishedHighlightCB;
373 textD->highlightCBArg = cbArg;
375 /* Call TextDSetFont to combine font information from style table and
376 primary font, adjust font-related parameters, and then redisplay */
377 TextDSetFont(textD, textD->fontStruct);
381 /* Change the (non syntax-highlit) colors */
382 void TextDSetColors(textDisp *textD, Pixel textFgP, Pixel textBgP,
383 Pixel selectFgP, Pixel selectBgP, Pixel hiliteFgP, Pixel hiliteBgP,
384 Pixel lineNoFgP, Pixel cursorFgP)
386 XGCValues values;
387 Display *d = XtDisplay(textD->w);
389 /* Update the stored pixels */
390 textD->lineNumFGPixel = lineNoFgP;
391 textD->bgPixel = textBgP;
392 textD->selectBGPixel = selectBgP;
393 textD->highlightBGPixel = hiliteBgP;
395 releaseGC(textD->w, textD->gc);
396 releaseGC(textD->w, textD->selectGC);
397 releaseGC(textD->w, textD->selectBGGC);
398 releaseGC(textD->w, textD->highlightGC);
399 releaseGC(textD->w, textD->highlightBGGC);
400 releaseGC(textD->w, textD->lineNumGC);
401 allocateFixedFontGCs(textD, textD->fontStruct, textBgP, textFgP, selectFgP,
402 selectBgP, hiliteFgP, hiliteBgP, lineNoFgP);
404 /* Change the cursor GC (the cursor GC is not shared). */
405 values.foreground = cursorFgP;
406 XChangeGC( d, textD->cursorFGGC, GCForeground, &values );
408 /* Redisplay */
409 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
410 textD->height);
411 redrawLineNumbers(textD, True);
415 ** Change the (non highlight) font
417 void TextDSetFont(textDisp *textD, XFontStruct *fontStruct)
419 Display *display = XtDisplay(textD->w);
420 int i, maxAscent = fontStruct->ascent, maxDescent = fontStruct->descent;
421 int width, height, fontWidth;
422 Pixel bgPixel, fgPixel, selectFGPixel, selectBGPixel;
423 Pixel highlightFGPixel, highlightBGPixel, lineNumFGPixel;
424 XGCValues values;
425 XFontStruct *styleFont;
427 /* If font size changes, cursor will be redrawn in a new position */
428 blankCursorProtrusions(textD);
430 /* If there is a (syntax highlighting) style table in use, find the new
431 maximum font height for this text display */
432 for (i=0; i<textD->nStyles; i++) {
433 styleFont = textD->styleTable[i].font;
434 if (styleFont != NULL && styleFont->ascent > maxAscent)
435 maxAscent = styleFont->ascent;
436 if (styleFont != NULL && styleFont->descent > maxDescent)
437 maxDescent = styleFont->descent;
439 textD->ascent = maxAscent;
440 textD->descent = maxDescent;
442 /* If all of the current fonts are fixed and match in width, compute */
443 fontWidth = fontStruct->max_bounds.width;
444 if (fontWidth != fontStruct->min_bounds.width)
445 fontWidth = -1;
446 else {
447 for (i=0; i<textD->nStyles; i++) {
448 styleFont = textD->styleTable[i].font;
449 if (styleFont != NULL &&
450 (styleFont->max_bounds.width != fontWidth ||
451 styleFont->max_bounds.width != styleFont->min_bounds.width))
452 fontWidth = -1;
455 textD->fixedFontWidth = fontWidth;
457 /* Don't let the height dip below one line, or bad things can happen */
458 if (textD->height < maxAscent + maxDescent)
459 textD->height = maxAscent + maxDescent;
461 /* Change the font. In most cases, this means re-allocating the
462 affected GCs (they are shared with other widgets, and if the primary
463 font changes, must be re-allocated to change it). Unfortunately,
464 this requres recovering all of the colors from the existing GCs */
465 textD->fontStruct = fontStruct;
466 XGetGCValues(display, textD->gc, GCForeground|GCBackground, &values);
467 fgPixel = values.foreground;
468 bgPixel = values.background;
469 XGetGCValues(display, textD->selectGC, GCForeground|GCBackground, &values);
470 selectFGPixel = values.foreground;
471 selectBGPixel = values.background;
472 XGetGCValues(display, textD->highlightGC,GCForeground|GCBackground,&values);
473 highlightFGPixel = values.foreground;
474 highlightBGPixel = values.background;
475 XGetGCValues(display, textD->lineNumGC, GCForeground, &values);
476 lineNumFGPixel = values.foreground;
477 releaseGC(textD->w, textD->gc);
478 releaseGC(textD->w, textD->selectGC);
479 releaseGC(textD->w, textD->highlightGC);
480 releaseGC(textD->w, textD->selectBGGC);
481 releaseGC(textD->w, textD->highlightBGGC);
482 releaseGC(textD->w, textD->lineNumGC);
483 allocateFixedFontGCs(textD, fontStruct, bgPixel, fgPixel, selectFGPixel,
484 selectBGPixel, highlightFGPixel, highlightBGPixel, lineNumFGPixel);
485 XSetFont(display, textD->styleGC, fontStruct->fid);
487 /* Do a full resize to force recalculation of font related parameters */
488 width = textD->width;
489 height = textD->height;
490 textD->width = textD->height = 0;
491 TextDResize(textD, width, height);
493 /* Redisplay */
494 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
495 textD->height);
497 /* Clean up line number area in case spacing has changed */
498 redrawLineNumbers(textD, True);
501 int TextDMinFontWidth(textDisp *textD, Boolean considerStyles)
503 int fontWidth = textD->fontStruct->max_bounds.width;
504 int i;
506 if (considerStyles) {
507 for (i = 0; i < textD->nStyles; ++i) {
508 int thisWidth = (textD->styleTable[i].font)->min_bounds.width;
509 if (thisWidth < fontWidth) {
510 fontWidth = thisWidth;
514 return(fontWidth);
517 int TextDMaxFontWidth(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)->max_bounds.width;
525 if (thisWidth > fontWidth) {
526 fontWidth = thisWidth;
530 return(fontWidth);
534 ** Change the size of the displayed text area
536 void TextDResize(textDisp *textD, int width, int height)
538 int oldVisibleLines = textD->nVisibleLines;
539 int canRedraw = XtWindow(textD->w) != 0;
540 int newVisibleLines = height / (textD->ascent + textD->descent);
541 int redrawAll = False;
542 int oldWidth = textD->width;
543 int exactHeight = height - height % (textD->ascent + textD->descent);
545 textD->width = width;
546 textD->height = height;
548 /* In continuous wrap mode, a change in width affects the total number of
549 lines in the buffer, and can leave the top line number incorrect, and
550 the top character no longer pointing at a valid line start */
551 if (textD->continuousWrap && textD->wrapMargin==0 && width!=oldWidth) {
552 int oldFirstChar = textD->firstChar;
553 textD->nBufferLines = TextDCountLines(textD, 0, textD->buffer->length,
554 True);
555 textD->firstChar = TextDStartOfLine(textD, textD->firstChar);
556 textD->topLineNum = TextDCountLines(textD, 0, textD->firstChar, True)+1;
557 redrawAll = True;
558 offsetAbsLineNum(textD, oldFirstChar);
561 /* reallocate and update the line starts array, which may have changed
562 size and/or contents. (contents can change in continuous wrap mode
563 when the width changes, even without a change in height) */
564 if (oldVisibleLines < newVisibleLines) {
565 XtFree((char *)textD->lineStarts);
566 textD->lineStarts = (int *)XtMalloc(sizeof(int) * newVisibleLines);
568 textD->nVisibleLines = newVisibleLines;
569 calcLineStarts(textD, 0, newVisibleLines);
570 calcLastChar(textD);
572 /* if the window became shorter, there may be partially drawn
573 text left at the bottom edge, which must be cleaned up */
574 if (canRedraw && oldVisibleLines>newVisibleLines && exactHeight!=height)
575 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->left,
576 textD->top + exactHeight, textD->width,
577 height - exactHeight, False);
579 /* if the window became taller, there may be an opportunity to display
580 more text by scrolling down */
581 if (canRedraw && oldVisibleLines < newVisibleLines && textD->topLineNum +
582 textD->nVisibleLines > textD->nBufferLines)
583 setScroll(textD, max(1, textD->nBufferLines - textD->nVisibleLines +
584 2 + TEXT_OF_TEXTD(textD).cursorVPadding),
585 textD->horizOffset, False, False);
587 /* Update the scroll bar page increment size (as well as other scroll
588 bar parameters. If updating the horizontal range caused scrolling,
589 redraw */
590 updateVScrollBarRange(textD);
591 if (updateHScrollBarRange(textD))
592 redrawAll = True;
594 /* If a full redraw is needed */
595 if (redrawAll && canRedraw)
596 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
597 textD->height);
599 /* Decide if the horizontal scroll bar needs to be visible */
600 hideOrShowHScrollBar(textD);
602 /* Refresh the line number display to draw more line numbers, or
603 erase extras */
604 redrawLineNumbers(textD, True);
606 /* Redraw the calltip */
607 TextDRedrawCalltip(textD, 0);
611 ** Refresh a rectangle of the text display. left and top are in coordinates of
612 ** the text drawing window
614 void TextDRedisplayRect(textDisp *textD, int left, int top, int width,
615 int height)
617 int fontHeight, firstLine, lastLine, line;
619 /* find the line number range of the display */
620 fontHeight = textD->ascent + textD->descent;
621 firstLine = (top - textD->top - fontHeight + 1) / fontHeight;
622 lastLine = (top + height - textD->top) / fontHeight;
624 /* If the graphics contexts are shared using XtAllocateGC, their
625 clipping rectangles may have changed since the last use */
626 resetClipRectangles(textD);
628 /* draw the lines of text */
629 for (line=firstLine; line<=lastLine; line++)
630 redisplayLine(textD, line, left, left+width, 0, INT_MAX);
632 /* draw the line numbers if exposed area includes them */
633 if (textD->lineNumWidth != 0 && left <= textD->lineNumLeft + textD->lineNumWidth)
634 redrawLineNumbers(textD, False);
638 ** Refresh all of the text between buffer positions "start" and "end"
639 ** not including the character at the position "end".
640 ** If end points beyond the end of the buffer, refresh the whole display
641 ** after pos, including blank lines which are not technically part of
642 ** any range of characters.
644 void TextDRedisplayRange(textDisp *textD, int start, int end)
646 int i, startLine, lastLine, startIndex, endIndex;
648 /* If the range is outside of the displayed text, just return */
649 if (end < textD->firstChar || (start > textD->lastChar &&
650 !emptyLinesVisible(textD)))
651 return;
653 /* Clean up the starting and ending values */
654 if (start < 0) start = 0;
655 if (start > textD->buffer->length) start = textD->buffer->length;
656 if (end < 0) end = 0;
657 if (end > textD->buffer->length) end = textD->buffer->length;
659 /* Get the starting and ending lines */
660 if (start < textD->firstChar)
661 start = textD->firstChar;
662 if (!posToVisibleLineNum(textD, start, &startLine))
663 startLine = textD->nVisibleLines - 1;
664 if (end >= textD->lastChar) {
665 lastLine = textD->nVisibleLines - 1;
666 } else {
667 if (!posToVisibleLineNum(textD, end, &lastLine)) {
668 /* shouldn't happen */
669 lastLine = textD->nVisibleLines - 1;
673 /* Get the starting and ending positions within the lines */
674 startIndex = textD->lineStarts[startLine] == -1 ? 0 :
675 start - textD->lineStarts[startLine];
676 if (end >= textD->lastChar)
677 endIndex = INT_MAX;
678 else if (textD->lineStarts[lastLine] == -1)
679 endIndex = 0;
680 else
681 endIndex = end - textD->lineStarts[lastLine];
683 /* Reset the clipping rectangles for the drawing GCs which are shared
684 using XtAllocateGC, and may have changed since the last use */
685 resetClipRectangles(textD);
687 /* If the starting and ending lines are the same, redisplay the single
688 line between "start" and "end" */
689 if (startLine == lastLine) {
690 redisplayLine(textD, startLine, 0, INT_MAX, startIndex, endIndex);
691 return;
694 /* Redisplay the first line from "start" */
695 redisplayLine(textD, startLine, 0, INT_MAX, startIndex, INT_MAX);
697 /* Redisplay the lines in between at their full width */
698 for (i=startLine+1; i<lastLine; i++)
699 redisplayLine(textD, i, 0, INT_MAX, 0, INT_MAX);
701 /* Redisplay the last line to "end" */
702 redisplayLine(textD, lastLine, 0, INT_MAX, 0, endIndex);
706 ** Set the scroll position of the text display vertically by line number and
707 ** horizontally by pixel offset from the left margin
709 void TextDSetScroll(textDisp *textD, int topLineNum, int horizOffset)
711 int sliderSize, sliderMax;
712 int vPadding = (int)(TEXT_OF_TEXTD(textD).cursorVPadding);
714 /* Limit the requested scroll position to allowable values */
715 if (topLineNum < 1)
716 topLineNum = 1;
717 else if ((topLineNum > textD->topLineNum) &&
718 (topLineNum > (textD->nBufferLines + 2 - textD->nVisibleLines +
719 vPadding)))
720 topLineNum = max(textD->topLineNum,
721 textD->nBufferLines + 2 - textD->nVisibleLines + vPadding);
722 XtVaGetValues(textD->hScrollBar, XmNmaximum, &sliderMax,
723 XmNsliderSize, &sliderSize, NULL);
724 if (horizOffset < 0)
725 horizOffset = 0;
726 if (horizOffset > sliderMax - sliderSize)
727 horizOffset = sliderMax - sliderSize;
729 setScroll(textD, topLineNum, horizOffset, True, True);
733 ** Get the current scroll position for the text display, in terms of line
734 ** number of the top line and horizontal pixel offset from the left margin
736 void TextDGetScroll(textDisp *textD, int *topLineNum, int *horizOffset)
738 *topLineNum = textD->topLineNum;
739 *horizOffset = textD->horizOffset;
743 ** Set the position of the text insertion cursor for text display "textD"
745 void TextDSetInsertPosition(textDisp *textD, int newPos)
747 /* make sure new position is ok, do nothing if it hasn't changed */
748 if (newPos == textD->cursorPos)
749 return;
750 if (newPos < 0) newPos = 0;
751 if (newPos > textD->buffer->length) newPos = textD->buffer->length;
753 /* cursor movement cancels vertical cursor motion column */
754 textD->cursorPreferredCol = -1;
756 /* erase the cursor at it's previous position */
757 TextDBlankCursor(textD);
759 /* draw it at its new position */
760 textD->cursorPos = newPos;
761 textD->cursorOn = True;
762 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos + 1);
765 void TextDBlankCursor(textDisp *textD)
767 if (!textD->cursorOn)
768 return;
770 blankCursorProtrusions(textD);
771 textD->cursorOn = False;
772 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
775 void TextDUnblankCursor(textDisp *textD)
777 if (!textD->cursorOn) {
778 textD->cursorOn = True;
779 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
783 void TextDSetCursorStyle(textDisp *textD, int style)
785 textD->cursorStyle = style;
786 blankCursorProtrusions(textD);
787 if (textD->cursorOn)
788 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos + 1);
791 void TextDSetWrapMode(textDisp *textD, int wrap, int wrapMargin)
793 textD->wrapMargin = wrapMargin;
794 textD->continuousWrap = wrap;
796 /* wrapping can change change the total number of lines, re-count */
797 textD->nBufferLines = TextDCountLines(textD, 0, textD->buffer->length,True);
799 /* changing wrap margins wrap or changing from wrapped mode to non-wrapped
800 can leave the character at the top no longer at a line start, and/or
801 change the line number */
802 textD->firstChar = TextDStartOfLine(textD, textD->firstChar);
803 textD->topLineNum = TextDCountLines(textD, 0, textD->firstChar, True) + 1;
804 resetAbsLineNum(textD);
806 /* update the line starts array */
807 calcLineStarts(textD, 0, textD->nVisibleLines);
808 calcLastChar(textD);
810 /* Update the scroll bar page increment size (as well as other scroll
811 bar parameters) */
812 updateVScrollBarRange(textD);
813 updateHScrollBarRange(textD);
815 /* Decide if the horizontal scroll bar needs to be visible */
816 hideOrShowHScrollBar(textD);
818 /* Do a full redraw */
819 TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left,
820 textD->height);
823 int TextDGetInsertPosition(textDisp *textD)
825 return textD->cursorPos;
829 ** Insert "text" at the current cursor location. This has the same
830 ** effect as inserting the text into the buffer using BufInsert and
831 ** then moving the insert position after the newly inserted text, except
832 ** that it's optimized to do less redrawing.
834 void TextDInsert(textDisp *textD, char *text)
836 int pos = textD->cursorPos;
838 textD->cursorToHint = pos + strlen(text);
839 BufInsert(textD->buffer, pos, text);
840 textD->cursorToHint = NO_HINT;
844 ** Insert "text" (which must not contain newlines), overstriking the current
845 ** cursor location.
847 void TextDOverstrike(textDisp *textD, char *text)
849 int startPos = textD->cursorPos;
850 textBuffer *buf = textD->buffer;
851 int lineStart = BufStartOfLine(buf, startPos);
852 int textLen = strlen(text);
853 int i, p, endPos, indent, startIndent, endIndent;
854 char *c, ch, *paddedText = NULL;
856 /* determine how many displayed character positions are covered */
857 startIndent = BufCountDispChars(textD->buffer, lineStart, startPos);
858 indent = startIndent;
859 for (c=text; *c!='\0'; c++)
860 indent += BufCharWidth(*c, indent, buf->tabDist, buf->nullSubsChar);
861 endIndent = indent;
863 /* find which characters to remove, and if necessary generate additional
864 padding to make up for removed control characters at the end */
865 indent=startIndent;
866 for (p=startPos; ; p++) {
867 if (p == buf->length)
868 break;
869 ch = BufGetCharacter(buf, p);
870 if (ch == '\n')
871 break;
872 indent += BufCharWidth(ch, indent, buf->tabDist, buf->nullSubsChar);
873 if (indent == endIndent) {
874 p++;
875 break;
876 } else if (indent > endIndent) {
877 if (ch != '\t') {
878 p++;
879 paddedText = XtMalloc(textLen + MAX_EXP_CHAR_LEN + 1);
880 strcpy(paddedText, text);
881 for (i=0; i<indent-endIndent; i++)
882 paddedText[textLen+i] = ' ';
883 paddedText[textLen+i] = '\0';
885 break;
888 endPos = p;
890 textD->cursorToHint = startPos + textLen;
891 BufReplace(buf, startPos, endPos, paddedText == NULL ? text : paddedText);
892 textD->cursorToHint = NO_HINT;
893 if (paddedText != NULL)
894 XtFree(paddedText);
898 ** Translate window coordinates to the nearest text cursor position.
900 int TextDXYToPosition(textDisp *textD, int x, int y)
902 return xyToPos(textD, x, y, CURSOR_POS);
906 ** Translate window coordinates to the nearest character cell.
908 int TextDXYToCharPos(textDisp *textD, int x, int y)
910 return xyToPos(textD, x, y, CHARACTER_POS);
914 ** Translate window coordinates to the nearest row and column number for
915 ** positioning the cursor. This, of course, makes no sense when the font
916 ** is proportional, since there are no absolute columns.
918 void TextDXYToUnconstrainedPosition(textDisp *textD, int x, int y, int *row,
919 int *column)
921 xyToUnconstrainedPos(textD, x, y, row, column, CURSOR_POS);
925 ** Translate line and column to the nearest row and column number for
926 ** positioning the cursor. This, of course, makes no sense when the font
927 ** is proportional, since there are no absolute columns.
929 int TextDLineAndColToPos(textDisp *textD, int lineNum, int column)
931 int i, lineEnd, charIndex, outIndex;
932 int lineStart=0, charLen=0;
933 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
935 /* Count lines */
936 if (lineNum < 1)
937 lineNum = 1;
938 lineEnd = -1;
939 for (i=1; i<=lineNum && lineEnd<textD->buffer->length; i++) {
940 lineStart = lineEnd + 1;
941 lineEnd = BufEndOfLine(textD->buffer, lineStart);
944 /* If line is beyond end of buffer, position at last character in buffer */
945 if ( lineNum >= i ) {
946 return lineEnd;
949 /* Start character index at zero */
950 charIndex=0;
952 /* Only have to count columns if column isn't zero (or negative) */
953 if (column > 0) {
954 /* Count columns, expanding each character */
955 lineStr = BufGetRange(textD->buffer, lineStart, lineEnd);
956 outIndex = 0;
957 for(i=lineStart; i<lineEnd; i++, charIndex++) {
958 charLen = BufExpandCharacter(lineStr[charIndex], outIndex,
959 expandedChar, textD->buffer->tabDist,
960 textD->buffer->nullSubsChar);
961 if ( outIndex+charLen >= column ) break;
962 outIndex+=charLen;
965 /* If the column is in the middle of an expanded character, put cursor
966 * in front of character if in first half of character, and behind
967 * character if in last half of character
969 if (column >= outIndex + ( charLen / 2 ))
970 charIndex++;
972 /* If we are beyond the end of the line, back up one space */
973 if ((i>=lineEnd)&&(charIndex>0)) charIndex--;
976 /* Position is the start of the line plus the index into line buffer */
977 return lineStart + charIndex;
981 ** Translate a buffer text position to the XY location where the center
982 ** of the cursor would be positioned to point to that character. Returns
983 ** False if the position is not displayed because it is VERTICALLY out
984 ** of view. If the position is horizontally out of view, returns the
985 ** x coordinate where the position would be if it were visible.
987 int TextDPositionToXY(textDisp *textD, int pos, int *x, int *y)
989 int charIndex, lineStartPos, fontHeight, lineLen;
990 int visLineNum, charLen, outIndex, xStep, charStyle;
991 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
993 /* If position is not displayed, return false */
994 if (pos < textD->firstChar ||
995 (pos > textD->lastChar && !emptyLinesVisible(textD)))
996 return False;
998 /* Calculate y coordinate */
999 if (!posToVisibleLineNum(textD, pos, &visLineNum))
1000 return False;
1001 fontHeight = textD->ascent + textD->descent;
1002 *y = textD->top + visLineNum*fontHeight + fontHeight/2;
1004 /* Get the text, length, and buffer position of the line. If the position
1005 is beyond the end of the buffer and should be at the first position on
1006 the first empty line, don't try to get or scan the text */
1007 lineStartPos = textD->lineStarts[visLineNum];
1008 if (lineStartPos == -1) {
1009 *x = textD->left - textD->horizOffset;
1010 return True;
1012 lineLen = visLineLength(textD, visLineNum);
1013 lineStr = BufGetRange(textD->buffer, lineStartPos, lineStartPos + lineLen);
1015 /* Step through character positions from the beginning of the line
1016 to "pos" to calculate the x coordinate */
1017 xStep = textD->left - textD->horizOffset;
1018 outIndex = 0;
1019 for(charIndex=0; charIndex<pos-lineStartPos; charIndex++) {
1020 charLen = BufExpandCharacter(lineStr[charIndex], outIndex, expandedChar,
1021 textD->buffer->tabDist, textD->buffer->nullSubsChar);
1022 charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1023 outIndex, lineStr[charIndex]);
1024 xStep += stringWidth(textD, expandedChar, charLen, charStyle);
1025 outIndex += charLen;
1027 *x = xStep;
1028 XtFree(lineStr);
1029 return True;
1033 ** If the text widget is maintaining a line number count appropriate to "pos"
1034 ** return the line and column numbers of pos, otherwise return False. If
1035 ** continuous wrap mode is on, returns the absolute line number (as opposed to
1036 ** the wrapped line number which is used for scrolling). THIS ROUTINE ONLY
1037 ** WORKS FOR DISPLAYED LINES AND, IN CONTINUOUS WRAP MODE, ONLY WHEN THE
1038 ** ABSOLUTE LINE NUMBER IS BEING MAINTAINED. Otherwise, it returns False.
1040 int TextDPosToLineAndCol(textDisp *textD, int pos, int *lineNum, int *column)
1042 textBuffer *buf = textD->buffer;
1044 /* In continuous wrap mode, the absolute (non-wrapped) line count is
1045 maintained separately, as needed. Only return it if we're actually
1046 keeping track of it and pos is in the displayed text */
1047 if (textD->continuousWrap) {
1048 if (!maintainingAbsTopLineNum(textD) || pos < textD->firstChar ||
1049 pos > textD->lastChar)
1050 return False;
1051 *lineNum = textD->absTopLineNum + BufCountLines(buf,
1052 textD->firstChar, pos);
1053 *column = BufCountDispChars(buf, BufStartOfLine(buf, pos), pos);
1054 return True;
1057 /* Only return the data if pos is within the displayed text */
1058 if (!posToVisibleLineNum(textD, pos, lineNum))
1059 return False;
1060 *column = BufCountDispChars(buf, textD->lineStarts[*lineNum], pos);
1061 *lineNum += textD->topLineNum;
1062 return True;
1066 ** Return True if position (x, y) is inside of the primary selection
1068 int TextDInSelection(textDisp *textD, int x, int y)
1070 int row, column, pos = xyToPos(textD, x, y, CHARACTER_POS);
1071 textBuffer *buf = textD->buffer;
1073 xyToUnconstrainedPos(textD, x, y, &row, &column, CHARACTER_POS);
1074 if (rangeTouchesRectSel(&buf->primary, textD->firstChar, textD->lastChar))
1075 column = TextDOffsetWrappedColumn(textD, row, column);
1076 return inSelection(&buf->primary, pos, BufStartOfLine(buf, pos), column);
1080 ** Correct a column number based on an unconstrained position (as returned by
1081 ** TextDXYToUnconstrainedPosition) to be relative to the last actual newline
1082 ** in the buffer before the row and column position given, rather than the
1083 ** last line start created by line wrapping. This is an adapter
1084 ** for rectangular selections and code written before continuous wrap mode,
1085 ** which thinks that the unconstrained column is the number of characters
1086 ** from the last newline. Obviously this is time consuming, because it
1087 ** invloves character re-counting.
1089 int TextDOffsetWrappedColumn(textDisp *textD, int row, int column)
1091 int lineStart, dispLineStart;
1093 if (!textD->continuousWrap || row < 0 || row > textD->nVisibleLines)
1094 return column;
1095 dispLineStart = textD->lineStarts[row];
1096 if (dispLineStart == -1)
1097 return column;
1098 lineStart = BufStartOfLine(textD->buffer, dispLineStart);
1099 return column + BufCountDispChars(textD->buffer, lineStart, dispLineStart);
1103 ** Correct a row number from an unconstrained position (as returned by
1104 ** TextDXYToUnconstrainedPosition) to a straight number of newlines from the
1105 ** top line of the display. Because rectangular selections are based on
1106 ** newlines, rather than display wrapping, and anywhere a rectangular selection
1107 ** needs a row, it needs it in terms of un-wrapped lines.
1109 int TextDOffsetWrappedRow(textDisp *textD, int row)
1111 if (!textD->continuousWrap || row < 0 || row > textD->nVisibleLines)
1112 return row;
1113 return BufCountLines(textD->buffer, textD->firstChar,
1114 textD->lineStarts[row]);
1118 ** Scroll the display to bring insertion cursor into view.
1120 ** Note: it would be nice to be able to do this without counting lines twice
1121 ** (setScroll counts them too) and/or to count from the most efficient
1122 ** starting point, but the efficiency of this routine is not as important to
1123 ** the overall performance of the text display.
1125 void TextDMakeInsertPosVisible(textDisp *textD)
1127 int hOffset, topLine, x, y;
1128 int cursorPos = textD->cursorPos;
1129 int linesFromTop = 0, do_padding = 1;
1130 int cursorVPadding = (int)TEXT_OF_TEXTD(textD).cursorVPadding;
1132 hOffset = textD->horizOffset;
1133 topLine = textD->topLineNum;
1135 /* Don't do padding if this is a mouse operation */
1136 do_padding = ((TEXT_OF_TEXTD(textD).dragState == NOT_CLICKED) &&
1137 (cursorVPadding > 0));
1139 /* Find the new top line number */
1140 if (cursorPos < textD->firstChar) {
1141 topLine -= TextDCountLines(textD, cursorPos, textD->firstChar, False);
1142 /* linesFromTop = 0; */
1143 } else if (cursorPos > textD->lastChar && !emptyLinesVisible(textD)) {
1144 topLine += TextDCountLines(textD, textD->lastChar -
1145 (wrapUsesCharacter(textD, textD->lastChar) ? 0 : 1),
1146 cursorPos, False);
1147 linesFromTop = textD->nVisibleLines-1;
1148 } else if (cursorPos == textD->lastChar && !emptyLinesVisible(textD) &&
1149 !wrapUsesCharacter(textD, textD->lastChar)) {
1150 topLine++;
1151 linesFromTop = textD->nVisibleLines-1;
1152 } else {
1153 /* Avoid extra counting if cursorVPadding is disabled */
1154 if (do_padding)
1155 linesFromTop = TextDCountLines(textD, textD->firstChar,
1156 cursorPos, True);
1158 if (topLine < 1) {
1159 fprintf(stderr, "internal consistency check tl1 failed\n");
1160 topLine = 1;
1163 if (do_padding) {
1164 /* Keep the cursor away from the top or bottom of screen. */
1165 if (textD->nVisibleLines <= 2*(int)cursorVPadding) {
1166 topLine += (linesFromTop - textD->nVisibleLines/2);
1167 topLine = max(topLine, 1);
1168 } else if (linesFromTop < (int)cursorVPadding) {
1169 topLine -= (cursorVPadding - linesFromTop);
1170 topLine = max(topLine, 1);
1171 } else if (linesFromTop > textD->nVisibleLines-(int)cursorVPadding-1) {
1172 topLine += (linesFromTop - (textD->nVisibleLines-cursorVPadding-1));
1176 /* Find the new setting for horizontal offset (this is a bit ungraceful).
1177 If the line is visible, just use TextDPositionToXY to get the position
1178 to scroll to, otherwise, do the vertical scrolling first, then the
1179 horizontal */
1180 if (!TextDPositionToXY(textD, cursorPos, &x, &y)) {
1181 setScroll(textD, topLine, hOffset, True, True);
1182 if (!TextDPositionToXY(textD, cursorPos, &x, &y))
1183 return; /* Give up, it's not worth it (but why does it fail?) */
1185 if (x > textD->left + textD->width)
1186 hOffset += x - (textD->left + textD->width);
1187 else if (x < textD->left)
1188 hOffset += x - textD->left;
1190 /* Do the scroll */
1191 setScroll(textD, topLine, hOffset, True, True);
1195 ** Return the current preferred column along with the current
1196 ** visible line index (-1 if not visible) and the lineStartPos
1197 ** of the current insert position.
1199 int TextDPreferredColumn(textDisp *textD, int *visLineNum, int *lineStartPos)
1201 int column;
1203 /* Find the position of the start of the line. Use the line starts array
1204 if possible, to avoid unbounded line-counting in continuous wrap mode */
1205 if (posToVisibleLineNum(textD, textD->cursorPos, visLineNum)) {
1206 *lineStartPos = textD->lineStarts[*visLineNum];
1208 else {
1209 *lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1210 *visLineNum = -1;
1213 /* Decide what column to move to, if there's a preferred column use that */
1214 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1215 BufCountDispChars(textD->buffer, *lineStartPos, textD->cursorPos);
1216 return(column);
1220 ** Return the insert position of the requested column given
1221 ** the lineStartPos.
1223 int TextDPosOfPreferredCol(textDisp *textD, int column, int lineStartPos)
1225 int newPos;
1227 newPos = BufCountForwardDispChars(textD->buffer, lineStartPos, column);
1228 if (textD->continuousWrap) {
1229 newPos = min(newPos, TextDEndOfLine(textD, lineStartPos, True));
1231 return(newPos);
1235 ** Cursor movement functions
1237 int TextDMoveRight(textDisp *textD)
1239 if (textD->cursorPos >= textD->buffer->length)
1240 return False;
1241 TextDSetInsertPosition(textD, textD->cursorPos + 1);
1242 return True;
1245 int TextDMoveLeft(textDisp *textD)
1247 if (textD->cursorPos <= 0)
1248 return False;
1249 TextDSetInsertPosition(textD, textD->cursorPos - 1);
1250 return True;
1253 int TextDMoveUp(textDisp *textD, int absolute)
1255 int lineStartPos, column, prevLineStartPos, newPos, visLineNum;
1257 /* Find the position of the start of the line. Use the line starts array
1258 if possible, to avoid unbounded line-counting in continuous wrap mode */
1259 if (absolute) {
1260 lineStartPos = BufStartOfLine(textD->buffer, textD->cursorPos);
1261 visLineNum = -1;
1262 } else if (posToVisibleLineNum(textD, textD->cursorPos, &visLineNum))
1263 lineStartPos = textD->lineStarts[visLineNum];
1264 else {
1265 lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1266 visLineNum = -1;
1268 if (lineStartPos == 0)
1269 return False;
1271 /* Decide what column to move to, if there's a preferred column use that */
1272 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1273 BufCountDispChars(textD->buffer, lineStartPos, textD->cursorPos);
1275 /* count forward from the start of the previous line to reach the column */
1276 if (absolute)
1277 prevLineStartPos = BufCountBackwardNLines(textD->buffer, lineStartPos, 1);
1278 else if (visLineNum != -1 && visLineNum != 0)
1279 prevLineStartPos = textD->lineStarts[visLineNum-1];
1280 else
1281 prevLineStartPos = TextDCountBackwardNLines(textD, lineStartPos, 1);
1282 newPos = BufCountForwardDispChars(textD->buffer, prevLineStartPos, column);
1283 if (textD->continuousWrap && !absolute)
1284 newPos = min(newPos, TextDEndOfLine(textD, prevLineStartPos, True));
1286 /* move the cursor */
1287 TextDSetInsertPosition(textD, newPos);
1289 /* if a preferred column wasn't aleady established, establish it */
1290 textD->cursorPreferredCol = column;
1292 return True;
1294 int TextDMoveDown(textDisp *textD, int absolute)
1296 int lineStartPos, column, nextLineStartPos, newPos, visLineNum;
1298 if (textD->cursorPos == textD->buffer->length)
1299 return False;
1300 if (absolute) {
1301 lineStartPos = BufStartOfLine(textD->buffer, textD->cursorPos);
1302 visLineNum = -1;
1303 } else if (posToVisibleLineNum(textD, textD->cursorPos, &visLineNum))
1304 lineStartPos = textD->lineStarts[visLineNum];
1305 else {
1306 lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1307 visLineNum = -1;
1309 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1310 BufCountDispChars(textD->buffer, lineStartPos, textD->cursorPos);
1311 if (absolute)
1312 nextLineStartPos = BufCountForwardNLines(textD->buffer, lineStartPos, 1);
1313 else
1314 nextLineStartPos = TextDCountForwardNLines(textD, lineStartPos, 1, True);
1315 newPos = BufCountForwardDispChars(textD->buffer, nextLineStartPos, column);
1316 if (textD->continuousWrap && !absolute)
1317 newPos = min(newPos, TextDEndOfLine(textD, nextLineStartPos, True));
1318 TextDSetInsertPosition(textD, newPos);
1319 textD->cursorPreferredCol = column;
1321 return True;
1325 ** Same as BufCountLines, but takes in to account wrapping if wrapping is
1326 ** turned on. If the caller knows that startPos is at a line start, it
1327 ** can pass "startPosIsLineStart" as True to make the call more efficient
1328 ** by avoiding the additional step of scanning back to the last newline.
1330 int TextDCountLines(textDisp *textD, int startPos, int endPos,
1331 int startPosIsLineStart)
1333 int retLines, retPos, retLineStart, retLineEnd;
1335 /* If we're not wrapping use simple (and more efficient) BufCountLines */
1336 if (!textD->continuousWrap)
1337 return BufCountLines(textD->buffer, startPos, endPos);
1339 wrappedLineCounter(textD, textD->buffer, startPos, endPos, INT_MAX,
1340 startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1341 &retLineEnd);
1342 return retLines;
1346 ** Same as BufCountForwardNLines, but takes in to account line breaks when
1347 ** wrapping is turned on. If the caller knows that startPos is at a line start,
1348 ** it can pass "startPosIsLineStart" as True to make the call more efficient
1349 ** by avoiding the additional step of scanning back to the last newline.
1351 int TextDCountForwardNLines(textDisp *textD, int startPos, int nLines,
1352 int startPosIsLineStart)
1354 int retLines, retPos, retLineStart, retLineEnd;
1356 /* if we're not wrapping use more efficient BufCountForwardNLines */
1357 if (!textD->continuousWrap)
1358 return BufCountForwardNLines(textD->buffer, startPos, nLines);
1360 /* wrappedLineCounter can't handle the 0 lines case */
1361 if (nLines == 0)
1362 return startPos;
1364 /* use the common line counting routine to count forward */
1365 wrappedLineCounter(textD, textD->buffer, startPos, textD->buffer->length,
1366 nLines, startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1367 &retLineEnd);
1368 return retPos;
1372 ** Same as BufEndOfLine, but takes in to account line breaks when wrapping
1373 ** is turned on. If the caller knows that startPos is at a line start, it
1374 ** can pass "startPosIsLineStart" as True to make the call more efficient
1375 ** by avoiding the additional step of scanning back to the last newline.
1377 ** Note that the definition of the end of a line is less clear when continuous
1378 ** wrap is on. With continuous wrap off, it's just a pointer to the newline
1379 ** that ends the line. When it's on, it's the character beyond the last
1380 ** DISPLAYABLE character on the line, where a whitespace character which has
1381 ** been "converted" to a newline for wrapping is not considered displayable.
1382 ** Also note that, a line can be wrapped at a non-whitespace character if the
1383 ** line had no whitespace. In this case, this routine returns a pointer to
1384 ** the start of the next line. This is also consistent with the model used by
1385 ** visLineLength.
1387 int TextDEndOfLine(textDisp *textD, int pos, int startPosIsLineStart)
1389 int retLines, retPos, retLineStart, retLineEnd;
1391 /* If we're not wrapping use more efficien BufEndOfLine */
1392 if (!textD->continuousWrap)
1393 return BufEndOfLine(textD->buffer, pos);
1395 if (pos == textD->buffer->length)
1396 return pos;
1397 wrappedLineCounter(textD, textD->buffer, pos, textD->buffer->length, 1,
1398 startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1399 &retLineEnd);
1400 return retLineEnd;
1404 ** Same as BufStartOfLine, but returns the character after last wrap point
1405 ** rather than the last newline.
1407 int TextDStartOfLine(textDisp *textD, int pos)
1409 int retLines, retPos, retLineStart, retLineEnd;
1411 /* If we're not wrapping, use the more efficient BufStartOfLine */
1412 if (!textD->continuousWrap)
1413 return BufStartOfLine(textD->buffer, pos);
1415 wrappedLineCounter(textD, textD->buffer, BufStartOfLine(textD->buffer, pos),
1416 pos, INT_MAX, True, 0, &retPos, &retLines, &retLineStart,
1417 &retLineEnd);
1418 return retLineStart;
1422 ** Same as BufCountBackwardNLines, but takes in to account line breaks when
1423 ** wrapping is turned on.
1425 int TextDCountBackwardNLines(textDisp *textD, int startPos, int nLines)
1427 textBuffer *buf = textD->buffer;
1428 int pos, lineStart, retLines, retPos, retLineStart, retLineEnd;
1430 /* If we're not wrapping, use the more efficient BufCountBackwardNLines */
1431 if (!textD->continuousWrap)
1432 return BufCountBackwardNLines(textD->buffer, startPos, nLines);
1434 pos = startPos;
1435 while (True) {
1436 lineStart = BufStartOfLine(buf, pos);
1437 wrappedLineCounter(textD, textD->buffer, lineStart, pos, INT_MAX,
1438 True, 0, &retPos, &retLines, &retLineStart, &retLineEnd);
1439 if (retLines > nLines)
1440 return TextDCountForwardNLines(textD, lineStart, retLines-nLines,
1441 True);
1442 nLines -= retLines;
1443 pos = lineStart - 1;
1444 if (pos < 0)
1445 return 0;
1446 nLines -= 1;
1451 ** Callback attached to the text buffer to receive delete information before
1452 ** the modifications are actually made.
1454 static void bufPreDeleteCB(int pos, int nDeleted, void *cbArg)
1456 textDisp *textD = (textDisp *)cbArg;
1457 if (textD->continuousWrap &&
1458 (textD->fixedFontWidth == -1 || textD->modifyingTabDist))
1459 /* Note: we must perform this measurement, even if there is not a
1460 single character deleted; the number of "deleted" lines is the
1461 number of visual lines spanned by the real line in which the
1462 modification takes place.
1463 Also, a modification of the tab distance requires the same
1464 kind of calculations in advance, even if the font width is "fixed",
1465 because when the width of the tab characters changes, the layout
1466 of the text may be completely different. */
1467 measureDeletedLines(textD, pos, nDeleted);
1468 else
1469 textD->suppressResync = 0; /* Probably not needed, but just in case */
1473 ** Callback attached to the text buffer to receive modification information
1475 static void bufModifiedCB(int pos, int nInserted, int nDeleted,
1476 int nRestyled, char *deletedText, void *cbArg)
1478 int linesInserted, linesDeleted, startDispPos, endDispPos;
1479 textDisp *textD = (textDisp *)cbArg;
1480 textBuffer *buf = textD->buffer;
1481 int oldFirstChar = textD->firstChar;
1482 int scrolled, origCursorPos = textD->cursorPos;
1483 int wrapModStart, wrapModEnd;
1485 /* buffer modification cancels vertical cursor motion column */
1486 if (nInserted != 0 || nDeleted != 0)
1487 textD->cursorPreferredCol = -1;
1489 /* Count the number of lines inserted and deleted, and in the case
1490 of continuous wrap mode, how much has changed */
1491 if (textD->continuousWrap) {
1492 findWrapRange(textD, deletedText, pos, nInserted, nDeleted,
1493 &wrapModStart, &wrapModEnd, &linesInserted, &linesDeleted);
1494 } else {
1495 linesInserted = nInserted == 0 ? 0 :
1496 BufCountLines(buf, pos, pos + nInserted);
1497 linesDeleted = nDeleted == 0 ? 0 : countLines(deletedText);
1500 /* Update the line starts and topLineNum */
1501 if (nInserted != 0 || nDeleted != 0) {
1502 if (textD->continuousWrap) {
1503 updateLineStarts(textD, wrapModStart, wrapModEnd-wrapModStart,
1504 nDeleted + pos-wrapModStart + (wrapModEnd-(pos+nInserted)),
1505 linesInserted, linesDeleted, &scrolled);
1506 } else {
1507 updateLineStarts(textD, pos, nInserted, nDeleted, linesInserted,
1508 linesDeleted, &scrolled);
1510 } else
1511 scrolled = False;
1513 /* If we're counting non-wrapped lines as well, maintain the absolute
1514 (non-wrapped) line number of the text displayed */
1515 if (maintainingAbsTopLineNum(textD) && (nInserted != 0 || nDeleted != 0)) {
1516 if (pos + nDeleted < oldFirstChar)
1517 textD->absTopLineNum += BufCountLines(buf, pos, pos + nInserted) -
1518 countLines(deletedText);
1519 else if (pos < oldFirstChar)
1520 resetAbsLineNum(textD);
1523 /* Update the line count for the whole buffer */
1524 textD->nBufferLines += linesInserted - linesDeleted;
1526 /* Update the scroll bar ranges (and value if the value changed). Note
1527 that updating the horizontal scroll bar range requires scanning the
1528 entire displayed text, however, it doesn't seem to hurt performance
1529 much. Note also, that the horizontal scroll bar update routine is
1530 allowed to re-adjust horizOffset if there is blank space to the right
1531 of all lines of text. */
1532 updateVScrollBarRange(textD);
1533 scrolled |= updateHScrollBarRange(textD);
1535 /* Update the cursor position */
1536 if (textD->cursorToHint != NO_HINT) {
1537 textD->cursorPos = textD->cursorToHint;
1538 textD->cursorToHint = NO_HINT;
1539 } else if (textD->cursorPos > pos) {
1540 if (textD->cursorPos < pos + nDeleted)
1541 textD->cursorPos = pos;
1542 else
1543 textD->cursorPos += nInserted - nDeleted;
1546 /* If the changes caused scrolling, re-paint everything and we're done. */
1547 if (scrolled) {
1548 blankCursorProtrusions(textD);
1549 TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left,
1550 textD->height);
1551 if (textD->styleBuffer) {/* See comments in extendRangeForStyleMods */
1552 textD->styleBuffer->primary.selected = False;
1553 textD->styleBuffer->primary.zeroWidth = False;
1555 return;
1558 /* If the changes didn't cause scrolling, decide the range of characters
1559 that need to be re-painted. Also if the cursor position moved, be
1560 sure that the redisplay range covers the old cursor position so the
1561 old cursor gets erased, and erase the bits of the cursor which extend
1562 beyond the left and right edges of the text. */
1563 startDispPos = textD->continuousWrap ? wrapModStart : pos;
1564 if (origCursorPos == startDispPos && textD->cursorPos != startDispPos)
1565 startDispPos = min(startDispPos, origCursorPos-1);
1566 if (linesInserted == linesDeleted) {
1567 if (nInserted == 0 && nDeleted == 0)
1568 endDispPos = pos + nRestyled;
1569 else {
1570 endDispPos = textD->continuousWrap ? wrapModEnd :
1571 BufEndOfLine(buf, pos + nInserted) + 1;
1572 if (origCursorPos >= startDispPos &&
1573 (origCursorPos <= endDispPos || endDispPos == buf->length))
1574 blankCursorProtrusions(textD);
1576 /* If more than one line is inserted/deleted, a line break may have
1577 been inserted or removed in between, and the line numbers may
1578 have changed. If only one line is altered, line numbers cannot
1579 be affected (the insertion or removal of a line break always
1580 results in at least two lines being redrawn). */
1581 if (linesInserted > 1) redrawLineNumbers(textD, False);
1582 } else { /* linesInserted != linesDeleted */
1583 endDispPos = textD->lastChar + 1;
1584 if (origCursorPos >= pos)
1585 blankCursorProtrusions(textD);
1586 redrawLineNumbers(textD, False);
1589 /* If there is a style buffer, check if the modification caused additional
1590 changes that need to be redisplayed. (Redisplaying separately would
1591 cause double-redraw on almost every modification involving styled
1592 text). Extend the redraw range to incorporate style changes */
1593 if (textD->styleBuffer)
1594 extendRangeForStyleMods(textD, &startDispPos, &endDispPos);
1596 /* Redisplay computed range */
1597 TextDRedisplayRange(textD, startDispPos, endDispPos);
1601 ** In continuous wrap mode, internal line numbers are calculated after
1602 ** wrapping. A separate non-wrapped line count is maintained when line
1603 ** numbering is turned on. There is some performance cost to maintaining this
1604 ** line count, so normally absolute line numbers are not tracked if line
1605 ** numbering is off. This routine allows callers to specify that they still
1606 ** want this line count maintained (for use via TextDPosToLineAndCol).
1607 ** More specifically, this allows the line number reported in the statistics
1608 ** line to be calibrated in absolute lines, rather than post-wrapped lines.
1610 void TextDMaintainAbsLineNum(textDisp *textD, int state)
1612 textD->needAbsTopLineNum = state;
1613 resetAbsLineNum(textD);
1617 ** Returns the absolute (non-wrapped) line number of the first line displayed.
1618 ** Returns 0 if the absolute top line number is not being maintained.
1620 static int getAbsTopLineNum(textDisp *textD)
1622 if (!textD->continuousWrap)
1623 return textD->topLineNum;
1624 if (maintainingAbsTopLineNum(textD))
1625 return textD->absTopLineNum;
1626 return 0;
1630 ** Re-calculate absolute top line number for a change in scroll position.
1632 static void offsetAbsLineNum(textDisp *textD, int oldFirstChar)
1634 if (maintainingAbsTopLineNum(textD)) {
1635 if (textD->firstChar < oldFirstChar)
1636 textD->absTopLineNum -= BufCountLines(textD->buffer,
1637 textD->firstChar, oldFirstChar);
1638 else
1639 textD->absTopLineNum += BufCountLines(textD->buffer,
1640 oldFirstChar, textD->firstChar);
1645 ** Return true if a separate absolute top line number is being maintained
1646 ** (for displaying line numbers or showing in the statistics line).
1648 static int maintainingAbsTopLineNum(textDisp *textD)
1650 return textD->continuousWrap &&
1651 (textD->lineNumWidth != 0 || textD->needAbsTopLineNum);
1655 ** Count lines from the beginning of the buffer to reestablish the
1656 ** absolute (non-wrapped) top line number. If mode is not continuous wrap,
1657 ** or the number is not being maintained, does nothing.
1659 static void resetAbsLineNum(textDisp *textD)
1661 textD->absTopLineNum = 1;
1662 offsetAbsLineNum(textD, 0);
1666 ** Find the line number of position "pos" relative to the first line of
1667 ** displayed text. Returns False if the line is not displayed.
1669 static int posToVisibleLineNum(textDisp *textD, int pos, int *lineNum)
1671 int i;
1673 if (pos < textD->firstChar)
1674 return False;
1675 if (pos > textD->lastChar) {
1676 if (emptyLinesVisible(textD)) {
1677 if (textD->lastChar < textD->buffer->length) {
1678 if (!posToVisibleLineNum(textD, textD->lastChar, lineNum)) {
1679 fprintf(stderr, "Consistency check ptvl failed\n");
1680 return False;
1682 return ++(*lineNum) <= textD->nVisibleLines-1;
1683 } else {
1684 posToVisibleLineNum(textD, max(textD->lastChar-1, 0), lineNum);
1685 return True;
1688 return False;
1691 for (i=textD->nVisibleLines-1; i>=0; i--) {
1692 if (textD->lineStarts[i] != -1 && pos >= textD->lineStarts[i]) {
1693 *lineNum = i;
1694 return True;
1697 return False; /* probably never be reached */
1701 ** Redisplay the text on a single line represented by "visLineNum" (the
1702 ** number of lines down from the top of the display), limited by
1703 ** "leftClip" and "rightClip" window coordinates and "leftCharIndex" and
1704 ** "rightCharIndex" character positions (not including the character at
1705 ** position "rightCharIndex").
1707 ** The cursor is also drawn if it appears on the line.
1709 static void redisplayLine(textDisp *textD, int visLineNum, int leftClip,
1710 int rightClip, int leftCharIndex, int rightCharIndex)
1712 textBuffer *buf = textD->buffer;
1713 int i, x, y, startX, charIndex, lineStartPos, lineLen, fontHeight;
1714 int stdCharWidth, charWidth, startIndex, charStyle, style;
1715 int charLen, outStartIndex, outIndex, cursorX = 0, hasCursor = False;
1716 int dispIndexOffset, cursorPos = textD->cursorPos, y_orig;
1717 char expandedChar[MAX_EXP_CHAR_LEN], outStr[MAX_DISP_LINE_LEN];
1718 char *lineStr, *outPtr;
1719 char baseChar;
1721 /* If line is not displayed, skip it */
1722 if (visLineNum < 0 || visLineNum >= textD->nVisibleLines)
1723 return;
1725 /* Shrink the clipping range to the active display area */
1726 leftClip = max(textD->left, leftClip);
1727 rightClip = min(rightClip, textD->left + textD->width);
1729 if (leftClip > rightClip) {
1730 return;
1733 /* Calculate y coordinate of the string to draw */
1734 fontHeight = textD->ascent + textD->descent;
1735 y = textD->top + visLineNum * fontHeight;
1737 /* Get the text, length, and buffer position of the line to display */
1738 lineStartPos = textD->lineStarts[visLineNum];
1739 if (lineStartPos == -1) {
1740 lineLen = 0;
1741 lineStr = NULL;
1742 } else {
1743 lineLen = visLineLength(textD, visLineNum);
1744 lineStr = BufGetRange(buf, lineStartPos, lineStartPos + lineLen);
1747 /* Space beyond the end of the line is still counted in units of characters
1748 of a standardized character width (this is done mostly because style
1749 changes based on character position can still occur in this region due
1750 to rectangular selections). stdCharWidth must be non-zero to prevent a
1751 potential infinite loop if x does not advance */
1752 stdCharWidth = textD->fontStruct->max_bounds.width;
1753 if (stdCharWidth <= 0) {
1754 fprintf(stderr, "Internal Error, bad font measurement\n");
1755 XtFree(lineStr);
1756 return;
1759 /* Rectangular selections are based on "real" line starts (after a newline
1760 or start of buffer). Calculate the difference between the last newline
1761 position and the line start we're using. Since scanning back to find a
1762 newline is expensive, only do so if there's actually a rectangular
1763 selection which needs it */
1764 if (textD->continuousWrap && (rangeTouchesRectSel(&buf->primary,
1765 lineStartPos, lineStartPos + lineLen) || rangeTouchesRectSel(
1766 &buf->secondary, lineStartPos, lineStartPos + lineLen) ||
1767 rangeTouchesRectSel(&buf->highlight, lineStartPos,
1768 lineStartPos + lineLen))) {
1769 dispIndexOffset = BufCountDispChars(buf,
1770 BufStartOfLine(buf, lineStartPos), lineStartPos);
1771 } else
1772 dispIndexOffset = 0;
1774 /* Step through character positions from the beginning of the line (even if
1775 that's off the left edge of the displayed area) to find the first
1776 character position that's not clipped, and the x coordinate for drawing
1777 that character */
1778 x = textD->left - textD->horizOffset;
1779 outIndex = 0;
1780 for(charIndex=0; ; charIndex++) {
1781 baseChar = '\0';
1782 charLen = charIndex >= lineLen ? 1 :
1783 BufExpandCharacter(baseChar = lineStr[charIndex], outIndex,
1784 expandedChar, buf->tabDist, buf->nullSubsChar);
1785 style = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1786 outIndex + dispIndexOffset, baseChar);
1787 charWidth = charIndex >= lineLen ? stdCharWidth :
1788 stringWidth(textD, expandedChar, charLen, style);
1789 if (x + charWidth >= leftClip && charIndex >= leftCharIndex) {
1790 startIndex = charIndex;
1791 outStartIndex = outIndex;
1792 startX = x;
1793 break;
1795 x += charWidth;
1796 outIndex += charLen;
1799 /* Scan character positions from the beginning of the clipping range, and
1800 draw parts whenever the style changes (also note if the cursor is on
1801 this line, and where it should be drawn to take advantage of the x
1802 position which we've gone to so much trouble to calculate) */
1803 outPtr = outStr;
1804 outIndex = outStartIndex;
1805 x = startX;
1806 for(charIndex=startIndex; charIndex<rightCharIndex; charIndex++) {
1807 if (lineStartPos+charIndex == cursorPos) {
1808 if (charIndex < lineLen || (charIndex == lineLen &&
1809 cursorPos >= buf->length)) {
1810 hasCursor = True;
1811 cursorX = x - 1;
1812 } else if (charIndex == lineLen) {
1813 if (wrapUsesCharacter(textD, cursorPos)) {
1814 hasCursor = True;
1815 cursorX = x - 1;
1819 baseChar = '\0';
1820 charLen = charIndex >= lineLen ? 1 :
1821 BufExpandCharacter(baseChar = lineStr[charIndex], outIndex,
1822 expandedChar, buf->tabDist, buf->nullSubsChar);
1823 charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1824 outIndex + dispIndexOffset, baseChar);
1825 for (i=0; i<charLen; i++) {
1826 if (i != 0 && charIndex < lineLen && lineStr[charIndex] == '\t')
1827 charStyle = styleOfPos(textD, lineStartPos, lineLen,
1828 charIndex, outIndex + dispIndexOffset, '\t');
1829 if (charStyle != style) {
1830 drawString(textD, style, startX, y, x, outStr, outPtr - outStr);
1831 outPtr = outStr;
1832 startX = x;
1833 style = charStyle;
1835 if (charIndex < lineLen) {
1836 *outPtr = expandedChar[i];
1837 charWidth = stringWidth(textD, &expandedChar[i], 1, charStyle);
1838 } else
1839 charWidth = stdCharWidth;
1840 outPtr++;
1841 x += charWidth;
1842 outIndex++;
1844 if (outPtr-outStr+MAX_EXP_CHAR_LEN>=MAX_DISP_LINE_LEN || x>=rightClip)
1845 break;
1848 /* Draw the remaining style segment */
1849 drawString(textD, style, startX, y, x, outStr, outPtr - outStr);
1851 /* Draw the cursor if part of it appeared on the redisplayed part of
1852 this line. Also check for the cases which are not caught as the
1853 line is scanned above: when the cursor appears at the very end
1854 of the redisplayed section. */
1855 y_orig = textD->cursorY;
1856 if (textD->cursorOn) {
1857 if (hasCursor)
1858 drawCursor(textD, cursorX, y);
1859 else if (charIndex<lineLen && (lineStartPos+charIndex+1 == cursorPos)
1860 && x == rightClip) {
1861 if (cursorPos >= buf->length)
1862 drawCursor(textD, x - 1, y);
1863 else {
1864 if (wrapUsesCharacter(textD, cursorPos))
1865 drawCursor(textD, x - 1, y);
1870 /* If the y position of the cursor has changed, redraw the calltip */
1871 if (hasCursor && (y_orig != textD->cursorY || y_orig != y))
1872 TextDRedrawCalltip(textD, 0);
1874 if (lineStr != NULL)
1875 XtFree(lineStr);
1879 ** Draw a string or blank area according to parameter "style", using the
1880 ** appropriate colors and drawing method for that style, with top left
1881 ** corner at x, y. If style says to draw text, use "string" as source of
1882 ** characters, and draw "nChars", if style is FILL, erase
1883 ** rectangle where text would have drawn from x to toX and from y to
1884 ** the maximum y extent of the current font(s).
1886 static void drawString(textDisp *textD, int style, int x, int y, int toX,
1887 char *string, int nChars)
1889 GC gc, bgGC;
1890 XGCValues gcValues;
1891 XFontStruct *fs = textD->fontStruct;
1892 Pixel bground = textD->bgPixel;
1893 Pixel fground = textD->fgPixel;
1894 int underlineStyle = FALSE;
1896 /* Don't draw if widget isn't realized */
1897 if (XtWindow(textD->w) == 0)
1898 return;
1900 /* select a GC */
1901 if (style & (STYLE_LOOKUP_MASK | BACKLIGHT_MASK | RANGESET_MASK)) {
1902 gc = bgGC = textD->styleGC;
1904 else if (style & HIGHLIGHT_MASK) {
1905 gc = textD->highlightGC;
1906 bgGC = textD->highlightBGGC;
1908 else if (style & PRIMARY_MASK) {
1909 gc = textD->selectGC;
1910 bgGC = textD->selectBGGC;
1912 else {
1913 gc = bgGC = textD->gc;
1916 if (gc == textD->styleGC) {
1917 /* we have work to do */
1918 styleTableEntry *styleRec;
1919 /* Set font, color, and gc depending on style. For normal text, GCs
1920 for normal drawing, or drawing within a selection or highlight are
1921 pre-allocated and pre-configured. For syntax highlighting, GCs are
1922 configured here, on the fly. */
1923 if (style & STYLE_LOOKUP_MASK) {
1924 styleRec = &textD->styleTable[(style & STYLE_LOOKUP_MASK) - ASCII_A];
1925 underlineStyle = styleRec->underline;
1926 fs = styleRec->font;
1927 gcValues.font = fs->fid;
1928 fground = styleRec->color;
1929 /* here you could pick up specific select and highlight fground */
1931 else {
1932 styleRec = NULL;
1933 gcValues.font = fs->fid;
1934 fground = textD->fgPixel;
1936 /* Background color priority order is:
1937 1 Primary(Selection), 2 Highlight(Parens),
1938 3 Rangeset, 4 SyntaxHighlightStyle,
1939 5 Backlight (if NOT fill), 6 DefaultBackground */
1940 bground =
1941 style & PRIMARY_MASK ? textD->selectBGPixel :
1942 style & HIGHLIGHT_MASK ? textD->highlightBGPixel :
1943 style & RANGESET_MASK ?
1944 getRangesetColor(textD,
1945 (style&RANGESET_MASK)>>RANGESET_SHIFT,
1946 bground) :
1947 styleRec && styleRec->bgColorName ? styleRec->bgColor :
1948 (style & BACKLIGHT_MASK) && !(style & FILL_MASK) ?
1949 textD->bgClassPixel[(style>>BACKLIGHT_SHIFT) & 0xff] :
1950 textD->bgPixel;
1951 if (fground == bground) /* B&W kludge */
1952 fground = textD->bgPixel;
1953 /* set up gc for clearing using the foreground color entry */
1954 gcValues.foreground = gcValues.background = bground;
1955 XChangeGC(XtDisplay(textD->w), gc,
1956 GCFont | GCForeground | GCBackground, &gcValues);
1959 /* Draw blank area rather than text, if that was the request */
1960 if (style & FILL_MASK) {
1961 /* wipes out to right hand edge of widget */
1962 if (toX >= textD->left)
1963 clearRect(textD, bgGC, max(x, textD->left), y,
1964 toX - max(x, textD->left), textD->ascent + textD->descent);
1965 return;
1968 /* If any space around the character remains unfilled (due to use of
1969 different sized fonts for highlighting), fill in above or below
1970 to erase previously drawn characters */
1971 if (fs->ascent < textD->ascent)
1972 clearRect(textD, bgGC, x, y, toX - x, textD->ascent - fs->ascent);
1973 if (fs->descent < textD->descent)
1974 clearRect(textD, bgGC, x, y + textD->ascent + fs->descent, toX - x,
1975 textD->descent - fs->descent);
1977 /* set up gc for writing text (set foreground properly) */
1978 if (bgGC == textD->styleGC) {
1979 gcValues.foreground = fground;
1980 XChangeGC(XtDisplay(textD->w), gc, GCForeground, &gcValues);
1983 /* Draw the string using gc and font set above */
1984 XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w), gc, x,
1985 y + textD->ascent, string, nChars);
1987 /* Underline if style is secondary selection */
1988 if (style & SECONDARY_MASK || underlineStyle)
1990 /* restore foreground in GC (was set to background by clearRect()) */
1991 gcValues.foreground = fground;
1992 XChangeGC(XtDisplay(textD->w), gc,
1993 GCForeground, &gcValues);
1994 /* draw underline */
1995 XDrawLine(XtDisplay(textD->w), XtWindow(textD->w), gc, x,
1996 y + textD->ascent, toX - 1, y + textD->ascent);
2001 ** Clear a rectangle with the appropriate background color for "style"
2003 static void clearRect(textDisp *textD, GC gc, int x, int y,
2004 int width, int height)
2006 /* A width of zero means "clear to end of window" to XClearArea */
2007 if (width == 0)
2008 return;
2010 if (gc == textD->gc) {
2011 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), x, y,
2012 width, height, False);
2014 else {
2015 XFillRectangle(XtDisplay(textD->w), XtWindow(textD->w),
2016 gc, x, y, width, height);
2021 ** Draw a cursor with top center at x, y.
2023 static void drawCursor(textDisp *textD, int x, int y)
2025 XSegment segs[5];
2026 int left, right, cursorWidth, midY;
2027 int fontWidth = textD->fontStruct->min_bounds.width, nSegs = 0;
2028 int fontHeight = textD->ascent + textD->descent;
2029 int bot = y + fontHeight - 1;
2031 if (XtWindow(textD->w) == 0 || x < textD->left-1 ||
2032 x > textD->left + textD->width)
2033 return;
2035 /* For cursors other than the block, make them around 2/3 of a character
2036 width, rounded to an even number of pixels so that X will draw an
2037 odd number centered on the stem at x. */
2038 cursorWidth = (fontWidth/3) * 2;
2039 left = x - cursorWidth/2;
2040 right = left + cursorWidth;
2042 /* Create segments and draw cursor */
2043 if (textD->cursorStyle == CARET_CURSOR) {
2044 midY = bot - fontHeight/5;
2045 segs[0].x1 = left; segs[0].y1 = bot; segs[0].x2 = x; segs[0].y2 = midY;
2046 segs[1].x1 = x; segs[1].y1 = midY; segs[1].x2 = right; segs[1].y2 = bot;
2047 segs[2].x1 = left; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2=midY-1;
2048 segs[3].x1 = x; segs[3].y1=midY-1; segs[3].x2 = right; segs[3].y2 = bot;
2049 nSegs = 4;
2050 } else if (textD->cursorStyle == NORMAL_CURSOR) {
2051 segs[0].x1 = left; segs[0].y1 = y; segs[0].x2 = right; segs[0].y2 = y;
2052 segs[1].x1 = x; segs[1].y1 = y; segs[1].x2 = x; segs[1].y2 = bot;
2053 segs[2].x1 = left; segs[2].y1 = bot; segs[2].x2 = right; segs[2].y2=bot;
2054 nSegs = 3;
2055 } else if (textD->cursorStyle == HEAVY_CURSOR) {
2056 segs[0].x1 = x-1; segs[0].y1 = y; segs[0].x2 = x-1; segs[0].y2 = bot;
2057 segs[1].x1 = x; segs[1].y1 = y; segs[1].x2 = x; segs[1].y2 = bot;
2058 segs[2].x1 = x+1; segs[2].y1 = y; segs[2].x2 = x+1; segs[2].y2 = bot;
2059 segs[3].x1 = left; segs[3].y1 = y; segs[3].x2 = right; segs[3].y2 = y;
2060 segs[4].x1 = left; segs[4].y1 = bot; segs[4].x2 = right; segs[4].y2=bot;
2061 nSegs = 5;
2062 } else if (textD->cursorStyle == DIM_CURSOR) {
2063 midY = y + fontHeight/2;
2064 segs[0].x1 = x; segs[0].y1 = y; segs[0].x2 = x; segs[0].y2 = y;
2065 segs[1].x1 = x; segs[1].y1 = midY; segs[1].x2 = x; segs[1].y2 = midY;
2066 segs[2].x1 = x; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2 = bot;
2067 nSegs = 3;
2068 } else if (textD->cursorStyle == BLOCK_CURSOR) {
2069 right = x + fontWidth;
2070 segs[0].x1 = x; segs[0].y1 = y; segs[0].x2 = right; segs[0].y2 = y;
2071 segs[1].x1 = right; segs[1].y1 = y; segs[1].x2 = right; segs[1].y2=bot;
2072 segs[2].x1 = right; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2 = bot;
2073 segs[3].x1 = x; segs[3].y1 = bot; segs[3].x2 = x; segs[3].y2 = y;
2074 nSegs = 4;
2076 XDrawSegments(XtDisplay(textD->w), XtWindow(textD->w),
2077 textD->cursorFGGC, segs, nSegs);
2079 /* Save the last position drawn */
2080 textD->cursorX = x;
2081 textD->cursorY = y;
2085 ** Determine the drawing method to use to draw a specific character from "buf".
2086 ** "lineStartPos" gives the character index where the line begins, "lineIndex",
2087 ** the number of characters past the beginning of the line, and "dispIndex",
2088 ** the number of displayed characters past the beginning of the line. Passing
2089 ** lineStartPos of -1 returns the drawing style for "no text".
2091 ** Why not just: styleOfPos(textD, pos)? Because style applies to blank areas
2092 ** of the window beyond the text boundaries, and because this routine must also
2093 ** decide whether a position is inside of a rectangular selection, and do so
2094 ** efficiently, without re-counting character positions from the start of the
2095 ** line.
2097 ** Note that style is a somewhat incorrect name, drawing method would
2098 ** be more appropriate.
2100 static int styleOfPos(textDisp *textD, int lineStartPos,
2101 int lineLen, int lineIndex, int dispIndex, int thisChar)
2103 textBuffer *buf = textD->buffer;
2104 textBuffer *styleBuf = textD->styleBuffer;
2105 int pos, style = 0;
2107 if (lineStartPos == -1 || buf == NULL)
2108 return FILL_MASK;
2110 pos = lineStartPos + min(lineIndex, lineLen);
2112 if (lineIndex >= lineLen)
2113 style = FILL_MASK;
2114 else if (styleBuf != NULL) {
2115 style = (unsigned char)BufGetCharacter(styleBuf, pos);
2116 if (style == textD->unfinishedStyle) {
2117 /* encountered "unfinished" style, trigger parsing */
2118 (textD->unfinishedHighlightCB)(textD, pos, textD->highlightCBArg);
2119 style = (unsigned char)BufGetCharacter(styleBuf, pos);
2122 if (inSelection(&buf->primary, pos, lineStartPos, dispIndex))
2123 style |= PRIMARY_MASK;
2124 if (inSelection(&buf->highlight, pos, lineStartPos, dispIndex))
2125 style |= HIGHLIGHT_MASK;
2126 if (inSelection(&buf->secondary, pos, lineStartPos, dispIndex))
2127 style |= SECONDARY_MASK;
2128 /* store in the RANGESET_MASK portion of style the rangeset index for pos */
2129 if (buf->rangesetTable) {
2130 int rangesetIndex = RangesetIndex1ofPos(buf->rangesetTable, pos, True);
2131 style |= ((rangesetIndex << RANGESET_SHIFT) & RANGESET_MASK);
2133 /* store in the BACKLIGHT_MASK portion of style the background color class
2134 of the character thisChar */
2135 if (textD->bgClass)
2137 style |= (textD->bgClass[(unsigned char)thisChar]<<BACKLIGHT_SHIFT);
2139 return style;
2143 ** Find the width of a string in the font of a particular style
2145 static int stringWidth(textDisp *textD, char *string, int length, int style)
2147 XFontStruct *fs;
2149 if (style & STYLE_LOOKUP_MASK)
2150 fs = textD->styleTable[(style & STYLE_LOOKUP_MASK) - ASCII_A].font;
2151 else
2152 fs = textD->fontStruct;
2153 return XTextWidth(fs, string, length);
2157 ** Return true if position "pos" with indentation "dispIndex" is in
2158 ** selection "sel"
2160 static int inSelection(selection *sel, int pos, int lineStartPos, int dispIndex)
2162 return sel->selected &&
2163 ((!sel->rectangular &&
2164 pos >= sel->start && pos < sel->end) ||
2165 (sel->rectangular &&
2166 pos >= sel->start && lineStartPos <= sel->end &&
2167 dispIndex >= sel->rectStart && dispIndex < sel->rectEnd));
2171 ** Translate window coordinates to the nearest (insert cursor or character
2172 ** cell) text position. The parameter posType specifies how to interpret the
2173 ** position: CURSOR_POS means translate the coordinates to the nearest cursor
2174 ** position, and CHARACTER_POS means return the position of the character
2175 ** closest to (x, y).
2177 static int xyToPos(textDisp *textD, int x, int y, int posType)
2179 int charIndex, lineStart, lineLen, fontHeight;
2180 int charWidth, charLen, charStyle, visLineNum, xStep, outIndex;
2181 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
2183 /* Find the visible line number corresponding to the y coordinate */
2184 fontHeight = textD->ascent + textD->descent;
2185 visLineNum = (y - textD->top) / fontHeight;
2186 if (visLineNum < 0)
2187 return textD->firstChar;
2188 if (visLineNum >= textD->nVisibleLines)
2189 visLineNum = textD->nVisibleLines - 1;
2191 /* Find the position at the start of the line */
2192 lineStart = textD->lineStarts[visLineNum];
2194 /* If the line start was empty, return the last position in the buffer */
2195 if (lineStart == -1)
2196 return textD->buffer->length;
2198 /* Get the line text and its length */
2199 lineLen = visLineLength(textD, visLineNum);
2200 lineStr = BufGetRange(textD->buffer, lineStart, lineStart + lineLen);
2202 /* Step through character positions from the beginning of the line
2203 to find the character position corresponding to the x coordinate */
2204 xStep = textD->left - textD->horizOffset;
2205 outIndex = 0;
2206 for(charIndex=0; charIndex<lineLen; charIndex++) {
2207 charLen = BufExpandCharacter(lineStr[charIndex], outIndex, expandedChar,
2208 textD->buffer->tabDist, textD->buffer->nullSubsChar);
2209 charStyle = styleOfPos(textD, lineStart, lineLen, charIndex, outIndex,
2210 lineStr[charIndex]);
2211 charWidth = stringWidth(textD, expandedChar, charLen, charStyle);
2212 if (x < xStep + (posType == CURSOR_POS ? charWidth/2 : charWidth)) {
2213 XtFree(lineStr);
2214 return lineStart + charIndex;
2216 xStep += charWidth;
2217 outIndex += charLen;
2220 /* If the x position was beyond the end of the line, return the position
2221 of the newline at the end of the line */
2222 XtFree(lineStr);
2223 return lineStart + lineLen;
2227 ** Translate window coordinates to the nearest row and column number for
2228 ** positioning the cursor. This, of course, makes no sense when the font is
2229 ** proportional, since there are no absolute columns. The parameter posType
2230 ** specifies how to interpret the position: CURSOR_POS means translate the
2231 ** coordinates to the nearest position between characters, and CHARACTER_POS
2232 ** means translate the position to the nearest character cell.
2234 static void xyToUnconstrainedPos(textDisp *textD, int x, int y, int *row,
2235 int *column, int posType)
2237 int fontHeight = textD->ascent + textD->descent;
2238 int fontWidth = textD->fontStruct->max_bounds.width;
2240 /* Find the visible line number corresponding to the y coordinate */
2241 *row = (y - textD->top) / fontHeight;
2242 if (*row < 0) *row = 0;
2243 if (*row >= textD->nVisibleLines) *row = textD->nVisibleLines - 1;
2244 *column = ((x-textD->left) + textD->horizOffset +
2245 (posType == CURSOR_POS ? fontWidth/2 : 0)) / fontWidth;
2246 if (*column < 0) *column = 0;
2250 ** Offset the line starts array, topLineNum, firstChar and lastChar, for a new
2251 ** vertical scroll position given by newTopLineNum. If any currently displayed
2252 ** lines will still be visible, salvage the line starts values, otherwise,
2253 ** count lines from the nearest known line start (start or end of buffer, or
2254 ** the closest value in the lineStarts array)
2256 static void offsetLineStarts(textDisp *textD, int newTopLineNum)
2258 int oldTopLineNum = textD->topLineNum;
2259 int oldFirstChar = textD->firstChar;
2260 int lineDelta = newTopLineNum - oldTopLineNum;
2261 int nVisLines = textD->nVisibleLines;
2262 int *lineStarts = textD->lineStarts;
2263 int i, lastLineNum;
2264 textBuffer *buf = textD->buffer;
2266 /* If there was no offset, nothing needs to be changed */
2267 if (lineDelta == 0)
2268 return;
2270 /* { int i;
2271 printf("Scroll, lineDelta %d\n", lineDelta);
2272 printf("lineStarts Before: ");
2273 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2274 printf("\n");
2275 } */
2277 /* Find the new value for firstChar by counting lines from the nearest
2278 known line start (start or end of buffer, or the closest value in the
2279 lineStarts array) */
2280 lastLineNum = oldTopLineNum + nVisLines - 1;
2281 if (newTopLineNum < oldTopLineNum && newTopLineNum < -lineDelta) {
2282 textD->firstChar = TextDCountForwardNLines(textD, 0, newTopLineNum-1,
2283 True);
2284 /* printf("counting forward %d lines from start\n", newTopLineNum-1);*/
2285 } else if (newTopLineNum < oldTopLineNum) {
2286 textD->firstChar = TextDCountBackwardNLines(textD, textD->firstChar,
2287 -lineDelta);
2288 /* printf("counting backward %d lines from firstChar\n", -lineDelta);*/
2289 } else if (newTopLineNum < lastLineNum) {
2290 textD->firstChar = lineStarts[newTopLineNum - oldTopLineNum];
2291 /* printf("taking new start from lineStarts[%d]\n",
2292 newTopLineNum - oldTopLineNum); */
2293 } else if (newTopLineNum-lastLineNum < textD->nBufferLines-newTopLineNum) {
2294 textD->firstChar = TextDCountForwardNLines(textD,
2295 lineStarts[nVisLines-1], newTopLineNum - lastLineNum, True);
2296 /* printf("counting forward %d lines from start of last line\n",
2297 newTopLineNum - lastLineNum); */
2298 } else {
2299 textD->firstChar = TextDCountBackwardNLines(textD, buf->length,
2300 textD->nBufferLines - newTopLineNum + 1);
2301 /* printf("counting backward %d lines from end\n",
2302 textD->nBufferLines - newTopLineNum + 1); */
2305 /* Fill in the line starts array */
2306 if (lineDelta < 0 && -lineDelta < nVisLines) {
2307 for (i=nVisLines-1; i >= -lineDelta; i--)
2308 lineStarts[i] = lineStarts[i+lineDelta];
2309 calcLineStarts(textD, 0, -lineDelta);
2310 } else if (lineDelta > 0 && lineDelta < nVisLines) {
2311 for (i=0; i<nVisLines-lineDelta; i++)
2312 lineStarts[i] = lineStarts[i+lineDelta];
2313 calcLineStarts(textD, nVisLines-lineDelta, nVisLines-1);
2314 } else
2315 calcLineStarts(textD, 0, nVisLines);
2317 /* Set lastChar and topLineNum */
2318 calcLastChar(textD);
2319 textD->topLineNum = newTopLineNum;
2321 /* If we're numbering lines or being asked to maintain an absolute line
2322 number, re-calculate the absolute line number */
2323 offsetAbsLineNum(textD, oldFirstChar);
2325 /* { int i;
2326 printf("lineStarts After: ");
2327 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2328 printf("\n");
2329 } */
2333 ** Update the line starts array, topLineNum, firstChar and lastChar for text
2334 ** display "textD" after a modification to the text buffer, given by the
2335 ** position where the change began "pos", and the nmubers of characters
2336 ** and lines inserted and deleted.
2338 static void updateLineStarts(textDisp *textD, int pos, int charsInserted,
2339 int charsDeleted, int linesInserted, int linesDeleted, int *scrolled)
2341 int *lineStarts = textD->lineStarts;
2342 int i, lineOfPos, lineOfEnd, nVisLines = textD->nVisibleLines;
2343 int charDelta = charsInserted - charsDeleted;
2344 int lineDelta = linesInserted - linesDeleted;
2346 /* { int i;
2347 printf("linesDeleted %d, linesInserted %d, charsInserted %d, charsDeleted %d\n",
2348 linesDeleted, linesInserted, charsInserted, charsDeleted);
2349 printf("lineStarts Before: ");
2350 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2351 printf("\n");
2352 } */
2353 /* If all of the changes were before the displayed text, the display
2354 doesn't change, just update the top line num and offset the line
2355 start entries and first and last characters */
2356 if (pos + charsDeleted < textD->firstChar) {
2357 textD->topLineNum += lineDelta;
2358 for (i=0; i<nVisLines && lineStarts[i] != -1; i++)
2359 lineStarts[i] += charDelta;
2360 /* { int i;
2361 printf("lineStarts after delete doesn't touch: ");
2362 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2363 printf("\n");
2364 } */
2365 textD->firstChar += charDelta;
2366 textD->lastChar += charDelta;
2367 *scrolled = False;
2368 return;
2371 /* The change began before the beginning of the displayed text, but
2372 part or all of the displayed text was deleted */
2373 if (pos < textD->firstChar) {
2374 /* If some text remains in the window, anchor on that */
2375 if (posToVisibleLineNum(textD, pos + charsDeleted, &lineOfEnd) &&
2376 ++lineOfEnd < nVisLines && lineStarts[lineOfEnd] != -1) {
2377 textD->topLineNum = max(1, textD->topLineNum + lineDelta);
2378 textD->firstChar = TextDCountBackwardNLines(textD,
2379 lineStarts[lineOfEnd] + charDelta, lineOfEnd);
2380 /* Otherwise anchor on original line number and recount everything */
2381 } else {
2382 if (textD->topLineNum > textD->nBufferLines + lineDelta) {
2383 textD->topLineNum = 1;
2384 textD->firstChar = 0;
2385 } else
2386 textD->firstChar = TextDCountForwardNLines(textD, 0,
2387 textD->topLineNum - 1, True);
2389 calcLineStarts(textD, 0, nVisLines-1);
2390 /* { int i;
2391 printf("lineStarts after delete encroaches: ");
2392 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2393 printf("\n");
2394 } */
2395 /* calculate lastChar by finding the end of the last displayed line */
2396 calcLastChar(textD);
2397 *scrolled = True;
2398 return;
2401 /* If the change was in the middle of the displayed text (it usually is),
2402 salvage as much of the line starts array as possible by moving and
2403 offsetting the entries after the changed area, and re-counting the
2404 added lines or the lines beyond the salvaged part of the line starts
2405 array */
2406 if (pos <= textD->lastChar) {
2407 /* find line on which the change began */
2408 posToVisibleLineNum(textD, pos, &lineOfPos);
2409 /* salvage line starts after the changed area */
2410 if (lineDelta == 0) {
2411 for (i=lineOfPos+1; i<nVisLines && lineStarts[i]!= -1; i++)
2412 lineStarts[i] += charDelta;
2413 } else if (lineDelta > 0) {
2414 for (i=nVisLines-1; i>=lineOfPos+lineDelta+1; i--)
2415 lineStarts[i] = lineStarts[i-lineDelta] +
2416 (lineStarts[i-lineDelta] == -1 ? 0 : charDelta);
2417 } else /* (lineDelta < 0) */ {
2418 for (i=max(0,lineOfPos+1); i<nVisLines+lineDelta; i++)
2419 lineStarts[i] = lineStarts[i-lineDelta] +
2420 (lineStarts[i-lineDelta] == -1 ? 0 : charDelta);
2422 /* { int i;
2423 printf("lineStarts after salvage: ");
2424 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2425 printf("\n");
2426 } */
2427 /* fill in the missing line starts */
2428 if (linesInserted >= 0)
2429 calcLineStarts(textD, lineOfPos + 1, lineOfPos + linesInserted);
2430 if (lineDelta < 0)
2431 calcLineStarts(textD, nVisLines+lineDelta, nVisLines);
2432 /* { int i;
2433 printf("lineStarts after recalculation: ");
2434 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2435 printf("\n");
2436 } */
2437 /* calculate lastChar by finding the end of the last displayed line */
2438 calcLastChar(textD);
2439 *scrolled = False;
2440 return;
2443 /* Change was past the end of the displayed text, but displayable by virtue
2444 of being an insert at the end of the buffer into visible blank lines */
2445 if (emptyLinesVisible(textD)) {
2446 posToVisibleLineNum(textD, pos, &lineOfPos);
2447 calcLineStarts(textD, lineOfPos, lineOfPos+linesInserted);
2448 calcLastChar(textD);
2449 /* { int i;
2450 printf("lineStarts after insert at end: ");
2451 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2452 printf("\n");
2453 } */
2454 *scrolled = False;
2455 return;
2458 /* Change was beyond the end of the buffer and not visible, do nothing */
2459 *scrolled = False;
2463 ** Scan through the text in the "textD"'s buffer and recalculate the line
2464 ** starts array values beginning at index "startLine" and continuing through
2465 ** (including) "endLine". It assumes that the line starts entry preceding
2466 ** "startLine" (or textD->firstChar if startLine is 0) is good, and re-counts
2467 ** newlines to fill in the requested entries. Out of range values for
2468 ** "startLine" and "endLine" are acceptable.
2470 static void calcLineStarts(textDisp *textD, int startLine, int endLine)
2472 int startPos, bufLen = textD->buffer->length;
2473 int line, lineEnd, nextLineStart, nVis = textD->nVisibleLines;
2474 int *lineStarts = textD->lineStarts;
2476 /* Clean up (possibly) messy input parameters */
2477 if (nVis == 0) return;
2478 if (endLine < 0) endLine = 0;
2479 if (endLine >= nVis) endLine = nVis - 1;
2480 if (startLine < 0) startLine = 0;
2481 if (startLine >=nVis) startLine = nVis - 1;
2482 if (startLine > endLine)
2483 return;
2485 /* Find the last known good line number -> position mapping */
2486 if (startLine == 0) {
2487 lineStarts[0] = textD->firstChar;
2488 startLine = 1;
2490 startPos = lineStarts[startLine-1];
2492 /* If the starting position is already past the end of the text,
2493 fill in -1's (means no text on line) and return */
2494 if (startPos == -1) {
2495 for (line=startLine; line<=endLine; line++)
2496 lineStarts[line] = -1;
2497 return;
2500 /* Loop searching for ends of lines and storing the positions of the
2501 start of the next line in lineStarts */
2502 for (line=startLine; line<=endLine; line++) {
2503 findLineEnd(textD, startPos, True, &lineEnd, &nextLineStart);
2504 startPos = nextLineStart;
2505 if (startPos >= bufLen) {
2506 /* If the buffer ends with a newline or line break, put
2507 buf->length in the next line start position (instead of
2508 a -1 which is the normal marker for an empty line) to
2509 indicate that the cursor may safely be displayed there */
2510 if (line == 0 || (lineStarts[line-1] != bufLen &&
2511 lineEnd != nextLineStart)) {
2512 lineStarts[line] = bufLen;
2513 line++;
2515 break;
2517 lineStarts[line] = startPos;
2520 /* Set any entries beyond the end of the text to -1 */
2521 for (; line<=endLine; line++)
2522 lineStarts[line] = -1;
2526 ** Given a textDisp with a complete, up-to-date lineStarts array, update
2527 ** the lastChar entry to point to the last buffer position displayed.
2529 static void calcLastChar(textDisp *textD)
2531 int i;
2533 for (i=textD->nVisibleLines-1; i>0 && textD->lineStarts[i]== -1; i--);
2534 textD->lastChar = i < 0 ? 0 :
2535 TextDEndOfLine(textD, textD->lineStarts[i], True);
2538 void TextDImposeGraphicsExposeTranslation(textDisp *textD, int *xOffset, int *yOffset)
2540 if (textD->graphicsExposeQueue) {
2541 graphicExposeTranslationEntry *thisGEQEntry = textD->graphicsExposeQueue->next;
2542 if (thisGEQEntry) {
2543 *xOffset += thisGEQEntry->horizontal;
2544 *yOffset += thisGEQEntry->vertical;
2549 Boolean TextDPopGraphicExposeQueueEntry(textDisp *textD)
2551 graphicExposeTranslationEntry *removedGEQEntry = textD->graphicsExposeQueue;
2553 if (removedGEQEntry) {
2554 textD->graphicsExposeQueue = removedGEQEntry->next;
2555 XtFree((char *)removedGEQEntry);
2557 return(removedGEQEntry?True:False);
2560 void TextDTranlateGraphicExposeQueue(textDisp *textD, int xOffset, int yOffset, Boolean appendEntry)
2562 graphicExposeTranslationEntry *newGEQEntry = NULL;
2563 if (appendEntry) {
2564 newGEQEntry = (graphicExposeTranslationEntry *)XtMalloc(sizeof(graphicExposeTranslationEntry));
2565 newGEQEntry->next = NULL;
2566 newGEQEntry->horizontal = xOffset;
2567 newGEQEntry->vertical = yOffset;
2569 if (textD->graphicsExposeQueue) {
2570 graphicExposeTranslationEntry *iter = textD->graphicsExposeQueue;
2571 while (iter->next) {
2572 iter->next->horizontal += xOffset;
2573 iter->next->vertical += yOffset;
2574 iter = iter->next;
2576 if (appendEntry) {
2577 iter->next = (struct graphicExposeTranslationEntry *)newGEQEntry;
2580 else {
2581 if (appendEntry) {
2582 textD->graphicsExposeQueue = newGEQEntry;
2587 static void setScroll(textDisp *textD, int topLineNum, int horizOffset,
2588 int updateVScrollBar, int updateHScrollBar)
2590 int fontHeight = textD->ascent + textD->descent;
2591 int origHOffset = textD->horizOffset;
2592 int lineDelta = textD->topLineNum - topLineNum;
2593 int xOffset, yOffset, srcX, srcY, dstX, dstY, width, height;
2594 int exactHeight = textD->height - textD->height %
2595 (textD->ascent + textD->descent);
2597 /* Do nothing if scroll position hasn't actually changed or there's no
2598 window to draw in yet */
2599 if (XtWindow(textD->w) == 0 || (textD->horizOffset == horizOffset &&
2600 textD->topLineNum == topLineNum))
2601 return;
2603 /* If part of the cursor is protruding beyond the text clipping region,
2604 clear it off */
2605 blankCursorProtrusions(textD);
2607 /* If the vertical scroll position has changed, update the line
2608 starts array and related counters in the text display */
2609 offsetLineStarts(textD, topLineNum);
2611 /* Just setting textD->horizOffset is enough information for redisplay */
2612 textD->horizOffset = horizOffset;
2614 /* Update the scroll bar positions if requested, note: updating the
2615 horizontal scroll bars can have the further side-effect of changing
2616 the horizontal scroll position, textD->horizOffset */
2617 if (updateVScrollBar && textD->vScrollBar != NULL)
2618 updateVScrollBarRange(textD);
2619 if (updateHScrollBar && textD->hScrollBar != NULL) {
2620 updateHScrollBarRange(textD);
2623 /* Redisplay everything if the window is partially obscured (since
2624 it's too hard to tell what displayed areas are salvageable) or
2625 if there's nothing to recover because the scroll distance is large */
2626 xOffset = origHOffset - textD->horizOffset;
2627 yOffset = lineDelta * fontHeight;
2628 if (abs(xOffset) > textD->width || abs(yOffset) > exactHeight) {
2629 TextDTranlateGraphicExposeQueue(textD, xOffset, yOffset, False);
2630 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
2631 textD->height);
2632 } else {
2633 /* Recover the useable window areas by moving to the proper location */
2634 srcX = textD->left + (xOffset >= 0 ? 0 : -xOffset);
2635 dstX = textD->left + (xOffset >= 0 ? xOffset : 0);
2636 width = textD->width - abs(xOffset);
2637 srcY = textD->top + (yOffset >= 0 ? 0 : -yOffset);
2638 dstY = textD->top + (yOffset >= 0 ? yOffset : 0);
2639 height = exactHeight - abs(yOffset);
2640 resetClipRectangles(textD);
2641 TextDTranlateGraphicExposeQueue(textD, xOffset, yOffset, True);
2642 XCopyArea(XtDisplay(textD->w), XtWindow(textD->w), XtWindow(textD->w),
2643 textD->gc, srcX, srcY, width, height, dstX, dstY);
2644 /* redraw the un-recoverable parts */
2645 if (yOffset > 0)
2646 TextDRedisplayRect(textD, textD->left, textD->top,
2647 textD->width, yOffset);
2648 else if (yOffset < 0)
2649 TextDRedisplayRect(textD, textD->left, textD->top +
2650 textD->height + yOffset, textD->width, -yOffset);
2651 if (xOffset > 0)
2652 TextDRedisplayRect(textD, textD->left, textD->top,
2653 xOffset, textD->height);
2654 else if (xOffset < 0)
2655 TextDRedisplayRect(textD, textD->left + textD->width + xOffset,
2656 textD->top, -xOffset, textD->height);
2657 /* Restore protruding parts of the cursor */
2658 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
2661 /* Refresh line number/calltip display if its up and we've scrolled
2662 vertically */
2663 if (lineDelta != 0) {
2664 redrawLineNumbers(textD, False);
2665 TextDRedrawCalltip(textD, 0);
2668 HandleAllPendingGraphicsExposeNoExposeEvents((TextWidget)textD->w, NULL);
2672 ** Update the minimum, maximum, slider size, page increment, and value
2673 ** for vertical scroll bar.
2675 static void updateVScrollBarRange(textDisp *textD)
2677 int sliderSize, sliderMax, sliderValue;
2679 if (textD->vScrollBar == NULL)
2680 return;
2682 /* The Vert. scroll bar value and slider size directly represent the top
2683 line number, and the number of visible lines respectively. The scroll
2684 bar maximum value is chosen to generally represent the size of the whole
2685 buffer, with minor adjustments to keep the scroll bar widget happy */
2686 sliderSize = max(textD->nVisibleLines, 1); /* Avoid X warning (size < 1) */
2687 sliderValue = textD->topLineNum;
2688 sliderMax = max(textD->nBufferLines + 2 +
2689 TEXT_OF_TEXTD(textD).cursorVPadding,
2690 sliderSize + sliderValue);
2691 XtVaSetValues(textD->vScrollBar,
2692 XmNmaximum, sliderMax,
2693 XmNsliderSize, sliderSize,
2694 XmNpageIncrement, max(1, textD->nVisibleLines - 1),
2695 XmNvalue, sliderValue, NULL);
2699 ** Update the minimum, maximum, slider size, page increment, and value
2700 ** for the horizontal scroll bar. If scroll position is such that there
2701 ** is blank space to the right of all lines of text, scroll back (adjust
2702 ** horizOffset but don't redraw) to take up the slack and position the
2703 ** right edge of the text at the right edge of the display.
2705 ** Note, there is some cost to this routine, since it scans the whole range
2706 ** of displayed text, particularly since it's usually called for each typed
2707 ** character!
2709 static int updateHScrollBarRange(textDisp *textD)
2711 int i, maxWidth = 0, sliderMax, sliderWidth;
2712 int origHOffset = textD->horizOffset;
2714 if (textD->hScrollBar == NULL || !XtIsManaged(textD->hScrollBar))
2715 return False;
2717 /* Scan all the displayed lines to find the width of the longest line */
2718 for (i=0; i<textD->nVisibleLines && textD->lineStarts[i]!= -1; i++)
2719 maxWidth = max(measureVisLine(textD, i), maxWidth);
2721 /* If the scroll position is beyond what's necessary to keep all lines
2722 in view, scroll to the left to bring the end of the longest line to
2723 the right margin */
2724 if (maxWidth < textD->width + textD->horizOffset && textD->horizOffset > 0)
2725 textD->horizOffset = max(0, maxWidth - textD->width);
2727 /* Readjust the scroll bar */
2728 sliderWidth = textD->width;
2729 sliderMax = max(maxWidth, sliderWidth + textD->horizOffset);
2730 XtVaSetValues(textD->hScrollBar,
2731 XmNmaximum, sliderMax,
2732 XmNsliderSize, sliderWidth,
2733 XmNpageIncrement, max(textD->width - 100, 10),
2734 XmNvalue, textD->horizOffset, NULL);
2736 /* Return True if scroll position was changed */
2737 return origHOffset != textD->horizOffset;
2741 ** Define area for drawing line numbers. A width of 0 disables line
2742 ** number drawing.
2744 void TextDSetLineNumberArea(textDisp *textD, int lineNumLeft, int lineNumWidth,
2745 int textLeft)
2747 int newWidth = textD->width + textD->left - textLeft;
2748 textD->lineNumLeft = lineNumLeft;
2749 textD->lineNumWidth = lineNumWidth;
2750 textD->left = textLeft;
2751 XClearWindow(XtDisplay(textD->w), XtWindow(textD->w));
2752 resetAbsLineNum(textD);
2753 TextDResize(textD, newWidth, textD->height);
2754 TextDRedisplayRect(textD, 0, textD->top, INT_MAX, textD->height);
2758 ** Refresh the line number area. If clearAll is False, writes only over
2759 ** the character cell areas. Setting clearAll to True will clear out any
2760 ** stray marks outside of the character cell area, which might have been
2761 ** left from before a resize or font change.
2763 static void redrawLineNumbers(textDisp *textD, int clearAll)
2765 int y, line, visLine, nCols, lineStart;
2766 char lineNumString[12];
2767 int lineHeight = textD->ascent + textD->descent;
2768 int charWidth = textD->fontStruct->max_bounds.width;
2769 XRectangle clipRect;
2770 Display *display = XtDisplay(textD->w);
2772 /* Don't draw if lineNumWidth == 0 (line numbers are hidden), or widget is
2773 not yet realized */
2774 if (textD->lineNumWidth == 0 || XtWindow(textD->w) == 0)
2775 return;
2777 /* Make sure we reset the clipping range for the line numbers GC, because
2778 the GC may be shared (eg, if the line numbers and text have the same
2779 color) and therefore the clipping ranges may be invalid. */
2780 clipRect.x = textD->lineNumLeft;
2781 clipRect.y = textD->top;
2782 clipRect.width = textD->lineNumWidth;
2783 clipRect.height = textD->height;
2784 XSetClipRectangles(display, textD->lineNumGC, 0, 0,
2785 &clipRect, 1, Unsorted);
2787 /* Erase the previous contents of the line number area, if requested */
2788 if (clearAll)
2789 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->lineNumLeft,
2790 textD->top, textD->lineNumWidth, textD->height, False);
2792 /* Draw the line numbers, aligned to the text */
2793 nCols = min(11, textD->lineNumWidth / charWidth);
2794 y = textD->top;
2795 line = getAbsTopLineNum(textD);
2796 for (visLine=0; visLine < textD->nVisibleLines; visLine++) {
2797 lineStart = textD->lineStarts[visLine];
2798 if (lineStart != -1 && (lineStart==0 ||
2799 BufGetCharacter(textD->buffer, lineStart-1)=='\n')) {
2800 sprintf(lineNumString, "%*d", nCols, line);
2801 XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w),
2802 textD->lineNumGC, textD->lineNumLeft, y + textD->ascent,
2803 lineNumString, strlen(lineNumString));
2804 line++;
2805 } else {
2806 XClearArea(XtDisplay(textD->w), XtWindow(textD->w),
2807 textD->lineNumLeft, y, textD->lineNumWidth,
2808 textD->ascent + textD->descent, False);
2809 if (visLine == 0)
2810 line++;
2812 y += lineHeight;
2817 ** Callbacks for drag or valueChanged on scroll bars
2819 static void vScrollCB(Widget w, XtPointer clientData, XtPointer callData)
2821 textDisp *textD = (textDisp *)clientData;
2822 int newValue = ((XmScrollBarCallbackStruct *)callData)->value;
2823 int lineDelta = newValue - textD->topLineNum;
2825 if (lineDelta == 0)
2826 return;
2827 setScroll(textD, newValue, textD->horizOffset, False, True);
2829 static void hScrollCB(Widget w, XtPointer clientData, XtPointer callData)
2831 textDisp *textD = (textDisp *)clientData;
2832 int newValue = ((XmScrollBarCallbackStruct *)callData)->value;
2834 if (newValue == textD->horizOffset)
2835 return;
2836 setScroll(textD, textD->topLineNum, newValue, False, False);
2839 static int max(int i1, int i2)
2841 return i1 >= i2 ? i1 : i2;
2844 static int min(int i1, int i2)
2846 return i1 <= i2 ? i1 : i2;
2850 ** Count the number of newlines in a null-terminated text string;
2852 static int countLines(char *string)
2854 char *c;
2855 int lineCount = 0;
2857 if (string == NULL)
2858 return 0;
2859 for (c=string; *c!='\0'; c++)
2860 if (*c == '\n') lineCount++;
2861 return lineCount;
2865 ** Return the width in pixels of the displayed line pointed to by "visLineNum"
2867 static int measureVisLine(textDisp *textD, int visLineNum)
2869 int i, width = 0, len, style, lineLen = visLineLength(textD, visLineNum);
2870 int charCount = 0, lineStartPos = textD->lineStarts[visLineNum];
2871 char expandedChar[MAX_EXP_CHAR_LEN];
2873 if (textD->styleBuffer == NULL) {
2874 for (i=0; i<lineLen; i++) {
2875 len = BufGetExpandedChar(textD->buffer, lineStartPos + i,
2876 charCount, expandedChar);
2877 width += XTextWidth(textD->fontStruct, expandedChar, len);
2878 charCount += len;
2880 } else {
2881 for (i=0; i<lineLen; i++) {
2882 len = BufGetExpandedChar(textD->buffer, lineStartPos+i,
2883 charCount, expandedChar);
2884 style = (unsigned char)BufGetCharacter(textD->styleBuffer,
2885 lineStartPos+i) - ASCII_A;
2886 width += XTextWidth(textD->styleTable[style].font, expandedChar,
2887 len);
2888 charCount += len;
2891 return width;
2895 ** Return true if there are lines visible with no corresponding buffer text
2897 static int emptyLinesVisible(textDisp *textD)
2899 return textD->nVisibleLines > 0 &&
2900 textD->lineStarts[textD->nVisibleLines-1] == -1;
2904 ** When the cursor is at the left or right edge of the text, part of it
2905 ** sticks off into the clipped region beyond the text. Normal redrawing
2906 ** can not overwrite this protruding part of the cursor, so it must be
2907 ** erased independently by calling this routine.
2909 static void blankCursorProtrusions(textDisp *textD)
2911 int x, width, cursorX = textD->cursorX, cursorY = textD->cursorY;
2912 int fontWidth = textD->fontStruct->max_bounds.width;
2913 int fontHeight = textD->ascent + textD->descent;
2914 int cursorWidth, left = textD->left, right = left + textD->width;
2916 cursorWidth = (fontWidth/3) * 2;
2917 if (cursorX >= left-1 && cursorX <= left + cursorWidth/2 - 1) {
2918 x = cursorX - cursorWidth/2;
2919 width = left - x;
2920 } else if (cursorX >= right - cursorWidth/2 && cursorX <= right) {
2921 x = right;
2922 width = cursorX + cursorWidth/2 + 2 - right;
2923 } else
2924 return;
2926 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), x, cursorY,
2927 width, fontHeight, False);
2931 ** Allocate shared graphics contexts used by the widget, which must be
2932 ** re-allocated on a font change.
2934 static void allocateFixedFontGCs(textDisp *textD, XFontStruct *fontStruct,
2935 Pixel bgPixel, Pixel fgPixel, Pixel selectFGPixel, Pixel selectBGPixel,
2936 Pixel highlightFGPixel, Pixel highlightBGPixel, Pixel lineNumFGPixel)
2938 textD->gc = allocateGC(textD->w, GCFont | GCForeground | GCBackground,
2939 fgPixel, bgPixel, fontStruct->fid, GCClipMask, GCArcMode);
2940 textD->selectGC = allocateGC(textD->w, GCFont | GCForeground | GCBackground,
2941 selectFGPixel, selectBGPixel, fontStruct->fid, GCClipMask,
2942 GCArcMode);
2943 textD->selectBGGC = allocateGC(textD->w, GCForeground, selectBGPixel, 0,
2944 fontStruct->fid, GCClipMask, GCArcMode);
2945 textD->highlightGC = allocateGC(textD->w, GCFont|GCForeground|GCBackground,
2946 highlightFGPixel, highlightBGPixel, fontStruct->fid, GCClipMask,
2947 GCArcMode);
2948 textD->highlightBGGC = allocateGC(textD->w, GCForeground, highlightBGPixel,
2949 0, fontStruct->fid, GCClipMask, GCArcMode);
2950 textD->lineNumGC = allocateGC(textD->w, GCFont | GCForeground |
2951 GCBackground, lineNumFGPixel, bgPixel, fontStruct->fid,
2952 GCClipMask, GCArcMode);
2956 ** X11R4 does not have the XtAllocateGC function for sharing graphics contexts
2957 ** with changeable fields. Unfortunately the R4 call for creating shared
2958 ** graphics contexts (XtGetGC) is rarely useful because most widgets need
2959 ** to be able to set and change clipping, and that makes the GC unshareable.
2961 ** This function allocates and returns a gc, using XtAllocateGC if possible,
2962 ** or XCreateGC on X11R4 systems where XtAllocateGC is not available.
2964 static GC allocateGC(Widget w, unsigned long valueMask,
2965 unsigned long foreground, unsigned long background, Font font,
2966 unsigned long dynamicMask, unsigned long dontCareMask)
2968 XGCValues gcValues;
2970 gcValues.font = font;
2971 gcValues.background = background;
2972 gcValues.foreground = foreground;
2973 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4
2974 return XtAllocateGC(w, 0, valueMask, &gcValues, dynamicMask,
2975 dontCareMask);
2976 #else
2977 return XCreateGC(XtDisplay(w), RootWindowOfScreen(XtScreen(w)),
2978 valueMask, &gcValues);
2979 #endif
2983 ** Release a gc allocated with allocateGC above
2985 static void releaseGC(Widget w, GC gc)
2987 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4
2988 XtReleaseGC(w, gc);
2989 #else
2990 XFreeGC(XtDisplay(w), gc);
2991 #endif
2995 ** resetClipRectangles sets the clipping rectangles for GCs which clip
2996 ** at the text boundary (as opposed to the window boundary). These GCs
2997 ** are shared such that the drawing styles are constant, but the clipping
2998 ** rectangles are allowed to change among different users of the GCs (the
2999 ** GCs were created with XtAllocGC). This routine resets them so the clipping
3000 ** rectangles are correct for this text display.
3002 static void resetClipRectangles(textDisp *textD)
3004 XRectangle clipRect;
3005 Display *display = XtDisplay(textD->w);
3007 clipRect.x = textD->left;
3008 clipRect.y = textD->top;
3009 clipRect.width = textD->width;
3010 clipRect.height = textD->height - textD->height %
3011 (textD->ascent + textD->descent);
3013 XSetClipRectangles(display, textD->gc, 0, 0,
3014 &clipRect, 1, Unsorted);
3015 XSetClipRectangles(display, textD->selectGC, 0, 0,
3016 &clipRect, 1, Unsorted);
3017 XSetClipRectangles(display, textD->highlightGC, 0, 0,
3018 &clipRect, 1, Unsorted);
3019 XSetClipRectangles(display, textD->selectBGGC, 0, 0,
3020 &clipRect, 1, Unsorted);
3021 XSetClipRectangles(display, textD->highlightBGGC, 0, 0,
3022 &clipRect, 1, Unsorted);
3023 XSetClipRectangles(display, textD->styleGC, 0, 0,
3024 &clipRect, 1, Unsorted);
3028 ** Return the length of a line (number of displayable characters) by examining
3029 ** entries in the line starts array rather than by scanning for newlines
3031 static int visLineLength(textDisp *textD, int visLineNum)
3033 int nextLineStart, lineStartPos = textD->lineStarts[visLineNum];
3035 if (lineStartPos == -1)
3036 return 0;
3037 if (visLineNum+1 >= textD->nVisibleLines)
3038 return textD->lastChar - lineStartPos;
3039 nextLineStart = textD->lineStarts[visLineNum+1];
3040 if (nextLineStart == -1)
3041 return textD->lastChar - lineStartPos;
3042 if (wrapUsesCharacter(textD, nextLineStart-1))
3043 return nextLineStart-1 - lineStartPos;
3044 return nextLineStart - lineStartPos;
3048 ** When continuous wrap is on, and the user inserts or deletes characters,
3049 ** wrapping can happen before and beyond the changed position. This routine
3050 ** finds the extent of the changes, and counts the deleted and inserted lines
3051 ** over that range. It also attempts to minimize the size of the range to
3052 ** what has to be counted and re-displayed, so the results can be useful
3053 ** both for delimiting where the line starts need to be recalculated, and
3054 ** for deciding what part of the text to redisplay.
3056 static void findWrapRange(textDisp *textD, char *deletedText, int pos,
3057 int nInserted, int nDeleted, int *modRangeStart, int *modRangeEnd,
3058 int *linesInserted, int *linesDeleted)
3060 int length, retPos, retLines, retLineStart, retLineEnd;
3061 textBuffer *deletedTextBuf, *buf = textD->buffer;
3062 int nVisLines = textD->nVisibleLines;
3063 int *lineStarts = textD->lineStarts;
3064 int countFrom, countTo, lineStart, adjLineStart, i;
3065 int visLineNum = 0, nLines = 0;
3068 ** Determine where to begin searching: either the previous newline, or
3069 ** if possible, limit to the start of the (original) previous displayed
3070 ** line, using information from the existing line starts array
3072 if (pos >= textD->firstChar && pos <= textD->lastChar) {
3073 for (i=nVisLines-1; i>0; i--)
3074 if (lineStarts[i] != -1 && pos >= lineStarts[i])
3075 break;
3076 if (i > 0) {
3077 countFrom = lineStarts[i-1];
3078 visLineNum = i-1;
3079 } else
3080 countFrom = BufStartOfLine(buf, pos);
3081 } else
3082 countFrom = BufStartOfLine(buf, pos);
3086 ** Move forward through the (new) text one line at a time, counting
3087 ** displayed lines, and looking for either a real newline, or for the
3088 ** line starts to re-sync with the original line starts array
3090 lineStart = countFrom;
3091 *modRangeStart = countFrom;
3092 while (True) {
3094 /* advance to the next line. If the line ended in a real newline
3095 or the end of the buffer, that's far enough */
3096 wrappedLineCounter(textD, buf, lineStart, buf->length, 1, True, 0,
3097 &retPos, &retLines, &retLineStart, &retLineEnd);
3098 if (retPos >= buf->length) {
3099 countTo = buf->length;
3100 *modRangeEnd = countTo;
3101 if (retPos != retLineEnd)
3102 nLines++;
3103 break;
3104 } else
3105 lineStart = retPos;
3106 nLines++;
3107 if (lineStart > pos + nInserted &&
3108 BufGetCharacter(buf, lineStart-1) == '\n') {
3109 countTo = lineStart;
3110 *modRangeEnd = lineStart;
3111 break;
3114 /* Don't try to resync in continuous wrap mode with non-fixed font
3115 sizes; it would result in a chicken-and-egg dependency between
3116 the calculations for the inserted and the deleted lines.
3117 If we're in that mode, the number of deleted lines is calculated in
3118 advance, without resynchronization, so we shouldn't resynchronize
3119 for the inserted lines either. */
3120 if (textD->suppressResync)
3121 continue;
3123 /* check for synchronization with the original line starts array
3124 before pos, if so, the modified range can begin later */
3125 if (lineStart <= pos) {
3126 while (visLineNum<nVisLines && lineStarts[visLineNum] < lineStart)
3127 visLineNum++;
3128 if (visLineNum < nVisLines && lineStarts[visLineNum] == lineStart) {
3129 countFrom = lineStart;
3130 nLines = 0;
3131 if (visLineNum+1 < nVisLines && lineStarts[visLineNum+1] != -1)
3132 *modRangeStart = min(pos, lineStarts[visLineNum+1]-1);
3133 else
3134 *modRangeStart = countFrom;
3135 } else
3136 *modRangeStart = min(*modRangeStart, lineStart-1);
3139 /* check for synchronization with the original line starts array
3140 after pos, if so, the modified range can end early */
3141 else if (lineStart > pos + nInserted) {
3142 adjLineStart = lineStart - nInserted + nDeleted;
3143 while (visLineNum<nVisLines && lineStarts[visLineNum]<adjLineStart)
3144 visLineNum++;
3145 if (visLineNum < nVisLines && lineStarts[visLineNum] != -1 &&
3146 lineStarts[visLineNum] == adjLineStart) {
3147 countTo = TextDEndOfLine(textD, lineStart, True);
3148 *modRangeEnd = lineStart;
3149 break;
3153 *linesInserted = nLines;
3156 /* Count deleted lines between countFrom and countTo as the text existed
3157 before the modification (that is, as if the text between pos and
3158 pos+nInserted were replaced by "deletedText"). This extra context is
3159 necessary because wrapping can occur outside of the modified region
3160 as a result of adding or deleting text in the region. This is done by
3161 creating a textBuffer containing the deleted text and the necessary
3162 additional context, and calling the wrappedLineCounter on it.
3164 NOTE: This must not be done in continuous wrap mode when the font
3165 width is not fixed. In that case, the calculation would try
3166 to access style information that is no longer available (deleted
3167 text), or out of date (updated highlighting), possibly leading
3168 to completely wrong calculations and/or even crashes eventually.
3169 (This is not theoretical; it really happened.)
3171 In that case, the calculation of the number of deleted lines
3172 has happened before the buffer was modified (only in that case,
3173 because resynchronization of the line starts is impossible
3174 in that case, which makes the whole calculation less efficient).
3176 if (textD->suppressResync) {
3177 *linesDeleted = textD->nLinesDeleted;
3178 textD->suppressResync = 0;
3179 return;
3182 length = (pos-countFrom) + nDeleted +(countTo-(pos+nInserted));
3183 deletedTextBuf = BufCreatePreallocated(length);
3184 if (pos > countFrom)
3185 BufCopyFromBuf(textD->buffer, deletedTextBuf, countFrom, pos, 0);
3186 if (nDeleted != 0)
3187 BufInsert(deletedTextBuf, pos-countFrom, deletedText);
3188 if (countTo > pos+nInserted)
3189 BufCopyFromBuf(textD->buffer, deletedTextBuf,
3190 pos+nInserted, countTo, pos-countFrom+nDeleted);
3191 /* Note that we need to take into account an offset for the style buffer:
3192 the deletedTextBuf can be out of sync with the style buffer. */
3193 wrappedLineCounter(textD, deletedTextBuf, 0, length, INT_MAX, True,
3194 countFrom, &retPos, &retLines, &retLineStart, &retLineEnd);
3195 BufFree(deletedTextBuf);
3196 *linesDeleted = retLines;
3197 textD->suppressResync = 0;
3201 ** This is a stripped-down version of the findWrapRange() function above,
3202 ** intended to be used to calculate the number of "deleted" lines during
3203 ** a buffer modification. It is called _before_ the modification takes place.
3205 ** This function should only be called in continuous wrap mode with a
3206 ** non-fixed font width. In that case, it is impossible to calculate
3207 ** the number of deleted lines, because the necessary style information
3208 ** is no longer available _after_ the modification. In other cases, we
3209 ** can still perform the calculation afterwards (possibly even more
3210 ** efficiently).
3212 static void measureDeletedLines(textDisp *textD, int pos, int nDeleted)
3214 int retPos, retLines, retLineStart, retLineEnd;
3215 textBuffer *buf = textD->buffer;
3216 int nVisLines = textD->nVisibleLines;
3217 int *lineStarts = textD->lineStarts;
3218 int countFrom, lineStart;
3219 int nLines = 0, i;
3221 ** Determine where to begin searching: either the previous newline, or
3222 ** if possible, limit to the start of the (original) previous displayed
3223 ** line, using information from the existing line starts array
3225 if (pos >= textD->firstChar && pos <= textD->lastChar) {
3226 for (i=nVisLines-1; i>0; i--)
3227 if (lineStarts[i] != -1 && pos >= lineStarts[i])
3228 break;
3229 if (i > 0) {
3230 countFrom = lineStarts[i-1];
3231 } else
3232 countFrom = BufStartOfLine(buf, pos);
3233 } else
3234 countFrom = BufStartOfLine(buf, pos);
3237 ** Move forward through the (new) text one line at a time, counting
3238 ** displayed lines, and looking for either a real newline, or for the
3239 ** line starts to re-sync with the original line starts array
3241 lineStart = countFrom;
3242 while (True) {
3243 /* advance to the next line. If the line ended in a real newline
3244 or the end of the buffer, that's far enough */
3245 wrappedLineCounter(textD, buf, lineStart, buf->length, 1, True, 0,
3246 &retPos, &retLines, &retLineStart, &retLineEnd);
3247 if (retPos >= buf->length) {
3248 if (retPos != retLineEnd)
3249 nLines++;
3250 break;
3251 } else
3252 lineStart = retPos;
3253 nLines++;
3254 if (lineStart > pos + nDeleted &&
3255 BufGetCharacter(buf, lineStart-1) == '\n') {
3256 break;
3259 /* Unlike in the findWrapRange() function above, we don't try to
3260 resync with the line starts, because we don't know the length
3261 of the inserted text yet, nor the updated style information.
3263 Because of that, we also shouldn't resync with the line starts
3264 after the modification either, because we must perform the
3265 calculations for the deleted and inserted lines in the same way.
3267 This can result in some unnecessary recalculation and redrawing
3268 overhead, and therefore we should only use this two-phase mode
3269 of calculation when it's really needed (continuous wrap + variable
3270 font width). */
3272 textD->nLinesDeleted = nLines;
3273 textD->suppressResync = 1;
3277 ** Count forward from startPos to either maxPos or maxLines (whichever is
3278 ** reached first), and return all relevant positions and line count.
3279 ** The provided textBuffer may differ from the actual text buffer of the
3280 ** widget. In that case it must be a (partial) copy of the actual text buffer
3281 ** and the styleBufOffset argument must indicate the starting position of the
3282 ** copy, to take into account the correct style information.
3284 ** Returned values:
3286 ** retPos: Position where counting ended. When counting lines, the
3287 ** position returned is the start of the line "maxLines"
3288 ** lines beyond "startPos".
3289 ** retLines: Number of line breaks counted
3290 ** retLineStart: Start of the line where counting ended
3291 ** retLineEnd: End position of the last line traversed
3293 static void wrappedLineCounter(textDisp *textD, textBuffer *buf, int startPos,
3294 int maxPos, int maxLines, int startPosIsLineStart, int styleBufOffset,
3295 int *retPos, int *retLines, int *retLineStart, int *retLineEnd)
3297 int lineStart, newLineStart = 0, b, p, colNum, wrapMargin;
3298 int maxWidth, width, countPixels, i, foundBreak;
3299 int nLines = 0, tabDist = textD->buffer->tabDist;
3300 unsigned char c;
3301 char nullSubsChar = textD->buffer->nullSubsChar;
3303 /* If the font is fixed, or there's a wrap margin set, it's more efficient
3304 to measure in columns, than to count pixels. Determine if we can count
3305 in columns (countPixels == False) or must count pixels (countPixels ==
3306 True), and set the wrap target for either pixels or columns */
3307 if (textD->fixedFontWidth != -1 || textD->wrapMargin != 0) {
3308 countPixels = False;
3309 wrapMargin = textD->wrapMargin != 0 ? textD->wrapMargin :
3310 textD->width / textD->fixedFontWidth;
3311 maxWidth = INT_MAX;
3312 } else {
3313 countPixels = True;
3314 wrapMargin = INT_MAX;
3315 maxWidth = textD->width;
3318 /* Find the start of the line if the start pos is not marked as a
3319 line start. */
3320 if (startPosIsLineStart)
3321 lineStart = startPos;
3322 else
3323 lineStart = TextDStartOfLine(textD, startPos);
3326 ** Loop until position exceeds maxPos or line count exceeds maxLines.
3327 ** (actually, contines beyond maxPos to end of line containing maxPos,
3328 ** in case later characters cause a word wrap back before maxPos)
3330 colNum = 0;
3331 width = 0;
3332 for (p=lineStart; p<buf->length; p++) {
3333 c = BufGetCharacter(buf, p);
3335 /* If the character was a newline, count the line and start over,
3336 otherwise, add it to the width and column counts */
3337 if (c == '\n') {
3338 if (p >= maxPos) {
3339 *retPos = maxPos;
3340 *retLines = nLines;
3341 *retLineStart = lineStart;
3342 *retLineEnd = maxPos;
3343 return;
3345 nLines++;
3346 if (nLines >= maxLines) {
3347 *retPos = p + 1;
3348 *retLines = nLines;
3349 *retLineStart = p + 1;
3350 *retLineEnd = p;
3351 return;
3353 lineStart = p + 1;
3354 colNum = 0;
3355 width = 0;
3356 } else {
3357 colNum += BufCharWidth(c, colNum, tabDist, nullSubsChar);
3358 if (countPixels)
3359 width += measurePropChar(textD, c, colNum, p+styleBufOffset);
3362 /* If character exceeded wrap margin, find the break point
3363 and wrap there */
3364 if (colNum > wrapMargin || width > maxWidth) {
3365 foundBreak = False;
3366 for (b=p; b>=lineStart; b--) {
3367 c = BufGetCharacter(buf, b);
3368 if (c == '\t' || c == ' ') {
3369 newLineStart = b + 1;
3370 if (countPixels) {
3371 colNum = 0;
3372 width = 0;
3373 for (i=b+1; i<p+1; i++) {
3374 width += measurePropChar(textD,
3375 BufGetCharacter(buf, i), colNum,
3376 i+styleBufOffset);
3377 colNum++;
3379 } else
3380 colNum = BufCountDispChars(buf, b+1, p+1);
3381 foundBreak = True;
3382 break;
3385 if (!foundBreak) { /* no whitespace, just break at margin */
3386 newLineStart = max(p, lineStart+1);
3387 colNum = BufCharWidth(c, colNum, tabDist, nullSubsChar);
3388 if (countPixels)
3389 width = measurePropChar(textD, c, colNum, p+styleBufOffset);
3391 if (p >= maxPos) {
3392 *retPos = maxPos;
3393 *retLines = maxPos < newLineStart ? nLines : nLines + 1;
3394 *retLineStart = maxPos < newLineStart ? lineStart :
3395 newLineStart;
3396 *retLineEnd = maxPos;
3397 return;
3399 nLines++;
3400 if (nLines >= maxLines) {
3401 *retPos = foundBreak ? b + 1 : max(p, lineStart+1);
3402 *retLines = nLines;
3403 *retLineStart = lineStart;
3404 *retLineEnd = foundBreak ? b : p;
3405 return;
3407 lineStart = newLineStart;
3411 /* reached end of buffer before reaching pos or line target */
3412 *retPos = buf->length;
3413 *retLines = nLines;
3414 *retLineStart = lineStart;
3415 *retLineEnd = buf->length;
3419 ** Measure the width in pixels of a character "c" at a particular column
3420 ** "colNum" and buffer position "pos". This is for measuring characters in
3421 ** proportional or mixed-width highlighting fonts.
3423 ** A note about proportional and mixed-width fonts: the mixed width and
3424 ** proportional font code in nedit does not get much use in general editing,
3425 ** because nedit doesn't allow per-language-mode fonts, and editing programs
3426 ** in a proportional font is usually a bad idea, so very few users would
3427 ** choose a proportional font as a default. There are still probably mixed-
3428 ** width syntax highlighting cases where things don't redraw properly for
3429 ** insertion/deletion, though static display and wrapping and resizing
3430 ** should now be solid because they are now used for online help display.
3432 static int measurePropChar(textDisp *textD, char c, int colNum, int pos)
3434 int charLen, style;
3435 char expChar[MAX_EXP_CHAR_LEN];
3436 textBuffer *styleBuf = textD->styleBuffer;
3438 charLen = BufExpandCharacter(c, colNum, expChar,
3439 textD->buffer->tabDist, textD->buffer->nullSubsChar);
3440 if (styleBuf == NULL) {
3441 style = 0;
3442 } else {
3443 style = (unsigned char)BufGetCharacter(styleBuf, pos);
3444 if (style == textD->unfinishedStyle) {
3445 /* encountered "unfinished" style, trigger parsing */
3446 (textD->unfinishedHighlightCB)(textD, pos, textD->highlightCBArg);
3447 style = (unsigned char)BufGetCharacter(styleBuf, pos);
3450 return stringWidth(textD, expChar, charLen, style);
3454 ** Finds both the end of the current line and the start of the next line. Why?
3455 ** In continuous wrap mode, if you need to know both, figuring out one from the
3456 ** other can be expensive or error prone. The problem comes when there's a
3457 ** trailing space or tab just before the end of the buffer. To translate an
3458 ** end of line value to or from the next lines start value, you need to know
3459 ** whether the trailing space or tab is being used as a line break or just a
3460 ** normal character, and to find that out would otherwise require counting all
3461 ** the way back to the beginning of the line.
3463 static void findLineEnd(textDisp *textD, int startPos, int startPosIsLineStart,
3464 int *lineEnd, int *nextLineStart)
3466 int retLines, retLineStart;
3468 /* if we're not wrapping use more efficient BufEndOfLine */
3469 if (!textD->continuousWrap) {
3470 *lineEnd = BufEndOfLine(textD->buffer, startPos);
3471 *nextLineStart = min(textD->buffer->length, *lineEnd + 1);
3472 return;
3475 /* use the wrapped line counter routine to count forward one line */
3476 wrappedLineCounter(textD, textD->buffer, startPos, textD->buffer->length,
3477 1, startPosIsLineStart, 0, nextLineStart, &retLines,
3478 &retLineStart, lineEnd);
3479 return;
3483 ** Line breaks in continuous wrap mode usually happen at newlines or
3484 ** whitespace. This line-terminating character is not included in line
3485 ** width measurements and has a special status as a non-visible character.
3486 ** However, lines with no whitespace are wrapped without the benefit of a
3487 ** line terminating character, and this distinction causes endless trouble
3488 ** with all of the text display code which was originally written without
3489 ** continuous wrap mode and always expects to wrap at a newline character.
3491 ** Given the position of the end of the line, as returned by TextDEndOfLine
3492 ** or BufEndOfLine, this returns true if there is a line terminating
3493 ** character, and false if there's not. On the last character in the
3494 ** buffer, this function can't tell for certain whether a trailing space was
3495 ** used as a wrap point, and just guesses that it wasn't. So if an exact
3496 ** accounting is necessary, don't use this function.
3498 static int wrapUsesCharacter(textDisp *textD, int lineEndPos)
3500 char c;
3502 if (!textD->continuousWrap || lineEndPos == textD->buffer->length)
3503 return True;
3505 c = BufGetCharacter(textD->buffer, lineEndPos);
3506 return c == '\n' || ((c == '\t' || c == ' ') &&
3507 lineEndPos + 1 != textD->buffer->length);
3511 ** Decide whether the user needs (or may need) a horizontal scroll bar,
3512 ** and manage or unmanage the scroll bar widget accordingly. The H.
3513 ** scroll bar is only hidden in continuous wrap mode when it's absolutely
3514 ** certain that the user will not need it: when wrapping is set
3515 ** to the window edge, or when the wrap margin is strictly less than
3516 ** the longest possible line.
3518 static void hideOrShowHScrollBar(textDisp *textD)
3520 if (textD->continuousWrap && (textD->wrapMargin == 0 || textD->wrapMargin *
3521 textD->fontStruct->max_bounds.width < textD->width))
3522 XtUnmanageChild(textD->hScrollBar);
3523 else
3524 XtManageChild(textD->hScrollBar);
3528 ** Return true if the selection "sel" is rectangular, and touches a
3529 ** buffer position withing "rangeStart" to "rangeEnd"
3531 static int rangeTouchesRectSel(selection *sel, int rangeStart, int rangeEnd)
3533 return sel->selected && sel->rectangular && sel->end >= rangeStart &&
3534 sel->start <= rangeEnd;
3538 ** Extend the range of a redraw request (from *start to *end) with additional
3539 ** redraw requests resulting from changes to the attached style buffer (which
3540 ** contains auxiliary information for coloring or styling text).
3542 static void extendRangeForStyleMods(textDisp *textD, int *start, int *end)
3544 selection *sel = &textD->styleBuffer->primary;
3545 int extended = False;
3547 /* The peculiar protocol used here is that modifications to the style
3548 buffer are marked by selecting them with the buffer's primary selection.
3549 The style buffer is usually modified in response to a modify callback on
3550 the text buffer BEFORE textDisp.c's modify callback, so that it can keep
3551 the style buffer in step with the text buffer. The style-update
3552 callback can't just call for a redraw, because textDisp hasn't processed
3553 the original text changes yet. Anyhow, to minimize redrawing and to
3554 avoid the complexity of scheduling redraws later, this simple protocol
3555 tells the text display's buffer modify callback to extend it's redraw
3556 range to show the text color/and font changes as well. */
3557 if (sel->selected) {
3558 if (sel->start < *start) {
3559 *start = sel->start;
3560 extended = True;
3562 if (sel->end > *end) {
3563 *end = sel->end;
3564 extended = True;
3568 /* If the selection was extended due to a style change, and some of the
3569 fonts don't match in spacing, extend redraw area to end of line to
3570 redraw characters exposed by possible font size changes */
3571 if (textD->fixedFontWidth == -1 && extended)
3572 *end = BufEndOfLine(textD->buffer, *end) + 1;
3575 /********************** Backlight Functions ******************************/
3577 ** Allocate a read-only (shareable) colormap cell for a named color, from the
3578 ** the default colormap of the screen on which the widget (w) is displayed. If
3579 ** the colormap is full and there's no suitable substitute, print an error on
3580 ** stderr, and return the widget's background color as a backup.
3582 static Pixel allocBGColor(Widget w, char *colorName, int *ok)
3584 int r,g,b;
3585 *ok = 1;
3586 return AllocColor(w, colorName, &r, &g, &b);
3589 Pixel getRangesetColor(textDisp *textD, int ind, Pixel bground)
3591 textBuffer *buf;
3592 RangesetTable *tab;
3593 Pixel color;
3594 char *color_name;
3595 int valid;
3597 if (ind > 0) {
3598 ind--;
3599 buf = textD->buffer;
3600 tab = buf->rangesetTable;
3602 valid = RangesetTableGetColorValid(tab, ind, &color);
3603 if (valid == 0) {
3604 color_name = RangesetTableGetColorName(tab, ind);
3605 if (color_name)
3606 color = allocBGColor(textD->w, color_name, &valid);
3607 RangesetTableAssignColorPixel(tab, ind, color, valid);
3609 if (valid > 0) {
3610 return color;
3613 return bground;
3617 ** Read the background color class specification string in str, allocating the
3618 ** necessary colors, and allocating and setting up the character->class_no and
3619 ** class_no->pixel map arrays, returned via *pp_bgClass and *pp_bgClassPixel
3620 ** respectively.
3621 ** Note: the allocation of class numbers could be more intelligent: there can
3622 ** never be more than 256 of these (one per character); but I don't think
3623 ** there'll be a pressing need. I suppose the scanning of the specification
3624 ** could be better too, but then, who cares!
3626 void TextDSetupBGClasses(Widget w, XmString str, Pixel **pp_bgClassPixel,
3627 unsigned char **pp_bgClass, Pixel bgPixelDefault)
3629 unsigned char bgClass[256];
3630 Pixel bgClassPixel[256];
3631 int class_no = 0;
3632 char *semicol;
3633 char *s = (char *)str;
3634 size_t was_semicol;
3635 int lo, hi, dummy;
3636 char *pos;
3637 Boolean is_good = True;
3639 XtFree((char *)*pp_bgClass);
3640 XtFree((char *)*pp_bgClassPixel);
3642 *pp_bgClassPixel = NULL;
3643 *pp_bgClass = NULL;
3645 if (!s)
3646 return;
3648 /* default for all chars is class number zero, for standard background */
3649 memset(bgClassPixel, 0, sizeof bgClassPixel);
3650 memset(bgClass, 0, sizeof bgClass);
3651 bgClassPixel[0] = bgPixelDefault;
3652 /* since class no == 0 in a "style" has no set bits in BACKLIGHT_MASK
3653 (see styleOfPos()), when drawString() is called for text with a
3654 backlight class no of zero, bgClassPixel[0] is never consulted, and
3655 the default background color is chosen. */
3657 /* The format of the class string s is:
3658 low[-high]{,low[-high]}:color{;low-high{,low[-high]}:color}
3660 32-255:#f0f0f0;1-31,127:red;128-159:orange;9-13:#e5e5e5
3661 where low and high represent a character range between ordinal
3662 ASCII values. Using strtol() allows automatic octal, dec and hex
3663 reading of low and high. The example format sets backgrounds as follows:
3664 char 1 - 8 colored red (control characters)
3665 char 9 - 13 colored #e5e5e5 (isspace() control characters)
3666 char 14 - 31 colored red (control characters)
3667 char 32 - 126 colored #f0f0f0
3668 char 127 colored red (delete character)
3669 char 128 - 159 colored orange ("shifted" control characters)
3670 char 160 - 255 colored #f0f0f0
3671 Notice that some of the later ranges overwrite the class values defined
3672 for earlier ones (eg the first clause, 32-255:#f0f0f0 sets the DEL
3673 character background color to #f0f0f0; it is then set to red by the
3674 clause 1-31,127:red). */
3676 while (s && class_no < 255) {
3677 class_no++; /* simple class alloc scheme */
3678 was_semicol = 0;
3679 is_good = True;
3680 if ((semicol = (char *)strchr(s, ';'))) {
3681 *semicol = '\0'; /* null-terminate low[-high]:color clause */
3682 was_semicol = 1;
3685 /* loop over ranges before the color spec, assigning the characters
3686 in the ranges to the current class number */
3687 for (lo = hi = strtol(s, &pos, 0);
3688 is_good;
3689 lo = hi = strtol(pos + 1, &pos, 0)) {
3690 if (pos && *pos == '-')
3691 hi = strtol(pos + 1, &pos, 0); /* get end of range */
3692 is_good = (pos && 0 <= lo && lo <= hi && hi <= 255);
3693 if (is_good)
3694 while (lo <= hi)
3695 bgClass[lo++] = (unsigned char)class_no;
3696 if (*pos != ',')
3697 break;
3699 if ((is_good = (is_good && *pos == ':'))) {
3700 is_good = (*pos++ != '\0'); /* pos now points to color */
3701 bgClassPixel[class_no] = allocBGColor(w, pos, &dummy);
3703 if (!is_good) {
3704 /* complain? this class spec clause (in string s) was faulty */
3707 /* end of loop iterator clauses */
3708 if (was_semicol)
3709 *semicol = ';'; /* un-null-terminate low[-high]:color clause */
3710 s = semicol + was_semicol;
3712 /* when we get here, we've set up our class table and class-to-pixel table
3713 in local variables: now put them into the "real thing" */
3714 if (class_no) {
3715 class_no++; /* bigger than all valid class_nos */
3716 *pp_bgClass = (unsigned char *)XtMalloc(256);
3717 *pp_bgClassPixel = (Pixel *)XtMalloc(class_no * sizeof (Pixel));
3718 if (!*pp_bgClass || !*pp_bgClassPixel) {
3719 XtFree((char *)*pp_bgClass);
3720 XtFree((char *)*pp_bgClassPixel);
3721 return;
3723 memcpy(*pp_bgClass, bgClass, 256);
3724 memcpy(*pp_bgClassPixel, bgClassPixel, class_no * sizeof (Pixel));