• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

Kate

katesearchbar.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE libraries
00002    Copyright (C) 2007 Sebastian Pipping <webmaster@hartwork.org>
00003    Copyright (C) 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
00004    Copyright (C) 2007 Thomas Friedrichsmeier <thomas.friedrichsmeier@ruhr-uni-bochum.de>
00005 
00006    This library is free software; you can redistribute it and/or
00007    modify it under the terms of the GNU Library General Public
00008    License version 2 as published by the Free Software Foundation.
00009 
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public License
00016    along with this library; see the file COPYING.LIB.  If not, write to
00017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018    Boston, MA 02110-1301, USA.
00019 */
00020 
00021 #include "katesearchbar.h"
00022 #include "kateview.h"
00023 #include "katedocument.h"
00024 #include "kateglobal.h"
00025 
00026 #include "ui_searchbarincremental.h"
00027 #include "ui_searchbarpower.h"
00028 
00029 #include <kactioncollection.h>
00030 
00031 #include <QtGui/QVBoxLayout>
00032 #include <QtGui/QComboBox>
00033 #include <QtGui/QCheckBox>
00034 #include <QStringListModel>
00035 #include <QCompleter>
00036 
00037 using namespace KTextEditor;
00038 
00039 
00040 
00041 // Turn debug messages on/off here
00042 // #define FAST_DEBUG_ENABLE
00043 
00044 #ifdef FAST_DEBUG_ENABLE
00045 # define FAST_DEBUG(x) (kDebug() << x)
00046 #else
00047 # define FAST_DEBUG(x)
00048 #endif
00049 
00050 
00051 
00052 KateSearchBar::KateSearchBar(KateViewBar * viewBar, bool initAsPower)
00053         : KateViewBarWidget(viewBar),
00054         m_view(viewBar->view()),
00055         m_topRange(NULL),
00056         m_layout(new QVBoxLayout()),
00057         m_widget(NULL),
00058         m_incUi(NULL),
00059         m_incMenu(NULL),
00060         m_incMenuMatchCase(NULL),
00061         m_incMenuFromCursor(NULL),
00062         m_incMenuHighlightAll(NULL),
00063         m_incInitCursor(0, 0),
00064         m_powerUi(NULL),
00065         m_incHighlightAll(false),
00066         m_incFromCursor(true),
00067         m_incMatchCase(false),
00068         m_powerMatchCase(true),
00069         m_powerFromCursor(false),
00070         m_powerHighlightAll(false),
00071         m_powerUsePlaceholders(false),
00072         m_powerMode(0) {
00073     // Modify parent
00074     QWidget * const widget = centralWidget();
00075     widget->setLayout(m_layout);
00076     m_layout->setMargin(2);
00077 
00078     // Init highlight
00079     m_topRange = m_view->doc()->newSmartRange(m_view->doc()->documentRange());
00080     static_cast<KateSmartRange*>(m_topRange)->setInternal();
00081     m_topRange->setInsertBehavior(SmartRange::ExpandLeft | SmartRange::ExpandRight);
00082     enableHighlights(true);
00083 
00084 
00085     // Copy global to local config backup
00086     KateViewConfig * const globalConfig = KateGlobal::self()->viewConfig();
00087     const long searchFlags = globalConfig->searchFlags();
00088     m_incHighlightAll = (searchFlags & KateViewConfig::IncHighlightAll) != 0;
00089     m_incFromCursor = (searchFlags & KateViewConfig::IncFromCursor) != 0;
00090     m_incMatchCase = (searchFlags & KateViewConfig::IncMatchCase) != 0;
00091     m_powerMatchCase = (searchFlags & KateViewConfig::PowerMatchCase) != 0;
00092     m_powerFromCursor = (searchFlags & KateViewConfig::PowerFromCursor) != 0;
00093     m_powerHighlightAll = (searchFlags & KateViewConfig::PowerHighlightAll) != 0;
00094     m_powerUsePlaceholders = (searchFlags & KateViewConfig::PowerUsePlaceholders) != 0;
00095     m_powerMode = ((searchFlags & KateViewConfig::PowerModeRegularExpression) != 0)
00096             ? MODE_REGEX
00097             : (((searchFlags & KateViewConfig::PowerModeEscapeSequences) != 0)
00098                 ? MODE_ESCAPE_SEQUENCES
00099                 : (((searchFlags & KateViewConfig::PowerModeWholeWords) != 0)
00100                     ? MODE_WHOLE_WORDS
00101                     : MODE_PLAIN_TEXT));
00102 
00103 
00104     // Load one of either dialogs
00105     if (initAsPower) {
00106         onMutatePower();
00107     } else {
00108         onMutateIncremental();
00109     }
00110 }
00111 
00112 
00113 
00114 KateSearchBar::~KateSearchBar() {
00115     delete m_topRange;
00116     delete m_layout;
00117     delete m_widget;
00118     delete m_incUi;
00119     delete m_incMenu;
00120     delete m_powerUi;
00121 }
00122 
00123 
00124 
00125 void KateSearchBar::findNext() {
00126     if (m_incUi != NULL) {
00127         onIncNext();
00128     } else {
00129         onPowerFindNext();
00130     }
00131 }
00132 
00133 
00134 
00135 void KateSearchBar::findPrevious() {
00136     if (m_incUi != NULL) {
00137         onIncPrev();
00138     } else {
00139         onPowerFindPrev();
00140     }
00141 }
00142 
00143 
00144 
00145 void KateSearchBar::highlight(const Range & range, const QColor & color) {
00146     SmartRange * const highlight = m_view->doc()->newSmartRange(range, m_topRange);
00147     highlight->setInsertBehavior(SmartRange::DoNotExpand);
00148     Attribute::Ptr attribute(new Attribute());
00149     attribute->setBackground(color);
00150     highlight->setAttribute(attribute);
00151 }
00152 
00153 
00154 
00155 void KateSearchBar::highlightMatch(const Range & range) {
00156     highlight(range, Qt::yellow); // TODO make this part of the color scheme
00157 }
00158 
00159 
00160 
00161 void KateSearchBar::highlightReplacement(const Range & range) {
00162     highlight(range, Qt::green); // TODO make this part of the color scheme
00163 }
00164 
00165 
00166 
00167 void KateSearchBar::highlightAllMatches(const QString & pattern,
00168         Search::SearchOptions searchOptions) {
00169     onForAll(pattern, m_view->doc()->documentRange(),
00170             searchOptions, NULL);
00171 }
00172 
00173 
00174 
00175 void KateSearchBar::indicateMatch(bool wrapped) {
00176     if (m_incUi != NULL) {
00177         // Green background for line edit
00178         QPalette background(m_incUi->pattern->palette());
00179         KColorScheme::adjustBackground(background, KColorScheme::PositiveBackground);
00180         m_incUi->pattern->setPalette(background);
00181 
00182         // Update status label
00183         m_incUi->status->setText(wrapped
00184                 ? i18n("Reached bottom, continued from top")
00185                 : "");
00186     } else {
00187         // Green background for line edit
00188         QLineEdit * const lineEdit = m_powerUi->pattern->lineEdit();
00189         Q_ASSERT(lineEdit != NULL);
00190         QPalette background(lineEdit->palette());
00191         KColorScheme::adjustBackground(background, KColorScheme::PositiveBackground);
00192         lineEdit->setPalette(background);
00193     }
00194 }
00195 
00196 
00197 
00198 void KateSearchBar::indicateMismatch() {
00199     if (m_incUi != NULL) {
00200         // Red background for line edit
00201         QPalette background(m_incUi->pattern->palette());
00202         KColorScheme::adjustBackground(background, KColorScheme::NegativeBackground);
00203         m_incUi->pattern->setPalette(background);
00204 
00205         // Update status label
00206         m_incUi->status->setText(i18n("Not found"));
00207     } else {
00208         // Red background for line edit
00209         QLineEdit * const lineEdit = m_powerUi->pattern->lineEdit();
00210         Q_ASSERT(lineEdit != NULL);
00211         QPalette background(lineEdit->palette());
00212         KColorScheme::adjustBackground(background, KColorScheme::NegativeBackground);
00213         lineEdit->setPalette(background);
00214     }
00215 }
00216 
00217 
00218 
00219 void KateSearchBar::indicateNothing() {
00220     if (m_incUi != NULL) {
00221         // Reset background of line edit
00222         m_incUi->pattern->setPalette(QPalette());
00223 
00224         // Update status label
00225         m_incUi->status->setText("");
00226     } else {
00227         // Reset background of line edit
00228         QLineEdit * const lineEdit = m_powerUi->pattern->lineEdit();
00229         Q_ASSERT(lineEdit != NULL);
00230         // ### this is fragile (depends on knowledge of QPalette::ColorGroup)
00231         // ...would it better to cache the original palette?
00232         QColor color = QPalette().color(QPalette::Base);
00233         QPalette background(lineEdit->palette());
00234         background.setBrush(QPalette::Active, QPalette::Base, QPalette().brush(QPalette::Active, QPalette::Base));
00235         background.setBrush(QPalette::Inactive, QPalette::Base, QPalette().brush(QPalette::Inactive, QPalette::Base));
00236         background.setBrush(QPalette::Disabled, QPalette::Base, QPalette().brush(QPalette::Disabled, QPalette::Base));
00237         lineEdit->setPalette(background);
00238     }
00239 }
00240 
00241 
00242 
00243 /*static*/ void KateSearchBar::selectRange(KateView * view, const KTextEditor::Range & range) {
00244     view->setCursorPositionInternal(range.start(), 1);
00245     view->setSelection(range);
00246 }
00247 
00248 
00249 
00250 void KateSearchBar::buildReplacement(QString & output, QList<ReplacementPart> & parts,
00251         const QVector<Range> & details, int replacementCounter) {
00252     const int MIN_REF_INDEX = 0;
00253     const int MAX_REF_INDEX = details.count() - 1;
00254 
00255     output.clear();
00256     ReplacementPart::Type caseConversion = ReplacementPart::KeepCase;
00257     for (QList<ReplacementPart>::iterator iter = parts.begin(); iter != parts.end(); iter++) {
00258         ReplacementPart & curPart = *iter;
00259         switch (curPart.type) {
00260         case ReplacementPart::Reference:
00261             if ((curPart.index < MIN_REF_INDEX) || (curPart.index > MAX_REF_INDEX)) {
00262                 // Insert just the number to be consistent with QRegExp ("\c" becomes "c")
00263                 output.append(QString::number(curPart.index));
00264             } else {
00265                 const Range & captureRange = details[curPart.index];
00266                 if (captureRange.isValid()) {
00267                     // Copy capture content
00268                     const bool blockMode = m_view->blockSelection();
00269                     const QString content = m_view->doc()->text(captureRange, blockMode);
00270                     switch (caseConversion) {
00271                     case ReplacementPart::UpperCase:
00272                         // Copy as uppercase
00273                         output.append(content.toUpper());
00274                         break;
00275 
00276                     case ReplacementPart::LowerCase:
00277                         // Copy as lowercase
00278                         output.append(content.toLower());
00279                         break;
00280 
00281                     case ReplacementPart::KeepCase: // FALLTHROUGH
00282                     default:
00283                         // Copy unmodified
00284                         output.append(content);
00285                         break;
00286 
00287                     }
00288                 }
00289             }
00290             break;
00291 
00292         case ReplacementPart::UpperCase: // FALLTHROUGH
00293         case ReplacementPart::LowerCase: // FALLTHROUGH
00294         case ReplacementPart::KeepCase:
00295             caseConversion = curPart.type;
00296             break;
00297 
00298         case ReplacementPart::Counter:
00299             {
00300                 // Zero padded counter value
00301                 const int minWidth = curPart.index;
00302                 const int number = replacementCounter;
00303                 output.append(QString("%1").arg(number, minWidth, 10, QLatin1Char('0')));
00304             }
00305             break;
00306 
00307         case ReplacementPart::Text: // FALLTHROUGH
00308         default:
00309             switch (caseConversion) {
00310             case ReplacementPart::UpperCase:
00311                 // Copy as uppercase
00312                 output.append(curPart.text.toUpper());
00313                 break;
00314 
00315             case ReplacementPart::LowerCase:
00316                 // Copy as lowercase
00317                 output.append(curPart.text.toLower());
00318                 break;
00319 
00320             case ReplacementPart::KeepCase: // FALLTHROUGH
00321             default:
00322                 // Copy unmodified
00323                 output.append(curPart.text);
00324                 break;
00325 
00326             }
00327             break;
00328 
00329         }
00330     }
00331 }
00332 
00333 
00334 
00335 void KateSearchBar::replaceMatch(const QVector<Range> & match, const QString & replacement,
00336         int replacementCounter) {
00337     const bool usePlaceholders = isChecked(m_powerUi->usePlaceholders);
00338     const Range & targetRange = match[0];
00339 
00340     QString finalReplacement;
00341     if (usePlaceholders) {
00342         // Resolve references and escape sequences
00343         QList<ReplacementPart> parts;
00344         QString writableHack(replacement);
00345         const bool REPLACEMENT_GOODIES = true;
00346         KateDocument::escapePlaintext(writableHack, &parts, REPLACEMENT_GOODIES);
00347         buildReplacement(finalReplacement, parts, match, replacementCounter);
00348     } else {
00349         // Plain text replacement
00350         finalReplacement = replacement;
00351     }
00352 
00353     const bool blockMode = (m_view->blockSelection() && !targetRange.onSingleLine());
00354     m_view->doc()->replaceText(targetRange, finalReplacement, blockMode);
00355 }
00356 
00357 
00358 
00359 void KateSearchBar::onIncPatternChanged(const QString & pattern, bool invokedByUserAction) {
00360     if (pattern.isEmpty()) {
00361         if (invokedByUserAction) {
00362             // Kill selection
00363             m_view->setSelection(Range::invalid());
00364 
00365             // Kill highlight
00366             resetHighlights();
00367         }
00368 
00369         // Reset edit color
00370         indicateNothing();
00371 
00372         // Disable next/prev
00373         m_incUi->next->setDisabled(true);
00374         m_incUi->prev->setDisabled(true);
00375         return;
00376     }
00377 
00378     // Enable next/prev
00379     m_incUi->next->setDisabled(false);
00380     m_incUi->prev->setDisabled(false);
00381 
00382     if (invokedByUserAction) {
00383         // How to find?
00384         Search::SearchOptions enabledOptions(KTextEditor::Search::Default);
00385         const bool matchCase = isChecked(m_incMenuMatchCase);
00386         if (!matchCase) {
00387             enabledOptions |= Search::CaseInsensitive;
00388         }
00389 
00390 
00391         // Where to find?
00392         Range inputRange;
00393         const bool fromCursor = isChecked(m_incMenuFromCursor);
00394         if (fromCursor) {
00395             inputRange.setRange(m_incInitCursor, m_view->doc()->documentEnd());
00396         } else {
00397             inputRange = m_view->doc()->documentRange();
00398         }
00399 
00400         // Find, first try
00401         const QVector<Range> resultRanges = m_view->doc()->searchText(inputRange, pattern, enabledOptions);
00402         const Range & match = resultRanges[0];
00403 
00404         bool found = false;
00405         if (match.isValid()) {
00406             selectRange(m_view, match);
00407             const bool NOT_WRAPPED = false;
00408             indicateMatch(NOT_WRAPPED);
00409             found = true;
00410         } else {
00411             // Wrap if it makes sense
00412             if (fromCursor) {
00413                 // Find, second try
00414                 inputRange = m_view->doc()->documentRange();
00415                 const QVector<Range> resultRanges2 = m_view->doc()->searchText(inputRange, pattern, enabledOptions);
00416                 const Range & match2 = resultRanges2[0];
00417                 if (match2.isValid()) {
00418                     selectRange(m_view, match2);
00419                     const bool WRAPPED = true;
00420                     indicateMatch(WRAPPED);
00421                     found = true;
00422                 } else {
00423                     indicateMismatch();
00424                 }
00425             } else {
00426                 indicateMismatch();
00427             }
00428         }
00429 
00430         // Highlight all
00431         if (found && isChecked(m_incMenuHighlightAll)) {
00432             highlightAllMatches(pattern, enabledOptions);
00433         }
00434     }
00435 }
00436 
00437 
00438 
00439 void KateSearchBar::onIncNext() {
00440     const bool FIND = false;
00441     onStep(FIND);
00442 }
00443 
00444 
00445 
00446 void KateSearchBar::onIncPrev() {
00447     const bool FIND = false;
00448     const bool BACKWARDS = false;
00449     onStep(FIND, BACKWARDS);
00450 }
00451 
00452 
00453 
00454 void KateSearchBar::onIncMatchCaseToggle(bool invokedByUserAction) {
00455     if (invokedByUserAction) {
00456         sendConfig();
00457 
00458         // Re-search with new settings
00459         const QString pattern = m_incUi->pattern->displayText();
00460         onIncPatternChanged(pattern);
00461     }
00462 }
00463 
00464 
00465 
00466 void KateSearchBar::onIncHighlightAllToggle(bool checked, bool invokedByUserAction) {
00467     if (invokedByUserAction) {
00468         sendConfig();
00469 
00470         if (checked) {
00471             const QString pattern = m_incUi->pattern->displayText();
00472             if (!pattern.isEmpty()) {
00473                 // How to search while highlighting?
00474                 Search::SearchOptions enabledOptions(KTextEditor::Search::Default);
00475                 const bool matchCase = isChecked(m_incMenuMatchCase);
00476                 if (!matchCase) {
00477                     enabledOptions |= Search::CaseInsensitive;
00478                 }
00479 
00480                 // Highlight them all
00481                 resetHighlights();
00482                 highlightAllMatches(pattern, enabledOptions);
00483             }
00484         } else {
00485             resetHighlights();
00486         }
00487     }
00488 }
00489 
00490 
00491 
00492 void KateSearchBar::onIncFromCursorToggle(bool invokedByUserAction) {
00493     if (invokedByUserAction) {
00494         sendConfig();
00495     }
00496 }
00497 
00498 
00499 
00500 void KateSearchBar::fixForSingleLine(Range & range, bool forwards) {
00501     FAST_DEBUG("Single-line workaround checking BEFORE" << range);
00502     if (forwards) {
00503         const int line = range.start().line();
00504         const int col = range.start().column();
00505         const int maxColWithNewline = m_view->doc()->lineLength(line) + 1;
00506         if (col == maxColWithNewline) {
00507             FAST_DEBUG("Starting on a newline" << range);
00508             const int maxLine = m_view->doc()->lines() - 1;
00509             if (line < maxLine) {
00510                 range.setRange(Cursor(line + 1, 0), range.end());
00511                 FAST_DEBUG("Search range fixed to " << range);
00512             } else {
00513                 FAST_DEBUG("Already at last line");
00514                 range = Range::invalid();
00515             }
00516         }
00517     } else {
00518         const int col = range.end().column();
00519         if (col == 0) {
00520             FAST_DEBUG("Ending after a newline" << range);
00521             const int line = range.end().line();
00522             if (line > 0) {
00523                 const int maxColWithNewline = m_view->doc()->lineLength(line - 1);
00524                 range.setRange(range.start(), Cursor(line - 1, maxColWithNewline));
00525                 FAST_DEBUG("Search range fixed to " << range);
00526             } else {
00527                 FAST_DEBUG("Already at first line");
00528                 range = Range::invalid();
00529             }
00530         }
00531     }
00532     FAST_DEBUG("Single-line workaround checking  AFTER" << range);
00533 }
00534 
00535 
00536 
00537 void KateSearchBar::onReturnPressed() {
00538     const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
00539     const bool shiftDown = (modifiers & Qt::ShiftModifier) != 0;
00540     const bool controlDown = (modifiers & Qt::ControlModifier) != 0;
00541 
00542     if (shiftDown) {
00543         // Shift down, search backwards
00544         if (m_powerUi != NULL) {
00545             onPowerFindPrev();
00546         } else {
00547             onIncPrev();
00548         }
00549     } else {
00550         // Shift up, search forwards
00551         if (m_powerUi != NULL) {
00552             onPowerFindNext();
00553         } else {
00554             onIncNext();
00555         }
00556     }
00557 
00558     if (controlDown) {
00559         hideBar();
00560     }
00561 }
00562 
00563 
00564 
00565 bool KateSearchBar::onStep(bool replace, bool forwards) {
00566     // What to find?
00567     const QString pattern = (m_powerUi != NULL)
00568             ? m_powerUi->pattern->currentText()
00569             : m_incUi->pattern->displayText();
00570     if (pattern.isEmpty()) {
00571         return false; // == Pattern error
00572     }
00573 
00574     // How to find?
00575     Search::SearchOptions enabledOptions(KTextEditor::Search::Default);
00576     const bool matchCase = (m_powerUi != NULL)
00577             ? isChecked(m_powerUi->matchCase)
00578             : isChecked(m_incMenuMatchCase);
00579     if (!matchCase) {
00580         enabledOptions |= Search::CaseInsensitive;
00581     }
00582 
00583     if (!forwards) {
00584         enabledOptions |= Search::Backwards;
00585     }
00586 
00587     bool multiLinePattern = false;
00588     bool regexMode = false;
00589     if (m_powerUi != NULL) {
00590         switch (m_powerUi->searchMode->currentIndex()) {
00591         case MODE_WHOLE_WORDS:
00592             enabledOptions |= Search::WholeWords;
00593             break;
00594 
00595         case MODE_ESCAPE_SEQUENCES:
00596             enabledOptions |= Search::EscapeSequences;
00597             break;
00598 
00599         case MODE_REGEX:
00600             {
00601                 // Check if pattern multi-line
00602                 QString patternCopy(pattern);
00603                 KateDocument::repairPattern(patternCopy, multiLinePattern);
00604                 regexMode = true;
00605             }
00606             enabledOptions |= Search::Regex;
00607             break;
00608 
00609         case MODE_PLAIN_TEXT: // FALLTHROUGH
00610         default:
00611             break;
00612 
00613         }
00614     }
00615 
00616 
00617     // Where to find?
00618     Range inputRange;
00619     Range selection;
00620     const bool selected = m_view->selection();
00621     const bool selectionOnly = (m_powerUi != NULL)
00622             ? isChecked(m_powerUi->selectionOnly)
00623             : false;
00624     if (selected) {
00625         selection = m_view->selectionRange();
00626         if (selectionOnly) {
00627             // First match in selection
00628             inputRange = selection;
00629         } else {
00630             // Next match after/before selection if a match was selected before
00631             if (forwards) {
00632                 inputRange.setRange(selection.start(), m_view->doc()->documentEnd());
00633             } else {
00634                 inputRange.setRange(Cursor(0, 0), selection.end());
00635             }
00636         }
00637     } else {
00638         // No selection
00639         const bool fromCursor = (m_powerUi != NULL)
00640                 ? isChecked(m_powerUi->fromCursor)
00641                 : isChecked(m_incMenuFromCursor);
00642         if (fromCursor) {
00643             const Cursor cursorPos = m_view->cursorPosition();
00644             if (forwards) {
00645                 inputRange.setRange(cursorPos, m_view->doc()->documentEnd());
00646             } else {
00647                 inputRange.setRange(Cursor(0, 0), cursorPos);
00648             }
00649         } else {
00650             inputRange = m_view->doc()->documentRange();
00651         }
00652     }
00653     FAST_DEBUG("Search range is" << inputRange);
00654 
00655     // Single-line pattern workaround
00656     if (regexMode && !multiLinePattern) {
00657         fixForSingleLine(inputRange, forwards);
00658     }
00659 
00660 
00661     // Find, first try
00662     const QVector<Range> resultRanges = m_view->doc()->searchText(inputRange, pattern, enabledOptions);
00663     const Range & match = resultRanges[0];
00664     bool wrap = false;
00665     bool found = false;
00666     SmartRange * afterReplace = NULL;
00667     if (match.isValid()) {
00668         // Previously selected match again?
00669         if (selected && !selectionOnly && (match == selection)) {
00670             // Same match again
00671             if (replace) {
00672                 // Selection is match -> replace
00673                 const QString replacement = m_powerUi->replacement->currentText();
00674                 afterReplace = m_view->doc()->newSmartRange(match);
00675                 afterReplace->setInsertBehavior(SmartRange::ExpandRight | SmartRange::ExpandLeft);
00676                 replaceMatch(resultRanges, replacement);
00677 
00678                 // Find, second try after replaced text
00679                 if (forwards) {
00680                     inputRange.setRange(afterReplace->end(), inputRange.end());
00681                 } else {
00682                     inputRange.setRange(inputRange.start(), afterReplace->start());
00683                 }
00684             } else {
00685                 // Find, second try after old selection
00686                 if (forwards) {
00687                     inputRange.setRange(selection.end(), inputRange.end());
00688                 } else {
00689                     inputRange.setRange(inputRange.start(), selection.start());
00690                 }
00691             }
00692 
00693             // Single-line pattern workaround
00694             fixForSingleLine(inputRange, forwards);
00695 
00696             const QVector<Range> resultRanges2 = m_view->doc()->searchText(inputRange, pattern, enabledOptions);
00697             const Range & match2 = resultRanges2[0];
00698             if (match2.isValid()) {
00699                 selectRange(m_view, match2);
00700                 found = true;
00701                 const bool NOT_WRAPPED = false;
00702                 indicateMatch(NOT_WRAPPED);
00703             } else {
00704                 // Find, third try from doc start on
00705                 wrap = true;
00706             }
00707         } else {
00708             selectRange(m_view, match);
00709             found = true;
00710             const bool NOT_WRAPPED = false;
00711             indicateMatch(NOT_WRAPPED);
00712         }
00713     } else if (!selected || !selectionOnly) {
00714         // Find, second try from doc start on
00715         wrap = true;
00716     }
00717 
00718     // Wrap around
00719     if (wrap) {
00720         inputRange = m_view->doc()->documentRange();
00721         const QVector<Range> resultRanges3 = m_view->doc()->searchText(inputRange, pattern, enabledOptions);
00722         const Range & match3 = resultRanges3[0];
00723         if (match3.isValid()) {
00724             // Previously selected match again?
00725             if (selected && !selectionOnly && (match3 == selection)) {
00726                 // NOOP, same match again
00727             } else {
00728                 selectRange(m_view, match3);
00729                 found = true;
00730             }
00731             const bool WRAPPED = true;
00732             indicateMatch(WRAPPED);
00733         } else {
00734             indicateMismatch();
00735         }
00736     }
00737 
00738     // Highlight all matches and/or replacement
00739     const bool highlightAll = (m_powerUi != NULL)
00740             ? isChecked(m_powerUi->highlightAll)
00741             : isChecked(m_incMenuHighlightAll);
00742     if ((found && highlightAll) || (afterReplace != NULL)) {
00743         // Highlight all matches
00744         if (found && highlightAll) {
00745             highlightAllMatches(pattern, enabledOptions);
00746         }
00747 
00748         // Highlight replacement (on top if overlapping) if new match selected
00749         if (found && (afterReplace != NULL)) {
00750             // Note: highlightAllMatches already reset for us
00751             if (!(found && highlightAll)) {
00752                 resetHighlights();
00753             }
00754 
00755             highlightReplacement(*afterReplace);
00756         }
00757 
00758     }
00759 
00760     delete afterReplace;
00761 
00762     return true; // == No pattern error
00763 }
00764 
00765 
00766 
00767 void KateSearchBar::onPowerPatternChanged(const QString & pattern) {
00768     givePatternFeedback(pattern);
00769     indicateNothing();
00770 }
00771 
00772 
00773 
00774 void KateSearchBar::givePatternFeedback(const QString & pattern) {
00775     bool enabled = true;
00776 
00777     if (pattern.isEmpty()) {
00778         enabled = false;
00779     } else {
00780         switch (m_powerUi->searchMode->currentIndex()) {
00781         case MODE_WHOLE_WORDS:
00782             if (pattern.trimmed() != pattern) {
00783                 enabled = false;
00784             }
00785             break;
00786 
00787         case MODE_REGEX:
00788             m_patternTester.setPattern(pattern);
00789             enabled = m_patternTester.isValid();
00790             break;
00791 
00792         case MODE_ESCAPE_SEQUENCES: // FALLTHROUGH
00793         case MODE_PLAIN_TEXT: // FALLTHROUGH
00794         default:
00795             ; // NOOP
00796 
00797         }
00798     }
00799 
00800     // Enable/disable next/prev and replace next/all
00801     m_powerUi->findNext->setDisabled(!enabled);
00802     m_powerUi->findPrev->setDisabled(!enabled);
00803     m_powerUi->replaceNext->setDisabled(!enabled);
00804     m_powerUi->replaceAll->setDisabled(!enabled);
00805 }
00806 
00807 
00808 
00809 void KateSearchBar::addCurrentTextToHistory(QComboBox * combo) {
00810     const QString text = combo->currentText();
00811     const int index = combo->findText(text);
00812     if (index != -1) {
00813         combo->removeItem(index);
00814     }
00815     combo->insertItem(0, text);
00816     combo->setCurrentIndex(0);
00817 }
00818 
00819 
00820 
00821 void KateSearchBar::backupConfig(bool ofPower) {
00822     if (ofPower) {
00823         m_powerMatchCase = isChecked(m_powerUi->matchCase);
00824         m_powerFromCursor = isChecked(m_powerUi->fromCursor);
00825         m_powerHighlightAll = isChecked(m_powerUi->highlightAll);
00826         m_powerUsePlaceholders = isChecked(m_powerUi->usePlaceholders);
00827         m_powerMode = m_powerUi->searchMode->currentIndex();
00828     } else {
00829         m_incHighlightAll = isChecked(m_incMenuHighlightAll);
00830         m_incFromCursor = isChecked(m_incMenuFromCursor);
00831         m_incMatchCase = isChecked(m_incMenuMatchCase);
00832     }
00833 }
00834 
00835 
00836 
00837 void KateSearchBar::sendConfig() {
00838     KateViewConfig * const globalConfig = KateGlobal::self()->viewConfig();
00839     const long pastFlags = globalConfig->searchFlags();
00840     long futureFlags = pastFlags;
00841 
00842     if (m_powerUi != NULL) {
00843         const bool OF_POWER = true;
00844         backupConfig(OF_POWER);
00845 
00846         // Update power search flags only
00847         const long incFlagsOnly = pastFlags
00848                 & (KateViewConfig::IncHighlightAll
00849                     | KateViewConfig::IncFromCursor
00850                     | KateViewConfig::IncMatchCase);
00851 
00852         futureFlags = incFlagsOnly
00853             | (m_powerMatchCase ? KateViewConfig::PowerMatchCase : 0)
00854             | (m_powerFromCursor ? KateViewConfig::PowerFromCursor : 0)
00855             | (m_powerHighlightAll ? KateViewConfig::PowerHighlightAll : 0)
00856             | (m_powerUsePlaceholders ? KateViewConfig::PowerUsePlaceholders : 0)
00857             | ((m_powerMode == MODE_REGEX)
00858                 ? KateViewConfig::PowerModeRegularExpression
00859                 : ((m_powerMode == MODE_ESCAPE_SEQUENCES)
00860                     ? KateViewConfig::PowerModeEscapeSequences
00861                     : ((m_powerMode == MODE_WHOLE_WORDS)
00862                         ? KateViewConfig::PowerModeWholeWords
00863                         : KateViewConfig::PowerModePlainText)));
00864 
00865     } else if (m_incUi != NULL) {
00866         const bool OF_INCREMENTAL = false;
00867         backupConfig(OF_INCREMENTAL);
00868 
00869         // Update incremental search flags only
00870         const long powerFlagsOnly = pastFlags
00871                 & (KateViewConfig::PowerMatchCase
00872                     | KateViewConfig::PowerFromCursor
00873                     | KateViewConfig::PowerHighlightAll
00874                     | KateViewConfig::PowerUsePlaceholders
00875                     | KateViewConfig::PowerModeRegularExpression
00876                     | KateViewConfig::PowerModeEscapeSequences
00877                     | KateViewConfig::PowerModeWholeWords
00878                     | KateViewConfig::PowerModePlainText);
00879 
00880         futureFlags = powerFlagsOnly
00881                 | (m_incHighlightAll ? KateViewConfig::IncHighlightAll : 0)
00882                 | (m_incFromCursor ? KateViewConfig::IncFromCursor : 0)
00883                 | (m_incMatchCase ? KateViewConfig::IncMatchCase : 0);
00884     }
00885 
00886     // Adjust global config
00887     globalConfig->setSearchFlags(futureFlags);
00888 }
00889 
00890 
00891 
00892 void KateSearchBar::onPowerFindNext() {
00893     const bool FIND = false;
00894     if (onStep(FIND)) {
00895         // Add to search history
00896         addCurrentTextToHistory(m_powerUi->pattern);
00897     }
00898 }
00899 
00900 
00901 
00902 void KateSearchBar::onPowerFindPrev() {
00903     const bool FIND = false;
00904     const bool BACKWARDS = false;
00905     if (onStep(FIND, BACKWARDS)) {
00906         // Add to search history
00907         addCurrentTextToHistory(m_powerUi->pattern);
00908     }
00909 }
00910 
00911 
00912 
00913 void KateSearchBar::onPowerReplaceNext() {
00914     const bool REPLACE = true;
00915     if (onStep(REPLACE)) {
00916         // Add to search history
00917         addCurrentTextToHistory(m_powerUi->pattern);
00918 
00919         // Add to replace history
00920         addCurrentTextToHistory(m_powerUi->replacement);
00921     }
00922 }
00923 
00924 
00925 
00926 // replacement == NULL --> Highlight all matches
00927 // replacement != NULL --> Replace and highlight all matches
00928 void KateSearchBar::onForAll(const QString & pattern, Range inputRange,
00929         Search::SearchOptions enabledOptions,
00930         const QString * replacement) {
00931     bool multiLinePattern = false;
00932     const bool regexMode = enabledOptions.testFlag(Search::Regex);
00933     if (regexMode) {
00934         // Check if pattern multi-line
00935         QString patternCopy(pattern);
00936         KateDocument::repairPattern(patternCopy, multiLinePattern);
00937     }
00938 
00939     // Clear backwards flag, this algorithm is for forward mode
00940     if (enabledOptions.testFlag(Search::Backwards)) {
00941         enabledOptions &= ~Search::SearchOptions(Search::Backwards);
00942     }
00943 
00944     // Before first match
00945     resetHighlights();
00946 
00947     SmartRange * const workingRange = m_view->doc()->newSmartRange(inputRange);
00948     int matchCounter = 0;
00949     for (;;) {
00950         const QVector<Range> resultRanges = m_view->doc()->searchText(*workingRange, pattern, enabledOptions);
00951         Range match = resultRanges[0];
00952         if (!match.isValid()) {
00953             break;
00954         }
00955         bool const originalMatchEmpty = match.isEmpty();
00956 
00957         // Work with the match
00958         if (replacement != NULL) {
00959             if (matchCounter == 0) {
00960                 m_view->doc()->editBegin();
00961             }
00962 
00963             // Track replacement operation
00964             SmartRange * const afterReplace = m_view->doc()->newSmartRange(match);
00965             afterReplace->setInsertBehavior(SmartRange::ExpandRight | SmartRange::ExpandLeft);
00966 
00967             // Replace
00968             replaceMatch(resultRanges, *replacement, ++matchCounter);
00969 
00970             // Highlight and continue after adjusted match
00971             highlightReplacement(*afterReplace);
00972             match = *afterReplace;
00973             delete afterReplace;
00974         } else {
00975             // Highlight and continue after original match
00976             highlightMatch(match);
00977             matchCounter++;
00978         }
00979 
00980         // Continue after match
00981         SmartCursor & workingStart = workingRange->smartStart();
00982         workingStart.setPosition(match.end());
00983         if (originalMatchEmpty) {
00984             // Can happen for regex patterns like "^".
00985             // If we don't advance here we will loop forever...
00986             workingStart.advance(1);
00987         } else if (regexMode && !multiLinePattern && workingStart.atEndOfLine()) {
00988             // single-line regexps might match the naked line end
00989             // therefore we better advance to the next line
00990             workingStart.advance(1);
00991         }
00992 
00993         // Are we done?
00994         if (!workingRange->isValid() || workingStart.atEndOfDocument()) {
00995             break;
00996         }
00997     }
00998 
00999     // After last match
01000     if (matchCounter > 0) {
01001         if (replacement != NULL) {
01002             m_view->doc()->editEnd();
01003         }
01004     }
01005 
01006     delete workingRange;
01007 }
01008 
01009 
01010 
01011 void KateSearchBar::onPowerReplaceAll() {
01012     // What to find/replace?
01013     const QString pattern = m_powerUi->pattern->currentText();
01014     const QString replacement = m_powerUi->replacement->currentText();
01015 
01016 
01017     // How to find?
01018     Search::SearchOptions enabledOptions(KTextEditor::Search::Default);
01019     const bool matchCase = isChecked(m_powerUi->matchCase);
01020     if (!matchCase) {
01021         enabledOptions |= Search::CaseInsensitive;
01022     }
01023 
01024     if (m_powerUi != NULL) {
01025         switch (m_powerUi->searchMode->currentIndex()) {
01026         case MODE_WHOLE_WORDS:
01027             enabledOptions |= Search::WholeWords;
01028             break;
01029 
01030         case MODE_ESCAPE_SEQUENCES:
01031             enabledOptions |= Search::EscapeSequences;
01032             break;
01033 
01034         case MODE_REGEX:
01035             enabledOptions |= Search::Regex;
01036             break;
01037 
01038         case MODE_PLAIN_TEXT: // FALLTHROUGH
01039         default:
01040             break;
01041 
01042         }
01043     }
01044 
01045 
01046     // Where to replace?
01047     Range selection;
01048     const bool selected = m_view->selection();
01049     const bool selectionOnly = isChecked(m_powerUi->selectionOnly);
01050     Range inputRange = (selected && selectionOnly)
01051             ? m_view->selectionRange()
01052             : m_view->doc()->documentRange();
01053 
01054 
01055     // Pass on the hard work
01056     onForAll(pattern, inputRange, enabledOptions, &replacement);
01057 
01058 
01059     // Add to search history
01060     addCurrentTextToHistory(m_powerUi->pattern);
01061 
01062     // Add to replace history
01063     addCurrentTextToHistory(m_powerUi->replacement);
01064 }
01065 
01066 
01067 
01068 struct ParInfo {
01069     int openIndex;
01070     bool capturing;
01071     int captureNumber; // 1..9
01072 };
01073 
01074 
01075 
01076 QVector<QString> KateSearchBar::getCapturePatterns(const QString & pattern) {
01077     QVector<QString> capturePatterns;
01078     capturePatterns.reserve(9);
01079     QStack<ParInfo> parInfos;
01080 
01081     const int inputLen = pattern.length();
01082     int input = 0; // walker index
01083     bool insideClass = false;
01084     int captureCount = 0;
01085 
01086     while (input < inputLen) {
01087         if (insideClass) {
01088             // Wait for closing, unescaped ']'
01089             if (pattern[input].unicode() == L']') {
01090                 insideClass = false;
01091             }
01092             input++;
01093         }
01094         else
01095         {
01096             switch (pattern[input].unicode())
01097             {
01098             case L'\\':
01099                 // Skip this and any next character
01100                 input += 2;
01101                 break;
01102 
01103             case L'(':
01104                 ParInfo curInfo;
01105                 curInfo.openIndex = input;
01106                 curInfo.capturing = (input + 1 >= inputLen) || (pattern[input + 1].unicode() != '?');
01107                 if (curInfo.capturing) {
01108                     captureCount++;
01109                 }
01110                 curInfo.captureNumber = captureCount;
01111                 parInfos.push(curInfo);
01112 
01113                 input++;
01114                 break;
01115 
01116             case L')':
01117                 if (!parInfos.empty()) {
01118                     ParInfo & top = parInfos.top();
01119                     if (top.capturing && (top.captureNumber <= 9)) {
01120                         const int start = top.openIndex + 1;
01121                         const int len = input - start;
01122                         if (capturePatterns.size() < top.captureNumber) {
01123                             capturePatterns.resize(top.captureNumber);
01124                         }
01125                         capturePatterns[top.captureNumber - 1] = pattern.mid(start, len);
01126                     }
01127                     parInfos.pop();
01128                 }
01129 
01130                 input++;
01131                 break;
01132 
01133             case L'[':
01134                 input++;
01135                 insideClass = true;
01136                 break;
01137 
01138             default:
01139                 input++;
01140                 break;
01141 
01142             }
01143         }
01144     }
01145 
01146     return capturePatterns;
01147 }
01148 
01149 
01150 
01151 void KateSearchBar::addMenuEntry(QMenu * menu, QVector<QString> & insertBefore, QVector<QString> & insertAfter,
01152         uint & walker, const QString & before, const QString after, const QString description,
01153         const QString & realBefore, const QString & realAfter) {
01154     QAction * const action = menu->addAction(before + after + '\t' + description);
01155     insertBefore[walker] = QString(realBefore.isEmpty() ? before : realBefore);
01156     insertAfter[walker] = QString(realAfter.isEmpty() ? after : realAfter);
01157     action->setData(QVariant(walker++));
01158 }
01159 
01160 
01161 
01162 void KateSearchBar::showAddMenu(bool forPattern) {
01163     QVector<QString> insertBefore(35);
01164     QVector<QString> insertAfter(35);
01165     uint walker = 0;
01166 
01167     // Build menu
01168     QMenu * const popupMenu = new QMenu();
01169     const bool regexMode = (m_powerUi->searchMode->currentIndex() == MODE_REGEX);
01170 
01171     if (forPattern) {
01172         if (regexMode) {
01173             addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "^", "", i18n("Beginning of line"));
01174             addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "$", "", i18n("End of line"));
01175             popupMenu->addSeparator();
01176             addMenuEntry(popupMenu, insertBefore, insertAfter, walker, ".", "", i18n("Any single character (excluding line breaks)"));
01177             popupMenu->addSeparator();
01178             addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "+", "", i18n("One or more occurrences"));
01179             addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "*", "", i18n("Zero or more occurrences"));
01180             addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "?", "", i18n("Zero or one occurrences"));
01181             addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "{a", ",b}", i18n("<a> through <b> occurrences"), "{", ",}");
01182             popupMenu->addSeparator();
01183             addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "(", ")", i18n("Group, capturing"));
01184             addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "|", "", i18n("Or"));
01185             addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "[", "]", i18n("Set of characters"));
01186             addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "[^", "]", i18n("Negative set of characters"));
01187             popupMenu->addSeparator();
01188         }
01189     } else {
01190         addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\0", "", i18n("Whole match reference"));
01191         popupMenu->addSeparator();
01192         if (regexMode) {
01193             const QString pattern = m_powerUi->pattern->currentText();
01194             const QVector<QString> capturePatterns = getCapturePatterns(pattern);
01195 
01196             const int captureCount = capturePatterns.count();
01197             for (int i = 1; i <= 9; i++) {
01198                 const QString number = QString::number(i);
01199                 const QString & captureDetails = (i <= captureCount)
01200                         ? (QString(" = (") + capturePatterns[i - 1].left(30)) + QString(")")
01201                         : QString();
01202                 addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\" + number, "",
01203                         i18n("Reference") + ' ' + number + captureDetails);
01204             }
01205 
01206             popupMenu->addSeparator();
01207         }
01208     }
01209 
01210     addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\n", "", i18n("Line break"));
01211     addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\t", "", i18n("Tab"));
01212 
01213     if (forPattern && regexMode) {
01214         addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\b", "", i18n("Word boundary"));
01215         addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\B", "", i18n("Not word boundary"));
01216         addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\d", "", i18n("Digit"));
01217         addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\D", "", i18n("Non-digit"));
01218         addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\s", "", i18n("Whitespace (excluding line breaks)"));
01219         addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\S", "", i18n("Non-whitespace (excluding line breaks)"));
01220         addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\w", "", i18n("Word character (alphanumerics plus '_')"));
01221         addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\W", "", i18n("Non-word character"));
01222     }
01223 
01224     addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\0???", "", i18n("Octal character 000 to 377 (2^8-1)"), "\\0");
01225     addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\x????", "", i18n("Hex character 0000 to FFFF (2^16-1)"), "\\x");
01226     addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\\\", "", i18n("Backslash"));
01227 
01228     if (forPattern && regexMode) {
01229         popupMenu->addSeparator();
01230         addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "(?:E", ")", i18n("Group, non-capturing"), "(?:");
01231         addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "(?=E", ")", i18n("Lookahead"), "(?=");
01232         addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "(?!E", ")", i18n("Negative lookahead"), "(?!");
01233     }
01234 
01235     if (!forPattern) {
01236         popupMenu->addSeparator();
01237         addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\L", "", i18n("Begin lowercase conversion"));
01238         addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\U", "", i18n("Begin uppercase conversion"));
01239         addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\E", "", i18n("End case conversion"));
01240         addMenuEntry(popupMenu, insertBefore, insertAfter, walker, "\\#[#..]", "", i18n("Replacement counter (for Replace all)"), "\\#");
01241     }
01242 
01243 
01244     // Show menu
01245     const QPoint topLeftGlobal = m_powerUi->patternAdd->mapToGlobal(QPoint(0, 0));
01246     QAction * const result = popupMenu->exec(topLeftGlobal);
01247     if (result != NULL) {
01248         QLineEdit * const lineEdit = forPattern
01249                 ? m_powerUi->pattern->lineEdit()
01250                 : m_powerUi->replacement->lineEdit();
01251         Q_ASSERT(lineEdit != NULL);
01252         const int cursorPos = lineEdit->cursorPosition();
01253         const int index = result->data().toUInt();
01254         const QString & before = insertBefore[index];
01255         const QString & after = insertAfter[index];
01256         lineEdit->insert(before + after);
01257         lineEdit->setCursorPosition(cursorPos + before.count());
01258         lineEdit->setFocus();
01259     }
01260 
01261 
01262     // Kill menu
01263     delete popupMenu;
01264 }
01265 
01266 
01267 
01268 void KateSearchBar::onPowerAddToPatternClicked() {
01269     const bool FOR_PATTERN = true;
01270     showAddMenu(FOR_PATTERN);
01271 }
01272 
01273 
01274 
01275 void KateSearchBar::onPowerAddToReplacementClicked() {
01276     const bool FOR_REPLACEMENT = false;
01277     showAddMenu(FOR_REPLACEMENT);
01278 }
01279 
01280 
01281 
01282 void KateSearchBar::onPowerUsePlaceholdersToggle(int state, bool invokedByUserAction) {
01283     const bool disabled = (state != Qt::Checked);
01284     m_powerUi->replacementAdd->setDisabled(disabled);
01285 
01286     if (invokedByUserAction) {
01287         sendConfig();
01288     }
01289 }
01290 
01291 
01292 
01293 void KateSearchBar::onPowerMatchCaseToggle(bool invokedByUserAction) {
01294     if (invokedByUserAction) {
01295         sendConfig();
01296         indicateNothing();
01297     }
01298 }
01299 
01300 
01301 
01302 void KateSearchBar::onPowerHighlightAllToggle(int state, bool invokedByUserAction) {
01303     if (invokedByUserAction) {
01304         sendConfig();
01305 
01306         const bool wanted = (state == Qt::Checked);
01307         if (wanted) {
01308             const QString pattern = m_powerUi->pattern->currentText();
01309             if (!pattern.isEmpty()) {
01310                 // How to search while highlighting?
01311                 Search::SearchOptions enabledOptions(KTextEditor::Search::Default);
01312                 const bool matchCase = isChecked(m_powerUi->matchCase);
01313                 if (!matchCase) {
01314                     enabledOptions |= Search::CaseInsensitive;
01315                 }
01316 
01317                 switch (m_powerUi->searchMode->currentIndex()) {
01318                 case MODE_WHOLE_WORDS:
01319                     enabledOptions |= Search::WholeWords;
01320                     break;
01321 
01322                 case MODE_ESCAPE_SEQUENCES:
01323                     enabledOptions |= Search::EscapeSequences;
01324                     break;
01325 
01326                 case MODE_REGEX:
01327                     enabledOptions |= Search::Regex;
01328                     break;
01329 
01330                 case MODE_PLAIN_TEXT: // FALLTHROUGH
01331                 default:
01332                     break;
01333 
01334                 }
01335 
01336                 // Highlight them all
01337                 resetHighlights();
01338                 highlightAllMatches(pattern, enabledOptions);
01339             }
01340         } else {
01341             resetHighlights();
01342         }
01343     }
01344 }
01345 
01346 
01347 
01348 void KateSearchBar::onPowerFromCursorToggle(bool invokedByUserAction) {
01349     if (invokedByUserAction) {
01350         sendConfig();
01351     }
01352 }
01353 
01354 
01355 
01356 void KateSearchBar::onPowerModeChanged(int index, bool invokedByUserAction) {
01357     const bool disabled = (index == MODE_PLAIN_TEXT)
01358             || (index == MODE_WHOLE_WORDS);
01359     m_powerUi->patternAdd->setDisabled(disabled);
01360 
01361     if (invokedByUserAction) {
01362         switch (index) {
01363         case MODE_REGEX:
01364             setChecked(m_powerUi->matchCase, true);
01365             // FALLTROUGH
01366 
01367         case MODE_ESCAPE_SEQUENCES:
01368             setChecked(m_powerUi->usePlaceholders, true);
01369             break;
01370 
01371         case MODE_WHOLE_WORDS: // FALLTROUGH
01372         case MODE_PLAIN_TEXT: // FALLTROUGH
01373         default:
01374             ; // NOOP
01375         }
01376 
01377         sendConfig();
01378         indicateNothing();
01379     }
01380 
01381     givePatternFeedback(m_powerUi->pattern->currentText());
01382 }
01383 
01384 
01385 
01386 /*static*/ void KateSearchBar::nextMatchForSelection(KateView * view, bool forwards) {
01387     const bool selected = view->selection();
01388     if (selected) {
01389         const QString pattern = view->selectionText();
01390 
01391         // How to find?
01392         Search::SearchOptions enabledOptions(KTextEditor::Search::Default);
01393         if (!forwards) {
01394             enabledOptions |= Search::Backwards;
01395         }
01396 
01397         // Where to find?
01398         const Range selRange = view->selectionRange();
01399         Range inputRange;
01400         if (forwards) {
01401             inputRange.setRange(selRange.end(), view->doc()->documentEnd());
01402         } else {
01403             inputRange.setRange(Cursor(0, 0), selRange.start());
01404         }
01405 
01406         // Find, first try
01407         const QVector<Range> resultRanges = view->doc()->searchText(inputRange, pattern, enabledOptions);
01408         const Range & match = resultRanges[0];
01409 
01410         if (match.isValid()) {
01411             selectRange(view, match);
01412         } else {
01413             // Find, second try
01414             if (forwards) {
01415                 inputRange.setRange(Cursor(0, 0), selRange.start());
01416             } else {
01417                 inputRange.setRange(selRange.end(), view->doc()->documentEnd());
01418             }
01419             const QVector<Range> resultRanges2 = view->doc()->searchText(inputRange, pattern, enabledOptions);
01420             const Range & match2 = resultRanges2[0];
01421             if (match2.isValid()) {
01422                 selectRange(view, match2);
01423             }
01424         }
01425     } else {
01426         // Select current word so we can search for that the next time
01427         const Cursor cursorPos = view->cursorPosition();
01428         view->selectWord(cursorPos);
01429     }
01430 }
01431 
01432 
01433 
01434 void KateSearchBar::onMutatePower() {
01435     QString initialPattern;
01436     bool selectionOnly = false;
01437 
01438     // Guess settings from context: init pattern with current selection
01439     const bool selected = m_view->selection();
01440     if (selected) {
01441         const Range & selection = m_view->selectionRange();
01442         if (selection.onSingleLine()) {
01443             // ... with current selection
01444             initialPattern = m_view->selectionText();
01445         } else {
01446             // Enable selection only
01447             selectionOnly = true;
01448         }
01449     }
01450 
01451     // If there's no new selection, we'll use the existing pattern
01452     if (initialPattern.isNull()) {
01453         // Coming from power search?
01454         const bool fromReplace = (m_powerUi != NULL) && (m_widget->isVisible());
01455         if (fromReplace) {
01456             QLineEdit * const patternLineEdit = m_powerUi->pattern->lineEdit();
01457             Q_ASSERT(patternLineEdit != NULL);
01458             patternLineEdit->selectAll();
01459             m_powerUi->pattern->setFocus(Qt::MouseFocusReason);
01460             return;
01461         }
01462 
01463         // Coming from incremental search?
01464         const bool fromIncremental = (m_incUi != NULL) && (m_widget->isVisible());
01465         if (fromIncremental) {
01466             initialPattern = m_incUi->pattern->displayText();
01467         }
01468     }
01469 
01470     // Create dialog
01471     const bool create = (m_powerUi == NULL);
01472     if (create) {
01473         // Kill incremental widget
01474         if (m_incUi != NULL) {
01475             // Backup current settings
01476             const bool OF_INCREMENTAL = false;
01477             backupConfig(OF_INCREMENTAL);
01478 
01479             // Kill widget
01480             delete m_incUi;
01481             delete m_incMenu;
01482             m_incUi = NULL;
01483             m_incMenu = NULL;
01484             m_incMenuMatchCase = NULL;
01485             m_incMenuFromCursor = NULL;
01486             m_incMenuHighlightAll = NULL;
01487             m_layout->removeWidget(m_widget);
01488             m_widget->deleteLater(); // I didn't get a crash here but for symmetrie to the other mutate slot^
01489         }
01490 
01491         // Add power widget
01492         m_widget = new QWidget(this);
01493         m_powerUi = new Ui::PowerSearchBar;
01494         m_powerUi->setupUi(m_widget);
01495         m_layout->addWidget(m_widget);
01496 
01497         // Bind to shared history models
01498         const int MAX_HISTORY_SIZE = 100; // Please don't lower this value! Thanks, Sebastian
01499         QStringListModel * const patternHistoryModel = KateHistoryModel::getPatternHistoryModel();
01500         QStringListModel * const replacementHistoryModel = KateHistoryModel::getReplacementHistoryModel();
01501         m_powerUi->pattern->setMaxCount(MAX_HISTORY_SIZE);
01502         m_powerUi->pattern->setModel(patternHistoryModel);
01503         m_powerUi->replacement->setMaxCount(MAX_HISTORY_SIZE);
01504         m_powerUi->replacement->setModel(replacementHistoryModel);
01505 
01506         // Icons
01507         m_powerUi->mutate->setIcon(KIcon("arrow-down-double"));
01508         m_powerUi->findNext->setIcon(KIcon("go-down"));
01509         m_powerUi->findPrev->setIcon(KIcon("go-up"));
01510         m_powerUi->patternAdd->setIcon(KIcon("list-add"));
01511         m_powerUi->replacementAdd->setIcon(KIcon("list-add"));
01512 
01513         // Focus proxy
01514         centralWidget()->setFocusProxy(m_powerUi->pattern);
01515 
01516         // Make completers case-sensitive
01517         QLineEdit * const patternLineEdit = m_powerUi->pattern->lineEdit();
01518         Q_ASSERT(patternLineEdit != NULL);
01519         patternLineEdit->completer()->setCaseSensitivity(Qt::CaseSensitive);
01520 
01521         QLineEdit * const replacementLineEdit = m_powerUi->pattern->lineEdit();
01522         Q_ASSERT(replacementLineEdit != NULL);
01523         replacementLineEdit->completer()->setCaseSensitivity(Qt::CaseSensitive);
01524     }
01525 
01526     setChecked(m_powerUi->selectionOnly, selectionOnly);
01527 
01528     // Restore previous settings
01529     if (create) {
01530         setChecked(m_powerUi->matchCase, m_powerMatchCase);
01531         setChecked(m_powerUi->highlightAll, m_powerHighlightAll);
01532         setChecked(m_powerUi->usePlaceholders, m_powerUsePlaceholders);
01533         setChecked(m_powerUi->fromCursor, m_powerFromCursor);
01534         m_powerUi->searchMode->setCurrentIndex(m_powerMode);
01535     }
01536 
01537     // Set initial search pattern
01538     QLineEdit * const patternLineEdit = m_powerUi->pattern->lineEdit();
01539     Q_ASSERT(patternLineEdit != NULL);
01540     patternLineEdit->setText(initialPattern);
01541     patternLineEdit->selectAll();
01542 
01543     // Set initial replacement text
01544     QLineEdit * const replacementLineEdit = m_powerUi->replacement->lineEdit();
01545     Q_ASSERT(replacementLineEdit != NULL);
01546     replacementLineEdit->setText("");
01547 
01548     // Propagate settings (slots are still inactive on purpose)
01549     onPowerPatternChanged(initialPattern);
01550     const bool NOT_INVOKED_BY_USER_ACTION = false;
01551     onPowerUsePlaceholdersToggle(m_powerUi->usePlaceholders->checkState(), NOT_INVOKED_BY_USER_ACTION);
01552     onPowerModeChanged(m_powerUi->searchMode->currentIndex(), NOT_INVOKED_BY_USER_ACTION);
01553 
01554     if (create) {
01555         // Slots
01556         connect(m_powerUi->mutate, SIGNAL(clicked()), this, SLOT(onMutateIncremental()));
01557         connect(patternLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(onPowerPatternChanged(const QString &)));
01558         connect(m_powerUi->findNext, SIGNAL(clicked()), this, SLOT(onPowerFindNext()));
01559         connect(m_powerUi->findPrev, SIGNAL(clicked()), this, SLOT(onPowerFindPrev()));
01560         connect(m_powerUi->replaceNext, SIGNAL(clicked()), this, SLOT(onPowerReplaceNext()));
01561         connect(m_powerUi->replaceAll, SIGNAL(clicked()), this, SLOT(onPowerReplaceAll()));
01562         connect(m_powerUi->searchMode, SIGNAL(currentIndexChanged(int)), this, SLOT(onPowerModeChanged(int)));
01563         connect(m_powerUi->patternAdd, SIGNAL(clicked()), this, SLOT(onPowerAddToPatternClicked()));
01564         connect(m_powerUi->usePlaceholders, SIGNAL(stateChanged(int)), this, SLOT(onPowerUsePlaceholdersToggle(int)));
01565         connect(m_powerUi->matchCase, SIGNAL(stateChanged(int)), this, SLOT(onPowerMatchCaseToggle()));
01566         connect(m_powerUi->highlightAll, SIGNAL(stateChanged(int)), this, SLOT(onPowerHighlightAllToggle(int)));
01567         connect(m_powerUi->fromCursor, SIGNAL(stateChanged(int)), this, SLOT(onPowerFromCursorToggle()));
01568         connect(m_powerUi->replacementAdd, SIGNAL(clicked()), this, SLOT(onPowerAddToReplacementClicked()));
01569 
01570         // Make [return] in pattern line edit trigger <find next> action
01571         connect(patternLineEdit, SIGNAL(returnPressed()), this, SLOT(onReturnPressed()));
01572         connect(replacementLineEdit, SIGNAL(returnPressed()), this, SLOT(onPowerReplaceNext()));
01573     }
01574 
01575     // Focus
01576     if (m_widget->isVisible()) {
01577         m_powerUi->pattern->setFocus(Qt::MouseFocusReason);
01578     }
01579 }
01580 
01581 
01582 
01583 void KateSearchBar::onMutateIncremental() {
01584     QString initialPattern;
01585 
01586     // Guess settings from context: init pattern with current selection
01587     const bool selected = m_view->selection();
01588     if (selected) {
01589         const Range & selection = m_view->selectionRange();
01590         if (selection.onSingleLine()) {
01591             // ... with current selection
01592             initialPattern = m_view->selectionText();
01593         }
01594     }
01595 
01596     // If there's no new selection, we'll use the existing pattern
01597     if (initialPattern.isNull()) {
01598         // Coming from incremental search?
01599         const bool fromIncremental = (m_incUi != NULL) && (m_widget->isVisible());
01600         if (fromIncremental) {
01601             m_incUi->pattern->selectAll();
01602             m_incUi->pattern->setFocus(Qt::MouseFocusReason);
01603             return;
01604         }
01605 
01606         // Coming from power search?
01607         const bool fromReplace = (m_powerUi != NULL) && (m_widget->isVisible());
01608         if (fromReplace) {
01609             initialPattern = m_powerUi->pattern->currentText();
01610         }
01611     }
01612 
01613     // Create dialog
01614     const bool create = (m_incUi == NULL);
01615     if (create) {
01616         // Kill power widget
01617         if (m_powerUi != NULL) {
01618             // Backup current settings
01619             const bool OF_POWER = true;
01620             backupConfig(OF_POWER);
01621 
01622             // Kill widget
01623             delete m_powerUi;
01624             m_powerUi = NULL;
01625             m_layout->removeWidget(m_widget);
01626             m_widget->deleteLater(); //deleteLater, because it's not a good idea too delete the widget and there for the button triggering this slot
01627         }
01628 
01629         // Add incremental widget
01630         m_widget = new QWidget(this);
01631         m_incUi = new Ui::IncrementalSearchBar;
01632         m_incUi->setupUi(m_widget);
01633         m_layout->addWidget(m_widget);
01634 
01635         // Fill options menu
01636         m_incMenu = new QMenu();
01637         m_incUi->options->setMenu(m_incMenu);
01638         m_incMenuHighlightAll = m_incMenu->addAction(i18n("Hi&ghlight all"));
01639         m_incMenuHighlightAll->setCheckable(true);
01640         m_incMenuMatchCase = m_incMenu->addAction(i18n("&Match case"));
01641         m_incMenuMatchCase->setCheckable(true);
01642         m_incMenuFromCursor = m_incMenu->addAction(i18n("From &cursor"));
01643         m_incMenuFromCursor->setCheckable(true);
01644 
01645         // Icons
01646         m_incUi->mutate->setIcon(KIcon("arrow-up-double"));
01647         m_incUi->next->setIcon(KIcon("go-down"));
01648         m_incUi->prev->setIcon(KIcon("go-up"));
01649 
01650         // Focus proxy
01651         centralWidget()->setFocusProxy(m_incUi->pattern);
01652     }
01653 
01654     // Restore previous settings
01655     if (create) {
01656         setChecked(m_incMenuHighlightAll, m_incHighlightAll);
01657         setChecked(m_incMenuFromCursor, m_incFromCursor);
01658         setChecked(m_incMenuMatchCase, m_incMatchCase);
01659     }
01660 
01661     // Set initial search pattern
01662     m_incUi->pattern->setText(initialPattern);
01663     m_incUi->pattern->selectAll();
01664 
01665     // Propagate settings (slots are still inactive on purpose)
01666     const bool NOT_INVOKED_BY_USER_ACTION = false;
01667     onIncPatternChanged(initialPattern, NOT_INVOKED_BY_USER_ACTION);
01668 
01669     if (create) {
01670         // Slots
01671         connect(m_incUi->mutate, SIGNAL(clicked()), this, SLOT(onMutatePower()));
01672         connect(m_incUi->pattern, SIGNAL(returnPressed()), this, SLOT(onReturnPressed()));
01673         connect(m_incUi->pattern, SIGNAL(textChanged(const QString &)), this, SLOT(onIncPatternChanged(const QString &)));
01674         connect(m_incUi->next, SIGNAL(clicked()), this, SLOT(onIncNext()));
01675         connect(m_incUi->prev, SIGNAL(clicked()), this, SLOT(onIncPrev()));
01676         connect(m_incMenuMatchCase, SIGNAL(changed()), this, SLOT(onIncMatchCaseToggle()));
01677         connect(m_incMenuFromCursor, SIGNAL(changed()), this, SLOT(onIncFromCursorToggle()));
01678         connect(m_incMenuHighlightAll, SIGNAL(toggled(bool)), this, SLOT(onIncHighlightAllToggle(bool)));
01679 
01680         // Make button click open the menu as well. IMHO with the dropdown arrow present the button
01681         // better shows his nature than in instant popup mode.
01682         connect(m_incUi->options, SIGNAL(clicked()), m_incUi->options, SLOT(showMenu()));
01683     }
01684 
01685     // Focus
01686     if (m_widget->isVisible()) {
01687         m_incUi->pattern->setFocus(Qt::MouseFocusReason);
01688     }
01689 }
01690 
01691 
01692 
01693 bool KateSearchBar::isChecked(QCheckBox * checkbox) {
01694     Q_ASSERT(checkbox != NULL);
01695     return checkbox->checkState() == Qt::Checked;
01696 }
01697 
01698 
01699 
01700 bool KateSearchBar::isChecked(QAction * menuAction) {
01701     Q_ASSERT(menuAction != NULL);
01702     return menuAction->isChecked();
01703 }
01704 
01705 
01706 
01707 void KateSearchBar::setChecked(QCheckBox * checkbox, bool checked) {
01708     Q_ASSERT(checkbox != NULL);
01709     checkbox->setCheckState(checked ? Qt::Checked : Qt::Unchecked);
01710 }
01711 
01712 
01713 
01714 void KateSearchBar::setChecked(QAction * menuAction, bool checked) {
01715     Q_ASSERT(menuAction != NULL);
01716     menuAction->setChecked(checked);
01717 }
01718 
01719 
01720 
01721 void KateSearchBar::enableHighlights(bool enable) {
01722     if (enable) {
01723         m_view->addInternalHighlight(m_topRange);
01724     } else {
01725         m_view->removeInternalHighlight(m_topRange);
01726         m_topRange->deleteChildRanges();
01727     }
01728 }
01729 
01730 
01731 
01732 void KateSearchBar::resetHighlights() {
01733     enableHighlights(false);
01734     enableHighlights(true);
01735 }
01736 
01737 
01738 
01739 void KateSearchBar::showEvent(QShowEvent * event) {
01740     // Update init cursor
01741     if (m_incUi != NULL) {
01742         m_incInitCursor = m_view->cursorPosition();
01743     }
01744 
01745     enableHighlights(true);
01746     KateViewBarWidget::showEvent(event);
01747 }
01748 
01749 
01750 
01751 void KateSearchBar::hideEvent(QHideEvent * event) {
01752     enableHighlights(false);
01753     KateViewBarWidget::hideEvent(event);
01754 }
01755 
01756 
01757 
01758 // Kill our helpers again
01759 #ifdef FAST_DEBUG_ENABLE
01760 # undef FAST_DEBUG_ENABLE
01761 #endif
01762 #undef FAST_DEBUG
01763 
01764 
01765 
01766 #include "katesearchbar.moc"
01767 

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
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • KIO
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.6
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