author | leseb <leseb> | 2002-07-14 21:21:35 (UTC) |
---|---|---|
committer | leseb <leseb> | 2002-07-14 21:21:35 (UTC) |
commit | 4feeec8b5b41cfd3d13274411f515524f687da09 (patch) (side-by-side diff) | |
tree | 002bbfb9997713e5d5975855d3cfbba7a71b9104 /noncore/apps/opie-write/qrichtext.cpp | |
parent | bdef9cf23ced569a9bc80c1d4f25d85861273b4a (diff) | |
download | opie-4feeec8b5b41cfd3d13274411f515524f687da09.zip opie-4feeec8b5b41cfd3d13274411f515524f687da09.tar.gz opie-4feeec8b5b41cfd3d13274411f515524f687da09.tar.bz2 |
opie-write first draft
Diffstat (limited to 'noncore/apps/opie-write/qrichtext.cpp') (more/less context) (ignore whitespace changes)
-rw-r--r-- | noncore/apps/opie-write/qrichtext.cpp | 8085 |
1 files changed, 8085 insertions, 0 deletions
diff --git a/noncore/apps/opie-write/qrichtext.cpp b/noncore/apps/opie-write/qrichtext.cpp new file mode 100644 index 0000000..7901000 --- a/dev/null +++ b/noncore/apps/opie-write/qrichtext.cpp @@ -0,0 +1,8085 @@ +/**************************************************************************** +** $Id$ +** +** Implementation of the internal Qt classes dealing with rich text +** +** Created : 990101 +** +** Copyright (C) 1992-2000 Trolltech AS. All rights reserved. +** +** This file is part of the kernel module of the Qt GUI Toolkit. +** +** This file may be distributed under the terms of the Q Public License +** as defined by Trolltech AS of Norway and appearing in the file +** LICENSE.QPL included in the packaging of this file. +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition +** licenses may use this file in accordance with the Qt Commercial License +** Agreement provided with the Software. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for +** information about Qt Commercial License Agreements. +** See http://www.trolltech.com/qpl/ for QPL licensing information. +** See http://www.trolltech.com/gpl/ for GPL licensing information. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#include "qrichtext_p.h" + +#include "qstringlist.h" +#include "qfont.h" +#include "qtextstream.h" +#include "qfile.h" +#include "qregexp.h" +#include "qapplication.h" +#include "qclipboard.h" +#include "qmap.h" +#include "qfileinfo.h" +#include "qstylesheet.h" +#include "qmime.h" +#include "qregexp.h" +#include "qimage.h" +#include "qdragobject.h" +#include "qpaintdevicemetrics.h" +#include "qpainter.h" +#include "qdrawutil.h" +#include "qcursor.h" +#include "qstack.h" +#include "qstyle.h" +#include "qcomplextext_p.h" +#include "qcleanuphandler.h" + +#include <stdlib.h> + +using namespace Qt3; + +//#define PARSER_DEBUG +//#define DEBUG_COLLECTION// ---> also in qrichtext_p.h +//#define DEBUG_TABLE_RENDERING + +static QTextFormatCollection *qFormatCollection = 0; + +const int QStyleSheetItem_WhiteSpaceNoCompression = 3; // ### belongs in QStyleSheetItem, fix 3.1 +const int QStyleSheetItem_WhiteSpaceNormalWithNewlines = 4; // ### belongs in QStyleSheetItem, fix 3.1 + +const int border_tolerance = 2; + +#if defined(PARSER_DEBUG) +static QString debug_indent; +#endif + +#ifdef Q_WS_WIN +#include "qt_windows.h" +#endif + +static inline bool is_printer( QPainter *p ) +{ + if ( !p || !p->device() ) + return FALSE; + return p->device()->devType() == QInternal::Printer; +} + +static inline int scale( int value, QPainter *painter ) +{ + if ( is_printer( painter ) ) { + QPaintDeviceMetrics metrics( painter->device() ); +#if defined(Q_WS_X11) + value = value * metrics.logicalDpiY() / QPaintDevice::x11AppDpiY(); +#elif defined (Q_WS_WIN) + HDC hdc = GetDC( 0 ); + int gdc = GetDeviceCaps( hdc, LOGPIXELSY ); + if ( gdc ) + value = value * metrics.logicalDpiY() / gdc; + ReleaseDC( 0, hdc ); +#elif defined (Q_WS_MAC) + value = value * metrics.logicalDpiY() / 75; // ##### FIXME +#elif defined (Q_WS_QWS) + value = value * metrics.logicalDpiY() / 75; +#endif + } + return value; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +void QTextCommandHistory::addCommand( QTextCommand *cmd ) +{ + if ( current < (int)history.count() - 1 ) { + QPtrList<QTextCommand> commands; + commands.setAutoDelete( FALSE ); + + for( int i = 0; i <= current; ++i ) { + commands.insert( i, history.at( 0 ) ); + history.take( 0 ); + } + + commands.append( cmd ); + history.clear(); + history = commands; + history.setAutoDelete( TRUE ); + } else { + history.append( cmd ); + } + + if ( (int)history.count() > steps ) + history.removeFirst(); + else + ++current; +} + +QTextCursor *QTextCommandHistory::undo( QTextCursor *c ) +{ + if ( current > -1 ) { + QTextCursor *c2 = history.at( current )->unexecute( c ); + --current; + return c2; + } + return 0; +} + +QTextCursor *QTextCommandHistory::redo( QTextCursor *c ) +{ + if ( current > -1 ) { + if ( current < (int)history.count() - 1 ) { + ++current; + return history.at( current )->execute( c ); + } + } else { + if ( history.count() > 0 ) { + ++current; + return history.at( current )->execute( c ); + } + } + return 0; +} + +bool QTextCommandHistory::isUndoAvailable() +{ + return current > -1; +} + +bool QTextCommandHistory::isRedoAvailable() +{ + return current > -1 && current < (int)history.count() - 1 || current == -1 && history.count() > 0; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextDeleteCommand::QTextDeleteCommand( QTextDocument *d, int i, int idx, const QMemArray<QTextStringChar> &str, + const QValueList< QPtrVector<QStyleSheetItem> > &os, + const QValueList<QStyleSheetItem::ListStyle> &ols, + const QMemArray<int> &oas) + : QTextCommand( d ), id( i ), index( idx ), parag( 0 ), text( str ), oldStyles( os ), oldListStyles( ols ), oldAligns( oas ) +{ + for ( int j = 0; j < (int)text.size(); ++j ) { + if ( text[ j ].format() ) + text[ j ].format()->addRef(); + } +} + +QTextDeleteCommand::QTextDeleteCommand( QTextParag *p, int idx, const QMemArray<QTextStringChar> &str ) + : QTextCommand( 0 ), id( -1 ), index( idx ), parag( p ), text( str ) +{ + for ( int i = 0; i < (int)text.size(); ++i ) { + if ( text[ i ].format() ) + text[ i ].format()->addRef(); + } +} + +QTextDeleteCommand::~QTextDeleteCommand() +{ + for ( int i = 0; i < (int)text.size(); ++i ) { + if ( text[ i ].format() ) + text[ i ].format()->removeRef(); + } + text.resize( 0 ); +} + +QTextCursor *QTextDeleteCommand::execute( QTextCursor *c ) +{ + QTextParag *s = doc ? doc->paragAt( id ) : parag; + if ( !s ) { + qWarning( "can't locate parag at %d, last parag: %d", id, doc->lastParag()->paragId() ); + return 0; + } + + cursor.setParag( s ); + cursor.setIndex( index ); + int len = text.size(); + if ( c ) + *c = cursor; + if ( doc ) { + doc->setSelectionStart( QTextDocument::Temp, &cursor ); + for ( int i = 0; i < len; ++i ) + cursor.gotoNextLetter(); + doc->setSelectionEnd( QTextDocument::Temp, &cursor ); + doc->removeSelectedText( QTextDocument::Temp, &cursor ); + if ( c ) + *c = cursor; + } else { + s->remove( index, len ); + } + + return c; +} + +QTextCursor *QTextDeleteCommand::unexecute( QTextCursor *c ) +{ + QTextParag *s = doc ? doc->paragAt( id ) : parag; + if ( !s ) { + qWarning( "can't locate parag at %d, last parag: %d", id, doc->lastParag()->paragId() ); + return 0; + } + + cursor.setParag( s ); + cursor.setIndex( index ); + QString str = QTextString::toString( text ); + cursor.insert( str, TRUE, &text ); + cursor.setParag( s ); + cursor.setIndex( index ); + if ( c ) { + c->setParag( s ); + c->setIndex( index ); + for ( int i = 0; i < (int)text.size(); ++i ) + c->gotoNextLetter(); + } + + QValueList< QPtrVector<QStyleSheetItem> >::Iterator it = oldStyles.begin(); + QValueList<QStyleSheetItem::ListStyle>::Iterator lit = oldListStyles.begin(); + int i = 0; + QTextParag *p = s; + bool end = FALSE; + while ( p ) { + if ( it != oldStyles.end() ) + p->setStyleSheetItems( *it ); + else + end = TRUE; + if ( lit != oldListStyles.end() ) + p->setListStyle( *lit ); + else + end = TRUE; + if ( i < (int)oldAligns.size() ) + p->setAlignment( oldAligns.at( i ) ); + else + end = TRUE; + if ( end ) + break; + p = p->next(); + ++it; + ++lit; + ++i; + } + + s = cursor.parag(); + while ( s ) { + s->format(); + s->setChanged( TRUE ); + if ( s == c->parag() ) + break; + s = s->next(); + } + + return &cursor; +} + +QTextFormatCommand::QTextFormatCommand( QTextDocument *d, int sid, int sidx, int eid, int eidx, + const QMemArray<QTextStringChar> &old, QTextFormat *f, int fl ) + : QTextCommand( d ), startId( sid ), startIndex( sidx ), endId( eid ), endIndex( eidx ), format( f ), oldFormats( old ), flags( fl ) +{ + format = d->formatCollection()->format( f ); + for ( int j = 0; j < (int)oldFormats.size(); ++j ) { + if ( oldFormats[ j ].format() ) + oldFormats[ j ].format()->addRef(); + } +} + +QTextFormatCommand::~QTextFormatCommand() +{ + format->removeRef(); + for ( int j = 0; j < (int)oldFormats.size(); ++j ) { + if ( oldFormats[ j ].format() ) + oldFormats[ j ].format()->removeRef(); + } +} + +QTextCursor *QTextFormatCommand::execute( QTextCursor *c ) +{ + QTextParag *sp = doc->paragAt( startId ); + QTextParag *ep = doc->paragAt( endId ); + if ( !sp || !ep ) + return c; + + QTextCursor start( doc ); + start.setParag( sp ); + start.setIndex( startIndex ); + QTextCursor end( doc ); + end.setParag( ep ); + end.setIndex( endIndex ); + + doc->setSelectionStart( QTextDocument::Temp, &start ); + doc->setSelectionEnd( QTextDocument::Temp, &end ); + doc->setFormat( QTextDocument::Temp, format, flags ); + doc->removeSelection( QTextDocument::Temp ); + if ( endIndex == ep->length() ) + end.gotoLeft(); + *c = end; + return c; +} + +QTextCursor *QTextFormatCommand::unexecute( QTextCursor *c ) +{ + QTextParag *sp = doc->paragAt( startId ); + QTextParag *ep = doc->paragAt( endId ); + if ( !sp || !ep ) + return 0; + + int idx = startIndex; + int fIndex = 0; + for ( ;; ) { + if ( oldFormats.at( fIndex ).c == '\n' ) { + if ( idx > 0 ) { + if ( idx < sp->length() && fIndex > 0 ) + sp->setFormat( idx, 1, oldFormats.at( fIndex - 1 ).format() ); + if ( sp == ep ) + break; + sp = sp->next(); + idx = 0; + } + fIndex++; + } + if ( oldFormats.at( fIndex ).format() ) + sp->setFormat( idx, 1, oldFormats.at( fIndex ).format() ); + idx++; + fIndex++; + if ( fIndex >= (int)oldFormats.size() ) + break; + if ( idx >= sp->length() ) { + if ( sp == ep ) + break; + sp = sp->next(); + idx = 0; + } + } + + QTextCursor end( doc ); + end.setParag( ep ); + end.setIndex( endIndex ); + if ( endIndex == ep->length() ) + end.gotoLeft(); + *c = end; + return c; +} + +QTextAlignmentCommand::QTextAlignmentCommand( QTextDocument *d, int fParag, int lParag, int na, const QMemArray<int> &oa ) + : QTextCommand( d ), firstParag( fParag ), lastParag( lParag ), newAlign( na ), oldAligns( oa ) +{ +} + +QTextCursor *QTextAlignmentCommand::execute( QTextCursor *c ) +{ + QTextParag *p = doc->paragAt( firstParag ); + if ( !p ) + return c; + while ( p ) { + p->setAlignment( newAlign ); + if ( p->paragId() == lastParag ) + break; + p = p->next(); + } + return c; +} + +QTextCursor *QTextAlignmentCommand::unexecute( QTextCursor *c ) +{ + QTextParag *p = doc->paragAt( firstParag ); + if ( !p ) + return c; + int i = 0; + while ( p ) { + if ( i < (int)oldAligns.size() ) + p->setAlignment( oldAligns.at( i ) ); + if ( p->paragId() == lastParag ) + break; + p = p->next(); + ++i; + } + return c; +} + +QTextParagTypeCommand::QTextParagTypeCommand( QTextDocument *d, int fParag, int lParag, bool l, + QStyleSheetItem::ListStyle s, const QValueList< QPtrVector<QStyleSheetItem> > &os, + const QValueList<QStyleSheetItem::ListStyle> &ols ) + : QTextCommand( d ), firstParag( fParag ), lastParag( lParag ), list( l ), listStyle( s ), oldStyles( os ), oldListStyles( ols ) +{ +} + +QTextCursor *QTextParagTypeCommand::execute( QTextCursor *c ) +{ + QTextParag *p = doc->paragAt( firstParag ); + if ( !p ) + return c; + while ( p ) { + p->setList( list, (int)listStyle ); + if ( p->paragId() == lastParag ) + break; + p = p->next(); + } + return c; +} + +QTextCursor *QTextParagTypeCommand::unexecute( QTextCursor *c ) +{ + QTextParag *p = doc->paragAt( firstParag ); + if ( !p ) + return c; + QValueList< QPtrVector<QStyleSheetItem> >::Iterator it = oldStyles.begin(); + QValueList<QStyleSheetItem::ListStyle>::Iterator lit = oldListStyles.begin(); + while ( p ) { + if ( it != oldStyles.end() ) + p->setStyleSheetItems( *it ); + if ( lit != oldListStyles.end() ) + p->setListStyle( *lit ); + if ( p->paragId() == lastParag ) + break; + p = p->next(); + ++it; + ++lit; + } + return c; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextCursor::QTextCursor( QTextDocument *d ) + : doc( d ), ox( 0 ), oy( 0 ) +{ + nested = FALSE; + idx = 0; + string = doc ? doc->firstParag() : 0; + tmpIndex = -1; + valid = TRUE; +} + +QTextCursor::QTextCursor() +{ +} + +QTextCursor::QTextCursor( const QTextCursor &c ) +{ + doc = c.doc; + ox = c.ox; + oy = c.oy; + nested = c.nested; + idx = c.idx; + string = c.string; + tmpIndex = c.tmpIndex; + indices = c.indices; + parags = c.parags; + xOffsets = c.xOffsets; + yOffsets = c.yOffsets; + valid = c.valid; +} + +QTextCursor &QTextCursor::operator=( const QTextCursor &c ) +{ + doc = c.doc; + ox = c.ox; + oy = c.oy; + nested = c.nested; + idx = c.idx; + string = c.string; + tmpIndex = c.tmpIndex; + indices = c.indices; + parags = c.parags; + xOffsets = c.xOffsets; + yOffsets = c.yOffsets; + valid = c.valid; + + return *this; +} + +bool QTextCursor::operator==( const QTextCursor &c ) const +{ + return doc == c.doc && string == c.string && idx == c.idx; +} + +int QTextCursor::totalOffsetX() const +{ + if ( !nested ) + return 0; + QValueStack<int>::ConstIterator xit = xOffsets.begin(); + int xoff = ox; + for ( ; xit != xOffsets.end(); ++xit ) + xoff += *xit; + return xoff; +} + +int QTextCursor::totalOffsetY() const +{ + if ( !nested ) + return 0; + QValueStack<int>::ConstIterator yit = yOffsets.begin(); + int yoff = oy; + for ( ; yit != yOffsets.end(); ++yit ) + yoff += *yit; + return yoff; +} + +void QTextCursor::gotoIntoNested( const QPoint &globalPos ) +{ + if ( !doc ) + return; + push(); + ox = 0; + int bl, y; + string->lineHeightOfChar( idx, &bl, &y ); + oy = y + string->rect().y(); + nested = TRUE; + QPoint p( globalPos.x() - offsetX(), globalPos.y() - offsetY() ); + Q_ASSERT( string->at( idx )->isCustom() ); + ox = string->at( idx )->x; + string->at( idx )->customItem()->enterAt( this, doc, string, idx, ox, oy, p ); +} + +void QTextCursor::invalidateNested() +{ + if ( nested ) { + QValueStack<QTextParag*>::Iterator it = parags.begin(); + QValueStack<int>::Iterator it2 = indices.begin(); + for ( ; it != parags.end(); ++it, ++it2 ) { + if ( *it == string ) + continue; + (*it)->invalidate( 0 ); + if ( (*it)->at( *it2 )->isCustom() ) + (*it)->at( *it2 )->customItem()->invalidate(); + } + } +} + +void QTextCursor::insert( const QString &str, bool checkNewLine, QMemArray<QTextStringChar> *formatting ) +{ + tmpIndex = -1; + bool justInsert = TRUE; + QString s( str ); +#if defined(Q_WS_WIN) + if ( checkNewLine ) + s = s.replace( QRegExp( "\\r" ), "" ); +#endif + if ( checkNewLine ) + justInsert = s.find( '\n' ) == -1; + if ( justInsert ) { + string->insert( idx, s ); + if ( formatting ) { + for ( int i = 0; i < (int)s.length(); ++i ) { + if ( formatting->at( i ).format() ) { + formatting->at( i ).format()->addRef(); + string->string()->setFormat( idx + i, formatting->at( i ).format(), TRUE ); + } + } + } + idx += s.length(); + } else { + QStringList lst = QStringList::split( '\n', s, TRUE ); + QStringList::Iterator it = lst.begin(); + int y = string->rect().y() + string->rect().height(); + int lastIndex = 0; + QTextFormat *lastFormat = 0; + for ( ; it != lst.end(); ) { + if ( it != lst.begin() ) { + splitAndInsertEmptyParag( FALSE, TRUE ); + string->setEndState( -1 ); + string->prev()->format( -1, FALSE ); + if ( lastFormat && formatting && string->prev() ) { + lastFormat->addRef(); + string->prev()->string()->setFormat( string->prev()->length() - 1, lastFormat, TRUE ); + } + } + lastFormat = 0; + QString s = *it; + ++it; + if ( !s.isEmpty() ) + string->insert( idx, s ); + else + string->invalidate( 0 ); + if ( formatting ) { + int len = s.length(); + for ( int i = 0; i < len; ++i ) { + if ( formatting->at( i + lastIndex ).format() ) { + formatting->at( i + lastIndex ).format()->addRef(); + string->string()->setFormat( i + idx, formatting->at( i + lastIndex ).format(), TRUE ); + } + } + if ( it != lst.end() ) + lastFormat = formatting->at( len + lastIndex ).format(); + ++len; + lastIndex += len; + } + + idx += s.length(); + } + string->format( -1, FALSE ); + int dy = string->rect().y() + string->rect().height() - y; + QTextParag *p = string; + p->setParagId( p->prev()->paragId() + 1 ); + p = p->next(); + while ( p ) { + p->setParagId( p->prev()->paragId() + 1 ); + p->move( dy ); + p->invalidate( 0 ); + p->setEndState( -1 ); + p = p->next(); + } + } + + int h = string->rect().height(); + string->format( -1, TRUE ); + if ( h != string->rect().height() ) + invalidateNested(); + else if ( doc && doc->parent() ) + doc->nextDoubleBuffered = TRUE; +} + +void QTextCursor::gotoLeft() +{ + if ( string->string()->isRightToLeft() ) + gotoNextLetter(); + else + gotoPreviousLetter(); +} + +void QTextCursor::gotoPreviousLetter() +{ + tmpIndex = -1; + + if ( idx > 0 ) { + idx--; + } else if ( string->prev() ) { + QTextParag *s = string->prev(); + while ( s && !s->isVisible() ) + s = s->prev(); + if ( s ) { + string = s; + idx = string->length() - 1; + } + } else { + if ( nested ) { + pop(); + processNesting( Prev ); + if ( idx == -1 ) { + pop(); + if ( idx > 0 ) { + idx--; + } else if ( string->prev() ) { + string = string->prev(); + idx = string->length() - 1; + } + } + } + } + + const QTextStringChar *tsc = string->at( idx ); + if ( tsc && tsc->isCustom() && tsc->customItem()->isNested() ) { + processNesting( EnterEnd ); + } +} + +void QTextCursor::push() +{ + indices.push( idx ); + parags.push( string ); + xOffsets.push( ox ); + yOffsets.push( oy ); + nestedStack.push( nested ); +} + +void QTextCursor::pop() +{ + if ( !doc ) + return; + idx = indices.pop(); + string = parags.pop(); + ox = xOffsets.pop(); + oy = yOffsets.pop(); + if ( doc->parent() ) + doc = doc->parent(); + nested = nestedStack.pop(); +} + +void QTextCursor::restoreState() +{ + while ( !indices.isEmpty() ) + pop(); +} + +bool QTextCursor::place( const QPoint &p, QTextParag *s, bool link ) +{ + QPoint pos( p ); + QRect r; + QTextParag *str = s; + if ( pos.y() < s->rect().y() ) + pos.setY( s->rect().y() ); + while ( s ) { + r = s->rect(); + r.setWidth( doc ? doc->width() : QWIDGETSIZE_MAX ); + if ( s->isVisible() ) + str = s; + if ( pos.y() >= r.y() && pos.y() <= r.y() + r.height() || !s->next() ) + break; + s = s->next(); + } + + if ( !s || !str ) + return FALSE; + + s = str; + + setParag( s, FALSE ); + int y = s->rect().y(); + int lines = s->lines(); + QTextStringChar *chr = 0; + int index = 0; + int i = 0; + int cy = 0; + int ch = 0; + for ( ; i < lines; ++i ) { + chr = s->lineStartOfLine( i, &index ); + cy = s->lineY( i ); + ch = s->lineHeight( i ); + if ( !chr ) + return FALSE; + if ( pos.y() >= y + cy && pos.y() <= y + cy + ch ) + break; + } + int nextLine; + if ( i < lines - 1 ) + s->lineStartOfLine( i+1, &nextLine ); + else + nextLine = s->length(); + i = index; + int x = s->rect().x(); + if ( pos.x() < x ) + pos.setX( x + 1 ); + int cw; + int curpos = s->length()-1; + int dist = 10000000; + bool inCustom = FALSE; + while ( i < nextLine ) { + chr = s->at(i); + int cpos = x + chr->x; + cw = s->string()->width( i ); + if ( chr->isCustom() && chr->customItem()->isNested() ) { + if ( pos.x() >= cpos && pos.x() <= cpos + cw && + pos.y() >= y + cy && pos.y() <= y + cy + chr->height() ) { + inCustom = TRUE; + curpos = i; + break; + } + } else { + if( chr->rightToLeft ) + cpos += cw; + int d = cpos - pos.x(); + bool dm = d < 0 ? !chr->rightToLeft : chr->rightToLeft; + if ( QABS( d ) < dist || (dist == d && dm == TRUE ) ) { + dist = QABS( d ); + if ( !link || pos.x() >= x + chr->x ) + curpos = i; + } + } + i++; + } + setIndex( curpos, FALSE ); + + if ( inCustom && doc && parag()->at( curpos )->isCustom() && parag()->at( curpos )->customItem()->isNested() ) { + QTextDocument *oldDoc = doc; + gotoIntoNested( pos ); + if ( oldDoc == doc ) + return TRUE; + QPoint p( pos.x() - offsetX(), pos.y() - offsetY() ); + if ( !place( p, document()->firstParag(), link ) ) + pop(); + } + return TRUE; +} + +void QTextCursor::processNesting( Operation op ) +{ + if ( !doc ) + return; + push(); + ox = string->at( idx )->x; + int bl, y; + string->lineHeightOfChar( idx, &bl, &y ); + oy = y + string->rect().y(); + nested = TRUE; + bool ok = FALSE; + + switch ( op ) { + case EnterBegin: + ok = string->at( idx )->customItem()->enter( this, doc, string, idx, ox, oy ); + break; + case EnterEnd: + ok = string->at( idx )->customItem()->enter( this, doc, string, idx, ox, oy, TRUE ); + break; + case Next: + ok = string->at( idx )->customItem()->next( this, doc, string, idx, ox, oy ); + break; + case Prev: + ok = string->at( idx )->customItem()->prev( this, doc, string, idx, ox, oy ); + break; + case Down: + ok = string->at( idx )->customItem()->down( this, doc, string, idx, ox, oy ); + break; + case Up: + ok = string->at( idx )->customItem()->up( this, doc, string, idx, ox, oy ); + break; + } + if ( !ok ) + pop(); +} + +void QTextCursor::gotoRight() +{ + if ( string->string()->isRightToLeft() ) + gotoPreviousLetter(); + else + gotoNextLetter(); +} + +void QTextCursor::gotoNextLetter() +{ + tmpIndex = -1; + + const QTextStringChar *tsc = string->at( idx ); + if ( tsc && tsc->isCustom() && tsc->customItem()->isNested() ) { + processNesting( EnterBegin ); + return; + } + + if ( idx < string->length() - 1 ) { + idx++; + } else if ( string->next() ) { + QTextParag *s = string->next(); + while ( s && !s->isVisible() ) + s = s->next(); + if ( s ) { + string = s; + idx = 0; + } + } else { + if ( nested ) { + pop(); + processNesting( Next ); + if ( idx == -1 ) { + pop(); + if ( idx < string->length() - 1 ) { + idx++; + } else if ( string->next() ) { + string = string->next(); + idx = 0; + } + } + } + } +} + +void QTextCursor::gotoUp() +{ + int indexOfLineStart; + int line; + QTextStringChar *c = string->lineStartOfChar( idx, &indexOfLineStart, &line ); + if ( !c ) + return; + + tmpIndex = QMAX( tmpIndex, idx - indexOfLineStart ); + if ( indexOfLineStart == 0 ) { + if ( !string->prev() ) { + if ( !nested ) + return; + pop(); + processNesting( Up ); + if ( idx == -1 ) { + pop(); + if ( !string->prev() ) + return; + idx = tmpIndex = 0; + } else { + tmpIndex = -1; + return; + } + } + QTextParag *s = string->prev(); + while ( s && !s->isVisible() ) + s = s->prev(); + if ( s ) + string = s; + int lastLine = string->lines() - 1; + if ( !string->lineStartOfLine( lastLine, &indexOfLineStart ) ) + return; + if ( indexOfLineStart + tmpIndex < string->length() ) + idx = indexOfLineStart + tmpIndex; + else + idx = string->length() - 1; + } else { + --line; + int oldIndexOfLineStart = indexOfLineStart; + if ( !string->lineStartOfLine( line, &indexOfLineStart ) ) + return; + if ( indexOfLineStart + tmpIndex < oldIndexOfLineStart ) + idx = indexOfLineStart + tmpIndex; + else + idx = oldIndexOfLineStart - 1; + } +} + +void QTextCursor::gotoDown() +{ + int indexOfLineStart; + int line; + QTextStringChar *c = string->lineStartOfChar( idx, &indexOfLineStart, &line ); + if ( !c ) + return; + + tmpIndex = QMAX( tmpIndex, idx - indexOfLineStart ); + if ( line == string->lines() - 1 ) { + if ( !string->next() ) { + if ( !nested ) + return; + pop(); + processNesting( Down ); + if ( idx == -1 ) { + pop(); + if ( !string->next() ) + return; + idx = tmpIndex = 0; + } else { + tmpIndex = -1; + return; + } + } + QTextParag *s = string->next(); + while ( s && !s->isVisible() ) + s = s->next(); + if ( s ) + string = s; + if ( !string->lineStartOfLine( 0, &indexOfLineStart ) ) + return; + int end; + if ( string->lines() == 1 ) + end = string->length(); + else + string->lineStartOfLine( 1, &end ); + if ( indexOfLineStart + tmpIndex < end ) + idx = indexOfLineStart + tmpIndex; + else + idx = end - 1; + } else { + ++line; + int end; + if ( line == string->lines() - 1 ) + end = string->length(); + else + string->lineStartOfLine( line + 1, &end ); + if ( !string->lineStartOfLine( line, &indexOfLineStart ) ) + return; + if ( indexOfLineStart + tmpIndex < end ) + idx = indexOfLineStart + tmpIndex; + else + idx = end - 1; + } +} + +void QTextCursor::gotoLineEnd() +{ + tmpIndex = -1; + int indexOfLineStart; + int line; + QTextStringChar *c = string->lineStartOfChar( idx, &indexOfLineStart, &line ); + if ( !c ) + return; + + if ( line == string->lines() - 1 ) { + idx = string->length() - 1; + } else { + c = string->lineStartOfLine( ++line, &indexOfLineStart ); + indexOfLineStart--; + idx = indexOfLineStart; + } +} + +void QTextCursor::gotoLineStart() +{ + tmpIndex = -1; + int indexOfLineStart; + int line; + QTextStringChar *c = string->lineStartOfChar( idx, &indexOfLineStart, &line ); + if ( !c ) + return; + + idx = indexOfLineStart; +} + +void QTextCursor::gotoHome() +{ + tmpIndex = -1; + if ( doc ) + string = doc->firstParag(); + idx = 0; +} + +void QTextCursor::gotoEnd() +{ + if ( doc && !doc->lastParag()->isValid() ) + return; + + tmpIndex = -1; + if ( doc ) + string = doc->lastParag(); + idx = string->length() - 1; +} + +void QTextCursor::gotoPageUp( int visibleHeight ) +{ + tmpIndex = -1; + QTextParag *s = string; + int h = visibleHeight; + int y = s->rect().y(); + while ( s ) { + if ( y - s->rect().y() >= h ) + break; + s = s->prev(); + } + + if ( !s && doc ) + s = doc->firstParag(); + + string = s; + idx = 0; +} + +void QTextCursor::gotoPageDown( int visibleHeight ) +{ + tmpIndex = -1; + QTextParag *s = string; + int h = visibleHeight; + int y = s->rect().y(); + while ( s ) { + if ( s->rect().y() - y >= h ) + break; + s = s->next(); + } + + if ( !s && doc ) { + s = doc->lastParag(); + string = s; + idx = string->length() - 1; + return; + } + + if ( !s->isValid() ) + return; + + string = s; + idx = 0; +} + +void QTextCursor::gotoWordRight() +{ + if ( string->string()->isRightToLeft() ) + gotoPreviousWord(); + else + gotoNextWord(); +} + +void QTextCursor::gotoWordLeft() +{ + if ( string->string()->isRightToLeft() ) + gotoNextWord(); + else + gotoPreviousWord(); +} + +void QTextCursor::gotoPreviousWord() +{ + gotoPreviousLetter(); + tmpIndex = -1; + QTextString *s = string->string(); + bool allowSame = FALSE; + if ( idx == ((int)s->length()-1) ) + return; + for ( int i = idx; i >= 0; --i ) { + if ( s->at( i ).c.isSpace() || s->at( i ).c == '\t' || s->at( i ).c == '.' || + s->at( i ).c == ',' || s->at( i ).c == ':' || s->at( i ).c == ';' ) { + if ( !allowSame ) + continue; + idx = i + 1; + return; + } + if ( !allowSame && !( s->at( i ).c.isSpace() || s->at( i ).c == '\t' || s->at( i ).c == '.' || + s->at( i ).c == ',' || s->at( i ).c == ':' || s->at( i ).c == ';' ) ) + allowSame = TRUE; + } + idx = 0; +} + +void QTextCursor::gotoNextWord() +{ + tmpIndex = -1; + QTextString *s = string->string(); + bool allowSame = FALSE; + for ( int i = idx; i < (int)s->length(); ++i ) { + if ( ! (s->at( i ).c.isSpace() || s->at( i ).c == '\t' || s->at( i ).c == '.' || + s->at( i ).c == ',' || s->at( i ).c == ':' || s->at( i ).c == ';') ) { + if ( !allowSame ) + continue; + idx = i; + return; + } + if ( !allowSame && ( s->at( i ).c.isSpace() || s->at( i ).c == '\t' || s->at( i ).c == '.' || + s->at( i ).c == ',' || s->at( i ).c == ':' || s->at( i ).c == ';' ) ) + allowSame = TRUE; + + } + + if ( idx < ((int)s->length()-1) ) { + gotoLineEnd(); + } else if ( string->next() ) { + QTextParag *s = string->next(); + while ( s && !s->isVisible() ) + s = s->next(); + if ( s ) { + string = s; + idx = 0; + } + } else { + gotoLineEnd(); + } +} + +bool QTextCursor::atParagStart() +{ + return idx == 0; +} + +bool QTextCursor::atParagEnd() +{ + return idx == string->length() - 1; +} + +void QTextCursor::splitAndInsertEmptyParag( bool ind, bool updateIds ) +{ + if ( !doc ) + return; + tmpIndex = -1; + QTextFormat *f = 0; + if ( doc->useFormatCollection() ) { + f = string->at( idx )->format(); + if ( idx == string->length() - 1 && idx > 0 ) + f = string->at( idx - 1 )->format(); + if ( f->isMisspelled() ) { + f->removeRef(); + f = doc->formatCollection()->format( f->font(), f->color() ); + } + } + + if ( atParagEnd() ) { + QTextParag *n = string->next(); + QTextParag *s = doc->createParag( doc, string, n, updateIds ); + if ( f ) + s->setFormat( 0, 1, f, TRUE ); + s->copyParagData( string ); + if ( ind ) { + int oi, ni; + s->indent( &oi, &ni ); + string = s; + idx = ni; + } else { + string = s; + idx = 0; + } + } else if ( atParagStart() ) { + QTextParag *p = string->prev(); + QTextParag *s = doc->createParag( doc, p, string, updateIds ); + if ( f ) + s->setFormat( 0, 1, f, TRUE ); + s->copyParagData( string ); + if ( ind ) { + s->indent(); + s->format(); + indent(); + string->format(); + } + } else { + QString str = string->string()->toString().mid( idx, 0xFFFFFF ); + QTextParag *n = string->next(); + QTextParag *s = doc->createParag( doc, string, n, updateIds ); + s->copyParagData( string ); + s->remove( 0, 1 ); + s->append( str, TRUE ); + for ( uint i = 0; i < str.length(); ++i ) { + s->setFormat( i, 1, string->at( idx + i )->format(), TRUE ); + if ( string->at( idx + i )->isCustom() ) { + QTextCustomItem * item = string->at( idx + i )->customItem(); + s->at( i )->setCustomItem( item ); + string->at( idx + i )->loseCustomItem(); + } + } + string->truncate( idx ); + if ( ind ) { + int oi, ni; + s->indent( &oi, &ni ); + string = s; + idx = ni; + } else { + string = s; + idx = 0; + } + } + + invalidateNested(); +} + +bool QTextCursor::remove() +{ + tmpIndex = -1; + if ( !atParagEnd() ) { + string->remove( idx, 1 ); + int h = string->rect().height(); + string->format( -1, TRUE ); + if ( h != string->rect().height() ) + invalidateNested(); + else if ( doc && doc->parent() ) + doc->nextDoubleBuffered = TRUE; + return FALSE; + } else if ( string->next() ) { + if ( string->length() == 1 ) { + string->next()->setPrev( string->prev() ); + if ( string->prev() ) + string->prev()->setNext( string->next() ); + QTextParag *p = string->next(); + delete string; + string = p; + string->invalidate( 0 ); + QTextParag *s = string; + while ( s ) { + s->id = s->p ? s->p->id + 1 : 0; + s->state = -1; + s->needPreProcess = TRUE; + s->changed = TRUE; + s = s->n; + } + string->format(); + } else { + string->join( string->next() ); + } + invalidateNested(); + return TRUE; + } + return FALSE; +} + +void QTextCursor::killLine() +{ + if ( atParagEnd() ) + return; + string->remove( idx, string->length() - idx - 1 ); + int h = string->rect().height(); + string->format( -1, TRUE ); + if ( h != string->rect().height() ) + invalidateNested(); + else if ( doc && doc->parent() ) + doc->nextDoubleBuffered = TRUE; +} + +void QTextCursor::indent() +{ + int oi = 0, ni = 0; + string->indent( &oi, &ni ); + if ( oi == ni ) + return; + + if ( idx >= oi ) + idx += ni - oi; + else + idx = ni; +} + +void QTextCursor::setDocument( QTextDocument *d ) +{ + doc = d; + string = d->firstParag(); + idx = 0; + nested = FALSE; + restoreState(); + tmpIndex = -1; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextDocument::QTextDocument( QTextDocument *p ) + : par( p ), parParag( 0 ), tc( 0 ), tArray( 0 ), tStopWidth( 0 ) +{ + fCollection = new QTextFormatCollection; + init(); +} + +QTextDocument::QTextDocument( QTextDocument *p, QTextFormatCollection *f ) + : par( p ), parParag( 0 ), tc( 0 ), tArray( 0 ), tStopWidth( 0 ) +{ + fCollection = f; + init(); +} + +void QTextDocument::init() +{ +#if defined(PARSER_DEBUG) + qDebug( debug_indent + "new QTextDocument (%p)", this ); +#endif + oTextValid = TRUE; + mightHaveCustomItems = FALSE; + if ( par ) + par->insertChild( this ); + pProcessor = 0; + useFC = TRUE; + pFormatter = 0; + indenter = 0; + fParag = 0; + txtFormat = Qt::AutoText; + preferRichText = FALSE; + pages = FALSE; + focusIndicator.parag = 0; + minw = 0; + wused = 0; + minwParag = curParag = 0; + align = AlignAuto; + nSelections = 1; + addMargs = FALSE; + + sheet_ = QStyleSheet::defaultSheet(); + factory_ = QMimeSourceFactory::defaultFactory(); + contxt = QString::null; + fCollection->setStyleSheet( sheet_ ); + + underlLinks = par ? par->underlLinks : TRUE; + backBrush = 0; + buf_pixmap = 0; + nextDoubleBuffered = FALSE; + + if ( par ) + withoutDoubleBuffer = par->withoutDoubleBuffer; + else + withoutDoubleBuffer = FALSE; + + lParag = fParag = createParag( this, 0, 0 ); + tmpCursor = 0; + + cx = 0; + cy = 2; + if ( par ) + cx = cy = 0; + cw = 600; + vw = 0; + flow_ = new QTextFlow; + flow_->setWidth( cw ); + + leftmargin = rightmargin = 4; + + selectionColors[ Standard ] = QApplication::palette().color( QPalette::Active, QColorGroup::Highlight ); + selectionText[ Standard ] = TRUE; + commandHistory = new QTextCommandHistory( 100 ); + tStopWidth = formatCollection()->defaultFormat()->width( 'x' ) * 8; +} + +QTextDocument::~QTextDocument() +{ + if ( par ) + par->removeChild( this ); + clear(); + delete commandHistory; + delete flow_; + if ( !par ) + delete pFormatter; + delete fCollection; + delete pProcessor; + delete buf_pixmap; + delete indenter; + delete backBrush; + if ( tArray ) + delete [] tArray; +} + +void QTextDocument::clear( bool createEmptyParag ) +{ + if ( flow_ ) + flow_->clear(); + while ( fParag ) { + QTextParag *p = fParag->next(); + delete fParag; + fParag = p; + } + fParag = lParag = 0; + if ( createEmptyParag ) + fParag = lParag = createParag( this ); + selections.clear(); +} + +int QTextDocument::widthUsed() const +{ + return wused + border_tolerance; +} + +int QTextDocument::height() const +{ + int h = 0; + if ( lParag ) + h = lParag->rect().top() + lParag->rect().height() + 1; + int fh = flow_->boundingRect().bottom(); + return QMAX( h, fh ); +} + + + +QTextParag *QTextDocument::createParag( QTextDocument *d, QTextParag *pr, QTextParag *nx, bool updateIds ) +{ + return new QTextParag( d, pr, nx, updateIds ); +} + +bool QTextDocument::setMinimumWidth( int needed, int used, QTextParag *p ) +{ + if ( needed == -1 ) { + minw = 0; + wused = 0; + p = 0; + } + if ( p == minwParag ) { + minw = needed; + emit minimumWidthChanged( minw ); + } else if ( needed > minw ) { + minw = needed; + minwParag = p; + emit minimumWidthChanged( minw ); + } + wused = QMAX( wused, used ); + wused = QMAX( wused, minw ); + cw = QMAX( minw, cw ); + return TRUE; +} + +void QTextDocument::setPlainText( const QString &text ) +{ + clear(); + preferRichText = FALSE; + oTextValid = TRUE; + oText = text; + + int lastNl = 0; + int nl = text.find( '\n' ); + if ( nl == -1 ) { + lParag = createParag( this, lParag, 0 ); + if ( !fParag ) + fParag = lParag; + QString s = text; + if ( !s.isEmpty() ) { + if ( s[ (int)s.length() - 1 ] == '\r' ) + s.remove( s.length() - 1, 1 ); + lParag->append( s ); + } + } else { + for (;;) { + lParag = createParag( this, lParag, 0 ); + if ( !fParag ) + fParag = lParag; + QString s = text.mid( lastNl, nl - lastNl ); + if ( !s.isEmpty() ) { + if ( s[ (int)s.length() - 1 ] == '\r' ) + s.remove( s.length() - 1, 1 ); + lParag->append( s ); + } + if ( nl == 0xffffff ) + break; + lastNl = nl + 1; + nl = text.find( '\n', nl + 1 ); + if ( nl == -1 ) + nl = 0xffffff; + } + } + if ( !lParag ) + lParag = fParag = createParag( this, 0, 0 ); +} + +struct Q_EXPORT QTextDocumentTag { + QTextDocumentTag(){} + QTextDocumentTag( const QString&n, const QStyleSheetItem* s, const QTextFormat& f ) + :name(n),style(s), format(f), alignment(Qt3::AlignAuto), direction(QChar::DirON),liststyle(QStyleSheetItem::ListDisc) { + wsm = QStyleSheetItem::WhiteSpaceNormal; + } + QString name; + const QStyleSheetItem* style; + QString anchorHref; + QStyleSheetItem::WhiteSpaceMode wsm; + QTextFormat format; + int alignment : 16; + int direction : 5; + QStyleSheetItem::ListStyle liststyle; + + QTextDocumentTag( const QTextDocumentTag& t ) { + name = t.name; + style = t.style; + anchorHref = t.anchorHref; + wsm = t.wsm; + format = t.format; + alignment = t.alignment; + direction = t.direction; + liststyle = t.liststyle; + } + QTextDocumentTag& operator=(const QTextDocumentTag& t) { + name = t.name; + style = t.style; + anchorHref = t.anchorHref; + wsm = t.wsm; + format = t.format; + alignment = t.alignment; + direction = t.direction; + liststyle = t.liststyle; + return *this; + } + +#if defined(Q_FULL_TEMPLATE_INSTANTIATION) + bool operator==( const QTextDocumentTag& ) const { return FALSE; } +#endif +}; + +#define NEWPAR do{ if ( !hasNewPar ) curpar = createParag( this, curpar ); \ + if ( curpar->isBr ) curpar->isBr = FALSE; \ + hasNewPar = TRUE; \ + curpar->setAlignment( curtag.alignment ); \ + curpar->setDirection( (QChar::Direction)curtag.direction ); \ + space = TRUE; \ + QPtrVector<QStyleSheetItem> vec( (uint)tags.count() + 1); \ + int i = 0; \ + for ( QValueStack<QTextDocumentTag>::Iterator it = tags.begin(); it != tags.end(); ++it ) \ + vec.insert( i++, (*it).style ); \ + vec.insert( i, curtag.style ); \ + curpar->setStyleSheetItems( vec ); }while(FALSE) + + +void QTextDocument::setRichText( const QString &text, const QString &context ) +{ + setTextFormat( Qt::RichText ); + if ( !context.isEmpty() ) + setContext( context ); + clear(); + fParag = lParag = createParag( this ); + setRichTextInternal( text ); +} + +static QStyleSheetItem::ListStyle chooseListStyle( const QStyleSheetItem *nstyle, + const QMap<QString, QString> &attr, + QStyleSheetItem::ListStyle curListStyle ) +{ + if ( nstyle->name() == "ol" || nstyle->name() == "ul" ) { + curListStyle = nstyle->listStyle(); + QMap<QString, QString>::ConstIterator it = attr.find( "type" ); + if ( it != attr.end() ) { + QString sl = *it; + if ( sl == "1" ) { + curListStyle = QStyleSheetItem::ListDecimal; + } else if ( sl == "a" ) { + curListStyle = QStyleSheetItem::ListLowerAlpha; + } else if ( sl == "A" ) { + curListStyle = QStyleSheetItem::ListUpperAlpha; + } else { + sl = sl.lower(); + if ( sl == "square" ) + curListStyle = QStyleSheetItem::ListSquare; + else if ( sl == "disc" ) + curListStyle = QStyleSheetItem::ListDisc; + else if ( sl == "circle" ) + curListStyle = QStyleSheetItem::ListCircle; + } + } + } + return curListStyle; +} + +void QTextDocument::setRichTextInternal( const QString &text ) +{ + oTextValid = TRUE; + oText = text; + QTextParag* curpar = lParag; + int pos = 0; + QValueStack<QTextDocumentTag> tags; + QTextDocumentTag initag( "", sheet_->item(""), *formatCollection()->defaultFormat() ); + QTextDocumentTag curtag = initag; + bool space = TRUE; + + const QChar* doc = text.unicode(); + int length = text.length(); + bool hasNewPar = curpar->length() <= 1; + QString lastClose; + QString anchorName; + while ( pos < length ) { + if ( hasPrefix(doc, length, pos, '<' ) ){ + if ( !hasPrefix( doc, length, pos+1, QChar('/') ) ) { + // open tag + QMap<QString, QString> attr; + bool emptyTag = FALSE; + QString tagname = parseOpenTag(doc, length, pos, attr, emptyTag); + if ( tagname.isEmpty() ) + continue; // nothing we could do with this, probably parse error + + if ( tagname == "title" ) { + QString title; + while ( pos < length ) { + if ( hasPrefix( doc, length, pos, QChar('<') ) && hasPrefix( doc, length, pos+1, QChar('/') ) && + parseCloseTag( doc, length, pos ) == "title" ) + break; + title += doc[ pos ]; + ++pos; + } + attribs.replace( "title", title ); + } + + const QStyleSheetItem* nstyle = sheet_->item(tagname); + + if ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) { +// if ( tagname == "br" ) { +// // our standard br emty-tag handling breaks +// // inside list items, we would get another +// // list item in this case. As workaround, fake +// // a new paragraph instead +// tagname = "p"; +// nstyle = sheet_->item( tagname ); +// } + if ( nstyle ) + hasNewPar = FALSE; // we want empty paragraphs in this case + } + + if ( nstyle ) { + // we might have to close some 'forgotten' tags + while ( !nstyle->allowedInContext( curtag.style ) ) { + QString msg; + msg.sprintf( "QText Warning: Document not valid ( '%s' not allowed in '%s' #%d)", + tagname.ascii(), curtag.style->name().ascii(), pos); + sheet_->error( msg ); + if ( tags.isEmpty() ) + break; + curtag = tags.pop(); + } + + // special handling for p. We do not want to nest there for HTML compatibility + if ( nstyle->displayMode() == QStyleSheetItem::DisplayBlock ) { + while ( curtag.style->name() == "p" ) { + if ( tags.isEmpty() ) + break; + curtag = tags.pop(); + } + } + + } + + QTextCustomItem* custom = 0; + // some well-known empty tags + if ( tagname == "br" ) { + emptyTag = TRUE; + hasNewPar = FALSE; + if ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) { + // when linebreaking a list item, we do not + // actually want a new list item but just a + // new line. Fake this by pushing a paragraph + // onto the stack + tags.push( curtag ); + curtag.name = tagname; + curtag.style = nstyle; + } + NEWPAR; + curpar->isBr = TRUE; + curpar->setAlignment( curtag.alignment ); + } else if ( tagname == "hr" ) { + emptyTag = TRUE; + custom = sheet_->tag( tagname, attr, contxt, *factory_ , emptyTag, this ); + NEWPAR; + } else if ( tagname == "table" ) { + QTextFormat format = curtag.format.makeTextFormat( nstyle, attr ); + curpar->setAlignment( curtag.alignment ); + custom = parseTable( attr, format, doc, length, pos, curpar ); + (void)eatSpace( doc, length, pos ); + emptyTag = TRUE; + } else if ( tagname == "qt" ) { + for ( QMap<QString, QString>::Iterator it = attr.begin(); it != attr.end(); ++it ) { + if ( it.key() == "bgcolor" ) { + QBrush *b = new QBrush( QColor( *it ) ); + setPaper( b ); + } else if ( it.key() == "background" ) { + QImage img; + const QMimeSource* m = factory_->data( *it, contxt ); + if ( !m ) { + qWarning("QRichText: no mimesource for %s", (*it).latin1() ); + } else { + if ( !QImageDrag::decode( m, img ) ) { + qWarning("QTextImage: cannot decode %s", (*it).latin1() ); + } + } + if ( !img.isNull() ) { + QPixmap pm; + pm.convertFromImage( img ); + QBrush *b = new QBrush( QColor(), pm ); + setPaper( b ); + } + } else if ( it.key() == "text" ) { + QColor c( *it ); + if ( formatCollection()->defaultFormat()->color() != c ) { + QDict<QTextFormat> formats = formatCollection()->dict(); + QDictIterator<QTextFormat> it( formats ); + while ( it.current() ) { + if ( it.current() == formatCollection()->defaultFormat() ) { + ++it; + continue; + } + it.current()->setColor( c ); + ++it; + } + formatCollection()->defaultFormat()->setColor( c ); + curtag.format.setColor( c ); + } + } else if ( it.key() == "link" ) { + linkColor = QColor( *it ); + } else if ( it.key() == "title" ) { + attribs.replace( it.key(), *it ); + } + } + } else { + custom = sheet_->tag( tagname, attr, contxt, *factory_ , emptyTag, this ); + } + + if ( !nstyle && !custom ) // we have no clue what this tag could be, ignore it + continue; + + if ( custom ) { + int index = curpar->length() - 1; + if ( index < 0 ) + index = 0; + QTextFormat format = curtag.format.makeTextFormat( nstyle, attr ); + curpar->append( QChar('*') ); + curpar->setFormat( index, 1, &format ); + curpar->at( index )->setCustomItem( custom ); + if ( !curtag.anchorHref.isEmpty() ) + curpar->at(index)->setAnchor( QString::null, curtag.anchorHref ); + if ( !anchorName.isEmpty() ) { + curpar->at(index)->setAnchor( anchorName, curpar->at(index)->anchorHref() ); + anchorName = QString::null; + } + registerCustomItem( custom, curpar ); + hasNewPar = FALSE; + } else if ( !emptyTag ) { + // ignore whitespace for inline elements if there was already one + if ( nstyle->whiteSpaceMode() == QStyleSheetItem::WhiteSpaceNormal + && ( space || nstyle->displayMode() != QStyleSheetItem::DisplayInline ) ) + eatSpace( doc, length, pos ); + + // if we do nesting, push curtag on the stack, + // otherwise reinint curag. + if ( nstyle != curtag.style || nstyle->selfNesting() ) { + tags.push( curtag ); + } else { + if ( !tags.isEmpty() ) + curtag = tags.top(); + else + curtag = initag; + } + + const QStyleSheetItem* ostyle = curtag.style; + + curtag.name = tagname; + curtag.style = nstyle; + curtag.name = tagname; + curtag.style = nstyle; + if ( nstyle->whiteSpaceMode() != QStyleSheetItem::WhiteSpaceNormal ) + curtag.wsm = nstyle->whiteSpaceMode(); + curtag.liststyle = chooseListStyle( nstyle, attr, curtag.liststyle ); + curtag.format = curtag.format.makeTextFormat( nstyle, attr ); + if ( nstyle->isAnchor() ) { + if ( !anchorName.isEmpty() ) + anchorName += "#" + attr["name"]; + else + anchorName = attr["name"]; + curtag.anchorHref = attr["href"]; + } + + if ( nstyle->alignment() != QStyleSheetItem::Undefined ) + curtag.alignment = nstyle->alignment(); + + if ( ostyle->displayMode() == QStyleSheetItem::DisplayListItem && + curpar->length() <= 1 + && nstyle->displayMode() == QStyleSheetItem::DisplayBlock ) { + // do not do anything, we reuse the paragraph we have + } else if ( nstyle->displayMode() != QStyleSheetItem::DisplayInline && nstyle->displayMode() != QStyleSheetItem::DisplayNone ) { + NEWPAR; + } + + if ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) { + curpar->setListStyle( curtag.liststyle ); + if ( attr.find( "value" ) != attr.end() ) + curpar->setListValue( (*attr.find( "value" )).toInt() ); + } + + if ( nstyle->displayMode() != QStyleSheetItem::DisplayInline ) + curpar->setFormat( &curtag.format ); + + if ( attr.contains( "align" ) && + ( curtag.name == "p" || + curtag.name == "div" || + curtag.name == "li" || + curtag.name[ 0 ] == 'h' ) ) { + QString align = attr["align"]; + if ( align == "center" ) + curtag.alignment = Qt::AlignCenter; + else if ( align == "right" ) + curtag.alignment = Qt::AlignRight; + else if ( align == "justify" ) + curtag.alignment = Qt3::AlignJustify; + } + if ( attr.contains( "dir" ) && + ( curtag.name == "p" || + curtag.name == "div" || + curtag.name == "li" || + curtag.name[ 0 ] == 'h' ) ) { + QString dir = attr["dir"]; + if ( dir == "rtl" ) + curtag.direction = QChar::DirR; + else if ( dir == "ltr" ) + curtag.direction = QChar::DirL; + } + if ( nstyle->displayMode() != QStyleSheetItem::DisplayInline ) { + curpar->setAlignment( curtag.alignment ); + curpar->setDirection( (QChar::Direction)curtag.direction ); + } + } + } else { + QString tagname = parseCloseTag( doc, length, pos ); + lastClose = tagname; + if ( tagname.isEmpty() ) + continue; // nothing we could do with this, probably parse error + if ( !sheet_->item( tagname ) ) // ignore unknown tags + continue; + + + // we close a block item. Since the text may continue, we need to have a new paragraph + bool needNewPar = curtag.style->displayMode() == QStyleSheetItem::DisplayBlock; + + if ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) { + needNewPar = TRUE; + hasNewPar = FALSE; // we want empty paragraphs in this case + } + + // html slopiness: handle unbalanched tag closing + while ( curtag.name != tagname ) { + QString msg; + msg.sprintf( "QText Warning: Document not valid ( '%s' not closed before '%s' #%d)", + curtag.name.ascii(), tagname.ascii(), pos); + sheet_->error( msg ); + if ( tags.isEmpty() ) + break; + curtag = tags.pop(); + } + + + // close the tag + if ( !tags.isEmpty() ) + curtag = tags.pop(); + else + curtag = initag; + + if ( needNewPar ) { + if ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) { + tags.push( curtag ); + curtag.name = "p"; + curtag.style = sheet_->item( curtag.name ); // a list item continues, use p for that + } + NEWPAR; + } + } + } else { + // normal contents + QString s; + QChar c; + while ( pos < length && !hasPrefix(doc, length, pos, QChar('<') ) ){ + QStyleSheetItem::WhiteSpaceMode wsm = curtag.wsm; + if ( s.length() > 4096 ) + wsm = (QStyleSheetItem::WhiteSpaceMode)QStyleSheetItem_WhiteSpaceNormalWithNewlines; + + c = parseChar( doc, length, pos, wsm ); + + if ( c == '\n' ) // happens only in whitespacepre-mode or with WhiteSpaceNormalWithNewlines. + break; // we want a new line in this case + + bool c_isSpace = c.isSpace() && c.unicode() != 0x00a0U && + curtag.wsm != QStyleSheetItem_WhiteSpaceNoCompression; + + if ( curtag.wsm == QStyleSheetItem::WhiteSpaceNormal && c_isSpace && space ) + continue; + if ( c == '\r' ) + continue; + space = c_isSpace; + s += c; + } + if ( !s.isEmpty() && curtag.style->displayMode() != QStyleSheetItem::DisplayNone ) { + hasNewPar = FALSE; + int index = curpar->length() - 1; + if ( index < 0 ) + index = 0; + curpar->append( s ); + QTextFormat* f = formatCollection()->format( &curtag.format ); + curpar->setFormat( index, s.length(), f, FALSE ); // do not use collection because we have done that already + f->ref += s.length() -1; // that what friends are for... + if ( !curtag.anchorHref.isEmpty() ) { + for ( int i = 0; i < int(s.length()); i++ ) + curpar->at(index + i)->setAnchor( QString::null, curtag.anchorHref ); + } + if ( !anchorName.isEmpty() ) { + curpar->at(index)->setAnchor( anchorName, curpar->at(index)->anchorHref() ); + anchorName = QString::null; + } + } + if ( c == '\n' ) { // happens in WhiteSpacePre mode + hasNewPar = FALSE; + tags.push( curtag ); + NEWPAR; + curtag = tags.pop(); + } + } + } + + if ( hasNewPar && curpar != fParag ) { + // cleanup unused last paragraphs + curpar = curpar->p; + delete curpar->n; + } + + if ( !anchorName.isEmpty() ) { + curpar->at(curpar->length() - 1)->setAnchor( anchorName, curpar->at( curpar->length() - 1 )->anchorHref() ); + anchorName = QString::null; + } +} + +void QTextDocument::setText( const QString &text, const QString &context ) +{ + focusIndicator.parag = 0; + selections.clear(); + if ( txtFormat == Qt::AutoText && QStyleSheet::mightBeRichText( text ) || + txtFormat == Qt::RichText ) + setRichText( text, context ); + else + setPlainText( text ); +} + +QString QTextDocument::plainText( QTextParag *p ) const +{ + if ( !p ) { + QString buffer; + QString s; + QTextParag *p = fParag; + while ( p ) { + if ( !p->mightHaveCustomItems ) { + s = p->string()->toString(); + } else { + for ( int i = 0; i < p->length() - 1; ++i ) { + if ( p->at( i )->isCustom() ) { + if ( p->at( i )->customItem()->isNested() ) { + s += "\n"; + QTextTable *t = (QTextTable*)p->at( i )->customItem(); + QPtrList<QTextTableCell> cells = t->tableCells(); + for ( QTextTableCell *c = cells.first(); c; c = cells.next() ) + s += c->richText()->plainText() + "\n"; + s += "\n"; + } + } else { + s += p->at( i )->c; + } + } + } + s.remove( s.length() - 1, 1 ); + if ( p->next() ) + s += "\n"; + buffer += s; + p = p->next(); + } + return buffer; + } else { + return p->string()->toString(); + } +} + +static QString align_to_string( const QString &tag, int a ) +{ + if ( tag == "p" || tag == "li" || ( tag[0] == 'h' && tag[1].isDigit() ) ) { + if ( a & Qt::AlignRight ) + return " align=\"right\""; + if ( a & Qt::AlignCenter ) + return " align=\"center\""; + if ( a & Qt3::AlignJustify ) + return " align=\"justify\""; + } + return ""; +} + +static QString direction_to_string( const QString &tag, int d ) +{ + if ( d != QChar::DirON && + ( tag == "p" || tag == "div" || tag == "li" || ( tag[0] == 'h' && tag[1].isDigit() ) ) ) + return ( d == QChar::DirL? " dir=\"ltr\"" : " dir=\"rtl\"" ); + return ""; +} + +QString QTextDocument::richText( QTextParag *p ) const +{ + QString s,n; + if ( !p ) { + p = fParag; + QPtrVector<QStyleSheetItem> lastItems, items; + while ( p ) { + items = p->styleSheetItems(); + if ( items.size() ) { + QStyleSheetItem *item = items[ items.size() - 1 ]; + items.resize( items.size() - 1 ); + if ( items.size() > lastItems.size() ) { + for ( int i = lastItems.size(); i < (int)items.size(); ++i ) { + n = items[i]->name(); + if ( n.isEmpty() || n == "li" ) + continue; + s += "<" + n + align_to_string( n, p->alignment() ) + ">"; + } + } else { + QString end; + for ( int i = items.size(); i < (int)lastItems.size(); ++i ) { + n = lastItems[i]->name(); + if ( n.isEmpty() || n == "li" || n == "br" ) + continue; + end.prepend( "</" + lastItems[ i ]->name() + ">" ); + } + s += end; + } + lastItems = items; + n = item->name(); + if ( n == "li" && p->listValue() != -1 ) { + s += "<li value=\"" + QString::number( p->listValue() ) + "\">"; + } else { + QString ps = p->richText(); + if ( ps.isEmpty() ) + s += "<br>"; // empty paragraph + else if ( !n.isEmpty() ) { + s += "<" + n + align_to_string( n, p->alignment() ) + + direction_to_string( n, p->direction() ) + ">" + ps; + if ( n != "li" && n != "br") + s += "</" + n + ">"; + } else + s += ps; + } + } else { + QString end; + for ( int i = 0; i < (int)lastItems.size(); ++i ) { + QString n = lastItems[i]->name(); + if ( n.isEmpty() || n == "li" || n == "br" ) + continue; + end.prepend( "</" + n + ">" ); + } + s += end; + QString ps = p->richText(); + if ( ps.isEmpty() ) + s += "<br>"; // empty paragraph + else + s += "<p" + align_to_string( "p", p->alignment() ) + direction_to_string( "p", p->direction() ) + + ">" + ps + "</p>"; + lastItems = items; + } + if ( ( p = p->next() ) ) + s += '\n'; + } + } else { + s = p->richText(); + } + + return s; +} + +QString QTextDocument::text() const +{ + if ( plainText().simplifyWhiteSpace().isEmpty() ) + return QString(""); + if ( txtFormat == Qt::AutoText && preferRichText || txtFormat == Qt::RichText ) + return richText(); + return plainText( 0 ); +} + +QString QTextDocument::text( int parag ) const +{ + QTextParag *p = paragAt( parag ); + if ( !p ) + return QString::null; + + if ( txtFormat == Qt::AutoText && preferRichText || txtFormat == Qt::RichText ) + return richText( p ); + else + return plainText( p ); +} + +void QTextDocument::invalidate() +{ + QTextParag *s = fParag; + while ( s ) { + s->invalidate( 0 ); + s = s->next(); + } +} + +void QTextDocument::selectionStart( int id, int ¶gId, int &index ) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return; + QTextDocumentSelection &sel = *it; + paragId = !sel.swapped ? sel.startCursor.parag()->paragId() : sel.endCursor.parag()->paragId(); + index = !sel.swapped ? sel.startCursor.index() : sel.endCursor.index(); +} + +QTextCursor QTextDocument::selectionStartCursor( int id) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return QTextCursor( this ); + QTextDocumentSelection &sel = *it; + if ( sel.swapped ) + return sel.endCursor; + return sel.startCursor; +} + +QTextCursor QTextDocument::selectionEndCursor( int id) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return QTextCursor( this ); + QTextDocumentSelection &sel = *it; + if ( !sel.swapped ) + return sel.endCursor; + return sel.startCursor; +} + +void QTextDocument::selectionEnd( int id, int ¶gId, int &index ) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return; + QTextDocumentSelection &sel = *it; + paragId = sel.swapped ? sel.startCursor.parag()->paragId() : sel.endCursor.parag()->paragId(); + index = sel.swapped ? sel.startCursor.index() : sel.endCursor.index(); +} + +QTextParag *QTextDocument::selectionStart( int id ) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return 0; + QTextDocumentSelection &sel = *it; + if ( sel.startCursor.parag()->paragId() < sel.endCursor.parag()->paragId() ) + return sel.startCursor.parag(); + return sel.endCursor.parag(); +} + +QTextParag *QTextDocument::selectionEnd( int id ) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return 0; + QTextDocumentSelection &sel = *it; + if ( sel.startCursor.parag()->paragId() > sel.endCursor.parag()->paragId() ) + return sel.startCursor.parag(); + return sel.endCursor.parag(); +} + +void QTextDocument::addSelection( int id ) +{ + nSelections = QMAX( nSelections, id + 1 ); +} + +static void setSelectionEndHelper( int id, QTextDocumentSelection &sel, QTextCursor &start, QTextCursor &end ) +{ + QTextCursor c1 = start; + QTextCursor c2 = end; + if ( sel.swapped ) { + c1 = end; + c2 = start; + } + + c1.parag()->removeSelection( id ); + c2.parag()->removeSelection( id ); + if ( c1.parag() != c2.parag() ) { + c1.parag()->setSelection( id, c1.index(), c1.parag()->length() - 1 ); + c2.parag()->setSelection( id, 0, c2.index() ); + } else { + c1.parag()->setSelection( id, QMIN( c1.index(), c2.index() ), QMAX( c1.index(), c2.index() ) ); + } + + sel.startCursor = start; + sel.endCursor = end; + if ( sel.startCursor.parag() == sel.endCursor.parag() ) + sel.swapped = sel.startCursor.index() > sel.endCursor.index(); +} + +bool QTextDocument::setSelectionEnd( int id, QTextCursor *cursor ) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return FALSE; + QTextDocumentSelection &sel = *it; + + QTextCursor start = sel.startCursor; + QTextCursor end = *cursor; + + if ( start == end ) { + removeSelection( id ); + setSelectionStart( id, cursor ); + return TRUE; + } + + if ( sel.endCursor.parag() == end.parag() ) { + setSelectionEndHelper( id, sel, start, end ); + return TRUE; + } + + bool inSelection = FALSE; + QTextCursor c( this ); + QTextCursor tmp = sel.startCursor; + if ( sel.swapped ) + tmp = sel.endCursor; + tmp.restoreState(); + QTextCursor tmp2 = *cursor; + tmp2.restoreState(); + c.setParag( tmp.parag()->paragId() < tmp2.parag()->paragId() ? tmp.parag() : tmp2.parag() ); + QTextCursor old; + bool hadStart = FALSE; + bool hadEnd = FALSE; + bool hadStartParag = FALSE; + bool hadEndParag = FALSE; + bool hadOldStart = FALSE; + bool hadOldEnd = FALSE; + bool leftSelection = FALSE; + sel.swapped = FALSE; + for ( ;; ) { + if ( c == start ) + hadStart = TRUE; + if ( c == end ) + hadEnd = TRUE; + if ( c.parag() == start.parag() ) + hadStartParag = TRUE; + if ( c.parag() == end.parag() ) + hadEndParag = TRUE; + if ( c == sel.startCursor ) + hadOldStart = TRUE; + if ( c == sel.endCursor ) + hadOldEnd = TRUE; + + if ( !sel.swapped && + ( hadEnd && !hadStart || + hadEnd && hadStart && start.parag() == end.parag() && start.index() > end.index() ) ) + sel.swapped = TRUE; + + if ( c == end && hadStartParag || + c == start && hadEndParag ) { + QTextCursor tmp = c; + tmp.restoreState(); + if ( tmp.parag() != c.parag() ) { + int sstart = tmp.parag()->selectionStart( id ); + tmp.parag()->removeSelection( id ); + tmp.parag()->setSelection( id, sstart, tmp.index() ); + } + } + + if ( inSelection && + ( c == end && hadStart || c == start && hadEnd ) ) + leftSelection = TRUE; + else if ( !leftSelection && !inSelection && ( hadStart || hadEnd ) ) + inSelection = TRUE; + + bool noSelectionAnymore = hadOldStart && hadOldEnd && leftSelection && !inSelection && !c.parag()->hasSelection( id ) && c.atParagEnd(); + c.parag()->removeSelection( id ); + if ( inSelection ) { + if ( c.parag() == start.parag() && start.parag() == end.parag() ) { + c.parag()->setSelection( id, QMIN( start.index(), end.index() ), QMAX( start.index(), end.index() ) ); + } else if ( c.parag() == start.parag() && !hadEndParag ) { + c.parag()->setSelection( id, start.index(), c.parag()->length() - 1 ); + } else if ( c.parag() == end.parag() && !hadStartParag ) { + c.parag()->setSelection( id, end.index(), c.parag()->length() - 1 ); + } else if ( c.parag() == end.parag() && hadEndParag ) { + c.parag()->setSelection( id, 0, end.index() ); + } else if ( c.parag() == start.parag() && hadStartParag ) { + c.parag()->setSelection( id, 0, start.index() ); + } else { + c.parag()->setSelection( id, 0, c.parag()->length() - 1 ); + } + } + + if ( leftSelection ) + inSelection = FALSE; + + old = c; + c.gotoNextLetter(); + if ( old == c || noSelectionAnymore ) + break; + } + + if ( !sel.swapped ) + sel.startCursor.parag()->setSelection( id, sel.startCursor.index(), sel.startCursor.parag()->length() - 1 ); + + sel.startCursor = start; + sel.endCursor = end; + if ( sel.startCursor.parag() == sel.endCursor.parag() ) + sel.swapped = sel.startCursor.index() > sel.endCursor.index(); + + setSelectionEndHelper( id, sel, start, end ); + + return TRUE; +} + +void QTextDocument::selectAll( int id ) +{ + removeSelection( id ); + + QTextDocumentSelection sel; + sel.swapped = FALSE; + QTextCursor c( this ); + + c.setParag( fParag ); + c.setIndex( 0 ); + sel.startCursor = c; + + c.setParag( lParag ); + c.setIndex( lParag->length() - 1 ); + sel.endCursor = c; + + QTextParag *p = fParag; + while ( p ) { + p->setSelection( id, 0, p->length() - 1 ); + for ( int i = 0; i < (int)p->length(); ++i ) { + if ( p->at( i )->isCustom() && p->at( i )->customItem()->isNested() ) { + QTextTable *t = (QTextTable*)p->at( i )->customItem(); + QPtrList<QTextTableCell> tableCells = t->tableCells(); + for ( QTextTableCell *c = tableCells.first(); c; c = tableCells.next() ) + c->richText()->selectAll( id ); + } + } + p = p->next(); + } + + selections.insert( id, sel ); +} + +bool QTextDocument::removeSelection( int id ) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return FALSE; + + QTextDocumentSelection &sel = *it; + + if ( sel.startCursor == sel.endCursor ) { + selections.remove( id ); + return TRUE; + } + + if ( !mightHaveCustomItems ) { + QTextCursor start = sel.startCursor; + QTextCursor end = sel.endCursor; + if ( sel.swapped ) { + start = sel.endCursor; + end = sel.startCursor; + } + + for ( QTextParag *p = start.parag(); p; p = p->next() ) { + p->removeSelection( id ); + if ( p == end.parag() ) + break; + } + + selections.remove( id ); + return TRUE; + } + + QTextCursor c( this ); + QTextCursor tmp = sel.startCursor; + if ( sel.swapped ) + tmp = sel.endCursor; + tmp.restoreState(); + c.setParag( tmp.parag() ); + QTextCursor old; + bool hadStart = FALSE; + bool hadEnd = FALSE; + QTextParag *lastParag = 0; + bool leftSelection = FALSE; + bool inSelection = FALSE; + sel.swapped = FALSE; + for ( ;; ) { + if ( c.parag() == sel.startCursor.parag() ) + hadStart = TRUE; + if ( c.parag() == sel.endCursor.parag() ) + hadEnd = TRUE; + + if ( inSelection && + ( c == sel.endCursor && hadStart || c == sel.startCursor && hadEnd ) ) + leftSelection = TRUE; + else if ( !leftSelection && !inSelection && ( c.parag() == sel.startCursor.parag() || c.parag() == sel.endCursor.parag() ) ) + inSelection = TRUE; + + bool noSelectionAnymore = leftSelection && !inSelection && !c.parag()->hasSelection( id ) && c.atParagEnd(); + + if ( lastParag != c.parag() ) + c.parag()->removeSelection( id ); + + old = c; + lastParag = c.parag(); + c.gotoNextLetter(); + if ( old == c || noSelectionAnymore ) + break; + } + + selections.remove( id ); + return TRUE; +} + +QString QTextDocument::selectedText( int id, bool withCustom ) const +{ + // ######## TODO: look at textFormat() and return rich text or plain text (like the text() method!) + QMap<int, QTextDocumentSelection>::ConstIterator it = selections.find( id ); + if ( it == selections.end() ) + return QString::null; + + QTextDocumentSelection sel = *it; + + + QTextCursor c1 = sel.startCursor; + QTextCursor c2 = sel.endCursor; + if ( sel.swapped ) { + c2 = sel.startCursor; + c1 = sel.endCursor; + } + + /* 3.0.3 improvement: Make it possible to get a reasonable + selection inside a table. This approach is very conservative: + make sure that both cursors have the same depth level and point + to paragraphs within the same text document. + + Meaning if you select text in two table cells, you will get the + entire table. This is still far better than the 3.0.2, where + you always got the entire table. + + ### Fix this properly for 3.0.4. + */ + while ( c2.nestedDepth() > c1.nestedDepth() ) + c2.oneUp(); + while ( c1.nestedDepth() > c2.nestedDepth() ) + c1.oneUp(); + while ( c1.nestedDepth() && c2.nestedDepth() && + c1.parag()->document() != c2.parag()->document() ) { + c1.oneUp(); + c2.oneUp(); + } + // do not trust sel_swapped with tables. Fix this properly for 3.0.4 as well + if ( c1.parag()->paragId() > c2.parag()->paragId() || + (c1.parag() == c2.parag() && c1.index() > c2.index() ) ) { + QTextCursor tmp = c1; + c2 = c1; + c1 = tmp; + } + + // end selection 3.0.3 improvement + + + if ( c1.parag() == c2.parag() ) { + QString s; + QTextParag *p = c1.parag(); + int end = c2.index(); + if ( p->at( QMAX( 0, end - 1 ) )->isCustom() ) + ++end; + if ( !withCustom || !p->mightHaveCustomItems ) { + s += p->string()->toString().mid( c1.index(), end - c1.index() ); + } else { + for ( int i = c1.index(); i < end; ++i ) { + if ( p->at( i )->isCustom() ) { + if ( p->at( i )->customItem()->isNested() ) { + s += "\n"; + QTextTable *t = (QTextTable*)p->at( i )->customItem(); + QPtrList<QTextTableCell> cells = t->tableCells(); + for ( QTextTableCell *c = cells.first(); c; c = cells.next() ) + s += c->richText()->plainText() + "\n"; + s += "\n"; + } + } else { + s += p->at( i )->c; + } + } + } + return s; + } + + QString s; + QTextParag *p = c1.parag(); + int start = c1.index(); + while ( p ) { + int end = p == c2.parag() ? c2.index() : p->length() - 1; + if ( p == c2.parag() && p->at( QMAX( 0, end - 1 ) )->isCustom() ) + ++end; + if ( !withCustom || !p->mightHaveCustomItems ) { + s += p->string()->toString().mid( start, end - start ); + if ( p != c2.parag() ) + s += "\n"; + } else { + for ( int i = start; i < end; ++i ) { + if ( p->at( i )->isCustom() ) { + if ( p->at( i )->customItem()->isNested() ) { + s += "\n"; + QTextTable *t = (QTextTable*)p->at( i )->customItem(); + QPtrList<QTextTableCell> cells = t->tableCells(); + for ( QTextTableCell *c = cells.first(); c; c = cells.next() ) + s += c->richText()->plainText() + "\n"; + s += "\n"; + } + } else { + s += p->at( i )->c; + } + } + } + start = 0; + if ( p == c2.parag() ) + break; + p = p->next(); + } + return s; +} + +void QTextDocument::setFormat( int id, QTextFormat *f, int flags ) +{ + QMap<int, QTextDocumentSelection>::ConstIterator it = selections.find( id ); + if ( it == selections.end() ) + return; + + QTextDocumentSelection sel = *it; + + QTextCursor c1 = sel.startCursor; + QTextCursor c2 = sel.endCursor; + if ( sel.swapped ) { + c2 = sel.startCursor; + c1 = sel.endCursor; + } + + c2.restoreState(); + c1.restoreState(); + + if ( c1.parag() == c2.parag() ) { + c1.parag()->setFormat( c1.index(), c2.index() - c1.index(), f, TRUE, flags ); + return; + } + + c1.parag()->setFormat( c1.index(), c1.parag()->length() - c1.index(), f, TRUE, flags ); + QTextParag *p = c1.parag()->next(); + while ( p && p != c2.parag() ) { + p->setFormat( 0, p->length(), f, TRUE, flags ); + p = p->next(); + } + c2.parag()->setFormat( 0, c2.index(), f, TRUE, flags ); +} + +void QTextDocument::copySelectedText( int id ) +{ +#ifndef QT_NO_CLIPBOARD + if ( !hasSelection( id ) ) + return; + + QApplication::clipboard()->setText( selectedText( id ) ); +#endif +} + +void QTextDocument::removeSelectedText( int id, QTextCursor *cursor ) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return; + + QTextDocumentSelection sel = *it; + + QTextCursor c1 = sel.startCursor; + QTextCursor c2 = sel.endCursor; + if ( sel.swapped ) { + c2 = sel.startCursor; + c1 = sel.endCursor; + } + + // ### no support for editing tables yet + if ( c1.nestedDepth() || c2.nestedDepth() ) + return; + + c2.restoreState(); + c1.restoreState(); + + *cursor = c1; + removeSelection( id ); + + if ( c1.parag() == c2.parag() ) { + c1.parag()->remove( c1.index(), c2.index() - c1.index() ); + return; + } + + if ( c1.parag() == fParag && c1.index() == 0 && + c2.parag() == lParag && c2.index() == lParag->length() - 1 ) + cursor->setValid( FALSE ); + + bool didGoLeft = FALSE; + if ( c1.index() == 0 && c1.parag() != fParag ) { + cursor->gotoPreviousLetter(); + if ( cursor->isValid() ) + didGoLeft = TRUE; + } + + c1.parag()->remove( c1.index(), c1.parag()->length() - 1 - c1.index() ); + QTextParag *p = c1.parag()->next(); + int dy = 0; + QTextParag *tmp; + while ( p && p != c2.parag() ) { + tmp = p->next(); + dy -= p->rect().height(); + delete p; + p = tmp; + } + c2.parag()->remove( 0, c2.index() ); + while ( p ) { + p->move( dy ); + p->invalidate( 0 ); + p->setEndState( -1 ); + p = p->next(); + } + + c1.parag()->join( c2.parag() ); + + if ( didGoLeft ) + cursor->gotoNextLetter(); +} + +void QTextDocument::indentSelection( int id ) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return; + + QTextDocumentSelection sel = *it; + QTextParag *startParag = sel.startCursor.parag(); + QTextParag *endParag = sel.endCursor.parag(); + if ( sel.endCursor.parag()->paragId() < sel.startCursor.parag()->paragId() ) { + endParag = sel.startCursor.parag(); + startParag = sel.endCursor.parag(); + } + + QTextParag *p = startParag; + while ( p && p != endParag ) { + p->indent(); + p = p->next(); + } +} + +void QTextDocument::addCommand( QTextCommand *cmd ) +{ + commandHistory->addCommand( cmd ); +} + +QTextCursor *QTextDocument::undo( QTextCursor *c ) +{ + return commandHistory->undo( c ); +} + +QTextCursor *QTextDocument::redo( QTextCursor *c ) +{ + return commandHistory->redo( c ); +} + +bool QTextDocument::find( const QString &expr, bool cs, bool wo, bool forward, + int *parag, int *index, QTextCursor *cursor ) +{ + QTextParag *p = forward ? fParag : lParag; + if ( parag ) + p = paragAt( *parag ); + else if ( cursor ) + p = cursor->parag(); + bool first = TRUE; + + while ( p ) { + QString s = p->string()->toString(); + s.remove( s.length() - 1, 1 ); // get rid of trailing space + int start = forward ? 0 : s.length() - 1; + if ( first && index ) + start = *index; + else if ( first ) + start = cursor->index(); + if ( !forward && first ) { + start -= expr.length() + 1; + if ( start < 0 ) { + first = FALSE; + p = p->prev(); + continue; + } + } + first = FALSE; + + for ( ;; ) { + int res = forward ? s.find( expr, start, cs ) : s.findRev( expr, start, cs ); + if ( res == -1 ) + break; + + bool ok = TRUE; + if ( wo ) { + int end = res + expr.length(); + if ( ( res == 0 || s[ res - 1 ].isSpace() || s[ res - 1 ].isPunct() ) && + ( end == (int)s.length() || s[ end ].isSpace() || s[ end ].isPunct() ) ) + ok = TRUE; + else + ok = FALSE; + } + if ( ok ) { + cursor->setParag( p ); + cursor->setIndex( res ); + setSelectionStart( Standard, cursor ); + cursor->setIndex( res + expr.length() ); + setSelectionEnd( Standard, cursor ); + if ( parag ) + *parag = p->paragId(); + if ( index ) + *index = res; + return TRUE; + } + if ( forward ) { + start = res + 1; + } else { + if ( res == 0 ) + break; + start = res - 1; + } + } + p = forward ? p->next() : p->prev(); + } + + return FALSE; +} + +void QTextDocument::setTextFormat( Qt::TextFormat f ) +{ + txtFormat = f; + if ( txtFormat == Qt::RichText && fParag && fParag == lParag && fParag->length() <= 1 ) { + QPtrVector<QStyleSheetItem> v = fParag->styleSheetItems(); + v.resize( v.size() + 1 ); + v.insert( v.size() - 1, styleSheet()->item( "p" ) ); + fParag->setStyleSheetItems( v ); + } + +} + +Qt::TextFormat QTextDocument::textFormat() const +{ + return txtFormat; +} + +bool QTextDocument::inSelection( int selId, const QPoint &pos ) const +{ + QMap<int, QTextDocumentSelection>::ConstIterator it = selections.find( selId ); + if ( it == selections.end() ) + return FALSE; + + QTextDocumentSelection sel = *it; + QTextParag *startParag = sel.startCursor.parag(); + QTextParag *endParag = sel.endCursor.parag(); + if ( sel.startCursor.parag() == sel.endCursor.parag() && + sel.startCursor.parag()->selectionStart( selId ) == sel.endCursor.parag()->selectionEnd( selId ) ) + return FALSE; + if ( sel.endCursor.parag()->paragId() < sel.startCursor.parag()->paragId() ) { + endParag = sel.startCursor.parag(); + startParag = sel.endCursor.parag(); + } + + QTextParag *p = startParag; + while ( p ) { + if ( p->rect().contains( pos ) ) { + bool inSel = FALSE; + int selStart = p->selectionStart( selId ); + int selEnd = p->selectionEnd( selId ); + int y = 0; + int h = 0; + for ( int i = 0; i < p->length(); ++i ) { + if ( i == selStart ) + inSel = TRUE; + if ( i == selEnd ) + break; + if ( p->at( i )->lineStart ) { + y = (*p->lineStarts.find( i ))->y; + h = (*p->lineStarts.find( i ))->h; + } + if ( pos.y() - p->rect().y() >= y && pos.y() - p->rect().y() <= y + h ) { + if ( inSel && pos.x() >= p->at( i )->x && + pos.x() <= p->at( i )->x + p->at( i )->format()->width( p->at( i )->c ) ) + return TRUE; + } + } + } + if ( pos.y() < p->rect().y() ) + break; + if ( p == endParag ) + break; + p = p->next(); + } + + return FALSE; +} + +void QTextDocument::doLayout( QPainter *p, int w ) +{ + minw = wused = 0; + if ( !is_printer( p ) ) + p = 0; + withoutDoubleBuffer = ( p != 0 ); + QPainter * oldPainter = QTextFormat::painter(); + QTextFormat::setPainter( p ); + flow_->setWidth( w ); + cw = w; + vw = w; + QTextParag *parag = fParag; + while ( parag ) { + parag->invalidate( 0 ); + if ( p ) + parag->adjustToPainter( p ); + parag->format(); + parag = parag->next(); + } + QTextFormat::setPainter( oldPainter ); +} + +QPixmap *QTextDocument::bufferPixmap( const QSize &s ) +{ + if ( !buf_pixmap ) { + int w = QABS( s.width() ); + int h = QABS( s.height() ); + buf_pixmap = new QPixmap( w, h ); + } else { + if ( buf_pixmap->width() < s.width() || + buf_pixmap->height() < s.height() ) { + buf_pixmap->resize( QMAX( s.width(), buf_pixmap->width() ), + QMAX( s.height(), buf_pixmap->height() ) ); + } + } + + return buf_pixmap; +} + +void QTextDocument::draw( QPainter *p, const QRect &rect, const QColorGroup &cg, const QBrush *paper ) +{ + if ( !firstParag() ) + return; + + if ( paper ) { + p->setBrushOrigin( 0, 0 ); + + p->fillRect( rect, *paper ); + } + + if ( formatCollection()->defaultFormat()->color() != cg.text() ) { + QDict<QTextFormat> formats = formatCollection()->dict(); + QDictIterator<QTextFormat> it( formats ); + while ( it.current() ) { + if ( it.current() == formatCollection()->defaultFormat() ) { + ++it; + continue; + } + it.current()->setColor( cg.text() ); + ++it; + } + formatCollection()->defaultFormat()->setColor( cg.text() ); + } + + QTextParag *parag = firstParag(); + while ( parag ) { + if ( !parag->isValid() ) + parag->format(); + int y = parag->rect().y(); + QRect pr( parag->rect() ); + pr.setX( 0 ); + pr.setWidth( QWIDGETSIZE_MAX ); + if ( !rect.isNull() && !rect.intersects( pr ) ) { + parag = parag->next(); + continue; + } + p->translate( 0, y ); + if ( rect.isValid() ) + parag->paint( *p, cg, 0, FALSE, rect.x(), rect.y(), rect.width(), rect.height() ); + else + parag->paint( *p, cg, 0, FALSE ); + p->translate( 0, -y ); + parag = parag->next(); + if ( !flow()->isEmpty() ) + flow()->drawFloatingItems( p, rect.x(), rect.y(), rect.width(), rect.height(), cg, FALSE ); + } +} + +void QTextDocument::drawParag( QPainter *p, QTextParag *parag, int cx, int cy, int cw, int ch, + QPixmap *&doubleBuffer, const QColorGroup &cg, + bool drawCursor, QTextCursor *cursor, bool resetChanged ) +{ + QPainter *painter = 0; + if ( resetChanged ) + parag->setChanged( FALSE ); + QRect ir( parag->rect() ); + bool useDoubleBuffer = !parag->document()->parent(); + if ( !useDoubleBuffer && parag->document()->nextDoubleBuffered ) + useDoubleBuffer = TRUE; + if ( is_printer( p ) ) + useDoubleBuffer = FALSE; + + if ( useDoubleBuffer ) { + painter = new QPainter; + if ( cx >= 0 && cy >= 0 ) + ir = ir.intersect( QRect( cx, cy, cw, ch ) ); + if ( !doubleBuffer || + ir.width() > doubleBuffer->width() || + ir.height() > doubleBuffer->height() ) { + doubleBuffer = bufferPixmap( ir.size() ); + painter->begin( doubleBuffer ); + } else { + painter->begin( doubleBuffer ); + } + } else { + painter = p; + painter->translate( ir.x(), ir.y() ); + } + + painter->setBrushOrigin( -ir.x(), -ir.y() ); + + if ( useDoubleBuffer || is_printer( painter ) ) { + if ( !parag->backgroundColor() ) + painter->fillRect( QRect( 0, 0, ir.width(), ir.height() ), + cg.brush( QColorGroup::Base ) ); + else + painter->fillRect( QRect( 0, 0, ir.width(), ir.height() ), + *parag->backgroundColor() ); + } else { + if ( cursor && cursor->parag() == parag ) { + if ( !parag->backgroundColor() ) + painter->fillRect( QRect( parag->at( cursor->index() )->x, 0, 2, ir.height() ), + cg.brush( QColorGroup::Base ) ); + else + painter->fillRect( QRect( parag->at( cursor->index() )->x, 0, 2, ir.height() ), + *parag->backgroundColor() ); + } + } + + painter->translate( -( ir.x() - parag->rect().x() ), + -( ir.y() - parag->rect().y() ) ); + parag->paint( *painter, cg, drawCursor ? cursor : 0, TRUE, cx, cy, cw, ch ); + + if ( useDoubleBuffer ) { + delete painter; + painter = 0; + p->drawPixmap( ir.topLeft(), *doubleBuffer, QRect( QPoint( 0, 0 ), ir.size() ) ); + } else { + painter->translate( -ir.x(), -ir.y() ); + } + + if ( parag->rect().x() + parag->rect().width() < parag->document()->x() + parag->document()->width() ) { + p->fillRect( parag->rect().x() + parag->rect().width(), parag->rect().y(), + ( parag->document()->x() + parag->document()->width() ) - + ( parag->rect().x() + parag->rect().width() ), + parag->rect().height(), cg.brush( QColorGroup::Base ) ); + } + + parag->document()->nextDoubleBuffered = FALSE; +} + +QTextParag *QTextDocument::draw( QPainter *p, int cx, int cy, int cw, int ch, const QColorGroup &cg, + bool onlyChanged, bool drawCursor, QTextCursor *cursor, bool resetChanged ) +{ + if ( withoutDoubleBuffer || par && par->withoutDoubleBuffer ) { + withoutDoubleBuffer = TRUE; + QRect r; + draw( p, r, cg ); + return 0; + } + withoutDoubleBuffer = FALSE; + + if ( !firstParag() ) + return 0; + + if ( drawCursor && cursor ) + tmpCursor = cursor; + if ( cx < 0 && cy < 0 ) { + cx = 0; + cy = 0; + cw = width(); + ch = height(); + } + + QTextParag *lastFormatted = 0; + QTextParag *parag = firstParag(); + + QPixmap *doubleBuffer = 0; + QPainter painter; + + while ( parag ) { + lastFormatted = parag; + if ( !parag->isValid() ) + parag->format(); + + if ( !parag->rect().intersects( QRect( cx, cy, cw, ch ) ) ) { + QRect pr( parag->rect() ); + pr.setWidth( parag->document()->width() ); + if ( pr.intersects( QRect( cx, cy, cw, ch ) ) ) + p->fillRect( pr.intersect( QRect( cx, cy, cw, ch ) ), cg.brush( QColorGroup::Base ) ); + if ( parag->rect().y() > cy + ch ) { + tmpCursor = 0; + goto floating; + } + parag = parag->next(); + continue; + } + + if ( !parag->hasChanged() && onlyChanged ) { + parag = parag->next(); + continue; + } + + drawParag( p, parag, cx, cy, cw, ch, doubleBuffer, cg, drawCursor, cursor, resetChanged ); + parag = parag->next(); + } + + parag = lastParag(); + + floating: + if ( parag->rect().y() + parag->rect().height() < parag->document()->height() ) { + if ( !parag->document()->parent() ) { // !useDoubleBuffer + p->fillRect( 0, parag->rect().y() + parag->rect().height(), parag->document()->width(), + parag->document()->height() - ( parag->rect().y() + parag->rect().height() ), + cg.brush( QColorGroup::Base ) ); + } + if ( !flow()->isEmpty() ) { + QRect cr( cx, cy, cw, ch ); +// cr = cr.intersect( QRect( 0, parag->rect().y() + parag->rect().height(), parag->document()->width(), +// parag->document()->height() - ( parag->rect().y() + parag->rect().height() ) ) ); + flow()->drawFloatingItems( p, cr.x(), cr.y(), cr.width(), cr.height(), cg, FALSE ); + } + } + + if ( buf_pixmap && buf_pixmap->height() > 300 ) { + delete buf_pixmap; + buf_pixmap = 0; + } + + tmpCursor = 0; + return lastFormatted; +} + +void QTextDocument::setDefaultFont( const QFont &f ) +{ + int s = f.pointSize(); + bool usePixels = FALSE; + if ( s == -1 ) { + s = f.pixelSize(); + usePixels = TRUE; + } + updateFontSizes( s, usePixels ); +} + +void QTextDocument::registerCustomItem( QTextCustomItem *i, QTextParag *p ) +{ + if ( i && i->placement() != QTextCustomItem::PlaceInline ) { + flow_->registerFloatingItem( i ); + p->registerFloatingItem( i ); + i->setParagraph( p ); + } + p->mightHaveCustomItems = mightHaveCustomItems = TRUE; +} + +void QTextDocument::unregisterCustomItem( QTextCustomItem *i, QTextParag *p ) +{ + flow_->unregisterFloatingItem( i ); + p->unregisterFloatingItem( i ); + i->setParagraph( 0 ); +} + +bool QTextDocument::hasFocusParagraph() const +{ + return !!focusIndicator.parag; +} + +QString QTextDocument::focusHref() const +{ + return focusIndicator.href; +} + +bool QTextDocument::focusNextPrevChild( bool next ) +{ + if ( !focusIndicator.parag ) { + if ( next ) { + focusIndicator.parag = fParag; + focusIndicator.start = 0; + focusIndicator.len = 0; + } else { + focusIndicator.parag = lParag; + focusIndicator.start = lParag->length(); + focusIndicator.len = 0; + } + } else { + focusIndicator.parag->setChanged( TRUE ); + } + focusIndicator.href = QString::null; + + if ( next ) { + QTextParag *p = focusIndicator.parag; + int index = focusIndicator.start + focusIndicator.len; + while ( p ) { + for ( int i = index; i < p->length(); ++i ) { + if ( p->at( i )->isAnchor() ) { + p->setChanged( TRUE ); + focusIndicator.parag = p; + focusIndicator.start = i; + focusIndicator.len = 0; + focusIndicator.href = p->at( i )->anchorHref(); + while ( i < p->length() ) { + if ( !p->at( i )->isAnchor() ) + return TRUE; + focusIndicator.len++; + i++; + } + } else if ( p->at( i )->isCustom() ) { + if ( p->at( i )->customItem()->isNested() ) { + QTextTable *t = (QTextTable*)p->at( i )->customItem(); + QPtrList<QTextTableCell> cells = t->tableCells(); + // first try to continue + QTextTableCell *c; + bool resetCells = TRUE; + for ( c = cells.first(); c; c = cells.next() ) { + if ( c->richText()->hasFocusParagraph() ) { + if ( c->richText()->focusNextPrevChild( next ) ) { + p->setChanged( TRUE ); + focusIndicator.parag = p; + focusIndicator.start = i; + focusIndicator.len = 0; + focusIndicator.href = c->richText()->focusHref(); + return TRUE; + } else { + resetCells = FALSE; + c = cells.next(); + break; + } + } + } + // now really try + if ( resetCells ) + c = cells.first(); + for ( ; c; c = cells.next() ) { + if ( c->richText()->focusNextPrevChild( next ) ) { + p->setChanged( TRUE ); + focusIndicator.parag = p; + focusIndicator.start = i; + focusIndicator.len = 0; + focusIndicator.href = c->richText()->focusHref(); + return TRUE; + } + } + } + } + } + index = 0; + p = p->next(); + } + } else { + QTextParag *p = focusIndicator.parag; + int index = focusIndicator.start - 1; + if ( focusIndicator.len == 0 && index < focusIndicator.parag->length() - 1 ) + index++; + while ( p ) { + for ( int i = index; i >= 0; --i ) { + if ( p->at( i )->isAnchor() ) { + p->setChanged( TRUE ); + focusIndicator.parag = p; + focusIndicator.start = i; + focusIndicator.len = 0; + focusIndicator.href = p->at( i )->anchorHref(); + while ( i >= -1 ) { + if ( i < 0 || !p->at( i )->isAnchor() ) { + focusIndicator.start++; + return TRUE; + } + if ( i < 0 ) + break; + focusIndicator.len++; + focusIndicator.start--; + i--; + } + } else if ( p->at( i )->isCustom() ) { + if ( p->at( i )->customItem()->isNested() ) { + QTextTable *t = (QTextTable*)p->at( i )->customItem(); + QPtrList<QTextTableCell> cells = t->tableCells(); + // first try to continue + QTextTableCell *c; + bool resetCells = TRUE; + for ( c = cells.last(); c; c = cells.prev() ) { + if ( c->richText()->hasFocusParagraph() ) { + if ( c->richText()->focusNextPrevChild( next ) ) { + p->setChanged( TRUE ); + focusIndicator.parag = p; + focusIndicator.start = i; + focusIndicator.len = 0; + focusIndicator.href = c->richText()->focusHref(); + return TRUE; + } else { + resetCells = FALSE; + c = cells.prev(); + break; + } + } + if ( cells.at() == 0 ) + break; + } + // now really try + if ( resetCells ) + c = cells.last(); + for ( ; c; c = cells.prev() ) { + if ( c->richText()->focusNextPrevChild( next ) ) { + p->setChanged( TRUE ); + focusIndicator.parag = p; + focusIndicator.start = i; + focusIndicator.len = 0; + focusIndicator.href = c->richText()->focusHref(); + return TRUE; + } + if ( cells.at() == 0 ) + break; + } + } + } + } + p = p->prev(); + if ( p ) + index = p->length() - 1; + } + } + + focusIndicator.parag = 0; + + return FALSE; +} + +int QTextDocument::length() const +{ + int l = 0; + QTextParag *p = fParag; + while ( p ) { + l += p->length() - 1; // don't count trailing space + p = p->next(); + } + return l; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +int QTextFormat::width( const QChar &c ) const +{ + if ( c.unicode() == 0xad ) // soft hyphen + return 0; + if ( !pntr || !pntr->isActive() ) { + if ( c == '\t' ) + return fm.width( 'x' ) * 8; + if ( ha == AlignNormal ) { + int w; + if ( c.row() ) + w = fm.width( c ); + else + w = widths[ c.unicode() ]; + if ( w == 0 && !c.row() ) { + w = fm.width( c ); + ( (QTextFormat*)this )->widths[ c.unicode() ] = w; + } + return w; + } else { + QFont f( fn ); + if ( usePixelSizes ) + f.setPixelSize( ( f.pixelSize() * 2 ) / 3 ); + else + f.setPointSize( ( f.pointSize() * 2 ) / 3 ); + QFontMetrics fm_( f ); + return fm_.width( c ); + } + } + + QFont f( fn ); + if ( ha != AlignNormal ) { + if ( usePixelSizes ) + f.setPixelSize( ( f.pixelSize() * 2 ) / 3 ); + else + f.setPointSize( ( f.pointSize() * 2 ) / 3 ); + } + pntr->setFont( f ); + + return pntr->fontMetrics().width( c ); +} + +int QTextFormat::width( const QString &str, int pos ) const +{ + int w = 0; + if ( str[ pos ].unicode() == 0xad ) + return w; + if ( !pntr || !pntr->isActive() ) { + if ( ha == AlignNormal ) { + w = fm.width( str[ pos ] ); + } else { + QFont f( fn ); + if ( usePixelSizes ) + f.setPixelSize( ( f.pixelSize() * 2 ) / 3 ); + else + f.setPointSize( ( f.pointSize() * 2 ) / 3 ); + QFontMetrics fm_( f ); + w = fm_.width( str[ pos ] ); + } + } else { + QFont f( fn ); + if ( ha != AlignNormal ) { + if ( usePixelSizes ) + f.setPixelSize( ( f.pixelSize() * 2 ) / 3 ); + else + f.setPointSize( ( f.pointSize() * 2 ) / 3 ); + } + pntr->setFont( f ); + w = pntr->fontMetrics().width( str[ pos ] ); + } + return w; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextString::QTextString() +{ + bidiDirty = FALSE; + bidi = FALSE; + rightToLeft = FALSE; + dir = QChar::DirON; +} + +QTextString::QTextString( const QTextString &s ) +{ + bidiDirty = s.bidiDirty; + bidi = s.bidi; + rightToLeft = s.rightToLeft; + dir = s.dir; + data = s.subString(); +} + +void QTextString::insert( int index, const QString &s, QTextFormat *f ) +{ + int os = data.size(); + data.resize( data.size() + s.length() ); + if ( index < os ) { + memmove( data.data() + index + s.length(), data.data() + index, + sizeof( QTextStringChar ) * ( os - index ) ); + } + for ( int i = 0; i < (int)s.length(); ++i ) { + data[ (int)index + i ].x = 0; + data[ (int)index + i ].lineStart = 0; + data[ (int)index + i ].d.format = 0; + data[ (int)index + i ].type = QTextStringChar::Regular; + data[ (int)index + i ].rightToLeft = 0; + data[ (int)index + i ].startOfRun = 0; + data[ (int)index + i ].c = s[ i ]; + data[ (int)index + i ].setFormat( f ); + } + bidiDirty = TRUE; +} + +QTextString::~QTextString() +{ + clear(); +} + +void QTextString::insert( int index, QTextStringChar *c ) +{ + int os = data.size(); + data.resize( data.size() + 1 ); + if ( index < os ) { + memmove( data.data() + index + 1, data.data() + index, + sizeof( QTextStringChar ) * ( os - index ) ); + } + data[ (int)index ].c = c->c; + data[ (int)index ].x = 0; + data[ (int)index ].lineStart = 0; + data[ (int)index ].rightToLeft = 0; + data[ (int)index ].d.format = 0; + data[ (int)index ].type = QTextStringChar::Regular; + data[ (int)index ].setFormat( c->format() ); + bidiDirty = TRUE; +} + +void QTextString::truncate( int index ) +{ + index = QMAX( index, 0 ); + index = QMIN( index, (int)data.size() - 1 ); + if ( index < (int)data.size() ) { + for ( int i = index + 1; i < (int)data.size(); ++i ) { + if ( !(data[ i ].type == QTextStringChar::Regular) ) { + delete data[ i ].customItem(); + if ( data[ i ].d.custom->format ) + data[ i ].d.custom->format->removeRef(); + delete data[ i ].d.custom; + data[ i ].d.custom = 0; + } else if ( data[ i ].format() ) { + data[ i ].format()->removeRef(); + } + } + } + data.truncate( index ); + bidiDirty = TRUE; +} + +void QTextString::remove( int index, int len ) +{ + for ( int i = index; i < (int)data.size() && i - index < len; ++i ) { + if ( !(data[ i ].type == QTextStringChar::Regular) ) { + delete data[ i ].customItem(); + if ( data[ i ].d.custom->format ) + data[ i ].d.custom->format->removeRef(); + delete data[ i ].d.custom; + data[ i ].d.custom = 0; + } else if ( data[ i ].format() ) { + data[ i ].format()->removeRef(); + } + } + memmove( data.data() + index, data.data() + index + len, + sizeof( QTextStringChar ) * ( data.size() - index - len ) ); + data.resize( data.size() - len ); + bidiDirty = TRUE; +} + +void QTextString::clear() +{ + for ( int i = 0; i < (int)data.count(); ++i ) { + if ( !(data[ i ].type == QTextStringChar::Regular) ) { + delete data[ i ].customItem(); + if ( data[ i ].d.custom->format ) + data[ i ].d.custom->format->removeRef(); + delete data[ i ].d.custom; + data[ i ].d.custom = 0; + } else if ( data[ i ].format() ) { + data[ i ].format()->removeRef(); + } + } + data.resize( 0 ); +} + +void QTextString::setFormat( int index, QTextFormat *f, bool useCollection ) +{ + if ( useCollection && data[ index ].format() ) + data[ index ].format()->removeRef(); + data[ index ].setFormat( f ); +} + +void QTextString::checkBidi() const +{ + bool rtlKnown = FALSE; + if ( dir == QChar::DirR ) { + ((QTextString *)this)->bidi = TRUE; + ((QTextString *)this)->rightToLeft = TRUE; + rtlKnown = TRUE; + return; + } else if ( dir == QChar::DirL ) { + ((QTextString *)this)->rightToLeft = FALSE; + rtlKnown = TRUE; + } else { + ((QTextString *)this)->rightToLeft = FALSE; + } + + int len = data.size(); + const QTextStringChar *c = data.data(); + ((QTextString *)this)->bidi = FALSE; + while( len ) { + if ( !rtlKnown ) { + switch( c->c.direction() ) + { + case QChar::DirL: + case QChar::DirLRO: + case QChar::DirLRE: + ((QTextString *)this)->rightToLeft = FALSE; + rtlKnown = TRUE; + break; + case QChar::DirR: + case QChar::DirAL: + case QChar::DirRLO: + case QChar::DirRLE: + ((QTextString *)this)->rightToLeft = TRUE; + rtlKnown = TRUE; + break; + default: + break; + } + } + uchar row = c->c.row(); + if( (row > 0x04 && row < 0x09) || (row > 0xfa && row < 0xff) ) { + ((QTextString *)this)->bidi = TRUE; + if ( rtlKnown ) + return; + } + len--; + ++c; + } +} + +void QTextDocument::setStyleSheet( QStyleSheet *s ) +{ + if ( !s ) + return; + sheet_ = s; + fCollection->setStyleSheet( s ); + updateStyles(); +} + +void QTextDocument::updateStyles() +{ + invalidate(); + if ( par ) + underlLinks = par->underlLinks; + fCollection->updateStyles(); + for ( QTextDocument *d = childList.first(); d; d = childList.next() ) + d->updateStyles(); +} + +void QTextDocument::updateFontSizes( int base, bool usePixels ) +{ + for ( QTextDocument *d = childList.first(); d; d = childList.next() ) + d->updateFontSizes( base, usePixels ); + invalidate(); + fCollection->updateFontSizes( base, usePixels ); +} + +void QTextDocument::updateFontAttributes( const QFont &f, const QFont &old ) +{ + for ( QTextDocument *d = childList.first(); d; d = childList.next() ) + d->updateFontAttributes( f, old ); + invalidate(); + fCollection->updateFontAttributes( f, old ); +} + +void QTextStringChar::setFormat( QTextFormat *f ) +{ + if ( type == Regular ) { + d.format = f; + } else { + if ( !d.custom ) { + d.custom = new CustomData; + d.custom->custom = 0; + } + d.custom->format = f; + } +} + +void QTextStringChar::setCustomItem( QTextCustomItem *i ) +{ + if ( type == Regular ) { + QTextFormat *f = format(); + d.custom = new CustomData; + d.custom->format = f; + } else { + delete d.custom->custom; + } + d.custom->custom = i; + type = (type == Anchor ? CustomAnchor : Custom); +} + +void QTextStringChar::loseCustomItem() +{ + if ( type == Custom ) { + QTextFormat *f = d.custom->format; + d.custom->custom = 0; + delete d.custom; + type = Regular; + d.format = f; + } else if ( type == CustomAnchor ) { + d.custom->custom = 0; + type = Anchor; + } +} + +QString QTextStringChar::anchorName() const +{ + if ( type == Regular ) + return QString::null; + else + return d.custom->anchorName; +} + +QString QTextStringChar::anchorHref() const +{ + if ( type == Regular ) + return QString::null; + else + return d.custom->anchorHref; +} + +void QTextStringChar::setAnchor( const QString& name, const QString& href ) +{ + if ( type == Regular ) { + QTextFormat *f = format(); + d.custom = new CustomData; + d.custom->custom = 0; + d.custom->format = f; + type = Anchor; + } else if ( type == Custom ) { + type = CustomAnchor; + } + d.custom->anchorName = name; + d.custom->anchorHref = href; +} + + +int QTextString::width( int idx ) const +{ + int w = 0; + QTextStringChar *c = &at( idx ); + if ( c->c.unicode() == 0xad ) + return 0; + if( c->isCustom() ) { + if( c->customItem()->placement() == QTextCustomItem::PlaceInline ) + w = c->customItem()->width; + } else { + int r = c->c.row(); + if( r < 0x06 || r > 0x1f ) + w = c->format()->width( c->c ); + else { + // complex text. We need some hacks to get the right metric here + QString str; + int pos = 0; + if( idx > 4 ) + pos = idx - 4; + int off = idx - pos; + int end = QMIN( length(), idx + 4 ); + while ( pos < end ) { + str += at(pos).c; + pos++; + } + w = c->format()->width( str, off ); + } + } + return w; +} + +QMemArray<QTextStringChar> QTextString::subString( int start, int len ) const +{ + if ( len == 0xFFFFFF ) + len = data.size(); + QMemArray<QTextStringChar> a; + a.resize( len ); + for ( int i = 0; i < len; ++i ) { + QTextStringChar *c = &data[ i + start ]; + a[ i ].c = c->c; + a[ i ].x = 0; + a[ i ].lineStart = 0; + a[ i ].rightToLeft = 0; + a[ i ].d.format = 0; + a[ i ].type = QTextStringChar::Regular; + a[ i ].setFormat( c->format() ); + if ( c->format() ) + c->format()->addRef(); + } + return a; +} + +QTextStringChar *QTextStringChar::clone() const +{ + QTextStringChar *chr = new QTextStringChar; + chr->c = c; + chr->x = 0; + chr->lineStart = 0; + chr->rightToLeft = 0; + chr->d.format = 0; + chr->type = QTextStringChar::Regular; + chr->setFormat( format() ); + if ( chr->format() ) + chr->format()->addRef(); + return chr; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextParag::QTextParag( QTextDocument *d, QTextParag *pr, QTextParag *nx, bool updateIds ) + : invalid( 0 ), p( pr ), n( nx ), docOrPseudo( d ), align( 0 ),mSelections( 0 ), + mStyleSheetItemsVec( 0 ), mFloatingItems( 0 ), listS( QStyleSheetItem::ListDisc ), + numSubParag( -1 ), tm( -1 ), bm( -1 ), lm( -1 ), rm( -1 ), flm( -1 ), + tArray(0), tabStopWidth(0), eData( 0 ) +{ + listS = QStyleSheetItem::ListDisc; + if ( ! (hasdoc = docOrPseudo != 0 ) ) + docOrPseudo = new QTextParagPseudoDocument; + bgcol = 0; + breakable = TRUE; + isBr = FALSE; + movedDown = FALSE; + mightHaveCustomItems = FALSE; + visible = TRUE; + list_val = -1; + newLinesAllowed = FALSE; + lastInFrame = FALSE; + defFormat = formatCollection()->defaultFormat(); + if ( !hasdoc ) { + tabStopWidth = defFormat->width( 'x' ) * 8; + pseudoDocument()->commandHistory = new QTextCommandHistory( 100 ); + } +#if defined(PARSER_DEBUG) + qDebug( debug_indent + "new QTextParag" ); +#endif + fullWidth = TRUE; + + if ( p ) + p->n = this; + if ( n ) + n->p = this; + + + if ( !p && hasdoc ) + document()->setFirstParag( this ); + if ( !n && hasdoc ) + document()->setLastParag( this ); + + changed = FALSE; + firstFormat = TRUE; + state = -1; + needPreProcess = FALSE; + + if ( p ) + id = p->id + 1; + else + id = 0; + if ( n && updateIds ) { + QTextParag *s = n; + while ( s ) { + s->id = s->p->id + 1; + s->numSubParag = -1; + s->lm = s->rm = s->tm = s->bm = -1, s->flm = -1; + s = s->n; + } + } + firstPProcess = TRUE; + + str = new QTextString(); + str->insert( 0, " ", formatCollection()->defaultFormat() ); +} + +QTextParag::~QTextParag() +{ + delete str; + if ( hasdoc ) { + register QTextDocument *doc = document(); + if ( this == doc->minwParag ) { + doc->minwParag = 0; + doc->minw = 0; + } + if ( this == doc->curParag ) + doc->curParag = 0; + } else { + delete pseudoDocument(); + } + if ( tArray ) + delete [] tArray; + delete eData; + QMap<int, QTextParagLineStart*>::Iterator it = lineStarts.begin(); + for ( ; it != lineStarts.end(); ++it ) + delete *it; + if ( mSelections ) + delete mSelections; + if ( mFloatingItems ) + delete mFloatingItems; + if ( mStyleSheetItemsVec ) + delete mStyleSheetItemsVec; + if ( p ) + p->setNext( n ); + if ( n ) + n->setPrev( p ); +} + +void QTextParag::setNext( QTextParag *s ) +{ + n = s; + if ( !n && hasdoc ) + document()->setLastParag( this ); +} + +void QTextParag::setPrev( QTextParag *s ) +{ + p = s; + if ( !p && hasdoc ) + document()->setFirstParag( this ); +} + +void QTextParag::invalidate( int chr ) +{ + if ( invalid < 0 ) + invalid = chr; + else + invalid = QMIN( invalid, chr ); + if ( mFloatingItems ) { + for ( QTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() ) + i->ypos = -1; + } + lm = rm = bm = tm = flm = -1; +} + +void QTextParag::insert( int index, const QString &s ) +{ + if ( hasdoc && !document()->useFormatCollection() && document()->preProcessor() ) + str->insert( index, s, + document()->preProcessor()->format( QTextPreProcessor::Standard ) ); + else + str->insert( index, s, formatCollection()->defaultFormat() ); + invalidate( index ); + needPreProcess = TRUE; +} + +void QTextParag::truncate( int index ) +{ + str->truncate( index ); + insert( length(), " " ); + needPreProcess = TRUE; +} + +void QTextParag::remove( int index, int len ) +{ + if ( index + len - str->length() > 0 ) + return; + for ( int i = index; i < index + len; ++i ) { + QTextStringChar *c = at( i ); + if ( hasdoc && c->isCustom() ) { + document()->unregisterCustomItem( c->customItem(), this ); + } + } + str->remove( index, len ); + invalidate( 0 ); + needPreProcess = TRUE; +} + +void QTextParag::join( QTextParag *s ) +{ + int oh = r.height() + s->r.height(); + n = s->n; + if ( n ) + n->p = this; + else if ( hasdoc ) + document()->setLastParag( this ); + + int start = str->length(); + if ( length() > 0 && at( length() - 1 )->c == ' ' ) { + remove( length() - 1, 1 ); + --start; + } + append( s->str->toString(), TRUE ); + + for ( int i = 0; i < s->length(); ++i ) { + if ( !hasdoc || document()->useFormatCollection() ) { + s->str->at( i ).format()->addRef(); + str->setFormat( i + start, s->str->at( i ).format(), TRUE ); + } + if ( s->str->at( i ).isCustom() ) { + QTextCustomItem * item = s->str->at( i ).customItem(); + str->at( i + start ).setCustomItem( item ); + s->str->at( i ).loseCustomItem(); + } + if ( s->str->at( i ).isAnchor() ) { + str->at( i + start ).setAnchor( s->str->at( i ).anchorName(), + s->str->at( i ).anchorHref() ); + } + } + + if ( !extraData() && s->extraData() ) { + setExtraData( s->extraData() ); + s->setExtraData( 0 ); + } else if ( extraData() && s->extraData() ) { + extraData()->join( s->extraData() ); + } + delete s; + invalidate( 0 ); + r.setHeight( oh ); + needPreProcess = TRUE; + if ( n ) { + QTextParag *s = n; + while ( s ) { + s->id = s->p->id + 1; + s->state = -1; + s->needPreProcess = TRUE; + s->changed = TRUE; + s = s->n; + } + } + format(); + state = -1; +} + +void QTextParag::move( int &dy ) +{ + if ( dy == 0 ) + return; + changed = TRUE; + r.moveBy( 0, dy ); + if ( mFloatingItems ) { + for ( QTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() ) + i->ypos += dy; + } + if ( p ) + p->lastInFrame = TRUE; + + // do page breaks if required + if ( hasdoc && document()->isPageBreakEnabled() ) { + int shift; + if ( ( shift = document()->formatter()->formatVertically( document(), this ) ) ) { + if ( p ) + p->setChanged( TRUE ); + dy += shift; + } + } +} + +void QTextParag::format( int start, bool doMove ) +{ + if ( !str || str->length() == 0 || !formatter() ) + return; + + if ( hasdoc && + document()->preProcessor() && + ( needPreProcess || state == -1 ) ) + document()->preProcessor()->process( document(), this, invalid <= 0 ? 0 : invalid ); + needPreProcess = FALSE; + + if ( invalid == -1 ) + return; + + r.moveTopLeft( QPoint( documentX(), p ? p->r.y() + p->r.height() : documentY() ) ); + r.setWidth( documentWidth() ); + if ( p ) + p->lastInFrame = FALSE; + + movedDown = FALSE; + bool formattedAgain = FALSE; + + formatAgain: + + if ( hasdoc && mFloatingItems ) { + for ( QTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() ) { + i->ypos = r.y(); + if ( i->placement() == QTextCustomItem::PlaceRight ) { + i->xpos = r.x() + r.width() - i->width; + } + } + } + QMap<int, QTextParagLineStart*> oldLineStarts = lineStarts; + lineStarts.clear(); + int y = formatter()->format( document(), this, start, oldLineStarts ); + + + r.setWidth( QMAX( r.width(), formatter()->minimumWidth() ) ); + + + QMap<int, QTextParagLineStart*>::Iterator it = oldLineStarts.begin(); + + for ( ; it != oldLineStarts.end(); ++it ) + delete *it; + + QTextStringChar *c = 0; + // do not do this on mac, as the paragraph + // with has to be the full document width on mac as the selections + // always extend completely to the right. This is a bit unefficient, + // as this results in a bigger double buffer than needed but ok for + // now. + if ( lineStarts.count() == 1 ) { //&& ( !doc || document()->flow()->isEmpty() ) ) { + if ( !string()->isBidi() ) { + c = &str->at( str->length() - 1 ); + r.setWidth( c->x + str->width( str->length() - 1 ) ); + } else { + r.setWidth( lineStarts[0]->w ); + } + } + + if ( newLinesAllowed ) { + it = lineStarts.begin(); + int usedw = 0; + for ( ; it != lineStarts.end(); ++it ) + usedw = QMAX( usedw, (*it)->w ); + if ( r.width() <= 0 ) { + // if the user specifies an invalid rect, this means that the + // bounding box should grow to the width that the text actually + // needs + r.setWidth( usedw ); + } else { + r.setWidth( QMIN( usedw, r.width() ) ); + } + } + + if ( y != r.height() ) + r.setHeight( y ); + + if ( !visible ) { + r.setHeight( 0 ); + } else { + int minw = formatter()->minimumWidth(); + int wused = formatter()->widthUsed(); + wused = QMAX( minw, wused ); + if ( hasdoc ) { + document()->setMinimumWidth( minw, wused, this ); + } else { + pseudoDocument()->minw = QMAX( pseudoDocument()->minw, minw ); + pseudoDocument()->wused = QMAX( pseudoDocument()->wused, wused ); + } + } + + // do page breaks if required + if ( hasdoc && document()->isPageBreakEnabled() ) { + int shift = document()->formatter()->formatVertically( document(), this ); + if ( shift && !formattedAgain ) { + formattedAgain = TRUE; + goto formatAgain; + } + } + + if ( n && doMove && n->invalid == -1 && r.y() + r.height() != n->r.y() ) { + int dy = ( r.y() + r.height() ) - n->r.y(); + QTextParag *s = n; + bool makeInvalid = p && p->lastInFrame; + while ( s && dy ) { + if ( !s->isFullWidth() ) + makeInvalid = TRUE; + if ( makeInvalid ) + s->invalidate( 0 ); + s->move( dy ); + if ( s->lastInFrame ) + makeInvalid = TRUE; + s = s->n; + } + } + + firstFormat = FALSE; + changed = TRUE; + invalid = -1; + //##### string()->setTextChanged( FALSE ); +} + +int QTextParag::lineHeightOfChar( int i, int *bl, int *y ) const +{ + if ( !isValid() ) + ( (QTextParag*)this )->format(); + + QMap<int, QTextParagLineStart*>::ConstIterator it = lineStarts.end(); + --it; + for ( ;; ) { + if ( i >= it.key() ) { + if ( bl ) + *bl = ( *it )->baseLine; + if ( y ) + *y = ( *it )->y; + return ( *it )->h; + } + if ( it == lineStarts.begin() ) + break; + --it; + } + + qWarning( "QTextParag::lineHeightOfChar: couldn't find lh for %d", i ); + return 15; +} + +QTextStringChar *QTextParag::lineStartOfChar( int i, int *index, int *line ) const +{ + if ( !isValid() ) + ( (QTextParag*)this )->format(); + + int l = (int)lineStarts.count() - 1; + QMap<int, QTextParagLineStart*>::ConstIterator it = lineStarts.end(); + --it; + for ( ;; ) { + if ( i >= it.key() ) { + if ( index ) + *index = it.key(); + if ( line ) + *line = l; + return &str->at( it.key() ); + } + if ( it == lineStarts.begin() ) + break; + --it; + --l; + } + + qWarning( "QTextParag::lineStartOfChar: couldn't find %d", i ); + return 0; +} + +int QTextParag::lines() const +{ + if ( !isValid() ) + ( (QTextParag*)this )->format(); + + return (int)lineStarts.count(); +} + +QTextStringChar *QTextParag::lineStartOfLine( int line, int *index ) const +{ + if ( !isValid() ) + ( (QTextParag*)this )->format(); + + if ( line >= 0 && line < (int)lineStarts.count() ) { + QMap<int, QTextParagLineStart*>::ConstIterator it = lineStarts.begin(); + while ( line-- > 0 ) + ++it; + int i = it.key(); + if ( index ) + *index = i; + return &str->at( i ); + } + + qWarning( "QTextParag::lineStartOfLine: couldn't find %d", line ); + return 0; +} + +int QTextParag::leftGap() const +{ + if ( !isValid() ) + ( (QTextParag*)this )->format(); + + int line = 0; + int x = str->at(0).x; /* set x to x of first char */ + if ( str->isBidi() ) { + for ( int i = 1; i < str->length(); ++i ) + x = QMIN(x, str->at(i).x); + return x; + } + + QMap<int, QTextParagLineStart*>::ConstIterator it = lineStarts.begin(); + while (line < (int)lineStarts.count()) { + int i = it.key(); /* char index */ + x = QMIN(x, str->at(i).x); + ++it; + ++line; + } + return x; +} + +void QTextParag::setFormat( int index, int len, QTextFormat *f, bool useCollection, int flags ) +{ + if ( !f ) + return; + if ( index < 0 ) + index = 0; + if ( index > str->length() - 1 ) + index = str->length() - 1; + if ( index + len >= str->length() ) + len = str->length() - index; + + QTextFormatCollection *fc = 0; + if ( useCollection ) + fc = formatCollection(); + QTextFormat *of; + for ( int i = 0; i < len; ++i ) { + of = str->at( i + index ).format(); + if ( !changed && f->key() != of->key() ) + changed = TRUE; + if ( invalid == -1 && + ( f->font().family() != of->font().family() || + f->font().pointSize() != of->font().pointSize() || + f->font().weight() != of->font().weight() || + f->font().italic() != of->font().italic() || + f->vAlign() != of->vAlign() ) ) { + invalidate( 0 ); + } + if ( flags == -1 || flags == QTextFormat::Format || !fc ) { + if ( fc ) + f = fc->format( f ); + str->setFormat( i + index, f, useCollection ); + } else { + QTextFormat *fm = fc->format( of, f, flags ); + str->setFormat( i + index, fm, useCollection ); + } + } +} + +void QTextParag::indent( int *oldIndent, int *newIndent ) +{ + if ( !hasdoc || !document()->indent() || style() && style()->displayMode() != QStyleSheetItem::DisplayBlock ) { + if ( oldIndent ) + *oldIndent = 0; + if ( newIndent ) + *newIndent = 0; + if ( oldIndent && newIndent ) + *newIndent = *oldIndent; + return; + } + document()->indent()->indent( document(), this, oldIndent, newIndent ); +} + +void QTextParag::paint( QPainter &painter, const QColorGroup &cg, QTextCursor *cursor, bool drawSelections, + int clipx, int clipy, int clipw, int cliph ) +{ + if ( !visible ) + return; + QTextStringChar *chr = at( 0 ); + int i = 0; + int h = 0; + int baseLine = 0, lastBaseLine = 0; + QTextStringChar *formatChar = 0; + int lastY = -1; + int startX = 0; + int bw = 0; + int cy = 0; + int curx = -1, cury = 0, curh = 0; + bool lastDirection = chr->rightToLeft; + const int full_sel_width = (hasdoc ? document()->width() : r.width()); +#if 0 // seems we don't need that anymore + int tw = 0; +#endif + + QString qstr = str->toString(); + // ### workaround so that \n are not drawn, actually this should be + // fixed in QFont somewhere (under Windows you get ugly boxes + // otherwise) + QChar* uc = (QChar*) qstr.unicode(); + for ( uint ii = 0; ii < qstr.length(); ii++ ) { + if ( uc[(int)ii]== '\n' ) + uc[(int)ii] = 0x20; + } + + + const int nSels = hasdoc ? document()->numSelections() : 1; + QMemArray<int> selectionStarts( nSels ); + QMemArray<int> selectionEnds( nSels ); + if ( drawSelections ) { + bool hasASelection = FALSE; + for ( i = 0; i < nSels; ++i ) { + if ( !hasSelection( i ) ) { + selectionStarts[ i ] = -1; + selectionEnds[ i ] = -1; + } else { + hasASelection = TRUE; + selectionStarts[ i ] = selectionStart( i ); + int end = selectionEnd( i ); + if ( end == length() - 1 && n && n->hasSelection( i ) ) + end++; + selectionEnds[ i ] = end; + } + } + if ( !hasASelection ) + drawSelections = FALSE; + } + + int line = -1; + int cw; + bool didListLabel = FALSE; + int paintStart = 0; + int paintEnd = -1; + int lasth = 0; + for ( i = 0; i < length(); i++ ) { + chr = at( i ); +#if 0 // seems we don't need that anymore + if ( !str->isBidi() && is_printer( &painter ) ) { // ### fix our broken ps-printer + if ( !chr->lineStart ) + chr->x = QMAX( chr->x, tw ); + else + tw = 0; + } +#endif + cw = string()->width( i ); + if ( chr->c == '\t' && i < length() - 1 ) + cw = at( i + 1 )->x - chr->x + 1; + if ( chr->c.unicode() == 0xad && i < length() - 1 ) + cw = 0; + + // init a new line + if ( chr->lineStart ) { +#if 0 // seems we don't need that anymore + tw = 0; +#endif + ++line; + lineInfo( line, cy, h, baseLine ); + lasth = h; + if ( clipy != -1 && cy > clipy - r.y() + cliph ) // outside clip area, leave + break; + if ( lastBaseLine == 0 ) + lastBaseLine = baseLine; + } + + // draw bullet list items + if ( !didListLabel && line == 0 && style() && style()->displayMode() == QStyleSheetItem::DisplayListItem ) { + didListLabel = TRUE; + drawLabel( &painter, chr->x, cy, 0, 0, baseLine, cg ); + } + + // check for cursor mark + if ( cursor && this == cursor->parag() && i == cursor->index() ) { + curx = cursor->x(); + QTextStringChar *c = chr; + if ( i > 0 ) + --c; + curh = c->format()->height(); + cury = cy + baseLine - c->format()->ascent(); + } + + // first time - start again... + if ( !formatChar || lastY == -1 ) { + formatChar = chr; + lastY = cy; + startX = chr->x; + if ( !chr->isCustom() && chr->c != '\n' ) + paintEnd = i; + bw = cw; + if ( !chr->isCustom() ) + continue; + } + + // check if selection state changed + bool selectionChange = FALSE; + if ( drawSelections ) { + for ( int j = 0; j < nSels; ++j ) { + selectionChange = selectionStarts[ j ] == i || selectionEnds[ j ] == i; + if ( selectionChange ) + break; + } + } + + //if something (format, etc.) changed, draw what we have so far + if ( ( ( ( alignment() & Qt3::AlignJustify ) == Qt3::AlignJustify && at(paintEnd)->c.isSpace() ) || + lastDirection != (bool)chr->rightToLeft || + chr->startOfRun || + lastY != cy || chr->format() != formatChar->format() || chr->isAnchor() != formatChar->isAnchor() || + ( paintEnd != -1 && at( paintEnd )->c =='\t' ) || chr->c == '\t' || + ( paintEnd != -1 && at( paintEnd )->c.unicode() == 0xad ) || chr->c.unicode() == 0xad || + selectionChange || chr->isCustom() ) ) { + if ( paintStart <= paintEnd ) { + // ### temporary hack until I get the new placement/shaping stuff working + int x = startX; + if ( ( alignment() & Qt3::AlignJustify ) == Qt3::AlignJustify && paintEnd != -1 && + paintEnd > 1 && at( paintEnd )->c.isSpace() ) { + int add = str->at(paintEnd).x - str->at(paintEnd-1).x - str->width(paintEnd-1); + bw += ( lastDirection ? 0 : add ); + } + drawParagString( painter, qstr, paintStart, paintEnd - paintStart + 1, x, lastY, + lastBaseLine, bw, lasth, drawSelections, + formatChar, i, selectionStarts, selectionEnds, cg, lastDirection ); + } +#if 0 // seems we don't need that anymore + if ( !str->isBidi() && is_printer( &painter ) ) { // ### fix our broken ps-printer + if ( !chr->lineStart ) { + // ### the next line doesn't look 100% correct for arabic + tw = startX + painter.fontMetrics().width( qstr.mid(paintStart, paintEnd - paintStart +1) ); + chr->x = QMAX( chr->x, tw ); + } else { + tw = 0; + } + } +#endif + if ( !chr->isCustom() ) { + if ( chr->c != '\n' ) { + paintStart = i; + paintEnd = i; + } else { + paintStart = i+1; + paintEnd = -1; + } + formatChar = chr; + lastY = cy; + startX = chr->x; + bw = cw; + } else { + if ( chr->customItem()->placement() == QTextCustomItem::PlaceInline ) { + chr->customItem()->draw( &painter, chr->x, cy, clipx - r.x(), clipy - r.y(), clipw, cliph, cg, + nSels && selectionStarts[ 0 ] <= i && selectionEnds[ 0 ] >= i ); + paintStart = i+1; + paintEnd = -1; + formatChar = chr; + lastY = cy; + startX = chr->x + string()->width( i ); + bw = 0; + } else { + chr->customItem()->resize( chr->customItem()->width ); + paintStart = i+1; + paintEnd = -1; + formatChar = chr; + lastY = cy; + startX = chr->x + string()->width( i ); + bw = 0; + } + } + } else { + if ( chr->c != '\n' ) { + if( chr->rightToLeft ) { + startX = chr->x; + } + paintEnd = i; + } + bw += cw; + } + lastBaseLine = baseLine; + lasth = h; + lastDirection = chr->rightToLeft; + } + + // if we are through the parag, but still have some stuff left to draw, draw it now + if ( paintStart <= paintEnd ) { + bool selectionChange = FALSE; + if ( drawSelections ) { + for ( int j = 0; j < nSels; ++j ) { + selectionChange = selectionStarts[ j ] == i || selectionEnds[ j ] == i; + if ( selectionChange ) + break; + } + } + int x = startX; + drawParagString( painter, qstr, paintStart, paintEnd-paintStart+1, x, lastY, + lastBaseLine, bw, h, drawSelections, + formatChar, i, selectionStarts, selectionEnds, cg, lastDirection ); + } + + // if we should draw a cursor, draw it now + if ( curx != -1 && cursor ) { + painter.fillRect( QRect( curx, cury, 1, curh - lineSpacing() ), cg.color( QColorGroup::Text ) ); + painter.save(); + if ( string()->isBidi() ) { + const int d = 4; + if ( at( cursor->index() )->rightToLeft ) { + painter.setPen( Qt::black ); + painter.drawLine( curx, cury, curx - d / 2, cury + d / 2 ); + painter.drawLine( curx, cury + d, curx - d / 2, cury + d / 2 ); + } else { + painter.setPen( Qt::black ); + painter.drawLine( curx, cury, curx + d / 2, cury + d / 2 ); + painter.drawLine( curx, cury + d, curx + d / 2, cury + d / 2 ); + } + } + painter.restore(); + } +} + +//#define BIDI_DEBUG + +void QTextParag::drawParagString( QPainter &painter, const QString &s, int start, int len, int startX, + int lastY, int baseLine, int bw, int h, bool drawSelections, + QTextStringChar *formatChar, int i, const QMemArray<int> &selectionStarts, + const QMemArray<int> &selectionEnds, const QColorGroup &cg, bool rightToLeft ) +{ + bool plainText = hasdoc ? document()->textFormat() == Qt::PlainText : FALSE; + QTextFormat* format = formatChar->format(); + QString str( s ); + if ( str[ (int)str.length() - 1 ].unicode() == 0xad ) + str.remove( str.length() - 1, 1 ); + if ( !plainText || hasdoc && format->color() != document()->formatCollection()->defaultFormat()->color() ) + painter.setPen( QPen( format->color() ) ); + else + painter.setPen( cg.text() ); + painter.setFont( format->font() ); + + if ( hasdoc && formatChar->isAnchor() && !formatChar->anchorHref().isEmpty() ) { + if ( format->useLinkColor() ) { + if ( document()->linkColor.isValid() ) + painter.setPen( document()->linkColor ); + else + painter.setPen( QPen( Qt::blue ) ); + } + if ( document()->underlineLinks() ) { + QFont fn = format->font(); + fn.setUnderline( TRUE ); + painter.setFont( fn ); + } + } + + if ( drawSelections ) { + const int nSels = hasdoc ? document()->numSelections() : 1; + const int startSel = is_printer( 0 ) ? 1 : 0; + for ( int j = startSel; j < nSels; ++j ) { + if ( i > selectionStarts[ j ] && i <= selectionEnds[ j ] ) { + if ( !hasdoc || document()->invertSelectionText( j ) ) + painter.setPen( QPen( cg.color( QColorGroup::HighlightedText ) ) ); + if ( j == QTextDocument::Standard ) + painter.fillRect( startX, lastY, bw, h, cg.color( QColorGroup::Highlight ) ); + else + painter.fillRect( startX, lastY, bw, h, hasdoc ? document()->selectionColor( j ) : cg.color( QColorGroup::Highlight ) ); + } + } + } + + if ( str[ start ] != '\t' && str[ start ].unicode() != 0xad ) { + if ( format->vAlign() == QTextFormat::AlignNormal ) { + painter.drawText( startX, lastY + baseLine, str.mid( start ), len ); +#ifdef BIDI_DEBUG + painter.save(); + painter.setPen ( Qt::red ); + painter.drawLine( startX, lastY, startX, lastY + baseLine ); + painter.drawLine( startX, lastY + baseLine/2, startX + 10, lastY + baseLine/2 ); + int w = 0; + int i = 0; + while( i < len ) + w += painter.fontMetrics().charWidth( str, start + i++ ); + painter.setPen ( Qt::blue ); + painter.drawLine( startX + w - 1, lastY, startX + w - 1, lastY + baseLine ); + painter.drawLine( startX + w - 1, lastY + baseLine/2, startX + w - 1 - 10, lastY + baseLine/2 ); + painter.restore(); +#endif + } else if ( format->vAlign() == QTextFormat::AlignSuperScript ) { + QFont f( painter.font() ); + if ( format->fontSizesInPixels() ) + f.setPixelSize( ( f.pixelSize() * 2 ) / 3 ); + else + f.setPointSize( ( f.pointSize() * 2 ) / 3 ); + painter.setFont( f ); + painter.drawText( startX, lastY + baseLine - ( painter.fontMetrics().height() / 2 ), + str.mid( start ), len ); + } else if ( format->vAlign() == QTextFormat::AlignSubScript ) { + QFont f( painter.font() ); + if ( format->fontSizesInPixels() ) + f.setPixelSize( ( f.pixelSize() * 2 ) / 3 ); + else + f.setPointSize( ( f.pointSize() * 2 ) / 3 ); + painter.setFont( f ); + painter.drawText( startX, lastY + baseLine + painter.fontMetrics().height() / 6, str.mid( start ), len ); + } + } + if ( i + 1 < length() && at( i + 1 )->lineStart && at( i )->c.unicode() == 0xad ) { + painter.drawText( startX + bw, lastY + baseLine, "\xad" ); + } + if ( format->isMisspelled() ) { + painter.save(); + painter.setPen( QPen( Qt::red, 1, Qt::DotLine ) ); + painter.drawLine( startX, lastY + baseLine + 1, startX + bw, lastY + baseLine + 1 ); + painter.restore(); + } + + i -= len; + + if ( hasdoc && formatChar->isAnchor() && !formatChar->anchorHref().isEmpty() && + document()->focusIndicator.parag == this && + ( document()->focusIndicator.start >= i && + document()->focusIndicator.start + document()->focusIndicator.len <= i + len || + document()->focusIndicator.start <= i && + document()->focusIndicator.start + document()->focusIndicator.len >= i + len ) ) { + painter.drawWinFocusRect( QRect( startX, lastY, bw, h ) ); + } + +} + +void QTextParag::drawLabel( QPainter* p, int x, int y, int w, int h, int base, const QColorGroup& cg ) +{ + if ( !style() ) + return; + QRect r ( x, y, w, h ); + QStyleSheetItem::ListStyle s = listStyle(); + + p->save(); + p->setPen( defFormat->color() ); + + QFont font2( defFormat->font() ); + if ( length() > 0 ) { + QTextFormat *format = at( 0 )->format(); + if ( format ) { + if ( format->fontSizesInPixels() ) + font2.setPixelSize( at( 0 )->format()->font().pixelSize() ); + else + font2.setPointSize( at( 0 )->format()->font().pointSize() ); + } + } + p->setFont( font2 ); + QFontMetrics fm( p->fontMetrics() ); + int size = fm.lineSpacing() / 3; + + switch ( s ) { + case QStyleSheetItem::ListDecimal: + case QStyleSheetItem::ListLowerAlpha: + case QStyleSheetItem::ListUpperAlpha: + { + int n = numberOfSubParagraph(); + QString l; + switch ( s ) { + case QStyleSheetItem::ListLowerAlpha: + if ( n < 27 ) { + l = QChar( ('a' + (char) (n-1))); + break; + } + case QStyleSheetItem::ListUpperAlpha: + if ( n < 27 ) { + l = QChar( ('A' + (char) (n-1))); + break; + } + break; + default: //QStyleSheetItem::ListDecimal: + l.setNum( n ); + break; + } + l += QString::fromLatin1(". "); + p->drawText( r.right() - fm.width( l ), r.top() + base, l ); + } + break; + case QStyleSheetItem::ListSquare: + { + QRect er( r.right() - size * 2, r.top() + fm.height() / 2 - size / 2, size, size ); + p->fillRect( er , cg.brush( QColorGroup::Foreground ) ); + } + break; + case QStyleSheetItem::ListCircle: + { + QRect er( r.right()-size*2, r.top() + fm.height() / 2 - size / 2, size, size); + p->drawEllipse( er ); + } + break; + case QStyleSheetItem::ListDisc: + default: + { + p->setBrush( cg.brush( QColorGroup::Foreground )); + QRect er( r.right()-size*2, r.top() + fm.height() / 2 - size / 2, size, size); + p->drawEllipse( er ); + p->setBrush( Qt::NoBrush ); + } + break; + } + + p->restore(); +} + +void QTextParag::setStyleSheetItems( const QPtrVector<QStyleSheetItem> &vec ) +{ + styleSheetItemsVec() = vec; + invalidate( 0 ); + lm = rm = tm = bm = flm = -1; + numSubParag = -1; +} + +void QTextParag::setList( bool b, int listStyle ) +{ + if ( !hasdoc ) + return; + + if ( !style() ) { + styleSheetItemsVec().resize( 2 ); + mStyleSheetItemsVec->insert( 0, document()->styleSheet()->item( "html" ) ); + mStyleSheetItemsVec->insert( 1, document()->styleSheet()->item( "p" ) ); + } + + if ( b ) { + if ( style()->displayMode() != QStyleSheetItem::DisplayListItem || this->listStyle() != listStyle ) { + styleSheetItemsVec().remove( styleSheetItemsVec().size() - 1 ); + QStyleSheetItem *item = (*mStyleSheetItemsVec)[ mStyleSheetItemsVec->size() - 1 ]; + if ( item ) + mStyleSheetItemsVec->remove( mStyleSheetItemsVec->size() - 1 ); + mStyleSheetItemsVec->insert( mStyleSheetItemsVec->size() - 1, + listStyle == QStyleSheetItem::ListDisc || listStyle == QStyleSheetItem::ListCircle + || listStyle == QStyleSheetItem::ListSquare ? + document()->styleSheet()->item( "ul" ) : document()->styleSheet()->item( "ol" ) ); + mStyleSheetItemsVec->insert( mStyleSheetItemsVec->size() - 1, document()->styleSheet()->item( "li" ) ); + setListStyle( (QStyleSheetItem::ListStyle)listStyle ); + } else { + return; + } + } else { + if ( style()->displayMode() != QStyleSheetItem::DisplayBlock ) { + styleSheetItemsVec().remove( styleSheetItemsVec().size() - 1 ); + if ( mStyleSheetItemsVec->size() >= 2 ) { + mStyleSheetItemsVec->remove( mStyleSheetItemsVec->size() - 2 ); + mStyleSheetItemsVec->resize( mStyleSheetItemsVec->size() - 2 ); + } else { + mStyleSheetItemsVec->resize( mStyleSheetItemsVec->size() - 1 ); + } + } else { + return; + } + } + invalidate( 0 ); + lm = rm = tm = bm = flm = -1; + numSubParag = -1; + if ( next() ) { + QTextParag *s = next(); + while ( s ) { + s->numSubParag = -1; + s->lm = s->rm = s->tm = s->bm = flm = -1; + s->numSubParag = -1; + s->invalidate( 0 ); + s = s->next(); + } + } +} + +void QTextParag::incDepth() +{ + if ( !style() || !hasdoc ) + return; + if ( style()->displayMode() != QStyleSheetItem::DisplayListItem ) + return; + styleSheetItemsVec().resize( styleSheetItemsVec().size() + 1 ); + mStyleSheetItemsVec->insert( mStyleSheetItemsVec->size() - 1, (*mStyleSheetItemsVec)[ mStyleSheetItemsVec->size() - 2 ] ); + mStyleSheetItemsVec->insert( mStyleSheetItemsVec->size() - 2, + listStyle() == QStyleSheetItem::ListDisc || listStyle() == QStyleSheetItem::ListCircle || + listStyle() == QStyleSheetItem::ListSquare ? + document()->styleSheet()->item( "ul" ) : document()->styleSheet()->item( "ol" ) ); + invalidate( 0 ); + lm = -1; + flm = -1; +} + +void QTextParag::decDepth() +{ + if ( !style() || !hasdoc ) + return; + if ( style()->displayMode() != QStyleSheetItem::DisplayListItem ) + return; + int numLists = 0; + QStyleSheetItem *lastList = 0; + int lastIndex = 0; + int i; + if ( mStyleSheetItemsVec ) { + for ( i = 0; i < (int)mStyleSheetItemsVec->size(); ++i ) { + QStyleSheetItem *item = (*mStyleSheetItemsVec)[ i ]; + if ( item->name() == "ol" || item->name() == "ul" ) { + lastList = item; + lastIndex = i; + numLists++; + } + } + } + + if ( !lastList ) + return; + styleSheetItemsVec().remove( lastIndex ); + for ( i = lastIndex; i < (int)mStyleSheetItemsVec->size() - 1; ++i ) + mStyleSheetItemsVec->insert( i, (*mStyleSheetItemsVec)[ i + 1 ] ); + mStyleSheetItemsVec->resize( mStyleSheetItemsVec->size() - 1 ); + if ( numLists == 1 ) + setList( FALSE, -1 ); + invalidate( 0 ); + lm = -1; + flm = -1; +} + +int QTextParag::listDepth() const +{ + int numLists = 0; + int i; + if ( mStyleSheetItemsVec ) { + for ( i = 0; i < (int)mStyleSheetItemsVec->size(); ++i ) { + QStyleSheetItem *item = (*mStyleSheetItemsVec)[ i ]; + if ( item->name() == "ol" || item->name() == "ul" ) + numLists++; + } + } + return numLists - 1; +} + +int *QTextParag::tabArray() const +{ + int *ta = tArray; + if ( !ta && hasdoc ) + ta = document()->tabArray(); + return ta; +} + +int QTextParag::nextTab( int, int x ) +{ + int *ta = tArray; + if ( hasdoc ) { + if ( !ta ) + ta = document()->tabArray(); + tabStopWidth = document()->tabStopWidth(); + } + if ( ta ) { + int i = 0; + while ( ta[ i ] ) { + if ( ta[ i ] >= x ) + return tArray[ i ]; + ++i; + } + return tArray[ 0 ]; + } else { + int d; + if ( tabStopWidth != 0 ) + d = x / tabStopWidth; + else + return x; + return tabStopWidth * ( d + 1 ); + } +} + +void QTextParag::adjustToPainter( QPainter *p ) +{ + for ( int i = 0; i < length(); ++i ) { + if ( at( i )->isCustom() ) + at( i )->customItem()->adjustToPainter( p ); + } +} + +QTextFormatCollection *QTextParag::formatCollection() const +{ + if ( hasdoc ) + return document()->formatCollection(); + if ( !qFormatCollection ) { + qFormatCollection = new QTextFormatCollection; + static QSingleCleanupHandler<QTextFormatCollection> qtfCleanup; + qtfCleanup.set( &qFormatCollection ); + } + return qFormatCollection; +} + +QString QTextParag::richText() const +{ + QString s; + QTextStringChar *formatChar = 0; + QString spaces; + bool lastCharWasSpace = FALSE; + int firstcol = 0; + for ( int i = 0; i < length()-1; ++i ) { + QTextStringChar *c = &str->at( i ); + if ( c->isAnchor() && !c->anchorName().isEmpty() ) { + if ( c->anchorName().contains( '#' ) ) { + QStringList l = QStringList::split( '#', c->anchorName() ); + for ( QStringList::ConstIterator it = l.begin(); it != l.end(); ++it ) + s += "<a name=\"" + *it + "\"></a>"; + } else { + s += "<a name=\"" + c->anchorName() + "\"></a>"; + } + } + if ( !formatChar ) { + s += c->format()->makeFormatChangeTags( 0, QString::null, c->anchorHref() ); + formatChar = c; + } else if ( ( formatChar->format()->key() != c->format()->key() ) || + (formatChar->isAnchor() != c->isAnchor() && + (!c->anchorHref().isEmpty() || !formatChar->anchorHref().isEmpty() ) ) ) {// lisp was here + + if ( !spaces.isEmpty() ) { + if ( spaces[0] == '\t' || lastCharWasSpace ) + s += "<wsp>" + spaces + "</wsp>"; + else if ( spaces.length() > 1 ) + s += "<wsp>" + spaces.mid(1) + "</wsp> "; + else + s += spaces; + lastCharWasSpace = TRUE; + spaces = QString::null; + } + s += c->format()->makeFormatChangeTags( formatChar->format() , formatChar->anchorHref(), c->anchorHref() ); + formatChar = c; + } + + if ( c->c == ' ' || c->c == '\t' ) { + spaces += c->c; + continue; + } else if ( !spaces.isEmpty() ) { + if ( spaces[0] == '\t' || lastCharWasSpace ) + s += "<wsp>" + spaces + "</wsp>"; + else if ( spaces.length() > 1 ) + s += "<wsp>" + spaces.mid(1) + "</wsp> "; + else + s += spaces; + spaces = QString::null; + if ( s.length() - firstcol > 60 ) { + s += '\n'; + firstcol = s.length(); + } + } + + lastCharWasSpace = FALSE; + if ( c->c == '<' ) { + s += "<"; + } else if ( c->c == '>' ) { + s += ">"; + } else if ( c->isCustom() ) { + s += c->customItem()->richText(); + } else { + s += c->c; + } + } + if ( !spaces.isEmpty() ) { + if ( spaces.length() > 1 || spaces[0] == '\t' || lastCharWasSpace ) + s += "<wsp>" + spaces + "</wsp>"; + else + s += spaces; + } + + if ( formatChar ) + s += formatChar->format()->makeFormatEndTags( formatChar->anchorHref() ); + return s; +} + +void QTextParag::addCommand( QTextCommand *cmd ) +{ + if ( !hasdoc ) + pseudoDocument()->commandHistory->addCommand( cmd ); + else + document()->commands()->addCommand( cmd ); +} + +QTextCursor *QTextParag::undo( QTextCursor *c ) +{ + if ( !hasdoc ) + return pseudoDocument()->commandHistory->undo( c ); + return document()->commands()->undo( c ); +} + +QTextCursor *QTextParag::redo( QTextCursor *c ) +{ + if ( !hasdoc ) + return pseudoDocument()->commandHistory->redo( c ); + return document()->commands()->redo( c ); +} + +int QTextParag::topMargin() const +{ + if ( !p && ( !hasdoc || !document()->addMargins() ) ) + return 0; + if ( tm != -1 ) + return tm; + QStyleSheetItem *item = style(); + if ( !item ) { + ( (QTextParag*)this )->tm = 0; + return 0; + } + + int m = 0; + if ( item->margin( QStyleSheetItem::MarginTop ) != QStyleSheetItem::Undefined ) + m = item->margin( QStyleSheetItem::MarginTop ); + if ( mStyleSheetItemsVec ) { + QStyleSheetItem *it = 0; + QStyleSheetItem *p = prev() ? prev()->style() : 0; + for ( int i = (int)mStyleSheetItemsVec->size() - 2 ; i >= 0; --i ) { + it = (*mStyleSheetItemsVec)[ i ]; + if ( it != p ) + break; + int mar = it->margin( QStyleSheetItem::MarginTop ); + m += (mar != QStyleSheetItem::Undefined) ? mar : 0; + if ( it->displayMode() != QStyleSheetItem::DisplayInline ) + break; + } + } + m = scale( m, QTextFormat::painter() ); + + ( (QTextParag*)this )->tm = m; + return tm; +} + +int QTextParag::bottomMargin() const +{ + if ( bm != -1 ) + return bm; + QStyleSheetItem *item = style(); + if ( !item || !next() ) { + ( (QTextParag*)this )->bm = 0; + return 0; + } + + int m = 0; + if ( item->margin( QStyleSheetItem::MarginBottom ) != QStyleSheetItem::Undefined ) + m = item->margin( QStyleSheetItem::MarginBottom ); + if ( mStyleSheetItemsVec ) { + QStyleSheetItem *it = 0; + QStyleSheetItem *n = next() ? next()->style() : 0; + for ( int i =(int)mStyleSheetItemsVec->size() - 2 ; i >= 0; --i ) { + it = (*mStyleSheetItemsVec)[ i ]; + if ( it != n ) + break; + int mar = it->margin( QStyleSheetItem::MarginBottom ); + m += mar != QStyleSheetItem::Undefined ? mar : 0; + if ( it->displayMode() != QStyleSheetItem::DisplayInline ) + break; + } + } + m = scale ( m, QTextFormat::painter() ); + + ( (QTextParag*)this )->bm = m; + return bm; +} + +int QTextParag::leftMargin() const +{ + if ( lm != -1 ) + return lm; + QStyleSheetItem *item = style(); + if ( !item ) { + ( (QTextParag*)this )->lm = 0; + return 0; + } + int m = 0; + if ( mStyleSheetItemsVec ) { + for ( int i = 0; i < (int)mStyleSheetItemsVec->size(); ++i ) { + item = (*mStyleSheetItemsVec)[ i ]; + int mar = item->margin( QStyleSheetItem::MarginLeft ); + m += mar != QStyleSheetItem::Undefined ? mar : 0; + if ( item->name() == "ol" || item->name() == "ul" ) { + QPainter* oldPainter = QTextFormat::painter(); + QTextFormat::setPainter( 0 ); + m += defFormat->width( '1' ) + + defFormat->width( '2' ) + + defFormat->width( '3' ) + + defFormat->width( '.' ); + QTextFormat::setPainter( oldPainter ); + } + } + } + + m = scale ( m, QTextFormat::painter() ); + + ( (QTextParag*)this )->lm = m; + return lm; +} + +int QTextParag::firstLineMargin() const +{ + if ( flm != -1 ) + return lm; + QStyleSheetItem *item = style(); + if ( !item ) { + ( (QTextParag*)this )->flm = 0; + return 0; + } + int m = 0; + if ( mStyleSheetItemsVec ) { + for ( int i = 0; i < (int)mStyleSheetItemsVec->size(); ++i ) { + item = (*mStyleSheetItemsVec)[ i ]; + int mar = item->margin( QStyleSheetItem::MarginFirstLine ); + m += mar != QStyleSheetItem::Undefined ? mar : 0; + } + } + + m = scale( m, QTextFormat::painter() ); + + ( (QTextParag*)this )->flm = m; + return flm; +} + +int QTextParag::rightMargin() const +{ + if ( rm != -1 ) + return rm; + QStyleSheetItem *item = style(); + if ( !item ) { + ( (QTextParag*)this )->rm = 0; + return 0; + } + int m = 0; + if ( mStyleSheetItemsVec ) { + for ( int i = 0; i < (int)mStyleSheetItemsVec->size(); ++i ) { + item = (*mStyleSheetItemsVec)[ i ]; + int mar = item->margin( QStyleSheetItem::MarginRight ); + m += mar != QStyleSheetItem::Undefined ? mar : 0; + } + } + m = scale( m, QTextFormat::painter() ); + + ( (QTextParag*)this )->rm = m; + return rm; +} + +int QTextParag::lineSpacing() const +{ + QStyleSheetItem *item = style(); + if ( !item ) + return 0; + + int ls = item->lineSpacing(); + if ( ls == QStyleSheetItem::Undefined ) + return 0; + ls = scale( ls, QTextFormat::painter() ); + + return ls; +} + +void QTextParag::copyParagData( QTextParag *parag ) +{ + setStyleSheetItems( parag->styleSheetItems() ); + setListStyle( parag->listStyle() ); + setAlignment( parag->alignment() ); + QColor *c = parag->backgroundColor(); + if ( c ) + setBackgroundColor( *c ); +} + +void QTextParag::show() +{ + if ( visible || !hasdoc ) + return; + visible = TRUE; +} + +void QTextParag::hide() +{ + if ( !visible || !hasdoc ) + return; + visible = FALSE; +} + +void QTextParag::setDirection( QChar::Direction d ) +{ + if ( str && str->direction() != d ) { + str->setDirection( d ); + invalidate( 0 ); + } +} + +QChar::Direction QTextParag::direction() const +{ + return (str ? str->direction() : QChar::DirON ); +} + +void QTextParag::setChanged( bool b, bool recursive ) +{ + changed = b; + if ( recursive ) { + if ( document() && document()->parentParag() ) + document()->parentParag()->setChanged( b, recursive ); + } +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +QTextPreProcessor::QTextPreProcessor() +{ +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextFormatter::QTextFormatter() + : thisminw(0), thiswused(0), wrapEnabled( TRUE ), wrapColumn( -1 ), biw( FALSE ) +{ +} + +/* only used for bidi or complex text reordering + */ +QTextParagLineStart *QTextFormatter::formatLine( QTextParag *parag, QTextString *string, QTextParagLineStart *line, + QTextStringChar *startChar, QTextStringChar *lastChar, int align, int space ) +{ +#ifndef QT_NO_COMPLEXTEXT + if( string->isBidi() ) + return bidiReorderLine( parag, string, line, startChar, lastChar, align, space ); +#endif + space = QMAX( space, 0 ); // #### with nested tables this gets negative because of a bug I didn't find yet, so workaround for now. This also means non-left aligned nested tables do not work at the moment + int start = (startChar - &string->at(0)); + int last = (lastChar - &string->at(0) ); + // do alignment Auto == Left in this case + if ( align & Qt::AlignHCenter || align & Qt::AlignRight ) { + if ( align & Qt::AlignHCenter ) + space /= 2; + for ( int j = start; j <= last; ++j ) + string->at( j ).x += space; + } else if ( align & Qt3::AlignJustify ) { + int numSpaces = 0; + for ( int j = start; j < last; ++j ) { + if( isBreakable( string, j ) ) { + numSpaces++; + } + } + int toAdd = 0; + for ( int k = start + 1; k <= last; ++k ) { + if( isBreakable( string, k ) && numSpaces ) { + int s = space / numSpaces; + toAdd += s; + space -= s; + numSpaces--; + } + string->at( k ).x += toAdd; + } + } + + if ( last >= 0 && last < string->length() ) + line->w = string->at( last ).x + string->width( last ); + else + line->w = 0; + + return new QTextParagLineStart(); +} + +#ifndef QT_NO_COMPLEXTEXT + +#ifdef BIDI_DEBUG +#include <iostream> +#endif + +// collects one line of the paragraph and transforms it to visual order +QTextParagLineStart *QTextFormatter::bidiReorderLine( QTextParag * /*parag*/, QTextString *text, QTextParagLineStart *line, + QTextStringChar *startChar, QTextStringChar *lastChar, int align, int space ) +{ + int start = (startChar - &text->at(0)); + int last = (lastChar - &text->at(0) ); + //qDebug("doing BiDi reordering from %d to %d!", start, last); + + QBidiControl *control = new QBidiControl( line->context(), line->status ); + QString str; + str.setUnicode( 0, last - start + 1 ); + // fill string with logically ordered chars. + QTextStringChar *ch = startChar; + QChar *qch = (QChar *)str.unicode(); + while ( ch <= lastChar ) { + *qch = ch->c; + qch++; + ch++; + } + int x = startChar->x; + + QPtrList<QTextRun> *runs; + runs = QComplexText::bidiReorderLine(control, str, 0, last - start + 1, + (text->isRightToLeft() ? QChar::DirR : QChar::DirL) ); + + // now construct the reordered string out of the runs... + + int numSpaces = 0; + // set the correct alignment. This is a bit messy.... + if( align == Qt3::AlignAuto ) { + // align according to directionality of the paragraph... + if ( text->isRightToLeft() ) + align = Qt::AlignRight; + } + + if ( align & Qt::AlignHCenter ) + x += space/2; + else if ( align & Qt::AlignRight ) + x += space; + else if ( align & Qt3::AlignJustify ) { + for ( int j = start; j < last; ++j ) { + if( isBreakable( text, j ) ) { + numSpaces++; + } + } + } + int toAdd = 0; + bool first = TRUE; + QTextRun *r = runs->first(); + int xmax = -0xffffff; + while ( r ) { + if(r->level %2) { + // odd level, need to reverse the string + int pos = r->stop + start; + while(pos >= r->start + start) { + QTextStringChar *c = &text->at(pos); + if( numSpaces && !first && isBreakable( text, pos ) ) { + int s = space / numSpaces; + toAdd += s; + space -= s; + numSpaces--; + } else if ( first ) { + first = FALSE; + if ( c->c == ' ' ) + x -= c->format()->width( ' ' ); + } + c->x = x + toAdd; + c->rightToLeft = TRUE; + c->startOfRun = FALSE; + int ww = 0; + if ( c->c.unicode() >= 32 || c->c == '\t' || c->c == '\n' || c->isCustom() ) { + ww = text->width( pos ); + } else { + ww = c->format()->width( ' ' ); + } + if ( xmax < x + toAdd + ww ) xmax = x + toAdd + ww; + x += ww; + pos--; + } + } else { + int pos = r->start + start; + while(pos <= r->stop + start) { + QTextStringChar* c = &text->at(pos); + if( numSpaces && !first && isBreakable( text, pos ) ) { + int s = space / numSpaces; + toAdd += s; + space -= s; + numSpaces--; + } else if ( first ) { + first = FALSE; + if ( c->c == ' ' ) + x -= c->format()->width( ' ' ); + } + c->x = x + toAdd; + c->rightToLeft = FALSE; + c->startOfRun = FALSE; + int ww = 0; + if ( c->c.unicode() >= 32 || c->c == '\t' || c->isCustom() ) { + ww = text->width( pos ); + } else { + ww = c->format()->width( ' ' ); + } + //qDebug("setting char %d at pos %d", pos, x); + if ( xmax < x + toAdd + ww ) xmax = x + toAdd + ww; + x += ww; + pos++; + } + } + text->at( r->start + start ).startOfRun = TRUE; + r = runs->next(); + } + + line->w = xmax + 10; + QTextParagLineStart *ls = new QTextParagLineStart( control->context, control->status ); + delete control; + delete runs; + return ls; +} +#endif + +bool QTextFormatter::isBreakable( QTextString *string, int pos ) const +{ + const QChar &c = string->at( pos ).c; + char ch = c.latin1(); + if ( c.isSpace() && ch != '\n' && c.unicode() != 0x00a0U ) + return TRUE; + if ( c.unicode() == 0xad ) // soft hyphen + return TRUE; + if ( !ch ) { + // not latin1, need to do more sophisticated checks for other scripts + uchar row = c.row(); + if ( row == 0x0e ) { + // 0e00 - 0e7f == Thai + if ( c.cell() < 0x80 ) { +#ifdef HAVE_THAI_BREAKS + // check for thai + if( string != cachedString ) { + // build up string of thai chars + QTextCodec *thaiCodec = QTextCodec::codecForMib(2259); + if ( !thaiCache ) + thaiCache = new QCString; + if ( !thaiIt ) + thaiIt = ThBreakIterator::createWordInstance(); + *thaiCache = thaiCodec->fromUnicode( s->string() ); + } + thaiIt->setText(thaiCache->data()); + for(int i = thaiIt->first(); i != thaiIt->DONE; i = thaiIt->next() ) { + if( i == pos ) + return TRUE; + if( i > pos ) + return FALSE; + } + return FALSE; +#else + // if we don't have a thai line breaking lib, allow + // breaks everywhere except directly before punctuation. + return TRUE; +#endif + } else + return FALSE; + } + if ( row < 0x11 ) // no asian font + return FALSE; + if ( row > 0x2d && row < 0xfb || row == 0x11 ) + // asian line breaking. Everywhere allowed except directly + // in front of a punctuation character. + return TRUE; + } + return FALSE; +} + +void QTextFormatter::insertLineStart( QTextParag *parag, int index, QTextParagLineStart *ls ) +{ + if ( index > 0 ) { // we can assume that only first line starts are insrted multiple times + parag->lineStartList().insert( index, ls ); + return; + } + QMap<int, QTextParagLineStart*>::Iterator it; + if ( ( it = parag->lineStartList().find( index ) ) == parag->lineStartList().end() ) { + parag->lineStartList().insert( index, ls ); + } else { + delete *it; + parag->lineStartList().remove( it ); + parag->lineStartList().insert( index, ls ); + } +} + + +/* Standard pagebreak algorithm using QTextFlow::adjustFlow. Returns + the shift of the paragraphs bottom line. + */ +int QTextFormatter::formatVertically( QTextDocument* doc, QTextParag* parag ) +{ + int oldHeight = parag->rect().height(); + QMap<int, QTextParagLineStart*>& lineStarts = parag->lineStartList(); + QMap<int, QTextParagLineStart*>::Iterator it = lineStarts.begin(); + int h = doc->addMargins() ? parag->topMargin() : 0; + for ( ; it != lineStarts.end() ; ++it ) { + QTextParagLineStart * ls = it.data(); + ls->y = h; + QTextStringChar *c = ¶g->string()->at(it.key()); + if ( c && c->customItem() && c->customItem()->ownLine() ) { + int h = c->customItem()->height; + c->customItem()->pageBreak( parag->rect().y() + ls->y + ls->baseLine - h, doc->flow() ); + int delta = c->customItem()->height - h; + ls->h += delta; + if ( delta ) + parag->setMovedDown( TRUE ); + } else { + int shift = doc->flow()->adjustFlow( parag->rect().y() + ls->y, ls->w, ls->h ); + ls->y += shift; + if ( shift ) + parag->setMovedDown( TRUE ); + } + h = ls->y + ls->h; + } + int m = parag->bottomMargin(); + if ( parag->next() && doc && !doc->addMargins() ) + m = QMAX( m, parag->next()->topMargin() ); + if ( parag->next() && parag->next()->isLineBreak() ) + m = 0; + h += m; + parag->setHeight( h ); + return h - oldHeight; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextFormatterBreakInWords::QTextFormatterBreakInWords() +{ +} + +int QTextFormatterBreakInWords::format( QTextDocument *doc,QTextParag *parag, + int start, const QMap<int, QTextParagLineStart*> & ) +{ + QTextStringChar *c = 0; + QTextStringChar *firstChar = 0; + int left = doc ? parag->leftMargin() + doc->leftMargin() : 4; + int x = left + ( doc ? parag->firstLineMargin() : 0 ); + int dw = parag->documentVisibleWidth() - ( doc ? doc->rightMargin() : 0 ); + int y = doc && doc->addMargins() ? parag->topMargin() : 0; + int h = y; + int len = parag->length(); + if ( doc ) + x = doc->flow()->adjustLMargin( y + parag->rect().y(), parag->rect().height(), x, 4 ); + int rm = parag->rightMargin(); + int w = dw - ( doc ? doc->flow()->adjustRMargin( y + parag->rect().y(), parag->rect().height(), rm, 4 ) : 0 ); + bool fullWidth = TRUE; + int minw = 0; + int wused = 0; + bool wrapEnabled = isWrapEnabled( parag ); + + start = 0; //######### what is the point with start?! (Matthias) + if ( start == 0 ) + c = ¶g->string()->at( 0 ); + + int i = start; + QTextParagLineStart *lineStart = new QTextParagLineStart( y, y, 0 ); + insertLineStart( parag, 0, lineStart ); + + QPainter *painter = QTextFormat::painter(); + + int col = 0; + int ww = 0; + QChar lastChr; + for ( ; i < len; ++i, ++col ) { + if ( c ) + lastChr = c->c; + c = ¶g->string()->at( i ); + c->rightToLeft = FALSE; + // ### the lines below should not be needed + if ( painter ) + c->format()->setPainter( painter ); + if ( i > 0 ) { + c->lineStart = 0; + } else { + c->lineStart = 1; + firstChar = c; + } + if ( c->c.unicode() >= 32 || c->isCustom() ) { + ww = parag->string()->width( i ); + } else if ( c->c == '\t' ) { + int nx = parag->nextTab( i, x - left ) + left; + if ( nx < x ) + ww = w - x; + else + ww = nx - x; + } else { + ww = c->format()->width( ' ' ); + } + + if ( c->isCustom() && c->customItem()->ownLine() ) { + x = doc ? doc->flow()->adjustLMargin( y + parag->rect().y(), parag->rect().height(), left, 4 ) : left; + w = dw - ( doc ? doc->flow()->adjustRMargin( y + parag->rect().y(), parag->rect().height(), rm, 4 ) : 0 ); + c->customItem()->resize( w - x ); + w = dw; + y += h; + h = c->height(); + lineStart = new QTextParagLineStart( y, h, h ); + insertLineStart( parag, i, lineStart ); + c->lineStart = 1; + firstChar = c; + x = 0xffffff; + continue; + } + + if ( wrapEnabled && + ( wrapAtColumn() == -1 && x + ww > w || + wrapAtColumn() != -1 && col >= wrapAtColumn() ) || + parag->isNewLinesAllowed() && lastChr == '\n' ) { + x = doc ? parag->document()->flow()->adjustLMargin( y + parag->rect().y(), parag->rect().height(), left, 4 ) : left; + w = dw; + y += h; + h = c->height(); + lineStart = formatLine( parag, parag->string(), lineStart, firstChar, c-1 ); + lineStart->y = y; + insertLineStart( parag, i, lineStart ); + lineStart->baseLine = c->ascent(); + lineStart->h = c->height(); + c->lineStart = 1; + firstChar = c; + col = 0; + if ( wrapAtColumn() != -1 ) + minw = QMAX( minw, w ); + } else if ( lineStart ) { + lineStart->baseLine = QMAX( lineStart->baseLine, c->ascent() ); + h = QMAX( h, c->height() ); + lineStart->h = h; + } + + c->x = x; + x += ww; + wused = QMAX( wused, x ); + } + + int m = parag->bottomMargin(); + if ( parag->next() && doc && !doc->addMargins() ) + m = QMAX( m, parag->next()->topMargin() ); + parag->setFullWidth( fullWidth ); + if ( parag->next() && parag->next()->isLineBreak() ) + m = 0; + y += h + m; + if ( !wrapEnabled ) + minw = QMAX(minw, wused); + + thisminw = minw; + thiswused = wused; + return y; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextFormatterBreakWords::QTextFormatterBreakWords() +{ +} + +#define DO_FLOW( lineStart ) do{ if ( doc && doc->isPageBreakEnabled() ) { \ + int yflow = lineStart->y + parag->rect().y();\ + int shift = doc->flow()->adjustFlow( yflow, dw, lineStart->h ); \ + lineStart->y += shift;\ + y += shift;\ + }}while(FALSE) + +int QTextFormatterBreakWords::format( QTextDocument *doc, QTextParag *parag, + int start, const QMap<int, QTextParagLineStart*> & ) +{ + QTextStringChar *c = 0; + QTextStringChar *firstChar = 0; + QTextString *string = parag->string(); + int left = doc ? parag->leftMargin() + doc->leftMargin() : 0; + int x = left + ( doc ? parag->firstLineMargin() : 0 ); + int y = doc && doc->addMargins() ? parag->topMargin() : 0; + int h = y; + int len = parag->length(); + if ( doc ) + x = doc->flow()->adjustLMargin( y + parag->rect().y(), parag->rect().height(), x, 0 ); + int dw = parag->documentVisibleWidth() - ( doc ? ( left != x ? 0 : doc->rightMargin() ) : 0 ); + + int curLeft = x; + int rm = parag->rightMargin(); + int rdiff = doc ? doc->flow()->adjustRMargin( y + parag->rect().y(), parag->rect().height(), rm, 0 ) : 0; + int w = dw - rdiff; + bool fullWidth = TRUE; + int marg = left + rdiff; + int minw = 0; + int wused = 0; + int tminw = marg; + int linespace = doc ? parag->lineSpacing() : 0; + bool wrapEnabled = isWrapEnabled( parag ); + + start = 0; + if ( start == 0 ) + c = ¶g->string()->at( 0 ); + + int i = start; + QTextParagLineStart *lineStart = new QTextParagLineStart( y, y, 0 ); + insertLineStart( parag, 0, lineStart ); + int lastBreak = -1; + int tmpBaseLine = 0, tmph = 0; + bool lastWasNonInlineCustom = FALSE; + + int align = parag->alignment(); + if ( align == Qt3::AlignAuto && doc && doc->alignment() != Qt3::AlignAuto ) + align = doc->alignment(); + + align &= Qt3::AlignHorizontal_Mask; + + QPainter *painter = QTextFormat::painter(); + int col = 0; + int ww = 0; + QChar lastChr; + for ( ; i < len; ++i, ++col ) { + if ( c ) + lastChr = c->c; + // ### next line should not be needed + if ( painter ) + c->format()->setPainter( painter ); + c = &string->at( i ); + c->rightToLeft = FALSE; + if ( i > 0 && (x > curLeft || ww == 0) || lastWasNonInlineCustom ) { + c->lineStart = 0; + } else { + c->lineStart = 1; + firstChar = c; + } + + if ( c->isCustom() && c->customItem()->placement() != QTextCustomItem::PlaceInline ) + lastWasNonInlineCustom = TRUE; + else + lastWasNonInlineCustom = FALSE; + + if ( c->c.unicode() >= 32 || c->isCustom() ) { + ww = string->width( i ); + } else if ( c->c == '\t' ) { + int nx = parag->nextTab( i, x - left ) + left; + if ( nx < x ) + ww = w - x; + else + ww = nx - x; + } else { + ww = c->format()->width( ' ' ); + } + + // last character ("invisible" space) has no width + if ( i == len - 1 ) + ww = 0; + + QTextCustomItem* ci = c->customItem(); + if ( c->isCustom() && ci->ownLine() ) { + x = doc ? doc->flow()->adjustLMargin( y + parag->rect().y(), parag->rect().height(), left, 4 ) : left; + w = dw - ( doc ? doc->flow()->adjustRMargin( y + parag->rect().y(), parag->rect().height(), rm, 4 ) : 0 ); + QTextParagLineStart *lineStart2 = formatLine( parag, string, lineStart, firstChar, c-1, align, w - x ); + ci->resize( w - x); + if ( ci->width < w - x ) { + if ( align & Qt::AlignHCenter ) + x = ( w - ci->width ) / 2; + else if ( align & Qt::AlignRight ) { + x = w - ci->width; + } + } + c->x = x; + curLeft = x; + if ( i == 0 || !isBreakable( string, i - 1 ) || string->at( i - 1 ).lineStart == 0 ) { + y += QMAX( h, tmph ); + tmph = c->height() + linespace; + h = tmph; + lineStart = lineStart2; + lineStart->y = y; + insertLineStart( parag, i, lineStart ); + c->lineStart = 1; + firstChar = c; + } else { + tmph = c->height() + linespace; + h = tmph; + delete lineStart2; + } + lineStart->h = h; + lineStart->baseLine = h; + tmpBaseLine = lineStart->baseLine; + lastBreak = -2; + x = 0xffffff; + minw = QMAX( minw, tminw ); + + int tw = ci->minimumWidth(); + if ( tw < QWIDGETSIZE_MAX ) + tminw = tw; + else + tminw = marg; + wused = QMAX( wused, ci->width ); + continue; + } else if ( c->isCustom() && ci->placement() != QTextCustomItem::PlaceInline ) { + int tw = ci->minimumWidth(); + if ( tw < QWIDGETSIZE_MAX ) + minw = QMAX( minw, tw ); + } + + if ( wrapEnabled && ( !c->c.isSpace() || lastBreak == -2 ) + && ( lastBreak != -1 || allowBreakInWords() ) && + ( wrapAtColumn() == -1 && x + ww > w && lastBreak != -1 || + wrapAtColumn() == -1 && x + ww > w - 4 && lastBreak == -1 && allowBreakInWords() || + wrapAtColumn() != -1 && col >= wrapAtColumn() ) || + parag->isNewLinesAllowed() && lastChr == '\n' && firstChar < c ) { + if ( wrapAtColumn() != -1 ) + minw = QMAX( minw, x + ww ); + if ( lastBreak < 0 ) { + if ( lineStart ) { + lineStart->baseLine = QMAX( lineStart->baseLine, tmpBaseLine ); + h = QMAX( h, tmph ); + lineStart->h = h; + DO_FLOW( lineStart ); + } + lineStart = formatLine( parag, string, lineStart, firstChar, c-1, align, w - x ); + x = doc ? doc->flow()->adjustLMargin( y + parag->rect().y(), parag->rect().height(), left, 4 ) : left; + w = dw - ( doc ? doc->flow()->adjustRMargin( y + parag->rect().y(), parag->rect().height(), rm, 4 ) : 0 ); + if ( parag->isNewLinesAllowed() && c->c == '\t' ) { + int nx = parag->nextTab( i, x - left ) + left; + if ( nx < x ) + ww = w - x; + else + ww = nx - x; + } + curLeft = x; + y += h; + tmph = c->height() + linespace; + h = 0; + lineStart->y = y; + insertLineStart( parag, i, lineStart ); + lineStart->baseLine = c->ascent(); + lineStart->h = c->height(); + c->lineStart = 1; + firstChar = c; + tmpBaseLine = lineStart->baseLine; + lastBreak = -1; + col = 0; + } else { + DO_FLOW( lineStart ); + i = lastBreak; + lineStart = formatLine( parag, string, lineStart, firstChar, parag->at( lastBreak ), align, w - string->at( i ).x ); + x = doc ? doc->flow()->adjustLMargin( y + parag->rect().y(), parag->rect().height(), left, 4 ) : left; + w = dw - ( doc ? doc->flow()->adjustRMargin( y + parag->rect().y(), parag->rect().height(), rm, 4 ) : 0 ); + if ( parag->isNewLinesAllowed() && c->c == '\t' ) { + int nx = parag->nextTab( i, x - left ) + left; + if ( nx < x ) + ww = w - x; + else + ww = nx - x; + } + curLeft = x; + y += h; + tmph = c->height() + linespace; + h = tmph; + lineStart->y = y; + insertLineStart( parag, i + 1, lineStart ); + lineStart->baseLine = c->ascent(); + lineStart->h = c->height(); + c->lineStart = 1; + firstChar = c; + tmpBaseLine = lineStart->baseLine; + lastBreak = -1; + col = 0; + tminw = marg; + continue; + } + } else if ( lineStart && ( isBreakable( string, i ) || parag->isNewLinesAllowed() && c->c == '\n' ) ) { + if ( len <= 2 || i < len - 1 ) { + tmpBaseLine = QMAX( tmpBaseLine, c->ascent() ); + tmph = QMAX( tmph, c->height() + linespace ); + } + minw = QMAX( minw, tminw ); + tminw = marg + ww; + lineStart->baseLine = QMAX( lineStart->baseLine, tmpBaseLine ); + h = QMAX( h, tmph ); + lineStart->h = h; + if ( i < len - 2 || c->c != ' ' ) + lastBreak = i; + } else { + tminw += ww; + int belowBaseLine = QMAX( tmph - tmpBaseLine, c->height() + linespace - c->ascent() ); + tmpBaseLine = QMAX( tmpBaseLine, c->ascent() ); + tmph = tmpBaseLine + belowBaseLine; + } + + c->x = x; + x += ww; + wused = QMAX( wused, x ); + } + + // ### hack. The last char in the paragraph is always invisible, and somehow sometimes has a wrong format. It changes between + // layouting and printing. This corrects some layouting errors in BiDi mode due to this. + if ( len > 1 && !c->isAnchor() ) { + c->format()->removeRef(); + c->setFormat( string->at( len - 2 ).format() ); + c->format()->addRef(); + } + + if ( lineStart ) { + lineStart->baseLine = QMAX( lineStart->baseLine, tmpBaseLine ); + h = QMAX( h, tmph ); + lineStart->h = h; + // last line in a paragraph is not justified + if ( align == Qt3::AlignJustify ) + align = Qt3::AlignAuto; + DO_FLOW( lineStart ); + lineStart = formatLine( parag, string, lineStart, firstChar, c, align, w - x ); + delete lineStart; + } + + minw = QMAX( minw, tminw ); + + int m = parag->bottomMargin(); + if ( parag->next() && doc && !doc->addMargins() ) + m = QMAX( m, parag->next()->topMargin() ); + parag->setFullWidth( fullWidth ); + if ( parag->next() && parag->next()->isLineBreak() ) + m = 0; + y += h + m; + + wused += rm; + if ( !wrapEnabled || wrapAtColumn() != -1 ) + minw = QMAX(minw, wused); + thisminw = minw; + thiswused = wused; + return y; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextIndent::QTextIndent() +{ +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextFormatCollection::QTextFormatCollection() + : cKey( 307 ), sheet( 0 ) +{ + defFormat = new QTextFormat( QApplication::font(), + QApplication::palette().color( QPalette::Active, QColorGroup::Text ) ); + lastFormat = cres = 0; + cflags = -1; + cKey.setAutoDelete( TRUE ); + cachedFormat = 0; +} + +QTextFormatCollection::~QTextFormatCollection() +{ + delete defFormat; +} + +QTextFormat *QTextFormatCollection::format( QTextFormat *f ) +{ + if ( f->parent() == this || f == defFormat ) { +#ifdef DEBUG_COLLECTION + qDebug( "need '%s', best case!", f->key().latin1() ); +#endif + lastFormat = f; + lastFormat->addRef(); + return lastFormat; + } + + if ( f == lastFormat || ( lastFormat && f->key() == lastFormat->key() ) ) { +#ifdef DEBUG_COLLECTION + qDebug( "need '%s', good case!", f->key().latin1() ); +#endif + lastFormat->addRef(); + return lastFormat; + } + + QTextFormat *fm = cKey.find( f->key() ); + if ( fm ) { +#ifdef DEBUG_COLLECTION + qDebug( "need '%s', normal case!", f->key().latin1() ); +#endif + lastFormat = fm; + lastFormat->addRef(); + return lastFormat; + } + + if ( f->key() == defFormat->key() ) + return defFormat; + +#ifdef DEBUG_COLLECTION + qDebug( "need '%s', worst case!", f->key().latin1() ); +#endif + lastFormat = createFormat( *f ); + lastFormat->collection = this; + cKey.insert( lastFormat->key(), lastFormat ); + return lastFormat; +} + +QTextFormat *QTextFormatCollection::format( QTextFormat *of, QTextFormat *nf, int flags ) +{ + if ( cres && kof == of->key() && knf == nf->key() && cflags == flags ) { +#ifdef DEBUG_COLLECTION + qDebug( "mix of '%s' and '%s, best case!", of->key().latin1(), nf->key().latin1() ); +#endif + cres->addRef(); + return cres; + } + + cres = createFormat( *of ); + kof = of->key(); + knf = nf->key(); + cflags = flags; + if ( flags & QTextFormat::Bold ) + cres->fn.setBold( nf->fn.bold() ); + if ( flags & QTextFormat::Italic ) + cres->fn.setItalic( nf->fn.italic() ); + if ( flags & QTextFormat::Underline ) + cres->fn.setUnderline( nf->fn.underline() ); + if ( flags & QTextFormat::Family ) + cres->fn.setFamily( nf->fn.family() ); + if ( flags & QTextFormat::Size ) { + if ( of->usePixelSizes ) + cres->fn.setPixelSize( nf->fn.pixelSize() ); + else + cres->fn.setPointSize( nf->fn.pointSize() ); + } + if ( flags & QTextFormat::Color ) + cres->col = nf->col; + if ( flags & QTextFormat::Misspelled ) + cres->missp = nf->missp; + if ( flags & QTextFormat::VAlign ) + cres->ha = nf->ha; + cres->update(); + + QTextFormat *fm = cKey.find( cres->key() ); + if ( !fm ) { +#ifdef DEBUG_COLLECTION + qDebug( "mix of '%s' and '%s, worst case!", of->key().latin1(), nf->key().latin1() ); +#endif + cres->collection = this; + cKey.insert( cres->key(), cres ); + } else { +#ifdef DEBUG_COLLECTION + qDebug( "mix of '%s' and '%s, good case!", of->key().latin1(), nf->key().latin1() ); +#endif + delete cres; + cres = fm; + cres->addRef(); + } + + return cres; +} + +QTextFormat *QTextFormatCollection::format( const QFont &f, const QColor &c ) +{ + if ( cachedFormat && cfont == f && ccol == c ) { +#ifdef DEBUG_COLLECTION + qDebug( "format of font and col '%s' - best case", cachedFormat->key().latin1() ); +#endif + cachedFormat->addRef(); + return cachedFormat; + } + + QString key = QTextFormat::getKey( f, c, FALSE, QTextFormat::AlignNormal ); + cachedFormat = cKey.find( key ); + cfont = f; + ccol = c; + + if ( cachedFormat ) { +#ifdef DEBUG_COLLECTION + qDebug( "format of font and col '%s' - good case", cachedFormat->key().latin1() ); +#endif + cachedFormat->addRef(); + return cachedFormat; + } + + if ( key == defFormat->key() ) + return defFormat; + + cachedFormat = createFormat( f, c ); + cachedFormat->collection = this; + cKey.insert( cachedFormat->key(), cachedFormat ); + if ( cachedFormat->key() != key ) + qWarning("ASSERT: keys for format not identical: '%s '%s'", cachedFormat->key().latin1(), key.latin1() ); +#ifdef DEBUG_COLLECTION + qDebug( "format of font and col '%s' - worst case", cachedFormat->key().latin1() ); +#endif + return cachedFormat; +} + +void QTextFormatCollection::remove( QTextFormat *f ) +{ + if ( lastFormat == f ) + lastFormat = 0; + if ( cres == f ) + cres = 0; + if ( cachedFormat == f ) + cachedFormat = 0; + cKey.remove( f->key() ); +} + +void QTextFormatCollection::debug() +{ +#ifdef DEBUG_COLLECTION + qDebug( "------------ QTextFormatCollection: debug --------------- BEGIN" ); + QDictIterator<QTextFormat> it( cKey ); + for ( ; it.current(); ++it ) { + qDebug( "format '%s' (%p): refcount: %d", it.current()->key().latin1(), + it.current(), it.current()->ref ); + } + qDebug( "------------ QTextFormatCollection: debug --------------- END" ); +#endif +} + +void QTextFormatCollection::updateStyles() +{ + QDictIterator<QTextFormat> it( cKey ); + QTextFormat *f; + while ( ( f = it.current() ) ) { + ++it; + f->updateStyle(); + } + updateKeys(); +} + +void QTextFormatCollection::updateFontSizes( int base, bool usePixels ) +{ + QDictIterator<QTextFormat> it( cKey ); + QTextFormat *f; + while ( ( f = it.current() ) ) { + ++it; + f->stdSize = base; + f->usePixelSizes = usePixels; + if ( usePixels ) + f->fn.setPixelSize( f->stdSize ); + else + f->fn.setPointSize( f->stdSize ); + styleSheet()->scaleFont( f->fn, f->logicalFontSize ); + f->update(); + } + f = defFormat; + f->stdSize = base; + f->usePixelSizes = usePixels; + if ( usePixels ) + f->fn.setPixelSize( f->stdSize ); + else + f->fn.setPointSize( f->stdSize ); + styleSheet()->scaleFont( f->fn, f->logicalFontSize ); + f->update(); + updateKeys(); +} + +void QTextFormatCollection::updateFontAttributes( const QFont &f, const QFont &old ) +{ + QDictIterator<QTextFormat> it( cKey ); + QTextFormat *fm; + while ( ( fm = it.current() ) ) { + ++it; + if ( fm->fn.family() == old.family() && + fm->fn.weight() == old.weight() && + fm->fn.italic() == old.italic() && + fm->fn.underline() == old.underline() ) { + fm->fn.setFamily( f.family() ); + fm->fn.setWeight( f.weight() ); + fm->fn.setItalic( f.italic() ); + fm->fn.setUnderline( f.underline() ); + fm->update(); + } + } + fm = defFormat; + if ( fm->fn.family() == old.family() && + fm->fn.weight() == old.weight() && + fm->fn.italic() == old.italic() && + fm->fn.underline() == old.underline() ) { + fm->fn.setFamily( f.family() ); + fm->fn.setWeight( f.weight() ); + fm->fn.setItalic( f.italic() ); + fm->fn.setUnderline( f.underline() ); + fm->update(); + } + updateKeys(); +} + + +// the keys in cKey have changed, rebuild the hashtable +void QTextFormatCollection::updateKeys() +{ + if ( cKey.isEmpty() ) + return; + cKey.setAutoDelete( FALSE ); + QTextFormat** formats = new QTextFormat*[ cKey.count() + 1]; + QTextFormat **f = formats; + QDictIterator<QTextFormat> it( cKey ); + while ( ( *f = it.current() ) ) { + ++it; + ++f; + } + cKey.clear(); + for ( f = formats; *f; f++ ) + cKey.insert( (*f)->key(), *f ); + cKey.setAutoDelete( TRUE ); +} + + + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +void QTextFormat::setBold( bool b ) +{ + if ( b == fn.bold() ) + return; + fn.setBold( b ); + update(); +} + +void QTextFormat::setMisspelled( bool b ) +{ + if ( b == (bool)missp ) + return; + missp = b; + update(); +} + +void QTextFormat::setVAlign( VerticalAlignment a ) +{ + if ( a == ha ) + return; + ha = a; + update(); +} + +void QTextFormat::setItalic( bool b ) +{ + if ( b == fn.italic() ) + return; + fn.setItalic( b ); + update(); +} + +void QTextFormat::setUnderline( bool b ) +{ + if ( b == fn.underline() ) + return; + fn.setUnderline( b ); + update(); +} + +void QTextFormat::setFamily( const QString &f ) +{ + if ( f == fn.family() ) + return; + fn.setFamily( f ); + update(); +} + +void QTextFormat::setPointSize( int s ) +{ + if ( s == fn.pointSize() ) + return; + fn.setPointSize( s ); + usePixelSizes = FALSE; + update(); +} + +void QTextFormat::setFont( const QFont &f ) +{ + if ( f == fn && !k.isEmpty() ) + return; + fn = f; + update(); +} + +void QTextFormat::setColor( const QColor &c ) +{ + if ( c == col ) + return; + col = c; + update(); +} + +static int makeLogicFontSize( int s ) +{ + int defSize = QApplication::font().pointSize(); + if ( s < defSize - 4 ) + return 1; + if ( s < defSize ) + return 2; + if ( s < defSize + 4 ) + return 3; + if ( s < defSize + 8 ) + return 4; + if ( s < defSize + 12 ) + return 5; + if (s < defSize + 16 ) + return 6; + return 7; +} + +static QTextFormat *defaultFormat = 0; + +QString QTextFormat::makeFormatChangeTags( QTextFormat *f, const QString& oldAnchorHref, const QString& anchorHref ) const +{ + if ( !defaultFormat ) // #### wrong, use the document's default format instead + defaultFormat = new QTextFormat( QApplication::font(), + QApplication::palette().color( QPalette::Active, QColorGroup::Text ) ); + + QString tag; + if ( f ) { + if ( f->font() != defaultFormat->font() ) { + if ( f->font().family() != defaultFormat->font().family() + || f->font().pointSize() != defaultFormat->font().pointSize() + || f->color().rgb() != defaultFormat->color().rgb() ) + tag += "</font>"; + if ( f->font().underline() && f->font().underline() != defaultFormat->font().underline() ) + tag += "</u>"; + if ( f->font().italic() && f->font().italic() != defaultFormat->font().italic() ) + tag += "</i>"; + if ( f->font().bold() && f->font().bold() != defaultFormat->font().bold() ) + tag += "</b>"; + } + if ( !oldAnchorHref.isEmpty() ) + tag += "</a>"; + } + + if ( !anchorHref.isEmpty() ) + tag += "<a href=\"" + anchorHref + "\">"; + + if ( font() != defaultFormat->font() ) { + if ( font().bold() && font().bold() != defaultFormat->font().bold() ) + tag += "<b>"; + if ( font().italic() && font().italic() != defaultFormat->font().italic() ) + tag += "<i>"; + if ( font().underline() && font().underline() != defaultFormat->font().underline() ) + tag += "<u>"; + } + if ( font() != defaultFormat->font() + || color().rgb() != defaultFormat->color().rgb() ) { + QString f; + if ( font().family() != defaultFormat->font().family() ) + f +=" face=\"" + fn.family() + "\""; + if ( font().pointSize() != defaultFormat->font().pointSize() ) { + f +=" size=\"" + QString::number( makeLogicFontSize( fn.pointSize() ) ) + "\""; + f +=" style=\"font-size:" + QString::number( fn.pointSize() ) + "pt\""; + } + if ( color().rgb() != defaultFormat->color().rgb() ) + f +=" color=\"" + col.name() + "\""; + if ( !f.isEmpty() ) + tag += "<font" + f + ">"; + } + + return tag; +} + +QString QTextFormat::makeFormatEndTags( const QString& anchorHref ) const +{ + if ( !defaultFormat ) + defaultFormat = new QTextFormat( QApplication::font(), + QApplication::palette().color( QPalette::Active, QColorGroup::Text ) ); + + QString tag; + if ( font() != defaultFormat->font() ) { + if ( font().family() != defaultFormat->font().family() + || font().pointSize() != defaultFormat->font().pointSize() + || color().rgb() != defaultFormat->color().rgb() ) + tag += "</font>"; + if ( font().underline() && font().underline() != defaultFormat->font().underline() ) + tag += "</u>"; + if ( font().italic() && font().italic() != defaultFormat->font().italic() ) + tag += "</i>"; + if ( font().bold() && font().bold() != defaultFormat->font().bold() ) + tag += "</b>"; + } + if ( !anchorHref.isEmpty() ) + tag += "</a>"; + return tag; +} + +QTextFormat QTextFormat::makeTextFormat( const QStyleSheetItem *style, const QMap<QString,QString>& attr ) const +{ + QTextFormat format(*this); + if ( style ) { + format.style = style->name(); + if ( style->name() == "font") { + if ( attr.contains("color") ) { + QString s = attr["color"]; + if ( !s.isEmpty() ) { + format.col.setNamedColor( s ); + format.linkColor = FALSE; + } + } + if ( attr.contains("size") ) { + QString a = attr["size"]; + int n = a.toInt(); + if ( a[0] == '+' || a[0] == '-' ) + n += format.logicalFontSize; + format.logicalFontSize = n; + if ( format.usePixelSizes ) + format.fn.setPixelSize( format.stdSize ); + else + format.fn.setPointSize( format.stdSize ); + style->styleSheet()->scaleFont( format.fn, format.logicalFontSize ); + } + if ( attr.contains("style" ) ) { + QString a = attr["style"]; + if ( a.startsWith( "font-size:" ) ) { + QString s = a.mid( a.find( ':' ) + 1 ); + int n = s.left( s.length() - 2 ).toInt(); + format.logicalFontSize = 0; + if ( format.usePixelSizes ) + format.fn.setPixelSize( n ); + else + format.fn.setPointSize( n ); + } + } + if ( attr.contains("face") ) { + QString a = attr["face"]; + if ( a.contains(',') ) + a = a.left( a.find(',') ); + format.fn.setFamily( a ); + } + } else { + if ( !style->isAnchor() && style->color().isValid() ) { + // the style is not an anchor and defines a color. + // It might be used inside an anchor and it should + // override the link color. + format.linkColor = FALSE; + } + switch ( style->verticalAlignment() ) { + case QStyleSheetItem::VAlignBaseline: + format.setVAlign( QTextFormat::AlignNormal ); + break; + case QStyleSheetItem::VAlignSuper: + format.setVAlign( QTextFormat::AlignSuperScript ); + break; + case QStyleSheetItem::VAlignSub: + format.setVAlign( QTextFormat::AlignSubScript ); + break; + } + + if ( style->fontWeight() != QStyleSheetItem::Undefined ) + format.fn.setWeight( style->fontWeight() ); + if ( style->fontSize() != QStyleSheetItem::Undefined ) { + format.fn.setPointSize( style->fontSize() ); + } else if ( style->logicalFontSize() != QStyleSheetItem::Undefined ) { + format.logicalFontSize = style->logicalFontSize(); + if ( format.usePixelSizes ) + format.fn.setPixelSize( format.stdSize ); + else + format.fn.setPointSize( format.stdSize ); + style->styleSheet()->scaleFont( format.fn, format.logicalFontSize ); + } else if ( style->logicalFontSizeStep() ) { + format.logicalFontSize += style->logicalFontSizeStep(); + if ( format.usePixelSizes ) + format.fn.setPixelSize( format.stdSize ); + else + format.fn.setPointSize( format.stdSize ); + style->styleSheet()->scaleFont( format.fn, format.logicalFontSize ); + } + if ( !style->fontFamily().isEmpty() ) + format.fn.setFamily( style->fontFamily() ); + if ( style->color().isValid() ) + format.col = style->color(); + if ( style->definesFontItalic() ) + format.fn.setItalic( style->fontItalic() ); + if ( style->definesFontUnderline() ) + format.fn.setUnderline( style->fontUnderline() ); + } + } + + format.update(); + return format; +} + +struct QPixmapInt +{ + QPixmapInt() : ref( 0 ) {} + QPixmap pm; + int ref; +}; + +static QMap<QString, QPixmapInt> *pixmap_map = 0; + +QTextImage::QTextImage( QTextDocument *p, const QMap<QString, QString> &attr, const QString& context, + QMimeSourceFactory &factory ) + : QTextCustomItem( p ) +{ +#if defined(PARSER_DEBUG) + qDebug( debug_indent + "new QTextImage (pappi: %p)", p ); +#endif + + width = height = 0; + if ( attr.contains("width") ) + width = attr["width"].toInt(); + if ( attr.contains("height") ) + height = attr["height"].toInt(); + + reg = 0; + QString imageName = attr["src"]; + + if (!imageName) + imageName = attr["source"]; + +#if defined(PARSER_DEBUG) + qDebug( debug_indent + " .." + imageName ); +#endif + + if ( !imageName.isEmpty() ) { + imgId = QString( "%1,%2,%3,%4" ).arg( imageName ).arg( width ).arg( height ).arg( (ulong)&factory ); + if ( !pixmap_map ) + pixmap_map = new QMap<QString, QPixmapInt>; + if ( pixmap_map->contains( imgId ) ) { + QPixmapInt& pmi = pixmap_map->operator[](imgId); + pm = pmi.pm; + pmi.ref++; + width = pm.width(); + height = pm.height(); + } else { + QImage img; + const QMimeSource* m = + factory.data( imageName, context ); + if ( !m ) { + qWarning("QTextImage: no mimesource for %s", imageName.latin1() ); + } + else { + if ( !QImageDrag::decode( m, img ) ) { + qWarning("QTextImage: cannot decode %s", imageName.latin1() ); + } + } + + if ( !img.isNull() ) { + if ( width == 0 ) { + width = img.width(); + if ( height != 0 ) { + width = img.width() * height / img.height(); + } + } + if ( height == 0 ) { + height = img.height(); + if ( width != img.width() ) { + height = img.height() * width / img.width(); + } + } + if ( img.width() != width || img.height() != height ){ +#ifndef QT_NO_IMAGE_SMOOTHSCALE + img = img.smoothScale(width, height); +#endif + width = img.width(); + height = img.height(); + } + pm.convertFromImage( img ); + } + if ( !pm.isNull() ) { + QPixmapInt& pmi = pixmap_map->operator[](imgId); + pmi.pm = pm; + pmi.ref++; + } + } + if ( pm.mask() ) { + QRegion mask( *pm.mask() ); + QRegion all( 0, 0, pm.width(), pm.height() ); + reg = new QRegion( all.subtract( mask ) ); + } + } + + if ( pm.isNull() && (width*height)==0 ) + width = height = 50; + + place = PlaceInline; + if ( attr["align"] == "left" ) + place = PlaceLeft; + else if ( attr["align"] == "right" ) + place = PlaceRight; + + tmpwidth = width; + tmpheight = height; + + attributes = attr; +} + +QTextImage::~QTextImage() +{ + if ( pixmap_map && pixmap_map->contains( imgId ) ) { + QPixmapInt& pmi = pixmap_map->operator[](imgId); + pmi.ref--; + if ( !pmi.ref ) { + pixmap_map->remove( imgId ); + if ( pixmap_map->isEmpty() ) { + delete pixmap_map; + pixmap_map = 0; + } + } + } +} + +QString QTextImage::richText() const +{ + QString s; + s += "<img "; + QMap<QString, QString>::ConstIterator it = attributes.begin(); + for ( ; it != attributes.end(); ++it ) + s += it.key() + "=" + *it + " "; + s += ">"; + return s; +} + +void QTextImage::adjustToPainter( QPainter* p ) +{ + width = scale( tmpwidth, p ); + height = scale( tmpheight, p ); +} + +#if !defined(Q_WS_X11) +#include <qbitmap.h> +#include "qcleanuphandler.h" +static QPixmap *qrt_selection = 0; +static QSingleCleanupHandler<QPixmap> qrt_cleanup_pixmap; +static void qrt_createSelectionPixmap( const QColorGroup &cg ) +{ + qrt_selection = new QPixmap( 2, 2 ); + qrt_cleanup_pixmap.set( &qrt_selection ); + qrt_selection->fill( Qt::color0 ); + QBitmap m( 2, 2 ); + m.fill( Qt::color1 ); + QPainter p( &m ); + p.setPen( Qt::color0 ); + for ( int j = 0; j < 2; ++j ) { + p.drawPoint( j % 2, j ); + } + p.end(); + qrt_selection->setMask( m ); + qrt_selection->fill( cg.highlight() ); +} +#endif + +void QTextImage::draw( QPainter* p, int x, int y, int cx, int cy, int cw, int ch, const QColorGroup& cg, bool selected ) +{ + if ( placement() != PlaceInline ) { + x = xpos; + y = ypos; + } + + if ( pm.isNull() ) { + p->fillRect( x , y, width, height, cg.dark() ); + return; + } + + if ( is_printer( p ) ) { + p->drawPixmap( x, y, pm ); + return; + } + + if ( placement() != PlaceInline && !QRect( xpos, ypos, width, height ).intersects( QRect( cx, cy, cw, ch ) ) ) + return; + + if ( placement() == PlaceInline ) + p->drawPixmap( x , y, pm ); + else + p->drawPixmap( cx , cy, pm, cx - x, cy - y, cw, ch ); + + if ( selected && placement() == PlaceInline && is_printer( p ) ) { +#if defined(Q_WS_X11) + p->fillRect( QRect( QPoint( x, y ), pm.size() ), QBrush( cg.highlight(), QBrush::Dense4Pattern) ); +#else // in WIN32 Dense4Pattern doesn't work correctly (transparency problem), so work around it + if ( !qrt_selection ) + qrt_createSelectionPixmap( cg ); + p->drawTiledPixmap( x, y, pm.width(), pm.height(), *qrt_selection ); +#endif + } +} + +void QTextHorizontalLine::adjustToPainter( QPainter* p ) +{ + height = scale( tmpheight, p ); +} + + +QTextHorizontalLine::QTextHorizontalLine( QTextDocument *p, const QMap<QString, QString> &attr, + const QString &, + QMimeSourceFactory & ) + : QTextCustomItem( p ) +{ + height = tmpheight = 8; + if ( attr.find( "color" ) != attr.end() ) + color = QColor( *attr.find( "color" ) ); +} + +QTextHorizontalLine::~QTextHorizontalLine() +{ +} + +QString QTextHorizontalLine::richText() const +{ + return "<hr>"; +} + +void QTextHorizontalLine::draw( QPainter* p, int x, int y, int , int , int , int , const QColorGroup& cg, bool selected ) +{ + QRect r( x, y, width, height); + if ( is_printer( p ) ) { + QPen oldPen = p->pen(); + if ( !color.isValid() ) + p->setPen( QPen( cg.text(), height/8 ) ); + else + p->setPen( QPen( color, height/8 ) ); + p->drawLine( r.left()-1, y + height / 2, r.right() + 1, y + height / 2 ); + p->setPen( oldPen ); + } else { + QColorGroup g( cg ); + if ( color.isValid() ) + g.setColor( QColorGroup::Dark, color ); + if ( selected ) + p->fillRect( r.left(), y, r.right(), y + height, g.highlight() ); + qDrawShadeLine( p, r.left() - 1, y + height / 2, r.right() + 1, y + height / 2, g, TRUE, height / 8 ); + } +} + + +/*****************************************************************/ +// Small set of utility functions to make the parser a bit simpler +// + +bool QTextDocument::hasPrefix(const QChar* doc, int length, int pos, QChar c) +{ + if ( pos >= length ) + return FALSE; + return doc[ pos ].lower() == c.lower(); +} + +bool QTextDocument::hasPrefix( const QChar* doc, int length, int pos, const QString& s ) +{ + if ( pos + (int) s.length() >= length ) + return FALSE; + for ( int i = 0; i < (int)s.length(); i++ ) { + if ( doc[ pos + i ].lower() != s[ i ].lower() ) + return FALSE; + } + return TRUE; +} + +static bool qt_is_cell_in_use( QPtrList<QTextTableCell>& cells, int row, int col ) +{ + for ( QTextTableCell* c = cells.first(); c; c = cells.next() ) { + if ( row >= c->row() && row < c->row() + c->rowspan() + && col >= c->column() && col < c->column() + c->colspan() ) + return TRUE; + } + return FALSE; +} + +QTextCustomItem* QTextDocument::parseTable( const QMap<QString, QString> &attr, const QTextFormat &fmt, + const QChar* doc, int length, int& pos, QTextParag *curpar ) +{ + + QTextTable* table = new QTextTable( this, attr ); + int row = -1; + int col = -1; + + QString rowbgcolor; + QString rowalign; + QString tablebgcolor = attr["bgcolor"]; + + QPtrList<QTextTableCell> multicells; + + QString tagname; + (void) eatSpace(doc, length, pos); + while ( pos < length) { + if (hasPrefix(doc, length, pos, QChar('<')) ){ + if (hasPrefix(doc, length, pos+1, QChar('/'))) { + tagname = parseCloseTag( doc, length, pos ); + if ( tagname == "table" ) { +#if defined(PARSER_DEBUG) + debug_indent.remove( debug_indent.length() - 3, 2 ); +#endif + return table; + } + } else { + QMap<QString, QString> attr2; + bool emptyTag = FALSE; + tagname = parseOpenTag( doc, length, pos, attr2, emptyTag ); + if ( tagname == "tr" ) { + rowbgcolor = attr2["bgcolor"]; + rowalign = attr2["align"]; + row++; + col = -1; + } + else if ( tagname == "td" || tagname == "th" ) { + col++; + while ( qt_is_cell_in_use( multicells, row, col ) ) { + col++; + } + + if ( row >= 0 && col >= 0 ) { + const QStyleSheetItem* s = sheet_->item(tagname); + if ( !attr2.contains("bgcolor") ) { + if (!rowbgcolor.isEmpty() ) + attr2["bgcolor"] = rowbgcolor; + else if (!tablebgcolor.isEmpty() ) + attr2["bgcolor"] = tablebgcolor; + } + if ( !attr2.contains("align") ) { + if (!rowalign.isEmpty() ) + attr2["align"] = rowalign; + } + + // extract the cell contents + int end = pos; + while ( end < length + && !hasPrefix( doc, length, end, "</td") + && !hasPrefix( doc, length, end, "<td") + && !hasPrefix( doc, length, end, "</th") + && !hasPrefix( doc, length, end, "<th") + && !hasPrefix( doc, length, end, "<td") + && !hasPrefix( doc, length, end, "</tr") + && !hasPrefix( doc, length, end, "<tr") + && !hasPrefix( doc, length, end, "</table") ) { + if ( hasPrefix( doc, length, end, "<table" ) ) { // nested table + int nested = 1; + ++end; + while ( end < length && nested != 0 ) { + if ( hasPrefix( doc, length, end, "</table" ) ) + nested--; + if ( hasPrefix( doc, length, end, "<table" ) ) + nested++; + end++; + } + } + end++; + } + QTextTableCell* cell = new QTextTableCell( table, row, col, + attr2, s, fmt.makeTextFormat( s, attr2 ), + contxt, *factory_, sheet_, + QString( doc, length).mid( pos, end - pos ) ); + cell->richText()->parParag = curpar; + if ( cell->colspan() > 1 || cell->rowspan() > 1 ) + multicells.append( cell ); + col += cell->colspan()-1; + pos = end; + } + } + } + + } else { + ++pos; + } + } +#if defined(PARSER_DEBUG) + debug_indent.remove( debug_indent.length() - 3, 2 ); +#endif + return table; +} + +bool QTextDocument::eatSpace(const QChar* doc, int length, int& pos, bool includeNbsp ) +{ + int old_pos = pos; + while (pos < length && doc[pos].isSpace() && ( includeNbsp || (doc[pos] != QChar::nbsp ) ) ) + pos++; + return old_pos < pos; +} + +bool QTextDocument::eat(const QChar* doc, int length, int& pos, QChar c) +{ + bool ok = pos < length && doc[pos] == c; + if ( ok ) + pos++; + return ok; +} +/*****************************************************************/ + +struct Entity { + const char * name; + Q_UINT16 code; +}; + +static const Entity entitylist [] = { + { "AElig", 0x00c6 }, + { "Aacute", 0x00c1 }, + { "Acirc", 0x00c2 }, + { "Agrave", 0x00c0 }, + { "Alpha", 0x0391 }, + { "AMP", 38 }, + { "Aring", 0x00c5 }, + { "Atilde", 0x00c3 }, + { "Auml", 0x00c4 }, + { "Beta", 0x0392 }, + { "Ccedil", 0x00c7 }, + { "Chi", 0x03a7 }, + { "Dagger", 0x2021 }, + { "Delta", 0x0394 }, + { "ETH", 0x00d0 }, + { "Eacute", 0x00c9 }, + { "Ecirc", 0x00ca }, + { "Egrave", 0x00c8 }, + { "Epsilon", 0x0395 }, + { "Eta", 0x0397 }, + { "Euml", 0x00cb }, + { "Gamma", 0x0393 }, + { "GT", 62 }, + { "Iacute", 0x00cd }, + { "Icirc", 0x00ce }, + { "Igrave", 0x00cc }, + { "Iota", 0x0399 }, + { "Iuml", 0x00cf }, + { "Kappa", 0x039a }, + { "Lambda", 0x039b }, + { "LT", 60 }, + { "Mu", 0x039c }, + { "Ntilde", 0x00d1 }, + { "Nu", 0x039d }, + { "OElig", 0x0152 }, + { "Oacute", 0x00d3 }, + { "Ocirc", 0x00d4 }, + { "Ograve", 0x00d2 }, + { "Omega", 0x03a9 }, + { "Omicron", 0x039f }, + { "Oslash", 0x00d8 }, + { "Otilde", 0x00d5 }, + { "Ouml", 0x00d6 }, + { "Phi", 0x03a6 }, + { "Pi", 0x03a0 }, + { "Prime", 0x2033 }, + { "Psi", 0x03a8 }, + { "QUOT", 34 }, + { "Rho", 0x03a1 }, + { "Scaron", 0x0160 }, + { "Sigma", 0x03a3 }, + { "THORN", 0x00de }, + { "Tau", 0x03a4 }, + { "Theta", 0x0398 }, + { "Uacute", 0x00da }, + { "Ucirc", 0x00db }, + { "Ugrave", 0x00d9 }, + { "Upsilon", 0x03a5 }, + { "Uuml", 0x00dc }, + { "Xi", 0x039e }, + { "Yacute", 0x00dd }, + { "Yuml", 0x0178 }, + { "Zeta", 0x0396 }, + { "aacute", 0x00e1 }, + { "acirc", 0x00e2 }, + { "acute", 0x00b4 }, + { "aelig", 0x00e6 }, + { "agrave", 0x00e0 }, + { "alefsym", 0x2135 }, + { "alpha", 0x03b1 }, + { "amp", 38 }, + { "and", 0x22a5 }, + { "ang", 0x2220 }, + { "apos", 0x0027 }, + { "aring", 0x00e5 }, + { "asymp", 0x2248 }, + { "atilde", 0x00e3 }, + { "auml", 0x00e4 }, + { "bdquo", 0x201e }, + { "beta", 0x03b2 }, + { "brvbar", 0x00a6 }, + { "bull", 0x2022 }, + { "cap", 0x2229 }, + { "ccedil", 0x00e7 }, + { "cedil", 0x00b8 }, + { "cent", 0x00a2 }, + { "chi", 0x03c7 }, + { "circ", 0x02c6 }, + { "clubs", 0x2663 }, + { "cong", 0x2245 }, + { "copy", 0x00a9 }, + { "crarr", 0x21b5 }, + { "cup", 0x222a }, + { "curren", 0x00a4 }, + { "dArr", 0x21d3 }, + { "dagger", 0x2020 }, + { "darr", 0x2193 }, + { "deg", 0x00b0 }, + { "delta", 0x03b4 }, + { "diams", 0x2666 }, + { "divide", 0x00f7 }, + { "eacute", 0x00e9 }, + { "ecirc", 0x00ea }, + { "egrave", 0x00e8 }, + { "empty", 0x2205 }, + { "emsp", 0x2003 }, + { "ensp", 0x2002 }, + { "epsilon", 0x03b5 }, + { "equiv", 0x2261 }, + { "eta", 0x03b7 }, + { "eth", 0x00f0 }, + { "euml", 0x00eb }, + { "euro", 0x20ac }, + { "exist", 0x2203 }, + { "fnof", 0x0192 }, + { "forall", 0x2200 }, + { "frac12", 0x00bd }, + { "frac14", 0x00bc }, + { "frac34", 0x00be }, + { "frasl", 0x2044 }, + { "gamma", 0x03b3 }, + { "ge", 0x2265 }, + { "gt", 62 }, + { "hArr", 0x21d4 }, + { "harr", 0x2194 }, + { "hearts", 0x2665 }, + { "hellip", 0x2026 }, + { "iacute", 0x00ed }, + { "icirc", 0x00ee }, + { "iexcl", 0x00a1 }, + { "igrave", 0x00ec }, + { "image", 0x2111 }, + { "infin", 0x221e }, + { "int", 0x222b }, + { "iota", 0x03b9 }, + { "iquest", 0x00bf }, + { "isin", 0x2208 }, + { "iuml", 0x00ef }, + { "kappa", 0x03ba }, + { "lArr", 0x21d0 }, + { "lambda", 0x03bb }, + { "lang", 0x2329 }, + { "laquo", 0x00ab }, + { "larr", 0x2190 }, + { "lceil", 0x2308 }, + { "ldquo", 0x201c }, + { "le", 0x2264 }, + { "lfloor", 0x230a }, + { "lowast", 0x2217 }, + { "loz", 0x25ca }, + { "lrm", 0x200e }, + { "lsaquo", 0x2039 }, + { "lsquo", 0x2018 }, + { "lt", 60 }, + { "macr", 0x00af }, + { "mdash", 0x2014 }, + { "micro", 0x00b5 }, + { "middot", 0x00b7 }, + { "minus", 0x2212 }, + { "mu", 0x03bc }, + { "nabla", 0x2207 }, + { "nbsp", 0x00a0 }, + { "ndash", 0x2013 }, + { "ne", 0x2260 }, + { "ni", 0x220b }, + { "not", 0x00ac }, + { "notin", 0x2209 }, + { "nsub", 0x2284 }, + { "ntilde", 0x00f1 }, + { "nu", 0x03bd }, + { "oacute", 0x00f3 }, + { "ocirc", 0x00f4 }, + { "oelig", 0x0153 }, + { "ograve", 0x00f2 }, + { "oline", 0x203e }, + { "omega", 0x03c9 }, + { "omicron", 0x03bf }, + { "oplus", 0x2295 }, + { "or", 0x22a6 }, + { "ordf", 0x00aa }, + { "ordm", 0x00ba }, + { "oslash", 0x00f8 }, + { "otilde", 0x00f5 }, + { "otimes", 0x2297 }, + { "ouml", 0x00f6 }, + { "para", 0x00b6 }, + { "part", 0x2202 }, + { "percnt", 0x0025 }, + { "permil", 0x2030 }, + { "perp", 0x22a5 }, + { "phi", 0x03c6 }, + { "pi", 0x03c0 }, + { "piv", 0x03d6 }, + { "plusmn", 0x00b1 }, + { "pound", 0x00a3 }, + { "prime", 0x2032 }, + { "prod", 0x220f }, + { "prop", 0x221d }, + { "psi", 0x03c8 }, + { "quot", 34 }, + { "rArr", 0x21d2 }, + { "radic", 0x221a }, + { "rang", 0x232a }, + { "raquo", 0x00bb }, + { "rarr", 0x2192 }, + { "rceil", 0x2309 }, + { "rdquo", 0x201d }, + { "real", 0x211c }, + { "reg", 0x00ae }, + { "rfloor", 0x230b }, + { "rho", 0x03c1 }, + { "rlm", 0x200f }, + { "rsaquo", 0x203a }, + { "rsquo", 0x2019 }, + { "sbquo", 0x201a }, + { "scaron", 0x0161 }, + { "sdot", 0x22c5 }, + { "sect", 0x00a7 }, + { "shy", 0x00ad }, + { "sigma", 0x03c3 }, + { "sigmaf", 0x03c2 }, + { "sim", 0x223c }, + { "spades", 0x2660 }, + { "sub", 0x2282 }, + { "sube", 0x2286 }, + { "sum", 0x2211 }, + { "sup1", 0x00b9 }, + { "sup2", 0x00b2 }, + { "sup3", 0x00b3 }, + { "sup", 0x2283 }, + { "supe", 0x2287 }, + { "szlig", 0x00df }, + { "tau", 0x03c4 }, + { "there4", 0x2234 }, + { "theta", 0x03b8 }, + { "thetasym", 0x03d1 }, + { "thinsp", 0x2009 }, + { "thorn", 0x00fe }, + { "tilde", 0x02dc }, + { "times", 0x00d7 }, + { "trade", 0x2122 }, + { "uArr", 0x21d1 }, + { "uacute", 0x00fa }, + { "uarr", 0x2191 }, + { "ucirc", 0x00fb }, + { "ugrave", 0x00f9 }, + { "uml", 0x00a8 }, + { "upsih", 0x03d2 }, + { "upsilon", 0x03c5 }, + { "uuml", 0x00fc }, + { "weierp", 0x2118 }, + { "xi", 0x03be }, + { "yacute", 0x00fd }, + { "yen", 0x00a5 }, + { "yuml", 0x00ff }, + { "zeta", 0x03b6 }, + { "zwj", 0x200d }, + { "zwnj", 0x200c }, + { "", 0x0000 } +}; + + + + + +static QMap<QCString, QChar> *html_map = 0; +static void qt_cleanup_html_map() +{ + delete html_map; + html_map = 0; +} + +static QMap<QCString, QChar> *htmlMap() +{ + if ( !html_map ) { + html_map = new QMap<QCString, QChar>; + qAddPostRoutine( qt_cleanup_html_map ); + + const Entity *ent = entitylist; + while( ent->code ) { + html_map->insert( ent->name, QChar(ent->code) ); + ent++; + } + } + return html_map; +} + +QChar QTextDocument::parseHTMLSpecialChar(const QChar* doc, int length, int& pos) +{ + QCString s; + pos++; + int recoverpos = pos; + while ( pos < length && doc[pos] != ';' && !doc[pos].isSpace() && pos < recoverpos + 6) { + s += doc[pos]; + pos++; + } + if (doc[pos] != ';' && !doc[pos].isSpace() ) { + pos = recoverpos; + return '&'; + } + pos++; + + if ( s.length() > 1 && s[0] == '#') { + int num = s.mid(1).toInt(); + if ( num == 151 ) // ### hack for designer manual + return '-'; + return num; + } + + QMap<QCString, QChar>::Iterator it = htmlMap()->find(s); + if ( it != htmlMap()->end() ) { + return *it; + } + + pos = recoverpos; + return '&'; +} + +QString QTextDocument::parseWord(const QChar* doc, int length, int& pos, bool lower) +{ + QString s; + + if (doc[pos] == '"') { + pos++; + while ( pos < length && doc[pos] != '"' ) { + s += doc[pos]; + pos++; + } + eat(doc, length, pos, '"'); + } else { + static QString term = QString::fromLatin1("/>"); + while( pos < length && + (doc[pos] != '>' && !hasPrefix( doc, length, pos, term)) + && doc[pos] != '<' + && doc[pos] != '=' + && !doc[pos].isSpace()) + { + if ( doc[pos] == '&') + s += parseHTMLSpecialChar( doc, length, pos ); + else { + s += doc[pos]; + pos++; + } + } + if (lower) + s = s.lower(); + } + return s; +} + +QChar QTextDocument::parseChar(const QChar* doc, int length, int& pos, QStyleSheetItem::WhiteSpaceMode wsm ) +{ + if ( pos >= length ) + return QChar::null; + + QChar c = doc[pos++]; + + if (c == '<' ) + return QChar::null; + + if ( c.isSpace() && c != QChar::nbsp ) { + if ( wsm == QStyleSheetItem::WhiteSpacePre ) { + if ( c == ' ' ) + return QChar::nbsp; + else + return c; + } else if ( wsm == QStyleSheetItem_WhiteSpaceNoCompression ) { + return c; + } else if ( wsm == QStyleSheetItem_WhiteSpaceNormalWithNewlines ) { + if ( c == '\n' ) + return c; + while ( pos< length && + doc[pos].isSpace() && doc[pos] != QChar::nbsp && doc[pos] != '\n' ) + pos++; + return ' '; + } else { // non-pre mode: collapse whitespace except nbsp + while ( pos< length && + doc[pos].isSpace() && doc[pos] != QChar::nbsp ) + pos++; + if ( wsm == QStyleSheetItem::WhiteSpaceNoWrap ) + return QChar::nbsp; + else + return ' '; + } + } + else if ( c == '&' ) + return parseHTMLSpecialChar( doc, length, --pos ); + else + return c; +} + +QString QTextDocument::parseOpenTag(const QChar* doc, int length, int& pos, + QMap<QString, QString> &attr, bool& emptyTag) +{ + emptyTag = FALSE; + pos++; + if ( hasPrefix(doc, length, pos, '!') ) { + if ( hasPrefix( doc, length, pos+1, "--")) { + pos += 3; + // eat comments + QString pref = QString::fromLatin1("-->"); + while ( !hasPrefix(doc, length, pos, pref ) && pos < length ) + pos++; + if ( hasPrefix(doc, length, pos, pref ) ) { + pos += 3; + eatSpace(doc, length, pos, TRUE); + } + emptyTag = TRUE; + return QString::null; + } + else { + // eat strange internal tags + while ( !hasPrefix(doc, length, pos, '>') && pos < length ) + pos++; + if ( hasPrefix(doc, length, pos, '>') ) { + pos++; + eatSpace(doc, length, pos, TRUE); + } + return QString::null; + } + } + + QString tag = parseWord(doc, length, pos ); + eatSpace(doc, length, pos, TRUE); + static QString term = QString::fromLatin1("/>"); + static QString s_TRUE = QString::fromLatin1("TRUE"); + + while (doc[pos] != '>' && ! (emptyTag = hasPrefix(doc, length, pos, term) )) { + QString key = parseWord(doc, length, pos ); + eatSpace(doc, length, pos, TRUE); + if ( key.isEmpty()) { + // error recovery + while ( pos < length && doc[pos] != '>' ) + pos++; + break; + } + QString value; + if (hasPrefix(doc, length, pos, '=') ){ + pos++; + eatSpace(doc, length, pos); + value = parseWord(doc, length, pos, FALSE); + } + else + value = s_TRUE; + attr.insert(key.lower(), value ); + eatSpace(doc, length, pos, TRUE); + } + + if (emptyTag) { + eat(doc, length, pos, '/'); + eat(doc, length, pos, '>'); + } + else + eat(doc, length, pos, '>'); + + return tag; +} + +QString QTextDocument::parseCloseTag( const QChar* doc, int length, int& pos ) +{ + pos++; + pos++; + QString tag = parseWord(doc, length, pos ); + eatSpace(doc, length, pos, TRUE); + eat(doc, length, pos, '>'); + return tag; +} + +QTextFlow::QTextFlow() +{ + w = pagesize = 0; + leftItems.setAutoDelete( FALSE ); + rightItems.setAutoDelete( FALSE ); +} + +QTextFlow::~QTextFlow() +{ +} + +void QTextFlow::clear() +{ + leftItems.clear(); + rightItems.clear(); +} + +void QTextFlow::setWidth( int width ) +{ + w = width; +} + +int QTextFlow::adjustLMargin( int yp, int, int margin, int space ) +{ + for ( QTextCustomItem* item = leftItems.first(); item; item = leftItems.next() ) { + if ( item->ypos == -1 ) + continue; + if ( yp >= item->ypos && yp < item->ypos + item->height ) + margin = QMAX( margin, item->xpos + item->width + space ); + } + return margin; +} + +int QTextFlow::adjustRMargin( int yp, int, int margin, int space ) +{ + for ( QTextCustomItem* item = rightItems.first(); item; item = rightItems.next() ) { + if ( item->ypos == -1 ) + continue; + if ( yp >= item->ypos && yp < item->ypos + item->height ) + margin = QMAX( margin, w - item->xpos - space ); + } + return margin; +} + + +int QTextFlow::adjustFlow( int y, int /*w*/, int h ) +{ + if ( pagesize > 0 ) { // check pages + int yinpage = y % pagesize; + if ( yinpage <= border_tolerance ) + return border_tolerance - yinpage; + else + if ( yinpage + h > pagesize - border_tolerance ) + return ( pagesize - yinpage ) + border_tolerance; + } + return 0; +} + +void QTextFlow::unregisterFloatingItem( QTextCustomItem* item ) +{ + leftItems.removeRef( item ); + rightItems.removeRef( item ); +} + +void QTextFlow::registerFloatingItem( QTextCustomItem* item ) +{ + if ( item->placement() == QTextCustomItem::PlaceRight ) { + if ( !rightItems.contains( item ) ) + rightItems.append( item ); + } else if ( item->placement() == QTextCustomItem::PlaceLeft && + !leftItems.contains( item ) ) { + leftItems.append( item ); + } +} + +QRect QTextFlow::boundingRect() const +{ + QRect br; + QPtrListIterator<QTextCustomItem> l( leftItems ); + while( l.current() ) { + br = br.unite( l.current()->geometry() ); + ++l; + } + QPtrListIterator<QTextCustomItem> r( rightItems ); + while( r.current() ) { + br = br.unite( r.current()->geometry() ); + ++r; + } + return br; +} + + +void QTextFlow::drawFloatingItems( QPainter* p, int cx, int cy, int cw, int ch, const QColorGroup& cg, bool selected ) +{ + QTextCustomItem *item; + for ( item = leftItems.first(); item; item = leftItems.next() ) { + if ( item->xpos == -1 || item->ypos == -1 ) + continue; + item->draw( p, item->xpos, item->ypos, cx, cy, cw, ch, cg, selected ); + } + + for ( item = rightItems.first(); item; item = rightItems.next() ) { + if ( item->xpos == -1 || item->ypos == -1 ) + continue; + item->draw( p, item->xpos, item->ypos, cx, cy, cw, ch, cg, selected ); + } +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +void QTextCustomItem::pageBreak( int /*y*/ , QTextFlow* /*flow*/ ) +{ +} + +QTextTable::QTextTable( QTextDocument *p, const QMap<QString, QString> & attr ) + : QTextCustomItem( p ) +{ + cells.setAutoDelete( FALSE ); +#if defined(PARSER_DEBUG) + debug_indent += "\t"; + qDebug( debug_indent + "new QTextTable (%p)", this ); + debug_indent += "\t"; +#endif + cellspacing = 2; + if ( attr.contains("cellspacing") ) + cellspacing = attr["cellspacing"].toInt(); + cellpadding = 1; + if ( attr.contains("cellpadding") ) + cellpadding = attr["cellpadding"].toInt(); + border = innerborder = 0; + if ( attr.contains("border" ) ) { + QString s( attr["border"] ); + if ( s == "TRUE" ) + border = 1; + else + border = attr["border"].toInt(); + } + us_b = border; + + innerborder = us_ib = border ? 1 : 0; + + if ( border ) + cellspacing += 2; + + us_ib = innerborder; + us_cs = cellspacing; + us_cp = cellpadding; + outerborder = cellspacing + border; + us_ob = outerborder; + layout = new QGridLayout( 1, 1, cellspacing ); + + fixwidth = 0; + stretch = 0; + if ( attr.contains("width") ) { + bool b; + QString s( attr["width"] ); + int w = s.toInt( &b ); + if ( b ) { + fixwidth = w; + } else { + s = s.stripWhiteSpace(); + if ( s.length() > 1 && s[ (int)s.length()-1 ] == '%' ) + stretch = s.left( s.length()-1).toInt(); + } + } + + place = PlaceInline; + if ( attr["align"] == "left" ) + place = PlaceLeft; + else if ( attr["align"] == "right" ) + place = PlaceRight; + cachewidth = 0; + attributes = attr; + pageBreakFor = -1; +} + +QTextTable::~QTextTable() +{ + delete layout; +} + +QString QTextTable::richText() const +{ + QString s; + s = "<table "; + QMap<QString, QString>::ConstIterator it = attributes.begin(); + for ( ; it != attributes.end(); ++it ) + s += it.key() + "=" + *it + " "; + s += ">\n"; + + int lastRow = -1; + bool needEnd = FALSE; + QPtrListIterator<QTextTableCell> it2( cells ); + while ( it2.current() ) { + QTextTableCell *cell = it2.current(); + ++it2; + if ( lastRow != cell->row() ) { + if ( lastRow != -1 ) + s += "</tr>\n"; + s += "<tr>"; + lastRow = cell->row(); + needEnd = TRUE; + } + s += "<td "; + it = cell->attributes.begin(); + for ( ; it != cell->attributes.end(); ++it ) + s += it.key() + "=" + *it + " "; + s += ">"; + s += cell->richText()->richText(); + s += "</td>"; + } + if ( needEnd ) + s += "</tr>\n"; + s += "</table>\n"; + return s; +} + +void QTextTable::adjustToPainter( QPainter* p ) +{ + cellspacing = scale( us_cs, p ); + cellpadding = scale( us_cp, p ); + border = scale( us_b , p ); + innerborder = scale( us_ib, p ); + outerborder = scale( us_ob ,p ); + width = 0; + cachewidth = 0; + for ( QTextTableCell* cell = cells.first(); cell; cell = cells.next() ) + cell->adjustToPainter( p ); +} + +void QTextTable::adjustCells( int y , int shift ) +{ + QPtrListIterator<QTextTableCell> it( cells ); + QTextTableCell* cell; + bool enlarge = FALSE; + while ( ( cell = it.current() ) ) { + ++it; + QRect r = cell->geometry(); + if ( y <= r.top() ) { + r.moveBy(0, shift ); + cell->setGeometry( r ); + enlarge = TRUE; + } else if ( y <= r.bottom() ) { + r.rBottom() += shift; + cell->setGeometry( r ); + enlarge = TRUE; + } + } + if ( enlarge ) + height += shift; +} + +void QTextTable::pageBreak( int yt, QTextFlow* flow ) +{ + if ( flow->pageSize() <= 0 ) + return; + if ( layout && pageBreakFor > 0 && pageBreakFor != yt ) { + layout->invalidate(); + int h = layout->heightForWidth( width-2*outerborder ); + layout->setGeometry( QRect(0, 0, width-2*outerborder, h) ); + height = layout->geometry().height()+2*outerborder; + } + pageBreakFor = yt; + QPtrListIterator<QTextTableCell> it( cells ); + QTextTableCell* cell; + while ( ( cell = it.current() ) ) { + ++it; + int y = yt + outerborder + cell->geometry().y(); + int shift = flow->adjustFlow( y - cellspacing, width, cell->richText()->height() + 2*cellspacing ); + adjustCells( y - outerborder - yt, shift ); + } +} + + +void QTextTable::draw(QPainter* p, int x, int y, int cx, int cy, int cw, int ch, const QColorGroup& cg, bool selected ) +{ + if ( placement() != PlaceInline ) { + x = xpos; + y = ypos; + } + + for (QTextTableCell* cell = cells.first(); cell; cell = cells.next() ) { + if ( cx < 0 && cy < 0 || + QRect( cx, cy, cw, ch ).intersects( QRect( x + outerborder + cell->geometry().x(), + y + outerborder + cell->geometry().y(), + cell->geometry().width(), cell->geometry().height() ) ) ) { + cell->draw( p, x+outerborder, y+outerborder, cx, cy, cw, ch, cg, selected ); + if ( border ) { + QRect r( x+outerborder+cell->geometry().x() - innerborder, + y+outerborder+cell->geometry().y() - innerborder, + cell->geometry().width() + 2 * innerborder, + cell->geometry().height() + 2 * innerborder ); + if ( is_printer( p ) ) { + QPen oldPen = p->pen(); + QRect r2 = r; + r2.setLeft( r2.left() + innerborder/2 ); + r2.setTop( r2.top() + innerborder/2 ); + r2.setRight( r2.right() - innerborder/2 ); + r2.setBottom( r2.bottom() - innerborder/2 ); + p->setPen( QPen( cg.text(), innerborder ) ); + p->drawRect( r2 ); + p->setPen( oldPen ); + } else { + int s = QMAX( cellspacing-2*innerborder, 0); + if ( s ) { + p->fillRect( r.left()-s, r.top(), s+1, r.height(), cg.button() ); + p->fillRect( r.right(), r.top(), s+1, r.height(), cg.button() ); + p->fillRect( r.left()-s, r.top()-s, r.width()+2*s, s, cg.button() ); + p->fillRect( r.left()-s, r.bottom(), r.width()+2*s, s, cg.button() ); + } + qDrawShadePanel( p, r, cg, TRUE, innerborder ); + } + } + } + } + if ( border ) { + QRect r ( x, y, width, height ); + if ( is_printer( p ) ) { + QRect r2 = r; + r2.setLeft( r2.left() + border/2 ); + r2.setTop( r2.top() + border/2 ); + r2.setRight( r2.right() - border/2 ); + r2.setBottom( r2.bottom() - border/2 ); + QPen oldPen = p->pen(); + p->setPen( QPen( cg.text(), border ) ); + p->drawRect( r2 ); + p->setPen( oldPen ); + } else { + int s = border+QMAX( cellspacing-2*innerborder, 0); + if ( s ) { + p->fillRect( r.left(), r.top(), s, r.height(), cg.button() ); + p->fillRect( r.right()-s, r.top(), s, r.height(), cg.button() ); + p->fillRect( r.left(), r.top(), r.width(), s, cg.button() ); + p->fillRect( r.left(), r.bottom()-s, r.width(), s, cg.button() ); + } + qDrawShadePanel( p, r, cg, FALSE, border ); + } + } + +#if defined(DEBUG_TABLE_RENDERING) + p->save(); + p->setPen( Qt::red ); + p->drawRect( x, y, width, height ); + p->restore(); +#endif +} + +int QTextTable::minimumWidth() const +{ + return (layout ? layout->minimumSize().width() : 0) + 2 * outerborder; +} + +void QTextTable::resize( int nwidth ) +{ + if ( fixwidth && cachewidth != 0 ) + return; + if ( nwidth == cachewidth ) + return; + + + cachewidth = nwidth; + int w = nwidth; + + format( w ); + + if ( stretch ) + nwidth = nwidth * stretch / 100; + + width = nwidth; + layout->invalidate(); + int shw = layout->sizeHint().width() + 2*outerborder; + int mw = layout->minimumSize().width() + 2*outerborder; + if ( stretch ) + width = QMAX( mw, nwidth ); + else + width = QMAX( mw, QMIN( nwidth, shw ) ); + + if ( fixwidth ) + width = fixwidth; + + layout->invalidate(); + mw = layout->minimumSize().width() + 2*outerborder; + width = QMAX( width, mw ); + + int h = layout->heightForWidth( width-2*outerborder ); + layout->setGeometry( QRect(0, 0, width-2*outerborder, h) ); + height = layout->geometry().height()+2*outerborder; +} + +void QTextTable::format( int w ) +{ + for ( int i = 0; i < (int)cells.count(); ++i ) { + QTextTableCell *cell = cells.at( i ); + QRect r = cell->geometry(); + r.setWidth( w - 2*outerborder ); + cell->setGeometry( r ); + } +} + +void QTextTable::addCell( QTextTableCell* cell ) +{ + cells.append( cell ); + layout->addMultiCell( cell, cell->row(), cell->row() + cell->rowspan()-1, + cell->column(), cell->column() + cell->colspan()-1 ); +} + +bool QTextTable::enter( QTextCursor *c, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy, bool atEnd ) +{ + currCell.remove( c ); + if ( !atEnd ) + return next( c, doc, parag, idx, ox, oy ); + currCell.insert( c, cells.count() ); + return prev( c, doc, parag, idx, ox, oy ); +} + +bool QTextTable::enterAt( QTextCursor *c, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy, const QPoint &pos ) +{ + currCell.remove( c ); + int lastCell = -1; + int lastY = -1; + int i; + for ( i = 0; i < (int)cells.count(); ++i ) { + QTextTableCell *cell = cells.at( i ); + if ( !cell ) + continue; + QRect r( cell->geometry().x(), + cell->geometry().y(), + cell->geometry().width() + 2 * innerborder + 2 * outerborder, + cell->geometry().height() + 2 * innerborder + 2 * outerborder ); + + if ( r.left() <= pos.x() && r.right() >= pos.x() ) { + if ( cell->geometry().y() > lastY ) { + lastCell = i; + lastY = cell->geometry().y(); + } + if ( r.top() <= pos.y() && r.bottom() >= pos.y() ) { + currCell.insert( c, i ); + break; + } + } + } + if ( i == (int) cells.count() ) + return FALSE; // no cell found + + if ( currCell.find( c ) == currCell.end() ) { + if ( lastY != -1 ) + currCell.insert( c, lastCell ); + else + return FALSE; + } + + QTextTableCell *cell = cells.at( *currCell.find( c ) ); + if ( !cell ) + return FALSE; + doc = cell->richText(); + parag = doc->firstParag(); + idx = 0; + ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x(); + oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder; + return TRUE; +} + +bool QTextTable::next( QTextCursor *c, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ) +{ + int cc = -1; + if ( currCell.find( c ) != currCell.end() ) + cc = *currCell.find( c ); + if ( cc > (int)cells.count() - 1 || cc < 0 ) + cc = -1; + currCell.remove( c ); + currCell.insert( c, ++cc ); + if ( cc >= (int)cells.count() ) { + currCell.insert( c, 0 ); + QTextCustomItem::next( c, doc, parag, idx, ox, oy ); + QTextTableCell *cell = cells.first(); + if ( !cell ) + return FALSE; + doc = cell->richText(); + idx = -1; + return TRUE; + } + + if ( currCell.find( c ) == currCell.end() ) + return FALSE; + QTextTableCell *cell = cells.at( *currCell.find( c ) ); + if ( !cell ) + return FALSE; + doc = cell->richText(); + parag = doc->firstParag(); + idx = 0; + ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x(); + oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder; + return TRUE; +} + +bool QTextTable::prev( QTextCursor *c, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ) +{ + int cc = -1; + if ( currCell.find( c ) != currCell.end() ) + cc = *currCell.find( c ); + if ( cc > (int)cells.count() - 1 || cc < 0 ) + cc = cells.count(); + currCell.remove( c ); + currCell.insert( c, --cc ); + if ( cc < 0 ) { + currCell.insert( c, 0 ); + QTextCustomItem::prev( c, doc, parag, idx, ox, oy ); + QTextTableCell *cell = cells.first(); + if ( !cell ) + return FALSE; + doc = cell->richText(); + idx = -1; + return TRUE; + } + + if ( currCell.find( c ) == currCell.end() ) + return FALSE; + QTextTableCell *cell = cells.at( *currCell.find( c ) ); + if ( !cell ) + return FALSE; + doc = cell->richText(); + parag = doc->firstParag(); + idx = parag->length() - 1; + ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x(); + oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder; + return TRUE; +} + +bool QTextTable::down( QTextCursor *c, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ) +{ + if ( currCell.find( c ) == currCell.end() ) + return FALSE; + QTextTableCell *cell = cells.at( *currCell.find( c ) ); + if ( cell->row_ == layout->numRows() - 1 ) { + currCell.insert( c, 0 ); + QTextCustomItem::down( c, doc, parag, idx, ox, oy ); + QTextTableCell *cell = cells.first(); + if ( !cell ) + return FALSE; + doc = cell->richText(); + idx = -1; + return TRUE; + } + + int oldRow = cell->row_; + int oldCol = cell->col_; + if ( currCell.find( c ) == currCell.end() ) + return FALSE; + int cc = *currCell.find( c ); + for ( int i = cc; i < (int)cells.count(); ++i ) { + cell = cells.at( i ); + if ( cell->row_ > oldRow && cell->col_ == oldCol ) { + currCell.insert( c, i ); + break; + } + } + doc = cell->richText(); + if ( !cell ) + return FALSE; + parag = doc->firstParag(); + idx = 0; + ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x(); + oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder; + return TRUE; +} + +bool QTextTable::up( QTextCursor *c, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ) +{ + if ( currCell.find( c ) == currCell.end() ) + return FALSE; + QTextTableCell *cell = cells.at( *currCell.find( c ) ); + if ( cell->row_ == 0 ) { + currCell.insert( c, 0 ); + QTextCustomItem::up( c, doc, parag, idx, ox, oy ); + QTextTableCell *cell = cells.first(); + if ( !cell ) + return FALSE; + doc = cell->richText(); + idx = -1; + return TRUE; + } + + int oldRow = cell->row_; + int oldCol = cell->col_; + if ( currCell.find( c ) == currCell.end() ) + return FALSE; + int cc = *currCell.find( c ); + for ( int i = cc; i >= 0; --i ) { + cell = cells.at( i ); + if ( cell->row_ < oldRow && cell->col_ == oldCol ) { + currCell.insert( c, i ); + break; + } + } + doc = cell->richText(); + if ( !cell ) + return FALSE; + parag = doc->lastParag(); + idx = parag->length() - 1; + ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x(); + oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder; + return TRUE; +} + +QTextTableCell::QTextTableCell( QTextTable* table, + int row, int column, + const QMap<QString, QString> &attr, + const QStyleSheetItem* /*style*/, // ### use them + const QTextFormat& /*fmt*/, const QString& context, + QMimeSourceFactory &factory, QStyleSheet *sheet, + const QString& doc) +{ +#if defined(PARSER_DEBUG) + qDebug( debug_indent + "new QTextTableCell1 (pappi: %p)", table ); + qDebug( debug_indent + doc ); +#endif + cached_width = -1; + cached_sizehint = -1; + + maxw = QWIDGETSIZE_MAX; + minw = 0; + + parent = table; + row_ = row; + col_ = column; + stretch_ = 0; + richtext = new QTextDocument( table->parent ); + richtext->setTableCell( this ); + QString a = *attr.find( "align" ); + if ( !a.isEmpty() ) { + a = a.lower(); + if ( a == "left" ) + richtext->setAlignment( Qt::AlignLeft ); + else if ( a == "center" ) + richtext->setAlignment( Qt::AlignHCenter ); + else if ( a == "right" ) + richtext->setAlignment( Qt::AlignRight ); + } + align = 0; + QString va = *attr.find( "valign" ); + if ( !va.isEmpty() ) { + va = va.lower(); + if ( va == "center" ) + align |= Qt::AlignVCenter; + else if ( va == "bottom" ) + align |= Qt::AlignBottom; + } + richtext->setFormatter( table->parent->formatter() ); + richtext->setUseFormatCollection( table->parent->useFormatCollection() ); + richtext->setMimeSourceFactory( &factory ); + richtext->setStyleSheet( sheet ); + richtext->setDefaultFont( table->parent->formatCollection()->defaultFormat()->font() ); + richtext->setRichText( doc, context ); + rowspan_ = 1; + colspan_ = 1; + if ( attr.contains("colspan") ) + colspan_ = attr["colspan"].toInt(); + if ( attr.contains("rowspan") ) + rowspan_ = attr["rowspan"].toInt(); + + background = 0; + if ( attr.contains("bgcolor") ) { + background = new QBrush(QColor( attr["bgcolor"] )); + } + + + hasFixedWidth = FALSE; + if ( attr.contains("width") ) { + bool b; + QString s( attr["width"] ); + int w = s.toInt( &b ); + if ( b ) { + maxw = w; + minw = maxw; + hasFixedWidth = TRUE; + } else { + s = s.stripWhiteSpace(); + if ( s.length() > 1 && s[ (int)s.length()-1 ] == '%' ) + stretch_ = s.left( s.length()-1).toInt(); + } + } + + attributes = attr; + + parent->addCell( this ); +} + +QTextTableCell::QTextTableCell( QTextTable* table, int row, int column ) +{ +#if defined(PARSER_DEBUG) + qDebug( debug_indent + "new QTextTableCell2( pappi: %p", table ); +#endif + maxw = QWIDGETSIZE_MAX; + minw = 0; + cached_width = -1; + cached_sizehint = -1; + + parent = table; + row_ = row; + col_ = column; + stretch_ = 0; + richtext = new QTextDocument( table->parent ); + richtext->setTableCell( this ); + richtext->setFormatter( table->parent->formatter() ); + richtext->setUseFormatCollection( table->parent->useFormatCollection() ); + richtext->setDefaultFont( table->parent->formatCollection()->defaultFormat()->font() ); + richtext->setRichText( "<html></html>", QString::null ); + rowspan_ = 1; + colspan_ = 1; + background = 0; + hasFixedWidth = FALSE; + parent->addCell( this ); +} + + +QTextTableCell::~QTextTableCell() +{ + delete background; + background = 0; + delete richtext; + richtext = 0; +} + +QSize QTextTableCell::sizeHint() const +{ + int extra = 2 * ( parent->innerborder + parent->cellpadding + border_tolerance); + int used = richtext->widthUsed() + extra; + + if (stretch_ ) { + int w = parent->width * stretch_ / 100 - 2*parent->cellspacing - 2*parent->cellpadding; + return QSize( QMIN( w, maxw ), 0 ).expandedTo( minimumSize() ); + } + + return QSize( used, 0 ).expandedTo( minimumSize() ); +} + +QSize QTextTableCell::minimumSize() const +{ + int extra = 2 * ( parent->innerborder + parent->cellpadding + border_tolerance); + return QSize( QMAX( richtext->minimumWidth() + extra, minw), 0 ); +} + +QSize QTextTableCell::maximumSize() const +{ + return QSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX ); +} + +QSizePolicy::ExpandData QTextTableCell::expanding() const +{ + return QSizePolicy::BothDirections; +} + +bool QTextTableCell::isEmpty() const +{ + return FALSE; +} +void QTextTableCell::setGeometry( const QRect& r ) +{ + int extra = 2 * ( parent->innerborder + parent->cellpadding ); + if ( r.width() != cached_width ) + richtext->doLayout( QTextFormat::painter(), r.width() - extra ); + cached_width = r.width(); + geom = r; +} + +QRect QTextTableCell::geometry() const +{ + return geom; +} + +bool QTextTableCell::hasHeightForWidth() const +{ + return TRUE; +} + +int QTextTableCell::heightForWidth( int w ) const +{ + int extra = 2 * ( parent->innerborder + parent->cellpadding ); + w = QMAX( minw, w ); + + if ( cached_width != w ) { + QTextTableCell* that = (QTextTableCell*) this; + that->richtext->doLayout( QTextFormat::painter(), w - extra ); + that->cached_width = w; + } + return richtext->height() + extra; +} + +void QTextTableCell::adjustToPainter( QPainter* p ) +{ + QTextParag *parag = richtext->firstParag(); + while ( parag ) { + parag->adjustToPainter( p ); + parag = parag->next(); + } +} + +int QTextTableCell::horizontalAlignmentOffset() const +{ + return parent->cellpadding; +} + +int QTextTableCell::verticalAlignmentOffset() const +{ + if ( (align & Qt::AlignVCenter ) == Qt::AlignVCenter ) + return ( geom.height() - richtext->height() ) / 2; + else if ( ( align & Qt::AlignBottom ) == Qt::AlignBottom ) + return geom.height() - parent->cellpadding - richtext->height() ; + return parent->cellpadding; +} + +void QTextTableCell::draw( QPainter* p, int x, int y, int cx, int cy, int cw, int ch, const QColorGroup& cg, bool ) +{ + if ( cached_width != geom.width() ) { + int extra = 2 * ( parent->innerborder + parent->cellpadding ); + richtext->doLayout( p, geom.width() - extra ); + cached_width = geom.width(); + } + QColorGroup g( cg ); + if ( background ) + g.setBrush( QColorGroup::Base, *background ); + else if ( richtext->paper() ) + g.setBrush( QColorGroup::Base, *richtext->paper() ); + + p->save(); + p->translate( x + geom.x(), y + geom.y() ); + if ( background ) + p->fillRect( 0, 0, geom.width(), geom.height(), *background ); + else if ( richtext->paper() ) + p->fillRect( 0, 0, geom.width(), geom.height(), *richtext->paper() ); + + p->translate( horizontalAlignmentOffset(), verticalAlignmentOffset() ); + + QRegion r; + QTextCursor *c = 0; + if ( richtext->parent()->tmpCursor ) + c = richtext->parent()->tmpCursor; + if ( cx >= 0 && cy >= 0 ) + richtext->draw( p, cx - ( x + horizontalAlignmentOffset() + geom.x() ), + cy - ( y + geom.y() + verticalAlignmentOffset() ), + cw, ch, g, FALSE, (c != 0), c ); + else + richtext->draw( p, -1, -1, -1, -1, g, FALSE, (c != 0), c ); + + p->restore(); +} |