00001 
00002 
00003 
00004 
00005 
00006 
00007 
00008 
00009 
00010 
00011 
00012 
00013 
00014 
00015 
00016 
00017 
00018 
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 
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 
00043 KFindNextDialog::KFindNextDialog(const QString &pattern, QWidget *parent) :
00044     KDialogBase(parent, 0, false,  
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     
00105     if (m_options & KFindDialog::FindBackwards)
00106         
00107         
00108         return ( m_index < 0 && m_lastResult != Match );
00109     else
00110         
00111         
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         
00148         if (m_options & KFindDialog::FindBackwards) {
00149             m_index--;
00150             if ( m_index == -1 ) 
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 
00163     {
00164         
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             
00172             if ( validateMatch( m_text, m_index, m_matchedLength ) )
00173             {
00174                 m_matches++;
00175                 
00176                 
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 
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 
00206 int KFind::find(const QString &text, const QString &pattern, int index, long options, int *matchedLength)
00207 {
00208     
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             
00223             while (index >= 0)
00224             {
00225                 
00226                 index = text.findRev(pattern, index, caseSensitive);
00227                 if (index == -1)
00228                     break;
00229 
00230                 
00231                 *matchedLength = pattern.length();
00232                 if (isWholeWords(text, index, *matchedLength))
00233                     break;
00234                 index--;
00235             }
00236         }
00237         else
00238         {
00239             
00240             while (index < (int)text.length())
00241             {
00242                 
00243                 index = text.find(pattern, index, caseSensitive);
00244                 if (index == -1)
00245                     break;
00246 
00247                 
00248                 *matchedLength = pattern.length();
00249                 if (isWholeWords(text, index, *matchedLength))
00250                     break;
00251                 index++;
00252             }
00253             if (index >= (int)text.length()) 
00254                 index = -1; 
00255         }
00256     }
00257     else
00258     {
00259         
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 
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             
00284             while (index >= 0)
00285             {
00286                 
00287                 index = text.findRev(pattern, index);
00288                 if (index == -1)
00289                     break;
00290 
00291                 
00292                 
00293                  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             
00303             while (index < (int)text.length())
00304             {
00305                 
00306                 index = text.find(pattern, index);
00307                 if (index == -1)
00308                     break;
00309 
00310                 
00311                 
00312                  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()) 
00319                 index = -1; 
00320         }
00321     }
00322     else
00323     {
00324         
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             
00336              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     
00384     
00385     
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"; 
00408     
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; 
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() ); 
00448 }
00449 
00450 QWidget* KFind::dialogsParent() const
00451 {
00452     
00453     
00454     
00455     return d->findDialog ? (QWidget*)d->findDialog : ( m_dialog ? m_dialog : parentWidget() );
00456 }
00457 
00458 #include "kfind.moc"