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