KDevelop API Documentation

koFind.cpp

Go to the documentation of this file.
00001 /*
00002     Copyright (C) 2001, S.R.Haque <srhaque@iee.org>.
00003     This file is part of the KDE project
00004 
00005     This library is free software; you can redistribute it and/or
00006     modify it under the terms of the GNU Library General Public
00007     License as published by the Free Software Foundation; either
00008     version 2 of the License, or (at your option) any later version.
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 <qcheckbox.h>
00022 #include <qcursor.h>
00023 #include <qgroupbox.h>
00024 #include <qlabel.h>
00025 #include <qlayout.h>
00026 #include <qpopupmenu.h>
00027 #include <qpushbutton.h>
00028 #include <qregexp.h>
00029 #include <kapplication.h>
00030 #include <kdebug.h>
00031 #include <kcombobox.h>
00032 #include <klocale.h>
00033 #include "koFind.h"
00034 #include <kmessagebox.h>
00035 
00036 KoFindDialog::KoFindDialog(QWidget *parent, const char *name, long options, const QStringList &findStrings, bool hasSelection) :
00037     KDialogBase(parent, name, true, i18n("Find Text"), Ok | Cancel, Ok)
00038 {
00039     init(false, findStrings, hasSelection);
00040     setOptions(options);
00041 }
00042 
00043 KoFindDialog::KoFindDialog(QWidget *parent, const char *name, bool /*forReplace*/) :
00044     KDialogBase(parent, name, true, i18n("Replace Text"), Ok | Cancel, Ok)
00045 {
00046 }
00047 
00048 KoFindDialog::~KoFindDialog()
00049 {
00050 }
00051 
00052 QWidget *KoFindDialog::findExtension()
00053 {
00054     return m_findExtension;
00055 }
00056 
00057 QStringList KoFindDialog::findHistory() const
00058 {
00059     return m_find->historyItems();
00060 }
00061 
00062 void KoFindDialog::init(bool forReplace, const QStringList &findStrings, bool hasSelection)
00063 {
00064     QVBoxLayout *topLayout;
00065     QGridLayout *optionsLayout;
00066 
00067     // Create common parts of dialog.
00068     QWidget *page = new QWidget(this);
00069     setMainWidget(page);
00070 
00071     topLayout = new QVBoxLayout(page);
00072     topLayout->setSpacing( KDialog::spacingHint() );
00073     topLayout->setMargin( KDialog::marginHint() );
00074 
00075     m_findGrp = new QGroupBox(0, Qt::Vertical, i18n("Find"), page);
00076     m_findGrp->layout()->setSpacing(KDialog::spacingHint());
00077     m_findGrp->layout()->setMargin(KDialog::marginHint());
00078     m_findLayout = new QGridLayout(m_findGrp->layout());
00079     m_findLayout->setSpacing( KDialog::spacingHint() );
00080     m_findLayout->setMargin( KDialog::marginHint() );
00081 
00082     m_findLabel = new QLabel(i18n("&Text to find:"), m_findGrp);
00083     m_find = new KHistoryCombo(true, m_findGrp);
00084     m_find->setMaxCount(10);
00085     m_find->setDuplicatesEnabled(false);
00086     m_regExp = new QCheckBox(i18n("Use patterns"), m_findGrp);
00087     m_regExpItem = new QPushButton(i18n("Insert Pattern"), m_findGrp);
00088     m_regExpItem->setEnabled(false);
00089     m_findExtension = new QWidget(m_findGrp);
00090 
00091     m_findLayout->addWidget(m_findLabel, 0, 0);
00092     m_findLayout->addMultiCellWidget(m_find, 1, 1, 0, 1);
00093     m_findLayout->addWidget(m_regExp, 2, 0);
00094     m_findLayout->addWidget(m_regExpItem, 2, 1);
00095     m_findLayout->addMultiCellWidget(m_findExtension, 3, 3, 0, 1);
00096     topLayout->addWidget(m_findGrp);
00097 
00098     m_replaceGrp = new QGroupBox(0, Qt::Vertical, i18n("Replace With"), page);
00099     m_replaceGrp->layout()->setSpacing(KDialog::spacingHint());
00100     m_replaceGrp->layout()->setMargin(KDialog::marginHint());
00101     m_replaceLayout = new QGridLayout(m_replaceGrp->layout());
00102     m_replaceLayout->setSpacing( KDialog::spacingHint() );
00103     m_replaceLayout->setMargin( KDialog::marginHint() );
00104 
00105     m_replaceLabel = new QLabel(i18n("&Replacement text:"), m_replaceGrp);
00106     m_replace = new KHistoryCombo(true, m_replaceGrp);
00107     m_replace->setMaxCount(10);
00108     m_replace->setDuplicatesEnabled(false);
00109     m_backRef = new QCheckBox(i18n("&Use placeholders"), m_replaceGrp);
00110     m_backRefItem = new QPushButton(i18n("Insert Placeholder"), m_replaceGrp);
00111     m_backRefItem->setEnabled(false);
00112     m_replaceExtension = new QWidget(m_replaceGrp);
00113 
00114     m_replaceLayout->addWidget(m_replaceLabel, 0, 0);
00115     m_replaceLayout->addMultiCellWidget(m_replace, 1, 1, 0, 1);
00116     m_replaceLayout->addWidget(m_backRef, 2, 0);
00117     m_replaceLayout->addWidget(m_backRefItem, 2, 1);
00118     m_replaceLayout->addMultiCellWidget(m_replaceExtension, 3, 3, 0, 1);
00119     topLayout->addWidget(m_replaceGrp);
00120 
00121     m_optionGrp = new QGroupBox(0, Qt::Vertical, i18n("Options"), page);
00122     m_optionGrp->layout()->setSpacing(KDialog::spacingHint());
00123     m_optionGrp->layout()->setMargin(KDialog::marginHint());
00124     optionsLayout = new QGridLayout(m_optionGrp->layout());
00125     optionsLayout->setSpacing( KDialog::spacingHint() );
00126     optionsLayout->setMargin( KDialog::marginHint() );
00127 
00128     m_caseSensitive = new QCheckBox(i18n("C&ase sensitive"), m_optionGrp);
00129     m_wholeWordsOnly = new QCheckBox(i18n("&Whole words only"), m_optionGrp);
00130     m_fromCursor = new QCheckBox(i18n("&From cursor"), m_optionGrp);
00131     m_findBackwards = new QCheckBox(i18n("Find &backwards"), m_optionGrp);
00132     m_selectedText = new QCheckBox(i18n("&Selected text"), m_optionGrp);
00133     setHasSelection( hasSelection );
00134     m_promptOnReplace = new QCheckBox(i18n("&Prompt on replace"), m_optionGrp);
00135     m_promptOnReplace->setChecked( true );
00136 
00137     optionsLayout->addWidget(m_caseSensitive, 0, 0);
00138     optionsLayout->addWidget(m_wholeWordsOnly, 1, 0);
00139     optionsLayout->addWidget(m_fromCursor, 2, 0);
00140     optionsLayout->addWidget(m_findBackwards, 0, 1);
00141     optionsLayout->addWidget(m_selectedText, 1, 1);
00142     optionsLayout->addWidget(m_promptOnReplace, 2, 1);
00143     topLayout->addWidget(m_optionGrp);
00144 
00145     // We delay creation of these until needed.
00146     m_patterns = 0L;
00147     m_placeholders = 0L;
00148 
00149     // signals and slots connections
00150     connect(m_selectedText, SIGNAL(toggled(bool)), this, SLOT(slotSelectedTextToggled(bool)));
00151     connect(m_regExp, SIGNAL(toggled(bool)), m_regExpItem, SLOT(setEnabled(bool)));
00152     connect(m_backRef, SIGNAL(toggled(bool)), m_backRefItem, SLOT(setEnabled(bool)));
00153     connect(m_regExpItem, SIGNAL(pressed()), this, SLOT(showPatterns()));
00154     connect(m_backRefItem, SIGNAL(pressed()), this, SLOT(showPlaceholders()));
00155 
00156     connect(m_find, SIGNAL(textChanged ( const QString & )),this, SLOT(textSearchChanged( const QString & )));
00157 
00158     // tab order
00159     setTabOrder(m_find, m_regExp);
00160     setTabOrder(m_regExp, m_regExpItem);
00161     setTabOrder(m_regExpItem, m_replace);
00162     setTabOrder(m_replace, m_backRef);
00163     setTabOrder(m_backRef, m_backRefItem);
00164     setTabOrder(m_backRefItem, m_caseSensitive);
00165     setTabOrder(m_caseSensitive, m_wholeWordsOnly);
00166     setTabOrder(m_wholeWordsOnly, m_fromCursor);
00167     setTabOrder(m_fromCursor, m_findBackwards);
00168     setTabOrder(m_findBackwards, m_selectedText);
00169     setTabOrder(m_selectedText, m_promptOnReplace);
00170 
00171     // buddies
00172     m_findLabel->setBuddy(m_find);
00173     m_replaceLabel->setBuddy(m_replace);
00174 
00175     if (!forReplace)
00176     {
00177         m_promptOnReplace->hide();
00178         m_replaceGrp->hide();
00179     }
00180     setFindHistory(findStrings);
00181     m_find->setFocus();
00182     enableButtonOK( !pattern().isEmpty() );   
00183     
00184 }
00185 
00186 void KoFindDialog::textSearchChanged( const QString & text)
00187 {
00188     enableButtonOK( !text.isEmpty() );
00189 }
00190 
00191 long KoFindDialog::options() const
00192 {
00193     long options = 0;
00194 
00195     if (m_caseSensitive->isChecked())
00196         options |= CaseSensitive;
00197     if (m_wholeWordsOnly->isChecked())
00198         options |= WholeWordsOnly;
00199     if (m_fromCursor->isChecked())
00200         options |= FromCursor;
00201     if (m_findBackwards->isChecked())
00202         options |= FindBackwards;
00203     if (m_selectedText->isChecked())
00204         options |= SelectedText;
00205     if (m_regExp->isChecked())
00206         options |= RegularExpression;
00207     return options;
00208 }
00209 
00210 QString KoFindDialog::pattern() const
00211 {
00212     return m_find->currentText();
00213 }
00214 
00215 void KoFindDialog::setFindHistory(const QStringList &strings)
00216 {
00217     if (strings.count() > 0)
00218     {
00219         m_find->setHistoryItems(strings, true);
00220         m_find->lineEdit()->setText( strings.first() );
00221         m_find->lineEdit()->selectAll();
00222     }
00223     else
00224         m_find->clearHistory();
00225 }
00226 
00227 void KoFindDialog::setHasSelection(bool hasSelection)
00228 {
00229     m_selectedText->setEnabled( hasSelection );
00230     // If we have a selection, we make 'find in selection' default
00231     // and if we don't, then the option has to be unchecked, obviously.
00232     m_selectedText->setChecked( hasSelection );
00233     slotSelectedTextToggled( hasSelection );
00234 }
00235 
00236 void KoFindDialog::slotSelectedTextToggled(bool selec)
00237 {
00238     // From cursor doesn't make sense if we have a selection
00239     m_fromCursor->setEnabled( !selec );
00240     if ( selec ) // uncheck if disabled
00241         m_fromCursor->setChecked( false );
00242 }
00243 
00244 void KoFindDialog::setOptions(long options)
00245 {
00246     m_caseSensitive->setChecked(options & CaseSensitive);
00247     m_wholeWordsOnly->setChecked(options & WholeWordsOnly);
00248     m_fromCursor->setChecked(options & FromCursor);
00249     m_findBackwards->setChecked(options & FindBackwards);
00250     m_selectedText->setChecked(options & SelectedText);
00251     m_regExp->setChecked(options & RegularExpression);
00252 }
00253 
00254 // Create a popup menu with a list of regular expression terms, to help the user
00255 // compose a regular expression search pattern.
00256 void KoFindDialog::showPatterns()
00257 {
00258     typedef struct
00259     {
00260         const char *description;
00261         const char *regExp;
00262         int cursorAdjustment;
00263     } term;
00264     static const term items[] =
00265     {
00266         { I18N_NOOP("Any Character"),                 ".",        0 },
00267         { I18N_NOOP("Start of Line"),                 "^",        0 },
00268         { I18N_NOOP("End of Line"),                   "$",        0 },
00269         { I18N_NOOP("Set of Characters"),             "[]",       -1 },
00270         { I18N_NOOP("Repeats, Zero or More Times"),   "*",        0 },
00271         { I18N_NOOP("Repeats, One or More Times"),    "+",        0 },
00272         { I18N_NOOP("Optional"),                      "?",        0 },
00273         { I18N_NOOP("Escape"),                        "\\",       0 },
00274         { I18N_NOOP("TAB"),                           "\\t",      0 },
00275         { I18N_NOOP("Newline"),                       "\\n",      0 },
00276         { I18N_NOOP("Carriage Return"),               "\\r",      0 },
00277         { I18N_NOOP("White Space"),                   "\\s",      0 },
00278         { I18N_NOOP("Digit"),                         "\\d",      0 },
00279         // Those don't work
00280         //{ I18N_NOOP("Unicode Point"),               "\x0000",   0 },
00281         //{ I18N_NOOP("ASCII/Latin-1 Character"),       "\000",     0 }
00282     };
00283     int i;
00284 
00285     // Populate the popup menu.
00286     if (!m_patterns)
00287     {
00288         m_patterns = new QPopupMenu(this);
00289         for (i = 0; (unsigned)i < sizeof(items) / sizeof(items[0]); i++)
00290         {
00291             m_patterns->insertItem(i18n(items[i].description), i, i);
00292         }
00293     }
00294 
00295     // Insert the selection into the edit control.
00296     i = m_patterns->exec(QCursor::pos());
00297     if (i != -1)
00298     {
00299         QLineEdit *editor = m_find->lineEdit();
00300 
00301         editor->insert(items[i].regExp);
00302         editor->setCursorPosition(editor->cursorPosition() + items[i].cursorAdjustment);
00303     }
00304 }
00305 
00306 // Create a popup menu with a list of backreference terms, to help the user
00307 // compose a regular expression replacement pattern.
00308 void KoFindDialog::showPlaceholders()
00309 {
00310     typedef struct
00311     {
00312         const char *description;
00313         const char *backRef;
00314     } term;
00315     static const term items[] =
00316     {
00317         { I18N_NOOP("Complete text found"),             "/0" },
00318     };
00319     int i;
00320 
00321     // Populate the popup menu.
00322     if (!m_placeholders)
00323     {
00324         m_placeholders = new QPopupMenu(this);
00325         for (i = 0; (unsigned)i < sizeof(items) / sizeof(items[0]); i++)
00326         {
00327             m_placeholders->insertItem(i18n(items[i].description), i, i);
00328         }
00329     }
00330 
00331     // Insert the selection into the edit control.
00332     i = m_placeholders->exec(QCursor::pos());
00333     if (i != -1)
00334     {
00335         QLineEdit *editor = m_replace->lineEdit();
00336 
00337         editor->insert(items[i].backRef);
00338     }
00339 }
00340 
00341 void KoFindDialog::slotOk()
00342 {
00343     // Nothing to find?
00344     if (pattern().isEmpty())
00345     {
00346         KMessageBox::error(this, i18n("You must enter some text to search for."));
00347         return;
00348     }
00349 
00350     if (m_regExp->isChecked())
00351     {
00352         // Check for a valid regular expression.
00353         QRegExp regExp(pattern());
00354 
00355         if (!regExp.isValid())
00356         {
00357             KMessageBox::error(this, i18n("Invalid regular expression."));
00358             return;
00359         }
00360     }
00361     m_find->addToHistory(pattern());
00362     emit okClicked();
00363     accept();
00364 }
00365 
00366 // Create the dialog.
00367 KoFind::KoFind(const QString &pattern, long options, QWidget *parent) :
00368     KDialogBase(parent, __FILE__, false,  // non-modal!
00369         i18n("Find"),
00370         User1 | Close,
00371         User1,
00372         false,
00373         KStdGuiItem::yes())
00374 {
00375     setMainWidget( new QLabel( i18n("Find next '%1'").arg(pattern), this ) );
00376 
00377     m_cancelled = false;
00378     m_options = options;
00379     m_parent = parent;
00380     m_matches = 0;
00381     if (m_options & KoFindDialog::RegularExpression)
00382         m_regExp = new QRegExp(pattern, m_options & KoFindDialog::CaseSensitive);
00383     else
00384         m_pattern = pattern;
00385     resize(minimumSize());
00386 }
00387 
00388 KoFind::~KoFind()
00389 {
00390     if (!m_matches && !m_cancelled)
00391         KMessageBox::information(m_parent, i18n("No match was found."));
00392 }
00393 
00394 void KoFind::slotClose()
00395 {
00396     m_cancelled = true;
00397     kapp->exit_loop();
00398 }
00399 
00400 void KoFind::abort()
00401 {
00402     slotClose();
00403 }
00404 
00405 bool KoFind::find(const QString &text, const QRect &expose)
00406 {
00407     if (m_options & KoFindDialog::FindBackwards)
00408     {
00409         m_index = text.length();
00410     }
00411     else
00412     {
00413         m_index = 0;
00414     }
00415     m_text = text;
00416     m_expose = expose;
00417     do
00418     {
00419         // Find the next match.
00420         if (m_options & KoFindDialog::RegularExpression)
00421             m_index = KoFind::find(m_text, *m_regExp, m_index, m_options, &m_matchedLength);
00422         else
00423             m_index = KoFind::find(m_text, m_pattern, m_index, m_options, &m_matchedLength);
00424         if (m_index != -1)
00425         {
00426             // Tell the world about the match we found, in case someone wants to
00427             // highlight it.
00428             if ( validateMatch( m_text, m_index, m_matchedLength ))
00429             {
00430                 emit highlight(m_text, m_index, m_matchedLength, m_expose);
00431                 show();
00432                 kapp->enter_loop();
00433             }
00434             else
00435                 m_index = m_index+m_matchedLength;
00436         }
00437     }
00438     while ((m_index != -1) && !m_cancelled);
00439 
00440     // Should the user continue?
00441     return !m_cancelled;
00442 }
00443 
00444 int KoFind::find(const QString &text, const QString &pattern, int index, long options, int *matchedLength)
00445 {
00446     // Handle regular expressions in the appropriate way.
00447     if (options & KoFindDialog::RegularExpression)
00448     {
00449         QRegExp regExp(pattern, options & KoFindDialog::CaseSensitive);
00450 
00451         return find(text, regExp, index, options, matchedLength);
00452     }
00453 
00454     bool caseSensitive = (options & KoFindDialog::CaseSensitive);
00455 
00456     if (options & KoFindDialog::WholeWordsOnly)
00457     {
00458         if (options & KoFindDialog::FindBackwards)
00459         {
00460             // Backward search, until the beginning of the line...
00461             while (index >= 0)
00462             {
00463                 // ...find the next match.
00464                 index = text.findRev(pattern, index, caseSensitive);
00465                 if (index == -1)
00466                     break;
00467 
00468                 // Is the match delimited correctly?
00469                 *matchedLength = pattern.length();
00470                 if (isWholeWords(text, index, *matchedLength))
00471                     break;
00472                 index--;
00473             }
00474         }
00475         else
00476         {
00477             // Forward search, until the end of the line...
00478             while (index < (int)text.length())
00479             {
00480                 // ...find the next match.
00481                 index = text.find(pattern, index, caseSensitive);
00482                 if (index == -1)
00483                     break;
00484 
00485                 // Is the match delimited correctly?
00486                 *matchedLength = pattern.length();
00487                 if (isWholeWords(text, index, *matchedLength))
00488                     break;
00489                 index++;
00490             }
00491             if (index >= (int)text.length()) // end of line
00492                 index = -1; // not found
00493         }
00494     }
00495     else
00496     {
00497         // Non-whole-word search.
00498         if (options & KoFindDialog::FindBackwards)
00499         {
00500             index = text.findRev(pattern, index, caseSensitive);
00501         }
00502         else
00503         {
00504             index = text.find(pattern, index, caseSensitive);
00505         }
00506         if (index != -1)
00507         {
00508             *matchedLength = pattern.length();
00509         }
00510     }
00511     return index;
00512 }
00513 
00514 int KoFind::find(const QString &text, const QRegExp &pattern, int index, long options, int *matchedLength)
00515 {
00516     if (options & KoFindDialog::WholeWordsOnly)
00517     {
00518         if (options & KoFindDialog::FindBackwards)
00519         {
00520             // Backward search, until the beginning of the line...
00521             while (index >= 0)
00522             {
00523                 // ...find the next match.
00524                 index = text.findRev(pattern, index);
00525                 if (index == -1)
00526                     break;
00527 
00528                 // Is the match delimited correctly?
00529                 //pattern.match(text, index, matchedLength, false);
00530                 /*int pos =*/ pattern.search( text.mid(index) );
00531                 *matchedLength = pattern.matchedLength();
00532                 if (isWholeWords(text, index, *matchedLength))
00533                     break;
00534                 index--;
00535             }
00536         }
00537         else
00538         {
00539             // Forward search, until the end of the line...
00540             while (index < (int)text.length())
00541             {
00542                 // ...find the next match.
00543                 index = text.find(pattern, index);
00544                 if (index == -1)
00545                     break;
00546 
00547                 // Is the match delimited correctly?
00548                 //pattern.match(text, index, matchedLength, false);
00549                 /*int pos =*/ pattern.search( text.mid(index) );
00550                 *matchedLength = pattern.matchedLength();
00551                 if (isWholeWords(text, index, *matchedLength))
00552                     break;
00553                 index++;
00554             }
00555             if (index >= (int)text.length()) // end of line
00556                 index = -1; // not found
00557         }
00558     }
00559     else
00560     {
00561         // Non-whole-word search.
00562         if (options & KoFindDialog::FindBackwards)
00563         {
00564             index = text.findRev(pattern, index);
00565         }
00566         else
00567         {
00568             index = text.find(pattern, index);
00569         }
00570         if (index != -1)
00571         {
00572             //pattern.match(text, index, matchedLength, false);
00573             /*int pos =*/ pattern.search( text.mid(index) );
00574             *matchedLength = pattern.matchedLength();
00575         }
00576     }
00577     return index;
00578 }
00579 
00580 bool KoFind::isInWord(QChar ch)
00581 {
00582     return ch.isLetter() || ch.isDigit() || ch == '_';
00583 }
00584 
00585 bool KoFind::isWholeWords(const QString &text, int starts, int matchedLength)
00586 {
00587     if ((starts == 0) || (!isInWord(text[starts - 1])))
00588     {
00589         int ends = starts + matchedLength;
00590 
00591         if ((ends == (int)text.length()) || (!isInWord(text[ends])))
00592             return true;
00593     }
00594     return false;
00595 }
00596 
00597 // Yes.
00598 void KoFind::slotUser1()
00599 {
00600     m_matches++;
00601     if (m_options & KoFindDialog::FindBackwards)
00602         m_index--;
00603     else
00604         m_index++;
00605     kapp->exit_loop();
00606 }
00607 
00608 #include "koFind.moc"
KDE Logo
This file is part of the documentation for KDevelop Version 3.1.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Tue Feb 22 09:22:24 2005 by doxygen 1.3.9.1 written by Dimitri van Heesch, © 1997-2003