summaryrefslogtreecommitdiff
path: root/noncore/apps/opie-write/qrichtext.cpp
Unidiff
Diffstat (limited to 'noncore/apps/opie-write/qrichtext.cpp') (more/less context) (ignore whitespace changes)
-rw-r--r--noncore/apps/opie-write/qrichtext.cpp8085
1 files changed, 8085 insertions, 0 deletions
diff --git a/noncore/apps/opie-write/qrichtext.cpp b/noncore/apps/opie-write/qrichtext.cpp
new file mode 100644
index 0000000..7901000
--- a/dev/null
+++ b/noncore/apps/opie-write/qrichtext.cpp
@@ -0,0 +1,8085 @@
1/****************************************************************************
2** $Id$
3**
4** Implementation of the internal Qt classes dealing with rich text
5**
6** Created : 990101
7**
8** Copyright (C) 1992-2000 Trolltech AS. All rights reserved.
9**
10** This file is part of the kernel module of the Qt GUI Toolkit.
11**
12** This file may be distributed under the terms of the Q Public License
13** as defined by Trolltech AS of Norway and appearing in the file
14** LICENSE.QPL included in the packaging of this file.
15**
16** This file may be distributed and/or modified under the terms of the
17** GNU General Public License version 2 as published by the Free Software
18** Foundation and appearing in the file LICENSE.GPL included in the
19** packaging of this file.
20**
21** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
22** licenses may use this file in accordance with the Qt Commercial License
23** Agreement provided with the Software.
24**
25** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
26** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
27**
28** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
29** information about Qt Commercial License Agreements.
30** See http://www.trolltech.com/qpl/ for QPL licensing information.
31** See http://www.trolltech.com/gpl/ for GPL licensing information.
32**
33** Contact info@trolltech.com if any conditions of this licensing are
34** not clear to you.
35**
36**********************************************************************/
37
38#include "qrichtext_p.h"
39
40#include "qstringlist.h"
41#include "qfont.h"
42#include "qtextstream.h"
43#include "qfile.h"
44#include "qregexp.h"
45#include "qapplication.h"
46#include "qclipboard.h"
47#include "qmap.h"
48#include "qfileinfo.h"
49#include "qstylesheet.h"
50#include "qmime.h"
51#include "qregexp.h"
52#include "qimage.h"
53#include "qdragobject.h"
54#include "qpaintdevicemetrics.h"
55#include "qpainter.h"
56#include "qdrawutil.h"
57#include "qcursor.h"
58#include "qstack.h"
59#include "qstyle.h"
60#include "qcomplextext_p.h"
61#include "qcleanuphandler.h"
62
63#include <stdlib.h>
64
65using namespace Qt3;
66
67//#define PARSER_DEBUG
68//#define DEBUG_COLLECTION// ---> also in qrichtext_p.h
69//#define DEBUG_TABLE_RENDERING
70
71static QTextFormatCollection *qFormatCollection = 0;
72
73const int QStyleSheetItem_WhiteSpaceNoCompression = 3; // ### belongs in QStyleSheetItem, fix 3.1
74const int QStyleSheetItem_WhiteSpaceNormalWithNewlines = 4; // ### belongs in QStyleSheetItem, fix 3.1
75
76const int border_tolerance = 2;
77
78#if defined(PARSER_DEBUG)
79static QString debug_indent;
80#endif
81
82#ifdef Q_WS_WIN
83#include "qt_windows.h"
84#endif
85
86static inline bool is_printer( QPainter *p )
87{
88 if ( !p || !p->device() )
89 return FALSE;
90 return p->device()->devType() == QInternal::Printer;
91}
92
93static inline int scale( int value, QPainter *painter )
94{
95 if ( is_printer( painter ) ) {
96 QPaintDeviceMetrics metrics( painter->device() );
97#if defined(Q_WS_X11)
98 value = value * metrics.logicalDpiY() / QPaintDevice::x11AppDpiY();
99#elif defined (Q_WS_WIN)
100 HDC hdc = GetDC( 0 );
101 int gdc = GetDeviceCaps( hdc, LOGPIXELSY );
102 if ( gdc )
103 value = value * metrics.logicalDpiY() / gdc;
104 ReleaseDC( 0, hdc );
105#elif defined (Q_WS_MAC)
106 value = value * metrics.logicalDpiY() / 75; // ##### FIXME
107#elif defined (Q_WS_QWS)
108 value = value * metrics.logicalDpiY() / 75;
109#endif
110 }
111 return value;
112}
113
114// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
115
116void QTextCommandHistory::addCommand( QTextCommand *cmd )
117{
118 if ( current < (int)history.count() - 1 ) {
119 QPtrList<QTextCommand> commands;
120 commands.setAutoDelete( FALSE );
121
122 for( int i = 0; i <= current; ++i ) {
123 commands.insert( i, history.at( 0 ) );
124 history.take( 0 );
125 }
126
127 commands.append( cmd );
128 history.clear();
129 history = commands;
130 history.setAutoDelete( TRUE );
131 } else {
132 history.append( cmd );
133 }
134
135 if ( (int)history.count() > steps )
136 history.removeFirst();
137 else
138 ++current;
139}
140
141QTextCursor *QTextCommandHistory::undo( QTextCursor *c )
142{
143 if ( current > -1 ) {
144 QTextCursor *c2 = history.at( current )->unexecute( c );
145 --current;
146 return c2;
147 }
148 return 0;
149}
150
151QTextCursor *QTextCommandHistory::redo( QTextCursor *c )
152{
153 if ( current > -1 ) {
154 if ( current < (int)history.count() - 1 ) {
155 ++current;
156 return history.at( current )->execute( c );
157 }
158 } else {
159 if ( history.count() > 0 ) {
160 ++current;
161 return history.at( current )->execute( c );
162 }
163 }
164 return 0;
165}
166
167bool QTextCommandHistory::isUndoAvailable()
168{
169 return current > -1;
170}
171
172bool QTextCommandHistory::isRedoAvailable()
173{
174 return current > -1 && current < (int)history.count() - 1 || current == -1 && history.count() > 0;
175}
176
177// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
178
179QTextDeleteCommand::QTextDeleteCommand( QTextDocument *d, int i, int idx, const QMemArray<QTextStringChar> &str,
180 const QValueList< QPtrVector<QStyleSheetItem> > &os,
181 const QValueList<QStyleSheetItem::ListStyle> &ols,
182 const QMemArray<int> &oas)
183 : QTextCommand( d ), id( i ), index( idx ), parag( 0 ), text( str ), oldStyles( os ), oldListStyles( ols ), oldAligns( oas )
184{
185 for ( int j = 0; j < (int)text.size(); ++j ) {
186 if ( text[ j ].format() )
187 text[ j ].format()->addRef();
188 }
189}
190
191QTextDeleteCommand::QTextDeleteCommand( QTextParag *p, int idx, const QMemArray<QTextStringChar> &str )
192 : QTextCommand( 0 ), id( -1 ), index( idx ), parag( p ), text( str )
193{
194 for ( int i = 0; i < (int)text.size(); ++i ) {
195 if ( text[ i ].format() )
196 text[ i ].format()->addRef();
197 }
198}
199
200QTextDeleteCommand::~QTextDeleteCommand()
201{
202 for ( int i = 0; i < (int)text.size(); ++i ) {
203 if ( text[ i ].format() )
204 text[ i ].format()->removeRef();
205 }
206 text.resize( 0 );
207}
208
209QTextCursor *QTextDeleteCommand::execute( QTextCursor *c )
210{
211 QTextParag *s = doc ? doc->paragAt( id ) : parag;
212 if ( !s ) {
213 qWarning( "can't locate parag at %d, last parag: %d", id, doc->lastParag()->paragId() );
214 return 0;
215 }
216
217 cursor.setParag( s );
218 cursor.setIndex( index );
219 int len = text.size();
220 if ( c )
221 *c = cursor;
222 if ( doc ) {
223 doc->setSelectionStart( QTextDocument::Temp, &cursor );
224 for ( int i = 0; i < len; ++i )
225 cursor.gotoNextLetter();
226 doc->setSelectionEnd( QTextDocument::Temp, &cursor );
227 doc->removeSelectedText( QTextDocument::Temp, &cursor );
228 if ( c )
229 *c = cursor;
230 } else {
231 s->remove( index, len );
232 }
233
234 return c;
235}
236
237QTextCursor *QTextDeleteCommand::unexecute( QTextCursor *c )
238{
239 QTextParag *s = doc ? doc->paragAt( id ) : parag;
240 if ( !s ) {
241 qWarning( "can't locate parag at %d, last parag: %d", id, doc->lastParag()->paragId() );
242 return 0;
243 }
244
245 cursor.setParag( s );
246 cursor.setIndex( index );
247 QString str = QTextString::toString( text );
248 cursor.insert( str, TRUE, &text );
249 cursor.setParag( s );
250 cursor.setIndex( index );
251 if ( c ) {
252 c->setParag( s );
253 c->setIndex( index );
254 for ( int i = 0; i < (int)text.size(); ++i )
255 c->gotoNextLetter();
256 }
257
258 QValueList< QPtrVector<QStyleSheetItem> >::Iterator it = oldStyles.begin();
259 QValueList<QStyleSheetItem::ListStyle>::Iterator lit = oldListStyles.begin();
260 int i = 0;
261 QTextParag *p = s;
262 bool end = FALSE;
263 while ( p ) {
264 if ( it != oldStyles.end() )
265 p->setStyleSheetItems( *it );
266 else
267 end = TRUE;
268 if ( lit != oldListStyles.end() )
269 p->setListStyle( *lit );
270 else
271 end = TRUE;
272 if ( i < (int)oldAligns.size() )
273 p->setAlignment( oldAligns.at( i ) );
274 else
275 end = TRUE;
276 if ( end )
277 break;
278 p = p->next();
279 ++it;
280 ++lit;
281 ++i;
282 }
283
284 s = cursor.parag();
285 while ( s ) {
286 s->format();
287 s->setChanged( TRUE );
288 if ( s == c->parag() )
289 break;
290 s = s->next();
291 }
292
293 return &cursor;
294}
295
296QTextFormatCommand::QTextFormatCommand( QTextDocument *d, int sid, int sidx, int eid, int eidx,
297 const QMemArray<QTextStringChar> &old, QTextFormat *f, int fl )
298 : QTextCommand( d ), startId( sid ), startIndex( sidx ), endId( eid ), endIndex( eidx ), format( f ), oldFormats( old ), flags( fl )
299{
300 format = d->formatCollection()->format( f );
301 for ( int j = 0; j < (int)oldFormats.size(); ++j ) {
302 if ( oldFormats[ j ].format() )
303 oldFormats[ j ].format()->addRef();
304 }
305}
306
307QTextFormatCommand::~QTextFormatCommand()
308{
309 format->removeRef();
310 for ( int j = 0; j < (int)oldFormats.size(); ++j ) {
311 if ( oldFormats[ j ].format() )
312 oldFormats[ j ].format()->removeRef();
313 }
314}
315
316QTextCursor *QTextFormatCommand::execute( QTextCursor *c )
317{
318 QTextParag *sp = doc->paragAt( startId );
319 QTextParag *ep = doc->paragAt( endId );
320 if ( !sp || !ep )
321 return c;
322
323 QTextCursor start( doc );
324 start.setParag( sp );
325 start.setIndex( startIndex );
326 QTextCursor end( doc );
327 end.setParag( ep );
328 end.setIndex( endIndex );
329
330 doc->setSelectionStart( QTextDocument::Temp, &start );
331 doc->setSelectionEnd( QTextDocument::Temp, &end );
332 doc->setFormat( QTextDocument::Temp, format, flags );
333 doc->removeSelection( QTextDocument::Temp );
334 if ( endIndex == ep->length() )
335 end.gotoLeft();
336 *c = end;
337 return c;
338}
339
340QTextCursor *QTextFormatCommand::unexecute( QTextCursor *c )
341{
342 QTextParag *sp = doc->paragAt( startId );
343 QTextParag *ep = doc->paragAt( endId );
344 if ( !sp || !ep )
345 return 0;
346
347 int idx = startIndex;
348 int fIndex = 0;
349 for ( ;; ) {
350 if ( oldFormats.at( fIndex ).c == '\n' ) {
351 if ( idx > 0 ) {
352 if ( idx < sp->length() && fIndex > 0 )
353 sp->setFormat( idx, 1, oldFormats.at( fIndex - 1 ).format() );
354 if ( sp == ep )
355 break;
356 sp = sp->next();
357 idx = 0;
358 }
359 fIndex++;
360 }
361 if ( oldFormats.at( fIndex ).format() )
362 sp->setFormat( idx, 1, oldFormats.at( fIndex ).format() );
363 idx++;
364 fIndex++;
365 if ( fIndex >= (int)oldFormats.size() )
366 break;
367 if ( idx >= sp->length() ) {
368 if ( sp == ep )
369 break;
370 sp = sp->next();
371 idx = 0;
372 }
373 }
374
375 QTextCursor end( doc );
376 end.setParag( ep );
377 end.setIndex( endIndex );
378 if ( endIndex == ep->length() )
379 end.gotoLeft();
380 *c = end;
381 return c;
382}
383
384QTextAlignmentCommand::QTextAlignmentCommand( QTextDocument *d, int fParag, int lParag, int na, const QMemArray<int> &oa )
385 : QTextCommand( d ), firstParag( fParag ), lastParag( lParag ), newAlign( na ), oldAligns( oa )
386{
387}
388
389QTextCursor *QTextAlignmentCommand::execute( QTextCursor *c )
390{
391 QTextParag *p = doc->paragAt( firstParag );
392 if ( !p )
393 return c;
394 while ( p ) {
395 p->setAlignment( newAlign );
396 if ( p->paragId() == lastParag )
397 break;
398 p = p->next();
399 }
400 return c;
401}
402
403QTextCursor *QTextAlignmentCommand::unexecute( QTextCursor *c )
404{
405 QTextParag *p = doc->paragAt( firstParag );
406 if ( !p )
407 return c;
408 int i = 0;
409 while ( p ) {
410 if ( i < (int)oldAligns.size() )
411 p->setAlignment( oldAligns.at( i ) );
412 if ( p->paragId() == lastParag )
413 break;
414 p = p->next();
415 ++i;
416 }
417 return c;
418}
419
420QTextParagTypeCommand::QTextParagTypeCommand( QTextDocument *d, int fParag, int lParag, bool l,
421 QStyleSheetItem::ListStyle s, const QValueList< QPtrVector<QStyleSheetItem> > &os,
422 const QValueList<QStyleSheetItem::ListStyle> &ols )
423 : QTextCommand( d ), firstParag( fParag ), lastParag( lParag ), list( l ), listStyle( s ), oldStyles( os ), oldListStyles( ols )
424{
425}
426
427QTextCursor *QTextParagTypeCommand::execute( QTextCursor *c )
428{
429 QTextParag *p = doc->paragAt( firstParag );
430 if ( !p )
431 return c;
432 while ( p ) {
433 p->setList( list, (int)listStyle );
434 if ( p->paragId() == lastParag )
435 break;
436 p = p->next();
437 }
438 return c;
439}
440
441QTextCursor *QTextParagTypeCommand::unexecute( QTextCursor *c )
442{
443 QTextParag *p = doc->paragAt( firstParag );
444 if ( !p )
445 return c;
446 QValueList< QPtrVector<QStyleSheetItem> >::Iterator it = oldStyles.begin();
447 QValueList<QStyleSheetItem::ListStyle>::Iterator lit = oldListStyles.begin();
448 while ( p ) {
449 if ( it != oldStyles.end() )
450 p->setStyleSheetItems( *it );
451 if ( lit != oldListStyles.end() )
452 p->setListStyle( *lit );
453 if ( p->paragId() == lastParag )
454 break;
455 p = p->next();
456 ++it;
457 ++lit;
458 }
459 return c;
460}
461
462// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
463
464QTextCursor::QTextCursor( QTextDocument *d )
465 : doc( d ), ox( 0 ), oy( 0 )
466{
467 nested = FALSE;
468 idx = 0;
469 string = doc ? doc->firstParag() : 0;
470 tmpIndex = -1;
471 valid = TRUE;
472}
473
474QTextCursor::QTextCursor()
475{
476}
477
478QTextCursor::QTextCursor( const QTextCursor &c )
479{
480 doc = c.doc;
481 ox = c.ox;
482 oy = c.oy;
483 nested = c.nested;
484 idx = c.idx;
485 string = c.string;
486 tmpIndex = c.tmpIndex;
487 indices = c.indices;
488 parags = c.parags;
489 xOffsets = c.xOffsets;
490 yOffsets = c.yOffsets;
491 valid = c.valid;
492}
493
494QTextCursor &QTextCursor::operator=( const QTextCursor &c )
495{
496 doc = c.doc;
497 ox = c.ox;
498 oy = c.oy;
499 nested = c.nested;
500 idx = c.idx;
501 string = c.string;
502 tmpIndex = c.tmpIndex;
503 indices = c.indices;
504 parags = c.parags;
505 xOffsets = c.xOffsets;
506 yOffsets = c.yOffsets;
507 valid = c.valid;
508
509 return *this;
510}
511
512bool QTextCursor::operator==( const QTextCursor &c ) const
513{
514 return doc == c.doc && string == c.string && idx == c.idx;
515}
516
517int QTextCursor::totalOffsetX() const
518{
519 if ( !nested )
520 return 0;
521 QValueStack<int>::ConstIterator xit = xOffsets.begin();
522 int xoff = ox;
523 for ( ; xit != xOffsets.end(); ++xit )
524 xoff += *xit;
525 return xoff;
526}
527
528int QTextCursor::totalOffsetY() const
529{
530 if ( !nested )
531 return 0;
532 QValueStack<int>::ConstIterator yit = yOffsets.begin();
533 int yoff = oy;
534 for ( ; yit != yOffsets.end(); ++yit )
535 yoff += *yit;
536 return yoff;
537}
538
539void QTextCursor::gotoIntoNested( const QPoint &globalPos )
540{
541 if ( !doc )
542 return;
543 push();
544 ox = 0;
545 int bl, y;
546 string->lineHeightOfChar( idx, &bl, &y );
547 oy = y + string->rect().y();
548 nested = TRUE;
549 QPoint p( globalPos.x() - offsetX(), globalPos.y() - offsetY() );
550 Q_ASSERT( string->at( idx )->isCustom() );
551 ox = string->at( idx )->x;
552 string->at( idx )->customItem()->enterAt( this, doc, string, idx, ox, oy, p );
553}
554
555void QTextCursor::invalidateNested()
556{
557 if ( nested ) {
558 QValueStack<QTextParag*>::Iterator it = parags.begin();
559 QValueStack<int>::Iterator it2 = indices.begin();
560 for ( ; it != parags.end(); ++it, ++it2 ) {
561 if ( *it == string )
562 continue;
563 (*it)->invalidate( 0 );
564 if ( (*it)->at( *it2 )->isCustom() )
565 (*it)->at( *it2 )->customItem()->invalidate();
566 }
567 }
568}
569
570void QTextCursor::insert( const QString &str, bool checkNewLine, QMemArray<QTextStringChar> *formatting )
571{
572 tmpIndex = -1;
573 bool justInsert = TRUE;
574 QString s( str );
575#if defined(Q_WS_WIN)
576 if ( checkNewLine )
577 s = s.replace( QRegExp( "\\r" ), "" );
578#endif
579 if ( checkNewLine )
580 justInsert = s.find( '\n' ) == -1;
581 if ( justInsert ) {
582 string->insert( idx, s );
583 if ( formatting ) {
584 for ( int i = 0; i < (int)s.length(); ++i ) {
585 if ( formatting->at( i ).format() ) {
586 formatting->at( i ).format()->addRef();
587 string->string()->setFormat( idx + i, formatting->at( i ).format(), TRUE );
588 }
589 }
590 }
591 idx += s.length();
592 } else {
593 QStringList lst = QStringList::split( '\n', s, TRUE );
594 QStringList::Iterator it = lst.begin();
595 int y = string->rect().y() + string->rect().height();
596 int lastIndex = 0;
597 QTextFormat *lastFormat = 0;
598 for ( ; it != lst.end(); ) {
599 if ( it != lst.begin() ) {
600 splitAndInsertEmptyParag( FALSE, TRUE );
601 string->setEndState( -1 );
602 string->prev()->format( -1, FALSE );
603 if ( lastFormat && formatting && string->prev() ) {
604 lastFormat->addRef();
605 string->prev()->string()->setFormat( string->prev()->length() - 1, lastFormat, TRUE );
606 }
607 }
608 lastFormat = 0;
609 QString s = *it;
610 ++it;
611 if ( !s.isEmpty() )
612 string->insert( idx, s );
613 else
614 string->invalidate( 0 );
615 if ( formatting ) {
616 int len = s.length();
617 for ( int i = 0; i < len; ++i ) {
618 if ( formatting->at( i + lastIndex ).format() ) {
619 formatting->at( i + lastIndex ).format()->addRef();
620 string->string()->setFormat( i + idx, formatting->at( i + lastIndex ).format(), TRUE );
621 }
622 }
623 if ( it != lst.end() )
624 lastFormat = formatting->at( len + lastIndex ).format();
625 ++len;
626 lastIndex += len;
627 }
628
629 idx += s.length();
630 }
631 string->format( -1, FALSE );
632 int dy = string->rect().y() + string->rect().height() - y;
633 QTextParag *p = string;
634 p->setParagId( p->prev()->paragId() + 1 );
635 p = p->next();
636 while ( p ) {
637 p->setParagId( p->prev()->paragId() + 1 );
638 p->move( dy );
639 p->invalidate( 0 );
640 p->setEndState( -1 );
641 p = p->next();
642 }
643 }
644
645 int h = string->rect().height();
646 string->format( -1, TRUE );
647 if ( h != string->rect().height() )
648 invalidateNested();
649 else if ( doc && doc->parent() )
650 doc->nextDoubleBuffered = TRUE;
651}
652
653void QTextCursor::gotoLeft()
654{
655 if ( string->string()->isRightToLeft() )
656 gotoNextLetter();
657 else
658 gotoPreviousLetter();
659}
660
661void QTextCursor::gotoPreviousLetter()
662{
663 tmpIndex = -1;
664
665 if ( idx > 0 ) {
666 idx--;
667 } else if ( string->prev() ) {
668 QTextParag *s = string->prev();
669 while ( s && !s->isVisible() )
670 s = s->prev();
671 if ( s ) {
672 string = s;
673 idx = string->length() - 1;
674 }
675 } else {
676 if ( nested ) {
677 pop();
678 processNesting( Prev );
679 if ( idx == -1 ) {
680 pop();
681 if ( idx > 0 ) {
682 idx--;
683 } else if ( string->prev() ) {
684 string = string->prev();
685 idx = string->length() - 1;
686 }
687 }
688 }
689 }
690
691 const QTextStringChar *tsc = string->at( idx );
692 if ( tsc && tsc->isCustom() && tsc->customItem()->isNested() ) {
693 processNesting( EnterEnd );
694 }
695}
696
697void QTextCursor::push()
698{
699 indices.push( idx );
700 parags.push( string );
701 xOffsets.push( ox );
702 yOffsets.push( oy );
703 nestedStack.push( nested );
704}
705
706void QTextCursor::pop()
707{
708 if ( !doc )
709 return;
710 idx = indices.pop();
711 string = parags.pop();
712 ox = xOffsets.pop();
713 oy = yOffsets.pop();
714 if ( doc->parent() )
715 doc = doc->parent();
716 nested = nestedStack.pop();
717}
718
719void QTextCursor::restoreState()
720{
721 while ( !indices.isEmpty() )
722 pop();
723}
724
725bool QTextCursor::place( const QPoint &p, QTextParag *s, bool link )
726{
727 QPoint pos( p );
728 QRect r;
729 QTextParag *str = s;
730 if ( pos.y() < s->rect().y() )
731 pos.setY( s->rect().y() );
732 while ( s ) {
733 r = s->rect();
734 r.setWidth( doc ? doc->width() : QWIDGETSIZE_MAX );
735 if ( s->isVisible() )
736 str = s;
737 if ( pos.y() >= r.y() && pos.y() <= r.y() + r.height() || !s->next() )
738 break;
739 s = s->next();
740 }
741
742 if ( !s || !str )
743 return FALSE;
744
745 s = str;
746
747 setParag( s, FALSE );
748 int y = s->rect().y();
749 int lines = s->lines();
750 QTextStringChar *chr = 0;
751 int index = 0;
752 int i = 0;
753 int cy = 0;
754 int ch = 0;
755 for ( ; i < lines; ++i ) {
756 chr = s->lineStartOfLine( i, &index );
757 cy = s->lineY( i );
758 ch = s->lineHeight( i );
759 if ( !chr )
760 return FALSE;
761 if ( pos.y() >= y + cy && pos.y() <= y + cy + ch )
762 break;
763 }
764 int nextLine;
765 if ( i < lines - 1 )
766 s->lineStartOfLine( i+1, &nextLine );
767 else
768 nextLine = s->length();
769 i = index;
770 int x = s->rect().x();
771 if ( pos.x() < x )
772 pos.setX( x + 1 );
773 int cw;
774 int curpos = s->length()-1;
775 int dist = 10000000;
776 bool inCustom = FALSE;
777 while ( i < nextLine ) {
778 chr = s->at(i);
779 int cpos = x + chr->x;
780 cw = s->string()->width( i );
781 if ( chr->isCustom() && chr->customItem()->isNested() ) {
782 if ( pos.x() >= cpos && pos.x() <= cpos + cw &&
783 pos.y() >= y + cy && pos.y() <= y + cy + chr->height() ) {
784 inCustom = TRUE;
785 curpos = i;
786 break;
787 }
788 } else {
789 if( chr->rightToLeft )
790 cpos += cw;
791 int d = cpos - pos.x();
792 bool dm = d < 0 ? !chr->rightToLeft : chr->rightToLeft;
793 if ( QABS( d ) < dist || (dist == d && dm == TRUE ) ) {
794 dist = QABS( d );
795 if ( !link || pos.x() >= x + chr->x )
796 curpos = i;
797 }
798 }
799 i++;
800 }
801 setIndex( curpos, FALSE );
802
803 if ( inCustom && doc && parag()->at( curpos )->isCustom() && parag()->at( curpos )->customItem()->isNested() ) {
804 QTextDocument *oldDoc = doc;
805 gotoIntoNested( pos );
806 if ( oldDoc == doc )
807 return TRUE;
808 QPoint p( pos.x() - offsetX(), pos.y() - offsetY() );
809 if ( !place( p, document()->firstParag(), link ) )
810 pop();
811 }
812 return TRUE;
813}
814
815void QTextCursor::processNesting( Operation op )
816{
817 if ( !doc )
818 return;
819 push();
820 ox = string->at( idx )->x;
821 int bl, y;
822 string->lineHeightOfChar( idx, &bl, &y );
823 oy = y + string->rect().y();
824 nested = TRUE;
825 bool ok = FALSE;
826
827 switch ( op ) {
828 case EnterBegin:
829 ok = string->at( idx )->customItem()->enter( this, doc, string, idx, ox, oy );
830 break;
831 case EnterEnd:
832 ok = string->at( idx )->customItem()->enter( this, doc, string, idx, ox, oy, TRUE );
833 break;
834 case Next:
835 ok = string->at( idx )->customItem()->next( this, doc, string, idx, ox, oy );
836 break;
837 case Prev:
838 ok = string->at( idx )->customItem()->prev( this, doc, string, idx, ox, oy );
839 break;
840 case Down:
841 ok = string->at( idx )->customItem()->down( this, doc, string, idx, ox, oy );
842 break;
843 case Up:
844 ok = string->at( idx )->customItem()->up( this, doc, string, idx, ox, oy );
845 break;
846 }
847 if ( !ok )
848 pop();
849}
850
851void QTextCursor::gotoRight()
852{
853 if ( string->string()->isRightToLeft() )
854 gotoPreviousLetter();
855 else
856 gotoNextLetter();
857}
858
859void QTextCursor::gotoNextLetter()
860{
861 tmpIndex = -1;
862
863 const QTextStringChar *tsc = string->at( idx );
864 if ( tsc && tsc->isCustom() && tsc->customItem()->isNested() ) {
865 processNesting( EnterBegin );
866 return;
867 }
868
869 if ( idx < string->length() - 1 ) {
870 idx++;
871 } else if ( string->next() ) {
872 QTextParag *s = string->next();
873 while ( s && !s->isVisible() )
874 s = s->next();
875 if ( s ) {
876 string = s;
877 idx = 0;
878 }
879 } else {
880 if ( nested ) {
881 pop();
882 processNesting( Next );
883 if ( idx == -1 ) {
884 pop();
885 if ( idx < string->length() - 1 ) {
886 idx++;
887 } else if ( string->next() ) {
888 string = string->next();
889 idx = 0;
890 }
891 }
892 }
893 }
894}
895
896void QTextCursor::gotoUp()
897{
898 int indexOfLineStart;
899 int line;
900 QTextStringChar *c = string->lineStartOfChar( idx, &indexOfLineStart, &line );
901 if ( !c )
902 return;
903
904 tmpIndex = QMAX( tmpIndex, idx - indexOfLineStart );
905 if ( indexOfLineStart == 0 ) {
906 if ( !string->prev() ) {
907 if ( !nested )
908 return;
909 pop();
910 processNesting( Up );
911 if ( idx == -1 ) {
912 pop();
913 if ( !string->prev() )
914 return;
915 idx = tmpIndex = 0;
916 } else {
917 tmpIndex = -1;
918 return;
919 }
920 }
921 QTextParag *s = string->prev();
922 while ( s && !s->isVisible() )
923 s = s->prev();
924 if ( s )
925 string = s;
926 int lastLine = string->lines() - 1;
927 if ( !string->lineStartOfLine( lastLine, &indexOfLineStart ) )
928 return;
929 if ( indexOfLineStart + tmpIndex < string->length() )
930 idx = indexOfLineStart + tmpIndex;
931 else
932 idx = string->length() - 1;
933 } else {
934 --line;
935 int oldIndexOfLineStart = indexOfLineStart;
936 if ( !string->lineStartOfLine( line, &indexOfLineStart ) )
937 return;
938 if ( indexOfLineStart + tmpIndex < oldIndexOfLineStart )
939 idx = indexOfLineStart + tmpIndex;
940 else
941 idx = oldIndexOfLineStart - 1;
942 }
943}
944
945void QTextCursor::gotoDown()
946{
947 int indexOfLineStart;
948 int line;
949 QTextStringChar *c = string->lineStartOfChar( idx, &indexOfLineStart, &line );
950 if ( !c )
951 return;
952
953 tmpIndex = QMAX( tmpIndex, idx - indexOfLineStart );
954 if ( line == string->lines() - 1 ) {
955 if ( !string->next() ) {
956 if ( !nested )
957 return;
958 pop();
959 processNesting( Down );
960 if ( idx == -1 ) {
961 pop();
962 if ( !string->next() )
963 return;
964 idx = tmpIndex = 0;
965 } else {
966 tmpIndex = -1;
967 return;
968 }
969 }
970 QTextParag *s = string->next();
971 while ( s && !s->isVisible() )
972 s = s->next();
973 if ( s )
974 string = s;
975 if ( !string->lineStartOfLine( 0, &indexOfLineStart ) )
976 return;
977 int end;
978 if ( string->lines() == 1 )
979 end = string->length();
980 else
981 string->lineStartOfLine( 1, &end );
982 if ( indexOfLineStart + tmpIndex < end )
983 idx = indexOfLineStart + tmpIndex;
984 else
985 idx = end - 1;
986 } else {
987 ++line;
988 int end;
989 if ( line == string->lines() - 1 )
990 end = string->length();
991 else
992 string->lineStartOfLine( line + 1, &end );
993 if ( !string->lineStartOfLine( line, &indexOfLineStart ) )
994 return;
995 if ( indexOfLineStart + tmpIndex < end )
996 idx = indexOfLineStart + tmpIndex;
997 else
998 idx = end - 1;
999 }
1000}
1001
1002void QTextCursor::gotoLineEnd()
1003{
1004 tmpIndex = -1;
1005 int indexOfLineStart;
1006 int line;
1007 QTextStringChar *c = string->lineStartOfChar( idx, &indexOfLineStart, &line );
1008 if ( !c )
1009 return;
1010
1011 if ( line == string->lines() - 1 ) {
1012 idx = string->length() - 1;
1013 } else {
1014 c = string->lineStartOfLine( ++line, &indexOfLineStart );
1015 indexOfLineStart--;
1016 idx = indexOfLineStart;
1017 }
1018}
1019
1020void QTextCursor::gotoLineStart()
1021{
1022 tmpIndex = -1;
1023 int indexOfLineStart;
1024 int line;
1025 QTextStringChar *c = string->lineStartOfChar( idx, &indexOfLineStart, &line );
1026 if ( !c )
1027 return;
1028
1029 idx = indexOfLineStart;
1030}
1031
1032void QTextCursor::gotoHome()
1033{
1034 tmpIndex = -1;
1035 if ( doc )
1036 string = doc->firstParag();
1037 idx = 0;
1038}
1039
1040void QTextCursor::gotoEnd()
1041{
1042 if ( doc && !doc->lastParag()->isValid() )
1043 return;
1044
1045 tmpIndex = -1;
1046 if ( doc )
1047 string = doc->lastParag();
1048 idx = string->length() - 1;
1049}
1050
1051void QTextCursor::gotoPageUp( int visibleHeight )
1052{
1053 tmpIndex = -1;
1054 QTextParag *s = string;
1055 int h = visibleHeight;
1056 int y = s->rect().y();
1057 while ( s ) {
1058 if ( y - s->rect().y() >= h )
1059 break;
1060 s = s->prev();
1061 }
1062
1063 if ( !s && doc )
1064 s = doc->firstParag();
1065
1066 string = s;
1067 idx = 0;
1068}
1069
1070void QTextCursor::gotoPageDown( int visibleHeight )
1071{
1072 tmpIndex = -1;
1073 QTextParag *s = string;
1074 int h = visibleHeight;
1075 int y = s->rect().y();
1076 while ( s ) {
1077 if ( s->rect().y() - y >= h )
1078 break;
1079 s = s->next();
1080 }
1081
1082 if ( !s && doc ) {
1083 s = doc->lastParag();
1084 string = s;
1085 idx = string->length() - 1;
1086 return;
1087 }
1088
1089 if ( !s->isValid() )
1090 return;
1091
1092 string = s;
1093 idx = 0;
1094}
1095
1096void QTextCursor::gotoWordRight()
1097{
1098 if ( string->string()->isRightToLeft() )
1099 gotoPreviousWord();
1100 else
1101 gotoNextWord();
1102}
1103
1104void QTextCursor::gotoWordLeft()
1105{
1106 if ( string->string()->isRightToLeft() )
1107 gotoNextWord();
1108 else
1109 gotoPreviousWord();
1110}
1111
1112void QTextCursor::gotoPreviousWord()
1113{
1114 gotoPreviousLetter();
1115 tmpIndex = -1;
1116 QTextString *s = string->string();
1117 bool allowSame = FALSE;
1118 if ( idx == ((int)s->length()-1) )
1119 return;
1120 for ( int i = idx; i >= 0; --i ) {
1121 if ( s->at( i ).c.isSpace() || s->at( i ).c == '\t' || s->at( i ).c == '.' ||
1122 s->at( i ).c == ',' || s->at( i ).c == ':' || s->at( i ).c == ';' ) {
1123 if ( !allowSame )
1124 continue;
1125 idx = i + 1;
1126 return;
1127 }
1128 if ( !allowSame && !( s->at( i ).c.isSpace() || s->at( i ).c == '\t' || s->at( i ).c == '.' ||
1129 s->at( i ).c == ',' || s->at( i ).c == ':' || s->at( i ).c == ';' ) )
1130 allowSame = TRUE;
1131 }
1132 idx = 0;
1133}
1134
1135void QTextCursor::gotoNextWord()
1136{
1137 tmpIndex = -1;
1138 QTextString *s = string->string();
1139 bool allowSame = FALSE;
1140 for ( int i = idx; i < (int)s->length(); ++i ) {
1141 if ( ! (s->at( i ).c.isSpace() || s->at( i ).c == '\t' || s->at( i ).c == '.' ||
1142 s->at( i ).c == ',' || s->at( i ).c == ':' || s->at( i ).c == ';') ) {
1143 if ( !allowSame )
1144 continue;
1145 idx = i;
1146 return;
1147 }
1148 if ( !allowSame && ( s->at( i ).c.isSpace() || s->at( i ).c == '\t' || s->at( i ).c == '.' ||
1149 s->at( i ).c == ',' || s->at( i ).c == ':' || s->at( i ).c == ';' ) )
1150 allowSame = TRUE;
1151
1152 }
1153
1154 if ( idx < ((int)s->length()-1) ) {
1155 gotoLineEnd();
1156 } else if ( string->next() ) {
1157 QTextParag *s = string->next();
1158 while ( s && !s->isVisible() )
1159 s = s->next();
1160 if ( s ) {
1161 string = s;
1162 idx = 0;
1163 }
1164 } else {
1165 gotoLineEnd();
1166 }
1167}
1168
1169bool QTextCursor::atParagStart()
1170{
1171 return idx == 0;
1172}
1173
1174bool QTextCursor::atParagEnd()
1175{
1176 return idx == string->length() - 1;
1177}
1178
1179void QTextCursor::splitAndInsertEmptyParag( bool ind, bool updateIds )
1180{
1181 if ( !doc )
1182 return;
1183 tmpIndex = -1;
1184 QTextFormat *f = 0;
1185 if ( doc->useFormatCollection() ) {
1186 f = string->at( idx )->format();
1187 if ( idx == string->length() - 1 && idx > 0 )
1188 f = string->at( idx - 1 )->format();
1189 if ( f->isMisspelled() ) {
1190 f->removeRef();
1191 f = doc->formatCollection()->format( f->font(), f->color() );
1192 }
1193 }
1194
1195 if ( atParagEnd() ) {
1196 QTextParag *n = string->next();
1197 QTextParag *s = doc->createParag( doc, string, n, updateIds );
1198 if ( f )
1199 s->setFormat( 0, 1, f, TRUE );
1200 s->copyParagData( string );
1201 if ( ind ) {
1202 int oi, ni;
1203 s->indent( &oi, &ni );
1204 string = s;
1205 idx = ni;
1206 } else {
1207 string = s;
1208 idx = 0;
1209 }
1210 } else if ( atParagStart() ) {
1211 QTextParag *p = string->prev();
1212 QTextParag *s = doc->createParag( doc, p, string, updateIds );
1213 if ( f )
1214 s->setFormat( 0, 1, f, TRUE );
1215 s->copyParagData( string );
1216 if ( ind ) {
1217 s->indent();
1218 s->format();
1219 indent();
1220 string->format();
1221 }
1222 } else {
1223 QString str = string->string()->toString().mid( idx, 0xFFFFFF );
1224 QTextParag *n = string->next();
1225 QTextParag *s = doc->createParag( doc, string, n, updateIds );
1226 s->copyParagData( string );
1227 s->remove( 0, 1 );
1228 s->append( str, TRUE );
1229 for ( uint i = 0; i < str.length(); ++i ) {
1230 s->setFormat( i, 1, string->at( idx + i )->format(), TRUE );
1231 if ( string->at( idx + i )->isCustom() ) {
1232 QTextCustomItem * item = string->at( idx + i )->customItem();
1233 s->at( i )->setCustomItem( item );
1234 string->at( idx + i )->loseCustomItem();
1235 }
1236 }
1237 string->truncate( idx );
1238 if ( ind ) {
1239 int oi, ni;
1240 s->indent( &oi, &ni );
1241 string = s;
1242 idx = ni;
1243 } else {
1244 string = s;
1245 idx = 0;
1246 }
1247 }
1248
1249 invalidateNested();
1250}
1251
1252bool QTextCursor::remove()
1253{
1254 tmpIndex = -1;
1255 if ( !atParagEnd() ) {
1256 string->remove( idx, 1 );
1257 int h = string->rect().height();
1258 string->format( -1, TRUE );
1259 if ( h != string->rect().height() )
1260 invalidateNested();
1261 else if ( doc && doc->parent() )
1262 doc->nextDoubleBuffered = TRUE;
1263 return FALSE;
1264 } else if ( string->next() ) {
1265 if ( string->length() == 1 ) {
1266 string->next()->setPrev( string->prev() );
1267 if ( string->prev() )
1268 string->prev()->setNext( string->next() );
1269 QTextParag *p = string->next();
1270 delete string;
1271 string = p;
1272 string->invalidate( 0 );
1273 QTextParag *s = string;
1274 while ( s ) {
1275 s->id = s->p ? s->p->id + 1 : 0;
1276 s->state = -1;
1277 s->needPreProcess = TRUE;
1278 s->changed = TRUE;
1279 s = s->n;
1280 }
1281 string->format();
1282 } else {
1283 string->join( string->next() );
1284 }
1285 invalidateNested();
1286 return TRUE;
1287 }
1288 return FALSE;
1289}
1290
1291void QTextCursor::killLine()
1292{
1293 if ( atParagEnd() )
1294 return;
1295 string->remove( idx, string->length() - idx - 1 );
1296 int h = string->rect().height();
1297 string->format( -1, TRUE );
1298 if ( h != string->rect().height() )
1299 invalidateNested();
1300 else if ( doc && doc->parent() )
1301 doc->nextDoubleBuffered = TRUE;
1302}
1303
1304void QTextCursor::indent()
1305{
1306 int oi = 0, ni = 0;
1307 string->indent( &oi, &ni );
1308 if ( oi == ni )
1309 return;
1310
1311 if ( idx >= oi )
1312 idx += ni - oi;
1313 else
1314 idx = ni;
1315}
1316
1317void QTextCursor::setDocument( QTextDocument *d )
1318{
1319 doc = d;
1320 string = d->firstParag();
1321 idx = 0;
1322 nested = FALSE;
1323 restoreState();
1324 tmpIndex = -1;
1325}
1326
1327// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1328
1329QTextDocument::QTextDocument( QTextDocument *p )
1330 : par( p ), parParag( 0 ), tc( 0 ), tArray( 0 ), tStopWidth( 0 )
1331{
1332 fCollection = new QTextFormatCollection;
1333 init();
1334}
1335
1336QTextDocument::QTextDocument( QTextDocument *p, QTextFormatCollection *f )
1337 : par( p ), parParag( 0 ), tc( 0 ), tArray( 0 ), tStopWidth( 0 )
1338{
1339 fCollection = f;
1340 init();
1341}
1342
1343void QTextDocument::init()
1344{
1345#if defined(PARSER_DEBUG)
1346 qDebug( debug_indent + "new QTextDocument (%p)", this );
1347#endif
1348 oTextValid = TRUE;
1349 mightHaveCustomItems = FALSE;
1350 if ( par )
1351 par->insertChild( this );
1352 pProcessor = 0;
1353 useFC = TRUE;
1354 pFormatter = 0;
1355 indenter = 0;
1356 fParag = 0;
1357 txtFormat = Qt::AutoText;
1358 preferRichText = FALSE;
1359 pages = FALSE;
1360 focusIndicator.parag = 0;
1361 minw = 0;
1362 wused = 0;
1363 minwParag = curParag = 0;
1364 align = AlignAuto;
1365 nSelections = 1;
1366 addMargs = FALSE;
1367
1368 sheet_ = QStyleSheet::defaultSheet();
1369 factory_ = QMimeSourceFactory::defaultFactory();
1370 contxt = QString::null;
1371 fCollection->setStyleSheet( sheet_ );
1372
1373 underlLinks = par ? par->underlLinks : TRUE;
1374 backBrush = 0;
1375 buf_pixmap = 0;
1376 nextDoubleBuffered = FALSE;
1377
1378 if ( par )
1379 withoutDoubleBuffer = par->withoutDoubleBuffer;
1380 else
1381 withoutDoubleBuffer = FALSE;
1382
1383 lParag = fParag = createParag( this, 0, 0 );
1384 tmpCursor = 0;
1385
1386 cx = 0;
1387 cy = 2;
1388 if ( par )
1389 cx = cy = 0;
1390 cw = 600;
1391 vw = 0;
1392 flow_ = new QTextFlow;
1393 flow_->setWidth( cw );
1394
1395 leftmargin = rightmargin = 4;
1396
1397 selectionColors[ Standard ] = QApplication::palette().color( QPalette::Active, QColorGroup::Highlight );
1398 selectionText[ Standard ] = TRUE;
1399 commandHistory = new QTextCommandHistory( 100 );
1400 tStopWidth = formatCollection()->defaultFormat()->width( 'x' ) * 8;
1401}
1402
1403QTextDocument::~QTextDocument()
1404{
1405 if ( par )
1406 par->removeChild( this );
1407 clear();
1408 delete commandHistory;
1409 delete flow_;
1410 if ( !par )
1411 delete pFormatter;
1412 delete fCollection;
1413 delete pProcessor;
1414 delete buf_pixmap;
1415 delete indenter;
1416 delete backBrush;
1417 if ( tArray )
1418 delete [] tArray;
1419}
1420
1421void QTextDocument::clear( bool createEmptyParag )
1422{
1423 if ( flow_ )
1424 flow_->clear();
1425 while ( fParag ) {
1426 QTextParag *p = fParag->next();
1427 delete fParag;
1428 fParag = p;
1429 }
1430 fParag = lParag = 0;
1431 if ( createEmptyParag )
1432 fParag = lParag = createParag( this );
1433 selections.clear();
1434}
1435
1436int QTextDocument::widthUsed() const
1437{
1438 return wused + border_tolerance;
1439}
1440
1441int QTextDocument::height() const
1442{
1443 int h = 0;
1444 if ( lParag )
1445 h = lParag->rect().top() + lParag->rect().height() + 1;
1446 int fh = flow_->boundingRect().bottom();
1447 return QMAX( h, fh );
1448}
1449
1450
1451
1452QTextParag *QTextDocument::createParag( QTextDocument *d, QTextParag *pr, QTextParag *nx, bool updateIds )
1453{
1454 return new QTextParag( d, pr, nx, updateIds );
1455}
1456
1457bool QTextDocument::setMinimumWidth( int needed, int used, QTextParag *p )
1458{
1459 if ( needed == -1 ) {
1460 minw = 0;
1461 wused = 0;
1462 p = 0;
1463 }
1464 if ( p == minwParag ) {
1465 minw = needed;
1466 emit minimumWidthChanged( minw );
1467 } else if ( needed > minw ) {
1468 minw = needed;
1469 minwParag = p;
1470 emit minimumWidthChanged( minw );
1471 }
1472 wused = QMAX( wused, used );
1473 wused = QMAX( wused, minw );
1474 cw = QMAX( minw, cw );
1475 return TRUE;
1476}
1477
1478void QTextDocument::setPlainText( const QString &text )
1479{
1480 clear();
1481 preferRichText = FALSE;
1482 oTextValid = TRUE;
1483 oText = text;
1484
1485 int lastNl = 0;
1486 int nl = text.find( '\n' );
1487 if ( nl == -1 ) {
1488 lParag = createParag( this, lParag, 0 );
1489 if ( !fParag )
1490 fParag = lParag;
1491 QString s = text;
1492 if ( !s.isEmpty() ) {
1493 if ( s[ (int)s.length() - 1 ] == '\r' )
1494 s.remove( s.length() - 1, 1 );
1495 lParag->append( s );
1496 }
1497 } else {
1498 for (;;) {
1499 lParag = createParag( this, lParag, 0 );
1500 if ( !fParag )
1501 fParag = lParag;
1502 QString s = text.mid( lastNl, nl - lastNl );
1503 if ( !s.isEmpty() ) {
1504 if ( s[ (int)s.length() - 1 ] == '\r' )
1505 s.remove( s.length() - 1, 1 );
1506 lParag->append( s );
1507 }
1508 if ( nl == 0xffffff )
1509 break;
1510 lastNl = nl + 1;
1511 nl = text.find( '\n', nl + 1 );
1512 if ( nl == -1 )
1513 nl = 0xffffff;
1514 }
1515 }
1516 if ( !lParag )
1517 lParag = fParag = createParag( this, 0, 0 );
1518}
1519
1520struct Q_EXPORT QTextDocumentTag {
1521 QTextDocumentTag(){}
1522 QTextDocumentTag( const QString&n, const QStyleSheetItem* s, const QTextFormat& f )
1523 :name(n),style(s), format(f), alignment(Qt3::AlignAuto), direction(QChar::DirON),liststyle(QStyleSheetItem::ListDisc) {
1524 wsm = QStyleSheetItem::WhiteSpaceNormal;
1525 }
1526 QString name;
1527 const QStyleSheetItem* style;
1528 QString anchorHref;
1529 QStyleSheetItem::WhiteSpaceMode wsm;
1530 QTextFormat format;
1531 int alignment : 16;
1532 int direction : 5;
1533 QStyleSheetItem::ListStyle liststyle;
1534
1535 QTextDocumentTag( const QTextDocumentTag& t ) {
1536 name = t.name;
1537 style = t.style;
1538 anchorHref = t.anchorHref;
1539 wsm = t.wsm;
1540 format = t.format;
1541 alignment = t.alignment;
1542 direction = t.direction;
1543 liststyle = t.liststyle;
1544 }
1545 QTextDocumentTag& operator=(const QTextDocumentTag& t) {
1546 name = t.name;
1547 style = t.style;
1548 anchorHref = t.anchorHref;
1549 wsm = t.wsm;
1550 format = t.format;
1551 alignment = t.alignment;
1552 direction = t.direction;
1553 liststyle = t.liststyle;
1554 return *this;
1555 }
1556
1557#if defined(Q_FULL_TEMPLATE_INSTANTIATION)
1558 bool operator==( const QTextDocumentTag& ) const { return FALSE; }
1559#endif
1560};
1561
1562#define NEWPAR do{ if ( !hasNewPar ) curpar = createParag( this, curpar ); \
1563 if ( curpar->isBr ) curpar->isBr = FALSE; \
1564 hasNewPar = TRUE; \
1565 curpar->setAlignment( curtag.alignment ); \
1566 curpar->setDirection( (QChar::Direction)curtag.direction ); \
1567 space = TRUE; \
1568 QPtrVector<QStyleSheetItem> vec( (uint)tags.count() + 1); \
1569 int i = 0; \
1570 for ( QValueStack<QTextDocumentTag>::Iterator it = tags.begin(); it != tags.end(); ++it ) \
1571 vec.insert( i++, (*it).style ); \
1572 vec.insert( i, curtag.style ); \
1573 curpar->setStyleSheetItems( vec ); }while(FALSE)
1574
1575
1576void QTextDocument::setRichText( const QString &text, const QString &context )
1577{
1578 setTextFormat( Qt::RichText );
1579 if ( !context.isEmpty() )
1580 setContext( context );
1581 clear();
1582 fParag = lParag = createParag( this );
1583 setRichTextInternal( text );
1584}
1585
1586static QStyleSheetItem::ListStyle chooseListStyle( const QStyleSheetItem *nstyle,
1587 const QMap<QString, QString> &attr,
1588 QStyleSheetItem::ListStyle curListStyle )
1589{
1590 if ( nstyle->name() == "ol" || nstyle->name() == "ul" ) {
1591 curListStyle = nstyle->listStyle();
1592 QMap<QString, QString>::ConstIterator it = attr.find( "type" );
1593 if ( it != attr.end() ) {
1594 QString sl = *it;
1595 if ( sl == "1" ) {
1596 curListStyle = QStyleSheetItem::ListDecimal;
1597 } else if ( sl == "a" ) {
1598 curListStyle = QStyleSheetItem::ListLowerAlpha;
1599 } else if ( sl == "A" ) {
1600 curListStyle = QStyleSheetItem::ListUpperAlpha;
1601 } else {
1602 sl = sl.lower();
1603 if ( sl == "square" )
1604 curListStyle = QStyleSheetItem::ListSquare;
1605 else if ( sl == "disc" )
1606 curListStyle = QStyleSheetItem::ListDisc;
1607 else if ( sl == "circle" )
1608 curListStyle = QStyleSheetItem::ListCircle;
1609 }
1610 }
1611 }
1612 return curListStyle;
1613}
1614
1615void QTextDocument::setRichTextInternal( const QString &text )
1616{
1617 oTextValid = TRUE;
1618 oText = text;
1619 QTextParag* curpar = lParag;
1620 int pos = 0;
1621 QValueStack<QTextDocumentTag> tags;
1622 QTextDocumentTag initag( "", sheet_->item(""), *formatCollection()->defaultFormat() );
1623 QTextDocumentTag curtag = initag;
1624 bool space = TRUE;
1625
1626 const QChar* doc = text.unicode();
1627 int length = text.length();
1628 bool hasNewPar = curpar->length() <= 1;
1629 QString lastClose;
1630 QString anchorName;
1631 while ( pos < length ) {
1632 if ( hasPrefix(doc, length, pos, '<' ) ){
1633 if ( !hasPrefix( doc, length, pos+1, QChar('/') ) ) {
1634 // open tag
1635 QMap<QString, QString> attr;
1636 bool emptyTag = FALSE;
1637 QString tagname = parseOpenTag(doc, length, pos, attr, emptyTag);
1638 if ( tagname.isEmpty() )
1639 continue; // nothing we could do with this, probably parse error
1640
1641 if ( tagname == "title" ) {
1642 QString title;
1643 while ( pos < length ) {
1644 if ( hasPrefix( doc, length, pos, QChar('<') ) && hasPrefix( doc, length, pos+1, QChar('/') ) &&
1645 parseCloseTag( doc, length, pos ) == "title" )
1646 break;
1647 title += doc[ pos ];
1648 ++pos;
1649 }
1650 attribs.replace( "title", title );
1651 }
1652
1653 const QStyleSheetItem* nstyle = sheet_->item(tagname);
1654
1655 if ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) {
1656 // if ( tagname == "br" ) {
1657 // // our standard br emty-tag handling breaks
1658 // // inside list items, we would get another
1659 // // list item in this case. As workaround, fake
1660 // // a new paragraph instead
1661 // tagname = "p";
1662 // nstyle = sheet_->item( tagname );
1663 // }
1664 if ( nstyle )
1665 hasNewPar = FALSE; // we want empty paragraphs in this case
1666 }
1667
1668 if ( nstyle ) {
1669 // we might have to close some 'forgotten' tags
1670 while ( !nstyle->allowedInContext( curtag.style ) ) {
1671 QString msg;
1672 msg.sprintf( "QText Warning: Document not valid ( '%s' not allowed in '%s' #%d)",
1673 tagname.ascii(), curtag.style->name().ascii(), pos);
1674 sheet_->error( msg );
1675 if ( tags.isEmpty() )
1676 break;
1677 curtag = tags.pop();
1678 }
1679
1680 // special handling for p. We do not want to nest there for HTML compatibility
1681 if ( nstyle->displayMode() == QStyleSheetItem::DisplayBlock ) {
1682 while ( curtag.style->name() == "p" ) {
1683 if ( tags.isEmpty() )
1684 break;
1685 curtag = tags.pop();
1686 }
1687 }
1688
1689 }
1690
1691 QTextCustomItem* custom = 0;
1692 // some well-known empty tags
1693 if ( tagname == "br" ) {
1694 emptyTag = TRUE;
1695 hasNewPar = FALSE;
1696 if ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) {
1697 // when linebreaking a list item, we do not
1698 // actually want a new list item but just a
1699 // new line. Fake this by pushing a paragraph
1700 // onto the stack
1701 tags.push( curtag );
1702 curtag.name = tagname;
1703 curtag.style = nstyle;
1704 }
1705 NEWPAR;
1706 curpar->isBr = TRUE;
1707 curpar->setAlignment( curtag.alignment );
1708 } else if ( tagname == "hr" ) {
1709 emptyTag = TRUE;
1710 custom = sheet_->tag( tagname, attr, contxt, *factory_ , emptyTag, this );
1711 NEWPAR;
1712 } else if ( tagname == "table" ) {
1713 QTextFormat format = curtag.format.makeTextFormat( nstyle, attr );
1714 curpar->setAlignment( curtag.alignment );
1715 custom = parseTable( attr, format, doc, length, pos, curpar );
1716 (void)eatSpace( doc, length, pos );
1717 emptyTag = TRUE;
1718 } else if ( tagname == "qt" ) {
1719 for ( QMap<QString, QString>::Iterator it = attr.begin(); it != attr.end(); ++it ) {
1720 if ( it.key() == "bgcolor" ) {
1721 QBrush *b = new QBrush( QColor( *it ) );
1722 setPaper( b );
1723 } else if ( it.key() == "background" ) {
1724 QImage img;
1725 const QMimeSource* m = factory_->data( *it, contxt );
1726 if ( !m ) {
1727 qWarning("QRichText: no mimesource for %s", (*it).latin1() );
1728 } else {
1729 if ( !QImageDrag::decode( m, img ) ) {
1730 qWarning("QTextImage: cannot decode %s", (*it).latin1() );
1731 }
1732 }
1733 if ( !img.isNull() ) {
1734 QPixmap pm;
1735 pm.convertFromImage( img );
1736 QBrush *b = new QBrush( QColor(), pm );
1737 setPaper( b );
1738 }
1739 } else if ( it.key() == "text" ) {
1740 QColor c( *it );
1741 if ( formatCollection()->defaultFormat()->color() != c ) {
1742 QDict<QTextFormat> formats = formatCollection()->dict();
1743 QDictIterator<QTextFormat> it( formats );
1744 while ( it.current() ) {
1745 if ( it.current() == formatCollection()->defaultFormat() ) {
1746 ++it;
1747 continue;
1748 }
1749 it.current()->setColor( c );
1750 ++it;
1751 }
1752 formatCollection()->defaultFormat()->setColor( c );
1753 curtag.format.setColor( c );
1754 }
1755 } else if ( it.key() == "link" ) {
1756 linkColor = QColor( *it );
1757 } else if ( it.key() == "title" ) {
1758 attribs.replace( it.key(), *it );
1759 }
1760 }
1761 } else {
1762 custom = sheet_->tag( tagname, attr, contxt, *factory_ , emptyTag, this );
1763 }
1764
1765 if ( !nstyle && !custom ) // we have no clue what this tag could be, ignore it
1766 continue;
1767
1768 if ( custom ) {
1769 int index = curpar->length() - 1;
1770 if ( index < 0 )
1771 index = 0;
1772 QTextFormat format = curtag.format.makeTextFormat( nstyle, attr );
1773 curpar->append( QChar('*') );
1774 curpar->setFormat( index, 1, &format );
1775 curpar->at( index )->setCustomItem( custom );
1776 if ( !curtag.anchorHref.isEmpty() )
1777 curpar->at(index)->setAnchor( QString::null, curtag.anchorHref );
1778 if ( !anchorName.isEmpty() ) {
1779 curpar->at(index)->setAnchor( anchorName, curpar->at(index)->anchorHref() );
1780 anchorName = QString::null;
1781 }
1782 registerCustomItem( custom, curpar );
1783 hasNewPar = FALSE;
1784 } else if ( !emptyTag ) {
1785 // ignore whitespace for inline elements if there was already one
1786 if ( nstyle->whiteSpaceMode() == QStyleSheetItem::WhiteSpaceNormal
1787 && ( space || nstyle->displayMode() != QStyleSheetItem::DisplayInline ) )
1788 eatSpace( doc, length, pos );
1789
1790 // if we do nesting, push curtag on the stack,
1791 // otherwise reinint curag.
1792 if ( nstyle != curtag.style || nstyle->selfNesting() ) {
1793 tags.push( curtag );
1794 } else {
1795 if ( !tags.isEmpty() )
1796 curtag = tags.top();
1797 else
1798 curtag = initag;
1799 }
1800
1801 const QStyleSheetItem* ostyle = curtag.style;
1802
1803 curtag.name = tagname;
1804 curtag.style = nstyle;
1805 curtag.name = tagname;
1806 curtag.style = nstyle;
1807 if ( nstyle->whiteSpaceMode() != QStyleSheetItem::WhiteSpaceNormal )
1808 curtag.wsm = nstyle->whiteSpaceMode();
1809 curtag.liststyle = chooseListStyle( nstyle, attr, curtag.liststyle );
1810 curtag.format = curtag.format.makeTextFormat( nstyle, attr );
1811 if ( nstyle->isAnchor() ) {
1812 if ( !anchorName.isEmpty() )
1813 anchorName += "#" + attr["name"];
1814 else
1815 anchorName = attr["name"];
1816 curtag.anchorHref = attr["href"];
1817 }
1818
1819 if ( nstyle->alignment() != QStyleSheetItem::Undefined )
1820 curtag.alignment = nstyle->alignment();
1821
1822 if ( ostyle->displayMode() == QStyleSheetItem::DisplayListItem &&
1823 curpar->length() <= 1
1824 && nstyle->displayMode() == QStyleSheetItem::DisplayBlock ) {
1825 // do not do anything, we reuse the paragraph we have
1826 } else if ( nstyle->displayMode() != QStyleSheetItem::DisplayInline && nstyle->displayMode() != QStyleSheetItem::DisplayNone ) {
1827 NEWPAR;
1828 }
1829
1830 if ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) {
1831 curpar->setListStyle( curtag.liststyle );
1832 if ( attr.find( "value" ) != attr.end() )
1833 curpar->setListValue( (*attr.find( "value" )).toInt() );
1834 }
1835
1836 if ( nstyle->displayMode() != QStyleSheetItem::DisplayInline )
1837 curpar->setFormat( &curtag.format );
1838
1839 if ( attr.contains( "align" ) &&
1840 ( curtag.name == "p" ||
1841 curtag.name == "div" ||
1842 curtag.name == "li" ||
1843 curtag.name[ 0 ] == 'h' ) ) {
1844 QString align = attr["align"];
1845 if ( align == "center" )
1846 curtag.alignment = Qt::AlignCenter;
1847 else if ( align == "right" )
1848 curtag.alignment = Qt::AlignRight;
1849 else if ( align == "justify" )
1850 curtag.alignment = Qt3::AlignJustify;
1851 }
1852 if ( attr.contains( "dir" ) &&
1853 ( curtag.name == "p" ||
1854 curtag.name == "div" ||
1855 curtag.name == "li" ||
1856 curtag.name[ 0 ] == 'h' ) ) {
1857 QString dir = attr["dir"];
1858 if ( dir == "rtl" )
1859 curtag.direction = QChar::DirR;
1860 else if ( dir == "ltr" )
1861 curtag.direction = QChar::DirL;
1862 }
1863 if ( nstyle->displayMode() != QStyleSheetItem::DisplayInline ) {
1864 curpar->setAlignment( curtag.alignment );
1865 curpar->setDirection( (QChar::Direction)curtag.direction );
1866 }
1867 }
1868 } else {
1869 QString tagname = parseCloseTag( doc, length, pos );
1870 lastClose = tagname;
1871 if ( tagname.isEmpty() )
1872 continue; // nothing we could do with this, probably parse error
1873 if ( !sheet_->item( tagname ) ) // ignore unknown tags
1874 continue;
1875
1876
1877 // we close a block item. Since the text may continue, we need to have a new paragraph
1878 bool needNewPar = curtag.style->displayMode() == QStyleSheetItem::DisplayBlock;
1879
1880 if ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) {
1881 needNewPar = TRUE;
1882 hasNewPar = FALSE; // we want empty paragraphs in this case
1883 }
1884
1885 // html slopiness: handle unbalanched tag closing
1886 while ( curtag.name != tagname ) {
1887 QString msg;
1888 msg.sprintf( "QText Warning: Document not valid ( '%s' not closed before '%s' #%d)",
1889 curtag.name.ascii(), tagname.ascii(), pos);
1890 sheet_->error( msg );
1891 if ( tags.isEmpty() )
1892 break;
1893 curtag = tags.pop();
1894 }
1895
1896
1897 // close the tag
1898 if ( !tags.isEmpty() )
1899 curtag = tags.pop();
1900 else
1901 curtag = initag;
1902
1903 if ( needNewPar ) {
1904 if ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) {
1905 tags.push( curtag );
1906 curtag.name = "p";
1907 curtag.style = sheet_->item( curtag.name ); // a list item continues, use p for that
1908 }
1909 NEWPAR;
1910 }
1911 }
1912 } else {
1913 // normal contents
1914 QString s;
1915 QChar c;
1916 while ( pos < length && !hasPrefix(doc, length, pos, QChar('<') ) ){
1917 QStyleSheetItem::WhiteSpaceMode wsm = curtag.wsm;
1918 if ( s.length() > 4096 )
1919 wsm = (QStyleSheetItem::WhiteSpaceMode)QStyleSheetItem_WhiteSpaceNormalWithNewlines;
1920
1921 c = parseChar( doc, length, pos, wsm );
1922
1923 if ( c == '\n' ) // happens only in whitespacepre-mode or with WhiteSpaceNormalWithNewlines.
1924 break; // we want a new line in this case
1925
1926 bool c_isSpace = c.isSpace() && c.unicode() != 0x00a0U &&
1927 curtag.wsm != QStyleSheetItem_WhiteSpaceNoCompression;
1928
1929 if ( curtag.wsm == QStyleSheetItem::WhiteSpaceNormal && c_isSpace && space )
1930 continue;
1931 if ( c == '\r' )
1932 continue;
1933 space = c_isSpace;
1934 s += c;
1935 }
1936 if ( !s.isEmpty() && curtag.style->displayMode() != QStyleSheetItem::DisplayNone ) {
1937 hasNewPar = FALSE;
1938 int index = curpar->length() - 1;
1939 if ( index < 0 )
1940 index = 0;
1941 curpar->append( s );
1942 QTextFormat* f = formatCollection()->format( &curtag.format );
1943 curpar->setFormat( index, s.length(), f, FALSE ); // do not use collection because we have done that already
1944 f->ref += s.length() -1; // that what friends are for...
1945 if ( !curtag.anchorHref.isEmpty() ) {
1946 for ( int i = 0; i < int(s.length()); i++ )
1947 curpar->at(index + i)->setAnchor( QString::null, curtag.anchorHref );
1948 }
1949 if ( !anchorName.isEmpty() ) {
1950 curpar->at(index)->setAnchor( anchorName, curpar->at(index)->anchorHref() );
1951 anchorName = QString::null;
1952 }
1953 }
1954 if ( c == '\n' ) { // happens in WhiteSpacePre mode
1955 hasNewPar = FALSE;
1956 tags.push( curtag );
1957 NEWPAR;
1958 curtag = tags.pop();
1959 }
1960 }
1961 }
1962
1963 if ( hasNewPar && curpar != fParag ) {
1964 // cleanup unused last paragraphs
1965 curpar = curpar->p;
1966 delete curpar->n;
1967 }
1968
1969 if ( !anchorName.isEmpty() ) {
1970 curpar->at(curpar->length() - 1)->setAnchor( anchorName, curpar->at( curpar->length() - 1 )->anchorHref() );
1971 anchorName = QString::null;
1972 }
1973}
1974
1975void QTextDocument::setText( const QString &text, const QString &context )
1976{
1977 focusIndicator.parag = 0;
1978 selections.clear();
1979 if ( txtFormat == Qt::AutoText && QStyleSheet::mightBeRichText( text ) ||
1980 txtFormat == Qt::RichText )
1981 setRichText( text, context );
1982 else
1983 setPlainText( text );
1984}
1985
1986QString QTextDocument::plainText( QTextParag *p ) const
1987{
1988 if ( !p ) {
1989 QString buffer;
1990 QString s;
1991 QTextParag *p = fParag;
1992 while ( p ) {
1993 if ( !p->mightHaveCustomItems ) {
1994 s = p->string()->toString();
1995 } else {
1996 for ( int i = 0; i < p->length() - 1; ++i ) {
1997 if ( p->at( i )->isCustom() ) {
1998 if ( p->at( i )->customItem()->isNested() ) {
1999 s += "\n";
2000 QTextTable *t = (QTextTable*)p->at( i )->customItem();
2001 QPtrList<QTextTableCell> cells = t->tableCells();
2002 for ( QTextTableCell *c = cells.first(); c; c = cells.next() )
2003 s += c->richText()->plainText() + "\n";
2004 s += "\n";
2005 }
2006 } else {
2007 s += p->at( i )->c;
2008 }
2009 }
2010 }
2011 s.remove( s.length() - 1, 1 );
2012 if ( p->next() )
2013 s += "\n";
2014 buffer += s;
2015 p = p->next();
2016 }
2017 return buffer;
2018 } else {
2019 return p->string()->toString();
2020 }
2021}
2022
2023static QString align_to_string( const QString &tag, int a )
2024{
2025 if ( tag == "p" || tag == "li" || ( tag[0] == 'h' && tag[1].isDigit() ) ) {
2026 if ( a & Qt::AlignRight )
2027 return " align=\"right\"";
2028 if ( a & Qt::AlignCenter )
2029 return " align=\"center\"";
2030 if ( a & Qt3::AlignJustify )
2031 return " align=\"justify\"";
2032 }
2033 return "";
2034}
2035
2036static QString direction_to_string( const QString &tag, int d )
2037{
2038 if ( d != QChar::DirON &&
2039 ( tag == "p" || tag == "div" || tag == "li" || ( tag[0] == 'h' && tag[1].isDigit() ) ) )
2040 return ( d == QChar::DirL? " dir=\"ltr\"" : " dir=\"rtl\"" );
2041 return "";
2042}
2043
2044QString QTextDocument::richText( QTextParag *p ) const
2045{
2046 QString s,n;
2047 if ( !p ) {
2048 p = fParag;
2049 QPtrVector<QStyleSheetItem> lastItems, items;
2050 while ( p ) {
2051 items = p->styleSheetItems();
2052 if ( items.size() ) {
2053 QStyleSheetItem *item = items[ items.size() - 1 ];
2054 items.resize( items.size() - 1 );
2055 if ( items.size() > lastItems.size() ) {
2056 for ( int i = lastItems.size(); i < (int)items.size(); ++i ) {
2057 n = items[i]->name();
2058 if ( n.isEmpty() || n == "li" )
2059 continue;
2060 s += "<" + n + align_to_string( n, p->alignment() ) + ">";
2061 }
2062 } else {
2063 QString end;
2064 for ( int i = items.size(); i < (int)lastItems.size(); ++i ) {
2065 n = lastItems[i]->name();
2066 if ( n.isEmpty() || n == "li" || n == "br" )
2067 continue;
2068 end.prepend( "</" + lastItems[ i ]->name() + ">" );
2069 }
2070 s += end;
2071 }
2072 lastItems = items;
2073 n = item->name();
2074 if ( n == "li" && p->listValue() != -1 ) {
2075 s += "<li value=\"" + QString::number( p->listValue() ) + "\">";
2076 } else {
2077 QString ps = p->richText();
2078 if ( ps.isEmpty() )
2079 s += "<br>"; // empty paragraph
2080 else if ( !n.isEmpty() ) {
2081 s += "<" + n + align_to_string( n, p->alignment() )
2082 + direction_to_string( n, p->direction() ) + ">" + ps;
2083 if ( n != "li" && n != "br")
2084 s += "</" + n + ">";
2085 } else
2086 s += ps;
2087 }
2088 } else {
2089 QString end;
2090 for ( int i = 0; i < (int)lastItems.size(); ++i ) {
2091 QString n = lastItems[i]->name();
2092 if ( n.isEmpty() || n == "li" || n == "br" )
2093 continue;
2094 end.prepend( "</" + n + ">" );
2095 }
2096 s += end;
2097 QString ps = p->richText();
2098 if ( ps.isEmpty() )
2099 s += "<br>"; // empty paragraph
2100 else
2101 s += "<p" + align_to_string( "p", p->alignment() ) + direction_to_string( "p", p->direction() )
2102 + ">" + ps + "</p>";
2103 lastItems = items;
2104 }
2105 if ( ( p = p->next() ) )
2106 s += '\n';
2107 }
2108 } else {
2109 s = p->richText();
2110 }
2111
2112 return s;
2113}
2114
2115QString QTextDocument::text() const
2116{
2117 if ( plainText().simplifyWhiteSpace().isEmpty() )
2118 return QString("");
2119 if ( txtFormat == Qt::AutoText && preferRichText || txtFormat == Qt::RichText )
2120 return richText();
2121 return plainText( 0 );
2122}
2123
2124QString QTextDocument::text( int parag ) const
2125{
2126 QTextParag *p = paragAt( parag );
2127 if ( !p )
2128 return QString::null;
2129
2130 if ( txtFormat == Qt::AutoText && preferRichText || txtFormat == Qt::RichText )
2131 return richText( p );
2132 else
2133 return plainText( p );
2134}
2135
2136void QTextDocument::invalidate()
2137{
2138 QTextParag *s = fParag;
2139 while ( s ) {
2140 s->invalidate( 0 );
2141 s = s->next();
2142 }
2143}
2144
2145void QTextDocument::selectionStart( int id, int &paragId, int &index )
2146{
2147 QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id );
2148 if ( it == selections.end() )
2149 return;
2150 QTextDocumentSelection &sel = *it;
2151 paragId = !sel.swapped ? sel.startCursor.parag()->paragId() : sel.endCursor.parag()->paragId();
2152 index = !sel.swapped ? sel.startCursor.index() : sel.endCursor.index();
2153}
2154
2155QTextCursor QTextDocument::selectionStartCursor( int id)
2156{
2157 QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id );
2158 if ( it == selections.end() )
2159 return QTextCursor( this );
2160 QTextDocumentSelection &sel = *it;
2161 if ( sel.swapped )
2162 return sel.endCursor;
2163 return sel.startCursor;
2164}
2165
2166QTextCursor QTextDocument::selectionEndCursor( int id)
2167{
2168 QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id );
2169 if ( it == selections.end() )
2170 return QTextCursor( this );
2171 QTextDocumentSelection &sel = *it;
2172 if ( !sel.swapped )
2173 return sel.endCursor;
2174 return sel.startCursor;
2175}
2176
2177void QTextDocument::selectionEnd( int id, int &paragId, int &index )
2178{
2179 QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id );
2180 if ( it == selections.end() )
2181 return;
2182 QTextDocumentSelection &sel = *it;
2183 paragId = sel.swapped ? sel.startCursor.parag()->paragId() : sel.endCursor.parag()->paragId();
2184 index = sel.swapped ? sel.startCursor.index() : sel.endCursor.index();
2185}
2186
2187QTextParag *QTextDocument::selectionStart( int id )
2188{
2189 QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id );
2190 if ( it == selections.end() )
2191 return 0;
2192 QTextDocumentSelection &sel = *it;
2193 if ( sel.startCursor.parag()->paragId() < sel.endCursor.parag()->paragId() )
2194 return sel.startCursor.parag();
2195 return sel.endCursor.parag();
2196}
2197
2198QTextParag *QTextDocument::selectionEnd( int id )
2199{
2200 QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id );
2201 if ( it == selections.end() )
2202 return 0;
2203 QTextDocumentSelection &sel = *it;
2204 if ( sel.startCursor.parag()->paragId() > sel.endCursor.parag()->paragId() )
2205 return sel.startCursor.parag();
2206 return sel.endCursor.parag();
2207}
2208
2209void QTextDocument::addSelection( int id )
2210{
2211 nSelections = QMAX( nSelections, id + 1 );
2212}
2213
2214static void setSelectionEndHelper( int id, QTextDocumentSelection &sel, QTextCursor &start, QTextCursor &end )
2215{
2216 QTextCursor c1 = start;
2217 QTextCursor c2 = end;
2218 if ( sel.swapped ) {
2219 c1 = end;
2220 c2 = start;
2221 }
2222
2223 c1.parag()->removeSelection( id );
2224 c2.parag()->removeSelection( id );
2225 if ( c1.parag() != c2.parag() ) {
2226 c1.parag()->setSelection( id, c1.index(), c1.parag()->length() - 1 );
2227 c2.parag()->setSelection( id, 0, c2.index() );
2228 } else {
2229 c1.parag()->setSelection( id, QMIN( c1.index(), c2.index() ), QMAX( c1.index(), c2.index() ) );
2230 }
2231
2232 sel.startCursor = start;
2233 sel.endCursor = end;
2234 if ( sel.startCursor.parag() == sel.endCursor.parag() )
2235 sel.swapped = sel.startCursor.index() > sel.endCursor.index();
2236}
2237
2238bool QTextDocument::setSelectionEnd( int id, QTextCursor *cursor )
2239{
2240 QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id );
2241 if ( it == selections.end() )
2242 return FALSE;
2243 QTextDocumentSelection &sel = *it;
2244
2245 QTextCursor start = sel.startCursor;
2246 QTextCursor end = *cursor;
2247
2248 if ( start == end ) {
2249 removeSelection( id );
2250 setSelectionStart( id, cursor );
2251 return TRUE;
2252 }
2253
2254 if ( sel.endCursor.parag() == end.parag() ) {
2255 setSelectionEndHelper( id, sel, start, end );
2256 return TRUE;
2257 }
2258
2259 bool inSelection = FALSE;
2260 QTextCursor c( this );
2261 QTextCursor tmp = sel.startCursor;
2262 if ( sel.swapped )
2263 tmp = sel.endCursor;
2264 tmp.restoreState();
2265 QTextCursor tmp2 = *cursor;
2266 tmp2.restoreState();
2267 c.setParag( tmp.parag()->paragId() < tmp2.parag()->paragId() ? tmp.parag() : tmp2.parag() );
2268 QTextCursor old;
2269 bool hadStart = FALSE;
2270 bool hadEnd = FALSE;
2271 bool hadStartParag = FALSE;
2272 bool hadEndParag = FALSE;
2273 bool hadOldStart = FALSE;
2274 bool hadOldEnd = FALSE;
2275 bool leftSelection = FALSE;
2276 sel.swapped = FALSE;
2277 for ( ;; ) {
2278 if ( c == start )
2279 hadStart = TRUE;
2280 if ( c == end )
2281 hadEnd = TRUE;
2282 if ( c.parag() == start.parag() )
2283 hadStartParag = TRUE;
2284 if ( c.parag() == end.parag() )
2285 hadEndParag = TRUE;
2286 if ( c == sel.startCursor )
2287 hadOldStart = TRUE;
2288 if ( c == sel.endCursor )
2289 hadOldEnd = TRUE;
2290
2291 if ( !sel.swapped &&
2292 ( hadEnd && !hadStart ||
2293 hadEnd && hadStart && start.parag() == end.parag() && start.index() > end.index() ) )
2294 sel.swapped = TRUE;
2295
2296 if ( c == end && hadStartParag ||
2297 c == start && hadEndParag ) {
2298 QTextCursor tmp = c;
2299 tmp.restoreState();
2300 if ( tmp.parag() != c.parag() ) {
2301 int sstart = tmp.parag()->selectionStart( id );
2302 tmp.parag()->removeSelection( id );
2303 tmp.parag()->setSelection( id, sstart, tmp.index() );
2304 }
2305 }
2306
2307 if ( inSelection &&
2308 ( c == end && hadStart || c == start && hadEnd ) )
2309 leftSelection = TRUE;
2310 else if ( !leftSelection && !inSelection && ( hadStart || hadEnd ) )
2311 inSelection = TRUE;
2312
2313 bool noSelectionAnymore = hadOldStart && hadOldEnd && leftSelection && !inSelection && !c.parag()->hasSelection( id ) && c.atParagEnd();
2314 c.parag()->removeSelection( id );
2315 if ( inSelection ) {
2316 if ( c.parag() == start.parag() && start.parag() == end.parag() ) {
2317 c.parag()->setSelection( id, QMIN( start.index(), end.index() ), QMAX( start.index(), end.index() ) );
2318 } else if ( c.parag() == start.parag() && !hadEndParag ) {
2319 c.parag()->setSelection( id, start.index(), c.parag()->length() - 1 );
2320 } else if ( c.parag() == end.parag() && !hadStartParag ) {
2321 c.parag()->setSelection( id, end.index(), c.parag()->length() - 1 );
2322 } else if ( c.parag() == end.parag() && hadEndParag ) {
2323 c.parag()->setSelection( id, 0, end.index() );
2324 } else if ( c.parag() == start.parag() && hadStartParag ) {
2325 c.parag()->setSelection( id, 0, start.index() );
2326 } else {
2327 c.parag()->setSelection( id, 0, c.parag()->length() - 1 );
2328 }
2329 }
2330
2331 if ( leftSelection )
2332 inSelection = FALSE;
2333
2334 old = c;
2335 c.gotoNextLetter();
2336 if ( old == c || noSelectionAnymore )
2337 break;
2338 }
2339
2340 if ( !sel.swapped )
2341 sel.startCursor.parag()->setSelection( id, sel.startCursor.index(), sel.startCursor.parag()->length() - 1 );
2342
2343 sel.startCursor = start;
2344 sel.endCursor = end;
2345 if ( sel.startCursor.parag() == sel.endCursor.parag() )
2346 sel.swapped = sel.startCursor.index() > sel.endCursor.index();
2347
2348 setSelectionEndHelper( id, sel, start, end );
2349
2350 return TRUE;
2351}
2352
2353void QTextDocument::selectAll( int id )
2354{
2355 removeSelection( id );
2356
2357 QTextDocumentSelection sel;
2358 sel.swapped = FALSE;
2359 QTextCursor c( this );
2360
2361 c.setParag( fParag );
2362 c.setIndex( 0 );
2363 sel.startCursor = c;
2364
2365 c.setParag( lParag );
2366 c.setIndex( lParag->length() - 1 );
2367 sel.endCursor = c;
2368
2369 QTextParag *p = fParag;
2370 while ( p ) {
2371 p->setSelection( id, 0, p->length() - 1 );
2372 for ( int i = 0; i < (int)p->length(); ++i ) {
2373 if ( p->at( i )->isCustom() && p->at( i )->customItem()->isNested() ) {
2374 QTextTable *t = (QTextTable*)p->at( i )->customItem();
2375 QPtrList<QTextTableCell> tableCells = t->tableCells();
2376 for ( QTextTableCell *c = tableCells.first(); c; c = tableCells.next() )
2377 c->richText()->selectAll( id );
2378 }
2379 }
2380 p = p->next();
2381 }
2382
2383 selections.insert( id, sel );
2384}
2385
2386bool QTextDocument::removeSelection( int id )
2387{
2388 QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id );
2389 if ( it == selections.end() )
2390 return FALSE;
2391
2392 QTextDocumentSelection &sel = *it;
2393
2394 if ( sel.startCursor == sel.endCursor ) {
2395 selections.remove( id );
2396 return TRUE;
2397 }
2398
2399 if ( !mightHaveCustomItems ) {
2400 QTextCursor start = sel.startCursor;
2401 QTextCursor end = sel.endCursor;
2402 if ( sel.swapped ) {
2403 start = sel.endCursor;
2404 end = sel.startCursor;
2405 }
2406
2407 for ( QTextParag *p = start.parag(); p; p = p->next() ) {
2408 p->removeSelection( id );
2409 if ( p == end.parag() )
2410 break;
2411 }
2412
2413 selections.remove( id );
2414 return TRUE;
2415 }
2416
2417 QTextCursor c( this );
2418 QTextCursor tmp = sel.startCursor;
2419 if ( sel.swapped )
2420 tmp = sel.endCursor;
2421 tmp.restoreState();
2422 c.setParag( tmp.parag() );
2423 QTextCursor old;
2424 bool hadStart = FALSE;
2425 bool hadEnd = FALSE;
2426 QTextParag *lastParag = 0;
2427 bool leftSelection = FALSE;
2428 bool inSelection = FALSE;
2429 sel.swapped = FALSE;
2430 for ( ;; ) {
2431 if ( c.parag() == sel.startCursor.parag() )
2432 hadStart = TRUE;
2433 if ( c.parag() == sel.endCursor.parag() )
2434 hadEnd = TRUE;
2435
2436 if ( inSelection &&
2437 ( c == sel.endCursor && hadStart || c == sel.startCursor && hadEnd ) )
2438 leftSelection = TRUE;
2439 else if ( !leftSelection && !inSelection && ( c.parag() == sel.startCursor.parag() || c.parag() == sel.endCursor.parag() ) )
2440 inSelection = TRUE;
2441
2442 bool noSelectionAnymore = leftSelection && !inSelection && !c.parag()->hasSelection( id ) && c.atParagEnd();
2443
2444 if ( lastParag != c.parag() )
2445 c.parag()->removeSelection( id );
2446
2447 old = c;
2448 lastParag = c.parag();
2449 c.gotoNextLetter();
2450 if ( old == c || noSelectionAnymore )
2451 break;
2452 }
2453
2454 selections.remove( id );
2455 return TRUE;
2456}
2457
2458QString QTextDocument::selectedText( int id, bool withCustom ) const
2459{
2460 // ######## TODO: look at textFormat() and return rich text or plain text (like the text() method!)
2461 QMap<int, QTextDocumentSelection>::ConstIterator it = selections.find( id );
2462 if ( it == selections.end() )
2463 return QString::null;
2464
2465 QTextDocumentSelection sel = *it;
2466
2467
2468 QTextCursor c1 = sel.startCursor;
2469 QTextCursor c2 = sel.endCursor;
2470 if ( sel.swapped ) {
2471 c2 = sel.startCursor;
2472 c1 = sel.endCursor;
2473 }
2474
2475 /* 3.0.3 improvement: Make it possible to get a reasonable
2476 selection inside a table. This approach is very conservative:
2477 make sure that both cursors have the same depth level and point
2478 to paragraphs within the same text document.
2479
2480 Meaning if you select text in two table cells, you will get the
2481 entire table. This is still far better than the 3.0.2, where
2482 you always got the entire table.
2483
2484 ### Fix this properly for 3.0.4.
2485 */
2486 while ( c2.nestedDepth() > c1.nestedDepth() )
2487 c2.oneUp();
2488 while ( c1.nestedDepth() > c2.nestedDepth() )
2489 c1.oneUp();
2490 while ( c1.nestedDepth() && c2.nestedDepth() &&
2491 c1.parag()->document() != c2.parag()->document() ) {
2492 c1.oneUp();
2493 c2.oneUp();
2494 }
2495 // do not trust sel_swapped with tables. Fix this properly for 3.0.4 as well
2496 if ( c1.parag()->paragId() > c2.parag()->paragId() ||
2497 (c1.parag() == c2.parag() && c1.index() > c2.index() ) ) {
2498 QTextCursor tmp = c1;
2499 c2 = c1;
2500 c1 = tmp;
2501 }
2502
2503 // end selection 3.0.3 improvement
2504
2505
2506 if ( c1.parag() == c2.parag() ) {
2507 QString s;
2508 QTextParag *p = c1.parag();
2509 int end = c2.index();
2510 if ( p->at( QMAX( 0, end - 1 ) )->isCustom() )
2511 ++end;
2512 if ( !withCustom || !p->mightHaveCustomItems ) {
2513 s += p->string()->toString().mid( c1.index(), end - c1.index() );
2514 } else {
2515 for ( int i = c1.index(); i < end; ++i ) {
2516 if ( p->at( i )->isCustom() ) {
2517 if ( p->at( i )->customItem()->isNested() ) {
2518 s += "\n";
2519 QTextTable *t = (QTextTable*)p->at( i )->customItem();
2520 QPtrList<QTextTableCell> cells = t->tableCells();
2521 for ( QTextTableCell *c = cells.first(); c; c = cells.next() )
2522 s += c->richText()->plainText() + "\n";
2523 s += "\n";
2524 }
2525 } else {
2526 s += p->at( i )->c;
2527 }
2528 }
2529 }
2530 return s;
2531 }
2532
2533 QString s;
2534 QTextParag *p = c1.parag();
2535 int start = c1.index();
2536 while ( p ) {
2537 int end = p == c2.parag() ? c2.index() : p->length() - 1;
2538 if ( p == c2.parag() && p->at( QMAX( 0, end - 1 ) )->isCustom() )
2539 ++end;
2540 if ( !withCustom || !p->mightHaveCustomItems ) {
2541 s += p->string()->toString().mid( start, end - start );
2542 if ( p != c2.parag() )
2543 s += "\n";
2544 } else {
2545 for ( int i = start; i < end; ++i ) {
2546 if ( p->at( i )->isCustom() ) {
2547 if ( p->at( i )->customItem()->isNested() ) {
2548 s += "\n";
2549 QTextTable *t = (QTextTable*)p->at( i )->customItem();
2550 QPtrList<QTextTableCell> cells = t->tableCells();
2551 for ( QTextTableCell *c = cells.first(); c; c = cells.next() )
2552 s += c->richText()->plainText() + "\n";
2553 s += "\n";
2554 }
2555 } else {
2556 s += p->at( i )->c;
2557 }
2558 }
2559 }
2560 start = 0;
2561 if ( p == c2.parag() )
2562 break;
2563 p = p->next();
2564 }
2565 return s;
2566}
2567
2568void QTextDocument::setFormat( int id, QTextFormat *f, int flags )
2569{
2570 QMap<int, QTextDocumentSelection>::ConstIterator it = selections.find( id );
2571 if ( it == selections.end() )
2572 return;
2573
2574 QTextDocumentSelection sel = *it;
2575
2576 QTextCursor c1 = sel.startCursor;
2577 QTextCursor c2 = sel.endCursor;
2578 if ( sel.swapped ) {
2579 c2 = sel.startCursor;
2580 c1 = sel.endCursor;
2581 }
2582
2583 c2.restoreState();
2584 c1.restoreState();
2585
2586 if ( c1.parag() == c2.parag() ) {
2587 c1.parag()->setFormat( c1.index(), c2.index() - c1.index(), f, TRUE, flags );
2588 return;
2589 }
2590
2591 c1.parag()->setFormat( c1.index(), c1.parag()->length() - c1.index(), f, TRUE, flags );
2592 QTextParag *p = c1.parag()->next();
2593 while ( p && p != c2.parag() ) {
2594 p->setFormat( 0, p->length(), f, TRUE, flags );
2595 p = p->next();
2596 }
2597 c2.parag()->setFormat( 0, c2.index(), f, TRUE, flags );
2598}
2599
2600void QTextDocument::copySelectedText( int id )
2601{
2602#ifndef QT_NO_CLIPBOARD
2603 if ( !hasSelection( id ) )
2604 return;
2605
2606 QApplication::clipboard()->setText( selectedText( id ) );
2607#endif
2608}
2609
2610void QTextDocument::removeSelectedText( int id, QTextCursor *cursor )
2611{
2612 QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id );
2613 if ( it == selections.end() )
2614 return;
2615
2616 QTextDocumentSelection sel = *it;
2617
2618 QTextCursor c1 = sel.startCursor;
2619 QTextCursor c2 = sel.endCursor;
2620 if ( sel.swapped ) {
2621 c2 = sel.startCursor;
2622 c1 = sel.endCursor;
2623 }
2624
2625 // ### no support for editing tables yet
2626 if ( c1.nestedDepth() || c2.nestedDepth() )
2627 return;
2628
2629 c2.restoreState();
2630 c1.restoreState();
2631
2632 *cursor = c1;
2633 removeSelection( id );
2634
2635 if ( c1.parag() == c2.parag() ) {
2636 c1.parag()->remove( c1.index(), c2.index() - c1.index() );
2637 return;
2638 }
2639
2640 if ( c1.parag() == fParag && c1.index() == 0 &&
2641 c2.parag() == lParag && c2.index() == lParag->length() - 1 )
2642 cursor->setValid( FALSE );
2643
2644 bool didGoLeft = FALSE;
2645 if ( c1.index() == 0 && c1.parag() != fParag ) {
2646 cursor->gotoPreviousLetter();
2647 if ( cursor->isValid() )
2648 didGoLeft = TRUE;
2649 }
2650
2651 c1.parag()->remove( c1.index(), c1.parag()->length() - 1 - c1.index() );
2652 QTextParag *p = c1.parag()->next();
2653 int dy = 0;
2654 QTextParag *tmp;
2655 while ( p && p != c2.parag() ) {
2656 tmp = p->next();
2657 dy -= p->rect().height();
2658 delete p;
2659 p = tmp;
2660 }
2661 c2.parag()->remove( 0, c2.index() );
2662 while ( p ) {
2663 p->move( dy );
2664 p->invalidate( 0 );
2665 p->setEndState( -1 );
2666 p = p->next();
2667 }
2668
2669 c1.parag()->join( c2.parag() );
2670
2671 if ( didGoLeft )
2672 cursor->gotoNextLetter();
2673}
2674
2675void QTextDocument::indentSelection( int id )
2676{
2677 QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id );
2678 if ( it == selections.end() )
2679 return;
2680
2681 QTextDocumentSelection sel = *it;
2682 QTextParag *startParag = sel.startCursor.parag();
2683 QTextParag *endParag = sel.endCursor.parag();
2684 if ( sel.endCursor.parag()->paragId() < sel.startCursor.parag()->paragId() ) {
2685 endParag = sel.startCursor.parag();
2686 startParag = sel.endCursor.parag();
2687 }
2688
2689 QTextParag *p = startParag;
2690 while ( p && p != endParag ) {
2691 p->indent();
2692 p = p->next();
2693 }
2694}
2695
2696void QTextDocument::addCommand( QTextCommand *cmd )
2697{
2698 commandHistory->addCommand( cmd );
2699}
2700
2701QTextCursor *QTextDocument::undo( QTextCursor *c )
2702{
2703 return commandHistory->undo( c );
2704}
2705
2706QTextCursor *QTextDocument::redo( QTextCursor *c )
2707{
2708 return commandHistory->redo( c );
2709}
2710
2711bool QTextDocument::find( const QString &expr, bool cs, bool wo, bool forward,
2712 int *parag, int *index, QTextCursor *cursor )
2713{
2714 QTextParag *p = forward ? fParag : lParag;
2715 if ( parag )
2716 p = paragAt( *parag );
2717 else if ( cursor )
2718 p = cursor->parag();
2719 bool first = TRUE;
2720
2721 while ( p ) {
2722 QString s = p->string()->toString();
2723 s.remove( s.length() - 1, 1 ); // get rid of trailing space
2724 int start = forward ? 0 : s.length() - 1;
2725 if ( first && index )
2726 start = *index;
2727 else if ( first )
2728 start = cursor->index();
2729 if ( !forward && first ) {
2730 start -= expr.length() + 1;
2731 if ( start < 0 ) {
2732 first = FALSE;
2733 p = p->prev();
2734 continue;
2735 }
2736 }
2737 first = FALSE;
2738
2739 for ( ;; ) {
2740 int res = forward ? s.find( expr, start, cs ) : s.findRev( expr, start, cs );
2741 if ( res == -1 )
2742 break;
2743
2744 bool ok = TRUE;
2745 if ( wo ) {
2746 int end = res + expr.length();
2747 if ( ( res == 0 || s[ res - 1 ].isSpace() || s[ res - 1 ].isPunct() ) &&
2748 ( end == (int)s.length() || s[ end ].isSpace() || s[ end ].isPunct() ) )
2749 ok = TRUE;
2750 else
2751 ok = FALSE;
2752 }
2753 if ( ok ) {
2754 cursor->setParag( p );
2755 cursor->setIndex( res );
2756 setSelectionStart( Standard, cursor );
2757 cursor->setIndex( res + expr.length() );
2758 setSelectionEnd( Standard, cursor );
2759 if ( parag )
2760 *parag = p->paragId();
2761 if ( index )
2762 *index = res;
2763 return TRUE;
2764 }
2765 if ( forward ) {
2766 start = res + 1;
2767 } else {
2768 if ( res == 0 )
2769 break;
2770 start = res - 1;
2771 }
2772 }
2773 p = forward ? p->next() : p->prev();
2774 }
2775
2776 return FALSE;
2777}
2778
2779void QTextDocument::setTextFormat( Qt::TextFormat f )
2780{
2781 txtFormat = f;
2782 if ( txtFormat == Qt::RichText && fParag && fParag == lParag && fParag->length() <= 1 ) {
2783 QPtrVector<QStyleSheetItem> v = fParag->styleSheetItems();
2784 v.resize( v.size() + 1 );
2785 v.insert( v.size() - 1, styleSheet()->item( "p" ) );
2786 fParag->setStyleSheetItems( v );
2787 }
2788
2789}
2790
2791Qt::TextFormat QTextDocument::textFormat() const
2792{
2793 return txtFormat;
2794}
2795
2796bool QTextDocument::inSelection( int selId, const QPoint &pos ) const
2797{
2798 QMap<int, QTextDocumentSelection>::ConstIterator it = selections.find( selId );
2799 if ( it == selections.end() )
2800 return FALSE;
2801
2802 QTextDocumentSelection sel = *it;
2803 QTextParag *startParag = sel.startCursor.parag();
2804 QTextParag *endParag = sel.endCursor.parag();
2805 if ( sel.startCursor.parag() == sel.endCursor.parag() &&
2806 sel.startCursor.parag()->selectionStart( selId ) == sel.endCursor.parag()->selectionEnd( selId ) )
2807 return FALSE;
2808 if ( sel.endCursor.parag()->paragId() < sel.startCursor.parag()->paragId() ) {
2809 endParag = sel.startCursor.parag();
2810 startParag = sel.endCursor.parag();
2811 }
2812
2813 QTextParag *p = startParag;
2814 while ( p ) {
2815 if ( p->rect().contains( pos ) ) {
2816 bool inSel = FALSE;
2817 int selStart = p->selectionStart( selId );
2818 int selEnd = p->selectionEnd( selId );
2819 int y = 0;
2820 int h = 0;
2821 for ( int i = 0; i < p->length(); ++i ) {
2822 if ( i == selStart )
2823 inSel = TRUE;
2824 if ( i == selEnd )
2825 break;
2826 if ( p->at( i )->lineStart ) {
2827 y = (*p->lineStarts.find( i ))->y;
2828 h = (*p->lineStarts.find( i ))->h;
2829 }
2830 if ( pos.y() - p->rect().y() >= y && pos.y() - p->rect().y() <= y + h ) {
2831 if ( inSel && pos.x() >= p->at( i )->x &&
2832 pos.x() <= p->at( i )->x + p->at( i )->format()->width( p->at( i )->c ) )
2833 return TRUE;
2834 }
2835 }
2836 }
2837 if ( pos.y() < p->rect().y() )
2838 break;
2839 if ( p == endParag )
2840 break;
2841 p = p->next();
2842 }
2843
2844 return FALSE;
2845}
2846
2847void QTextDocument::doLayout( QPainter *p, int w )
2848{
2849 minw = wused = 0;
2850 if ( !is_printer( p ) )
2851 p = 0;
2852 withoutDoubleBuffer = ( p != 0 );
2853 QPainter * oldPainter = QTextFormat::painter();
2854 QTextFormat::setPainter( p );
2855 flow_->setWidth( w );
2856 cw = w;
2857 vw = w;
2858 QTextParag *parag = fParag;
2859 while ( parag ) {
2860 parag->invalidate( 0 );
2861 if ( p )
2862 parag->adjustToPainter( p );
2863 parag->format();
2864 parag = parag->next();
2865 }
2866 QTextFormat::setPainter( oldPainter );
2867}
2868
2869QPixmap *QTextDocument::bufferPixmap( const QSize &s )
2870{
2871 if ( !buf_pixmap ) {
2872 int w = QABS( s.width() );
2873 int h = QABS( s.height() );
2874 buf_pixmap = new QPixmap( w, h );
2875 } else {
2876 if ( buf_pixmap->width() < s.width() ||
2877 buf_pixmap->height() < s.height() ) {
2878 buf_pixmap->resize( QMAX( s.width(), buf_pixmap->width() ),
2879 QMAX( s.height(), buf_pixmap->height() ) );
2880 }
2881 }
2882
2883 return buf_pixmap;
2884}
2885
2886void QTextDocument::draw( QPainter *p, const QRect &rect, const QColorGroup &cg, const QBrush *paper )
2887{
2888 if ( !firstParag() )
2889 return;
2890
2891 if ( paper ) {
2892 p->setBrushOrigin( 0, 0 );
2893
2894 p->fillRect( rect, *paper );
2895 }
2896
2897 if ( formatCollection()->defaultFormat()->color() != cg.text() ) {
2898 QDict<QTextFormat> formats = formatCollection()->dict();
2899 QDictIterator<QTextFormat> it( formats );
2900 while ( it.current() ) {
2901 if ( it.current() == formatCollection()->defaultFormat() ) {
2902 ++it;
2903 continue;
2904 }
2905 it.current()->setColor( cg.text() );
2906 ++it;
2907 }
2908 formatCollection()->defaultFormat()->setColor( cg.text() );
2909 }
2910
2911 QTextParag *parag = firstParag();
2912 while ( parag ) {
2913 if ( !parag->isValid() )
2914 parag->format();
2915 int y = parag->rect().y();
2916 QRect pr( parag->rect() );
2917 pr.setX( 0 );
2918 pr.setWidth( QWIDGETSIZE_MAX );
2919 if ( !rect.isNull() && !rect.intersects( pr ) ) {
2920 parag = parag->next();
2921 continue;
2922 }
2923 p->translate( 0, y );
2924 if ( rect.isValid() )
2925 parag->paint( *p, cg, 0, FALSE, rect.x(), rect.y(), rect.width(), rect.height() );
2926 else
2927 parag->paint( *p, cg, 0, FALSE );
2928 p->translate( 0, -y );
2929 parag = parag->next();
2930 if ( !flow()->isEmpty() )
2931 flow()->drawFloatingItems( p, rect.x(), rect.y(), rect.width(), rect.height(), cg, FALSE );
2932 }
2933}
2934
2935void QTextDocument::drawParag( QPainter *p, QTextParag *parag, int cx, int cy, int cw, int ch,
2936 QPixmap *&doubleBuffer, const QColorGroup &cg,
2937 bool drawCursor, QTextCursor *cursor, bool resetChanged )
2938{
2939 QPainter *painter = 0;
2940 if ( resetChanged )
2941 parag->setChanged( FALSE );
2942 QRect ir( parag->rect() );
2943 bool useDoubleBuffer = !parag->document()->parent();
2944 if ( !useDoubleBuffer && parag->document()->nextDoubleBuffered )
2945 useDoubleBuffer = TRUE;
2946 if ( is_printer( p ) )
2947 useDoubleBuffer = FALSE;
2948
2949 if ( useDoubleBuffer ) {
2950 painter = new QPainter;
2951 if ( cx >= 0 && cy >= 0 )
2952 ir = ir.intersect( QRect( cx, cy, cw, ch ) );
2953 if ( !doubleBuffer ||
2954 ir.width() > doubleBuffer->width() ||
2955 ir.height() > doubleBuffer->height() ) {
2956 doubleBuffer = bufferPixmap( ir.size() );
2957 painter->begin( doubleBuffer );
2958 } else {
2959 painter->begin( doubleBuffer );
2960 }
2961 } else {
2962 painter = p;
2963 painter->translate( ir.x(), ir.y() );
2964 }
2965
2966 painter->setBrushOrigin( -ir.x(), -ir.y() );
2967
2968 if ( useDoubleBuffer || is_printer( painter ) ) {
2969 if ( !parag->backgroundColor() )
2970 painter->fillRect( QRect( 0, 0, ir.width(), ir.height() ),
2971 cg.brush( QColorGroup::Base ) );
2972 else
2973 painter->fillRect( QRect( 0, 0, ir.width(), ir.height() ),
2974 *parag->backgroundColor() );
2975 } else {
2976 if ( cursor && cursor->parag() == parag ) {
2977 if ( !parag->backgroundColor() )
2978 painter->fillRect( QRect( parag->at( cursor->index() )->x, 0, 2, ir.height() ),
2979 cg.brush( QColorGroup::Base ) );
2980 else
2981 painter->fillRect( QRect( parag->at( cursor->index() )->x, 0, 2, ir.height() ),
2982 *parag->backgroundColor() );
2983 }
2984 }
2985
2986 painter->translate( -( ir.x() - parag->rect().x() ),
2987 -( ir.y() - parag->rect().y() ) );
2988 parag->paint( *painter, cg, drawCursor ? cursor : 0, TRUE, cx, cy, cw, ch );
2989
2990 if ( useDoubleBuffer ) {
2991 delete painter;
2992 painter = 0;
2993 p->drawPixmap( ir.topLeft(), *doubleBuffer, QRect( QPoint( 0, 0 ), ir.size() ) );
2994 } else {
2995 painter->translate( -ir.x(), -ir.y() );
2996 }
2997
2998 if ( parag->rect().x() + parag->rect().width() < parag->document()->x() + parag->document()->width() ) {
2999 p->fillRect( parag->rect().x() + parag->rect().width(), parag->rect().y(),
3000 ( parag->document()->x() + parag->document()->width() ) -
3001 ( parag->rect().x() + parag->rect().width() ),
3002 parag->rect().height(), cg.brush( QColorGroup::Base ) );
3003 }
3004
3005 parag->document()->nextDoubleBuffered = FALSE;
3006}
3007
3008QTextParag *QTextDocument::draw( QPainter *p, int cx, int cy, int cw, int ch, const QColorGroup &cg,
3009 bool onlyChanged, bool drawCursor, QTextCursor *cursor, bool resetChanged )
3010{
3011 if ( withoutDoubleBuffer || par && par->withoutDoubleBuffer ) {
3012 withoutDoubleBuffer = TRUE;
3013 QRect r;
3014 draw( p, r, cg );
3015 return 0;
3016 }
3017 withoutDoubleBuffer = FALSE;
3018
3019 if ( !firstParag() )
3020 return 0;
3021
3022 if ( drawCursor && cursor )
3023 tmpCursor = cursor;
3024 if ( cx < 0 && cy < 0 ) {
3025 cx = 0;
3026 cy = 0;
3027 cw = width();
3028 ch = height();
3029 }
3030
3031 QTextParag *lastFormatted = 0;
3032 QTextParag *parag = firstParag();
3033
3034 QPixmap *doubleBuffer = 0;
3035 QPainter painter;
3036
3037 while ( parag ) {
3038 lastFormatted = parag;
3039 if ( !parag->isValid() )
3040 parag->format();
3041
3042 if ( !parag->rect().intersects( QRect( cx, cy, cw, ch ) ) ) {
3043 QRect pr( parag->rect() );
3044 pr.setWidth( parag->document()->width() );
3045 if ( pr.intersects( QRect( cx, cy, cw, ch ) ) )
3046 p->fillRect( pr.intersect( QRect( cx, cy, cw, ch ) ), cg.brush( QColorGroup::Base ) );
3047 if ( parag->rect().y() > cy + ch ) {
3048 tmpCursor = 0;
3049 goto floating;
3050 }
3051 parag = parag->next();
3052 continue;
3053 }
3054
3055 if ( !parag->hasChanged() && onlyChanged ) {
3056 parag = parag->next();
3057 continue;
3058 }
3059
3060 drawParag( p, parag, cx, cy, cw, ch, doubleBuffer, cg, drawCursor, cursor, resetChanged );
3061 parag = parag->next();
3062 }
3063
3064 parag = lastParag();
3065
3066 floating:
3067 if ( parag->rect().y() + parag->rect().height() < parag->document()->height() ) {
3068 if ( !parag->document()->parent() ) { // !useDoubleBuffer
3069 p->fillRect( 0, parag->rect().y() + parag->rect().height(), parag->document()->width(),
3070 parag->document()->height() - ( parag->rect().y() + parag->rect().height() ),
3071 cg.brush( QColorGroup::Base ) );
3072 }
3073 if ( !flow()->isEmpty() ) {
3074 QRect cr( cx, cy, cw, ch );
3075 // cr = cr.intersect( QRect( 0, parag->rect().y() + parag->rect().height(), parag->document()->width(),
3076 // parag->document()->height() - ( parag->rect().y() + parag->rect().height() ) ) );
3077 flow()->drawFloatingItems( p, cr.x(), cr.y(), cr.width(), cr.height(), cg, FALSE );
3078 }
3079 }
3080
3081 if ( buf_pixmap && buf_pixmap->height() > 300 ) {
3082 delete buf_pixmap;
3083 buf_pixmap = 0;
3084 }
3085
3086 tmpCursor = 0;
3087 return lastFormatted;
3088}
3089
3090void QTextDocument::setDefaultFont( const QFont &f )
3091{
3092 int s = f.pointSize();
3093 bool usePixels = FALSE;
3094 if ( s == -1 ) {
3095 s = f.pixelSize();
3096 usePixels = TRUE;
3097 }
3098 updateFontSizes( s, usePixels );
3099}
3100
3101void QTextDocument::registerCustomItem( QTextCustomItem *i, QTextParag *p )
3102{
3103 if ( i && i->placement() != QTextCustomItem::PlaceInline ) {
3104 flow_->registerFloatingItem( i );
3105 p->registerFloatingItem( i );
3106 i->setParagraph( p );
3107 }
3108 p->mightHaveCustomItems = mightHaveCustomItems = TRUE;
3109}
3110
3111void QTextDocument::unregisterCustomItem( QTextCustomItem *i, QTextParag *p )
3112{
3113 flow_->unregisterFloatingItem( i );
3114 p->unregisterFloatingItem( i );
3115 i->setParagraph( 0 );
3116}
3117
3118bool QTextDocument::hasFocusParagraph() const
3119{
3120 return !!focusIndicator.parag;
3121}
3122
3123QString QTextDocument::focusHref() const
3124{
3125 return focusIndicator.href;
3126}
3127
3128bool QTextDocument::focusNextPrevChild( bool next )
3129{
3130 if ( !focusIndicator.parag ) {
3131 if ( next ) {
3132 focusIndicator.parag = fParag;
3133 focusIndicator.start = 0;
3134 focusIndicator.len = 0;
3135 } else {
3136 focusIndicator.parag = lParag;
3137 focusIndicator.start = lParag->length();
3138 focusIndicator.len = 0;
3139 }
3140 } else {
3141 focusIndicator.parag->setChanged( TRUE );
3142 }
3143 focusIndicator.href = QString::null;
3144
3145 if ( next ) {
3146 QTextParag *p = focusIndicator.parag;
3147 int index = focusIndicator.start + focusIndicator.len;
3148 while ( p ) {
3149 for ( int i = index; i < p->length(); ++i ) {
3150 if ( p->at( i )->isAnchor() ) {
3151 p->setChanged( TRUE );
3152 focusIndicator.parag = p;
3153 focusIndicator.start = i;
3154 focusIndicator.len = 0;
3155 focusIndicator.href = p->at( i )->anchorHref();
3156 while ( i < p->length() ) {
3157 if ( !p->at( i )->isAnchor() )
3158 return TRUE;
3159 focusIndicator.len++;
3160 i++;
3161 }
3162 } else if ( p->at( i )->isCustom() ) {
3163 if ( p->at( i )->customItem()->isNested() ) {
3164 QTextTable *t = (QTextTable*)p->at( i )->customItem();
3165 QPtrList<QTextTableCell> cells = t->tableCells();
3166 // first try to continue
3167 QTextTableCell *c;
3168 bool resetCells = TRUE;
3169 for ( c = cells.first(); c; c = cells.next() ) {
3170 if ( c->richText()->hasFocusParagraph() ) {
3171 if ( c->richText()->focusNextPrevChild( next ) ) {
3172 p->setChanged( TRUE );
3173 focusIndicator.parag = p;
3174 focusIndicator.start = i;
3175 focusIndicator.len = 0;
3176 focusIndicator.href = c->richText()->focusHref();
3177 return TRUE;
3178 } else {
3179 resetCells = FALSE;
3180 c = cells.next();
3181 break;
3182 }
3183 }
3184 }
3185 // now really try
3186 if ( resetCells )
3187 c = cells.first();
3188 for ( ; c; c = cells.next() ) {
3189 if ( c->richText()->focusNextPrevChild( next ) ) {
3190 p->setChanged( TRUE );
3191 focusIndicator.parag = p;
3192 focusIndicator.start = i;
3193 focusIndicator.len = 0;
3194 focusIndicator.href = c->richText()->focusHref();
3195 return TRUE;
3196 }
3197 }
3198 }
3199 }
3200 }
3201 index = 0;
3202 p = p->next();
3203 }
3204 } else {
3205 QTextParag *p = focusIndicator.parag;
3206 int index = focusIndicator.start - 1;
3207 if ( focusIndicator.len == 0 && index < focusIndicator.parag->length() - 1 )
3208 index++;
3209 while ( p ) {
3210 for ( int i = index; i >= 0; --i ) {
3211 if ( p->at( i )->isAnchor() ) {
3212 p->setChanged( TRUE );
3213 focusIndicator.parag = p;
3214 focusIndicator.start = i;
3215 focusIndicator.len = 0;
3216 focusIndicator.href = p->at( i )->anchorHref();
3217 while ( i >= -1 ) {
3218 if ( i < 0 || !p->at( i )->isAnchor() ) {
3219 focusIndicator.start++;
3220 return TRUE;
3221 }
3222 if ( i < 0 )
3223 break;
3224 focusIndicator.len++;
3225 focusIndicator.start--;
3226 i--;
3227 }
3228 } else if ( p->at( i )->isCustom() ) {
3229 if ( p->at( i )->customItem()->isNested() ) {
3230 QTextTable *t = (QTextTable*)p->at( i )->customItem();
3231 QPtrList<QTextTableCell> cells = t->tableCells();
3232 // first try to continue
3233 QTextTableCell *c;
3234 bool resetCells = TRUE;
3235 for ( c = cells.last(); c; c = cells.prev() ) {
3236 if ( c->richText()->hasFocusParagraph() ) {
3237 if ( c->richText()->focusNextPrevChild( next ) ) {
3238 p->setChanged( TRUE );
3239 focusIndicator.parag = p;
3240 focusIndicator.start = i;
3241 focusIndicator.len = 0;
3242 focusIndicator.href = c->richText()->focusHref();
3243 return TRUE;
3244 } else {
3245 resetCells = FALSE;
3246 c = cells.prev();
3247 break;
3248 }
3249 }
3250 if ( cells.at() == 0 )
3251 break;
3252 }
3253 // now really try
3254 if ( resetCells )
3255 c = cells.last();
3256 for ( ; c; c = cells.prev() ) {
3257 if ( c->richText()->focusNextPrevChild( next ) ) {
3258 p->setChanged( TRUE );
3259 focusIndicator.parag = p;
3260 focusIndicator.start = i;
3261 focusIndicator.len = 0;
3262 focusIndicator.href = c->richText()->focusHref();
3263 return TRUE;
3264 }
3265 if ( cells.at() == 0 )
3266 break;
3267 }
3268 }
3269 }
3270 }
3271 p = p->prev();
3272 if ( p )
3273 index = p->length() - 1;
3274 }
3275 }
3276
3277 focusIndicator.parag = 0;
3278
3279 return FALSE;
3280}
3281
3282int QTextDocument::length() const
3283{
3284 int l = 0;
3285 QTextParag *p = fParag;
3286 while ( p ) {
3287 l += p->length() - 1; // don't count trailing space
3288 p = p->next();
3289 }
3290 return l;
3291}
3292
3293// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3294
3295int QTextFormat::width( const QChar &c ) const
3296{
3297 if ( c.unicode() == 0xad ) // soft hyphen
3298 return 0;
3299 if ( !pntr || !pntr->isActive() ) {
3300 if ( c == '\t' )
3301 return fm.width( 'x' ) * 8;
3302 if ( ha == AlignNormal ) {
3303 int w;
3304 if ( c.row() )
3305 w = fm.width( c );
3306 else
3307 w = widths[ c.unicode() ];
3308 if ( w == 0 && !c.row() ) {
3309 w = fm.width( c );
3310 ( (QTextFormat*)this )->widths[ c.unicode() ] = w;
3311 }
3312 return w;
3313 } else {
3314 QFont f( fn );
3315 if ( usePixelSizes )
3316 f.setPixelSize( ( f.pixelSize() * 2 ) / 3 );
3317 else
3318 f.setPointSize( ( f.pointSize() * 2 ) / 3 );
3319 QFontMetrics fm_( f );
3320 return fm_.width( c );
3321 }
3322 }
3323
3324 QFont f( fn );
3325 if ( ha != AlignNormal ) {
3326 if ( usePixelSizes )
3327 f.setPixelSize( ( f.pixelSize() * 2 ) / 3 );
3328 else
3329 f.setPointSize( ( f.pointSize() * 2 ) / 3 );
3330 }
3331 pntr->setFont( f );
3332
3333 return pntr->fontMetrics().width( c );
3334}
3335
3336int QTextFormat::width( const QString &str, int pos ) const
3337{
3338 int w = 0;
3339 if ( str[ pos ].unicode() == 0xad )
3340 return w;
3341 if ( !pntr || !pntr->isActive() ) {
3342 if ( ha == AlignNormal ) {
3343 w = fm.width( str[ pos ] );
3344 } else {
3345 QFont f( fn );
3346 if ( usePixelSizes )
3347 f.setPixelSize( ( f.pixelSize() * 2 ) / 3 );
3348 else
3349 f.setPointSize( ( f.pointSize() * 2 ) / 3 );
3350 QFontMetrics fm_( f );
3351 w = fm_.width( str[ pos ] );
3352 }
3353 } else {
3354 QFont f( fn );
3355 if ( ha != AlignNormal ) {
3356 if ( usePixelSizes )
3357 f.setPixelSize( ( f.pixelSize() * 2 ) / 3 );
3358 else
3359 f.setPointSize( ( f.pointSize() * 2 ) / 3 );
3360 }
3361 pntr->setFont( f );
3362 w = pntr->fontMetrics().width( str[ pos ] );
3363 }
3364 return w;
3365}
3366
3367// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3368
3369QTextString::QTextString()
3370{
3371 bidiDirty = FALSE;
3372 bidi = FALSE;
3373 rightToLeft = FALSE;
3374 dir = QChar::DirON;
3375}
3376
3377QTextString::QTextString( const QTextString &s )
3378{
3379 bidiDirty = s.bidiDirty;
3380 bidi = s.bidi;
3381 rightToLeft = s.rightToLeft;
3382 dir = s.dir;
3383 data = s.subString();
3384}
3385
3386void QTextString::insert( int index, const QString &s, QTextFormat *f )
3387{
3388 int os = data.size();
3389 data.resize( data.size() + s.length() );
3390 if ( index < os ) {
3391 memmove( data.data() + index + s.length(), data.data() + index,
3392 sizeof( QTextStringChar ) * ( os - index ) );
3393 }
3394 for ( int i = 0; i < (int)s.length(); ++i ) {
3395 data[ (int)index + i ].x = 0;
3396 data[ (int)index + i ].lineStart = 0;
3397 data[ (int)index + i ].d.format = 0;
3398 data[ (int)index + i ].type = QTextStringChar::Regular;
3399 data[ (int)index + i ].rightToLeft = 0;
3400 data[ (int)index + i ].startOfRun = 0;
3401 data[ (int)index + i ].c = s[ i ];
3402 data[ (int)index + i ].setFormat( f );
3403 }
3404 bidiDirty = TRUE;
3405}
3406
3407QTextString::~QTextString()
3408{
3409 clear();
3410}
3411
3412void QTextString::insert( int index, QTextStringChar *c )
3413{
3414 int os = data.size();
3415 data.resize( data.size() + 1 );
3416 if ( index < os ) {
3417 memmove( data.data() + index + 1, data.data() + index,
3418 sizeof( QTextStringChar ) * ( os - index ) );
3419 }
3420 data[ (int)index ].c = c->c;
3421 data[ (int)index ].x = 0;
3422 data[ (int)index ].lineStart = 0;
3423 data[ (int)index ].rightToLeft = 0;
3424 data[ (int)index ].d.format = 0;
3425 data[ (int)index ].type = QTextStringChar::Regular;
3426 data[ (int)index ].setFormat( c->format() );
3427 bidiDirty = TRUE;
3428}
3429
3430void QTextString::truncate( int index )
3431{
3432 index = QMAX( index, 0 );
3433 index = QMIN( index, (int)data.size() - 1 );
3434 if ( index < (int)data.size() ) {
3435 for ( int i = index + 1; i < (int)data.size(); ++i ) {
3436 if ( !(data[ i ].type == QTextStringChar::Regular) ) {
3437 delete data[ i ].customItem();
3438 if ( data[ i ].d.custom->format )
3439 data[ i ].d.custom->format->removeRef();
3440 delete data[ i ].d.custom;
3441 data[ i ].d.custom = 0;
3442 } else if ( data[ i ].format() ) {
3443 data[ i ].format()->removeRef();
3444 }
3445 }
3446 }
3447 data.truncate( index );
3448 bidiDirty = TRUE;
3449}
3450
3451void QTextString::remove( int index, int len )
3452{
3453 for ( int i = index; i < (int)data.size() && i - index < len; ++i ) {
3454 if ( !(data[ i ].type == QTextStringChar::Regular) ) {
3455 delete data[ i ].customItem();
3456 if ( data[ i ].d.custom->format )
3457 data[ i ].d.custom->format->removeRef();
3458 delete data[ i ].d.custom;
3459 data[ i ].d.custom = 0;
3460 } else if ( data[ i ].format() ) {
3461 data[ i ].format()->removeRef();
3462 }
3463 }
3464 memmove( data.data() + index, data.data() + index + len,
3465 sizeof( QTextStringChar ) * ( data.size() - index - len ) );
3466 data.resize( data.size() - len );
3467 bidiDirty = TRUE;
3468}
3469
3470void QTextString::clear()
3471{
3472 for ( int i = 0; i < (int)data.count(); ++i ) {
3473 if ( !(data[ i ].type == QTextStringChar::Regular) ) {
3474 delete data[ i ].customItem();
3475 if ( data[ i ].d.custom->format )
3476 data[ i ].d.custom->format->removeRef();
3477 delete data[ i ].d.custom;
3478 data[ i ].d.custom = 0;
3479 } else if ( data[ i ].format() ) {
3480 data[ i ].format()->removeRef();
3481 }
3482 }
3483 data.resize( 0 );
3484}
3485
3486void QTextString::setFormat( int index, QTextFormat *f, bool useCollection )
3487{
3488 if ( useCollection && data[ index ].format() )
3489 data[ index ].format()->removeRef();
3490 data[ index ].setFormat( f );
3491}
3492
3493void QTextString::checkBidi() const
3494{
3495 bool rtlKnown = FALSE;
3496 if ( dir == QChar::DirR ) {
3497 ((QTextString *)this)->bidi = TRUE;
3498 ((QTextString *)this)->rightToLeft = TRUE;
3499 rtlKnown = TRUE;
3500 return;
3501 } else if ( dir == QChar::DirL ) {
3502 ((QTextString *)this)->rightToLeft = FALSE;
3503 rtlKnown = TRUE;
3504 } else {
3505 ((QTextString *)this)->rightToLeft = FALSE;
3506 }
3507
3508 int len = data.size();
3509 const QTextStringChar *c = data.data();
3510 ((QTextString *)this)->bidi = FALSE;
3511 while( len ) {
3512 if ( !rtlKnown ) {
3513 switch( c->c.direction() )
3514 {
3515 case QChar::DirL:
3516 case QChar::DirLRO:
3517 case QChar::DirLRE:
3518 ((QTextString *)this)->rightToLeft = FALSE;
3519 rtlKnown = TRUE;
3520 break;
3521 case QChar::DirR:
3522 case QChar::DirAL:
3523 case QChar::DirRLO:
3524 case QChar::DirRLE:
3525 ((QTextString *)this)->rightToLeft = TRUE;
3526 rtlKnown = TRUE;
3527 break;
3528 default:
3529 break;
3530 }
3531 }
3532 uchar row = c->c.row();
3533 if( (row > 0x04 && row < 0x09) || (row > 0xfa && row < 0xff) ) {
3534 ((QTextString *)this)->bidi = TRUE;
3535 if ( rtlKnown )
3536 return;
3537 }
3538 len--;
3539 ++c;
3540 }
3541}
3542
3543void QTextDocument::setStyleSheet( QStyleSheet *s )
3544{
3545 if ( !s )
3546 return;
3547 sheet_ = s;
3548 fCollection->setStyleSheet( s );
3549 updateStyles();
3550}
3551
3552void QTextDocument::updateStyles()
3553{
3554 invalidate();
3555 if ( par )
3556 underlLinks = par->underlLinks;
3557 fCollection->updateStyles();
3558 for ( QTextDocument *d = childList.first(); d; d = childList.next() )
3559 d->updateStyles();
3560}
3561
3562void QTextDocument::updateFontSizes( int base, bool usePixels )
3563{
3564 for ( QTextDocument *d = childList.first(); d; d = childList.next() )
3565 d->updateFontSizes( base, usePixels );
3566 invalidate();
3567 fCollection->updateFontSizes( base, usePixels );
3568}
3569
3570void QTextDocument::updateFontAttributes( const QFont &f, const QFont &old )
3571{
3572 for ( QTextDocument *d = childList.first(); d; d = childList.next() )
3573 d->updateFontAttributes( f, old );
3574 invalidate();
3575 fCollection->updateFontAttributes( f, old );
3576}
3577
3578void QTextStringChar::setFormat( QTextFormat *f )
3579{
3580 if ( type == Regular ) {
3581 d.format = f;
3582 } else {
3583 if ( !d.custom ) {
3584 d.custom = new CustomData;
3585 d.custom->custom = 0;
3586 }
3587 d.custom->format = f;
3588 }
3589}
3590
3591void QTextStringChar::setCustomItem( QTextCustomItem *i )
3592{
3593 if ( type == Regular ) {
3594 QTextFormat *f = format();
3595 d.custom = new CustomData;
3596 d.custom->format = f;
3597 } else {
3598 delete d.custom->custom;
3599 }
3600 d.custom->custom = i;
3601 type = (type == Anchor ? CustomAnchor : Custom);
3602}
3603
3604void QTextStringChar::loseCustomItem()
3605{
3606 if ( type == Custom ) {
3607 QTextFormat *f = d.custom->format;
3608 d.custom->custom = 0;
3609 delete d.custom;
3610 type = Regular;
3611 d.format = f;
3612 } else if ( type == CustomAnchor ) {
3613 d.custom->custom = 0;
3614 type = Anchor;
3615 }
3616}
3617
3618QString QTextStringChar::anchorName() const
3619{
3620 if ( type == Regular )
3621 return QString::null;
3622 else
3623 return d.custom->anchorName;
3624}
3625
3626QString QTextStringChar::anchorHref() const
3627{
3628 if ( type == Regular )
3629 return QString::null;
3630 else
3631 return d.custom->anchorHref;
3632}
3633
3634void QTextStringChar::setAnchor( const QString& name, const QString& href )
3635{
3636 if ( type == Regular ) {
3637 QTextFormat *f = format();
3638 d.custom = new CustomData;
3639 d.custom->custom = 0;
3640 d.custom->format = f;
3641 type = Anchor;
3642 } else if ( type == Custom ) {
3643 type = CustomAnchor;
3644 }
3645 d.custom->anchorName = name;
3646 d.custom->anchorHref = href;
3647}
3648
3649
3650int QTextString::width( int idx ) const
3651{
3652 int w = 0;
3653 QTextStringChar *c = &at( idx );
3654 if ( c->c.unicode() == 0xad )
3655 return 0;
3656 if( c->isCustom() ) {
3657 if( c->customItem()->placement() == QTextCustomItem::PlaceInline )
3658 w = c->customItem()->width;
3659 } else {
3660 int r = c->c.row();
3661 if( r < 0x06 || r > 0x1f )
3662 w = c->format()->width( c->c );
3663 else {
3664 // complex text. We need some hacks to get the right metric here
3665 QString str;
3666 int pos = 0;
3667 if( idx > 4 )
3668 pos = idx - 4;
3669 int off = idx - pos;
3670 int end = QMIN( length(), idx + 4 );
3671 while ( pos < end ) {
3672 str += at(pos).c;
3673 pos++;
3674 }
3675 w = c->format()->width( str, off );
3676 }
3677 }
3678 return w;
3679}
3680
3681QMemArray<QTextStringChar> QTextString::subString( int start, int len ) const
3682{
3683 if ( len == 0xFFFFFF )
3684 len = data.size();
3685 QMemArray<QTextStringChar> a;
3686 a.resize( len );
3687 for ( int i = 0; i < len; ++i ) {
3688 QTextStringChar *c = &data[ i + start ];
3689 a[ i ].c = c->c;
3690 a[ i ].x = 0;
3691 a[ i ].lineStart = 0;
3692 a[ i ].rightToLeft = 0;
3693 a[ i ].d.format = 0;
3694 a[ i ].type = QTextStringChar::Regular;
3695 a[ i ].setFormat( c->format() );
3696 if ( c->format() )
3697 c->format()->addRef();
3698 }
3699 return a;
3700}
3701
3702QTextStringChar *QTextStringChar::clone() const
3703{
3704 QTextStringChar *chr = new QTextStringChar;
3705 chr->c = c;
3706 chr->x = 0;
3707 chr->lineStart = 0;
3708 chr->rightToLeft = 0;
3709 chr->d.format = 0;
3710 chr->type = QTextStringChar::Regular;
3711 chr->setFormat( format() );
3712 if ( chr->format() )
3713 chr->format()->addRef();
3714 return chr;
3715}
3716
3717// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3718
3719QTextParag::QTextParag( QTextDocument *d, QTextParag *pr, QTextParag *nx, bool updateIds )
3720 : invalid( 0 ), p( pr ), n( nx ), docOrPseudo( d ), align( 0 ),mSelections( 0 ),
3721 mStyleSheetItemsVec( 0 ), mFloatingItems( 0 ), listS( QStyleSheetItem::ListDisc ),
3722 numSubParag( -1 ), tm( -1 ), bm( -1 ), lm( -1 ), rm( -1 ), flm( -1 ),
3723 tArray(0), tabStopWidth(0), eData( 0 )
3724{
3725 listS = QStyleSheetItem::ListDisc;
3726 if ( ! (hasdoc = docOrPseudo != 0 ) )
3727 docOrPseudo = new QTextParagPseudoDocument;
3728 bgcol = 0;
3729 breakable = TRUE;
3730 isBr = FALSE;
3731 movedDown = FALSE;
3732 mightHaveCustomItems = FALSE;
3733 visible = TRUE;
3734 list_val = -1;
3735 newLinesAllowed = FALSE;
3736 lastInFrame = FALSE;
3737 defFormat = formatCollection()->defaultFormat();
3738 if ( !hasdoc ) {
3739 tabStopWidth = defFormat->width( 'x' ) * 8;
3740 pseudoDocument()->commandHistory = new QTextCommandHistory( 100 );
3741 }
3742#if defined(PARSER_DEBUG)
3743 qDebug( debug_indent + "new QTextParag" );
3744#endif
3745 fullWidth = TRUE;
3746
3747 if ( p )
3748 p->n = this;
3749 if ( n )
3750 n->p = this;
3751
3752
3753 if ( !p && hasdoc )
3754 document()->setFirstParag( this );
3755 if ( !n && hasdoc )
3756 document()->setLastParag( this );
3757
3758 changed = FALSE;
3759 firstFormat = TRUE;
3760 state = -1;
3761 needPreProcess = FALSE;
3762
3763 if ( p )
3764 id = p->id + 1;
3765 else
3766 id = 0;
3767 if ( n && updateIds ) {
3768 QTextParag *s = n;
3769 while ( s ) {
3770 s->id = s->p->id + 1;
3771 s->numSubParag = -1;
3772 s->lm = s->rm = s->tm = s->bm = -1, s->flm = -1;
3773 s = s->n;
3774 }
3775 }
3776 firstPProcess = TRUE;
3777
3778 str = new QTextString();
3779 str->insert( 0, " ", formatCollection()->defaultFormat() );
3780}
3781
3782QTextParag::~QTextParag()
3783{
3784 delete str;
3785 if ( hasdoc ) {
3786 register QTextDocument *doc = document();
3787 if ( this == doc->minwParag ) {
3788 doc->minwParag = 0;
3789 doc->minw = 0;
3790 }
3791 if ( this == doc->curParag )
3792 doc->curParag = 0;
3793 } else {
3794 delete pseudoDocument();
3795 }
3796 if ( tArray )
3797 delete [] tArray;
3798 delete eData;
3799 QMap<int, QTextParagLineStart*>::Iterator it = lineStarts.begin();
3800 for ( ; it != lineStarts.end(); ++it )
3801 delete *it;
3802 if ( mSelections )
3803 delete mSelections;
3804 if ( mFloatingItems )
3805 delete mFloatingItems;
3806 if ( mStyleSheetItemsVec )
3807 delete mStyleSheetItemsVec;
3808 if ( p )
3809 p->setNext( n );
3810 if ( n )
3811 n->setPrev( p );
3812}
3813
3814void QTextParag::setNext( QTextParag *s )
3815{
3816 n = s;
3817 if ( !n && hasdoc )
3818 document()->setLastParag( this );
3819}
3820
3821void QTextParag::setPrev( QTextParag *s )
3822{
3823 p = s;
3824 if ( !p && hasdoc )
3825 document()->setFirstParag( this );
3826}
3827
3828void QTextParag::invalidate( int chr )
3829{
3830 if ( invalid < 0 )
3831 invalid = chr;
3832 else
3833 invalid = QMIN( invalid, chr );
3834 if ( mFloatingItems ) {
3835 for ( QTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() )
3836 i->ypos = -1;
3837 }
3838 lm = rm = bm = tm = flm = -1;
3839}
3840
3841void QTextParag::insert( int index, const QString &s )
3842{
3843 if ( hasdoc && !document()->useFormatCollection() && document()->preProcessor() )
3844 str->insert( index, s,
3845 document()->preProcessor()->format( QTextPreProcessor::Standard ) );
3846 else
3847 str->insert( index, s, formatCollection()->defaultFormat() );
3848 invalidate( index );
3849 needPreProcess = TRUE;
3850}
3851
3852void QTextParag::truncate( int index )
3853{
3854 str->truncate( index );
3855 insert( length(), " " );
3856 needPreProcess = TRUE;
3857}
3858
3859void QTextParag::remove( int index, int len )
3860{
3861 if ( index + len - str->length() > 0 )
3862 return;
3863 for ( int i = index; i < index + len; ++i ) {
3864 QTextStringChar *c = at( i );
3865 if ( hasdoc && c->isCustom() ) {
3866 document()->unregisterCustomItem( c->customItem(), this );
3867 }
3868 }
3869 str->remove( index, len );
3870 invalidate( 0 );
3871 needPreProcess = TRUE;
3872}
3873
3874void QTextParag::join( QTextParag *s )
3875{
3876 int oh = r.height() + s->r.height();
3877 n = s->n;
3878 if ( n )
3879 n->p = this;
3880 else if ( hasdoc )
3881 document()->setLastParag( this );
3882
3883 int start = str->length();
3884 if ( length() > 0 && at( length() - 1 )->c == ' ' ) {
3885 remove( length() - 1, 1 );
3886 --start;
3887 }
3888 append( s->str->toString(), TRUE );
3889
3890 for ( int i = 0; i < s->length(); ++i ) {
3891 if ( !hasdoc || document()->useFormatCollection() ) {
3892 s->str->at( i ).format()->addRef();
3893 str->setFormat( i + start, s->str->at( i ).format(), TRUE );
3894 }
3895 if ( s->str->at( i ).isCustom() ) {
3896 QTextCustomItem * item = s->str->at( i ).customItem();
3897 str->at( i + start ).setCustomItem( item );
3898 s->str->at( i ).loseCustomItem();
3899 }
3900 if ( s->str->at( i ).isAnchor() ) {
3901 str->at( i + start ).setAnchor( s->str->at( i ).anchorName(),
3902 s->str->at( i ).anchorHref() );
3903 }
3904 }
3905
3906 if ( !extraData() && s->extraData() ) {
3907 setExtraData( s->extraData() );
3908 s->setExtraData( 0 );
3909 } else if ( extraData() && s->extraData() ) {
3910 extraData()->join( s->extraData() );
3911 }
3912 delete s;
3913 invalidate( 0 );
3914 r.setHeight( oh );
3915 needPreProcess = TRUE;
3916 if ( n ) {
3917 QTextParag *s = n;
3918 while ( s ) {
3919 s->id = s->p->id + 1;
3920 s->state = -1;
3921 s->needPreProcess = TRUE;
3922 s->changed = TRUE;
3923 s = s->n;
3924 }
3925 }
3926 format();
3927 state = -1;
3928}
3929
3930void QTextParag::move( int &dy )
3931{
3932 if ( dy == 0 )
3933 return;
3934 changed = TRUE;
3935 r.moveBy( 0, dy );
3936 if ( mFloatingItems ) {
3937 for ( QTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() )
3938 i->ypos += dy;
3939 }
3940 if ( p )
3941 p->lastInFrame = TRUE;
3942
3943 // do page breaks if required
3944 if ( hasdoc && document()->isPageBreakEnabled() ) {
3945 int shift;
3946 if ( ( shift = document()->formatter()->formatVertically( document(), this ) ) ) {
3947 if ( p )
3948 p->setChanged( TRUE );
3949 dy += shift;
3950 }
3951 }
3952}
3953
3954void QTextParag::format( int start, bool doMove )
3955{
3956 if ( !str || str->length() == 0 || !formatter() )
3957 return;
3958
3959 if ( hasdoc &&
3960 document()->preProcessor() &&
3961 ( needPreProcess || state == -1 ) )
3962 document()->preProcessor()->process( document(), this, invalid <= 0 ? 0 : invalid );
3963 needPreProcess = FALSE;
3964
3965 if ( invalid == -1 )
3966 return;
3967
3968 r.moveTopLeft( QPoint( documentX(), p ? p->r.y() + p->r.height() : documentY() ) );
3969 r.setWidth( documentWidth() );
3970 if ( p )
3971 p->lastInFrame = FALSE;
3972
3973 movedDown = FALSE;
3974 bool formattedAgain = FALSE;
3975
3976 formatAgain:
3977
3978 if ( hasdoc && mFloatingItems ) {
3979 for ( QTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() ) {
3980 i->ypos = r.y();
3981 if ( i->placement() == QTextCustomItem::PlaceRight ) {
3982 i->xpos = r.x() + r.width() - i->width;
3983 }
3984 }
3985 }
3986 QMap<int, QTextParagLineStart*> oldLineStarts = lineStarts;
3987 lineStarts.clear();
3988 int y = formatter()->format( document(), this, start, oldLineStarts );
3989
3990
3991 r.setWidth( QMAX( r.width(), formatter()->minimumWidth() ) );
3992
3993
3994 QMap<int, QTextParagLineStart*>::Iterator it = oldLineStarts.begin();
3995
3996 for ( ; it != oldLineStarts.end(); ++it )
3997 delete *it;
3998
3999 QTextStringChar *c = 0;
4000 // do not do this on mac, as the paragraph
4001 // with has to be the full document width on mac as the selections
4002 // always extend completely to the right. This is a bit unefficient,
4003 // as this results in a bigger double buffer than needed but ok for
4004 // now.
4005 if ( lineStarts.count() == 1 ) { //&& ( !doc || document()->flow()->isEmpty() ) ) {
4006 if ( !string()->isBidi() ) {
4007 c = &str->at( str->length() - 1 );
4008 r.setWidth( c->x + str->width( str->length() - 1 ) );
4009 } else {
4010 r.setWidth( lineStarts[0]->w );
4011 }
4012 }
4013
4014 if ( newLinesAllowed ) {
4015 it = lineStarts.begin();
4016 int usedw = 0;
4017 for ( ; it != lineStarts.end(); ++it )
4018 usedw = QMAX( usedw, (*it)->w );
4019 if ( r.width() <= 0 ) {
4020 // if the user specifies an invalid rect, this means that the
4021 // bounding box should grow to the width that the text actually
4022 // needs
4023 r.setWidth( usedw );
4024 } else {
4025 r.setWidth( QMIN( usedw, r.width() ) );
4026 }
4027 }
4028
4029 if ( y != r.height() )
4030 r.setHeight( y );
4031
4032 if ( !visible ) {
4033 r.setHeight( 0 );
4034 } else {
4035 int minw = formatter()->minimumWidth();
4036 int wused = formatter()->widthUsed();
4037 wused = QMAX( minw, wused );
4038 if ( hasdoc ) {
4039 document()->setMinimumWidth( minw, wused, this );
4040 } else {
4041 pseudoDocument()->minw = QMAX( pseudoDocument()->minw, minw );
4042 pseudoDocument()->wused = QMAX( pseudoDocument()->wused, wused );
4043 }
4044 }
4045
4046 // do page breaks if required
4047 if ( hasdoc && document()->isPageBreakEnabled() ) {
4048 int shift = document()->formatter()->formatVertically( document(), this );
4049 if ( shift && !formattedAgain ) {
4050 formattedAgain = TRUE;
4051 goto formatAgain;
4052 }
4053 }
4054
4055 if ( n && doMove && n->invalid == -1 && r.y() + r.height() != n->r.y() ) {
4056 int dy = ( r.y() + r.height() ) - n->r.y();
4057 QTextParag *s = n;
4058 bool makeInvalid = p && p->lastInFrame;
4059 while ( s && dy ) {
4060 if ( !s->isFullWidth() )
4061 makeInvalid = TRUE;
4062 if ( makeInvalid )
4063 s->invalidate( 0 );
4064 s->move( dy );
4065 if ( s->lastInFrame )
4066 makeInvalid = TRUE;
4067 s = s->n;
4068 }
4069 }
4070
4071 firstFormat = FALSE;
4072 changed = TRUE;
4073 invalid = -1;
4074 //##### string()->setTextChanged( FALSE );
4075}
4076
4077int QTextParag::lineHeightOfChar( int i, int *bl, int *y ) const
4078{
4079 if ( !isValid() )
4080 ( (QTextParag*)this )->format();
4081
4082 QMap<int, QTextParagLineStart*>::ConstIterator it = lineStarts.end();
4083 --it;
4084 for ( ;; ) {
4085 if ( i >= it.key() ) {
4086 if ( bl )
4087 *bl = ( *it )->baseLine;
4088 if ( y )
4089 *y = ( *it )->y;
4090 return ( *it )->h;
4091 }
4092 if ( it == lineStarts.begin() )
4093 break;
4094 --it;
4095 }
4096
4097 qWarning( "QTextParag::lineHeightOfChar: couldn't find lh for %d", i );
4098 return 15;
4099}
4100
4101QTextStringChar *QTextParag::lineStartOfChar( int i, int *index, int *line ) const
4102{
4103 if ( !isValid() )
4104 ( (QTextParag*)this )->format();
4105
4106 int l = (int)lineStarts.count() - 1;
4107 QMap<int, QTextParagLineStart*>::ConstIterator it = lineStarts.end();
4108 --it;
4109 for ( ;; ) {
4110 if ( i >= it.key() ) {
4111 if ( index )
4112 *index = it.key();
4113 if ( line )
4114 *line = l;
4115 return &str->at( it.key() );
4116 }
4117 if ( it == lineStarts.begin() )
4118 break;
4119 --it;
4120 --l;
4121 }
4122
4123 qWarning( "QTextParag::lineStartOfChar: couldn't find %d", i );
4124 return 0;
4125}
4126
4127int QTextParag::lines() const
4128{
4129 if ( !isValid() )
4130 ( (QTextParag*)this )->format();
4131
4132 return (int)lineStarts.count();
4133}
4134
4135QTextStringChar *QTextParag::lineStartOfLine( int line, int *index ) const
4136{
4137 if ( !isValid() )
4138 ( (QTextParag*)this )->format();
4139
4140 if ( line >= 0 && line < (int)lineStarts.count() ) {
4141 QMap<int, QTextParagLineStart*>::ConstIterator it = lineStarts.begin();
4142 while ( line-- > 0 )
4143 ++it;
4144 int i = it.key();
4145 if ( index )
4146 *index = i;
4147 return &str->at( i );
4148 }
4149
4150 qWarning( "QTextParag::lineStartOfLine: couldn't find %d", line );
4151 return 0;
4152}
4153
4154int QTextParag::leftGap() const
4155{
4156 if ( !isValid() )
4157 ( (QTextParag*)this )->format();
4158
4159 int line = 0;
4160 int x = str->at(0).x; /* set x to x of first char */
4161 if ( str->isBidi() ) {
4162 for ( int i = 1; i < str->length(); ++i )
4163 x = QMIN(x, str->at(i).x);
4164 return x;
4165 }
4166
4167 QMap<int, QTextParagLineStart*>::ConstIterator it = lineStarts.begin();
4168 while (line < (int)lineStarts.count()) {
4169 int i = it.key(); /* char index */
4170 x = QMIN(x, str->at(i).x);
4171 ++it;
4172 ++line;
4173 }
4174 return x;
4175}
4176
4177void QTextParag::setFormat( int index, int len, QTextFormat *f, bool useCollection, int flags )
4178{
4179 if ( !f )
4180 return;
4181 if ( index < 0 )
4182 index = 0;
4183 if ( index > str->length() - 1 )
4184 index = str->length() - 1;
4185 if ( index + len >= str->length() )
4186 len = str->length() - index;
4187
4188 QTextFormatCollection *fc = 0;
4189 if ( useCollection )
4190 fc = formatCollection();
4191 QTextFormat *of;
4192 for ( int i = 0; i < len; ++i ) {
4193 of = str->at( i + index ).format();
4194 if ( !changed && f->key() != of->key() )
4195 changed = TRUE;
4196 if ( invalid == -1 &&
4197 ( f->font().family() != of->font().family() ||
4198 f->font().pointSize() != of->font().pointSize() ||
4199 f->font().weight() != of->font().weight() ||
4200 f->font().italic() != of->font().italic() ||
4201 f->vAlign() != of->vAlign() ) ) {
4202 invalidate( 0 );
4203 }
4204 if ( flags == -1 || flags == QTextFormat::Format || !fc ) {
4205 if ( fc )
4206 f = fc->format( f );
4207 str->setFormat( i + index, f, useCollection );
4208 } else {
4209 QTextFormat *fm = fc->format( of, f, flags );
4210 str->setFormat( i + index, fm, useCollection );
4211 }
4212 }
4213}
4214
4215void QTextParag::indent( int *oldIndent, int *newIndent )
4216{
4217 if ( !hasdoc || !document()->indent() || style() && style()->displayMode() != QStyleSheetItem::DisplayBlock ) {
4218 if ( oldIndent )
4219 *oldIndent = 0;
4220 if ( newIndent )
4221 *newIndent = 0;
4222 if ( oldIndent && newIndent )
4223 *newIndent = *oldIndent;
4224 return;
4225 }
4226 document()->indent()->indent( document(), this, oldIndent, newIndent );
4227}
4228
4229void QTextParag::paint( QPainter &painter, const QColorGroup &cg, QTextCursor *cursor, bool drawSelections,
4230 int clipx, int clipy, int clipw, int cliph )
4231{
4232 if ( !visible )
4233 return;
4234 QTextStringChar *chr = at( 0 );
4235 int i = 0;
4236 int h = 0;
4237 int baseLine = 0, lastBaseLine = 0;
4238 QTextStringChar *formatChar = 0;
4239 int lastY = -1;
4240 int startX = 0;
4241 int bw = 0;
4242 int cy = 0;
4243 int curx = -1, cury = 0, curh = 0;
4244 bool lastDirection = chr->rightToLeft;
4245 const int full_sel_width = (hasdoc ? document()->width() : r.width());
4246#if 0 // seems we don't need that anymore
4247 int tw = 0;
4248#endif
4249
4250 QString qstr = str->toString();
4251 // ### workaround so that \n are not drawn, actually this should be
4252 // fixed in QFont somewhere (under Windows you get ugly boxes
4253 // otherwise)
4254 QChar* uc = (QChar*) qstr.unicode();
4255 for ( uint ii = 0; ii < qstr.length(); ii++ ) {
4256 if ( uc[(int)ii]== '\n' )
4257 uc[(int)ii] = 0x20;
4258 }
4259
4260
4261 const int nSels = hasdoc ? document()->numSelections() : 1;
4262 QMemArray<int> selectionStarts( nSels );
4263 QMemArray<int> selectionEnds( nSels );
4264 if ( drawSelections ) {
4265 bool hasASelection = FALSE;
4266 for ( i = 0; i < nSels; ++i ) {
4267 if ( !hasSelection( i ) ) {
4268 selectionStarts[ i ] = -1;
4269 selectionEnds[ i ] = -1;
4270 } else {
4271 hasASelection = TRUE;
4272 selectionStarts[ i ] = selectionStart( i );
4273 int end = selectionEnd( i );
4274 if ( end == length() - 1 && n && n->hasSelection( i ) )
4275 end++;
4276 selectionEnds[ i ] = end;
4277 }
4278 }
4279 if ( !hasASelection )
4280 drawSelections = FALSE;
4281 }
4282
4283 int line = -1;
4284 int cw;
4285 bool didListLabel = FALSE;
4286 int paintStart = 0;
4287 int paintEnd = -1;
4288 int lasth = 0;
4289 for ( i = 0; i < length(); i++ ) {
4290 chr = at( i );
4291#if 0 // seems we don't need that anymore
4292 if ( !str->isBidi() && is_printer( &painter ) ) { // ### fix our broken ps-printer
4293 if ( !chr->lineStart )
4294 chr->x = QMAX( chr->x, tw );
4295 else
4296 tw = 0;
4297 }
4298#endif
4299 cw = string()->width( i );
4300 if ( chr->c == '\t' && i < length() - 1 )
4301 cw = at( i + 1 )->x - chr->x + 1;
4302 if ( chr->c.unicode() == 0xad && i < length() - 1 )
4303 cw = 0;
4304
4305 // init a new line
4306 if ( chr->lineStart ) {
4307#if 0 // seems we don't need that anymore
4308 tw = 0;
4309#endif
4310 ++line;
4311 lineInfo( line, cy, h, baseLine );
4312 lasth = h;
4313 if ( clipy != -1 && cy > clipy - r.y() + cliph ) // outside clip area, leave
4314 break;
4315 if ( lastBaseLine == 0 )
4316 lastBaseLine = baseLine;
4317 }
4318
4319 // draw bullet list items
4320 if ( !didListLabel && line == 0 && style() && style()->displayMode() == QStyleSheetItem::DisplayListItem ) {
4321 didListLabel = TRUE;
4322 drawLabel( &painter, chr->x, cy, 0, 0, baseLine, cg );
4323 }
4324
4325 // check for cursor mark
4326 if ( cursor && this == cursor->parag() && i == cursor->index() ) {
4327 curx = cursor->x();
4328 QTextStringChar *c = chr;
4329 if ( i > 0 )
4330 --c;
4331 curh = c->format()->height();
4332 cury = cy + baseLine - c->format()->ascent();
4333 }
4334
4335 // first time - start again...
4336 if ( !formatChar || lastY == -1 ) {
4337 formatChar = chr;
4338 lastY = cy;
4339 startX = chr->x;
4340 if ( !chr->isCustom() && chr->c != '\n' )
4341 paintEnd = i;
4342 bw = cw;
4343 if ( !chr->isCustom() )
4344 continue;
4345 }
4346
4347 // check if selection state changed
4348 bool selectionChange = FALSE;
4349 if ( drawSelections ) {
4350 for ( int j = 0; j < nSels; ++j ) {
4351 selectionChange = selectionStarts[ j ] == i || selectionEnds[ j ] == i;
4352 if ( selectionChange )
4353 break;
4354 }
4355 }
4356
4357 //if something (format, etc.) changed, draw what we have so far
4358 if ( ( ( ( alignment() & Qt3::AlignJustify ) == Qt3::AlignJustify && at(paintEnd)->c.isSpace() ) ||
4359 lastDirection != (bool)chr->rightToLeft ||
4360 chr->startOfRun ||
4361 lastY != cy || chr->format() != formatChar->format() || chr->isAnchor() != formatChar->isAnchor() ||
4362 ( paintEnd != -1 && at( paintEnd )->c =='\t' ) || chr->c == '\t' ||
4363 ( paintEnd != -1 && at( paintEnd )->c.unicode() == 0xad ) || chr->c.unicode() == 0xad ||
4364 selectionChange || chr->isCustom() ) ) {
4365 if ( paintStart <= paintEnd ) {
4366 // ### temporary hack until I get the new placement/shaping stuff working
4367 int x = startX;
4368 if ( ( alignment() & Qt3::AlignJustify ) == Qt3::AlignJustify && paintEnd != -1 &&
4369 paintEnd > 1 && at( paintEnd )->c.isSpace() ) {
4370 int add = str->at(paintEnd).x - str->at(paintEnd-1).x - str->width(paintEnd-1);
4371 bw += ( lastDirection ? 0 : add );
4372 }
4373 drawParagString( painter, qstr, paintStart, paintEnd - paintStart + 1, x, lastY,
4374 lastBaseLine, bw, lasth, drawSelections,
4375 formatChar, i, selectionStarts, selectionEnds, cg, lastDirection );
4376 }
4377#if 0 // seems we don't need that anymore
4378 if ( !str->isBidi() && is_printer( &painter ) ) { // ### fix our broken ps-printer
4379 if ( !chr->lineStart ) {
4380 // ### the next line doesn't look 100% correct for arabic
4381 tw = startX + painter.fontMetrics().width( qstr.mid(paintStart, paintEnd - paintStart +1) );
4382 chr->x = QMAX( chr->x, tw );
4383 } else {
4384 tw = 0;
4385 }
4386 }
4387#endif
4388 if ( !chr->isCustom() ) {
4389 if ( chr->c != '\n' ) {
4390 paintStart = i;
4391 paintEnd = i;
4392 } else {
4393 paintStart = i+1;
4394 paintEnd = -1;
4395 }
4396 formatChar = chr;
4397 lastY = cy;
4398 startX = chr->x;
4399 bw = cw;
4400 } else {
4401 if ( chr->customItem()->placement() == QTextCustomItem::PlaceInline ) {
4402 chr->customItem()->draw( &painter, chr->x, cy, clipx - r.x(), clipy - r.y(), clipw, cliph, cg,
4403 nSels && selectionStarts[ 0 ] <= i && selectionEnds[ 0 ] >= i );
4404 paintStart = i+1;
4405 paintEnd = -1;
4406 formatChar = chr;
4407 lastY = cy;
4408 startX = chr->x + string()->width( i );
4409 bw = 0;
4410 } else {
4411 chr->customItem()->resize( chr->customItem()->width );
4412 paintStart = i+1;
4413 paintEnd = -1;
4414 formatChar = chr;
4415 lastY = cy;
4416 startX = chr->x + string()->width( i );
4417 bw = 0;
4418 }
4419 }
4420 } else {
4421 if ( chr->c != '\n' ) {
4422 if( chr->rightToLeft ) {
4423 startX = chr->x;
4424 }
4425 paintEnd = i;
4426 }
4427 bw += cw;
4428 }
4429 lastBaseLine = baseLine;
4430 lasth = h;
4431 lastDirection = chr->rightToLeft;
4432 }
4433
4434 // if we are through the parag, but still have some stuff left to draw, draw it now
4435 if ( paintStart <= paintEnd ) {
4436 bool selectionChange = FALSE;
4437 if ( drawSelections ) {
4438 for ( int j = 0; j < nSels; ++j ) {
4439 selectionChange = selectionStarts[ j ] == i || selectionEnds[ j ] == i;
4440 if ( selectionChange )
4441 break;
4442 }
4443 }
4444 int x = startX;
4445 drawParagString( painter, qstr, paintStart, paintEnd-paintStart+1, x, lastY,
4446 lastBaseLine, bw, h, drawSelections,
4447 formatChar, i, selectionStarts, selectionEnds, cg, lastDirection );
4448 }
4449
4450 // if we should draw a cursor, draw it now
4451 if ( curx != -1 && cursor ) {
4452 painter.fillRect( QRect( curx, cury, 1, curh - lineSpacing() ), cg.color( QColorGroup::Text ) );
4453 painter.save();
4454 if ( string()->isBidi() ) {
4455 const int d = 4;
4456 if ( at( cursor->index() )->rightToLeft ) {
4457 painter.setPen( Qt::black );
4458 painter.drawLine( curx, cury, curx - d / 2, cury + d / 2 );
4459 painter.drawLine( curx, cury + d, curx - d / 2, cury + d / 2 );
4460 } else {
4461 painter.setPen( Qt::black );
4462 painter.drawLine( curx, cury, curx + d / 2, cury + d / 2 );
4463 painter.drawLine( curx, cury + d, curx + d / 2, cury + d / 2 );
4464 }
4465 }
4466 painter.restore();
4467 }
4468}
4469
4470//#define BIDI_DEBUG
4471
4472void QTextParag::drawParagString( QPainter &painter, const QString &s, int start, int len, int startX,
4473 int lastY, int baseLine, int bw, int h, bool drawSelections,
4474 QTextStringChar *formatChar, int i, const QMemArray<int> &selectionStarts,
4475 const QMemArray<int> &selectionEnds, const QColorGroup &cg, bool rightToLeft )
4476{
4477 bool plainText = hasdoc ? document()->textFormat() == Qt::PlainText : FALSE;
4478 QTextFormat* format = formatChar->format();
4479 QString str( s );
4480 if ( str[ (int)str.length() - 1 ].unicode() == 0xad )
4481 str.remove( str.length() - 1, 1 );
4482 if ( !plainText || hasdoc && format->color() != document()->formatCollection()->defaultFormat()->color() )
4483 painter.setPen( QPen( format->color() ) );
4484 else
4485 painter.setPen( cg.text() );
4486 painter.setFont( format->font() );
4487
4488 if ( hasdoc && formatChar->isAnchor() && !formatChar->anchorHref().isEmpty() ) {
4489 if ( format->useLinkColor() ) {
4490 if ( document()->linkColor.isValid() )
4491 painter.setPen( document()->linkColor );
4492 else
4493 painter.setPen( QPen( Qt::blue ) );
4494 }
4495 if ( document()->underlineLinks() ) {
4496 QFont fn = format->font();
4497 fn.setUnderline( TRUE );
4498 painter.setFont( fn );
4499 }
4500 }
4501
4502 if ( drawSelections ) {
4503 const int nSels = hasdoc ? document()->numSelections() : 1;
4504 const int startSel = is_printer( 0 ) ? 1 : 0;
4505 for ( int j = startSel; j < nSels; ++j ) {
4506 if ( i > selectionStarts[ j ] && i <= selectionEnds[ j ] ) {
4507 if ( !hasdoc || document()->invertSelectionText( j ) )
4508 painter.setPen( QPen( cg.color( QColorGroup::HighlightedText ) ) );
4509 if ( j == QTextDocument::Standard )
4510 painter.fillRect( startX, lastY, bw, h, cg.color( QColorGroup::Highlight ) );
4511 else
4512 painter.fillRect( startX, lastY, bw, h, hasdoc ? document()->selectionColor( j ) : cg.color( QColorGroup::Highlight ) );
4513 }
4514 }
4515 }
4516
4517 if ( str[ start ] != '\t' && str[ start ].unicode() != 0xad ) {
4518 if ( format->vAlign() == QTextFormat::AlignNormal ) {
4519 painter.drawText( startX, lastY + baseLine, str.mid( start ), len );
4520#ifdef BIDI_DEBUG
4521 painter.save();
4522 painter.setPen ( Qt::red );
4523 painter.drawLine( startX, lastY, startX, lastY + baseLine );
4524 painter.drawLine( startX, lastY + baseLine/2, startX + 10, lastY + baseLine/2 );
4525 int w = 0;
4526 int i = 0;
4527 while( i < len )
4528 w += painter.fontMetrics().charWidth( str, start + i++ );
4529 painter.setPen ( Qt::blue );
4530 painter.drawLine( startX + w - 1, lastY, startX + w - 1, lastY + baseLine );
4531 painter.drawLine( startX + w - 1, lastY + baseLine/2, startX + w - 1 - 10, lastY + baseLine/2 );
4532 painter.restore();
4533#endif
4534 } else if ( format->vAlign() == QTextFormat::AlignSuperScript ) {
4535 QFont f( painter.font() );
4536 if ( format->fontSizesInPixels() )
4537 f.setPixelSize( ( f.pixelSize() * 2 ) / 3 );
4538 else
4539 f.setPointSize( ( f.pointSize() * 2 ) / 3 );
4540 painter.setFont( f );
4541 painter.drawText( startX, lastY + baseLine - ( painter.fontMetrics().height() / 2 ),
4542 str.mid( start ), len );
4543 } else if ( format->vAlign() == QTextFormat::AlignSubScript ) {
4544 QFont f( painter.font() );
4545 if ( format->fontSizesInPixels() )
4546 f.setPixelSize( ( f.pixelSize() * 2 ) / 3 );
4547 else
4548 f.setPointSize( ( f.pointSize() * 2 ) / 3 );
4549 painter.setFont( f );
4550 painter.drawText( startX, lastY + baseLine + painter.fontMetrics().height() / 6, str.mid( start ), len );
4551 }
4552 }
4553 if ( i + 1 < length() && at( i + 1 )->lineStart && at( i )->c.unicode() == 0xad ) {
4554 painter.drawText( startX + bw, lastY + baseLine, "\xad" );
4555 }
4556 if ( format->isMisspelled() ) {
4557 painter.save();
4558 painter.setPen( QPen( Qt::red, 1, Qt::DotLine ) );
4559 painter.drawLine( startX, lastY + baseLine + 1, startX + bw, lastY + baseLine + 1 );
4560 painter.restore();
4561 }
4562
4563 i -= len;
4564
4565 if ( hasdoc && formatChar->isAnchor() && !formatChar->anchorHref().isEmpty() &&
4566 document()->focusIndicator.parag == this &&
4567 ( document()->focusIndicator.start >= i &&
4568 document()->focusIndicator.start + document()->focusIndicator.len <= i + len ||
4569 document()->focusIndicator.start <= i &&
4570 document()->focusIndicator.start + document()->focusIndicator.len >= i + len ) ) {
4571 painter.drawWinFocusRect( QRect( startX, lastY, bw, h ) );
4572 }
4573
4574}
4575
4576void QTextParag::drawLabel( QPainter* p, int x, int y, int w, int h, int base, const QColorGroup& cg )
4577{
4578 if ( !style() )
4579 return;
4580 QRect r ( x, y, w, h );
4581 QStyleSheetItem::ListStyle s = listStyle();
4582
4583 p->save();
4584 p->setPen( defFormat->color() );
4585
4586 QFont font2( defFormat->font() );
4587 if ( length() > 0 ) {
4588 QTextFormat *format = at( 0 )->format();
4589 if ( format ) {
4590 if ( format->fontSizesInPixels() )
4591 font2.setPixelSize( at( 0 )->format()->font().pixelSize() );
4592 else
4593 font2.setPointSize( at( 0 )->format()->font().pointSize() );
4594 }
4595 }
4596 p->setFont( font2 );
4597 QFontMetrics fm( p->fontMetrics() );
4598 int size = fm.lineSpacing() / 3;
4599
4600 switch ( s ) {
4601 case QStyleSheetItem::ListDecimal:
4602 case QStyleSheetItem::ListLowerAlpha:
4603 case QStyleSheetItem::ListUpperAlpha:
4604 {
4605 int n = numberOfSubParagraph();
4606 QString l;
4607 switch ( s ) {
4608 case QStyleSheetItem::ListLowerAlpha:
4609 if ( n < 27 ) {
4610 l = QChar( ('a' + (char) (n-1)));
4611 break;
4612 }
4613 case QStyleSheetItem::ListUpperAlpha:
4614 if ( n < 27 ) {
4615 l = QChar( ('A' + (char) (n-1)));
4616 break;
4617 }
4618 break;
4619 default: //QStyleSheetItem::ListDecimal:
4620 l.setNum( n );
4621 break;
4622 }
4623 l += QString::fromLatin1(". ");
4624 p->drawText( r.right() - fm.width( l ), r.top() + base, l );
4625 }
4626 break;
4627 case QStyleSheetItem::ListSquare:
4628 {
4629 QRect er( r.right() - size * 2, r.top() + fm.height() / 2 - size / 2, size, size );
4630 p->fillRect( er , cg.brush( QColorGroup::Foreground ) );
4631 }
4632 break;
4633 case QStyleSheetItem::ListCircle:
4634 {
4635 QRect er( r.right()-size*2, r.top() + fm.height() / 2 - size / 2, size, size);
4636 p->drawEllipse( er );
4637 }
4638 break;
4639 case QStyleSheetItem::ListDisc:
4640 default:
4641 {
4642 p->setBrush( cg.brush( QColorGroup::Foreground ));
4643 QRect er( r.right()-size*2, r.top() + fm.height() / 2 - size / 2, size, size);
4644 p->drawEllipse( er );
4645 p->setBrush( Qt::NoBrush );
4646 }
4647 break;
4648 }
4649
4650 p->restore();
4651}
4652
4653void QTextParag::setStyleSheetItems( const QPtrVector<QStyleSheetItem> &vec )
4654{
4655 styleSheetItemsVec() = vec;
4656 invalidate( 0 );
4657 lm = rm = tm = bm = flm = -1;
4658 numSubParag = -1;
4659}
4660
4661void QTextParag::setList( bool b, int listStyle )
4662{
4663 if ( !hasdoc )
4664 return;
4665
4666 if ( !style() ) {
4667 styleSheetItemsVec().resize( 2 );
4668 mStyleSheetItemsVec->insert( 0, document()->styleSheet()->item( "html" ) );
4669 mStyleSheetItemsVec->insert( 1, document()->styleSheet()->item( "p" ) );
4670 }
4671
4672 if ( b ) {
4673 if ( style()->displayMode() != QStyleSheetItem::DisplayListItem || this->listStyle() != listStyle ) {
4674 styleSheetItemsVec().remove( styleSheetItemsVec().size() - 1 );
4675 QStyleSheetItem *item = (*mStyleSheetItemsVec)[ mStyleSheetItemsVec->size() - 1 ];
4676 if ( item )
4677 mStyleSheetItemsVec->remove( mStyleSheetItemsVec->size() - 1 );
4678 mStyleSheetItemsVec->insert( mStyleSheetItemsVec->size() - 1,
4679 listStyle == QStyleSheetItem::ListDisc || listStyle == QStyleSheetItem::ListCircle
4680 || listStyle == QStyleSheetItem::ListSquare ?
4681 document()->styleSheet()->item( "ul" ) : document()->styleSheet()->item( "ol" ) );
4682 mStyleSheetItemsVec->insert( mStyleSheetItemsVec->size() - 1, document()->styleSheet()->item( "li" ) );
4683 setListStyle( (QStyleSheetItem::ListStyle)listStyle );
4684 } else {
4685 return;
4686 }
4687 } else {
4688 if ( style()->displayMode() != QStyleSheetItem::DisplayBlock ) {
4689 styleSheetItemsVec().remove( styleSheetItemsVec().size() - 1 );
4690 if ( mStyleSheetItemsVec->size() >= 2 ) {
4691 mStyleSheetItemsVec->remove( mStyleSheetItemsVec->size() - 2 );
4692 mStyleSheetItemsVec->resize( mStyleSheetItemsVec->size() - 2 );
4693 } else {
4694 mStyleSheetItemsVec->resize( mStyleSheetItemsVec->size() - 1 );
4695 }
4696 } else {
4697 return;
4698 }
4699 }
4700 invalidate( 0 );
4701 lm = rm = tm = bm = flm = -1;
4702 numSubParag = -1;
4703 if ( next() ) {
4704 QTextParag *s = next();
4705 while ( s ) {
4706 s->numSubParag = -1;
4707 s->lm = s->rm = s->tm = s->bm = flm = -1;
4708 s->numSubParag = -1;
4709 s->invalidate( 0 );
4710 s = s->next();
4711 }
4712 }
4713}
4714
4715void QTextParag::incDepth()
4716{
4717 if ( !style() || !hasdoc )
4718 return;
4719 if ( style()->displayMode() != QStyleSheetItem::DisplayListItem )
4720 return;
4721 styleSheetItemsVec().resize( styleSheetItemsVec().size() + 1 );
4722 mStyleSheetItemsVec->insert( mStyleSheetItemsVec->size() - 1, (*mStyleSheetItemsVec)[ mStyleSheetItemsVec->size() - 2 ] );
4723 mStyleSheetItemsVec->insert( mStyleSheetItemsVec->size() - 2,
4724 listStyle() == QStyleSheetItem::ListDisc || listStyle() == QStyleSheetItem::ListCircle ||
4725 listStyle() == QStyleSheetItem::ListSquare ?
4726 document()->styleSheet()->item( "ul" ) : document()->styleSheet()->item( "ol" ) );
4727 invalidate( 0 );
4728 lm = -1;
4729 flm = -1;
4730}
4731
4732void QTextParag::decDepth()
4733{
4734 if ( !style() || !hasdoc )
4735 return;
4736 if ( style()->displayMode() != QStyleSheetItem::DisplayListItem )
4737 return;
4738 int numLists = 0;
4739 QStyleSheetItem *lastList = 0;
4740 int lastIndex = 0;
4741 int i;
4742 if ( mStyleSheetItemsVec ) {
4743 for ( i = 0; i < (int)mStyleSheetItemsVec->size(); ++i ) {
4744 QStyleSheetItem *item = (*mStyleSheetItemsVec)[ i ];
4745 if ( item->name() == "ol" || item->name() == "ul" ) {
4746 lastList = item;
4747 lastIndex = i;
4748 numLists++;
4749 }
4750 }
4751 }
4752
4753 if ( !lastList )
4754 return;
4755 styleSheetItemsVec().remove( lastIndex );
4756 for ( i = lastIndex; i < (int)mStyleSheetItemsVec->size() - 1; ++i )
4757 mStyleSheetItemsVec->insert( i, (*mStyleSheetItemsVec)[ i + 1 ] );
4758 mStyleSheetItemsVec->resize( mStyleSheetItemsVec->size() - 1 );
4759 if ( numLists == 1 )
4760 setList( FALSE, -1 );
4761 invalidate( 0 );
4762 lm = -1;
4763 flm = -1;
4764}
4765
4766int QTextParag::listDepth() const
4767{
4768 int numLists = 0;
4769 int i;
4770 if ( mStyleSheetItemsVec ) {
4771 for ( i = 0; i < (int)mStyleSheetItemsVec->size(); ++i ) {
4772 QStyleSheetItem *item = (*mStyleSheetItemsVec)[ i ];
4773 if ( item->name() == "ol" || item->name() == "ul" )
4774 numLists++;
4775 }
4776 }
4777 return numLists - 1;
4778}
4779
4780int *QTextParag::tabArray() const
4781{
4782 int *ta = tArray;
4783 if ( !ta && hasdoc )
4784 ta = document()->tabArray();
4785 return ta;
4786}
4787
4788int QTextParag::nextTab( int, int x )
4789{
4790 int *ta = tArray;
4791 if ( hasdoc ) {
4792 if ( !ta )
4793 ta = document()->tabArray();
4794 tabStopWidth = document()->tabStopWidth();
4795 }
4796 if ( ta ) {
4797 int i = 0;
4798 while ( ta[ i ] ) {
4799 if ( ta[ i ] >= x )
4800 return tArray[ i ];
4801 ++i;
4802 }
4803 return tArray[ 0 ];
4804 } else {
4805 int d;
4806 if ( tabStopWidth != 0 )
4807 d = x / tabStopWidth;
4808 else
4809 return x;
4810 return tabStopWidth * ( d + 1 );
4811 }
4812}
4813
4814void QTextParag::adjustToPainter( QPainter *p )
4815{
4816 for ( int i = 0; i < length(); ++i ) {
4817 if ( at( i )->isCustom() )
4818 at( i )->customItem()->adjustToPainter( p );
4819 }
4820}
4821
4822QTextFormatCollection *QTextParag::formatCollection() const
4823{
4824 if ( hasdoc )
4825 return document()->formatCollection();
4826 if ( !qFormatCollection ) {
4827 qFormatCollection = new QTextFormatCollection;
4828 static QSingleCleanupHandler<QTextFormatCollection> qtfCleanup;
4829 qtfCleanup.set( &qFormatCollection );
4830 }
4831 return qFormatCollection;
4832}
4833
4834QString QTextParag::richText() const
4835{
4836 QString s;
4837 QTextStringChar *formatChar = 0;
4838 QString spaces;
4839 bool lastCharWasSpace = FALSE;
4840 int firstcol = 0;
4841 for ( int i = 0; i < length()-1; ++i ) {
4842 QTextStringChar *c = &str->at( i );
4843 if ( c->isAnchor() && !c->anchorName().isEmpty() ) {
4844 if ( c->anchorName().contains( '#' ) ) {
4845 QStringList l = QStringList::split( '#', c->anchorName() );
4846 for ( QStringList::ConstIterator it = l.begin(); it != l.end(); ++it )
4847 s += "<a name=\"" + *it + "\"></a>";
4848 } else {
4849 s += "<a name=\"" + c->anchorName() + "\"></a>";
4850 }
4851 }
4852 if ( !formatChar ) {
4853 s += c->format()->makeFormatChangeTags( 0, QString::null, c->anchorHref() );
4854 formatChar = c;
4855 } else if ( ( formatChar->format()->key() != c->format()->key() ) ||
4856 (formatChar->isAnchor() != c->isAnchor() &&
4857 (!c->anchorHref().isEmpty() || !formatChar->anchorHref().isEmpty() ) ) ) {// lisp was here
4858
4859 if ( !spaces.isEmpty() ) {
4860 if ( spaces[0] == '\t' || lastCharWasSpace )
4861 s += "<wsp>" + spaces + "</wsp>";
4862 else if ( spaces.length() > 1 )
4863 s += "<wsp>" + spaces.mid(1) + "</wsp> ";
4864 else
4865 s += spaces;
4866 lastCharWasSpace = TRUE;
4867 spaces = QString::null;
4868 }
4869 s += c->format()->makeFormatChangeTags( formatChar->format() , formatChar->anchorHref(), c->anchorHref() );
4870 formatChar = c;
4871 }
4872
4873 if ( c->c == ' ' || c->c == '\t' ) {
4874 spaces += c->c;
4875 continue;
4876 } else if ( !spaces.isEmpty() ) {
4877 if ( spaces[0] == '\t' || lastCharWasSpace )
4878 s += "<wsp>" + spaces + "</wsp>";
4879 else if ( spaces.length() > 1 )
4880 s += "<wsp>" + spaces.mid(1) + "</wsp> ";
4881 else
4882 s += spaces;
4883 spaces = QString::null;
4884 if ( s.length() - firstcol > 60 ) {
4885 s += '\n';
4886 firstcol = s.length();
4887 }
4888 }
4889
4890 lastCharWasSpace = FALSE;
4891 if ( c->c == '<' ) {
4892 s += "&lt;";
4893 } else if ( c->c == '>' ) {
4894 s += "&gt;";
4895 } else if ( c->isCustom() ) {
4896 s += c->customItem()->richText();
4897 } else {
4898 s += c->c;
4899 }
4900 }
4901 if ( !spaces.isEmpty() ) {
4902 if ( spaces.length() > 1 || spaces[0] == '\t' || lastCharWasSpace )
4903 s += "<wsp>" + spaces + "</wsp>";
4904 else
4905 s += spaces;
4906 }
4907
4908 if ( formatChar )
4909 s += formatChar->format()->makeFormatEndTags( formatChar->anchorHref() );
4910 return s;
4911}
4912
4913void QTextParag::addCommand( QTextCommand *cmd )
4914{
4915 if ( !hasdoc )
4916 pseudoDocument()->commandHistory->addCommand( cmd );
4917 else
4918 document()->commands()->addCommand( cmd );
4919}
4920
4921QTextCursor *QTextParag::undo( QTextCursor *c )
4922{
4923 if ( !hasdoc )
4924 return pseudoDocument()->commandHistory->undo( c );
4925 return document()->commands()->undo( c );
4926}
4927
4928QTextCursor *QTextParag::redo( QTextCursor *c )
4929{
4930 if ( !hasdoc )
4931 return pseudoDocument()->commandHistory->redo( c );
4932 return document()->commands()->redo( c );
4933}
4934
4935int QTextParag::topMargin() const
4936{
4937 if ( !p && ( !hasdoc || !document()->addMargins() ) )
4938 return 0;
4939 if ( tm != -1 )
4940 return tm;
4941 QStyleSheetItem *item = style();
4942 if ( !item ) {
4943 ( (QTextParag*)this )->tm = 0;
4944 return 0;
4945 }
4946
4947 int m = 0;
4948 if ( item->margin( QStyleSheetItem::MarginTop ) != QStyleSheetItem::Undefined )
4949 m = item->margin( QStyleSheetItem::MarginTop );
4950 if ( mStyleSheetItemsVec ) {
4951 QStyleSheetItem *it = 0;
4952 QStyleSheetItem *p = prev() ? prev()->style() : 0;
4953 for ( int i = (int)mStyleSheetItemsVec->size() - 2 ; i >= 0; --i ) {
4954 it = (*mStyleSheetItemsVec)[ i ];
4955 if ( it != p )
4956 break;
4957 int mar = it->margin( QStyleSheetItem::MarginTop );
4958 m += (mar != QStyleSheetItem::Undefined) ? mar : 0;
4959 if ( it->displayMode() != QStyleSheetItem::DisplayInline )
4960 break;
4961 }
4962 }
4963 m = scale( m, QTextFormat::painter() );
4964
4965 ( (QTextParag*)this )->tm = m;
4966 return tm;
4967}
4968
4969int QTextParag::bottomMargin() const
4970{
4971 if ( bm != -1 )
4972 return bm;
4973 QStyleSheetItem *item = style();
4974 if ( !item || !next() ) {
4975 ( (QTextParag*)this )->bm = 0;
4976 return 0;
4977 }
4978
4979 int m = 0;
4980 if ( item->margin( QStyleSheetItem::MarginBottom ) != QStyleSheetItem::Undefined )
4981 m = item->margin( QStyleSheetItem::MarginBottom );
4982 if ( mStyleSheetItemsVec ) {
4983 QStyleSheetItem *it = 0;
4984 QStyleSheetItem *n = next() ? next()->style() : 0;
4985 for ( int i =(int)mStyleSheetItemsVec->size() - 2 ; i >= 0; --i ) {
4986 it = (*mStyleSheetItemsVec)[ i ];
4987 if ( it != n )
4988 break;
4989 int mar = it->margin( QStyleSheetItem::MarginBottom );
4990 m += mar != QStyleSheetItem::Undefined ? mar : 0;
4991 if ( it->displayMode() != QStyleSheetItem::DisplayInline )
4992 break;
4993 }
4994 }
4995 m = scale ( m, QTextFormat::painter() );
4996
4997 ( (QTextParag*)this )->bm = m;
4998 return bm;
4999}
5000
5001int QTextParag::leftMargin() const
5002{
5003 if ( lm != -1 )
5004 return lm;
5005 QStyleSheetItem *item = style();
5006 if ( !item ) {
5007 ( (QTextParag*)this )->lm = 0;
5008 return 0;
5009 }
5010 int m = 0;
5011 if ( mStyleSheetItemsVec ) {
5012 for ( int i = 0; i < (int)mStyleSheetItemsVec->size(); ++i ) {
5013 item = (*mStyleSheetItemsVec)[ i ];
5014 int mar = item->margin( QStyleSheetItem::MarginLeft );
5015 m += mar != QStyleSheetItem::Undefined ? mar : 0;
5016 if ( item->name() == "ol" || item->name() == "ul" ) {
5017 QPainter* oldPainter = QTextFormat::painter();
5018 QTextFormat::setPainter( 0 );
5019 m += defFormat->width( '1' ) +
5020 defFormat->width( '2' ) +
5021 defFormat->width( '3' ) +
5022 defFormat->width( '.' );
5023 QTextFormat::setPainter( oldPainter );
5024 }
5025 }
5026 }
5027
5028 m = scale ( m, QTextFormat::painter() );
5029
5030 ( (QTextParag*)this )->lm = m;
5031 return lm;
5032}
5033
5034int QTextParag::firstLineMargin() const
5035{
5036 if ( flm != -1 )
5037 return lm;
5038 QStyleSheetItem *item = style();
5039 if ( !item ) {
5040 ( (QTextParag*)this )->flm = 0;
5041 return 0;
5042 }
5043 int m = 0;
5044 if ( mStyleSheetItemsVec ) {
5045 for ( int i = 0; i < (int)mStyleSheetItemsVec->size(); ++i ) {
5046 item = (*mStyleSheetItemsVec)[ i ];
5047 int mar = item->margin( QStyleSheetItem::MarginFirstLine );
5048 m += mar != QStyleSheetItem::Undefined ? mar : 0;
5049 }
5050 }
5051
5052 m = scale( m, QTextFormat::painter() );
5053
5054 ( (QTextParag*)this )->flm = m;
5055 return flm;
5056}
5057
5058int QTextParag::rightMargin() const
5059{
5060 if ( rm != -1 )
5061 return rm;
5062 QStyleSheetItem *item = style();
5063 if ( !item ) {
5064 ( (QTextParag*)this )->rm = 0;
5065 return 0;
5066 }
5067 int m = 0;
5068 if ( mStyleSheetItemsVec ) {
5069 for ( int i = 0; i < (int)mStyleSheetItemsVec->size(); ++i ) {
5070 item = (*mStyleSheetItemsVec)[ i ];
5071 int mar = item->margin( QStyleSheetItem::MarginRight );
5072 m += mar != QStyleSheetItem::Undefined ? mar : 0;
5073 }
5074 }
5075 m = scale( m, QTextFormat::painter() );
5076
5077 ( (QTextParag*)this )->rm = m;
5078 return rm;
5079}
5080
5081int QTextParag::lineSpacing() const
5082{
5083 QStyleSheetItem *item = style();
5084 if ( !item )
5085 return 0;
5086
5087 int ls = item->lineSpacing();
5088 if ( ls == QStyleSheetItem::Undefined )
5089 return 0;
5090 ls = scale( ls, QTextFormat::painter() );
5091
5092 return ls;
5093}
5094
5095void QTextParag::copyParagData( QTextParag *parag )
5096{
5097 setStyleSheetItems( parag->styleSheetItems() );
5098 setListStyle( parag->listStyle() );
5099 setAlignment( parag->alignment() );
5100 QColor *c = parag->backgroundColor();
5101 if ( c )
5102 setBackgroundColor( *c );
5103}
5104
5105void QTextParag::show()
5106{
5107 if ( visible || !hasdoc )
5108 return;
5109 visible = TRUE;
5110}
5111
5112void QTextParag::hide()
5113{
5114 if ( !visible || !hasdoc )
5115 return;
5116 visible = FALSE;
5117}
5118
5119void QTextParag::setDirection( QChar::Direction d )
5120{
5121 if ( str && str->direction() != d ) {
5122 str->setDirection( d );
5123 invalidate( 0 );
5124 }
5125}
5126
5127QChar::Direction QTextParag::direction() const
5128{
5129 return (str ? str->direction() : QChar::DirON );
5130}
5131
5132void QTextParag::setChanged( bool b, bool recursive )
5133{
5134 changed = b;
5135 if ( recursive ) {
5136 if ( document() && document()->parentParag() )
5137 document()->parentParag()->setChanged( b, recursive );
5138 }
5139}
5140
5141// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5142
5143
5144QTextPreProcessor::QTextPreProcessor()
5145{
5146}
5147
5148// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5149
5150QTextFormatter::QTextFormatter()
5151 : thisminw(0), thiswused(0), wrapEnabled( TRUE ), wrapColumn( -1 ), biw( FALSE )
5152{
5153}
5154
5155/* only used for bidi or complex text reordering
5156 */
5157QTextParagLineStart *QTextFormatter::formatLine( QTextParag *parag, QTextString *string, QTextParagLineStart *line,
5158 QTextStringChar *startChar, QTextStringChar *lastChar, int align, int space )
5159{
5160#ifndef QT_NO_COMPLEXTEXT
5161 if( string->isBidi() )
5162 return bidiReorderLine( parag, string, line, startChar, lastChar, align, space );
5163#endif
5164 space = QMAX( space, 0 ); // #### with nested tables this gets negative because of a bug I didn't find yet, so workaround for now. This also means non-left aligned nested tables do not work at the moment
5165 int start = (startChar - &string->at(0));
5166 int last = (lastChar - &string->at(0) );
5167 // do alignment Auto == Left in this case
5168 if ( align & Qt::AlignHCenter || align & Qt::AlignRight ) {
5169 if ( align & Qt::AlignHCenter )
5170 space /= 2;
5171 for ( int j = start; j <= last; ++j )
5172 string->at( j ).x += space;
5173 } else if ( align & Qt3::AlignJustify ) {
5174 int numSpaces = 0;
5175 for ( int j = start; j < last; ++j ) {
5176 if( isBreakable( string, j ) ) {
5177 numSpaces++;
5178 }
5179 }
5180 int toAdd = 0;
5181 for ( int k = start + 1; k <= last; ++k ) {
5182 if( isBreakable( string, k ) && numSpaces ) {
5183 int s = space / numSpaces;
5184 toAdd += s;
5185 space -= s;
5186 numSpaces--;
5187 }
5188 string->at( k ).x += toAdd;
5189 }
5190 }
5191
5192 if ( last >= 0 && last < string->length() )
5193 line->w = string->at( last ).x + string->width( last );
5194 else
5195 line->w = 0;
5196
5197 return new QTextParagLineStart();
5198}
5199
5200#ifndef QT_NO_COMPLEXTEXT
5201
5202#ifdef BIDI_DEBUG
5203#include <iostream>
5204#endif
5205
5206// collects one line of the paragraph and transforms it to visual order
5207QTextParagLineStart *QTextFormatter::bidiReorderLine( QTextParag * /*parag*/, QTextString *text, QTextParagLineStart *line,
5208 QTextStringChar *startChar, QTextStringChar *lastChar, int align, int space )
5209{
5210 int start = (startChar - &text->at(0));
5211 int last = (lastChar - &text->at(0) );
5212 //qDebug("doing BiDi reordering from %d to %d!", start, last);
5213
5214 QBidiControl *control = new QBidiControl( line->context(), line->status );
5215 QString str;
5216 str.setUnicode( 0, last - start + 1 );
5217 // fill string with logically ordered chars.
5218 QTextStringChar *ch = startChar;
5219 QChar *qch = (QChar *)str.unicode();
5220 while ( ch <= lastChar ) {
5221 *qch = ch->c;
5222 qch++;
5223 ch++;
5224 }
5225 int x = startChar->x;
5226
5227 QPtrList<QTextRun> *runs;
5228 runs = QComplexText::bidiReorderLine(control, str, 0, last - start + 1,
5229 (text->isRightToLeft() ? QChar::DirR : QChar::DirL) );
5230
5231 // now construct the reordered string out of the runs...
5232
5233 int numSpaces = 0;
5234 // set the correct alignment. This is a bit messy....
5235 if( align == Qt3::AlignAuto ) {
5236 // align according to directionality of the paragraph...
5237 if ( text->isRightToLeft() )
5238 align = Qt::AlignRight;
5239 }
5240
5241 if ( align & Qt::AlignHCenter )
5242 x += space/2;
5243 else if ( align & Qt::AlignRight )
5244 x += space;
5245 else if ( align & Qt3::AlignJustify ) {
5246 for ( int j = start; j < last; ++j ) {
5247 if( isBreakable( text, j ) ) {
5248 numSpaces++;
5249 }
5250 }
5251 }
5252 int toAdd = 0;
5253 bool first = TRUE;
5254 QTextRun *r = runs->first();
5255 int xmax = -0xffffff;
5256 while ( r ) {
5257 if(r->level %2) {
5258 // odd level, need to reverse the string
5259 int pos = r->stop + start;
5260 while(pos >= r->start + start) {
5261 QTextStringChar *c = &text->at(pos);
5262 if( numSpaces && !first && isBreakable( text, pos ) ) {
5263 int s = space / numSpaces;
5264 toAdd += s;
5265 space -= s;
5266 numSpaces--;
5267 } else if ( first ) {
5268 first = FALSE;
5269 if ( c->c == ' ' )
5270 x -= c->format()->width( ' ' );
5271 }
5272 c->x = x + toAdd;
5273 c->rightToLeft = TRUE;
5274 c->startOfRun = FALSE;
5275 int ww = 0;
5276 if ( c->c.unicode() >= 32 || c->c == '\t' || c->c == '\n' || c->isCustom() ) {
5277 ww = text->width( pos );
5278 } else {
5279 ww = c->format()->width( ' ' );
5280 }
5281 if ( xmax < x + toAdd + ww ) xmax = x + toAdd + ww;
5282 x += ww;
5283 pos--;
5284 }
5285 } else {
5286 int pos = r->start + start;
5287 while(pos <= r->stop + start) {
5288 QTextStringChar* c = &text->at(pos);
5289 if( numSpaces && !first && isBreakable( text, pos ) ) {
5290 int s = space / numSpaces;
5291 toAdd += s;
5292 space -= s;
5293 numSpaces--;
5294 } else if ( first ) {
5295 first = FALSE;
5296 if ( c->c == ' ' )
5297 x -= c->format()->width( ' ' );
5298 }
5299 c->x = x + toAdd;
5300 c->rightToLeft = FALSE;
5301 c->startOfRun = FALSE;
5302 int ww = 0;
5303 if ( c->c.unicode() >= 32 || c->c == '\t' || c->isCustom() ) {
5304 ww = text->width( pos );
5305 } else {
5306 ww = c->format()->width( ' ' );
5307 }
5308 //qDebug("setting char %d at pos %d", pos, x);
5309 if ( xmax < x + toAdd + ww ) xmax = x + toAdd + ww;
5310 x += ww;
5311 pos++;
5312 }
5313 }
5314 text->at( r->start + start ).startOfRun = TRUE;
5315 r = runs->next();
5316 }
5317
5318 line->w = xmax + 10;
5319 QTextParagLineStart *ls = new QTextParagLineStart( control->context, control->status );
5320 delete control;
5321 delete runs;
5322 return ls;
5323}
5324#endif
5325
5326bool QTextFormatter::isBreakable( QTextString *string, int pos ) const
5327{
5328 const QChar &c = string->at( pos ).c;
5329 char ch = c.latin1();
5330 if ( c.isSpace() && ch != '\n' && c.unicode() != 0x00a0U )
5331 return TRUE;
5332 if ( c.unicode() == 0xad ) // soft hyphen
5333 return TRUE;
5334 if ( !ch ) {
5335 // not latin1, need to do more sophisticated checks for other scripts
5336 uchar row = c.row();
5337 if ( row == 0x0e ) {
5338 // 0e00 - 0e7f == Thai
5339 if ( c.cell() < 0x80 ) {
5340#ifdef HAVE_THAI_BREAKS
5341 // check for thai
5342 if( string != cachedString ) {
5343 // build up string of thai chars
5344 QTextCodec *thaiCodec = QTextCodec::codecForMib(2259);
5345 if ( !thaiCache )
5346 thaiCache = new QCString;
5347 if ( !thaiIt )
5348 thaiIt = ThBreakIterator::createWordInstance();
5349 *thaiCache = thaiCodec->fromUnicode( s->string() );
5350 }
5351 thaiIt->setText(thaiCache->data());
5352 for(int i = thaiIt->first(); i != thaiIt->DONE; i = thaiIt->next() ) {
5353 if( i == pos )
5354 return TRUE;
5355 if( i > pos )
5356 return FALSE;
5357 }
5358 return FALSE;
5359#else
5360 // if we don't have a thai line breaking lib, allow
5361 // breaks everywhere except directly before punctuation.
5362 return TRUE;
5363#endif
5364 } else
5365 return FALSE;
5366 }
5367 if ( row < 0x11 ) // no asian font
5368 return FALSE;
5369 if ( row > 0x2d && row < 0xfb || row == 0x11 )
5370 // asian line breaking. Everywhere allowed except directly
5371 // in front of a punctuation character.
5372 return TRUE;
5373 }
5374 return FALSE;
5375}
5376
5377void QTextFormatter::insertLineStart( QTextParag *parag, int index, QTextParagLineStart *ls )
5378{
5379 if ( index > 0 ) { // we can assume that only first line starts are insrted multiple times
5380 parag->lineStartList().insert( index, ls );
5381 return;
5382 }
5383 QMap<int, QTextParagLineStart*>::Iterator it;
5384 if ( ( it = parag->lineStartList().find( index ) ) == parag->lineStartList().end() ) {
5385 parag->lineStartList().insert( index, ls );
5386 } else {
5387 delete *it;
5388 parag->lineStartList().remove( it );
5389 parag->lineStartList().insert( index, ls );
5390 }
5391}
5392
5393
5394/* Standard pagebreak algorithm using QTextFlow::adjustFlow. Returns
5395 the shift of the paragraphs bottom line.
5396 */
5397int QTextFormatter::formatVertically( QTextDocument* doc, QTextParag* parag )
5398{
5399 int oldHeight = parag->rect().height();
5400 QMap<int, QTextParagLineStart*>& lineStarts = parag->lineStartList();
5401 QMap<int, QTextParagLineStart*>::Iterator it = lineStarts.begin();
5402 int h = doc->addMargins() ? parag->topMargin() : 0;
5403 for ( ; it != lineStarts.end() ; ++it ) {
5404 QTextParagLineStart * ls = it.data();
5405 ls->y = h;
5406 QTextStringChar *c = &parag->string()->at(it.key());
5407 if ( c && c->customItem() && c->customItem()->ownLine() ) {
5408 int h = c->customItem()->height;
5409 c->customItem()->pageBreak( parag->rect().y() + ls->y + ls->baseLine - h, doc->flow() );
5410 int delta = c->customItem()->height - h;
5411 ls->h += delta;
5412 if ( delta )
5413 parag->setMovedDown( TRUE );
5414 } else {
5415 int shift = doc->flow()->adjustFlow( parag->rect().y() + ls->y, ls->w, ls->h );
5416 ls->y += shift;
5417 if ( shift )
5418 parag->setMovedDown( TRUE );
5419 }
5420 h = ls->y + ls->h;
5421 }
5422 int m = parag->bottomMargin();
5423 if ( parag->next() && doc && !doc->addMargins() )
5424 m = QMAX( m, parag->next()->topMargin() );
5425 if ( parag->next() && parag->next()->isLineBreak() )
5426 m = 0;
5427 h += m;
5428 parag->setHeight( h );
5429 return h - oldHeight;
5430}
5431
5432// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5433
5434QTextFormatterBreakInWords::QTextFormatterBreakInWords()
5435{
5436}
5437
5438int QTextFormatterBreakInWords::format( QTextDocument *doc,QTextParag *parag,
5439 int start, const QMap<int, QTextParagLineStart*> & )
5440{
5441 QTextStringChar *c = 0;
5442 QTextStringChar *firstChar = 0;
5443 int left = doc ? parag->leftMargin() + doc->leftMargin() : 4;
5444 int x = left + ( doc ? parag->firstLineMargin() : 0 );
5445 int dw = parag->documentVisibleWidth() - ( doc ? doc->rightMargin() : 0 );
5446 int y = doc && doc->addMargins() ? parag->topMargin() : 0;
5447 int h = y;
5448 int len = parag->length();
5449 if ( doc )
5450 x = doc->flow()->adjustLMargin( y + parag->rect().y(), parag->rect().height(), x, 4 );
5451 int rm = parag->rightMargin();
5452 int w = dw - ( doc ? doc->flow()->adjustRMargin( y + parag->rect().y(), parag->rect().height(), rm, 4 ) : 0 );
5453 bool fullWidth = TRUE;
5454 int minw = 0;
5455 int wused = 0;
5456 bool wrapEnabled = isWrapEnabled( parag );
5457
5458 start = 0; //######### what is the point with start?! (Matthias)
5459 if ( start == 0 )
5460 c = &parag->string()->at( 0 );
5461
5462 int i = start;
5463 QTextParagLineStart *lineStart = new QTextParagLineStart( y, y, 0 );
5464 insertLineStart( parag, 0, lineStart );
5465
5466 QPainter *painter = QTextFormat::painter();
5467
5468 int col = 0;
5469 int ww = 0;
5470 QChar lastChr;
5471 for ( ; i < len; ++i, ++col ) {
5472 if ( c )
5473 lastChr = c->c;
5474 c = &parag->string()->at( i );
5475 c->rightToLeft = FALSE;
5476 // ### the lines below should not be needed
5477 if ( painter )
5478 c->format()->setPainter( painter );
5479 if ( i > 0 ) {
5480 c->lineStart = 0;
5481 } else {
5482 c->lineStart = 1;
5483 firstChar = c;
5484 }
5485 if ( c->c.unicode() >= 32 || c->isCustom() ) {
5486 ww = parag->string()->width( i );
5487 } else if ( c->c == '\t' ) {
5488 int nx = parag->nextTab( i, x - left ) + left;
5489 if ( nx < x )
5490 ww = w - x;
5491 else
5492 ww = nx - x;
5493 } else {
5494 ww = c->format()->width( ' ' );
5495 }
5496
5497 if ( c->isCustom() && c->customItem()->ownLine() ) {
5498 x = doc ? doc->flow()->adjustLMargin( y + parag->rect().y(), parag->rect().height(), left, 4 ) : left;
5499 w = dw - ( doc ? doc->flow()->adjustRMargin( y + parag->rect().y(), parag->rect().height(), rm, 4 ) : 0 );
5500 c->customItem()->resize( w - x );
5501 w = dw;
5502 y += h;
5503 h = c->height();
5504 lineStart = new QTextParagLineStart( y, h, h );
5505 insertLineStart( parag, i, lineStart );
5506 c->lineStart = 1;
5507 firstChar = c;
5508 x = 0xffffff;
5509 continue;
5510 }
5511
5512 if ( wrapEnabled &&
5513 ( wrapAtColumn() == -1 && x + ww > w ||
5514 wrapAtColumn() != -1 && col >= wrapAtColumn() ) ||
5515 parag->isNewLinesAllowed() && lastChr == '\n' ) {
5516 x = doc ? parag->document()->flow()->adjustLMargin( y + parag->rect().y(), parag->rect().height(), left, 4 ) : left;
5517 w = dw;
5518 y += h;
5519 h = c->height();
5520 lineStart = formatLine( parag, parag->string(), lineStart, firstChar, c-1 );
5521 lineStart->y = y;
5522 insertLineStart( parag, i, lineStart );
5523 lineStart->baseLine = c->ascent();
5524 lineStart->h = c->height();
5525 c->lineStart = 1;
5526 firstChar = c;
5527 col = 0;
5528 if ( wrapAtColumn() != -1 )
5529 minw = QMAX( minw, w );
5530 } else if ( lineStart ) {
5531 lineStart->baseLine = QMAX( lineStart->baseLine, c->ascent() );
5532 h = QMAX( h, c->height() );
5533 lineStart->h = h;
5534 }
5535
5536 c->x = x;
5537 x += ww;
5538 wused = QMAX( wused, x );
5539 }
5540
5541 int m = parag->bottomMargin();
5542 if ( parag->next() && doc && !doc->addMargins() )
5543 m = QMAX( m, parag->next()->topMargin() );
5544 parag->setFullWidth( fullWidth );
5545 if ( parag->next() && parag->next()->isLineBreak() )
5546 m = 0;
5547 y += h + m;
5548 if ( !wrapEnabled )
5549 minw = QMAX(minw, wused);
5550
5551 thisminw = minw;
5552 thiswused = wused;
5553 return y;
5554}
5555
5556// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5557
5558QTextFormatterBreakWords::QTextFormatterBreakWords()
5559{
5560}
5561
5562#define DO_FLOW( lineStart ) do{ if ( doc && doc->isPageBreakEnabled() ) { \
5563 int yflow = lineStart->y + parag->rect().y();\
5564 int shift = doc->flow()->adjustFlow( yflow, dw, lineStart->h ); \
5565 lineStart->y += shift;\
5566 y += shift;\
5567 }}while(FALSE)
5568
5569int QTextFormatterBreakWords::format( QTextDocument *doc, QTextParag *parag,
5570 int start, const QMap<int, QTextParagLineStart*> & )
5571{
5572 QTextStringChar *c = 0;
5573 QTextStringChar *firstChar = 0;
5574 QTextString *string = parag->string();
5575 int left = doc ? parag->leftMargin() + doc->leftMargin() : 0;
5576 int x = left + ( doc ? parag->firstLineMargin() : 0 );
5577 int y = doc && doc->addMargins() ? parag->topMargin() : 0;
5578 int h = y;
5579 int len = parag->length();
5580 if ( doc )
5581 x = doc->flow()->adjustLMargin( y + parag->rect().y(), parag->rect().height(), x, 0 );
5582 int dw = parag->documentVisibleWidth() - ( doc ? ( left != x ? 0 : doc->rightMargin() ) : 0 );
5583
5584 int curLeft = x;
5585 int rm = parag->rightMargin();
5586 int rdiff = doc ? doc->flow()->adjustRMargin( y + parag->rect().y(), parag->rect().height(), rm, 0 ) : 0;
5587 int w = dw - rdiff;
5588 bool fullWidth = TRUE;
5589 int marg = left + rdiff;
5590 int minw = 0;
5591 int wused = 0;
5592 int tminw = marg;
5593 int linespace = doc ? parag->lineSpacing() : 0;
5594 bool wrapEnabled = isWrapEnabled( parag );
5595
5596 start = 0;
5597 if ( start == 0 )
5598 c = &parag->string()->at( 0 );
5599
5600 int i = start;
5601 QTextParagLineStart *lineStart = new QTextParagLineStart( y, y, 0 );
5602 insertLineStart( parag, 0, lineStart );
5603 int lastBreak = -1;
5604 int tmpBaseLine = 0, tmph = 0;
5605 bool lastWasNonInlineCustom = FALSE;
5606
5607 int align = parag->alignment();
5608 if ( align == Qt3::AlignAuto && doc && doc->alignment() != Qt3::AlignAuto )
5609 align = doc->alignment();
5610
5611 align &= Qt3::AlignHorizontal_Mask;
5612
5613 QPainter *painter = QTextFormat::painter();
5614 int col = 0;
5615 int ww = 0;
5616 QChar lastChr;
5617 for ( ; i < len; ++i, ++col ) {
5618 if ( c )
5619 lastChr = c->c;
5620 // ### next line should not be needed
5621 if ( painter )
5622 c->format()->setPainter( painter );
5623 c = &string->at( i );
5624 c->rightToLeft = FALSE;
5625 if ( i > 0 && (x > curLeft || ww == 0) || lastWasNonInlineCustom ) {
5626 c->lineStart = 0;
5627 } else {
5628 c->lineStart = 1;
5629 firstChar = c;
5630 }
5631
5632 if ( c->isCustom() && c->customItem()->placement() != QTextCustomItem::PlaceInline )
5633 lastWasNonInlineCustom = TRUE;
5634 else
5635 lastWasNonInlineCustom = FALSE;
5636
5637 if ( c->c.unicode() >= 32 || c->isCustom() ) {
5638 ww = string->width( i );
5639 } else if ( c->c == '\t' ) {
5640 int nx = parag->nextTab( i, x - left ) + left;
5641 if ( nx < x )
5642 ww = w - x;
5643 else
5644 ww = nx - x;
5645 } else {
5646 ww = c->format()->width( ' ' );
5647 }
5648
5649 // last character ("invisible" space) has no width
5650 if ( i == len - 1 )
5651 ww = 0;
5652
5653 QTextCustomItem* ci = c->customItem();
5654 if ( c->isCustom() && ci->ownLine() ) {
5655 x = doc ? doc->flow()->adjustLMargin( y + parag->rect().y(), parag->rect().height(), left, 4 ) : left;
5656 w = dw - ( doc ? doc->flow()->adjustRMargin( y + parag->rect().y(), parag->rect().height(), rm, 4 ) : 0 );
5657 QTextParagLineStart *lineStart2 = formatLine( parag, string, lineStart, firstChar, c-1, align, w - x );
5658 ci->resize( w - x);
5659 if ( ci->width < w - x ) {
5660 if ( align & Qt::AlignHCenter )
5661 x = ( w - ci->width ) / 2;
5662 else if ( align & Qt::AlignRight ) {
5663 x = w - ci->width;
5664 }
5665 }
5666 c->x = x;
5667 curLeft = x;
5668 if ( i == 0 || !isBreakable( string, i - 1 ) || string->at( i - 1 ).lineStart == 0 ) {
5669 y += QMAX( h, tmph );
5670 tmph = c->height() + linespace;
5671 h = tmph;
5672 lineStart = lineStart2;
5673 lineStart->y = y;
5674 insertLineStart( parag, i, lineStart );
5675 c->lineStart = 1;
5676 firstChar = c;
5677 } else {
5678 tmph = c->height() + linespace;
5679 h = tmph;
5680 delete lineStart2;
5681 }
5682 lineStart->h = h;
5683 lineStart->baseLine = h;
5684 tmpBaseLine = lineStart->baseLine;
5685 lastBreak = -2;
5686 x = 0xffffff;
5687 minw = QMAX( minw, tminw );
5688
5689 int tw = ci->minimumWidth();
5690 if ( tw < QWIDGETSIZE_MAX )
5691 tminw = tw;
5692 else
5693 tminw = marg;
5694 wused = QMAX( wused, ci->width );
5695 continue;
5696 } else if ( c->isCustom() && ci->placement() != QTextCustomItem::PlaceInline ) {
5697 int tw = ci->minimumWidth();
5698 if ( tw < QWIDGETSIZE_MAX )
5699 minw = QMAX( minw, tw );
5700 }
5701
5702 if ( wrapEnabled && ( !c->c.isSpace() || lastBreak == -2 )
5703 && ( lastBreak != -1 || allowBreakInWords() ) &&
5704 ( wrapAtColumn() == -1 && x + ww > w && lastBreak != -1 ||
5705 wrapAtColumn() == -1 && x + ww > w - 4 && lastBreak == -1 && allowBreakInWords() ||
5706 wrapAtColumn() != -1 && col >= wrapAtColumn() ) ||
5707 parag->isNewLinesAllowed() && lastChr == '\n' && firstChar < c ) {
5708 if ( wrapAtColumn() != -1 )
5709 minw = QMAX( minw, x + ww );
5710 if ( lastBreak < 0 ) {
5711 if ( lineStart ) {
5712 lineStart->baseLine = QMAX( lineStart->baseLine, tmpBaseLine );
5713 h = QMAX( h, tmph );
5714 lineStart->h = h;
5715 DO_FLOW( lineStart );
5716 }
5717 lineStart = formatLine( parag, string, lineStart, firstChar, c-1, align, w - x );
5718 x = doc ? doc->flow()->adjustLMargin( y + parag->rect().y(), parag->rect().height(), left, 4 ) : left;
5719 w = dw - ( doc ? doc->flow()->adjustRMargin( y + parag->rect().y(), parag->rect().height(), rm, 4 ) : 0 );
5720 if ( parag->isNewLinesAllowed() && c->c == '\t' ) {
5721 int nx = parag->nextTab( i, x - left ) + left;
5722 if ( nx < x )
5723 ww = w - x;
5724 else
5725 ww = nx - x;
5726 }
5727 curLeft = x;
5728 y += h;
5729 tmph = c->height() + linespace;
5730 h = 0;
5731 lineStart->y = y;
5732 insertLineStart( parag, i, lineStart );
5733 lineStart->baseLine = c->ascent();
5734 lineStart->h = c->height();
5735 c->lineStart = 1;
5736 firstChar = c;
5737 tmpBaseLine = lineStart->baseLine;
5738 lastBreak = -1;
5739 col = 0;
5740 } else {
5741 DO_FLOW( lineStart );
5742 i = lastBreak;
5743 lineStart = formatLine( parag, string, lineStart, firstChar, parag->at( lastBreak ), align, w - string->at( i ).x );
5744 x = doc ? doc->flow()->adjustLMargin( y + parag->rect().y(), parag->rect().height(), left, 4 ) : left;
5745 w = dw - ( doc ? doc->flow()->adjustRMargin( y + parag->rect().y(), parag->rect().height(), rm, 4 ) : 0 );
5746 if ( parag->isNewLinesAllowed() && c->c == '\t' ) {
5747 int nx = parag->nextTab( i, x - left ) + left;
5748 if ( nx < x )
5749 ww = w - x;
5750 else
5751 ww = nx - x;
5752 }
5753 curLeft = x;
5754 y += h;
5755 tmph = c->height() + linespace;
5756 h = tmph;
5757 lineStart->y = y;
5758 insertLineStart( parag, i + 1, lineStart );
5759 lineStart->baseLine = c->ascent();
5760 lineStart->h = c->height();
5761 c->lineStart = 1;
5762 firstChar = c;
5763 tmpBaseLine = lineStart->baseLine;
5764 lastBreak = -1;
5765 col = 0;
5766 tminw = marg;
5767 continue;
5768 }
5769 } else if ( lineStart && ( isBreakable( string, i ) || parag->isNewLinesAllowed() && c->c == '\n' ) ) {
5770 if ( len <= 2 || i < len - 1 ) {
5771 tmpBaseLine = QMAX( tmpBaseLine, c->ascent() );
5772 tmph = QMAX( tmph, c->height() + linespace );
5773 }
5774 minw = QMAX( minw, tminw );
5775 tminw = marg + ww;
5776 lineStart->baseLine = QMAX( lineStart->baseLine, tmpBaseLine );
5777 h = QMAX( h, tmph );
5778 lineStart->h = h;
5779 if ( i < len - 2 || c->c != ' ' )
5780 lastBreak = i;
5781 } else {
5782 tminw += ww;
5783 int belowBaseLine = QMAX( tmph - tmpBaseLine, c->height() + linespace - c->ascent() );
5784 tmpBaseLine = QMAX( tmpBaseLine, c->ascent() );
5785 tmph = tmpBaseLine + belowBaseLine;
5786 }
5787
5788 c->x = x;
5789 x += ww;
5790 wused = QMAX( wused, x );
5791 }
5792
5793 // ### hack. The last char in the paragraph is always invisible, and somehow sometimes has a wrong format. It changes between
5794 // layouting and printing. This corrects some layouting errors in BiDi mode due to this.
5795 if ( len > 1 && !c->isAnchor() ) {
5796 c->format()->removeRef();
5797 c->setFormat( string->at( len - 2 ).format() );
5798 c->format()->addRef();
5799 }
5800
5801 if ( lineStart ) {
5802 lineStart->baseLine = QMAX( lineStart->baseLine, tmpBaseLine );
5803 h = QMAX( h, tmph );
5804 lineStart->h = h;
5805 // last line in a paragraph is not justified
5806 if ( align == Qt3::AlignJustify )
5807 align = Qt3::AlignAuto;
5808 DO_FLOW( lineStart );
5809 lineStart = formatLine( parag, string, lineStart, firstChar, c, align, w - x );
5810 delete lineStart;
5811 }
5812
5813 minw = QMAX( minw, tminw );
5814
5815 int m = parag->bottomMargin();
5816 if ( parag->next() && doc && !doc->addMargins() )
5817 m = QMAX( m, parag->next()->topMargin() );
5818 parag->setFullWidth( fullWidth );
5819 if ( parag->next() && parag->next()->isLineBreak() )
5820 m = 0;
5821 y += h + m;
5822
5823 wused += rm;
5824 if ( !wrapEnabled || wrapAtColumn() != -1 )
5825 minw = QMAX(minw, wused);
5826 thisminw = minw;
5827 thiswused = wused;
5828 return y;
5829}
5830
5831// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5832
5833QTextIndent::QTextIndent()
5834{
5835}
5836
5837// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5838
5839QTextFormatCollection::QTextFormatCollection()
5840 : cKey( 307 ), sheet( 0 )
5841{
5842 defFormat = new QTextFormat( QApplication::font(),
5843 QApplication::palette().color( QPalette::Active, QColorGroup::Text ) );
5844 lastFormat = cres = 0;
5845 cflags = -1;
5846 cKey.setAutoDelete( TRUE );
5847 cachedFormat = 0;
5848}
5849
5850QTextFormatCollection::~QTextFormatCollection()
5851{
5852 delete defFormat;
5853}
5854
5855QTextFormat *QTextFormatCollection::format( QTextFormat *f )
5856{
5857 if ( f->parent() == this || f == defFormat ) {
5858#ifdef DEBUG_COLLECTION
5859 qDebug( "need '%s', best case!", f->key().latin1() );
5860#endif
5861 lastFormat = f;
5862 lastFormat->addRef();
5863 return lastFormat;
5864 }
5865
5866 if ( f == lastFormat || ( lastFormat && f->key() == lastFormat->key() ) ) {
5867#ifdef DEBUG_COLLECTION
5868 qDebug( "need '%s', good case!", f->key().latin1() );
5869#endif
5870 lastFormat->addRef();
5871 return lastFormat;
5872 }
5873
5874 QTextFormat *fm = cKey.find( f->key() );
5875 if ( fm ) {
5876#ifdef DEBUG_COLLECTION
5877 qDebug( "need '%s', normal case!", f->key().latin1() );
5878#endif
5879 lastFormat = fm;
5880 lastFormat->addRef();
5881 return lastFormat;
5882 }
5883
5884 if ( f->key() == defFormat->key() )
5885 return defFormat;
5886
5887#ifdef DEBUG_COLLECTION
5888 qDebug( "need '%s', worst case!", f->key().latin1() );
5889#endif
5890 lastFormat = createFormat( *f );
5891 lastFormat->collection = this;
5892 cKey.insert( lastFormat->key(), lastFormat );
5893 return lastFormat;
5894}
5895
5896QTextFormat *QTextFormatCollection::format( QTextFormat *of, QTextFormat *nf, int flags )
5897{
5898 if ( cres && kof == of->key() && knf == nf->key() && cflags == flags ) {
5899#ifdef DEBUG_COLLECTION
5900 qDebug( "mix of '%s' and '%s, best case!", of->key().latin1(), nf->key().latin1() );
5901#endif
5902 cres->addRef();
5903 return cres;
5904 }
5905
5906 cres = createFormat( *of );
5907 kof = of->key();
5908 knf = nf->key();
5909 cflags = flags;
5910 if ( flags & QTextFormat::Bold )
5911 cres->fn.setBold( nf->fn.bold() );
5912 if ( flags & QTextFormat::Italic )
5913 cres->fn.setItalic( nf->fn.italic() );
5914 if ( flags & QTextFormat::Underline )
5915 cres->fn.setUnderline( nf->fn.underline() );
5916 if ( flags & QTextFormat::Family )
5917 cres->fn.setFamily( nf->fn.family() );
5918 if ( flags & QTextFormat::Size ) {
5919 if ( of->usePixelSizes )
5920 cres->fn.setPixelSize( nf->fn.pixelSize() );
5921 else
5922 cres->fn.setPointSize( nf->fn.pointSize() );
5923 }
5924 if ( flags & QTextFormat::Color )
5925 cres->col = nf->col;
5926 if ( flags & QTextFormat::Misspelled )
5927 cres->missp = nf->missp;
5928 if ( flags & QTextFormat::VAlign )
5929 cres->ha = nf->ha;
5930 cres->update();
5931
5932 QTextFormat *fm = cKey.find( cres->key() );
5933 if ( !fm ) {
5934#ifdef DEBUG_COLLECTION
5935 qDebug( "mix of '%s' and '%s, worst case!", of->key().latin1(), nf->key().latin1() );
5936#endif
5937 cres->collection = this;
5938 cKey.insert( cres->key(), cres );
5939 } else {
5940#ifdef DEBUG_COLLECTION
5941 qDebug( "mix of '%s' and '%s, good case!", of->key().latin1(), nf->key().latin1() );
5942#endif
5943 delete cres;
5944 cres = fm;
5945 cres->addRef();
5946 }
5947
5948 return cres;
5949}
5950
5951QTextFormat *QTextFormatCollection::format( const QFont &f, const QColor &c )
5952{
5953 if ( cachedFormat && cfont == f && ccol == c ) {
5954#ifdef DEBUG_COLLECTION
5955 qDebug( "format of font and col '%s' - best case", cachedFormat->key().latin1() );
5956#endif
5957 cachedFormat->addRef();
5958 return cachedFormat;
5959 }
5960
5961 QString key = QTextFormat::getKey( f, c, FALSE, QTextFormat::AlignNormal );
5962 cachedFormat = cKey.find( key );
5963 cfont = f;
5964 ccol = c;
5965
5966 if ( cachedFormat ) {
5967#ifdef DEBUG_COLLECTION
5968 qDebug( "format of font and col '%s' - good case", cachedFormat->key().latin1() );
5969#endif
5970 cachedFormat->addRef();
5971 return cachedFormat;
5972 }
5973
5974 if ( key == defFormat->key() )
5975 return defFormat;
5976
5977 cachedFormat = createFormat( f, c );
5978 cachedFormat->collection = this;
5979 cKey.insert( cachedFormat->key(), cachedFormat );
5980 if ( cachedFormat->key() != key )
5981 qWarning("ASSERT: keys for format not identical: '%s '%s'", cachedFormat->key().latin1(), key.latin1() );
5982#ifdef DEBUG_COLLECTION
5983 qDebug( "format of font and col '%s' - worst case", cachedFormat->key().latin1() );
5984#endif
5985 return cachedFormat;
5986}
5987
5988void QTextFormatCollection::remove( QTextFormat *f )
5989{
5990 if ( lastFormat == f )
5991 lastFormat = 0;
5992 if ( cres == f )
5993 cres = 0;
5994 if ( cachedFormat == f )
5995 cachedFormat = 0;
5996 cKey.remove( f->key() );
5997}
5998
5999void QTextFormatCollection::debug()
6000{
6001#ifdef DEBUG_COLLECTION
6002 qDebug( "------------ QTextFormatCollection: debug --------------- BEGIN" );
6003 QDictIterator<QTextFormat> it( cKey );
6004 for ( ; it.current(); ++it ) {
6005 qDebug( "format '%s' (%p): refcount: %d", it.current()->key().latin1(),
6006 it.current(), it.current()->ref );
6007 }
6008 qDebug( "------------ QTextFormatCollection: debug --------------- END" );
6009#endif
6010}
6011
6012void QTextFormatCollection::updateStyles()
6013{
6014 QDictIterator<QTextFormat> it( cKey );
6015 QTextFormat *f;
6016 while ( ( f = it.current() ) ) {
6017 ++it;
6018 f->updateStyle();
6019 }
6020 updateKeys();
6021}
6022
6023void QTextFormatCollection::updateFontSizes( int base, bool usePixels )
6024{
6025 QDictIterator<QTextFormat> it( cKey );
6026 QTextFormat *f;
6027 while ( ( f = it.current() ) ) {
6028 ++it;
6029 f->stdSize = base;
6030 f->usePixelSizes = usePixels;
6031 if ( usePixels )
6032 f->fn.setPixelSize( f->stdSize );
6033 else
6034 f->fn.setPointSize( f->stdSize );
6035 styleSheet()->scaleFont( f->fn, f->logicalFontSize );
6036 f->update();
6037 }
6038 f = defFormat;
6039 f->stdSize = base;
6040 f->usePixelSizes = usePixels;
6041 if ( usePixels )
6042 f->fn.setPixelSize( f->stdSize );
6043 else
6044 f->fn.setPointSize( f->stdSize );
6045 styleSheet()->scaleFont( f->fn, f->logicalFontSize );
6046 f->update();
6047 updateKeys();
6048}
6049
6050void QTextFormatCollection::updateFontAttributes( const QFont &f, const QFont &old )
6051{
6052 QDictIterator<QTextFormat> it( cKey );
6053 QTextFormat *fm;
6054 while ( ( fm = it.current() ) ) {
6055 ++it;
6056 if ( fm->fn.family() == old.family() &&
6057 fm->fn.weight() == old.weight() &&
6058 fm->fn.italic() == old.italic() &&
6059 fm->fn.underline() == old.underline() ) {
6060 fm->fn.setFamily( f.family() );
6061 fm->fn.setWeight( f.weight() );
6062 fm->fn.setItalic( f.italic() );
6063 fm->fn.setUnderline( f.underline() );
6064 fm->update();
6065 }
6066 }
6067 fm = defFormat;
6068 if ( fm->fn.family() == old.family() &&
6069 fm->fn.weight() == old.weight() &&
6070 fm->fn.italic() == old.italic() &&
6071 fm->fn.underline() == old.underline() ) {
6072 fm->fn.setFamily( f.family() );
6073 fm->fn.setWeight( f.weight() );
6074 fm->fn.setItalic( f.italic() );
6075 fm->fn.setUnderline( f.underline() );
6076 fm->update();
6077 }
6078 updateKeys();
6079}
6080
6081
6082// the keys in cKey have changed, rebuild the hashtable
6083void QTextFormatCollection::updateKeys()
6084{
6085 if ( cKey.isEmpty() )
6086 return;
6087 cKey.setAutoDelete( FALSE );
6088 QTextFormat** formats = new QTextFormat*[ cKey.count() + 1];
6089 QTextFormat **f = formats;
6090 QDictIterator<QTextFormat> it( cKey );
6091 while ( ( *f = it.current() ) ) {
6092 ++it;
6093 ++f;
6094 }
6095 cKey.clear();
6096 for ( f = formats; *f; f++ )
6097 cKey.insert( (*f)->key(), *f );
6098 cKey.setAutoDelete( TRUE );
6099}
6100
6101
6102
6103// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6104
6105void QTextFormat::setBold( bool b )
6106{
6107 if ( b == fn.bold() )
6108 return;
6109 fn.setBold( b );
6110 update();
6111}
6112
6113void QTextFormat::setMisspelled( bool b )
6114{
6115 if ( b == (bool)missp )
6116 return;
6117 missp = b;
6118 update();
6119}
6120
6121void QTextFormat::setVAlign( VerticalAlignment a )
6122{
6123 if ( a == ha )
6124 return;
6125 ha = a;
6126 update();
6127}
6128
6129void QTextFormat::setItalic( bool b )
6130{
6131 if ( b == fn.italic() )
6132 return;
6133 fn.setItalic( b );
6134 update();
6135}
6136
6137void QTextFormat::setUnderline( bool b )
6138{
6139 if ( b == fn.underline() )
6140 return;
6141 fn.setUnderline( b );
6142 update();
6143}
6144
6145void QTextFormat::setFamily( const QString &f )
6146{
6147 if ( f == fn.family() )
6148 return;
6149 fn.setFamily( f );
6150 update();
6151}
6152
6153void QTextFormat::setPointSize( int s )
6154{
6155 if ( s == fn.pointSize() )
6156 return;
6157 fn.setPointSize( s );
6158 usePixelSizes = FALSE;
6159 update();
6160}
6161
6162void QTextFormat::setFont( const QFont &f )
6163{
6164 if ( f == fn && !k.isEmpty() )
6165 return;
6166 fn = f;
6167 update();
6168}
6169
6170void QTextFormat::setColor( const QColor &c )
6171{
6172 if ( c == col )
6173 return;
6174 col = c;
6175 update();
6176}
6177
6178static int makeLogicFontSize( int s )
6179{
6180 int defSize = QApplication::font().pointSize();
6181 if ( s < defSize - 4 )
6182 return 1;
6183 if ( s < defSize )
6184 return 2;
6185 if ( s < defSize + 4 )
6186 return 3;
6187 if ( s < defSize + 8 )
6188 return 4;
6189 if ( s < defSize + 12 )
6190 return 5;
6191 if (s < defSize + 16 )
6192 return 6;
6193 return 7;
6194}
6195
6196static QTextFormat *defaultFormat = 0;
6197
6198QString QTextFormat::makeFormatChangeTags( QTextFormat *f, const QString& oldAnchorHref, const QString& anchorHref ) const
6199{
6200 if ( !defaultFormat ) // #### wrong, use the document's default format instead
6201 defaultFormat = new QTextFormat( QApplication::font(),
6202 QApplication::palette().color( QPalette::Active, QColorGroup::Text ) );
6203
6204 QString tag;
6205 if ( f ) {
6206 if ( f->font() != defaultFormat->font() ) {
6207 if ( f->font().family() != defaultFormat->font().family()
6208 || f->font().pointSize() != defaultFormat->font().pointSize()
6209 || f->color().rgb() != defaultFormat->color().rgb() )
6210 tag += "</font>";
6211 if ( f->font().underline() && f->font().underline() != defaultFormat->font().underline() )
6212 tag += "</u>";
6213 if ( f->font().italic() && f->font().italic() != defaultFormat->font().italic() )
6214 tag += "</i>";
6215 if ( f->font().bold() && f->font().bold() != defaultFormat->font().bold() )
6216 tag += "</b>";
6217 }
6218 if ( !oldAnchorHref.isEmpty() )
6219 tag += "</a>";
6220 }
6221
6222 if ( !anchorHref.isEmpty() )
6223 tag += "<a href=\"" + anchorHref + "\">";
6224
6225 if ( font() != defaultFormat->font() ) {
6226 if ( font().bold() && font().bold() != defaultFormat->font().bold() )
6227 tag += "<b>";
6228 if ( font().italic() && font().italic() != defaultFormat->font().italic() )
6229 tag += "<i>";
6230 if ( font().underline() && font().underline() != defaultFormat->font().underline() )
6231 tag += "<u>";
6232 }
6233 if ( font() != defaultFormat->font()
6234 || color().rgb() != defaultFormat->color().rgb() ) {
6235 QString f;
6236 if ( font().family() != defaultFormat->font().family() )
6237 f +=" face=\"" + fn.family() + "\"";
6238 if ( font().pointSize() != defaultFormat->font().pointSize() ) {
6239 f +=" size=\"" + QString::number( makeLogicFontSize( fn.pointSize() ) ) + "\"";
6240 f +=" style=\"font-size:" + QString::number( fn.pointSize() ) + "pt\"";
6241 }
6242 if ( color().rgb() != defaultFormat->color().rgb() )
6243 f +=" color=\"" + col.name() + "\"";
6244 if ( !f.isEmpty() )
6245 tag += "<font" + f + ">";
6246 }
6247
6248 return tag;
6249}
6250
6251QString QTextFormat::makeFormatEndTags( const QString& anchorHref ) const
6252{
6253 if ( !defaultFormat )
6254 defaultFormat = new QTextFormat( QApplication::font(),
6255 QApplication::palette().color( QPalette::Active, QColorGroup::Text ) );
6256
6257 QString tag;
6258 if ( font() != defaultFormat->font() ) {
6259 if ( font().family() != defaultFormat->font().family()
6260 || font().pointSize() != defaultFormat->font().pointSize()
6261 || color().rgb() != defaultFormat->color().rgb() )
6262 tag += "</font>";
6263 if ( font().underline() && font().underline() != defaultFormat->font().underline() )
6264 tag += "</u>";
6265 if ( font().italic() && font().italic() != defaultFormat->font().italic() )
6266 tag += "</i>";
6267 if ( font().bold() && font().bold() != defaultFormat->font().bold() )
6268 tag += "</b>";
6269 }
6270 if ( !anchorHref.isEmpty() )
6271 tag += "</a>";
6272 return tag;
6273}
6274
6275QTextFormat QTextFormat::makeTextFormat( const QStyleSheetItem *style, const QMap<QString,QString>& attr ) const
6276{
6277 QTextFormat format(*this);
6278 if ( style ) {
6279 format.style = style->name();
6280 if ( style->name() == "font") {
6281 if ( attr.contains("color") ) {
6282 QString s = attr["color"];
6283 if ( !s.isEmpty() ) {
6284 format.col.setNamedColor( s );
6285 format.linkColor = FALSE;
6286 }
6287 }
6288 if ( attr.contains("size") ) {
6289 QString a = attr["size"];
6290 int n = a.toInt();
6291 if ( a[0] == '+' || a[0] == '-' )
6292 n += format.logicalFontSize;
6293 format.logicalFontSize = n;
6294 if ( format.usePixelSizes )
6295 format.fn.setPixelSize( format.stdSize );
6296 else
6297 format.fn.setPointSize( format.stdSize );
6298 style->styleSheet()->scaleFont( format.fn, format.logicalFontSize );
6299 }
6300 if ( attr.contains("style" ) ) {
6301 QString a = attr["style"];
6302 if ( a.startsWith( "font-size:" ) ) {
6303 QString s = a.mid( a.find( ':' ) + 1 );
6304 int n = s.left( s.length() - 2 ).toInt();
6305 format.logicalFontSize = 0;
6306 if ( format.usePixelSizes )
6307 format.fn.setPixelSize( n );
6308 else
6309 format.fn.setPointSize( n );
6310 }
6311 }
6312 if ( attr.contains("face") ) {
6313 QString a = attr["face"];
6314 if ( a.contains(',') )
6315 a = a.left( a.find(',') );
6316 format.fn.setFamily( a );
6317 }
6318 } else {
6319 if ( !style->isAnchor() && style->color().isValid() ) {
6320 // the style is not an anchor and defines a color.
6321 // It might be used inside an anchor and it should
6322 // override the link color.
6323 format.linkColor = FALSE;
6324 }
6325 switch ( style->verticalAlignment() ) {
6326 case QStyleSheetItem::VAlignBaseline:
6327 format.setVAlign( QTextFormat::AlignNormal );
6328 break;
6329 case QStyleSheetItem::VAlignSuper:
6330 format.setVAlign( QTextFormat::AlignSuperScript );
6331 break;
6332 case QStyleSheetItem::VAlignSub:
6333 format.setVAlign( QTextFormat::AlignSubScript );
6334 break;
6335 }
6336
6337 if ( style->fontWeight() != QStyleSheetItem::Undefined )
6338 format.fn.setWeight( style->fontWeight() );
6339 if ( style->fontSize() != QStyleSheetItem::Undefined ) {
6340 format.fn.setPointSize( style->fontSize() );
6341 } else if ( style->logicalFontSize() != QStyleSheetItem::Undefined ) {
6342 format.logicalFontSize = style->logicalFontSize();
6343 if ( format.usePixelSizes )
6344 format.fn.setPixelSize( format.stdSize );
6345 else
6346 format.fn.setPointSize( format.stdSize );
6347 style->styleSheet()->scaleFont( format.fn, format.logicalFontSize );
6348 } else if ( style->logicalFontSizeStep() ) {
6349 format.logicalFontSize += style->logicalFontSizeStep();
6350 if ( format.usePixelSizes )
6351 format.fn.setPixelSize( format.stdSize );
6352 else
6353 format.fn.setPointSize( format.stdSize );
6354 style->styleSheet()->scaleFont( format.fn, format.logicalFontSize );
6355 }
6356 if ( !style->fontFamily().isEmpty() )
6357 format.fn.setFamily( style->fontFamily() );
6358 if ( style->color().isValid() )
6359 format.col = style->color();
6360 if ( style->definesFontItalic() )
6361 format.fn.setItalic( style->fontItalic() );
6362 if ( style->definesFontUnderline() )
6363 format.fn.setUnderline( style->fontUnderline() );
6364 }
6365 }
6366
6367 format.update();
6368 return format;
6369}
6370
6371struct QPixmapInt
6372{
6373 QPixmapInt() : ref( 0 ) {}
6374 QPixmap pm;
6375 int ref;
6376};
6377
6378static QMap<QString, QPixmapInt> *pixmap_map = 0;
6379
6380QTextImage::QTextImage( QTextDocument *p, const QMap<QString, QString> &attr, const QString& context,
6381 QMimeSourceFactory &factory )
6382 : QTextCustomItem( p )
6383{
6384#if defined(PARSER_DEBUG)
6385 qDebug( debug_indent + "new QTextImage (pappi: %p)", p );
6386#endif
6387
6388 width = height = 0;
6389 if ( attr.contains("width") )
6390 width = attr["width"].toInt();
6391 if ( attr.contains("height") )
6392 height = attr["height"].toInt();
6393
6394 reg = 0;
6395 QString imageName = attr["src"];
6396
6397 if (!imageName)
6398 imageName = attr["source"];
6399
6400#if defined(PARSER_DEBUG)
6401 qDebug( debug_indent + " .." + imageName );
6402#endif
6403
6404 if ( !imageName.isEmpty() ) {
6405 imgId = QString( "%1,%2,%3,%4" ).arg( imageName ).arg( width ).arg( height ).arg( (ulong)&factory );
6406 if ( !pixmap_map )
6407 pixmap_map = new QMap<QString, QPixmapInt>;
6408 if ( pixmap_map->contains( imgId ) ) {
6409 QPixmapInt& pmi = pixmap_map->operator[](imgId);
6410 pm = pmi.pm;
6411 pmi.ref++;
6412 width = pm.width();
6413 height = pm.height();
6414 } else {
6415 QImage img;
6416 const QMimeSource* m =
6417 factory.data( imageName, context );
6418 if ( !m ) {
6419 qWarning("QTextImage: no mimesource for %s", imageName.latin1() );
6420 }
6421 else {
6422 if ( !QImageDrag::decode( m, img ) ) {
6423 qWarning("QTextImage: cannot decode %s", imageName.latin1() );
6424 }
6425 }
6426
6427 if ( !img.isNull() ) {
6428 if ( width == 0 ) {
6429 width = img.width();
6430 if ( height != 0 ) {
6431 width = img.width() * height / img.height();
6432 }
6433 }
6434 if ( height == 0 ) {
6435 height = img.height();
6436 if ( width != img.width() ) {
6437 height = img.height() * width / img.width();
6438 }
6439 }
6440 if ( img.width() != width || img.height() != height ){
6441#ifndef QT_NO_IMAGE_SMOOTHSCALE
6442 img = img.smoothScale(width, height);
6443#endif
6444 width = img.width();
6445 height = img.height();
6446 }
6447 pm.convertFromImage( img );
6448 }
6449 if ( !pm.isNull() ) {
6450 QPixmapInt& pmi = pixmap_map->operator[](imgId);
6451 pmi.pm = pm;
6452 pmi.ref++;
6453 }
6454 }
6455 if ( pm.mask() ) {
6456 QRegion mask( *pm.mask() );
6457 QRegion all( 0, 0, pm.width(), pm.height() );
6458 reg = new QRegion( all.subtract( mask ) );
6459 }
6460 }
6461
6462 if ( pm.isNull() && (width*height)==0 )
6463 width = height = 50;
6464
6465 place = PlaceInline;
6466 if ( attr["align"] == "left" )
6467 place = PlaceLeft;
6468 else if ( attr["align"] == "right" )
6469 place = PlaceRight;
6470
6471 tmpwidth = width;
6472 tmpheight = height;
6473
6474 attributes = attr;
6475}
6476
6477QTextImage::~QTextImage()
6478{
6479 if ( pixmap_map && pixmap_map->contains( imgId ) ) {
6480 QPixmapInt& pmi = pixmap_map->operator[](imgId);
6481 pmi.ref--;
6482 if ( !pmi.ref ) {
6483 pixmap_map->remove( imgId );
6484 if ( pixmap_map->isEmpty() ) {
6485 delete pixmap_map;
6486 pixmap_map = 0;
6487 }
6488 }
6489 }
6490}
6491
6492QString QTextImage::richText() const
6493{
6494 QString s;
6495 s += "<img ";
6496 QMap<QString, QString>::ConstIterator it = attributes.begin();
6497 for ( ; it != attributes.end(); ++it )
6498 s += it.key() + "=" + *it + " ";
6499 s += ">";
6500 return s;
6501}
6502
6503void QTextImage::adjustToPainter( QPainter* p )
6504{
6505 width = scale( tmpwidth, p );
6506 height = scale( tmpheight, p );
6507}
6508
6509#if !defined(Q_WS_X11)
6510#include <qbitmap.h>
6511#include "qcleanuphandler.h"
6512static QPixmap *qrt_selection = 0;
6513static QSingleCleanupHandler<QPixmap> qrt_cleanup_pixmap;
6514static void qrt_createSelectionPixmap( const QColorGroup &cg )
6515{
6516 qrt_selection = new QPixmap( 2, 2 );
6517 qrt_cleanup_pixmap.set( &qrt_selection );
6518 qrt_selection->fill( Qt::color0 );
6519 QBitmap m( 2, 2 );
6520 m.fill( Qt::color1 );
6521 QPainter p( &m );
6522 p.setPen( Qt::color0 );
6523 for ( int j = 0; j < 2; ++j ) {
6524 p.drawPoint( j % 2, j );
6525 }
6526 p.end();
6527 qrt_selection->setMask( m );
6528 qrt_selection->fill( cg.highlight() );
6529}
6530#endif
6531
6532void QTextImage::draw( QPainter* p, int x, int y, int cx, int cy, int cw, int ch, const QColorGroup& cg, bool selected )
6533{
6534 if ( placement() != PlaceInline ) {
6535 x = xpos;
6536 y = ypos;
6537 }
6538
6539 if ( pm.isNull() ) {
6540 p->fillRect( x , y, width, height, cg.dark() );
6541 return;
6542 }
6543
6544 if ( is_printer( p ) ) {
6545 p->drawPixmap( x, y, pm );
6546 return;
6547 }
6548
6549 if ( placement() != PlaceInline && !QRect( xpos, ypos, width, height ).intersects( QRect( cx, cy, cw, ch ) ) )
6550 return;
6551
6552 if ( placement() == PlaceInline )
6553 p->drawPixmap( x , y, pm );
6554 else
6555 p->drawPixmap( cx , cy, pm, cx - x, cy - y, cw, ch );
6556
6557 if ( selected && placement() == PlaceInline && is_printer( p ) ) {
6558#if defined(Q_WS_X11)
6559 p->fillRect( QRect( QPoint( x, y ), pm.size() ), QBrush( cg.highlight(), QBrush::Dense4Pattern) );
6560#else // in WIN32 Dense4Pattern doesn't work correctly (transparency problem), so work around it
6561 if ( !qrt_selection )
6562 qrt_createSelectionPixmap( cg );
6563 p->drawTiledPixmap( x, y, pm.width(), pm.height(), *qrt_selection );
6564#endif
6565 }
6566}
6567
6568void QTextHorizontalLine::adjustToPainter( QPainter* p )
6569{
6570 height = scale( tmpheight, p );
6571}
6572
6573
6574QTextHorizontalLine::QTextHorizontalLine( QTextDocument *p, const QMap<QString, QString> &attr,
6575 const QString &,
6576 QMimeSourceFactory & )
6577 : QTextCustomItem( p )
6578{
6579 height = tmpheight = 8;
6580 if ( attr.find( "color" ) != attr.end() )
6581 color = QColor( *attr.find( "color" ) );
6582}
6583
6584QTextHorizontalLine::~QTextHorizontalLine()
6585{
6586}
6587
6588QString QTextHorizontalLine::richText() const
6589{
6590 return "<hr>";
6591}
6592
6593void QTextHorizontalLine::draw( QPainter* p, int x, int y, int , int , int , int , const QColorGroup& cg, bool selected )
6594{
6595 QRect r( x, y, width, height);
6596 if ( is_printer( p ) ) {
6597 QPen oldPen = p->pen();
6598 if ( !color.isValid() )
6599 p->setPen( QPen( cg.text(), height/8 ) );
6600 else
6601 p->setPen( QPen( color, height/8 ) );
6602 p->drawLine( r.left()-1, y + height / 2, r.right() + 1, y + height / 2 );
6603 p->setPen( oldPen );
6604 } else {
6605 QColorGroup g( cg );
6606 if ( color.isValid() )
6607 g.setColor( QColorGroup::Dark, color );
6608 if ( selected )
6609 p->fillRect( r.left(), y, r.right(), y + height, g.highlight() );
6610 qDrawShadeLine( p, r.left() - 1, y + height / 2, r.right() + 1, y + height / 2, g, TRUE, height / 8 );
6611 }
6612}
6613
6614
6615/*****************************************************************/
6616// Small set of utility functions to make the parser a bit simpler
6617//
6618
6619bool QTextDocument::hasPrefix(const QChar* doc, int length, int pos, QChar c)
6620{
6621 if ( pos >= length )
6622 return FALSE;
6623 return doc[ pos ].lower() == c.lower();
6624}
6625
6626bool QTextDocument::hasPrefix( const QChar* doc, int length, int pos, const QString& s )
6627{
6628 if ( pos + (int) s.length() >= length )
6629 return FALSE;
6630 for ( int i = 0; i < (int)s.length(); i++ ) {
6631 if ( doc[ pos + i ].lower() != s[ i ].lower() )
6632 return FALSE;
6633 }
6634 return TRUE;
6635}
6636
6637static bool qt_is_cell_in_use( QPtrList<QTextTableCell>& cells, int row, int col )
6638{
6639 for ( QTextTableCell* c = cells.first(); c; c = cells.next() ) {
6640 if ( row >= c->row() && row < c->row() + c->rowspan()
6641 && col >= c->column() && col < c->column() + c->colspan() )
6642 return TRUE;
6643 }
6644 return FALSE;
6645}
6646
6647QTextCustomItem* QTextDocument::parseTable( const QMap<QString, QString> &attr, const QTextFormat &fmt,
6648 const QChar* doc, int length, int& pos, QTextParag *curpar )
6649{
6650
6651 QTextTable* table = new QTextTable( this, attr );
6652 int row = -1;
6653 int col = -1;
6654
6655 QString rowbgcolor;
6656 QString rowalign;
6657 QString tablebgcolor = attr["bgcolor"];
6658
6659 QPtrList<QTextTableCell> multicells;
6660
6661 QString tagname;
6662 (void) eatSpace(doc, length, pos);
6663 while ( pos < length) {
6664 if (hasPrefix(doc, length, pos, QChar('<')) ){
6665 if (hasPrefix(doc, length, pos+1, QChar('/'))) {
6666 tagname = parseCloseTag( doc, length, pos );
6667 if ( tagname == "table" ) {
6668#if defined(PARSER_DEBUG)
6669 debug_indent.remove( debug_indent.length() - 3, 2 );
6670#endif
6671 return table;
6672 }
6673 } else {
6674 QMap<QString, QString> attr2;
6675 bool emptyTag = FALSE;
6676 tagname = parseOpenTag( doc, length, pos, attr2, emptyTag );
6677 if ( tagname == "tr" ) {
6678 rowbgcolor = attr2["bgcolor"];
6679 rowalign = attr2["align"];
6680 row++;
6681 col = -1;
6682 }
6683 else if ( tagname == "td" || tagname == "th" ) {
6684 col++;
6685 while ( qt_is_cell_in_use( multicells, row, col ) ) {
6686 col++;
6687 }
6688
6689 if ( row >= 0 && col >= 0 ) {
6690 const QStyleSheetItem* s = sheet_->item(tagname);
6691 if ( !attr2.contains("bgcolor") ) {
6692 if (!rowbgcolor.isEmpty() )
6693 attr2["bgcolor"] = rowbgcolor;
6694 else if (!tablebgcolor.isEmpty() )
6695 attr2["bgcolor"] = tablebgcolor;
6696 }
6697 if ( !attr2.contains("align") ) {
6698 if (!rowalign.isEmpty() )
6699 attr2["align"] = rowalign;
6700 }
6701
6702 // extract the cell contents
6703 int end = pos;
6704 while ( end < length
6705 && !hasPrefix( doc, length, end, "</td")
6706 && !hasPrefix( doc, length, end, "<td")
6707 && !hasPrefix( doc, length, end, "</th")
6708 && !hasPrefix( doc, length, end, "<th")
6709 && !hasPrefix( doc, length, end, "<td")
6710 && !hasPrefix( doc, length, end, "</tr")
6711 && !hasPrefix( doc, length, end, "<tr")
6712 && !hasPrefix( doc, length, end, "</table") ) {
6713 if ( hasPrefix( doc, length, end, "<table" ) ) { // nested table
6714 int nested = 1;
6715 ++end;
6716 while ( end < length && nested != 0 ) {
6717 if ( hasPrefix( doc, length, end, "</table" ) )
6718 nested--;
6719 if ( hasPrefix( doc, length, end, "<table" ) )
6720 nested++;
6721 end++;
6722 }
6723 }
6724 end++;
6725 }
6726 QTextTableCell* cell = new QTextTableCell( table, row, col,
6727 attr2, s, fmt.makeTextFormat( s, attr2 ),
6728 contxt, *factory_, sheet_,
6729 QString( doc, length).mid( pos, end - pos ) );
6730 cell->richText()->parParag = curpar;
6731 if ( cell->colspan() > 1 || cell->rowspan() > 1 )
6732 multicells.append( cell );
6733 col += cell->colspan()-1;
6734 pos = end;
6735 }
6736 }
6737 }
6738
6739 } else {
6740 ++pos;
6741 }
6742 }
6743#if defined(PARSER_DEBUG)
6744 debug_indent.remove( debug_indent.length() - 3, 2 );
6745#endif
6746 return table;
6747}
6748
6749bool QTextDocument::eatSpace(const QChar* doc, int length, int& pos, bool includeNbsp )
6750{
6751 int old_pos = pos;
6752 while (pos < length && doc[pos].isSpace() && ( includeNbsp || (doc[pos] != QChar::nbsp ) ) )
6753 pos++;
6754 return old_pos < pos;
6755}
6756
6757bool QTextDocument::eat(const QChar* doc, int length, int& pos, QChar c)
6758{
6759 bool ok = pos < length && doc[pos] == c;
6760 if ( ok )
6761 pos++;
6762 return ok;
6763}
6764/*****************************************************************/
6765
6766struct Entity {
6767 const char * name;
6768 Q_UINT16 code;
6769};
6770
6771static const Entity entitylist [] = {
6772 { "AElig", 0x00c6 },
6773 { "Aacute", 0x00c1 },
6774 { "Acirc", 0x00c2 },
6775 { "Agrave", 0x00c0 },
6776 { "Alpha", 0x0391 },
6777 { "AMP", 38 },
6778 { "Aring", 0x00c5 },
6779 { "Atilde", 0x00c3 },
6780 { "Auml", 0x00c4 },
6781 { "Beta", 0x0392 },
6782 { "Ccedil", 0x00c7 },
6783 { "Chi", 0x03a7 },
6784 { "Dagger", 0x2021 },
6785 { "Delta", 0x0394 },
6786 { "ETH", 0x00d0 },
6787 { "Eacute", 0x00c9 },
6788 { "Ecirc", 0x00ca },
6789 { "Egrave", 0x00c8 },
6790 { "Epsilon", 0x0395 },
6791 { "Eta", 0x0397 },
6792 { "Euml", 0x00cb },
6793 { "Gamma", 0x0393 },
6794 { "GT", 62 },
6795 { "Iacute", 0x00cd },
6796 { "Icirc", 0x00ce },
6797 { "Igrave", 0x00cc },
6798 { "Iota", 0x0399 },
6799 { "Iuml", 0x00cf },
6800 { "Kappa", 0x039a },
6801 { "Lambda", 0x039b },
6802 { "LT", 60 },
6803 { "Mu", 0x039c },
6804 { "Ntilde", 0x00d1 },
6805 { "Nu", 0x039d },
6806 { "OElig", 0x0152 },
6807 { "Oacute", 0x00d3 },
6808 { "Ocirc", 0x00d4 },
6809 { "Ograve", 0x00d2 },
6810 { "Omega", 0x03a9 },
6811 { "Omicron", 0x039f },
6812 { "Oslash", 0x00d8 },
6813 { "Otilde", 0x00d5 },
6814 { "Ouml", 0x00d6 },
6815 { "Phi", 0x03a6 },
6816 { "Pi", 0x03a0 },
6817 { "Prime", 0x2033 },
6818 { "Psi", 0x03a8 },
6819 { "QUOT", 34 },
6820 { "Rho", 0x03a1 },
6821 { "Scaron", 0x0160 },
6822 { "Sigma", 0x03a3 },
6823 { "THORN", 0x00de },
6824 { "Tau", 0x03a4 },
6825 { "Theta", 0x0398 },
6826 { "Uacute", 0x00da },
6827 { "Ucirc", 0x00db },
6828 { "Ugrave", 0x00d9 },
6829 { "Upsilon", 0x03a5 },
6830 { "Uuml", 0x00dc },
6831 { "Xi", 0x039e },
6832 { "Yacute", 0x00dd },
6833 { "Yuml", 0x0178 },
6834 { "Zeta", 0x0396 },
6835 { "aacute", 0x00e1 },
6836 { "acirc", 0x00e2 },
6837 { "acute", 0x00b4 },
6838 { "aelig", 0x00e6 },
6839 { "agrave", 0x00e0 },
6840 { "alefsym", 0x2135 },
6841 { "alpha", 0x03b1 },
6842 { "amp", 38 },
6843 { "and", 0x22a5 },
6844 { "ang", 0x2220 },
6845 { "apos", 0x0027 },
6846 { "aring", 0x00e5 },
6847 { "asymp", 0x2248 },
6848 { "atilde", 0x00e3 },
6849 { "auml", 0x00e4 },
6850 { "bdquo", 0x201e },
6851 { "beta", 0x03b2 },
6852 { "brvbar", 0x00a6 },
6853 { "bull", 0x2022 },
6854 { "cap", 0x2229 },
6855 { "ccedil", 0x00e7 },
6856 { "cedil", 0x00b8 },
6857 { "cent", 0x00a2 },
6858 { "chi", 0x03c7 },
6859 { "circ", 0x02c6 },
6860 { "clubs", 0x2663 },
6861 { "cong", 0x2245 },
6862 { "copy", 0x00a9 },
6863 { "crarr", 0x21b5 },
6864 { "cup", 0x222a },
6865 { "curren", 0x00a4 },
6866 { "dArr", 0x21d3 },
6867 { "dagger", 0x2020 },
6868 { "darr", 0x2193 },
6869 { "deg", 0x00b0 },
6870 { "delta", 0x03b4 },
6871 { "diams", 0x2666 },
6872 { "divide", 0x00f7 },
6873 { "eacute", 0x00e9 },
6874 { "ecirc", 0x00ea },
6875 { "egrave", 0x00e8 },
6876 { "empty", 0x2205 },
6877 { "emsp", 0x2003 },
6878 { "ensp", 0x2002 },
6879 { "epsilon", 0x03b5 },
6880 { "equiv", 0x2261 },
6881 { "eta", 0x03b7 },
6882 { "eth", 0x00f0 },
6883 { "euml", 0x00eb },
6884 { "euro", 0x20ac },
6885 { "exist", 0x2203 },
6886 { "fnof", 0x0192 },
6887 { "forall", 0x2200 },
6888 { "frac12", 0x00bd },
6889 { "frac14", 0x00bc },
6890 { "frac34", 0x00be },
6891 { "frasl", 0x2044 },
6892 { "gamma", 0x03b3 },
6893 { "ge", 0x2265 },
6894 { "gt", 62 },
6895 { "hArr", 0x21d4 },
6896 { "harr", 0x2194 },
6897 { "hearts", 0x2665 },
6898 { "hellip", 0x2026 },
6899 { "iacute", 0x00ed },
6900 { "icirc", 0x00ee },
6901 { "iexcl", 0x00a1 },
6902 { "igrave", 0x00ec },
6903 { "image", 0x2111 },
6904 { "infin", 0x221e },
6905 { "int", 0x222b },
6906 { "iota", 0x03b9 },
6907 { "iquest", 0x00bf },
6908 { "isin", 0x2208 },
6909 { "iuml", 0x00ef },
6910 { "kappa", 0x03ba },
6911 { "lArr", 0x21d0 },
6912 { "lambda", 0x03bb },
6913 { "lang", 0x2329 },
6914 { "laquo", 0x00ab },
6915 { "larr", 0x2190 },
6916 { "lceil", 0x2308 },
6917 { "ldquo", 0x201c },
6918 { "le", 0x2264 },
6919 { "lfloor", 0x230a },
6920 { "lowast", 0x2217 },
6921 { "loz", 0x25ca },
6922 { "lrm", 0x200e },
6923 { "lsaquo", 0x2039 },
6924 { "lsquo", 0x2018 },
6925 { "lt", 60 },
6926 { "macr", 0x00af },
6927 { "mdash", 0x2014 },
6928 { "micro", 0x00b5 },
6929 { "middot", 0x00b7 },
6930 { "minus", 0x2212 },
6931 { "mu", 0x03bc },
6932 { "nabla", 0x2207 },
6933 { "nbsp", 0x00a0 },
6934 { "ndash", 0x2013 },
6935 { "ne", 0x2260 },
6936 { "ni", 0x220b },
6937 { "not", 0x00ac },
6938 { "notin", 0x2209 },
6939 { "nsub", 0x2284 },
6940 { "ntilde", 0x00f1 },
6941 { "nu", 0x03bd },
6942 { "oacute", 0x00f3 },
6943 { "ocirc", 0x00f4 },
6944 { "oelig", 0x0153 },
6945 { "ograve", 0x00f2 },
6946 { "oline", 0x203e },
6947 { "omega", 0x03c9 },
6948 { "omicron", 0x03bf },
6949 { "oplus", 0x2295 },
6950 { "or", 0x22a6 },
6951 { "ordf", 0x00aa },
6952 { "ordm", 0x00ba },
6953 { "oslash", 0x00f8 },
6954 { "otilde", 0x00f5 },
6955 { "otimes", 0x2297 },
6956 { "ouml", 0x00f6 },
6957 { "para", 0x00b6 },
6958 { "part", 0x2202 },
6959 { "percnt", 0x0025 },
6960 { "permil", 0x2030 },
6961 { "perp", 0x22a5 },
6962 { "phi", 0x03c6 },
6963 { "pi", 0x03c0 },
6964 { "piv", 0x03d6 },
6965 { "plusmn", 0x00b1 },
6966 { "pound", 0x00a3 },
6967 { "prime", 0x2032 },
6968 { "prod", 0x220f },
6969 { "prop", 0x221d },
6970 { "psi", 0x03c8 },
6971 { "quot", 34 },
6972 { "rArr", 0x21d2 },
6973 { "radic", 0x221a },
6974 { "rang", 0x232a },
6975 { "raquo", 0x00bb },
6976 { "rarr", 0x2192 },
6977 { "rceil", 0x2309 },
6978 { "rdquo", 0x201d },
6979 { "real", 0x211c },
6980 { "reg", 0x00ae },
6981 { "rfloor", 0x230b },
6982 { "rho", 0x03c1 },
6983 { "rlm", 0x200f },
6984 { "rsaquo", 0x203a },
6985 { "rsquo", 0x2019 },
6986 { "sbquo", 0x201a },
6987 { "scaron", 0x0161 },
6988 { "sdot", 0x22c5 },
6989 { "sect", 0x00a7 },
6990 { "shy", 0x00ad },
6991 { "sigma", 0x03c3 },
6992 { "sigmaf", 0x03c2 },
6993 { "sim", 0x223c },
6994 { "spades", 0x2660 },
6995 { "sub", 0x2282 },
6996 { "sube", 0x2286 },
6997 { "sum", 0x2211 },
6998 { "sup1", 0x00b9 },
6999 { "sup2", 0x00b2 },
7000 { "sup3", 0x00b3 },
7001 { "sup", 0x2283 },
7002 { "supe", 0x2287 },
7003 { "szlig", 0x00df },
7004 { "tau", 0x03c4 },
7005 { "there4", 0x2234 },
7006 { "theta", 0x03b8 },
7007 { "thetasym", 0x03d1 },
7008 { "thinsp", 0x2009 },
7009 { "thorn", 0x00fe },
7010 { "tilde", 0x02dc },
7011 { "times", 0x00d7 },
7012 { "trade", 0x2122 },
7013 { "uArr", 0x21d1 },
7014 { "uacute", 0x00fa },
7015 { "uarr", 0x2191 },
7016 { "ucirc", 0x00fb },
7017 { "ugrave", 0x00f9 },
7018 { "uml", 0x00a8 },
7019 { "upsih", 0x03d2 },
7020 { "upsilon", 0x03c5 },
7021 { "uuml", 0x00fc },
7022 { "weierp", 0x2118 },
7023 { "xi", 0x03be },
7024 { "yacute", 0x00fd },
7025 { "yen", 0x00a5 },
7026 { "yuml", 0x00ff },
7027 { "zeta", 0x03b6 },
7028 { "zwj", 0x200d },
7029 { "zwnj", 0x200c },
7030 { "", 0x0000 }
7031};
7032
7033
7034
7035
7036
7037static QMap<QCString, QChar> *html_map = 0;
7038static void qt_cleanup_html_map()
7039{
7040 delete html_map;
7041 html_map = 0;
7042}
7043
7044static QMap<QCString, QChar> *htmlMap()
7045{
7046 if ( !html_map ) {
7047 html_map = new QMap<QCString, QChar>;
7048 qAddPostRoutine( qt_cleanup_html_map );
7049
7050 const Entity *ent = entitylist;
7051 while( ent->code ) {
7052 html_map->insert( ent->name, QChar(ent->code) );
7053 ent++;
7054 }
7055 }
7056 return html_map;
7057}
7058
7059QChar QTextDocument::parseHTMLSpecialChar(const QChar* doc, int length, int& pos)
7060{
7061 QCString s;
7062 pos++;
7063 int recoverpos = pos;
7064 while ( pos < length && doc[pos] != ';' && !doc[pos].isSpace() && pos < recoverpos + 6) {
7065 s += doc[pos];
7066 pos++;
7067 }
7068 if (doc[pos] != ';' && !doc[pos].isSpace() ) {
7069 pos = recoverpos;
7070 return '&';
7071 }
7072 pos++;
7073
7074 if ( s.length() > 1 && s[0] == '#') {
7075 int num = s.mid(1).toInt();
7076 if ( num == 151 ) // ### hack for designer manual
7077 return '-';
7078 return num;
7079 }
7080
7081 QMap<QCString, QChar>::Iterator it = htmlMap()->find(s);
7082 if ( it != htmlMap()->end() ) {
7083 return *it;
7084 }
7085
7086 pos = recoverpos;
7087 return '&';
7088}
7089
7090QString QTextDocument::parseWord(const QChar* doc, int length, int& pos, bool lower)
7091{
7092 QString s;
7093
7094 if (doc[pos] == '"') {
7095 pos++;
7096 while ( pos < length && doc[pos] != '"' ) {
7097 s += doc[pos];
7098 pos++;
7099 }
7100 eat(doc, length, pos, '"');
7101 } else {
7102 static QString term = QString::fromLatin1("/>");
7103 while( pos < length &&
7104 (doc[pos] != '>' && !hasPrefix( doc, length, pos, term))
7105 && doc[pos] != '<'
7106 && doc[pos] != '='
7107 && !doc[pos].isSpace())
7108 {
7109 if ( doc[pos] == '&')
7110 s += parseHTMLSpecialChar( doc, length, pos );
7111 else {
7112 s += doc[pos];
7113 pos++;
7114 }
7115 }
7116 if (lower)
7117 s = s.lower();
7118 }
7119 return s;
7120}
7121
7122QChar QTextDocument::parseChar(const QChar* doc, int length, int& pos, QStyleSheetItem::WhiteSpaceMode wsm )
7123{
7124 if ( pos >= length )
7125 return QChar::null;
7126
7127 QChar c = doc[pos++];
7128
7129 if (c == '<' )
7130 return QChar::null;
7131
7132 if ( c.isSpace() && c != QChar::nbsp ) {
7133 if ( wsm == QStyleSheetItem::WhiteSpacePre ) {
7134 if ( c == ' ' )
7135 return QChar::nbsp;
7136 else
7137 return c;
7138 } else if ( wsm == QStyleSheetItem_WhiteSpaceNoCompression ) {
7139 return c;
7140 } else if ( wsm == QStyleSheetItem_WhiteSpaceNormalWithNewlines ) {
7141 if ( c == '\n' )
7142 return c;
7143 while ( pos< length &&
7144 doc[pos].isSpace() && doc[pos] != QChar::nbsp && doc[pos] != '\n' )
7145 pos++;
7146 return ' ';
7147 } else { // non-pre mode: collapse whitespace except nbsp
7148 while ( pos< length &&
7149 doc[pos].isSpace() && doc[pos] != QChar::nbsp )
7150 pos++;
7151 if ( wsm == QStyleSheetItem::WhiteSpaceNoWrap )
7152 return QChar::nbsp;
7153 else
7154 return ' ';
7155 }
7156 }
7157 else if ( c == '&' )
7158 return parseHTMLSpecialChar( doc, length, --pos );
7159 else
7160 return c;
7161}
7162
7163QString QTextDocument::parseOpenTag(const QChar* doc, int length, int& pos,
7164 QMap<QString, QString> &attr, bool& emptyTag)
7165{
7166 emptyTag = FALSE;
7167 pos++;
7168 if ( hasPrefix(doc, length, pos, '!') ) {
7169 if ( hasPrefix( doc, length, pos+1, "--")) {
7170 pos += 3;
7171 // eat comments
7172 QString pref = QString::fromLatin1("-->");
7173 while ( !hasPrefix(doc, length, pos, pref ) && pos < length )
7174 pos++;
7175 if ( hasPrefix(doc, length, pos, pref ) ) {
7176 pos += 3;
7177 eatSpace(doc, length, pos, TRUE);
7178 }
7179 emptyTag = TRUE;
7180 return QString::null;
7181 }
7182 else {
7183 // eat strange internal tags
7184 while ( !hasPrefix(doc, length, pos, '>') && pos < length )
7185 pos++;
7186 if ( hasPrefix(doc, length, pos, '>') ) {
7187 pos++;
7188 eatSpace(doc, length, pos, TRUE);
7189 }
7190 return QString::null;
7191 }
7192 }
7193
7194 QString tag = parseWord(doc, length, pos );
7195 eatSpace(doc, length, pos, TRUE);
7196 static QString term = QString::fromLatin1("/>");
7197 static QString s_TRUE = QString::fromLatin1("TRUE");
7198
7199 while (doc[pos] != '>' && ! (emptyTag = hasPrefix(doc, length, pos, term) )) {
7200 QString key = parseWord(doc, length, pos );
7201 eatSpace(doc, length, pos, TRUE);
7202 if ( key.isEmpty()) {
7203 // error recovery
7204 while ( pos < length && doc[pos] != '>' )
7205 pos++;
7206 break;
7207 }
7208 QString value;
7209 if (hasPrefix(doc, length, pos, '=') ){
7210 pos++;
7211 eatSpace(doc, length, pos);
7212 value = parseWord(doc, length, pos, FALSE);
7213 }
7214 else
7215 value = s_TRUE;
7216 attr.insert(key.lower(), value );
7217 eatSpace(doc, length, pos, TRUE);
7218 }
7219
7220 if (emptyTag) {
7221 eat(doc, length, pos, '/');
7222 eat(doc, length, pos, '>');
7223 }
7224 else
7225 eat(doc, length, pos, '>');
7226
7227 return tag;
7228}
7229
7230QString QTextDocument::parseCloseTag( const QChar* doc, int length, int& pos )
7231{
7232 pos++;
7233 pos++;
7234 QString tag = parseWord(doc, length, pos );
7235 eatSpace(doc, length, pos, TRUE);
7236 eat(doc, length, pos, '>');
7237 return tag;
7238}
7239
7240QTextFlow::QTextFlow()
7241{
7242 w = pagesize = 0;
7243 leftItems.setAutoDelete( FALSE );
7244 rightItems.setAutoDelete( FALSE );
7245}
7246
7247QTextFlow::~QTextFlow()
7248{
7249}
7250
7251void QTextFlow::clear()
7252{
7253 leftItems.clear();
7254 rightItems.clear();
7255}
7256
7257void QTextFlow::setWidth( int width )
7258{
7259 w = width;
7260}
7261
7262int QTextFlow::adjustLMargin( int yp, int, int margin, int space )
7263{
7264 for ( QTextCustomItem* item = leftItems.first(); item; item = leftItems.next() ) {
7265 if ( item->ypos == -1 )
7266 continue;
7267 if ( yp >= item->ypos && yp < item->ypos + item->height )
7268 margin = QMAX( margin, item->xpos + item->width + space );
7269 }
7270 return margin;
7271}
7272
7273int QTextFlow::adjustRMargin( int yp, int, int margin, int space )
7274{
7275 for ( QTextCustomItem* item = rightItems.first(); item; item = rightItems.next() ) {
7276 if ( item->ypos == -1 )
7277 continue;
7278 if ( yp >= item->ypos && yp < item->ypos + item->height )
7279 margin = QMAX( margin, w - item->xpos - space );
7280 }
7281 return margin;
7282}
7283
7284
7285int QTextFlow::adjustFlow( int y, int /*w*/, int h )
7286{
7287 if ( pagesize > 0 ) { // check pages
7288 int yinpage = y % pagesize;
7289 if ( yinpage <= border_tolerance )
7290 return border_tolerance - yinpage;
7291 else
7292 if ( yinpage + h > pagesize - border_tolerance )
7293 return ( pagesize - yinpage ) + border_tolerance;
7294 }
7295 return 0;
7296}
7297
7298void QTextFlow::unregisterFloatingItem( QTextCustomItem* item )
7299{
7300 leftItems.removeRef( item );
7301 rightItems.removeRef( item );
7302}
7303
7304void QTextFlow::registerFloatingItem( QTextCustomItem* item )
7305{
7306 if ( item->placement() == QTextCustomItem::PlaceRight ) {
7307 if ( !rightItems.contains( item ) )
7308 rightItems.append( item );
7309 } else if ( item->placement() == QTextCustomItem::PlaceLeft &&
7310 !leftItems.contains( item ) ) {
7311 leftItems.append( item );
7312 }
7313}
7314
7315QRect QTextFlow::boundingRect() const
7316{
7317 QRect br;
7318 QPtrListIterator<QTextCustomItem> l( leftItems );
7319 while( l.current() ) {
7320 br = br.unite( l.current()->geometry() );
7321 ++l;
7322 }
7323 QPtrListIterator<QTextCustomItem> r( rightItems );
7324 while( r.current() ) {
7325 br = br.unite( r.current()->geometry() );
7326 ++r;
7327 }
7328 return br;
7329}
7330
7331
7332void QTextFlow::drawFloatingItems( QPainter* p, int cx, int cy, int cw, int ch, const QColorGroup& cg, bool selected )
7333{
7334 QTextCustomItem *item;
7335 for ( item = leftItems.first(); item; item = leftItems.next() ) {
7336 if ( item->xpos == -1 || item->ypos == -1 )
7337 continue;
7338 item->draw( p, item->xpos, item->ypos, cx, cy, cw, ch, cg, selected );
7339 }
7340
7341 for ( item = rightItems.first(); item; item = rightItems.next() ) {
7342 if ( item->xpos == -1 || item->ypos == -1 )
7343 continue;
7344 item->draw( p, item->xpos, item->ypos, cx, cy, cw, ch, cg, selected );
7345 }
7346}
7347
7348// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7349
7350void QTextCustomItem::pageBreak( int /*y*/ , QTextFlow* /*flow*/ )
7351{
7352}
7353
7354QTextTable::QTextTable( QTextDocument *p, const QMap<QString, QString> & attr )
7355 : QTextCustomItem( p )
7356{
7357 cells.setAutoDelete( FALSE );
7358#if defined(PARSER_DEBUG)
7359 debug_indent += "\t";
7360 qDebug( debug_indent + "new QTextTable (%p)", this );
7361 debug_indent += "\t";
7362#endif
7363 cellspacing = 2;
7364 if ( attr.contains("cellspacing") )
7365 cellspacing = attr["cellspacing"].toInt();
7366 cellpadding = 1;
7367 if ( attr.contains("cellpadding") )
7368 cellpadding = attr["cellpadding"].toInt();
7369 border = innerborder = 0;
7370 if ( attr.contains("border" ) ) {
7371 QString s( attr["border"] );
7372 if ( s == "TRUE" )
7373 border = 1;
7374 else
7375 border = attr["border"].toInt();
7376 }
7377 us_b = border;
7378
7379 innerborder = us_ib = border ? 1 : 0;
7380
7381 if ( border )
7382 cellspacing += 2;
7383
7384 us_ib = innerborder;
7385 us_cs = cellspacing;
7386 us_cp = cellpadding;
7387 outerborder = cellspacing + border;
7388 us_ob = outerborder;
7389 layout = new QGridLayout( 1, 1, cellspacing );
7390
7391 fixwidth = 0;
7392 stretch = 0;
7393 if ( attr.contains("width") ) {
7394 bool b;
7395 QString s( attr["width"] );
7396 int w = s.toInt( &b );
7397 if ( b ) {
7398 fixwidth = w;
7399 } else {
7400 s = s.stripWhiteSpace();
7401 if ( s.length() > 1 && s[ (int)s.length()-1 ] == '%' )
7402 stretch = s.left( s.length()-1).toInt();
7403 }
7404 }
7405
7406 place = PlaceInline;
7407 if ( attr["align"] == "left" )
7408 place = PlaceLeft;
7409 else if ( attr["align"] == "right" )
7410 place = PlaceRight;
7411 cachewidth = 0;
7412 attributes = attr;
7413 pageBreakFor = -1;
7414}
7415
7416QTextTable::~QTextTable()
7417{
7418 delete layout;
7419}
7420
7421QString QTextTable::richText() const
7422{
7423 QString s;
7424 s = "<table ";
7425 QMap<QString, QString>::ConstIterator it = attributes.begin();
7426 for ( ; it != attributes.end(); ++it )
7427 s += it.key() + "=" + *it + " ";
7428 s += ">\n";
7429
7430 int lastRow = -1;
7431 bool needEnd = FALSE;
7432 QPtrListIterator<QTextTableCell> it2( cells );
7433 while ( it2.current() ) {
7434 QTextTableCell *cell = it2.current();
7435 ++it2;
7436 if ( lastRow != cell->row() ) {
7437 if ( lastRow != -1 )
7438 s += "</tr>\n";
7439 s += "<tr>";
7440 lastRow = cell->row();
7441 needEnd = TRUE;
7442 }
7443 s += "<td ";
7444 it = cell->attributes.begin();
7445 for ( ; it != cell->attributes.end(); ++it )
7446 s += it.key() + "=" + *it + " ";
7447 s += ">";
7448 s += cell->richText()->richText();
7449 s += "</td>";
7450 }
7451 if ( needEnd )
7452 s += "</tr>\n";
7453 s += "</table>\n";
7454 return s;
7455}
7456
7457void QTextTable::adjustToPainter( QPainter* p )
7458{
7459 cellspacing = scale( us_cs, p );
7460 cellpadding = scale( us_cp, p );
7461 border = scale( us_b , p );
7462 innerborder = scale( us_ib, p );
7463 outerborder = scale( us_ob ,p );
7464 width = 0;
7465 cachewidth = 0;
7466 for ( QTextTableCell* cell = cells.first(); cell; cell = cells.next() )
7467 cell->adjustToPainter( p );
7468}
7469
7470void QTextTable::adjustCells( int y , int shift )
7471{
7472 QPtrListIterator<QTextTableCell> it( cells );
7473 QTextTableCell* cell;
7474 bool enlarge = FALSE;
7475 while ( ( cell = it.current() ) ) {
7476 ++it;
7477 QRect r = cell->geometry();
7478 if ( y <= r.top() ) {
7479 r.moveBy(0, shift );
7480 cell->setGeometry( r );
7481 enlarge = TRUE;
7482 } else if ( y <= r.bottom() ) {
7483 r.rBottom() += shift;
7484 cell->setGeometry( r );
7485 enlarge = TRUE;
7486 }
7487 }
7488 if ( enlarge )
7489 height += shift;
7490}
7491
7492void QTextTable::pageBreak( int yt, QTextFlow* flow )
7493{
7494 if ( flow->pageSize() <= 0 )
7495 return;
7496 if ( layout && pageBreakFor > 0 && pageBreakFor != yt ) {
7497 layout->invalidate();
7498 int h = layout->heightForWidth( width-2*outerborder );
7499 layout->setGeometry( QRect(0, 0, width-2*outerborder, h) );
7500 height = layout->geometry().height()+2*outerborder;
7501 }
7502 pageBreakFor = yt;
7503 QPtrListIterator<QTextTableCell> it( cells );
7504 QTextTableCell* cell;
7505 while ( ( cell = it.current() ) ) {
7506 ++it;
7507 int y = yt + outerborder + cell->geometry().y();
7508 int shift = flow->adjustFlow( y - cellspacing, width, cell->richText()->height() + 2*cellspacing );
7509 adjustCells( y - outerborder - yt, shift );
7510 }
7511}
7512
7513
7514void QTextTable::draw(QPainter* p, int x, int y, int cx, int cy, int cw, int ch, const QColorGroup& cg, bool selected )
7515{
7516 if ( placement() != PlaceInline ) {
7517 x = xpos;
7518 y = ypos;
7519 }
7520
7521 for (QTextTableCell* cell = cells.first(); cell; cell = cells.next() ) {
7522 if ( cx < 0 && cy < 0 ||
7523 QRect( cx, cy, cw, ch ).intersects( QRect( x + outerborder + cell->geometry().x(),
7524 y + outerborder + cell->geometry().y(),
7525 cell->geometry().width(), cell->geometry().height() ) ) ) {
7526 cell->draw( p, x+outerborder, y+outerborder, cx, cy, cw, ch, cg, selected );
7527 if ( border ) {
7528 QRect r( x+outerborder+cell->geometry().x() - innerborder,
7529 y+outerborder+cell->geometry().y() - innerborder,
7530 cell->geometry().width() + 2 * innerborder,
7531 cell->geometry().height() + 2 * innerborder );
7532 if ( is_printer( p ) ) {
7533 QPen oldPen = p->pen();
7534 QRect r2 = r;
7535 r2.setLeft( r2.left() + innerborder/2 );
7536 r2.setTop( r2.top() + innerborder/2 );
7537 r2.setRight( r2.right() - innerborder/2 );
7538 r2.setBottom( r2.bottom() - innerborder/2 );
7539 p->setPen( QPen( cg.text(), innerborder ) );
7540 p->drawRect( r2 );
7541 p->setPen( oldPen );
7542 } else {
7543 int s = QMAX( cellspacing-2*innerborder, 0);
7544 if ( s ) {
7545 p->fillRect( r.left()-s, r.top(), s+1, r.height(), cg.button() );
7546 p->fillRect( r.right(), r.top(), s+1, r.height(), cg.button() );
7547 p->fillRect( r.left()-s, r.top()-s, r.width()+2*s, s, cg.button() );
7548 p->fillRect( r.left()-s, r.bottom(), r.width()+2*s, s, cg.button() );
7549 }
7550 qDrawShadePanel( p, r, cg, TRUE, innerborder );
7551 }
7552 }
7553 }
7554 }
7555 if ( border ) {
7556 QRect r ( x, y, width, height );
7557 if ( is_printer( p ) ) {
7558 QRect r2 = r;
7559 r2.setLeft( r2.left() + border/2 );
7560 r2.setTop( r2.top() + border/2 );
7561 r2.setRight( r2.right() - border/2 );
7562 r2.setBottom( r2.bottom() - border/2 );
7563 QPen oldPen = p->pen();
7564 p->setPen( QPen( cg.text(), border ) );
7565 p->drawRect( r2 );
7566 p->setPen( oldPen );
7567 } else {
7568 int s = border+QMAX( cellspacing-2*innerborder, 0);
7569 if ( s ) {
7570 p->fillRect( r.left(), r.top(), s, r.height(), cg.button() );
7571 p->fillRect( r.right()-s, r.top(), s, r.height(), cg.button() );
7572 p->fillRect( r.left(), r.top(), r.width(), s, cg.button() );
7573 p->fillRect( r.left(), r.bottom()-s, r.width(), s, cg.button() );
7574 }
7575 qDrawShadePanel( p, r, cg, FALSE, border );
7576 }
7577 }
7578
7579#if defined(DEBUG_TABLE_RENDERING)
7580 p->save();
7581 p->setPen( Qt::red );
7582 p->drawRect( x, y, width, height );
7583 p->restore();
7584#endif
7585}
7586
7587int QTextTable::minimumWidth() const
7588{
7589 return (layout ? layout->minimumSize().width() : 0) + 2 * outerborder;
7590}
7591
7592void QTextTable::resize( int nwidth )
7593{
7594 if ( fixwidth && cachewidth != 0 )
7595 return;
7596 if ( nwidth == cachewidth )
7597 return;
7598
7599
7600 cachewidth = nwidth;
7601 int w = nwidth;
7602
7603 format( w );
7604
7605 if ( stretch )
7606 nwidth = nwidth * stretch / 100;
7607
7608 width = nwidth;
7609 layout->invalidate();
7610 int shw = layout->sizeHint().width() + 2*outerborder;
7611 int mw = layout->minimumSize().width() + 2*outerborder;
7612 if ( stretch )
7613 width = QMAX( mw, nwidth );
7614 else
7615 width = QMAX( mw, QMIN( nwidth, shw ) );
7616
7617 if ( fixwidth )
7618 width = fixwidth;
7619
7620 layout->invalidate();
7621 mw = layout->minimumSize().width() + 2*outerborder;
7622 width = QMAX( width, mw );
7623
7624 int h = layout->heightForWidth( width-2*outerborder );
7625 layout->setGeometry( QRect(0, 0, width-2*outerborder, h) );
7626 height = layout->geometry().height()+2*outerborder;
7627}
7628
7629void QTextTable::format( int w )
7630{
7631 for ( int i = 0; i < (int)cells.count(); ++i ) {
7632 QTextTableCell *cell = cells.at( i );
7633 QRect r = cell->geometry();
7634 r.setWidth( w - 2*outerborder );
7635 cell->setGeometry( r );
7636 }
7637}
7638
7639void QTextTable::addCell( QTextTableCell* cell )
7640{
7641 cells.append( cell );
7642 layout->addMultiCell( cell, cell->row(), cell->row() + cell->rowspan()-1,
7643 cell->column(), cell->column() + cell->colspan()-1 );
7644}
7645
7646bool QTextTable::enter( QTextCursor *c, QTextDocument *&doc, QTextParag *&parag, int &idx, int &ox, int &oy, bool atEnd )
7647{
7648 currCell.remove( c );
7649 if ( !atEnd )
7650 return next( c, doc, parag, idx, ox, oy );
7651 currCell.insert( c, cells.count() );
7652 return prev( c, doc, parag, idx, ox, oy );
7653}
7654
7655bool QTextTable::enterAt( QTextCursor *c, QTextDocument *&doc, QTextParag *&parag, int &idx, int &ox, int &oy, const QPoint &pos )
7656{
7657 currCell.remove( c );
7658 int lastCell = -1;
7659 int lastY = -1;
7660 int i;
7661 for ( i = 0; i < (int)cells.count(); ++i ) {
7662 QTextTableCell *cell = cells.at( i );
7663 if ( !cell )
7664 continue;
7665 QRect r( cell->geometry().x(),
7666 cell->geometry().y(),
7667 cell->geometry().width() + 2 * innerborder + 2 * outerborder,
7668 cell->geometry().height() + 2 * innerborder + 2 * outerborder );
7669
7670 if ( r.left() <= pos.x() && r.right() >= pos.x() ) {
7671 if ( cell->geometry().y() > lastY ) {
7672 lastCell = i;
7673 lastY = cell->geometry().y();
7674 }
7675 if ( r.top() <= pos.y() && r.bottom() >= pos.y() ) {
7676 currCell.insert( c, i );
7677 break;
7678 }
7679 }
7680 }
7681 if ( i == (int) cells.count() )
7682 return FALSE; // no cell found
7683
7684 if ( currCell.find( c ) == currCell.end() ) {
7685 if ( lastY != -1 )
7686 currCell.insert( c, lastCell );
7687 else
7688 return FALSE;
7689 }
7690
7691 QTextTableCell *cell = cells.at( *currCell.find( c ) );
7692 if ( !cell )
7693 return FALSE;
7694 doc = cell->richText();
7695 parag = doc->firstParag();
7696 idx = 0;
7697 ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x();
7698 oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder;
7699 return TRUE;
7700}
7701
7702bool QTextTable::next( QTextCursor *c, QTextDocument *&doc, QTextParag *&parag, int &idx, int &ox, int &oy )
7703{
7704 int cc = -1;
7705 if ( currCell.find( c ) != currCell.end() )
7706 cc = *currCell.find( c );
7707 if ( cc > (int)cells.count() - 1 || cc < 0 )
7708 cc = -1;
7709 currCell.remove( c );
7710 currCell.insert( c, ++cc );
7711 if ( cc >= (int)cells.count() ) {
7712 currCell.insert( c, 0 );
7713 QTextCustomItem::next( c, doc, parag, idx, ox, oy );
7714 QTextTableCell *cell = cells.first();
7715 if ( !cell )
7716 return FALSE;
7717 doc = cell->richText();
7718 idx = -1;
7719 return TRUE;
7720 }
7721
7722 if ( currCell.find( c ) == currCell.end() )
7723 return FALSE;
7724 QTextTableCell *cell = cells.at( *currCell.find( c ) );
7725 if ( !cell )
7726 return FALSE;
7727 doc = cell->richText();
7728 parag = doc->firstParag();
7729 idx = 0;
7730 ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x();
7731 oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder;
7732 return TRUE;
7733}
7734
7735bool QTextTable::prev( QTextCursor *c, QTextDocument *&doc, QTextParag *&parag, int &idx, int &ox, int &oy )
7736{
7737 int cc = -1;
7738 if ( currCell.find( c ) != currCell.end() )
7739 cc = *currCell.find( c );
7740 if ( cc > (int)cells.count() - 1 || cc < 0 )
7741 cc = cells.count();
7742 currCell.remove( c );
7743 currCell.insert( c, --cc );
7744 if ( cc < 0 ) {
7745 currCell.insert( c, 0 );
7746 QTextCustomItem::prev( c, doc, parag, idx, ox, oy );
7747 QTextTableCell *cell = cells.first();
7748 if ( !cell )
7749 return FALSE;
7750 doc = cell->richText();
7751 idx = -1;
7752 return TRUE;
7753 }
7754
7755 if ( currCell.find( c ) == currCell.end() )
7756 return FALSE;
7757 QTextTableCell *cell = cells.at( *currCell.find( c ) );
7758 if ( !cell )
7759 return FALSE;
7760 doc = cell->richText();
7761 parag = doc->firstParag();
7762 idx = parag->length() - 1;
7763 ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x();
7764 oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder;
7765 return TRUE;
7766}
7767
7768bool QTextTable::down( QTextCursor *c, QTextDocument *&doc, QTextParag *&parag, int &idx, int &ox, int &oy )
7769{
7770 if ( currCell.find( c ) == currCell.end() )
7771 return FALSE;
7772 QTextTableCell *cell = cells.at( *currCell.find( c ) );
7773 if ( cell->row_ == layout->numRows() - 1 ) {
7774 currCell.insert( c, 0 );
7775 QTextCustomItem::down( c, doc, parag, idx, ox, oy );
7776 QTextTableCell *cell = cells.first();
7777 if ( !cell )
7778 return FALSE;
7779 doc = cell->richText();
7780 idx = -1;
7781 return TRUE;
7782 }
7783
7784 int oldRow = cell->row_;
7785 int oldCol = cell->col_;
7786 if ( currCell.find( c ) == currCell.end() )
7787 return FALSE;
7788 int cc = *currCell.find( c );
7789 for ( int i = cc; i < (int)cells.count(); ++i ) {
7790 cell = cells.at( i );
7791 if ( cell->row_ > oldRow && cell->col_ == oldCol ) {
7792 currCell.insert( c, i );
7793 break;
7794 }
7795 }
7796 doc = cell->richText();
7797 if ( !cell )
7798 return FALSE;
7799 parag = doc->firstParag();
7800 idx = 0;
7801 ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x();
7802 oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder;
7803 return TRUE;
7804}
7805
7806bool QTextTable::up( QTextCursor *c, QTextDocument *&doc, QTextParag *&parag, int &idx, int &ox, int &oy )
7807{
7808 if ( currCell.find( c ) == currCell.end() )
7809 return FALSE;
7810 QTextTableCell *cell = cells.at( *currCell.find( c ) );
7811 if ( cell->row_ == 0 ) {
7812 currCell.insert( c, 0 );
7813 QTextCustomItem::up( c, doc, parag, idx, ox, oy );
7814 QTextTableCell *cell = cells.first();
7815 if ( !cell )
7816 return FALSE;
7817 doc = cell->richText();
7818 idx = -1;
7819 return TRUE;
7820 }
7821
7822 int oldRow = cell->row_;
7823 int oldCol = cell->col_;
7824 if ( currCell.find( c ) == currCell.end() )
7825 return FALSE;
7826 int cc = *currCell.find( c );
7827 for ( int i = cc; i >= 0; --i ) {
7828 cell = cells.at( i );
7829 if ( cell->row_ < oldRow && cell->col_ == oldCol ) {
7830 currCell.insert( c, i );
7831 break;
7832 }
7833 }
7834 doc = cell->richText();
7835 if ( !cell )
7836 return FALSE;
7837 parag = doc->lastParag();
7838 idx = parag->length() - 1;
7839 ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x();
7840 oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder;
7841 return TRUE;
7842}
7843
7844QTextTableCell::QTextTableCell( QTextTable* table,
7845 int row, int column,
7846 const QMap<QString, QString> &attr,
7847 const QStyleSheetItem* /*style*/, // ### use them
7848 const QTextFormat& /*fmt*/, const QString& context,
7849 QMimeSourceFactory &factory, QStyleSheet *sheet,
7850 const QString& doc)
7851{
7852#if defined(PARSER_DEBUG)
7853 qDebug( debug_indent + "new QTextTableCell1 (pappi: %p)", table );
7854 qDebug( debug_indent + doc );
7855#endif
7856 cached_width = -1;
7857 cached_sizehint = -1;
7858
7859 maxw = QWIDGETSIZE_MAX;
7860 minw = 0;
7861
7862 parent = table;
7863 row_ = row;
7864 col_ = column;
7865 stretch_ = 0;
7866 richtext = new QTextDocument( table->parent );
7867 richtext->setTableCell( this );
7868 QString a = *attr.find( "align" );
7869 if ( !a.isEmpty() ) {
7870 a = a.lower();
7871 if ( a == "left" )
7872 richtext->setAlignment( Qt::AlignLeft );
7873 else if ( a == "center" )
7874 richtext->setAlignment( Qt::AlignHCenter );
7875 else if ( a == "right" )
7876 richtext->setAlignment( Qt::AlignRight );
7877 }
7878 align = 0;
7879 QString va = *attr.find( "valign" );
7880 if ( !va.isEmpty() ) {
7881 va = va.lower();
7882 if ( va == "center" )
7883 align |= Qt::AlignVCenter;
7884 else if ( va == "bottom" )
7885 align |= Qt::AlignBottom;
7886 }
7887 richtext->setFormatter( table->parent->formatter() );
7888 richtext->setUseFormatCollection( table->parent->useFormatCollection() );
7889 richtext->setMimeSourceFactory( &factory );
7890 richtext->setStyleSheet( sheet );
7891 richtext->setDefaultFont( table->parent->formatCollection()->defaultFormat()->font() );
7892 richtext->setRichText( doc, context );
7893 rowspan_ = 1;
7894 colspan_ = 1;
7895 if ( attr.contains("colspan") )
7896 colspan_ = attr["colspan"].toInt();
7897 if ( attr.contains("rowspan") )
7898 rowspan_ = attr["rowspan"].toInt();
7899
7900 background = 0;
7901 if ( attr.contains("bgcolor") ) {
7902 background = new QBrush(QColor( attr["bgcolor"] ));
7903 }
7904
7905
7906 hasFixedWidth = FALSE;
7907 if ( attr.contains("width") ) {
7908 bool b;
7909 QString s( attr["width"] );
7910 int w = s.toInt( &b );
7911 if ( b ) {
7912 maxw = w;
7913 minw = maxw;
7914 hasFixedWidth = TRUE;
7915 } else {
7916 s = s.stripWhiteSpace();
7917 if ( s.length() > 1 && s[ (int)s.length()-1 ] == '%' )
7918 stretch_ = s.left( s.length()-1).toInt();
7919 }
7920 }
7921
7922 attributes = attr;
7923
7924 parent->addCell( this );
7925}
7926
7927QTextTableCell::QTextTableCell( QTextTable* table, int row, int column )
7928{
7929#if defined(PARSER_DEBUG)
7930 qDebug( debug_indent + "new QTextTableCell2( pappi: %p", table );
7931#endif
7932 maxw = QWIDGETSIZE_MAX;
7933 minw = 0;
7934 cached_width = -1;
7935 cached_sizehint = -1;
7936
7937 parent = table;
7938 row_ = row;
7939 col_ = column;
7940 stretch_ = 0;
7941 richtext = new QTextDocument( table->parent );
7942 richtext->setTableCell( this );
7943 richtext->setFormatter( table->parent->formatter() );
7944 richtext->setUseFormatCollection( table->parent->useFormatCollection() );
7945 richtext->setDefaultFont( table->parent->formatCollection()->defaultFormat()->font() );
7946 richtext->setRichText( "<html></html>", QString::null );
7947 rowspan_ = 1;
7948 colspan_ = 1;
7949 background = 0;
7950 hasFixedWidth = FALSE;
7951 parent->addCell( this );
7952}
7953
7954
7955QTextTableCell::~QTextTableCell()
7956{
7957 delete background;
7958 background = 0;
7959 delete richtext;
7960 richtext = 0;
7961}
7962
7963QSize QTextTableCell::sizeHint() const
7964{
7965 int extra = 2 * ( parent->innerborder + parent->cellpadding + border_tolerance);
7966 int used = richtext->widthUsed() + extra;
7967
7968 if (stretch_ ) {
7969 int w = parent->width * stretch_ / 100 - 2*parent->cellspacing - 2*parent->cellpadding;
7970 return QSize( QMIN( w, maxw ), 0 ).expandedTo( minimumSize() );
7971 }
7972
7973 return QSize( used, 0 ).expandedTo( minimumSize() );
7974}
7975
7976QSize QTextTableCell::minimumSize() const
7977{
7978 int extra = 2 * ( parent->innerborder + parent->cellpadding + border_tolerance);
7979 return QSize( QMAX( richtext->minimumWidth() + extra, minw), 0 );
7980}
7981
7982QSize QTextTableCell::maximumSize() const
7983{
7984 return QSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX );
7985}
7986
7987QSizePolicy::ExpandData QTextTableCell::expanding() const
7988{
7989 return QSizePolicy::BothDirections;
7990}
7991
7992bool QTextTableCell::isEmpty() const
7993{
7994 return FALSE;
7995}
7996void QTextTableCell::setGeometry( const QRect& r )
7997{
7998 int extra = 2 * ( parent->innerborder + parent->cellpadding );
7999 if ( r.width() != cached_width )
8000 richtext->doLayout( QTextFormat::painter(), r.width() - extra );
8001 cached_width = r.width();
8002 geom = r;
8003}
8004
8005QRect QTextTableCell::geometry() const
8006{
8007 return geom;
8008}
8009
8010bool QTextTableCell::hasHeightForWidth() const
8011{
8012 return TRUE;
8013}
8014
8015int QTextTableCell::heightForWidth( int w ) const
8016{
8017 int extra = 2 * ( parent->innerborder + parent->cellpadding );
8018 w = QMAX( minw, w );
8019
8020 if ( cached_width != w ) {
8021 QTextTableCell* that = (QTextTableCell*) this;
8022 that->richtext->doLayout( QTextFormat::painter(), w - extra );
8023 that->cached_width = w;
8024 }
8025 return richtext->height() + extra;
8026}
8027
8028void QTextTableCell::adjustToPainter( QPainter* p )
8029{
8030 QTextParag *parag = richtext->firstParag();
8031 while ( parag ) {
8032 parag->adjustToPainter( p );
8033 parag = parag->next();
8034 }
8035}
8036
8037int QTextTableCell::horizontalAlignmentOffset() const
8038{
8039 return parent->cellpadding;
8040}
8041
8042int QTextTableCell::verticalAlignmentOffset() const
8043{
8044 if ( (align & Qt::AlignVCenter ) == Qt::AlignVCenter )
8045 return ( geom.height() - richtext->height() ) / 2;
8046 else if ( ( align & Qt::AlignBottom ) == Qt::AlignBottom )
8047 return geom.height() - parent->cellpadding - richtext->height() ;
8048 return parent->cellpadding;
8049}
8050
8051void QTextTableCell::draw( QPainter* p, int x, int y, int cx, int cy, int cw, int ch, const QColorGroup& cg, bool )
8052{
8053 if ( cached_width != geom.width() ) {
8054 int extra = 2 * ( parent->innerborder + parent->cellpadding );
8055 richtext->doLayout( p, geom.width() - extra );
8056 cached_width = geom.width();
8057 }
8058 QColorGroup g( cg );
8059 if ( background )
8060 g.setBrush( QColorGroup::Base, *background );
8061 else if ( richtext->paper() )
8062 g.setBrush( QColorGroup::Base, *richtext->paper() );
8063
8064 p->save();
8065 p->translate( x + geom.x(), y + geom.y() );
8066 if ( background )
8067 p->fillRect( 0, 0, geom.width(), geom.height(), *background );
8068 else if ( richtext->paper() )
8069 p->fillRect( 0, 0, geom.width(), geom.height(), *richtext->paper() );
8070
8071 p->translate( horizontalAlignmentOffset(), verticalAlignmentOffset() );
8072
8073 QRegion r;
8074 QTextCursor *c = 0;
8075 if ( richtext->parent()->tmpCursor )
8076 c = richtext->parent()->tmpCursor;
8077 if ( cx >= 0 && cy >= 0 )
8078 richtext->draw( p, cx - ( x + horizontalAlignmentOffset() + geom.x() ),
8079 cy - ( y + geom.y() + verticalAlignmentOffset() ),
8080 cw, ch, g, FALSE, (c != 0), c );
8081 else
8082 richtext->draw( p, -1, -1, -1, -1, g, FALSE, (c != 0), c );
8083
8084 p->restore();
8085}