lib

KoTextDocument.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2001-2005 David Faure <faure@kde.org>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18*/
19
20#include "KoTextDocument.h"
21#include "KoTextParag.h"
22#include "KoTextZoomHandler.h"
23#include "KoTextFormatter.h"
24#include "KoTextFormat.h"
25#include "KoParagCounter.h"
26#include "KoTextCommand.h"
27#include "KoOasisContext.h"
28#include "KoVariable.h"
29#include <KoXmlWriter.h>
30#include <KoXmlNS.h>
31#include <KoDom.h>
32#include <kdebug.h>
33#include <tdeversion.h>
34#include <tqapplication.h>
35#include <assert.h>
36
37//#define DEBUG_PAINTING
38
41
42KoTextDocument::KoTextDocument( KoTextZoomHandler *zoomHandler, KoTextFormatCollection *fc,
43 KoTextFormatter *formatter, bool createInitialParag )
44 : m_zoomHandler( zoomHandler ),
45 m_bDestroying( false ),
46#ifdef TQTEXTTABLE_AVAILABLE
47 par( 0L /*we don't use parent documents */ ),
48 tc( 0 ),
49#endif
50 tArray( 0 ), tStopWidth( 0 )
51{
52 fCollection = fc;
53 init(); // see korichtext.cpp
54
55 m_drawingFlags = 0;
56 if ( !formatter )
57 formatter = new KoTextFormatter;
58 setFormatter( formatter );
59
60 setY( 0 );
61 setLeftMargin( 0 );
62 setRightMargin( 0 );
63
64 // Delete the KoTextParag created by KoTextDocument::init() if createInitialParag is false.
65 if ( !createInitialParag )
66 clear( false );
67}
68
69void KoTextDocument::init()
70{
71 //pProcessor = 0;
72 useFC = true;
73 pFormatter = 0;
74 fParag = 0;
75 m_pageBreakEnabled = false;
76 //minw = 0;
77 align = TQt::AlignAuto;
78 nSelections = 2;
79
80 underlLinks = true;
81 backBrush = 0;
82 buf_pixmap = 0;
83 //nextDoubleBuffered = false;
84
85 //if ( par )
86// withoutDoubleBuffer = par->withoutDoubleBuffer;
87// else
88 withoutDoubleBuffer = false;
89
90 lParag = fParag = createParag( this, 0, 0 );
91
92 //cx = 0;
93 //cy = 2;
94 //if ( par )
95 cx = cy = 0;
96 //cw = 600; // huh?
97 //vw = 0;
98 flow_ = new KoTextFlow;
99 //flow_->setWidth( cw );
100
101 leftmargin = 0; // 4 in TQRT
102 rightmargin = 0; // 4 in TQRT
103
104 selectionColors[ Standard ] = TQApplication::palette().color( TQPalette::Active, TQColorGroup::Highlight );
105 selectionText[ Standard ] = true;
106 assert( Standard < nSelections );
107 selectionText[ InputMethodPreedit ] = false;
108 assert( InputMethodPreedit < nSelections );
109 commandHistory = new KoTextDocCommandHistory( 100 );
110 tStopWidth = formatCollection()->defaultFormat()->width( 'x' ) * 8;
111}
112
113KoTextDocument::~KoTextDocument()
114{
115 //if ( par )
116// par->removeChild( this );
118 m_bDestroying = true;
119 clear( false );
121 delete commandHistory;
122 delete flow_;
123 //if ( !par )
124 delete pFormatter;
125 delete fCollection;
126 //delete pProcessor;
127 delete buf_pixmap;
128 delete backBrush;
129 if ( tArray )
130 delete [] tArray;
131}
132
133void KoTextDocument::clear( bool createEmptyParag )
134{
135 if ( flow_ )
136 flow_->clear();
137 while ( fParag ) {
138 KoTextParag *p = fParag->next();
139 fParag->string()->clear(); // avoid the "unregister custom items" code, not needed
140 delete fParag;
141 fParag = p;
142 }
143 fParag = lParag = 0;
144 if ( createEmptyParag )
145 fParag = lParag = createParag( this );
146 selections.clear();
147 customItems.clear();
148}
149
150/*
151 // Looks slow!
152int KoTextDocument::widthUsed() const
153{
154 KoTextParag *p = fParag;
155 int w = 0;
156 while ( p ) {
157 int a = p->alignment();
158 p->setAlignment( TQt::AlignLeft );
159 p->invalidate( 0 );
160 p->format();
161 w = TQMAX( w, p->rect().width() );
162 p->setAlignment( a );
163 p->invalidate( 0 );
164 p = p->next();
165 }
166 return w;
167}
168*/
169
170int KoTextDocument::height() const
171{
172 int h = 0;
173 if ( lParag )
174 h = lParag->rect().top() + lParag->rect().height() + 1;
175 //int fh = flow_->boundingRect().height();
176 //return TQMAX( h, fh );
177 return h;
178}
179
180
181KoTextParag *KoTextDocument::createParag( KoTextDocument *d, KoTextParag *pr, KoTextParag *nx, bool updateIds )
182{
183 return new KoTextParag( d, pr, nx, updateIds );
184}
185
186void KoTextDocument::setPlainText( const TQString &text )
187{
188 clear();
189 //preferRichText = false;
190 //oTextValid = true;
191 //oText = text;
192
193 int lastNl = 0;
194 int nl = text.find( '\n' );
195 if ( nl == -1 ) {
196 lParag = createParag( this, lParag, 0 );
197 if ( !fParag )
198 fParag = lParag;
199 TQString s = text;
200 if ( !s.isEmpty() ) {
201 if ( s[ (int)s.length() - 1 ] == '\r' )
202 s.remove( s.length() - 1, 1 );
203 lParag->append( s );
204 }
205 } else {
206 for (;;) {
207 lParag = createParag( this, lParag, 0 );
208 if ( !fParag )
209 fParag = lParag;
210 TQString s = text.mid( lastNl, nl - lastNl );
211 if ( !s.isEmpty() ) {
212 if ( s[ (int)s.length() - 1 ] == '\r' )
213 s.remove( s.length() - 1, 1 );
214 lParag->append( s );
215 }
216 if ( nl == 0xffffff )
217 break;
218 lastNl = nl + 1;
219 nl = text.find( '\n', nl + 1 );
220 if ( nl == -1 )
221 nl = 0xffffff;
222 }
223 }
224 if ( !lParag )
225 lParag = fParag = createParag( this, 0, 0 );
226}
227
228void KoTextDocument::setText( const TQString &text, const TQString & /*context*/ )
229{
230 selections.clear();
231 setPlainText( text );
232}
233
234TQString KoTextDocument::plainText() const
235{
236 TQString buffer;
237 TQString s;
238 KoTextParag *p = fParag;
239 while ( p ) {
240 s = p->string()->toString();
241 s.remove( s.length() - 1, 1 );
242 if ( p->next() )
243 s += "\n";
244 buffer += s;
245 p = p->next();
246 }
247 return buffer;
248}
249
250void KoTextDocument::invalidate()
251{
252 KoTextParag *s = fParag;
253 while ( s ) {
254 s->invalidate( 0 );
255 s = s->next();
256 }
257}
258
259void KoTextDocument::informParagraphDeleted( KoTextParag* parag )
260{
261 TQMap<int, KoTextDocumentSelection>::Iterator it = selections.begin();
262 for ( ; it != selections.end(); ++it )
263 {
264 if ( (*it).startCursor.parag() == parag ) {
265 if ( parag->prev() ) {
266 KoTextParag* prevP = parag->prev();
267 (*it).startCursor.setParag( prevP );
268 (*it).startCursor.setIndex( prevP->length()-1 );
269 } else
270 (*it).startCursor.setParag( parag->next() ); // sets index to 0
271 }
272 if ( (*it).endCursor.parag() == parag ) {
273 if ( parag->prev() ) {
274 KoTextParag* prevP = parag->prev();
275 (*it).endCursor.setParag( prevP );
276 (*it).endCursor.setIndex( prevP->length()-1 );
277 } else
278 (*it).endCursor.setParag( parag->next() ); // sets index to 0
279 }
280 }
281 emit paragraphDeleted( parag );
282}
283
284void KoTextDocument::selectionStart( int id, int &paragId, int &index )
285{
286 TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
287 if ( it == selections.end() )
288 return;
289 KoTextDocumentSelection &sel = *it;
290 paragId = !sel.swapped ? sel.startCursor.parag()->paragId() : sel.endCursor.parag()->paragId();
291 index = !sel.swapped ? sel.startCursor.index() : sel.endCursor.index();
292}
293
294KoTextCursor KoTextDocument::selectionStartCursor( int id)
295{
296 TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
297 if ( it == selections.end() )
298 return KoTextCursor( this );
299 KoTextDocumentSelection &sel = *it;
300 if ( sel.swapped )
301 return sel.endCursor;
302 return sel.startCursor;
303}
304
305KoTextCursor KoTextDocument::selectionEndCursor( int id)
306{
307 TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
308 if ( it == selections.end() )
309 return KoTextCursor( this );
310 KoTextDocumentSelection &sel = *it;
311 if ( !sel.swapped )
312 return sel.endCursor;
313 return sel.startCursor;
314}
315
316void KoTextDocument::selectionEnd( int id, int &paragId, int &index )
317{
318 TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
319 if ( it == selections.end() )
320 return;
321 KoTextDocumentSelection &sel = *it;
322 paragId = sel.swapped ? sel.startCursor.parag()->paragId() : sel.endCursor.parag()->paragId();
323 index = sel.swapped ? sel.startCursor.index() : sel.endCursor.index();
324}
325
326bool KoTextDocument::isSelectionSwapped( int id )
327{
328 TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
329 if ( it == selections.end() )
330 return false;
331 KoTextDocumentSelection &sel = *it;
332 return sel.swapped;
333}
334
335KoTextParag *KoTextDocument::selectionStart( int id )
336{
337 TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
338 if ( it == selections.end() )
339 return 0;
340 KoTextDocumentSelection &sel = *it;
341 if ( sel.startCursor.parag()->paragId() < sel.endCursor.parag()->paragId() )
342 return sel.startCursor.parag();
343 return sel.endCursor.parag();
344}
345
346KoTextParag *KoTextDocument::selectionEnd( int id )
347{
348 TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
349 if ( it == selections.end() )
350 return 0;
351 KoTextDocumentSelection &sel = *it;
352 if ( sel.startCursor.parag()->paragId() > sel.endCursor.parag()->paragId() )
353 return sel.startCursor.parag();
354 return sel.endCursor.parag();
355}
356
357void KoTextDocument::addSelection( int id )
358{
359 nSelections = TQMAX( nSelections, id + 1 );
360}
361
362static void setSelectionEndHelper( int id, KoTextDocumentSelection &sel, KoTextCursor &start, KoTextCursor &end )
363{
364 KoTextCursor c1 = start;
365 KoTextCursor c2 = end;
366 if ( sel.swapped ) {
367 c1 = end;
368 c2 = start;
369 }
370
371 c1.parag()->removeSelection( id );
372 c2.parag()->removeSelection( id );
373 if ( c1.parag() != c2.parag() ) {
374 c1.parag()->setSelection( id, c1.index(), c1.parag()->length() - 1 );
375 c2.parag()->setSelection( id, 0, c2.index() );
376 } else {
377 c1.parag()->setSelection( id, TQMIN( c1.index(), c2.index() ), TQMAX( c1.index(), c2.index() ) );
378 }
379
380 sel.startCursor = start;
381 sel.endCursor = end;
382 if ( sel.startCursor.parag() == sel.endCursor.parag() )
383 sel.swapped = sel.startCursor.index() > sel.endCursor.index();
384}
385
386bool KoTextDocument::setSelectionEnd( int id, KoTextCursor *cursor )
387{
388 TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
389 if ( it == selections.end() )
390 return false;
391 KoTextDocumentSelection &sel = *it;
392
393 KoTextCursor start = sel.startCursor;
394 KoTextCursor end = *cursor;
395
396 if ( start == end ) {
397 removeSelection( id );
398 setSelectionStart( id, cursor );
399 return true;
400 }
401
402 if ( sel.endCursor.parag() == end.parag() ) {
403 setSelectionEndHelper( id, sel, start, end );
404 return true;
405 }
406
407 bool inSelection = false;
408 KoTextCursor c( this );
409 KoTextCursor tmp = sel.startCursor;
410 if ( sel.swapped )
411 tmp = sel.endCursor;
412 KoTextCursor tmp2 = *cursor;
413 c.setParag( tmp.parag()->paragId() < tmp2.parag()->paragId() ? tmp.parag() : tmp2.parag() );
414 KoTextCursor old;
415 bool hadStart = false;
416 bool hadEnd = false;
417 bool hadStartParag = false;
418 bool hadEndParag = false;
419 bool hadOldStart = false;
420 bool hadOldEnd = false;
421 bool leftSelection = false;
422 sel.swapped = false;
423 for ( ;; ) {
424 if ( c == start )
425 hadStart = true;
426 if ( c == end )
427 hadEnd = true;
428 if ( c.parag() == start.parag() )
429 hadStartParag = true;
430 if ( c.parag() == end.parag() )
431 hadEndParag = true;
432 if ( c == sel.startCursor )
433 hadOldStart = true;
434 if ( c == sel.endCursor )
435 hadOldEnd = true;
436
437 if ( !sel.swapped &&
438 ( hadEnd && !hadStart ||
439 hadEnd && hadStart && start.parag() == end.parag() && start.index() > end.index() ) )
440 sel.swapped = true;
441
442 if ( c == end && hadStartParag ||
443 c == start && hadEndParag ) {
444 KoTextCursor tmp = c;
445 if ( tmp.parag() != c.parag() ) {
446 int sstart = tmp.parag()->selectionStart( id );
447 tmp.parag()->removeSelection( id );
448 tmp.parag()->setSelection( id, sstart, tmp.index() );
449 }
450 }
451
452 if ( inSelection &&
453 ( c == end && hadStart || c == start && hadEnd ) )
454 leftSelection = true;
455 else if ( !leftSelection && !inSelection && ( hadStart || hadEnd ) )
456 inSelection = true;
457
458 bool noSelectionAnymore = hadOldStart && hadOldEnd && leftSelection && !inSelection && !c.parag()->hasSelection( id ) && c.atParagEnd();
459 c.parag()->removeSelection( id );
460 if ( inSelection ) {
461 if ( c.parag() == start.parag() && start.parag() == end.parag() ) {
462 c.parag()->setSelection( id, TQMIN( start.index(), end.index() ), TQMAX( start.index(), end.index() ) );
463 } else if ( c.parag() == start.parag() && !hadEndParag ) {
464 c.parag()->setSelection( id, start.index(), c.parag()->length() - 1 );
465 } else if ( c.parag() == end.parag() && !hadStartParag ) {
466 c.parag()->setSelection( id, end.index(), c.parag()->length() - 1 );
467 } else if ( c.parag() == end.parag() && hadEndParag ) {
468 c.parag()->setSelection( id, 0, end.index() );
469 } else if ( c.parag() == start.parag() && hadStartParag ) {
470 c.parag()->setSelection( id, 0, start.index() );
471 } else {
472 c.parag()->setSelection( id, 0, c.parag()->length() - 1 );
473 }
474 }
475
476 if ( leftSelection )
477 inSelection = false;
478
479 old = c;
480 c.gotoNextLetter();
481 if ( old == c || noSelectionAnymore )
482 break;
483 }
484
485 if ( !sel.swapped )
486 sel.startCursor.parag()->setSelection( id, sel.startCursor.index(), sel.startCursor.parag()->length() - 1 );
487
488 sel.startCursor = start;
489 sel.endCursor = end;
490 if ( sel.startCursor.parag() == sel.endCursor.parag() )
491 sel.swapped = sel.startCursor.index() > sel.endCursor.index();
492
493 setSelectionEndHelper( id, sel, start, end );
494
495 return true;
496}
497
498void KoTextDocument::selectAll( int id )
499{
500 removeSelection( id );
501
502 KoTextDocumentSelection sel;
503 sel.swapped = false;
504 KoTextCursor c( this );
505
506 c.setParag( fParag );
507 c.setIndex( 0 );
508 sel.startCursor = c;
509
510 c.setParag( lParag );
511 c.setIndex( lParag->length() - 1 );
512 sel.endCursor = c;
513
514 KoTextParag *p = fParag;
515 while ( p ) {
516 p->setSelection( id, 0, p->length() - 1 );
517#ifdef TQTEXTTABLE_AVAILABLE
518 for ( int i = 0; i < (int)p->length(); ++i ) {
519 if ( p->at( i )->isCustom() && p->at( i )->customItem()->isNested() ) {
520 KoTextTable *t = (KoTextTable*)p->at( i )->customItem();
521 TQPtrList<KoTextTableCell> tableCells = t->tableCells();
522 for ( KoTextTableCell *c = tableCells.first(); c; c = tableCells.next() )
523 c->richText()->selectAll( id );
524 }
525 }
526#endif
527 p = p->next();
528 }
529
530 selections.insert( id, sel );
531}
532
533bool KoTextDocument::removeSelection( int id )
534{
535 TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
536 if ( it == selections.end() )
537 return false;
538
539 KoTextDocumentSelection &sel = *it;
540
541 KoTextCursor c( this );
542 KoTextCursor tmp = sel.startCursor;
543 if ( sel.swapped )
544 tmp = sel.endCursor;
545 c.setParag( tmp.parag() );
546 KoTextCursor old;
547 bool hadStart = false;
548 bool hadEnd = false;
549 KoTextParag *lastParag = 0;
550 bool leftSelection = false;
551 bool inSelection = false;
552 sel.swapped = false;
553 for ( ;; ) {
554 if ( !hadStart && c.parag() == sel.startCursor.parag() )
555 hadStart = true;
556 if ( !hadEnd && c.parag() == sel.endCursor.parag() )
557 hadEnd = true;
558
559 if ( !leftSelection && !inSelection && ( c.parag() == sel.startCursor.parag() || c.parag() == sel.endCursor.parag() ) )
560 inSelection = true;
561
562 if ( inSelection &&
563 ( c == sel.endCursor && hadStart || c == sel.startCursor && hadEnd ) ) {
564 leftSelection = true;
565 inSelection = false;
566 }
567
568 bool noSelectionAnymore = leftSelection && !inSelection && !c.parag()->hasSelection( id ) && c.atParagEnd();
569
570 if ( lastParag != c.parag() )
571 c.parag()->removeSelection( id );
572
573 old = c;
574 lastParag = c.parag();
575 c.gotoNextLetter();
576 if ( old == c || noSelectionAnymore )
577 break;
578 }
579
580 selections.remove( id );
581 return true;
582}
583
584TQString KoTextDocument::selectedText( int id, bool withCustom ) const
585{
586 // ######## TODO: look at textFormat() and return rich text or plain text (like the text() method!)
587 TQMap<int, KoTextDocumentSelection>::ConstIterator it = selections.find( id );
588 if ( it == selections.end() )
589 return TQString();
590
591 KoTextDocumentSelection sel = *it;
592
593
594 KoTextCursor c1 = sel.startCursor;
595 KoTextCursor c2 = sel.endCursor;
596 if ( sel.swapped ) {
597 c2 = sel.startCursor;
598 c1 = sel.endCursor;
599 }
600
601 if ( c1.parag() == c2.parag() ) {
602 TQString s;
603 KoTextParag *p = c1.parag();
604 int end = c2.index();
605 if ( p->at( TQMAX( 0, end - 1 ) )->isCustom() )
606 ++end;
607 if ( !withCustom || !p->customItems() ) {
608 s += p->string()->toString().mid( c1.index(), end - c1.index() );
609 } else {
610 for ( int i = c1.index(); i < end; ++i ) {
611 if ( p->at( i )->isCustom() ) {
612#ifdef TQTEXTTABLE_AVAILABLE
613 if ( p->at( i )->customItem()->isNested() ) {
614 s += "\n";
615 KoTextTable *t = (KoTextTable*)p->at( i )->customItem();
616 TQPtrList<KoTextTableCell> cells = t->tableCells();
617 for ( KoTextTableCell *c = cells.first(); c; c = cells.next() )
618 s += c->richText()->plainText() + "\n";
619 s += "\n";
620 }
621#endif
622 } else {
623 s += p->at( i )->c;
624 }
625 s += "\n";
626 }
627 }
628 return s;
629 }
630
631 TQString s;
632 KoTextParag *p = c1.parag();
633 int start = c1.index();
634 while ( p ) {
635 int end = p == c2.parag() ? c2.index() : p->length() - 1;
636 if ( p == c2.parag() && p->at( TQMAX( 0, end - 1 ) )->isCustom() )
637 ++end;
638 if ( !withCustom || !p->customItems() ) {
639 s += p->string()->toString().mid( start, end - start );
640 if ( p != c2.parag() )
641 s += "\n";
642 } else {
643 for ( int i = start; i < end; ++i ) {
644 if ( p->at( i )->isCustom() ) {
645#ifdef TQTEXTTABLE_AVAILABLE
646 if ( p->at( i )->customItem()->isNested() ) {
647 s += "\n";
648 KoTextTable *t = (KoTextTable*)p->at( i )->customItem();
649 TQPtrList<KoTextTableCell> cells = t->tableCells();
650 for ( KoTextTableCell *c = cells.first(); c; c = cells.next() )
651 s += c->richText()->plainText() + "\n";
652 s += "\n";
653 }
654#endif
655 } else {
656 s += p->at( i )->c;
657 }
658 s += "\n";
659 }
660 }
661 start = 0;
662 if ( p == c2.parag() )
663 break;
664 p = p->next();
665 }
666 return s;
667}
668
669TQString KoTextDocument::copySelection( KoXmlWriter& writer, KoSavingContext& context, int selectionId )
670{
671 KoTextCursor c1 = selectionStartCursor( selectionId );
672 KoTextCursor c2 = selectionEndCursor( selectionId );
673 TQString text;
674 if ( c1.parag() == c2.parag() )
675 {
676 text = c1.parag()->toString( c1.index(), c2.index() - c1.index() );
677
678 c1.parag()->saveOasis( writer, context, c1.index(), c2.index()-1, true );
679 }
680 else
681 {
682 text += c1.parag()->toString( c1.index() ) + "\n";
683
684 c1.parag()->saveOasis( writer, context, c1.index(), c1.parag()->length()-2, true );
685 KoTextParag *p = c1.parag()->next();
686 while ( p && p != c2.parag() ) {
687 text += p->toString() + "\n";
688 p->saveOasis( writer, context, 0, p->length()-2, true );
689 p = p->next();
690 }
691 text += c2.parag()->toString( 0, c2.index() );
692 c2.parag()->saveOasis( writer, context, 0, c2.index() - 1, true );
693 }
694 return text;
695}
696
697void KoTextDocument::setFormat( int id, const KoTextFormat *f, int flags )
698{
699 TQMap<int, KoTextDocumentSelection>::ConstIterator it = selections.find( id );
700 if ( it == selections.end() )
701 return;
702
703 KoTextDocumentSelection sel = *it;
704
705 KoTextCursor c1 = sel.startCursor;
706 KoTextCursor c2 = sel.endCursor;
707 if ( sel.swapped ) {
708 c2 = sel.startCursor;
709 c1 = sel.endCursor;
710 }
711
712 if ( c1.parag() == c2.parag() ) {
713 c1.parag()->setFormat( c1.index(), c2.index() - c1.index(), f, true, flags );
714 return;
715 }
716
717 c1.parag()->setFormat( c1.index(), c1.parag()->length() - c1.index(), f, true, flags );
718 KoTextParag *p = c1.parag()->next();
719 while ( p && p != c2.parag() ) {
720 p->setFormat( 0, p->length(), f, true, flags );
721 p = p->next();
722 }
723 c2.parag()->setFormat( 0, c2.index(), f, true, flags );
724}
725
726/*void KoTextDocument::copySelectedText( int id )
727{
728#ifndef TQT_NO_CLIPBOARD
729 if ( !hasSelection( id ) )
730 return;
731
732 TQApplication::clipboard()->setText( selectedText( id ) );
733#endif
734}*/
735
736void KoTextDocument::removeSelectedText( int id, KoTextCursor *cursor )
737{
738 TQMap<int, KoTextDocumentSelection>::Iterator it = selections.find( id );
739 if ( it == selections.end() )
740 return;
741
742 KoTextDocumentSelection sel = *it;
743
744 KoTextCursor c1 = sel.startCursor;
745 KoTextCursor c2 = sel.endCursor;
746 if ( sel.swapped ) {
747 c2 = sel.startCursor;
748 c1 = sel.endCursor;
749 }
750
751 *cursor = c1;
752 removeSelection( id );
753
754 if ( c1.parag() == c2.parag() ) {
755 c1.parag()->remove( c1.index(), c2.index() - c1.index() );
756 return;
757 }
758
759 // ## TQt has a strange setValid/isValid on TQTextCursor, only used in the few lines below !?!?
760 bool valid = true;
761 if ( c1.parag() == fParag && c1.index() == 0 &&
762 c2.parag() == lParag && c2.index() == lParag->length() - 1 )
763 valid = false;
764
765 bool didGoLeft = false;
766 if ( c1.index() == 0 && c1.parag() != fParag ) {
767 cursor->gotoPreviousLetter();
768 if ( valid )
769 didGoLeft = true;
770 }
771
772 c1.parag()->remove( c1.index(), c1.parag()->length() - 1 - c1.index() );
773 KoTextParag *p = c1.parag()->next();
774 int dy = 0;
775 KoTextParag *tmp;
776 while ( p && p != c2.parag() ) {
777 tmp = p->next();
778 dy -= p->rect().height();
779 //emit paragraphDeleted( p ); // done by KoTextParag dtor already
780 delete p;
781 p = tmp;
782 }
783 c2.parag()->remove( 0, c2.index() );
784 while ( p ) {
785 p->move( dy );
787 if ( p->paragLayout().counter )
788 p->paragLayout().counter->invalidate();
790 p->invalidate( 0 );
791 //p->setEndState( -1 );
792 p = p->next();
793 }
794
795 c1.parag()->join( c2.parag() );
796
797 if ( didGoLeft )
798 cursor->gotoNextLetter();
799}
800
801void KoTextDocument::addCommand( KoTextDocCommand *cmd )
802{
803 commandHistory->addCommand( cmd );
804}
805
806KoTextCursor *KoTextDocument::undo( KoTextCursor *c )
807{
808 return commandHistory->undo( c );
809}
810
811KoTextCursor *KoTextDocument::redo( KoTextCursor *c )
812{
813 return commandHistory->redo( c );
814}
815
816bool KoTextDocument::find( const TQString &expr, bool cs, bool wo, bool forward,
817 int *parag, int *index, KoTextCursor *cursor )
818{
819 KoTextParag *p = forward ? fParag : lParag;
820 if ( parag )
821 p = paragAt( *parag );
822 else if ( cursor )
823 p = cursor->parag();
824 bool first = true;
825
826 while ( p ) {
827 TQString s = p->string()->toString();
828 s.remove( s.length() - 1, 1 ); // get rid of trailing space
829 int start = forward ? 0 : s.length() - 1;
830 if ( first && index )
831 start = *index;
832 else if ( first )
833 start = cursor->index();
834 if ( !forward && first ) {
835 start -= expr.length() + 1;
836 if ( start < 0 ) {
837 first = false;
838 p = p->prev();
839 continue;
840 }
841 }
842 first = false;
843
844 for ( ;; ) {
845 int res = forward ? s.find( expr, start, cs ) : s.findRev( expr, start, cs );
846 if ( res == -1 )
847 break;
848
849 bool ok = true;
850 if ( wo ) {
851 int end = res + expr.length();
852 if ( ( res == 0 || s[ res - 1 ].isSpace() || s[ res - 1 ].isPunct() ) &&
853 ( end == (int)s.length() || s[ end ].isSpace() || s[ end ].isPunct() ) )
854 ok = true;
855 else
856 ok = false;
857 }
858 if ( ok ) {
859 cursor->setParag( p );
860 cursor->setIndex( res );
861 setSelectionStart( Standard, cursor );
862 cursor->setIndex( res + expr.length() );
863 setSelectionEnd( Standard, cursor );
864 if ( parag )
865 *parag = p->paragId();
866 if ( index )
867 *index = res;
868 return true;
869 }
870 if ( forward ) {
871 start = res + 1;
872 } else {
873 if ( res == 0 )
874 break;
875 start = res - 1;
876 }
877 }
878 p = forward ? p->next() : p->prev();
879 }
880
881 return false;
882}
883
884bool KoTextDocument::inSelection( int selId, const TQPoint &pos ) const
885{
886 TQMap<int, KoTextDocumentSelection>::ConstIterator it = selections.find( selId );
887 if ( it == selections.end() )
888 return false;
889
890 KoTextDocumentSelection sel = *it;
891 KoTextParag *startParag = sel.startCursor.parag();
892 KoTextParag *endParag = sel.endCursor.parag();
893 if ( sel.startCursor.parag() == sel.endCursor.parag() &&
894 sel.startCursor.parag()->selectionStart( selId ) == sel.endCursor.parag()->selectionEnd( selId ) )
895 return false;
896 if ( sel.endCursor.parag()->paragId() < sel.startCursor.parag()->paragId() ) {
897 endParag = sel.startCursor.parag();
898 startParag = sel.endCursor.parag();
899 }
900
901 KoTextParag *p = startParag;
902 while ( p ) {
903 if ( p->rect().contains( pos ) ) {
904 bool inSel = false;
905 int selStart = p->selectionStart( selId );
906 int selEnd = p->selectionEnd( selId );
907 int y = 0;
908 int h = 0;
909 for ( int i = 0; i < p->length(); ++i ) {
910 if ( i == selStart )
911 inSel = true;
912 if ( i == selEnd )
913 break;
914 if ( p->at( i )->lineStart ) {
915 y = (*p->lineStarts.find( i ))->y;
916 h = (*p->lineStarts.find( i ))->h;
917 }
918 if ( pos.y() - p->rect().y() >= y && pos.y() - p->rect().y() <= y + h ) {
919 if ( inSel && pos.x() >= p->at( i )->x &&
920 pos.x() <= p->at( i )->x + p->at( i )->width /*p->at( i )->format()->width( p->at( i )->c )*/ )
921 return true;
922 }
923 }
924 }
925 if ( pos.y() < p->rect().y() )
926 break;
927 if ( p == endParag )
928 break;
929 p = p->next();
930 }
931
932 return false;
933}
934
935TQPixmap *KoTextDocument::bufferPixmap( const TQSize &s )
936{
937 if ( !buf_pixmap ) {
938 int w = TQABS( s.width() );
939 int h = TQABS( s.height() );
940 buf_pixmap = new TQPixmap( w, h );
941 } else {
942 if ( buf_pixmap->width() < s.width() ||
943 buf_pixmap->height() < s.height() ) {
944 buf_pixmap->resize( TQMAX( s.width(), buf_pixmap->width() ),
945 TQMAX( s.height(), buf_pixmap->height() ) );
946 }
947 }
948
949 return buf_pixmap;
950}
951
952void KoTextDocument::registerCustomItem( KoTextCustomItem *i, KoTextParag *p )
953{
954 if ( i && i->placement() != KoTextCustomItem::PlaceInline )
955 flow_->registerFloatingItem( i );
956 p->registerFloatingItem( i );
957 i->setParagraph( p );
958 //kdDebug(32500) << "KoTextDocument::registerCustomItem " << (void*)i << endl;
959 customItems.append( i );
960}
961
962void KoTextDocument::unregisterCustomItem( KoTextCustomItem *i, KoTextParag *p )
963{
964 flow_->unregisterFloatingItem( i );
965 p->unregisterFloatingItem( i );
966 i->setParagraph( 0 );
967 customItems.removeRef( i );
968}
969
970int KoTextDocument::length() const
971{
972 int l = 0;
973 KoTextParag *p = fParag;
974 while ( p ) {
975 l += p->length() - 1; // don't count trailing space
976 p = p->next();
977 }
978 return l;
979}
980
981bool KoTextDocument::visitSelection( int selectionId, KoParagVisitor* visitor, bool forward )
982{
983 KoTextCursor c1 = selectionStartCursor( selectionId );
984 KoTextCursor c2 = selectionEndCursor( selectionId );
985 if ( c1 == c2 )
986 return true;
987 return visitFromTo( c1.parag(), c1.index(), c2.parag(), c2.index(), visitor, forward );
988}
989
990bool KoTextDocument::hasSelection( int id, bool visible ) const
991{
992 return ( selections.find( id ) != selections.end() &&
993 ( !visible ||
994 ( (KoTextDocument*)this )->selectionStartCursor( id ) !=
995 ( (KoTextDocument*)this )->selectionEndCursor( id ) ) );
996}
997
998void KoTextDocument::setSelectionStart( int id, KoTextCursor *cursor )
999{
1000 KoTextDocumentSelection sel;
1001 sel.startCursor = *cursor;
1002 sel.endCursor = *cursor;
1003 sel.swapped = false;
1004 selections[ id ] = sel;
1005}
1006
1007KoTextParag *KoTextDocument::paragAt( int i ) const
1008{
1009 KoTextParag *s = fParag;
1010 while ( s ) {
1011 if ( s->paragId() == i )
1012 return s;
1013 s = s->next();
1014 }
1015 return 0;
1016}
1017
1018bool KoTextDocument::visitDocument( KoParagVisitor *visitor, bool forward )
1019{
1020 return visitFromTo( firstParag(), 0, lastParag(), lastParag()->length()-1, visitor, forward );
1021}
1022
1023bool KoTextDocument::visitFromTo( KoTextParag *firstParag, int firstIndex, KoTextParag* lastParag, int lastIndex, KoParagVisitor* visitor, bool forw )
1024{
1025 if ( firstParag == lastParag )
1026 {
1027 return visitor->visit( firstParag, firstIndex, lastIndex );
1028 }
1029 else
1030 {
1031 bool ret = true;
1032 if ( forw )
1033 {
1034 // the -1 is for the trailing space
1035 ret = visitor->visit( firstParag, firstIndex, firstParag->length() - 1 );
1036 if (!ret) return false;
1037 }
1038 else
1039 {
1040 ret = visitor->visit( lastParag, 0, lastIndex );
1041 if (!ret) return false;
1042 }
1043
1044 KoTextParag* currentParag = forw ? firstParag->next() : lastParag->prev();
1045 KoTextParag * endParag = forw ? lastParag : firstParag;
1046 while ( currentParag && currentParag != endParag )
1047 {
1048 ret = visitor->visit( currentParag, 0, currentParag->length() - 1 );
1049 if (!ret) return false;
1050 currentParag = forw ? currentParag->next() : currentParag->prev();
1051 }
1052 Q_ASSERT( currentParag );
1053 Q_ASSERT( endParag == currentParag );
1054 if ( forw )
1055 ret = visitor->visit( lastParag, 0, lastIndex );
1056 else
1057 ret = visitor->visit( currentParag, firstIndex, currentParag->length() - 1 );
1058 return ret;
1059 }
1060}
1061
1062static bool is_printer( TQPainter *p )
1063{
1064 return p && p->device() && p->device()->devType() == TQInternal::Printer;
1065}
1066
1067KoTextParag *KoTextDocument::drawWYSIWYG( TQPainter *p, int cx, int cy, int cw, int ch, const TQColorGroup &cg,
1068 KoTextZoomHandler* zoomHandler, bool onlyChanged,
1069 bool drawCursor, KoTextCursor *cursor,
1070 bool resetChanged, uint drawingFlags )
1071{
1072 m_drawingFlags = drawingFlags;
1073 // We need to draw without double-buffering if
1074 // 1) printing (to send text and not bitmaps to the printer)
1075 // 2) drawing a transparent embedded document
1076 //
1077 if ( is_printer( p ) || ( drawingFlags & TransparentBackground ) ) {
1078 // This stuff relies on doLayout()... simpler to just test for Printer.
1079 // If someone understand doLayout() please tell me (David)
1080 /*if ( isWithoutDoubleBuffer() || par && par->withoutDoubleBuffer ) { */
1081 //setWithoutDoubleBuffer( true );
1082 TQRect crect( cx, cy, cw, ch );
1083 drawWithoutDoubleBuffer( p, crect, cg, zoomHandler );
1084 return 0;
1085 }
1086 //setWithoutDoubleBuffer( false );
1087
1088 if ( !firstParag() )
1089 return 0;
1090
1091 KoTextParag *lastFormatted = 0;
1092 KoTextParag *parag = firstParag();
1093
1094 TQPixmap *doubleBuffer = 0;
1095 TQPainter painter;
1096 // All the coordinates in this method are in view pixels
1097 TQRect crect( cx, cy, cw, ch );
1098 Q_ASSERT( ch > 0 );
1099#ifdef DEBUG_PAINTING
1100 kdDebug(32500) << "\nKoTextDocument::drawWYSIWYG crect=" << crect << endl;
1101#endif
1102
1103 // Space above first parag
1104 TQRect pixelRect = parag->pixelRect( zoomHandler );
1105 if ( isPageBreakEnabled() && parag && cy <= pixelRect.y() && pixelRect.y() > 0 ) {
1106 TQRect r( 0, 0,
1107 zoomHandler->layoutUnitToPixelX( parag->document()->x() + parag->document()->width() ),
1108 pixelRect.y() );
1109 r &= crect;
1110 if ( !r.isEmpty() ) {
1111#ifdef DEBUG_PAINTING
1112 kdDebug(32500) << " drawWYSIWYG: space above first parag: " << r << " (pixels)" << endl;
1113#endif
1114 p->fillRect( r, cg.brush( TQColorGroup::Base ) );
1115 }
1116 }
1117
1118 while ( parag ) {
1119 lastFormatted = parag;
1120 if ( !parag->isValid() )
1121 parag->format();
1122
1123 TQRect ir = parag->pixelRect( zoomHandler );
1124#ifdef DEBUG_PAINTING
1125 kdDebug(32500) << " drawWYSIWYG: ir=" << ir << endl;
1126#endif
1127 if ( isPageBreakEnabled() && parag->next() && ( drawingFlags & TransparentBackground ) == 0 )
1128 {
1129 int nexty = parag->next()->pixelRect(zoomHandler).y();
1130 // Test ir.y+ir.height, which is the first pixel _under_ the parag
1131 // (as opposed ir.bottom() which is the last pixel of the parag)
1132 if ( ir.y() + ir.height() < nexty ) {
1133 TQRect r( 0, ir.y() + ir.height(),
1134 zoomHandler->layoutUnitToPixelX( parag->document()->x() + parag->document()->width() ),
1135 nexty - ( ir.y() + ir.height() ) );
1136 r &= crect;
1137 if ( !r.isEmpty() )
1138 {
1139#ifdef DEBUG_PAINTING
1140 kdDebug(32500) << " drawWYSIWYG: space between parag " << parag->paragId() << " and " << parag->next()->paragId() << " : " << r << " (pixels)" << endl;
1141#endif
1142 p->fillRect( r, cg.brush( TQColorGroup::Base ) );
1143 }
1144 }
1145 }
1146
1147 if ( !ir.intersects( crect ) ) {
1148 // Paragraph is not in the crect - but let's check if the area on its right is.
1149 ir.setWidth( zoomHandler->layoutUnitToPixelX( parag->document()->width() ) );
1150 if ( ir.intersects( crect ) && ( drawingFlags & TransparentBackground ) == 0 )
1151 p->fillRect( ir.intersect( crect ), cg.brush( TQColorGroup::Base ) );
1152 if ( ir.y() > cy + ch ) {
1153 goto floating;
1154 }
1155 }
1156 else if ( parag->hasChanged() || !onlyChanged ) {
1157 // lineChanged() only makes sense if we're drawing with onlyChanged=true
1158 // otherwise, call setChanged() to make sure we'll paint it all (lineChanged=-1).
1159 // (this avoids having to send onlyChanged to drawParagWYSIWYG)
1160 if ( !onlyChanged && parag->lineChanged() > 0 )
1161 parag->setChanged( false );
1162 drawParagWYSIWYG( p, parag, cx, cy, cw, ch, doubleBuffer, cg,
1163 zoomHandler, drawCursor, cursor, resetChanged, drawingFlags );
1164 }
1165 parag = parag->next();
1166 }
1167
1168 parag = lastParag();
1169
1170floating:
1171 pixelRect = parag->pixelRect(zoomHandler);
1172 int docheight = zoomHandler->layoutUnitToPixelY( parag->document()->height() );
1173 if ( pixelRect.y() + pixelRect.height() < docheight ) {
1174 int docwidth = zoomHandler->layoutUnitToPixelX( parag->document()->width() );
1175 if ( ( drawingFlags & TransparentBackground ) == 0 ) {
1176 p->fillRect( 0, pixelRect.y() + pixelRect.height(),
1177 docwidth, docheight - ( pixelRect.y() + pixelRect.height() ),
1178 cg.brush( TQColorGroup::Base ) );
1179 }
1180 if ( !flow()->isEmpty() ) {
1181 TQRect cr( cx, cy, cw, ch );
1182 cr = cr.intersect( TQRect( 0, pixelRect.y() + pixelRect.height(), docwidth,
1183 docheight - ( pixelRect.y() + pixelRect.height() ) ) );
1184 flow()->drawFloatingItems( p, cr.x(), cr.y(), cr.width(), cr.height(), cg, false );
1185 }
1186 }
1187
1188 if ( buf_pixmap && buf_pixmap->height() > 300 ) {
1189 delete buf_pixmap;
1190 buf_pixmap = 0;
1191 }
1192
1193 return lastFormatted;
1194}
1195
1196
1197// Used for printing
1198void KoTextDocument::drawWithoutDoubleBuffer( TQPainter *p, const TQRect &cr, const TQColorGroup &cg,
1199 KoTextZoomHandler* zoomHandler, const TQBrush *paper )
1200{
1201 if ( !firstParag() )
1202 return;
1203
1204 Q_ASSERT( (m_drawingFlags & DrawSelections) == 0 );
1205 if (m_drawingFlags & DrawSelections)
1206 kdDebug() << kdBacktrace();
1207 if ( paper && ( m_drawingFlags & TransparentBackground ) == 0 ) {
1208 p->setBrushOrigin( -(int)p->translationX(), -(int)p->translationY() );
1209 p->fillRect( cr, *paper );
1210 }
1211
1212 KoTextParag *parag = firstParag();
1213 while ( parag ) {
1214 if ( !parag->isValid() )
1215 parag->format();
1216
1217 TQRect pr( parag->pixelRect( zoomHandler ) );
1218 pr.setLeft( 0 );
1219 pr.setWidth( TQWIDGETSIZE_MAX );
1220 // The cliprect is checked in layout units, in KoTextParag::paint
1221 TQRect crect_lu( parag->rect() );
1222
1223 if ( !cr.isNull() && !cr.intersects( pr ) ) {
1224 parag = parag->next();
1225 continue;
1226 }
1227
1228 p->translate( 0, pr.y() );
1229
1230 // No need to brush plain white on a printer. Brush all
1231 // other cases (except "full transparent" case).
1232 TQBrush brush = cg.brush( TQColorGroup::Base );;
1233 bool needBrush = brush.style() != TQt::NoBrush &&
1234 !(brush.style() == TQt::SolidPattern &&
1235 brush.color() == TQt::white &&
1236 is_printer(p));
1237 if ( needBrush && ( m_drawingFlags & TransparentBackground ) == 0 )
1238 p->fillRect( TQRect( 0, 0, pr.width(), pr.height() ), brush );
1239
1240 //p->setBrushOrigin( p->brushOrigin() + TQPoint( 0, pr.y() ) );
1241 parag->paint( *p, cg, 0, false,
1242 crect_lu.x(), crect_lu.y(),
1243 crect_lu.width(), crect_lu.height() );
1244 p->translate( 0, -pr.y() );
1245 //p->setBrushOrigin( p->brushOrigin() - TQPoint( 0, pr.y() ) );
1246 parag = parag->next();
1247 }
1248}
1249
1250// Used for screen display (and also printing?)
1251// Called by drawWYSIWYG and the app's drawCursor
1252void KoTextDocument::drawParagWYSIWYG( TQPainter *p, KoTextParag *parag, int cx, int cy, int cw, int ch,
1253 TQPixmap *&doubleBuffer, const TQColorGroup &cg,
1254 KoTextZoomHandler* zoomHandler, bool drawCursor,
1255 KoTextCursor *cursor, bool resetChanged, uint drawingFlags )
1256{
1257 if ( cw <= 0 || ch <= 0 ) { Q_ASSERT( cw > 0 ); Q_ASSERT( ch > 0 ); return; }
1258#ifdef DEBUG_PAINTING
1259 kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG " << (void*)parag << " id:" << parag->paragId() << endl;
1260#endif
1261 m_drawingFlags = drawingFlags;
1262 TQPainter *painter = 0;
1263 // Those three rects are in pixels, in the document coordinates (0,0 == topleft of first parag)
1264 TQRect rect = parag->pixelRect( zoomHandler ); // the parag rect
1265
1266 int offsetY = 0;
1267 // Start painting from a given line number.
1268 if ( parag->lineChanged() > -1 )
1269 {
1270 offsetY = zoomHandler->layoutUnitToPixelY( parag->lineY( parag->lineChanged() ) - parag->topMargin() );
1271#ifdef DEBUG_PAINTING
1272 kdDebug(32500) << " Repainting from lineChanged=" << parag->lineChanged() << " -> adding " << offsetY << " to rect" << endl;
1273#endif
1274 // Skip the lines that are not repainted by moving Top. The bottom doesn't change.
1275 rect.rTop() += offsetY;
1276 }
1277
1278 TQRect crect( cx, cy, cw, ch ); // the overall crect
1279 TQRect ir( rect ); // will be the rect to be repainted
1280
1281 TQBrush brush = cg.brush( TQColorGroup::Base );
1282
1283 // No need to brush plain white on a printer. Brush all
1284 // other cases (except "full transparent" case).
1285 bool needBrush = brush.style() != TQt::NoBrush &&
1286 ( drawingFlags & TransparentBackground ) == 0 &&
1287 !(brush.style() == TQt::SolidPattern &&
1288 brush.color() == TQt::white &&
1289 is_printer(p));
1290
1291 bool useDoubleBuffer = !parag->document()->parent();
1292 if ( is_printer(p) )
1293 useDoubleBuffer = false;
1294 // Can't handle transparency using double-buffering, in case of rotation/scaling (due to bitBlt)
1295 // The test on mat is almost like isIdentity(), but allows for translation.
1297 // of being white.
1298 TQWMatrix mat = p->worldMatrix();
1299 if ( ( mat.m11() != 1.0 || mat.m22() != 1.0 || mat.m12() != 0.0 || mat.m21() != 0.0 )
1300 && brush.style() != TQt::SolidPattern )
1301 useDoubleBuffer = false;
1302
1303#ifdef DEBUG_PAINTING
1304 kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG parag->rect=" << parag->rect()
1305 << " pixelRect(ir)=" << ir
1306 << " crect (pixels)=" << crect
1307 << " useDoubleBuffer=" << useDoubleBuffer << endl;
1308#endif
1309
1310 if ( useDoubleBuffer ) {
1311 painter = new TQPainter;
1312 if ( cx >= 0 && cy >= 0 )
1313 ir = ir.intersect( crect );
1314 if ( !doubleBuffer ||
1315 ir.width() > doubleBuffer->width() ||
1316 ir.height() > doubleBuffer->height() )
1317 {
1318 doubleBuffer = bufferPixmap( ir.size() );
1319 }
1320 painter->begin( doubleBuffer );
1321
1322 } else {
1323 p->save();
1324 painter = p;
1325 painter->translate( ir.x(), ir.y() );
1326 }
1327 // Until the next translate(), (0,0) in the painter will be at ir.topLeft() in reality
1328 //kdDebug() << "KoTextDocument::drawParagWYSIWYG ir=" << ir << endl;
1329
1330
1331 // Cumulate ir.x(), ir.y() with the current brush origin
1332 //painter->setBrushOrigin( painter->brushOrigin() + ir.topLeft() );
1333
1334 if ( useDoubleBuffer || is_printer( painter ) ) {
1335 // Transparent -> grab background from p's device
1336 if ( brush.style() != TQt::SolidPattern ) {
1337 bitBlt( doubleBuffer, 0, 0, p->device(),
1338 ir.x() + (int)p->translationX(), ir.y() + (int)p->translationY(),
1339 ir.width(), ir.height() );
1340 }
1341 }
1342
1343 if ( needBrush )
1344 painter->fillRect( TQRect( 0, 0, ir.width(), ir.height() ), brush );
1345
1346 // Now revert the previous painter translation, and instead make (0,0) the topleft of the PARAGRAPH
1347 painter->translate( rect.x() - ir.x(), rect.y() - ir.y() );
1348#ifdef DEBUG_PAINTING
1349 kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG translate " << rect.x() - ir.x() << "," << rect.y() - ir.y() << endl;
1350#endif
1351 //painter->setBrushOrigin( painter->brushOrigin() + rect.topLeft() - ir.topLeft() );
1352
1353 // The cliprect is checked in layout units, in KoTextParag::paint
1354 TQRect crect_lu( zoomHandler->pixelToLayoutUnit( crect ) );
1355#ifdef DEBUG_PAINTING
1356 kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG crect_lu=" << crect_lu << endl;
1357#endif
1358
1359 // paintDefault will paint line 'lineChanged' at its normal Y position.
1360 // But the buffer-pixmap below starts at Y. We need to translate by -Y
1361 // so that the painting happens at the right place.
1362 painter->translate( 0, -offsetY );
1363
1364 parag->paint( *painter, cg, drawCursor ? cursor : 0, (m_drawingFlags & DrawSelections),
1365 crect_lu.x(), crect_lu.y(), crect_lu.width(), crect_lu.height() );
1366
1367
1368 if ( useDoubleBuffer ) {
1369 delete painter;
1370 painter = 0;
1371 p->drawPixmap( ir.topLeft(), *doubleBuffer, TQRect( TQPoint( 0, 0 ), ir.size() ) );
1372#if 0 // for debug!
1373 p->save();
1374 p->setPen( TQt::blue );
1375 p->drawRect( ir.x(), ir.y(), ir.width(), ir.height() );
1376 p->restore();
1377#endif
1378 } else {
1379 // undo previous translations, painter is 'p', i.e. will be used later on
1380 p->restore();
1381 //painter->translate( -ir.x(), -ir.y() );
1382 //painter->translate( 0, +offsetY );
1383 //painter->setBrushOrigin( painter->brushOrigin() - ir.topLeft() );
1384 }
1385
1386 if ( needBrush ) {
1387 int docright = zoomHandler->layoutUnitToPixelX( parag->document()->x() + parag->document()->width() );
1388#ifdef DEBUG_PAINTING
1389// kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG my rect is: " << rect << endl;
1390#endif
1391 if ( rect.x() + rect.width() < docright ) {
1392#ifdef DEBUG_PAINTING
1393 kdDebug(32500) << "KoTextDocument::drawParagWYSIWYG rect doesn't go up to docright=" << docright << endl;
1394#endif
1395 p->fillRect( rect.x() + rect.width(), rect.y(),
1396 docright - ( rect.x() + rect.width() ),
1397 rect.height(), cg.brush( TQColorGroup::Base ) );
1398 }
1399 }
1400
1401 if ( resetChanged )
1402 parag->setChanged( false );
1403}
1404
1405
1406KoTextDocCommand *KoTextDocument::deleteTextCommand( KoTextDocument *textdoc, int id, int index, const TQMemArray<KoTextStringChar> & str, const CustomItemsMap & customItemsMap, const TQValueList<KoParagLayout> & oldParagLayouts )
1407{
1408 return new KoTextDeleteCommand( textdoc, id, index, str, customItemsMap, oldParagLayouts );
1409}
1410
1411KoTextParag* KoTextDocument::loadOasisText( const TQDomElement& bodyElem, KoOasisContext& context, KoTextParag* lastParagraph, KoStyleCollection* styleColl, KoTextParag* nextParagraph )
1412{
1413 // was OoWriterImport::parseBodyOrSimilar
1414 TQDomElement tag;
1415 forEachElement( tag, bodyElem )
1416 {
1417 context.styleStack().save();
1418 const TQString localName = tag.localName();
1419 const bool isTextNS = tag.namespaceURI() == KoXmlNS::text;
1420 uint pos = 0;
1421 if ( isTextNS && localName == "p" ) { // text paragraph
1422 context.fillStyleStack( tag, KoXmlNS::text, "style-name", "paragraph" );
1423
1424 KoTextParag *parag = createParag( this, lastParagraph, nextParagraph );
1425 parag->loadOasis( tag, context, styleColl, pos );
1426 if ( !lastParagraph ) // First parag
1427 setFirstParag( parag );
1428 lastParagraph = parag;
1429 }
1430 else if ( isTextNS && localName == "h" ) // heading
1431 {
1432 //kdDebug(32500) << " heading " << endl;
1433 context.fillStyleStack( tag, KoXmlNS::text, "style-name", "paragraph" );
1434 int level = tag.attributeNS( KoXmlNS::text, "outline-level", TQString() ).toInt();
1435 bool listOK = false;
1436 // When a heading is inside a list, it seems that the list prevails.
1437 // Example:
1438 // <text:list text:style-name="Numbering 1">
1439 // <text:list-item text:start-value="5">
1440 // <text:h text:style-name="P2" text:level="4">The header</text:h>
1441 // where P2 has list-style-name="something else"
1442 // Result: the numbering of the header follows "Numbering 1".
1443 // So we use the style for the outline level only if we're not inside a list:
1444 //if ( !context.atStartOfListItem() )
1445 // === The new method for this is that we simply override it after loading.
1446 listOK = context.pushOutlineListLevelStyle( level );
1447 int restartNumbering = -1;
1448 if ( tag.hasAttributeNS( KoXmlNS::text, "start-value" ) )
1449 // OASIS extension http://lists.oasis-open.org/archives/office/200310/msg00033.html
1450 restartNumbering = tag.attributeNS( KoXmlNS::text, "start-value", TQString() ).toInt();
1451
1452 KoTextParag *parag = createParag( this, lastParagraph, nextParagraph );
1453 parag->loadOasis( tag, context, styleColl, pos );
1454 if ( !lastParagraph ) // First parag
1455 setFirstParag( parag );
1456 lastParagraph = parag;
1457 if ( listOK ) {
1458 parag->applyListStyle( context, restartNumbering, true /*ordered*/, true /*heading*/, level );
1459 context.listStyleStack().pop();
1460 }
1461 }
1462 else if ( isTextNS &&
1463 ( localName == "unordered-list" || localName == "ordered-list" // OOo-1.1
1464 || localName == "list" || localName == "numbered-paragraph" ) ) // OASIS
1465 {
1466 lastParagraph = loadList( tag, context, lastParagraph, styleColl, nextParagraph );
1467 }
1468 else if ( isTextNS && localName == "section" ) // Temporary support (###TODO)
1469 {
1470 kdDebug(32500) << "Section found!" << endl;
1471 context.fillStyleStack( tag, KoXmlNS::text, "style-name", "section" );
1472 lastParagraph = loadOasisText( tag, context, lastParagraph, styleColl, nextParagraph );
1473 }
1474 else if ( isTextNS && localName == "variable-decls" )
1475 {
1476 // We don't parse variable-decls since we ignore var types right now
1477 // (and just storing a list of available var names wouldn't be much use)
1478 }
1479 else if ( isTextNS && localName == "user-field-decls" )
1480 {
1481 TQDomElement fd;
1482 forEachElement( fd, tag )
1483 {
1484 if ( fd.namespaceURI() == KoXmlNS::text && fd.localName() == "user-field-decl" )
1485 {
1486 const TQString name = fd.attributeNS( KoXmlNS::text, "name", TQString() );
1487 const TQString value = fd.attributeNS( KoXmlNS::office, "value", TQString() );
1488 if ( !name.isEmpty() )
1489 context.variableCollection().setVariableValue( name, value );
1490 }
1491 }
1492 }
1493 else if ( isTextNS && localName == "number" ) // text:number
1494 {
1495 // This is the number in front of a numbered paragraph,
1496 // written out to help export filters. We can ignore it.
1497 }
1498 else if ( !loadOasisBodyTag( tag, context, lastParagraph, styleColl, nextParagraph ) )
1499 {
1500 kdWarning(32500) << "Unsupported body element '" << localName << "'" << endl;
1501 }
1502
1503 context.styleStack().restore(); // remove the styles added by the paragraph or list
1504 //use signal slot ?
1505 //m_doc->progressItemLoaded(); // ## check
1506 }
1507 return lastParagraph;
1508}
1509
1510KoTextParag* KoTextDocument::loadList( const TQDomElement& list, KoOasisContext& context, KoTextParag* lastParagraph, KoStyleCollection * styleColl, KoTextParag* nextParagraph )
1511{
1512 //kdDebug(32500) << "loadList: " << list.attributeNS( KoXmlNS::text, "style-name", TQString() ) << endl;
1513
1514 const TQString oldListStyleName = context.currentListStyleName();
1515 if ( list.hasAttributeNS( KoXmlNS::text, "style-name" ) )
1516 context.setCurrentListStyleName( list.attributeNS( KoXmlNS::text, "style-name", TQString() ) );
1517 bool listOK = !context.currentListStyleName().isEmpty();
1518 int level;
1519 if ( list.localName() == "numbered-paragraph" )
1520 level = list.attributeNS( KoXmlNS::text, "level", "1" ).toInt();
1521 else
1522 level = context.listStyleStack().level() + 1;
1523 if ( listOK )
1524 listOK = context.pushListLevelStyle( context.currentListStyleName(), level );
1525
1526 const TQDomElement listStyle = context.listStyleStack().currentListStyle();
1527 // The tag is either list-level-style-number or list-level-style-bullet
1528 const bool orderedList = listStyle.localName() == "list-level-style-number";
1529
1530 if ( list.localName() == "numbered-paragraph" )
1531 {
1532 // A numbered-paragraph contains paragraphs directly (it's both a list and a list-item)
1533 int restartNumbering = -1;
1534 if ( list.hasAttributeNS( KoXmlNS::text, "start-value" ) )
1535 restartNumbering = list.attributeNS( KoXmlNS::text, "start-value", TQString() ).toInt();
1536 KoTextParag* oldLast = lastParagraph;
1537 lastParagraph = loadOasisText( list, context, lastParagraph, styleColl, nextParagraph );
1538 KoTextParag* firstListItem = oldLast ? oldLast->next() : firstParag();
1539 // Apply list style to first paragraph inside numbered-parag - there's only one anyway
1540 // Keep the "is outline" property though
1541 bool isOutline = firstListItem->counter() && firstListItem->counter()->numbering() == KoParagCounter::NUM_CHAPTER;
1542 firstListItem->applyListStyle( context, restartNumbering, orderedList,
1543 isOutline, level );
1544 }
1545 else
1546 {
1547 // Iterate over list items
1548 for ( TQDomNode n = list.firstChild(); !n.isNull(); n = n.nextSibling() )
1549 {
1550 TQDomElement listItem = n.toElement();
1551 int restartNumbering = -1;
1552 if ( listItem.hasAttributeNS( KoXmlNS::text, "start-value" ) )
1553 restartNumbering = listItem.attributeNS( KoXmlNS::text, "start-value", TQString() ).toInt();
1554 bool isListHeader = listItem.localName() == "list-header" || listItem.attributeNS( KoXmlNS::text, "is-list-header", TQString() ) == "is-list-header";
1555 KoTextParag* oldLast = lastParagraph;
1556 lastParagraph = loadOasisText( listItem, context, lastParagraph, styleColl, nextParagraph );
1557 KoTextParag* firstListItem = oldLast ? oldLast->next() : firstParag();
1558 KoTextParag* p = firstListItem;
1559 // It's either list-header (normal text on top of list) or list-item
1560 if ( !isListHeader && firstListItem ) {
1561 // Apply list style to first paragraph inside list-item
1562 bool isOutline = firstListItem->counter() && firstListItem->counter()->numbering() == KoParagCounter::NUM_CHAPTER;
1563 firstListItem->applyListStyle( context, restartNumbering, orderedList, isOutline, level );
1564 p = p->next();
1565 }
1566 // Make text:h inside list-item (as non first child) unnumbered.
1567 while ( p && p != lastParagraph->next() ) {
1568 if ( p->counter() )
1569 p->counter()->setNumbering( KoParagCounter::NUM_NONE );
1570 p = p->next();
1571 }
1572 }
1573 }
1574 if ( listOK )
1575 context.listStyleStack().pop();
1576 context.setCurrentListStyleName( oldListStyleName );
1577 return lastParagraph;
1578}
1579
1580void KoTextDocument::saveOasisContent( KoXmlWriter& writer, KoSavingContext& context ) const
1581{
1582 // Basically just call saveOasis on every paragraph.
1583 // KWord doesn't use this method because it does table-of-contents-handling in addition.
1584 KoTextParag* parag = firstParag();
1585 while ( parag ) {
1586 // Save the whole parag, without the trailing space.
1587 parag->saveOasis( writer, context, 0, parag->lastCharPos() );
1588 parag = parag->next();
1589 }
1590}
1591
1592#include "KoTextDocument.moc"
A CustomItemsMap associates a custom item to an index Used in the undo/redo info for insert/delete te...
void pop()
Removes the style on top of the stack.
TQDomElement currentListStyle() const
Used during loading of Oasis format (and discarded at the end of the loading).
bool pushListLevelStyle(const TQString &listStyleName, int level)
Used for lists (numbered paragraphs)
bool pushOutlineListLevelStyle(int level)
Used for outline levels.
void fillStyleStack(const TQDomElement &element, const char *nsURI, const char *attrName, const char *family)
Convenience method for loading the style of an object before loading that object.
Base class for "visitors".
virtual bool visit(KoTextParag *parag, int start, int end)=0
Visit the paragraph parag, from index start to index end.
Used during saving to Oasis format (and discarded at the end of the saving).
TODO rename to KoParagStyleCollection - or should char styles be part of it too?
void save()
Save the current state of the stack.
void restore()
Restore the stack to the state it was at the corresponding save() call.
KoTextCustomItem is the base class for custom items (i.e.
Command created when deleting some text.
Each character (KoTextStringChar) points to a KoTextFormat that defines the formatting of that charac...
We implement our own text formatter to implement WYSIWYG: It is heavily based on KoTextFormatterBaseB...
This class extends KoZoomHandler to add support for WYSIWYG text layouting.
int layoutUnitToPixelX(int lupix) const
The "layout unit pixel" -> "[zoomed] view pixel" conversions.
A class for writing out XML (to any TQIODevice), with a special attention on performance.
Definition KoXmlWriter.h:37
TDEAction * forward(const TQObject *recvr, const char *slot, TDEActionCollection *parent, const char *name=0)
TDEAction * clear(const TQObject *recvr, const char *slot, TDEActionCollection *parent, const char *name=0)
const char * name(StdAction id)