• Skip to content
  • Skip to link menu
KDE 4.3 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

KDEUI

kfind.cpp

Go to the documentation of this file.
00001 /*
00002     Copyright (C) 2001, S.R.Haque <srhaque@iee.org>.
00003     Copyright (C) 2002, David Faure <david@mandrakesoft.com>
00004     Copyright (C) 2004, Arend van Beelen jr. <arend@auton.nl>
00005     This file is part of the KDE project
00006 
00007     This library is free software; you can redistribute it and/or
00008     modify it under the terms of the GNU Library General Public
00009     License version 2, as published by the Free Software Foundation.
00010 
00011     This library is distributed in the hope that it will be useful,
00012     but WITHOUT ANY WARRANTY; without even the implied warranty of
00013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014     Library General Public License for more details.
00015 
00016     You should have received a copy of the GNU Library General Public License
00017     along with this library; see the file COPYING.LIB.  If not, write to
00018     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019     Boston, MA 02110-1301, USA.
00020 */
00021 
00022 #include "kfind.h"
00023 #include "kfind_p.h"
00024 #include "kfinddialog.h"
00025 
00026 #include <kapplication.h>
00027 #include <klocale.h>
00028 #include <kmessagebox.h>
00029 #include <kdebug.h>
00030 
00031 #include <QtGui/QLabel>
00032 #include <QtCore/QRegExp>
00033 #include <QtCore/QHash>
00034 #include <QTextDocument>
00035 
00036 //#define DEBUG_FIND
00037 
00038 #define INDEX_NOMATCH -1
00039 
00040 class KFindNextDialog : public KDialog
00041 {
00042 public:
00043     KFindNextDialog(const QString &pattern, QWidget *parent);
00044 };
00045 
00046 // Create the dialog.
00047 KFindNextDialog::KFindNextDialog(const QString &pattern, QWidget *parent) :
00048     KDialog(parent)
00049 {
00050     setModal( false );
00051     setCaption( i18n("Find Next") );
00052     setButtons( User1 | Close );
00053     setButtonGuiItem( User1, KStandardGuiItem::find() );
00054     setDefaultButton( User1 );
00055     showButtonSeparator( false );
00056 
00057     setMainWidget( new QLabel( i18n("<qt>Find next occurrence of '<b>%1</b>'?</qt>", pattern), this ) );
00058 }
00059 
00061 
00062 
00063 KFind::KFind( const QString &pattern, long options, QWidget *parent )
00064     : QObject( parent ),
00065     d(new KFind::Private(this))
00066 {
00067     d->options = options;
00068     d->init( pattern );
00069 }
00070 
00071 KFind::KFind( const QString &pattern, long options, QWidget *parent, QWidget *findDialog )
00072     : QObject( parent ),
00073     d(new KFind::Private(this))
00074 {
00075     d->findDialog = findDialog;
00076     d->options = options;
00077     d->init( pattern );
00078 }
00079 
00080 void KFind::Private::init( const QString& _pattern )
00081 {
00082     matches = 0;
00083     pattern = _pattern;
00084     dialog = 0;
00085     dialogClosed = false;
00086     index = INDEX_NOMATCH;
00087     lastResult = NoMatch;
00088     regExp = 0;
00089     q->setOptions( options ); // create d->regExp with the right options
00090 }
00091 
00092 KFind::~KFind()
00093 {
00094     delete d;
00095     kDebug() ;
00096 }
00097 
00098 bool KFind::needData() const
00099 {
00100     // always true when d->text is empty.
00101     if (d->options & KFind::FindBackwards)
00102         // d->index==-1 and d->lastResult==Match means we haven't answered nomatch yet
00103         // This is important in the "replace with a prompt" case.
00104         return ( d->index < 0 && d->lastResult != Match );
00105     else
00106         // "index over length" test removed: we want to get a nomatch before we set data again
00107         // This is important in the "replace with a prompt" case.
00108         return d->index == INDEX_NOMATCH;
00109 }
00110 
00111 void KFind::setData( const QString& data, int startPos )
00112 {
00113     setData( -1, data, startPos );
00114 }
00115 
00116 void KFind::setData( int id, const QString& data, int startPos )
00117 {
00118     // cache the data for incremental find
00119     if ( d->options & KFind::FindIncremental )
00120     {
00121         if ( id != -1 )
00122             d->customIds = true;
00123         else
00124             id = d->currentId + 1;
00125 
00126         Q_ASSERT( id <= d->data.size() );
00127 
00128         if ( id == d->data.size() )
00129             d->data.append( Private::Data(id, data, true) );
00130         else
00131             d->data.replace( id, Private::Data(id, data, true) );
00132         Q_ASSERT( d->data.at(id).text == data );
00133     }
00134 
00135     if ( !(d->options & KFind::FindIncremental) || needData() )
00136     {
00137         d->text = data;
00138 
00139         if ( startPos != -1 )
00140             d->index = startPos;
00141         else if (d->options & KFind::FindBackwards)
00142             d->index = d->text.length();
00143         else
00144             d->index = 0;
00145 #ifdef DEBUG_FIND
00146         kDebug() << "setData: '" << d->text << "' d->index=" << d->index;
00147 #endif
00148         Q_ASSERT( d->index != INDEX_NOMATCH );
00149         d->lastResult = NoMatch;
00150 
00151         d->currentId = id;
00152     }
00153 }
00154 
00155 KDialog* KFind::findNextDialog( bool create )
00156 {
00157     if ( !d->dialog && create )
00158     {
00159         d->dialog = new KFindNextDialog( d->pattern, parentWidget() );
00160         connect( d->dialog, SIGNAL( user1Clicked() ), this, SLOT( _k_slotFindNext() ) );
00161         connect( d->dialog, SIGNAL( finished() ), this, SLOT( _k_slotDialogClosed() ) );
00162     }
00163     return d->dialog;
00164 }
00165 
00166 KFind::Result KFind::find()
00167 {
00168     Q_ASSERT( d->index != INDEX_NOMATCH || d->patternChanged );
00169 
00170     if ( d->lastResult == Match && !d->patternChanged )
00171     {
00172         // Move on before looking for the next match, _if_ we just found a match
00173         if (d->options & KFind::FindBackwards) {
00174             d->index--;
00175             if ( d->index == -1 ) // don't call KFind::find with -1, it has a special meaning
00176             {
00177                 d->lastResult = NoMatch;
00178                 return NoMatch;
00179             }
00180         } else
00181             d->index++;
00182     }
00183     d->patternChanged = false;
00184 
00185     if ( d->options & KFind::FindIncremental )
00186     {
00187         // if the current pattern is shorter than the matchedPattern we can
00188         // probably look up the match in the incrementalPath
00189         if ( d->pattern.length() < d->matchedPattern.length() )
00190         {
00191             Private::Match match;
00192             if ( !d->pattern.isEmpty() )
00193                 match = d->incrementalPath.value( d->pattern );
00194             else if ( d->emptyMatch )
00195                 match = *d->emptyMatch;
00196             QString previousPattern (d->matchedPattern);
00197             d->matchedPattern = d->pattern;
00198             if ( !match.isNull() )
00199             {
00200                 bool clean = true;
00201 
00202                 // find the first result backwards on the path that isn't dirty
00203                 while ( d->data.at(match.dataId).dirty == true &&
00204                         !d->pattern.isEmpty() )
00205                 {
00206                     d->pattern.truncate( d->pattern.length() - 1 );
00207 
00208                     match = d->incrementalPath.value( d->pattern );
00209 
00210                     clean = false;
00211                 }
00212 
00213                 // remove all matches that lie after the current match
00214                 while ( d->pattern.length() < previousPattern.length() )
00215                 {
00216                     d->incrementalPath.remove(previousPattern);
00217                     previousPattern.truncate(previousPattern.length() - 1);
00218                 }
00219 
00220                 // set the current text, index, etc. to the found match
00221                 d->text = d->data.at(match.dataId).text;
00222                 d->index = match.index;
00223                 d->matchedLength = match.matchedLength;
00224                 d->currentId = match.dataId;
00225 
00226                 // if the result is clean we can return it now
00227                 if ( clean )
00228                 {
00229                     if ( d->customIds )
00230                         emit highlight(d->currentId, d->index, d->matchedLength);
00231                     else
00232                         emit highlight(d->text, d->index, d->matchedLength);
00233 
00234                     d->lastResult = Match;
00235                     d->matchedPattern = d->pattern;
00236                     return Match;
00237                 }
00238             }
00239             // if we couldn't look up the match, the new pattern isn't a
00240             // substring of the matchedPattern, so we start a new search
00241             else
00242             {
00243                 d->startNewIncrementalSearch();
00244             }
00245         }
00246         // if the new pattern is longer than the matchedPattern we might be
00247         // able to proceed from the last search
00248         else if ( d->pattern.length() > d->matchedPattern.length() )
00249         {
00250             // continue from the previous pattern
00251             if ( d->pattern.startsWith(d->matchedPattern) )
00252             {
00253                 // we can't proceed from the previous position if the previous
00254                 // position already failed
00255                 if ( d->index == INDEX_NOMATCH )
00256                     return NoMatch;
00257 
00258                 QString temp (d->pattern);
00259                 d->pattern.truncate(d->matchedPattern.length() + 1);
00260                 d->matchedPattern = temp;
00261             }
00262             // start a new search
00263             else
00264             {
00265                 d->startNewIncrementalSearch();
00266             }
00267         }
00268         // if the new pattern is as long as the matchedPattern, we reset if
00269         // they are not equal
00270         else if ( d->pattern != d->matchedPattern )
00271         {
00272              d->startNewIncrementalSearch();
00273         }
00274     }
00275 
00276 #ifdef DEBUG_FIND
00277     kDebug() << "d->index=" << d->index;
00278 #endif
00279     do
00280     {
00281         // if we have multiple data blocks in our cache, walk through these
00282         // blocks till we either searched all blocks or we find a match
00283         do
00284         {
00285             // Find the next candidate match.
00286             if ( d->options & KFind::RegularExpression )
00287                 d->index = KFind::find(d->text, *d->regExp, d->index, d->options, &d->matchedLength);
00288             else
00289                 d->index = KFind::find(d->text, d->pattern, d->index, d->options, &d->matchedLength);
00290 
00291 
00292             if ( d->options & KFind::FindIncremental )
00293                 d->data[d->currentId].dirty = false;
00294 
00295             if ( d->index == -1 && d->currentId < (int) d->data.count() - 1 )
00296             {
00297                 d->text = d->data.at(++d->currentId).text;
00298 
00299                 if ( d->options & KFind::FindBackwards )
00300                     d->index = d->text.length();
00301                 else
00302                     d->index = 0;
00303             }
00304             else
00305                 break;
00306         } while ( !(d->options & KFind::RegularExpression) );
00307 
00308         if ( d->index != -1 )
00309         {
00310             // Flexibility: the app can add more rules to validate a possible match
00311             if ( validateMatch( d->text, d->index, d->matchedLength ) )
00312             {
00313                 bool done = true;
00314 
00315                 if ( d->options & KFind::FindIncremental )
00316                 {
00317                     if ( d->pattern.isEmpty() ) {
00318                         delete d->emptyMatch;
00319                         d->emptyMatch = new Private::Match( d->currentId, d->index, d->matchedLength );
00320                     } else
00321                         d->incrementalPath.insert(d->pattern, Private::Match(d->currentId, d->index, d->matchedLength));
00322 
00323                     if ( d->pattern.length() < d->matchedPattern.length() )
00324                     {
00325                         d->pattern += d->matchedPattern.mid(d->pattern.length(), 1);
00326                         done = false;
00327                     }
00328                 }
00329 
00330                 if ( done )
00331                 {
00332                     d->matches++;
00333                     // Tell the world about the match we found, in case someone wants to
00334                     // highlight it.
00335                     if ( d->customIds )
00336                         emit highlight(d->currentId, d->index, d->matchedLength);
00337                     else
00338                         emit highlight(d->text, d->index, d->matchedLength);
00339 
00340                     if ( !d->dialogClosed )
00341                         findNextDialog(true)->show();
00342 
00343 #ifdef DEBUG_FIND
00344                     kDebug() << "Match. Next d->index=" << d->index;
00345 #endif
00346                     d->lastResult = Match;
00347                     return Match;
00348                 }
00349             }
00350             else // Skip match
00351             {
00352                 if (d->options & KFind::FindBackwards)
00353                     d->index--;
00354                 else
00355                     d->index++;
00356             }
00357         }
00358         else
00359         {
00360             if ( d->options & KFind::FindIncremental )
00361             {
00362                 QString temp (d->pattern);
00363                 temp.truncate(temp.length() - 1);
00364                 d->pattern = d->matchedPattern;
00365                 d->matchedPattern = temp;
00366             }
00367 
00368             d->index = INDEX_NOMATCH;
00369         }
00370     }
00371     while (d->index != INDEX_NOMATCH);
00372 
00373 #ifdef DEBUG_FIND
00374     kDebug() << "NoMatch. d->index=" << d->index;
00375 #endif
00376     d->lastResult = NoMatch;
00377     return NoMatch;
00378 }
00379 
00380 void KFind::Private::startNewIncrementalSearch()
00381 {
00382     Private::Match *match = emptyMatch;
00383     if(match == 0)
00384     {
00385         text.clear();
00386         index = 0;
00387         currentId = 0;
00388     }
00389     else
00390     {
00391         text = data.at(match->dataId).text;
00392         index = match->index;
00393         currentId = match->dataId;
00394     }
00395     matchedLength = 0;
00396     incrementalPath.clear();
00397     delete emptyMatch; emptyMatch = 0;
00398     matchedPattern = pattern;
00399     pattern.clear();
00400 }
00401 
00402 // static
00403 int KFind::find(const QString &text, const QString &pattern, int index, long options, int *matchedLength)
00404 {
00405     // Handle regular expressions in the appropriate way.
00406     if (options & KFind::RegularExpression)
00407     {
00408         Qt::CaseSensitivity caseSensitive = (options & KFind::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive;
00409         QRegExp regExp(pattern, caseSensitive);
00410 
00411         return find(text, regExp, index, options, matchedLength);
00412     }
00413 
00414     // In Qt4 QString("aaaaaa").lastIndexOf("a",6) returns -1; we need
00415     // to start at text.length() - pattern.length() to give a valid index to QString.
00416     if (options & KFind::FindBackwards)
00417         index = qMin( text.length() - pattern.length(), index );
00418 
00419     Qt::CaseSensitivity caseSensitive = (options & KFind::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive;
00420 
00421     if (options & KFind::WholeWordsOnly)
00422     {
00423         if (options & KFind::FindBackwards)
00424         {
00425             // Backward search, until the beginning of the line...
00426             while (index >= 0)
00427             {
00428                 // ...find the next match.
00429                 index = text.lastIndexOf(pattern, index, caseSensitive);
00430                 if (index == -1)
00431                     break;
00432 
00433                 // Is the match delimited correctly?
00434                 *matchedLength = pattern.length();
00435                 if (Private::isWholeWords(text, index, *matchedLength))
00436                     break;
00437                 index--;
00438             }
00439         }
00440         else
00441         {
00442             // Forward search, until the end of the line...
00443             while (index < (int)text.length())
00444             {
00445                 // ...find the next match.
00446                 index = text.indexOf(pattern, index, caseSensitive);
00447                 if (index == -1)
00448                     break;
00449 
00450                 // Is the match delimited correctly?
00451                 *matchedLength = pattern.length();
00452                 if (Private::isWholeWords(text, index, *matchedLength))
00453                     break;
00454                 index++;
00455             }
00456             if (index >= (int)text.length()) // end of line
00457                 index = -1; // not found
00458         }
00459     }
00460     else
00461     {
00462         // Non-whole-word search.
00463         if (options & KFind::FindBackwards)
00464         {
00465             index = text.lastIndexOf(pattern, index, caseSensitive);
00466         }
00467         else
00468         {
00469             index = text.indexOf(pattern, index, caseSensitive);
00470         }
00471         if (index != -1)
00472         {
00473             *matchedLength = pattern.length();
00474         }
00475     }
00476     return index;
00477 }
00478 
00479 // static
00480 int KFind::find(const QString &text, const QRegExp &pattern, int index, long options, int *matchedLength)
00481 {
00482     if (options & KFind::WholeWordsOnly)
00483     {
00484         if (options & KFind::FindBackwards)
00485         {
00486             // Backward search, until the beginning of the line...
00487             while (index >= 0)
00488             {
00489                 // ...find the next match.
00490                 index = text.lastIndexOf(pattern, index);
00491                 if (index == -1)
00492                     break;
00493 
00494                 // Is the match delimited correctly?
00495                 //pattern.match(text, index, matchedLength, false);
00496                 /*int pos =*/ pattern.indexIn( text.mid(index) );
00497                 *matchedLength = pattern.matchedLength();
00498                 if (Private::isWholeWords(text, index, *matchedLength))
00499                     break;
00500                 index--;
00501             }
00502         }
00503         else
00504         {
00505             // Forward search, until the end of the line...
00506             while (index < (int)text.length())
00507             {
00508                 // ...find the next match.
00509                 index = text.indexOf(pattern, index);
00510                 if (index == -1)
00511                     break;
00512 
00513                 // Is the match delimited correctly?
00514                 //pattern.match(text, index, matchedLength, false);
00515                 /*int pos =*/ pattern.indexIn( text.mid(index) );
00516                 *matchedLength = pattern.matchedLength();
00517                 if (Private::isWholeWords(text, index, *matchedLength))
00518                     break;
00519                 index++;
00520             }
00521             if (index >= (int)text.length()) // end of line
00522                 index = -1; // not found
00523         }
00524     }
00525     else
00526     {
00527         // Non-whole-word search.
00528         if (options & KFind::FindBackwards)
00529         {
00530             index = text.lastIndexOf(pattern, index);
00531         }
00532         else
00533         {
00534             index = text.indexOf(pattern, index);
00535         }
00536         if (index != -1)
00537         {
00538             //pattern.match(text, index, matchedLength, false);
00539             /*int pos =*/ pattern.indexIn( text.mid(index) );
00540             *matchedLength = pattern.matchedLength();
00541         }
00542     }
00543     return index;
00544 }
00545 
00546 bool KFind::Private::isInWord(QChar ch)
00547 {
00548     return ch.isLetter() || ch.isDigit() || ch == '_';
00549 }
00550 
00551 bool KFind::Private::isWholeWords(const QString &text, int starts, int matchedLength)
00552 {
00553     if ((starts == 0) || (!isInWord(text.at(starts-1))))
00554     {
00555         int ends = starts + matchedLength;
00556 
00557         if ((ends == (int)text.length()) || (!isInWord(text.at(ends))))
00558             return true;
00559     }
00560     return false;
00561 }
00562 
00563 void KFind::Private::_k_slotFindNext()
00564 {
00565     emit q->findNext();
00566 }
00567 
00568 void KFind::Private::_k_slotDialogClosed()
00569 {
00570 #ifdef DEBUG_FIND
00571     kDebug() << " Begin";
00572 #endif
00573     emit q->dialogClosed();
00574     dialogClosed = true;
00575 #ifdef DEBUG_FIND
00576     kDebug() << " End";
00577 #endif
00578 
00579 }
00580 
00581 void KFind::displayFinalDialog() const
00582 {
00583     QString message;
00584     if ( numMatches() )
00585         message = i18np( "1 match found.", "%1 matches found.", numMatches() );
00586     else
00587         message = i18n("<qt>No matches found for '<b>%1</b>'.</qt>", Qt::escape(d->pattern));
00588     KMessageBox::information(dialogsParent(), message);
00589 }
00590 
00591 bool KFind::shouldRestart( bool forceAsking, bool showNumMatches ) const
00592 {
00593     // Only ask if we did a "find from cursor", otherwise it's pointless.
00594     // Well, unless the user can modify the document during a search operation,
00595     // hence the force boolean.
00596     if ( !forceAsking && (d->options & KFind::FromCursor) == 0 )
00597     {
00598         displayFinalDialog();
00599         return false;
00600     }
00601     QString message;
00602     if ( showNumMatches )
00603     {
00604         if ( numMatches() )
00605             message = i18np( "1 match found.", "%1 matches found.", numMatches() );
00606         else
00607             message = i18n("No matches found for '<b>%1</b>'.", Qt::escape(d->pattern));
00608     }
00609     else
00610     {
00611         if ( d->options & KFind::FindBackwards )
00612             message = i18n( "Beginning of document reached." );
00613         else
00614             message = i18n( "End of document reached." );
00615     }
00616 
00617     message += "<br><br>"; // can't be in the i18n() of the first if() because of the plural form.
00618     // Hope this word puzzle is ok, it's a different sentence
00619     message +=
00620         ( d->options & KFind::FindBackwards ) ?
00621         i18n("Continue from the end?")
00622         : i18n("Continue from the beginning?");
00623 
00624     int ret = KMessageBox::questionYesNo( dialogsParent(), "<qt>"+message+"</qt>",
00625                                           QString(), KStandardGuiItem::cont(), KStandardGuiItem::stop() );
00626     bool yes = ( ret == KMessageBox::Yes );
00627     if ( yes )
00628         const_cast<KFind*>(this)->d->options &= ~KFind::FromCursor; // clear FromCursor option
00629     return yes;
00630 }
00631 
00632 long KFind::options() const
00633 {
00634     return d->options;
00635 }
00636 
00637 void KFind::setOptions( long options )
00638 {
00639     d->options = options;
00640 
00641     delete d->regExp;
00642     if (d->options & KFind::RegularExpression) {
00643         Qt::CaseSensitivity caseSensitive = (d->options & KFind::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive;
00644         d->regExp = new QRegExp(d->pattern, caseSensitive);
00645     } else
00646         d->regExp = 0;
00647 }
00648 
00649 void KFind::closeFindNextDialog()
00650 {
00651     if (d->dialog) {
00652         d->dialog->deleteLater();
00653         d->dialog = 0;
00654     }
00655     d->dialogClosed = true;
00656 }
00657 
00658 int KFind::index() const
00659 {
00660     return d->index;
00661 }
00662 
00663 QString KFind::pattern() const
00664 {
00665     return d->pattern;
00666 }
00667 
00668 void KFind::setPattern( const QString& pattern )
00669 {
00670     if ( d->options & KFind::FindIncremental && d->pattern != pattern )
00671         d->patternChanged = true;
00672 
00673     d->pattern = pattern;
00674     setOptions( options() ); // rebuild d->regExp if necessary
00675 }
00676 
00677 int KFind::numMatches() const
00678 {
00679     return d->matches;
00680 }
00681 
00682 void KFind::resetCounts()
00683 {
00684     d->matches = 0;
00685 }
00686 
00687 bool KFind::validateMatch( const QString &, int, int )
00688 {
00689     return true;
00690 }
00691 
00692 QWidget* KFind::parentWidget() const
00693 {
00694     return (QWidget *)parent();
00695 }
00696 
00697 QWidget* KFind::dialogsParent() const
00698 {
00699     // If the find dialog is still up, it should get the focus when closing a message box
00700     // Otherwise, maybe the "find next?" dialog is up
00701     // Otherwise, the "view" is the parent.
00702     return d->findDialog ? (QWidget*)d->findDialog : ( d->dialog ? d->dialog : parentWidget() );
00703 }
00704 
00705 #include "kfind.moc"

KDEUI

Skip menu "KDEUI"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.6.1
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal