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;
KDE 4.6 API Reference