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_Buffer.cxx

Go to the documentation of this file.
00001 //
00002 // "$Id: Fl_Text_Buffer.cxx 8040 2010-12-15 17:38:39Z manolo $"
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 #include <stdio.h>
00029 #include <stdlib.h>
00030 #include <FL/fl_utf8.h>
00031 #include "flstring.h"
00032 #include <ctype.h>
00033 #include <FL/Fl.H>
00034 #include <FL/Fl_Text_Buffer.H>
00035 #include <FL/fl_ask.H>
00036 
00037 
00038 /*
00039  This file is based on a port of NEdit to FLTK many years ago. NEdit at that
00040  point was already stretched beyond the task it was designed for which explains
00041  why the source code is sometimes pretty convoluted. It still is a very useful
00042  widget for FLTK, and we are thankful that the nedit team allowed us to 
00043  integrate their code.
00044 
00045  With the introduction of Unicode and UTF-8, Fl_Text_... has to go into a whole
00046  new generation of code. Originally designed for monospaced fonts only, many
00047  features make less sense in the multibyte and multiwidth world of UTF-8.
00048 
00049  Columns are a good example. There is simply no such thing. The new Fl_Text_...
00050  widget converts columns to pixels by multiplying them with the average 
00051  character width for a given font.
00052 
00053  Rectangular selections were rarely used (if at all) and make little sense when
00054  using variable width fonts. They have been removed.
00055 
00056  Using multiple spaces to emulate tab stops has been replaced by pixel counting
00057  routines. They are slower, but give the expected result for proportional fonts.
00058 
00059  And constantly recalculating character widths is just much too expensive. Lines
00060  of text are now subdivided into blocks of text which are measured at once 
00061  instead of individual characters. 
00062  */
00063 
00064 
00065 #ifndef min
00066 
00067 static int max(int i1, int i2)
00068 {
00069   return i1 >= i2 ? i1 : i2;
00070 }
00071 
00072 static int min(int i1, int i2)
00073 {
00074   return i1 <= i2 ? i1 : i2;
00075 }
00076 
00077 #endif
00078 
00079 
00080 static char *undobuffer;
00081 static int undobufferlength;
00082 static Fl_Text_Buffer *undowidget;
00083 static int undoat;              // points after insertion
00084 static int undocut;             // number of characters deleted there
00085 static int undoinsert;          // number of characters inserted
00086 static int undoyankcut;         // length of valid contents of buffer, even if undocut=0
00087 
00088 /*
00089  Resize the undo buffer to match at least the requested size.
00090  */
00091 static void undobuffersize(int n)
00092 {
00093   if (n > undobufferlength) {
00094     if (undobuffer) {
00095       do {
00096         undobufferlength *= 2;
00097       } while (undobufferlength < n);
00098       undobuffer = (char *) realloc(undobuffer, undobufferlength);
00099     } else {
00100       undobufferlength = n + 9;
00101       undobuffer = (char *) malloc(undobufferlength);
00102     }
00103   }
00104 }
00105 
00106 static void def_transcoding_warning_action(Fl_Text_Buffer *text)
00107 {
00108   fl_alert("%s", text->file_encoding_warning_message);
00109 }
00110 
00111 /*
00112  Initialize all variables.
00113  */
00114 Fl_Text_Buffer::Fl_Text_Buffer(int requestedSize, int preferredGapSize)
00115 {
00116   mLength = 0;
00117   mPreferredGapSize = preferredGapSize;
00118   mBuf = (char *) malloc(requestedSize + mPreferredGapSize);
00119   mGapStart = 0;
00120   mGapEnd = mPreferredGapSize;
00121   mTabDist = 8;
00122   mPrimary.mSelected = 0;
00123   mPrimary.mStart = mPrimary.mEnd = 0;
00124   mSecondary.mSelected = 0;
00125   mSecondary.mStart = mSecondary.mEnd = 0;
00126   mHighlight.mSelected = 0;
00127   mHighlight.mStart = mHighlight.mEnd = 0;
00128   mModifyProcs = NULL;
00129   mCbArgs = NULL;
00130   mNModifyProcs = 0;
00131   mNPredeleteProcs = 0;
00132   mPredeleteProcs = NULL;
00133   mPredeleteCbArgs = NULL;
00134   mCursorPosHint = 0;
00135   mCanUndo = 1;
00136   input_file_was_transcoded = 0;
00137   transcoding_warning_action = def_transcoding_warning_action;
00138 }
00139 
00140 
00141 /*
00142  Free all resources.
00143  */
00144 Fl_Text_Buffer::~Fl_Text_Buffer()
00145 {
00146   free(mBuf);
00147   if (mNModifyProcs != 0) {
00148     delete[]mModifyProcs;
00149     delete[]mCbArgs;
00150   }
00151   if (mNPredeleteProcs != 0) {
00152     delete[]mPredeleteProcs;
00153     delete[]mPredeleteCbArgs;
00154   }
00155 }
00156 
00157 
00158 /*
00159  This function copies verbose whatever is in front and after the gap into a
00160  single buffer.
00161  */
00162 char *Fl_Text_Buffer::text() const {
00163   char *t = (char *) malloc(mLength + 1);
00164   memcpy(t, mBuf, mGapStart);
00165   memcpy(t+mGapStart, mBuf+mGapEnd, mLength - mGapStart);
00166   t[mLength] = '\0';
00167   return t;
00168 } 
00169 
00170 
00171 /*
00172  Set the text buffer to a new string.
00173  */
00174 void Fl_Text_Buffer::text(const char *t)
00175 {
00176   IS_UTF8_ALIGNED(t)
00177   
00178   call_predelete_callbacks(0, length());
00179   
00180   /* Save information for redisplay, and get rid of the old buffer */
00181   const char *deletedText = text();
00182   int deletedLength = mLength;
00183   free((void *) mBuf);
00184   
00185   /* Start a new buffer with a gap of mPreferredGapSize at the end */
00186   int insertedLength = strlen(t);
00187   mBuf = (char *) malloc(insertedLength + mPreferredGapSize);
00188   mLength = insertedLength;
00189   mGapStart = insertedLength;
00190   mGapEnd = mGapStart + mPreferredGapSize;
00191   memcpy(mBuf, t, insertedLength);
00192   
00193   /* Zero all of the existing selections */
00194   update_selections(0, deletedLength, 0);
00195   
00196   /* Call the saved display routine(s) to update the screen */
00197   call_modify_callbacks(0, deletedLength, insertedLength, 0, deletedText);
00198   free((void *) deletedText);
00199 }
00200 
00201 
00202 /*
00203  Creates a range of text to a new buffer and copies verbose from around the gap.
00204  */
00205 char *Fl_Text_Buffer::text_range(int start, int end) const {
00206   IS_UTF8_ALIGNED2(this, (start))
00207   IS_UTF8_ALIGNED2(this, (end))
00208   
00209   char *s = NULL;
00210   
00211   /* Make sure start and end are ok, and allocate memory for returned string.
00212    If start is bad, return "", if end is bad, adjust it. */
00213   if (start < 0 || start > mLength)
00214   {
00215     s = (char *) malloc(1);
00216     s[0] = '\0';
00217     return s;
00218   }
00219   if (end < start) {
00220     int temp = start;
00221     start = end;
00222     end = temp;
00223   }
00224   if (end > mLength)
00225     end = mLength;
00226   int copiedLength = end - start;
00227   s = (char *) malloc(copiedLength + 1);
00228   
00229   /* Copy the text from the buffer to the returned string */
00230   if (end <= mGapStart) {
00231     memcpy(s, mBuf + start, copiedLength);
00232   } else if (start >= mGapStart) {
00233     memcpy(s, mBuf + start + (mGapEnd - mGapStart), copiedLength);
00234   } else {
00235     int part1Length = mGapStart - start;
00236     memcpy(s, mBuf + start, part1Length);
00237     memcpy(s + part1Length, mBuf + mGapEnd, copiedLength - part1Length);
00238   }
00239   s[copiedLength] = '\0';
00240   return s;
00241 }
00242 
00243 /*
00244  Return a UCS-4 character at the given index.
00245  Pos must be at a character boundary.
00246  */
00247 unsigned int Fl_Text_Buffer::char_at(int pos) const {  
00248   if (pos < 0 || pos >= mLength)
00249     return '\0';
00250   
00251   IS_UTF8_ALIGNED2(this, (pos))
00252   
00253   const char *src = address(pos);
00254   return fl_utf8decode(src, 0, 0);
00255 } 
00256 
00257 
00258 /*
00259  Return the raw byte at the given index.
00260  This function ignores all unicode encoding.
00261  */
00262 char Fl_Text_Buffer::byte_at(int pos) const {
00263   if (pos < 0 || pos >= mLength)
00264     return '\0';
00265   const char *src = address(pos);
00266   return *src;
00267 } 
00268 
00269 
00270 /*
00271  Insert some text at the given index.
00272  Pos must be at a character boundary.
00273 */
00274 void Fl_Text_Buffer::insert(int pos, const char *text)
00275 {
00276   IS_UTF8_ALIGNED2(this, (pos))
00277   IS_UTF8_ALIGNED(text)
00278   
00279   /* check if there is actually any text */
00280   if (!text || !*text)
00281     return;
00282   
00283   /* if pos is not contiguous to existing text, make it */
00284   if (pos > mLength)
00285     pos = mLength;
00286   if (pos < 0)
00287     pos = 0;
00288   
00289   /* Even if nothing is deleted, we must call these callbacks */
00290   call_predelete_callbacks(pos, 0);
00291   
00292   /* insert and redisplay */
00293   int nInserted = insert_(pos, text);
00294   mCursorPosHint = pos + nInserted;
00295   IS_UTF8_ALIGNED2(this, (mCursorPosHint))
00296   call_modify_callbacks(pos, 0, nInserted, 0, NULL);
00297 }
00298 
00299 
00300 /*
00301  Replace a range of text with new text.
00302  Start and end must be at a character boundary.
00303 */
00304 void Fl_Text_Buffer::replace(int start, int end, const char *text)
00305 {
00306   // Range check...
00307   if (!text)
00308     return;
00309   if (start < 0)
00310     start = 0;
00311   if (end > mLength)
00312     end = mLength;
00313 
00314   IS_UTF8_ALIGNED2(this, (start))
00315   IS_UTF8_ALIGNED2(this, (end))
00316   IS_UTF8_ALIGNED(text)
00317   
00318   call_predelete_callbacks(start, end - start);
00319   const char *deletedText = text_range(start, end);
00320   remove_(start, end);
00321   int nInserted = insert_(start, text);
00322   mCursorPosHint = start + nInserted;
00323   call_modify_callbacks(start, end - start, nInserted, 0, deletedText);
00324   free((void *) deletedText);
00325 }
00326 
00327 
00328 /*
00329  Remove a range of text.
00330  Start and End must be at a character boundary.
00331 */
00332 void Fl_Text_Buffer::remove(int start, int end)
00333 {
00334   /* Make sure the arguments make sense */
00335   if (start > end) {
00336     int temp = start;
00337     start = end;
00338     end = temp;
00339   }
00340   if (start > mLength)
00341     start = mLength;
00342   if (start < 0)
00343     start = 0;
00344   if (end > mLength)
00345     end = mLength;
00346   if (end < 0)
00347     end = 0;
00348 
00349   IS_UTF8_ALIGNED2(this, (start))
00350   IS_UTF8_ALIGNED2(this, (end))  
00351   
00352   if (start == end)
00353     return;
00354   
00355   call_predelete_callbacks(start, end - start);
00356   /* Remove and redisplay */
00357   const char *deletedText = text_range(start, end);
00358   remove_(start, end);
00359   mCursorPosHint = start;
00360   call_modify_callbacks(start, end - start, 0, 0, deletedText);
00361   free((void *) deletedText);
00362 }
00363 
00364 
00365 /*
00366  Copy a range of text from another text buffer.
00367  fromStart, fromEnd, and toPos must be at a character boundary.
00368  */
00369 void Fl_Text_Buffer::copy(Fl_Text_Buffer * fromBuf, int fromStart,
00370                           int fromEnd, int toPos)
00371 {
00372   IS_UTF8_ALIGNED2(fromBuf, fromStart)
00373   IS_UTF8_ALIGNED2(fromBuf, fromEnd)
00374   IS_UTF8_ALIGNED2(this, (toPos))
00375   
00376   int copiedLength = fromEnd - fromStart;
00377   
00378   /* Prepare the buffer to receive the new text.  If the new text fits in
00379    the current buffer, just move the gap (if necessary) to where
00380    the text should be inserted.  If the new text is too large, reallocate
00381    the buffer with a gap large enough to accomodate the new text and a
00382    gap of mPreferredGapSize */
00383   if (copiedLength > mGapEnd - mGapStart)
00384     reallocate_with_gap(toPos, copiedLength + mPreferredGapSize);
00385   else if (toPos != mGapStart)
00386     move_gap(toPos);
00387   
00388   /* Insert the new text (toPos now corresponds to the start of the gap) */
00389   if (fromEnd <= fromBuf->mGapStart) {
00390     memcpy(&mBuf[toPos], &fromBuf->mBuf[fromStart], copiedLength);
00391   } else if (fromStart >= fromBuf->mGapStart) {
00392     memcpy(&mBuf[toPos],
00393            &fromBuf->mBuf[fromStart + (fromBuf->mGapEnd - fromBuf->mGapStart)],
00394            copiedLength);
00395   } else {
00396     int part1Length = fromBuf->mGapStart - fromStart;
00397     memcpy(&mBuf[toPos], &fromBuf->mBuf[fromStart], part1Length);
00398     memcpy(&mBuf[toPos + part1Length],
00399            &fromBuf->mBuf[fromBuf->mGapEnd], copiedLength - part1Length);
00400   }
00401   mGapStart += copiedLength;
00402   mLength += copiedLength;
00403   update_selections(toPos, 0, copiedLength);
00404 }
00405 
00406 
00407 /*
00408  Take the previous changes and undo them. Return the previous
00409  cursor position in cursorPos. Returns 1 if the undo was applied.
00410  CursorPos will be at a character boundary.
00411  */ 
00412 int Fl_Text_Buffer::undo(int *cursorPos)
00413 {
00414   if (undowidget != this || (!undocut && !undoinsert && !mCanUndo))
00415     return 0;
00416   
00417   int ilen = undocut;
00418   int xlen = undoinsert;
00419   int b = undoat - xlen;
00420   
00421   if (xlen && undoyankcut && !ilen) {
00422     ilen = undoyankcut;
00423   }
00424   
00425   if (xlen && ilen) {
00426     undobuffersize(ilen + 1);
00427     undobuffer[ilen] = 0;
00428     char *tmp = strdup(undobuffer);
00429     replace(b, undoat, tmp);
00430     if (cursorPos)
00431       *cursorPos = mCursorPosHint;
00432     free(tmp);
00433   } else if (xlen) {
00434     remove(b, undoat);
00435     if (cursorPos)
00436       *cursorPos = mCursorPosHint;
00437   } else if (ilen) {
00438     undobuffersize(ilen + 1);
00439     undobuffer[ilen] = 0;
00440     insert(undoat, undobuffer);
00441     if (cursorPos)
00442       *cursorPos = mCursorPosHint;
00443     undoyankcut = 0;
00444   }
00445   
00446   return 1;
00447 }
00448 
00449 
00450 /*
00451  Set a flag if undo function will work.
00452  */
00453 void Fl_Text_Buffer::canUndo(char flag)
00454 {
00455   mCanUndo = flag;
00456   // disabling undo also clears the last undo operation!
00457   if (!mCanUndo && undowidget==this) 
00458     undowidget = 0;
00459 }
00460 
00461 
00462 /*
00463  Change the tab width. This will cause a couple of callbacks and a complete 
00464  redisplay. 
00465  Matt: I am not entirely sure why we need to trigger callbacks because
00466  tabs are only a graphical hint, not changing any text at all, but I leave
00467  this in here for back compatibility. 
00468  */
00469 void Fl_Text_Buffer::tab_distance(int tabDist)
00470 {
00471   /* First call the pre-delete callbacks with the previous tab setting 
00472    still active. */
00473   call_predelete_callbacks(0, mLength);
00474   
00475   /* Change the tab setting */
00476   mTabDist = tabDist;
00477   
00478   /* Force any display routines to redisplay everything (unfortunately,
00479    this means copying the whole buffer contents to provide "deletedText" */
00480   const char *deletedText = text();
00481   call_modify_callbacks(0, mLength, mLength, 0, deletedText);
00482   free((void *) deletedText);
00483 }
00484 
00485 
00486 /*
00487  Select a range of text.
00488  Start and End must be at a character boundary.
00489  */
00490 void Fl_Text_Buffer::select(int start, int end)
00491 {
00492   IS_UTF8_ALIGNED2(this, (start))
00493   IS_UTF8_ALIGNED2(this, (end))  
00494   
00495   Fl_Text_Selection oldSelection = mPrimary;
00496   
00497   mPrimary.set(start, end);
00498   redisplay_selection(&oldSelection, &mPrimary);
00499 }
00500 
00501 
00502 /*
00503  Clear the primary selection.
00504  */
00505 void Fl_Text_Buffer::unselect()
00506 {
00507   Fl_Text_Selection oldSelection = mPrimary;
00508   
00509   mPrimary.mSelected = 0;
00510   redisplay_selection(&oldSelection, &mPrimary);
00511 }
00512 
00513   
00514 /*
00515  Return the primary selection range.
00516  */
00517 int Fl_Text_Buffer::selection_position(int *start, int *end)
00518 {
00519   return mPrimary.position(start, end);
00520 }
00521 
00522 
00523 /*
00524  Return a copy of the selected text.
00525  */
00526 char *Fl_Text_Buffer::selection_text()
00527 {
00528   return selection_text_(&mPrimary);
00529 }
00530 
00531 
00532 /*
00533  Remove the selected text.
00534  */
00535 void Fl_Text_Buffer::remove_selection()
00536 {
00537   remove_selection_(&mPrimary);
00538 }
00539 
00540 
00541 /*
00542  Replace the selected text.
00543  */
00544 void Fl_Text_Buffer::replace_selection(const char *text)
00545 {
00546   replace_selection_(&mPrimary, text);
00547 }
00548 
00549 
00550 /*
00551  Select text.
00552  Start and End must be at a character boundary.
00553  */
00554 void Fl_Text_Buffer::secondary_select(int start, int end)
00555 {
00556   Fl_Text_Selection oldSelection = mSecondary;
00557   
00558   mSecondary.set(start, end);
00559   redisplay_selection(&oldSelection, &mSecondary);
00560 }
00561 
00562 
00563 /*
00564  Deselect text.
00565  */
00566 void Fl_Text_Buffer::secondary_unselect()
00567 {
00568   Fl_Text_Selection oldSelection = mSecondary;
00569   
00570   mSecondary.mSelected = 0;
00571   redisplay_selection(&oldSelection, &mSecondary);
00572 }
00573 
00574   
00575 /*
00576  Return the selected range.
00577  */
00578 int Fl_Text_Buffer::secondary_selection_position(int *start, int *end)
00579 {
00580   return mSecondary.position(start, end);
00581 }
00582 
00583 
00584 /*
00585  Return a copy of the text in this selection.
00586  */
00587 char *Fl_Text_Buffer::secondary_selection_text()
00588 {
00589   return selection_text_(&mSecondary);
00590 }
00591 
00592 
00593 /*
00594  Remove the selected text.
00595  */
00596 void Fl_Text_Buffer::remove_secondary_selection()
00597 {
00598   remove_selection_(&mSecondary);
00599 }
00600 
00601 
00602 /*
00603  Replace selected text.
00604  */
00605 void Fl_Text_Buffer::replace_secondary_selection(const char *text)
00606 {
00607   replace_selection_(&mSecondary, text);
00608 }
00609 
00610 
00611 /*
00612  Highlight a range of text.
00613  Start and End must be at a character boundary.
00614  */
00615 void Fl_Text_Buffer::highlight(int start, int end)
00616 {
00617   Fl_Text_Selection oldSelection = mHighlight;
00618   
00619   mHighlight.set(start, end);
00620   redisplay_selection(&oldSelection, &mHighlight);
00621 }
00622 
00623 
00624 /*
00625  Remove text highlighting.
00626  */
00627 void Fl_Text_Buffer::unhighlight()
00628 {
00629   Fl_Text_Selection oldSelection = mHighlight;
00630   
00631   mHighlight.mSelected = 0;
00632   redisplay_selection(&oldSelection, &mHighlight);
00633 }
00634 
00635   
00636 /*
00637  Return position of highlight.
00638  */
00639 int Fl_Text_Buffer::highlight_position(int *start, int *end)
00640 {
00641   return mHighlight.position(start, end);
00642 }
00643 
00644 
00645 /*
00646  Return a copy of highlighted text.
00647  */
00648 char *Fl_Text_Buffer::highlight_text()
00649 {
00650   return selection_text_(&mHighlight);
00651 }
00652 
00653 
00654 /*
00655  Add a callback that is called whenever text is modified.
00656  */
00657 void Fl_Text_Buffer::add_modify_callback(Fl_Text_Modify_Cb bufModifiedCB,
00658                                          void *cbArg)
00659 {
00660   Fl_Text_Modify_Cb *newModifyProcs =
00661   new Fl_Text_Modify_Cb[mNModifyProcs + 1];
00662   void **newCBArgs = new void *[mNModifyProcs + 1];
00663   for (int i = 0; i < mNModifyProcs; i++) {
00664     newModifyProcs[i + 1] = mModifyProcs[i];
00665     newCBArgs[i + 1] = mCbArgs[i];
00666   }
00667   if (mNModifyProcs != 0) {
00668     delete[]mModifyProcs;
00669     delete[]mCbArgs;
00670   }
00671   newModifyProcs[0] = bufModifiedCB;
00672   newCBArgs[0] = cbArg;
00673   mNModifyProcs++;
00674   mModifyProcs = newModifyProcs;
00675   mCbArgs = newCBArgs;
00676 }
00677 
00678 
00679 /*
00680  Remove a callback.
00681  */
00682 void Fl_Text_Buffer::remove_modify_callback(Fl_Text_Modify_Cb bufModifiedCB, 
00683                                             void *cbArg)
00684 {
00685   int i, toRemove = -1;
00686   
00687   /* find the matching callback to remove */
00688   for (i = 0; i < mNModifyProcs; i++) {
00689     if (mModifyProcs[i] == bufModifiedCB && mCbArgs[i] == cbArg) {
00690       toRemove = i;
00691       break;
00692     }
00693   }
00694   if (toRemove == -1) {
00695     Fl::error
00696     ("Fl_Text_Buffer::remove_modify_callback(): Can't find modify CB to remove");
00697     return;
00698   }
00699   
00700   /* Allocate new lists for remaining callback procs and args (if
00701    any are left) */
00702   mNModifyProcs--;
00703   if (mNModifyProcs == 0) {
00704     mNModifyProcs = 0;
00705     delete[]mModifyProcs;
00706     mModifyProcs = NULL;
00707     delete[]mCbArgs;
00708     mCbArgs = NULL;
00709     return;
00710   }
00711   Fl_Text_Modify_Cb *newModifyProcs = new Fl_Text_Modify_Cb[mNModifyProcs];
00712   void **newCBArgs = new void *[mNModifyProcs];
00713   
00714   /* copy out the remaining members and free the old lists */
00715   for (i = 0; i < toRemove; i++) {
00716     newModifyProcs[i] = mModifyProcs[i];
00717     newCBArgs[i] = mCbArgs[i];
00718   }
00719   for (; i < mNModifyProcs; i++) {
00720     newModifyProcs[i] = mModifyProcs[i + 1];
00721     newCBArgs[i] = mCbArgs[i + 1];
00722   }
00723   delete[]mModifyProcs;
00724   delete[]mCbArgs;
00725   mModifyProcs = newModifyProcs;
00726   mCbArgs = newCBArgs;
00727 }
00728 
00729 
00730 /*
00731  Add a callback that is called before deleting text.
00732  */
00733 void Fl_Text_Buffer::add_predelete_callback(Fl_Text_Predelete_Cb bufPreDeleteCB, 
00734                                             void *cbArg)
00735 {
00736   Fl_Text_Predelete_Cb *newPreDeleteProcs =
00737   new Fl_Text_Predelete_Cb[mNPredeleteProcs + 1];
00738   void **newCBArgs = new void *[mNPredeleteProcs + 1];
00739   for (int i = 0; i < mNPredeleteProcs; i++) {
00740     newPreDeleteProcs[i + 1] = mPredeleteProcs[i];
00741     newCBArgs[i + 1] = mPredeleteCbArgs[i];
00742   }
00743   if (!mNPredeleteProcs != 0) {
00744     delete[]mPredeleteProcs;
00745     delete[]mPredeleteCbArgs;
00746   }
00747   newPreDeleteProcs[0] = bufPreDeleteCB;
00748   newCBArgs[0] = cbArg;
00749   mNPredeleteProcs++;
00750   mPredeleteProcs = newPreDeleteProcs;
00751   mPredeleteCbArgs = newCBArgs;
00752 }
00753 
00754 
00755 /*
00756  Remove a callback.
00757  */
00758 void Fl_Text_Buffer::remove_predelete_callback(Fl_Text_Predelete_Cb bufPreDeleteCB, void *cbArg)
00759 {
00760   int i, toRemove = -1;
00761   /* find the matching callback to remove */
00762   for (i = 0; i < mNPredeleteProcs; i++) {
00763     if (mPredeleteProcs[i] == bufPreDeleteCB &&
00764         mPredeleteCbArgs[i] == cbArg) {
00765       toRemove = i;
00766       break;
00767     }
00768   }
00769   if (toRemove == -1) {
00770     Fl::error
00771     ("Fl_Text_Buffer::remove_predelete_callback(): Can't find pre-delete CB to remove");
00772     return;
00773   }
00774   
00775   /* Allocate new lists for remaining callback procs and args (if
00776    any are left) */
00777   mNPredeleteProcs--;
00778   if (mNPredeleteProcs == 0) {
00779     mNPredeleteProcs = 0;
00780     delete[]mPredeleteProcs;
00781     mPredeleteProcs = NULL;
00782     delete[]mPredeleteCbArgs;
00783     mPredeleteCbArgs = NULL;
00784     return;
00785   }
00786   Fl_Text_Predelete_Cb *newPreDeleteProcs =
00787   new Fl_Text_Predelete_Cb[mNPredeleteProcs];
00788   void **newCBArgs = new void *[mNPredeleteProcs];
00789   
00790   /* copy out the remaining members and free the old lists */
00791   for (i = 0; i < toRemove; i++) {
00792     newPreDeleteProcs[i] = mPredeleteProcs[i];
00793     newCBArgs[i] = mPredeleteCbArgs[i];
00794   }
00795   for (; i < mNPredeleteProcs; i++) {
00796     newPreDeleteProcs[i] = mPredeleteProcs[i + 1];
00797     newCBArgs[i] = mPredeleteCbArgs[i + 1];
00798   }
00799   delete[]mPredeleteProcs;
00800   delete[]mPredeleteCbArgs;
00801   mPredeleteProcs = newPreDeleteProcs;
00802   mPredeleteCbArgs = newCBArgs;
00803 }
00804 
00805 
00806 /*
00807  Return a copy of the line that contains a given index.
00808  Pos must be at a character boundary.
00809  */
00810 char *Fl_Text_Buffer::line_text(int pos) const {
00811   return text_range(line_start(pos), line_end(pos));
00812 } 
00813 
00814 
00815 /*
00816  Find the beginning of the line.
00817  */
00818 int Fl_Text_Buffer::line_start(int pos) const 
00819 {
00820   if (!findchar_backward(pos, '\n', &pos))
00821     return 0;
00822   return pos + 1;
00823 } 
00824 
00825 
00826 /*
00827  Find the end of the line.
00828  */
00829 int Fl_Text_Buffer::line_end(int pos) const {
00830   if (!findchar_forward(pos, '\n', &pos))
00831     pos = mLength;
00832   return pos;
00833 } 
00834 
00835 
00836 /*
00837  Find the beginning of a word.
00838  NOT UNICODE SAFE.
00839  */
00840 int Fl_Text_Buffer::word_start(int pos) const {
00841   // FIXME: character is ucs-4
00842   while (pos>0 && (isalnum(char_at(pos)) || char_at(pos) == '_')) {
00843     pos = prev_char(pos);
00844   } 
00845   // FIXME: character is ucs-4
00846   if (!(isalnum(char_at(pos)) || char_at(pos) == '_'))
00847     pos = next_char(pos);
00848   return pos;
00849 }
00850 
00851 
00852 /*
00853  Find the end of a word.
00854  NOT UNICODE SAFE.
00855  */
00856 int Fl_Text_Buffer::word_end(int pos) const {
00857   // FIXME: character is ucs-4
00858   while (pos < length() && (isalnum(char_at(pos)) || char_at(pos) == '_'))
00859   {
00860     pos = next_char(pos);
00861   }
00862   return pos;
00863 }
00864 
00865 
00866 /*
00867  Count the number of characters between two positions.
00868  */
00869 int Fl_Text_Buffer::count_displayed_characters(int lineStartPos,
00870                                                int targetPos) const
00871 {
00872   IS_UTF8_ALIGNED2(this, (lineStartPos))
00873   IS_UTF8_ALIGNED2(this, (targetPos))
00874   
00875   int charCount = 0;
00876   
00877   int pos = lineStartPos;
00878   while (pos < targetPos) {
00879     pos = next_char(pos);
00880     charCount++;
00881   }
00882   return charCount;
00883 } 
00884 
00885 
00886 /*
00887  Skip ahead a number of characters from a given index.
00888  This function breaks early if it encounters a newline character.
00889  */
00890 int Fl_Text_Buffer::skip_displayed_characters(int lineStartPos, int nChars)
00891 {
00892   IS_UTF8_ALIGNED2(this, (lineStartPos))
00893 
00894   int pos = lineStartPos;
00895   
00896   for (int charCount = 0; charCount < nChars && pos < mLength; charCount++) {
00897     unsigned int c = char_at(pos);
00898     if (c == '\n')
00899       return pos;
00900     pos = next_char(pos);
00901   }
00902   return pos;
00903 }
00904 
00905 
00906 /*
00907  Count the number of newline characters between start and end.
00908  startPos and endPos must be at a character boundary.
00909  This function is optimized for speed by not using UTF-8 calls.
00910  */
00911 int Fl_Text_Buffer::count_lines(int startPos, int endPos) const {
00912   IS_UTF8_ALIGNED2(this, (startPos))
00913   IS_UTF8_ALIGNED2(this, (endPos))
00914   
00915   int gapLen = mGapEnd - mGapStart;
00916   int lineCount = 0;
00917   
00918   int pos = startPos;
00919   while (pos < mGapStart)
00920   {
00921     if (pos == endPos)
00922       return lineCount;
00923     if (mBuf[pos++] == '\n')
00924       lineCount++;
00925   } 
00926   while (pos < mLength) {
00927     if (pos == endPos)
00928       return lineCount;
00929     if (mBuf[pos++ + gapLen] == '\n')
00930       lineCount++;
00931   }
00932   return lineCount;
00933 }
00934 
00935 
00936 /*
00937  Skip to the first character, n lines ahead.
00938  StartPos must be at a character boundary.
00939  This function is optimized for speed by not using UTF-8 calls.
00940  */
00941 int Fl_Text_Buffer::skip_lines(int startPos, int nLines)
00942 {
00943   IS_UTF8_ALIGNED2(this, (startPos))
00944   
00945   if (nLines == 0)
00946     return startPos;
00947   
00948   int gapLen = mGapEnd - mGapStart;
00949   int pos = startPos;
00950   int lineCount = 0;
00951   while (pos < mGapStart) {
00952     if (mBuf[pos++] == '\n') {
00953       lineCount++;
00954       if (lineCount == nLines) {
00955         IS_UTF8_ALIGNED2(this, (pos))
00956         return pos;
00957       }
00958     }
00959   }
00960   while (pos < mLength) {
00961     if (mBuf[pos++ + gapLen] == '\n') {
00962       lineCount++;
00963       if (lineCount >= nLines) {
00964         IS_UTF8_ALIGNED2(this, (pos))
00965         return pos;
00966       }
00967     }
00968   }
00969   IS_UTF8_ALIGNED2(this, (pos))
00970   return pos;
00971 }
00972 
00973 
00974 /*
00975  Skip to the first character, n lines back.
00976  StartPos must be at a character boundary.
00977  This function is optimized for speed by not using UTF-8 calls.
00978  */
00979 int Fl_Text_Buffer::rewind_lines(int startPos, int nLines)
00980 {
00981   IS_UTF8_ALIGNED2(this, (startPos))
00982   
00983   int pos = startPos - 1;
00984   if (pos <= 0)
00985     return 0;
00986   
00987   int gapLen = mGapEnd - mGapStart;
00988   int lineCount = -1;
00989   while (pos >= mGapStart) {
00990     if (mBuf[pos + gapLen] == '\n') {
00991       if (++lineCount >= nLines) {
00992         IS_UTF8_ALIGNED2(this, (pos+1))
00993         return pos + 1;
00994       }
00995     }
00996     pos--;
00997   }
00998   while (pos >= 0) {
00999     if (mBuf[pos] == '\n') {
01000       if (++lineCount >= nLines) {
01001         IS_UTF8_ALIGNED2(this, (pos+1))
01002         return pos + 1;
01003       }
01004     }
01005     pos--;
01006   }
01007   return 0;
01008 }
01009 
01010 
01011 /*
01012  Find a matching string in the buffer.
01013  */
01014 int Fl_Text_Buffer::search_forward(int startPos, const char *searchString,
01015                                    int *foundPos, int matchCase) const 
01016 {
01017   IS_UTF8_ALIGNED2(this, (startPos))
01018   IS_UTF8_ALIGNED(searchString)
01019   
01020   if (!searchString)
01021     return 0;
01022   int bp;
01023   const char *sp;
01024   if (matchCase) {
01025     while (startPos < length()) {
01026       bp = startPos;
01027       sp = searchString;
01028       for (;;) {
01029         char c = *sp;
01030         // we reached the end of the "needle", so we found the string!
01031         if (!c) {
01032           *foundPos = startPos;
01033           return 1;
01034         }
01035         int l = fl_utf8len1(c);
01036         if (memcmp(sp, address(bp), l))
01037           break;
01038         sp += l; bp += l;
01039       }
01040       startPos = next_char(startPos);
01041     }
01042   } else {
01043     while (startPos < length()) {
01044       bp = startPos;
01045       sp = searchString;
01046       for (;;) {
01047         // we reached the end of the "needle", so we found the string!
01048         if (!*sp) {
01049           *foundPos = startPos;
01050           return 1;
01051         }
01052         int l;
01053         unsigned int b = char_at(bp);
01054         unsigned int s = fl_utf8decode(sp, 0, &l);
01055         if (fl_tolower(b)!=fl_tolower(s))
01056           break;
01057         sp += l; 
01058         bp = next_char(bp);
01059       }
01060       startPos = next_char(startPos);
01061     }
01062   }  
01063   return 0;
01064 }
01065 
01066 int Fl_Text_Buffer::search_backward(int startPos, const char *searchString,
01067                                     int *foundPos, int matchCase) const 
01068 {
01069   IS_UTF8_ALIGNED2(this, (startPos))
01070   IS_UTF8_ALIGNED(searchString)
01071   
01072   if (!searchString)
01073     return 0;
01074   int bp;
01075   const char *sp;
01076   if (matchCase) {
01077     while (startPos >= 0) {
01078       bp = startPos;
01079       sp = searchString;
01080       for (;;) {
01081         char c = *sp;
01082         // we reached the end of the "needle", so we found the string!
01083         if (!c) {
01084           *foundPos = startPos;
01085           return 1;
01086         }
01087         int l = fl_utf8len1(c);
01088         if (memcmp(sp, address(bp), l))
01089           break;
01090         sp += l; bp += l;
01091       }
01092       startPos = prev_char(startPos);
01093     }
01094   } else {
01095     while (startPos >= 0) {
01096       bp = startPos;
01097       sp = searchString;
01098       for (;;) {
01099         // we reached the end of the "needle", so we found the string!
01100         if (!*sp) {
01101           *foundPos = startPos;
01102           return 1;
01103         }
01104         int l;
01105         unsigned int b = char_at(bp);
01106         unsigned int s = fl_utf8decode(sp, 0, &l);
01107         if (fl_tolower(b)!=fl_tolower(s))
01108           break;
01109         sp += l; 
01110         bp = next_char(bp);
01111       }
01112       startPos = prev_char(startPos);
01113     }
01114   }  
01115   return 0;
01116 }
01117 
01118 
01119 
01120 /*
01121  Insert a string into the buffer.
01122  Pos must be at a character boundary. Text must be a correct UTF-8 string.
01123  */
01124 int Fl_Text_Buffer::insert_(int pos, const char *text)
01125 {
01126   if (!text || !*text)
01127     return 0;
01128   
01129   int insertedLength = strlen(text);
01130   
01131   /* Prepare the buffer to receive the new text.  If the new text fits in
01132    the current buffer, just move the gap (if necessary) to where
01133    the text should be inserted.  If the new text is too large, reallocate
01134    the buffer with a gap large enough to accomodate the new text and a
01135    gap of mPreferredGapSize */
01136   if (insertedLength > mGapEnd - mGapStart)
01137     reallocate_with_gap(pos, insertedLength + mPreferredGapSize);
01138   else if (pos != mGapStart)
01139     move_gap(pos);
01140   
01141   /* Insert the new text (pos now corresponds to the start of the gap) */
01142   memcpy(&mBuf[pos], text, insertedLength);
01143   mGapStart += insertedLength;
01144   mLength += insertedLength;
01145   update_selections(pos, 0, insertedLength);
01146   
01147   if (mCanUndo) {
01148     if (undowidget == this && undoat == pos && undoinsert) {
01149       undoinsert += insertedLength;
01150     } else {
01151       undoinsert = insertedLength;
01152       undoyankcut = (undoat == pos) ? undocut : 0;
01153     }
01154     undoat = pos + insertedLength;
01155     undocut = 0;
01156     undowidget = this;
01157   }
01158   
01159   return insertedLength;
01160 }
01161 
01162 
01163 /*
01164  Remove a string from the buffer.
01165  Unicode safe. Start and end must be at a character boundary.
01166  */
01167 void Fl_Text_Buffer::remove_(int start, int end)
01168 {
01169   /* if the gap is not contiguous to the area to remove, move it there */
01170   
01171   if (mCanUndo) {
01172     if (undowidget == this && undoat == end && undocut) {
01173       undobuffersize(undocut + end - start + 1);
01174       memmove(undobuffer + end - start, undobuffer, undocut);
01175       undocut += end - start;
01176     } else {
01177       undocut = end - start;
01178       undobuffersize(undocut);
01179     }
01180     undoat = start;
01181     undoinsert = 0;
01182     undoyankcut = 0;
01183     undowidget = this;
01184   }
01185   
01186   if (start > mGapStart) {
01187     if (mCanUndo)
01188       memcpy(undobuffer, mBuf + (mGapEnd - mGapStart) + start,
01189              end - start);
01190     move_gap(start);
01191   } else if (end < mGapStart) {
01192     if (mCanUndo)
01193       memcpy(undobuffer, mBuf + start, end - start);
01194     move_gap(end);
01195   } else {
01196     int prelen = mGapStart - start;
01197     if (mCanUndo) {
01198       memcpy(undobuffer, mBuf + start, prelen);
01199       memcpy(undobuffer + prelen, mBuf + mGapEnd, end - start - prelen);
01200     }
01201   }
01202   
01203   /* expand the gap to encompass the deleted characters */
01204   mGapEnd += end - mGapStart;
01205   mGapStart -= mGapStart - start;
01206   
01207   /* update the length */
01208   mLength -= end - start;
01209   
01210   /* fix up any selections which might be affected by the change */
01211   update_selections(start, end - start, 0);
01212 }
01213 
01214   
01215 /*
01216  simple setter.
01217  Unicode safe. Start and end must be at a character boundary.
01218  */
01219 void Fl_Text_Selection::set(int startpos, int endpos)
01220 {
01221   mSelected = startpos != endpos;
01222   mStart = min(startpos, endpos);
01223   mEnd = max(startpos, endpos);
01224 }
01225 
01226 
01227 /*
01228  simple getter.
01229  Unicode safe. Start and end will be at a character boundary.
01230  */
01231 int Fl_Text_Selection::position(int *startpos, int *endpos) const {
01232   if (!mSelected)
01233     return 0;
01234   *startpos = mStart;
01235   *endpos = mEnd;
01236   
01237   return 1;
01238 } 
01239 
01240 
01241 /*
01242  Return if a position is inside the selected area.
01243  Unicode safe. Pos must be at a character boundary.
01244  */
01245 int Fl_Text_Selection::includes(int pos) const {
01246   return (selected() && pos >= start() && pos < end() );
01247 }
01248 
01249 
01250 /*
01251  Return a duplicate of the selected text, or an empty string.
01252  Unicode safe.
01253  */
01254 char *Fl_Text_Buffer::selection_text_(Fl_Text_Selection * sel) const {
01255   int start, end;
01256   
01257   /* If there's no selection, return an allocated empty string */
01258   if (!sel->position(&start, &end))
01259   {
01260     char *s = (char *) malloc(1);
01261     *s = '\0';
01262     return s;
01263   }
01264   
01265   /* Return the selected range */
01266     return text_range(start, end);
01267 }
01268 
01269 
01270 /*
01271  Remove the selected text.
01272  Unicode safe.
01273  */
01274 void Fl_Text_Buffer::remove_selection_(Fl_Text_Selection * sel)
01275 {
01276   int start, end;
01277   
01278   if (!sel->position(&start, &end))
01279     return;
01280   remove(start, end);
01281   //undoyankcut = undocut;
01282 }
01283 
01284 
01285 /*
01286  Replace selection with text.
01287  Unicode safe.
01288  */
01289 void Fl_Text_Buffer::replace_selection_(Fl_Text_Selection * sel,
01290                                         const char *text)
01291 {
01292   Fl_Text_Selection oldSelection = *sel;
01293   
01294   /* If there's no selection, return */
01295   int start, end;
01296   if (!sel->position(&start, &end))
01297     return;
01298   
01299   /* Do the appropriate type of replace */
01300     replace(start, end, text);
01301   
01302   /* Unselect (happens automatically in BufReplace, but BufReplaceRect
01303    can't detect when the contents of a selection goes away) */
01304   sel->mSelected = 0;
01305   redisplay_selection(&oldSelection, sel);
01306 }
01307 
01308   
01309 /*
01310  Call all callbacks.
01311  Unicode safe.
01312  */
01313 void Fl_Text_Buffer::call_modify_callbacks(int pos, int nDeleted,
01314                                            int nInserted, int nRestyled,
01315                                            const char *deletedText) const {
01316   IS_UTF8_ALIGNED2(this, pos)
01317   for (int i = 0; i < mNModifyProcs; i++)
01318     (*mModifyProcs[i]) (pos, nInserted, nDeleted, nRestyled,
01319                         deletedText, mCbArgs[i]);
01320 } 
01321 
01322 
01323 /*
01324  Call all callbacks.
01325  Unicode safe.
01326  */
01327 void Fl_Text_Buffer::call_predelete_callbacks(int pos, int nDeleted) const {
01328   for (int i = 0; i < mNPredeleteProcs; i++)
01329     (*mPredeleteProcs[i]) (pos, nDeleted, mPredeleteCbArgs[i]);
01330 } 
01331 
01332 
01333 /*
01334  Redisplay a new selected area.
01335  Unicode safe.
01336  */
01337 void Fl_Text_Buffer::redisplay_selection(Fl_Text_Selection *
01338                                            oldSelection,
01339                                            Fl_Text_Selection *
01340                                            newSelection) const
01341 {
01342   int oldStart, oldEnd, newStart, newEnd, ch1Start, ch1End, ch2Start,
01343   ch2End;
01344   
01345   /* If either selection is rectangular, add an additional character to
01346    the end of the selection to request the redraw routines to wipe out
01347    the parts of the selection beyond the end of the line */
01348   oldStart = oldSelection->mStart;
01349   newStart = newSelection->mStart;
01350   oldEnd = oldSelection->mEnd;
01351   newEnd = newSelection->mEnd;
01352   
01353   /* If the old or new selection is unselected, just redisplay the
01354    single area that is (was) selected and return */
01355   if (!oldSelection->mSelected && !newSelection->mSelected)
01356     return;
01357   if (!oldSelection->mSelected)
01358   {
01359     call_modify_callbacks(newStart, 0, 0, newEnd - newStart, NULL);
01360     return;
01361   }
01362   if (!newSelection->mSelected) {
01363     call_modify_callbacks(oldStart, 0, 0, oldEnd - oldStart, NULL);
01364     return;
01365   }
01366   
01367   /* If the selections are non-contiguous, do two separate updates
01368    and return */
01369   if (oldEnd < newStart || newEnd < oldStart) {
01370     call_modify_callbacks(oldStart, 0, 0, oldEnd - oldStart, NULL);
01371     call_modify_callbacks(newStart, 0, 0, newEnd - newStart, NULL);
01372     return;
01373   }
01374   
01375   /* Otherwise, separate into 3 separate regions: ch1, and ch2 (the two
01376    changed areas), and the unchanged area of their intersection,
01377    and update only the changed area(s) */
01378   ch1Start = min(oldStart, newStart);
01379   ch2End = max(oldEnd, newEnd);
01380   ch1End = max(oldStart, newStart);
01381   ch2Start = min(oldEnd, newEnd);
01382   if (ch1Start != ch1End)
01383     call_modify_callbacks(ch1Start, 0, 0, ch1End - ch1Start, NULL);
01384   if (ch2Start != ch2End)
01385     call_modify_callbacks(ch2Start, 0, 0, ch2End - ch2Start, NULL);
01386 }
01387 
01388 
01389 /*
01390  Move the gap around without changing buffer content.
01391  Unicode safe. Pos must be at a character boundary.
01392  */
01393 void Fl_Text_Buffer::move_gap(int pos)
01394 {
01395   int gapLen = mGapEnd - mGapStart;
01396   
01397   if (pos > mGapStart)
01398     memmove(&mBuf[mGapStart], &mBuf[mGapEnd], pos - mGapStart);
01399   else
01400     memmove(&mBuf[pos + gapLen], &mBuf[pos], mGapStart - pos);
01401   mGapEnd += pos - mGapStart;
01402   mGapStart += pos - mGapStart;
01403 }
01404 
01405 
01406 /*
01407  Create a larger gap.
01408  Unicode safe. Start must be at a character boundary.
01409  */
01410 void Fl_Text_Buffer::reallocate_with_gap(int newGapStart, int newGapLen)
01411 {
01412   char *newBuf = (char *) malloc(mLength + newGapLen);
01413   int newGapEnd = newGapStart + newGapLen;
01414   
01415   if (newGapStart <= mGapStart) {
01416     memcpy(newBuf, mBuf, newGapStart);
01417     memcpy(&newBuf[newGapEnd], &mBuf[newGapStart],
01418            mGapStart - newGapStart);
01419     memcpy(&newBuf[newGapEnd + mGapStart - newGapStart],
01420            &mBuf[mGapEnd], mLength - mGapStart);
01421   } else {                      /* newGapStart > mGapStart */
01422     memcpy(newBuf, mBuf, mGapStart);
01423     memcpy(&newBuf[mGapStart], &mBuf[mGapEnd], newGapStart - mGapStart);
01424     memcpy(&newBuf[newGapEnd],
01425            &mBuf[mGapEnd + newGapStart - mGapStart],
01426            mLength - newGapStart);
01427   }
01428   free((void *) mBuf);
01429   mBuf = newBuf;
01430   mGapStart = newGapStart;
01431   mGapEnd = newGapEnd;
01432   }
01433 
01434 
01435 /*
01436  Update selection range if characters were inserted.
01437  Unicode safe. Pos must be at a character boundary.
01438  */
01439 void Fl_Text_Buffer::update_selections(int pos, int nDeleted,
01440                                        int nInserted)
01441 {
01442   mPrimary.update(pos, nDeleted, nInserted);
01443   mSecondary.update(pos, nDeleted, nInserted);
01444   mHighlight.update(pos, nDeleted, nInserted);
01445 }
01446 
01447 
01448 // unicode safe, assuming the arguments are on character boundaries
01449 void Fl_Text_Selection::update(int pos, int nDeleted, int nInserted)
01450 {
01451   if (!mSelected || pos > mEnd)
01452     return;
01453   if (pos + nDeleted <= mStart) {
01454     mStart += nInserted - nDeleted;
01455     mEnd += nInserted - nDeleted;
01456   } else if (pos <= mStart && pos + nDeleted >= mEnd) {
01457     mStart = pos;
01458     mEnd = pos;
01459     mSelected = 0;
01460   } else if (pos <= mStart && pos + nDeleted < mEnd) {
01461     mStart = pos;
01462     mEnd = nInserted + mEnd - nDeleted;
01463   } else if (pos < mEnd) {
01464     mEnd += nInserted - nDeleted;
01465     if (mEnd <= mStart)
01466       mSelected = 0;
01467   }
01468 }
01469 
01470 
01471 /*
01472  Find a UCS-4 character.
01473  StartPos must be at a character boundary, searchChar is UCS-4 encoded.
01474  */
01475 int Fl_Text_Buffer::findchar_forward(int startPos, unsigned searchChar,
01476                                      int *foundPos) const 
01477 {
01478   if (startPos >= mLength) {
01479     *foundPos = mLength;
01480     return 0;
01481   }
01482   
01483   if (startPos<0)
01484     startPos = 0;
01485   
01486   for ( ; startPos<mLength; startPos = next_char(startPos)) {
01487     if (searchChar == char_at(startPos)) {
01488       *foundPos = startPos;
01489       return 1;
01490     }
01491   }
01492   
01493   *foundPos = mLength;
01494   return 0;
01495 }
01496 
01497   
01498 /*
01499  Find a UCS-4 character.
01500  StartPos must be at a character boundary, searchChar is UCS-4 encoded.
01501  */
01502 int Fl_Text_Buffer::findchar_backward(int startPos, unsigned int searchChar,
01503                                       int *foundPos) const {
01504   if (startPos <= 0) {
01505     *foundPos = 0;
01506     return 0;
01507   }
01508   
01509   if (startPos > mLength)
01510     startPos = mLength;
01511   
01512   for (startPos = prev_char(startPos); startPos>=0; startPos = prev_char(startPos)) {
01513     if (searchChar == char_at(startPos)) {
01514       *foundPos = startPos;
01515       return 1;
01516     }
01517   }
01518   
01519   *foundPos = 0;
01520   return 0;
01521 }
01522 
01523 //#define EXAMPLE_ENCODING // shows how to process any encoding for which a decoding function exists
01524 #ifdef EXAMPLE_ENCODING 
01525 
01526 // returns the UCS equivalent of *p in CP1252 and advances p by 1
01527 unsigned cp1252toucs(char* &p)
01528 {
01529   // Codes 0x80..0x9f from the Microsoft CP1252 character set, translated
01530   // to Unicode
01531   static unsigned cp1252[32] = {
01532     0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021,
01533     0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f,
01534     0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014,
01535     0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178
01536   };
01537   unsigned char uc = *(unsigned char*)p;
01538   p++;
01539   return (uc < 0x80 || uc >= 0xa0 ? uc : cp1252[uc - 0x80]);
01540 }
01541 
01542 // returns the UCS equivalent of *p in UTF-16 and advances p by 2 (or more for surrogates)
01543 unsigned utf16toucs(char* &p)
01544 {
01545   union {
01546 #if WORDS_BIGENDIAN
01547     struct { unsigned char a, b;} chars;
01548 #else
01549     struct { unsigned char b, a;} chars;
01550 #endif
01551     U16 short_val;
01552   } u;
01553   u.chars.a = *(unsigned char*)p++;
01554   u.chars.b = *(unsigned char*)p++;
01555   return u.short_val;
01556 }
01557 
01558 // filter that produces, from an input stream fed by reading from fp,
01559 // a UTF-8-encoded output stream written in buffer.
01560 // Input can be any (e.g., 8-bit, UTF-16) encoding.
01561 // Output is true UTF-8.
01562 // p_trf points to a function that transforms encoded byte(s) into one UCS
01563 // and that increases the pointer by the adequate quantity
01564 static int general_input_filter(char *buffer, int buflen, 
01565                                  char *line, int sline, char* &endline, 
01566                                  unsigned (*p_trf)(char* &),
01567                                  FILE *fp)
01568 {
01569   char *p, *q, multibyte[5];
01570   int lq, r, offset;
01571   p = endline = line;
01572   q = buffer;
01573   while (q < buffer + buflen) {
01574     if (p >= endline) {
01575       r = fread(line, 1, sline, fp);
01576       endline = line + r; 
01577       if (r == 0) return q - buffer;
01578       p = line;
01579     }
01580     if (q + 4 /*max width of utf-8 char*/ > buffer + buflen) {
01581       memmove(line, p, endline - p);
01582       endline -= (p - line);
01583       return q - buffer;
01584     }
01585     lq = fl_utf8encode( p_trf(p), multibyte );
01586     memcpy(q, multibyte, lq);
01587     q += lq; 
01588   }
01589   memmove(line, p, endline - p);
01590   endline -= (p - line);
01591   return q - buffer;
01592 }
01593 #endif // EXAMPLE_ENCODING
01594 
01595 /*
01596  filter that produces, from an input stream fed by reading from fp,
01597  a UTF-8-encoded output stream written in buffer.
01598  Input can be UTF-8. If it is not, it is decoded with CP1252.
01599  Output is UTF-8.
01600  *input_was_changed is set to true if the input was not strict UTF-8 so output
01601  differs from input.
01602  */
01603 static int utf8_input_filter(char *buffer, int buflen, char *line, int sline, char* &endline, 
01604               FILE *fp, int *input_was_changed)
01605 {
01606   char *p, *q, multibyte[5];
01607   int l, lp, lq, r;
01608   unsigned u;
01609   p = endline = line;
01610   q = buffer;
01611   while (q < buffer + buflen) {
01612     if (p >= endline) {
01613       r = fread(line, 1, sline, fp);
01614       endline = line + r; 
01615       if (r == 0) return q - buffer;
01616       p = line;
01617     }
01618     l = fl_utf8len1(*p);
01619     if (p + l > endline) {
01620       memmove(line, p, endline - p);
01621       endline -= (p - line);
01622       r = fread(endline, 1, sline - (endline - line), fp);
01623       endline += r;
01624       p = line;
01625       if (endline - line < l) break;
01626     }
01627     while ( l > 0) {
01628       u = fl_utf8decode(p, p+l, &lp);
01629       lq = fl_utf8encode(u, multibyte);
01630       if (lp != l || lq != l) *input_was_changed = true;
01631       if (q + lq > buffer + buflen) {
01632         memmove(line, p, endline - p);
01633         endline -= (p - line);
01634         return q - buffer;
01635       }
01636       memcpy(q, multibyte, lq);
01637       q += lq; 
01638       p += lp;
01639       l -= lp;
01640     }
01641   }
01642   memmove(line, p, endline - p);
01643   endline -= (p - line);
01644   return q - buffer;
01645 }
01646 
01647 const char *Fl_Text_Buffer::file_encoding_warning_message = 
01648 "Displayed text contains the UTF-8 transcoding\n"
01649 "of the input file which was not UTF-8 encoded.\n"
01650 "Some changes may have occurred.";
01651 
01652 /*
01653  Insert text from a file.
01654  Input file can be of various encodings according to what input fiter is used.
01655  utf8_input_filter accepts UTF-8 or CP1252 as input encoding.
01656  Output is always UTF-8.
01657  */
01658  int Fl_Text_Buffer::insertfile(const char *file, int pos, int buflen)
01659 {
01660   FILE *fp;
01661   if (!(fp = fl_fopen(file, "r")))
01662     return 1;
01663   char *buffer = new char[buflen + 1];  
01664   char *endline, line[100];
01665   int l;
01666   input_file_was_transcoded = false;
01667   endline = line;
01668   while (true) {
01669 #ifdef EXAMPLE_ENCODING
01670     // example of 16-bit encoding: UTF-16
01671     l = general_input_filter(buffer, buflen, 
01672                                   line, sizeof(line), endline, 
01673                                   utf16toucs, // use cp1252toucs to read CP1252-encoded files
01674                                   fp);
01675     input_file_was_transcoded = true;
01676 #else
01677     l = utf8_input_filter(buffer, buflen, line, sizeof(line), endline, 
01678                           fp, &input_file_was_transcoded);
01679 #endif
01680     if (l == 0) break;
01681     buffer[l] = 0;
01682     insert(pos, buffer);
01683     pos += l;
01684     }
01685   int e = ferror(fp) ? 2 : 0;
01686   fclose(fp);
01687   delete[]buffer;
01688   if ( (!e) && input_file_was_transcoded && transcoding_warning_action) {
01689     transcoding_warning_action(this);
01690     }
01691   return e;
01692 }
01693 
01694 
01695 /*
01696  Write text to file.
01697  Unicode safe.
01698  */
01699 int Fl_Text_Buffer::outputfile(const char *file,
01700                                int start, int end,
01701                                int buflen) {
01702   FILE *fp;
01703   if (!(fp = fl_fopen(file, "w")))
01704     return 1;
01705   for (int n; (n = min(end - start, buflen)); start += n) {
01706     const char *p = text_range(start, start + n);
01707     int r = fwrite(p, 1, n, fp);
01708     free((void *) p);
01709     if (r != n)
01710       break;
01711   }
01712   
01713   int e = ferror(fp) ? 2 : 0;
01714   fclose(fp);
01715   return e;
01716 }
01717 
01718 
01719 /*
01720  Return the previous character position.
01721  Unicode safe.
01722  */
01723 int Fl_Text_Buffer::prev_char_clipped(int pos) const
01724 {
01725   if (pos<=0)
01726     return 0;
01727 
01728   IS_UTF8_ALIGNED2(this, (pos))  
01729 
01730   char c;
01731   do {
01732     pos--;
01733     if (pos==0)
01734       return 0;
01735     c = byte_at(pos);
01736   } while ( (c&0xc0) == 0x80);
01737   
01738   IS_UTF8_ALIGNED2(this, (pos))  
01739   return pos;
01740 }
01741 
01742 
01743 /*
01744  Return the previous character position.
01745  Returns -1 if the beginning of the buffer is reached.
01746  */
01747 int Fl_Text_Buffer::prev_char(int pos) const
01748 {
01749   if (pos==0) return -1;
01750   return prev_char_clipped(pos);
01751 }
01752 
01753 
01754 /*
01755  Return the next character position.
01756  Returns length() if the end of the buffer is reached.
01757  */
01758 int Fl_Text_Buffer::next_char(int pos) const
01759 {
01760   IS_UTF8_ALIGNED2(this, (pos))  
01761   int n = fl_utf8len1(byte_at(pos));
01762   pos += n;
01763   if (pos>=mLength)
01764     return mLength;
01765   IS_UTF8_ALIGNED2(this, (pos))  
01766   return pos;
01767 }
01768 
01769 
01770 /*
01771  Return the next character position.
01772  If the end of the buffer is reached, it returns the current position.
01773  */
01774 int Fl_Text_Buffer::next_char_clipped(int pos) const
01775 {
01776   return next_char(pos);
01777 }
01778 
01779 /*
01780  Align an index to the current UTF-8 boundary.
01781  */
01782 int Fl_Text_Buffer::utf8_align(int pos) const 
01783 {
01784   char c = byte_at(pos);
01785   while ( (c&0xc0) == 0x80) {
01786     pos--;
01787     c = byte_at(pos);
01788   }
01789   return pos;
01790 }
01791 
01792 //
01793 // End of "$Id: Fl_Text_Buffer.cxx 8040 2010-12-15 17:38:39Z manolo $".
01794 //