1 static const char CVSID
[] = "$Id: textDisp.c,v 1.71 2008/01/04 22:31:48 yooden Exp $";
2 /*******************************************************************************
4 * textDisp.c - Display text from a text buffer *
6 * Copyright (C) 1999 Mark Edel *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. In addition, you may distribute version of this program linked to *
12 * Motif or Open Motif. See README for details. *
14 * This software is distributed in the hope that it will be useful, but WITHOUT *
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
19 * You should have received a copy of the GNU General Public License along with *
20 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
21 * Place, Suite 330, Boston, MA 02111-1307 USA *
23 * Nirvana Text Editor *
26 * Written by Mark Edel *
28 *******************************************************************************/
31 #include "../config.h"
40 #include "highlight.h"
48 #include "../util/VMSparam.h"
51 #include <sys/param.h>
56 #include <Xm/ScrolledW.h>
57 #include <Xm/ScrollBar.h>
59 #include <X11/Shell.h>
65 /* Masks for text drawing methods. These are or'd together to form an
66 integer which describes what drawing calls to use to draw a string */
68 #define SECONDARY_SHIFT 9
69 #define PRIMARY_SHIFT 10
70 #define HIGHLIGHT_SHIFT 11
71 #define STYLE_LOOKUP_SHIFT 0
72 #define BACKLIGHT_SHIFT 12
74 #define FILL_MASK (1 << FILL_SHIFT)
75 #define SECONDARY_MASK (1 << SECONDARY_SHIFT)
76 #define PRIMARY_MASK (1 << PRIMARY_SHIFT)
77 #define HIGHLIGHT_MASK (1 << HIGHLIGHT_SHIFT)
78 #define STYLE_LOOKUP_MASK (0xff << STYLE_LOOKUP_SHIFT)
79 #define BACKLIGHT_MASK (0xff << BACKLIGHT_SHIFT)
81 #define RANGESET_SHIFT (20)
82 #define RANGESET_MASK (0x3F << RANGESET_SHIFT)
84 /* If you use both 32-Bit Style mask layout:
85 Bits +----------------+----------------+----------------+----------------+
86 hex |1F1E1D1C1B1A1918|1716151413121110| F E D C B A 9 8| 7 6 5 4 3 2 1 0|
87 dec |3130292827262524|2322212019181716|151413121110 9 8| 7 6 5 4 3 2 1 0|
88 +----------------+----------------+----------------+----------------+
89 Type | r r| r r r r b b b b| b b b b H 1 2 F| s s s s s s s s|
90 +----------------+----------------+----------------+----------------+
91 where: s - style lookup value (8 bits)
93 2 - secondary selection (1 bit)
94 1 - primary selection (1 bit)
96 b - backlighting index (8 bits)
97 r - rangeset index (6 bits)
98 This leaves 6 "unused" bits */
100 /* Maximum displayable line length (how many characters will fit across the
101 widest window). This amount of memory is temporarily allocated from the
102 stack in the redisplayLine routine for drawing strings */
103 #define MAX_DISP_LINE_LEN 1000
105 /* Macro for getting the TextPart from a textD */
106 #define TEXT_OF_TEXTD(t) (((TextWidget)((t)->w))->text)
108 enum positionTypes
{CURSOR_POS
, CHARACTER_POS
};
110 static void updateLineStarts(textDisp
*textD
, int pos
, int charsInserted
,
111 int charsDeleted
, int linesInserted
, int linesDeleted
, int *scrolled
);
112 static void offsetLineStarts(textDisp
*textD
, int newTopLineNum
);
113 static void calcLineStarts(textDisp
*textD
, int startLine
, int endLine
);
114 static void calcLastChar(textDisp
*textD
);
115 static int posToVisibleLineNum(textDisp
*textD
, int pos
, int *lineNum
);
116 static void redisplayLine(textDisp
*textD
, int visLineNum
, int leftClip
,
117 int rightClip
, int leftCharIndex
, int rightCharIndex
);
118 static void drawString(textDisp
*textD
, int style
, int x
, int y
, int toX
,
119 char *string
, int nChars
);
120 static void clearRect(textDisp
*textD
, GC gc
, int x
, int y
,
121 int width
, int height
);
122 static void drawCursor(textDisp
*textD
, int x
, int y
);
123 static int styleOfPos(textDisp
*textD
, int lineStartPos
,
124 int lineLen
, int lineIndex
, int dispIndex
, int thisChar
);
125 static int stringWidth(const textDisp
* textD
, const char* string
,
126 const int length
, const int style
);
127 static int inSelection(selection
*sel
, int pos
, int lineStartPos
,
129 static int xyToPos(textDisp
*textD
, int x
, int y
, int posType
);
130 static void xyToUnconstrainedPos(textDisp
*textD
, int x
, int y
, int *row
,
131 int *column
, int posType
);
132 static void bufPreDeleteCB(int pos
, int nDeleted
, void *cbArg
);
133 static void bufModifiedCB(int pos
, int nInserted
, int nDeleted
,
134 int nRestyled
, const char *deletedText
, void *cbArg
);
135 static void setScroll(textDisp
*textD
, int topLineNum
, int horizOffset
,
136 int updateVScrollBar
, int updateHScrollBar
);
137 static void hScrollCB(Widget w
, XtPointer clientData
, XtPointer callData
);
138 static void vScrollCB(Widget w
, XtPointer clientData
, XtPointer callData
);
139 static void visibilityEH(Widget w
, XtPointer data
, XEvent
*event
,
140 Boolean
*continueDispatch
);
141 static void redrawLineNumbers(textDisp
*textD
, int clearAll
);
142 static void updateVScrollBarRange(textDisp
*textD
);
143 static int updateHScrollBarRange(textDisp
*textD
);
144 static int max(int i1
, int i2
);
145 static int min(int i1
, int i2
);
146 static int countLines(const char *string
);
147 static int measureVisLine(textDisp
*textD
, int visLineNum
);
148 static int emptyLinesVisible(textDisp
*textD
);
149 static void blankCursorProtrusions(textDisp
*textD
);
150 static void allocateFixedFontGCs(textDisp
*textD
, XFontStruct
*fontStruct
,
151 Pixel bgPixel
, Pixel fgPixel
, Pixel selectFGPixel
, Pixel selectBGPixel
,
152 Pixel highlightFGPixel
, Pixel highlightBGPixel
, Pixel lineNumFGPixel
);
153 static GC
allocateGC(Widget w
, unsigned long valueMask
,
154 unsigned long foreground
, unsigned long background
, Font font
,
155 unsigned long dynamicMask
, unsigned long dontCareMask
);
156 static void releaseGC(Widget w
, GC gc
);
157 static void resetClipRectangles(textDisp
*textD
);
158 static int visLineLength(textDisp
*textD
, int visLineNum
);
159 static void measureDeletedLines(textDisp
*textD
, int pos
, int nDeleted
);
160 static void findWrapRange(textDisp
*textD
, const char *deletedText
, int pos
,
161 int nInserted
, int nDeleted
, int *modRangeStart
, int *modRangeEnd
,
162 int *linesInserted
, int *linesDeleted
);
163 static void wrappedLineCounter(const textDisp
* textD
, const textBuffer
* buf
,
164 const int startPos
, const int maxPos
, const int maxLines
,
165 const Boolean startPosIsLineStart
, const int styleBufOffset
,
166 int* retPos
, int* retLines
, int* retLineStart
, int* retLineEnd
);
167 static void findLineEnd(textDisp
*textD
, int startPos
, int startPosIsLineStart
,
168 int *lineEnd
, int *nextLineStart
);
169 static int wrapUsesCharacter(textDisp
*textD
, int lineEndPos
);
170 static void hideOrShowHScrollBar(textDisp
*textD
);
171 static int rangeTouchesRectSel(selection
*sel
, int rangeStart
, int rangeEnd
);
172 static void extendRangeForStyleMods(textDisp
*textD
, int *start
, int *end
);
173 static int getAbsTopLineNum(textDisp
*textD
);
174 static void offsetAbsLineNum(textDisp
*textD
, int oldFirstChar
);
175 static int maintainingAbsTopLineNum(textDisp
*textD
);
176 static void resetAbsLineNum(textDisp
*textD
);
177 static int measurePropChar(const textDisp
* textD
, const char c
,
178 const int colNum
, const int pos
);
179 static Pixel
allocBGColor(Widget w
, char *colorName
, int *ok
);
180 static Pixel
getRangesetColor(textDisp
*textD
, int ind
, Pixel bground
);
181 static void textDRedisplayRange(textDisp
*textD
, int start
, int end
);
183 textDisp
*TextDCreate(Widget widget
, Widget hScrollBar
, Widget vScrollBar
,
184 Position left
, Position top
, Position width
, Position height
,
185 Position lineNumLeft
, Position lineNumWidth
, textBuffer
*buffer
,
186 XFontStruct
*fontStruct
, Pixel bgPixel
, Pixel fgPixel
,
187 Pixel selectFGPixel
, Pixel selectBGPixel
, Pixel highlightFGPixel
,
188 Pixel highlightBGPixel
, Pixel cursorFGPixel
, Pixel lineNumFGPixel
,
189 int continuousWrap
, int wrapMargin
, XmString bgClassString
,
190 Pixel calltipFGPixel
, Pixel calltipBGPixel
)
196 textD
= (textDisp
*)XtMalloc(sizeof(textDisp
));
200 textD
->width
= width
;
201 textD
->height
= height
;
202 textD
->cursorOn
= True
;
203 textD
->cursorPos
= 0;
204 textD
->cursorX
= -100;
205 textD
->cursorY
= -100;
206 textD
->cursorToHint
= NO_HINT
;
207 textD
->cursorStyle
= NORMAL_CURSOR
;
208 textD
->cursorPreferredCol
= -1;
209 textD
->buffer
= buffer
;
210 textD
->firstChar
= 0;
212 textD
->nBufferLines
= 0;
213 textD
->topLineNum
= 1;
214 textD
->absTopLineNum
= 1;
215 textD
->needAbsTopLineNum
= False
;
216 textD
->horizOffset
= 0;
217 textD
->visibility
= VisibilityUnobscured
;
218 textD
->hScrollBar
= hScrollBar
;
219 textD
->vScrollBar
= vScrollBar
;
220 textD
->fontStruct
= fontStruct
;
221 textD
->ascent
= fontStruct
->ascent
;
222 textD
->descent
= fontStruct
->descent
;
223 textD
->fixedFontWidth
= fontStruct
->min_bounds
.width
==
224 fontStruct
->max_bounds
.width
? fontStruct
->min_bounds
.width
: -1;
225 textD
->styleBuffer
= NULL
;
226 textD
->styleTable
= NULL
;
228 textD
->bgPixel
= bgPixel
;
229 textD
->fgPixel
= fgPixel
;
230 textD
->selectFGPixel
= selectFGPixel
;
231 textD
->highlightFGPixel
= highlightFGPixel
;
232 textD
->selectBGPixel
= selectBGPixel
;
233 textD
->highlightBGPixel
= highlightBGPixel
;
234 textD
->lineNumFGPixel
= lineNumFGPixel
;
235 textD
->cursorFGPixel
= cursorFGPixel
;
236 textD
->wrapMargin
= wrapMargin
;
237 textD
->continuousWrap
= continuousWrap
;
238 allocateFixedFontGCs(textD
, fontStruct
, bgPixel
, fgPixel
, selectFGPixel
,
239 selectBGPixel
, highlightFGPixel
, highlightBGPixel
, lineNumFGPixel
);
240 textD
->styleGC
= allocateGC(textD
->w
, 0, 0, 0, fontStruct
->fid
,
241 GCClipMask
|GCForeground
|GCBackground
, GCArcMode
);
242 textD
->lineNumLeft
= lineNumLeft
;
243 textD
->lineNumWidth
= lineNumWidth
;
244 textD
->nVisibleLines
= (height
- 1) / (textD
->ascent
+ textD
->descent
) + 1;
245 gcValues
.foreground
= cursorFGPixel
;
246 textD
->cursorFGGC
= XtGetGC(widget
, GCForeground
, &gcValues
);
247 textD
->lineStarts
= (int *)XtMalloc(sizeof(int) * textD
->nVisibleLines
);
248 textD
->lineStarts
[0] = 0;
249 textD
->calltipW
= NULL
;
250 textD
->calltipShell
= NULL
;
251 textD
->calltip
.ID
= 0;
252 textD
->calltipFGPixel
= calltipFGPixel
;
253 textD
->calltipBGPixel
= calltipBGPixel
;
254 for (i
=1; i
<textD
->nVisibleLines
; i
++)
255 textD
->lineStarts
[i
] = -1;
256 textD
->bgClassPixel
= NULL
;
257 textD
->bgClass
= NULL
;
258 TextDSetupBGClasses(widget
, bgClassString
, &textD
->bgClassPixel
,
259 &textD
->bgClass
, bgPixel
);
260 textD
->suppressResync
= 0;
261 textD
->nLinesDeleted
= 0;
262 textD
->modifyingTabDist
= 0;
263 textD
->pointerHidden
= False
;
264 textD
->graphicsExposeQueue
= NULL
;
266 /* Attach an event handler to the widget so we can know the visibility
267 (used for choosing the fastest drawing method) */
268 XtAddEventHandler(widget
, VisibilityChangeMask
, False
,
269 visibilityEH
, textD
);
271 /* Attach the callback to the text buffer for receiving modification
273 if (buffer
!= NULL
) {
274 BufAddModifyCB(buffer
, bufModifiedCB
, textD
);
275 BufAddPreDeleteCB(buffer
, bufPreDeleteCB
, textD
);
278 /* Initialize the scroll bars and attach movement callbacks */
279 if (vScrollBar
!= NULL
) {
280 XtVaSetValues(vScrollBar
, XmNminimum
, 1, XmNmaximum
, 2,
281 XmNsliderSize
, 1, XmNrepeatDelay
, 10, XmNvalue
, 1, NULL
);
282 XtAddCallback(vScrollBar
, XmNdragCallback
, vScrollCB
, (XtPointer
)textD
);
283 XtAddCallback(vScrollBar
, XmNvalueChangedCallback
, vScrollCB
,
286 if (hScrollBar
!= NULL
) {
287 XtVaSetValues(hScrollBar
, XmNminimum
, 0, XmNmaximum
, 1,
288 XmNsliderSize
, 1, XmNrepeatDelay
, 10, XmNvalue
, 0,
289 XmNincrement
, fontStruct
->max_bounds
.width
, NULL
);
290 XtAddCallback(hScrollBar
, XmNdragCallback
, hScrollCB
, (XtPointer
)textD
);
291 XtAddCallback(hScrollBar
, XmNvalueChangedCallback
, hScrollCB
,
295 /* Update the display to reflect the contents of the buffer */
297 bufModifiedCB(0, buffer
->length
, 0, 0, NULL
, textD
);
299 /* Decide if the horizontal scroll bar needs to be visible */
300 hideOrShowHScrollBar(textD
);
306 ** Free a text display and release its associated memory. Note, the text
307 ** BUFFER that the text display displays is a separate entity and is not
308 ** freed, nor are the style buffer or style table.
310 void TextDFree(textDisp
*textD
)
312 BufRemoveModifyCB(textD
->buffer
, bufModifiedCB
, textD
);
313 BufRemovePreDeleteCB(textD
->buffer
, bufPreDeleteCB
, textD
);
314 releaseGC(textD
->w
, textD
->gc
);
315 releaseGC(textD
->w
, textD
->selectGC
);
316 releaseGC(textD
->w
, textD
->highlightGC
);
317 releaseGC(textD
->w
, textD
->selectBGGC
);
318 releaseGC(textD
->w
, textD
->highlightBGGC
);
319 releaseGC(textD
->w
, textD
->styleGC
);
320 releaseGC(textD
->w
, textD
->lineNumGC
);
321 XtFree((char *)textD
->lineStarts
);
322 while (TextDPopGraphicExposeQueueEntry(textD
)) {
324 XtFree((char *)textD
->bgClassPixel
);
325 XtFree((char *)textD
->bgClass
);
326 XtFree((char *)textD
);
330 ** Attach a text buffer to display, replacing the current buffer (if any)
332 void TextDSetBuffer(textDisp
*textD
, textBuffer
*buffer
)
334 /* If the text display is already displaying a buffer, clear it off
335 of the display and remove our callback from it */
336 if (textD
->buffer
!= NULL
) {
337 bufModifiedCB(0, 0, textD
->buffer
->length
, 0, NULL
, textD
);
338 BufRemoveModifyCB(textD
->buffer
, bufModifiedCB
, textD
);
339 BufRemovePreDeleteCB(textD
->buffer
, bufPreDeleteCB
, textD
);
342 /* Add the buffer to the display, and attach a callback to the buffer for
343 receiving modification information when the buffer contents change */
344 textD
->buffer
= buffer
;
345 BufAddModifyCB(buffer
, bufModifiedCB
, textD
);
346 BufAddPreDeleteCB(buffer
, bufPreDeleteCB
, textD
);
348 /* Update the display */
349 bufModifiedCB(0, buffer
->length
, 0, 0, NULL
, textD
);
353 ** Attach (or remove) highlight information in text display and redisplay.
354 ** Highlighting information consists of a style buffer which parallels the
355 ** normal text buffer, but codes font and color information for the display;
356 ** a style table which translates style buffer codes (indexed by buffer
357 ** character - 65 (ASCII code for 'A')) into fonts and colors; and a callback
358 ** mechanism for as-needed highlighting, triggered by a style buffer entry of
359 ** "unfinishedStyle". Style buffer can trigger additional redisplay during
360 ** a normal buffer modification if the buffer contains a primary selection
361 ** (see extendRangeForStyleMods for more information on this protocol).
363 ** Style buffers, tables and their associated memory are managed by the caller.
365 void TextDAttachHighlightData(textDisp
*textD
, textBuffer
*styleBuffer
,
366 styleTableEntry
*styleTable
, int nStyles
, char unfinishedStyle
,
367 unfinishedStyleCBProc unfinishedHighlightCB
, void *cbArg
)
369 textD
->styleBuffer
= styleBuffer
;
370 textD
->styleTable
= styleTable
;
371 textD
->nStyles
= nStyles
;
372 textD
->unfinishedStyle
= unfinishedStyle
;
373 textD
->unfinishedHighlightCB
= unfinishedHighlightCB
;
374 textD
->highlightCBArg
= cbArg
;
376 /* Call TextDSetFont to combine font information from style table and
377 primary font, adjust font-related parameters, and then redisplay */
378 TextDSetFont(textD
, textD
->fontStruct
);
382 /* Change the (non syntax-highlit) colors */
383 void TextDSetColors(textDisp
*textD
, Pixel textFgP
, Pixel textBgP
,
384 Pixel selectFgP
, Pixel selectBgP
, Pixel hiliteFgP
, Pixel hiliteBgP
,
385 Pixel lineNoFgP
, Pixel cursorFgP
)
388 Display
*d
= XtDisplay(textD
->w
);
390 /* Update the stored pixels */
391 textD
->fgPixel
= textFgP
;
392 textD
->bgPixel
= textBgP
;
393 textD
->selectFGPixel
= selectFgP
;
394 textD
->selectBGPixel
= selectBgP
;
395 textD
->highlightFGPixel
= hiliteFgP
;
396 textD
->highlightBGPixel
= hiliteBgP
;
397 textD
->lineNumFGPixel
= lineNoFgP
;
398 textD
->cursorFGPixel
= cursorFgP
;
400 releaseGC(textD
->w
, textD
->gc
);
401 releaseGC(textD
->w
, textD
->selectGC
);
402 releaseGC(textD
->w
, textD
->selectBGGC
);
403 releaseGC(textD
->w
, textD
->highlightGC
);
404 releaseGC(textD
->w
, textD
->highlightBGGC
);
405 releaseGC(textD
->w
, textD
->lineNumGC
);
406 allocateFixedFontGCs(textD
, textD
->fontStruct
, textBgP
, textFgP
, selectFgP
,
407 selectBgP
, hiliteFgP
, hiliteBgP
, lineNoFgP
);
409 /* Change the cursor GC (the cursor GC is not shared). */
410 values
.foreground
= cursorFgP
;
411 XChangeGC( d
, textD
->cursorFGGC
, GCForeground
, &values
);
414 TextDRedisplayRect(textD
, textD
->left
, textD
->top
, textD
->width
,
416 redrawLineNumbers(textD
, True
);
420 ** Change the (non highlight) font
422 void TextDSetFont(textDisp
*textD
, XFontStruct
*fontStruct
)
424 Display
*display
= XtDisplay(textD
->w
);
425 int i
, maxAscent
= fontStruct
->ascent
, maxDescent
= fontStruct
->descent
;
426 int width
, height
, fontWidth
;
427 Pixel bgPixel
, fgPixel
, selectFGPixel
, selectBGPixel
;
428 Pixel highlightFGPixel
, highlightBGPixel
, lineNumFGPixel
;
430 XFontStruct
*styleFont
;
432 /* If font size changes, cursor will be redrawn in a new position */
433 blankCursorProtrusions(textD
);
435 /* If there is a (syntax highlighting) style table in use, find the new
436 maximum font height for this text display */
437 for (i
=0; i
<textD
->nStyles
; i
++) {
438 styleFont
= textD
->styleTable
[i
].font
;
439 if (styleFont
!= NULL
&& styleFont
->ascent
> maxAscent
)
440 maxAscent
= styleFont
->ascent
;
441 if (styleFont
!= NULL
&& styleFont
->descent
> maxDescent
)
442 maxDescent
= styleFont
->descent
;
444 textD
->ascent
= maxAscent
;
445 textD
->descent
= maxDescent
;
447 /* If all of the current fonts are fixed and match in width, compute */
448 fontWidth
= fontStruct
->max_bounds
.width
;
449 if (fontWidth
!= fontStruct
->min_bounds
.width
)
452 for (i
=0; i
<textD
->nStyles
; i
++) {
453 styleFont
= textD
->styleTable
[i
].font
;
454 if (styleFont
!= NULL
&&
455 (styleFont
->max_bounds
.width
!= fontWidth
||
456 styleFont
->max_bounds
.width
!= styleFont
->min_bounds
.width
))
460 textD
->fixedFontWidth
= fontWidth
;
462 /* Don't let the height dip below one line, or bad things can happen */
463 if (textD
->height
< maxAscent
+ maxDescent
)
464 textD
->height
= maxAscent
+ maxDescent
;
466 /* Change the font. In most cases, this means re-allocating the
467 affected GCs (they are shared with other widgets, and if the primary
468 font changes, must be re-allocated to change it). Unfortunately,
469 this requres recovering all of the colors from the existing GCs */
470 textD
->fontStruct
= fontStruct
;
471 XGetGCValues(display
, textD
->gc
, GCForeground
|GCBackground
, &values
);
472 fgPixel
= values
.foreground
;
473 bgPixel
= values
.background
;
474 XGetGCValues(display
, textD
->selectGC
, GCForeground
|GCBackground
, &values
);
475 selectFGPixel
= values
.foreground
;
476 selectBGPixel
= values
.background
;
477 XGetGCValues(display
, textD
->highlightGC
,GCForeground
|GCBackground
,&values
);
478 highlightFGPixel
= values
.foreground
;
479 highlightBGPixel
= values
.background
;
480 XGetGCValues(display
, textD
->lineNumGC
, GCForeground
, &values
);
481 lineNumFGPixel
= values
.foreground
;
482 releaseGC(textD
->w
, textD
->gc
);
483 releaseGC(textD
->w
, textD
->selectGC
);
484 releaseGC(textD
->w
, textD
->highlightGC
);
485 releaseGC(textD
->w
, textD
->selectBGGC
);
486 releaseGC(textD
->w
, textD
->highlightBGGC
);
487 releaseGC(textD
->w
, textD
->lineNumGC
);
488 allocateFixedFontGCs(textD
, fontStruct
, bgPixel
, fgPixel
, selectFGPixel
,
489 selectBGPixel
, highlightFGPixel
, highlightBGPixel
, lineNumFGPixel
);
490 XSetFont(display
, textD
->styleGC
, fontStruct
->fid
);
492 /* Do a full resize to force recalculation of font related parameters */
493 width
= textD
->width
;
494 height
= textD
->height
;
495 textD
->width
= textD
->height
= 0;
496 TextDResize(textD
, width
, height
);
498 /* if the shell window doesn't get resized, and the new fonts are
499 of smaller sizes, sometime we get some residual text on the
500 blank space at the bottom part of text area. Clear it here. */
501 clearRect(textD
, textD
->gc
, textD
->left
,
502 textD
->top
+ textD
->height
- maxAscent
- maxDescent
,
503 textD
->width
, maxAscent
+ maxDescent
);
506 TextDRedisplayRect(textD
, textD
->left
, textD
->top
, textD
->width
,
509 /* Clean up line number area in case spacing has changed */
510 redrawLineNumbers(textD
, True
);
513 int TextDMinFontWidth(textDisp
*textD
, Boolean considerStyles
)
515 int fontWidth
= textD
->fontStruct
->max_bounds
.width
;
518 if (considerStyles
) {
519 for (i
= 0; i
< textD
->nStyles
; ++i
) {
520 int thisWidth
= (textD
->styleTable
[i
].font
)->min_bounds
.width
;
521 if (thisWidth
< fontWidth
) {
522 fontWidth
= thisWidth
;
529 int TextDMaxFontWidth(textDisp
*textD
, Boolean considerStyles
)
531 int fontWidth
= textD
->fontStruct
->max_bounds
.width
;
534 if (considerStyles
) {
535 for (i
= 0; i
< textD
->nStyles
; ++i
) {
536 int thisWidth
= (textD
->styleTable
[i
].font
)->max_bounds
.width
;
537 if (thisWidth
> fontWidth
) {
538 fontWidth
= thisWidth
;
546 ** Change the size of the displayed text area
548 void TextDResize(textDisp
*textD
, int width
, int height
)
550 int oldVisibleLines
= textD
->nVisibleLines
;
551 int canRedraw
= XtWindow(textD
->w
) != 0;
552 int newVisibleLines
= height
/ (textD
->ascent
+ textD
->descent
);
553 int redrawAll
= False
;
554 int oldWidth
= textD
->width
;
555 int exactHeight
= height
- height
% (textD
->ascent
+ textD
->descent
);
557 textD
->width
= width
;
558 textD
->height
= height
;
560 /* In continuous wrap mode, a change in width affects the total number of
561 lines in the buffer, and can leave the top line number incorrect, and
562 the top character no longer pointing at a valid line start */
563 if (textD
->continuousWrap
&& textD
->wrapMargin
==0 && width
!=oldWidth
) {
564 int oldFirstChar
= textD
->firstChar
;
565 textD
->nBufferLines
= TextDCountLines(textD
, 0, textD
->buffer
->length
,
567 textD
->firstChar
= TextDStartOfLine(textD
, textD
->firstChar
);
568 textD
->topLineNum
= TextDCountLines(textD
, 0, textD
->firstChar
, True
)+1;
570 offsetAbsLineNum(textD
, oldFirstChar
);
573 /* reallocate and update the line starts array, which may have changed
574 size and/or contents. (contents can change in continuous wrap mode
575 when the width changes, even without a change in height) */
576 if (oldVisibleLines
< newVisibleLines
) {
577 XtFree((char *)textD
->lineStarts
);
578 textD
->lineStarts
= (int *)XtMalloc(sizeof(int) * newVisibleLines
);
580 textD
->nVisibleLines
= newVisibleLines
;
581 calcLineStarts(textD
, 0, newVisibleLines
);
584 /* if the window became shorter, there may be partially drawn
585 text left at the bottom edge, which must be cleaned up */
586 if (canRedraw
&& oldVisibleLines
>newVisibleLines
&& exactHeight
!=height
)
587 XClearArea(XtDisplay(textD
->w
), XtWindow(textD
->w
), textD
->left
,
588 textD
->top
+ exactHeight
, textD
->width
,
589 height
- exactHeight
, False
);
591 /* if the window became taller, there may be an opportunity to display
592 more text by scrolling down */
593 if (canRedraw
&& oldVisibleLines
< newVisibleLines
&& textD
->topLineNum
+
594 textD
->nVisibleLines
> textD
->nBufferLines
)
595 setScroll(textD
, max(1, textD
->nBufferLines
- textD
->nVisibleLines
+
596 2 + TEXT_OF_TEXTD(textD
).cursorVPadding
),
597 textD
->horizOffset
, False
, False
);
599 /* Update the scroll bar page increment size (as well as other scroll
600 bar parameters. If updating the horizontal range caused scrolling,
602 updateVScrollBarRange(textD
);
603 if (updateHScrollBarRange(textD
))
606 /* If a full redraw is needed */
607 if (redrawAll
&& canRedraw
)
608 TextDRedisplayRect(textD
, textD
->left
, textD
->top
, textD
->width
,
611 /* Decide if the horizontal scroll bar needs to be visible */
612 hideOrShowHScrollBar(textD
);
614 /* Refresh the line number display to draw more line numbers, or
616 redrawLineNumbers(textD
, True
);
618 /* Redraw the calltip */
619 TextDRedrawCalltip(textD
, 0);
623 ** Refresh a rectangle of the text display. left and top are in coordinates of
624 ** the text drawing window
626 void TextDRedisplayRect(textDisp
*textD
, int left
, int top
, int width
,
629 int fontHeight
, firstLine
, lastLine
, line
;
631 /* find the line number range of the display */
632 fontHeight
= textD
->ascent
+ textD
->descent
;
633 firstLine
= (top
- textD
->top
- fontHeight
+ 1) / fontHeight
;
634 lastLine
= (top
+ height
- textD
->top
) / fontHeight
;
636 /* If the graphics contexts are shared using XtAllocateGC, their
637 clipping rectangles may have changed since the last use */
638 resetClipRectangles(textD
);
640 /* draw the lines of text */
641 for (line
=firstLine
; line
<=lastLine
; line
++)
642 redisplayLine(textD
, line
, left
, left
+width
, 0, INT_MAX
);
644 /* draw the line numbers if exposed area includes them */
645 if (textD
->lineNumWidth
!= 0 && left
<= textD
->lineNumLeft
+ textD
->lineNumWidth
)
646 redrawLineNumbers(textD
, False
);
650 ** Refresh all of the text between buffer positions "start" and "end"
651 ** not including the character at the position "end".
652 ** If end points beyond the end of the buffer, refresh the whole display
653 ** after pos, including blank lines which are not technically part of
654 ** any range of characters.
656 static void textDRedisplayRange(textDisp
*textD
, int start
, int end
)
658 int i
, startLine
, lastLine
, startIndex
, endIndex
;
660 /* If the range is outside of the displayed text, just return */
661 if (end
< textD
->firstChar
|| (start
> textD
->lastChar
&&
662 !emptyLinesVisible(textD
)))
665 /* Clean up the starting and ending values */
666 if (start
< 0) start
= 0;
667 if (start
> textD
->buffer
->length
) start
= textD
->buffer
->length
;
668 if (end
< 0) end
= 0;
669 if (end
> textD
->buffer
->length
) end
= textD
->buffer
->length
;
671 /* Get the starting and ending lines */
672 if (start
< textD
->firstChar
) {
673 start
= textD
->firstChar
;
676 if (!posToVisibleLineNum(textD
, start
, &startLine
)) {
677 startLine
= textD
->nVisibleLines
- 1;
680 if (end
>= textD
->lastChar
) {
681 lastLine
= textD
->nVisibleLines
- 1;
683 if (!posToVisibleLineNum(textD
, end
, &lastLine
)) {
684 /* shouldn't happen */
685 lastLine
= textD
->nVisibleLines
- 1;
689 /* Get the starting and ending positions within the lines */
690 startIndex
= (textD
->lineStarts
[startLine
] == -1)
692 : start
- textD
->lineStarts
[startLine
];
693 if (end
>= textD
->lastChar
)
695 /* Request to redisplay beyond textD->lastChar, so tell
696 redisplayLine() to display everything to infy. */
698 } else if (textD
->lineStarts
[lastLine
] == -1)
700 /* Here, lastLine is determined by posToVisibleLineNum() (see
701 if/else above) but deemed to be out of display according to
702 textD->lineStarts. */
706 endIndex
= end
- textD
->lineStarts
[lastLine
];
709 /* Reset the clipping rectangles for the drawing GCs which are shared
710 using XtAllocateGC, and may have changed since the last use */
711 resetClipRectangles(textD
);
713 /* If the starting and ending lines are the same, redisplay the single
714 line between "start" and "end" */
715 if (startLine
== lastLine
) {
716 redisplayLine(textD
, startLine
, 0, INT_MAX
, startIndex
, endIndex
);
720 /* Redisplay the first line from "start" */
721 redisplayLine(textD
, startLine
, 0, INT_MAX
, startIndex
, INT_MAX
);
723 /* Redisplay the lines in between at their full width */
724 for (i
=startLine
+1; i
<lastLine
; i
++)
725 redisplayLine(textD
, i
, 0, INT_MAX
, 0, INT_MAX
);
727 /* Redisplay the last line to "end" */
728 redisplayLine(textD
, lastLine
, 0, INT_MAX
, 0, endIndex
);
732 ** Set the scroll position of the text display vertically by line number and
733 ** horizontally by pixel offset from the left margin
735 void TextDSetScroll(textDisp
*textD
, int topLineNum
, int horizOffset
)
737 int sliderSize
, sliderMax
;
738 int vPadding
= (int)(TEXT_OF_TEXTD(textD
).cursorVPadding
);
740 /* Limit the requested scroll position to allowable values */
743 else if ((topLineNum
> textD
->topLineNum
) &&
744 (topLineNum
> (textD
->nBufferLines
+ 2 - textD
->nVisibleLines
+
746 topLineNum
= max(textD
->topLineNum
,
747 textD
->nBufferLines
+ 2 - textD
->nVisibleLines
+ vPadding
);
748 XtVaGetValues(textD
->hScrollBar
, XmNmaximum
, &sliderMax
,
749 XmNsliderSize
, &sliderSize
, NULL
);
752 if (horizOffset
> sliderMax
- sliderSize
)
753 horizOffset
= sliderMax
- sliderSize
;
755 setScroll(textD
, topLineNum
, horizOffset
, True
, True
);
759 ** Get the current scroll position for the text display, in terms of line
760 ** number of the top line and horizontal pixel offset from the left margin
762 void TextDGetScroll(textDisp
*textD
, int *topLineNum
, int *horizOffset
)
764 *topLineNum
= textD
->topLineNum
;
765 *horizOffset
= textD
->horizOffset
;
769 ** Set the position of the text insertion cursor for text display "textD"
771 void TextDSetInsertPosition(textDisp
*textD
, int newPos
)
773 /* make sure new position is ok, do nothing if it hasn't changed */
774 if (newPos
== textD
->cursorPos
)
776 if (newPos
< 0) newPos
= 0;
777 if (newPos
> textD
->buffer
->length
) newPos
= textD
->buffer
->length
;
779 /* cursor movement cancels vertical cursor motion column */
780 textD
->cursorPreferredCol
= -1;
782 /* erase the cursor at it's previous position */
783 TextDBlankCursor(textD
);
785 /* draw it at its new position */
786 textD
->cursorPos
= newPos
;
787 textD
->cursorOn
= True
;
788 textDRedisplayRange(textD
, textD
->cursorPos
-1, textD
->cursorPos
+ 1);
791 void TextDBlankCursor(textDisp
*textD
)
793 if (!textD
->cursorOn
)
796 blankCursorProtrusions(textD
);
797 textD
->cursorOn
= False
;
798 textDRedisplayRange(textD
, textD
->cursorPos
-1, textD
->cursorPos
+1);
801 void TextDUnblankCursor(textDisp
*textD
)
803 if (!textD
->cursorOn
) {
804 textD
->cursorOn
= True
;
805 textDRedisplayRange(textD
, textD
->cursorPos
-1, textD
->cursorPos
+1);
809 void TextDSetCursorStyle(textDisp
*textD
, int style
)
811 textD
->cursorStyle
= style
;
812 blankCursorProtrusions(textD
);
813 if (textD
->cursorOn
) {
814 textDRedisplayRange(textD
, textD
->cursorPos
-1, textD
->cursorPos
+ 1);
818 void TextDSetWrapMode(textDisp
*textD
, int wrap
, int wrapMargin
)
820 textD
->wrapMargin
= wrapMargin
;
821 textD
->continuousWrap
= wrap
;
823 /* wrapping can change change the total number of lines, re-count */
824 textD
->nBufferLines
= TextDCountLines(textD
, 0, textD
->buffer
->length
,True
);
826 /* changing wrap margins wrap or changing from wrapped mode to non-wrapped
827 can leave the character at the top no longer at a line start, and/or
828 change the line number */
829 textD
->firstChar
= TextDStartOfLine(textD
, textD
->firstChar
);
830 textD
->topLineNum
= TextDCountLines(textD
, 0, textD
->firstChar
, True
) + 1;
831 resetAbsLineNum(textD
);
833 /* update the line starts array */
834 calcLineStarts(textD
, 0, textD
->nVisibleLines
);
837 /* Update the scroll bar page increment size (as well as other scroll
839 updateVScrollBarRange(textD
);
840 updateHScrollBarRange(textD
);
842 /* Decide if the horizontal scroll bar needs to be visible */
843 hideOrShowHScrollBar(textD
);
845 /* Do a full redraw */
846 TextDRedisplayRect(textD
, 0, textD
->top
, textD
->width
+ textD
->left
,
850 int TextDGetInsertPosition(textDisp
*textD
)
852 return textD
->cursorPos
;
856 ** Insert "text" at the current cursor location. This has the same
857 ** effect as inserting the text into the buffer using BufInsert and
858 ** then moving the insert position after the newly inserted text, except
859 ** that it's optimized to do less redrawing.
861 void TextDInsert(textDisp
*textD
, char *text
)
863 int pos
= textD
->cursorPos
;
865 textD
->cursorToHint
= pos
+ strlen(text
);
866 BufInsert(textD
->buffer
, pos
, text
);
867 textD
->cursorToHint
= NO_HINT
;
871 ** Insert "text" (which must not contain newlines), overstriking the current
874 void TextDOverstrike(textDisp
*textD
, char *text
)
876 int startPos
= textD
->cursorPos
;
877 textBuffer
*buf
= textD
->buffer
;
878 int lineStart
= BufStartOfLine(buf
, startPos
);
879 int textLen
= strlen(text
);
880 int i
, p
, endPos
, indent
, startIndent
, endIndent
;
881 char *c
, ch
, *paddedText
= NULL
;
883 /* determine how many displayed character positions are covered */
884 startIndent
= BufCountDispChars(textD
->buffer
, lineStart
, startPos
);
885 indent
= startIndent
;
886 for (c
=text
; *c
!='\0'; c
++)
887 indent
+= BufCharWidth(*c
, indent
, buf
->tabDist
, buf
->nullSubsChar
);
890 /* find which characters to remove, and if necessary generate additional
891 padding to make up for removed control characters at the end */
893 for (p
=startPos
; ; p
++) {
894 if (p
== buf
->length
)
896 ch
= BufGetCharacter(buf
, p
);
899 indent
+= BufCharWidth(ch
, indent
, buf
->tabDist
, buf
->nullSubsChar
);
900 if (indent
== endIndent
) {
903 } else if (indent
> endIndent
) {
906 paddedText
= XtMalloc(textLen
+ MAX_EXP_CHAR_LEN
+ 1);
907 strcpy(paddedText
, text
);
908 for (i
=0; i
<indent
-endIndent
; i
++)
909 paddedText
[textLen
+i
] = ' ';
910 paddedText
[textLen
+i
] = '\0';
917 textD
->cursorToHint
= startPos
+ textLen
;
918 BufReplace(buf
, startPos
, endPos
, paddedText
== NULL
? text
: paddedText
);
919 textD
->cursorToHint
= NO_HINT
;
924 ** Translate window coordinates to the nearest text cursor position.
926 int TextDXYToPosition(textDisp
*textD
, int x
, int y
)
928 return xyToPos(textD
, x
, y
, CURSOR_POS
);
932 ** Translate window coordinates to the nearest character cell.
934 int TextDXYToCharPos(textDisp
*textD
, int x
, int y
)
936 return xyToPos(textD
, x
, y
, CHARACTER_POS
);
940 ** Translate window coordinates to the nearest row and column number for
941 ** positioning the cursor. This, of course, makes no sense when the font
942 ** is proportional, since there are no absolute columns.
944 void TextDXYToUnconstrainedPosition(textDisp
*textD
, int x
, int y
, int *row
,
947 xyToUnconstrainedPos(textD
, x
, y
, row
, column
, CURSOR_POS
);
951 ** Translate line and column to the nearest row and column number for
952 ** positioning the cursor. This, of course, makes no sense when the font
953 ** is proportional, since there are no absolute columns.
955 int TextDLineAndColToPos(textDisp
*textD
, int lineNum
, int column
)
957 int i
, lineEnd
, charIndex
, outIndex
;
958 int lineStart
=0, charLen
=0;
959 char *lineStr
, expandedChar
[MAX_EXP_CHAR_LEN
];
965 for (i
=1; i
<=lineNum
&& lineEnd
<textD
->buffer
->length
; i
++) {
966 lineStart
= lineEnd
+ 1;
967 lineEnd
= BufEndOfLine(textD
->buffer
, lineStart
);
970 /* If line is beyond end of buffer, position at last character in buffer */
971 if ( lineNum
>= i
) {
975 /* Start character index at zero */
978 /* Only have to count columns if column isn't zero (or negative) */
980 /* Count columns, expanding each character */
981 lineStr
= BufGetRange(textD
->buffer
, lineStart
, lineEnd
);
983 for(i
=lineStart
; i
<lineEnd
; i
++, charIndex
++) {
984 charLen
= BufExpandCharacter(lineStr
[charIndex
], outIndex
,
985 expandedChar
, textD
->buffer
->tabDist
,
986 textD
->buffer
->nullSubsChar
);
987 if ( outIndex
+charLen
>= column
) break;
991 /* If the column is in the middle of an expanded character, put cursor
992 * in front of character if in first half of character, and behind
993 * character if in last half of character
995 if (column
>= outIndex
+ ( charLen
/ 2 ))
998 /* If we are beyond the end of the line, back up one space */
999 if ((i
>=lineEnd
)&&(charIndex
>0)) charIndex
--;
1002 /* Position is the start of the line plus the index into line buffer */
1003 return lineStart
+ charIndex
;
1007 ** Translate a buffer text position to the XY location where the center
1008 ** of the cursor would be positioned to point to that character. Returns
1009 ** False if the position is not displayed because it is VERTICALLY out
1010 ** of view. If the position is horizontally out of view, returns the
1011 ** x coordinate where the position would be if it were visible.
1013 int TextDPositionToXY(textDisp
*textD
, int pos
, int *x
, int *y
)
1015 int charIndex
, lineStartPos
, fontHeight
, lineLen
;
1016 int visLineNum
, charLen
, outIndex
, xStep
, charStyle
;
1017 char *lineStr
, expandedChar
[MAX_EXP_CHAR_LEN
];
1019 /* If position is not displayed, return false */
1020 if (pos
< textD
->firstChar
||
1021 (pos
> textD
->lastChar
&& !emptyLinesVisible(textD
)))
1024 /* Calculate y coordinate */
1025 if (!posToVisibleLineNum(textD
, pos
, &visLineNum
))
1027 fontHeight
= textD
->ascent
+ textD
->descent
;
1028 *y
= textD
->top
+ visLineNum
*fontHeight
+ fontHeight
/2;
1030 /* Get the text, length, and buffer position of the line. If the position
1031 is beyond the end of the buffer and should be at the first position on
1032 the first empty line, don't try to get or scan the text */
1033 lineStartPos
= textD
->lineStarts
[visLineNum
];
1034 if (lineStartPos
== -1) {
1035 *x
= textD
->left
- textD
->horizOffset
;
1038 lineLen
= visLineLength(textD
, visLineNum
);
1039 lineStr
= BufGetRange(textD
->buffer
, lineStartPos
, lineStartPos
+ lineLen
);
1041 /* Step through character positions from the beginning of the line
1042 to "pos" to calculate the x coordinate */
1043 xStep
= textD
->left
- textD
->horizOffset
;
1045 for(charIndex
=0; charIndex
<pos
-lineStartPos
; charIndex
++) {
1046 charLen
= BufExpandCharacter(lineStr
[charIndex
], outIndex
, expandedChar
,
1047 textD
->buffer
->tabDist
, textD
->buffer
->nullSubsChar
);
1048 charStyle
= styleOfPos(textD
, lineStartPos
, lineLen
, charIndex
,
1049 outIndex
, lineStr
[charIndex
]);
1050 xStep
+= stringWidth(textD
, expandedChar
, charLen
, charStyle
);
1051 outIndex
+= charLen
;
1059 ** If the text widget is maintaining a line number count appropriate to "pos"
1060 ** return the line and column numbers of pos, otherwise return False. If
1061 ** continuous wrap mode is on, returns the absolute line number (as opposed to
1062 ** the wrapped line number which is used for scrolling). THIS ROUTINE ONLY
1063 ** WORKS FOR DISPLAYED LINES AND, IN CONTINUOUS WRAP MODE, ONLY WHEN THE
1064 ** ABSOLUTE LINE NUMBER IS BEING MAINTAINED. Otherwise, it returns False.
1066 int TextDPosToLineAndCol(textDisp
*textD
, int pos
, int *lineNum
, int *column
)
1068 textBuffer
*buf
= textD
->buffer
;
1070 /* In continuous wrap mode, the absolute (non-wrapped) line count is
1071 maintained separately, as needed. Only return it if we're actually
1072 keeping track of it and pos is in the displayed text */
1073 if (textD
->continuousWrap
) {
1074 if (!maintainingAbsTopLineNum(textD
) || pos
< textD
->firstChar
||
1075 pos
> textD
->lastChar
)
1077 *lineNum
= textD
->absTopLineNum
+ BufCountLines(buf
,
1078 textD
->firstChar
, pos
);
1079 *column
= BufCountDispChars(buf
, BufStartOfLine(buf
, pos
), pos
);
1083 /* Only return the data if pos is within the displayed text */
1084 if (!posToVisibleLineNum(textD
, pos
, lineNum
))
1086 *column
= BufCountDispChars(buf
, textD
->lineStarts
[*lineNum
], pos
);
1087 *lineNum
+= textD
->topLineNum
;
1092 ** Return True if position (x, y) is inside of the primary selection
1094 int TextDInSelection(textDisp
*textD
, int x
, int y
)
1096 int row
, column
, pos
= xyToPos(textD
, x
, y
, CHARACTER_POS
);
1097 textBuffer
*buf
= textD
->buffer
;
1099 xyToUnconstrainedPos(textD
, x
, y
, &row
, &column
, CHARACTER_POS
);
1100 if (rangeTouchesRectSel(&buf
->primary
, textD
->firstChar
, textD
->lastChar
))
1101 column
= TextDOffsetWrappedColumn(textD
, row
, column
);
1102 return inSelection(&buf
->primary
, pos
, BufStartOfLine(buf
, pos
), column
);
1106 ** Correct a column number based on an unconstrained position (as returned by
1107 ** TextDXYToUnconstrainedPosition) to be relative to the last actual newline
1108 ** in the buffer before the row and column position given, rather than the
1109 ** last line start created by line wrapping. This is an adapter
1110 ** for rectangular selections and code written before continuous wrap mode,
1111 ** which thinks that the unconstrained column is the number of characters
1112 ** from the last newline. Obviously this is time consuming, because it
1113 ** invloves character re-counting.
1115 int TextDOffsetWrappedColumn(textDisp
*textD
, int row
, int column
)
1117 int lineStart
, dispLineStart
;
1119 if (!textD
->continuousWrap
|| row
< 0 || row
> textD
->nVisibleLines
)
1121 dispLineStart
= textD
->lineStarts
[row
];
1122 if (dispLineStart
== -1)
1124 lineStart
= BufStartOfLine(textD
->buffer
, dispLineStart
);
1125 return column
+ BufCountDispChars(textD
->buffer
, lineStart
, dispLineStart
);
1129 ** Correct a row number from an unconstrained position (as returned by
1130 ** TextDXYToUnconstrainedPosition) to a straight number of newlines from the
1131 ** top line of the display. Because rectangular selections are based on
1132 ** newlines, rather than display wrapping, and anywhere a rectangular selection
1133 ** needs a row, it needs it in terms of un-wrapped lines.
1135 int TextDOffsetWrappedRow(textDisp
*textD
, int row
)
1137 if (!textD
->continuousWrap
|| row
< 0 || row
> textD
->nVisibleLines
)
1139 return BufCountLines(textD
->buffer
, textD
->firstChar
,
1140 textD
->lineStarts
[row
]);
1144 ** Scroll the display to bring insertion cursor into view.
1146 ** Note: it would be nice to be able to do this without counting lines twice
1147 ** (setScroll counts them too) and/or to count from the most efficient
1148 ** starting point, but the efficiency of this routine is not as important to
1149 ** the overall performance of the text display.
1151 void TextDMakeInsertPosVisible(textDisp
*textD
)
1153 int hOffset
, topLine
, x
, y
;
1154 int cursorPos
= textD
->cursorPos
;
1155 int linesFromTop
= 0, do_padding
= 1;
1156 int cursorVPadding
= (int)TEXT_OF_TEXTD(textD
).cursorVPadding
;
1158 hOffset
= textD
->horizOffset
;
1159 topLine
= textD
->topLineNum
;
1161 /* Don't do padding if this is a mouse operation */
1162 do_padding
= ((TEXT_OF_TEXTD(textD
).dragState
== NOT_CLICKED
) &&
1163 (cursorVPadding
> 0));
1165 /* Find the new top line number */
1166 if (cursorPos
< textD
->firstChar
) {
1167 topLine
-= TextDCountLines(textD
, cursorPos
, textD
->firstChar
, False
);
1168 /* linesFromTop = 0; */
1169 } else if (cursorPos
> textD
->lastChar
&& !emptyLinesVisible(textD
)) {
1170 topLine
+= TextDCountLines(textD
, textD
->lastChar
-
1171 (wrapUsesCharacter(textD
, textD
->lastChar
) ? 0 : 1),
1173 linesFromTop
= textD
->nVisibleLines
-1;
1174 } else if (cursorPos
== textD
->lastChar
&& !emptyLinesVisible(textD
) &&
1175 !wrapUsesCharacter(textD
, textD
->lastChar
)) {
1177 linesFromTop
= textD
->nVisibleLines
-1;
1179 /* Avoid extra counting if cursorVPadding is disabled */
1181 linesFromTop
= TextDCountLines(textD
, textD
->firstChar
,
1185 fprintf(stderr
, "nedit: internal consistency check tl1 failed\n");
1190 /* Keep the cursor away from the top or bottom of screen. */
1191 if (textD
->nVisibleLines
<= 2*(int)cursorVPadding
) {
1192 topLine
+= (linesFromTop
- textD
->nVisibleLines
/2);
1193 topLine
= max(topLine
, 1);
1194 } else if (linesFromTop
< (int)cursorVPadding
) {
1195 topLine
-= (cursorVPadding
- linesFromTop
);
1196 topLine
= max(topLine
, 1);
1197 } else if (linesFromTop
> textD
->nVisibleLines
-(int)cursorVPadding
-1) {
1198 topLine
+= (linesFromTop
- (textD
->nVisibleLines
-cursorVPadding
-1));
1202 /* Find the new setting for horizontal offset (this is a bit ungraceful).
1203 If the line is visible, just use TextDPositionToXY to get the position
1204 to scroll to, otherwise, do the vertical scrolling first, then the
1206 if (!TextDPositionToXY(textD
, cursorPos
, &x
, &y
)) {
1207 setScroll(textD
, topLine
, hOffset
, True
, True
);
1208 if (!TextDPositionToXY(textD
, cursorPos
, &x
, &y
))
1209 return; /* Give up, it's not worth it (but why does it fail?) */
1211 if (x
> textD
->left
+ textD
->width
)
1212 hOffset
+= x
- (textD
->left
+ textD
->width
);
1213 else if (x
< textD
->left
)
1214 hOffset
+= x
- textD
->left
;
1217 setScroll(textD
, topLine
, hOffset
, True
, True
);
1221 ** Return the current preferred column along with the current
1222 ** visible line index (-1 if not visible) and the lineStartPos
1223 ** of the current insert position.
1225 int TextDPreferredColumn(textDisp
*textD
, int *visLineNum
, int *lineStartPos
)
1229 /* Find the position of the start of the line. Use the line starts array
1230 if possible, to avoid unbounded line-counting in continuous wrap mode */
1231 if (posToVisibleLineNum(textD
, textD
->cursorPos
, visLineNum
)) {
1232 *lineStartPos
= textD
->lineStarts
[*visLineNum
];
1235 *lineStartPos
= TextDStartOfLine(textD
, textD
->cursorPos
);
1239 /* Decide what column to move to, if there's a preferred column use that */
1240 column
= (textD
->cursorPreferredCol
>= 0)
1241 ? textD
->cursorPreferredCol
1242 : BufCountDispChars(textD
->buffer
, *lineStartPos
, textD
->cursorPos
);
1247 ** Return the insert position of the requested column given
1248 ** the lineStartPos.
1250 int TextDPosOfPreferredCol(textDisp
*textD
, int column
, int lineStartPos
)
1254 newPos
= BufCountForwardDispChars(textD
->buffer
, lineStartPos
, column
);
1255 if (textD
->continuousWrap
) {
1256 newPos
= min(newPos
, TextDEndOfLine(textD
, lineStartPos
, True
));
1262 ** Cursor movement functions
1264 int TextDMoveRight(textDisp
*textD
)
1266 if (textD
->cursorPos
>= textD
->buffer
->length
)
1268 TextDSetInsertPosition(textD
, textD
->cursorPos
+ 1);
1272 int TextDMoveLeft(textDisp
*textD
)
1274 if (textD
->cursorPos
<= 0)
1276 TextDSetInsertPosition(textD
, textD
->cursorPos
- 1);
1280 int TextDMoveUp(textDisp
*textD
, int absolute
)
1282 int lineStartPos
, column
, prevLineStartPos
, newPos
, visLineNum
;
1284 /* Find the position of the start of the line. Use the line starts array
1285 if possible, to avoid unbounded line-counting in continuous wrap mode */
1287 lineStartPos
= BufStartOfLine(textD
->buffer
, textD
->cursorPos
);
1289 } else if (posToVisibleLineNum(textD
, textD
->cursorPos
, &visLineNum
))
1290 lineStartPos
= textD
->lineStarts
[visLineNum
];
1292 lineStartPos
= TextDStartOfLine(textD
, textD
->cursorPos
);
1295 if (lineStartPos
== 0)
1298 /* Decide what column to move to, if there's a preferred column use that */
1299 column
= textD
->cursorPreferredCol
>= 0
1300 ? textD
->cursorPreferredCol
1301 : BufCountDispChars(textD
->buffer
, lineStartPos
, textD
->cursorPos
);
1303 /* count forward from the start of the previous line to reach the column */
1305 prevLineStartPos
= BufCountBackwardNLines(textD
->buffer
, lineStartPos
, 1);
1306 } else if (visLineNum
!= -1 && visLineNum
!= 0) {
1307 prevLineStartPos
= textD
->lineStarts
[visLineNum
-1];
1309 prevLineStartPos
= TextDCountBackwardNLines(textD
, lineStartPos
, 1);
1312 newPos
= BufCountForwardDispChars(textD
->buffer
, prevLineStartPos
, column
);
1313 if (textD
->continuousWrap
&& !absolute
)
1314 newPos
= min(newPos
, TextDEndOfLine(textD
, prevLineStartPos
, True
));
1316 /* move the cursor */
1317 TextDSetInsertPosition(textD
, newPos
);
1319 /* if a preferred column wasn't aleady established, establish it */
1320 textD
->cursorPreferredCol
= column
;
1325 int TextDMoveDown(textDisp
*textD
, int absolute
)
1327 int lineStartPos
, column
, nextLineStartPos
, newPos
, visLineNum
;
1329 if (textD
->cursorPos
== textD
->buffer
->length
) {
1334 lineStartPos
= BufStartOfLine(textD
->buffer
, textD
->cursorPos
);
1336 } else if (posToVisibleLineNum(textD
, textD
->cursorPos
, &visLineNum
)) {
1337 lineStartPos
= textD
->lineStarts
[visLineNum
];
1339 lineStartPos
= TextDStartOfLine(textD
, textD
->cursorPos
);
1343 column
= textD
->cursorPreferredCol
>= 0
1344 ? textD
->cursorPreferredCol
1345 : BufCountDispChars(textD
->buffer
, lineStartPos
, textD
->cursorPos
);
1348 nextLineStartPos
= BufCountForwardNLines(textD
->buffer
, lineStartPos
, 1);
1350 nextLineStartPos
= TextDCountForwardNLines(textD
, lineStartPos
, 1, True
);
1352 newPos
= BufCountForwardDispChars(textD
->buffer
, nextLineStartPos
, column
);
1354 if (textD
->continuousWrap
&& !absolute
) {
1355 newPos
= min(newPos
, TextDEndOfLine(textD
, nextLineStartPos
, True
));
1358 TextDSetInsertPosition(textD
, newPos
);
1359 textD
->cursorPreferredCol
= column
;
1365 ** Same as BufCountLines, but takes in to account wrapping if wrapping is
1366 ** turned on. If the caller knows that startPos is at a line start, it
1367 ** can pass "startPosIsLineStart" as True to make the call more efficient
1368 ** by avoiding the additional step of scanning back to the last newline.
1370 int TextDCountLines(textDisp
*textD
, int startPos
, int endPos
,
1371 int startPosIsLineStart
)
1373 int retLines
, retPos
, retLineStart
, retLineEnd
;
1375 /* If we're not wrapping use simple (and more efficient) BufCountLines */
1376 if (!textD
->continuousWrap
)
1377 return BufCountLines(textD
->buffer
, startPos
, endPos
);
1379 wrappedLineCounter(textD
, textD
->buffer
, startPos
, endPos
, INT_MAX
,
1380 startPosIsLineStart
, 0, &retPos
, &retLines
, &retLineStart
,
1386 ** Same as BufCountForwardNLines, but takes in to account line breaks when
1387 ** wrapping is turned on. If the caller knows that startPos is at a line start,
1388 ** it can pass "startPosIsLineStart" as True to make the call more efficient
1389 ** by avoiding the additional step of scanning back to the last newline.
1391 int TextDCountForwardNLines(const textDisp
* textD
, const int startPos
,
1392 const unsigned nLines
, const Boolean startPosIsLineStart
)
1394 int retLines
, retPos
, retLineStart
, retLineEnd
;
1396 /* if we're not wrapping use more efficient BufCountForwardNLines */
1397 if (!textD
->continuousWrap
)
1398 return BufCountForwardNLines(textD
->buffer
, startPos
, nLines
);
1400 /* wrappedLineCounter can't handle the 0 lines case */
1404 /* use the common line counting routine to count forward */
1405 wrappedLineCounter(textD
, textD
->buffer
, startPos
, textD
->buffer
->length
,
1406 nLines
, startPosIsLineStart
, 0, &retPos
, &retLines
, &retLineStart
,
1412 ** Same as BufEndOfLine, but takes in to account line breaks when wrapping
1413 ** is turned on. If the caller knows that startPos is at a line start, it
1414 ** can pass "startPosIsLineStart" as True to make the call more efficient
1415 ** by avoiding the additional step of scanning back to the last newline.
1417 ** Note that the definition of the end of a line is less clear when continuous
1418 ** wrap is on. With continuous wrap off, it's just a pointer to the newline
1419 ** that ends the line. When it's on, it's the character beyond the last
1420 ** DISPLAYABLE character on the line, where a whitespace character which has
1421 ** been "converted" to a newline for wrapping is not considered displayable.
1422 ** Also note that, a line can be wrapped at a non-whitespace character if the
1423 ** line had no whitespace. In this case, this routine returns a pointer to
1424 ** the start of the next line. This is also consistent with the model used by
1427 int TextDEndOfLine(const textDisp
* textD
, const int pos
,
1428 const Boolean startPosIsLineStart
)
1430 int retLines
, retPos
, retLineStart
, retLineEnd
;
1432 /* If we're not wrapping use more efficient BufEndOfLine */
1433 if (!textD
->continuousWrap
)
1434 return BufEndOfLine(textD
->buffer
, pos
);
1436 if (pos
== textD
->buffer
->length
)
1438 wrappedLineCounter(textD
, textD
->buffer
, pos
, textD
->buffer
->length
, 1,
1439 startPosIsLineStart
, 0, &retPos
, &retLines
, &retLineStart
,
1445 ** Same as BufStartOfLine, but returns the character after last wrap point
1446 ** rather than the last newline.
1448 int TextDStartOfLine(const textDisp
* textD
, const int pos
)
1450 int retLines
, retPos
, retLineStart
, retLineEnd
;
1452 /* If we're not wrapping, use the more efficient BufStartOfLine */
1453 if (!textD
->continuousWrap
)
1454 return BufStartOfLine(textD
->buffer
, pos
);
1456 wrappedLineCounter(textD
, textD
->buffer
, BufStartOfLine(textD
->buffer
, pos
),
1457 pos
, INT_MAX
, True
, 0, &retPos
, &retLines
, &retLineStart
,
1459 return retLineStart
;
1463 ** Same as BufCountBackwardNLines, but takes in to account line breaks when
1464 ** wrapping is turned on.
1466 int TextDCountBackwardNLines(textDisp
*textD
, int startPos
, int nLines
)
1468 textBuffer
*buf
= textD
->buffer
;
1469 int pos
, lineStart
, retLines
, retPos
, retLineStart
, retLineEnd
;
1471 /* If we're not wrapping, use the more efficient BufCountBackwardNLines */
1472 if (!textD
->continuousWrap
)
1473 return BufCountBackwardNLines(textD
->buffer
, startPos
, nLines
);
1477 lineStart
= BufStartOfLine(buf
, pos
);
1478 wrappedLineCounter(textD
, textD
->buffer
, lineStart
, pos
, INT_MAX
,
1479 True
, 0, &retPos
, &retLines
, &retLineStart
, &retLineEnd
);
1480 if (retLines
> nLines
)
1481 return TextDCountForwardNLines(textD
, lineStart
, retLines
-nLines
,
1484 pos
= lineStart
- 1;
1492 ** Callback attached to the text buffer to receive delete information before
1493 ** the modifications are actually made.
1495 static void bufPreDeleteCB(int pos
, int nDeleted
, void *cbArg
)
1497 textDisp
*textD
= (textDisp
*)cbArg
;
1498 if (textD
->continuousWrap
&&
1499 (textD
->fixedFontWidth
== -1 || textD
->modifyingTabDist
))
1500 /* Note: we must perform this measurement, even if there is not a
1501 single character deleted; the number of "deleted" lines is the
1502 number of visual lines spanned by the real line in which the
1503 modification takes place.
1504 Also, a modification of the tab distance requires the same
1505 kind of calculations in advance, even if the font width is "fixed",
1506 because when the width of the tab characters changes, the layout
1507 of the text may be completely different. */
1508 measureDeletedLines(textD
, pos
, nDeleted
);
1510 textD
->suppressResync
= 0; /* Probably not needed, but just in case */
1514 ** Callback attached to the text buffer to receive modification information
1516 static void bufModifiedCB(int pos
, int nInserted
, int nDeleted
,
1517 int nRestyled
, const char *deletedText
, void *cbArg
)
1519 int linesInserted
, linesDeleted
, startDispPos
, endDispPos
;
1520 textDisp
*textD
= (textDisp
*)cbArg
;
1521 textBuffer
*buf
= textD
->buffer
;
1522 int oldFirstChar
= textD
->firstChar
;
1523 int scrolled
, origCursorPos
= textD
->cursorPos
;
1524 int wrapModStart
, wrapModEnd
;
1526 /* buffer modification cancels vertical cursor motion column */
1527 if (nInserted
!= 0 || nDeleted
!= 0)
1528 textD
->cursorPreferredCol
= -1;
1530 /* Count the number of lines inserted and deleted, and in the case
1531 of continuous wrap mode, how much has changed */
1532 if (textD
->continuousWrap
) {
1533 findWrapRange(textD
, deletedText
, pos
, nInserted
, nDeleted
,
1534 &wrapModStart
, &wrapModEnd
, &linesInserted
, &linesDeleted
);
1536 linesInserted
= nInserted
== 0 ? 0 :
1537 BufCountLines(buf
, pos
, pos
+ nInserted
);
1538 linesDeleted
= nDeleted
== 0 ? 0 : countLines(deletedText
);
1541 /* Update the line starts and topLineNum */
1542 if (nInserted
!= 0 || nDeleted
!= 0) {
1543 if (textD
->continuousWrap
) {
1544 updateLineStarts(textD
, wrapModStart
, wrapModEnd
-wrapModStart
,
1545 nDeleted
+ pos
-wrapModStart
+ (wrapModEnd
-(pos
+nInserted
)),
1546 linesInserted
, linesDeleted
, &scrolled
);
1548 updateLineStarts(textD
, pos
, nInserted
, nDeleted
, linesInserted
,
1549 linesDeleted
, &scrolled
);
1554 /* If we're counting non-wrapped lines as well, maintain the absolute
1555 (non-wrapped) line number of the text displayed */
1556 if (maintainingAbsTopLineNum(textD
) && (nInserted
!= 0 || nDeleted
!= 0)) {
1557 if (pos
+ nDeleted
< oldFirstChar
)
1558 textD
->absTopLineNum
+= BufCountLines(buf
, pos
, pos
+ nInserted
) -
1559 countLines(deletedText
);
1560 else if (pos
< oldFirstChar
)
1561 resetAbsLineNum(textD
);
1564 /* Update the line count for the whole buffer */
1565 textD
->nBufferLines
+= linesInserted
- linesDeleted
;
1567 /* Update the scroll bar ranges (and value if the value changed). Note
1568 that updating the horizontal scroll bar range requires scanning the
1569 entire displayed text, however, it doesn't seem to hurt performance
1570 much. Note also, that the horizontal scroll bar update routine is
1571 allowed to re-adjust horizOffset if there is blank space to the right
1572 of all lines of text. */
1573 updateVScrollBarRange(textD
);
1574 scrolled
|= updateHScrollBarRange(textD
);
1576 /* Update the cursor position */
1577 if (textD
->cursorToHint
!= NO_HINT
) {
1578 textD
->cursorPos
= textD
->cursorToHint
;
1579 textD
->cursorToHint
= NO_HINT
;
1580 } else if (textD
->cursorPos
> pos
) {
1581 if (textD
->cursorPos
< pos
+ nDeleted
)
1582 textD
->cursorPos
= pos
;
1584 textD
->cursorPos
+= nInserted
- nDeleted
;
1587 /* If the changes caused scrolling, re-paint everything and we're done. */
1589 blankCursorProtrusions(textD
);
1590 TextDRedisplayRect(textD
, 0, textD
->top
, textD
->width
+ textD
->left
,
1592 if (textD
->styleBuffer
) {/* See comments in extendRangeForStyleMods */
1593 textD
->styleBuffer
->primary
.selected
= False
;
1594 textD
->styleBuffer
->primary
.zeroWidth
= False
;
1599 /* If the changes didn't cause scrolling, decide the range of characters
1600 that need to be re-painted. Also if the cursor position moved, be
1601 sure that the redisplay range covers the old cursor position so the
1602 old cursor gets erased, and erase the bits of the cursor which extend
1603 beyond the left and right edges of the text. */
1604 startDispPos
= textD
->continuousWrap
? wrapModStart
: pos
;
1605 if (origCursorPos
== startDispPos
&& textD
->cursorPos
!= startDispPos
)
1606 startDispPos
= min(startDispPos
, origCursorPos
-1);
1607 if (linesInserted
== linesDeleted
) {
1608 if (nInserted
== 0 && nDeleted
== 0)
1609 endDispPos
= pos
+ nRestyled
;
1611 endDispPos
= textD
->continuousWrap
? wrapModEnd
:
1612 BufEndOfLine(buf
, pos
+ nInserted
) + 1;
1613 if (origCursorPos
>= startDispPos
&&
1614 (origCursorPos
<= endDispPos
|| endDispPos
== buf
->length
))
1615 blankCursorProtrusions(textD
);
1617 /* If more than one line is inserted/deleted, a line break may have
1618 been inserted or removed in between, and the line numbers may
1619 have changed. If only one line is altered, line numbers cannot
1620 be affected (the insertion or removal of a line break always
1621 results in at least two lines being redrawn). */
1622 if (linesInserted
> 1) redrawLineNumbers(textD
, False
);
1623 } else { /* linesInserted != linesDeleted */
1624 endDispPos
= textD
->lastChar
+ 1;
1625 if (origCursorPos
>= pos
)
1626 blankCursorProtrusions(textD
);
1627 redrawLineNumbers(textD
, False
);
1630 /* If there is a style buffer, check if the modification caused additional
1631 changes that need to be redisplayed. (Redisplaying separately would
1632 cause double-redraw on almost every modification involving styled
1633 text). Extend the redraw range to incorporate style changes */
1634 if (textD
->styleBuffer
)
1635 extendRangeForStyleMods(textD
, &startDispPos
, &endDispPos
);
1637 /* Redisplay computed range */
1638 textDRedisplayRange(textD
, startDispPos
, endDispPos
);
1642 ** In continuous wrap mode, internal line numbers are calculated after
1643 ** wrapping. A separate non-wrapped line count is maintained when line
1644 ** numbering is turned on. There is some performance cost to maintaining this
1645 ** line count, so normally absolute line numbers are not tracked if line
1646 ** numbering is off. This routine allows callers to specify that they still
1647 ** want this line count maintained (for use via TextDPosToLineAndCol).
1648 ** More specifically, this allows the line number reported in the statistics
1649 ** line to be calibrated in absolute lines, rather than post-wrapped lines.
1651 void TextDMaintainAbsLineNum(textDisp
*textD
, int state
)
1653 textD
->needAbsTopLineNum
= state
;
1654 resetAbsLineNum(textD
);
1658 ** Returns the absolute (non-wrapped) line number of the first line displayed.
1659 ** Returns 0 if the absolute top line number is not being maintained.
1661 static int getAbsTopLineNum(textDisp
*textD
)
1663 if (!textD
->continuousWrap
)
1664 return textD
->topLineNum
;
1665 if (maintainingAbsTopLineNum(textD
))
1666 return textD
->absTopLineNum
;
1671 ** Re-calculate absolute top line number for a change in scroll position.
1673 static void offsetAbsLineNum(textDisp
*textD
, int oldFirstChar
)
1675 if (maintainingAbsTopLineNum(textD
)) {
1676 if (textD
->firstChar
< oldFirstChar
)
1677 textD
->absTopLineNum
-= BufCountLines(textD
->buffer
,
1678 textD
->firstChar
, oldFirstChar
);
1680 textD
->absTopLineNum
+= BufCountLines(textD
->buffer
,
1681 oldFirstChar
, textD
->firstChar
);
1686 ** Return true if a separate absolute top line number is being maintained
1687 ** (for displaying line numbers or showing in the statistics line).
1689 static int maintainingAbsTopLineNum(textDisp
*textD
)
1691 return textD
->continuousWrap
&&
1692 (textD
->lineNumWidth
!= 0 || textD
->needAbsTopLineNum
);
1696 ** Count lines from the beginning of the buffer to reestablish the
1697 ** absolute (non-wrapped) top line number. If mode is not continuous wrap,
1698 ** or the number is not being maintained, does nothing.
1700 static void resetAbsLineNum(textDisp
*textD
)
1702 textD
->absTopLineNum
= 1;
1703 offsetAbsLineNum(textD
, 0);
1707 ** Find the line number of position "pos" relative to the first line of
1708 ** displayed text. Returns False if the line is not displayed.
1710 static int posToVisibleLineNum(textDisp
*textD
, int pos
, int *lineNum
)
1714 if (pos
< textD
->firstChar
)
1716 if (pos
> textD
->lastChar
) {
1717 if (emptyLinesVisible(textD
)) {
1718 if (textD
->lastChar
< textD
->buffer
->length
) {
1719 if (!posToVisibleLineNum(textD
, textD
->lastChar
, lineNum
)) {
1720 fprintf(stderr
, "nedit: Consistency check ptvl failed\n");
1723 return ++(*lineNum
) <= textD
->nVisibleLines
-1;
1725 posToVisibleLineNum(textD
, max(textD
->lastChar
-1, 0), lineNum
);
1732 for (i
=textD
->nVisibleLines
-1; i
>=0; i
--) {
1733 if (textD
->lineStarts
[i
] != -1 && pos
>= textD
->lineStarts
[i
]) {
1743 ** Redisplay the text on a single line represented by "visLineNum" (the
1744 ** number of lines down from the top of the display), limited by
1745 ** "leftClip" and "rightClip" window coordinates and "leftCharIndex" and
1746 ** "rightCharIndex" character positions (not including the character at
1747 ** position "rightCharIndex").
1749 ** The cursor is also drawn if it appears on the line.
1751 static void redisplayLine(textDisp
*textD
, int visLineNum
, int leftClip
,
1752 int rightClip
, int leftCharIndex
, int rightCharIndex
)
1754 textBuffer
*buf
= textD
->buffer
;
1755 int i
, x
, y
, startX
, charIndex
, lineStartPos
, lineLen
, fontHeight
;
1756 int stdCharWidth
, charWidth
, startIndex
, charStyle
, style
;
1757 int charLen
, outStartIndex
, outIndex
, cursorX
= 0, hasCursor
= False
;
1758 int dispIndexOffset
, cursorPos
= textD
->cursorPos
, y_orig
;
1759 char expandedChar
[MAX_EXP_CHAR_LEN
], outStr
[MAX_DISP_LINE_LEN
];
1760 char *lineStr
, *outPtr
;
1763 /* If line is not displayed, skip it */
1764 if (visLineNum
< 0 || visLineNum
>= textD
->nVisibleLines
)
1767 /* Shrink the clipping range to the active display area */
1768 leftClip
= max(textD
->left
, leftClip
);
1769 rightClip
= min(rightClip
, textD
->left
+ textD
->width
);
1771 if (leftClip
> rightClip
) {
1775 /* Calculate y coordinate of the string to draw */
1776 fontHeight
= textD
->ascent
+ textD
->descent
;
1777 y
= textD
->top
+ visLineNum
* fontHeight
;
1779 /* Get the text, length, and buffer position of the line to display */
1780 lineStartPos
= textD
->lineStarts
[visLineNum
];
1781 if (lineStartPos
== -1) {
1785 lineLen
= visLineLength(textD
, visLineNum
);
1786 lineStr
= BufGetRange(buf
, lineStartPos
, lineStartPos
+ lineLen
);
1789 /* Space beyond the end of the line is still counted in units of characters
1790 of a standardized character width (this is done mostly because style
1791 changes based on character position can still occur in this region due
1792 to rectangular selections). stdCharWidth must be non-zero to prevent a
1793 potential infinite loop if x does not advance */
1794 stdCharWidth
= textD
->fontStruct
->max_bounds
.width
;
1795 if (stdCharWidth
<= 0) {
1796 fprintf(stderr
, "nedit: Internal Error, bad font measurement\n");
1801 /* Rectangular selections are based on "real" line starts (after a newline
1802 or start of buffer). Calculate the difference between the last newline
1803 position and the line start we're using. Since scanning back to find a
1804 newline is expensive, only do so if there's actually a rectangular
1805 selection which needs it */
1806 if (textD
->continuousWrap
&& (rangeTouchesRectSel(&buf
->primary
,
1807 lineStartPos
, lineStartPos
+ lineLen
) || rangeTouchesRectSel(
1808 &buf
->secondary
, lineStartPos
, lineStartPos
+ lineLen
) ||
1809 rangeTouchesRectSel(&buf
->highlight
, lineStartPos
,
1810 lineStartPos
+ lineLen
))) {
1811 dispIndexOffset
= BufCountDispChars(buf
,
1812 BufStartOfLine(buf
, lineStartPos
), lineStartPos
);
1814 dispIndexOffset
= 0;
1816 /* Step through character positions from the beginning of the line (even if
1817 that's off the left edge of the displayed area) to find the first
1818 character position that's not clipped, and the x coordinate for drawing
1820 x
= textD
->left
- textD
->horizOffset
;
1823 for (charIndex
= 0; ; charIndex
++) {
1825 charLen
= charIndex
>= lineLen
1827 : BufExpandCharacter(baseChar
= lineStr
[charIndex
], outIndex
,
1828 expandedChar
, buf
->tabDist
, buf
->nullSubsChar
);
1829 style
= styleOfPos(textD
, lineStartPos
, lineLen
, charIndex
,
1830 outIndex
+ dispIndexOffset
, baseChar
);
1831 charWidth
= charIndex
>= lineLen
1833 : stringWidth(textD
, expandedChar
, charLen
, style
);
1835 if (x
+ charWidth
>= leftClip
&& charIndex
>= leftCharIndex
) {
1836 startIndex
= charIndex
;
1837 outStartIndex
= outIndex
;
1842 outIndex
+= charLen
;
1845 /* Scan character positions from the beginning of the clipping range, and
1846 draw parts whenever the style changes (also note if the cursor is on
1847 this line, and where it should be drawn to take advantage of the x
1848 position which we've gone to so much trouble to calculate) */
1850 outIndex
= outStartIndex
;
1852 for (charIndex
= startIndex
; charIndex
< rightCharIndex
; charIndex
++) {
1853 if (lineStartPos
+charIndex
== cursorPos
) {
1854 if (charIndex
< lineLen
1855 || (charIndex
== lineLen
&& cursorPos
>= buf
->length
)) {
1858 } else if (charIndex
== lineLen
) {
1859 if (wrapUsesCharacter(textD
, cursorPos
)) {
1867 charLen
= charIndex
>= lineLen
1869 : BufExpandCharacter(baseChar
= lineStr
[charIndex
], outIndex
,
1870 expandedChar
, buf
->tabDist
, buf
->nullSubsChar
);
1871 charStyle
= styleOfPos(textD
, lineStartPos
, lineLen
, charIndex
,
1872 outIndex
+ dispIndexOffset
, baseChar
);
1873 for (i
= 0; i
< charLen
; i
++) {
1874 if (i
!= 0 && charIndex
< lineLen
&& lineStr
[charIndex
] == '\t') {
1875 charStyle
= styleOfPos(textD
, lineStartPos
, lineLen
, charIndex
,
1876 outIndex
+ dispIndexOffset
, '\t');
1879 if (charStyle
!= style
) {
1880 drawString(textD
, style
, startX
, y
, x
, outStr
, outPtr
- outStr
);
1886 if (charIndex
< lineLen
) {
1887 *outPtr
= expandedChar
[i
];
1888 charWidth
= stringWidth(textD
, &expandedChar
[i
], 1, charStyle
);
1890 charWidth
= stdCharWidth
;
1898 if (outPtr
- outStr
+ MAX_EXP_CHAR_LEN
>= MAX_DISP_LINE_LEN
1899 || x
>= rightClip
) {
1904 /* Draw the remaining style segment */
1905 drawString(textD
, style
, startX
, y
, x
, outStr
, outPtr
- outStr
);
1907 /* Draw the cursor if part of it appeared on the redisplayed part of
1908 this line. Also check for the cases which are not caught as the
1909 line is scanned above: when the cursor appears at the very end
1910 of the redisplayed section. */
1911 y_orig
= textD
->cursorY
;
1912 if (textD
->cursorOn
) {
1914 drawCursor(textD
, cursorX
, y
);
1915 } else if (charIndex
< lineLen
1916 && (lineStartPos
+charIndex
+1 == cursorPos
)
1917 && x
== rightClip
) {
1918 if (cursorPos
>= buf
->length
) {
1919 drawCursor(textD
, x
- 1, y
);
1921 if (wrapUsesCharacter(textD
, cursorPos
)) {
1922 drawCursor(textD
, x
- 1, y
);
1925 } else if ((lineStartPos
+ rightCharIndex
) == cursorPos
) {
1926 drawCursor(textD
, x
- 1, y
);
1930 /* If the y position of the cursor has changed, redraw the calltip */
1931 if (hasCursor
&& (y_orig
!= textD
->cursorY
|| y_orig
!= y
))
1932 TextDRedrawCalltip(textD
, 0);
1938 ** Draw a string or blank area according to parameter "style", using the
1939 ** appropriate colors and drawing method for that style, with top left
1940 ** corner at x, y. If style says to draw text, use "string" as source of
1941 ** characters, and draw "nChars", if style is FILL, erase
1942 ** rectangle where text would have drawn from x to toX and from y to
1943 ** the maximum y extent of the current font(s).
1945 static void drawString(textDisp
*textD
, int style
, int x
, int y
, int toX
,
1946 char *string
, int nChars
)
1950 XFontStruct
*fs
= textD
->fontStruct
;
1951 Pixel bground
= textD
->bgPixel
;
1952 Pixel fground
= textD
->fgPixel
;
1953 int underlineStyle
= FALSE
;
1955 /* Don't draw if widget isn't realized */
1956 if (XtWindow(textD
->w
) == 0)
1960 if (style
& (STYLE_LOOKUP_MASK
| BACKLIGHT_MASK
| RANGESET_MASK
)) {
1961 gc
= bgGC
= textD
->styleGC
;
1963 else if (style
& HIGHLIGHT_MASK
) {
1964 gc
= textD
->highlightGC
;
1965 bgGC
= textD
->highlightBGGC
;
1967 else if (style
& PRIMARY_MASK
) {
1968 gc
= textD
->selectGC
;
1969 bgGC
= textD
->selectBGGC
;
1972 gc
= bgGC
= textD
->gc
;
1975 if (gc
== textD
->styleGC
) {
1976 /* we have work to do */
1977 styleTableEntry
*styleRec
;
1978 /* Set font, color, and gc depending on style. For normal text, GCs
1979 for normal drawing, or drawing within a selection or highlight are
1980 pre-allocated and pre-configured. For syntax highlighting, GCs are
1981 configured here, on the fly. */
1982 if (style
& STYLE_LOOKUP_MASK
) {
1983 styleRec
= &textD
->styleTable
[(style
& STYLE_LOOKUP_MASK
) - ASCII_A
];
1984 underlineStyle
= styleRec
->underline
;
1985 fs
= styleRec
->font
;
1986 gcValues
.font
= fs
->fid
;
1987 fground
= styleRec
->color
;
1988 /* here you could pick up specific select and highlight fground */
1992 gcValues
.font
= fs
->fid
;
1993 fground
= textD
->fgPixel
;
1995 /* Background color priority order is:
1996 1 Primary(Selection), 2 Highlight(Parens),
1997 3 Rangeset, 4 SyntaxHighlightStyle,
1998 5 Backlight (if NOT fill), 6 DefaultBackground */
2000 style
& PRIMARY_MASK
? textD
->selectBGPixel
:
2001 style
& HIGHLIGHT_MASK
? textD
->highlightBGPixel
:
2002 style
& RANGESET_MASK
?
2003 getRangesetColor(textD
,
2004 (style
&RANGESET_MASK
)>>RANGESET_SHIFT
,
2006 styleRec
&& styleRec
->bgColorName
? styleRec
->bgColor
:
2007 (style
& BACKLIGHT_MASK
) && !(style
& FILL_MASK
) ?
2008 textD
->bgClassPixel
[(style
>>BACKLIGHT_SHIFT
) & 0xff] :
2010 if (fground
== bground
) /* B&W kludge */
2011 fground
= textD
->bgPixel
;
2012 /* set up gc for clearing using the foreground color entry */
2013 gcValues
.foreground
= gcValues
.background
= bground
;
2014 XChangeGC(XtDisplay(textD
->w
), gc
,
2015 GCFont
| GCForeground
| GCBackground
, &gcValues
);
2018 /* Draw blank area rather than text, if that was the request */
2019 if (style
& FILL_MASK
) {
2020 /* wipes out to right hand edge of widget */
2021 if (toX
>= textD
->left
)
2022 clearRect(textD
, bgGC
, max(x
, textD
->left
), y
,
2023 toX
- max(x
, textD
->left
), textD
->ascent
+ textD
->descent
);
2027 /* If any space around the character remains unfilled (due to use of
2028 different sized fonts for highlighting), fill in above or below
2029 to erase previously drawn characters */
2030 if (fs
->ascent
< textD
->ascent
)
2031 clearRect(textD
, bgGC
, x
, y
, toX
- x
, textD
->ascent
- fs
->ascent
);
2032 if (fs
->descent
< textD
->descent
)
2033 clearRect(textD
, bgGC
, x
, y
+ textD
->ascent
+ fs
->descent
, toX
- x
,
2034 textD
->descent
- fs
->descent
);
2036 /* set up gc for writing text (set foreground properly) */
2037 if (bgGC
== textD
->styleGC
) {
2038 gcValues
.foreground
= fground
;
2039 XChangeGC(XtDisplay(textD
->w
), gc
, GCForeground
, &gcValues
);
2042 /* Draw the string using gc and font set above */
2043 XDrawImageString(XtDisplay(textD
->w
), XtWindow(textD
->w
), gc
, x
,
2044 y
+ textD
->ascent
, string
, nChars
);
2046 /* Underline if style is secondary selection */
2047 if (style
& SECONDARY_MASK
|| underlineStyle
)
2049 /* restore foreground in GC (was set to background by clearRect()) */
2050 gcValues
.foreground
= fground
;
2051 XChangeGC(XtDisplay(textD
->w
), gc
,
2052 GCForeground
, &gcValues
);
2053 /* draw underline */
2054 XDrawLine(XtDisplay(textD
->w
), XtWindow(textD
->w
), gc
, x
,
2055 y
+ textD
->ascent
, toX
- 1, y
+ textD
->ascent
);
2060 ** Clear a rectangle with the appropriate background color for "style"
2062 static void clearRect(textDisp
*textD
, GC gc
, int x
, int y
,
2063 int width
, int height
)
2065 /* A width of zero means "clear to end of window" to XClearArea */
2066 if (width
== 0 || XtWindow(textD
->w
) == 0)
2069 if (gc
== textD
->gc
) {
2070 XClearArea(XtDisplay(textD
->w
), XtWindow(textD
->w
), x
, y
,
2071 width
, height
, False
);
2074 XFillRectangle(XtDisplay(textD
->w
), XtWindow(textD
->w
),
2075 gc
, x
, y
, width
, height
);
2080 ** Draw a cursor with top center at x, y.
2082 static void drawCursor(textDisp
*textD
, int x
, int y
)
2085 int left
, right
, cursorWidth
, midY
;
2086 int fontWidth
= textD
->fontStruct
->min_bounds
.width
, nSegs
= 0;
2087 int fontHeight
= textD
->ascent
+ textD
->descent
;
2088 int bot
= y
+ fontHeight
- 1;
2090 if (XtWindow(textD
->w
) == 0 || x
< textD
->left
-1 ||
2091 x
> textD
->left
+ textD
->width
)
2094 /* For cursors other than the block, make them around 2/3 of a character
2095 width, rounded to an even number of pixels so that X will draw an
2096 odd number centered on the stem at x. */
2097 cursorWidth
= (fontWidth
/3) * 2;
2098 left
= x
- cursorWidth
/2;
2099 right
= left
+ cursorWidth
;
2101 /* Create segments and draw cursor */
2102 if (textD
->cursorStyle
== CARET_CURSOR
) {
2103 midY
= bot
- fontHeight
/5;
2104 segs
[0].x1
= left
; segs
[0].y1
= bot
; segs
[0].x2
= x
; segs
[0].y2
= midY
;
2105 segs
[1].x1
= x
; segs
[1].y1
= midY
; segs
[1].x2
= right
; segs
[1].y2
= bot
;
2106 segs
[2].x1
= left
; segs
[2].y1
= bot
; segs
[2].x2
= x
; segs
[2].y2
=midY
-1;
2107 segs
[3].x1
= x
; segs
[3].y1
=midY
-1; segs
[3].x2
= right
; segs
[3].y2
= bot
;
2109 } else if (textD
->cursorStyle
== NORMAL_CURSOR
) {
2110 segs
[0].x1
= left
; segs
[0].y1
= y
; segs
[0].x2
= right
; segs
[0].y2
= y
;
2111 segs
[1].x1
= x
; segs
[1].y1
= y
; segs
[1].x2
= x
; segs
[1].y2
= bot
;
2112 segs
[2].x1
= left
; segs
[2].y1
= bot
; segs
[2].x2
= right
; segs
[2].y2
=bot
;
2114 } else if (textD
->cursorStyle
== HEAVY_CURSOR
) {
2115 segs
[0].x1
= x
-1; segs
[0].y1
= y
; segs
[0].x2
= x
-1; segs
[0].y2
= bot
;
2116 segs
[1].x1
= x
; segs
[1].y1
= y
; segs
[1].x2
= x
; segs
[1].y2
= bot
;
2117 segs
[2].x1
= x
+1; segs
[2].y1
= y
; segs
[2].x2
= x
+1; segs
[2].y2
= bot
;
2118 segs
[3].x1
= left
; segs
[3].y1
= y
; segs
[3].x2
= right
; segs
[3].y2
= y
;
2119 segs
[4].x1
= left
; segs
[4].y1
= bot
; segs
[4].x2
= right
; segs
[4].y2
=bot
;
2121 } else if (textD
->cursorStyle
== DIM_CURSOR
) {
2122 midY
= y
+ fontHeight
/2;
2123 segs
[0].x1
= x
; segs
[0].y1
= y
; segs
[0].x2
= x
; segs
[0].y2
= y
;
2124 segs
[1].x1
= x
; segs
[1].y1
= midY
; segs
[1].x2
= x
; segs
[1].y2
= midY
;
2125 segs
[2].x1
= x
; segs
[2].y1
= bot
; segs
[2].x2
= x
; segs
[2].y2
= bot
;
2127 } else if (textD
->cursorStyle
== BLOCK_CURSOR
) {
2128 right
= x
+ fontWidth
;
2129 segs
[0].x1
= x
; segs
[0].y1
= y
; segs
[0].x2
= right
; segs
[0].y2
= y
;
2130 segs
[1].x1
= right
; segs
[1].y1
= y
; segs
[1].x2
= right
; segs
[1].y2
=bot
;
2131 segs
[2].x1
= right
; segs
[2].y1
= bot
; segs
[2].x2
= x
; segs
[2].y2
= bot
;
2132 segs
[3].x1
= x
; segs
[3].y1
= bot
; segs
[3].x2
= x
; segs
[3].y2
= y
;
2135 XDrawSegments(XtDisplay(textD
->w
), XtWindow(textD
->w
),
2136 textD
->cursorFGGC
, segs
, nSegs
);
2138 /* Save the last position drawn */
2144 ** Determine the drawing method to use to draw a specific character from "buf".
2145 ** "lineStartPos" gives the character index where the line begins, "lineIndex",
2146 ** the number of characters past the beginning of the line, and "dispIndex",
2147 ** the number of displayed characters past the beginning of the line. Passing
2148 ** lineStartPos of -1 returns the drawing style for "no text".
2150 ** Why not just: styleOfPos(textD, pos)? Because style applies to blank areas
2151 ** of the window beyond the text boundaries, and because this routine must also
2152 ** decide whether a position is inside of a rectangular selection, and do so
2153 ** efficiently, without re-counting character positions from the start of the
2156 ** Note that style is a somewhat incorrect name, drawing method would
2157 ** be more appropriate.
2159 static int styleOfPos(textDisp
*textD
, int lineStartPos
,
2160 int lineLen
, int lineIndex
, int dispIndex
, int thisChar
)
2162 textBuffer
*buf
= textD
->buffer
;
2163 textBuffer
*styleBuf
= textD
->styleBuffer
;
2166 if (lineStartPos
== -1 || buf
== NULL
)
2169 pos
= lineStartPos
+ min(lineIndex
, lineLen
);
2171 if (lineIndex
>= lineLen
)
2173 else if (styleBuf
!= NULL
) {
2174 style
= (unsigned char)BufGetCharacter(styleBuf
, pos
);
2175 if (style
== textD
->unfinishedStyle
) {
2176 /* encountered "unfinished" style, trigger parsing */
2177 (textD
->unfinishedHighlightCB
)(textD
, pos
, textD
->highlightCBArg
);
2178 style
= (unsigned char)BufGetCharacter(styleBuf
, pos
);
2181 if (inSelection(&buf
->primary
, pos
, lineStartPos
, dispIndex
))
2182 style
|= PRIMARY_MASK
;
2183 if (inSelection(&buf
->highlight
, pos
, lineStartPos
, dispIndex
))
2184 style
|= HIGHLIGHT_MASK
;
2185 if (inSelection(&buf
->secondary
, pos
, lineStartPos
, dispIndex
))
2186 style
|= SECONDARY_MASK
;
2187 /* store in the RANGESET_MASK portion of style the rangeset index for pos */
2188 if (buf
->rangesetTable
) {
2189 int rangesetIndex
= RangesetIndex1ofPos(buf
->rangesetTable
, pos
, True
);
2190 style
|= ((rangesetIndex
<< RANGESET_SHIFT
) & RANGESET_MASK
);
2192 /* store in the BACKLIGHT_MASK portion of style the background color class
2193 of the character thisChar */
2196 style
|= (textD
->bgClass
[(unsigned char)thisChar
]<<BACKLIGHT_SHIFT
);
2202 ** Find the width of a string in the font of a particular style
2204 static int stringWidth(const textDisp
* textD
, const char *string
,
2205 const int length
, const int style
)
2209 if (style
& STYLE_LOOKUP_MASK
)
2210 fs
= textD
->styleTable
[(style
& STYLE_LOOKUP_MASK
) - ASCII_A
].font
;
2212 fs
= textD
->fontStruct
;
2213 return XTextWidth(fs
, (char*) string
, (int) length
);
2217 ** Return true if position "pos" with indentation "dispIndex" is in
2220 static int inSelection(selection
*sel
, int pos
, int lineStartPos
, int dispIndex
)
2222 return sel
->selected
&&
2223 ((!sel
->rectangular
&&
2224 pos
>= sel
->start
&& pos
< sel
->end
) ||
2225 (sel
->rectangular
&&
2226 pos
>= sel
->start
&& lineStartPos
<= sel
->end
&&
2227 dispIndex
>= sel
->rectStart
&& dispIndex
< sel
->rectEnd
));
2231 ** Translate window coordinates to the nearest (insert cursor or character
2232 ** cell) text position. The parameter posType specifies how to interpret the
2233 ** position: CURSOR_POS means translate the coordinates to the nearest cursor
2234 ** position, and CHARACTER_POS means return the position of the character
2235 ** closest to (x, y).
2237 static int xyToPos(textDisp
*textD
, int x
, int y
, int posType
)
2239 int charIndex
, lineStart
, lineLen
, fontHeight
;
2240 int charWidth
, charLen
, charStyle
, visLineNum
, xStep
, outIndex
;
2241 char *lineStr
, expandedChar
[MAX_EXP_CHAR_LEN
];
2243 /* Find the visible line number corresponding to the y coordinate */
2244 fontHeight
= textD
->ascent
+ textD
->descent
;
2245 visLineNum
= (y
- textD
->top
) / fontHeight
;
2247 return textD
->firstChar
;
2248 if (visLineNum
>= textD
->nVisibleLines
)
2249 visLineNum
= textD
->nVisibleLines
- 1;
2251 /* Find the position at the start of the line */
2252 lineStart
= textD
->lineStarts
[visLineNum
];
2254 /* If the line start was empty, return the last position in the buffer */
2255 if (lineStart
== -1)
2256 return textD
->buffer
->length
;
2258 /* Get the line text and its length */
2259 lineLen
= visLineLength(textD
, visLineNum
);
2260 lineStr
= BufGetRange(textD
->buffer
, lineStart
, lineStart
+ lineLen
);
2262 /* Step through character positions from the beginning of the line
2263 to find the character position corresponding to the x coordinate */
2264 xStep
= textD
->left
- textD
->horizOffset
;
2266 for(charIndex
=0; charIndex
<lineLen
; charIndex
++) {
2267 charLen
= BufExpandCharacter(lineStr
[charIndex
], outIndex
, expandedChar
,
2268 textD
->buffer
->tabDist
, textD
->buffer
->nullSubsChar
);
2269 charStyle
= styleOfPos(textD
, lineStart
, lineLen
, charIndex
, outIndex
,
2270 lineStr
[charIndex
]);
2271 charWidth
= stringWidth(textD
, expandedChar
, charLen
, charStyle
);
2272 if (x
< xStep
+ (posType
== CURSOR_POS
? charWidth
/2 : charWidth
)) {
2274 return lineStart
+ charIndex
;
2277 outIndex
+= charLen
;
2280 /* If the x position was beyond the end of the line, return the position
2281 of the newline at the end of the line */
2283 return lineStart
+ lineLen
;
2287 ** Translate window coordinates to the nearest row and column number for
2288 ** positioning the cursor. This, of course, makes no sense when the font is
2289 ** proportional, since there are no absolute columns. The parameter posType
2290 ** specifies how to interpret the position: CURSOR_POS means translate the
2291 ** coordinates to the nearest position between characters, and CHARACTER_POS
2292 ** means translate the position to the nearest character cell.
2294 static void xyToUnconstrainedPos(textDisp
*textD
, int x
, int y
, int *row
,
2295 int *column
, int posType
)
2297 int fontHeight
= textD
->ascent
+ textD
->descent
;
2298 int fontWidth
= textD
->fontStruct
->max_bounds
.width
;
2300 /* Find the visible line number corresponding to the y coordinate */
2301 *row
= (y
- textD
->top
) / fontHeight
;
2302 if (*row
< 0) *row
= 0;
2303 if (*row
>= textD
->nVisibleLines
) *row
= textD
->nVisibleLines
- 1;
2304 *column
= ((x
-textD
->left
) + textD
->horizOffset
+
2305 (posType
== CURSOR_POS
? fontWidth
/2 : 0)) / fontWidth
;
2306 if (*column
< 0) *column
= 0;
2310 ** Offset the line starts array, topLineNum, firstChar and lastChar, for a new
2311 ** vertical scroll position given by newTopLineNum. If any currently displayed
2312 ** lines will still be visible, salvage the line starts values, otherwise,
2313 ** count lines from the nearest known line start (start or end of buffer, or
2314 ** the closest value in the lineStarts array)
2316 static void offsetLineStarts(textDisp
*textD
, int newTopLineNum
)
2318 int oldTopLineNum
= textD
->topLineNum
;
2319 int oldFirstChar
= textD
->firstChar
;
2320 int lineDelta
= newTopLineNum
- oldTopLineNum
;
2321 int nVisLines
= textD
->nVisibleLines
;
2322 int *lineStarts
= textD
->lineStarts
;
2324 textBuffer
*buf
= textD
->buffer
;
2326 /* If there was no offset, nothing needs to be changed */
2331 printf("Scroll, lineDelta %d\n", lineDelta);
2332 printf("lineStarts Before: ");
2333 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2337 /* Find the new value for firstChar by counting lines from the nearest
2338 known line start (start or end of buffer, or the closest value in the
2339 lineStarts array) */
2340 lastLineNum
= oldTopLineNum
+ nVisLines
- 1;
2341 if (newTopLineNum
< oldTopLineNum
&& newTopLineNum
< -lineDelta
) {
2342 textD
->firstChar
= TextDCountForwardNLines(textD
, 0, newTopLineNum
-1,
2344 /* printf("counting forward %d lines from start\n", newTopLineNum-1);*/
2345 } else if (newTopLineNum
< oldTopLineNum
) {
2346 textD
->firstChar
= TextDCountBackwardNLines(textD
, textD
->firstChar
,
2348 /* printf("counting backward %d lines from firstChar\n", -lineDelta);*/
2349 } else if (newTopLineNum
< lastLineNum
) {
2350 textD
->firstChar
= lineStarts
[newTopLineNum
- oldTopLineNum
];
2351 /* printf("taking new start from lineStarts[%d]\n",
2352 newTopLineNum - oldTopLineNum); */
2353 } else if (newTopLineNum
-lastLineNum
< textD
->nBufferLines
-newTopLineNum
) {
2354 textD
->firstChar
= TextDCountForwardNLines(textD
,
2355 lineStarts
[nVisLines
-1], newTopLineNum
- lastLineNum
, True
);
2356 /* printf("counting forward %d lines from start of last line\n",
2357 newTopLineNum - lastLineNum); */
2359 textD
->firstChar
= TextDCountBackwardNLines(textD
, buf
->length
,
2360 textD
->nBufferLines
- newTopLineNum
+ 1);
2361 /* printf("counting backward %d lines from end\n",
2362 textD->nBufferLines - newTopLineNum + 1); */
2365 /* Fill in the line starts array */
2366 if (lineDelta
< 0 && -lineDelta
< nVisLines
) {
2367 for (i
=nVisLines
-1; i
>= -lineDelta
; i
--)
2368 lineStarts
[i
] = lineStarts
[i
+lineDelta
];
2369 calcLineStarts(textD
, 0, -lineDelta
);
2370 } else if (lineDelta
> 0 && lineDelta
< nVisLines
) {
2371 for (i
=0; i
<nVisLines
-lineDelta
; i
++)
2372 lineStarts
[i
] = lineStarts
[i
+lineDelta
];
2373 calcLineStarts(textD
, nVisLines
-lineDelta
, nVisLines
-1);
2375 calcLineStarts(textD
, 0, nVisLines
);
2377 /* Set lastChar and topLineNum */
2378 calcLastChar(textD
);
2379 textD
->topLineNum
= newTopLineNum
;
2381 /* If we're numbering lines or being asked to maintain an absolute line
2382 number, re-calculate the absolute line number */
2383 offsetAbsLineNum(textD
, oldFirstChar
);
2386 printf("lineStarts After: ");
2387 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2393 ** Update the line starts array, topLineNum, firstChar and lastChar for text
2394 ** display "textD" after a modification to the text buffer, given by the
2395 ** position where the change began "pos", and the nmubers of characters
2396 ** and lines inserted and deleted.
2398 static void updateLineStarts(textDisp
*textD
, int pos
, int charsInserted
,
2399 int charsDeleted
, int linesInserted
, int linesDeleted
, int *scrolled
)
2401 int *lineStarts
= textD
->lineStarts
;
2402 int i
, lineOfPos
, lineOfEnd
, nVisLines
= textD
->nVisibleLines
;
2403 int charDelta
= charsInserted
- charsDeleted
;
2404 int lineDelta
= linesInserted
- linesDeleted
;
2407 printf("linesDeleted %d, linesInserted %d, charsInserted %d, charsDeleted %d\n",
2408 linesDeleted, linesInserted, charsInserted, charsDeleted);
2409 printf("lineStarts Before: ");
2410 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2413 /* If all of the changes were before the displayed text, the display
2414 doesn't change, just update the top line num and offset the line
2415 start entries and first and last characters */
2416 if (pos
+ charsDeleted
< textD
->firstChar
) {
2417 textD
->topLineNum
+= lineDelta
;
2418 for (i
=0; i
<nVisLines
&& lineStarts
[i
] != -1; i
++)
2419 lineStarts
[i
] += charDelta
;
2421 printf("lineStarts after delete doesn't touch: ");
2422 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2425 textD
->firstChar
+= charDelta
;
2426 textD
->lastChar
+= charDelta
;
2431 /* The change began before the beginning of the displayed text, but
2432 part or all of the displayed text was deleted */
2433 if (pos
< textD
->firstChar
) {
2434 /* If some text remains in the window, anchor on that */
2435 if (posToVisibleLineNum(textD
, pos
+ charsDeleted
, &lineOfEnd
) &&
2436 ++lineOfEnd
< nVisLines
&& lineStarts
[lineOfEnd
] != -1) {
2437 textD
->topLineNum
= max(1, textD
->topLineNum
+ lineDelta
);
2438 textD
->firstChar
= TextDCountBackwardNLines(textD
,
2439 lineStarts
[lineOfEnd
] + charDelta
, lineOfEnd
);
2440 /* Otherwise anchor on original line number and recount everything */
2442 if (textD
->topLineNum
> textD
->nBufferLines
+ lineDelta
) {
2443 textD
->topLineNum
= 1;
2444 textD
->firstChar
= 0;
2446 textD
->firstChar
= TextDCountForwardNLines(textD
, 0,
2447 textD
->topLineNum
- 1, True
);
2449 calcLineStarts(textD
, 0, nVisLines
-1);
2451 printf("lineStarts after delete encroaches: ");
2452 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2455 /* calculate lastChar by finding the end of the last displayed line */
2456 calcLastChar(textD
);
2461 /* If the change was in the middle of the displayed text (it usually is),
2462 salvage as much of the line starts array as possible by moving and
2463 offsetting the entries after the changed area, and re-counting the
2464 added lines or the lines beyond the salvaged part of the line starts
2466 if (pos
<= textD
->lastChar
) {
2467 /* find line on which the change began */
2468 posToVisibleLineNum(textD
, pos
, &lineOfPos
);
2469 /* salvage line starts after the changed area */
2470 if (lineDelta
== 0) {
2471 for (i
=lineOfPos
+1; i
<nVisLines
&& lineStarts
[i
]!= -1; i
++)
2472 lineStarts
[i
] += charDelta
;
2473 } else if (lineDelta
> 0) {
2474 for (i
=nVisLines
-1; i
>=lineOfPos
+lineDelta
+1; i
--)
2475 lineStarts
[i
] = lineStarts
[i
-lineDelta
] +
2476 (lineStarts
[i
-lineDelta
] == -1 ? 0 : charDelta
);
2477 } else /* (lineDelta < 0) */ {
2478 for (i
=max(0,lineOfPos
+1); i
<nVisLines
+lineDelta
; i
++)
2479 lineStarts
[i
] = lineStarts
[i
-lineDelta
] +
2480 (lineStarts
[i
-lineDelta
] == -1 ? 0 : charDelta
);
2483 printf("lineStarts after salvage: ");
2484 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2487 /* fill in the missing line starts */
2488 if (linesInserted
>= 0)
2489 calcLineStarts(textD
, lineOfPos
+ 1, lineOfPos
+ linesInserted
);
2491 calcLineStarts(textD
, nVisLines
+lineDelta
, nVisLines
);
2493 printf("lineStarts after recalculation: ");
2494 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2497 /* calculate lastChar by finding the end of the last displayed line */
2498 calcLastChar(textD
);
2503 /* Change was past the end of the displayed text, but displayable by virtue
2504 of being an insert at the end of the buffer into visible blank lines */
2505 if (emptyLinesVisible(textD
)) {
2506 posToVisibleLineNum(textD
, pos
, &lineOfPos
);
2507 calcLineStarts(textD
, lineOfPos
, lineOfPos
+linesInserted
);
2508 calcLastChar(textD
);
2510 printf("lineStarts after insert at end: ");
2511 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2518 /* Change was beyond the end of the buffer and not visible, do nothing */
2523 ** Scan through the text in the "textD"'s buffer and recalculate the line
2524 ** starts array values beginning at index "startLine" and continuing through
2525 ** (including) "endLine". It assumes that the line starts entry preceding
2526 ** "startLine" (or textD->firstChar if startLine is 0) is good, and re-counts
2527 ** newlines to fill in the requested entries. Out of range values for
2528 ** "startLine" and "endLine" are acceptable.
2530 static void calcLineStarts(textDisp
*textD
, int startLine
, int endLine
)
2532 int startPos
, bufLen
= textD
->buffer
->length
;
2533 int line
, lineEnd
, nextLineStart
, nVis
= textD
->nVisibleLines
;
2534 int *lineStarts
= textD
->lineStarts
;
2536 /* Clean up (possibly) messy input parameters */
2537 if (nVis
== 0) return;
2538 if (endLine
< 0) endLine
= 0;
2539 if (endLine
>= nVis
) endLine
= nVis
- 1;
2540 if (startLine
< 0) startLine
= 0;
2541 if (startLine
>=nVis
) startLine
= nVis
- 1;
2542 if (startLine
> endLine
)
2545 /* Find the last known good line number -> position mapping */
2546 if (startLine
== 0) {
2547 lineStarts
[0] = textD
->firstChar
;
2550 startPos
= lineStarts
[startLine
-1];
2552 /* If the starting position is already past the end of the text,
2553 fill in -1's (means no text on line) and return */
2554 if (startPos
== -1) {
2555 for (line
=startLine
; line
<=endLine
; line
++)
2556 lineStarts
[line
] = -1;
2560 /* Loop searching for ends of lines and storing the positions of the
2561 start of the next line in lineStarts */
2562 for (line
=startLine
; line
<=endLine
; line
++) {
2563 findLineEnd(textD
, startPos
, True
, &lineEnd
, &nextLineStart
);
2564 startPos
= nextLineStart
;
2565 if (startPos
>= bufLen
) {
2566 /* If the buffer ends with a newline or line break, put
2567 buf->length in the next line start position (instead of
2568 a -1 which is the normal marker for an empty line) to
2569 indicate that the cursor may safely be displayed there */
2570 if (line
== 0 || (lineStarts
[line
-1] != bufLen
&&
2571 lineEnd
!= nextLineStart
)) {
2572 lineStarts
[line
] = bufLen
;
2577 lineStarts
[line
] = startPos
;
2580 /* Set any entries beyond the end of the text to -1 */
2581 for (; line
<=endLine
; line
++)
2582 lineStarts
[line
] = -1;
2586 ** Given a textDisp with a complete, up-to-date lineStarts array, update
2587 ** the lastChar entry to point to the last buffer position displayed.
2589 static void calcLastChar(textDisp
*textD
)
2593 for (i
=textD
->nVisibleLines
-1; i
>0 && textD
->lineStarts
[i
]== -1; i
--);
2594 textD
->lastChar
= i
< 0 ? 0 :
2595 TextDEndOfLine(textD
, textD
->lineStarts
[i
], True
);
2598 void TextDImposeGraphicsExposeTranslation(textDisp
*textD
, int *xOffset
, int *yOffset
)
2600 if (textD
->graphicsExposeQueue
) {
2601 graphicExposeTranslationEntry
*thisGEQEntry
= textD
->graphicsExposeQueue
->next
;
2603 *xOffset
+= thisGEQEntry
->horizontal
;
2604 *yOffset
+= thisGEQEntry
->vertical
;
2609 Boolean
TextDPopGraphicExposeQueueEntry(textDisp
*textD
)
2611 graphicExposeTranslationEntry
*removedGEQEntry
= textD
->graphicsExposeQueue
;
2613 if (removedGEQEntry
) {
2614 textD
->graphicsExposeQueue
= removedGEQEntry
->next
;
2615 XtFree((char *)removedGEQEntry
);
2617 return(removedGEQEntry
?True
:False
);
2620 void TextDTranlateGraphicExposeQueue(textDisp
*textD
, int xOffset
, int yOffset
, Boolean appendEntry
)
2622 graphicExposeTranslationEntry
*newGEQEntry
= NULL
;
2624 newGEQEntry
= (graphicExposeTranslationEntry
*)XtMalloc(sizeof(graphicExposeTranslationEntry
));
2625 newGEQEntry
->next
= NULL
;
2626 newGEQEntry
->horizontal
= xOffset
;
2627 newGEQEntry
->vertical
= yOffset
;
2629 if (textD
->graphicsExposeQueue
) {
2630 graphicExposeTranslationEntry
*iter
= textD
->graphicsExposeQueue
;
2631 while (iter
->next
) {
2632 iter
->next
->horizontal
+= xOffset
;
2633 iter
->next
->vertical
+= yOffset
;
2637 iter
->next
= (struct graphicExposeTranslationEntry
*)newGEQEntry
;
2642 textD
->graphicsExposeQueue
= newGEQEntry
;
2647 static void setScroll(textDisp
*textD
, int topLineNum
, int horizOffset
,
2648 int updateVScrollBar
, int updateHScrollBar
)
2650 int fontHeight
= textD
->ascent
+ textD
->descent
;
2651 int origHOffset
= textD
->horizOffset
;
2652 int lineDelta
= textD
->topLineNum
- topLineNum
;
2653 int xOffset
, yOffset
, srcX
, srcY
, dstX
, dstY
, width
, height
;
2654 int exactHeight
= textD
->height
- textD
->height
%
2655 (textD
->ascent
+ textD
->descent
);
2657 /* Do nothing if scroll position hasn't actually changed or there's no
2658 window to draw in yet */
2659 if (XtWindow(textD
->w
) == 0 || (textD
->horizOffset
== horizOffset
&&
2660 textD
->topLineNum
== topLineNum
))
2663 /* If part of the cursor is protruding beyond the text clipping region,
2665 blankCursorProtrusions(textD
);
2667 /* If the vertical scroll position has changed, update the line
2668 starts array and related counters in the text display */
2669 offsetLineStarts(textD
, topLineNum
);
2671 /* Just setting textD->horizOffset is enough information for redisplay */
2672 textD
->horizOffset
= horizOffset
;
2674 /* Update the scroll bar positions if requested, note: updating the
2675 horizontal scroll bars can have the further side-effect of changing
2676 the horizontal scroll position, textD->horizOffset */
2677 if (updateVScrollBar
&& textD
->vScrollBar
!= NULL
) {
2678 updateVScrollBarRange(textD
);
2680 if (updateHScrollBar
&& textD
->hScrollBar
!= NULL
) {
2681 updateHScrollBarRange(textD
);
2684 /* Redisplay everything if the window is partially obscured (since
2685 it's too hard to tell what displayed areas are salvageable) or
2686 if there's nothing to recover because the scroll distance is large */
2687 xOffset
= origHOffset
- textD
->horizOffset
;
2688 yOffset
= lineDelta
* fontHeight
;
2689 if (textD
->visibility
!= VisibilityUnobscured
||
2690 abs(xOffset
) > textD
->width
|| abs(yOffset
) > exactHeight
) {
2691 TextDTranlateGraphicExposeQueue(textD
, xOffset
, yOffset
, False
);
2692 TextDRedisplayRect(textD
, textD
->left
, textD
->top
, textD
->width
,
2695 /* If the window is not obscured, paint most of the window using XCopyArea
2696 from existing displayed text, and redraw only what's necessary */
2697 /* Recover the useable window areas by moving to the proper location */
2698 srcX
= textD
->left
+ (xOffset
>= 0 ? 0 : -xOffset
);
2699 dstX
= textD
->left
+ (xOffset
>= 0 ? xOffset
: 0);
2700 width
= textD
->width
- abs(xOffset
);
2701 srcY
= textD
->top
+ (yOffset
>= 0 ? 0 : -yOffset
);
2702 dstY
= textD
->top
+ (yOffset
>= 0 ? yOffset
: 0);
2703 height
= exactHeight
- abs(yOffset
);
2704 resetClipRectangles(textD
);
2705 TextDTranlateGraphicExposeQueue(textD
, xOffset
, yOffset
, True
);
2706 XCopyArea(XtDisplay(textD
->w
), XtWindow(textD
->w
), XtWindow(textD
->w
),
2707 textD
->gc
, srcX
, srcY
, width
, height
, dstX
, dstY
);
2708 /* redraw the un-recoverable parts */
2710 TextDRedisplayRect(textD
, textD
->left
, textD
->top
,
2711 textD
->width
, yOffset
);
2713 else if (yOffset
< 0) {
2714 TextDRedisplayRect(textD
, textD
->left
, textD
->top
+
2715 textD
->height
+ yOffset
, textD
->width
, -yOffset
);
2718 TextDRedisplayRect(textD
, textD
->left
, textD
->top
,
2719 xOffset
, textD
->height
);
2721 else if (xOffset
< 0) {
2722 TextDRedisplayRect(textD
, textD
->left
+ textD
->width
+ xOffset
,
2723 textD
->top
, -xOffset
, textD
->height
);
2725 /* Restore protruding parts of the cursor */
2726 textDRedisplayRange(textD
, textD
->cursorPos
-1, textD
->cursorPos
+1);
2729 /* Refresh line number/calltip display if its up and we've scrolled
2731 if (lineDelta
!= 0) {
2732 redrawLineNumbers(textD
, False
);
2733 TextDRedrawCalltip(textD
, 0);
2736 HandleAllPendingGraphicsExposeNoExposeEvents((TextWidget
)textD
->w
, NULL
);
2740 ** Update the minimum, maximum, slider size, page increment, and value
2741 ** for vertical scroll bar.
2743 static void updateVScrollBarRange(textDisp
*textD
)
2745 int sliderSize
, sliderMax
, sliderValue
;
2747 if (textD
->vScrollBar
== NULL
)
2750 /* The Vert. scroll bar value and slider size directly represent the top
2751 line number, and the number of visible lines respectively. The scroll
2752 bar maximum value is chosen to generally represent the size of the whole
2753 buffer, with minor adjustments to keep the scroll bar widget happy */
2754 sliderSize
= max(textD
->nVisibleLines
, 1); /* Avoid X warning (size < 1) */
2755 sliderValue
= textD
->topLineNum
;
2756 sliderMax
= max(textD
->nBufferLines
+ 2 +
2757 TEXT_OF_TEXTD(textD
).cursorVPadding
,
2758 sliderSize
+ sliderValue
);
2759 XtVaSetValues(textD
->vScrollBar
,
2760 XmNmaximum
, sliderMax
,
2761 XmNsliderSize
, sliderSize
,
2762 XmNpageIncrement
, max(1, textD
->nVisibleLines
- 1),
2763 XmNvalue
, sliderValue
, NULL
);
2767 ** Update the minimum, maximum, slider size, page increment, and value
2768 ** for the horizontal scroll bar. If scroll position is such that there
2769 ** is blank space to the right of all lines of text, scroll back (adjust
2770 ** horizOffset but don't redraw) to take up the slack and position the
2771 ** right edge of the text at the right edge of the display.
2773 ** Note, there is some cost to this routine, since it scans the whole range
2774 ** of displayed text, particularly since it's usually called for each typed
2777 static int updateHScrollBarRange(textDisp
*textD
)
2779 int i
, maxWidth
= 0, sliderMax
, sliderWidth
;
2780 int origHOffset
= textD
->horizOffset
;
2782 if (textD
->hScrollBar
== NULL
|| !XtIsManaged(textD
->hScrollBar
))
2785 /* Scan all the displayed lines to find the width of the longest line */
2786 for (i
=0; i
<textD
->nVisibleLines
&& textD
->lineStarts
[i
]!= -1; i
++)
2787 maxWidth
= max(measureVisLine(textD
, i
), maxWidth
);
2789 /* If the scroll position is beyond what's necessary to keep all lines
2790 in view, scroll to the left to bring the end of the longest line to
2792 if (maxWidth
< textD
->width
+ textD
->horizOffset
&& textD
->horizOffset
> 0)
2793 textD
->horizOffset
= max(0, maxWidth
- textD
->width
);
2795 /* Readjust the scroll bar */
2796 sliderWidth
= textD
->width
;
2797 sliderMax
= max(maxWidth
, sliderWidth
+ textD
->horizOffset
);
2798 XtVaSetValues(textD
->hScrollBar
,
2799 XmNmaximum
, sliderMax
,
2800 XmNsliderSize
, sliderWidth
,
2801 XmNpageIncrement
, max(textD
->width
- 100, 10),
2802 XmNvalue
, textD
->horizOffset
, NULL
);
2804 /* Return True if scroll position was changed */
2805 return origHOffset
!= textD
->horizOffset
;
2809 ** Define area for drawing line numbers. A width of 0 disables line
2812 void TextDSetLineNumberArea(textDisp
*textD
, int lineNumLeft
, int lineNumWidth
,
2815 int newWidth
= textD
->width
+ textD
->left
- textLeft
;
2816 textD
->lineNumLeft
= lineNumLeft
;
2817 textD
->lineNumWidth
= lineNumWidth
;
2818 textD
->left
= textLeft
;
2819 XClearWindow(XtDisplay(textD
->w
), XtWindow(textD
->w
));
2820 resetAbsLineNum(textD
);
2821 TextDResize(textD
, newWidth
, textD
->height
);
2822 TextDRedisplayRect(textD
, 0, textD
->top
, INT_MAX
, textD
->height
);
2826 ** Refresh the line number area. If clearAll is False, writes only over
2827 ** the character cell areas. Setting clearAll to True will clear out any
2828 ** stray marks outside of the character cell area, which might have been
2829 ** left from before a resize or font change.
2831 static void redrawLineNumbers(textDisp
*textD
, int clearAll
)
2833 int y
, line
, visLine
, nCols
, lineStart
;
2834 char lineNumString
[12];
2835 int lineHeight
= textD
->ascent
+ textD
->descent
;
2836 int charWidth
= textD
->fontStruct
->max_bounds
.width
;
2837 XRectangle clipRect
;
2838 Display
*display
= XtDisplay(textD
->w
);
2840 /* Don't draw if lineNumWidth == 0 (line numbers are hidden), or widget is
2842 if (textD
->lineNumWidth
== 0 || XtWindow(textD
->w
) == 0)
2845 /* Make sure we reset the clipping range for the line numbers GC, because
2846 the GC may be shared (eg, if the line numbers and text have the same
2847 color) and therefore the clipping ranges may be invalid. */
2848 clipRect
.x
= textD
->lineNumLeft
;
2849 clipRect
.y
= textD
->top
;
2850 clipRect
.width
= textD
->lineNumWidth
;
2851 clipRect
.height
= textD
->height
;
2852 XSetClipRectangles(display
, textD
->lineNumGC
, 0, 0,
2853 &clipRect
, 1, Unsorted
);
2855 /* Erase the previous contents of the line number area, if requested */
2857 XClearArea(XtDisplay(textD
->w
), XtWindow(textD
->w
), textD
->lineNumLeft
,
2858 textD
->top
, textD
->lineNumWidth
, textD
->height
, False
);
2860 /* Draw the line numbers, aligned to the text */
2861 nCols
= min(11, textD
->lineNumWidth
/ charWidth
);
2863 line
= getAbsTopLineNum(textD
);
2864 for (visLine
=0; visLine
< textD
->nVisibleLines
; visLine
++) {
2865 lineStart
= textD
->lineStarts
[visLine
];
2866 if (lineStart
!= -1 && (lineStart
==0 ||
2867 BufGetCharacter(textD
->buffer
, lineStart
-1)=='\n')) {
2868 sprintf(lineNumString
, "%*d", nCols
, line
);
2869 XDrawImageString(XtDisplay(textD
->w
), XtWindow(textD
->w
),
2870 textD
->lineNumGC
, textD
->lineNumLeft
, y
+ textD
->ascent
,
2871 lineNumString
, strlen(lineNumString
));
2874 XClearArea(XtDisplay(textD
->w
), XtWindow(textD
->w
),
2875 textD
->lineNumLeft
, y
, textD
->lineNumWidth
,
2876 textD
->ascent
+ textD
->descent
, False
);
2885 ** Callbacks for drag or valueChanged on scroll bars
2887 static void vScrollCB(Widget w
, XtPointer clientData
, XtPointer callData
)
2889 textDisp
*textD
= (textDisp
*)clientData
;
2890 int newValue
= ((XmScrollBarCallbackStruct
*)callData
)->value
;
2891 int lineDelta
= newValue
- textD
->topLineNum
;
2895 setScroll(textD
, newValue
, textD
->horizOffset
, False
, True
);
2897 static void hScrollCB(Widget w
, XtPointer clientData
, XtPointer callData
)
2899 textDisp
*textD
= (textDisp
*)clientData
;
2900 int newValue
= ((XmScrollBarCallbackStruct
*)callData
)->value
;
2902 if (newValue
== textD
->horizOffset
)
2904 setScroll(textD
, textD
->topLineNum
, newValue
, False
, False
);
2907 static void visibilityEH(Widget w
, XtPointer data
, XEvent
*event
,
2908 Boolean
*continueDispatch
)
2910 /* Record whether the window is fully visible or not. This information
2911 is used for choosing the scrolling methodology for optimal performance,
2912 if the window is partially obscured, XCopyArea may not work */
2913 ((textDisp
*)data
)->visibility
= ((XVisibilityEvent
*)event
)->state
;
2916 static int max(int i1
, int i2
)
2918 return i1
>= i2
? i1
: i2
;
2921 static int min(int i1
, int i2
)
2923 return i1
<= i2
? i1
: i2
;
2927 ** Count the number of newlines in a null-terminated text string;
2929 static int countLines(const char *string
)
2936 for (c
=string
; *c
!='\0'; c
++)
2937 if (*c
== '\n') lineCount
++;
2942 ** Return the width in pixels of the displayed line pointed to by "visLineNum"
2944 static int measureVisLine(textDisp
*textD
, int visLineNum
)
2946 int i
, width
= 0, len
, style
, lineLen
= visLineLength(textD
, visLineNum
);
2947 int charCount
= 0, lineStartPos
= textD
->lineStarts
[visLineNum
];
2948 char expandedChar
[MAX_EXP_CHAR_LEN
];
2950 if (textD
->styleBuffer
== NULL
) {
2951 for (i
=0; i
<lineLen
; i
++) {
2952 len
= BufGetExpandedChar(textD
->buffer
, lineStartPos
+ i
,
2953 charCount
, expandedChar
);
2954 width
+= XTextWidth(textD
->fontStruct
, expandedChar
, len
);
2958 for (i
=0; i
<lineLen
; i
++) {
2959 len
= BufGetExpandedChar(textD
->buffer
, lineStartPos
+i
,
2960 charCount
, expandedChar
);
2961 style
= (unsigned char)BufGetCharacter(textD
->styleBuffer
,
2962 lineStartPos
+i
) - ASCII_A
;
2963 width
+= XTextWidth(textD
->styleTable
[style
].font
, expandedChar
,
2972 ** Return true if there are lines visible with no corresponding buffer text
2974 static int emptyLinesVisible(textDisp
*textD
)
2976 return textD
->nVisibleLines
> 0 &&
2977 textD
->lineStarts
[textD
->nVisibleLines
-1] == -1;
2981 ** When the cursor is at the left or right edge of the text, part of it
2982 ** sticks off into the clipped region beyond the text. Normal redrawing
2983 ** can not overwrite this protruding part of the cursor, so it must be
2984 ** erased independently by calling this routine.
2986 static void blankCursorProtrusions(textDisp
*textD
)
2988 int x
, width
, cursorX
= textD
->cursorX
, cursorY
= textD
->cursorY
;
2989 int fontWidth
= textD
->fontStruct
->max_bounds
.width
;
2990 int fontHeight
= textD
->ascent
+ textD
->descent
;
2991 int cursorWidth
, left
= textD
->left
, right
= left
+ textD
->width
;
2993 cursorWidth
= (fontWidth
/3) * 2;
2994 if (cursorX
>= left
-1 && cursorX
<= left
+ cursorWidth
/2 - 1) {
2995 x
= cursorX
- cursorWidth
/2;
2997 } else if (cursorX
>= right
- cursorWidth
/2 && cursorX
<= right
) {
2999 width
= cursorX
+ cursorWidth
/2 + 2 - right
;
3003 XClearArea(XtDisplay(textD
->w
), XtWindow(textD
->w
), x
, cursorY
,
3004 width
, fontHeight
, False
);
3008 ** Allocate shared graphics contexts used by the widget, which must be
3009 ** re-allocated on a font change.
3011 static void allocateFixedFontGCs(textDisp
*textD
, XFontStruct
*fontStruct
,
3012 Pixel bgPixel
, Pixel fgPixel
, Pixel selectFGPixel
, Pixel selectBGPixel
,
3013 Pixel highlightFGPixel
, Pixel highlightBGPixel
, Pixel lineNumFGPixel
)
3015 textD
->gc
= allocateGC(textD
->w
, GCFont
| GCForeground
| GCBackground
,
3016 fgPixel
, bgPixel
, fontStruct
->fid
, GCClipMask
, GCArcMode
);
3017 textD
->selectGC
= allocateGC(textD
->w
, GCFont
| GCForeground
| GCBackground
,
3018 selectFGPixel
, selectBGPixel
, fontStruct
->fid
, GCClipMask
,
3020 textD
->selectBGGC
= allocateGC(textD
->w
, GCForeground
, selectBGPixel
, 0,
3021 fontStruct
->fid
, GCClipMask
, GCArcMode
);
3022 textD
->highlightGC
= allocateGC(textD
->w
, GCFont
|GCForeground
|GCBackground
,
3023 highlightFGPixel
, highlightBGPixel
, fontStruct
->fid
, GCClipMask
,
3025 textD
->highlightBGGC
= allocateGC(textD
->w
, GCForeground
, highlightBGPixel
,
3026 0, fontStruct
->fid
, GCClipMask
, GCArcMode
);
3027 textD
->lineNumGC
= allocateGC(textD
->w
, GCFont
| GCForeground
|
3028 GCBackground
, lineNumFGPixel
, bgPixel
, fontStruct
->fid
,
3029 GCClipMask
, GCArcMode
);
3033 ** X11R4 does not have the XtAllocateGC function for sharing graphics contexts
3034 ** with changeable fields. Unfortunately the R4 call for creating shared
3035 ** graphics contexts (XtGetGC) is rarely useful because most widgets need
3036 ** to be able to set and change clipping, and that makes the GC unshareable.
3038 ** This function allocates and returns a gc, using XtAllocateGC if possible,
3039 ** or XCreateGC on X11R4 systems where XtAllocateGC is not available.
3041 static GC
allocateGC(Widget w
, unsigned long valueMask
,
3042 unsigned long foreground
, unsigned long background
, Font font
,
3043 unsigned long dynamicMask
, unsigned long dontCareMask
)
3047 gcValues
.font
= font
;
3048 gcValues
.background
= background
;
3049 gcValues
.foreground
= foreground
;
3050 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4
3051 return XtAllocateGC(w
, 0, valueMask
, &gcValues
, dynamicMask
,
3054 return XCreateGC(XtDisplay(w
), RootWindowOfScreen(XtScreen(w
)),
3055 valueMask
, &gcValues
);
3060 ** Release a gc allocated with allocateGC above
3062 static void releaseGC(Widget w
, GC gc
)
3064 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4
3067 XFreeGC(XtDisplay(w
), gc
);
3072 ** resetClipRectangles sets the clipping rectangles for GCs which clip
3073 ** at the text boundary (as opposed to the window boundary). These GCs
3074 ** are shared such that the drawing styles are constant, but the clipping
3075 ** rectangles are allowed to change among different users of the GCs (the
3076 ** GCs were created with XtAllocGC). This routine resets them so the clipping
3077 ** rectangles are correct for this text display.
3079 static void resetClipRectangles(textDisp
*textD
)
3081 XRectangle clipRect
;
3082 Display
*display
= XtDisplay(textD
->w
);
3084 clipRect
.x
= textD
->left
;
3085 clipRect
.y
= textD
->top
;
3086 clipRect
.width
= textD
->width
;
3087 clipRect
.height
= textD
->height
- textD
->height
%
3088 (textD
->ascent
+ textD
->descent
);
3090 XSetClipRectangles(display
, textD
->gc
, 0, 0,
3091 &clipRect
, 1, Unsorted
);
3092 XSetClipRectangles(display
, textD
->selectGC
, 0, 0,
3093 &clipRect
, 1, Unsorted
);
3094 XSetClipRectangles(display
, textD
->highlightGC
, 0, 0,
3095 &clipRect
, 1, Unsorted
);
3096 XSetClipRectangles(display
, textD
->selectBGGC
, 0, 0,
3097 &clipRect
, 1, Unsorted
);
3098 XSetClipRectangles(display
, textD
->highlightBGGC
, 0, 0,
3099 &clipRect
, 1, Unsorted
);
3100 XSetClipRectangles(display
, textD
->styleGC
, 0, 0,
3101 &clipRect
, 1, Unsorted
);
3105 ** Return the length of a line (number of displayable characters) by examining
3106 ** entries in the line starts array rather than by scanning for newlines
3108 static int visLineLength(textDisp
*textD
, int visLineNum
)
3110 int nextLineStart
, lineStartPos
= textD
->lineStarts
[visLineNum
];
3112 if (lineStartPos
== -1)
3114 if (visLineNum
+1 >= textD
->nVisibleLines
)
3115 return textD
->lastChar
- lineStartPos
;
3116 nextLineStart
= textD
->lineStarts
[visLineNum
+1];
3117 if (nextLineStart
== -1)
3118 return textD
->lastChar
- lineStartPos
;
3119 if (wrapUsesCharacter(textD
, nextLineStart
-1))
3120 return nextLineStart
-1 - lineStartPos
;
3121 return nextLineStart
- lineStartPos
;
3125 ** When continuous wrap is on, and the user inserts or deletes characters,
3126 ** wrapping can happen before and beyond the changed position. This routine
3127 ** finds the extent of the changes, and counts the deleted and inserted lines
3128 ** over that range. It also attempts to minimize the size of the range to
3129 ** what has to be counted and re-displayed, so the results can be useful
3130 ** both for delimiting where the line starts need to be recalculated, and
3131 ** for deciding what part of the text to redisplay.
3133 static void findWrapRange(textDisp
*textD
, const char *deletedText
, int pos
,
3134 int nInserted
, int nDeleted
, int *modRangeStart
, int *modRangeEnd
,
3135 int *linesInserted
, int *linesDeleted
)
3137 int length
, retPos
, retLines
, retLineStart
, retLineEnd
;
3138 textBuffer
*deletedTextBuf
, *buf
= textD
->buffer
;
3139 int nVisLines
= textD
->nVisibleLines
;
3140 int *lineStarts
= textD
->lineStarts
;
3141 int countFrom
, countTo
, lineStart
, adjLineStart
, i
;
3142 int visLineNum
= 0, nLines
= 0;
3145 ** Determine where to begin searching: either the previous newline, or
3146 ** if possible, limit to the start of the (original) previous displayed
3147 ** line, using information from the existing line starts array
3149 if (pos
>= textD
->firstChar
&& pos
<= textD
->lastChar
) {
3150 for (i
=nVisLines
-1; i
>0; i
--)
3151 if (lineStarts
[i
] != -1 && pos
>= lineStarts
[i
])
3154 countFrom
= lineStarts
[i
-1];
3157 countFrom
= BufStartOfLine(buf
, pos
);
3159 countFrom
= BufStartOfLine(buf
, pos
);
3163 ** Move forward through the (new) text one line at a time, counting
3164 ** displayed lines, and looking for either a real newline, or for the
3165 ** line starts to re-sync with the original line starts array
3167 lineStart
= countFrom
;
3168 *modRangeStart
= countFrom
;
3171 /* advance to the next line. If the line ended in a real newline
3172 or the end of the buffer, that's far enough */
3173 wrappedLineCounter(textD
, buf
, lineStart
, buf
->length
, 1, True
, 0,
3174 &retPos
, &retLines
, &retLineStart
, &retLineEnd
);
3175 if (retPos
>= buf
->length
) {
3176 countTo
= buf
->length
;
3177 *modRangeEnd
= countTo
;
3178 if (retPos
!= retLineEnd
)
3184 if (lineStart
> pos
+ nInserted
&&
3185 BufGetCharacter(buf
, lineStart
-1) == '\n') {
3186 countTo
= lineStart
;
3187 *modRangeEnd
= lineStart
;
3191 /* Don't try to resync in continuous wrap mode with non-fixed font
3192 sizes; it would result in a chicken-and-egg dependency between
3193 the calculations for the inserted and the deleted lines.
3194 If we're in that mode, the number of deleted lines is calculated in
3195 advance, without resynchronization, so we shouldn't resynchronize
3196 for the inserted lines either. */
3197 if (textD
->suppressResync
)
3200 /* check for synchronization with the original line starts array
3201 before pos, if so, the modified range can begin later */
3202 if (lineStart
<= pos
) {
3203 while (visLineNum
<nVisLines
&& lineStarts
[visLineNum
] < lineStart
)
3205 if (visLineNum
< nVisLines
&& lineStarts
[visLineNum
] == lineStart
) {
3206 countFrom
= lineStart
;
3208 if (visLineNum
+1 < nVisLines
&& lineStarts
[visLineNum
+1] != -1)
3209 *modRangeStart
= min(pos
, lineStarts
[visLineNum
+1]-1);
3211 *modRangeStart
= countFrom
;
3213 *modRangeStart
= min(*modRangeStart
, lineStart
-1);
3216 /* check for synchronization with the original line starts array
3217 after pos, if so, the modified range can end early */
3218 else if (lineStart
> pos
+ nInserted
) {
3219 adjLineStart
= lineStart
- nInserted
+ nDeleted
;
3220 while (visLineNum
<nVisLines
&& lineStarts
[visLineNum
]<adjLineStart
)
3222 if (visLineNum
< nVisLines
&& lineStarts
[visLineNum
] != -1 &&
3223 lineStarts
[visLineNum
] == adjLineStart
) {
3224 countTo
= TextDEndOfLine(textD
, lineStart
, True
);
3225 *modRangeEnd
= lineStart
;
3230 *linesInserted
= nLines
;
3233 /* Count deleted lines between countFrom and countTo as the text existed
3234 before the modification (that is, as if the text between pos and
3235 pos+nInserted were replaced by "deletedText"). This extra context is
3236 necessary because wrapping can occur outside of the modified region
3237 as a result of adding or deleting text in the region. This is done by
3238 creating a textBuffer containing the deleted text and the necessary
3239 additional context, and calling the wrappedLineCounter on it.
3241 NOTE: This must not be done in continuous wrap mode when the font
3242 width is not fixed. In that case, the calculation would try
3243 to access style information that is no longer available (deleted
3244 text), or out of date (updated highlighting), possibly leading
3245 to completely wrong calculations and/or even crashes eventually.
3246 (This is not theoretical; it really happened.)
3248 In that case, the calculation of the number of deleted lines
3249 has happened before the buffer was modified (only in that case,
3250 because resynchronization of the line starts is impossible
3251 in that case, which makes the whole calculation less efficient).
3253 if (textD
->suppressResync
) {
3254 *linesDeleted
= textD
->nLinesDeleted
;
3255 textD
->suppressResync
= 0;
3259 length
= (pos
-countFrom
) + nDeleted
+(countTo
-(pos
+nInserted
));
3260 deletedTextBuf
= BufCreatePreallocated(length
);
3261 if (pos
> countFrom
)
3262 BufCopyFromBuf(textD
->buffer
, deletedTextBuf
, countFrom
, pos
, 0);
3264 BufInsert(deletedTextBuf
, pos
-countFrom
, deletedText
);
3265 if (countTo
> pos
+nInserted
)
3266 BufCopyFromBuf(textD
->buffer
, deletedTextBuf
,
3267 pos
+nInserted
, countTo
, pos
-countFrom
+nDeleted
);
3268 /* Note that we need to take into account an offset for the style buffer:
3269 the deletedTextBuf can be out of sync with the style buffer. */
3270 wrappedLineCounter(textD
, deletedTextBuf
, 0, length
, INT_MAX
, True
,
3271 countFrom
, &retPos
, &retLines
, &retLineStart
, &retLineEnd
);
3272 BufFree(deletedTextBuf
);
3273 *linesDeleted
= retLines
;
3274 textD
->suppressResync
= 0;
3278 ** This is a stripped-down version of the findWrapRange() function above,
3279 ** intended to be used to calculate the number of "deleted" lines during
3280 ** a buffer modification. It is called _before_ the modification takes place.
3282 ** This function should only be called in continuous wrap mode with a
3283 ** non-fixed font width. In that case, it is impossible to calculate
3284 ** the number of deleted lines, because the necessary style information
3285 ** is no longer available _after_ the modification. In other cases, we
3286 ** can still perform the calculation afterwards (possibly even more
3289 static void measureDeletedLines(textDisp
*textD
, int pos
, int nDeleted
)
3291 int retPos
, retLines
, retLineStart
, retLineEnd
;
3292 textBuffer
*buf
= textD
->buffer
;
3293 int nVisLines
= textD
->nVisibleLines
;
3294 int *lineStarts
= textD
->lineStarts
;
3295 int countFrom
, lineStart
;
3298 ** Determine where to begin searching: either the previous newline, or
3299 ** if possible, limit to the start of the (original) previous displayed
3300 ** line, using information from the existing line starts array
3302 if (pos
>= textD
->firstChar
&& pos
<= textD
->lastChar
) {
3303 for (i
=nVisLines
-1; i
>0; i
--)
3304 if (lineStarts
[i
] != -1 && pos
>= lineStarts
[i
])
3307 countFrom
= lineStarts
[i
-1];
3309 countFrom
= BufStartOfLine(buf
, pos
);
3311 countFrom
= BufStartOfLine(buf
, pos
);
3314 ** Move forward through the (new) text one line at a time, counting
3315 ** displayed lines, and looking for either a real newline, or for the
3316 ** line starts to re-sync with the original line starts array
3318 lineStart
= countFrom
;
3320 /* advance to the next line. If the line ended in a real newline
3321 or the end of the buffer, that's far enough */
3322 wrappedLineCounter(textD
, buf
, lineStart
, buf
->length
, 1, True
, 0,
3323 &retPos
, &retLines
, &retLineStart
, &retLineEnd
);
3324 if (retPos
>= buf
->length
) {
3325 if (retPos
!= retLineEnd
)
3331 if (lineStart
> pos
+ nDeleted
&&
3332 BufGetCharacter(buf
, lineStart
-1) == '\n') {
3336 /* Unlike in the findWrapRange() function above, we don't try to
3337 resync with the line starts, because we don't know the length
3338 of the inserted text yet, nor the updated style information.
3340 Because of that, we also shouldn't resync with the line starts
3341 after the modification either, because we must perform the
3342 calculations for the deleted and inserted lines in the same way.
3344 This can result in some unnecessary recalculation and redrawing
3345 overhead, and therefore we should only use this two-phase mode
3346 of calculation when it's really needed (continuous wrap + variable
3349 textD
->nLinesDeleted
= nLines
;
3350 textD
->suppressResync
= 1;
3354 ** Count forward from startPos to either maxPos or maxLines (whichever is
3355 ** reached first), and return all relevant positions and line count.
3356 ** The provided textBuffer may differ from the actual text buffer of the
3357 ** widget. In that case it must be a (partial) copy of the actual text buffer
3358 ** and the styleBufOffset argument must indicate the starting position of the
3359 ** copy, to take into account the correct style information.
3363 ** retPos: Position where counting ended. When counting lines, the
3364 ** position returned is the start of the line "maxLines"
3365 ** lines beyond "startPos".
3366 ** retLines: Number of line breaks counted
3367 ** retLineStart: Start of the line where counting ended
3368 ** retLineEnd: End position of the last line traversed
3370 static void wrappedLineCounter(const textDisp
* textD
, const textBuffer
* buf
,
3371 const int startPos
, const int maxPos
, const int maxLines
,
3372 const Boolean startPosIsLineStart
, const int styleBufOffset
,
3373 int* retPos
, int* retLines
, int* retLineStart
, int* retLineEnd
)
3375 int lineStart
, newLineStart
= 0, b
, p
, colNum
, wrapMargin
;
3376 int maxWidth
, width
, countPixels
, i
, foundBreak
;
3377 int nLines
= 0, tabDist
= textD
->buffer
->tabDist
;
3379 char nullSubsChar
= textD
->buffer
->nullSubsChar
;
3381 /* If the font is fixed, or there's a wrap margin set, it's more efficient
3382 to measure in columns, than to count pixels. Determine if we can count
3383 in columns (countPixels == False) or must count pixels (countPixels ==
3384 True), and set the wrap target for either pixels or columns */
3385 if (textD
->fixedFontWidth
!= -1 || textD
->wrapMargin
!= 0) {
3386 countPixels
= False
;
3387 wrapMargin
= textD
->wrapMargin
!= 0 ? textD
->wrapMargin
:
3388 textD
->width
/ textD
->fixedFontWidth
;
3392 wrapMargin
= INT_MAX
;
3393 maxWidth
= textD
->width
;
3396 /* Find the start of the line if the start pos is not marked as a
3398 if (startPosIsLineStart
)
3399 lineStart
= startPos
;
3401 lineStart
= TextDStartOfLine(textD
, startPos
);
3404 ** Loop until position exceeds maxPos or line count exceeds maxLines.
3405 ** (actually, contines beyond maxPos to end of line containing maxPos,
3406 ** in case later characters cause a word wrap back before maxPos)
3410 for (p
=lineStart
; p
<buf
->length
; p
++) {
3411 c
= BufGetCharacter(buf
, p
);
3413 /* If the character was a newline, count the line and start over,
3414 otherwise, add it to the width and column counts */
3419 *retLineStart
= lineStart
;
3420 *retLineEnd
= maxPos
;
3424 if (nLines
>= maxLines
) {
3427 *retLineStart
= p
+ 1;
3435 colNum
+= BufCharWidth(c
, colNum
, tabDist
, nullSubsChar
);
3437 width
+= measurePropChar(textD
, c
, colNum
, p
+styleBufOffset
);
3440 /* If character exceeded wrap margin, find the break point
3442 if (colNum
> wrapMargin
|| width
> maxWidth
) {
3444 for (b
=p
; b
>=lineStart
; b
--) {
3445 c
= BufGetCharacter(buf
, b
);
3446 if (c
== '\t' || c
== ' ') {
3447 newLineStart
= b
+ 1;
3451 for (i
=b
+1; i
<p
+1; i
++) {
3452 width
+= measurePropChar(textD
,
3453 BufGetCharacter(buf
, i
), colNum
,
3458 colNum
= BufCountDispChars(buf
, b
+1, p
+1);
3463 if (!foundBreak
) { /* no whitespace, just break at margin */
3464 newLineStart
= max(p
, lineStart
+1);
3465 colNum
= BufCharWidth(c
, colNum
, tabDist
, nullSubsChar
);
3467 width
= measurePropChar(textD
, c
, colNum
, p
+styleBufOffset
);
3471 *retLines
= maxPos
< newLineStart
? nLines
: nLines
+ 1;
3472 *retLineStart
= maxPos
< newLineStart
? lineStart
:
3474 *retLineEnd
= maxPos
;
3478 if (nLines
>= maxLines
) {
3479 *retPos
= foundBreak
? b
+ 1 : max(p
, lineStart
+1);
3481 *retLineStart
= lineStart
;
3482 *retLineEnd
= foundBreak
? b
: p
;
3485 lineStart
= newLineStart
;
3489 /* reached end of buffer before reaching pos or line target */
3490 *retPos
= buf
->length
;
3492 *retLineStart
= lineStart
;
3493 *retLineEnd
= buf
->length
;
3497 ** Measure the width in pixels of a character "c" at a particular column
3498 ** "colNum" and buffer position "pos". This is for measuring characters in
3499 ** proportional or mixed-width highlighting fonts.
3501 ** A note about proportional and mixed-width fonts: the mixed width and
3502 ** proportional font code in nedit does not get much use in general editing,
3503 ** because nedit doesn't allow per-language-mode fonts, and editing programs
3504 ** in a proportional font is usually a bad idea, so very few users would
3505 ** choose a proportional font as a default. There are still probably mixed-
3506 ** width syntax highlighting cases where things don't redraw properly for
3507 ** insertion/deletion, though static display and wrapping and resizing
3508 ** should now be solid because they are now used for online help display.
3510 static int measurePropChar(const textDisp
* textD
, const char c
,
3511 const int colNum
, const int pos
)
3514 char expChar
[MAX_EXP_CHAR_LEN
];
3515 textBuffer
*styleBuf
= textD
->styleBuffer
;
3517 charLen
= BufExpandCharacter(c
, colNum
, expChar
,
3518 textD
->buffer
->tabDist
, textD
->buffer
->nullSubsChar
);
3519 if (styleBuf
== NULL
) {
3522 style
= (unsigned char)BufGetCharacter(styleBuf
, pos
);
3523 if (style
== textD
->unfinishedStyle
) {
3524 /* encountered "unfinished" style, trigger parsing */
3525 (textD
->unfinishedHighlightCB
)(textD
, pos
, textD
->highlightCBArg
);
3526 style
= (unsigned char)BufGetCharacter(styleBuf
, pos
);
3529 return stringWidth(textD
, expChar
, charLen
, style
);
3533 ** Finds both the end of the current line and the start of the next line. Why?
3534 ** In continuous wrap mode, if you need to know both, figuring out one from the
3535 ** other can be expensive or error prone. The problem comes when there's a
3536 ** trailing space or tab just before the end of the buffer. To translate an
3537 ** end of line value to or from the next lines start value, you need to know
3538 ** whether the trailing space or tab is being used as a line break or just a
3539 ** normal character, and to find that out would otherwise require counting all
3540 ** the way back to the beginning of the line.
3542 static void findLineEnd(textDisp
*textD
, int startPos
, int startPosIsLineStart
,
3543 int *lineEnd
, int *nextLineStart
)
3545 int retLines
, retLineStart
;
3547 /* if we're not wrapping use more efficient BufEndOfLine */
3548 if (!textD
->continuousWrap
) {
3549 *lineEnd
= BufEndOfLine(textD
->buffer
, startPos
);
3550 *nextLineStart
= min(textD
->buffer
->length
, *lineEnd
+ 1);
3554 /* use the wrapped line counter routine to count forward one line */
3555 wrappedLineCounter(textD
, textD
->buffer
, startPos
, textD
->buffer
->length
,
3556 1, startPosIsLineStart
, 0, nextLineStart
, &retLines
,
3557 &retLineStart
, lineEnd
);
3562 ** Line breaks in continuous wrap mode usually happen at newlines or
3563 ** whitespace. This line-terminating character is not included in line
3564 ** width measurements and has a special status as a non-visible character.
3565 ** However, lines with no whitespace are wrapped without the benefit of a
3566 ** line terminating character, and this distinction causes endless trouble
3567 ** with all of the text display code which was originally written without
3568 ** continuous wrap mode and always expects to wrap at a newline character.
3570 ** Given the position of the end of the line, as returned by TextDEndOfLine
3571 ** or BufEndOfLine, this returns true if there is a line terminating
3572 ** character, and false if there's not. On the last character in the
3573 ** buffer, this function can't tell for certain whether a trailing space was
3574 ** used as a wrap point, and just guesses that it wasn't. So if an exact
3575 ** accounting is necessary, don't use this function.
3577 static int wrapUsesCharacter(textDisp
*textD
, int lineEndPos
)
3581 if (!textD
->continuousWrap
|| lineEndPos
== textD
->buffer
->length
)
3584 c
= BufGetCharacter(textD
->buffer
, lineEndPos
);
3585 return c
== '\n' || ((c
== '\t' || c
== ' ') &&
3586 lineEndPos
+ 1 != textD
->buffer
->length
);
3590 ** Decide whether the user needs (or may need) a horizontal scroll bar,
3591 ** and manage or unmanage the scroll bar widget accordingly. The H.
3592 ** scroll bar is only hidden in continuous wrap mode when it's absolutely
3593 ** certain that the user will not need it: when wrapping is set
3594 ** to the window edge, or when the wrap margin is strictly less than
3595 ** the longest possible line.
3597 static void hideOrShowHScrollBar(textDisp
*textD
)
3599 if (textD
->continuousWrap
&& (textD
->wrapMargin
== 0 || textD
->wrapMargin
*
3600 textD
->fontStruct
->max_bounds
.width
< textD
->width
))
3601 XtUnmanageChild(textD
->hScrollBar
);
3603 XtManageChild(textD
->hScrollBar
);
3607 ** Return true if the selection "sel" is rectangular, and touches a
3608 ** buffer position withing "rangeStart" to "rangeEnd"
3610 static int rangeTouchesRectSel(selection
*sel
, int rangeStart
, int rangeEnd
)
3612 return sel
->selected
&& sel
->rectangular
&& sel
->end
>= rangeStart
&&
3613 sel
->start
<= rangeEnd
;
3617 ** Extend the range of a redraw request (from *start to *end) with additional
3618 ** redraw requests resulting from changes to the attached style buffer (which
3619 ** contains auxiliary information for coloring or styling text).
3621 static void extendRangeForStyleMods(textDisp
*textD
, int *start
, int *end
)
3623 selection
*sel
= &textD
->styleBuffer
->primary
;
3624 int extended
= False
;
3626 /* The peculiar protocol used here is that modifications to the style
3627 buffer are marked by selecting them with the buffer's primary selection.
3628 The style buffer is usually modified in response to a modify callback on
3629 the text buffer BEFORE textDisp.c's modify callback, so that it can keep
3630 the style buffer in step with the text buffer. The style-update
3631 callback can't just call for a redraw, because textDisp hasn't processed
3632 the original text changes yet. Anyhow, to minimize redrawing and to
3633 avoid the complexity of scheduling redraws later, this simple protocol
3634 tells the text display's buffer modify callback to extend it's redraw
3635 range to show the text color/and font changes as well. */
3636 if (sel
->selected
) {
3637 if (sel
->start
< *start
) {
3638 *start
= sel
->start
;
3641 if (sel
->end
> *end
) {
3647 /* If the selection was extended due to a style change, and some of the
3648 fonts don't match in spacing, extend redraw area to end of line to
3649 redraw characters exposed by possible font size changes */
3650 if (textD
->fixedFontWidth
== -1 && extended
)
3651 *end
= BufEndOfLine(textD
->buffer
, *end
) + 1;
3654 /********************** Backlight Functions ******************************/
3656 ** Allocate a read-only (shareable) colormap cell for a named color, from the
3657 ** the default colormap of the screen on which the widget (w) is displayed. If
3658 ** the colormap is full and there's no suitable substitute, print an error on
3659 ** stderr, and return the widget's background color as a backup.
3661 static Pixel
allocBGColor(Widget w
, char *colorName
, int *ok
)
3665 return AllocColor(w
, colorName
, &r
, &g
, &b
);
3668 static Pixel
getRangesetColor(textDisp
*textD
, int ind
, Pixel bground
)
3678 buf
= textD
->buffer
;
3679 tab
= buf
->rangesetTable
;
3681 valid
= RangesetTableGetColorValid(tab
, ind
, &color
);
3683 color_name
= RangesetTableGetColorName(tab
, ind
);
3685 color
= allocBGColor(textD
->w
, color_name
, &valid
);
3686 RangesetTableAssignColorPixel(tab
, ind
, color
, valid
);
3696 ** Read the background color class specification string in str, allocating the
3697 ** necessary colors, and allocating and setting up the character->class_no and
3698 ** class_no->pixel map arrays, returned via *pp_bgClass and *pp_bgClassPixel
3700 ** Note: the allocation of class numbers could be more intelligent: there can
3701 ** never be more than 256 of these (one per character); but I don't think
3702 ** there'll be a pressing need. I suppose the scanning of the specification
3703 ** could be better too, but then, who cares!
3705 void TextDSetupBGClasses(Widget w
, XmString str
, Pixel
**pp_bgClassPixel
,
3706 unsigned char **pp_bgClass
, Pixel bgPixelDefault
)
3708 unsigned char bgClass
[256];
3709 Pixel bgClassPixel
[256];
3712 char *s
= (char *)str
;
3716 Boolean is_good
= True
;
3718 XtFree((char *)*pp_bgClass
);
3719 XtFree((char *)*pp_bgClassPixel
);
3721 *pp_bgClassPixel
= NULL
;
3727 /* default for all chars is class number zero, for standard background */
3728 memset(bgClassPixel
, 0, sizeof bgClassPixel
);
3729 memset(bgClass
, 0, sizeof bgClass
);
3730 bgClassPixel
[0] = bgPixelDefault
;
3731 /* since class no == 0 in a "style" has no set bits in BACKLIGHT_MASK
3732 (see styleOfPos()), when drawString() is called for text with a
3733 backlight class no of zero, bgClassPixel[0] is never consulted, and
3734 the default background color is chosen. */
3736 /* The format of the class string s is:
3737 low[-high]{,low[-high]}:color{;low-high{,low[-high]}:color}
3739 32-255:#f0f0f0;1-31,127:red;128-159:orange;9-13:#e5e5e5
3740 where low and high represent a character range between ordinal
3741 ASCII values. Using strtol() allows automatic octal, dec and hex
3742 reading of low and high. The example format sets backgrounds as follows:
3743 char 1 - 8 colored red (control characters)
3744 char 9 - 13 colored #e5e5e5 (isspace() control characters)
3745 char 14 - 31 colored red (control characters)
3746 char 32 - 126 colored #f0f0f0
3747 char 127 colored red (delete character)
3748 char 128 - 159 colored orange ("shifted" control characters)
3749 char 160 - 255 colored #f0f0f0
3750 Notice that some of the later ranges overwrite the class values defined
3751 for earlier ones (eg the first clause, 32-255:#f0f0f0 sets the DEL
3752 character background color to #f0f0f0; it is then set to red by the
3753 clause 1-31,127:red). */
3755 while (s
&& class_no
< 255) {
3756 class_no
++; /* simple class alloc scheme */
3759 if ((semicol
= (char *)strchr(s
, ';'))) {
3760 *semicol
= '\0'; /* null-terminate low[-high]:color clause */
3764 /* loop over ranges before the color spec, assigning the characters
3765 in the ranges to the current class number */
3766 for (lo
= hi
= strtol(s
, &pos
, 0);
3768 lo
= hi
= strtol(pos
+ 1, &pos
, 0)) {
3769 if (pos
&& *pos
== '-')
3770 hi
= strtol(pos
+ 1, &pos
, 0); /* get end of range */
3771 is_good
= (pos
&& 0 <= lo
&& lo
<= hi
&& hi
<= 255);
3774 bgClass
[lo
++] = (unsigned char)class_no
;
3778 if ((is_good
= (is_good
&& *pos
== ':'))) {
3779 is_good
= (*pos
++ != '\0'); /* pos now points to color */
3780 bgClassPixel
[class_no
] = allocBGColor(w
, pos
, &dummy
);
3783 /* complain? this class spec clause (in string s) was faulty */
3786 /* end of loop iterator clauses */
3788 *semicol
= ';'; /* un-null-terminate low[-high]:color clause */
3789 s
= semicol
+ was_semicol
;
3792 /* when we get here, we've set up our class table and class-to-pixel table
3793 in local variables: now put them into the "real thing" */
3794 class_no
++; /* bigger than all valid class_nos */
3795 *pp_bgClass
= (unsigned char *)XtMalloc(256);
3796 *pp_bgClassPixel
= (Pixel
*)XtMalloc(class_no
* sizeof (Pixel
));
3797 if (!*pp_bgClass
|| !*pp_bgClassPixel
) {
3798 XtFree((char *)*pp_bgClass
);
3799 XtFree((char *)*pp_bgClassPixel
);
3802 memcpy(*pp_bgClass
, bgClass
, 256);
3803 memcpy(*pp_bgClassPixel
, bgClassPixel
, class_no
* sizeof (Pixel
));