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 <kdebug.h>
00029 
00030 //#define DEBUG_FIND
00031 
00032 #define INDEX_NOMATCH -1
00033 
00034 class KFindNextDialog : public KDialogBase
00035 {
00036 public:
00037     KFindNextDialog(const QString &pattern, QWidget *parent);
00038 };
00039 
00040 // Create the dialog.
00041 KFindNextDialog::KFindNextDialog(const QString &pattern, QWidget *parent) :
00042     KDialogBase(parent, 0, false,  // non-modal!
00043         i18n("Find"),
00044         User1 | Close,
00045         User1,
00046         false,
00047         i18n("&Yes"))
00048 {
00049     setMainWidget( new QLabel( i18n("<qt>Find next occurrence of '<b>%1</b>'?</qt>").arg(pattern), this ) );
00050 }
00051 
00053 
00054 KFind::KFind( const QString &pattern, long options, QWidget *parent )
00055     : QObject( parent )
00056 {
00057     m_options = options;
00058     init( pattern );
00059 }
00060 
00061 void KFind::init( const QString& pattern )
00062 {
00063     m_matches = 0;
00064     m_pattern = pattern;
00065     m_dialog = 0;
00066     m_dialogClosed = false;
00067     m_index = INDEX_NOMATCH;
00068     m_lastResult = NoMatch;
00069     if (m_options & KFindDialog::RegularExpression)
00070         m_regExp = new QRegExp(pattern, m_options & KFindDialog::CaseSensitive);
00071     else {
00072         m_regExp = 0;
00073     }
00074 }
00075 
00076 KFind::~KFind()
00077 {
00078     delete m_dialog;
00079 }
00080 
00081 bool KFind::needData() const
00082 {
00083     // always true when m_text is empty.
00084     if (m_options & KFindDialog::FindBackwards)
00085         return m_index < 0;
00086     else
00087         return m_index >= (int)m_text.length() || m_index == INDEX_NOMATCH;
00088 }
00089 
00090 void KFind::setData( const QString& data, int startPos )
00091 {
00092     m_text = data;
00093     if ( startPos != -1 )
00094         m_index = startPos;
00095     else if (m_options & KFindDialog::FindBackwards)
00096         m_index = QMAX( (int)m_text.length() - 1, 0 );
00097     else
00098         m_index = 0;
00099 #ifdef DEBUG_FIND
00100     kdDebug() << "setData: '" << m_text << "' m_index=" << m_index << endl;
00101 #endif
00102     Q_ASSERT( m_index != INDEX_NOMATCH );
00103     m_lastResult = NoMatch;
00104 }
00105 
00106 KDialogBase* KFind::findNextDialog( bool create )
00107 {
00108     if ( !m_dialog && create )
00109     {
00110         m_dialog = new KFindNextDialog( m_pattern, parentWidget() );
00111         connect( m_dialog, SIGNAL( user1Clicked() ), this, SLOT( slotFindNext() ) );
00112         connect( m_dialog, SIGNAL( finished() ), this, SLOT( slotDialogClosed() ) );
00113     }
00114     return m_dialog;
00115 }
00116 
00117 KFind::Result KFind::find()
00118 {
00119     Q_ASSERT( m_index != INDEX_NOMATCH );
00120     if ( m_lastResult == Match )
00121     {
00122         // Move on before looking for the next match, _if_ we just found a match
00123         if (m_options & KFindDialog::FindBackwards) {
00124             m_index--;
00125             if ( m_index == -1 ) // don't call KFind::find with -1, it has a special meaning
00126             {
00127                 m_lastResult = NoMatch;
00128                 return NoMatch;
00129             }
00130         } else
00131             m_index++;
00132     }
00133 
00134 #ifdef DEBUG_FIND
00135     kdDebug() << k_funcinfo << "m_index=" << m_index << endl;
00136 #endif
00137     do // this loop is only because validateMatch can fail
00138     {
00139         // Find the next candidate match.
00140         if ( m_options & KFindDialog::RegularExpression )
00141             m_index = KFind::find(m_text, *m_regExp, m_index, m_options, &m_matchedLength);
00142         else
00143             m_index = KFind::find(m_text, m_pattern, m_index, m_options, &m_matchedLength);
00144         if ( m_index != -1 )
00145         {
00146             // Flexibility: the app can add more rules to validate a possible match
00147             if ( validateMatch( m_text, m_index, m_matchedLength ) )
00148             {
00149                 m_matches++;
00150                 // Tell the world about the match we found, in case someone wants to
00151                 // highlight it.
00152                 emit highlight(m_text, m_index, m_matchedLength);
00153 
00154                 if ( !m_dialogClosed )
00155                     findNextDialog(true)->show();
00156 
00157 #ifdef DEBUG_FIND
00158                 kdDebug() << k_funcinfo << "Match. Next m_index=" << m_index << endl;
00159 #endif
00160                 m_lastResult = Match;
00161                 return Match;
00162             }
00163             else // Skip match
00164                 if (m_options & KFindDialog::FindBackwards)
00165                     m_index--;
00166                 else
00167                     m_index++;
00168         } else
00169             m_index = INDEX_NOMATCH;
00170     }
00171     while (m_index != INDEX_NOMATCH);
00172 
00173 #ifdef DEBUG_FIND
00174     kdDebug() << k_funcinfo << "NoMatch. m_index=" << m_index << endl;
00175 #endif
00176     m_lastResult = NoMatch;
00177     return NoMatch;
00178 }
00179 
00180 // static
00181 int KFind::find(const QString &text, const QString &pattern, int index, long options, int *matchedLength)
00182 {
00183     // Handle regular expressions in the appropriate way.
00184     if (options & KFindDialog::RegularExpression)
00185     {
00186         QRegExp regExp(pattern, options & KFindDialog::CaseSensitive);
00187 
00188         return find(text, regExp, index, options, matchedLength);
00189     }
00190 
00191     bool caseSensitive = (options & KFindDialog::CaseSensitive);
00192 
00193     if (options & KFindDialog::WholeWordsOnly)
00194     {
00195         if (options & KFindDialog::FindBackwards)
00196         {
00197             // Backward search, until the beginning of the line...
00198             while (index >= 0)
00199             {
00200                 // ...find the next match.
00201                 index = text.findRev(pattern, index, caseSensitive);
00202                 if (index == -1)
00203                     break;
00204 
00205                 // Is the match delimited correctly?
00206                 *matchedLength = pattern.length();
00207                 if (isWholeWords(text, index, *matchedLength))
00208                     break;
00209                 index--;
00210             }
00211         }
00212         else
00213         {
00214             // Forward search, until the end of the line...
00215             while (index < (int)text.length())
00216             {
00217                 // ...find the next match.
00218                 index = text.find(pattern, index, caseSensitive);
00219                 if (index == -1)
00220                     break;
00221 
00222                 // Is the match delimited correctly?
00223                 *matchedLength = pattern.length();
00224                 if (isWholeWords(text, index, *matchedLength))
00225                     break;
00226                 index++;
00227             }
00228             if (index >= (int)text.length()) // end of line
00229                 index = -1; // not found
00230         }
00231     }
00232     else
00233     {
00234         // Non-whole-word search.
00235         if (options & KFindDialog::FindBackwards)
00236         {
00237             index = text.findRev(pattern, index, caseSensitive);
00238         }
00239         else
00240         {
00241             index = text.find(pattern, index, caseSensitive);
00242         }
00243         if (index != -1)
00244         {
00245             *matchedLength = pattern.length();
00246         }
00247     }
00248     return index;
00249 }
00250 
00251 // static
00252 int KFind::find(const QString &text, const QRegExp &pattern, int index, long options, int *matchedLength)
00253 {
00254     if (options & KFindDialog::WholeWordsOnly)
00255     {
00256         if (options & KFindDialog::FindBackwards)
00257         {
00258             // Backward search, until the beginning of the line...
00259             while (index >= 0)
00260             {
00261                 // ...find the next match.
00262                 index = text.findRev(pattern, index);
00263                 if (index == -1)
00264                     break;
00265 
00266                 // Is the match delimited correctly?
00267                 //pattern.match(text, index, matchedLength, false);
00268                 /*int pos =*/ pattern.search( text.mid(index) );
00269                 *matchedLength = pattern.matchedLength();
00270                 if (isWholeWords(text, index, *matchedLength))
00271                     break;
00272                 index--;
00273             }
00274         }
00275         else
00276         {
00277             // Forward search, until the end of the line...
00278             while (index < (int)text.length())
00279             {
00280                 // ...find the next match.
00281                 index = text.find(pattern, index);
00282                 if (index == -1)
00283                     break;
00284 
00285                 // Is the match delimited correctly?
00286                 //pattern.match(text, index, matchedLength, false);
00287                 /*int pos =*/ pattern.search( text.mid(index) );
00288                 *matchedLength = pattern.matchedLength();
00289                 if (isWholeWords(text, index, *matchedLength))
00290                     break;
00291                 index++;
00292             }
00293             if (index >= (int)text.length()) // end of line
00294                 index = -1; // not found
00295         }
00296     }
00297     else
00298     {
00299         // Non-whole-word search.
00300         if (options & KFindDialog::FindBackwards)
00301         {
00302             index = text.findRev(pattern, index);
00303         }
00304         else
00305         {
00306             index = text.find(pattern, index);
00307         }
00308         if (index != -1)
00309         {
00310             //pattern.match(text, index, matchedLength, false);
00311             /*int pos =*/ pattern.search( text.mid(index) );
00312             *matchedLength = pattern.matchedLength();
00313         }
00314     }
00315     return index;
00316 }
00317 
00318 bool KFind::isInWord(QChar ch)
00319 {
00320     return ch.isLetter() || ch.isDigit() || ch == '_';
00321 }
00322 
00323 bool KFind::isWholeWords(const QString &text, int starts, int matchedLength)
00324 {
00325     if ((starts == 0) || (!isInWord(text[starts - 1])))
00326     {
00327         int ends = starts + matchedLength;
00328 
00329         if ((ends == (int)text.length()) || (!isInWord(text[ends])))
00330             return true;
00331     }
00332     return false;
00333 }
00334 
00335 void KFind::slotFindNext()
00336 {
00337     emit findNext();
00338 }
00339 
00340 void KFind::slotDialogClosed()
00341 {
00342     emit dialogClosed();
00343     m_dialogClosed = true;
00344 }
00345 
00346 void KFind::displayFinalDialog() const
00347 {
00348     QString message;
00349     if ( numMatches() )
00350         message = i18n( "1 match found.", "%n matches found.", numMatches() );
00351     else
00352         message = i18n("<qt>No matches found for '<b>%1</b>'.</qt>").arg(m_pattern);
00353     KMessageBox::information(parentWidget(), message);
00354 }
00355 
00356 bool KFind::shouldRestart( bool forceAsking, bool /*showNumMatches*/ ) const
00357 {
00358     // Only ask if we did a "find from cursor", otherwise it's pointless.
00359     // Well, unless the user can modify the document during a search operation,
00360     // hence the force boolean.
00361     if ( !forceAsking && (m_options & KFindDialog::FromCursor) == 0 )
00362     {
00363         displayFinalDialog();
00364         return false;
00365     }
00368     QString message;
00369     if ( m_options & KFindDialog::FindBackwards )
00370         message = i18n( "Beginning of document reached.\n"\
00371                         "Continue from the end?" );
00372     else
00373         message = i18n( "End of document reached.\n"\
00374                         "Continue from the beginning?" );
00375 
00376     int ret = KMessageBox::questionYesNo( parentWidget(), QString("<qt>")+message+QString("</qt>") );
00377     bool yes = ( ret == KMessageBox::Yes );
00378     if ( yes )
00379         const_cast<KFind*>(this)->m_options &= ~KFindDialog::FromCursor; // clear FromCursor option
00380     return yes;
00381 }
00382 
00383 void KFind::setOptions( long options )
00384 {
00385     m_options = options;
00386 }
00387 
00388 void KFind::closeFindNextDialog()
00389 {
00390     delete m_dialog;
00391     m_dialog = 0L;
00392     m_dialogClosed = true;
00393 }
00394 
00395 #include "kfind.moc"
KDE Logo
This file is part of the documentation for kdelibs Version 3.1.0.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Wed Oct 8 12:21:55 2003 by doxygen 1.2.18 written by Dimitri van Heesch, © 1997-2001