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.
1593 lines
52 KiB
1593 lines
52 KiB
/* This file is part of the KDE project
|
|
Copyright (C) 2001-2005 David Faure <faure@kde.org>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "KoTextDocument.h"
|
|
#include "KoTextParag.h"
|
|
#include "KoTextZoomHandler.h"
|
|
#include "KoTextFormatter.h"
|
|
#include "KoTextFormat.h"
|
|
#include "KoParagCounter.h"
|
|
#include "KoTextCommand.h"
|
|
#include "KoOasisContext.h"
|
|
#include "KoVariable.h"
|
|
#include <KoXmlWriter.h>
|
|
#include <KoXmlNS.h>
|
|
#include <KoDom.h>
|
|
#include <kdebug.h>
|
|
#include <tdeversion.h>
|
|
#include <tqapplication.h>
|
|
#include <assert.h>
|
|
|
|
//#define DEBUG_PAINTING
|
|
|
|
//// Note that many methods are implemented in korichtext.cpp
|
|
//// Those are the ones that come from TQt, and that mostly work :)
|
|
|
|
KoTextDocument::KoTextDocument( KoTextZoomHandler *zoomHandler, KoTextFormatCollection *fc,
|
|
KoTextFormatter *formatter, bool createInitialParag )
|
|
: m_zoomHandler( zoomHandler ),
|
|
m_bDestroying( false ),
|
|
#ifdef TQTEXTTABLE_AVAILABLE
|
|
par( 0L /*we don't use parent documents */ ),
|
|
tc( 0 ),
|
|
#endif
|
|
tArray( 0 ), tStopWidth( 0 )
|
|
{
|
|
fCollection = fc;
|
|
init(); // see korichtext.cpp
|
|
|
|
m_drawingFlags = 0;
|
|
if ( !formatter )
|
|
formatter = new KoTextFormatter;
|
|
setFormatter( formatter );
|
|
|
|
setY( 0 );
|
|
setLeftMargin( 0 );
|
|
setRightMargin( 0 );
|
|
|
|
// Delete the KoTextParag created by KoTextDocument::init() if createInitialParag is false.
|
|
if ( !createInitialParag )
|
|
clear( false );
|
|
}
|
|
|
|
void KoTextDocument::init()
|
|
{
|
|
//pProcessor = 0;
|
|
useFC = TRUE;
|
|
pFormatter = 0;
|
|
fParag = 0;
|
|
m_pageBreakEnabled = false;
|
|
//minw = 0;
|
|
align = TQt::AlignAuto;
|
|
nSelections = 2;
|
|
|
|
underlLinks = TRUE;
|
|
backBrush = 0;
|
|
buf_pixmap = 0;
|
|
//nextDoubleBuffered = FALSE;
|
|
|
|
//if ( par )
|
|
// withoutDoubleBuffer = par->withoutDoubleBuffer;
|
|
// else
|
|
withoutDoubleBuffer = FALSE;
|
|
|
|
lParag = fParag = createParag( this, 0, 0 );
|
|
|
|
//cx = 0;
|
|
//cy = 2;
|
|
//if ( par )
|
|
cx = cy = 0;
|
|
//cw = 600; // huh?
|
|
//vw = 0;
|
|
flow_ = new KoTextFlow;
|
|
//flow_->setWidth( cw );
|
|
|
|
leftmargin = 0; // 4 in TQRT
|
|
rightmargin = 0; // 4 in TQRT
|
|
|
|
selectionColors[ Standard ] = TQApplication::palette().color( TQPalette::Active, TQColorGroup::Highlight );
|
|
selectionText[ Standard ] = TRUE;
|
|
assert( Standard < nSelections );
|
|
selectionText[ InputMethodPreedit ] = FALSE;
|
|
assert( InputMethodPreedit < nSelections );
|
|
commandHistory = new KoTextDocCommandHistory( 100 );
|
|
tStopWidth = formatCollection()->defaultFormat()->width( 'x' ) * 8;
|
|
}
|
|
|
|
KoTextDocument::~KoTextDocument()
|
|
{
|
|
//if ( par )
|
|
// par->removeChild( this );
|
|
//// kotext
|
|
m_bDestroying = true;
|
|
clear( false );
|
|
////
|
|
delete commandHistory;
|
|
delete flow_;
|
|
//if ( !par )
|
|
delete pFormatter;
|
|
delete fCollection;
|
|
//delete pProcessor;
|
|
delete buf_pixmap;
|
|
delete backBrush;
|
|
if ( tArray )
|
|
delete [] tArray;
|
|
}
|
|
|
|
void KoTextDocument::clear( bool createEmptyParag )
|
|
{
|
|
if ( flow_ )
|
|
flow_->clear();
|
|
while ( fParag ) {
|
|
KoTextParag *p = fParag->next();
|
|
fParag->string()->clear(); // avoid the "unregister custom items" code, not needed
|
|
delete fParag;
|
|
fParag = p;
|
|
}
|
|
fParag = lParag = 0;
|
|
if ( createEmptyParag )
|
|
fParag = lParag = createParag( this );
|
|
selections.clear();
|
|
customItems.clear();
|
|
}
|
|
|
|
/*
|
|
// Looks slow!
|
|
int KoTextDocument::widthUsed() const
|
|
{
|
|
KoTextParag *p = fParag;
|
|
int w = 0;
|
|
while ( p ) {
|
|
int a = p->alignment();
|
|
p->setAlignment( TQt::AlignLeft );
|
|
p->invalidate( 0 );
|
|
p->format();
|
|
w = TQMAX( w, p->rect().width() );
|
|
p->setAlignment( a );
|
|
p->invalidate( 0 );
|
|
p = p->next();
|
|
}
|
|
return w;
|
|
}
|
|
*/
|
|
|
|
int KoTextDocument::height() const
|
|
{
|
|
int h = 0;
|
|
if ( lParag )
|
|
h = lParag->rect().top() + lParag->rect().height() + 1;
|
|
//int fh = flow_->boundingRect().height();
|
|
//return TQMAX( h, fh );
|
|
return h;
|
|
}
|
|
|
|
|
|
KoTextParag *KoTextDocument::createParag( KoTextDocument *d, KoTextParag *pr, KoTextParag *nx, bool updateIds )
|
|
{
|
|
return new KoTextParag( d, pr, nx, updateIds );
|
|
}
|
|
|
|
void KoTextDocument::setPlainText( const TQString &text )
|
|
{
|
|
clear();
|
|
//preferRichText = FALSE;
|
|
//oTextValid = TRUE;
|
|
//oText = text;
|
|
|
|
int lastNl = 0;
|
|
int nl = text.find( '\n' );
|
|
if ( nl == -1 ) {
|
|
lParag = createParag( this, lParag, 0 );
|
|
if ( !fParag )
|
|
fParag = lParag;
|
|
TQString s = text;
|
|
if ( !s.isEmpty() ) {
|
|
if ( s[ (int)s.length() - 1 ] == '\r' )
|
|
s.remove( s.length() - 1, 1 );
|
|
lParag->append( s );
|
|
}
|
|
} else {
|
|
for (;;) {
|
|
lParag = createParag( this, lParag, 0 );
|
|
if ( !fParag )
|
|
fParag = lParag;
|
|
TQString s = text.mid( lastNl, nl - lastNl );
|
|
if ( !s.isEmpty() ) {
|
|
if ( s[ (int)s.length() - 1 ] == '\r' )
|
|
s.remove( s.length() - 1, 1 );
|
|
lParag->append( s );
|
|
}
|
|
if ( nl == 0xffffff )
|
|
break;
|
|
lastNl = nl + 1;
|
|
nl = text.find( '\n', nl + 1 );
|
|
if ( nl == -1 )
|
|
nl = 0xffffff;
|
|
}
|
|
}
|
|
if ( !lParag )
|
|
lParag = fParag = createParag( this, 0, 0 );
|
|
}
|
|
|
|
void KoTextDocument::setText( const TQString &text, const TQString & /*context*/ )
|
|
{
|
|
selections.clear();
|
|
setPlainText( text );
|
|
}
|
|
|
|
TQString KoTextDocument::plainText() const
|
|
{
|
|
TQString buffer;
|
|
TQString s;
|
|
KoTextParag *p = fParag;
|
|
while ( p ) {
|
|
s = p->string()->toString();
|
|
s.remove( s.length() - 1, 1 );
|
|
if ( p->next() )
|
|
s += "\n";
|
|
buffer += s;
|
|
p = p->next();
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
void KoTextDocument::invalidate()
|
|
{
|
|
KoTextParag *s = fParag;
|
|
while ( s ) {
|
|
s->invalidate( 0 );
|
|
s = s->next();
|
|
}
|
|
}
|
|
|
|
void KoTextDocument::informParagraphDeleted( KoTextParag* parag )
|
|
{
|
|
TQMap<int, KoTextDocumentSelection>::Iterator it = selections.begin();
|
|
for ( ; it != selections.end(); ++it )
|
|
{
|
|
if ( (*it).startCursor.parag() == parag ) {
|
|
if ( parag->prev() ) {
|
|
KoTextParag* prevP = parag->prev();
|
|
(*it).startCursor.setParag( prevP );
|
|
(*it).startCursor.setIndex( prevP->length()-1 );
|
|
} else
|
|
(*it).startCursor.setParag( parag->next() ); // sets index to 0
|
|
}
|
|
if ( (*it).endCursor.parag() == parag ) {
|
|
if ( parag->prev() ) {
|
|
KoTextParag* prevP = parag->prev();
|
|
(*it).endCursor.setParag( prevP );
|
|
(*it).endCursor.setIndex( prevP->length()-1 );
|
|
} else
|
|
(*it).endCursor.setParag( parag->next() ); // sets index to 0
|
|
}
|
|
}
|
|
emit paragraphDeleted( parag );
|
|
}
|
|
|
|
void KoTextDocument::selectionStart( int id, int ¶gId, int &index )
|
|
{
|
|
TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
|
|
if ( it == selections.end() )
|
|
return;
|
|
KoTextDocumentSelection &sel = *it;
|
|
paragId = !sel.swapped ? sel.startCursor.parag()->paragId() : sel.endCursor.parag()->paragId();
|
|
index = !sel.swapped ? sel.startCursor.index() : sel.endCursor.index();
|
|
}
|
|
|
|
KoTextCursor KoTextDocument::selectionStartCursor( int id)
|
|
{
|
|
TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
|
|
if ( it == selections.end() )
|
|
return KoTextCursor( this );
|
|
KoTextDocumentSelection &sel = *it;
|
|
if ( sel.swapped )
|
|
return sel.endCursor;
|
|
return sel.startCursor;
|
|
}
|
|
|
|
KoTextCursor KoTextDocument::selectionEndCursor( int id)
|
|
{
|
|
TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
|
|
if ( it == selections.end() )
|
|
return KoTextCursor( this );
|
|
KoTextDocumentSelection &sel = *it;
|
|
if ( !sel.swapped )
|
|
return sel.endCursor;
|
|
return sel.startCursor;
|
|
}
|
|
|
|
void KoTextDocument::selectionEnd( int id, int ¶gId, int &index )
|
|
{
|
|
TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
|
|
if ( it == selections.end() )
|
|
return;
|
|
KoTextDocumentSelection &sel = *it;
|
|
paragId = sel.swapped ? sel.startCursor.parag()->paragId() : sel.endCursor.parag()->paragId();
|
|
index = sel.swapped ? sel.startCursor.index() : sel.endCursor.index();
|
|
}
|
|
|
|
bool KoTextDocument::isSelectionSwapped( int id )
|
|
{
|
|
TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
|
|
if ( it == selections.end() )
|
|
return false;
|
|
KoTextDocumentSelection &sel = *it;
|
|
return sel.swapped;
|
|
}
|
|
|
|
KoTextParag *KoTextDocument::selectionStart( int id )
|
|
{
|
|
TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
|
|
if ( it == selections.end() )
|
|
return 0;
|
|
KoTextDocumentSelection &sel = *it;
|
|
if ( sel.startCursor.parag()->paragId() < sel.endCursor.parag()->paragId() )
|
|
return sel.startCursor.parag();
|
|
return sel.endCursor.parag();
|
|
}
|
|
|
|
KoTextParag *KoTextDocument::selectionEnd( int id )
|
|
{
|
|
TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
|
|
if ( it == selections.end() )
|
|
return 0;
|
|
KoTextDocumentSelection &sel = *it;
|
|
if ( sel.startCursor.parag()->paragId() > sel.endCursor.parag()->paragId() )
|
|
return sel.startCursor.parag();
|
|
return sel.endCursor.parag();
|
|
}
|
|
|
|
void KoTextDocument::addSelection( int id )
|
|
{
|
|
nSelections = TQMAX( nSelections, id + 1 );
|
|
}
|
|
|
|
static void setSelectionEndHelper( int id, KoTextDocumentSelection &sel, KoTextCursor &start, KoTextCursor &end )
|
|
{
|
|
KoTextCursor c1 = start;
|
|
KoTextCursor c2 = end;
|
|
if ( sel.swapped ) {
|
|
c1 = end;
|
|
c2 = start;
|
|
}
|
|
|
|
c1.parag()->removeSelection( id );
|
|
c2.parag()->removeSelection( id );
|
|
if ( c1.parag() != c2.parag() ) {
|
|
c1.parag()->setSelection( id, c1.index(), c1.parag()->length() - 1 );
|
|
c2.parag()->setSelection( id, 0, c2.index() );
|
|
} else {
|
|
c1.parag()->setSelection( id, TQMIN( c1.index(), c2.index() ), TQMAX( c1.index(), c2.index() ) );
|
|
}
|
|
|
|
sel.startCursor = start;
|
|
sel.endCursor = end;
|
|
if ( sel.startCursor.parag() == sel.endCursor.parag() )
|
|
sel.swapped = sel.startCursor.index() > sel.endCursor.index();
|
|
}
|
|
|
|
bool KoTextDocument::setSelectionEnd( int id, KoTextCursor *cursor )
|
|
{
|
|
TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
|
|
if ( it == selections.end() )
|
|
return FALSE;
|
|
KoTextDocumentSelection &sel = *it;
|
|
|
|
KoTextCursor start = sel.startCursor;
|
|
KoTextCursor end = *cursor;
|
|
|
|
if ( start == end ) {
|
|
removeSelection( id );
|
|
setSelectionStart( id, cursor );
|
|
return TRUE;
|
|
}
|
|
|
|
if ( sel.endCursor.parag() == end.parag() ) {
|
|
setSelectionEndHelper( id, sel, start, end );
|
|
return TRUE;
|
|
}
|
|
|
|
bool inSelection = FALSE;
|
|
KoTextCursor c( this );
|
|
KoTextCursor tmp = sel.startCursor;
|
|
if ( sel.swapped )
|
|
tmp = sel.endCursor;
|
|
KoTextCursor tmp2 = *cursor;
|
|
c.setParag( tmp.parag()->paragId() < tmp2.parag()->paragId() ? tmp.parag() : tmp2.parag() );
|
|
KoTextCursor old;
|
|
bool hadStart = FALSE;
|
|
bool hadEnd = FALSE;
|
|
bool hadStartParag = FALSE;
|
|
bool hadEndParag = FALSE;
|
|
bool hadOldStart = FALSE;
|
|
bool hadOldEnd = FALSE;
|
|
bool leftSelection = FALSE;
|
|
sel.swapped = FALSE;
|
|
for ( ;; ) {
|
|
if ( c == start )
|
|
hadStart = TRUE;
|
|
if ( c == end )
|
|
hadEnd = TRUE;
|
|
if ( c.parag() == start.parag() )
|
|
hadStartParag = TRUE;
|
|
if ( c.parag() == end.parag() )
|
|
hadEndParag = TRUE;
|
|
if ( c == sel.startCursor )
|
|
hadOldStart = TRUE;
|
|
if ( c == sel.endCursor )
|
|
hadOldEnd = TRUE;
|
|
|
|
if ( !sel.swapped &&
|
|
( hadEnd && !hadStart ||
|
|
hadEnd && hadStart && start.parag() == end.parag() && start.index() > end.index() ) )
|
|
sel.swapped = TRUE;
|
|
|
|
if ( c == end && hadStartParag ||
|
|
c == start && hadEndParag ) {
|
|
KoTextCursor tmp = c;
|
|
if ( tmp.parag() != c.parag() ) {
|
|
int sstart = tmp.parag()->selectionStart( id );
|
|
tmp.parag()->removeSelection( id );
|
|
tmp.parag()->setSelection( id, sstart, tmp.index() );
|
|
}
|
|
}
|
|
|
|
if ( inSelection &&
|
|
( c == end && hadStart || c == start && hadEnd ) )
|
|
leftSelection = TRUE;
|
|
else if ( !leftSelection && !inSelection && ( hadStart || hadEnd ) )
|
|
inSelection = TRUE;
|
|
|
|
bool noSelectionAnymore = hadOldStart && hadOldEnd && leftSelection && !inSelection && !c.parag()->hasSelection( id ) && c.atParagEnd();
|
|
c.parag()->removeSelection( id );
|
|
if ( inSelection ) {
|
|
if ( c.parag() == start.parag() && start.parag() == end.parag() ) {
|
|
c.parag()->setSelection( id, TQMIN( start.index(), end.index() ), TQMAX( start.index(), end.index() ) );
|
|
} else if ( c.parag() == start.parag() && !hadEndParag ) {
|
|
c.parag()->setSelection( id, start.index(), c.parag()->length() - 1 );
|
|
} else if ( c.parag() == end.parag() && !hadStartParag ) {
|
|
c.parag()->setSelection( id, end.index(), c.parag()->length() - 1 );
|
|
} else if ( c.parag() == end.parag() && hadEndParag ) {
|
|
c.parag()->setSelection( id, 0, end.index() );
|
|
} else if ( c.parag() == start.parag() && hadStartParag ) {
|
|
c.parag()->setSelection( id, 0, start.index() );
|
|
} else {
|
|
c.parag()->setSelection( id, 0, c.parag()->length() - 1 );
|
|
}
|
|
}
|
|
|
|
if ( leftSelection )
|
|
inSelection = FALSE;
|
|
|
|
old = c;
|
|
c.gotoNextLetter();
|
|
if ( old == c || noSelectionAnymore )
|
|
break;
|
|
}
|
|
|
|
if ( !sel.swapped )
|
|
sel.startCursor.parag()->setSelection( id, sel.startCursor.index(), sel.startCursor.parag()->length() - 1 );
|
|
|
|
sel.startCursor = start;
|
|
sel.endCursor = end;
|
|
if ( sel.startCursor.parag() == sel.endCursor.parag() )
|
|
sel.swapped = sel.startCursor.index() > sel.endCursor.index();
|
|
|
|
setSelectionEndHelper( id, sel, start, end );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void KoTextDocument::selectAll( int id )
|
|
{
|
|
removeSelection( id );
|
|
|
|
KoTextDocumentSelection sel;
|
|
sel.swapped = FALSE;
|
|
KoTextCursor c( this );
|
|
|
|
c.setParag( fParag );
|
|
c.setIndex( 0 );
|
|
sel.startCursor = c;
|
|
|
|
c.setParag( lParag );
|
|
c.setIndex( lParag->length() - 1 );
|
|
sel.endCursor = c;
|
|
|
|
KoTextParag *p = fParag;
|
|
while ( p ) {
|
|
p->setSelection( id, 0, p->length() - 1 );
|
|
#ifdef TQTEXTTABLE_AVAILABLE
|
|
for ( int i = 0; i < (int)p->length(); ++i ) {
|
|
if ( p->at( i )->isCustom() && p->at( i )->customItem()->isNested() ) {
|
|
KoTextTable *t = (KoTextTable*)p->at( i )->customItem();
|
|
TQPtrList<KoTextTableCell> tableCells = t->tableCells();
|
|
for ( KoTextTableCell *c = tableCells.first(); c; c = tableCells.next() )
|
|
c->richText()->selectAll( id );
|
|
}
|
|
}
|
|
#endif
|
|
p = p->next();
|
|
}
|
|
|
|
selections.insert( id, sel );
|
|
}
|
|
|
|
bool KoTextDocument::removeSelection( int id )
|
|
{
|
|
TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
|
|
if ( it == selections.end() )
|
|
return FALSE;
|
|
|
|
KoTextDocumentSelection &sel = *it;
|
|
|
|
KoTextCursor c( this );
|
|
KoTextCursor tmp = sel.startCursor;
|
|
if ( sel.swapped )
|
|
tmp = sel.endCursor;
|
|
c.setParag( tmp.parag() );
|
|
KoTextCursor old;
|
|
bool hadStart = FALSE;
|
|
bool hadEnd = FALSE;
|
|
KoTextParag *lastParag = 0;
|
|
bool leftSelection = FALSE;
|
|
bool inSelection = FALSE;
|
|
sel.swapped = FALSE;
|
|
for ( ;; ) {
|
|
if ( !hadStart && c.parag() == sel.startCursor.parag() )
|
|
hadStart = TRUE;
|
|
if ( !hadEnd && c.parag() == sel.endCursor.parag() )
|
|
hadEnd = TRUE;
|
|
|
|
if ( !leftSelection && !inSelection && ( c.parag() == sel.startCursor.parag() || c.parag() == sel.endCursor.parag() ) )
|
|
inSelection = TRUE;
|
|
|
|
if ( inSelection &&
|
|
( c == sel.endCursor && hadStart || c == sel.startCursor && hadEnd ) ) {
|
|
leftSelection = TRUE;
|
|
inSelection = FALSE;
|
|
}
|
|
|
|
bool noSelectionAnymore = leftSelection && !inSelection && !c.parag()->hasSelection( id ) && c.atParagEnd();
|
|
|
|
if ( lastParag != c.parag() )
|
|
c.parag()->removeSelection( id );
|
|
|
|
old = c;
|
|
lastParag = c.parag();
|
|
c.gotoNextLetter();
|
|
if ( old == c || noSelectionAnymore )
|
|
break;
|
|
}
|
|
|
|
selections.remove( id );
|
|
return TRUE;
|
|
}
|
|
|
|
TQString KoTextDocument::selectedText( int id, bool withCustom ) const
|
|
{
|
|
// ######## TODO: look at textFormat() and return rich text or plain text (like the text() method!)
|
|
TQMap<int, KoTextDocumentSelection>::ConstIterator it = selections.find( id );
|
|
if ( it == selections.end() )
|
|
return TQString();
|
|
|
|
KoTextDocumentSelection sel = *it;
|
|
|
|
|
|
KoTextCursor c1 = sel.startCursor;
|
|
KoTextCursor c2 = sel.endCursor;
|
|
if ( sel.swapped ) {
|
|
c2 = sel.startCursor;
|
|
c1 = sel.endCursor;
|
|
}
|
|
|
|
if ( c1.parag() == c2.parag() ) {
|
|
TQString s;
|
|
KoTextParag *p = c1.parag();
|
|
int end = c2.index();
|
|
if ( p->at( TQMAX( 0, end - 1 ) )->isCustom() )
|
|
++end;
|
|
if ( !withCustom || !p->customItems() ) {
|
|
s += p->string()->toString().mid( c1.index(), end - c1.index() );
|
|
} else {
|
|
for ( int i = c1.index(); i < end; ++i ) {
|
|
if ( p->at( i )->isCustom() ) {
|
|
#ifdef TQTEXTTABLE_AVAILABLE
|
|
if ( p->at( i )->customItem()->isNested() ) {
|
|
s += "\n";
|
|
KoTextTable *t = (KoTextTable*)p->at( i )->customItem();
|
|
TQPtrList<KoTextTableCell> cells = t->tableCells();
|
|
for ( KoTextTableCell *c = cells.first(); c; c = cells.next() )
|
|
s += c->richText()->plainText() + "\n";
|
|
s += "\n";
|
|
}
|
|
#endif
|
|
} else {
|
|
s += p->at( i )->c;
|
|
}
|
|
s += "\n";
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
TQString s;
|
|
KoTextParag *p = c1.parag();
|
|
int start = c1.index();
|
|
while ( p ) {
|
|
int end = p == c2.parag() ? c2.index() : p->length() - 1;
|
|
if ( p == c2.parag() && p->at( TQMAX( 0, end - 1 ) )->isCustom() )
|
|
++end;
|
|
if ( !withCustom || !p->customItems() ) {
|
|
s += p->string()->toString().mid( start, end - start );
|
|
if ( p != c2.parag() )
|
|
s += "\n";
|
|
} else {
|
|
for ( int i = start; i < end; ++i ) {
|
|
if ( p->at( i )->isCustom() ) {
|
|
#ifdef TQTEXTTABLE_AVAILABLE
|
|
if ( p->at( i )->customItem()->isNested() ) {
|
|
s += "\n";
|
|
KoTextTable *t = (KoTextTable*)p->at( i )->customItem();
|
|
TQPtrList<KoTextTableCell> cells = t->tableCells();
|
|
for ( KoTextTableCell *c = cells.first(); c; c = cells.next() )
|
|
s += c->richText()->plainText() + "\n";
|
|
s += "\n";
|
|
}
|
|
#endif
|
|
} else {
|
|
s += p->at( i )->c;
|
|
}
|
|
s += "\n";
|
|
}
|
|
}
|
|
start = 0;
|
|
if ( p == c2.parag() )
|
|
break;
|
|
p = p->next();
|
|
}
|
|
return s;
|
|
}
|
|
|
|
TQString KoTextDocument::copySelection( KoXmlWriter& writer, KoSavingContext& context, int selectionId )
|
|
{
|
|
KoTextCursor c1 = selectionStartCursor( selectionId );
|
|
KoTextCursor c2 = selectionEndCursor( selectionId );
|
|
TQString text;
|
|
if ( c1.parag() == c2.parag() )
|
|
{
|
|
text = c1.parag()->toString( c1.index(), c2.index() - c1.index() );
|
|
|
|
c1.parag()->saveOasis( writer, context, c1.index(), c2.index()-1, true );
|
|
}
|
|
else
|
|
{
|
|
text += c1.parag()->toString( c1.index() ) + "\n";
|
|
|
|
c1.parag()->saveOasis( writer, context, c1.index(), c1.parag()->length()-2, true );
|
|
KoTextParag *p = c1.parag()->next();
|
|
while ( p && p != c2.parag() ) {
|
|
text += p->toString() + "\n";
|
|
p->saveOasis( writer, context, 0, p->length()-2, true );
|
|
p = p->next();
|
|
}
|
|
text += c2.parag()->toString( 0, c2.index() );
|
|
c2.parag()->saveOasis( writer, context, 0, c2.index() - 1, true );
|
|
}
|
|
return text;
|
|
}
|
|
|
|
void KoTextDocument::setFormat( int id, const KoTextFormat *f, int flags )
|
|
{
|
|
TQMap<int, KoTextDocumentSelection>::ConstIterator it = selections.find( id );
|
|
if ( it == selections.end() )
|
|
return;
|
|
|
|
KoTextDocumentSelection sel = *it;
|
|
|
|
KoTextCursor c1 = sel.startCursor;
|
|
KoTextCursor c2 = sel.endCursor;
|
|
if ( sel.swapped ) {
|
|
c2 = sel.startCursor;
|
|
c1 = sel.endCursor;
|
|
}
|
|
|
|
if ( c1.parag() == c2.parag() ) {
|
|
c1.parag()->setFormat( c1.index(), c2.index() - c1.index(), f, TRUE, flags );
|
|
return;
|
|
}
|
|
|
|
c1.parag()->setFormat( c1.index(), c1.parag()->length() - c1.index(), f, TRUE, flags );
|
|
KoTextParag *p = c1.parag()->next();
|
|
while ( p && p != c2.parag() ) {
|
|
p->setFormat( 0, p->length(), f, TRUE, flags );
|
|
p = p->next();
|
|
}
|
|
c2.parag()->setFormat( 0, c2.index(), f, TRUE, flags );
|
|
}
|
|
|
|
/*void KoTextDocument::copySelectedText( int id )
|
|
{
|
|
#ifndef TQT_NO_CLIPBOARD
|
|
if ( !hasSelection( id ) )
|
|
return;
|
|
|
|
TQApplication::clipboard()->setText( selectedText( id ) );
|
|
#endif
|
|
}*/
|
|
|
|
void KoTextDocument::removeSelectedText( int id, KoTextCursor *cursor )
|
|
{
|
|
TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
|
|
if ( it == selections.end() )
|
|
return;
|
|
|
|
KoTextDocumentSelection sel = *it;
|
|
|
|
KoTextCursor c1 = sel.startCursor;
|
|
KoTextCursor c2 = sel.endCursor;
|
|
if ( sel.swapped ) {
|
|
c2 = sel.startCursor;
|
|
c1 = sel.endCursor;
|
|
}
|
|
|
|
*cursor = c1;
|
|
removeSelection( id );
|
|
|
|
if ( c1.parag() == c2.parag() ) {
|
|
c1.parag()->remove( c1.index(), c2.index() - c1.index() );
|
|
return;
|
|
}
|
|
|
|
// ## TQt has a strange setValid/isValid on TQTextCursor, only used in the few lines below !?!?
|
|
bool valid = true;
|
|
if ( c1.parag() == fParag && c1.index() == 0 &&
|
|
c2.parag() == lParag && c2.index() == lParag->length() - 1 )
|
|
valid = FALSE;
|
|
|
|
bool didGoLeft = FALSE;
|
|
if ( c1.index() == 0 && c1.parag() != fParag ) {
|
|
cursor->gotoPreviousLetter();
|
|
if ( valid )
|
|
didGoLeft = TRUE;
|
|
}
|
|
|
|
c1.parag()->remove( c1.index(), c1.parag()->length() - 1 - c1.index() );
|
|
KoTextParag *p = c1.parag()->next();
|
|
int dy = 0;
|
|
KoTextParag *tmp;
|
|
while ( p && p != c2.parag() ) {
|
|
tmp = p->next();
|
|
dy -= p->rect().height();
|
|
//emit paragraphDeleted( p ); // done by KoTextParag dtor already
|
|
delete p;
|
|
p = tmp;
|
|
}
|
|
c2.parag()->remove( 0, c2.index() );
|
|
while ( p ) {
|
|
p->move( dy );
|
|
//// kotext
|
|
if ( p->paragLayout().counter )
|
|
p->paragLayout().counter->invalidate();
|
|
////
|
|
p->invalidate( 0 );
|
|
//p->setEndState( -1 );
|
|
p = p->next();
|
|
}
|
|
|
|
c1.parag()->join( c2.parag() );
|
|
|
|
if ( didGoLeft )
|
|
cursor->gotoNextLetter();
|
|
}
|
|
|
|
void KoTextDocument::addCommand( KoTextDocCommand *cmd )
|
|
{
|
|
commandHistory->addCommand( cmd );
|
|
}
|
|
|
|
KoTextCursor *KoTextDocument::undo( KoTextCursor *c )
|
|
{
|
|
return commandHistory->undo( c );
|
|
}
|
|
|
|
KoTextCursor *KoTextDocument::redo( KoTextCursor *c )
|
|
{
|
|
return commandHistory->redo( c );
|
|
}
|
|
|
|
bool KoTextDocument::find( const TQString &expr, bool cs, bool wo, bool forward,
|
|
int *parag, int *index, KoTextCursor *cursor )
|
|
{
|
|
KoTextParag *p = forward ? fParag : lParag;
|
|
if ( parag )
|
|
p = paragAt( *parag );
|
|
else if ( cursor )
|
|
p = cursor->parag();
|
|
bool first = TRUE;
|
|
|
|
while ( p ) {
|
|
TQString s = p->string()->toString();
|
|
s.remove( s.length() - 1, 1 ); // get rid of trailing space
|
|
int start = forward ? 0 : s.length() - 1;
|
|
if ( first && index )
|
|
start = *index;
|
|
else if ( first )
|
|
start = cursor->index();
|
|
if ( !forward && first ) {
|
|
start -= expr.length() + 1;
|
|
if ( start < 0 ) {
|
|
first = FALSE;
|
|
p = p->prev();
|
|
continue;
|
|
}
|
|
}
|
|
first = FALSE;
|
|
|
|
for ( ;; ) {
|
|
int res = forward ? s.find( expr, start, cs ) : s.findRev( expr, start, cs );
|
|
if ( res == -1 )
|
|
break;
|
|
|
|
bool ok = TRUE;
|
|
if ( wo ) {
|
|
int end = res + expr.length();
|
|
if ( ( res == 0 || s[ res - 1 ].isSpace() || s[ res - 1 ].isPunct() ) &&
|
|
( end == (int)s.length() || s[ end ].isSpace() || s[ end ].isPunct() ) )
|
|
ok = TRUE;
|
|
else
|
|
ok = FALSE;
|
|
}
|
|
if ( ok ) {
|
|
cursor->setParag( p );
|
|
cursor->setIndex( res );
|
|
setSelectionStart( Standard, cursor );
|
|
cursor->setIndex( res + expr.length() );
|
|
setSelectionEnd( Standard, cursor );
|
|
if ( parag )
|
|
*parag = p->paragId();
|
|
if ( index )
|
|
*index = res;
|
|
return TRUE;
|
|
}
|
|
if ( forward ) {
|
|
start = res + 1;
|
|
} else {
|
|
if ( res == 0 )
|
|
break;
|
|
start = res - 1;
|
|
}
|
|
}
|
|
p = forward ? p->next() : p->prev();
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bool KoTextDocument::inSelection( int selId, const TQPoint &pos ) const
|
|
{
|
|
TQMap<int, KoTextDocumentSelection>::ConstIterator it = selections.find( selId );
|
|
if ( it == selections.end() )
|
|
return FALSE;
|
|
|
|
KoTextDocumentSelection sel = *it;
|
|
KoTextParag *startParag = sel.startCursor.parag();
|
|
KoTextParag *endParag = sel.endCursor.parag();
|
|
if ( sel.startCursor.parag() == sel.endCursor.parag() &&
|
|
sel.startCursor.parag()->selectionStart( selId ) == sel.endCursor.parag()->selectionEnd( selId ) )
|
|
return FALSE;
|
|
if ( sel.endCursor.parag()->paragId() < sel.startCursor.parag()->paragId() ) {
|
|
endParag = sel.startCursor.parag();
|
|
startParag = sel.endCursor.parag();
|
|
}
|
|
|
|
KoTextParag *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 )->width /*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;
|
|
}
|
|
|
|
TQPixmap *KoTextDocument::bufferPixmap( const TQSize &s )
|
|
{
|
|
if ( !buf_pixmap ) {
|
|
int w = TQABS( s.width() );
|
|
int h = TQABS( s.height() );
|
|
buf_pixmap = new TQPixmap( w, h );
|
|
} else {
|
|
if ( buf_pixmap->width() < s.width() ||
|
|
buf_pixmap->height() < s.height() ) {
|
|
buf_pixmap->resize( TQMAX( s.width(), buf_pixmap->width() ),
|
|
TQMAX( s.height(), buf_pixmap->height() ) );
|
|
}
|
|
}
|
|
|
|
return buf_pixmap;
|
|
}
|
|
|
|
void KoTextDocument::registerCustomItem( KoTextCustomItem *i, KoTextParag *p )
|
|
{
|
|
if ( i && i->placement() != KoTextCustomItem::PlaceInline )
|
|
flow_->registerFloatingItem( i );
|
|
p->registerFloatingItem( i );
|
|
i->setParagraph( p );
|
|
//kdDebug(32500) << "KoTextDocument::registerCustomItem " << (void*)i << endl;
|
|
customItems.append( i );
|
|
}
|
|
|
|
void KoTextDocument::unregisterCustomItem( KoTextCustomItem *i, KoTextParag *p )
|
|
{
|
|
flow_->unregisterFloatingItem( i );
|
|
p->unregisterFloatingItem( i );
|
|
i->setParagraph( 0 );
|
|
customItems.removeRef( i );
|
|
}
|
|
|
|
int KoTextDocument::length() const
|
|
{
|
|
int l = 0;
|
|
KoTextParag *p = fParag;
|
|
while ( p ) {
|
|
l += p->length() - 1; // don't count trailing space
|
|
p = p->next();
|
|
}
|
|
return l;
|
|
}
|
|
|
|
bool KoTextDocument::visitSelection( int selectionId, KoParagVisitor* visitor, bool forward )
|
|
{
|
|
KoTextCursor c1 = selectionStartCursor( selectionId );
|
|
KoTextCursor c2 = selectionEndCursor( selectionId );
|
|
if ( c1 == c2 )
|
|
return true;
|
|
return visitFromTo( c1.parag(), c1.index(), c2.parag(), c2.index(), visitor, forward );
|
|
}
|
|
|
|
bool KoTextDocument::hasSelection( int id, bool visible ) const
|
|
{
|
|
return ( selections.find( id ) != selections.end() &&
|
|
( !visible ||
|
|
( (KoTextDocument*)this )->selectionStartCursor( id ) !=
|
|
( (KoTextDocument*)this )->selectionEndCursor( id ) ) );
|
|
}
|
|
|
|
void KoTextDocument::setSelectionStart( int id, KoTextCursor *cursor )
|
|
{
|
|
KoTextDocumentSelection sel;
|
|
sel.startCursor = *cursor;
|
|
sel.endCursor = *cursor;
|
|
sel.swapped = FALSE;
|
|
selections[ id ] = sel;
|
|
}
|
|
|
|
KoTextParag *KoTextDocument::paragAt( int i ) const
|
|
{
|
|
KoTextParag *s = fParag;
|
|
while ( s ) {
|
|
if ( s->paragId() == i )
|
|
return s;
|
|
s = s->next();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool KoTextDocument::visitDocument( KoParagVisitor *visitor, bool forward )
|
|
{
|
|
return visitFromTo( firstParag(), 0, lastParag(), lastParag()->length()-1, visitor, forward );
|
|
}
|
|
|
|
bool KoTextDocument::visitFromTo( KoTextParag *firstParag, int firstIndex, KoTextParag* lastParag, int lastIndex, KoParagVisitor* visitor, bool forw )
|
|
{
|
|
if ( firstParag == lastParag )
|
|
{
|
|
return visitor->visit( firstParag, firstIndex, lastIndex );
|
|
}
|
|
else
|
|
{
|
|
bool ret = true;
|
|
if ( forw )
|
|
{
|
|
// the -1 is for the trailing space
|
|
ret = visitor->visit( firstParag, firstIndex, firstParag->length() - 1 );
|
|
if (!ret) return false;
|
|
}
|
|
else
|
|
{
|
|
ret = visitor->visit( lastParag, 0, lastIndex );
|
|
if (!ret) return false;
|
|
}
|
|
|
|
KoTextParag* currentParag = forw ? firstParag->next() : lastParag->prev();
|
|
KoTextParag * endParag = forw ? lastParag : firstParag;
|
|
while ( currentParag && currentParag != endParag )
|
|
{
|
|
ret = visitor->visit( currentParag, 0, currentParag->length() - 1 );
|
|
if (!ret) return false;
|
|
currentParag = forw ? currentParag->next() : currentParag->prev();
|
|
}
|
|
Q_ASSERT( currentParag );
|
|
Q_ASSERT( endParag == currentParag );
|
|
if ( forw )
|
|
ret = visitor->visit( lastParag, 0, lastIndex );
|
|
else
|
|
ret = visitor->visit( currentParag, firstIndex, currentParag->length() - 1 );
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static bool is_printer( TQPainter *p )
|
|
{
|
|
return p && p->device() && p->device()->devType() == TQInternal::Printer;
|
|
}
|
|
|
|
KoTextParag *KoTextDocument::drawWYSIWYG( TQPainter *p, int cx, int cy, int cw, int ch, const TQColorGroup &cg,
|
|
KoTextZoomHandler* zoomHandler, bool onlyChanged,
|
|
bool drawCursor, KoTextCursor *cursor,
|
|
bool resetChanged, uint drawingFlags )
|
|
{
|
|
m_drawingFlags = drawingFlags;
|
|
// We need to draw without double-buffering if
|
|
// 1) printing (to send text and not bitmaps to the printer)
|
|
// 2) drawing a transparent embedded document
|
|
//
|
|
if ( is_printer( p ) || ( drawingFlags & TransparentBackground ) ) {
|
|
// This stuff relies on doLayout()... simpler to just test for Printer.
|
|
// If someone understand doLayout() please tell me (David)
|
|
/*if ( isWithoutDoubleBuffer() || par && par->withoutDoubleBuffer ) { */
|
|
//setWithoutDoubleBuffer( TRUE );
|
|
TQRect crect( cx, cy, cw, ch );
|
|
drawWithoutDoubleBuffer( p, crect, cg, zoomHandler );
|
|
return 0;
|
|
}
|
|
//setWithoutDoubleBuffer( FALSE );
|
|
|
|
if ( !firstParag() )
|
|
return 0;
|
|
|
|
KoTextParag *lastFormatted = 0;
|
|
KoTextParag *parag = firstParag();
|
|
|
|
TQPixmap *doubleBuffer = 0;
|
|
TQPainter painter;
|
|
// All the coordinates in this method are in view pixels
|
|
TQRect crect( cx, cy, cw, ch );
|
|
Q_ASSERT( ch > 0 );
|
|
#ifdef DEBUG_PAINTING
|
|
kdDebug(32500) << "\nKoTextDocument::drawWYSIWYG crect=" << crect << endl;
|
|
#endif
|
|
|
|
// Space above first parag
|
|
TQRect pixelRect = parag->pixelRect( zoomHandler );
|
|
if ( isPageBreakEnabled() && parag && cy <= pixelRect.y() && pixelRect.y() > 0 ) {
|
|
TQRect r( 0, 0,
|
|
zoomHandler->layoutUnitToPixelX( parag->document()->x() + parag->document()->width() ),
|
|
pixelRect.y() );
|
|
r &= crect;
|
|
if ( !r.isEmpty() ) {
|
|
#ifdef DEBUG_PAINTING
|
|
kdDebug(32500) << " drawWYSIWYG: space above first parag: " << r << " (pixels)" << endl;
|
|
#endif
|
|
p->fillRect( r, cg.brush( TQColorGroup::Base ) );
|
|
}
|
|
}
|
|
|
|
while ( parag ) {
|
|
lastFormatted = parag;
|
|
if ( !parag->isValid() )
|
|
parag->format();
|
|
|
|
TQRect ir = parag->pixelRect( zoomHandler );
|
|
#ifdef DEBUG_PAINTING
|
|
kdDebug(32500) << " drawWYSIWYG: ir=" << ir << endl;
|
|
#endif
|
|
if ( isPageBreakEnabled() && parag->next() && ( drawingFlags & TransparentBackground ) == 0 )
|
|
{
|
|
int nexty = parag->next()->pixelRect(zoomHandler).y();
|
|
// Test ir.y+ir.height, which is the first pixel _under_ the parag
|
|
// (as opposed ir.bottom() which is the last pixel of the parag)
|
|
if ( ir.y() + ir.height() < nexty ) {
|
|
TQRect r( 0, ir.y() + ir.height(),
|
|
zoomHandler->layoutUnitToPixelX( parag->document()->x() + parag->document()->width() ),
|
|
nexty - ( ir.y() + ir.height() ) );
|
|
r &= crect;
|
|
if ( !r.isEmpty() )
|
|
{
|
|
#ifdef DEBUG_PAINTING
|
|
kdDebug(32500) << " drawWYSIWYG: space between parag " << parag->paragId() << " and " << parag->next()->paragId() << " : " << r << " (pixels)" << endl;
|
|
#endif
|
|
p->fillRect( r, cg.brush( TQColorGroup::Base ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !ir.intersects( crect ) ) {
|
|
// Paragraph is not in the crect - but let's check if the area on its right is.
|
|
ir.setWidth( zoomHandler->layoutUnitToPixelX( parag->document()->width() ) );
|
|
if ( ir.intersects( crect ) && ( drawingFlags & TransparentBackground ) == 0 )
|
|
p->fillRect( ir.intersect( crect ), cg.brush( TQColorGroup::Base ) );
|
|
if ( ir.y() > cy + ch ) {
|
|
goto floating;
|
|
}
|
|
}
|
|
else if ( parag->hasChanged() || !onlyChanged ) {
|
|
// lineChanged() only makes sense if we're drawing with onlyChanged=true
|
|
// otherwise, call setChanged() to make sure we'll paint it all (lineChanged=-1).
|
|
// (this avoids having to send onlyChanged to drawParagWYSIWYG)
|
|
if ( !onlyChanged && parag->lineChanged() > 0 )
|
|
parag->setChanged( false );
|
|
drawParagWYSIWYG( p, parag, cx, cy, cw, ch, doubleBuffer, cg,
|
|
zoomHandler, drawCursor, cursor, resetChanged, drawingFlags );
|
|
}
|
|
parag = parag->next();
|
|
}
|
|
|
|
parag = lastParag();
|
|
|
|
floating:
|
|
pixelRect = parag->pixelRect(zoomHandler);
|
|
int docheight = zoomHandler->layoutUnitToPixelY( parag->document()->height() );
|
|
if ( pixelRect.y() + pixelRect.height() < docheight ) {
|
|
int docwidth = zoomHandler->layoutUnitToPixelX( parag->document()->width() );
|
|
if ( ( drawingFlags & TransparentBackground ) == 0 ) {
|
|
p->fillRect( 0, pixelRect.y() + pixelRect.height(),
|
|
docwidth, docheight - ( pixelRect.y() + pixelRect.height() ),
|
|
cg.brush( TQColorGroup::Base ) );
|
|
}
|
|
if ( !flow()->isEmpty() ) {
|
|
TQRect cr( cx, cy, cw, ch );
|
|
cr = cr.intersect( TQRect( 0, pixelRect.y() + pixelRect.height(), docwidth,
|
|
docheight - ( pixelRect.y() + pixelRect.height() ) ) );
|
|
flow()->drawFloatingItems( p, cr.x(), cr.y(), cr.width(), cr.height(), cg, FALSE );
|
|
}
|
|
}
|
|
|
|
if ( buf_pixmap && buf_pixmap->height() > 300 ) {
|
|
delete buf_pixmap;
|
|
buf_pixmap = 0;
|
|
}
|
|
|
|
return lastFormatted;
|
|
}
|
|
|
|
|
|
// Used for printing
|
|
void KoTextDocument::drawWithoutDoubleBuffer( TQPainter *p, const TQRect &cr, const TQColorGroup &cg,
|
|
KoTextZoomHandler* zoomHandler, const TQBrush *paper )
|
|
{
|
|
if ( !firstParag() )
|
|
return;
|
|
|
|
Q_ASSERT( (m_drawingFlags & DrawSelections) == 0 );
|
|
if (m_drawingFlags & DrawSelections)
|
|
kdDebug() << kdBacktrace();
|
|
if ( paper && ( m_drawingFlags & TransparentBackground ) == 0 ) {
|
|
p->setBrushOrigin( -(int)p->translationX(), -(int)p->translationY() );
|
|
p->fillRect( cr, *paper );
|
|
}
|
|
|
|
KoTextParag *parag = firstParag();
|
|
while ( parag ) {
|
|
if ( !parag->isValid() )
|
|
parag->format();
|
|
|
|
TQRect pr( parag->pixelRect( zoomHandler ) );
|
|
pr.setLeft( 0 );
|
|
pr.setWidth( TQWIDGETSIZE_MAX );
|
|
// The cliprect is checked in layout units, in KoTextParag::paint
|
|
TQRect crect_lu( parag->rect() );
|
|
|
|
if ( !cr.isNull() && !cr.intersects( pr ) ) {
|
|
parag = parag->next();
|
|
continue;
|
|
}
|
|
|
|
p->translate( 0, pr.y() );
|
|
|
|
// No need to brush plain white on a printer. Brush all
|
|
// other cases (except "full transparent" case).
|
|
TQBrush brush = cg.brush( TQColorGroup::Base );;
|
|
bool needBrush = brush.style() != TQt::NoBrush &&
|
|
!(brush.style() == TQt::SolidPattern &&
|
|
brush.color() == TQt::white &&
|
|
is_printer(p));
|
|
if ( needBrush && ( m_drawingFlags & TransparentBackground ) == 0 )
|
|
p->fillRect( TQRect( 0, 0, pr.width(), pr.height() ), brush );
|
|
|
|
//p->setBrushOrigin( p->brushOrigin() + TQPoint( 0, pr.y() ) );
|
|
parag->paint( *p, cg, 0, FALSE,
|
|
crect_lu.x(), crect_lu.y(),
|
|
crect_lu.width(), crect_lu.height() );
|
|
p->translate( 0, -pr.y() );
|
|
//p->setBrushOrigin( p->brushOrigin() - TQPoint( 0, pr.y() ) );
|
|
parag = parag->next();
|
|
}
|
|
}
|
|
|
|
// Used for screen display (and also printing?)
|
|
// Called by drawWYSIWYG and the app's drawCursor
|
|
void KoTextDocument::drawParagWYSIWYG( TQPainter *p, KoTextParag *parag, int cx, int cy, int cw, int ch,
|
|
TQPixmap *&doubleBuffer, const TQColorGroup &cg,
|
|
KoTextZoomHandler* zoomHandler, bool drawCursor,
|
|
KoTextCursor *cursor, bool resetChanged, uint drawingFlags )
|
|
{
|
|
if ( cw <= 0 || ch <= 0 ) { Q_ASSERT( cw > 0 ); Q_ASSERT( ch > 0 ); return; }
|
|
#ifdef DEBUG_PAINTING
|
|
kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG " << (void*)parag << " id:" << parag->paragId() << endl;
|
|
#endif
|
|
m_drawingFlags = drawingFlags;
|
|
TQPainter *painter = 0;
|
|
// Those three rects are in pixels, in the document coordinates (0,0 == topleft of first parag)
|
|
TQRect rect = parag->pixelRect( zoomHandler ); // the parag rect
|
|
|
|
int offsetY = 0;
|
|
// Start painting from a given line number.
|
|
if ( parag->lineChanged() > -1 )
|
|
{
|
|
offsetY = zoomHandler->layoutUnitToPixelY( parag->lineY( parag->lineChanged() ) - parag->topMargin() );
|
|
#ifdef DEBUG_PAINTING
|
|
kdDebug(32500) << " Repainting from lineChanged=" << parag->lineChanged() << " -> adding " << offsetY << " to rect" << endl;
|
|
#endif
|
|
// Skip the lines that are not repainted by moving Top. The bottom doesn't change.
|
|
rect.rTop() += offsetY;
|
|
}
|
|
|
|
TQRect crect( cx, cy, cw, ch ); // the overall crect
|
|
TQRect ir( rect ); // will be the rect to be repainted
|
|
|
|
TQBrush brush = cg.brush( TQColorGroup::Base );
|
|
|
|
// No need to brush plain white on a printer. Brush all
|
|
// other cases (except "full transparent" case).
|
|
bool needBrush = brush.style() != TQt::NoBrush &&
|
|
( drawingFlags & TransparentBackground ) == 0 &&
|
|
!(brush.style() == TQt::SolidPattern &&
|
|
brush.color() == TQt::white &&
|
|
is_printer(p));
|
|
|
|
bool useDoubleBuffer = !parag->document()->parent();
|
|
if ( is_printer(p) )
|
|
useDoubleBuffer = FALSE;
|
|
// Can't handle transparency using double-buffering, in case of rotation/scaling (due to bitBlt)
|
|
// The test on mat is almost like isIdentity(), but allows for translation.
|
|
//// ##### The way to fix this: initialize the pixmap to be fully transparent instead
|
|
// of being white.
|
|
TQWMatrix mat = p->worldMatrix();
|
|
if ( ( mat.m11() != 1.0 || mat.m22() != 1.0 || mat.m12() != 0.0 || mat.m21() != 0.0 )
|
|
&& brush.style() != TQt::SolidPattern )
|
|
useDoubleBuffer = FALSE;
|
|
|
|
#ifdef DEBUG_PAINTING
|
|
kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG parag->rect=" << parag->rect()
|
|
<< " pixelRect(ir)=" << ir
|
|
<< " crect (pixels)=" << crect
|
|
<< " useDoubleBuffer=" << useDoubleBuffer << endl;
|
|
#endif
|
|
|
|
if ( useDoubleBuffer ) {
|
|
painter = new TQPainter;
|
|
if ( cx >= 0 && cy >= 0 )
|
|
ir = ir.intersect( crect );
|
|
if ( !doubleBuffer ||
|
|
ir.width() > doubleBuffer->width() ||
|
|
ir.height() > doubleBuffer->height() )
|
|
{
|
|
doubleBuffer = bufferPixmap( ir.size() );
|
|
}
|
|
painter->begin( doubleBuffer );
|
|
|
|
} else {
|
|
p->save();
|
|
painter = p;
|
|
painter->translate( ir.x(), ir.y() );
|
|
}
|
|
// Until the next translate(), (0,0) in the painter will be at ir.topLeft() in reality
|
|
//kdDebug() << "KoTextDocument::drawParagWYSIWYG ir=" << ir << endl;
|
|
|
|
|
|
// Cumulate ir.x(), ir.y() with the current brush origin
|
|
//painter->setBrushOrigin( painter->brushOrigin() + ir.topLeft() );
|
|
|
|
if ( useDoubleBuffer || is_printer( painter ) ) {
|
|
// Transparent -> grab background from p's device
|
|
if ( brush.style() != TQt::SolidPattern ) {
|
|
bitBlt( doubleBuffer, 0, 0, p->device(),
|
|
ir.x() + (int)p->translationX(), ir.y() + (int)p->translationY(),
|
|
ir.width(), ir.height() );
|
|
}
|
|
}
|
|
|
|
if ( needBrush )
|
|
painter->fillRect( TQRect( 0, 0, ir.width(), ir.height() ), brush );
|
|
|
|
// Now revert the previous painter translation, and instead make (0,0) the topleft of the PARAGRAPH
|
|
painter->translate( rect.x() - ir.x(), rect.y() - ir.y() );
|
|
#ifdef DEBUG_PAINTING
|
|
kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG translate " << rect.x() - ir.x() << "," << rect.y() - ir.y() << endl;
|
|
#endif
|
|
//painter->setBrushOrigin( painter->brushOrigin() + rect.topLeft() - ir.topLeft() );
|
|
|
|
// The cliprect is checked in layout units, in KoTextParag::paint
|
|
TQRect crect_lu( zoomHandler->pixelToLayoutUnit( crect ) );
|
|
#ifdef DEBUG_PAINTING
|
|
kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG crect_lu=" << crect_lu << endl;
|
|
#endif
|
|
|
|
// paintDefault will paint line 'lineChanged' at its normal Y position.
|
|
// But the buffer-pixmap below starts at Y. We need to translate by -Y
|
|
// so that the painting happens at the right place.
|
|
painter->translate( 0, -offsetY );
|
|
|
|
parag->paint( *painter, cg, drawCursor ? cursor : 0, (m_drawingFlags & DrawSelections),
|
|
crect_lu.x(), crect_lu.y(), crect_lu.width(), crect_lu.height() );
|
|
|
|
|
|
if ( useDoubleBuffer ) {
|
|
delete painter;
|
|
painter = 0;
|
|
p->drawPixmap( ir.topLeft(), *doubleBuffer, TQRect( TQPoint( 0, 0 ), ir.size() ) );
|
|
#if 0 // for debug!
|
|
p->save();
|
|
p->setPen( TQt::blue );
|
|
p->drawRect( ir.x(), ir.y(), ir.width(), ir.height() );
|
|
p->restore();
|
|
#endif
|
|
} else {
|
|
// undo previous translations, painter is 'p', i.e. will be used later on
|
|
p->restore();
|
|
//painter->translate( -ir.x(), -ir.y() );
|
|
//painter->translate( 0, +offsetY );
|
|
//painter->setBrushOrigin( painter->brushOrigin() - ir.topLeft() );
|
|
}
|
|
|
|
if ( needBrush ) {
|
|
int docright = zoomHandler->layoutUnitToPixelX( parag->document()->x() + parag->document()->width() );
|
|
#ifdef DEBUG_PAINTING
|
|
// kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG my rect is: " << rect << endl;
|
|
#endif
|
|
if ( rect.x() + rect.width() < docright ) {
|
|
#ifdef DEBUG_PAINTING
|
|
kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG rect doesn't go up to docright=" << docright << endl;
|
|
#endif
|
|
p->fillRect( rect.x() + rect.width(), rect.y(),
|
|
docright - ( rect.x() + rect.width() ),
|
|
rect.height(), cg.brush( TQColorGroup::Base ) );
|
|
}
|
|
}
|
|
|
|
if ( resetChanged )
|
|
parag->setChanged( FALSE );
|
|
}
|
|
|
|
|
|
KoTextDocCommand *KoTextDocument::deleteTextCommand( KoTextDocument *textdoc, int id, int index, const TQMemArray<KoTextStringChar> & str, const CustomItemsMap & customItemsMap, const TQValueList<KoParagLayout> & oldParagLayouts )
|
|
{
|
|
return new KoTextDeleteCommand( textdoc, id, index, str, customItemsMap, oldParagLayouts );
|
|
}
|
|
|
|
KoTextParag* KoTextDocument::loadOasisText( const TQDomElement& bodyElem, KoOasisContext& context, KoTextParag* lastParagraph, KoStyleCollection* styleColl, KoTextParag* nextParagraph )
|
|
{
|
|
// was OoWriterImport::parseBodyOrSimilar
|
|
TQDomElement tag;
|
|
forEachElement( tag, bodyElem )
|
|
{
|
|
context.styleStack().save();
|
|
const TQString localName = tag.localName();
|
|
const bool isTextNS = tag.namespaceURI() == KoXmlNS::text;
|
|
uint pos = 0;
|
|
if ( isTextNS && localName == "p" ) { // text paragraph
|
|
context.fillStyleStack( tag, KoXmlNS::text, "style-name", "paragraph" );
|
|
|
|
KoTextParag *parag = createParag( this, lastParagraph, nextParagraph );
|
|
parag->loadOasis( tag, context, styleColl, pos );
|
|
if ( !lastParagraph ) // First parag
|
|
setFirstParag( parag );
|
|
lastParagraph = parag;
|
|
}
|
|
else if ( isTextNS && localName == "h" ) // heading
|
|
{
|
|
//kdDebug(32500) << " heading " << endl;
|
|
context.fillStyleStack( tag, KoXmlNS::text, "style-name", "paragraph" );
|
|
int level = tag.attributeNS( KoXmlNS::text, "outline-level", TQString() ).toInt();
|
|
bool listOK = false;
|
|
// When a heading is inside a list, it seems that the list prevails.
|
|
// Example:
|
|
// <text:list text:style-name="Numbering 1">
|
|
// <text:list-item text:start-value="5">
|
|
// <text:h text:style-name="P2" text:level="4">The header</text:h>
|
|
// where P2 has list-style-name="something else"
|
|
// Result: the numbering of the header follows "Numbering 1".
|
|
// So we use the style for the outline level only if we're not inside a list:
|
|
//if ( !context.atStartOfListItem() )
|
|
// === The new method for this is that we simply override it after loading.
|
|
listOK = context.pushOutlineListLevelStyle( level );
|
|
int restartNumbering = -1;
|
|
if ( tag.hasAttributeNS( KoXmlNS::text, "start-value" ) )
|
|
// OASIS extension http://lists.oasis-open.org/archives/office/200310/msg00033.html
|
|
restartNumbering = tag.attributeNS( KoXmlNS::text, "start-value", TQString() ).toInt();
|
|
|
|
KoTextParag *parag = createParag( this, lastParagraph, nextParagraph );
|
|
parag->loadOasis( tag, context, styleColl, pos );
|
|
if ( !lastParagraph ) // First parag
|
|
setFirstParag( parag );
|
|
lastParagraph = parag;
|
|
if ( listOK ) {
|
|
parag->applyListStyle( context, restartNumbering, true /*ordered*/, true /*heading*/, level );
|
|
context.listStyleStack().pop();
|
|
}
|
|
}
|
|
else if ( isTextNS &&
|
|
( localName == "unordered-list" || localName == "ordered-list" // OOo-1.1
|
|
|| localName == "list" || localName == "numbered-paragraph" ) ) // OASIS
|
|
{
|
|
lastParagraph = loadList( tag, context, lastParagraph, styleColl, nextParagraph );
|
|
}
|
|
else if ( isTextNS && localName == "section" ) // Temporary support (###TODO)
|
|
{
|
|
kdDebug(32500) << "Section found!" << endl;
|
|
context.fillStyleStack( tag, KoXmlNS::text, "style-name", "section" );
|
|
lastParagraph = loadOasisText( tag, context, lastParagraph, styleColl, nextParagraph );
|
|
}
|
|
else if ( isTextNS && localName == "variable-decls" )
|
|
{
|
|
// We don't parse variable-decls since we ignore var types right now
|
|
// (and just storing a list of available var names wouldn't be much use)
|
|
}
|
|
else if ( isTextNS && localName == "user-field-decls" )
|
|
{
|
|
TQDomElement fd;
|
|
forEachElement( fd, tag )
|
|
{
|
|
if ( fd.namespaceURI() == KoXmlNS::text && fd.localName() == "user-field-decl" )
|
|
{
|
|
const TQString name = fd.attributeNS( KoXmlNS::text, "name", TQString() );
|
|
const TQString value = fd.attributeNS( KoXmlNS::office, "value", TQString() );
|
|
if ( !name.isEmpty() )
|
|
context.variableCollection().setVariableValue( name, value );
|
|
}
|
|
}
|
|
}
|
|
else if ( isTextNS && localName == "number" ) // text:number
|
|
{
|
|
// This is the number in front of a numbered paragraph,
|
|
// written out to help export filters. We can ignore it.
|
|
}
|
|
else if ( !loadOasisBodyTag( tag, context, lastParagraph, styleColl, nextParagraph ) )
|
|
{
|
|
kdWarning(32500) << "Unsupported body element '" << localName << "'" << endl;
|
|
}
|
|
|
|
context.styleStack().restore(); // remove the styles added by the paragraph or list
|
|
//use signal slot ?
|
|
//m_doc->progressItemLoaded(); // ## check
|
|
}
|
|
return lastParagraph;
|
|
}
|
|
|
|
KoTextParag* KoTextDocument::loadList( const TQDomElement& list, KoOasisContext& context, KoTextParag* lastParagraph, KoStyleCollection * styleColl, KoTextParag* nextParagraph )
|
|
{
|
|
//kdDebug(32500) << "loadList: " << list.attributeNS( KoXmlNS::text, "style-name", TQString() ) << endl;
|
|
|
|
const TQString oldListStyleName = context.currentListStyleName();
|
|
if ( list.hasAttributeNS( KoXmlNS::text, "style-name" ) )
|
|
context.setCurrentListStyleName( list.attributeNS( KoXmlNS::text, "style-name", TQString() ) );
|
|
bool listOK = !context.currentListStyleName().isEmpty();
|
|
int level;
|
|
if ( list.localName() == "numbered-paragraph" )
|
|
level = list.attributeNS( KoXmlNS::text, "level", "1" ).toInt();
|
|
else
|
|
level = context.listStyleStack().level() + 1;
|
|
if ( listOK )
|
|
listOK = context.pushListLevelStyle( context.currentListStyleName(), level );
|
|
|
|
const TQDomElement listStyle = context.listStyleStack().currentListStyle();
|
|
// The tag is either list-level-style-number or list-level-style-bullet
|
|
const bool orderedList = listStyle.localName() == "list-level-style-number";
|
|
|
|
if ( list.localName() == "numbered-paragraph" )
|
|
{
|
|
// A numbered-paragraph contains paragraphs directly (it's both a list and a list-item)
|
|
int restartNumbering = -1;
|
|
if ( list.hasAttributeNS( KoXmlNS::text, "start-value" ) )
|
|
restartNumbering = list.attributeNS( KoXmlNS::text, "start-value", TQString() ).toInt();
|
|
KoTextParag* oldLast = lastParagraph;
|
|
lastParagraph = loadOasisText( list, context, lastParagraph, styleColl, nextParagraph );
|
|
KoTextParag* firstListItem = oldLast ? oldLast->next() : firstParag();
|
|
// Apply list style to first paragraph inside numbered-parag - there's only one anyway
|
|
// Keep the "is outline" property though
|
|
bool isOutline = firstListItem->counter() && firstListItem->counter()->numbering() == KoParagCounter::NUM_CHAPTER;
|
|
firstListItem->applyListStyle( context, restartNumbering, orderedList,
|
|
isOutline, level );
|
|
}
|
|
else
|
|
{
|
|
// Iterate over list items
|
|
for ( TQDomNode n = list.firstChild(); !n.isNull(); n = n.nextSibling() )
|
|
{
|
|
TQDomElement listItem = n.toElement();
|
|
int restartNumbering = -1;
|
|
if ( listItem.hasAttributeNS( KoXmlNS::text, "start-value" ) )
|
|
restartNumbering = listItem.attributeNS( KoXmlNS::text, "start-value", TQString() ).toInt();
|
|
bool isListHeader = listItem.localName() == "list-header" || listItem.attributeNS( KoXmlNS::text, "is-list-header", TQString() ) == "is-list-header";
|
|
KoTextParag* oldLast = lastParagraph;
|
|
lastParagraph = loadOasisText( listItem, context, lastParagraph, styleColl, nextParagraph );
|
|
KoTextParag* firstListItem = oldLast ? oldLast->next() : firstParag();
|
|
KoTextParag* p = firstListItem;
|
|
// It's either list-header (normal text on top of list) or list-item
|
|
if ( !isListHeader && firstListItem ) {
|
|
// Apply list style to first paragraph inside list-item
|
|
bool isOutline = firstListItem->counter() && firstListItem->counter()->numbering() == KoParagCounter::NUM_CHAPTER;
|
|
firstListItem->applyListStyle( context, restartNumbering, orderedList, isOutline, level );
|
|
p = p->next();
|
|
}
|
|
// Make text:h inside list-item (as non first child) unnumbered.
|
|
while ( p && p != lastParagraph->next() ) {
|
|
if ( p->counter() )
|
|
p->counter()->setNumbering( KoParagCounter::NUM_NONE );
|
|
p = p->next();
|
|
}
|
|
}
|
|
}
|
|
if ( listOK )
|
|
context.listStyleStack().pop();
|
|
context.setCurrentListStyleName( oldListStyleName );
|
|
return lastParagraph;
|
|
}
|
|
|
|
void KoTextDocument::saveOasisContent( KoXmlWriter& writer, KoSavingContext& context ) const
|
|
{
|
|
// Basically just call saveOasis on every paragraph.
|
|
// KWord doesn't use this method because it does table-of-contents-handling in addition.
|
|
KoTextParag* parag = firstParag();
|
|
while ( parag ) {
|
|
// Save the whole parag, without the trailing space.
|
|
parag->saveOasis( writer, context, 0, parag->lastCharPos() );
|
|
parag = parag->next();
|
|
}
|
|
}
|
|
|
|
#include "KoTextDocument.moc"
|