Logo Search packages:      
Sourcecode: gambas version File versions  Download package

qeditor.cpp

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

  qeditor.cpp

  (c) 2000-2003 Beno� Minisini <gambas@users.sourceforge.net>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 1, or (at your option)
  any later version.

  This program 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 General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

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

/**********************************************************************
** $Id: qt/src/widgets/qeditor.cpp   2.3.1   edited 2001-03-30 $
**
** Implementation of QEditor widget class
**
** Created : 961005
**
** Copyright (C) 1992-2000 Trolltech AS.  All rights reserved.
**
** This file is part of the widgets module of the Qt GUI Toolkit.
**
** This file may be distributed under the terms of the Q Public License
** as defined by Trolltech AS of Norway and appearing in the file
** LICENSE.QPL included in the packaging of this file.
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
** licenses may use this file in accordance with the Qt Commercial License
** Agreement provided with the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
**   information about Qt Commercial License Agreements.
** See http://www.trolltech.com/qpl/ for QPL licensing information.
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
**********************************************************************/

#include <qeditor.h>
#include <qpainter.h>
#include <qscrollbar.h>
#include <qclipboard.h>
#include <qpixmap.h>
#include <qregexp.h>
#include <qapplication.h>
#include <qdragobject.h>
#include <qpopupmenu.h>
#include <qtimer.h>
#include <qdict.h>
#include <qcursor.h>
#include <qstyle.h>

#include <ctype.h>
#include "main.h"

/* I want windows shortcuts */
#define _WS_WIN_

#define xOffset() contentsX()
#define yOffset() contentsY()
#define viewWidth() visibleWidth()
#define viewHeight() visibleHeight()
#define totalHeight() contentsHeight()
//#define updateContents() viewport()->repaint()

#undef QMAX
#undef QMIN


static int QMAX(int a, int b)
{
  return a >= b ? a : b;
}

static int QMIN(int a, int b)
{
  return a <= b ? a : b;
}

int QEditor::topCell() const
{
  return rowAt(contentsY());
}

void QEditor::setTopCell(int row)
{
  updateScrollBars();
  setContentsPos(contentsX(), row * cellHeight());
  emit scrolled();
}

void QEditor::setBottomCell(int row)
{
  updateScrollBars();
  setContentsPos(contentsX(), QMAX(0, (row  + 1) * cellHeight() - visibleHeight()));
  emit scrolled();
}

void QEditor::ensureLineVisible(int row)
{
  //setContentsPos(contentsX(), QMAX(0, row * cellHeight() + (cellHeight() - visibleHeight()) / 2));
  ensureVisible(0, row * cellHeight() + cellHeight() / 2, 0, 100);
}

void QEditor::setXOffset(int x)
{
  setContentsPos(x, contentsY());
}

void QEditor::setYOffset(int y)
{
  //qDebug("setYOffset(%d) -> topCell = %d", y, topCell());
  setContentsPos(contentsX(), y);
}

bool QEditor::rowIsVisible(int row) const
{
  return (row >= rowAt(contentsY()) && row <= rowAt(contentsY() + visibleHeight() - 1));
}

int QEditor::lastRowVisible() const
{
  return rowAt(contentsY() + visibleHeight() - cellHeight());
}

bool QEditor::rowYPos(int row, int *yPos) const
{
  int y = row * cellHeight() - contentsY();

  *yPos = y;
  return !(y < 0 || y >= (visibleHeight() - cellHeight()));
}

int QEditor::findRow(int y) const
{
  if (y < 0 || y >= visibleHeight())
    return -1;
  else
    return rowAt(contentsY() + y);
}


QRect QEditor::viewRect() const
{
  return QRect(0, 0, visibleWidth(), visibleHeight());
}


int QEditor::posX(int x, int y) const
{
  if (x < 0)
    x = cursorX;

  if (y < 0)
    y = cursorY;

  return mapToView(x, y) - contentsX();
}

int QEditor::posY(int x, int y) const
{
  if (y < 0)
    y = cursorY;

  return y * cellHeight() - contentsY();
}

void QEditor::setY(int newY)
{
  if (newY < 0)
    newY = 0;
  if (newY >= numLines())
    newY = numLines() - 1;

  if (cursorY != newY)
    colorize(cursorY);
  cursorY = newY;
}


static void addData(ColorDataArray *data, int state, bool nocompress = false)
{
  unsigned int pos;

  pos = data->count();

  if (nocompress || data->isNull() || (*data)[pos - 1].state != (unsigned int)state || (*data)[pos - 1].len == 4095)
  {
    data->resize(pos + 1);
    (*data)[pos].state = state;
    (*data)[pos].len = 1;
  }
  else
  {
    (*data)[pos - 1].len = (*data)[pos - 1].len + 1;
  }

  //qDebug("addData: data->count() = %d", data->count());
}


static void changeData(ColorDataArray *data, int state)
{
  if (data->isNull())
    return;

  (*data)[data->count() - 1].state = state;
}


bool QEditorRow::isProc(QString &s)
{
  uint i;
  QChar c;
  QString symbol;

  for (i = 0; i < s.length(); i++)
  {
    c = s[i];

    if (!c.isLetter() && symbol.length() > 0)
    {
      symbol = symbol.upper();
      if (symbol == "PRIVATE" || symbol == "PUBLIC" || symbol == "STATIC")
      {
        symbol = "";
        continue;
      }
      else
        break;
    }

    if (c.isLetter())
      symbol += c;
    else if (!c.isSpace())
      return false;
  }

  return (symbol == "SUB" || symbol == "PROCEDURE" || symbol == "FUNCTION");
}

void QEditorRow::analyze(QString &s, ColorDataArray &data)
{
  enum
  { Decimal, Binary, Hexa };

  unsigned int i;
  QChar c, cn;
  QString sc;
  int state = Normal;
  int prev_state = Normal;
  int sub_type = 0;
  QChar last_op;
  unsigned int start = 0;
  bool first_symbol = true;
  bool esc = false;
  bool nocompress;
  bool first_keyword = false;
  bool last_decl = false;
  bool decl_keyword = false;

  data.resize(0);

  for (i = 0; i < s.length(); i++)
  {
    if (i == 0)
      c = s[0];
    else
      c = cn;

    if (i < s.length())
      cn = s[i + 1];
    else
      cn = '\n';

    prev_state = state;

    nocompress = false;

    if (state == Normal && !c.isSpace())
    {
      if (c.isLetter() || c == '$' || c == '_')
      {
        state = Symbol;
        start = i;
      }
      else if (c == '"')
      {
        state = String;
      }
      else if (c == '\'')
        state = Commentary;
      else if (c == '&')
      {
        if (cn.lower() == 'x')
        {
          state = Number;
          sub_type = Binary;
        }
        else if (cn.lower() == 'h' || cn.isDigit()
                 || (cn.lower() >= 'a' && cn.lower() <= 'f'))
        {
          state = Number;
          sub_type = Hexa;
        }
        else
          state = Operator;
      }
      else if (c.isDigit())
      {
        state = Number;
        sub_type = Decimal;
      }
      else if ((c == '+' || c == '-') && cn.isDigit())
      {
        state = Number;
        sub_type = Decimal;
      }
      else if (c == '?')
      {
        sc = "PRINT";
        if (cn != ' ')
          sc = sc + ' ';

        s = s.replace(i, 1, sc);

        start = i;
        state = Symbol;
        cn = 'R';
      }
      else
      {
        state = Operator;
        last_op = c;
        nocompress = (c != '>' && c != '/' && c != '=');
      }
    }

    addData(&data, state, nocompress);

    switch (state)
    {
      case Symbol:

        if (cn.isLetter() || cn.isDigit() || cn == '_' || cn == '?'
            || cn == '$')
        {
          // continue
        }
        else
        {
          unsigned int len = i - start + 1;
          sc = s.mid(start, len).lower();
          const char *look = sc.latin1();
          const char *repl = NULL;

          //qDebug("Symbol = '%s'", (s.mid(start, len)).latin1(););

          if (last_decl)
          {
            last_decl = false;
          }
          else if (last_op != "." && last_op != "!")
          {
            if (cn != '(')
            {
              if (!first_keyword)
              {
                repl = QEditor::firstDict[look];
                if (repl)
                {
                  last_decl = true;
                  state = Keyword;
                }
              }

              if (!repl)
              {
                repl = QEditor::keywordDict[look];
                if (repl)
                {
                  if ((cn != '.' && cn != '[') || qstrcmp(repl, "ME") == 0 || qstrcmp(repl, "LAST") == 0)
                  {
                    state = Keyword;
                    decl_keyword = qstrcmp(repl, "AS") == 0 || qstrcmp(repl, "NEW") == 0;
                  }
                  else
                    repl = NULL;
                }
              }
            }

            if (!repl && decl_keyword)
            {
              repl = QEditor::typeDict[look];
              if (repl && (i < (s.length() - 2)) && (cn == '[') && (s[i + 2] == ']'))
              {
                //qDebug("repl = %s", repl);
                addData(&data, state);
                addData(&data, state);
                len += 2;
                sc = s.mid(start, len);
                look = sc.latin1();
                repl = QEditor::typeDict[look];
                i += 2;
              }

              state = Datatype;
            }

            if (!repl && decl_keyword)
              decl_keyword = false;

            if (!repl)
            {
              repl = QEditor::subrDict[look];
              state = Subr;
            }

            if (repl)
            {
              s = s.replace(start, len, repl);
              changeData(&data, state);
              first_keyword = true;
            }
          }

          first_symbol = false;
          state = Normal;
          last_op = 0;
        }
        break;

      case Number:

        if (sub_type != Decimal && c == '&' && prev_state != Normal)
          state = Normal;
        else if (!(cn.isLetter() || cn.isDigit()
                   || (sub_type == Decimal
                       && (cn == '.' || cn.lower() == 'e' || cn == '+' || cn == '-'))
                   || (sub_type != Decimal
                       && cn == '&')))
          state = Normal;
        break;

      case String:

        if ((prev_state == String) && c == '"' && !esc)
          state = Normal;
        else if (esc)
          esc = false;
        else
          esc = (c == '\\');

        break;

      case Commentary:
        break;

      default:

        state = Normal;
        break;
    }
  }
}

void QEditorRow::colorize(void)
{
  unsigned int i;
  QChar c;

  if (!modify)
    return;

  modify = false;

  for (i = s.length() - 1; i >= 0; i--)
  {
    if (!s[i].isSpace())
      break;
  }

  //if (i >= 0)
  //  s = s.left(i + 1);

  /*for (i = 0; i < s.length(); i++)
  {
    c = s[i];
    if (!c.isSpace())
    {
      if (c == '?')
        s.replace(i, 1, "PRINT ");
      break;
    }

  }*/

  analyze(s, data);
  line = isProc(s);

  //qDebug("colorize, data.count() = %d", data.count());
}


void QEditorRow::drawBack(QPainter & p, int x, int y, int w, int h,
                          QColor *color, bool current)
{
  if (type)
  {
    if (type == TypeCurrent)
      p.fillRect(x, y, w, h, color[Current]);
    else if (type == TypeBreakpoint)
      p.fillRect(x, y, w, h, color[Breakpoint]);
    else
    {
      p.fillRect(x, y, w, h,
        QColor((color[Current].red() + color[Breakpoint].red()) / 2,
               (color[Current].green() + color[Breakpoint].green()) / 2,
               (color[Current].blue() + color[Breakpoint].blue()) / 2));
    }
  }
  else if (current)
    p.fillRect(x, y, w, h, color[Line]);
}

void QEditorRow::draw(QPainter & p, int x, int y, int w, int h,
                      QColor color[], bool useRelief)
{
  unsigned int i, pos;
  int len, wd, style;
  QString sd;

  y += p.fontMetrics().ascent();

  if (type)
  {
    if (useRelief)
    {
      p.setPen(color[Normal]);
      p.drawText(x + 1, y + 1, s);
    }
    p.setPen(color[Background]);
    p.drawText(x, y, s);
    return;
  }

  if (modify)
  {
    p.setPen(color[Normal]);
    p.drawText(x, y, s);
    return;
  }

  pos = 0;
  for (i = 0; i < data.count(); i ++)
  {
    style = data[i].state;
    len = data[i].len;

    sd = s.mid(pos, len);
    //if (style == Keyword)
    //  sd = sd.upper();

    //p.drawText(x, y, w, h, Qt::ExpandTabs, sd);
    if (style == Keyword && useRelief)
    {
      p.setPen(color[Normal]);
      p.drawText(x + 1, y + 1, sd);
      p.setPen(color[style]);
      p.drawText(x, y, sd);
    }
    else
    {
      p.setPen(color[style]);
      p.drawText(x, y, sd);
    }

    pos += len;
    wd = p.fontMetrics().width(sd);     //p.boundingRect(x, y, w, h, Qt::ExpandTabs, sd).width();
    x += wd;
    w -= wd;
  }

  if (pos < s.length())
  {
    p.setPen(color[Normal]);
    //p.drawText(x, y, w, h, Qt::ExpandTabs, s.mid(pos));
    p.drawText(x, y, s.mid(pos));
  }
}

void QEditorRow::drawNormal(QPainter & p, int x, int y, int w, int h, QColor color[])
{
  y += p.fontMetrics().ascent();

  p.setPen(color[Normal]);
  p.drawText(x, y, s);
}


QColor QEditor::default_color[NUM_COLORS] = {
  Qt::black,
  Qt::white,
  Qt::yellow,
  Qt::yellow,
  Qt::yellow,
  Qt::white,
  Qt::cyan,
  Qt::green,
  Qt::gray,
  Qt::red,
  Qt::yellow,
  QColor(0xFF, 0x80, 0x00),
  Qt::blue,
  Qt::yellow,
  QColor(0x10, 0x10, 0x10),
};

bool QEditor::init = false;

QAsciiDict<char> QEditor::firstDict(15, false, true);
QAsciiDict<char> QEditor::keywordDict(127, false, true);
QAsciiDict<char> QEditor::subrDict(127, false, true);
QAsciiDict<char> QEditor::typeDict(31, false, true);

static void initDictWith(QAsciiDict<char> &dict, const char *init[])
{
  int i;
  uint j;
  const char *p;
  char key[32];

  for (i = 0; ; i++)
  {
    p = init[i];
    if (p == 0)
      break;

    strcpy(key, p);
    for (j = 0; j < strlen(key); j++)
      key[j] = GB.tolower(key[j]);

    dict.insert(key, p);
  }
}

void QEditor::initDict(void)
{
  static const char *first[] = {
    "CLASS","INHERITS", //"IMPLEMENTS",
    NULL
    };

  static const char *keyword[] = {
    "FUNCTION","STRUCT","CONST","PRIVATE","PUBLIC",
    "STATIC","EVENT","AS","DIM","NEW","PROCEDURE","SUB",
    "PARAM","OPTIONAL","OUTPUT","RETURN",
    "DO","LOOP","WHILE","UNTIL","REPEAT","WEND","IF","THEN","ELSE","ENDIF","END","FOR",
    "TO","STEP","NEXT","SELECT","CASE","EXIT","BREAK","CONTINUE","GOTO","ME","LAST","TRY",
    "FINALLY","CATCH","WITH","TRUE","FALSE","SWAP","NULL","EXTERN","EACH","IN","DEFAULT",
    "STOP","QUIT","PRINT","INPUT","READ","WRITE","OPEN","CLOSE","SEEK","APPEND","CREATE",
    "BINARY","DIRECT","LINE","FLUSH","NOT","AND","OR","XOR","MOD","IS","LIKE","WAIT","EXEC",
    "SHELL","KILL","RENAME","INC","DEC","MKDIR","RMDIR","WATCH","USE","BIG","LITTLE","COPY",
    "PROPERTY","RAISE","LINK","ERROR",
    NULL
    };

  static const char *type[] = {
    "Boolean","Byte","Date","Float","Integer","Long","Short","String","Variant",
    "Class","Collection","Application","Error","File","Process","Object","System",
    "Boolean[]","Byte[]","Short[]","Integer[]","String[]","Date[]","Float[]",
    "Variant[]","Object[]",
    NULL
    };

  static const char *subr[] = {
    "Left","Left$","Mid","Mid$","Right","Right$","Len","Space","Space$","String","String$",
    "Trim","Trim$","Upper","Upper$","Lower","Lower$","UCase","UCase$","LCase","LCase$",
    "Chr","Chr$","Asc","Instr","RInstr","Subst","Subst$","Compare","Replace","Replace$",
    "Split","Abs","Int","Frac","Log","Exp","Sqr","Sin","Cos","Tan","Atn","ATan","Asn",
    "ASin","Acs","ACos","Deg","Rad","Log10","Sinh","Cosh","Tanh","Asnh","ASinh","Acsh",
    "ACosh","Atnh","ATanh","Sgn","Fix","Pi","Round","Randomize","Rnd","Min","Max","BClr",
    "BSet","BTst","BChg","Shl","Shr","Rol","Ror","CBool","CByte","CShort","CInt",
    "CInteger","CLng","CLong","CFloat","CDate","CStr","CString","Bin","Bin$","Hex","Hex$",
    "Val","Str","Str$","Format","Format$","Timer","Now","Year","Month","Day","Hour",
    "Minute","Second","WeekDay","Date","Time","Shell","Beep","Wait",
    "Eof","Lof","Seek", "Exist","Stat","Dir","Dir$","IsBoolean","Boolean?",
    "IsByte","Byte?","IsShort","Short?","IsInteger","Integer?","IsLong","Long?","IsFloat",
    "Float?","IsDate","Date?","IsString","String?","IsNull","Null?","IsObject","Object?",
    "IsNumber","Number?","TypeOf","If","IIf","Choose","LTrim","LTrim$",
    "RTrim","RTrim$","Eval","New","Conv","Conv$","Temp","Temp$","Array","IsDir","Dir?",
    "Access",
    NULL
    };

  if (init)
    return;

  initDictWith(keywordDict, keyword);
  initDictWith(subrDict, subr);
  initDictWith(typeDict, type);
  initDictWith(firstDict, first);
}


QColor QEditor::getColor(int elt)
{
  if (elt < 0 || elt >= NUM_COLORS)
    return QColor(0, 0, 0);
  else
    return color[elt];
}


void QEditor::setColor(int elt, QColor col)
{
  if (elt >= 0 && elt < NUM_COLORS)
  {
    color[elt] = col;
    //qDebug("color[%d] = #%lX", elt, col.rgb());

    if (elt == QEditorRow::Background)
      viewport()->setPaletteBackgroundColor(col);
  }
}


long QEditor::toPos(int row, int col)
{
  int i;
  long pos;

  row = QMAX( QMIN( row, numLines() - 1), 0 );
  col = QMAX( QMIN( col,  lineLength( row )), 0 );

  if (row == 0)
        return QMIN(col, lineLength(0));

  pos = 0;

      for(i = 0; i < row ; i++)
  {
    pos += lineLength(i);
    if (isEndOfParagraph(i))
      pos++;
      }

      pos += col;
  return pos;
}


void QEditor::fromPos(long pos, int *row, int *col)
{
  int l = 0;

  if (pos <= 0)
  {
    *row = 0;
    *col = 0;
    return;
  }

  for (l = 0; l < numLines(); l++)
  {
    if (pos <= lineLength(l))
    {
      *row = l;
      *col = pos;
      return;
    }

    pos -= lineLength(l);
    if (isEndOfParagraph(l))
      pos--;
  }

  *row = l - 1;
  *col = lineLength(l - 1);
}


void QEditor::getSelection(long *start, long *length)
{
  bool sel;
  int line1, col1, line2, col2;

  sel = getMarkedRegion(&line1, &col1, &line2, &col2 );

  if (!sel)
  {
    getCursorPosition(&line1, &col1);
    *start = toPos(line1, col1);
    *length = 0;
    return;
  }

  *start = toPos(line1, col1);
  *length = toPos(line2, col2) - *start;
}


void QEditor::setSelection(long start, long length)
{
  int line1, col1, line2, col2;

  fromPos(start, &line1, &col1);

  if (length <= 0)
  {
    deselect();
    setCursorPosition(line1, col1);
    return;
  }

  fromPos(start + length, &line2, &col2);
  setMarkedRegion(line1, col1, line2, col2 );
}


void QEditor::setLineType(int row, int type)
{
  contents->at(row)->type = type;
  repaintCell(row, 0, false);
}


int QEditor::findNextBreakpoint(int row)
{
  for(;;)
  {
    if ((unsigned int)row >= contents->count())
      return -1;

    if (contents->at(row)->type & QEditorRow::TypeBreakpoint)
      return row;

    row++;
  }
}


/////////////////////////////////////////////////////////////////////////////////

class QEditorCommand
{
public:
  enum Commands
  { Invalid, Begin, End, Insert, Delete, InsertTab, DeleteTab };
  virtual ~ QEditorCommand()
  {
  };
  virtual Commands type()
  {
    return Invalid;
  };
  virtual int terminator()
  {
    return 0;
  }

  virtual bool merge(QEditorCommand *)
  {
    return false;
  }
};

class QBeginCommand:public QEditorCommand
{

public:
  QBeginCommand()
  {
  }
  int terminator()
  {
    return 1;
  }
  Commands type()
  {
    return Begin;
  };
};

class QEndCommand:public QEditorCommand
{
public:
  QEndCommand()
  {
  }
  int terminator()
  {
    return -1;
  }
  Commands type()
  {
    return End;
  };

  bool merge(QEditorCommand * other)
  {
    return (other->type() == Begin);
  }
};

// QEditorUndoRedo methods
class QDelTextCmd:public QEditorCommand
{
public:
  int mOffset;
  QString mStr;

  // have to handle deletion of current selection
  QDelTextCmd(int offset, const QString & str):mOffset(offset), mStr(str)
  {
  }
  Commands type()
  {
    return Delete;
  };

  bool merge(QEditorCommand * other)
  {
    if (other->type() == type())
    {
      QDelTextCmd *o = (QDelTextCmd *) other;
      if (mOffset + int (mStr.length()) == o->mOffset)
      {
        o->mStr.prepend(mStr);
        o->mOffset = mOffset;
        return true;
      }
    }
    return false;
  }


};

class QInsTextCmd:public QDelTextCmd
{

public:
  QInsTextCmd(int offset, const QString & str):QDelTextCmd(offset, str)
  {
  }

  Commands type()
  {
    return Insert;
  };

  bool merge(QEditorCommand * other)
  {
    if (other->type() == type())
    {
      QInsTextCmd *o = (QInsTextCmd *) other;
      if (mOffset == o->mOffset + int (o->mStr.length()))
      {
        o->mStr += mStr;
        return true;
      }
    }
    return false;
  }
};


class QDelTabCmd:public QEditorCommand
{
public:
  int line1, line2;

  QDelTabCmd(int l1, int l2): line1(l1), line2(l2)
  {
  }

  Commands type()
  {
    return DeleteTab;
  };

  bool merge(QEditorCommand * other)
  {
    return false;
  }
};

class QInsTabCmd:public QDelTabCmd
{
public:

  QInsTabCmd(int l1, int l2): QDelTabCmd(l1, l2)
  {
  }

  Commands type()
  {
    return InsertTab;
  };

  bool merge(QEditorCommand * other)
  {
    return false;
  }
};


// NOT REVISED
/*=
  \class QMultiLineEdit qeditor.h

  \brief The QMultiLineEdit widget is a simple editor for inputting text.

  \ingroup advanced

  The QMultiLineEdit widget provides multiple line text input and display.
  It is intended for moderate amounts of text. There are no arbitrary
  limitations, but if you try to handle megabytes of data, performance
  will suffer.

  Per default, the edit widget does not perform any word
  wrapping. This can be adjusted by calling setWordWrap(). Both
  dynamic wrapping according to the visible width or a fixed number of
  character or pixels is supported.

  The widget can be used to display text by calling setReadOnly(true)

  The default key bindings are described in keyPressEvent(); they cannot
  be customized except by inheriting the class.

  <img src=qmlined-m.png> <img src=qmlined-w.png>
*/

/*
static const char *const arrow_xpm[] = {
  "     8     8        2            1",
  ". c None",
  "# c #000000",
  "........",
  "..####..",
  "..#####.",
  ".....##.",
  ".#..###.",
  ".#####..",
  ".####...",
  ".#####.."
};
*/

enum
{
  IdUndo,
  IdRedo,
#ifndef QT_NO_CLIPBOARD
  IdCut,
  IdCopy,
  IdPaste,
  IdPasteSpecial,
#endif
  IdClear,
  IdSelectAll,
  IdCount
};

struct QMultiLineData
{
  QMultiLineData():isHandlingEvent(false),
    edited(false),
    maxLineWidth(0),
    maxlines(-1),
    maxlinelen(-1),
    maxlen(-1),
    //wordwrap(QEditor::NoWrap), wrapcol(-1), wrappolicy(QEditor::AtWhiteSpace),

    // This doesn't use font bearings, as textWidthWithTabs does that.
    // This is just an aesthetics value.
    // It should probably be QMAX(0,3-fontMetrics().minLeftBearing()) though,
    // as bearings give some border anyway.
    lr_marg(6),
    marg_extra(0),
    //echomode(QEditor::Normal),
    val(0),
    dnd_primed(false), dnd_forcecursor(false), undo(true), undodepth(256),
    blinkLevel(0)
    {
      undoList.setAutoDelete(true);
      redoList.setAutoDelete(true);
      clearChartable();
    }

  bool isHandlingEvent;
  bool edited;
  int maxLineWidth;
  int scrollTime;
  int scrollAccel;
  int numlines;
  int maxlines;
  int maxlinelen;
  int maxlen;
  //QEditor::WordWrap wordwrap;
  int wrapcol;
  //QEditor::WrapPolicy wrappolicy;
  int lr_marg;
  int marg_extra;
  //QEditor::EchoMode echomode;
  const QValidator *val;

  bool dnd_primed;              // If true, user has pressed
  bool dnd_forcecursor;         // If true show cursor for DND feedback,
  // even if !hasFocus()
  QPtrList < QEditorCommand > undoList;
  QPtrList < QEditorCommand > redoList;
  bool undo;
  int undodepth;
  short chartable[256];
  void clearChartable()
  {
    int i = 256;
    while (i)
      chartable[--i] = 0;
  }
  QPixmap arrow;
  QPoint dnd_startpos;
  QTimer *blinkTimer, *scrollTimer;
  int blinkLevel;
#ifndef QT_NO_DRAGANDDROP
  QTimer *dnd_timer;
#endif
};


#define CLEAR_UNDO {d->undoList.clear(); emit undoAvailable( false );\
    d->redoList.clear(); emit redoAvailable( false );}

void QEditor::addUndoCmd(QEditorCommand * c)
{
  if (d->undoList.isEmpty())
  {
    emit undoAvailable(true);
  }
  else if (c->merge(d->undoList.last()))
  {
    delete c;
    return;
  }
  if (int (d->undoList.count()) >= d->undodepth)
    d->undoList.removeFirst();
  d->undoList.append(c);

  if (!d->redoList.isEmpty())
  {
    d->redoList.clear();
    emit redoAvailable(false);
  }
}

void QEditor::addRedoCmd(QEditorCommand * c)
{
  if (d->redoList.isEmpty())
    emit redoAvailable(true);
  d->redoList.append(c);
}

void QEditor::colorize(int line)
{
  QString old, s;
  QEditorRow *r = contents->at(line);

  if (!useColor)
    return;

  old = r->s;
  r->colorize();
  if (old.length() != r->s.length())
  {
    s = old;
    old = r->s;
    r->s = s;
    setTextLine(line, old);
  }
}




static const int initialScrollTime = 50;        // mark text scroll time
static const int initialScrollAccel = 3;        // mark text scroll accel (0=fastest)
static const int scroll_margin = 16;    // auto-scroll edge in DND

#define WORD_WRAP false
#define DYNAMIC_WRAP false
#define FIXED_WIDTH_WRAP false
#define FIXED_COLUMN_WRAP false
#define BREAK_WITHIN_WORDS false

/*
#define WORD_WRAP ( d->wordwrap != QEditor::NoWrap )
#define DYNAMIC_WRAP ( d->wordwrap == QEditor::WidgetWidth )
#define FIXED_WIDTH_WRAP ( d->wordwrap == QEditor::FixedPixelWidth )
#define FIXED_COLUMN_WRAP ( d->wordwrap == QEditor::FixedColumnWidth )
#define BREAK_WITHIN_WORDS ( d->wrappolicy == QEditor::Anywhere )
*/

static int defTabStop = 8;

static int tabStopDist(const QFontMetrics & fm)
{
  int dist;
  dist = fm.width(QChar('x'));
  if (dist == 0)
    dist = fm.maxWidth();
  return defTabStop * dist;
}


/*=
  Sets the distance between tab stops for all QEditor instances
  to \a ex, which is measured in multiples of the width of a lower case 'x'
  in the widget's font. The initial value is 8.

  \warning This function does not cause a redraw. It is best to call
  it before any QEditor widgets are shown.

  \sa defaultTabStop()
*/

void QEditor::setDefaultTabStop(int ex)
{
  defTabStop = ex;
}



/*=
  Returns the distance between tab stops.

  \sa setDefaultTabStop();
*/

int QEditor::defaultTabStop()
{
  return defTabStop;
}




static int textWidthWithTabs(const QFontMetrics & fm, const QString & s,
                             uint start, uint nChars)
{
  if (s.isEmpty())
    return 0;


  int dist = 0; //-fm.leftBearing(s[(int) start]);
  int i = start;
  int tabDist = -1;             // lazy eval

  while ((uint) i < s.length() && (uint) i < start + nChars)
  {
    if (s[i] == '\t')
    {
      if (tabDist < 0)
        tabDist = tabStopDist(fm);
      dist = ((dist + tabDist + 1) / tabDist) * tabDist;
      i++;
    }
    else
    {
      int ii = i;
      while ((uint) i < s.length() && (uint) i < start + nChars
             && s[i] != '\t')
        i++;
      dist += fm.width(s.mid(ii, i - ii));
    }
  }


  //return -fm.leftBearing(s[(int)start]) + fm.width(s.mid(start));
  return dist;
}


static int xPosToCursorPos(const QString & s, const QFontMetrics & fm,
                           int xPos, int width)
{
  int i = 0;
  int dist;
  int tabDist;

  if (s.isEmpty())
    return 0;
  if (xPos > width)
    xPos = width;
  if (xPos <= 0)
    return 0;

  dist = 0; //-fm.leftBearing(s[0]);

//    if ( align == Qt::AlignCenter || align == Qt::AlignHCenter )
//      dist = ( width - textWidthWithTabs( fm, s, 0, s.length(), align ) ) / 2;
//    else if ( align == Qt::AlignRight )
//      dist = width - textWidthWithTabs( fm, s, 0, s.length(), align );

  int distBeforeLastTab = dist;
  tabDist = tabStopDist(fm);
  while ((uint) i < s.length() && dist < xPos)
  {
    if (s[i] == '\t')
    {
      distBeforeLastTab = dist;
      dist = (dist / tabDist + 1) * tabDist;
    }
    else
    {
      dist += fm.width(s[i]);
    }
    i++;
  }
  if (dist > xPos)
  {
    if (dist > width)
    {
      i--;
    }
    else
    {
      if (s[i - 1] == '\t')
      {                         // dist equals a tab stop position
        if (xPos - distBeforeLastTab < (dist - distBeforeLastTab) / 2)
          i--;
      }
      else
      {
        if (fm.width(s[i - 1]) / 2 < dist - xPos)
          i--;
      }
    }
  }
  return i;
}

/*=
  Constructs a new, empty, QEditor.
*/

QEditor::QEditor(QWidget * parent, const char *name):QGridView(parent, name,
           WNorthWestGravity | WRepaintNoErase)
{
  d = new QMultiLineData;
  QFontMetrics fm(font());
  setCellHeight(fm.lineSpacing());
  setNumCols(1);

  contents = new QPtrList < QEditorRow >;
  contents->setAutoDelete(true);

  cursorX = 0;
  cursorY = 0;
  brotherX = -1;
  brotherX2 = -1;
  brotherY = -1;
  oldX = -1;
  oldY = -1;
  curXPos = 0;

  //setBackgroundMode(PaletteBase);
  //setWFlags(WResizeNoErase);

  setKeyCompression(true);
  setFocusPolicy(WheelFocus);
#ifndef QT_NO_CURSOR
  viewport()->setCursor(ibeamCursor);
#endif
  readOnly = false;
  cursorOn = false;
  markIsOn = false;
  dragScrolling = false;
  dragMarking = false;
  textDirty = false;
  wordMark = false;
  overWrite = false;
  showProc = true;
  showChange = true;
  useRelief = true;
  useColor = true;
  noEmitCursorMoved = 0;

  markAnchorX = 0;
  markAnchorY = 0;
  markDragX = 0;
  markDragY = 0;

  d->blinkTimer = new QTimer(this);
  connect(d->blinkTimer, SIGNAL(timeout()), this, SLOT(blinkTimerTimeout()));
  d->scrollTimer = new QTimer(this);
  connect(d->scrollTimer, SIGNAL(timeout()),
          this, SLOT(scrollTimerTimeout()));
#ifndef QT_NO_DRAGANDDROP
  d->dnd_timer = new QTimer(this);
  connect(d->dnd_timer, SIGNAL(timeout()), this, SLOT(dndTimeout()));
#endif
  d->scrollTime = 0;

  dummy = true;

  int w = textWidth(QString::fromLatin1(""));
  contents->append(new QEditorRow(QString::fromLatin1(""), w));
  (void) setNumRowsAndTruncate();
  setWidth(w);
  setAcceptDrops(true);
  if (d->maxlines >= 0 && d->maxlines <= 6)
  {
    setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
  }
  else
  {
    setSizePolicy(QSizePolicy
                  (QSizePolicy::Expanding, QSizePolicy::Expanding));
  }

  setInputMethodEnabled( TRUE );

  // BM
  initDict();

  // gcc 3.1 does not like the following:
  // color = default_color;

  for (int i = 0; i < NUM_COLORS; i++)
    color[i] = default_color[i];

  setColor(QEditorRow::Background, color[QEditorRow::Background]);
  tabWidth = 2;
}


void QEditor::setReadOnly(bool on)
{
  if (readOnly != on)
  {
    readOnly = on;
#ifndef QT_NO_CURSOR
    viewport()->setCursor(on ? arrowCursor : ibeamCursor);
#endif
  }
}

/*=
  Returns the width in pixels of the longest text line in this editor.
*/
int QEditor::maxLineWidth() const
{
  // [BM]
  //return QMAX(d->maxLineWidth, contentsRect().width());
  return d->maxLineWidth;
}

/*=
  Destroys the QEditor
*/

QEditor::~QEditor()
{
  delete contents;
  delete d;
}

static QPixmap *buffer = 0;

static void cleanupMLBuffer()
{
  delete buffer;
  buffer = 0;
}

static QPixmap *getCacheBuffer(QSize sz)
{
  if (!buffer)
  {
    qAddPostRoutine(cleanupMLBuffer);
    buffer = new QPixmap;
  }

  if (buffer->width() < sz.width() || buffer->height() < sz.height())
    buffer->resize(sz);
  return buffer;
}

/*=
  Implements the basic drawing logic.
*/

void QEditor::paintCell(QPainter * painter, int row, int)
{
  QEditorRow *r = contents->at(row);
  int len = r->s.length();
  bool selected = false;
  int x, y, w, h;
  int markX1, markX2;           // in x-coordinate pixels
  int markBeginX, markBeginY;
  int markEndX, markEndY;
  bool cursor;

  //markX1 = markX2 = 0;          // avoid gcc warning

  QRect updateR = cellRect(); //cellUpdateRect();

  // [BM]
  //max_width = width() - frameWidth() * 2;
  //updateR.setWidth(max_width);

  QPixmap *buffer = getCacheBuffer(updateR.size());
  ASSERT(buffer);
  buffer->fill(color[QEditorRow::Background]);  //g.base()

  cursor = (hasFocus() || d->dnd_forcecursor);

  QPainter p(buffer);
  p.setFont(painter->font());
  p.translate(-updateR.left(), -updateR.top());
  // [BM] p.setTabStops(tabStopDist(fm));

  //x = 0; // [BM] d->lr_marg - p.fontMetrics().leftBearing(s[0]);
  //w = cellWidth() - x; // cellWidth() - d->lr_marg - x
  x = d->lr_marg;
  y = 0;
  w = cellWidth();
  h = cellHeight();

  r->drawBack(p, 0, y, w, h, color, showCurrent && row == cursorY);

  selected = false;

  if (cursor && row == brotherY && brotherX >= 0)
  {
    int x = QMAX(mapToView(brotherX, row), 0);
    int w = QMAX(mapToView(brotherX + 1, row), 0) - x;

    p.fillRect(x, 0, w, cellHeight(), color[QEditorRow::Highlight]);

    x = QMAX(mapToView(brotherX2, row), 0);
    w = QMAX(mapToView(brotherX2 + 1, row), 0) - x;

    p.fillRect(x, 0, w, cellHeight(), color[QEditorRow::Highlight]);
  }

  if (markIsOn)
  {
    getMarkedRegion(&markBeginY, &markBeginX, &markEndY, &markEndX);
    if (row >= markBeginY && row <= markEndY)
    {
      selected = true;

      if (row == markBeginY)
      {
        markX1 = markBeginX;
        if (row == markEndY)    // both marks on same row
          markX2 = markEndX;
        else
        {
          markX2 = len;  // mark till end of line
          if (markX1 == 0)
            markX1 = -1;
        }
      }
      else
      {
        if (row == markEndY)
        {
          markX1 = -1;
          markX2 = markEndX;
          //if (markX2 == 0)
          //  selected = false;
        }
        else
        {
          markX1 = -1;           // whole line is marked
          markX2 = len;  // whole line is marked
        }
      }

      if (selected)
      {
        int fillxpos1;
        int fillxpos2;

        if (markX1 < 0)
          fillxpos1 = 0;
        else
          fillxpos1 = mapToView(markX1, row);

        if (markX2 == len && row < markEndY)
          fillxpos2 = updateR.right() + 1;
        else
          fillxpos2 = mapToView(markX2, row);

        p.setClipping(true);
        p.setClipRect(fillxpos1 - updateR.left(), 0,
                      fillxpos2 - fillxpos1, cellHeight());
        p.fillRect(fillxpos1, 0, fillxpos2 - fillxpos1, cellHeight(),
                   color[QEditorRow::Selection]);
        //p.setPen( g.highlightedText() );
        //p.drawText( x,  yPos, cellWidth()-d->lr_marg-x, cellHeight(),
        //      d->align == AlignLeft?ExpandTabs:0, s );
        p.setClipping(false);
      }

    }
  }

  if (useColor)
  {
    r->draw(p, x, y, w, h, color, useRelief);
    if (showProc && r->line)
    {
      p.setPen(color[QEditorRow::Selection]);
      p.drawLine(0, y, w - 1, y);
    }
  }
  else
    r->drawNormal(p, x, y, w, h, color);

  if (showChange && r->changed)
  {
    p.fillRect(0, y, x - 2, h, color[QEditorRow::Highlight]);
    //p.setPen(color[QEditorRow::Normal]);
    //p.drawLine(x - 2, y, x - 2, y + h - 1);
  }

  if (cursor && cursorOn)
  {
    if (row == cursorY) // && !readOnly)
    {
      int cursorPos = QMIN((int)len, cursorX);
      int cXPos = QMAX(mapToView(cursorPos, row), 0);
      int cYPos = 0;

      p.fillRect(cXPos, cYPos - 1, 2, cellHeight() + 2,
                 color[QEditorRow::Normal]);
    }
  }

  p.end();

  painter->drawPixmap(updateR.left(), updateR.top(), *buffer, 0, 0,
                      updateR.width(), updateR.height());
}


/*=
  Returns the width in pixels of the string \a s.
  NOTE: only appropriate for whole lines.
*/

int QEditor::textWidth(const QString & s)
{
  int w = 0;
  if (!s.isNull())
  {
    w = textWidthWithTabs(QFontMetrics(font()), s, 0, s.length());
  }
  return w + 2 * d->lr_marg + d->marg_extra;
}


/*=
  Returns the width in pixels of the text at line \a line.
*/

int QEditor::textWidth(int line)
{
/*
  if (d->echomode == Password)
  {
    QString s = stringShown(line);
    return textWidth(s);
  }
*/
  QEditorRow *r = contents->at(line);
  return r ? r->w : 0;
}

/*=
  Starts the cursor blinking.
*/

void QEditor::focusInEvent(QFocusEvent *)
{
  stopAutoScroll();
  //if (!d->blinkTimer->isActive())
  d->blinkLevel = 0;
  startBlink();
  //repaintCell(cursorY, 0, false);
}


/*=\reimp
*/
void QEditor::leaveEvent(QEvent *)
{
}


/*=\reimp
*/
void QEditor::focusOutEvent(QFocusEvent *)
{
  stopAutoScroll();
  d->blinkLevel = 1;
  stopBlink();
  if (!cursorOn)
  {
    cursorOn = true;
    repaintCell(cursorY, 0, false);
  }
}


#ifndef QT_NO_DRAGANDDROP
void QEditor::doDrag()
{
  if (d->dnd_timer)
  {
    d->dnd_timer->stop();
  }
  QDragObject *drag_text = new QTextDrag(markedText(), this);
  if (readOnly)
  {
    drag_text->dragCopy();
  }
  else
  {
    if (drag_text->drag() && QDragObject::target() != this)
    {
      del();
      if (textDirty && !d->isHandlingEvent)
        emit textChanged();
    }
  }
  d->dnd_primed = false;
}
#endif

/*=
  If there is marked text, sets \a line1, \a col1, \a line2 and \a col2
  to the start and end of the marked region and returns true. Returns
  false if there is no marked text.
 */
bool QEditor::getMarkedRegion(int *line1, int *col1, int *line2, int *col2) const
{
  if (!markIsOn || !line1 || !line2 || !col1 || !col2)
    return false;
  if (markAnchorY < markDragY ||
      markAnchorY == markDragY && markAnchorX < markDragX)
  {
    *line1 = markAnchorY;
    *col1 = markAnchorX;
    *line2 = markDragY;
    *col2 = markDragX;
    if (*line2 > numLines() - 1)
    {
      *line2 = numLines() - 1;
      *col2 = lineLength(*line2);
    }
  }
  else
  {
    *line1 = markDragY;
    *col1 = markDragX;
    *line2 = markAnchorY;
    *col2 = markAnchorX;
    if (*line2 > numLines() - 1)
    {
      *line2 = numLines() - 1;
      *col2 = lineLength(*line2);
    }
  }
  return markIsOn;
}


/*=
  Returns true if there is marked text.
*/

bool QEditor::hasMarkedText() const
{
  return markIsOn;
}


/*=
  Returns a copy of the marked text.
*/

QString QEditor::markedText() const
{
  int markBeginX, markBeginY;
  int markEndX, markEndY;
  if (!getMarkedRegion(&markBeginY, &markBeginX, &markEndY, &markEndX))
    return QString();
  if (markBeginY == markEndY)
  {                             //just one line
    QString *s = getString(markBeginY);
    return s->mid(markBeginX, markEndX - markBeginX);
  }
  else
  {                             //multiline
    QString *firstS, *lastS;
    firstS = getString(markBeginY);
    lastS = getString(markEndY);
    int i;
    QString tmp;
    if (firstS)
      tmp += firstS->mid(markBeginX);
    if (contents->at(markBeginY)->newline)
      tmp += '\n';

    for (i = markBeginY + 1; i < markEndY; i++)
    {
      tmp += *getString(i);
      if (contents->at(i)->newline)
        tmp += '\n';
    }

    if (lastS)
    {
      tmp += lastS->left(markEndX);
    }
    else
    {
      tmp.truncate(tmp.length() - 1);
    }

    return tmp;
  }
}



/*=
  Returns the text at line number \a line (possibly the empty string),
  or a \link QString::operator!() null string\endlink if \a line is invalid.
*/

QString QEditor::textLine(int line) const
{
  QString *s = getString(line);
  if (s)
  {
    if (s->isNull())
      return QString::fromLatin1("");
    else
      return *s;
  }
  else
    return QString::null;
}


void QEditor::setTextLine(int line, QString & s)
{
  int offset = positionToOffsetInternal(line, 0);

  addUndoCmd(new QBeginCommand);

  d->undoList.append(new QDelTextCmd(offset, contents->at(line)->s));
  contents->at(line)->s = QString::fromLatin1("");
  insertAt(s, line, 0, false);

  colorize(line);

  addUndoCmd(new QEndCommand);

  emit textChanged();
}


/*=
  Returns a copy of the whole text. If the multi line edit contains no
  text, a
  \link QString::operator!() null string\endlink
  is returned.
*/

QString QEditor::text()
{
  static QString tmp;

  colorize(cursorY);

  tmp = "";
  for (int i = 0; i < (int) contents->count(); i++)
  {
    tmp += *getString(i);

    if (i + 1 < (int) contents->count() && contents->at(i)->newline)
      tmp += '\n';
  }

  return tmp;
}



/*=
  Selects all text without moving the cursor.
*/

void QEditor::selectAll()
{
  markAnchorX = 0;
  markAnchorY = 0;
  markDragY = numLines() - 1;
  markDragX = lineLength(markDragY);
  turnMark(markDragX != markAnchorX || markDragY != markAnchorY);
  if (autoUpdate())
    updateContents();
}



/*=
  Deselects all text (i.e. removes marking) and leaves the cursor at the
  current position.
*/

void QEditor::deselect()
{
  turnMark(false);
}


/*=
  Sets the text to \a s, removing old text, if any.
*/

void QEditor::resetChangedLines()
{
  for (int i = 0; i < (int) contents->count(); i++)
    contents->at(i)->changed = false;
  if (autoUpdate())
    updateContents();
}


void QEditor::setText(const QString & s)
{
  bool oldUndo = isUndoEnabled();
  setUndoEnabled(false);
  bool oldAuto = autoUpdate();
  setAutoUpdate(false);
  bool b = signalsBlocked();
  blockSignals(true);
  clear();
  CLEAR_UNDO blockSignals(b);
  insertLine(s, -1);

  emit textChanged();
  setAutoUpdate(oldAuto);
  if (autoUpdate())
    viewport()->repaint();
  setUndoEnabled(oldUndo);
}


/*=
  Appends \a s to the text.
*/

void QEditor::append(const QString & s)
{
  bool oldUndo = isUndoEnabled();
  setUndoEnabled(false);
  insertLine(s, -1);
  setUndoEnabled(oldUndo);
  emit textChanged();
}

/*void QEditor::wheelEvent(QWheelEvent * e)
{
  QApplication::sendEvent(verticalScrollBar(), e);
}
*/

/*=
  The key press event handler converts a key press to some line editor
  action.

  Here are the default key bindings when isReadOnly() is false:
  <ul>
  <li><i> Left Arrow </i> Move the cursor one character leftwards
  <li><i> Right Arrow </i> Move the cursor one character rightwards
  <li><i> Up Arrow </i> Move the cursor one line upwards
  <li><i> Down Arrow </i> Move the cursor one line downwards
  <li><i> Page Up </i> Move the cursor one page upwards
  <li><i> Page Down </i> Move the cursor one page downwards
  <li><i> Backspace </i> Delete the character to the left of the cursor
  <li><i> Home </i> Move the cursor to the beginning of the line
  <li><i> End </i> Move the cursor to the end of the line
  <li><i> Delete </i> Delete the character to the right of the cursor
  <li><i> Shift - Left Arrow </i> Mark text one character leftwards
  <li><i> Shift - Right Arrow </i> Mark text one character rightwards
  <li><i> Control-A </i> Move the cursor to the beginning of the line
  <li><i> Control-B </i> Move the cursor one character leftwards
  <li><i> Control-C </i> Copy the marked text to the clipboard
  <li><i> Control-D </i> Delete the character to the right of the cursor
  <li><i> Control-E </i> Move the cursor to the end of the line
  <li><i> Control-F </i> Move the cursor one character rightwards
  <li><i> Control-H </i> Delete the character to the left of the cursor
  <li><i> Control-K </i> Delete to end of line
  <li><i> Control-N </i> Move the cursor one line downwards
  <li><i> Control-P </i> Move the cursor one line upwards
  <li><i> Control-V </i> Paste the clipboard text into line edit
  <li><i> Control-X </i> Cut the marked text, copy to clipboard
  <li><i> Control-Z </i> Undo the last operation
  <li><i> Control-Y </i> Redo the last operation
  <li><i> Control - Left Arrow </i> Move the cursor one word leftwards
  <li><i> Control - Right Arrow </i> Move the cursor one word rightwards
  <li><i> Control - Up Arrow </i> Move the cursor one word upwards
  <li><i> Control - Down Arrow </i> Move the cursor one word downwards
  <li><i> Control - Home Arrow </i> Move the cursor to the beginning of the text
  <li><i> Control - End Arrow </i> Move the cursor to the end of the text
  </ul>
  In addition, the following key bindings are used on Windows:
  <ul>
  <li><i> Shift - Delete </i> Cut the marked text, copy to clipboard
  <li><i> Shift - Insert </i> Paste the clipboard text into line edit
  <li><i> Control - Insert </i> Copy the marked text to the clipboard
  </ul>
  All other keys with valid ASCII codes insert themselves into the line.

  Here are the default key bindings when isReadOnly() is true:
  <ul>
  <li><i> Left Arrow </i> Scrolls the table rightwards
  <li><i> Right Arrow </i> Scrolls the table rightwards
  <li><i> Up Arrow </i> Scrolls the table one line downwards
  <li><i> Down Arrow </i> Scrolls the table one line upwards
  <li><i> Page Up </i> Scrolls the table one page downwards
  <li><i> Page Down </i> Scrolls the table one page upwards
  <li><i> Control-C </i> Copy the marked text to the clipboard
  </ul>

*/

void QEditor::keyPressEvent(QKeyEvent * e)
{
  textDirty = false;
  d->isHandlingEvent = true;
  int unknown = 0;
  if (readOnly)
  {
    int pageSize = contentsHeight() / cellHeight();

    switch (e->key())
    {
      case Key_Left:
        setXOffset(xOffset() - viewWidth() / 10);
        break;
      case Key_Right:
        setXOffset(xOffset() + viewWidth() / 10);
        break;
      case Key_Up:
        //setTopCell(topCell() - 1);
        cursorUp(false);
        break;
      case Key_Down:
        //setTopCell(topCell() + 1);
        cursorDown(false);
        break;
      case Key_Home:
        setCursorPosition(0, 0, e->state() & ShiftButton);
        break;
      case Key_End:
        setCursorPosition(numLines() - 1, lineLength(numLines() - 1),
                          e->state() & ShiftButton);
        break;
      case Key_Next:
        setTopCell(topCell() + pageSize);
        break;
      case Key_Prior:
        setTopCell(QMAX(topCell() - pageSize, 0));
        break;
#ifndef QT_NO_CLIPBOARD
      case Key_C:
        //if (echoMode() == Normal && (e->state() & ControlButton))
        if (e->state() & ControlButton)
          copy();
        else
          unknown++;
        break;
      case Key_F16:            // Copy key on Sun keyboards
        //if (echoMode() == Normal)
          copy();
        //else
        //  unknown++;
        break;
#endif
      default:
        unknown++;
    }
    if (unknown)
      e->ignore();
    d->isHandlingEvent = false;
    return;
  }
  if (e->text().length() &&
      e->key() != Key_Return &&
      e->key() != Key_Enter &&
      e->key() != Key_Delete &&
      e->key() != Key_Backspace && (!e->ascii() || e->ascii() >= 32))
  {
    insert(e->text());
    //QApplication::sendPostedEvents( this, QEvent::Paint );
    d->isHandlingEvent = false;
    return;
  }
  if (e->state() & ControlButton)
  {
    switch (e->key())
    {
      case Key_A:
        home(e->state() & ShiftButton);
        break;
      case Key_B:
        cursorLeft(e->state() & ShiftButton);
        break;
#ifndef QT_NO_CLIPBOARD
      case Key_C:
        //if (echoMode() == Normal)
          copy();
        break;
#endif
      case Key_D:
        del();
        break;
      case Key_E:
        end(e->state() & ShiftButton);
        break;
      case Key_Left:
        cursorWordBackward(e->state() & ShiftButton);
        break;
      case Key_Right:
        cursorWordForward(e->state() & ShiftButton);
        break;
      case Key_Up:
        cursorUp(e->state() & ShiftButton);
        break;
      case Key_Down:
        cursorDown(e->state() & ShiftButton);
        break;
      case Key_Home:
        setCursorPosition(0, 0, e->state() & ShiftButton);
        break;
      case Key_End:
        setCursorPosition(numLines() - 1, lineLength(numLines() - 1),
                          e->state() & ShiftButton);
        break;
      case Key_F:
        cursorRight(e->state() & ShiftButton);
        break;
      case Key_H:
        backspace();
        break;
      case Key_K:
        killLine();
        break;
      case Key_N:
        cursorDown(e->state() & ShiftButton);
        break;
      case Key_P:
        cursorUp(e->state() & ShiftButton);
        break;
#ifndef QT_NO_CLIPBOARD
      case Key_V:
        paste();
        break;
      case Key_X:
        cut();
        break;
#endif
      case Key_Z:
        undo();
        break;
      case Key_Y:
        redo();
        break;
#if defined (_WS_WIN_)
      case Key_Insert:
        copy();
#endif
      default:
        unknown++;
    }
  }
  else
  {
    switch (e->key())
    {
      case Key_Left:
        cursorLeft(e->state() & ShiftButton);
        break;
      case Key_Right:
        cursorRight(e->state() & ShiftButton);
        break;
      case Key_Up:
        cursorUp(e->state() & ShiftButton);
        break;
      case Key_Down:
        cursorDown(e->state() & ShiftButton);
        break;
      case Key_Backspace:
        backspace();
        break;
      case Key_Home:
        home(e->state() & ShiftButton);
        break;
      case Key_End:
        end(e->state() & ShiftButton);
        break;
      case Key_Delete:
#if defined (_WS_WIN_)
        if (e->state() & ShiftButton)
        {
          cut();
          break;
        }
#endif
        del();
        break;
      case Key_Next:
        pageDown(e->state() & ShiftButton);
        break;
      case Key_Prior:
        pageUp(e->state() & ShiftButton);
        break;
      case Key_Enter:
      case Key_Return:
        newLine();
        emit returnPressed();
        break;
      case Key_Tab:
        tab(false);
        break;
      case Key_BackTab:
        tab(true);
        break;
#if defined (_WS_WIN_)
      case Key_Insert:
        if (e->state() & ShiftButton)
          paste();
        else
          unknown++;
        break;
#endif
      case Key_F14:            // Undo key on Sun keyboards
        undo();
        break;
#ifndef QT_NO_CLIPBOARD
      case Key_F16:            // Copy key on Sun keyboards
        //if (echoMode() == Normal)
          copy();
        break;
      case Key_F18:            // Paste key on Sun keyboards
        paste();
        break;
      case Key_F20:            // Paste key on Sun keyboards
        cut();
        break;
#endif
      default:
        unknown++;
    }
  }
  if (textDirty)
    emit textChanged();

  if (unknown)                  // unknown key
    e->ignore();

  d->isHandlingEvent = false;
}


/*=
  Moves the cursor one page down.  If \a mark is true, the text
  is marked.
*/

void QEditor::pageDown(bool mark)
{
  bool oldAuto = autoUpdate();

  if (cursorY >= (int) contents->count() - 1)
  {
    makeVisible();
    return;
  }

  if (mark)
    setAutoUpdate(false);

  if (partiallyInvisible(cursorY))
    setY(topCell());

  int delta = cursorY - topCell();
  int pageSize = viewHeight() / cellHeight();
  int newTopCell = QMIN(topCell() + pageSize, numLines() - pageSize - 1);

  if (pageSize >= numLines())
  {                             // quick fix to handle small texts
    newTopCell = topCell();
  }
  if (!curXPos)
    curXPos = mapToView(cursorX, cursorY);
  int oldY = cursorY;

  if (mark && !hasMarkedText())
  {
    markAnchorX = cursorX;
    markAnchorY = cursorY;
  }
  if (newTopCell != topCell())
  {
    setY(newTopCell + delta);
    cursorX = mapFromView(curXPos, cursorY);
    if (mark)
      newMark(cursorX, cursorY, false);
    setTopCell(newTopCell);
  }
  else if (cursorY != (int) contents->count() - 1)
  {                             // just move the cursor
    setY(QMIN(cursorY + pageSize, numLines() - 1));
    cursorX = mapFromView(curXPos, cursorY);
    if (mark)
      newMark(cursorX, cursorY, false);
    //makeVisible();
  }
  if (oldAuto)
    if (mark)
    {
      setAutoUpdate(true);
      updateContents();
    }
    else
    {
      repaintCell(oldY, 0, false);
    }
  if (!mark)
    turnMark(false);

  makeVisible();
}


/*=
  Moves the cursor one page up.  If \a mark is true, the text
  is marked.
*/

void QEditor::pageUp(bool mark)
{
  bool oldAuto = autoUpdate();
  if (mark)
    setAutoUpdate(false);
  if (partiallyInvisible(cursorY))
    setY(topCell());

  int delta = cursorY - topCell();
  int pageSize = viewHeight() / cellHeight();
  bool partial = delta == pageSize && viewHeight() != pageSize * cellHeight();
  int newTopCell = QMAX(topCell() - pageSize, 0);
  if (pageSize > numLines())
  {                             // quick fix to handle small texts
    newTopCell = 0;
    delta = 0;
  }
  if (mark && !hasMarkedText())
  {
    markAnchorX = cursorX;
    markAnchorY = cursorY;
  }
  if (!curXPos)
    curXPos = mapToView(cursorX, cursorY);
  int oldY = cursorY;
  if (newTopCell != topCell())
  {
    setY(QMIN(newTopCell + delta, numLines() - 1));
    if (partial)
      setY(cursorY - 1);
    cursorX = mapFromView(curXPos, cursorY);
    if (mark)
      newMark(cursorX, cursorY, false);
    setTopCell(newTopCell);
  }
  else
  {                             // just move the cursor
    setY(QMAX(cursorY - pageSize, 0));
    cursorX = mapFromView(curXPos, cursorY);
    if (mark)
      newMark(cursorX, cursorY, false);
  }
  if (oldAuto)
    if (mark)
    {
      setAutoUpdate(true);
      updateContents();
    }
    else
    {
      repaintCell(oldY, 0, false);
    }
  if (!mark)
    turnMark(false);

  makeVisible();
}

// THE CORE INSERTION FUNCTION
void QEditor::insertAtAux(const QString & txt, int line, int col, bool mark)
{
  dummy = false;
  stopBlink();
  cursorOn = true;
  //int oldw = contentsRect().width();

  line = QMAX(QMIN(line, numLines() - 1), 0);
  col = QMAX(QMIN(col, lineLength(line)), 0);

  QString itxt = txt;
  QEditorRow *row = contents->at(line);
  if (d->maxlen >= 0 && length() + int (txt.length()) > d->maxlen)
    itxt.truncate(d->maxlen - length());

  row->s.insert(uint(col), itxt);
  row->change();

  if (mark)
  {
    markAnchorX = col;
    markAnchorY = line;
  }
  if (cursorX == col && cursorY == line)
  {
    cursorX += itxt.length();
  }
  // BM - QFontMetrics fm(font());
  #if 0
  if (!WORD_WRAP || (col == 0 && itxt.contains('\n') == int (itxt.length())))
    wrapLine(line, 0);
  /*else if (WORD_WRAP && itxt.find('\n') < 0 && itxt.find('\t') < 0
           && ((DYNAMIC_WRAP
                && fm.width(itxt) + row->w <
                contentsRect().width() - 2 * d->lr_marg - d->marg_extra)
               || (FIXED_WIDTH_WRAP
                   && (d->wrapcol < 0
                       || fm.width(itxt) + row->w < d->wrapcol))
               || (FIXED_COLUMN_WRAP
                   && (d->wrapcol < 0 || int (row->s.length()) < d->wrapcol)))
           && (itxt.find(' ') < 0 || row->s.find(' ') >= 0
               && row->s.find(' ') < col))
  {
    row->w = textWidth(row->s);
    setWidth(QMAX(maxLineWidth(), row->w));
    repaintCell(line, 0, false);
  }*/
  else
  {
    if (line > 0 && !contents->at(line - 1)->newline)
      rebreakParagraph(line - 1);
    else
      rebreakParagraph(line);
  }
  #endif

  wrapLine(line, 0);

  if (mark)
    newMark(cursorX, cursorY, false);

  setNumRowsAndTruncate();

  textDirty = true;
  d->edited = true;

  #if 0
  if (autoUpdate())
  {
    makeVisible();
    if (DYNAMIC_WRAP && oldw != contentsRect().width())
    {
      setAutoUpdate(false);
      rebreakAll();
      setAutoUpdate(true);
      updateContents();
    }
  }
  #endif

  startBlink();
}


/*=
  Inserts \a txt at line number \a line. If \a line is less than zero,
  or larger than the number of rows, the new text is put at the end.
  If \a txt contains newline characters, several lines are inserted.

  The cursor position is not changed.
*/

void QEditor::insertLine(const QString & txt, int line)
{
  QString s = txt;
  int oldXPos = cursorX;
  int oldYPos = cursorY;
  if (line < 0 || line >= int (contents->count()))
  {
    if (!dummy)
      contents->append(new QEditorRow(QString::fromLatin1(""), 0));
    insertAt(s, numLines() - 1, 0);
  }
  else
  {
    s.append('\n');
    insertAt(s, line, 0);
  }

  cursorX = oldXPos;
  cursorY = oldYPos;
}

/*=
  Deletes the line at line number \a line. If \a
  line is less than zero, or larger than the number of lines,
  no line is deleted.
*/

void QEditor::removeLine(int line)
{
  CLEAR_UNDO

  if (line >= numLines())
    return;
  if (cursorY >= line && cursorY > 0)
  {
    setY(cursorY - 1);
  }

  bool updt = autoUpdate() && rowIsVisible(line);
  QEditorRow *r = contents->at(line);
  ASSERT(r);
  bool recalc = r->w == maxLineWidth();
  contents->remove(line);
  if (contents->count() == 0)
  {
    int w = textWidth(QString::fromLatin1(""));
    contents->append(new QEditorRow(QString::fromLatin1(""), w));
    setWidth(w);
    dummy = true;
  }
  if (setNumRowsAndTruncate())
    recalc = updt = false;
  if (recalc)
    updateCellWidth();
  makeVisible();
  if (updt)
    updateContents();
  textDirty = true;
  d->edited = true;
}

/*=
  Inserts \a s at the current cursor position.
*/
void QEditor::insert(const QString & s)
{
  insert(s, false);
  if (textDirty)
    emit textChanged();
}

/*=
  Inserts \a c at the current cursor position.
*/

void QEditor::insert(const QString & str, bool mark)
{
  bool nl = str.contains('\n');
  dummy = false;
  bool wasMarkedText = hasMarkedText();
  if (wasMarkedText)
    addUndoCmd(new QBeginCommand);
  if (wasMarkedText)
    del();                      // ## Will flicker
  QString *s = getString(cursorY);
  if (cursorX > (int) s->length())
    cursorX = s->length();
  else if (overWrite && !wasMarkedText && cursorX < (int) s->length())
    del();                      // ## Will flicker
  insertAt(str, cursorY, cursorX, mark);
  makeVisible();

  if (nl)
  {
    colorize(cursorY);
    repaintCell(cursorY, 0, false);
  }

  if (wasMarkedText)
    addUndoCmd(new QEndCommand());
}

/*=
  Makes a line break at the current cursor position.
*/


int QEditor::getIndent(int line, bool &empty)
{
  empty = true;

  if (line < 0 || line > numLines())
    return 0;

  QEditorRow *r = contents->at(line);
  int i = 0;

  for (i = 0; i < (int)r->s.length(); i++)
  {
    if (!r->s[i].isSpace())
    {
      empty = false;
      break;
    }
  }

  return i;
}


void QEditor::newLine()
{
  bool empty;
  QEditorRow *r = contents->at(cursorY);
  insert("\n" + r->s.left(QMIN(cursorX, getIndent(cursorY, empty))));
}

void QEditor::tab(bool back, bool noundo /* = false */)
{
  int line1, col1, line2, col2, y;
  int indent, i;
  QString ins;
  QEditorRow *r;
  //int offset;
  bool autoupdate;
  bool empty;

  if (!getMarkedRegion(&line1, &col1, &line2, &col2))
  {
    if (!back)
    {
      ins.fill(' ', tabWidth - (cursorX % tabWidth));
      insert(ins);
    }
    return;
  }

  autoupdate = autoUpdate();
  setAutoUpdate(false);

  stopBlink();

  col1 = 0;

  if (col2 > 0)
  {
    if (line2 < (numLines() - 1))
    {
      line2++;
      col2 = 0;
    }
    else
      col2 = lineLength(line2);
  }

  setCursorPosition(line1, col1, false);
  setCursorPosition(line2, col2, true);


  y = line1;
  indent = 65536;
  for (y = line1; y < line2; y++)
  {
    i = getIndent(y, empty);
    if (!empty)
      indent = QMIN(indent, i);
  }

  if (back && indent <= 0)
  {
    setAutoUpdate(autoupdate);
    startBlink();
    return;
  }

  if (!back)
  {
    ins.fill(' ', tabWidth - (indent % tabWidth));

    for (y = line1; y < line2; y++)
    {
      //insertAt(ins, y, 0);
      r = contents->at(y);

      /*
      if (!noundo)
      {
        offset = positionToOffsetInternal(y, 0);
        d->undoList.append(new QInsTextCmd(offset, ins));
      }
      */

      r->s = ins + r->s;
      r->change();
      colorize(y);
    }

    if (!noundo)
      addUndoCmd(new QInsTabCmd(line1, line2));
  }
  else
  {
    indent %= tabWidth;
    if (indent == 0)
      indent = tabWidth;

    ins.fill(' ', indent);

    for (y = line1; y < line2; y++)
    {
      r = contents->at(y);

      /*
      if (!noundo)
      {
        offset = positionToOffsetInternal(y, 0);
        d->undoList.append(new QDelTextCmd(offset, r->s.left(indent)));
      }
      */

      if (r->s.length() < ins.length() || r->s.left(indent) == ins)
      {
        r->s = r->s.mid(indent);
        r->change();
        colorize(y);
      }
    }

    if (!noundo)
      addUndoCmd(new QDelTabCmd(line1, line2));
  }

  setAutoUpdate(autoupdate);
  if (autoUpdate())
    updateContents();

  startBlink();

  emit textChanged();
}

void QEditor::indent(bool back)
{
  if (!hasMarkedText())
    return;

  tab(back);
}



/*=
  Deletes text from the current cursor position to the end of the line.
*/

void QEditor::killLineAux()
{
  deselect();                   // -sanders Don't let del() delete marked region
  QEditorRow *r = contents->at(cursorY);
  if (cursorX == (int) r->s.length())
  {
    //      if (r->newline) // -sanders Only del newlines!
    del();
    return;
  }
  else
  {
    bool recalc = r->w == maxLineWidth();

    r->s.remove(cursorX, r->s.length());
    r->w = textWidth(r->s);
    r->change();

    repaintCell(cursorY, 0, false);
    if (recalc)
      updateCellWidth();
    rebreakParagraph(cursorY);  // -sanders
    textDirty = true;
    d->edited = true;
  }
  curXPos = 0;
  makeVisible();
  turnMark(false);
}


/*=
  Moves the cursor one character to the left. If \a mark is true, the text
  is marked. If \a wrap is true, the cursor moves to the end of the
  previous line  if it is placed at the beginning of the current line.

  \sa cursorRight() cursorUp() cursorDown()
*/

void QEditor::cursorLeft(bool mark, bool wrap)
{
  cursorLeft(mark, !mark, wrap);
}

void QEditor::cursorLeft(bool mark, bool clear_mark, bool wrap)
{
  int oldY;

  if (cursorX != 0 || cursorY != 0 && wrap)
  {
    if (mark && !hasMarkedText())
    {
      markAnchorX = cursorX;
      markAnchorY = cursorY;
    }
    stopBlink();
    int ll = lineLength(cursorY);
    if (cursorX > ll)
      cursorX = ll;
    cursorOn = true;
    cursorX--;

    oldY = cursorY;

    if (cursorX < 0)
    {
      if (cursorY > 0)
      {
        setY(cursorY - 1);
        cursorX = lineLength(cursorY);
        if (cursorX > 1 && !isEndOfParagraph(cursorY))
          cursorX--;
      }
      else
      {
        setY(0);            //### ?
        cursorX = 0;
      }
    }
    if (mark)
      newMark(cursorX, cursorY, false);

    if (oldY != cursorY)
      repaintCell(oldY, 0, false);

    startBlink();

    repaintCell(cursorY, 0, false);
  }
  curXPos = 0;
  makeVisible();
  if (clear_mark)
    turnMark(false);
}

/*=
  Moves the cursor one character to the right.  If \a mark is true, the text
  is marked. If \a wrap is true, the cursor moves to the beginning of the next
  line if it is placed at the end of the current line.
  \sa cursorLeft() cursorUp() cursorDown()
*/

void QEditor::cursorRight(bool mark, bool wrap)
{
  cursorRight(mark, !mark, wrap);
}

void QEditor::cursorRight(bool mark, bool clear_mark, bool wrap)
{
  int strl = lineLength(cursorY);
  int oldY = cursorY;

  if (strl > 1 && !isEndOfParagraph(cursorY))
    strl--;
  if (cursorX < strl || cursorY < (int) contents->count() - 1 && wrap)
  {
    if (mark && !hasMarkedText())
    {
      markAnchorX = cursorX;
      markAnchorY = cursorY;
    }
    stopBlink();
    cursorOn = true;
    cursorX++;
    if (cursorX > strl)
    {
      if (cursorY < (int) contents->count() - 1)
      {
        setY(cursorY + 1);
        cursorX = 0;
      }
      else
      {
        cursorX = lineLength(cursorY);
      }
    }
    if (mark)
      newMark(cursorX, cursorY, false);

    if (oldY != cursorY)
      repaintCell(oldY, 0, false);
    repaintCell(cursorY, 0, false);

    startBlink();
  }
  curXPos = 0;
  makeVisible();
  if (clear_mark)
    turnMark(false);
}

/*=
  Moves the cursor up one line.  If \a mark is true, the text
  is marked.
  \sa cursorDown() cursorLeft() cursorRight()
*/

void QEditor::cursorUp(bool mark)
{
  cursorUp(mark, !mark);
}

void QEditor::cursorUp(bool mark, bool clear_mark)
{
  if (cursorY != 0)
  {
    if (mark && !hasMarkedText())
    {
      markAnchorX = cursorX;
      markAnchorY = cursorY;
    }
    if (!curXPos)
      curXPos = mapToView(cursorX, cursorY);
    int oldY = cursorY;
    stopBlink();
    cursorOn = true;

    setY(cursorY - 1);

    cursorX = mapFromView(curXPos, cursorY);
    if (mark)
      newMark(cursorX, cursorY, false);
    repaintCell(oldY, 0, false);
    repaintCell(cursorY, 0, false);
    startBlink();
  }
  makeVisible();
  if (clear_mark)
    turnMark(false);
}

/*=
  Moves the cursor one line down.  If \a mark is true, the text
  is marked.
  \sa cursorUp() cursorLeft() cursorRight()
*/

void QEditor::cursorDown(bool mark)
{
  cursorDown(mark, !mark);
}

void QEditor::cursorDown(bool mark, bool clear_mark)
{
  int lastLin = contents->count() - 1;
  if (cursorY != lastLin)
  {
    if (mark && !hasMarkedText())
    {
      markAnchorX = cursorX;
      markAnchorY = cursorY;
    }
    if (!curXPos)
      curXPos = mapToView(cursorX, cursorY);
    int oldY = cursorY;
    stopBlink();
    cursorOn = true;

    setY(cursorY + 1);

    cursorX = mapFromView(curXPos, cursorY);
    if (mark)
      newMark(cursorX, cursorY, false);
    //qDebug("cursorDown: updateCell: %d", oldY);
    repaintCell(oldY, 0, false);
    //qDebug("cursorDown: updateCell: %d", cursorY);
    repaintCell(cursorY, 0, false);
    startBlink();
  }
  makeVisible();
  if (clear_mark)
    turnMark(false);
}

/*=
  Turns off marked text
*/
void QEditor::turnMark(bool on)
{
  if (on != markIsOn)
  {
    markIsOn = on;
    //if (echoMode() == Normal)
      emit copyAvailable(on);
    updateContents();
  }
}




/*=
  Deletes the character on the left side of the text cursor and moves
  the cursor one position to the left. If a text has been marked by
  the user (e.g. by clicking and dragging) the cursor is put at the
  beginning of the marked text and the marked text is removed.
  \sa del()
*/

void QEditor::backspace()
{
  int line;
  bool empty;

  noEmitCursorMoved++;

  if (hasMarkedText())
  {
    del();
  }
  else if (!atBeginning())
  {
    QEditorRow *r = contents->at(cursorY);
    int indent = 0;

    if (cursorX > 0 && r->s.left(cursorX).stripWhiteSpace().length() == 0)
    {
      line = cursorY;
      for (;;)
      {
        line--;
        if (line < 0)
          break;
        indent = getIndent(line, empty);
        if (empty)
          continue;
        if (cursorX > indent)
          break;
      }

      if (cursorX > indent)
        setCursorPosition(cursorY, indent, true);
      else
        setCursorPosition(cursorY, 0, true);

      del();
    }
    else
    {
      cursorLeft(false);
      del();
    }
  }

  noEmitCursorMoved--;
  makeVisible();
}


void QEditor::delAux()
{
  int markBeginX, markBeginY;
  int markEndX, markEndY;
  QRect oldContents = contentsRect();

  noEmitCursorMoved++;

  if (getMarkedRegion(&markBeginY, &markBeginX, &markEndY, &markEndX))
  {

    turnMark(false);
    textDirty = true;
    d->edited = true;

    if (markBeginY == markEndY)
    {                           //just one line

      QEditorRow *r = contents->at(markBeginY);
      ASSERT(r);
      bool recalc = r->w == maxLineWidth();

      r->s.remove(markBeginX, markEndX - markBeginX);
      r->w = textWidth(r->s);

      cursorX = markBeginX;
      cursorY = markBeginY;

      if (autoUpdate())
        repaintCell(cursorY, 0, false);
      if (recalc)
        updateCellWidth();

      r->change();

    }
    else
    {                           //multiline

      bool oldAuto = autoUpdate();
      setAutoUpdate(false);
      ASSERT(markBeginY >= 0);
      ASSERT(markEndY < (int) contents->count());

      QEditorRow *firstR, *lastR;

      firstR = contents->at(markBeginY);
      lastR = contents->at(markEndY);
      ASSERT(firstR != lastR);
      firstR->s.remove(markBeginX, firstR->s.length() - markBeginX);
      lastR->s.remove(0, markEndX);
      firstR->s.append(lastR->s);       // lastS will be removed in loop below
      firstR->newline = lastR->newline; // Don't forget this -sanders
      firstR->w = textWidth(firstR->s);

      firstR->change();
      lastR->change();

      for (int i = markBeginY + 1; i <= markEndY; i++)
        contents->remove(markBeginY + 1);

      if (contents->isEmpty())
        insertLine(QString::fromLatin1(""), -1);

      cursorX = markBeginX;
      //setY(markBeginY);
      cursorY = markBeginY;
      curXPos = 0;

      setNumRowsAndTruncate();
      updateCellWidth();

      setAutoUpdate(oldAuto);
      if (autoUpdate())
        viewport()->repaint();
    }

    markAnchorY = markDragY = cursorY;
    markAnchorX = markDragX = cursorX;

  }
  else
  {

    if (!atEnd())
    {

      textDirty = true;
      d->edited = true;

      QEditorRow *r = contents->at(cursorY);

      if (cursorX == (int) r->s.length())
      {                         // remove newline
        QEditorRow *other = contents->at(cursorY + 1);
        if (!r->newline && cursorX)
          r->s.truncate(r->s.length() - 1);

        bool needBreak = !r->s.isEmpty();
        r->s += other->s;
        r->newline = other->newline;
        contents->remove(cursorY + 1);
        if (needBreak)
          rebreakParagraph(cursorY, 1);
        else
          wrapLine(cursorY, 1);

        colorize(cursorY);
        repaintCell(cursorY, 0, false);
      }
      else
      {

        bool recalc = r->w == maxLineWidth();
        r->s.remove(cursorX, 1);
        rebreakParagraph(cursorY);
        if (recalc)
          updateCellWidth();

        r->change();
      }

    }
  }

  #if 0
  if (DYNAMIC_WRAP && oldContents != contentsRect())
  {
    if (oldContents.width() != contentsRect().width())
    {
      bool oldAuto = autoUpdate();
      setAutoUpdate(false);
      rebreakAll();
      setAutoUpdate(oldAuto);
    }
    if (autoUpdate())
      updateContents();
  }
  #endif

  curXPos = 0;
  noEmitCursorMoved--;
  makeVisible();
}

/*=
  Moves the text cursor to the left end of the line. If \a mark is
  true, text is marked towards the first position. If it is false and
  the cursor is moved, all marked text is unmarked.

  \sa end()
*/

void QEditor::home(bool mark)
{
  bool empty;
  int indent = getIndent(cursorY, empty);

  //if ( cursorX != 0 ) {

  if (mark && !hasMarkedText())
  {
    markAnchorX = cursorX;
    markAnchorY = cursorY;
  }
  stopBlink();

  if (cursorX > indent || cursorX == 0)
    cursorX = indent;
  else
    cursorX = 0;

  cursorOn = true;

  if (mark)
    newMark(cursorX, cursorY, false);

  repaintCell(cursorY, 0, false);

  startBlink();
  //  }

  curXPos = 0;

  if (!mark)
    turnMark(false);

  makeVisible();
}

/*=
  Moves the text cursor to the right end of the line. If mark is true
  text is marked towards the last position.  If it is false and the
  cursor is moved, all marked text is unmarked.

  \sa home()
*/

void QEditor::end(bool mark)
{
  int tlen = lineLength(cursorY);

  if (cursorX != tlen)
  {
    if (mark && !hasMarkedText())
    {
      markAnchorX = cursorX;
      markAnchorY = cursorY;
    }
    stopBlink();
    cursorX = tlen;
    cursorOn = true;

    if (mark)
      newMark(cursorX, cursorY, false);

    startBlink();
    repaintCell(cursorY, 0, false);
  }

  curXPos = 0;

  makeVisible();

  if (!mark)
    turnMark(false);
}

/*=\reimp
*/
void QEditor::mousePressEvent(QMouseEvent * e)
{
  stopAutoScroll();
  d->dnd_startpos = e->pos();

/*
  if (e->button() == RightButton)
  {
    QPopupMenu *popup = new QPopupMenu(this);
    int id[(int) IdCount];
    id[IdUndo] = popup->insertItem(tr("Undo"));
    id[IdRedo] = popup->insertItem(tr("Redo"));
    popup->insertSeparator();
#ifndef QT_NO_CLIPBOARD
    id[IdCut] = popup->insertItem(tr("Cut"));
    id[IdCopy] = popup->insertItem(tr("Copy"));
    id[IdPaste] = popup->insertItem(tr("Paste"));
#ifndef QT_NO_MIMECLIPBOARD
    id[IdPasteSpecial] = popup->insertItem(tr("Paste special..."));
#endif
#endif
    id[IdClear] = popup->insertItem(tr("Clear"));
    popup->insertSeparator();
    id[IdSelectAll] = popup->insertItem(tr("Select All"));
    popup->setItemEnabled(id[IdUndo], !this->d->undoList.isEmpty());
    popup->setItemEnabled(id[IdRedo], !this->d->redoList.isEmpty());
#ifndef QT_NO_CLIPBOARD
    popup->setItemEnabled(id[IdCut], !isReadOnly() && hasMarkedText());
    popup->setItemEnabled(id[IdCopy], hasMarkedText());
    popup->setItemEnabled(id[IdPaste],
                          !isReadOnly()
                          && (bool) QApplication::clipboard()->text().
                          length());
#ifndef QT_NO_MIMECLIPBOARD
    // Any non-plain types?
    QMimeSource *ms = QApplication::clipboard()->data();
    bool ps = false;
    if (ms)
    {
      if (!isReadOnly())
      {
        const char *fmt;
        for (int i = 0; !ps && (fmt = ms->format(i)); i++)
        {
          ps = qstrnicmp(fmt, "text/", 5) == 0
            && qstrnicmp(fmt + 5, "plain", 5) != 0;
        }
      }
    }
    popup->setItemEnabled(id[IdPasteSpecial], ps);
#endif
#endif
    popup->setItemEnabled(id[IdClear],
                          !isReadOnly() && (bool) text().length());
    int allSelected = markIsOn && markAnchorX == 0 && markAnchorY == 0 &&
      markDragY == numLines() - 1 && markDragX == lineLength(markDragY);
    popup->setItemEnabled(id[IdSelectAll],
                          (bool) text().length() && !allSelected);

    int r = popup->exec(e->globalPos());
    delete popup;

    if (r == id[IdUndo])
      undo();
    else if (r == id[IdRedo])
      redo();
#ifndef QT_NO_CLIPBOARD
    else if (r == id[IdCut])
      cut();
    else if (r == id[IdCopy])
      copy();
    else if (r == id[IdPaste])
      paste();
# ifndef QT_NO_MIMECLIPBOARD
    else if (r == id[IdPasteSpecial])
      pasteSpecial(QCursor::pos());
# endif
#endif
    else if (r == id[IdClear])
      clear();
    else if (r == id[IdSelectAll])
      selectAll();
    return;
  }
*/
  if (e->button() != MidButton && e->button() != LeftButton)
    return;

  int newX, newY;
  pixelPosToCursorPos(e->pos(), &newX, &newY);

  if (e->state() & ShiftButton)
  {
    wordMark = false;
    dragMarking = true;
    setCursorPosition(newY, newX, true);
    return;
  }

#ifndef QT_NO_DRAGANDDROP
  if (inMark(newX, newY)        // Click on highlighted text
      //&& echoMode() == Normal   // No DnD of passwords, etc.
      && e->pos().y() < totalHeight()   // Click past the end is not dragging
    )
  {
    // The user might be trying to drag
    d->dnd_primed = true;
    d->dnd_timer->start(QApplication::startDragTime(), false);
  }
  else
#endif
  {
    wordMark = false;
    dragMarking = true;
    setCursorPixelPosition(e->pos());
  }
}

void QEditor::pixelPosToCursorPos(QPoint p, int *x, int *y) const
{
  *y = findRow(p.y());
  if (*y < 0)
  {
    //qDebug("y = %d", p.y());
    if (p.y() <= lineWidth())
      *y = topCell();
    else
    {
      *y = lastRowVisible();
      p.setX(cellWidth());
    }
  }

  *y = QMIN((int) contents->count() - 1, *y);
  QFontMetrics fm(font());
  *x = xPosToCursorPos(stringShown(*y), fm,
                       p.x() - d->lr_marg + xOffset(),
                       cellWidth() - 2 * d->lr_marg - d->marg_extra);
  QEditorRow *r = contents->at(*y);
  if (r && !r->newline && ((unsigned int) *x == r->s.length()) && (*x > 0))
    -- * x;
}

void QEditor::setCursorPixelPosition(QPoint p, bool clear_mark)
{
  int newY;
  pixelPosToCursorPos(p, &cursorX, &newY);
  curXPos = 0;
  if (clear_mark)
  {
    markAnchorX = cursorX;
    markAnchorY = newY;
    bool markWasOn = markIsOn;
    turnMark(false);
    if (markWasOn)
    {
      setY(newY);
      updateContents();
      d->isHandlingEvent = false;

      emitCursorMoved();
      return;
    }
  }
  if (cursorY != newY)
  {
    int oldY = cursorY;
    setY(newY);
    repaintCell(oldY, 0, false);
  }
  repaintCell(cursorY, 0, false);        // ###

  emitCursorMoved();
}

void QEditor::startAutoScroll()
{
  if (!dragScrolling)
  {
    d->scrollTime = initialScrollTime;
    d->scrollAccel = initialScrollAccel;
    d->scrollTimer->start(d->scrollTime, false);
    dragScrolling = true;
  }
}

void QEditor::stopAutoScroll()
{
  if (dragScrolling)
  {
    d->scrollTimer->stop();
    dragScrolling = false;
  }
}

/*=\reimp
*/
void QEditor::mouseMoveEvent(QMouseEvent * e)
{
#ifndef QT_NO_DRAGANDDROP
  d->dnd_timer->stop();
  if (d->dnd_primed &&
      (d->dnd_startpos - e->pos()).manhattanLength() >
      QApplication::startDragDistance())
  {
    doDrag();
    return;
  }
#endif
  if (!dragMarking)
    return;
  if (rect().contains(e->pos()))
  {
    stopAutoScroll();
  }
  else if (!dragScrolling)
  {
    startAutoScroll();
  }

  int newX, newY;
  pixelPosToCursorPos(e->pos(), &newX, &newY);

  if (wordMark)
  {
    extendSelectionWord(newX, newY);
  }

  if (markDragX != newX || markDragY != newY)
  {
    int oldY = markDragY;
    newMark(newX, newY, false);
    for (int i = QMIN(oldY, newY); i <= QMAX(oldY, newY); i++)
      repaintCell(i, 0, false);
  }

  emitCursorMoved();
}

void QEditor::extendSelectionWord(int &newX, int &newY)
{
  QString s = stringShown(newY);
  int lim = s.length();
  if (newX >= 0 && newX < lim)
  {
    int i = newX;
    int startclass = charClass(s.at(i));
    if (markAnchorY < markDragY ||
        (markAnchorY == markDragY && markAnchorX < markDragX))
    {
      // going right
      while (i < lim && charClass(s.at(i)) == startclass)
        i++;
    }
    else
    {
      // going left
      while (i >= 0 && charClass(s.at(i)) == startclass)
        i--;
      i++;
    }
    newX = i;
  }
}




/*=\reimp
*/
void QEditor::mouseReleaseEvent(QMouseEvent * e)
{
  stopAutoScroll();
#ifndef QT_NO_DRAGANDDROP
  if (d->dnd_timer->isActive())
  {
    d->dnd_timer->stop();
    d->dnd_primed = false;
    setCursorPixelPosition(e->pos());
  }
#endif
  wordMark = false;
  dragMarking = false;
  textDirty = false;
  d->isHandlingEvent = true;
  if (markAnchorY == markDragY && markAnchorX == markDragX)
    turnMark(false);

#ifndef QT_NO_CLIPBOARD
#if defined(_WS_X11_)
  else //if (echoMode() == Normal)
    copy();
#endif

  if (e->button() == MidButton && !readOnly)
  {
#if defined(_WS_X11_)
    paste();                    // Will repaint the cursor line.
#else
  if ( style().styleHint(QStyle::SH_GUIStyle) == Qt::MotifStyle )
    paste();
#endif
  }
#endif

  d->isHandlingEvent = false;

  if (!readOnly && textDirty)
    emit textChanged();

  emitCursorMoved();
}


/*=\reimp
*/
void QEditor::mouseDoubleClickEvent(QMouseEvent * m)
{
  if (m->button() == LeftButton)
  {
    if (m->state() & ShiftButton)
    {
      int newX = cursorX;
      int newY = cursorY;
      extendSelectionWord(newX, newY);
      newMark(newX, newY, false);
    }
    else
    {
      markWord(cursorX, cursorY);
    }
    dragMarking = true;
    wordMark = true;
    repaintCell(cursorY, 0, false);

  }
}

#ifndef QT_NO_DRAGANDDROP

/*=
  \reimp
*/

void QEditor::dragEnterEvent(QDragEnterEvent *)
{
  cursorOn = true;
  repaintCell(cursorY, 0, false);
}

/*=\reimp
*/
void QEditor::dragMoveEvent(QDragMoveEvent * event)
{
  if (readOnly)
    return;
  event->accept(QTextDrag::canDecode(event));
  d->dnd_forcecursor = true;
  setCursorPixelPosition(event->pos(), false);
  d->dnd_forcecursor = false;
  QRect inside_margin(scroll_margin, scroll_margin,
                      width() - scroll_margin * 2,
                      height() - scroll_margin * 2);
  if (!inside_margin.contains(event->pos()))
  {
    startAutoScroll();
  }
  if (event->source() == this && event->action() == QDropEvent::Move)
    event->acceptAction();
}

/*=\reimp
*/
void QEditor::dragLeaveEvent(QDragLeaveEvent *)
{
  if (cursorOn)
  {
    cursorOn = false;
    repaintCell(cursorY, 0, false);
  }
  stopAutoScroll();
}

/*=\reimp
*/
void QEditor::dropEvent(QDropEvent * event)
{
  if (readOnly)
    return;
  QString text;
  QCString fmt = pickSpecial(event, false, event->pos());
  if (QTextDrag::decode(event, text, fmt))
  {
    int i = -1;
    while ((i = text.find('\r')) != -1)
      text.replace(i, 1, "");
    if (event->source() == this && event->action() == QDropEvent::Move)
    {
      event->acceptAction();
      // Careful not to tread on my own feet
      int newX, newY;
      pixelPosToCursorPos(event->pos(), &newX, &newY);
      if (afterMark(newX, newY))
      {
        // The tricky case
        int x1, y1, x2, y2;
        getMarkedRegion(&y1, &x1, &y2, &x2);
        if (newY == y2)
        {
          newY = y1;
          newX = x1 + newX - x2;
        }
        else
        {
          newY -= y2 - y1;
        }
        addUndoCmd(new QBeginCommand);
        del();
        setCursorPosition(newY, newX);
        insert(text, true);
        addUndoCmd(new QEndCommand);
      }
      else if (beforeMark(newX, newY))
      {
        // Easy
        addUndoCmd(new QBeginCommand);
        del();
        setCursorPosition(newY, newX);
        insert(text, true);
        addUndoCmd(new QEndCommand);
      }
      else
      {
        // Do nothing.
      }
    }
    else
    {
      setCursorPixelPosition(event->pos());
      insert(text, true);
    }
    updateContents();
    emit textChanged();
  }
}

#endif // QT_NO_DRAGANDDROP


/*=
  Returns true if line \a line is invisible or partially invisible.
*/

bool QEditor::partiallyInvisible(int line)
{
  int y;
  if (!rowYPos(line, &y))
    return true;
  if (y < 0)
    return true;
  else if ((y + cellHeight()) >= viewHeight())
    return true;

  return false;
}

/*=
  Scrolls such that the cursor is visible
*/

void QEditor::makeVisible()
{
  if (autoUpdate())
  {
    if (partiallyInvisible(cursorY))
    {
      if (cursorY >= lastRowVisible())
        setBottomCell(cursorY);
      else
        setTopCell(cursorY);
    }
    int xPos = mapToView(cursorX, cursorY);
    if (xPos < xOffset())
    {
      int of = xPos - 10;         //###
      setXOffset(of);
    }
    else if (xPos > xOffset() + viewWidth())
    {
      int of = xPos - viewWidth() + 10;   //###
      setXOffset(of);
    }
  }

  emitCursorMoved(); // En attendant mieux
}

/*=
  Computes the character position in line \a line which corresponds
  to pixel \a xPos
*/

int QEditor::mapFromView(int xPos, int line)
{
  QString s = stringShown(line);
  if (!s)
    return 0;
  QFontMetrics fm(font());
  int index = xPosToCursorPos(s, fm,
                              xPos - d->lr_marg,
                              cellWidth() - 2 * d->lr_marg - d->marg_extra);
  QEditorRow *r = contents->at(line);
  if (r && !r->newline && ((unsigned int) index == r->s.length())
      && (index > 0))
    --index;
  return index;
}

/*=
  Computes the pixel position in line \a line which corresponds to
  character position \a xIndex
*/

int QEditor::mapToView(int xIndex, int line) const
{
  QString s = stringShown(line);
  xIndex = QMIN((int) s.length(), xIndex);
  QFontMetrics fm(font());
  //int wcell = cellWidth() - 2 * d->lr_marg;// - d->marg_extra;
  //int wrow = contents->at( line )->w;
  int w = textWidthWithTabs(fm, s, 0, xIndex); // [BM]  - 1;
  /*
     if ( d->align == Qt::AlignCenter || d->align == Qt::AlignHCenter )
     w += (wcell - wrow) / 2;
     else if ( d->align == Qt::AlignRight )
     w += wcell - wrow;
   */
  return d->lr_marg + w;
}

/*=
  Traverses the list and finds an item with the maximum width, and
  updates the internal list box structures accordingly.
*/

void QEditor::updateCellWidth()
{
  QEditorRow *r = contents->first();

  int maxW = 0;
  int w;

  while (r)
  {
    w = r->w;
    if (w > maxW)
      maxW = w;
    r = contents->next();
  }

  setWidth(maxW);

  /*
     switch ( d->echomode ) {
     case Normal:
     while ( r ) {
     w = r->w;
     if ( w > maxW )
     maxW = w;
     r = contents->next();
     }
     break;
     case Password: {
     uint l = 0;
     while ( r ) {
     l = QMAX(l,  r->s.length() );
     r = contents->next();
     }
     QString t;
     t.fill(QChar('*'), l);
     maxW = textWidth(t);
     }
     break;
     case NoEcho:
     maxW = textWidth(QString::fromLatin1(""));
     }
     setWidth( maxW );
   */
}


#ifndef QT_NO_CLIPBOARD

/*=
  Copies text in MIME subtype \a subtype from the clipboard onto the current
  cursor position.
  Any marked text is first deleted.
*/
void QEditor::pasteSubType(const QCString & subtype)
{
  QCString st = subtype;
  addUndoCmd(new QBeginCommand);

  if (hasMarkedText())
    del();
  QString t = QApplication::clipboard()->text(st);
  if (!t.isEmpty())
  {
    if (hasMarkedText())
      turnMark(false);

#if defined(_OS_WIN32_)
    // Need to convert CRLF to NL
    QRegExp crlf("\\r\\n");
    t.replace(crlf, "\n");
#endif

    QString sp;
    sp.fill(' ', tabWidth);
    t.replace("\t", sp);

    for (int i = 0; (uint) i < t.length(); i++)
    {
      if ((t[i] < ' ' || t[i].isSpace()) && t[i] != '\n')
        t[i] = ' ';
    }
    insertAt(t, cursorY, cursorX);
    turnMark(false);
    curXPos = 0;
    makeVisible();
  }
  if (textDirty && !d->isHandlingEvent)
    emit textChanged();

  addUndoCmd(new QEndCommand);
}

/*=
  Copies plain text from the clipboard onto the current cursor position.
  Any marked text is first deleted.
*/
void QEditor::paste()
{
  pasteSubType("plain");
}

#ifndef QT_NO_MIMECLIPBOARD
/*=
  Prompts the user for a type from a list of text types available,
  Then copies text from the clipboard onto the current cursor position.
  Any marked text is first deleted.
*/
void QEditor::pasteSpecial(const QPoint & pt)
{
  QCString st = pickSpecial(QApplication::clipboard()->data(), true, pt);
  if (!st.isEmpty())
    pasteSubType(st);
}
#endif
#ifndef QT_NO_MIME
QCString QEditor::pickSpecial(QMimeSource * ms, bool always_ask,
                              const QPoint & pt)
{
  if (ms)
  {
    QPopupMenu popup(this);
    QString fmt;
    int n = 0;
    QDict < void >done;
    for (int i = 0; !(fmt = ms->format(i)).isNull(); i++)
    {
      int semi = fmt.find(";");
      if (semi >= 0)
        fmt = fmt.left(semi);
      if (fmt.left(5) == "text/")
      {
        fmt = fmt.mid(5);
        if (!done.find(fmt))
        {
          done.insert(fmt, (void *) 1);
          popup.insertItem(fmt, i);
          n++;
        }
      }
    }
    if (n)
    {
      int i = n == 1 && !always_ask ? popup.idAt(0) : popup.exec(pt);
      if (i >= 0)
        return popup.text(i).latin1();
    }
  }
  return QCString();
}
#endif // QT_NO_MIME
#endif // QT_NO_CLIPBOARD


/*=
  Removes all text.
*/

void QEditor::clear()
{
  addUndoCmd(new QDelTextCmd(0, text()));
  setEdited(true);
  contents->clear();
  cursorX = cursorY = 0;
  int w = textWidth(QString::fromLatin1(""));
  contents->append(new QEditorRow(QString::fromLatin1(""), w));
  setNumRowsAndTruncate();
  setWidth(w);
  dummy = true;
  turnMark(false);
  if (autoUpdate())
    updateContents();
  if (!d->isHandlingEvent)      //# && not already empty
    emit textChanged();
  emitCursorMoved();
  updateContents();
}


/*=\reimp
*/

void QEditor::setFont(const QFont & font)
{
  QWidget::setFont(font);
  d->clearChartable();
  QFontMetrics fm(font);
  setCellHeight(fm.lineSpacing());
  for (QEditorRow * r = contents->first(); r; r = contents->next())
    r->w = textWidth(r->s);
  rebreakAll();
  updateCellWidth();
  viewport()->repaint();
}

/*=
  Sets a new marked text limit, does not repaint the widget.
*/

void QEditor::newMark(int posx, int posy, bool /*copy */ )
{
  if (markIsOn && markDragX == posx && markDragY == posy &&
      cursorX == posx && cursorY == posy)
    return;

  markDragX = posx;
  markDragY = posy;
  cursorX = posx;
  setY(posy);
  turnMark(markDragX != markAnchorX || markDragY != markAnchorY);
  emitCursorMoved();
}

bool QEditor::beforeMark(int posx, int posy) const
{
  int x1, y1, x2, y2;
  if (!getMarkedRegion(&y1, &x1, &y2, &x2))
    return false;
  return
    (y1 > posy || y1 == posy && x1 > posx)
    && (y2 > posy || y2 == posy && x2 > posx);
}

bool QEditor::afterMark(int posx, int posy) const
{
  int x1, y1, x2, y2;
  if (!getMarkedRegion(&y1, &x1, &y2, &x2))
    return false;
  return
    (y1 < posy || y1 == posy && x1 < posx)
    && (y2 < posy || y2 == posy && x2 < posx);
}

bool QEditor::inMark(int posx, int posy) const
{
  int x1, y1, x2, y2;
  if (!getMarkedRegion(&y1, &x1, &y2, &x2))
    return false;
  return
    (y1 < posy || y1 == posy && x1 <= posx)
    && (y2 > posy || y2 == posy && x2 >= posx);
}

/*=
  Marks the word at character position \a posx, \a posy.
 */
void QEditor::markWord(int posx, int posy)
{
  QString & s = contents->at(posy)->s;

  int i = posx - 1;
  while (i >= 0 && s[i].isPrint() && s[i].isLetterOrNumber())
    i--;
  i++;
  markAnchorY = posy;
  markAnchorX = i;

  while (s[i].isPrint() && s[i].isLetterOrNumber())
    i++;
  markDragX = i;
  markDragY = posy;
  turnMark(markDragX != markAnchorX || markDragY != markAnchorY);

#ifndef QT_NO_CLIPBOARD
#if defined(_WS_X11_)
  //if (echoMode() == Normal)
    copy();
#endif
#endif
}

/*=
  This may become a protected virtual member in a future Qt.
  This implementation is an example of a useful classification
  that aids selection of common units like filenames and URLs.
*/
int QEditor::charClass(QChar ch)
{
  if (ch.isLetter() || ch == '?' || ch == '_' || ch == '$')
    return 1;
  else if (!ch.isPrint() || ch.isSpace())
    return 2;
  else
    return 3;
}

#ifndef QT_NO_CLIPBOARD
/*=
  Copies the marked text to the clipboard.  Will copy only
  if echoMode() is Normal.
*/

void QEditor::copy() const
{
  QString t = markedText();
  if (!t.isEmpty()) // && echoMode() == Normal)
  {
#if defined(_WS_X11_)
    disconnect(QApplication::clipboard(), SIGNAL(dataChanged()), this, 0);
#endif
#if defined(_OS_WIN32_)
    // Need to convert NL to CRLF
    QRegExp nl("\\n");
    t.replace(nl, "\r\n");
#endif
    QApplication::clipboard()->setText(t);
#if defined(_WS_X11_)
    connect(QApplication::clipboard(), SIGNAL(dataChanged()),
            this, SLOT(clipboardChanged()));
#endif
  }
}

/*= \obsolete

  Backward compatibility.
*/
void QEditor::copyText() const
{
  copy();
}

/*=
  Copies the selected text to the clipboard and deletes the selected text.
*/

void QEditor::cut()
{
  if (hasMarkedText())
  {
    //if (echoMode() == Normal)
      copy();
    del();
    if (textDirty && !d->isHandlingEvent)
      emit textChanged();
  }
}

#endif

/*=
  This private slot is activated when this line edit owns the clipboard and
  some other widget/application takes over the clipboard. (X11 only)
*/

void QEditor::clipboardChanged()
{
#if defined(_WS_X11_)
  disconnect(QApplication::clipboard(), SIGNAL(dataChanged()),
             this, SLOT(clipboardChanged()));
  turnMark(false);
  updateContents();
#endif
}


/*=
   Sets maxLineWidth() and maybe cellWidth() to \a w without updating the entire widget.
 */


void QEditor::setWidth(int w)
{
  d->maxLineWidth = w;

  bool u = autoUpdate();
  setAutoUpdate(false);
  setCellWidth(QMAX(d->maxLineWidth, contentsRect().width()));
  setAutoUpdate(u);

  // [BM] if ( autoUpdate() && d->align != AlignLeft )
  //if (autoUpdate())
  //  update();
}


/*=
  Sets the cursor position to character number \a col in line number \a line.
  The parameters are adjusted to lie within the legal range.

  If \a mark is false, the selection is cleared. otherwise it is extended

  \sa cursorPosition()
*/

void QEditor::setCursorPosition(int line, int col, bool mark)
{
  stopBlink();

  if (mark && !hasMarkedText())
  {
    markAnchorX = cursorX;
    markAnchorY = cursorY;
  }
  int oldY = cursorY;
  setY(line);
  cursorX = QMAX(QMIN(col, lineLength(cursorY)), 0);
  curXPos = 0;

  if (mark)
  {
    newMark(cursorX, cursorY, false);
    for (int i = QMIN(oldY, cursorY); i <= QMAX(oldY, cursorY); i++)
      repaintCell(i, 0, false);
  }
  else
  {
    repaintCell(oldY, 0, false);
    turnMark(false);
  }

  makeVisible();
  startBlink();
}



/*= \obsolete

  Use getCursorPosition() instead.
*/

void QEditor::cursorPosition(int *line, int *col) const
{
  getCursorPosition(line, col);
}


/*=
  Returns the current line and character
  position within that line, in the variables pointed to
  by \a line and \a col respectively.

  \sa setCursorPosition()
*/

void QEditor::getCursorPosition(int *line, int *col) const
{
  if (line)
    *line = cursorY;
  if (col)
    *col = cursorX;
}


/*=
  Returns true if the view updates itself automatically whenever it
  is changed in some way.

  \sa setAutoUpdate()
*/

bool QEditor::autoUpdate() const
{
  //return QtTableView::autoUpdate();
  return isUpdatesEnabled();
}


/*=
  Sets the auto-update option of multi-line editor to \e enable.

  If \e enable is true (this is the default) then the editor updates
  itself automatically whenever it has changed in some way (generally,
  when text has been inserted or deleted).

  If \e enable is false, the view does NOT repaint itself, or update
  its internal state variables itself when it is changed.  This can be
  useful to avoid flicker during large changes, and is singularly
  useless otherwise: Disable auto-update, do the changes, re-enable
  auto-update, and call repaint().

  \warning Do not leave the view in this state for a long time
  (i.e. between events ). If, for example, the user interacts with the
  view when auto-update is off, strange things can happen.

  Setting auto-update to true does not repaint the view, you must call
  repaint() to do this (preferable repaint(false) to avoid flicker).

  \sa autoUpdate() repaint()
*/

void QEditor::setAutoUpdate(bool enable)
{
  //QtTableView::setAutoUpdate(enable);
  if (enable != isUpdatesEnabled())
    setUpdatesEnabled(enable);
}

/*=
  Sets the fixed height of the QEditor so that \e lines text lines
  are visible given the current font.

  \sa setMaxLines(), setFixedHeight()
 */
void QEditor::setFixedVisibleLines(int lines)
{
  int ls = fontMetrics().lineSpacing();
  setFixedHeight(frameWidth() * 2 + ls * lines);
  return;
}



/*=
  Returns the top center point where the cursor is drawn
*/

QPoint QEditor::cursorPoint() const
{
  QPoint cp(0, 0);

  QFontMetrics fm(font());
  int col, row;
  col = row = 0;
  cursorPosition(&row, &col);
  QString line = textLine(row);
  ASSERT(line);
  cp.setX(d->lr_marg + textWidthWithTabs(fm, line, 0, col) - 1);
  cp.setY((row * cellHeight()) + viewRect().y());
  return cp;
}


/*= \reimp
*/
QSizePolicy QEditor::sizePolicy() const
{
  //### removeme 3.0
  return QWidget::sizePolicy();
}


/*=\reimp
*/
QSize QEditor::sizeHint() const
{
  constPolish();
  int expected_lines;
  if (d->maxlines >= 0 && d->maxlines <= 6)
  {
    expected_lines = d->maxlines;
  }
  else
  {
    expected_lines = 6;
  }
  QFontMetrics fm(font());
  int h =
    fm.lineSpacing() * (expected_lines - 1) + fm.height() + frameWidth() * 2;
  int w = fm.width('x') * 35;

  int maxh = maximumSize().height();
  if (maxh < QWIDGETSIZE_MAX)
    h = maxh;

  return QSize(w, h).expandedTo(QApplication::globalStrut());
}


/*=
  Returns a size sufficient for one character, and scroll bars.
*/

QSize QEditor::minimumSizeHint() const
{
  constPolish();
  QFontMetrics fm(font());
  int h = fm.lineSpacing() + frameWidth() * 2;
  int w = fm.maxWidth();
  h += frameWidth();
  w += frameWidth();
  //if (testTableFlags(Tbl_hScrollBar | Tbl_autoHScrollBar))
  if (verticalScrollBar())
    w += verticalScrollBar()->sizeHint().width();
  //if (testTableFlags(Tbl_vScrollBar | Tbl_autoVScrollBar))
  if (horizontalScrollBar())
    h += horizontalScrollBar()->sizeHint().height();
  return QSize(w, h);
}



/*=\reimp
*/

void QEditor::resizeEvent(QResizeEvent * e)
{
  //QtTableView::resizeEvent(e);
  QGridView::resizeEvent(e);
  setWidth(d->maxLineWidth);
}


/*=
  Sets the alignment. Possible values are \c AlignLeft, \c Align(H)Center
  and \c AlignRight.

  \sa alignment(), Qt::AlignmentFlags
*/
/*
void QEditor::setAlignment( int flags )
{
    if ( d->align != flags ) {
      d->align = flags;
      update();
    }
}
*/
/*=
  Returns the alignment.

  \sa setAlignment(), Qt::AlignmentFlags.
*/
/*
int QEditor::alignment() const
{
    return d->align;
}
*/


/*=
  Not supported at this time.
*/
void QEditor::setValidator(const QValidator * v)
{
  d->val = v;
  // #### validate text now
}

/*=
  Not supported at this time.
*/
const QValidator *QEditor::validator() const
{
  return d->val;
}

/*=  Sets the edited flag of this line edit to \a on.  The edited flag
is never read by QEditor, but is changed to true whenever the user
changes its contents.

This is useful e.g. for things that need to provide a default value,
but cannot find the default at once.  Just open the widget without the
best default and when the default is known, check the edited() return
value and set the line edit's contents if the user has not started
editing the line edit.  Another example is to detect whether the
contents need saving.

\sa edited()
*/
void QEditor::setEdited(bool e)
{
  d->edited = e;
}

/*=  Returns the edited flag of the line edit.  If this returns false,
the contents has not been changed since the construction of the
QEditor (or the last call to setEdited( false ), if any).  If
it returns true, the contents have been edited, or setEdited( true )
has been called.

\sa setEdited()
*/
bool QEditor::edited() const
{
  return d->edited;
}

/*= \enum QEditor::EchoMode

  This enum type describes the ways in which QLineEdit can display its
  contents.  The currently defined values are: <ul>

  <li> \c Normal - display characters as they are entered.  This is
  the default.

  <li> \c NoEcho - do not display anything.

  <li> \c Password - display asterisks instead of the characters
  actually entered.

  </ul>

  \sa setEchoMode() echoMode() QLineEdit::EchoMode
*/


/*=
  Sets the echo mode to \a em.  The default is \c Normal.

  The display is updated according.

  \sa setEchoMode()
*/
/*
void QEditor::setEchoMode(EchoMode em)
{
  if (d->echomode != em)
  {
    d->echomode = em;
    updateCellWidth();
    update();
  }
}
*/
/*=
  Returns the currently set echo mode.

  \sa setEchoMode()
*/
/*
QEditor::EchoMode QEditor::echoMode() const
{
  return d->echomode;
}
*/

/*=
  Returns the string shown at line \a row, including
  processing of the echoMode().
*/

QString QEditor::stringShown(int row) const
{
  QString *s = getString(row);
  if (!s)
    return QString::null;

  if (!*s)
    return QString::fromLatin1("");

  return *s;
  /*
  switch (d->echomode)
  {
    case Normal:
    case Password:
      {
        QString r;
        r.fill(QChar('*'), (int) s->length());
        if (!r)
          r = QString::fromLatin1("");
        return r;
      }
    case NoEcho:
      return QString::fromLatin1("");
  }
  return QString::fromLatin1("");
  */
}

/*=
  Sets the maximum text length  to \a m.  Use -1 for unlimited
  (the default).  Existing overlong text  will be truncated.

  \sa maxLength()
*/
void QEditor::setMaxLength(int m)
{
  d->maxlen = m;
}

/*=
  Returns the currently set text length limit, or -1 if there is
  no limit (this is the default).

  \sa setMaxLength()
*/
int QEditor::maxLength() const
{
  return d->maxlen;
}


/*=
  Returns the length of the current text.

  \sa setMaxLength()
 */
int QEditor::length() const
{
  int l = 0;
  for (QEditorRow * r = contents->first(); r; r = contents->next())
  {
    l += r->s.length();
    if (r->newline)
      ++l;
  }
  return l - 1;
}


/*=
  Sets the maximum length of lines to \a m.  Use -1 for unlimited
  (the default).  Existing long lines will be truncated.

  \sa maxLineLength()
*/
void QEditor::setMaxLineLength(int m)
{
  bool trunc = d->maxlinelen < 0 || m < d->maxlinelen;
  d->maxlinelen = m;
  if (trunc)
  {
    QEditorRow *r = contents->first();
    while (r)
    {
      r->s.truncate(m);
      r->change();
      r = contents->next();
    }
    if (cursorX > m)
      cursorX = m;
    if (markAnchorX > m)
      markAnchorX = m;
    if (markDragX > m)
      markDragX = m;
    updateContents();
    updateCellWidth();
  }
}

/*=
  Returns the currently set line length limit, or -1 if there is
  no limit (this is the default).

  \sa setMaxLineLength()
*/
int QEditor::maxLineLength() const
{
  return d->maxlinelen;
}

/*=
  Sets the maximum number of lines to \a m.  Use -1 for unlimited
  (the default).  Existing excess lines will be deleted.

  Note that excess lines are deleted from the \e bottom of the
  lines. If you want teletype behaviour with lines disappearing
  from the \e top as the limit is exceed, you probably just want
  to use removeLine(0) prior to adding an excess line.

  \sa maxLines(), numLines()
*/
void QEditor::setMaxLines(int m)
{
  if (m == 0)                   // bad value
    m = -1;
  d->maxlines = m;
  if (d->maxlines >= 0 && d->maxlines <= 6)
  {
    setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
  }
  else
  {
    setSizePolicy(QSizePolicy
                  (QSizePolicy::Expanding, QSizePolicy::Expanding));
  }
  if (setNumRowsAndTruncate())
  {
    updateCellWidth();
    updateContents();
  }
}

/*=
  Returns the currently set line limit, or -1 if there is
  no limit (the default).

  \sa setMaxLines()
*/
int QEditor::maxLines() const
{
  return d->maxlines;
}

/*=
  Sets the horizontal margin.

  \sa hMargin()
*/
void QEditor::setHMargin(int m)
{
  if (m != d->lr_marg)
  {
    d->lr_marg = m;
    updateCellWidth();
    if (autoUpdate())
      updateContents();
  }
}

/*=
  Returns the horizontal margin current set.  The default is 3.

  \sa setHMargin()
*/
int QEditor::hMargin() const
{
  return d->lr_marg;
}

/*=
  Marks the text starting at \a row_from, \a col_from and ending
  at row_to, col_to.
*/
void QEditor::setMarkedRegion(int row_from, int col_from, int row_to, int col_to)
{
  setCursorPosition(row_from, col_from, false);
  setCursorPosition(row_to, col_to, true);
}


/*=
  Moves the cursor one word to the right.  If \a mark is true, the text
  is marked.
  \sa cursorWordBackward()
*/

static inline bool isLimitChar(const QChar c)
{
  return (c.isSpace() || c.isPunct());
}

void QEditor::cursorWordForward(bool mark)
{
  int x = cursorX;
  int y = cursorY;

  if (x == lineLength(y) || isLimitChar(textLine(y).at(x)))
  {
    while (x < lineLength(y) && isLimitChar(textLine(y).at(x)))
      ++x;
    if (x == lineLength(y))
    {
      if (y < (int) contents->count() - 1)
      {
        ++y;
        x = 0;
        while (x < lineLength(y) && isLimitChar(textLine(y).at(x)))
          ++x;
      }
    }
  }
  else
  {
    while (x < lineLength(y) && !isLimitChar(textLine(y).at(x)))
      ++x;
    int xspace = x;
    while (xspace < lineLength(y) && isLimitChar(textLine(y).at(xspace)))
      ++xspace;
    if (xspace < lineLength(y))
      x = xspace;
  }
  cursorOn = true;
  int oldY = cursorY;
  setCursorPosition(y, x, mark);
  if (oldY != cursorY)
    repaintCell(oldY, 0, false);
  repaintCell(cursorY, 0, false);
  startBlink();
}

/*=
  Moves the cursor one word to the left.  If \a mark is true, the text
  is marked.
  \sa cursorWordForward()
*/
void QEditor::cursorWordBackward(bool mark)
{
  int x = cursorX;
  int y = cursorY;

  while (x > 0 && isLimitChar(textLine(y).at(x - 1)))
    --x;

  if (x == 0)
  {
    if (y > 0)
    {
      --y;
      x = lineLength(y);
      while (x > 0 && isLimitChar(textLine(y).at(x - 1)))
        --x;
    }
  }
  else
  {
    while (x > 0 && !isLimitChar(textLine(y).at(x - 1)))
      --x;
  }
  cursorOn = true;
  int oldY = cursorY;
  setCursorPosition(y, x, mark);
  if (oldY != cursorY)
    repaintCell(oldY, 0, false);
  repaintCell(cursorY, 0, false);
  startBlink();
}

#define DO_BREAK {doBreak = true; if ( lastSpace > a ) { \
i = lastSpace; \
linew = lastw; \
} \
else \
i = QMAX( a, i-1 );}

void QEditor::wrapLine(int line, int removed)
{
  QEditorRow *r = contents->at(line);
  int yPos;
  (void) rowYPos(line, &yPos);
  QFontMetrics fm(font());
  int i = 0;
  QString s = r->s;
  int a = 0;
  int l = line;
  int w = 0;
  int nlines = 0;
  int lastSpace = -1;
  bool doBreak = false;
  int linew = 0;
  int lastw = 0;
  int tabDist = -1;             // lazy eval

  while (i < int (s.length()))
  {
    doBreak = false;
    if (s[i] == '\t' && true /*d->align == Qt::AlignLeft */ )
    {
      if (tabDist < 0)
        tabDist = tabStopDist(fm);
      linew = (linew / tabDist + 1) * tabDist;
    }
    else if (s[i] != '\n')
    {
      int c = (char)s[i].latin1();
      if (c > 0)
      {
        if (!d->chartable[c])
          d->chartable[c] = fm.width(s[i]);
        linew += d->chartable[c];
      }
      else
      {
        linew += fm.width(s[i]);
      }
    }

    #if 0
    if (WORD_WRAP && lastSpace >= a && s[i] != '\n')
    {
      if (DYNAMIC_WRAP)
      {
        if (linew >= contentsRect().width() - 2 * d->lr_marg - d->marg_extra)
        {
        DO_BREAK}
      }
      else if (FIXED_COLUMN_WRAP)
      {
        if (d->wrapcol >= 0 && i - a >= d->wrapcol)
        {
        DO_BREAK}
      }
      else if (FIXED_WIDTH_WRAP)
      {
        if (d->wrapcol >= 0 && linew > d->wrapcol - d->marg_extra)
        {
        DO_BREAK}
      }
    }
    #endif

    if (s[i] == '\n' || doBreak)
    {
      r->s = s.mid(a, i - a + (doBreak ? 1 : 0));
      r->w = linew /* - fm.leftBearing(r->s[0])*/ + 2 * d->lr_marg + d->marg_extra;
      r->change();
      colorize(l);

      if (r->w > w)
        w = r->w;
      if (cursorY > l)
      {
        setY(cursorY + 1);
      }
      else if (cursorY == line && cursorX >= a
               && cursorX <= i + (doBreak ? 1 : 0))
      {
        setY(l);
        cursorX -= a;
      }
      if (markAnchorY > l)
        ++markAnchorY;
      else if (markAnchorY == line && markAnchorX >= a
               && markAnchorX <= i + (doBreak ? 1 : 0))
      {
        markAnchorY = l;
        markAnchorX -= a;
      }
      a = i + 1;
      lastSpace = a;
      linew = 0;
      bool oldnewline = r->newline;
      r->newline = !doBreak;

      r = new QEditorRow(QString::null, 0, oldnewline);
      ++nlines;
      contents->insert(l + 1, r);
      ++l;
    }

    if (s[i].isSpace() || BREAK_WITHIN_WORDS)
    {
      lastSpace = i;
      lastw = linew;
    }
    if (lastSpace <= a)
      lastw = linew;

    ++i;
  }

  if (a < int (s.length()))
  {
    r->s = s.mid(a, i - a);
    r->w = linew /*- fm.leftBearing(r->s[0])*/ + 2 * d->lr_marg + d->marg_extra;
    r->change();
  }
  if (cursorY == line && cursorX >= a)
  {
    setY(l);
    cursorX -= a;
  }
  if (markAnchorY == line && markAnchorX >= a)
  {
    markAnchorY = l;
    markAnchorX -= a;
  }
  if (r->w > w)
    w = r->w;

  setWidth(QMAX(maxLineWidth(), w));
  // BM - bool oldAuto = autoUpdate();
  // BM - setAutoUpdate(false);
  (void) setNumRowsAndTruncate();
  // BM - setAutoUpdate(oldAuto);

  yPos += (nlines + 1) * cellHeight();
  int sh = (nlines - removed) * cellHeight();
  if (autoUpdate())
  {
    if (sh && yPos >= 0 && yPos < visibleHeight())
    {
      int h = visibleHeight() - yPos + 1;
      if (d->maxlines >= 0)
      {
        int maxy;
        if (rowYPos(d->maxlines - 1, &maxy))
        {
          maxy += cellHeight();
          if (maxy < visibleHeight() && maxy > yPos)
            h = maxy - yPos + 1;
        }
      }
      viewport()->scroll(0, sh, QRect(0, yPos, visibleWidth(), h));
    }
    for (int ul = 0; ul <= nlines; ++ul)
      repaintCell(line + ul, 0, false);
  }
}

void QEditor::rebreakParagraph(int line, int removed)
{
  QEditorRow *r = contents->at(line);
  if (WORD_WRAP)
  {
    QEditorRow *other = 0;
    while (line < int (contents->count()) - 1 && !r->newline)
    {
      other = contents->at(line + 1);
      if (cursorY > line)
      {
        setY(cursorY - 1);
        if (cursorY == line)
        {
          cursorX += r->s.length();
        }
      }
      if (markAnchorY > line)
      {
        --markAnchorY;
        if (markAnchorY == line)
        {
          markAnchorX += r->s.length();
        }
      }
      r->s.append(other->s);
      r->newline = other->newline;
      r->change();
      contents->remove(line + 1);
      ++removed;
    }
  }
  wrapLine(line, removed);
}

void QEditor::rebreakAll()
{
  if (!WORD_WRAP)
    return;
  d->maxLineWidth = 0;
  for (int i = 0; i < int (contents->count()); ++i)
  {
    if (contents->at(i)->newline &&
        contents->at(i)->w <
        contentsRect().width() - 2 * d->lr_marg - d->marg_extra)
    {
      setWidth(QMAX(d->maxLineWidth, contents->at(i)->w));
      continue;
    }
    rebreakParagraph(i);
    while (i < int (contents->count()) && !contents->at(i)->newline)
      ++i;
  }
}


/*= \enum QEditor::WordWrap

  This enum describes the multiline edit's word wrap mode.

  The following values are valid:
    <ul>
    <li> \c NoWrap - no word wrap at all.
    <li> \c WidgetWidth - word wrap depending on the current
     width of the editor widget
    <li> \c FixedPixelWidth - wrap according to a fix amount
     of pixels ( see wrapColumnOrWidth() )
    <li> \c FixedColumnWidth - wrap according to a fix character
     column. This is useful whenever you need formatted text that
     can also be displayed gracefully on devices with monospaced
     fonts, for example a standard VT100 terminal. In that case
     wrapColumnOrWidth() should typically be set to 80.
  </ul>

 \sa setWordWrap()
*/

/*=
  Sets the word wrap mode.

  Per default, wrapping keeps words intact. To allow breaking within
  words, set the wrap policy to \c Anywhere (see setWrapPolicy() ).

  The default wrap mode is \c NoWrap.

  \sa wordWrap(), setWrapColumnOrWidth(), setWrapPolicy()
 */

/*
void QEditor::setWordWrap(WordWrap mode)
{
  if (mode == d->wordwrap)
    return;
  d->wordwrap = mode;

  if (BREAK_WITHIN_WORDS)
  {
    d->arrow = QPixmap((const char **) arrow_xpm);
    d->marg_extra = 8;
    if (DYNAMIC_WRAP)
      clearTableFlags(Tbl_autoHScrollBar);
    else
      setTableFlags(Tbl_autoHScrollBar);
  }
  else
  {
    d->marg_extra = 0;
    setTableFlags(Tbl_autoHScrollBar);
  }
  if (!text().isEmpty())
    setText(text());
}
*/

/*=
  Returns the current word wrap mode.

  \sa setWordWrap()
 */

/*
QEditor::WordWrap QEditor::wordWrap() const
{
  return d->wordwrap;
}
*/

/*=
  Sets the wrap column or wrap width, depending on the word wrap mode.

  \sa setWordWrap()
 */

/*
void QEditor::setWrapColumnOrWidth(int value)
{
  if (value == d->wrapcol)
    return;
  d->wrapcol = value;
  if (wordWrap() != NoWrap)
    setText(text());
}
*/

/*=
  Returns the wrap column or wrap width, depending on the word wrap
  mode.

  \sa setWordWrap(), setWrapColumnOrWidth()
 */

/*
int QEditor::wrapColumnOrWidth() const
{
  return d->wrapcol;
}
*/

/*= \enum QEditor::WrapPolicy

  Defines where text can be wrapped in word wrap mode.

  The following values are valid:
  <ul>
  <li> \c AtWhiteSpace - break only after whitespace
  <li> \c Anywhere - break anywhere
   </ul>

   \sa setWrapPolicy()
*/

/*=
  Defines where text can be wrapped in word wrap mode.

   The default is \c AtWhiteSpace.

  \sa setWordWrap(), wrapPolicy()
 */

/*
void QEditor::setWrapPolicy(WrapPolicy policy)
{
  if (d->wrappolicy == policy)
    return;
  d->wrappolicy = policy;
  WordWrap m = d->wordwrap;
  if (m != NoWrap)
  {                             // trigger update
    d->wordwrap = NoWrap;
    setWordWrap(m);
  }
}
*/

/*=

  Returns the current word wrap policy.

  \sa setWrapPolicy()
 */

/*
QEditor::WrapPolicy QEditor::wrapPolicy() const
{
  return d->wrappolicy;
}
*/

/*=
  Returns wether \a row is the last row in a paragraph.

  This function is only interesting in word wrap mode, otherwise its
  return value is always true.

  \sa setWordWrap()
 */
bool QEditor::isEndOfParagraph(int row) const
{
  return contents->at(row)->newline;
}

int QEditor::positionToOffsetInternal(int row, int col) const
{
  row = QMAX(QMIN(row, numLines() - 1), 0);     // Sanity check
  col = QMAX(QMIN(col, lineLength(row)), 0);    // Sanity check
  if (row == 0)
    return QMIN(col, lineLength(0));
  else
  {
    int lastI;
    lastI = lineLength(row);
    int i, tmp = 0;

    for (i = 0; i < row; i++)
    {
      tmp += lineLength(i);
      if (contents->at(i)->newline)
        ++tmp;
    }

    tmp += QMIN(lastI, col);

    return tmp;
  }

}

// if position is <0 = returns row 0, col 0, if position >= amount of text
// returns pointer to end of text.
void QEditor::offsetToPositionInternal(int position, int *row, int *col) const
{
  if (position <= 0)
  {
    *row = 0;
    *col = 0;
    return;
  }
  else
  {
    int charsLeft = position;
    int i;

    for (i = 0; contents->at(i); ++i)
    {
      if (lineLength(i) < charsLeft)
        charsLeft -= lineLength(i);
      else
      {
        *row = i;
        *col = charsLeft;
        return;
      }
      if (contents->at(i)->newline)
        --charsLeft;
    }

    if (contents->at(i - 1) && !contents->at(i - 1)->newline)
    {
      *row = i - 1;
      *col = lineLength(i - 1);
    }
    else
    {
      *row = i;
      *col = 0;
    }
    return;
  }
}


/*=
  Processes an undo/redo command \a cmd, depending on \a undo.
 */
void QEditor::processCmd(QEditorCommand * cmd, bool undo)
{
  bool ins;

  if (cmd->type() == QEditorCommand::InsertTab || cmd->type() == QEditorCommand::DeleteTab)
  {
    QDelTabCmd *tabcmd = (QDelTabCmd *)cmd;
    ins = undo ^ (cmd->type() == QEditorCommand::InsertTab);
    setMarkedRegion(tabcmd->line1, 0, tabcmd->line2, 0);
    tab(!ins, true);
  }
  else
  {
    QDelTextCmd *delcmd = (QDelTextCmd *) cmd;
    ins = true;
    if (cmd->type() == QEditorCommand::Delete)
      ins = undo;
    else if (cmd->type() == QEditorCommand::Insert)
      ins = !undo;
    else
      return;

    if (ins)
    {
      int row, col;
      offsetToPositionInternal(delcmd->mOffset, &row, &col);
      setCursorPosition(row, col, false);
      insertAt(delcmd->mStr, row, col, false);
      offsetToPositionInternal(delcmd->mOffset + delcmd->mStr.length(), &row,
                               &col);
      setCursorPosition(row, col, false);
    }
    else
    {                             // del
      int row, col, rowEnd, colEnd;
      offsetToPositionInternal(delcmd->mOffset, &row, &col);
      offsetToPositionInternal(delcmd->mOffset + delcmd->mStr.length(), &rowEnd,
                               &colEnd);
      markAnchorY = row;
      markAnchorX = col;
      setCursorPosition(rowEnd, colEnd, false);
      markDragY = rowEnd;
      markDragX = colEnd;
      turnMark(true);
      del();
    }
  }
}

/*=
  Undoes the last text operation.
 */
void QEditor::undo()
{
  if (d->undoList.isEmpty() || isReadOnly())
    return;
  textDirty = false;
  int macroLevel = 0;
  bool before = d->undo;

  bool oldAuto = false;
  bool update = false;

  d->undo = false;

  if (d->undoList.current()->terminator())
  {
    oldAuto = autoUpdate();
    setAutoUpdate(false);
    update = true;
  }

  do
  {
    QEditorCommand *command = d->undoList.take();
    if (!command)
      break;
    processCmd(command, true);
    macroLevel += command->terminator();
    if (d->undoList.isEmpty())
      emit undoAvailable(false);
    addRedoCmd(command);
  }
  while (macroLevel != 0);
  d->undo = before;

  if (update)
  {
    setAutoUpdate(oldAuto);
    if (autoUpdate())
      updateContents();
  }

  if (textDirty)
    emit textChanged();
  textDirty = false;
}

/*=
  Redoes the last text operation.
 */
void QEditor::redo()
{
  if (d->redoList.isEmpty() || isReadOnly())
    return;
  textDirty = false;
  int macroLevel = 0;
  bool before = d->undo;

  bool oldAuto = false;
  bool update = false;

  d->undo = false;

  if (d->redoList.current()->terminator())
  {
    oldAuto = autoUpdate();
    setAutoUpdate(false);
    update = true;
  }

  do
  {
    QEditorCommand *command = d->redoList.take();
    if (!command)
      break;
    processCmd(command, false);
    macroLevel += command->terminator();
    if (d->redoList.isEmpty())
      emit redoAvailable(false);
    if (d->undoList.isEmpty())
      emit undoAvailable(true);
    d->undoList.append(command);
  }
  while (macroLevel != 0);

  d->undo = before;

  if (update)
  {
    setAutoUpdate(oldAuto);
    if (autoUpdate())
      updateContents();
  }

  if (textDirty)
    emit textChanged();
  textDirty = false;
}

/*=
  Inserts \a txt at line number \a line, after character number \a col
  in the line.
  If \a txt contains newline characters, new lines are inserted.

  The cursor position is adjusted. If the insertion position is equal to
  the cursor position, the cursor is placed after the end of the new text.

 */

void QEditor::insertAt(const QString & s, int line, int col, bool mark)
{
  if (d->undo)
  {
    d->undo = false;
    QString itxt = s;
    int offset = positionToOffsetInternal(line, col);
    if (d->maxlen >= 0 && length() + int (s.length()) > d->maxlen)
      itxt.truncate(d->maxlen - length());
    addUndoCmd(new QInsTextCmd(offset, itxt));
    insertAtAux(s, line, col, mark);    // may perform del op
    d->undo = true;
  }
  else
    insertAtAux(s, line, col, mark);    // may perform del op
}

void QEditor::deleteNextChar(int offset, int row, int col)
{
  int row2, col2;
  setCursorPosition(row, col, false);
  offsetToPositionInternal(offset + 1, &row2, &col2);
  setCursorPosition(row2, col2, true);

  QString str = markedText();
  addUndoCmd(new QDelTextCmd(offset, str));

  setCursorPosition(row, col, false);
}

/*=
  Deletes text from the current cursor position to the end of the line.
*/

void QEditor::killLine()
{
  if (d->undo)
  {
    d->undo = false;
    int curY, curX;
    cursorPosition(&curY, &curX);
    int offset = positionToOffsetInternal(curY, curX);
    QEditorRow *r = contents->at(curY);
    deselect();

    addUndoCmd(new QBeginCommand);
    if (curX == (int) r->s.length())
    {
      if (!atEnd() && r->newline)
        deleteNextChar(offset, curY, curX);
    }
    else
    {
      QString str = r->s.mid(curX, r->s.length());
      addUndoCmd(new QDelTextCmd(offset, str));
    }

    addUndoCmd(new QEndCommand);
    killLineAux();
    d->undo = true;
  }
  else
    killLineAux();
}

/*=
  Deletes the character on the right side of the text cursor. If a
  text has been marked by the user (e.g. by clicking and dragging) the
  cursor is put at the beginning of the marked text and the marked
  text is removed.  \sa backspace()
*/

void QEditor::del()
{
  if (d->undo)
  {
    d->undo = false;
    bool oldAuto = autoUpdate();
    setAutoUpdate(false);
    int markBeginX, markBeginY;
    int markEndX, markEndY;

    if (getMarkedRegion(&markBeginY, &markBeginX, &markEndY, &markEndX))
    {
      addUndoCmd(new QBeginCommand);
      int offset = positionToOffsetInternal(markBeginY, markBeginX);
      QString str = markedText();
      d->undoList.append(new QDelTextCmd(offset, str));
      addUndoCmd(new QEndCommand);
    }
    else if (!atEnd())
    {
      int crsorY, crsorX;
      cursorPosition(&crsorY, &crsorX);
      int offset = positionToOffsetInternal(crsorY, crsorX);
      QEditorRow *r = contents->at(crsorY);
      if (r)
      {
        if (crsorX != (int) r->s.length())
          deleteNextChar(offset, crsorY, crsorX);
        else if (r->newline)
          deleteNextChar(offset, crsorY, crsorX);
        // else noop
      }
    }
    setAutoUpdate(oldAuto);
    delAux();
    d->undo = true;
  }
  else
    delAux();
}

/*=
  Sets undo enabled to \a enable.

  \sa isUndoEnabled()
*/
void QEditor::setUndoEnabled(bool enable)
{
  if (d->undo == enable)
    return;
  d->undo = enable;
  if (!enable)
  {
  CLEAR_UNDO}
}


/*=
  Returns whether the multilineedit is currently undo enabled or not.

  \sa setUndoEnabled()
 */
bool QEditor::isUndoEnabled() const
{
  return d->undo;
}


/*=
  Sets the maximum number of operations that can be stored on the undo stack.

  \sa undoDepth()
 */
void QEditor::setUndoDepth(int depth)
{
  d->undodepth = depth;
}


/*=
  Returns the maximum number of operations that can be stored on the undo stack.

  \sa setUndoDepth()
 */
int QEditor::undoDepth() const
{
  return d->undodepth;
}

void QEditor::blinkTimerTimeout()
{
  cursorOn = !cursorOn;
  repaintCell(cursorY, 0, false);
}

void QEditor::scrollTimerTimeout()
{
  QPoint p = mapFromGlobal(QCursor::pos());
  if (d->scrollAccel-- <= 0 && d->scrollTime)
  {
    d->scrollAccel = initialScrollAccel;
    d->scrollTime--;
    d->scrollTimer->stop();
    d->scrollTimer->start(d->scrollTime);
  }
  int l = QMAX(1, (initialScrollTime - d->scrollTime) / 5);

  // auto scrolling is dual-use - for highlighting and DND
  int margin = d->dnd_primed ? scroll_margin : 0;
  bool mark = !d->dnd_primed;
  bool clear_mark = d->dnd_primed ? false : !mark;

  for (int i = 0; i < l; i++)
  {
    if (p.y() < margin)
    {
      cursorUp(mark, clear_mark);
    }
    else if (p.y() > height() - margin)
    {
      cursorDown(mark, clear_mark);
    }
    else if (p.x() < margin)
    {
      cursorLeft(mark, clear_mark, false);
    }
    else if (p.x() > width() - margin)
    {
      cursorRight(mark, clear_mark, false);
    }
    else
    {
      stopAutoScroll();
      break;
    }
  }
}

void QEditor::dndTimeout()
{
#ifndef QT_NO_DRAGANDDROP
  doDrag();
#endif
}

int QEditor::setNumRowsAndTruncate()
{
  int n = contents->count();
  int r = 0;
  while (d->maxlines >= 0 && n > d->maxlines)
  {
    // truncate
    contents->at(n - 2)->newline = true;
    contents->removeLast();
    if (markAnchorY == n - 1)
      markAnchorY--;
    if (markDragY == n - 1)
      markDragY--;
    if (cursorY == n - 1)
    {
      setY(cursorY - 1);
      cursorX = contents->at(cursorY)->s.length();
    }
    n--;
    r++;
  }
  setNumRows(n);
  return r;
}

/*= \reimp
*/
bool QEditor::event(QEvent * e)
{
  if (e->type() == QEvent::AccelOverride) //&& !isReadOnly())
  {
    QKeyEvent *ke = (QKeyEvent *) e;
    if (ke->state() & ControlButton)
    {
      switch (ke->key())
      {
        case Key_A:
        case Key_E:
#if defined (_WS_WIN_)
        case Key_Insert:
#endif
        case Key_X:
        case Key_V:
        case Key_C:
        case Key_Z:
        case Key_Y:
        case Key_Left:
        case Key_Right:
        case Key_Up:
        case Key_Down:
        case Key_Home:
        case Key_End:
          ke->accept();
        default:
          break;
      }
    }
    else if (!isReadOnly())
    {
      switch (ke->key())
      {
        case Key_Delete:
        case Key_Home:
        case Key_End:
        case Key_Backspace:
          ke->accept();
        default:
          break;
      }
    }
  }
  return QWidget::event(e);
}

/*= \reimp
*/

bool QEditor::focusNextPrevChild(bool)
{
  return false;
}


void QEditor::emitCursorMoved()
{
  static QString look("()[]{}");
  QChar c, cr;
  int pos, d, l, bx, by, bx2;
  QString str = contents->at(cursorY)->s;
  int len = (int)str.length();
  QChar s[len];
  bool ignore;
  int i;

  bx = -1;
  bx2 = -1;
  by = -1;
  ignore = false;

  pos = -1;

  for (i = 0; i < len; i++)
  {
    c = str[i];

    if (ignore)
    {
      if (c == '\\')
      {
        s[i] = c;
        i++;
        s[i] = str[i];
        continue;
      }

      if (c == '"')
        ignore = false;
      else
        c = ' ';
    }
    else
    {
      if (c == '"')
        ignore = true;
    }

    s[i] = c;
  }

  if (cursorX > 0)
  {
    bx2 = cursorX - 1;
    c = s[bx2];
    pos = look.find(c);
  }

  if (pos < 0 && cursorX <= len)
  {
    bx2 = cursorX;
    c = s[bx2];
    pos = look.find(c);
  }

  if (pos >= 0)
  {
    d = (pos & 1) ? -1 : 1;
    cr = look[pos + d];

    pos = bx2;
    l = 0;
    for(;;)
    {
      pos += d;
      if (pos < 0 || pos >= len)
        break;

      if (s[pos] == c)
        l++;
      else if (s[pos] == cr)
      {
        l--;
        if (l < 0)
        {
          bx = pos;
          by = cursorY;
          break;
        }
      }
    }
  }

  if (by != brotherY || bx != brotherX || bx2 != brotherX2)
  {
    int old = brotherY;

    brotherX = bx;
    brotherX2 = bx2;
    brotherY = by;

    if (old >= 0 && old != brotherY)
      repaintCell(old, 0, false);

    if (brotherY >= 0)
      repaintCell(brotherY, 0, false);
  }

  if (noEmitCursorMoved)
    return;

  if (cursorX != oldX || cursorY != oldY)
  {
    oldX = cursorX;
    oldY = cursorY;
    emit cursorMoved();
  }
}


void QEditor::startBlink()
{
  d->blinkLevel--;
  if (d->blinkLevel <= 0)
  {
    d->blinkTimer->start(QApplication::cursorFlashTime() / 2, false);
    cursorOn = true;
    repaintCell(cursorY, 0, false);
  }
}


void QEditor::stopBlink()
{
  if (d->blinkLevel <= 0)
    d->blinkTimer->stop();
  d->blinkLevel++;
}


void QEditor::startUndo()
{
  addUndoCmd(new QBeginCommand);
}

void QEditor::endUndo()
{
  addUndoCmd(new QEndCommand);
}

void QEditor::setTabSpace(int n)
{
  if (n < 2)
    n = 2;
  else if (n > 16)
    n = 16;

  if (tabWidth == n)
    return;

  tabWidth = n;

  if (autoUpdate())
    updateContents();
}


void QEditor::setShowProc(bool b)
{
  showProc = b;
  if (autoUpdate())
    updateContents();
}

void QEditor::setUseRelief(bool b)
{
  useRelief = b;
  if (autoUpdate())
    updateContents();
}

void QEditor::setShowCurrent(bool b)
{
  showCurrent = b;
  if (autoUpdate())
    updateContents();
}

void QEditor::setShowChange(bool b)
{
  if (showChange == b)
    return;

  showChange = b;
  setHMargin(b ? 6 : 2);
}

void QEditor::setUseColor(bool b)
{
  useColor = b;
  if (autoUpdate())
    updateContents();
}

void QEditor::imStartEvent( QIMEvent *e )
{
      if (readOnly)
  {
            e->ignore();
            return;
      }

      //d->removeSelectedText();
      //d->updateMicroFocusHint();
      //d->imstart = d->imend = d->imselstart = d->imselend = d->cursor;
}

void QEditor::imComposeEvent( QIMEvent *e )
{
      if (readOnly)
  {
            e->ignore();
            return;
      }

  //d->text.replace( d->imstart, d->imend - d->imstart, e->text() );
      //d->imend = d->imstart + e->text().length();
      //d->imselstart = d->imstart + e->cursorPos();
      //d->imselend = d->imselstart + e->selectionLength();
      //d->cursor = e->selectionLength() ? d->imend : d->imselend;
      //d->updateTextLayout();
      //update();
}

void QEditor::imEndEvent( QIMEvent *e )
{
      if (readOnly)
  {
            e->ignore();
            return;
      }

  //d->text.remove( d->imstart, d->imend - d->imstart );
      //d->cursor = d->imselstart = d->imselend = d->imend = d->imstart;
      //d->textDirty = TRUE;
      insert(e->text());
}

Generated by  Doxygen 1.6.0   Back to index