F:/KPlato/koffice/libs/kotext/KoTextParag.cpp

Aller à la documentation de ce fichier.
00001 /* This file is part of the KDE project
00002    Copyright (C) 2001-2006 David Faure <faure@kde.org>
00003    Copyright (C) 2005-2006 Martin Ellis <martin.ellis@kdemail.net>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009 
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public License
00016    along with this library; see the file COPYING.LIB.  If not, write to
00017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018  * Boston, MA 02110-1301, USA.
00019 */
00020 
00021 #include "KoTextParag.h"
00022 #include "KoTextDocument.h"
00023 #include "KoParagCounter.h"
00024 #include "KoTextZoomHandler.h"
00025 #include "KoStyleCollection.h"
00026 #include "KoVariable.h"
00027 #include <KoOasisContext.h>
00028 #include <KoXmlWriter.h>
00029 #include <KoGenStyles.h>
00030 #include <KoDom.h>
00031 #include <KoXmlNS.h>
00032 #include <KoUnit.h>
00033 #include <kglobal.h>
00034 #include <klocale.h>
00035 #include <kdebug.h>
00036 #include <kglobalsettings.h>
00037 #include <assert.h>
00038 //Added by qt3to4:
00039 #include <Q3MemArray>
00040 #include <Q3PtrList>
00041 
00042 //#define DEBUG_PAINT
00043 
00044 KoTextParag::KoTextParag( KoTextDocument *d, KoTextParag *pr, KoTextParag *nx, bool updateIds )
00045     : p( pr ), n( nx ), doc( d ),
00046       m_invalid( true ),
00047       changed( false ),
00048       fullWidth( true ),
00049       newLinesAllowed( true ), // default in kotext
00050       visible( true ), //breakable( true ),
00051       movedDown( false ),
00052       m_toc( false ),
00053       align( 0 ),
00054       m_lineChanged( -1 ),
00055       m_wused( 0 ),
00056       mSelections( 0 ),
00057       mFloatingItems( 0 ),
00058       tArray( 0 )
00059 {
00060     defFormat = formatCollection()->defaultFormat();
00061 
00062     if ( p ) {
00063         p->n = this;
00064     }
00065     if ( n ) {
00066         n->p = this;
00067     }
00068 
00069     if ( !p && doc )
00070         doc->setFirstParag( this );
00071     if ( !n && doc )
00072         doc->setLastParag( this );
00073 
00074     //firstFormat = true; //// unused
00075     //firstPProcess = true;
00076     //state = -1;
00077     //needPreProcess = false;
00078 
00079     if ( p )
00080         id = p->id + 1;
00081     else
00082         id = 0;
00083     if ( n && updateIds ) {
00084         KoTextParag *s = n;
00085         while ( s ) {
00086             s->id = s->p->id + 1;
00087             //s->lm = s->rm = s->tm = s->bm = -1, s->flm = -1;
00088             s = s->n;
00089         }
00090     }
00091 
00092     str = new KoTextString();
00093     str->insert( 0, " ", formatCollection()->defaultFormat() );
00094     setJoinBorder( true );
00095 }
00096 
00097 KoTextParag::~KoTextParag()
00098 {
00099     //kDebug(32500) << "KoTextParag::~KoTextParag " << this << " id=" << paragId() << endl;
00100 
00101     // #107961: unregister custom items; KoTextString::clear() will delete them
00102     const int len = str->length();
00103     for ( int i = 0; i < len; ++i ) {
00104         KoTextStringChar *c = at( i );
00105         if ( doc && c->isCustom() ) {
00106             doc->unregisterCustomItem( c->customItem(), this );
00107             //removeCustomItem();
00108         }
00109     }
00110 
00111     delete str;
00112     str = 0;
00113 //    if ( doc && p == doc->minwParag ) {
00114 //      doc->minwParag = 0;
00115 //      doc->minw = 0;
00116 //    }
00117     delete [] tArray;
00118     //delete eData;
00119     QMap<int, KoTextParagLineStart*>::Iterator it = lineStarts.begin();
00120     for ( ; it != lineStarts.end(); ++it )
00121         delete *it;
00122     if ( mSelections ) delete mSelections;
00123     if ( mFloatingItems ) delete mFloatingItems;
00124 
00125     if (p)
00126        p->setNext(n);
00127     if (n)
00128        n->setPrev(p);
00129 
00131     if ( !doc->isDestroying() )
00132     {
00133         doc->informParagraphDeleted( this );
00134     }
00135     //kDebug(32500) << "KoTextParag::~KoTextParag " << this << " done" << endl;
00137 }
00138 
00139 void KoTextParag::setNext( KoTextParag *s )
00140 {
00141     n = s;
00142     if ( !n && doc )
00143         doc->setLastParag( this );
00144 }
00145 
00146 void KoTextParag::setPrev( KoTextParag *s )
00147 {
00148     p = s;
00149     if ( !p && doc )
00150         doc->setFirstParag( this );
00151 }
00152 
00153 void KoTextParag::invalidate( int /*chr, ignored*/ )
00154 {
00155     m_invalid = true;
00156 #if 0
00157     if ( invalid < 0 )
00158         invalid = chr;
00159     else
00160         invalid = qMin( invalid, chr );
00161 #endif
00162 }
00163 
00164 void KoTextParag::setChanged( bool b, bool /*recursive*/ )
00165 {
00166     changed = b;
00167     m_lineChanged = -1; // all
00168 }
00169 
00170 void KoTextParag::setLineChanged( short int line )
00171 {
00172     if ( m_lineChanged == -1 ) {
00173         if ( !changed ) // only if the whole parag wasn't "changed" already
00174             m_lineChanged = line;
00175     }
00176     else
00177         m_lineChanged = qMin( m_lineChanged, line ); // also works if line=-1
00178     changed = true;
00179     //kDebug(32500) << "KoTextParag::setLineChanged line=" << line << " -> m_lineChanged=" << m_lineChanged << endl;
00180 }
00181 
00182 void KoTextParag::insert( int index, const QString &s )
00183 {
00184     str->insert( index, s, formatCollection()->defaultFormat() );
00185     invalidate( index );
00186     //needPreProcess = true;
00187 }
00188 
00189 void KoTextParag::truncate( int index )
00190 {
00191     str->truncate( index );
00192     insert( length(), " " );
00193     //needPreProcess = true;
00194 }
00195 
00196 void KoTextParag::remove( int index, int len )
00197 {
00198     if ( index + len - str->length() > 0 )
00199         return;
00200     for ( int i = index; i < index + len; ++i ) {
00201         KoTextStringChar *c = at( i );
00202         if ( doc && c->isCustom() ) {
00203             doc->unregisterCustomItem( c->customItem(), this );
00204             //removeCustomItem();
00205         }
00206     }
00207     str->remove( index, len );
00208     invalidate( 0 );
00209     //needPreProcess = true;
00210 }
00211 
00212 void KoTextParag::join( KoTextParag *s )
00213 {
00214     //kDebug(32500) << "KoTextParag::join this=" << paragId() << " (length " << length() << ") with " << s->paragId() << " (length " << s->length() << ")" << endl;
00215     int oh = r.height() + s->r.height();
00216     n = s->n;
00217     if ( n )
00218         n->p = this;
00219     else 
00220         doc->setLastParag( this );
00221 
00222     int start = str->length();
00223     if ( length() > 0 && at( length() - 1 )->c == ' ' ) {
00224         remove( length() - 1, 1 );
00225         --start;
00226     }
00227     append( s->str->toString(), true );
00228 
00229     for ( int i = 0; i < s->length(); ++i ) {
00230         if ( doc->useFormatCollection() ) {
00231             s->str->at( i ).format()->addRef();
00232             str->setFormat( i + start, s->str->at( i ).format(), true );
00233         }
00234         if ( s->str->at( i ).isCustom() ) {
00235             KoTextCustomItem * item = s->str->at( i ).customItem();
00236             str->at( i + start ).setCustomItem( item );
00237             s->str->at( i ).loseCustomItem();
00238             doc->unregisterCustomItem( item, s ); // ### missing in QRT
00239             doc->registerCustomItem( item, this );
00240         }
00241     }
00242     Q_ASSERT(str->at(str->length()-1).c == ' ');
00243 
00244     /*if ( !extraData() && s->extraData() ) {
00245         setExtraData( s->extraData() );
00246         s->setExtraData( 0 );
00247     } else if ( extraData() && s->extraData() ) {
00248         extraData()->join( s->extraData() );
00249         }*/
00250     delete s;
00251     invalidate( 0 );
00253     invalidateCounters();
00255     r.setHeight( oh );
00256     //needPreProcess = true;
00257     if ( n ) {
00258         KoTextParag *s = n;
00259         while ( s ) {
00260             s->id = s->p->id + 1;
00261             //s->state = -1;
00262             //s->needPreProcess = true;
00263             s->changed = true;
00264             s = s->n;
00265         }
00266     }
00267     format();
00268     //state = -1;
00269 }
00270 
00271 void KoTextParag::move( int &dy )
00272 {
00273     //kDebug(32500) << "KoTextParag::move paragId=" << paragId() << " dy=" << dy << endl;
00274     if ( dy == 0 )
00275         return;
00276     changed = true;
00277     r.moveBy( 0, dy );
00278     if ( mFloatingItems ) {
00279         for ( KoTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() ) {
00280                 i->finalize();
00281         }
00282     }
00283     //if ( p )
00284     //    p->lastInFrame = true; // Qt does this, but the loop at the end of format() calls move a lot!
00285 
00286     movedDown = false;
00287 
00288     // do page breaks if required
00289     if ( doc && doc->isPageBreakEnabled() ) {
00290         int shift;
00291         if ( ( shift = doc->formatter()->formatVertically(  doc, this ) ) ) {
00292             if ( p )
00293                 p->setChanged( true );
00294             dy += shift;
00295         }
00296     }
00297 }
00298 
00299 void KoTextParag::format( int start, bool doMove )
00300 {
00301     if ( !str || str->length() == 0 || !formatter() )
00302         return;
00303 
00304     if ( isValid() )
00305         return;
00306 
00307     //kDebug(32500) << "KoTextParag::format " << this << " id:" << paragId() << endl;
00308 
00309     r.moveTopLeft( QPoint( documentX(), p ? p->r.y() + p->r.height() : documentY() ) );
00310     //if ( p )
00311     //    p->lastInFrame = false;
00312 
00313     movedDown = false;
00314     bool formattedAgain = false;
00315 
00316  formatAgain:
00317     r.setWidth( documentWidth() );
00318 
00319     // Not really useful....
00320     if ( doc && mFloatingItems ) {
00321         for ( KoTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() ) {
00322             if ( i->placement() == KoTextCustomItem::PlaceRight )
00323                 i->move( r.x() + r.width() - i->width, r.y() );
00324             else
00325                 i->move( i->x(), r.y() );
00326         }
00327     }
00328     QMap<int, KoTextParagLineStart*> oldLineStarts = lineStarts;
00329     lineStarts.clear();
00330     int y;
00331     bool formatterWorked = formatter()->format( doc, this, start, oldLineStarts, y, m_wused );
00332 
00333     // It can't happen that width < minimumWidth -- hopefully.
00334     //r.setWidth( qMax( r.width(), formatter()->minimumWidth() ) );
00335     //m_minw = formatter()->minimumWidth();
00336 
00337     QMap<int, KoTextParagLineStart*>::Iterator it = oldLineStarts.begin();
00338 
00339     for ( ; it != oldLineStarts.end(); ++it )
00340         delete *it;
00341 
00342 /*    if ( hasBorder() || str->isRightToLeft() )
00345     {
00346         setWidth( textDocument()->width() - 1 );
00347     }
00348     else*/
00349     {
00350         if ( lineStarts.count() == 1 ) { //&& ( !doc || doc->flow()->isEmpty() ) ) {
00351 // kotext: for proper parag borders, we want all parags to be as wide as linestart->w
00352 /*            if ( !str->isBidi() ) {
00353                 KoTextStringChar *c = &str->at( str->length() - 1 );
00354                 r.setWidth( c->x + c->width );
00355             } else*/ {
00356                 r.setWidth( lineStarts[0]->w );
00357             }
00358         }
00359         if ( newLinesAllowed ) {
00360             it = lineStarts.begin();
00361             int usedw = 0; int lineid = 0;
00362             for ( ; it != lineStarts.end(); ++it, ++lineid ) {
00363                 usedw = qMax( usedw, (*it)->w );
00364             }
00365             if ( r.width() <= 0 ) {
00366                 // if the user specifies an invalid rect, this means that the
00367                 // bounding box should grow to the width that the text actually
00368                 // needs
00369                 r.setWidth( usedw );
00370             } else {
00371                 r.setWidth( qMin( usedw, r.width() ) );
00372             }
00373         }
00374     }
00375 
00376     if ( y != r.height() )
00377         r.setHeight( y );
00378 
00379     if ( !visible )
00380         r.setHeight( 0 );
00381 
00382     // do page breaks if required
00383     if ( doc && doc->isPageBreakEnabled() ) {
00384         int shift = doc->formatter()->formatVertically( doc, this );
00385         //kDebug(32500) << "formatVertically returned shift=" << shift << endl;
00386         if ( shift && !formattedAgain ) {
00387             formattedAgain = true;
00388             goto formatAgain;
00389         }
00390     }
00391 
00392     if ( doc )
00393         doc->formatter()->postFormat( this );
00394 
00395     if ( n && doMove && n->isValid() && r.y() + r.height() != n->r.y() ) {
00396         //kDebug(32500) << "r=" << r << " n->r=" << n->r << endl;
00397         int dy = ( r.y() + r.height() ) - n->r.y();
00398         KoTextParag *s = n;
00399         bool makeInvalid = false; //p && p->lastInFrame;
00400         //kDebug(32500) << "might move of dy=" << dy << ". previous's lastInFrame (=makeInvalid): " << makeInvalid << endl;
00401         while ( s && dy ) {
00402             if ( s->movedDown ) { // (not in QRT) : moved down -> invalidate and stop moving down
00403                 s->invalidate( 0 ); // (there is no point in moving down a parag that has a frame break...)
00404                 break;
00405             }
00406             if ( !s->isFullWidth() )
00407                 makeInvalid = true;
00408             if ( makeInvalid )
00409                 s->invalidate( 0 );
00410             s->move( dy );
00411             //if ( s->lastInFrame )
00412             //    makeInvalid = true;
00413             s = s->n;
00414         }
00415     }
00416 
00417 //#define DEBUG_CI_PLACEMENT
00418     if ( mFloatingItems ) {
00419 #ifdef DEBUG_CI_PLACEMENT
00420         kDebug(32500) << lineStarts.count() << " lines" << endl;
00421 #endif
00422         // Place custom items - after the formatting is finished
00423         int len = length();
00424         int line = -1;
00425         int lineY = 0; // the one called "cy" in other algos
00426         int baseLine = 0;
00427         QMap<int, KoTextParagLineStart*>::Iterator it = lineStarts.begin();
00428         for ( int i = 0 ; i < len; ++i ) {
00429             KoTextStringChar *chr = &str->at( i );
00430             if ( chr->lineStart ) {
00431                 ++line;
00432                 if ( line > 0 )
00433                     ++it;
00434                 lineY = (*it)->y;
00435                 baseLine = (*it)->baseLine;
00436 #ifdef DEBUG_CI_PLACEMENT
00437                 kDebug(32500) << "New line (" << line << "): lineStart=" << (*it) << " lineY=" << lineY << " baseLine=" << baseLine << " height=" << (*it)->h << endl;
00438 #endif
00439             }
00440             if ( chr->isCustom() ) {
00441                 int x = chr->x;
00442                 KoTextCustomItem* item = chr->customItem();
00443                 Q_ASSERT( baseLine >= item->ascent() ); // something went wrong in KoTextFormatter if this isn't the case
00444                 int y = lineY + baseLine - item->ascent();
00445 #ifdef DEBUG_CI_PLACEMENT
00446                 kDebug(32500) << "Custom item: i=" << i << " x=" << x << " lineY=" << lineY << " baseLine=" << baseLine << " ascent=" << item->ascent() << " -> y=" << y << endl;
00447 #endif
00448                 item->move( x, y );
00449                 item->finalize();
00450             }
00451         }
00452     }
00453 
00454     //firstFormat = false; //// unused
00455     if ( formatterWorked ) // only if it worked, i.e. we had some width to format it
00456     {
00457         m_invalid = false;
00458     }
00459     changed = true;
00460     //####   str->setTextChanged( false );
00461 }
00462 
00463 int KoTextParag::lineHeightOfChar( int i, int *bl, int *y ) const
00464 {
00465     if ( !isValid() )
00466         ( (KoTextParag*)this )->format();
00467 
00468     QMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.end();
00469     --it;
00470     for ( ;; ) {
00471         if ( i >= it.key() ) {
00472             if ( bl )
00473                 *bl = ( *it )->baseLine;
00474             if ( y )
00475                 *y = ( *it )->y;
00476             return ( *it )->h;
00477         }
00478         if ( it == lineStarts.begin() )
00479             break;
00480         --it;
00481     }
00482 
00483     kWarning(32500) << "KoTextParag::lineHeightOfChar: couldn't find lh for " << i << endl;
00484     return 15;
00485 }
00486 
00487 KoTextStringChar *KoTextParag::lineStartOfChar( int i, int *index, int *line ) const
00488 {
00489     if ( !isValid() )
00490         ( (KoTextParag*)this )->format();
00491 
00492     int l = (int)lineStarts.count() - 1;
00493     QMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.end();
00494     --it;
00495     for ( ;; ) {
00496         if ( i >= it.key() ) {
00497             if ( index )
00498                 *index = it.key();
00499             if ( line )
00500                 *line = l;
00501             return &str->at( it.key() );
00502         }
00503         if ( it == lineStarts.begin() )
00504             break;
00505         --it;
00506         --l;
00507     }
00508 
00509     kWarning(32500) << "KoTextParag::lineStartOfChar: couldn't find " << i << endl;
00510     return 0;
00511 }
00512 
00513 int KoTextParag::lines() const
00514 {
00515     if ( !isValid() )
00516         ( (KoTextParag*)this )->format();
00517 
00518     return (int)lineStarts.count();
00519 }
00520 
00521 KoTextStringChar *KoTextParag::lineStartOfLine( int line, int *index ) const
00522 {
00523     if ( !isValid() )
00524         ( (KoTextParag*)this )->format();
00525 
00526     if ( line >= 0 && line < (int)lineStarts.count() ) {
00527         QMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
00528         while ( line-- > 0 )
00529             ++it;
00530         int i = it.key();
00531         if ( index )
00532             *index = i;
00533         return &str->at( i );
00534     }
00535 
00536     kWarning(32500) << "KoTextParag::lineStartOfLine: couldn't find " << line << endl;
00537     return 0;
00538 }
00539 
00540 int KoTextParag::leftGap() const
00541 {
00542     if ( !isValid() )
00543         ( (KoTextParag*)this )->format();
00544 
00545     int line = 0;
00546     int x = str->at(0).x;  /* set x to x of first char */
00547     if ( str->isBidi() ) {
00548         for ( int i = 1; i < str->length(); ++i )
00549             x = qMin(x, str->at(i).x);
00550         return x;
00551     }
00552 
00553     QMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
00554     while (line < (int)lineStarts.count()) {
00555         int i = it.key(); /* char index */
00556         x = qMin(x, str->at(i).x);
00557         ++it;
00558         ++line;
00559     }
00560     return x;
00561 }
00562 
00563 void KoTextParag::setFormat( int index, int len, const KoTextFormat *_f, bool useCollection, int flags )
00564 {
00565     Q_ASSERT( useCollection ); // just for info
00566     if ( index < 0 )
00567         index = 0;
00568     if ( index > str->length() - 1 )
00569         index = str->length() - 1;
00570     if ( index + len >= str->length() )
00571         len = str->length() - index;
00572 
00573     KoTextFormatCollection *fc = 0;
00574     if ( useCollection )
00575         fc = formatCollection();
00576     KoTextFormat *of;
00577     for ( int i = 0; i < len; ++i ) {
00578         of = str->at( i + index ).format();
00579         if ( !changed && _f->key() != of->key() )
00580             changed = true;
00581         // Check things that need the textformatter to run
00582         // (e.g. not color changes)
00583         // ######## Is this test exhaustive?
00584         if ( m_invalid == false &&
00585              ( _f->font().family() != of->font().family() ||
00586                _f->pointSize() != of->pointSize() ||
00587                _f->font().weight() != of->font().weight() ||
00588                _f->font().italic() != of->font().italic() ||
00589                _f->vAlign() != of->vAlign() ||
00590                _f->relativeTextSize() != of->relativeTextSize() ||
00591                _f->offsetFromBaseLine() != of->offsetFromBaseLine() ||
00592                _f->wordByWord() != of->wordByWord()  ||
00593                _f->attributeFont() != of->attributeFont() ||
00594                _f->language() != of->language() ||
00595                _f->hyphenation() != of->hyphenation() ||
00596                _f->shadowDistanceX() != of->shadowDistanceX() ||
00597                _f->shadowDistanceY() != of->shadowDistanceY()
00598                  ) ) {
00599             invalidate( 0 );
00600         }
00601         if ( flags == -1 || flags == KoTextFormat::Format || !fc ) {
00602 #ifdef DEBUG_COLLECTION
00603             kDebug(32500) << " KoTextParag::setFormat, will use format(f) " << f << " " << _f->key() << endl;
00604 #endif
00605             KoTextFormat* f = fc ? fc->format( _f ) : const_cast<KoTextFormat *>( _f );
00606             str->setFormat( i + index, f, useCollection, true );
00607         } else {
00608 #ifdef DEBUG_COLLECTION
00609             kDebug(32500) << " KoTextParag::setFormat, will use format(of,f,flags) of=" << of << " " << of->key() << ", f=" << _f << " " << _f->key() << endl;
00610 #endif
00611             KoTextFormat *fm = fc->format( of, _f, flags );
00612 #ifdef DEBUG_COLLECTION
00613             kDebug(32500) << " KoTextParag::setFormat, format(of,f,flags) returned " << fm << " " << fm->key() << " " << endl;
00614 #endif
00615             str->setFormat( i + index, fm, useCollection );
00616         }
00617     }
00618 }
00619 
00620 void KoTextParag::drawCursorDefault( QPainter &painter, KoTextCursor *cursor, int curx, int cury, int curh, const QColorGroup &cg )
00621 {
00622     painter.fillRect( QRect( curx, cury, 1, curh ), cg.color( QColorGroup::Text ) );
00623     painter.save();
00624     if ( str->isBidi() ) {
00625         const int d = 4;
00626         if ( at( cursor->index() )->rightToLeft ) {
00627             painter.setPen( Qt::black );
00628             painter.drawLine( curx, cury, curx - d / 2, cury + d / 2 );
00629             painter.drawLine( curx, cury + d, curx - d / 2, cury + d / 2 );
00630         } else {
00631             painter.setPen( Qt::black );
00632             painter.drawLine( curx, cury, curx + d / 2, cury + d / 2 );
00633             painter.drawLine( curx, cury + d, curx + d / 2, cury + d / 2 );
00634         }
00635     }
00636     painter.restore();
00637 }
00638 
00639 int *KoTextParag::tabArray() const
00640 {
00641     int *ta = tArray;
00642     if ( !ta && doc )
00643         ta = doc->tabArray();
00644     return ta;
00645 }
00646 
00647 int KoTextParag::nextTabDefault( int, int x )
00648 {
00649     int *ta = tArray;
00650     //if ( doc ) {
00651         if ( !ta )
00652             ta = doc->tabArray();
00653         int tabStopWidth = doc->tabStopWidth();
00654     //}
00655     if ( tabStopWidth != 0 )
00656         return tabStopWidth*(x/tabStopWidth+1);
00657     else
00658         return x;
00659 }
00660 
00661 KoTextFormatCollection *KoTextParag::formatCollection() const
00662 {
00663     if ( doc )
00664         return doc->formatCollection();
00665     //if ( !qFormatCollection )
00666     //    qFormatCollection = new KoTextFormatCollection;
00667     //return qFormatCollection;
00668     return 0L;
00669 }
00670 
00671 void KoTextParag::show()
00672 {
00673     if ( visible )
00674         return;
00675     visible = true;
00676 }
00677 
00678 void KoTextParag::hide()
00679 {
00680     if ( !visible )
00681         return;
00682     visible = false;
00683 }
00684 
00685 void KoTextParag::setDirection( QChar::Direction d )
00686 {
00687     if ( str && str->direction() != d ) {
00688         str->setDirection( d );
00689         invalidate( 0 );
00691         m_layout.direction = d;
00692         invalidateCounters(); // #47178
00694     }
00695 }
00696 
00697 QChar::Direction KoTextParag::direction() const
00698 {
00699     return (str ? str->direction() : QChar::DirON );
00700 }
00701 
00702 void KoTextParag::setSelection( int id, int start, int end )
00703 {
00704     QMap<int, KoTextParagSelection>::ConstIterator it = selections().find( id );
00705     if ( it != mSelections->end() ) {
00706         if ( start == ( *it ).start && end == ( *it ).end )
00707             return;
00708     }
00709 
00710     KoTextParagSelection sel;
00711     sel.start = start;
00712     sel.end = end;
00713     (*mSelections)[ id ] = sel;
00714     setChanged( true, true );
00715 }
00716 
00717 void KoTextParag::removeSelection( int id )
00718 {
00719     if ( !hasSelection( id ) )
00720         return;
00721     if ( mSelections )
00722         mSelections->remove( id );
00723     setChanged( true, true );
00724 }
00725 
00726 int KoTextParag::selectionStart( int id ) const
00727 {
00728     if ( !mSelections )
00729         return -1;
00730     QMap<int, KoTextParagSelection>::ConstIterator it = mSelections->find( id );
00731     if ( it == mSelections->end() )
00732         return -1;
00733     return ( *it ).start;
00734 }
00735 
00736 int KoTextParag::selectionEnd( int id ) const
00737 {
00738     if ( !mSelections )
00739         return -1;
00740     QMap<int, KoTextParagSelection>::ConstIterator it = mSelections->find( id );
00741     if ( it == mSelections->end() )
00742         return -1;
00743     return ( *it ).end;
00744 }
00745 
00746 bool KoTextParag::hasSelection( int id ) const
00747 {
00748     if ( !mSelections )
00749         return false;
00750     QMap<int, KoTextParagSelection>::ConstIterator it = mSelections->find( id );
00751     if ( it == mSelections->end() )
00752         return false;
00753     return ( *it ).start != ( *it ).end || length() == 1;
00754 }
00755 
00756 bool KoTextParag::fullSelected( int id ) const
00757 {
00758     if ( !mSelections )
00759         return false;
00760     QMap<int, KoTextParagSelection>::ConstIterator it = mSelections->find( id );
00761     if ( it == mSelections->end() )
00762         return false;
00763     return ( *it ).start == 0 && ( *it ).end == str->length() - 1;
00764 }
00765 
00766 int KoTextParag::lineY( int l ) const
00767 {
00768     if ( l > (int)lineStarts.count() - 1 ) {
00769         kWarning(32500) << "KoTextParag::lineY: line " << l << " out of range!" << endl;
00770         return 0;
00771     }
00772 
00773     if ( !isValid() )
00774         ( (KoTextParag*)this )->format();
00775 
00776     QMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
00777     while ( l-- > 0 )
00778         ++it;
00779     return ( *it )->y;
00780 }
00781 
00782 int KoTextParag::lineBaseLine( int l ) const
00783 {
00784     if ( l > (int)lineStarts.count() - 1 ) {
00785         kWarning(32500) << "KoTextParag::lineBaseLine: line " << l << " out of range!" << endl;
00786         return 10;
00787     }
00788 
00789     if ( !isValid() )
00790         ( (KoTextParag*)this )->format();
00791 
00792     QMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
00793     while ( l-- > 0 )
00794         ++it;
00795     return ( *it )->baseLine;
00796 }
00797 
00798 int KoTextParag::lineHeight( int l ) const
00799 {
00800     if ( l > (int)lineStarts.count() - 1 ) {
00801         kWarning(32500) << "KoTextParag::lineHeight: line " << l << " out of range!" << endl;
00802         return 15;
00803     }
00804 
00805     if ( !isValid() )
00806         ( (KoTextParag*)this )->format();
00807 
00808     QMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
00809     while ( l-- > 0 )
00810         ++it;
00811     return ( *it )->h;
00812 }
00813 
00814 void KoTextParag::lineInfo( int l, int &y, int &h, int &bl ) const
00815 {
00816     if ( l > (int)lineStarts.count() - 1 ) {
00817         kWarning(32500) << "KoTextParag::lineInfo: line " << l << " out of range!" << endl;
00818         kDebug(32500) << (int)lineStarts.count() - 1 << " " << l << endl;
00819         y = 0;
00820         h = 15;
00821         bl = 10;
00822         return;
00823     }
00824 
00825     if ( !isValid() )
00826         ( (KoTextParag*)this )->format();
00827 
00828     QMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
00829     while ( l-- > 0 )
00830         ++it;
00831     y = ( *it )->y;
00832     h = ( *it )->h;
00833     bl = ( *it )->baseLine;
00834 }
00835 
00836 uint KoTextParag::alignment() const
00837 {
00838     return align;
00839 }
00840 
00841 void KoTextParag::setFormat( KoTextFormat *fm )
00842 {
00843 #if 0
00844     bool doUpdate = false;
00845     if (defFormat && (defFormat != formatCollection()->defaultFormat()))
00846        doUpdate = true;
00847 #endif
00848     defFormat = formatCollection()->format( fm );
00849 #if 0
00850     if ( !doUpdate )
00851         return;
00852     for ( int i = 0; i < length(); ++i ) {
00853         if ( at( i )->format()->styleName() == defFormat->styleName() )
00854             at( i )->format()->updateStyle();
00855     }
00856 #endif
00857 }
00858 
00859 KoTextFormatterBase *KoTextParag::formatter() const
00860 {
00861     if ( doc )
00862         return doc->formatter();
00863     return 0;
00864 }
00865 
00866 /*int KoTextParag::minimumWidth() const
00867 {
00868     //return doc ? doc->minimumWidth() : 0;
00869     return m_minw;
00870 }*/
00871 
00872 int KoTextParag::widthUsed() const
00873 {
00874     return m_wused;
00875 }
00876 
00877 void KoTextParag::setTabArray( int *a )
00878 {
00879     delete [] tArray;
00880     tArray = a;
00881 }
00882 
00883 void KoTextParag::setTabStops( int tw )
00884 {
00885     if ( doc )
00886         doc->setTabStops( tw );
00887     //else
00888     //    tabStopWidth = tw;
00889 }
00890 
00891 QMap<int, KoTextParagSelection> &KoTextParag::selections() const
00892 {
00893     if ( !mSelections )
00894         ((KoTextParag *)this)->mSelections = new QMap<int, KoTextParagSelection>;
00895     return *mSelections;
00896 }
00897 
00898 Q3PtrList<KoTextCustomItem> &KoTextParag::floatingItems() const
00899 {
00900     if ( !mFloatingItems )
00901         ((KoTextParag *)this)->mFloatingItems = new Q3PtrList<KoTextCustomItem>;
00902     return *mFloatingItems;
00903 }
00904 
00905 void KoTextCursor::setIndex( int i, bool /*restore*/ )
00906 {
00907 // Note: QRT doesn't allow to position the cursor at string->length
00908 // However we need it, when applying a style to a paragraph, so that
00909 // the trailing space gets the style change applied as well.
00910 // Obviously "right of the trailing space" isn't a good place for a real
00911 // cursor, but this needs to be checked somewhere else.
00912     if ( i < 0 || i > string->length() ) {
00913 #if defined(QT_CHECK_RANGE)
00914         kWarning(32500) << "KoTextCursor::setIndex: " << i << " out of range" << endl;
00915         //abort();
00916 #endif
00917         i = i < 0 ? 0 : string->length() - 1;
00918     }
00919 
00920     tmpIndex = -1;
00921     idx = i;
00922 }
00923 
00925 
00926 // Return the counter associated with this paragraph.
00927 KoParagCounter *KoTextParag::counter()
00928 {
00929     if ( !m_layout.counter )
00930         return 0L;
00931 
00932     // Garbage collect un-needed counters.
00933     if ( m_layout.counter->numbering() == KoParagCounter::NUM_NONE
00934         // [keep it for unnumbered outlines (the depth is useful)]
00935          && ( !m_layout.style || !m_layout.style->isOutline() ) )
00936         setNoCounter();
00937     return m_layout.counter;
00938 }
00939 
00940 void KoTextParag::setMargin( Q3StyleSheetItem::Margin m, double _i )
00941 {
00942     //kDebug(32500) << "KoTextParag::setMargin " << m << " margin " << _i << endl;
00943     m_layout.margins[m] = _i;
00944     if ( m == Q3StyleSheetItem::MarginTop && prev() )
00945         prev()->invalidate(0);     // for top margin (post-1.1: remove this, not necessary anymore)
00946     invalidate(0);
00947 }
00948 
00949 void KoTextParag::setMargins( const double * margins )
00950 {
00951     for ( int i = 0 ; i < 5 ; ++i )
00952         m_layout.margins[i] = margins[i];
00953     invalidate(0);
00954 }
00955 
00956 void KoTextParag::setAlign( int align )
00957 {
00958     Q_ASSERT( align <= Qt::AlignJustify );
00959     align &= Qt::AlignHorizontal_Mask;
00960     setAlignment( align );
00961     m_layout.alignment = align;
00962 }
00963 
00964 int KoTextParag::resolveAlignment() const
00965 {
00966     if ( (int)m_layout.alignment == Qt::AlignLeft )
00967         return str->isRightToLeft() ? Qt::AlignRight : Qt::AlignLeft;
00968     return m_layout.alignment;
00969 }
00970 
00971 void KoTextParag::setLineSpacing( double _i )
00972 {
00973     m_layout.setLineSpacingValue(_i);
00974     invalidate(0);
00975 }
00976 
00977 void KoTextParag::setLineSpacingType( KoParagLayout::SpacingType _type )
00978 {
00979     m_layout.lineSpacingType = _type;
00980     invalidate(0);
00981 }
00982 
00983 void KoTextParag::setTopBorder( const KoBorder & _brd )
00984 {
00985     m_layout.topBorder = _brd;
00986     invalidate(0);
00987 }
00988 
00989 void KoTextParag::setBottomBorder( const KoBorder & _brd )
00990 {
00991     m_layout.bottomBorder = _brd;
00992     invalidate(0);
00993 }
00994 
00995 void KoTextParag::setJoinBorder( bool join )
00996 {
00997     m_layout.joinBorder = join;
00998     invalidate(0);
00999 }
01000 
01001 void KoTextParag::setBackgroundColor ( const QColor& color )
01002 {
01003     m_layout.backgroundColor = color;
01004     invalidate(0);
01005 }
01006 
01007 void KoTextParag::setNoCounter()
01008 {
01009     delete m_layout.counter;
01010     m_layout.counter = 0L;
01011     invalidateCounters();
01012 }
01013 
01014 void KoTextParag::setCounter( const KoParagCounter * pCounter )
01015 {
01016     // Preserve footnote numbering when applying a style
01017     const bool isFootNote = m_layout.counter &&
01018                             m_layout.counter->numbering() == KoParagCounter::NUM_FOOTNOTE;
01019     if ( isFootNote ) {
01020         const QString footNotePrefix = m_layout.counter->prefix(); // this is where the number is
01021         delete m_layout.counter;
01022         m_layout.counter = pCounter ? new KoParagCounter( *pCounter ) : new KoParagCounter();
01023         m_layout.counter->setNumbering( KoParagCounter::NUM_FOOTNOTE );
01024         m_layout.counter->setStyle( KoParagCounter::STYLE_NONE ); // no number after the 'prefix'
01025         m_layout.counter->setPrefix( footNotePrefix );
01026         m_layout.counter->setSuffix( QString::null );
01027         invalidateCounters();
01028     } else {
01029         if ( pCounter )
01030             setCounter( *pCounter );
01031         else
01032             setNoCounter();
01033     }
01034 }
01035 
01036 void KoTextParag::setCounter( const KoParagCounter & counter )
01037 {
01038     // Garbage collect unnneeded counters.
01039     if ( counter.numbering() == KoParagCounter::NUM_NONE
01040          // [keep it for unnumbered outlines (the depth is useful)]
01041          && ( !m_layout.style || !m_layout.style->isOutline() ) )
01042     {
01043         setNoCounter();
01044     }
01045     else
01046     {
01047         delete m_layout.counter;
01048         m_layout.counter = new KoParagCounter( counter );
01049 
01050         // Invalidate the counters
01051         invalidateCounters();
01052     }
01053 }
01054 
01055 void KoTextParag::invalidateCounters()
01056 {
01057     // Invalidate this paragraph and all the following ones
01058     // (Numbering may have changed)
01059     invalidate( 0 );
01060     if ( m_layout.counter )
01061         m_layout.counter->invalidate();
01062     KoTextParag *s = next();
01063     // #### Possible optimization: since any invalidation propagates down,
01064     // it's enough to stop at the first paragraph with an already-invalidated counter, isn't it?
01065     // This is only true if nobody else calls counter->invalidate...
01066     while ( s ) {
01067         if ( s->m_layout.counter )
01068             s->m_layout.counter->invalidate();
01069         s->invalidate( 0 );
01070         s = s->next();
01071     }
01072 }
01073 
01074 int KoTextParag::counterWidth() const
01075 {
01076     if ( !m_layout.counter )
01077         return 0;
01078 
01079     return m_layout.counter->width( this );
01080 }
01081 
01082 // Draw the complete label (i.e. heading/list numbers/bullets) for this paragraph.
01083 // This is called by KoTextParag::paint.
01084 void KoTextParag::drawLabel( QPainter* p, int xLU, int yLU, int /*wLU*/, int /*hLU*/, int baseLU, const QColorGroup& /*cg*/ )
01085 {
01086     if ( !m_layout.counter ) // shouldn't happen
01087         return;
01088 
01089     if ( m_layout.counter->numbering() == KoParagCounter::NUM_NONE )
01090         return;
01091 
01092     int counterWidthLU = m_layout.counter->width( this );
01093 
01094     // We use the formatting of the first char as the formatting of the counter
01095     KoTextFormat counterFormat( *KoParagCounter::counterFormat( this ) );
01096     if ( !m_layout.style || !m_layout.style->isOutline() )
01097     {
01098       // But without bold/italic for normal lists, since some items could be bold and others not.
01099       // For headings we must keep the bold when the heading is bold.
01100       counterFormat.setBold( false );
01101       counterFormat.setItalic( false );
01102     }
01103     KoTextFormat* format = &counterFormat;
01104     p->save();
01105 
01106     QColor textColor( format->color() );
01107     if ( !textColor.isValid() ) // Resolve the color at this point
01108         textColor = KoTextFormat::defaultTextColor( p );
01109     p->setPen( QPen( textColor ) );
01110 
01111     KoTextZoomHandler * zh = textDocument()->paintingZoomHandler();
01112     assert( zh );
01113     //bool forPrint = ( p->device()->devType() == QInternal::Printer );
01114 
01115     bool rtl = str->isRightToLeft(); // when true, we put suffix+counter+prefix at the RIGHT of the paragraph.
01116     int xLeft = zh->layoutUnitToPixelX( xLU - (rtl ? 0 : counterWidthLU) );
01117     int y = zh->layoutUnitToPixelY( yLU );
01118     //int h = zh->layoutUnitToPixelY( yLU, hLU );
01119     int base = zh->layoutUnitToPixelY( yLU, baseLU );
01120     int counterWidth = zh->layoutUnitToPixelX( xLU, counterWidthLU );
01121     int height = zh->layoutUnitToPixelY( yLU, format->height() );
01122 
01123     QFont font( format->screenFont( zh ) );
01124     // Footnote numbers are in superscript (in WP and Word, not in OO)
01125     if ( m_layout.counter->numbering() == KoParagCounter::NUM_FOOTNOTE )
01126     {
01127         int pointSize = ( ( font.pointSize() * 2 ) / 3 );
01128         font.setPointSize( pointSize );
01129         y -= ( height - QFontMetrics(font).height() );
01130     }
01131     p->setFont( font );
01132 
01133     // Now draw any bullet that is required over the space left for it.
01134     if ( m_layout.counter->isBullet() )
01135     {
01136         int xBullet = xLeft + zh->layoutUnitToPixelX( m_layout.counter->bulletX() );
01137 
01138         //kDebug(32500) << "KoTextParag::drawLabel xLU=" << xLU << " counterWidthLU=" << counterWidthLU << endl;
01139         // The width and height of the bullet is the width of one space
01140         int width = zh->layoutUnitToPixelX( xLeft, format->width( ' ' ) );
01141 
01142         //kDebug(32500) << "Pix: xLeft=" << xLeft << " counterWidth=" << counterWidth
01143         //          << " xBullet=" << xBullet << " width=" << width << endl;
01144 
01145         QString prefix = m_layout.counter->prefix();
01146         if ( !prefix.isEmpty() )
01147         {
01148             if ( rtl )
01149                 prefix.prepend( ' ' /*the space before the bullet in RightToLeft mode*/ );
01150             KoTextParag::drawFontEffects( p, format, zh, format->screenFont( zh ), textColor, xLeft, base, width, y, height, prefix[0] );
01151 
01152             int posY =y + base - format->offsetFromBaseLine();
01153             //we must move to bottom text because we create
01154             //shadow to 'top'.
01155             int sy = format->shadowY( zh );
01156             if ( sy < 0)
01157                 posY -= sy;
01158 
01159             p->drawText( xLeft, posY, prefix );
01160         }
01161 
01162         QRect er( xBullet + (rtl ? width : 0), y + height / 2 - width / 2, width, width );
01163         // Draw the bullet.
01164         int posY = 0;
01165         switch ( m_layout.counter->style() )
01166         {
01167             case KoParagCounter::STYLE_DISCBULLET:
01168                 p->setBrush( QBrush(textColor) );
01169                 p->drawEllipse( er );
01170                 p->setBrush( Qt::NoBrush );
01171                 break;
01172             case KoParagCounter::STYLE_SQUAREBULLET:
01173                 p->fillRect( er, QBrush(textColor) );
01174                 break;
01175             case KoParagCounter::STYLE_BOXBULLET:
01176                 p->drawRect( er );
01177                 break;
01178             case KoParagCounter::STYLE_CIRCLEBULLET:
01179                 p->drawEllipse( er );
01180                 break;
01181             case KoParagCounter::STYLE_CUSTOMBULLET:
01182             {
01183                 // The user has selected a symbol from a special font. Override the paragraph
01184                 // font with the given family. This conserves the right size etc.
01185                 if ( !m_layout.counter->customBulletFont().isEmpty() )
01186                 {
01187                     QFont bulletFont( p->font() );
01188                     bulletFont.setFamily( m_layout.counter->customBulletFont() );
01189                     p->setFont( bulletFont );
01190                 }
01191                 KoTextParag::drawFontEffects( p, format, zh, format->screenFont( zh ), textColor, xBullet, base, width, y, height, ' ' );
01192 
01193                 posY = y + base- format->offsetFromBaseLine();
01194                 //we must move to bottom text because we create
01195                 //shadow to 'top'.
01196                 int sy = format->shadowY( zh );
01197                 if ( sy < 0)
01198                     posY -= sy;
01199 
01200                 p->drawText( xBullet, posY, m_layout.counter->customBulletCharacter() );
01201                 break;
01202             }
01203             default:
01204                 break;
01205         }
01206 
01207         QString suffix = m_layout.counter->suffix();
01208         if ( !suffix.isEmpty() )
01209         {
01210             if ( !rtl )
01211                 suffix += ' ' /*the space after the bullet*/;
01212 
01213             KoTextParag::drawFontEffects( p, format, zh, format->screenFont( zh ), textColor, xBullet + width, base, counterWidth, y,height, suffix[0] );
01214 
01215             int posY =y + base- format->offsetFromBaseLine();
01216             //we must move to bottom text because we create
01217             //shadow to 'top'.
01218             int sy = format->shadowY( zh );
01219             if ( sy < 0)
01220                 posY -= sy;
01221 
01222             p->drawText( xBullet + width, posY, suffix, -1 );
01223         }
01224     }
01225     else
01226     {
01227         QString counterText = m_layout.counter->text( this );
01228         // There are no bullets...any parent bullets have already been suppressed.
01229         // Just draw the text! Note: one space is always appended.
01230         if ( !counterText.isEmpty() )
01231         {
01232             KoTextParag::drawFontEffects( p, format, zh, format->screenFont( zh ), textColor, xLeft, base, counterWidth, y, height, counterText[0] );
01233 
01234             counterText += ' ' /*the space after the bullet (before in RightToLeft mode)*/;
01235 
01236             int posY =y + base - format->offsetFromBaseLine();
01237             //we must move to bottom text because we create
01238             //shadow to 'top'.
01239             int sy = format->shadowY( zh );
01240             if ( sy < 0)
01241                 posY -= sy;
01242 
01243             p->drawText( xLeft, posY , counterText, -1 );
01244         }
01245     }
01246     p->restore();
01247 }
01248 
01249 int KoTextParag::breakableTopMargin() const
01250 {
01251     KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
01252     return zh->ptToLayoutUnitPixY(
01253         m_layout.margins[ Q3StyleSheetItem::MarginTop ] );
01254 }
01255 
01256 int KoTextParag::topMargin() const
01257 {
01258     KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
01259     return zh->ptToLayoutUnitPixY(
01260         m_layout.margins[ Q3StyleSheetItem::MarginTop ]
01261         + ( ( prev() && prev()->joinBorder() && prev()->bottomBorder() == m_layout.bottomBorder &&
01262         prev()->topBorder() == m_layout.topBorder && prev()->leftBorder() == m_layout.leftBorder &&
01263         prev()->rightBorder() == m_layout.rightBorder) ? 0 : m_layout.topBorder.width() ) );
01264 }
01265 
01266 int KoTextParag::bottomMargin() const
01267 {
01268     KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
01269     return zh->ptToLayoutUnitPixY(
01270         m_layout.margins[ Q3StyleSheetItem::MarginBottom ]
01271         + ( ( joinBorder() && next() && next()->bottomBorder() == m_layout.bottomBorder &&
01272         next()->topBorder() == m_layout.topBorder && next()->leftBorder() == m_layout.leftBorder &&
01273         next()->rightBorder() == m_layout.rightBorder) ? 0 : m_layout.bottomBorder.width() ) );
01274 }
01275 
01276 int KoTextParag::leftMargin() const
01277 {
01278     KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
01279     return zh->ptToLayoutUnitPixX(
01280         m_layout.margins[ Q3StyleSheetItem::MarginLeft ]
01281         + m_layout.leftBorder.width() );
01282 }
01283 
01284 int KoTextParag::rightMargin() const
01285 {
01286     KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
01287     int cw=0;
01288     if( m_layout.counter && str->isRightToLeft() &&
01289         (( m_layout.counter->alignment() == Qt::AlignRight ) || ( m_layout.counter->alignment() == Qt::AlignLeft )))
01290             cw = counterWidth();
01291 
01292     return zh->ptToLayoutUnitPixX(
01293         m_layout.margins[ Q3StyleSheetItem::MarginRight ]
01294         + m_layout.rightBorder.width() )
01295         + cw; /* in layout units already */
01296 }
01297 
01298 int KoTextParag::firstLineMargin() const
01299 {
01300     KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
01301     return zh->ptToLayoutUnitPixY(
01302         m_layout.margins[ Q3StyleSheetItem::MarginFirstLine ] );
01303 }
01304 
01305 int KoTextParag::lineSpacing( int line ) const
01306 {
01307     Q_ASSERT( isValid() );
01308     if ( m_layout.lineSpacingType == KoParagLayout::LS_SINGLE )
01309         return 0; // or shadow, see calculateLineSpacing
01310     else {
01311         if( line >= (int)lineStarts.count() )
01312         {
01313             kError() << "KoTextParag::lineSpacing assert(line<lines) failed: line=" << line << " lines=" << lineStarts.count() << endl;
01314             return 0;
01315         }
01316         QMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
01317         while ( line-- > 0 )
01318             ++it;
01319         return (*it)->lineSpacing;
01320     }
01321 }
01322 
01323 // Called by KoTextFormatter
01324 int KoTextParag::calculateLineSpacing( int line, int startChar, int lastChar ) const
01325 {
01326     KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
01327     // TODO add shadow in KoTextFormatter!
01328     int shadow = 0; //QABS( zh->ptToLayoutUnitPixY( shadowDistanceY() ) );
01329     if ( m_layout.lineSpacingType == KoParagLayout::LS_SINGLE )
01330         return shadow;
01331     else if ( m_layout.lineSpacingType == KoParagLayout::LS_CUSTOM )
01332         return zh->ptToLayoutUnitPixY( m_layout.lineSpacingValue() ) + shadow;
01333     else {
01334         if( line >= (int)lineStarts.count() )
01335         {
01336             kError() << "KoTextParag::lineSpacing assert(line<lines) failed: line=" << line << " lines=" << lineStarts.count() << endl;
01337             return 0+shadow;
01338         }
01339         QMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
01340         while ( line-- > 0 )
01341             ++it;
01342 
01343         //kDebug(32500) << " line spacing type: " << m_layout.lineSpacingType << " value:" << m_layout.lineSpacingValue() << " line_height=" << (*it)->h << " startChar=" << startChar << " lastChar=" << lastChar << endl;
01344         switch ( m_layout.lineSpacingType )
01345         {
01346         case KoParagLayout::LS_MULTIPLE:
01347         {
01348             double n = m_layout.lineSpacingValue() - 1.0; // yes, can be negative
01349             return shadow + qRound( n * heightForLineSpacing( startChar, lastChar ) );
01350         }
01351         case KoParagLayout::LS_ONEANDHALF:
01352         {
01353             // Special case of LS_MULTIPLE, with n=1.5
01354             return shadow + heightForLineSpacing( startChar, lastChar ) / 2;
01355         }
01356         case KoParagLayout::LS_DOUBLE:
01357         {
01358             // Special case of LS_MULTIPLE, with n=1
01359             return shadow + heightForLineSpacing( startChar, lastChar );
01360         }
01361         case KoParagLayout::LS_AT_LEAST:
01362         {
01363             int atLeast = zh->ptToLayoutUnitPixY( m_layout.lineSpacingValue() );
01364             const int lineHeight = ( *it )->h;
01365             int h = qMax( lineHeight, atLeast );
01366             // height is now the required total height
01367             return shadow + h - lineHeight;
01368         }
01369         case KoParagLayout::LS_FIXED:
01370         {
01371             const int lineHeight = ( *it )->h;
01372             return shadow + zh->ptToLayoutUnitPixY( m_layout.lineSpacingValue() ) - lineHeight;
01373         }
01374         // Silence compiler warnings
01375         case KoParagLayout::LS_SINGLE:
01376         case KoParagLayout::LS_CUSTOM:
01377             break;
01378         }
01379     }
01380     kWarning() << "Unhandled linespacing type : " << m_layout.lineSpacingType << endl;
01381     return 0+shadow;
01382 }
01383 
01384 QRect KoTextParag::pixelRect( KoTextZoomHandler *zh ) const
01385 {
01386     QRect rct( zh->layoutUnitToPixel( rect() ) );
01387     //kDebug(32500) << "   pixelRect for parag " << paragId()
01388     //               << ": rect=" << rect() << " pixelRect=" << rct << endl;
01389 
01390     // After division we almost always end up with the top overwriting the bottom of the parag above
01391     if ( prev() )
01392     {
01393         QRect prevRect( zh->layoutUnitToPixel( prev()->rect() ) );
01394         if ( rct.top() < prevRect.bottom() + 1 )
01395         {
01396             //kDebug(32500) << "   pixelRect: rct.top() adjusted to " << prevRect.bottom() + 1 << " (was " << rct.top() << ")" << endl;
01397             rct.setTop( prevRect.bottom() + 1 );
01398         }
01399     }
01400     return rct;
01401 }
01402 
01403 // Paint this paragraph. This is called by KoTextDocument::drawParagWYSIWYG
01404 // (KoTextDocument::drawWithoutDoubleBuffer when printing)
01405 void KoTextParag::paint( QPainter &painter, const QColorGroup &cg, KoTextCursor *cursor, bool drawSelections,
01406                          int clipx, int clipy, int clipw, int cliph )
01407 {
01408 #ifdef DEBUG_PAINT
01409     kDebug(32500) << "KoTextParag::paint =====  id=" << paragId() << " clipx=" << clipx << " clipy=" << clipy << " clipw=" << clipw << " cliph=" << cliph << endl;
01410     kDebug(32500) << " clipw in pix (approx) : " << textDocument()->paintingZoomHandler()->layoutUnitToPixelX( clipw ) << " cliph in pix (approx) : " << textDocument()->paintingZoomHandler()->layoutUnitToPixelX( cliph ) << endl;
01411 #endif
01412 
01413     KoTextZoomHandler * zh = textDocument()->paintingZoomHandler();
01414     assert(zh);
01415 
01416     QRect paraRect = pixelRect( zh );
01417 
01418     // Find left margin size, first line offset and right margin in pixels
01419     int leftMarginPix = zh->layoutUnitToPixelX( leftMargin() );
01420     int firstLineOffset = zh->layoutUnitToPixelX( firstLineMargin() );
01421 
01422     // The furthest left and right x-coords of the paragraph,
01423     // including the bullet/counter, but not the borders.
01424     int leftExtent = qMin ( leftMarginPix,  leftMarginPix + firstLineOffset );
01425     int rightExtent = paraRect.width() - zh->layoutUnitToPixelX( rightMargin() );
01426 
01427     // Draw the paragraph background color
01428     if ( backgroundColor().isValid() )
01429     {
01430         // Render background from either left margin indent, or first line indent,
01431         // whichever is nearer the left.
01432         int backgroundWidth = rightExtent - leftExtent + 1;
01433         int backgroundHeight = pixelRect( zh ).height();
01434         painter.fillRect( leftExtent, 0,
01435                           backgroundWidth, backgroundHeight,
01436                           backgroundColor() );
01437     }
01438 
01439     // Let's call drawLabel ourselves, rather than having to deal with QStyleSheetItem to get paintLines to call it!
01440     if ( m_layout.counter && m_layout.counter->numbering() != KoParagCounter::NUM_NONE && m_lineChanged <= 0 )
01441     {
01442         int cy, h, baseLine;
01443         lineInfo( 0, cy, h, baseLine );
01444         int xLabel = at(0)->x;
01445         if ( str->isRightToLeft() )
01446             xLabel += at(0)->width;
01447         drawLabel( &painter, xLabel, cy, 0, 0, baseLine, cg );
01448     }
01449 
01450     paintLines( painter, cg, cursor, drawSelections, clipx, clipy, clipw, cliph );
01451 
01452     // Now draw paragraph border
01453     if ( m_layout.hasBorder() )
01454     {
01455         bool const drawTopBorder = !prev() || !prev()->joinBorder() || prev()->bottomBorder() != bottomBorder() || prev()->topBorder() != topBorder() || prev()->leftBorder() != leftBorder() || prev()->rightBorder() != rightBorder();
01456         bool const drawBottomBorder = !joinBorder() || !next() || next()->bottomBorder() != bottomBorder() || next()->topBorder() != topBorder() || next()->leftBorder() != leftBorder() || next()->rightBorder() != rightBorder();
01457 
01458         // Paragraph borders surround the paragraph and its
01459         // counters/bullets, but they only touch the frame border if
01460         // the paragraph margins are of non-zero length.
01461         // This is what OpenOffice does (no really, it is this time).
01462         //
01463         // drawBorders paints outside the give rect, so for the
01464         // x-coords, it just needs to know the left and right extent
01465         // of the paragraph.
01466         QRect r;
01467         r.setLeft( leftExtent );
01468         r.setRight( rightExtent );
01469         r.setTop( zh->layoutUnitToPixelY(lineY( 0 )) );
01470 
01471         int lastLine = lines() - 1;
01472         // We need to start from the pixelRect, to make sure the bottom border is entirely painted.
01473         // This is a case where we DO want to subtract pixels to pixels...
01474         int paragBottom = pixelRect(zh).height()-1;
01475         // If we don't have a bottom border, we need go as low as possible ( to touch the next parag's border ).
01476         // If we have a bottom border, then we rather exclude the linespacing. Looks nicer. OO does that too.
01477         if ( m_layout.bottomBorder.width() > 0 && drawBottomBorder)
01478             paragBottom -= zh->layoutUnitToPixelY( lineSpacing( lastLine ) );
01479         paragBottom -= KoBorder::zoomWidthY( m_layout.bottomBorder.width(), zh, 0 );
01480         //kDebug(32500) << "Parag border: paragBottom=" << paragBottom
01481         //               << " bottom border width = " << KoBorder::zoomWidthY( m_layout.bottomBorder.width(), zh, 0 ) << endl;
01482         r.setBottom( paragBottom );
01483 
01484         //kDebug(32500) << "KoTextParag::paint documentWidth=" << documentWidth() << " LU (" << zh->layoutUnitToPixelX(documentWidth()) << " pixels) bordersRect=" << r << endl;
01485         KoBorder::drawBorders( painter, zh, r,
01486                                m_layout.leftBorder, m_layout.rightBorder, m_layout.topBorder, m_layout.bottomBorder,
01487                                0, QPen(), drawTopBorder, drawBottomBorder );
01488     }
01489 }
01490 
01491 
01492 void KoTextParag::paintLines( QPainter &painter, const QColorGroup &cg, KoTextCursor *cursor, bool drawSelections,
01493                         int clipx, int clipy, int clipw, int cliph )
01494 {
01495     if ( !visible )
01496         return;
01497     //KoTextStringChar *chr = at( 0 );
01498     //if (!chr) { kDebug(32500) << "paragraph " << (void*)this << " " << paragId() << ", can't paint, EMPTY !" << endl;
01499 
01500     // This is necessary with the current code, but in theory it shouldn't
01501     // be necessary, if Xft really gives us fully proportionnal chars....
01502 #define CHECK_PIXELXADJ
01503 
01504     int curx = -1, cury = 0, curh = 0, curline = 0;
01505     int xstart, xend = 0;
01506 
01507     QString qstr = str->toString();
01508     qstr.replace( QChar(0x00a0U), ' ' ); // Not all fonts have non-breakable-space glyph
01509 
01510     const int nSels = doc ? doc->numSelections() : 1;
01511     Q3MemArray<int> selectionStarts( nSels );
01512     Q3MemArray<int> selectionEnds( nSels );
01513     if ( drawSelections ) {
01514         bool hasASelection = false;
01515         for ( int i = 0; i < nSels; ++i ) {
01516             if ( !hasSelection( i ) ) {
01517                 selectionStarts[ i ] = -1;
01518                 selectionEnds[ i ] = -1;
01519             } else {
01520                 hasASelection = true;
01521                 selectionStarts[ i ] = selectionStart( i );
01522                 int end = selectionEnd( i );
01523                 if ( end == length() - 1 && n && n->hasSelection( i ) )
01524                     end++;
01525                 selectionEnds[ i ] = end;
01526             }
01527         }
01528         if ( !hasASelection )
01529             drawSelections = false;
01530     }
01531 
01532     // Draw the lines!
01533     int line = m_lineChanged;
01534     if (line<0) line = 0;
01535 
01536     int numLines = lines();
01537 #ifdef DEBUG_PAINT
01538     kDebug(32500) << " paintLines: from line " << line << " to " << numLines-1 << endl;
01539 #endif
01540     for( ; line<numLines ; line++ )
01541     {
01542         // get the start and length of the line
01543         int nextLine;
01544         int startOfLine;
01545         lineStartOfLine(line, &startOfLine);
01546         if (line == numLines-1 )
01547             nextLine = length();
01548         else
01549             lineStartOfLine(line+1, &nextLine);
01550 
01551         // init this line
01552         int cy, h, baseLine;
01553         lineInfo( line, cy, h, baseLine );
01554         if ( clipy != -1 && cy > clipy - r.y() + cliph ) // outside clip area, leave
01555             break;
01556 
01557         // Vars related to the current "run of text"
01558         int paintStart = startOfLine;
01559         KoTextStringChar* chr = at(startOfLine);
01560         KoTextStringChar* nextchr = chr;
01561 
01562         // okay, paint the line!
01563         for(int i=startOfLine;i<nextLine;i++)
01564         {
01565             chr = nextchr;
01566             if ( i < nextLine-1 )
01567                 nextchr = at( i+1 );
01568 
01569             // we flush at end of line
01570             bool flush = ( i == nextLine - 1 );
01571             // Optimization note: QRT uses "flush |=", which doesn't have shortcut optimization
01572 
01573             // we flush on format changes
01574             flush = flush || ( nextchr->format() != chr->format() );
01575             // we flush on link changes
01576             //flush = flush || ( nextchr->isLink() != chr->isLink() );
01577             // we flush on small caps changes
01578             if ( !flush && chr->format()->attributeFont() == KoTextFormat::ATT_SMALL_CAPS )
01579             {
01580                 bool isLowercase = chr->c.upper() != chr->c;
01581                 bool nextLowercase = nextchr->c.upper() != nextchr->c;
01582                 flush = isLowercase != nextLowercase;
01583             }
01584             // we flush on start of run
01585             flush = flush || nextchr->startOfRun;
01586             // we flush on bidi changes
01587             flush = flush || ( nextchr->rightToLeft != chr->rightToLeft );
01588 #ifdef CHECK_PIXELXADJ
01589             // we flush when the value of pixelxadj changes
01590             // [unless inside a ligature]
01591             flush = flush || ( nextchr->pixelxadj != chr->pixelxadj && nextchr->charStop );
01592 #endif
01593             // we flush before and after tabs
01594             flush = flush || ( chr->c == '\t' || nextchr->c == '\t' );
01595             // we flush on soft hyphens
01596             flush = flush || ( chr->c.unicode() == 0xad );
01597             // we flush on custom items
01598             flush = flush || chr->isCustom();
01599             // we flush before custom items
01600             flush = flush || nextchr->isCustom();
01601             // when painting justified we flush on spaces
01602             if ((alignment() & Qt::AlignJustify) == Qt::AlignJustify )
01603                 //flush = flush || QTextFormatter::isBreakable( str, i );
01604                 flush = flush || chr->whiteSpace;
01605             // when underlining or striking "word by word" we flush before/after spaces
01606             if (!flush && chr->format()->wordByWord() && chr->format()->isStrikedOrUnderlined())
01607                 flush = flush || chr->whiteSpace || nextchr->whiteSpace;
01608             // we flush when the string is getting too long
01609             flush = flush || ( i - paintStart >= 256 );
01610             // we flush when the selection state changes
01611             if ( drawSelections ) {
01612                 // check if selection state changed - TODO update from QRT
01613                 bool selectionChange = false;
01614                 if ( drawSelections ) {
01615                     for ( int j = 0; j < nSels; ++j ) {
01616                         selectionChange = selectionStarts[ j ] == i+1 || selectionEnds[ j ] == i+1;
01617                         if ( selectionChange )
01618                             break;
01619                     }
01620                 }
01621                 flush = flush || selectionChange;
01622             }
01623 
01624             // check for cursor mark
01625             if ( cursor && this == cursor->parag() && i == cursor->index() ) {
01626                 curx = cursor->x();
01627                 curline = line;
01628                 KoTextStringChar *c = chr;
01629                 if ( i > 0 )
01630                     --c;
01631                 curh = c->height();
01632                 cury = cy + baseLine - c->ascent();
01633             }
01634 
01635             if ( flush ) {  // something changed, draw what we have so far
01636 
01637                 KoTextStringChar* cStart = at( paintStart );
01638                 if ( chr->rightToLeft ) {
01639                     xstart = chr->x;
01640                     xend = cStart->x + cStart->width;
01641                 } else {
01642                     xstart = cStart->x;
01643                         if ( i < length() - 1 && !str->at( i + 1 ).lineStart &&
01644                          str->at( i + 1 ).rightToLeft == chr->rightToLeft )
01645                         xend = str->at( i + 1 ).x;
01646                     else
01647                         xend = chr->x + chr->width;
01648                 }
01649 
01650                 if ( (clipx == -1 || clipw == -1) || (xend >= clipx && xstart <= clipx + clipw) ) {
01651                     if ( !chr->isCustom() ) {
01652                         drawParagString( painter, qstr, paintStart, i - paintStart + 1, xstart, cy,
01653                                          baseLine, xend-xstart, h, drawSelections,
01654                                          chr->format(), selectionStarts, selectionEnds,
01655                                          cg, chr->rightToLeft, line );
01656                     }
01657                     else
01658                         if ( chr->customItem()->placement() == KoTextCustomItem::PlaceInline ) {
01659                             chr->customItem()->draw( &painter, chr->x, cy + baseLine - chr->customItem()->ascent(),
01660                                                      clipx - r.x(), clipy - r.y(), clipw, cliph, cg,
01661                                                      drawSelections && nSels && selectionStarts[ 0 ] <= i && selectionEnds[ 0 ] > i );
01662                     }
01663                 }
01664                 paintStart = i+1;
01665             }
01666         } // end of character loop
01667     } // end of line loop
01668 
01669     // if we should draw a cursor, draw it now
01670     if ( curx != -1 && cursor ) {
01671         drawCursor( painter, cursor, curx, cury, curh, cg );
01672     }
01673 }
01674 
01675 // Called by KoTextParag::paintLines
01676 // Draw a set of characters with the same formattings.
01677 // Reimplemented here to convert coordinates first, and call @ref drawFormattingChars.
01678 void KoTextParag::drawParagString( QPainter &painter, const QString &str, int start, int len, int startX,
01679                                    int lastY, int baseLine, int bw, int h, bool drawSelections,
01680                                    KoTextFormat *format, const Q3MemArray<int> &selectionStarts,
01681                                    const Q3MemArray<int> &selectionEnds, const QColorGroup &cg, bool rightToLeft, int line )
01682 {
01683     KoTextZoomHandler * zh = textDocument()->paintingZoomHandler();
01684     assert(zh);
01685 
01686 #ifdef DEBUG_PAINT
01687     kDebug(32500) << "KoTextParag::drawParagString drawing from " << start << " to " << start+len << endl;
01688     kDebug(32500) << " startX in LU: " << startX << " lastY in LU:" << lastY
01689                    << " baseLine in LU:" << baseLine << endl;
01690 #endif
01691 
01692     // Calculate offset (e.g. due to shadow on left or top)
01693     // Important: don't use the 2-args methods here, offsets are not heights
01694     // (0 should be 0, not 1) (#63256)
01695     int shadowOffsetX_pix = zh->layoutUnitToPixelX( format->offsetX() );
01696     int shadowOffsetY_pix = zh->layoutUnitToPixelY( format->offsetY() );
01697 
01698     // Calculate startX in pixels
01699     int startX_pix = zh->layoutUnitToPixelX( startX ) /* + at( rightToLeft ? start+len-1 : start )->pixelxadj */;
01700 #ifdef DEBUG_PAINT
01701     kDebug(32500) << "KoTextParag::drawParagString startX in pixels : " << startX_pix /*<< " adjustment:" << at( rightToLeft ? start+len-1 : start )->pixelxadj*/ << " bw=" << bw << endl;
01702 #endif
01703 
01704     int bw_pix = zh->layoutUnitToPixelX( startX, bw );
01705     int lastY_pix = zh->layoutUnitToPixelY( lastY );
01706     int baseLine_pix = zh->layoutUnitToPixelY( lastY, baseLine ); // 2 args=>+1. Is that correct?
01707     int h_pix = zh->layoutUnitToPixelY( lastY, h );
01708 #ifdef DEBUG_PAINT
01709     kDebug(32500) << "KoTextParag::drawParagString h(LU)=" << h << " lastY(LU)=" << lastY
01710                    << " h(PIX)=" << h_pix << " lastY(PIX)=" << lastY_pix
01711                    << " baseLine(PIX)=" << baseLine_pix << endl;
01712 #endif
01713 
01714     if ( format->textBackgroundColor().isValid() )
01715         painter.fillRect( startX_pix, lastY_pix, bw_pix, h_pix, format->textBackgroundColor() );
01716 
01717     // don't want to draw line breaks but want them when drawing formatting chars
01718     int draw_len = len;
01719     int draw_startX = startX;
01720     int draw_bw = bw_pix;
01721     if ( at( start + len - 1 )->c == '\n' )
01722     {
01723         draw_len--;
01724         draw_bw -= at( start + len - 1 )->pixelwidth;
01725         if ( rightToLeft && draw_len > 0 )
01726             draw_startX = at( start + draw_len - 1 )->x;
01727     }
01728 
01729     // Draw selection (moved here to do it before applying the offset from the shadow)
01730     // (and because it's not part of the shadow drawing)
01731     if ( drawSelections ) {
01732         bool inSelection = false;
01733         const int nSels = doc ? doc->numSelections() : 1;
01734         for ( int j = 0; j < nSels; ++j ) {
01735             if ( start >= selectionStarts[ j ] && start < selectionEnds[ j ] ) {
01736                 inSelection = true;
01737                 switch (j) {
01738                 case KoTextDocument::Standard:
01739                     painter.fillRect( startX_pix, lastY_pix, bw_pix, h_pix, cg.color( QColorGroup::Highlight ) );
01740                     break;
01741                 case KoTextDocument::InputMethodPreedit:
01742                     // no highlight
01743                     break;
01744                 default:
01745                     painter.fillRect( startX_pix, lastY_pix, bw_pix, h_pix, doc ? doc->selectionColor( j ) : cg.color( QColorGroup::Highlight ) );
01746                     break;
01747                 }
01748             }
01749         }
01750         if ( !inSelection )
01751             drawSelections = false; // save time in drawParagStringInternal
01752     }
01753 
01754     // Draw InputMethod Preedit Underline
01755     const int nSels = doc ? doc->numSelections() : 1;
01756     if ( KoTextDocument::InputMethodPreedit < nSels
01757          && doc->hasSelection( KoTextDocument::InputMethodPreedit )
01758          && start >= selectionStarts[ KoTextDocument::InputMethodPreedit ]
01759          && start < selectionEnds[ KoTextDocument::InputMethodPreedit ] )
01760     {
01761         QColor textColor( format->color() );
01762         painter.setPen( QPen( textColor ) );
01763 
01764         QPoint p1( startX_pix, lastY_pix + h_pix - 1 );
01765         QPoint p2( startX_pix + bw_pix, lastY_pix + h_pix - 1 );
01766         painter.drawLine( p1, p2 );
01767     }
01768 
01769     if ( draw_len > 0 )
01770     {
01771         int draw_startX_pix = zh->layoutUnitToPixelX( draw_startX ) /* + at( rightToLeft ? start+draw_len-1 : start )->pixelxadj*/;
01772         draw_startX_pix += shadowOffsetX_pix;
01773         lastY_pix += shadowOffsetY_pix;
01774 
01775         if ( format->shadowDistanceX() != 0 || format->shadowDistanceY() != 0 ) {
01776             int sx = format->shadowX( zh );
01777             int sy = format->shadowY( zh );
01778             if ( sx != 0 || sy != 0 )
01779             {
01780                 painter.save();
01781                 painter.translate( sx, sy );
01782                 drawParagStringInternal( painter, str, start, draw_len, draw_startX_pix,
01783                                          lastY_pix, baseLine_pix,
01784                                          draw_bw,
01785                                          h_pix, false /*drawSelections*/,
01786                                          format, selectionStarts,
01787                                          selectionEnds, cg, rightToLeft, line, zh, true );
01788                 painter.restore();
01789             }
01790         }
01791 
01792         drawParagStringInternal( painter, str, start, draw_len, draw_startX_pix,
01793                                  lastY_pix, baseLine_pix,
01794                                  draw_bw,
01795                                  h_pix, drawSelections, format, selectionStarts,
01796                                  selectionEnds, cg, rightToLeft, line, zh, false );
01797     }
01798 
01799     bool forPrint = ( painter.device()->devType() == QInternal::Printer );
01800     if ( textDocument()->drawFormattingChars() && !forPrint )
01801     {
01802         drawFormattingChars( painter, start, len,
01803                              lastY_pix, baseLine_pix, h_pix,
01804                              drawSelections,
01805                              format, selectionStarts,
01806                              selectionEnds, cg, rightToLeft,
01807                              line, zh, AllFormattingChars );
01808     }
01809 }
01810 
01811 // Copied from the original KoTextParag
01812 // (we have to copy it here, so that color & font changes don't require changing
01813 // a local copy of the text format)
01814 // And we have to keep it separate from drawParagString to avoid s/startX/startX_pix/ etc.
01815 void KoTextParag::drawParagStringInternal( QPainter &painter, const QString &s, int start, int len, int startX,
01816                                    int lastY, int baseLine, int bw, int h, bool drawSelections,
01817                                    KoTextFormat *format, const Q3MemArray<int> &selectionStarts,
01818                                    const Q3MemArray<int> &selectionEnds, const QColorGroup &cg, bool rightToLeft, int line, KoTextZoomHandler* zh, bool drawingShadow )
01819 {
01820 #ifdef DEBUG_PAINT
01821     kDebug(32500) << "KoTextParag::drawParagStringInternal start=" << start << " len=" << len << " : '" << s.mid(start,len) << "'" << endl;
01822     kDebug(32500) << "In pixels:  startX=" << startX << " lastY=" << lastY << " baseLine=" << baseLine
01823                    << " bw=" << bw << " h=" << h << " rightToLeft=" << rightToLeft << endl;
01824 #endif
01825     if ( drawingShadow && format->shadowDistanceX() == 0 && format->shadowDistanceY() == 0 )
01826         return;
01827     // 1) Sort out the color
01828     QColor textColor( drawingShadow ? format->shadowColor() : format->color() );
01829     if ( !textColor.isValid() ) // Resolve the color at this point
01830         textColor = KoTextFormat::defaultTextColor( &painter );
01831 
01832     // 2) Sort out the font
01833     QFont font( format->screenFont( zh ) );
01834     if ( format->attributeFont() == KoTextFormat::ATT_SMALL_CAPS && s[start].upper() != s[start] )
01835         font = format->smallCapsFont( zh, true );
01836 
01837 #if 0
01838     QFontInfo fi( font );
01839     kDebug(32500) << "KoTextParag::drawParagStringInternal requested font " << font.pointSizeF() << " using font " << fi.pointSize() << "pt (format font: " << format->font().pointSizeFloat() << "pt)" << endl;
01840     QFontMetrics fm( font );
01841     kDebug(32500) << "Real font: " << fi.family() << ". Font height in pixels: " << fm.height() << endl;
01842 #endif
01843 
01844     // 3) Paint
01845     QString str( s );
01846     if ( str[ (int)str.length() - 1 ].unicode() == 0xad )
01847         str.remove( str.length() - 1, 1 );
01848     painter.setPen( QPen( textColor ) );
01849     painter.setFont( font );
01850 
01851     KoTextDocument* doc = document();
01852 
01853     if ( drawSelections ) {
01854         const int nSels = doc ? doc->numSelections() : 1;
01855         for ( int j = 0; j < nSels; ++j ) {
01856             if ( start >= selectionStarts[ j ] && start < selectionEnds[ j ] ) {
01857                 if ( doc->invertSelectionText( j ) )
01858                     textColor = cg.color( QColorGroup::HighlightedText );
01859                     painter.setPen( QPen( textColor ) );
01860                     break;
01861             }
01862         }
01863     }
01864 
01865     Qt::LayoutDirection dir = rightToLeft ? Qt::RightToLeft : Qt::LeftToRight;
01866 
01867     if ( dir != Qt::RightToLeft && start + len == length() ) // don't draw the last character (trailing space)
01868     {
01869        len--;
01870        if ( len <= 0 )
01871            return;
01872        bw-=at(length()-1)->pixelwidth;
01873     }
01874     KoTextParag::drawFontEffects( &painter, format, zh, font, textColor, startX, baseLine, bw, lastY, h, str[start] );
01875 
01876     if ( str[ start ] != '\t' && str[ start ].unicode() != 0xad ) {
01877         str = format->displayedString( str ); // #### This converts the whole string, instead of from start to start+len!
01878         if ( format->vAlign() == KoTextFormat::AlignNormal ) {
01879             int posY = lastY + baseLine;
01880             //we must move to bottom text because we create
01881             //shadow to 'top'.
01882             int sy = format->shadowY( zh );
01883             if ( sy < 0)
01884                 posY -= sy;
01885             // TODO RTL painter.drawText( startX, posY, str, start, len, dir );
01886             painter.drawText( startX, posY, str, start, len );
01887 #ifdef BIDI_DEBUG
01888             painter.save();
01889             painter.setPen ( Qt::red );
01890             painter.drawLine( startX, lastY, startX, lastY + baseLine );
01891             painter.drawLine( startX, lastY + baseLine/2, startX + 10, lastY + baseLine/2 );
01892             int w = 0;
01893             int i = 0;
01894             while( i < len )
01895                 w += painter.fontMetrics().charWidth( str, start + i++ );
01896             painter.setPen ( Qt::blue );
01897             painter.drawLine( startX + w - 1, lastY, startX + w - 1, lastY + baseLine );
01898             painter.drawLine( startX + w - 1, lastY + baseLine/2, startX + w - 1 - 10, lastY + baseLine/2 );
01899             painter.restore();
01900 #endif
01901         } else if ( format->vAlign() == KoTextFormat::AlignSuperScript ) {
01902             int posY =lastY + baseLine - ( painter.fontMetrics().height() / 2 );
01903             //we must move to bottom text because we create
01904             //shadow to 'top'.
01905             int sy = format->shadowY( zh );
01906             if ( sy < 0)
01907                 posY -= sy;
01908             painter.drawText( startX, posY, str, start, len /*TODO , dir*/ );
01909         } else if ( format->vAlign() == KoTextFormat::AlignSubScript ) {
01910             int posY =lastY + baseLine + ( painter.fontMetrics().height() / 6 );
01911             //we must move to bottom text because we create
01912             //shadow to 'top'.
01913             int sy = format->shadowY( zh );
01914             if ( sy < 0)
01915                 posY -= sy;
01916             painter.drawText( startX, posY, str, start, len /*TODO , dir*/ );
01917         } else if ( format->vAlign() == KoTextFormat::AlignCustom ) {
01918             int posY = lastY + baseLine - format->offsetFromBaseLine();
01919             //we must move to bottom text because we create
01920             //shadow to 'top'.
01921             int sy = format->shadowY( zh );
01922             if ( sy < 0)
01923                 posY -= sy;
01924             painter.drawText( startX, posY, str, start, len /*TODO , dir */ );
01925         }
01926     }
01927     if ( str[ start ] == '\t' && m_tabCache.contains( start ) ) {
01928         painter.save();
01929         KoTextZoomHandler * zh = textDocument()->paintingZoomHandler();
01930         const KoTabulator& tab = m_layout.tabList()[ m_tabCache[ start ] ];
01931         int lineWidth = zh->zoomItYOld( tab.ptWidth );
01932         switch ( tab.filling ) {
01933             case TF_DOTS:
01934                 painter.setPen( QPen( textColor, lineWidth, Qt::DotLine ) );
01935                 painter.drawLine( startX, lastY + baseLine, startX + bw, lastY + baseLine );
01936                 break;
01937             case TF_LINE:
01938                 painter.setPen( QPen( textColor, lineWidth, Qt::SolidLine ) );
01939                 painter.drawLine( startX, lastY + baseLine, startX + bw, lastY + baseLine );
01940                 break;
01941             case TF_DASH:
01942                 painter.setPen( QPen( textColor, lineWidth, Qt::DashLine ) );
01943                 painter.drawLine( startX, lastY + baseLine, startX + bw, lastY + baseLine );
01944                 break;
01945             case TF_DASH_DOT:
01946                 painter.setPen( QPen( textColor, lineWidth, Qt::DashDotLine ) );
01947                 painter.drawLine( startX, lastY + baseLine, startX + bw, lastY + baseLine );
01948                 break;
01949             case TF_DASH_DOT_DOT:
01950                 painter.setPen( QPen( textColor, lineWidth, Qt::DashDotDotLine ) );
01951                 painter.drawLine( startX, lastY + baseLine, startX + bw, lastY + baseLine );
01952                 break;
01953 
01954             default:
01955                 break;
01956         }
01957         painter.restore();
01958     }
01959 
01960     if ( start+len < length() && at( start+len )->lineStart )
01961     {
01962 #ifdef DEBUG_PAINT
01963         //kDebug(32500) << "we are drawing the end of line " << line << ". Auto-hyphenated: " << lineHyphenated( line ) << endl;
01964 #endif
01965         bool drawHyphen = at( start+len-1 )->c.unicode() == 0xad;
01966         drawHyphen = drawHyphen || lineHyphenated( line );
01967         if ( drawHyphen ) {
01968 #ifdef DEBUG_PAINT
01969             kDebug(32500) << "drawing hyphen at x=" << startX+bw << endl;
01970 #endif
01971             painter.drawText( startX + bw, lastY + baseLine, "-" ); // \xad gives squares with some fonts (!?)
01972         }
01973     }
01974 
01975     // Paint a zigzag line for "wrong" background spellchecking checked words:
01976     if(
01977                 painter.device()->devType() != QInternal::Printer &&
01978                 format->isMisspelled() &&
01979                 !drawingShadow &&
01980                 textDocument()->drawingMissingSpellLine() )
01981         {
01982                 painter.save();
01983                 painter.setPen( QPen( Qt::red, 1 ) );
01984 
01985                 // Draw 3 pixel lines with increasing offset and distance 4:
01986                 for( int zigzag_line = 0; zigzag_line < 3; ++zigzag_line )
01987                 {
01988                         for( int zigzag_x = zigzag_line; zigzag_x < bw; zigzag_x += 4 )
01989                         {
01990                                 painter.drawPoint(
01991                                         startX + zigzag_x,
01992                                         lastY + baseLine + h/12 - 1 + zigzag_line );
01993                         }
01994                 }
01995 
01996                 // "Double" the pixel number for the middle line:
01997                 for( int zigzag_x = 3; zigzag_x < bw; zigzag_x += 4 )
01998                 {
01999                         painter.drawPoint(
02000                                 startX + zigzag_x,
02001                                 lastY + baseLine + h/12 );
02002                 }
02003 
02004                 painter.restore();
02005         }
02006 }
02007 
02008 bool KoTextParag::lineHyphenated( int l ) const
02009 {
02010     if ( l > (int)lineStarts.count() - 1 ) {
02011         kWarning() << "KoTextParag::lineHyphenated: line " << l << " out of range!" << endl;
02012         return false;
02013     }
02014 
02015     if ( !isValid() )
02016         const_cast<KoTextParag*>(this)->format();
02017 
02018     QMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
02019     while ( l-- > 0 )
02020         ++it;
02021     return ( *it )->hyphenated;
02022 }
02023 
02025 void KoTextParag::drawCursor( QPainter &painter, KoTextCursor *cursor, int curx, int cury, int curh, const QColorGroup &cg )
02026 {
02027     KoTextZoomHandler * zh = textDocument()->paintingZoomHandler();
02028     int x = zh->layoutUnitToPixelX( curx ) /*+ cursor->parag()->at( cursor->index() )->pixelxadj*/;
02029     //kDebug(32500) << "  drawCursor: LU: [cur]x=" << curx << ", cury=" << cury << " -> PIX: x=" << x << ", y=" << zh->layoutUnitToPixelY( cury ) << endl;
02030     KoTextParag::drawCursorDefault( painter, cursor, x,
02031                             zh->layoutUnitToPixelY( cury ),
02032                             zh->layoutUnitToPixelY( cury, curh ), cg );
02033 }
02034 
02035 // Reimplemented from KoTextParag
02036 void KoTextParag::copyParagData( KoTextParag *parag )
02037 {
02038     // Style of the previous paragraph
02039     KoParagStyle * style = parag->style();
02040     // Obey "following style" setting
02041     bool styleApplied = false;
02042     if ( style )
02043     {
02044         KoParagStyle * newStyle = style->followingStyle();
02045         if ( newStyle && style != newStyle ) // if same style, keep paragraph-specific changes as usual
02046         {
02047             setParagLayout( newStyle->paragLayout() );
02048             KoTextFormat * format = &newStyle->format();
02049             setFormat( format );
02050             format->addRef();
02051             str->setFormat( 0, format, true ); // prepare format for text insertion
02052             styleApplied = true;
02053         }
02054     }
02055     // This should never happen in KWord, but it happens in KPresenter
02056     //else
02057     //    kWarning() << "Paragraph has no style " << paragId() << endl;
02058 
02059     // No "following style" setting, or same style -> copy layout & format of previous paragraph
02060     if (!styleApplied)
02061     {
02062         setParagLayout( parag->paragLayout() );
02063         // Remove pagebreak flags from initial parag - they got copied to the new parag
02064         parag->m_layout.pageBreaking &= ~KoParagLayout::HardFrameBreakBefore;
02065         parag->m_layout.pageBreaking &= ~KoParagLayout::HardFrameBreakAfter;
02066         // Remove footnote counter text from second parag
02067         if ( m_layout.counter && m_layout.counter->numbering() == KoParagCounter::NUM_FOOTNOTE )
02068             setNoCounter();
02069         // Do not copy 'restart numbering at this paragraph' option (would be silly)
02070         if ( m_layout.counter )
02071             m_layout.counter->setRestartCounter(false);
02072 
02073         // set parag format to the format of the trailing space of the previous parag
02074         setFormat( parag->at( parag->length()-1 )->format() );
02075         // KoTextCursor::splitAndInsertEmptyParag takes care of setting the format
02076         // for the chars in the new parag
02077     }
02078 
02079     // Note: we don't call the original KoTextParag::copyParagData on purpose.
02080     // We don't want setListStyle to get called - it ruins our stylesheetitems
02081     // And we don't care about copying the stylesheetitems directly,
02082     // applying the parag layout will create them
02083 }
02084 
02085 void KoTextParag::setTabList( const KoTabulatorList &tabList )
02086 {
02087     KoTabulatorList lst( tabList );
02088     m_layout.setTabList( lst );
02089     if ( !tabList.isEmpty() )
02090     {
02091         KoTextZoomHandler* zh = textDocument()->formattingZoomHandler();
02092         int * tabs = new int[ tabList.count() + 1 ]; // will be deleted by ~KoTextParag
02093         KoTabulatorList::Iterator it = lst.begin();
02094         unsigned int i = 0;
02095         for ( ; it != lst.end() ; ++it, ++i )
02096             tabs[i] = zh->ptToLayoutUnitPixX( (*it).ptPos );
02097         tabs[i] = 0;
02098         assert( i == tabList.count() );
02099         setTabArray( tabs );
02100     } else
02101     {
02102         setTabArray( 0 );
02103     }
02104     invalidate( 0 );
02105 }
02106 
02108 int KoTextParag::nextTab( int chnum, int x, int availableWidth )
02109 {
02110     if ( !m_layout.tabList().isEmpty() )
02111     {
02112         // Fetch the zoomed and sorted tab positions from KoTextParag
02113         // We stored them there for faster access
02114         int * tArray = tabArray();
02115         int i = 0;
02116         if ( str->isRightToLeft() )
02117             i = m_layout.tabList().size() - 1;
02118         KoTextZoomHandler* zh = textDocument()->formattingZoomHandler();
02119 
02120         while ( i >= 0 && i < (int)m_layout.tabList().size() ) {
02121             //kDebug(32500) << "KoTextParag::nextTab tArray[" << i << "]=" << tArray[i] << " type " << m_layout.tabList()[i].type << endl;
02122             int tab = tArray[ i ];
02123 
02124             // If a right-aligned tab is after the right edge then assume
02125             // that it -is- on the right edge, otherwise the last letters will fall off.
02126             // This is compatible with OOo's behavior.
02127             if ( tab > availableWidth ) {
02128                 //kDebug(32500) << "Tab position adjusted to availableWidth=" << availableWidth << endl;
02129                 tab = availableWidth;
02130             }
02131 
02132             if ( str->isRightToLeft() )
02133                 tab = availableWidth - tab;
02134 
02135             if ( tab > x ) {
02136                 int type = m_layout.tabList()[i].type;
02137 
02138                 // fix the tab type for right to left text
02139                 if ( str->isRightToLeft() )
02140                     if ( type == T_RIGHT )
02141                         type = T_LEFT;
02142                     else if ( type == T_LEFT )
02143                         type = T_RIGHT;
02144 
02145                 switch ( type ) {
02146                 case T_RIGHT:
02147                 case T_CENTER:
02148                 {
02149                     // Look for the next tab (or EOL)
02150                     int c = chnum + 1;
02151                     int w = 0;
02152                     while ( c < str->length() - 1 && str->at( c ).c != '\t' && str->at( c ).c != '\n' )
02153                     {
02154                         KoTextStringChar & ch = str->at( c );
02155                         // Determine char width
02156                         // This must be done in the same way as in KoTextFormatter::format() or there can be different rounding errors.
02157                         if ( ch.isCustom() )
02158                             w += ch.customItem()->width;
02159                         else
02160                         {
02161                             KoTextFormat *charFormat = ch.format();
02162                             int ww = charFormat->charWidth( zh, false, &ch, this, c );
02163                             ww = KoTextZoomHandler::ptToLayoutUnitPt( ww );
02164                             w += ww;
02165                         }
02166                         ++c;
02167                     }
02168 
02169                     m_tabCache[chnum] = i;
02170 
02171                     if ( type == T_RIGHT )
02172                         return tab - w;
02173                     else // T_CENTER
02174                         return tab - w/2;
02175                 }
02176                 case T_DEC_PNT:
02177                 {
02178                     // Look for the next tab (or EOL), and for alignChar
02179                     // Default to right-aligned if no decimal point found (behavior from msword)
02180                     int c = chnum + 1;
02181                     int w = 0;
02182                     while ( c < str->length()-1 && str->at( c ).c != '\t' && str->at( c ).c != '\n' )
02183                     {
02184                         KoTextStringChar & ch = str->at( c );
02185                         if ( ch.c == m_layout.tabList()[i].alignChar )
02186                         {
02187                             // Can't use ch.width yet, since the formatter hasn't run over those chars
02188                             int ww = ch.format()->charWidth( zh, false, &ch, this, c );
02189                             ww = KoTextZoomHandler::ptToLayoutUnitPt( ww );
02190                             if ( str->isRightToLeft() )
02191                             {
02192                                 w = ww / 2; // center around the decimal point
02193                                 ++c;
02194                                 continue;
02195                             }
02196                             else
02197                             {
02198                                 w += ww / 2; // center around the decimal point
02199                                 break;
02200                             }
02201                         }
02202 
02203                         // Determine char width
02204                         if ( ch.isCustom() )
02205                             w += ch.customItem()->width;
02206                         else
02207                         {
02208                             int ww = ch.format()->charWidth( zh, false, &ch, this, c );
02209                             w += KoTextZoomHandler::ptToLayoutUnitPt( ww );
02210                         }
02211 
02212                         ++c;
02213                     }
02214                     m_tabCache[chnum] = i;
02215                     return tab - w;
02216                 }
02217                 default: // case T_LEFT:
02218                     m_tabCache[chnum] = i;
02219                     return tab;
02220                 }
02221             }
02222             if ( str->isRightToLeft() )
02223                 --i;
02224             else
02225                 ++i;
02226         }
02227     }
02228     // No tab list, use tab-stop-width. qrichtext.cpp has the code :)
02229     return KoTextParag::nextTabDefault( chnum, x );
02230 }
02231 
02232 void KoTextParag::applyStyle( KoParagStyle *style )
02233 {
02234     setParagLayout( style->paragLayout() );
02235     KoTextFormat *newFormat = &style->format();
02236     setFormat( 0, str->length(), newFormat );
02237     setFormat( newFormat );
02238 }
02239 
02240 void KoTextParag::setParagLayout( const KoParagLayout & layout, int flags, int marginIndex )
02241 {
02242     //kDebug(32500) << "KoTextParag::setParagLayout flags=" << flags << endl;
02243     if ( flags & KoParagLayout::Alignment )
02244         setAlign( layout.alignment );
02245     if ( flags & KoParagLayout::Margins ) {
02246         if ( marginIndex == -1 )
02247             setMargins( layout.margins );
02248         else
02249             setMargin( (Q3StyleSheetItem::Margin)marginIndex, layout.margins[marginIndex] );
02250     }
02251     if ( flags & KoParagLayout::LineSpacing )
02252     {
02253         setLineSpacingType( layout.lineSpacingType );
02254         setLineSpacing( layout.lineSpacingValue() );
02255     }
02256     if ( flags & KoParagLayout::Borders )
02257     {
02258         setLeftBorder( layout.leftBorder );
02259         setRightBorder( layout.rightBorder );
02260         setTopBorder( layout.topBorder );
02261         setBottomBorder( layout.bottomBorder );
02262         setJoinBorder( layout.joinBorder );
02263     }
02264     if ( flags & KoParagLayout::BackgroundColor )
02265     {
02266         setBackgroundColor( layout.backgroundColor );
02267     }
02268     if ( flags & KoParagLayout::BulletNumber )
02269         setCounter( layout.counter );
02270     if ( flags & KoParagLayout::Tabulator )
02271         setTabList( layout.tabList() );
02272     if ( flags == KoParagLayout::All )
02273     {
02274         setDirection( static_cast<QChar::Direction>(layout.direction) );
02275         // Don't call applyStyle from here, it would overwrite any paragraph-specific settings
02276         setStyle( layout.style );
02277     }
02278 }
02279 
02280 void KoTextParag::setCustomItem( int index, KoTextCustomItem * custom, KoTextFormat * currentFormat )
02281 {
02282     //kDebug(32500) << "KoTextParag::setCustomItem " << index << "  " << (void*)custom
02283     //               << "  currentFormat=" << (void*)currentFormat << endl;
02284     if ( currentFormat )
02285         setFormat( index, 1, currentFormat );
02286     at( index )->setCustomItem( custom );
02287     //addCustomItem();
02288     document()->registerCustomItem( custom, this );
02289     custom->recalc(); // calc value (e.g. for variables) and set initial size
02290     invalidate( 0 );
02291     setChanged( true );
02292 }
02293 
02294 void KoTextParag::removeCustomItem( int index )
02295 {
02296     Q_ASSERT( at( index )->isCustom() );
02297     KoTextCustomItem * item = at( index )->customItem();
02298     at( index )->loseCustomItem();
02299     //KoTextParag::removeCustomItem();
02300     document()->unregisterCustomItem( item, this );
02301 }
02302 
02303 
02304 int KoTextParag::findCustomItem( const KoTextCustomItem * custom ) const
02305 {
02306     int len = str->length();
02307     for ( int i = 0; i < len; ++i )
02308     {
02309         KoTextStringChar & ch = str->at(i);
02310         if ( ch.isCustom() && ch.customItem() == custom )
02311             return i;
02312     }
02313     kWarning() << "KoTextParag::findCustomItem custom item " << (void*)custom
02314               << " not found in paragraph " << paragId() << endl;
02315     return 0;
02316 }
02317 
02318 #ifndef NDEBUG
02319 void KoTextParag::printRTDebug( int info )
02320 {
02321     QString specialFlags;
02322     if ( str->needsSpellCheck() )
02323         specialFlags += " needsSpellCheck=true";
02324     if ( wasMovedDown() )
02325         specialFlags += " wasMovedDown=true";
02326     if ( partOfTableOfContents() )
02327         specialFlags += " part-of-TOC=true";
02328     kDebug(32500) << "Paragraph " << this << " (" << paragId() << ") [changed="
02329               << hasChanged() << ", valid=" << isValid()
02330               << specialFlags
02331               << "] ------------------ " << endl;
02332     if ( prev() && prev()->paragId() + 1 != paragId() )
02333         kWarning() << "  Previous paragraph " << prev() << " has ID " << prev()->paragId() << endl;
02334     if ( next() && next()->paragId() != paragId() + 1 )
02335         kWarning() << "  Next paragraph " << next() << " has ID " << next()->paragId() << endl;
02336     //if ( !next() )
02337     //    kDebug(32500) << "  next is 0L" << endl;
02338     kDebug(32500) << "  Style: " << style() << " " << ( style() ? style()->name().toLocal8Bit().data() : "NO STYLE" ) << endl;
02339     kDebug(32500) << "  Text: '" << str->toString() << "'" << endl;
02340     if ( info == 0 ) // paragraph info
02341     {
02342         if ( m_layout.counter )
02343         {
02344             m_layout.counter->printRTDebug( this );
02345         }
02346         static const char * const s_align[] = { "Auto", "Left", "Right", "ERROR", "HCenter", "ERR", "ERR", "ERR", "Justify", };
02347         static const char * const s_linespacing[] = { "Single", "1.5", "2", "custom", "atLeast", "Multiple", "Fixed" };
02348         static const char * const s_dir[] = { "DirL", "DirR", "DirEN", "DirES", "DirET", "DirAN", "DirCS", "DirB", "DirS", "DirWS", "DirON", "DirLRE", "DirLRO", "DirAL", "DirRLE", "DirRLO", "DirPDF", "DirNSM", "DirBN" };
02349         kDebug(32500) << "  align: " << s_align[alignment()] << "  resolveAlignment: " << s_align[resolveAlignment()]
02350                   << "  isRTL:" << str->isRightToLeft()
02351                   << "  dir: " << s_dir[direction()] << endl;
02352         QRect pixr = pixelRect( textDocument()->paintingZoomHandler() );
02353         kDebug(32500) << "  rect() : " << DEBUGRECT( rect() )
02354                   << "  pixelRect() : " << DEBUGRECT( pixr ) << endl;
02355         kDebug(32500) << "  topMargin()=" << topMargin() << " bottomMargin()=" << bottomMargin()
02356                   << " leftMargin()=" << leftMargin() << " firstLineMargin()=" << firstLineMargin()
02357                   << " rightMargin()=" << rightMargin() << endl;
02358         if ( kwLineSpacingType() != KoParagLayout::LS_SINGLE )
02359             kDebug(32500) << "  linespacing type=" << s_linespacing[ -kwLineSpacingType() ]
02360                            << " value=" << kwLineSpacing() << endl;
02361         const int pageBreaking = m_layout.pageBreaking;
02362         QStringList pageBreakingFlags;
02363         if ( pageBreaking & KoParagLayout::KeepLinesTogether )
02364             pageBreakingFlags.append( "KeepLinesTogether" );
02365         if ( pageBreaking & KoParagLayout::HardFrameBreakBefore )
02366             pageBreakingFlags.append( "HardFrameBreakBefore" );
02367         if ( pageBreaking & KoParagLayout::HardFrameBreakAfter )
02368             pageBreakingFlags.append( "HardFrameBreakAfter" );
02369         if ( pageBreaking & KoParagLayout::KeepWithPrevious )
02370             pageBreakingFlags.append( "KeepWithPrevious" );
02371         if ( pageBreaking & KoParagLayout::KeepWithNext )
02372             pageBreakingFlags.append( "KeepWithNext" );
02373         if ( !pageBreakingFlags.isEmpty() )
02374             kDebug(32500) << " page Breaking: " << pageBreakingFlags.join(",") << endl;
02375 
02376         static const char * const tabtype[] = { "T_LEFT", "T_CENTER", "T_RIGHT", "T_DEC_PNT", "error!!!" };
02377         KoTabulatorList tabList = m_layout.tabList();
02378         if ( tabList.isEmpty() ) {
02379             if ( str->toString().find( '\t' ) != -1 )
02380                 kDebug(32500) << "Tab width: " << textDocument()->tabStopWidth() << endl;
02381         } else {
02382             KoTabulatorList::Iterator it = tabList.begin();
02383             for ( ; it != tabList.end() ; it++ )
02384                 kDebug(32500) << "Tab type:" << tabtype[(*it).type] << " at: " << (*it).ptPos << endl;
02385         }
02386     } else if ( info == 1 ) // formatting info
02387     {
02388         kDebug(32500) << "  Paragraph format=" << paragFormat() << " " << paragFormat()->key()
02389                   << " fontsize:" << dynamic_cast<KoTextFormat *>(paragFormat())->pointSize() << endl;
02390 
02391         for ( int line = 0 ; line < lines(); ++ line ) {
02392             int y, h, baseLine;
02393             lineInfo( line, y, h, baseLine );
02394             int startOfLine;
02395             lineStartOfLine( line, &startOfLine );
02396             kDebug(32500) << "  Line " << line << " y=" << y << " height=" << h << " baseLine=" << baseLine << " startOfLine(index)=" << startOfLine << endl;
02397         }
02398         kDebug(32500) << endl;
02399         KoTextString * s = string();
02400         int lastX = 0; // pixels
02401         int lastW = 0; // pixels
02402         for ( int i = 0 ; i < s->length() ; ++i )
02403         {
02404             KoTextStringChar & ch = s->at(i);
02405             int pixelx =  textDocument()->formattingZoomHandler()->layoutUnitToPixelX( ch.x )
02406                           + ch.pixelxadj;
02407             if ( ch.lineStart )
02408                 kDebug(32500) << "LINESTART" << endl;
02409             QString attrs = " ";
02410             if ( ch.whiteSpace )
02411                 attrs += "whitespace ";
02412             if ( !ch.charStop )
02413                 attrs += "notCharStop ";
02414             if ( ch.wordStop )
02415                 attrs += "wordStop ";
02416             attrs.truncate( attrs.length() - 1 );
02417 
02418             kDebug(32500) << i << ": '" << QString(ch.c).rightJustified(2)
02419                            << "' (" << QString::number( ch.c.unicode() ).rightJustified(3) << ")"
02420                       << " x(LU)=" << ch.x
02421                       << " w(LU)=" << ch.width//s->width(i)
02422                       << " x(PIX)=" << pixelx
02423                       << " (xadj=" << + ch.pixelxadj << ")"
02424                       << " w(PIX)=" << ch.pixelwidth
02425                       << " height=" << ch.height()
02426                       << attrs
02427                 //      << " format=" << ch.format()
02428                 //      << " \"" << ch.format()->key() << "\" "
02429                 //<< " fontsize:" << dynamic_cast<KoTextFormat *>(ch.format())->pointSize()
02430                       << endl;
02431 
02432             // Check that the format is in the collection (i.e. its defaultFormat or in the dict)
02433             if ( ch.format() != textDocument()->formatCollection()->defaultFormat() )
02434                 Q_ASSERT( textDocument()->formatCollection()->dict()[ch.format()->key()] );
02435 
02436             if ( !str->isBidi() && !ch.lineStart )
02437                 Q_ASSERT( lastX + lastW == pixelx ); // looks like some rounding problem with justified spaces
02438             lastX = pixelx;
02439             lastW = ch.pixelwidth;
02440             if ( ch.isCustom() )
02441             {
02442                 KoTextCustomItem * item = ch.customItem();
02443                 kDebug(32500) << " - custom item " << item
02444                           << " ownline=" << item->ownLine()
02445                           << " size=" << item->width << "x" << item->height
02446                           << " ascent=" << item->ascent()
02447                           << endl;
02448             }
02449         }
02450     }
02451 }
02452 #endif
02453 
02454 void KoTextParag::drawFontEffects( QPainter * p, KoTextFormat *format, KoTextZoomHandler *zh, QFont font, const QColor & color, int startX, int baseLine, int bw, int lastY, int /*h*/, QChar firstChar )
02455 {
02456     // This is about drawing underlines and strikeouts
02457     // So abort immediately if there's none to draw.
02458     if ( !format->isStrikedOrUnderlined() )
02459         return;
02460     //kDebug(32500) << "drawFontEffects wordByWord=" << format->wordByWord() <<
02461     //    " firstChar='" << QString(firstChar) << "'" << endl;
02462     // paintLines ensures that we're called word by word if wordByWord is true.
02463     if ( format->wordByWord() && firstChar.isSpace() )
02464         return;
02465 
02466     double dimd;
02467     int y;
02468     int offset = 0;
02469     if (format->vAlign() == KoTextFormat::AlignSubScript )
02470         offset = p->fontMetrics().height() / 6;
02471     else if (format->vAlign() == KoTextFormat::AlignSuperScript )
02472         offset = -p->fontMetrics().height() / 2;
02473 
02474     dimd = KoBorder::zoomWidthY( format->underLineWidth(), zh, 1 );
02475     if((format->vAlign() == KoTextFormat::AlignSuperScript) ||
02476         (format->vAlign() == KoTextFormat::AlignSubScript ) || (format->vAlign() == KoTextFormat::AlignCustom ))
02477         dimd*=format->relativeTextSize();
02478     y = lastY + baseLine + offset - ( (format->vAlign() == KoTextFormat::AlignCustom)?format->offsetFromBaseLine():0 );
02479 
02480     if ( format->doubleUnderline())
02481     {
02482         QColor col = format->textUnderlineColor().isValid() ? format->textUnderlineColor(): color ;
02483         int dim=static_cast<int>(0.75*dimd);
02484         dim=dim?dim:1; //width of line should be at least 1
02485         p->save();
02486 
02487         switch( format->underlineStyle())
02488         {
02489         case KoTextFormat::U_SOLID:
02490             p->setPen( QPen( col, dim, Qt::SolidLine ) );
02491             break;
02492         case KoTextFormat::U_DASH:
02493             p->setPen( QPen( col, dim, Qt::DashLine ) );
02494             break;
02495         case KoTextFormat::U_DOT:
02496             p->setPen( QPen( col, dim, Qt::DotLine ) );
02497             break;
02498         case KoTextFormat::U_DASH_DOT:
02499             p->setPen( QPen( col, dim, Qt::DashDotLine ) );
02500             break;
02501         case KoTextFormat::U_DASH_DOT_DOT:
02502             p->setPen( QPen( col, dim, Qt::DashDotDotLine ) );
02503             break;
02504         default:
02505             p->setPen( QPen( color, dim, Qt::SolidLine ) );
02506         }
02507 
02508         y += static_cast<int>(1.125*dimd); // slightly under the baseline if possible
02509         p->drawLine( startX, y, startX + bw, y );
02510         y += static_cast<int>(1.5*dimd);
02511         p->drawLine( startX, y, startX + bw, y );
02512         p->restore();
02513         if ( font.underline() ) { // can this happen?
02514             font.setUnderline( false );
02515             p->setFont( font );
02516         }
02517     }
02518     else if ( format->underline() ||
02519                 format->underlineType() == KoTextFormat::U_SIMPLE_BOLD)
02520     {
02521 
02522         QColor col = format->textUnderlineColor().isValid() ? format->textUnderlineColor(): color ;
02523         p->save();
02524         int dim=(format->underlineType() == KoTextFormat::U_SIMPLE_BOLD)?static_cast<int>(2*dimd):static_cast<int>(dimd);
02525         dim=dim?dim:1; //width of line should be at least 1
02526         y += static_cast<int>(1.875*dimd);
02527 
02528         switch( format->underlineStyle() )
02529         {
02530         case KoTextFormat::U_SOLID:
02531             p->setPen( QPen( col, dim, Qt::SolidLine ) );
02532             break;
02533         case KoTextFormat::U_DASH:
02534             p->setPen( QPen( col, dim, Qt::DashLine ) );
02535             break;
02536         case KoTextFormat::U_DOT:
02537             p->setPen( QPen( col, dim, Qt::DotLine ) );
02538             break;
02539         case KoTextFormat::U_DASH_DOT:
02540             p->setPen( QPen( col, dim, Qt::DashDotLine ) );
02541             break;
02542         case KoTextFormat::U_DASH_DOT_DOT:
02543             p->setPen( QPen( col, dim, Qt::DashDotDotLine ) );
02544             break;
02545         default:
02546             p->setPen( QPen( col, dim, Qt::SolidLine ) );
02547         }
02548 
02549         p->drawLine( startX, y, startX + bw, y );
02550         p->restore();
02551         font.setUnderline( false );
02552         p->setFont( font );
02553     }
02554     else if ( format->waveUnderline() )
02555     {
02556         int dim=static_cast<int>(dimd);
02557         dim=dim?dim:1; //width of line should be at least 1
02558         y += dim;
02559         QColor col = format->textUnderlineColor().isValid() ? format->textUnderlineColor(): color ;
02560         p->save();
02561         int offset = 2 * dim;
02562         QPen pen(col, dim, Qt::SolidLine);
02563         pen.setCapStyle(Qt::RoundCap);
02564         p->setPen(pen);
02565         Q_ASSERT(offset);
02566         double anc=acos(1.0-2*(static_cast<double>(offset-(startX)%offset)/static_cast<double>(offset)))/3.1415*180;
02567         int pos=1;
02568         //set starting position
02569         if(2*((startX/offset)/2)==startX/offset)
02570             pos*=-1;
02571         //draw first part of wave
02572         p->drawArc( (startX/offset)*offset, y, offset, offset, 0, -qRound(pos*anc*16) );
02573         //now the main part
02574         int zigzag_x = (startX/offset+1)*offset;
02575         for ( ; zigzag_x + offset <= bw+startX; zigzag_x += offset)
02576         {
02577             p->drawArc( zigzag_x, y, offset, offset, 0, pos*180*16 );
02578             pos*=-1;
02579         }
02580         //and here we finish
02581         anc=acos(1.0-2*(static_cast<double>((startX+bw)%offset)/static_cast<double>(offset)))/3.1415*180;
02582         p->drawArc( zigzag_x, y, offset, offset, 180*16, -qRound(pos*anc*16) );
02583         p->restore();
02584         font.setUnderline( false );
02585         p->setFont( font );
02586     }
02587 
02588     dimd = KoBorder::zoomWidthY( static_cast<double>(format->pointSize())/18.0, zh, 1 );
02589     if((format->vAlign() == KoTextFormat::AlignSuperScript) ||
02590         (format->vAlign() == KoTextFormat::AlignSubScript ) || (format->vAlign() == KoTextFormat::AlignCustom ))
02591         dimd*=format->relativeTextSize();
02592     y = lastY + baseLine + offset - ( (format->vAlign() == KoTextFormat::AlignCustom)?format->offsetFromBaseLine():0 );
02593 
02594     if ( format->strikeOutType() == KoTextFormat::S_SIMPLE
02595          || format->strikeOutType() == KoTextFormat::S_SIMPLE_BOLD)
02596     {
02597         unsigned int dim = (format->strikeOutType() == KoTextFormat::S_SIMPLE_BOLD)? static_cast<int>(2*dimd) : static_cast<int>(dimd);
02598         p->save();
02599 
02600         switch( format->strikeOutStyle() )
02601         {
02602         case KoTextFormat::S_SOLID:
02603             p->setPen( QPen( color, dim, Qt::SolidLine ) );
02604             break;
02605         case KoTextFormat::S_DASH:
02606             p->setPen( QPen( color, dim, Qt::DashLine ) );
02607             break;
02608         case KoTextFormat::S_DOT:
02609             p->setPen( QPen( color, dim, Qt::DotLine ) );
02610             break;
02611         case KoTextFormat::S_DASH_DOT:
02612             p->setPen( QPen( color, dim, Qt::DashDotLine ) );
02613             break;
02614         case KoTextFormat::S_DASH_DOT_DOT:
02615             p->setPen( QPen( color, dim, Qt::DashDotDotLine ) );
02616             break;
02617         default:
02618             p->setPen( QPen( color, dim, Qt::SolidLine ) );
02619         }
02620 
02621         y -= static_cast<int>(5*dimd);
02622         p->drawLine( startX, y, startX + bw, y );
02623         p->restore();
02624         font.setStrikeOut( false );
02625         p->setFont( font );
02626     }
02627     else if ( format->strikeOutType() == KoTextFormat::S_DOUBLE )
02628     {
02629         unsigned int dim = static_cast<int>(dimd);
02630         p->save();
02631 
02632         switch( format->strikeOutStyle() )
02633         {
02634         case KoTextFormat::S_SOLID:
02635             p->setPen( QPen( color, dim, Qt::SolidLine ) );
02636             break;
02637         case KoTextFormat::S_DASH:
02638             p->setPen( QPen( color, dim, Qt::DashLine ) );
02639             break;
02640         case KoTextFormat::S_DOT:
02641             p->setPen( QPen( color, dim, Qt::DotLine ) );
02642             break;
02643         case KoTextFormat::S_DASH_DOT:
02644             p->setPen( QPen( color, dim, Qt::DashDotLine ) );
02645             break;
02646         case KoTextFormat::S_DASH_DOT_DOT:
02647             p->setPen( QPen( color, dim, Qt::DashDotDotLine ) );
02648             break;
02649         default:
02650             p->setPen( QPen( color, dim, Qt::SolidLine ) );
02651         }
02652 
02653         y -= static_cast<int>(4*dimd);
02654         p->drawLine( startX, y, startX + bw, y);
02655         y -= static_cast<int>(2*dimd);
02656         p->drawLine( startX, y, startX + bw, y);
02657         p->restore();
02658         font.setStrikeOut( false );
02659         p->setFont( font );
02660     }
02661 
02662 }
02663 
02664 // ### is this method correct for RightToLeft text?
02665 QString KoTextParag::toString( int from, int length ) const
02666 {
02667     QString str;
02668     if ( from == 0 && m_layout.counter && m_layout.counter->numbering() != KoParagCounter::NUM_FOOTNOTE )
02669         str += m_layout.counter->text( this ) + ' ';
02670     if ( length == -1 )
02671         length = this->length() - 1 /*trailing space*/ - from;
02672     for ( int i = from ; i < (length+from) ; ++i )
02673     {
02674         KoTextStringChar *ch = at( i );
02675         if ( ch->isCustom() )
02676         {
02677             KoVariable * var = dynamic_cast<KoVariable *>(ch->customItem());
02678             if ( var )
02679                 str += var->text(true);
02680             else //frame inline
02681                 str +=' ';
02682         }
02683         else
02684             str += ch->c;
02685     }
02686     return str;
02687 }
02688 
02689 void KoTextParag::loadOasisSpan( const QDomElement& parent, KoOasisContext& context, uint& pos )
02690 {
02691     // Parse every child node of the parent
02692     // Can't use forEachElement here since we also care about text nodes
02693     QDomNode node;
02694     for ( node = parent.firstChild(); !node.isNull(); node = node.nextSibling() )
02695     {
02696         QDomElement ts = node.toElement();
02697         QString textData;
02698         const QString localName( ts.localName() );
02699         const bool isTextNS = ts.namespaceURI() == KoXmlNS::text;
02700         KoTextCustomItem* customItem = 0;
02701 
02702         // allow loadSpanTag to modify the stylestack
02703         context.styleStack().save();
02704 
02705         // Try to keep the order of the tag names by probability of happening
02706         if ( node.isText() )
02707         {
02708             textData = node.toText().data();
02709         }
02710         else if ( isTextNS && localName == "span" ) // text:span
02711         {
02712             context.styleStack().save();
02713             context.fillStyleStack( ts, KoXmlNS::text, "style-name", "text" );
02714             loadOasisSpan( ts, context, pos ); // recurse
02715             context.styleStack().restore();
02716         }
02717         else if ( isTextNS && localName == "s" ) // text:s
02718         {
02719             int howmany = 1;
02720             if (ts.hasAttributeNS( KoXmlNS::text, "c"))
02721                 howmany = ts.attributeNS( KoXmlNS::text, "c", QString::null).toInt();
02722 
02723             textData.fill(32, howmany);
02724         }
02725         else if ( isTextNS && localName == "tab" ) // text:tab (it's tab-stop in OO-1.1 but tab in oasis)
02726         {
02727             textData = '\t';
02728         }
02729         else if ( isTextNS && localName == "line-break" ) // text:line-break
02730         {
02731             textData = '\n';
02732         }
02733         else if ( isTextNS && localName == "number" ) // text:number
02734         {
02735             // This is the number in front of a numbered paragraph,
02736             // written out to help export filters. We can ignore it.
02737         }
02738         else if ( node.isProcessingInstruction() )
02739         {
02740             QDomProcessingInstruction pi = node.toProcessingInstruction();
02741             if ( pi.target() == "opendocument" && pi.data().startsWith( "cursor-position" ) )
02742             {
02743                 context.setCursorPosition( this, pos );
02744             }
02745         }
02746         else
02747         {
02748             bool handled = false;
02749             // Check if it's a variable
02750             KoVariable* var = context.variableCollection().loadOasisField( textDocument(), ts, context );
02751             if ( var )
02752             {
02753                 textData = "#";     // field placeholder
02754                 customItem = var;
02755                 handled = true;
02756             }
02757             if ( !handled )
02758             {
02759                 handled = textDocument()->loadSpanTag( ts, context,
02760                                                        this, pos,
02761                                                        textData, customItem );
02762                 if ( !handled )
02763                 {
02764                     kWarning(32500) << "Ignoring tag " << ts.tagName() << endl;
02765                     context.styleStack().restore();
02766                     continue;
02767                 }
02768             }
02769         }
02770 
02771         const uint length = textData.length();
02772         if ( length )
02773         {
02774             insert( pos, textData );
02775             if ( customItem )
02776                 setCustomItem( pos, customItem, 0 );
02777             KoTextFormat f;
02778             f.load( context );
02779             //kDebug(32500) << "loadOasisSpan: applying formatting from " << pos << " to " << pos+length << "\n   format=" << f.key() << endl;
02780             setFormat( pos, length, document()->formatCollection()->format( &f ), true );
02781             pos += length;
02782         }
02783         context.styleStack().restore();
02784     }
02785 }
02786 
02787 KoParagLayout KoTextParag::loadParagLayout( KoOasisContext& context, KoStyleCollection *styleCollection, bool findStyle )
02788 {
02789     KoParagLayout layout;
02790 
02791     // Only when loading paragraphs, not when loading styles
02792     if ( findStyle )
02793     {
02794         KoParagStyle *style;
02795         // Name of the style. If there is no style, then we do not supply
02796         // any default!
02797         QString styleName = context.styleStack().userStyleName( "paragraph" );
02798         if ( !styleName.isEmpty() )
02799         {
02800             style = styleCollection->findStyle( styleName );
02801             // When pasting the style names are random, the display names matter
02802             if (!style)
02803                 style = styleCollection->findStyleByDisplayName( context.styleStack().userStyleDisplayName( "paragraph" ) );
02804             if (!style)
02805             {
02806                 kError(32500) << "Cannot find style \"" << styleName << "\" - using Standard" << endl;
02807                 style = styleCollection->findStyle( "Standard" );
02808             }
02809             //else kDebug() << "KoParagLayout::KoParagLayout setting style to " << style << " " << style->name() << endl;
02810         }
02811         else
02812         {
02813             kError(32500) << "No style name !? - using Standard" << endl;
02814             style = styleCollection->findStyle( "Standard" );
02815         }
02816         Q_ASSERT(style);
02817         layout.style = style;
02818     }
02819 
02820     KoParagLayout::loadOasisParagLayout( layout, context );
02821 
02822     return layout;
02823 }
02824 
02825 void KoTextParag::loadOasis( const QDomElement& parent, KoOasisContext& context, KoStyleCollection *styleCollection, uint& pos )
02826 {
02827     // First load layout from style
02828     KoParagLayout paragLayout = loadParagLayout( context, styleCollection, true );
02829     setParagLayout( paragLayout );
02830 
02831     // Load paragraph format
02832     KoTextFormat defaultFormat;
02833     defaultFormat.load( context );
02834     setFormat( document()->formatCollection()->format( &defaultFormat ) );
02835 
02836     // Load text
02837     loadOasisSpan( parent, context, pos );
02838 
02839     // Apply default format to trailing space
02840     const int len = str->length();
02841     Q_ASSERT( len >= 1 );
02842     setFormat( len - 1, 1, paragFormat(), true );
02843 
02844     setChanged( true );
02845     invalidate( 0 );
02846 }
02847 
02848 void KoTextParag::saveOasis( KoXmlWriter& writer, KoSavingContext& context,
02849                              int from /* default 0 */, int to /* usually length()-2 */,
02850                              bool /*saveAnchorsFramesets*/ /* default false */ ) const
02851 {
02852     KoGenStyles& mainStyles = context.mainStyles();
02853 
02854     // Write paraglayout to styles (with parent == the parag's style)
02855     QString parentStyleName;
02856     if ( m_layout.style )
02857         parentStyleName = m_layout.style->name();
02858 
02859     KoGenStyle autoStyle( KoGenStyle::STYLE_AUTO, "paragraph", parentStyleName );
02860     paragFormat()->save( autoStyle, context );
02861     m_layout.saveOasis( autoStyle, context, false );
02862 
02863     // First paragraph is special, it includes page-layout info (for word-processing at least)
02864     if ( !prev() ) {
02865         if ( context.variableSettings() )
02866             autoStyle.addProperty( "style:page-number", context.variableSettings()->startingPageNumber() );
02867         // Well we support only one page layout, so the first parag always points to "Standard".
02868         autoStyle.addAttribute( "style:master-page-name", "Standard" );
02869     }
02870 
02871 
02872     QString autoParagStyleName = mainStyles.lookup( autoStyle, "P", KoGenStyles::ForceNumbering );
02873 
02874     KoParagCounter* paragCounter = m_layout.counter;
02875     // outline (text:h) assumes paragCounter != 0 (because depth is mandatory)
02876     bool outline = m_layout.style && m_layout.style->isOutline() && paragCounter;
02877     bool normalList = paragCounter && paragCounter->style() != KoParagCounter::STYLE_NONE && !outline;
02878     if ( normalList ) // non-heading list
02879     {
02880         writer.startElement( "text:numbered-paragraph" );
02881         writer.addAttribute( "text:level", (int)paragCounter->depth() + 1 );
02882         if ( paragCounter->restartCounter() )
02883             writer.addAttribute( "text:start-value", paragCounter->startNumber() );
02884 
02885         KoGenStyle listStyle( KoGenStyle::STYLE_AUTO_LIST /*, no family*/ );
02886         paragCounter->saveOasis( listStyle );
02887 
02888         QString autoListStyleName = mainStyles.lookup( listStyle, "L", KoGenStyles::ForceNumbering );
02889         writer.addAttribute( "text:style-name", autoListStyleName );
02890 
02891         QString textNumber = m_layout.counter->text( this );
02892         if ( !textNumber.isEmpty() )
02893         {
02894             // This is to help export filters
02895             writer.startElement( "text:number" );
02896             writer.addTextNode( textNumber );
02897             writer.endElement();
02898         }
02899     }
02900     else if ( outline ) // heading
02901     {
02902         writer.startElement( "text:h", false /*no indent inside this tag*/ );
02903         writer.addAttribute( "text:style-name", autoParagStyleName );
02904         writer.addAttribute( "text:outline-level", (int)paragCounter->depth() + 1 );
02905         if ( paragCounter->numbering() == KoParagCounter::NUM_NONE )
02906             writer.addAttribute( "text:is-list-header", "true" );
02907 
02908         QString textNumber = paragCounter->text( this );
02909         if ( !textNumber.isEmpty() )
02910         {
02911             // This is to help export filters
02912             writer.startElement( "text:number" );
02913             writer.addTextNode( textNumber );
02914             writer.endElement();
02915         }
02916     }
02917 
02918     if ( !outline ) // normal (non-numbered) paragraph, or normalList
02919     {
02920         writer.startElement( "text:p", false /*no indent inside this tag*/ );
02921         writer.addAttribute( "text:style-name", autoParagStyleName );
02922     }
02923 
02924     QString text = str->toString();
02925     Q_ASSERT( text.right(1)[0] == ' ' );
02926 
02927     const int cursorIndex = context.cursorTextParagraph() == this ? context.cursorTextIndex() : -1;
02928 
02929     //kDebug() << k_funcinfo << "'" << text << "' from=" << from << " to=" << to << " cursorIndex=" << cursorIndex << endl;
02930 
02931     // A helper method would need no less than 7 params...
02932 #define WRITESPAN( next ) { \
02933         if ( curFormat == paragFormat() ) {                             \
02934             writer.addTextSpan( text.mid( startPos, next - startPos ), m_tabCache ); \
02935         } else {                                                        \
02936             KoGenStyle gs( KoGenStyle::STYLE_AUTO, "text" );            \
02937             curFormat->save( gs, context, paragFormat() );              \
02938             writer.startElement( "text:span" );                         \
02939             if ( !gs.isEmpty() ) {                                      \
02940                 const QString autoStyleName = mainStyles.lookup( gs, "T" ); \
02941                 writer.addAttribute( "text:style-name", autoStyleName );    \
02942             }                                                           \
02943             writer.addTextSpan( text.mid( startPos, next - startPos ), m_tabCache ); \
02944             writer.endElement();                                        \
02945         }                                                               \
02946     }
02947 #define ISSTARTBOOKMARK( i ) bkStartIter != bookmarkStarts.end() && (*bkStartIter).pos == i
02948 #define ISENDBOOKMARK( i ) bkEndIter != bookmarkEnds.end() && (*bkEndIter).pos == i
02949 #define CHECKPOS( i ) \
02950         if ( cursorIndex == i ) { \
02951             writer.addProcessingInstruction( "opendocument cursor-position" ); \
02952         } \
02953         if ( ISSTARTBOOKMARK( i ) ) { \
02954             if ( (*bkStartIter).startEqualsEnd ) \
02955                 writer.startElement( "text:bookmark" ); \
02956             else \
02957                 writer.startElement( "text:bookmark-start" ); \
02958             writer.addAttribute( "text:name", (*bkStartIter).name ); \
02959             writer.endElement(); \
02960             ++bkStartIter; \
02961         } \
02962         if ( ISENDBOOKMARK( i ) ) { \
02963             writer.startElement( "text:bookmark-end" ); \
02964             writer.addAttribute( "text:name", (*bkEndIter).name ); \
02965             writer.endElement(); \
02966             ++bkEndIter; \
02967         }
02968 
02969 
02970 
02971     // Make (shallow) copy of bookmark list, since saving an inline frame might overwrite it
02972     // from the context while we're saving this paragraph.
02973     typedef KoSavingContext::BookmarkPositions BookmarkPositions;
02974     BookmarkPositions bookmarkStarts = context.bookmarkStarts();
02975     BookmarkPositions::const_iterator bkStartIter = bookmarkStarts.begin();
02976     while ( bkStartIter != bookmarkStarts.end() && (*bkStartIter).pos < from )
02977         ++bkStartIter;
02978     //int nextBookmarkStart = bkStartIter == bookmarkStarts.end() ? -1 : (*bkStartIter).pos;
02979     BookmarkPositions bookmarkEnds = context.bookmarkEnds();
02980     BookmarkPositions::const_iterator bkEndIter = bookmarkEnds.begin();
02981     while ( bkEndIter != bookmarkEnds.end() && (*bkEndIter).pos < from )
02982         ++bkEndIter;
02983 
02984     KoTextFormat *curFormat = 0;
02985     KoTextFormat *lastFormatRaw = 0; // this is for speeding up "removing misspelled" from each char
02986     KoTextFormat *lastFormatFixed = 0; // raw = as stored in the chars; fixed = after removing misspelled
02987     int startPos = from;
02988     for ( int i = from; i <= to; ++i ) {
02989         KoTextStringChar & ch = str->at(i);
02990         KoTextFormat * newFormat = static_cast<KoTextFormat *>( ch.format() );
02991         if ( newFormat->isMisspelled() ) {
02992             if ( newFormat == lastFormatRaw )
02993                 newFormat = lastFormatFixed; // the fast way
02994             else
02995             {
02996                 lastFormatRaw = newFormat;
02997                 // Remove isMisspelled from format, to avoid useless derived styles
02998                 // (which would be identical to their parent style)
02999                 KoTextFormat tmpFormat( *newFormat );
03000                 tmpFormat.setMisspelled( false );
03001                 newFormat = formatCollection()->format( &tmpFormat );
03002                 lastFormatFixed = newFormat;
03003             }
03004         }
03005         if ( !curFormat )
03006             curFormat = newFormat;
03007         if ( newFormat != curFormat  // Format changed, save previous one.
03008              || ch.isCustom() || cursorIndex == i || ISSTARTBOOKMARK( i ) || ISENDBOOKMARK( i ) )
03009         {
03010             WRITESPAN( i ) // write text up to i-1
03011             startPos = i;
03012             curFormat = newFormat;
03013         }
03014         CHECKPOS( i ) // do cursor position and bookmarks
03015         if ( ch.isCustom() ) {
03016             KoGenStyle gs( KoGenStyle::STYLE_AUTO, "text" );
03017             curFormat->save( gs, context, paragFormat() );
03018             writer.startElement( "text:span" );
03019             if ( !gs.isEmpty() ) {
03020                 const QString autoStyleName = mainStyles.lookup( gs, "T" );
03021                 writer.addAttribute( "text:style-name", autoStyleName );
03022             }
03023             KoTextCustomItem* customItem = ch.customItem();
03024             customItem->saveOasis( writer, context );
03025             writer.endElement();
03026             startPos = i + 1;
03027         }
03028     }
03029 
03030     //kDebug() << k_funcinfo << "startPos=" << startPos << " to=" << to << " curFormat=" << curFormat << endl;
03031 
03032     if ( to >= startPos ) { // Save last format
03033         WRITESPAN( to + 1 )
03034     }
03035     CHECKPOS( to + 1 ) // do cursor position and bookmarks
03036 
03037     writer.endElement(); // text:p or text:h
03038     if ( normalList )
03039         writer.endElement(); // text:numbered-paragraph (englobing a text:p)
03040 }
03041 
03042 void KoTextParag::applyListStyle( KoOasisContext& context, int restartNumbering, bool orderedList, bool heading, int level )
03043 {
03044     //kDebug(32500) << k_funcinfo << "applyListStyle to parag " << this << " heading=" << heading << endl;
03045     delete m_layout.counter;
03046     m_layout.counter = new KoParagCounter;
03047     m_layout.counter->loadOasis( context, restartNumbering, orderedList, heading, level );
03048     // We emulate space-before with a left paragraph indent (#109223)
03049     const QDomElement listStyleProperties = context.listStyleStack().currentListStyleProperties();
03050     if ( listStyleProperties.hasAttributeNS( KoXmlNS::text, "space-before" ) )
03051     {
03052         double spaceBefore = KoUnit::parseValue( listStyleProperties.attributeNS( KoXmlNS::text, "space-before", QString::null ) );
03053         m_layout.margins[ Q3StyleSheetItem::MarginLeft ] += spaceBefore; // added to left-margin, see 15.12 in spec.
03054     }
03055     // need to call invalidateCounters() ? Not during the initial loading at least.
03056 }
03057 
03058 int KoTextParag::documentWidth() const
03059 {
03060     return doc ? doc->width() : 0; //docRect.width();
03061 }
03062 
03063 //int KoTextParag::documentVisibleWidth() const
03064 //{
03065 //    return doc ? doc->visibleWidth() : 0; //docRect.width();
03066 //}
03067 
03068 int KoTextParag::documentX() const
03069 {
03070     return doc ? doc->x() : 0; //docRect.x();
03071 }
03072 
03073 int KoTextParag::documentY() const
03074 {
03075     return doc ? doc->y() : 0; //docRect.y();
03076 }
03077 
03078 void KoTextParag::fixParagWidth( bool viewFormattingChars )
03079 {
03080     // Fixing the parag rect for the formatting chars (only CR here, KWord handles framebreak).
03081     if ( viewFormattingChars && lineStartList().count() == 1 ) // don't use lines() here, parag not formatted yet
03082     {
03083         KoTextFormat * lastFormat = at( length() - 1 )->format();
03084         setWidth( qMin( rect().width() + lastFormat->width('x'), doc->width() ) );
03085     }
03086     // Warning, if adding anything else here, adjust KWTextFrameSet::fixParagWidth
03087 }
03088 
03089 // Called by KoTextParag::drawParagString - all params are in pixel coordinates
03090 void KoTextParag::drawFormattingChars( QPainter &painter, int start, int len,
03091                                        int lastY_pix, int baseLine_pix, int h_pix, // in pixels
03092                                        bool /*drawSelections*/,
03093                                        KoTextFormat * /*lastFormat*/, const Q3MemArray<int> &/*selectionStarts*/,
03094                                        const Q3MemArray<int> &/*selectionEnds*/, const QColorGroup & /*cg*/,
03095                                        bool rightToLeft, int /*line*/, KoTextZoomHandler* zh,
03096                                        int whichFormattingChars )
03097 {
03098     if ( !whichFormattingChars )
03099         return;
03100     painter.save();
03101     //QPen pen( cg.color( QColorGroup::Highlight ) );
03102     QPen pen( KGlobalSettings::linkColor() ); // #101820
03103     painter.setPen( pen );
03104     //kDebug() << "KWTextParag::drawFormattingChars start=" << start << " len=" << len << " length=" << length() << endl;
03105     if ( start + len == length() && ( whichFormattingChars & FormattingEndParag ) )
03106     {
03107         // drawing the end of the parag
03108         KoTextStringChar &ch = str->at( length() - 1 );
03109         KoTextFormat* format = static_cast<KoTextFormat *>( ch.format() );
03110         int w = format->charWidth( zh, true, &ch, this, 'X' );
03111         int size = qMin( w, h_pix * 3 / 4 );
03112         // x,y is the bottom right corner of the
03113         //kDebug() << "startX=" << startX << " bw=" << bw << " w=" << w << endl;
03114         int x;
03115         if ( rightToLeft )
03116             x = zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ + ch.pixelwidth - 1;
03117         else
03118             x = zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ + w;
03119         int y = lastY_pix + baseLine_pix;
03120         //kDebug() << "KWTextParag::drawFormattingChars drawing CR at " << x << "," << y << endl;
03121         painter.drawLine( (int)(x - size * 0.2), y - size, (int)(x - size * 0.2), y );
03122         painter.drawLine( (int)(x - size * 0.5), y - size, (int)(x - size * 0.5), y );
03123         painter.drawLine( x, y, (int)(x - size * 0.7), y );
03124         painter.drawLine( x, y - size, (int)(x - size * 0.5), y - size);
03125         painter.drawArc( x - size, y - size, size, (int)(size / 2), -90*16, -180*16 );
03126 #ifdef DEBUG_FORMATTING
03127         painter.setPen( Qt::blue );
03128         painter.drawRect( zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ - 1, lastY_pix, ch.pixelwidth, baseLine_pix );
03129         QPen pen( cg.color( QColorGroup::Highlight ) );
03130         painter.setPen( pen );
03131 #endif
03132     }
03133 
03134     // Now draw spaces, tabs and newlines
03135     if ( (whichFormattingChars & FormattingSpace) ||
03136          (whichFormattingChars & FormattingTabs) ||
03137          (whichFormattingChars & FormattingBreak) )
03138     {
03139         int end = qMin( start + len, length() - 1 ); // don't look at the trailing space
03140         for ( int i = start ; i < end ; ++i )
03141         {
03142             KoTextStringChar &ch = str->at(i);
03143 #ifdef DEBUG_FORMATTING
03144             painter.setPen( (i % 2)? Qt::red: Qt::green );
03145             painter.drawRect( zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ - 1, lastY_pix, ch.pixelwidth, baseLine_pix );
03146             QPen pen( cg.color( QColorGroup::Highlight ) );
03147             painter.setPen( pen );
03148 #endif
03149             if ( ch.isCustom() )
03150                 continue;
03151             if ( (ch.c == ' ' || ch.c.unicode() == 0x00a0U)
03152                  && (whichFormattingChars & FormattingSpace))
03153             {
03154                 // Don't use ch.pixelwidth here. We want a square with
03155                 // the same size for all spaces, even the justified ones
03156                 int w = zh->layoutUnitToPixelX( ch.format()->width( ' ' ) );
03157                 int height = zh->layoutUnitToPixelY( ch.ascent() );
03158                 int size = qMax( 2, qMin( w/2, height/3 ) ); // Enfore that it's a square, and that it's visible
03159                 int x = zh->layoutUnitToPixelX( ch.x ); // + ch.pixelxadj;
03160                 QRect spcRect( x + (ch.pixelwidth - size) / 2, lastY_pix + baseLine_pix - (height - size) / 2, size, size );
03161                 if ( ch.c == ' ' )
03162                     painter.drawRect( spcRect );
03163                 else // nbsp
03164                     painter.fillRect( spcRect, pen.color() );
03165             }
03166             else if ( ch.c == '\t' && (whichFormattingChars & FormattingTabs) )
03167             {
03168                 /*KoTextStringChar &nextch = str->at(i+1);
03169                   int nextx = (nextch.x > ch.x) ? nextch.x : rect().width();
03170                   //kDebug() << "tab x=" << ch.x << " nextch.x=" << nextch.x
03171                   //          << " nextx=" << nextx << " startX=" << startX << " bw=" << bw << endl;
03172                   int availWidth = nextx - ch.x - 1;
03173                   availWidth=zh->layoutUnitToPixelX(availWidth);*/
03174 
03175                 int availWidth = ch.pixelwidth;
03176 
03177                 KoTextFormat* format = ch.format();
03178                 int x = zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ + availWidth / 2;
03179                 int charWidth = format->screenFontMetrics( zh ).width( 'W' );
03180                 int size = qMin( availWidth, charWidth ) / 2 ; // actually the half size
03181                 int y = lastY_pix + baseLine_pix - zh->layoutUnitToPixelY( ch.ascent()/2 );
03182                 int arrowsize = zh->zoomItYOld( 2 );
03183                 painter.drawLine( x - size, y, x + size, y );
03184                 if ( rightToLeft )
03185                 {
03186                     painter.drawLine( x - size, y, x - size + arrowsize, y - arrowsize );
03187                     painter.drawLine( x - size, y, x - size + arrowsize, y + arrowsize );
03188                 }
03189                 else
03190                 {
03191                     painter.drawLine( x + size, y, x + size - arrowsize, y - arrowsize );
03192                     painter.drawLine( x + size, y, x + size - arrowsize, y + arrowsize );
03193                 }
03194             }
03195             else if ( ch.c == '\n' && (whichFormattingChars & FormattingBreak) )
03196             {
03197                 // draw line break
03198                 KoTextFormat* format = static_cast<KoTextFormat *>( ch.format() );
03199                 int w = format->charWidth( zh, true, &ch, this, 'X' );
03200                 int size = qMin( w, h_pix * 3 / 4 );
03201                 int arrowsize = zh->zoomItYOld( 2 );
03202                 // x,y is the bottom right corner of the reversed L
03203                 //kDebug() << "startX=" << startX << " bw=" << bw << " w=" << w << endl;
03204                 int y = lastY_pix + baseLine_pix - arrowsize;
03205                 //kDebug() << "KWTextParag::drawFormattingChars drawing Line Break at " << x << "," << y << endl;
03206                 if ( rightToLeft )
03207                 {
03208                     int x = zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ + ch.pixelwidth - 1;
03209                     painter.drawLine( x - size, y - size, x - size, y );
03210                     painter.drawLine( x - size, y, (int)(x - size * 0.3), y );
03211                     // Now the arrow
03212                     painter.drawLine( (int)(x - size * 0.3), y, (int)(x - size * 0.3 - arrowsize), y - arrowsize );
03213                     painter.drawLine( (int)(x - size * 0.3), y, (int)(x - size * 0.3 - arrowsize), y + arrowsize );
03214                 }
03215                 else
03216                 {
03217                     int x = zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ + w - 1;
03218                     painter.drawLine( x, y - size, x, y );
03219                     painter.drawLine( x, y, (int)(x - size * 0.7), y );
03220                     // Now the arrow
03221                     painter.drawLine( (int)(x - size * 0.7), y, (int)(x - size * 0.7 + arrowsize), y - arrowsize );
03222                     painter.drawLine( (int)(x - size * 0.7), y, (int)(x - size * 0.7 + arrowsize), y + arrowsize );
03223                 }
03224             }
03225         }
03226         painter.restore();
03227     }
03228 }
03229 
03230 int KoTextParag::heightForLineSpacing( int startChar, int lastChar ) const
03231 {
03232     int h = 0;
03233     int end = qMin( lastChar, length() - 1 ); // don't look at the trailing space
03234     for( int i = startChar; i <= end; ++i )
03235     {
03236         const KoTextStringChar &chr = str->at( i );
03237         if ( !chr.isCustom() )
03238             h = qMax( h, chr.format()->height() );
03239     }
03240     return h;
03241 }

Généré le Wed Nov 22 23:41:10 2006 pour KPlato par  doxygen 1.5.1-p1