|
|
|
/* 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), TQ_SIGNAL( paragraphDeleted( KoTextParag* ) ),
|
|
|
|
this, TQ_SLOT( slotParagraphDeleted( KoTextParag* ) ) );
|
|
|
|
connect( (*it), TQ_SIGNAL( paragraphModified( KoTextParag*, int, int, int ) ),
|
|
|
|
this, TQ_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 ? qMakePair( m_firstIndex, str.mid( m_firstIndex, m_lastIndex - m_firstIndex ) )
|
|
|
|
: qMakePair( m_lastIndex, str.mid( m_lastIndex, m_firstIndex - m_lastIndex ) );
|
|
|
|
else
|
|
|
|
return forw ? qMakePair( m_firstIndex, str.mid( m_firstIndex ) )
|
|
|
|
: qMakePair( 0, str.left( m_firstIndex ) );
|
|
|
|
}
|
|
|
|
if ( m_currentParag == m_lastParag )
|
|
|
|
{
|
|
|
|
return forw ? qMakePair( 0, str.left( m_lastIndex ) )
|
|
|
|
: qMakePair( m_lastIndex, str.mid( m_lastIndex ) );
|
|
|
|
}
|
|
|
|
// Not the first parag, nor the last, so we return it all
|
|
|
|
return qMakePair( 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"
|