// This version is starting off as just a copy of FXText.cpp that I can
// patch and change to support variable-height lines. I may merge
// my changes back into the original version sometime or I may fork this
// as a genuine new file - I do not know yet how things will work out!
// This was originally from FOX 1.2.9.
//
// I am really getting to think that the scope of changes I will need to make
// here will make it a clone-and-hack job rather than an inherit-and-override,
// if only because of delicacies about just what would remain in the base
// class and what would need re-implementing because not very much is
// made virtual.
//
//
//                                           Arthur Norman, September 2004
//                                                          January 2006
//                                                          January 2007

/* Signature: 493645c4 18-Jan-2007 */


/********************************************************************************
*                                                                               *
*                    M u l t i - L i ne   T e x t   O b j e c t                 *
*                                                                               *
*********************************************************************************
* Copyright (C) 1998,2005 by Jeroen van der Zijp.   All Rights Reserved.        *
*********************************************************************************
* This library is free software; you can redistribute it and/or                 *
* modify it under the terms of the GNU Lesser General Public                    *
* License as published by the Free Software Foundation;                         *
* version 2.1 of the License.                                                   *
*                                                                               *
* 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             *
* Lesser General Public License for more details.                               *
*                                                                               *
* You should have received a copy of the GNU Lesser 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.    *
*********************************************************************************
* $Id: FXMathText.cpp,v 1.299 2005/01/16 16:06:07 fox Exp $                         *
********************************************************************************/


#include "config.h"

#include "xincs.h"
#include "fxver.h"

#if FOX_MAJOR>1 || (FOX_MAJOR==1 && FOX_MINOR>=4)
// Fox 1.4.x is different from 1.2.x. I have not checked which
// of these the 1.3.x releases follow!
#define FOX14 1
#endif

#include "fxdefs.h"
#include "fxkeys.h"
#include "FXHash.h"

#ifdef FOX14
#include "FXThread.h"
#endif

#include "FXStream.h"
#include "FXString.h"
#include "FXRex.h"
#include "FXSize.h"
#include "FXPoint.h"
#include "FXRectangle.h"
#include "FXObject.h"
#include "FXRegistry.h"
#include "FXAccelTable.h"
#include "FXApp.h"
#include "FXDCWindow.h"
#include "FXFont.h"
#include "FXGIFIcon.h"
#include "FXScrollBar.h"
#include "FXMathText.h"
#include "FXInputDialog.h"
#include "FXReplaceDialog.h"
#include "FXSearchDialog.h"
#include "FXFile.h"
#include "icons.h"

#if (FOX_MINOR>4)
// The following could possibly turn into fxunicode.h one day.
#include "fxascii.h"

#define isspace(ch) Ascii::isSpace(ch)
#define isdigit(ch) Ascii::isDigit(ch)
#define toupper(ch) Ascii::toUpper(ch)
#define tolower(ch) Ascii::toLower(ch)
#endif

/*
  ACN Notes re introduction of support for maths display:
  - Space for maths is made by (ab)using the existing scheme where
    a line of text can end up wrapped to form several rows.
  - mouse identification within maths is not yet addressed AT ALL.
  - a Maths line that (say) needs the spaec of 4 rows will be
    represented as
        0x02 0x02 0x02 0x02 <data> 0x05 '\n'
    and each 0x02 is treated as starting a "row". Any draw operation
    steps up and draws all the rows involved. The <data> will not have
    and 0x02, 0x03 or '\n' bytes in it.
  - I will probably also want to mark the maths material with a style
    tag, but partly because I want to work with an existing interface
    that generates 0x02 and 0x05 to surround displayed maths I will
    start off using them rather than styles.
 */


/*
  Notes:
  - Line start array is one longer than number of visible lines.
  - We want both tab translated to spaces as well as tab-stops array.
  - Control characters in the buffer are OK (e.g. ^L)
  - Drag cursor should be same as normal one until drag starts!
  - Change of cursor only implies makePositionVisible() if done by user.
  - Breaking:
    Soft-hyphen     173  \xAD
    No break space  240  \xF0
  - Buffer layout:

    Content  :  A  B  C  .  .  .  .  .  .  .  .  D  E  F  G
    Position :  0  1  2 			 3  4  5  6    length=7
    Addresss :  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14    buffersize=7+11-3=15
             		 ^			 ^
	     		 |			 |
	     	      gapstart=3	       gapend=11       gaplen=11-3=8

    The gap is moved around the buffer so newly added text
    can be entered into the gap; when the gap becomes too small,
    the buffer is resized.  This gapped-buffer technique minimizes
    the number of resizes of the buffer, and minimizes the number
    of block moves.

    The tail end of the visrows array will look like:

    visrow[0]= 0: "Q R S T U V W \n"
    visrow[1]= 8: "X Y Z"
    visrow[2]=11: <no text>
    visrow[3]=11: <no text>            length = 11

    The last legal position is length = 11.

  - While resizing window, keep track of a position which should remain visible,
    i.e. toppos=rowStart(position).  The position is changed same as toppos, except during
    resize.
  - When changing text, if we're looking at the tail end of the buffer, avoid jumping
    the top lines when the content hight shrinks.
  - Add undo capability. (First undo will turn mod flag back off).
  - Add incremental search, search/replace, selection search.
  - Style table stuff.
  - Need to allow for one single routine to update style buffer same as text buffer
  - Suggested additional bindings:
    Ctl-F   Find
    Ctl-R   Replace
    Ctl-G   Find again (Shift-Ctl-G Find again backward)
    Ctl-T   Replace again (Shift-Ctl-G Replace again backward)
    Ctl-L   Goto line number
    Ctl-E   Goto selected
    Ctl-I   Indent (shift 1 character right)
    Ctl-U   Unindent (shift 1 character left)
    Ctl-Z   undo
    Ctl-Y   redo
    Ctl-M   Goto matching (Shift-Ctl-M backward)
    Insert  toggle overstrike mode
    Brace matching
  - Maybe put all keyboard bindings into accelerator table.
  - Variable cursorcol should take tabcolumns into account.
  - Italic fonts are bit problematic on border between selected/unselected text
    due to kerning.
  - Tab should work as tabcolumns columns when computing a column.
  - Need rectangular selection capability.
  - Perhaps split off buffer management into separate text buffer class (allows for multiple views).
  - Need to implement regex search/replace.
  - Add better support for subclassing (syntax coloring e.g.).
  - Add support for line numbers.
  - Improve book keeping based on line/column numbers, not rows/characters.
  - If there is a style table, the style buffer is used as index into the style table,
    allowing for up to 255 styles (style index==0 is the default style).
    The style member in the FXHiliteStyle struct is used for underlining, strikeouts,
    and other effects.
    If there is NO style table but there is a style buffer, the style buffer can still
    be used for underlining, strikeouts, and other effects.
  - Sending SEL_CHANGED is pretty useless; should only be sent AFTER text change,
    and void* should contain some sensible info.
  - When in overstrike mode and having a selection, entering a character should
    replace the selection, not delete the selection and then overstrike the character
    after the selection.
  - Middle mouse paste does not paste inside selection, and does not kill selection.
  - When pasting or dropping whole lines, insert at begin of line instead of at cursor;
    question:- how to know we're pasting whole lines?
  - Need block cursor when in overstrike mode.
  - Inserting lots of stuff should show cursor.
*/


#define MINSIZE   80                  // Minimum gap size
#define NVISROWS  24                  // Initial visible rows

#define TEXT_MASK   (TEXT_FIXEDWRAP|TEXT_WORDWRAP|TEXT_COLUMNWRAP|TEXT_OVERSTRIKE|TEXT_READONLY|TEXT_NO_TABS|TEXT_AUTOINDENT|TEXT_SHOWACTIVE)

using namespace FX;

/*******************************************************************************/

namespace FX {


// Map
FXDEFMAP(FXMathText) FXMathTextMap[]={
// Well I will leave all the selectors here as the ones used with FXText...
  FXMAPFUNC(SEL_PAINT,0,FXMathText::onPaint),
  FXMAPFUNC(SEL_UPDATE,0,FXMathText::onUpdate),
  FXMAPFUNC(SEL_MOTION,0,FXMathText::onMotion),
  FXMAPFUNC(SEL_DRAGGED,0,FXMathText::onDragged),
  FXMAPFUNC(SEL_TIMEOUT,FXMathText::ID_BLINK,FXMathText::onBlink),
  FXMAPFUNC(SEL_TIMEOUT,FXMathText::ID_AUTOSCROLL,FXMathText::onAutoScroll),
  FXMAPFUNC(SEL_TIMEOUT,FXMathText::ID_FLASH,FXMathText::onFlash),
  FXMAPFUNC(SEL_FOCUSIN,0,FXMathText::onFocusIn),
  FXMAPFUNC(SEL_FOCUSOUT,0,FXMathText::onFocusOut),
  FXMAPFUNC(SEL_BEGINDRAG,0,FXMathText::onBeginDrag),
  FXMAPFUNC(SEL_ENDDRAG,0,FXMathText::onEndDrag),
  FXMAPFUNC(SEL_LEFTBUTTONPRESS,0,FXMathText::onLeftBtnPress),
  FXMAPFUNC(SEL_LEFTBUTTONRELEASE,0,FXMathText::onLeftBtnRelease),
  FXMAPFUNC(SEL_MIDDLEBUTTONPRESS,0,FXMathText::onMiddleBtnPress),
  FXMAPFUNC(SEL_MIDDLEBUTTONRELEASE,0,FXMathText::onMiddleBtnRelease),
  FXMAPFUNC(SEL_RIGHTBUTTONPRESS,0,FXMathText::onRightBtnPress),
  FXMAPFUNC(SEL_RIGHTBUTTONRELEASE,0,FXMathText::onRightBtnRelease),
  FXMAPFUNC(SEL_UNGRABBED,0,FXMathText::onUngrabbed),
  FXMAPFUNC(SEL_DND_ENTER,0,FXMathText::onDNDEnter),
  FXMAPFUNC(SEL_DND_LEAVE,0,FXMathText::onDNDLeave),
  FXMAPFUNC(SEL_DND_DROP,0,FXMathText::onDNDDrop),
  FXMAPFUNC(SEL_DND_MOTION,0,FXMathText::onDNDMotion),
  FXMAPFUNC(SEL_DND_REQUEST,0,FXMathText::onDNDRequest),
  FXMAPFUNC(SEL_SELECTION_LOST,0,FXMathText::onSelectionLost),
  FXMAPFUNC(SEL_SELECTION_GAINED,0,FXMathText::onSelectionGained),
  FXMAPFUNC(SEL_SELECTION_REQUEST,0,FXMathText::onSelectionRequest),
  FXMAPFUNC(SEL_CLIPBOARD_LOST,0,FXMathText::onClipboardLost),
  FXMAPFUNC(SEL_CLIPBOARD_GAINED,0,FXMathText::onClipboardGained),
  FXMAPFUNC(SEL_CLIPBOARD_REQUEST,0,FXMathText::onClipboardRequest),
  FXMAPFUNC(SEL_KEYPRESS,0,FXMathText::onKeyPress),
  FXMAPFUNC(SEL_KEYRELEASE,0,FXMathText::onKeyRelease),
#ifdef FOX14
  FXMAPFUNC(SEL_QUERY_TIP,0,FXMathText::onQueryTip),
  FXMAPFUNC(SEL_QUERY_HELP,0,FXMathText::onQueryHelp),
#else
  FXMAPFUNC(SEL_UPDATE,FXMathText::ID_QUERY_HELP,FXMathText::onQueryHelp),
  FXMAPFUNC(SEL_UPDATE,FXMathText::ID_QUERY_TIP,FXMathText::onQueryTip),
#endif
  FXMAPFUNC(SEL_UPDATE,FXMathText::ID_TOGGLE_EDITABLE,FXMathText::onUpdToggleEditable),
  FXMAPFUNC(SEL_UPDATE,FXMathText::ID_TOGGLE_OVERSTRIKE,FXMathText::onUpdToggleOverstrike),
  FXMAPFUNC(SEL_UPDATE,FXMathText::ID_CURSOR_ROW,FXMathText::onUpdCursorRow),
  FXMAPFUNC(SEL_UPDATE,FXMathText::ID_CURSOR_COLUMN,FXMathText::onUpdCursorColumn),
  FXMAPFUNC(SEL_UPDATE,FXMathText::ID_CUT_SEL,FXMathText::onUpdHaveSelection),
  FXMAPFUNC(SEL_UPDATE,FXMathText::ID_COPY_SEL,FXMathText::onUpdHaveSelection),
  FXMAPFUNC(SEL_UPDATE,FXMathText::ID_PASTE_SEL,FXMathText::onUpdYes),
  FXMAPFUNC(SEL_UPDATE,FXMathText::ID_DELETE_SEL,FXMathText::onUpdHaveSelection),
  FXMAPFUNC(SEL_UPDATE,FXMathText::ID_SELECT_ALL,FXMathText::onUpdSelectAll),
  FXMAPFUNC(SEL_UPDATE,FXMathText::ID_UPPER_CASE,FXMathText::onUpdHaveSelection),
  FXMAPFUNC(SEL_UPDATE,FXMathText::ID_LOWER_CASE,FXMathText::onUpdHaveSelection),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_TOP,FXMathText::onCmdCursorTop),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_BOTTOM,FXMathText::onCmdCursorBottom),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_HOME,FXMathText::onCmdCursorHome),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_END,FXMathText::onCmdCursorEnd),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_RIGHT,FXMathText::onCmdCursorRight),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_LEFT,FXMathText::onCmdCursorLeft),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_UP,FXMathText::onCmdCursorUp),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_DOWN,FXMathText::onCmdCursorDown),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_WORD_LEFT,FXMathText::onCmdCursorWordLeft),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_WORD_RIGHT,FXMathText::onCmdCursorWordRight),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_WORD_START,FXMathText::onCmdCursorWordStart),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_WORD_END,FXMathText::onCmdCursorWordEnd),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_PAGEDOWN,FXMathText::onCmdCursorPageDown),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_PAGEUP,FXMathText::onCmdCursorPageUp),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_SCRNTOP,FXMathText::onCmdCursorScreenTop),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_SCRNBTM,FXMathText::onCmdCursorScreenBottom),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_SCRNCTR,FXMathText::onCmdCursorScreenCenter),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_PAR_HOME,FXMathText::onCmdCursorParHome),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_PAR_END,FXMathText::onCmdCursorParEnd),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_SCROLL_UP,FXMathText::onCmdScrollUp),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_SCROLL_DOWN,FXMathText::onCmdScrollDown),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_MARK,FXMathText::onCmdMark),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_EXTEND,FXMathText::onCmdExtend),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_OVERST_STRING,FXMathText::onCmdOverstString),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_INSERT_STRING,FXMathText::onCmdInsertString),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_INSERT_NEWLINE,FXMathText::onCmdInsertNewline),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_INSERT_TAB,FXMathText::onCmdInsertTab),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CUT_SEL,FXMathText::onCmdCutSel),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_COPY_SEL,FXMathText::onCmdCopySel),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_DELETE_SEL,FXMathText::onCmdDeleteSel),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_PASTE_SEL,FXMathText::onCmdPasteSel),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_PASTE_MIDDLE,FXMathText::onCmdPasteMiddle),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_SELECT_CHAR,FXMathText::onCmdSelectChar),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_SELECT_WORD,FXMathText::onCmdSelectWord),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_SELECT_LINE,FXMathText::onCmdSelectLine),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_SELECT_ALL,FXMathText::onCmdSelectAll),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_DESELECT_ALL,FXMathText::onCmdDeselectAll),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_BACKSPACE,FXMathText::onCmdBackspace),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_BACKSPACE_WORD,FXMathText::onCmdBackspaceWord),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_BACKSPACE_BOL,FXMathText::onCmdBackspaceBol),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_DELETE,FXMathText::onCmdDelete),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_DELETE_WORD,FXMathText::onCmdDeleteWord),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_DELETE_EOL,FXMathText::onCmdDeleteEol),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_DELETE_ALL,FXMathText::onCmdDeleteAll),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_DELETE_LINE,FXMathText::onCmdDeleteLine),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_TOGGLE_EDITABLE,FXMathText::onCmdToggleEditable),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_TOGGLE_OVERSTRIKE,FXMathText::onCmdToggleOverstrike),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_ROW,FXMathText::onCmdCursorRow),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_CURSOR_COLUMN,FXMathText::onCmdCursorColumn),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_SETSTRINGVALUE,FXMathText::onCmdSetStringValue),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_GETSTRINGVALUE,FXMathText::onCmdGetStringValue),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_UPPER_CASE,FXMathText::onCmdChangeCase),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_LOWER_CASE,FXMathText::onCmdChangeCase),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_GOTO_MATCHING,FXMathText::onCmdGotoMatching),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_GOTO_SELECTED,FXMathText::onCmdGotoSelected),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_GOTO_LINE,FXMathText::onCmdGotoLine),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_SELECT_MATCHING,FXMathText::onCmdSelectMatching),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_SEARCH,FXMathText::onCmdSearch),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_REPLACE,FXMathText::onCmdReplace),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_SEARCH_FORW,FXMathText::onCmdSearchNext),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_SEARCH_BACK,FXMathText::onCmdSearchNext),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_SEARCH_FORW_SEL,FXMathText::onCmdSearchSel),
  FXMAPFUNC(SEL_COMMAND,FXMathText::ID_SEARCH_BACK_SEL,FXMathText::onCmdSearchSel),
  FXMAPFUNCS(SEL_COMMAND,FXMathText::ID_SELECT_BRACE,FXMathText::ID_SELECT_ANG,FXMathText::onCmdSelectBlock),
  FXMAPFUNCS(SEL_COMMAND,FXMathText::ID_LEFT_BRACE,FXMathText::ID_LEFT_ANG,FXMathText::onCmdBlockBeg),
  FXMAPFUNCS(SEL_COMMAND,FXMathText::ID_RIGHT_BRACE,FXMathText::ID_RIGHT_ANG,FXMathText::onCmdBlockEnd),
  FXMAPFUNCS(SEL_COMMAND,FXMathText::ID_CLEAN_INDENT,FXMathText::ID_SHIFT_TABRIGHT,FXMathText::onCmdShiftText),
  };


// Object implementation
FXIMPLEMENT(FXMathText,FXScrollArea,FXMathTextMap,ARRAYNUMBER(FXMathTextMap))


// Delimiters
const FXchar FXMathText::textDelimiters[]="~.,/\\`'!@#$%^&*()-=+{}|[]\":;<>?";

/*******************************************************************************/


// Absolute value
static inline FXint fxabs(FXint a){ return a<0?-a:a; }


// For deserialization
FXMathText::FXMathText(){
  flags|=FLAG_ENABLED|FLAG_DROPTARGET;
  buffer=NULL;
  sbuffer=NULL;
  visrows=NULL;
  length=0;
  nrows=1;
  nvisrows=0;
  gapstart=0;
  gapend=0;
  toppos=0;
  keeppos=0;
  toprow=0;
  selstartpos=0;
  selendpos=0;
  hilitestartpos=0;
  hiliteendpos=0;
  anchorpos=0;
  cursorpos=0;
  revertpos=0;
  cursorstart=0;
  cursorend=0;
  cursorrow=0;
  cursorcol=0;
  prefcol=-1;
  wrapwidth=80;
  wrapcolumns=80;
  tabwidth=8;
  tabcolumns=8;
  barwidth=0;
  barcolumns=0;
  hilitestyles=NULL;
  textWidth=0;
  textHeight=0;
  searchflags=SEARCH_EXACT;
  delimiters=textDelimiters;
  clipbuffer=NULL;
  cliplength=0;
  vrows=0;
  vcols=0;
  matchtime=0;
  modified=FALSE;
  mode=MOUSE_NONE;
  grabx=0;
  graby=0;
  }


// Text widget
#ifdef FOX14
FXMathText::FXMathText(FXComposite *p,FXObject* tgt,FXSelector sel,FXuint opts,FXint x,FXint y,FXint w,FXint h,FXint pl,FXint pr,FXint pt,FXint pb):
#else
FXMathText::FXMathText(FXComposite *p,FXObject* tgt,FXSelector sel,FXuint opts,FXint x,FXint y,FXint w,FXint h):
#endif
  FXScrollArea(p,opts,x,y,w,h){
  flags|=FLAG_ENABLED|FLAG_DROPTARGET;
  target=tgt;
  message=sel;
  FXCALLOC(&buffer,FXchar,MINSIZE);
  sbuffer=NULL;
  FXCALLOC(&visrows,FXint,NVISROWS+1);
  length=0;
  nrows=1;
  nvisrows=NVISROWS;
  gapstart=0;
  gapend=MINSIZE;
  toppos=0;
  keeppos=0;
  toprow=0;
  selstartpos=0;
  selendpos=0;
  hilitestartpos=0;
  hiliteendpos=0;
  anchorpos=0;
  cursorpos=0;
  revertpos=0;
  cursorstart=0;
  cursorend=0;
  cursorrow=0;
  cursorcol=0;
  prefcol=-1;
#ifdef FOX14
  margintop=pt;
  marginbottom=pb;
  marginleft=pl;
  marginright=pr;
#else
  margintop=2;
  marginbottom=2;
  marginleft=3;
  marginright=3;
#endif
  wrapwidth=80;
  wrapcolumns=80;
  tabwidth=8;
  tabcolumns=8;
  barwidth=0;
  barcolumns=0;
  font=getApp()->getNormalFont();
  hilitestyles=NULL;
  defaultCursor=getApp()->getDefaultCursor(DEF_TEXT_CURSOR);
  dragCursor=getApp()->getDefaultCursor(DEF_TEXT_CURSOR);
  textColor=getApp()->getForeColor();
  selbackColor=getApp()->getSelbackColor();
  seltextColor=getApp()->getSelforeColor();
  hilitebackColor=FXRGB(255,128,128);
  hilitetextColor=getApp()->getForeColor();
  activebackColor=backColor;
  cursorColor=getApp()->getForeColor();
  numberColor=textColor;
  barColor=backColor;
  textWidth=0;
  textHeight=0;
  searchflags=SEARCH_EXACT;
  delimiters=textDelimiters;
  clipbuffer=NULL;
  cliplength=0;
  vrows=0;
  vcols=0;
  matchtime=0;
  modified=FALSE;
  mode=MOUSE_NONE;
  grabx=0;
  graby=0;
  }


// Create window
void FXMathText::create(){
  FXScrollArea::create();
  font->create();
  if(!deleteType){ deleteType=getApp()->registerDragType(deleteTypeName); }
  if(!textType){ textType=getApp()->registerDragType(textTypeName); }
  if(options&TEXT_FIXEDWRAP){ wrapwidth=wrapcolumns*font->getTextWidth(" ",1); }
  tabwidth=tabcolumns*font->getTextWidth(" ",1);
  barwidth=barcolumns*font->getTextWidth("8",1);
  recalc();
  }


// Detach window
void FXMathText::detach(){
  FXScrollArea::detach();
  font->detach();
  deleteType=0;
  textType=0;
  }


// If window can have focus
#if (FOX_MINOR<=4)
FXbool FXMathText::canFocus() const { return 1; }
#else
bool FXMathText::canFocus() const { return 1; }
#endif

// Into focus chain
void FXMathText::setFocus(){
  FXScrollArea::setFocus();
  setDefault(TRUE);
  flags&=~FLAG_UPDATE;
  }


// Out of focus chain
void FXMathText::killFocus(){
  FXScrollArea::killFocus();
  setDefault(MAYBE);
  flags|=FLAG_UPDATE;
  }


// Make a valid position
FXint FXMathText::validPos(FXint pos) const {
  return pos<0 ? 0 : pos>length ? length : pos;
  }


// Get default width
FXint FXMathText::getDefaultWidth(){
  if(0<vcols){ return marginleft+barwidth+marginright+vcols*font->getTextWidth("8",1); }
  return FXScrollArea::getDefaultWidth();
  }


// Get default height
FXint FXMathText::getDefaultHeight(){
  if(0<vrows){ return margintop+marginbottom+vrows*font->getFontHeight(); }
  return FXScrollArea::getDefaultHeight();
  }


// Enable the window
void FXMathText::enable(){
  if(!(flags&FLAG_ENABLED)){
    FXScrollArea::enable();
    update(0,0,viewport_w,viewport_h);
    }
  }


// Disable the window
void FXMathText::disable(){
  if(flags&FLAG_ENABLED){
    FXScrollArea::disable();
    update(0,0,viewport_w,viewport_h);
    }
  }


// Propagate size change
void FXMathText::recalc(){
  FXScrollArea::recalc();
  flags|=FLAG_RECALC;
  }


/*******************************************************************************/


// Get character
FXint FXMathText::getChar(FXint pos) const {
  FXASSERT(0<=pos && pos<length);
// @@@ an extra variant on the assertion to help with some desparate debugging
  if (pos>=length) // @@@@@
  { printf("pos=%d >= length=%d\nAbort now!!!\n", pos, length);
    fflush(stdout);
    *(char *)0 = 10;
  }
  return (FXuchar)buffer[pos<gapstart ? pos : pos-gapstart+gapend];
  }


// Get style
FXint FXMathText::getStyle(FXint pos) const {
  FXASSERT(0<=pos && pos<length);
  return (FXuchar)sbuffer[pos<gapstart ? pos : pos-gapstart+gapend];
  }


// Move the gap
void FXMathText::movegap(FXint pos){
  register FXint gaplen=gapend-gapstart;
  FXASSERT(0<=pos && pos<=length);
  FXASSERT(0<=gapstart && gapstart<=length);
  if(gapstart<pos){
    memmove(&buffer[gapstart],&buffer[gapend],pos-gapstart);
    if(sbuffer){memmove(&sbuffer[gapstart],&sbuffer[gapend],pos-gapstart);}
    gapend=pos+gaplen;
    gapstart=pos;
    }
  else if(pos<gapstart){
    memmove(&buffer[pos+gaplen],&buffer[pos],gapstart-pos);
    if(sbuffer){memmove(&sbuffer[pos+gaplen],&sbuffer[pos],gapstart-pos);}
    gapend=pos+gaplen;
    gapstart=pos;
    }
  }


// Size gap
void FXMathText::sizegap(FXint sz){
  register FXint gaplen=gapend-gapstart;
  FXASSERT(0<=gapstart && gapstart<=length);
  if(sz>=gaplen){
    sz+=MINSIZE;
    if(!FXRESIZE(&buffer,FXchar,length+sz)){
      fxerror("%s::sizegap: out of memory.\n",getClassName());
      }
    memmove(&buffer[gapstart+sz],&buffer[gapend],length-gapstart);
    if(sbuffer){
      if(!FXRESIZE(&sbuffer,FXchar,length+sz)){
        fxerror("%s::sizegap: out of memory.\n",getClassName());
        }
      memmove(&sbuffer[gapstart+sz],&sbuffer[gapend],length-gapstart);
      }
    gapend=gapstart+sz;
    }
  }


// Squeeze out the gap by moving it to the end of the buffer
void FXMathText::squeezegap(){
  if(gapstart!=length){
    memmove(&buffer[gapstart],&buffer[gapend],length-gapstart);
    if(sbuffer){memmove(&sbuffer[gapstart],&sbuffer[gapend],length-gapstart);}
    gapend=length+gapend-gapstart;
    gapstart=length;
    }
  }


/*******************************************************************************/


// Character width
FXint FXMathText::charWidth(FXchar ch,FXint indent) const {
  if(' ' <= ((FXuchar)ch)) return font->getTextWidth(&ch,1);
  if(ch == '\t') return (tabwidth-indent%tabwidth);
  ch|=0x40;
  return font->getTextWidth("^",1)+font->getTextWidth(&ch,1);
  }


// Start of next wrapped line

FXint FXMathText::wrap(FXint start) const {
  register FXint lw,cw,p,s,c;
  FXASSERT(0<=start && start<=length);
  lw=0;
  p=s=start;
// I should really only recognize maths if the style says so too! @@@@@
  int math0 = (p<length && getChar(p)==0x02);
  int math1 = (p+1<length && getChar(p+1)==0x02);
// A maths line breaks after any of the initial 0x02 bytes except the
// last of them - after that it only breaks at newline.
  if (math0)
  {
// If the first character after an 0x02 is ANOTHER 0x02 then I view the
// next row as starting with that second character.
    if (math1) return p+1;
// Otherwise the line just goes on until a terminating newline (or the end
// of the buffer)
    while (p<length)        // Find '\n' or end of buffer.
    { c = getChar(p);
      if (c=='\n') return p+1;
      p++;
    }
    return length;
  }
  while(p<length){
    c=getChar(p);
    if(c=='\n') return p+1; // Newline always breaks
    cw=charWidth(c,lw);
    if(lw+cw>wrapwidth){    // Technically, a tab-before-wrap should be as wide as space!
      if(s>start) return s;     // We remembered the last space we encountered; break there!
      if(p==start) p++;         // Always at least one character on each line!
      return p;
      }
    lw+=cw;
    p++;
    if(isspace(c)&&!(options&TEXT_COLUMNWRAP))
      s=p;                      // Remember potential break point!
    }
  return length;
  }


// Count number of newlines
FXint FXMathText::countLines(FXint start,FXint end) const {
  register FXint p,nl=0;
  FXASSERT(0<=start && end<=length+1);
  p=start;
  while(p<end){
    if(p>=length) return nl+1;
    if(getChar(p)=='\n') nl++;
    p++;
    }
  return nl;
  }


// Count number of rows; start and end should be on a row start

// I believe that the logic in this should mirror that in wrap(), and
// so the newer shorter version I have here keeps that logic in just
// one place.

FXint FXMathText::countRows(FXint start,FXint end) const {
  int nr=0;
  FXASSERT(0<=start && end<=length+1);
  while (start < end && start < length)
  { start = wrap(start);
    nr++;
  }
  return nr;
}



// Count number of columns

// NB when applied to a region within which there is some displayed
// mathematics this will record a value that reflects the number of bytes
// in the internal representation of the mathematical formula. Often that will
// be quote long, and also often it will not be very meaningful to the user!

FXint FXMathText::countCols(FXint start,FXint end) const {
  register FXint nc=0,in=0,ch;
  FXASSERT(0<=start && end<=length);
  while(start<end){
    ch=getChar(start);
    if(ch=='\n'){
      if(in>nc) nc=in;
      in=0;
      }
    else if(ch=='\t'){
      in+=(tabcolumns-nc%tabcolumns);
      }
    else{
      in++;
      }
    start++;
    }
  if(in>nc) nc=in;
  return nc;
  }


// Measure lines; start and end should be on a row start

FXint FXMathText::measureText(FXint start,FXint end,FXint& wmax,FXint& hmax) const {
  register FXint nr=0,w=0,c,p;
  FXASSERT(0<=start && end<=length+1);
  if(options&TEXT_WORDWRAP){
// When WORDWRAP is enabled I always return wrapwidth as the measured width,
// even if all rows are actually rather short. So the height to worry about
// is just a count of the number of rows used.
    wmax=wrapwidth;
    nr = countRows(start, end);
    }
  else{
    wmax=0;
    p=start;
    while(p<end){
      if(p>=length){
        if(w>wmax) wmax=w;
        nr++;
        break;
        }
      c=getChar(p);
      if(c=='\n'){
        if(w>wmax) wmax=w;
        nr++;
        w=0;
        }
      else{
        w+=charWidth(c,w);
        }
      p++;
      }
    }
  hmax=nr*font->getFontHeight();
  return nr;
  }


// Find end of previous word
FXint FXMathText::leftWord(FXint pos) const {
  register FXint ch;
  if(pos>length) pos=length;
  if(0<pos){
    ch=getChar(pos-1);
    if(strchr(delimiters,ch)) return pos-1;
    }
  while(0<pos){
    ch=getChar(pos-1);
    if(strchr(delimiters,ch)) return pos;
    if(isspace(ch)) break;
    pos--;
    }
  while(0<pos){
    ch=getChar(pos-1);
    if(!isspace(ch)) return pos;
    pos--;
    }
  return 0;
  }


// Find begin of next word
FXint FXMathText::rightWord(FXint pos) const {
  register FXint ch;
  if(pos<0) pos=0;
  if(pos<length){
    ch=getChar(pos);
    if(strchr(delimiters,ch)) return pos+1;
    }
  while(pos<length){
    ch=getChar(pos);
    if(strchr(delimiters,ch)) return pos;
    if(isspace(ch)) break;
    pos++;
    }
  while(pos<length){
    ch=getChar(pos);
    if(!isspace(ch)) return pos;
    pos++;
    }
  return length;
  }


// Find begin of a word
FXint FXMathText::wordStart(FXint pos) const {
  register FXint c=' ';
  if(pos<=0) return 0;
  if(pos<length) c=getChar(pos); else pos=length;
  if(c==' ' || c=='\t'){
    while(0<pos){
      c=getChar(pos-1);
      if(c!=' ' && c!='\t') return pos;
      pos--;
      }
    }
  else if(strchr(delimiters,c)){
    while(0<pos){
      c=getChar(pos-1);
      if(!strchr(delimiters,c)) return pos;
      pos--;
      }
    }
  else{
    while(0<pos){
      c=getChar(pos-1);
      if(strchr(delimiters,c) || isspace(c)) return pos;
      pos--;
      }
    }
  return 0;
  }


// Find end of word
FXint FXMathText::wordEnd(FXint pos) const {
  register FXint c=' ';
  if(pos>=length) return length;
  if(0<=pos) c=getChar(pos); else pos=0;
  if(c==' ' || c=='\t'){
    while(pos<length){
      c=getChar(pos);
      if(c!=' ' && c!='\t') return pos;
      pos++;
      }
    }
  else if(strchr(delimiters,c)){
    while(pos<length){
      c=getChar(pos);
      if(!strchr(delimiters,c)) return pos;
      pos++;
      }
    }
  else{
    while(pos<length){
      c=getChar(pos);
      if(strchr(delimiters,c) || isspace(c)) return pos;
      pos++;
      }
    }
  return length;
  }


// Return position of begin of paragraph
FXint FXMathText::lineStart(FXint pos) const {
  FXASSERT(0<=pos && pos<=length);
  while(0<pos){ if(getChar(pos-1)=='\n') return pos; pos--; }
  return 0;
  }


// Return position of end of paragraph
FXint FXMathText::lineEnd(FXint pos) const {
  FXASSERT(0<=pos && pos<=length);
  while(pos<length){ if(getChar(pos)=='\n') return pos; pos++; }
  return length;
  }


// Return start of next line
FXint FXMathText::nextLine(FXint pos,FXint nl) const {
  FXASSERT(0<=pos && pos<=length);
  if(nl<=0) return pos;
  while(pos<length){
    if(getChar(pos)=='\n'){
      if(--nl==0) return pos+1;
      }
    pos++;
    }
  return length;
  }


// Return start of previous line
FXint FXMathText::prevLine(FXint pos,FXint nl) const {
  FXASSERT(0<=pos && pos<=length);
  if(nl<=0) return pos;
  while(0<pos){
    if(getChar(pos-1)=='\n'){
      if(nl--==0) return pos;
      }
    pos--;
    }
  return 0;
  }


// Return row start

FXint FXMathText::rowStart(FXint pos) const {
  register FXint p,t;
  FXASSERT(0<=pos && pos<=length);
  p=lineStart(pos);
  if(!(options&TEXT_WORDWRAP)) return p;
  while(p<pos && (t=wrap(p))<=pos && t<length) p=t;
  FXASSERT(0<=p && p<=pos);
  return p;
  }


// Return row end

FXint FXMathText::rowEnd(FXint pos) const {
  register FXint p;
  FXASSERT(0<=pos && pos<=length);
  if(!(options&TEXT_WORDWRAP)) return lineEnd(pos);
  p=lineStart(pos);
  while(p<length && p<=pos) p=wrap(p);
  FXASSERT(0<=p && p<=length);
  if(pos<p && isspace(getChar(p-1))) p--;
  FXASSERT(pos<=p && p<=length);
  return p;
  }


// Move to next row given start of line

FXint FXMathText::nextRow(FXint pos,FXint nr) const {
  register FXint p;
  FXASSERT(0<=pos && pos<=length);
  if(!(options&TEXT_WORDWRAP)) return nextLine(pos,nr);
  if(nr<=0) return pos;
  p=rowStart(pos);
  while(p<length && 0<nr){ p=wrap(p); nr--; }
  FXASSERT(0<=p && p<=length);
  return p;
  }


// Move to previous row given start of line

FXint FXMathText::prevRow(FXint pos,FXint nr) const {
  register FXint p,q,t;
  FXASSERT(0<=pos && pos<=length);
  if(!(options&TEXT_WORDWRAP)) return prevLine(pos,nr);
  if(nr<=0) return pos;
  while(0<pos){
    p=lineStart(pos);
    for(q=p; q<pos && (t=wrap(q))<=pos && t<length; q=t) nr--;
    if(nr==0) return p;
    if(nr<0){
      do{p=wrap(p);}while(++nr);
      FXASSERT(0<=p && p<=length);
      return p;
      }
    pos=p-1;
    nr--;
    }
  return 0;
  }


// Backs up to the begin of the line preceding the line containing pos, or the
// start of the line containing pos if the preceding line terminated in a newline

FXint FXMathText::changeBeg(FXint pos) const {
  register FXint p1,p2,t;
  FXASSERT(0<=pos && pos<=length);
  p1=p2=lineStart(pos);
  if(!(options&TEXT_WORDWRAP)) return p1;
  while(p2<pos && (t=wrap(p2))<=pos){
    p1=p2;
    p2=t;
    }
  FXASSERT(0<=p1 && p1<=length);
  return p1;
  }


// Scan forward to the end of affected area, which is the start of the next
// paragraph; a change can cause the rest of the paragraph to reflow.
FXint FXMathText::changeEnd(FXint pos) const {
  FXASSERT(0<=pos && pos<=length);
  while(pos<length){
    if(getChar(pos)=='\n') return pos+1;
    pos++;
    }
  return length+1;  // YES, one more!
  }


// Calculate line width

// The width of a "maths" line is a matter of delicacy here. At present
// I return the answer 0, but wrapWidth would also be a possibility. This
// procedure is called from a number of places:
//    getXOfPos  (makePositionVisible and hence mouse clicks, cursor
//                movement, text inserts and deletes)
//    various things that call update() to ensure that bits of the screen
//                get re-painted.

FXint FXMathText::lineWidth(FXint pos,FXint n) const {
  register FXint end=pos+n,w=0;
  FXASSERT(0<=pos && end<=length);
  if (pos<end && getChar(pos) == 0x02) return 0; // Maths mode line
  while(pos<end){ w+=charWidth(getChar(pos),w); pos++; }
  return w;
  }


// Determine indent of position pos relative to start

FXint FXMathText::indentFromPos(FXint start,FXint pos) const {
  register FXint in=0,ch;
  FXASSERT(0<=start && pos<=length);
  if (start<pos && getChar(start)==0x02) return in; // maths mode
  while(start<pos){
    ch=getChar(start);
    if(ch=='\n'){
      in=0;
      }
    else if(ch=='\t'){
      in+=(tabcolumns-in%tabcolumns);
      }
    else{
      in+=1;
      }
    start++;
    }
  return in;
  }


// Determine position of indent relative to start

FXint FXMathText::posFromIndent(FXint start,FXint indent) const {
  register FXint in,pos,ch;
  FXASSERT(0<=start && start<=length);
  in=0;
  pos=start;
  if (pos<length && getChar(pos)==0x02) return pos; // maths mode
  while(in<indent && pos<length){
    ch=getChar(pos);
    if(ch=='\n')
      break;
    else if(ch=='\t')
      in+=(tabcolumns-in%tabcolumns);
    else
      in+=1;
    pos++;
    }
  return pos;
  }



// Search forward for match
FXint FXMathText::matchForward(FXint pos,FXint end,FXchar l,FXchar r,FXint level) const {
  register FXchar c;
  FXASSERT(0<=end && end<=length);
  FXASSERT(0<=pos && pos<=length);
  while(pos<end){
    c=getChar(pos);
    if(c==r){
      level--;
      if(level<=0) return pos;
      }
    else if(c==l){
      level++;
      }
    pos++;
    }
  return -1;
  }


// Search backward for match
FXint FXMathText::matchBackward(FXint pos,FXint beg,FXchar l,FXchar r,FXint level) const {
  register FXchar c;
  FXASSERT(0<=beg && beg<=length);
  FXASSERT(0<=pos && pos<=length);
  while(beg<=pos){
    if (pos<length)
    {  c=getChar(pos);
      if(c==l){
        level--;
        if(level<=0) return pos;
        }
      else if(c==r){
        level++;
        }
    }
    pos--;
    }
  return -1;
  }


// Search for matching character
FXint FXMathText::findMatching(FXint pos,FXint beg,FXint end,FXchar ch,FXint level) const {
  FXASSERT(0<=level);
  FXASSERT(0<=pos && pos<=length);
  switch(ch){
    case '{': return matchForward(pos+1,end,'{','}',level);
    case '}': return matchBackward(pos-1,beg,'{','}',level);
    case '[': return matchForward(pos+1,end,'[',']',level);
    case ']': return matchBackward(pos-1,beg,'[',']',level);
    case '(': return matchForward(pos+1,end,'(',')',level);
    case ')': return matchBackward(pos-1,beg,'(',')',level);
    }
  return -1;
  }


// Flash matching braces or parentheses, if within visible part of buffer
void FXMathText::flashMatching(){
  FXint matchpos;
  killHighlight();
  getApp()->removeTimeout(this,ID_FLASH);
  if(matchtime && 0<cursorpos){
    matchpos=findMatching(cursorpos-1,visrows[0],visrows[nvisrows],getChar(cursorpos-1),1);
    if(0<=matchpos){
      getApp()->addTimeout(this,ID_FLASH,matchtime);
      setHighlight(matchpos,1);
      }
    }
  }


// Search for text
FXbool FXMathText::findText(const FXString& string,FXint* beg,FXint* end,FXint start,FXuint flgs,FXint npar){
  register FXint rexmode;
  FXRex rex;

  // Compile flags
  rexmode=REX_VERBATIM;
  if(1<npar) rexmode|=REX_CAPTURE;
  if(flgs&SEARCH_REGEX) rexmode&=~REX_VERBATIM;
  if(flgs&SEARCH_IGNORECASE) rexmode|=REX_ICASE;

  // Try parse the regex
  if(rex.parse(string,rexmode)==REGERR_OK){

    // Make all characters contiguous in the buffer
    squeezegap();

    // Search backward
    if(flgs&SEARCH_BACKWARD){

      // Search from start to begin of buffer
      if(rex.match(buffer,length,beg,end,REX_BACKWARD,npar,0,start)) return TRUE;

      if(!(flgs&SEARCH_WRAP)) return FALSE;

      // Search from end of buffer backwards
      if(rex.match(buffer,length,beg,end,REX_BACKWARD,npar,start,length)) return TRUE;
      }

    // Search forward
    else{

      // Search from start to end of buffer
      if(rex.match(buffer,length,beg,end,REX_FORWARD,npar,start,length)) return TRUE;

      if(!(flgs&SEARCH_WRAP)) return FALSE;

      // Search from begin of buffer forwards
      if(rex.match(buffer,length,beg,end,REX_FORWARD,npar,0,start)) return TRUE;
      }
    }
  return FALSE;
  }


/*******************************************************************************/


// See if pos is a visible position

FXbool FXMathText::posVisible(FXint pos) const {
  return visrows[0]<=pos && pos<=visrows[nvisrows];
  }


// See if position is in the selection, and the selection is non-empty
FXbool FXMathText::isPosSelected(FXint pos) const {
  return selstartpos<selendpos && selstartpos<=pos && pos<=selendpos;
  }


// Find line number (well row?) from visible pos (in buffer)
FXint FXMathText::posToLine(FXint pos,FXint ln) const {
  FXASSERT(0<=ln && ln<nvisrows);
  FXASSERT(visrows[ln]<=pos && pos<=visrows[nvisrows]);
  while(ln<nvisrows-1 && visrows[ln+1]<=pos && visrows[ln]<visrows[ln+1]) ln++;
  FXASSERT(0<=ln && ln<nvisrows);
  FXASSERT(visrows[ln]<=pos && pos<=visrows[ln+1]);
  return ln;
  }


// Localize position at x,y

// For a maths-mode line I just indicate the start of the row
// involved, and at present I do not attempt to localise information
// within the expression itself. Sometime LATER ON I may try to cope with
// selection etc within formulae. Actually hooking onto the box structure
// that I use will probably make identifying a location within a formula
// fairly easy!

FXint FXMathText::getPosAt(FXint x,FXint y) const {
  register FXint row,ls,le,cx,cw,ch;
  y=y-pos_y-margintop;
  row=y/font->getFontHeight();
  if(row<0) return 0;               // Before first row
  if(row>=nrows) return length;     // Below last row
  if(row<toprow){                   // Above visible area
    ls=prevRow(toppos,toprow-row);
    le=nextRow(ls,1);
    }
  else if(row>=toprow+nvisrows){    // Below visible area
    ls=nextRow(toppos,row-toprow);
    le=nextRow(ls,1);
    }
  else{                             // Inside visible area
    ls=visrows[row-toprow];
    le=visrows[row-toprow+1];
    }
  x=x-pos_x-marginleft-barwidth;    // Before begin of line
  if(x<0) return ls;
  FXASSERT(0<=ls);
  FXASSERT(ls<=le);
  FXASSERT(le<=length);
  if(ls<le && (((ch=getChar(le-1))=='\n') || (le<length && isspace(ch)))) le--;
  cx=0;
  if (ls<le && getChar(ls)==0x02) return ls; // maths mode selects its start
  while(ls<le){
    ch=getChar(ls);
    cw=charWidth(ch,cx);
    if(x<=(cx+(cw>>1))) return ls;
    cx+=cw;
    ls+=1;
    }
  return le;
  }


// Determine Y from position pos

FXint FXMathText::getYOfPos(FXint pos) const {
  register FXint h=font->getFontHeight();
  register FXint n,y;
  if(pos>length) pos=length;
  if(pos<0) pos=0;

  // Above visible part of buffer
  if(pos<visrows[0]){
    n=countRows(rowStart(pos),visrows[0]);
    y=(toprow-n)*h;
    FXTRACE((150,"getYOfPos(%d < visrows[0]=%d) = %d\n",pos,visrows[0],margintop+y));
    }

  // Below visible part of buffer
  else if(pos>visrows[nvisrows]){
    n=countRows(visrows[nvisrows-1],pos);
    y=(toprow+nvisrows-1+n)*h;
    FXTRACE((150,"getYOfPos(%d > visrows[%d]=%d) = %d\n",pos,nvisrows,visrows[nvisrows],margintop+y));
    }

  // In visible part of buffer
  else{
    n=posToLine(pos,0);
    y=(toprow+n)*h;
    FXTRACE((150,"getYOfPos(visrows[0]=%d <= %d <= visrows[%d]=%d) = %d\n",visrows[0],pos,nvisrows,visrows[nvisrows],margintop+y));
    }
  return margintop+y;
  }


// Calculate X position of pos

FXint FXMathText::getXOfPos(FXint pos) const {
  register FXint base=rowStart(pos);
  return marginleft+barwidth+lineWidth(base,pos-base);
  }


// Force position to become fully visible
void FXMathText::makePositionVisible(FXint pos){
  register FXint x,y,nx,ny;

  // Get coordinates of position
  x=getXOfPos(pos);
  y=getYOfPos(pos);

  // Old scroll position
  ny=pos_y;
  nx=pos_x;

  // Check vertical visibility
  if(pos_y+y<margintop){
    ny=margintop-y;
    nx=0;
    }
  else if(pos_y+y+font->getFontHeight()>viewport_h-marginbottom){
    ny=viewport_h-font->getFontHeight()-marginbottom-y;
    nx=0;
    }

  // Check Horizontal visibility
  if(pos_x+x<marginleft+barwidth){
    nx=marginleft+barwidth-x;
    }
  else if(pos_x+x>viewport_w-marginright){
    nx=viewport_w-marginright-x;
    }

  // If needed, scroll
  if(nx!=pos_x || ny!=pos_y){
    setPosition(nx,ny);
    }
  }


// Return TRUE if position is visible
FXbool FXMathText::isPosVisible(FXint pos) const {
  if(visrows[0]<=pos && pos<=visrows[nvisrows]){
    register FXint h=font->getFontHeight();
    register FXint y=pos_y+margintop+(toprow+posToLine(pos,0))*h;
    return margintop<=y && y+h<viewport_h-marginbottom;
    }
  return FALSE;
  }


// Make line containing pos the top visible line
void FXMathText::setTopLine(FXint pos){
  setPosition(pos_x,margintop-getYOfPos(pos));
  }


// Make line containing pos the bottom visible line
void FXMathText::setBottomLine(FXint pos){
  setPosition(pos_x,viewport_h-font->getFontHeight()-marginbottom-getYOfPos(pos));
  }


// Center line of pos in the middle of the screen
void FXMathText::setCenterLine(FXint pos){
  setPosition(pos_x,viewport_h/2+font->getFontHeight()/2-getYOfPos(pos));
  }


// Get top line
FXint FXMathText::getTopLine() const {
  return visrows[0];
  }


// Get bottom line
FXint FXMathText::getBottomLine() const {
  return visrows[nvisrows-1];
  }


// Move content

void FXMathText::moveContents(FXint x,FXint y){
  register FXint delta,i,dx,dy;

  // Erase fragments of cursor overhanging margins
  eraseCursorOverhang();

  // Number of lines scrolled
  delta=-y/font->getFontHeight() - toprow;

  // Scrolled up one or more lines
  if(delta<0){
    if(toprow+delta<=0){
      toppos=0;
      toprow=0;
      }
    else{
      toppos=prevRow(toppos,-delta);
      toprow=toprow+delta;
      }
    if(-delta<nvisrows){
      for(i=nvisrows; i>=-delta; i--) visrows[i]=visrows[delta+i];
      calcVisRows(0,-delta);
      }
    else{
      calcVisRows(0,nvisrows);
      }
    }

  // Scrolled down one or more lines
  else if(delta>0){
    if(toprow+delta>=nrows-1){
      toppos=rowStart(length);
      toprow=nrows-1;
      }
    else{
      toppos=nextRow(toppos,delta);
      toprow=toprow+delta;
      }
    if(delta<nvisrows){
      for(i=0; i<=nvisrows-delta; i++) visrows[i]=visrows[delta+i];
      calcVisRows(nvisrows-delta,nvisrows);
      }
    else{
      calcVisRows(0,nvisrows);
      }
    }

  // This is now the new keep position
  keeppos=toppos;

  // Hopefully, all is still in range (why test if I have done nothing?)
  if (delta != 0) {
  FXASSERT(0<=toprow && toprow<=nrows-1);
  FXASSERT(0<=toppos && toppos<=length);

  // Scroll the contents
  dx=x-pos_x;
  dy=y-pos_y;
  pos_x=x;
  pos_y=y;

  // Scroll stuff in the bar only vertically
  scroll(0,0,barwidth,viewport_h,0,dy);

  // Scroll the text
  scroll(marginleft+barwidth,margintop,viewport_w-marginleft-barwidth-marginright,viewport_h-margintop-marginbottom,dx,dy);
  }
}

/*******************************************************************************/


// Recalculate line starts

void FXMathText::calcVisRows(FXint startline,FXint endline){
  register FXint line,pos;
  FXASSERT(nvisrows>0);
  if(startline<0)
    startline=0;
  else if(startline>nvisrows)
    startline=nvisrows;
  if(endline<0)
    endline=0;
  else if(endline>nvisrows)
    endline=nvisrows;
  if(startline<=endline){
    if(startline==0){
      FXASSERT(0<=toppos && toppos<=length);
      visrows[0]=toppos;
      startline=1;
      }
    pos=visrows[startline-1];
    line=startline;
    if(options&TEXT_WORDWRAP){
      while(line<=endline && pos<length){
        pos=wrap(pos);
        FXASSERT(0<=pos && pos<=length);
        visrows[line++]=pos;
        }
      }
    else{
      while(line<=endline && pos<length){
        pos=nextLine(pos);
        FXASSERT(0<=pos && pos<=length);
        visrows[line++]=pos;
        }
      }
    while(line<=endline){
      visrows[line++]=length;
      }
    }
  }


// There has been a mutation in the buffer

// "math display" lines measure rather as if they had been empty lines, and so
// any changes in them will not be reflected by a change in their length.
// However I am going to be treating math display as read-only once it is
// in the buffer and hence mutations will only happen within non-maths
// sections (apart I suppose from when maths is initially inserted) and I
// hope that no problems will arise here.

void FXMathText::mutation(FXint pos,FXint ncins,FXint ncdel,FXint nrins,FXint nrdel){
  register FXint ncdelta=ncins-ncdel;
  register FXint nrdelta=nrins-nrdel;
  register FXint line,i,x,y;

  FXTRACE((150,"BEFORE: pos=%d ncins=%d ncdel=%d nrins=%d nrdel=%d toppos=%d toprow=%d nrows=%d nvisrows=%d\n",pos,ncins,ncdel,nrins,nrdel,toppos,toprow,nrows,nvisrows));

  // All of the change is below the last visible line
  if(visrows[nvisrows]<pos){
    FXTRACE((150,"change below visible\n"));
    nrows+=nrdelta;
    }

  // All change above first visible line
  else if(pos+ncdel<=visrows[0]){
    FXTRACE((150,"change above visible\n"));
    nrows+=nrdelta;
    toprow+=nrdelta;
    toppos+=ncdelta;
    keeppos=toppos;
    for(i=0; i<=nvisrows; i++) visrows[i]+=ncdelta;
    pos_y-=nrdelta*font->getFontHeight();
    FXASSERT(0<=toppos && toppos<=length);
    if(nrdelta) update(0,0,barwidth,height);
    }

  // Top visible part unchanged
  else if(visrows[0]<=pos){
    line=posToLine(pos,0);
    FXTRACE((150,"change below visible line %d\n",line));

    // More lines means paint the bottom half
    if(nrdelta>0){
      FXTRACE((150,"inserted %d rows\n",nrdelta));
      nrows+=nrdelta;
      for(i=nvisrows; i>line+nrdelta; i--) visrows[i]=visrows[i-nrdelta]+ncdelta;
      calcVisRows(line+1,line+nrins);
      FXASSERT(0<=toppos && toppos<=length);
      y=pos_y+margintop+(toprow+line)*font->getFontHeight();
      update(barwidth,y,width-barwidth,height-y);
      }

    // Less lines means paint bottom half also
    else if(nrdelta<0){
      FXTRACE((150,"deleted %d rows\n",-nrdelta));
      nrows+=nrdelta;
      for(i=line+1; i<=nvisrows+nrdelta; i++) visrows[i]=visrows[i-nrdelta]+ncdelta;
      calcVisRows(nvisrows+nrdelta,nvisrows);
      calcVisRows(line+1,line+nrins);
      FXASSERT(0<=toppos && toppos<=length);
      y=pos_y+margintop+(toprow+line)*font->getFontHeight();
      update(barwidth,y,width-barwidth,height-y);
      }

    // Same lines means paint the changed area only
    else{
      FXTRACE((150,"same number of rows\n"));
      for(i=line+1; i<=nvisrows; i++) visrows[i]=visrows[i]+ncdelta;
      calcVisRows(line+1,line+nrins);
      FXASSERT(0<=toppos && toppos<=length);
      if(nrins==0){
        x=pos_x+marginleft+barwidth+lineWidth(visrows[line],pos-visrows[line]);
        y=pos_y+margintop+(toprow+line)*font->getFontHeight();
        update(x,y,width-x,font->getFontHeight());
        FXTRACE((150,"update(%d,%d,%d,%d)\n",x,y,width-x,font->getFontHeight()));
        }
      else{
        y=pos_y+margintop+(toprow+line)*font->getFontHeight();
        update(barwidth,y,width-barwidth,nrins*font->getFontHeight());
        FXTRACE((150,"update(%d,%d,%d,%d)\n",0,y,width,nrins*font->getFontHeight()));
        }
      }
    }

  // Bottom visible part unchanged
  else if(pos+ncdel<visrows[nvisrows-1]){
    nrows+=nrdelta;
    line=1+posToLine(pos+ncdel,0);
    FXASSERT(0<=line && line<nvisrows);
    FXASSERT(pos+ncdel<=visrows[line]);
    FXTRACE((150,"change above visible line %d\n",line));

    // Too few lines left to display
    if(toprow+nrdelta<=line){
      FXTRACE((150,"reset to top\n"));
      toprow=0;
      toppos=0;
      keeppos=0;
      pos_y=0;
      calcVisRows(0,nvisrows);
      FXASSERT(0<=toppos && toppos<=length);
      update();
      }

    // Redisplay only the top
    else{
      FXTRACE((150,"redraw top %d lines\n",line));
      toprow+=nrdelta;
      toppos=prevRow(visrows[line]+ncdelta,line);
      keeppos=toppos;
      pos_y-=nrdelta*font->getFontHeight();
      calcVisRows(0,nvisrows);
      FXASSERT(0<=toppos && toppos<=length);
      update(barwidth,0,width-barwidth,pos_y+margintop+(toprow+line)*font->getFontHeight());
      if(nrdelta) update(0,0,barwidth,height);
      }
    }

  // All visible text changed
  else{
    FXTRACE((150,"change all visible lines\n"));
    nrows+=nrdelta;

    // Reset to top because too few lines left
    if(toprow>=nrows){
      FXTRACE((150,"reset to top\n"));
      toprow=0;
      toppos=0;
      keeppos=0;
      FXASSERT(0<=toppos && toppos<=length);
      pos_y=0;
      }

    // Maintain same row as before
    else{
      FXTRACE((150,"set to same row %d\n",toprow));
      toppos=nextRow(0,toprow);
      keeppos=toppos;
      FXASSERT(0<=toppos && toppos<=length);
      }
    calcVisRows(0,nvisrows);
    update();
    }

  FXTRACE((150,"AFTER : pos=%d ncins=%d ncdel=%d nrins=%d nrdel=%d toppos=%d toprow=%d nrows=%d\n",pos,ncins,ncdel,nrins,nrdel,toppos,toprow,nrows));
  }


// Replace m characters at pos by n characters

void FXMathText::replace(FXint pos,FXint m,const FXchar *text,FXint n,FXint style){
  register FXint nrdel,nrins,ncdel,ncins,wbeg,wend,del;
  FXint wdel,hdel,wins,hins;
  drawCursor(0);    // FIXME can we do without this?

  FXTRACE((150,"pos=%d mdel=%d nins=%d\n",pos,m,n));

  // Delta in characters
  del=n-m;

  // Bracket potentially affected character range for wrapping purposes
  wbeg=changeBeg(pos);
  wend=changeEnd(pos+m);

  // Measure stuff prior to change
  nrdel=measureText(wbeg,wend,wdel,hdel);
  ncdel=wend-wbeg;

  FXTRACE((150,"wbeg=%d wend=%d nrdel=%d ncdel=%d length=%d wdel=%d hdel=%d\n",wbeg,wend,nrdel,ncdel,length,wdel,hdel));

  // Modify the buffer
  sizegap(del);
  movegap(pos);
  memcpy(&buffer[pos],text,n);
  if(sbuffer){memset(&sbuffer[pos],style,n);}
  gapstart+=n;
  gapend+=m;
  length+=del;

  // Measure stuff after change
  nrins=measureText(wbeg,wend+n-m,wins,hins);
  ncins=wend+n-m-wbeg;

  FXTRACE((150,"wbeg=%d wend+n-m=%d nrins=%d ncins=%d length=%d wins=%d hins=%d\n",wbeg,wend+n-m,nrins,ncins,length,wins,hins));

  // Update stuff
  mutation(wbeg,ncins,ncdel,nrins,nrdel);

  // Fix text metrics
  textHeight=textHeight+hins-hdel;
  textWidth=FXMAX(textWidth,wins);

  // Fix selection range
  FXASSERT(selstartpos<=selendpos);
  if(pos+m<=selstartpos){
    selstartpos+=del;
    selendpos+=del;
    }
  else if(pos<selendpos){
    if(selendpos<=pos+m) selendpos=pos+n; else selendpos+=del;
    if(pos<=selstartpos) selstartpos=pos+n;
    }

  // Fix highlight range
  FXASSERT(hilitestartpos<=hiliteendpos);
  if(pos+m<=hilitestartpos){
    hilitestartpos+=del;
    hiliteendpos+=del;
    }
  else if(pos<hiliteendpos){
    if(hiliteendpos<=pos+m) hiliteendpos=pos+n; else hiliteendpos+=del;
    if(pos<=hilitestartpos) hilitestartpos=pos+n;
    }

  // Fix anchor position
  if(pos+m<=anchorpos) anchorpos+=del;
  else if(pos<=anchorpos) anchorpos=pos+n;

  // Update cursor position variables
  if(wend<=cursorpos){
    cursorpos+=del;
    cursorstart+=del;
    cursorend+=del;
    cursorrow+=nrins-nrdel;
    }
  else if(wbeg<=cursorpos){
    if(pos+m<=cursorpos) cursorpos+=del;
    else if(pos<=cursorpos) cursorpos=pos+n;
    cursorstart=rowStart(cursorpos);
    cursorend=nextRow(cursorstart);
    cursorcol=indentFromPos(cursorstart,cursorpos);
    if(cursorstart<toppos){
      cursorrow=toprow-countRows(cursorstart,toppos);
      }
    else{
      cursorrow=toprow+countRows(toppos,cursorstart);
      }
    }

  // Reconcile scrollbars
  FXScrollArea::layout();     // FIXME:- scrollbars, but no layout

  // Forget preferred column
  prefcol=-1;
  }


// Replace m characters at pos by n characters
void FXMathText::replaceStyledText(FXint pos,FXint m,const FXchar *text,FXint n,FXint style,FXbool notify){
  FXTextChange textchange;
  if(n<0 || m<0 || pos<0 || length<pos+m){ fxerror("%s::replaceStyledText: bad argument range.\n",getClassName()); }
  FXTRACE((130,"replaceStyledText(%d,%d,text,%d)\n",pos,m,n));
  textchange.pos=pos;
  textchange.ndel=m;
  textchange.nins=n;
  textchange.ins=(FXchar*)text;
  FXMALLOC(&textchange.del,FXchar,m);
  extractText(textchange.del,pos,m);
  replace(pos,m,text,n,style);
  if(notify && target){
    target->tryHandle(this,FXSEL(SEL_REPLACED,message),(void*)&textchange);
    target->tryHandle(this,FXSEL(SEL_CHANGED,message),(void*)(FXival)cursorpos);
    }
  FXFREE(&textchange.del);
  }


// Replace text by other text
void FXMathText::replaceText(FXint pos,FXint m,const FXchar *text,FXint n,FXbool notify){
  replaceStyledText(pos,m,text,n,0,notify);
  }


// Add text at the end
void FXMathText::appendStyledText(const FXchar *text,FXint n,FXint style,FXbool notify){
  FXTextChange textchange;
  if(n<0){ fxerror("%s::appendStyledText: bad argument range.\n",getClassName()); }
  FXTRACE((130,"appendStyledText(text,%d)\n",n));
  textchange.pos=length;
  textchange.ndel=0;
  textchange.nins=n;
  textchange.ins=(FXchar*)text;
  textchange.del=(FXchar*)"";
  replace(length,0,text,n,style);
  if(notify && target){
    target->tryHandle(this,FXSEL(SEL_INSERTED,message),(void*)&textchange);
    target->tryHandle(this,FXSEL(SEL_CHANGED,message),(void*)(FXival)cursorpos);
    }
  }


// Add text at the end
void FXMathText::appendText(const FXchar *text,FXint n,FXbool notify){
  appendStyledText(text,n,0,notify);
  }


// Insert some text at pos
void FXMathText::insertStyledText(FXint pos,const FXchar *text,FXint n,FXint style,FXbool notify){
  FXTextChange textchange;
  if(n<0 || pos<0 || length<pos){ fxerror("%s::insertStyledText: bad argument range.\n",getClassName()); }
  FXTRACE((130,"insertStyledText(%d,text,%d)\n",pos,n));
  textchange.pos=pos;
  textchange.ndel=0;
  textchange.nins=n;
  textchange.ins=(FXchar*)text;
  textchange.del=(FXchar*)"";
  replace(pos,0,text,n,style);
  if(notify && target){
    target->tryHandle(this,FXSEL(SEL_INSERTED,message),(void*)&textchange);
    target->tryHandle(this,FXSEL(SEL_CHANGED,message),(void*)(FXival)cursorpos);
    }
  }


// Insert some text at pos
void FXMathText::insertText(FXint pos,const FXchar *text,FXint n,FXbool notify){
  insertStyledText(pos,text,n,0,notify);
  }


// Remove some text at pos
void FXMathText::removeText(FXint pos,FXint n,FXbool notify){
  FXTextChange textchange;
  if(n<0 || pos<0 || length<pos+n){ fxerror("%s::removeText: bad argument range.\n",getClassName()); }
  FXTRACE((130,"removeText(%d,%d)\n",pos,n));
  textchange.pos=pos;
  textchange.ndel=n;
  textchange.nins=0;
  textchange.ins=(FXchar*)"";
  FXMALLOC(&textchange.del,FXchar,n);
  extractText(textchange.del,pos,n);
  replace(pos,n,NULL,0,0);
  if(notify && target){
    target->tryHandle(this,FXSEL(SEL_DELETED,message),(void*)&textchange);
    target->tryHandle(this,FXSEL(SEL_CHANGED,message),(void*)(FXival)cursorpos);
    }
  FXFREE(&textchange.del);
  }


// Grab range of text
void FXMathText::extractText(FXchar *text,FXint pos,FXint n) const {
  if(n<0 || pos<0 || length<pos+n){ fxerror("%s::extractText: bad argument.\n",getClassName()); }
  FXASSERT(0<=n && 0<=pos && pos+n<=length);
  if(pos+n<=gapstart){
    memcpy(text,&buffer[pos],n);
    }
  else if(pos>=gapstart){
    memcpy(text,&buffer[pos-gapstart+gapend],n);
    }
  else{
    memcpy(text,&buffer[pos],gapstart-pos);
    memcpy(&text[gapstart-pos],&buffer[gapend],pos+n-gapstart);
    }
  }


// Grab range of style
void FXMathText::extractStyle(FXchar *style,FXint pos,FXint n) const {
  if(n<0 || pos<0 || length<pos+n){ fxerror("%s::extractStyle: bad argument.\n",getClassName()); }
  FXASSERT(0<=n && 0<=pos && pos+n<=length);
  if(sbuffer){
    if(pos+n<=gapstart){
      memcpy(style,&sbuffer[pos],n);
      }
    else if(pos>=gapstart){
      memcpy(style,&sbuffer[pos-gapstart+gapend],n);
      }
    else{
      memcpy(style,&sbuffer[pos],gapstart-pos);
      memcpy(&style[gapstart-pos],&sbuffer[gapend],pos+n-gapstart);
      }
    }
  }


// Change style of text range
void FXMathText::changeStyle(FXint pos,FXint n,FXint style){
  if(n<0 || pos<0 || length<pos+n){ fxerror("%s::changeStyle: bad argument range.\n",getClassName()); }
  if(sbuffer){
    if(pos+n<=gapstart){
      memset(&sbuffer[pos],style,n);
      }
    else if(pos>=gapstart){
      memset(&sbuffer[pos-gapstart+gapend],style,n);
      }
    else{
      memset(&sbuffer[pos],style,gapstart-pos);
      memset(&sbuffer[gapend],style,pos+n-gapstart);
      }
    updateRange(pos,pos+n);
    }
  }


// Change style of text range from style-array
void FXMathText::changeStyle(FXint pos,FXint n,const FXchar* style){
  if(n<0 || pos<0 || length<pos+n){ fxerror("%s::changeStyle: bad argument range.\n",getClassName()); }
  if(sbuffer && style){
    if(pos+n<=gapstart){
      memcpy(&sbuffer[pos],style,n);
      }
    else if(pos>=gapstart){
      memcpy(&sbuffer[pos-gapstart+gapend],style,n);
      }
    else{
      memcpy(&sbuffer[pos],style,gapstart-pos);
      memcpy(&sbuffer[gapend],&style[gapstart-pos],pos+n-gapstart);
      }
    updateRange(pos,pos+n);
    }
  }


// Change the text in the buffer to new text
void FXMathText::setStyledText(const FXchar* text,FXint n,FXint style,FXbool notify){
  FXTextChange textchange;
  if(n<0){ fxerror("%s::setStyledText: bad argument range.\n",getClassName()); }
  if(!FXRESIZE(&buffer,FXchar,n+MINSIZE)){
    fxerror("%s::setStyledText: out of memory.\n",getClassName());
    }
  memcpy(buffer,text,n);
  if(sbuffer){
    if(!FXRESIZE(&sbuffer,FXchar,n+MINSIZE)){
      fxerror("%s::setStyledText: out of memory.\n",getClassName());
      }
    memset(sbuffer,style,n);
    }
  gapstart=n;
  gapend=gapstart+MINSIZE;
  length=n;
  toppos=0;
  toprow=0;
  keeppos=0;
  selstartpos=0;
  selendpos=0;
  hilitestartpos=0;
  hiliteendpos=0;
  anchorpos=0;
  cursorpos=0;
  cursorstart=0;
  cursorend=0;
  cursorrow=0;
  cursorcol=0;
  prefcol=-1;
  pos_x=0;
  pos_y=0;
  textchange.pos=0;
  textchange.ndel=0;
  textchange.nins=n;
  textchange.ins=(FXchar*)text;
  textchange.del=(FXchar*)"";
  if(notify && target){
    target->tryHandle(this,FXSEL(SEL_INSERTED,message),(void*)&textchange);
    target->tryHandle(this,FXSEL(SEL_CHANGED,message),(void*)(FXival)cursorpos);
    }
  recalc();
  layout();
  update();
  }


// Change the text in the buffer to new text
void FXMathText::setText(const FXchar* text,FXint n,FXbool notify){
  setStyledText(text,n,0,notify);
  }


// Retrieve text into buffer
void FXMathText::getText(FXchar* text,FXint n) const {
  extractText(text,0,n);
  }


// Change all of the text
void FXMathText::setStyledText(const FXString& text,FXint style,FXbool notify){
  setStyledText(text.text(),text.length(),style,notify);
  }


// Change all of the text
void FXMathText::setText(const FXString& text,FXbool notify){
  setStyledText(text.text(),text.length(),0,notify);
  }


// We return a constant copy of the buffer
FXString FXMathText::getText() const {
  FXString value;
  FXASSERT(0<=gapstart && gapstart<=length);
  value.append(buffer,gapstart);
  value.append(&buffer[gapend],length-gapstart);
  return value;
  }


// Perform belated layout

long FXMathText::onUpdate(FXObject* sender,FXSelector sel,void* ptr){
  FXScrollArea::onUpdate(sender,sel,ptr);
  // FIXME full text reflow should be done by delayed layout,
  // rather than immediately.
  return 1;
  }


// Completely reflow the text, because font, wrapwidth, or all of the
// text may have changed and everything needs to be recomputed

void FXMathText::recompute(){
  FXint hh=font->getFontHeight();
  FXint ww1,ww2,ww3,hh1,hh2,hh3;

  // Major recalc
  if(flags&FLAG_RECALC){

    // Make it point somewhere sensible
    if(keeppos<0) keeppos=0;
    if(keeppos>length) keeppos=length;

    // Make sure we're pointing to the start of a row again
    toppos=rowStart(keeppos);

    // Get start
    cursorstart=rowStart(cursorpos);
    cursorend=nextRow(cursorstart);
    cursorcol=indentFromPos(cursorstart,cursorpos);

    // Avoid measuring huge chunks of text twice!
    if(cursorstart<toprow){
      cursorrow=measureText(0,cursorstart,ww1,hh1);
      toprow=cursorrow+measureText(cursorstart,toppos,ww2,hh2);
      nrows=toprow+measureText(toppos,length+1,ww3,hh3);
      }
    else{
      toprow=measureText(0,toppos,ww1,hh1);
      cursorrow=toprow+measureText(toppos,cursorstart,ww2,hh2);
      nrows=cursorrow+measureText(cursorstart,length+1,ww3,hh3);
      }

    textWidth=FXMAX3(ww1,ww2,ww3);
    textHeight=hh1+hh2+hh3;

    // Adjust position; we keep the same fractional position
    pos_y=-toprow*hh-(-pos_y%hh);
    }

  // Number of visible lines may have changed
  nvisrows=(height-margintop-marginbottom+hh+hh-1)/hh;
  if(nvisrows<1) nvisrows=1;

  // Number of visible lines changed; lines is 1 longer than nvisrows,
  // so we can find the end of a line faster for every visible line
  FXRESIZE(&visrows,FXint,nvisrows+1);

  // Recompute line starts
  calcVisRows(0,nvisrows);

  FXTRACE((150,"recompute : toprow=%d toppos=%d nrows=%d nvisrows=%d textWidth=%d textHeight=%d length=%d cursorrow=%d cursorcol=%d\n",toprow,toppos,nrows,nvisrows,textWidth,textHeight,length,cursorrow,cursorcol));

  // Done with that
  flags&=~(FLAG_RECALC|FLAG_DIRTY);
  }


/*******************************************************************************/


// Determine content width of scroll area
FXint FXMathText::getContentWidth(){
  if(flags&FLAG_DIRTY) recompute();
  return marginleft+barwidth+marginright+textWidth;
  }


// Determine content height of scroll area
FXint FXMathText::getContentHeight(){
  if(flags&FLAG_DIRTY) recompute();
  return margintop+marginbottom+textHeight;
  }


// Recalculate layout

void FXMathText::layout(){

  // Compute new wrap width
  if(!(options&TEXT_FIXEDWRAP)){
    wrapwidth=width-marginleft-barwidth-marginright;
    if(!(options&VSCROLLER_NEVER)) wrapwidth-=vertical->getDefaultWidth();
    }
  else{
    wrapwidth=wrapcolumns*font->getTextWidth(" ",1);
    }

  // Scrollbars adjusted
  FXScrollArea::layout();

  // Set line size based on font
  vertical->setLine(font->getFontHeight());
  horizontal->setLine(font->getTextWidth(" ",1));

  // Force repaint
  update();

  // Done
  flags&=~FLAG_DIRTY;
  }


// The widget is resized
void FXMathText::resize(FXint w,FXint h){
  FXint hh=font->getFontHeight();
  FXint nv=(h-margintop-marginbottom+hh+hh-1)/hh;
  if(nv<1) nv=1;

  // In wrap mode, a width change causes a content recalculation
  if((options&TEXT_WORDWRAP) && !(options&TEXT_FIXEDWRAP) && (width!=w)) flags|=(FLAG_RECALC|FLAG_DIRTY);

  // Need to redo line starts
  if(nv!=nvisrows) flags|=FLAG_DIRTY;

  // Resize the window, and do layout
  FXScrollArea::resize(w,h);
  }


// The widget is moved and possibly resized
void FXMathText::position(FXint x,FXint y,FXint w,FXint h){
  FXint hh=font->getFontHeight();
  FXint nv=(h-margintop-marginbottom+hh+hh-1)/hh;
  if(nv<1) nv=1;
  //FXTRACE((100,"FXMathText::position width=%d height=%d w=%d h=%d hh=%d nv=%d\n",width,height,w,h,hh,nv));

  // In wrap mode, a width change causes a content recalculation
  if((options&TEXT_WORDWRAP) && !(options&TEXT_FIXEDWRAP) && (width!=w)) flags|=(FLAG_RECALC|FLAG_DIRTY);

  // Need to redo line starts
  if(nv!=nvisrows) flags|=FLAG_DIRTY;

  // Place the window, and do layout
  FXScrollArea::position(x,y,w,h);
  }


/*******************************************************************************/


// Blink the cursor
long FXMathText::onBlink(FXObject*,FXSelector,void*){
  drawCursor(flags^FLAG_CARET);
  getApp()->addTimeout(this,ID_BLINK,getApp()->getBlinkSpeed());
  return 0;
  }


// Gained focus
long FXMathText::onFocusIn(FXObject* sender,FXSelector sel,void* ptr){
  FXScrollArea::onFocusIn(sender,sel,ptr);
  getApp()->addTimeout(this,ID_BLINK,getApp()->getBlinkSpeed());
  drawCursor(FLAG_CARET);
  return 1;
  }


// Lost focus
long FXMathText::onFocusOut(FXObject* sender,FXSelector sel,void* ptr){
  FXScrollArea::onFocusOut(sender,sel,ptr);
  getApp()->removeTimeout(this,ID_BLINK);
  drawCursor(0);
  flags|=FLAG_UPDATE;
  return 1;
  }


// We were asked about tip text
long FXMathText::onQueryTip(FXObject* sender,FXSelector sel,void* ptr){
  if(FXWindow::onQueryTip(sender,sel,ptr)) return 1;
  if((flags&FLAG_TIP) && !tip.empty()){
    sender->handle(this,FXSEL(SEL_COMMAND,ID_SETSTRINGVALUE),(void*)&tip);
    return 1;
    }
  return 0;
  }


// We were asked about status text
long FXMathText::onQueryHelp(FXObject* sender,FXSelector sel,void* ptr){
  if(FXWindow::onQueryHelp(sender,sel,ptr)) return 1;
  if((flags&FLAG_HELP) && !help.empty()){
    sender->handle(this,FXSEL(SEL_COMMAND,ID_SETSTRINGVALUE),(void*)&help);
    return 1;
    }
  return 0;
  }


// Flash matching brace
long FXMathText::onFlash(FXObject*,FXSelector,void*){
  killHighlight();
  return 0;
  }


// Pressed left button
long FXMathText::onLeftBtnPress(FXObject*,FXSelector,void* ptr){
  FXEvent* event=(FXEvent*)ptr;
  FXint pos;
  flags&=~FLAG_TIP;
  handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
  if(isEnabled()){
    grab();
    if(target && target->tryHandle(this,FXSEL(SEL_LEFTBUTTONPRESS,message),ptr)) return 1;
    flags&=~FLAG_UPDATE;

    // Select characters
    if(event->click_count==1){
      pos=getPosAt(event->win_x,event->win_y);
      FXTRACE((150,"getPosAt(%d,%d) = %d getYOfPos(%d) = %d getXOfPos(%d)=%d\n",event->win_x,event->win_y,pos,pos,getYOfPos(pos),pos,getXOfPos(pos)));
      setCursorPos(pos,TRUE);
      makePositionVisible(pos);
      if(event->state&SHIFTMASK){
        extendSelection(pos,SELECT_CHARS,TRUE);
        }
      else{
        killSelection(TRUE);
        setAnchorPos(pos);
        flashMatching();
        }
      mode=MOUSE_CHARS;
      }

    // Select words
    else if(event->click_count==2){
      setAnchorPos(cursorpos);
      extendSelection(cursorpos,SELECT_WORDS,TRUE);
      mode=MOUSE_WORDS;
      }

    // Select lines
    else{
      setAnchorPos(cursorpos);
      extendSelection(cursorpos,SELECT_LINES,TRUE);
      mode=MOUSE_LINES;
      }
    return 1;
    }
  return 0;
  }


// Released left button
long FXMathText::onLeftBtnRelease(FXObject*,FXSelector,void* ptr){
  if(isEnabled()){
    ungrab();
    mode=MOUSE_NONE;
    stopAutoScroll();
    if(target && target->tryHandle(this,FXSEL(SEL_LEFTBUTTONRELEASE,message),ptr)) return 1;
    return 1;
    }
  return 0;
  }


// Pressed middle button
long FXMathText::onMiddleBtnPress(FXObject*,FXSelector,void* ptr){
  FXEvent* event=(FXEvent*)ptr;
  FXint pos;
  flags&=~FLAG_TIP;
  handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
  if(isEnabled()){
    grab();
    if(target && target->tryHandle(this,FXSEL(SEL_MIDDLEBUTTONPRESS,message),ptr)) return 1;
    pos=getPosAt(event->win_x,event->win_y);

    // Move over
    setCursorPos(pos,TRUE);
    makePositionVisible(pos);

    // Start text drag
    if(isPosSelected(pos)){
      mode=MOUSE_TRYDRAG;
      }
    flags&=~FLAG_UPDATE;
    return 1;
    }
  return 0;
  }


// Released middle button
long FXMathText::onMiddleBtnRelease(FXObject*,FXSelector,void* ptr){
  FXuint md=mode;
  if(isEnabled()){
    ungrab();
    stopAutoScroll();
    mode=MOUSE_NONE;
    if(target && target->tryHandle(this,FXSEL(SEL_MIDDLEBUTTONRELEASE,message),ptr)) return 1;

    // Drop text somewhere
    if(md==MOUSE_DRAG){
      handle(this,FXSEL(SEL_ENDDRAG,0),ptr);
      }

    // Paste selection
    else{
      handle(this,FXSEL(SEL_COMMAND,ID_PASTE_MIDDLE),NULL);
      }
    return 1;
    }
  return 0;
  }


// Pressed right button
long FXMathText::onRightBtnPress(FXObject*,FXSelector,void* ptr){
  FXEvent* event=(FXEvent*)ptr;
  flags&=~FLAG_TIP;
  handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
  if(isEnabled()){
    grab();
    if(target && target->tryHandle(this,FXSEL(SEL_RIGHTBUTTONPRESS,message),ptr)) return 1;
    mode=MOUSE_SCROLL;
    grabx=event->win_x-pos_x;
    graby=event->win_y-pos_y;
    flags&=~FLAG_UPDATE;
    return 1;
    }
  return 0;
  }


// Released right button
long FXMathText::onRightBtnRelease(FXObject*,FXSelector,void* ptr){
  if(isEnabled()){
    ungrab();
    mode=MOUSE_NONE;
    if(target && target->tryHandle(this,FXSEL(SEL_RIGHTBUTTONRELEASE,message),ptr)) return 1;
    return 1;
    }
  return 0;
  }


// The widget lost the grab for some reason
long FXMathText::onUngrabbed(FXObject* sender,FXSelector sel,void* ptr){
  FXScrollArea::onUngrabbed(sender,sel,ptr);
  mode=MOUSE_NONE;
  flags|=FLAG_UPDATE;
  stopAutoScroll();
  return 1;
  }


// Autoscroll timer fired
long FXMathText::onAutoScroll(FXObject* sender,FXSelector sel,void* ptr){
  FXEvent* event=(FXEvent*)ptr;
  FXint pos;
  FXScrollArea::onAutoScroll(sender,sel,ptr);
  switch(mode){
    case MOUSE_CHARS:
      if((fxabs(event->win_x-event->click_x)>getApp()->getDragDelta())||(fxabs(event->win_y-event->click_y)>getApp()->getDragDelta())){
        pos=getPosAt(event->win_x,event->win_y);
        extendSelection(pos,SELECT_CHARS,TRUE);
        setCursorPos(pos,TRUE);
        }
      return 1;
    case MOUSE_WORDS:
      if((fxabs(event->win_x-event->click_x)>getApp()->getDragDelta())||(fxabs(event->win_y-event->click_y)>getApp()->getDragDelta())){
        pos=getPosAt(event->win_x,event->win_y);
        extendSelection(pos,SELECT_WORDS,TRUE);
        setCursorPos(pos,TRUE);
        }
      return 1;
    case MOUSE_LINES:
      if((fxabs(event->win_x-event->click_x)>getApp()->getDragDelta())||(fxabs(event->win_y-event->click_y)>getApp()->getDragDelta())){
        pos=getPosAt(event->win_x,event->win_y);
        extendSelection(pos,SELECT_LINES,TRUE);
        setCursorPos(pos,TRUE);
        }
      return 1;
    }
  return 0;
  }


// Handle real or simulated mouse motion
long FXMathText::onMotion(FXObject*,FXSelector,void* ptr){
  FXEvent* event=(FXEvent*)ptr;
  FXint pos;
  switch(mode){
    case MOUSE_CHARS:
      if(startAutoScroll(event,FALSE)) return 1;
      if((fxabs(event->win_x-event->click_x)>getApp()->getDragDelta())||(fxabs(event->win_y-event->click_y)>getApp()->getDragDelta())){
        pos=getPosAt(event->win_x,event->win_y);
        extendSelection(pos,SELECT_CHARS,TRUE);
        setCursorPos(pos,TRUE);
        }
      return 1;
    case MOUSE_WORDS:
      if(startAutoScroll(event,FALSE)) return 1;
      if((fxabs(event->win_x-event->click_x)>getApp()->getDragDelta())||(fxabs(event->win_y-event->click_y)>getApp()->getDragDelta())){
        pos=getPosAt(event->win_x,event->win_y);
        extendSelection(pos,SELECT_WORDS,TRUE);
        setCursorPos(pos,TRUE);
        }
      return 1;
    case MOUSE_LINES:
      if(startAutoScroll(event,FALSE)) return 1;
      if((fxabs(event->win_x-event->click_x)>getApp()->getDragDelta())||(fxabs(event->win_y-event->click_y)>getApp()->getDragDelta())){
        pos=getPosAt(event->win_x,event->win_y);
        extendSelection(pos,SELECT_LINES,TRUE);
        setCursorPos(pos,TRUE);
        }
      return 1;
    case MOUSE_SCROLL:
      setPosition(event->win_x-grabx,event->win_y-graby);
      return 1;
    case MOUSE_DRAG:
      handle(this,FXSEL(SEL_DRAGGED,0),ptr);
      return 1;
    case MOUSE_TRYDRAG:
      if(event->moved){
        mode=MOUSE_NONE;
        if(handle(this,FXSEL(SEL_BEGINDRAG,0),ptr)){
          mode=MOUSE_DRAG;
          }
        }
      return 1;
    }
  return 0;
  }


/*******************************************************************************/


// Start a drag operation
long FXMathText::onBeginDrag(FXObject* sender,FXSelector sel,void* ptr){
  if(FXScrollArea::onBeginDrag(sender,sel,ptr)) return 1;
  beginDrag(&textType,1);
  setDragCursor(getApp()->getDefaultCursor(DEF_DNDSTOP_CURSOR));
  return 1;
  }


// End drag operation
long FXMathText::onEndDrag(FXObject* sender,FXSelector sel,void* ptr){
  if(FXScrollArea::onEndDrag(sender,sel,ptr)) return 1;
  endDrag((didAccept()!=DRAG_REJECT));
  setDragCursor(getApp()->getDefaultCursor(DEF_TEXT_CURSOR));
  return 1;
  }


// Dragged stuff around
long FXMathText::onDragged(FXObject* sender,FXSelector sel,void* ptr){
  FXEvent* event=(FXEvent*)ptr;
  FXDragAction action;
  if(FXScrollArea::onDragged(sender,sel,ptr)) return 1;
  action=DRAG_COPY;
  if(isEditable()){
    if(isDropTarget()) action=DRAG_MOVE;
    if(event->state&CONTROLMASK) action=DRAG_COPY;
    if(event->state&SHIFTMASK) action=DRAG_MOVE;
    }
  handleDrag(event->root_x,event->root_y,action);
  if(didAccept()!=DRAG_REJECT){
    if(action==DRAG_MOVE)
      setDragCursor(getApp()->getDefaultCursor(DEF_DNDMOVE_CURSOR));
    else
      setDragCursor(getApp()->getDefaultCursor(DEF_DNDCOPY_CURSOR));
    }
  else{
    setDragCursor(getApp()->getDefaultCursor(DEF_DNDSTOP_CURSOR));
    }
  return 1;
  }


// Handle drag-and-drop enter
long FXMathText::onDNDEnter(FXObject* sender,FXSelector sel,void* ptr){
  FXScrollArea::onDNDEnter(sender,sel,ptr);
  drawCursor(FLAG_CARET);
  revertpos=cursorpos;
  return 1;
  }


// Handle drag-and-drop leave
long FXMathText::onDNDLeave(FXObject* sender,FXSelector sel,void* ptr){
  FXScrollArea::onDNDLeave(sender,sel,ptr);
  stopAutoScroll();
  drawCursor(0);
  setCursorPos(revertpos,TRUE);
  return 1;
  }


// Handle drag-and-drop motion
long FXMathText::onDNDMotion(FXObject* sender,FXSelector sel,void* ptr){
  FXEvent* event=(FXEvent*)ptr;
  FXDragAction action;
  FXint pos;

  // Scroll into view
  if(startAutoScroll(event,TRUE)) return 1;

  // Handled elsewhere
  if(FXScrollArea::onDNDMotion(sender,sel,ptr)) return 1;

  // Correct drop type
  if(offeredDNDType(FROM_DRAGNDROP,textType)){

    // Is target editable?
    if(isEditable()){
      action=inquireDNDAction();

      // Check for legal DND action
      if(action==DRAG_COPY || action==DRAG_MOVE){

        // Get the suggested drop position
        pos=getPosAt(event->win_x,event->win_y);

        // Move cursor to new position
        setCursorPos(pos,TRUE);
        makePositionVisible(pos);

        // We don't accept a drop on the selection
        if(!isPosSelected(pos)){
          acceptDrop(DRAG_ACCEPT);
          }
        }
      }
    return 1;
    }

  // Didn't handle it here
  return 0;
  }


// Handle drag-and-drop drop
long FXMathText::onDNDDrop(FXObject* sender,FXSelector sel,void* ptr){
  FXuchar *data,*junk; FXuint len,dum;

  // Stop scrolling
  stopAutoScroll();
  drawCursor(0);

  // Try handling it in base class first
  if(FXScrollArea::onDNDDrop(sender,sel,ptr)) return 1;

  // Should really not have gotten this if non-editable
  if(isEditable()){

    // Try handle here
    if(getDNDData(FROM_DRAGNDROP,textType,data,len)){
      FXRESIZE(&data,FXchar,len+1); data[len]='\0';

      // Need to ask the source to delete his copy
      if(inquireDNDAction()==DRAG_MOVE){
        getDNDData(FROM_DRAGNDROP,deleteType,junk,dum);
        FXASSERT(!junk);
        }

      // Insert the new text
      handle(this,FXSEL(SEL_COMMAND,ID_INSERT_STRING),(void*)data);
      FXFREE(&data);
      }
    return 1;
    }
  return 0;
  }


// Service requested DND data
long FXMathText::onDNDRequest(FXObject* sender,FXSelector sel,void* ptr){
  FXEvent *event=(FXEvent*)ptr; FXuchar *data; FXuint len;

  // Perhaps the target wants to supply its own data
  if(FXScrollArea::onDNDRequest(sender,sel,ptr)) return 1;

  // Return dragged text
  if(event->target==textType){
    len=selendpos-selstartpos;
    FXMALLOC(&data,FXuchar,len);
    extractText((FXchar*)data,selstartpos,len);
    setDNDData(FROM_DRAGNDROP,textType,data,len);
    return 1;
    }

  // Delete dragged text
  if(event->target==deleteType){
    if(isEditable()){
      handle(this,FXSEL(SEL_COMMAND,ID_DELETE_SEL),NULL);
      }
    return 1;
    }

  return 0;
  }


/*******************************************************************************/


// We now really do have the selection
long FXMathText::onSelectionGained(FXObject* sender,FXSelector sel,void* ptr){
  FXScrollArea::onSelectionGained(sender,sel,ptr);
  return 1;
  }


// We lost the selection somehow
long FXMathText::onSelectionLost(FXObject* sender,FXSelector sel,void* ptr){
  FXint what[2];
  FXScrollArea::onSelectionLost(sender,sel,ptr);
  if(target){
    what[0]=selstartpos;
    what[1]=selendpos-selstartpos;
    target->tryHandle(this,FXSEL(SEL_DESELECTED,message),(void*)what);
    }
  updateRange(selstartpos,selendpos);
  selstartpos=0;
  selendpos=0;
  return 1;
  }


// Somebody wants our selection
long FXMathText::onSelectionRequest(FXObject* sender,FXSelector sel,void* ptr){
  FXEvent *event=(FXEvent*)ptr; FXchar *data; FXint len;

  // Perhaps the target wants to supply its own data for the selection
  if(FXScrollArea::onSelectionRequest(sender,sel,ptr)) return 1;

  // Return text of the selection
  if(event->target==stringType || event->target==textType){
    len=selendpos-selstartpos;
    FXMALLOC(&data,FXchar,len);
    extractText(data,selstartpos,len);
#ifdef WIN32
    fxtoDOS(data,len);
#endif
    setDNDData(FROM_SELECTION,event->target,(FXuchar*)data,(FXuint)len);
    return 1;
    }

  return 0;
  }


/*******************************************************************************/


// We now really do have the selection
long FXMathText::onClipboardGained(FXObject* sender,FXSelector sel,void* ptr){
  FXScrollArea::onClipboardGained(sender,sel,ptr);
  return 1;
  }


// We lost the selection somehow
long FXMathText::onClipboardLost(FXObject* sender,FXSelector sel,void* ptr){
  FXScrollArea::onClipboardLost(sender,sel,ptr);
  FXFREE(&clipbuffer);
  clipbuffer=NULL;
  cliplength=0;
  return 1;
  }


// Somebody wants our selection
long FXMathText::onClipboardRequest(FXObject* sender,FXSelector sel,void* ptr){
  FXEvent *event=(FXEvent*)ptr; FXchar *data; FXint len;

  // Try handling it in base class first
  if(FXScrollArea::onClipboardRequest(sender,sel,ptr)) return 1;

  // Requested data from clipboard
  if(event->target==stringType || event->target==textType){
    len=cliplength;
    FXMALLOC(&data,FXchar,len);
    memcpy(data,clipbuffer,len);
#ifdef WIN32
    fxtoDOS(data,len);
#endif
    setDNDData(FROM_CLIPBOARD,event->target,(FXuchar*)data,(FXuint)len);
    return 1;
    }

  return 0;
  }

/*******************************************************************************/

// Keyboard press
long FXMathText::onKeyPress(FXObject*,FXSelector,void* ptr){
  FXEvent* event=(FXEvent*)ptr;
  flags&=~FLAG_TIP;
  if(!isEnabled()) return 0;
    FXTRACE((200,"%s::onKeyPress keysym=0x%04x state=%04x\n",getClassName(),event->code,event->state));
    if(target && target->tryHandle(this,FXSEL(SEL_KEYPRESS,message),ptr)) return 1;
    flags&=~FLAG_UPDATE;
    switch(event->code){
      case KEY_Shift_L:
      case KEY_Shift_R:
      case KEY_Control_L:
      case KEY_Control_R:
        if(mode==MOUSE_DRAG){handle(this,FXSEL(SEL_DRAGGED,0),ptr);}
        return 1;
      case KEY_Up:
      case KEY_KP_Up:
        if(event->state&CONTROLMASK){
          handle(this,FXSEL(SEL_COMMAND,ID_SCROLL_UP),NULL);
          }
        else{
          if(!(event->state&SHIFTMASK)){
            handle(this,FXSEL(SEL_COMMAND,ID_DESELECT_ALL),NULL);
            }
          handle(this,FXSEL(SEL_COMMAND,ID_CURSOR_UP),NULL);
          if(event->state&SHIFTMASK){
            handle(this,FXSEL(SEL_COMMAND,ID_EXTEND),NULL);
            }
          else{
            handle(this,FXSEL(SEL_COMMAND,ID_MARK),NULL);
            }
          }
        return 1;
      case KEY_Down:
      case KEY_KP_Down:
        if(event->state&CONTROLMASK){
          handle(this,FXSEL(SEL_COMMAND,ID_SCROLL_DOWN),NULL);
          }
        else{
          if(!(event->state&SHIFTMASK)){
            handle(this,FXSEL(SEL_COMMAND,ID_DESELECT_ALL),NULL);
            }
          handle(this,FXSEL(SEL_COMMAND,ID_CURSOR_DOWN),NULL);
          if(event->state&SHIFTMASK){
            handle(this,FXSEL(SEL_COMMAND,ID_EXTEND),NULL);
            }
          else{
            handle(this,FXSEL(SEL_COMMAND,ID_MARK),NULL);
            }
          }
        return 1;
      case KEY_Left:
      case KEY_KP_Left:
        if(!(event->state&SHIFTMASK)){
          handle(this,FXSEL(SEL_COMMAND,ID_DESELECT_ALL),NULL);
          }
        if(event->state&CONTROLMASK){
          handle(this,FXSEL(SEL_COMMAND,ID_CURSOR_WORD_LEFT),NULL);
          }
        else{
          handle(this,FXSEL(SEL_COMMAND,ID_CURSOR_LEFT),NULL);
          }
        if(event->state&SHIFTMASK){
          handle(this,FXSEL(SEL_COMMAND,ID_EXTEND),NULL);
          }
        else{
          handle(this,FXSEL(SEL_COMMAND,ID_MARK),NULL);
          }
        return 1;
      case KEY_Right:
      case KEY_KP_Right:
        if(!(event->state&SHIFTMASK)){
          handle(this,FXSEL(SEL_COMMAND,ID_DESELECT_ALL),NULL);
          }
        if(event->state&CONTROLMASK){
          handle(this,FXSEL(SEL_COMMAND,ID_CURSOR_WORD_RIGHT),NULL);
          }
        else{
          handle(this,FXSEL(SEL_COMMAND,ID_CURSOR_RIGHT),NULL);
          }
        if(event->state&SHIFTMASK){
          handle(this,FXSEL(SEL_COMMAND,ID_EXTEND),NULL);
          }
        else{
          handle(this,FXSEL(SEL_COMMAND,ID_MARK),NULL);
          }
        return 1;
      case KEY_Home:
      case KEY_KP_Home:
        if(!(event->state&SHIFTMASK)){
          handle(this,FXSEL(SEL_COMMAND,ID_DESELECT_ALL),NULL);
          }
        if(event->state&CONTROLMASK){
          handle(this,FXSEL(SEL_COMMAND,ID_CURSOR_TOP),NULL);
          }
        else{
          handle(this,FXSEL(SEL_COMMAND,ID_CURSOR_HOME),NULL);
          }
        if(event->state&SHIFTMASK){
          handle(this,FXSEL(SEL_COMMAND,ID_EXTEND),NULL);
          }
        else{
          handle(this,FXSEL(SEL_COMMAND,ID_MARK),NULL);
          }
        return 1;
      case KEY_End:
      case KEY_KP_End:
        if(!(event->state&SHIFTMASK)){
          handle(this,FXSEL(SEL_COMMAND,ID_DESELECT_ALL),NULL);
          }
        if(event->state&CONTROLMASK){
          handle(this,FXSEL(SEL_COMMAND,ID_CURSOR_BOTTOM),NULL);
          }
        else{
          handle(this,FXSEL(SEL_COMMAND,ID_CURSOR_END),NULL);
          }
        if(event->state&SHIFTMASK){
          handle(this,FXSEL(SEL_COMMAND,ID_EXTEND),NULL);
          }
        else{
          handle(this,FXSEL(SEL_COMMAND,ID_MARK),NULL);
          }
        return 1;
      case KEY_Page_Up:
      case KEY_KP_Page_Up:
        if(!(event->state&SHIFTMASK)){
          handle(this,FXSEL(SEL_COMMAND,ID_DESELECT_ALL),NULL);
          }
        handle(this,FXSEL(SEL_COMMAND,ID_CURSOR_PAGEUP),NULL);
        if(event->state&SHIFTMASK){
          handle(this,FXSEL(SEL_COMMAND,ID_EXTEND),NULL);
          }
        else{
          handle(this,FXSEL(SEL_COMMAND,ID_MARK),NULL);
          }
        return 1;
      case KEY_Page_Down:
      case KEY_KP_Page_Down:
        if(!(event->state&SHIFTMASK)){
          handle(this,FXSEL(SEL_COMMAND,ID_DESELECT_ALL),NULL);
          }
        handle(this,FXSEL(SEL_COMMAND,ID_CURSOR_PAGEDOWN),NULL);
        if(event->state&SHIFTMASK){
          handle(this,FXSEL(SEL_COMMAND,ID_EXTEND),NULL);
          }
        else{
          handle(this,FXSEL(SEL_COMMAND,ID_MARK),NULL);
          }
        return 1;
      case KEY_Insert:
      case KEY_KP_Insert:
        if(event->state&CONTROLMASK){
          handle(this,FXSEL(SEL_COMMAND,ID_COPY_SEL),NULL);
          }
        else if(event->state&SHIFTMASK){
          if(isEditable()){
            handle(this,FXSEL(SEL_COMMAND,ID_PASTE_SEL),NULL);
            }
          else{
            getApp()->beep();
            }
          }
        else{
          handle(this,FXSEL(SEL_COMMAND,ID_TOGGLE_OVERSTRIKE),NULL);
          }
        return 1;
      case KEY_Delete:
      case KEY_KP_Delete:
        if(isEditable()){
          if(isPosSelected(cursorpos)){
            if(event->state&SHIFTMASK){
              handle(this,FXSEL(SEL_COMMAND,ID_CUT_SEL),NULL);
              }
            else{
              handle(this,FXSEL(SEL_COMMAND,ID_DELETE_SEL),NULL);
              }
            }
          else{
            handle(this,FXSEL(SEL_COMMAND,ID_DESELECT_ALL),NULL);
            if(event->state&CONTROLMASK){
              handle(this,FXSEL(SEL_COMMAND,ID_DELETE_WORD),NULL);
              }
            else if(event->state&SHIFTMASK){
              handle(this,FXSEL(SEL_COMMAND,ID_DELETE_EOL),NULL);
              }
            else{
              handle(this,FXSEL(SEL_COMMAND,ID_DELETE),NULL);
              }
            }
          }
        else{
          getApp()->beep();
          }
        return 1;
      case KEY_BackSpace:
        if(isEditable()){
          if(isPosSelected(cursorpos)){
            handle(this,FXSEL(SEL_COMMAND,ID_DELETE_SEL),NULL);
            }
          else{
           handle(this,FXSEL(SEL_COMMAND,ID_DESELECT_ALL),NULL);
           if(event->state&CONTROLMASK){
              handle(this,FXSEL(SEL_COMMAND,ID_BACKSPACE_WORD),NULL);
              }
            else if(event->state&SHIFTMASK){
              handle(this,FXSEL(SEL_COMMAND,ID_BACKSPACE_BOL),NULL);
              }
            else{
              handle(this,FXSEL(SEL_COMMAND,ID_BACKSPACE),NULL);
              }
            }
          }
        else{
          getApp()->beep();
          }
        return 1;
      case KEY_Return:
      case KEY_KP_Enter:
        if(isEditable()){
          handle(this,FXSEL(SEL_COMMAND,ID_INSERT_NEWLINE),NULL);
          }
        else{
          getApp()->beep();
          }
        return 1;
      case KEY_Tab:
      case KEY_KP_Tab:
        if(isEditable()){
          if(event->state&CONTROLMASK){
            handle(this,FXSEL(SEL_COMMAND,ID_INSERT_STRING),(void*)"\t");
            }
          else{
            handle(this,FXSEL(SEL_COMMAND,ID_INSERT_TAB),NULL);
            }
          }
        else{
          getApp()->beep();
          }
        return 1;
      case KEY_a:
        if(!(event->state&CONTROLMASK)) goto ins;
        handle(this,FXSEL(SEL_COMMAND,ID_SELECT_ALL),NULL);
        return 1;
      case KEY_x:
        if(!(event->state&CONTROLMASK)) goto ins;
      case KEY_F20:                               // Sun Cut key
        if(isEditable()){
          handle(this,FXSEL(SEL_COMMAND,ID_CUT_SEL),NULL);
          }
        else{
          getApp()->beep();
          }
        return 1;
      case KEY_c:
        if(!(event->state&CONTROLMASK)) goto ins;
      case KEY_F16:                               // Sun Copy key
        handle(this,FXSEL(SEL_COMMAND,ID_COPY_SEL),NULL);
        return 1;
      case KEY_v:
        if(!(event->state&CONTROLMASK)) goto ins;
      case KEY_F18:                               // Sun Paste key
        if(isEditable()){
          handle(this,FXSEL(SEL_COMMAND,ID_PASTE_SEL),NULL);
          }
        else{
          getApp()->beep();
          }
        return 1;
      default:
ins:    if((event->state&(CONTROLMASK|ALTMASK)) || ((FXuchar)event->text[0]<32)) return 0;
        if(isEditable()){
          if(options&TEXT_OVERSTRIKE){
            handle(this,FXSEL(SEL_COMMAND,ID_OVERST_STRING),(void*)event->text.text());
            }
          else{
            handle(this,FXSEL(SEL_COMMAND,ID_INSERT_STRING),(void*)event->text.text());
            }
          }
        else{
          getApp()->beep();
          }
        return 1;
      }
  return 0;
  }


// Keyboard release
long FXMathText::onKeyRelease(FXObject*,FXSelector,void* ptr){
  FXEvent* event=(FXEvent*)ptr;
  if(!isEnabled()) return 0;
    FXTRACE((200,"%s::onKeyRelease keysym=0x%04x state=%04x\n",getClassName(),event->code,event->state));
    if(target && target->tryHandle(this,FXSEL(SEL_KEYRELEASE,message),ptr)) return 1;
    switch(event->code){
      case KEY_Shift_L:
      case KEY_Shift_R:
      case KEY_Control_L:
      case KEY_Control_R:
        if(mode==MOUSE_DRAG){handle(this,FXSEL(SEL_DRAGGED,0),ptr);}
        return 1;
      }
  return 0;
  }


/*******************************************************************************/

// Move cursor to top of buffer
long FXMathText::onCmdCursorTop(FXObject*,FXSelector,void*){
  setCursorPos(0,TRUE);
  makePositionVisible(cursorpos);
  flashMatching();
  return 1;
  }


// Move cursor to bottom of buffer
long FXMathText::onCmdCursorBottom(FXObject*,FXSelector,void*){
  setCursorPos(length,TRUE);
  makePositionVisible(cursorpos);
  flashMatching();
  return 1;
  }


// Move cursor to begin of line (well, beginning of ROW?)

long FXMathText::onCmdCursorHome(FXObject*,FXSelector,void*){
  setCursorPos(rowStart(cursorpos),TRUE);
  makePositionVisible(cursorpos);
  flashMatching();
  return 1;
  }


// Move cursor to end of line (ROW?)

long FXMathText::onCmdCursorEnd(FXObject*,FXSelector,void*){
  setCursorPos(rowEnd(cursorpos),TRUE);
  makePositionVisible(cursorpos);
  flashMatching();
  return 1;
  }


// Move cursor right

long FXMathText::onCmdCursorRight(FXObject*,FXSelector,void*){
  if(cursorpos>=length) return 1;
// skip over body of a maths line...
  if(getChar(cursorpos)==0x02) cursorpos=lineEnd(cursorpos);
  setCursorPos(cursorpos+1,TRUE);
  makePositionVisible(cursorpos);
  flashMatching();
  return 1;
  }


// Move cursor left

// @@@@@ move back onto maths

long FXMathText::onCmdCursorLeft(FXObject*,FXSelector,void*){
  if(cursorpos<=0) return 1;
  setCursorPos(cursorpos-1,TRUE);
  makePositionVisible(cursorpos);
  flashMatching();
  return 1;
  }


// Move cursor to previous line (or ROW???)

long FXMathText::onCmdCursorUp(FXObject*,FXSelector,void*){
  FXint newrow,newpos,col;
  col=(0<=prefcol) ? prefcol : cursorcol;
  newrow=prevRow(cursorpos);
  newpos=posFromIndent(newrow,col);
  setCursorPos(newpos,TRUE);
  makePositionVisible(cursorpos);
  flashMatching();
  prefcol=col;
  return 1;
  }


// Move cursor to next line (or ROW???)

long FXMathText::onCmdCursorDown(FXObject*,FXSelector,void*){
  FXint newrow,newpos,col;
  col=(0<=prefcol) ? prefcol : cursorcol;
  newrow=nextRow(cursorpos);
  newpos=posFromIndent(newrow,col);
  setCursorPos(newpos,TRUE);
  makePositionVisible(cursorpos);
  flashMatching();
  prefcol=col;
  return 1;
  }


// Page down

long FXMathText::onCmdCursorPageDown(FXObject*,FXSelector,void*){
  FXint newrow,newpos,col;
  col=(0<=prefcol) ? prefcol : cursorcol;
  newrow=nextRow(cursorpos,(viewport_h)/font->getFontHeight());
  newpos=posFromIndent(newrow,col);
  setTopLine(nextRow(toppos,viewport_h/font->getFontHeight()));
  setCursorPos(newpos,TRUE);
  makePositionVisible(cursorpos);
  prefcol=col;
  return 1;
  }


// Page up

long FXMathText::onCmdCursorPageUp(FXObject*,FXSelector,void*){
  FXint newrow,newpos,col;
  col=(0<=prefcol) ? prefcol : cursorcol;
  newrow=prevRow(cursorpos,(viewport_h)/font->getFontHeight());
  newpos=posFromIndent(newrow,col);
  setTopLine(prevRow(toppos,viewport_h/font->getFontHeight()));
  setCursorPos(newpos,TRUE);
  makePositionVisible(cursorpos);
  prefcol=col;
  return 1;
  }


// Word Left

// @@@@@ over line boundary into maths?

long FXMathText::onCmdCursorWordLeft(FXObject*,FXSelector,void*){
  setCursorPos(leftWord(cursorpos),TRUE);
  makePositionVisible(cursorpos);
  flashMatching();
  return 1;
  }


// Word Right

// @@@@@ over line boundary into maths?

long FXMathText::onCmdCursorWordRight(FXObject*,FXSelector,void*){
  setCursorPos(rightWord(cursorpos),TRUE);
  makePositionVisible(cursorpos);
  flashMatching();
  return 1;
  }


// Word Start
long FXMathText::onCmdCursorWordStart(FXObject*,FXSelector,void*){
  setCursorPos(wordStart(cursorpos),TRUE);
  makePositionVisible(cursorpos);
  flashMatching();
  return 1;
  }


// Word End
long FXMathText::onCmdCursorWordEnd(FXObject*,FXSelector,void*){
  setCursorPos(wordEnd(cursorpos),TRUE);
  makePositionVisible(cursorpos);
  flashMatching();
  return 1;
  }


// Cursor pos to top of screen
long FXMathText::onCmdCursorScreenTop(FXObject*,FXSelector,void*){
  setTopLine(cursorpos);
  return 1;
  }


// Cursor pos to bottom of screen
long FXMathText::onCmdCursorScreenBottom(FXObject*,FXSelector,void*){
  setBottomLine(cursorpos);
  return 1;
  }


// Cursor pos to center of screen
long FXMathText::onCmdCursorScreenCenter(FXObject*,FXSelector,void*){
  setCenterLine(cursorpos);
  return 1;
  }


// Scroll up one line, leaving cursor in place
long FXMathText::onCmdScrollUp(FXObject*,FXSelector,void*){
  setTopLine(prevRow(toppos,1));
  return 1;
  }


// Scroll down one line, leaving cursor in place
long FXMathText::onCmdScrollDown(FXObject*,FXSelector,void*){
  setTopLine(nextRow(toppos,1));
  return 1;
  }


// Move cursor to begin of paragraph
long FXMathText::onCmdCursorParHome(FXObject*,FXSelector,void*){
  setCursorPos(lineStart(cursorpos),TRUE);
  makePositionVisible(cursorpos);
  return 1;
  }


// Move cursor to end of paragraph
long FXMathText::onCmdCursorParEnd(FXObject*,FXSelector,void*){
  setCursorPos(lineEnd(cursorpos),TRUE);
  makePositionVisible(cursorpos);
  return 1;
  }


// Mark
long FXMathText::onCmdMark(FXObject*,FXSelector,void*){
  setAnchorPos(cursorpos);
  return 1;
  }


// Extend
long FXMathText::onCmdExtend(FXObject*,FXSelector,void*){
  extendSelection(cursorpos,SELECT_CHARS,TRUE);
  return 1;
  }


// Overstrike a string
long FXMathText::onCmdOverstString(FXObject*,FXSelector,void* ptr){
  FXint sindent,oindent,nindent,pos,ch,reppos,replen;
  FXchar* string=(FXchar*)ptr;
  FXint len=strlen(string);
  if(!isEditable()) return 1;
  if(isPosSelected(cursorpos)){
    reppos=selstartpos;
    replen=selendpos-selstartpos;
    }
  else{
    sindent=0;
    pos=lineStart(cursorpos);
    while(pos<cursorpos){                               // Measure indent of reppos
      if(getChar(pos)=='\t')
        sindent+=(tabcolumns-sindent%tabcolumns);
      else
        sindent+=1;
      pos++;
      }
    nindent=sindent;
    pos=0;
    while(pos<len){                                     // Measure indent of new string
      if(string[pos]=='\t')
        nindent+=(tabcolumns-nindent%tabcolumns);
      else
        nindent+=1;
      pos++;
      }
    oindent=sindent;
    pos=cursorpos;
    while(pos<length && (ch=getChar(pos))!='\n'){       // Measure indent of old string
      if(ch=='\t')
        oindent+=(tabcolumns-oindent%tabcolumns);
      else
        oindent+=1;
      if(oindent==nindent){                             // Same indent
        pos++;                                          // Include last character
        break;
        }
      if(oindent>nindent){                              // Greater indent
        if(ch!='\t') pos++;                             // Don't include last character if it was a tab
        break;
        }
      pos++;
      }
    reppos=cursorpos;
    replen=pos-reppos;
    }
  replaceText(reppos,replen,string,len,TRUE);
  killSelection(TRUE);
  setCursorPos(reppos+len,TRUE);
  makePositionVisible(cursorpos);
  flashMatching();
  flags|=FLAG_CHANGED;
  modified=TRUE;
  return 1;
  }


// Insert a string
long FXMathText::onCmdInsertString(FXObject*,FXSelector,void* ptr){
  FXchar* string=(FXchar*)ptr;
  FXint len=strlen(string);
  FXint reppos=cursorpos;
  FXint replen=0;
  if(!isEditable()) return 1;
  if(isPosSelected(cursorpos)){
    reppos=selstartpos;
    replen=selendpos-selstartpos;
    }
  replaceText(reppos,replen,string,len,TRUE);
  killSelection(TRUE);
  setCursorPos(reppos+len,TRUE);
  makePositionVisible(cursorpos);
  flashMatching();
  flags|=FLAG_CHANGED;
  modified=TRUE;
  return 1;
  }


// Insert a character
long FXMathText::onCmdInsertNewline(FXObject*,FXSelector,void*){
  FXint reppos=cursorpos;
  FXint replen=0;
  FXint len=1;
  if(!isEditable()) return 1;
  if(isPosSelected(cursorpos)){
    reppos=selstartpos;
    replen=selendpos-selstartpos;
    }
  if(options&TEXT_AUTOINDENT){
    FXint start=lineStart(reppos);
    FXint end=start;
    FXchar *string;
    while(end<reppos){
      if(!isspace(getChar(end))) break;
      end++;
      }
    len=end-start+1;
    FXMALLOC(&string,FXchar,len);
    string[0]='\n';
    extractText(&string[1],start,end-start);
    replaceText(reppos,replen,string,len,TRUE);
    FXFREE(&string);
    }
  else{
    replaceText(reppos,replen,"\n",1,TRUE);
    }
  setCursorPos(reppos+len,TRUE);
  makePositionVisible(cursorpos);
  flags|=FLAG_CHANGED;
  modified=TRUE;
  return 1;
  }



// Insert a character
long FXMathText::onCmdInsertTab(FXObject*,FXSelector,void*){
  FXint reppos=cursorpos;
  FXint replen=0;
  FXint len=1;
  if(!isEditable()) return 1;
  if(isPosSelected(cursorpos)){
    reppos=selstartpos;
    replen=selendpos-selstartpos;
    }
  if(options&TEXT_NO_TABS){
    FXint start=lineStart(reppos);
    FXint indent=0;
    FXchar *string;
    while(start<reppos){
      if(getChar(start)=='\t')
        indent+=(tabcolumns-indent%tabcolumns);
      else
        indent+=1;
      start++;
      }
    len=tabcolumns-indent%tabcolumns;
    FXMALLOC(&string,FXchar,len);
    memset(string,' ',len);
    replaceText(reppos,replen,string,len,TRUE);
    FXFREE(&string);
    }
  else{
    replaceText(reppos,replen,"\t",1,TRUE);
    }
  setCursorPos(reppos+len,TRUE);
  makePositionVisible(cursorpos);
  flags|=FLAG_CHANGED;
  modified=TRUE;
  return 1;
  }


// Cut

// @@@@@ cutting maths?

long FXMathText::onCmdCutSel(FXObject*,FXSelector,void*){
  FXDragType types[2];
  if(selstartpos<selendpos){
    if(isEditable()){
      types[0]=stringType;
      types[1]=textType;
      if(acquireClipboard(types,2)){
        FXFREE(&clipbuffer);
        FXASSERT(selstartpos<=selendpos);
        cliplength=selendpos-selstartpos;
        FXCALLOC(&clipbuffer,FXchar,cliplength+1);
        if(!clipbuffer){
          fxwarning("%s::onCmdCutSel: out of memory\n",getClassName());
          cliplength=0;
          }
        else{
          extractText(clipbuffer,selstartpos,cliplength);
          handle(this,FXSEL(SEL_COMMAND,ID_DELETE_SEL),NULL);
          }
        }
      }
    else{
      getApp()->beep();
      }
    }
  return 1;
  }


// Copy

// @@@@@ copying maths?

long FXMathText::onCmdCopySel(FXObject*,FXSelector,void*){
  FXDragType types[2];
  if(selstartpos<selendpos){
    types[0]=stringType;
    types[1]=textType;
    if(acquireClipboard(types,2)){
      FXFREE(&clipbuffer);
      FXASSERT(selstartpos<=selendpos);
      cliplength=selendpos-selstartpos;
      FXCALLOC(&clipbuffer,FXchar,cliplength+1);
      if(!clipbuffer){
        fxwarning("%s::onCmdCopySel: out of memory\n",getClassName());
        cliplength=0;
        }
      else{
        extractText(clipbuffer,selstartpos,cliplength);
        }
      }
    }
  return 1;
  }


// Delete selection

// @@@@@ can maths be selected?

long FXMathText::onCmdDeleteSel(FXObject*,FXSelector,void*){
  if(selstartpos<selendpos){
    if(isEditable()){
      removeText(selstartpos,selendpos-selstartpos,TRUE);
      killSelection(TRUE);
      setCursorPos(cursorpos,TRUE);
      makePositionVisible(cursorpos);
      flags|=FLAG_CHANGED;
      modified=TRUE;
      }
    else{
      getApp()->beep();
      }
    }
  return 1;
  }


// Paste clipboard
long FXMathText::onCmdPasteSel(FXObject*,FXSelector,void*){
  FXchar *data; FXint reppos,replen,len;
  if(isEditable()){
    if(getDNDData(FROM_CLIPBOARD,stringType,(FXuchar*&)data,(FXuint&)len)){
#ifdef WIN32
      fxfromDOS(data,len);
#endif
      reppos=cursorpos;
      replen=0;
      if(isPosSelected(cursorpos)){
        reppos=selstartpos;
        replen=selendpos-selstartpos;
        }
      replaceText(reppos,replen,data,len,TRUE);
      FXFREE(&data);
      killSelection(TRUE);
      setCursorPos(reppos+len,TRUE);
      makePositionVisible(cursorpos);
      flashMatching();
      flags|=FLAG_CHANGED;
      modified=TRUE;
      }
    }
  else{
    getApp()->beep();
    }
  return 1;
  }


// Paste selection
long FXMathText::onCmdPasteMiddle(FXObject*,FXSelector,void*){
  FXchar *string; FXint len;
  if(selstartpos==selendpos || cursorpos<=selstartpos || selendpos<=cursorpos){ // Avoid paste inside selection
    if(isEditable()){
      if(getDNDData(FROM_SELECTION,stringType,(FXuchar*&)string,(FXuint&)len)){
#ifdef WIN32
        fxfromDOS(string,len);
#endif
        insertText(cursorpos,string,len,TRUE);                                  // Don't kill selection; we may paste again
        FXFREE(&string);
        setCursorPos(cursorpos,TRUE);
        makePositionVisible(cursorpos);
        flashMatching();
        flags|=FLAG_CHANGED;
        modified=TRUE;
        }
      }
    else{
      getApp()->beep();
      }
    }
  return 1;
  }


// Select character
long FXMathText::onCmdSelectChar(FXObject*,FXSelector,void*){
  setAnchorPos(cursorpos);
  extendSelection(cursorpos+1,SELECT_CHARS,TRUE);
  return 1;
  }


// Select Word
long FXMathText::onCmdSelectWord(FXObject*,FXSelector,void*){
  setAnchorPos(cursorpos);
  extendSelection(cursorpos,SELECT_WORDS,TRUE);
  return 1;
  }


// Select Line
long FXMathText::onCmdSelectLine(FXObject*,FXSelector,void*){
  setAnchorPos(cursorpos);
  extendSelection(cursorpos,SELECT_LINES,TRUE);
  return 1;
  }


// Select All
long FXMathText::onCmdSelectAll(FXObject*,FXSelector,void*){
  setAnchorPos(0);
  extendSelection(length,SELECT_CHARS,TRUE);
  return 1;
  }


// Deselect All
long FXMathText::onCmdDeselectAll(FXObject*,FXSelector,void*){
  killSelection(TRUE);
  return 1;
  }


// Backspace character
long FXMathText::onCmdBackspace(FXObject*,FXSelector,void*){
  if(!isEditable()) return 1;
  if(cursorpos==0){ getApp()->beep(); return 1; }
  removeText(cursorpos-1,1,TRUE);
  setCursorPos(cursorpos,TRUE);
  makePositionVisible(cursorpos);
  flags|=FLAG_CHANGED;
  modified=TRUE;
  return 1;
  }


// Backspace word
long FXMathText::onCmdBackspaceWord(FXObject*,FXSelector,void*){
  if(!isEditable()) return 1;
  FXint pos=leftWord(cursorpos);
  removeText(pos,cursorpos-pos,TRUE);
  setCursorPos(cursorpos,TRUE);
  makePositionVisible(cursorpos);
  flags|=FLAG_CHANGED;
  modified=TRUE;
  return 1;
  }


// Backspace bol
long FXMathText::onCmdBackspaceBol(FXObject*,FXSelector,void*){
  if(!isEditable()) return 1;
  FXint pos=rowStart(cursorpos);
  removeText(pos,cursorpos-pos,TRUE);
  setCursorPos(cursorpos,TRUE);
  makePositionVisible(cursorpos);
  flags|=FLAG_CHANGED;
  modified=TRUE;
  return 1;
  }


// Delete character
long FXMathText::onCmdDelete(FXObject*,FXSelector,void*){
  if(!isEditable()) return 1;
  if(cursorpos==length){ getApp()->beep(); return 1; }
  removeText(cursorpos,1,TRUE);
  setCursorPos(cursorpos,TRUE);
  makePositionVisible(cursorpos);
  flags|=FLAG_CHANGED;
  modified=TRUE;
  return 1;
  }


// Delete word
long FXMathText::onCmdDeleteWord(FXObject*,FXSelector,void*){
  if(!isEditable()) return 1;
  FXint num=rightWord(cursorpos)-cursorpos;
  removeText(cursorpos,num,TRUE);
  setCursorPos(cursorpos,TRUE);
  makePositionVisible(cursorpos);
  flags|=FLAG_CHANGED;
  modified=TRUE;
  return 1;
  }


// Delete to end of line
long FXMathText::onCmdDeleteEol(FXObject*,FXSelector,void*){
  if(!isEditable()) return 1;
  FXint num=rowEnd(cursorpos)-cursorpos;
  removeText(cursorpos,num,TRUE);
  setCursorPos(cursorpos,TRUE);
  makePositionVisible(cursorpos);
  flags|=FLAG_CHANGED;
  modified=TRUE;
  return 1;
  }


// Delete line
long FXMathText::onCmdDeleteLine(FXObject*,FXSelector,void*){
  if(!isEditable()) return 1;
  FXint pos=rowStart(cursorpos);
  FXint num=nextRow(cursorpos)-pos;
  removeText(pos,num,TRUE);
  setCursorPos(cursorpos,TRUE);
  makePositionVisible(cursorpos);
  flags|=FLAG_CHANGED;
  modified=TRUE;
  return 1;
  }


// Delete all text
long FXMathText::onCmdDeleteAll(FXObject*,FXSelector,void*){
  if(!isEditable()) return 1;
  removeText(0,length,TRUE);
  setCursorPos(0,TRUE);
  makePositionVisible(0);
  flags|=FLAG_CHANGED;
  modified=TRUE;
  return 1;
  }


// Make selected text upper case
long FXMathText::onCmdChangeCase(FXObject*,FXSelector sel,void*){
  register FXint i,pos,num;
  FXchar *text;
  if(!isEditable()) return 1;
  pos=selstartpos;
  num=selendpos-selstartpos;
  FXMALLOC(&text,FXchar,num);
  extractText(text,pos,num);
  if(FXSELID(sel)==ID_UPPER_CASE){
    for(i=0; i<num; i++) text[i]=toupper((FXuchar)text[i]);
    }
  else{
    for(i=0; i<num; i++) text[i]=tolower((FXuchar)text[i]);
    }
  replaceText(pos,num,text,num,TRUE);
  setCursorPos(cursorpos,TRUE);
  makePositionVisible(cursorpos);
  setSelection(pos,num,TRUE);
  FXFREE(&text);
  flags|=FLAG_CHANGED;
  modified=TRUE;
  return 1;
  }


// Shift text by certain amount
FXint FXMathText::shiftText(FXint start,FXint end,FXint amount,FXbool notify){
  FXint white,p,len,size,c;
  FXchar *text;
  if(start<0) start=0;
  if(end>length) end=length;
  FXASSERT(0<tabcolumns);
  if(start<end){
    p=start;
    white=0;
    size=0;
    while(p<end){
      c=getChar(p++);
      if(c==' '){
        white++;
        }
      else if(c=='\t'){
        white+=(tabcolumns-white%tabcolumns);
        }
      else if(c=='\n'){
        size++; white=0;
        }
      else{
        white+=amount;
        if(white<0) white=0;
        if(!(options&TEXT_NO_TABS)){ size+=(white/tabcolumns+white%tabcolumns); } else { size+=white; }
        size++;
        while(p<end){
          c=getChar(p++);
          size++;
          if(c=='\n') break;
          }
        white=0;
        }
      }
    FXMALLOC(&text,FXchar,size);
    p=start;
    white=0;
    len=0;
    while(p<end){
      c=getChar(p++);
      if(c==' '){
        white++;
        }
      else if(c=='\t'){
        white+=(tabcolumns-white%tabcolumns);
        }
      else if(c=='\n'){
        text[len++]='\n'; white=0;
        }
      else{
        white+=amount;
        if(white<0) white=0;
        if(!(options&TEXT_NO_TABS)){ while(white>=tabcolumns){ text[len++]='\t'; white-=tabcolumns;} }
        while(white>0){ text[len++]=' '; white--; }
        text[len++]=c;
        while(p<end){
          c=getChar(p++);
          text[len++]=c;
          if(c=='\n') break;
          }
        white=0;
        }
      }
    FXASSERT(len<=size);
    replaceText(start,end-start,text,len,notify);
    FXFREE(&text);
    return len;
    }
  return 0;
  }


// Shift selected lines left or right
long FXMathText::onCmdShiftText(FXObject*,FXSelector sel,void*){
  FXint start,end,len,amount;
  if(!isEditable()) return 1;
  amount=0;
  switch(FXSELID(sel)){
    case ID_SHIFT_LEFT: amount=-1; break;
    case ID_SHIFT_RIGHT: amount=1; break;
    case ID_SHIFT_TABLEFT: amount=-tabcolumns; break;
    case ID_SHIFT_TABRIGHT: amount=tabcolumns; break;
    }
  if(selstartpos<selendpos){
    FXASSERT(0<=selstartpos && selstartpos<=length);
    FXASSERT(0<=selendpos && selendpos<=length);
    start=lineStart(selstartpos);
    end=selendpos;
    if(0<end && getChar(end-1)!='\n') end=nextLine(end);
    }
  else{
    start=lineStart(cursorpos);
    end=lineEnd(cursorpos);
    if(end<length) end++;
    }
  len=shiftText(start,end,amount,TRUE);
  setAnchorPos(start);
  extendSelection(start+len,SELECT_CHARS,TRUE);
  setCursorPos(start,TRUE);
  flags|=FLAG_CHANGED;
  modified=TRUE;
  return 1;
  }


// Goto matching character
long FXMathText::onCmdGotoMatching(FXObject*,FXSelector,void*){
  if(0<cursorpos){
    FXchar ch=getChar(cursorpos-1);
    FXint pos=findMatching(cursorpos-1,0,length,ch,1);
    if(0<=pos){
      setCursorPos(pos+1);
      makePositionVisible(cursorpos);
      return 1;
      }
    }
  getApp()->beep();
  return 1;
  }


// Select text till matching character
long FXMathText::onCmdSelectMatching(FXObject*,FXSelector,void*){
  if(0<cursorpos){
    FXchar ch=getChar(cursorpos-1);
    FXint pos=findMatching(cursorpos-1,0,length,ch,1);
    if(0<=pos){
      if(pos>cursorpos){
        setAnchorPos(cursorpos-1);
        extendSelection(pos+1,SELECT_CHARS,TRUE);
        }
      else{
        setAnchorPos(pos);
        extendSelection(cursorpos,SELECT_CHARS,TRUE);
        }
      return 1;
      }
    }
  getApp()->beep();
  return 1;
  }


static const FXchar righthand[]="}])>";
static const FXchar lefthand[]="{[(<";


// Select entire enclosing block
long FXMathText::onCmdSelectBlock(FXObject*,FXSelector sel,void*){
  FXint beg,end,what,level=1;
  while(1){
    what=FXSELID(sel)-ID_SELECT_BRACE;
    beg=matchBackward(cursorpos-1,0,lefthand[what],righthand[what],level);
    end=matchForward(cursorpos,length,lefthand[what],righthand[what],level);
    if(0<=beg && beg<end){
      if(isPosSelected(beg) && isPosSelected(end+1)){ level++; continue; }
      setAnchorPos(beg);
      extendSelection(end+1,SELECT_CHARS,TRUE);
      return 1;
      }
    getApp()->beep();
    break;
    }
  return 1;
  }


// Goto start of enclosing block
long FXMathText::onCmdBlockBeg(FXObject*,FXSelector sel,void*){
  FXint what=FXSELID(sel)-ID_LEFT_BRACE;
  FXint beg=cursorpos-1;
  if(0<beg){
    if(getChar(beg)==lefthand[what]) beg--;
    FXint pos=matchBackward(beg,0,lefthand[what],righthand[what],1);
    if(0<=pos){
      setCursorPos(pos+1);
      makePositionVisible(cursorpos);
      return 1;
      }
    }
  getApp()->beep();
  return 1;
  }


// Goto end of enclosing block
long FXMathText::onCmdBlockEnd(FXObject*,FXSelector sel,void*){
  FXint what=FXSELID(sel)-ID_RIGHT_BRACE;
  FXint start=cursorpos;
  if(start<length){
    if(getChar(start)==righthand[what]) start++;
    FXint pos=matchForward(start,length,lefthand[what],righthand[what],1);
    if(0<=pos){
      setCursorPos(pos);
      makePositionVisible(cursorpos);
      return 1;
      }
    }
  getApp()->beep();
  return 1;
  }


// Search for selected text
long FXMathText::onCmdSearchSel(FXObject*,FXSelector sel,void*){
  FXchar *data; FXint len;
  if(getDNDData(FROM_SELECTION,stringType,(FXuchar*&)data,(FXuint&)len)){
    FXint pos=cursorpos;
    FXint beg,end;
#ifdef WIN32
    fxfromDOS(data,len);
#endif
    searchstring.assign(data,len);
    searchflags=SEARCH_EXACT;
    FXFREE(&data);
    if(FXSELID(sel)==ID_SEARCH_FORW_SEL){
      if(isPosSelected(pos)) pos=selendpos;
      searchflags&=~SEARCH_BACKWARD;
      }
    else{
      if(isPosSelected(pos)) pos=selstartpos-1;
      searchflags|=SEARCH_BACKWARD;
      }
    if(findText(searchstring,&beg,&end,pos,searchflags|SEARCH_WRAP)){
      if(beg!=selstartpos || end!=selendpos){
        setAnchorPos(beg);
        extendSelection(end,SELECT_CHARS,TRUE);
        setCursorPos(end);
        makePositionVisible(beg);
        makePositionVisible(end);
        return 1;
        }
      }
    }
  getApp()->beep();
  return 1;
  }


// Search for next occurence
long FXMathText::onCmdSearchNext(FXObject*,FXSelector sel,void*){
  if(!searchstring.empty()){
    FXint pos=cursorpos;
    FXint beg[10];
    FXint end[10];
    if(FXSELID(sel)==ID_SEARCH_FORW){
      if(isPosSelected(pos)) pos=selendpos;
      searchflags&=~SEARCH_BACKWARD;
      }
    else{
      if(isPosSelected(pos)) pos=selstartpos-1;
      searchflags|=SEARCH_BACKWARD;
      }
    if(findText(searchstring,beg,end,pos,searchflags|SEARCH_WRAP,10)){
      if(beg[0]!=selstartpos || end[0]!=selendpos){
        setAnchorPos(beg[0]);
        extendSelection(end[0],SELECT_CHARS,TRUE);
        setCursorPos(end[0]);
        makePositionVisible(beg[0]);
        makePositionVisible(end[0]);
        return 1;
        }
      }
    }
  getApp()->beep();
  return 1;
  }


// Search text
long FXMathText::onCmdSearch(FXObject*,FXSelector,void*){
  FXGIFIcon icon(getApp(),searchicon);
  FXSearchDialog searchdialog(this,"Search",&icon);
  FXint beg[10];
  FXint end[10];
  FXint pos;
  FXuint code;
  do{
    code=searchdialog.execute();
    if(code==FXSearchDialog::DONE) return 1;
    searchstring=searchdialog.getSearchText();
    searchflags=searchdialog.getSearchMode();
    pos=isPosSelected(cursorpos) ? (searchflags&SEARCH_BACKWARD) ? selstartpos-1 : selendpos : cursorpos;
    if(findText(searchstring,beg,end,pos,searchflags|SEARCH_WRAP,10)){
      setAnchorPos(beg[0]);
      extendSelection(end[0],SELECT_CHARS,TRUE);
      setCursorPos(end[0],TRUE);
      makePositionVisible(beg[0]);
      makePositionVisible(end[0]);
      }
    else{
      getApp()->beep();
      }
    }
  while(code==FXSearchDialog::SEARCH_NEXT);
  return 1;
  }


// Replace text; we assume that findText has called squeezegap()!
long FXMathText::onCmdReplace(FXObject*,FXSelector,void*){
  FXGIFIcon icon(getApp(),searchicon);
  FXReplaceDialog replacedialog(this,"Replace",&icon);
  FXint beg[10],end[10],fm,to,len,pos;
  FXuint searchflags,code;
  FXString searchstring;
  FXString replacestring;
  FXString replacevalue;
  do{
    code=replacedialog.execute();
    if(code==FXReplaceDialog::DONE) return 1;
    searchflags=replacedialog.getSearchMode();
    searchstring=replacedialog.getSearchText();
    replacestring=replacedialog.getReplaceText();
    replacevalue=FXString::null;
    fm=-1;
    to=-1;
    if(code==FXReplaceDialog::REPLACE_ALL){
      searchflags&=~SEARCH_BACKWARD;
      pos=0;
      while(findText(searchstring,beg,end,pos,searchflags,10)){
        if(0<=fm) replacevalue.append(&buffer[pos],beg[0]-pos);
        replacevalue.append(FXRex::substitute(buffer,length,beg,end,replacestring,10));
        if(fm<0) fm=beg[0];
        to=end[0];
        pos=end[0];
        if(beg[0]==end[0]) pos++;
        }
      }
    else{
      pos=isPosSelected(cursorpos) ? (searchflags&SEARCH_BACKWARD) ? selstartpos-1 : selendpos : cursorpos;
      if(findText(searchstring,beg,end,pos,searchflags|SEARCH_WRAP,10)){
        replacevalue=FXRex::substitute(buffer,length,beg,end,replacestring,10);
        fm=beg[0];
        to=end[0];
        }
      }
    if(0<=fm){
      len=replacevalue.length();
      replaceText(fm,to-fm,replacevalue.text(),len,TRUE);
      setCursorPos(fm+len,TRUE);
      makePositionVisible(getCursorPos());
      modified=TRUE;
      }
    else{
      getApp()->beep();
      }
    }
  while(code==FXReplaceDialog::REPLACE_NEXT);
  return 1;
  }


// Goto selected line number
long FXMathText::onCmdGotoSelected(FXObject*,FXSelector,void*){
  FXchar *data; FXint len,row,i;
  if(getDNDData(FROM_SELECTION,stringType,(FXuchar*&)data,(FXuint&)len)){
#ifdef WIN32
    fxfromDOS(data,len);
#endif
    for(i=0; i<len && !isdigit((FXuchar)data[i]); i++);
    for(row=0; i<len && isdigit((FXuchar)data[i]); i++) row=row*10+(data[i]-'0');
    FXFREE(&data);
    if(1<=row){
      setCursorRow(row-1,TRUE);
      makePositionVisible(cursorpos);
      return 1;
      }
    }
  getApp()->beep();
  return 1;
  }


// Goto line number
long FXMathText::onCmdGotoLine(FXObject*,FXSelector,void*){
  FXGIFIcon icon(getApp(),gotoicon);
  FXint row=cursorrow+1;
  if(FXInputDialog::getInteger(row,this,"Goto Line","&Goto line number:",&icon,1,2147483647)){
    update();
    setCursorRow(row-1,TRUE);
    makePositionVisible(cursorpos);
    }
  return 1;
  }


/*******************************************************************************/

// Editable toggle
long FXMathText::onCmdToggleEditable(FXObject*,FXSelector,void*){
  options^=TEXT_READONLY;
  return 1;
  }


// Update editable toggle
long FXMathText::onUpdToggleEditable(FXObject* sender,FXSelector,void*){
  sender->handle(this,(options&TEXT_READONLY)?FXSEL(SEL_COMMAND,ID_UNCHECK):FXSEL(SEL_COMMAND,ID_CHECK),NULL);
  sender->handle(this,FXSEL(SEL_COMMAND,ID_SHOW),NULL);
  sender->handle(this,FXSEL(SEL_COMMAND,ID_ENABLE),NULL);
  return 1;
  }


// Overstrike toggle
long FXMathText::onCmdToggleOverstrike(FXObject*,FXSelector,void*){
  options^=TEXT_OVERSTRIKE;
  return 1;
  }


// Update overstrike toggle
long FXMathText::onUpdToggleOverstrike(FXObject* sender,FXSelector,void*){
  sender->handle(this,(options&TEXT_OVERSTRIKE)?FXSEL(SEL_COMMAND,ID_CHECK):FXSEL(SEL_COMMAND,ID_UNCHECK),NULL);
  sender->handle(this,FXSEL(SEL_COMMAND,ID_SHOW),NULL);
  sender->handle(this,FXSEL(SEL_COMMAND,ID_ENABLE),NULL);
  return 1;
  }


// Move cursor to indicated row
long FXMathText::onCmdCursorRow(FXObject* sender,FXSelector,void*){
  FXint row=cursorrow+1;
  sender->handle(this,FXSEL(SEL_COMMAND,ID_GETINTVALUE),(void*)&row);
  setCursorRow(row-1,TRUE);
  makePositionVisible(cursorpos);
  return 1;
  }


// Being asked about current row number
long FXMathText::onUpdCursorRow(FXObject* sender,FXSelector,void*){
  FXint row=cursorrow+1;
  sender->handle(this,FXSEL(SEL_COMMAND,ID_SETINTVALUE),(void*)&row);
  return 1;
  }


// Move cursor to indicated column
long FXMathText::onCmdCursorColumn(FXObject* sender,FXSelector,void*){
  FXint col=cursorcol;
  sender->handle(this,FXSEL(SEL_COMMAND,ID_GETINTVALUE),(void*)&col);
  setCursorColumn(col,TRUE);
  makePositionVisible(cursorpos);
  return 1;
  }


// Being asked about current column
long FXMathText::onUpdCursorColumn(FXObject* sender,FXSelector,void*){
  sender->handle(this,FXSEL(SEL_COMMAND,FXWindow::ID_SETINTVALUE),(void*)&cursorcol);
  return 1;
  }


// Update somebody who works on the selection
long FXMathText::onUpdHaveSelection(FXObject* sender,FXSelector,void*){
  sender->handle(this,(selstartpos<selendpos)?FXSEL(SEL_COMMAND,ID_ENABLE):FXSEL(SEL_COMMAND,ID_DISABLE),NULL);
  return 1;
  }


// Update somebody who works on the selection
long FXMathText::onUpdSelectAll(FXObject* sender,FXSelector,void*){
  sender->handle(this,(length==0)?FXSEL(SEL_COMMAND,ID_DISABLE):FXSEL(SEL_COMMAND,ID_ENABLE),NULL);
  return 1;
  }


/*******************************************************************************/


// Draw fragment of text in given style

// This gets overridden

void FXMathText::drawBufferText(FXDCWindow& dc,FXint x,FXint y,FXint,FXint,FXint pos,FXint n,FXuint style) const {
  register FXuint index=(style&STYLE_MASK);
  register FXuint usedstyle=style;                                              // Style flags from style buffer
  register FXColor color;
  FXchar str[2];
  color=0;
  if(hilitestyles && index){                                                    // Get colors from style table
    usedstyle=hilitestyles[index-1].style;                                      // Style flags now from style table
    if(style&STYLE_SELECTED) color=hilitestyles[index-1].selectForeColor;
    else if(style&STYLE_HILITE) color=hilitestyles[index-1].hiliteForeColor;
    if(color==0) color=hilitestyles[index-1].normalForeColor;                   // Fall back on normal foreground color
    }
  if(color==0){                                                                 // Fall back to default style
    if(style&STYLE_SELECTED) color=seltextColor;
    else if(style&STYLE_HILITE) color=hilitetextColor;
    if(color==0) color=textColor;                                               // Fall back to normal text color
    }
  dc.setForeground(color);
  if(style&STYLE_CONTROL){
    y+=font->getFontAscent();
    str[0]='^';
    while(pos<gapstart && 0<n){
      str[1]=buffer[pos]|0x40;
      dc.drawText(x,y,str,2);
      if(usedstyle&STYLE_BOLD) dc.drawText(x+1,y,str,2);
      x+=font->getTextWidth(str,2);
      pos++;
      n--;
      }
    while(0<n){
      str[1]=buffer[pos-gapstart+gapend]|0x40;
      dc.drawText(x,y,str,2);
      if(usedstyle&STYLE_BOLD) dc.drawText(x+1,y,str,2);
      x+=font->getTextWidth(str,2);
      pos++;
      n--;
      }
    }
  else{
    y+=font->getFontAscent();
    if(pos+n<=gapstart){
      dc.drawText(x,y,&buffer[pos],n);
      if(usedstyle&STYLE_BOLD) dc.drawText(x+1,y,&buffer[pos],n);
      }
    else if(pos>=gapstart){
      dc.drawText(x,y,&buffer[pos-gapstart+gapend],n);
      if(usedstyle&STYLE_BOLD) dc.drawText(x+1,y,&buffer[pos-gapstart+gapend],n);
      }
    else{
      dc.drawText(x,y,&buffer[pos],gapstart-pos);
      x+=font->getTextWidth(&buffer[pos],gapstart-pos);
      dc.drawText(x,y,&buffer[gapend],pos+n-gapstart);
      if(usedstyle&STYLE_BOLD) dc.drawText(x+1,y,&buffer[gapend],pos+n-gapstart);
      }
    }
  }


// Fill fragment of background in given style
void FXMathText::fillBufferRect(FXDCWindow& dc,FXint x,FXint y,FXint w,FXint h,FXuint style) const {
  register FXuint index=(style&STYLE_MASK);
  register FXuint usedstyle=style;                                              // Style flags from style buffer
  register FXColor bgcolor,fgcolor;
  bgcolor=fgcolor=0;
  if(hilitestyles && index){                                                    // Get colors from style table
    usedstyle=hilitestyles[index-1].style;                                      // Style flags now from style table
    if(style&STYLE_SELECTED){
      bgcolor=hilitestyles[index-1].selectBackColor;
      fgcolor=hilitestyles[index-1].selectForeColor;
      }
    else if(style&STYLE_HILITE){
      bgcolor=hilitestyles[index-1].hiliteBackColor;
      fgcolor=hilitestyles[index-1].hiliteForeColor;
      }
    else if(style&STYLE_ACTIVE){
      bgcolor=hilitestyles[index-1].activeBackColor;
      }
    else{
      bgcolor=hilitestyles[index-1].normalBackColor;
      }
    if(fgcolor==0){                                                             // Fall back to normal foreground color
      fgcolor=hilitestyles[index-1].normalForeColor;
      }
    }
  if(bgcolor==0){                                                               // Fall back to default background colors
    if(style&STYLE_SELECTED) bgcolor=selbackColor;
    else if(style&STYLE_HILITE) bgcolor=hilitebackColor;
    else if(style&STYLE_ACTIVE) bgcolor=activebackColor;
    else bgcolor=backColor;
    }
  if(fgcolor==0){                                                               // Fall back to default foreground colors
    if(style&STYLE_SELECTED) fgcolor=seltextColor;
    else if(style&STYLE_HILITE) fgcolor=hilitetextColor;
    if(fgcolor==0) fgcolor=textColor;                                           // Fall back to text color
    }
  dc.setForeground(bgcolor);
  dc.fillRectangle(x,y,w,h);
  if(usedstyle&STYLE_UNDERLINE){
    dc.setForeground(fgcolor);
    dc.fillRectangle(x,y+font->getFontAscent()+1,w,1);
    }
  if(usedstyle&STYLE_STRIKEOUT){
    dc.setForeground(fgcolor);
    dc.fillRectangle(x,y+font->getFontAscent()/2,w,1);
    }
  }


// Obtain text style at position pos; note pos may be outside of text
// to allow for rectangular selections!
FXuint FXMathText::style(FXint row,FXint,FXint end,FXint pos) const {
  register FXuint s=0;
  register FXchar ch;

  // Selected part of text
  if(selstartpos<=pos && pos<selendpos) s|=STYLE_SELECTED;

  // Highlighted part of text
  if(hilitestartpos<=pos && pos<hiliteendpos) s|=STYLE_HILITE;

  // Current active line
  if((row==cursorrow)&&(options&TEXT_SHOWACTIVE)) s|=STYLE_ACTIVE;

  // Blank part of line
  if(pos>=end) return s;

  // Special style for control characters
  ch=getChar(pos);

  // Get value from style buffer
  if(sbuffer) s|=getStyle(pos);

  // Tabs are just fill
  if(ch == '\t') return s;

  // Spaces are just fill
  if(ch == ' ') return s;

  // Newlines are just fill
  if(ch == '\n') return s;

  // Get special style for control codes
  if((FXuchar)ch < ' ') return s|STYLE_CONTROL|STYLE_TEXT;

  return s|STYLE_TEXT;
  }


// Draw partial text line with correct style

// @@@@@ N.B. that I override this in FXTerminal.cpp so the version here
// is something of a dead duck.

// this uses drawBufferText on each differently-styled section of a row.

void FXMathText::drawTextRow(FXDCWindow& dc,FXint line,FXint left,FXint right) const {
  register FXint x,y,w,h,linebeg,lineend,truelineend,cw,sp,ep,row,edge;
  register FXuint curstyle,newstyle;
  linebeg=visrows[line];
  lineend=truelineend=visrows[line+1];
  if(linebeg<lineend && isspace(getChar(lineend-1))) lineend--;
            // Back off last space
  x=0;
  w=0;
  h=font->getFontHeight();
  y=pos_y+margintop+(toprow+line)*h;
  edge=pos_x+marginleft+barwidth;
  row=toprow+line;

  // Scan ahead till until we hit the end or the left edge
  for(sp=linebeg; sp<lineend; sp++){
    cw=charWidth(getChar(sp),x);
    if(x+edge+cw>=left) break;
    x+=cw;
    }

  // First style to display
  curstyle=style(row,linebeg,lineend,sp);

  // Draw until we hit the end or the right edge
  for(ep=sp; ep<lineend; ep++){
    newstyle=style(row,linebeg,truelineend,ep);
    if(newstyle!=curstyle){
      fillBufferRect(dc,edge+x,y,w,h,curstyle);
      if(curstyle&STYLE_TEXT) drawBufferText(dc,edge+x,y,w,h,sp,ep-sp,curstyle);
      curstyle=newstyle;
      sp=ep;
      x+=w;
      w=0;
      }
    cw=charWidth(getChar(ep),x+w);
    if(x+edge+w>=right) break;
    w+=cw;
    }

  // Draw unfinished fragment
  fillBufferRect(dc,edge+x,y,w,h,curstyle);
  if(curstyle&STYLE_TEXT) drawBufferText(dc,edge+x,y,w,h,sp,ep-sp,curstyle);
  x+=w;

  // Fill any left-overs outside of text
  if(x+edge<right){
    curstyle=style(row,linebeg,truelineend,ep);
    fillBufferRect(dc,edge+x,y,right-edge-x,h,curstyle);
    }
  }


// Draw the cursor

// @@@@@ if cursorrow identified a row of maths I might have trouble here

void FXMathText::drawCursor(FXuint state){
  register FXint xx,yt,yb,xlo,xhi,fh;
  if((state^flags)&FLAG_CARET){
    if(xid){
      FXASSERT(0<=cursorpos && cursorpos<=length);
      FXASSERT(0<=cursorrow && cursorrow<=nrows);
      if(toprow<=cursorrow && cursorrow<toprow+nvisrows){
        xx=pos_x+marginleft+barwidth+lineWidth(cursorstart,cursorpos-cursorstart)-1;
        if(barwidth<=xx+3 && xx-2<viewport_w){
          FXDCWindow dc(this);
          fh=font->getFontHeight();
          yt=pos_y+margintop+cursorrow*fh;
          yb=yt+fh-1;

          // Cursor can overhang margins but not line number bar
          dc.setClipRectangle(barwidth,0,viewport_w-barwidth,viewport_h);

          // Draw I beam
          if(state&FLAG_CARET){

            // Draw I-beam
            dc.setForeground(cursorColor);
            dc.fillRectangle(xx,yt,2,yb-yt);
            dc.fillRectangle(xx-2,yt,6,1);
            dc.fillRectangle(xx-2,yb,6,1);
            }

          // Erase I-beam
          else{

            // Erase I-beam, plus the text immediately surrounding it
            dc.setForeground(backColor);
            dc.fillRectangle(xx-2,yt,6,yb-yt+1);

            // Clip the text to the margins AND the rectangle that was
            // just erased.  We don't want to overdraw any existing
            // characters, because of ClearType.
            xlo=FXMAX(xx-2,marginleft+barwidth);
            xhi=FXMIN(xx+4,viewport_w-marginright);
            dc.setClipRectangle(xlo,margintop,xhi-xlo,viewport_h-margintop-marginbottom);

            // Restore text
            dc.setFont(font);
            drawTextRow(dc,cursorrow-toprow,xx-3,xx+4);
            }
          }
        }
      }
    flags^=FLAG_CARET;
    }
  }


// Erase cursor overhang outside of margins
void FXMathText::eraseCursorOverhang(){
  register FXint xx,yt,yb,fh;
  FXASSERT(0<=cursorpos && cursorpos<=length);
  FXASSERT(0<=cursorrow && cursorrow<=nrows);
  if(toprow<=cursorrow && cursorrow<toprow+nvisrows){
    xx=pos_x+marginleft+barwidth+lineWidth(cursorstart,cursorpos-cursorstart)-1;
    if(barwidth<=xx+3 && xx-2<viewport_w){
      FXDCWindow dc(this);
      fh=font->getFontHeight();
      yt=pos_y+margintop+cursorrow*fh;
      yb=yt+fh-1;
      dc.setClipRectangle(barwidth,0,viewport_w-barwidth,viewport_h);
      if(xx-2<=marginleft+barwidth && barwidth<=xx+3){
        dc.setForeground(backColor);
        dc.fillRectangle(barwidth,yt,marginleft,fh);
        }
      if(viewport_w-marginright<=xx+3 && xx-2<=viewport_w){
        dc.setForeground(backColor);
        dc.fillRectangle(viewport_w-marginright,yt,marginright,fh);
        }
      if(yt<=margintop && 0<=yb){
        dc.setForeground(backColor);
        dc.fillRectangle(xx-2,0,5,margintop);
        }
      if(viewport_h-marginbottom<=yb && yt<viewport_h){
        dc.setForeground(backColor);
        dc.fillRectangle(xx-2,viewport_h-marginbottom,5,marginbottom);
        }
      }
    }
  }


// Repaint lines of text

// Apart from drawCursor, where for now I hope that I do not put the
// cursor on a row of maths, this is the only place where drawTextRow is
// called. It draws text from top to bottom. Thus the several rows that make
// up one bit of maths can be spotted and handled here.

void FXMathText::drawContents(FXDCWindow& dc,FXint x,FXint y,FXint w,FXint h) const {
  register FXint hh=font->getFontHeight();
  register FXint yy=pos_y+margintop+toprow*hh;
  register FXint tl=(y-yy)/hh;
  register FXint bl=(y+h-yy)/hh;
  register FXint ln;
  if(tl<0) tl=0;
  if(bl>=nvisrows) bl=nvisrows-1;
  for(ln=tl; ln<=bl; ln++){
    drawTextRow(dc,ln,x,x+w);
    }
  }


// Repaint line numbers
void FXMathText::drawNumbers(FXDCWindow& dc,FXint x,FXint y,FXint w,FXint h) const {
  register FXint hh=font->getFontHeight();
  register FXint yy=pos_y+margintop+toprow*hh;
  register FXint tl=(y-yy)/hh;
  register FXint bl=(y+h-yy)/hh;
  register FXint ln,n,tw;
  FXchar lineno[20];
  if(tl<0) tl=0;
  if(bl>=nvisrows) bl=nvisrows-1;
  dc.setForeground(barColor);
  dc.fillRectangle(x,y,w,h);
  dc.setForeground(numberColor);
  for(ln=tl; ln<=bl; ln++){
    n=sprintf(lineno,"%d",toprow+ln+1);
    tw=font->getTextWidth(lineno,n);
    dc.drawText(barwidth-tw,yy+ln*hh+font->getFontAscent(),lineno,n);
    }
  }


// Repaint text range
void FXMathText::updateRange(FXint beg,FXint end) const {
  register FXint tl,bl,fc,lc,ty,by,lx,rx,t;
  if(beg>end){t=beg;beg=end;end=t;}
  if(beg<visrows[nvisrows] && visrows[0]<end && beg<end){
    if(beg<visrows[0]) beg=visrows[0];
    if(end>visrows[nvisrows]) end=visrows[nvisrows];
    tl=posToLine(beg,0);
    bl=posToLine(end,tl);
    if(tl==bl){
      fc=beg-visrows[tl];
      lc=end-visrows[tl];
      ty=pos_y+margintop+(toprow+tl)*font->getFontHeight();
      by=ty+font->getFontHeight();
      lx=pos_x+marginleft+barwidth+lineWidth(visrows[tl],fc);
      if(end<=(visrows[tl+1]-1)) rx=pos_x+marginleft+barwidth+lineWidth(visrows[tl],lc); else rx=width;
      }
    else{
      ty=pos_y+margintop+(toprow+tl)*font->getFontHeight();
      by=pos_y+margintop+(toprow+bl+1)*font->getFontHeight();
      lx=barwidth;
      rx=width;
      }
    update(lx,ty,rx-lx,by-ty);
    }
  }


// Draw item list
long FXMathText::onPaint(FXObject*,FXSelector,void* ptr){
  FXEvent* event=(FXEvent*)ptr;
  FXDCWindow dc(this,event);
  dc.setFont(font);
//dc.setForeground(FXRGB(255,0,0));
//dc.fillRectangle(event->rect.x,event->rect.y,event->rect.w,event->rect.h);

  // Paint top margin
  if(event->rect.y<=margintop){
    dc.setForeground(backColor);
    dc.fillRectangle(barwidth,0,viewport_w-barwidth,margintop);
    }

  // Paint bottom margin
  if(event->rect.y+event->rect.h>=viewport_h-marginbottom){
    dc.setForeground(backColor);
    dc.fillRectangle(barwidth,viewport_h-marginbottom,viewport_w-barwidth,marginbottom);
    }

  // Paint left margin
  if(event->rect.x<barwidth+marginleft){
    dc.setForeground(backColor);
    dc.fillRectangle(barwidth,margintop,marginleft,viewport_h-margintop-marginbottom);
    }

  // Paint right margin
  if(event->rect.x+event->rect.w>=viewport_w-marginright){
    dc.setForeground(backColor);
    dc.fillRectangle(viewport_w-marginright,margintop,marginright,viewport_h-margintop-marginbottom);
    }

  // Paint line numbers
  if(event->rect.x<barwidth){
    dc.setClipRectangle(0,0,barwidth,height);
    drawNumbers(dc,event->rect.x,event->rect.y,event->rect.w,event->rect.h);
    }

  // Paint text
  dc.setClipRectangle(marginleft+barwidth,margintop,viewport_w-marginright-marginleft-barwidth,viewport_h-margintop-marginbottom);
  drawContents(dc,event->rect.x,event->rect.y,event->rect.w,event->rect.h);

  drawCursor(flags);
  return 1;
  }


/*******************************************************************************/


// Move the cursor
void FXMathText::setCursorPos(FXint pos,FXbool notify){
  register FXint cursorstartold,cursorendold;
  if(pos>length) pos=length;
  if(pos<0) pos=0;
  if(cursorpos!=pos){
    drawCursor(0);
    if(pos<cursorstart || cursorend<=pos){    // Move to other line?
      cursorstartold=cursorstart;
      cursorendold=cursorend;
      cursorstart=rowStart(pos);
      cursorend=nextRow(cursorstart);
      if(cursorstart<cursorstartold){
        cursorrow=cursorrow-countRows(cursorstart,cursorstartold);
        }
      else{
        cursorrow=cursorrow+countRows(cursorstartold,cursorstart);
        }
      if(options&TEXT_SHOWACTIVE){
        updateRange(cursorstartold,cursorendold);
        updateRange(cursorstart,cursorend);
        }
      }
    cursorcol=indentFromPos(cursorstart,pos);
    cursorpos=pos;
    drawCursor(FLAG_CARET);
    prefcol=-1;
    if(target && notify){
      target->tryHandle(this,FXSEL(SEL_CHANGED,message),(void*)(FXival)cursorpos);
      }
    }
  }


// Set cursor row
void FXMathText::setCursorRow(FXint row,FXbool notify){
  FXint col,newrow,newpos;
  if(row!=cursorrow){
    if(row<0) row=0;
    if(row>=nrows) row=nrows-1;
    col=(0<=prefcol) ? prefcol : cursorcol;
    if(row>cursorrow){
      newrow=nextRow(cursorpos,row-cursorrow);
      }
    else{
      newrow=prevRow(cursorpos,cursorrow-row);
      }
    newpos=posFromIndent(newrow,col);
    setCursorPos(newpos,notify);
    prefcol=col;
    }
  }


// Set cursor column
void FXMathText::setCursorColumn(FXint col,FXbool notify){
  FXint newpos;
  if(cursorcol!=col){
    newpos=posFromIndent(cursorstart,col);
    setCursorPos(newpos,notify);
    }
  }


// Set anchor position
void FXMathText::setAnchorPos(FXint pos){
  if(pos>length) pos=length;
  if(pos<0) pos=0;
  anchorpos=pos;
  }


// Select all text
FXbool FXMathText::selectAll(FXbool notify){
  return setSelection(0,length,notify);
  }


// Extend selection
FXbool FXMathText::extendSelection(FXint pos,FXTextSelectionMode select,FXbool notify){
  FXint sp,ep;

  // Validate position
  if(pos<0) pos=0;
  if(pos>length) pos=length;

  // Did position change?
  switch(select){

    // Selecting words
    case SELECT_WORDS:
      if(pos<=anchorpos){
        sp=wordStart(pos);
        ep=wordEnd(anchorpos);
        }
      else{
        sp=wordStart(anchorpos);
        ep=wordEnd(pos);
        }
      break;

    // Selecting lines
    case SELECT_LINES:
      if(pos<=anchorpos){
        sp=rowStart(pos);
        ep=nextRow(anchorpos);
        }
      else{
        sp=rowStart(anchorpos);
        ep=nextRow(pos);
        }
      break;

    // Selecting characters
    default:
      if(pos<=anchorpos){
        sp=pos;
        ep=anchorpos;
        }
      else{
        sp=anchorpos;
        ep=pos;
        }
      break;
    }

  // Select the new range
  return setSelection(sp,ep-sp,notify);
  }


// Set selection
FXbool FXMathText::setSelection(FXint pos,FXint len,FXbool notify){
  FXDragType types[2];
  FXint what[2];
  FXint ep=pos+len;
  FXint sp=pos;

  // Validate position
  if(sp<0) sp=0;
  if(ep<0) ep=0;
  if(sp>length) sp=length;
  if(ep>length) ep=length;

  // Something changed?
  if(selstartpos!=sp || selendpos!=ep){

    // Release selection
    if(sp==ep){
      if(notify && target){
        what[0]=selstartpos;
        what[1]=selendpos-selstartpos;
        target->tryHandle(this,FXSEL(SEL_DESELECTED,message),(void*)what);
        }
      if(hasSelection()) releaseSelection();
      }

    // Minimally update
    if(ep<=selstartpos || selendpos<=sp){
      updateRange(selstartpos,selendpos);
      updateRange(sp,ep);
      }
    else{
      updateRange(sp,selstartpos);
      updateRange(selendpos,ep);
      }

    selstartpos=sp;
    selendpos=ep;

    // Acquire selection
    if(sp!=ep){
      types[0]=stringType;
      types[1]=textType;
      if(!hasSelection()) acquireSelection(types,2);
      if(notify && target){
        what[0]=selstartpos;
        what[1]=selendpos-selstartpos;
        target->tryHandle(this,FXSEL(SEL_SELECTED,message),(void*)what);
        }
      }
    return TRUE;
    }
  return FALSE;
  }


// Kill the selection
FXbool FXMathText::killSelection(FXbool notify){
  FXint what[2];
  if(selstartpos<selendpos){
    if(notify && target){
      what[0]=selstartpos;
      what[1]=selendpos-selstartpos;
      target->tryHandle(this,FXSEL(SEL_DESELECTED,message),(void*)what);
      }
    if(hasSelection()) releaseSelection();
    updateRange(selstartpos,selendpos);
    selstartpos=0;
    selendpos=0;
    return TRUE;
    }
  return FALSE;
  }


// Set highlight
FXbool FXMathText::setHighlight(FXint pos,FXint len){
  FXint he=pos+len;
  FXint hs=pos;

  // Validate
  if(hs<0) hs=0;
  if(he<0) he=0;
  if(hs>length) hs=length;
  if(he>length) he=length;

  // Anything changed?
  if(hs!=hilitestartpos || he!=hiliteendpos){

    // Minimally update
    if(he<=hilitestartpos || hiliteendpos<=hs){
      updateRange(hilitestartpos,hiliteendpos);
      updateRange(hs,he);
      }
    else{
      updateRange(hs,hilitestartpos);
      updateRange(hiliteendpos,he);
      }

    // Keep new range
    hilitestartpos=hs;
    hiliteendpos=he;

    return TRUE;
    }
  return FALSE;
  }


// Unhighlight the text
FXbool FXMathText::killHighlight(){
  if(hilitestartpos<hiliteendpos){
    updateRange(hilitestartpos,hiliteendpos);
    hilitestartpos=0;
    hiliteendpos=0;
    return TRUE;
    }
  return FALSE;
  }


/*******************************************************************************/


// Change top margin
void FXMathText::setMarginTop(FXint mt){
  if(margintop!=mt){
    margintop=mt;
    recalc();
    update();
    }
  }

// Change bottom margin
void FXMathText::setMarginBottom(FXint mb){
  if(marginbottom!=mb){
    marginbottom=mb;
    recalc();
    update();
    }
  }


// Change left margin
void FXMathText::setMarginLeft(FXint ml){
  if(marginleft!=ml){
    marginleft=ml;
    recalc();
    update();
    }
  }


// Change right margin
void FXMathText::setMarginRight(FXint mr){
  if(marginright!=mr){
    marginright=mr;
    recalc();
    update();
    }
  }


// Change the font

// @@@@@ at some level I will want the maths font size to scale as the
// main font size.

void FXMathText::setFont(FXFont* fnt){
  if(!fnt){ fxerror("%s::setFont: NULL font specified.\n",getClassName()); }
  if(font!=fnt){
    font=fnt;
    recalc();
    tabwidth=tabcolumns*font->getTextWidth(" ",1);
    barwidth=barcolumns*font->getTextWidth("8",1);
    if(options&TEXT_FIXEDWRAP){ wrapwidth=wrapcolumns*font->getTextWidth(" ",1); }
    layout();
    update();
    }
  }


// Set wrap columns
void FXMathText::setWrapColumns(FXint cols){
  if(cols<=0) cols=1;
  if(cols!=wrapcolumns){
    wrapcolumns=cols;
    if(options&TEXT_FIXEDWRAP){ wrapwidth=wrapcolumns*font->getTextWidth(" ",1); }
    recalc();
    update();
    }
  }


// Set tab columns
void FXMathText::setTabColumns(FXint cols){
  if(cols<=0) cols=1;
  if(cols!=tabcolumns){
    tabcolumns=cols;
    tabwidth=tabcolumns*font->getTextWidth(" ",1);
    recalc();
    update();
    }
  }

// Change number of columns used for line numbers
void FXMathText::setBarColumns(FXint cols){
  if(cols<=0) cols=0;
  if(cols!=barcolumns){
    barcolumns=cols;
    barwidth=barcolumns*font->getTextWidth("8",1);
    recalc();
    update();
    }
  }

// Set text color
void FXMathText::setTextColor(FXColor clr){
  if(clr!=textColor){
    textColor=clr;
    update(barwidth,0,width-barwidth,height);
    }
  }


// Set select background color
void FXMathText::setSelBackColor(FXColor clr){
  if(clr!=selbackColor){
    selbackColor=clr;
    updateRange(selstartpos,selendpos);
    }
  }


// Set selected text color
void FXMathText::setSelTextColor(FXColor clr){
  if(clr!=seltextColor){
    seltextColor=clr;
    updateRange(selstartpos,selendpos);
    }
  }


// Change highlighted text color
void FXMathText::setHiliteTextColor(FXColor clr){
  if(clr!=hilitetextColor){
    hilitetextColor=clr;
    updateRange(hilitestartpos,hiliteendpos);
    }
  }


// Change highlighted background color
void FXMathText::setHiliteBackColor(FXColor clr){
  if(clr!=hilitebackColor){
    hilitebackColor=clr;
    updateRange(hilitestartpos,hiliteendpos);
    }
  }


// Change active background color
void FXMathText::setActiveBackColor(FXColor clr){
  if(clr!=activebackColor){
    activebackColor=clr;
    update(barwidth,0,width-barwidth,height);
    }
  }

// Change line number color
void FXMathText::setNumberColor(FXColor clr){
  if(clr!=numberColor){
    numberColor=clr;
    update(0,0,barwidth,height);
    }
  }


// Change bar color
void FXMathText::setBarColor(FXColor clr){
  if(clr!=barColor){
    barColor=clr;
    update(0,0,barwidth,height);
    }
  }


// Set cursor color
void FXMathText::setCursorColor(FXColor clr){
  if(clr!=cursorColor){
    cursorColor=clr;
    updateRange(cursorstart,cursorend);
    }
  }


// Change text style
void FXMathText::setTextStyle(FXuint style){
  FXuint opts=(options&~TEXT_MASK) | (style&TEXT_MASK);
  if(options!=opts){
    options=opts;
    if(options&TEXT_FIXEDWRAP){ wrapwidth=wrapcolumns*font->getTextWidth(" ",1); }
    recalc();
    update();
    }
  }


// Get text style
FXuint FXMathText::getTextStyle() const {
  return (options&TEXT_MASK);
  }


// Return true if editable
FXbool FXMathText::isEditable() const {
  return (options&TEXT_READONLY)==0;
  }


// Set widget is editable or not
void FXMathText::setEditable(FXbool edit){
  if(edit) options&=~TEXT_READONLY; else options|=TEXT_READONLY;
  }


// Set styled text mode
void FXMathText::setStyled(FXbool styled){
  if(styled && !sbuffer){
    if(!FXCALLOC(&sbuffer,FXchar,length+gapend-gapstart)){fxerror("%s::setStyled: out of memory.\n",getClassName());}
    update();
    }
  if(!styled && sbuffer){
    FXFREE(&sbuffer);
    update();
    }
  }


// Set highlight styles
void FXMathText::setHiliteStyles(const FXHiliteStyle* styles){
  hilitestyles=styles;
  update();
  }


// Change number of visible rows
void FXMathText::setVisibleRows(FXint rows){
  if(rows<0) rows=0;
  if(vrows!=rows){
    vrows=rows;
    recalc();
    }
  }


// Change number of visible columns
void FXMathText::setVisibleColumns(FXint cols){
  if(cols<0) cols=0;
  if(vcols!=cols){
    vcols=cols;
    recalc();
    }
  }


/*******************************************************************************/


// Update value from a message
long FXMathText::onCmdSetStringValue(FXObject*,FXSelector,void* ptr){
  setText(*((FXString*)ptr));
  return 1;
  }


// Obtain value from text
long FXMathText::onCmdGetStringValue(FXObject*,FXSelector,void* ptr){
  *((FXString*)ptr)=getText();
  return 1;
  }


/*******************************************************************************/


// Save object to stream
void FXMathText::save(FXStream& store) const {
  FXScrollArea::save(store);
  store << length;
  store.save(buffer,gapstart);
  store.save(buffer+gapend,length-gapstart);
  store << nvisrows;
  store.save(visrows,nvisrows+1);
  store << wrapcolumns;
  store << tabcolumns;
  store << margintop;
  store << marginbottom;
  store << marginleft;
  store << marginright;
  store << font;
  store << textColor;
  store << selbackColor;
  store << seltextColor;
  store << hilitebackColor;
  store << hilitetextColor;
  store << cursorColor;
  store << help;
  store << tip;
  store << matchtime;
  }


// Load object from stream
void FXMathText::load(FXStream& store){
  FXScrollArea::load(store);
  store >> length;
  FXMALLOC(&buffer,FXchar,length+MINSIZE);    // FIXME should we save text&style?
  store.load(buffer,length);
  gapstart=length;
  gapend=length+MINSIZE;
  store >> nvisrows;
  FXMALLOC(&visrows,FXint,nvisrows+1);
  store.load(visrows,nvisrows+1);
  store >> wrapcolumns;
  store >> tabcolumns;
  store >> margintop;
  store >> marginbottom;
  store >> marginleft;
  store >> marginright;
  store >> font;
  store >> textColor;
  store >> selbackColor;
  store >> seltextColor;
  store >> hilitebackColor;
  store >> hilitetextColor;
  store >> cursorColor;
  store >> help;
  store >> tip;
  store >> matchtime;
  }


// Clean up
FXMathText::~FXMathText(){
  getApp()->removeTimeout(this,ID_BLINK);
  getApp()->removeTimeout(this,ID_FLASH);
  FXFREE(&buffer);
  FXFREE(&sbuffer);
  FXFREE(&visrows);
  FXFREE(&clipbuffer);
  buffer=(FXchar*)-1L;
  sbuffer=(FXchar*)-1L;
  clipbuffer=(FXchar*)-1L;
  visrows=(FXint*)-1L;
  font=(FXFont*)-1L;
  hilitestyles=(FXHiliteStyle*)-1L;
  }

}

// end of FXMathText.cpp
