/* This file is part of the KDE project Copyright (C) 2002-2006 David Faure <faure@kde.org> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoTextIterator.h" #include "KoTextParag.h" #include "KoTextView.h" #include <kfinddialog.h> #include <kdebug.h> #include <assert.h> //#define DEBUG_ITERATOR /** * The search direction (forward or backward) is handled in a bit of a tricky way. * m_firstParag/m_firstIndex is where the search starts, whichever the direction * m_lastParag/m_lastIndex is where the search ends, whichever the direction * But the list of textobjects is as given (we assume document order). * So we go from the first to the last textobject, or from the last to the first textobject. */ void KoTextIterator::init( const TQValueList<KoTextObject *> & lstObjects, KoTextView* textView, int options ) { Q_ASSERT( !lstObjects.isEmpty() ); m_lstObjects.clear(); m_firstParag = 0; m_firstIndex = 0; m_options = options; // 'From Cursor' option if ( options & KFindDialog::FromCursor ) { if ( textView ) { m_firstParag = textView->cursor()->parag(); m_firstIndex = textView->cursor()->index(); } else { // !? FromCursor option can't work m_options &= ~KFindDialog::FromCursor; kdWarning(32500) << "FromCursor specified, but no textview?" << endl; } } // no else here ! bool forw = ! ( options & KFindDialog::FindBackwards ); // 'Selected Text' option if ( textView && ( options & KFindDialog::SelectedText ) ) { KoTextObject* textObj = textView->textObject(); KoTextCursor c1 = textObj->textDocument()->selectionStartCursor( KoTextDocument::Standard ); KoTextCursor c2 = textObj->textDocument()->selectionEndCursor( KoTextDocument::Standard ); if ( !m_firstParag ) // not from cursor { m_firstParag = forw ? c1.parag() : c2.parag(); m_firstIndex = forw ? c1.index() : c2.index(); } m_lastParag = forw ? c2.parag() : c1.parag(); m_lastIndex = forw ? c2.index() : c1.index(); // Find in the selection only -> only one textobject m_lstObjects.append( textObj ); m_currentTextObj = m_lstObjects.begin(); } else { // Not "selected text" -> loop through all textobjects m_lstObjects = lstObjects; if ( textView && (options & KFindDialog::FromCursor) ) { KoTextObject* initialFirst = m_lstObjects.first(); // textView->textObject() should be first in m_lstObjects (last when going backwards) ! // Let's ensure this is the case, but without changing the order of the objects. if ( forw ) { while( m_lstObjects.first() != textView->textObject() ) { KoTextObject* textobj = m_lstObjects.front(); m_lstObjects.pop_front(); m_lstObjects.push_back( textobj ); if ( m_lstObjects.first() == initialFirst ) { // safety kdWarning(32500) << "Didn't manage to find " << textView->textObject() << " in the list of textobjects!!!" << endl; break; } } } else { while( m_lstObjects.last() != textView->textObject() ) { KoTextObject* textobj = m_lstObjects.back(); m_lstObjects.pop_back(); m_lstObjects.push_front( textobj ); if ( m_lstObjects.first() == initialFirst ) { // safety kdWarning(32500) << "Didn't manage to find " << textView->textObject() << " in the list of textobjects!!!" << endl; break; } } } } KoTextParag* firstParag = m_lstObjects.first()->textDocument()->firstParag(); int firstIndex = 0; KoTextParag* lastParag = m_lstObjects.last()->textDocument()->lastParag(); int lastIndex = lastParag->length()-1; if ( !m_firstParag ) // only set this when not 'from cursor'. { m_firstParag = forw ? firstParag : lastParag; m_firstIndex = forw ? firstIndex : lastIndex; } // always set the ending point m_lastParag = forw ? lastParag : firstParag; m_lastIndex = forw ? lastIndex : firstIndex; m_currentTextObj = forw ? m_lstObjects.begin() : m_lstObjects.fromLast(); } assert( *m_currentTextObj ); // all branches set it assert( m_firstParag ); assert( m_lastParag ); Q_ASSERT( (*m_currentTextObj)->isVisible() ); m_currentParag = m_firstParag; #ifdef DEBUG_ITERATOR 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; TQValueList<KoTextObject *>::Iterator it = m_lstObjects.begin(); for( ; it != m_lstObjects.end(); ++it ) kdDebug(32500) << (*it) << " " << (*it)->name() << endl; #endif Q_ASSERT( (*m_currentTextObj)->textDocument() == m_currentParag->textDocument() ); Q_ASSERT( (forw?m_lstObjects.last():m_lstObjects.first())->textDocument() == m_lastParag->textDocument() ); connectTextObjects(); } void KoTextIterator::restart() { if( m_lstObjects.isEmpty() ) return; m_currentParag = m_firstParag; bool forw = ! ( m_options & KFindDialog::FindBackwards ); Q_ASSERT( ! (m_options & KFindDialog::FromCursor) ); // doesn't make much sense to keep it, right? if ( (m_options & KFindDialog::FromCursor) || forw ) m_currentTextObj = m_lstObjects.begin(); else m_currentTextObj = m_lstObjects.fromLast(); if ( !(*m_currentTextObj)->isVisible() ) nextTextObject(); #ifdef DEBUG_ITERATOR if ( m_currentParag ) 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; else kdDebug(32500) << "KoTextIterator::restart - nowhere to go!" << endl; #endif } void KoTextIterator::connectTextObjects() { TQValueList<KoTextObject *>::Iterator it = m_lstObjects.begin(); for( ; it != m_lstObjects.end(); ++it ) { connect( (*it), TQT_SIGNAL( paragraphDeleted( KoTextParag* ) ), this, TQT_SLOT( slotParagraphDeleted( KoTextParag* ) ) ); connect( (*it), TQT_SIGNAL( paragraphModified( KoTextParag*, int, int, int ) ), this, TQT_SLOT( slotParagraphModified( KoTextParag*, int, int, int ) ) ); // We don't connect to destroyed(), because for undo/redo purposes, // we never really delete textdocuments nor textobjects. // So this is never called. // Instead the textobject is simply set to invisible, and this is handled by nextTextObject } } void KoTextIterator::slotParagraphModified( KoTextParag* parag, int modifyType, int pos, int length ) { if ( parag == m_currentParag ) emit currentParagraphModified( modifyType, pos, length ); } void KoTextIterator::slotParagraphDeleted( KoTextParag* parag ) { #ifdef DEBUG_ITERATOR kdDebug(32500) << "KoTextIterator::slotParagraphDeleted " << parag << " (" << parag->paragId() << ")" << endl; #endif // Note that the direction doesn't matter here. A begin/end // at end of parag N or at beginning of parag N+1 is the same, // and m_firstIndex/m_lastIndex becomes irrelevant, anyway. if ( parag == m_lastParag ) { if ( m_lastParag->prev() ) { m_lastParag = m_lastParag->prev(); m_lastIndex = m_lastParag->length()-1; } else { m_lastParag = m_lastParag->next(); m_lastIndex = 0; } } if ( parag == m_firstParag ) { if ( m_firstParag->prev() ) { m_firstParag = m_firstParag->prev(); m_firstIndex = m_firstParag->length()-1; } else { m_firstParag = m_firstParag->next(); m_firstIndex = 0; } } if ( parag == m_currentParag ) { operator++(); emit currentParagraphDeleted(); } #ifdef DEBUG_ITERATOR if ( m_currentParag ) kdDebug(32500) << "KoTextIterator: firstParag:" << m_firstParag << " (" << m_firstParag->paragId() << ") - lastParag:" << m_lastParag << " (" << m_lastParag->paragId() << ") m_currentParag:" << m_currentParag << " (" << m_currentParag->paragId() << ")" << endl; #endif } // Go to next paragraph that we must iterate over void KoTextIterator::operator++() { if ( !m_currentParag ) { kdDebug(32500) << k_funcinfo << " called past the end" << endl; return; } if ( m_currentParag == m_lastParag ) { m_currentParag = 0L; #ifdef DEBUG_ITERATOR kdDebug(32500) << "KoTextIterator++: done, after last parag " << m_lastParag << endl; #endif return; } bool forw = ! ( m_options & KFindDialog::FindBackwards ); KoTextParag* parag = forw ? m_currentParag->next() : m_currentParag->prev(); if ( parag ) { m_currentParag = parag; } else { nextTextObject(); } #ifdef DEBUG_ITERATOR if ( m_currentParag ) kdDebug(32500) << "KoTextIterator++ (" << *m_currentTextObj << "," << m_currentParag->paragId() << ")" << endl; else kdDebug(32500) << "KoTextIterator++ (at end)" << endl; #endif } void KoTextIterator::nextTextObject() { bool forw = ! ( m_options & KFindDialog::FindBackwards ); do { if ( forw ) { ++m_currentTextObj; if ( m_currentTextObj == m_lstObjects.end() ) m_currentParag = 0L; // done else m_currentParag = (*m_currentTextObj)->textDocument()->firstParag(); } else { if ( m_currentTextObj == m_lstObjects.begin() ) m_currentParag = 0L; // done else { --m_currentTextObj; m_currentParag = (*m_currentTextObj)->textDocument()->lastParag(); } } } // loop in case this new textobject is not visible while ( m_currentParag && !(*m_currentTextObj)->isVisible() ); #ifdef DEBUG_ITERATOR if ( m_currentParag ) kdDebug(32500) << k_funcinfo << " m_currentTextObj=" << (*m_currentTextObj) << endl; #endif } bool KoTextIterator::atEnd() const { // operator++ sets m_currentParag to 0 when it's done return m_currentParag == 0L; } int KoTextIterator::currentStartIndex() const { return currentTextAndIndex().first; } TQString KoTextIterator::currentText() const { return currentTextAndIndex().second; } TQPair<int, TQString> KoTextIterator::currentTextAndIndex() const { Q_ASSERT( m_currentParag ); Q_ASSERT( m_currentParag->string() ); TQString str = m_currentParag->string()->toString(); str.truncate( str.length() - 1 ); // remove trailing space bool forw = ! ( m_options & KFindDialog::FindBackwards ); if ( m_currentParag == m_firstParag ) { if ( m_firstParag == m_lastParag ) // special case, needs truncating at both ends return forw ? tqMakePair( m_firstIndex, str.mid( m_firstIndex, m_lastIndex - m_firstIndex ) ) : tqMakePair( m_lastIndex, str.mid( m_lastIndex, m_firstIndex - m_lastIndex ) ); else return forw ? tqMakePair( m_firstIndex, str.mid( m_firstIndex ) ) : tqMakePair( 0, str.left( m_firstIndex ) ); } if ( m_currentParag == m_lastParag ) { return forw ? tqMakePair( 0, str.left( m_lastIndex ) ) : tqMakePair( m_lastIndex, str.mid( m_lastIndex ) ); } // Not the first parag, nor the last, so we return it all return tqMakePair( 0, str ); } bool KoTextIterator::hasText() const { // Same logic as currentTextAndIndex, but w/o calling it, to avoid all the string copying bool forw = ! ( m_options & KFindDialog::FindBackwards ); int strLength = m_currentParag->string()->length() - 1; if ( m_currentParag == m_firstParag ) { if ( m_firstParag == m_lastParag ) return m_firstIndex < m_lastIndex; else return forw ? m_firstIndex < strLength : m_firstIndex > 0; } if ( m_currentParag == m_lastParag ) return forw ? m_lastIndex > 0 : m_lastIndex < strLength; return strLength > 0; } void KoTextIterator::setOptions( int options ) { if ( m_options != options ) { bool wasBack = (m_options & KFindDialog::FindBackwards); bool isBack = (options & KFindDialog::FindBackwards); if ( wasBack != isBack ) { tqSwap( m_firstParag, m_lastParag ); tqSwap( m_firstIndex, m_lastIndex ); if ( m_currentParag == 0 ) // done? -> reinit { #ifdef DEBUG_ITERATOR kdDebug(32500) << k_funcinfo << "was done -> reinit" << endl; #endif restart(); } } bool wasFromCursor = (m_options & KFindDialog::FromCursor); bool isFromCursor = (options & KFindDialog::FromCursor); // We can only handle the case where fromcursor got removed. // If it got added, then we need a textview to take the cursor position from... if ( wasFromCursor && !isFromCursor ) { // We also can't handle the "selected text" option here // It's very hard to have a cursor that's not at the beginning // or end of the selection, anyway. if ( ! (options & KFindDialog::SelectedText ) ) { // Set m_firstParag/m_firstIndex to the beginning of the first object // (end of last object when going backwards) KoTextParag* firstParag = m_lstObjects.first()->textDocument()->firstParag(); int firstIndex = 0; KoTextParag* lastParag = m_lstObjects.last()->textDocument()->lastParag(); int lastIndex = lastParag->length()-1; m_firstParag = (!isBack) ? firstParag : lastParag; m_firstIndex = (!isBack) ? firstIndex : lastIndex; #ifdef DEBUG_ITERATOR kdDebug(32500) << "setOptions: FromCursor removed. New m_firstParag=" << m_firstParag << " (" << m_firstParag->paragId() << ") isBack=" << isBack << endl; #endif } } m_options = options; } } #include "KoTextIterator.moc"