1 //
2 // "$Id: Fl_Text_Display.cxx 6105 2008-04-21 21:03:22Z matt $"
3 //
4 // Copyright 2001-2006 by Bill Spitzak and others.
5 // Original code Copyright Mark Edel. Permission to distribute under
6 // the LGPL for the FLTK library granted by Mark Edel.
7 //
8 // This library is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Library General Public
10 // License as published by the Free Software Foundation; either
11 // version 2 of the License, or (at your option) any later version.
12 //
13 // This library is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // Library General Public License for more details.
17 //
18 // You should have received a copy of the GNU Library General Public
19 // License along with this library; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
21 // USA.
22 //
23 // Please report all bugs and problems on the following page:
24 //
25 // http://www.fltk.org/str.php
26 //
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include "flstring.h"
31 #include <limits.h>
32 #include <ctype.h>
33 #include <FL/Fl.H>
34 #include <FL/Fl_Text_Buffer.H>
35 #include <FL/Fl_Text_Display.H>
36 #include <FL/Fl_Window.H>
37
38 #undef min
39 #undef max
40
41 // Text area margins. Left & right margins should be at least 3 so that
42 // there is some room for the overhanging parts of the cursor!
43 #define TOP_MARGIN 1
44 #define BOTTOM_MARGIN 1
45 #define LEFT_MARGIN 3
46 #define RIGHT_MARGIN 3
47
48 #define NO_HINT -1
49
50 /* Masks for text drawing methods. These are or'd together to form an
51 integer which describes what drawing calls to use to draw a string */
52 #define FILL_MASK 0x0100
53 #define SECONDARY_MASK 0x0200
54 #define PRIMARY_MASK 0x0400
55 #define HIGHLIGHT_MASK 0x0800
56 #define BG_ONLY_MASK 0x1000
57 #define TEXT_ONLY_MASK 0x2000
58 #define STYLE_LOOKUP_MASK 0xff
59
60 /* Maximum displayable line length (how many characters will fit across the
61 widest window). This amount of memory is temporarily allocated from the
62 stack in the draw_vline() method for drawing strings */
63 #define MAX_DISP_LINE_LEN 1000
64
65 static int max( int i1, int i2 );
66 static int min( int i1, int i2 );
67 static int countlines( const char *string );
68
69 /* The variables below are used in a timer event to allow smooth
70 scrolling of the text area when the pointer has left the area. */
71 static int scroll_direction = 0;
72 static int scroll_amount = 0;
73 static int scroll_y = 0;
74 static int scroll_x = 0;
75
76 // CET - FIXME
77 #define TMPFONTWIDTH 6
78
Fl_Text_Display(int X,int Y,int W,int H,const char * l)79 Fl_Text_Display::Fl_Text_Display(int X, int Y, int W, int H, const char* l)
80 : Fl_Group(X, Y, W, H, l) {
81 int i;
82
83 mMaxsize = 0;
84 damage_range1_start = damage_range1_end = -1;
85 damage_range2_start = damage_range2_end = -1;
86 dragPos = dragType = dragging = 0;
87 display_insert_position_hint = 0;
88
89 color(FL_BACKGROUND2_COLOR, FL_SELECTION_COLOR);
90 box(FL_DOWN_FRAME);
91 textsize((uchar)FL_NORMAL_SIZE);
92 textcolor(FL_FOREGROUND_COLOR);
93 textfont(FL_HELVETICA);
94
95 text_area.x = 0;
96 text_area.y = 0;
97 text_area.w = 0;
98 text_area.h = 0;
99
100 mVScrollBar = new Fl_Scrollbar(0,0,1,1);
101 mVScrollBar->callback((Fl_Callback*)v_scrollbar_cb, this);
102 mHScrollBar = new Fl_Scrollbar(0,0,1,1);
103 mHScrollBar->callback((Fl_Callback*)h_scrollbar_cb, this);
104 mHScrollBar->type(FL_HORIZONTAL);
105
106 end();
107
108 scrollbar_width(Fl::scrollbar_size());
109 scrollbar_align(FL_ALIGN_BOTTOM_RIGHT);
110
111 mCursorOn = 0;
112 mCursorPos = 0;
113 mCursorOldY = -100;
114 mCursorToHint = NO_HINT;
115 mCursorStyle = NORMAL_CURSOR;
116 mCursorPreferredCol = -1;
117 mBuffer = 0;
118 mFirstChar = 0;
119 mLastChar = 0;
120 mNBufferLines = 0;
121 mTopLineNum = mTopLineNumHint = 1;
122 mAbsTopLineNum = 1;
123 mNeedAbsTopLineNum = 0;
124 mHorizOffset = mHorizOffsetHint = 0;
125
126 mCursor_color = FL_FOREGROUND_COLOR;
127
128 mFixedFontWidth = -1;
129 mStyleBuffer = 0;
130 mStyleTable = 0;
131 mNStyles = 0;
132 mNVisibleLines = 1;
133 mLineStarts = new int[mNVisibleLines];
134 mLineStarts[0] = 0;
135 for (i=1; i<mNVisibleLines; i++)
136 mLineStarts[i] = -1;
137 mSuppressResync = 0;
138 mNLinesDeleted = 0;
139 mModifyingTabDistance = 0;
140
141 mUnfinishedStyle = 0;
142 mUnfinishedHighlightCB = 0;
143 mHighlightCBArg = 0;
144
145 mLineNumLeft = mLineNumWidth = 0;
146 mContinuousWrap = 0;
147 mWrapMargin = 0;
148 mSuppressResync = mNLinesDeleted = mModifyingTabDistance = 0;
149 }
150
151 /*
152 ** Free a text display and release its associated memory. Note, the text
153 ** BUFFER that the text display displays is a separate entity and is not
154 ** freed, nor are the style buffer or style table.
155 */
~Fl_Text_Display()156 Fl_Text_Display::~Fl_Text_Display() {
157 if (scroll_direction) {
158 Fl::remove_timeout(scroll_timer_cb, this);
159 scroll_direction = 0;
160 }
161 if (mBuffer) {
162 mBuffer->remove_modify_callback(buffer_modified_cb, this);
163 mBuffer->remove_predelete_callback(buffer_predelete_cb, this);
164 }
165 if (mLineStarts) delete[] mLineStarts;
166 }
167
168 /*
169 ** Attach a text buffer to display, replacing the current buffer (if any)
170 */
buffer(Fl_Text_Buffer * buf)171 void Fl_Text_Display::buffer( Fl_Text_Buffer *buf ) {
172 /* If the text display is already displaying a buffer, clear it off
173 of the display and remove our callback from it */
174 if ( buf == mBuffer) return;
175 if ( mBuffer != 0 ) {
176 buffer_modified_cb( 0, 0, mBuffer->length(), 0, 0, this );
177 mNBufferLines = 0;
178 mBuffer->remove_modify_callback( buffer_modified_cb, this );
179 mBuffer->remove_predelete_callback( buffer_predelete_cb, this );
180 }
181
182 /* Add the buffer to the display, and attach a callback to the buffer for
183 receiving modification information when the buffer contents change */
184 mBuffer = buf;
185 if (mBuffer) {
186 mBuffer->add_modify_callback( buffer_modified_cb, this );
187 mBuffer->add_predelete_callback( buffer_predelete_cb, this );
188
189 /* Update the display */
190 buffer_modified_cb( 0, buf->length(), 0, 0, 0, this );
191 }
192
193 /* Resize the widget to update the screen... */
194 resize(x(), y(), w(), h());
195 }
196
197 /*
198 ** Attach (or remove) highlight information in text display and redisplay.
199 ** Highlighting information consists of a style buffer which parallels the
200 ** normal text buffer, but codes font and color information for the display;
201 ** a style table which translates style buffer codes (indexed by buffer
202 ** character - 'A') into fonts and colors; and a callback mechanism for
203 ** as-needed highlighting, triggered by a style buffer entry of
204 ** "unfinishedStyle". Style buffer can trigger additional redisplay during
205 ** a normal buffer modification if the buffer contains a primary Fl_Text_Selection
206 ** (see extendRangeForStyleMods for more information on this protocol).
207 **
208 ** Style buffers, tables and their associated memory are managed by the caller.
209 */
210 void
highlight_data(Fl_Text_Buffer * styleBuffer,const Style_Table_Entry * styleTable,int nStyles,char unfinishedStyle,Unfinished_Style_Cb unfinishedHighlightCB,void * cbArg)211 Fl_Text_Display::highlight_data(Fl_Text_Buffer *styleBuffer,
212 const Style_Table_Entry *styleTable,
213 int nStyles, char unfinishedStyle,
214 Unfinished_Style_Cb unfinishedHighlightCB,
215 void *cbArg ) {
216 mStyleBuffer = styleBuffer;
217 mStyleTable = styleTable;
218 mNStyles = nStyles;
219 mUnfinishedStyle = unfinishedStyle;
220 mUnfinishedHighlightCB = unfinishedHighlightCB;
221 mHighlightCBArg = cbArg;
222
223 mStyleBuffer->canUndo(0);
224 #if 0
225 // FIXME: this is in nedit code -- is it needed?
226 /* Call TextDSetFont to combine font information from style table and
227 primary font, adjust font-related parameters, and then redisplay */
228 TextDSetFont(textD, textD->fontStruct);
229 #endif
230 damage(FL_DAMAGE_EXPOSE);
231 }
232
233 #if 0
234 // FIXME: this is in nedit code -- is it needed?
235 /*
236 ** Change the (non highlight) font
237 */
238 void TextDSetFont(textDisp *textD, XFontStruct *fontStruct) {
239 Display *display = XtDisplay(textD->w);
240 int i, maxAscent = fontStruct->ascent, maxDescent = fontStruct->descent;
241 int width, height, fontWidth;
242 Pixel bgPixel, fgPixel, selectFGPixel, selectBGPixel;
243 Pixel highlightFGPixel, highlightBGPixel;
244 XGCValues values;
245 XFontStruct *styleFont;
246
247 /* If font size changes, cursor will be redrawn in a new position */
248 blankCursorProtrusions(textD);
249
250 /* If there is a (syntax highlighting) style table in use, find the new
251 maximum font height for this text display */
252 for (i=0; i<textD->nStyles; i++) {
253 styleFont = textD->styleTable[i].font;
254 if (styleFont != NULL && styleFont->ascent > maxAscent)
255 maxAscent = styleFont->ascent;
256 if (styleFont != NULL && styleFont->descent > maxDescent)
257 maxDescent = styleFont->descent;
258 }
259 textD->ascent = maxAscent;
260 textD->descent = maxDescent;
261
262 /* If all of the current fonts are fixed and match in width, compute */
263 fontWidth = fontStruct->max_bounds.width;
264 if (fontWidth != fontStruct->min_bounds.width)
265 fontWidth = -1;
266 else {
267 for (i=0; i<textD->nStyles; i++) {
268 styleFont = textD->styleTable[i].font;
269 if (styleFont != NULL && (styleFont->max_bounds.width != fontWidth ||
270 styleFont->max_bounds.width != styleFont->min_bounds.width))
271 fontWidth = -1;
272 }
273 }
274 textD->fixedFontWidth = fontWidth;
275
276 /* Don't let the height dip below one line, or bad things can happen */
277 if (textD->height < maxAscent + maxDescent)
278 textD->height = maxAscent + maxDescent;
279
280 /* Change the font. In most cases, this means re-allocating the
281 affected GCs (they are shared with other widgets, and if the primary
282 font changes, must be re-allocated to change it). Unfortunately,
283 this requres recovering all of the colors from the existing GCs */
284 textD->fontStruct = fontStruct;
285 XGetGCValues(display, textD->gc, GCForeground|GCBackground, &values);
286 fgPixel = values.foreground;
287 bgPixel = values.background;
288 XGetGCValues(display, textD->selectGC, GCForeground|GCBackground, &values);
289 selectFGPixel = values.foreground;
290 selectBGPixel = values.background;
291 XGetGCValues(display, textD->highlightGC,GCForeground|GCBackground,&values);
292 highlightFGPixel = values.foreground;
293 highlightBGPixel = values.background;
294 releaseGC(textD->w, textD->gc);
295 releaseGC(textD->w, textD->selectGC);
296 releaseGC(textD->w, textD->highlightGC);
297 releaseGC(textD->w, textD->selectBGGC);
298 releaseGC(textD->w, textD->highlightBGGC);
299 if (textD->lineNumGC != NULL)
300 releaseGC(textD->w, textD->lineNumGC);
301 textD->lineNumGC = NULL;
302 allocateFixedFontGCs(textD, fontStruct, bgPixel, fgPixel, selectFGPixel,
303 selectBGPixel, highlightFGPixel, highlightBGPixel);
304 XSetFont(display, textD->styleGC, fontStruct->fid);
305
306 /* Do a full resize to force recalculation of font related parameters */
307 width = textD->width;
308 height = textD->height;
309 textD->width = textD->height = 0;
310 TextDResize(textD, width, height);
311
312 /* Redisplay */
313 TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
314 textD->height);
315
316 /* Clean up line number area in case spacing has changed */
317 draw_line_numbers(textD, True);
318 }
319
320 int TextDMinFontWidth(textDisp *textD, Boolean considerStyles) {
321 int fontWidth = textD->fontStruct->max_bounds.width;
322 int i;
323
324 if (considerStyles) {
325 for (i = 0; i < textD->nStyles; ++i) {
326 int thisWidth = (textD->styleTable[i].font)->min_bounds.width;
327 if (thisWidth < fontWidth) {
328 fontWidth = thisWidth;
329 }
330 }
331 }
332 return(fontWidth);
333 }
334
335 int TextDMaxFontWidth(textDisp *textD, Boolean considerStyles) {
336 int fontWidth = textD->fontStruct->max_bounds.width;
337 int i;
338
339 if (considerStyles) {
340 for (i = 0; i < textD->nStyles; ++i) {
341 int thisWidth = (textD->styleTable[i].font)->max_bounds.width;
342 if (thisWidth > fontWidth) {
343 fontWidth = thisWidth;
344 }
345 }
346 }
347 return(fontWidth);
348 }
349 #endif
350
longest_vline()351 int Fl_Text_Display::longest_vline() {
352 int longest = 0;
353 for (int i = 0; i < mNVisibleLines; i++)
354 longest = max(longest, measure_vline(i));
355 return longest;
356 }
357
358 /*
359 ** Change the size of the displayed text area
360 */
resize(int X,int Y,int W,int H)361 void Fl_Text_Display::resize(int X, int Y, int W, int H) {
362 #ifdef DEBUG
363 printf("Fl_Text_Display::resize(X=%d, Y=%d, W=%d, H=%d)\n", X, Y, W, H);
364 #endif // DEBUG
365 const int oldWidth = w();
366 #ifdef DEBUG
367 printf(" oldWidth=%d, mContinuousWrap=%d, mWrapMargin=%d\n", oldWidth,
368 mContinuousWrap, mWrapMargin);
369 #endif // DEBUG
370 Fl_Widget::resize(X,Y,W,H);
371 if (!buffer()) return;
372 X += Fl::box_dx(box());
373 Y += Fl::box_dy(box());
374 W -= Fl::box_dw(box());
375 H -= Fl::box_dh(box());
376
377 text_area.x = X+LEFT_MARGIN;
378 text_area.y = Y+BOTTOM_MARGIN;
379 text_area.w = W-LEFT_MARGIN-RIGHT_MARGIN;
380 text_area.h = H-TOP_MARGIN-BOTTOM_MARGIN;
381 int i;
382
383 /* Find the new maximum font height for this text display */
384 for (i = 0, mMaxsize = fl_height(textfont(), textsize()); i < mNStyles; i++)
385 mMaxsize = max(mMaxsize, fl_height(mStyleTable[i].font, mStyleTable[i].size));
386
387 // did we have scrollbars initially?
388 int hscrollbarvisible = mHScrollBar->visible();
389 int vscrollbarvisible = mVScrollBar->visible();
390
391 // try without scrollbars first
392 mVScrollBar->clear_visible();
393 mHScrollBar->clear_visible();
394
395 for (int again = 1; again;) {
396 again = 0;
397 /* In continuous wrap mode, a change in width affects the total number of
398 lines in the buffer, and can leave the top line number incorrect, and
399 the top character no longer pointing at a valid line start */
400 if (mContinuousWrap && !mWrapMargin && W!=oldWidth) {
401 int oldFirstChar = mFirstChar;
402 mNBufferLines = count_lines(0, buffer()->length(), true);
403 mFirstChar = line_start(mFirstChar);
404 mTopLineNum = count_lines(0, mFirstChar, true)+1;
405 absolute_top_line_number(oldFirstChar);
406
407 #ifdef DEBUG
408 printf(" mNBufferLines=%d\n", mNBufferLines);
409 #endif // DEBUG
410 }
411
412 /* reallocate and update the line starts array, which may have changed
413 size and / or contents. */
414 int nvlines = (text_area.h + mMaxsize - 1) / mMaxsize;
415 if (nvlines < 1) nvlines = 1;
416 if (mNVisibleLines != nvlines) {
417 mNVisibleLines = nvlines;
418 if (mLineStarts) delete[] mLineStarts;
419 mLineStarts = new int [mNVisibleLines];
420 }
421
422 calc_line_starts(0, mNVisibleLines);
423 calc_last_char();
424
425 // figure the scrollbars
426 if (scrollbar_width()) {
427 /* Decide if the vertical scroll bar needs to be visible */
428 if (scrollbar_align() & (FL_ALIGN_LEFT|FL_ALIGN_RIGHT) &&
429 mNBufferLines >= mNVisibleLines - 1)
430 {
431 mVScrollBar->set_visible();
432 if (scrollbar_align() & FL_ALIGN_LEFT) {
433 text_area.x = X+scrollbar_width()+LEFT_MARGIN;
434 text_area.w = W-scrollbar_width()-LEFT_MARGIN-RIGHT_MARGIN;
435 mVScrollBar->resize(X, text_area.y-TOP_MARGIN, scrollbar_width(),
436 text_area.h+TOP_MARGIN+BOTTOM_MARGIN);
437 } else {
438 text_area.x = X+LEFT_MARGIN;
439 text_area.w = W-scrollbar_width()-LEFT_MARGIN-RIGHT_MARGIN;
440 mVScrollBar->resize(X+W-scrollbar_width(), text_area.y-TOP_MARGIN,
441 scrollbar_width(), text_area.h+TOP_MARGIN+BOTTOM_MARGIN);
442 }
443 }
444
445 /*
446 Decide if the horizontal scroll bar needs to be visible. If there
447 is a vertical scrollbar, a horizontal is always created too. This
448 is because the alternatives are unatractive:
449 * Dynamically creating a horizontal scrollbar based on the currently
450 visible lines is what the original nedit does, but it always wastes
451 space for the scrollbar even when it's not used. Since the FLTK
452 widget dynamically allocates the space for the scrollbar and
453 rearranges the widget to make room for it, this would create a very
454 visually displeasing "bounce" effect when the vertical scrollbar is
455 dragged. Trust me, I tried it and it looks really bad.
456 * The other alternative would be to keep track of what the longest
457 line in the entire buffer is and base the scrollbar on that. I
458 didn't do this because I didn't see any easy way to do that using
459 the nedit code and this could involve a lengthy calculation for
460 large buffers. If an efficient and non-costly way of doing this
461 can be found, this might be a way to go.
462 */
463 /* WAS: Suggestion: Try turning the horizontal scrollbar on when
464 you first see a line that is too wide in the window, but then
465 don't turn it off (ie mix both of your solutions). */
466 if (scrollbar_align() & (FL_ALIGN_TOP|FL_ALIGN_BOTTOM) &&
467 (mVScrollBar->visible() || longest_vline() > text_area.w))
468 {
469 if (!mHScrollBar->visible()) {
470 mHScrollBar->set_visible();
471 again = 1; // loop again to see if we now need vert. & recalc sizes
472 }
473 if (scrollbar_align() & FL_ALIGN_TOP) {
474 text_area.y = Y + scrollbar_width()+TOP_MARGIN;
475 text_area.h = H - scrollbar_width()-TOP_MARGIN-BOTTOM_MARGIN;
476 mHScrollBar->resize(text_area.x-LEFT_MARGIN, Y,
477 text_area.w+LEFT_MARGIN+RIGHT_MARGIN, scrollbar_width());
478 } else {
479 text_area.y = Y+TOP_MARGIN;
480 text_area.h = H - scrollbar_width()-TOP_MARGIN-BOTTOM_MARGIN;
481 mHScrollBar->resize(text_area.x-LEFT_MARGIN, Y+H-scrollbar_width(),
482 text_area.w+LEFT_MARGIN+RIGHT_MARGIN, scrollbar_width());
483 }
484 }
485 }
486 }
487
488 // user request to change viewport
489 if (mTopLineNumHint != mTopLineNum || mHorizOffsetHint != mHorizOffset)
490 scroll_(mTopLineNumHint, mHorizOffsetHint);
491
492 // everything will fit in the viewport
493 if (mNBufferLines < mNVisibleLines || mBuffer == NULL || mBuffer->length() == 0)
494 scroll_(1, mHorizOffset);
495 /* if empty lines become visible, there may be an opportunity to
496 display more text by scrolling down */
497 else while (mLineStarts[mNVisibleLines-2] == -1)
498 scroll_(mTopLineNum-1, mHorizOffset);
499
500 // user request to display insert position
501 if (display_insert_position_hint)
502 display_insert();
503
504 // in case horizontal offset is now greater than longest line
505 int maxhoffset = max(0, longest_vline()-text_area.w);
506 if (mHorizOffset > maxhoffset)
507 scroll_(mTopLineNumHint, maxhoffset);
508
509 mTopLineNumHint = mTopLineNum;
510 mHorizOffsetHint = mHorizOffset;
511 display_insert_position_hint = 0;
512
513 if (mContinuousWrap ||
514 hscrollbarvisible != mHScrollBar->visible() ||
515 vscrollbarvisible != mVScrollBar->visible())
516 redraw();
517
518 update_v_scrollbar();
519 update_h_scrollbar();
520 }
521
522 /*
523 ** Refresh a rectangle of the text display. left and top are in coordinates of
524 ** the text drawing window
525 */
draw_text(int left,int top,int width,int height)526 void Fl_Text_Display::draw_text( int left, int top, int width, int height ) {
527 int fontHeight, firstLine, lastLine, line;
528
529 /* find the line number range of the display */
530 fontHeight = mMaxsize ? mMaxsize : textsize_;
531 firstLine = ( top - text_area.y - fontHeight + 1 ) / fontHeight;
532 lastLine = ( top + height - text_area.y ) / fontHeight + 1;
533
534 fl_push_clip( left, top, width, height );
535
536 /* draw the lines */
537 for ( line = firstLine; line <= lastLine; line++ )
538 draw_vline( line, left, left + width, 0, INT_MAX );
539
540 /* draw the line numbers if exposed area includes them */
541 if (mLineNumWidth != 0 && left <= mLineNumLeft + mLineNumWidth)
542 draw_line_numbers(false);
543
544 fl_pop_clip();
545 }
546
redisplay_range(int startpos,int endpos)547 void Fl_Text_Display::redisplay_range(int startpos, int endpos) {
548 if (damage_range1_start == -1 && damage_range1_end == -1) {
549 damage_range1_start = startpos;
550 damage_range1_end = endpos;
551 } else if ((startpos >= damage_range1_start && startpos <= damage_range1_end) ||
552 (endpos >= damage_range1_start && endpos <= damage_range1_end)) {
553 damage_range1_start = min(damage_range1_start, startpos);
554 damage_range1_end = max(damage_range1_end, endpos);
555 } else if (damage_range2_start == -1 && damage_range2_end == -1) {
556 damage_range2_start = startpos;
557 damage_range2_end = endpos;
558 } else {
559 damage_range2_start = min(damage_range2_start, startpos);
560 damage_range2_end = max(damage_range2_end, endpos);
561 }
562 damage(FL_DAMAGE_SCROLL);
563 }
564 /*
565 ** Refresh all of the text between buffer positions "start" and "end"
566 ** not including the character at the position "end".
567 ** If end points beyond the end of the buffer, refresh the whole display
568 ** after pos, including blank lines which are not technically part of
569 ** any range of characters.
570 */
draw_range(int startpos,int endpos)571 void Fl_Text_Display::draw_range(int startpos, int endpos) {
572 int i, startLine, lastLine, startIndex, endIndex;
573
574 /* If the range is outside of the displayed text, just return */
575 if ( endpos < mFirstChar || ( startpos > mLastChar &&
576 !empty_vlines() ) ) return;
577
578 /* Clean up the starting and ending values */
579 if ( startpos < 0 ) startpos = 0;
580 if ( startpos > mBuffer->length() ) startpos = mBuffer->length();
581 if ( endpos < 0 ) endpos = 0;
582 if ( endpos > mBuffer->length() ) endpos = mBuffer->length();
583
584 /* Get the starting and ending lines */
585 if ( startpos < mFirstChar )
586 startpos = mFirstChar;
587 if ( !position_to_line( startpos, &startLine ) )
588 startLine = mNVisibleLines - 1;
589 if ( endpos >= mLastChar ) {
590 lastLine = mNVisibleLines - 1;
591 } else {
592 if ( !position_to_line( endpos, &lastLine ) ) {
593 /* shouldn't happen */
594 lastLine = mNVisibleLines - 1;
595 }
596 }
597
598 /* Get the starting and ending positions within the lines */
599 startIndex = mLineStarts[ startLine ] == -1 ? 0 :
600 startpos - mLineStarts[ startLine ];
601 if ( endpos >= mLastChar )
602 endIndex = INT_MAX;
603 else if ( mLineStarts[ lastLine ] == -1 )
604 endIndex = 0;
605 else
606 endIndex = endpos - mLineStarts[ lastLine ];
607
608 /* If the starting and ending lines are the same, redisplay the single
609 line between "start" and "end" */
610 if ( startLine == lastLine ) {
611 draw_vline( startLine, 0, INT_MAX, startIndex, endIndex );
612 return;
613 }
614
615 /* Redisplay the first line from "start" */
616 draw_vline( startLine, 0, INT_MAX, startIndex, INT_MAX );
617
618 /* Redisplay the lines in between at their full width */
619 for ( i = startLine + 1; i < lastLine; i++ )
620 draw_vline( i, 0, INT_MAX, 0, INT_MAX );
621
622 /* Redisplay the last line to "end" */
623 draw_vline( lastLine, 0, INT_MAX, 0, endIndex );
624 }
625
626 /*
627 ** Set the position of the text insertion cursor for text display
628 */
insert_position(int newPos)629 void Fl_Text_Display::insert_position( int newPos ) {
630 /* make sure new position is ok, do nothing if it hasn't changed */
631 if ( newPos == mCursorPos )
632 return;
633 if ( newPos < 0 ) newPos = 0;
634 if ( newPos > mBuffer->length() ) newPos = mBuffer->length();
635
636 /* cursor movement cancels vertical cursor motion column */
637 mCursorPreferredCol = -1;
638
639 /* erase the cursor at it's previous position */
640 redisplay_range(mCursorPos - 1, mCursorPos + 1);
641
642 mCursorPos = newPos;
643
644 /* draw cursor at its new position */
645 redisplay_range(mCursorPos - 1, mCursorPos + 1);
646 }
647
show_cursor(int b)648 void Fl_Text_Display::show_cursor(int b) {
649 mCursorOn = b;
650 redisplay_range(mCursorPos - 1, mCursorPos + 1);
651 }
652
cursor_style(int style)653 void Fl_Text_Display::cursor_style(int style) {
654 mCursorStyle = style;
655 if (mCursorOn) show_cursor();
656 }
657
wrap_mode(int wrap,int wrapMargin)658 void Fl_Text_Display::wrap_mode(int wrap, int wrapMargin) {
659 mWrapMargin = wrapMargin;
660 mContinuousWrap = wrap;
661
662 if (buffer()) {
663 /* wrapping can change the total number of lines, re-count */
664 mNBufferLines = count_lines(0, buffer()->length(), true);
665
666 /* changing wrap margins or changing from wrapped mode to non-wrapped
667 can leave the character at the top no longer at a line start, and/or
668 change the line number */
669 mFirstChar = line_start(mFirstChar);
670 mTopLineNum = count_lines(0, mFirstChar, true) + 1;
671
672 reset_absolute_top_line_number();
673
674 /* update the line starts array */
675 calc_line_starts(0, mNVisibleLines);
676 calc_last_char();
677 } else {
678 // No buffer, so just clear the state info for later...
679 mNBufferLines = 0;
680 mFirstChar = 0;
681 mTopLineNum = 1;
682 mAbsTopLineNum = 0;
683 }
684
685 resize(x(), y(), w(), h());
686 }
687
688 /*
689 ** Insert "text" at the current cursor location. This has the same
690 ** effect as inserting the text into the buffer using BufInsert and
691 ** then moving the insert position after the newly inserted text, except
692 ** that it's optimized to do less redrawing.
693 */
insert(const char * text)694 void Fl_Text_Display::insert(const char* text) {
695 int pos = mCursorPos;
696
697 mCursorToHint = pos + strlen( text );
698 mBuffer->insert( pos, text );
699 mCursorToHint = NO_HINT;
700 }
701
702 /*
703 ** Insert "text" (which must not contain newlines), overstriking the current
704 ** cursor location.
705 */
overstrike(const char * text)706 void Fl_Text_Display::overstrike(const char* text) {
707 int startPos = mCursorPos;
708 Fl_Text_Buffer *buf = mBuffer;
709 int lineStart = buf->line_start( startPos );
710 int textLen = strlen( text );
711 int i, p, endPos, indent, startIndent, endIndent;
712 const char *c;
713 char ch, *paddedText = NULL;
714
715 /* determine how many displayed character positions are covered */
716 startIndent = mBuffer->count_displayed_characters( lineStart, startPos );
717 indent = startIndent;
718 for ( c = text; *c != '\0'; c++ )
719 indent += Fl_Text_Buffer::character_width( *c, indent, buf->tab_distance(), buf->null_substitution_character() );
720 endIndent = indent;
721
722 /* find which characters to remove, and if necessary generate additional
723 padding to make up for removed control characters at the end */
724 indent = startIndent;
725 for ( p = startPos; ; p++ ) {
726 if ( p == buf->length() )
727 break;
728 ch = buf->character( p );
729 if ( ch == '\n' )
730 break;
731 indent += Fl_Text_Buffer::character_width( ch, indent, buf->tab_distance(), buf->null_substitution_character() );
732 if ( indent == endIndent ) {
733 p++;
734 break;
735 } else if ( indent > endIndent ) {
736 if ( ch != '\t' ) {
737 p++;
738 paddedText = new char [ textLen + FL_TEXT_MAX_EXP_CHAR_LEN + 1 ];
739 strcpy( paddedText, text );
740 for ( i = 0; i < indent - endIndent; i++ )
741 paddedText[ textLen + i ] = ' ';
742 paddedText[ textLen + i ] = '\0';
743 }
744 break;
745 }
746 }
747 endPos = p;
748
749 mCursorToHint = startPos + textLen;
750 buf->replace( startPos, endPos, paddedText == NULL ? text : paddedText );
751 mCursorToHint = NO_HINT;
752 if ( paddedText != NULL )
753 delete [] paddedText;
754 }
755
756 /*
757 ** Translate a buffer text position to the XY location where the top left
758 ** of the cursor would be positioned to point to that character. Returns
759 ** 0 if the position is not displayed because it is VERTICALLY out
760 ** of view. If the position is horizontally out of view, returns the
761 ** X coordinate where the position would be if it were visible.
762 */
763
position_to_xy(int pos,int * X,int * Y)764 int Fl_Text_Display::position_to_xy( int pos, int* X, int* Y ) {
765 int charIndex, lineStartPos, fontHeight, lineLen;
766 int visLineNum, charLen, outIndex, xStep, charStyle;
767 char expandedChar[ FL_TEXT_MAX_EXP_CHAR_LEN ];
768 const char *lineStr;
769
770 // printf("position_to_xy(pos=%d, X=%p, Y=%p)\n", pos, X, Y);
771
772 /* If position is not displayed, return false */
773 if (pos < mFirstChar || (pos > mLastChar && !empty_vlines())) {
774 // printf(" returning 0\n"
775 // " mFirstChar=%d, mLastChar=%d, empty_vlines()=0\n",
776 // mFirstChar, mLastChar);
777 return 0;
778 }
779
780 /* Calculate Y coordinate */
781 if (!position_to_line(pos, &visLineNum)) {
782 // puts(" returning 0\n"
783 // " position_to_line()=0");
784 return 0;
785 }
786
787 if (visLineNum < 0 || visLineNum > mNBufferLines) {
788 // printf(" returning 0\n"
789 // " visLineNum=%d, mNBufferLines=%d\n",
790 // visLineNum, mNBufferLines);
791 return 0;
792 }
793
794 fontHeight = mMaxsize;
795 *Y = text_area.y + visLineNum * fontHeight;
796
797 /* Get the text, length, and buffer position of the line. If the position
798 is beyond the end of the buffer and should be at the first position on
799 the first empty line, don't try to get or scan the text */
800 lineStartPos = mLineStarts[visLineNum];
801 if ( lineStartPos == -1 ) {
802 *X = text_area.x - mHorizOffset;
803 return 1;
804 }
805 lineLen = vline_length( visLineNum );
806 lineStr = mBuffer->text_range( lineStartPos, lineStartPos + lineLen );
807
808 /* Step through character positions from the beginning of the line
809 to "pos" to calculate the X coordinate */
810 xStep = text_area.x - mHorizOffset;
811 outIndex = 0;
812 for ( charIndex = 0; charIndex < lineLen && charIndex < pos - lineStartPos; charIndex++ ) {
813 charLen = Fl_Text_Buffer::expand_character( lineStr[ charIndex ], outIndex, expandedChar,
814 mBuffer->tab_distance(), mBuffer->null_substitution_character() );
815 charStyle = position_style( lineStartPos, lineLen, charIndex,
816 outIndex );
817 xStep += string_width( expandedChar, charLen, charStyle );
818 outIndex += charLen;
819 }
820 *X = xStep;
821 free((char *)lineStr);
822 return 1;
823 }
824
825 /*
826 ** Find the line number of position "pos". Note: this only works for
827 ** displayed lines. If the line is not displayed, the function returns
828 ** 0 (without the mLineStarts array it could turn in to very long
829 ** calculation involving scanning large amounts of text in the buffer).
830 ** If continuous wrap mode is on, returns the absolute line number (as opposed
831 ** to the wrapped line number which is used for scrolling).
832 */
position_to_linecol(int pos,int * lineNum,int * column)833 int Fl_Text_Display::position_to_linecol( int pos, int* lineNum, int* column ) {
834 int retVal;
835
836 /* In continuous wrap mode, the absolute (non-wrapped) line count is
837 maintained separately, as needed. Only return it if we're actually
838 keeping track of it and pos is in the displayed text */
839 if (mContinuousWrap) {
840 if (!maintaining_absolute_top_line_number() ||
841 pos < mFirstChar || pos > mLastChar)
842 return 0;
843 *lineNum = mAbsTopLineNum + buffer()->count_lines(mFirstChar, pos);
844 *column
845 = buffer()->count_displayed_characters(buffer()->line_start(pos), pos);
846 return 1;
847 }
848
849 retVal = position_to_line( pos, lineNum );
850 if ( retVal ) {
851 *column = mBuffer->count_displayed_characters(
852 mLineStarts[ *lineNum ], pos );
853 *lineNum += mTopLineNum;
854 }
855 return retVal;
856 }
857
858 /*
859 ** Return 1 if position (X, Y) is inside of the primary Fl_Text_Selection
860 */
in_selection(int X,int Y)861 int Fl_Text_Display::in_selection( int X, int Y ) {
862 int row, column, pos = xy_to_position( X, Y, CHARACTER_POS );
863 Fl_Text_Buffer *buf = mBuffer;
864
865 xy_to_rowcol( X, Y, &row, &column, CHARACTER_POS );
866 if (range_touches_selection(buf->primary_selection(), mFirstChar, mLastChar))
867 column = wrapped_column(row, column);
868 return buf->primary_selection()->includes(pos, buf->line_start( pos ), column);
869 }
870
871 /*
872 ** Correct a column number based on an unconstrained position (as returned by
873 ** TextDXYToUnconstrainedPosition) to be relative to the last actual newline
874 ** in the buffer before the row and column position given, rather than the
875 ** last line start created by line wrapping. This is an adapter
876 ** for rectangular selections and code written before continuous wrap mode,
877 ** which thinks that the unconstrained column is the number of characters
878 ** from the last newline. Obviously this is time consuming, because it
879 ** invloves character re-counting.
880 */
wrapped_column(int row,int column)881 int Fl_Text_Display::wrapped_column(int row, int column) {
882 int lineStart, dispLineStart;
883
884 if (!mContinuousWrap || row < 0 || row > mNVisibleLines)
885 return column;
886 dispLineStart = mLineStarts[row];
887 if (dispLineStart == -1)
888 return column;
889 lineStart = buffer()->line_start(dispLineStart);
890 return column
891 + buffer()->count_displayed_characters(lineStart, dispLineStart);
892 }
893
894 /*
895 ** Correct a row number from an unconstrained position (as returned by
896 ** TextDXYToUnconstrainedPosition) to a straight number of newlines from the
897 ** top line of the display. Because rectangular selections are based on
898 ** newlines, rather than display wrapping, and anywhere a rectangular selection
899 ** needs a row, it needs it in terms of un-wrapped lines.
900 */
wrapped_row(int row)901 int Fl_Text_Display::wrapped_row(int row) {
902 if (!mContinuousWrap || row < 0 || row > mNVisibleLines)
903 return row;
904 return buffer()->count_lines(mFirstChar, mLineStarts[row]);
905 }
906
907 /*
908 ** Scroll the display to bring insertion cursor into view.
909 **
910 ** Note: it would be nice to be able to do this without counting lines twice
911 ** (scroll_() counts them too) and/or to count from the most efficient
912 ** starting point, but the efficiency of this routine is not as important to
913 ** the overall performance of the text display.
914 */
display_insert()915 void Fl_Text_Display::display_insert() {
916 int hOffset, topLine, X, Y;
917 hOffset = mHorizOffset;
918 topLine = mTopLineNum;
919
920 // FIXME: I don't understand this well enough to know if it is correct
921 // it is different than nedit 5.3
922 if (insert_position() < mFirstChar) {
923 topLine -= count_lines(insert_position(), mFirstChar, false);
924 } else if (mLineStarts[mNVisibleLines-2] != -1) {
925 int lastChar = line_end(mLineStarts[mNVisibleLines-2],true);
926 if (insert_position() >= lastChar)
927 topLine
928 += count_lines(lastChar - (wrap_uses_character(mLastChar) ? 0 : 1),
929 insert_position(), false);
930 }
931
932 /* Find the new setting for horizontal offset (this is a bit ungraceful).
933 If the line is visible, just use PositionToXY to get the position
934 to scroll to, otherwise, do the vertical scrolling first, then the
935 horizontal */
936 if (!position_to_xy( mCursorPos, &X, &Y )) {
937 scroll_(topLine, hOffset);
938 if (!position_to_xy( mCursorPos, &X, &Y )) {
939 #ifdef DEBUG
940 printf ("*** display_insert/position_to_xy # GIVE UP !\n"); fflush(stdout);
941 #endif // DEBUG
942 return; /* Give up, it's not worth it (but why does it fail?) */
943 }
944 }
945 if (X > text_area.x + text_area.w)
946 hOffset += X-(text_area.x + text_area.w);
947 else if (X < text_area.x)
948 hOffset += X-text_area.x;
949
950 /* Do the scroll */
951 if (topLine != mTopLineNum || hOffset != mHorizOffset)
952 scroll_(topLine, hOffset);
953 }
954
show_insert_position()955 void Fl_Text_Display::show_insert_position() {
956 display_insert_position_hint = 1;
957 resize(x(), y(), w(), h());
958 }
959
960 /*
961 ** Cursor movement functions
962 */
move_right()963 int Fl_Text_Display::move_right() {
964 if ( mCursorPos >= mBuffer->length() )
965 return 0;
966 insert_position( mCursorPos + 1 );
967 return 1;
968 }
969
move_left()970 int Fl_Text_Display::move_left() {
971 if ( mCursorPos <= 0 )
972 return 0;
973 insert_position( mCursorPos - 1 );
974 return 1;
975 }
976
move_up()977 int Fl_Text_Display::move_up() {
978 int lineStartPos, column, prevLineStartPos, newPos, visLineNum;
979
980 /* Find the position of the start of the line. Use the line starts array
981 if possible */
982 if ( position_to_line( mCursorPos, &visLineNum ) )
983 lineStartPos = mLineStarts[ visLineNum ];
984 else {
985 lineStartPos = line_start( mCursorPos );
986 visLineNum = -1;
987 }
988 if ( lineStartPos == 0 )
989 return 0;
990
991 /* Decide what column to move to, if there's a preferred column use that */
992 column = mCursorPreferredCol >= 0 ? mCursorPreferredCol :
993 mBuffer->count_displayed_characters( lineStartPos, mCursorPos );
994
995 /* count forward from the start of the previous line to reach the column */
996 if ( visLineNum != -1 && visLineNum != 0 )
997 prevLineStartPos = mLineStarts[ visLineNum - 1 ];
998 else
999 prevLineStartPos = rewind_lines( lineStartPos, 1 );
1000 newPos = mBuffer->skip_displayed_characters( prevLineStartPos, column );
1001 if (mContinuousWrap)
1002 newPos = min(newPos, line_end(prevLineStartPos, true));
1003
1004 /* move the cursor */
1005 insert_position( newPos );
1006
1007 /* if a preferred column wasn't aleady established, establish it */
1008 mCursorPreferredCol = column;
1009 return 1;
1010 }
1011
move_down()1012 int Fl_Text_Display::move_down() {
1013 int lineStartPos, column, nextLineStartPos, newPos, visLineNum;
1014
1015 if ( mCursorPos == mBuffer->length() )
1016 return 0;
1017 if ( position_to_line( mCursorPos, &visLineNum ) )
1018 lineStartPos = mLineStarts[ visLineNum ];
1019 else {
1020 lineStartPos = line_start( mCursorPos );
1021 visLineNum = -1;
1022 }
1023 column = mCursorPreferredCol >= 0 ? mCursorPreferredCol :
1024 mBuffer->count_displayed_characters( lineStartPos, mCursorPos );
1025 nextLineStartPos = skip_lines( lineStartPos, 1, true );
1026 newPos = mBuffer->skip_displayed_characters( nextLineStartPos, column );
1027 if (mContinuousWrap)
1028 newPos = min(newPos, line_end(nextLineStartPos, true));
1029
1030 insert_position( newPos );
1031 mCursorPreferredCol = column;
1032 return 1;
1033 }
1034
1035 /*
1036 ** Same as BufCountLines, but takes in to account wrapping if wrapping is
1037 ** turned on. If the caller knows that startPos is at a line start, it
1038 ** can pass "startPosIsLineStart" as True to make the call more efficient
1039 ** by avoiding the additional step of scanning back to the last newline.
1040 */
count_lines(int startPos,int endPos,bool startPosIsLineStart)1041 int Fl_Text_Display::count_lines(int startPos, int endPos,
1042 bool startPosIsLineStart) {
1043 int retLines, retPos, retLineStart, retLineEnd;
1044
1045 #ifdef DEBUG
1046 printf("Fl_Text_Display::count_lines(startPos=%d, endPos=%d, startPosIsLineStart=%d\n",
1047 startPos, endPos, startPosIsLineStart);
1048 #endif // DEBUG
1049
1050 /* If we're not wrapping use simple (and more efficient) BufCountLines */
1051 if (!mContinuousWrap)
1052 return buffer()->count_lines(startPos, endPos);
1053
1054 wrapped_line_counter(buffer(), startPos, endPos, INT_MAX,
1055 startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1056 &retLineEnd);
1057
1058 #ifdef DEBUG
1059 printf(" # after WLC: retPos=%d, retLines=%d, retLineStart=%d, retLineEnd=%d\n",
1060 retPos, retLines, retLineStart, retLineEnd);
1061 #endif // DEBUG
1062
1063 return retLines;
1064 }
1065
1066 /*
1067 ** Same as BufCountForwardNLines, but takes in to account line breaks when
1068 ** wrapping is turned on. If the caller knows that startPos is at a line start,
1069 ** it can pass "startPosIsLineStart" as True to make the call more efficient
1070 ** by avoiding the additional step of scanning back to the last newline.
1071 */
skip_lines(int startPos,int nLines,bool startPosIsLineStart)1072 int Fl_Text_Display::skip_lines(int startPos, int nLines,
1073 bool startPosIsLineStart) {
1074 int retLines, retPos, retLineStart, retLineEnd;
1075
1076 /* if we're not wrapping use more efficient BufCountForwardNLines */
1077 if (!mContinuousWrap)
1078 return buffer()->skip_lines(startPos, nLines);
1079
1080 /* wrappedLineCounter can't handle the 0 lines case */
1081 if (nLines == 0)
1082 return startPos;
1083
1084 /* use the common line counting routine to count forward */
1085 wrapped_line_counter(buffer(), startPos, buffer()->length(),
1086 nLines, startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1087 &retLineEnd);
1088 return retPos;
1089 }
1090
1091 /*
1092 ** Same as BufEndOfLine, but takes in to account line breaks when wrapping
1093 ** is turned on. If the caller knows that startPos is at a line start, it
1094 ** can pass "startPosIsLineStart" as True to make the call more efficient
1095 ** by avoiding the additional step of scanning back to the last newline.
1096 **
1097 ** Note that the definition of the end of a line is less clear when continuous
1098 ** wrap is on. With continuous wrap off, it's just a pointer to the newline
1099 ** that ends the line. When it's on, it's the character beyond the last
1100 ** DISPLAYABLE character on the line, where a whitespace character which has
1101 ** been "converted" to a newline for wrapping is not considered displayable.
1102 ** Also note that, a line can be wrapped at a non-whitespace character if the
1103 ** line had no whitespace. In this case, this routine returns a pointer to
1104 ** the start of the next line. This is also consistent with the model used by
1105 ** visLineLength.
1106 */
line_end(int pos,bool startPosIsLineStart)1107 int Fl_Text_Display::line_end(int pos, bool startPosIsLineStart) {
1108 int retLines, retPos, retLineStart, retLineEnd;
1109
1110 /* If we're not wrapping use more efficien BufEndOfLine */
1111 if (!mContinuousWrap)
1112 return buffer()->line_end(pos);
1113
1114 if (pos == buffer()->length())
1115 return pos;
1116 wrapped_line_counter(buffer(), pos, buffer()->length(), 1,
1117 startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1118 &retLineEnd);
1119 return retLineEnd;
1120 }
1121
1122 /*
1123 ** Same as BufStartOfLine, but returns the character after last wrap point
1124 ** rather than the last newline.
1125 */
line_start(int pos)1126 int Fl_Text_Display::line_start(int pos) {
1127 int retLines, retPos, retLineStart, retLineEnd;
1128
1129 /* If we're not wrapping, use the more efficient BufStartOfLine */
1130 if (!mContinuousWrap)
1131 return buffer()->line_start(pos);
1132
1133 wrapped_line_counter(buffer(), buffer()->line_start(pos), pos, INT_MAX, true, 0,
1134 &retPos, &retLines, &retLineStart, &retLineEnd);
1135 return retLineStart;
1136 }
1137
1138 /*
1139 ** Same as BufCountBackwardNLines, but takes in to account line breaks when
1140 ** wrapping is turned on.
1141 */
rewind_lines(int startPos,int nLines)1142 int Fl_Text_Display::rewind_lines(int startPos, int nLines) {
1143 Fl_Text_Buffer *buf = buffer();
1144 int pos, lineStart, retLines, retPos, retLineStart, retLineEnd;
1145
1146 /* If we're not wrapping, use the more efficient BufCountBackwardNLines */
1147 if (!mContinuousWrap)
1148 return buf->rewind_lines(startPos, nLines);
1149
1150 pos = startPos;
1151 for (;;) {
1152 lineStart = buf->line_start(pos);
1153 wrapped_line_counter(buf, lineStart, pos, INT_MAX,
1154 true, 0, &retPos, &retLines, &retLineStart, &retLineEnd, false);
1155 if (retLines > nLines)
1156 return skip_lines(lineStart, retLines-nLines,
1157 true);
1158 nLines -= retLines;
1159 pos = lineStart - 1;
1160 if (pos < 0)
1161 return 0;
1162 nLines -= 1;
1163 }
1164 }
1165
fl_isseparator(int c)1166 static inline int fl_isseparator(int c) {
1167 return c != '$' && c != '_' && (isspace(c) || ispunct(c));
1168 }
1169
next_word()1170 void Fl_Text_Display::next_word() {
1171 int pos = insert_position();
1172 while (pos < buffer()->length() && !fl_isseparator(buffer()->character(pos))) {
1173 pos++;
1174 }
1175 while (pos < buffer()->length() && fl_isseparator(buffer()->character(pos))) {
1176 pos++;
1177 }
1178
1179 insert_position( pos );
1180 }
1181
previous_word()1182 void Fl_Text_Display::previous_word() {
1183 int pos = insert_position();
1184 if (pos==0) return;
1185 pos--;
1186 while (pos && fl_isseparator(buffer()->character(pos))) {
1187 pos--;
1188 }
1189 while (pos && !fl_isseparator(buffer()->character(pos))) {
1190 pos--;
1191 }
1192 if (fl_isseparator(buffer()->character(pos))) pos++;
1193
1194 insert_position( pos );
1195 }
1196
1197 /*
1198 ** Callback attached to the text buffer to receive delete information before
1199 ** the modifications are actually made.
1200 */
buffer_predelete_cb(int pos,int nDeleted,void * cbArg)1201 void Fl_Text_Display::buffer_predelete_cb(int pos, int nDeleted, void *cbArg) {
1202 Fl_Text_Display *textD = (Fl_Text_Display *)cbArg;
1203 if (textD->mContinuousWrap &&
1204 (textD->mFixedFontWidth == -1 || textD->mModifyingTabDistance))
1205 /* Note: we must perform this measurement, even if there is not a
1206 single character deleted; the number of "deleted" lines is the
1207 number of visual lines spanned by the real line in which the
1208 modification takes place.
1209 Also, a modification of the tab distance requires the same
1210 kind of calculations in advance, even if the font width is "fixed",
1211 because when the width of the tab characters changes, the layout
1212 of the text may be completely different. */
1213 textD->measure_deleted_lines(pos, nDeleted);
1214 else
1215 textD->mSuppressResync = 0; /* Probably not needed, but just in case */
1216 }
1217
1218 /*
1219 ** Callback attached to the text buffer to receive modification information
1220 */
buffer_modified_cb(int pos,int nInserted,int nDeleted,int nRestyled,const char * deletedText,void * cbArg)1221 void Fl_Text_Display::buffer_modified_cb( int pos, int nInserted, int nDeleted,
1222 int nRestyled, const char *deletedText, void *cbArg ) {
1223 int linesInserted, linesDeleted, startDispPos, endDispPos;
1224 Fl_Text_Display *textD = ( Fl_Text_Display * ) cbArg;
1225 Fl_Text_Buffer *buf = textD->mBuffer;
1226 int oldFirstChar = textD->mFirstChar;
1227 int scrolled, origCursorPos = textD->mCursorPos;
1228 int wrapModStart, wrapModEnd;
1229
1230 /* buffer modification cancels vertical cursor motion column */
1231 if ( nInserted != 0 || nDeleted != 0 )
1232 textD->mCursorPreferredCol = -1;
1233
1234 /* Count the number of lines inserted and deleted, and in the case
1235 of continuous wrap mode, how much has changed */
1236 if (textD->mContinuousWrap) {
1237 textD->find_wrap_range(deletedText, pos, nInserted, nDeleted,
1238 &wrapModStart, &wrapModEnd, &linesInserted, &linesDeleted);
1239 } else {
1240 linesInserted = nInserted == 0 ? 0 :
1241 buf->count_lines( pos, pos + nInserted );
1242 linesDeleted = nDeleted == 0 ? 0 : countlines( deletedText );
1243 }
1244
1245 /* Update the line starts and mTopLineNum */
1246 if ( nInserted != 0 || nDeleted != 0 ) {
1247 if (textD->mContinuousWrap) {
1248 textD->update_line_starts( wrapModStart, wrapModEnd-wrapModStart,
1249 nDeleted + pos-wrapModStart + (wrapModEnd-(pos+nInserted)),
1250 linesInserted, linesDeleted, &scrolled );
1251 } else {
1252 textD->update_line_starts( pos, nInserted, nDeleted, linesInserted,
1253 linesDeleted, &scrolled );
1254 }
1255 } else
1256 scrolled = 0;
1257
1258 /* If we're counting non-wrapped lines as well, maintain the absolute
1259 (non-wrapped) line number of the text displayed */
1260 if (textD->maintaining_absolute_top_line_number() &&
1261 (nInserted != 0 || nDeleted != 0)) {
1262 if (pos + nDeleted < oldFirstChar)
1263 textD->mAbsTopLineNum += buf->count_lines(pos, pos + nInserted) -
1264 countlines(deletedText);
1265 else if (pos < oldFirstChar)
1266 textD->reset_absolute_top_line_number();
1267 }
1268
1269 /* Update the line count for the whole buffer */
1270 textD->mNBufferLines += linesInserted - linesDeleted;
1271
1272 /* Update the cursor position */
1273 if ( textD->mCursorToHint != NO_HINT ) {
1274 textD->mCursorPos = textD->mCursorToHint;
1275 textD->mCursorToHint = NO_HINT;
1276 } else if ( textD->mCursorPos > pos ) {
1277 if ( textD->mCursorPos < pos + nDeleted )
1278 textD->mCursorPos = pos;
1279 else
1280 textD->mCursorPos += nInserted - nDeleted;
1281 }
1282
1283 // refigure scrollbars & stuff
1284 textD->resize(textD->x(), textD->y(), textD->w(), textD->h());
1285
1286 // don't need to do anything else if not visible?
1287 if (!textD->visible_r()) return;
1288
1289 /* If the changes caused scrolling, re-paint everything and we're done. */
1290 if ( scrolled ) {
1291 textD->damage(FL_DAMAGE_EXPOSE);
1292 if ( textD->mStyleBuffer ) /* See comments in extendRangeForStyleMods */
1293 textD->mStyleBuffer->primary_selection()->selected(0);
1294 return;
1295 }
1296
1297 /* If the changes didn't cause scrolling, decide the range of characters
1298 that need to be re-painted. Also if the cursor position moved, be
1299 sure that the redisplay range covers the old cursor position so the
1300 old cursor gets erased, and erase the bits of the cursor which extend
1301 beyond the left and right edges of the text. */
1302 startDispPos = textD->mContinuousWrap ? wrapModStart : pos;
1303 if ( origCursorPos == startDispPos && textD->mCursorPos != startDispPos )
1304 startDispPos = min( startDispPos, origCursorPos - 1 );
1305 if ( linesInserted == linesDeleted ) {
1306 if ( nInserted == 0 && nDeleted == 0 )
1307 endDispPos = pos + nRestyled;
1308 else {
1309 endDispPos = textD->mContinuousWrap ? wrapModEnd :
1310 buf->line_end( pos + nInserted ) + 1;
1311 // CET - FIXME if ( origCursorPos >= startDispPos &&
1312 // ( origCursorPos <= endDispPos || endDispPos == buf->length() ) )
1313 }
1314
1315 if (linesInserted > 1) textD->draw_line_numbers(false);
1316 } else {
1317 endDispPos = textD->mLastChar + 1;
1318 // CET - FIXME if ( origCursorPos >= pos )
1319 /* If more than one line is inserted/deleted, a line break may have
1320 been inserted or removed in between, and the line numbers may
1321 have changed. If only one line is altered, line numbers cannot
1322 be affected (the insertion or removal of a line break always
1323 results in at least two lines being redrawn). */
1324 textD->draw_line_numbers(false);
1325 }
1326
1327 /* If there is a style buffer, check if the modification caused additional
1328 changes that need to be redisplayed. (Redisplaying separately would
1329 cause double-redraw on almost every modification involving styled
1330 text). Extend the redraw range to incorporate style changes */
1331 if ( textD->mStyleBuffer )
1332 textD->extend_range_for_styles( &startDispPos, &endDispPos );
1333
1334 /* Redisplay computed range */
1335 textD->redisplay_range( startDispPos, endDispPos );
1336 }
1337
1338 /*
1339 ** In continuous wrap mode, internal line numbers are calculated after
1340 ** wrapping. A separate non-wrapped line count is maintained when line
1341 ** numbering is turned on. There is some performance cost to maintaining this
1342 ** line count, so normally absolute line numbers are not tracked if line
1343 ** numbering is off. This routine allows callers to specify that they still
1344 ** want this line count maintained (for use via TextDPosToLineAndCol).
1345 ** More specifically, this allows the line number reported in the statistics
1346 ** line to be calibrated in absolute lines, rather than post-wrapped lines.
1347 */
maintain_absolute_top_line_number(int state)1348 void Fl_Text_Display::maintain_absolute_top_line_number(int state) {
1349 mNeedAbsTopLineNum = state;
1350 reset_absolute_top_line_number();
1351 }
1352
1353 /*
1354 ** Returns the absolute (non-wrapped) line number of the first line displayed.
1355 ** Returns 0 if the absolute top line number is not being maintained.
1356 */
get_absolute_top_line_number()1357 int Fl_Text_Display::get_absolute_top_line_number() {
1358 if (!mContinuousWrap)
1359 return mTopLineNum;
1360 if (maintaining_absolute_top_line_number())
1361 return mAbsTopLineNum;
1362 return 0;
1363 }
1364
1365 /*
1366 ** Re-calculate absolute top line number for a change in scroll position.
1367 */
absolute_top_line_number(int oldFirstChar)1368 void Fl_Text_Display::absolute_top_line_number(int oldFirstChar) {
1369 if (maintaining_absolute_top_line_number()) {
1370 if (mFirstChar < oldFirstChar)
1371 mAbsTopLineNum -= buffer()->count_lines(mFirstChar, oldFirstChar);
1372 else
1373 mAbsTopLineNum += buffer()->count_lines(oldFirstChar, mFirstChar);
1374 }
1375 }
1376
1377 /*
1378 ** Return true if a separate absolute top line number is being maintained
1379 ** (for displaying line numbers or showing in the statistics line).
1380 */
maintaining_absolute_top_line_number()1381 int Fl_Text_Display::maintaining_absolute_top_line_number() {
1382 return mContinuousWrap &&
1383 (mLineNumWidth != 0 || mNeedAbsTopLineNum);
1384 }
1385
1386 /*
1387 ** Count lines from the beginning of the buffer to reestablish the
1388 ** absolute (non-wrapped) top line number. If mode is not continuous wrap,
1389 ** or the number is not being maintained, does nothing.
1390 */
reset_absolute_top_line_number()1391 void Fl_Text_Display::reset_absolute_top_line_number() {
1392 mAbsTopLineNum = 1;
1393 absolute_top_line_number(0);
1394 }
1395
1396 /*
1397 ** Find the line number of position "pos" relative to the first line of
1398 ** displayed text. Returns 0 if the line is not displayed.
1399 */
position_to_line(int pos,int * lineNum)1400 int Fl_Text_Display::position_to_line( int pos, int *lineNum ) {
1401 int i;
1402
1403 *lineNum = 0;
1404 if ( pos < mFirstChar ) return 0;
1405 if ( pos > mLastChar ) {
1406 if ( empty_vlines() ) {
1407 if ( mLastChar < mBuffer->length() ) {
1408 if ( !position_to_line( mLastChar, lineNum ) ) {
1409 Fl::error("Fl_Text_Display::position_to_line(): Consistency check ptvl failed");
1410 return 0;
1411 }
1412 return ++( *lineNum ) <= mNVisibleLines - 1;
1413 } else {
1414 position_to_line( mLastChar - 1, lineNum );
1415 return 1;
1416 }
1417 }
1418 return 0;
1419 }
1420
1421 for ( i = mNVisibleLines - 1; i >= 0; i-- ) {
1422 if ( mLineStarts[ i ] != -1 && pos >= mLineStarts[ i ] ) {
1423 *lineNum = i;
1424 return 1;
1425 }
1426 }
1427 return 0; /* probably never be reached */
1428 }
1429
1430 /*
1431 ** Draw the text on a single line represented by "visLineNum" (the
1432 ** number of lines down from the top of the display), limited by
1433 ** "leftClip" and "rightClip" window coordinates and "leftCharIndex" and
1434 ** "rightCharIndex" character positions (not including the character at
1435 ** position "rightCharIndex").
1436 */
draw_vline(int visLineNum,int leftClip,int rightClip,int leftCharIndex,int rightCharIndex)1437 void Fl_Text_Display::draw_vline(int visLineNum, int leftClip, int rightClip,
1438 int leftCharIndex, int rightCharIndex) {
1439 Fl_Text_Buffer * buf = mBuffer;
1440 int i, X, Y, startX, charIndex, lineStartPos, lineLen, fontHeight;
1441 int stdCharWidth, charWidth, startIndex, charStyle, style;
1442 int charLen, outStartIndex, outIndex;
1443 int dispIndexOffset;
1444 char expandedChar[ FL_TEXT_MAX_EXP_CHAR_LEN ], outStr[ MAX_DISP_LINE_LEN ];
1445 char *outPtr;
1446 const char *lineStr;
1447
1448 // printf("draw_vline(visLineNum=%d, leftClip=%d, rightClip=%d, leftCharIndex=%d, rightCharIndex=%d)\n",
1449 // visLineNum, leftClip, rightClip, leftCharIndex, rightCharIndex);
1450 // printf("nNVisibleLines=%d\n", mNVisibleLines);
1451
1452 /* If line is not displayed, skip it */
1453 if ( visLineNum < 0 || visLineNum >= mNVisibleLines )
1454 return;
1455
1456 /* Calculate Y coordinate of the string to draw */
1457 fontHeight = mMaxsize;
1458 Y = text_area.y + visLineNum * fontHeight;
1459
1460 /* Get the text, length, and buffer position of the line to display */
1461 lineStartPos = mLineStarts[ visLineNum ];
1462 // printf("lineStartPos=%d\n", lineStartPos);
1463 if ( lineStartPos == -1 ) {
1464 lineLen = 0;
1465 lineStr = NULL;
1466 } else {
1467 lineLen = vline_length( visLineNum );
1468 lineStr = buf->text_range( lineStartPos, lineStartPos + lineLen );
1469 }
1470
1471 /* Space beyond the end of the line is still counted in units of characters
1472 of a standardized character width (this is done mostly because style
1473 changes based on character position can still occur in this region due
1474 to rectangular Fl_Text_Selections). stdCharWidth must be non-zero to
1475 prevent a potential infinite loop if X does not advance */
1476 stdCharWidth = TMPFONTWIDTH; //mFontStruct->max_bounds.width;
1477 if ( stdCharWidth <= 0 ) {
1478 Fl::error("Fl_Text_Display::draw_vline(): bad font measurement");
1479 free((void *)lineStr);
1480 return;
1481 }
1482
1483 /* Shrink the clipping range to the active display area */
1484 leftClip = max( text_area.x, leftClip );
1485 rightClip = min( rightClip, text_area.x + text_area.w );
1486
1487 /* Rectangular Fl_Text_Selections are based on "real" line starts (after
1488 a newline or start of buffer). Calculate the difference between the
1489 last newline position and the line start we're using. Since scanning
1490 back to find a newline is expensive, only do so if there's actually a
1491 rectangular Fl_Text_Selection which needs it */
1492 if (mContinuousWrap && (range_touches_selection(buf->primary_selection(),
1493 lineStartPos, lineStartPos + lineLen) || range_touches_selection(
1494 buf->secondary_selection(), lineStartPos, lineStartPos + lineLen) ||
1495 range_touches_selection(buf->highlight_selection(), lineStartPos,
1496 lineStartPos + lineLen))) {
1497 dispIndexOffset = buf->count_displayed_characters(
1498 buf->line_start(lineStartPos), lineStartPos);
1499 } else
1500 dispIndexOffset = 0;
1501
1502 /* Step through character positions from the beginning of the line (even if
1503 that's off the left edge of the displayed area) to find the first
1504 character position that's not clipped, and the X coordinate for drawing
1505 that character */
1506 X = text_area.x - mHorizOffset;
1507 outIndex = 0;
1508 for ( charIndex = 0; ; charIndex++ ) {
1509 charLen = charIndex >= lineLen ? 1 :
1510 Fl_Text_Buffer::expand_character( lineStr[ charIndex ], outIndex,
1511 expandedChar, buf->tab_distance(), buf->null_substitution_character() );
1512 style = position_style( lineStartPos, lineLen, charIndex,
1513 outIndex + dispIndexOffset );
1514 charWidth = charIndex >= lineLen ? stdCharWidth :
1515 string_width( expandedChar, charLen, style );
1516 if ( X + charWidth >= leftClip && charIndex >= leftCharIndex ) {
1517 startIndex = charIndex;
1518 outStartIndex = outIndex;
1519 startX = X;
1520 break;
1521 }
1522 X += charWidth;
1523 outIndex += charLen;
1524 }
1525
1526 /* Scan character positions from the beginning of the clipping range, and
1527 draw parts whenever the style changes (also note if the cursor is on
1528 this line, and where it should be drawn to take advantage of the x
1529 position which we've gone to so much trouble to calculate) */
1530 /* since characters between style may overlap, we draw the full
1531 background first */
1532 int sX = startX;
1533 outPtr = outStr;
1534 outIndex = outStartIndex;
1535 X = startX;
1536 for ( charIndex = startIndex; charIndex < rightCharIndex; charIndex++ ) {
1537 charLen = charIndex >= lineLen ? 1 :
1538 Fl_Text_Buffer::expand_character( lineStr[ charIndex ], outIndex, expandedChar,
1539 buf->tab_distance(), buf->null_substitution_character() );
1540 charStyle = position_style( lineStartPos, lineLen, charIndex,
1541 outIndex + dispIndexOffset );
1542 for ( i = 0; i < charLen; i++ ) {
1543 if ( i != 0 && charIndex < lineLen && lineStr[ charIndex ] == '\t' )
1544 charStyle = position_style( lineStartPos, lineLen,
1545 charIndex, outIndex + dispIndexOffset );
1546 if ( charStyle != style ) {
1547 draw_string( style|BG_ONLY_MASK, sX, Y, X, outStr, outPtr - outStr );
1548 outPtr = outStr;
1549 sX = X;
1550 style = charStyle;
1551 }
1552 if ( charIndex < lineLen ) {
1553 *outPtr = expandedChar[ i ];
1554 charWidth = string_width( &expandedChar[ i ], 1, charStyle );
1555 } else
1556 charWidth = stdCharWidth;
1557 outPtr++;
1558 X += charWidth;
1559 outIndex++;
1560 }
1561 if ( outPtr - outStr + FL_TEXT_MAX_EXP_CHAR_LEN >= MAX_DISP_LINE_LEN || X >= rightClip )
1562 break;
1563 }
1564 draw_string( style|BG_ONLY_MASK, sX, Y, X, outStr, outPtr - outStr );
1565
1566 /* now draw the text over the previously erased background */
1567 outPtr = outStr;
1568 outIndex = outStartIndex;
1569 X = startX;
1570 for ( charIndex = startIndex; charIndex < rightCharIndex; charIndex++ ) {
1571 charLen = charIndex >= lineLen ? 1 :
1572 Fl_Text_Buffer::expand_character( lineStr[ charIndex ], outIndex, expandedChar,
1573 buf->tab_distance(), buf->null_substitution_character() );
1574 charStyle = position_style( lineStartPos, lineLen, charIndex,
1575 outIndex + dispIndexOffset );
1576 for ( i = 0; i < charLen; i++ ) {
1577 if ( i != 0 && charIndex < lineLen && lineStr[ charIndex ] == '\t' )
1578 charStyle = position_style( lineStartPos, lineLen,
1579 charIndex, outIndex + dispIndexOffset );
1580 if ( charStyle != style ) {
1581 draw_string( style|TEXT_ONLY_MASK, startX, Y, X, outStr, outPtr - outStr );
1582 outPtr = outStr;
1583 startX = X;
1584 style = charStyle;
1585 }
1586 if ( charIndex < lineLen ) {
1587 *outPtr = expandedChar[ i ];
1588 charWidth = string_width( &expandedChar[ i ], 1, charStyle );
1589 } else
1590 charWidth = stdCharWidth;
1591 outPtr++;
1592 X += charWidth;
1593 outIndex++;
1594 }
1595 if ( outPtr - outStr + FL_TEXT_MAX_EXP_CHAR_LEN >= MAX_DISP_LINE_LEN || X >= rightClip )
1596 break;
1597 }
1598
1599 /* Draw the remaining style segment */
1600 draw_string( style|TEXT_ONLY_MASK, startX, Y, X, outStr, outPtr - outStr );
1601
1602 /* Draw the cursor if part of it appeared on the redisplayed part of
1603 this line. Also check for the cases which are not caught as the
1604 line is scanned above: when the cursor appears at the very end
1605 of the redisplayed section. */
1606 /* CET - FIXME
1607 if ( mCursorOn )
1608 {
1609 if ( hasCursor )
1610 draw_cursor( cursorX, Y );
1611 else if ( charIndex < lineLen && ( lineStartPos + charIndex + 1 == cursorPos )
1612 && X == rightClip )
1613 {
1614 if ( cursorPos >= buf->length() )
1615 draw_cursor( X - 1, Y );
1616 else
1617 {
1618 draw_cursor( X - 1, Y );
1619 }
1620 }
1621 }
1622 */
1623 if ( lineStr != NULL )
1624 free((void *)lineStr);
1625 }
1626
1627 /*
1628 ** Draw a string or blank area according to parameter "style", using the
1629 ** appropriate colors and drawing method for that style, with top left
1630 ** corner at X, y. If style says to draw text, use "string" as source of
1631 ** characters, and draw "nChars", if style is FILL, erase
1632 ** rectangle where text would have drawn from X to toX and from Y to
1633 ** the maximum Y extent of the current font(s).
1634 */
draw_string(int style,int X,int Y,int toX,const char * string,int nChars)1635 void Fl_Text_Display::draw_string( int style, int X, int Y, int toX,
1636 const char *string, int nChars ) {
1637 const Style_Table_Entry * styleRec;
1638
1639 /* Draw blank area rather than text, if that was the request */
1640 if ( style & FILL_MASK ) {
1641 if (style & TEXT_ONLY_MASK) return;
1642 clear_rect( style, X, Y, toX - X, mMaxsize );
1643 return;
1644 }
1645
1646 /* Set font, color, and gc depending on style. For normal text, GCs
1647 for normal drawing, or drawing within a Fl_Text_Selection or highlight are
1648 pre-allocated and pre-configured. For syntax highlighting, GCs are
1649 configured here, on the fly. */
1650
1651 Fl_Font font = textfont();
1652 int fsize = textsize();
1653 Fl_Color foreground;
1654 Fl_Color background;
1655
1656 if ( style & STYLE_LOOKUP_MASK ) {
1657 int si = (style & STYLE_LOOKUP_MASK) - 'A';
1658 if (si < 0) si = 0;
1659 else if (si >= mNStyles) si = mNStyles - 1;
1660
1661 styleRec = mStyleTable + si;
1662 font = styleRec->font;
1663 fsize = styleRec->size;
1664
1665 if (style & PRIMARY_MASK) {
1666 if (Fl::focus() == this) background = selection_color();
1667 else background = fl_color_average(color(), selection_color(), 0.4f);
1668 } else if (style & HIGHLIGHT_MASK) {
1669 if (Fl::focus() == this) background = fl_color_average(color(), selection_color(), 0.5f);
1670 else background = fl_color_average(color(), selection_color(), 0.6f);
1671 } else background = color();
1672 foreground = fl_contrast(styleRec->color, background);
1673 } else if (style & PRIMARY_MASK) {
1674 if (Fl::focus() == this) background = selection_color();
1675 else background = fl_color_average(color(), selection_color(), 0.4f);
1676 foreground = fl_contrast(textcolor(), background);
1677 } else if (style & HIGHLIGHT_MASK) {
1678 if (Fl::focus() == this) background = fl_color_average(color(), selection_color(), 0.5f);
1679 else background = fl_color_average(color(), selection_color(), 0.6f);
1680 foreground = fl_contrast(textcolor(), background);
1681 } else {
1682 foreground = textcolor();
1683 background = color();
1684 }
1685
1686 if (!(style & TEXT_ONLY_MASK)) {
1687 fl_color( background );
1688 fl_rectf( X, Y, toX - X, mMaxsize );
1689 }
1690 if (!(style & BG_ONLY_MASK)) {
1691 fl_color( foreground );
1692 fl_font( font, fsize );
1693 fl_draw( string, nChars, X, Y + mMaxsize - fl_descent());
1694 }
1695
1696 // CET - FIXME
1697 /* If any space around the character remains unfilled (due to use of
1698 different sized fonts for highlighting), fill in above or below
1699 to erase previously drawn characters */
1700 /*
1701 if (fs->ascent < mAscent)
1702 clear_rect( style, X, Y, toX - X, mAscent - fs->ascent);
1703 if (fs->descent < mDescent)
1704 clear_rect( style, X, Y + mAscent + fs->descent, toX - x,
1705 mDescent - fs->descent);
1706 */
1707 /* Underline if style is secondary Fl_Text_Selection */
1708
1709 /*
1710 if (style & SECONDARY_MASK)
1711 XDrawLine(XtDisplay(mW), XtWindow(mW), gc, x,
1712 y + mAscent, toX - 1, Y + fs->ascent);
1713 */
1714 }
1715
1716
1717 /*
1718 ** Clear a rectangle with the appropriate background color for "style"
1719 */
clear_rect(int style,int X,int Y,int width,int height)1720 void Fl_Text_Display::clear_rect( int style, int X, int Y,
1721 int width, int height ) {
1722 /* A width of zero means "clear to end of window" to XClearArea */
1723 if ( width == 0 )
1724 return;
1725
1726 if (style & PRIMARY_MASK) {
1727 if (Fl::focus()==this) {
1728 fl_color(selection_color());
1729 } else {
1730 fl_color(fl_color_average(color(), selection_color(), 0.4f));
1731 }
1732 } else if (style & HIGHLIGHT_MASK) {
1733 if (Fl::focus()==this) {
1734 fl_color(fl_color_average(color(), selection_color(), 0.5f));
1735 } else {
1736 fl_color(fl_color_average(color(), selection_color(), 0.6f));
1737 }
1738 } else {
1739 fl_color( color() );
1740 }
1741 fl_rectf( X, Y, width, height );
1742 }
1743
1744
1745 /*
1746 ** Draw a cursor with top center at X, y.
1747 */
draw_cursor(int X,int Y)1748 void Fl_Text_Display::draw_cursor( int X, int Y ) {
1749 typedef struct {
1750 int x1, y1, x2, y2;
1751 }
1752 Segment;
1753
1754 Segment segs[ 5 ];
1755 int left, right, cursorWidth, midY;
1756 // int fontWidth = mFontStruct->min_bounds.width, nSegs = 0;
1757 int fontWidth = TMPFONTWIDTH; // CET - FIXME
1758 int nSegs = 0;
1759 int fontHeight = mMaxsize;
1760 int bot = Y + fontHeight - 1;
1761
1762 if ( X < text_area.x - 1 || X > text_area.x + text_area.w )
1763 return;
1764
1765 /* For cursors other than the block, make them around 2/3 of a character
1766 width, rounded to an even number of pixels so that X will draw an
1767 odd number centered on the stem at x. */
1768 cursorWidth = 4; //(fontWidth/3) * 2;
1769 left = X - cursorWidth / 2;
1770 right = left + cursorWidth;
1771
1772 /* Create segments and draw cursor */
1773 if ( mCursorStyle == CARET_CURSOR ) {
1774 midY = bot - fontHeight / 5;
1775 segs[ 0 ].x1 = left; segs[ 0 ].y1 = bot; segs[ 0 ].x2 = X; segs[ 0 ].y2 = midY;
1776 segs[ 1 ].x1 = X; segs[ 1 ].y1 = midY; segs[ 1 ].x2 = right; segs[ 1 ].y2 = bot;
1777 segs[ 2 ].x1 = left; segs[ 2 ].y1 = bot; segs[ 2 ].x2 = X; segs[ 2 ].y2 = midY - 1;
1778 segs[ 3 ].x1 = X; segs[ 3 ].y1 = midY - 1; segs[ 3 ].x2 = right; segs[ 3 ].y2 = bot;
1779 nSegs = 4;
1780 } else if ( mCursorStyle == NORMAL_CURSOR ) {
1781 segs[ 0 ].x1 = left; segs[ 0 ].y1 = Y; segs[ 0 ].x2 = right; segs[ 0 ].y2 = Y;
1782 segs[ 1 ].x1 = X; segs[ 1 ].y1 = Y; segs[ 1 ].x2 = X; segs[ 1 ].y2 = bot;
1783 segs[ 2 ].x1 = left; segs[ 2 ].y1 = bot; segs[ 2 ].x2 = right; segs[ 2 ].y2 = bot;
1784 nSegs = 3;
1785 } else if ( mCursorStyle == HEAVY_CURSOR ) {
1786 segs[ 0 ].x1 = X - 1; segs[ 0 ].y1 = Y; segs[ 0 ].x2 = X - 1; segs[ 0 ].y2 = bot;
1787 segs[ 1 ].x1 = X; segs[ 1 ].y1 = Y; segs[ 1 ].x2 = X; segs[ 1 ].y2 = bot;
1788 segs[ 2 ].x1 = X + 1; segs[ 2 ].y1 = Y; segs[ 2 ].x2 = X + 1; segs[ 2 ].y2 = bot;
1789 segs[ 3 ].x1 = left; segs[ 3 ].y1 = Y; segs[ 3 ].x2 = right; segs[ 3 ].y2 = Y;
1790 segs[ 4 ].x1 = left; segs[ 4 ].y1 = bot; segs[ 4 ].x2 = right; segs[ 4 ].y2 = bot;
1791 nSegs = 5;
1792 } else if ( mCursorStyle == DIM_CURSOR ) {
1793 midY = Y + fontHeight / 2;
1794 segs[ 0 ].x1 = X; segs[ 0 ].y1 = Y; segs[ 0 ].x2 = X; segs[ 0 ].y2 = Y;
1795 segs[ 1 ].x1 = X; segs[ 1 ].y1 = midY; segs[ 1 ].x2 = X; segs[ 1 ].y2 = midY;
1796 segs[ 2 ].x1 = X; segs[ 2 ].y1 = bot; segs[ 2 ].x2 = X; segs[ 2 ].y2 = bot;
1797 nSegs = 3;
1798 } else if ( mCursorStyle == BLOCK_CURSOR ) {
1799 right = X + fontWidth;
1800 segs[ 0 ].x1 = X; segs[ 0 ].y1 = Y; segs[ 0 ].x2 = right; segs[ 0 ].y2 = Y;
1801 segs[ 1 ].x1 = right; segs[ 1 ].y1 = Y; segs[ 1 ].x2 = right; segs[ 1 ].y2 = bot;
1802 segs[ 2 ].x1 = right; segs[ 2 ].y1 = bot; segs[ 2 ].x2 = X; segs[ 2 ].y2 = bot;
1803 segs[ 3 ].x1 = X; segs[ 3 ].y1 = bot; segs[ 3 ].x2 = X; segs[ 3 ].y2 = Y;
1804 nSegs = 4;
1805 }
1806 fl_color( mCursor_color );
1807
1808 for ( int k = 0; k < nSegs; k++ ) {
1809 fl_line( segs[ k ].x1, segs[ k ].y1, segs[ k ].x2, segs[ k ].y2 );
1810 }
1811 }
1812
1813 /*
1814 ** Determine the drawing method to use to draw a specific character from "buf".
1815 ** "lineStartPos" gives the character index where the line begins, "lineIndex",
1816 ** the number of characters past the beginning of the line, and "dispIndex",
1817 ** the number of displayed characters past the beginning of the line. Passing
1818 ** lineStartPos of -1 returns the drawing style for "no text".
1819 **
1820 ** Why not just: position_style(pos)? Because style applies to blank areas
1821 ** of the window beyond the text boundaries, and because this routine must also
1822 ** decide whether a position is inside of a rectangular Fl_Text_Selection, and do
1823 ** so efficiently, without re-counting character positions from the start of the
1824 ** line.
1825 **
1826 ** Note that style is a somewhat incorrect name, drawing method would
1827 ** be more appropriate.
1828 */
position_style(int lineStartPos,int lineLen,int lineIndex,int dispIndex)1829 int Fl_Text_Display::position_style( int lineStartPos,
1830 int lineLen, int lineIndex, int dispIndex ) {
1831 Fl_Text_Buffer * buf = mBuffer;
1832 Fl_Text_Buffer *styleBuf = mStyleBuffer;
1833 int pos, style = 0;
1834
1835 if ( lineStartPos == -1 || buf == NULL )
1836 return FILL_MASK;
1837
1838 pos = lineStartPos + min( lineIndex, lineLen );
1839
1840 if ( lineIndex >= lineLen )
1841 style = FILL_MASK;
1842 else if ( styleBuf != NULL ) {
1843 style = ( unsigned char ) styleBuf->character( pos );
1844 if (style == mUnfinishedStyle && mUnfinishedHighlightCB) {
1845 /* encountered "unfinished" style, trigger parsing */
1846 (mUnfinishedHighlightCB)( pos, mHighlightCBArg);
1847 style = (unsigned char) styleBuf->character( pos);
1848 }
1849 }
1850 if (buf->primary_selection()->includes(pos, lineStartPos, dispIndex))
1851 style |= PRIMARY_MASK;
1852 if (buf->highlight_selection()->includes(pos, lineStartPos, dispIndex))
1853 style |= HIGHLIGHT_MASK;
1854 if (buf->secondary_selection()->includes(pos, lineStartPos, dispIndex))
1855 style |= SECONDARY_MASK;
1856 return style;
1857 }
1858
1859 /*
1860 ** Find the width of a string in the font of a particular style
1861 */
string_width(const char * string,int length,int style)1862 int Fl_Text_Display::string_width( const char *string, int length, int style ) {
1863 Fl_Font font;
1864 int fsize;
1865
1866 if ( style & STYLE_LOOKUP_MASK ) {
1867 int si = (style & STYLE_LOOKUP_MASK) - 'A';
1868 if (si < 0) si = 0;
1869 else if (si >= mNStyles) si = mNStyles - 1;
1870
1871 font = mStyleTable[si].font;
1872 fsize = mStyleTable[si].size;
1873 } else {
1874 font = textfont();
1875 fsize = textsize();
1876 }
1877 fl_font( font, fsize );
1878
1879 return ( int ) ( fl_width( string, length ) );
1880 }
1881
1882 /*
1883 ** Translate window coordinates to the nearest (insert cursor or character
1884 ** cell) text position. The parameter posType specifies how to interpret the
1885 ** position: CURSOR_POS means translate the coordinates to the nearest cursor
1886 ** position, and CHARACTER_POS means return the position of the character
1887 ** closest to (X, Y).
1888 */
xy_to_position(int X,int Y,int posType)1889 int Fl_Text_Display::xy_to_position( int X, int Y, int posType ) {
1890 int charIndex, lineStart, lineLen, fontHeight;
1891 int charWidth, charLen, charStyle, visLineNum, xStep, outIndex;
1892 char expandedChar[ FL_TEXT_MAX_EXP_CHAR_LEN ];
1893 const char *lineStr;
1894
1895 /* Find the visible line number corresponding to the Y coordinate */
1896 fontHeight = mMaxsize;
1897 visLineNum = ( Y - text_area.y ) / fontHeight;
1898 if ( visLineNum < 0 )
1899 return mFirstChar;
1900 if ( visLineNum >= mNVisibleLines )
1901 visLineNum = mNVisibleLines - 1;
1902
1903 /* Find the position at the start of the line */
1904 lineStart = mLineStarts[ visLineNum ];
1905
1906 /* If the line start was empty, return the last position in the buffer */
1907 if ( lineStart == -1 )
1908 return mBuffer->length();
1909
1910 /* Get the line text and its length */
1911 lineLen = vline_length( visLineNum );
1912 lineStr = mBuffer->text_range( lineStart, lineStart + lineLen );
1913
1914 /* Step through character positions from the beginning of the line
1915 to find the character position corresponding to the X coordinate */
1916 xStep = text_area.x - mHorizOffset;
1917 outIndex = 0;
1918 for ( charIndex = 0; charIndex < lineLen; charIndex++ ) {
1919 charLen = Fl_Text_Buffer::expand_character( lineStr[ charIndex ], outIndex, expandedChar,
1920 mBuffer->tab_distance(), mBuffer->null_substitution_character() );
1921 charStyle = position_style( lineStart, lineLen, charIndex, outIndex );
1922 charWidth = string_width( expandedChar, charLen, charStyle );
1923 if ( X < xStep + ( posType == CURSOR_POS ? charWidth / 2 : charWidth ) ) {
1924 free((char *)lineStr);
1925 return lineStart + charIndex;
1926 }
1927 xStep += charWidth;
1928 outIndex += charLen;
1929 }
1930
1931 /* If the X position was beyond the end of the line, return the position
1932 of the newline at the end of the line */
1933 free((char *)lineStr);
1934 return lineStart + lineLen;
1935 }
1936
1937 /*
1938 ** Translate window coordinates to the nearest row and column number for
1939 ** positioning the cursor. This, of course, makes no sense when the font is
1940 ** proportional, since there are no absolute columns. The parameter posType
1941 ** specifies how to interpret the position: CURSOR_POS means translate the
1942 ** coordinates to the nearest position between characters, and CHARACTER_POS
1943 ** means translate the position to the nearest character cell.
1944 */
xy_to_rowcol(int X,int Y,int * row,int * column,int posType)1945 void Fl_Text_Display::xy_to_rowcol( int X, int Y, int *row,
1946 int *column, int posType ) {
1947 int fontHeight = mMaxsize;
1948 int fontWidth = TMPFONTWIDTH; //mFontStruct->max_bounds.width;
1949
1950 /* Find the visible line number corresponding to the Y coordinate */
1951 *row = ( Y - text_area.y ) / fontHeight;
1952 if ( *row < 0 ) * row = 0;
1953 if ( *row >= mNVisibleLines ) * row = mNVisibleLines - 1;
1954 *column = ( ( X - text_area.x ) + mHorizOffset +
1955 ( posType == CURSOR_POS ? fontWidth / 2 : 0 ) ) / fontWidth;
1956 if ( *column < 0 ) * column = 0;
1957 }
1958
1959 /*
1960 ** Offset the line starts array, mTopLineNum, mFirstChar and lastChar, for a new
1961 ** vertical scroll position given by newTopLineNum. If any currently displayed
1962 ** lines will still be visible, salvage the line starts values, otherwise,
1963 ** count lines from the nearest known line start (start or end of buffer, or
1964 ** the closest value in the mLineStarts array)
1965 */
offset_line_starts(int newTopLineNum)1966 void Fl_Text_Display::offset_line_starts( int newTopLineNum ) {
1967 int oldTopLineNum = mTopLineNum;
1968 int oldFirstChar = mFirstChar;
1969 int lineDelta = newTopLineNum - oldTopLineNum;
1970 int nVisLines = mNVisibleLines;
1971 int *lineStarts = mLineStarts;
1972 int i, lastLineNum;
1973 Fl_Text_Buffer *buf = mBuffer;
1974
1975 /* If there was no offset, nothing needs to be changed */
1976 if ( lineDelta == 0 )
1977 return;
1978
1979 /* Find the new value for mFirstChar by counting lines from the nearest
1980 known line start (start or end of buffer, or the closest value in the
1981 lineStarts array) */
1982 lastLineNum = oldTopLineNum + nVisLines - 1;
1983 if ( newTopLineNum < oldTopLineNum && newTopLineNum < -lineDelta ) {
1984 mFirstChar = skip_lines( 0, newTopLineNum - 1, true );
1985 } else if ( newTopLineNum < oldTopLineNum ) {
1986 mFirstChar = rewind_lines( mFirstChar, -lineDelta );
1987 } else if ( newTopLineNum < lastLineNum ) {
1988 mFirstChar = lineStarts[ newTopLineNum - oldTopLineNum ];
1989 } else if ( newTopLineNum - lastLineNum < mNBufferLines - newTopLineNum ) {
1990 mFirstChar = skip_lines( lineStarts[ nVisLines - 1 ],
1991 newTopLineNum - lastLineNum, true );
1992 } else {
1993 mFirstChar = rewind_lines( buf->length(), mNBufferLines - newTopLineNum + 1 );
1994 }
1995
1996 /* Fill in the line starts array */
1997 if ( lineDelta < 0 && -lineDelta < nVisLines ) {
1998 for ( i = nVisLines - 1; i >= -lineDelta; i-- )
1999 lineStarts[ i ] = lineStarts[ i + lineDelta ];
2000 calc_line_starts( 0, -lineDelta );
2001 } else if ( lineDelta > 0 && lineDelta < nVisLines ) {
2002 for ( i = 0; i < nVisLines - lineDelta; i++ )
2003 lineStarts[ i ] = lineStarts[ i + lineDelta ];
2004 calc_line_starts( nVisLines - lineDelta, nVisLines - 1 );
2005 } else
2006 calc_line_starts( 0, nVisLines );
2007
2008 /* Set lastChar and mTopLineNum */
2009 calc_last_char();
2010 mTopLineNum = newTopLineNum;
2011
2012 /* If we're numbering lines or being asked to maintain an absolute line
2013 number, re-calculate the absolute line number */
2014 absolute_top_line_number(oldFirstChar);
2015 }
2016
2017 /*
2018 ** Update the line starts array, mTopLineNum, mFirstChar and lastChar for text
2019 ** display "textD" after a modification to the text buffer, given by the
2020 ** position where the change began "pos", and the nmubers of characters
2021 ** and lines inserted and deleted.
2022 */
update_line_starts(int pos,int charsInserted,int charsDeleted,int linesInserted,int linesDeleted,int * scrolled)2023 void Fl_Text_Display::update_line_starts( int pos, int charsInserted,
2024 int charsDeleted, int linesInserted, int linesDeleted, int *scrolled ) {
2025 int * lineStarts = mLineStarts;
2026 int i, lineOfPos, lineOfEnd, nVisLines = mNVisibleLines;
2027 int charDelta = charsInserted - charsDeleted;
2028 int lineDelta = linesInserted - linesDeleted;
2029
2030 /* If all of the changes were before the displayed text, the display
2031 doesn't change, just update the top line num and offset the line
2032 start entries and first and last characters */
2033 if ( pos + charsDeleted < mFirstChar ) {
2034 mTopLineNum += lineDelta;
2035 for ( i = 0; i < nVisLines && lineStarts[i] != -1; i++ )
2036 lineStarts[ i ] += charDelta;
2037 mFirstChar += charDelta;
2038 mLastChar += charDelta;
2039 *scrolled = 0;
2040 return;
2041 }
2042
2043 /* The change began before the beginning of the displayed text, but
2044 part or all of the displayed text was deleted */
2045 if ( pos < mFirstChar ) {
2046 /* If some text remains in the window, anchor on that */
2047 if ( position_to_line( pos + charsDeleted, &lineOfEnd ) &&
2048 ++lineOfEnd < nVisLines && lineStarts[ lineOfEnd ] != -1 ) {
2049 mTopLineNum = max( 1, mTopLineNum + lineDelta );
2050 mFirstChar = rewind_lines(
2051 lineStarts[ lineOfEnd ] + charDelta, lineOfEnd );
2052 /* Otherwise anchor on original line number and recount everything */
2053 } else {
2054 if ( mTopLineNum > mNBufferLines + lineDelta ) {
2055 mTopLineNum = 1;
2056 mFirstChar = 0;
2057 } else
2058 mFirstChar = skip_lines( 0, mTopLineNum - 1, true );
2059 }
2060 calc_line_starts( 0, nVisLines - 1 );
2061 /* calculate lastChar by finding the end of the last displayed line */
2062 calc_last_char();
2063 *scrolled = 1;
2064 return;
2065 }
2066
2067 /* If the change was in the middle of the displayed text (it usually is),
2068 salvage as much of the line starts array as possible by moving and
2069 offsetting the entries after the changed area, and re-counting the
2070 added lines or the lines beyond the salvaged part of the line starts
2071 array */
2072 if ( pos <= mLastChar ) {
2073 /* find line on which the change began */
2074 position_to_line( pos, &lineOfPos );
2075 /* salvage line starts after the changed area */
2076 if ( lineDelta == 0 ) {
2077 for ( i = lineOfPos + 1; i < nVisLines && lineStarts[ i ] != -1; i++ )
2078 lineStarts[ i ] += charDelta;
2079 } else if ( lineDelta > 0 ) {
2080 for ( i = nVisLines - 1; i >= lineOfPos + lineDelta + 1; i-- )
2081 lineStarts[ i ] = lineStarts[ i - lineDelta ] +
2082 ( lineStarts[ i - lineDelta ] == -1 ? 0 : charDelta );
2083 } else /* (lineDelta < 0) */ {
2084 for ( i = max( 0, lineOfPos + 1 ); i < nVisLines + lineDelta; i++ )
2085 lineStarts[ i ] = lineStarts[ i - lineDelta ] +
2086 ( lineStarts[ i - lineDelta ] == -1 ? 0 : charDelta );
2087 }
2088 /* fill in the missing line starts */
2089 if ( linesInserted >= 0 )
2090 calc_line_starts( lineOfPos + 1, lineOfPos + linesInserted );
2091 if ( lineDelta < 0 )
2092 calc_line_starts( nVisLines + lineDelta, nVisLines );
2093 /* calculate lastChar by finding the end of the last displayed line */
2094 calc_last_char();
2095 *scrolled = 0;
2096 return;
2097 }
2098
2099 /* Change was past the end of the displayed text, but displayable by virtue
2100 of being an insert at the end of the buffer into visible blank lines */
2101 if ( empty_vlines() ) {
2102 position_to_line( pos, &lineOfPos );
2103 calc_line_starts( lineOfPos, lineOfPos + linesInserted );
2104 calc_last_char();
2105 *scrolled = 0;
2106 return;
2107 }
2108
2109 /* Change was beyond the end of the buffer and not visible, do nothing */
2110 *scrolled = 0;
2111 }
2112
2113 /*
2114 ** Scan through the text in the "textD"'s buffer and recalculate the line
2115 ** starts array values beginning at index "startLine" and continuing through
2116 ** (including) "endLine". It assumes that the line starts entry preceding
2117 ** "startLine" (or mFirstChar if startLine is 0) is good, and re-counts
2118 ** newlines to fill in the requested entries. Out of range values for
2119 ** "startLine" and "endLine" are acceptable.
2120 */
calc_line_starts(int startLine,int endLine)2121 void Fl_Text_Display::calc_line_starts( int startLine, int endLine ) {
2122 int startPos, bufLen = mBuffer->length();
2123 int line, lineEnd, nextLineStart, nVis = mNVisibleLines;
2124 int *lineStarts = mLineStarts;
2125
2126 /* Clean up (possibly) messy input parameters */
2127 if ( endLine < 0 ) endLine = 0;
2128 if ( endLine >= nVis ) endLine = nVis - 1;
2129 if ( startLine < 0 ) startLine = 0;
2130 if ( startLine >= nVis ) startLine = nVis - 1;
2131 if ( startLine > endLine )
2132 return;
2133
2134 /* Find the last known good line number -> position mapping */
2135 if ( startLine == 0 ) {
2136 lineStarts[ 0 ] = mFirstChar;
2137 startLine = 1;
2138 }
2139 startPos = lineStarts[ startLine - 1 ];
2140
2141 /* If the starting position is already past the end of the text,
2142 fill in -1's (means no text on line) and return */
2143 if ( startPos == -1 ) {
2144 for ( line = startLine; line <= endLine; line++ )
2145 lineStarts[ line ] = -1;
2146 return;
2147 }
2148
2149 /* Loop searching for ends of lines and storing the positions of the
2150 start of the next line in lineStarts */
2151 for ( line = startLine; line <= endLine; line++ ) {
2152 find_line_end(startPos, true, &lineEnd, &nextLineStart);
2153 startPos = nextLineStart;
2154 if ( startPos >= bufLen ) {
2155 /* If the buffer ends with a newline or line break, put
2156 buf->length() in the next line start position (instead of
2157 a -1 which is the normal marker for an empty line) to
2158 indicate that the cursor may safely be displayed there */
2159 if ( line == 0 || ( lineStarts[ line - 1 ] != bufLen &&
2160 lineEnd != nextLineStart ) ) {
2161 lineStarts[ line ] = bufLen;
2162 line++;
2163 }
2164 break;
2165 }
2166 lineStarts[ line ] = startPos;
2167 }
2168
2169 /* Set any entries beyond the end of the text to -1 */
2170 for ( ; line <= endLine; line++ )
2171 lineStarts[ line ] = -1;
2172 }
2173
2174 /*
2175 ** Given a Fl_Text_Display with a complete, up-to-date lineStarts array, update
2176 ** the lastChar entry to point to the last buffer position displayed.
2177 */
calc_last_char()2178 void Fl_Text_Display::calc_last_char() {
2179 int i;
2180 for (i = mNVisibleLines - 1; i >= 0 && mLineStarts[i] == -1; i--) ;
2181 mLastChar = i < 0 ? 0 : line_end(mLineStarts[i], true);
2182 }
2183
scroll(int topLineNum,int horizOffset)2184 void Fl_Text_Display::scroll(int topLineNum, int horizOffset) {
2185 mTopLineNumHint = topLineNum;
2186 mHorizOffsetHint = horizOffset;
2187 resize(x(), y(), w(), h());
2188 }
2189
scroll_(int topLineNum,int horizOffset)2190 void Fl_Text_Display::scroll_(int topLineNum, int horizOffset) {
2191 /* Limit the requested scroll position to allowable values */
2192 if (topLineNum > mNBufferLines + 3 - mNVisibleLines)
2193 topLineNum = mNBufferLines + 3 - mNVisibleLines;
2194 if (topLineNum < 1) topLineNum = 1;
2195
2196 if (horizOffset > longest_vline() - text_area.w)
2197 horizOffset = longest_vline() - text_area.w;
2198 if (horizOffset < 0) horizOffset = 0;
2199
2200 /* Do nothing if scroll position hasn't actually changed or there's no
2201 window to draw in yet */
2202 if (mHorizOffset == horizOffset && mTopLineNum == topLineNum)
2203 return;
2204
2205 /* If the vertical scroll position has changed, update the line
2206 starts array and related counters in the text display */
2207 offset_line_starts(topLineNum);
2208
2209 /* Just setting mHorizOffset is enough information for redisplay */
2210 mHorizOffset = horizOffset;
2211
2212 // redraw all text
2213 damage(FL_DAMAGE_EXPOSE);
2214 }
2215
2216 /*
2217 ** Update the minimum, maximum, slider size, page increment, and value
2218 ** for vertical scroll bar.
2219 */
update_v_scrollbar()2220 void Fl_Text_Display::update_v_scrollbar() {
2221 /* The Vert. scroll bar value and slider size directly represent the top
2222 line number, and the number of visible lines respectively. The scroll
2223 bar maximum value is chosen to generally represent the size of the whole
2224 buffer, with minor adjustments to keep the scroll bar widget happy */
2225 #ifdef DEBUG
2226 printf("Fl_Text_Display::update_v_scrollbar():\n"
2227 " mTopLineNum=%d, mNVisibleLines=%d, mNBufferLines=%d\n",
2228 mTopLineNum, mNVisibleLines, mNBufferLines);
2229 #endif // DEBUG
2230
2231 mVScrollBar->value(mTopLineNum, mNVisibleLines, 1, mNBufferLines+2);
2232 mVScrollBar->linesize(3);
2233 }
2234
2235 /*
2236 ** Update the minimum, maximum, slider size, page increment, and value
2237 ** for the horizontal scroll bar.
2238 */
update_h_scrollbar()2239 void Fl_Text_Display::update_h_scrollbar() {
2240 int sliderMax = max(longest_vline(), text_area.w + mHorizOffset);
2241 mHScrollBar->value( mHorizOffset, text_area.w, 0, sliderMax );
2242 }
2243
2244 /*
2245 ** Callbacks for drag or valueChanged on scroll bars
2246 */
v_scrollbar_cb(Fl_Scrollbar * b,Fl_Text_Display * textD)2247 void Fl_Text_Display::v_scrollbar_cb(Fl_Scrollbar* b, Fl_Text_Display* textD) {
2248 if (b->value() == textD->mTopLineNum) return;
2249 textD->scroll(b->value(), textD->mHorizOffset);
2250 }
2251
h_scrollbar_cb(Fl_Scrollbar * b,Fl_Text_Display * textD)2252 void Fl_Text_Display::h_scrollbar_cb(Fl_Scrollbar* b, Fl_Text_Display* textD) {
2253 if (b->value() == textD->mHorizOffset) return;
2254 textD->scroll(textD->mTopLineNum, b->value());
2255 }
2256
2257 /*
2258 ** Refresh the line number area. If clearAll is False, writes only over
2259 ** the character cell areas. Setting clearAll to True will clear out any
2260 ** stray marks outside of the character cell area, which might have been
2261 ** left from before a resize or font change.
2262 */
draw_line_numbers(bool)2263 void Fl_Text_Display::draw_line_numbers(bool /*clearAll*/) {
2264 #if 0
2265 // FIXME: don't want this yet, so will leave for another time
2266
2267 int y, line, visLine, nCols, lineStart;
2268 char lineNumString[12];
2269 int lineHeight = mMaxsize ? mMaxsize : textsize_;
2270 int charWidth = TMPFONTWIDTH; //mFontStruct->max_bounds.width;
2271
2272 /* Don't draw if mLineNumWidth == 0 (line numbers are hidden), or widget is
2273 not yet realized */
2274 if (mLineNumWidth == 0 || visible_r())
2275 return;
2276
2277 /* GC is allocated on demand, since not everyone will use line numbering */
2278 if (textD->lineNumGC == NULL) {
2279 XGCValues values;
2280 values.foreground = textD->lineNumFGPixel;
2281 values.background = textD->bgPixel;
2282 values.font = textD->fontStruct->fid;
2283 textD->lineNumGC = XtGetGC(textD->w,
2284 GCFont| GCForeground | GCBackground, &values);
2285 }
2286
2287 /* Erase the previous contents of the line number area, if requested */
2288 if (clearAll)
2289 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->lineNumLeft,
2290 textD->top, textD->lineNumWidth, textD->height, False);
2291
2292 /* Draw the line numbers, aligned to the text */
2293 nCols = min(11, textD->lineNumWidth / charWidth);
2294 y = textD->top;
2295 line = getAbsTopLineNum(textD);
2296 for (visLine=0; visLine < textD->nVisibleLines; visLine++) {
2297 lineStart = textD->lineStarts[visLine];
2298 if (lineStart != -1 && (lineStart==0 ||
2299 BufGetCharacter(textD->buffer, lineStart-1)=='\n')) {
2300 sprintf(lineNumString, "%*d", nCols, line);
2301 XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w),
2302 textD->lineNumGC, textD->lineNumLeft, y + textD->ascent,
2303 lineNumString, strlen(lineNumString));
2304 line++;
2305 } else {
2306 XClearArea(XtDisplay(textD->w), XtWindow(textD->w),
2307 textD->lineNumLeft, y, textD->lineNumWidth,
2308 textD->ascent + textD->descent, False);
2309 if (visLine == 0)
2310 line++;
2311 }
2312 y += lineHeight;
2313 }
2314 #endif
2315 }
2316
max(int i1,int i2)2317 static int max( int i1, int i2 ) {
2318 return i1 >= i2 ? i1 : i2;
2319 }
2320
min(int i1,int i2)2321 static int min( int i1, int i2 ) {
2322 return i1 <= i2 ? i1 : i2;
2323 }
2324
2325 /*
2326 ** Count the number of newlines in a null-terminated text string;
2327 */
countlines(const char * string)2328 static int countlines( const char *string ) {
2329 const char * c;
2330 int lineCount = 0;
2331
2332 if (!string) return 0;
2333
2334 for ( c = string; *c != '\0'; c++ )
2335 if ( *c == '\n' ) lineCount++;
2336 return lineCount;
2337 }
2338
2339 /*
2340 ** Return the width in pixels of the displayed line pointed to by "visLineNum"
2341 */
measure_vline(int visLineNum)2342 int Fl_Text_Display::measure_vline( int visLineNum ) {
2343 int i, width = 0, len, style, lineLen = vline_length( visLineNum );
2344 int charCount = 0, lineStartPos = mLineStarts[ visLineNum ];
2345 char expandedChar[ FL_TEXT_MAX_EXP_CHAR_LEN ];
2346
2347 if (lineStartPos < 0 || lineLen == 0) return 0;
2348 if ( mStyleBuffer == NULL ) {
2349 for ( i = 0; i < lineLen; i++ ) {
2350 len = mBuffer->expand_character( lineStartPos + i,
2351 charCount, expandedChar );
2352
2353 fl_font( textfont(), textsize() );
2354
2355 width += ( int ) fl_width( expandedChar, len );
2356
2357 charCount += len;
2358 }
2359 } else {
2360 for ( i = 0; i < lineLen; i++ ) {
2361 len = mBuffer->expand_character( lineStartPos + i,
2362 charCount, expandedChar );
2363 style = ( unsigned char ) mStyleBuffer->character(
2364 lineStartPos + i ) - 'A';
2365
2366 if (style < 0) style = 0;
2367 else if (style >= mNStyles) style = mNStyles - 1;
2368
2369 fl_font( mStyleTable[ style ].font, mStyleTable[ style ].size );
2370
2371 width += ( int ) fl_width( expandedChar, len );
2372
2373 charCount += len;
2374 }
2375 }
2376 return width;
2377 }
2378
2379 /*
2380 ** Return true if there are lines visible with no corresponding buffer text
2381 */
empty_vlines()2382 int Fl_Text_Display::empty_vlines() {
2383 return mNVisibleLines > 0 &&
2384 mLineStarts[ mNVisibleLines - 1 ] == -1;
2385 }
2386
2387 /*
2388 ** Return the length of a line (number of displayable characters) by examining
2389 ** entries in the line starts array rather than by scanning for newlines
2390 */
vline_length(int visLineNum)2391 int Fl_Text_Display::vline_length( int visLineNum ) {
2392 int nextLineStart, lineStartPos;
2393
2394 if (visLineNum < 0 || visLineNum >= mNVisibleLines)
2395 return (0);
2396
2397 lineStartPos = mLineStarts[ visLineNum ];
2398
2399 if ( lineStartPos == -1 )
2400 return 0;
2401 if ( visLineNum + 1 >= mNVisibleLines )
2402 return mLastChar - lineStartPos;
2403 nextLineStart = mLineStarts[ visLineNum + 1 ];
2404 if ( nextLineStart == -1 )
2405 return mLastChar - lineStartPos;
2406 if (wrap_uses_character(nextLineStart-1))
2407 return nextLineStart-1 - lineStartPos;
2408 return nextLineStart - lineStartPos;
2409 }
2410
2411 /*
2412 ** When continuous wrap is on, and the user inserts or deletes characters,
2413 ** wrapping can happen before and beyond the changed position. This routine
2414 ** finds the extent of the changes, and counts the deleted and inserted lines
2415 ** over that range. It also attempts to minimize the size of the range to
2416 ** what has to be counted and re-displayed, so the results can be useful
2417 ** both for delimiting where the line starts need to be recalculated, and
2418 ** for deciding what part of the text to redisplay.
2419 */
find_wrap_range(const char * deletedText,int pos,int nInserted,int nDeleted,int * modRangeStart,int * modRangeEnd,int * linesInserted,int * linesDeleted)2420 void Fl_Text_Display::find_wrap_range(const char *deletedText, int pos,
2421 int nInserted, int nDeleted, int *modRangeStart, int *modRangeEnd,
2422 int *linesInserted, int *linesDeleted) {
2423 int length, retPos, retLines, retLineStart, retLineEnd;
2424 Fl_Text_Buffer *deletedTextBuf, *buf = buffer();
2425 int nVisLines = mNVisibleLines;
2426 int *lineStarts = mLineStarts;
2427 int countFrom, countTo, lineStart, adjLineStart, i;
2428 int visLineNum = 0, nLines = 0;
2429
2430 /*
2431 ** Determine where to begin searching: either the previous newline, or
2432 ** if possible, limit to the start of the (original) previous displayed
2433 ** line, using information from the existing line starts array
2434 */
2435 if (pos >= mFirstChar && pos <= mLastChar) {
2436 for (i=nVisLines-1; i>0; i--)
2437 if (lineStarts[i] != -1 && pos >= lineStarts[i])
2438 break;
2439 if (i > 0) {
2440 countFrom = lineStarts[i-1];
2441 visLineNum = i-1;
2442 } else
2443 countFrom = buf->line_start(pos);
2444 } else
2445 countFrom = buf->line_start(pos);
2446
2447
2448 /*
2449 ** Move forward through the (new) text one line at a time, counting
2450 ** displayed lines, and looking for either a real newline, or for the
2451 ** line starts to re-sync with the original line starts array
2452 */
2453 lineStart = countFrom;
2454 *modRangeStart = countFrom;
2455 for (;;) {
2456
2457 /* advance to the next line. If the line ended in a real newline
2458 or the end of the buffer, that's far enough */
2459 wrapped_line_counter(buf, lineStart, buf->length(), 1, true, 0,
2460 &retPos, &retLines, &retLineStart, &retLineEnd);
2461 if (retPos >= buf->length()) {
2462 countTo = buf->length();
2463 *modRangeEnd = countTo;
2464 if (retPos != retLineEnd)
2465 nLines++;
2466 break;
2467 } else
2468 lineStart = retPos;
2469 nLines++;
2470 if (lineStart > pos + nInserted &&
2471 buf->character(lineStart-1) == '\n') {
2472 countTo = lineStart;
2473 *modRangeEnd = lineStart;
2474 break;
2475 }
2476
2477 /* Don't try to resync in continuous wrap mode with non-fixed font
2478 sizes; it would result in a chicken-and-egg dependency between
2479 the calculations for the inserted and the deleted lines.
2480 If we're in that mode, the number of deleted lines is calculated in
2481 advance, without resynchronization, so we shouldn't resynchronize
2482 for the inserted lines either. */
2483 if (mSuppressResync)
2484 continue;
2485
2486 /* check for synchronization with the original line starts array
2487 before pos, if so, the modified range can begin later */
2488 if (lineStart <= pos) {
2489 while (visLineNum<nVisLines && lineStarts[visLineNum] < lineStart)
2490 visLineNum++;
2491 if (visLineNum < nVisLines && lineStarts[visLineNum] == lineStart) {
2492 countFrom = lineStart;
2493 nLines = 0;
2494 if (visLineNum+1 < nVisLines && lineStarts[visLineNum+1] != -1)
2495 *modRangeStart = min(pos, lineStarts[visLineNum+1]-1);
2496 else
2497 *modRangeStart = countFrom;
2498 } else
2499 *modRangeStart = min(*modRangeStart, lineStart-1);
2500 }
2501
2502 /* check for synchronization with the original line starts array
2503 after pos, if so, the modified range can end early */
2504 else if (lineStart > pos + nInserted) {
2505 adjLineStart = lineStart - nInserted + nDeleted;
2506 while (visLineNum<nVisLines && lineStarts[visLineNum]<adjLineStart)
2507 visLineNum++;
2508 if (visLineNum < nVisLines && lineStarts[visLineNum] != -1 &&
2509 lineStarts[visLineNum] == adjLineStart) {
2510 countTo = line_end(lineStart, true);
2511 *modRangeEnd = lineStart;
2512 break;
2513 }
2514 }
2515 }
2516 *linesInserted = nLines;
2517
2518
2519 /* Count deleted lines between countFrom and countTo as the text existed
2520 before the modification (that is, as if the text between pos and
2521 pos+nInserted were replaced by "deletedText"). This extra context is
2522 necessary because wrapping can occur outside of the modified region
2523 as a result of adding or deleting text in the region. This is done by
2524 creating a textBuffer containing the deleted text and the necessary
2525 additional context, and calling the wrappedLineCounter on it.
2526
2527 NOTE: This must not be done in continuous wrap mode when the font
2528 width is not fixed. In that case, the calculation would try
2529 to access style information that is no longer available (deleted
2530 text), or out of date (updated highlighting), possibly leading
2531 to completely wrong calculations and/or even crashes eventually.
2532 (This is not theoretical; it really happened.)
2533
2534 In that case, the calculation of the number of deleted lines
2535 has happened before the buffer was modified (only in that case,
2536 because resynchronization of the line starts is impossible
2537 in that case, which makes the whole calculation less efficient).
2538 */
2539 if (mSuppressResync) {
2540 *linesDeleted = mNLinesDeleted;
2541 mSuppressResync = 0;
2542 return;
2543 }
2544
2545 length = (pos-countFrom) + nDeleted +(countTo-(pos+nInserted));
2546 deletedTextBuf = new Fl_Text_Buffer(length);
2547 deletedTextBuf->copy(buffer(), countFrom, pos, 0);
2548 if (nDeleted != 0)
2549 deletedTextBuf->insert(pos-countFrom, deletedText);
2550 deletedTextBuf->copy(buffer(),
2551 pos+nInserted, countTo, pos-countFrom+nDeleted);
2552 /* Note that we need to take into account an offset for the style buffer:
2553 the deletedTextBuf can be out of sync with the style buffer. */
2554 wrapped_line_counter(deletedTextBuf, 0, length, INT_MAX, true,
2555 countFrom, &retPos, &retLines, &retLineStart, &retLineEnd, false);
2556 delete deletedTextBuf;
2557 *linesDeleted = retLines;
2558 mSuppressResync = 0;
2559 }
2560
2561 /*
2562 ** This is a stripped-down version of the findWrapRange() function above,
2563 ** intended to be used to calculate the number of "deleted" lines during
2564 ** a buffer modification. It is called _before_ the modification takes place.
2565 **
2566 ** This function should only be called in continuous wrap mode with a
2567 ** non-fixed font width. In that case, it is impossible to calculate
2568 ** the number of deleted lines, because the necessary style information
2569 ** is no longer available _after_ the modification. In other cases, we
2570 ** can still perform the calculation afterwards (possibly even more
2571 ** efficiently).
2572 */
measure_deleted_lines(int pos,int nDeleted)2573 void Fl_Text_Display::measure_deleted_lines(int pos, int nDeleted) {
2574 int retPos, retLines, retLineStart, retLineEnd;
2575 Fl_Text_Buffer *buf = buffer();
2576 int nVisLines = mNVisibleLines;
2577 int *lineStarts = mLineStarts;
2578 int countFrom, lineStart;
2579 int nLines = 0, i;
2580 /*
2581 ** Determine where to begin searching: either the previous newline, or
2582 ** if possible, limit to the start of the (original) previous displayed
2583 ** line, using information from the existing line starts array
2584 */
2585 if (pos >= mFirstChar && pos <= mLastChar) {
2586 for (i=nVisLines-1; i>0; i--)
2587 if (lineStarts[i] != -1 && pos >= lineStarts[i])
2588 break;
2589 if (i > 0) countFrom = lineStarts[i-1];
2590 else countFrom = buf->line_start(pos);
2591 } else
2592 countFrom = buf->line_start(pos);
2593
2594 /*
2595 ** Move forward through the (new) text one line at a time, counting
2596 ** displayed lines, and looking for either a real newline, or for the
2597 ** line starts to re-sync with the original line starts array
2598 */
2599 lineStart = countFrom;
2600 for (;;) {
2601 /* advance to the next line. If the line ended in a real newline
2602 or the end of the buffer, that's far enough */
2603 wrapped_line_counter(buf, lineStart, buf->length(), 1, true, 0,
2604 &retPos, &retLines, &retLineStart, &retLineEnd);
2605 if (retPos >= buf->length()) {
2606 if (retPos != retLineEnd)
2607 nLines++;
2608 break;
2609 } else
2610 lineStart = retPos;
2611 nLines++;
2612 if (lineStart > pos + nDeleted &&
2613 buf->character(lineStart-1) == '\n') {
2614 break;
2615 }
2616
2617 /* Unlike in the findWrapRange() function above, we don't try to
2618 resync with the line starts, because we don't know the length
2619 of the inserted text yet, nor the updated style information.
2620
2621 Because of that, we also shouldn't resync with the line starts
2622 after the modification either, because we must perform the
2623 calculations for the deleted and inserted lines in the same way.
2624
2625 This can result in some unnecessary recalculation and redrawing
2626 overhead, and therefore we should only use this two-phase mode
2627 of calculation when it's really needed (continuous wrap + variable
2628 font width). */
2629 }
2630 mNLinesDeleted = nLines;
2631 mSuppressResync = 1;
2632 }
2633
2634 /*
2635 ** Count forward from startPos to either maxPos or maxLines (whichever is
2636 ** reached first), and return all relevant positions and line count.
2637 ** The provided textBuffer may differ from the actual text buffer of the
2638 ** widget. In that case it must be a (partial) copy of the actual text buffer
2639 ** and the styleBufOffset argument must indicate the starting position of the
2640 ** copy, to take into account the correct style information.
2641 **
2642 ** Returned values:
2643 **
2644 ** retPos: Position where counting ended. When counting lines, the
2645 ** position returned is the start of the line "maxLines"
2646 ** lines beyond "startPos".
2647 ** retLines: Number of line breaks counted
2648 ** retLineStart: Start of the line where counting ended
2649 ** retLineEnd: End position of the last line traversed
2650 */
wrapped_line_counter(Fl_Text_Buffer * buf,int startPos,int maxPos,int maxLines,bool startPosIsLineStart,int styleBufOffset,int * retPos,int * retLines,int * retLineStart,int * retLineEnd,bool countLastLineMissingNewLine)2651 void Fl_Text_Display::wrapped_line_counter(Fl_Text_Buffer *buf, int startPos,
2652 int maxPos, int maxLines, bool startPosIsLineStart, int styleBufOffset,
2653 int *retPos, int *retLines, int *retLineStart, int *retLineEnd,
2654 bool countLastLineMissingNewLine) {
2655 int lineStart, newLineStart = 0, b, p, colNum, wrapMargin;
2656 int maxWidth, i, foundBreak, width;
2657 bool countPixels;
2658 int nLines = 0, tabDist = buffer()->tab_distance();
2659 unsigned char c;
2660 char nullSubsChar = buffer()->null_substitution_character();
2661
2662 /* If the font is fixed, or there's a wrap margin set, it's more efficient
2663 to measure in columns, than to count pixels. Determine if we can count
2664 in columns (countPixels == False) or must count pixels (countPixels ==
2665 True), and set the wrap target for either pixels or columns */
2666 if (mFixedFontWidth != -1 || mWrapMargin != 0) {
2667 countPixels = false;
2668 wrapMargin = mWrapMargin ? mWrapMargin : text_area.w / (mFixedFontWidth + 1);
2669 maxWidth = INT_MAX;
2670 } else {
2671 countPixels = true;
2672 wrapMargin = INT_MAX;
2673 maxWidth = text_area.w;
2674 }
2675
2676 /* Find the start of the line if the start pos is not marked as a
2677 line start. */
2678 if (startPosIsLineStart)
2679 lineStart = startPos;
2680 else
2681 lineStart = line_start(startPos);
2682
2683 /*
2684 ** Loop until position exceeds maxPos or line count exceeds maxLines.
2685 ** (actually, contines beyond maxPos to end of line containing maxPos,
2686 ** in case later characters cause a word wrap back before maxPos)
2687 */
2688 colNum = 0;
2689 width = 0;
2690 for (p=lineStart; p<buf->length(); p++) {
2691 c = (unsigned char)buf->character(p);
2692
2693 /* If the character was a newline, count the line and start over,
2694 otherwise, add it to the width and column counts */
2695 if (c == '\n') {
2696 if (p >= maxPos) {
2697 *retPos = maxPos;
2698 *retLines = nLines;
2699 *retLineStart = lineStart;
2700 *retLineEnd = maxPos;
2701 return;
2702 }
2703 nLines++;
2704 if (nLines >= maxLines) {
2705 *retPos = p + 1;
2706 *retLines = nLines;
2707 *retLineStart = p + 1;
2708 *retLineEnd = p;
2709 return;
2710 }
2711 lineStart = p + 1;
2712 colNum = 0;
2713 width = 0;
2714 } else {
2715 colNum += Fl_Text_Buffer::character_width(c, colNum, tabDist, nullSubsChar);
2716 if (countPixels)
2717 width += measure_proportional_character(c, colNum, p+styleBufOffset);
2718 }
2719
2720 /* If character exceeded wrap margin, find the break point
2721 and wrap there */
2722 if (colNum > wrapMargin || width > maxWidth) {
2723 foundBreak = false;
2724 for (b=p; b>=lineStart; b--) {
2725 c = (unsigned char)buf->character(b);
2726 if (c == '\t' || c == ' ') {
2727 newLineStart = b + 1;
2728 if (countPixels) {
2729 colNum = 0;
2730 width = 0;
2731 for (i=b+1; i<p+1; i++) {
2732 width += measure_proportional_character(
2733 buf->character(i), colNum,
2734 i+styleBufOffset);
2735 colNum++;
2736 }
2737 } else
2738 colNum = buf->count_displayed_characters(b+1, p+1);
2739 foundBreak = true;
2740 break;
2741 }
2742 }
2743 if (!foundBreak) { /* no whitespace, just break at margin */
2744 newLineStart = max(p, lineStart+1);
2745 colNum = Fl_Text_Buffer::character_width(c, colNum, tabDist, nullSubsChar);
2746 if (countPixels)
2747 width = measure_proportional_character(c, colNum, p+styleBufOffset);
2748 }
2749 if (p >= maxPos) {
2750 *retPos = maxPos;
2751 *retLines = maxPos < newLineStart ? nLines : nLines + 1;
2752 *retLineStart = maxPos < newLineStart ? lineStart :
2753 newLineStart;
2754 *retLineEnd = maxPos;
2755 return;
2756 }
2757 nLines++;
2758 if (nLines >= maxLines) {
2759 *retPos = foundBreak ? b + 1 : max(p, lineStart+1);
2760 *retLines = nLines;
2761 *retLineStart = lineStart;
2762 *retLineEnd = foundBreak ? b : p;
2763 return;
2764 }
2765 lineStart = newLineStart;
2766 }
2767 }
2768
2769 /* reached end of buffer before reaching pos or line target */
2770 *retPos = buf->length();
2771 *retLines = nLines;
2772 if (countLastLineMissingNewLine && colNum > 0)
2773 ++(*retLines);
2774 *retLineStart = lineStart;
2775 *retLineEnd = buf->length();
2776 }
2777
2778 /*
2779 ** Measure the width in pixels of a character "c" at a particular column
2780 ** "colNum" and buffer position "pos". This is for measuring characters in
2781 ** proportional or mixed-width highlighting fonts.
2782 **
2783 ** A note about proportional and mixed-width fonts: the mixed width and
2784 ** proportional font code in nedit does not get much use in general editing,
2785 ** because nedit doesn't allow per-language-mode fonts, and editing programs
2786 ** in a proportional font is usually a bad idea, so very few users would
2787 ** choose a proportional font as a default. There are still probably mixed-
2788 ** width syntax highlighting cases where things don't redraw properly for
2789 ** insertion/deletion, though static display and wrapping and resizing
2790 ** should now be solid because they are now used for online help display.
2791 */
measure_proportional_character(char c,int colNum,int pos)2792 int Fl_Text_Display::measure_proportional_character(char c, int colNum, int pos) {
2793 int charLen, style;
2794 char expChar[ FL_TEXT_MAX_EXP_CHAR_LEN ];
2795 Fl_Text_Buffer *styleBuf = mStyleBuffer;
2796
2797 charLen = Fl_Text_Buffer::expand_character(c, colNum, expChar,
2798 buffer()->tab_distance(), buffer()->null_substitution_character());
2799 if (styleBuf == 0) {
2800 style = 0;
2801 } else {
2802 style = (unsigned char)styleBuf->character(pos);
2803 if (style == mUnfinishedStyle && mUnfinishedHighlightCB) {
2804 /* encountered "unfinished" style, trigger parsing */
2805 (mUnfinishedHighlightCB)(pos, mHighlightCBArg);
2806 style = (unsigned char)styleBuf->character(pos);
2807 }
2808 }
2809 return string_width(expChar, charLen, style);
2810 }
2811
2812 /*
2813 ** Finds both the end of the current line and the start of the next line. Why?
2814 ** In continuous wrap mode, if you need to know both, figuring out one from the
2815 ** other can be expensive or error prone. The problem comes when there's a
2816 ** trailing space or tab just before the end of the buffer. To translate an
2817 ** end of line value to or from the next lines start value, you need to know
2818 ** whether the trailing space or tab is being used as a line break or just a
2819 ** normal character, and to find that out would otherwise require counting all
2820 ** the way back to the beginning of the line.
2821 */
find_line_end(int startPos,bool startPosIsLineStart,int * lineEnd,int * nextLineStart)2822 void Fl_Text_Display::find_line_end(int startPos, bool startPosIsLineStart,
2823 int *lineEnd, int *nextLineStart) {
2824 int retLines, retLineStart;
2825
2826 /* if we're not wrapping use more efficient BufEndOfLine */
2827 if (!mContinuousWrap) {
2828 *lineEnd = buffer()->line_end(startPos);
2829 *nextLineStart = min(buffer()->length(), *lineEnd + 1);
2830 return;
2831 }
2832
2833 /* use the wrapped line counter routine to count forward one line */
2834 wrapped_line_counter(buffer(), startPos, buffer()->length(),
2835 1, startPosIsLineStart, 0, nextLineStart, &retLines,
2836 &retLineStart, lineEnd);
2837 return;
2838 }
2839
2840 /*
2841 ** Line breaks in continuous wrap mode usually happen at newlines or
2842 ** whitespace. This line-terminating character is not included in line
2843 ** width measurements and has a special status as a non-visible character.
2844 ** However, lines with no whitespace are wrapped without the benefit of a
2845 ** line terminating character, and this distinction causes endless trouble
2846 ** with all of the text display code which was originally written without
2847 ** continuous wrap mode and always expects to wrap at a newline character.
2848 **
2849 ** Given the position of the end of the line, as returned by TextDEndOfLine
2850 ** or BufEndOfLine, this returns true if there is a line terminating
2851 ** character, and false if there's not. On the last character in the
2852 ** buffer, this function can't tell for certain whether a trailing space was
2853 ** used as a wrap point, and just guesses that it wasn't. So if an exact
2854 ** accounting is necessary, don't use this function.
2855 */
wrap_uses_character(int lineEndPos)2856 int Fl_Text_Display::wrap_uses_character(int lineEndPos) {
2857 char c;
2858
2859 if (!mContinuousWrap || lineEndPos == buffer()->length())
2860 return 1;
2861
2862 c = buffer()->character(lineEndPos);
2863 return c == '\n' || ((c == '\t' || c == ' ') &&
2864 lineEndPos + 1 != buffer()->length());
2865 }
2866
2867 /*
2868 ** Return true if the selection "sel" is rectangular, and touches a
2869 ** buffer position withing "rangeStart" to "rangeEnd"
2870 */
range_touches_selection(Fl_Text_Selection * sel,int rangeStart,int rangeEnd)2871 int Fl_Text_Display::range_touches_selection(Fl_Text_Selection *sel,
2872 int rangeStart, int rangeEnd) {
2873 return sel->selected() && sel->rectangular() && sel->end() >= rangeStart &&
2874 sel->start() <= rangeEnd;
2875 }
2876
2877 /*
2878 ** Extend the range of a redraw request (from *start to *end) with additional
2879 ** redraw requests resulting from changes to the attached style buffer (which
2880 ** contains auxiliary information for coloring or styling text).
2881 */
extend_range_for_styles(int * startpos,int * endpos)2882 void Fl_Text_Display::extend_range_for_styles( int *startpos, int *endpos ) {
2883 Fl_Text_Selection * sel = mStyleBuffer->primary_selection();
2884 int extended = 0;
2885
2886 /* The peculiar protocol used here is that modifications to the style
2887 buffer are marked by selecting them with the buffer's primary Fl_Text_Selection.
2888 The style buffer is usually modified in response to a modify callback on
2889 the text buffer BEFORE Fl_Text_Display.c's modify callback, so that it can keep
2890 the style buffer in step with the text buffer. The style-update
2891 callback can't just call for a redraw, because Fl_Text_Display hasn't processed
2892 the original text changes yet. Anyhow, to minimize redrawing and to
2893 avoid the complexity of scheduling redraws later, this simple protocol
2894 tells the text display's buffer modify callback to extend it's redraw
2895 range to show the text color/and font changes as well. */
2896 if ( sel->selected() ) {
2897 if ( sel->start() < *startpos ) {
2898 *startpos = sel->start();
2899 extended = 1;
2900 }
2901 if ( sel->end() > *endpos ) {
2902 *endpos = sel->end();
2903 extended = 1;
2904 }
2905 }
2906
2907 /* If the Fl_Text_Selection was extended due to a style change, and some of the
2908 fonts don't match in spacing, extend redraw area to end of line to
2909 redraw characters exposed by possible font size changes */
2910 if ( mFixedFontWidth == -1 && extended )
2911 *endpos = mBuffer->line_end( *endpos ) + 1;
2912 }
2913
2914 // The draw() method. It tries to minimize what is draw as much as possible.
draw(void)2915 void Fl_Text_Display::draw(void) {
2916 // don't even try if there is no associated text buffer!
2917 if (!buffer()) { draw_box(); return; }
2918
2919 fl_push_clip(x(),y(),w(),h()); // prevent drawing outside widget area
2920
2921 // draw the non-text, non-scrollbar areas.
2922 if (damage() & FL_DAMAGE_ALL) {
2923 // printf("drawing all (box = %d)\n", box());
2924 // draw the box()
2925 int W = w(), H = h();
2926 draw_box(box(), x(), y(), W, H, color());
2927
2928 if (mHScrollBar->visible())
2929 W -= scrollbar_width();
2930 if (mVScrollBar->visible())
2931 H -= scrollbar_width();
2932
2933 // left margin
2934 fl_rectf(text_area.x-LEFT_MARGIN, text_area.y-TOP_MARGIN,
2935 LEFT_MARGIN, text_area.h+TOP_MARGIN+BOTTOM_MARGIN,
2936 color());
2937
2938 // right margin
2939 fl_rectf(text_area.x+text_area.w, text_area.y-TOP_MARGIN,
2940 RIGHT_MARGIN, text_area.h+TOP_MARGIN+BOTTOM_MARGIN,
2941 color());
2942
2943 // top margin
2944 fl_rectf(text_area.x, text_area.y-TOP_MARGIN,
2945 text_area.w, TOP_MARGIN, color());
2946
2947 // bottom margin
2948 fl_rectf(text_area.x, text_area.y+text_area.h,
2949 text_area.w, BOTTOM_MARGIN, color());
2950
2951 // draw that little box in the corner of the scrollbars
2952 if (mVScrollBar->visible() && mHScrollBar->visible())
2953 fl_rectf(mVScrollBar->x(), mHScrollBar->y(),
2954 mVScrollBar->w(), mHScrollBar->h(),
2955 FL_GRAY);
2956
2957 // blank the previous cursor protrusions
2958 }
2959 else if (damage() & (FL_DAMAGE_SCROLL | FL_DAMAGE_EXPOSE)) {
2960 // printf("blanking previous cursor extrusions at Y: %d\n", mCursorOldY);
2961 // CET - FIXME - save old cursor position instead and just draw side needed?
2962 fl_push_clip(text_area.x-LEFT_MARGIN,
2963 text_area.y,
2964 text_area.w+LEFT_MARGIN+RIGHT_MARGIN,
2965 text_area.h);
2966 fl_rectf(text_area.x-LEFT_MARGIN, mCursorOldY,
2967 LEFT_MARGIN, mMaxsize, color());
2968 fl_rectf(text_area.x+text_area.w, mCursorOldY,
2969 RIGHT_MARGIN, mMaxsize, color());
2970 fl_pop_clip();
2971 }
2972
2973 // draw the scrollbars
2974 if (damage() & (FL_DAMAGE_ALL | FL_DAMAGE_CHILD)) {
2975 mVScrollBar->damage(FL_DAMAGE_ALL);
2976 mHScrollBar->damage(FL_DAMAGE_ALL);
2977 }
2978 update_child(*mVScrollBar);
2979 update_child(*mHScrollBar);
2980
2981 // draw all of the text
2982 if (damage() & (FL_DAMAGE_ALL | FL_DAMAGE_EXPOSE)) {
2983 //printf("drawing all text\n");
2984 int X, Y, W, H;
2985 if (fl_clip_box(text_area.x, text_area.y, text_area.w, text_area.h,
2986 X, Y, W, H)) {
2987 // Draw text using the intersected clipping box...
2988 // (this sets the clipping internally)
2989 draw_text(X, Y, W, H);
2990 } else {
2991 // Draw the whole area...
2992 draw_text(text_area.x, text_area.y, text_area.w, text_area.h);
2993 }
2994 }
2995 else if (damage() & FL_DAMAGE_SCROLL) {
2996 // draw some lines of text
2997 fl_push_clip(text_area.x, text_area.y,
2998 text_area.w, text_area.h);
2999 //printf("drawing text from %d to %d\n", damage_range1_start, damage_range1_end);
3000 draw_range(damage_range1_start, damage_range1_end);
3001 if (damage_range2_end != -1) {
3002 //printf("drawing text from %d to %d\n", damage_range2_start, damage_range2_end);
3003 draw_range(damage_range2_start, damage_range2_end);
3004 }
3005 damage_range1_start = damage_range1_end = -1;
3006 damage_range2_start = damage_range2_end = -1;
3007 fl_pop_clip();
3008 }
3009
3010 // draw the text cursor
3011 if (damage() & (FL_DAMAGE_ALL | FL_DAMAGE_SCROLL | FL_DAMAGE_EXPOSE)
3012 && !buffer()->primary_selection()->selected() &&
3013 mCursorOn && Fl::focus() == this ) {
3014 fl_push_clip(text_area.x-LEFT_MARGIN,
3015 text_area.y,
3016 text_area.w+LEFT_MARGIN+RIGHT_MARGIN,
3017 text_area.h);
3018
3019 int X, Y;
3020 if (position_to_xy(mCursorPos, &X, &Y)) draw_cursor(X, Y);
3021 // else puts("position_to_xy() failed - unable to draw cursor!");
3022 //printf("drew cursor at pos: %d (%d,%d)\n", mCursorPos, X, Y);
3023 mCursorOldY = Y;
3024 fl_pop_clip();
3025 }
3026 fl_pop_clip();
3027 }
3028
3029 // this processes drag events due to mouse for Fl_Text_Display and
3030 // also drags due to cursor movement with shift held down for
3031 // Fl_Text_Editor
fl_text_drag_me(int pos,Fl_Text_Display * d)3032 void fl_text_drag_me(int pos, Fl_Text_Display* d) {
3033 if (d->dragType == Fl_Text_Display::DRAG_CHAR) {
3034 if (pos >= d->dragPos) {
3035 d->buffer()->select(d->dragPos, pos);
3036 } else {
3037 d->buffer()->select(pos, d->dragPos);
3038 }
3039 d->insert_position(pos);
3040 } else if (d->dragType == Fl_Text_Display::DRAG_WORD) {
3041 if (pos >= d->dragPos) {
3042 d->insert_position(d->word_end(pos));
3043 d->buffer()->select(d->word_start(d->dragPos), d->word_end(pos));
3044 } else {
3045 d->insert_position(d->word_start(pos));
3046 d->buffer()->select(d->word_start(pos), d->word_end(d->dragPos));
3047 }
3048 } else if (d->dragType == Fl_Text_Display::DRAG_LINE) {
3049 if (pos >= d->dragPos) {
3050 d->insert_position(d->buffer()->line_end(pos)+1);
3051 d->buffer()->select(d->buffer()->line_start(d->dragPos),
3052 d->buffer()->line_end(pos)+1);
3053 } else {
3054 d->insert_position(d->buffer()->line_start(pos));
3055 d->buffer()->select(d->buffer()->line_start(pos),
3056 d->buffer()->line_end(d->dragPos)+1);
3057 }
3058 }
3059 }
3060
3061 // This timer event scrolls the text view proportionally to
3062 // how far the mouse pointer has left the text area. This
3063 // allows for smooth scrolling without "wiggeling" the mouse.
scroll_timer_cb(void * user_data)3064 void Fl_Text_Display::scroll_timer_cb(void *user_data) {
3065 Fl_Text_Display *w = (Fl_Text_Display*)user_data;
3066 int pos;
3067 switch (scroll_direction) {
3068 case 1: // mouse is to the right, scroll left
3069 w->scroll(w->mTopLineNum, w->mHorizOffset + scroll_amount);
3070 pos = w->xy_to_position(w->text_area.x + w->text_area.w, scroll_y, CURSOR_POS);
3071 break;
3072 case 2: // mouse is to the left, scroll right
3073 w->scroll(w->mTopLineNum, w->mHorizOffset + scroll_amount);
3074 pos = w->xy_to_position(w->text_area.x, scroll_y, CURSOR_POS);
3075 break;
3076 case 3: // mouse is above, scroll down
3077 w->scroll(w->mTopLineNum + scroll_amount, w->mHorizOffset);
3078 pos = w->xy_to_position(scroll_x, w->text_area.y, CURSOR_POS);
3079 break;
3080 case 4: // mouse is below, scroll up
3081 w->scroll(w->mTopLineNum + scroll_amount, w->mHorizOffset);
3082 pos = w->xy_to_position(scroll_x, w->text_area.y + w->text_area.h, CURSOR_POS);
3083 break;
3084 default:
3085 return;
3086 }
3087 fl_text_drag_me(pos, w);
3088 Fl::repeat_timeout(.1, scroll_timer_cb, user_data);
3089 }
3090
3091
handle(int event)3092 int Fl_Text_Display::handle(int event) {
3093 if (!buffer()) return 0;
3094 // This isn't very elegant!
3095 if (!Fl::event_inside(text_area.x, text_area.y, text_area.w, text_area.h) &&
3096 !dragging && event != FL_LEAVE && event != FL_ENTER &&
3097 event != FL_MOVE && event != FL_FOCUS && event != FL_UNFOCUS &&
3098 event != FL_KEYBOARD && event != FL_KEYUP) {
3099 return Fl_Group::handle(event);
3100 }
3101
3102 switch (event) {
3103 case FL_ENTER:
3104 case FL_MOVE:
3105 if (active_r()) {
3106 if (Fl::event_inside(text_area.x, text_area.y, text_area.w,
3107 text_area.h)) window()->cursor(FL_CURSOR_INSERT);
3108 else window()->cursor(FL_CURSOR_DEFAULT);
3109 return 1;
3110 } else {
3111 return 0;
3112 }
3113
3114 case FL_LEAVE:
3115 case FL_HIDE:
3116 if (active_r() && window()) {
3117 window()->cursor(FL_CURSOR_DEFAULT);
3118
3119 return 1;
3120 } else {
3121 return 0;
3122 }
3123
3124 case FL_PUSH: {
3125 if (active_r() && window()) {
3126 if (Fl::event_inside(text_area.x, text_area.y, text_area.w,
3127 text_area.h)) window()->cursor(FL_CURSOR_INSERT);
3128 else window()->cursor(FL_CURSOR_DEFAULT);
3129 }
3130
3131 if (Fl::focus() != this) {
3132 Fl::focus(this);
3133 handle(FL_FOCUS);
3134 }
3135 if (Fl_Group::handle(event)) return 1;
3136 if (Fl::event_state()&FL_SHIFT) return handle(FL_DRAG);
3137 dragging = 1;
3138 int pos = xy_to_position(Fl::event_x(), Fl::event_y(), CURSOR_POS);
3139 dragType = Fl::event_clicks();
3140 dragPos = pos;
3141 if (dragType == DRAG_CHAR)
3142 buffer()->unselect();
3143 else if (dragType == DRAG_WORD)
3144 buffer()->select(word_start(pos), word_end(pos));
3145 else if (dragType == DRAG_LINE)
3146 buffer()->select(buffer()->line_start(pos), buffer()->line_end(pos)+1);
3147
3148 if (buffer()->primary_selection()->selected())
3149 insert_position(buffer()->primary_selection()->end());
3150 else
3151 insert_position(pos);
3152 show_insert_position();
3153 return 1;
3154 }
3155
3156 case FL_DRAG: {
3157 if (dragType < 0) return 1;
3158 int X = Fl::event_x(), Y = Fl::event_y(), pos;
3159 // if we leave the text_area, we start a timer event
3160 // that will take care of scrolling and selecting
3161 if (Y < text_area.y) {
3162 scroll_x = X;
3163 scroll_amount = (Y - text_area.y) / 5 - 1;
3164 if (!scroll_direction) {
3165 Fl::add_timeout(.01, scroll_timer_cb, this);
3166 }
3167 scroll_direction = 3;
3168 } else if (Y >= text_area.y+text_area.h) {
3169 scroll_x = X;
3170 scroll_amount = (Y - text_area.y - text_area.h) / 5 + 1;
3171 if (!scroll_direction) {
3172 Fl::add_timeout(.01, scroll_timer_cb, this);
3173 }
3174 scroll_direction = 4;
3175 } else if (X < text_area.x) {
3176 scroll_y = Y;
3177 scroll_amount = (X - text_area.x) / 2 - 1;
3178 if (!scroll_direction) {
3179 Fl::add_timeout(.01, scroll_timer_cb, this);
3180 }
3181 scroll_direction = 2;
3182 } else if (X >= text_area.x+text_area.w) {
3183 scroll_y = Y;
3184 scroll_amount = (X - text_area.x - text_area.w) / 2 + 1;
3185 if (!scroll_direction) {
3186 Fl::add_timeout(.01, scroll_timer_cb, this);
3187 }
3188 scroll_direction = 1;
3189 } else {
3190 if (scroll_direction) {
3191 Fl::remove_timeout(scroll_timer_cb, this);
3192 scroll_direction = 0;
3193 }
3194 pos = xy_to_position(X, Y, CURSOR_POS);
3195 fl_text_drag_me(pos, this);
3196 }
3197 return 1;
3198 }
3199
3200 case FL_RELEASE: {
3201 dragging = 0;
3202 if (scroll_direction) {
3203 Fl::remove_timeout(scroll_timer_cb, this);
3204 scroll_direction = 0;
3205 }
3206
3207 // convert from WORD or LINE selection to CHAR
3208 if (insert_position() >= dragPos)
3209 dragPos = buffer()->primary_selection()->start();
3210 else
3211 dragPos = buffer()->primary_selection()->end();
3212 dragType = DRAG_CHAR;
3213
3214 const char* copy = buffer()->selection_text();
3215 if (*copy) Fl::copy(copy, strlen(copy), 0);
3216 free((void*)copy);
3217 return 1;
3218 }
3219
3220 case FL_MOUSEWHEEL:
3221 if (Fl::event_dy()) return mVScrollBar->handle(event);
3222 else return mHScrollBar->handle(event);
3223
3224 case FL_UNFOCUS:
3225 if (active_r() && window()) window()->cursor(FL_CURSOR_DEFAULT);
3226 case FL_FOCUS:
3227 if (buffer()->selected()) {
3228 int start, end;
3229 if (buffer()->selection_position(&start, &end))
3230 redisplay_range(start, end);
3231 }
3232 if (buffer()->secondary_selected()) {
3233 int start, end;
3234 if (buffer()->secondary_selection_position(&start, &end))
3235 redisplay_range(start, end);
3236 }
3237 if (buffer()->highlight()) {
3238 int start, end;
3239 if (buffer()->highlight_position(&start, &end))
3240 redisplay_range(start, end);
3241 }
3242 return 1;
3243
3244 case FL_KEYBOARD:
3245 // Copy?
3246 if ((Fl::event_state()&(FL_CTRL|FL_COMMAND)) && Fl::event_key()=='c') {
3247 if (!buffer()->selected()) return 1;
3248 const char *copy = buffer()->selection_text();
3249 if (*copy) Fl::copy(copy, strlen(copy), 1);
3250 free((void*)copy);
3251 return 1;
3252 }
3253
3254 // Select all ?
3255 if ((Fl::event_state()&(FL_CTRL|FL_COMMAND)) && Fl::event_key()=='a') {
3256 buffer()->select(0,buffer()->length());
3257 return 1;
3258 }
3259
3260 if (mVScrollBar->handle(event)) return 1;
3261 if (mHScrollBar->handle(event)) return 1;
3262
3263 break;
3264 }
3265
3266 return 0;
3267 }
3268
3269
3270 //
3271 // End of "$Id: Fl_Text_Display.cxx 6105 2008-04-21 21:03:22Z matt $".
3272 //
3273