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