lib

KoTextIterator.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2002-2006 David Faure <faure@kde.org>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License version 2 as published by the Free Software Foundation.
00007 
00008    This library is distributed in the hope that it will be useful,
00009    but WITHOUT ANY WARRANTY; without even the implied warranty of
00010    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00011    Library General Public License for more details.
00012 
00013    You should have received a copy of the GNU Library General Public License
00014    along with this library; see the file COPYING.LIB.  If not, write to
00015    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00016  * Boston, MA 02110-1301, USA.
00017 */
00018 
00019 #include "KoTextIterator.h"
00020 #include "KoTextParag.h"
00021 #include "KoTextView.h"
00022 #include <kfinddialog.h>
00023 #include <kdebug.h>
00024 #include <assert.h>
00025 
00026 //#define DEBUG_ITERATOR
00027 
00036 void KoTextIterator::init( const QValueList<KoTextObject *> & lstObjects, KoTextView* textView, int options )
00037 {
00038     Q_ASSERT( !lstObjects.isEmpty() );
00039 
00040     m_lstObjects.clear();
00041     m_firstParag = 0;
00042     m_firstIndex = 0;
00043     m_options = options;
00044 
00045     // 'From Cursor' option
00046     if ( options & KFindDialog::FromCursor )
00047     {
00048         if ( textView ) {
00049             m_firstParag = textView->cursor()->parag();
00050             m_firstIndex = textView->cursor()->index();
00051         } else {
00052             // !? FromCursor option can't work
00053             m_options &= ~KFindDialog::FromCursor;
00054             kdWarning(32500) << "FromCursor specified, but no textview?" << endl;
00055         }
00056     } // no else here !
00057 
00058     bool forw = ! ( options & KFindDialog::FindBackwards );
00059 
00060     // 'Selected Text' option
00061     if ( textView && ( options & KFindDialog::SelectedText ) )
00062     {
00063         KoTextObject* textObj = textView->textObject();
00064         KoTextCursor c1 = textObj->textDocument()->selectionStartCursor( KoTextDocument::Standard );
00065         KoTextCursor c2 = textObj->textDocument()->selectionEndCursor( KoTextDocument::Standard );
00066         if ( !m_firstParag ) // not from cursor
00067         {
00068             m_firstParag = forw ? c1.parag() : c2.parag();
00069             m_firstIndex = forw ? c1.index() : c2.index();
00070         }
00071         m_lastParag = forw ? c2.parag() : c1.parag();
00072         m_lastIndex = forw ? c2.index() : c1.index();
00073         // Find in the selection only -> only one textobject
00074         m_lstObjects.append( textObj );
00075         m_currentTextObj = m_lstObjects.begin();
00076     }
00077     else
00078     {
00079         // Not "selected text" -> loop through all textobjects
00080         m_lstObjects = lstObjects;
00081         if ( textView && (options & KFindDialog::FromCursor) )
00082         {
00083             KoTextObject* initialFirst = m_lstObjects.first();
00084             // textView->textObject() should be first in m_lstObjects (last when going backwards) !
00085             // Let's ensure this is the case, but without changing the order of the objects.
00086             if ( forw ) {
00087                 while( m_lstObjects.first() != textView->textObject() ) {
00088                     KoTextObject* textobj = m_lstObjects.front();
00089                     m_lstObjects.pop_front();
00090                     m_lstObjects.push_back( textobj );
00091                     if ( m_lstObjects.first() == initialFirst ) { // safety
00092                         kdWarning(32500) << "Didn't manage to find " << textView->textObject() << " in the list of textobjects!!!" << endl;
00093                         break;
00094                     }
00095                 }
00096             } else {
00097                 while( m_lstObjects.last() != textView->textObject() ) {
00098                     KoTextObject* textobj = m_lstObjects.back();
00099                     m_lstObjects.pop_back();
00100                     m_lstObjects.push_front( textobj );
00101                     if ( m_lstObjects.first() == initialFirst ) { // safety
00102                         kdWarning(32500) << "Didn't manage to find " << textView->textObject() << " in the list of textobjects!!!" << endl;
00103                         break;
00104                     }
00105                 }
00106             }
00107         }
00108 
00109         KoTextParag* firstParag = m_lstObjects.first()->textDocument()->firstParag();
00110         int firstIndex = 0;
00111         KoTextParag* lastParag = m_lstObjects.last()->textDocument()->lastParag();
00112         int lastIndex = lastParag->length()-1;
00113         if ( !m_firstParag ) // only set this when not 'from cursor'.
00114         {
00115             m_firstParag = forw ? firstParag : lastParag;
00116             m_firstIndex = forw ? firstIndex : lastIndex;
00117         }
00118         // always set the ending point
00119         m_lastParag = forw ? lastParag : firstParag;
00120         m_lastIndex = forw ? lastIndex : firstIndex;
00121         m_currentTextObj = forw ? m_lstObjects.begin() : m_lstObjects.fromLast();
00122     }
00123 
00124     assert( *m_currentTextObj ); // all branches set it
00125     assert( m_firstParag );
00126     assert( m_lastParag );
00127     Q_ASSERT( (*m_currentTextObj)->isVisible() );
00128     m_currentParag = m_firstParag;
00129 #ifdef DEBUG_ITERATOR
00130     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;
00131     QValueList<KoTextObject *>::Iterator it = m_lstObjects.begin();
00132     for( ; it != m_lstObjects.end(); ++it )
00133         kdDebug(32500) << (*it) << " " << (*it)->name() << endl;
00134 #endif
00135     Q_ASSERT( (*m_currentTextObj)->textDocument() == m_currentParag->textDocument() );
00136     Q_ASSERT( (forw?m_lstObjects.last():m_lstObjects.first())->textDocument() == m_lastParag->textDocument() );
00137 
00138     connectTextObjects();
00139 }
00140 
00141 void KoTextIterator::restart()
00142 {
00143     if( m_lstObjects.isEmpty() )
00144         return;
00145     m_currentParag = m_firstParag;
00146     bool forw = ! ( m_options & KFindDialog::FindBackwards );
00147     Q_ASSERT( ! (m_options & KFindDialog::FromCursor) ); // doesn't make much sense to keep it, right?
00148     if ( (m_options & KFindDialog::FromCursor) || forw )
00149         m_currentTextObj = m_lstObjects.begin();
00150     else
00151         m_currentTextObj = m_lstObjects.fromLast();
00152     if ( !(*m_currentTextObj)->isVisible() )
00153         nextTextObject();
00154 #ifdef DEBUG_ITERATOR
00155     if ( m_currentParag )
00156         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;
00157     else
00158         kdDebug(32500) << "KoTextIterator::restart - nowhere to go!" << endl;
00159 #endif
00160 }
00161 
00162 void KoTextIterator::connectTextObjects()
00163 {
00164     QValueList<KoTextObject *>::Iterator it = m_lstObjects.begin();
00165     for( ; it != m_lstObjects.end(); ++it ) {
00166         connect( (*it), SIGNAL( paragraphDeleted( KoTextParag* ) ),
00167                  this, SLOT( slotParagraphDeleted( KoTextParag* ) ) );
00168         connect( (*it), SIGNAL( paragraphModified( KoTextParag*, int, int, int ) ),
00169                  this, SLOT( slotParagraphModified( KoTextParag*, int, int, int ) ) );
00170         // We don't connect to destroyed(), because for undo/redo purposes,
00171         // we never really delete textdocuments nor textobjects.
00172         // So this is never called.
00173         // Instead the textobject is simply set to invisible, and this is handled by nextTextObject
00174     }
00175 }
00176 
00177 void KoTextIterator::slotParagraphModified( KoTextParag* parag, int modifyType, int pos, int length )
00178 {
00179     if ( parag == m_currentParag )
00180         emit currentParagraphModified( modifyType, pos, length );
00181 }
00182 
00183 void KoTextIterator::slotParagraphDeleted( KoTextParag* parag )
00184 {
00185 #ifdef DEBUG_ITERATOR
00186     kdDebug(32500) << "KoTextIterator::slotParagraphDeleted " << parag << " (" << parag->paragId() << ")" << endl;
00187 #endif
00188     // Note that the direction doesn't matter here. A begin/end
00189     // at end of parag N or at beginning of parag N+1 is the same,
00190     // and m_firstIndex/m_lastIndex becomes irrelevant, anyway.
00191     if ( parag == m_lastParag )
00192     {
00193         if ( m_lastParag->prev() ) {
00194             m_lastParag = m_lastParag->prev();
00195             m_lastIndex = m_lastParag->length()-1;
00196         } else {
00197             m_lastParag = m_lastParag->next();
00198             m_lastIndex = 0;
00199         }
00200     }
00201     if ( parag == m_firstParag )
00202     {
00203         if ( m_firstParag->prev() ) {
00204             m_firstParag = m_firstParag->prev();
00205             m_firstIndex = m_firstParag->length()-1;
00206         } else {
00207             m_firstParag = m_firstParag->next();
00208             m_firstIndex = 0;
00209         }
00210     }
00211     if ( parag == m_currentParag )
00212     {
00213         operator++();
00214         emit currentParagraphDeleted();
00215     }
00216 #ifdef DEBUG_ITERATOR
00217     if ( m_currentParag )
00218         kdDebug(32500) << "KoTextIterator: firstParag:" << m_firstParag << " (" << m_firstParag->paragId() << ") -  lastParag:" << m_lastParag << " (" << m_lastParag->paragId() << ") m_currentParag:" << m_currentParag << " (" << m_currentParag->paragId() << ")" << endl;
00219 #endif
00220 }
00221 
00222 // Go to next paragraph that we must iterate over
00223 void KoTextIterator::operator++()
00224 {
00225     if ( !m_currentParag ) {
00226         kdDebug(32500) << k_funcinfo << " called past the end" << endl;
00227         return;
00228     }
00229     if ( m_currentParag == m_lastParag ) {
00230         m_currentParag = 0L;
00231 #ifdef DEBUG_ITERATOR
00232         kdDebug(32500) << "KoTextIterator++: done, after last parag " << m_lastParag << endl;
00233 #endif
00234         return;
00235     }
00236     bool forw = ! ( m_options & KFindDialog::FindBackwards );
00237     KoTextParag* parag = forw ? m_currentParag->next() : m_currentParag->prev();
00238     if ( parag )
00239     {
00240         m_currentParag = parag;
00241     }
00242     else
00243     {
00244         nextTextObject();
00245     }
00246 #ifdef DEBUG_ITERATOR
00247     if ( m_currentParag )
00248         kdDebug(32500) << "KoTextIterator++ (" << *m_currentTextObj << "," <<
00249             m_currentParag->paragId() << ")" << endl;
00250     else
00251         kdDebug(32500) << "KoTextIterator++ (at end)" << endl;
00252 #endif
00253 }
00254 
00255 void KoTextIterator::nextTextObject()
00256 {
00257     bool forw = ! ( m_options & KFindDialog::FindBackwards );
00258     do {
00259         if ( forw ) {
00260             ++m_currentTextObj;
00261             if ( m_currentTextObj == m_lstObjects.end() )
00262                 m_currentParag = 0L; // done
00263             else
00264                 m_currentParag = (*m_currentTextObj)->textDocument()->firstParag();
00265         } else {
00266             if ( m_currentTextObj == m_lstObjects.begin() )
00267                 m_currentParag = 0L; // done
00268             else
00269             {
00270                 --m_currentTextObj;
00271                 m_currentParag = (*m_currentTextObj)->textDocument()->lastParag();
00272             }
00273         }
00274     }
00275     // loop in case this new textobject is not visible
00276     while ( m_currentParag && !(*m_currentTextObj)->isVisible() );
00277 #ifdef DEBUG_ITERATOR
00278     if ( m_currentParag )
00279         kdDebug(32500) << k_funcinfo << " m_currentTextObj=" << (*m_currentTextObj) << endl;
00280 #endif
00281 }
00282 
00283 bool KoTextIterator::atEnd() const
00284 {
00285     // operator++ sets m_currentParag to 0 when it's done
00286     return m_currentParag == 0L;
00287 }
00288 
00289 int KoTextIterator::currentStartIndex() const
00290 {
00291     return currentTextAndIndex().first;
00292 }
00293 
00294 QString KoTextIterator::currentText() const
00295 {
00296     return currentTextAndIndex().second;
00297 }
00298 
00299 QPair<int, QString> KoTextIterator::currentTextAndIndex() const
00300 {
00301     Q_ASSERT( m_currentParag );
00302     Q_ASSERT( m_currentParag->string() );
00303     QString str = m_currentParag->string()->toString();
00304     str.truncate( str.length() - 1 ); // remove trailing space
00305     bool forw = ! ( m_options & KFindDialog::FindBackwards );
00306     if ( m_currentParag == m_firstParag )
00307     {
00308         if ( m_firstParag == m_lastParag ) // special case, needs truncating at both ends
00309             return forw ? qMakePair( m_firstIndex, str.mid( m_firstIndex, m_lastIndex - m_firstIndex ) )
00310                 : qMakePair( m_lastIndex, str.mid( m_lastIndex, m_firstIndex - m_lastIndex ) );
00311         else
00312             return forw ? qMakePair( m_firstIndex, str.mid( m_firstIndex ) )
00313                         : qMakePair( 0, str.left( m_firstIndex ) );
00314     }
00315     if ( m_currentParag == m_lastParag )
00316     {
00317         return forw ? qMakePair( 0, str.left( m_lastIndex ) )
00318                     : qMakePair( m_lastIndex, str.mid( m_lastIndex ) );
00319     }
00320     // Not the first parag, nor the last, so we return it all
00321     return qMakePair( 0, str );
00322 }
00323 
00324 bool KoTextIterator::hasText() const
00325 {
00326     // Same logic as currentTextAndIndex, but w/o calling it, to avoid all the string copying
00327     bool forw = ! ( m_options & KFindDialog::FindBackwards );
00328     int strLength = m_currentParag->string()->length() - 1;
00329     if ( m_currentParag == m_firstParag )
00330     {
00331         if ( m_firstParag == m_lastParag )
00332             return m_firstIndex < m_lastIndex;
00333         else
00334             return forw ? m_firstIndex < strLength
00335                         : m_firstIndex > 0;
00336     }
00337     if ( m_currentParag == m_lastParag )
00338         return forw ? m_lastIndex > 0
00339                     : m_lastIndex < strLength;
00340     return strLength > 0;
00341 }
00342 
00343 void KoTextIterator::setOptions( int options )
00344 {
00345     if ( m_options != options )
00346     {
00347         bool wasBack = (m_options & KFindDialog::FindBackwards);
00348         bool isBack = (options & KFindDialog::FindBackwards);
00349         if ( wasBack != isBack )
00350         {
00351             qSwap( m_firstParag, m_lastParag );
00352             qSwap( m_firstIndex, m_lastIndex );
00353             if ( m_currentParag == 0 ) // done? -> reinit
00354             {
00355 #ifdef DEBUG_ITERATOR
00356                 kdDebug(32500) << k_funcinfo << "was done -> reinit" << endl;
00357 #endif
00358                 restart();
00359             }
00360         }
00361         bool wasFromCursor = (m_options & KFindDialog::FromCursor);
00362         bool isFromCursor = (options & KFindDialog::FromCursor);
00363         // We can only handle the case where fromcursor got removed.
00364         // If it got added, then we need a textview to take the cursor position from...
00365         if ( wasFromCursor && !isFromCursor )
00366         {
00367             // We also can't handle the "selected text" option here
00368             // It's very hard to have a cursor that's not at the beginning
00369             // or end of the selection, anyway.
00370             if ( ! (options & KFindDialog::SelectedText ) )
00371             {
00372                 // Set m_firstParag/m_firstIndex to the beginning of the first object
00373                 // (end of last object when going backwards)
00374                 KoTextParag* firstParag = m_lstObjects.first()->textDocument()->firstParag();
00375                 int firstIndex = 0;
00376                 KoTextParag* lastParag = m_lstObjects.last()->textDocument()->lastParag();
00377                 int lastIndex = lastParag->length()-1;
00378                 m_firstParag = (!isBack) ? firstParag : lastParag;
00379                 m_firstIndex = (!isBack) ? firstIndex : lastIndex;
00380 #ifdef DEBUG_ITERATOR
00381                 kdDebug(32500) << "setOptions: FromCursor removed. New m_firstParag=" << m_firstParag << " (" << m_firstParag->paragId() << ") isBack=" << isBack << endl;
00382 #endif
00383             }
00384         }
00385         m_options = options;
00386     }
00387 }
00388 
00389 #include "KoTextIterator.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys