diff options
Diffstat (limited to 'src/attic/qtmultilineedit.cpp')
-rw-r--r-- | src/attic/qtmultilineedit.cpp | 4236 |
1 files changed, 4236 insertions, 0 deletions
diff --git a/src/attic/qtmultilineedit.cpp b/src/attic/qtmultilineedit.cpp new file mode 100644 index 0000000..3c8f346 --- /dev/null +++ b/src/attic/qtmultilineedit.cpp @@ -0,0 +1,4236 @@ +/********************************************************************** +** +** Implementation of QtMultiLineEdit widget class +** +** Created : 961005 +** +** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved. +** +** This file contains a class moved out of the Qt GUI Toolkit API. It +** may be used, distributed and modified without limitation. +** +**********************************************************************/ + +#include "qtmultilineedit.h" +#ifndef QT_NO_QTMULTILINEEDIT +#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" +#ifndef QT_NO_COMPAT +#include "qstyle.h" +#endif + + +class QtMultiLineEditCommand +{ +public: + enum Commands { Invalid, Begin, End, Insert, Delete }; + virtual ~QtMultiLineEditCommand() {}; + virtual Commands type() { return Invalid; }; + virtual int terminator() { return 0; } + + virtual bool merge( QtMultiLineEditCommand* ) { return FALSE;} +}; + +class QBeginCommand : public QtMultiLineEditCommand +{ + +public: + QBeginCommand() {} + int terminator() { return 1; } + Commands type() { return Begin; }; +}; + +class QEndCommand : public QtMultiLineEditCommand +{ +public: + QEndCommand() {} + int terminator() { return -1; } + Commands type() { return End; }; +}; + +// QtMultiLineEditUndoRedo methods +class QDelTextCmd : public QtMultiLineEditCommand +{ +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( QtMultiLineEditCommand* 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( QtMultiLineEditCommand* 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 QtMultiLineEdit qtmultilineedit.h + + \brief The QtMultiLineEdit widget is a simple editor for inputting text. + + \obsolete + + The QtMultiLineEdit 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> +*/ + +/* + \property QtMultiLineEdit::numLines + \brief the number of lines in the multi-line edit + + numLines() returns the number of lines in the editor. The count + includes any empty lines at top and bottom, so for an empty editor + this method will return 1. +*/ +/* + \property QtMultiLineEdit::atBeginning + \brief whether the cursor is at the beginning + + atBeginning() returns TRUE if the cursor is placed at the + beginning of the text. +*/ +/* + \property QtMultiLineEdit::atEnd + \brief whether the cursor is at the end + + atEnd() returns TRUE if the cursor is placed at the end of the text. +*/ +/* + \property QtMultiLineEdit::maxLineWidth + \brief the maximum line width in pixels + Returns the width in pixels of the longest text line in this editor. +*/ +/* + \property QtMultiLineEdit::alignment + \brief the alignment + + Possible values are \c AlignLeft, \c Align(H)Center and \c + AlignRight. + \sa Qt::AlignmentFlags +*/ +/* + \property QtMultiLineEdit::edited + \brief whether the text had been edited + +edited() returns the edited flag of the line edit. If this returns FALSE, +the contents has not been changed since the construction of the +QtMultiLineEdit (or the last call to setEdited( FALSE ), if any). If +it returns TRUE, the contents have been edited, or setEdited( TRUE ) +has been called. + +setEdited() sets the edited flag of this line edit to \e e. The +edited flag is never read by QtMultiLineEdit, 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. + +*/ +/* + \property QtMultiLineEdit::echoMode + \brief the echo mode +*/ +/* + \property QtMultiLineEdit::maxLength + \brief the maximum length of the text + + The currently set text length limit, or -1 if there is + no limit (this is the default). + +*/ +/* + \property QtMultiLineEdit::maxLines + \brief the maximum number of lines + The currently set line limit, or -1 if there is + no limit (the default). + + 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. + +*/ +/* + \property QtMultiLineEdit::hMargin + \brief the horizontal margin + The horizontal margin current set. The default is 3. +*/ +/* + \property QtMultiLineEdit::wordWrap + \brief the word wrap mode + + By 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() +*/ +/* + \property QtMultiLineEdit::wrapColumnOrWidth + \brief the wrap width in columns or pixels + The wrap column or wrap width, depending on the word wrap + mode. + \sa setWordWrap(), setWrapColumnOrWidth() +*/ +/* + \property QtMultiLineEdit::wrapPolicy + \brief the wrap policy mode + The default is \c AtWhiteSpace. + +*/ +/* + \property QtMultiLineEdit::autoUpdate + \brief whether auto update is enabled + + autoUpdate() returns TRUE if the view updates itself automatically + whenever it is changed in some way. + + If autoUpdate() 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 autoUpdate() 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() + +*/ +/* + \property QtMultiLineEdit::undoEnabled + \brief whether undo is enabled +*/ +/* + \property QtMultiLineEdit::undoDepth + \brief the undo depth + + The maximum number of operations that can be stored on the undo stack. + + \sa setUndoDepth() +*/ +/* + \property QtMultiLineEdit::readOnly + \brief whether the multi-line edit is read-only +*/ +/* + \property QtMultiLineEdit::overWriteMode + \brief the overwrite mode +*/ +/* + \property QtMultiLineEdit::text + \brief the multi-line edit's text +*/ +/* + \property QtMultiLineEdit::length + \brief the length of the text +*/ + +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 QtMultiLineData +{ + QtMultiLineData() : + isHandlingEvent(FALSE), + edited(FALSE), + maxLineWidth(0), + align(Qt::AlignLeft), + maxlines(-1), + maxlinelen(-1), + maxlen(-1), + wordwrap( QtMultiLineEdit::NoWrap ), + wrapcol( -1 ), + wrappolicy( QtMultiLineEdit::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(3), + marg_extra(0), + echomode(QtMultiLineEdit::Normal), + val(0), + dnd_primed(FALSE), + dnd_forcecursor(FALSE), + undo( TRUE ), + undodepth( 256 ) + { + undoList.setAutoDelete( TRUE ); + redoList.setAutoDelete( TRUE ); + clearChartable(); + } + bool isHandlingEvent; + bool edited; + int maxLineWidth; + int scrollTime; + int scrollAccel; + int align; + int numlines; + int maxlines; + int maxlinelen; + int maxlen; + QtMultiLineEdit::WordWrap wordwrap; + int wrapcol; + QtMultiLineEdit::WrapPolicy wrappolicy; + int lr_marg; + int marg_extra; + QtMultiLineEdit::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<QtMultiLineEditCommand> undoList; + QPtrList<QtMultiLineEditCommand> 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; +#ifndef QT_NO_DRAGANDDROP + QTimer *dnd_timer; +#endif +}; + + +#define CLEAR_UNDO {d->undoList.clear(); emit undoAvailable( FALSE );\ + d->redoList.clear(); emit redoAvailable( FALSE );} + +void QtMultiLineEdit::addUndoCmd(QtMultiLineEditCommand* 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 QtMultiLineEdit::addRedoCmd(QtMultiLineEditCommand* c) +{ + if ( d->redoList.isEmpty() ) + emit redoAvailable(TRUE); + d->redoList.append(c); +} + +static const int initialScrollTime = 50; // mark text scroll time +static const int initialScrollAccel = 5; // mark text scroll accel (0=fastest) +static const int scroll_margin = 16; // auto-scroll edge in DND + +#define WORD_WRAP ( d->wordwrap != QtMultiLineEdit::NoWrap ) +#define DYNAMIC_WRAP ( d->wordwrap == QtMultiLineEdit::WidgetWidth ) +#define FIXED_WIDTH_WRAP ( d->wordwrap == QtMultiLineEdit::FixedPixelWidth ) +#define FIXED_COLUMN_WRAP ( d->wordwrap == QtMultiLineEdit::FixedColumnWidth ) +#define BREAK_WITHIN_WORDS ( d->wrappolicy == QtMultiLineEdit::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 QtMultiLineEdit 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 QtMultiLineEdit widgets are shown. + + \sa defaultTabStop() +*/ + +void QtMultiLineEdit::setDefaultTabStop( int ex ) +{ + defTabStop = ex; +} + + + +/* + Returns the distance between tab stops. + + \sa setDefaultTabStop(); +*/ + +int QtMultiLineEdit::defaultTabStop() +{ + return defTabStop; +} + + + + +static int textWidthWithTabs( const QFontMetrics &fm, const QString &s, uint start, uint nChars, int align ) +{ + if ( s.isEmpty() ) + return 0; + + int dist = -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' && align == Qt::AlignLeft ) { + 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 && ( align != Qt::AlignLeft || s[i] != '\t' ) ) + i++; + dist += fm.width( s.mid(ii,i-ii) ); + } + } + return dist; +} + +static int xPosToCursorPos( const QString &s, const QFontMetrics &fm, + int xPos, int width, int align ) +{ + int i = 0; + int dist; + int tabDist; + + if ( s.isEmpty() ) + return 0; + if ( xPos > width ) + xPos = width; + if ( xPos <= 0 ) + return 0; + + dist = -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' && align == Qt::AlignLeft ) { + 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' && align == Qt::AlignLeft ) { // 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, QtMultiLineEdit with parent \a parent and + called \a name. +*/ + +QtMultiLineEdit::QtMultiLineEdit( QWidget *parent , const char *name ) + :QtTableView( parent, name, WStaticContents | WRepaintNoErase ) +{ + d = new QtMultiLineData; + QFontMetrics fm( font() ); + setCellHeight( fm.lineSpacing() ); + setNumCols( 1 ); + + contents = new QPtrList<QtMultiLineEditRow>; + contents->setAutoDelete( TRUE ); + + cursorX = 0; cursorY = 0; + curXPos = 0; + + setTableFlags( Tbl_autoVScrollBar|Tbl_autoHScrollBar| + Tbl_smoothVScrolling | + Tbl_clipCellPainting + ); + setFrameStyle( QFrame::WinPanel | QFrame::Sunken ); + setBackgroundMode( PaletteBase ); + setWFlags( WResizeNoErase ); + setKeyCompression( TRUE ); + setFocusPolicy( WheelFocus ); +#ifndef QT_NO_CURSOR + setCursor( ibeamCursor ); + verticalScrollBar()->setCursor( arrowCursor ); + horizontalScrollBar()->setCursor( arrowCursor ); +#endif + readOnly = FALSE; + cursorOn = FALSE; + markIsOn = FALSE; + dragScrolling = FALSE; + dragMarking = FALSE; + textDirty = FALSE; + wordMark = FALSE; + overWrite = FALSE; + 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 QtMultiLineEditRow(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 ) ); + } +} + +/* + \fn int QtMultiLineEdit::lineLength( int line ) const + Returns the number of characters at line number \a line. +*/ + +/* \fn QString *QtMultiLineEdit::getString( int line ) const + + Returns a pointer to the text at line \a line. +*/ + +/* \fn void QtMultiLineEdit::textChanged() + + This signal is emitted when the text is changed by an event or by a + slot. Note that the signal is not emitted when you call a non-slot + function such as insertLine(). + + \sa returnPressed() +*/ + +/* \fn void QtMultiLineEdit::returnPressed() + + This signal is emitted when the user presses the return or enter + key. It is not emitted if isReadOnly() is TRUE. + + \sa textChanged() +*/ + +/* + \fn void QtMultiLineEdit::undoAvailable (bool yes) + + This signal is emitted when the availability of undo changes. + If \a yes is TRUE, then undo() will work until + undoAvailable( FALSE ) is next emitted. +*/ + +/* + \fn void QtMultiLineEdit::redoAvailable (bool yes) + + This signal is emitted when the availability of redo changes. + If \a yes is TRUE, then redo() will work until + redoAvailable( FALSE ) is next emitted. +*/ + +/* + \fn void QtMultiLineEdit::copyAvailable (bool yes) + + This signal is emitted when the availability of cut/copy changes. + If \a yes is TRUE, then cut() and copy() will work until + copyAvailable( FALSE ) is next emitted. +*/ + + +/* + If \a on is FALSE, this multi line edit accepts text input. + Scrolling and cursor movements are accepted in any case. + + \sa isReadOnly() QWidget::setEnabled() +*/ + +void QtMultiLineEdit::setReadOnly( bool on ) +{ + if ( readOnly != on ) { + readOnly = on; +#ifndef QT_NO_CURSOR + setCursor( on ? arrowCursor : ibeamCursor ); +#endif + } +} + +/* +*/ +int QtMultiLineEdit::maxLineWidth() const +{ + return d->maxLineWidth; +} + +/* + Destroys the QtMultiLineEdit +*/ + +QtMultiLineEdit::~QtMultiLineEdit() +{ + 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. Paints the line at row \a row + using painter \a painter. The \a col parameter is ignored. +*/ +void QtMultiLineEdit::paintCell( QPainter *painter, int row, int ) +{ + const QColorGroup & g = colorGroup(); + QFontMetrics fm( painter->font() ); + QString s = stringShown(row); + if ( s.isNull() ) { + qWarning( "QtMultiLineEdit::paintCell: (%s) no text at line %d", + name( "unnamed" ), row ); + return; + } + QRect updateR = cellUpdateRect(); + QPixmap *buffer = getCacheBuffer( updateR.size() ); + ASSERT(buffer); + buffer->fill ( g.base() ); + + QPainter p( buffer ); + p.setFont( painter->font() ); + p.translate( -updateR.left(), -updateR.top() ); + + p.setTabStops( tabStopDist(fm) ); + + int yPos = 0; + int markX1, markX2; // in x-coordinate pixels + markX1 = markX2 = 0; // avoid gcc warning + if ( markIsOn ) { + int markBeginX, markBeginY; + int markEndX, markEndY; + getMarkedRegion( &markBeginY, &markBeginX, &markEndY, &markEndX ); + if ( row >= markBeginY && row <= markEndY ) { + if ( row == markBeginY ) { + markX1 = markBeginX; + if ( row == markEndY ) // both marks on same row + markX2 = markEndX; + else + markX2 = s.length(); // mark till end of line + } else { + if ( row == markEndY ) { + markX1 = 0; + markX2 = markEndX; + } else { + markX1 = 0; // whole line is marked + markX2 = s.length(); // whole line is marked + } + } + } + } + p.setPen( g.text() ); + QtMultiLineEditRow* r = contents->at( row ); + int wcell = cellWidth() - 2*d->lr_marg;// - d->marg_extra; + int wrow = r->w; + int x = d->lr_marg - p.fontMetrics().leftBearing(s[0]); + if ( d->align == Qt::AlignCenter || d->align == Qt::AlignHCenter ) + x += (wcell - wrow) / 2; + else if ( d->align == Qt::AlignRight ) + x += wcell - wrow; + p.drawText( x, yPos, cellWidth()-d->lr_marg-x, cellHeight(), + d->align == AlignLeft?ExpandTabs:0, s ); + if ( !r->newline && BREAK_WITHIN_WORDS ) + p.drawPixmap( x + wrow - d->lr_marg - d->marg_extra, yPos, d->arrow ); +#if 0 + if ( r->newline ) + p.drawLine( d->lr_marg, yPos+cellHeight()-2, cellWidth() - d->lr_marg, yPos+cellHeight()-2); +#endif + if ( markX1 != markX2 ) { + int sLength = s.length(); + int xpos1 = mapToView( markX1, row ); + int xpos2 = mapToView( markX2, row ); + int fillxpos1 = xpos1; + int fillxpos2 = xpos2; + if ( markX1 == 0 ) + fillxpos1 -= 2; + if ( markX2 == sLength ) + fillxpos2 += 3; + p.setClipping( TRUE ); + p.setClipRect( fillxpos1 - updateR.left(), 0, + fillxpos2 - fillxpos1, cellHeight(row) ); + p.fillRect( fillxpos1, 0, fillxpos2 - fillxpos1, cellHeight(row), + g.brush( QColorGroup::Highlight ) ); + p.setPen( g.highlightedText() ); + p.drawText( x, yPos, cellWidth()-d->lr_marg-x, cellHeight(), + d->align == AlignLeft?ExpandTabs:0, s ); + p.setClipping( FALSE ); + } + + if ( row == cursorY && cursorOn && !readOnly ) { + int cursorPos = QMIN( (int)s.length(), cursorX ); + int cXPos = QMAX( mapToView( cursorPos, row ), 0 ); + int cYPos = 0; + if ( hasFocus() || d->dnd_forcecursor ) { + p.setPen( g.text() ); + /* styled? + p.drawLine( cXPos - 2, cYPos, + cXPos + 2, cYPos ); + */ + p.drawLine( cXPos, cYPos, + cXPos, cYPos + fm.height() - 2); + /* styled? + p.drawLine( cXPos - 2, cYPos + fm.height() - 2, + cXPos + 2, cYPos + fm.height() - 2); + */ + +#ifndef QT_NO_TRANSFORMATIONS + // TODO: set it other times, eg. when scrollbar moves view + QWMatrix wm = painter->worldMatrix(); + setMicroFocusHint( int(wm.dx()+cXPos), + int (wm.dy()+cYPos), + 1, fm.ascent() ); +#else + setMicroFocusHint( cXPos, + cYPos, + 1, fm.ascent() ); +#endif + } + } + p.end(); + painter->drawPixmap( updateR.left(), updateR.top(), *buffer, + 0, 0, updateR.width(), updateR.height() ); +} + + +/* + \overload + Returns the width in pixels of the string \a s. + NOTE: only appropriate for whole lines. +*/ + +int QtMultiLineEdit::textWidth( const QString &s ) +{ + int w = 0; + if ( !s.isNull() ) { + w = textWidthWithTabs( QFontMetrics( font() ), s, 0, s.length(), + d->align ); + } + return w + 2 * d->lr_marg + d->marg_extra; +} + + +/* + Returns the width in pixels of the text at line \a line. +*/ + +int QtMultiLineEdit::textWidth( int line ) +{ + if ( d->echomode == Password) { + QString s = stringShown(line); + return textWidth( s ); + } + QtMultiLineEditRow* r = contents->at(line); + return r?r->w:0; +} + +/* + Starts the cursor blinking. +*/ + +void QtMultiLineEdit::focusInEvent( QFocusEvent * ) +{ + stopAutoScroll(); + if ( !d->blinkTimer->isActive() ) + d->blinkTimer->start( QApplication::cursorFlashTime() / 2, FALSE ); + cursorOn = TRUE; + updateCell( cursorY, 0, FALSE ); +} + + +/*\reimp +*/ +void QtMultiLineEdit::leaveEvent( QEvent * ) +{ +} + + +/*\reimp +*/ +void QtMultiLineEdit::focusOutEvent( QFocusEvent * ) +{ + stopAutoScroll(); + d->blinkTimer->stop(); + if ( cursorOn ) { + cursorOn = FALSE; + updateCell( cursorY, 0, FALSE ); + } +} + + +/* + \reimp + Present for binary compatibility only! +*/ + +void QtMultiLineEdit::timerEvent( QTimerEvent * ) +{ + // ############ Remove in 3.0!!!!!!!! +} + +#ifndef QT_NO_DRAGANDDROP +void QtMultiLineEdit::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 QtMultiLineEdit::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 QtMultiLineEdit::hasMarkedText() const +{ + return markIsOn; +} + + +/* + Returns a copy of the marked text. +*/ + +QString QtMultiLineEdit::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 QtMultiLineEdit::textLine( int line ) const +{ + QString *s = getString(line); + if ( s ) { + if ( s->isNull() ) + return QString::fromLatin1(""); + else + return *s; + } else + return QString::null; +} + + +/* + 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 QtMultiLineEdit::text() const +{ + QString 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 QtMultiLineEdit::selectAll() +{ + markAnchorX = 0; + markAnchorY = 0; + markDragY = numLines() - 1; + markDragX = lineLength( markDragY ); + turnMark( markDragX != markAnchorX || markDragY != markAnchorY ); + if ( autoUpdate() ) + update(); +} + + + +/* + Deselects all text (i.e. removes marking) and leaves the cursor at the + current position. +*/ + +void QtMultiLineEdit::deselect() +{ + turnMark( FALSE ); +} + + +/* + Sets the text to \a s, removing old text, if any. +*/ + +void QtMultiLineEdit::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() ) + update(); + setUndoEnabled( oldUndo ); +} + + +/* + Appends \a s to the text. +*/ + +void QtMultiLineEdit::append( const QString &s ) +{ + bool oldUndo = isUndoEnabled(); + setUndoEnabled( FALSE ); + insertLine( s, -1 ); + setUndoEnabled( oldUndo ); + emit textChanged(); +} + +/* \reimp +Passes wheel events to the vertical scrollbar. +*/ +void QtMultiLineEdit::wheelEvent( QWheelEvent *e ){ + QApplication::sendEvent( verticalScrollBar(), e); +} + + +/* + The key press event handler converts a key press in event \a e to + some line editor action. + + Here are the default key bindings when isReadOnly() is FALSE: + \list + \i <i> Left Arrow </i> Move the cursor one character leftwards + \i <i> Right Arrow </i> Move the cursor one character rightwards + \i <i> Up Arrow </i> Move the cursor one line upwards + \i <i> Down Arrow </i> Move the cursor one line downwards + \i <i> Page Up </i> Move the cursor one page upwards + \i <i> Page Down </i> Move the cursor one page downwards + \i <i> Backspace </i> Delete the character to the left of the cursor + \i <i> Home </i> Move the cursor to the beginning of the line + \i <i> End </i> Move the cursor to the end of the line + \i <i> Delete </i> Delete the character to the right of the cursor + \i <i> Shift - Left Arrow </i> Mark text one character leftwards + \i <i> Shift - Right Arrow </i> Mark text one character rightwards + \i <i> Control-A </i> Move the cursor to the beginning of the line + \i <i> Control-B </i> Move the cursor one character leftwards + \i <i> Control-C </i> Copy the marked text to the clipboard + \i <i> Control-D </i> Delete the character to the right of the cursor + \i <i> Control-E </i> Move the cursor to the end of the line + \i <i> Control-F </i> Move the cursor one character rightwards + \i <i> Control-H </i> Delete the character to the left of the cursor + \i <i> Control-K </i> Delete to end of line + \i <i> Control-N </i> Move the cursor one line downwards + \i <i> Control-P </i> Move the cursor one line upwards + \i <i> Control-V </i> Paste the clipboard text into line edit + \i <i> Control-X </i> Cut the marked text, copy to clipboard + \i <i> Control-Z </i> Undo the last operation + \i <i> Control-Y </i> Redo the last operation + \i <i> Control - Left Arrow </i> Move the cursor one word leftwards + \i <i> Control - Right Arrow </i> Move the cursor one word rightwards + \i <i> Control - Up Arrow </i> Move the cursor one word upwards + \i <i> Control - Down Arrow </i> Move the cursor one word downwards + \i <i> Control - Home Arrow </i> Move the cursor to the beginning of the text + \i <i> Control - End Arrow </i> Move the cursor to the end of the text + \endlist + In addition, the following key bindings are used on Windows: + \list + \i <i> Shift - Delete </i> Cut the marked text, copy to clipboard + \i <i> Shift - Insert </i> Paste the clipboard text into line edit + \i <i> Control - Insert </i> Copy the marked text to the clipboard + \endlist + All other keys with valid ASCII codes insert themselves into the line. + + Here are the default key bindings when isReadOnly() is TRUE: + \list + \i <i> Left Arrow </i> Scrolls the table rightwards + \i <i> Right Arrow </i> Scrolls the table rightwards + \i <i> Up Arrow </i> Scrolls the table one line downwards + \i <i> Down Arrow </i> Scrolls the table one line upwards + \i <i> Page Up </i> Scrolls the table one page downwards + \i <i> Page Down </i> Scrolls the table one page upwards + \i <i> Control-C </i> Copy the marked text to the clipboard + \endlist + +*/ + +void QtMultiLineEdit::keyPressEvent( QKeyEvent *e ) +{ + textDirty = FALSE; + d->isHandlingEvent = TRUE; + int unknown = 0; + if ( readOnly ) { + int pageSize = viewHeight() / 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 ); + break; + case Key_Down: + setTopCell( topCell() + 1 ); + 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) ) + 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 ); + if ( textDirty ) + emit textChanged(); + 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: + insert( e->text() ); + 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 QtMultiLineEdit::pageDown( bool mark ) +{ + bool oldAuto = autoUpdate(); + if ( mark ) + setAutoUpdate( FALSE ); + + if ( partiallyInvisible( cursorY ) ) + cursorY = topCell(); + int delta = cursorY - topCell(); + int pageSize = viewHeight() / cellHeight(); + int newTopCell = QMIN( topCell() + pageSize, numLines() - 1 - pageSize ); + + 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() ) { + cursorY = 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 + cursorY = QMIN( cursorY + pageSize, numLines() - 1); + cursorX = mapFromView( curXPos, cursorY ); + if ( mark ) + newMark( cursorX, cursorY, FALSE ); + makeVisible(); + } + if ( oldAuto ) + if ( mark ) { + setAutoUpdate( TRUE ); + update(); + } else { + updateCell( oldY, 0, FALSE ); + } + if ( !mark ) + turnMark( FALSE ); +} + + +/* + Moves the cursor one page up. If \a mark is TRUE, the text + is marked. +*/ + +void QtMultiLineEdit::pageUp( bool mark ) +{ + bool oldAuto = autoUpdate(); + if ( mark ) + setAutoUpdate( FALSE ); + if ( partiallyInvisible( cursorY ) ) + cursorY = 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() ) { + cursorY = QMIN( newTopCell + delta, numLines() - 1 ); + if ( partial ) + cursorY--; + cursorX = mapFromView( curXPos, cursorY ); + if ( mark ) + newMark( cursorX, cursorY, FALSE ); + setTopCell( newTopCell ); + } else { // just move the cursor + cursorY = QMAX( cursorY - pageSize, 0 ); + cursorX = mapFromView( curXPos, cursorY ); + if ( mark ) + newMark( cursorX, cursorY, FALSE ); + } + if ( oldAuto ) + if ( mark ) { + setAutoUpdate( TRUE ); + update(); + } else { + updateCell( oldY, 0, FALSE ); + } + if ( !mark ) + turnMark( FALSE ); +} + +// THE CORE INSERTION FUNCTION +void QtMultiLineEdit::insertAtAux( const QString &txt, int line, int col, bool mark ) +{ + dummy = FALSE; + d->blinkTimer->stop(); + cursorOn = TRUE; + int oldw = contentsRect().width(); + + line = QMAX( QMIN( line, numLines() - 1), 0 ); + col = QMAX( QMIN( col, lineLength( line )), 0 ); + + QString itxt = txt; + QtMultiLineEditRow *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 ); + + if ( mark ) { + markAnchorX = col; + markAnchorY = line; + } + if ( cursorX == col && cursorY == line ) { + cursorX += itxt.length(); + } + QFontMetrics fm( font() ); + 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) ); + updateCell( line, 0, FALSE ); + } + else { + if ( line > 0 && !contents->at( line-1)->newline ) + rebreakParagraph( line-1 ); + else + rebreakParagraph( line ); + } + if ( mark ) + newMark( cursorX, cursorY, FALSE ); + + setNumRowsAndTruncate(); + + textDirty = TRUE; + d->edited = TRUE; + if ( autoUpdate() ) { + makeVisible(); + d->blinkTimer->start( QApplication::cursorFlashTime() / 2, FALSE ); + if ( DYNAMIC_WRAP && oldw != contentsRect().width() ) { + setAutoUpdate( FALSE ); + rebreakAll(); + setAutoUpdate( TRUE ); + update(); + } + } +} + + +/* + 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 QtMultiLineEdit::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 QtMultiLineEditRow(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 QtMultiLineEdit::removeLine( int line ) +{ + CLEAR_UNDO + if ( line >= numLines() ) + return; + if ( cursorY >= line && cursorY > 0 ) + cursorY--; + bool updt = autoUpdate() && rowIsVisible( line ); + QtMultiLineEditRow* 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 QtMultiLineEditRow(QString::fromLatin1(""), w) ); + setWidth( w ); + dummy = TRUE; + } + if ( setNumRowsAndTruncate() ) + recalc = updt = FALSE; + if ( recalc ) + updateCellWidth(); + makeVisible(); + if (updt) + update(); + textDirty = TRUE; + d->edited = TRUE; +} + +/* + Inserts \a s at the current cursor position. +*/ +void QtMultiLineEdit::insert( const QString& s ) +{ + insert( s, FALSE ); +} + +/* + Inserts \a c at the current cursor position. + (this function is provided for backward compatibility - + it simply calls insert()). +*/ +void QtMultiLineEdit::insertChar( QChar c ) +{ + insert(c); +} + +/* + \overload + Inserts string \a str at the current cursor position. If \a mark is + TRUE the string is marked. +*/ + +void QtMultiLineEdit::insert( const QString& str, bool mark ) +{ + 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 ( wasMarkedText ) + addUndoCmd( new QEndCommand() ); +} + +/* + Makes a line break at the current cursor position. +*/ + +void QtMultiLineEdit::newLine() +{ + insert("\n"); +} + +/* + Deletes text from the current cursor position to the end of the line. +*/ + +void QtMultiLineEdit::killLineAux() +{ + deselect(); // -sanders Don't let del() delete marked region + QtMultiLineEditRow* 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 ); + updateCell( 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 QtMultiLineEdit::cursorLeft( bool mark, bool wrap ) +{ + cursorLeft(mark,!mark,wrap); +} +void QtMultiLineEdit::cursorLeft( bool mark, bool clear_mark, bool wrap ) +{ + if ( cursorX != 0 || cursorY != 0 && wrap ) { + if ( mark && !hasMarkedText() ) { + markAnchorX = cursorX; + markAnchorY = cursorY; + } + d->blinkTimer->stop(); + int ll = lineLength( cursorY ); + if ( cursorX > ll ) + cursorX = ll; + cursorOn = TRUE; + cursorX--; + if ( cursorX < 0 ) { + int oldY = cursorY; + if ( cursorY > 0 ) { + cursorY--; + cursorX = lineLength( cursorY ); + if ( cursorX > 1 && !isEndOfParagraph( cursorY ) ) + cursorX--; + } else { + cursorY = 0; //### ? + cursorX = 0; + } + updateCell( oldY, 0, FALSE ); + } + if ( mark ) + newMark( cursorX, cursorY, FALSE ); + d->blinkTimer->start( QApplication::cursorFlashTime() / 2, FALSE ); + updateCell( 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 QtMultiLineEdit::cursorRight( bool mark, bool wrap ) +{ + cursorRight(mark,!mark,wrap); +} +void QtMultiLineEdit::cursorRight( bool mark, bool clear_mark, bool wrap ) +{ + int strl = lineLength( cursorY ); + if ( strl > 1 && !isEndOfParagraph( cursorY ) ) + strl--; + if ( cursorX < strl || cursorY < (int)contents->count() - 1 && wrap ) { + if ( mark && !hasMarkedText() ) { + markAnchorX = cursorX; + markAnchorY = cursorY; + } + d->blinkTimer->stop(); + cursorOn = TRUE; + cursorX++; + if ( cursorX > strl ) { + int oldY = cursorY; + if ( cursorY < (int) contents->count() - 1 ) { + cursorY++; + cursorX = 0; + } else { + cursorX = lineLength( cursorY ); + } + updateCell( oldY, 0, FALSE ); + } + if ( mark ) + newMark( cursorX, cursorY, FALSE ); + updateCell( cursorY, 0, FALSE ); + d->blinkTimer->start( QApplication::cursorFlashTime() / 2, FALSE ); + } + 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 QtMultiLineEdit::cursorUp( bool mark ) +{ + cursorUp(mark,!mark); +} +void QtMultiLineEdit::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; + d->blinkTimer->stop(); + cursorOn = TRUE; + cursorY--; + if ( cursorY < 0 ) { + cursorY = 0; + } + cursorX = mapFromView( curXPos, cursorY ); + if ( mark ) + newMark( cursorX, cursorY, FALSE ); + updateCell( oldY, 0, FALSE ); + updateCell( cursorY, 0, FALSE ); + d->blinkTimer->start( QApplication::cursorFlashTime() / 2, FALSE ); + } + 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 QtMultiLineEdit::cursorDown( bool mark ) +{ + cursorDown(mark,!mark); +} +void QtMultiLineEdit::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; + d->blinkTimer->stop(); + cursorOn = TRUE; + cursorY++; + if ( cursorY > lastLin ) { + cursorY = lastLin; + } + cursorX = mapFromView( curXPos, cursorY ); + if ( mark ) + newMark( cursorX, cursorY, FALSE ); + updateCell( oldY, 0, FALSE ); + updateCell( cursorY, 0, FALSE ); + d->blinkTimer->start( QApplication::cursorFlashTime() / 2, FALSE ); + } + makeVisible(); + if ( clear_mark ) + turnMark( FALSE ); +} + +/* + Turns off marked text +*/ +void QtMultiLineEdit::turnMark( bool on ) +{ + if ( on != markIsOn ) { + markIsOn = on; + if ( echoMode() == Normal ) + emit copyAvailable( on ); + update(); + } +} + + + + +/* + 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 QtMultiLineEdit::backspace() +{ + if ( hasMarkedText() ) { + del(); + } else { + if ( !atBeginning() ) { + cursorLeft( FALSE ); + del(); + } + } + makeVisible(); +} + +void QtMultiLineEdit::delAux() +{ + int markBeginX, markBeginY; + int markEndX, markEndY; + QRect oldContents = contentsRect(); + if ( getMarkedRegion( &markBeginY, &markBeginX, &markEndY, &markEndX ) ) { + turnMark( FALSE ); + textDirty = TRUE; + d->edited = TRUE; + if ( markBeginY == markEndY ) { //just one line + QtMultiLineEditRow *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() ) + updateCell( cursorY, 0, FALSE ); + if ( recalc ) + updateCellWidth(); + } else { //multiline + bool oldAuto = autoUpdate(); + setAutoUpdate( FALSE ); + ASSERT( markBeginY >= 0); + ASSERT( markEndY < (int)contents->count() ); + + QtMultiLineEditRow *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 ); + + for( int i = markBeginY + 1 ; i <= markEndY ; i++ ) + contents->remove( markBeginY + 1 ); + + if ( contents->isEmpty() ) + insertLine( QString::fromLatin1(""), -1 ); + + cursorX = markBeginX; + cursorY = markBeginY; + curXPos = 0; + + setNumRowsAndTruncate(); + updateCellWidth(); + setAutoUpdate( oldAuto ); + if ( autoUpdate() ) + update(); + } + markAnchorY = markDragY = cursorY; + markAnchorX = markDragX = cursorX; + } else { + if ( !atEnd() ) { + textDirty = TRUE; + d->edited = TRUE; + QtMultiLineEditRow *r = contents->at( cursorY ); + if ( cursorX == (int) r->s.length() ) { // remove newline + QtMultiLineEditRow* 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 ); + } else { + bool recalc = r->w == maxLineWidth(); + r->s.remove( cursorX, 1 ); + rebreakParagraph( cursorY ); + if ( recalc ) + updateCellWidth(); + } + } + } + if ( DYNAMIC_WRAP && oldContents != contentsRect() ) { + if ( oldContents.width() != contentsRect().width() ) { + bool oldAuto = autoUpdate(); + setAutoUpdate( FALSE ); + rebreakAll(); + setAutoUpdate( oldAuto ); + } + if ( autoUpdate() ) + update(); + } + curXPos = 0; + 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 QtMultiLineEdit::home( bool mark ) +{ + if ( cursorX != 0 ) { + if ( mark && !hasMarkedText() ) { + markAnchorX = cursorX; + markAnchorY = cursorY; + } + d->blinkTimer->stop(); + cursorX = 0; + cursorOn = TRUE; + if ( mark ) + newMark( cursorX, cursorY, FALSE ); + updateCell( cursorY, 0, FALSE ); + d->blinkTimer->start( QApplication::cursorFlashTime() / 2, FALSE ); + } + curXPos = 0; + if ( !mark ) + turnMark( FALSE ); + makeVisible(); +} + +/* + Moves the text cursor to the right end of the line. If \a 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 QtMultiLineEdit::end( bool mark ) +{ + int tlen = lineLength( cursorY ); + if ( cursorX != tlen ) { + if ( mark && !hasMarkedText() ) { + markAnchorX = cursorX; + markAnchorY = cursorY; + } + d->blinkTimer->stop(); + cursorX = tlen; + cursorOn = TRUE; + if ( mark ) + newMark( cursorX, cursorY, FALSE ); + d->blinkTimer->start( QApplication::cursorFlashTime() / 2, FALSE ); + updateCell( cursorY, 0, FALSE ); + } + curXPos = 0; + makeVisible(); + if ( !mark ) + turnMark( FALSE ); +} + +/*\reimp +*/ +void QtMultiLineEdit::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 QtMultiLineEdit::pixelPosToCursorPos(QPoint p, int* x, int* y) const +{ + *y = findRow( p.y() ); + if ( *y < 0 ) { + 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, + d->align ); + QtMultiLineEditRow* r = contents->at( *y ); + if (r && !r->newline && ((unsigned int)*x == r->s.length()) && (*x > 0)) + --*x; +} + +void QtMultiLineEdit::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 ) { + cursorY = newY; + update(); + d->isHandlingEvent = FALSE; + return; + } + } + if ( cursorY != newY ) { + int oldY = cursorY; + cursorY = newY; + updateCell( oldY, 0, FALSE ); + } + updateCell( cursorY, 0, FALSE ); // ### +} + +void QtMultiLineEdit::startAutoScroll() +{ + if ( !dragScrolling ) { + d->scrollTime = initialScrollTime; + d->scrollAccel = initialScrollAccel; + d->scrollTimer->start( d->scrollTime, FALSE ); + dragScrolling = TRUE; + } +} + +void QtMultiLineEdit::stopAutoScroll() +{ + if ( dragScrolling ) { + d->scrollTimer->stop(); + dragScrolling = FALSE; + } +} + +/*\reimp +*/ +void QtMultiLineEdit::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 ) + return; + int oldY = markDragY; + newMark( newX, newY, FALSE ); + for ( int i = QMIN(oldY,newY); i <= QMAX(oldY,newY); i++ ) + updateCell( i, 0, FALSE ); +} + +void QtMultiLineEdit::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 QtMultiLineEdit::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 +#ifndef QT_NO_COMPAT + if ( style().styleHint(QStyle::SH_GUIStyle) == Qt::MotifStyle ) + paste(); +#endif +#endif + } +#endif + + d->isHandlingEvent = FALSE; + + if ( !readOnly && textDirty ) + emit textChanged(); +} + + +/*\reimp +*/ +void QtMultiLineEdit::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; + updateCell( cursorY, 0, FALSE ); + + } +} + +#ifndef QT_NO_DRAGANDDROP + +/* + \reimp +*/ + +void QtMultiLineEdit::dragEnterEvent( QDragEnterEvent * ) +{ + cursorOn = TRUE; + updateCell( cursorY, 0, FALSE ); +} + +/*\reimp +*/ +void QtMultiLineEdit::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 QtMultiLineEdit::dragLeaveEvent( QDragLeaveEvent* ) +{ + if ( cursorOn ) { + cursorOn = FALSE; + updateCell( cursorY, 0, FALSE ); + } + stopAutoScroll(); +} + +/*\reimp +*/ +void QtMultiLineEdit::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); + } + update(); + emit textChanged(); + } +} + +#endif // QT_NO_DRAGANDDROP + + +/* + Returns TRUE if line \a line is invisible or partially invisible. +*/ + +bool QtMultiLineEdit::partiallyInvisible( int line ) +{ + int y; + if ( !rowYPos( line, &y ) ) + return TRUE; + if ( y < 0 ) + return TRUE; + else if ( y + cellHeight() - 2 > viewHeight() ) + return TRUE; + + return FALSE; +} + +/* + Scrolls such that the cursor is visible +*/ + +void QtMultiLineEdit::makeVisible() +{ + if ( !autoUpdate() ) + return; + + 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 ); + } +} + +/* + Computes the character position in line \a line which corresponds + to pixel \a xPos +*/ + +int QtMultiLineEdit::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, + d->align ); + QtMultiLineEditRow* 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 QtMultiLineEdit::mapToView( int xIndex, int line ) +{ + 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, d->align ) - 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 QtMultiLineEdit::updateCellWidth() +{ + QtMultiLineEditRow* r = contents->first(); + int maxW = 0; + int w; + 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 ); +} + + +/* + Sets the bottommost visible line to \a line. +*/ + +void QtMultiLineEdit::setBottomCell( int line ) +{ + int rowY = cellHeight() * line; + int newYPos = rowY + cellHeight() - viewHeight(); + setYOffset( QMAX( newYPos, 0 ) ); +} + +#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 QtMultiLineEdit::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 + t.replace( "\r\n", "\n" ); +#endif + + for (int i=0; (uint)i<t.length(); i++) { + if ( t[i] < ' ' && t[i] != '\n' && t[i] != '\t' ) + 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 QtMultiLineEdit::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 QtMultiLineEdit::pasteSpecial(const QPoint& pt) +{ + QCString st = pickSpecial(QApplication::clipboard()->data(),TRUE,pt); + if ( !st.isEmpty() ) + pasteSubType(st); +} +#endif +#ifndef QT_NO_MIME +QCString QtMultiLineEdit::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 QtMultiLineEdit::clear() +{ + addUndoCmd( new QDelTextCmd( 0, text() ) ); + setEdited( TRUE ); + contents->clear(); + cursorX = cursorY = 0; + int w = textWidth( QString::fromLatin1("") ); + contents->append( new QtMultiLineEditRow(QString::fromLatin1(""), w) ); + setNumRowsAndTruncate(); + setWidth( w ); + dummy = TRUE; + turnMark( FALSE ); + if ( autoUpdate() ) + update(); + if ( !d->isHandlingEvent ) //# && not already empty + emit textChanged(); + update(); +} + + +/*\reimp +*/ + +void QtMultiLineEdit::setFont( const QFont &font ) +{ + QWidget::setFont( font ); + d->clearChartable(); + QFontMetrics fm( font ); + setCellHeight( fm.lineSpacing() ); + for ( QtMultiLineEditRow* r = contents->first(); r; r = contents->next() ) + r->w = textWidth( r->s ); + rebreakAll(); + updateCellWidth(); +} + +/* + Sets a new marked text limit, does not repaint the widget. +*/ + +void QtMultiLineEdit::newMark( int posx, int posy, bool /*copy*/ ) +{ + if ( markIsOn && markDragX == posx && markDragY == posy && + cursorX == posx && cursorY == posy ) + return; + markDragX = posx; + markDragY = posy; + cursorX = posx; + cursorY = posy; + turnMark( markDragX != markAnchorX || markDragY != markAnchorY ); +} + +bool QtMultiLineEdit::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 QtMultiLineEdit::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 QtMultiLineEdit::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 QtMultiLineEdit::markWord( int posx, int posy ) +{ + QString& s = contents->at( posy )->s; + + int i = posx - 1; + while ( i >= 0 && s[i].isPrint() && !s[i].isSpace() ) + i--; + i++; + markAnchorY = posy; + markAnchorX = i; + + while ( s[i].isPrint() && !s[i].isSpace() ) + 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 QtMultiLineEdit::charClass( QChar ch ) +{ + if ( !ch.isPrint() || ch.isSpace() ) return 1; + else if ( ch.isLetter() || ch=='-' || ch=='+' || ch==':' + || ch=='.' || ch=='/' || ch=='\\' + || ch=='@' || ch=='$' || ch=='~' ) return 2; + else return 3; +} + +#ifndef QT_NO_CLIPBOARD +/* + Copies the marked text to the clipboard. Will copy only + if echoMode() is Normal. +*/ + +void QtMultiLineEdit::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 + t.replace( "\n", "\r\n" ); +#endif + QApplication::clipboard()->setText( t ); +#if defined(_WS_X11_) + connect( QApplication::clipboard(), SIGNAL(dataChanged()), + this, SLOT(clipboardChanged()) ); +#endif + } +} + +/* \obsolete + + Backward compatibility. +*/ +void QtMultiLineEdit::copyText() const +{ + copy(); +} + +/* + Copies the selected text to the clipboard and deletes the selected text. +*/ + +void QtMultiLineEdit::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 QtMultiLineEdit::clipboardChanged() +{ +#if defined(_WS_X11_) + disconnect( QApplication::clipboard(), SIGNAL(dataChanged()), + this, SLOT(clipboardChanged()) ); + turnMark( FALSE ); + update(); +#endif +} + + +/* + Sets maxLineWidth() and maybe cellWidth() to \a w without updating the entire widget. + */ + +void QtMultiLineEdit::setWidth( int w ) +{ + if ( w ==d->maxLineWidth ) + return; + bool u = autoUpdate(); + setAutoUpdate( FALSE ); + d->maxLineWidth = w; + if ( d->align == AlignLeft ) + setCellWidth( w ); + else + setCellWidth( QMAX( w, contentsRect().width() ) ); + setAutoUpdate( u ); + if ( autoUpdate() && d->align != AlignLeft ) + 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 QtMultiLineEdit::setCursorPosition( int line, int col, bool mark ) +{ + if ( mark && !hasMarkedText() ) { + markAnchorX = cursorX; + markAnchorY = cursorY; + } + int oldY = cursorY; + cursorY = QMAX( QMIN( line, numLines() - 1), 0 ); + cursorX = QMAX( QMIN( col, lineLength( cursorY )), 0 ); + curXPos = 0; + makeVisible(); + if ( mark ) { + newMark( cursorX, cursorY, FALSE ); + for ( int i = QMIN(oldY,cursorY); i <= QMAX(oldY,cursorY); i++ ) + updateCell( i, 0, FALSE ); + } else { + updateCell( oldY, 0, FALSE ); + turnMark( FALSE ); + } +} + + + +/* \obsolete + + Use getCursorPosition() instead. +*/ + +void QtMultiLineEdit::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 QtMultiLineEdit::getCursorPosition( int *line, int *col ) const +{ + if ( line ) + *line = cursorY; + if ( col ) + *col = cursorX; +} + + +/* + \sa setAutoUpdate() +*/ + +bool QtMultiLineEdit::autoUpdate() const +{ + return QtTableView::autoUpdate(); +} + + +/* + Sets the auto-update option of multi-line editor to \a enable. + +*/ + +void QtMultiLineEdit::setAutoUpdate( bool enable ) +{ + QtTableView::setAutoUpdate( enable ); +} + +/* + Sets the fixed height of the QtMultiLineEdit so that \a lines text lines + are visible given the current font. + + \sa setMaxLines(), setFixedHeight() + */ +void QtMultiLineEdit::setFixedVisibleLines( int lines ) +{ + int ls = fontMetrics().lineSpacing(); + setFixedHeight( frameWidth()*2 + ls*lines ); + return; +} + + + +/* + Returns the top center point where the cursor is drawn +*/ + +QPoint QtMultiLineEdit::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, d->align ) - 1 ); + cp.setY( (row * cellHeight()) + viewRect().y() ); + return cp; +} + + +/* \reimp +*/ +QSizePolicy QtMultiLineEdit::sizePolicy() const +{ + //### removeme 3.0 + return QWidget::sizePolicy(); +} + + +/*\reimp +*/ +QSize QtMultiLineEdit::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 QtMultiLineEdit::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) ) + w += verticalScrollBar()->sizeHint().width(); + if ( testTableFlags(Tbl_vScrollBar|Tbl_autoVScrollBar) ) + h += horizontalScrollBar()->sizeHint().height(); + return QSize(w,h); +} + + + +/*\reimp +*/ + +void QtMultiLineEdit::resizeEvent( QResizeEvent *e ) +{ + int oldw = contentsRect().width(); + QtTableView::resizeEvent( e ); + if ( DYNAMIC_WRAP + && (e->oldSize().width() != width() + || oldw != contentsRect().width() ) ) { + bool oldAuto = autoUpdate(); + setAutoUpdate( FALSE ); + rebreakAll(); + if ( oldw != contentsRect().width() ) + rebreakAll(); + setAutoUpdate( oldAuto ); + if ( autoUpdate() ) + repaint( FALSE ); + } else if ( d->align != AlignLeft ) { + d->maxLineWidth = 0; // trigger update + updateCellWidth(); + } + if ( isVisible() ) + deselect(); +} + +/* + Sets the alignment to \a flags. Possible values are \c AlignLeft, \c + Align(H)Center and \c AlignRight. + + \sa alignment(), Qt::AlignmentFlags +*/ +void QtMultiLineEdit::setAlignment( int flags ) +{ + if ( d->align != flags ) { + d->align = flags; + update(); + } +} + +/* + Returns the alignment. + +*/ +int QtMultiLineEdit::alignment() const +{ + return d->align; +} + + +/* + Not supported at this time. + \a v is the validator to set. +*/ +void QtMultiLineEdit::setValidator( const QValidator *v ) +{ + d->val = v; + // #### validate text now +} + +/* + Not supported at this time. +*/ +const QValidator * QtMultiLineEdit::validator() const +{ + return d->val; +} + +/* \sa edited() +*/ +void QtMultiLineEdit::setEdited( bool e ) +{ + d->edited = e; +} + +/* \sa setEdited() +*/ +bool QtMultiLineEdit::edited() const +{ + return d->edited; +} + +/* \enum QtMultiLineEdit::EchoMode + + This enum type describes the ways in which QLineEdit can display its + contents. The currently defined values are: \list + + \i Normal - display characters as they are entered. This is + the default. + + \i NoEcho - do not display anything. + + \i Password - display asterisks instead of the characters + actually entered. + + \endlist + + \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 QtMultiLineEdit::setEchoMode( EchoMode em ) +{ + if ( d->echomode != em ) { + d->echomode = em; + updateCellWidth(); + update(); + } +} + +/* + Returns the currently set echo mode. + + \sa setEchoMode() +*/ +QtMultiLineEdit::EchoMode QtMultiLineEdit::echoMode() const +{ + return d->echomode; +} + + +/* + Returns the string shown at line \a row, including + processing of the echoMode(). +*/ + +QString QtMultiLineEdit::stringShown(int row) const +{ + QString* s = getString(row); + if ( !s ) return QString::null; + switch ( d->echomode ) { + case Normal: + if (!*s) return QString::fromLatin1(""); + return *s; + 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(""); +} + +/* + + \sa maxLength() +*/ +void QtMultiLineEdit::setMaxLength(int m) +{ + d->maxlen = m; +} + +/* + \sa setMaxLength() +*/ +int QtMultiLineEdit::maxLength() const +{ + return d->maxlen; +} + + +/* + Returns the length of the current text. + + \sa setMaxLength() + */ +int QtMultiLineEdit::length() const +{ + int l = 0; + for ( QtMultiLineEditRow* 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 QtMultiLineEdit::setMaxLineLength(int m) +{ + bool trunc = d->maxlinelen < 0 || m < d->maxlinelen; + d->maxlinelen = m; + if ( trunc ) { + QtMultiLineEditRow* r = contents->first(); + while ( r ) { + r->s.truncate( m ); + r = contents->next(); + } + if ( cursorX > m ) cursorX = m; + if ( markAnchorX > m ) markAnchorX = m; + if ( markDragX > m ) markDragX = m; + update(); + updateCellWidth(); + } +} + +/* + Returns the currently set line length limit, or -1 if there is + no limit (this is the default). + + \sa setMaxLineLength() +*/ +int QtMultiLineEdit::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. + + \sa maxLines(), numLines() +*/ +void QtMultiLineEdit::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(); + update(); + } +} + +/* + \sa setMaxLines() +*/ +int QtMultiLineEdit::maxLines() const +{ + return d->maxlines; +} + +/* + Sets the horizontal margin to \a m. + + \sa hMargin() +*/ +void QtMultiLineEdit::setHMargin(int m) +{ + if ( m != d->lr_marg ) { + d->lr_marg = m; + updateCellWidth(); + update(); + } +} + +/* + + \sa setHMargin() +*/ +int QtMultiLineEdit::hMargin() const +{ + return d->lr_marg; +} + +/* + Marks the text starting at \a row_from, \a col_from and ending + at \a row_to, \a col_to. +*/ +void QtMultiLineEdit::setSelection( 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() +*/ +void QtMultiLineEdit::cursorWordForward( bool mark ) +{ + int x = cursorX; + int y = cursorY; + + if ( x == lineLength( y ) || textLine(y).at(x).isSpace() ) { + while ( x < lineLength( y ) && textLine(y).at(x).isSpace() ) + ++x; + if ( x == lineLength( y ) ) { + if ( y < (int)contents->count() - 1) { + ++y; + x = 0; + while ( x < lineLength( y ) && textLine(y).at(x).isSpace() ) + ++x; + } + } + } + else { + while ( x < lineLength( y ) && !textLine(y).at(x).isSpace() ) + ++x; + int xspace = x; + while ( xspace < lineLength( y ) && textLine(y).at(xspace).isSpace() ) + ++xspace; + if ( xspace < lineLength( y ) ) + x = xspace; + } + cursorOn = TRUE; + int oldY = cursorY; + setCursorPosition( y, x, mark ); + if ( oldY != cursorY ) + updateCell( oldY, 0, FALSE ); + updateCell( cursorY, 0, FALSE ); + d->blinkTimer->start( QApplication::cursorFlashTime() / 2, FALSE ); +} + +/* + Moves the cursor one word to the left. If \a mark is TRUE, the text + is marked. + \sa cursorWordForward() +*/ +void QtMultiLineEdit::cursorWordBackward( bool mark ) +{ + int x = cursorX; + int y = cursorY; + + while ( x > 0 && textLine(y).at(x-1).isSpace() ) + --x; + + if ( x == 0 ) { + if ( y > 0 ) { + --y; + x = lineLength( y ); + while ( x > 0 && textLine(y).at(x-1).isSpace() ) + --x; + } + } + else { + while ( x > 0 && !textLine(y).at(x-1).isSpace() ) + --x; + } + cursorOn = TRUE; + int oldY = cursorY; + setCursorPosition( y, x, mark ); + if ( oldY != cursorY ) + updateCell( oldY, 0, FALSE ); + updateCell( cursorY, 0, FALSE ); + d->blinkTimer->start( QApplication::cursorFlashTime() / 2, FALSE ); +} + +#define DO_BREAK {doBreak = TRUE; if ( lastSpace > a ) { \ +i = lastSpace; \ +linew = lastw; \ +} \ +else \ +i = QMAX( a, i-1 );} + +void QtMultiLineEdit::wrapLine( int line, int removed ) +{ + QtMultiLineEditRow* 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' && d->align == Qt::AlignLeft ) { + if ( tabDist<0 ) + tabDist = tabStopDist(fm); + linew = ( linew/tabDist + 1 ) * tabDist; + } else if ( s[i] != '\n' ) { + char c = 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 ( 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 + } + } + } + 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; + if ( r->w > w ) + w = r->w; + if ( cursorY > l ) + ++cursorY; + else if ( cursorY == line && cursorX >=a && cursorX <= i + (doBreak?1:0)) { + cursorY = 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 QtMultiLineEditRow( 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; + } + if ( cursorY == line && cursorX >= a ) { + cursorY = l; + cursorX -= a; + } + if ( markAnchorY == line && markAnchorX >= a ) { + markAnchorY = l; + markAnchorX -= a; + } + if ( r->w > w ) + w = r->w; + + setWidth( QMAX( maxLineWidth(), w ) ); + bool oldAuto = autoUpdate(); + setAutoUpdate( FALSE ); + (void)setNumRowsAndTruncate(); + setAutoUpdate( oldAuto ); + + yPos += (nlines+1) * cellHeight(); + int sh = (nlines-removed) * cellHeight(); + if ( autoUpdate() ) { + if ( sh && yPos >= contentsRect().top() && yPos < contentsRect().bottom() ) { + int h = contentsRect().bottom() - yPos + 1; + if ( d->maxlines >= 0 ) { + int maxy; + if ( rowYPos( d->maxlines-1, &maxy ) ) { + maxy += cellHeight(); + if ( maxy < contentsRect().bottom() && maxy > yPos ) + h = maxy - yPos + 1; + } + } + QWidget::scroll( 0, sh, QRect( contentsRect().left(), yPos, + contentsRect().width(), + h ) ); + } + for (int ul = 0; ul <= nlines; ++ul ) + updateCell( line + ul, 0, FALSE ); + } +} + +void QtMultiLineEdit::rebreakParagraph( int line, int removed ) +{ + QtMultiLineEditRow* r = contents->at( line ); + if ( WORD_WRAP ) { + QtMultiLineEditRow* other = 0; + while (line < int(contents->count())-1 && !r->newline ) { + other = contents->at( line + 1 ); + if ( cursorY > line ) { + --cursorY; + 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; + contents->remove( line + 1 ); + ++removed; + } + } + wrapLine( line, removed ); +} + +void QtMultiLineEdit::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 QtMultiLineEdit::WordWrap + + This enum describes the multiline edit's word wrap mode. + + The following values are valid: + \list + \i NoWrap - no word wrap at all. + \i WidgetWidth - word wrap depending on the current + width of the editor widget + \i FixedPixelWidth - wrap according to a fix amount + of pixels ( see wrapColumnOrWidth() ) + \i 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. + \endlist + + \sa setWordWrap() +*/ + +/* + Sets the word wrap mode to \a mode. + + */ +void QtMultiLineEdit::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() + */ +QtMultiLineEdit::WordWrap QtMultiLineEdit::wordWrap() const +{ + return d->wordwrap; +} + +/* + Sets the wrap column or wrap width to \a value, depending on the + word wrap mode. + + \sa setWordWrap() + */ +void QtMultiLineEdit::setWrapColumnOrWidth( int value ) +{ + if ( value == d->wrapcol ) + return; + d->wrapcol = value; + if ( wordWrap() != NoWrap ) + setText( text() ); +} + +/* + */ +int QtMultiLineEdit::wrapColumnOrWidth() const +{ + return d->wrapcol; +} + + +/* \enum QtMultiLineEdit::WrapPolicy + + Defines where text can be wrapped in word wrap mode. + + The following values are valid: + \list + \i AtWhiteSpace - break only after whitespace + \i Anywhere - break anywhere + \endlist + + \sa setWrapPolicy() +*/ + +/* + Sets the wrap \a policy, i.e. where text can be wrapped in word wrap + mode. + + \sa setWordWrap(), wrapPolicy() + */ +void QtMultiLineEdit::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() + */ +QtMultiLineEdit::WrapPolicy QtMultiLineEdit::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 QtMultiLineEdit::isEndOfParagraph( int row ) const +{ + return contents->at( row )->newline; +} + +int QtMultiLineEdit::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 QtMultiLineEdit::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 QtMultiLineEdit::processCmd( QtMultiLineEditCommand* cmd, bool undo) +{ + QDelTextCmd* delcmd = (QDelTextCmd*) cmd; + bool ins = TRUE; + if (cmd->type() == QtMultiLineEditCommand::Delete ) + ins = undo; + else if (cmd->type() == QtMultiLineEditCommand::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 QtMultiLineEdit::undo() +{ + if ( d->undoList.isEmpty() || isReadOnly() ) + return; + textDirty = FALSE; + int macroLevel = 0; + bool before = d->undo; + d->undo = FALSE; + do { + QtMultiLineEditCommand *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 ( textDirty ) + emit textChanged(); + textDirty = FALSE; +} + +/* + Redoes the last text operation. + */ +void QtMultiLineEdit::redo() +{ + if ( d->redoList.isEmpty() || isReadOnly() ) + return; + textDirty = FALSE; + int macroLevel = 0; + bool before = d->undo; + d->undo = FALSE; + do { + QtMultiLineEditCommand *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 ( textDirty ) + emit textChanged(); + textDirty = FALSE; +} + +/* + Inserts \a s at line number \a line, after character number \a col + in the line. + If \a s contains newline characters, new lines are inserted. + If \a mark is TRUE the inserted text is selected. + + 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 QtMultiLineEdit::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 QtMultiLineEdit::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 QtMultiLineEdit::killLine() +{ + if ( d->undo ) { + d->undo = FALSE; + int curY, curX; + cursorPosition( &curY, &curX ); + int offset = positionToOffsetInternal( curY, curX ); + QtMultiLineEditRow* 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 QtMultiLineEdit::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 ); + QtMultiLineEditRow* 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 QtMultiLineEdit::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 QtMultiLineEdit::isUndoEnabled() const +{ + return d->undo; +} + + +/* + Sets the maximum number of operations that can be stored on the undo + stack to \a depth. + + \sa undoDepth() + */ +void QtMultiLineEdit::setUndoDepth( int depth ) +{ + d->undodepth = depth; +} + + +/* + */ +int QtMultiLineEdit::undoDepth() const +{ + return d->undodepth; +} + +void QtMultiLineEdit::blinkTimerTimeout() +{ + cursorOn = !cursorOn; + updateCell( cursorY, 0, FALSE ); +} + +void QtMultiLineEdit::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 QtMultiLineEdit::dndTimeout() +{ +#ifndef QT_NO_DRAGANDDROP + doDrag(); +#endif +} + +int QtMultiLineEdit::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 ) { + cursorY--; + cursorX = contents->at(cursorY)->s.length(); + } + n--; + r++; + } + setNumRows( n ); + return r; +} + +/* \reimp +*/ +bool QtMultiLineEdit::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_Left: + case Key_Right: + case Key_Up: + case Key_Down: + case Key_Home: + case Key_End: + ke->accept(); + default: + break; + } + } else { + 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 QtMultiLineEdit::focusNextPrevChild( bool ) +{ + return FALSE; +} + +#endif |