File indexing completed on 2024-04-28 17:06:21
0001 /* 0002 SPDX-FileCopyrightText: 2010 Jan Lepper <dehtris@yahoo.de> 0003 SPDX-FileCopyrightText: 2010-2022 Krusader Krew <https://krusader.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "krsearchbar.h" 0009 0010 #include "../FileSystem/dirlisterinterface.h" 0011 #include "../defaults.h" 0012 #include "../icon.h" 0013 #include "../krglobal.h" 0014 #include "PanelView/krview.h" 0015 #include "PanelView/krviewitem.h" 0016 0017 #include <QDebug> 0018 #include <QGuiApplication> 0019 #include <QHBoxLayout> 0020 #include <QKeyEvent> 0021 #include <QLineEdit> 0022 #include <QToolButton> 0023 #include <QToolTip> 0024 0025 #include <KConfigCore/KSharedConfig> 0026 #include <KI18n/KLocalizedString> 0027 0028 KrSearchBar::KrSearchBar(KrView *view, QWidget *parent) 0029 : QWidget(parent) 0030 , _view(nullptr) 0031 , _rightArrowEntersDirFlag(true) 0032 { 0033 // close button 0034 auto *closeButton = new QToolButton(this); 0035 closeButton->setAutoRaise(true); 0036 closeButton->setIcon(Icon(QStringLiteral("dialog-close"))); 0037 closeButton->setToolTip(i18n("Close the search bar")); 0038 connect(closeButton, &QToolButton::clicked, this, &KrSearchBar::hideBar); 0039 0040 // combo box for changing search mode 0041 _modeBox = new QComboBox(this); 0042 _modeBox->addItems(QStringList() << i18n("Search") << i18n("Select") << i18n("Filter")); 0043 _modeBox->setToolTip(i18n("Change the search mode")); 0044 connect(_modeBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &KrSearchBar::onModeChange); 0045 0046 _currentMode = static_cast<SearchMode>(_modeBox->currentIndex()); 0047 0048 // combo box for entering search string 0049 _textBox = new KComboBox(this); 0050 _textBox->setEditable(true); 0051 _modeBox->setToolTip(i18n("Enter or select search string")); 0052 QStringList savedSearches = KConfigGroup(krConfig, "Private").readEntry("Predefined Selections", QStringList()); 0053 if (savedSearches.count() > 0) 0054 _textBox->addItems(savedSearches); 0055 _textBox->setCurrentText(""); 0056 _textBox->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); 0057 connect(_textBox, &KComboBox::currentTextChanged, this, &KrSearchBar::onSearchChange); 0058 0059 auto *saveSearchBtn = new QToolButton(this); 0060 saveSearchBtn->setIcon(Icon("document-save")); 0061 saveSearchBtn->setFixedSize(20, 20); 0062 saveSearchBtn->setToolTip(i18n("Save the current search string")); 0063 connect(saveSearchBtn, &QToolButton::clicked, this, &KrSearchBar::saveSearchString); 0064 0065 _openSelectDialogBtn = new QToolButton(this); 0066 _openSelectDialogBtn->setIcon(Icon("configure")); 0067 _openSelectDialogBtn->setFixedSize(20, 20); 0068 _openSelectDialogBtn->setToolTip(i18n("Open selection dialog")); 0069 0070 auto *layout = new QHBoxLayout(this); 0071 layout->setContentsMargins(0, 0, 0, 0); 0072 layout->addWidget(closeButton); 0073 layout->addWidget(_modeBox); 0074 layout->addWidget(_textBox); 0075 layout->addWidget(saveSearchBtn); 0076 layout->addWidget(_openSelectDialogBtn); 0077 0078 _textBox->installEventFilter(this); 0079 0080 setView(view); 0081 } 0082 0083 void KrSearchBar::setView(KrView *view) 0084 { 0085 if (_view) { 0086 _view->widget()->removeEventFilter(this); 0087 disconnect(_openSelectDialogBtn, nullptr, nullptr, nullptr); 0088 } 0089 0090 _view = view; 0091 0092 connect(_openSelectDialogBtn, &QToolButton::clicked, this, [this]() { 0093 _view->customSelection(true); 0094 }); 0095 _view->widget()->installEventFilter(this); 0096 } 0097 0098 void KrSearchBar::hideBarIfSearching() 0099 { 0100 if (_currentMode == MODE_SEARCH) 0101 hideBar(); 0102 } 0103 0104 // #### public slots 0105 0106 void KrSearchBar::showBar(SearchMode mode) 0107 { 0108 int index = 0109 mode == MODE_DEFAULT ? KConfigGroup(krConfig, "Look&Feel").readEntry("Default Search Mode", QString::number(KrSearchBar::MODE_SEARCH)).toInt() : mode; 0110 _modeBox->setCurrentIndex(index); 0111 0112 show(); 0113 _textBox->setFocus(); 0114 _rightArrowEntersDirFlag = true; 0115 } 0116 0117 void KrSearchBar::hideBar() 0118 { 0119 resetSearch(); 0120 if (_textBox->hasFocus()) 0121 _view->widget()->setFocus(); 0122 hide(); 0123 } 0124 0125 void KrSearchBar::resetSearch() 0126 { 0127 _textBox->clearEditText(); 0128 indicateMatch(true); 0129 } 0130 0131 // #### protected slots 0132 0133 void KrSearchBar::onModeChange() 0134 { 0135 if (_currentMode == MODE_FILTER) { 0136 _view->op()->filterSearch(QString(), true); // reset filter 0137 } 0138 _currentMode = static_cast<SearchMode>(_modeBox->currentIndex()); 0139 0140 onSearchChange(); 0141 } 0142 0143 void KrSearchBar::onSearchChange() 0144 { 0145 const QString text = _textBox->currentText(); 0146 0147 switch (_currentMode) { 0148 case MODE_SEARCH: { 0149 const bool anyMatch = _view->op()->searchItem(text, caseSensitive()); 0150 indicateMatch(anyMatch); 0151 break; 0152 } 0153 case MODE_SELECT: { 0154 _view->unselectAll(); 0155 if (!text.isEmpty()) { 0156 const bool anyMatch = _view->changeSelection(KrQuery(text, caseSensitive()), true); 0157 indicateMatch(anyMatch); 0158 } 0159 break; 0160 } 0161 case MODE_FILTER: { 0162 const bool anyMatch = _view->op()->filterSearch(text, caseSensitive()); 0163 indicateMatch(anyMatch); 0164 break; 0165 } 0166 default: 0167 qWarning() << "unexpected search mode: " << _currentMode; 0168 } 0169 0170 _textBox->setFocus(); 0171 } 0172 0173 void KrSearchBar::saveSearchString() 0174 { 0175 KConfigGroup group(krConfig, "Private"); 0176 QStringList lst = group.readEntry("Predefined Selections", QStringList()); 0177 QString searchString = _textBox->currentText(); 0178 if (lst.indexOf(searchString) != -1) { 0179 // already saved 0180 return; 0181 } 0182 0183 lst.append(searchString); 0184 group.writeEntry("Predefined Selections", lst); 0185 0186 _textBox->addItem(searchString); 0187 QToolTip::showText(QCursor::pos(), i18n("Saved search text to history")); 0188 } 0189 0190 // #### protected 0191 0192 void KrSearchBar::keyPressEvent(QKeyEvent *event) 0193 { 0194 const bool handled = handleKeyPressEvent(static_cast<QKeyEvent *>(event)); 0195 if (handled) { 0196 return; 0197 } 0198 0199 QWidget::keyPressEvent(event); 0200 } 0201 0202 bool KrSearchBar::eventFilter(QObject *watched, QEvent *event) 0203 { 0204 // only handle KeyPress events in this method 0205 if (event->type() != QEvent::KeyPress) { 0206 return false; 0207 } 0208 0209 qDebug() << "key press event=" << event; 0210 0211 auto *ke = static_cast<QKeyEvent *>(event); 0212 auto modifiers = ke->modifiers(); 0213 0214 if (watched == _view->widget()) { 0215 KConfigGroup grpSv(krConfig, "Look&Feel"); 0216 const bool autoShow = grpSv.readEntry("New Style Quicksearch", _NewStyleQuicksearch); 0217 0218 if (isHidden() && !autoShow) { 0219 return false; 0220 } 0221 0222 if (!isHidden()) { 0223 // view widget has focus but search bar is open and may wants to steal key events 0224 const bool handled = handleKeyPressEvent(ke); 0225 if (handled) { 0226 return true; 0227 } 0228 } 0229 0230 if (isHidden() || 0231 // view can handle its own event if user does not want to remove text or... 0232 !((ke->key() == Qt::Key_Backspace && !_textBox->currentText().isEmpty()) || 0233 // ...insert space in search bar (even if not focused) 0234 (ke->key() == Qt::Key_Space && _currentMode == KrSearchBar::MODE_SEARCH))) { 0235 const bool handled = _view->handleKeyEvent(ke); 0236 if (handled) { 0237 return true; 0238 } 0239 } 0240 0241 if (ke->text().isEmpty() || (modifiers != Qt::NoModifier && modifiers != Qt::ShiftModifier && modifiers != Qt::KeypadModifier)) { 0242 return false; 0243 } 0244 0245 // start searching if bar is hidden? 0246 if (isHidden()) { 0247 if (autoShow) { 0248 showBar(); 0249 } else { 0250 return false; 0251 } 0252 } 0253 0254 // bar is visible and gets the key input 0255 _textBox->setFocus(); 0256 if (ke->key() == Qt::Key_Backspace) { 0257 _textBox->lineEdit()->backspace(); 0258 } else { 0259 _textBox->setEditText(_textBox->currentText().append(ke->text())); 0260 } 0261 return true; 0262 } else if (watched == _textBox) { 0263 const bool handled = handleKeyPressEvent(ke); 0264 if (handled) { 0265 _view->widget()->setFocus(); 0266 return true; 0267 } 0268 // allow the view to handle (most) key events from the text box 0269 if ((modifiers == Qt::NoModifier || modifiers == Qt::KeypadModifier) && ke->key() != Qt::Key_Space && ke->key() != Qt::Key_Backspace 0270 && ke->key() != Qt::Key_Left && ke->key() != Qt::Key_Right) { 0271 const bool handled = _view->handleKeyEvent(ke); 0272 if (handled) { 0273 _view->widget()->setFocus(); 0274 return true; 0275 } 0276 } 0277 } 0278 return false; 0279 } 0280 0281 // #### private 0282 0283 bool KrSearchBar::handleKeyPressEvent(QKeyEvent *ke) 0284 { 0285 auto modifiers = ke->modifiers(); 0286 if (!(modifiers == Qt::NoModifier || modifiers == Qt::KeypadModifier)) { 0287 return false; 0288 } 0289 0290 switch (ke->key()) { 0291 case Qt::Key_Escape: { 0292 hideBar(); 0293 return true; 0294 } 0295 0296 case Qt::Key_Enter: 0297 case Qt::Key_Return: { 0298 hideBarIfSearching(); 0299 return false; 0300 } 0301 0302 case Qt::Key_Up: 0303 return handleUpDownKeyPress(true); 0304 case Qt::Key_Down: 0305 return handleUpDownKeyPress(false); 0306 case Qt::Key_Left: 0307 case Qt::Key_Right: 0308 return handleLeftRightKeyPress(ke); 0309 case Qt::Key_Insert: { 0310 // select current item and jump to next search result 0311 KrViewItem *item = _view->getCurrentKrViewItem(); 0312 if (item) { 0313 item->setSelected(!item->isSelected()); 0314 _view->op()->searchItem(_textBox->currentText(), caseSensitive(), 1); 0315 } 0316 return true; 0317 } 0318 case Qt::Key_Home: { 0319 // jump to first search result 0320 KrViewItem *item = _view->getLast(); 0321 if (item) { 0322 _view->setCurrentKrViewItem(_view->getLast()); 0323 _view->op()->searchItem(_textBox->currentText(), caseSensitive(), 1); 0324 } 0325 return true; 0326 } 0327 case Qt::Key_End: { 0328 // jump to last search result 0329 KrViewItem *item = _view->getFirst(); 0330 if (item) { 0331 _view->setCurrentKrViewItem(_view->getFirst()); 0332 _view->op()->searchItem(_textBox->currentText(), caseSensitive(), -1); 0333 } 0334 return true; 0335 } 0336 } 0337 return false; 0338 } 0339 0340 bool KrSearchBar::handleUpDownKeyPress(bool up) 0341 { 0342 if (_currentMode != MODE_SEARCH) { 0343 return false; 0344 } 0345 0346 const bool updownCancel = KConfigGroup(krConfig, "Look&Feel").readEntry("Up/Down Cancels Quicksearch", false); 0347 if (updownCancel) { 0348 hideBar(); 0349 return false; 0350 } 0351 0352 const bool anyMatch = _view->op()->searchItem(_textBox->currentText(), caseSensitive(), up ? -1 : 1); 0353 indicateMatch(anyMatch); 0354 return true; 0355 } 0356 0357 bool KrSearchBar::handleLeftRightKeyPress(QKeyEvent *ke) 0358 { 0359 const bool useQuickDirectoryNavigation = KConfigGroup(krConfig, "Look&Feel").readEntry("Navigation with Right Arrow Quicksearch", true); 0360 if (!useQuickDirectoryNavigation) 0361 return false; 0362 0363 const bool isRight = ke->key() == Qt::Key_Right; 0364 0365 if (isRight && _rightArrowEntersDirFlag) { 0366 // in case the Right Arrow has been pressed when cursor is in the end of the line 0367 if (_textBox->cursorPosition() == _textBox->currentText().length()) 0368 // we let the view enter the directory if it's selected 0369 return _view->handleKeyEvent(ke); 0370 } else { 0371 _rightArrowEntersDirFlag = false; 0372 } 0373 0374 return false; 0375 } 0376 0377 void KrSearchBar::indicateMatch(bool anyMatch) 0378 { 0379 KConfigGroup gc(krConfig, "Colors"); 0380 QPalette p = QGuiApplication::palette(); 0381 QString foreground, background; 0382 if (anyMatch) { 0383 foreground = "Quicksearch Match Foreground"; 0384 background = "Quicksearch Match Background"; 0385 } else { 0386 foreground = "Quicksearch Non-match Foreground"; 0387 background = "Quicksearch Non-match Background"; 0388 } 0389 0390 QColor fore = Qt::black; 0391 QString foreSetting = gc.readEntry(foreground, QString()); 0392 if (foreSetting == "KDE default") { 0393 fore = p.color(QPalette::Active, QPalette::Text); 0394 } else if (!foreSetting.isEmpty()) { 0395 fore = gc.readEntry(foreground, fore); 0396 } 0397 0398 QColor back = anyMatch ? QColor(192, 255, 192) : QColor(255, 192, 192); 0399 QString backSetting = gc.readEntry(background, QString()); 0400 if (backSetting == "KDE default") { 0401 back = p.color(QPalette::Active, QPalette::Base); 0402 } else if (!backSetting.isEmpty()) { 0403 back = gc.readEntry(background, back); 0404 } 0405 0406 QPalette pal = palette(); 0407 pal.setColor(QPalette::Base, back); 0408 pal.setColor(QPalette::Text, fore); 0409 _textBox->lineEdit()->setPalette(pal); 0410 } 0411 0412 bool KrSearchBar::caseSensitive() 0413 { 0414 KConfigGroup grpSvr(krConfig, "Look&Feel"); 0415 return grpSvr.readEntry("Case Sensitive Quicksearch", _CaseSensitiveQuicksearch); 0416 }