lib

KoTextIterator.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2002-2006 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 version 2 as published by the Free Software Foundation.
7
8 This library is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 Library General Public License for more details.
12
13 You should have received a copy of the GNU Library General Public License
14 along with this library; see the file COPYING.LIB. If not, write to
15 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17*/
18
19#include "KoTextIterator.h"
20#include "KoTextParag.h"
21#include "KoTextView.h"
22#include <kfinddialog.h>
23#include <kdebug.h>
24#include <assert.h>
25
26//#define DEBUG_ITERATOR
27
36void KoTextIterator::init( const TQValueList<KoTextObject *> & lstObjects, KoTextView* textView, int options )
37{
38 Q_ASSERT( !lstObjects.isEmpty() );
39
40 m_lstObjects.clear();
41 m_firstParag = 0;
42 m_firstIndex = 0;
43 m_options = options;
44
45 // 'From Cursor' option
46 if ( options & KFindDialog::FromCursor )
47 {
48 if ( textView ) {
49 m_firstParag = textView->cursor()->parag();
50 m_firstIndex = textView->cursor()->index();
51 } else {
52 // !? FromCursor option can't work
53 m_options &= ~KFindDialog::FromCursor;
54 kdWarning(32500) << "FromCursor specified, but no textview?" << endl;
55 }
56 } // no else here !
57
58 bool forw = ! ( options & KFindDialog::FindBackwards );
59
60 // 'Selected Text' option
61 if ( textView && ( options & KFindDialog::SelectedText ) )
62 {
63 KoTextObject* textObj = textView->textObject();
64 KoTextCursor c1 = textObj->textDocument()->selectionStartCursor( KoTextDocument::Standard );
65 KoTextCursor c2 = textObj->textDocument()->selectionEndCursor( KoTextDocument::Standard );
66 if ( !m_firstParag ) // not from cursor
67 {
68 m_firstParag = forw ? c1.parag() : c2.parag();
69 m_firstIndex = forw ? c1.index() : c2.index();
70 }
71 m_lastParag = forw ? c2.parag() : c1.parag();
72 m_lastIndex = forw ? c2.index() : c1.index();
73 // Find in the selection only -> only one textobject
74 m_lstObjects.append( textObj );
75 m_currentTextObj = m_lstObjects.begin();
76 }
77 else
78 {
79 // Not "selected text" -> loop through all textobjects
80 m_lstObjects = lstObjects;
81 if ( textView && (options & KFindDialog::FromCursor) )
82 {
83 KoTextObject* initialFirst = m_lstObjects.first();
84 // textView->textObject() should be first in m_lstObjects (last when going backwards) !
85 // Let's ensure this is the case, but without changing the order of the objects.
86 if ( forw ) {
87 while( m_lstObjects.first() != textView->textObject() ) {
88 KoTextObject* textobj = m_lstObjects.front();
89 m_lstObjects.pop_front();
90 m_lstObjects.push_back( textobj );
91 if ( m_lstObjects.first() == initialFirst ) { // safety
92 kdWarning(32500) << "Didn't manage to find " << textView->textObject() << " in the list of textobjects!!!" << endl;
93 break;
94 }
95 }
96 } else {
97 while( m_lstObjects.last() != textView->textObject() ) {
98 KoTextObject* textobj = m_lstObjects.back();
99 m_lstObjects.pop_back();
100 m_lstObjects.push_front( textobj );
101 if ( m_lstObjects.first() == initialFirst ) { // safety
102 kdWarning(32500) << "Didn't manage to find " << textView->textObject() << " in the list of textobjects!!!" << endl;
103 break;
104 }
105 }
106 }
107 }
108
109 KoTextParag* firstParag = m_lstObjects.first()->textDocument()->firstParag();
110 int firstIndex = 0;
111 KoTextParag* lastParag = m_lstObjects.last()->textDocument()->lastParag();
112 int lastIndex = lastParag->length()-1;
113 if ( !m_firstParag ) // only set this when not 'from cursor'.
114 {
115 m_firstParag = forw ? firstParag : lastParag;
116 m_firstIndex = forw ? firstIndex : lastIndex;
117 }
118 // always set the ending point
119 m_lastParag = forw ? lastParag : firstParag;
120 m_lastIndex = forw ? lastIndex : firstIndex;
121 m_currentTextObj = forw ? m_lstObjects.begin() : m_lstObjects.fromLast();
122 }
123
124 assert( *m_currentTextObj ); // all branches set it
125 assert( m_firstParag );
126 assert( m_lastParag );
127 Q_ASSERT( (*m_currentTextObj)->isVisible() );
128 m_currentParag = m_firstParag;
129#ifdef DEBUG_ITERATOR
130 kdDebug(32500) << "KoTextIterator::init from(" << *m_currentTextObj << "," << m_firstParag->paragId() << ") - to(" << (forw?m_lstObjects.last():m_lstObjects.first()) << "," << m_lastParag->paragId() << "), " << m_lstObjects.count() << " textObjects." << endl;
131 TQValueList<KoTextObject *>::Iterator it = m_lstObjects.begin();
132 for( ; it != m_lstObjects.end(); ++it )
133 kdDebug(32500) << (*it) << " " << (*it)->name() << endl;
134#endif
135 Q_ASSERT( (*m_currentTextObj)->textDocument() == m_currentParag->textDocument() );
136 Q_ASSERT( (forw?m_lstObjects.last():m_lstObjects.first())->textDocument() == m_lastParag->textDocument() );
137
138 connectTextObjects();
139}
140
142{
143 if( m_lstObjects.isEmpty() )
144 return;
145 m_currentParag = m_firstParag;
146 bool forw = ! ( m_options & KFindDialog::FindBackwards );
147 Q_ASSERT( ! (m_options & KFindDialog::FromCursor) ); // doesn't make much sense to keep it, right?
148 if ( (m_options & KFindDialog::FromCursor) || forw )
149 m_currentTextObj = m_lstObjects.begin();
150 else
151 m_currentTextObj = m_lstObjects.fromLast();
152 if ( !(*m_currentTextObj)->isVisible() )
153 nextTextObject();
154#ifdef DEBUG_ITERATOR
155 if ( m_currentParag )
156 kdDebug(32500) << "KoTextIterator::restart from(" << *m_currentTextObj << "," << m_currentParag->paragId() << ") - to(" << (forw?m_lstObjects.last():m_lstObjects.first()) << "," << m_lastParag->paragId() << "), " << m_lstObjects.count() << " textObjects." << endl;
157 else
158 kdDebug(32500) << "KoTextIterator::restart - nowhere to go!" << endl;
159#endif
160}
161
162void KoTextIterator::connectTextObjects()
163{
164 TQValueList<KoTextObject *>::Iterator it = m_lstObjects.begin();
165 for( ; it != m_lstObjects.end(); ++it ) {
166 connect( (*it), TQ_SIGNAL( paragraphDeleted( KoTextParag* ) ),
167 this, TQ_SLOT( slotParagraphDeleted( KoTextParag* ) ) );
168 connect( (*it), TQ_SIGNAL( paragraphModified( KoTextParag*, int, int, int ) ),
169 this, TQ_SLOT( slotParagraphModified( KoTextParag*, int, int, int ) ) );
170 // We don't connect to destroyed(), because for undo/redo purposes,
171 // we never really delete textdocuments nor textobjects.
172 // So this is never called.
173 // Instead the textobject is simply set to invisible, and this is handled by nextTextObject
174 }
175}
176
177void KoTextIterator::slotParagraphModified( KoTextParag* parag, int modifyType, int pos, int length )
178{
179 if ( parag == m_currentParag )
180 emit currentParagraphModified( modifyType, pos, length );
181}
182
183void KoTextIterator::slotParagraphDeleted( KoTextParag* parag )
184{
185#ifdef DEBUG_ITERATOR
186 kdDebug(32500) << "KoTextIterator::slotParagraphDeleted " << parag << " (" << parag->paragId() << ")" << endl;
187#endif
188 // Note that the direction doesn't matter here. A begin/end
189 // at end of parag N or at beginning of parag N+1 is the same,
190 // and m_firstIndex/m_lastIndex becomes irrelevant, anyway.
191 if ( parag == m_lastParag )
192 {
193 if ( m_lastParag->prev() ) {
194 m_lastParag = m_lastParag->prev();
195 m_lastIndex = m_lastParag->length()-1;
196 } else {
197 m_lastParag = m_lastParag->next();
198 m_lastIndex = 0;
199 }
200 }
201 if ( parag == m_firstParag )
202 {
203 if ( m_firstParag->prev() ) {
204 m_firstParag = m_firstParag->prev();
205 m_firstIndex = m_firstParag->length()-1;
206 } else {
207 m_firstParag = m_firstParag->next();
208 m_firstIndex = 0;
209 }
210 }
211 if ( parag == m_currentParag )
212 {
213 operator++();
215 }
216#ifdef DEBUG_ITERATOR
217 if ( m_currentParag )
218 kdDebug(32500) << "KoTextIterator: firstParag:" << m_firstParag << " (" << m_firstParag->paragId() << ") - lastParag:" << m_lastParag << " (" << m_lastParag->paragId() << ") m_currentParag:" << m_currentParag << " (" << m_currentParag->paragId() << ")" << endl;
219#endif
220}
221
222// Go to next paragraph that we must iterate over
224{
225 if ( !m_currentParag ) {
226 kdDebug(32500) << k_funcinfo << " called past the end" << endl;
227 return;
228 }
229 if ( m_currentParag == m_lastParag ) {
230 m_currentParag = 0L;
231#ifdef DEBUG_ITERATOR
232 kdDebug(32500) << "KoTextIterator++: done, after last parag " << m_lastParag << endl;
233#endif
234 return;
235 }
236 bool forw = ! ( m_options & KFindDialog::FindBackwards );
237 KoTextParag* parag = forw ? m_currentParag->next() : m_currentParag->prev();
238 if ( parag )
239 {
240 m_currentParag = parag;
241 }
242 else
243 {
244 nextTextObject();
245 }
246#ifdef DEBUG_ITERATOR
247 if ( m_currentParag )
248 kdDebug(32500) << "KoTextIterator++ (" << *m_currentTextObj << "," <<
249 m_currentParag->paragId() << ")" << endl;
250 else
251 kdDebug(32500) << "KoTextIterator++ (at end)" << endl;
252#endif
253}
254
255void KoTextIterator::nextTextObject()
256{
257 bool forw = ! ( m_options & KFindDialog::FindBackwards );
258 do {
259 if ( forw ) {
260 ++m_currentTextObj;
261 if ( m_currentTextObj == m_lstObjects.end() )
262 m_currentParag = 0L; // done
263 else
264 m_currentParag = (*m_currentTextObj)->textDocument()->firstParag();
265 } else {
266 if ( m_currentTextObj == m_lstObjects.begin() )
267 m_currentParag = 0L; // done
268 else
269 {
270 --m_currentTextObj;
271 m_currentParag = (*m_currentTextObj)->textDocument()->lastParag();
272 }
273 }
274 }
275 // loop in case this new textobject is not visible
276 while ( m_currentParag && !(*m_currentTextObj)->isVisible() );
277#ifdef DEBUG_ITERATOR
278 if ( m_currentParag )
279 kdDebug(32500) << k_funcinfo << " m_currentTextObj=" << (*m_currentTextObj) << endl;
280#endif
281}
282
284{
285 // operator++ sets m_currentParag to 0 when it's done
286 return m_currentParag == 0L;
287}
288
290{
291 return currentTextAndIndex().first;
292}
293
295{
296 return currentTextAndIndex().second;
297}
298
299TQPair<int, TQString> KoTextIterator::currentTextAndIndex() const
300{
301 Q_ASSERT( m_currentParag );
302 Q_ASSERT( m_currentParag->string() );
303 TQString str = m_currentParag->string()->toString();
304 str.truncate( str.length() - 1 ); // remove trailing space
305 bool forw = ! ( m_options & KFindDialog::FindBackwards );
306 if ( m_currentParag == m_firstParag )
307 {
308 if ( m_firstParag == m_lastParag ) // special case, needs truncating at both ends
309 return forw ? qMakePair( m_firstIndex, str.mid( m_firstIndex, m_lastIndex - m_firstIndex ) )
310 : qMakePair( m_lastIndex, str.mid( m_lastIndex, m_firstIndex - m_lastIndex ) );
311 else
312 return forw ? qMakePair( m_firstIndex, str.mid( m_firstIndex ) )
313 : qMakePair( 0, str.left( m_firstIndex ) );
314 }
315 if ( m_currentParag == m_lastParag )
316 {
317 return forw ? qMakePair( 0, str.left( m_lastIndex ) )
318 : qMakePair( m_lastIndex, str.mid( m_lastIndex ) );
319 }
320 // Not the first parag, nor the last, so we return it all
321 return qMakePair( 0, str );
322}
323
325{
326 // Same logic as currentTextAndIndex, but w/o calling it, to avoid all the string copying
327 bool forw = ! ( m_options & KFindDialog::FindBackwards );
328 int strLength = m_currentParag->string()->length() - 1;
329 if ( m_currentParag == m_firstParag )
330 {
331 if ( m_firstParag == m_lastParag )
332 return m_firstIndex < m_lastIndex;
333 else
334 return forw ? m_firstIndex < strLength
335 : m_firstIndex > 0;
336 }
337 if ( m_currentParag == m_lastParag )
338 return forw ? m_lastIndex > 0
339 : m_lastIndex < strLength;
340 return strLength > 0;
341}
342
343void KoTextIterator::setOptions( int options )
344{
345 if ( m_options != options )
346 {
347 bool wasBack = (m_options & KFindDialog::FindBackwards);
348 bool isBack = (options & KFindDialog::FindBackwards);
349 if ( wasBack != isBack )
350 {
351 tqSwap( m_firstParag, m_lastParag );
352 tqSwap( m_firstIndex, m_lastIndex );
353 if ( m_currentParag == 0 ) // done? -> reinit
354 {
355#ifdef DEBUG_ITERATOR
356 kdDebug(32500) << k_funcinfo << "was done -> reinit" << endl;
357#endif
358 restart();
359 }
360 }
361 bool wasFromCursor = (m_options & KFindDialog::FromCursor);
362 bool isFromCursor = (options & KFindDialog::FromCursor);
363 // We can only handle the case where fromcursor got removed.
364 // If it got added, then we need a textview to take the cursor position from...
365 if ( wasFromCursor && !isFromCursor )
366 {
367 // We also can't handle the "selected text" option here
368 // It's very hard to have a cursor that's not at the beginning
369 // or end of the selection, anyway.
370 if ( ! (options & KFindDialog::SelectedText ) )
371 {
372 // Set m_firstParag/m_firstIndex to the beginning of the first object
373 // (end of last object when going backwards)
374 KoTextParag* firstParag = m_lstObjects.first()->textDocument()->firstParag();
375 int firstIndex = 0;
376 KoTextParag* lastParag = m_lstObjects.last()->textDocument()->lastParag();
377 int lastIndex = lastParag->length()-1;
378 m_firstParag = (!isBack) ? firstParag : lastParag;
379 m_firstIndex = (!isBack) ? firstIndex : lastIndex;
380#ifdef DEBUG_ITERATOR
381 kdDebug(32500) << "setOptions: FromCursor removed. New m_firstParag=" << m_firstParag << " (" << m_firstParag->paragId() << ") isBack=" << isBack << endl;
382#endif
383 }
384 }
385 m_options = options;
386 }
387}
388
389#include "KoTextIterator.moc"
TQPair< int, TQString > currentTextAndIndex() const
void currentParagraphDeleted()
Emitted when the current paragraph has been deleted by the user.
int options() const
Return the options currently used by the iterator.
void restart()
Restart from the beginning - assumes same parameters given to init.
bool hasText() const
int currentStartIndex() const
Where in currentParag() does currentText() start?
void setOptions(int options)
Change options during iteration.
void operator++()
Go to next paragraph that we must iterate over.
void init(const TQValueList< KoTextObject * > &lstObjects, KoTextView *textView, int options)
The search direction (forward or backward) is handled in a bit of a tricky way.
void currentParagraphModified(int modifyType, int pos, int length)
Emitted when the current paragraph has been modified by the user.
bool atEnd() const
TQString currentText() const
The KoTextObject is the high-level object that contains a KoTextDocument (the list of paragraphs),...
KoTextDocument * textDocument() const
Return the text document contained in this KoTextObject.
Object that is created to edit a KoTextObject.
Definition KoTextView.h:54