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