482504: Bad CapsLock grab on certain keyboard configurations
[nedit.git] / source / textDisp.c
blobe8dc7293d29227df90fe09a8eb059886d1ee9955
1 static const char CVSID[] = "$Id: textDisp.c,v 1.11 2001/11/16 10:06:34 amai Exp $";
2 /*******************************************************************************
3 * *
4 * textDisp.c - Display text from a text buffer *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. *
12 * *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
20 * Place, Suite 330, Boston, MA 02111-1307 USA *
21 * *
22 * Nirvana Text Editor *
23 * June 15, 1995 *
24 * *
25 * Written by Mark Edel *
26 * *
27 *******************************************************************************/
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <limits.h>
31 #include <Xm/Xm.h>
32 #include <Xm/ScrolledW.h>
33 #include <Xm/ScrollBar.h>
34 #include "textBuf.h"
35 #include "textDisp.h"
37 #define TOP_MARGIN 1
38 #define BOTTOM_MARGIN 1
39 #define LEFT_MARGIN 3
40 #define RIGHT_MARGIN 3
42 /* Masks for text drawing methods. These are or'd together to form an
43 integer which describes what drawing calls to use to draw a string */
44 #define FILL_MASK 0x100
45 #define SECONDARY_MASK 0x200
46 #define PRIMARY_MASK 0x400
47 #define HIGHLIGHT_MASK 0x800
48 #define STYLE_LOOKUP_MASK 0xff
50 /* Maximum displayable line length (how many characters will fit across the
51 widest window). This amount of memory is temporarily allocated from the
52 stack in the redisplayLine routine for drawing strings */
53 #define MAX_DISP_LINE_LEN 1000
55 enum positionTypes {CURSOR_POS, CHARACTER_POS};
57 static void updateLineStarts(textDisp *textD, int pos, int charsInserted,
58 int charsDeleted, int linesInserted, int linesDeleted, int *scrolled);
59 static void offsetLineStarts(textDisp *textD, int newTopLineNum);
60 static void calcLineStarts(textDisp *textD, int startLine, int endLine);
61 static void calcLastChar(textDisp *textD);
62 static int posToVisibleLineNum(textDisp *textD, int pos, int *lineNum);
63 static void redisplayLine(textDisp *textD, int visLineNum, int leftClip,
64 int rightClip, int leftCharIndex, int rightCharIndex);
65 static void drawString(textDisp *textD, int style, int x, int y, int toX,
66 char *string, int nChars);
67 static void clearRect(textDisp *textD, int style, int x, int y,
68 int width, int height);
69 static void drawCursor(textDisp *textD, int x, int y);
70 static int styleOfPos(textDisp *textD, int lineStartPos,
71 int lineLen, int lineIndex, int dispIndex);
72 static int stringWidth(textDisp *textD, char *string, int length, int style);
73 static int inSelection(selection *sel, int pos, int lineStartPos,
74 int dispIndex);
75 static int xyToPos(textDisp *textD, int x, int y, int posType);
76 static void xyToUnconstrainedPos(textDisp *textD, int x, int y, int *row,
77 int *column, int posType);
78 static void bufModifiedCB(int pos, int nInserted, int nDeleted,
79 int nRestyled, char *deletedText, void *cbArg);
80 static void setScroll(textDisp *textD, int topLineNum, int horizOffset,
81 int updateVScrollBar, int updateHScrollBar);
82 static void hScrollCB(Widget w, XtPointer clientData, XtPointer callData);
83 static void vScrollCB(Widget w, XtPointer clientData, XtPointer callData);
84 static void redrawLineNumbers(textDisp *textD, int clearAll);
85 static void visibilityEH(Widget w, XtPointer data, XEvent *event,
86 Boolean *continueDispatch);
87 static void updateVScrollBarRange(textDisp *textD);
88 static int updateHScrollBarRange(textDisp *textD);
89 static int max(int i1, int i2);
90 static int min(int i1, int i2);
91 static int countLines(char *string);
92 static int measureVisLine(textDisp *textD, int visLineNum);
93 static int emptyLinesVisible(textDisp *textD);
94 static void blankCursorProtrusions(textDisp *textD);
95 static void allocateFixedFontGCs(textDisp *textD, XFontStruct *fontStruct,
96 Pixel bgPixel, Pixel fgPixel, Pixel selectFGPixel, Pixel selectBGPixel,
97 Pixel highlightFGPixel, Pixel highlightBGPixel);
98 static GC allocateGC(Widget w, unsigned long valueMask,
99 unsigned long foreground, unsigned long background, Font font,
100 unsigned long dynamicMask, unsigned long dontCareMask);
101 static void releaseGC(Widget w, GC gc);
102 static void resetClipRectangles(textDisp *textD);
103 static int visLineLength(textDisp *textD, int visLineNum);
104 static void findWrapRange(textDisp *textD, char *deletedText, int pos,
105 int nInserted, int nDeleted, int *modRangeStart, int *modRangeEnd,
106 int *linesInserted, int *linesDeleted);
107 static void wrappedLineCounter(textDisp *textD, textBuffer *buf, int startPos,
108 int maxPos, int maxLines, int startPosIsLineStart, int *retPos,
109 int *retLines, int *retLineStart, int *retLineEnd);
110 static void findLineEnd(textDisp *textD, int startPos, int startPosIsLineStart,
111 int *lineEnd, int *nextLineStart);
112 static int wrapUsesCharacter(textDisp *textD, int lineEndPos);
113 static void hideOrShowHScrollBar(textDisp *textD);
114 static int rangeTouchesRectSel(selection *sel, int rangeStart, int rangeEnd);
115 static void extendRangeForStyleMods(textDisp *textD, int *start, int *end);
116 static int getAbsTopLineNum(textDisp *textD);
117 static void offsetAbsLineNum(textDisp *textD, int oldFirstChar);
118 static int maintainingAbsTopLineNum(textDisp *textD);
119 static void resetAbsLineNum(textDisp *textD);
120 static int measurePropChar(textDisp *textD, char c, int colNum, int pos);
122 textDisp *TextDCreate(Widget widget, Widget hScrollBar, Widget vScrollBar,
123 Position left, Position top, Position width, Position height,
124 Position lineNumLeft, Position lineNumWidth, textBuffer *buffer,
125 XFontStruct *fontStruct, Pixel bgPixel, Pixel fgPixel,
126 Pixel selectFGPixel, Pixel selectBGPixel, Pixel highlightFGPixel,
127 Pixel highlightBGPixel, Pixel cursorFGPixel, Pixel lineNumFGPixel,
128 int continuousWrap, int wrapMargin)
130 textDisp *textD;
131 XGCValues gcValues;
132 int i;
134 textD = (textDisp *)XtMalloc(sizeof(textDisp));
135 textD->w = widget;
136 textD->top = top;
137 textD->left = left;
138 textD->width = width;
139 textD->height = height;
140 textD->cursorOn = True;
141 textD->cursorPos = 0;
142 textD->cursorX = -100;
143 textD->cursorY = -100;
144 textD->cursorToHint = NO_HINT;
145 textD->cursorStyle = NORMAL_CURSOR;
146 textD->cursorPreferredCol = -1;
147 textD->buffer = buffer;
148 textD->firstChar = 0;
149 textD->lastChar = 0;
150 textD->nBufferLines = 0;
151 textD->topLineNum = 1;
152 textD->absTopLineNum = 1;
153 textD->needAbsTopLineNum = False;
154 textD->horizOffset = 0;
155 textD->visibility = VisibilityUnobscured;
156 textD->hScrollBar = hScrollBar;
157 textD->vScrollBar = vScrollBar;
158 textD->fontStruct = fontStruct;
159 textD->ascent = fontStruct->ascent;
160 textD->descent = fontStruct->descent;
161 textD->fixedFontWidth = fontStruct->min_bounds.width ==
162 fontStruct->max_bounds.width ? fontStruct->min_bounds.width : -1;
163 textD->styleBuffer = NULL;
164 textD->styleTable = NULL;
165 textD->nStyles = 0;
166 textD->bgPixel = bgPixel;
167 textD->selectBGPixel = selectBGPixel;
168 textD->highlightBGPixel = highlightBGPixel;
169 textD->lineNumFGPixel = lineNumFGPixel;
170 textD->wrapMargin = wrapMargin;
171 textD->continuousWrap = continuousWrap;
172 allocateFixedFontGCs(textD, fontStruct, bgPixel, fgPixel, selectFGPixel,
173 selectBGPixel, highlightFGPixel, highlightBGPixel);
174 textD->styleGC = allocateGC(textD->w, 0, 0, 0, fontStruct->fid,
175 GCClipMask|GCForeground|GCBackground, GCArcMode);
176 textD->lineNumGC = NULL;
177 textD->lineNumLeft = lineNumLeft;
178 textD->lineNumWidth = lineNumWidth;
179 textD->nVisibleLines = (height - 1) / (textD->ascent + textD->descent) + 1;
180 gcValues.foreground = cursorFGPixel;
181 textD->cursorFGGC = XtGetGC(widget, GCForeground, &gcValues);
182 textD->lineStarts = (int *)XtMalloc(sizeof(int) * textD->nVisibleLines);
183 textD->lineStarts[0] = 0;
184 for (i=1; i<textD->nVisibleLines; i++)
185 textD->lineStarts[i] = -1;
187 /* Attach an event handler to the widget so we can know the visibility
188 (used for choosing the fastest drawing method) */
189 XtAddEventHandler(widget, VisibilityChangeMask, False,
190 visibilityEH, textD);
192 /* Attach the callback to the text buffer for receiving modification
193 information */
194 if (buffer != NULL)
195 BufAddModifyCB(buffer, bufModifiedCB, textD);
197 /* Initialize the scroll bars and attach movement callbacks */
198 if (vScrollBar != NULL) {
199 XtVaSetValues(vScrollBar, XmNminimum, 1, XmNmaximum, 2,
200 XmNsliderSize, 1, XmNrepeatDelay, 10, XmNvalue, 1, NULL);
201 XtAddCallback(vScrollBar, XmNdragCallback, vScrollCB, (XtPointer)textD);
202 XtAddCallback(vScrollBar, XmNvalueChangedCallback, vScrollCB,
203 (XtPointer)textD);
205 if (hScrollBar != NULL) {
206 XtVaSetValues(hScrollBar, XmNminimum, 0, XmNmaximum, 1,
207 XmNsliderSize, 1, XmNrepeatDelay, 10, XmNvalue, 0,
208 XmNincrement, fontStruct->max_bounds.width, NULL);
209 XtAddCallback(hScrollBar, XmNdragCallback, hScrollCB, (XtPointer)textD);
210 XtAddCallback(hScrollBar, XmNvalueChangedCallback, hScrollCB,
211 (XtPointer)textD);
214 /* Update the display to reflect the contents of the buffer */
215 if (buffer != NULL)
216 bufModifiedCB(0, buffer->length, 0, 0, NULL, textD);
218 /* Decide if the horizontal scroll bar needs to be visible */
219 hideOrShowHScrollBar(textD);
221 return textD;
225 ** Free a text display and release its associated memory. Note, the text
226 ** BUFFER that the text display displays is a separate entity and is not
227 ** freed, nor are the style buffer or style table.
229 void TextDFree(textDisp *textD)
231 BufRemoveModifyCB(textD->buffer, bufModifiedCB, textD);
232 releaseGC(textD->w, textD->gc);
233 releaseGC(textD->w, textD->selectGC);
234 releaseGC(textD->w, textD->highlightGC);
235 releaseGC(textD->w, textD->selectBGGC);
236 releaseGC(textD->w, textD->highlightBGGC);
237 releaseGC(textD->w, textD->styleGC);
238 if (textD->lineNumGC != NULL)
239 XtReleaseGC(textD->w, textD->lineNumGC);
240 XtFree((char *)textD->lineStarts);
241 XtFree((char *)textD);
245 ** Attach a text buffer to display, replacing the current buffer (if any)
247 void TextDSetBuffer(textDisp *textD, textBuffer *buffer)
249 /* If the text display is already displaying a buffer, clear it off
250 of the display and remove our callback from it */
251 if (textD->buffer != NULL) {
252 bufModifiedCB(0, 0, textD->buffer->length, 0, NULL, textD);
253 BufRemoveModifyCB(textD->buffer, bufModifiedCB, textD);
256 /* Add the buffer to the display, and attach a callback to the buffer for
257 receiving modification information when the buffer contents change */
258 textD->buffer = buffer;
259 BufAddModifyCB(buffer, bufModifiedCB, textD);
261 /* Update the display */
262 bufModifiedCB(0, buffer->length, 0, 0, NULL, textD);
266 ** return the displayed text buffer
268 textBuffer *TextDGetBuffer(textDisp *textD)
270 return textD->buffer;
274 ** Attach (or remove) highlight information in text display and redisplay.
275 ** Highlighting information consists of a style buffer which parallels the
276 ** normal text buffer, but codes font and color information for the display;
277 ** a style table which translates style buffer codes (indexed by buffer
278 ** character - 'A') into fonts and colors; and a callback mechanism for
279 ** as-needed highlighting, triggered by a style buffer entry of
280 ** "unfinishedStyle". Style buffer can trigger additional redisplay during
281 ** a normal buffer modification if the buffer contains a primary selection
282 ** (see extendRangeForStyleMods for more information on this protocol).
284 ** Style buffers, tables and their associated memory are managed by the caller.
286 void TextDAttachHighlightData(textDisp *textD, textBuffer *styleBuffer,
287 styleTableEntry *styleTable, int nStyles, char unfinishedStyle,
288 unfinishedStyleCBProc unfinishedHighlightCB, void *cbArg)
290 textD->styleBuffer = styleBuffer;
291 textD->styleTable = styleTable;
292 textD->nStyles = nStyles;
293 textD->unfinishedStyle = unfinishedStyle;
294 textD->unfinishedHighlightCB = unfinishedHighlightCB;
295 textD->highlightCBArg = cbArg;
297 /* Call TextDSetFont to combine font information from style table and
298 primary font, adjust font-related parameters, and then redisplay */
299 TextDSetFont(textD, textD->fontStruct);
303 ** Change the (non highlight) font
305 void TextDSetFont(textDisp *textD, XFontStruct *fontStruct)
307 Display *display = XtDisplay(textD->w);
308 int i, maxAscent = fontStruct->ascent, maxDescent = fontStruct->descent;
309 int width, height, fontWidth;
310 Pixel bgPixel, fgPixel, selectFGPixel, selectBGPixel;
311 Pixel highlightFGPixel, highlightBGPixel;
312 XGCValues values;
313 XFontStruct *styleFont;
315 /* If font size changes, cursor will be redrawn in a new position */
316 blankCursorProtrusions(textD);
318 /* If there is a (syntax highlighting) style table in use, find the new
319 maximum font height for this text display */
320 for (i=0; i<textD->nStyles; i++) {
321 if (textD->styleTable[i].font->ascent > maxAscent)
322 maxAscent = textD->styleTable[i].font->ascent;
323 if (textD->styleTable[i].font->descent > maxDescent)
324 maxDescent = textD->styleTable[i].font->descent;
326 textD->ascent = maxAscent;
327 textD->descent = maxDescent;
329 /* If all of the current fonts are fixed and match in width, compute */
330 fontWidth = fontStruct->max_bounds.width;
331 if (fontWidth != fontStruct->min_bounds.width)
332 fontWidth = -1;
333 else {
334 for (i=0; i<textD->nStyles; i++) {
335 styleFont = textD->styleTable[i].font;
336 if (styleFont->max_bounds.width != fontWidth ||
337 styleFont->max_bounds.width != styleFont->min_bounds.width)
338 fontWidth = -1;
341 textD->fixedFontWidth = fontWidth;
343 /* Don't let the height dip below one line, or bad things can happen */
344 if (textD->height < maxAscent + maxDescent)
345 textD->height = maxAscent + maxDescent;
347 /* Change the font. In most cases, this means re-allocating the
348 affected GCs (they are shared with other widgets, and if the primary
349 font changes, must be re-allocated to change it). Unfortunately,
350 this requres recovering all of the colors from the existing GCs */
351 textD->fontStruct = fontStruct;
352 XGetGCValues(display, textD->gc, GCForeground|GCBackground, &values);
353 fgPixel = values.foreground;
354 bgPixel = values.background;
355 XGetGCValues(display, textD->selectGC, GCForeground|GCBackground, &values);
356 selectFGPixel = values.foreground;
357 selectBGPixel = values.background;
358 XGetGCValues(display, textD->highlightGC,GCForeground|GCBackground,&values);
359 highlightFGPixel = values.foreground;
360 highlightBGPixel = values.background;
361 releaseGC(textD->w, textD->gc);
362 releaseGC(textD->w, textD->selectGC);
363 releaseGC(textD->w, textD->highlightGC);
364 releaseGC(textD->w, textD->selectBGGC);
365 releaseGC(textD->w, textD->highlightBGGC);
366 if (textD->lineNumGC != NULL)
367 releaseGC(textD->w, textD->lineNumGC);
368 textD->lineNumGC = NULL;
369 allocateFixedFontGCs(textD, fontStruct, bgPixel, fgPixel, selectFGPixel,
370 selectBGPixel, highlightFGPixel, highlightBGPixel);
371 XSetFont(display, textD->styleGC, fontStruct->fid);
373 /* Do a full resize to force recalculation of font related parameters */
374 width = textD->width;
375 height = textD->height;
376 textD->width = textD->height = 0;
377 TextDResize(textD, width, height);
379 /* Redisplay */
380 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
381 textD->height);
383 /* Clean up line number area in case spacing has changed */
384 redrawLineNumbers(textD, True);
387 int TextDMinFontWidth(textDisp *textD, Boolean considerStyles)
389 int fontWidth = textD->fontStruct->max_bounds.width;
390 int i;
392 if (considerStyles) {
393 for (i = 0; i < textD->nStyles; ++i) {
394 int thisWidth = (textD->styleTable[i].font)->min_bounds.width;
395 if (thisWidth < fontWidth) {
396 fontWidth = thisWidth;
400 return(fontWidth);
403 int TextDMaxFontWidth(textDisp *textD, Boolean considerStyles)
405 int fontWidth = textD->fontStruct->max_bounds.width;
406 int i;
408 if (considerStyles) {
409 for (i = 0; i < textD->nStyles; ++i) {
410 int thisWidth = (textD->styleTable[i].font)->max_bounds.width;
411 if (thisWidth > fontWidth) {
412 fontWidth = thisWidth;
416 return(fontWidth);
420 ** Change the size of the displayed text area
422 void TextDResize(textDisp *textD, int width, int height)
424 int oldVisibleLines = textD->nVisibleLines;
425 int canRedraw = XtWindow(textD->w) != 0;
426 int newVisibleLines = height / (textD->ascent + textD->descent);
427 int redrawAll = False;
428 int oldWidth = textD->width;
429 int exactHeight = height - height % (textD->ascent + textD->descent);
431 textD->width = width;
432 textD->height = height;
434 /* In continuous wrap mode, a change in width affects the total number of
435 lines in the buffer, and can leave the top line number incorrect, and
436 the top character no longer pointing at a valid line start */
437 if (textD->continuousWrap && textD->wrapMargin==0 && width!=oldWidth) {
438 int oldFirstChar = textD->firstChar;
439 textD->nBufferLines = TextDCountLines(textD, 0, textD->buffer->length,
440 True);
441 textD->firstChar = TextDStartOfLine(textD, textD->firstChar);
442 textD->topLineNum = TextDCountLines(textD, 0, textD->firstChar, True)+1;
443 redrawAll = True;
444 offsetAbsLineNum(textD, oldFirstChar);
447 /* reallocate and update the line starts array, which may have changed
448 size and/or contents. (contents can change in continuous wrap mode
449 when the width changes, even without a change in height) */
450 if (oldVisibleLines < newVisibleLines) {
451 XtFree((char *)textD->lineStarts);
452 textD->lineStarts = (int *)XtMalloc(sizeof(int) * newVisibleLines);
454 textD->nVisibleLines = newVisibleLines;
455 calcLineStarts(textD, 0, newVisibleLines);
456 calcLastChar(textD);
458 /* if the window became shorter, there may be partially drawn
459 text left at the bottom edge, which must be cleaned up */
460 if (canRedraw && oldVisibleLines>newVisibleLines && exactHeight!=height)
461 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->left,
462 textD->top + exactHeight, textD->width,
463 height - exactHeight, False);
465 /* if the window became taller, there may be an opportunity to display
466 more text by scrolling down */
467 if (canRedraw && oldVisibleLines < newVisibleLines && textD->topLineNum +
468 textD->nVisibleLines > textD->nBufferLines)
469 setScroll(textD, max(1, textD->nBufferLines - textD->nVisibleLines + 2),
470 textD->horizOffset, False, False);
472 /* Update the scroll bar page increment size (as well as other scroll
473 bar parameters. If updating the horizontal range caused scrolling,
474 redraw */
475 updateVScrollBarRange(textD);
476 if (updateHScrollBarRange(textD))
477 redrawAll = True;
479 /* If a full redraw is needed */
480 if (redrawAll && canRedraw)
481 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
482 textD->height);
484 /* Decide if the horizontal scroll bar needs to be visible */
485 hideOrShowHScrollBar(textD);
487 /* Refresh the line number display to draw more line numbers, or
488 erase extras */
489 redrawLineNumbers(textD, True);
493 ** Refresh a rectangle of the text display. left and top are in coordinates of
494 ** the text drawing window
496 void TextDRedisplayRect(textDisp *textD, int left, int top, int width,
497 int height)
499 int fontHeight, firstLine, lastLine, line;
501 /* find the line number range of the display */
502 fontHeight = textD->ascent + textD->descent;
503 firstLine = (top - textD->top - fontHeight + 1) / fontHeight;
504 lastLine = (top + height - textD->top) / fontHeight;
506 /* If the graphics contexts are shared using XtAllocateGC, their
507 clipping rectangles may have changed since the last use */
508 resetClipRectangles(textD);
510 /* draw the lines of text */
511 for (line=firstLine; line<=lastLine; line++)
512 redisplayLine(textD, line, left, left+width, 0, INT_MAX);
514 /* draw the line numbers if exposed area includes them */
515 if (textD->lineNumWidth != 0 && left <= textD->lineNumLeft + textD->lineNumWidth)
516 redrawLineNumbers(textD, False);
520 ** Refresh all of the text between buffer positions "start" and "end"
521 ** not including the character at the position "end".
522 ** If end points beyond the end of the buffer, refresh the whole display
523 ** after pos, including blank lines which are not technically part of
524 ** any range of characters.
526 void TextDRedisplayRange(textDisp *textD, int start, int end)
528 int i, startLine, lastLine, startIndex, endIndex;
530 /* If the range is outside of the displayed text, just return */
531 if (end < textD->firstChar || (start > textD->lastChar &&
532 !emptyLinesVisible(textD)))
533 return;
535 /* Clean up the starting and ending values */
536 if (start < 0) start = 0;
537 if (start > textD->buffer->length) start = textD->buffer->length;
538 if (end < 0) end = 0;
539 if (end > textD->buffer->length) end = textD->buffer->length;
541 /* Get the starting and ending lines */
542 if (start < textD->firstChar)
543 start = textD->firstChar;
544 if (!posToVisibleLineNum(textD, start, &startLine))
545 startLine = textD->nVisibleLines - 1;
546 if (end >= textD->lastChar) {
547 lastLine = textD->nVisibleLines - 1;
548 } else {
549 if (!posToVisibleLineNum(textD, end, &lastLine)) {
550 /* shouldn't happen */
551 lastLine = textD->nVisibleLines - 1;
555 /* Get the starting and ending positions within the lines */
556 startIndex = textD->lineStarts[startLine] == -1 ? 0 :
557 start - textD->lineStarts[startLine];
558 if (end >= textD->lastChar)
559 endIndex = INT_MAX;
560 else if (textD->lineStarts[lastLine] == -1)
561 endIndex = 0;
562 else
563 endIndex = end - textD->lineStarts[lastLine];
565 /* Reset the clipping rectangles for the drawing GCs which are shared
566 using XtAllocateGC, and may have changed since the last use */
567 resetClipRectangles(textD);
569 /* If the starting and ending lines are the same, redisplay the single
570 line between "start" and "end" */
571 if (startLine == lastLine) {
572 redisplayLine(textD, startLine, 0, INT_MAX, startIndex, endIndex);
573 return;
576 /* Redisplay the first line from "start" */
577 redisplayLine(textD, startLine, 0, INT_MAX, startIndex, INT_MAX);
579 /* Redisplay the lines in between at their full width */
580 for (i=startLine+1; i<lastLine; i++)
581 redisplayLine(textD, i, 0, INT_MAX, 0, INT_MAX);
583 /* Redisplay the last line to "end" */
584 redisplayLine(textD, lastLine, 0, INT_MAX, 0, endIndex);
588 ** Set the scroll position of the text display vertically by line number and
589 ** horizontally by pixel offset from the left margin
591 void TextDSetScroll(textDisp *textD, int topLineNum, int horizOffset)
593 int sliderSize, sliderMax;
595 /* Limit the requested scroll position to allowable values */
596 if (topLineNum < 1)
597 topLineNum = 1;
598 else if (topLineNum > textD->topLineNum &&
599 topLineNum > textD->nBufferLines + 2 - textD->nVisibleLines)
600 topLineNum = max(textD->topLineNum,
601 textD->nBufferLines + 2 - textD->nVisibleLines);
602 XtVaGetValues(textD->hScrollBar, XmNmaximum, &sliderMax,
603 XmNsliderSize, &sliderSize, NULL);
604 if (horizOffset < 0)
605 horizOffset = 0;
606 if (horizOffset > sliderMax - sliderSize)
607 horizOffset = sliderMax - sliderSize;
609 setScroll(textD, topLineNum, horizOffset, True, True);
613 ** Get the current scroll position for the text display, in terms of line
614 ** number of the top line and horizontal pixel offset from the left margin
616 void TextDGetScroll(textDisp *textD, int *topLineNum, int *horizOffset)
618 *topLineNum = textD->topLineNum;
619 *horizOffset = textD->horizOffset;
623 ** Set the position of the text insertion cursor for text display "textD"
625 void TextDSetInsertPosition(textDisp *textD, int newPos)
627 /* make sure new position is ok, do nothing if it hasn't changed */
628 if (newPos == textD->cursorPos)
629 return;
630 if (newPos < 0) newPos = 0;
631 if (newPos > textD->buffer->length) newPos = textD->buffer->length;
633 /* cursor movement cancels vertical cursor motion column */
634 textD->cursorPreferredCol = -1;
636 /* erase the cursor at it's previous position */
637 TextDBlankCursor(textD);
639 /* draw it at its new position */
640 textD->cursorPos = newPos;
641 textD->cursorOn = True;
642 TextDRedisplayRange(textD, textD->cursorPos, textD->cursorPos + 1);
645 void TextDBlankCursor(textDisp *textD)
647 if (!textD->cursorOn)
648 return;
650 blankCursorProtrusions(textD);
651 textD->cursorOn = False;
652 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
655 void TextDUnblankCursor(textDisp *textD)
657 if (!textD->cursorOn) {
658 textD->cursorOn = True;
659 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
663 void TextDSetCursorStyle(textDisp *textD, int style)
665 textD->cursorStyle = style;
666 blankCursorProtrusions(textD);
667 if (textD->cursorOn)
668 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos + 1);
671 void TextDSetWrapMode(textDisp *textD, int wrap, int wrapMargin)
673 textD->wrapMargin = wrapMargin;
674 textD->continuousWrap = wrap;
676 /* wrapping can change change the total number of lines, re-count */
677 textD->nBufferLines = TextDCountLines(textD, 0, textD->buffer->length,True);
679 /* changing wrap margins wrap or changing from wrapped mode to non-wrapped
680 can leave the character at the top no longer at a line start, and/or
681 change the line number */
682 textD->firstChar = TextDStartOfLine(textD, textD->firstChar);
683 textD->topLineNum = TextDCountLines(textD, 0, textD->firstChar, True) + 1;
684 resetAbsLineNum(textD);
686 /* update the line starts array */
687 calcLineStarts(textD, 0, textD->nVisibleLines);
688 calcLastChar(textD);
690 /* Update the scroll bar page increment size (as well as other scroll
691 bar parameters) */
692 updateVScrollBarRange(textD);
693 updateHScrollBarRange(textD);
695 /* Decide if the horizontal scroll bar needs to be visible */
696 hideOrShowHScrollBar(textD);
698 /* Do a full redraw */
699 TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left,
700 textD->height);
703 int TextDGetInsertPosition(textDisp *textD)
705 return textD->cursorPos;
709 ** Insert "text" at the current cursor location. This has the same
710 ** effect as inserting the text into the buffer using BufInsert and
711 ** then moving the insert position after the newly inserted text, except
712 ** that it's optimized to do less redrawing.
714 void TextDInsert(textDisp *textD, char *text)
716 int pos = textD->cursorPos;
718 textD->cursorToHint = pos + strlen(text);
719 BufInsert(textD->buffer, pos, text);
720 textD->cursorToHint = NO_HINT;
724 ** Insert "text" (which must not contain newlines), overstriking the current
725 ** cursor location.
727 void TextDOverstrike(textDisp *textD, char *text)
729 int startPos = textD->cursorPos;
730 textBuffer *buf = textD->buffer;
731 int lineStart = BufStartOfLine(buf, startPos);
732 int textLen = strlen(text);
733 int i, p, endPos, indent, startIndent, endIndent;
734 char *c, ch, *paddedText = NULL;
736 /* determine how many displayed character positions are covered */
737 startIndent = BufCountDispChars(textD->buffer, lineStart, startPos);
738 indent = startIndent;
739 for (c=text; *c!='\0'; c++)
740 indent += BufCharWidth(*c, indent, buf->tabDist, buf->nullSubsChar);
741 endIndent = indent;
743 /* find which characters to remove, and if necessary generate additional
744 padding to make up for removed control characters at the end */
745 indent=startIndent;
746 for (p=startPos; ; p++) {
747 if (p == buf->length)
748 break;
749 ch = BufGetCharacter(buf, p);
750 if (ch == '\n')
751 break;
752 indent += BufCharWidth(ch, indent, buf->tabDist, buf->nullSubsChar);
753 if (indent == endIndent) {
754 p++;
755 break;
756 } else if (indent > endIndent) {
757 if (ch != '\t') {
758 p++;
759 paddedText = XtMalloc(textLen + MAX_EXP_CHAR_LEN + 1);
760 strcpy(paddedText, text);
761 for (i=0; i<indent-endIndent; i++)
762 paddedText[textLen+i] = ' ';
763 paddedText[textLen+i] = '\0';
765 break;
768 endPos = p;
770 textD->cursorToHint = startPos + textLen;
771 BufReplace(buf, startPos, endPos, paddedText == NULL ? text : paddedText);
772 textD->cursorToHint = NO_HINT;
773 if (paddedText != NULL)
774 XtFree(paddedText);
778 ** Translate window coordinates to the nearest text cursor position.
780 int TextDXYToPosition(textDisp *textD, int x, int y)
782 return xyToPos(textD, x, y, CURSOR_POS);
786 ** Translate window coordinates to the nearest character cell.
788 int TextDXYToCharPos(textDisp *textD, int x, int y)
790 return xyToPos(textD, x, y, CHARACTER_POS);
794 ** Translate window coordinates to the nearest row and column number for
795 ** positioning the cursor. This, of course, makes no sense when the font
796 ** is proportional, since there are no absolute columns.
798 void TextDXYToUnconstrainedPosition(textDisp *textD, int x, int y, int *row,
799 int *column)
801 xyToUnconstrainedPos(textD, x, y, row, column, CURSOR_POS);
805 ** Translate line and column to the nearest row and column number for
806 ** positioning the cursor. This, of course, makes no sense when the font
807 ** is proportional, since there are no absolute columns.
809 int TextDLineAndColToPos(textDisp *textD, int lineNum, int column)
811 int i, lineStart, lineEnd, charIndex, outIndex, charLen;
812 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
814 /* Count lines */
815 if (lineNum < 1)
816 lineNum = 1;
817 lineEnd = -1;
818 for (i=1; i<=lineNum && lineEnd<textD->buffer->length; i++) {
819 lineStart = lineEnd + 1;
820 lineEnd = BufEndOfLine(textD->buffer, lineStart);
823 /* If line is beyond end of buffer, position at last character in buffer */
824 if ( lineNum >= i ) {
825 return lineEnd;
828 /* Start character index at zero */
829 charIndex=0;
831 /* Only have to count columns if column isn't zero (or negative) */
832 if (column > 0) {
833 /* Count columns, expanding each character */
834 lineStr = BufGetRange(textD->buffer, lineStart, lineEnd);
835 outIndex = 0;
836 for(i=lineStart; i<lineEnd; i++, charIndex++) {
837 charLen = BufExpandCharacter(lineStr[charIndex], outIndex,
838 expandedChar, textD->buffer->tabDist,
839 textD->buffer->nullSubsChar);
840 if ( outIndex+charLen >= column ) break;
841 outIndex+=charLen;
844 /* If the column is in the middle of an expanded character, put cursor
845 * in front of character if in first half of character, and behind
846 * character if in last half of character
848 if (column >= outIndex + ( charLen / 2 ))
849 charIndex++;
851 /* If we are beyond the end of the line, back up one space */
852 if ((i>=lineEnd)&&(charIndex>0)) charIndex--;
855 /* Position is the start of the line plus the index into line buffer */
856 return lineStart + charIndex;
860 ** Translate a buffer text position to the XY location where the center
861 ** of the cursor would be positioned to point to that character. Returns
862 ** False if the position is not displayed because it is VERTICALLY out
863 ** of view. If the position is horizontally out of view, returns the
864 ** x coordinate where the position would be if it were visible.
866 int TextDPositionToXY(textDisp *textD, int pos, int *x, int *y)
868 int charIndex, lineStartPos, fontHeight, lineLen;
869 int visLineNum, charLen, outIndex, xStep, charStyle;
870 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
872 /* If position is not displayed, return false */
873 if (pos < textD->firstChar ||
874 (pos > textD->lastChar && !emptyLinesVisible(textD)))
875 return False;
877 /* Calculate y coordinate */
878 if (!posToVisibleLineNum(textD, pos, &visLineNum))
879 return False;
880 fontHeight = textD->ascent + textD->descent;
881 *y = textD->top + visLineNum*fontHeight + fontHeight/2;
883 /* Get the text, length, and buffer position of the line. If the position
884 is beyond the end of the buffer and should be at the first position on
885 the first empty line, don't try to get or scan the text */
886 lineStartPos = textD->lineStarts[visLineNum];
887 if (lineStartPos == -1) {
888 *x = textD->left - textD->horizOffset;
889 return True;
891 lineLen = visLineLength(textD, visLineNum);
892 lineStr = BufGetRange(textD->buffer, lineStartPos, lineStartPos + lineLen);
894 /* Step through character positions from the beginning of the line
895 to "pos" to calculate the x coordinate */
896 xStep = textD->left - textD->horizOffset;
897 outIndex = 0;
898 for(charIndex=0; charIndex<pos-lineStartPos; charIndex++) {
899 charLen = BufExpandCharacter(lineStr[charIndex], outIndex, expandedChar,
900 textD->buffer->tabDist, textD->buffer->nullSubsChar);
901 charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex,
902 outIndex);
903 xStep += stringWidth(textD, expandedChar, charLen, charStyle);
904 outIndex += charLen;
906 *x = xStep;
907 XtFree(lineStr);
908 return True;
912 ** If the text widget is maintaining a line number count appropriate to "pos"
913 ** return the line and column numbers of pos, otherwise return False. If
914 ** continuous wrap mode is on, returns the absolute line number (as opposed to
915 ** the wrapped line number which is used for scrolling). THIS ROUTINE ONLY
916 ** WORKS FOR DISPLAYED LINES AND, IN CONTINUOUS WRAP MODE, ONLY WHEN THE
917 ** ABSOLUTE LINE NUMBER IS BEING MAINTAINED. Otherwise, it returns False.
919 int TextDPosToLineAndCol(textDisp *textD, int pos, int *lineNum, int *column)
921 textBuffer *buf = textD->buffer;
923 /* In continuous wrap mode, the absolute (non-wrapped) line count is
924 maintained separately, as needed. Only return it if we're actually
925 keeping track of it and pos is in the displayed text */
926 if (textD->continuousWrap) {
927 if (!maintainingAbsTopLineNum(textD) || pos < textD->firstChar ||
928 pos > textD->lastChar)
929 return False;
930 *lineNum = textD->absTopLineNum + BufCountLines(buf,
931 textD->firstChar, pos);
932 *column = BufCountDispChars(buf, BufStartOfLine(buf, pos), pos);
933 return True;
936 /* Only return the data if pos is within the displayed text */
937 if (!posToVisibleLineNum(textD, pos, lineNum))
938 return False;
939 *column = BufCountDispChars(buf, textD->lineStarts[*lineNum], pos);
940 *lineNum += textD->topLineNum;
941 return True;
945 ** Return True if position (x, y) is inside of the primary selection
947 int TextDInSelection(textDisp *textD, int x, int y)
949 int row, column, pos = xyToPos(textD, x, y, CHARACTER_POS);
950 textBuffer *buf = textD->buffer;
952 xyToUnconstrainedPos(textD, x, y, &row, &column, CHARACTER_POS);
953 if (rangeTouchesRectSel(&buf->primary, textD->firstChar, textD->lastChar))
954 column = TextDOffsetWrappedColumn(textD, row, column);
955 return inSelection(&buf->primary, pos, BufStartOfLine(buf, pos), column);
959 ** Correct a column number based on an unconstrained position (as returned by
960 ** TextDXYToUnconstrainedPosition) to be relative to the last actual newline
961 ** in the buffer before the row and column position given, rather than the
962 ** last line start created by line wrapping. This is an adapter
963 ** for rectangular selections and code written before continuous wrap mode,
964 ** which thinks that the unconstrained column is the number of characters
965 ** from the last newline. Obviously this is time consuming, because it
966 ** invloves character re-counting.
968 int TextDOffsetWrappedColumn(textDisp *textD, int row, int column)
970 int lineStart, dispLineStart;
972 if (!textD->continuousWrap || row < 0 || row > textD->nVisibleLines)
973 return column;
974 dispLineStart = textD->lineStarts[row];
975 if (dispLineStart == -1)
976 return column;
977 lineStart = BufStartOfLine(textD->buffer, dispLineStart);
978 return column + BufCountDispChars(textD->buffer, lineStart, dispLineStart);
982 ** Correct a row number from an unconstrained position (as returned by
983 ** TextDXYToUnconstrainedPosition) to a straight number of newlines from the
984 ** top line of the display. Because rectangular selections are based on
985 ** newlines, rather than display wrapping, and anywhere a rectangular selection
986 ** needs a row, it needs it in terms of un-wrapped lines.
988 int TextDOffsetWrappedRow(textDisp *textD, int row)
990 if (!textD->continuousWrap || row < 0 || row > textD->nVisibleLines)
991 return row;
992 return BufCountLines(textD->buffer, textD->firstChar,
993 textD->lineStarts[row]);
997 ** Scroll the display to bring insertion cursor into view.
999 ** Note: it would be nice to be able to do this without counting lines twice
1000 ** (setScroll counts them too) and/or to count from the most efficient
1001 ** starting point, but the efficiency of this routine is not as important to
1002 ** the overall performance of the text display.
1004 void TextDMakeInsertPosVisible(textDisp *textD)
1006 int hOffset, topLine, x, y;
1007 int cursorPos = textD->cursorPos;
1009 hOffset = textD->horizOffset;
1010 topLine = textD->topLineNum;
1012 /* Find the new top line number */
1013 if (cursorPos < textD->firstChar)
1014 topLine -= TextDCountLines(textD, cursorPos, textD->firstChar, False);
1015 else if (cursorPos > textD->lastChar && !emptyLinesVisible(textD)) {
1016 topLine += TextDCountLines(textD, textD->lastChar -
1017 (wrapUsesCharacter(textD, textD->lastChar) ? 0 : 1),
1018 cursorPos, False);
1019 } else if (cursorPos == textD->lastChar && !emptyLinesVisible(textD) &&
1020 !wrapUsesCharacter(textD, textD->lastChar))
1021 topLine++;
1022 if (topLine < 1) {
1023 fprintf(stderr, "internal consistency check tl1 failed\n");
1024 topLine = 1;
1027 /* Find the new setting for horizontal offset (this is a bit ungraceful).
1028 If the line is visible, just use TextDPositionToXY to get the position
1029 to scroll to, otherwise, do the vertical scrolling first, then the
1030 horizontal */
1031 if (!TextDPositionToXY(textD, cursorPos, &x, &y)) {
1032 setScroll(textD, topLine, hOffset, True, True);
1033 if (!TextDPositionToXY(textD, cursorPos, &x, &y))
1034 return; /* Give up, it's not worth it (but why does it fail?) */
1036 if (x > textD->left + textD->width)
1037 hOffset += x - (textD->left + textD->width);
1038 else if (x < textD->left)
1039 hOffset += x - textD->left;
1041 /* Do the scroll */
1042 setScroll(textD, topLine, hOffset, True, True);
1046 ** Return the current preferred column along with the current
1047 ** visible line index (-1 if not visible) and the lineStartPos
1048 ** of the current insert position.
1050 int TextDPreferredColumn(textDisp *textD, int *visLineNum, int *lineStartPos)
1052 int column;
1054 /* Find the position of the start of the line. Use the line starts array
1055 if possible, to avoid unbounded line-counting in continuous wrap mode */
1056 if (posToVisibleLineNum(textD, textD->cursorPos, visLineNum)) {
1057 *lineStartPos = textD->lineStarts[*visLineNum];
1059 else {
1060 *lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1061 *visLineNum = -1;
1064 /* Decide what column to move to, if there's a preferred column use that */
1065 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1066 BufCountDispChars(textD->buffer, *lineStartPos, textD->cursorPos);
1067 return(column);
1071 ** Return the insert position of the requested column given
1072 ** the lineStartPos.
1074 int TextDPosOfPreferredCol(textDisp *textD, int column, int lineStartPos)
1076 int newPos;
1078 newPos = BufCountForwardDispChars(textD->buffer, lineStartPos, column);
1079 if (textD->continuousWrap) {
1080 newPos = min(newPos, TextDEndOfLine(textD, lineStartPos, True));
1082 return(newPos);
1086 ** Cursor movement functions
1088 int TextDMoveRight(textDisp *textD)
1090 if (textD->cursorPos >= textD->buffer->length)
1091 return False;
1092 TextDSetInsertPosition(textD, textD->cursorPos + 1);
1093 return True;
1096 int TextDMoveLeft(textDisp *textD)
1098 if (textD->cursorPos <= 0)
1099 return False;
1100 TextDSetInsertPosition(textD, textD->cursorPos - 1);
1101 return True;
1104 int TextDMoveUp(textDisp *textD)
1106 int lineStartPos, column, prevLineStartPos, newPos, visLineNum;
1108 /* Find the position of the start of the line. Use the line starts array
1109 if possible, to avoid unbounded line-counting in continuous wrap mode */
1110 if (posToVisibleLineNum(textD, textD->cursorPos, &visLineNum))
1111 lineStartPos = textD->lineStarts[visLineNum];
1112 else {
1113 lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1114 visLineNum = -1;
1116 if (lineStartPos == 0)
1117 return False;
1119 /* Decide what column to move to, if there's a preferred column use that */
1120 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1121 BufCountDispChars(textD->buffer, lineStartPos, textD->cursorPos);
1123 /* count forward from the start of the previous line to reach the column */
1124 if (visLineNum != -1 && visLineNum != 0)
1125 prevLineStartPos = textD->lineStarts[visLineNum-1];
1126 else
1127 prevLineStartPos = TextDCountBackwardNLines(textD, lineStartPos, 1);
1128 newPos = BufCountForwardDispChars(textD->buffer, prevLineStartPos, column);
1129 if (textD->continuousWrap)
1130 newPos = min(newPos, TextDEndOfLine(textD, prevLineStartPos, True));
1132 /* move the cursor */
1133 TextDSetInsertPosition(textD, newPos);
1135 /* if a preferred column wasn't aleady established, establish it */
1136 textD->cursorPreferredCol = column;
1137 return True;
1139 int TextDMoveDown(textDisp *textD)
1141 int lineStartPos, column, nextLineStartPos, newPos, visLineNum;
1143 if (textD->cursorPos == textD->buffer->length)
1144 return False;
1145 if (posToVisibleLineNum(textD, textD->cursorPos, &visLineNum))
1146 lineStartPos = textD->lineStarts[visLineNum];
1147 else {
1148 lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1149 visLineNum = -1;
1151 column = textD->cursorPreferredCol >= 0 ? textD->cursorPreferredCol :
1152 BufCountDispChars(textD->buffer, lineStartPos, textD->cursorPos);
1153 nextLineStartPos = TextDCountForwardNLines(textD, lineStartPos, 1, True);
1154 newPos = BufCountForwardDispChars(textD->buffer, nextLineStartPos, column);
1155 if (textD->continuousWrap)
1156 newPos = min(newPos, TextDEndOfLine(textD, nextLineStartPos, True));
1157 TextDSetInsertPosition(textD, newPos);
1158 textD->cursorPreferredCol = column;
1159 return True;
1163 ** Same as BufCountLines, but takes in to account wrapping if wrapping is
1164 ** turned on. If the caller knows that startPos is at a line start, it
1165 ** can pass "startPosIsLineStart" as True to make the call more efficient
1166 ** by avoiding the additional step of scanning back to the last newline.
1168 int TextDCountLines(textDisp *textD, int startPos, int endPos,
1169 int startPosIsLineStart)
1171 int retLines, retPos, retLineStart, retLineEnd;
1173 /* If we're not wrapping use simple (and more efficient) BufCountLines */
1174 if (!textD->continuousWrap)
1175 return BufCountLines(textD->buffer, startPos, endPos);
1177 wrappedLineCounter(textD, textD->buffer, startPos, endPos, INT_MAX,
1178 startPosIsLineStart, &retPos, &retLines, &retLineStart,&retLineEnd);
1179 return retLines;
1183 ** Same as BufCountForwardNLines, but takes in to account line breaks when
1184 ** wrapping is turned on. If the caller knows that startPos is at a line start,
1185 ** it can pass "startPosIsLineStart" as True to make the call more efficient
1186 ** by avoiding the additional step of scanning back to the last newline.
1188 int TextDCountForwardNLines(textDisp *textD, int startPos, int nLines,
1189 int startPosIsLineStart)
1191 int retLines, retPos, retLineStart, retLineEnd;
1193 /* if we're not wrapping use more efficient BufCountForwardNLines */
1194 if (!textD->continuousWrap)
1195 return BufCountForwardNLines(textD->buffer, startPos, nLines);
1197 /* wrappedLineCounter can't handle the 0 lines case */
1198 if (nLines == 0)
1199 return startPos;
1201 /* use the common line counting routine to count forward */
1202 wrappedLineCounter(textD, textD->buffer, startPos, textD->buffer->length,
1203 nLines, startPosIsLineStart, &retPos, &retLines, &retLineStart,
1204 &retLineEnd);
1205 return retPos;
1209 ** Same as BufEndOfLine, but takes in to account line breaks when wrapping
1210 ** is turned on. If the caller knows that startPos is at a line start, it
1211 ** can pass "startPosIsLineStart" as True to make the call more efficient
1212 ** by avoiding the additional step of scanning back to the last newline.
1214 ** Note that the definition of the end of a line is less clear when continuous
1215 ** wrap is on. With continuous wrap off, it's just a pointer to the newline
1216 ** that ends the line. When it's on, it's the character beyond the last
1217 ** DISPLAYABLE character on the line, where a whitespace character which has
1218 ** been "converted" to a newline for wrapping is not considered displayable.
1219 ** Also note that, a line can be wrapped at a non-whitespace character if the
1220 ** line had no whitespace. In this case, this routine returns a pointer to
1221 ** the start of the next line. This is also consistent with the model used by
1222 ** visLineLength.
1224 int TextDEndOfLine(textDisp *textD, int pos, int startPosIsLineStart)
1226 int retLines, retPos, retLineStart, retLineEnd;
1228 /* If we're not wrapping use more efficien BufEndOfLine */
1229 if (!textD->continuousWrap)
1230 return BufEndOfLine(textD->buffer, pos);
1232 if (pos == textD->buffer->length)
1233 return pos;
1234 wrappedLineCounter(textD, textD->buffer, pos, textD->buffer->length, 1,
1235 startPosIsLineStart, &retPos, &retLines, &retLineStart,&retLineEnd);
1236 return retLineEnd;
1240 ** Same as BufStartOfLine, but returns the character after last wrap point
1241 ** rather than the last newline.
1243 int TextDStartOfLine(textDisp *textD, int pos)
1245 int retLines, retPos, retLineStart, retLineEnd;
1247 /* If we're not wrapping, use the more efficient BufStartOfLine */
1248 if (!textD->continuousWrap)
1249 return BufStartOfLine(textD->buffer, pos);
1251 wrappedLineCounter(textD, textD->buffer, BufStartOfLine(textD->buffer, pos),
1252 pos, INT_MAX, True, &retPos, &retLines, &retLineStart, &retLineEnd);
1253 return retLineStart;
1257 ** Same as BufCountBackwardNLines, but takes in to account line breaks when
1258 ** wrapping is turned on.
1260 int TextDCountBackwardNLines(textDisp *textD, int startPos, int nLines)
1262 textBuffer *buf = textD->buffer;
1263 int pos, lineStart, retLines, retPos, retLineStart, retLineEnd;
1265 /* If we're not wrapping, use the more efficient BufCountBackwardNLines */
1266 if (!textD->continuousWrap)
1267 return BufCountBackwardNLines(textD->buffer, startPos, nLines);
1269 pos = startPos;
1270 while (True) {
1271 lineStart = BufStartOfLine(buf, pos);
1272 wrappedLineCounter(textD, textD->buffer, lineStart, pos, INT_MAX,
1273 True, &retPos, &retLines, &retLineStart, &retLineEnd);
1274 if (retLines > nLines)
1275 return TextDCountForwardNLines(textD, lineStart, retLines-nLines,
1276 True);
1277 nLines -= retLines;
1278 pos = lineStart - 1;
1279 if (pos < 0)
1280 return 0;
1281 nLines -= 1;
1286 ** Callback attached to the text buffer to receive modification information
1288 static void bufModifiedCB(int pos, int nInserted, int nDeleted,
1289 int nRestyled, char *deletedText, void *cbArg)
1291 int linesInserted, linesDeleted, startDispPos, endDispPos;
1292 textDisp *textD = (textDisp *)cbArg;
1293 textBuffer *buf = textD->buffer;
1294 int oldFirstChar = textD->firstChar;
1295 int scrolled, origCursorPos = textD->cursorPos;
1296 int wrapModStart, wrapModEnd;
1298 /* buffer modification cancels vertical cursor motion column */
1299 if (nInserted != 0 || nDeleted != 0)
1300 textD->cursorPreferredCol = -1;
1302 /* Count the number of lines inserted and deleted, and in the case
1303 of continuous wrap mode, how much has changed */
1304 if (textD->continuousWrap) {
1305 findWrapRange(textD, deletedText, pos, nInserted, nDeleted,
1306 &wrapModStart, &wrapModEnd, &linesInserted, &linesDeleted);
1307 } else {
1308 linesInserted = nInserted == 0 ? 0 :
1309 BufCountLines(buf, pos, pos + nInserted);
1310 linesDeleted = nDeleted == 0 ? 0 : countLines(deletedText);
1313 /* Update the line starts and topLineNum */
1314 if (nInserted != 0 || nDeleted != 0) {
1315 if (textD->continuousWrap) {
1316 updateLineStarts(textD, wrapModStart, wrapModEnd-wrapModStart,
1317 nDeleted + pos-wrapModStart + (wrapModEnd-(pos+nInserted)),
1318 linesInserted, linesDeleted, &scrolled);
1319 } else {
1320 updateLineStarts(textD, pos, nInserted, nDeleted, linesInserted,
1321 linesDeleted, &scrolled);
1323 } else
1324 scrolled = False;
1326 /* If we're counting non-wrapped lines as well, maintain the absolute
1327 (non-wrapped) line number of the text displayed */
1328 if (maintainingAbsTopLineNum(textD) && (nInserted != 0 || nDeleted != 0)) {
1329 if (pos + nDeleted < oldFirstChar)
1330 textD->absTopLineNum += BufCountLines(buf, pos, pos + nInserted) -
1331 countLines(deletedText);
1332 else if (pos < oldFirstChar)
1333 resetAbsLineNum(textD);
1336 /* Update the line count for the whole buffer */
1337 textD->nBufferLines += linesInserted - linesDeleted;
1339 /* Update the scroll bar ranges (and value if the value changed). Note
1340 that updating the horizontal scroll bar range requires scanning the
1341 entire displayed text, however, it doesn't seem to hurt performance
1342 much. Note also, that the horizontal scroll bar update routine is
1343 allowed to re-adjust horizOffset if there is blank space to the right
1344 of all lines of text. */
1345 updateVScrollBarRange(textD);
1346 scrolled |= updateHScrollBarRange(textD);
1348 /* Update the cursor position */
1349 if (textD->cursorToHint != NO_HINT) {
1350 textD->cursorPos = textD->cursorToHint;
1351 textD->cursorToHint = NO_HINT;
1352 } else if (textD->cursorPos > pos) {
1353 if (textD->cursorPos < pos + nDeleted)
1354 textD->cursorPos = pos;
1355 else
1356 textD->cursorPos += nInserted - nDeleted;
1359 /* If the changes caused scrolling, re-paint everything and we're done. */
1360 if (scrolled) {
1361 blankCursorProtrusions(textD);
1362 TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left,
1363 textD->height);
1364 if (textD->styleBuffer) /* See comments in extendRangeForStyleMods */
1365 textD->styleBuffer->primary.selected = False;
1366 return;
1369 /* If the changes didn't cause scrolling, decide the range of characters
1370 that need to be re-painted. Also if the cursor position moved, be
1371 sure that the redisplay range covers the old cursor position so the
1372 old cursor gets erased, and erase the bits of the cursor which extend
1373 beyond the left and right edges of the text. */
1374 startDispPos = textD->continuousWrap ? wrapModStart : pos;
1375 if (origCursorPos == startDispPos && textD->cursorPos != startDispPos)
1376 startDispPos = min(startDispPos, origCursorPos-1);
1377 if (linesInserted == linesDeleted) {
1378 if (nInserted == 0 && nDeleted == 0)
1379 endDispPos = pos + nRestyled;
1380 else {
1381 endDispPos = textD->continuousWrap ? wrapModEnd :
1382 BufEndOfLine(buf, pos + nInserted) + 1;
1383 if (origCursorPos >= startDispPos &&
1384 (origCursorPos <= endDispPos || endDispPos == buf->length))
1385 blankCursorProtrusions(textD);
1387 } else { /* linesInserted != linesDeleted */
1388 endDispPos = textD->lastChar + 1;
1389 if (origCursorPos >= pos)
1390 blankCursorProtrusions(textD);
1391 redrawLineNumbers(textD, False);
1394 /* If there is a style buffer, check if the modification caused additional
1395 changes that need to be redisplayed. (Redisplaying separately would
1396 cause double-redraw on almost every modification involving styled
1397 text). Extend the redraw range to incorporate style changes */
1398 if (textD->styleBuffer)
1399 extendRangeForStyleMods(textD, &startDispPos, &endDispPos);
1401 /* Redisplay computed range */
1402 TextDRedisplayRange(textD, startDispPos, endDispPos);
1406 ** In continuous wrap mode, internal line numbers are calculated after
1407 ** wrapping. A separate non-wrapped line count is maintained when line
1408 ** numbering is turned on. There is some performance cost to maintaining this
1409 ** line count, so normally absolute line numbers are not tracked if line
1410 ** numbering is off. This routine allows callers to specify that they still
1411 ** want this line count maintained (for use via TextDPosToLineAndCol).
1412 ** More specifically, this allows the line number reported in the statistics
1413 ** line to be calibrated in absolute lines, rather than post-wrapped lines.
1415 void TextDMaintainAbsLineNum(textDisp *textD, int state)
1417 textD->needAbsTopLineNum = state;
1418 resetAbsLineNum(textD);
1422 ** Returns the absolute (non-wrapped) line number of the first line displayed.
1423 ** Returns 0 if the absolute top line number is not being maintained.
1425 static int getAbsTopLineNum(textDisp *textD)
1427 if (!textD->continuousWrap)
1428 return textD->topLineNum;
1429 if (maintainingAbsTopLineNum(textD))
1430 return textD->absTopLineNum;
1431 return 0;
1435 ** Re-calculate absolute top line number for a change in scroll position.
1437 static void offsetAbsLineNum(textDisp *textD, int oldFirstChar)
1439 if (maintainingAbsTopLineNum(textD)) {
1440 if (textD->firstChar < oldFirstChar)
1441 textD->absTopLineNum -= BufCountLines(textD->buffer,
1442 textD->firstChar, oldFirstChar);
1443 else
1444 textD->absTopLineNum += BufCountLines(textD->buffer,
1445 oldFirstChar, textD->firstChar);
1450 ** Return true if a separate absolute top line number is being maintained
1451 ** (for displaying line numbers or showing in the statistics line).
1453 static int maintainingAbsTopLineNum(textDisp *textD)
1455 return textD->continuousWrap &&
1456 (textD->lineNumWidth != 0 || textD->needAbsTopLineNum);
1460 ** Count lines from the beginning of the buffer to reestablish the
1461 ** absolute (non-wrapped) top line number. If mode is not continuous wrap,
1462 ** or the number is not being maintained, does nothing.
1464 static void resetAbsLineNum(textDisp *textD)
1466 textD->absTopLineNum = 1;
1467 offsetAbsLineNum(textD, 0);
1471 ** Find the line number of position "pos" relative to the first line of
1472 ** displayed text. Returns False if the line is not displayed.
1474 static int posToVisibleLineNum(textDisp *textD, int pos, int *lineNum)
1476 int i;
1478 if (pos < textD->firstChar)
1479 return False;
1480 if (pos > textD->lastChar) {
1481 if (emptyLinesVisible(textD)) {
1482 if (textD->lastChar < textD->buffer->length) {
1483 if (!posToVisibleLineNum(textD, textD->lastChar, lineNum)) {
1484 fprintf(stderr, "Consistency check ptvl failed\n");
1485 return False;
1487 return ++(*lineNum) <= textD->nVisibleLines-1;
1488 } else {
1489 posToVisibleLineNum(textD, textD->lastChar-1, lineNum);
1490 return True;
1493 return False;
1496 for (i=textD->nVisibleLines-1; i>=0; i--) {
1497 if (textD->lineStarts[i] != -1 && pos >= textD->lineStarts[i]) {
1498 *lineNum = i;
1499 return True;
1502 return False; /* probably never be reached */
1506 ** Redisplay the text on a single line represented by "visLineNum" (the
1507 ** number of lines down from the top of the display), limited by
1508 ** "leftClip" and "rightClip" window coordinates and "leftCharIndex" and
1509 ** "rightCharIndex" character positions (not including the character at
1510 ** position "rightCharIndex").
1512 ** The cursor is also drawn if it appears on the line.
1514 static void redisplayLine(textDisp *textD, int visLineNum, int leftClip,
1515 int rightClip, int leftCharIndex, int rightCharIndex)
1517 textBuffer *buf = textD->buffer;
1518 int i, x, y, startX, charIndex, lineStartPos, lineLen, fontHeight;
1519 int stdCharWidth, charWidth, startIndex, charStyle, style;
1520 int charLen, outStartIndex, outIndex, cursorX = 0, hasCursor = False;
1521 int dispIndexOffset, cursorPos = textD->cursorPos;
1522 char expandedChar[MAX_EXP_CHAR_LEN], outStr[MAX_DISP_LINE_LEN];
1523 char *lineStr, *outPtr;
1525 /* If line is not displayed, skip it */
1526 if (visLineNum < 0 || visLineNum >= textD->nVisibleLines)
1527 return;
1529 /* Calculate y coordinate of the string to draw */
1530 fontHeight = textD->ascent + textD->descent;
1531 y = textD->top + visLineNum * fontHeight;
1533 /* Get the text, length, and buffer position of the line to display */
1534 lineStartPos = textD->lineStarts[visLineNum];
1535 if (lineStartPos == -1) {
1536 lineLen = 0;
1537 lineStr = NULL;
1538 } else {
1539 lineLen = visLineLength(textD, visLineNum);
1540 lineStr = BufGetRange(buf, lineStartPos, lineStartPos + lineLen);
1543 /* Space beyond the end of the line is still counted in units of characters
1544 of a standardized character width (this is done mostly because style
1545 changes based on character position can still occur in this region due
1546 to rectangular selections). stdCharWidth must be non-zero to prevent a
1547 potential infinite loop if x does not advance */
1548 stdCharWidth = textD->fontStruct->max_bounds.width;
1549 if (stdCharWidth <= 0) {
1550 fprintf(stderr, "Internal Error, bad font measurement\n");
1551 XtFree(lineStr);
1552 return;
1555 /* Shrink the clipping range to the active display area */
1556 leftClip = max(textD->left, leftClip);
1557 rightClip = min(rightClip, textD->left + textD->width);
1559 /* Rectangular selections are based on "real" line starts (after a newline
1560 or start of buffer). Calculate the difference between the last newline
1561 position and the line start we're using. Since scanning back to find a
1562 newline is expensive, only do so if there's actually a rectangular
1563 selection which needs it */
1564 if (textD->continuousWrap && (rangeTouchesRectSel(&buf->primary,
1565 lineStartPos, lineStartPos + lineLen) || rangeTouchesRectSel(
1566 &buf->secondary, lineStartPos, lineStartPos + lineLen) ||
1567 rangeTouchesRectSel(&buf->highlight, lineStartPos,
1568 lineStartPos + lineLen))) {
1569 dispIndexOffset = BufCountDispChars(buf,
1570 BufStartOfLine(buf, lineStartPos), lineStartPos);
1571 } else
1572 dispIndexOffset = 0;
1574 /* Step through character positions from the beginning of the line (even if
1575 that's off the left edge of the displayed area) to find the first
1576 character position that's not clipped, and the x coordinate for drawing
1577 that character */
1578 x = textD->left - textD->horizOffset;
1579 outIndex = 0;
1580 for(charIndex=0; ; charIndex++) {
1581 charLen = charIndex >= lineLen ? 1 :
1582 BufExpandCharacter(lineStr[charIndex], outIndex,
1583 expandedChar, buf->tabDist, buf->nullSubsChar);
1584 style = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1585 outIndex + dispIndexOffset);
1586 charWidth = charIndex >= lineLen ? stdCharWidth :
1587 stringWidth(textD, expandedChar, charLen, style);
1588 if (x + charWidth >= leftClip && charIndex >= leftCharIndex) {
1589 startIndex = charIndex;
1590 outStartIndex = outIndex;
1591 startX = x;
1592 break;
1594 x += charWidth;
1595 outIndex += charLen;
1598 /* Scan character positions from the beginning of the clipping range, and
1599 draw parts whenever the style changes (also note if the cursor is on
1600 this line, and where it should be drawn to take advantage of the x
1601 position which we've gone to so much trouble to calculate) */
1602 outPtr = outStr;
1603 outIndex = outStartIndex;
1604 x = startX;
1605 for(charIndex=startIndex; charIndex<rightCharIndex; charIndex++) {
1606 if (lineStartPos+charIndex == cursorPos) {
1607 if (charIndex < lineLen || (charIndex == lineLen &&
1608 cursorPos >= buf->length)) {
1609 hasCursor = True;
1610 cursorX = x - 1;
1611 } else if (charIndex == lineLen) {
1612 if (wrapUsesCharacter(textD, cursorPos)) {
1613 hasCursor = True;
1614 cursorX = x - 1;
1618 charLen = charIndex >= lineLen ? 1 :
1619 BufExpandCharacter(lineStr[charIndex], outIndex, expandedChar,
1620 buf->tabDist, buf->nullSubsChar);
1621 charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1622 outIndex + dispIndexOffset);
1623 for (i=0; i<charLen; i++) {
1624 if (i != 0 && charIndex < lineLen && lineStr[charIndex] == '\t')
1625 charStyle = styleOfPos(textD, lineStartPos, lineLen,
1626 charIndex, outIndex + dispIndexOffset);
1627 if (charStyle != style) {
1628 drawString(textD, style, startX, y, x, outStr, outPtr - outStr);
1629 outPtr = outStr;
1630 startX = x;
1631 style = charStyle;
1633 if (charIndex < lineLen) {
1634 *outPtr = expandedChar[i];
1635 charWidth = stringWidth(textD, &expandedChar[i], 1, charStyle);
1636 } else
1637 charWidth = stdCharWidth;
1638 outPtr++;
1639 x += charWidth;
1640 outIndex++;
1642 if (outPtr-outStr+MAX_EXP_CHAR_LEN>=MAX_DISP_LINE_LEN || x>=rightClip)
1643 break;
1646 /* Draw the remaining style segment */
1647 drawString(textD, style, startX, y, x, outStr, outPtr - outStr);
1649 /* Draw the cursor if part of it appeared on the redisplayed part of
1650 this line. Also check for the cases which are not caught as the
1651 line is scanned above: when the cursor appears at the very end
1652 of the redisplayed section. */
1653 if (textD->cursorOn) {
1654 if (hasCursor)
1655 drawCursor(textD, cursorX, y);
1656 else if (charIndex<lineLen && (lineStartPos+charIndex+1 == cursorPos)
1657 && x == rightClip) {
1658 if (cursorPos >= buf->length)
1659 drawCursor(textD, x - 1, y);
1660 else {
1661 if (wrapUsesCharacter(textD, cursorPos))
1662 drawCursor(textD, x - 1, y);
1667 if (lineStr != NULL)
1668 XtFree(lineStr);
1672 ** Draw a string or blank area according to parameter "style", using the
1673 ** appropriate colors and drawing method for that style, with top left
1674 ** corner at x, y. If style says to draw text, use "string" as source of
1675 ** characters, and draw "nChars", if style is FILL, erase
1676 ** rectangle where text would have drawn from x to toX and from y to
1677 ** the maximum y extent of the current font(s).
1679 static void drawString(textDisp *textD, int style, int x, int y, int toX,
1680 char *string, int nChars)
1682 GC gc;
1683 XGCValues gcValues;
1684 XFontStruct *fs = textD->fontStruct;
1685 styleTableEntry *styleRec;
1686 int underlineStyle = FALSE;
1688 /* Don't draw if widget isn't realized */
1689 if (XtWindow(textD->w) == 0)
1690 return;
1692 /* Draw blank area rather than text, if that was the request */
1693 if (style & FILL_MASK) {
1694 if (toX >= textD->left)
1695 clearRect(textD, style, max(x, textD->left), y,
1696 toX - max(x, textD->left), textD->ascent + textD->descent);
1697 return;
1700 /* Set font, color, and gc depending on style. For normal text, GCs
1701 for normal drawing, or drawing within a selection or highlight are
1702 pre-allocated and pre-configured. For syntax highlighting, GCs are
1703 configured here, on the fly. */
1704 if (style & STYLE_LOOKUP_MASK) {
1705 styleRec = &textD->styleTable[(style & STYLE_LOOKUP_MASK) - 'A'];
1706 underlineStyle = styleRec->underline;
1707 fs = styleRec->font;
1708 gc = textD->styleGC ;
1709 gcValues.font = fs->fid;
1710 gcValues.foreground = styleRec->color;
1711 gcValues.background = style&PRIMARY_MASK ? textD->selectBGPixel :
1712 style&HIGHLIGHT_MASK ? textD->highlightBGPixel : textD->bgPixel;
1713 if (gcValues.foreground == gcValues.background) /* B&W kludge */
1714 gcValues.foreground = textD->bgPixel;
1715 XChangeGC(XtDisplay(textD->w), gc,
1716 GCFont | GCForeground | GCBackground, &gcValues);
1717 } else if (style & HIGHLIGHT_MASK)
1718 gc = textD->highlightGC;
1719 else if (style & PRIMARY_MASK)
1720 gc = textD->selectGC;
1721 else
1722 gc = textD->gc;
1724 /* Draw the string using gc and font set above */
1725 XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w), gc, x,
1726 y + textD->ascent, string, nChars);
1728 /* If any space around the character remains unfilled (due to use of
1729 different sized fonts for highlighting), fill in above or below
1730 to erase previously drawn characters */
1731 if (fs->ascent < textD->ascent)
1732 clearRect(textD, style, x, y, toX - x, textD->ascent - fs->ascent);
1733 if (fs->descent < textD->descent)
1734 clearRect(textD, style, x, y + textD->ascent + fs->descent, toX - x,
1735 textD->descent - fs->descent);
1737 /* Underline if style is secondary selection */
1738 if (style & SECONDARY_MASK || underlineStyle)
1739 XDrawLine(XtDisplay(textD->w), XtWindow(textD->w), gc, x,
1740 y + textD->ascent, toX - 1, y + textD->ascent);
1744 ** Clear a rectangle with the appropriate background color for "style"
1746 static void clearRect(textDisp *textD, int style, int x, int y,
1747 int width, int height)
1749 /* A width of zero means "clear to end of window" to XClearArea */
1750 if (width == 0)
1751 return;
1753 if (style & HIGHLIGHT_MASK)
1754 XFillRectangle(XtDisplay(textD->w), XtWindow(textD->w),
1755 textD->highlightBGGC, x, y, width, height);
1756 else if (style & PRIMARY_MASK)
1757 XFillRectangle(XtDisplay(textD->w), XtWindow(textD->w),
1758 textD->selectBGGC, x, y, width, height);
1759 else
1760 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), x, y,
1761 width, height, False);
1766 ** Draw a cursor with top center at x, y.
1768 static void drawCursor(textDisp *textD, int x, int y)
1770 XSegment segs[5];
1771 int left, right, cursorWidth, midY;
1772 int fontWidth = textD->fontStruct->min_bounds.width, nSegs = 0;
1773 int fontHeight = textD->ascent + textD->descent;
1774 int bot = y + fontHeight - 1;
1776 if (XtWindow(textD->w) == 0 || x < textD->left-1 ||
1777 x > textD->left + textD->width)
1778 return;
1780 /* For cursors other than the block, make them around 2/3 of a character
1781 width, rounded to an even number of pixels so that X will draw an
1782 odd number centered on the stem at x. */
1783 cursorWidth = (fontWidth/3) * 2;
1784 left = x - cursorWidth/2;
1785 right = left + cursorWidth;
1787 /* Create segments and draw cursor */
1788 if (textD->cursorStyle == CARET_CURSOR) {
1789 midY = bot - fontHeight/5;
1790 segs[0].x1 = left; segs[0].y1 = bot; segs[0].x2 = x; segs[0].y2 = midY;
1791 segs[1].x1 = x; segs[1].y1 = midY; segs[1].x2 = right; segs[1].y2 = bot;
1792 segs[2].x1 = left; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2=midY-1;
1793 segs[3].x1 = x; segs[3].y1=midY-1; segs[3].x2 = right; segs[3].y2 = bot;
1794 nSegs = 4;
1795 } else if (textD->cursorStyle == NORMAL_CURSOR) {
1796 segs[0].x1 = left; segs[0].y1 = y; segs[0].x2 = right; segs[0].y2 = y;
1797 segs[1].x1 = x; segs[1].y1 = y; segs[1].x2 = x; segs[1].y2 = bot;
1798 segs[2].x1 = left; segs[2].y1 = bot; segs[2].x2 = right; segs[2].y2=bot;
1799 nSegs = 3;
1800 } else if (textD->cursorStyle == HEAVY_CURSOR) {
1801 segs[0].x1 = x-1; segs[0].y1 = y; segs[0].x2 = x-1; segs[0].y2 = bot;
1802 segs[1].x1 = x; segs[1].y1 = y; segs[1].x2 = x; segs[1].y2 = bot;
1803 segs[2].x1 = x+1; segs[2].y1 = y; segs[2].x2 = x+1; segs[2].y2 = bot;
1804 segs[3].x1 = left; segs[3].y1 = y; segs[3].x2 = right; segs[3].y2 = y;
1805 segs[4].x1 = left; segs[4].y1 = bot; segs[4].x2 = right; segs[4].y2=bot;
1806 nSegs = 5;
1807 } else if (textD->cursorStyle == DIM_CURSOR) {
1808 midY = y + fontHeight/2;
1809 segs[0].x1 = x; segs[0].y1 = y; segs[0].x2 = x; segs[0].y2 = y;
1810 segs[1].x1 = x; segs[1].y1 = midY; segs[1].x2 = x; segs[1].y2 = midY;
1811 segs[2].x1 = x; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2 = bot;
1812 nSegs = 3;
1813 } else if (textD->cursorStyle == BLOCK_CURSOR) {
1814 right = x + fontWidth;
1815 segs[0].x1 = x; segs[0].y1 = y; segs[0].x2 = right; segs[0].y2 = y;
1816 segs[1].x1 = right; segs[1].y1 = y; segs[1].x2 = right; segs[1].y2=bot;
1817 segs[2].x1 = right; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2 = bot;
1818 segs[3].x1 = x; segs[3].y1 = bot; segs[3].x2 = x; segs[3].y2 = y;
1819 nSegs = 4;
1821 XDrawSegments(XtDisplay(textD->w), XtWindow(textD->w),
1822 textD->cursorFGGC, segs, nSegs);
1824 /* Save the last position drawn */
1825 textD->cursorX = x;
1826 textD->cursorY = y;
1830 ** Determine the drawing method to use to draw a specific character from "buf".
1831 ** "lineStartPos" gives the character index where the line begins, "lineIndex",
1832 ** the number of characters past the beginning of the line, and "dispIndex",
1833 ** the number of displayed characters past the beginning of the line. Passing
1834 ** lineStartPos of -1 returns the drawing style for "no text".
1836 ** Why not just: styleOfPos(textD, pos)? Because style applies to blank areas
1837 ** of the window beyond the text boundaries, and because this routine must also
1838 ** decide whether a position is inside of a rectangular selection, and do so
1839 ** efficiently, without re-counting character positions from the start of the
1840 ** line.
1842 ** Note that style is a somewhat incorrect name, drawing method would
1843 ** be more appropriate.
1845 static int styleOfPos(textDisp *textD, int lineStartPos,
1846 int lineLen, int lineIndex, int dispIndex)
1848 textBuffer *buf = textD->buffer;
1849 textBuffer *styleBuf = textD->styleBuffer;
1850 int pos, style = 0;
1852 if (lineStartPos == -1 || buf == NULL)
1853 return FILL_MASK;
1855 pos = lineStartPos + min(lineIndex, lineLen);
1857 if (lineIndex >= lineLen)
1858 style = FILL_MASK;
1859 else if (styleBuf != NULL) {
1860 style = (unsigned char)BufGetCharacter(styleBuf, pos);
1861 if (style == textD->unfinishedStyle) {
1862 /* encountered "unfinished" style, trigger parsing */
1863 (textD->unfinishedHighlightCB)(textD, pos, textD->highlightCBArg);
1864 style = (unsigned char)BufGetCharacter(styleBuf, pos);
1867 if (inSelection(&buf->primary, pos, lineStartPos, dispIndex))
1868 style |= PRIMARY_MASK;
1869 if (inSelection(&buf->highlight, pos, lineStartPos, dispIndex))
1870 style |= HIGHLIGHT_MASK;
1871 if (inSelection(&buf->secondary, pos, lineStartPos, dispIndex))
1872 style |= SECONDARY_MASK;
1873 return style;
1877 ** Find the width of a string in the font of a particular style
1879 static int stringWidth(textDisp *textD, char *string, int length, int style)
1881 XFontStruct *fs;
1883 if (style & STYLE_LOOKUP_MASK)
1884 fs = textD->styleTable[(style & STYLE_LOOKUP_MASK) - 'A'].font;
1885 else
1886 fs = textD->fontStruct;
1887 return XTextWidth(fs, string, length);
1891 ** Return true if position "pos" with indentation "dispIndex" is in
1892 ** selection "sel"
1894 static int inSelection(selection *sel, int pos, int lineStartPos, int dispIndex)
1896 return sel->selected &&
1897 ((!sel->rectangular &&
1898 pos >= sel->start && pos < sel->end) ||
1899 (sel->rectangular &&
1900 pos >= sel->start && lineStartPos <= sel->end &&
1901 dispIndex >= sel->rectStart && dispIndex < sel->rectEnd));
1905 ** Translate window coordinates to the nearest (insert cursor or character
1906 ** cell) text position. The parameter posType specifies how to interpret the
1907 ** position: CURSOR_POS means translate the coordinates to the nearest cursor
1908 ** position, and CHARACTER_POS means return the position of the character
1909 ** closest to (x, y).
1911 static int xyToPos(textDisp *textD, int x, int y, int posType)
1913 int charIndex, lineStart, lineLen, fontHeight;
1914 int charWidth, charLen, charStyle, visLineNum, xStep, outIndex;
1915 char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
1917 /* Find the visible line number corresponding to the y coordinate */
1918 fontHeight = textD->ascent + textD->descent;
1919 visLineNum = (y - textD->top) / fontHeight;
1920 if (visLineNum < 0)
1921 return textD->firstChar;
1922 if (visLineNum >= textD->nVisibleLines)
1923 visLineNum = textD->nVisibleLines - 1;
1925 /* Find the position at the start of the line */
1926 lineStart = textD->lineStarts[visLineNum];
1928 /* If the line start was empty, return the last position in the buffer */
1929 if (lineStart == -1)
1930 return textD->buffer->length;
1932 /* Get the line text and its length */
1933 lineLen = visLineLength(textD, visLineNum);
1934 lineStr = BufGetRange(textD->buffer, lineStart, lineStart + lineLen);
1936 /* Step through character positions from the beginning of the line
1937 to find the character position corresponding to the x coordinate */
1938 xStep = textD->left - textD->horizOffset;
1939 outIndex = 0;
1940 for(charIndex=0; charIndex<lineLen; charIndex++) {
1941 charLen = BufExpandCharacter(lineStr[charIndex], outIndex, expandedChar,
1942 textD->buffer->tabDist, textD->buffer->nullSubsChar);
1943 charStyle = styleOfPos(textD, lineStart, lineLen, charIndex, outIndex);
1944 charWidth = stringWidth(textD, expandedChar, charLen, charStyle);
1945 if (x < xStep + (posType == CURSOR_POS ? charWidth/2 : charWidth)) {
1946 XtFree(lineStr);
1947 return lineStart + charIndex;
1949 xStep += charWidth;
1950 outIndex += charLen;
1953 /* If the x position was beyond the end of the line, return the position
1954 of the newline at the end of the line */
1955 XtFree(lineStr);
1956 return lineStart + lineLen;
1960 ** Translate window coordinates to the nearest row and column number for
1961 ** positioning the cursor. This, of course, makes no sense when the font is
1962 ** proportional, since there are no absolute columns. The parameter posType
1963 ** specifies how to interpret the position: CURSOR_POS means translate the
1964 ** coordinates to the nearest position between characters, and CHARACTER_POS
1965 ** means translate the position to the nearest character cell.
1967 static void xyToUnconstrainedPos(textDisp *textD, int x, int y, int *row,
1968 int *column, int posType)
1970 int fontHeight = textD->ascent + textD->descent;
1971 int fontWidth = textD->fontStruct->max_bounds.width;
1973 /* Find the visible line number corresponding to the y coordinate */
1974 *row = (y - textD->top) / fontHeight;
1975 if (*row < 0) *row = 0;
1976 if (*row >= textD->nVisibleLines) *row = textD->nVisibleLines - 1;
1977 *column = ((x-textD->left) + textD->horizOffset +
1978 (posType == CURSOR_POS ? fontWidth/2 : 0)) / fontWidth;
1979 if (*column < 0) *column = 0;
1983 ** Offset the line starts array, topLineNum, firstChar and lastChar, for a new
1984 ** vertical scroll position given by newTopLineNum. If any currently displayed
1985 ** lines will still be visible, salvage the line starts values, otherwise,
1986 ** count lines from the nearest known line start (start or end of buffer, or
1987 ** the closest value in the lineStarts array)
1989 static void offsetLineStarts(textDisp *textD, int newTopLineNum)
1991 int oldTopLineNum = textD->topLineNum;
1992 int oldFirstChar = textD->firstChar;
1993 int lineDelta = newTopLineNum - oldTopLineNum;
1994 int nVisLines = textD->nVisibleLines;
1995 int *lineStarts = textD->lineStarts;
1996 int i, lastLineNum;
1997 textBuffer *buf = textD->buffer;
1999 /* If there was no offset, nothing needs to be changed */
2000 if (lineDelta == 0)
2001 return;
2003 /* { int i;
2004 printf("Scroll, lineDelta %d\n", lineDelta);
2005 printf("lineStarts Before: ");
2006 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2007 printf("\n");
2008 } */
2010 /* Find the new value for firstChar by counting lines from the nearest
2011 known line start (start or end of buffer, or the closest value in the
2012 lineStarts array) */
2013 lastLineNum = oldTopLineNum + nVisLines - 1;
2014 if (newTopLineNum < oldTopLineNum && newTopLineNum < -lineDelta) {
2015 textD->firstChar = TextDCountForwardNLines(textD, 0, newTopLineNum-1,
2016 True);
2017 /* printf("counting forward %d lines from start\n", newTopLineNum-1);*/
2018 } else if (newTopLineNum < oldTopLineNum) {
2019 textD->firstChar = TextDCountBackwardNLines(textD, textD->firstChar,
2020 -lineDelta);
2021 /* printf("counting backward %d lines from firstChar\n", -lineDelta);*/
2022 } else if (newTopLineNum < lastLineNum) {
2023 textD->firstChar = lineStarts[newTopLineNum - oldTopLineNum];
2024 /* printf("taking new start from lineStarts[%d]\n",
2025 newTopLineNum - oldTopLineNum); */
2026 } else if (newTopLineNum-lastLineNum < textD->nBufferLines-newTopLineNum) {
2027 textD->firstChar = TextDCountForwardNLines(textD, lineStarts[nVisLines-1],
2028 newTopLineNum - lastLineNum, True);
2029 /* printf("counting forward %d lines from start of last line\n",
2030 newTopLineNum - lastLineNum); */
2031 } else {
2032 textD->firstChar = TextDCountBackwardNLines(textD, buf->length,
2033 textD->nBufferLines - newTopLineNum + 1);
2034 /* printf("counting backward %d lines from end\n",
2035 textD->nBufferLines - newTopLineNum + 1); */
2038 /* Fill in the line starts array */
2039 if (lineDelta < 0 && -lineDelta < nVisLines) {
2040 for (i=nVisLines-1; i >= -lineDelta; i--)
2041 lineStarts[i] = lineStarts[i+lineDelta];
2042 calcLineStarts(textD, 0, -lineDelta);
2043 } else if (lineDelta > 0 && lineDelta < nVisLines) {
2044 for (i=0; i<nVisLines-lineDelta; i++)
2045 lineStarts[i] = lineStarts[i+lineDelta];
2046 calcLineStarts(textD, nVisLines-lineDelta, nVisLines-1);
2047 } else
2048 calcLineStarts(textD, 0, nVisLines);
2050 /* Set lastChar and topLineNum */
2051 calcLastChar(textD);
2052 textD->topLineNum = newTopLineNum;
2054 /* If we're numbering lines or being asked to maintain an absolute line
2055 number, re-calculate the absolute line number */
2056 offsetAbsLineNum(textD, oldFirstChar);
2058 /* { int i;
2059 printf("lineStarts After: ");
2060 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2061 printf("\n");
2062 } */
2066 ** Update the line starts array, topLineNum, firstChar and lastChar for text
2067 ** display "textD" after a modification to the text buffer, given by the
2068 ** position where the change began "pos", and the nmubers of characters
2069 ** and lines inserted and deleted.
2071 static void updateLineStarts(textDisp *textD, int pos, int charsInserted,
2072 int charsDeleted, int linesInserted, int linesDeleted, int *scrolled)
2074 int *lineStarts = textD->lineStarts;
2075 int i, lineOfPos, lineOfEnd, nVisLines = textD->nVisibleLines;
2076 int charDelta = charsInserted - charsDeleted;
2077 int lineDelta = linesInserted - linesDeleted;
2079 /* { int i;
2080 printf("linesDeleted %d, linesInserted %d, charsInserted %d, charsDeleted %d\n",
2081 linesDeleted, linesInserted, charsInserted, charsDeleted);
2082 printf("lineStarts Before: ");
2083 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2084 printf("\n");
2085 } */
2086 /* If all of the changes were before the displayed text, the display
2087 doesn't change, just update the top line num and offset the line
2088 start entries and first and last characters */
2089 if (pos + charsDeleted < textD->firstChar) {
2090 textD->topLineNum += lineDelta;
2091 for (i=0; i<nVisLines && lineStarts[i] != -1; i++)
2092 lineStarts[i] += charDelta;
2093 /* { int i;
2094 printf("lineStarts after delete doesn't touch: ");
2095 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2096 printf("\n");
2097 } */
2098 textD->firstChar += charDelta;
2099 textD->lastChar += charDelta;
2100 *scrolled = False;
2101 return;
2104 /* The change began before the beginning of the displayed text, but
2105 part or all of the displayed text was deleted */
2106 if (pos < textD->firstChar) {
2107 /* If some text remains in the window, anchor on that */
2108 if (posToVisibleLineNum(textD, pos + charsDeleted, &lineOfEnd) &&
2109 ++lineOfEnd < nVisLines && lineStarts[lineOfEnd] != -1) {
2110 textD->topLineNum = max(1, textD->topLineNum + lineDelta);
2111 textD->firstChar = TextDCountBackwardNLines(textD,
2112 lineStarts[lineOfEnd] + charDelta, lineOfEnd);
2113 /* Otherwise anchor on original line number and recount everything */
2114 } else {
2115 if (textD->topLineNum > textD->nBufferLines + lineDelta) {
2116 textD->topLineNum = 1;
2117 textD->firstChar = 0;
2118 } else
2119 textD->firstChar = TextDCountForwardNLines(textD, 0,
2120 textD->topLineNum - 1, True);
2122 calcLineStarts(textD, 0, nVisLines-1);
2123 /* { int i;
2124 printf("lineStarts after delete encroaches: ");
2125 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2126 printf("\n");
2127 } */
2128 /* calculate lastChar by finding the end of the last displayed line */
2129 calcLastChar(textD);
2130 *scrolled = True;
2131 return;
2134 /* If the change was in the middle of the displayed text (it usually is),
2135 salvage as much of the line starts array as possible by moving and
2136 offsetting the entries after the changed area, and re-counting the
2137 added lines or the lines beyond the salvaged part of the line starts
2138 array */
2139 if (pos <= textD->lastChar) {
2140 /* find line on which the change began */
2141 posToVisibleLineNum(textD, pos, &lineOfPos);
2142 /* salvage line starts after the changed area */
2143 if (lineDelta == 0) {
2144 for (i=lineOfPos+1; i<nVisLines && lineStarts[i]!= -1; i++)
2145 lineStarts[i] += charDelta;
2146 } else if (lineDelta > 0) {
2147 for (i=nVisLines-1; i>=lineOfPos+lineDelta+1; i--)
2148 lineStarts[i] = lineStarts[i-lineDelta] +
2149 (lineStarts[i-lineDelta] == -1 ? 0 : charDelta);
2150 } else /* (lineDelta < 0) */ {
2151 for (i=max(0,lineOfPos+1); i<nVisLines+lineDelta; i++)
2152 lineStarts[i] = lineStarts[i-lineDelta] +
2153 (lineStarts[i-lineDelta] == -1 ? 0 : charDelta);
2155 /* { int i;
2156 printf("lineStarts after salvage: ");
2157 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2158 printf("\n");
2159 } */
2160 /* fill in the missing line starts */
2161 if (linesInserted >= 0)
2162 calcLineStarts(textD, lineOfPos + 1, lineOfPos + linesInserted);
2163 if (lineDelta < 0)
2164 calcLineStarts(textD, nVisLines+lineDelta, nVisLines);
2165 /* { int i;
2166 printf("lineStarts after recalculation: ");
2167 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2168 printf("\n");
2169 } */
2170 /* calculate lastChar by finding the end of the last displayed line */
2171 calcLastChar(textD);
2172 *scrolled = False;
2173 return;
2176 /* Change was past the end of the displayed text, but displayable by virtue
2177 of being an insert at the end of the buffer into visible blank lines */
2178 if (emptyLinesVisible(textD)) {
2179 posToVisibleLineNum(textD, pos, &lineOfPos);
2180 calcLineStarts(textD, lineOfPos, lineOfPos+linesInserted);
2181 calcLastChar(textD);
2182 /* { int i;
2183 printf("lineStarts after insert at end: ");
2184 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2185 printf("\n");
2186 } */
2187 *scrolled = False;
2188 return;
2191 /* Change was beyond the end of the buffer and not visible, do nothing */
2192 *scrolled = False;
2196 ** Scan through the text in the "textD"'s buffer and recalculate the line
2197 ** starts array values beginning at index "startLine" and continuing through
2198 ** (including) "endLine". It assumes that the line starts entry preceding
2199 ** "startLine" (or textD->firstChar if startLine is 0) is good, and re-counts
2200 ** newlines to fill in the requested entries. Out of range values for
2201 ** "startLine" and "endLine" are acceptable.
2203 static void calcLineStarts(textDisp *textD, int startLine, int endLine)
2205 int startPos, bufLen = textD->buffer->length;
2206 int line, lineEnd, nextLineStart, nVis = textD->nVisibleLines;
2207 int *lineStarts = textD->lineStarts;
2209 /* Clean up (possibly) messy input parameters */
2210 if (endLine < 0) endLine = 0;
2211 if (endLine >= nVis) endLine = nVis - 1;
2212 if (startLine < 0) startLine = 0;
2213 if (startLine >=nVis) startLine = nVis - 1;
2214 if (startLine > endLine)
2215 return;
2217 /* Find the last known good line number -> position mapping */
2218 if (startLine == 0) {
2219 lineStarts[0] = textD->firstChar;
2220 startLine = 1;
2222 startPos = lineStarts[startLine-1];
2224 /* If the starting position is already past the end of the text,
2225 fill in -1's (means no text on line) and return */
2226 if (startPos == -1) {
2227 for (line=startLine; line<=endLine; line++)
2228 lineStarts[line] = -1;
2229 return;
2232 /* Loop searching for ends of lines and storing the positions of the
2233 start of the next line in lineStarts */
2234 for (line=startLine; line<=endLine; line++) {
2235 findLineEnd(textD, startPos, True, &lineEnd, &nextLineStart);
2236 startPos = nextLineStart;
2237 if (startPos >= bufLen) {
2238 /* If the buffer ends with a newline or line break, put
2239 buf->length in the next line start position (instead of
2240 a -1 which is the normal marker for an empty line) to
2241 indicate that the cursor may safely be displayed there */
2242 if (line == 0 || (lineStarts[line-1] != bufLen &&
2243 lineEnd != nextLineStart)) {
2244 lineStarts[line] = bufLen;
2245 line++;
2247 break;
2249 lineStarts[line] = startPos;
2252 /* Set any entries beyond the end of the text to -1 */
2253 for (; line<=endLine; line++)
2254 lineStarts[line] = -1;
2258 ** Given a textDisp with a complete, up-to-date lineStarts array, update
2259 ** the lastChar entry to point to the last buffer position displayed.
2261 static void calcLastChar(textDisp *textD)
2263 int i;
2265 for (i=textD->nVisibleLines-1; i>0 && textD->lineStarts[i]== -1; i--);
2266 textD->lastChar = i < 0 ? 0 :
2267 TextDEndOfLine(textD, textD->lineStarts[i], True);
2270 static void setScroll(textDisp *textD, int topLineNum, int horizOffset,
2271 int updateVScrollBar, int updateHScrollBar)
2273 int fontHeight = textD->ascent + textD->descent;
2274 int origHOffset = textD->horizOffset;
2275 int lineDelta = textD->topLineNum - topLineNum;
2276 int xOffset, yOffset, srcX, srcY, dstX, dstY, width, height;
2277 int exactHeight = textD->height - textD->height %
2278 (textD->ascent + textD->descent);
2280 /* Do nothing if scroll position hasn't actually changed or there's no
2281 window to draw in yet */
2282 if (XtWindow(textD->w) == 0 || (textD->horizOffset == horizOffset &&
2283 textD->topLineNum == topLineNum))
2284 return;
2286 /* If part of the cursor is protruding beyond the text clipping region,
2287 clear it off */
2288 blankCursorProtrusions(textD);
2290 /* If the vertical scroll position has changed, update the line
2291 starts array and related counters in the text display */
2292 offsetLineStarts(textD, topLineNum);
2294 /* Just setting textD->horizOffset is enough information for redisplay */
2295 textD->horizOffset = horizOffset;
2297 /* Update the scroll bar positions if requested, note: updating the
2298 horizontal scroll bars can have the further side-effect of changing
2299 the horizontal scroll position, textD->horizOffset */
2300 if (updateVScrollBar && textD->vScrollBar != NULL)
2301 updateVScrollBarRange(textD);
2302 if (updateHScrollBar && textD->hScrollBar != NULL) {
2303 updateHScrollBarRange(textD);
2306 /* Redisplay everything if the window is partially obscured (since
2307 it's too hard to tell what displayed areas are salvageable) or
2308 if there's nothing to recover because the scroll distance is large */
2309 xOffset = origHOffset - textD->horizOffset;
2310 yOffset = lineDelta * fontHeight;
2311 if (textD->visibility != VisibilityUnobscured ||
2312 abs(xOffset) > textD->width || abs(yOffset) > exactHeight) {
2313 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
2314 textD->height);
2316 /* If the window is not obscured, paint most of the window using XCopyArea
2317 from existing displayed text, and redraw only what's necessary */
2318 } else {
2319 /* Recover the useable window areas by moving to the proper location */
2320 srcX = textD->left + (xOffset >= 0 ? 0 : -xOffset);
2321 dstX = textD->left + (xOffset >= 0 ? xOffset : 0);
2322 width = textD->width - abs(xOffset);
2323 srcY = textD->top + (yOffset >= 0 ? 0 : -yOffset);
2324 dstY = textD->top + (yOffset >= 0 ? yOffset : 0);
2325 height = exactHeight - abs(yOffset);
2326 resetClipRectangles(textD);
2327 XCopyArea(XtDisplay(textD->w), XtWindow(textD->w), XtWindow(textD->w),
2328 textD->gc, srcX, srcY, width, height, dstX, dstY);
2329 /* redraw the un-recoverable parts */
2330 if (yOffset > 0)
2331 TextDRedisplayRect(textD, textD->left, textD->top,
2332 textD->width, yOffset);
2333 else if (yOffset < 0)
2334 TextDRedisplayRect(textD, textD->left, textD->top +
2335 textD->height + yOffset, textD->width, -yOffset);
2336 if (xOffset > 0)
2337 TextDRedisplayRect(textD, textD->left, textD->top,
2338 xOffset, textD->height);
2339 else if (xOffset < 0)
2340 TextDRedisplayRect(textD, textD->left + textD->width + xOffset,
2341 textD->top, -xOffset, textD->height);
2342 /* Restore protruding parts of the cursor */
2343 TextDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
2346 /* Refresh line number display if its up and we've scrolled vertically */
2347 if (lineDelta != 0)
2348 redrawLineNumbers(textD, False);
2352 ** Update the minimum, maximum, slider size, page increment, and value
2353 ** for vertical scroll bar.
2355 static void updateVScrollBarRange(textDisp *textD)
2357 int sliderSize, sliderMax, sliderValue;
2359 if (textD->vScrollBar == NULL)
2360 return;
2362 /* The Vert. scroll bar value and slider size directly represent the top
2363 line number, and the number of visible lines respectively. The scroll
2364 bar maximum value is chosen to generally represent the size of the whole
2365 buffer, with minor adjustments to keep the scroll bar widget happy */
2366 sliderSize = textD->nVisibleLines;
2367 sliderValue = textD->topLineNum;
2368 sliderMax = max(textD->nBufferLines + 2, sliderSize + sliderValue);
2369 XtVaSetValues(textD->vScrollBar,
2370 XmNmaximum, sliderMax,
2371 XmNsliderSize, sliderSize,
2372 XmNpageIncrement, max(1, textD->nVisibleLines - 1),
2373 XmNvalue, sliderValue, NULL);
2377 ** Update the minimum, maximum, slider size, page increment, and value
2378 ** for the horizontal scroll bar. If scroll position is such that there
2379 ** is blank space to the right of all lines of text, scroll back (adjust
2380 ** horizOffset but don't redraw) to take up the slack and position the
2381 ** right edge of the text at the right edge of the display.
2383 ** Note, there is some cost to this routine, since it scans the whole range
2384 ** of displayed text, particularly since it's usually called for each typed
2385 ** character!
2387 static int updateHScrollBarRange(textDisp *textD)
2389 int i, maxWidth = 0, sliderMax, sliderWidth;
2390 int origHOffset = textD->horizOffset;
2392 if (textD->hScrollBar == NULL || !XtIsManaged(textD->hScrollBar))
2393 return False;
2395 /* Scan all the displayed lines to find the width of the longest line */
2396 for (i=0; i<textD->nVisibleLines && textD->lineStarts[i]!= -1; i++)
2397 maxWidth = max(measureVisLine(textD, i), maxWidth);
2399 /* If the scroll position is beyond what's necessary to keep all lines
2400 in view, scroll to the left to bring the end of the longest line to
2401 the right margin */
2402 if (maxWidth < textD->width + textD->horizOffset && textD->horizOffset > 0)
2403 textD->horizOffset = max(0, maxWidth - textD->width);
2405 /* Readjust the scroll bar */
2406 sliderWidth = textD->width;
2407 sliderMax = max(maxWidth, sliderWidth + textD->horizOffset);
2408 XtVaSetValues(textD->hScrollBar,
2409 XmNmaximum, sliderMax,
2410 XmNsliderSize, sliderWidth,
2411 XmNpageIncrement, max(textD->width - 100, 10),
2412 XmNvalue, textD->horizOffset, NULL);
2414 /* Return True if scroll position was changed */
2415 return origHOffset != textD->horizOffset;
2419 ** Define area for drawing line numbers. A width of 0 disables line
2420 ** number drawing.
2422 void TextDSetLineNumberArea(textDisp *textD, int lineNumLeft, int lineNumWidth,
2423 int textLeft)
2425 int newWidth = textD->width + textD->left - textLeft;
2426 textD->lineNumLeft = lineNumLeft;
2427 textD->lineNumWidth = lineNumWidth;
2428 textD->left = textLeft;
2429 XClearWindow(XtDisplay(textD->w), XtWindow(textD->w));
2430 resetAbsLineNum(textD);
2431 TextDResize(textD, newWidth, textD->height);
2432 TextDRedisplayRect(textD, 0, textD->top, INT_MAX, textD->height);
2436 ** Refresh the line number area. If clearAll is False, writes only over
2437 ** the character cell areas. Setting clearAll to True will clear out any
2438 ** stray marks outside of the character cell area, which might have been
2439 ** left from before a resize or font change.
2441 static void redrawLineNumbers(textDisp *textD, int clearAll)
2443 int y, line, visLine, nCols, lineStart;
2444 char lineNumString[12];
2445 int lineHeight = textD->ascent + textD->descent;
2446 int charWidth = textD->fontStruct->max_bounds.width;
2448 /* Don't draw if lineNumWidth == 0 (line numbers are hidden), or widget is
2449 not yet realized */
2450 if (textD->lineNumWidth == 0 || XtWindow(textD->w) == 0)
2451 return;
2453 /* GC is allocated on demand, since not everyone will use line numbering */
2454 if (textD->lineNumGC == NULL) {
2455 XGCValues values;
2456 values.foreground = textD->lineNumFGPixel;
2457 values.background = textD->bgPixel;
2458 values.font = textD->fontStruct->fid;
2459 textD->lineNumGC = XtGetGC(textD->w,
2460 GCFont| GCForeground | GCBackground, &values);
2463 /* Erase the previous contents of the line number area, if requested */
2464 if (clearAll)
2465 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->lineNumLeft,
2466 textD->top, textD->lineNumWidth, textD->height, False);
2468 /* Draw the line numbers, aligned to the text */
2469 nCols = min(11, textD->lineNumWidth / charWidth);
2470 y = textD->top;
2471 line = getAbsTopLineNum(textD);
2472 for (visLine=0; visLine < textD->nVisibleLines; visLine++) {
2473 lineStart = textD->lineStarts[visLine];
2474 if (lineStart != -1 && (lineStart==0 ||
2475 BufGetCharacter(textD->buffer, lineStart-1)=='\n')) {
2476 sprintf(lineNumString, "%*d", nCols, line);
2477 XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w),
2478 textD->lineNumGC, textD->lineNumLeft, y + textD->ascent,
2479 lineNumString, strlen(lineNumString));
2480 line++;
2481 } else {
2482 XClearArea(XtDisplay(textD->w), XtWindow(textD->w),
2483 textD->lineNumLeft, y, textD->lineNumWidth,
2484 textD->ascent + textD->descent, False);
2485 if (visLine == 0)
2486 line++;
2488 y += lineHeight;
2493 ** Callbacks for drag or valueChanged on scroll bars
2495 static void vScrollCB(Widget w, XtPointer clientData, XtPointer callData)
2497 textDisp *textD = (textDisp *)clientData;
2498 int newValue = ((XmScrollBarCallbackStruct *)callData)->value;
2499 int lineDelta = newValue - textD->topLineNum;
2501 if (lineDelta == 0)
2502 return;
2503 setScroll(textD, newValue, textD->horizOffset, False, True);
2505 static void hScrollCB(Widget w, XtPointer clientData, XtPointer callData)
2507 textDisp *textD = (textDisp *)clientData;
2508 int newValue = ((XmScrollBarCallbackStruct *)callData)->value;
2510 if (newValue == textD->horizOffset)
2511 return;
2512 setScroll(textD, textD->topLineNum, newValue, False, False);
2515 static void visibilityEH(Widget w, XtPointer data, XEvent *event,
2516 Boolean *continueDispatch)
2518 /* Record whether the window is fully visible or not. This information
2519 is used for choosing the scrolling methodology for optimal performance,
2520 if the window is partially obscured, XCopyArea may not work */
2521 ((textDisp *)data)->visibility = ((XVisibilityEvent *)event)->state;
2524 static int max(int i1, int i2)
2526 return i1 >= i2 ? i1 : i2;
2529 static int min(int i1, int i2)
2531 return i1 <= i2 ? i1 : i2;
2535 ** Count the number of newlines in a null-terminated text string;
2537 static int countLines(char *string)
2539 char *c;
2540 int lineCount = 0;
2542 if (string == NULL)
2543 return 0;
2544 for (c=string; *c!='\0'; c++)
2545 if (*c == '\n') lineCount++;
2546 return lineCount;
2550 ** Return the width in pixels of the displayed line pointed to by "visLineNum"
2552 static int measureVisLine(textDisp *textD, int visLineNum)
2554 int i, width = 0, len, style, lineLen = visLineLength(textD, visLineNum);
2555 int charCount = 0, lineStartPos = textD->lineStarts[visLineNum];
2556 char expandedChar[MAX_EXP_CHAR_LEN];
2558 if (textD->styleBuffer == NULL) {
2559 for (i=0; i<lineLen; i++) {
2560 len = BufGetExpandedChar(textD->buffer, lineStartPos + i,
2561 charCount, expandedChar);
2562 width += XTextWidth(textD->fontStruct, expandedChar, len);
2563 charCount += len;
2565 } else {
2566 for (i=0; i<lineLen; i++) {
2567 len = BufGetExpandedChar(textD->buffer, lineStartPos+i,
2568 charCount, expandedChar);
2569 style = (unsigned char)BufGetCharacter(textD->styleBuffer,
2570 lineStartPos+i) - 'A';
2571 width += XTextWidth(textD->styleTable[style].font, expandedChar,
2572 len);
2573 charCount += len;
2576 return width;
2580 ** Return true if there are lines visible with no corresponding buffer text
2582 static int emptyLinesVisible(textDisp *textD)
2584 return textD->nVisibleLines > 0 &&
2585 textD->lineStarts[textD->nVisibleLines-1] == -1;
2589 ** When the cursor is at the left or right edge of the text, part of it
2590 ** sticks off into the clipped region beyond the text. Normal redrawing
2591 ** can not overwrite this protruding part of the cursor, so it must be
2592 ** erased independently by calling this routine.
2594 static void blankCursorProtrusions(textDisp *textD)
2596 int x, width, cursorX = textD->cursorX, cursorY = textD->cursorY;
2597 int fontWidth = textD->fontStruct->max_bounds.width;
2598 int fontHeight = textD->ascent + textD->descent;
2599 int cursorWidth, left = textD->left, right = left + textD->width;
2601 cursorWidth = (fontWidth/3) * 2;
2602 if (cursorX >= left-1 && cursorX <= left + cursorWidth/2 - 1) {
2603 x = cursorX - cursorWidth/2;
2604 width = left - x;
2605 } else if (cursorX >= right - cursorWidth/2 && cursorX <= right) {
2606 x = right;
2607 width = cursorX + cursorWidth/2 + 2 - right;
2608 } else
2609 return;
2611 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), x, cursorY,
2612 width, fontHeight, False);
2616 ** Allocate shared graphics contexts used by the widget, which must be
2617 ** re-allocated on a font change.
2619 static void allocateFixedFontGCs(textDisp *textD, XFontStruct *fontStruct,
2620 Pixel bgPixel, Pixel fgPixel, Pixel selectFGPixel, Pixel selectBGPixel,
2621 Pixel highlightFGPixel, Pixel highlightBGPixel)
2623 textD->gc = allocateGC(textD->w, GCFont | GCForeground | GCBackground,
2624 fgPixel, bgPixel, fontStruct->fid, GCClipMask, GCArcMode);
2625 textD->selectGC = allocateGC(textD->w, GCFont | GCForeground | GCBackground,
2626 selectFGPixel, selectBGPixel, fontStruct->fid, GCClipMask,
2627 GCArcMode);
2628 textD->selectBGGC = allocateGC(textD->w, GCForeground, selectBGPixel, 0,
2629 fontStruct->fid, GCClipMask, GCArcMode);
2630 textD->highlightGC = allocateGC(textD->w, GCFont|GCForeground|GCBackground,
2631 highlightFGPixel, highlightBGPixel, fontStruct->fid, GCClipMask,
2632 GCArcMode);
2633 textD->highlightBGGC = allocateGC(textD->w, GCForeground, highlightBGPixel,
2634 0, fontStruct->fid, GCClipMask, GCArcMode);
2638 ** X11R4 does not have the XtAllocateGC function for sharing graphics contexts
2639 ** with changeable fields. Unfortunately the R4 call for creating shared
2640 ** graphics contexts (XtGetGC) is rarely useful because most widgets need
2641 ** to be able to set and change clipping, and that makes the GC unshareable.
2643 ** This function allocates and returns a gc, using XtAllocateGC if possible,
2644 ** or XCreateGC on X11R4 systems where XtAllocateGC is not available.
2646 static GC allocateGC(Widget w, unsigned long valueMask,
2647 unsigned long foreground, unsigned long background, Font font,
2648 unsigned long dynamicMask, unsigned long dontCareMask)
2650 XGCValues gcValues;
2652 gcValues.font = font;
2653 gcValues.background = background;
2654 gcValues.foreground = foreground;
2655 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4
2656 return XtAllocateGC(w, 0, valueMask, &gcValues, dynamicMask,
2657 dontCareMask);
2658 #else
2659 return XCreateGC(XtDisplay(w), RootWindowOfScreen(XtScreen(w)),
2660 valueMask, &gcValues);
2661 #endif
2665 ** Release a gc allocated with allocateGC above
2667 static void releaseGC(Widget w, GC gc)
2669 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4
2670 XtReleaseGC(w, gc);
2671 #else
2672 XFreeGC(XtDisplay(w), gc);
2673 #endif
2677 ** resetClipRectangles sets the clipping rectangles for GCs which clip
2678 ** at the text boundary (as opposed to the window boundary). These GCs
2679 ** are shared such that the drawing styles are constant, but the clipping
2680 ** rectangles are allowed to change among different users of the GCs (the
2681 ** GCs were created with XtAllocGC). This routine resets them so the clipping
2682 ** rectangles are correct for this text display.
2684 static void resetClipRectangles(textDisp *textD)
2686 XRectangle clipRect;
2687 Display *display = XtDisplay(textD->w);
2689 clipRect.x = textD->left;
2690 clipRect.y = textD->top;
2691 clipRect.width = textD->width;
2692 clipRect.height = textD->height - textD->height %
2693 (textD->ascent + textD->descent);
2695 XSetClipRectangles(display, textD->gc, 0, 0,
2696 &clipRect, 1, Unsorted);
2697 XSetClipRectangles(display, textD->selectGC, 0, 0,
2698 &clipRect, 1, Unsorted);
2699 XSetClipRectangles(display, textD->highlightGC, 0, 0,
2700 &clipRect, 1, Unsorted);
2701 XSetClipRectangles(display, textD->selectBGGC, 0, 0,
2702 &clipRect, 1, Unsorted);
2703 XSetClipRectangles(display, textD->highlightBGGC, 0, 0,
2704 &clipRect, 1, Unsorted);
2705 XSetClipRectangles(display, textD->styleGC, 0, 0,
2706 &clipRect, 1, Unsorted);
2710 ** Return the length of a line (number of displayable characters) by examining
2711 ** entries in the line starts array rather than by scanning for newlines
2713 static int visLineLength(textDisp *textD, int visLineNum)
2715 int nextLineStart, lineStartPos = textD->lineStarts[visLineNum];
2717 if (lineStartPos == -1)
2718 return 0;
2719 if (visLineNum+1 >= textD->nVisibleLines)
2720 return textD->lastChar - lineStartPos;
2721 nextLineStart = textD->lineStarts[visLineNum+1];
2722 if (nextLineStart == -1)
2723 return textD->lastChar - lineStartPos;
2724 if (wrapUsesCharacter(textD, nextLineStart-1))
2725 return nextLineStart-1 - lineStartPos;
2726 return nextLineStart - lineStartPos;
2730 ** When continuous wrap is on, and the user inserts or deletes characters,
2731 ** wrapping can happen before and beyond the changed position. This routine
2732 ** finds the extent of the changes, and counts the deleted and inserted lines
2733 ** over that range. It also attempts to minimize the size of the range to
2734 ** what has to be counted and re-displayed, so the results can be useful
2735 ** both for delimiting where the line starts need to be recalculated, and
2736 ** for deciding what part of the text to redisplay.
2738 static void findWrapRange(textDisp *textD, char *deletedText, int pos,
2739 int nInserted, int nDeleted, int *modRangeStart, int *modRangeEnd,
2740 int *linesInserted, int *linesDeleted)
2742 int length, retPos, retLines, retLineStart, retLineEnd;
2743 textBuffer *deletedTextBuf, *buf = textD->buffer;
2744 int nVisLines = textD->nVisibleLines;
2745 int *lineStarts = textD->lineStarts;
2746 int countFrom, countTo, lineStart, adjLineStart, i;
2747 int visLineNum = 0, nLines = 0;
2750 ** Determine where to begin searching: either the previous newline, or
2751 ** if possible, limit to the start of the (original) previous displayed
2752 ** line, using information from the existing line starts array
2754 if (pos >= textD->firstChar && pos <= textD->lastChar) {
2755 for (i=nVisLines-1; i>0; i--)
2756 if (lineStarts[i] != -1 && pos >= lineStarts[i])
2757 break;
2758 if (i > 0) {
2759 countFrom = lineStarts[i-1];
2760 visLineNum = i-1;
2761 } else
2762 countFrom = BufStartOfLine(buf, pos);
2763 } else
2764 countFrom = BufStartOfLine(buf, pos);
2768 ** Move forward through the (new) text one line at a time, counting
2769 ** displayed lines, and looking for either a real newline, or for the
2770 ** line starts to re-sync with the original line starts array
2772 lineStart = countFrom;
2773 *modRangeStart = countFrom;
2774 while (True) {
2776 /* advance to the next line. If the line ended in a real newline
2777 or the end of the buffer, that's far enough */
2778 wrappedLineCounter(textD, buf, lineStart, buf->length, 1, True,
2779 &retPos, &retLines, &retLineStart, &retLineEnd);
2780 if (retPos >= buf->length) {
2781 countTo = buf->length;
2782 *modRangeEnd = countTo;
2783 if (retPos != retLineEnd)
2784 nLines++;
2785 break;
2786 } else
2787 lineStart = retPos;
2788 nLines++;
2789 if (lineStart > pos + nInserted &&
2790 BufGetCharacter(buf, lineStart-1) == '\n') {
2791 countTo = lineStart;
2792 *modRangeEnd = lineStart;
2793 break;
2796 /* check for synchronization with the original line starts array
2797 before pos, if so, the modified range can begin later */
2798 if (lineStart <= pos) {
2799 while (visLineNum<nVisLines && lineStarts[visLineNum] < lineStart)
2800 visLineNum++;
2801 if (visLineNum < nVisLines && lineStarts[visLineNum] == lineStart) {
2802 countFrom = lineStart;
2803 nLines = 0;
2804 if (visLineNum+1 < nVisLines && lineStarts[visLineNum+1] != -1)
2805 *modRangeStart = min(pos, lineStarts[visLineNum+1]-1);
2806 else
2807 *modRangeStart = countFrom;
2808 } else
2809 *modRangeStart = min(*modRangeStart, lineStart-1);
2812 /* check for synchronization with the original line starts array
2813 after pos, if so, the modified range can end early */
2814 else if (lineStart > pos + nInserted) {
2815 adjLineStart = lineStart - nInserted + nDeleted;
2816 while (visLineNum<nVisLines && lineStarts[visLineNum]<adjLineStart)
2817 visLineNum++;
2818 if (visLineNum < nVisLines && lineStarts[visLineNum] != -1 &&
2819 lineStarts[visLineNum] == adjLineStart) {
2820 countTo = TextDEndOfLine(textD, lineStart, True);
2821 *modRangeEnd = lineStart;
2822 break;
2826 *linesInserted = nLines;
2829 /* Count deleted lines between countFrom and countTo as the text existed
2830 before the modification (that is, as if the text between pos and
2831 pos+nInserted were replaced by "deletedText"). This extra context is
2832 necessary because wrapping can occur outside of the modified region
2833 as a result of adding or deleting text in the region. This is done by
2834 creating a textBuffer containing the deleted text and the necessary
2835 additional context, and calling the wrappedLineCounter on it. */
2836 length = (pos-countFrom) + nDeleted +(countTo-(pos+nInserted));
2837 deletedTextBuf = BufCreatePreallocated(length);
2838 BufCopyFromBuf(textD->buffer, deletedTextBuf, countFrom, pos, 0);
2839 if (nDeleted != 0)
2840 BufInsert(deletedTextBuf, pos-countFrom, deletedText);
2841 BufCopyFromBuf(textD->buffer, deletedTextBuf,
2842 pos+nInserted, countTo, pos-countFrom+nDeleted);
2843 wrappedLineCounter(textD, deletedTextBuf, 0, length, INT_MAX, True,
2844 &retPos, &retLines, &retLineStart, &retLineEnd);
2845 BufFree(deletedTextBuf);
2846 *linesDeleted = retLines;
2850 ** Count forward from startPos to either maxPos or maxLines (whichever is
2851 ** reached first), and return all relevant positions and line count.
2853 ** Returned values:
2855 ** retPos: Position where counting ended. When counting lines, the
2856 ** position returned is the start of the line "maxLines"
2857 ** lines beyond "startPos".
2858 ** retLines: Number of line breaks counted
2859 ** retLineStart: Start of the line where counting ended
2860 ** retLineEnd: End position of the last line traversed
2862 static void wrappedLineCounter(textDisp *textD, textBuffer *buf, int startPos,
2863 int maxPos, int maxLines, int startPosIsLineStart, int *retPos,
2864 int *retLines, int *retLineStart, int *retLineEnd)
2866 int lineStart, newLineStart = 0, b, p, colNum, wrapMargin;
2867 int maxWidth, width, countPixels, i, foundBreak;
2868 int nLines = 0, tabDist = textD->buffer->tabDist;
2869 unsigned char c;
2870 char nullSubsChar = textD->buffer->nullSubsChar;
2872 /* If the font is fixed, or there's a wrap margin set, it's more efficient
2873 to measure in columns, than to count pixels. Determine if we can count
2874 in columns (countPixels == False) or must count pixels (countPixels ==
2875 True), and set the wrap target for either pixels or columns */
2876 if (textD->fixedFontWidth != -1 || textD->wrapMargin != 0) {
2877 countPixels = False;
2878 wrapMargin = textD->wrapMargin != 0 ? textD->wrapMargin :
2879 textD->width / textD->fixedFontWidth;
2880 maxWidth = INT_MAX;
2881 } else {
2882 countPixels = True;
2883 wrapMargin = INT_MAX;
2884 maxWidth = textD->width;
2887 /* Find the start of the line if the start pos is not marked as a
2888 line start. */
2889 if (startPosIsLineStart)
2890 lineStart = startPos;
2891 else
2892 lineStart = TextDStartOfLine(textD, startPos);
2895 ** Loop until position exceeds maxPos or line count exceeds maxLines.
2896 ** (actually, contines beyond maxPos to end of line containing maxPos,
2897 ** in case later characters cause a word wrap back before maxPos)
2899 colNum = 0;
2900 width = 0;
2901 for (p=lineStart; p<buf->length; p++) {
2902 c = BufGetCharacter(buf, p);
2904 /* If the character was a newline, count the line and start over,
2905 otherwise, add it to the width and column counts */
2906 if (c == '\n') {
2907 if (p >= maxPos) {
2908 *retPos = maxPos;
2909 *retLines = nLines;
2910 *retLineStart = lineStart;
2911 *retLineEnd = maxPos;
2912 return;
2914 nLines++;
2915 if (nLines >= maxLines) {
2916 *retPos = p + 1;
2917 *retLines = nLines;
2918 *retLineStart = p + 1;
2919 *retLineEnd = p;
2920 return;
2922 lineStart = p + 1;
2923 colNum = 0;
2924 width = 0;
2925 } else {
2926 colNum += BufCharWidth(c, colNum, tabDist, nullSubsChar);
2927 if (countPixels)
2928 width += measurePropChar(textD, c, colNum, p);
2931 /* If character exceeded wrap margin, find the break point
2932 and wrap there */
2933 if (colNum > wrapMargin || width > maxWidth) {
2934 foundBreak = False;
2935 for (b=p; b>=lineStart; b--) {
2936 c = BufGetCharacter(buf, b);
2937 if (c == '\t' || c == ' ') {
2938 newLineStart = b + 1;
2939 if (countPixels) {
2940 colNum = 0;
2941 width = 0;
2942 for (i=b+1; i<p+1; i++) {
2943 width += measurePropChar(textD,
2944 BufGetCharacter(buf, i), colNum, i);
2945 colNum++;
2947 } else
2948 colNum = BufCountDispChars(buf, b+1, p+1);
2949 foundBreak = True;
2950 break;
2953 if (!foundBreak) { /* no whitespace, just break at margin */
2954 newLineStart = max(p, lineStart+1);
2955 colNum = BufCharWidth(c, colNum, tabDist, nullSubsChar);
2956 if (countPixels)
2957 width = measurePropChar(textD, c, colNum, p);
2959 if (p >= maxPos) {
2960 *retPos = maxPos;
2961 *retLines = maxPos < newLineStart ? nLines : nLines + 1;
2962 *retLineStart = maxPos < newLineStart ? lineStart :
2963 newLineStart;
2964 *retLineEnd = maxPos;
2965 return;
2967 nLines++;
2968 if (nLines >= maxLines) {
2969 *retPos = foundBreak ? b + 1 : max(p, lineStart+1);
2970 *retLines = nLines;
2971 *retLineStart = lineStart;
2972 *retLineEnd = foundBreak ? b : p;
2973 return;
2975 lineStart = newLineStart;
2979 /* reached end of buffer before reaching pos or line target */
2980 *retPos = buf->length;
2981 *retLines = nLines;
2982 *retLineStart = lineStart;
2983 *retLineEnd = buf->length;
2987 ** Measure the width in pixels of a character "c" at a particular column
2988 ** "colNum" and buffer position "pos". This is for measuring characters in
2989 ** proportional or mixed-width highlighting fonts.
2991 ** A note about proportional and mixed-width fonts: the mixed width and
2992 ** proportional font code in nedit does not get much use in general editing,
2993 ** because nedit doesn't allow per-language-mode fonts, and editing programs
2994 ** in a proportional font is usually a bad idea, so very few users would
2995 ** choose a proportional font as a default. There are still probably mixed-
2996 ** width syntax highlighting cases where things don't redraw properly for
2997 ** insertion/deletion, though static display and wrapping and resizing
2998 ** should now be solid because they are now used for online help display.
3000 static int measurePropChar(textDisp *textD, char c, int colNum, int pos)
3002 int charLen, style;
3003 char expChar[MAX_EXP_CHAR_LEN];
3004 textBuffer *styleBuf = textD->styleBuffer;
3006 charLen = BufExpandCharacter(c, colNum, expChar,
3007 textD->buffer->tabDist, textD->buffer->nullSubsChar);
3008 if (styleBuf == NULL) {
3009 style = 0;
3010 } else {
3011 style = (unsigned char)BufGetCharacter(styleBuf, pos);
3012 if (style == textD->unfinishedStyle) {
3013 /* encountered "unfinished" style, trigger parsing */
3014 (textD->unfinishedHighlightCB)(textD, pos, textD->highlightCBArg);
3015 style = (unsigned char)BufGetCharacter(styleBuf, pos);
3018 return stringWidth(textD, expChar, charLen, style);
3022 ** Finds both the end of the current line and the start of the next line. Why?
3023 ** In continuous wrap mode, if you need to know both, figuring out one from the
3024 ** other can be expensive or error prone. The problem comes when there's a
3025 ** trailing space or tab just before the end of the buffer. To translate an
3026 ** end of line value to or from the next lines start value, you need to know
3027 ** whether the trailing space or tab is being used as a line break or just a
3028 ** normal character, and to find that out would otherwise require counting all
3029 ** the way back to the beginning of the line.
3031 static void findLineEnd(textDisp *textD, int startPos, int startPosIsLineStart,
3032 int *lineEnd, int *nextLineStart)
3034 int retLines, retLineStart;
3036 /* if we're not wrapping use more efficient BufEndOfLine */
3037 if (!textD->continuousWrap) {
3038 *lineEnd = BufEndOfLine(textD->buffer, startPos);
3039 *nextLineStart = min(textD->buffer->length, *lineEnd + 1);
3040 return;
3043 /* use the wrapped line counter routine to count forward one line */
3044 wrappedLineCounter(textD, textD->buffer, startPos, textD->buffer->length,
3045 1, startPosIsLineStart, nextLineStart, &retLines,
3046 &retLineStart, lineEnd);
3047 return;
3051 ** Line breaks in continuous wrap mode usually happen at newlines or
3052 ** whitespace. This line-terminating character is not included in line
3053 ** width measurements and has a special status as a non-visible character.
3054 ** However, lines with no whitespace are wrapped without the benefit of a
3055 ** line terminating character, and this distinction causes endless trouble
3056 ** with all of the text display code which was originally written without
3057 ** continuous wrap mode and always expects to wrap at a newline character.
3059 ** Given the position of the end of the line, as returned by TextDEndOfLine
3060 ** or BufEndOfLine, this returns true if there is a line terminating
3061 ** character, and false if there's not. On the last character in the
3062 ** buffer, this function can't tell for certain whether a trailing space was
3063 ** used as a wrap point, and just guesses that it wasn't. So if an exact
3064 ** accounting is necessary, don't use this function.
3066 static int wrapUsesCharacter(textDisp *textD, int lineEndPos)
3068 char c;
3070 if (!textD->continuousWrap || lineEndPos == textD->buffer->length)
3071 return True;
3073 c = BufGetCharacter(textD->buffer, lineEndPos);
3074 return c == '\n' || ((c == '\t' || c == ' ') &&
3075 lineEndPos + 1 != textD->buffer->length);
3079 ** Decide whether the user needs (or may need) a horizontal scroll bar,
3080 ** and manage or unmanage the scroll bar widget accordingly. The H.
3081 ** scroll bar is only hidden in continuous wrap mode when it's absolutely
3082 ** certain that the user will not need it: when wrapping is set
3083 ** to the window edge, or when the wrap margin is strictly less than
3084 ** the longest possible line.
3086 static void hideOrShowHScrollBar(textDisp *textD)
3088 if (textD->continuousWrap && (textD->wrapMargin == 0 || textD->wrapMargin *
3089 textD->fontStruct->max_bounds.width < textD->width))
3090 XtUnmanageChild(textD->hScrollBar);
3091 else
3092 XtManageChild(textD->hScrollBar);
3096 ** Return true if the selection "sel" is rectangular, and touches a
3097 ** buffer position withing "rangeStart" to "rangeEnd"
3099 static int rangeTouchesRectSel(selection *sel, int rangeStart, int rangeEnd)
3101 return sel->selected && sel->rectangular && sel->end >= rangeStart &&
3102 sel->start <= rangeEnd;
3106 ** Extend the range of a redraw request (from *start to *end) with additional
3107 ** redraw requests resulting from changes to the attached style buffer (which
3108 ** contains auxiliary information for coloring or styling text).
3110 static void extendRangeForStyleMods(textDisp *textD, int *start, int *end)
3112 selection *sel = &textD->styleBuffer->primary;
3113 int extended = False;
3115 /* The peculiar protocol used here is that modifications to the style
3116 buffer are marked by selecting them with the buffer's primary selection.
3117 The style buffer is usually modified in response to a modify callback on
3118 the text buffer BEFORE textDisp.c's modify callback, so that it can keep
3119 the style buffer in step with the text buffer. The style-update
3120 callback can't just call for a redraw, because textDisp hasn't processed
3121 the original text changes yet. Anyhow, to minimize redrawing and to
3122 avoid the complexity of scheduling redraws later, this simple protocol
3123 tells the text display's buffer modify callback to extend it's redraw
3124 range to show the text color/and font changes as well. */
3125 if (sel->selected) {
3126 if (sel->start < *start) {
3127 *start = sel->start;
3128 extended = True;
3130 if (sel->end > *end) {
3131 *end = sel->end;
3132 extended = True;
3136 /* If the selection was extended due to a style change, and some of the
3137 fonts don't match in spacing, extend redraw area to end of line to
3138 redraw characters exposed by possible font size changes */
3139 if (textD->fixedFontWidth == -1 && extended)
3140 *end = BufEndOfLine(textD->buffer, *end) + 1;