lib

KoTextParag.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2001-2006 David Faure <faure@kde.org>
3 Copyright (C) 2005-2006 Martin Ellis <martin.ellis@kdemail.net>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19*/
20
21#include "KoTextParag.h"
22#include "KoTextDocument.h"
23#include "KoParagCounter.h"
24#include "KoTextZoomHandler.h"
25#include "KoStyleCollection.h"
26#include "KoVariable.h"
27#include <KoOasisContext.h>
28#include <KoXmlWriter.h>
29#include <KoGenStyles.h>
30#include <KoDom.h>
31#include <KoXmlNS.h>
32#include <tdeglobal.h>
33#include <tdelocale.h>
34#include <kdebug.h>
35#include <tdeglobalsettings.h>
36#include <assert.h>
37
38//#define DEBUG_PAINT
39
40KoTextParag::KoTextParag( KoTextDocument *d, KoTextParag *pr, KoTextParag *nx, bool updateIds )
41 : p( pr ), n( nx ), doc( d ),
42 m_invalid( true ),
43 changed( false ),
44 fullWidth( true ),
45 newLinesAllowed( true ), // default in kotext
46 visible( true ), //breakable( true ),
47 movedDown( false ),
48 m_toc( false ),
49 align( 0 ),
50 m_lineChanged( -1 ),
51 m_wused( 0 ),
52 mSelections( 0 ),
53 mFloatingItems( 0 ),
54 tArray( 0 )
55{
56 defFormat = formatCollection()->defaultFormat();
57 /*if ( !doc ) {
58 tabStopWidth = defFormat->width( 'x' ) * 8;
59 commandHistory = new KoTextDocCommandHistory( 100 );
60 }*/
61
62 if ( p ) {
63 p->n = this;
64 }
65 if ( n ) {
66 n->p = this;
67 }
68
69 if ( !p && doc )
70 doc->setFirstParag( this );
71 if ( !n && doc )
72 doc->setLastParag( this );
73
74 //firstFormat = true; //// unused
75 //firstPProcess = true;
76 //state = -1;
77 //needPreProcess = false;
78
79 if ( p )
80 id = p->id + 1;
81 else
82 id = 0;
83 if ( n && updateIds ) {
84 KoTextParag *s = n;
85 while ( s ) {
86 s->id = s->p->id + 1;
87 //s->lm = s->rm = s->tm = s->bm = -1, s->flm = -1;
88 s = s->n;
89 }
90 }
91
92 str = new KoTextString();
93 str->insert( 0, " ", formatCollection()->defaultFormat() );
94 setJoinBorder( true );
95}
96
97KoTextParag::~KoTextParag()
98{
99 //kdDebug(32500) << "KoTextParag::~KoTextParag " << this << " id=" << paragId() << endl;
100
101 // #107961: unregister custom items; KoTextString::clear() will delete them
102 const int len = str->length();
103 for ( int i = 0; i < len; ++i ) {
104 KoTextStringChar *c = at( i );
105 if ( doc && c->isCustom() ) {
106 doc->unregisterCustomItem( c->customItem(), this );
107 //removeCustomItem();
108 }
109 }
110
111 delete str;
112 str = 0;
113// if ( doc && p == doc->minwParag ) {
114// doc->minwParag = 0;
115// doc->minw = 0;
116// }
117 if ( !doc ) {
118 //delete pFormatter;
119 //delete commandHistory;
120 }
121 delete [] tArray;
122 //delete eData;
123 TQMap<int, KoTextParagLineStart*>::Iterator it = lineStarts.begin();
124 for ( ; it != lineStarts.end(); ++it )
125 delete *it;
126 if ( mSelections ) delete mSelections;
127 if ( mFloatingItems ) delete mFloatingItems;
128
129 if (p)
130 p->setNext(n);
131 if (n)
132 n->setPrev(p);
133
135 if ( doc && !doc->isDestroying() )
136 {
137 doc->informParagraphDeleted( this );
138 }
139 //kdDebug(32500) << "KoTextParag::~KoTextParag " << this << " done" << endl;
141}
142
143void KoTextParag::setNext( KoTextParag *s )
144{
145 n = s;
146 if ( !n && doc )
147 doc->setLastParag( this );
148}
149
150void KoTextParag::setPrev( KoTextParag *s )
151{
152 p = s;
153 if ( !p && doc )
154 doc->setFirstParag( this );
155}
156
157void KoTextParag::invalidate( int /*chr, ignored*/ )
158{
159 m_invalid = true;
160#if 0
161 if ( invalid < 0 )
162 invalid = chr;
163 else
164 invalid = TQMIN( invalid, chr );
165#endif
166}
167
168void KoTextParag::setChanged( bool b, bool /*recursive*/ )
169{
170 changed = b;
171 m_lineChanged = -1; // all
172}
173
174void KoTextParag::setLineChanged( short int line )
175{
176 if ( m_lineChanged == -1 ) {
177 if ( !changed ) // only if the whole parag wasn't "changed" already
178 m_lineChanged = line;
179 }
180 else
181 m_lineChanged = TQMIN( m_lineChanged, line ); // also works if line=-1
182 changed = true;
183 //kdDebug(32500) << "KoTextParag::setLineChanged line=" << line << " -> m_lineChanged=" << m_lineChanged << endl;
184}
185
186void KoTextParag::insert( int index, const TQString &s )
187{
188 str->insert( index, s, formatCollection()->defaultFormat() );
189 invalidate( index );
190 //needPreProcess = true;
191}
192
193void KoTextParag::truncate( int index )
194{
195 str->truncate( index );
196 insert( length(), " " );
197 //needPreProcess = true;
198}
199
200void KoTextParag::remove( int index, int len )
201{
202 if ( index + len - str->length() > 0 )
203 return;
204 for ( int i = index; i < index + len; ++i ) {
205 KoTextStringChar *c = at( i );
206 if ( doc && c->isCustom() ) {
207 doc->unregisterCustomItem( c->customItem(), this );
208 //removeCustomItem();
209 }
210 }
211 str->remove( index, len );
212 invalidate( 0 );
213 //needPreProcess = true;
214}
215
216void KoTextParag::join( KoTextParag *s )
217{
218 //kdDebug(32500) << "KoTextParag::join this=" << paragId() << " (length " << length() << ") with " << s->paragId() << " (length " << s->length() << ")" << endl;
219 int oh = r.height() + s->r.height();
220 n = s->n;
221 if ( n )
222 n->p = this;
223 else if ( doc )
224 doc->setLastParag( this );
225
226 int start = str->length();
227 if ( length() > 0 && at( length() - 1 )->c == ' ' ) {
228 remove( length() - 1, 1 );
229 --start;
230 }
231 append( s->str->toString(), true );
232
233 for ( int i = 0; i < s->length(); ++i ) {
234 if ( doc->useFormatCollection() ) {
235 s->str->at( i ).format()->addRef();
236 str->setFormat( i + start, s->str->at( i ).format(), true );
237 }
238 if ( s->str->at( i ).isCustom() ) {
239 KoTextCustomItem * item = s->str->at( i ).customItem();
240 str->at( i + start ).setCustomItem( item );
241 s->str->at( i ).loseCustomItem();
242 doc->unregisterCustomItem( item, s ); // ### missing in TQRT
243 doc->registerCustomItem( item, this );
244 }
245 }
246 Q_ASSERT(str->at(str->length()-1).c == ' ');
247
248 /*if ( !extraData() && s->extraData() ) {
249 setExtraData( s->extraData() );
250 s->setExtraData( 0 );
251 } else if ( extraData() && s->extraData() ) {
252 extraData()->join( s->extraData() );
253 }*/
254 delete s;
255 invalidate( 0 );
257 invalidateCounters();
259 r.setHeight( oh );
260 //needPreProcess = true;
261 if ( n ) {
262 KoTextParag *s = n;
263 while ( s ) {
264 s->id = s->p->id + 1;
265 //s->state = -1;
266 //s->needPreProcess = true;
267 s->changed = true;
268 s = s->n;
269 }
270 }
271 format();
272 //state = -1;
273}
274
275void KoTextParag::move( int &dy )
276{
277 //kdDebug(32500) << "KoTextParag::move paragId=" << paragId() << " dy=" << dy << endl;
278 if ( dy == 0 )
279 return;
280 changed = true;
281 r.moveBy( 0, dy );
282 if ( mFloatingItems ) {
283 for ( KoTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() ) {
284 i->finalize();
285 }
286 }
287 //if ( p )
288 // p->lastInFrame = true; // TQt does this, but the loop at the end of format() calls move a lot!
289
290 movedDown = false;
291
292 // do page breaks if required
293 if ( doc && doc->isPageBreakEnabled() ) {
294 int shift;
295 if ( ( shift = doc->formatter()->formatVertically( doc, this ) ) ) {
296 if ( p )
297 p->setChanged( true );
298 dy += shift;
299 }
300 }
301}
302
303void KoTextParag::format( int start, bool doMove )
304{
305 if ( !str || str->length() == 0 || !formatter() )
306 return;
307
308 if ( isValid() )
309 return;
310
311 //kdDebug(32500) << "KoTextParag::format " << this << " id:" << paragId() << endl;
312
313 r.moveTopLeft( TQPoint( documentX(), p ? p->r.y() + p->r.height() : documentY() ) );
314 //if ( p )
315 // p->lastInFrame = false;
316
317 movedDown = false;
318 bool formattedAgain = false;
319
320 formatAgain:
321 r.setWidth( documentWidth() );
322
323 // Not really useful....
324 if ( doc && mFloatingItems ) {
325 for ( KoTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() ) {
326 if ( i->placement() == KoTextCustomItem::PlaceRight )
327 i->move( r.x() + r.width() - i->width, r.y() );
328 else
329 i->move( i->x(), r.y() );
330 }
331 }
332 TQMap<int, KoTextParagLineStart*> oldLineStarts = lineStarts;
333 lineStarts.clear();
334 int y;
335 bool formatterWorked = formatter()->format( doc, this, start, oldLineStarts, y, m_wused );
336
337 // It can't happen that width < minimumWidth -- hopefully.
338 //r.setWidth( TQMAX( r.width(), formatter()->minimumWidth() ) );
339 //m_minw = formatter()->minimumWidth();
340
341 TQMap<int, KoTextParagLineStart*>::Iterator it = oldLineStarts.begin();
342
343 for ( ; it != oldLineStarts.end(); ++it )
344 delete *it;
345
346/* if ( hasBorder() || str->isRightToLeft() )
349 {
350 setWidth( textDocument()->width() - 1 );
351 }
352 else*/
353 {
354 if ( lineStarts.count() == 1 ) { //&& ( !doc || doc->flow()->isEmpty() ) ) {
355// kotext: for proper parag borders, we want all parags to be as wide as linestart->w
356/* if ( !str->isBidi() ) {
357 KoTextStringChar *c = &str->at( str->length() - 1 );
358 r.setWidth( c->x + c->width );
359 } else*/ {
360 r.setWidth( lineStarts[0]->w );
361 }
362 }
363 if ( newLinesAllowed ) {
364 it = lineStarts.begin();
365 int usedw = 0; int lineid = 0;
366 for ( ; it != lineStarts.end(); ++it, ++lineid ) {
367 usedw = TQMAX( usedw, (*it)->w );
368 }
369 if ( r.width() <= 0 ) {
370 // if the user specifies an invalid rect, this means that the
371 // bounding box should grow to the width that the text actually
372 // needs
373 r.setWidth( usedw );
374 } else {
375 r.setWidth( TQMIN( usedw, r.width() ) );
376 }
377 }
378 }
379
380 if ( y != r.height() )
381 r.setHeight( y );
382
383 if ( !visible )
384 r.setHeight( 0 );
385
386 // do page breaks if required
387 if ( doc && doc->isPageBreakEnabled() ) {
388 int shift = doc->formatter()->formatVertically( doc, this );
389 //kdDebug(32500) << "formatVertically returned shift=" << shift << endl;
390 if ( shift && !formattedAgain ) {
391 formattedAgain = true;
392 goto formatAgain;
393 }
394 }
395
396 if ( doc )
397 doc->formatter()->postFormat( this );
398
399 if ( n && doMove && n->isValid() && r.y() + r.height() != n->r.y() ) {
400 //kdDebug(32500) << "r=" << r << " n->r=" << n->r << endl;
401 int dy = ( r.y() + r.height() ) - n->r.y();
402 KoTextParag *s = n;
403 bool makeInvalid = false; //p && p->lastInFrame;
404 //kdDebug(32500) << "might move of dy=" << dy << ". previous's lastInFrame (=makeInvalid): " << makeInvalid << endl;
405 while ( s && dy ) {
406 if ( s->movedDown ) { // (not in TQRT) : moved down -> invalidate and stop moving down
407 s->invalidate( 0 ); // (there is no point in moving down a parag that has a frame break...)
408 break;
409 }
410 if ( !s->isFullWidth() )
411 makeInvalid = true;
412 if ( makeInvalid )
413 s->invalidate( 0 );
414 s->move( dy );
415 //if ( s->lastInFrame )
416 // makeInvalid = true;
417 s = s->n;
418 }
419 }
420
421//#define DEBUG_CI_PLACEMENT
422 if ( mFloatingItems ) {
423#ifdef DEBUG_CI_PLACEMENT
424 kdDebug(32500) << lineStarts.count() << " lines" << endl;
425#endif
426 // Place custom items - after the formatting is finished
427 int len = length();
428 int line = -1;
429 int lineY = 0; // the one called "cy" in other algos
430 int baseLine = 0;
431 TQMap<int, KoTextParagLineStart*>::Iterator it = lineStarts.begin();
432 for ( int i = 0 ; i < len; ++i ) {
433 KoTextStringChar *chr = &str->at( i );
434 if ( chr->lineStart ) {
435 ++line;
436 if ( line > 0 )
437 ++it;
438 lineY = (*it)->y;
439 baseLine = (*it)->baseLine;
440#ifdef DEBUG_CI_PLACEMENT
441 kdDebug(32500) << "New line (" << line << "): lineStart=" << (*it) << " lineY=" << lineY << " baseLine=" << baseLine << " height=" << (*it)->h << endl;
442#endif
443 }
444 if ( chr->isCustom() ) {
445 int x = chr->x;
446 KoTextCustomItem* item = chr->customItem();
447 Q_ASSERT( baseLine >= item->ascent() ); // something went wrong in KoTextFormatter if this isn't the case
448 int y = lineY + baseLine - item->ascent();
449#ifdef DEBUG_CI_PLACEMENT
450 kdDebug(32500) << "Custom item: i=" << i << " x=" << x << " lineY=" << lineY << " baseLine=" << baseLine << " ascent=" << item->ascent() << " -> y=" << y << endl;
451#endif
452 item->move( x, y );
453 item->finalize();
454 }
455 }
456 }
457
458 //firstFormat = false; //// unused
459 if ( formatterWorked ) // only if it worked, i.e. we had some width to format it
460 {
461 m_invalid = false;
462 }
463 changed = true;
464 //#### str->setTextChanged( false );
465}
466
467int KoTextParag::lineHeightOfChar( int i, int *bl, int *y ) const
468{
469 if ( !isValid() )
470 ( (KoTextParag*)this )->format();
471
472 TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.end();
473 --it;
474 for ( ;; ) {
475 if ( i >= it.key() ) {
476 if ( bl )
477 *bl = ( *it )->baseLine;
478 if ( y )
479 *y = ( *it )->y;
480 return ( *it )->h;
481 }
482 if ( it == lineStarts.begin() )
483 break;
484 --it;
485 }
486
487 kdWarning(32500) << "KoTextParag::lineHeightOfChar: couldn't find lh for " << i << endl;
488 return 15;
489}
490
491KoTextStringChar *KoTextParag::lineStartOfChar( int i, int *index, int *line ) const
492{
493 if ( !isValid() )
494 ( (KoTextParag*)this )->format();
495
496 int l = (int)lineStarts.count() - 1;
497 TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.end();
498 --it;
499 for ( ;; ) {
500 if ( i >= it.key() ) {
501 if ( index )
502 *index = it.key();
503 if ( line )
504 *line = l;
505 return &str->at( it.key() );
506 }
507 if ( it == lineStarts.begin() )
508 break;
509 --it;
510 --l;
511 }
512
513 kdWarning(32500) << "KoTextParag::lineStartOfChar: couldn't find " << i << endl;
514 return 0;
515}
516
517int KoTextParag::lines() const
518{
519 if ( !isValid() )
520 ( (KoTextParag*)this )->format();
521
522 return (int)lineStarts.count();
523}
524
525KoTextStringChar *KoTextParag::lineStartOfLine( int line, int *index ) const
526{
527 if ( !isValid() )
528 ( (KoTextParag*)this )->format();
529
530 if ( line >= 0 && line < (int)lineStarts.count() ) {
531 TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
532 while ( line-- > 0 )
533 ++it;
534 int i = it.key();
535 if ( index )
536 *index = i;
537 return &str->at( i );
538 }
539
540 kdWarning(32500) << "KoTextParag::lineStartOfLine: couldn't find " << line << endl;
541 return 0;
542}
543
544int KoTextParag::leftGap() const
545{
546 if ( !isValid() )
547 ( (KoTextParag*)this )->format();
548
549 int line = 0;
550 int x = str->at(0).x; /* set x to x of first char */
551 if ( str->isBidi() ) {
552 for ( int i = 1; i < str->length(); ++i )
553 x = TQMIN(x, str->at(i).x);
554 return x;
555 }
556
557 TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
558 while (line < (int)lineStarts.count()) {
559 int i = it.key(); /* char index */
560 x = TQMIN(x, str->at(i).x);
561 ++it;
562 ++line;
563 }
564 return x;
565}
566
567void KoTextParag::setFormat( int index, int len, const KoTextFormat *_f, bool useCollection, int flags )
568{
569 Q_ASSERT( useCollection ); // just for info
570 if ( index < 0 )
571 index = 0;
572 if ( index > str->length() - 1 )
573 index = str->length() - 1;
574 if ( index + len >= str->length() )
575 len = str->length() - index;
576
577 KoTextFormatCollection *fc = 0;
578 if ( useCollection )
579 fc = formatCollection();
580 KoTextFormat *of;
581 for ( int i = 0; i < len; ++i ) {
582 of = str->at( i + index ).format();
583 if ( !changed && _f->key() != of->key() )
584 changed = true;
585 // Check things that need the textformatter to run
586 // (e.g. not color changes)
587 // ######## Is this test exhaustive?
588 if ( m_invalid == false &&
589 ( _f->font().family() != of->font().family() ||
590 _f->pointSize() != of->pointSize() ||
591 _f->font().weight() != of->font().weight() ||
592 _f->font().italic() != of->font().italic() ||
593 _f->vAlign() != of->vAlign() ||
594 _f->relativeTextSize() != of->relativeTextSize() ||
595 _f->offsetFromBaseLine() != of->offsetFromBaseLine() ||
596 _f->wordByWord() != of->wordByWord() ||
597 _f->attributeFont() != of->attributeFont() ||
598 _f->language() != of->language() ||
599 _f->hyphenation() != of->hyphenation() ||
600 _f->shadowDistanceX() != of->shadowDistanceX() ||
601 _f->shadowDistanceY() != of->shadowDistanceY()
602 ) ) {
603 invalidate( 0 );
604 }
605 if ( flags == -1 || flags == KoTextFormat::Format || !fc ) {
606#ifdef DEBUG_COLLECTION
607 kdDebug(32500) << " KoTextParag::setFormat, will use format(f) " << f << " " << _f->key() << endl;
608#endif
609 KoTextFormat* f = fc ? fc->format( _f ) : const_cast<KoTextFormat *>( _f );
610 str->setFormat( i + index, f, useCollection, true );
611 } else {
612#ifdef DEBUG_COLLECTION
613 kdDebug(32500) << " KoTextParag::setFormat, will use format(of,f,flags) of=" << of << " " << of->key() << ", f=" << _f << " " << _f->key() << endl;
614#endif
615 KoTextFormat *fm = fc->format( of, _f, flags );
616#ifdef DEBUG_COLLECTION
617 kdDebug(32500) << " KoTextParag::setFormat, format(of,f,flags) returned " << fm << " " << fm->key() << " " << endl;
618#endif
619 str->setFormat( i + index, fm, useCollection );
620 }
621 }
622}
623
624void KoTextParag::drawCursorDefault( TQPainter &painter, KoTextCursor *cursor, int curx, int cury, int curh, const TQColorGroup &cg )
625{
626 painter.fillRect( TQRect( curx, cury, 1, curh ), cg.color( TQColorGroup::Text ) );
627 painter.save();
628 if ( str->isBidi() ) {
629 const int d = 4;
630 if ( at( cursor->index() )->rightToLeft ) {
631 painter.setPen( TQt::black );
632 painter.drawLine( curx, cury, curx - d / 2, cury + d / 2 );
633 painter.drawLine( curx, cury + d, curx - d / 2, cury + d / 2 );
634 } else {
635 painter.setPen( TQt::black );
636 painter.drawLine( curx, cury, curx + d / 2, cury + d / 2 );
637 painter.drawLine( curx, cury + d, curx + d / 2, cury + d / 2 );
638 }
639 }
640 painter.restore();
641}
642
643int *KoTextParag::tabArray() const
644{
645 int *ta = tArray;
646 if ( !ta && doc )
647 ta = doc->tabArray();
648 return ta;
649}
650
651int KoTextParag::nextTabDefault( int, int x )
652{
653 int *ta = tArray;
654 //if ( doc ) {
655 if ( !ta )
656 ta = doc->tabArray();
657 int tabStopWidth = doc->tabStopWidth();
658 //}
659 if ( tabStopWidth != 0 )
660 return tabStopWidth*(x/tabStopWidth+1);
661 else
662 return x;
663}
664
665KoTextFormatCollection *KoTextParag::formatCollection() const
666{
667 if ( doc )
668 return doc->formatCollection();
669 //if ( !qFormatCollection )
670 // qFormatCollection = new KoTextFormatCollection;
671 //return qFormatCollection;
672 return 0L;
673}
674
675void KoTextParag::show()
676{
677 if ( visible || !doc )
678 return;
679 visible = true;
680}
681
682void KoTextParag::hide()
683{
684 if ( !visible || !doc )
685 return;
686 visible = false;
687}
688
689void KoTextParag::setDirection( TQChar::Direction d )
690{
691 if ( str && str->direction() != d ) {
692 str->setDirection( d );
693 invalidate( 0 );
695 m_layout.direction = d;
696 invalidateCounters(); // #47178
698 }
699}
700
701TQChar::Direction KoTextParag::direction() const
702{
703 return (str ? str->direction() : TQChar::DirON );
704}
705
706void KoTextParag::setSelection( int id, int start, int end )
707{
708 TQMap<int, KoTextParagSelection>::ConstIterator it = selections().find( id );
709 if ( it != mSelections->end() ) {
710 if ( start == ( *it ).start && end == ( *it ).end )
711 return;
712 }
713
714 KoTextParagSelection sel;
715 sel.start = start;
716 sel.end = end;
717 (*mSelections)[ id ] = sel;
718 setChanged( true, true );
719}
720
721void KoTextParag::removeSelection( int id )
722{
723 if ( !hasSelection( id ) )
724 return;
725 if ( mSelections )
726 mSelections->remove( id );
727 setChanged( true, true );
728}
729
730int KoTextParag::selectionStart( int id ) const
731{
732 if ( !mSelections )
733 return -1;
734 TQMap<int, KoTextParagSelection>::ConstIterator it = mSelections->find( id );
735 if ( it == mSelections->end() )
736 return -1;
737 return ( *it ).start;
738}
739
740int KoTextParag::selectionEnd( int id ) const
741{
742 if ( !mSelections )
743 return -1;
744 TQMap<int, KoTextParagSelection>::ConstIterator it = mSelections->find( id );
745 if ( it == mSelections->end() )
746 return -1;
747 return ( *it ).end;
748}
749
750bool KoTextParag::hasSelection( int id ) const
751{
752 if ( !mSelections )
753 return false;
754 TQMap<int, KoTextParagSelection>::ConstIterator it = mSelections->find( id );
755 if ( it == mSelections->end() )
756 return false;
757 return ( *it ).start != ( *it ).end || length() == 1;
758}
759
760bool KoTextParag::fullSelected( int id ) const
761{
762 if ( !mSelections )
763 return false;
764 TQMap<int, KoTextParagSelection>::ConstIterator it = mSelections->find( id );
765 if ( it == mSelections->end() )
766 return false;
767 return ( *it ).start == 0 && ( *it ).end == str->length() - 1;
768}
769
770int KoTextParag::lineY( int l ) const
771{
772 if ( l > (int)lineStarts.count() - 1 ) {
773 kdWarning(32500) << "KoTextParag::lineY: line " << l << " out of range!" << endl;
774 return 0;
775 }
776
777 if ( !isValid() )
778 ( (KoTextParag*)this )->format();
779
780 TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
781 while ( l-- > 0 )
782 ++it;
783 return ( *it )->y;
784}
785
786int KoTextParag::lineBaseLine( int l ) const
787{
788 if ( l > (int)lineStarts.count() - 1 ) {
789 kdWarning(32500) << "KoTextParag::lineBaseLine: line " << l << " out of range!" << endl;
790 return 10;
791 }
792
793 if ( !isValid() )
794 ( (KoTextParag*)this )->format();
795
796 TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
797 while ( l-- > 0 )
798 ++it;
799 return ( *it )->baseLine;
800}
801
802int KoTextParag::lineHeight( int l ) const
803{
804 if ( l > (int)lineStarts.count() - 1 ) {
805 kdWarning(32500) << "KoTextParag::lineHeight: line " << l << " out of range!" << endl;
806 return 15;
807 }
808
809 if ( !isValid() )
810 ( (KoTextParag*)this )->format();
811
812 TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
813 while ( l-- > 0 )
814 ++it;
815 return ( *it )->h;
816}
817
818void KoTextParag::lineInfo( int l, int &y, int &h, int &bl ) const
819{
820 if ( l > (int)lineStarts.count() - 1 ) {
821 kdWarning(32500) << "KoTextParag::lineInfo: line " << l << " out of range!" << endl;
822 kdDebug(32500) << (int)lineStarts.count() - 1 << " " << l << endl;
823 y = 0;
824 h = 15;
825 bl = 10;
826 return;
827 }
828
829 if ( !isValid() )
830 ( (KoTextParag*)this )->format();
831
832 TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
833 while ( l-- > 0 )
834 ++it;
835 y = ( *it )->y;
836 h = ( *it )->h;
837 bl = ( *it )->baseLine;
838}
839
840uint KoTextParag::alignment() const
841{
842 return align;
843}
844
845void KoTextParag::setFormat( KoTextFormat *fm )
846{
847#if 0
848 bool doUpdate = false;
849 if (defFormat && (defFormat != formatCollection()->defaultFormat()))
850 doUpdate = true;
851#endif
852 defFormat = formatCollection()->format( fm );
853#if 0
854 if ( !doUpdate )
855 return;
856 for ( int i = 0; i < length(); ++i ) {
857 if ( at( i )->format()->styleName() == defFormat->styleName() )
858 at( i )->format()->updateStyle();
859 }
860#endif
861}
862
863KoTextFormatterBase *KoTextParag::formatter() const
864{
865 if ( doc )
866 return doc->formatter();
867 return 0;
868}
869
870/*int KoTextParag::minimumWidth() const
871{
872 //return doc ? doc->minimumWidth() : 0;
873 return m_minw;
874}*/
875
876int KoTextParag::widthUsed() const
877{
878 return m_wused;
879}
880
881void KoTextParag::setTabArray( int *a )
882{
883 delete [] tArray;
884 tArray = a;
885}
886
887void KoTextParag::setTabStops( int tw )
888{
889 if ( doc )
890 doc->setTabStops( tw );
891 //else
892 // tabStopWidth = tw;
893}
894
895TQMap<int, KoTextParagSelection> &KoTextParag::selections() const
896{
897 if ( !mSelections )
898 ((KoTextParag *)this)->mSelections = new TQMap<int, KoTextParagSelection>;
899 return *mSelections;
900}
901
902TQPtrList<KoTextCustomItem> &KoTextParag::floatingItems() const
903{
904 if ( !mFloatingItems )
905 ((KoTextParag *)this)->mFloatingItems = new TQPtrList<KoTextCustomItem>;
906 return *mFloatingItems;
907}
908
909void KoTextCursor::setIndex( int i, bool /*restore*/ )
910{
911// Note: TQRT doesn't allow to position the cursor at string->length
912// However we need it, when applying a style to a paragraph, so that
913// the trailing space gets the style change applied as well.
914// Obviously "right of the trailing space" isn't a good place for a real
915// cursor, but this needs to be checked somewhere else.
916 if ( i < 0 || i > string->length() ) {
917#if defined(TQT_CHECK_RANGE)
918 kdWarning(32500) << "KoTextCursor::setIndex: " << i << " out of range" << endl;
919 //abort();
920#endif
921 i = i < 0 ? 0 : string->length() - 1;
922 }
923
924 tmpIndex = -1;
925 idx = i;
926}
927
929
930// Return the counter associated with this paragraph.
931KoParagCounter *KoTextParag::counter()
932{
933 if ( !m_layout.counter )
934 return 0L;
935
936 // Garbage collect un-needed counters.
937 if ( m_layout.counter->numbering() == KoParagCounter::NUM_NONE
938 // [keep it for unnumbered outlines (the depth is useful)]
939 && ( !m_layout.style || !m_layout.style->isOutline() ) )
940 setNoCounter();
941 return m_layout.counter;
942}
943
944void KoTextParag::setMargin( TQStyleSheetItem::Margin m, double _i )
945{
946 //kdDebug(32500) << "KoTextParag::setMargin " << m << " margin " << _i << endl;
947 m_layout.margins[m] = _i;
948 if ( m == TQStyleSheetItem::MarginTop && prev() )
949 prev()->invalidate(0); // for top margin (post-1.1: remove this, not necessary anymore)
950 invalidate(0);
951}
952
953void KoTextParag::setMargins( const double * margins )
954{
955 for ( int i = 0 ; i < 5 ; ++i )
956 m_layout.margins[i] = margins[i];
957 invalidate(0);
958}
959
960void KoTextParag::setAlign( int align )
961{
962 Q_ASSERT( align <= TQt::AlignJustify );
963 align &= TQt::AlignHorizontal_Mask;
964 setAlignment( align );
965 m_layout.alignment = align;
966}
967
968int KoTextParag::resolveAlignment() const
969{
970 if ( (int)m_layout.alignment == TQt::AlignAuto )
971 return str->isRightToLeft() ? TQt::AlignRight : TQt::AlignLeft;
972 return m_layout.alignment;
973}
974
975void KoTextParag::setLineSpacing( double _i )
976{
977 m_layout.setLineSpacingValue(_i);
978 invalidate(0);
979}
980
981void KoTextParag::setLineSpacingType( KoParagLayout::SpacingType _type )
982{
983 m_layout.lineSpacingType = _type;
984 invalidate(0);
985}
986
987void KoTextParag::setTopBorder( const KoBorder & _brd )
988{
989 m_layout.topBorder = _brd;
990 invalidate(0);
991}
992
993void KoTextParag::setBottomBorder( const KoBorder & _brd )
994{
995 m_layout.bottomBorder = _brd;
996 invalidate(0);
997}
998
999void KoTextParag::setJoinBorder( bool join )
1000{
1001 m_layout.joinBorder = join;
1002 invalidate(0);
1003}
1004
1005void KoTextParag::setBackgroundColor ( const TQColor& color )
1006{
1007 m_layout.backgroundColor = color;
1008 invalidate(0);
1009}
1010
1011void KoTextParag::setNoCounter()
1012{
1013 delete m_layout.counter;
1014 m_layout.counter = 0L;
1015 invalidateCounters();
1016}
1017
1018void KoTextParag::setCounter( const KoParagCounter * pCounter )
1019{
1020 // Preserve footnote numbering when applying a style
1021 const bool isFootNote = m_layout.counter &&
1022 m_layout.counter->numbering() == KoParagCounter::NUM_FOOTNOTE;
1023 if ( isFootNote ) {
1024 const TQString footNotePrefix = m_layout.counter->prefix(); // this is where the number is
1025 delete m_layout.counter;
1026 m_layout.counter = pCounter ? new KoParagCounter( *pCounter ) : new KoParagCounter();
1027 m_layout.counter->setNumbering( KoParagCounter::NUM_FOOTNOTE );
1028 m_layout.counter->setStyle( KoParagCounter::STYLE_NONE ); // no number after the 'prefix'
1029 m_layout.counter->setPrefix( footNotePrefix );
1030 m_layout.counter->setSuffix( TQString() );
1031 invalidateCounters();
1032 } else {
1033 if ( pCounter )
1034 setCounter( *pCounter );
1035 else
1036 setNoCounter();
1037 }
1038}
1039
1040void KoTextParag::setCounter( const KoParagCounter & counter )
1041{
1042 // Garbage collect unnneeded counters.
1043 if ( counter.numbering() == KoParagCounter::NUM_NONE
1044 // [keep it for unnumbered outlines (the depth is useful)]
1045 && ( !m_layout.style || !m_layout.style->isOutline() ) )
1046 {
1047 setNoCounter();
1048 }
1049 else
1050 {
1051 delete m_layout.counter;
1052 m_layout.counter = new KoParagCounter( counter );
1053
1054 // Invalidate the counters
1055 invalidateCounters();
1056 }
1057}
1058
1059void KoTextParag::invalidateCounters()
1060{
1061 // Invalidate this paragraph and all the following ones
1062 // (Numbering may have changed)
1063 invalidate( 0 );
1064 if ( m_layout.counter )
1065 m_layout.counter->invalidate();
1066 KoTextParag *s = next();
1067 // #### Possible optimization: since any invalidation propagates down,
1068 // it's enough to stop at the first paragraph with an already-invalidated counter, isn't it?
1069 // This is only true if nobody else calls counter->invalidate...
1070 while ( s ) {
1071 if ( s->m_layout.counter )
1072 s->m_layout.counter->invalidate();
1073 s->invalidate( 0 );
1074 s = s->next();
1075 }
1076}
1077
1078int KoTextParag::counterWidth() const
1079{
1080 if ( !m_layout.counter )
1081 return 0;
1082
1083 return m_layout.counter->width( this );
1084}
1085
1086// Draw the complete label (i.e. heading/list numbers/bullets) for this paragraph.
1087// This is called by KoTextParag::paint.
1088void KoTextParag::drawLabel( TQPainter* p, int xLU, int yLU, int /*wLU*/, int /*hLU*/, int baseLU, const TQColorGroup& /*cg*/ )
1089{
1090 if ( !m_layout.counter ) // shouldn't happen
1091 return;
1092
1093 if ( m_layout.counter->numbering() == KoParagCounter::NUM_NONE )
1094 return;
1095
1096 int counterWidthLU = m_layout.counter->width( this );
1097
1098 // We use the formatting of the first char as the formatting of the counter
1099 KoTextFormat counterFormat( *KoParagCounter::counterFormat( this ) );
1100 if ( !m_layout.style || !m_layout.style->isOutline() )
1101 {
1102 // But without bold/italic for normal lists, since some items could be bold and others not.
1103 // For headings we must keep the bold when the heading is bold.
1104 counterFormat.setBold( false );
1105 counterFormat.setItalic( false );
1106 }
1107 KoTextFormat* format = &counterFormat;
1108 p->save();
1109
1110 TQColor textColor( format->color() );
1111 if ( !textColor.isValid() ) // Resolve the color at this point
1112 textColor = KoTextFormat::defaultTextColor( p );
1113 p->setPen( TQPen( textColor ) );
1114
1115 KoTextZoomHandler * zh = textDocument()->paintingZoomHandler();
1116 assert( zh );
1117 //bool forPrint = ( p->device()->devType() == TQInternal::Printer );
1118
1119 bool rtl = str->isRightToLeft(); // when true, we put suffix+counter+prefix at the RIGHT of the paragraph.
1120 int xLeft = zh->layoutUnitToPixelX( xLU - (rtl ? 0 : counterWidthLU) );
1121 int y = zh->layoutUnitToPixelY( yLU );
1122 //int h = zh->layoutUnitToPixelY( yLU, hLU );
1123 int base = zh->layoutUnitToPixelY( yLU, baseLU );
1124 int counterWidth = zh->layoutUnitToPixelX( xLU, counterWidthLU );
1125 int height = zh->layoutUnitToPixelY( yLU, format->height() );
1126
1127 TQFont font( format->screenFont( zh ) );
1128 // Footnote numbers are in superscript (in WP and Word, not in OO)
1129 if ( m_layout.counter->numbering() == KoParagCounter::NUM_FOOTNOTE )
1130 {
1131 int pointSize = ( ( font.pointSize() * 2 ) / 3 );
1132 font.setPointSize( pointSize );
1133 y -= ( height - TQFontMetrics(font).height() );
1134 }
1135 p->setFont( font );
1136
1137 // Now draw any bullet that is required over the space left for it.
1138 if ( m_layout.counter->isBullet() )
1139 {
1140 int xBullet = xLeft + zh->layoutUnitToPixelX( m_layout.counter->bulletX() );
1141
1142 //kdDebug(32500) << "KoTextParag::drawLabel xLU=" << xLU << " counterWidthLU=" << counterWidthLU << endl;
1143 // The width and height of the bullet is the width of one space
1144 int width = zh->layoutUnitToPixelX( xLeft, format->width( ' ' ) );
1145
1146 //kdDebug(32500) << "Pix: xLeft=" << xLeft << " counterWidth=" << counterWidth
1147 // << " xBullet=" << xBullet << " width=" << width << endl;
1148
1149 TQString prefix = m_layout.counter->prefix();
1150 if ( !prefix.isEmpty() )
1151 {
1152 if ( rtl )
1153 prefix.prepend( ' ' /*the space before the bullet in RTL mode*/ );
1154 KoTextParag::drawFontEffects( p, format, zh, format->screenFont( zh ), textColor, xLeft, base, width, y, height, prefix[0] );
1155
1156 int posY =y + base - format->offsetFromBaseLine();
1157 //we must move to bottom text because we create
1158 //shadow to 'top'.
1159 int sy = format->shadowY( zh );
1160 if ( sy < 0)
1161 posY -= sy;
1162
1163 p->drawText( xLeft, posY, prefix );
1164 }
1165
1166 TQRect er( xBullet + (rtl ? width : 0), y + height / 2 - width / 2, width, width );
1167 // Draw the bullet.
1168 int posY = 0;
1169 switch ( m_layout.counter->style() )
1170 {
1171 case KoParagCounter::STYLE_DISCBULLET:
1172 p->setBrush( TQBrush(textColor) );
1173 p->drawEllipse( er );
1174 p->setBrush( TQt::NoBrush );
1175 break;
1176 case KoParagCounter::STYLE_SQUAREBULLET:
1177 p->fillRect( er, TQBrush(textColor) );
1178 break;
1179 case KoParagCounter::STYLE_BOXBULLET:
1180 p->drawRect( er );
1181 break;
1182 case KoParagCounter::STYLE_CIRCLEBULLET:
1183 p->drawEllipse( er );
1184 break;
1185 case KoParagCounter::STYLE_CUSTOMBULLET:
1186 {
1187 // The user has selected a symbol from a special font. Override the paragraph
1188 // font with the given family. This conserves the right size etc.
1189 if ( !m_layout.counter->customBulletFont().isEmpty() )
1190 {
1191 TQFont bulletFont( p->font() );
1192 bulletFont.setFamily( m_layout.counter->customBulletFont() );
1193 p->setFont( bulletFont );
1194 }
1195 KoTextParag::drawFontEffects( p, format, zh, format->screenFont( zh ), textColor, xBullet, base, width, y, height, ' ' );
1196
1197 posY = y + base- format->offsetFromBaseLine();
1198 //we must move to bottom text because we create
1199 //shadow to 'top'.
1200 int sy = format->shadowY( zh );
1201 if ( sy < 0)
1202 posY -= sy;
1203
1204 p->drawText( xBullet, posY, TQString(m_layout.counter->customBulletCharacter()) );
1205 break;
1206 }
1207 default:
1208 break;
1209 }
1210
1211 TQString suffix = m_layout.counter->suffix();
1212 if ( !suffix.isEmpty() )
1213 {
1214 if ( !rtl )
1215 suffix += ' ' /*the space after the bullet*/;
1216
1217 KoTextParag::drawFontEffects( p, format, zh, format->screenFont( zh ), textColor, xBullet + width, base, counterWidth, y,height, suffix[0] );
1218
1219 int posY =y + base- format->offsetFromBaseLine();
1220 //we must move to bottom text because we create
1221 //shadow to 'top'.
1222 int sy = format->shadowY( zh );
1223 if ( sy < 0)
1224 posY -= sy;
1225
1226 p->drawText( xBullet + width, posY, suffix, -1 );
1227 }
1228 }
1229 else
1230 {
1231 TQString counterText = m_layout.counter->text( this );
1232 // There are no bullets...any parent bullets have already been suppressed.
1233 // Just draw the text! Note: one space is always appended.
1234 if ( !counterText.isEmpty() )
1235 {
1236 KoTextParag::drawFontEffects( p, format, zh, format->screenFont( zh ), textColor, xLeft, base, counterWidth, y, height, counterText[0] );
1237
1238 counterText += ' ' /*the space after the bullet (before in RTL mode)*/;
1239
1240 int posY =y + base - format->offsetFromBaseLine();
1241 //we must move to bottom text because we create
1242 //shadow to 'top'.
1243 int sy = format->shadowY( zh );
1244 if ( sy < 0)
1245 posY -= sy;
1246
1247 p->drawText( xLeft, posY , counterText, -1 );
1248 }
1249 }
1250 p->restore();
1251}
1252
1253int KoTextParag::breakableTopMargin() const
1254{
1255 KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
1256 return zh->ptToLayoutUnitPixY(
1257 m_layout.margins[ TQStyleSheetItem::MarginTop ] );
1258}
1259
1260int KoTextParag::topMargin() const
1261{
1262 KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
1263 return zh->ptToLayoutUnitPixY(
1264 m_layout.margins[ TQStyleSheetItem::MarginTop ]
1265 + ( ( prev() && prev()->joinBorder() && prev()->bottomBorder() == m_layout.bottomBorder &&
1266 prev()->topBorder() == m_layout.topBorder && prev()->leftBorder() == m_layout.leftBorder &&
1267 prev()->rightBorder() == m_layout.rightBorder) ? 0 : m_layout.topBorder.width() ) );
1268}
1269
1270int KoTextParag::bottomMargin() const
1271{
1272 KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
1273 return zh->ptToLayoutUnitPixY(
1274 m_layout.margins[ TQStyleSheetItem::MarginBottom ]
1275 + ( ( joinBorder() && next() && next()->bottomBorder() == m_layout.bottomBorder &&
1276 next()->topBorder() == m_layout.topBorder && next()->leftBorder() == m_layout.leftBorder &&
1277 next()->rightBorder() == m_layout.rightBorder) ? 0 : m_layout.bottomBorder.width() ) );
1278}
1279
1280int KoTextParag::leftMargin() const
1281{
1282 KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
1283 return zh->ptToLayoutUnitPixX(
1284 m_layout.margins[ TQStyleSheetItem::MarginLeft ]
1285 + m_layout.leftBorder.width() );
1286}
1287
1288int KoTextParag::rightMargin() const
1289{
1290 KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
1291 int cw=0;
1292 if( m_layout.counter && str->isRightToLeft() &&
1293 (( m_layout.counter->alignment() == TQt::AlignRight ) || ( m_layout.counter->alignment() == TQt::AlignAuto )))
1294 cw = counterWidth();
1295
1296 return zh->ptToLayoutUnitPixX(
1297 m_layout.margins[ TQStyleSheetItem::MarginRight ]
1298 + m_layout.rightBorder.width() )
1299 + cw; /* in layout units already */
1300}
1301
1302int KoTextParag::firstLineMargin() const
1303{
1304 KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
1305 return zh->ptToLayoutUnitPixY(
1306 m_layout.margins[ TQStyleSheetItem::MarginFirstLine ] );
1307}
1308
1309int KoTextParag::lineSpacing( int line ) const
1310{
1311 Q_ASSERT( isValid() );
1312 if ( m_layout.lineSpacingType == KoParagLayout::LS_SINGLE )
1313 return 0; // or shadow, see calculateLineSpacing
1314 else {
1315 if( line >= (int)lineStarts.count() )
1316 {
1317 kdError() << "KoTextParag::lineSpacing assert(line<lines) failed: line=" << line << " lines=" << lineStarts.count() << endl;
1318 return 0;
1319 }
1320 TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
1321 while ( line-- > 0 )
1322 ++it;
1323 return (*it)->lineSpacing;
1324 }
1325}
1326
1327// Called by KoTextFormatter
1328int KoTextParag::calculateLineSpacing( int line, int startChar, int lastChar ) const
1329{
1330 KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
1331 // TODO add shadow in KoTextFormatter!
1332 int shadow = 0; //TQABS( zh->ptToLayoutUnitPixY( shadowDistanceY() ) );
1333 if ( m_layout.lineSpacingType == KoParagLayout::LS_SINGLE )
1334 return shadow;
1335 else if ( m_layout.lineSpacingType == KoParagLayout::LS_CUSTOM )
1336 return zh->ptToLayoutUnitPixY( m_layout.lineSpacingValue() ) + shadow;
1337 else {
1338 if( line >= (int)lineStarts.count() )
1339 {
1340 kdError() << "KoTextParag::lineSpacing assert(line<lines) failed: line=" << line << " lines=" << lineStarts.count() << endl;
1341 return 0+shadow;
1342 }
1343 TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
1344 while ( line-- > 0 )
1345 ++it;
1346
1347 //kdDebug(32500) << " line spacing type: " << m_layout.lineSpacingType << " value:" << m_layout.lineSpacingValue() << " line_height=" << (*it)->h << " startChar=" << startChar << " lastChar=" << lastChar << endl;
1348 switch ( m_layout.lineSpacingType )
1349 {
1350 case KoParagLayout::LS_MULTIPLE:
1351 {
1352 double n = m_layout.lineSpacingValue() - 1.0; // yes, can be negative
1353 return shadow + tqRound( n * heightForLineSpacing( startChar, lastChar ) );
1354 }
1355 case KoParagLayout::LS_ONEANDHALF:
1356 {
1357 // Special case of LS_MULTIPLE, with n=1.5
1358 return shadow + heightForLineSpacing( startChar, lastChar ) / 2;
1359 }
1360 case KoParagLayout::LS_DOUBLE:
1361 {
1362 // Special case of LS_MULTIPLE, with n=1
1363 return shadow + heightForLineSpacing( startChar, lastChar );
1364 }
1365 case KoParagLayout::LS_AT_LEAST:
1366 {
1367 int atLeast = zh->ptToLayoutUnitPixY( m_layout.lineSpacingValue() );
1368 const int lineHeight = ( *it )->h;
1369 int h = TQMAX( lineHeight, atLeast );
1370 // height is now the required total height
1371 return shadow + h - lineHeight;
1372 }
1373 case KoParagLayout::LS_FIXED:
1374 {
1375 const int lineHeight = ( *it )->h;
1376 return shadow + zh->ptToLayoutUnitPixY( m_layout.lineSpacingValue() ) - lineHeight;
1377 }
1378 // Silence compiler warnings
1379 case KoParagLayout::LS_SINGLE:
1380 case KoParagLayout::LS_CUSTOM:
1381 break;
1382 }
1383 }
1384 kdWarning() << "Unhandled linespacing type : " << m_layout.lineSpacingType << endl;
1385 return 0+shadow;
1386}
1387
1388TQRect KoTextParag::pixelRect( KoTextZoomHandler *zh ) const
1389{
1390 TQRect rct( zh->layoutUnitToPixel( rect() ) );
1391 //kdDebug(32500) << " pixelRect for parag " << paragId()
1392 // << ": rect=" << rect() << " pixelRect=" << rct << endl;
1393
1394 // After division we almost always end up with the top overwriting the bottom of the parag above
1395 if ( prev() )
1396 {
1397 TQRect prevRect( zh->layoutUnitToPixel( prev()->rect() ) );
1398 if ( rct.top() < prevRect.bottom() + 1 )
1399 {
1400 //kdDebug(32500) << " pixelRect: rct.top() adjusted to " << prevRect.bottom() + 1 << " (was " << rct.top() << ")" << endl;
1401 rct.setTop( prevRect.bottom() + 1 );
1402 }
1403 }
1404 return rct;
1405}
1406
1407// Paint this paragraph. This is called by KoTextDocument::drawParagWYSIWYG
1408// (KoTextDocument::drawWithoutDoubleBuffer when printing)
1409void KoTextParag::paint( TQPainter &painter, const TQColorGroup &cg, KoTextCursor *cursor, bool drawSelections,
1410 int clipx, int clipy, int clipw, int cliph )
1411{
1412#ifdef DEBUG_PAINT
1413 kdDebug(32500) << "KoTextParag::paint ===== id=" << paragId() << " clipx=" << clipx << " clipy=" << clipy << " clipw=" << clipw << " cliph=" << cliph << endl;
1414 kdDebug(32500) << " clipw in pix (approx) : " << textDocument()->paintingZoomHandler()->layoutUnitToPixelX( clipw ) << " cliph in pix (approx) : " << textDocument()->paintingZoomHandler()->layoutUnitToPixelX( cliph ) << endl;
1415#endif
1416
1417 KoTextZoomHandler * zh = textDocument()->paintingZoomHandler();
1418 assert(zh);
1419
1420 TQRect paraRect = pixelRect( zh );
1421
1422 // Find left margin size, first line offset and right margin in pixels
1423 int leftMarginPix = zh->layoutUnitToPixelX( leftMargin() );
1424 int firstLineOffset = zh->layoutUnitToPixelX( firstLineMargin() );
1425
1426 // The furthest left and right x-coords of the paragraph,
1427 // including the bullet/counter, but not the borders.
1428 int leftExtent = TQMIN ( leftMarginPix, leftMarginPix + firstLineOffset );
1429 int rightExtent = paraRect.width() - zh->layoutUnitToPixelX( rightMargin() );
1430
1431 // Draw the paragraph background color
1432 if ( backgroundColor().isValid() )
1433 {
1434 // Render background from either left margin indent, or first line indent,
1435 // whichever is nearer the left.
1436 int backgroundWidth = rightExtent - leftExtent + 1;
1437 int backgroundHeight = pixelRect( zh ).height();
1438 painter.fillRect( leftExtent, 0,
1439 backgroundWidth, backgroundHeight,
1440 backgroundColor() );
1441 }
1442
1443 // Let's call drawLabel ourselves, rather than having to deal with TQStyleSheetItem to get paintLines to call it!
1444 if ( m_layout.counter && m_layout.counter->numbering() != KoParagCounter::NUM_NONE && m_lineChanged <= 0 )
1445 {
1446 int cy, h, baseLine;
1447 lineInfo( 0, cy, h, baseLine );
1448 int xLabel = at(0)->x;
1449 if ( str->isRightToLeft() )
1450 xLabel += at(0)->width;
1451 drawLabel( &painter, xLabel, cy, 0, 0, baseLine, cg );
1452 }
1453
1454 paintLines( painter, cg, cursor, drawSelections, clipx, clipy, clipw, cliph );
1455
1456 // Now draw paragraph border
1457 if ( m_layout.hasBorder() )
1458 {
1459 bool const drawTopBorder = !prev() || !prev()->joinBorder() || prev()->bottomBorder() != bottomBorder() || prev()->topBorder() != topBorder() || prev()->leftBorder() != leftBorder() || prev()->rightBorder() != rightBorder();
1460 bool const drawBottomBorder = !joinBorder() || !next() || next()->bottomBorder() != bottomBorder() || next()->topBorder() != topBorder() || next()->leftBorder() != leftBorder() || next()->rightBorder() != rightBorder();
1461
1462 // Paragraph borders surround the paragraph and its
1463 // counters/bullets, but they only touch the frame border if
1464 // the paragraph margins are of non-zero length.
1465 // This is what OpenOffice does (no really, it is this time).
1466 //
1467 // drawBorders paints outside the give rect, so for the
1468 // x-coords, it just needs to know the left and right extent
1469 // of the paragraph.
1470 TQRect r;
1471 r.setLeft( leftExtent );
1472 r.setRight( rightExtent );
1473 r.setTop( zh->layoutUnitToPixelY(lineY( 0 )) );
1474
1475 int lastLine = lines() - 1;
1476 // We need to start from the pixelRect, to make sure the bottom border is entirely painted.
1477 // This is a case where we DO want to subtract pixels to pixels...
1478 int paragBottom = pixelRect(zh).height()-1;
1479 // If we don't have a bottom border, we need go as low as possible ( to touch the next parag's border ).
1480 // If we have a bottom border, then we rather exclude the linespacing. Looks nicer. OO does that too.
1481 if ( m_layout.bottomBorder.width() > 0 && drawBottomBorder)
1482 paragBottom -= zh->layoutUnitToPixelY( lineSpacing( lastLine ) );
1483 paragBottom -= KoBorder::zoomWidthY( m_layout.bottomBorder.width(), zh, 0 );
1484 //kdDebug(32500) << "Parag border: paragBottom=" << paragBottom
1485 // << " bottom border width = " << KoBorder::zoomWidthY( m_layout.bottomBorder.width(), zh, 0 ) << endl;
1486 r.setBottom( paragBottom );
1487
1488 //kdDebug(32500) << "KoTextParag::paint documentWidth=" << documentWidth() << " LU (" << zh->layoutUnitToPixelX(documentWidth()) << " pixels) bordersRect=" << r << endl;
1489 KoBorder::drawBorders( painter, zh, r,
1490 m_layout.leftBorder, m_layout.rightBorder, m_layout.topBorder, m_layout.bottomBorder,
1491 0, TQPen(), drawTopBorder, drawBottomBorder );
1492 }
1493}
1494
1495
1496void KoTextParag::paintLines( TQPainter &painter, const TQColorGroup &cg, KoTextCursor *cursor, bool drawSelections,
1497 int clipx, int clipy, int clipw, int cliph )
1498{
1499 if ( !visible )
1500 return;
1501 //KoTextStringChar *chr = at( 0 );
1502 //if (!chr) { kdDebug(32500) << "paragraph " << (void*)this << " " << paragId() << ", can't paint, EMPTY !" << endl;
1503
1504 // This is necessary with the current code, but in theory it shouldn't
1505 // be necessary, if Xft really gives us fully proportionnal chars....
1506#define CHECK_PIXELXADJ
1507
1508 int curx = -1, cury = 0, curh = 0, curline = 0;
1509 int xstart, xend = 0;
1510
1511 TQString qstr = str->toString();
1512 qstr.replace( TQChar(0x00a0U), ' ' ); // Not all fonts have non-breakable-space glyph
1513
1514 const int nSels = doc ? doc->numSelections() : 1;
1515 TQMemArray<int> selectionStarts( nSels );
1516 TQMemArray<int> selectionEnds( nSels );
1517 if ( drawSelections ) {
1518 bool hasASelection = false;
1519 for ( int i = 0; i < nSels; ++i ) {
1520 if ( !hasSelection( i ) ) {
1521 selectionStarts[ i ] = -1;
1522 selectionEnds[ i ] = -1;
1523 } else {
1524 hasASelection = true;
1525 selectionStarts[ i ] = selectionStart( i );
1526 int end = selectionEnd( i );
1527 if ( end == length() - 1 && n && n->hasSelection( i ) )
1528 end++;
1529 selectionEnds[ i ] = end;
1530 }
1531 }
1532 if ( !hasASelection )
1533 drawSelections = false;
1534 }
1535
1536 // Draw the lines!
1537 int line = m_lineChanged;
1538 if (line<0) line = 0;
1539
1540 int numLines = lines();
1541#ifdef DEBUG_PAINT
1542 kdDebug(32500) << " paintLines: from line " << line << " to " << numLines-1 << endl;
1543#endif
1544 for( ; line<numLines ; line++ )
1545 {
1546 // get the start and length of the line
1547 int nextLine;
1548 int startOfLine;
1549 lineStartOfLine(line, &startOfLine);
1550 if (line == numLines-1 )
1551 nextLine = length();
1552 else
1553 lineStartOfLine(line+1, &nextLine);
1554
1555 // init this line
1556 int cy, h, baseLine;
1557 lineInfo( line, cy, h, baseLine );
1558 if ( clipy != -1 && cy > clipy - r.y() + cliph ) // outside clip area, leave
1559 break;
1560
1561 // Vars related to the current "run of text"
1562 int paintStart = startOfLine;
1563 KoTextStringChar* chr = at(startOfLine);
1564 KoTextStringChar* nextchr = chr;
1565
1566 // okay, paint the line!
1567 for(int i=startOfLine;i<nextLine;i++)
1568 {
1569 chr = nextchr;
1570 if ( i < nextLine-1 )
1571 nextchr = at( i+1 );
1572
1573 // we flush at end of line
1574 bool flush = ( i == nextLine - 1 );
1575 // Optimization note: TQRT uses "flush |=", which doesn't have shortcut optimization
1576
1577 // we flush on format changes
1578 flush = flush || ( nextchr->format() != chr->format() );
1579 // we flush on link changes
1580 //flush = flush || ( nextchr->isLink() != chr->isLink() );
1581 // we flush on small caps changes
1582 if ( !flush && chr->format()->attributeFont() == KoTextFormat::ATT_SMALL_CAPS )
1583 {
1584 bool isLowercase = chr->c.upper() != chr->c;
1585 bool nextLowercase = nextchr->c.upper() != nextchr->c;
1586 flush = isLowercase != nextLowercase;
1587 }
1588 // we flush on start of run
1589 flush = flush || nextchr->startOfRun;
1590 // we flush on bidi changes
1591 flush = flush || ( nextchr->rightToLeft != chr->rightToLeft );
1592#ifdef CHECK_PIXELXADJ
1593 // we flush when the value of pixelxadj changes
1594 // [unless inside a ligature]
1595 flush = flush || ( nextchr->pixelxadj != chr->pixelxadj && nextchr->charStop );
1596#endif
1597 // we flush before and after tabs
1598 flush = flush || ( chr->c == '\t' || nextchr->c == '\t' );
1599 // we flush on soft hypens
1600 flush = flush || ( chr->c.unicode() == 0xad );
1601 // we flush on custom items
1602 flush = flush || chr->isCustom();
1603 // we flush before custom items
1604 flush = flush || nextchr->isCustom();
1605 // when painting justified we flush on spaces
1606 if ((alignment() & TQt::AlignJustify) == TQt::AlignJustify )
1607 //flush = flush || TQTextFormatter::isBreakable( str, i );
1608 flush = flush || chr->whiteSpace;
1609 // when underlining or striking "word by word" we flush before/after spaces
1610 if (!flush && chr->format()->wordByWord() && chr->format()->isStrikedOrUnderlined())
1611 flush = flush || chr->whiteSpace || nextchr->whiteSpace;
1612 // we flush when the string is getting too long
1613 flush = flush || ( i - paintStart >= 256 );
1614 // we flush when the selection state changes
1615 if ( drawSelections ) {
1616 // check if selection state changed - TODO update from TQRT
1617 bool selectionChange = false;
1618 if ( drawSelections ) {
1619 for ( int j = 0; j < nSels; ++j ) {
1620 selectionChange = selectionStarts[ j ] == i+1 || selectionEnds[ j ] == i+1;
1621 if ( selectionChange )
1622 break;
1623 }
1624 }
1625 flush = flush || selectionChange;
1626 }
1627
1628 // check for cursor mark
1629 if ( cursor && this == cursor->parag() && i == cursor->index() ) {
1630 curx = cursor->x();
1631 curline = line;
1632 KoTextStringChar *c = chr;
1633 if ( i > 0 )
1634 --c;
1635 curh = c->height();
1636 cury = cy + baseLine - c->ascent();
1637 }
1638
1639 if ( flush ) { // something changed, draw what we have so far
1640
1641 KoTextStringChar* cStart = at( paintStart );
1642 if ( chr->rightToLeft ) {
1643 xstart = chr->x;
1644 xend = cStart->x + cStart->width;
1645 } else {
1646 xstart = cStart->x;
1647 if ( i < length() - 1 && !str->at( i + 1 ).lineStart &&
1648 str->at( i + 1 ).rightToLeft == chr->rightToLeft )
1649 xend = str->at( i + 1 ).x;
1650 else
1651 xend = chr->x + chr->width;
1652 }
1653
1654 if ( (clipx == -1 || clipw == -1) || (xend >= clipx && xstart <= clipx + clipw) ) {
1655 if ( !chr->isCustom() ) {
1656 drawParagString( painter, qstr, paintStart, i - paintStart + 1, xstart, cy,
1657 baseLine, xend-xstart, h, drawSelections,
1658 chr->format(), selectionStarts, selectionEnds,
1659 cg, chr->rightToLeft, line );
1660 }
1661 else
1662 if ( chr->customItem()->placement() == KoTextCustomItem::PlaceInline ) {
1663 chr->customItem()->draw( &painter, chr->x, cy + baseLine - chr->customItem()->ascent(),
1664 clipx - r.x(), clipy - r.y(), clipw, cliph, cg,
1665 drawSelections && nSels && selectionStarts[ 0 ] <= i && selectionEnds[ 0 ] > i );
1666 }
1667 }
1668 paintStart = i+1;
1669 }
1670 } // end of character loop
1671 } // end of line loop
1672
1673 // if we should draw a cursor, draw it now
1674 if ( curx != -1 && cursor ) {
1675 drawCursor( painter, cursor, curx, cury, curh, cg );
1676 }
1677}
1678
1679// Called by KoTextParag::paintLines
1680// Draw a set of characters with the same formattings.
1681// Reimplemented here to convert coordinates first, and call @ref drawFormattingChars.
1682void KoTextParag::drawParagString( TQPainter &painter, const TQString &str, int start, int len, int startX,
1683 int lastY, int baseLine, int bw, int h, bool drawSelections,
1684 KoTextFormat *format, const TQMemArray<int> &selectionStarts,
1685 const TQMemArray<int> &selectionEnds, const TQColorGroup &cg, bool rightToLeft, int line )
1686{
1687 KoTextZoomHandler * zh = textDocument()->paintingZoomHandler();
1688 assert(zh);
1689
1690#ifdef DEBUG_PAINT
1691 kdDebug(32500) << "KoTextParag::drawParagString drawing from " << start << " to " << start+len << endl;
1692 kdDebug(32500) << " startX in LU: " << startX << " lastY in LU:" << lastY
1693 << " baseLine in LU:" << baseLine << endl;
1694#endif
1695
1696 // Calculate offset (e.g. due to shadow on left or top)
1697 // Important: don't use the 2-args methods here, offsets are not heights
1698 // (0 should be 0, not 1) (#63256)
1699 int shadowOffsetX_pix = zh->layoutUnitToPixelX( format->offsetX() );
1700 int shadowOffsetY_pix = zh->layoutUnitToPixelY( format->offsetY() );
1701
1702 // Calculate startX in pixels
1703 int startX_pix = zh->layoutUnitToPixelX( startX ) /* + at( rightToLeft ? start+len-1 : start )->pixelxadj */;
1704#ifdef DEBUG_PAINT
1705 kdDebug(32500) << "KoTextParag::drawParagString startX in pixels : " << startX_pix /*<< " adjustment:" << at( rightToLeft ? start+len-1 : start )->pixelxadj*/ << " bw=" << bw << endl;
1706#endif
1707
1708 int bw_pix = zh->layoutUnitToPixelX( startX, bw );
1709 int lastY_pix = zh->layoutUnitToPixelY( lastY );
1710 int baseLine_pix = zh->layoutUnitToPixelY( lastY, baseLine ); // 2 args=>+1. Is that correct?
1711 int h_pix = zh->layoutUnitToPixelY( lastY, h );
1712#ifdef DEBUG_PAINT
1713 kdDebug(32500) << "KoTextParag::drawParagString h(LU)=" << h << " lastY(LU)=" << lastY
1714 << " h(PIX)=" << h_pix << " lastY(PIX)=" << lastY_pix
1715 << " baseLine(PIX)=" << baseLine_pix << endl;
1716#endif
1717
1718 if ( format->textBackgroundColor().isValid() )
1719 painter.fillRect( startX_pix, lastY_pix, bw_pix, h_pix, format->textBackgroundColor() );
1720
1721 // don't want to draw line breaks but want them when drawing formatting chars
1722 int draw_len = len;
1723 int draw_startX = startX;
1724 int draw_bw = bw_pix;
1725 if ( at( start + len - 1 )->c == '\n' )
1726 {
1727 draw_len--;
1728 draw_bw -= at( start + len - 1 )->pixelwidth;
1729 if ( rightToLeft && draw_len > 0 )
1730 draw_startX = at( start + draw_len - 1 )->x;
1731 }
1732
1733 // Draw selection (moved here to do it before applying the offset from the shadow)
1734 // (and because it's not part of the shadow drawing)
1735 if ( drawSelections ) {
1736 bool inSelection = false;
1737 const int nSels = doc ? doc->numSelections() : 1;
1738 for ( int j = 0; j < nSels; ++j ) {
1739 if ( start >= selectionStarts[ j ] && start < selectionEnds[ j ] ) {
1740 inSelection = true;
1741 switch (j) {
1742 case KoTextDocument::Standard:
1743 painter.fillRect( startX_pix, lastY_pix, bw_pix, h_pix, cg.color( TQColorGroup::Highlight ) );
1744 break;
1745 case KoTextDocument::InputMethodPreedit:
1746 // no highlight
1747 break;
1748 default:
1749 painter.fillRect( startX_pix, lastY_pix, bw_pix, h_pix, doc ? doc->selectionColor( j ) : cg.color( TQColorGroup::Highlight ) );
1750 break;
1751 }
1752 }
1753 }
1754 if ( !inSelection )
1755 drawSelections = false; // save time in drawParagStringInternal
1756 }
1757
1758 // Draw InputMethod Preedit Underline
1759 const int nSels = doc ? doc->numSelections() : 1;
1760 if ( KoTextDocument::InputMethodPreedit < nSels
1761 && doc->hasSelection( KoTextDocument::InputMethodPreedit )
1762 && start >= selectionStarts[ KoTextDocument::InputMethodPreedit ]
1763 && start < selectionEnds[ KoTextDocument::InputMethodPreedit ] )
1764 {
1765 TQColor textColor( format->color() );
1766 painter.setPen( TQPen( textColor ) );
1767
1768 TQPoint p1( startX_pix, lastY_pix + h_pix - 1 );
1769 TQPoint p2( startX_pix + bw_pix, lastY_pix + h_pix - 1 );
1770 painter.drawLine( p1, p2 );
1771 }
1772
1773 if ( draw_len > 0 )
1774 {
1775 int draw_startX_pix = zh->layoutUnitToPixelX( draw_startX ) /* + at( rightToLeft ? start+draw_len-1 : start )->pixelxadj*/;
1776 draw_startX_pix += shadowOffsetX_pix;
1777 lastY_pix += shadowOffsetY_pix;
1778
1779 if ( format->shadowDistanceX() != 0 || format->shadowDistanceY() != 0 ) {
1780 int sx = format->shadowX( zh );
1781 int sy = format->shadowY( zh );
1782 if ( sx != 0 || sy != 0 )
1783 {
1784 painter.save();
1785 painter.translate( sx, sy );
1786 drawParagStringInternal( painter, str, start, draw_len, draw_startX_pix,
1787 lastY_pix, baseLine_pix,
1788 draw_bw,
1789 h_pix, false /*drawSelections*/,
1790 format, selectionStarts,
1791 selectionEnds, cg, rightToLeft, line, zh, true );
1792 painter.restore();
1793 }
1794 }
1795
1796 drawParagStringInternal( painter, str, start, draw_len, draw_startX_pix,
1797 lastY_pix, baseLine_pix,
1798 draw_bw,
1799 h_pix, drawSelections, format, selectionStarts,
1800 selectionEnds, cg, rightToLeft, line, zh, false );
1801 }
1802
1803 bool forPrint = ( painter.device()->devType() == TQInternal::Printer );
1804 if ( textDocument()->drawFormattingChars() && !forPrint )
1805 {
1806 drawFormattingChars( painter, start, len,
1807 lastY_pix, baseLine_pix, h_pix,
1808 drawSelections,
1809 format, selectionStarts,
1810 selectionEnds, cg, rightToLeft,
1811 line, zh, AllFormattingChars );
1812 }
1813}
1814
1815// Copied from the original KoTextParag
1816// (we have to copy it here, so that color & font changes don't require changing
1817// a local copy of the text format)
1818// And we have to keep it separate from drawParagString to avoid s/startX/startX_pix/ etc.
1819void KoTextParag::drawParagStringInternal( TQPainter &painter, const TQString &s, int start, int len, int startX,
1820 int lastY, int baseLine, int bw, int h, bool drawSelections,
1821 KoTextFormat *format, const TQMemArray<int> &selectionStarts,
1822 const TQMemArray<int> &selectionEnds, const TQColorGroup &cg, bool rightToLeft, int line, KoTextZoomHandler* zh, bool drawingShadow )
1823{
1824#ifdef DEBUG_PAINT
1825 kdDebug(32500) << "KoTextParag::drawParagStringInternal start=" << start << " len=" << len << " : '" << s.mid(start,len) << "'" << endl;
1826 kdDebug(32500) << "In pixels: startX=" << startX << " lastY=" << lastY << " baseLine=" << baseLine
1827 << " bw=" << bw << " h=" << h << " rightToLeft=" << rightToLeft << endl;
1828#endif
1829 if ( drawingShadow && format->shadowDistanceX() == 0 && format->shadowDistanceY() == 0 )
1830 return;
1831 // 1) Sort out the color
1832 TQColor textColor( drawingShadow ? format->shadowColor() : format->color() );
1833 if ( !textColor.isValid() ) // Resolve the color at this point
1834 textColor = KoTextFormat::defaultTextColor( &painter );
1835
1836 // 2) Sort out the font
1837 TQFont font( format->screenFont( zh ) );
1838 if ( format->attributeFont() == KoTextFormat::ATT_SMALL_CAPS && s[start].upper() != s[start] )
1839 font = format->smallCapsFont( zh, true );
1840
1841#if 0
1842 TQFontInfo fi( font );
1843 kdDebug(32500) << "KoTextParag::drawParagStringInternal requested font " << font.pointSizeFloat() << " using font " << fi.pointSize() << "pt (format font: " << format->font().pointSizeFloat() << "pt)" << endl;
1844 TQFontMetrics fm( font );
1845 kdDebug(32500) << "Real font: " << fi.family() << ". Font height in pixels: " << fm.height() << endl;
1846#endif
1847
1848 // 3) Paint
1849 TQString str( s );
1850 if ( str[ (int)str.length() - 1 ].unicode() == 0xad )
1851 str.remove( str.length() - 1, 1 );
1852 painter.setPen( TQPen( textColor ) );
1853 painter.setFont( font );
1854
1855 KoTextDocument* doc = document();
1856
1857 if ( drawSelections ) {
1858 const int nSels = doc ? doc->numSelections() : 1;
1859 for ( int j = 0; j < nSels; ++j ) {
1860 if ( start >= selectionStarts[ j ] && start < selectionEnds[ j ] ) {
1861 if ( !doc || doc->invertSelectionText( j ) )
1862 textColor = cg.color( TQColorGroup::HighlightedText );
1863 painter.setPen( TQPen( textColor ) );
1864 break;
1865 }
1866 }
1867 }
1868
1869 TQPainter::TextDirection dir = rightToLeft ? TQPainter::RTL : TQPainter::LTR;
1870
1871 if ( dir != TQPainter::RTL && start + len == length() ) // don't draw the last character (trailing space)
1872 {
1873 len--;
1874 if ( len <= 0 )
1875 return;
1876 bw-=at(length()-1)->pixelwidth;
1877 }
1878 KoTextParag::drawFontEffects( &painter, format, zh, font, textColor, startX, baseLine, bw, lastY, h, str[start] );
1879
1880 if ( str[ start ] != '\t' && str[ start ].unicode() != 0xad ) {
1881 str = format->displayedString( str ); // #### This converts the whole string, instead of from start to start+len!
1882 if ( format->vAlign() == KoTextFormat::AlignNormal ) {
1883 int posY = lastY + baseLine;
1884 //we must move to bottom text because we create
1885 //shadow to 'top'.
1886 int sy = format->shadowY( zh );
1887 if ( sy < 0)
1888 posY -= sy;
1889 painter.drawText( startX, posY, str, start, len, dir );
1890#ifdef BIDI_DEBUG
1891 painter.save();
1892 painter.setPen ( TQt::red );
1893 painter.drawLine( startX, lastY, startX, lastY + baseLine );
1894 painter.drawLine( startX, lastY + baseLine/2, startX + 10, lastY + baseLine/2 );
1895 int w = 0;
1896 int i = 0;
1897 while( i < len )
1898 w += painter.fontMetrics().charWidth( str, start + i++ );
1899 painter.setPen ( TQt::blue );
1900 painter.drawLine( startX + w - 1, lastY, startX + w - 1, lastY + baseLine );
1901 painter.drawLine( startX + w - 1, lastY + baseLine/2, startX + w - 1 - 10, lastY + baseLine/2 );
1902 painter.restore();
1903#endif
1904 } else if ( format->vAlign() == KoTextFormat::AlignSuperScript ) {
1905 int posY =lastY + baseLine - ( painter.fontMetrics().height() / 2 );
1906 //we must move to bottom text because we create
1907 //shadow to 'top'.
1908 int sy = format->shadowY( zh );
1909 if ( sy < 0)
1910 posY -= sy;
1911 painter.drawText( startX, posY, str, start, len, dir );
1912 } else if ( format->vAlign() == KoTextFormat::AlignSubScript ) {
1913 int posY =lastY + baseLine + ( painter.fontMetrics().height() / 6 );
1914 //we must move to bottom text because we create
1915 //shadow to 'top'.
1916 int sy = format->shadowY( zh );
1917 if ( sy < 0)
1918 posY -= sy;
1919 painter.drawText( startX, posY, str, start, len, dir );
1920 } else if ( format->vAlign() == KoTextFormat::AlignCustom ) {
1921 int posY = lastY + baseLine - format->offsetFromBaseLine();
1922 //we must move to bottom text because we create
1923 //shadow to 'top'.
1924 int sy = format->shadowY( zh );
1925 if ( sy < 0)
1926 posY -= sy;
1927 painter.drawText( startX, posY, str, start, len, dir );
1928 }
1929 }
1930 if ( str[ start ] == '\t' && m_tabCache.contains( start ) ) {
1931 painter.save();
1932 KoTextZoomHandler * zh = textDocument()->paintingZoomHandler();
1933 const KoTabulator& tab = m_layout.tabList()[ m_tabCache[ start ] ];
1934 int lineWidth = zh->zoomItY( tab.ptWidth );
1935 switch ( tab.filling ) {
1936 case TF_DOTS:
1937 painter.setPen( TQPen( textColor, lineWidth, TQt::DotLine ) );
1938 painter.drawLine( startX, lastY + baseLine, startX + bw, lastY + baseLine );
1939 break;
1940 case TF_LINE:
1941 painter.setPen( TQPen( textColor, lineWidth, TQt::SolidLine ) );
1942 painter.drawLine( startX, lastY + baseLine, startX + bw, lastY + baseLine );
1943 break;
1944 case TF_DASH:
1945 painter.setPen( TQPen( textColor, lineWidth, TQt::DashLine ) );
1946 painter.drawLine( startX, lastY + baseLine, startX + bw, lastY + baseLine );
1947 break;
1948 case TF_DASH_DOT:
1949 painter.setPen( TQPen( textColor, lineWidth, TQt::DashDotLine ) );
1950 painter.drawLine( startX, lastY + baseLine, startX + bw, lastY + baseLine );
1951 break;
1952 case TF_DASH_DOT_DOT:
1953 painter.setPen( TQPen( textColor, lineWidth, TQt::DashDotDotLine ) );
1954 painter.drawLine( startX, lastY + baseLine, startX + bw, lastY + baseLine );
1955 break;
1956
1957 default:
1958 break;
1959 }
1960 painter.restore();
1961 }
1962
1963 if ( start+len < length() && at( start+len )->lineStart )
1964 {
1965#ifdef DEBUG_PAINT
1966 //kdDebug(32500) << "we are drawing the end of line " << line << ". Auto-hyphenated: " << lineHyphenated( line ) << endl;
1967#endif
1968 bool drawHyphen = at( start+len-1 )->c.unicode() == 0xad;
1969 drawHyphen = drawHyphen || lineHyphenated( line );
1970 if ( drawHyphen ) {
1971#ifdef DEBUG_PAINT
1972 kdDebug(32500) << "drawing hyphen at x=" << startX+bw << endl;
1973#endif
1974 painter.drawText( startX + bw, lastY + baseLine, TQString("-") ); // \xad gives squares with some fonts (!?)
1975 }
1976 }
1977
1978 // Paint a zigzag line for "wrong" background spellchecking checked words:
1979 if(
1980 painter.device()->devType() != TQInternal::Printer &&
1981 format->isMisspelled() &&
1982 !drawingShadow &&
1983 textDocument()->drawingMissingSpellLine() )
1984 {
1985 painter.save();
1986 painter.setPen( TQPen( TQt::red, 1 ) );
1987
1988 // Draw 3 pixel lines with increasing offset and distance 4:
1989 for( int zigzag_line = 0; zigzag_line < 3; ++zigzag_line )
1990 {
1991 for( int zigzag_x = zigzag_line; zigzag_x < bw; zigzag_x += 4 )
1992 {
1993 painter.drawPoint(
1994 startX + zigzag_x,
1995 lastY + baseLine + h/12 - 1 + zigzag_line );
1996 }
1997 }
1998
1999 // "Double" the pixel number for the middle line:
2000 for( int zigzag_x = 3; zigzag_x < bw; zigzag_x += 4 )
2001 {
2002 painter.drawPoint(
2003 startX + zigzag_x,
2004 lastY + baseLine + h/12 );
2005 }
2006
2007 painter.restore();
2008 }
2009}
2010
2011bool KoTextParag::lineHyphenated( int l ) const
2012{
2013 if ( l > (int)lineStarts.count() - 1 ) {
2014 kdWarning() << "KoTextParag::lineHyphenated: line " << l << " out of range!" << endl;
2015 return false;
2016 }
2017
2018 if ( !isValid() )
2019 const_cast<KoTextParag*>(this)->format();
2020
2021 TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
2022 while ( l-- > 0 )
2023 ++it;
2024 return ( *it )->hyphenated;
2025}
2026
2028void KoTextParag::drawCursor( TQPainter &painter, KoTextCursor *cursor, int curx, int cury, int curh, const TQColorGroup &cg )
2029{
2030 KoTextZoomHandler * zh = textDocument()->paintingZoomHandler();
2031 int x = zh->layoutUnitToPixelX( curx ) /*+ cursor->parag()->at( cursor->index() )->pixelxadj*/;
2032 //kdDebug(32500) << " drawCursor: LU: [cur]x=" << curx << ", cury=" << cury << " -> PIX: x=" << x << ", y=" << zh->layoutUnitToPixelY( cury ) << endl;
2033 KoTextParag::drawCursorDefault( painter, cursor, x,
2034 zh->layoutUnitToPixelY( cury ),
2035 zh->layoutUnitToPixelY( cury, curh ), cg );
2036}
2037
2038// Reimplemented from KoTextParag
2039void KoTextParag::copyParagData( KoTextParag *parag )
2040{
2041 // Style of the previous paragraph
2042 KoParagStyle * style = parag->style();
2043 // Obey "following style" setting
2044 bool styleApplied = false;
2045 if ( style )
2046 {
2047 KoParagStyle * newStyle = style->followingStyle();
2048 if ( newStyle && style != newStyle ) // if same style, keep paragraph-specific changes as usual
2049 {
2050 setParagLayout( newStyle->paragLayout() );
2051 KoTextFormat * format = &newStyle->format();
2052 setFormat( format );
2053 format->addRef();
2054 str->setFormat( 0, format, true ); // prepare format for text insertion
2055 styleApplied = true;
2056 }
2057 }
2058 // This should never happen in KWord, but it happens in KPresenter
2059 //else
2060 // kdWarning() << "Paragraph has no style " << paragId() << endl;
2061
2062 // No "following style" setting, or same style -> copy layout & format of previous paragraph
2063 if (!styleApplied)
2064 {
2065 setParagLayout( parag->paragLayout() );
2066 // Remove pagebreak flags from initial parag - they got copied to the new parag
2067 parag->m_layout.pageBreaking &= ~KoParagLayout::HardFrameBreakBefore;
2068 parag->m_layout.pageBreaking &= ~KoParagLayout::HardFrameBreakAfter;
2069 // Remove footnote counter text from second parag
2070 if ( m_layout.counter && m_layout.counter->numbering() == KoParagCounter::NUM_FOOTNOTE )
2071 setNoCounter();
2072 // Do not copy 'restart numbering at this paragraph' option (would be silly)
2073 if ( m_layout.counter )
2074 m_layout.counter->setRestartCounter(false);
2075
2076 // set parag format to the format of the trailing space of the previous parag
2077 setFormat( parag->at( parag->length()-1 )->format() );
2078 // KoTextCursor::splitAndInsertEmptyParag takes care of setting the format
2079 // for the chars in the new parag
2080 }
2081
2082 // Note: we don't call the original KoTextParag::copyParagData on purpose.
2083 // We don't want setListStyle to get called - it ruins our stylesheetitems
2084 // And we don't care about copying the stylesheetitems directly,
2085 // applying the parag layout will create them
2086}
2087
2088void KoTextParag::setTabList( const KoTabulatorList &tabList )
2089{
2090 KoTabulatorList lst( tabList );
2091 m_layout.setTabList( lst );
2092 if ( !tabList.isEmpty() )
2093 {
2094 KoTextZoomHandler* zh = textDocument()->formattingZoomHandler();
2095 int * tabs = new int[ tabList.count() + 1 ]; // will be deleted by ~KoTextParag
2096 KoTabulatorList::Iterator it = lst.begin();
2097 unsigned int i = 0;
2098 for ( ; it != lst.end() ; ++it, ++i )
2099 tabs[i] = zh->ptToLayoutUnitPixX( (*it).ptPos );
2100 tabs[i] = 0;
2101 assert( i == tabList.count() );
2102 setTabArray( tabs );
2103 } else
2104 {
2105 setTabArray( 0 );
2106 }
2107 invalidate( 0 );
2108}
2109
2111int KoTextParag::nextTab( int chnum, int x, int availableWidth )
2112{
2113 if ( !m_layout.tabList().isEmpty() )
2114 {
2115 // Fetch the zoomed and sorted tab positions from KoTextParag
2116 // We stored them there for faster access
2117 int * tArray = tabArray();
2118 int i = 0;
2119 if ( str->isRightToLeft() )
2120 i = m_layout.tabList().size() - 1;
2121 KoTextZoomHandler* zh = textDocument()->formattingZoomHandler();
2122
2123 while ( i >= 0 && i < (int)m_layout.tabList().size() ) {
2124 //kdDebug(32500) << "KoTextParag::nextTab tArray[" << i << "]=" << tArray[i] << " type " << m_layout.tabList()[i].type << endl;
2125 int tab = tArray[ i ];
2126
2127 // If a right-aligned tab is after the right edge then assume
2128 // that it -is- on the right edge, otherwise the last letters will fall off.
2129 // This is compatible with OOo's behavior.
2130 if ( tab > availableWidth ) {
2131 //kdDebug(32500) << "Tab position adjusted to availableWidth=" << availableWidth << endl;
2132 tab = availableWidth;
2133 }
2134
2135 if ( str->isRightToLeft() )
2136 tab = availableWidth - tab;
2137
2138 if ( tab > x ) {
2139 int type = m_layout.tabList()[i].type;
2140
2141 // fix the tab type for right to left text
2142 if ( str->isRightToLeft() )
2143 if ( type == T_RIGHT )
2144 type = T_LEFT;
2145 else if ( type == T_LEFT )
2146 type = T_RIGHT;
2147
2148 switch ( type ) {
2149 case T_RIGHT:
2150 case T_CENTER:
2151 {
2152 // Look for the next tab (or EOL)
2153 int c = chnum + 1;
2154 int w = 0;
2155 while ( c < str->length() - 1 && str->at( c ).c != '\t' && str->at( c ).c != '\n' )
2156 {
2157 KoTextStringChar & ch = str->at( c );
2158 // Determine char width
2159 // This must be done in the same way as in KoTextFormatter::format() or there can be different rounding errors.
2160 if ( ch.isCustom() )
2161 w += ch.customItem()->width;
2162 else
2163 {
2164 KoTextFormat *charFormat = ch.format();
2165 int ww = charFormat->charWidth( zh, false, &ch, this, c );
2167 w += ww;
2168 }
2169 ++c;
2170 }
2171
2172 m_tabCache[chnum] = i;
2173
2174 if ( type == T_RIGHT )
2175 return tab - w;
2176 else // T_CENTER
2177 return tab - w/2;
2178 }
2179 case T_DEC_PNT:
2180 {
2181 // Look for the next tab (or EOL), and for alignChar
2182 // Default to right-aligned if no decimal point found (behavior from msword)
2183 int c = chnum + 1;
2184 int w = 0;
2185 while ( c < str->length()-1 && str->at( c ).c != '\t' && str->at( c ).c != '\n' )
2186 {
2187 KoTextStringChar & ch = str->at( c );
2188 if ( ch.c == m_layout.tabList()[i].alignChar )
2189 {
2190 // Can't use ch.width yet, since the formatter hasn't run over those chars
2191 int ww = ch.format()->charWidth( zh, false, &ch, this, c );
2193 if ( str->isRightToLeft() )
2194 {
2195 w = ww / 2; // center around the decimal point
2196 ++c;
2197 continue;
2198 }
2199 else
2200 {
2201 w += ww / 2; // center around the decimal point
2202 break;
2203 }
2204 }
2205
2206 // Determine char width
2207 if ( ch.isCustom() )
2208 w += ch.customItem()->width;
2209 else
2210 {
2211 int ww = ch.format()->charWidth( zh, false, &ch, this, c );
2213 }
2214
2215 ++c;
2216 }
2217 m_tabCache[chnum] = i;
2218 return tab - w;
2219 }
2220 default: // case T_LEFT:
2221 m_tabCache[chnum] = i;
2222 return tab;
2223 }
2224 }
2225 if ( str->isRightToLeft() )
2226 --i;
2227 else
2228 ++i;
2229 }
2230 }
2231 // No tab list, use tab-stop-width. qrichtext.cpp has the code :)
2232 return KoTextParag::nextTabDefault( chnum, x );
2233}
2234
2235void KoTextParag::applyStyle( KoParagStyle *style )
2236{
2237 setParagLayout( style->paragLayout() );
2238 KoTextFormat *newFormat = &style->format();
2239 setFormat( 0, str->length(), newFormat );
2240 setFormat( newFormat );
2241}
2242
2243void KoTextParag::setParagLayout( const KoParagLayout & layout, int flags, int marginIndex )
2244{
2245 //kdDebug(32500) << "KoTextParag::setParagLayout flags=" << flags << endl;
2246 if ( flags & KoParagLayout::Alignment )
2247 setAlign( layout.alignment );
2248 if ( flags & KoParagLayout::Margins ) {
2249 if ( marginIndex == -1 )
2250 setMargins( layout.margins );
2251 else
2252 setMargin( (TQStyleSheetItem::Margin)marginIndex, layout.margins[marginIndex] );
2253 }
2254 if ( flags & KoParagLayout::LineSpacing )
2255 {
2256 setLineSpacingType( layout.lineSpacingType );
2257 setLineSpacing( layout.lineSpacingValue() );
2258 }
2259 if ( flags & KoParagLayout::Borders )
2260 {
2261 setLeftBorder( layout.leftBorder );
2262 setRightBorder( layout.rightBorder );
2263 setTopBorder( layout.topBorder );
2264 setBottomBorder( layout.bottomBorder );
2265 setJoinBorder( layout.joinBorder );
2266 }
2267 if ( flags & KoParagLayout::BackgroundColor )
2268 {
2269 setBackgroundColor( layout.backgroundColor );
2270 }
2271 if ( flags & KoParagLayout::BulletNumber )
2272 setCounter( layout.counter );
2273 if ( flags & KoParagLayout::Tabulator )
2274 setTabList( layout.tabList() );
2275 if ( flags == KoParagLayout::All )
2276 {
2277 setDirection( static_cast<TQChar::Direction>(layout.direction) );
2278 // Don't call applyStyle from here, it would overwrite any paragraph-specific settings
2279 setStyle( layout.style );
2280 }
2281}
2282
2283void KoTextParag::setCustomItem( int index, KoTextCustomItem * custom, KoTextFormat * currentFormat )
2284{
2285 //kdDebug(32500) << "KoTextParag::setCustomItem " << index << " " << (void*)custom
2286 // << " currentFormat=" << (void*)currentFormat << endl;
2287 if ( currentFormat )
2288 setFormat( index, 1, currentFormat );
2289 at( index )->setCustomItem( custom );
2290 //addCustomItem();
2291 document()->registerCustomItem( custom, this );
2292 custom->recalc(); // calc value (e.g. for variables) and set initial size
2293 invalidate( 0 );
2294 setChanged( true );
2295}
2296
2297void KoTextParag::removeCustomItem( int index )
2298{
2299 Q_ASSERT( at( index )->isCustom() );
2300 KoTextCustomItem * item = at( index )->customItem();
2301 at( index )->loseCustomItem();
2302 //KoTextParag::removeCustomItem();
2303 document()->unregisterCustomItem( item, this );
2304}
2305
2306
2307int KoTextParag::findCustomItem( const KoTextCustomItem * custom ) const
2308{
2309 int len = str->length();
2310 for ( int i = 0; i < len; ++i )
2311 {
2312 KoTextStringChar & ch = str->at(i);
2313 if ( ch.isCustom() && ch.customItem() == custom )
2314 return i;
2315 }
2316 kdWarning() << "KoTextParag::findCustomItem custom item " << (void*)custom
2317 << " not found in paragraph " << paragId() << endl;
2318 return 0;
2319}
2320
2321#ifndef NDEBUG
2322void KoTextParag::printRTDebug( int info )
2323{
2324 TQString specialFlags;
2325 if ( str->needsSpellCheck() )
2326 specialFlags += " needsSpellCheck=true";
2327 if ( wasMovedDown() )
2328 specialFlags += " wasMovedDown=true";
2329 if ( partOfTableOfContents() )
2330 specialFlags += " part-of-TOC=true";
2331 kdDebug(32500) << "Paragraph " << this << " (" << paragId() << ") [changed="
2332 << hasChanged() << ", valid=" << isValid()
2333 << specialFlags
2334 << "] ------------------ " << endl;
2335 if ( prev() && prev()->paragId() + 1 != paragId() )
2336 kdWarning() << " Previous paragraph " << prev() << " has ID " << prev()->paragId() << endl;
2337 if ( next() && next()->paragId() != paragId() + 1 )
2338 kdWarning() << " Next paragraph " << next() << " has ID " << next()->paragId() << endl;
2339 //if ( !next() )
2340 // kdDebug(32500) << " next is 0L" << endl;
2341 kdDebug(32500) << " Style: " << style() << " " << ( style() ? style()->name().local8Bit().data() : "NO STYLE" ) << endl;
2342 kdDebug(32500) << " Text: '" << str->toString() << "'" << endl;
2343 if ( info == 0 ) // paragraph info
2344 {
2345 if ( m_layout.counter )
2346 {
2347 m_layout.counter->printRTDebug( this );
2348 }
2349 static const char * const s_align[] = { "Auto", "Left", "Right", "ERROR", "HCenter", "ERR", "ERR", "ERR", "Justify", };
2350 static const char * const s_linespacing[] = { "Single", "1.5", "2", "custom", "atLeast", "Multiple", "Fixed" };
2351 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" };
2352 kdDebug(32500) << " align: " << s_align[alignment()] << " resolveAlignment: " << s_align[resolveAlignment()]
2353 << " isRTL:" << str->isRightToLeft()
2354 << " dir: " << s_dir[direction()] << endl;
2355 TQRect pixr = pixelRect( textDocument()->paintingZoomHandler() );
2356 kdDebug(32500) << " rect() : " << DEBUGRECT( rect() )
2357 << " pixelRect() : " << DEBUGRECT( pixr ) << endl;
2358 kdDebug(32500) << " topMargin()=" << topMargin()
2359 << " breakableTopMargin()=" << breakableTopMargin()
2360 << " bottomMargin()=" << bottomMargin()
2361 << " leftMargin()=" << leftMargin() << " firstLineMargin()=" << firstLineMargin()
2362 << " rightMargin()=" << rightMargin() << endl;
2363 if ( kwLineSpacingType() != KoParagLayout::LS_SINGLE )
2364 kdDebug(32500) << " linespacing type=" << s_linespacing[ -kwLineSpacingType() ]
2365 << " value=" << kwLineSpacing() << endl;
2366 const int pageBreaking = m_layout.pageBreaking;
2367 TQStringList pageBreakingFlags;
2368 if ( pageBreaking & KoParagLayout::KeepLinesTogether )
2369 pageBreakingFlags.append( "KeepLinesTogether" );
2370 if ( pageBreaking & KoParagLayout::HardFrameBreakBefore )
2371 pageBreakingFlags.append( "HardFrameBreakBefore" );
2372 if ( pageBreaking & KoParagLayout::HardFrameBreakAfter )
2373 pageBreakingFlags.append( "HardFrameBreakAfter" );
2374 if ( pageBreaking & KoParagLayout::KeepWithPrevious )
2375 pageBreakingFlags.append( "KeepWithPrevious" );
2376 if ( pageBreaking & KoParagLayout::KeepWithNext )
2377 pageBreakingFlags.append( "KeepWithNext" );
2378 if ( !pageBreakingFlags.isEmpty() )
2379 kdDebug(32500) << " page Breaking: " << pageBreakingFlags.join(",") << endl;
2380
2381 static const char * const tabtype[] = { "T_LEFT", "T_CENTER", "T_RIGHT", "T_DEC_PNT", "error!!!" };
2382 KoTabulatorList tabList = m_layout.tabList();
2383 if ( tabList.isEmpty() ) {
2384 if ( str->toString().find( '\t' ) != -1 )
2385 kdDebug(32500) << "Tab width: " << textDocument()->tabStopWidth() << endl;
2386 } else {
2387 KoTabulatorList::Iterator it = tabList.begin();
2388 for ( ; it != tabList.end() ; it++ )
2389 kdDebug(32500) << "Tab type:" << tabtype[(*it).type] << " at: " << (*it).ptPos << endl;
2390 }
2391 } else if ( info == 1 ) // formatting info
2392 {
2393 kdDebug(32500) << " Paragraph format=" << paragFormat() << " " << paragFormat()->key()
2394 << " fontsize:" << dynamic_cast<KoTextFormat *>(paragFormat())->pointSize() << endl;
2395
2396 for ( int line = 0 ; line < lines(); ++ line ) {
2397 int y, h, baseLine;
2398 lineInfo( line, y, h, baseLine );
2399 int startOfLine;
2400 lineStartOfLine( line, &startOfLine );
2401 kdDebug(32500) << " Line " << line << " y=" << y << " height=" << h << " baseLine=" << baseLine << " startOfLine(index)=" << startOfLine << endl;
2402 }
2403 kdDebug(32500) << endl;
2404 KoTextString * s = string();
2405 int lastX = 0; // pixels
2406 int lastW = 0; // pixels
2407 for ( int i = 0 ; i < s->length() ; ++i )
2408 {
2409 KoTextStringChar & ch = s->at(i);
2410 int pixelx = textDocument()->formattingZoomHandler()->layoutUnitToPixelX( ch.x )
2411 + ch.pixelxadj;
2412 if ( ch.lineStart )
2413 kdDebug(32500) << "LINESTART" << endl;
2414 TQString attrs = " ";
2415 if ( ch.whiteSpace )
2416 attrs += "whitespace ";
2417 if ( !ch.charStop )
2418 attrs += "notCharStop ";
2419 if ( ch.wordStop )
2420 attrs += "wordStop ";
2421 attrs.truncate( attrs.length() - 1 );
2422
2423 kdDebug(32500) << i << ": '" << TQString(ch.c).rightJustify(2)
2424 << "' (" << TQString::number( ch.c.unicode() ).rightJustify(3) << ")"
2425 << " x(LU)=" << ch.x
2426 << " w(LU)=" << ch.width//s->width(i)
2427 << " x(PIX)=" << pixelx
2428 << " (xadj=" << + ch.pixelxadj << ")"
2429 << " w(PIX)=" << ch.pixelwidth
2430 << " height=" << ch.height()
2431 << attrs
2432 // << " format=" << ch.format()
2433 // << " \"" << ch.format()->key() << "\" "
2434 //<< " fontsize:" << dynamic_cast<KoTextFormat *>(ch.format())->pointSize()
2435 << endl;
2436
2437 // Check that the format is in the collection (i.e. its defaultFormat or in the dict)
2438 if ( ch.format() != textDocument()->formatCollection()->defaultFormat() )
2439 Q_ASSERT( textDocument()->formatCollection()->dict()[ch.format()->key()] );
2440
2441 if ( !str->isBidi() && !ch.lineStart )
2442 Q_ASSERT( lastX + lastW == pixelx ); // looks like some rounding problem with justified spaces
2443 lastX = pixelx;
2444 lastW = ch.pixelwidth;
2445 if ( ch.isCustom() )
2446 {
2447 KoTextCustomItem * item = ch.customItem();
2448 kdDebug(32500) << " - custom item " << item
2449 << " ownline=" << item->ownLine()
2450 << " size=" << item->width << "x" << item->height
2451 << " ascent=" << item->ascent()
2452 << endl;
2453 }
2454 }
2455 }
2456}
2457#endif
2458
2459void KoTextParag::drawFontEffects( TQPainter * p, KoTextFormat *format, KoTextZoomHandler *zh, TQFont font, const TQColor & color, int startX, int baseLine, int bw, int lastY, int /*h*/, TQChar firstChar )
2460{
2461 // This is about drawing underlines and strikeouts
2462 // So abort immediately if there's none to draw.
2463 if ( !format->isStrikedOrUnderlined() )
2464 return;
2465 //kdDebug(32500) << "drawFontEffects wordByWord=" << format->wordByWord() <<
2466 // " firstChar='" << TQString(firstChar) << "'" << endl;
2467 // paintLines ensures that we're called word by word if wordByWord is true.
2468 if ( format->wordByWord() && firstChar.isSpace() )
2469 return;
2470
2471 double dimd;
2472 int y;
2473 int offset = 0;
2474 if (format->vAlign() == KoTextFormat::AlignSubScript )
2475 offset = p->fontMetrics().height() / 6;
2476 else if (format->vAlign() == KoTextFormat::AlignSuperScript )
2477 offset = -p->fontMetrics().height() / 2;
2478
2479 dimd = KoBorder::zoomWidthY( format->underLineWidth(), zh, 1 );
2480 if((format->vAlign() == KoTextFormat::AlignSuperScript) ||
2481 (format->vAlign() == KoTextFormat::AlignSubScript ) || (format->vAlign() == KoTextFormat::AlignCustom ))
2482 dimd*=format->relativeTextSize();
2483 y = lastY + baseLine + offset - ( (format->vAlign() == KoTextFormat::AlignCustom)?format->offsetFromBaseLine():0 );
2484
2485 if ( format->doubleUnderline())
2486 {
2487 TQColor col = format->textUnderlineColor().isValid() ? format->textUnderlineColor(): color ;
2488 int dim=static_cast<int>(0.75*dimd);
2489 dim=dim?dim:1; //width of line should be at least 1
2490 p->save();
2491
2492 switch( format->underlineStyle())
2493 {
2494 case KoTextFormat::U_SOLID:
2495 p->setPen( TQPen( col, dim, TQt::SolidLine ) );
2496 break;
2497 case KoTextFormat::U_DASH:
2498 p->setPen( TQPen( col, dim, TQt::DashLine ) );
2499 break;
2500 case KoTextFormat::U_DOT:
2501 p->setPen( TQPen( col, dim, TQt::DotLine ) );
2502 break;
2503 case KoTextFormat::U_DASH_DOT:
2504 p->setPen( TQPen( col, dim, TQt::DashDotLine ) );
2505 break;
2506 case KoTextFormat::U_DASH_DOT_DOT:
2507 p->setPen( TQPen( col, dim, TQt::DashDotDotLine ) );
2508 break;
2509 default:
2510 p->setPen( TQPen( color, dim, TQt::SolidLine ) );
2511 }
2512
2513 y += static_cast<int>(1.125*dimd); // slightly under the baseline if possible
2514 p->drawLine( startX, y, startX + bw, y );
2515 y += static_cast<int>(1.5*dimd);
2516 p->drawLine( startX, y, startX + bw, y );
2517 p->restore();
2518 if ( font.underline() ) { // can this happen?
2519 font.setUnderline( false );
2520 p->setFont( font );
2521 }
2522 }
2523 else if ( format->underline() ||
2524 format->underlineType() == KoTextFormat::U_SIMPLE_BOLD)
2525 {
2526
2527 TQColor col = format->textUnderlineColor().isValid() ? format->textUnderlineColor(): color ;
2528 p->save();
2529 int dim=(format->underlineType() == KoTextFormat::U_SIMPLE_BOLD)?static_cast<int>(2*dimd):static_cast<int>(dimd);
2530 dim=dim?dim:1; //width of line should be at least 1
2531 y += static_cast<int>(1.875*dimd);
2532
2533 switch( format->underlineStyle() )
2534 {
2535 case KoTextFormat::U_SOLID:
2536 p->setPen( TQPen( col, dim, TQt::SolidLine ) );
2537 break;
2538 case KoTextFormat::U_DASH:
2539 p->setPen( TQPen( col, dim, TQt::DashLine ) );
2540 break;
2541 case KoTextFormat::U_DOT:
2542 p->setPen( TQPen( col, dim, TQt::DotLine ) );
2543 break;
2544 case KoTextFormat::U_DASH_DOT:
2545 p->setPen( TQPen( col, dim, TQt::DashDotLine ) );
2546 break;
2547 case KoTextFormat::U_DASH_DOT_DOT:
2548 p->setPen( TQPen( col, dim, TQt::DashDotDotLine ) );
2549 break;
2550 default:
2551 p->setPen( TQPen( col, dim, TQt::SolidLine ) );
2552 }
2553
2554 p->drawLine( startX, y, startX + bw, y );
2555 p->restore();
2556 font.setUnderline( false );
2557 p->setFont( font );
2558 }
2559 else if ( format->waveUnderline() )
2560 {
2561 int dim=static_cast<int>(dimd);
2562 dim=dim?dim:1; //width of line should be at least 1
2563 y += dim;
2564 TQColor col = format->textUnderlineColor().isValid() ? format->textUnderlineColor(): color ;
2565 p->save();
2566 int offset = 2 * dim;
2567 TQPen pen(col, dim, TQt::SolidLine);
2568 pen.setCapStyle(TQt::RoundCap);
2569 p->setPen(pen);
2570 Q_ASSERT(offset);
2571 double anc=acos(1.0-2*(static_cast<double>(offset-(startX)%offset)/static_cast<double>(offset)))/3.1415*180;
2572 int pos=1;
2573 //set starting position
2574 if(2*((startX/offset)/2)==startX/offset)
2575 pos*=-1;
2576 //draw first part of wave
2577 p->drawArc( (startX/offset)*offset, y, offset, offset, 0, -tqRound(pos*anc*16) );
2578 //now the main part
2579 int zigzag_x = (startX/offset+1)*offset;
2580 for ( ; zigzag_x + offset <= bw+startX; zigzag_x += offset)
2581 {
2582 p->drawArc( zigzag_x, y, offset, offset, 0, pos*180*16 );
2583 pos*=-1;
2584 }
2585 //and here we finish
2586 anc=acos(1.0-2*(static_cast<double>((startX+bw)%offset)/static_cast<double>(offset)))/3.1415*180;
2587 p->drawArc( zigzag_x, y, offset, offset, 180*16, -tqRound(pos*anc*16) );
2588 p->restore();
2589 font.setUnderline( false );
2590 p->setFont( font );
2591 }
2592
2593 dimd = KoBorder::zoomWidthY( static_cast<double>(format->pointSize())/18.0, zh, 1 );
2594 if((format->vAlign() == KoTextFormat::AlignSuperScript) ||
2595 (format->vAlign() == KoTextFormat::AlignSubScript ) || (format->vAlign() == KoTextFormat::AlignCustom ))
2596 dimd*=format->relativeTextSize();
2597 y = lastY + baseLine + offset - ( (format->vAlign() == KoTextFormat::AlignCustom)?format->offsetFromBaseLine():0 );
2598
2599 if ( format->strikeOutType() == KoTextFormat::S_SIMPLE
2600 || format->strikeOutType() == KoTextFormat::S_SIMPLE_BOLD)
2601 {
2602 unsigned int dim = (format->strikeOutType() == KoTextFormat::S_SIMPLE_BOLD)? static_cast<int>(2*dimd) : static_cast<int>(dimd);
2603 p->save();
2604
2605 switch( format->strikeOutStyle() )
2606 {
2607 case KoTextFormat::S_SOLID:
2608 p->setPen( TQPen( color, dim, TQt::SolidLine ) );
2609 break;
2610 case KoTextFormat::S_DASH:
2611 p->setPen( TQPen( color, dim, TQt::DashLine ) );
2612 break;
2613 case KoTextFormat::S_DOT:
2614 p->setPen( TQPen( color, dim, TQt::DotLine ) );
2615 break;
2616 case KoTextFormat::S_DASH_DOT:
2617 p->setPen( TQPen( color, dim, TQt::DashDotLine ) );
2618 break;
2619 case KoTextFormat::S_DASH_DOT_DOT:
2620 p->setPen( TQPen( color, dim, TQt::DashDotDotLine ) );
2621 break;
2622 default:
2623 p->setPen( TQPen( color, dim, TQt::SolidLine ) );
2624 }
2625
2626 y -= static_cast<int>(5*dimd);
2627 p->drawLine( startX, y, startX + bw, y );
2628 p->restore();
2629 font.setStrikeOut( false );
2630 p->setFont( font );
2631 }
2632 else if ( format->strikeOutType() == KoTextFormat::S_DOUBLE )
2633 {
2634 unsigned int dim = static_cast<int>(dimd);
2635 p->save();
2636
2637 switch( format->strikeOutStyle() )
2638 {
2639 case KoTextFormat::S_SOLID:
2640 p->setPen( TQPen( color, dim, TQt::SolidLine ) );
2641 break;
2642 case KoTextFormat::S_DASH:
2643 p->setPen( TQPen( color, dim, TQt::DashLine ) );
2644 break;
2645 case KoTextFormat::S_DOT:
2646 p->setPen( TQPen( color, dim, TQt::DotLine ) );
2647 break;
2648 case KoTextFormat::S_DASH_DOT:
2649 p->setPen( TQPen( color, dim, TQt::DashDotLine ) );
2650 break;
2651 case KoTextFormat::S_DASH_DOT_DOT:
2652 p->setPen( TQPen( color, dim, TQt::DashDotDotLine ) );
2653 break;
2654 default:
2655 p->setPen( TQPen( color, dim, TQt::SolidLine ) );
2656 }
2657
2658 y -= static_cast<int>(4*dimd);
2659 p->drawLine( startX, y, startX + bw, y);
2660 y -= static_cast<int>(2*dimd);
2661 p->drawLine( startX, y, startX + bw, y);
2662 p->restore();
2663 font.setStrikeOut( false );
2664 p->setFont( font );
2665 }
2666
2667}
2668
2669// ### is this method correct for RTL text?
2670TQString KoTextParag::toString( int from, int length ) const
2671{
2672 TQString str;
2673 if ( from == 0 && m_layout.counter && m_layout.counter->numbering() != KoParagCounter::NUM_FOOTNOTE )
2674 str += m_layout.counter->text( this ) + ' ';
2675 if ( length == -1 )
2676 length = this->length() - 1 /*trailing space*/ - from;
2677 for ( int i = from ; i < (length+from) ; ++i )
2678 {
2679 KoTextStringChar *ch = at( i );
2680 if ( ch->isCustom() )
2681 {
2682 KoVariable * var = dynamic_cast<KoVariable *>(ch->customItem());
2683 if ( var )
2684 str += var->text(true);
2685 else //frame inline
2686 str +=' ';
2687 }
2688 else
2689 str += ch->c;
2690 }
2691 return str;
2692}
2693
2694// we cannot use TQString::simplifyWhiteSpace() because it removes
2695// leading and trailing whitespace, but such whitespace is significant
2696// in ODF -- so we use this function to compress sequences of space characters
2697// into single spaces
2698static TQString normalizeWhitespace( const TQString& in, bool leadingSpace )
2699{
2700 TQString text = in;
2701 int r, w = 0;
2702 int len = text.length();
2703
2704 for ( r = 0; r < len; ++r )
2705 {
2706 TQCharRef ch = text[r];
2707
2708 // check for space, tab, line feed, carriage return
2709 if ( ch == ' ' || ch == '\t' ||ch == '\r' || ch == '\n')
2710 {
2711 // if we were lead by whitespace in some parent or previous sibling element,
2712 // we completely collapse this space
2713 if ( r != 0 || !leadingSpace )
2714 text[w++] = TQChar( ' ' );
2715
2716 // find the end of the whitespace run
2717 while ( r < len && text[r].isSpace() )
2718 ++r;
2719
2720 // and then record the next non-whitespace character
2721 if ( r < len )
2722 text[w++] = text[r];
2723 }
2724 else
2725 {
2726 text[w++] = ch;
2727 }
2728 }
2729
2730 // and now trim off the unused part of the string
2731 text.truncate(w);
2732
2733 return text;
2734}
2735
2736void KoTextParag::loadOasisSpan( const TQDomElement& parent, KoOasisContext& context, uint& pos, bool stripLeadingSpace )
2737{
2738 bool dummy;
2739
2740 return loadOasisSpan( parent, context, pos, stripLeadingSpace, &dummy );
2741}
2742
2743void KoTextParag::loadOasisSpan( const TQDomElement& parent, KoOasisContext& context, uint& pos, bool stripLeadingSpace, bool *hasTrailingSpace )
2744{
2745 // Parse every child node of the parent
2746 // Can't use forEachElement here since we also care about text nodes
2747 TQDomNode node;
2748 for ( node = parent.firstChild(); !node.isNull(); node = node.nextSibling() )
2749 {
2750 TQDomElement ts = node.toElement();
2751 TQString textData;
2752 const TQString localName( ts.localName() );
2753 const bool isTextNS = ts.namespaceURI() == KoXmlNS::text;
2754 KoTextCustomItem* customItem = 0;
2755
2756 // allow loadSpanTag to modify the stylestack
2757 context.styleStack().save();
2758
2759 // Try to keep the order of the tag names by probability of happening
2760 if ( node.isText() )
2761 {
2762 textData = normalizeWhitespace( node.toText().data(), stripLeadingSpace );
2763 *hasTrailingSpace = stripLeadingSpace = textData[textData.length() - 1].isSpace();
2764 }
2765 else if ( isTextNS && localName == "span" ) // text:span
2766 {
2767 context.styleStack().save();
2768 context.fillStyleStack( ts, KoXmlNS::text, "style-name", "text" );
2769
2770 // the ODF spec states that whitespace is compressed through tags: e.g.
2771 // "Foo <text:span> Bar </text:span> Baz"
2772 // should only have one space between each of "Foo", "Bar", and "Baz"
2773 // so we need to keep track of whether there was any trailing whitespace
2774 // in sub-spans so that we can propogate the whitespace compression state
2775 // back up to the parent element
2776 loadOasisSpan( ts, context, pos, stripLeadingSpace, hasTrailingSpace ); // recurse
2777 stripLeadingSpace = *hasTrailingSpace;
2778 context.styleStack().restore();
2779 }
2780 else if ( isTextNS && localName == "s" ) // text:s
2781 {
2782 int howmany = 1;
2783 if (ts.hasAttributeNS( KoXmlNS::text, "c"))
2784 howmany = ts.attributeNS( KoXmlNS::text, "c", TQString()).toInt();
2785
2786 textData.fill(32, howmany);
2787 }
2788 else if ( isTextNS && localName == "tab" ) // text:tab (it's tab-stop in OO-1.1 but tab in oasis)
2789 {
2790 textData = '\t';
2791 }
2792 else if ( isTextNS && localName == "line-break" ) // text:line-break
2793 {
2794 textData = '\n';
2795 }
2796 else if ( isTextNS && localName == "number" ) // text:number
2797 {
2798 // This is the number in front of a numbered paragraph,
2799 // written out to help export filters. We can ignore it.
2800 }
2801 else if ( node.isProcessingInstruction() )
2802 {
2803 TQDomProcessingInstruction pi = node.toProcessingInstruction();
2804 if ( pi.target() == "opendocument" && pi.data().startsWith( "cursor-position" ) )
2805 {
2806 context.setCursorPosition( this, pos );
2807 }
2808 }
2809 else
2810 {
2811 bool handled = false;
2812 // Check if it's a variable
2813 KoVariable* var = context.variableCollection().loadOasisField( textDocument(), ts, context );
2814 if ( var )
2815 {
2816 textData = "#"; // field placeholder
2817 customItem = var;
2818 handled = true;
2819 }
2820 if ( !handled )
2821 {
2822 handled = textDocument()->loadSpanTag( ts, context,
2823 this, pos,
2824 textData, customItem );
2825 if ( !handled )
2826 {
2827 kdWarning(32500) << "Ignoring tag " << ts.tagName() << endl;
2828 context.styleStack().restore();
2829 continue;
2830 }
2831 }
2832 }
2833
2834 const uint length = textData.length();
2835 if ( length )
2836 {
2837 insert( pos, textData );
2838 if ( customItem )
2839 setCustomItem( pos, customItem, 0 );
2840 KoTextFormat f;
2841 f.load( context );
2842 //kdDebug(32500) << "loadOasisSpan: applying formatting from " << pos << " to " << pos+length << "\n format=" << f.key() << endl;
2843 setFormat( pos, length, document()->formatCollection()->format( &f ), true );
2844 pos += length;
2845 }
2846 context.styleStack().restore();
2847 }
2848}
2849
2850KoParagLayout KoTextParag::loadParagLayout( KoOasisContext& context, KoStyleCollection *styleCollection, bool findStyle )
2851{
2852 KoParagLayout layout;
2853
2854 // Only when loading paragraphs, not when loading styles
2855 if ( findStyle )
2856 {
2857 KoParagStyle *style;
2858 // Name of the style. If there is no style, then we do not supply
2859 // any default!
2860 TQString styleName = context.styleStack().userStyleName( "paragraph" );
2861 if ( !styleName.isEmpty() )
2862 {
2863 style = styleCollection->findStyle( styleName );
2864 // When pasting the style names are random, the display names matter
2865 if (!style)
2866 style = styleCollection->findStyleByDisplayName( context.styleStack().userStyleDisplayName( "paragraph" ) );
2867 if (!style)
2868 {
2869 kdError(32500) << "Cannot find style \"" << styleName << "\" - using Standard" << endl;
2870 style = styleCollection->findStyle( "Standard" );
2871 }
2872 //else kdDebug() << "KoParagLayout::KoParagLayout setting style to " << style << " " << style->name() << endl;
2873 }
2874 else
2875 {
2876 kdError(32500) << "No style name !? - using Standard" << endl;
2877 style = styleCollection->findStyle( "Standard" );
2878 }
2879 Q_ASSERT(style);
2880 layout.style = style;
2881 }
2882
2883 KoParagLayout::loadOasisParagLayout( layout, context );
2884
2885 return layout;
2886}
2887
2888void KoTextParag::loadOasis( const TQDomElement& parent, KoOasisContext& context, KoStyleCollection *styleCollection, uint& pos )
2889{
2890 // First load layout from style
2891 KoParagLayout paragLayout = loadParagLayout( context, styleCollection, true );
2892 setParagLayout( paragLayout );
2893
2894 // Load paragraph format
2895 KoTextFormat defaultFormat;
2896 defaultFormat.load( context );
2897 setFormat( document()->formatCollection()->format( &defaultFormat ) );
2898
2899 // Load text
2900 // OO.o compatibility: ignore leading whitespace in <p> and <h> elements
2901 loadOasisSpan( parent, context, pos, true );
2902
2903 // Apply default format to trailing space
2904 const int len = str->length();
2905 Q_ASSERT( len >= 1 );
2906 setFormat( len - 1, 1, paragFormat(), true );
2907
2908 setChanged( true );
2909 invalidate( 0 );
2910}
2911
2912void KoTextParag::saveOasis( KoXmlWriter& writer, KoSavingContext& context,
2913 int from /* default 0 */, int to /* usually length()-2 */,
2914 bool /*saveAnchorsFramesets*/ /* default false */ ) const
2915{
2916 KoGenStyles& mainStyles = context.mainStyles();
2917
2918 // Write paraglayout to styles (with parent == the parag's style)
2919 TQString parentStyleName;
2920 if ( m_layout.style )
2921 parentStyleName = m_layout.style->name();
2922
2923 KoGenStyle autoStyle( KoGenStyle::STYLE_AUTO, "paragraph", parentStyleName );
2924 paragFormat()->save( autoStyle, context );
2925 m_layout.saveOasis( autoStyle, context, false );
2926
2927 // First paragraph is special, it includes page-layout info (for word-processing at least)
2928 if ( !prev() ) {
2929 if ( context.variableSettings() )
2930 autoStyle.addProperty( "style:page-number", context.variableSettings()->startingPageNumber() );
2931 // Well we support only one page layout, so the first parag always points to "Standard".
2932 autoStyle.addAttribute( "style:master-page-name", "Standard" );
2933 }
2934
2935
2936 TQString autoParagStyleName = mainStyles.lookup( autoStyle, "P", KoGenStyles::ForceNumbering );
2937
2938 KoParagCounter* paragCounter = m_layout.counter;
2939 // outline (text:h) assumes paragCounter != 0 (because depth is mandatory)
2940 bool outline = m_layout.style && m_layout.style->isOutline() && paragCounter;
2941 bool normalList = paragCounter && paragCounter->style() != KoParagCounter::STYLE_NONE && !outline;
2942 if ( normalList ) // non-heading list
2943 {
2944 writer.startElement( "text:numbered-paragraph" );
2945 writer.addAttribute( "text:level", (int)paragCounter->depth() + 1 );
2946 if ( paragCounter->restartCounter() )
2947 writer.addAttribute( "text:start-value", paragCounter->startNumber() );
2948
2949 KoGenStyle listStyle( KoGenStyle::STYLE_AUTO_LIST /*, no family*/ );
2950 paragCounter->saveOasis( listStyle );
2951
2952 TQString autoListStyleName = mainStyles.lookup( listStyle, "L", KoGenStyles::ForceNumbering );
2953 writer.addAttribute( "text:style-name", autoListStyleName );
2954
2955 TQString textNumber = m_layout.counter->text( this );
2956 if ( !textNumber.isEmpty() )
2957 {
2958 // This is to help export filters
2959 writer.startElement( "text:number" );
2960 writer.addTextNode( textNumber );
2961 writer.endElement();
2962 }
2963 }
2964 else if ( outline ) // heading
2965 {
2966 writer.startElement( "text:h", false /*no indent inside this tag*/ );
2967 writer.addAttribute( "text:style-name", autoParagStyleName );
2968 writer.addAttribute( "text:outline-level", (int)paragCounter->depth() + 1 );
2969 if ( paragCounter->numbering() == KoParagCounter::NUM_NONE )
2970 writer.addAttribute( "text:is-list-header", "true" );
2971
2972 TQString textNumber = paragCounter->text( this );
2973 if ( !textNumber.isEmpty() )
2974 {
2975 // This is to help export filters
2976 writer.startElement( "text:number" );
2977 writer.addTextNode( textNumber );
2978 writer.endElement();
2979 }
2980 }
2981
2982 if ( !outline ) // normal (non-numbered) paragraph, or normalList
2983 {
2984 writer.startElement( "text:p", false /*no indent inside this tag*/ );
2985 writer.addAttribute( "text:style-name", autoParagStyleName );
2986 }
2987
2988 TQString text = str->toString();
2989 Q_ASSERT( text.right(1)[0] == ' ' );
2990
2991 const int cursorIndex = context.cursorTextParagraph() == this ? context.cursorTextIndex() : -1;
2992
2993 //kdDebug() << k_funcinfo << "'" << text << "' from=" << from << " to=" << to << " cursorIndex=" << cursorIndex << endl;
2994
2995 // A helper method would need no less than 7 params...
2996#define WRITESPAN( next ) { \
2997 if ( curFormat == paragFormat() ) { \
2998 writer.addTextSpan( text.mid( startPos, next - startPos ), m_tabCache ); \
2999 } else { \
3000 KoGenStyle gs( KoGenStyle::STYLE_AUTO, "text" ); \
3001 curFormat->save( gs, context, paragFormat() ); \
3002 writer.startElement( "text:span" ); \
3003 if ( !gs.isEmpty() ) { \
3004 const TQString autoStyleName = mainStyles.lookup( gs, "T" ); \
3005 writer.addAttribute( "text:style-name", autoStyleName ); \
3006 } \
3007 writer.addTextSpan( text.mid( startPos, next - startPos ), m_tabCache ); \
3008 writer.endElement(); \
3009 } \
3010 }
3011#define ISSTARTBOOKMARK( i ) bkStartIter != bookmarkStarts.end() && (*bkStartIter).pos == i
3012#define ISENDBOOKMARK( i ) bkEndIter != bookmarkEnds.end() && (*bkEndIter).pos == i
3013#define CHECKPOS( i ) \
3014 if ( cursorIndex == i ) { \
3015 writer.addProcessingInstruction( "opendocument cursor-position" ); \
3016 } \
3017 if ( ISSTARTBOOKMARK( i ) ) { \
3018 if ( (*bkStartIter).startEqualsEnd ) \
3019 writer.startElement( "text:bookmark" ); \
3020 else \
3021 writer.startElement( "text:bookmark-start" ); \
3022 writer.addAttribute( "text:name", (*bkStartIter).name ); \
3023 writer.endElement(); \
3024 ++bkStartIter; \
3025 } \
3026 if ( ISENDBOOKMARK( i ) ) { \
3027 writer.startElement( "text:bookmark-end" ); \
3028 writer.addAttribute( "text:name", (*bkEndIter).name ); \
3029 writer.endElement(); \
3030 ++bkEndIter; \
3031 }
3032
3033
3034
3035 // Make (shallow) copy of bookmark list, since saving an inline frame might overwrite it
3036 // from the context while we're saving this paragraph.
3037 typedef KoSavingContext::BookmarkPositions BookmarkPositions;
3038 BookmarkPositions bookmarkStarts = context.bookmarkStarts();
3039 BookmarkPositions::const_iterator bkStartIter = bookmarkStarts.begin();
3040 while ( bkStartIter != bookmarkStarts.end() && (*bkStartIter).pos < from )
3041 ++bkStartIter;
3042 //int nextBookmarkStart = bkStartIter == bookmarkStarts.end() ? -1 : (*bkStartIter).pos;
3043 BookmarkPositions bookmarkEnds = context.bookmarkEnds();
3044 BookmarkPositions::const_iterator bkEndIter = bookmarkEnds.begin();
3045 while ( bkEndIter != bookmarkEnds.end() && (*bkEndIter).pos < from )
3046 ++bkEndIter;
3047
3048 KoTextFormat *curFormat = 0;
3049 KoTextFormat *lastFormatRaw = 0; // this is for speeding up "removing misspelled" from each char
3050 KoTextFormat *lastFormatFixed = 0; // raw = as stored in the chars; fixed = after removing misspelled
3051 int startPos = from;
3052 for ( int i = from; i <= to; ++i ) {
3053 KoTextStringChar & ch = str->at(i);
3054 KoTextFormat * newFormat = static_cast<KoTextFormat *>( ch.format() );
3055 if ( newFormat->isMisspelled() ) {
3056 if ( newFormat == lastFormatRaw )
3057 newFormat = lastFormatFixed; // the fast way
3058 else
3059 {
3060 lastFormatRaw = newFormat;
3061 // Remove isMisspelled from format, to avoid useless derived styles
3062 // (which would be indentical to their parent style)
3063 KoTextFormat tmpFormat( *newFormat );
3064 tmpFormat.setMisspelled( false );
3065 newFormat = formatCollection()->format( &tmpFormat );
3066 lastFormatFixed = newFormat;
3067 }
3068 }
3069 if ( !curFormat )
3070 curFormat = newFormat;
3071 if ( newFormat != curFormat // Format changed, save previous one.
3072 || ch.isCustom() || cursorIndex == i || ISSTARTBOOKMARK( i ) || ISENDBOOKMARK( i ) )
3073 {
3074 WRITESPAN( i ) // write text up to i-1
3075 startPos = i;
3076 curFormat = newFormat;
3077 }
3078 CHECKPOS( i ) // do cursor position and bookmarks
3079 if ( ch.isCustom() ) {
3080 KoGenStyle gs( KoGenStyle::STYLE_AUTO, "text" );
3081 curFormat->save( gs, context, paragFormat() );
3082 writer.startElement( "text:span" );
3083 if ( !gs.isEmpty() ) {
3084 const TQString autoStyleName = mainStyles.lookup( gs, "T" );
3085 writer.addAttribute( "text:style-name", autoStyleName );
3086 }
3087 KoTextCustomItem* customItem = ch.customItem();
3088 customItem->saveOasis( writer, context );
3089 writer.endElement();
3090 startPos = i + 1;
3091 }
3092 }
3093
3094 //kdDebug() << k_funcinfo << "startPos=" << startPos << " to=" << to << " curFormat=" << curFormat << endl;
3095
3096 if ( to >= startPos ) { // Save last format
3097 WRITESPAN( to + 1 )
3098 }
3099 CHECKPOS( to + 1 ) // do cursor position and bookmarks
3100
3101 writer.endElement(); // text:p or text:h
3102 if ( normalList )
3103 writer.endElement(); // text:numbered-paragraph (englobing a text:p)
3104}
3105
3106void KoTextParag::applyListStyle( KoOasisContext& context, int restartNumbering, bool orderedList, bool heading, int level )
3107{
3108 //kdDebug(32500) << k_funcinfo << "applyListStyle to parag " << this << " heading=" << heading << endl;
3109 delete m_layout.counter;
3110 m_layout.counter = new KoParagCounter;
3111 m_layout.counter->loadOasis( context, restartNumbering, orderedList, heading, level );
3112 // We emulate space-before with a left paragraph indent (#109223)
3113 const TQDomElement listStyleProperties = context.listStyleStack().currentListStyleProperties();
3114 if ( listStyleProperties.hasAttributeNS( KoXmlNS::text, "space-before" ) )
3115 {
3116 double spaceBefore = KoUnit::parseValue( listStyleProperties.attributeNS( KoXmlNS::text, "space-before", TQString() ) );
3117 m_layout.margins[ TQStyleSheetItem::MarginLeft ] += spaceBefore; // added to left-margin, see 15.12 in spec.
3118 }
3119 // need to call invalidateCounters() ? Not during the initial loading at least.
3120}
3121
3122int KoTextParag::documentWidth() const
3123{
3124 return doc ? doc->width() : 0; //docRect.width();
3125}
3126
3127//int KoTextParag::documentVisibleWidth() const
3128//{
3129// return doc ? doc->visibleWidth() : 0; //docRect.width();
3130//}
3131
3132int KoTextParag::documentX() const
3133{
3134 return doc ? doc->x() : 0; //docRect.x();
3135}
3136
3137int KoTextParag::documentY() const
3138{
3139 return doc ? doc->y() : 0; //docRect.y();
3140}
3141
3142void KoTextParag::fixParagWidth( bool viewFormattingChars )
3143{
3144 // Fixing the parag rect for the formatting chars (only CR here, KWord handles framebreak).
3145 if ( viewFormattingChars && lineStartList().count() == 1 ) // don't use lines() here, parag not formatted yet
3146 {
3147 KoTextFormat * lastFormat = at( length() - 1 )->format();
3148 setWidth( TQMIN( rect().width() + lastFormat->width('x'), doc->width() ) );
3149 }
3150 // Warning, if adding anything else here, adjust KWTextFrameSet::fixParagWidth
3151}
3152
3153// Called by KoTextParag::drawParagString - all params are in pixel coordinates
3154void KoTextParag::drawFormattingChars( TQPainter &painter, int start, int len,
3155 int lastY_pix, int baseLine_pix, int h_pix, // in pixels
3156 bool /*drawSelections*/,
3157 KoTextFormat * /*lastFormat*/, const TQMemArray<int> &/*selectionStarts*/,
3158 const TQMemArray<int> &/*selectionEnds*/, const TQColorGroup & /*cg*/,
3159 bool rightToLeft, int /*line*/, KoTextZoomHandler* zh,
3160 int whichFormattingChars )
3161{
3162 if ( !whichFormattingChars )
3163 return;
3164 painter.save();
3165 //TQPen pen( cg.color( TQColorGroup::Highlight ) );
3166 TQPen pen( TDEGlobalSettings::linkColor() ); // #101820
3167 painter.setPen( pen );
3168 //kdDebug() << "KWTextParag::drawFormattingChars start=" << start << " len=" << len << " length=" << length() << endl;
3169 if ( start + len == length() && ( whichFormattingChars & FormattingEndParag ) )
3170 {
3171 // drawing the end of the parag
3172 KoTextStringChar &ch = str->at( length() - 1 );
3173 KoTextFormat* format = static_cast<KoTextFormat *>( ch.format() );
3174 int w = format->charWidth( zh, true, &ch, this, 'X' );
3175 int size = TQMIN( w, h_pix * 3 / 4 );
3176 // x,y is the bottom right corner of the
3177 //kdDebug() << "startX=" << startX << " bw=" << bw << " w=" << w << endl;
3178 int x;
3179 if ( rightToLeft )
3180 x = zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ + ch.pixelwidth - 1;
3181 else
3182 x = zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ + w;
3183 int y = lastY_pix + baseLine_pix;
3184 //kdDebug() << "KWTextParag::drawFormattingChars drawing CR at " << x << "," << y << endl;
3185 painter.drawLine( (int)(x - size * 0.2), y - size, (int)(x - size * 0.2), y );
3186 painter.drawLine( (int)(x - size * 0.5), y - size, (int)(x - size * 0.5), y );
3187 painter.drawLine( x, y, (int)(x - size * 0.7), y );
3188 painter.drawLine( x, y - size, (int)(x - size * 0.5), y - size);
3189 painter.drawArc( x - size, y - size, size, (int)(size / 2), -90*16, -180*16 );
3190#ifdef DEBUG_FORMATTING
3191 painter.setPen( TQt::blue );
3192 painter.drawRect( zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ - 1, lastY_pix, ch.pixelwidth, baseLine_pix );
3193 TQPen pen( cg.color( TQColorGroup::Highlight ) );
3194 painter.setPen( pen );
3195#endif
3196 }
3197
3198 // Now draw spaces, tabs and newlines
3199 if ( (whichFormattingChars & FormattingSpace) ||
3200 (whichFormattingChars & FormattingTabs) ||
3201 (whichFormattingChars & FormattingBreak) )
3202 {
3203 int end = TQMIN( start + len, length() - 1 ); // don't look at the trailing space
3204 for ( int i = start ; i < end ; ++i )
3205 {
3206 KoTextStringChar &ch = str->at(i);
3207#ifdef DEBUG_FORMATTING
3208 painter.setPen( (i % 2)? TQt::red: TQt::green );
3209 painter.drawRect( zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ - 1, lastY_pix, ch.pixelwidth, baseLine_pix );
3210 TQPen pen( cg.color( TQColorGroup::Highlight ) );
3211 painter.setPen( pen );
3212#endif
3213 if ( ch.isCustom() )
3214 continue;
3215 if ( (ch.c == ' ' || ch.c.unicode() == 0x00a0U)
3216 && (whichFormattingChars & FormattingSpace))
3217 {
3218 // Don't use ch.pixelwidth here. We want a square with
3219 // the same size for all spaces, even the justified ones
3220 int w = zh->layoutUnitToPixelX( ch.format()->width( ' ' ) );
3221 int height = zh->layoutUnitToPixelY( ch.ascent() );
3222 int size = TQMAX( 2, TQMIN( w/2, height/3 ) ); // Enfore that it's a square, and that it's visible
3223 int x = zh->layoutUnitToPixelX( ch.x ); // + ch.pixelxadj;
3224 TQRect spcRect( x + (ch.pixelwidth - size) / 2, lastY_pix + baseLine_pix - (height - size) / 2, size, size );
3225 if ( ch.c == ' ' )
3226 painter.drawRect( spcRect );
3227 else // nbsp
3228 painter.fillRect( spcRect, pen.color() );
3229 }
3230 else if ( ch.c == '\t' && (whichFormattingChars & FormattingTabs) )
3231 {
3232 /*KoTextStringChar &nextch = str->at(i+1);
3233 int nextx = (nextch.x > ch.x) ? nextch.x : rect().width();
3234 //kdDebug() << "tab x=" << ch.x << " nextch.x=" << nextch.x
3235 // << " nextx=" << nextx << " startX=" << startX << " bw=" << bw << endl;
3236 int availWidth = nextx - ch.x - 1;
3237 availWidth=zh->layoutUnitToPixelX(availWidth);*/
3238
3239 int availWidth = ch.pixelwidth;
3240
3241 KoTextFormat* format = ch.format();
3242 int x = zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ + availWidth / 2;
3243 int charWidth = format->screenFontMetrics( zh ).width( 'W' );
3244 int size = TQMIN( availWidth, charWidth ) / 2 ; // actually the half size
3245 int y = lastY_pix + baseLine_pix - zh->layoutUnitToPixelY( ch.ascent()/2 );
3246 int arrowsize = zh->zoomItY( 2 );
3247 painter.drawLine( x - size, y, x + size, y );
3248 if ( rightToLeft )
3249 {
3250 painter.drawLine( x - size, y, x - size + arrowsize, y - arrowsize );
3251 painter.drawLine( x - size, y, x - size + arrowsize, y + arrowsize );
3252 }
3253 else
3254 {
3255 painter.drawLine( x + size, y, x + size - arrowsize, y - arrowsize );
3256 painter.drawLine( x + size, y, x + size - arrowsize, y + arrowsize );
3257 }
3258 }
3259 else if ( ch.c == '\n' && (whichFormattingChars & FormattingBreak) )
3260 {
3261 // draw line break
3262 KoTextFormat* format = static_cast<KoTextFormat *>( ch.format() );
3263 int w = format->charWidth( zh, true, &ch, this, 'X' );
3264 int size = TQMIN( w, h_pix * 3 / 4 );
3265 int arrowsize = zh->zoomItY( 2 );
3266 // x,y is the bottom right corner of the reversed L
3267 //kdDebug() << "startX=" << startX << " bw=" << bw << " w=" << w << endl;
3268 int y = lastY_pix + baseLine_pix - arrowsize;
3269 //kdDebug() << "KWTextParag::drawFormattingChars drawing Line Break at " << x << "," << y << endl;
3270 if ( rightToLeft )
3271 {
3272 int x = zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ + ch.pixelwidth - 1;
3273 painter.drawLine( x - size, y - size, x - size, y );
3274 painter.drawLine( x - size, y, (int)(x - size * 0.3), y );
3275 // Now the arrow
3276 painter.drawLine( (int)(x - size * 0.3), y, (int)(x - size * 0.3 - arrowsize), y - arrowsize );
3277 painter.drawLine( (int)(x - size * 0.3), y, (int)(x - size * 0.3 - arrowsize), y + arrowsize );
3278 }
3279 else
3280 {
3281 int x = zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ + w - 1;
3282 painter.drawLine( x, y - size, x, y );
3283 painter.drawLine( x, y, (int)(x - size * 0.7), y );
3284 // Now the arrow
3285 painter.drawLine( (int)(x - size * 0.7), y, (int)(x - size * 0.7 + arrowsize), y - arrowsize );
3286 painter.drawLine( (int)(x - size * 0.7), y, (int)(x - size * 0.7 + arrowsize), y + arrowsize );
3287 }
3288 }
3289 }
3290 painter.restore();
3291 }
3292}
3293
3294int KoTextParag::heightForLineSpacing( int startChar, int lastChar ) const
3295{
3296 int h = 0;
3297 int end = TQMIN( lastChar, length() - 1 ); // don't look at the trailing space
3298 for( int i = startChar; i <= end; ++i )
3299 {
3300 const KoTextStringChar &chr = str->at( i );
3301 if ( !chr.isCustom() )
3302 h = TQMAX( h, chr.format()->height() );
3303 }
3304 return h;
3305}
This class represents a border - for anything, like paragraph, or frame.
Definition KoBorder.h:34
const KoTextFormat & format() const
Return a format.
A generic style, i.e.
Repository of styles used during saving of OASIS/OOo file.
Definition KoGenStyles.h:60
const KoGenStyle * style(const TQString &name) const
TQString lookup(const KoGenStyle &style, const TQString &name=TQString(), int flags=NoFlag)
Look up a style in the collection, inserting it if necessary.
Used during loading of Oasis format (and discarded at the end of the loading).
void setCursorPosition(KoTextParag *cursorTextParagraph, int cursorTextIndex)
Set cursor position (set by KoTextParag upon finding the processing instruction)
void fillStyleStack(const TQDomElement &element, const char *nsURI, const char *attrName, const char *family)
Convenience method for loading the style of an object before loading that object.
This is the structure associated with a paragraph (KoTextParag), to define the bullet or numbering of...
unsigned int depth() const
The level of the numbering.
void loadOasis(KoOasisContext &context, int restartNumbering, bool orderedList, bool heading, int level, bool loadingStyle=false)
Load from OASIS XML.
void saveOasis(KoGenStyle &listStyle, bool savingStyle=false) const
Save as OASIS XML.
Numbering numbering() const
Numbering type and style.
int startNumber() const
Starting number.
bool restartCounter() const
Should this counter start at "startNumber" (instead of being the 'last counter of the same type + 1')
This class holds the paragraph-specific formatting information It's separated from KoTextParag so tha...
static void loadOasisParagLayout(KoParagLayout &layout, KoOasisContext &context)
Load this parag layout from Oasis XML.
SpacingType
Line spacing values Assuming h is the height of the biggest char in the line, and v is the linespacin...
TQColor backgroundColor
The background color of the paragraph.
double margins[5]
left, right, top, bottom, firstLineSpacing - in pt
KoParagCounter * counter
can be 0 if no counter set
char alignment
Alignment flag (AlignAuto/AlignLeft/AlignRight/AlignJustify)
A paragraph style is a combination of a character style and paragraph-layout attributes,...
Used during saving to Oasis format (and discarded at the end of the saving).
TODO rename to KoParagStyleCollection - or should char styles be part of it too?
KoParagStyle * findStyleByDisplayName(const TQString &name) const
Find style based on the display name displayName.
KoParagStyle * findStyle(const TQString &name) const
Find style based on the internal name name.
void save()
Save the current state of the stack.
void restore()
Restore the stack to the state it was at the corresponding save() call.
TQString userStyleDisplayName(const TQString &family) const
Return the display name of the style specified by the user, i.e.
TQString userStyleName(const TQString &family) const
Return the name of the style specified by the user, i.e.
KoTextCustomItem is the base class for custom items (i.e.
virtual void saveOasis(KoXmlWriter &writer, KoSavingContext &context) const =0
Save to Oasis XML.
virtual void recalc()
Reimplemented by KoVariable to recalculate the value.
Each character (KoTextStringChar) points to a KoTextFormat that defines the formatting of that charac...
void save(KoGenStyle &gs, KoSavingContext &context, KoTextFormat *refFormat=0) const
Save a text format to OASIS XML Only saves what differs from @refFormat, if set.
const TQFontMetrics & screenFontMetrics(const KoTextZoomHandler *zh) const
Returns the font metrics for the font used at the zoom & resolution given by 'zh'.
int shadowY(KoTextZoomHandler *zh) const
Return the amount of pixels for the vertical shadow distance at a given zoom level.
int charWidth(const KoTextZoomHandler *zh, bool applyZoom, const KoTextStringChar *c, const KoTextParag *parag, int i) const
Return the width of one char in one paragraph.
void load(KoOasisContext &context)
Load a text format from OASIS XML.
int width(const TQChar &c) const
Return the width of one char (from a string, not necessarily from a paragraph) in LU pixels.
int shadowX(KoTextZoomHandler *zh) const
Return the amount of pixels for the horizontal shadow distance at a given zoom level.
TQFont screenFont(const KoTextZoomHandler *zh) const
Returns the font to be used at the zoom & resolution given by 'zh'.
static TQColor defaultTextColor(TQPainter *painter)
Call this when a text color is set to 'invalid', meaning 'whatever the default for the color scheme i...
This class extends KoZoomHandler to add support for WYSIWYG text layouting.
static double ptToLayoutUnitPt(double pt)
Not zoom dependent.
int ptToLayoutUnitPixX(double x_pt) const
The "document pt" -> "Layout Unit pixels" conversions, for convenience.
int layoutUnitToPixelX(int lupix) const
The "layout unit pixel" -> "[zoomed] view pixel" conversions.
static double parseValue(TQString value, double defaultVal=0.0)
parse common KOffice and OO values, like "10cm", "5mm" to pt
Definition KoUnit.cpp:148
TQString name() const
The internal name (used for loading/saving, but not shown to the user) Should be unique in a given st...
Definition KoUserStyle.h:40
A KoVariable is a custom item, i.e.
Definition KoVariable.h:364
virtual TQString text(bool realValue=false)
Returns the text to be displayed for this variable It doesn't need to be cached, convert() is fast,...
A class for writing out XML (to any TQIODevice), with a special attention on performance.
Definition KoXmlWriter.h:37
void startElement(const char *tagName, bool indentInside=true)
Start a new element, as a child of the current element.
void addTextNode(const TQString &str)
Overloaded version of addTextNode( const char* ), which is a bit slower because it needs to convert s...
void endElement()
Terminate the current element.
void addAttribute(const char *attrName, const TQString &value)
Overloaded version of addAttribute( const char*, const char* ), which is a bit slower because it need...
Definition KoXmlWriter.h:76
TDEAction * next(const TQObject *recvr, const char *slot, TDEActionCollection *parent, const char *name=0)
Struct: KoTabulator Defines the position of a tabulation (in pt), and its type.
Definition KoRuler.h:46
KoTabulatorFilling filling
Type of tab filling.
Definition KoRuler.h:58
double ptWidth
Width of the tab filling line.
Definition KoRuler.h:62