Added Motif exception to license
[nedit.git] / source / textDisp.c
blob4c0c25f69377f91a336edf0d0d259a5ac64a5d07
1 static const char CVSID[] = "$Id: textDisp.c,v 1.61 2004/10/18 19:27:24 arnef 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. In addition, you may distribute version of this program linked to *
12 * Motif or Open Motif. See README for details. *
13 * *
14 * This software is distributed in the hope that it will be useful, but WITHOUT *
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
17 * for more details. *
18 * *
19 * You should have received a copy of the GNU General Public License along with *
20 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
21 * Place, Suite 330, Boston, MA 02111-1307 USA *
22 * *
23 * Nirvana Text Editor *
24 * June 15, 1995 *
25 * *
26 * Written by Mark Edel *
27 * *
28 *******************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 #include "../config.h"
32 #endif
34 #include "textDisp.h"
35 #include "textBuf.h"
36 #include "text.h"
37 #include "textP.h"
38 #include "nedit.h"
39 #include "calltips.h"
40 #include "highlight.h"
41 #include "rangeset.h"
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <limits.h>
47 #ifdef VMS
48 #include "../util/VMSparam.h"
49 #else
50 #ifndef __MVS__
51 #include <sys/param.h>
52 #endif
53 #endif /*VMS*/
55 #include <Xm/Xm.h>
56 #include <Xm/ScrolledW.h>
57 #include <Xm/ScrollBar.h>
58 #include <Xm/Label.h>
59 #include <X11/Shell.h>
61 #ifdef HAVE_DEBUG_H
62 #include "../debug.h"
63 #endif
65 #define TOP_MARGIN 1
66 #define BOTTOM_MARGIN 1
67 #define LEFT_MARGIN 3
68 #define RIGHT_MARGIN 3
70 /* Masks for text drawing methods. These are or'd together to form an
71 integer which describes what drawing calls to use to draw a string */
72 #define FILL_SHIFT 8
73 #define SECONDARY_SHIFT 9
74 #define PRIMARY_SHIFT 10
75 #define HIGHLIGHT_SHIFT 11
76 #define STYLE_LOOKUP_SHIFT 0
77 #define BACKLIGHT_SHIFT 12
79 #define FILL_MASK (1 << FILL_SHIFT)
80 #define SECONDARY_MASK (1 << SECONDARY_SHIFT)
81 #define PRIMARY_MASK (1 << PRIMARY_SHIFT)
82 #define HIGHLIGHT_MASK (1 << HIGHLIGHT_SHIFT)
83 #define STYLE_LOOKUP_MASK (0xff << STYLE_LOOKUP_SHIFT)
84 #define BACKLIGHT_MASK (0xff << BACKLIGHT_SHIFT)
86 #define RANGESET_SHIFT (20)
87 #define RANGESET_MASK (0x3F << RANGESET_SHIFT)
89 /* If you use both 32-Bit Style mask layout:
90 Bits +----------------+----------------+----------------+----------------+
91 hex |1F1E1D1C1B1A1918|1716151413121110| F E D C B A 9 8| 7 6 5 4 3 2 1 0|
92 dec |3130292827262524|2322212019181716|151413121110 9 8| 7 6 5 4 3 2 1 0|
93 +----------------+----------------+----------------+----------------+
94 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|
95 +----------------+----------------+----------------+----------------+
96 where: s - style lookup value (8 bits)
97 F - fill (1 bit)
98 2 - secondary selection (1 bit)
99 1 - primary selection (1 bit)
100 H - highlight (1 bit)
101 b - backlighting index (8 bits)
102 r - rangeset index (6 bits)
103 This leaves 6 "unused" bits */
105 /* Maximum displayable line length (how many characters will fit across the
106 widest window). This amount of memory is temporarily allocated from the
107 stack in the redisplayLine routine for drawing strings */
108 #define MAX_DISP_LINE_LEN 1000
110 /* Macro for getting the TextPart from a textD */
111 #define TEXT_OF_TEXTD(t) (((TextWidget)((t)->w))->text)
113 enum positionTypes {CURSOR_POS, CHARACTER_POS};
115 static void updateLineStarts(textDisp *textD, int pos, int charsInserted,
116 int charsDeleted, int linesInserted, int linesDeleted, int *scrolled);
117 static void offsetLineStarts(textDisp *textD, int newTopLineNum);
118 static void calcLineStarts(textDisp *textD, int startLine, int endLine);
119 static void calcLastChar(textDisp *textD);
120 static int posToVisibleLineNum(textDisp *textD, int pos, int *lineNum);
121 static void redisplayLine(textDisp *textD, int visLineNum, int leftClip,
122 int rightClip, int leftCharIndex, int rightCharIndex);
123 static void drawString(textDisp *textD, int style, int x, int y, int toX,
124 char *string, int nChars);
125 static void clearRect(textDisp *textD, GC gc, int x, int y,
126 int width, int height);
127 static void drawCursor(textDisp *textD, int x, int y);
128 static int styleOfPos(textDisp *textD, int lineStartPos,
129 int lineLen, int lineIndex, int dispIndex, int thisChar);
130 static int stringWidth(textDisp *textD, char *string, int length, int style);
131 static int inSelection(selection *sel, int pos, int lineStartPos,
132 int dispIndex);
133 static int xyToPos(textDisp *textD, int x, int y, int posType);
134 static void xyToUnconstrainedPos(textDisp *textD, int x, int y, int *row,
135 int *column, int posType);
136 static void bufPreDeleteCB(int pos, int nDeleted, void *cbArg);
137 static void bufModifiedCB(int pos, int nInserted, int nDeleted,
138 int nRestyled, char *deletedText, void *cbArg);
139 static void setScroll(textDisp *textD, int topLineNum, int horizOffset,
140 int updateVScrollBar, int updateHScrollBar);
141 static void hScrollCB(Widget w, XtPointer clientData, XtPointer callData);
142 static void vScrollCB(Widget w, XtPointer clientData, XtPointer callData);
143 static void visibilityEH(Widget w, XtPointer data, XEvent *event,
144 Boolean *continueDispatch);
145 static void redrawLineNumbers(textDisp *textD, int clearAll);
146 static void updateVScrollBarRange(textDisp *textD);
147 static int updateHScrollBarRange(textDisp *textD);
148 static int max(int i1, int i2);
149 static int min(int i1, int i2);
150 static int countLines(char *string);
151 static int measureVisLine(textDisp *textD, int visLineNum);
152 static int emptyLinesVisible(textDisp *textD);
153 static void blankCursorProtrusions(textDisp *textD);
154 static void allocateFixedFontGCs(textDisp *textD, XFontStruct *fontStruct,
155 Pixel bgPixel, Pixel fgPixel, Pixel selectFGPixel, Pixel selectBGPixel,
156 Pixel highlightFGPixel, Pixel highlightBGPixel, Pixel lineNumFGPixel);
157 static GC allocateGC(Widget w, unsigned long valueMask,
158 unsigned long foreground, unsigned long background, Font font,
159 unsigned long dynamicMask, unsigned long dontCareMask);
160 static void releaseGC(Widget w, GC gc);
161 static void resetClipRectangles(textDisp *textD);
162 static int visLineLength(textDisp *textD, int visLineNum);
163 static void measureDeletedLines(textDisp *textD, int pos, int nDeleted);
164 static void findWrapRange(textDisp *textD, char *deletedText, int pos,
165 int nInserted, int nDeleted, int *modRangeStart, int *modRangeEnd,
166 int *linesInserted, int *linesDeleted);
167 static void wrappedLineCounter(textDisp *textD, textBuffer *buf, int startPos,
168 int maxPos, int maxLines, int startPosIsLineStart, int styleBufOffset,
169 int *retPos, int *retLines, int *retLineStart, int *retLineEnd);
170 static void findLineEnd(textDisp *textD, int startPos, int startPosIsLineStart,
171 int *lineEnd, int *nextLineStart);
172 static int wrapUsesCharacter(textDisp *textD, int lineEndPos);
173 static void hideOrShowHScrollBar(textDisp *textD);
174 static int rangeTouchesRectSel(selection *sel, int rangeStart, int rangeEnd);
175 static void extendRangeForStyleMods(textDisp *textD, int *start, int *end);
176 static int getAbsTopLineNum(textDisp *textD);
177 static void offsetAbsLineNum(textDisp *textD, int oldFirstChar);
178 static int maintainingAbsTopLineNum(textDisp *textD);
179 static void resetAbsLineNum(textDisp *textD);
180 static int measurePropChar(textDisp *textD, char c, int colNum, int pos);
181 static Pixel allocBGColor(Widget w, char *colorName, int *ok);
182 static Pixel getRangesetColor(textDisp *textD, int ind, Pixel bground);
184 textDisp *TextDCreate(Widget widget, Widget hScrollBar, Widget vScrollBar,
185 Position left, Position top, Position width, Position height,
186 Position lineNumLeft, Position lineNumWidth, textBuffer *buffer,
187 XFontStruct *fontStruct, Pixel bgPixel, Pixel fgPixel,
188 Pixel selectFGPixel, Pixel selectBGPixel, Pixel highlightFGPixel,
189 Pixel highlightBGPixel, Pixel cursorFGPixel, Pixel lineNumFGPixel,
190 int continuousWrap, int wrapMargin, XmString bgClassString,
191 Pixel calltipFGPixel, Pixel calltipBGPixel)
193 textDisp *textD;
194 XGCValues gcValues;
195 int i;
197 textD = (textDisp *)XtMalloc(sizeof(textDisp));
198 textD->w = widget;
199 textD->top = top;
200 textD->left = left;
201 textD->width = width;
202 textD->height = height;
203 textD->cursorOn = True;
204 textD->cursorPos = 0;
205 textD->cursorX = -100;
206 textD->cursorY = -100;
207 textD->cursorToHint = NO_HINT;
208 textD->cursorStyle = NORMAL_CURSOR;
209 textD->cursorPreferredCol = -1;
210 textD->buffer = buffer;
211 textD->firstChar = 0;
212 textD->lastChar = 0;
213 textD->nBufferLines = 0;
214 textD->topLineNum = 1;
215 textD->absTopLineNum = 1;
216 textD->needAbsTopLineNum = False;
217 textD->horizOffset = 0;
218 textD->visibility = VisibilityUnobscured;
219 textD->hScrollBar = hScrollBar;
220 textD->vScrollBar = vScrollBar;
221 textD->fontStruct = fontStruct;
222 textD->ascent = fontStruct->ascent;
223 textD->descent = fontStruct->descent;
224 textD->fixedFontWidth = fontStruct->min_bounds.width ==
225 fontStruct->max_bounds.width ? fontStruct->min_bounds.width : -1;
226 textD->styleBuffer = NULL;
227 textD->styleTable = NULL;
228 textD->nStyles = 0;
229 textD->bgPixel = bgPixel;
230 textD->fgPixel = fgPixel;
231 textD->selectFGPixel = selectFGPixel;
232 textD->highlightFGPixel = highlightFGPixel;
233 textD->selectBGPixel = selectBGPixel;
234 textD->highlightBGPixel = highlightBGPixel;
235 textD->lineNumFGPixel = lineNumFGPixel;
236 textD->cursorFGPixel = cursorFGPixel;
237 textD->wrapMargin = wrapMargin;
238 textD->continuousWrap = continuousWrap;
239 allocateFixedFontGCs(textD, fontStruct, bgPixel, fgPixel, selectFGPixel,
240 selectBGPixel, highlightFGPixel, highlightBGPixel, lineNumFGPixel);
241 textD->styleGC = allocateGC(textD->w, 0, 0, 0, fontStruct->fid,
242 GCClipMask|GCForeground|GCBackground, GCArcMode);
243 textD->lineNumLeft = lineNumLeft;
244 textD->lineNumWidth = lineNumWidth;
245 textD->nVisibleLines = (height - 1) / (textD->ascent + textD->descent) + 1;
246 gcValues.foreground = cursorFGPixel;
247 textD->cursorFGGC = XtGetGC(widget, GCForeground, &gcValues);
248 textD->lineStarts = (int *)XtMalloc(sizeof(int) * textD->nVisibleLines);
249 textD->lineStarts[0] = 0;
250 textD->calltipW = NULL;
251 textD->calltipShell = NULL;
252 textD->calltip.ID = 0;
253 textD->calltipFGPixel = calltipFGPixel;
254 textD->calltipBGPixel = calltipBGPixel;
255 for (i=1; i<textD->nVisibleLines; i++)
256 textD->lineStarts[i] = -1;
257 textD->bgClassPixel = NULL;
258 textD->bgClass = NULL;
259 TextDSetupBGClasses(widget, bgClassString, &textD->bgClassPixel,
260 &textD->bgClass, bgPixel);
261 textD->suppressResync = 0;
262 textD->nLinesDeleted = 0;
263 textD->modifyingTabDist = 0;
264 textD->pointerHidden = False;
265 textD->graphicsExposeQueue = NULL;
267 /* Attach an event handler to the widget so we can know the visibility
268 (used for choosing the fastest drawing method) */
269 XtAddEventHandler(widget, VisibilityChangeMask, False,
270 visibilityEH, textD);
272 /* Attach the callback to the text buffer for receiving modification
273 information */
274 if (buffer != NULL) {
275 BufAddModifyCB(buffer, bufModifiedCB, textD);
276 BufAddPreDeleteCB(buffer, bufPreDeleteCB, textD);
279 /* Initialize the scroll bars and attach movement callbacks */
280 if (vScrollBar != NULL) {
281 XtVaSetValues(vScrollBar, XmNminimum, 1, XmNmaximum, 2,
282 XmNsliderSize, 1, XmNrepeatDelay, 10, XmNvalue, 1, NULL);
283 XtAddCallback(vScrollBar, XmNdragCallback, vScrollCB, (XtPointer)textD);
284 XtAddCallback(vScrollBar, XmNvalueChangedCallback, vScrollCB,
285 (XtPointer)textD);
287 if (hScrollBar != NULL) {
288 XtVaSetValues(hScrollBar, XmNminimum, 0, XmNmaximum, 1,
289 XmNsliderSize, 1, XmNrepeatDelay, 10, XmNvalue, 0,
290 XmNincrement, fontStruct->max_bounds.width, NULL);
291 XtAddCallback(hScrollBar, XmNdragCallback, hScrollCB, (XtPointer)textD);
292 XtAddCallback(hScrollBar, XmNvalueChangedCallback, hScrollCB,
293 (XtPointer)textD);
296 /* Update the display to reflect the contents of the buffer */
297 if (buffer != NULL)
298 bufModifiedCB(0, buffer->length, 0, 0, NULL, textD);
300 /* Decide if the horizontal scroll bar needs to be visible */
301 hideOrShowHScrollBar(textD);
303 return textD;
307 ** Free a text display and release its associated memory. Note, the text
308 ** BUFFER that the text display displays is a separate entity and is not
309 ** freed, nor are the style buffer or style table.
311 void TextDFree(textDisp *textD)
313 BufRemoveModifyCB(textD->buffer, bufModifiedCB, textD);
314 BufRemovePreDeleteCB(textD->buffer, bufPreDeleteCB, textD);
315 releaseGC(textD->w, textD->gc);
316 releaseGC(textD->w, textD->selectGC);
317 releaseGC(textD->w, textD->highlightGC);
318 releaseGC(textD->w, textD->selectBGGC);
319 releaseGC(textD->w, textD->highlightBGGC);
320 releaseGC(textD->w, textD->styleGC);
321 releaseGC(textD->w, textD->lineNumGC);
322 XtFree((char *)textD->lineStarts);
323 while (TextDPopGraphicExposeQueueEntry(textD)) {
325 XtFree((char *)textD->bgClassPixel);
326 XtFree((char *)textD->bgClass);
327 XtFree((char *)textD);
331 ** Attach a text buffer to display, replacing the current buffer (if any)
333 void TextDSetBuffer(textDisp *textD, textBuffer *buffer)
335 /* If the text display is already displaying a buffer, clear it off
336 of the display and remove our callback from it */
337 if (textD->buffer != NULL) {
338 bufModifiedCB(0, 0, textD->buffer->length, 0, NULL, textD);
339 BufRemoveModifyCB(textD->buffer, bufModifiedCB, textD);
340 BufRemovePreDeleteCB(textD->buffer, bufPreDeleteCB, textD);
343 /* Add the buffer to the display, and attach a callback to the buffer for
344 receiving modification information when the buffer contents change */
345 textD->buffer = buffer;
346 BufAddModifyCB(buffer, bufModifiedCB, textD);
347 BufAddPreDeleteCB(buffer, bufPreDeleteCB, textD);
349 /* Update the display */
350 bufModifiedCB(0, buffer->length, 0, 0, NULL, textD);
354 ** return the displayed text buffer
356 textBuffer *TextDGetBuffer(textDisp *textD)
358 return textD->buffer;
362 ** Attach (or remove) highlight information in text display and redisplay.
363 ** Highlighting information consists of a style buffer which parallels the
364 ** normal text buffer, but codes font and color information for the display;
365 ** a style table which translates style buffer codes (indexed by buffer
366 ** character - 65 (ASCII code for 'A')) into fonts and colors; and a callback
367 ** mechanism for as-needed highlighting, triggered by a style buffer entry of
368 ** "unfinishedStyle". Style buffer can trigger additional redisplay during
369 ** a normal buffer modification if the buffer contains a primary selection
370 ** (see extendRangeForStyleMods for more information on this protocol).
372 ** Style buffers, tables and their associated memory are managed by the caller.
374 void TextDAttachHighlightData(textDisp *textD, textBuffer *styleBuffer,
375 styleTableEntry *styleTable, int nStyles, char unfinishedStyle,
376 unfinishedStyleCBProc unfinishedHighlightCB, void *cbArg)
378 textD->styleBuffer = styleBuffer;
379 textD->styleTable = styleTable;
380 textD->nStyles = nStyles;
381 textD->unfinishedStyle = unfinishedStyle;
382 textD->unfinishedHighlightCB = unfinishedHighlightCB;
383 textD->highlightCBArg = cbArg;
385 /* Call TextDSetFont to combine font information from style table and
386 primary font, adjust font-related parameters, and then redisplay */
387 TextDSetFont(textD, textD->fontStruct);
391 /* Change the (non syntax-highlit) colors */
392 void TextDSetColors(textDisp *textD, Pixel textFgP, Pixel textBgP,
393 Pixel selectFgP, Pixel selectBgP, Pixel hiliteFgP, Pixel hiliteBgP,
394 Pixel lineNoFgP, Pixel cursorFgP)
396 XGCValues values;
397 Display *d = XtDisplay(textD->w);
399 /* Update the stored pixels */
400 textD->fgPixel = textFgP;
401 textD->bgPixel = textBgP;
402 textD->selectFGPixel = selectFgP;
403 textD->selectBGPixel = selectBgP;
404 textD->highlightFGPixel = hiliteFgP;
405 textD->highlightBGPixel = hiliteBgP;
406 textD->lineNumFGPixel = lineNoFgP;
407 textD->cursorFGPixel = cursorFgP;
409 releaseGC(textD->w, textD->gc);
410 releaseGC(textD->w, textD->selectGC);
411 releaseGC(textD->w, textD->selectBGGC);
412 releaseGC(textD->w, textD->highlightGC);
413 releaseGC(textD->w, textD->highlightBGGC);
414 releaseGC(textD->w, textD->lineNumGC);
415 allocateFixedFontGCs(textD, textD->fontStruct, textBgP, textFgP, selectFgP,
416 selectBgP, hiliteFgP, hiliteBgP, lineNoFgP);
418 /* Change the cursor GC (the cursor GC is not shared). */
419 values.foreground = cursorFgP;
420 XChangeGC( d, textD->cursorFGGC, GCForeground, &values );
422 /* Redisplay */
423 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
424 textD->height);
425 redrawLineNumbers(textD, True);
429 ** Change the (non highlight) font
431 void TextDSetFont(textDisp *textD, XFontStruct *fontStruct)
433 Display *display = XtDisplay(textD->w);
434 int i, maxAscent = fontStruct->ascent, maxDescent = fontStruct->descent;
435 int width, height, fontWidth;
436 Pixel bgPixel, fgPixel, selectFGPixel, selectBGPixel;
437 Pixel highlightFGPixel, highlightBGPixel, lineNumFGPixel;
438 XGCValues values;
439 XFontStruct *styleFont;
441 /* If font size changes, cursor will be redrawn in a new position */
442 blankCursorProtrusions(textD);
444 /* If there is a (syntax highlighting) style table in use, find the new
445 maximum font height for this text display */
446 for (i=0; i<textD->nStyles; i++) {
447 styleFont = textD->styleTable[i].font;
448 if (styleFont != NULL && styleFont->ascent > maxAscent)
449 maxAscent = styleFont->ascent;
450 if (styleFont != NULL && styleFont->descent > maxDescent)
451 maxDescent = styleFont->descent;
453 textD->ascent = maxAscent;
454 textD->descent = maxDescent;
456 /* If all of the current fonts are fixed and match in width, compute */
457 fontWidth = fontStruct->max_bounds.width;
458 if (fontWidth != fontStruct->min_bounds.width)
459 fontWidth = -1;
460 else {
461 for (i=0; i<textD->nStyles; i++) {
462 styleFont = textD->styleTable[i].font;
463 if (styleFont != NULL &&
464 (styleFont->max_bounds.width != fontWidth ||
465 styleFont->max_bounds.width != styleFont->min_bounds.width))
466 fontWidth = -1;
469 textD->fixedFontWidth = fontWidth;
471 /* Don't let the height dip below one line, or bad things can happen */
472 if (textD->height < maxAscent + maxDescent)
473 textD->height = maxAscent + maxDescent;
475 /* Change the font. In most cases, this means re-allocating the
476 affected GCs (they are shared with other widgets, and if the primary
477 font changes, must be re-allocated to change it). Unfortunately,
478 this requres recovering all of the colors from the existing GCs */
479 textD->fontStruct = fontStruct;
480 XGetGCValues(display, textD->gc, GCForeground|GCBackground, &values);
481 fgPixel = values.foreground;
482 bgPixel = values.background;
483 XGetGCValues(display, textD->selectGC, GCForeground|GCBackground, &values);
484 selectFGPixel = values.foreground;
485 selectBGPixel = values.background;
486 XGetGCValues(display, textD->highlightGC,GCForeground|GCBackground,&values);
487 highlightFGPixel = values.foreground;
488 highlightBGPixel = values.background;
489 XGetGCValues(display, textD->lineNumGC, GCForeground, &values);
490 lineNumFGPixel = values.foreground;
491 releaseGC(textD->w, textD->gc);
492 releaseGC(textD->w, textD->selectGC);
493 releaseGC(textD->w, textD->highlightGC);
494 releaseGC(textD->w, textD->selectBGGC);
495 releaseGC(textD->w, textD->highlightBGGC);
496 releaseGC(textD->w, textD->lineNumGC);
497 allocateFixedFontGCs(textD, fontStruct, bgPixel, fgPixel, selectFGPixel,
498 selectBGPixel, highlightFGPixel, highlightBGPixel, lineNumFGPixel);
499 XSetFont(display, textD->styleGC, fontStruct->fid);
501 /* Do a full resize to force recalculation of font related parameters */
502 width = textD->width;
503 height = textD->height;
504 textD->width = textD->height = 0;
505 TextDResize(textD, width, height);
507 /* if the shell window doesn't get resized, and the new fonts are
508 of smaller sizes, sometime we get some residual text on the
509 blank space at the bottom part of text area. Clear it here. */
510 clearRect(textD, textD->gc, textD->left,
511 textD->top + textD->height - maxAscent - maxDescent,
512 textD->width, maxAscent + maxDescent);
514 /* Redisplay */
515 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
516 textD->height);
518 /* Clean up line number area in case spacing has changed */
519 redrawLineNumbers(textD, True);
522 int TextDMinFontWidth(textDisp *textD, Boolean considerStyles)
524 int fontWidth = textD->fontStruct->max_bounds.width;
525 int i;
527 if (considerStyles) {
528 for (i = 0; i < textD->nStyles; ++i) {
529 int thisWidth = (textD->styleTable[i].font)->min_bounds.width;
530 if (thisWidth < fontWidth) {
531 fontWidth = thisWidth;
535 return(fontWidth);
538 int TextDMaxFontWidth(textDisp *textD, Boolean considerStyles)
540 int fontWidth = textD->fontStruct->max_bounds.width;
541 int i;
543 if (considerStyles) {
544 for (i = 0; i < textD->nStyles; ++i) {
545 int thisWidth = (textD->styleTable[i].font)->max_bounds.width;
546 if (thisWidth > fontWidth) {
547 fontWidth = thisWidth;
551 return(fontWidth);
555 ** Change the size of the displayed text area
557 void TextDResize(textDisp *textD, int width, int height)
559 int oldVisibleLines = textD->nVisibleLines;
560 int canRedraw = XtWindow(textD->w) != 0;
561 int newVisibleLines = height / (textD->ascent + textD->descent);
562 int redrawAll = False;
563 int oldWidth = textD->width;
564 int exactHeight = height - height % (textD->ascent + textD->descent);
566 textD->width = width;
567 textD->height = height;
569 /* In continuous wrap mode, a change in width affects the total number of
570 lines in the buffer, and can leave the top line number incorrect, and
571 the top character no longer pointing at a valid line start */
572 if (textD->continuousWrap && textD->wrapMargin==0 && width!=oldWidth) {
573 int oldFirstChar = textD->firstChar;
574 textD->nBufferLines = TextDCountLines(textD, 0, textD->buffer->length,
575 True);
576 textD->firstChar = TextDStartOfLine(textD, textD->firstChar);
577 textD->topLineNum = TextDCountLines(textD, 0, textD->firstChar, True)+1;
578 redrawAll = True;
579 offsetAbsLineNum(textD, oldFirstChar);
582 /* reallocate and update the line starts array, which may have changed
583 size and/or contents. (contents can change in continuous wrap mode
584 when the width changes, even without a change in height) */
585 if (oldVisibleLines < newVisibleLines) {
586 XtFree((char *)textD->lineStarts);
587 textD->lineStarts = (int *)XtMalloc(sizeof(int) * newVisibleLines);
589 textD->nVisibleLines = newVisibleLines;
590 calcLineStarts(textD, 0, newVisibleLines);
591 calcLastChar(textD);
593 /* if the window became shorter, there may be partially drawn
594 text left at the bottom edge, which must be cleaned up */
595 if (canRedraw && oldVisibleLines>newVisibleLines && exactHeight!=height)
596 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->left,
597 textD->top + exactHeight, textD->width,
598 height - exactHeight, False);
600 /* if the window became taller, there may be an opportunity to display
601 more text by scrolling down */
602 if (canRedraw && oldVisibleLines < newVisibleLines && textD->topLineNum +
603 textD->nVisibleLines > textD->nBufferLines)
604 setScroll(textD, max(1, textD->nBufferLines - textD->nVisibleLines +
605 2 + TEXT_OF_TEXTD(textD).cursorVPadding),
606 textD->horizOffset, False, False);
608 /* Update the scroll bar page increment size (as well as other scroll
609 bar parameters. If updating the horizontal range caused scrolling,
610 redraw */
611 updateVScrollBarRange(textD);
612 if (updateHScrollBarRange(textD))
613 redrawAll = True;
615 /* If a full redraw is needed */
616 if (redrawAll && canRedraw)
617 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
618 textD->height);
620 /* Decide if the horizontal scroll bar needs to be visible */
621 hideOrShowHScrollBar(textD);
623 /* Refresh the line number display to draw more line numbers, or
624 erase extras */
625 redrawLineNumbers(textD, True);
627 /* Redraw the calltip */
628 TextDRedrawCalltip(textD, 0);
632 ** Refresh a rectangle of the text display. left and top are in coordinates of
633 ** the text drawing window
635 void TextDRedisplayRect(textDisp *textD, int left, int top, int width,
636 int height)
638 int fontHeight, firstLine, lastLine, line;
640 /* find the line number range of the display */
641 fontHeight = textD->ascent + textD->descent;
642 firstLine = (top - textD->top - fontHeight + 1) / fontHeight;
643 lastLine = (top + height - textD->top) / fontHeight;
645 /* If the graphics contexts are shared using XtAllocateGC, their
646 clipping rectangles may have changed since the last use */
647 resetClipRectangles(textD);
649 /* draw the lines of text */
650 for (line=firstLine; line<=lastLine; line++)
651 redisplayLine(textD, line, left, left+width, 0, INT_MAX);
653 /* draw the line numbers if exposed area includes them */
654 if (textD->lineNumWidth != 0 && left <= textD->lineNumLeft + textD->lineNumWidth)
655 redrawLineNumbers(textD, False);
659 ** Refresh all of the text between buffer positions "start" and "end"
660 ** not including the character at the position "end".
661 ** If end points beyond the end of the buffer, refresh the whole display
662 ** after pos, including blank lines which are not technically part of
663 ** any range of characters.
665 void TextDRedisplayRange(textDisp *textD, int start, int end)
667 int i, startLine, lastLine, startIndex, endIndex;
669 /* If the range is outside of the displayed text, just return */
670 if (end < textD->firstChar || (start > textD->lastChar &&
671 !emptyLinesVisible(textD)))
672 return;
674 /* Clean up the starting and ending values */
675 if (start < 0) start = 0;
676 if (start > textD->buffer->length) start = textD->buffer->length;
677 if (end < 0) end = 0;
678 if (end > textD->buffer->length) end = textD->buffer->length;
680 /* Get the starting and ending lines */
681 if (start < textD->firstChar)
682 start = textD->firstChar;
683 if (!posToVisibleLineNum(textD, start, &startLine))
684 startLine = textD->nVisibleLines - 1;
685 if (end >= textD->lastChar) {
686 lastLine = textD->nVisibleLines - 1;
687 } else {
688 if (!posToVisibleLineNum(textD, end, &lastLine)) {
689 /* shouldn't happen */
690 lastLine = textD->nVisibleLines - 1;
694 /* Get the starting and ending positions within the lines */
695 startIndex = textD->lineStarts[startLine] == -1 ? 0 :
696 start - textD->lineStarts[startLine];
697 if (end >= textD->lastChar)
698 endIndex = INT_MAX;
699 else if (textD->lineStarts[lastLine] == -1)
700 endIndex = 0;
701 else
702 endIndex = end - textD->lineStarts[lastLine];
704 /* Reset the clipping rectangles for the drawing GCs which are shared
705 using XtAllocateGC, and may have changed since the last use */
706 resetClipRectangles(textD);
708 /* If the starting and ending lines are the same, redisplay the single
709 line between "start" and "end" */
710 if (startLine == lastLine) {
711 redisplayLine(textD, startLine, 0, INT_MAX, startIndex, endIndex);
712 return;
715 /* Redisplay the first line from "start" */
716 redisplayLine(textD, startLine, 0, INT_MAX, startIndex, INT_MAX);
718 /* Redisplay the lines in between at their full width */
719 for (i=startLine+1; i<lastLine; i++)
720 redisplayLine(textD, i, 0, INT_MAX, 0, INT_MAX);
722 /* Redisplay the last line to "end" */
723 redisplayLine(textD, lastLine, 0, INT_MAX, 0, endIndex);
727 ** Set the scroll position of the text display vertically by line number and
728 ** horizontally by pixel offset from the left margin
730 void TextDSetScroll(textDisp *textD, int topLineNum, int horizOffset)
732 int sliderSize, sliderMax;
733 int vPadding = (int)(TEXT_OF_TEXTD(textD).cursorVPadding);
735 /* Limit the requested scroll position to allowable values */
736 if (topLineNum < 1)
737 topLineNum = 1;
738 else if ((topLineNum > textD->topLineNum) &&
739 (topLineNum > (textD->nBufferLines + 2 - textD->nVisibleLines +
740 vPadding)))
741 topLineNum = max(textD->topLineNum,
742 textD->nBufferLines + 2 - textD->nVisibleLines + vPadding);
743 XtVaGetValues(textD->hScrollBar, XmNmaximum, &sliderMax,
744 XmNsliderSize, &sliderSize, NULL);
745 if (horizOffset < 0)
746 horizOffset = 0;
747 if (horizOffset > sliderMax - sliderSize)
748 horizOffset = sliderMax - sliderSize;
750 setScroll(textD, topLineNum, horizOffset, True, True);
754 ** Get the current scroll position for the text display, in terms of line
755 ** number of the top line and horizontal pixel offset from the left margin
757 void TextDGetScroll(textDisp *textD, int *topLineNum, int *horizOffset)
759 *topLineNum = textD->topLineNum;
760 *horizOffset = textD->horizOffset;
764 ** Set the position of the text insertion cursor for text display "textD"
766 void TextDSetInsertPosition(textDisp *textD, int newPos)
768 /* make sure new position is ok, do nothing if it hasn't changed */
769 if (newPos == textD->cursorPos)
770 return;
771 if (newPos < 0) newPos = 0;
772 if (newPos > textD->buffer->length) newPos = textD->buffer->length;
774 /* cursor movement cancels vertical cursor motion column */
775 textD->cursorPreferredCol = -1;
777 /* erase the cursor at it's previous position */
778 TextDBlankCursor(textD);
780 /* draw it at its new position */
781 textD->cursorPos = newPos;
782 textD->cursorOn = True;
783 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos + 1);
786 void TextDBlankCursor(textDisp *textD)
788 if (!textD->cursorOn)
789 return;
791 blankCursorProtrusions(textD);
792 textD->cursorOn = False;
793 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
796 void TextDUnblankCursor(textDisp *textD)
798 if (!textD->cursorOn) {
799 textD->cursorOn = True;
800 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
804 void TextDSetCursorStyle(textDisp *textD, int style)
806 textD->cursorStyle = style;
807 blankCursorProtrusions(textD);
808 if (textD->cursorOn)
809 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos + 1);
812 void TextDSetWrapMode(textDisp *textD, int wrap, int wrapMargin)
814 textD->wrapMargin = wrapMargin;
815 textD->continuousWrap = wrap;
817 /* wrapping can change change the total number of lines, re-count */
818 textD->nBufferLines = TextDCountLines(textD, 0, textD->buffer->length,True);
820 /* changing wrap margins wrap or changing from wrapped mode to non-wrapped
821 can leave the character at the top no longer at a line start, and/or
822 change the line number */
823 textD->firstChar = TextDStartOfLine(textD, textD->firstChar);
824 textD->topLineNum = TextDCountLines(textD, 0, textD->firstChar, True) + 1;
825 resetAbsLineNum(textD);
827 /* update the line starts array */
828 calcLineStarts(textD, 0, textD->nVisibleLines);
829 calcLastChar(textD);
831 /* Update the scroll bar page increment size (as well as other scroll
832 bar parameters) */
833 updateVScrollBarRange(textD);
834 updateHScrollBarRange(textD);
836 /* Decide if the horizontal scroll bar needs to be visible */
837 hideOrShowHScrollBar(textD);
839 /* Do a full redraw */
840 TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left,
841 textD->height);
844 int TextDGetInsertPosition(textDisp *textD)
846 return textD->cursorPos;
850 ** Insert "text" at the current cursor location. This has the same
851 ** effect as inserting the text into the buffer using BufInsert and
852 ** then moving the insert position after the newly inserted text, except
853 ** that it's optimized to do less redrawing.
855 void TextDInsert(textDisp *textD, char *text)
857 int pos = textD->cursorPos;
859 textD->cursorToHint = pos + strlen(text);
860 BufInsert(textD->buffer, pos, text);
861 textD->cursorToHint = NO_HINT;
865 ** Insert "text" (which must not contain newlines), overstriking the current
866 ** cursor location.
868 void TextDOverstrike(textDisp *textD, char *text)
870 int startPos = textD->cursorPos;
871 textBuffer *buf = textD->buffer;
872 int lineStart = BufStartOfLine(buf, startPos);
873 int textLen = strlen(text);
874 int i, p, endPos, indent, startIndent, endIndent;
875 char *c, ch, *paddedText = NULL;
877 /* determine how many displayed character positions are covered */
878 startIndent = BufCountDispChars(textD->buffer, lineStart, startPos);
879 indent = startIndent;
880 for (c=text; *c!='\0'; c++)
881 indent += BufCharWidth(*c, indent, buf->tabDist, buf->nullSubsChar);
882 endIndent = indent;
884 /* find which characters to remove, and if necessary generate additional
885 padding to make up for removed control characters at the end */
886 indent=startIndent;
887 for (p=startPos; ; p++) {
888 if (p == buf->length)
889 break;
890 ch = BufGetCharacter(buf, p);
891 if (ch == '\n')
892 break;
893 indent += BufCharWidth(ch, indent, buf->tabDist, buf->nullSubsChar);
894 if (indent == endIndent) {
895 p++;
896 break;
897 } else if (indent > endIndent) {
898 if (ch != '\t') {
899 p++;
900 paddedText = XtMalloc(textLen + MAX_EXP_CHAR_LEN + 1);
901 strcpy(paddedText, text);
902 for (i=0; i<indent-endIndent; i++)
903 paddedText[textLen+i] = ' ';
904 paddedText[textLen+i] = '\0';
906 break;
909 endPos = p;
911 textD->cursorToHint = startPos + textLen;
912 BufReplace(buf, startPos, endPos, paddedText == NULL ? text : paddedText);
913 textD->cursorToHint = NO_HINT;
914 if (paddedText != NULL)
915 XtFree(paddedText);
919 ** Translate window coordinates to the nearest text cursor position.
921 int TextDXYToPosition(textDisp *textD, int x, int y)
923 return xyToPos(textD, x, y, CURSOR_POS);
927 ** Translate window coordinates to the nearest character cell.
929 int TextDXYToCharPos(textDisp *textD, int x, int y)
931 return xyToPos(textD, x, y, CHARACTER_POS);
935 ** Translate window coordinates to the nearest row and column number for
936 ** positioning the cursor. This, of course, makes no sense when the font
937 ** is proportional, since there are no absolute columns.
939 void TextDXYToUnconstrainedPosition(textDisp *textD, int x, int y, int *row,
940 int *column)
942 xyToUnconstrainedPos(textD, x, y, row, column, CURSOR_POS);
946 ** Translate line and column to the nearest row and column number for
947 ** positioning the cursor. This, of course, makes no sense when the font
948 ** is proportional, since there are no absolute columns.
950 int TextDLineAndColToPos(textDisp *textD, int lineNum, int column)
952 int i, lineEnd, charIndex, outIndex;
953 int lineStart=0, charLen=0;
954 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
956 /* Count lines */
957 if (lineNum < 1)
958 lineNum = 1;
959 lineEnd = -1;
960 for (i=1; i<=lineNum && lineEnd<textD->buffer->length; i++) {
961 lineStart = lineEnd + 1;
962 lineEnd = BufEndOfLine(textD->buffer, lineStart);
965 /* If line is beyond end of buffer, position at last character in buffer */
966 if ( lineNum >= i ) {
967 return lineEnd;
970 /* Start character index at zero */
971 charIndex=0;
973 /* Only have to count columns if column isn't zero (or negative) */
974 if (column > 0) {
975 /* Count columns, expanding each character */
976 lineStr = BufGetRange(textD->buffer, lineStart, lineEnd);
977 outIndex = 0;
978 for(i=lineStart; i<lineEnd; i++, charIndex++) {
979 charLen = BufExpandCharacter(lineStr[charIndex], outIndex,
980 expandedChar, textD->buffer->tabDist,
981 textD->buffer->nullSubsChar);
982 if ( outIndex+charLen >= column ) break;
983 outIndex+=charLen;
986 /* If the column is in the middle of an expanded character, put cursor
987 * in front of character if in first half of character, and behind
988 * character if in last half of character
990 if (column >= outIndex + ( charLen / 2 ))
991 charIndex++;
993 /* If we are beyond the end of the line, back up one space */
994 if ((i>=lineEnd)&&(charIndex>0)) charIndex--;
997 /* Position is the start of the line plus the index into line buffer */
998 return lineStart + charIndex;
1002 ** Translate a buffer text position to the XY location where the center
1003 ** of the cursor would be positioned to point to that character. Returns
1004 ** False if the position is not displayed because it is VERTICALLY out
1005 ** of view. If the position is horizontally out of view, returns the
1006 ** x coordinate where the position would be if it were visible.
1008 int TextDPositionToXY(textDisp *textD, int pos, int *x, int *y)
1010 int charIndex, lineStartPos, fontHeight, lineLen;
1011 int visLineNum, charLen, outIndex, xStep, charStyle;
1012 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
1014 /* If position is not displayed, return false */
1015 if (pos < textD->firstChar ||
1016 (pos > textD->lastChar && !emptyLinesVisible(textD)))
1017 return False;
1019 /* Calculate y coordinate */
1020 if (!posToVisibleLineNum(textD, pos, &visLineNum))
1021 return False;
1022 fontHeight = textD->ascent + textD->descent;
1023 *y = textD->top + visLineNum*fontHeight + fontHeight/2;
1025 /* Get the text, length, and buffer position of the line. If the position
1026 is beyond the end of the buffer and should be at the first position on
1027 the first empty line, don't try to get or scan the text */
1028 lineStartPos = textD->lineStarts[visLineNum];
1029 if (lineStartPos == -1) {
1030 *x = textD->left - textD->horizOffset;
1031 return True;
1033 lineLen = visLineLength(textD, visLineNum);
1034 lineStr = BufGetRange(textD->buffer, lineStartPos, lineStartPos + lineLen);
1036 /* Step through character positions from the beginning of the line
1037 to "pos" to calculate the x coordinate */
1038 xStep = textD->left - textD->horizOffset;
1039 outIndex = 0;
1040 for(charIndex=0; charIndex<pos-lineStartPos; charIndex++) {
1041 charLen = BufExpandCharacter(lineStr[charIndex], outIndex, expandedChar,
1042 textD->buffer->tabDist, textD->buffer->nullSubsChar);
1043 charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1044 outIndex, lineStr[charIndex]);
1045 xStep += stringWidth(textD, expandedChar, charLen, charStyle);
1046 outIndex += charLen;
1048 *x = xStep;
1049 XtFree(lineStr);
1050 return True;
1054 ** If the text widget is maintaining a line number count appropriate to "pos"
1055 ** return the line and column numbers of pos, otherwise return False. If
1056 ** continuous wrap mode is on, returns the absolute line number (as opposed to
1057 ** the wrapped line number which is used for scrolling). THIS ROUTINE ONLY
1058 ** WORKS FOR DISPLAYED LINES AND, IN CONTINUOUS WRAP MODE, ONLY WHEN THE
1059 ** ABSOLUTE LINE NUMBER IS BEING MAINTAINED. Otherwise, it returns False.
1061 int TextDPosToLineAndCol(textDisp *textD, int pos, int *lineNum, int *column)
1063 textBuffer *buf = textD->buffer;
1065 /* In continuous wrap mode, the absolute (non-wrapped) line count is
1066 maintained separately, as needed. Only return it if we're actually
1067 keeping track of it and pos is in the displayed text */
1068 if (textD->continuousWrap) {
1069 if (!maintainingAbsTopLineNum(textD) || pos < textD->firstChar ||
1070 pos > textD->lastChar)
1071 return False;
1072 *lineNum = textD->absTopLineNum + BufCountLines(buf,
1073 textD->firstChar, pos);
1074 *column = BufCountDispChars(buf, BufStartOfLine(buf, pos), pos);
1075 return True;
1078 /* Only return the data if pos is within the displayed text */
1079 if (!posToVisibleLineNum(textD, pos, lineNum))
1080 return False;
1081 *column = BufCountDispChars(buf, textD->lineStarts[*lineNum], pos);
1082 *lineNum += textD->topLineNum;
1083 return True;
1087 ** Return True if position (x, y) is inside of the primary selection
1089 int TextDInSelection(textDisp *textD, int x, int y)
1091 int row, column, pos = xyToPos(textD, x, y, CHARACTER_POS);
1092 textBuffer *buf = textD->buffer;
1094 xyToUnconstrainedPos(textD, x, y, &row, &column, CHARACTER_POS);
1095 if (rangeTouchesRectSel(&buf->primary, textD->firstChar, textD->lastChar))
1096 column = TextDOffsetWrappedColumn(textD, row, column);
1097 return inSelection(&buf->primary, pos, BufStartOfLine(buf, pos), column);
1101 ** Correct a column number based on an unconstrained position (as returned by
1102 ** TextDXYToUnconstrainedPosition) to be relative to the last actual newline
1103 ** in the buffer before the row and column position given, rather than the
1104 ** last line start created by line wrapping. This is an adapter
1105 ** for rectangular selections and code written before continuous wrap mode,
1106 ** which thinks that the unconstrained column is the number of characters
1107 ** from the last newline. Obviously this is time consuming, because it
1108 ** invloves character re-counting.
1110 int TextDOffsetWrappedColumn(textDisp *textD, int row, int column)
1112 int lineStart, dispLineStart;
1114 if (!textD->continuousWrap || row < 0 || row > textD->nVisibleLines)
1115 return column;
1116 dispLineStart = textD->lineStarts[row];
1117 if (dispLineStart == -1)
1118 return column;
1119 lineStart = BufStartOfLine(textD->buffer, dispLineStart);
1120 return column + BufCountDispChars(textD->buffer, lineStart, dispLineStart);
1124 ** Correct a row number from an unconstrained position (as returned by
1125 ** TextDXYToUnconstrainedPosition) to a straight number of newlines from the
1126 ** top line of the display. Because rectangular selections are based on
1127 ** newlines, rather than display wrapping, and anywhere a rectangular selection
1128 ** needs a row, it needs it in terms of un-wrapped lines.
1130 int TextDOffsetWrappedRow(textDisp *textD, int row)
1132 if (!textD->continuousWrap || row < 0 || row > textD->nVisibleLines)
1133 return row;
1134 return BufCountLines(textD->buffer, textD->firstChar,
1135 textD->lineStarts[row]);
1139 ** Scroll the display to bring insertion cursor into view.
1141 ** Note: it would be nice to be able to do this without counting lines twice
1142 ** (setScroll counts them too) and/or to count from the most efficient
1143 ** starting point, but the efficiency of this routine is not as important to
1144 ** the overall performance of the text display.
1146 void TextDMakeInsertPosVisible(textDisp *textD)
1148 int hOffset, topLine, x, y;
1149 int cursorPos = textD->cursorPos;
1150 int linesFromTop = 0, do_padding = 1;
1151 int cursorVPadding = (int)TEXT_OF_TEXTD(textD).cursorVPadding;
1153 hOffset = textD->horizOffset;
1154 topLine = textD->topLineNum;
1156 /* Don't do padding if this is a mouse operation */
1157 do_padding = ((TEXT_OF_TEXTD(textD).dragState == NOT_CLICKED) &&
1158 (cursorVPadding > 0));
1160 /* Find the new top line number */
1161 if (cursorPos < textD->firstChar) {
1162 topLine -= TextDCountLines(textD, cursorPos, textD->firstChar, False);
1163 /* linesFromTop = 0; */
1164 } else if (cursorPos > textD->lastChar && !emptyLinesVisible(textD)) {
1165 topLine += TextDCountLines(textD, textD->lastChar -
1166 (wrapUsesCharacter(textD, textD->lastChar) ? 0 : 1),
1167 cursorPos, False);
1168 linesFromTop = textD->nVisibleLines-1;
1169 } else if (cursorPos == textD->lastChar && !emptyLinesVisible(textD) &&
1170 !wrapUsesCharacter(textD, textD->lastChar)) {
1171 topLine++;
1172 linesFromTop = textD->nVisibleLines-1;
1173 } else {
1174 /* Avoid extra counting if cursorVPadding is disabled */
1175 if (do_padding)
1176 linesFromTop = TextDCountLines(textD, textD->firstChar,
1177 cursorPos, True);
1179 if (topLine < 1) {
1180 fprintf(stderr, "internal consistency check tl1 failed\n");
1181 topLine = 1;
1184 if (do_padding) {
1185 /* Keep the cursor away from the top or bottom of screen. */
1186 if (textD->nVisibleLines <= 2*(int)cursorVPadding) {
1187 topLine += (linesFromTop - textD->nVisibleLines/2);
1188 topLine = max(topLine, 1);
1189 } else if (linesFromTop < (int)cursorVPadding) {
1190 topLine -= (cursorVPadding - linesFromTop);
1191 topLine = max(topLine, 1);
1192 } else if (linesFromTop > textD->nVisibleLines-(int)cursorVPadding-1) {
1193 topLine += (linesFromTop - (textD->nVisibleLines-cursorVPadding-1));
1197 /* Find the new setting for horizontal offset (this is a bit ungraceful).
1198 If the line is visible, just use TextDPositionToXY to get the position
1199 to scroll to, otherwise, do the vertical scrolling first, then the
1200 horizontal */
1201 if (!TextDPositionToXY(textD, cursorPos, &x, &y)) {
1202 setScroll(textD, topLine, hOffset, True, True);
1203 if (!TextDPositionToXY(textD, cursorPos, &x, &y))
1204 return; /* Give up, it's not worth it (but why does it fail?) */
1206 if (x > textD->left + textD->width)
1207 hOffset += x - (textD->left + textD->width);
1208 else if (x < textD->left)
1209 hOffset += x - textD->left;
1211 /* Do the scroll */
1212 setScroll(textD, topLine, hOffset, True, True);
1216 ** Return the current preferred column along with the current
1217 ** visible line index (-1 if not visible) and the lineStartPos
1218 ** of the current insert position.
1220 int TextDPreferredColumn(textDisp *textD, int *visLineNum, int *lineStartPos)
1222 int column;
1224 /* Find the position of the start of the line. Use the line starts array
1225 if possible, to avoid unbounded line-counting in continuous wrap mode */
1226 if (posToVisibleLineNum(textD, textD->cursorPos, visLineNum)) {
1227 *lineStartPos = textD->lineStarts[*visLineNum];
1229 else {
1230 *lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1231 *visLineNum = -1;
1234 /* Decide what column to move to, if there's a preferred column use that */
1235 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1236 BufCountDispChars(textD->buffer, *lineStartPos, textD->cursorPos);
1237 return(column);
1241 ** Return the insert position of the requested column given
1242 ** the lineStartPos.
1244 int TextDPosOfPreferredCol(textDisp *textD, int column, int lineStartPos)
1246 int newPos;
1248 newPos = BufCountForwardDispChars(textD->buffer, lineStartPos, column);
1249 if (textD->continuousWrap) {
1250 newPos = min(newPos, TextDEndOfLine(textD, lineStartPos, True));
1252 return(newPos);
1256 ** Cursor movement functions
1258 int TextDMoveRight(textDisp *textD)
1260 if (textD->cursorPos >= textD->buffer->length)
1261 return False;
1262 TextDSetInsertPosition(textD, textD->cursorPos + 1);
1263 return True;
1266 int TextDMoveLeft(textDisp *textD)
1268 if (textD->cursorPos <= 0)
1269 return False;
1270 TextDSetInsertPosition(textD, textD->cursorPos - 1);
1271 return True;
1274 int TextDMoveUp(textDisp *textD, int absolute)
1276 int lineStartPos, column, prevLineStartPos, newPos, visLineNum;
1278 /* Find the position of the start of the line. Use the line starts array
1279 if possible, to avoid unbounded line-counting in continuous wrap mode */
1280 if (absolute) {
1281 lineStartPos = BufStartOfLine(textD->buffer, textD->cursorPos);
1282 visLineNum = -1;
1283 } else if (posToVisibleLineNum(textD, textD->cursorPos, &visLineNum))
1284 lineStartPos = textD->lineStarts[visLineNum];
1285 else {
1286 lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1287 visLineNum = -1;
1289 if (lineStartPos == 0)
1290 return False;
1292 /* Decide what column to move to, if there's a preferred column use that */
1293 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1294 BufCountDispChars(textD->buffer, lineStartPos, textD->cursorPos);
1296 /* count forward from the start of the previous line to reach the column */
1297 if (absolute)
1298 prevLineStartPos = BufCountBackwardNLines(textD->buffer, lineStartPos, 1);
1299 else if (visLineNum != -1 && visLineNum != 0)
1300 prevLineStartPos = textD->lineStarts[visLineNum-1];
1301 else
1302 prevLineStartPos = TextDCountBackwardNLines(textD, lineStartPos, 1);
1303 newPos = BufCountForwardDispChars(textD->buffer, prevLineStartPos, column);
1304 if (textD->continuousWrap && !absolute)
1305 newPos = min(newPos, TextDEndOfLine(textD, prevLineStartPos, True));
1307 /* move the cursor */
1308 TextDSetInsertPosition(textD, newPos);
1310 /* if a preferred column wasn't aleady established, establish it */
1311 textD->cursorPreferredCol = column;
1313 return True;
1315 int TextDMoveDown(textDisp *textD, int absolute)
1317 int lineStartPos, column, nextLineStartPos, newPos, visLineNum;
1319 if (textD->cursorPos == textD->buffer->length)
1320 return False;
1321 if (absolute) {
1322 lineStartPos = BufStartOfLine(textD->buffer, textD->cursorPos);
1323 visLineNum = -1;
1324 } else if (posToVisibleLineNum(textD, textD->cursorPos, &visLineNum))
1325 lineStartPos = textD->lineStarts[visLineNum];
1326 else {
1327 lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1328 visLineNum = -1;
1330 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1331 BufCountDispChars(textD->buffer, lineStartPos, textD->cursorPos);
1332 if (absolute)
1333 nextLineStartPos = BufCountForwardNLines(textD->buffer, lineStartPos, 1);
1334 else
1335 nextLineStartPos = TextDCountForwardNLines(textD, lineStartPos, 1, True);
1336 newPos = BufCountForwardDispChars(textD->buffer, nextLineStartPos, column);
1337 if (textD->continuousWrap && !absolute)
1338 newPos = min(newPos, TextDEndOfLine(textD, nextLineStartPos, True));
1339 TextDSetInsertPosition(textD, newPos);
1340 textD->cursorPreferredCol = column;
1342 return True;
1346 ** Same as BufCountLines, but takes in to account wrapping if wrapping is
1347 ** turned on. If the caller knows that startPos is at a line start, it
1348 ** 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 TextDCountLines(textDisp *textD, int startPos, int endPos,
1352 int startPosIsLineStart)
1354 int retLines, retPos, retLineStart, retLineEnd;
1356 /* If we're not wrapping use simple (and more efficient) BufCountLines */
1357 if (!textD->continuousWrap)
1358 return BufCountLines(textD->buffer, startPos, endPos);
1360 wrappedLineCounter(textD, textD->buffer, startPos, endPos, INT_MAX,
1361 startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1362 &retLineEnd);
1363 return retLines;
1367 ** Same as BufCountForwardNLines, but takes in to account line breaks when
1368 ** wrapping is turned on. If the caller knows that startPos is at a line start,
1369 ** it can pass "startPosIsLineStart" as True to make the call more efficient
1370 ** by avoiding the additional step of scanning back to the last newline.
1372 int TextDCountForwardNLines(textDisp *textD, int startPos, int nLines,
1373 int startPosIsLineStart)
1375 int retLines, retPos, retLineStart, retLineEnd;
1377 /* if we're not wrapping use more efficient BufCountForwardNLines */
1378 if (!textD->continuousWrap)
1379 return BufCountForwardNLines(textD->buffer, startPos, nLines);
1381 /* wrappedLineCounter can't handle the 0 lines case */
1382 if (nLines == 0)
1383 return startPos;
1385 /* use the common line counting routine to count forward */
1386 wrappedLineCounter(textD, textD->buffer, startPos, textD->buffer->length,
1387 nLines, startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1388 &retLineEnd);
1389 return retPos;
1393 ** Same as BufEndOfLine, but takes in to account line breaks when wrapping
1394 ** is turned on. If the caller knows that startPos is at a line start, it
1395 ** can pass "startPosIsLineStart" as True to make the call more efficient
1396 ** by avoiding the additional step of scanning back to the last newline.
1398 ** Note that the definition of the end of a line is less clear when continuous
1399 ** wrap is on. With continuous wrap off, it's just a pointer to the newline
1400 ** that ends the line. When it's on, it's the character beyond the last
1401 ** DISPLAYABLE character on the line, where a whitespace character which has
1402 ** been "converted" to a newline for wrapping is not considered displayable.
1403 ** Also note that, a line can be wrapped at a non-whitespace character if the
1404 ** line had no whitespace. In this case, this routine returns a pointer to
1405 ** the start of the next line. This is also consistent with the model used by
1406 ** visLineLength.
1408 int TextDEndOfLine(textDisp *textD, int pos, int startPosIsLineStart)
1410 int retLines, retPos, retLineStart, retLineEnd;
1412 /* If we're not wrapping use more efficien BufEndOfLine */
1413 if (!textD->continuousWrap)
1414 return BufEndOfLine(textD->buffer, pos);
1416 if (pos == textD->buffer->length)
1417 return pos;
1418 wrappedLineCounter(textD, textD->buffer, pos, textD->buffer->length, 1,
1419 startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1420 &retLineEnd);
1421 return retLineEnd;
1425 ** Same as BufStartOfLine, but returns the character after last wrap point
1426 ** rather than the last newline.
1428 int TextDStartOfLine(textDisp *textD, int pos)
1430 int retLines, retPos, retLineStart, retLineEnd;
1432 /* If we're not wrapping, use the more efficient BufStartOfLine */
1433 if (!textD->continuousWrap)
1434 return BufStartOfLine(textD->buffer, pos);
1436 wrappedLineCounter(textD, textD->buffer, BufStartOfLine(textD->buffer, pos),
1437 pos, INT_MAX, True, 0, &retPos, &retLines, &retLineStart,
1438 &retLineEnd);
1439 return retLineStart;
1443 ** Same as BufCountBackwardNLines, but takes in to account line breaks when
1444 ** wrapping is turned on.
1446 int TextDCountBackwardNLines(textDisp *textD, int startPos, int nLines)
1448 textBuffer *buf = textD->buffer;
1449 int pos, lineStart, retLines, retPos, retLineStart, retLineEnd;
1451 /* If we're not wrapping, use the more efficient BufCountBackwardNLines */
1452 if (!textD->continuousWrap)
1453 return BufCountBackwardNLines(textD->buffer, startPos, nLines);
1455 pos = startPos;
1456 while (True) {
1457 lineStart = BufStartOfLine(buf, pos);
1458 wrappedLineCounter(textD, textD->buffer, lineStart, pos, INT_MAX,
1459 True, 0, &retPos, &retLines, &retLineStart, &retLineEnd);
1460 if (retLines > nLines)
1461 return TextDCountForwardNLines(textD, lineStart, retLines-nLines,
1462 True);
1463 nLines -= retLines;
1464 pos = lineStart - 1;
1465 if (pos < 0)
1466 return 0;
1467 nLines -= 1;
1472 ** Callback attached to the text buffer to receive delete information before
1473 ** the modifications are actually made.
1475 static void bufPreDeleteCB(int pos, int nDeleted, void *cbArg)
1477 textDisp *textD = (textDisp *)cbArg;
1478 if (textD->continuousWrap &&
1479 (textD->fixedFontWidth == -1 || textD->modifyingTabDist))
1480 /* Note: we must perform this measurement, even if there is not a
1481 single character deleted; the number of "deleted" lines is the
1482 number of visual lines spanned by the real line in which the
1483 modification takes place.
1484 Also, a modification of the tab distance requires the same
1485 kind of calculations in advance, even if the font width is "fixed",
1486 because when the width of the tab characters changes, the layout
1487 of the text may be completely different. */
1488 measureDeletedLines(textD, pos, nDeleted);
1489 else
1490 textD->suppressResync = 0; /* Probably not needed, but just in case */
1494 ** Callback attached to the text buffer to receive modification information
1496 static void bufModifiedCB(int pos, int nInserted, int nDeleted,
1497 int nRestyled, char *deletedText, void *cbArg)
1499 int linesInserted, linesDeleted, startDispPos, endDispPos;
1500 textDisp *textD = (textDisp *)cbArg;
1501 textBuffer *buf = textD->buffer;
1502 int oldFirstChar = textD->firstChar;
1503 int scrolled, origCursorPos = textD->cursorPos;
1504 int wrapModStart, wrapModEnd;
1506 /* buffer modification cancels vertical cursor motion column */
1507 if (nInserted != 0 || nDeleted != 0)
1508 textD->cursorPreferredCol = -1;
1510 /* Count the number of lines inserted and deleted, and in the case
1511 of continuous wrap mode, how much has changed */
1512 if (textD->continuousWrap) {
1513 findWrapRange(textD, deletedText, pos, nInserted, nDeleted,
1514 &wrapModStart, &wrapModEnd, &linesInserted, &linesDeleted);
1515 } else {
1516 linesInserted = nInserted == 0 ? 0 :
1517 BufCountLines(buf, pos, pos + nInserted);
1518 linesDeleted = nDeleted == 0 ? 0 : countLines(deletedText);
1521 /* Update the line starts and topLineNum */
1522 if (nInserted != 0 || nDeleted != 0) {
1523 if (textD->continuousWrap) {
1524 updateLineStarts(textD, wrapModStart, wrapModEnd-wrapModStart,
1525 nDeleted + pos-wrapModStart + (wrapModEnd-(pos+nInserted)),
1526 linesInserted, linesDeleted, &scrolled);
1527 } else {
1528 updateLineStarts(textD, pos, nInserted, nDeleted, linesInserted,
1529 linesDeleted, &scrolled);
1531 } else
1532 scrolled = False;
1534 /* If we're counting non-wrapped lines as well, maintain the absolute
1535 (non-wrapped) line number of the text displayed */
1536 if (maintainingAbsTopLineNum(textD) && (nInserted != 0 || nDeleted != 0)) {
1537 if (pos + nDeleted < oldFirstChar)
1538 textD->absTopLineNum += BufCountLines(buf, pos, pos + nInserted) -
1539 countLines(deletedText);
1540 else if (pos < oldFirstChar)
1541 resetAbsLineNum(textD);
1544 /* Update the line count for the whole buffer */
1545 textD->nBufferLines += linesInserted - linesDeleted;
1547 /* Update the scroll bar ranges (and value if the value changed). Note
1548 that updating the horizontal scroll bar range requires scanning the
1549 entire displayed text, however, it doesn't seem to hurt performance
1550 much. Note also, that the horizontal scroll bar update routine is
1551 allowed to re-adjust horizOffset if there is blank space to the right
1552 of all lines of text. */
1553 updateVScrollBarRange(textD);
1554 scrolled |= updateHScrollBarRange(textD);
1556 /* Update the cursor position */
1557 if (textD->cursorToHint != NO_HINT) {
1558 textD->cursorPos = textD->cursorToHint;
1559 textD->cursorToHint = NO_HINT;
1560 } else if (textD->cursorPos > pos) {
1561 if (textD->cursorPos < pos + nDeleted)
1562 textD->cursorPos = pos;
1563 else
1564 textD->cursorPos += nInserted - nDeleted;
1567 /* If the changes caused scrolling, re-paint everything and we're done. */
1568 if (scrolled) {
1569 blankCursorProtrusions(textD);
1570 TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left,
1571 textD->height);
1572 if (textD->styleBuffer) {/* See comments in extendRangeForStyleMods */
1573 textD->styleBuffer->primary.selected = False;
1574 textD->styleBuffer->primary.zeroWidth = False;
1576 return;
1579 /* If the changes didn't cause scrolling, decide the range of characters
1580 that need to be re-painted. Also if the cursor position moved, be
1581 sure that the redisplay range covers the old cursor position so the
1582 old cursor gets erased, and erase the bits of the cursor which extend
1583 beyond the left and right edges of the text. */
1584 startDispPos = textD->continuousWrap ? wrapModStart : pos;
1585 if (origCursorPos == startDispPos && textD->cursorPos != startDispPos)
1586 startDispPos = min(startDispPos, origCursorPos-1);
1587 if (linesInserted == linesDeleted) {
1588 if (nInserted == 0 && nDeleted == 0)
1589 endDispPos = pos + nRestyled;
1590 else {
1591 endDispPos = textD->continuousWrap ? wrapModEnd :
1592 BufEndOfLine(buf, pos + nInserted) + 1;
1593 if (origCursorPos >= startDispPos &&
1594 (origCursorPos <= endDispPos || endDispPos == buf->length))
1595 blankCursorProtrusions(textD);
1597 /* If more than one line is inserted/deleted, a line break may have
1598 been inserted or removed in between, and the line numbers may
1599 have changed. If only one line is altered, line numbers cannot
1600 be affected (the insertion or removal of a line break always
1601 results in at least two lines being redrawn). */
1602 if (linesInserted > 1) redrawLineNumbers(textD, False);
1603 } else { /* linesInserted != linesDeleted */
1604 endDispPos = textD->lastChar + 1;
1605 if (origCursorPos >= pos)
1606 blankCursorProtrusions(textD);
1607 redrawLineNumbers(textD, False);
1610 /* If there is a style buffer, check if the modification caused additional
1611 changes that need to be redisplayed. (Redisplaying separately would
1612 cause double-redraw on almost every modification involving styled
1613 text). Extend the redraw range to incorporate style changes */
1614 if (textD->styleBuffer)
1615 extendRangeForStyleMods(textD, &startDispPos, &endDispPos);
1617 /* Redisplay computed range */
1618 TextDRedisplayRange(textD, startDispPos, endDispPos);
1622 ** In continuous wrap mode, internal line numbers are calculated after
1623 ** wrapping. A separate non-wrapped line count is maintained when line
1624 ** numbering is turned on. There is some performance cost to maintaining this
1625 ** line count, so normally absolute line numbers are not tracked if line
1626 ** numbering is off. This routine allows callers to specify that they still
1627 ** want this line count maintained (for use via TextDPosToLineAndCol).
1628 ** More specifically, this allows the line number reported in the statistics
1629 ** line to be calibrated in absolute lines, rather than post-wrapped lines.
1631 void TextDMaintainAbsLineNum(textDisp *textD, int state)
1633 textD->needAbsTopLineNum = state;
1634 resetAbsLineNum(textD);
1638 ** Returns the absolute (non-wrapped) line number of the first line displayed.
1639 ** Returns 0 if the absolute top line number is not being maintained.
1641 static int getAbsTopLineNum(textDisp *textD)
1643 if (!textD->continuousWrap)
1644 return textD->topLineNum;
1645 if (maintainingAbsTopLineNum(textD))
1646 return textD->absTopLineNum;
1647 return 0;
1651 ** Re-calculate absolute top line number for a change in scroll position.
1653 static void offsetAbsLineNum(textDisp *textD, int oldFirstChar)
1655 if (maintainingAbsTopLineNum(textD)) {
1656 if (textD->firstChar < oldFirstChar)
1657 textD->absTopLineNum -= BufCountLines(textD->buffer,
1658 textD->firstChar, oldFirstChar);
1659 else
1660 textD->absTopLineNum += BufCountLines(textD->buffer,
1661 oldFirstChar, textD->firstChar);
1666 ** Return true if a separate absolute top line number is being maintained
1667 ** (for displaying line numbers or showing in the statistics line).
1669 static int maintainingAbsTopLineNum(textDisp *textD)
1671 return textD->continuousWrap &&
1672 (textD->lineNumWidth != 0 || textD->needAbsTopLineNum);
1676 ** Count lines from the beginning of the buffer to reestablish the
1677 ** absolute (non-wrapped) top line number. If mode is not continuous wrap,
1678 ** or the number is not being maintained, does nothing.
1680 static void resetAbsLineNum(textDisp *textD)
1682 textD->absTopLineNum = 1;
1683 offsetAbsLineNum(textD, 0);
1687 ** Find the line number of position "pos" relative to the first line of
1688 ** displayed text. Returns False if the line is not displayed.
1690 static int posToVisibleLineNum(textDisp *textD, int pos, int *lineNum)
1692 int i;
1694 if (pos < textD->firstChar)
1695 return False;
1696 if (pos > textD->lastChar) {
1697 if (emptyLinesVisible(textD)) {
1698 if (textD->lastChar < textD->buffer->length) {
1699 if (!posToVisibleLineNum(textD, textD->lastChar, lineNum)) {
1700 fprintf(stderr, "Consistency check ptvl failed\n");
1701 return False;
1703 return ++(*lineNum) <= textD->nVisibleLines-1;
1704 } else {
1705 posToVisibleLineNum(textD, max(textD->lastChar-1, 0), lineNum);
1706 return True;
1709 return False;
1712 for (i=textD->nVisibleLines-1; i>=0; i--) {
1713 if (textD->lineStarts[i] != -1 && pos >= textD->lineStarts[i]) {
1714 *lineNum = i;
1715 return True;
1718 return False; /* probably never be reached */
1722 ** Redisplay the text on a single line represented by "visLineNum" (the
1723 ** number of lines down from the top of the display), limited by
1724 ** "leftClip" and "rightClip" window coordinates and "leftCharIndex" and
1725 ** "rightCharIndex" character positions (not including the character at
1726 ** position "rightCharIndex").
1728 ** The cursor is also drawn if it appears on the line.
1730 static void redisplayLine(textDisp *textD, int visLineNum, int leftClip,
1731 int rightClip, int leftCharIndex, int rightCharIndex)
1733 textBuffer *buf = textD->buffer;
1734 int i, x, y, startX, charIndex, lineStartPos, lineLen, fontHeight;
1735 int stdCharWidth, charWidth, startIndex, charStyle, style;
1736 int charLen, outStartIndex, outIndex, cursorX = 0, hasCursor = False;
1737 int dispIndexOffset, cursorPos = textD->cursorPos, y_orig;
1738 char expandedChar[MAX_EXP_CHAR_LEN], outStr[MAX_DISP_LINE_LEN];
1739 char *lineStr, *outPtr;
1740 char baseChar;
1742 /* If line is not displayed, skip it */
1743 if (visLineNum < 0 || visLineNum >= textD->nVisibleLines)
1744 return;
1746 /* Shrink the clipping range to the active display area */
1747 leftClip = max(textD->left, leftClip);
1748 rightClip = min(rightClip, textD->left + textD->width);
1750 if (leftClip > rightClip) {
1751 return;
1754 /* Calculate y coordinate of the string to draw */
1755 fontHeight = textD->ascent + textD->descent;
1756 y = textD->top + visLineNum * fontHeight;
1758 /* Get the text, length, and buffer position of the line to display */
1759 lineStartPos = textD->lineStarts[visLineNum];
1760 if (lineStartPos == -1) {
1761 lineLen = 0;
1762 lineStr = NULL;
1763 } else {
1764 lineLen = visLineLength(textD, visLineNum);
1765 lineStr = BufGetRange(buf, lineStartPos, lineStartPos + lineLen);
1768 /* Space beyond the end of the line is still counted in units of characters
1769 of a standardized character width (this is done mostly because style
1770 changes based on character position can still occur in this region due
1771 to rectangular selections). stdCharWidth must be non-zero to prevent a
1772 potential infinite loop if x does not advance */
1773 stdCharWidth = textD->fontStruct->max_bounds.width;
1774 if (stdCharWidth <= 0) {
1775 fprintf(stderr, "Internal Error, bad font measurement\n");
1776 XtFree(lineStr);
1777 return;
1780 /* Rectangular selections are based on "real" line starts (after a newline
1781 or start of buffer). Calculate the difference between the last newline
1782 position and the line start we're using. Since scanning back to find a
1783 newline is expensive, only do so if there's actually a rectangular
1784 selection which needs it */
1785 if (textD->continuousWrap && (rangeTouchesRectSel(&buf->primary,
1786 lineStartPos, lineStartPos + lineLen) || rangeTouchesRectSel(
1787 &buf->secondary, lineStartPos, lineStartPos + lineLen) ||
1788 rangeTouchesRectSel(&buf->highlight, lineStartPos,
1789 lineStartPos + lineLen))) {
1790 dispIndexOffset = BufCountDispChars(buf,
1791 BufStartOfLine(buf, lineStartPos), lineStartPos);
1792 } else
1793 dispIndexOffset = 0;
1795 /* Step through character positions from the beginning of the line (even if
1796 that's off the left edge of the displayed area) to find the first
1797 character position that's not clipped, and the x coordinate for drawing
1798 that character */
1799 x = textD->left - textD->horizOffset;
1800 outIndex = 0;
1801 for(charIndex=0; ; charIndex++) {
1802 baseChar = '\0';
1803 charLen = charIndex >= lineLen ? 1 :
1804 BufExpandCharacter(baseChar = lineStr[charIndex], outIndex,
1805 expandedChar, buf->tabDist, buf->nullSubsChar);
1806 style = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1807 outIndex + dispIndexOffset, baseChar);
1808 charWidth = charIndex >= lineLen ? stdCharWidth :
1809 stringWidth(textD, expandedChar, charLen, style);
1810 if (x + charWidth >= leftClip && charIndex >= leftCharIndex) {
1811 startIndex = charIndex;
1812 outStartIndex = outIndex;
1813 startX = x;
1814 break;
1816 x += charWidth;
1817 outIndex += charLen;
1820 /* Scan character positions from the beginning of the clipping range, and
1821 draw parts whenever the style changes (also note if the cursor is on
1822 this line, and where it should be drawn to take advantage of the x
1823 position which we've gone to so much trouble to calculate) */
1824 outPtr = outStr;
1825 outIndex = outStartIndex;
1826 x = startX;
1827 for(charIndex=startIndex; charIndex<rightCharIndex; charIndex++) {
1828 if (lineStartPos+charIndex == cursorPos) {
1829 if (charIndex < lineLen || (charIndex == lineLen &&
1830 cursorPos >= buf->length)) {
1831 hasCursor = True;
1832 cursorX = x - 1;
1833 } else if (charIndex == lineLen) {
1834 if (wrapUsesCharacter(textD, cursorPos)) {
1835 hasCursor = True;
1836 cursorX = x - 1;
1840 baseChar = '\0';
1841 charLen = charIndex >= lineLen ? 1 :
1842 BufExpandCharacter(baseChar = lineStr[charIndex], outIndex,
1843 expandedChar, buf->tabDist, buf->nullSubsChar);
1844 charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1845 outIndex + dispIndexOffset, baseChar);
1846 for (i=0; i<charLen; i++) {
1847 if (i != 0 && charIndex < lineLen && lineStr[charIndex] == '\t')
1848 charStyle = styleOfPos(textD, lineStartPos, lineLen,
1849 charIndex, outIndex + dispIndexOffset, '\t');
1850 if (charStyle != style) {
1851 drawString(textD, style, startX, y, x, outStr, outPtr - outStr);
1852 outPtr = outStr;
1853 startX = x;
1854 style = charStyle;
1856 if (charIndex < lineLen) {
1857 *outPtr = expandedChar[i];
1858 charWidth = stringWidth(textD, &expandedChar[i], 1, charStyle);
1859 } else
1860 charWidth = stdCharWidth;
1861 outPtr++;
1862 x += charWidth;
1863 outIndex++;
1865 if (outPtr-outStr+MAX_EXP_CHAR_LEN>=MAX_DISP_LINE_LEN || x>=rightClip)
1866 break;
1869 /* Draw the remaining style segment */
1870 drawString(textD, style, startX, y, x, outStr, outPtr - outStr);
1872 /* Draw the cursor if part of it appeared on the redisplayed part of
1873 this line. Also check for the cases which are not caught as the
1874 line is scanned above: when the cursor appears at the very end
1875 of the redisplayed section. */
1876 y_orig = textD->cursorY;
1877 if (textD->cursorOn) {
1878 if (hasCursor)
1879 drawCursor(textD, cursorX, y);
1880 else if (charIndex<lineLen && (lineStartPos+charIndex+1 == cursorPos)
1881 && x == rightClip) {
1882 if (cursorPos >= buf->length)
1883 drawCursor(textD, x - 1, y);
1884 else {
1885 if (wrapUsesCharacter(textD, cursorPos))
1886 drawCursor(textD, x - 1, y);
1891 /* If the y position of the cursor has changed, redraw the calltip */
1892 if (hasCursor && (y_orig != textD->cursorY || y_orig != y))
1893 TextDRedrawCalltip(textD, 0);
1895 if (lineStr != NULL)
1896 XtFree(lineStr);
1900 ** Draw a string or blank area according to parameter "style", using the
1901 ** appropriate colors and drawing method for that style, with top left
1902 ** corner at x, y. If style says to draw text, use "string" as source of
1903 ** characters, and draw "nChars", if style is FILL, erase
1904 ** rectangle where text would have drawn from x to toX and from y to
1905 ** the maximum y extent of the current font(s).
1907 static void drawString(textDisp *textD, int style, int x, int y, int toX,
1908 char *string, int nChars)
1910 GC gc, bgGC;
1911 XGCValues gcValues;
1912 XFontStruct *fs = textD->fontStruct;
1913 Pixel bground = textD->bgPixel;
1914 Pixel fground = textD->fgPixel;
1915 int underlineStyle = FALSE;
1917 /* Don't draw if widget isn't realized */
1918 if (XtWindow(textD->w) == 0)
1919 return;
1921 /* select a GC */
1922 if (style & (STYLE_LOOKUP_MASK | BACKLIGHT_MASK | RANGESET_MASK)) {
1923 gc = bgGC = textD->styleGC;
1925 else if (style & HIGHLIGHT_MASK) {
1926 gc = textD->highlightGC;
1927 bgGC = textD->highlightBGGC;
1929 else if (style & PRIMARY_MASK) {
1930 gc = textD->selectGC;
1931 bgGC = textD->selectBGGC;
1933 else {
1934 gc = bgGC = textD->gc;
1937 if (gc == textD->styleGC) {
1938 /* we have work to do */
1939 styleTableEntry *styleRec;
1940 /* Set font, color, and gc depending on style. For normal text, GCs
1941 for normal drawing, or drawing within a selection or highlight are
1942 pre-allocated and pre-configured. For syntax highlighting, GCs are
1943 configured here, on the fly. */
1944 if (style & STYLE_LOOKUP_MASK) {
1945 styleRec = &textD->styleTable[(style & STYLE_LOOKUP_MASK) - ASCII_A];
1946 underlineStyle = styleRec->underline;
1947 fs = styleRec->font;
1948 gcValues.font = fs->fid;
1949 fground = styleRec->color;
1950 /* here you could pick up specific select and highlight fground */
1952 else {
1953 styleRec = NULL;
1954 gcValues.font = fs->fid;
1955 fground = textD->fgPixel;
1957 /* Background color priority order is:
1958 1 Primary(Selection), 2 Highlight(Parens),
1959 3 Rangeset, 4 SyntaxHighlightStyle,
1960 5 Backlight (if NOT fill), 6 DefaultBackground */
1961 bground =
1962 style & PRIMARY_MASK ? textD->selectBGPixel :
1963 style & HIGHLIGHT_MASK ? textD->highlightBGPixel :
1964 style & RANGESET_MASK ?
1965 getRangesetColor(textD,
1966 (style&RANGESET_MASK)>>RANGESET_SHIFT,
1967 bground) :
1968 styleRec && styleRec->bgColorName ? styleRec->bgColor :
1969 (style & BACKLIGHT_MASK) && !(style & FILL_MASK) ?
1970 textD->bgClassPixel[(style>>BACKLIGHT_SHIFT) & 0xff] :
1971 textD->bgPixel;
1972 if (fground == bground) /* B&W kludge */
1973 fground = textD->bgPixel;
1974 /* set up gc for clearing using the foreground color entry */
1975 gcValues.foreground = gcValues.background = bground;
1976 XChangeGC(XtDisplay(textD->w), gc,
1977 GCFont | GCForeground | GCBackground, &gcValues);
1980 /* Draw blank area rather than text, if that was the request */
1981 if (style & FILL_MASK) {
1982 /* wipes out to right hand edge of widget */
1983 if (toX >= textD->left)
1984 clearRect(textD, bgGC, max(x, textD->left), y,
1985 toX - max(x, textD->left), textD->ascent + textD->descent);
1986 return;
1989 /* If any space around the character remains unfilled (due to use of
1990 different sized fonts for highlighting), fill in above or below
1991 to erase previously drawn characters */
1992 if (fs->ascent < textD->ascent)
1993 clearRect(textD, bgGC, x, y, toX - x, textD->ascent - fs->ascent);
1994 if (fs->descent < textD->descent)
1995 clearRect(textD, bgGC, x, y + textD->ascent + fs->descent, toX - x,
1996 textD->descent - fs->descent);
1998 /* set up gc for writing text (set foreground properly) */
1999 if (bgGC == textD->styleGC) {
2000 gcValues.foreground = fground;
2001 XChangeGC(XtDisplay(textD->w), gc, GCForeground, &gcValues);
2004 /* Draw the string using gc and font set above */
2005 XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w), gc, x,
2006 y + textD->ascent, string, nChars);
2008 /* Underline if style is secondary selection */
2009 if (style & SECONDARY_MASK || underlineStyle)
2011 /* restore foreground in GC (was set to background by clearRect()) */
2012 gcValues.foreground = fground;
2013 XChangeGC(XtDisplay(textD->w), gc,
2014 GCForeground, &gcValues);
2015 /* draw underline */
2016 XDrawLine(XtDisplay(textD->w), XtWindow(textD->w), gc, x,
2017 y + textD->ascent, toX - 1, y + textD->ascent);
2022 ** Clear a rectangle with the appropriate background color for "style"
2024 static void clearRect(textDisp *textD, GC gc, int x, int y,
2025 int width, int height)
2027 /* A width of zero means "clear to end of window" to XClearArea */
2028 if (width == 0 || XtWindow(textD->w) == 0)
2029 return;
2031 if (gc == textD->gc) {
2032 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), x, y,
2033 width, height, False);
2035 else {
2036 XFillRectangle(XtDisplay(textD->w), XtWindow(textD->w),
2037 gc, x, y, width, height);
2042 ** Draw a cursor with top center at x, y.
2044 static void drawCursor(textDisp *textD, int x, int y)
2046 XSegment segs[5];
2047 int left, right, cursorWidth, midY;
2048 int fontWidth = textD->fontStruct->min_bounds.width, nSegs = 0;
2049 int fontHeight = textD->ascent + textD->descent;
2050 int bot = y + fontHeight - 1;
2052 if (XtWindow(textD->w) == 0 || x < textD->left-1 ||
2053 x > textD->left + textD->width)
2054 return;
2056 /* For cursors other than the block, make them around 2/3 of a character
2057 width, rounded to an even number of pixels so that X will draw an
2058 odd number centered on the stem at x. */
2059 cursorWidth = (fontWidth/3) * 2;
2060 left = x - cursorWidth/2;
2061 right = left + cursorWidth;
2063 /* Create segments and draw cursor */
2064 if (textD->cursorStyle == CARET_CURSOR) {
2065 midY = bot - fontHeight/5;
2066 segs[0].x1 = left; segs[0].y1 = bot; segs[0].x2 = x; segs[0].y2 = midY;
2067 segs[1].x1 = x; segs[1].y1 = midY; segs[1].x2 = right; segs[1].y2 = bot;
2068 segs[2].x1 = left; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2=midY-1;
2069 segs[3].x1 = x; segs[3].y1=midY-1; segs[3].x2 = right; segs[3].y2 = bot;
2070 nSegs = 4;
2071 } else if (textD->cursorStyle == NORMAL_CURSOR) {
2072 segs[0].x1 = left; segs[0].y1 = y; segs[0].x2 = right; segs[0].y2 = y;
2073 segs[1].x1 = x; segs[1].y1 = y; segs[1].x2 = x; segs[1].y2 = bot;
2074 segs[2].x1 = left; segs[2].y1 = bot; segs[2].x2 = right; segs[2].y2=bot;
2075 nSegs = 3;
2076 } else if (textD->cursorStyle == HEAVY_CURSOR) {
2077 segs[0].x1 = x-1; segs[0].y1 = y; segs[0].x2 = x-1; segs[0].y2 = bot;
2078 segs[1].x1 = x; segs[1].y1 = y; segs[1].x2 = x; segs[1].y2 = bot;
2079 segs[2].x1 = x+1; segs[2].y1 = y; segs[2].x2 = x+1; segs[2].y2 = bot;
2080 segs[3].x1 = left; segs[3].y1 = y; segs[3].x2 = right; segs[3].y2 = y;
2081 segs[4].x1 = left; segs[4].y1 = bot; segs[4].x2 = right; segs[4].y2=bot;
2082 nSegs = 5;
2083 } else if (textD->cursorStyle == DIM_CURSOR) {
2084 midY = y + fontHeight/2;
2085 segs[0].x1 = x; segs[0].y1 = y; segs[0].x2 = x; segs[0].y2 = y;
2086 segs[1].x1 = x; segs[1].y1 = midY; segs[1].x2 = x; segs[1].y2 = midY;
2087 segs[2].x1 = x; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2 = bot;
2088 nSegs = 3;
2089 } else if (textD->cursorStyle == BLOCK_CURSOR) {
2090 right = x + fontWidth;
2091 segs[0].x1 = x; segs[0].y1 = y; segs[0].x2 = right; segs[0].y2 = y;
2092 segs[1].x1 = right; segs[1].y1 = y; segs[1].x2 = right; segs[1].y2=bot;
2093 segs[2].x1 = right; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2 = bot;
2094 segs[3].x1 = x; segs[3].y1 = bot; segs[3].x2 = x; segs[3].y2 = y;
2095 nSegs = 4;
2097 XDrawSegments(XtDisplay(textD->w), XtWindow(textD->w),
2098 textD->cursorFGGC, segs, nSegs);
2100 /* Save the last position drawn */
2101 textD->cursorX = x;
2102 textD->cursorY = y;
2106 ** Determine the drawing method to use to draw a specific character from "buf".
2107 ** "lineStartPos" gives the character index where the line begins, "lineIndex",
2108 ** the number of characters past the beginning of the line, and "dispIndex",
2109 ** the number of displayed characters past the beginning of the line. Passing
2110 ** lineStartPos of -1 returns the drawing style for "no text".
2112 ** Why not just: styleOfPos(textD, pos)? Because style applies to blank areas
2113 ** of the window beyond the text boundaries, and because this routine must also
2114 ** decide whether a position is inside of a rectangular selection, and do so
2115 ** efficiently, without re-counting character positions from the start of the
2116 ** line.
2118 ** Note that style is a somewhat incorrect name, drawing method would
2119 ** be more appropriate.
2121 static int styleOfPos(textDisp *textD, int lineStartPos,
2122 int lineLen, int lineIndex, int dispIndex, int thisChar)
2124 textBuffer *buf = textD->buffer;
2125 textBuffer *styleBuf = textD->styleBuffer;
2126 int pos, style = 0;
2128 if (lineStartPos == -1 || buf == NULL)
2129 return FILL_MASK;
2131 pos = lineStartPos + min(lineIndex, lineLen);
2133 if (lineIndex >= lineLen)
2134 style = FILL_MASK;
2135 else if (styleBuf != NULL) {
2136 style = (unsigned char)BufGetCharacter(styleBuf, pos);
2137 if (style == textD->unfinishedStyle) {
2138 /* encountered "unfinished" style, trigger parsing */
2139 (textD->unfinishedHighlightCB)(textD, pos, textD->highlightCBArg);
2140 style = (unsigned char)BufGetCharacter(styleBuf, pos);
2143 if (inSelection(&buf->primary, pos, lineStartPos, dispIndex))
2144 style |= PRIMARY_MASK;
2145 if (inSelection(&buf->highlight, pos, lineStartPos, dispIndex))
2146 style |= HIGHLIGHT_MASK;
2147 if (inSelection(&buf->secondary, pos, lineStartPos, dispIndex))
2148 style |= SECONDARY_MASK;
2149 /* store in the RANGESET_MASK portion of style the rangeset index for pos */
2150 if (buf->rangesetTable) {
2151 int rangesetIndex = RangesetIndex1ofPos(buf->rangesetTable, pos, True);
2152 style |= ((rangesetIndex << RANGESET_SHIFT) & RANGESET_MASK);
2154 /* store in the BACKLIGHT_MASK portion of style the background color class
2155 of the character thisChar */
2156 if (textD->bgClass)
2158 style |= (textD->bgClass[(unsigned char)thisChar]<<BACKLIGHT_SHIFT);
2160 return style;
2164 ** Find the width of a string in the font of a particular style
2166 static int stringWidth(textDisp *textD, char *string, int length, int style)
2168 XFontStruct *fs;
2170 if (style & STYLE_LOOKUP_MASK)
2171 fs = textD->styleTable[(style & STYLE_LOOKUP_MASK) - ASCII_A].font;
2172 else
2173 fs = textD->fontStruct;
2174 return XTextWidth(fs, string, length);
2178 ** Return true if position "pos" with indentation "dispIndex" is in
2179 ** selection "sel"
2181 static int inSelection(selection *sel, int pos, int lineStartPos, int dispIndex)
2183 return sel->selected &&
2184 ((!sel->rectangular &&
2185 pos >= sel->start && pos < sel->end) ||
2186 (sel->rectangular &&
2187 pos >= sel->start && lineStartPos <= sel->end &&
2188 dispIndex >= sel->rectStart && dispIndex < sel->rectEnd));
2192 ** Translate window coordinates to the nearest (insert cursor or character
2193 ** cell) text position. The parameter posType specifies how to interpret the
2194 ** position: CURSOR_POS means translate the coordinates to the nearest cursor
2195 ** position, and CHARACTER_POS means return the position of the character
2196 ** closest to (x, y).
2198 static int xyToPos(textDisp *textD, int x, int y, int posType)
2200 int charIndex, lineStart, lineLen, fontHeight;
2201 int charWidth, charLen, charStyle, visLineNum, xStep, outIndex;
2202 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
2204 /* Find the visible line number corresponding to the y coordinate */
2205 fontHeight = textD->ascent + textD->descent;
2206 visLineNum = (y - textD->top) / fontHeight;
2207 if (visLineNum < 0)
2208 return textD->firstChar;
2209 if (visLineNum >= textD->nVisibleLines)
2210 visLineNum = textD->nVisibleLines - 1;
2212 /* Find the position at the start of the line */
2213 lineStart = textD->lineStarts[visLineNum];
2215 /* If the line start was empty, return the last position in the buffer */
2216 if (lineStart == -1)
2217 return textD->buffer->length;
2219 /* Get the line text and its length */
2220 lineLen = visLineLength(textD, visLineNum);
2221 lineStr = BufGetRange(textD->buffer, lineStart, lineStart + lineLen);
2223 /* Step through character positions from the beginning of the line
2224 to find the character position corresponding to the x coordinate */
2225 xStep = textD->left - textD->horizOffset;
2226 outIndex = 0;
2227 for(charIndex=0; charIndex<lineLen; charIndex++) {
2228 charLen = BufExpandCharacter(lineStr[charIndex], outIndex, expandedChar,
2229 textD->buffer->tabDist, textD->buffer->nullSubsChar);
2230 charStyle = styleOfPos(textD, lineStart, lineLen, charIndex, outIndex,
2231 lineStr[charIndex]);
2232 charWidth = stringWidth(textD, expandedChar, charLen, charStyle);
2233 if (x < xStep + (posType == CURSOR_POS ? charWidth/2 : charWidth)) {
2234 XtFree(lineStr);
2235 return lineStart + charIndex;
2237 xStep += charWidth;
2238 outIndex += charLen;
2241 /* If the x position was beyond the end of the line, return the position
2242 of the newline at the end of the line */
2243 XtFree(lineStr);
2244 return lineStart + lineLen;
2248 ** Translate window coordinates to the nearest row and column number for
2249 ** positioning the cursor. This, of course, makes no sense when the font is
2250 ** proportional, since there are no absolute columns. The parameter posType
2251 ** specifies how to interpret the position: CURSOR_POS means translate the
2252 ** coordinates to the nearest position between characters, and CHARACTER_POS
2253 ** means translate the position to the nearest character cell.
2255 static void xyToUnconstrainedPos(textDisp *textD, int x, int y, int *row,
2256 int *column, int posType)
2258 int fontHeight = textD->ascent + textD->descent;
2259 int fontWidth = textD->fontStruct->max_bounds.width;
2261 /* Find the visible line number corresponding to the y coordinate */
2262 *row = (y - textD->top) / fontHeight;
2263 if (*row < 0) *row = 0;
2264 if (*row >= textD->nVisibleLines) *row = textD->nVisibleLines - 1;
2265 *column = ((x-textD->left) + textD->horizOffset +
2266 (posType == CURSOR_POS ? fontWidth/2 : 0)) / fontWidth;
2267 if (*column < 0) *column = 0;
2271 ** Offset the line starts array, topLineNum, firstChar and lastChar, for a new
2272 ** vertical scroll position given by newTopLineNum. If any currently displayed
2273 ** lines will still be visible, salvage the line starts values, otherwise,
2274 ** count lines from the nearest known line start (start or end of buffer, or
2275 ** the closest value in the lineStarts array)
2277 static void offsetLineStarts(textDisp *textD, int newTopLineNum)
2279 int oldTopLineNum = textD->topLineNum;
2280 int oldFirstChar = textD->firstChar;
2281 int lineDelta = newTopLineNum - oldTopLineNum;
2282 int nVisLines = textD->nVisibleLines;
2283 int *lineStarts = textD->lineStarts;
2284 int i, lastLineNum;
2285 textBuffer *buf = textD->buffer;
2287 /* If there was no offset, nothing needs to be changed */
2288 if (lineDelta == 0)
2289 return;
2291 /* { int i;
2292 printf("Scroll, lineDelta %d\n", lineDelta);
2293 printf("lineStarts Before: ");
2294 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2295 printf("\n");
2296 } */
2298 /* Find the new value for firstChar by counting lines from the nearest
2299 known line start (start or end of buffer, or the closest value in the
2300 lineStarts array) */
2301 lastLineNum = oldTopLineNum + nVisLines - 1;
2302 if (newTopLineNum < oldTopLineNum && newTopLineNum < -lineDelta) {
2303 textD->firstChar = TextDCountForwardNLines(textD, 0, newTopLineNum-1,
2304 True);
2305 /* printf("counting forward %d lines from start\n", newTopLineNum-1);*/
2306 } else if (newTopLineNum < oldTopLineNum) {
2307 textD->firstChar = TextDCountBackwardNLines(textD, textD->firstChar,
2308 -lineDelta);
2309 /* printf("counting backward %d lines from firstChar\n", -lineDelta);*/
2310 } else if (newTopLineNum < lastLineNum) {
2311 textD->firstChar = lineStarts[newTopLineNum - oldTopLineNum];
2312 /* printf("taking new start from lineStarts[%d]\n",
2313 newTopLineNum - oldTopLineNum); */
2314 } else if (newTopLineNum-lastLineNum < textD->nBufferLines-newTopLineNum) {
2315 textD->firstChar = TextDCountForwardNLines(textD,
2316 lineStarts[nVisLines-1], newTopLineNum - lastLineNum, True);
2317 /* printf("counting forward %d lines from start of last line\n",
2318 newTopLineNum - lastLineNum); */
2319 } else {
2320 textD->firstChar = TextDCountBackwardNLines(textD, buf->length,
2321 textD->nBufferLines - newTopLineNum + 1);
2322 /* printf("counting backward %d lines from end\n",
2323 textD->nBufferLines - newTopLineNum + 1); */
2326 /* Fill in the line starts array */
2327 if (lineDelta < 0 && -lineDelta < nVisLines) {
2328 for (i=nVisLines-1; i >= -lineDelta; i--)
2329 lineStarts[i] = lineStarts[i+lineDelta];
2330 calcLineStarts(textD, 0, -lineDelta);
2331 } else if (lineDelta > 0 && lineDelta < nVisLines) {
2332 for (i=0; i<nVisLines-lineDelta; i++)
2333 lineStarts[i] = lineStarts[i+lineDelta];
2334 calcLineStarts(textD, nVisLines-lineDelta, nVisLines-1);
2335 } else
2336 calcLineStarts(textD, 0, nVisLines);
2338 /* Set lastChar and topLineNum */
2339 calcLastChar(textD);
2340 textD->topLineNum = newTopLineNum;
2342 /* If we're numbering lines or being asked to maintain an absolute line
2343 number, re-calculate the absolute line number */
2344 offsetAbsLineNum(textD, oldFirstChar);
2346 /* { int i;
2347 printf("lineStarts After: ");
2348 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2349 printf("\n");
2350 } */
2354 ** Update the line starts array, topLineNum, firstChar and lastChar for text
2355 ** display "textD" after a modification to the text buffer, given by the
2356 ** position where the change began "pos", and the nmubers of characters
2357 ** and lines inserted and deleted.
2359 static void updateLineStarts(textDisp *textD, int pos, int charsInserted,
2360 int charsDeleted, int linesInserted, int linesDeleted, int *scrolled)
2362 int *lineStarts = textD->lineStarts;
2363 int i, lineOfPos, lineOfEnd, nVisLines = textD->nVisibleLines;
2364 int charDelta = charsInserted - charsDeleted;
2365 int lineDelta = linesInserted - linesDeleted;
2367 /* { int i;
2368 printf("linesDeleted %d, linesInserted %d, charsInserted %d, charsDeleted %d\n",
2369 linesDeleted, linesInserted, charsInserted, charsDeleted);
2370 printf("lineStarts Before: ");
2371 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2372 printf("\n");
2373 } */
2374 /* If all of the changes were before the displayed text, the display
2375 doesn't change, just update the top line num and offset the line
2376 start entries and first and last characters */
2377 if (pos + charsDeleted < textD->firstChar) {
2378 textD->topLineNum += lineDelta;
2379 for (i=0; i<nVisLines && lineStarts[i] != -1; i++)
2380 lineStarts[i] += charDelta;
2381 /* { int i;
2382 printf("lineStarts after delete doesn't touch: ");
2383 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2384 printf("\n");
2385 } */
2386 textD->firstChar += charDelta;
2387 textD->lastChar += charDelta;
2388 *scrolled = False;
2389 return;
2392 /* The change began before the beginning of the displayed text, but
2393 part or all of the displayed text was deleted */
2394 if (pos < textD->firstChar) {
2395 /* If some text remains in the window, anchor on that */
2396 if (posToVisibleLineNum(textD, pos + charsDeleted, &lineOfEnd) &&
2397 ++lineOfEnd < nVisLines && lineStarts[lineOfEnd] != -1) {
2398 textD->topLineNum = max(1, textD->topLineNum + lineDelta);
2399 textD->firstChar = TextDCountBackwardNLines(textD,
2400 lineStarts[lineOfEnd] + charDelta, lineOfEnd);
2401 /* Otherwise anchor on original line number and recount everything */
2402 } else {
2403 if (textD->topLineNum > textD->nBufferLines + lineDelta) {
2404 textD->topLineNum = 1;
2405 textD->firstChar = 0;
2406 } else
2407 textD->firstChar = TextDCountForwardNLines(textD, 0,
2408 textD->topLineNum - 1, True);
2410 calcLineStarts(textD, 0, nVisLines-1);
2411 /* { int i;
2412 printf("lineStarts after delete encroaches: ");
2413 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2414 printf("\n");
2415 } */
2416 /* calculate lastChar by finding the end of the last displayed line */
2417 calcLastChar(textD);
2418 *scrolled = True;
2419 return;
2422 /* If the change was in the middle of the displayed text (it usually is),
2423 salvage as much of the line starts array as possible by moving and
2424 offsetting the entries after the changed area, and re-counting the
2425 added lines or the lines beyond the salvaged part of the line starts
2426 array */
2427 if (pos <= textD->lastChar) {
2428 /* find line on which the change began */
2429 posToVisibleLineNum(textD, pos, &lineOfPos);
2430 /* salvage line starts after the changed area */
2431 if (lineDelta == 0) {
2432 for (i=lineOfPos+1; i<nVisLines && lineStarts[i]!= -1; i++)
2433 lineStarts[i] += charDelta;
2434 } else if (lineDelta > 0) {
2435 for (i=nVisLines-1; i>=lineOfPos+lineDelta+1; i--)
2436 lineStarts[i] = lineStarts[i-lineDelta] +
2437 (lineStarts[i-lineDelta] == -1 ? 0 : charDelta);
2438 } else /* (lineDelta < 0) */ {
2439 for (i=max(0,lineOfPos+1); i<nVisLines+lineDelta; i++)
2440 lineStarts[i] = lineStarts[i-lineDelta] +
2441 (lineStarts[i-lineDelta] == -1 ? 0 : charDelta);
2443 /* { int i;
2444 printf("lineStarts after salvage: ");
2445 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2446 printf("\n");
2447 } */
2448 /* fill in the missing line starts */
2449 if (linesInserted >= 0)
2450 calcLineStarts(textD, lineOfPos + 1, lineOfPos + linesInserted);
2451 if (lineDelta < 0)
2452 calcLineStarts(textD, nVisLines+lineDelta, nVisLines);
2453 /* { int i;
2454 printf("lineStarts after recalculation: ");
2455 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2456 printf("\n");
2457 } */
2458 /* calculate lastChar by finding the end of the last displayed line */
2459 calcLastChar(textD);
2460 *scrolled = False;
2461 return;
2464 /* Change was past the end of the displayed text, but displayable by virtue
2465 of being an insert at the end of the buffer into visible blank lines */
2466 if (emptyLinesVisible(textD)) {
2467 posToVisibleLineNum(textD, pos, &lineOfPos);
2468 calcLineStarts(textD, lineOfPos, lineOfPos+linesInserted);
2469 calcLastChar(textD);
2470 /* { int i;
2471 printf("lineStarts after insert at end: ");
2472 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2473 printf("\n");
2474 } */
2475 *scrolled = False;
2476 return;
2479 /* Change was beyond the end of the buffer and not visible, do nothing */
2480 *scrolled = False;
2484 ** Scan through the text in the "textD"'s buffer and recalculate the line
2485 ** starts array values beginning at index "startLine" and continuing through
2486 ** (including) "endLine". It assumes that the line starts entry preceding
2487 ** "startLine" (or textD->firstChar if startLine is 0) is good, and re-counts
2488 ** newlines to fill in the requested entries. Out of range values for
2489 ** "startLine" and "endLine" are acceptable.
2491 static void calcLineStarts(textDisp *textD, int startLine, int endLine)
2493 int startPos, bufLen = textD->buffer->length;
2494 int line, lineEnd, nextLineStart, nVis = textD->nVisibleLines;
2495 int *lineStarts = textD->lineStarts;
2497 /* Clean up (possibly) messy input parameters */
2498 if (nVis == 0) return;
2499 if (endLine < 0) endLine = 0;
2500 if (endLine >= nVis) endLine = nVis - 1;
2501 if (startLine < 0) startLine = 0;
2502 if (startLine >=nVis) startLine = nVis - 1;
2503 if (startLine > endLine)
2504 return;
2506 /* Find the last known good line number -> position mapping */
2507 if (startLine == 0) {
2508 lineStarts[0] = textD->firstChar;
2509 startLine = 1;
2511 startPos = lineStarts[startLine-1];
2513 /* If the starting position is already past the end of the text,
2514 fill in -1's (means no text on line) and return */
2515 if (startPos == -1) {
2516 for (line=startLine; line<=endLine; line++)
2517 lineStarts[line] = -1;
2518 return;
2521 /* Loop searching for ends of lines and storing the positions of the
2522 start of the next line in lineStarts */
2523 for (line=startLine; line<=endLine; line++) {
2524 findLineEnd(textD, startPos, True, &lineEnd, &nextLineStart);
2525 startPos = nextLineStart;
2526 if (startPos >= bufLen) {
2527 /* If the buffer ends with a newline or line break, put
2528 buf->length in the next line start position (instead of
2529 a -1 which is the normal marker for an empty line) to
2530 indicate that the cursor may safely be displayed there */
2531 if (line == 0 || (lineStarts[line-1] != bufLen &&
2532 lineEnd != nextLineStart)) {
2533 lineStarts[line] = bufLen;
2534 line++;
2536 break;
2538 lineStarts[line] = startPos;
2541 /* Set any entries beyond the end of the text to -1 */
2542 for (; line<=endLine; line++)
2543 lineStarts[line] = -1;
2547 ** Given a textDisp with a complete, up-to-date lineStarts array, update
2548 ** the lastChar entry to point to the last buffer position displayed.
2550 static void calcLastChar(textDisp *textD)
2552 int i;
2554 for (i=textD->nVisibleLines-1; i>0 && textD->lineStarts[i]== -1; i--);
2555 textD->lastChar = i < 0 ? 0 :
2556 TextDEndOfLine(textD, textD->lineStarts[i], True);
2559 void TextDImposeGraphicsExposeTranslation(textDisp *textD, int *xOffset, int *yOffset)
2561 if (textD->graphicsExposeQueue) {
2562 graphicExposeTranslationEntry *thisGEQEntry = textD->graphicsExposeQueue->next;
2563 if (thisGEQEntry) {
2564 *xOffset += thisGEQEntry->horizontal;
2565 *yOffset += thisGEQEntry->vertical;
2570 Boolean TextDPopGraphicExposeQueueEntry(textDisp *textD)
2572 graphicExposeTranslationEntry *removedGEQEntry = textD->graphicsExposeQueue;
2574 if (removedGEQEntry) {
2575 textD->graphicsExposeQueue = removedGEQEntry->next;
2576 XtFree((char *)removedGEQEntry);
2578 return(removedGEQEntry?True:False);
2581 void TextDTranlateGraphicExposeQueue(textDisp *textD, int xOffset, int yOffset, Boolean appendEntry)
2583 graphicExposeTranslationEntry *newGEQEntry = NULL;
2584 if (appendEntry) {
2585 newGEQEntry = (graphicExposeTranslationEntry *)XtMalloc(sizeof(graphicExposeTranslationEntry));
2586 newGEQEntry->next = NULL;
2587 newGEQEntry->horizontal = xOffset;
2588 newGEQEntry->vertical = yOffset;
2590 if (textD->graphicsExposeQueue) {
2591 graphicExposeTranslationEntry *iter = textD->graphicsExposeQueue;
2592 while (iter->next) {
2593 iter->next->horizontal += xOffset;
2594 iter->next->vertical += yOffset;
2595 iter = iter->next;
2597 if (appendEntry) {
2598 iter->next = (struct graphicExposeTranslationEntry *)newGEQEntry;
2601 else {
2602 if (appendEntry) {
2603 textD->graphicsExposeQueue = newGEQEntry;
2608 static void setScroll(textDisp *textD, int topLineNum, int horizOffset,
2609 int updateVScrollBar, int updateHScrollBar)
2611 int fontHeight = textD->ascent + textD->descent;
2612 int origHOffset = textD->horizOffset;
2613 int lineDelta = textD->topLineNum - topLineNum;
2614 int xOffset, yOffset, srcX, srcY, dstX, dstY, width, height;
2615 int exactHeight = textD->height - textD->height %
2616 (textD->ascent + textD->descent);
2618 /* Do nothing if scroll position hasn't actually changed or there's no
2619 window to draw in yet */
2620 if (XtWindow(textD->w) == 0 || (textD->horizOffset == horizOffset &&
2621 textD->topLineNum == topLineNum))
2622 return;
2624 /* If part of the cursor is protruding beyond the text clipping region,
2625 clear it off */
2626 blankCursorProtrusions(textD);
2628 /* If the vertical scroll position has changed, update the line
2629 starts array and related counters in the text display */
2630 offsetLineStarts(textD, topLineNum);
2632 /* Just setting textD->horizOffset is enough information for redisplay */
2633 textD->horizOffset = horizOffset;
2635 /* Update the scroll bar positions if requested, note: updating the
2636 horizontal scroll bars can have the further side-effect of changing
2637 the horizontal scroll position, textD->horizOffset */
2638 if (updateVScrollBar && textD->vScrollBar != NULL) {
2639 updateVScrollBarRange(textD);
2641 if (updateHScrollBar && textD->hScrollBar != NULL) {
2642 updateHScrollBarRange(textD);
2645 /* Redisplay everything if the window is partially obscured (since
2646 it's too hard to tell what displayed areas are salvageable) or
2647 if there's nothing to recover because the scroll distance is large */
2648 xOffset = origHOffset - textD->horizOffset;
2649 yOffset = lineDelta * fontHeight;
2650 if (textD->visibility != VisibilityUnobscured ||
2651 abs(xOffset) > textD->width || abs(yOffset) > exactHeight) {
2652 TextDTranlateGraphicExposeQueue(textD, xOffset, yOffset, False);
2653 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
2654 textD->height);
2655 } else {
2656 /* If the window is not obscured, paint most of the window using XCopyArea
2657 from existing displayed text, and redraw only what's necessary */
2658 /* Recover the useable window areas by moving to the proper location */
2659 srcX = textD->left + (xOffset >= 0 ? 0 : -xOffset);
2660 dstX = textD->left + (xOffset >= 0 ? xOffset : 0);
2661 width = textD->width - abs(xOffset);
2662 srcY = textD->top + (yOffset >= 0 ? 0 : -yOffset);
2663 dstY = textD->top + (yOffset >= 0 ? yOffset : 0);
2664 height = exactHeight - abs(yOffset);
2665 resetClipRectangles(textD);
2666 TextDTranlateGraphicExposeQueue(textD, xOffset, yOffset, True);
2667 XCopyArea(XtDisplay(textD->w), XtWindow(textD->w), XtWindow(textD->w),
2668 textD->gc, srcX, srcY, width, height, dstX, dstY);
2669 /* redraw the un-recoverable parts */
2670 if (yOffset > 0) {
2671 TextDRedisplayRect(textD, textD->left, textD->top,
2672 textD->width, yOffset);
2674 else if (yOffset < 0) {
2675 TextDRedisplayRect(textD, textD->left, textD->top +
2676 textD->height + yOffset, textD->width, -yOffset);
2678 if (xOffset > 0) {
2679 TextDRedisplayRect(textD, textD->left, textD->top,
2680 xOffset, textD->height);
2682 else if (xOffset < 0) {
2683 TextDRedisplayRect(textD, textD->left + textD->width + xOffset,
2684 textD->top, -xOffset, textD->height);
2686 /* Restore protruding parts of the cursor */
2687 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
2690 /* Refresh line number/calltip display if its up and we've scrolled
2691 vertically */
2692 if (lineDelta != 0) {
2693 redrawLineNumbers(textD, False);
2694 TextDRedrawCalltip(textD, 0);
2697 HandleAllPendingGraphicsExposeNoExposeEvents((TextWidget)textD->w, NULL);
2701 ** Update the minimum, maximum, slider size, page increment, and value
2702 ** for vertical scroll bar.
2704 static void updateVScrollBarRange(textDisp *textD)
2706 int sliderSize, sliderMax, sliderValue;
2708 if (textD->vScrollBar == NULL)
2709 return;
2711 /* The Vert. scroll bar value and slider size directly represent the top
2712 line number, and the number of visible lines respectively. The scroll
2713 bar maximum value is chosen to generally represent the size of the whole
2714 buffer, with minor adjustments to keep the scroll bar widget happy */
2715 sliderSize = max(textD->nVisibleLines, 1); /* Avoid X warning (size < 1) */
2716 sliderValue = textD->topLineNum;
2717 sliderMax = max(textD->nBufferLines + 2 +
2718 TEXT_OF_TEXTD(textD).cursorVPadding,
2719 sliderSize + sliderValue);
2720 XtVaSetValues(textD->vScrollBar,
2721 XmNmaximum, sliderMax,
2722 XmNsliderSize, sliderSize,
2723 XmNpageIncrement, max(1, textD->nVisibleLines - 1),
2724 XmNvalue, sliderValue, NULL);
2728 ** Update the minimum, maximum, slider size, page increment, and value
2729 ** for the horizontal scroll bar. If scroll position is such that there
2730 ** is blank space to the right of all lines of text, scroll back (adjust
2731 ** horizOffset but don't redraw) to take up the slack and position the
2732 ** right edge of the text at the right edge of the display.
2734 ** Note, there is some cost to this routine, since it scans the whole range
2735 ** of displayed text, particularly since it's usually called for each typed
2736 ** character!
2738 static int updateHScrollBarRange(textDisp *textD)
2740 int i, maxWidth = 0, sliderMax, sliderWidth;
2741 int origHOffset = textD->horizOffset;
2743 if (textD->hScrollBar == NULL || !XtIsManaged(textD->hScrollBar))
2744 return False;
2746 /* Scan all the displayed lines to find the width of the longest line */
2747 for (i=0; i<textD->nVisibleLines && textD->lineStarts[i]!= -1; i++)
2748 maxWidth = max(measureVisLine(textD, i), maxWidth);
2750 /* If the scroll position is beyond what's necessary to keep all lines
2751 in view, scroll to the left to bring the end of the longest line to
2752 the right margin */
2753 if (maxWidth < textD->width + textD->horizOffset && textD->horizOffset > 0)
2754 textD->horizOffset = max(0, maxWidth - textD->width);
2756 /* Readjust the scroll bar */
2757 sliderWidth = textD->width;
2758 sliderMax = max(maxWidth, sliderWidth + textD->horizOffset);
2759 XtVaSetValues(textD->hScrollBar,
2760 XmNmaximum, sliderMax,
2761 XmNsliderSize, sliderWidth,
2762 XmNpageIncrement, max(textD->width - 100, 10),
2763 XmNvalue, textD->horizOffset, NULL);
2765 /* Return True if scroll position was changed */
2766 return origHOffset != textD->horizOffset;
2770 ** Define area for drawing line numbers. A width of 0 disables line
2771 ** number drawing.
2773 void TextDSetLineNumberArea(textDisp *textD, int lineNumLeft, int lineNumWidth,
2774 int textLeft)
2776 int newWidth = textD->width + textD->left - textLeft;
2777 textD->lineNumLeft = lineNumLeft;
2778 textD->lineNumWidth = lineNumWidth;
2779 textD->left = textLeft;
2780 XClearWindow(XtDisplay(textD->w), XtWindow(textD->w));
2781 resetAbsLineNum(textD);
2782 TextDResize(textD, newWidth, textD->height);
2783 TextDRedisplayRect(textD, 0, textD->top, INT_MAX, textD->height);
2787 ** Refresh the line number area. If clearAll is False, writes only over
2788 ** the character cell areas. Setting clearAll to True will clear out any
2789 ** stray marks outside of the character cell area, which might have been
2790 ** left from before a resize or font change.
2792 static void redrawLineNumbers(textDisp *textD, int clearAll)
2794 int y, line, visLine, nCols, lineStart;
2795 char lineNumString[12];
2796 int lineHeight = textD->ascent + textD->descent;
2797 int charWidth = textD->fontStruct->max_bounds.width;
2798 XRectangle clipRect;
2799 Display *display = XtDisplay(textD->w);
2801 /* Don't draw if lineNumWidth == 0 (line numbers are hidden), or widget is
2802 not yet realized */
2803 if (textD->lineNumWidth == 0 || XtWindow(textD->w) == 0)
2804 return;
2806 /* Make sure we reset the clipping range for the line numbers GC, because
2807 the GC may be shared (eg, if the line numbers and text have the same
2808 color) and therefore the clipping ranges may be invalid. */
2809 clipRect.x = textD->lineNumLeft;
2810 clipRect.y = textD->top;
2811 clipRect.width = textD->lineNumWidth;
2812 clipRect.height = textD->height;
2813 XSetClipRectangles(display, textD->lineNumGC, 0, 0,
2814 &clipRect, 1, Unsorted);
2816 /* Erase the previous contents of the line number area, if requested */
2817 if (clearAll)
2818 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->lineNumLeft,
2819 textD->top, textD->lineNumWidth, textD->height, False);
2821 /* Draw the line numbers, aligned to the text */
2822 nCols = min(11, textD->lineNumWidth / charWidth);
2823 y = textD->top;
2824 line = getAbsTopLineNum(textD);
2825 for (visLine=0; visLine < textD->nVisibleLines; visLine++) {
2826 lineStart = textD->lineStarts[visLine];
2827 if (lineStart != -1 && (lineStart==0 ||
2828 BufGetCharacter(textD->buffer, lineStart-1)=='\n')) {
2829 sprintf(lineNumString, "%*d", nCols, line);
2830 XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w),
2831 textD->lineNumGC, textD->lineNumLeft, y + textD->ascent,
2832 lineNumString, strlen(lineNumString));
2833 line++;
2834 } else {
2835 XClearArea(XtDisplay(textD->w), XtWindow(textD->w),
2836 textD->lineNumLeft, y, textD->lineNumWidth,
2837 textD->ascent + textD->descent, False);
2838 if (visLine == 0)
2839 line++;
2841 y += lineHeight;
2846 ** Callbacks for drag or valueChanged on scroll bars
2848 static void vScrollCB(Widget w, XtPointer clientData, XtPointer callData)
2850 textDisp *textD = (textDisp *)clientData;
2851 int newValue = ((XmScrollBarCallbackStruct *)callData)->value;
2852 int lineDelta = newValue - textD->topLineNum;
2854 if (lineDelta == 0)
2855 return;
2856 setScroll(textD, newValue, textD->horizOffset, False, True);
2858 static void hScrollCB(Widget w, XtPointer clientData, XtPointer callData)
2860 textDisp *textD = (textDisp *)clientData;
2861 int newValue = ((XmScrollBarCallbackStruct *)callData)->value;
2863 if (newValue == textD->horizOffset)
2864 return;
2865 setScroll(textD, textD->topLineNum, newValue, False, False);
2868 static void visibilityEH(Widget w, XtPointer data, XEvent *event,
2869 Boolean *continueDispatch)
2871 /* Record whether the window is fully visible or not. This information
2872 is used for choosing the scrolling methodology for optimal performance,
2873 if the window is partially obscured, XCopyArea may not work */
2874 ((textDisp *)data)->visibility = ((XVisibilityEvent *)event)->state;
2877 static int max(int i1, int i2)
2879 return i1 >= i2 ? i1 : i2;
2882 static int min(int i1, int i2)
2884 return i1 <= i2 ? i1 : i2;
2888 ** Count the number of newlines in a null-terminated text string;
2890 static int countLines(char *string)
2892 char *c;
2893 int lineCount = 0;
2895 if (string == NULL)
2896 return 0;
2897 for (c=string; *c!='\0'; c++)
2898 if (*c == '\n') lineCount++;
2899 return lineCount;
2903 ** Return the width in pixels of the displayed line pointed to by "visLineNum"
2905 static int measureVisLine(textDisp *textD, int visLineNum)
2907 int i, width = 0, len, style, lineLen = visLineLength(textD, visLineNum);
2908 int charCount = 0, lineStartPos = textD->lineStarts[visLineNum];
2909 char expandedChar[MAX_EXP_CHAR_LEN];
2911 if (textD->styleBuffer == NULL) {
2912 for (i=0; i<lineLen; i++) {
2913 len = BufGetExpandedChar(textD->buffer, lineStartPos + i,
2914 charCount, expandedChar);
2915 width += XTextWidth(textD->fontStruct, expandedChar, len);
2916 charCount += len;
2918 } else {
2919 for (i=0; i<lineLen; i++) {
2920 len = BufGetExpandedChar(textD->buffer, lineStartPos+i,
2921 charCount, expandedChar);
2922 style = (unsigned char)BufGetCharacter(textD->styleBuffer,
2923 lineStartPos+i) - ASCII_A;
2924 width += XTextWidth(textD->styleTable[style].font, expandedChar,
2925 len);
2926 charCount += len;
2929 return width;
2933 ** Return true if there are lines visible with no corresponding buffer text
2935 static int emptyLinesVisible(textDisp *textD)
2937 return textD->nVisibleLines > 0 &&
2938 textD->lineStarts[textD->nVisibleLines-1] == -1;
2942 ** When the cursor is at the left or right edge of the text, part of it
2943 ** sticks off into the clipped region beyond the text. Normal redrawing
2944 ** can not overwrite this protruding part of the cursor, so it must be
2945 ** erased independently by calling this routine.
2947 static void blankCursorProtrusions(textDisp *textD)
2949 int x, width, cursorX = textD->cursorX, cursorY = textD->cursorY;
2950 int fontWidth = textD->fontStruct->max_bounds.width;
2951 int fontHeight = textD->ascent + textD->descent;
2952 int cursorWidth, left = textD->left, right = left + textD->width;
2954 cursorWidth = (fontWidth/3) * 2;
2955 if (cursorX >= left-1 && cursorX <= left + cursorWidth/2 - 1) {
2956 x = cursorX - cursorWidth/2;
2957 width = left - x;
2958 } else if (cursorX >= right - cursorWidth/2 && cursorX <= right) {
2959 x = right;
2960 width = cursorX + cursorWidth/2 + 2 - right;
2961 } else
2962 return;
2964 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), x, cursorY,
2965 width, fontHeight, False);
2969 ** Allocate shared graphics contexts used by the widget, which must be
2970 ** re-allocated on a font change.
2972 static void allocateFixedFontGCs(textDisp *textD, XFontStruct *fontStruct,
2973 Pixel bgPixel, Pixel fgPixel, Pixel selectFGPixel, Pixel selectBGPixel,
2974 Pixel highlightFGPixel, Pixel highlightBGPixel, Pixel lineNumFGPixel)
2976 textD->gc = allocateGC(textD->w, GCFont | GCForeground | GCBackground,
2977 fgPixel, bgPixel, fontStruct->fid, GCClipMask, GCArcMode);
2978 textD->selectGC = allocateGC(textD->w, GCFont | GCForeground | GCBackground,
2979 selectFGPixel, selectBGPixel, fontStruct->fid, GCClipMask,
2980 GCArcMode);
2981 textD->selectBGGC = allocateGC(textD->w, GCForeground, selectBGPixel, 0,
2982 fontStruct->fid, GCClipMask, GCArcMode);
2983 textD->highlightGC = allocateGC(textD->w, GCFont|GCForeground|GCBackground,
2984 highlightFGPixel, highlightBGPixel, fontStruct->fid, GCClipMask,
2985 GCArcMode);
2986 textD->highlightBGGC = allocateGC(textD->w, GCForeground, highlightBGPixel,
2987 0, fontStruct->fid, GCClipMask, GCArcMode);
2988 textD->lineNumGC = allocateGC(textD->w, GCFont | GCForeground |
2989 GCBackground, lineNumFGPixel, bgPixel, fontStruct->fid,
2990 GCClipMask, GCArcMode);
2994 ** X11R4 does not have the XtAllocateGC function for sharing graphics contexts
2995 ** with changeable fields. Unfortunately the R4 call for creating shared
2996 ** graphics contexts (XtGetGC) is rarely useful because most widgets need
2997 ** to be able to set and change clipping, and that makes the GC unshareable.
2999 ** This function allocates and returns a gc, using XtAllocateGC if possible,
3000 ** or XCreateGC on X11R4 systems where XtAllocateGC is not available.
3002 static GC allocateGC(Widget w, unsigned long valueMask,
3003 unsigned long foreground, unsigned long background, Font font,
3004 unsigned long dynamicMask, unsigned long dontCareMask)
3006 XGCValues gcValues;
3008 gcValues.font = font;
3009 gcValues.background = background;
3010 gcValues.foreground = foreground;
3011 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4
3012 return XtAllocateGC(w, 0, valueMask, &gcValues, dynamicMask,
3013 dontCareMask);
3014 #else
3015 return XCreateGC(XtDisplay(w), RootWindowOfScreen(XtScreen(w)),
3016 valueMask, &gcValues);
3017 #endif
3021 ** Release a gc allocated with allocateGC above
3023 static void releaseGC(Widget w, GC gc)
3025 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4
3026 XtReleaseGC(w, gc);
3027 #else
3028 XFreeGC(XtDisplay(w), gc);
3029 #endif
3033 ** resetClipRectangles sets the clipping rectangles for GCs which clip
3034 ** at the text boundary (as opposed to the window boundary). These GCs
3035 ** are shared such that the drawing styles are constant, but the clipping
3036 ** rectangles are allowed to change among different users of the GCs (the
3037 ** GCs were created with XtAllocGC). This routine resets them so the clipping
3038 ** rectangles are correct for this text display.
3040 static void resetClipRectangles(textDisp *textD)
3042 XRectangle clipRect;
3043 Display *display = XtDisplay(textD->w);
3045 clipRect.x = textD->left;
3046 clipRect.y = textD->top;
3047 clipRect.width = textD->width;
3048 clipRect.height = textD->height - textD->height %
3049 (textD->ascent + textD->descent);
3051 XSetClipRectangles(display, textD->gc, 0, 0,
3052 &clipRect, 1, Unsorted);
3053 XSetClipRectangles(display, textD->selectGC, 0, 0,
3054 &clipRect, 1, Unsorted);
3055 XSetClipRectangles(display, textD->highlightGC, 0, 0,
3056 &clipRect, 1, Unsorted);
3057 XSetClipRectangles(display, textD->selectBGGC, 0, 0,
3058 &clipRect, 1, Unsorted);
3059 XSetClipRectangles(display, textD->highlightBGGC, 0, 0,
3060 &clipRect, 1, Unsorted);
3061 XSetClipRectangles(display, textD->styleGC, 0, 0,
3062 &clipRect, 1, Unsorted);
3066 ** Return the length of a line (number of displayable characters) by examining
3067 ** entries in the line starts array rather than by scanning for newlines
3069 static int visLineLength(textDisp *textD, int visLineNum)
3071 int nextLineStart, lineStartPos = textD->lineStarts[visLineNum];
3073 if (lineStartPos == -1)
3074 return 0;
3075 if (visLineNum+1 >= textD->nVisibleLines)
3076 return textD->lastChar - lineStartPos;
3077 nextLineStart = textD->lineStarts[visLineNum+1];
3078 if (nextLineStart == -1)
3079 return textD->lastChar - lineStartPos;
3080 if (wrapUsesCharacter(textD, nextLineStart-1))
3081 return nextLineStart-1 - lineStartPos;
3082 return nextLineStart - lineStartPos;
3086 ** When continuous wrap is on, and the user inserts or deletes characters,
3087 ** wrapping can happen before and beyond the changed position. This routine
3088 ** finds the extent of the changes, and counts the deleted and inserted lines
3089 ** over that range. It also attempts to minimize the size of the range to
3090 ** what has to be counted and re-displayed, so the results can be useful
3091 ** both for delimiting where the line starts need to be recalculated, and
3092 ** for deciding what part of the text to redisplay.
3094 static void findWrapRange(textDisp *textD, char *deletedText, int pos,
3095 int nInserted, int nDeleted, int *modRangeStart, int *modRangeEnd,
3096 int *linesInserted, int *linesDeleted)
3098 int length, retPos, retLines, retLineStart, retLineEnd;
3099 textBuffer *deletedTextBuf, *buf = textD->buffer;
3100 int nVisLines = textD->nVisibleLines;
3101 int *lineStarts = textD->lineStarts;
3102 int countFrom, countTo, lineStart, adjLineStart, i;
3103 int visLineNum = 0, nLines = 0;
3106 ** Determine where to begin searching: either the previous newline, or
3107 ** if possible, limit to the start of the (original) previous displayed
3108 ** line, using information from the existing line starts array
3110 if (pos >= textD->firstChar && pos <= textD->lastChar) {
3111 for (i=nVisLines-1; i>0; i--)
3112 if (lineStarts[i] != -1 && pos >= lineStarts[i])
3113 break;
3114 if (i > 0) {
3115 countFrom = lineStarts[i-1];
3116 visLineNum = i-1;
3117 } else
3118 countFrom = BufStartOfLine(buf, pos);
3119 } else
3120 countFrom = BufStartOfLine(buf, pos);
3124 ** Move forward through the (new) text one line at a time, counting
3125 ** displayed lines, and looking for either a real newline, or for the
3126 ** line starts to re-sync with the original line starts array
3128 lineStart = countFrom;
3129 *modRangeStart = countFrom;
3130 while (True) {
3132 /* advance to the next line. If the line ended in a real newline
3133 or the end of the buffer, that's far enough */
3134 wrappedLineCounter(textD, buf, lineStart, buf->length, 1, True, 0,
3135 &retPos, &retLines, &retLineStart, &retLineEnd);
3136 if (retPos >= buf->length) {
3137 countTo = buf->length;
3138 *modRangeEnd = countTo;
3139 if (retPos != retLineEnd)
3140 nLines++;
3141 break;
3142 } else
3143 lineStart = retPos;
3144 nLines++;
3145 if (lineStart > pos + nInserted &&
3146 BufGetCharacter(buf, lineStart-1) == '\n') {
3147 countTo = lineStart;
3148 *modRangeEnd = lineStart;
3149 break;
3152 /* Don't try to resync in continuous wrap mode with non-fixed font
3153 sizes; it would result in a chicken-and-egg dependency between
3154 the calculations for the inserted and the deleted lines.
3155 If we're in that mode, the number of deleted lines is calculated in
3156 advance, without resynchronization, so we shouldn't resynchronize
3157 for the inserted lines either. */
3158 if (textD->suppressResync)
3159 continue;
3161 /* check for synchronization with the original line starts array
3162 before pos, if so, the modified range can begin later */
3163 if (lineStart <= pos) {
3164 while (visLineNum<nVisLines && lineStarts[visLineNum] < lineStart)
3165 visLineNum++;
3166 if (visLineNum < nVisLines && lineStarts[visLineNum] == lineStart) {
3167 countFrom = lineStart;
3168 nLines = 0;
3169 if (visLineNum+1 < nVisLines && lineStarts[visLineNum+1] != -1)
3170 *modRangeStart = min(pos, lineStarts[visLineNum+1]-1);
3171 else
3172 *modRangeStart = countFrom;
3173 } else
3174 *modRangeStart = min(*modRangeStart, lineStart-1);
3177 /* check for synchronization with the original line starts array
3178 after pos, if so, the modified range can end early */
3179 else if (lineStart > pos + nInserted) {
3180 adjLineStart = lineStart - nInserted + nDeleted;
3181 while (visLineNum<nVisLines && lineStarts[visLineNum]<adjLineStart)
3182 visLineNum++;
3183 if (visLineNum < nVisLines && lineStarts[visLineNum] != -1 &&
3184 lineStarts[visLineNum] == adjLineStart) {
3185 countTo = TextDEndOfLine(textD, lineStart, True);
3186 *modRangeEnd = lineStart;
3187 break;
3191 *linesInserted = nLines;
3194 /* Count deleted lines between countFrom and countTo as the text existed
3195 before the modification (that is, as if the text between pos and
3196 pos+nInserted were replaced by "deletedText"). This extra context is
3197 necessary because wrapping can occur outside of the modified region
3198 as a result of adding or deleting text in the region. This is done by
3199 creating a textBuffer containing the deleted text and the necessary
3200 additional context, and calling the wrappedLineCounter on it.
3202 NOTE: This must not be done in continuous wrap mode when the font
3203 width is not fixed. In that case, the calculation would try
3204 to access style information that is no longer available (deleted
3205 text), or out of date (updated highlighting), possibly leading
3206 to completely wrong calculations and/or even crashes eventually.
3207 (This is not theoretical; it really happened.)
3209 In that case, the calculation of the number of deleted lines
3210 has happened before the buffer was modified (only in that case,
3211 because resynchronization of the line starts is impossible
3212 in that case, which makes the whole calculation less efficient).
3214 if (textD->suppressResync) {
3215 *linesDeleted = textD->nLinesDeleted;
3216 textD->suppressResync = 0;
3217 return;
3220 length = (pos-countFrom) + nDeleted +(countTo-(pos+nInserted));
3221 deletedTextBuf = BufCreatePreallocated(length);
3222 if (pos > countFrom)
3223 BufCopyFromBuf(textD->buffer, deletedTextBuf, countFrom, pos, 0);
3224 if (nDeleted != 0)
3225 BufInsert(deletedTextBuf, pos-countFrom, deletedText);
3226 if (countTo > pos+nInserted)
3227 BufCopyFromBuf(textD->buffer, deletedTextBuf,
3228 pos+nInserted, countTo, pos-countFrom+nDeleted);
3229 /* Note that we need to take into account an offset for the style buffer:
3230 the deletedTextBuf can be out of sync with the style buffer. */
3231 wrappedLineCounter(textD, deletedTextBuf, 0, length, INT_MAX, True,
3232 countFrom, &retPos, &retLines, &retLineStart, &retLineEnd);
3233 BufFree(deletedTextBuf);
3234 *linesDeleted = retLines;
3235 textD->suppressResync = 0;
3239 ** This is a stripped-down version of the findWrapRange() function above,
3240 ** intended to be used to calculate the number of "deleted" lines during
3241 ** a buffer modification. It is called _before_ the modification takes place.
3243 ** This function should only be called in continuous wrap mode with a
3244 ** non-fixed font width. In that case, it is impossible to calculate
3245 ** the number of deleted lines, because the necessary style information
3246 ** is no longer available _after_ the modification. In other cases, we
3247 ** can still perform the calculation afterwards (possibly even more
3248 ** efficiently).
3250 static void measureDeletedLines(textDisp *textD, int pos, int nDeleted)
3252 int retPos, retLines, retLineStart, retLineEnd;
3253 textBuffer *buf = textD->buffer;
3254 int nVisLines = textD->nVisibleLines;
3255 int *lineStarts = textD->lineStarts;
3256 int countFrom, lineStart;
3257 int nLines = 0, i;
3259 ** Determine where to begin searching: either the previous newline, or
3260 ** if possible, limit to the start of the (original) previous displayed
3261 ** line, using information from the existing line starts array
3263 if (pos >= textD->firstChar && pos <= textD->lastChar) {
3264 for (i=nVisLines-1; i>0; i--)
3265 if (lineStarts[i] != -1 && pos >= lineStarts[i])
3266 break;
3267 if (i > 0) {
3268 countFrom = lineStarts[i-1];
3269 } else
3270 countFrom = BufStartOfLine(buf, pos);
3271 } else
3272 countFrom = BufStartOfLine(buf, pos);
3275 ** Move forward through the (new) text one line at a time, counting
3276 ** displayed lines, and looking for either a real newline, or for the
3277 ** line starts to re-sync with the original line starts array
3279 lineStart = countFrom;
3280 while (True) {
3281 /* advance to the next line. If the line ended in a real newline
3282 or the end of the buffer, that's far enough */
3283 wrappedLineCounter(textD, buf, lineStart, buf->length, 1, True, 0,
3284 &retPos, &retLines, &retLineStart, &retLineEnd);
3285 if (retPos >= buf->length) {
3286 if (retPos != retLineEnd)
3287 nLines++;
3288 break;
3289 } else
3290 lineStart = retPos;
3291 nLines++;
3292 if (lineStart > pos + nDeleted &&
3293 BufGetCharacter(buf, lineStart-1) == '\n') {
3294 break;
3297 /* Unlike in the findWrapRange() function above, we don't try to
3298 resync with the line starts, because we don't know the length
3299 of the inserted text yet, nor the updated style information.
3301 Because of that, we also shouldn't resync with the line starts
3302 after the modification either, because we must perform the
3303 calculations for the deleted and inserted lines in the same way.
3305 This can result in some unnecessary recalculation and redrawing
3306 overhead, and therefore we should only use this two-phase mode
3307 of calculation when it's really needed (continuous wrap + variable
3308 font width). */
3310 textD->nLinesDeleted = nLines;
3311 textD->suppressResync = 1;
3315 ** Count forward from startPos to either maxPos or maxLines (whichever is
3316 ** reached first), and return all relevant positions and line count.
3317 ** The provided textBuffer may differ from the actual text buffer of the
3318 ** widget. In that case it must be a (partial) copy of the actual text buffer
3319 ** and the styleBufOffset argument must indicate the starting position of the
3320 ** copy, to take into account the correct style information.
3322 ** Returned values:
3324 ** retPos: Position where counting ended. When counting lines, the
3325 ** position returned is the start of the line "maxLines"
3326 ** lines beyond "startPos".
3327 ** retLines: Number of line breaks counted
3328 ** retLineStart: Start of the line where counting ended
3329 ** retLineEnd: End position of the last line traversed
3331 static void wrappedLineCounter(textDisp *textD, textBuffer *buf, int startPos,
3332 int maxPos, int maxLines, int startPosIsLineStart, int styleBufOffset,
3333 int *retPos, int *retLines, int *retLineStart, int *retLineEnd)
3335 int lineStart, newLineStart = 0, b, p, colNum, wrapMargin;
3336 int maxWidth, width, countPixels, i, foundBreak;
3337 int nLines = 0, tabDist = textD->buffer->tabDist;
3338 unsigned char c;
3339 char nullSubsChar = textD->buffer->nullSubsChar;
3341 /* If the font is fixed, or there's a wrap margin set, it's more efficient
3342 to measure in columns, than to count pixels. Determine if we can count
3343 in columns (countPixels == False) or must count pixels (countPixels ==
3344 True), and set the wrap target for either pixels or columns */
3345 if (textD->fixedFontWidth != -1 || textD->wrapMargin != 0) {
3346 countPixels = False;
3347 wrapMargin = textD->wrapMargin != 0 ? textD->wrapMargin :
3348 textD->width / textD->fixedFontWidth;
3349 maxWidth = INT_MAX;
3350 } else {
3351 countPixels = True;
3352 wrapMargin = INT_MAX;
3353 maxWidth = textD->width;
3356 /* Find the start of the line if the start pos is not marked as a
3357 line start. */
3358 if (startPosIsLineStart)
3359 lineStart = startPos;
3360 else
3361 lineStart = TextDStartOfLine(textD, startPos);
3364 ** Loop until position exceeds maxPos or line count exceeds maxLines.
3365 ** (actually, contines beyond maxPos to end of line containing maxPos,
3366 ** in case later characters cause a word wrap back before maxPos)
3368 colNum = 0;
3369 width = 0;
3370 for (p=lineStart; p<buf->length; p++) {
3371 c = BufGetCharacter(buf, p);
3373 /* If the character was a newline, count the line and start over,
3374 otherwise, add it to the width and column counts */
3375 if (c == '\n') {
3376 if (p >= maxPos) {
3377 *retPos = maxPos;
3378 *retLines = nLines;
3379 *retLineStart = lineStart;
3380 *retLineEnd = maxPos;
3381 return;
3383 nLines++;
3384 if (nLines >= maxLines) {
3385 *retPos = p + 1;
3386 *retLines = nLines;
3387 *retLineStart = p + 1;
3388 *retLineEnd = p;
3389 return;
3391 lineStart = p + 1;
3392 colNum = 0;
3393 width = 0;
3394 } else {
3395 colNum += BufCharWidth(c, colNum, tabDist, nullSubsChar);
3396 if (countPixels)
3397 width += measurePropChar(textD, c, colNum, p+styleBufOffset);
3400 /* If character exceeded wrap margin, find the break point
3401 and wrap there */
3402 if (colNum > wrapMargin || width > maxWidth) {
3403 foundBreak = False;
3404 for (b=p; b>=lineStart; b--) {
3405 c = BufGetCharacter(buf, b);
3406 if (c == '\t' || c == ' ') {
3407 newLineStart = b + 1;
3408 if (countPixels) {
3409 colNum = 0;
3410 width = 0;
3411 for (i=b+1; i<p+1; i++) {
3412 width += measurePropChar(textD,
3413 BufGetCharacter(buf, i), colNum,
3414 i+styleBufOffset);
3415 colNum++;
3417 } else
3418 colNum = BufCountDispChars(buf, b+1, p+1);
3419 foundBreak = True;
3420 break;
3423 if (!foundBreak) { /* no whitespace, just break at margin */
3424 newLineStart = max(p, lineStart+1);
3425 colNum = BufCharWidth(c, colNum, tabDist, nullSubsChar);
3426 if (countPixels)
3427 width = measurePropChar(textD, c, colNum, p+styleBufOffset);
3429 if (p >= maxPos) {
3430 *retPos = maxPos;
3431 *retLines = maxPos < newLineStart ? nLines : nLines + 1;
3432 *retLineStart = maxPos < newLineStart ? lineStart :
3433 newLineStart;
3434 *retLineEnd = maxPos;
3435 return;
3437 nLines++;
3438 if (nLines >= maxLines) {
3439 *retPos = foundBreak ? b + 1 : max(p, lineStart+1);
3440 *retLines = nLines;
3441 *retLineStart = lineStart;
3442 *retLineEnd = foundBreak ? b : p;
3443 return;
3445 lineStart = newLineStart;
3449 /* reached end of buffer before reaching pos or line target */
3450 *retPos = buf->length;
3451 *retLines = nLines;
3452 *retLineStart = lineStart;
3453 *retLineEnd = buf->length;
3457 ** Measure the width in pixels of a character "c" at a particular column
3458 ** "colNum" and buffer position "pos". This is for measuring characters in
3459 ** proportional or mixed-width highlighting fonts.
3461 ** A note about proportional and mixed-width fonts: the mixed width and
3462 ** proportional font code in nedit does not get much use in general editing,
3463 ** because nedit doesn't allow per-language-mode fonts, and editing programs
3464 ** in a proportional font is usually a bad idea, so very few users would
3465 ** choose a proportional font as a default. There are still probably mixed-
3466 ** width syntax highlighting cases where things don't redraw properly for
3467 ** insertion/deletion, though static display and wrapping and resizing
3468 ** should now be solid because they are now used for online help display.
3470 static int measurePropChar(textDisp *textD, char c, int colNum, int pos)
3472 int charLen, style;
3473 char expChar[MAX_EXP_CHAR_LEN];
3474 textBuffer *styleBuf = textD->styleBuffer;
3476 charLen = BufExpandCharacter(c, colNum, expChar,
3477 textD->buffer->tabDist, textD->buffer->nullSubsChar);
3478 if (styleBuf == NULL) {
3479 style = 0;
3480 } else {
3481 style = (unsigned char)BufGetCharacter(styleBuf, pos);
3482 if (style == textD->unfinishedStyle) {
3483 /* encountered "unfinished" style, trigger parsing */
3484 (textD->unfinishedHighlightCB)(textD, pos, textD->highlightCBArg);
3485 style = (unsigned char)BufGetCharacter(styleBuf, pos);
3488 return stringWidth(textD, expChar, charLen, style);
3492 ** Finds both the end of the current line and the start of the next line. Why?
3493 ** In continuous wrap mode, if you need to know both, figuring out one from the
3494 ** other can be expensive or error prone. The problem comes when there's a
3495 ** trailing space or tab just before the end of the buffer. To translate an
3496 ** end of line value to or from the next lines start value, you need to know
3497 ** whether the trailing space or tab is being used as a line break or just a
3498 ** normal character, and to find that out would otherwise require counting all
3499 ** the way back to the beginning of the line.
3501 static void findLineEnd(textDisp *textD, int startPos, int startPosIsLineStart,
3502 int *lineEnd, int *nextLineStart)
3504 int retLines, retLineStart;
3506 /* if we're not wrapping use more efficient BufEndOfLine */
3507 if (!textD->continuousWrap) {
3508 *lineEnd = BufEndOfLine(textD->buffer, startPos);
3509 *nextLineStart = min(textD->buffer->length, *lineEnd + 1);
3510 return;
3513 /* use the wrapped line counter routine to count forward one line */
3514 wrappedLineCounter(textD, textD->buffer, startPos, textD->buffer->length,
3515 1, startPosIsLineStart, 0, nextLineStart, &retLines,
3516 &retLineStart, lineEnd);
3517 return;
3521 ** Line breaks in continuous wrap mode usually happen at newlines or
3522 ** whitespace. This line-terminating character is not included in line
3523 ** width measurements and has a special status as a non-visible character.
3524 ** However, lines with no whitespace are wrapped without the benefit of a
3525 ** line terminating character, and this distinction causes endless trouble
3526 ** with all of the text display code which was originally written without
3527 ** continuous wrap mode and always expects to wrap at a newline character.
3529 ** Given the position of the end of the line, as returned by TextDEndOfLine
3530 ** or BufEndOfLine, this returns true if there is a line terminating
3531 ** character, and false if there's not. On the last character in the
3532 ** buffer, this function can't tell for certain whether a trailing space was
3533 ** used as a wrap point, and just guesses that it wasn't. So if an exact
3534 ** accounting is necessary, don't use this function.
3536 static int wrapUsesCharacter(textDisp *textD, int lineEndPos)
3538 char c;
3540 if (!textD->continuousWrap || lineEndPos == textD->buffer->length)
3541 return True;
3543 c = BufGetCharacter(textD->buffer, lineEndPos);
3544 return c == '\n' || ((c == '\t' || c == ' ') &&
3545 lineEndPos + 1 != textD->buffer->length);
3549 ** Decide whether the user needs (or may need) a horizontal scroll bar,
3550 ** and manage or unmanage the scroll bar widget accordingly. The H.
3551 ** scroll bar is only hidden in continuous wrap mode when it's absolutely
3552 ** certain that the user will not need it: when wrapping is set
3553 ** to the window edge, or when the wrap margin is strictly less than
3554 ** the longest possible line.
3556 static void hideOrShowHScrollBar(textDisp *textD)
3558 if (textD->continuousWrap && (textD->wrapMargin == 0 || textD->wrapMargin *
3559 textD->fontStruct->max_bounds.width < textD->width))
3560 XtUnmanageChild(textD->hScrollBar);
3561 else
3562 XtManageChild(textD->hScrollBar);
3566 ** Return true if the selection "sel" is rectangular, and touches a
3567 ** buffer position withing "rangeStart" to "rangeEnd"
3569 static int rangeTouchesRectSel(selection *sel, int rangeStart, int rangeEnd)
3571 return sel->selected && sel->rectangular && sel->end >= rangeStart &&
3572 sel->start <= rangeEnd;
3576 ** Extend the range of a redraw request (from *start to *end) with additional
3577 ** redraw requests resulting from changes to the attached style buffer (which
3578 ** contains auxiliary information for coloring or styling text).
3580 static void extendRangeForStyleMods(textDisp *textD, int *start, int *end)
3582 selection *sel = &textD->styleBuffer->primary;
3583 int extended = False;
3585 /* The peculiar protocol used here is that modifications to the style
3586 buffer are marked by selecting them with the buffer's primary selection.
3587 The style buffer is usually modified in response to a modify callback on
3588 the text buffer BEFORE textDisp.c's modify callback, so that it can keep
3589 the style buffer in step with the text buffer. The style-update
3590 callback can't just call for a redraw, because textDisp hasn't processed
3591 the original text changes yet. Anyhow, to minimize redrawing and to
3592 avoid the complexity of scheduling redraws later, this simple protocol
3593 tells the text display's buffer modify callback to extend it's redraw
3594 range to show the text color/and font changes as well. */
3595 if (sel->selected) {
3596 if (sel->start < *start) {
3597 *start = sel->start;
3598 extended = True;
3600 if (sel->end > *end) {
3601 *end = sel->end;
3602 extended = True;
3606 /* If the selection was extended due to a style change, and some of the
3607 fonts don't match in spacing, extend redraw area to end of line to
3608 redraw characters exposed by possible font size changes */
3609 if (textD->fixedFontWidth == -1 && extended)
3610 *end = BufEndOfLine(textD->buffer, *end) + 1;
3613 /********************** Backlight Functions ******************************/
3615 ** Allocate a read-only (shareable) colormap cell for a named color, from the
3616 ** the default colormap of the screen on which the widget (w) is displayed. If
3617 ** the colormap is full and there's no suitable substitute, print an error on
3618 ** stderr, and return the widget's background color as a backup.
3620 static Pixel allocBGColor(Widget w, char *colorName, int *ok)
3622 int r,g,b;
3623 *ok = 1;
3624 return AllocColor(w, colorName, &r, &g, &b);
3627 static Pixel getRangesetColor(textDisp *textD, int ind, Pixel bground)
3629 textBuffer *buf;
3630 RangesetTable *tab;
3631 Pixel color;
3632 char *color_name;
3633 int valid;
3635 if (ind > 0) {
3636 ind--;
3637 buf = textD->buffer;
3638 tab = buf->rangesetTable;
3640 valid = RangesetTableGetColorValid(tab, ind, &color);
3641 if (valid == 0) {
3642 color_name = RangesetTableGetColorName(tab, ind);
3643 if (color_name)
3644 color = allocBGColor(textD->w, color_name, &valid);
3645 RangesetTableAssignColorPixel(tab, ind, color, valid);
3647 if (valid > 0) {
3648 return color;
3651 return bground;
3655 ** Read the background color class specification string in str, allocating the
3656 ** necessary colors, and allocating and setting up the character->class_no and
3657 ** class_no->pixel map arrays, returned via *pp_bgClass and *pp_bgClassPixel
3658 ** respectively.
3659 ** Note: the allocation of class numbers could be more intelligent: there can
3660 ** never be more than 256 of these (one per character); but I don't think
3661 ** there'll be a pressing need. I suppose the scanning of the specification
3662 ** could be better too, but then, who cares!
3664 void TextDSetupBGClasses(Widget w, XmString str, Pixel **pp_bgClassPixel,
3665 unsigned char **pp_bgClass, Pixel bgPixelDefault)
3667 unsigned char bgClass[256];
3668 Pixel bgClassPixel[256];
3669 int class_no = 0;
3670 char *semicol;
3671 char *s = (char *)str;
3672 size_t was_semicol;
3673 int lo, hi, dummy;
3674 char *pos;
3675 Boolean is_good = True;
3677 XtFree((char *)*pp_bgClass);
3678 XtFree((char *)*pp_bgClassPixel);
3680 *pp_bgClassPixel = NULL;
3681 *pp_bgClass = NULL;
3683 if (!s)
3684 return;
3686 /* default for all chars is class number zero, for standard background */
3687 memset(bgClassPixel, 0, sizeof bgClassPixel);
3688 memset(bgClass, 0, sizeof bgClass);
3689 bgClassPixel[0] = bgPixelDefault;
3690 /* since class no == 0 in a "style" has no set bits in BACKLIGHT_MASK
3691 (see styleOfPos()), when drawString() is called for text with a
3692 backlight class no of zero, bgClassPixel[0] is never consulted, and
3693 the default background color is chosen. */
3695 /* The format of the class string s is:
3696 low[-high]{,low[-high]}:color{;low-high{,low[-high]}:color}
3698 32-255:#f0f0f0;1-31,127:red;128-159:orange;9-13:#e5e5e5
3699 where low and high represent a character range between ordinal
3700 ASCII values. Using strtol() allows automatic octal, dec and hex
3701 reading of low and high. The example format sets backgrounds as follows:
3702 char 1 - 8 colored red (control characters)
3703 char 9 - 13 colored #e5e5e5 (isspace() control characters)
3704 char 14 - 31 colored red (control characters)
3705 char 32 - 126 colored #f0f0f0
3706 char 127 colored red (delete character)
3707 char 128 - 159 colored orange ("shifted" control characters)
3708 char 160 - 255 colored #f0f0f0
3709 Notice that some of the later ranges overwrite the class values defined
3710 for earlier ones (eg the first clause, 32-255:#f0f0f0 sets the DEL
3711 character background color to #f0f0f0; it is then set to red by the
3712 clause 1-31,127:red). */
3714 while (s && class_no < 255) {
3715 class_no++; /* simple class alloc scheme */
3716 was_semicol = 0;
3717 is_good = True;
3718 if ((semicol = (char *)strchr(s, ';'))) {
3719 *semicol = '\0'; /* null-terminate low[-high]:color clause */
3720 was_semicol = 1;
3723 /* loop over ranges before the color spec, assigning the characters
3724 in the ranges to the current class number */
3725 for (lo = hi = strtol(s, &pos, 0);
3726 is_good;
3727 lo = hi = strtol(pos + 1, &pos, 0)) {
3728 if (pos && *pos == '-')
3729 hi = strtol(pos + 1, &pos, 0); /* get end of range */
3730 is_good = (pos && 0 <= lo && lo <= hi && hi <= 255);
3731 if (is_good)
3732 while (lo <= hi)
3733 bgClass[lo++] = (unsigned char)class_no;
3734 if (*pos != ',')
3735 break;
3737 if ((is_good = (is_good && *pos == ':'))) {
3738 is_good = (*pos++ != '\0'); /* pos now points to color */
3739 bgClassPixel[class_no] = allocBGColor(w, pos, &dummy);
3741 if (!is_good) {
3742 /* complain? this class spec clause (in string s) was faulty */
3745 /* end of loop iterator clauses */
3746 if (was_semicol)
3747 *semicol = ';'; /* un-null-terminate low[-high]:color clause */
3748 s = semicol + was_semicol;
3750 /* when we get here, we've set up our class table and class-to-pixel table
3751 in local variables: now put them into the "real thing" */
3752 if (class_no) {
3753 class_no++; /* bigger than all valid class_nos */
3754 *pp_bgClass = (unsigned char *)XtMalloc(256);
3755 *pp_bgClassPixel = (Pixel *)XtMalloc(class_no * sizeof (Pixel));
3756 if (!*pp_bgClass || !*pp_bgClassPixel) {
3757 XtFree((char *)*pp_bgClass);
3758 XtFree((char *)*pp_bgClassPixel);
3759 return;
3761 memcpy(*pp_bgClass, bgClass, 256);
3762 memcpy(*pp_bgClassPixel, bgClassPixel, class_no * sizeof (Pixel));