kutils Library API Documentation

kfind.cpp

00001 /*
00002     Copyright (C) 2001, S.R.Haque <srhaque@iee.org>.
00003     Copyright (C) 2002, David Faure <david@mandrakesoft.com>
00004     This file is part of the KDE project
00005 
00006     This library is free software; you can redistribute it and/or
00007     modify it under the terms of the GNU Library General Public
00008     License version 2, as published by the Free Software Foundation.
00009 
00010     This library is distributed in the hope that it will be useful,
00011     but WITHOUT ANY WARRANTY; without even the implied warranty of
00012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013     Library General Public License for more details.
00014 
00015     You should have received a copy of the GNU Library General Public License
00016     along with this library; see the file COPYING.LIB.  If not, write to
00017     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00018     Boston, MA 02111-1307, USA.
00019 */
00020 
00021 #include "kfind.h"
00022 #include "kfinddialog.h"
00023 #include <kapplication.h>
00024 #include <klocale.h>
00025 #include <kmessagebox.h>
00026 #include <qlabel.h>
00027 #include <qregexp.h>
00028 #include <qstylesheet.h>
00029 #include <qguardedptr.h>
00030 #include <kdebug.h>
00031 
00032 //#define DEBUG_FIND
00033 
00034 #define INDEX_NOMATCH -1
00035 
00036 class KFindNextDialog : public KDialogBase
00037 {
00038 public:
00039     KFindNextDialog(const QString &pattern, QWidget *parent);
00040 };
00041 
00042 // Create the dialog.
00043 KFindNextDialog::KFindNextDialog(const QString &pattern, QWidget *parent) :
00044     KDialogBase(parent, 0, false,  // non-modal!
00045         i18n("Find Next"),
00046         User1 | Close,
00047         User1,
00048         false,
00049         i18n("&Find"))
00050 {
00051     setMainWidget( new QLabel( i18n("<qt>Find next occurrence of '<b>%1</b>'?</qt>").arg(pattern), this ) );
00052 }
00053 
00055 
00056 struct KFind::Private {
00057   Private() {
00058      findDialog = 0;
00059   }
00060   QGuardedPtr<QWidget> findDialog;
00061 };
00063 
00064 KFind::KFind( const QString &pattern, long options, QWidget *parent )
00065     : QObject( parent )
00066 {
00067     d = new KFind::Private;
00068     m_options = options;
00069     init( pattern );
00070 }
00071 
00072 KFind::KFind( const QString &pattern, long options, QWidget *parent, QWidget *findDialog )
00073     : QObject( parent )
00074 {
00075     d = new KFind::Private;
00076     d->findDialog = findDialog;
00077     m_options = options;
00078     init( pattern );
00079 }
00080 
00081 void KFind::init( const QString& pattern )
00082 {
00083     m_matches = 0;
00084     m_pattern = pattern;
00085     m_dialog = 0;
00086     m_dialogClosed = false;
00087     m_index = INDEX_NOMATCH;
00088     m_lastResult = NoMatch;
00089     if (m_options & KFindDialog::RegularExpression)
00090         m_regExp = new QRegExp(pattern, m_options & KFindDialog::CaseSensitive);
00091     else {
00092         m_regExp = 0;
00093     }
00094 }
00095 
00096 KFind::~KFind()
00097 {
00098     delete m_dialog;
00099     delete d;
00100 }
00101 
00102 bool KFind::needData() const
00103 {
00104     // always true when m_text is empty.
00105     if (m_options & KFindDialog::FindBackwards)
00106         // m_index==-1 and m_lastResult==Match means we haven't answered nomatch yet
00107         // This is important in the "replace with a prompt" case.
00108         return ( m_index < 0 && m_lastResult != Match );
00109     else
00110         // "index over length" test removed: we want to get a nomatch before we set data again
00111         // This is important in the "replace with a prompt" case.
00112         return m_index == INDEX_NOMATCH;
00113 }
00114 
00115 void KFind::setData( const QString& data, int startPos )
00116 {
00117     m_text = data;
00118     if ( startPos != -1 )
00119         m_index = startPos;
00120     else if (m_options & KFindDialog::FindBackwards)
00121         m_index = QMAX( (int)m_text.length() - 1, 0 );
00122     else
00123         m_index = 0;
00124 #ifdef DEBUG_FIND
00125     kdDebug() << "setData: '" << m_text << "' m_index=" << m_index << endl;
00126 #endif
00127     Q_ASSERT( m_index != INDEX_NOMATCH );
00128     m_lastResult = NoMatch;
00129 }
00130 
00131 KDialogBase* KFind::findNextDialog( bool create )
00132 {
00133     if ( !m_dialog && create )
00134     {
00135         m_dialog = new KFindNextDialog( m_pattern, parentWidget() );
00136         connect( m_dialog, SIGNAL( user1Clicked() ), this, SLOT( slotFindNext() ) );
00137         connect( m_dialog, SIGNAL( finished() ), this, SLOT( slotDialogClosed() ) );
00138     }
00139     return m_dialog;
00140 }
00141 
00142 KFind::Result KFind::find()
00143 {
00144     Q_ASSERT( m_index != INDEX_NOMATCH );
00145     if ( m_lastResult == Match )
00146     {
00147         // Move on before looking for the next match, _if_ we just found a match
00148         if (m_options & KFindDialog::FindBackwards) {
00149             m_index--;
00150             if ( m_index == -1 ) // don't call KFind::find with -1, it has a special meaning
00151             {
00152                 m_lastResult = NoMatch;
00153                 return NoMatch;
00154             }
00155         } else
00156             m_index++;
00157     }
00158 
00159 #ifdef DEBUG_FIND
00160     kdDebug() << k_funcinfo << "m_index=" << m_index << endl;
00161 #endif
00162     do // this loop is only because validateMatch can fail
00163     {
00164         // Find the next candidate match.
00165         if ( m_options & KFindDialog::RegularExpression )
00166             m_index = KFind::find(m_text, *m_regExp, m_index, m_options, &m_matchedLength);
00167         else
00168             m_index = KFind::find(m_text, m_pattern, m_index, m_options, &m_matchedLength);
00169         if ( m_index != -1 )
00170         {
00171             // Flexibility: the app can add more rules to validate a possible match
00172             if ( validateMatch( m_text, m_index, m_matchedLength ) )
00173             {
00174                 m_matches++;
00175                 // Tell the world about the match we found, in case someone wants to
00176                 // highlight it.
00177                 emit highlight(m_text, m_index, m_matchedLength);
00178 
00179                 if ( !m_dialogClosed )
00180                     findNextDialog(true)->show();
00181 
00182 #ifdef DEBUG_FIND
00183                 kdDebug() << k_funcinfo << "Match. Next m_index=" << m_index << endl;
00184 #endif
00185                 m_lastResult = Match;
00186                 return Match;
00187             }
00188             else // Skip match
00189                 if (m_options & KFindDialog::FindBackwards)
00190                     m_index--;
00191                 else
00192                     m_index++;
00193         } else
00194             m_index = INDEX_NOMATCH;
00195     }
00196     while (m_index != INDEX_NOMATCH);
00197 
00198 #ifdef DEBUG_FIND
00199     kdDebug() << k_funcinfo << "NoMatch. m_index=" << m_index << endl;
00200 #endif
00201     m_lastResult = NoMatch;
00202     return NoMatch;
00203 }
00204 
00205 // static
00206 int KFind::find(const QString &text, const QString &pattern, int index, long options, int *matchedLength)
00207 {
00208     // Handle regular expressions in the appropriate way.
00209     if (options & KFindDialog::RegularExpression)
00210     {
00211         QRegExp regExp(pattern, options & KFindDialog::CaseSensitive);
00212 
00213         return find(text, regExp, index, options, matchedLength);
00214     }
00215 
00216     bool caseSensitive = (options & KFindDialog::CaseSensitive);
00217 
00218     if (options & KFindDialog::WholeWordsOnly)
00219     {
00220         if (options & KFindDialog::FindBackwards)
00221         {
00222             // Backward search, until the beginning of the line...
00223             while (index >= 0)
00224             {
00225                 // ...find the next match.
00226                 index = text.findRev(pattern, index, caseSensitive);
00227                 if (index == -1)
00228                     break;
00229 
00230                 // Is the match delimited correctly?
00231                 *matchedLength = pattern.length();
00232                 if (isWholeWords(text, index, *matchedLength))
00233                     break;
00234                 index--;
00235             }
00236         }
00237         else
00238         {
00239             // Forward search, until the end of the line...
00240             while (index < (int)text.length())
00241             {
00242                 // ...find the next match.
00243                 index = text.find(pattern, index, caseSensitive);
00244                 if (index == -1)
00245                     break;
00246 
00247                 // Is the match delimited correctly?
00248                 *matchedLength = pattern.length();
00249                 if (isWholeWords(text, index, *matchedLength))
00250                     break;
00251                 index++;
00252             }
00253             if (index >= (int)text.length()) // end of line
00254                 index = -1; // not found
00255         }
00256     }
00257     else
00258     {
00259         // Non-whole-word search.
00260         if (options & KFindDialog::FindBackwards)
00261         {
00262             index = text.findRev(pattern, index, caseSensitive);
00263         }
00264         else
00265         {
00266             index = text.find(pattern, index, caseSensitive);
00267         }
00268         if (index != -1)
00269         {
00270             *matchedLength = pattern.length();
00271         }
00272     }
00273     return index;
00274 }
00275 
00276 // static
00277 int KFind::find(const QString &text, const QRegExp &pattern, int index, long options, int *matchedLength)
00278 {
00279     if (options & KFindDialog::WholeWordsOnly)
00280     {
00281         if (options & KFindDialog::FindBackwards)
00282         {
00283             // Backward search, until the beginning of the line...
00284             while (index >= 0)
00285             {
00286                 // ...find the next match.
00287                 index = text.findRev(pattern, index);
00288                 if (index == -1)
00289                     break;
00290 
00291                 // Is the match delimited correctly?
00292                 //pattern.match(text, index, matchedLength, false);
00293                 /*int pos =*/ pattern.search( text.mid(index) );
00294                 *matchedLength = pattern.matchedLength();
00295                 if (isWholeWords(text, index, *matchedLength))
00296                     break;
00297                 index--;
00298             }
00299         }
00300         else
00301         {
00302             // Forward search, until the end of the line...
00303             while (index < (int)text.length())
00304             {
00305                 // ...find the next match.
00306                 index = text.find(pattern, index);
00307                 if (index == -1)
00308                     break;
00309 
00310                 // Is the match delimited correctly?
00311                 //pattern.match(text, index, matchedLength, false);
00312                 /*int pos =*/ pattern.search( text.mid(index) );
00313                 *matchedLength = pattern.matchedLength();
00314                 if (isWholeWords(text, index, *matchedLength))
00315                     break;
00316                 index++;
00317             }
00318             if (index >= (int)text.length()) // end of line
00319                 index = -1; // not found
00320         }
00321     }
00322     else
00323     {
00324         // Non-whole-word search.
00325         if (options & KFindDialog::FindBackwards)
00326         {
00327             index = text.findRev(pattern, index);
00328         }
00329         else
00330         {
00331             index = text.find(pattern, index);
00332         }
00333         if (index != -1)
00334         {
00335             //pattern.match(text, index, matchedLength, false);
00336             /*int pos =*/ pattern.search( text.mid(index) );
00337             *matchedLength = pattern.matchedLength();
00338         }
00339     }
00340     return index;
00341 }
00342 
00343 bool KFind::isInWord(QChar ch)
00344 {
00345     return ch.isLetter() || ch.isDigit() || ch == '_';
00346 }
00347 
00348 bool KFind::isWholeWords(const QString &text, int starts, int matchedLength)
00349 {
00350     if ((starts == 0) || (!isInWord(text[starts - 1])))
00351     {
00352         int ends = starts + matchedLength;
00353 
00354         if ((ends == (int)text.length()) || (!isInWord(text[ends])))
00355             return true;
00356     }
00357     return false;
00358 }
00359 
00360 void KFind::slotFindNext()
00361 {
00362     emit findNext();
00363 }
00364 
00365 void KFind::slotDialogClosed()
00366 {
00367     emit dialogClosed();
00368     m_dialogClosed = true;
00369 }
00370 
00371 void KFind::displayFinalDialog() const
00372 {
00373     QString message;
00374     if ( numMatches() )
00375         message = i18n( "1 match found.", "%n matches found.", numMatches() );
00376     else
00377         message = i18n("<qt>No matches found for '<b>%1</b>'.</qt>").arg(QStyleSheet::escape(m_pattern));
00378     KMessageBox::information(dialogsParent(), message);
00379 }
00380 
00381 bool KFind::shouldRestart( bool forceAsking, bool showNumMatches ) const
00382 {
00383     // Only ask if we did a "find from cursor", otherwise it's pointless.
00384     // Well, unless the user can modify the document during a search operation,
00385     // hence the force boolean.
00386     if ( !forceAsking && (m_options & KFindDialog::FromCursor) == 0 )
00387     {
00388         displayFinalDialog();
00389         return false;
00390     }
00391     QString message;
00392     if ( showNumMatches )
00393     {
00394         if ( numMatches() )
00395             message = i18n( "1 match found.", "%n matches found.", numMatches() );
00396         else
00397             message = i18n("No matches found for '<b>%1</b>'.").arg(QStyleSheet::escape(m_pattern));
00398     }
00399     else
00400     {
00401         if ( m_options & KFindDialog::FindBackwards )
00402             message = i18n( "Beginning of document reached." );
00403         else
00404             message = i18n( "End of document reached." );
00405     }
00406 
00407     message += "\n"; // can't be in the i18n() of the first if() because of the plural form.
00408     // Hope this word puzzle is ok, it's a different sentence
00409     message +=
00410         ( m_options & KFindDialog::FindBackwards ) ?
00411         i18n("Do you want to restart search from the end?")
00412         : i18n("Do you want to restart search at the beginning?");
00413 
00414     int ret = KMessageBox::questionYesNo( dialogsParent(), QString("<qt>")+message+QString("</qt>") );
00415     bool yes = ( ret == KMessageBox::Yes );
00416     if ( yes )
00417         const_cast<KFind*>(this)->m_options &= ~KFindDialog::FromCursor; // clear FromCursor option
00418     return yes;
00419 }
00420 
00421 void KFind::setOptions( long options )
00422 {
00423     m_options = options;
00424     if (m_options & KFindDialog::RegularExpression)
00425         m_regExp = new QRegExp(m_pattern, m_options & KFindDialog::CaseSensitive);
00426     else {
00427         delete m_regExp;
00428         m_regExp = 0;
00429     }
00430 }
00431 
00432 void KFind::closeFindNextDialog()
00433 {
00434     delete m_dialog;
00435     m_dialog = 0L;
00436     m_dialogClosed = true;
00437 }
00438 
00439 int KFind::index() const
00440 {
00441     return m_index;
00442 }
00443 
00444 void KFind::setPattern( const QString& pattern )
00445 {
00446     m_pattern = pattern;
00447     setOptions( options() ); // rebuild m_regExp if necessary
00448 }
00449 
00450 QWidget* KFind::dialogsParent() const
00451 {
00452     // If the find dialog is still up, it should get the focus when closing a message box
00453     // Otherwise, maybe the "find next?" dialog is up
00454     // Otherwise, the "view" is the parent.
00455     return d->findDialog ? (QWidget*)d->findDialog : ( m_dialog ? m_dialog : parentWidget() );
00456 }
00457 
00458 #include "kfind.moc"
KDE Logo
This file is part of the documentation for kutils Library Version 3.2.3.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Wed Aug 4 05:27:21 2004 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2003