• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

Kate

katesearchbar.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE libraries
00002    Copyright (C) 2009-2010 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
00003    Copyright (C) 2007 Sebastian Pipping <webmaster@hartwork.org>
00004    Copyright (C) 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
00005    Copyright (C) 2007 Thomas Friedrichsmeier <thomas.friedrichsmeier@ruhr-uni-bochum.de>
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 "katesearchbar.h"
00023 #include "katesearchbar.moc"
00024 
00025 #include "kateregexp.h"
00026 #include "katematch.h"
00027 #include "kateview.h"
00028 #include "katedocument.h"
00029 #include "kateconfig.h"
00030 
00031 #include <ktexteditor/movingcursor.h>
00032 #include <ktexteditor/movingrange.h>
00033 
00034 #include "ui_searchbarincremental.h"
00035 #include "ui_searchbarpower.h"
00036 
00037 #include <kcolorscheme.h>
00038 #include <kstandardaction.h>
00039 #include <kpassivepopup.h>
00040 
00041 #include <QtGui/QVBoxLayout>
00042 #include <QtGui/QComboBox>
00043 #include <QtGui/QCheckBox>
00044 #include <QtGui/QShortcut>
00045 #include <QCompleter>
00046 
00047 // Turn debug messages on/off here
00048 // #define FAST_DEBUG_ENABLE
00049 
00050 #ifdef FAST_DEBUG_ENABLE
00051 # define FAST_DEBUG(x) kDebug() << x
00052 #else
00053 # define FAST_DEBUG(x)
00054 #endif
00055 
00056 using namespace KTextEditor;
00057 
00058 namespace {
00059 
00060 class AddMenuManager {
00061 
00062 private:
00063     QVector<QString> m_insertBefore;
00064     QVector<QString> m_insertAfter;
00065     QSet<QAction *> m_actionPointers;
00066     uint m_indexWalker;
00067     QMenu * m_menu;
00068 
00069 public:
00070     AddMenuManager(QMenu * parent, int expectedItemCount)
00071             : m_insertBefore(QVector<QString>(expectedItemCount)),
00072             m_insertAfter(QVector<QString>(expectedItemCount)),
00073             m_indexWalker(0),
00074             m_menu(NULL) {
00075         Q_ASSERT(parent != NULL);
00076         m_menu = parent->addMenu(i18n("Add..."));
00077         if (m_menu == NULL) {
00078             return;
00079         }
00080         m_menu->setIcon(KIcon("list-add"));
00081     }
00082 
00083     void enableMenu(bool enabled) {
00084         if (m_menu == NULL) {
00085             return;
00086         }
00087         m_menu->setEnabled(enabled);
00088     }
00089 
00090     void addEntry(const QString & before, const QString after,
00091             const QString description, const QString & realBefore = QString(),
00092             const QString & realAfter = QString()) {
00093         if (m_menu == NULL) {
00094             return;
00095         }
00096         QAction * const action = m_menu->addAction(before + after + '\t' + description);
00097         m_insertBefore[m_indexWalker] = QString(realBefore.isEmpty() ? before : realBefore);
00098         m_insertAfter[m_indexWalker] = QString(realAfter.isEmpty() ? after : realAfter);
00099         action->setData(QVariant(m_indexWalker++));
00100         m_actionPointers.insert(action);
00101     }
00102 
00103     void addSeparator() {
00104         if (m_menu == NULL) {
00105             return;
00106         }
00107         m_menu->addSeparator();
00108     }
00109 
00110     void handle(QAction * action, QLineEdit * lineEdit) {
00111         if (!m_actionPointers.contains(action)) {
00112             return;
00113         }
00114 
00115         const int cursorPos = lineEdit->cursorPosition();
00116         const int index = action->data().toUInt();
00117         const QString & before = m_insertBefore[index];
00118         const QString & after = m_insertAfter[index];
00119         lineEdit->insert(before + after);
00120         lineEdit->setCursorPosition(cursorPos + before.count());
00121         lineEdit->setFocus();
00122     }
00123 };
00124 
00125 } // anon namespace
00126 
00127 
00128 
00129 KateSearchBar::KateSearchBar(bool initAsPower, KateView* view, KateViewConfig *config)
00130         : KateViewBarWidget(true, view),
00131         m_view(view),
00132         m_config(config),
00133         m_layout(new QVBoxLayout()),
00134         m_widget(NULL),
00135         m_incUi(NULL),
00136         m_incInitCursor(view->cursorPosition()),
00137         m_powerUi(NULL),
00138         highlightMatchAttribute (new Attribute()),
00139         highlightReplacementAttribute (new Attribute()),
00140         m_incHighlightAll(false),
00141         m_incFromCursor(true),
00142         m_incMatchCase(false),
00143         m_powerMatchCase(true),
00144         m_powerFromCursor(false),
00145         m_powerHighlightAll(false),
00146         m_powerMode(0)
00147 {
00148 
00149     connect(view, SIGNAL(cursorPositionChanged(KTextEditor::View *, KTextEditor::Cursor const &)),
00150             this, SLOT(updateIncInitCursor()));
00151 
00152     // init match attribute
00153     highlightMatchAttribute->setBackground(Qt::yellow); // TODO make this part of the color scheme
00154 
00155     Attribute::Ptr mouseInAttribute(new Attribute());
00156     mouseInAttribute->setFontBold(true);
00157     mouseInAttribute->setBackground(Qt::yellow); // TODO make this part of the color scheme
00158     highlightMatchAttribute->setDynamicAttribute (Attribute::ActivateMouseIn, mouseInAttribute);
00159 
00160     Attribute::Ptr caretInAttribute(new Attribute());
00161     caretInAttribute->setFontItalic(true);
00162     caretInAttribute->setBackground(Qt::yellow); // TODO make this part of the color scheme
00163     highlightMatchAttribute->setDynamicAttribute (Attribute::ActivateCaretIn, caretInAttribute);
00164 
00165     // init replacement attribute
00166     highlightReplacementAttribute->setBackground(Qt::green); // TODO make this part of the color scheme
00167 
00168     // Modify parent
00169     QWidget * const widget = centralWidget();
00170     widget->setLayout(m_layout);
00171     m_layout->setMargin(0);
00172 
00173 
00174     // Copy global to local config backup
00175     const long searchFlags = m_config->searchFlags();
00176     m_incHighlightAll = (searchFlags & KateViewConfig::IncHighlightAll) != 0;
00177     m_incFromCursor = (searchFlags & KateViewConfig::IncFromCursor) != 0;
00178     m_incMatchCase = (searchFlags & KateViewConfig::IncMatchCase) != 0;
00179     m_powerMatchCase = (searchFlags & KateViewConfig::PowerMatchCase) != 0;
00180     m_powerFromCursor = (searchFlags & KateViewConfig::PowerFromCursor) != 0;
00181     m_powerHighlightAll = (searchFlags & KateViewConfig::PowerHighlightAll) != 0;
00182     m_powerMode = ((searchFlags & KateViewConfig::PowerModeRegularExpression) != 0)
00183             ? MODE_REGEX
00184             : (((searchFlags & KateViewConfig::PowerModeEscapeSequences) != 0)
00185                 ? MODE_ESCAPE_SEQUENCES
00186                 : (((searchFlags & KateViewConfig::PowerModeWholeWords) != 0)
00187                     ? MODE_WHOLE_WORDS
00188                     : MODE_PLAIN_TEXT));
00189 
00190 
00191     // Load one of either dialogs
00192     if (initAsPower) {
00193         enterPowerMode();
00194     } else {
00195         enterIncrementalMode();
00196     }
00197 
00198     updateSelectionOnly();
00199     connect(view, SIGNAL(selectionChanged(KTextEditor::View *)),
00200             this, SLOT(updateSelectionOnly()));
00201 }
00202 
00203 
00204 
00205 KateSearchBar::~KateSearchBar() {
00206     clearHighlights();
00207     delete m_layout;
00208     delete m_widget;
00209 
00210     delete m_incUi;
00211     delete m_powerUi;
00212 }
00213 
00214 
00215 
00216 
00217 void KateSearchBar::setReplacePattern(const QString &replacementPattern) {
00218     Q_ASSERT(isPower());
00219 
00220     if (this->replacementPattern() == replacementPattern)
00221         return;
00222 
00223     m_powerUi->replacement->setEditText(replacementPattern);
00224 }
00225 
00226 
00227 
00228 QString KateSearchBar::replacementPattern() const {
00229     Q_ASSERT(isPower());
00230 
00231     return m_powerUi->replacement->currentText();
00232 }
00233 
00234 
00235 
00236 void KateSearchBar::setSearchMode(KateSearchBar::SearchMode mode) {
00237     Q_ASSERT(isPower());
00238 
00239     m_powerUi->searchMode->setCurrentIndex(mode);
00240 }
00241 
00242 
00243 
00244 void KateSearchBar::findNext() {
00245     const bool found = find();
00246 
00247     if (found) {
00248         QComboBox *combo = m_powerUi != 0 ? m_powerUi->pattern : m_incUi->pattern;
00249 
00250         // Add to search history
00251         addCurrentTextToHistory(combo);
00252     }
00253 }
00254 
00255 
00256 
00257 void KateSearchBar::findPrevious() {
00258     const bool found = find(SearchBackward);
00259 
00260     if (found) {
00261         QComboBox *combo = m_powerUi != 0 ? m_powerUi->pattern : m_incUi->pattern;
00262 
00263         // Add to search history
00264         addCurrentTextToHistory(combo);
00265     }
00266 }
00267 
00268 void KateSearchBar::highlightMatch(const Range & range) {
00269     KTextEditor::MovingRange* const highlight = m_view->doc()->newMovingRange(range, Kate::TextRange::DoNotExpand);
00270     highlight->setView(m_view); // show only in this view
00271     highlight->setAttributeOnlyForViews(true);
00272     // use z depth defined in moving ranges interface
00273     highlight->setZDepth (-10000.0);
00274     highlight->setAttribute(highlightMatchAttribute);
00275     m_hlRanges.append(highlight);
00276 }
00277 
00278 void KateSearchBar::highlightReplacement(const Range & range) {
00279     KTextEditor::MovingRange* const highlight = m_view->doc()->newMovingRange(range, Kate::TextRange::DoNotExpand);
00280     highlight->setView(m_view); // show only in this view
00281     highlight->setAttributeOnlyForViews(true);
00282     // use z depth defined in moving ranges interface
00283     highlight->setZDepth (-10000.0);
00284     highlight->setAttribute(highlightReplacementAttribute);
00285     m_hlRanges.append(highlight);
00286 }
00287 
00288 void KateSearchBar::indicateMatch(MatchResult matchResult) {
00289     QLineEdit * const lineEdit = isPower() ? m_powerUi->pattern->lineEdit()
00290                                            : m_incUi->pattern->lineEdit();
00291     QPalette background(lineEdit->palette());
00292 
00293     switch (matchResult) {
00294     case MatchFound:  // FALLTHROUGH
00295     case MatchWrappedForward:
00296     case MatchWrappedBackward:
00297         // Green background for line edit
00298         KColorScheme::adjustBackground(background, KColorScheme::PositiveBackground);
00299         break;
00300     case MatchMismatch:
00301         // Red background for line edit
00302         KColorScheme::adjustBackground(background, KColorScheme::NegativeBackground);
00303         break;
00304     case MatchNothing:
00305         // Reset background of line edit
00306         background = QPalette();
00307         break;
00308     case MatchNeutral:
00309         KColorScheme::adjustBackground(background, KColorScheme::NeutralBackground);
00310         break;
00311     }
00312 
00313     // Update status label
00314     if (m_incUi != NULL) {
00315         QPalette foreground(m_incUi->status->palette());
00316         switch (matchResult) {
00317         case MatchFound: // FALLTHROUGH
00318         case MatchNothing:
00319             KColorScheme::adjustForeground(foreground, KColorScheme::NormalText, QPalette::WindowText, KColorScheme::Window);
00320             m_incUi->status->setText("");
00321             break;
00322         case MatchWrappedForward:
00323         case MatchWrappedBackward:
00324             KColorScheme::adjustForeground(foreground, KColorScheme::ActiveText, QPalette::WindowText, KColorScheme::Window);
00325             if (matchResult == MatchWrappedBackward) {
00326                 m_incUi->status->setText(i18n("Reached top, continued from bottom"));
00327             } else {
00328                 m_incUi->status->setText(i18n("Reached bottom, continued from top"));
00329             }
00330             break;
00331         case MatchMismatch:
00332             KColorScheme::adjustForeground(foreground, KColorScheme::NegativeText, QPalette::WindowText, KColorScheme::Window);
00333             m_incUi->status->setText(i18n("Not found"));
00334             break;
00335         case MatchNeutral:
00336             /* do nothing */
00337             break;
00338         }
00339         m_incUi->status->setPalette(foreground);
00340     }
00341 
00342     lineEdit->setPalette(background);
00343 }
00344 
00345 
00346 
00347 /*static*/ void KateSearchBar::selectRange(KateView * view, const KTextEditor::Range & range) {
00348     view->setCursorPositionInternal(range.end());
00349 
00350     // don't make a selection if the vi input mode is used
00351     if (!view->viInputMode())
00352         view->setSelection(range);
00353 }
00354 
00355 
00356 
00357 void KateSearchBar::selectRange2(const KTextEditor::Range & range) {
00358     disconnect(m_view, SIGNAL(selectionChanged(KTextEditor::View *)), this, SLOT(updateSelectionOnly()));
00359     selectRange(m_view, range);
00360     connect(m_view, SIGNAL(selectionChanged(KTextEditor::View *)), this, SLOT(updateSelectionOnly()));
00361 }
00362 
00363 
00364 
00365 void KateSearchBar::onIncPatternChanged(const QString & pattern) {
00366     if (!m_incUi)
00367         return;
00368     clearHighlights();
00369 
00370     m_incUi->next->setDisabled(pattern.isEmpty());
00371     m_incUi->prev->setDisabled(pattern.isEmpty());
00372 
00373     KateMatch match(m_view->doc(), searchOptions());
00374 
00375     if (!pattern.isEmpty()) {
00376         // Find, first try
00377         const Range inputRange = KTextEditor::Range(m_incInitCursor, m_view->document()->documentEnd());
00378         match.searchText(inputRange, pattern);
00379     }
00380 
00381     const bool wrap = !match.isValid() && !pattern.isEmpty();
00382 
00383     if (wrap) {
00384         // Find, second try
00385         const KTextEditor::Range inputRange = m_view->document()->documentRange();
00386         match.searchText(inputRange, pattern);
00387     }
00388 
00389     const MatchResult matchResult = match.isValid()   ? (wrap ? MatchWrappedForward : MatchFound) :
00390                                     pattern.isEmpty() ? MatchNothing :
00391                                                         MatchMismatch;
00392 
00393     const Range selectionRange = pattern.isEmpty() ? Range(m_incInitCursor, m_incInitCursor) :
00394                                  match.isValid()   ? match.range() :
00395                                                      Range::invalid();
00396 
00397     // don't update m_incInitCursor when we move the cursor
00398     disconnect(m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor const&)),
00399                this, SLOT(updateIncInitCursor()));
00400     selectRange2(selectionRange);
00401     connect(m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor const&)),
00402             this, SLOT(updateIncInitCursor()));
00403 
00404     indicateMatch(matchResult);
00405 }
00406 
00407 
00408 
00409 void KateSearchBar::setMatchCase(bool matchCase) {
00410     if (this->matchCase() == matchCase)
00411         return;
00412 
00413     if (isPower())
00414         m_powerUi->matchCase->setChecked(matchCase);
00415     else
00416         m_incUi->matchCase->setChecked(matchCase);
00417 }
00418 
00419 
00420 
00421 void KateSearchBar::onMatchCaseToggled(bool /*matchCase*/) {
00422     sendConfig();
00423 
00424     if (m_incUi != 0) {
00425        // Re-search with new settings
00426         const QString pattern = m_incUi->pattern->currentText();
00427         onIncPatternChanged(pattern);
00428     } else {
00429         indicateMatch(MatchNothing);
00430     }
00431 }
00432 
00433 
00434 
00435 bool KateSearchBar::matchCase() const
00436 {
00437     return isPower() ? m_powerUi->matchCase->isChecked()
00438                      : m_incUi->matchCase->isChecked();
00439 }
00440 
00441 
00442 
00443 void KateSearchBar::fixForSingleLine(Range & range, SearchDirection searchDirection) {
00444     FAST_DEBUG("Single-line workaround checking BEFORE" << range);
00445     if (searchDirection == SearchForward) {
00446         const int line = range.start().line();
00447         const int col = range.start().column();
00448         const int maxColWithNewline = m_view->document()->lineLength(line) + 1;
00449         if (col == maxColWithNewline) {
00450             FAST_DEBUG("Starting on a newline" << range);
00451             const int maxLine = m_view->document()->lines() - 1;
00452             if (line < maxLine) {
00453                 range.setRange(Cursor(line + 1, 0), range.end());
00454                 FAST_DEBUG("Search range fixed to " << range);
00455             } else {
00456                 FAST_DEBUG("Already at last line");
00457                 range = Range::invalid();
00458             }
00459         }
00460     } else {
00461         const int col = range.end().column();
00462         if (col == 0) {
00463             FAST_DEBUG("Ending after a newline" << range);
00464             const int line = range.end().line();
00465             if (line > 0) {
00466                 const int maxColWithNewline = m_view->document()->lineLength(line - 1);
00467                 range.setRange(range.start(), Cursor(line - 1, maxColWithNewline));
00468                 FAST_DEBUG("Search range fixed to " << range);
00469             } else {
00470                 FAST_DEBUG("Already at first line");
00471                 range = Range::invalid();
00472             }
00473         }
00474     }
00475     FAST_DEBUG("Single-line workaround checking  AFTER" << range);
00476 }
00477 
00478 
00479 
00480 void KateSearchBar::onReturnPressed() {
00481     const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
00482     const bool shiftDown = (modifiers & Qt::ShiftModifier) != 0;
00483     const bool controlDown = (modifiers & Qt::ControlModifier) != 0;
00484 
00485     // if vi input mode is active, the search box should be closed when hitting enter
00486     if (m_view->viInputMode()) {
00487         emit hideMe();
00488         return;
00489     }
00490 
00491     if (shiftDown) {
00492         // Shift down, search backwards
00493         findPrevious();
00494     } else {
00495         // Shift up, search forwards
00496         findNext();
00497     }
00498 
00499     if (controlDown) {
00500         emit hideMe();
00501     }
00502 }
00503 
00504 
00505 
00506 bool KateSearchBar::find(SearchDirection searchDirection, const QString * replacement) {
00507     // What to find?
00508     if (searchPattern().isEmpty()) {
00509         return false; // == Pattern error
00510     }
00511 
00512     const Search::SearchOptions enabledOptions = searchOptions(searchDirection);
00513 
00514     // Where to find?
00515     Range inputRange;
00516     const Range selection = m_view->selection() ? m_view->selectionRange() : Range::invalid();
00517     if (selection.isValid()) {
00518         if (selectionOnly()) {
00519             // First match in selection
00520             inputRange = selection;
00521         } else {
00522             // Next match after/before selection if a match was selected before
00523             if (searchDirection == SearchForward) {
00524                 inputRange.setRange(selection.start(), m_view->document()->documentEnd());
00525             } else {
00526                 inputRange.setRange(Cursor(0, 0), selection.end());
00527             }
00528         }
00529     } else {
00530         // No selection
00531             const Cursor cursorPos = m_view->cursorPosition();
00532             if (searchDirection == SearchForward) {
00533                 // if the vi input mode is used, the cursor will stay a the first character of the
00534                 // matched pattern (no selection will be made), so the next search should start from
00535                 // match column + 1
00536                 if (!m_view->viInputMode()) {
00537                     inputRange.setRange(cursorPos, m_view->document()->documentEnd());
00538                 } else {
00539                     inputRange.setRange(Cursor(cursorPos.line(), cursorPos.column()+1), m_view->document()->documentEnd());
00540                 }
00541             } else {
00542                 inputRange.setRange(Cursor(0, 0), cursorPos);
00543             }
00544     }
00545     FAST_DEBUG("Search range is" << inputRange);
00546 
00547     {
00548         const bool regexMode = enabledOptions.testFlag(Search::Regex);
00549         const bool multiLinePattern = regexMode ? KateRegExp(searchPattern()).isMultiLine() : false;
00550 
00551         // Single-line pattern workaround
00552         if (regexMode && !multiLinePattern) {
00553             fixForSingleLine(inputRange, searchDirection);
00554         }
00555     }
00556 
00557     KateMatch match(m_view->doc(), enabledOptions);
00558     Range afterReplace = Range::invalid();
00559 
00560     // Find, first try
00561     match.searchText(inputRange, searchPattern());
00562     if (match.isValid() && match.range() == selection) {
00563             // Same match again
00564             if (replacement != 0) {
00565                 // Selection is match -> replace
00566                 KTextEditor::MovingRange *smartInputRange = m_view->doc()->newMovingRange (inputRange, KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight);
00567                 afterReplace = match.replace(*replacement, m_view->blockSelection());
00568                 inputRange = *smartInputRange;
00569                 delete smartInputRange;
00570             }
00571 
00572             if (!selectionOnly()) {
00573                 // Find, second try after old selection
00574                 if (searchDirection == SearchForward) {
00575                     const Cursor start = (replacement != 0) ? afterReplace.end() : selection.end();
00576                     inputRange.setRange(start, inputRange.end());
00577                 } else {
00578                     const Cursor end = (replacement != 0) ? afterReplace.start() : selection.start();
00579                     inputRange.setRange(inputRange.start(), end);
00580                 }
00581             }
00582 
00583             // Single-line pattern workaround
00584             fixForSingleLine(inputRange, searchDirection);
00585 
00586             match.searchText(inputRange, searchPattern());
00587     }
00588 
00589     const bool wrap = !match.isValid() && (!selection.isValid() || !selectionOnly());
00590     if (wrap) {
00591         inputRange = m_view->document()->documentRange();
00592         match.searchText(inputRange, searchPattern());
00593     }
00594 
00595     if (match.isValid()) {
00596         selectRange2(match.range());
00597     }
00598 
00599     const MatchResult matchResult = !match.isValid()                 ? MatchMismatch :
00600                                     !wrap                            ? MatchFound :
00601                                     searchDirection == SearchForward ? MatchWrappedForward :
00602                                                                        MatchWrappedBackward;
00603     indicateMatch(matchResult);
00604 
00605     // Reset highlighting for all matches and highlight replacement if there is one
00606     clearHighlights();
00607     if (afterReplace.isValid()) {
00608         highlightReplacement(afterReplace);
00609     }
00610 
00611     return true; // == No pattern error
00612 }
00613 
00614 
00615 
00616 
00617 void KateSearchBar::findAll()
00618 {
00619     clearHighlights();
00620     Range inputRange = (m_view->selection() && selectionOnly())
00621             ? m_view->selectionRange()
00622             : m_view->document()->documentRange();
00623     const int occurrences = findAll(inputRange, NULL);
00624 
00625     KPassivePopup::message(i18np("1 match found", "%1 matches found", occurrences), this);
00626 
00627     indicateMatch(occurrences > 0 ? MatchFound : MatchMismatch);
00628 }
00629 
00630 
00631 
00632 void KateSearchBar::onPowerPatternChanged(const QString & /*pattern*/) {
00633     givePatternFeedback();
00634     indicateMatch(MatchNothing);
00635 }
00636 
00637 
00638 
00639 bool KateSearchBar::isPatternValid() const {
00640     if (searchPattern().isEmpty())
00641         return false;
00642 
00643     return searchOptions().testFlag(Search::WholeWords) ? searchPattern().trimmed() == searchPattern() :
00644            searchOptions().testFlag(Search::Regex)      ? QRegExp(searchPattern()).isValid() :
00645                                                           true;
00646 }
00647 
00648 
00649 
00650 void KateSearchBar::givePatternFeedback() {
00651     // Enable/disable next/prev and replace next/all
00652     m_powerUi->findNext->setEnabled(isPatternValid());
00653     m_powerUi->findPrev->setEnabled(isPatternValid());
00654     m_powerUi->replaceNext->setEnabled(isPatternValid());
00655     m_powerUi->replaceAll->setEnabled(isPatternValid());
00656 }
00657 
00658 
00659 
00660 void KateSearchBar::addCurrentTextToHistory(QComboBox * combo) {
00661     const QString text = combo->currentText();
00662     const int index = combo->findText(text);
00663 
00664     if (index > 0)
00665         combo->removeItem(index);
00666     if (index != 0) {
00667         combo->insertItem(0, text);
00668         combo->setCurrentIndex(0);
00669     }
00670 }
00671 
00672 
00673 
00674 void KateSearchBar::backupConfig(bool ofPower) {
00675     if (ofPower) {
00676         m_powerMatchCase = m_powerUi->matchCase->isChecked();
00677         m_powerMode = m_powerUi->searchMode->currentIndex();
00678     } else {
00679         m_incMatchCase = m_incUi->matchCase->isChecked();
00680     }
00681 }
00682 
00683 
00684 
00685 void KateSearchBar::sendConfig() {
00686     const long pastFlags = m_config->searchFlags();
00687     long futureFlags = pastFlags;
00688 
00689     if (m_powerUi != NULL) {
00690         const bool OF_POWER = true;
00691         backupConfig(OF_POWER);
00692 
00693         // Update power search flags only
00694         const long incFlagsOnly = pastFlags
00695                 & (KateViewConfig::IncHighlightAll
00696                     | KateViewConfig::IncFromCursor
00697                     | KateViewConfig::IncMatchCase);
00698 
00699         futureFlags = incFlagsOnly
00700             | (m_powerMatchCase ? KateViewConfig::PowerMatchCase : 0)
00701             | (m_powerFromCursor ? KateViewConfig::PowerFromCursor : 0)
00702             | (m_powerHighlightAll ? KateViewConfig::PowerHighlightAll : 0)
00703             | ((m_powerMode == MODE_REGEX)
00704                 ? KateViewConfig::PowerModeRegularExpression
00705                 : ((m_powerMode == MODE_ESCAPE_SEQUENCES)
00706                     ? KateViewConfig::PowerModeEscapeSequences
00707                     : ((m_powerMode == MODE_WHOLE_WORDS)
00708                         ? KateViewConfig::PowerModeWholeWords
00709                         : KateViewConfig::PowerModePlainText)));
00710 
00711     } else if (m_incUi != NULL) {
00712         const bool OF_INCREMENTAL = false;
00713         backupConfig(OF_INCREMENTAL);
00714 
00715         // Update incremental search flags only
00716         const long powerFlagsOnly = pastFlags
00717                 & (KateViewConfig::PowerMatchCase
00718                     | KateViewConfig::PowerFromCursor
00719                     | KateViewConfig::PowerHighlightAll
00720                     | KateViewConfig::PowerModeRegularExpression
00721                     | KateViewConfig::PowerModeEscapeSequences
00722                     | KateViewConfig::PowerModeWholeWords
00723                     | KateViewConfig::PowerModePlainText);
00724 
00725         futureFlags = powerFlagsOnly
00726                 | (m_incHighlightAll ? KateViewConfig::IncHighlightAll : 0)
00727                 | (m_incFromCursor ? KateViewConfig::IncFromCursor : 0)
00728                 | (m_incMatchCase ? KateViewConfig::IncMatchCase : 0);
00729     }
00730 
00731     // Adjust global config
00732     m_config->setSearchFlags(futureFlags);
00733 }
00734 
00735 
00736 
00737 void KateSearchBar::replaceNext() {
00738     const QString replacement = m_powerUi->replacement->currentText();
00739 
00740     if (find(SearchForward, &replacement)) {
00741         // Add to search history
00742         addCurrentTextToHistory(m_powerUi->pattern);
00743 
00744         // Add to replace history
00745         addCurrentTextToHistory(m_powerUi->replacement);
00746     }
00747 }
00748 
00749 
00750 
00751 // replacement == NULL --> Highlight all matches
00752 // replacement != NULL --> Replace and highlight all matches
00753 int KateSearchBar::findAll(Range inputRange, const QString * replacement) {
00754     const Search::SearchOptions enabledOptions = searchOptions(SearchForward);
00755 
00756     const bool regexMode = enabledOptions.testFlag(Search::Regex);
00757     const bool multiLinePattern = regexMode ? KateRegExp(searchPattern()).isMultiLine() : false;
00758 
00759     // Before first match
00760     clearHighlights();
00761 
00762     KTextEditor::MovingRange * workingRange = m_view->doc()->newMovingRange(inputRange);
00763     QList<Range> highlightRanges;
00764     int matchCounter = 0;
00765 
00766     bool block = m_view->selection() && m_view->blockSelection();
00767     int line = inputRange.start().line();
00768     do {
00769         if (block)
00770             workingRange = m_view->doc()->newMovingRange(m_view->doc()->rangeOnLine(inputRange, line));
00771 
00772         for (;;) {
00773             KateMatch match(m_view->doc(), enabledOptions);
00774             match.searchText(*workingRange, searchPattern());
00775             if (!match.isValid()) {
00776                 break;
00777             }
00778             bool const originalMatchEmpty = match.isEmpty();
00779 
00780             // Work with the match
00781             if (replacement != NULL) {
00782                 if (matchCounter == 0) {
00783                     m_view->document()->startEditing();
00784                 }
00785 
00786                 // Replace
00787                 const Range afterReplace = match.replace(*replacement, false, ++matchCounter);
00788 
00789                 // Highlight and continue after adjusted match
00790                 //highlightReplacement(*afterReplace);
00791                 highlightRanges << afterReplace;
00792             } else {
00793                 // Highlight and continue after original match
00794                 //highlightMatch(match);
00795                 highlightRanges << match.range();
00796                 matchCounter++;
00797             }
00798 
00799             // Continue after match
00800             if (highlightRanges.last().end() >= workingRange->end())
00801                 break;
00802             KTextEditor::MovingCursor* workingStart =
00803                 static_cast<KateDocument*>(m_view->document())->newMovingCursor(highlightRanges.last().end());
00804             if (originalMatchEmpty) {
00805                 // Can happen for regex patterns like "^".
00806                 // If we don't advance here we will loop forever...
00807                 workingStart->move(1);
00808             } else if (regexMode && !multiLinePattern && workingStart->atEndOfLine()) {
00809                 // single-line regexps might match the naked line end
00810                 // therefore we better advance to the next line
00811                 workingStart->move(1);
00812             }
00813             workingRange->setRange(*workingStart, workingRange->end());
00814 
00815             const bool atEndOfDocument = workingStart->atEndOfDocument();
00816             delete workingStart;
00817             // Are we done?
00818             if (!workingRange->toRange().isValid() || atEndOfDocument) {
00819                 break;
00820             }
00821         }
00822 
00823     } while (block && ++line <= inputRange.end().line());
00824 
00825     // After last match
00826     if (matchCounter > 0) {
00827         if (replacement != NULL) {
00828             m_view->document()->endEditing();
00829         }
00830     }
00831 
00832     if (replacement == NULL)
00833         foreach (const Range &r, highlightRanges) {
00834             highlightMatch(r);
00835         }
00836     else
00837         foreach (const Range &r, highlightRanges) {
00838             highlightReplacement(r);
00839         }
00840 
00841     delete workingRange;
00842 
00843     return matchCounter;
00844 }
00845 
00846 
00847 
00848 void KateSearchBar::replaceAll() {
00849     // What to find/replace?
00850     const QString replacement = m_powerUi->replacement->currentText();
00851 
00852     // Where to replace?
00853     Range selection;
00854     const bool selected = m_view->selection();
00855     Range inputRange = (selected && selectionOnly())
00856             ? m_view->selectionRange()
00857             : m_view->document()->documentRange();
00858 
00859 
00860     // Pass on the hard work
00861     int replacementsDone=findAll(inputRange, &replacement);
00862     KPassivePopup::message(i18np("1 replacement has been made","%1 replacements have been made",replacementsDone),this);
00863 
00864     // Add to search history
00865     addCurrentTextToHistory(m_powerUi->pattern);
00866 
00867     // Add to replace history
00868     addCurrentTextToHistory(m_powerUi->replacement);
00869 }
00870 
00871 
00872 
00873 void KateSearchBar::setSearchPattern(const QString &searchPattern)
00874 {
00875     if (searchPattern == this->searchPattern())
00876         return;
00877 
00878     if (isPower())
00879         m_powerUi->pattern->setEditText(searchPattern);
00880     else
00881         m_incUi->pattern->setEditText(searchPattern);
00882 }
00883 
00884 
00885 
00886 QString KateSearchBar::searchPattern() const {
00887     return (m_powerUi != 0) ? m_powerUi->pattern->currentText()
00888                             : m_incUi->pattern->currentText();
00889 }
00890 
00891 
00892 
00893 void KateSearchBar::setSelectionOnly(bool selectionOnly)
00894 {
00895     if (this->selectionOnly() == selectionOnly)
00896         return;
00897 
00898     if (isPower())
00899         m_powerUi->selectionOnly->setChecked(selectionOnly);
00900 }
00901 
00902 
00903 
00904 
00905 bool KateSearchBar::selectionOnly() const {
00906     return isPower() ? m_powerUi->selectionOnly->isChecked()
00907                      : false;
00908 }
00909 
00910 
00911 
00912 KTextEditor::Search::SearchOptions KateSearchBar::searchOptions(SearchDirection searchDirection) const {
00913     Search::SearchOptions enabledOptions = KTextEditor::Search::Default;
00914 
00915     if (!matchCase()) {
00916         enabledOptions |= Search::CaseInsensitive;
00917     }
00918 
00919     if (searchDirection == SearchBackward) {
00920         enabledOptions |= Search::Backwards;
00921     }
00922 
00923     if (m_powerUi != NULL) {
00924         switch (m_powerUi->searchMode->currentIndex()) {
00925         case MODE_WHOLE_WORDS:
00926             enabledOptions |= Search::WholeWords;
00927             break;
00928 
00929         case MODE_ESCAPE_SEQUENCES:
00930             enabledOptions |= Search::EscapeSequences;
00931             break;
00932 
00933         case MODE_REGEX:
00934             enabledOptions |= Search::Regex;
00935             break;
00936 
00937         case MODE_PLAIN_TEXT: // FALLTHROUGH
00938         default:
00939             break;
00940 
00941         }
00942     }
00943 
00944     return enabledOptions;
00945 }
00946 
00947 
00948 
00949 
00950 struct ParInfo {
00951     int openIndex;
00952     bool capturing;
00953     int captureNumber; // 1..9
00954 };
00955 
00956 
00957 
00958 QVector<QString> KateSearchBar::getCapturePatterns(const QString & pattern) const {
00959     QVector<QString> capturePatterns;
00960     capturePatterns.reserve(9);
00961     QStack<ParInfo> parInfos;
00962 
00963     const int inputLen = pattern.length();
00964     int input = 0; // walker index
00965     bool insideClass = false;
00966     int captureCount = 0;
00967 
00968     while (input < inputLen) {
00969         if (insideClass) {
00970             // Wait for closing, unescaped ']'
00971             if (pattern[input].unicode() == L']') {
00972                 insideClass = false;
00973             }
00974             input++;
00975         }
00976         else
00977         {
00978             switch (pattern[input].unicode())
00979             {
00980             case L'\\':
00981                 // Skip this and any next character
00982                 input += 2;
00983                 break;
00984 
00985             case L'(':
00986                 ParInfo curInfo;
00987                 curInfo.openIndex = input;
00988                 curInfo.capturing = (input + 1 >= inputLen) || (pattern[input + 1].unicode() != '?');
00989                 if (curInfo.capturing) {
00990                     captureCount++;
00991                 }
00992                 curInfo.captureNumber = captureCount;
00993                 parInfos.push(curInfo);
00994 
00995                 input++;
00996                 break;
00997 
00998             case L')':
00999                 if (!parInfos.empty()) {
01000                     ParInfo & top = parInfos.top();
01001                     if (top.capturing && (top.captureNumber <= 9)) {
01002                         const int start = top.openIndex + 1;
01003                         const int len = input - start;
01004                         if (capturePatterns.size() < top.captureNumber) {
01005                             capturePatterns.resize(top.captureNumber);
01006                         }
01007                         capturePatterns[top.captureNumber - 1] = pattern.mid(start, len);
01008                     }
01009                     parInfos.pop();
01010                 }
01011 
01012                 input++;
01013                 break;
01014 
01015             case L'[':
01016                 input++;
01017                 insideClass = true;
01018                 break;
01019 
01020             default:
01021                 input++;
01022                 break;
01023 
01024             }
01025         }
01026     }
01027 
01028     return capturePatterns;
01029 }
01030 
01031 
01032 
01033 void KateSearchBar::showExtendedContextMenu(bool forPattern, const QPoint& pos) {
01034     // Make original menu
01035     QComboBox* comboBox = forPattern ? m_powerUi->pattern : m_powerUi->replacement;
01036     QMenu* const contextMenu = comboBox->lineEdit()->createStandardContextMenu();
01037 
01038     if (contextMenu == NULL) {
01039         return;
01040     }
01041 
01042     bool extendMenu = false;
01043     bool regexMode = false;
01044     switch (m_powerUi->searchMode->currentIndex()) {
01045     case MODE_REGEX:
01046         regexMode = true;
01047         // FALLTHROUGH
01048 
01049     case MODE_ESCAPE_SEQUENCES:
01050         extendMenu = true;
01051         break;
01052 
01053     default:
01054         break;
01055     }
01056 
01057     AddMenuManager addMenuManager(contextMenu, 37);
01058     if (!extendMenu) {
01059         addMenuManager.enableMenu(extendMenu);
01060     } else {
01061         // Build menu
01062         if (forPattern) {
01063             if (regexMode) {
01064                 addMenuManager.addEntry("^", "", i18n("Beginning of line"));
01065                 addMenuManager.addEntry("$", "", i18n("End of line"));
01066                 addMenuManager.addSeparator();
01067                 addMenuManager.addEntry(".", "", i18n("Any single character (excluding line breaks)"));
01068                 addMenuManager.addSeparator();
01069                 addMenuManager.addEntry("+", "", i18n("One or more occurrences"));
01070                 addMenuManager.addEntry("*", "", i18n("Zero or more occurrences"));
01071                 addMenuManager.addEntry("?", "", i18n("Zero or one occurrences"));
01072                 addMenuManager.addEntry("{a", ",b}", i18n("<a> through <b> occurrences"), "{", ",}");
01073                 addMenuManager.addSeparator();
01074                 addMenuManager.addEntry("(", ")", i18n("Group, capturing"));
01075                 addMenuManager.addEntry("|", "", i18n("Or"));
01076                 addMenuManager.addEntry("[", "]", i18n("Set of characters"));
01077                 addMenuManager.addEntry("[^", "]", i18n("Negative set of characters"));
01078                 addMenuManager.addSeparator();
01079             }
01080         } else {
01081             addMenuManager.addEntry("\\0", "", i18n("Whole match reference"));
01082             addMenuManager.addSeparator();
01083             if (regexMode) {
01084                 const QString pattern = m_powerUi->pattern->currentText();
01085                 const QVector<QString> capturePatterns = getCapturePatterns(pattern);
01086 
01087                 const int captureCount = capturePatterns.count();
01088                 for (int i = 1; i <= 9; i++) {
01089                     const QString number = QString::number(i);
01090                     const QString & captureDetails = (i <= captureCount)
01091                             ? (QString(" = (") + capturePatterns[i - 1].left(30)) + QString(")")
01092                             : QString();
01093                     addMenuManager.addEntry("\\" + number, "",
01094                             i18n("Reference") + ' ' + number + captureDetails);
01095                 }
01096 
01097                 addMenuManager.addSeparator();
01098             }
01099         }
01100 
01101         addMenuManager.addEntry("\\n", "", i18n("Line break"));
01102         addMenuManager.addEntry("\\t", "", i18n("Tab"));
01103 
01104         if (forPattern && regexMode) {
01105             addMenuManager.addEntry("\\b", "", i18n("Word boundary"));
01106             addMenuManager.addEntry("\\B", "", i18n("Not word boundary"));
01107             addMenuManager.addEntry("\\d", "", i18n("Digit"));
01108             addMenuManager.addEntry("\\D", "", i18n("Non-digit"));
01109             addMenuManager.addEntry("\\s", "", i18n("Whitespace (excluding line breaks)"));
01110             addMenuManager.addEntry("\\S", "", i18n("Non-whitespace (excluding line breaks)"));
01111             addMenuManager.addEntry("\\w", "", i18n("Word character (alphanumerics plus '_')"));
01112             addMenuManager.addEntry("\\W", "", i18n("Non-word character"));
01113         }
01114 
01115         addMenuManager.addEntry("\\0???", "", i18n("Octal character 000 to 377 (2^8-1)"), "\\0");
01116         addMenuManager.addEntry("\\x????", "", i18n("Hex character 0000 to FFFF (2^16-1)"), "\\x");
01117         addMenuManager.addEntry("\\\\", "", i18n("Backslash"));
01118 
01119         if (forPattern && regexMode) {
01120             addMenuManager.addSeparator();
01121             addMenuManager.addEntry("(?:E", ")", i18n("Group, non-capturing"), "(?:");
01122             addMenuManager.addEntry("(?=E", ")", i18n("Lookahead"), "(?=");
01123             addMenuManager.addEntry("(?!E", ")", i18n("Negative lookahead"), "(?!");
01124         }
01125 
01126         if (!forPattern) {
01127             addMenuManager.addSeparator();
01128             addMenuManager.addEntry("\\L", "", i18n("Begin lowercase conversion"));
01129             addMenuManager.addEntry("\\U", "", i18n("Begin uppercase conversion"));
01130             addMenuManager.addEntry("\\E", "", i18n("End case conversion"));
01131             addMenuManager.addEntry("\\l", "", i18n("Lowercase first character conversion"));
01132             addMenuManager.addEntry("\\u", "", i18n("Uppercase first character conversion"));
01133             addMenuManager.addEntry("\\#[#..]", "", i18n("Replacement counter (for Replace All)"), "\\#");
01134         }
01135     }
01136 
01137     // Show menu
01138     QAction * const result = contextMenu->exec(comboBox->mapToGlobal(pos));
01139     if (result != NULL) {
01140         addMenuManager.handle(result, comboBox->lineEdit());
01141     }
01142 }
01143 
01144 
01145 
01146 void KateSearchBar::onPowerModeChanged(int /*index*/) {
01147     if (m_powerUi->searchMode->currentIndex() == MODE_REGEX) {
01148         m_powerUi->matchCase->setChecked(true);
01149     }
01150 
01151     sendConfig();
01152     indicateMatch(MatchNothing);
01153 
01154     givePatternFeedback();
01155 }
01156 
01157 
01158 
01159 /*static*/ void KateSearchBar::nextMatchForSelection(KateView * view, SearchDirection searchDirection) {
01160     const bool selected = view->selection();
01161     if (selected) {
01162         const QString pattern = view->selectionText();
01163 
01164         // How to find?
01165         Search::SearchOptions enabledOptions(KTextEditor::Search::Default);
01166         if (searchDirection == SearchBackward) {
01167             enabledOptions |= Search::Backwards;
01168         }
01169 
01170         // Where to find?
01171         const Range selRange = view->selectionRange();
01172         Range inputRange;
01173         if (searchDirection == SearchForward) {
01174             inputRange.setRange(selRange.end(), view->doc()->documentEnd());
01175         } else {
01176             inputRange.setRange(Cursor(0, 0), selRange.start());
01177         }
01178 
01179         // Find, first try
01180         KateMatch match(view->doc(), enabledOptions);
01181         match.searchText(inputRange, pattern);
01182 
01183         if (match.isValid()) {
01184             selectRange(view, match.range());
01185         } else {
01186             // Find, second try
01187             if (searchDirection == SearchForward) {
01188                 inputRange.setRange(Cursor(0, 0), selRange.start());
01189             } else {
01190                 inputRange.setRange(selRange.end(), view->doc()->documentEnd());
01191             }
01192             KateMatch match2(view->doc(), enabledOptions);
01193             match2.searchText(inputRange, pattern);
01194             if (match2.isValid()) {
01195                 selectRange(view, match2.range());
01196             }
01197         }
01198     } else {
01199         // Select current word so we can search for that the next time
01200         const Cursor cursorPos = view->cursorPosition();
01201         view->selectWord(cursorPos);
01202     }
01203 }
01204 
01205 
01206 
01207 void KateSearchBar::enterPowerMode() {
01208     QString initialPattern;
01209     bool selectionOnly = false;
01210 
01211     // Guess settings from context: init pattern with current selection
01212     const bool selected = m_view->selection();
01213     if (selected) {
01214         const Range & selection = m_view->selectionRange();
01215         if (selection.onSingleLine()) {
01216             // ... with current selection
01217             initialPattern = m_view->selectionText();
01218         } else {
01219             // Enable selection only
01220             selectionOnly = true;
01221         }
01222     }
01223 
01224     // If there's no new selection, we'll use the existing pattern
01225     if (initialPattern.isNull()) {
01226         // Coming from power search?
01227         const bool fromReplace = (m_powerUi != NULL) && (m_widget->isVisible());
01228         if (fromReplace) {
01229             QLineEdit * const patternLineEdit = m_powerUi->pattern->lineEdit();
01230             Q_ASSERT(patternLineEdit != NULL);
01231             patternLineEdit->selectAll();
01232             m_powerUi->pattern->setFocus(Qt::MouseFocusReason);
01233             return;
01234         }
01235 
01236         // Coming from incremental search?
01237         const bool fromIncremental = (m_incUi != NULL) && (m_widget->isVisible());
01238         if (fromIncremental) {
01239             initialPattern = m_incUi->pattern->currentText();
01240         }
01241     }
01242 
01243     // Create dialog
01244     const bool create = (m_powerUi == NULL);
01245     if (create) {
01246         // Kill incremental widget
01247         if (m_incUi != NULL) {
01248             // Backup current settings
01249             const bool OF_INCREMENTAL = false;
01250             backupConfig(OF_INCREMENTAL);
01251 
01252             // Kill widget
01253             delete m_incUi;
01254             m_incUi = NULL;
01255             m_layout->removeWidget(m_widget);
01256             m_widget->deleteLater(); // I didn't get a crash here but for symmetrie to the other mutate slot^
01257         }
01258 
01259         // Add power widget
01260         m_widget = new QWidget(this);
01261         m_powerUi = new Ui::PowerSearchBar;
01262         m_powerUi->setupUi(m_widget);
01263         m_layout->addWidget(m_widget);
01264 
01265         // Bind to shared history models
01266         m_powerUi->pattern->setDuplicatesEnabled(false);
01267         m_powerUi->pattern->setInsertPolicy(QComboBox::InsertAtTop);
01268         m_powerUi->pattern->setMaxCount(m_config->maxHistorySize());
01269         m_powerUi->pattern->setModel(m_config->patternHistoryModel());
01270         m_powerUi->replacement->setDuplicatesEnabled(false);
01271         m_powerUi->replacement->setInsertPolicy(QComboBox::InsertAtTop);
01272         m_powerUi->replacement->setMaxCount(m_config->maxHistorySize());
01273         m_powerUi->replacement->setModel(m_config->replacementHistoryModel());
01274 
01275         // Icons
01276         m_powerUi->mutate->setIcon(KIcon("arrow-down-double"));
01277         m_powerUi->findNext->setIcon(KIcon("go-down-search"));
01278         m_powerUi->findPrev->setIcon(KIcon("go-up-search"));
01279         m_powerUi->findAll->setIcon(KIcon("edit-find"));
01280 
01281         // Focus proxy
01282         centralWidget()->setFocusProxy(m_powerUi->pattern);
01283 
01284         // Make completers case-sensitive
01285         QLineEdit * const patternLineEdit = m_powerUi->pattern->lineEdit();
01286         Q_ASSERT(patternLineEdit != NULL);
01287         patternLineEdit->completer()->setCaseSensitivity(Qt::CaseSensitive);
01288 
01289         QLineEdit * const replacementLineEdit = m_powerUi->replacement->lineEdit();
01290         Q_ASSERT(replacementLineEdit != NULL);
01291         replacementLineEdit->completer()->setCaseSensitivity(Qt::CaseSensitive);
01292     }
01293 
01294     m_powerUi->selectionOnly->setChecked(selectionOnly);
01295 
01296     // Restore previous settings
01297     if (create) {
01298         m_powerUi->matchCase->setChecked(m_powerMatchCase);
01299         m_powerUi->searchMode->setCurrentIndex(m_powerMode);
01300     }
01301 
01302     // force current index of -1 --> <cursor down> shows 1st completion entry instead of 2nd
01303     m_powerUi->pattern->setCurrentIndex(-1);
01304     m_powerUi->replacement->setCurrentIndex(-1);
01305 
01306     // Set initial search pattern
01307     QLineEdit * const patternLineEdit = m_powerUi->pattern->lineEdit();
01308     Q_ASSERT(patternLineEdit != NULL);
01309     patternLineEdit->setText(initialPattern);
01310     patternLineEdit->selectAll();
01311 
01312     // Set initial replacement text
01313     QLineEdit * const replacementLineEdit = m_powerUi->replacement->lineEdit();
01314     Q_ASSERT(replacementLineEdit != NULL);
01315     replacementLineEdit->setText("");
01316 
01317     // Propagate settings (slots are still inactive on purpose)
01318     onPowerPatternChanged(initialPattern);
01319     givePatternFeedback();
01320 
01321     if (create) {
01322         // Slots
01323         connect(m_powerUi->mutate, SIGNAL(clicked()), this, SLOT(enterIncrementalMode()));
01324         connect(patternLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(onPowerPatternChanged(const QString &)));
01325         connect(m_powerUi->findNext, SIGNAL(clicked()), this, SLOT(findNext()));
01326         connect(m_powerUi->findPrev, SIGNAL(clicked()), this, SLOT(findPrevious()));
01327         connect(m_powerUi->replaceNext, SIGNAL(clicked()), this, SLOT(replaceNext()));
01328         connect(m_powerUi->replaceAll, SIGNAL(clicked()), this, SLOT(replaceAll()));
01329         connect(m_powerUi->searchMode, SIGNAL(currentIndexChanged(int)), this, SLOT(onPowerModeChanged(int)));
01330         connect(m_powerUi->matchCase, SIGNAL(toggled(bool)), this, SLOT(onMatchCaseToggled(bool)));
01331         connect(m_powerUi->findAll, SIGNAL(clicked()), this, SLOT(findAll()));
01332 
01333         // Make [return] in pattern line edit trigger <find next> action
01334         connect(patternLineEdit, SIGNAL(returnPressed()), this, SLOT(onReturnPressed()));
01335         connect(replacementLineEdit, SIGNAL(returnPressed()), this, SLOT(replaceNext()));
01336 
01337         // Hook into line edit context menus
01338         m_powerUi->pattern->setContextMenuPolicy(Qt::CustomContextMenu);
01339         connect(m_powerUi->pattern, SIGNAL(customContextMenuRequested(const QPoint&)), this,
01340                 SLOT(onPowerPatternContextMenuRequest(const QPoint&)));
01341         m_powerUi->replacement->setContextMenuPolicy(Qt::CustomContextMenu);
01342         connect(m_powerUi->replacement, SIGNAL(customContextMenuRequested(const QPoint&)), this,
01343                 SLOT(onPowerReplacmentContextMenuRequest(const QPoint&)));
01344     }
01345 
01346     // Focus
01347     if (m_widget->isVisible()) {
01348         m_powerUi->pattern->setFocus(Qt::MouseFocusReason);
01349     }
01350 }
01351 
01352 
01353 
01354 void KateSearchBar::enterIncrementalMode() {
01355     QString initialPattern;
01356 
01357     // Guess settings from context: init pattern with current selection
01358     const bool selected = m_view->selection();
01359     if (selected) {
01360         const Range & selection = m_view->selectionRange();
01361         if (selection.onSingleLine()) {
01362             // ... with current selection
01363             initialPattern = m_view->selectionText();
01364         }
01365     }
01366 
01367     // If there's no new selection, we'll use the existing pattern
01368     if (initialPattern.isNull()) {
01369         // Coming from incremental search?
01370         const bool fromIncremental = (m_incUi != NULL) && (m_widget->isVisible());
01371         if (fromIncremental) {
01372             m_incUi->pattern->lineEdit()->selectAll();
01373             m_incUi->pattern->setFocus(Qt::MouseFocusReason);
01374             return;
01375         }
01376 
01377         // Coming from power search?
01378         const bool fromReplace = (m_powerUi != NULL) && (m_widget->isVisible());
01379         if (fromReplace) {
01380             initialPattern = m_powerUi->pattern->currentText();
01381         }
01382     }
01383 
01384     // Still no search pattern? Use the word under the cursor
01385     if (initialPattern.isNull()) {
01386         const KTextEditor::Cursor cursorPosition = m_view->cursorPosition();
01387         initialPattern = m_view->doc()->getWord( cursorPosition );
01388     }
01389 
01390     // Create dialog
01391     const bool create = (m_incUi == NULL);
01392     if (create) {
01393         // Kill power widget
01394         if (m_powerUi != NULL) {
01395             // Backup current settings
01396             const bool OF_POWER = true;
01397             backupConfig(OF_POWER);
01398 
01399             // Kill widget
01400             delete m_powerUi;
01401             m_powerUi = NULL;
01402             m_layout->removeWidget(m_widget);
01403             m_widget->deleteLater(); //deleteLater, because it's not a good idea too delete the widget and there for the button triggering this slot
01404         }
01405 
01406         // Add incremental widget
01407         m_widget = new QWidget(this);
01408         m_incUi = new Ui::IncrementalSearchBar;
01409         m_incUi->setupUi(m_widget);
01410         m_layout->addWidget(m_widget);
01411 
01412 //         new QShortcut(KStandardShortcut::paste().primary(), m_incUi->pattern, SLOT(paste()), 0, Qt::WidgetWithChildrenShortcut);
01413 //         if (!KStandardShortcut::paste().alternate().isEmpty())
01414 //             new QShortcut(KStandardShortcut::paste().alternate(), m_incUi->pattern, SLOT(paste()), 0, Qt::WidgetWithChildrenShortcut);
01415 
01416 
01417         // Icons
01418         m_incUi->mutate->setIcon(KIcon("arrow-up-double"));
01419         m_incUi->next->setIcon(KIcon("go-down-search"));
01420         m_incUi->prev->setIcon(KIcon("go-up-search"));
01421 
01422         // Ensure minimum size
01423         m_incUi->pattern->setMinimumWidth(12 * m_incUi->pattern->fontMetrics().height());
01424 
01425     // Customize status area
01426     m_incUi->status->setTextElideMode(Qt::ElideLeft);
01427 
01428         // Focus proxy
01429         centralWidget()->setFocusProxy(m_incUi->pattern);
01430 
01431         m_incUi->pattern->setDuplicatesEnabled(false);
01432         m_incUi->pattern->setInsertPolicy(QComboBox::InsertAtTop);
01433         m_incUi->pattern->setMaxCount(m_config->maxHistorySize());
01434         m_incUi->pattern->setModel(m_config->patternHistoryModel());
01435         m_incUi->pattern->setAutoCompletion(false);
01436     }
01437 
01438     // Restore previous settings
01439     if (create) {
01440         m_incUi->matchCase->setChecked(m_incMatchCase);
01441     }
01442 
01443     // force current index of -1 --> <cursor down> shows 1st completion entry instead of 2nd
01444     m_incUi->pattern->setCurrentIndex(-1);
01445 
01446     // Set initial search pattern
01447     if (!create)
01448         disconnect(m_incUi->pattern, SIGNAL(textChanged(const QString&)), this, SLOT(onIncPatternChanged(const QString&)));
01449     m_incUi->pattern->setEditText(initialPattern);
01450     connect(m_incUi->pattern, SIGNAL(textChanged(const QString&)), this, SLOT(onIncPatternChanged(const QString&)));
01451     m_incUi->pattern->lineEdit()->selectAll();
01452 
01453     // Propagate settings (slots are still inactive on purpose)
01454     if (initialPattern.isEmpty()) {
01455         // Reset edit color
01456         indicateMatch(MatchNothing);
01457     }
01458 
01459     // Enable/disable next/prev
01460     m_incUi->next->setDisabled(initialPattern.isEmpty());
01461     m_incUi->prev->setDisabled(initialPattern.isEmpty());
01462 
01463     if (create) {
01464         // Slots
01465         connect(m_incUi->mutate, SIGNAL(clicked()), this, SLOT(enterPowerMode()));
01466         connect(m_incUi->pattern->lineEdit(), SIGNAL(returnPressed()), this, SLOT(onReturnPressed()));
01467         connect(m_incUi->next, SIGNAL(clicked()), this, SLOT(findNext()));
01468         connect(m_incUi->prev, SIGNAL(clicked()), this, SLOT(findPrevious()));
01469         connect(m_incUi->matchCase, SIGNAL(toggled(bool)), this, SLOT(onMatchCaseToggled(bool)));
01470     }
01471 
01472     // Focus
01473     if (m_widget->isVisible()) {
01474         m_incUi->pattern->setFocus(Qt::MouseFocusReason);
01475     }
01476 }
01477 
01478 
01479 void KateSearchBar::clearHighlights() {
01480     qDeleteAll(m_hlRanges);
01481     m_hlRanges.clear();
01482 }
01483 
01484 
01485 void KateSearchBar::showEvent(QShowEvent * event) {
01486     // Update init cursor
01487     if (m_incUi != NULL) {
01488         m_incInitCursor = m_view->cursorPosition();
01489     }
01490 
01491     updateSelectionOnly();
01492     KateViewBarWidget::showEvent(event);
01493 }
01494 
01495 
01496 void KateSearchBar::updateSelectionOnly() {
01497     if (m_powerUi == NULL) {
01498         return;
01499     }
01500 
01501     // Re-init "Selection only" checkbox if power search bar open
01502     const bool selected = m_view->selection();
01503     bool selectionOnly = selected;
01504     if (selected) {
01505         Range const & selection = m_view->selectionRange();
01506         selectionOnly = !selection.onSingleLine();
01507     }
01508     m_powerUi->selectionOnly->setChecked(selectionOnly);
01509 }
01510 
01511 
01512 void KateSearchBar::updateIncInitCursor() {
01513     if (m_incUi == NULL) {
01514         return;
01515     }
01516 
01517     // Update init cursor
01518     m_incInitCursor = m_view->cursorPosition();
01519 }
01520 
01521 
01522 void KateSearchBar::onPowerPatternContextMenuRequest(const QPoint& pos) {
01523     const bool FOR_PATTERN = true;
01524     showExtendedContextMenu(FOR_PATTERN, pos);
01525 }
01526 
01527 void KateSearchBar::onPowerPatternContextMenuRequest() {
01528     onPowerPatternContextMenuRequest(m_powerUi->pattern->mapFromGlobal(QCursor::pos()));
01529 }
01530 
01531 
01532 void KateSearchBar::onPowerReplacmentContextMenuRequest(const QPoint& pos) {
01533     const bool FOR_REPLACEMENT = false;
01534     showExtendedContextMenu(FOR_REPLACEMENT, pos);
01535 }
01536 
01537 void KateSearchBar::onPowerReplacmentContextMenuRequest() {
01538     onPowerReplacmentContextMenuRequest(m_powerUi->replacement->mapFromGlobal(QCursor::pos()));
01539 }
01540 
01541 
01542 bool KateSearchBar::isPower() const {
01543     return m_powerUi != 0;
01544 }
01545 
01546 // kate: space-indent on; indent-width 4; replace-tabs on;

Kate

Skip menu "Kate"
  • Main Page
  • 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
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
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