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