fltk 1.3.0rc3
About: FLTK (Fast Light Tool Kit) is a cross-platform C++ GUI toolkit for UNIX/Linux (X11), Microsoft Windows, and MacOS X. Release candidate.
  SfR Fresh Dox: fltk-1.3.0rc3-source.tar.gz ("inofficial" and yet experimental doxygen-generated source code documentation)  

Fl_Text_Display.cxx

Go to the documentation of this file.
00001 //
00002 // "$Id: Fl_Text_Display.cxx 8078 2010-12-20 17:32:36Z AlbrechtS $"
00003 //
00004 // Copyright 2001-2010 by Bill Spitzak and others.
00005 // Original code Copyright Mark Edel.  Permission to distribute under
00006 // the LGPL for the FLTK library granted by Mark Edel.
00007 //
00008 // This library is free software; you can redistribute it and/or
00009 // modify it under the terms of the GNU Library General Public
00010 // License as published by the Free Software Foundation; either
00011 // version 2 of the License, or (at your option) any later version.
00012 //
00013 // This library is distributed in the hope that it will be useful,
00014 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00015 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016 // Library General Public License for more details.
00017 //
00018 // You should have received a copy of the GNU Library General Public
00019 // License along with this library; if not, write to the Free Software
00020 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
00021 // USA.
00022 //
00023 // Please report all bugs and problems on the following page:
00024 //
00025 //     http://www.fltk.org/str.php
00026 //
00027 
00028 // TODO: rendering of the "optional hyphen"
00029 // TODO: make line numbering work again
00030 
00031 #include <stdio.h>
00032 #include <stdlib.h>
00033 #include <FL/fl_utf8.h>
00034 #include "flstring.h"
00035 #include <limits.h>
00036 #include <ctype.h>
00037 #include <FL/Fl.H>
00038 #include <FL/Fl_Text_Buffer.H>
00039 #include <FL/Fl_Text_Display.H>
00040 #include <FL/Fl_Window.H>
00041 
00042 #undef min
00043 #undef max
00044 
00045 // Text area margins.  Left & right margins should be at least 3 so that
00046 // there is some room for the overhanging parts of the cursor!
00047 #define TOP_MARGIN 1
00048 #define BOTTOM_MARGIN 1
00049 #define LEFT_MARGIN 3
00050 #define RIGHT_MARGIN 3
00051 
00052 #define NO_HINT -1
00053 
00054 /* Masks for text drawing methods.  These are or'd together to form an
00055  integer which describes what drawing calls to use to draw a string */
00056 #define FILL_MASK         0x0100
00057 #define SECONDARY_MASK    0x0200
00058 #define PRIMARY_MASK      0x0400
00059 #define HIGHLIGHT_MASK    0x0800
00060 #define BG_ONLY_MASK      0x1000
00061 #define TEXT_ONLY_MASK    0x2000
00062 #define STYLE_LOOKUP_MASK   0xff
00063 
00064 /* Maximum displayable line length (how many characters will fit across the
00065  widest window).  This amount of memory is temporarily allocated from the
00066  stack in the draw_vline() method for drawing strings */
00067 #define MAX_DISP_LINE_LEN 1000
00068 
00069 static int max( int i1, int i2 );
00070 static int min( int i1, int i2 );
00071 static int countlines( const char *string );
00072 
00073 /* The variables below are used in a timer event to allow smooth
00074  scrolling of the text area when the pointer has left the area. */
00075 static int scroll_direction = 0;
00076 static int scroll_amount = 0;
00077 static int scroll_y = 0;
00078 static int scroll_x = 0;
00079 
00080 // CET - FIXME
00081 #define TMPFONTWIDTH 6
00082 
00083 
00084 
00091 Fl_Text_Display::Fl_Text_Display(int X, int Y, int W, int H, const char* l)
00092 : Fl_Group(X, Y, W, H, l) {
00093   int i;
00094   
00095   mMaxsize = 0;
00096   damage_range1_start = damage_range1_end = -1;
00097   damage_range2_start = damage_range2_end = -1;
00098   dragPos = dragging = 0;
00099   dragType = DRAG_CHAR;
00100   display_insert_position_hint = 0;
00101   shortcut_ = 0;
00102   
00103   color(FL_BACKGROUND2_COLOR, FL_SELECTION_COLOR);
00104   box(FL_DOWN_FRAME);
00105   textsize(FL_NORMAL_SIZE);
00106   textcolor(FL_FOREGROUND_COLOR);
00107   textfont(FL_HELVETICA);
00108   set_flag(SHORTCUT_LABEL);
00109   
00110   text_area.x = 0;
00111   text_area.y = 0;
00112   text_area.w = 0;
00113   text_area.h = 0;
00114   
00115   mVScrollBar = new Fl_Scrollbar(0,0,1,1);
00116   mVScrollBar->callback((Fl_Callback*)v_scrollbar_cb, this);
00117   mHScrollBar = new Fl_Scrollbar(0,0,1,1);
00118   mHScrollBar->callback((Fl_Callback*)h_scrollbar_cb, this);
00119   mHScrollBar->type(FL_HORIZONTAL);
00120   
00121   end();
00122   
00123   scrollbar_width(Fl::scrollbar_size());
00124   scrollbar_align(FL_ALIGN_BOTTOM_RIGHT);
00125   
00126   mCursorOn = 0;
00127   mCursorPos = 0;
00128   mCursorOldY = -100;
00129   mCursorToHint = NO_HINT;
00130   mCursorStyle = NORMAL_CURSOR;
00131   mCursorPreferredXPos = -1;
00132   mBuffer = 0;
00133   mFirstChar = 0;
00134   mLastChar = 0;
00135   mNBufferLines = 0;
00136   mTopLineNum = mTopLineNumHint = 1;
00137   mAbsTopLineNum = 1;
00138   mNeedAbsTopLineNum = 0;
00139   mHorizOffset = mHorizOffsetHint = 0;
00140   
00141   mCursor_color = FL_FOREGROUND_COLOR;
00142   
00143   mStyleBuffer = 0;
00144   mStyleTable = 0;
00145   mNStyles = 0;
00146   mNVisibleLines = 1;
00147   mLineStarts = new int[mNVisibleLines];
00148   mLineStarts[0] = 0;
00149   for (i=1; i<mNVisibleLines; i++)
00150     mLineStarts[i] = -1;
00151   mSuppressResync = 0;
00152   mNLinesDeleted = 0;
00153   mModifyingTabDistance = 0;
00154   
00155   mUnfinishedStyle = 0;
00156   mUnfinishedHighlightCB = 0;
00157   mHighlightCBArg = 0;
00158   
00159   mLineNumLeft = mLineNumWidth = 0;
00160   mContinuousWrap = 0;
00161   mWrapMarginPix = 0;
00162   mSuppressResync = mNLinesDeleted = mModifyingTabDistance = 0;
00163 }
00164 
00165 
00166 
00173 Fl_Text_Display::~Fl_Text_Display() {
00174   if (scroll_direction) {
00175     Fl::remove_timeout(scroll_timer_cb, this);
00176     scroll_direction = 0;
00177   }
00178   if (mBuffer) {
00179     mBuffer->remove_modify_callback(buffer_modified_cb, this);
00180     mBuffer->remove_predelete_callback(buffer_predelete_cb, this);
00181   }
00182   if (mLineStarts) delete[] mLineStarts;
00183 }
00184 
00185 
00186 
00191 void Fl_Text_Display::buffer( Fl_Text_Buffer *buf ) {
00192   /* If the text display is already displaying a buffer, clear it off
00193    of the display and remove our callback from it */
00194   if ( buf == mBuffer) return;
00195   if ( mBuffer != 0 ) {
00196     // we must provide a copy of the buffer that we are deleting!
00197     char *deletedText = mBuffer->text();
00198     buffer_modified_cb( 0, 0, mBuffer->length(), 0, deletedText, this );
00199     free(deletedText);
00200     mNBufferLines = 0;
00201     mBuffer->remove_modify_callback( buffer_modified_cb, this );
00202     mBuffer->remove_predelete_callback( buffer_predelete_cb, this );
00203   }
00204   
00205   /* Add the buffer to the display, and attach a callback to the buffer for
00206    receiving modification information when the buffer contents change */
00207   mBuffer = buf;
00208   if (mBuffer) {
00209     mBuffer->add_modify_callback( buffer_modified_cb, this );
00210     mBuffer->add_predelete_callback( buffer_predelete_cb, this );
00211     
00212     /* Update the display */
00213     buffer_modified_cb( 0, buf->length(), 0, 0, 0, this );
00214   }
00215   
00216   /* Resize the widget to update the screen... */
00217   resize(x(), y(), w(), h());
00218 }
00219 
00220 
00221 
00249 void Fl_Text_Display::highlight_data(Fl_Text_Buffer *styleBuffer,
00250                                      const Style_Table_Entry *styleTable,
00251                                      int nStyles, char unfinishedStyle,
00252                                      Unfinished_Style_Cb unfinishedHighlightCB,
00253                                      void *cbArg ) {
00254   mStyleBuffer = styleBuffer;
00255   mStyleTable = styleTable;
00256   mNStyles = nStyles;
00257   mUnfinishedStyle = unfinishedStyle;
00258   mUnfinishedHighlightCB = unfinishedHighlightCB;
00259   mHighlightCBArg = cbArg;
00260   mColumnScale = 0;
00261   
00262   mStyleBuffer->canUndo(0);
00263   damage(FL_DAMAGE_EXPOSE);
00264 }
00265 
00266 
00267 
00272 int Fl_Text_Display::longest_vline() const {
00273   int longest = 0;
00274   for (int i = 0; i < mNVisibleLines; i++)
00275     longest = max(longest, measure_vline(i));
00276   return longest;
00277 }
00278 
00279 
00280 
00287 void Fl_Text_Display::resize(int X, int Y, int W, int H) {
00288 #ifdef DEBUG
00289   printf("Fl_Text_Display::resize(X=%d, Y=%d, W=%d, H=%d)\n", X, Y, W, H);
00290 #endif // DEBUG
00291   const int oldWidth = w();
00292 #ifdef DEBUG
00293   printf("    oldWidth=%d, mContinuousWrap=%d, mWrapMargin=%d\n", oldWidth, mContinuousWrap, mWrapMargin);
00294 #endif // DEBUG
00295   Fl_Widget::resize(X,Y,W,H);
00296   if (!buffer()) return;
00297   X += Fl::box_dx(box());
00298   Y += Fl::box_dy(box());
00299   W -= Fl::box_dw(box());
00300   H -= Fl::box_dh(box());
00301   
00302   text_area.x = X+LEFT_MARGIN;
00303   text_area.y = Y+TOP_MARGIN;
00304   text_area.w = W-LEFT_MARGIN-RIGHT_MARGIN;
00305   text_area.h = H-TOP_MARGIN-BOTTOM_MARGIN;
00306   int i;
00307   
00308   /* Find the new maximum font height for this text display */
00309   for (i = 0, mMaxsize = fl_height(textfont(), textsize()); i < mNStyles; i++)
00310     mMaxsize = max(mMaxsize, fl_height(mStyleTable[i].font, mStyleTable[i].size));
00311   
00312   // did we have scrollbars initially?
00313   unsigned int hscrollbarvisible = mHScrollBar->visible();
00314   unsigned int vscrollbarvisible = mVScrollBar->visible();
00315   
00316   // try without scrollbars first
00317   mVScrollBar->clear_visible();
00318   mHScrollBar->clear_visible();
00319   
00320   for (int again = 1; again;) {
00321     again = 0;
00322     /* In continuous wrap mode, a change in width affects the total number of
00323      lines in the buffer, and can leave the top line number incorrect, and
00324      the top character no longer pointing at a valid line start */
00325     if (mContinuousWrap && !mWrapMarginPix && W!=oldWidth) {
00326       int oldFirstChar = mFirstChar;
00327       mNBufferLines = count_lines(0, buffer()->length(), true);
00328       mFirstChar = line_start(mFirstChar);
00329       mTopLineNum = count_lines(0, mFirstChar, true)+1;
00330       absolute_top_line_number(oldFirstChar);      
00331 #ifdef DEBUG
00332       printf("    mNBufferLines=%d\n", mNBufferLines);
00333 #endif // DEBUG
00334     }
00335     
00336     /* reallocate and update the line starts array, which may have changed
00337      size and / or contents.  */
00338     int nvlines = (text_area.h + mMaxsize - 1) / mMaxsize;
00339     if (nvlines < 1) nvlines = 1;
00340     if (mNVisibleLines != nvlines) {
00341       mNVisibleLines = nvlines;
00342       if (mLineStarts) delete[] mLineStarts;
00343       mLineStarts = new int [mNVisibleLines];
00344     }
00345     
00346     calc_line_starts(0, mNVisibleLines);
00347     calc_last_char();
00348     
00349     // figure the scrollbars
00350     if (scrollbar_width()) {
00351       /* Decide if the vertical scrollbar needs to be visible */
00352       if (scrollbar_align() & (FL_ALIGN_LEFT|FL_ALIGN_RIGHT) &&
00353           mNBufferLines >= mNVisibleLines - 1)
00354       {
00355         mVScrollBar->set_visible();
00356         if (scrollbar_align() & FL_ALIGN_LEFT) {
00357           text_area.x = X+scrollbar_width()+LEFT_MARGIN;
00358           text_area.w = W-scrollbar_width()-LEFT_MARGIN-RIGHT_MARGIN;
00359           mVScrollBar->resize(X, text_area.y-TOP_MARGIN, scrollbar_width(),
00360                               text_area.h+TOP_MARGIN+BOTTOM_MARGIN);
00361         } else {
00362           text_area.x = X+LEFT_MARGIN;
00363           text_area.w = W-scrollbar_width()-LEFT_MARGIN-RIGHT_MARGIN;
00364           mVScrollBar->resize(X+W-scrollbar_width(), text_area.y-TOP_MARGIN,
00365                               scrollbar_width(), text_area.h+TOP_MARGIN+BOTTOM_MARGIN);
00366         }
00367       }
00368       
00369       /*
00370        Decide if the horizontal scrollbar needs to be visible.  If there
00371        is a vertical scrollbar, a horizontal is always created too.  This
00372        is because the alternatives are unattractive:
00373        * Dynamically creating a horizontal scrollbar based on the currently
00374        visible lines is what the original nedit does, but it always wastes
00375        space for the scrollbar even when it's not used.  Since the FLTK
00376        widget dynamically allocates the space for the scrollbar and
00377        rearranges the widget to make room for it, this would create a very
00378        visually displeasing "bounce" effect when the vertical scrollbar is
00379        dragged.  Trust me, I tried it and it looks really bad.
00380        * The other alternative would be to keep track of what the longest
00381        line in the entire buffer is and base the scrollbar on that.  I
00382        didn't do this because I didn't see any easy way to do that using
00383        the nedit code and this could involve a lengthy calculation for
00384        large buffers.  If an efficient and non-costly way of doing this
00385        can be found, this might be a way to go.
00386        */
00387       /* WAS: Suggestion: Try turning the horizontal scrollbar on when
00388        you first see a line that is too wide in the window, but then
00389        don't turn it off (ie mix both of your solutions). */
00390       if (scrollbar_align() & (FL_ALIGN_TOP|FL_ALIGN_BOTTOM) &&
00391           (mVScrollBar->visible() || longest_vline() > text_area.w))
00392       {
00393         if (!mHScrollBar->visible()) {
00394           mHScrollBar->set_visible();
00395           again = 1; // loop again to see if we now need vert. & recalc sizes
00396         }
00397         if (scrollbar_align() & FL_ALIGN_TOP) {
00398           text_area.y = Y + scrollbar_width()+TOP_MARGIN;
00399           text_area.h = H - scrollbar_width()-TOP_MARGIN-BOTTOM_MARGIN;
00400           mHScrollBar->resize(text_area.x-LEFT_MARGIN, Y,
00401                               text_area.w+LEFT_MARGIN+RIGHT_MARGIN, scrollbar_width());
00402         } else {
00403           text_area.y = Y+TOP_MARGIN;
00404           text_area.h = H - scrollbar_width()-TOP_MARGIN-BOTTOM_MARGIN;
00405           mHScrollBar->resize(text_area.x-LEFT_MARGIN, Y+H-scrollbar_width(),
00406                               text_area.w+LEFT_MARGIN+RIGHT_MARGIN, scrollbar_width());
00407         }
00408       }
00409     }
00410   }
00411   
00412   // user request to change viewport
00413   if (mTopLineNumHint != mTopLineNum || mHorizOffsetHint != mHorizOffset)
00414     scroll_(mTopLineNumHint, mHorizOffsetHint);
00415   
00416   // everything will fit in the viewport
00417   if (mNBufferLines < mNVisibleLines || mBuffer == NULL || mBuffer->length() == 0) {
00418     scroll_(1, mHorizOffset);
00419   /* if empty lines become visible, there may be an opportunity to
00420    display more text by scrolling down */
00421   } else {
00422     while (   (mLineStarts[mNVisibleLines-2]==-1) 
00423            && scroll_(mTopLineNum-1, mHorizOffset))
00424     { }
00425   }
00426   
00427   // user request to display insert position
00428   if (display_insert_position_hint)
00429     display_insert();
00430   
00431   // in case horizontal offset is now greater than longest line
00432   int maxhoffset = max(0, longest_vline()-text_area.w);
00433   if (mHorizOffset > maxhoffset)
00434     scroll_(mTopLineNumHint, maxhoffset);
00435   
00436   mTopLineNumHint = mTopLineNum;
00437   mHorizOffsetHint = mHorizOffset;
00438   display_insert_position_hint = 0;
00439   
00440   if (mContinuousWrap ||
00441       hscrollbarvisible != mHScrollBar->visible() ||
00442       vscrollbarvisible != mVScrollBar->visible())
00443     redraw();
00444   
00445   update_v_scrollbar();
00446   update_h_scrollbar();
00447 }
00448 
00449 
00450 
00456 void Fl_Text_Display::draw_text( int left, int top, int width, int height ) {
00457   int fontHeight, firstLine, lastLine, line;
00458   
00459   /* find the line number range of the display */
00460   fontHeight = mMaxsize ? mMaxsize : textsize_;
00461   firstLine = ( top - text_area.y - fontHeight + 1 ) / fontHeight;
00462   lastLine = ( top + height - text_area.y ) / fontHeight + 1;
00463   
00464   fl_push_clip( left, top, width, height );
00465   
00466   /* draw the lines */
00467   for ( line = firstLine; line <= lastLine; line++ )
00468     draw_vline( line, left, left + width, 0, INT_MAX );
00469   
00470   /* draw the line numbers if exposed area includes them */
00471   if (mLineNumWidth != 0 && left <= mLineNumLeft + mLineNumWidth)
00472     draw_line_numbers(false);
00473   
00474   fl_pop_clip();
00475 }
00476 
00477 
00478 
00486 void Fl_Text_Display::redisplay_range(int startpos, int endpos) {
00487   IS_UTF8_ALIGNED2(buffer(), startpos)
00488   IS_UTF8_ALIGNED2(buffer(), endpos)
00489   
00490   if (damage_range1_start == -1 && damage_range1_end == -1) {
00491     damage_range1_start = startpos;
00492     damage_range1_end = endpos;
00493   } else if ((startpos >= damage_range1_start && startpos <= damage_range1_end) ||
00494              (endpos >= damage_range1_start && endpos <= damage_range1_end)) {
00495     damage_range1_start = min(damage_range1_start, startpos);
00496     damage_range1_end = max(damage_range1_end, endpos);
00497   } else if (damage_range2_start == -1 && damage_range2_end == -1) {
00498     damage_range2_start = startpos;
00499     damage_range2_end = endpos;
00500   } else {
00501     damage_range2_start = min(damage_range2_start, startpos);
00502     damage_range2_end = max(damage_range2_end, endpos);
00503   }
00504   damage(FL_DAMAGE_SCROLL);
00505 }
00506 
00507 
00508 
00522 void Fl_Text_Display::draw_range(int startpos, int endpos) {
00523   startpos = buffer()->utf8_align(startpos);
00524   endpos = buffer()->utf8_align(endpos);
00525   
00526   int i, startLine, lastLine, startIndex, endIndex;
00527   
00528   /* If the range is outside of the displayed text, just return */
00529   if ( endpos < mFirstChar || ( startpos > mLastChar && !empty_vlines() ) ) 
00530     return;
00531   
00532   /* Clean up the starting and ending values */
00533   if ( startpos < 0 ) startpos = 0;
00534   if ( startpos > mBuffer->length() ) startpos = mBuffer->length();
00535   if ( endpos < 0 ) endpos = 0;
00536   if ( endpos > mBuffer->length() ) endpos = mBuffer->length();
00537   
00538   /* Get the starting and ending lines */
00539   if ( startpos < mFirstChar )
00540     startpos = mFirstChar;
00541   if ( !position_to_line( startpos, &startLine ) )
00542     startLine = mNVisibleLines - 1;
00543   if ( endpos >= mLastChar ) {
00544     lastLine = mNVisibleLines - 1;
00545   } else {
00546     if ( !position_to_line( endpos, &lastLine ) ) {
00547       /* shouldn't happen */
00548       lastLine = mNVisibleLines - 1;
00549     }
00550   }
00551   
00552   /* Get the starting and ending positions within the lines */
00553   startIndex = mLineStarts[ startLine ] == -1 ? 0 : startpos - mLineStarts[ startLine ];
00554   if ( endpos >= mLastChar )
00555     endIndex = INT_MAX;
00556   else if ( mLineStarts[ lastLine ] == -1 )
00557     endIndex = 0;
00558   else
00559     endIndex = endpos - mLineStarts[ lastLine ];
00560   
00561   /* If the starting and ending lines are the same, redisplay the single
00562    line between "start" and "end" */
00563   if ( startLine == lastLine ) {
00564     draw_vline( startLine, 0, INT_MAX, startIndex, endIndex );
00565     return;
00566   }
00567 
00568   /* Redisplay the first line from "start" */
00569   draw_vline( startLine, 0, INT_MAX, startIndex, INT_MAX );
00570 
00571   /* Redisplay the lines in between at their full width */
00572   for ( i = startLine + 1; i < lastLine; i++ )
00573     draw_vline( i, 0, INT_MAX, 0, INT_MAX );
00574 
00575   /* Redisplay the last line to "end" */
00576   draw_vline( lastLine, 0, INT_MAX, 0, endIndex );
00577 }
00578 
00579 
00580 
00587 void Fl_Text_Display::insert_position( int newPos ) {
00588   IS_UTF8_ALIGNED2(buffer(), newPos)
00589   
00590   /* make sure new position is ok, do nothing if it hasn't changed */
00591   if ( newPos == mCursorPos ) return;
00592   if ( newPos < 0 ) newPos = 0;
00593   if ( newPos > mBuffer->length() ) newPos = mBuffer->length();
00594   
00595   /* cursor movement cancels vertical cursor motion column */
00596   mCursorPreferredXPos = -1;
00597   
00598   /* erase the cursor at its previous position */
00599   redisplay_range(buffer()->prev_char_clipped(mCursorPos), buffer()->next_char(mCursorPos));
00600   
00601   mCursorPos = newPos;
00602   
00603   /* draw cursor at its new position */
00604   redisplay_range(buffer()->prev_char_clipped(mCursorPos), buffer()->next_char(mCursorPos));
00605 }
00606 
00607 
00608 
00614 void Fl_Text_Display::show_cursor(int b) {
00615   mCursorOn = b;
00616   redisplay_range(buffer()->prev_char_clipped(mCursorPos), buffer()->next_char(mCursorPos));
00617 }
00618 
00619 
00620 
00636 void Fl_Text_Display::cursor_style(int style) {
00637   mCursorStyle = style;
00638   if (mCursorOn) show_cursor();
00639 }
00640 
00641 
00642 
00663 void Fl_Text_Display::wrap_mode(int wrap, int wrapMargin) {
00664   switch (wrap) {
00665     case WRAP_NONE: 
00666       mWrapMarginPix = 0;
00667       mContinuousWrap = 0;
00668       break;
00669     case WRAP_AT_COLUMN: 
00670     default:
00671       mWrapMarginPix = int(col_to_x(wrapMargin));
00672       mContinuousWrap = 1;
00673       break;
00674     case WRAP_AT_PIXEL: 
00675       mWrapMarginPix = wrapMargin;
00676       mContinuousWrap = 1;
00677       break;
00678     case WRAP_AT_BOUNDS: 
00679       mWrapMarginPix = 0;
00680       mContinuousWrap = 1;
00681       break;
00682   }
00683   
00684   if (buffer()) {
00685     /* wrapping can change the total number of lines, re-count */
00686     mNBufferLines = count_lines(0, buffer()->length(), true);
00687     
00688     /* changing wrap margins or changing from wrapped mode to non-wrapped
00689      can leave the character at the top no longer at a line start, and/or
00690      change the line number */
00691     mFirstChar = line_start(mFirstChar);
00692     mTopLineNum = count_lines(0, mFirstChar, true) + 1;
00693     
00694     reset_absolute_top_line_number();
00695     
00696     /* update the line starts array */
00697     calc_line_starts(0, mNVisibleLines);
00698     calc_last_char();
00699   } else {
00700     // No buffer, so just clear the state info for later...
00701     mNBufferLines  = 0;
00702     mFirstChar     = 0;
00703     mTopLineNum    = 1;
00704     mAbsTopLineNum = 0;
00705   }
00706   
00707   resize(x(), y(), w(), h());
00708 }
00709 
00710 
00711 
00721 void Fl_Text_Display::insert(const char* text) {
00722   IS_UTF8_ALIGNED2(buffer(), mCursorPos)
00723   IS_UTF8_ALIGNED(text)
00724   
00725   int pos = mCursorPos;
00726   
00727   mCursorToHint = pos + strlen( text );
00728   mBuffer->insert( pos, text );
00729   mCursorToHint = NO_HINT;
00730 }
00731 
00732 
00733 
00740 void Fl_Text_Display::overstrike(const char* text) {
00741   IS_UTF8_ALIGNED2(buffer(), mCursorPos)
00742   IS_UTF8_ALIGNED(text)
00743   
00744   int startPos = mCursorPos;
00745   Fl_Text_Buffer *buf = mBuffer;
00746   int lineStart = buf->line_start( startPos );
00747   int textLen = strlen( text );
00748   int i, p, endPos, indent, startIndent, endIndent;
00749   const char *c;
00750   unsigned int ch;
00751   char *paddedText = NULL;
00752   
00753   /* determine how many displayed character positions are covered */
00754   startIndent = mBuffer->count_displayed_characters( lineStart, startPos );
00755   indent = startIndent;
00756   for ( c = text; *c != '\0'; c += fl_utf8len1(*c) )
00757     indent++;
00758   endIndent = indent;
00759   
00760   /* find which characters to remove, and if necessary generate additional
00761    padding to make up for removed control characters at the end */
00762   indent = startIndent;
00763   for ( p = startPos; ; p=buffer()->next_char(p) ) {
00764     if ( p == buf->length() )
00765       break;
00766     ch = buf->char_at( p );
00767     if ( ch == '\n' )
00768       break;
00769     indent++;
00770     if ( indent == endIndent ) {
00771       p++;
00772       break;
00773     } else if ( indent > endIndent ) {
00774       if ( ch != '\t' ) {
00775         p++;
00776         paddedText = new char [ textLen + FL_TEXT_MAX_EXP_CHAR_LEN + 1 ];
00777         strcpy( paddedText, text );
00778         for ( i = 0; i < indent - endIndent; i++ )
00779           paddedText[ textLen + i ] = ' ';
00780         paddedText[ textLen + i ] = '\0';
00781       }
00782       break;
00783     }
00784   }
00785   endPos = p;
00786   
00787   mCursorToHint = startPos + textLen;
00788   buf->replace( startPos, endPos, paddedText == NULL ? text : paddedText );
00789   mCursorToHint = NO_HINT;
00790   if ( paddedText != NULL )
00791     delete [] paddedText;
00792 }
00793 
00794 
00795 
00809 int Fl_Text_Display::position_to_xy( int pos, int* X, int* Y ) const {
00810   IS_UTF8_ALIGNED2(buffer(), pos)
00811 
00812   int lineStartPos, fontHeight, lineLen;
00813   int visLineNum;
00814   
00815   /* If position is not displayed, return false */
00816   if (pos < mFirstChar || (pos > mLastChar && !empty_vlines())) {
00817     return 0;
00818   }
00819   
00820   /* Calculate Y coordinate */
00821   if (!position_to_line(pos, &visLineNum)) {
00822     return 0;
00823   }
00824   if (visLineNum < 0 || visLineNum > mNBufferLines) {
00825     return 0;
00826   }
00827   
00828   fontHeight = mMaxsize;
00829   *Y = text_area.y + visLineNum * fontHeight;
00830   
00831   /* Get the text, length, and buffer position of the line. If the position
00832    is beyond the end of the buffer and should be at the first position on
00833    the first empty line, don't try to get or scan the text  */
00834   lineStartPos = mLineStarts[visLineNum];
00835   if ( lineStartPos == -1 ) {
00836     *X = text_area.x - mHorizOffset;
00837     return 1;
00838   }
00839   lineLen = vline_length( visLineNum );
00840   *X = text_area.x + handle_vline(GET_WIDTH, lineStartPos, pos-lineStartPos, 0, 0, 0, 0, 0, 0) - mHorizOffset;
00841   return 1;
00842 }
00843 
00844 
00845 
00863 int Fl_Text_Display::position_to_linecol( int pos, int* lineNum, int* column ) const {
00864   IS_UTF8_ALIGNED2(buffer(), pos)
00865   
00866   int retVal;
00867   
00868   /* In continuous wrap mode, the absolute (non-wrapped) line count is
00869    maintained separately, as needed.  Only return it if we're actually
00870    keeping track of it and pos is in the displayed text */
00871   if (mContinuousWrap) {
00872     if (!maintaining_absolute_top_line_number() || pos < mFirstChar || pos > mLastChar)
00873       return 0;
00874     *lineNum = mAbsTopLineNum + buffer()->count_lines(mFirstChar, pos);
00875     *column = buffer()->count_displayed_characters(buffer()->line_start(pos), pos);
00876     return 1;
00877   }
00878   
00879   retVal = position_to_line( pos, lineNum );
00880   if ( retVal ) {
00881     *column = mBuffer->count_displayed_characters( mLineStarts[ *lineNum ], pos );
00882     *lineNum += mTopLineNum;
00883   }
00884   return retVal;
00885 }
00886 
00887 
00888 
00894 int Fl_Text_Display::in_selection( int X, int Y ) const {
00895   int pos = xy_to_position( X, Y, CHARACTER_POS );
00896   IS_UTF8_ALIGNED2(buffer(), pos)
00897   Fl_Text_Buffer *buf = mBuffer;
00898   return buf->primary_selection()->includes(pos);
00899 }
00900 
00901 
00902 
00923 int Fl_Text_Display::wrapped_column(int row, int column) const {
00924   int lineStart, dispLineStart;
00925   
00926   if (!mContinuousWrap || row < 0 || row > mNVisibleLines)
00927     return column;
00928   dispLineStart = mLineStarts[row];
00929   if (dispLineStart == -1)
00930     return column;
00931   lineStart = buffer()->line_start(dispLineStart);
00932   return column + buffer()->count_displayed_characters(lineStart, dispLineStart);
00933 }
00934 
00935 
00936 
00951 int Fl_Text_Display::wrapped_row(int row) const {
00952   if (!mContinuousWrap || row < 0 || row > mNVisibleLines)
00953     return row;
00954   return buffer()->count_lines(mFirstChar, mLineStarts[row]);
00955 }
00956 
00957 
00958 
00969 void Fl_Text_Display::display_insert() {
00970   int hOffset, topLine, X, Y;
00971   hOffset = mHorizOffset;
00972   topLine = mTopLineNum;
00973   
00974   if (insert_position() < mFirstChar) {
00975     topLine -= count_lines(insert_position(), mFirstChar, false);
00976   } else if (mLineStarts[mNVisibleLines-2] != -1) {
00977     int lastChar = line_end(mLineStarts[mNVisibleLines-2],true);
00978     if (insert_position() >= lastChar)
00979       topLine += count_lines(lastChar - (wrap_uses_character(mLastChar) ? 0 : 1),
00980                              insert_position(), false);
00981   }
00982   
00983   /* Find the new setting for horizontal offset (this is a bit ungraceful).
00984    If the line is visible, just use PositionToXY to get the position
00985    to scroll to, otherwise, do the vertical scrolling first, then the
00986    horizontal */
00987   if (!position_to_xy( mCursorPos, &X, &Y )) {
00988     scroll_(topLine, hOffset);
00989     if (!position_to_xy( mCursorPos, &X, &Y )) {
00990 #ifdef DEBUG
00991       printf ("*** display_insert/position_to_xy # GIVE UP !\n"); fflush(stdout);
00992 #endif // DEBUG
00993       return;   /* Give up, it's not worth it (but why does it fail?) */
00994     }
00995   }
00996   if (X > text_area.x + text_area.w)
00997     hOffset += X-(text_area.x + text_area.w);
00998   else if (X < text_area.x)
00999     hOffset += X-text_area.x;
01000   
01001   /* Do the scroll */
01002   if (topLine != mTopLineNum || hOffset != mHorizOffset)
01003     scroll_(topLine, hOffset);
01004 }
01005 
01006 
01012 void Fl_Text_Display::show_insert_position() {
01013   display_insert_position_hint = 1;
01014   resize(x(), y(), w(), h());
01015 }
01016 
01017 
01018 /*
01019  Cursor movement functions
01020  */
01021 
01026 int Fl_Text_Display::move_right() {
01027   if ( mCursorPos >= mBuffer->length() )
01028     return 0;
01029   int p = insert_position();
01030   int q = buffer()->next_char(p);
01031   insert_position(q);
01032   return 1;
01033 }
01034 
01035 
01036 
01041 int Fl_Text_Display::move_left() {
01042   if ( mCursorPos <= 0 )
01043     return 0;
01044   int p = insert_position();
01045   int q = buffer()->prev_char_clipped(p);
01046   insert_position(q);
01047   return 1;
01048 }
01049 
01050 
01051 
01056 int Fl_Text_Display::move_up() {
01057   int lineStartPos, xPos, prevLineStartPos, newPos, visLineNum;
01058   
01059   /* Find the position of the start of the line.  Use the line starts array
01060    if possible */
01061   if ( position_to_line( mCursorPos, &visLineNum ) )
01062     lineStartPos = mLineStarts[ visLineNum ];
01063   else {
01064     lineStartPos = line_start( mCursorPos );
01065     visLineNum = -1;
01066   }
01067   if ( lineStartPos == 0 )
01068     return 0;
01069   
01070   /* Decide what column to move to, if there's a preferred column use that */
01071   if (mCursorPreferredXPos >= 0)
01072     xPos = mCursorPreferredXPos;
01073   else
01074     xPos = handle_vline(GET_WIDTH, lineStartPos, mCursorPos-lineStartPos, 
01075                         0, 0, 0, 0, 0, INT_MAX);
01076   
01077   /* count forward from the start of the previous line to reach the column */
01078   if ( visLineNum != -1 && visLineNum != 0 )
01079     prevLineStartPos = mLineStarts[ visLineNum - 1 ];
01080   else
01081     prevLineStartPos = rewind_lines( lineStartPos, 1 );
01082   
01083   int lineEnd = line_end(prevLineStartPos, true);
01084   newPos = handle_vline(FIND_INDEX_FROM_ZERO, prevLineStartPos, lineEnd-prevLineStartPos, 
01085                         0, 0, 0, 0, 0, xPos);
01086   
01087   /* move the cursor */
01088   insert_position( newPos );
01089   
01090   /* if a preferred column wasn't aleady established, establish it */
01091   mCursorPreferredXPos = xPos;
01092   return 1;
01093 }
01094 
01095 
01096 
01101 int Fl_Text_Display::move_down() {
01102   int lineStartPos, xPos, newPos, visLineNum;
01103   
01104   if ( mCursorPos == mBuffer->length() )
01105     return 0;
01106   
01107   if ( position_to_line( mCursorPos, &visLineNum ) )
01108     lineStartPos = mLineStarts[ visLineNum ];
01109   else {
01110     lineStartPos = line_start( mCursorPos );
01111     visLineNum = -1;
01112   }
01113   if (mCursorPreferredXPos >= 0) {
01114     xPos = mCursorPreferredXPos;
01115   } else {
01116     xPos = handle_vline(GET_WIDTH, lineStartPos, mCursorPos-lineStartPos, 
01117                         0, 0, 0, 0, 0, INT_MAX);
01118   }
01119   
01120   int nextLineStartPos = skip_lines( lineStartPos, 1, true );
01121   int lineEnd = line_end(nextLineStartPos, true);
01122   newPos = handle_vline(FIND_INDEX_FROM_ZERO, nextLineStartPos, lineEnd-nextLineStartPos, 
01123                         0, 0, 0, 0, 0, xPos);
01124   
01125   insert_position( newPos );
01126   mCursorPreferredXPos = xPos;
01127   return 1;
01128 }
01129 
01130 
01131 
01145 int Fl_Text_Display::count_lines(int startPos, int endPos,
01146                                  bool startPosIsLineStart) const {
01147   IS_UTF8_ALIGNED2(buffer(), startPos)
01148   IS_UTF8_ALIGNED2(buffer(), endPos)
01149   
01150   int retLines, retPos, retLineStart, retLineEnd;
01151   
01152 #ifdef DEBUG
01153   printf("Fl_Text_Display::count_lines(startPos=%d, endPos=%d, startPosIsLineStart=%d\n",
01154          startPos, endPos, startPosIsLineStart);
01155 #endif // DEBUG
01156   
01157   /* If we're not wrapping use simple (and more efficient) BufCountLines */
01158   if (!mContinuousWrap)
01159     return buffer()->count_lines(startPos, endPos);
01160   
01161   wrapped_line_counter(buffer(), startPos, endPos, INT_MAX,
01162                        startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
01163                        &retLineEnd);
01164   
01165 #ifdef DEBUG
01166   printf("   # after WLC: retPos=%d, retLines=%d, retLineStart=%d, retLineEnd=%d\n",
01167          retPos, retLines, retLineStart, retLineEnd);
01168 #endif // DEBUG
01169   
01170   return retLines;
01171 }
01172 
01173 
01174 
01188 int Fl_Text_Display::skip_lines(int startPos, int nLines,
01189                                 bool startPosIsLineStart) {
01190   IS_UTF8_ALIGNED2(buffer(), startPos)
01191 
01192   int retLines, retPos, retLineStart, retLineEnd;
01193   
01194   /* if we're not wrapping use more efficient BufCountForwardNLines */
01195   if (!mContinuousWrap)
01196     return buffer()->skip_lines(startPos, nLines);
01197   
01198   /* wrappedLineCounter can't handle the 0 lines case */
01199   if (nLines == 0)
01200     return startPos;
01201   
01202   /* use the common line counting routine to count forward */
01203   wrapped_line_counter(buffer(), startPos, buffer()->length(),
01204                        nLines, startPosIsLineStart, 0, 
01205                        &retPos, &retLines, &retLineStart, &retLineEnd);
01206   IS_UTF8_ALIGNED2(buffer(), retPos)
01207   return retPos;
01208 }
01209 
01210 
01211 
01234 int Fl_Text_Display::line_end(int startPos, bool startPosIsLineStart) const {
01235   IS_UTF8_ALIGNED2(buffer(), startPos)
01236 
01237   int retLines, retPos, retLineStart, retLineEnd;
01238   
01239   /* If we're not wrapping use more efficient BufEndOfLine */
01240   if (!mContinuousWrap)
01241     return buffer()->line_end(startPos);
01242   
01243   if (startPos == buffer()->length())
01244     return startPos;
01245   
01246   wrapped_line_counter(buffer(), startPos, buffer()->length(), 1,
01247                        startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
01248                        &retLineEnd);
01249   
01250   IS_UTF8_ALIGNED2(buffer(), retLineEnd)
01251   return retLineEnd;
01252 }
01253 
01254 
01255 
01265 int Fl_Text_Display::line_start(int pos) const {
01266   IS_UTF8_ALIGNED2(buffer(), pos)
01267 
01268   int retLines, retPos, retLineStart, retLineEnd;
01269   
01270   /* If we're not wrapping, use the more efficient BufStartOfLine */
01271   if (!mContinuousWrap)
01272     return buffer()->line_start(pos);
01273   
01274   wrapped_line_counter(buffer(), buffer()->line_start(pos), pos, INT_MAX, true, 0,
01275                        &retPos, &retLines, &retLineStart, &retLineEnd);
01276 
01277   IS_UTF8_ALIGNED2(buffer(), retLineStart)
01278   return retLineStart;
01279 }
01280 
01281 
01282 
01293 int Fl_Text_Display::rewind_lines(int startPos, int nLines) {
01294   IS_UTF8_ALIGNED2(buffer(), startPos)
01295 
01296   Fl_Text_Buffer *buf = buffer();
01297   int pos, lineStart, retLines, retPos, retLineStart, retLineEnd;
01298   
01299   /* If we're not wrapping, use the more efficient BufCountBackwardNLines */
01300   if (!mContinuousWrap)
01301     return buf->rewind_lines(startPos, nLines);
01302   
01303   pos = startPos;
01304   for (;;) {
01305     lineStart = buf->line_start(pos);
01306     wrapped_line_counter(buf, lineStart, pos, INT_MAX, true, 0, 
01307                          &retPos, &retLines, &retLineStart, &retLineEnd, false);
01308     if (retLines > nLines)
01309       return skip_lines(lineStart, retLines-nLines, true);
01310     nLines -= retLines;
01311     pos = lineStart - 1;
01312     if (pos < 0)
01313       return 0;
01314     nLines -= 1;
01315   }
01316 }
01317 
01318 
01319 
01320 static inline int fl_isseparator(unsigned int c) {
01321   // FIXME: this does not take UCS-4 encoding into account
01322   return c != '$' && c != '_' && (isspace(c) || ispunct(c));
01323 }
01324 
01325 
01326 
01330 void Fl_Text_Display::next_word() {
01331   int pos = insert_position();
01332 
01333   while (pos < buffer()->length() && !fl_isseparator(buffer()->char_at(pos))) {
01334     pos = buffer()->next_char(pos);
01335   }
01336 
01337   while (pos < buffer()->length() && fl_isseparator(buffer()->char_at(pos))) {
01338     pos = buffer()->next_char(pos);
01339   }
01340   
01341   insert_position( pos );
01342 }
01343 
01344 
01345 
01349 void Fl_Text_Display::previous_word() {
01350   int pos = insert_position();
01351   if (pos==0) return;
01352   pos = buffer()->prev_char(pos);
01353 
01354   while (pos && fl_isseparator(buffer()->char_at(pos))) {
01355     pos = buffer()->prev_char(pos);
01356   }
01357 
01358   while (pos && !fl_isseparator(buffer()->char_at(pos))) {
01359     pos = buffer()->prev_char(pos);
01360   }
01361 
01362   if (fl_isseparator(buffer()->char_at(pos))) {
01363     pos = buffer()->next_char(pos);
01364   }
01365   
01366   insert_position( pos );
01367 }
01368 
01369 
01370 
01381 void Fl_Text_Display::buffer_predelete_cb(int pos, int nDeleted, void *cbArg) {
01382   Fl_Text_Display *textD = (Fl_Text_Display *)cbArg;
01383   if (textD->mContinuousWrap) {
01384   /* Note: we must perform this measurement, even if there is not a
01385    single character deleted; the number of "deleted" lines is the
01386    number of visual lines spanned by the real line in which the
01387    modification takes place.
01388    Also, a modification of the tab distance requires the same
01389    kind of calculations in advance, even if the font width is "fixed",
01390    because when the width of the tab characters changes, the layout
01391    of the text may be completely different. */
01392     IS_UTF8_ALIGNED2(textD->buffer(), pos)
01393     textD->measure_deleted_lines(pos, nDeleted);
01394   } else {
01395     textD->mSuppressResync = 0; /* Probably not needed, but just in case */
01396   }
01397 }
01398 
01399 
01400 
01413 void Fl_Text_Display::buffer_modified_cb( int pos, int nInserted, int nDeleted,
01414                                          int nRestyled, const char *deletedText, void *cbArg ) {
01415   int linesInserted, linesDeleted, startDispPos, endDispPos;
01416   Fl_Text_Display *textD = ( Fl_Text_Display * ) cbArg;
01417   Fl_Text_Buffer *buf = textD->mBuffer;
01418   int oldFirstChar = textD->mFirstChar;
01419   int scrolled, origCursorPos = textD->mCursorPos;
01420   int wrapModStart = 0, wrapModEnd = 0;
01421 
01422   IS_UTF8_ALIGNED2(buf, pos)
01423   IS_UTF8_ALIGNED2(buf, oldFirstChar)
01424   
01425   /* buffer modification cancels vertical cursor motion column */
01426   if ( nInserted != 0 || nDeleted != 0 )
01427     textD->mCursorPreferredXPos = -1;
01428   
01429   /* Count the number of lines inserted and deleted, and in the case
01430    of continuous wrap mode, how much has changed */
01431   if (textD->mContinuousWrap) {
01432     textD->find_wrap_range(deletedText, pos, nInserted, nDeleted,
01433                            &wrapModStart, &wrapModEnd, &linesInserted, &linesDeleted);
01434   } else {
01435     linesInserted = nInserted == 0 ? 0 : buf->count_lines( pos, pos + nInserted );
01436     linesDeleted = nDeleted == 0 ? 0 : countlines( deletedText );
01437   }
01438   
01439   /* Update the line starts and mTopLineNum */
01440   if ( nInserted != 0 || nDeleted != 0 ) {
01441     if (textD->mContinuousWrap) {
01442       textD->update_line_starts( wrapModStart, wrapModEnd-wrapModStart,
01443                                 nDeleted + pos-wrapModStart + (wrapModEnd-(pos+nInserted)),
01444                                 linesInserted, linesDeleted, &scrolled );
01445     } else {
01446       textD->update_line_starts( pos, nInserted, nDeleted, linesInserted,
01447                                 linesDeleted, &scrolled );
01448     }
01449   } else
01450     scrolled = 0;
01451   
01452   /* If we're counting non-wrapped lines as well, maintain the absolute
01453    (non-wrapped) line number of the text displayed */
01454   if (textD->maintaining_absolute_top_line_number() &&
01455       (nInserted != 0 || nDeleted != 0)) {
01456     if (deletedText && (pos + nDeleted < oldFirstChar))
01457       textD->mAbsTopLineNum += buf->count_lines(pos, pos + nInserted) -
01458                                countlines(deletedText);
01459     else if (pos < oldFirstChar)
01460       textD->reset_absolute_top_line_number();
01461   }         
01462   
01463   /* Update the line count for the whole buffer */
01464   textD->mNBufferLines += linesInserted - linesDeleted;
01465   
01466   /* Update the cursor position */
01467   if ( textD->mCursorToHint != NO_HINT ) {
01468     textD->mCursorPos = textD->mCursorToHint;
01469     textD->mCursorToHint = NO_HINT;
01470   } else if ( textD->mCursorPos > pos ) {
01471     if ( textD->mCursorPos < pos + nDeleted )
01472       textD->mCursorPos = pos;
01473     else
01474       textD->mCursorPos += nInserted - nDeleted;
01475   }
01476   
01477   // refigure scrollbars & stuff
01478   textD->resize(textD->x(), textD->y(), textD->w(), textD->h());
01479   
01480   // don't need to do anything else if not visible?
01481   if (!textD->visible_r()) return;
01482   
01483   /* If the changes caused scrolling, re-paint everything and we're done. */
01484   if ( scrolled ) {
01485     textD->damage(FL_DAMAGE_EXPOSE);
01486     if ( textD->mStyleBuffer )   /* See comments in extendRangeForStyleMods */
01487       textD->mStyleBuffer->primary_selection()->selected(0);
01488     return;
01489   }
01490   
01491   /* If the changes didn't cause scrolling, decide the range of characters
01492    that need to be re-painted.  Also if the cursor position moved, be
01493    sure that the redisplay range covers the old cursor position so the
01494    old cursor gets erased, and erase the bits of the cursor which extend
01495    beyond the left and right edges of the text. */
01496   startDispPos = textD->mContinuousWrap ? wrapModStart : pos;
01497   IS_UTF8_ALIGNED2(buf, startDispPos)
01498   
01499   if ( origCursorPos == startDispPos && textD->mCursorPos != startDispPos )
01500     startDispPos = min( startDispPos, buf->prev_char_clipped(origCursorPos) );
01501   IS_UTF8_ALIGNED2(buf, startDispPos)
01502   
01503   if ( linesInserted == linesDeleted ) {
01504     if ( nInserted == 0 && nDeleted == 0 )
01505       endDispPos = pos + nRestyled;
01506     else {
01507       if (textD->mContinuousWrap)
01508         endDispPos = wrapModEnd;
01509       else
01510         endDispPos = buf->next_char(buf->line_end( pos + nInserted ));
01511       
01512       // CET - FIXME      if ( origCursorPos >= startDispPos &&
01513       //                ( origCursorPos <= endDispPos || endDispPos == buf->length() ) )
01514     }
01515     
01516     if (linesInserted > 1) textD->draw_line_numbers(false);
01517   } else {
01518     endDispPos = buf->next_char(textD->mLastChar);
01519     // CET - FIXME   if ( origCursorPos >= pos )
01520     /* If more than one line is inserted/deleted, a line break may have
01521      been inserted or removed in between, and the line numbers may
01522      have changed. If only one line is altered, line numbers cannot
01523      be affected (the insertion or removal of a line break always 
01524      results in at least two lines being redrawn). */
01525     textD->draw_line_numbers(false);
01526   }
01527   IS_UTF8_ALIGNED2(buf, startDispPos)
01528   IS_UTF8_ALIGNED2(buf, endDispPos)
01529   
01530   /* If there is a style buffer, check if the modification caused additional
01531    changes that need to be redisplayed.  (Redisplaying separately would
01532    cause double-redraw on almost every modification involving styled
01533    text).  Extend the redraw range to incorporate style changes */
01534   if ( textD->mStyleBuffer )
01535     textD->extend_range_for_styles( &startDispPos, &endDispPos );
01536   IS_UTF8_ALIGNED2(buf, startDispPos)
01537   IS_UTF8_ALIGNED2(buf, endDispPos)
01538   
01539   /* Redisplay computed range */
01540   textD->redisplay_range( startDispPos, endDispPos );
01541 }
01542 
01543 
01544 
01557 void Fl_Text_Display::maintain_absolute_top_line_number(int state) {
01558   mNeedAbsTopLineNum = state;
01559   reset_absolute_top_line_number();
01560 }
01561 
01562 
01563 
01570 int Fl_Text_Display::get_absolute_top_line_number() const {
01571   if (!mContinuousWrap)
01572     return mTopLineNum;
01573   if (maintaining_absolute_top_line_number())
01574     return mAbsTopLineNum;
01575   return 0;
01576 }
01577 
01578 
01579 
01585 void Fl_Text_Display::absolute_top_line_number(int oldFirstChar) {
01586   if (maintaining_absolute_top_line_number()) {
01587     if (mFirstChar < oldFirstChar)
01588       mAbsTopLineNum -= buffer()->count_lines(mFirstChar, oldFirstChar);
01589     else
01590       mAbsTopLineNum += buffer()->count_lines(oldFirstChar, mFirstChar);
01591   }
01592 }
01593 
01594 
01595 
01602 int Fl_Text_Display::maintaining_absolute_top_line_number() const {
01603   return mContinuousWrap &&
01604   (mLineNumWidth != 0 || mNeedAbsTopLineNum);
01605 }
01606 
01607 
01608 
01616 void Fl_Text_Display::reset_absolute_top_line_number() {
01617   mAbsTopLineNum = 1;
01618   absolute_top_line_number(0);
01619 }
01620 
01621 
01622 
01634 int Fl_Text_Display::position_to_line( int pos, int *lineNum ) const {
01635   IS_UTF8_ALIGNED2(buffer(), pos)
01636 
01637   int i;
01638   
01639   *lineNum = 0;
01640   if ( pos < mFirstChar ) return 0;
01641   if ( pos > mLastChar ) {
01642     if ( empty_vlines() ) {
01643       if ( mLastChar < mBuffer->length() ) {
01644         if ( !position_to_line( mLastChar, lineNum ) ) {
01645           Fl::error("Fl_Text_Display::position_to_line(): Consistency check ptvl failed");
01646           return 0;
01647         }
01648         return ++( *lineNum ) <= mNVisibleLines - 1;
01649       } else {
01650         position_to_line( buffer()->prev_char_clipped(mLastChar), lineNum );
01651         return 1;
01652       }
01653     }
01654     return 0;
01655   }
01656   
01657   for ( i = mNVisibleLines - 1; i >= 0; i-- ) {
01658     if ( mLineStarts[ i ] != -1 && pos >= mLineStarts[ i ] ) {
01659       *lineNum = i;
01660       return 1;
01661     }
01662   }
01663   return 0;   /* probably never be reached */
01664 }
01665 
01666 
01690 int Fl_Text_Display::handle_vline(
01691                                   int mode, 
01692                                   int lineStartPos, int lineLen, int leftChar, int rightChar,
01693                                   int Y, int bottomClip,
01694                                   int leftClip, int rightClip) const
01695 {
01696   IS_UTF8_ALIGNED2(buffer(), lineStartPos)
01697 
01698   // FIXME: we need to allow two modes for FIND_INDEX: one on the edge of the 
01699   // FIXME: character for selection, and one on the character center for cursors.
01700   int i, X, startX, startIndex, style, charStyle;
01701   char *lineStr;
01702   
01703   if ( lineStartPos == -1 ) {
01704     lineStr = NULL;
01705   } else {
01706     lineStr = mBuffer->text_range( lineStartPos, lineStartPos + lineLen );
01707   }
01708   
01709   if (mode==GET_WIDTH) {
01710     X = 0;
01711   } else if (mode==FIND_INDEX_FROM_ZERO) {
01712     X = 0; 
01713     mode = FIND_INDEX;
01714   } else {
01715     X = text_area.x - mHorizOffset;
01716   }
01717   
01718   startX = X;
01719   startIndex = 0;
01720   if (!lineStr) {
01721     // just clear the background
01722     if (mode==DRAW_LINE) {
01723       style = position_style(lineStartPos, lineLen, -1);
01724       draw_string( style|BG_ONLY_MASK, text_area.x, Y, text_area.x+text_area.w, lineStr, lineLen );
01725     }
01726     if (mode==FIND_INDEX) {
01727       IS_UTF8_ALIGNED2(buffer(), lineStartPos)
01728       return lineStartPos;
01729     }
01730     return 0;
01731   }
01732   
01733   char currChar = 0, prevChar = 0;
01734   // draw the line
01735   style = position_style(lineStartPos, lineLen, 0);
01736   for (i=0; i<lineLen; ) {
01737     currChar = lineStr[i]; // one byte is enough to handele tabs and other cases
01738     int len = fl_utf8len1(currChar);
01739     if (len<=0) len = 1; // OUCH!
01740     charStyle = position_style(lineStartPos, lineLen, i);
01741     if (charStyle!=style || currChar=='\t' || prevChar=='\t') {
01742       // draw a segment whenever the style changes or a Tab is found
01743       int w = 0;
01744       if (prevChar=='\t') {
01745         // draw a single Tab space
01746         int tab = (int)col_to_x(8);
01747         int xAbs = (mode==GET_WIDTH) ? startX : startX+mHorizOffset-text_area.x;
01748         w = (((xAbs/tab)+1)*tab) - xAbs;
01749         if (mode==DRAW_LINE)
01750           draw_string( style|BG_ONLY_MASK, startX, Y, startX+w, 0, 0 );
01751         if (mode==FIND_INDEX && startX+w>rightClip) {
01752           // find x pos inside block
01753           free(lineStr);
01754           return lineStartPos + startIndex;
01755         }
01756       } else {
01757         // draw a text segment
01758         w = int( string_width( lineStr+startIndex, i-startIndex, style ) );
01759         if (mode==DRAW_LINE)
01760           draw_string( style, startX, Y, startX+w, lineStr+startIndex, i-startIndex );
01761         if (mode==FIND_INDEX && startX+w>rightClip) {
01762           // find x pos inside block
01763           int di = find_x(lineStr+startIndex, i-startIndex, style, rightClip-startX);
01764           free(lineStr);
01765           IS_UTF8_ALIGNED2(buffer(), (lineStartPos+startIndex+di))
01766           return lineStartPos + startIndex + di;
01767         }
01768       }
01769       style = charStyle;
01770       startX += w;
01771       startIndex = i;
01772     }
01773     i += len;
01774     prevChar = currChar;
01775   }
01776   int w = 0;
01777   if (currChar=='\t') {
01778     // draw a single Tab space
01779     int tab = (int)col_to_x(8);
01780     int xAbs = (mode==GET_WIDTH) ? startX : startX+mHorizOffset-text_area.x;
01781     w = (((xAbs/tab)+1)*tab) - xAbs;
01782     if (mode==DRAW_LINE)
01783       draw_string( style|BG_ONLY_MASK, startX, Y, startX+w, 0, 0 );
01784     if (mode==FIND_INDEX) {
01785       // find x pos inside block
01786       free(lineStr);
01787       return lineStartPos + startIndex + ( rightClip-startX>w ? 1 : 0 );
01788     }
01789   } else {
01790     w = int( string_width( lineStr+startIndex, i-startIndex, style ) );
01791     if (mode==DRAW_LINE)
01792       draw_string( style, startX, Y, startX+w, lineStr+startIndex, i-startIndex );
01793     if (mode==FIND_INDEX) {
01794       // find x pos inside block
01795       int di = find_x(lineStr+startIndex, i-startIndex, style, rightClip-startX);
01796       free(lineStr);
01797       IS_UTF8_ALIGNED2(buffer(), (lineStartPos+startIndex+di))
01798       return lineStartPos + startIndex + di;
01799     }
01800   }
01801   if (mode==GET_WIDTH) {
01802     free(lineStr);
01803     return startX+w;
01804   }
01805   
01806   // clear the rest of the line
01807   startX += w;
01808   style = position_style(lineStartPos, lineLen, i);
01809   if (mode==DRAW_LINE)
01810     draw_string( style|BG_ONLY_MASK, startX, Y, text_area.x+text_area.w, lineStr, lineLen );
01811   
01812   free(lineStr);
01813   IS_UTF8_ALIGNED2(buffer(), (lineStartPos+lineLen))
01814   return lineStartPos + lineLen;
01815 }
01816 
01817 
01826 int Fl_Text_Display::find_x(const char *s, int len, int style, int x) const {
01827   IS_UTF8_ALIGNED(s)
01828 
01829   // TODO: use binary search which may be quicker.
01830   int i = 0;
01831   while (i<len) {
01832     int cl = fl_utf8len1(s[i]);
01833     int w = int( string_width(s, i+cl, style) );
01834     if (w>x) 
01835       return i;
01836     i += cl;
01837   }  
01838   return len;
01839 }
01840 
01841 
01842 
01856 void Fl_Text_Display::draw_vline(int visLineNum, int leftClip, int rightClip,
01857                                  int leftCharIndex, int rightCharIndex) {
01858   int Y, lineStartPos, lineLen, fontHeight;
01859   
01860   //  printf("draw_vline(visLineNum=%d, leftClip=%d, rightClip=%d, leftCharIndex=%d, rightCharIndex=%d)\n",
01861   //         visLineNum, leftClip, rightClip, leftCharIndex, rightCharIndex);
01862   //  printf("nNVisibleLines=%d\n", mNVisibleLines);
01863   
01864   /* If line is not displayed, skip it */
01865   if ( visLineNum < 0 || visLineNum >= mNVisibleLines )
01866     return;
01867   
01868   /* Calculate Y coordinate of the string to draw */
01869   fontHeight = mMaxsize;
01870   Y = text_area.y + visLineNum * fontHeight;
01871   
01872   /* Get the text, length, and  buffer position of the line to display */
01873   lineStartPos = mLineStarts[ visLineNum ];
01874   if ( lineStartPos == -1 ) {
01875     lineLen = 0;
01876   } else {
01877     lineLen = vline_length( visLineNum );
01878   }
01879   
01880   /* Shrink the clipping range to the active display area */
01881   leftClip = max( text_area.x, leftClip );
01882   rightClip = min( rightClip, text_area.x + text_area.w );
01883   
01884   handle_vline(DRAW_LINE, 
01885                lineStartPos, lineLen, leftCharIndex, rightCharIndex,
01886                Y, Y+fontHeight, leftClip, rightClip);
01887   return;
01888 }
01889 
01890 
01891 
01908 void Fl_Text_Display::draw_string(int style, 
01909                                   int X, int Y, int toX,
01910                                   const char *string, int nChars) const {
01911   IS_UTF8_ALIGNED(string)
01912 
01913   const Style_Table_Entry * styleRec;
01914   
01915   /* Draw blank area rather than text, if that was the request */
01916   if ( style & FILL_MASK ) {
01917     if (style & TEXT_ONLY_MASK) return;
01918     clear_rect( style, X, Y, toX - X, mMaxsize );
01919     return;
01920   }
01921   /* Set font, color, and gc depending on style.  For normal text, GCs
01922    for normal drawing, or drawing within a Fl_Text_Selection or highlight are
01923    pre-allocated and pre-configured.  For syntax highlighting, GCs are
01924    configured here, on the fly. */
01925   
01926   Fl_Font font = textfont();
01927   int fsize = textsize();
01928   Fl_Color foreground;
01929   Fl_Color background;
01930   
01931   if ( style & STYLE_LOOKUP_MASK ) {
01932     int si = (style & STYLE_LOOKUP_MASK) - 'A';
01933     if (si < 0) si = 0;
01934     else if (si >= mNStyles) si = mNStyles - 1;
01935     
01936     styleRec = mStyleTable + si;
01937     font  = styleRec->font;
01938     fsize = styleRec->size;
01939     
01940     if (style & PRIMARY_MASK) {
01941       if (Fl::focus() == this) background = selection_color();
01942       else background = fl_color_average(color(), selection_color(), 0.4f);
01943     } else if (style & HIGHLIGHT_MASK) {
01944       if (Fl::focus() == this) background = fl_color_average(color(), selection_color(), 0.5f);
01945       else background = fl_color_average(color(), selection_color(), 0.6f);
01946     } else background = color();
01947     foreground = fl_contrast(styleRec->color, background);
01948   } else if (style & PRIMARY_MASK) {
01949     if (Fl::focus() == this) background = selection_color();
01950     else background = fl_color_average(color(), selection_color(), 0.4f);
01951     foreground = fl_contrast(textcolor(), background);
01952   } else if (style & HIGHLIGHT_MASK) {
01953     if (Fl::focus() == this) background = fl_color_average(color(), selection_color(), 0.5f);
01954     else background = fl_color_average(color(), selection_color(), 0.6f);
01955     foreground = fl_contrast(textcolor(), background);
01956   } else {
01957     foreground = textcolor();
01958     background = color();
01959   }
01960   
01961   if (!(style & TEXT_ONLY_MASK)) {
01962     fl_color( background );
01963     fl_rectf( X, Y, toX - X, mMaxsize );
01964   }
01965   if (!(style & BG_ONLY_MASK)) {
01966     fl_color( foreground );
01967     fl_font( font, fsize );
01968 #if !(defined(__APPLE__) || defined(WIN32)) && USE_XFT
01969     // makes sure antialiased ÄÖÜ do not leak on line above
01970     fl_push_clip(X, Y, toX - X, mMaxsize);
01971 #endif
01972     fl_draw( string, nChars, X, Y + mMaxsize - fl_descent());
01973 #if !(defined(__APPLE__) || defined(WIN32)) && USE_XFT
01974     fl_pop_clip();
01975 #endif
01976   }
01977   
01978   // CET - FIXME
01979   /* If any space around the character remains unfilled (due to use of
01980    different sized fonts for highlighting), fill in above or below
01981    to erase previously drawn characters */
01982   /*
01983    if (fs->ascent < mAscent)
01984    clear_rect( style, X, Y, toX - X, mAscent - fs->ascent);
01985    if (fs->descent < mDescent)
01986    clear_rect( style, X, Y + mAscent + fs->descent, toX - x,
01987    mDescent - fs->descent);
01988    */
01989   /* Underline if style is secondary Fl_Text_Selection */
01990   
01991   /*
01992    if (style & SECONDARY_MASK)
01993    XDrawLine(XtDisplay(mW), XtWindow(mW), gc, x,
01994    y + mAscent, toX - 1, Y + fs->ascent);
01995    */
01996 }
01997 
01998 
01999 
02006 void Fl_Text_Display::clear_rect(int style, 
02007                                  int X, int Y,
02008                                  int width, int height) const {
02009   /* A width of zero means "clear to end of window" to XClearArea */
02010   if ( width == 0 )
02011     return;
02012   
02013   if (style & PRIMARY_MASK) {
02014     if (Fl::focus()==this) {
02015       fl_color(selection_color());
02016     } else {
02017       fl_color(fl_color_average(color(), selection_color(), 0.4f));
02018     }
02019   } else if (style & HIGHLIGHT_MASK) {
02020     if (Fl::focus()==this) {
02021       fl_color(fl_color_average(color(), selection_color(), 0.5f));
02022     } else {
02023       fl_color(fl_color_average(color(), selection_color(), 0.6f));
02024     }
02025   } else {
02026     fl_color( color() );
02027   }
02028   fl_rectf( X, Y, width, height );
02029 }
02030 
02031 
02032 
02038 void Fl_Text_Display::draw_cursor( int X, int Y ) {
02039 
02040   typedef struct {
02041     int x1, y1, x2, y2;
02042   }
02043   Segment;
02044 
02045   Segment segs[ 5 ];
02046   int left, right, cursorWidth, midY;
02047   //    int fontWidth = mFontStruct->min_bounds.width, nSegs = 0;
02048   int fontWidth = TMPFONTWIDTH; // CET - FIXME
02049   int nSegs = 0;
02050   int fontHeight = mMaxsize;
02051   int bot = Y + fontHeight - 1;
02052   
02053   if ( X < text_area.x - 1 || X > text_area.x + text_area.w )
02054     return;
02055   
02056   /* For cursors other than the block, make them around 2/3 of a character
02057    width, rounded to an even number of pixels so that X will draw an
02058    odd number centered on the stem at x. */
02059   cursorWidth = 4;   //(fontWidth/3) * 2;
02060   left = X - cursorWidth / 2;
02061   right = left + cursorWidth;
02062   
02063   /* Create segments and draw cursor */
02064   if ( mCursorStyle == CARET_CURSOR ) {
02065     midY = bot - fontHeight / 5;
02066     segs[ 0 ].x1 = left; segs[ 0 ].y1 = bot; segs[ 0 ].x2 = X; segs[ 0 ].y2 = midY;
02067     segs[ 1 ].x1 = X; segs[ 1 ].y1 = midY; segs[ 1 ].x2 = right; segs[ 1 ].y2 = bot;
02068     segs[ 2 ].x1 = left; segs[ 2 ].y1 = bot; segs[ 2 ].x2 = X; segs[ 2 ].y2 = midY - 1;
02069     segs[ 3 ].x1 = X; segs[ 3 ].y1 = midY - 1; segs[ 3 ].x2 = right; segs[ 3 ].y2 = bot;
02070     nSegs = 4;
02071   } else if ( mCursorStyle == NORMAL_CURSOR ) {
02072     segs[ 0 ].x1 = left; segs[ 0 ].y1 = Y; segs[ 0 ].x2 = right; segs[ 0 ].y2 = Y;
02073     segs[ 1 ].x1 = X; segs[ 1 ].y1 = Y; segs[ 1 ].x2 = X; segs[ 1 ].y2 = bot;
02074     segs[ 2 ].x1 = left; segs[ 2 ].y1 = bot; segs[ 2 ].x2 = right; segs[ 2 ].y2 = bot;
02075     nSegs = 3;
02076   } else if ( mCursorStyle == HEAVY_CURSOR ) {
02077     segs[ 0 ].x1 = X - 1; segs[ 0 ].y1 = Y; segs[ 0 ].x2 = X - 1; segs[ 0 ].y2 = bot;
02078     segs[ 1 ].x1 = X; segs[ 1 ].y1 = Y; segs[ 1 ].x2 = X; segs[ 1 ].y2 = bot;
02079     segs[ 2 ].x1 = X + 1; segs[ 2 ].y1 = Y; segs[ 2 ].x2 = X + 1; segs[ 2 ].y2 = bot;
02080     segs[ 3 ].x1 = left; segs[ 3 ].y1 = Y; segs[ 3 ].x2 = right; segs[ 3 ].y2 = Y;
02081     segs[ 4 ].x1 = left; segs[ 4 ].y1 = bot; segs[ 4 ].x2 = right; segs[ 4 ].y2 = bot;
02082     nSegs = 5;
02083   } else if ( mCursorStyle == DIM_CURSOR ) {
02084     midY = Y + fontHeight / 2;
02085     segs[ 0 ].x1 = X; segs[ 0 ].y1 = Y; segs[ 0 ].x2 = X; segs[ 0 ].y2 = Y;
02086     segs[ 1 ].x1 = X; segs[ 1 ].y1 = midY; segs[ 1 ].x2 = X; segs[ 1 ].y2 = midY;
02087     segs[ 2 ].x1 = X; segs[ 2 ].y1 = bot; segs[ 2 ].x2 = X; segs[ 2 ].y2 = bot;
02088     nSegs = 3;
02089   } else if ( mCursorStyle == BLOCK_CURSOR ) {
02090     right = X + fontWidth;
02091     segs[ 0 ].x1 = X; segs[ 0 ].y1 = Y; segs[ 0 ].x2 = right; segs[ 0 ].y2 = Y;
02092     segs[ 1 ].x1 = right; segs[ 1 ].y1 = Y; segs[ 1 ].x2 = right; segs[ 1 ].y2 = bot;
02093     segs[ 2 ].x1 = right; segs[ 2 ].y1 = bot; segs[ 2 ].x2 = X; segs[ 2 ].y2 = bot;
02094     segs[ 3 ].x1 = X; segs[ 3 ].y1 = bot; segs[ 3 ].x2 = X; segs[ 3 ].y2 = Y;
02095     nSegs = 4;
02096   }
02097   fl_color( mCursor_color );
02098   
02099   for ( int k = 0; k < nSegs; k++ ) {
02100     fl_line( segs[ k ].x1, segs[ k ].y1, segs[ k ].x2, segs[ k ].y2 );
02101   }
02102 }
02103 
02104 
02105 
02129 int Fl_Text_Display::position_style( int lineStartPos, int lineLen, int lineIndex) const 
02130 {
02131   IS_UTF8_ALIGNED2(buffer(), lineStartPos)
02132 
02133   Fl_Text_Buffer * buf = mBuffer;
02134   Fl_Text_Buffer *styleBuf = mStyleBuffer;
02135   int pos, style = 0;
02136   
02137   if ( lineStartPos == -1 || buf == NULL )
02138     return FILL_MASK;
02139   
02140   pos = lineStartPos + min( lineIndex, lineLen );
02141   
02142   if ( lineIndex >= lineLen )
02143     style = FILL_MASK;
02144   else if ( styleBuf != NULL ) {
02145     style = ( unsigned char ) styleBuf->byte_at( pos );
02146     if (style == mUnfinishedStyle && mUnfinishedHighlightCB) {
02147       /* encountered "unfinished" style, trigger parsing */
02148       (mUnfinishedHighlightCB)( pos, mHighlightCBArg);
02149       style = (unsigned char) styleBuf->byte_at( pos);
02150     }
02151   }
02152   if (buf->primary_selection()->includes(pos))
02153     style |= PRIMARY_MASK;
02154   if (buf->highlight_selection()->includes(pos))
02155     style |= HIGHLIGHT_MASK;
02156   if (buf->secondary_selection()->includes(pos))
02157     style |= SECONDARY_MASK;
02158   return style;
02159 }
02160 
02161 
02170 double Fl_Text_Display::string_width( const char *string, int length, int style ) const {
02171   IS_UTF8_ALIGNED(string)
02172 
02173   Fl_Font font;
02174   Fl_Fontsize fsize;
02175   
02176   if ( mNStyles && (style & STYLE_LOOKUP_MASK) ) {
02177     int si = (style & STYLE_LOOKUP_MASK) - 'A';
02178     if (si < 0) si = 0;
02179     else if (si >= mNStyles) si = mNStyles - 1;
02180     
02181     font  = mStyleTable[si].font;
02182     fsize = mStyleTable[si].size;
02183   } else {
02184     font  = textfont();
02185     fsize = textsize();
02186   }
02187   fl_font( font, fsize );
02188   return fl_width( string, length );
02189 }
02190 
02191 
02192 
02206 int Fl_Text_Display::xy_to_position( int X, int Y, int posType ) const {
02207   int lineStart, lineLen, fontHeight;
02208   int visLineNum;
02209   
02210   /* Find the visible line number corresponding to the Y coordinate */
02211   fontHeight = mMaxsize;
02212   visLineNum = ( Y - text_area.y ) / fontHeight;
02213   if ( visLineNum < 0 )
02214     return mFirstChar;
02215   if ( visLineNum >= mNVisibleLines )
02216     visLineNum = mNVisibleLines - 1;
02217   
02218   /* Find the position at the start of the line */
02219   lineStart = mLineStarts[ visLineNum ];
02220   
02221   /* If the line start was empty, return the last position in the buffer */
02222   if ( lineStart == -1 )
02223     return mBuffer->length();
02224   
02225   /* Get the line text and its length */
02226   lineLen = vline_length( visLineNum );
02227   
02228   return handle_vline(FIND_INDEX, 
02229                       lineStart, lineLen, 0, 0, 
02230                       0, 0,
02231                       text_area.x, X);
02232 }
02233 
02234 
02235 
02250 void Fl_Text_Display::xy_to_rowcol( int X, int Y, int *row,
02251                                    int *column, int posType ) const {
02252   int fontHeight = mMaxsize;
02253   int fontWidth = TMPFONTWIDTH;   //mFontStruct->max_bounds.width;
02254   
02255   /* Find the visible line number corresponding to the Y coordinate */
02256   *row = ( Y - text_area.y ) / fontHeight;
02257   if ( *row < 0 ) *row = 0;
02258   if ( *row >= mNVisibleLines ) *row = mNVisibleLines - 1;
02259   
02260   *column = ( ( X - text_area.x ) + mHorizOffset +
02261              ( posType == CURSOR_POS ? fontWidth / 2 : 0 ) ) / fontWidth;
02262   if ( *column < 0 ) * column = 0;
02263 }
02264 
02265 
02266 
02278 void Fl_Text_Display::offset_line_starts( int newTopLineNum ) {
02279   int oldTopLineNum = mTopLineNum;
02280   int oldFirstChar = mFirstChar;
02281   int lineDelta = newTopLineNum - oldTopLineNum;
02282   int nVisLines = mNVisibleLines;
02283   int *lineStarts = mLineStarts;
02284   int i, lastLineNum;
02285   Fl_Text_Buffer *buf = mBuffer;
02286   
02287   /* If there was no offset, nothing needs to be changed */
02288   if ( lineDelta == 0 )
02289     return;
02290   
02291   /* Find the new value for mFirstChar by counting lines from the nearest
02292    known line start (start or end of buffer, or the closest value in the
02293    lineStarts array) */
02294   lastLineNum = oldTopLineNum + nVisLines - 1;
02295   if ( newTopLineNum < oldTopLineNum && newTopLineNum < -lineDelta ) {
02296     mFirstChar = skip_lines( 0, newTopLineNum - 1, true );
02297   } else if ( newTopLineNum < oldTopLineNum ) {
02298     mFirstChar = rewind_lines( mFirstChar, -lineDelta );
02299   } else if ( newTopLineNum < lastLineNum ) {
02300     mFirstChar = lineStarts[ newTopLineNum - oldTopLineNum ];
02301   } else if ( newTopLineNum - lastLineNum < mNBufferLines - newTopLineNum ) {
02302     mFirstChar = skip_lines( lineStarts[ nVisLines - 1 ],
02303                             newTopLineNum - lastLineNum, true );
02304   } else {
02305     mFirstChar = rewind_lines( buf->length(), mNBufferLines - newTopLineNum + 1 );
02306   }
02307   
02308   /* Fill in the line starts array */
02309   if ( lineDelta < 0 && -lineDelta < nVisLines ) {
02310     for ( i = nVisLines - 1; i >= -lineDelta; i-- )
02311       lineStarts[ i ] = lineStarts[ i + lineDelta ];
02312     calc_line_starts( 0, -lineDelta );
02313   } else if ( lineDelta > 0 && lineDelta < nVisLines ) {
02314     for ( i = 0; i < nVisLines - lineDelta; i++ )
02315       lineStarts[ i ] = lineStarts[ i + lineDelta ];
02316     calc_line_starts( nVisLines - lineDelta, nVisLines - 1 );
02317   } else
02318     calc_line_starts( 0, nVisLines );
02319   
02320   /* Set lastChar and mTopLineNum */
02321   calc_last_char();
02322   mTopLineNum = newTopLineNum;
02323   
02324   /* If we're numbering lines or being asked to maintain an absolute line
02325    number, re-calculate the absolute line number */
02326   absolute_top_line_number(oldFirstChar);
02327 }
02328 
02329 
02330 
02346 void Fl_Text_Display::update_line_starts(int pos, int charsInserted,
02347                                          int charsDeleted, int linesInserted, 
02348                                          int linesDeleted, int *scrolled ) {
02349   IS_UTF8_ALIGNED2(buffer(), pos)
02350 
02351   int *lineStarts = mLineStarts;
02352   int i, lineOfPos, lineOfEnd, nVisLines = mNVisibleLines;
02353   int charDelta = charsInserted - charsDeleted;
02354   int lineDelta = linesInserted - linesDeleted;
02355   
02356   /* If all of the changes were before the displayed text, the display
02357    doesn't change, just update the top line num and offset the line
02358    start entries and first and last characters */
02359   if ( pos + charsDeleted < mFirstChar ) {
02360     mTopLineNum += lineDelta;
02361     for ( i = 0; i < nVisLines && lineStarts[i] != -1; i++ )
02362       lineStarts[ i ] += charDelta;
02363     mFirstChar += charDelta;
02364     mLastChar += charDelta;
02365     *scrolled = 0;
02366     return;
02367   }
02368   
02369   /* The change began before the beginning of the displayed text, but
02370    part or all of the displayed text was deleted */
02371   if ( pos < mFirstChar ) {
02372     /* If some text remains in the window, anchor on that  */
02373     if ( position_to_line( pos + charsDeleted, &lineOfEnd ) &&
02374         ++lineOfEnd < nVisLines && lineStarts[ lineOfEnd ] != -1 ) {
02375       mTopLineNum = max( 1, mTopLineNum + lineDelta );
02376       mFirstChar = rewind_lines(lineStarts[ lineOfEnd ] + charDelta, lineOfEnd );
02377       /* Otherwise anchor on original line number and recount everything */
02378     } else {
02379       if ( mTopLineNum > mNBufferLines + lineDelta ) {
02380         mTopLineNum = 1;
02381         mFirstChar = 0;
02382       } else
02383         mFirstChar = skip_lines( 0, mTopLineNum - 1, true );
02384     }
02385     calc_line_starts( 0, nVisLines - 1 );
02386     /* calculate lastChar by finding the end of the last displayed line */
02387     calc_last_char();
02388     *scrolled = 1;
02389     return;
02390   }
02391   
02392   /* If the change was in the middle of the displayed text (it usually is),
02393    salvage as much of the line starts array as possible by moving and
02394    offsetting the entries after the changed area, and re-counting the
02395    added lines or the lines beyond the salvaged part of the line starts
02396    array */
02397   if ( pos <= mLastChar ) {
02398     /* find line on which the change began */
02399     position_to_line( pos, &lineOfPos );
02400     /* salvage line starts after the changed area */
02401     if ( lineDelta == 0 ) {
02402       for ( i = lineOfPos + 1; i < nVisLines && lineStarts[ i ] != -1; i++ )
02403         lineStarts[ i ] += charDelta;
02404     } else if ( lineDelta > 0 ) {
02405       for ( i = nVisLines - 1; i >= lineOfPos + lineDelta + 1; i-- )
02406         lineStarts[ i ] = lineStarts[ i - lineDelta ] +
02407         ( lineStarts[ i - lineDelta ] == -1 ? 0 : charDelta );
02408     } else /* (lineDelta < 0) */ {
02409       for ( i = max( 0, lineOfPos + 1 ); i < nVisLines + lineDelta; i++ )
02410         lineStarts[ i ] = lineStarts[ i - lineDelta ] +
02411         ( lineStarts[ i - lineDelta ] == -1 ? 0 : charDelta );
02412     }
02413     /* fill in the missing line starts */
02414     if ( linesInserted >= 0 )
02415       calc_line_starts( lineOfPos + 1, lineOfPos + linesInserted );
02416     if ( lineDelta < 0 )
02417       calc_line_starts( nVisLines + lineDelta, nVisLines );
02418     /* calculate lastChar by finding the end of the last displayed line */
02419     calc_last_char();
02420     *scrolled = 0;
02421     return;
02422   }
02423   
02424   /* Change was past the end of the displayed text, but displayable by virtue
02425    of being an insert at the end of the buffer into visible blank lines */
02426   if ( empty_vlines() ) {
02427     position_to_line( pos, &lineOfPos );
02428     calc_line_starts( lineOfPos, lineOfPos + linesInserted );
02429     calc_last_char();
02430     *scrolled = 0;
02431     return;
02432   }
02433   
02434   /* Change was beyond the end of the buffer and not visible, do nothing */
02435   *scrolled = 0;
02436 }
02437 
02438 
02439 
02452 void Fl_Text_Display::calc_line_starts( int startLine, int endLine ) {
02453   int startPos, bufLen = mBuffer->length();
02454   int line, lineEnd, nextLineStart, nVis = mNVisibleLines;
02455   int *lineStarts = mLineStarts;
02456   
02457   /* Clean up (possibly) messy input parameters */
02458   if ( endLine < 0 ) endLine = 0;
02459   if ( endLine >= nVis ) endLine = nVis - 1;
02460   if ( startLine < 0 ) startLine = 0;
02461   if ( startLine >= nVis ) startLine = nVis - 1;
02462   if ( startLine > endLine )
02463     return;
02464   
02465   /* Find the last known good line number -> position mapping */
02466   if ( startLine == 0 ) {
02467     lineStarts[ 0 ] = mFirstChar;
02468     startLine = 1;
02469   }
02470   startPos = lineStarts[ startLine - 1 ];
02471   
02472   /* If the starting position is already past the end of the text,
02473    fill in -1's (means no text on line) and return */
02474   if ( startPos == -1 ) {
02475     for ( line = startLine; line <= endLine; line++ )
02476       lineStarts[ line ] = -1;
02477     return;
02478   }
02479   
02480   /* Loop searching for ends of lines and storing the positions of the
02481    start of the next line in lineStarts */
02482   for ( line = startLine; line <= endLine; line++ ) {
02483     find_line_end(startPos, true, &lineEnd, &nextLineStart);
02484     startPos = nextLineStart;
02485     if ( startPos >= bufLen ) {
02486       /* If the buffer ends with a newline or line break, put
02487        buf->length() in the next line start position (instead of
02488        a -1 which is the normal marker for an empty line) to
02489        indicate that the cursor may safely be displayed there */
02490       if ( line == 0 || ( lineStarts[ line - 1 ] != bufLen &&
02491                          lineEnd != nextLineStart ) ) {
02492         lineStarts[ line ] = bufLen;
02493         line++;
02494       }
02495       break;
02496     }
02497     lineStarts[ line ] = startPos;
02498   }
02499   
02500   /* Set any entries beyond the end of the text to -1 */
02501   for ( ; line <= endLine; line++ )
02502     lineStarts[ line ] = -1;
02503 }
02504 
02505 
02506 
02513 void Fl_Text_Display::calc_last_char() {
02514   int i;
02515   for (i = mNVisibleLines - 1; i >= 0 && mLineStarts[i] == -1; i--) ;
02516   mLastChar = i < 0 ? 0 : line_end(mLineStarts[i], true);
02517 }
02518 
02519 
02520 
02527 void Fl_Text_Display::scroll(int topLineNum, int horizOffset) {
02528   mTopLineNumHint = topLineNum;
02529   mHorizOffsetHint = horizOffset;
02530   resize(x(), y(), w(), h());
02531 }
02532 
02533 
02534 
02541 int Fl_Text_Display::scroll_(int topLineNum, int horizOffset) {
02542   /* Limit the requested scroll position to allowable values */
02543   if (topLineNum > mNBufferLines + 3 - mNVisibleLines)
02544     topLineNum = mNBufferLines + 3 - mNVisibleLines;
02545   if (topLineNum < 1) topLineNum = 1;
02546   
02547   if (horizOffset > longest_vline() - text_area.w)
02548     horizOffset = longest_vline() - text_area.w;
02549   if (horizOffset < 0) horizOffset = 0;
02550   
02551   /* Do nothing if scroll position hasn't actually changed or there's no
02552    window to draw in yet */
02553   if (mHorizOffset == horizOffset && mTopLineNum == topLineNum)
02554     return 0;
02555   
02556   /* If the vertical scroll position has changed, update the line
02557    starts array and related counters in the text display */
02558   offset_line_starts(topLineNum);
02559   
02560   /* Just setting mHorizOffset is enough information for redisplay */
02561   mHorizOffset = horizOffset;
02562   
02563   // redraw all text
02564   damage(FL_DAMAGE_EXPOSE);
02565   return 1;
02566 }
02567 
02568 
02569 
02576 void Fl_Text_Display::update_v_scrollbar() {
02577   /* The vertical scrollbar value and slider size directly represent the top
02578    line number, and the number of visible lines respectively.  The scroll
02579    bar maximum value is chosen to generally represent the size of the whole
02580    buffer, with minor adjustments to keep the scrollbar widget happy */
02581 #ifdef DEBUG
02582   printf("Fl_Text_Display::update_v_scrollbar():\n"
02583          "    mTopLineNum=%d, mNVisibleLines=%d, mNBufferLines=%d\n",
02584          mTopLineNum, mNVisibleLines, mNBufferLines);
02585 #endif // DEBUG
02586   
02587   mVScrollBar->value(mTopLineNum, mNVisibleLines, 1, mNBufferLines+2);
02588   mVScrollBar->linesize(3);
02589 }
02590 
02591 
02598 void Fl_Text_Display::update_h_scrollbar() {
02599   int sliderMax = max(longest_vline(), text_area.w + mHorizOffset);
02600   mHScrollBar->value( mHorizOffset, text_area.w, 0, sliderMax );
02601 }
02602 
02603 
02604 
02608 void Fl_Text_Display::v_scrollbar_cb(Fl_Scrollbar* b, Fl_Text_Display* textD) {
02609   if (b->value() == textD->mTopLineNum) return;
02610   textD->scroll(b->value(), textD->mHorizOffset);
02611 }
02612 
02613 
02614 
02618 void Fl_Text_Display::h_scrollbar_cb(Fl_Scrollbar* b, Fl_Text_Display* textD) {
02619   if (b->value() == textD->mHorizOffset) return;
02620   textD->scroll(textD->mTopLineNum, b->value());
02621 }
02622 
02623 
02624 
02634 void Fl_Text_Display::draw_line_numbers(bool /*clearAll*/) {
02635 #if 0
02636   int y, line, visLine, nCols, lineStart;
02637   char lineNumString[12];
02638   int lineHeight = mMaxsize ? mMaxsize : textsize_;
02639   int charWidth = TMPFONTWIDTH;   //mFontStruct->max_bounds.width;
02640   
02641   /* Don't draw if mLineNumWidth == 0 (line numbers are hidden), or widget is
02642    not yet realized */
02643   if (mLineNumWidth == 0 || visible_r())
02644     return;
02645   
02646   /* GC is allocated on demand, since not everyone will use line numbering */
02647   if (textD->lineNumGC == NULL) {
02648     XGCValues values;
02649     values.foreground = textD->lineNumFGPixel;
02650     values.background = textD->bgPixel;
02651     values.font = textD->fontStruct->fid;
02652     textD->lineNumGC = XtGetGC(textD->w,
02653                                GCFont| GCForeground | GCBackground, &values);
02654   }
02655   
02656   /* Erase the previous contents of the line number area, if requested */
02657   if (clearAll)
02658     XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->lineNumLeft,
02659                textD->top, textD->lineNumWidth, textD->height, False);
02660   
02661   /* Draw the line numbers, aligned to the text */
02662   nCols = min(11, textD->lineNumWidth / charWidth);
02663   y = textD->top;
02664   line = getAbsTopLineNum(textD);
02665   for (visLine=0; visLine < textD->nVisibleLines; visLine++) {
02666     lineStart = textD->lineStarts[visLine];
02667     if (lineStart != -1 && (lineStart==0 ||
02668                             BufGetCharacter(textD->buffer, lineStart-1)=='\n')) {
02669       sprintf(lineNumString, "%*d", nCols, line);
02670       XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w),
02671                        textD->lineNumGC, textD->lineNumLeft, y + textD->ascent,
02672                        lineNumString, strlen(lineNumString));
02673       line++;
02674     } else {
02675       XClearArea(XtDisplay(textD->w), XtWindow(textD->w),
02676                  textD->lineNumLeft, y, textD->lineNumWidth,
02677                  textD->ascent + textD->descent, False);
02678       if (visLine == 0)
02679         line++;
02680     }
02681     y += lineHeight;
02682   }
02683 #endif
02684 }
02685 
02686 static int max( int i1, int i2 ) {
02687   return i1 >= i2 ? i1 : i2;
02688 }
02689 
02690 static int min( int i1, int i2 ) {
02691   return i1 <= i2 ? i1 : i2;
02692 }
02693 
02694 
02695 
02699 static int countlines( const char *string ) {
02700   IS_UTF8_ALIGNED(string)
02701 
02702   const char * c;
02703   int lineCount = 0;
02704   
02705   if (!string) return 0;
02706   
02707   for ( c = string; *c != '\0'; c++ )
02708     if ( *c == '\n' ) lineCount++;
02709   return lineCount;
02710 }
02711 
02712 
02713 
02714 
02720 int Fl_Text_Display::measure_vline( int visLineNum ) const {
02721   int lineLen = vline_length( visLineNum );
02722   int lineStartPos = mLineStarts[ visLineNum ];
02723   if (lineStartPos < 0 || lineLen == 0) return 0;
02724   return handle_vline(GET_WIDTH, lineStartPos, lineLen, 0, 0, 0, 0, 0, 0);
02725 }
02726 
02727 
02728 
02733 int Fl_Text_Display::empty_vlines() const {
02734   return (mNVisibleLines > 0) && (mLineStarts[ mNVisibleLines - 1 ] == -1);
02735 }
02736 
02737 
02738 
02748 int Fl_Text_Display::vline_length( int visLineNum ) const {
02749   int nextLineStart, lineStartPos;
02750   
02751   if (visLineNum < 0 || visLineNum >= mNVisibleLines)
02752     return (0);
02753   
02754   lineStartPos = mLineStarts[ visLineNum ];
02755   
02756   if ( lineStartPos == -1 )
02757     return 0;
02758   
02759   if ( visLineNum + 1 >= mNVisibleLines )
02760     return mLastChar - lineStartPos;
02761   
02762   nextLineStart = mLineStarts[ visLineNum + 1 ];
02763   if ( nextLineStart == -1 )
02764     return mLastChar - lineStartPos;
02765   
02766   int nextLineStartMinus1 = buffer()->prev_char(nextLineStart);
02767   if (wrap_uses_character(nextLineStartMinus1))
02768     return nextLineStartMinus1 - lineStartPos;
02769   
02770   return nextLineStart - lineStartPos;
02771 }
02772 
02773 
02774 
02795 void Fl_Text_Display::find_wrap_range(const char *deletedText, int pos,
02796                                       int nInserted, int nDeleted, 
02797                                       int *modRangeStart, int *modRangeEnd,
02798                                       int *linesInserted, int *linesDeleted) {
02799   IS_UTF8_ALIGNED(deletedText)
02800   IS_UTF8_ALIGNED2(buffer(), pos)
02801 
02802   int length, retPos, retLines, retLineStart, retLineEnd;
02803   Fl_Text_Buffer *deletedTextBuf, *buf = buffer();
02804   int nVisLines = mNVisibleLines;
02805   int *lineStarts = mLineStarts;
02806   int countFrom, countTo, lineStart, adjLineStart, i;
02807   int visLineNum = 0, nLines = 0;
02808   
02809   /*
02810    ** Determine where to begin searching: either the previous newline, or
02811    ** if possible, limit to the start of the (original) previous displayed
02812    ** line, using information from the existing line starts array
02813    */
02814   if (pos >= mFirstChar && pos <= mLastChar) {
02815     for (i=nVisLines-1; i>0; i--) {
02816       if (lineStarts[i] != -1 && pos >= lineStarts[i]) {
02817         break;
02818       }
02819     }
02820     if (i > 0) {
02821       countFrom = lineStarts[i-1];
02822       visLineNum = i-1;
02823     } else {
02824       countFrom = buf->line_start(pos);
02825     }
02826   } else {
02827     countFrom = buf->line_start(pos);
02828   }
02829   
02830   IS_UTF8_ALIGNED2(buf, countFrom)
02831   
02832   /*
02833    ** Move forward through the (new) text one line at a time, counting
02834    ** displayed lines, and looking for either a real newline, or for the
02835    ** line starts to re-sync with the original line starts array
02836    */
02837   lineStart = countFrom;
02838   *modRangeStart = countFrom;
02839   for (;;) {
02840     
02841     /* advance to the next line.  If the line ended in a real newline
02842      or the end of the buffer, that's far enough */
02843     wrapped_line_counter(buf, lineStart, buf->length(), 1, true, 0,
02844                          &retPos, &retLines, &retLineStart, &retLineEnd);
02845     if (retPos >= buf->length()) {
02846       countTo = buf->length();
02847       *modRangeEnd = countTo;
02848       if (retPos != retLineEnd)
02849         nLines++;
02850       break;
02851     } else {
02852       lineStart = retPos;
02853     }
02854     nLines++;
02855     if (lineStart > pos + nInserted && buf->char_at(buf->prev_char(lineStart)) == '\n') {
02856       countTo = lineStart;
02857       *modRangeEnd = lineStart;
02858       break;
02859     }
02860     
02861     /* Don't try to resync in continuous wrap mode with non-fixed font
02862      sizes; it would result in a chicken-and-egg dependency between
02863      the calculations for the inserted and the deleted lines. 
02864      If we're in that mode, the number of deleted lines is calculated in
02865      advance, without resynchronization, so we shouldn't resynchronize
02866      for the inserted lines either. */
02867     if (mSuppressResync)
02868       continue;
02869     
02870     /* check for synchronization with the original line starts array
02871      before pos, if so, the modified range can begin later */
02872     if (lineStart <= pos) {
02873       while (visLineNum<nVisLines && lineStarts[visLineNum] < lineStart)
02874         visLineNum++;
02875       if (visLineNum < nVisLines && lineStarts[visLineNum] == lineStart) {
02876         countFrom = lineStart;
02877         nLines = 0;
02878         if (visLineNum+1 < nVisLines && lineStarts[visLineNum+1] != -1)
02879           *modRangeStart = min(pos, buf->prev_char(lineStarts[visLineNum+1]));
02880         else
02881           *modRangeStart = countFrom;
02882       } else
02883         *modRangeStart = min(*modRangeStart, buf->prev_char(lineStart));
02884     }
02885     
02886     /* check for synchronization with the original line starts array
02887      after pos, if so, the modified range can end early */
02888     else if (lineStart > pos + nInserted) {
02889       adjLineStart = lineStart - nInserted + nDeleted;
02890       while (visLineNum<nVisLines && lineStarts[visLineNum]<adjLineStart)
02891         visLineNum++;
02892       if (visLineNum < nVisLines && lineStarts[visLineNum] != -1 &&
02893           lineStarts[visLineNum] == adjLineStart) {
02894         countTo = line_end(lineStart, true);
02895         *modRangeEnd = lineStart;
02896         break;
02897       }
02898     }
02899   }
02900   *linesInserted = nLines;
02901   
02902   
02903   /* Count deleted lines between countFrom and countTo as the text existed
02904    before the modification (that is, as if the text between pos and
02905    pos+nInserted were replaced by "deletedText").  This extra context is
02906    necessary because wrapping can occur outside of the modified region
02907    as a result of adding or deleting text in the region. This is done by
02908    creating a textBuffer containing the deleted text and the necessary
02909    additional context, and calling the wrappedLineCounter on it.
02910    
02911    NOTE: This must not be done in continuous wrap mode when the font
02912    width is not fixed. In that case, the calculation would try
02913    to access style information that is no longer available (deleted
02914    text), or out of date (updated highlighting), possibly leading 
02915    to completely wrong calculations and/or even crashes eventually.
02916    (This is not theoretical; it really happened.)
02917    
02918    In that case, the calculation of the number of deleted lines
02919    has happened before the buffer was modified (only in that case,
02920    because resynchronization of the line starts is impossible
02921    in that case, which makes the whole calculation less efficient).
02922    */
02923   if (mSuppressResync) {
02924     *linesDeleted = mNLinesDeleted;
02925     mSuppressResync = 0;
02926     return;
02927   }
02928   
02929   length = (pos-countFrom) + nDeleted +(countTo-(pos+nInserted));
02930   deletedTextBuf = new Fl_Text_Buffer(length);
02931   deletedTextBuf->copy(buffer(), countFrom, pos, 0);
02932   if (nDeleted != 0)
02933     deletedTextBuf->insert(pos-countFrom, deletedText);
02934   deletedTextBuf->copy(buffer(), pos+nInserted, countTo, pos-countFrom+nDeleted);
02935   /* Note that we need to take into account an offset for the style buffer:
02936    the deletedTextBuf can be out of sync with the style buffer. */
02937   wrapped_line_counter(deletedTextBuf, 0, length, INT_MAX, true, countFrom, 
02938                        &retPos, &retLines, &retLineStart, &retLineEnd, false);
02939   delete deletedTextBuf;
02940   *linesDeleted = retLines;
02941   mSuppressResync = 0;
02942 }
02943 
02944 
02945 
02963 void Fl_Text_Display::measure_deleted_lines(int pos, int nDeleted) {
02964   IS_UTF8_ALIGNED2(buffer(), pos)
02965 
02966   int retPos, retLines, retLineStart, retLineEnd;
02967   Fl_Text_Buffer *buf = buffer();
02968   int nVisLines = mNVisibleLines;
02969   int *lineStarts = mLineStarts;
02970   int countFrom, lineStart;
02971   int visLineNum = 0, nLines = 0, i;
02972   /*
02973    ** Determine where to begin searching: either the previous newline, or
02974    ** if possible, limit to the start of the (original) previous displayed
02975    ** line, using information from the existing line starts array
02976    */
02977   if (pos >= mFirstChar && pos <= mLastChar) {
02978     for (i=nVisLines-1; i>0; i--)
02979       if (lineStarts[i] != -1 && pos >= lineStarts[i])
02980         break;
02981     if (i > 0) {
02982       countFrom = lineStarts[i-1];
02983       visLineNum = i-1;
02984     } else
02985       countFrom = buf->line_start(pos);
02986   } else
02987     countFrom = buf->line_start(pos);
02988   
02989   /*
02990    ** Move forward through the (new) text one line at a time, counting
02991    ** displayed lines, and looking for either a real newline, or for the
02992    ** line starts to re-sync with the original line starts array
02993    */
02994   lineStart = countFrom;
02995   for (;;) {
02996     /* advance to the next line.  If the line ended in a real newline
02997      or the end of the buffer, that's far enough */
02998     wrapped_line_counter(buf, lineStart, buf->length(), 1, true, 0,
02999                          &retPos, &retLines, &retLineStart, &retLineEnd);
03000     if (retPos >= buf->length()) {
03001       if (retPos != retLineEnd)
03002         nLines++;
03003       break;
03004     } else
03005       lineStart = retPos;
03006     nLines++;
03007     if (lineStart > pos + nDeleted && buf->char_at(lineStart-1) == '\n') {
03008       break;
03009     }
03010     
03011     /* Unlike in the findWrapRange() function above, we don't try to 
03012      resync with the line starts, because we don't know the length 
03013      of the inserted text yet, nor the updated style information. 
03014      
03015      Because of that, we also shouldn't resync with the line starts
03016      after the modification either, because we must perform the
03017      calculations for the deleted and inserted lines in the same way. 
03018      
03019      This can result in some unnecessary recalculation and redrawing
03020      overhead, and therefore we should only use this two-phase mode
03021      of calculation when it's really needed (continuous wrap + variable
03022      font width). */
03023   }
03024   mNLinesDeleted = nLines;
03025   mSuppressResync = 1;
03026 }
03027 
03028 
03029 
03055 void Fl_Text_Display::wrapped_line_counter(Fl_Text_Buffer *buf, int startPos,
03056                                            int maxPos, int maxLines, bool startPosIsLineStart, int styleBufOffset,
03057                                            int *retPos, int *retLines, int *retLineStart, int *retLineEnd,
03058                                            bool countLastLineMissingNewLine) const {
03059   IS_UTF8_ALIGNED2(buf, startPos)
03060   IS_UTF8_ALIGNED2(buf, maxPos)
03061 
03062   int lineStart, newLineStart = 0, b, p, colNum, wrapMarginPix;
03063   int i, foundBreak;
03064   double width;
03065   int nLines = 0;
03066   unsigned int c;
03067   
03068   /* Set the wrap margin to the wrap column or the view width */
03069   if (mWrapMarginPix != 0) {
03070     wrapMarginPix = mWrapMarginPix;
03071   } else {
03072     wrapMarginPix = text_area.w;
03073   }
03074   
03075   /* Find the start of the line if the start pos is not marked as a
03076    line start. */
03077   if (startPosIsLineStart)
03078     lineStart = startPos;
03079   else
03080     lineStart = line_start(startPos);
03081   
03082   /*
03083    ** Loop until position exceeds maxPos or line count exceeds maxLines.
03084    ** (actually, continues beyond maxPos to end of line containing maxPos,
03085    ** in case later characters cause a word wrap back before maxPos)
03086    */
03087   colNum = 0;
03088   width = 0;
03089   for (p=lineStart; p<buf->length(); p=buf->next_char(p)) {
03090     c = buf->char_at(p);  // UCS-4
03091     
03092     /* If the character was a newline, count the line and start over,
03093      otherwise, add it to the width and column counts */
03094     if (c == '\n') {
03095       if (p >= maxPos) {
03096         *retPos = maxPos;
03097         *retLines = nLines;
03098         *retLineStart = lineStart;
03099         *retLineEnd = maxPos;
03100         return;
03101       }
03102       nLines++;
03103       int p1 = buf->next_char(p);
03104       if (nLines >= maxLines) {
03105         *retPos = p1;
03106         *retLines = nLines;
03107         *retLineStart = p1;
03108         *retLineEnd = p;
03109         return;
03110       }
03111       lineStart = p1;
03112       colNum = 0;
03113       width = 0;
03114     } else {
03115       const char *s = buf->address(p);
03116       colNum++;
03117       // FIXME: it is not a good idea to simply add character widths because on
03118       // some platforms, the width is a floating point value and depends on the 
03119       // previous character as well.
03120       width += measure_proportional_character(s, (int)width, p+styleBufOffset);
03121     }
03122     
03123     /* If character exceeded wrap margin, find the break point and wrap there */
03124     if (width > wrapMarginPix) {
03125       foundBreak = false;
03126       for (b=p; b>=lineStart; b=buf->prev_char(b)) {
03127         c = buf->char_at(b);
03128         if (c == '\t' || c == ' ') {
03129           newLineStart = buf->next_char(b);
03130           colNum = 0;
03131           width = 0;
03132           int iMax = buf->next_char(p);
03133           for (i=buf->next_char(b); i<iMax; i = buf->next_char(i)) {
03134             width += measure_proportional_character(buf->address(i), (int)width, 
03135                                                     i+styleBufOffset);
03136             colNum++;
03137           }
03138           foundBreak = true;
03139           break;
03140         }
03141       }
03142       if (!foundBreak) { /* no whitespace, just break at margin */
03143         newLineStart = max(p, buf->next_char(lineStart));
03144         const char *s = buf->address(b);
03145         colNum++;
03146         width = measure_proportional_character(s, 0, p+styleBufOffset);
03147       }
03148       if (p >= maxPos) {
03149         *retPos = maxPos;
03150         *retLines = maxPos < newLineStart ? nLines : nLines + 1;
03151         *retLineStart = maxPos < newLineStart ? lineStart : newLineStart;
03152         *retLineEnd = maxPos;
03153         return;
03154       }
03155       nLines++;
03156       if (nLines >= maxLines) {
03157         *retPos = foundBreak ? buf->next_char(b) : max(p, buf->next_char(lineStart));
03158         *retLines = nLines;
03159         *retLineStart = lineStart;
03160         *retLineEnd = foundBreak ? b : p;
03161         return;
03162       }
03163       lineStart = newLineStart;
03164     }
03165   }
03166   
03167   /* reached end of buffer before reaching pos or line target */
03168   *retPos = buf->length();
03169   *retLines = nLines;
03170   if (countLastLineMissingNewLine && colNum > 0) 
03171     *retLines = buf->next_char(*retLines);
03172   *retLineStart = lineStart;
03173   *retLineEnd = buf->length();
03174 }
03175 
03176 
03177 
03199 double Fl_Text_Display::measure_proportional_character(const char *s, int xPix, int pos) const {
03200   IS_UTF8_ALIGNED(s)
03201   
03202   if (*s=='\t') {
03203     int tab = (int)col_to_x(8);
03204     return (((xPix/tab)+1)*tab) - xPix;
03205   }
03206   
03207   int charLen = fl_utf8len1(*s), style = 0;
03208   if (mStyleBuffer) {
03209     style = mStyleBuffer->byte_at(pos);
03210   }
03211   return string_width(s, charLen, style);
03212 }
03213 
03214 
03215 
03233 void Fl_Text_Display::find_line_end(int startPos, bool startPosIsLineStart,
03234                                     int *lineEnd, int *nextLineStart) const {
03235   IS_UTF8_ALIGNED2(buffer(), startPos)
03236 
03237   int retLines, retLineStart;
03238   
03239   /* if we're not wrapping use more efficient BufEndOfLine */
03240   if (!mContinuousWrap) {
03241     int le = buffer()->line_end(startPos);
03242     int ls = buffer()->next_char(le);
03243     *lineEnd = le;
03244     *nextLineStart = min(buffer()->length(), ls);
03245     return;
03246   }
03247   
03248   /* use the wrapped line counter routine to count forward one line */
03249   wrapped_line_counter(buffer(), startPos, buffer()->length(),
03250                        1, startPosIsLineStart, 0, nextLineStart, &retLines,
03251                        &retLineStart, lineEnd);
03252 }
03253 
03254 
03255 
03277 int Fl_Text_Display::wrap_uses_character(int lineEndPos) const {
03278   IS_UTF8_ALIGNED2(buffer(), lineEndPos)
03279 
03280   unsigned int c;
03281   
03282   if (!mContinuousWrap || lineEndPos == buffer()->length())
03283     return 1;
03284   
03285   c = buffer()->char_at(lineEndPos);
03286   return c == '\n' || ((c == '\t' || c == ' ') &&
03287                        lineEndPos + 1 < buffer()->length());
03288 }
03289 
03290 
03291 
03304 void Fl_Text_Display::extend_range_for_styles( int *startpos, int *endpos ) {
03305   IS_UTF8_ALIGNED2(buffer(), (*startpos))  
03306   IS_UTF8_ALIGNED2(buffer(), (*endpos))  
03307   
03308   Fl_Text_Selection * sel = mStyleBuffer->primary_selection();
03309   int extended = 0;
03310   
03311   /* The peculiar protocol used here is that modifications to the style
03312    buffer are marked by selecting them with the buffer's primary Fl_Text_Selection.
03313    The style buffer is usually modified in response to a modify callback on
03314    the text buffer BEFORE Fl_Text_Display.c's modify callback, so that it can keep
03315    the style buffer in step with the text buffer.  The style-update
03316    callback can't just call for a redraw, because Fl_Text_Display hasn't processed
03317    the original text changes yet.  Anyhow, to minimize redrawing and to
03318    avoid the complexity of scheduling redraws later, this simple protocol
03319    tells the text display's buffer modify callback to extend its redraw
03320    range to show the text color/and font changes as well. */
03321   if ( sel->selected() ) {
03322     if ( sel->start() < *startpos ) {
03323       *startpos = sel->start();
03324       // somewhere while deleting, alignment is lost. We do this just to be sure.
03325       *startpos = buffer()->utf8_align(*startpos);
03326       IS_UTF8_ALIGNED2(buffer(), (*startpos))  
03327       extended = 1;
03328     }
03329     if ( sel->end() > *endpos ) {
03330       *endpos = sel->end();
03331       *endpos = buffer()->utf8_align(*endpos);
03332       IS_UTF8_ALIGNED2(buffer(), (*endpos))  
03333       extended = 1;
03334     }
03335   }
03336   
03337   /* If the Fl_Text_Selection was extended due to a style change, and some of the
03338    fonts don't match in spacing, extend redraw area to end of line to
03339    redraw characters exposed by possible font size changes */
03340   if ( extended )
03341     *endpos = mBuffer->line_end( *endpos ) + 1;
03342   
03343   IS_UTF8_ALIGNED2(buffer(), (*endpos))
03344 }
03345 
03346 
03347 
03353 void Fl_Text_Display::draw(void) {
03354   // don't even try if there is no associated text buffer!
03355   if (!buffer()) { draw_box(); return; }
03356   
03357   fl_push_clip(x(),y(),w(),h());        // prevent drawing outside widget area
03358   
03359   // draw the non-text, non-scrollbar areas.
03360   if (damage() & FL_DAMAGE_ALL) {
03361     //    printf("drawing all (box = %d)\n", box());
03362     // draw the box()
03363     int W = w(), H = h();
03364     draw_box(box(), x(), y(), W, H, color());
03365     
03366     if (mHScrollBar->visible())
03367       W -= scrollbar_width();
03368     if (mVScrollBar->visible())
03369       H -= scrollbar_width();
03370     
03371     // left margin
03372     fl_rectf(text_area.x-LEFT_MARGIN, text_area.y-TOP_MARGIN,
03373              LEFT_MARGIN, text_area.h+TOP_MARGIN+BOTTOM_MARGIN,
03374              color());
03375     
03376     // right margin
03377     fl_rectf(text_area.x+text_area.w, text_area.y-TOP_MARGIN,
03378              RIGHT_MARGIN, text_area.h+TOP_MARGIN+BOTTOM_MARGIN,
03379              color());
03380     
03381     // top margin
03382     fl_rectf(text_area.x, text_area.y-TOP_MARGIN,
03383              text_area.w, TOP_MARGIN, color());
03384     
03385     // bottom margin
03386     fl_rectf(text_area.x, text_area.y+text_area.h,
03387              text_area.w, BOTTOM_MARGIN, color());
03388     
03389     // draw that little box in the corner of the scrollbars
03390     if (mVScrollBar->visible() && mHScrollBar->visible())
03391       fl_rectf(mVScrollBar->x(), mHScrollBar->y(),
03392                mVScrollBar->w(), mHScrollBar->h(),
03393                FL_GRAY);
03394     
03395     // blank the previous cursor protrusions
03396   }
03397   else if (damage() & (FL_DAMAGE_SCROLL | FL_DAMAGE_EXPOSE)) {
03398     //    printf("blanking previous cursor extrusions at Y: %d\n", mCursorOldY);
03399     // CET - FIXME - save old cursor position instead and just draw side needed?
03400     fl_push_clip(text_area.x-LEFT_MARGIN,
03401                  text_area.y,
03402                  text_area.w+LEFT_MARGIN+RIGHT_MARGIN,
03403                  text_area.h);
03404     fl_rectf(text_area.x-LEFT_MARGIN, mCursorOldY,
03405              LEFT_MARGIN, mMaxsize, color());
03406     fl_rectf(text_area.x+text_area.w, mCursorOldY,
03407              RIGHT_MARGIN, mMaxsize, color());
03408     fl_pop_clip();
03409   }
03410   
03411   // draw the scrollbars
03412   if (damage() & (FL_DAMAGE_ALL | FL_DAMAGE_CHILD)) {
03413     mVScrollBar->damage(FL_DAMAGE_ALL);
03414     mHScrollBar->damage(FL_DAMAGE_ALL);
03415   }
03416   update_child(*mVScrollBar);
03417   update_child(*mHScrollBar);
03418   
03419   // draw all of the text
03420   if (damage() & (FL_DAMAGE_ALL | FL_DAMAGE_EXPOSE)) {
03421     //printf("drawing all text\n");
03422     int X, Y, W, H;
03423     if (fl_clip_box(text_area.x, text_area.y, text_area.w, text_area.h,
03424                     X, Y, W, H)) {
03425       // Draw text using the intersected clipping box...
03426       // (this sets the clipping internally)
03427       draw_text(X, Y, W, H);
03428     } else {
03429       // Draw the whole area...
03430       draw_text(text_area.x, text_area.y, text_area.w, text_area.h);
03431     }
03432   }
03433   else if (damage() & FL_DAMAGE_SCROLL) {
03434     // draw some lines of text
03435     fl_push_clip(text_area.x, text_area.y,
03436                  text_area.w, text_area.h);
03437     //printf("drawing text from %d to %d\n", damage_range1_start, damage_range1_end);
03438     draw_range(damage_range1_start, damage_range1_end);
03439     if (damage_range2_end != -1) {
03440       //printf("drawing text from %d to %d\n", damage_range2_start, damage_range2_end);
03441       draw_range(damage_range2_start, damage_range2_end);
03442     }
03443     damage_range1_start = damage_range1_end = -1;
03444     damage_range2_start = damage_range2_end = -1;
03445     fl_pop_clip();
03446   }
03447   
03448   // draw the text cursor
03449   if (damage() & (FL_DAMAGE_ALL | FL_DAMAGE_SCROLL | FL_DAMAGE_EXPOSE)
03450       && !buffer()->primary_selection()->selected() &&
03451       mCursorOn && Fl::focus() == this ) {
03452     fl_push_clip(text_area.x-LEFT_MARGIN,
03453                  text_area.y,
03454                  text_area.w+LEFT_MARGIN+RIGHT_MARGIN,
03455                  text_area.h);
03456     
03457     int X, Y;
03458     if (position_to_xy(mCursorPos, &X, &Y)) draw_cursor(X, Y);
03459     //    else puts("position_to_xy() failed - unable to draw cursor!");
03460     //printf("drew cursor at pos: %d (%d,%d)\n", mCursorPos, X, Y);
03461     mCursorOldY = Y;
03462     fl_pop_clip();
03463   }
03464   fl_pop_clip();
03465 }
03466 
03467 
03468 
03469 // this processes drag events due to mouse for Fl_Text_Display and
03470 // also drags due to cursor movement with shift held down for
03471 // Fl_Text_Editor
03472 void fl_text_drag_me(int pos, Fl_Text_Display* d) {
03473   if (d->dragType == Fl_Text_Display::DRAG_CHAR) {
03474     if (pos >= d->dragPos) {
03475       d->buffer()->select(d->dragPos, pos);
03476     } else {
03477       d->buffer()->select(pos, d->dragPos);
03478     }
03479     d->insert_position(pos);
03480   } else if (d->dragType == Fl_Text_Display::DRAG_WORD) {
03481     if (pos >= d->dragPos) {
03482       d->insert_position(d->word_end(pos));
03483       d->buffer()->select(d->word_start(d->dragPos), d->word_end(pos));
03484     } else {
03485       d->insert_position(d->word_start(pos));
03486       d->buffer()->select(d->word_start(pos), d->word_end(d->dragPos));
03487     }
03488   } else if (d->dragType == Fl_Text_Display::DRAG_LINE) {
03489     if (pos >= d->dragPos) {
03490       d->insert_position(d->buffer()->line_end(pos)+1);
03491       d->buffer()->select(d->buffer()->line_start(d->dragPos),
03492                           d->buffer()->line_end(pos)+1);
03493     } else {
03494       d->insert_position(d->buffer()->line_start(pos));
03495       d->buffer()->select(d->buffer()->line_start(pos),
03496                           d->buffer()->line_end(d->dragPos)+1);
03497     }
03498   }
03499 }
03500 
03501 
03502 
03510 void Fl_Text_Display::scroll_timer_cb(void *user_data) {
03511   Fl_Text_Display *w = (Fl_Text_Display*)user_data;
03512   int pos;
03513   switch (scroll_direction) {
03514     case 1: // mouse is to the right, scroll left
03515       w->scroll(w->mTopLineNum, w->mHorizOffset + scroll_amount);
03516       pos = w->xy_to_position(w->text_area.x + w->text_area.w, scroll_y, CURSOR_POS);
03517       break;
03518     case 2: // mouse is to the left, scroll right
03519       w->scroll(w->mTopLineNum, w->mHorizOffset + scroll_amount);
03520       pos = w->xy_to_position(w->text_area.x, scroll_y, CURSOR_POS);
03521       break;
03522     case 3: // mouse is above, scroll down
03523       w->scroll(w->mTopLineNum + scroll_amount, w->mHorizOffset);
03524       pos = w->xy_to_position(scroll_x, w->text_area.y, CURSOR_POS);
03525       break;
03526     case 4: // mouse is below, scroll up
03527       w->scroll(w->mTopLineNum + scroll_amount, w->mHorizOffset);
03528       pos = w->xy_to_position(scroll_x, w->text_area.y + w->text_area.h, CURSOR_POS);
03529       break;
03530     default:
03531       return;
03532   }
03533   fl_text_drag_me(pos, w);
03534   Fl::repeat_timeout(.1, scroll_timer_cb, user_data);
03535 }
03536 
03537 
03538 
03542 int Fl_Text_Display::handle(int event) {
03543   if (!buffer()) return 0;
03544   // This isn't very elegant!
03545   if (!Fl::event_inside(text_area.x, text_area.y, text_area.w, text_area.h) &&
03546       !dragging && event != FL_LEAVE && event != FL_ENTER &&
03547       event != FL_MOVE && event != FL_FOCUS && event != FL_UNFOCUS &&
03548       event != FL_KEYBOARD && event != FL_KEYUP) {
03549     return Fl_Group::handle(event);
03550   }
03551   
03552   switch (event) {
03553     case FL_ENTER:
03554     case FL_MOVE:
03555       if (active_r()) {
03556         if (Fl::event_inside(text_area.x, text_area.y, text_area.w,
03557                              text_area.h)) window()->cursor(FL_CURSOR_INSERT);
03558         else window()->cursor(FL_CURSOR_DEFAULT);
03559         return 1;
03560       } else {
03561         return 0;
03562       }
03563       
03564     case FL_LEAVE:
03565     case FL_HIDE:
03566       if (active_r() && window()) {
03567         window()->cursor(FL_CURSOR_DEFAULT);
03568         
03569         return 1;
03570       } else {
03571         return 0;
03572       }
03573       
03574     case FL_PUSH: {
03575       if (active_r() && window()) {
03576         if (Fl::event_inside(text_area.x, text_area.y, text_area.w,
03577                              text_area.h)) window()->cursor(FL_CURSOR_INSERT);
03578         else window()->cursor(FL_CURSOR_DEFAULT);
03579       }
03580       
03581       if (Fl::focus() != this) {
03582         Fl::focus(this);
03583         handle(FL_FOCUS);
03584       }
03585       if (Fl_Group::handle(event)) return 1;
03586       if (Fl::event_state()&FL_SHIFT) return handle(FL_DRAG);
03587       dragging = 1;
03588       int pos = xy_to_position(Fl::event_x(), Fl::event_y(), CURSOR_POS);
03589       dragPos = pos;
03590       if (buffer()->primary_selection()->includes(pos)) {
03591         dragType = DRAG_START_DND;
03592         return 1;
03593       }
03594       dragType = Fl::event_clicks();
03595       if (dragType == DRAG_CHAR) {
03596         buffer()->unselect();
03597         Fl::copy("", 0, 0);
03598       }
03599       else if (dragType == DRAG_WORD) {
03600         buffer()->select(word_start(pos), word_end(pos));
03601         dragPos = word_start(pos);
03602         }
03603       
03604       if (buffer()->primary_selection()->selected())
03605         insert_position(buffer()->primary_selection()->end());
03606       else
03607         insert_position(pos);
03608       show_insert_position();
03609       return 1;
03610     }
03611       
03612     case FL_DRAG: {
03613       if (dragType==DRAG_NONE)
03614         return 1;
03615       if (dragType==DRAG_START_DND) {
03616         if (!Fl::event_is_click() && Fl::dnd_text_ops()) {
03617           const char* copy = buffer()->selection_text();
03618           Fl::dnd();
03619           free((void*)copy);
03620         }
03621         return 1;
03622       }
03623       int X = Fl::event_x(), Y = Fl::event_y(), pos = insert_position();
03624       // if we leave the text_area, we start a timer event
03625       // that will take care of scrolling and selecting
03626       if (Y < text_area.y) {
03627         scroll_x = X;
03628         scroll_amount = (Y - text_area.y) / 5 - 1;
03629         if (!scroll_direction) {
03630           Fl::add_timeout(.01, scroll_timer_cb, this);
03631         }
03632         scroll_direction = 3;
03633       } else if (Y >= text_area.y+text_area.h) {
03634         scroll_x = X;
03635         scroll_amount = (Y - text_area.y - text_area.h) / 5 + 1;
03636         if (!scroll_direction) {
03637           Fl::add_timeout(.01, scroll_timer_cb, this);
03638         }
03639         scroll_direction = 4;
03640       } else if (X < text_area.x) {
03641         scroll_y = Y;
03642         scroll_amount = (X - text_area.x) / 2 - 1;
03643         if (!scroll_direction) {
03644           Fl::add_timeout(.01, scroll_timer_cb, this);
03645         }
03646         scroll_direction = 2;
03647       } else if (X >= text_area.x+text_area.w) {
03648         scroll_y = Y;
03649         scroll_amount = (X - text_area.x - text_area.w) / 2 + 1;
03650         if (!scroll_direction) {
03651           Fl::add_timeout(.01, scroll_timer_cb, this);
03652         }
03653         scroll_direction = 1;
03654       } else {
03655         if (scroll_direction) {
03656           Fl::remove_timeout(scroll_timer_cb, this);
03657           scroll_direction = 0;
03658         }
03659         pos = xy_to_position(X, Y, CURSOR_POS);
03660         pos = buffer()->next_char(pos);
03661       }
03662       fl_text_drag_me(pos, this);
03663       return 1;
03664     }
03665       
03666     case FL_RELEASE: {
03667       if (Fl::event_is_click() && (! Fl::event_clicks()) && 
03668           buffer()->primary_selection()->includes(dragPos) && !(Fl::event_state()&FL_SHIFT) ) {
03669         buffer()->unselect(); // clicking in the selection: unselect and move cursor
03670         insert_position(dragPos);
03671         return 1;
03672       } else if (Fl::event_clicks() == DRAG_LINE && Fl::event_button() == FL_LEFT_MOUSE) {
03673         buffer()->select(buffer()->line_start(dragPos), buffer()->next_char(buffer()->line_end(dragPos)));
03674         dragPos = line_start(dragPos);
03675         dragType = DRAG_CHAR;
03676       } else {
03677         dragging = 0;
03678         if (scroll_direction) {
03679           Fl::remove_timeout(scroll_timer_cb, this);
03680           scroll_direction = 0;
03681         }
03682         
03683         // convert from WORD or LINE selection to CHAR
03684         /*if (insert_position() >= dragPos)
03685           dragPos = buffer()->primary_selection()->start();
03686         else
03687           dragPos = buffer()->primary_selection()->end();*/
03688         dragType = DRAG_CHAR;
03689       }
03690       
03691       const char* copy = buffer()->selection_text();
03692       if (*copy) Fl::copy(copy, strlen(copy), 0);
03693       free((void*)copy);
03694       return 1;
03695     }
03696       
03697     case FL_MOUSEWHEEL:
03698       if (Fl::event_dy()) return mVScrollBar->handle(event);
03699       else return mHScrollBar->handle(event);
03700       
03701     case FL_UNFOCUS:
03702       if (active_r() && window()) window()->cursor(FL_CURSOR_DEFAULT);
03703     case FL_FOCUS:
03704       if (buffer()->selected()) {
03705         int start, end;
03706         if (buffer()->selection_position(&start, &end))
03707           redisplay_range(start, end);
03708       }
03709       if (buffer()->secondary_selected()) {
03710         int start, end;
03711         if (buffer()->secondary_selection_position(&start, &end))
03712           redisplay_range(start, end);
03713       }
03714       if (buffer()->highlight()) {
03715         int start, end;
03716         if (buffer()->highlight_position(&start, &end))
03717           redisplay_range(start, end);
03718       }
03719       return 1;
03720       
03721     case FL_KEYBOARD:
03722       // Copy?
03723       if ((Fl::event_state()&(FL_CTRL|FL_COMMAND)) && Fl::event_key()=='c') {
03724         if (!buffer()->selected()) return 1;
03725         const char *copy = buffer()->selection_text();
03726         if (*copy) Fl::copy(copy, strlen(copy), 1);
03727         free((void*)copy);
03728         return 1;
03729       }
03730       
03731       // Select all ?
03732       if ((Fl::event_state()&(FL_CTRL|FL_COMMAND)) && Fl::event_key()=='a') {
03733         buffer()->select(0,buffer()->length());
03734         const char *copy = buffer()->selection_text();
03735         if (*copy) Fl::copy(copy, strlen(copy), 0);
03736         free((void*)copy);
03737         return 1;
03738       }
03739       
03740       if (mVScrollBar->handle(event)) return 1;
03741       if (mHScrollBar->handle(event)) return 1;
03742       
03743       break;
03744       
03745     case FL_SHORTCUT:
03746       if (!(shortcut() ? Fl::test_shortcut(shortcut()) : test_shortcut()))
03747         return 0;
03748       if (Fl::visible_focus() && handle(FL_FOCUS)) {
03749         Fl::focus(this);
03750         return 1;
03751       }
03752       break;
03753       
03754   }
03755   
03756   return 0;
03757 }
03758 
03759 
03760 /*
03761  Convert an x pixel position into a column number.
03762  */
03763 double Fl_Text_Display::x_to_col(double y) const
03764 {
03765   if (!mColumnScale) {
03766     mColumnScale = string_width("Mitg", 4, 'A') / 4.0;
03767   }
03768   return (y/mColumnScale)+0.5;
03769 }
03770 
03771 
03775 double Fl_Text_Display::col_to_x(double col) const
03776 {
03777   if (!mColumnScale) {
03778     // recalculate column scale value
03779     x_to_col(0); 
03780   }
03781   return col*mColumnScale;
03782 }
03783 
03784 
03785 
03786 
03787 //
03788 // End of "$Id: Fl_Text_Display.cxx 8078 2010-12-20 17:32:36Z AlbrechtS $".
03789 //