You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
qt3/src/kernel/qrichtext.cpp

8261 lines
224 KiB

/****************************************************************************
**
** Implementation of the internal Qt classes dealing with rich text
**
** Created : 990101
**
** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved.
**
** This file is part of the kernel module of the Qt GUI Toolkit.
**
** This file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free
** Software Foundation and appearing in the files LICENSE.GPL2
** and LICENSE.GPL3 included in the packaging of this file.
** Alternatively you may (at your option) use any later version
** of the GNU General Public License if such license has been
** publicly approved by Trolltech ASA (or its successors, if any)
** and the KDE Free Qt Foundation.
**
** Please review the following information to ensure GNU General
** Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/.
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** This file may be used under the terms of the Q Public License as
** defined by Trolltech ASA and appearing in the file LICENSE.QPL
** included in the packaging of this file. Licensees holding valid Qt
** Commercial 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 WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted
** herein.
**
**********************************************************************/
#include "qrichtext_p.h"
#ifndef QT_NO_RICHTEXT
#include "qstringlist.h"
#include "qfont.h"
#include "qtextstream.h"
#include "qfile.h"
#include "qapplication.h"
#include "qmap.h"
#include "qfileinfo.h"
#include "qstylesheet.h"
#include "qmime.h"
#include "qimage.h"
#include "qdragobject.h"
#include "qpaintdevicemetrics.h"
#include "qpainter.h"
#include "qdrawutil.h"
#include "qcursor.h"
#include "qptrstack.h"
#include "qptrdict.h"
#include "qstyle.h"
#include "qcleanuphandler.h"
#include "qtextengine_p.h"
#include <private/qunicodetables_p.h>
#include <stdlib.h>
static QTextCursor* richTextExportStart = 0;
static QTextCursor* richTextExportEnd = 0;
class QTextFormatCollection;
const int border_tolerance = 2;
#ifdef Q_WS_WIN
#include "qt_windows.h"
#endif
#define QChar_linesep QChar(0x2028U)
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( painter->device()->x11Screen() );
#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;
}
inline bool isBreakable( QTextString *string, int pos )
{
if (string->at(pos).nobreak)
return FALSE;
return (pos < string->length()-1 && string->at(pos+1).softBreak);
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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 QByteArray& oldStyleInfo )
: QTextCommand( d ), id( i ), index( idx ), parag( 0 ), text( str ), styleInformation( oldStyleInfo )
{
for ( int j = 0; j < (int)text.size(); ++j ) {
if ( text[ j ].format() )
text[ j ].format()->addRef();
}
}
QTextDeleteCommand::QTextDeleteCommand( QTextParagraph *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 )
{
QTextParagraph *s = doc ? doc->paragAt( id ) : parag;
if ( !s ) {
qWarning( "can't locate parag at %d, last parag: %d", id, doc->lastParagraph()->paragId() );
return 0;
}
cursor.setParagraph( 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 )
{
QTextParagraph *s = doc ? doc->paragAt( id ) : parag;
if ( !s ) {
qWarning( "can't locate parag at %d, last parag: %d", id, doc->lastParagraph()->paragId() );
return 0;
}
cursor.setParagraph( s );
cursor.setIndex( index );
QString str = QTextString::toString( text );
cursor.insert( str, TRUE, &text );
if ( c )
*c = cursor;
cursor.setParagraph( s );
cursor.setIndex( index );
#ifndef QT_NO_DATASTREAM
if ( !styleInformation.isEmpty() ) {
QDataStream styleStream( styleInformation, IO_ReadOnly );
int num;
styleStream >> num;
QTextParagraph *p = s;
while ( num-- && p ) {
p->readStyleInformation( styleStream );
p = p->next();
}
}
#endif
s = cursor.paragraph();
while ( s ) {
s->format();
s->setChanged( TRUE );
if ( s == c->paragraph() )
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 )
{
QTextParagraph *sp = doc->paragAt( startId );
QTextParagraph *ep = doc->paragAt( endId );
if ( !sp || !ep )
return c;
QTextCursor start( doc );
start.setParagraph( sp );
start.setIndex( startIndex );
QTextCursor end( doc );
end.setParagraph( 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 )
{
QTextParagraph *sp = doc->paragAt( startId );
QTextParagraph *ep = doc->paragAt( endId );
if ( !sp || !ep )
return 0;
int idx = startIndex;
int fIndex = 0;
while ( fIndex < int(oldFormats.size()) ) {
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.setParagraph( ep );
end.setIndex( endIndex );
if ( endIndex == ep->length() )
end.gotoLeft();
*c = end;
return c;
}
QTextStyleCommand::QTextStyleCommand( QTextDocument *d, int fParag, int lParag, const QByteArray& beforeChange )
: QTextCommand( d ), firstParag( fParag ), lastParag( lParag ), before( beforeChange )
{
after = readStyleInformation( d, fParag, lParag );
}
QByteArray QTextStyleCommand::readStyleInformation( QTextDocument* doc, int fParag, int lParag )
{
QByteArray style;
#ifndef QT_NO_DATASTREAM
QTextParagraph *p = doc->paragAt( fParag );
if ( !p )
return style;
QDataStream styleStream( style, IO_WriteOnly );
int num = lParag - fParag + 1;
styleStream << num;
while ( num -- && p ) {
p->writeStyleInformation( styleStream );
p = p->next();
}
#endif
return style;
}
void QTextStyleCommand::writeStyleInformation( QTextDocument* doc, int fParag, const QByteArray& style )
{
#ifndef QT_NO_DATASTREAM
QTextParagraph *p = doc->paragAt( fParag );
if ( !p )
return;
QDataStream styleStream( style, IO_ReadOnly );
int num;
styleStream >> num;
while ( num-- && p ) {
p->readStyleInformation( styleStream );
p = p->next();
}
#endif
}
QTextCursor *QTextStyleCommand::execute( QTextCursor *c )
{
writeStyleInformation( doc, firstParag, after );
return c;
}
QTextCursor *QTextStyleCommand::unexecute( QTextCursor *c )
{
writeStyleInformation( doc, firstParag, before );
return c;
}
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
QTextCursor::QTextCursor( QTextDocument *d )
: idx( 0 ), tmpX( -1 ), ox( 0 ), oy( 0 ),
valid( TRUE )
{
para = d ? d->firstParagraph() : 0;
}
QTextCursor::QTextCursor( const QTextCursor &c )
{
ox = c.ox;
oy = c.oy;
idx = c.idx;
para = c.para;
tmpX = c.tmpX;
indices = c.indices;
paras = c.paras;
xOffsets = c.xOffsets;
yOffsets = c.yOffsets;
valid = c.valid;
}
QTextCursor &QTextCursor::operator=( const QTextCursor &c )
{
ox = c.ox;
oy = c.oy;
idx = c.idx;
para = c.para;
tmpX = c.tmpX;
indices = c.indices;
paras = c.paras;
xOffsets = c.xOffsets;
yOffsets = c.yOffsets;
valid = c.valid;
return *this;
}
bool QTextCursor::operator==( const QTextCursor &c ) const
{
return para == c.para && idx == c.idx;
}
int QTextCursor::totalOffsetX() const
{
int xoff = ox;
for ( QValueStack<int>::ConstIterator xit = xOffsets.begin(); xit != xOffsets.end(); ++xit )
xoff += *xit;
return xoff;
}
int QTextCursor::totalOffsetY() const
{
int yoff = oy;
for ( QValueStack<int>::ConstIterator yit = yOffsets.begin(); yit != yOffsets.end(); ++yit )
yoff += *yit;
return yoff;
}
#ifndef QT_NO_TEXTCUSTOMITEM
void QTextCursor::gotoIntoNested( const QPoint &globalPos )
{
if ( !para )
return;
Q_ASSERT( para->at( idx )->isCustom() );
push();
ox = 0;
int bl, y;
para->lineHeightOfChar( idx, &bl, &y );
oy = y + para->rect().y();
ox = para->at( idx )->x;
QTextDocument* doc = document();
para->at( idx )->customItem()->enterAt( this, doc, para, idx, ox, oy, globalPos-QPoint(ox,oy) );
}
#endif
void QTextCursor::invalidateNested()
{
if ( nestedDepth() ) {
QValueStack<QTextParagraph*>::Iterator it = paras.begin();
QValueStack<int>::Iterator it2 = indices.begin();
for ( ; it != paras.end(); ++it, ++it2 ) {
if ( *it == para )
continue;
(*it)->invalidate( 0 );
#ifndef QT_NO_TEXTCUSTOMITEM
if ( (*it)->at( *it2 )->isCustom() )
(*it)->at( *it2 )->customItem()->invalidate();
#endif
}
}
}
void QTextCursor::insert( const QString &str, bool checkNewLine, QMemArray<QTextStringChar> *formatting )
{
tmpX = -1;
bool justInsert = TRUE;
QString s( str );
#if defined(Q_WS_WIN)
if ( checkNewLine ) {
int i = 0;
while ( ( i = s.find( '\r', i ) ) != -1 )
s.remove( i ,1 );
}
#endif
if ( checkNewLine )
justInsert = s.find( '\n' ) == -1;
if ( justInsert ) { // we ignore new lines and insert all in the current para at the current index
para->insert( idx, s.unicode(), s.length() );
if ( formatting ) {
for ( int i = 0; i < (int)s.length(); ++i ) {
if ( formatting->at( i ).format() ) {
formatting->at( i ).format()->addRef();
para->string()->setFormat( idx + i, formatting->at( i ).format(), TRUE );
}
}
}
idx += s.length();
} else { // we split at new lines
int start = -1;
int end;
int y = para->rect().y() + para->rect().height();
int lastIndex = 0;
do {
end = s.find( '\n', start + 1 ); // find line break
if ( end == -1 ) // didn't find one, so end of line is end of string
end = s.length();
int len = (start == -1 ? end : end - start - 1);
if ( len > 0 ) // insert the line
para->insert( idx, s.unicode() + start + 1, len );
else
para->invalidate( 0 );
if ( formatting ) { // set formats to the chars of the line
for ( int i = 0; i < len; ++i ) {
if ( formatting->at( i + lastIndex ).format() ) {
formatting->at( i + lastIndex ).format()->addRef();
para->string()->setFormat( i + idx, formatting->at( i + lastIndex ).format(), TRUE );
}
}
lastIndex += len;
}
start = end; // next start is at the end of this line
idx += len; // increase the index of the cursor to the end of the inserted text
if ( s[end] == '\n' ) { // if at the end was a line break, break the line
splitAndInsertEmptyParagraph( FALSE, TRUE );
para->setEndState( -1 );
para->prev()->format( -1, FALSE );
lastIndex++;
}
} while ( end < (int)s.length() );
para->format( -1, FALSE );
int dy = para->rect().y() + para->rect().height() - y;
QTextParagraph *p = para;
p->setParagId( p->prev() ? p->prev()->paragId() + 1 : 0 );
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 = para->rect().height();
para->format( -1, TRUE );
if ( h != para->rect().height() )
invalidateNested();
else if ( para->document() && para->document()->parent() )
para->document()->nextDoubleBuffered = TRUE;
fixCursorPosition();
}
void QTextCursor::gotoLeft()
{
if ( para->string()->isRightToLeft() )
gotoNextLetter();
else
gotoPreviousLetter();
}
void QTextCursor::gotoPreviousLetter()
{
tmpX = -1;
if ( idx > 0 ) {
idx = para->string()->previousCursorPosition( idx );
#ifndef QT_NO_TEXTCUSTOMITEM
const QTextStringChar *tsc = para->at( idx );
if ( tsc && tsc->isCustom() && tsc->customItem()->isNested() )
processNesting( EnterEnd );
#endif
} else if ( para->prev() ) {
para = para->prev();
while ( !para->isVisible() && para->prev() )
para = para->prev();
idx = para->length() - 1;
} else if ( nestedDepth() ) {
pop();
processNesting( Prev );
if ( idx == -1 ) {
pop();
if ( idx > 0 ) {
idx = para->string()->previousCursorPosition( idx );
#ifndef QT_NO_TEXTCUSTOMITEM
const QTextStringChar *tsc = para->at( idx );
if ( tsc && tsc->isCustom() && tsc->customItem()->isNested() )
processNesting( EnterEnd );
#endif
} else if ( para->prev() ) {
para = para->prev();
idx = para->length() - 1;
}
}
}
}
void QTextCursor::push()
{
indices.push( idx );
paras.push( para );
xOffsets.push( ox );
yOffsets.push( oy );
}
void QTextCursor::pop()
{
if ( indices.isEmpty() )
return;
idx = indices.pop();
para = paras.pop();
ox = xOffsets.pop();
oy = yOffsets.pop();
}
void QTextCursor::restoreState()
{
while ( !indices.isEmpty() )
pop();
}
bool QTextCursor::place( const QPoint &p, QTextParagraph *s, bool link, bool loosePlacing, bool matchBetweenCharacters )
{
QPoint pos( p );
QRect r;
QTextParagraph *str = s;
if ( pos.y() < s->rect().y() ) {
pos.setY( s->rect().y() );
#ifdef Q_WS_MACX
pos.setX( s->rect().x() );
#endif
}
while ( s ) {
r = s->rect();
r.setWidth( document() ? document()->width() : QWIDGETSIZE_MAX );
if ( s->isVisible() )
str = s;
if ( pos.y() >= r.y() && pos.y() <= r.y() + r.height() )
break;
if ( loosePlacing == TRUE && !s->next() ) {
#ifdef Q_WS_MACX
pos.setX( s->rect().x() + s->rect().width() );
#endif
break;
}
s = s->next();
}
if ( !s || !str )
return FALSE;
s = str;
setParagraph( s );
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 + 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 = -1;
int dist = 10000000;
bool inCustom = FALSE;
while ( i < nextLine ) {
chr = s->at(i);
int cpos = x + chr->x;
cw = s->string()->width( i );
#ifndef QT_NO_TEXTCUSTOMITEM
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
#endif
{
if( chr->rightToLeft )
cpos += cw;
int d = cpos - pos.x();
bool dm = d < 0 ? !chr->rightToLeft : chr->rightToLeft;
if ( ( matchBetweenCharacters == TRUE && (QABS( d ) < dist || (dist == d && dm == TRUE )) && para->string()->validCursorPosition( i ) ) ||
( matchBetweenCharacters == FALSE && ( d == 0 || dm == TRUE ) ) ) {
dist = QABS( d );
if ( !link || ( pos.x() >= x + chr->x && ( loosePlacing == TRUE || pos.x() < cpos ) ) )
curpos = i;
}
}
i++;
}
if ( curpos == -1 ) {
if ( loosePlacing == TRUE )
curpos = s->length()-1;
else
return FALSE;
}
setIndex( curpos );
#ifndef QT_NO_TEXTCUSTOMITEM
if ( inCustom && para->document() && para->at( curpos )->isCustom() && para->at( curpos )->customItem()->isNested() ) {
QTextDocument *oldDoc = para->document();
gotoIntoNested( pos );
if ( oldDoc == para->document() )
return TRUE;
QPoint p( pos.x() - offsetX(), pos.y() - offsetY() );
if ( !place( p, document()->firstParagraph(), link ) )
pop();
}
#endif
return TRUE;
}
bool QTextCursor::processNesting( Operation op )
{
if ( !para->document() )
return FALSE;
QTextDocument* doc = para->document();
push();
ox = para->at( idx )->x;
int bl, y;
para->lineHeightOfChar( idx, &bl, &y );
oy = y + para->rect().y();
bool ok = FALSE;
#ifndef QT_NO_TEXTCUSTOMITEM
switch ( op ) {
case EnterBegin:
ok = para->at( idx )->customItem()->enter( this, doc, para, idx, ox, oy );
break;
case EnterEnd:
ok = para->at( idx )->customItem()->enter( this, doc, para, idx, ox, oy, TRUE );
break;
case Next:
ok = para->at( idx )->customItem()->next( this, doc, para, idx, ox, oy );
break;
case Prev:
ok = para->at( idx )->customItem()->prev( this, doc, para, idx, ox, oy );
break;
case Down:
ok = para->at( idx )->customItem()->down( this, doc, para, idx, ox, oy );
break;
case Up:
ok = para->at( idx )->customItem()->up( this, doc, para, idx, ox, oy );
break;
}
if ( !ok )
#endif
pop();
return ok;
}
void QTextCursor::gotoRight()
{
if ( para->string()->isRightToLeft() )
gotoPreviousLetter();
else
gotoNextLetter();
}
void QTextCursor::gotoNextLetter()
{
tmpX = -1;
#ifndef QT_NO_TEXTCUSTOMITEM
const QTextStringChar *tsc = para->at( idx );
if ( tsc && tsc->isCustom() && tsc->customItem()->isNested() ) {
if ( processNesting( EnterBegin ) )
return;
}
#endif
if ( idx < para->length() - 1 ) {
idx = para->string()->nextCursorPosition( idx );
} else if ( para->next() ) {
para = para->next();
while ( !para->isVisible() && para->next() )
para = para->next();
idx = 0;
} else if ( nestedDepth() ) {
pop();
processNesting( Next );
if ( idx == -1 ) {
pop();
if ( idx < para->length() - 1 ) {
idx = para->string()->nextCursorPosition( idx );
} else if ( para->next() ) {
para = para->next();
idx = 0;
}
}
}
}
void QTextCursor::gotoUp()
{
int indexOfLineStart;
int line;
QTextStringChar *c = para->lineStartOfChar( idx, &indexOfLineStart, &line );
if ( !c )
return;
if (tmpX < 0)
tmpX = x();
if ( indexOfLineStart == 0 ) {
if ( !para->prev() ) {
if ( !nestedDepth() )
return;
pop();
processNesting( Up );
if ( idx == -1 ) {
pop();
if ( !para->prev() )
return;
idx = tmpX = 0;
} else {
tmpX = -1;
return;
}
}
QTextParagraph *p = para->prev();
while ( p && !p->isVisible() )
p = p->prev();
if ( p )
para = p;
int lastLine = para->lines() - 1;
if ( !para->lineStartOfLine( lastLine, &indexOfLineStart ) )
return;
idx = indexOfLineStart;
while (idx < para->length()-1 && para->at(idx)->x < tmpX)
++idx;
if (idx > indexOfLineStart &&
para->at(idx)->x - tmpX > tmpX - para->at(idx-1)->x)
--idx;
} else {
--line;
int oldIndexOfLineStart = indexOfLineStart;
if ( !para->lineStartOfLine( line, &indexOfLineStart ) )
return;
idx = indexOfLineStart;
while (idx < oldIndexOfLineStart-1 && para->at(idx)->x < tmpX)
++idx;
if (idx > indexOfLineStart &&
para->at(idx)->x - tmpX > tmpX - para->at(idx-1)->x)
--idx;
}
fixCursorPosition();
}
void QTextCursor::gotoDown()
{
int indexOfLineStart;
int line;
QTextStringChar *c = para->lineStartOfChar( idx, &indexOfLineStart, &line );
if ( !c )
return;
if (tmpX < 0)
tmpX = x();
if ( line == para->lines() - 1 ) {
if ( !para->next() ) {
if ( !nestedDepth() )
return;
pop();
processNesting( Down );
if ( idx == -1 ) {
pop();
if ( !para->next() )
return;
idx = tmpX = 0;
} else {
tmpX = -1;
return;
}
}
QTextParagraph *s = para->next();
while ( s && !s->isVisible() )
s = s->next();
if ( s )
para = s;
if ( !para->lineStartOfLine( 0, &indexOfLineStart ) )
return;
int end;
if ( para->lines() == 1 )
end = para->length();
else
para->lineStartOfLine( 1, &end );
idx = indexOfLineStart;
while (idx < end-1 && para->at(idx)->x < tmpX)
++idx;
if (idx > indexOfLineStart &&
para->at(idx)->x - tmpX > tmpX - para->at(idx-1)->x)
--idx;
} else {
++line;
int end;
if ( line == para->lines() - 1 )
end = para->length();
else
para->lineStartOfLine( line + 1, &end );
if ( !para->lineStartOfLine( line, &indexOfLineStart ) )
return;
idx = indexOfLineStart;
while (idx < end-1 && para->at(idx)->x < tmpX)
++idx;
if (idx > indexOfLineStart &&
para->at(idx)->x - tmpX > tmpX - para->at(idx-1)->x)
--idx;
}
fixCursorPosition();
}
void QTextCursor::gotoLineEnd()
{
tmpX = -1;
int indexOfLineStart;
int line;
QTextStringChar *c = para->lineStartOfChar( idx, &indexOfLineStart, &line );
if ( !c )
return;
if ( line == para->lines() - 1 ) {
idx = para->length() - 1;
} else {
c = para->lineStartOfLine( ++line, &indexOfLineStart );
indexOfLineStart--;
idx = indexOfLineStart;
}
}
void QTextCursor::gotoLineStart()
{
tmpX = -1;
int indexOfLineStart;
int line;
QTextStringChar *c = para->lineStartOfChar( idx, &indexOfLineStart, &line );
if ( !c )
return;
idx = indexOfLineStart;
}
void QTextCursor::gotoHome()
{
if ( topParagraph()->document() )
gotoPosition( topParagraph()->document()->firstParagraph() );
else
gotoLineStart();
}
void QTextCursor::gotoEnd()
{
if ( topParagraph()->document() && topParagraph()->document()->lastParagraph()->isValid() )
gotoPosition( topParagraph()->document()->lastParagraph(),
topParagraph()->document()->lastParagraph()->length() - 1);
else
gotoLineEnd();
}
void QTextCursor::gotoPageUp( int visibleHeight )
{
int targetY = globalY() - visibleHeight;
QTextParagraph* old; int index;
do {
old = para; index = idx;
gotoUp();
} while ( (old != para || index != idx) && globalY() > targetY );
}
void QTextCursor::gotoPageDown( int visibleHeight )
{
int targetY = globalY() + visibleHeight;
QTextParagraph* old; int index;
do {
old = para; index = idx;
gotoDown();
} while ( (old != para || index != idx) && globalY() < targetY );
}
void QTextCursor::gotoWordRight()
{
if ( para->string()->isRightToLeft() )
gotoPreviousWord();
else
gotoNextWord();
}
void QTextCursor::gotoWordLeft()
{
if ( para->string()->isRightToLeft() )
gotoNextWord();
else
gotoPreviousWord();
}
static bool is_seperator( const QChar &c, bool onlySpace )
{
if ( onlySpace )
return c.isSpace();
return c.isSpace() ||
c == '\t' ||
c == '.' ||
c == ',' ||
c == ':' ||
c == ';' ||
c == '-' ||
c == '<' ||
c == '>' ||
c == '[' ||
c == ']' ||
c == '(' ||
c == ')' ||
c == '{' ||
c == '}';
}
void QTextCursor::gotoPreviousWord( bool onlySpace )
{
gotoPreviousLetter();
tmpX = -1;
QTextString *s = para->string();
bool allowSame = FALSE;
if ( idx == ((int)s->length()-1) )
return;
for ( int i = idx; i >= 0; --i ) {
if ( is_seperator( s->at( i ).c, onlySpace ) ) {
if ( !allowSame )
continue;
idx = i + 1;
return;
}
if ( !allowSame && !is_seperator( s->at( i ).c, onlySpace ) )
allowSame = TRUE;
}
idx = 0;
}
void QTextCursor::gotoNextWord( bool onlySpace )
{
tmpX = -1;
QTextString *s = para->string();
bool allowSame = FALSE;
for ( int i = idx; i < (int)s->length(); ++i ) {
if ( !is_seperator( s->at( i ).c, onlySpace ) ) {
if ( !allowSame )
continue;
idx = i;
return;
}
if ( !allowSame && is_seperator( s->at( i ).c, onlySpace ) )
allowSame = TRUE;
}
if ( idx < ((int)s->length()-1) ) {
gotoLineEnd();
} else if ( para->next() ) {
QTextParagraph *p = para->next();
while ( p && !p->isVisible() )
p = p->next();
if ( s ) {
para = p;
idx = 0;
}
} else {
gotoLineEnd();
}
}
bool QTextCursor::atParagStart()
{
return idx == 0;
}
bool QTextCursor::atParagEnd()
{
return idx == para->length() - 1;
}
void QTextCursor::splitAndInsertEmptyParagraph( bool ind, bool updateIds )
{
if ( !para->document() )
return;
tmpX = -1;
QTextFormat *f = 0;
if ( para->document()->useFormatCollection() ) {
f = para->at( idx )->format();
if ( idx == para->length() - 1 && idx > 0 )
f = para->at( idx - 1 )->format();
if ( f->isMisspelled() ) {
f->removeRef();
f = para->document()->formatCollection()->format( f->font(), f->color() );
}
}
if ( atParagEnd() ) {
QTextParagraph *n = para->next();
QTextParagraph *s = para->document()->createParagraph( para->document(), para, n, updateIds );
if ( f )
s->setFormat( 0, 1, f, TRUE );
s->copyParagData( para );
if ( ind ) {
int oi, ni;
s->indent( &oi, &ni );
para = s;
idx = ni;
} else {
para = s;
idx = 0;
}
} else if ( atParagStart() ) {
QTextParagraph *p = para->prev();
QTextParagraph *s = para->document()->createParagraph( para->document(), p, para, updateIds );
if ( f )
s->setFormat( 0, 1, f, TRUE );
s->copyParagData( para );
if ( ind ) {
s->indent();
s->format();
indent();
para->format();
}
} else {
QString str = para->string()->toString().mid( idx, 0xFFFFFF );
QTextParagraph *n = para->next();
QTextParagraph *s = para->document()->createParagraph( para->document(), para, n, updateIds );
s->copyParagData( para );
s->remove( 0, 1 );
s->append( str, TRUE );
for ( uint i = 0; i < str.length(); ++i ) {
QTextStringChar* tsc = para->at( idx + i );
s->setFormat( i, 1, tsc->format(), TRUE );
#ifndef QT_NO_TEXTCUSTOMITEM
if ( tsc->isCustom() ) {
QTextCustomItem * item = tsc->customItem();
s->at( i )->setCustomItem( item );
tsc->loseCustomItem();
}
#endif
if ( tsc->isAnchor() )
s->at( i )->setAnchor( tsc->anchorName(),
tsc->anchorHref() );
}
para->truncate( idx );
if ( ind ) {
int oi, ni;
s->indent( &oi, &ni );
para = s;
idx = ni;
} else {
para = s;
idx = 0;
}
}
invalidateNested();
}
bool QTextCursor::remove()
{
tmpX = -1;
if ( !atParagEnd() ) {
int next = para->string()->nextCursorPosition( idx );
para->remove( idx, next-idx );
int h = para->rect().height();
para->format( -1, TRUE );
if ( h != para->rect().height() )
invalidateNested();
else if ( para->document() && para->document()->parent() )
para->document()->nextDoubleBuffered = TRUE;
return FALSE;
} else if ( para->next() ) {
para->join( para->next() );
invalidateNested();
return TRUE;
}
return FALSE;
}
/* needed to implement backspace the correct way */
bool QTextCursor::removePreviousChar()
{
tmpX = -1;
if ( !atParagStart() ) {
para->remove( idx-1, 1 );
int h = para->rect().height();
idx--;
// shouldn't be needed, just to make sure.
fixCursorPosition();
para->format( -1, TRUE );
if ( h != para->rect().height() )
invalidateNested();
else if ( para->document() && para->document()->parent() )
para->document()->nextDoubleBuffered = TRUE;
return FALSE;
} else if ( para->prev() ) {
para = para->prev();
para->join( para->next() );
invalidateNested();
return TRUE;
}
return FALSE;
}
void QTextCursor::indent()
{
int oi = 0, ni = 0;
para->indent( &oi, &ni );
if ( oi == ni )
return;
if ( idx >= oi )
idx += ni - oi;
else
idx = ni;
}
void QTextCursor::fixCursorPosition()
{
// searches for the closest valid cursor position
if ( para->string()->validCursorPosition( idx ) )
return;
int lineIdx;
QTextStringChar *start = para->lineStartOfChar( idx, &lineIdx, 0 );
int x = para->string()->at( idx ).x;
int diff = QABS(start->x - x);
int best = lineIdx;
QTextStringChar *c = start;
++c;
QTextStringChar *end = &para->string()->at( para->length()-1 );
while ( c <= end && !c->lineStart ) {
int xp = c->x;
if ( c->rightToLeft )
xp += para->string()->width( lineIdx + (c-start) );
int ndiff = QABS(xp - x);
if ( ndiff < diff && para->string()->validCursorPosition(lineIdx + (c-start)) ) {
diff = ndiff;
best = lineIdx + (c-start);
}
++c;
}
idx = best;
}
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
QTextDocument::QTextDocument( QTextDocument *p )
: par( p ), parentPar( 0 )
#ifndef QT_NO_TEXTCUSTOMITEM
, tc( 0 )
#endif
, tArray( 0 ), tStopWidth( 0 )
{
fCollection = par ? par->fCollection : new QTextFormatCollection;
init();
}
void QTextDocument::init()
{
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;
setStyleSheet( QStyleSheet::defaultSheet() );
#ifndef QT_NO_MIME
factory_ = QMimeSourceFactory::defaultFactory();
#endif
contxt = QString::null;
underlLinks = par ? par->underlLinks : TRUE;
backBrush = 0;
buf_pixmap = 0;
nextDoubleBuffered = FALSE;
if ( par )
withoutDoubleBuffer = par->withoutDoubleBuffer;
else
withoutDoubleBuffer = FALSE;
lParag = fParag = createParagraph( this, 0, 0 );
cx = 0;
cy = 2;
if ( par )
cx = cy = 0;
cw = 600;
vw = 0;
flow_ = new QTextFlow;
flow_->setWidth( cw );
leftmargin = rightmargin = 4;
scaleFontsFactor = 1;
selectionColors[ Standard ] = QApplication::palette().color( QPalette::Active, QColorGroup::Highlight );
selectionText[ Standard ] = TRUE;
selectionText[ IMSelectionText ] = TRUE;
selectionText[ IMCompositionText ] = FALSE;
commandHistory = new QTextCommandHistory( 100 );
tStopWidth = formatCollection()->defaultFormat()->width( 'x' ) * 8;
}
QTextDocument::~QTextDocument()
{
delete commandHistory;
if ( par )
par->removeChild( this );
clear();
delete flow_;
if ( !par ) {
delete pFormatter;
delete fCollection;
}
delete pProcessor;
delete buf_pixmap;
delete indenter;
delete backBrush;
delete [] tArray;
}
void QTextDocument::clear( bool createEmptyParag )
{
while ( fParag ) {
QTextParagraph *p = fParag->next();
delete fParag;
fParag = p;
}
if ( flow_ )
flow_->clear();
fParag = lParag = 0;
if ( createEmptyParag )
fParag = lParag = createParagraph( this );
focusIndicator.parag = 0;
selections.clear();
oText = QString::null;
oTextValid = FALSE;
}
int QTextDocument::widthUsed() const
{
return wused + 2*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 );
}
QTextParagraph *QTextDocument::createParagraph( QTextDocument *d, QTextParagraph *pr, QTextParagraph *nx, bool updateIds )
{
return new QTextParagraph( d, pr, nx, updateIds );
}
bool QTextDocument::setMinimumWidth( int needed, int used, QTextParagraph *p )
{
if ( needed == -1 ) {
minw = 0;
wused = 0;
p = 0;
}
if ( p == minwParag ) {
if (minw > needed) {
QTextParagraph *tp = fParag;
while (tp) {
if (tp != p && tp->minwidth > needed) {
needed = tp->minwidth;
minwParag = tp;
}
tp = tp->n;
}
}
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 )
{
preferRichText = FALSE;
clear();
oTextValid = TRUE;
oText = text;
int lastNl = 0;
int nl = text.find( '\n' );
if ( nl == -1 ) {
lParag = createParagraph( 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 = createParagraph( this, lParag, 0 );
if ( !fParag )
fParag = lParag;
int l = nl - lastNl;
if ( l > 0 ) {
if (text.unicode()[nl-1] == '\r')
l--;
QConstString cs(text.unicode()+lastNl, l);
lParag->append( cs.string() );
}
if ( nl == (int)text.length() )
break;
lastNl = nl + 1;
nl = text.find( '\n', nl + 1 );
if ( nl == -1 )
nl = text.length();
}
}
if ( !lParag )
lParag = fParag = createParagraph( 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(Qt::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;
}
Q_DUMMY_COMPARISON_OPERATOR(QTextDocumentTag)
};
#define NEWPAR do{ if ( !hasNewPar) { \
if ( !textEditMode && curpar && curpar->length()>1 && curpar->at( curpar->length()-2)->c == QChar_linesep ) \
curpar->remove( curpar->length()-2, 1 ); \
curpar = createParagraph( this, curpar, curpar->next() ); styles.append( vec ); vec = 0;} \
hasNewPar = TRUE; \
curpar->rtext = TRUE; \
curpar->align = curtag.alignment; \
curpar->lstyle = curtag.liststyle; \
curpar->litem = ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ); \
curpar->str->setDirection( (QChar::Direction)curtag.direction ); \
space = TRUE; \
tabExpansionColumn = 0; \
delete vec; vec = new QPtrVector<QStyleSheetItem>( (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 ); \
}while(FALSE);
void QTextDocument::setRichText( const QString &text, const QString &context, const QTextFormat *initialFormat )
{
preferRichText = TRUE;
if ( !context.isEmpty() )
setContext( context );
clear();
fParag = lParag = createParagraph( this );
oTextValid = TRUE;
oText = text;
setRichTextInternal( text, 0, initialFormat );
fParag->rtext = TRUE;
}
void QTextDocument::setRichTextInternal( const QString &text, QTextCursor* cursor, const QTextFormat *initialFormat )
{
QTextParagraph* curpar = lParag;
int pos = 0;
QValueStack<QTextDocumentTag> tags;
if ( !initialFormat )
initialFormat = formatCollection()->defaultFormat();
QTextDocumentTag initag( "", sheet_->item(""), *initialFormat );
if ( bodyText.isValid() )
initag.format.setColor( bodyText );
QTextDocumentTag curtag = initag;
bool space = TRUE;
bool canMergeLi = FALSE;
bool textEditMode = FALSE;
int tabExpansionColumn = 0;
const QChar* doc = text.unicode();
int length = text.length();
bool hasNewPar = curpar->length() <= 1;
QString anchorName;
// style sheet handling for margin and line spacing calculation below
QTextParagraph* stylesPar = curpar;
QPtrVector<QStyleSheetItem>* vec = 0;
QPtrList< QPtrVector<QStyleSheetItem> > styles;
styles.setAutoDelete( TRUE );
if ( cursor ) {
cursor->splitAndInsertEmptyParagraph();
QTextCursor tmp = *cursor;
tmp.gotoPreviousLetter();
stylesPar = curpar = tmp.paragraph();
hasNewPar = TRUE;
textEditMode = TRUE;
} else {
NEWPAR;
}
// set rtext spacing to FALSE for the initial paragraph.
curpar->rtext = FALSE;
QString wellKnownTags = "br hr wsp table qt body meta title";
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
const QStyleSheetItem* nstyle = sheet_->item(tagname);
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 and li for HTML
compatibility. We do not want to embed blocks in
p, and we do not want new blocks inside non-empty
lis. Plus we want to merge empty lis sometimes. */
if( nstyle->displayMode() == QStyleSheetItem::DisplayListItem ) {
canMergeLi = TRUE;
} else if ( nstyle->displayMode() == QStyleSheetItem::DisplayBlock ) {
while ( curtag.style->name() == "p" ) {
if ( tags.isEmpty() )
break;
curtag = tags.pop();
}
if ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) {
// we are in a li and a new block comes along
if ( nstyle->name() == "ul" || nstyle->name() == "ol" )
hasNewPar = FALSE; // we want an empty li (like most browsers)
if ( !hasNewPar ) {
/* do not add new blocks inside
non-empty lis */
while ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) {
if ( tags.isEmpty() )
break;
curtag = tags.pop();
}
} else if ( canMergeLi ) {
/* we have an empty li and a block
comes along, merge them */
nstyle = curtag.style;
}
canMergeLi = FALSE;
}
}
}
#ifndef QT_NO_TEXTCUSTOMITEM
QTextCustomItem* custom = 0;
#else
bool custom = FALSE;
#endif
// some well-known tags, some have a nstyle, some not
if ( wellKnownTags.find( tagname ) != -1 ) {
if ( tagname == "br" ) {
emptyTag = space = TRUE;
int index = QMAX( curpar->length(),1) - 1;
QTextFormat format = curtag.format.makeTextFormat( nstyle, attr, scaleFontsFactor );
curpar->append( QChar_linesep );
curpar->setFormat( index, 1, &format );
hasNewPar = false;
} else if ( tagname == "hr" ) {
emptyTag = space = TRUE;
#ifndef QT_NO_TEXTCUSTOMITEM
custom = sheet_->tag( tagname, attr, contxt, *factory_ , emptyTag, this );
#endif
} else if ( tagname == "table" ) {
emptyTag = space = TRUE;
#ifndef QT_NO_TEXTCUSTOMITEM
QTextFormat format = curtag.format.makeTextFormat( nstyle, attr, scaleFontsFactor );
curpar->setAlignment( curtag.alignment );
custom = parseTable( attr, format, doc, length, pos, curpar );
#endif
} else if ( tagname == "qt" || tagname == "body" ) {
if ( attr.contains( "bgcolor" ) ) {
QBrush *b = new QBrush( QColor( attr["bgcolor"] ) );
setPaper( b );
}
if ( attr.contains( "background" ) ) {
#ifndef QT_NO_MIME
QImage img;
QString bg = attr["background"];
const QMimeSource* m = factory_->data( bg, contxt );
if ( !m ) {
qWarning("QRichText: no mimesource for %s", bg.latin1() );
} else {
if ( !QImageDrag::decode( m, img ) ) {
qWarning("QTextImage: cannot decode %s", bg.latin1() );
}
}
if ( !img.isNull() ) {
QBrush *b = new QBrush( QColor(), QPixmap( img ) );
setPaper( b );
}
#endif
}
if ( attr.contains( "text" ) ) {
QColor c( attr["text"] );
initag.format.setColor( c );
curtag.format.setColor( c );
bodyText = c;
}
if ( attr.contains( "link" ) )
linkColor = QColor( attr["link"] );
if ( attr.contains( "title" ) )
attribs.replace( "title", attr["title"] );
if ( textEditMode ) {
if ( attr.contains("style" ) ) {
QString a = attr["style"];
for ( int s = 0; s < a.contains(';')+1; s++ ) {
QString style = a.section( ';', s, s );
if ( style.startsWith("font-size:" ) && style.endsWith("pt") ) {
scaleFontsFactor = double( formatCollection()->defaultFormat()->fn.pointSize() ) /
style.mid( 10, style.length() - 12 ).toInt();
}
}
}
nstyle = 0; // ignore body in textEditMode
}
// end qt- and body-tag handling
} else if ( tagname == "meta" ) {
if ( attr["name"] == "qrichtext" && attr["content"] == "1" )
textEditMode = TRUE;
} else 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 );
}
} // end of well-known tag handling
#ifndef QT_NO_TEXTCUSTOMITEM
if ( !custom ) // try generic custom item
custom = sheet_->tag( tagname, attr, contxt, *factory_ , emptyTag, this );
#endif
if ( !nstyle && !custom ) // we have no clue what this tag could be, ignore it
continue;
if ( custom ) {
#ifndef QT_NO_TEXTCUSTOMITEM
int index = QMAX( curpar->length(),1) - 1;
QTextFormat format = curtag.format.makeTextFormat( nstyle, attr, scaleFontsFactor );
curpar->append( QChar('*') );
QTextFormat* f = formatCollection()->format( &format );
curpar->setFormat( index, 1, f );
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;
#endif
} else if ( !emptyTag ) {
/* if we do nesting, push curtag on the stack,
otherwise reinint curag. */
if ( curtag.style->name() != tagname || nstyle->selfNesting() ) {
tags.push( curtag );
} else {
if ( !tags.isEmpty() )
curtag = tags.top();
else
curtag = initag;
}
curtag.name = tagname;
curtag.style = nstyle;
curtag.name = tagname;
curtag.style = nstyle;
if ( nstyle->whiteSpaceMode() != QStyleSheetItem::WhiteSpaceModeUndefined )
curtag.wsm = nstyle->whiteSpaceMode();
/* netscape compatibility: eat a newline and only a newline if a pre block starts */
if ( curtag.wsm == QStyleSheetItem::WhiteSpacePre &&
nstyle->displayMode() == QStyleSheetItem::DisplayBlock )
eat( doc, length, pos, '\n' );
/* ignore whitespace for inline elements if there
was already one*/
if ( !textEditMode &&
(curtag.wsm == QStyleSheetItem::WhiteSpaceNormal
|| curtag.wsm == QStyleSheetItem::WhiteSpaceNoWrap)
&& ( space || nstyle->displayMode() != QStyleSheetItem::DisplayInline ) )
eatSpace( doc, length, pos );
curtag.format = curtag.format.makeTextFormat( nstyle, attr, scaleFontsFactor );
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 ( nstyle->listStyle() != QStyleSheetItem::ListStyleUndefined )
curtag.liststyle = nstyle->listStyle();
if ( nstyle->displayMode() == QStyleSheetItem::DisplayBlock
|| nstyle->displayMode() == QStyleSheetItem::DisplayListItem ) {
if ( nstyle->name() == "ol" || nstyle->name() == "ul" || nstyle->name() == "li") {
QString type = attr["type"];
if ( !type.isEmpty() ) {
if ( type == "1" ) {
curtag.liststyle = QStyleSheetItem::ListDecimal;
} else if ( type == "a" ) {
curtag.liststyle = QStyleSheetItem::ListLowerAlpha;
} else if ( type == "A" ) {
curtag.liststyle = QStyleSheetItem::ListUpperAlpha;
} else {
type = type.lower();
if ( type == "square" )
curtag.liststyle = QStyleSheetItem::ListSquare;
else if ( type == "disc" )
curtag.liststyle = QStyleSheetItem::ListDisc;
else if ( type == "circle" )
curtag.liststyle = QStyleSheetItem::ListCircle;
}
}
}
/* Internally we treat ordered and bullet
lists the same for margin calculations. In
order to have fast pointer compares in the
xMargin() functions we restrict ourselves to
<ol>. Once we calculate the margins in the
parser rathern than later, the unelegance of
this approach goes awy
*/
if ( nstyle->name() == "ul" )
curtag.style = sheet_->item( "ol" );
if ( attr.contains( "align" ) ) {
QString align = attr["align"].lower();
if ( align == "center" )
curtag.alignment = Qt::AlignCenter;
else if ( align == "right" )
curtag.alignment = Qt::AlignRight;
else if ( align == "justify" )
curtag.alignment = Qt::AlignJustify;
}
if ( attr.contains( "dir" ) ) {
QString dir = attr["dir"];
if ( dir == "rtl" )
curtag.direction = QChar::DirR;
else if ( dir == "ltr" )
curtag.direction = QChar::DirL;
}
NEWPAR;
if ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) {
if ( attr.contains( "value " ) )
curpar->setListValue( attr["value"].toInt() );
}
if ( attr.contains( "style" ) ) {
QString a = attr["style"];
bool ok = TRUE;
for ( int s = 0; ok && s < a.contains(';')+1; s++ ) {
QString style = a.section( ';', s, s );
if ( style.startsWith("margin-top:" ) && style.endsWith("px") )
curpar->utm = 1+style.mid(11, style.length() - 13).toInt(&ok);
else if ( style.startsWith("margin-bottom:" ) && style.endsWith("px") )
curpar->ubm = 1+style.mid(14, style.length() - 16).toInt(&ok);
else if ( style.startsWith("margin-left:" ) && style.endsWith("px") )
curpar->ulm = 1+style.mid(12, style.length() - 14).toInt(&ok);
else if ( style.startsWith("margin-right:" ) && style.endsWith("px") )
curpar->urm = 1+style.mid(13, style.length() - 15).toInt(&ok);
else if ( style.startsWith("text-indent:" ) && style.endsWith("px") )
curpar->uflm = 1+style.mid(12, style.length() - 14).toInt(&ok);
}
if ( !ok ) // be pressmistic
curpar->utm = curpar->ubm = curpar->urm = curpar->ulm = 0;
}
}
}
} else {
QString tagname = parseCloseTag( doc, length, pos );
if ( tagname.isEmpty() )
continue; // nothing we could do with this, probably parse error
if ( !sheet_->item( tagname ) ) // ignore unknown tags
continue;
if ( tagname == "li" )
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
|| curtag.style->displayMode() == QStyleSheetItem::DisplayListItem;
// 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 ( textEditMode && (tagname == "p" || tagname == "div" ) ) // preserve empty paragraphs
hasNewPar = FALSE;
NEWPAR;
}
}
} else {
// normal contents
QString s;
QChar c;
while ( pos < length && !hasPrefix(doc, length, pos, QChar('<') ) ){
if ( textEditMode ) {
// text edit mode: we handle all white space but ignore newlines
c = parseChar( doc, length, pos, QStyleSheetItem::WhiteSpacePre );
if ( c == QChar_linesep )
break;
} else {
int l = pos;
c = parseChar( doc, length, pos, curtag.wsm );
// in white space pre mode: treat any space as non breakable
// and expand tabs to eight character wide columns.
if ( curtag.wsm == QStyleSheetItem::WhiteSpacePre ) {
if ( c == '\t' ) {
c = ' ';
while( (++tabExpansionColumn)%8 )
s += c;
}
if ( c == QChar_linesep )
tabExpansionColumn = 0;
else
tabExpansionColumn++;
}
if ( c == ' ' || c == QChar_linesep ) {
/* avoid overlong paragraphs by forcing a new
paragraph after 4096 characters. This case can
occur when loading undiscovered plain text
documents in rich text mode. Instead of hanging
forever, we do the trick.
*/
if ( curtag.wsm == QStyleSheetItem::WhiteSpaceNormal && s.length() > 4096 ) do {
if ( doc[l] == '\n' ) {
hasNewPar = FALSE; // for a new paragraph ...
NEWPAR;
hasNewPar = FALSE; // ... and make it non-reusable
c = '\n'; // make sure we break below
break;
}
} while ( ++l < pos );
}
}
if ( c == '\n' )
break; // break on newlines, pre delievers a QChar_linesep
bool c_isSpace = c.isSpace() && c.unicode() != 0x00a0U && !textEditMode;
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 = QMAX( curpar->length(),1) - 1;
curpar->append( s );
if (curtag.wsm != QStyleSheetItem::WhiteSpaceNormal) {
QTextString *str = curpar->string();
for (uint i = index; i < index + s.length(); ++i)
str->at(i).nobreak = TRUE;
}
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() ) {
for ( int i = 0; i < int(s.length()); i++ )
curpar->at(index + i)->setAnchor( anchorName, curpar->at(index + i)->anchorHref() );
anchorName = QString::null;
}
}
}
}
if ( hasNewPar && curpar != fParag && !cursor && stylesPar != curpar ) {
// 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;
}
setRichTextMarginsInternal( styles, stylesPar );
if ( cursor ) {
cursor->gotoPreviousLetter();
cursor->remove();
}
delete vec;
}
void QTextDocument::setRichTextMarginsInternal( QPtrList< QPtrVector<QStyleSheetItem> >& styles, QTextParagraph* stylesPar )
{
// margin and line spacing calculation
QPtrVector<QStyleSheetItem>* prevStyle = 0;
QPtrVector<QStyleSheetItem>* curStyle = styles.first();
QPtrVector<QStyleSheetItem>* nextStyle = styles.next();
while ( stylesPar ) {
if ( !curStyle ) {
stylesPar = stylesPar->next();
prevStyle = curStyle;
curStyle = nextStyle;
nextStyle = styles.next();
continue;
}
int i, mar;
QStyleSheetItem* mainStyle = curStyle->size() ? (*curStyle)[curStyle->size()-1] : 0;
if ( mainStyle && mainStyle->displayMode() == QStyleSheetItem::DisplayListItem )
stylesPar->setListItem( TRUE );
int numLists = 0;
for ( i = 0; i < (int)curStyle->size(); ++i ) {
if ( (*curStyle)[ i ]->displayMode() == QStyleSheetItem::DisplayBlock
&& (*curStyle)[ i ]->listStyle() != QStyleSheetItem::ListStyleUndefined )
numLists++;
}
stylesPar->ldepth = numLists;
if ( stylesPar->next() && nextStyle ) {
// also set the depth of the next paragraph, required for the margin calculation
numLists = 0;
for ( i = 0; i < (int)nextStyle->size(); ++i ) {
if ( (*nextStyle)[ i ]->displayMode() == QStyleSheetItem::DisplayBlock
&& (*nextStyle)[ i ]->listStyle() != QStyleSheetItem::ListStyleUndefined )
numLists++;
}
stylesPar->next()->ldepth = numLists;
}
// do the top margin
QStyleSheetItem* item = mainStyle;
int m;
if (stylesPar->utm > 0 ) {
m = stylesPar->utm-1;
stylesPar->utm = 0;
} else {
m = QMAX(0, item->margin( QStyleSheetItem::MarginTop ) );
if ( stylesPar->ldepth ) {
if ( item->displayMode() == QStyleSheetItem::DisplayListItem )
m /= stylesPar->ldepth * stylesPar->ldepth;
else
m = 0;
}
}
for ( i = (int)curStyle->size() - 2 ; i >= 0; --i ) {
item = (*curStyle)[ i ];
if ( prevStyle && i < (int) prevStyle->size() &&
( item->displayMode() == QStyleSheetItem::DisplayBlock &&
(*prevStyle)[ i ] == item ) )
break;
// emulate CSS2' standard 0 vertical margin for multiple ul or ol tags
if ( item->listStyle() != QStyleSheetItem::ListStyleUndefined &&
( ( i> 0 && (*curStyle)[ i-1 ] == item ) || (*curStyle)[i+1] == item ) )
continue;
mar = QMAX( 0, item->margin( QStyleSheetItem::MarginTop ) );
m = QMAX( m, mar );
}
stylesPar->utm = m - stylesPar->topMargin();
// do the bottom margin
item = mainStyle;
if (stylesPar->ubm > 0 ) {
m = stylesPar->ubm-1;
stylesPar->ubm = 0;
} else {
m = QMAX(0, item->margin( QStyleSheetItem::MarginBottom ) );
if ( stylesPar->ldepth ) {
if ( item->displayMode() == QStyleSheetItem::DisplayListItem )
m /= stylesPar->ldepth * stylesPar->ldepth;
else
m = 0;
}
}
for ( i = (int)curStyle->size() - 2 ; i >= 0; --i ) {
item = (*curStyle)[ i ];
if ( nextStyle && i < (int) nextStyle->size() &&
( item->displayMode() == QStyleSheetItem::DisplayBlock &&
(*nextStyle)[ i ] == item ) )
break;
// emulate CSS2' standard 0 vertical margin for multiple ul or ol tags
if ( item->listStyle() != QStyleSheetItem::ListStyleUndefined &&
( ( i> 0 && (*curStyle)[ i-1 ] == item ) || (*curStyle)[i+1] == item ) )
continue;
mar = QMAX(0, item->margin( QStyleSheetItem::MarginBottom ) );
m = QMAX( m, mar );
}
stylesPar->ubm = m - stylesPar->bottomMargin();
// do the left margin, simplyfied
item = mainStyle;
if (stylesPar->ulm > 0 ) {
m = stylesPar->ulm-1;
stylesPar->ulm = 0;
} else {
m = QMAX( 0, item->margin( QStyleSheetItem::MarginLeft ) );
}
for ( i = (int)curStyle->size() - 2 ; i >= 0; --i ) {
item = (*curStyle)[ i ];
m += QMAX( 0, item->margin( QStyleSheetItem::MarginLeft ) );
}
stylesPar->ulm = m - stylesPar->leftMargin();
// do the right margin, simplyfied
item = mainStyle;
if (stylesPar->urm > 0 ) {
m = stylesPar->urm-1;
stylesPar->urm = 0;
} else {
m = QMAX( 0, item->margin( QStyleSheetItem::MarginRight ) );
}
for ( i = (int)curStyle->size() - 2 ; i >= 0; --i ) {
item = (*curStyle)[ i ];
m += QMAX( 0, item->margin( QStyleSheetItem::MarginRight ) );
}
stylesPar->urm = m - stylesPar->rightMargin();
// do the first line margin, which really should be called text-indent
item = mainStyle;
if (stylesPar->uflm > 0 ) {
m = stylesPar->uflm-1;
stylesPar->uflm = 0;
} else {
m = QMAX( 0, item->margin( QStyleSheetItem::MarginFirstLine ) );
}
for ( i = (int)curStyle->size() - 2 ; i >= 0; --i ) {
item = (*curStyle)[ i ];
mar = QMAX( 0, item->margin( QStyleSheetItem::MarginFirstLine ) );
m = QMAX( m, mar );
}
stylesPar->uflm =m - stylesPar->firstLineMargin();
// do the bogus line "spacing", which really is just an extra margin
item = mainStyle;
for ( i = (int)curStyle->size() - 1 ; i >= 0; --i ) {
item = (*curStyle)[ i ];
if ( item->lineSpacing() != QStyleSheetItem::Undefined ) {
stylesPar->ulinespacing = item->lineSpacing();
if ( formatCollection() &&
stylesPar->ulinespacing < formatCollection()->defaultFormat()->height() )
stylesPar->ulinespacing += formatCollection()->defaultFormat()->height();
break;
}
}
stylesPar = stylesPar->next();
prevStyle = curStyle;
curStyle = nextStyle;
nextStyle = styles.next();
}
}
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() const
{
QString buffer;
QString s;
QTextParagraph *p = fParag;
while ( p ) {
if ( !p->mightHaveCustomItems ) {
const QTextString *ts = p->string(); // workaround VC++ and Borland
s = ts->toString(); // with FALSE we don't fix spaces (nbsp)
} else {
for ( int i = 0; i < p->length() - 1; ++i ) {
#ifndef QT_NO_TEXTCUSTOMITEM
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
#endif
{
s += p->at( i )->c;
}
}
}
s.remove( s.length() - 1, 1 );
if ( p->next() )
s += "\n";
buffer += s;
p = p->next();
}
return buffer;
}
static QString align_to_string( int a )
{
if ( a & Qt::AlignRight )
return " align=\"right\"";
if ( a & Qt::AlignHCenter )
return " align=\"center\"";
if ( a & Qt::AlignJustify )
return " align=\"justify\"";
return QString::null;
}
static QString direction_to_string( int d )
{
if ( d != QChar::DirON )
return ( d == QChar::DirL? " dir=\"ltr\"" : " dir=\"rtl\"" );
return QString::null;
}
static QString list_value_to_string( int v )
{
if ( v != -1 )
return " listvalue=\"" + QString::number( v ) + "\"";
return QString::null;
}
static QString list_style_to_string( int v )
{
switch( v ) {
case QStyleSheetItem::ListDecimal: return "\"1\"";
case QStyleSheetItem::ListLowerAlpha: return "\"a\"";
case QStyleSheetItem::ListUpperAlpha: return "\"A\"";
case QStyleSheetItem::ListDisc: return "\"disc\"";
case QStyleSheetItem::ListSquare: return "\"square\"";
case QStyleSheetItem::ListCircle: return "\"circle\"";
default:
return QString::null;
}
}
static inline bool list_is_ordered( int v )
{
return v == QStyleSheetItem::ListDecimal ||
v == QStyleSheetItem::ListLowerAlpha ||
v == QStyleSheetItem::ListUpperAlpha;
}
static QString margin_to_string( QStyleSheetItem* style, int t, int b, int l, int r, int fl )
{
QString s;
if ( l > 0 )
s += QString(!!s?";":"") + "margin-left:" + QString::number(l+QMAX(0,style->margin(QStyleSheetItem::MarginLeft))) + "px";
if ( r > 0 )
s += QString(!!s?";":"") + "margin-right:" + QString::number(r+QMAX(0,style->margin(QStyleSheetItem::MarginRight))) + "px";
if ( t > 0 )
s += QString(!!s?";":"") + "margin-top:" + QString::number(t+QMAX(0,style->margin(QStyleSheetItem::MarginTop))) + "px";
if ( b > 0 )
s += QString(!!s?";":"") + "margin-bottom:" + QString::number(b+QMAX(0,style->margin(QStyleSheetItem::MarginBottom))) + "px";
if ( fl > 0 )
s += QString(!!s?";":"") + "text-indent:" + QString::number(fl+QMAX(0,style->margin(QStyleSheetItem::MarginFirstLine))) + "px";
if ( !!s )
return " style=\"" + s + "\"";
return QString::null;
}
QString QTextDocument::richText() const
{
QString s = "";
if ( !par ) {
s += "<html><head><meta name=\"qrichtext\" content=\"1\" /></head><body style=\"font-size:" ;
s += QString::number( formatCollection()->defaultFormat()->font().pointSize() );
s += "pt;font-family:";
s += formatCollection()->defaultFormat()->font().family();
s +="\">";
}
QTextParagraph* p = fParag;
QStyleSheetItem* item_p = styleSheet()->item("p");
QStyleSheetItem* item_div = styleSheet()->item("div");
QStyleSheetItem* item_ul = styleSheet()->item("ul");
QStyleSheetItem* item_ol = styleSheet()->item("ol");
QStyleSheetItem* item_li = styleSheet()->item("li");
if ( !item_p || !item_div || !item_ul || !item_ol || !item_li ) {
qWarning( "QTextEdit: cannot export HTML due to insufficient stylesheet (lack of p, div, ul, ol, or li)" );
return QString::null;
}
int pastListDepth = 0;
int listDepth = 0;
#if 0
int futureListDepth = 0;
#endif
QMemArray<int> listStyles(10);
while ( p ) {
listDepth = p->listDepth();
if ( listDepth < pastListDepth ) {
for ( int i = pastListDepth; i > listDepth; i-- )
s += list_is_ordered( listStyles[i] ) ? "</ol>" : "</ul>";
s += '\n';
} else if ( listDepth > pastListDepth ) {
s += '\n';
listStyles.resize( QMAX( (int)listStyles.size(), listDepth+1 ) );
QString list_type;
listStyles[listDepth] = p->listStyle();
if ( !list_is_ordered( p->listStyle() ) || item_ol->listStyle() != p->listStyle() )
list_type = " type=" + list_style_to_string( p->listStyle() );
for ( int i = pastListDepth; i < listDepth; i++ ) {
s += list_is_ordered( p->listStyle() ) ? "<ol" : "<ul" ;
s += list_type + ">";
}
} else {
s += '\n';
}
QString ps = p->richText();
#if 0
// for the bottom margin we need to know whether we are at the end of a list
futureListDepth = 0;
if ( listDepth > 0 && p->next() )
futureListDepth = p->next()->listDepth();
#endif
if ( richTextExportStart && richTextExportStart->paragraph() ==p &&
richTextExportStart->index() == 0 )
s += "<!--StartFragment-->";
if ( p->isListItem() ) {
s += "<li";
if ( p->listStyle() != listStyles[listDepth] )
s += " type=" + list_style_to_string( p->listStyle() );
s +=align_to_string( p->alignment() );
s += margin_to_string( item_li, p->utm, p->ubm, p->ulm, p->urm, p->uflm );
s += list_value_to_string( p->listValue() );
s += direction_to_string( p->direction() );
s +=">";
s += ps;
s += "</li>";
} else if ( p->listDepth() ) {
s += "<div";
s += align_to_string( p->alignment() );
s += margin_to_string( item_div, p->utm, p->ubm, p->ulm, p->urm, p->uflm );
s +=direction_to_string( p->direction() );
s += ">";
s += ps;
s += "</div>";
} else {
// normal paragraph item
s += "<p";
s += align_to_string( p->alignment() );
s += margin_to_string( item_p, p->utm, p->ubm, p->ulm, p->urm, p->uflm );
s +=direction_to_string( p->direction() );
s += ">";
s += ps;
s += "</p>";
}
pastListDepth = listDepth;
p = p->next();
}
while ( listDepth > 0 ) {
s += list_is_ordered( listStyles[listDepth] ) ? "</ol>" : "</ul>";
listDepth--;
}
if ( !par )
s += "\n</body></html>\n";
return s;
}
QString QTextDocument::text() const
{
if ( ( txtFormat == Qt::AutoText && preferRichText ) || txtFormat == Qt::RichText )
return richText();
return plainText();
}
QString QTextDocument::text( int parag ) const
{
QTextParagraph *p = paragAt( parag );
if ( !p )
return QString::null;
if ( ( txtFormat == Qt::AutoText && preferRichText ) || txtFormat == Qt::RichText )
return p->richText();
else
return p->string()->toString();
}
void QTextDocument::invalidate()
{
QTextParagraph *s = fParag;
while ( s ) {
s->invalidate( 0 );
s = s->next();
}
}
void QTextDocument::selectionStart( int id, int &paragId, int &index )
{
QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id );
if ( it == selections.end() )
return;
QTextDocumentSelection &sel = *it;
paragId = !sel.swapped ? sel.startCursor.paragraph()->paragId() : sel.endCursor.paragraph()->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 &paragId, int &index )
{
QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id );
if ( it == selections.end() )
return;
QTextDocumentSelection &sel = *it;
paragId = sel.swapped ? sel.startCursor.paragraph()->paragId() : sel.endCursor.paragraph()->paragId();
index = sel.swapped ? sel.startCursor.index() : sel.endCursor.index();
}
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.paragraph()->removeSelection( id );
c2.paragraph()->removeSelection( id );
if ( c1.paragraph() != c2.paragraph() ) {
c1.paragraph()->setSelection( id, c1.index(), c1.paragraph()->length() - 1 );
c2.paragraph()->setSelection( id, 0, c2.index() );
} else {
c1.paragraph()->setSelection( id, QMIN( c1.index(), c2.index() ), QMAX( c1.index(), c2.index() ) );
}
sel.startCursor = start;
sel.endCursor = end;
if ( sel.startCursor.paragraph() == sel.endCursor.paragraph() )
sel.swapped = sel.startCursor.index() > sel.endCursor.index();
}
bool QTextDocument::setSelectionEnd( int id, const 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.paragraph() == end.paragraph() ) {
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.setParagraph( tmp.paragraph()->paragId() < tmp2.paragraph()->paragId() ? tmp.paragraph() : tmp2.paragraph() );
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.paragraph() == start.paragraph() )
hadStartParag = TRUE;
if ( c.paragraph() == end.paragraph() )
hadEndParag = TRUE;
if ( c == sel.startCursor )
hadOldStart = TRUE;
if ( c == sel.endCursor )
hadOldEnd = TRUE;
if ( !sel.swapped &&
( ( hadEnd && !hadStart ) ||
( hadEnd && hadStart && start.paragraph() == end.paragraph() && start.index() > end.index() ) ) )
sel.swapped = TRUE;
if ( ( c == end && hadStartParag ) ||
( c == start && hadEndParag ) ) {
QTextCursor tmp = c;
tmp.restoreState();
if ( tmp.paragraph() != c.paragraph() ) {
int sstart = tmp.paragraph()->selectionStart( id );
tmp.paragraph()->removeSelection( id );
tmp.paragraph()->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.paragraph()->hasSelection( id ) && c.atParagEnd();
c.paragraph()->removeSelection( id );
if ( inSelection ) {
if ( c.paragraph() == start.paragraph() && start.paragraph() == end.paragraph() ) {
c.paragraph()->setSelection( id, QMIN( start.index(), end.index() ), QMAX( start.index(), end.index() ) );
} else if ( c.paragraph() == start.paragraph() && !hadEndParag ) {
c.paragraph()->setSelection( id, start.index(), c.paragraph()->length() - 1 );
} else if ( c.paragraph() == end.paragraph() && !hadStartParag ) {
c.paragraph()->setSelection( id, end.index(), c.paragraph()->length() - 1 );
} else if ( c.paragraph() == end.paragraph() && hadEndParag ) {
c.paragraph()->setSelection( id, 0, end.index() );
} else if ( c.paragraph() == start.paragraph() && hadStartParag ) {
c.paragraph()->setSelection( id, 0, start.index() );
} else {
c.paragraph()->setSelection( id, 0, c.paragraph()->length() - 1 );
}
}
if ( leftSelection )
inSelection = FALSE;
if ( noSelectionAnymore )
break;
// *ugle*hack optimization
QTextParagraph *p = c.paragraph();
if ( p->mightHaveCustomItems || p == start.paragraph() || p == end.paragraph() || p == lastParagraph() ) {
c.gotoNextLetter();
if ( p == lastParagraph() && c.atParagEnd() )
break;
} else {
if ( p->document()->parent() )
do {
c.gotoNextLetter();
} while ( c.paragraph() == p );
else
c.setParagraph( p->next() );
}
}
if ( !sel.swapped )
sel.startCursor.paragraph()->setSelection( id, sel.startCursor.index(), sel.startCursor.paragraph()->length() - 1 );
sel.startCursor = start;
sel.endCursor = end;
if ( sel.startCursor.paragraph() == sel.endCursor.paragraph() )
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.setParagraph( fParag );
c.setIndex( 0 );
sel.startCursor = c;
c.setParagraph( lParag );
c.setIndex( lParag->length() - 1 );
sel.endCursor = c;
selections.insert( id, sel );
QTextParagraph *p = fParag;
while ( p ) {
p->setSelection( id, 0, p->length() - 1 );
p = p->next();
}
for ( QTextDocument *d = childList.first(); d; d = childList.next() )
d->selectAll( id );
}
bool QTextDocument::removeSelection( int id )
{
if ( !selections.contains( id ) )
return FALSE;
QTextDocumentSelection &sel = selections[ id ];
QTextCursor start = sel.swapped ? sel.endCursor : sel.startCursor;
QTextCursor end = sel.swapped ? sel.startCursor : sel.endCursor;
QTextParagraph* p = 0;
while ( start != end ) {
if ( p != start.paragraph() ) {
p = start.paragraph();
p->removeSelection( id );
//### avoid endless loop by all means necessary, did somebody mention refactoring?
if ( !parent() && p == lParag )
break;
}
start.gotoNextLetter();
}
p = start.paragraph();
p->removeSelection( id );
selections.remove( id );
return TRUE;
}
QString QTextDocument::selectedText( int id, bool asRichText ) const
{
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 when refactoring
*/
while ( c2.nestedDepth() > c1.nestedDepth() )
c2.oneUp();
while ( c1.nestedDepth() > c2.nestedDepth() )
c1.oneUp();
while ( c1.nestedDepth() && c2.nestedDepth() &&
c1.paragraph()->document() != c2.paragraph()->document() ) {
c1.oneUp();
c2.oneUp();
}
// do not trust sel_swapped with tables. Fix this properly when refactoring as well
if ( c1.paragraph()->paragId() > c2.paragraph()->paragId() ||
(c1.paragraph() == c2.paragraph() && c1.index() > c2.index() ) ) {
QTextCursor tmp = c1;
c2 = c1;
c1 = tmp;
}
// end selection 3.0.3 improvement
if ( asRichText && !parent() ) {
richTextExportStart = &c1;
richTextExportEnd = &c2;
QString sel = richText();
int from = sel.find( "<!--StartFragment-->" );
if ( from >= 0 ) {
from += 20;
// find the previous span and move it into the start fragment before we clip it
QString prevspan;
int pspan = sel.findRev( "<span", from-21 );
if ( pspan > sel.findRev( "</span", from-21 ) ) {
int spanend = sel.find( '>', pspan );
prevspan = sel.mid( pspan, spanend - pspan + 1 );
}
int to = sel.findRev( "<!--EndFragment-->" );
if ( from <= to )
sel = "<!--StartFragment-->" + prevspan + sel.mid( from, to - from );
}
richTextExportStart = richTextExportEnd = 0;
return sel;
}
QString s;
if ( c1.paragraph() == c2.paragraph() ) {
QTextParagraph *p = c1.paragraph();
int end = c2.index();
if ( p->at( QMAX( 0, end - 1 ) )->isCustom() )
++end;
if ( !p->mightHaveCustomItems ) {
s += p->string()->toString().mid( c1.index(), end - c1.index() );
} else {
for ( int i = c1.index(); i < end; ++i ) {
#ifndef QT_NO_TEXTCUSTOMITEM
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
#endif
{
s += p->at( i )->c;
}
}
}
} else {
QTextParagraph *p = c1.paragraph();
int start = c1.index();
while ( p ) {
int end = p == c2.paragraph() ? c2.index() : p->length() - 1;
if ( p == c2.paragraph() && p->at( QMAX( 0, end - 1 ) )->isCustom() )
++end;
if ( !p->mightHaveCustomItems ) {
s += p->string()->toString().mid( start, end - start );
if ( p != c2.paragraph() )
s += "\n";
} else {
for ( int i = start; i < end; ++i ) {
#ifndef QT_NO_TEXTCUSTOMITEM
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
#endif
{
s += p->at( i )->c;
}
}
}
start = 0;
if ( p == c2.paragraph() )
break;
p = p->next();
}
}
// ### workaround for plain text export until we get proper
// mime types: turn unicode line seperators into the more
// widely understood \n. Makes copy and pasting code snipplets
// from within Assistent possible
QChar* uc = (QChar*) s.unicode();
for ( uint ii = 0; ii < s.length(); ii++ ) {
if ( uc[(int)ii] == QChar_linesep )
uc[(int)ii] = QChar('\n');
else if ( uc[(int)ii] == QChar::nbsp )
uc[(int)ii] = QChar(' ');
}
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.paragraph() == c2.paragraph() ) {
c1.paragraph()->setFormat( c1.index(), c2.index() - c1.index(), f, TRUE, flags );
return;
}
c1.paragraph()->setFormat( c1.index(), c1.paragraph()->length() - c1.index(), f, TRUE, flags );
QTextParagraph *p = c1.paragraph()->next();
while ( p && p != c2.paragraph() ) {
p->setFormat( 0, p->length(), f, TRUE, flags );
p = p->next();
}
c2.paragraph()->setFormat( 0, c2.index(), f, TRUE, flags );
}
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.paragraph() == c2.paragraph() ) {
c1.paragraph()->remove( c1.index(), c2.index() - c1.index() );
return;
}
if ( c1.paragraph() == fParag && c1.index() == 0 &&
c2.paragraph() == lParag && c2.index() == lParag->length() - 1 )
cursor->setValid( FALSE );
bool didGoLeft = FALSE;
if ( c1.index() == 0 && c1.paragraph() != fParag ) {
cursor->gotoPreviousLetter();
didGoLeft = cursor->isValid();
}
c1.paragraph()->remove( c1.index(), c1.paragraph()->length() - 1 - c1.index() );
QTextParagraph *p = c1.paragraph()->next();
int dy = 0;
QTextParagraph *tmp;
while ( p && p != c2.paragraph() ) {
tmp = p->next();
dy -= p->rect().height();
delete p;
p = tmp;
}
c2.paragraph()->remove( 0, c2.index() );
while ( p ) {
p->move( dy );
p->invalidate( 0 );
p->setEndState( -1 );
p = p->next();
}
c1.paragraph()->join( c2.paragraph() );
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;
QTextParagraph *startParag = sel.startCursor.paragraph();
QTextParagraph *endParag = sel.endCursor.paragraph();
if ( sel.endCursor.paragraph()->paragId() < sel.startCursor.paragraph()->paragId() ) {
endParag = sel.startCursor.paragraph();
startParag = sel.endCursor.paragraph();
}
QTextParagraph *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( QTextCursor& cursor, const QString &expr, bool cs, bool wo, bool forward )
{
removeSelection( Standard );
QTextParagraph *p = 0;
if ( expr.isEmpty() )
return FALSE;
for (;;) {
if ( p != cursor.paragraph() ) {
p = cursor.paragraph();
QString s = cursor.paragraph()->string()->toString();
int start = cursor.index();
for ( ;; ) {
int res = forward ? s.find( expr, start, cs ) : s.findRev( expr, start, cs );
int end = res + expr.length();
if ( res == -1 || ( !forward && start <= res ) )
break;
if ( !wo || ( ( res == 0 || s[ res - 1 ].isSpace() || s[ res - 1 ].isPunct() ) &&
( end == (int)s.length() || s[ end ].isSpace() || s[ end ].isPunct() ) ) ) {
removeSelection( Standard );
cursor.setIndex( forward ? end : res );
setSelectionStart( Standard, cursor );
cursor.setIndex( forward ? res : end );
setSelectionEnd( Standard, cursor );
if ( !forward )
cursor.setIndex( res );
return TRUE;
}
start = res + (forward ? 1 : -1);
}
}
if ( forward ) {
if ( cursor.paragraph() == lastParagraph() && cursor.atParagEnd() )
break;
cursor.gotoNextLetter();
} else {
if ( cursor.paragraph() == firstParagraph() && cursor.atParagStart() )
break;
cursor.gotoPreviousLetter();
}
}
return FALSE;
}
void QTextDocument::setTextFormat( Qt::TextFormat f )
{
txtFormat = f;
if ( fParag == lParag && fParag->length() <= 1 )
fParag->rtext = ( f == Qt::RichText );
}
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;
QTextParagraph *startParag = sel.startCursor.paragraph();
QTextParagraph *endParag = sel.endCursor.paragraph();
if ( sel.startCursor.paragraph() == sel.endCursor.paragraph() &&
sel.startCursor.paragraph()->selectionStart( selId ) == sel.endCursor.paragraph()->selectionEnd( selId ) )
return FALSE;
if ( sel.endCursor.paragraph()->paragId() < sel.startCursor.paragraph()->paragId() ) {
endParag = sel.startCursor.paragraph();
startParag = sel.endCursor.paragraph();
}
QTextParagraph *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 );
tStopWidth = formatCollection()->defaultFormat()->width( 'x' ) * 8;
flow_->setWidth( w );
cw = w;
vw = w;
QTextParagraph *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 )
buf_pixmap = new QPixmap( s.expandedTo( QSize(1,1) ) );
else if ( buf_pixmap->size() != s )
buf_pixmap->resize( s.expandedTo( buf_pixmap->size() ) );
return buf_pixmap;
}
void QTextDocument::draw( QPainter *p, const QRect &rect, const QColorGroup &cg, const QBrush *paper )
{
if ( !firstParagraph() )
return;
if ( paper ) {
p->setBrushOrigin( -int( p->translationX() ),
-int( p->translationY() ) );
p->fillRect( rect, *paper );
}
QPainter * oldPainter = QTextFormat::painter();
QTextFormat::setPainter( p );
if ( formatCollection()->defaultFormat()->color() != cg.text() )
setDefaultFormat( formatCollection()->defaultFormat()->font(), cg.text() );
QTextParagraph *parag = firstParagraph();
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 );
}
QTextFormat::setPainter(oldPainter);
}
void QTextDocument::drawParagraph( QPainter *p, QTextParagraph *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() );
#ifndef QT_NO_TEXTCUSTOMITEM
if (!parag->tableCell())
#endif
ir.setWidth(width());
bool uDoubleBuffer = useDoubleBuffer( parag, p );
if ( uDoubleBuffer ) {
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 ( uDoubleBuffer || is_printer( painter ) )
painter->fillRect( QRect( 0, 0, ir.width(), ir.height() ), parag->backgroundBrush( cg ) );
else if ( cursor && cursor->paragraph() == parag )
painter->fillRect( QRect( parag->at( cursor->index() )->x, 0, 2, ir.height() ),
parag->backgroundBrush( cg ) );
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 ( uDoubleBuffer ) {
delete painter;
painter = 0;
p->drawPixmap( ir.topLeft(), *doubleBuffer, QRect( QPoint( 0, 0 ), ir.size() ) );
} else {
painter->translate( -ir.x(), -ir.y() );
}
parag->document()->nextDoubleBuffered = FALSE;
}
QTextParagraph *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 ( !firstParagraph() )
return 0;
QPainter * oldPainter = QTextFormat::painter();
QTextFormat::setPainter( p );
if ( formatCollection()->defaultFormat()->color() != cg.text() )
setDefaultFormat( formatCollection()->defaultFormat()->font(), cg.text() );
if ( cx < 0 && cy < 0 ) {
cx = 0;
cy = 0;
cw = width();
ch = height();
}
QTextParagraph *lastFormatted = 0;
QTextParagraph *parag = firstParagraph();
QPixmap *doubleBuffer = 0;
while ( parag ) {
lastFormatted = parag;
if ( !parag->isValid() )
parag->format();
QRect pr = parag->rect();
pr.setWidth( parag->document()->width() );
if ( pr.y() > cy + ch )
goto floating;
QRect clipr( cx, cy, cw, ch );
if ( !pr.intersects( clipr ) || ( onlyChanged && !parag->hasChanged() ) ) {
pr.setWidth( parag->document()->width() );
parag = parag->next();
continue;
}
drawParagraph( p, parag, cx, cy, cw, ch, doubleBuffer, cg, drawCursor, cursor, resetChanged );
parag = parag->next();
}
parag = lastParagraph();
floating:
if ( parag->rect().y() + parag->rect().height() < parag->document()->height() ) {
if ( !parag->document()->parent() ) {
QRect fillRect = QRect( 0, parag->rect().y() + parag->rect().height(), parag->document()->width(),
parag->document()->height() - ( parag->rect().y() + parag->rect().height() ) );
if ( QRect( cx, cy, cw, ch ).intersects( fillRect ) )
p->fillRect( fillRect, cg.brush( QColorGroup::Base ) );
}
if ( !flow()->isEmpty() ) {
QRect cr( cx, cy, cw, ch );
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;
}
QTextFormat::setPainter(oldPainter);
return lastFormatted;
}
/*
#### this function only sets the default font size in the format collection
*/
void QTextDocument::setDefaultFormat( const QFont &font, const QColor &color )
{
bool reformat = font != fCollection->defaultFormat()->font();
for ( QTextDocument *d = childList.first(); d; d = childList.next() )
d->setDefaultFormat( font, color );
fCollection->updateDefaultFormat( font, color, sheet_ );
if ( !reformat )
return;
tStopWidth = formatCollection()->defaultFormat()->width( 'x' ) * 8;
// invalidate paragraphs and custom items
QTextParagraph *p = fParag;
while ( p ) {
p->invalidate( 0 );
#ifndef QT_NO_TEXTCUSTOMITEM
for ( int i = 0; i < p->length() - 1; ++i )
if ( p->at( i )->isCustom() )
p->at( i )->customItem()->invalidate();
#endif
p = p->next();
}
}
#ifndef QT_NO_TEXTCUSTOMITEM
void QTextDocument::registerCustomItem( QTextCustomItem *i, QTextParagraph *p )
{
if ( i && i->placement() != QTextCustomItem::PlaceInline ) {
flow_->registerFloatingItem( i );
p->registerFloatingItem( i );
}
if (i) i->setParagraph( p );
p->mightHaveCustomItems = mightHaveCustomItems = TRUE;
}
void QTextDocument::unregisterCustomItem( QTextCustomItem *i, QTextParagraph *p )
{
p->unregisterFloatingItem( i );
i->setParagraph( 0 );
flow_->unregisterFloatingItem( i );
}
#endif
bool QTextDocument::hasFocusParagraph() const
{
return !!focusIndicator.parag;
}
QString QTextDocument::focusHref() const
{
return focusIndicator.href;
}
QString QTextDocument::focusName() const
{
return focusIndicator.name;
}
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;
focusIndicator.name = QString::null;
if ( next ) {
QTextParagraph *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();
focusIndicator.name = p->at( i )->anchorName();
while ( i < p->length() ) {
if ( !p->at( i )->isAnchor() )
return TRUE;
focusIndicator.len++;
i++;
}
#ifndef QT_NO_TEXTCUSTOMITEM
} 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();
focusIndicator.name = c->richText()->focusName();
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();
focusIndicator.name = c->richText()->focusName();
return TRUE;
}
}
}
#endif
}
}
index = 0;
p = p->next();
}
} else {
QTextParagraph *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();
focusIndicator.name = p->at( i )->anchorName();
while ( i >= -1 ) {
if ( i < 0 || !p->at( i )->isAnchor() ) {
focusIndicator.start++;
return TRUE;
}
if ( i < 0 )
break;
focusIndicator.len++;
focusIndicator.start--;
i--;
}
#ifndef QT_NO_TEXTCUSTOMITEM
} 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();
focusIndicator.name = c->richText()->focusName();
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();
focusIndicator.name = c->richText()->focusName();
return TRUE;
}
if ( cells.at() == 0 )
break;
}
}
#endif
}
}
p = p->prev();
if ( p )
index = p->length() - 1;
}
}
focusIndicator.parag = 0;
return FALSE;
}
int QTextDocument::length() const
{
int l = -1;
QTextParagraph *p = fParag;
while ( p ) {
l += p->length();
p = p->next();
}
return QMAX(0,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( ' ' );
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 );
}
applyFont( f );
return pntr_fm->width( c );
}
int QTextFormat::width( const QString &str, int pos ) const
{
int w = 0;
if ( str.unicode()[ pos ].unicode() == 0xad )
return w;
if ( !pntr || !pntr->isActive() ) {
if ( ha == AlignNormal ) {
w = fm.charWidth( 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_.charWidth( str, pos );
}
} else {
QFont f( fn );
if ( ha != AlignNormal ) {
if ( usePixelSizes )
f.setPixelSize( ( f.pixelSize() * 2 ) / 3 );
else
f.setPointSize( ( f.pointSize() * 2 ) / 3 );
}
applyFont( f );
w = pntr_fm->charWidth( str, pos );
}
return w;
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
QTextString::QTextString()
{
bidiDirty = TRUE;
bidi = FALSE;
rightToLeft = FALSE;
dir = QChar::DirON;
}
QTextString::QTextString( const QTextString &s )
{
bidiDirty = TRUE;
bidi = s.bidi;
rightToLeft = s.rightToLeft;
dir = s.dir;
data = s.data;
data.detach();
for ( int i = 0; i < (int)data.size(); ++i ) {
QTextFormat *f = data[i].format();
if ( f )
f->addRef();
}
}
void QTextString::insert( int index, const QString &s, QTextFormat *f )
{
insert( index, s.unicode(), s.length(), f );
}
void QTextString::insert( int index, const QChar *unicode, int len, QTextFormat *f )
{
int os = data.size();
data.resize( data.size() + len, QGArray::SpeedOptim );
if ( index < os ) {
memmove( data.data() + index + len, data.data() + index,
sizeof( QTextStringChar ) * ( os - index ) );
}
QTextStringChar *ch = data.data() + index;
for ( int i = 0; i < len; ++i ) {
ch->x = 0;
ch->lineStart = 0;
ch->d.format = 0;
ch->nobreak = FALSE;
ch->type = QTextStringChar::Regular;
ch->d.format = f;
ch->rightToLeft = 0;
ch->c = unicode[i];
++ch;
}
bidiDirty = TRUE;
}
QTextString::~QTextString()
{
clear();
}
void QTextString::insert( int index, QTextStringChar *c, bool doAddRefFormat )
{
int os = data.size();
data.resize( data.size() + 1, QGArray::SpeedOptim );
if ( index < os ) {
memmove( data.data() + index + 1, data.data() + index,
sizeof( QTextStringChar ) * ( os - index ) );
}
QTextStringChar &ch = data[ (int)index ];
ch.c = c->c;
ch.x = 0;
ch.lineStart = 0;
ch.rightToLeft = 0;
ch.d.format = 0;
ch.type = QTextStringChar::Regular;
ch.nobreak = FALSE;
if ( doAddRefFormat && c->format() )
c->format()->addRef();
ch.setFormat( c->format() );
bidiDirty = TRUE;
}
int QTextString::appendParagraphs( QTextParagraph *start, QTextParagraph *end )
{
int paragCount = 0;
int newLength = data.size();
QTextParagraph *p = start;
for (; p != end; p = p->next()) {
newLength += p->length();
++paragCount;
}
const int oldLength = data.size();
data.resize(newLength, QGArray::SpeedOptim);
QTextStringChar *d = &data[oldLength];
for (p = start; p != end; p = p->next()) {
const QTextStringChar * const src = p->at(0);
int i = 0;
for (; i < p->length() - 1; ++i) {
d[i].c = src[i].c;
d[i].x = 0;
d[i].lineStart = 0;
d[i].rightToLeft = 0;
d[i].type = QTextStringChar::Regular;
d[i].nobreak = FALSE;
d[i].d.format = src[i].format();
if (d[i].d.format)
d[i].d.format->addRef();
}
d[i].x = 0;
d[i].lineStart = 0;
d[i].nobreak = FALSE;
d[i].type = QTextStringChar::Regular;
d[i].d.format = 0;
d[i].rightToLeft = 0;
d[i].c = '\n';
d += p->length();
}
bidiDirty = TRUE;
return paragCount;
}
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 ) {
QTextStringChar &ch = data[ i ];
#ifndef QT_NO_TEXTCUSTOMITEM
if ( !(ch.type == QTextStringChar::Regular) ) {
delete ch.customItem();
if ( ch.d.custom->format )
ch.d.custom->format->removeRef();
delete ch.d.custom;
ch.d.custom = 0;
} else
#endif
if ( ch.format() ) {
ch.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 ) {
QTextStringChar &ch = data[ i ];
#ifndef QT_NO_TEXTCUSTOMITEM
if ( !(ch.type == QTextStringChar::Regular) ) {
delete ch.customItem();
if ( ch.d.custom->format )
ch.d.custom->format->removeRef();
delete ch.d.custom;
ch.d.custom = 0;
} else
#endif
if ( ch.format() ) {
ch.format()->removeRef();
}
}
memmove( data.data() + index, data.data() + index + len,
sizeof( QTextStringChar ) * ( data.size() - index - len ) );
data.resize( data.size() - len, QGArray::SpeedOptim );
bidiDirty = TRUE;
}
void QTextString::clear()
{
for ( int i = 0; i < (int)data.count(); ++i ) {
QTextStringChar &ch = data[ i ];
#ifndef QT_NO_TEXTCUSTOMITEM
if ( !(ch.type == QTextStringChar::Regular) ) {
if ( ch.customItem() && ch.customItem()->placement() == QTextCustomItem::PlaceInline )
delete ch.customItem();
if ( ch.d.custom->format )
ch.d.custom->format->removeRef();
delete ch.d.custom;
ch.d.custom = 0;
} else
#endif
if ( ch.format() ) {
ch.format()->removeRef();
}
}
data.resize( 0 );
bidiDirty = TRUE;
}
void QTextString::setFormat( int index, QTextFormat *f, bool useCollection )
{
QTextStringChar &ch = data[ index ];
if ( useCollection && ch.format() )
ch.format()->removeRef();
ch.setFormat( f );
}
void QTextString::checkBidi() const
{
QTextString *that = (QTextString *)this;
that->bidiDirty = FALSE;
int length = data.size();
if ( !length ) {
that->bidi = FALSE;
that->rightToLeft = dir == QChar::DirR;
return;
}
const QTextStringChar *start = data.data();
const QTextStringChar *end = start + length;
((QTextString *)this)->stringCache = toString(data);
// determines the properties we need for layouting
QTextEngine textEngine( toString(), 0 );
textEngine.direction = (QChar::Direction) dir;
textEngine.itemize(QTextEngine::SingleLine);
const QCharAttributes *ca = textEngine.attributes() + length-1;
QTextStringChar *ch = (QTextStringChar *)end - 1;
QScriptItem *item = &textEngine.items[textEngine.items.size()-1];
unsigned char bidiLevel = item->analysis.bidiLevel;
if ( bidiLevel )
that->bidi = TRUE;
int pos = length-1;
while ( ch >= start ) {
if ( item->position > pos ) {
--item;
Q_ASSERT( item >= &textEngine.items[0] );
Q_ASSERT( item < &textEngine.items[textEngine.items.size()] );
bidiLevel = item->analysis.bidiLevel;
if ( bidiLevel )
that->bidi = TRUE;
}
ch->softBreak = ca->softBreak;
ch->whiteSpace = ca->whiteSpace;
ch->charStop = ca->charStop;
ch->wordStop = ca->wordStop;
ch->bidiLevel = bidiLevel;
ch->rightToLeft = (bidiLevel%2);
--ch;
--ca;
--pos;
}
if ( dir == QChar::DirR ) {
that->bidi = TRUE;
that->rightToLeft = TRUE;
} else if ( dir == QChar::DirL ) {
that->rightToLeft = FALSE;
} else {
that->rightToLeft = (textEngine.direction == QChar::DirR);
}
}
void QTextDocument::setStyleSheet( QStyleSheet *s )
{
if ( !s )
return;
sheet_ = s;
list_tm = list_bm = par_tm = par_bm = 12;
list_lm = 40;
li_tm = li_bm = 0;
QStyleSheetItem* item = s->item( "ol" );
if ( item ) {
list_tm = QMAX(0,item->margin( QStyleSheetItem::MarginTop ));
list_bm = QMAX(0,item->margin( QStyleSheetItem::MarginBottom ));
list_lm = QMAX(0,item->margin( QStyleSheetItem::MarginLeft ));
}
if ( (item = s->item( "li" ) ) ) {
li_tm = QMAX(0,item->margin( QStyleSheetItem::MarginTop ));
li_bm = QMAX(0,item->margin( QStyleSheetItem::MarginBottom ));
}
if ( (item = s->item( "p" ) ) ) {
par_tm = QMAX(0,item->margin( QStyleSheetItem::MarginTop ));
par_bm = QMAX(0,item->margin( QStyleSheetItem::MarginBottom ));
}
}
void QTextDocument::setUnderlineLinks( bool b ) {
underlLinks = b;
for ( QTextDocument *d = childList.first(); d; d = childList.next() )
d->setUnderlineLinks( b );
}
void QTextStringChar::setFormat( QTextFormat *f )
{
if ( type == Regular ) {
d.format = f;
} else {
#ifndef QT_NO_TEXTCUSTOMITEM
if ( !d.custom ) {
d.custom = new CustomData;
d.custom->custom = 0;
}
d.custom->format = f;
#endif
}
}
#ifndef QT_NO_TEXTCUSTOMITEM
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;
}
}
#endif
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;
#ifndef QT_NO_TEXTCUSTOMITEM
d.custom->custom = 0;
#endif
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->charStop || c->c.unicode() == 0xad || c->c.unicode() == 0x2028 )
return 0;
#ifndef QT_NO_TEXTCUSTOMITEM
if( c->isCustom() ) {
if( c->customItem()->placement() == QTextCustomItem::PlaceInline )
w = c->customItem()->width;
} else
#endif
{
int r = c->c.row();
if(r < 0x06
#ifndef Q_WS_WIN
// Uniscribe's handling of Asian makes the condition below fail.
|| (r > 0x1f && !(r > 0xd7 && r < 0xe0))
#endif
) {
w = c->format()->width( c->c );
} else {
w = c->format()->width(toString(), idx);
}
}
return w;
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
QTextParagraph::QTextParagraph( QTextDocument *d, QTextParagraph *pr, QTextParagraph *nx, bool updateIds )
: p( pr ), n( nx ), docOrPseudo( d ),
changed(FALSE), firstFormat(TRUE), firstPProcess(TRUE), needPreProcess(FALSE), fullWidth(TRUE),
lastInFrame(FALSE), visible(TRUE), breakable(TRUE), movedDown(FALSE),
mightHaveCustomItems(FALSE), hasdoc( d != 0 ), litem(FALSE), rtext(FALSE),
align( 0 ), lstyle( QStyleSheetItem::ListDisc ), invalid( 0 ), mSelections( 0 ),
#ifndef QT_NO_TEXTCUSTOMITEM
mFloatingItems( 0 ),
#endif
utm( 0 ), ubm( 0 ), ulm( 0 ), urm( 0 ), uflm( 0 ), ulinespacing( 0 ),
tabStopWidth(0), minwidth(0), tArray(0), eData( 0 ), ldepth( 0 )
{
lstyle = QStyleSheetItem::ListDisc;
if ( !hasdoc )
docOrPseudo = new QTextParagraphPseudoDocument;
bgcol = 0;
list_val = -1;
paintdevice = 0;
QTextFormat* defFormat = formatCollection()->defaultFormat();
if ( !hasdoc ) {
tabStopWidth = defFormat->width( 'x' ) * 8;
pseudoDocument()->commandHistory = new QTextCommandHistory( 100 );
}
if ( p )
p->n = this;
if ( n )
n->p = this;
if ( !p && hasdoc )
document()->setFirstParagraph( this );
if ( !n && hasdoc )
document()->setLastParagraph( this );
state = -1;
if ( p )
id = p->id + 1;
else
id = 0;
if ( n && updateIds ) {
QTextParagraph *s = n;
while ( s ) {
s->id = s->p->id + 1;
s->invalidateStyleCache();
s = s->n;
}
}
str = new QTextString();
QChar ch(' ');
str->insert( 0, &ch, 1, formatCollection()->defaultFormat() );
}
QTextParagraph::~QTextParagraph()
{
delete str;
if ( hasdoc ) {
QTextDocument *doc = document();
if ( this == doc->minwParag ) {
doc->minwParag = 0;
doc->minw = 0;
}
if ( this == doc->curParag )
doc->curParag = 0;
} else {
delete pseudoDocument();
}
delete [] tArray;
delete eData;
QMap<int, QTextLineStart*>::Iterator it = lineStarts.begin();
for ( ; it != lineStarts.end(); ++it )
delete *it;
if ( mSelections )
delete mSelections;
#ifndef QT_NO_TEXTCUSTOMITEM
if ( mFloatingItems )
delete mFloatingItems;
#endif
if ( p )
p->setNext( n );
if ( n )
n->setPrev( p );
delete bgcol;
}
void QTextParagraph::setNext( QTextParagraph *s )
{
n = s;
if ( !n && hasdoc )
document()->setLastParagraph( this );
}
void QTextParagraph::setPrev( QTextParagraph *s )
{
p = s;
if ( !p && hasdoc )
document()->setFirstParagraph( this );
}
void QTextParagraph::invalidate( int chr )
{
if ( invalid < 0 )
invalid = chr;
else
invalid = QMIN( invalid, chr );
#ifndef QT_NO_TEXTCUSTOMITEM
if ( mFloatingItems ) {
for ( QTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() )
i->ypos = -1;
}
#endif
invalidateStyleCache();
}
void QTextParagraph::invalidateStyleCache()
{
if ( list_val < 0 )
list_val = -1;
}
void QTextParagraph::insert( int index, const QString &s )
{
insert( index, s.unicode(), s.length() );
}
void QTextParagraph::insert( int index, const QChar *unicode, int len )
{
if ( hasdoc && !document()->useFormatCollection() && document()->preProcessor() )
str->insert( index, unicode, len,
document()->preProcessor()->format( QTextPreProcessor::Standard ) );
else
str->insert( index, unicode, len, formatCollection()->defaultFormat() );
invalidate( index );
needPreProcess = TRUE;
}
void QTextParagraph::truncate( int index )
{
str->truncate( index );
insert( length(), " " );
needPreProcess = TRUE;
}
void QTextParagraph::remove( int index, int len )
{
if ( index + len - str->length() > 0 )
return;
#ifndef QT_NO_TEXTCUSTOMITEM
for ( int i = index; i < index + len; ++i ) {
QTextStringChar *c = at( i );
if ( hasdoc && c->isCustom() ) {
document()->unregisterCustomItem( c->customItem(), this );
}
}
#endif
str->remove( index, len );
invalidate( 0 );
needPreProcess = TRUE;
}
void QTextParagraph::join( QTextParagraph *s )
{
int oh = r.height() + s->r.height();
n = s->n;
if ( n )
n->p = this;
else if ( hasdoc )
document()->setLastParagraph( 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 );
}
#ifndef QT_NO_TEXTCUSTOMITEM
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 ( hasdoc ) {
document()->unregisterCustomItem( item, s );
document()->registerCustomItem( item, this );
}
}
if ( s->str->at( i ).isAnchor() ) {
str->at( i + start ).setAnchor( s->str->at( i ).anchorName(),
s->str->at( i ).anchorHref() );
}
#endif
}
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 ) {
QTextParagraph *s = n;
s->invalidate( 0 );
while ( s ) {
s->id = s->p->id + 1;
s->state = -1;
s->needPreProcess = TRUE;
s->changed = TRUE;
s->invalidateStyleCache();
s = s->n;
}
}
format();
state = -1;
}
void QTextParagraph::move( int &dy )
{
if ( dy == 0 )
return;
changed = TRUE;
r.moveBy( 0, dy );
#ifndef QT_NO_TEXTCUSTOMITEM
if ( mFloatingItems ) {
for ( QTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() )
i->ypos += dy;
}
#endif
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 QTextParagraph::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() ) );
if ( p )
p->lastInFrame = FALSE;
movedDown = FALSE;
bool formattedAgain = FALSE;
formatAgain:
r.setWidth( documentWidth() );
#ifndef QT_NO_TEXTCUSTOMITEM
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;
}
}
}
#endif
QMap<int, QTextLineStart*> oldLineStarts = lineStarts;
lineStarts.clear();
int y = formatter()->format( document(), this, start, oldLineStarts );
r.setWidth( QMAX( r.width(), formatter()->minimumWidth() ) );
QMap<int, QTextLineStart*>::Iterator it = oldLineStarts.begin();
for ( ; it != oldLineStarts.end(); ++it )
delete *it;
if ( !hasdoc ) { // qt_format_text bounding rect handling
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 = minwidth = 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();
QTextParagraph *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 QTextParagraph::lineHeightOfChar( int i, int *bl, int *y ) const
{
if ( !isValid() )
( (QTextParagraph*)this )->format();
QMap<int, QTextLineStart*>::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( "QTextParagraph::lineHeightOfChar: couldn't find lh for %d", i );
return 15;
}
QTextStringChar *QTextParagraph::lineStartOfChar( int i, int *index, int *line ) const
{
if ( !isValid() )
( (QTextParagraph*)this )->format();
int l = (int)lineStarts.count() - 1;
QMap<int, QTextLineStart*>::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( "QTextParagraph::lineStartOfChar: couldn't find %d", i );
return 0;
}
int QTextParagraph::lines() const
{
if ( !isValid() )
( (QTextParagraph*)this )->format();
return (int)lineStarts.count();
}
QTextStringChar *QTextParagraph::lineStartOfLine( int line, int *index ) const
{
if ( !isValid() )
( (QTextParagraph*)this )->format();
if ( line >= 0 && line < (int)lineStarts.count() ) {
QMap<int, QTextLineStart*>::ConstIterator it = lineStarts.begin();
while ( line-- > 0 )
++it;
int i = it.key();
if ( index )
*index = i;
return &str->at( i );
}
qWarning( "QTextParagraph::lineStartOfLine: couldn't find %d", line );
return 0;
}
int QTextParagraph::leftGap() const
{
if ( !isValid() )
( (QTextParagraph*)this )->format();
if ( str->length() == 0)
return 0;
int line = 0;
int x = str->length() ? str->at(0).x : 0; /* set x to x of first char */
if ( str->isBidi() ) {
for ( int i = 1; i < str->length()-1; ++i )
x = QMIN(x, str->at(i).x);
return x;
}
QMap<int, QTextLineStart*>::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 QTextParagraph::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 && ( !of || 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 QTextParagraph::indent( int *oldIndent, int *newIndent )
{
if ( !hasdoc || !document()->indent() || isListItem() ) {
if ( oldIndent )
*oldIndent = 0;
if ( newIndent )
*newIndent = 0;
if ( oldIndent && newIndent )
*newIndent = *oldIndent;
return;
}
document()->indent()->indent( document(), this, oldIndent, newIndent );
}
void QTextParagraph::paint( QPainter &painter, const QColorGroup &cg, QTextCursor *cursor, bool drawSelections,
int clipx, int clipy, int clipw, int cliph )
{
if ( !visible )
return;
int i, y, h, baseLine, xstart, xend = 0;
i = y =h = baseLine = 0;
QRect cursorRect;
drawSelections &= ( mSelections != 0 );
// macintosh full-width selection style
bool fullWidthStyle = QApplication::style().styleHint(QStyle::SH_RichText_FullWidthSelection);
int fullSelectionWidth = 0;
if ( drawSelections && fullWidthStyle )
fullSelectionWidth = (hasdoc ? document()->width() : r.width());
QString qstr = str->toString();
// detach string
qstr.setLength(qstr.length());
// ### 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] == '\t' )
uc[(int)ii] = 0x20;
int line = -1;
int paintStart = 0;
QTextStringChar *chr = 0;
QTextStringChar *nextchr = at( 0 );
for ( i = 0; i < length(); i++ ) {
chr = nextchr;
if ( i < length()-1 )
nextchr = at( i+1 );
// we flush at end of document
bool flush = (i == length()-1);
bool ignoreSoftHyphen = FALSE;
if ( !flush ) {
// we flush at end of line
flush |= nextchr->lineStart;
// we flush on format changes
flush |= ( nextchr->format() != chr->format() );
// we flush on link changes
flush |= ( nextchr->isLink() != chr->isLink() );
// we flush on start of run
flush |= ( nextchr->bidiLevel != chr->bidiLevel );
// we flush on bidi changes
flush |= ( nextchr->rightToLeft != chr->rightToLeft );
// we flush before and after tabs
flush |= ( chr->c == '\t' || nextchr->c == '\t' );
// we flush on soft hypens
if (chr->c.unicode() == 0xad) {
flush = TRUE;
if (!nextchr->lineStart)
ignoreSoftHyphen = TRUE;
}
// we flush on custom items
flush |= chr->isCustom();
// we flush before custom items
flush |= nextchr->isCustom();
// when painting justified, we flush on spaces
if ((alignment() & Qt::AlignJustify) == Qt::AlignJustify )
flush |= chr->whiteSpace;
}
// init a new line
if ( chr->lineStart ) {
++line;
paintStart = i;
lineInfo( line, y, h, baseLine );
if ( clipy != -1 && cliph != 0 && y + r.y() - h > clipy + cliph ) { // outside clip area, leave
break;
}
// if this is the first line and we are a list item, draw the the bullet label
if ( line == 0 && isListItem() ) {
int x = chr->x;
if (str->isBidi()) {
if (str->isRightToLeft()) {
x = chr->x + str->width(0);
for (int k = 1; k < length(); ++k) {
if (str->at(k).lineStart)
break;
x = QMAX(x, str->at(k).x + str->width(k));
}
} else {
x = chr->x;
for (int k = 1; k < length(); ++k) {
if (str->at(k).lineStart)
break;
x = QMIN(x, str->at(k).x);
}
}
}
drawLabel( &painter, x, y, 0, 0, baseLine, cg );
}
}
// check for cursor mark
if ( cursor && this == cursor->paragraph() && i == cursor->index() ) {
QTextStringChar *c = i == 0 ? chr : chr - 1;
cursorRect.setRect( cursor->x() , y + baseLine - c->format()->ascent(),
1, c->format()->height() );
}
if ( flush ) { // something changed, draw what we have so far
if ( chr->rightToLeft ) {
xstart = chr->x;
xend = at( paintStart )->x + str->width( paintStart );
} else {
xstart = at( paintStart )->x;
xend = chr->x;
if ( i < length() - 1 ) {
if ( !str->at( i + 1 ).lineStart &&
str->at( i + 1 ).rightToLeft == chr->rightToLeft )
xend = str->at( i + 1 ).x;
else
xend += str->width( i );
}
}
if ( (clipx == -1 || clipw <= 0 || (xend >= clipx && xstart <= clipx + clipw)) &&
( clipy == -1 || clipy < y+r.y()+h ) ) {
if ( !chr->isCustom() )
drawString( painter, qstr, paintStart, i - paintStart + (ignoreSoftHyphen ? 0 : 1), xstart, y,
baseLine, xend-xstart, h, drawSelections, fullSelectionWidth,
chr, cg, chr->rightToLeft );
#ifndef QT_NO_TEXTCUSTOMITEM
else if ( chr->customItem()->placement() == QTextCustomItem::PlaceInline ) {
bool inSelection = FALSE;
if (drawSelections) {
QMap<int, QTextParagraphSelection>::ConstIterator it = mSelections->find( QTextDocument::Standard );
inSelection = (it != mSelections->end() && (*it).start <= i && (*it).end > i);
}
chr->customItem()->draw( &painter, chr->x, y,
clipx == -1 ? clipx : (clipx - r.x()),
clipy == -1 ? clipy : (clipy - r.y()),
clipw, cliph, cg, inSelection );
}
#endif
}
paintStart = i+1;
}
}
// time to draw the cursor
const int cursor_extent = 4;
if ( !cursorRect.isNull() && cursor &&
((clipx == -1 || clipw == -1) || (cursorRect.right()+cursor_extent >= clipx && cursorRect.left()-cursor_extent <= clipx + clipw)) ) {
painter.fillRect( cursorRect, cg.color( QColorGroup::Text ) );
painter.save();
if ( string()->isBidi() ) {
if ( at( cursor->index() )->rightToLeft ) {
painter.setPen( Qt::black );
painter.drawLine( cursorRect.x(), cursorRect.y(), cursorRect.x() - cursor_extent / 2, cursorRect.y() + cursor_extent / 2 );
painter.drawLine( cursorRect.x(), cursorRect.y() + cursor_extent, cursorRect.x() - cursor_extent / 2, cursorRect.y() + cursor_extent / 2 );
} else {
painter.setPen( Qt::black );
painter.drawLine( cursorRect.x(), cursorRect.y(), cursorRect.x() + cursor_extent / 2, cursorRect.y() + cursor_extent / 2 );
painter.drawLine( cursorRect.x(), cursorRect.y() + cursor_extent, cursorRect.x() + cursor_extent / 2, cursorRect.y() + cursor_extent / 2 );
}
}
painter.restore();
}
}
//#define BIDI_DEBUG
void QTextParagraph::setColorForSelection( QColor &color, QPainter &painter,
const QColorGroup& cg, int selection )
{
if (selection < 0)
return;
color = ( hasdoc && selection != QTextDocument::Standard ) ?
document()->selectionColor( selection ) :
cg.color( QColorGroup::Highlight );
if ( selection == QTextDocument::IMCompositionText ) {
#ifndef Q_WS_MACX
int h1, s1, v1, h2, s2, v2;
cg.color( QColorGroup::Base ).hsv( &h1, &s1, &v1 );
cg.color( QColorGroup::Background ).hsv( &h2, &s2, &v2 );
color.setHsv( h1, s1, ( v1 + v2 ) / 2 );
#else
color = Qt::lightGray;
#endif
painter.setPen( cg.color( QColorGroup::Text ) );
} else if ( selection == QTextDocument::IMSelectionText ) {
color = cg.color( QColorGroup::Dark );
painter.setPen( cg.color( QColorGroup::BrightText ) );
} else if ( !hasdoc || document()->invertSelectionText( selection ) ) {
painter.setPen( cg.color( QColorGroup::HighlightedText ) );
}
}
void QTextParagraph::drawString( QPainter &painter, const QString &str, int start, int len, int xstart,
int y, int baseLine, int w, int h, bool drawSelections, int fullSelectionWidth,
QTextStringChar *formatChar, const QColorGroup& cg,
bool rightToLeft )
{
bool plainText = hasdoc ? document()->textFormat() == Qt::PlainText : FALSE;
QTextFormat* format = formatChar->format();
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() )
painter.setPen(document()->linkColor.isValid() ? document()->linkColor : cg.link());
if ( document()->underlineLinks() ) {
QFont fn = format->font();
fn.setUnderline( TRUE );
painter.setFont( fn );
}
}
QPainter::TextDirection dir = rightToLeft ? QPainter::RTL : QPainter::LTR;
int real_length = len;
if (len && dir != QPainter::RTL && start + len == length() ) // don't draw the last character (trailing space)
len--;
if (len && str.unicode()[start+len-1] == QChar_linesep)
len--;
QTextFormat::VerticalAlignment vAlign = format->vAlign();
if ( vAlign != QTextFormat::AlignNormal ) {
// sub or superscript
QFont f( painter.font() );
if ( format->fontSizesInPixels() )
f.setPixelSize( ( f.pixelSize() * 2 ) / 3 );
else
f.setPointSize( ( f.pointSize() * 2 ) / 3 );
painter.setFont( f );
int h = painter.fontMetrics().height();
baseLine += (vAlign == QTextFormat::AlignSubScript) ? h/6 : -h/2;
}
bool allSelected = FALSE;
if (drawSelections) {
QMap<int, QTextParagraphSelection>::ConstIterator it = mSelections->find( QTextDocument::Standard );
allSelected = (it != mSelections->end() && (*it).start <= start && (*it).end >= start+len);
}
if (!allSelected)
painter.drawText(xstart, y + baseLine, str, start, len, dir);
#ifdef BIDI_DEBUG
painter.save();
painter.setPen ( Qt::red );
painter.drawLine( xstart, y, xstart, y + baseLine );
painter.drawLine( xstart, y + baseLine/2, xstart + 10, y + baseLine/2 );
int w = 0;
int i = 0;
while( i < len )
w += painter.fontMetrics().charWidth( str, start + i++ );
painter.setPen ( Qt::blue );
painter.drawLine( xstart + w - 1, y, xstart + w - 1, y + baseLine );
painter.drawLine( xstart + w - 1, y + baseLine/2, xstart + w - 1 - 10, y + baseLine/2 );
painter.restore();
#endif
// check if we are in a selection and draw it
if (drawSelections) {
QMap<int, QTextParagraphSelection>::ConstIterator it = mSelections->end();
while ( it != mSelections->begin() ) {
--it;
int selStart = (*it).start;
int selEnd = (*it).end;
int tmpw = w;
selStart = QMAX(selStart, start);
int real_selEnd = QMIN(selEnd, start+real_length);
selEnd = QMIN(selEnd, start+len);
bool extendRight = FALSE;
bool extendLeft = FALSE;
bool selWrap = (real_selEnd == length()-1 && n && n->hasSelection(it.key()));
if (selWrap || this->str->at(real_selEnd).lineStart) {
extendRight = (fullSelectionWidth != 0);
if (!extendRight && !rightToLeft)
tmpw += painter.fontMetrics().width(' ');
}
if (fullSelectionWidth && (selStart == 0 || this->str->at(selStart).lineStart)) {
extendLeft = TRUE;
}
if (this->str->isRightToLeft() != rightToLeft)
extendLeft = extendRight = FALSE;
if (this->str->isRightToLeft()) {
bool tmp = extendLeft;
extendLeft = extendRight;
extendRight = tmp;
}
if (selStart < real_selEnd ||
(selWrap && fullSelectionWidth && extendRight &&
// don't draw the standard selection on a printer=
(it.key() != QTextDocument::Standard || !is_printer( &painter)))) {
int selection = it.key();
QColor color;
setColorForSelection( color, painter, cg, selection );
if (selStart != start || selEnd != start + len || selWrap) {
// have to clip
painter.save();
int cs, ce;
if (rightToLeft) {
cs = (selEnd != start + len) ?
this->str->at(this->str->previousCursorPosition(selEnd)).x : xstart;
ce = (selStart != start) ?
this->str->at(this->str->previousCursorPosition(selStart)).x : xstart+tmpw;
} else {
cs = (selStart != start) ? this->str->at(selStart).x : xstart;
ce = (selEnd != start + len) ? this->str->at(selEnd).x : xstart+tmpw;
}
QRect r(cs, y, ce-cs, h);
if (extendLeft)
r.setLeft(0);
if (extendRight)
r.setRight(fullSelectionWidth);
QRegion reg(r);
if ( painter.hasClipping() )
reg &= painter.clipRegion(QPainter::CoordPainter);
painter.setClipRegion(reg, QPainter::CoordPainter);
}
int xleft = xstart;
if ( extendLeft ) {
tmpw += xstart;
xleft = 0;
}
if ( extendRight )
tmpw = fullSelectionWidth - xleft;
painter.fillRect( xleft, y, tmpw, h, color );
painter.drawText( xstart, y + baseLine, str, start, len, dir );
// draw preedit's underline
if (selection == QTextDocument::IMCompositionText)
painter.drawLine(xstart, y + baseLine + 1, xstart + w, y + baseLine + 1);
if (selStart != start || selEnd != start + len || selWrap)
painter.restore();
}
}
}
if ( format->isMisspelled() ) {
painter.save();
painter.setPen( QPen( Qt::red, 1, Qt::DotLine ) );
painter.drawLine( xstart, y + baseLine + 1, xstart + w, y + baseLine + 1 );
painter.restore();
}
if ( hasdoc && formatChar->isAnchor() && !formatChar->anchorHref().isEmpty() &&
document()->focusIndicator.parag == this &&
( ( document()->focusIndicator.start >= start &&
document()->focusIndicator.start + document()->focusIndicator.len <= start + len ) ||
( document()->focusIndicator.start <= start &&
document()->focusIndicator.start + document()->focusIndicator.len >= start + len ) ) )
painter.drawWinFocusRect( QRect( xstart, y, w, h ) );
}
void QTextParagraph::drawLabel( QPainter* p, int x, int y, int w, int h, int base, const QColorGroup& cg )
{
QRect r ( x, y, w, h );
QStyleSheetItem::ListStyle s = listStyle();
p->save();
QTextFormat *format = at( 0 )->format();
if ( format ) {
p->setPen( format->color() );
p->setFont( format->font() );
}
QFontMetrics fm( p->fontMetrics() );
int size = fm.lineSpacing() / 3;
bool rtl = str->isRightToLeft();
switch ( s ) {
case QStyleSheetItem::ListDecimal:
case QStyleSheetItem::ListLowerAlpha:
case QStyleSheetItem::ListUpperAlpha:
{
if ( list_val == -1 ) { // uninitialised list value, calcluate the right one
int depth = listDepth();
list_val--;
// ### evil, square and expensive. This needs to be done when formatting, not when painting
QTextParagraph* s = prev();
int depth_s;
while ( s && (depth_s = s->listDepth()) >= depth ) {
if ( depth_s == depth && s->isListItem() )
list_val--;
s = s->prev();
}
}
int n = list_val;
if ( n < -1 )
n = -n - 1;
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;
}
if (rtl)
l.prepend(" .");
else
l += QString::fromLatin1(". ");
int x = ( rtl ? r.left() : r.right() - fm.width(l));
p->drawText( x, r.top() + base, l );
}
break;
case QStyleSheetItem::ListSquare:
{
int x = rtl ? r.left() + size : r.right() - size*2;
QRect er( x, r.top() + fm.height() / 2 - size / 2, size, size );
p->fillRect( er , cg.brush( QColorGroup::Text ) );
}
break;
case QStyleSheetItem::ListCircle:
{
int x = rtl ? r.left() + size : r.right() - size*2;
QRect er( x, r.top() + fm.height() / 2 - size / 2, size, size);
p->drawEllipse( er );
}
break;
case QStyleSheetItem::ListDisc:
default:
{
p->setBrush( cg.brush( QColorGroup::Text ));
int x = rtl ? r.left() + size : r.right() - size*2;
QRect er( x, r.top() + fm.height() / 2 - size / 2, size, size);
p->drawEllipse( er );
p->setBrush( Qt::NoBrush );
}
break;
}
p->restore();
}
#ifndef QT_NO_DATASTREAM
void QTextParagraph::readStyleInformation( QDataStream& stream )
{
int int_align, int_lstyle;
uchar uchar_litem, uchar_rtext, uchar_dir;
stream >> int_align >> int_lstyle >> utm >> ubm >> ulm >> urm >> uflm
>> ulinespacing >> ldepth >> uchar_litem >> uchar_rtext >> uchar_dir;
align = int_align; lstyle = (QStyleSheetItem::ListStyle) int_lstyle;
litem = uchar_litem; rtext = uchar_rtext; str->setDirection( (QChar::Direction)uchar_dir );
QTextParagraph* s = prev() ? prev() : this;
while ( s ) {
s->invalidate( 0 );
s = s->next();
}
}
void QTextParagraph::writeStyleInformation( QDataStream& stream ) const
{
stream << (int) align << (int) lstyle << utm << ubm << ulm << urm << uflm << ulinespacing << ldepth << (uchar)litem << (uchar)rtext << (uchar)str->direction();
}
#endif
void QTextParagraph::setListItem( bool li )
{
if ( (bool)litem == li )
return;
litem = li;
changed = TRUE;
QTextParagraph* s = prev() ? prev() : this;
while ( s ) {
s->invalidate( 0 );
s = s->next();
}
}
void QTextParagraph::setListDepth( int depth ) {
if ( !hasdoc || depth == ldepth )
return;
ldepth = depth;
QTextParagraph* s = prev() ? prev() : this;
while ( s ) {
s->invalidate( 0 );
s = s->next();
}
}
int *QTextParagraph::tabArray() const
{
int *ta = tArray;
if ( !ta && hasdoc )
ta = document()->tabArray();
return ta;
}
int QTextParagraph::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 QTextParagraph::adjustToPainter( QPainter *p )
{
#ifndef QT_NO_TEXTCUSTOMITEM
for ( int i = 0; i < length(); ++i ) {
if ( at( i )->isCustom() )
at( i )->customItem()->adjustToPainter( p );
}
#endif
}
QTextFormatCollection *QTextParagraph::formatCollection() const
{
if ( hasdoc )
return document()->formatCollection();
QTextFormatCollection* fc = &pseudoDocument()->collection;
if ( paintdevice != fc->paintDevice() )
fc->setPaintDevice( paintdevice );
return fc;
}
QString QTextParagraph::richText() const
{
QString s;
QTextStringChar *formatChar = 0;
QString spaces;
bool doStart = richTextExportStart && richTextExportStart->paragraph() == this;
bool doEnd = richTextExportEnd && richTextExportEnd->paragraph() == this;
int i;
QString lastAnchorName;
for ( i = 0; i < length()-1; ++i ) {
if ( doStart && i && richTextExportStart->index() == i )
s += "<!--StartFragment-->";
if ( doEnd && richTextExportEnd->index() == i )
s += "<!--EndFragment-->";
QTextStringChar *c = &str->at( i );
if ( c->isAnchor() && !c->anchorName().isEmpty() && c->anchorName() != lastAnchorName ) {
lastAnchorName = c->anchorName();
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( formatCollection()->defaultFormat(),
0, QString::null, c->anchorHref() );
formatChar = c;
} else if ( ( formatChar->format()->key() != c->format()->key() ) ||
(c->anchorHref() != formatChar->anchorHref() ) ) {
s += c->format()->makeFormatChangeTags( formatCollection()->defaultFormat(),
formatChar->format() , formatChar->anchorHref(), c->anchorHref() );
formatChar = c;
}
if ( c->c == '<' )
s += "&lt;";
else if ( c->c == '>' )
s += "&gt;";
else if ( c->c =='&' )
s += "&amp;";
else if ( c->c =='\"' )
s += "&quot;";
#ifndef QT_NO_TEXTCUSTOMITEM
else if ( c->isCustom() )
s += c->customItem()->richText();
#endif
else if ( c->c == '\n' || c->c == QChar_linesep )
s += "<br />"; // space on purpose for compatibility with Netscape, Lynx & Co.
else
s += c->c;
}
if ( doEnd && richTextExportEnd->index() == i )
s += "<!--EndFragment-->";
if ( formatChar )
s += formatChar->format()->makeFormatEndTags( formatCollection()->defaultFormat(), formatChar->anchorHref() );
return s;
}
void QTextParagraph::addCommand( QTextCommand *cmd )
{
if ( !hasdoc )
pseudoDocument()->commandHistory->addCommand( cmd );
else
document()->commands()->addCommand( cmd );
}
QTextCursor *QTextParagraph::undo( QTextCursor *c )
{
if ( !hasdoc )
return pseudoDocument()->commandHistory->undo( c );
return document()->commands()->undo( c );
}
QTextCursor *QTextParagraph::redo( QTextCursor *c )
{
if ( !hasdoc )
return pseudoDocument()->commandHistory->redo( c );
return document()->commands()->redo( c );
}
int QTextParagraph::topMargin() const
{
int m = 0;
if ( rtext ) {
m = isListItem() ? (document()->li_tm/QMAX(1,listDepth()*listDepth())) :
( listDepth() ? 0 : document()->par_tm );
if ( listDepth() == 1 &&( !prev() || prev()->listDepth() < listDepth() ) )
m = QMAX( m, document()->list_tm );
}
m += utm;
return scale( m, QTextFormat::painter() );
}
int QTextParagraph::bottomMargin() const
{
int m = 0;
if ( rtext ) {
m = isListItem() ? (document()->li_bm/QMAX(1,listDepth()*listDepth())) :
( listDepth() ? 0 : document()->par_bm );
if ( listDepth() == 1 &&( !next() || next()->listDepth() < listDepth() ) )
m = QMAX( m, document()->list_bm );
}
m += ubm;
return scale( m, QTextFormat::painter() );
}
int QTextParagraph::leftMargin() const
{
int m = ulm;
if ( listDepth() && !string()->isRightToLeft() )
m += listDepth() * document()->list_lm;
return scale( m, QTextFormat::painter() );
}
int QTextParagraph::firstLineMargin() const
{
int m = uflm;
return scale( m, QTextFormat::painter() );
}
int QTextParagraph::rightMargin() const
{
int m = urm;
if ( listDepth() && string()->isRightToLeft() )
m += listDepth() * document()->list_lm;
return scale( m, QTextFormat::painter() );
}
int QTextParagraph::lineSpacing() const
{
int l = ulinespacing;
l = scale( l, QTextFormat::painter() );
return l;
}
void QTextParagraph::copyParagData( QTextParagraph *parag )
{
rtext = parag->rtext;
lstyle = parag->lstyle;
ldepth = parag->ldepth;
litem = parag->litem;
align = parag->align;
utm = parag->utm;
ubm = parag->ubm;
urm = parag->urm;
ulm = parag->ulm;
uflm = parag->uflm;
ulinespacing = parag->ulinespacing;
QColor *c = parag->backgroundColor();
if ( c )
setBackgroundColor( *c );
str->setDirection( parag->str->direction() );
}
void QTextParagraph::show()
{
if ( visible || !hasdoc )
return;
visible = TRUE;
}
void QTextParagraph::hide()
{
if ( !visible || !hasdoc )
return;
visible = FALSE;
}
void QTextParagraph::setDirection( QChar::Direction d )
{
if ( str && str->direction() != d ) {
str->setDirection( d );
invalidate( 0 );
}
}
QChar::Direction QTextParagraph::direction() const
{
return (str ? str->direction() : QChar::DirON );
}
void QTextParagraph::setChanged( bool b, bool recursive )
{
changed = b;
if ( recursive ) {
if ( document() && document()->parentParagraph() )
document()->parentParagraph()->setChanged( b, recursive );
}
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
QTextPreProcessor::QTextPreProcessor()
{
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
QTextFormatter::QTextFormatter()
: thisminw(0), thiswused(0), wrapEnabled( TRUE ), wrapColumn( -1 ), biw( FALSE )
{
}
QTextLineStart *QTextFormatter::formatLine( QTextParagraph *parag, QTextString *string, QTextLineStart *line,
QTextStringChar *startChar, QTextStringChar *lastChar, int align, int space )
{
if ( lastChar < startChar )
return new QTextLineStart;
#ifndef QT_NO_COMPLEXTEXT
if( string->isBidi() )
return bidiReorderLine( parag, string, line, startChar, lastChar, align, space );
#endif
int start = (startChar - &string->at(0));
int last = (lastChar - &string->at(0) );
// ignore white space at the end of the line.
QTextStringChar *ch = lastChar;
while ( ch > startChar && ch->whiteSpace ) {
space += ch->format()->width( ' ' );
--ch;
}
if (space < 0)
space = 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 & Qt::AlignJustify ) {
int numSpaces = 0;
// End at "last-1", the last space ends up with a width of 0
for ( int j = last-1; j >= start; --j ) {
// Start at last tab, if any.
QTextStringChar &ch = string->at( j );
if ( ch.c == '\t' ) {
start = j+1;
break;
}
if(ch.whiteSpace)
numSpaces++;
}
int toAdd = 0;
for ( int k = start + 1; k <= last; ++k ) {
QTextStringChar &ch = string->at( k );
if( numSpaces && ch.whiteSpace ) {
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 QTextLineStart;
}
#ifndef QT_NO_COMPLEXTEXT
#ifdef BIDI_DEBUG
#include <iostream>
#endif
// collects one line of the paragraph and transforms it to visual order
QTextLineStart *QTextFormatter::bidiReorderLine( QTextParagraph * /*parag*/, QTextString *text, QTextLineStart *line,
QTextStringChar *startChar, QTextStringChar *lastChar, int align, int space )
{
// ignore white space at the end of the line.
int endSpaces = 0;
while ( lastChar > startChar && lastChar->whiteSpace ) {
space += lastChar->format()->width( ' ' );
--lastChar;
++endSpaces;
}
int start = (startChar - &text->at(0));
int last = (lastChar - &text->at(0) );
int length = lastChar - startChar + 1;
int x = startChar->x;
unsigned char _levels[256];
int _visual[256];
unsigned char *levels = _levels;
int *visual = _visual;
if ( length > 255 ) {
levels = (unsigned char *)malloc( length*sizeof( unsigned char ) );
visual = (int *)malloc( length*sizeof( int ) );
}
//qDebug("bidiReorderLine: length=%d (%d-%d)", length, start, last );
QTextStringChar *ch = startChar;
unsigned char *l = levels;
while ( ch <= lastChar ) {
//qDebug( " level: %d", ch->bidiLevel );
*(l++) = (ch++)->bidiLevel;
}
QTextEngine::bidiReorder( length, levels, visual );
// now construct the reordered string out of the runs...
int numSpaces = 0;
// set the correct alignment. This is a bit messy....
if( align == Qt::AlignAuto ) {
// align according to directionality of the paragraph...
if ( text->isRightToLeft() )
align = Qt::AlignRight;
}
// This is not really correct, but as we can't make the scrollbar move to the left of the origin,
// this ensures all text can be scrolled to and read.
if (space < 0)
space = 0;
if ( align & Qt::AlignHCenter )
x += space/2;
else if ( align & Qt::AlignRight )
x += space;
else if ( align & Qt::AlignJustify ) {
// End at "last-1", the last space ends up with a width of 0
for ( int j = last-1; j >= start; --j ) {
// Start at last tab, if any.
QTextStringChar &ch = text->at( j );
if ( ch.c == '\t' ) {
start = j+1;
break;
}
if(ch.whiteSpace)
numSpaces++;
}
}
int toAdd = 0;
int xorig = x;
QTextStringChar *lc = startChar + visual[0];
for ( int i = 0; i < length; i++ ) {
QTextStringChar *ch = startChar + visual[i];
if (numSpaces && ch->whiteSpace) {
int s = space / numSpaces;
toAdd += s;
space -= s;
numSpaces--;
}
if (lc->format() != ch->format() && !ch->c.isSpace()
&& lc->format()->font().italic() && !ch->format()->font().italic()) {
int rb = lc->format()->fontMetrics().rightBearing(lc->c);
if (rb < 0)
x -= rb;
}
ch->x = x + toAdd;
ch->rightToLeft = ch->bidiLevel % 2;
//qDebug("visual: %d (%x) placed at %d rightToLeft=%d", visual[i], ch->c.unicode(), x +toAdd, ch->rightToLeft );
int ww = 0;
if ( ch->c.unicode() >= 32 || ch->c == '\t' || ch->c == '\n' || ch->isCustom() ) {
ww = text->width( start+visual[i] );
} else {
ww = ch->format()->width( ' ' );
}
x += ww;
lc = ch;
}
x += toAdd;
while ( endSpaces-- ) {
++lastChar;
int sw = lastChar->format()->width( ' ' );
if ( text->isRightToLeft() ) {
xorig -= sw;
lastChar->x = xorig;
ch->rightToLeft = TRUE;
} else {
lastChar->x = x;
x += sw;
ch->rightToLeft = FALSE;
}
}
line->w = x;
if ( length > 255 ) {
free( levels );
free( visual );
}
return new QTextLineStart;
}
#endif
void QTextFormatter::insertLineStart( QTextParagraph *parag, int index, QTextLineStart *ls )
{
QMap<int, QTextLineStart*>::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, QTextParagraph* parag )
{
int oldHeight = parag->rect().height();
QMap<int, QTextLineStart*>& lineStarts = parag->lineStartList();
QMap<int, QTextLineStart*>::Iterator it = lineStarts.begin();
int h = parag->prev() ? QMAX(parag->prev()->bottomMargin(),parag->topMargin() ) / 2: 0;
for ( ; it != lineStarts.end() ; ++it ) {
QTextLineStart * ls = it.data();
ls->y = h;
QTextStringChar *c = &parag->string()->at(it.key());
#ifndef QT_NO_TEXTCUSTOMITEM
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
#endif
{
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() )
m = 0;
else
m = QMAX(m, parag->next()->topMargin() ) / 2;
h += m;
parag->setHeight( h );
return h - oldHeight;
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
QTextFormatterBreakInWords::QTextFormatterBreakInWords()
{
}
#define SPACE(s) s
int QTextFormatterBreakInWords::format( QTextDocument *doc,QTextParagraph *parag,
int start, const QMap<int, QTextLineStart*> & )
{
// make sure bidi information is correct.
(void )parag->string()->isBidi();
QTextStringChar *c = 0;
QTextStringChar *firstChar = 0;
int left = doc ? parag->leftMargin() + doc->leftMargin() : 0;
int x = left + ( doc ? parag->firstLineMargin() : 0 );
int dw = parag->documentVisibleWidth() - ( doc ? doc->rightMargin() : 0 );
int y = parag->prev() ? QMAX(parag->prev()->bottomMargin(),parag->topMargin()) / 2: 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 = &parag->string()->at( 0 );
int i = start;
QTextLineStart *lineStart = new QTextLineStart( 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 = &parag->string()->at( i );
// ### 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( ' ' );
}
#ifndef QT_NO_TEXTCUSTOMITEM
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 QTextLineStart( y, h, h );
insertLineStart( parag, i, lineStart );
c->lineStart = 1;
firstChar = c;
x = 0xffffff;
continue;
}
#endif
if ( wrapEnabled &&
( ( wrapAtColumn() == -1 && x + ww > w ) ||
( wrapAtColumn() != -1 && col >= wrapAtColumn() ) ) ) {
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() )
m = 0;
else
m = QMAX(m, parag->next()->topMargin() ) / 2;
parag->setFullWidth( fullWidth );
y += h + m;
if ( doc )
minw += doc->rightMargin();
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, QTextParagraph *parag,
int start, const QMap<int, QTextLineStart*> & )
{
// make sure bidi information is correct.
(void )parag->string()->isBidi();
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 = parag->prev() ? QMAX(parag->prev()->bottomMargin(),parag->topMargin()) / 2: 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 linespacing = doc ? parag->lineSpacing() : 0;
bool wrapEnabled = isWrapEnabled( parag );
start = 0;
int i = start;
QTextLineStart *lineStart = new QTextLineStart( y, y, 0 );
insertLineStart( parag, 0, lineStart );
int lastBreak = -1;
int tmpBaseLine = 0, tmph = 0;
bool lastWasNonInlineCustom = FALSE;
int align = parag->alignment();
if ( align == Qt::AlignAuto && doc && doc->alignment() != Qt::AlignAuto )
align = doc->alignment();
align &= Qt::AlignHorizontal_Mask;
// ### 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 = &parag->string()->at(len - 1);
if (!c->isAnchor()) {
if (c->format())
c->format()->removeRef();
c->setFormat( string->at( len - 2 ).format() );
if (c->format())
c->format()->addRef();
}
}
c = &parag->string()->at( 0 );
QPainter *painter = QTextFormat::painter();
int col = 0;
int ww = 0;
QChar lastChr = c->c;
QTextFormat *lastFormat = c->format();
for ( ; i < len; ++i, ++col ) {
if ( i ) {
c = &parag->string()->at(i-1);
lastChr = c->c;
lastFormat = c->format();
}
bool lastWasOwnLineCustomItem = lastBreak == -2;
bool hadBreakableChar = lastBreak != -1;
bool lastWasHardBreak = lastChr == QChar_linesep;
// ### next line should not be needed
if ( painter )
c->format()->setPainter( painter );
c = &string->at( i );
if (lastFormat != c->format() && !c->c.isSpace()
&& lastFormat->font().italic() && !c->format()->font().italic()) {
int rb = lastFormat->fontMetrics().rightBearing(lastChr);
if (rb < 0)
x -= rb;
}
if ( ( i > 0 && (x > curLeft || ww == 0) ) || lastWasNonInlineCustom ) {
c->lineStart = 0;
} else {
c->lineStart = 1;
firstChar = c;
}
// ignore non spacing marks for column count.
if (col != 0 && ::category(c->c) == QChar::Mark_NonSpacing)
--col;
#ifndef QT_NO_TEXTCUSTOMITEM
lastWasNonInlineCustom = ( c->isCustom() && c->customItem()->placement() != QTextCustomItem::PlaceInline );
#endif
if ( c->c.unicode() >= 32 || c->isCustom() ) {
ww = string->width( i );
} else if ( c->c == '\t' ) {
if ( align == Qt::AlignRight || align == Qt::AlignCenter ) {
// we can not (yet) do tabs
ww = c->format()->width(' ' );
} else {
int tabx = lastWasHardBreak ? (left + ( doc ? parag->firstLineMargin() : 0 )) : x;
int nx = parag->nextTab( i, tabx - left ) + left;
if ( nx < tabx ) // strrrange...
ww = 0;
else
ww = nx - tabx;
}
} else {
ww = c->format()->width( ' ' );
}
#ifndef QT_NO_TEXTCUSTOMITEM
QTextCustomItem* ci = c->customItem();
if ( c->isCustom() && ci->ownLine() ) {
QTextLineStart *lineStart2 = formatLine( parag, string, lineStart, firstChar, c-1, align, SPACE(w - x - ww) );
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 );
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, QMAX( tmph, linespacing ) );
tmph = c->height();
h = tmph;
lineStart = lineStart2;
lineStart->y = y;
insertLineStart( parag, i, lineStart );
c->lineStart = 1;
firstChar = c;
} else {
tmph = c->height();
h = tmph;
delete lineStart2;
}
lineStart->h = h;
lineStart->baseLine = h;
tmpBaseLine = lineStart->baseLine;
lastBreak = -2;
x = w;
minw = QMAX( minw, tminw );
int tw = ci->minimumWidth() + ( doc ? doc->leftMargin() : 0 );
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 );
}
#endif
// we break if
// 1. the last character was a hard break (QChar_linesep) or
// 2. the last charater was a own-line custom item (eg. table or ruler) or
// 3. wrapping was enabled, it was not a space and following
// condition is true: We either had a breakable character
// previously or we ar allowed to break in words and - either
// we break at w pixels and the current char would exceed that
// or - we break at a column and the current character would
// exceed that.
if ( lastWasHardBreak || lastWasOwnLineCustomItem ||
( wrapEnabled &&
( (!c->c.isSpace() && (hadBreakableChar || allowBreakInWords()) &&
( (wrapAtColumn() == -1 && x + ww > w) ||
(wrapAtColumn() != -1 && col >= wrapAtColumn()) ) ) )
)
) {
if ( wrapAtColumn() != -1 )
minw = QMAX( minw, x + ww );
// if a break was forced (no breakable char, hard break or own line custom item), break immediately....
if ( !hadBreakableChar || lastWasHardBreak || lastWasOwnLineCustomItem ) {
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, SPACE(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 ( !doc && c->c == '\t' ) { // qt_format_text tab handling
int nx = parag->nextTab( i, x - left ) + left;
if ( nx < x )
ww = w - x;
else
ww = nx - x;
}
curLeft = x;
y += QMAX( h, linespacing );
tmph = c->height();
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;
if ( allowBreakInWords() || lastWasHardBreak ) {
minw = QMAX(minw, tminw);
tminw = marg + ww;
}
} else { // ... otherwise if we had a breakable char, break there
DO_FLOW( lineStart );
c->x = x;
i = lastBreak;
lineStart = formatLine( parag, string, lineStart, firstChar, parag->at( lastBreak ),align, SPACE(w - string->at( i+1 ).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 ( !doc && c->c == '\t' ) { // qt_format_text tab handling
int nx = parag->nextTab( i, x - left ) + left;
if ( nx < x )
ww = w - x;
else
ww = nx - x;
}
curLeft = x;
y += QMAX( h, linespacing );
tmph = c->height();
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;
minw = QMAX(minw, tminw);
tminw = marg;
continue;
}
} else if (lineStart && isBreakable(string, i)) {
if ( len <= 2 || i < len - 1 ) {
tmpBaseLine = QMAX( tmpBaseLine, c->ascent() );
tmph = QMAX( tmph, c->height() );
}
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 cascent = c->ascent();
int cheight = c->height();
int belowBaseLine = QMAX( tmph - tmpBaseLine, cheight-cascent );
tmpBaseLine = QMAX( tmpBaseLine, cascent );
tmph = tmpBaseLine + belowBaseLine;
}
c->x = x;
x += ww;
wused = QMAX( wused, x );
}
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 == Qt::AlignJustify )
align = Qt::AlignAuto;
DO_FLOW( lineStart );
lineStart = formatLine( parag, string, lineStart, firstChar, c, align, SPACE(w - x) );
delete lineStart;
}
minw = QMAX( minw, tminw );
if ( doc )
minw += doc->rightMargin();
int m = parag->bottomMargin();
if ( !parag->next() )
m = 0;
else
m = QMAX(m, parag->next()->topMargin() ) / 2;
parag->setFullWidth( fullWidth );
y += QMAX( h, linespacing ) + m;
wused += rm;
if ( !wrapEnabled || wrapAtColumn() != -1 )
minw = QMAX(minw, wused);
// This is the case where we are breaking wherever we darn well please
// in cases like that, the minw should not be the length of the entire
// word, because we necessarily want to show the word on the whole line.
// example: word wrap in iconview
if ( allowBreakInWords() && minw > wused )
minw = wused;
thisminw = minw;
thiswused = wused;
return y;
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
QTextIndent::QTextIndent()
{
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
QTextFormatCollection::QTextFormatCollection()
: cKey( 307 ), paintdevice( 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;
}
void QTextFormatCollection::setPaintDevice( QPaintDevice *pd )
{
paintdevice = pd;
#if defined(Q_WS_X11)
int scr = ( paintdevice ) ? paintdevice->x11Screen() : QPaintDevice::x11AppScreen();
defFormat->fn.x11SetScreen( scr );
defFormat->update();
QDictIterator<QTextFormat> it( cKey );
QTextFormat *format;
while ( ( format = it.current() ) != 0 ) {
++it;
format->fn.x11SetScreen( scr );
format->update();
}
#endif // Q_WS_X11
}
QTextFormat *QTextFormatCollection::format( QTextFormat *f )
{
if ( f->parent() == this || f == defFormat ) {
lastFormat = f;
lastFormat->addRef();
return lastFormat;
}
if ( f == lastFormat || ( lastFormat && f->key() == lastFormat->key() ) ) {
lastFormat->addRef();
return lastFormat;
}
QTextFormat *fm = cKey.find( f->key() );
if ( fm ) {
lastFormat = fm;
lastFormat->addRef();
return lastFormat;
}
if ( f->key() == defFormat->key() )
return defFormat;
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 ) {
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::StrikeOut )
cres->fn.setStrikeOut( nf->fn.strikeOut() );
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 ) {
cres->collection = this;
cKey.insert( cres->key(), cres );
} else {
delete cres;
cres = fm;
cres->addRef();
}
return cres;
}
QTextFormat *QTextFormatCollection::format( const QFont &f, const QColor &c )
{
if ( cachedFormat && cfont == f && ccol == c ) {
cachedFormat->addRef();
return cachedFormat;
}
QString key = QTextFormat::getKey( f, c, FALSE, QTextFormat::AlignNormal );
cachedFormat = cKey.find( key );
cfont = f;
ccol = c;
if ( cachedFormat ) {
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() );
return cachedFormat;
}
void QTextFormatCollection::remove( QTextFormat *f )
{
if ( lastFormat == f )
lastFormat = 0;
if ( cres == f )
cres = 0;
if ( cachedFormat == f )
cachedFormat = 0;
if (cKey.find(f->key()) == f)
cKey.remove( f->key() );
}
#define UPDATE( up, lo, rest ) \
if ( font.lo##rest() != defFormat->fn.lo##rest() && fm->fn.lo##rest() == defFormat->fn.lo##rest() ) \
fm->fn.set##up##rest( font.lo##rest() )
void QTextFormatCollection::updateDefaultFormat( const QFont &font, const QColor &color, QStyleSheet *sheet )
{
QDictIterator<QTextFormat> it( cKey );
QTextFormat *fm;
bool usePixels = font.pointSize() == -1;
bool changeSize = usePixels ? font.pixelSize() != defFormat->fn.pixelSize() :
font.pointSize() != defFormat->fn.pointSize();
int base = usePixels ? font.pixelSize() : font.pointSize();
while ( ( fm = it.current() ) ) {
++it;
UPDATE( F, f, amily );
UPDATE( W, w, eight );
UPDATE( B, b, old );
UPDATE( I, i, talic );
UPDATE( U, u, nderline );
if ( changeSize ) {
fm->stdSize = base;
fm->usePixelSizes = usePixels;
if ( usePixels )
fm->fn.setPixelSize( fm->stdSize );
else
fm->fn.setPointSize( fm->stdSize );
sheet->scaleFont( fm->fn, fm->logicalFontSize );
}
if ( color.isValid() && color != defFormat->col && fm->col == defFormat->col )
fm->col = color;
fm->update();
}
defFormat->fn = font;
defFormat->col = color;
defFormat->update();
defFormat->stdSize = base;
defFormat->usePixelSizes = usePixels;
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 );
delete [] formats;
}
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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::setStrikeOut( bool b )
{
if ( b == fn.strikeOut() )
return;
fn.setStrikeOut( 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();
}
QString QTextFormat::makeFormatChangeTags( QTextFormat* defaultFormat, QTextFormat *f,
const QString& oldAnchorHref, const QString& anchorHref ) const
{
QString tag;
if ( f )
tag += f->makeFormatEndTags( defaultFormat, oldAnchorHref );
if ( !anchorHref.isEmpty() )
tag += "<a href=\"" + anchorHref + "\">";
if ( font() != defaultFormat->font()
|| vAlign() != defaultFormat->vAlign()
|| color().rgb() != defaultFormat->color().rgb() ) {
QString s;
if ( font().family() != defaultFormat->font().family() )
s += QString(!!s?";":"") + "font-family:" + fn.family();
if ( font().italic() && font().italic() != defaultFormat->font().italic() )
s += QString(!!s?";":"") + "font-style:" + (font().italic() ? "italic" : "normal");
if ( font().pointSize() != defaultFormat->font().pointSize() )
s += QString(!!s?";":"") + "font-size:" + QString::number( fn.pointSize() ) + "pt";
if ( font().weight() != defaultFormat->font().weight() )
s += QString(!!s?";":"") + "font-weight:" + QString::number( fn.weight() * 8 );
QString textDecoration;
bool none = FALSE;
if ( font().underline() != defaultFormat->font().underline() ) {
if (font().underline())
textDecoration = "underline";
else
none = TRUE;
}
if ( font().overline() != defaultFormat->font().overline() ) {
if (font().overline())
textDecoration += " overline";
else
none = TRUE;
}
if ( font().strikeOut() != defaultFormat->font().strikeOut() ) {
if (font().strikeOut())
textDecoration += " line-through";
else
none = TRUE;
}
if (none && textDecoration.isEmpty())
textDecoration = "none";
if (!textDecoration.isEmpty())
s += QString(!!s?";":"") + "text-decoration:" + textDecoration;
if ( vAlign() != defaultFormat->vAlign() ) {
s += QString(!!s?";":"") + "vertical-align:";
if ( vAlign() == QTextFormat::AlignSuperScript )
s += "super";
else if ( vAlign() == QTextFormat::AlignSubScript )
s += "sub";
else
s += "normal";
}
if ( color().rgb() != defaultFormat->color().rgb() )
s += QString(!!s?";":"") + "color:" + col.name();
if ( !s.isEmpty() )
tag += "<span style=\"" + s + "\">";
}
return tag;
}
QString QTextFormat::makeFormatEndTags( QTextFormat* defaultFormat, const QString& anchorHref ) const
{
QString tag;
if ( font().family() != defaultFormat->font().family()
|| font().pointSize() != defaultFormat->font().pointSize()
|| font().weight() != defaultFormat->font().weight()
|| font().italic() != defaultFormat->font().italic()
|| font().underline() != defaultFormat->font().underline()
|| font().strikeOut() != defaultFormat->font().strikeOut()
|| vAlign() != defaultFormat->vAlign()
|| color().rgb() != defaultFormat->color().rgb() )
tag += "</span>";
if ( !anchorHref.isEmpty() )
tag += "</a>";
return tag;
}
QTextFormat QTextFormat::makeTextFormat( const QStyleSheetItem *style, const QMap<QString,QString>& attr, double scaleFontsFactor ) const
{
QTextFormat format(*this);
if (!style )
return format;
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() );
if ( style->definesFontStrikeOut() )
format.fn.setStrikeOut( style->fontStrikeOut() );
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("face") ) {
QString a = attr["face"];
QString family = a.section( ',', 0, 0 );
if ( !!family )
format.fn.setFamily( family );
}
if ( attr.contains("size") ) {
QString a = attr["size"];
int n = a.toInt();
if ( a[0] == '+' || a[0] == '-' )
n += 3;
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"];
for ( int s = 0; s < a.contains(';')+1; s++ ) {
QString style = a.section( ';', s, s );
if ( style.startsWith("font-size:" ) && style.endsWith("pt") ) {
format.logicalFontSize = 0;
int size = int( scaleFontsFactor * style.mid( 10, style.length() - 12 ).toDouble() );
format.setPointSize( size );
} else if ( style.startsWith("font-style:" ) ) {
QString s = style.mid( 11 ).stripWhiteSpace();
if ( s == "normal" )
format.fn.setItalic( FALSE );
else if ( s == "italic" || s == "oblique" )
format.fn.setItalic( TRUE );
} else if ( style.startsWith("font-weight:" ) ) {
QString s = style.mid( 12 );
bool ok = TRUE;
int n = s.toInt( &ok );
if ( ok )
format.fn.setWeight( n/8 );
} else if ( style.startsWith("font-family:" ) ) {
QString family = style.mid(12).section(',',0,0);
family.replace( '\"', ' ' );
family.replace( '\'', ' ' );
family = family.stripWhiteSpace();
format.fn.setFamily( family );
} else if ( style.startsWith("text-decoration:" ) ) {
QString s = style.mid( 16 );
format.fn.setOverline( s.find("overline") != -1 );
format.fn.setStrikeOut( s.find("line-through") != -1 );
format.fn.setUnderline( s.find("underline") != -1 );
} else if ( style.startsWith("vertical-align:" ) ) {
QString s = style.mid( 15 ).stripWhiteSpace();
if ( s == "sub" )
format.setVAlign( QTextFormat::AlignSubScript );
else if ( s == "super" )
format.setVAlign( QTextFormat::AlignSuperScript );
else
format.setVAlign( QTextFormat::AlignNormal );
} else if ( style.startsWith("color:" ) ) {
format.col.setNamedColor( style.mid(6) );
format.linkColor = FALSE;
}
}
}
format.update();
return format;
}
#ifndef QT_NO_TEXTCUSTOMITEM
struct QPixmapInt
{
QPixmapInt() : ref( 0 ) {}
QPixmap pm;
int ref;
Q_DUMMY_COMPARISON_OPERATOR(QPixmapInt)
};
static QMap<QString, QPixmapInt> *pixmap_map = 0;
QTextImage::QTextImage( QTextDocument *p, const QMap<QString, QString> &attr, const QString& context,
QMimeSourceFactory &factory )
: QTextCustomItem( p )
{
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 ( !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;
}
}
}
delete reg;
}
QString QTextImage::richText() const
{
QString s;
s += "<img ";
QMap<QString, QString>::ConstIterator it = attributes.begin();
for ( ; it != attributes.end(); ++it ) {
s += it.key() + "=";
if ( (*it).find( ' ' ) != -1 )
s += "\"" + *it + "\"" + " ";
else
s += *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( QRect( x, y, width, height ), 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" ) );
shade = attr.find( "noshade" ) == attr.end();
}
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 ) || !shade ) {
QPen oldPen = p->pen();
if ( !color.isValid() )
p->setPen( QPen( cg.text(), is_printer( p ) ? height/8 : QMAX( 2, height/4 ) ) );
else
p->setPen( QPen( color, is_printer( p ) ? height/8 : QMAX( 2, height/4 ) ) );
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, g.highlight() );
qDrawShadeLine( p, r.left() - 1, y + height / 2, r.right() + 1, y + height / 2, g, TRUE, height / 8 );
}
}
#endif //QT_NO_TEXTCUSTOMITEM
/*****************************************************************/
// 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 + 1 > 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;
}
#ifndef QT_NO_TEXTCUSTOMITEM
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, QTextParagraph *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" ) {
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, scaleFontsFactor ),
contxt, *factory_, sheet_,
QConstString( doc + pos, end - pos ).string() );
cell->richText()->parentPar = curpar;
if ( cell->colspan() > 1 || cell->rowspan() > 1 )
multicells.append( cell );
col += cell->colspan()-1;
pos = end;
}
}
}
} else {
++pos;
}
}
return table;
}
#endif // QT_NO_TEXTCUSTOMITEM
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<QString, QChar> *html_map = 0;
static void qt_cleanup_html_map()
{
delete html_map;
html_map = 0;
}
static QMap<QString, QChar> *htmlMap()
{
if ( !html_map ) {
html_map = new QMap<QString, 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)
{
QString s;
pos++;
int recoverpos = pos;
while ( pos < length && doc[pos] != ';' && !doc[pos].isSpace() && pos < recoverpos + 8 ) {
s += doc[pos];
pos++;
}
if (doc[pos] != ';' && !doc[pos].isSpace() ) {
pos = recoverpos;
return '&';
}
pos++;
if ( s.length() > 1 && s[0] == '#') {
int off = 1;
int base = 10;
if (s[1] == 'x') {
off = 2;
base = 16;
}
bool ok;
int num = s.mid(off).toInt(&ok, base);
if ( num == 151 ) // ### hack for designer manual
return '-';
if (ok)
return num;
} else {
QMap<QString, 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] != '"' ) {
if ( doc[pos] == '&' ) {
s += parseHTMLSpecialChar( doc, length, pos );
} else {
s += doc[pos];
pos++;
}
}
eat(doc, length, pos, '"');
} else 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 == '\n' )
return QChar_linesep;
else
return c;
} else { // non-pre mode: collapse whitespace except nbsp
while ( pos< length &&
doc[pos].isSpace() && doc[pos] != QChar::nbsp )
pos++;
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;
}
QTextFlow::~QTextFlow()
{
clear();
}
void QTextFlow::clear()
{
#ifndef QT_NO_TEXTCUSTOMITEM
leftItems.setAutoDelete( TRUE );
rightItems.setAutoDelete( TRUE );
leftItems.clear();
rightItems.clear();
leftItems.setAutoDelete( FALSE );
rightItems.setAutoDelete( FALSE );
#endif
}
void QTextFlow::setWidth( int width )
{
w = width;
}
int QTextFlow::adjustLMargin( int yp, int, int margin, int space )
{
#ifndef QT_NO_TEXTCUSTOMITEM
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 );
}
#endif
return margin;
}
int QTextFlow::adjustRMargin( int yp, int, int margin, int space )
{
#ifndef QT_NO_TEXTCUSTOMITEM
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 );
}
#endif
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;
}
#ifndef QT_NO_TEXTCUSTOMITEM
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 );
}
}
#endif // QT_NO_TEXTCUSTOMITEM
QRect QTextFlow::boundingRect() const
{
QRect br;
#ifndef QT_NO_TEXTCUSTOMITEM
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;
}
#endif
return br;
}
void QTextFlow::drawFloatingItems( QPainter* p, int cx, int cy, int cw, int ch, const QColorGroup& cg, bool selected )
{
#ifndef QT_NO_TEXTCUSTOMITEM
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 );
}
#endif
}
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#ifndef QT_NO_TEXTCUSTOMITEM
void QTextCustomItem::pageBreak( int /*y*/ , QTextFlow* /*flow*/ )
{
}
#endif
#ifndef QT_NO_TEXTCUSTOMITEM
QTextTable::QTextTable( QTextDocument *p, const QMap<QString, QString> & attr )
: QTextCustomItem( p )
{
cells.setAutoDelete( FALSE );
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();
}
}
us_fixwidth = fixwidth;
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::setParagraph(QTextParagraph *p)
{
for ( QTextTableCell* cell = cells.first(); cell; cell = cells.next() )
cell->richText()->parentPar = p;
QTextCustomItem::setParagraph(p);
}
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 );
fixwidth = scale( us_fixwidth, 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.addCoords( innerborder/2, innerborder/2, -innerborder/2, -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.addCoords( border/2, border/2, -border/2, -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 );
}
}
}
int QTextTable::minimumWidth() const
{
return fixwidth ? fixwidth : ((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, QTextParagraph *&parag, 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, QTextParagraph *&parag, 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->firstParagraph();
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, QTextParagraph *&parag, 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->firstParagraph();
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, QTextParagraph *&parag, 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->lastParagraph();
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, QTextParagraph *&parag, 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->firstParagraph();
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, QTextParagraph *&parag, 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->lastParagraph();
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)
{
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->formatCollection()->setPaintDevice( table->parent->formatCollection()->paintDevice() );
richtext->bodyText = fmt.color();
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 == "top" )
align |= Qt::AlignTop;
else if ( va == "center" || va == "middle" )
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->setRichText( doc, context, &fmt );
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()
{
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( maxw, 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 )
{
QTextParagraph *parag = richtext->firstParagraph();
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;
if ( cx >= 0 && cy >= 0 )
richtext->draw( p, cx - ( x + horizontalAlignmentOffset() + geom.x() ),
cy - ( y + geom.y() + verticalAlignmentOffset() ),
cw, ch, g, FALSE, FALSE, 0 );
else
richtext->draw( p, -1, -1, -1, -1, g, FALSE, FALSE, 0 );
p->restore();
}
#endif
#endif //QT_NO_RICHTEXT