Include a stripped-down version of FLTK in tree and add a USE_INCLUDED_FLTK option to build against it.


git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@4603 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/common/fltk/src/Fl_Text_Buffer.cxx b/common/fltk/src/Fl_Text_Buffer.cxx
new file mode 100644
index 0000000..9714f51
--- /dev/null
+++ b/common/fltk/src/Fl_Text_Buffer.cxx
@@ -0,0 +1,1794 @@
+//
+// "$Id: Fl_Text_Buffer.cxx 8040 2010-12-15 17:38:39Z manolo $"
+//
+// Copyright 2001-2010 by Bill Spitzak and others.
+// Original code Copyright Mark Edel.  Permission to distribute under
+// the LGPL for the FLTK library granted by Mark Edel.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems on the following page:
+//
+//     http://www.fltk.org/str.php
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <FL/fl_utf8.h>
+#include "flstring.h"
+#include <ctype.h>
+#include <FL/Fl.H>
+#include <FL/Fl_Text_Buffer.H>
+#include <FL/fl_ask.H>
+
+
+/*
+ This file is based on a port of NEdit to FLTK many years ago. NEdit at that
+ point was already stretched beyond the task it was designed for which explains
+ why the source code is sometimes pretty convoluted. It still is a very useful
+ widget for FLTK, and we are thankful that the nedit team allowed us to 
+ integrate their code.
+
+ With the introduction of Unicode and UTF-8, Fl_Text_... has to go into a whole
+ new generation of code. Originally designed for monospaced fonts only, many
+ features make less sense in the multibyte and multiwidth world of UTF-8.
+
+ Columns are a good example. There is simply no such thing. The new Fl_Text_...
+ widget converts columns to pixels by multiplying them with the average 
+ character width for a given font.
+
+ Rectangular selections were rarely used (if at all) and make little sense when
+ using variable width fonts. They have been removed.
+
+ Using multiple spaces to emulate tab stops has been replaced by pixel counting
+ routines. They are slower, but give the expected result for proportional fonts.
+
+ And constantly recalculating character widths is just much too expensive. Lines
+ of text are now subdivided into blocks of text which are measured at once 
+ instead of individual characters. 
+ */
+
+
+#ifndef min
+
+static int max(int i1, int i2)
+{
+  return i1 >= i2 ? i1 : i2;
+}
+
+static int min(int i1, int i2)
+{
+  return i1 <= i2 ? i1 : i2;
+}
+
+#endif
+
+
+static char *undobuffer;
+static int undobufferlength;
+static Fl_Text_Buffer *undowidget;
+static int undoat;		// points after insertion
+static int undocut;		// number of characters deleted there
+static int undoinsert;		// number of characters inserted
+static int undoyankcut;		// length of valid contents of buffer, even if undocut=0
+
+/*
+ Resize the undo buffer to match at least the requested size.
+ */
+static void undobuffersize(int n)
+{
+  if (n > undobufferlength) {
+    if (undobuffer) {
+      do {
+	undobufferlength *= 2;
+      } while (undobufferlength < n);
+      undobuffer = (char *) realloc(undobuffer, undobufferlength);
+    } else {
+      undobufferlength = n + 9;
+      undobuffer = (char *) malloc(undobufferlength);
+    }
+  }
+}
+
+static void def_transcoding_warning_action(Fl_Text_Buffer *text)
+{
+  fl_alert("%s", text->file_encoding_warning_message);
+}
+
+/*
+ Initialize all variables.
+ */
+Fl_Text_Buffer::Fl_Text_Buffer(int requestedSize, int preferredGapSize)
+{
+  mLength = 0;
+  mPreferredGapSize = preferredGapSize;
+  mBuf = (char *) malloc(requestedSize + mPreferredGapSize);
+  mGapStart = 0;
+  mGapEnd = mPreferredGapSize;
+  mTabDist = 8;
+  mPrimary.mSelected = 0;
+  mPrimary.mStart = mPrimary.mEnd = 0;
+  mSecondary.mSelected = 0;
+  mSecondary.mStart = mSecondary.mEnd = 0;
+  mHighlight.mSelected = 0;
+  mHighlight.mStart = mHighlight.mEnd = 0;
+  mModifyProcs = NULL;
+  mCbArgs = NULL;
+  mNModifyProcs = 0;
+  mNPredeleteProcs = 0;
+  mPredeleteProcs = NULL;
+  mPredeleteCbArgs = NULL;
+  mCursorPosHint = 0;
+  mCanUndo = 1;
+  input_file_was_transcoded = 0;
+  transcoding_warning_action = def_transcoding_warning_action;
+}
+
+
+/*
+ Free all resources.
+ */
+Fl_Text_Buffer::~Fl_Text_Buffer()
+{
+  free(mBuf);
+  if (mNModifyProcs != 0) {
+    delete[]mModifyProcs;
+    delete[]mCbArgs;
+  }
+  if (mNPredeleteProcs != 0) {
+    delete[]mPredeleteProcs;
+    delete[]mPredeleteCbArgs;
+  }
+}
+
+
+/*
+ This function copies verbose whatever is in front and after the gap into a
+ single buffer.
+ */
+char *Fl_Text_Buffer::text() const {
+  char *t = (char *) malloc(mLength + 1);
+  memcpy(t, mBuf, mGapStart);
+  memcpy(t+mGapStart, mBuf+mGapEnd, mLength - mGapStart);
+  t[mLength] = '\0';
+  return t;
+} 
+
+
+/*
+ Set the text buffer to a new string.
+ */
+void Fl_Text_Buffer::text(const char *t)
+{
+  IS_UTF8_ALIGNED(t)
+  
+  call_predelete_callbacks(0, length());
+  
+  /* Save information for redisplay, and get rid of the old buffer */
+  const char *deletedText = text();
+  int deletedLength = mLength;
+  free((void *) mBuf);
+  
+  /* Start a new buffer with a gap of mPreferredGapSize at the end */
+  int insertedLength = strlen(t);
+  mBuf = (char *) malloc(insertedLength + mPreferredGapSize);
+  mLength = insertedLength;
+  mGapStart = insertedLength;
+  mGapEnd = mGapStart + mPreferredGapSize;
+  memcpy(mBuf, t, insertedLength);
+  
+  /* Zero all of the existing selections */
+  update_selections(0, deletedLength, 0);
+  
+  /* Call the saved display routine(s) to update the screen */
+  call_modify_callbacks(0, deletedLength, insertedLength, 0, deletedText);
+  free((void *) deletedText);
+}
+
+
+/*
+ Creates a range of text to a new buffer and copies verbose from around the gap.
+ */
+char *Fl_Text_Buffer::text_range(int start, int end) const {
+  IS_UTF8_ALIGNED2(this, (start))
+  IS_UTF8_ALIGNED2(this, (end))
+  
+  char *s = NULL;
+  
+  /* Make sure start and end are ok, and allocate memory for returned string.
+   If start is bad, return "", if end is bad, adjust it. */
+  if (start < 0 || start > mLength)
+  {
+    s = (char *) malloc(1);
+    s[0] = '\0';
+    return s;
+  }
+  if (end < start) {
+    int temp = start;
+    start = end;
+    end = temp;
+  }
+  if (end > mLength)
+    end = mLength;
+  int copiedLength = end - start;
+  s = (char *) malloc(copiedLength + 1);
+  
+  /* Copy the text from the buffer to the returned string */
+  if (end <= mGapStart) {
+    memcpy(s, mBuf + start, copiedLength);
+  } else if (start >= mGapStart) {
+    memcpy(s, mBuf + start + (mGapEnd - mGapStart), copiedLength);
+  } else {
+    int part1Length = mGapStart - start;
+    memcpy(s, mBuf + start, part1Length);
+    memcpy(s + part1Length, mBuf + mGapEnd, copiedLength - part1Length);
+  }
+  s[copiedLength] = '\0';
+  return s;
+}
+
+/*
+ Return a UCS-4 character at the given index.
+ Pos must be at a character boundary.
+ */
+unsigned int Fl_Text_Buffer::char_at(int pos) const {  
+  if (pos < 0 || pos >= mLength)
+    return '\0';
+  
+  IS_UTF8_ALIGNED2(this, (pos))
+  
+  const char *src = address(pos);
+  return fl_utf8decode(src, 0, 0);
+} 
+
+
+/*
+ Return the raw byte at the given index.
+ This function ignores all unicode encoding.
+ */
+char Fl_Text_Buffer::byte_at(int pos) const {
+  if (pos < 0 || pos >= mLength)
+    return '\0';
+  const char *src = address(pos);
+  return *src;
+} 
+
+
+/*
+ Insert some text at the given index.
+ Pos must be at a character boundary.
+*/
+void Fl_Text_Buffer::insert(int pos, const char *text)
+{
+  IS_UTF8_ALIGNED2(this, (pos))
+  IS_UTF8_ALIGNED(text)
+  
+  /* check if there is actually any text */
+  if (!text || !*text)
+    return;
+  
+  /* if pos is not contiguous to existing text, make it */
+  if (pos > mLength)
+    pos = mLength;
+  if (pos < 0)
+    pos = 0;
+  
+  /* Even if nothing is deleted, we must call these callbacks */
+  call_predelete_callbacks(pos, 0);
+  
+  /* insert and redisplay */
+  int nInserted = insert_(pos, text);
+  mCursorPosHint = pos + nInserted;
+  IS_UTF8_ALIGNED2(this, (mCursorPosHint))
+  call_modify_callbacks(pos, 0, nInserted, 0, NULL);
+}
+
+
+/*
+ Replace a range of text with new text.
+ Start and end must be at a character boundary.
+*/
+void Fl_Text_Buffer::replace(int start, int end, const char *text)
+{
+  // Range check...
+  if (!text)
+    return;
+  if (start < 0)
+    start = 0;
+  if (end > mLength)
+    end = mLength;
+
+  IS_UTF8_ALIGNED2(this, (start))
+  IS_UTF8_ALIGNED2(this, (end))
+  IS_UTF8_ALIGNED(text)
+  
+  call_predelete_callbacks(start, end - start);
+  const char *deletedText = text_range(start, end);
+  remove_(start, end);
+  int nInserted = insert_(start, text);
+  mCursorPosHint = start + nInserted;
+  call_modify_callbacks(start, end - start, nInserted, 0, deletedText);
+  free((void *) deletedText);
+}
+
+
+/*
+ Remove a range of text.
+ Start and End must be at a character boundary.
+*/
+void Fl_Text_Buffer::remove(int start, int end)
+{
+  /* Make sure the arguments make sense */
+  if (start > end) {
+    int temp = start;
+    start = end;
+    end = temp;
+  }
+  if (start > mLength)
+    start = mLength;
+  if (start < 0)
+    start = 0;
+  if (end > mLength)
+    end = mLength;
+  if (end < 0)
+    end = 0;
+
+  IS_UTF8_ALIGNED2(this, (start))
+  IS_UTF8_ALIGNED2(this, (end))  
+  
+  if (start == end)
+    return;
+  
+  call_predelete_callbacks(start, end - start);
+  /* Remove and redisplay */
+  const char *deletedText = text_range(start, end);
+  remove_(start, end);
+  mCursorPosHint = start;
+  call_modify_callbacks(start, end - start, 0, 0, deletedText);
+  free((void *) deletedText);
+}
+
+
+/*
+ Copy a range of text from another text buffer.
+ fromStart, fromEnd, and toPos must be at a character boundary.
+ */
+void Fl_Text_Buffer::copy(Fl_Text_Buffer * fromBuf, int fromStart,
+			  int fromEnd, int toPos)
+{
+  IS_UTF8_ALIGNED2(fromBuf, fromStart)
+  IS_UTF8_ALIGNED2(fromBuf, fromEnd)
+  IS_UTF8_ALIGNED2(this, (toPos))
+  
+  int copiedLength = fromEnd - fromStart;
+  
+  /* Prepare the buffer to receive the new text.  If the new text fits in
+   the current buffer, just move the gap (if necessary) to where
+   the text should be inserted.  If the new text is too large, reallocate
+   the buffer with a gap large enough to accomodate the new text and a
+   gap of mPreferredGapSize */
+  if (copiedLength > mGapEnd - mGapStart)
+    reallocate_with_gap(toPos, copiedLength + mPreferredGapSize);
+  else if (toPos != mGapStart)
+    move_gap(toPos);
+  
+  /* Insert the new text (toPos now corresponds to the start of the gap) */
+  if (fromEnd <= fromBuf->mGapStart) {
+    memcpy(&mBuf[toPos], &fromBuf->mBuf[fromStart], copiedLength);
+  } else if (fromStart >= fromBuf->mGapStart) {
+    memcpy(&mBuf[toPos],
+	   &fromBuf->mBuf[fromStart + (fromBuf->mGapEnd - fromBuf->mGapStart)],
+	   copiedLength);
+  } else {
+    int part1Length = fromBuf->mGapStart - fromStart;
+    memcpy(&mBuf[toPos], &fromBuf->mBuf[fromStart], part1Length);
+    memcpy(&mBuf[toPos + part1Length],
+	   &fromBuf->mBuf[fromBuf->mGapEnd], copiedLength - part1Length);
+  }
+  mGapStart += copiedLength;
+  mLength += copiedLength;
+  update_selections(toPos, 0, copiedLength);
+}
+
+
+/*
+ Take the previous changes and undo them. Return the previous
+ cursor position in cursorPos. Returns 1 if the undo was applied.
+ CursorPos will be at a character boundary.
+ */ 
+int Fl_Text_Buffer::undo(int *cursorPos)
+{
+  if (undowidget != this || (!undocut && !undoinsert && !mCanUndo))
+    return 0;
+  
+  int ilen = undocut;
+  int xlen = undoinsert;
+  int b = undoat - xlen;
+  
+  if (xlen && undoyankcut && !ilen) {
+    ilen = undoyankcut;
+  }
+  
+  if (xlen && ilen) {
+    undobuffersize(ilen + 1);
+    undobuffer[ilen] = 0;
+    char *tmp = strdup(undobuffer);
+    replace(b, undoat, tmp);
+    if (cursorPos)
+      *cursorPos = mCursorPosHint;
+    free(tmp);
+  } else if (xlen) {
+    remove(b, undoat);
+    if (cursorPos)
+      *cursorPos = mCursorPosHint;
+  } else if (ilen) {
+    undobuffersize(ilen + 1);
+    undobuffer[ilen] = 0;
+    insert(undoat, undobuffer);
+    if (cursorPos)
+      *cursorPos = mCursorPosHint;
+    undoyankcut = 0;
+  }
+  
+  return 1;
+}
+
+
+/*
+ Set a flag if undo function will work.
+ */
+void Fl_Text_Buffer::canUndo(char flag)
+{
+  mCanUndo = flag;
+  // disabling undo also clears the last undo operation!
+  if (!mCanUndo && undowidget==this) 
+    undowidget = 0;
+}
+
+
+/*
+ Change the tab width. This will cause a couple of callbacks and a complete 
+ redisplay. 
+ Matt: I am not entirely sure why we need to trigger callbacks because
+ tabs are only a graphical hint, not changing any text at all, but I leave
+ this in here for back compatibility. 
+ */
+void Fl_Text_Buffer::tab_distance(int tabDist)
+{
+  /* First call the pre-delete callbacks with the previous tab setting 
+   still active. */
+  call_predelete_callbacks(0, mLength);
+  
+  /* Change the tab setting */
+  mTabDist = tabDist;
+  
+  /* Force any display routines to redisplay everything (unfortunately,
+   this means copying the whole buffer contents to provide "deletedText" */
+  const char *deletedText = text();
+  call_modify_callbacks(0, mLength, mLength, 0, deletedText);
+  free((void *) deletedText);
+}
+
+
+/*
+ Select a range of text.
+ Start and End must be at a character boundary.
+ */
+void Fl_Text_Buffer::select(int start, int end)
+{
+  IS_UTF8_ALIGNED2(this, (start))
+  IS_UTF8_ALIGNED2(this, (end))  
+  
+  Fl_Text_Selection oldSelection = mPrimary;
+  
+  mPrimary.set(start, end);
+  redisplay_selection(&oldSelection, &mPrimary);
+}
+
+
+/*
+ Clear the primary selection.
+ */
+void Fl_Text_Buffer::unselect()
+{
+  Fl_Text_Selection oldSelection = mPrimary;
+  
+  mPrimary.mSelected = 0;
+  redisplay_selection(&oldSelection, &mPrimary);
+}
+
+  
+/*
+ Return the primary selection range.
+ */
+int Fl_Text_Buffer::selection_position(int *start, int *end)
+{
+  return mPrimary.position(start, end);
+}
+
+
+/*
+ Return a copy of the selected text.
+ */
+char *Fl_Text_Buffer::selection_text()
+{
+  return selection_text_(&mPrimary);
+}
+
+
+/*
+ Remove the selected text.
+ */
+void Fl_Text_Buffer::remove_selection()
+{
+  remove_selection_(&mPrimary);
+}
+
+
+/*
+ Replace the selected text.
+ */
+void Fl_Text_Buffer::replace_selection(const char *text)
+{
+  replace_selection_(&mPrimary, text);
+}
+
+
+/*
+ Select text.
+ Start and End must be at a character boundary.
+ */
+void Fl_Text_Buffer::secondary_select(int start, int end)
+{
+  Fl_Text_Selection oldSelection = mSecondary;
+  
+  mSecondary.set(start, end);
+  redisplay_selection(&oldSelection, &mSecondary);
+}
+
+
+/*
+ Deselect text.
+ */
+void Fl_Text_Buffer::secondary_unselect()
+{
+  Fl_Text_Selection oldSelection = mSecondary;
+  
+  mSecondary.mSelected = 0;
+  redisplay_selection(&oldSelection, &mSecondary);
+}
+
+  
+/*
+ Return the selected range.
+ */
+int Fl_Text_Buffer::secondary_selection_position(int *start, int *end)
+{
+  return mSecondary.position(start, end);
+}
+
+
+/*
+ Return a copy of the text in this selection.
+ */
+char *Fl_Text_Buffer::secondary_selection_text()
+{
+  return selection_text_(&mSecondary);
+}
+
+
+/*
+ Remove the selected text.
+ */
+void Fl_Text_Buffer::remove_secondary_selection()
+{
+  remove_selection_(&mSecondary);
+}
+
+
+/*
+ Replace selected text.
+ */
+void Fl_Text_Buffer::replace_secondary_selection(const char *text)
+{
+  replace_selection_(&mSecondary, text);
+}
+
+
+/*
+ Highlight a range of text.
+ Start and End must be at a character boundary.
+ */
+void Fl_Text_Buffer::highlight(int start, int end)
+{
+  Fl_Text_Selection oldSelection = mHighlight;
+  
+  mHighlight.set(start, end);
+  redisplay_selection(&oldSelection, &mHighlight);
+}
+
+
+/*
+ Remove text highlighting.
+ */
+void Fl_Text_Buffer::unhighlight()
+{
+  Fl_Text_Selection oldSelection = mHighlight;
+  
+  mHighlight.mSelected = 0;
+  redisplay_selection(&oldSelection, &mHighlight);
+}
+
+  
+/*
+ Return position of highlight.
+ */
+int Fl_Text_Buffer::highlight_position(int *start, int *end)
+{
+  return mHighlight.position(start, end);
+}
+
+
+/*
+ Return a copy of highlighted text.
+ */
+char *Fl_Text_Buffer::highlight_text()
+{
+  return selection_text_(&mHighlight);
+}
+
+
+/*
+ Add a callback that is called whenever text is modified.
+ */
+void Fl_Text_Buffer::add_modify_callback(Fl_Text_Modify_Cb bufModifiedCB,
+					 void *cbArg)
+{
+  Fl_Text_Modify_Cb *newModifyProcs =
+  new Fl_Text_Modify_Cb[mNModifyProcs + 1];
+  void **newCBArgs = new void *[mNModifyProcs + 1];
+  for (int i = 0; i < mNModifyProcs; i++) {
+    newModifyProcs[i + 1] = mModifyProcs[i];
+    newCBArgs[i + 1] = mCbArgs[i];
+  }
+  if (mNModifyProcs != 0) {
+    delete[]mModifyProcs;
+    delete[]mCbArgs;
+  }
+  newModifyProcs[0] = bufModifiedCB;
+  newCBArgs[0] = cbArg;
+  mNModifyProcs++;
+  mModifyProcs = newModifyProcs;
+  mCbArgs = newCBArgs;
+}
+
+
+/*
+ Remove a callback.
+ */
+void Fl_Text_Buffer::remove_modify_callback(Fl_Text_Modify_Cb bufModifiedCB, 
+                                            void *cbArg)
+{
+  int i, toRemove = -1;
+  
+  /* find the matching callback to remove */
+  for (i = 0; i < mNModifyProcs; i++) {
+    if (mModifyProcs[i] == bufModifiedCB && mCbArgs[i] == cbArg) {
+      toRemove = i;
+      break;
+    }
+  }
+  if (toRemove == -1) {
+    Fl::error
+    ("Fl_Text_Buffer::remove_modify_callback(): Can't find modify CB to remove");
+    return;
+  }
+  
+  /* Allocate new lists for remaining callback procs and args (if
+   any are left) */
+  mNModifyProcs--;
+  if (mNModifyProcs == 0) {
+    mNModifyProcs = 0;
+    delete[]mModifyProcs;
+    mModifyProcs = NULL;
+    delete[]mCbArgs;
+    mCbArgs = NULL;
+    return;
+  }
+  Fl_Text_Modify_Cb *newModifyProcs = new Fl_Text_Modify_Cb[mNModifyProcs];
+  void **newCBArgs = new void *[mNModifyProcs];
+  
+  /* copy out the remaining members and free the old lists */
+  for (i = 0; i < toRemove; i++) {
+    newModifyProcs[i] = mModifyProcs[i];
+    newCBArgs[i] = mCbArgs[i];
+  }
+  for (; i < mNModifyProcs; i++) {
+    newModifyProcs[i] = mModifyProcs[i + 1];
+    newCBArgs[i] = mCbArgs[i + 1];
+  }
+  delete[]mModifyProcs;
+  delete[]mCbArgs;
+  mModifyProcs = newModifyProcs;
+  mCbArgs = newCBArgs;
+}
+
+
+/*
+ Add a callback that is called before deleting text.
+ */
+void Fl_Text_Buffer::add_predelete_callback(Fl_Text_Predelete_Cb bufPreDeleteCB, 
+                                            void *cbArg)
+{
+  Fl_Text_Predelete_Cb *newPreDeleteProcs =
+  new Fl_Text_Predelete_Cb[mNPredeleteProcs + 1];
+  void **newCBArgs = new void *[mNPredeleteProcs + 1];
+  for (int i = 0; i < mNPredeleteProcs; i++) {
+    newPreDeleteProcs[i + 1] = mPredeleteProcs[i];
+    newCBArgs[i + 1] = mPredeleteCbArgs[i];
+  }
+  if (!mNPredeleteProcs != 0) {
+    delete[]mPredeleteProcs;
+    delete[]mPredeleteCbArgs;
+  }
+  newPreDeleteProcs[0] = bufPreDeleteCB;
+  newCBArgs[0] = cbArg;
+  mNPredeleteProcs++;
+  mPredeleteProcs = newPreDeleteProcs;
+  mPredeleteCbArgs = newCBArgs;
+}
+
+
+/*
+ Remove a callback.
+ */
+void Fl_Text_Buffer::remove_predelete_callback(Fl_Text_Predelete_Cb bufPreDeleteCB, void *cbArg)
+{
+  int i, toRemove = -1;
+  /* find the matching callback to remove */
+  for (i = 0; i < mNPredeleteProcs; i++) {
+    if (mPredeleteProcs[i] == bufPreDeleteCB &&
+	mPredeleteCbArgs[i] == cbArg) {
+      toRemove = i;
+      break;
+    }
+  }
+  if (toRemove == -1) {
+    Fl::error
+    ("Fl_Text_Buffer::remove_predelete_callback(): Can't find pre-delete CB to remove");
+    return;
+  }
+  
+  /* Allocate new lists for remaining callback procs and args (if
+   any are left) */
+  mNPredeleteProcs--;
+  if (mNPredeleteProcs == 0) {
+    mNPredeleteProcs = 0;
+    delete[]mPredeleteProcs;
+    mPredeleteProcs = NULL;
+    delete[]mPredeleteCbArgs;
+    mPredeleteCbArgs = NULL;
+    return;
+  }
+  Fl_Text_Predelete_Cb *newPreDeleteProcs =
+  new Fl_Text_Predelete_Cb[mNPredeleteProcs];
+  void **newCBArgs = new void *[mNPredeleteProcs];
+  
+  /* copy out the remaining members and free the old lists */
+  for (i = 0; i < toRemove; i++) {
+    newPreDeleteProcs[i] = mPredeleteProcs[i];
+    newCBArgs[i] = mPredeleteCbArgs[i];
+  }
+  for (; i < mNPredeleteProcs; i++) {
+    newPreDeleteProcs[i] = mPredeleteProcs[i + 1];
+    newCBArgs[i] = mPredeleteCbArgs[i + 1];
+  }
+  delete[]mPredeleteProcs;
+  delete[]mPredeleteCbArgs;
+  mPredeleteProcs = newPreDeleteProcs;
+  mPredeleteCbArgs = newCBArgs;
+}
+
+
+/*
+ Return a copy of the line that contains a given index.
+ Pos must be at a character boundary.
+ */
+char *Fl_Text_Buffer::line_text(int pos) const {
+  return text_range(line_start(pos), line_end(pos));
+} 
+
+
+/*
+ Find the beginning of the line.
+ */
+int Fl_Text_Buffer::line_start(int pos) const 
+{
+  if (!findchar_backward(pos, '\n', &pos))
+    return 0;
+  return pos + 1;
+} 
+
+
+/*
+ Find the end of the line.
+ */
+int Fl_Text_Buffer::line_end(int pos) const {
+  if (!findchar_forward(pos, '\n', &pos))
+    pos = mLength;
+  return pos;
+} 
+
+
+/*
+ Find the beginning of a word.
+ NOT UNICODE SAFE.
+ */
+int Fl_Text_Buffer::word_start(int pos) const {
+  // FIXME: character is ucs-4
+  while (pos>0 && (isalnum(char_at(pos)) || char_at(pos) == '_')) {
+    pos = prev_char(pos);
+  } 
+  // FIXME: character is ucs-4
+  if (!(isalnum(char_at(pos)) || char_at(pos) == '_'))
+    pos = next_char(pos);
+  return pos;
+}
+
+
+/*
+ Find the end of a word.
+ NOT UNICODE SAFE.
+ */
+int Fl_Text_Buffer::word_end(int pos) const {
+  // FIXME: character is ucs-4
+  while (pos < length() && (isalnum(char_at(pos)) || char_at(pos) == '_'))
+  {
+    pos = next_char(pos);
+  }
+  return pos;
+}
+
+
+/*
+ Count the number of characters between two positions.
+ */
+int Fl_Text_Buffer::count_displayed_characters(int lineStartPos,
+					       int targetPos) const
+{
+  IS_UTF8_ALIGNED2(this, (lineStartPos))
+  IS_UTF8_ALIGNED2(this, (targetPos))
+  
+  int charCount = 0;
+  
+  int pos = lineStartPos;
+  while (pos < targetPos) {
+    pos = next_char(pos);
+    charCount++;
+  }
+  return charCount;
+} 
+
+
+/*
+ Skip ahead a number of characters from a given index.
+ This function breaks early if it encounters a newline character.
+ */
+int Fl_Text_Buffer::skip_displayed_characters(int lineStartPos, int nChars)
+{
+  IS_UTF8_ALIGNED2(this, (lineStartPos))
+
+  int pos = lineStartPos;
+  
+  for (int charCount = 0; charCount < nChars && pos < mLength; charCount++) {
+    unsigned int c = char_at(pos);
+    if (c == '\n')
+      return pos;
+    pos = next_char(pos);
+  }
+  return pos;
+}
+
+
+/*
+ Count the number of newline characters between start and end.
+ startPos and endPos must be at a character boundary.
+ This function is optimized for speed by not using UTF-8 calls.
+ */
+int Fl_Text_Buffer::count_lines(int startPos, int endPos) const {
+  IS_UTF8_ALIGNED2(this, (startPos))
+  IS_UTF8_ALIGNED2(this, (endPos))
+  
+  int gapLen = mGapEnd - mGapStart;
+  int lineCount = 0;
+  
+  int pos = startPos;
+  while (pos < mGapStart)
+  {
+    if (pos == endPos)
+      return lineCount;
+    if (mBuf[pos++] == '\n')
+      lineCount++;
+  } 
+  while (pos < mLength) {
+    if (pos == endPos)
+      return lineCount;
+    if (mBuf[pos++ + gapLen] == '\n')
+      lineCount++;
+  }
+  return lineCount;
+}
+
+
+/*
+ Skip to the first character, n lines ahead.
+ StartPos must be at a character boundary.
+ This function is optimized for speed by not using UTF-8 calls.
+ */
+int Fl_Text_Buffer::skip_lines(int startPos, int nLines)
+{
+  IS_UTF8_ALIGNED2(this, (startPos))
+  
+  if (nLines == 0)
+    return startPos;
+  
+  int gapLen = mGapEnd - mGapStart;
+  int pos = startPos;
+  int lineCount = 0;
+  while (pos < mGapStart) {
+    if (mBuf[pos++] == '\n') {
+      lineCount++;
+      if (lineCount == nLines) {
+        IS_UTF8_ALIGNED2(this, (pos))
+	return pos;
+      }
+    }
+  }
+  while (pos < mLength) {
+    if (mBuf[pos++ + gapLen] == '\n') {
+      lineCount++;
+      if (lineCount >= nLines) {
+        IS_UTF8_ALIGNED2(this, (pos))
+	return pos;
+      }
+    }
+  }
+  IS_UTF8_ALIGNED2(this, (pos))
+  return pos;
+}
+
+
+/*
+ Skip to the first character, n lines back.
+ StartPos must be at a character boundary.
+ This function is optimized for speed by not using UTF-8 calls.
+ */
+int Fl_Text_Buffer::rewind_lines(int startPos, int nLines)
+{
+  IS_UTF8_ALIGNED2(this, (startPos))
+  
+  int pos = startPos - 1;
+  if (pos <= 0)
+    return 0;
+  
+  int gapLen = mGapEnd - mGapStart;
+  int lineCount = -1;
+  while (pos >= mGapStart) {
+    if (mBuf[pos + gapLen] == '\n') {
+      if (++lineCount >= nLines) {
+        IS_UTF8_ALIGNED2(this, (pos+1))
+	return pos + 1;
+      }
+    }
+    pos--;
+  }
+  while (pos >= 0) {
+    if (mBuf[pos] == '\n') {
+      if (++lineCount >= nLines) {
+        IS_UTF8_ALIGNED2(this, (pos+1))
+	return pos + 1;
+      }
+    }
+    pos--;
+  }
+  return 0;
+}
+
+
+/*
+ Find a matching string in the buffer.
+ */
+int Fl_Text_Buffer::search_forward(int startPos, const char *searchString,
+				   int *foundPos, int matchCase) const 
+{
+  IS_UTF8_ALIGNED2(this, (startPos))
+  IS_UTF8_ALIGNED(searchString)
+  
+  if (!searchString)
+    return 0;
+  int bp;
+  const char *sp;
+  if (matchCase) {
+    while (startPos < length()) {
+      bp = startPos;
+      sp = searchString;
+      for (;;) {
+        char c = *sp;
+        // we reached the end of the "needle", so we found the string!
+        if (!c) {
+          *foundPos = startPos;
+          return 1;
+        }
+        int l = fl_utf8len1(c);
+        if (memcmp(sp, address(bp), l))
+          break;
+        sp += l; bp += l;
+      }
+      startPos = next_char(startPos);
+    }
+  } else {
+    while (startPos < length()) {
+      bp = startPos;
+      sp = searchString;
+      for (;;) {
+        // we reached the end of the "needle", so we found the string!
+        if (!*sp) {
+          *foundPos = startPos;
+          return 1;
+        }
+        int l;
+        unsigned int b = char_at(bp);
+        unsigned int s = fl_utf8decode(sp, 0, &l);
+        if (fl_tolower(b)!=fl_tolower(s))
+          break;
+        sp += l; 
+        bp = next_char(bp);
+      }
+      startPos = next_char(startPos);
+    }
+  }  
+  return 0;
+}
+
+int Fl_Text_Buffer::search_backward(int startPos, const char *searchString,
+				    int *foundPos, int matchCase) const 
+{
+  IS_UTF8_ALIGNED2(this, (startPos))
+  IS_UTF8_ALIGNED(searchString)
+  
+  if (!searchString)
+    return 0;
+  int bp;
+  const char *sp;
+  if (matchCase) {
+    while (startPos >= 0) {
+      bp = startPos;
+      sp = searchString;
+      for (;;) {
+        char c = *sp;
+        // we reached the end of the "needle", so we found the string!
+        if (!c) {
+          *foundPos = startPos;
+          return 1;
+        }
+        int l = fl_utf8len1(c);
+        if (memcmp(sp, address(bp), l))
+          break;
+        sp += l; bp += l;
+      }
+      startPos = prev_char(startPos);
+    }
+  } else {
+    while (startPos >= 0) {
+      bp = startPos;
+      sp = searchString;
+      for (;;) {
+        // we reached the end of the "needle", so we found the string!
+        if (!*sp) {
+          *foundPos = startPos;
+          return 1;
+        }
+        int l;
+        unsigned int b = char_at(bp);
+        unsigned int s = fl_utf8decode(sp, 0, &l);
+        if (fl_tolower(b)!=fl_tolower(s))
+          break;
+        sp += l; 
+        bp = next_char(bp);
+      }
+      startPos = prev_char(startPos);
+    }
+  }  
+  return 0;
+}
+
+
+
+/*
+ Insert a string into the buffer.
+ Pos must be at a character boundary. Text must be a correct UTF-8 string.
+ */
+int Fl_Text_Buffer::insert_(int pos, const char *text)
+{
+  if (!text || !*text)
+    return 0;
+  
+  int insertedLength = strlen(text);
+  
+  /* Prepare the buffer to receive the new text.  If the new text fits in
+   the current buffer, just move the gap (if necessary) to where
+   the text should be inserted.  If the new text is too large, reallocate
+   the buffer with a gap large enough to accomodate the new text and a
+   gap of mPreferredGapSize */
+  if (insertedLength > mGapEnd - mGapStart)
+    reallocate_with_gap(pos, insertedLength + mPreferredGapSize);
+  else if (pos != mGapStart)
+    move_gap(pos);
+  
+  /* Insert the new text (pos now corresponds to the start of the gap) */
+  memcpy(&mBuf[pos], text, insertedLength);
+  mGapStart += insertedLength;
+  mLength += insertedLength;
+  update_selections(pos, 0, insertedLength);
+  
+  if (mCanUndo) {
+    if (undowidget == this && undoat == pos && undoinsert) {
+      undoinsert += insertedLength;
+    } else {
+      undoinsert = insertedLength;
+      undoyankcut = (undoat == pos) ? undocut : 0;
+    }
+    undoat = pos + insertedLength;
+    undocut = 0;
+    undowidget = this;
+  }
+  
+  return insertedLength;
+}
+
+
+/*
+ Remove a string from the buffer.
+ Unicode safe. Start and end must be at a character boundary.
+ */
+void Fl_Text_Buffer::remove_(int start, int end)
+{
+  /* if the gap is not contiguous to the area to remove, move it there */
+  
+  if (mCanUndo) {
+    if (undowidget == this && undoat == end && undocut) {
+      undobuffersize(undocut + end - start + 1);
+      memmove(undobuffer + end - start, undobuffer, undocut);
+      undocut += end - start;
+    } else {
+      undocut = end - start;
+      undobuffersize(undocut);
+    }
+    undoat = start;
+    undoinsert = 0;
+    undoyankcut = 0;
+    undowidget = this;
+  }
+  
+  if (start > mGapStart) {
+    if (mCanUndo)
+      memcpy(undobuffer, mBuf + (mGapEnd - mGapStart) + start,
+	     end - start);
+    move_gap(start);
+  } else if (end < mGapStart) {
+    if (mCanUndo)
+      memcpy(undobuffer, mBuf + start, end - start);
+    move_gap(end);
+  } else {
+    int prelen = mGapStart - start;
+    if (mCanUndo) {
+      memcpy(undobuffer, mBuf + start, prelen);
+      memcpy(undobuffer + prelen, mBuf + mGapEnd, end - start - prelen);
+    }
+  }
+  
+  /* expand the gap to encompass the deleted characters */
+  mGapEnd += end - mGapStart;
+  mGapStart -= mGapStart - start;
+  
+  /* update the length */
+  mLength -= end - start;
+  
+  /* fix up any selections which might be affected by the change */
+  update_selections(start, end - start, 0);
+}
+
+  
+/*
+ simple setter.
+ Unicode safe. Start and end must be at a character boundary.
+ */
+void Fl_Text_Selection::set(int startpos, int endpos)
+{
+  mSelected = startpos != endpos;
+  mStart = min(startpos, endpos);
+  mEnd = max(startpos, endpos);
+}
+
+
+/*
+ simple getter.
+ Unicode safe. Start and end will be at a character boundary.
+ */
+int Fl_Text_Selection::position(int *startpos, int *endpos) const {
+  if (!mSelected)
+    return 0;
+  *startpos = mStart;
+  *endpos = mEnd;
+  
+  return 1;
+} 
+
+
+/*
+ Return if a position is inside the selected area.
+ Unicode safe. Pos must be at a character boundary.
+ */
+int Fl_Text_Selection::includes(int pos) const {
+  return (selected() && pos >= start() && pos < end() );
+}
+
+
+/*
+ Return a duplicate of the selected text, or an empty string.
+ Unicode safe.
+ */
+char *Fl_Text_Buffer::selection_text_(Fl_Text_Selection * sel) const {
+  int start, end;
+  
+  /* If there's no selection, return an allocated empty string */
+  if (!sel->position(&start, &end))
+  {
+    char *s = (char *) malloc(1);
+    *s = '\0';
+    return s;
+  }
+  
+  /* Return the selected range */
+    return text_range(start, end);
+}
+
+
+/*
+ Remove the selected text.
+ Unicode safe.
+ */
+void Fl_Text_Buffer::remove_selection_(Fl_Text_Selection * sel)
+{
+  int start, end;
+  
+  if (!sel->position(&start, &end))
+    return;
+  remove(start, end);
+  //undoyankcut = undocut;
+}
+
+
+/*
+ Replace selection with text.
+ Unicode safe.
+ */
+void Fl_Text_Buffer::replace_selection_(Fl_Text_Selection * sel,
+					const char *text)
+{
+  Fl_Text_Selection oldSelection = *sel;
+  
+  /* If there's no selection, return */
+  int start, end;
+  if (!sel->position(&start, &end))
+    return;
+  
+  /* Do the appropriate type of replace */
+    replace(start, end, text);
+  
+  /* Unselect (happens automatically in BufReplace, but BufReplaceRect
+   can't detect when the contents of a selection goes away) */
+  sel->mSelected = 0;
+  redisplay_selection(&oldSelection, sel);
+}
+
+  
+/*
+ Call all callbacks.
+ Unicode safe.
+ */
+void Fl_Text_Buffer::call_modify_callbacks(int pos, int nDeleted,
+					   int nInserted, int nRestyled,
+					   const char *deletedText) const {
+  IS_UTF8_ALIGNED2(this, pos)
+  for (int i = 0; i < mNModifyProcs; i++)
+    (*mModifyProcs[i]) (pos, nInserted, nDeleted, nRestyled,
+			deletedText, mCbArgs[i]);
+} 
+
+
+/*
+ Call all callbacks.
+ Unicode safe.
+ */
+void Fl_Text_Buffer::call_predelete_callbacks(int pos, int nDeleted) const {
+  for (int i = 0; i < mNPredeleteProcs; i++)
+    (*mPredeleteProcs[i]) (pos, nDeleted, mPredeleteCbArgs[i]);
+} 
+
+
+/*
+ Redisplay a new selected area.
+ Unicode safe.
+ */
+void Fl_Text_Buffer::redisplay_selection(Fl_Text_Selection *
+					   oldSelection,
+					   Fl_Text_Selection *
+					   newSelection) const
+{
+  int oldStart, oldEnd, newStart, newEnd, ch1Start, ch1End, ch2Start,
+  ch2End;
+  
+  /* If either selection is rectangular, add an additional character to
+   the end of the selection to request the redraw routines to wipe out
+   the parts of the selection beyond the end of the line */
+  oldStart = oldSelection->mStart;
+  newStart = newSelection->mStart;
+  oldEnd = oldSelection->mEnd;
+  newEnd = newSelection->mEnd;
+  
+  /* If the old or new selection is unselected, just redisplay the
+   single area that is (was) selected and return */
+  if (!oldSelection->mSelected && !newSelection->mSelected)
+    return;
+  if (!oldSelection->mSelected)
+  {
+    call_modify_callbacks(newStart, 0, 0, newEnd - newStart, NULL);
+    return;
+  }
+  if (!newSelection->mSelected) {
+    call_modify_callbacks(oldStart, 0, 0, oldEnd - oldStart, NULL);
+    return;
+  }
+  
+  /* If the selections are non-contiguous, do two separate updates
+   and return */
+  if (oldEnd < newStart || newEnd < oldStart) {
+    call_modify_callbacks(oldStart, 0, 0, oldEnd - oldStart, NULL);
+    call_modify_callbacks(newStart, 0, 0, newEnd - newStart, NULL);
+    return;
+  }
+  
+  /* Otherwise, separate into 3 separate regions: ch1, and ch2 (the two
+   changed areas), and the unchanged area of their intersection,
+   and update only the changed area(s) */
+  ch1Start = min(oldStart, newStart);
+  ch2End = max(oldEnd, newEnd);
+  ch1End = max(oldStart, newStart);
+  ch2Start = min(oldEnd, newEnd);
+  if (ch1Start != ch1End)
+    call_modify_callbacks(ch1Start, 0, 0, ch1End - ch1Start, NULL);
+  if (ch2Start != ch2End)
+    call_modify_callbacks(ch2Start, 0, 0, ch2End - ch2Start, NULL);
+}
+
+
+/*
+ Move the gap around without changing buffer content.
+ Unicode safe. Pos must be at a character boundary.
+ */
+void Fl_Text_Buffer::move_gap(int pos)
+{
+  int gapLen = mGapEnd - mGapStart;
+  
+  if (pos > mGapStart)
+    memmove(&mBuf[mGapStart], &mBuf[mGapEnd], pos - mGapStart);
+  else
+    memmove(&mBuf[pos + gapLen], &mBuf[pos], mGapStart - pos);
+  mGapEnd += pos - mGapStart;
+  mGapStart += pos - mGapStart;
+}
+
+
+/*
+ Create a larger gap.
+ Unicode safe. Start must be at a character boundary.
+ */
+void Fl_Text_Buffer::reallocate_with_gap(int newGapStart, int newGapLen)
+{
+  char *newBuf = (char *) malloc(mLength + newGapLen);
+  int newGapEnd = newGapStart + newGapLen;
+  
+  if (newGapStart <= mGapStart) {
+    memcpy(newBuf, mBuf, newGapStart);
+    memcpy(&newBuf[newGapEnd], &mBuf[newGapStart],
+	   mGapStart - newGapStart);
+    memcpy(&newBuf[newGapEnd + mGapStart - newGapStart],
+	   &mBuf[mGapEnd], mLength - mGapStart);
+  } else {			/* newGapStart > mGapStart */
+    memcpy(newBuf, mBuf, mGapStart);
+    memcpy(&newBuf[mGapStart], &mBuf[mGapEnd], newGapStart - mGapStart);
+    memcpy(&newBuf[newGapEnd],
+	   &mBuf[mGapEnd + newGapStart - mGapStart],
+	   mLength - newGapStart);
+  }
+  free((void *) mBuf);
+  mBuf = newBuf;
+  mGapStart = newGapStart;
+  mGapEnd = newGapEnd;
+  }
+
+
+/*
+ Update selection range if characters were inserted.
+ Unicode safe. Pos must be at a character boundary.
+ */
+void Fl_Text_Buffer::update_selections(int pos, int nDeleted,
+				       int nInserted)
+{
+  mPrimary.update(pos, nDeleted, nInserted);
+  mSecondary.update(pos, nDeleted, nInserted);
+  mHighlight.update(pos, nDeleted, nInserted);
+}
+
+
+// unicode safe, assuming the arguments are on character boundaries
+void Fl_Text_Selection::update(int pos, int nDeleted, int nInserted)
+{
+  if (!mSelected || pos > mEnd)
+    return;
+  if (pos + nDeleted <= mStart) {
+    mStart += nInserted - nDeleted;
+    mEnd += nInserted - nDeleted;
+  } else if (pos <= mStart && pos + nDeleted >= mEnd) {
+    mStart = pos;
+    mEnd = pos;
+    mSelected = 0;
+  } else if (pos <= mStart && pos + nDeleted < mEnd) {
+    mStart = pos;
+    mEnd = nInserted + mEnd - nDeleted;
+  } else if (pos < mEnd) {
+    mEnd += nInserted - nDeleted;
+    if (mEnd <= mStart)
+      mSelected = 0;
+  }
+}
+
+
+/*
+ Find a UCS-4 character.
+ StartPos must be at a character boundary, searchChar is UCS-4 encoded.
+ */
+int Fl_Text_Buffer::findchar_forward(int startPos, unsigned searchChar,
+				     int *foundPos) const 
+{
+  if (startPos >= mLength) {
+    *foundPos = mLength;
+    return 0;
+  }
+  
+  if (startPos<0)
+    startPos = 0;
+  
+  for ( ; startPos<mLength; startPos = next_char(startPos)) {
+    if (searchChar == char_at(startPos)) {
+      *foundPos = startPos;
+      return 1;
+    }
+  }
+  
+  *foundPos = mLength;
+  return 0;
+}
+
+  
+/*
+ Find a UCS-4 character.
+ StartPos must be at a character boundary, searchChar is UCS-4 encoded.
+ */
+int Fl_Text_Buffer::findchar_backward(int startPos, unsigned int searchChar,
+				      int *foundPos) const {
+  if (startPos <= 0) {
+    *foundPos = 0;
+    return 0;
+  }
+  
+  if (startPos > mLength)
+    startPos = mLength;
+  
+  for (startPos = prev_char(startPos); startPos>=0; startPos = prev_char(startPos)) {
+    if (searchChar == char_at(startPos)) {
+      *foundPos = startPos;
+      return 1;
+    }
+  }
+  
+  *foundPos = 0;
+  return 0;
+}
+
+//#define EXAMPLE_ENCODING // shows how to process any encoding for which a decoding function exists
+#ifdef EXAMPLE_ENCODING 
+
+// returns the UCS equivalent of *p in CP1252 and advances p by 1
+unsigned cp1252toucs(char* &p)
+{
+  // Codes 0x80..0x9f from the Microsoft CP1252 character set, translated
+  // to Unicode
+  static unsigned cp1252[32] = {
+    0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021,
+    0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f,
+    0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014,
+    0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178
+  };
+  unsigned char uc = *(unsigned char*)p;
+  p++;
+  return (uc < 0x80 || uc >= 0xa0 ? uc : cp1252[uc - 0x80]);
+}
+
+// returns the UCS equivalent of *p in UTF-16 and advances p by 2 (or more for surrogates)
+unsigned utf16toucs(char* &p)
+{
+  union {
+#if WORDS_BIGENDIAN
+    struct { unsigned char a, b;} chars;
+#else
+    struct { unsigned char b, a;} chars;
+#endif
+    U16 short_val;
+  } u;
+  u.chars.a = *(unsigned char*)p++;
+  u.chars.b = *(unsigned char*)p++;
+  return u.short_val;
+}
+
+// filter that produces, from an input stream fed by reading from fp,
+// a UTF-8-encoded output stream written in buffer.
+// Input can be any (e.g., 8-bit, UTF-16) encoding.
+// Output is true UTF-8.
+// p_trf points to a function that transforms encoded byte(s) into one UCS
+// and that increases the pointer by the adequate quantity
+static int general_input_filter(char *buffer, int buflen, 
+				 char *line, int sline, char* &endline, 
+				 unsigned (*p_trf)(char* &),
+				 FILE *fp)
+{
+  char *p, *q, multibyte[5];
+  int lq, r, offset;
+  p = endline = line;
+  q = buffer;
+  while (q < buffer + buflen) {
+    if (p >= endline) {
+      r = fread(line, 1, sline, fp);
+      endline = line + r; 
+      if (r == 0) return q - buffer;
+      p = line;
+    }
+    if (q + 4 /*max width of utf-8 char*/ > buffer + buflen) {
+      memmove(line, p, endline - p);
+      endline -= (p - line);
+      return q - buffer;
+    }
+    lq = fl_utf8encode( p_trf(p), multibyte );
+    memcpy(q, multibyte, lq);
+    q += lq; 
+  }
+  memmove(line, p, endline - p);
+  endline -= (p - line);
+  return q - buffer;
+}
+#endif // EXAMPLE_ENCODING
+
+/*
+ filter that produces, from an input stream fed by reading from fp,
+ a UTF-8-encoded output stream written in buffer.
+ Input can be UTF-8. If it is not, it is decoded with CP1252.
+ Output is UTF-8.
+ *input_was_changed is set to true if the input was not strict UTF-8 so output
+ differs from input.
+ */
+static int utf8_input_filter(char *buffer, int buflen, char *line, int sline, char* &endline, 
+	      FILE *fp, int *input_was_changed)
+{
+  char *p, *q, multibyte[5];
+  int l, lp, lq, r;
+  unsigned u;
+  p = endline = line;
+  q = buffer;
+  while (q < buffer + buflen) {
+    if (p >= endline) {
+      r = fread(line, 1, sline, fp);
+      endline = line + r; 
+      if (r == 0) return q - buffer;
+      p = line;
+    }
+    l = fl_utf8len1(*p);
+    if (p + l > endline) {
+      memmove(line, p, endline - p);
+      endline -= (p - line);
+      r = fread(endline, 1, sline - (endline - line), fp);
+      endline += r;
+      p = line;
+      if (endline - line < l) break;
+    }
+    while ( l > 0) {
+      u = fl_utf8decode(p, p+l, &lp);
+      lq = fl_utf8encode(u, multibyte);
+      if (lp != l || lq != l) *input_was_changed = true;
+      if (q + lq > buffer + buflen) {
+	memmove(line, p, endline - p);
+	endline -= (p - line);
+	return q - buffer;
+      }
+      memcpy(q, multibyte, lq);
+      q += lq; 
+      p += lp;
+      l -= lp;
+    }
+  }
+  memmove(line, p, endline - p);
+  endline -= (p - line);
+  return q - buffer;
+}
+
+const char *Fl_Text_Buffer::file_encoding_warning_message = 
+"Displayed text contains the UTF-8 transcoding\n"
+"of the input file which was not UTF-8 encoded.\n"
+"Some changes may have occurred.";
+
+/*
+ Insert text from a file.
+ Input file can be of various encodings according to what input fiter is used.
+ utf8_input_filter accepts UTF-8 or CP1252 as input encoding.
+ Output is always UTF-8.
+ */
+ int Fl_Text_Buffer::insertfile(const char *file, int pos, int buflen)
+{
+  FILE *fp;
+  if (!(fp = fl_fopen(file, "r")))
+    return 1;
+  char *buffer = new char[buflen + 1];  
+  char *endline, line[100];
+  int l;
+  input_file_was_transcoded = false;
+  endline = line;
+  while (true) {
+#ifdef EXAMPLE_ENCODING
+    // example of 16-bit encoding: UTF-16
+    l = general_input_filter(buffer, buflen, 
+				  line, sizeof(line), endline, 
+				  utf16toucs, // use cp1252toucs to read CP1252-encoded files
+				  fp);
+    input_file_was_transcoded = true;
+#else
+    l = utf8_input_filter(buffer, buflen, line, sizeof(line), endline, 
+			  fp, &input_file_was_transcoded);
+#endif
+    if (l == 0) break;
+    buffer[l] = 0;
+    insert(pos, buffer);
+    pos += l;
+    }
+  int e = ferror(fp) ? 2 : 0;
+  fclose(fp);
+  delete[]buffer;
+  if ( (!e) && input_file_was_transcoded && transcoding_warning_action) {
+    transcoding_warning_action(this);
+    }
+  return e;
+}
+
+
+/*
+ Write text to file.
+ Unicode safe.
+ */
+int Fl_Text_Buffer::outputfile(const char *file,
+			       int start, int end,
+			       int buflen) {
+  FILE *fp;
+  if (!(fp = fl_fopen(file, "w")))
+    return 1;
+  for (int n; (n = min(end - start, buflen)); start += n) {
+    const char *p = text_range(start, start + n);
+    int r = fwrite(p, 1, n, fp);
+    free((void *) p);
+    if (r != n)
+      break;
+  }
+  
+  int e = ferror(fp) ? 2 : 0;
+  fclose(fp);
+  return e;
+}
+
+
+/*
+ Return the previous character position.
+ Unicode safe.
+ */
+int Fl_Text_Buffer::prev_char_clipped(int pos) const
+{
+  if (pos<=0)
+    return 0;
+
+  IS_UTF8_ALIGNED2(this, (pos))  
+
+  char c;
+  do {
+    pos--;
+    if (pos==0)
+      return 0;
+    c = byte_at(pos);
+  } while ( (c&0xc0) == 0x80);
+  
+  IS_UTF8_ALIGNED2(this, (pos))  
+  return pos;
+}
+
+
+/*
+ Return the previous character position.
+ Returns -1 if the beginning of the buffer is reached.
+ */
+int Fl_Text_Buffer::prev_char(int pos) const
+{
+  if (pos==0) return -1;
+  return prev_char_clipped(pos);
+}
+
+
+/*
+ Return the next character position.
+ Returns length() if the end of the buffer is reached.
+ */
+int Fl_Text_Buffer::next_char(int pos) const
+{
+  IS_UTF8_ALIGNED2(this, (pos))  
+  int n = fl_utf8len1(byte_at(pos));
+  pos += n;
+  if (pos>=mLength)
+    return mLength;
+  IS_UTF8_ALIGNED2(this, (pos))  
+  return pos;
+}
+
+
+/*
+ Return the next character position.
+ If the end of the buffer is reached, it returns the current position.
+ */
+int Fl_Text_Buffer::next_char_clipped(int pos) const
+{
+  return next_char(pos);
+}
+
+/*
+ Align an index to the current UTF-8 boundary.
+ */
+int Fl_Text_Buffer::utf8_align(int pos) const 
+{
+  char c = byte_at(pos);
+  while ( (c&0xc0) == 0x80) {
+    pos--;
+    c = byte_at(pos);
+  }
+  return pos;
+}
+
+//
+// End of "$Id: Fl_Text_Buffer.cxx 8040 2010-12-15 17:38:39Z manolo $".
+//