lib

KoTextParag.cpp

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