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