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.
koffice/lib/kotext/KoRichText.cpp

1770 lines
44 KiB

/****************************************************************************
** Implementation of the internal TQt classes dealing with rich text
**
** Created : 990101
**
** Copyright (C) 1992-2000 Trolltech AS. All rights reserved.
**
** This file is part of the kernel module of the TQt GUI Toolkit.
**
** This file may be distributed under the terms of the Q Public License
** as defined by Trolltech AS of Norway and appearing in the file
** LICENSE.TQPL included in the packaging of this file.
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding valid TQt Enterprise Edition or TQt Professional Edition
** licenses may use this file in accordance with the TQt Commercial License
** Agreement provided with the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
** information about TQt Commercial License Agreements.
** See http://www.trolltech.com/qpl/ for TQPL licensing information.
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
**********************************************************************/
#include "KoRichText.h"
#include "KoTextFormat.h"
#include "KoTextParag.h"
#include <tqpaintdevicemetrics.h>
#include "tqdrawutil.h" // for KoTextHorizontalLine
#include <stdlib.h>
#include "KoParagCounter.h"
#include "KoTextDocument.h"
#include <kdebug.h>
#include <tdeversion.h>
#include <tdeglobal.h>
#include <tdelocale.h>
#include <private/tqtextengine_p.h>
//#define DEBUG_COLLECTION
//#define DEBUG_TABLE_RENDERING
//static KoTextFormatCollection *qFormatCollection = 0;
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void KoTextDocCommandHistory::addCommand( KoTextDocCommand *cmd )
{
if ( current < (int)history.count() - 1 ) {
TQPtrList<KoTextDocCommand> 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;
}
KoTextCursor *KoTextDocCommandHistory::undo( KoTextCursor *c )
{
if ( current > -1 ) {
KoTextCursor *c2 = history.at( current )->unexecute( c );
--current;
return c2;
}
return 0;
}
KoTextCursor *KoTextDocCommandHistory::redo( KoTextCursor *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 KoTextDocCommandHistory::isUndoAvailable()
{
return current > -1;
}
bool KoTextDocCommandHistory::isRedoAvailable()
{
return current > -1 && current < (int)history.count() - 1 || current == -1 && history.count() > 0;
}
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
KoTextDocDeleteCommand::KoTextDocDeleteCommand( KoTextDocument *d, int i, int idx, const TQMemArray<KoTextStringChar> &str )
: KoTextDocCommand( d ), id( i ), index( idx ), parag( 0 ), text( str )
{
for ( int j = 0; j < (int)text.size(); ++j ) {
if ( text[ j ].format() )
text[ j ].format()->addRef();
}
}
/*KoTextDocDeleteCommand::KoTextDocDeleteCommand( KoTextParag *p, int idx, const TQMemArray<KoTextStringChar> &str )
: KoTextDocCommand( 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();
}
}*/
KoTextDocDeleteCommand::~KoTextDocDeleteCommand()
{
for ( int i = 0; i < (int)text.size(); ++i ) {
if ( text[ i ].format() )
text[ i ].format()->removeRef();
}
text.resize( 0 );
}
KoTextCursor *KoTextDocDeleteCommand::execute( KoTextCursor *c )
{
KoTextParag *s = doc ? doc->paragAt( id ) : parag;
if ( !s ) {
if(doc)
kdWarning(32500) << "can't locate parag at " << id << ", last parag: " << doc->lastParag()->paragId() << endl;
return 0;
}
cursor.setParag( s );
cursor.setIndex( index );
int len = text.size();
if ( c )
*c = cursor;
if ( doc ) {
doc->setSelectionStart( KoTextDocument::Temp, &cursor );
for ( int i = 0; i < len; ++i )
cursor.gotoNextLetter();
doc->setSelectionEnd( KoTextDocument::Temp, &cursor );
doc->removeSelectedText( KoTextDocument::Temp, &cursor );
if ( c )
*c = cursor;
} else {
s->remove( index, len );
}
return c;
}
KoTextCursor *KoTextDocDeleteCommand::unexecute( KoTextCursor *c )
{
KoTextParag *s = doc ? doc->paragAt( id ) : parag;
if ( !s ) {
if(doc)
kdWarning(32500) << "can't locate parag at " << id << ", last parag: " << doc->lastParag()->paragId() << endl;
return 0;
}
cursor.setParag( s );
cursor.setIndex( index );
TQString str = KoTextString::toString( text );
cursor.insert( str, TRUE, &text );
cursor.setParag( s );
cursor.setIndex( index );
if ( c ) {
c->setParag( s );
c->setIndex( index );
for ( int i = 0; i < (int)text.size(); ++i )
c->gotoNextLetter();
}
s = cursor.parag();
while ( s ) {
s->format();
s->setChanged( TRUE );
if ( c && s == c->parag() )
break;
s = s->next();
}
return &cursor;
}
KoTextDocFormatCommand::KoTextDocFormatCommand( KoTextDocument *d, int sid, int sidx, int eid, int eidx,
const TQMemArray<KoTextStringChar> &old, const KoTextFormat *f, int fl )
: KoTextDocCommand( d ), startId( sid ), startIndex( sidx ), endId( eid ), endIndex( eidx ), 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();
}
}
KoTextDocFormatCommand::~KoTextDocFormatCommand()
{
format->removeRef();
for ( int j = 0; j < (int)oldFormats.size(); ++j ) {
if ( oldFormats[ j ].format() )
oldFormats[ j ].format()->removeRef();
}
}
KoTextCursor *KoTextDocFormatCommand::execute( KoTextCursor *c )
{
KoTextParag *sp = doc->paragAt( startId );
KoTextParag *ep = doc->paragAt( endId );
if ( !sp || !ep )
return c;
KoTextCursor start( doc );
start.setParag( sp );
start.setIndex( startIndex );
KoTextCursor end( doc );
end.setParag( ep );
end.setIndex( endIndex );
doc->setSelectionStart( KoTextDocument::Temp, &start );
doc->setSelectionEnd( KoTextDocument::Temp, &end );
doc->setFormat( KoTextDocument::Temp, format, flags );
doc->removeSelection( KoTextDocument::Temp );
if ( endIndex == ep->length() ) // ### Not in TQRT - report sent. Description at http://bugs.kde.org/db/34/34556.html
end.gotoLeft();
*c = end;
return c;
}
KoTextCursor *KoTextDocFormatCommand::unexecute( KoTextCursor *c )
{
KoTextParag *sp = doc->paragAt( startId );
KoTextParag *ep = doc->paragAt( endId );
if ( !sp || !ep )
return 0;
int idx = startIndex;
int fIndex = 0;
if( !oldFormats.isEmpty()) // ## not in TQRT. Not sure how it can happen.
{
for ( ;; ) {
if ( oldFormats.at( fIndex ).c == '\n' ) {
if ( idx > 0 ) {
if ( idx < sp->length() && fIndex > 0 )
sp->setFormat( idx, 1, oldFormats.at( fIndex - 1 ).format() );
if ( sp == ep )
break;
sp = sp->next();
idx = 0;
}
fIndex++;
}
if ( oldFormats.at( fIndex ).format() )
sp->setFormat( idx, 1, oldFormats.at( fIndex ).format() );
idx++;
fIndex++;
if ( fIndex >= (int)oldFormats.size() )
break;
if ( idx >= sp->length() ) {
if ( sp == ep )
break;
sp = sp->next();
idx = 0;
}
}
}
KoTextCursor end( doc );
end.setParag( ep );
end.setIndex( endIndex );
if ( endIndex == ep->length() )
end.gotoLeft();
*c = end;
return c;
}
KoTextAlignmentCommand::KoTextAlignmentCommand( KoTextDocument *d, int fParag, int lParag, int na, const TQMemArray<int> &oa )
: KoTextDocCommand( d ), firstParag( fParag ), lastParag( lParag ), newAlign( na ), oldAligns( oa )
{
}
KoTextCursor *KoTextAlignmentCommand::execute( KoTextCursor *c )
{
KoTextParag *p = doc->paragAt( firstParag );
if ( !p )
return c;
while ( p ) {
p->setAlignment( newAlign );
if ( p->paragId() == lastParag )
break;
p = p->next();
}
return c;
}
KoTextCursor *KoTextAlignmentCommand::unexecute( KoTextCursor *c )
{
KoTextParag *p = doc->paragAt( firstParag );
if ( !p )
return c;
int i = 0;
while ( p ) {
if ( i < (int)oldAligns.size() )
p->setAlignment( oldAligns.at( i ) );
if ( p->paragId() == lastParag )
break;
p = p->next();
++i;
}
return c;
}
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
KoTextCursor::KoTextCursor( KoTextDocument *d )
: doc( d )
{
idx = 0;
string = doc ? doc->firstParag() : 0;
tmpIndex = -1;
}
KoTextCursor::KoTextCursor()
{
}
KoTextCursor::KoTextCursor( const KoTextCursor &c )
{
doc = c.doc;
idx = c.idx;
string = c.string;
tmpIndex = c.tmpIndex;
}
KoTextCursor &KoTextCursor::operator=( const KoTextCursor &c )
{
doc = c.doc;
idx = c.idx;
string = c.string;
tmpIndex = c.tmpIndex;
return *this;
}
bool KoTextCursor::operator==( const KoTextCursor &c ) const
{
return doc == c.doc && string == c.string && idx == c.idx;
}
void KoTextCursor::insert( const TQString &str, bool checkNewLine, TQMemArray<KoTextStringChar> *formatting )
{
string->invalidate( idx );
tmpIndex = -1;
bool justInsert = TRUE;
TQString s( str );
#if defined(TQ_WS_WIN)
if ( checkNewLine )
s = s.replace( TQRegExp( "\\r" ), "" );
#endif
if ( checkNewLine )
justInsert = s.find( '\n' ) == -1;
if ( justInsert ) {
string->insert( idx, s );
if ( formatting ) {
for ( int i = 0; i < (int)s.length(); ++i ) {
if ( formatting->at( i ).format() ) {
formatting->at( i ).format()->addRef();
string->string()->setFormat( idx + i, formatting->at( i ).format(), TRUE );
}
}
}
idx += s.length();
} else {
TQStringList lst = TQStringList::split( '\n', s, TRUE );
TQStringList::Iterator it = lst.begin();
//int y = string->rect().y() + string->rect().height();
int lastIndex = 0;
KoTextFormat *lastFormat = 0;
for ( ; it != lst.end(); ) {
if ( it != lst.begin() ) {
splitAndInsertEmptyParag( FALSE, TRUE );
//string->setEndState( -1 );
#if 0 // no!
string->prev()->format( -1, FALSE );
#endif
if ( lastFormat && formatting && string->prev() ) {
lastFormat->addRef();
string->prev()->string()->setFormat( string->prev()->length() - 1, lastFormat, TRUE );
}
}
lastFormat = 0;
TQString s = *it;
++it;
if ( !s.isEmpty() )
string->insert( idx, s );
else
string->invalidate( 0 );
if ( formatting ) {
int len = s.length();
for ( int i = 0; i < len; ++i ) {
if ( formatting->at( i + lastIndex ).format() ) {
formatting->at( i + lastIndex ).format()->addRef();
string->string()->setFormat( i + idx, formatting->at( i + lastIndex ).format(), TRUE );
}
}
if ( it != lst.end() )
lastFormat = formatting->at( len + lastIndex ).format();
++len;
lastIndex += len;
}
idx += s.length();
}
#if 0 //// useless and wrong. We'll format things and move them down correctly in KoTextObject::insert().
string->format( -1, FALSE );
int dy = string->rect().y() + string->rect().height() - y;
#endif
KoTextParag *p = string;
p->setParagId( p->prev()->paragId() + 1 );
p = p->next();
while ( p ) {
p->setParagId( p->prev()->paragId() + 1 );
//p->move( dy );
p->invalidate( 0 );
p = p->next();
}
}
#if 0 //// useless and slow
int h = string->rect().height();
string->format( -1, TRUE );
#endif
fixCursorPosition();
}
void KoTextCursor::gotoLeft()
{
if ( string->string()->isRightToLeft() )
gotoNextLetter();
else
gotoPreviousLetter();
}
void KoTextCursor::gotoPreviousLetter()
{
tmpIndex = -1;
if ( idx > 0 ) {
idx = string->string()->previousCursorPosition( idx );
} else if ( string->prev() ) {
string = string->prev();
while ( !string->isVisible() )
string = string->prev();
idx = string->length() - 1;
}
}
bool KoTextCursor::place( const TQPoint &p, KoTextParag *s, bool link, int *customItemIndex )
{
if ( customItemIndex )
*customItemIndex = -1;
TQPoint pos( p );
TQRect r;
if ( pos.y() < s->rect().y() )
pos.setY( s->rect().y() );
while ( s ) {
r = s->rect();
r.setWidth( doc ? doc->width() : TQWIDGETSIZE_MAX );
if ( !s->next() || ( pos.y() >= r.y() && pos.y() < s->next()->rect().y() ) )
break;
s = s->next();
}
if ( !s )
return FALSE;
setParag( s, FALSE );
int y = s->rect().y();
int lines = s->lines();
KoTextStringChar *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 ( i < lines - 1 && pos.y() >= y + cy && pos.y() <= y + s->lineY( i+1 ) )
break;
}
int nextLine;
if ( i < lines - 1 )
s->lineStartOfLine( i+1, &nextLine );
else
nextLine = s->length();
i = index;
int x = s->rect().x();
if ( pos.x() < x )
pos.setX( x + 1 );
int cw;
int curpos = s->length()-1;
int dist = 10000000;
while ( i < nextLine ) {
chr = s->at(i);
int cpos = x + chr->x;
cw = chr->width; //s->string()->width( i );
if ( chr->isCustom() ) {
if ( pos.x() >= cpos && pos.x() <= cpos + cw &&
pos.y() >= y + cy && pos.y() <= y + cy + chr->height() ) {
if ( customItemIndex )
*customItemIndex = i;
}
}
if( chr->rightToLeft )
cpos += cw;
int d = cpos - pos.x();
bool dm = d < 0 ? !chr->rightToLeft : chr->rightToLeft;
if ( (TQABS( d ) < dist || (dist == d && dm == TRUE )) && string->string()->validCursorPosition( i ) ) {
dist = TQABS( d );
if ( !link || pos.x() >= x + chr->x ) {
curpos = i;
}
}
i++;
}
setIndex( curpos, FALSE );
return TRUE;
}
void KoTextCursor::gotoRight()
{
if ( string->string()->isRightToLeft() )
gotoPreviousLetter();
else
gotoNextLetter();
}
void KoTextCursor::gotoNextLetter()
{
tmpIndex = -1;
int len = string->length() - 1;
if ( idx < len ) {
idx = string->string()->nextCursorPosition( idx );
} else if ( string->next() ) {
string = string->next();
while ( !string->isVisible() )
string = string->next();
idx = 0;
}
}
void KoTextCursor::gotoUp()
{
int indexOfLineStart;
int line;
KoTextStringChar *c = string->lineStartOfChar( idx, &indexOfLineStart, &line );
if ( !c )
return;
tmpIndex = TQMAX( tmpIndex, idx - indexOfLineStart );
if ( indexOfLineStart == 0 ) {
if ( !string->prev() ) {
return;
}
string = string->prev();
while ( !string->isVisible() )
string = string->prev();
int lastLine = string->lines() - 1;
if ( !string->lineStartOfLine( lastLine, &indexOfLineStart ) )
return;
if ( indexOfLineStart + tmpIndex < string->length() )
idx = indexOfLineStart + tmpIndex;
else
idx = string->length() - 1;
} else {
--line;
int oldIndexOfLineStart = indexOfLineStart;
if ( !string->lineStartOfLine( line, &indexOfLineStart ) )
return;
if ( indexOfLineStart + tmpIndex < oldIndexOfLineStart )
idx = indexOfLineStart + tmpIndex;
else
idx = oldIndexOfLineStart - 1;
}
fixCursorPosition();
}
void KoTextCursor::gotoDown()
{
int indexOfLineStart;
int line;
KoTextStringChar *c = string->lineStartOfChar( idx, &indexOfLineStart, &line );
if ( !c )
return;
tmpIndex = TQMAX( tmpIndex, idx - indexOfLineStart );
if ( line == string->lines() - 1 ) {
if ( !string->next() ) {
return;
}
string = string->next();
while ( !string->isVisible() )
string = string->next();
if ( !string->lineStartOfLine( 0, &indexOfLineStart ) )
return;
int end;
if ( string->lines() == 1 )
end = string->length();
else
string->lineStartOfLine( 1, &end );
if ( indexOfLineStart + tmpIndex < end )
idx = indexOfLineStart + tmpIndex;
else
idx = end - 1;
} else {
++line;
int end;
if ( line == string->lines() - 1 )
end = string->length();
else
string->lineStartOfLine( line + 1, &end );
if ( !string->lineStartOfLine( line, &indexOfLineStart ) )
return;
if ( indexOfLineStart + tmpIndex < end )
idx = indexOfLineStart + tmpIndex;
else
idx = end - 1;
}
fixCursorPosition();
}
void KoTextCursor::gotoLineEnd()
{
tmpIndex = -1;
int indexOfLineStart;
int line;
KoTextStringChar *c = string->lineStartOfChar( idx, &indexOfLineStart, &line );
if ( !c )
return;
if ( line == string->lines() - 1 ) {
idx = string->length() - 1;
} else {
c = string->lineStartOfLine( ++line, &indexOfLineStart );
indexOfLineStart--;
idx = indexOfLineStart;
}
}
void KoTextCursor::gotoLineStart()
{
tmpIndex = -1;
int indexOfLineStart;
int line;
KoTextStringChar *c = string->lineStartOfChar( idx, &indexOfLineStart, &line );
if ( !c )
return;
idx = indexOfLineStart;
}
void KoTextCursor::gotoHome()
{
tmpIndex = -1;
if ( doc )
string = doc->firstParag();
idx = 0;
}
void KoTextCursor::gotoEnd()
{
// This can happen in a no-auto-resize frame with overflowing contents.
// Don't prevent going to the end of the text, even if it's not visible.
//if ( doc && !doc->lastParag()->isValid() )
//{
// kdDebug(32500) << "Last parag, " << doc->lastParag()->paragId() << ", is invalid - aborting gotoEnd() !" << endl;
// return;
// }
tmpIndex = -1;
if ( doc )
string = doc->lastParag();
idx = string->length() - 1;
}
void KoTextCursor::gotoPageUp( int visibleHeight )
{
tmpIndex = -1;
KoTextParag *s = string;
int h = visibleHeight;
int y = s->rect().y();
while ( s ) {
if ( y - s->rect().y() >= h )
break;
s = s->prev();
}
if ( !s && doc )
s = doc->firstParag();
string = s;
idx = 0;
}
void KoTextCursor::gotoPageDown( int visibleHeight )
{
tmpIndex = -1;
KoTextParag *s = string;
int h = visibleHeight;
int y = s->rect().y();
while ( s ) {
if ( s->rect().y() - y >= h )
break;
s = s->next();
}
if ( !s && doc ) {
s = doc->lastParag();
string = s;
idx = string->length() - 1;
return;
}
if ( !s->isValid() )
return;
string = s;
idx = 0;
}
void KoTextCursor::gotoWordRight()
{
if ( string->string()->isRightToLeft() )
gotoPreviousWord();
else
gotoNextWord();
}
void KoTextCursor::gotoWordLeft()
{
if ( string->string()->isRightToLeft() )
gotoNextWord();
else
gotoPreviousWord();
}
void KoTextCursor::gotoPreviousWord()
{
gotoPreviousLetter();
tmpIndex = -1;
KoTextString *s = string->string();
bool allowSame = FALSE;
if ( idx == ( (int)s->length()-1 ) )
return;
for ( int i = idx; i >= 0; --i ) {
if ( s->at( i ).c.isSpace() || s->at( i ).c == '\t' || s->at( i ).c == '.' ||
s->at( i ).c == ',' || s->at( i ).c == ':' || s->at( i ).c == ';' ) {
if ( !allowSame )
continue;
idx = i + 1;
return;
}
if ( !allowSame && !( s->at( i ).c.isSpace() || s->at( i ).c == '\t' || s->at( i ).c == '.' ||
s->at( i ).c == ',' || s->at( i ).c == ':' || s->at( i ).c == ';' ) )
allowSame = TRUE;
}
idx = 0;
}
void KoTextCursor::gotoNextWord()
{
tmpIndex = -1;
KoTextString *s = string->string();
bool allowSame = FALSE;
for ( int i = idx; i < (int)s->length(); ++i ) {
if ( ! ( s->at( i ).c.isSpace() || s->at( i ).c == '\t' || s->at( i ).c == '.' ||
s->at( i ).c == ',' || s->at( i ).c == ':' || s->at( i ).c == ';' ) ) {
if ( !allowSame )
continue;
idx = i;
return;
}
if ( !allowSame && ( s->at( i ).c.isSpace() || s->at( i ).c == '\t' || s->at( i ).c == '.' ||
s->at( i ).c == ',' || s->at( i ).c == ':' || s->at( i ).c == ';' ) )
allowSame = TRUE;
}
if ( idx < ((int)s->length()-1) ) {
gotoLineEnd();
} else if ( string->next() ) {
string = string->next();
while ( !string->isVisible() )
string = string->next();
idx = 0;
} else {
gotoLineEnd();
}
}
bool KoTextCursor::atParagStart() const
{
return idx == 0;
}
bool KoTextCursor::atParagEnd() const
{
return idx == string->length() - 1;
}
void KoTextCursor::splitAndInsertEmptyParag( bool ind, bool updateIds )
{
if ( !doc )
return;
tmpIndex = -1;
KoTextFormat *f = 0;
if ( doc->useFormatCollection() ) {
f = string->at( idx )->format();
if ( idx == string->length() - 1 && idx > 0 )
f = string->at( idx - 1 )->format();
if ( f->isMisspelled() ) {
KoTextFormat fNoMisspelled( *f );
fNoMisspelled.setMisspelled( false );
f = doc->formatCollection()->format( &fNoMisspelled );
}
}
if ( atParagEnd() ) {
KoTextParag *n = string->next();
KoTextParag *s = doc->createParag( doc, string, n, updateIds );
if ( f )
s->setFormat( 0, 1, f, TRUE );
s->copyParagData( string );
#if 0
if ( ind ) {
int oi, ni;
s->indent( &oi, &ni );
string = s;
idx = ni;
} else
#endif
{
string = s;
idx = 0;
}
} else if ( atParagStart() ) {
KoTextParag *p = string->prev();
KoTextParag *s = doc->createParag( doc, p, string, updateIds );
if ( f )
s->setFormat( 0, 1, f, TRUE );
s->copyParagData( string );
if ( ind ) {
//s->indent();
s->format();
//indent();
string->format();
}
} else {
TQString str = string->string()->toString().mid( idx, 0xFFFFFF );
KoTextParag *n = string->next();
KoTextParag *s = doc->createParag( doc, string, n, updateIds );
s->copyParagData( string );
s->remove( 0, 1 );
s->append( str, TRUE );
for ( uint i = 0; i < str.length(); ++i ) {
KoTextStringChar* tsc = string->at( idx + i );
s->setFormat( i, 1, tsc->format(), TRUE );
if ( tsc->isCustom() ) {
KoTextCustomItem * item = tsc->customItem();
s->at( i )->setCustomItem( item );
tsc->loseCustomItem();
#if 0
s->addCustomItem();
string->removeCustomItem();
#endif
doc->unregisterCustomItem( item, string );
doc->registerCustomItem( item, s );
}
}
string->truncate( idx );
#if 0
if ( ind ) {
int oi, ni;
s->indent( &oi, &ni );
string = s;
idx = ni;
} else
#endif
{
string = s;
idx = 0;
}
}
}
bool KoTextCursor::removePreviousChar()
{
tmpIndex = -1;
if ( !atParagStart() ) {
string->remove( idx-1, 1 );
idx--;
// shouldn't be needed, just to make sure.
fixCursorPosition();
string->format( -1, TRUE );
//else if ( string->document() && string->document()->parent() )
// string->document()->nextDoubleBuffered = TRUE;
return FALSE;
} else if ( string->prev() ) {
string = string->prev();
string->join( string->next() );
string->invalidateCounters();
return TRUE;
}
return FALSE;
}
bool KoTextCursor::remove()
{
tmpIndex = -1;
if ( !atParagEnd() ) {
int next = string->string()->nextCursorPosition( idx );
string->remove( idx, next-idx );
string->format( -1, TRUE );
//else if ( doc && doc->parent() )
// doc->nextDoubleBuffered = TRUE;
return FALSE;
} else if ( string->next() ) {
if ( string->length() == 1 ) {
string->next()->setPrev( string->prev() );
if ( string->prev() )
string->prev()->setNext( string->next() );
KoTextParag *p = string->next();
delete string;
string = p;
string->invalidate( 0 );
//// kotext
string->invalidateCounters();
////
KoTextParag *s = string;
while ( s ) {
s->id = s->p ? s->p->id + 1 : 0;
//s->state = -1;
//s->needPreProcess = TRUE;
s->changed = TRUE;
s = s->n;
}
string->format();
} else {
string->join( string->next() );
}
return TRUE;
}
return FALSE;
}
void KoTextCursor::killLine()
{
if ( atParagEnd() )
return;
string->remove( idx, string->length() - idx - 1 );
string->format( -1, TRUE );
//else if ( doc && doc->parent() )
//doc->nextDoubleBuffered = TRUE;
}
#if 0
void KoTextCursor::indent()
{
int oi = 0, ni = 0;
string->indent( &oi, &ni );
if ( oi == ni )
return;
if ( idx >= oi )
idx += ni - oi;
else
idx = ni;
}
#endif
void KoTextCursor::setDocument( KoTextDocument *d )
{
doc = d;
string = d->firstParag();
idx = 0;
tmpIndex = -1;
}
int KoTextCursor::x() const
{
KoTextStringChar *c = string->at( idx );
int curx = c->x;
if ( c->rightToLeft )
curx += c->width; //string->string()->width( idx );
return curx;
}
int KoTextCursor::y() const
{
int dummy, line;
string->lineStartOfChar( idx, &dummy, &line );
return string->lineY( line );
}
void KoTextCursor::fixCursorPosition()
{
// searches for the closest valid cursor position
if ( string->string()->validCursorPosition( idx ) )
return;
int lineIdx;
KoTextStringChar *start = string->lineStartOfChar( idx, &lineIdx, 0 );
int x = string->string()->at( idx ).x;
int diff = TQABS(start->x - x);
int best = lineIdx;
KoTextStringChar *c = start;
++c;
KoTextStringChar *end = &string->string()->at( string->length()-1 );
while ( c <= end && !c->lineStart ) {
int xp = c->x;
if ( c->rightToLeft )
xp += c->pixelwidth; //string->string()->width( lineIdx + (c-start) );
int ndiff = TQABS(xp - x);
if ( ndiff < diff && string->string()->validCursorPosition(lineIdx + (c-start)) ) {
diff = ndiff;
best = lineIdx + (c-start);
}
++c;
}
idx = best;
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
KoTextString::KoTextString()
{
bidiDirty = TRUE;
bNeedsSpellCheck = true;
bidi = FALSE;
rightToLeft = FALSE;
dir = TQChar::DirON;
}
KoTextString::KoTextString( const KoTextString &s )
{
bidiDirty = s.bidiDirty;
bNeedsSpellCheck = s.bNeedsSpellCheck;
bidi = s.bidi;
rightToLeft = s.rightToLeft;
dir = s.dir;
data = s.data;
data.detach();
for ( int i = 0; i < (int)data.size(); ++i ) {
KoTextFormat *f = data[i].format();
if ( f )
f->addRef();
}
}
void KoTextString::insert( int index, const TQString &s, KoTextFormat *f )
{
int os = data.size();
data.resize( data.size() + s.length() );
if ( index < os ) {
memmove( data.data() + index + s.length(), data.data() + index,
sizeof( KoTextStringChar ) * ( os - index ) );
}
for ( int i = 0; i < (int)s.length(); ++i ) {
KoTextStringChar &ch = data[ (int)index + i ];
ch.x = 0;
ch.pixelxadj = 0;
ch.pixelwidth = 0;
ch.width = 0;
ch.lineStart = 0;
ch.d.format = 0;
ch.type = KoTextStringChar::Regular;
ch.rightToLeft = 0;
ch.startOfRun = 0;
ch.c = s[ i ];
#ifdef DEBUG_COLLECTION
kdDebug(32500) << "KoTextString::insert setting format " << f << " to character " << (int)index+i << endl;
#endif
ch.setFormat( f );
}
bidiDirty = TRUE;
bNeedsSpellCheck = true;
}
KoTextString::~KoTextString()
{
clear();
}
void KoTextString::insert( int index, KoTextStringChar *c )
{
int os = data.size();
data.resize( data.size() + 1 );
if ( index < os ) {
memmove( data.data() + index + 1, data.data() + index,
sizeof( KoTextStringChar ) * ( os - index ) );
}
KoTextStringChar &ch = data[ (int)index ];
ch.c = c->c;
ch.x = 0;
ch.pixelxadj = 0;
ch.pixelwidth = 0;
ch.width = 0;
ch.lineStart = 0;
ch.rightToLeft = 0;
ch.d.format = 0;
ch.type = KoTextStringChar::Regular;
ch.setFormat( c->format() );
bidiDirty = TRUE;
bNeedsSpellCheck = true;
}
void KoTextString::truncate( int index )
{
index = TQMAX( index, 0 );
index = TQMIN( index, (int)data.size() - 1 );
if ( index < (int)data.size() ) {
for ( int i = index + 1; i < (int)data.size(); ++i ) {
KoTextStringChar &ch = data[ i ];
if ( ch.isCustom() ) {
delete ch.customItem();
if ( ch.d.custom->format )
ch.d.custom->format->removeRef();
delete ch.d.custom;
ch.d.custom = 0;
} else if ( ch.format() ) {
ch.format()->removeRef();
}
}
}
data.truncate( index );
bidiDirty = TRUE;
bNeedsSpellCheck = true;
}
void KoTextString::remove( int index, int len )
{
for ( int i = index; i < (int)data.size() && i - index < len; ++i ) {
KoTextStringChar &ch = data[ i ];
if ( ch.isCustom() ) {
delete ch.customItem();
if ( ch.d.custom->format )
ch.d.custom->format->removeRef();
delete ch.d.custom;
ch.d.custom = 0;
} else if ( ch.format() ) {
ch.format()->removeRef();
}
}
memmove( data.data() + index, data.data() + index + len,
sizeof( KoTextStringChar ) * ( data.size() - index - len ) );
data.resize( data.size() - len, TQGArray::SpeedOptim );
bidiDirty = TRUE;
bNeedsSpellCheck = true;
}
void KoTextString::clear()
{
for ( int i = 0; i < (int)data.count(); ++i ) {
KoTextStringChar &ch = data[ i ];
if ( ch.isCustom() ) {
// Can't do that here, no access to the doc. See ~KoTextParag instead.
// However clear() is also called by operator=, many times in kotextobject.cpp...
// Hopefully not with customitems in there...
//if ( doc )
// doc->unregisterCustomItem( ch->customItem(), this );
delete ch.customItem();
if ( ch.d.custom->format )
ch.d.custom->format->removeRef();
delete ch.d.custom;
ch.d.custom = 0;
} else if ( ch.format() ) {
ch.format()->removeRef();
}
}
data.resize( 0 );
}
void KoTextString::setFormat( int index, KoTextFormat *f, bool useCollection, bool setFormatAgain )
{
KoTextStringChar &ch = data[ index ];
// kdDebug(32500) << "KoTextString::setFormat index=" << index << " f=" << f << endl;
if ( useCollection && ch.format() )
{
//kdDebug(32500) << "KoTextString::setFormat removing ref on old format " << ch.format() << endl;
ch.format()->removeRef();
}
ch.setFormat( f, setFormatAgain );
}
void KoTextString::checkBidi() const
{
KoTextString *that = (KoTextString *)this;
that->bidiDirty = FALSE;
int length = data.size();
if ( !length ) {
that->bidi = FALSE;
that->rightToLeft = dir == TQChar::DirR;
return;
}
const KoTextStringChar *start = data.data();
const KoTextStringChar *end = start + length;
// determines the properties we need for layouting
TQTextEngine textEngine( toString(), 0 );
textEngine.direction = (TQChar::Direction) dir;
textEngine.itemize(TQTextEngine::SingleLine);
const TQCharAttributes *ca = textEngine.attributes() + length-1;
KoTextStringChar *ch = (KoTextStringChar *)end - 1;
TQScriptItem *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 == TQChar::DirR ) {
that->bidi = TRUE;
that->rightToLeft = TRUE;
} else if ( dir == TQChar::DirL ) {
that->rightToLeft = FALSE;
} else {
that->rightToLeft = (textEngine.direction == TQChar::DirR);
}
}
TQMemArray<KoTextStringChar> KoTextString::subString( int start, int len ) const
{
if ( len == 0xFFFFFF )
len = data.size();
TQMemArray<KoTextStringChar> a;
a.resize( len );
for ( int i = 0; i < len; ++i ) {
KoTextStringChar *c = &data[ i + start ];
a[ i ].c = c->c;
a[ i ].x = 0;
a[ i ].pixelxadj = 0;
a[ i ].pixelwidth = 0;
a[ i ].width = 0;
a[ i ].lineStart = 0;
a[ i ].rightToLeft = 0;
a[ i ].d.format = 0;
a[ i ].type = KoTextStringChar::Regular;
a[ i ].setFormat( c->format() );
if ( c->format() )
c->format()->addRef();
}
return a;
}
TQString KoTextString::mid( int start, int len ) const
{
if ( len == 0xFFFFFF )
len = data.size();
TQString res;
res.setLength( len );
for ( int i = 0; i < len; ++i ) {
KoTextStringChar *c = &data[ i + start ];
res[ i ] = c->c;
}
return res;
}
TQString KoTextString::toString( const TQMemArray<KoTextStringChar> &data )
{
TQString s;
int l = data.size();
s.setUnicode( 0, l );
KoTextStringChar *c = data.data();
TQChar *uc = (TQChar *)s.unicode();
while ( l-- ) {
*uc = c->c;
uc++;
c++;
}
return s;
}
TQString KoTextString::toReverseString() const
{
TQString s;
int l = length();
s.setUnicode(0, l);
KoTextStringChar *c = data.data() + (l-1);
TQChar *uc = (TQChar *)s.unicode();
while ( l-- ) {
*uc = c->c;
uc++;
c--;
}
return s;
}
TQString KoTextString::stringToSpellCheck()
{
if ( !bNeedsSpellCheck )
return TQString();
bNeedsSpellCheck = false;
if ( length() <= 1 )
return TQString();
TQString str = toString();
str.truncate( str.length() - 1 ); // remove trailing space
return str;
}
int KoTextString::nextCursorPosition( int next )
{
if ( bidiDirty )
checkBidi();
const KoTextStringChar *c = data.data();
int len = length();
if ( next < len - 1 ) {
next++;
while ( next < len - 1 && !c[next].charStop )
next++;
}
return next;
}
int KoTextString::previousCursorPosition( int prev )
{
if ( bidiDirty )
checkBidi();
const KoTextStringChar *c = data.data();
if ( prev ) {
prev--;
while ( prev && !c[prev].charStop )
prev--;
}
return prev;
}
bool KoTextString::validCursorPosition( int idx )
{
if ( bidiDirty )
checkBidi();
return (at( idx ).charStop);
}
////
void KoTextStringChar::setFormat( KoTextFormat *f, bool setFormatAgain )
{
if ( type == Regular ) {
d.format = f;
} else {
if ( !d.custom ) {
d.custom = new CustomData;
d.custom->custom = 0;
}
d.custom->format = f;
if ( d.custom->custom && setFormatAgain )
d.custom->custom->setFormat( f );
}
}
void KoTextStringChar::setCustomItem( KoTextCustomItem *i )
{
if ( type == Regular ) {
KoTextFormat *f = format();
d.custom = new CustomData;
d.custom->format = f;
type = Custom;
} else {
delete d.custom->custom;
}
d.custom->custom = i;
}
void KoTextStringChar::loseCustomItem() // setRegular() might be a better name
{
if ( isCustom() ) {
KoTextFormat *f = d.custom->format;
d.custom->custom = 0;
delete d.custom;
type = Regular;
d.format = f;
}
}
KoTextStringChar::~KoTextStringChar()
{
if ( format() )
format()->removeRef();
switch ( type ) {
case Custom:
delete d.custom; break;
default:
break;
}
}
int KoTextStringChar::height() const
{
return !isCustom() ? format()->height() : ( customItem()->placement() == KoTextCustomItem::PlaceInline ? customItem()->height : 0 );
}
int KoTextStringChar::ascent() const
{
return !isCustom() ? format()->ascent() : ( customItem()->placement() == KoTextCustomItem::PlaceInline ? customItem()->ascent() : 0 );
}
int KoTextStringChar::descent() const
{
return !isCustom() ? format()->descent() : 0;
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
KoTextFormatterBase::KoTextFormatterBase()
: wrapColumn( -1 ), //wrapEnabled( TRUE ),
m_bViewFormattingChars( false ),
biw( true /*default in kotext*/ )
{
}
#ifdef BIDI_DEBUG
#include <iostream>
#endif
// collects one line of the paragraph and transforms it to visual order
KoTextParagLineStart *KoTextFormatterBase::bidiReorderLine( KoTextParag * /*parag*/, KoTextString *text, KoTextParagLineStart *line,
KoTextStringChar *startChar, KoTextStringChar *lastChar, int align, int space )
{
int start = (startChar - &text->at(0));
int last = (lastChar - &text->at(0) );
//kdDebug(32500) << "doing BiDi reordering from " << start << " to " << last << "!" << endl;
KoBidiControl *control = new KoBidiControl( line->context(), line->status );
TQString str;
str.setUnicode( 0, last - start + 1 );
// fill string with logically ordered chars.
KoTextStringChar *ch = startChar;
TQChar *qch = (TQChar *)str.unicode();
while ( ch <= lastChar ) {
*qch = ch->c;
qch++;
ch++;
}
int x = startChar->x;
TQPtrList<KoTextRun> *runs;
runs = KoComplexText::bidiReorderLine(control, str, 0, last - start + 1,
(text->isRightToLeft() ? TQChar::DirR : TQChar::DirL) );
// now construct the reordered string out of the runs...
int numSpaces = 0;
// set the correct alignment. This is a bit messy....
if( align == TQt::AlignAuto ) {
// align according to directionality of the paragraph...
if ( text->isRightToLeft() )
align = TQt::AlignRight;
}
if ( align & TQt::AlignHCenter )
x += space/2;
else if ( align & TQt::AlignRight )
x += space;
else if ( align & TQt::AlignJustify ) {
for ( int j = start; j < last; ++j ) {
if( isBreakable( text, j ) ) {
numSpaces++;
}
}
}
int toAdd = 0;
bool first = TRUE;
KoTextRun *r = runs->first();
int xmax = -0xffffff;
while ( r ) {
if(r->level %2) {
// odd level, need to reverse the string
int pos = r->stop + start;
while(pos >= r->start + start) {
KoTextStringChar *c = &text->at(pos);
if( numSpaces && !first && isBreakable( text, pos ) ) {
int s = space / numSpaces;
toAdd += s;
space -= s;
numSpaces--;
} else if ( first ) {
first = FALSE;
if ( c->c == ' ' )
x -= c->format()->width( ' ' );
}
c->x = x + toAdd;
c->rightToLeft = TRUE;
c->startOfRun = FALSE;
int ww = 0;
if ( c->c.unicode() >= 32 || c->c == '\t' || c->c == '\n' || c->isCustom() ) {
ww = c->width;
} else {
ww = c->format()->width( ' ' );
}
if ( xmax < x + toAdd + ww ) xmax = x + toAdd + ww;
x += ww;
pos--;
}
} else {
int pos = r->start + start;
while(pos <= r->stop + start) {
KoTextStringChar* c = &text->at(pos);
if( numSpaces && !first && isBreakable( text, pos ) ) {
int s = space / numSpaces;
toAdd += s;
space -= s;
numSpaces--;
} else if ( first ) {
first = FALSE;
if ( c->c == ' ' )
x -= c->format()->width( ' ' );
}
c->x = x + toAdd;
c->rightToLeft = FALSE;
c->startOfRun = FALSE;
int ww = 0;
if ( c->c.unicode() >= 32 || c->c == '\t' || c->isCustom() ) {
ww = c->width;
} else {
ww = c->format()->width( ' ' );
}
//kdDebug(32500) << "setting char " << pos << " at pos " << x << endl;
if ( xmax < x + toAdd + ww ) xmax = x + toAdd + ww;
x += ww;
pos++;
}
}
text->at( r->start + start ).startOfRun = TRUE;
r = runs->next();
}
line->w = xmax + 10;
KoTextParagLineStart *ls = new KoTextParagLineStart( control->context, control->status );
delete control;
delete runs;
return ls;
}
bool KoTextFormatterBase::isStretchable( KoTextString *string, int pos ) const
{
if ( string->at( pos ).c == TQChar(160) ) //non-breaking space
return true;
KoTextStringChar& chr = string->at( pos );
return chr.whiteSpace;
//return isBreakable( string, pos );
}
bool KoTextFormatterBase::isBreakable( KoTextString *string, int pos ) const
{
//if (string->at(pos).nobreak)
// return FALSE;
return (pos < string->length()-1 && string->at(pos+1).softBreak);
}
void KoTextParag::insertLineStart( int index, KoTextParagLineStart *ls )
{
// This tests if we break at the same character in more than one line,
// i.e. there no space even for _one_ char in a given line.
// However this shouldn't happen, KoTextFormatter prevents it, otherwise
// we could loop forever (e.g. if one char is wider than the page...)
#ifndef NDEBUG
TQMap<int, KoTextParagLineStart*>::Iterator it;
if ( ( it = lineStarts.find( index ) ) == lineStarts.end() ) {
lineStarts.insert( index, ls );
} else {
kdWarning(32500) << "insertLineStart: there's already a line for char index=" << index << endl;
delete *it;
lineStarts.remove( it );
lineStarts.insert( index, ls );
}
#else // non-debug code, take the fast route
lineStarts.insert( index, ls );
#endif
}
/* Standard pagebreak algorithm using KoTextFlow::adjustFlow. Returns
the shift of the paragraphs bottom line.
*/
int KoTextFormatterBase::formatVertically( KoTextDocument* doc, KoTextParag* parag )
{
int oldHeight = parag->rect().height();
TQMap<int, KoTextParagLineStart*>& lineStarts = parag->lineStartList();
TQMap<int, KoTextParagLineStart*>::Iterator it = lineStarts.begin();
int h = doc->addMargins() ? parag->topMargin() : 0;
for ( ; it != lineStarts.end() ; ++it ) {
KoTextParagLineStart * ls = it.data();
ls->y = h;
KoTextStringChar *c = &parag->string()->at(it.key());
if ( c && c->customItem() && c->customItem()->ownLine() ) {
int h = c->customItem()->height;
c->customItem()->pageBreak( parag->rect().y() + ls->y + ls->baseLine - h, doc->flow() );
int delta = c->customItem()->height - h;
ls->h += delta;
if ( delta )
parag->setMovedDown( TRUE );
} else {
int shift = doc->flow()->adjustFlow( parag->rect().y() + ls->y, ls->w, ls->h );
ls->y += shift;
if ( shift )
parag->setMovedDown( TRUE );
}
h = ls->y + ls->h;
}
int m = parag->bottomMargin();
if ( parag->next() && doc && !doc->addMargins() )
m = TQMAX( m, parag->next()->topMargin() );
//if ( parag->next() && parag->next()->isLineBreak() )
// m = 0;
h += m;
parag->setHeight( h );
return h - oldHeight;
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
KoTextCustomItem::KoTextCustomItem( KoTextDocument *p )
: width(-1), height(0), parent(p), xpos(0), ypos(-1), parag(0)
{
m_deleted = false; // added for kotext
}
KoTextCustomItem::~KoTextCustomItem()
{
}
KoTextFlow::KoTextFlow()
{
w = 0;
leftItems.setAutoDelete( FALSE );
rightItems.setAutoDelete( FALSE );
}
KoTextFlow::~KoTextFlow()
{
}
void KoTextFlow::clear()
{
leftItems.clear();
rightItems.clear();
}
// Called by KoTextDocument::setWidth
void KoTextFlow::setWidth( int width )
{
w = width;
}
void KoTextFlow::adjustMargins( int, int, int, int&, int&, int& pageWidth, KoTextParag* )
{
pageWidth = w;
}
int KoTextFlow::adjustFlow( int /*y*/, int, int /*h*/ )
{
return 0;
}
void KoTextFlow::unregisterFloatingItem( KoTextCustomItem* item )
{
leftItems.removeRef( item );
rightItems.removeRef( item );
}
void KoTextFlow::registerFloatingItem( KoTextCustomItem* item )
{
if ( item->placement() == KoTextCustomItem::PlaceRight ) {
if ( !rightItems.contains( item ) )
rightItems.append( item );
} else if ( item->placement() == KoTextCustomItem::PlaceLeft &&
!leftItems.contains( item ) ) {
leftItems.append( item );
}
}
int KoTextFlow::availableHeight() const
{
return -1; // no limit
}
void KoTextFlow::drawFloatingItems( TQPainter* p, int cx, int cy, int cw, int ch, const TQColorGroup& cg, bool selected )
{
KoTextCustomItem *item;
for ( item = leftItems.first(); item; item = leftItems.next() ) {
if ( item->x() == -1 || item->y() == -1 )
continue;
item->draw( p, item->x(), item->y(), cx, cy, cw, ch, cg, selected );
}
for ( item = rightItems.first(); item; item = rightItems.next() ) {
if ( item->x() == -1 || item->y() == -1 )
continue;
item->draw( p, item->x(), item->y(), cx, cy, cw, ch, cg, selected );
}
}
//void KoTextFlow::setPageSize( int ps ) { pagesize = ps; }
bool KoTextFlow::isEmpty() { return leftItems.isEmpty() && rightItems.isEmpty(); }