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