lib Library API Documentation

kotextparag.cc

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