lib

KoTextDocument.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2001-2005 David Faure <faure@kde.org>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017  * Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "KoTextDocument.h"
00021 #include "KoTextParag.h"
00022 #include "KoTextZoomHandler.h"
00023 #include "KoTextFormatter.h"
00024 #include "KoTextFormat.h"
00025 #include "KoParagCounter.h"
00026 #include "KoTextCommand.h"
00027 #include "KoOasisContext.h"
00028 #include "KoVariable.h"
00029 #include <KoXmlWriter.h>
00030 #include <KoXmlNS.h>
00031 #include <KoDom.h>
00032 #include <kdebug.h>
00033 #include <kdeversion.h>
00034 #include <qapplication.h>
00035 #include <assert.h>
00036 
00037 //#define DEBUG_PAINTING
00038 
00041 
00042 KoTextDocument::KoTextDocument( KoTextZoomHandler *zoomHandler, KoTextFormatCollection *fc,
00043                                 KoTextFormatter *formatter, bool createInitialParag )
00044     : m_zoomHandler( zoomHandler ),
00045       m_bDestroying( false ),
00046 #ifdef QTEXTTABLE_AVAILABLE
00047       par( 0L /*we don't use parent documents */ ),
00048       tc( 0 ),
00049 #endif
00050       tArray( 0 ), tStopWidth( 0 )
00051 {
00052     fCollection = fc;
00053     init(); // see korichtext.cpp
00054 
00055     m_drawingFlags = 0;
00056     if ( !formatter )
00057         formatter = new KoTextFormatter;
00058     setFormatter( formatter );
00059 
00060     setY( 0 );
00061     setLeftMargin( 0 );
00062     setRightMargin( 0 );
00063 
00064     // Delete the KoTextParag created by KoTextDocument::init() if createInitialParag is false.
00065     if ( !createInitialParag )
00066         clear( false );
00067 }
00068 
00069 void KoTextDocument::init()
00070 {
00071     //pProcessor = 0;
00072     useFC = TRUE;
00073     pFormatter = 0;
00074     fParag = 0;
00075     m_pageBreakEnabled = false;
00076     //minw = 0;
00077     align = Qt::AlignAuto;
00078     nSelections = 2;
00079 
00080     underlLinks = TRUE;
00081     backBrush = 0;
00082     buf_pixmap = 0;
00083     //nextDoubleBuffered = FALSE;
00084 
00085     //if ( par )
00086 //  withoutDoubleBuffer = par->withoutDoubleBuffer;
00087 //    else
00088     withoutDoubleBuffer = FALSE;
00089 
00090     lParag = fParag = createParag( this, 0, 0 );
00091 
00092     //cx = 0;
00093     //cy = 2;
00094     //if ( par )
00095     cx = cy = 0;
00096     //cw = 600; // huh?
00097     //vw = 0;
00098     flow_ = new KoTextFlow;
00099     //flow_->setWidth( cw );
00100 
00101     leftmargin = 0; // 4 in QRT
00102     rightmargin = 0; // 4 in QRT
00103 
00104     selectionColors[ Standard ] = QApplication::palette().color( QPalette::Active, QColorGroup::Highlight );
00105     selectionText[ Standard ] = TRUE;
00106     assert( Standard < nSelections );
00107     selectionText[ InputMethodPreedit ] = FALSE;
00108     assert( InputMethodPreedit < nSelections );
00109     commandHistory = new KoTextDocCommandHistory( 100 );
00110     tStopWidth = formatCollection()->defaultFormat()->width( 'x' ) * 8;
00111 }
00112 
00113 KoTextDocument::~KoTextDocument()
00114 {
00115     //if ( par )
00116 //  par->removeChild( this );
00118     m_bDestroying = true;
00119     clear( false );
00121     delete commandHistory;
00122     delete flow_;
00123     //if ( !par )
00124     delete pFormatter;
00125     delete fCollection;
00126     //delete pProcessor;
00127     delete buf_pixmap;
00128     delete backBrush;
00129     if ( tArray )
00130     delete [] tArray;
00131 }
00132 
00133 void KoTextDocument::clear( bool createEmptyParag )
00134 {
00135     if ( flow_ )
00136     flow_->clear();
00137     while ( fParag ) {
00138     KoTextParag *p = fParag->next();
00139     fParag->string()->clear(); // avoid the "unregister custom items" code, not needed
00140     delete fParag;
00141     fParag = p;
00142     }
00143     fParag = lParag = 0;
00144     if ( createEmptyParag )
00145     fParag = lParag = createParag( this );
00146     selections.clear();
00147     customItems.clear();
00148 }
00149 
00150 /*
00151    // Looks slow!
00152 int KoTextDocument::widthUsed() const
00153 {
00154     KoTextParag *p = fParag;
00155     int w = 0;
00156     while ( p ) {
00157     int a = p->alignment();
00158     p->setAlignment( Qt::AlignLeft );
00159     p->invalidate( 0 );
00160     p->format();
00161     w = QMAX( w, p->rect().width() );
00162     p->setAlignment( a );
00163     p->invalidate( 0 );
00164     p = p->next();
00165     }
00166     return w;
00167 }
00168 */
00169 
00170 int KoTextDocument::height() const
00171 {
00172     int h = 0;
00173     if ( lParag )
00174     h = lParag->rect().top() + lParag->rect().height() + 1;
00175     //int fh = flow_->boundingRect().height();
00176     //return QMAX( h, fh );
00177     return h;
00178 }
00179 
00180 
00181 KoTextParag *KoTextDocument::createParag( KoTextDocument *d, KoTextParag *pr, KoTextParag *nx, bool updateIds )
00182 {
00183     return new KoTextParag( d, pr, nx, updateIds );
00184 }
00185 
00186 void KoTextDocument::setPlainText( const QString &text )
00187 {
00188     clear();
00189     //preferRichText = FALSE;
00190     //oTextValid = TRUE;
00191     //oText = text;
00192 
00193     int lastNl = 0;
00194     int nl = text.find( '\n' );
00195     if ( nl == -1 ) {
00196     lParag = createParag( this, lParag, 0 );
00197     if ( !fParag )
00198         fParag = lParag;
00199     QString s = text;
00200     if ( !s.isEmpty() ) {
00201         if ( s[ (int)s.length() - 1 ] == '\r' )
00202         s.remove( s.length() - 1, 1 );
00203         lParag->append( s );
00204     }
00205     } else {
00206     for (;;) {
00207         lParag = createParag( this, lParag, 0 );
00208         if ( !fParag )
00209         fParag = lParag;
00210         QString s = text.mid( lastNl, nl - lastNl );
00211         if ( !s.isEmpty() ) {
00212         if ( s[ (int)s.length() - 1 ] == '\r' )
00213             s.remove( s.length() - 1, 1 );
00214         lParag->append( s );
00215         }
00216         if ( nl == 0xffffff )
00217         break;
00218         lastNl = nl + 1;
00219         nl = text.find( '\n', nl + 1 );
00220         if ( nl == -1 )
00221         nl = 0xffffff;
00222     }
00223     }
00224     if ( !lParag )
00225     lParag = fParag = createParag( this, 0, 0 );
00226 }
00227 
00228 void KoTextDocument::setText( const QString &text, const QString & /*context*/ )
00229 {
00230     selections.clear();
00231     setPlainText( text );
00232 }
00233 
00234 QString KoTextDocument::plainText() const
00235 {
00236     QString buffer;
00237     QString s;
00238     KoTextParag *p = fParag;
00239     while ( p ) {
00240         s = p->string()->toString();
00241         s.remove( s.length() - 1, 1 );
00242         if ( p->next() )
00243             s += "\n";
00244         buffer += s;
00245         p = p->next();
00246     }
00247     return buffer;
00248 }
00249 
00250 void KoTextDocument::invalidate()
00251 {
00252     KoTextParag *s = fParag;
00253     while ( s ) {
00254     s->invalidate( 0 );
00255     s = s->next();
00256     }
00257 }
00258 
00259 void KoTextDocument::informParagraphDeleted( KoTextParag* parag )
00260 {
00261     QMap<int, KoTextDocumentSelection>::Iterator it = selections.begin();
00262     for ( ; it != selections.end(); ++it )
00263     {
00264         if ( (*it).startCursor.parag() == parag ) {
00265             if ( parag->prev() ) {
00266                 KoTextParag* prevP = parag->prev();
00267                 (*it).startCursor.setParag( prevP );
00268                 (*it).startCursor.setIndex( prevP->length()-1 );
00269             } else
00270                 (*it).startCursor.setParag( parag->next() ); // sets index to 0
00271         }
00272         if ( (*it).endCursor.parag() == parag ) {
00273             if ( parag->prev() ) {
00274                 KoTextParag* prevP = parag->prev();
00275                 (*it).endCursor.setParag( prevP );
00276                 (*it).endCursor.setIndex( prevP->length()-1 );
00277             } else
00278                 (*it).endCursor.setParag( parag->next() ); // sets index to 0
00279         }
00280     }
00281     emit paragraphDeleted( parag );
00282 }
00283 
00284 void KoTextDocument::selectionStart( int id, int &paragId, int &index )
00285 {
00286     QMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
00287     if ( it == selections.end() )
00288     return;
00289     KoTextDocumentSelection &sel = *it;
00290     paragId = !sel.swapped ? sel.startCursor.parag()->paragId() : sel.endCursor.parag()->paragId();
00291     index = !sel.swapped ? sel.startCursor.index() : sel.endCursor.index();
00292 }
00293 
00294 KoTextCursor KoTextDocument::selectionStartCursor( int id)
00295 {
00296     QMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
00297     if ( it == selections.end() )
00298     return KoTextCursor( this );
00299     KoTextDocumentSelection &sel = *it;
00300     if ( sel.swapped )
00301     return sel.endCursor;
00302     return sel.startCursor;
00303 }
00304 
00305 KoTextCursor KoTextDocument::selectionEndCursor( int id)
00306 {
00307     QMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
00308     if ( it == selections.end() )
00309     return KoTextCursor( this );
00310     KoTextDocumentSelection &sel = *it;
00311     if ( !sel.swapped )
00312     return sel.endCursor;
00313     return sel.startCursor;
00314 }
00315 
00316 void KoTextDocument::selectionEnd( int id, int &paragId, int &index )
00317 {
00318     QMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
00319     if ( it == selections.end() )
00320     return;
00321     KoTextDocumentSelection &sel = *it;
00322     paragId = sel.swapped ? sel.startCursor.parag()->paragId() : sel.endCursor.parag()->paragId();
00323     index = sel.swapped ? sel.startCursor.index() : sel.endCursor.index();
00324 }
00325 
00326 bool KoTextDocument::isSelectionSwapped( int id )
00327 {
00328     QMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
00329     if ( it == selections.end() )
00330     return false;
00331     KoTextDocumentSelection &sel = *it;
00332     return sel.swapped;
00333 }
00334 
00335 KoTextParag *KoTextDocument::selectionStart( int id )
00336 {
00337     QMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
00338     if ( it == selections.end() )
00339     return 0;
00340     KoTextDocumentSelection &sel = *it;
00341     if ( sel.startCursor.parag()->paragId() <  sel.endCursor.parag()->paragId() )
00342     return sel.startCursor.parag();
00343     return sel.endCursor.parag();
00344 }
00345 
00346 KoTextParag *KoTextDocument::selectionEnd( int id )
00347 {
00348     QMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
00349     if ( it == selections.end() )
00350     return 0;
00351     KoTextDocumentSelection &sel = *it;
00352     if ( sel.startCursor.parag()->paragId() >  sel.endCursor.parag()->paragId() )
00353     return sel.startCursor.parag();
00354     return sel.endCursor.parag();
00355 }
00356 
00357 void KoTextDocument::addSelection( int id )
00358 {
00359     nSelections = QMAX( nSelections, id + 1 );
00360 }
00361 
00362 static void setSelectionEndHelper( int id, KoTextDocumentSelection &sel, KoTextCursor &start, KoTextCursor &end )
00363 {
00364     KoTextCursor c1 = start;
00365     KoTextCursor c2 = end;
00366     if ( sel.swapped ) {
00367     c1 = end;
00368     c2 = start;
00369     }
00370 
00371     c1.parag()->removeSelection( id );
00372     c2.parag()->removeSelection( id );
00373     if ( c1.parag() != c2.parag() ) {
00374     c1.parag()->setSelection( id, c1.index(), c1.parag()->length() - 1 );
00375     c2.parag()->setSelection( id, 0, c2.index() );
00376     } else {
00377     c1.parag()->setSelection( id, QMIN( c1.index(), c2.index() ), QMAX( c1.index(), c2.index() ) );
00378     }
00379 
00380     sel.startCursor = start;
00381     sel.endCursor = end;
00382     if ( sel.startCursor.parag() == sel.endCursor.parag() )
00383     sel.swapped = sel.startCursor.index() > sel.endCursor.index();
00384 }
00385 
00386 bool KoTextDocument::setSelectionEnd( int id, KoTextCursor *cursor )
00387 {
00388     QMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
00389     if ( it == selections.end() )
00390     return FALSE;
00391     KoTextDocumentSelection &sel = *it;
00392 
00393     KoTextCursor start = sel.startCursor;
00394     KoTextCursor end = *cursor;
00395 
00396     if ( start == end ) {
00397     removeSelection( id );
00398     setSelectionStart( id, cursor );
00399     return TRUE;
00400     }
00401 
00402     if ( sel.endCursor.parag() == end.parag() ) {
00403     setSelectionEndHelper( id, sel, start, end );
00404     return TRUE;
00405     }
00406 
00407     bool inSelection = FALSE;
00408     KoTextCursor c( this );
00409     KoTextCursor tmp = sel.startCursor;
00410     if ( sel.swapped )
00411     tmp = sel.endCursor;
00412     KoTextCursor tmp2 = *cursor;
00413     c.setParag( tmp.parag()->paragId() < tmp2.parag()->paragId() ? tmp.parag() : tmp2.parag() );
00414     KoTextCursor old;
00415     bool hadStart = FALSE;
00416     bool hadEnd = FALSE;
00417     bool hadStartParag = FALSE;
00418     bool hadEndParag = FALSE;
00419     bool hadOldStart = FALSE;
00420     bool hadOldEnd = FALSE;
00421     bool leftSelection = FALSE;
00422     sel.swapped = FALSE;
00423     for ( ;; ) {
00424     if ( c == start )
00425         hadStart = TRUE;
00426     if ( c == end )
00427         hadEnd = TRUE;
00428     if ( c.parag() == start.parag() )
00429         hadStartParag = TRUE;
00430     if ( c.parag() == end.parag() )
00431         hadEndParag = TRUE;
00432     if ( c == sel.startCursor )
00433         hadOldStart = TRUE;
00434     if ( c == sel.endCursor )
00435         hadOldEnd = TRUE;
00436 
00437     if ( !sel.swapped &&
00438          ( hadEnd && !hadStart ||
00439            hadEnd && hadStart && start.parag() == end.parag() && start.index() > end.index() ) )
00440         sel.swapped = TRUE;
00441 
00442     if ( c == end && hadStartParag ||
00443          c == start && hadEndParag ) {
00444         KoTextCursor tmp = c;
00445         if ( tmp.parag() != c.parag() ) {
00446         int sstart = tmp.parag()->selectionStart( id );
00447         tmp.parag()->removeSelection( id );
00448         tmp.parag()->setSelection( id, sstart, tmp.index() );
00449         }
00450     }
00451 
00452     if ( inSelection &&
00453          ( c == end && hadStart || c == start && hadEnd ) )
00454          leftSelection = TRUE;
00455     else if ( !leftSelection && !inSelection && ( hadStart || hadEnd ) )
00456         inSelection = TRUE;
00457 
00458     bool noSelectionAnymore = hadOldStart && hadOldEnd && leftSelection && !inSelection && !c.parag()->hasSelection( id ) && c.atParagEnd();
00459     c.parag()->removeSelection( id );
00460     if ( inSelection ) {
00461         if ( c.parag() == start.parag() && start.parag() == end.parag() ) {
00462         c.parag()->setSelection( id, QMIN( start.index(), end.index() ), QMAX( start.index(), end.index() ) );
00463         } else if ( c.parag() == start.parag() && !hadEndParag ) {
00464         c.parag()->setSelection( id, start.index(), c.parag()->length() - 1 );
00465         } else if ( c.parag() == end.parag() && !hadStartParag ) {
00466         c.parag()->setSelection( id, end.index(), c.parag()->length() - 1 );
00467         } else if ( c.parag() == end.parag() && hadEndParag ) {
00468         c.parag()->setSelection( id, 0, end.index() );
00469         } else if ( c.parag() == start.parag() && hadStartParag ) {
00470         c.parag()->setSelection( id, 0, start.index() );
00471         } else {
00472         c.parag()->setSelection( id, 0, c.parag()->length() - 1 );
00473         }
00474     }
00475 
00476     if ( leftSelection )
00477         inSelection = FALSE;
00478 
00479     old = c;
00480     c.gotoNextLetter();
00481     if ( old == c || noSelectionAnymore )
00482         break;
00483     }
00484 
00485     if ( !sel.swapped )
00486     sel.startCursor.parag()->setSelection( id, sel.startCursor.index(), sel.startCursor.parag()->length() - 1 );
00487 
00488     sel.startCursor = start;
00489     sel.endCursor = end;
00490     if ( sel.startCursor.parag() == sel.endCursor.parag() )
00491     sel.swapped = sel.startCursor.index() > sel.endCursor.index();
00492 
00493     setSelectionEndHelper( id, sel, start, end );
00494 
00495     return TRUE;
00496 }
00497 
00498 void KoTextDocument::selectAll( int id )
00499 {
00500     removeSelection( id );
00501 
00502     KoTextDocumentSelection sel;
00503     sel.swapped = FALSE;
00504     KoTextCursor c( this );
00505 
00506     c.setParag( fParag );
00507     c.setIndex( 0 );
00508     sel.startCursor = c;
00509 
00510     c.setParag( lParag );
00511     c.setIndex( lParag->length() - 1 );
00512     sel.endCursor = c;
00513 
00514     KoTextParag *p = fParag;
00515     while ( p ) {
00516     p->setSelection( id, 0, p->length() - 1 );
00517 #ifdef QTEXTTABLE_AVAILABLE
00518     for ( int i = 0; i < (int)p->length(); ++i ) {
00519         if ( p->at( i )->isCustom() && p->at( i )->customItem()->isNested() ) {
00520         KoTextTable *t = (KoTextTable*)p->at( i )->customItem();
00521         QPtrList<KoTextTableCell> tableCells = t->tableCells();
00522         for ( KoTextTableCell *c = tableCells.first(); c; c = tableCells.next() )
00523             c->richText()->selectAll( id );
00524         }
00525     }
00526 #endif
00527     p = p->next();
00528     }
00529 
00530     selections.insert( id, sel );
00531 }
00532 
00533 bool KoTextDocument::removeSelection( int id )
00534 {
00535     QMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
00536     if ( it == selections.end() )
00537     return FALSE;
00538 
00539     KoTextDocumentSelection &sel = *it;
00540 
00541     KoTextCursor c( this );
00542     KoTextCursor tmp = sel.startCursor;
00543     if ( sel.swapped )
00544     tmp = sel.endCursor;
00545     c.setParag( tmp.parag() );
00546     KoTextCursor old;
00547     bool hadStart = FALSE;
00548     bool hadEnd = FALSE;
00549     KoTextParag *lastParag = 0;
00550     bool leftSelection = FALSE;
00551     bool inSelection = FALSE;
00552     sel.swapped = FALSE;
00553     for ( ;; ) {
00554     if ( !hadStart && c.parag() == sel.startCursor.parag() )
00555         hadStart = TRUE;
00556     if ( !hadEnd && c.parag() == sel.endCursor.parag() )
00557         hadEnd = TRUE;
00558 
00559         if ( !leftSelection && !inSelection && ( c.parag() == sel.startCursor.parag() || c.parag() == sel.endCursor.parag() ) )
00560         inSelection = TRUE;
00561 
00562     if ( inSelection &&
00563          ( c == sel.endCursor && hadStart || c == sel.startCursor && hadEnd ) ) {
00564          leftSelection = TRUE;
00565              inSelection = FALSE;
00566         }
00567 
00568     bool noSelectionAnymore = leftSelection && !inSelection && !c.parag()->hasSelection( id ) && c.atParagEnd();
00569 
00570     if ( lastParag != c.parag() )
00571         c.parag()->removeSelection( id );
00572 
00573     old = c;
00574     lastParag = c.parag();
00575     c.gotoNextLetter();
00576     if ( old == c || noSelectionAnymore )
00577         break;
00578     }
00579 
00580     selections.remove( id );
00581     return TRUE;
00582 }
00583 
00584 QString KoTextDocument::selectedText( int id, bool withCustom ) const
00585 {
00586     // ######## TODO: look at textFormat() and return rich text or plain text (like the text() method!)
00587     QMap<int, KoTextDocumentSelection>::ConstIterator it = selections.find( id );
00588     if ( it == selections.end() )
00589     return QString::null;
00590 
00591     KoTextDocumentSelection sel = *it;
00592 
00593 
00594     KoTextCursor c1 = sel.startCursor;
00595     KoTextCursor c2 = sel.endCursor;
00596     if ( sel.swapped ) {
00597     c2 = sel.startCursor;
00598     c1 = sel.endCursor;
00599     }
00600 
00601     if ( c1.parag() == c2.parag() ) {
00602     QString s;
00603     KoTextParag *p = c1.parag();
00604     int end = c2.index();
00605     if ( p->at( QMAX( 0, end - 1 ) )->isCustom() )
00606         ++end;
00607     if ( !withCustom || !p->customItems() ) {
00608         s += p->string()->toString().mid( c1.index(), end - c1.index() );
00609     } else {
00610         for ( int i = c1.index(); i < end; ++i ) {
00611         if ( p->at( i )->isCustom() ) {
00612 #ifdef QTEXTTABLE_AVAILABLE
00613             if ( p->at( i )->customItem()->isNested() ) {
00614             s += "\n";
00615             KoTextTable *t = (KoTextTable*)p->at( i )->customItem();
00616             QPtrList<KoTextTableCell> cells = t->tableCells();
00617             for ( KoTextTableCell *c = cells.first(); c; c = cells.next() )
00618                 s += c->richText()->plainText() + "\n";
00619             s += "\n";
00620             }
00621 #endif
00622         } else {
00623             s += p->at( i )->c;
00624         }
00625         s += "\n";
00626         }
00627     }
00628     return s;
00629     }
00630 
00631     QString s;
00632     KoTextParag *p = c1.parag();
00633     int start = c1.index();
00634     while ( p ) {
00635     int end = p == c2.parag() ? c2.index() : p->length() - 1;
00636     if ( p == c2.parag() && p->at( QMAX( 0, end - 1 ) )->isCustom() )
00637         ++end;
00638     if ( !withCustom || !p->customItems() ) {
00639         s += p->string()->toString().mid( start, end - start );
00640         if ( p != c2.parag() )
00641         s += "\n";
00642     } else {
00643         for ( int i = start; i < end; ++i ) {
00644         if ( p->at( i )->isCustom() ) {
00645 #ifdef QTEXTTABLE_AVAILABLE
00646             if ( p->at( i )->customItem()->isNested() ) {
00647             s += "\n";
00648             KoTextTable *t = (KoTextTable*)p->at( i )->customItem();
00649             QPtrList<KoTextTableCell> cells = t->tableCells();
00650             for ( KoTextTableCell *c = cells.first(); c; c = cells.next() )
00651                 s += c->richText()->plainText() + "\n";
00652             s += "\n";
00653             }
00654 #endif
00655         } else {
00656             s += p->at( i )->c;
00657         }
00658         s += "\n";
00659         }
00660     }
00661     start = 0;
00662     if ( p == c2.parag() )
00663         break;
00664     p = p->next();
00665     }
00666     return s;
00667 }
00668 
00669 QString KoTextDocument::copySelection( KoXmlWriter& writer, KoSavingContext& context, int selectionId )
00670 {
00671     KoTextCursor c1 = selectionStartCursor( selectionId );
00672     KoTextCursor c2 = selectionEndCursor( selectionId );
00673     QString text;
00674     if ( c1.parag() == c2.parag() )
00675     {
00676         text = c1.parag()->toString( c1.index(), c2.index() - c1.index() );
00677 
00678         c1.parag()->saveOasis( writer, context, c1.index(), c2.index()-1, true );
00679     }
00680     else
00681     {
00682         text += c1.parag()->toString( c1.index() ) + "\n";
00683 
00684         c1.parag()->saveOasis( writer, context, c1.index(), c1.parag()->length()-2, true );
00685         KoTextParag *p = c1.parag()->next();
00686         while ( p && p != c2.parag() ) {
00687             text += p->toString() + "\n";
00688             p->saveOasis( writer, context, 0, p->length()-2, true );
00689             p = p->next();
00690         }
00691         text += c2.parag()->toString( 0, c2.index() );
00692         c2.parag()->saveOasis( writer, context, 0, c2.index() - 1, true );
00693     }
00694     return text;
00695 }
00696 
00697 void KoTextDocument::setFormat( int id, const KoTextFormat *f, int flags )
00698 {
00699     QMap<int, KoTextDocumentSelection>::ConstIterator it = selections.find( id );
00700     if ( it == selections.end() )
00701     return;
00702 
00703     KoTextDocumentSelection sel = *it;
00704 
00705     KoTextCursor c1 = sel.startCursor;
00706     KoTextCursor c2 = sel.endCursor;
00707     if ( sel.swapped ) {
00708     c2 = sel.startCursor;
00709     c1 = sel.endCursor;
00710     }
00711 
00712     if ( c1.parag() == c2.parag() ) {
00713     c1.parag()->setFormat( c1.index(), c2.index() - c1.index(), f, TRUE, flags );
00714     return;
00715     }
00716 
00717     c1.parag()->setFormat( c1.index(), c1.parag()->length() - c1.index(), f, TRUE, flags );
00718     KoTextParag *p = c1.parag()->next();
00719     while ( p && p != c2.parag() ) {
00720     p->setFormat( 0, p->length(), f, TRUE, flags );
00721     p = p->next();
00722     }
00723     c2.parag()->setFormat( 0, c2.index(), f, TRUE, flags );
00724 }
00725 
00726 /*void KoTextDocument::copySelectedText( int id )
00727 {
00728 #ifndef QT_NO_CLIPBOARD
00729     if ( !hasSelection( id ) )
00730     return;
00731 
00732     QApplication::clipboard()->setText( selectedText( id ) );
00733 #endif
00734 }*/
00735 
00736 void KoTextDocument::removeSelectedText( int id, KoTextCursor *cursor )
00737 {
00738     QMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
00739     if ( it == selections.end() )
00740     return;
00741 
00742     KoTextDocumentSelection sel = *it;
00743 
00744     KoTextCursor c1 = sel.startCursor;
00745     KoTextCursor c2 = sel.endCursor;
00746     if ( sel.swapped ) {
00747     c2 = sel.startCursor;
00748     c1 = sel.endCursor;
00749     }
00750 
00751     *cursor = c1;
00752     removeSelection( id );
00753 
00754     if ( c1.parag() == c2.parag() ) {
00755     c1.parag()->remove( c1.index(), c2.index() - c1.index() );
00756     return;
00757     }
00758 
00759     // ## Qt has a strange setValid/isValid on QTextCursor, only used in the few lines below !?!?
00760     bool valid = true;
00761     if ( c1.parag() == fParag && c1.index() == 0 &&
00762          c2.parag() == lParag && c2.index() == lParag->length() - 1 )
00763         valid = FALSE;
00764 
00765     bool didGoLeft = FALSE;
00766     if (  c1.index() == 0 && c1.parag() != fParag ) {
00767     cursor->gotoPreviousLetter();
00768         if ( valid )
00769             didGoLeft = TRUE;
00770     }
00771 
00772     c1.parag()->remove( c1.index(), c1.parag()->length() - 1 - c1.index() );
00773     KoTextParag *p = c1.parag()->next();
00774     int dy = 0;
00775     KoTextParag *tmp;
00776     while ( p && p != c2.parag() ) {
00777     tmp = p->next();
00778     dy -= p->rect().height();
00779         //emit paragraphDeleted( p ); // done by KoTextParag dtor already
00780     delete p;
00781     p = tmp;
00782     }
00783     c2.parag()->remove( 0, c2.index() );
00784     while ( p ) {
00785     p->move( dy );
00787         if ( p->paragLayout().counter )
00788             p->paragLayout().counter->invalidate();
00790     p->invalidate( 0 );
00791     //p->setEndState( -1 );
00792     p = p->next();
00793     }
00794 
00795     c1.parag()->join( c2.parag() );
00796 
00797     if ( didGoLeft )
00798     cursor->gotoNextLetter();
00799 }
00800 
00801 void KoTextDocument::addCommand( KoTextDocCommand *cmd )
00802 {
00803     commandHistory->addCommand( cmd );
00804 }
00805 
00806 KoTextCursor *KoTextDocument::undo( KoTextCursor *c )
00807 {
00808     return commandHistory->undo( c );
00809 }
00810 
00811 KoTextCursor *KoTextDocument::redo( KoTextCursor *c )
00812 {
00813     return commandHistory->redo( c );
00814 }
00815 
00816 bool KoTextDocument::find( const QString &expr, bool cs, bool wo, bool forward,
00817               int *parag, int *index, KoTextCursor *cursor )
00818 {
00819     KoTextParag *p = forward ? fParag : lParag;
00820     if ( parag )
00821     p = paragAt( *parag );
00822     else if ( cursor )
00823     p = cursor->parag();
00824     bool first = TRUE;
00825 
00826     while ( p ) {
00827     QString s = p->string()->toString();
00828     s.remove( s.length() - 1, 1 ); // get rid of trailing space
00829     int start = forward ? 0 : s.length() - 1;
00830     if ( first && index )
00831         start = *index;
00832     else if ( first )
00833         start = cursor->index();
00834     if ( !forward && first ) {
00835         start -= expr.length() + 1;
00836         if ( start < 0 ) {
00837         first = FALSE;
00838         p = p->prev();
00839         continue;
00840         }
00841     }
00842     first = FALSE;
00843 
00844     for ( ;; ) {
00845         int res = forward ? s.find( expr, start, cs ) : s.findRev( expr, start, cs );
00846         if ( res == -1 )
00847         break;
00848 
00849         bool ok = TRUE;
00850         if ( wo ) {
00851         int end = res + expr.length();
00852         if ( ( res == 0 || s[ res - 1 ].isSpace() || s[ res - 1 ].isPunct() ) &&
00853              ( end == (int)s.length() || s[ end ].isSpace() || s[ end ].isPunct() ) )
00854             ok = TRUE;
00855         else
00856             ok = FALSE;
00857         }
00858         if ( ok ) {
00859         cursor->setParag( p );
00860         cursor->setIndex( res );
00861         setSelectionStart( Standard, cursor );
00862         cursor->setIndex( res + expr.length() );
00863         setSelectionEnd( Standard, cursor );
00864         if ( parag )
00865             *parag = p->paragId();
00866         if ( index )
00867             *index = res;
00868         return TRUE;
00869         }
00870         if ( forward ) {
00871         start = res + 1;
00872         } else {
00873         if ( res == 0 )
00874             break;
00875         start = res - 1;
00876         }
00877     }
00878     p = forward ? p->next() : p->prev();
00879     }
00880 
00881     return FALSE;
00882 }
00883 
00884 bool KoTextDocument::inSelection( int selId, const QPoint &pos ) const
00885 {
00886     QMap<int, KoTextDocumentSelection>::ConstIterator it = selections.find( selId );
00887     if ( it == selections.end() )
00888     return FALSE;
00889 
00890     KoTextDocumentSelection sel = *it;
00891     KoTextParag *startParag = sel.startCursor.parag();
00892     KoTextParag *endParag = sel.endCursor.parag();
00893     if ( sel.startCursor.parag() == sel.endCursor.parag() &&
00894      sel.startCursor.parag()->selectionStart( selId ) == sel.endCursor.parag()->selectionEnd( selId ) )
00895     return FALSE;
00896     if ( sel.endCursor.parag()->paragId() < sel.startCursor.parag()->paragId() ) {
00897     endParag = sel.startCursor.parag();
00898     startParag = sel.endCursor.parag();
00899     }
00900 
00901     KoTextParag *p = startParag;
00902     while ( p ) {
00903     if ( p->rect().contains( pos ) ) {
00904         bool inSel = FALSE;
00905         int selStart = p->selectionStart( selId );
00906         int selEnd = p->selectionEnd( selId );
00907         int y = 0;
00908         int h = 0;
00909         for ( int i = 0; i < p->length(); ++i ) {
00910         if ( i == selStart )
00911             inSel = TRUE;
00912         if ( i == selEnd )
00913             break;
00914         if ( p->at( i )->lineStart ) {
00915             y = (*p->lineStarts.find( i ))->y;
00916             h = (*p->lineStarts.find( i ))->h;
00917         }
00918         if ( pos.y() - p->rect().y() >= y && pos.y() - p->rect().y() <= y + h ) {
00919             if ( inSel && pos.x() >= p->at( i )->x &&
00920              pos.x() <= p->at( i )->x + p->at( i )->width /*p->at( i )->format()->width( p->at( i )->c )*/ )
00921             return TRUE;
00922         }
00923         }
00924     }
00925     if ( pos.y() < p->rect().y() )
00926         break;
00927     if ( p == endParag )
00928         break;
00929     p = p->next();
00930     }
00931 
00932     return FALSE;
00933 }
00934 
00935 QPixmap *KoTextDocument::bufferPixmap( const QSize &s )
00936 {
00937     if ( !buf_pixmap ) {
00938     int w = QABS( s.width() );
00939     int h = QABS( s.height() );
00940     buf_pixmap = new QPixmap( w, h );
00941     } else {
00942     if ( buf_pixmap->width() < s.width() ||
00943          buf_pixmap->height() < s.height() ) {
00944         buf_pixmap->resize( QMAX( s.width(), buf_pixmap->width() ),
00945                 QMAX( s.height(), buf_pixmap->height() ) );
00946     }
00947     }
00948 
00949     return buf_pixmap;
00950 }
00951 
00952 void KoTextDocument::registerCustomItem( KoTextCustomItem *i, KoTextParag *p )
00953 {
00954     if ( i && i->placement() != KoTextCustomItem::PlaceInline )
00955     flow_->registerFloatingItem( i );
00956     p->registerFloatingItem( i );
00957     i->setParagraph( p );
00958     //kdDebug(32500) << "KoTextDocument::registerCustomItem " << (void*)i << endl;
00959     customItems.append( i );
00960 }
00961 
00962 void KoTextDocument::unregisterCustomItem( KoTextCustomItem *i, KoTextParag *p )
00963 {
00964     flow_->unregisterFloatingItem( i );
00965     p->unregisterFloatingItem( i );
00966     i->setParagraph( 0 );
00967     customItems.removeRef( i );
00968 }
00969 
00970 int KoTextDocument::length() const
00971 {
00972     int l = 0;
00973     KoTextParag *p = fParag;
00974     while ( p ) {
00975     l += p->length() - 1; // don't count trailing space
00976     p = p->next();
00977     }
00978     return l;
00979 }
00980 
00981 bool KoTextDocument::visitSelection( int selectionId, KoParagVisitor* visitor, bool forward )
00982 {
00983     KoTextCursor c1 = selectionStartCursor( selectionId );
00984     KoTextCursor c2 = selectionEndCursor( selectionId );
00985     if ( c1 == c2 )
00986         return true;
00987     return visitFromTo( c1.parag(), c1.index(), c2.parag(), c2.index(), visitor, forward );
00988 }
00989 
00990 bool KoTextDocument::hasSelection( int id, bool visible ) const
00991 {
00992     return ( selections.find( id ) != selections.end() &&
00993              ( !visible ||
00994                ( (KoTextDocument*)this )->selectionStartCursor( id ) !=
00995                ( (KoTextDocument*)this )->selectionEndCursor( id ) ) );
00996 }
00997 
00998 void KoTextDocument::setSelectionStart( int id, KoTextCursor *cursor )
00999 {
01000     KoTextDocumentSelection sel;
01001     sel.startCursor = *cursor;
01002     sel.endCursor = *cursor;
01003     sel.swapped = FALSE;
01004     selections[ id ] = sel;
01005 }
01006 
01007 KoTextParag *KoTextDocument::paragAt( int i ) const
01008 {
01009     KoTextParag *s = fParag;
01010     while ( s ) {
01011     if ( s->paragId() == i )
01012         return s;
01013     s = s->next();
01014     }
01015     return 0;
01016 }
01017 
01018 bool KoTextDocument::visitDocument( KoParagVisitor *visitor, bool forward )
01019 {
01020     return visitFromTo( firstParag(), 0, lastParag(), lastParag()->length()-1, visitor, forward );
01021 }
01022 
01023 bool KoTextDocument::visitFromTo( KoTextParag *firstParag, int firstIndex, KoTextParag* lastParag, int lastIndex, KoParagVisitor* visitor, bool forw )
01024 {
01025     if ( firstParag == lastParag )
01026     {
01027         return visitor->visit( firstParag, firstIndex, lastIndex );
01028     }
01029     else
01030     {
01031         bool ret = true;
01032         if ( forw )
01033         {
01034             // the -1 is for the trailing space
01035             ret = visitor->visit( firstParag, firstIndex, firstParag->length() - 1 );
01036             if (!ret) return false;
01037         }
01038         else
01039         {
01040             ret = visitor->visit( lastParag, 0, lastIndex );
01041             if (!ret) return false;
01042         }
01043 
01044         KoTextParag* currentParag = forw ? firstParag->next() : lastParag->prev();
01045         KoTextParag * endParag = forw ? lastParag : firstParag;
01046         while ( currentParag && currentParag != endParag )
01047         {
01048             ret = visitor->visit( currentParag, 0, currentParag->length() - 1 );
01049             if (!ret) return false;
01050             currentParag = forw ? currentParag->next() : currentParag->prev();
01051         }
01052         Q_ASSERT( currentParag );
01053         Q_ASSERT( endParag == currentParag );
01054         if ( forw )
01055             ret = visitor->visit( lastParag, 0, lastIndex );
01056         else
01057             ret = visitor->visit( currentParag, firstIndex, currentParag->length() - 1 );
01058         return ret;
01059     }
01060 }
01061 
01062 static bool is_printer( QPainter *p )
01063 {
01064     return p && p->device() && p->device()->devType() == QInternal::Printer;
01065 }
01066 
01067 KoTextParag *KoTextDocument::drawWYSIWYG( QPainter *p, int cx, int cy, int cw, int ch, const QColorGroup &cg,
01068                                           KoTextZoomHandler* zoomHandler, bool onlyChanged,
01069                                           bool drawCursor, KoTextCursor *cursor,
01070                                           bool resetChanged, uint drawingFlags )
01071 {
01072     m_drawingFlags = drawingFlags;
01073     // We need to draw without double-buffering if
01074     // 1) printing (to send text and not bitmaps to the printer)
01075     // 2) drawing a transparent embedded document
01076     //
01077     if ( is_printer( p ) || ( drawingFlags & TransparentBackground ) ) {
01078     // This stuff relies on doLayout()... simpler to just test for Printer.
01079     // If someone understand doLayout() please tell me (David)
01080     /*if ( isWithoutDoubleBuffer() || par && par->withoutDoubleBuffer ) { */
01081     //setWithoutDoubleBuffer( TRUE );
01082     QRect crect( cx, cy, cw, ch );
01083     drawWithoutDoubleBuffer( p, crect, cg, zoomHandler );
01084     return 0;
01085     }
01086     //setWithoutDoubleBuffer( FALSE );
01087 
01088     if ( !firstParag() )
01089         return 0;
01090 
01091     KoTextParag *lastFormatted = 0;
01092     KoTextParag *parag = firstParag();
01093 
01094     QPixmap *doubleBuffer = 0;
01095     QPainter painter;
01096     // All the coordinates in this method are in view pixels
01097     QRect crect( cx, cy, cw, ch );
01098     Q_ASSERT( ch > 0 );
01099 #ifdef DEBUG_PAINTING
01100     kdDebug(32500) << "\nKoTextDocument::drawWYSIWYG crect=" << crect << endl;
01101 #endif
01102 
01103     // Space above first parag
01104     QRect pixelRect = parag->pixelRect( zoomHandler );
01105     if ( isPageBreakEnabled() && parag && cy <= pixelRect.y() && pixelRect.y() > 0 ) {
01106         QRect r( 0, 0,
01107                  zoomHandler->layoutUnitToPixelX( parag->document()->x() + parag->document()->width() ),
01108                  pixelRect.y() );
01109         r &= crect;
01110         if ( !r.isEmpty() ) {
01111 #ifdef DEBUG_PAINTING
01112             kdDebug(32500) << " drawWYSIWYG: space above first parag: " << r << " (pixels)" << endl;
01113 #endif
01114             p->fillRect( r, cg.brush( QColorGroup::Base ) );
01115         }
01116     }
01117 
01118     while ( parag ) {
01119     lastFormatted = parag;
01120     if ( !parag->isValid() )
01121         parag->format();
01122 
01123     QRect ir = parag->pixelRect( zoomHandler );
01124 #ifdef DEBUG_PAINTING
01125         kdDebug(32500) << " drawWYSIWYG: ir=" << ir << endl;
01126 #endif
01127     if ( isPageBreakEnabled() && parag->next() && ( drawingFlags & TransparentBackground ) == 0 )
01128         {
01129             int nexty = parag->next()->pixelRect(zoomHandler).y();
01130             // Test ir.y+ir.height, which is the first pixel _under_ the parag
01131             // (as opposed ir.bottom() which is the last pixel of the parag)
01132         if ( ir.y() + ir.height() < nexty ) {
01133         QRect r( 0, ir.y() + ir.height(),
01134              zoomHandler->layoutUnitToPixelX( parag->document()->x() + parag->document()->width() ),
01135              nexty - ( ir.y() + ir.height() ) );
01136         r &= crect;
01137         if ( !r.isEmpty() )
01138                 {
01139 #ifdef DEBUG_PAINTING
01140                     kdDebug(32500) << " drawWYSIWYG: space between parag " << parag->paragId() << " and " << parag->next()->paragId() << " : " << r << " (pixels)" << endl;
01141 #endif
01142             p->fillRect( r, cg.brush( QColorGroup::Base ) );
01143                 }
01144         }
01145         }
01146 
01147         if ( !ir.intersects( crect ) ) {
01148             // Paragraph is not in the crect - but let's check if the area on its right is.
01149         ir.setWidth( zoomHandler->layoutUnitToPixelX( parag->document()->width() ) );
01150         if ( ir.intersects( crect ) && ( drawingFlags & TransparentBackground ) == 0 )
01151         p->fillRect( ir.intersect( crect ), cg.brush( QColorGroup::Base ) );
01152         if ( ir.y() > cy + ch ) {
01153                 goto floating;
01154         }
01155     }
01156         else if ( parag->hasChanged() || !onlyChanged ) {
01157             // lineChanged() only makes sense if we're drawing with onlyChanged=true
01158             // otherwise, call setChanged() to make sure we'll paint it all (lineChanged=-1).
01159             // (this avoids having to send onlyChanged to drawParagWYSIWYG)
01160             if ( !onlyChanged && parag->lineChanged() > 0 )
01161                 parag->setChanged( false );
01162             drawParagWYSIWYG( p, parag, cx, cy, cw, ch, doubleBuffer, cg,
01163                               zoomHandler, drawCursor, cursor, resetChanged, drawingFlags );
01164         }
01165     parag = parag->next();
01166     }
01167 
01168     parag = lastParag();
01169 
01170 floating:
01171     pixelRect = parag->pixelRect(zoomHandler);
01172     int docheight = zoomHandler->layoutUnitToPixelY( parag->document()->height() );
01173     if ( pixelRect.y() + pixelRect.height() < docheight ) {
01174         int docwidth = zoomHandler->layoutUnitToPixelX( parag->document()->width() );
01175         if ( ( drawingFlags & TransparentBackground ) == 0 ) {
01176             p->fillRect( 0, pixelRect.y() + pixelRect.height(),
01177                          docwidth, docheight - ( pixelRect.y() + pixelRect.height() ),
01178                          cg.brush( QColorGroup::Base ) );
01179         }
01180     if ( !flow()->isEmpty() ) {
01181         QRect cr( cx, cy, cw, ch );
01182         cr = cr.intersect( QRect( 0, pixelRect.y() + pixelRect.height(), docwidth,
01183                       docheight - ( pixelRect.y() + pixelRect.height() ) ) );
01184         flow()->drawFloatingItems( p, cr.x(), cr.y(), cr.width(), cr.height(), cg, FALSE );
01185     }
01186     }
01187 
01188     if ( buf_pixmap && buf_pixmap->height() > 300 ) {
01189     delete buf_pixmap;
01190     buf_pixmap = 0;
01191     }
01192 
01193     return lastFormatted;
01194 }
01195 
01196 
01197 // Used for printing
01198 void KoTextDocument::drawWithoutDoubleBuffer( QPainter *p, const QRect &cr, const QColorGroup &cg,
01199                                               KoTextZoomHandler* zoomHandler, const QBrush *paper )
01200 {
01201     if ( !firstParag() )
01202         return;
01203 
01204     Q_ASSERT( (m_drawingFlags & DrawSelections) == 0 );
01205     if (m_drawingFlags & DrawSelections)
01206            kdDebug() << kdBacktrace();
01207     if ( paper && ( m_drawingFlags & TransparentBackground ) == 0 ) {
01208         p->setBrushOrigin(  -(int)p->translationX(),  -(int)p->translationY() );
01209         p->fillRect( cr, *paper );
01210     }
01211 
01212     KoTextParag *parag = firstParag();
01213     while ( parag ) {
01214         if ( !parag->isValid() )
01215             parag->format();
01216 
01217         QRect pr( parag->pixelRect( zoomHandler ) );
01218         pr.setLeft( 0 );
01219         pr.setWidth( QWIDGETSIZE_MAX );
01220         // The cliprect is checked in layout units, in KoTextParag::paint
01221         QRect crect_lu( parag->rect() );
01222 
01223         if ( !cr.isNull() && !cr.intersects( pr ) ) {
01224             parag = parag->next();
01225             continue;
01226         }
01227 
01228         p->translate( 0, pr.y() );
01229 
01230         // No need to brush plain white on a printer. Brush all
01231         // other cases (except "full transparent" case).
01232         QBrush brush = cg.brush( QColorGroup::Base );;
01233         bool needBrush = brush.style() != Qt::NoBrush &&
01234                          !(brush.style() == Qt::SolidPattern &&
01235                            brush.color() == Qt::white &&
01236                            is_printer(p));
01237         if ( needBrush && ( m_drawingFlags & TransparentBackground ) == 0 )
01238             p->fillRect( QRect( 0, 0, pr.width(), pr.height() ), brush );
01239 
01240         //p->setBrushOrigin( p->brushOrigin() + QPoint( 0, pr.y() ) );
01241         parag->paint( *p, cg, 0, FALSE,
01242                       crect_lu.x(), crect_lu.y(),
01243                       crect_lu.width(), crect_lu.height() );
01244         p->translate( 0, -pr.y() );
01245         //p->setBrushOrigin( p->brushOrigin() - QPoint( 0, pr.y() ) );
01246         parag = parag->next();
01247     }
01248 }
01249 
01250 // Used for screen display (and also printing?)
01251 // Called by drawWYSIWYG and the app's drawCursor
01252 void KoTextDocument::drawParagWYSIWYG( QPainter *p, KoTextParag *parag, int cx, int cy, int cw, int ch,
01253                                        QPixmap *&doubleBuffer, const QColorGroup &cg,
01254                                        KoTextZoomHandler* zoomHandler, bool drawCursor,
01255                                        KoTextCursor *cursor, bool resetChanged, uint drawingFlags )
01256 {
01257     if ( cw <= 0 || ch <= 0 ) { Q_ASSERT( cw > 0 ); Q_ASSERT( ch > 0 ); return; }
01258 #ifdef DEBUG_PAINTING
01259     kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG " << (void*)parag << " id:" << parag->paragId() << endl;
01260 #endif
01261     m_drawingFlags = drawingFlags;
01262     QPainter *painter = 0;
01263     // Those three rects are in pixels, in the document coordinates (0,0 == topleft of first parag)
01264     QRect rect = parag->pixelRect( zoomHandler ); // the parag rect
01265 
01266     int offsetY = 0;
01267     // Start painting from a given line number.
01268     if ( parag->lineChanged() > -1 )
01269     {
01270         offsetY = zoomHandler->layoutUnitToPixelY( parag->lineY( parag->lineChanged() ) - parag->topMargin() );
01271 #ifdef DEBUG_PAINTING
01272         kdDebug(32500) << " Repainting from lineChanged=" << parag->lineChanged() << " -> adding " << offsetY << " to rect" << endl;
01273 #endif
01274         // Skip the lines that are not repainted by moving Top. The bottom doesn't change.
01275         rect.rTop() += offsetY;
01276     }
01277 
01278     QRect crect( cx, cy, cw, ch ); // the overall crect
01279     QRect ir( rect ); // will be the rect to be repainted
01280 
01281     QBrush brush = cg.brush( QColorGroup::Base );
01282 
01283     // No need to brush plain white on a printer. Brush all
01284     // other cases (except "full transparent" case).
01285     bool needBrush = brush.style() != Qt::NoBrush &&
01286                      ( drawingFlags & TransparentBackground ) == 0 &&
01287                      !(brush.style() == Qt::SolidPattern &&
01288                        brush.color() == Qt::white &&
01289                        is_printer(p));
01290 
01291     bool useDoubleBuffer = !parag->document()->parent();
01292     if ( is_printer(p) )
01293         useDoubleBuffer = FALSE;
01294     // Can't handle transparency using double-buffering, in case of rotation/scaling (due to bitBlt)
01295     // The test on mat is almost like isIdentity(), but allows for translation.
01297     // of being white.
01298     QWMatrix mat = p->worldMatrix();
01299     if ( ( mat.m11() != 1.0 || mat.m22() != 1.0 || mat.m12() != 0.0 || mat.m21() != 0.0 )
01300          && brush.style() != Qt::SolidPattern )
01301         useDoubleBuffer = FALSE;
01302 
01303 #ifdef DEBUG_PAINTING
01304     kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG parag->rect=" << parag->rect()
01305                    << " pixelRect(ir)=" << ir
01306                    << " crect (pixels)=" << crect
01307                    << " useDoubleBuffer=" << useDoubleBuffer << endl;
01308 #endif
01309 
01310     if ( useDoubleBuffer  ) {
01311     painter = new QPainter;
01312     if ( cx >= 0 && cy >= 0 )
01313         ir = ir.intersect( crect );
01314     if ( !doubleBuffer ||
01315          ir.width() > doubleBuffer->width() ||
01316          ir.height() > doubleBuffer->height() )
01317         {
01318         doubleBuffer = bufferPixmap( ir.size() );
01319         }
01320         painter->begin( doubleBuffer );
01321 
01322     } else {
01323         p->save();
01324     painter = p;
01325     painter->translate( ir.x(), ir.y() );
01326     }
01327     // Until the next translate(), (0,0) in the painter will be at ir.topLeft() in reality
01328     //kdDebug() << "KoTextDocument::drawParagWYSIWYG ir=" << ir << endl;
01329 
01330 
01331     // Cumulate ir.x(), ir.y() with the current brush origin
01332     //painter->setBrushOrigin( painter->brushOrigin() + ir.topLeft() );
01333 
01334     if ( useDoubleBuffer || is_printer( painter ) ) {
01335         // Transparent -> grab background from p's device
01336         if ( brush.style() != Qt::SolidPattern ) {
01337             bitBlt( doubleBuffer, 0, 0, p->device(),
01338                     ir.x() + (int)p->translationX(), ir.y() + (int)p->translationY(),
01339                     ir.width(), ir.height() );
01340         }
01341     }
01342 
01343     if ( needBrush )
01344         painter->fillRect( QRect( 0, 0, ir.width(), ir.height() ), brush );
01345 
01346     // Now revert the previous painter translation, and instead make (0,0) the topleft of the PARAGRAPH
01347     painter->translate( rect.x() - ir.x(), rect.y() - ir.y() );
01348 #ifdef DEBUG_PAINTING
01349     kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG translate " << rect.x() - ir.x() << "," << rect.y() - ir.y() << endl;
01350 #endif
01351     //painter->setBrushOrigin( painter->brushOrigin() + rect.topLeft() - ir.topLeft() );
01352 
01353     // The cliprect is checked in layout units, in KoTextParag::paint
01354     QRect crect_lu( zoomHandler->pixelToLayoutUnit( crect ) );
01355 #ifdef DEBUG_PAINTING
01356     kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG crect_lu=" << crect_lu << endl;
01357 #endif
01358 
01359     // paintDefault will paint line 'lineChanged' at its normal Y position.
01360     // But the buffer-pixmap below starts at Y. We need to translate by -Y
01361     // so that the painting happens at the right place.
01362     painter->translate( 0, -offsetY );
01363 
01364     parag->paint( *painter, cg, drawCursor ? cursor : 0, (m_drawingFlags & DrawSelections),
01365                   crect_lu.x(), crect_lu.y(), crect_lu.width(), crect_lu.height() );
01366 
01367 
01368     if ( useDoubleBuffer ) {
01369     delete painter;
01370     painter = 0;
01371     p->drawPixmap( ir.topLeft(), *doubleBuffer, QRect( QPoint( 0, 0 ), ir.size() ) );
01372 #if 0 // for debug!
01373         p->save();
01374         p->setPen( Qt::blue );
01375         p->drawRect( ir.x(), ir.y(), ir.width(), ir.height() );
01376         p->restore();
01377 #endif
01378     } else {
01379         // undo previous translations, painter is 'p', i.e. will be used later on
01380         p->restore();
01381     //painter->translate( -ir.x(), -ir.y() );
01382         //painter->translate( 0, +offsetY );
01383         //painter->setBrushOrigin( painter->brushOrigin() - ir.topLeft() );
01384     }
01385 
01386     if ( needBrush ) {
01387         int docright = zoomHandler->layoutUnitToPixelX( parag->document()->x() + parag->document()->width() );
01388 #ifdef DEBUG_PAINTING
01389 //        kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG my rect is: " << rect << endl;
01390 #endif
01391         if ( rect.x() + rect.width() < docright ) {
01392 #ifdef DEBUG_PAINTING
01393             kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG rect doesn't go up to docright=" << docright << endl;
01394 #endif
01395             p->fillRect( rect.x() + rect.width(), rect.y(),
01396                          docright - ( rect.x() + rect.width() ),
01397                          rect.height(), cg.brush( QColorGroup::Base ) );
01398         }
01399     }
01400 
01401     if ( resetChanged )
01402     parag->setChanged( FALSE );
01403 }
01404 
01405 
01406 KoTextDocCommand *KoTextDocument::deleteTextCommand( KoTextDocument *textdoc, int id, int index, const QMemArray<KoTextStringChar> & str, const CustomItemsMap & customItemsMap, const QValueList<KoParagLayout> & oldParagLayouts )
01407 {
01408     return new KoTextDeleteCommand( textdoc, id, index, str, customItemsMap, oldParagLayouts );
01409 }
01410 
01411 KoTextParag* KoTextDocument::loadOasisText( const QDomElement& bodyElem, KoOasisContext& context, KoTextParag* lastParagraph, KoStyleCollection* styleColl, KoTextParag* nextParagraph )
01412 {
01413     // was OoWriterImport::parseBodyOrSimilar
01414     QDomElement tag;
01415     forEachElement( tag, bodyElem )
01416     {
01417         context.styleStack().save();
01418         const QString localName = tag.localName();
01419         const bool isTextNS = tag.namespaceURI() == KoXmlNS::text;
01420         uint pos = 0;
01421         if ( isTextNS && localName == "p" ) {  // text paragraph
01422             context.fillStyleStack( tag, KoXmlNS::text, "style-name", "paragraph" );
01423 
01424             KoTextParag *parag = createParag( this, lastParagraph, nextParagraph );
01425             parag->loadOasis( tag, context, styleColl, pos );
01426             if ( !lastParagraph )        // First parag
01427                 setFirstParag( parag );
01428             lastParagraph = parag;
01429         }
01430         else if ( isTextNS && localName == "h" ) // heading
01431         {
01432             //kdDebug(32500) << " heading " << endl;
01433             context.fillStyleStack( tag, KoXmlNS::text, "style-name", "paragraph" );
01434             int level = tag.attributeNS( KoXmlNS::text, "outline-level", QString::null ).toInt();
01435             bool listOK = false;
01436             // When a heading is inside a list, it seems that the list prevails.
01437             // Example:
01438             //    <text:list text:style-name="Numbering 1">
01439             //      <text:list-item text:start-value="5">
01440             //        <text:h text:style-name="P2" text:level="4">The header</text:h>
01441             // where P2 has list-style-name="something else"
01442             // Result: the numbering of the header follows "Numbering 1".
01443             // So we use the style for the outline level only if we're not inside a list:
01444             //if ( !context.atStartOfListItem() )
01445             // === The new method for this is that we simply override it after loading.
01446             listOK = context.pushOutlineListLevelStyle( level );
01447             int restartNumbering = -1;
01448             if ( tag.hasAttributeNS( KoXmlNS::text, "start-value" ) )
01449                 // OASIS extension http://lists.oasis-open.org/archives/office/200310/msg00033.html
01450                 restartNumbering = tag.attributeNS( KoXmlNS::text, "start-value", QString::null ).toInt();
01451 
01452             KoTextParag *parag = createParag( this, lastParagraph, nextParagraph );
01453             parag->loadOasis( tag, context, styleColl, pos );
01454             if ( !lastParagraph )        // First parag
01455                 setFirstParag( parag );
01456             lastParagraph = parag;
01457             if ( listOK ) {
01458                 parag->applyListStyle( context, restartNumbering, true /*ordered*/, true /*heading*/, level );
01459                 context.listStyleStack().pop();
01460             }
01461         }
01462         else if ( isTextNS &&
01463                   ( localName == "unordered-list" || localName == "ordered-list" // OOo-1.1
01464                     || localName == "list" || localName == "numbered-paragraph" ) )  // OASIS
01465         {
01466             lastParagraph = loadList( tag, context, lastParagraph, styleColl, nextParagraph );
01467         }
01468         else if ( isTextNS && localName == "section" ) // Temporary support (###TODO)
01469         {
01470             kdDebug(32500) << "Section found!" << endl;
01471             context.fillStyleStack( tag, KoXmlNS::text, "style-name", "section" );
01472             lastParagraph = loadOasisText( tag, context, lastParagraph, styleColl, nextParagraph );
01473         }
01474         else if ( isTextNS && localName == "variable-decls" )
01475         {
01476             // We don't parse variable-decls since we ignore var types right now
01477             // (and just storing a list of available var names wouldn't be much use)
01478         }
01479         else if ( isTextNS && localName == "user-field-decls" )
01480         {
01481             QDomElement fd;
01482             forEachElement( fd, tag )
01483             {
01484                 if ( fd.namespaceURI() == KoXmlNS::text && fd.localName() == "user-field-decl" )
01485                 {
01486                     const QString name = fd.attributeNS( KoXmlNS::text, "name", QString::null );
01487                     const QString value = fd.attributeNS( KoXmlNS::office, "value", QString::null );
01488                     if ( !name.isEmpty() )
01489                         context.variableCollection().setVariableValue( name, value );
01490                 }
01491             }
01492         }
01493         else if ( isTextNS && localName == "number" ) // text:number
01494         {
01495             // This is the number in front of a numbered paragraph,
01496             // written out to help export filters. We can ignore it.
01497         }
01498         else if ( !loadOasisBodyTag( tag, context, lastParagraph, styleColl, nextParagraph ) )
01499         {
01500             kdWarning(32500) << "Unsupported body element '" << localName << "'" << endl;
01501         }
01502 
01503         context.styleStack().restore(); // remove the styles added by the paragraph or list
01504         //use signal slot ?
01505         //m_doc->progressItemLoaded(); // ## check
01506     }
01507     return lastParagraph;
01508 }
01509 
01510 KoTextParag* KoTextDocument::loadList( const QDomElement& list, KoOasisContext& context, KoTextParag* lastParagraph, KoStyleCollection * styleColl, KoTextParag* nextParagraph )
01511 {
01512     //kdDebug(32500) << "loadList: " << list.attributeNS( KoXmlNS::text, "style-name", QString::null ) << endl;
01513 
01514     const QString oldListStyleName = context.currentListStyleName();
01515     if ( list.hasAttributeNS( KoXmlNS::text, "style-name" ) )
01516         context.setCurrentListStyleName( list.attributeNS( KoXmlNS::text, "style-name", QString::null ) );
01517     bool listOK = !context.currentListStyleName().isEmpty();
01518     int level;
01519     if ( list.localName() == "numbered-paragraph" )
01520         level = list.attributeNS( KoXmlNS::text, "level", "1" ).toInt();
01521     else
01522         level = context.listStyleStack().level() + 1;
01523     if ( listOK )
01524         listOK = context.pushListLevelStyle( context.currentListStyleName(), level );
01525 
01526     const QDomElement listStyle = context.listStyleStack().currentListStyle();
01527     // The tag is either list-level-style-number or list-level-style-bullet
01528     const bool orderedList = listStyle.localName() == "list-level-style-number";
01529 
01530     if ( list.localName() == "numbered-paragraph" )
01531     {
01532         // A numbered-paragraph contains paragraphs directly (it's both a list and a list-item)
01533         int restartNumbering = -1;
01534         if ( list.hasAttributeNS( KoXmlNS::text, "start-value" ) )
01535             restartNumbering = list.attributeNS( KoXmlNS::text, "start-value", QString::null ).toInt();
01536         KoTextParag* oldLast = lastParagraph;
01537         lastParagraph = loadOasisText( list, context, lastParagraph, styleColl, nextParagraph );
01538         KoTextParag* firstListItem = oldLast ? oldLast->next() : firstParag();
01539         // Apply list style to first paragraph inside numbered-parag - there's only one anyway
01540         // Keep the "is outline" property though
01541         bool isOutline = firstListItem->counter() && firstListItem->counter()->numbering() == KoParagCounter::NUM_CHAPTER;
01542         firstListItem->applyListStyle( context, restartNumbering, orderedList,
01543                                        isOutline, level );
01544     }
01545     else
01546     {
01547         // Iterate over list items
01548         for ( QDomNode n = list.firstChild(); !n.isNull(); n = n.nextSibling() )
01549         {
01550             QDomElement listItem = n.toElement();
01551             int restartNumbering = -1;
01552             if ( listItem.hasAttributeNS( KoXmlNS::text, "start-value" ) )
01553                 restartNumbering = listItem.attributeNS( KoXmlNS::text, "start-value", QString::null ).toInt();
01554             bool isListHeader = listItem.localName() == "list-header" || listItem.attributeNS( KoXmlNS::text, "is-list-header", QString::null ) == "is-list-header";
01555             KoTextParag* oldLast = lastParagraph;
01556             lastParagraph = loadOasisText( listItem, context, lastParagraph, styleColl, nextParagraph );
01557             KoTextParag* firstListItem = oldLast ? oldLast->next() : firstParag();
01558             KoTextParag* p = firstListItem;
01559             // It's either list-header (normal text on top of list) or list-item
01560             if ( !isListHeader && firstListItem ) {
01561                 // Apply list style to first paragraph inside list-item
01562                 bool isOutline = firstListItem->counter() && firstListItem->counter()->numbering() == KoParagCounter::NUM_CHAPTER;
01563                 firstListItem->applyListStyle( context, restartNumbering, orderedList, isOutline, level );
01564                 p = p->next();
01565             }
01566             // Make text:h inside list-item (as non first child) unnumbered.
01567             while ( p && p != lastParagraph->next() ) {
01568                 if ( p->counter() )
01569                     p->counter()->setNumbering( KoParagCounter::NUM_NONE );
01570                 p = p->next();
01571             }
01572         }
01573     }
01574     if ( listOK )
01575         context.listStyleStack().pop();
01576     context.setCurrentListStyleName( oldListStyleName );
01577     return lastParagraph;
01578 }
01579 
01580 void KoTextDocument::saveOasisContent( KoXmlWriter& writer, KoSavingContext& context ) const
01581 {
01582     // Basically just call saveOasis on every paragraph.
01583     // KWord doesn't use this method because it does table-of-contents-handling in addition.
01584     KoTextParag* parag = firstParag();
01585     while ( parag ) {
01586         // Save the whole parag, without the trailing space.
01587         parag->saveOasis( writer, context, 0, parag->lastCharPos() );
01588         parag = parag->next();
01589     }
01590 }
01591 
01592 #include "KoTextDocument.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys