File indexing completed on 2024-04-28 05:50:54
0001 /* 0002 SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 // Own 0008 #include "widgets/IncrementalSearchBar.h" 0009 0010 // Qt 0011 #include <QApplication> 0012 #include <QHBoxLayout> 0013 #include <QKeyEvent> 0014 #include <QMenu> 0015 #include <QTimer> 0016 #include <QToolButton> 0017 0018 // KDE 0019 #include "KonsoleSettings.h" 0020 0021 #include <KLocalizedString> 0022 #include <KStatefulBrush> 0023 #include <QLineEdit> 0024 #include <kconfigwidgets_version.h> 0025 0026 using namespace Konsole; 0027 0028 IncrementalSearchBar::IncrementalSearchBar(QWidget *parent) 0029 : QWidget(parent) 0030 , _searchEdit(nullptr) 0031 , _caseSensitive(nullptr) 0032 , _regExpression(nullptr) 0033 , _highlightMatches(nullptr) 0034 , _reverseSearch(nullptr) 0035 , _findNextButton(nullptr) 0036 , _findPreviousButton(nullptr) 0037 , _searchFromButton(nullptr) 0038 , _searchTimer(nullptr) 0039 { 0040 setPalette(qApp->palette()); 0041 setAutoFillBackground(true); 0042 0043 // SubWindow flag limits tab focus switching to this widget 0044 setWindowFlags(windowFlags() | Qt::SubWindow); 0045 0046 _searchEdit = new QLineEdit(this); 0047 _searchEdit->setClearButtonEnabled(true); 0048 _searchEdit->installEventFilter(this); 0049 _searchEdit->setPlaceholderText(i18nc("@label:textbox", "Find...")); 0050 _searchEdit->setObjectName(QStringLiteral("search-edit")); 0051 _searchEdit->setToolTip(i18nc("@info:tooltip", "Enter the text to search for here")); 0052 _searchEdit->setCursor(Qt::IBeamCursor); 0053 _searchEdit->setStyleSheet(QString()); 0054 _searchEdit->setFont(QApplication::font()); 0055 // When the widget focus is set, focus input box instead 0056 setFocusProxy(_searchEdit); 0057 0058 setCursor(Qt::ArrowCursor); 0059 // text box may be a minimum of 6 characters wide and a maximum of 10 characters wide 0060 // (since the maxWidth metric is used here, more characters probably will fit in than 6 0061 // and 10) 0062 QFontMetrics metrics(_searchEdit->font()); 0063 int maxWidth = metrics.maxWidth(); 0064 _searchEdit->setMinimumWidth(maxWidth * 6); 0065 _searchEdit->setMaximumWidth(maxWidth * 10); 0066 0067 _searchTimer = new QTimer(this); 0068 _searchTimer->setInterval(250); 0069 _searchTimer->setSingleShot(true); 0070 connect(_searchTimer, &QTimer::timeout, this, &Konsole::IncrementalSearchBar::notifySearchChanged); 0071 connect(_searchEdit, &QLineEdit::textChanged, _searchTimer, static_cast<void (QTimer::*)()>(&QTimer::start)); 0072 0073 _findNextButton = new QToolButton(this); 0074 _findNextButton->setObjectName(QStringLiteral("find-next-button")); 0075 _findNextButton->setText(i18nc("@action:button Go to the next phrase", "Next")); 0076 _findNextButton->setToolButtonStyle(Qt::ToolButtonIconOnly); 0077 _findNextButton->setAutoRaise(true); 0078 _findNextButton->setToolTip(i18nc("@info:tooltip", "Find the next match for the current search phrase")); 0079 _findNextButton->installEventFilter(this); 0080 connect(_findNextButton, &QToolButton::clicked, this, &Konsole::IncrementalSearchBar::findNextClicked); 0081 0082 _findPreviousButton = new QToolButton(this); 0083 _findPreviousButton->setAutoRaise(true); 0084 _findPreviousButton->setObjectName(QStringLiteral("find-previous-button")); 0085 _findPreviousButton->setText(i18nc("@action:button Go to the previous phrase", "Previous")); 0086 _findPreviousButton->setToolButtonStyle(Qt::ToolButtonIconOnly); 0087 _findPreviousButton->setToolTip(i18nc("@info:tooltip", "Find the previous match for the current search phrase")); 0088 _findPreviousButton->installEventFilter(this); 0089 connect(_findPreviousButton, &QToolButton::clicked, this, &Konsole::IncrementalSearchBar::findPreviousClicked); 0090 0091 _searchFromButton = new QToolButton(this); 0092 _searchFromButton->setAutoRaise(true); 0093 _searchFromButton->setObjectName(QStringLiteral("search-from-button")); 0094 _searchFromButton->installEventFilter(this); 0095 connect(_searchFromButton, &QToolButton::clicked, this, &Konsole::IncrementalSearchBar::searchFromClicked); 0096 0097 auto optionsButton = new QToolButton(this); 0098 optionsButton->setObjectName(QStringLiteral("find-options-button")); 0099 optionsButton->setCheckable(false); 0100 optionsButton->setPopupMode(QToolButton::InstantPopup); 0101 optionsButton->setToolButtonStyle(Qt::ToolButtonIconOnly); 0102 optionsButton->setToolTip(i18nc("@info:tooltip", "Display the options menu")); 0103 optionsButton->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); 0104 optionsButton->setAutoRaise(true); 0105 optionsButton->installEventFilter(this); 0106 0107 auto closeButton = new QToolButton(this); 0108 closeButton->setObjectName(QStringLiteral("close-button")); 0109 closeButton->setToolTip(i18nc("@info:tooltip", "Close the search bar")); 0110 closeButton->setAutoRaise(true); 0111 closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); 0112 closeButton->installEventFilter(this); 0113 connect(closeButton, &QToolButton::clicked, this, &Konsole::IncrementalSearchBar::closeClicked); 0114 0115 // Fill the options menu 0116 auto optionsMenu = new QMenu(this); 0117 optionsButton->setMenu(optionsMenu); 0118 0119 _caseSensitive = optionsMenu->addAction(i18nc("@item:inmenu", "Case sensitive")); 0120 _caseSensitive->setCheckable(true); 0121 _caseSensitive->setToolTip(i18nc("@info:tooltip", "Sets whether the search is case sensitive")); 0122 connect(_caseSensitive, &QAction::toggled, this, &Konsole::IncrementalSearchBar::matchCaseToggled); 0123 0124 _regExpression = optionsMenu->addAction(i18nc("@item:inmenu", "Match regular expression")); 0125 _regExpression->setCheckable(true); 0126 connect(_regExpression, &QAction::toggled, this, &Konsole::IncrementalSearchBar::matchRegExpToggled); 0127 0128 _highlightMatches = optionsMenu->addAction(i18nc("@item:inmenu", "Highlight all matches")); 0129 _highlightMatches->setCheckable(true); 0130 _highlightMatches->setToolTip(i18nc("@info:tooltip", "Sets whether matching text should be highlighted")); 0131 connect(_highlightMatches, &QAction::toggled, this, &Konsole::IncrementalSearchBar::highlightMatchesToggled); 0132 0133 _reverseSearch = optionsMenu->addAction(i18nc("@item:inmenu", "Search backwards")); 0134 _reverseSearch->setCheckable(true); 0135 _reverseSearch->setToolTip(i18nc("@info:tooltip", "Sets whether search should start from the bottom")); 0136 connect(_reverseSearch, &QAction::toggled, this, &Konsole::IncrementalSearchBar::updateButtonsAccordingToReverseSearchSetting); 0137 connect(_reverseSearch, &QAction::toggled, this, &Konsole::IncrementalSearchBar::reverseSearchToggled); 0138 updateButtonsAccordingToReverseSearchSetting(); 0139 setOptions(); 0140 0141 auto barLayout = new QHBoxLayout(this); 0142 barLayout->addWidget(_searchEdit); 0143 barLayout->addWidget(_findNextButton); 0144 barLayout->addWidget(_findPreviousButton); 0145 barLayout->addWidget(_searchFromButton); 0146 barLayout->addWidget(optionsButton); 0147 barLayout->addWidget(closeButton); 0148 barLayout->setContentsMargins(4, 4, 4, 4); 0149 barLayout->setSpacing(0); 0150 0151 setLayout(barLayout); 0152 adjustSize(); 0153 clearLineEdit(); 0154 } 0155 0156 void IncrementalSearchBar::notifySearchChanged() 0157 { 0158 Q_EMIT searchChanged(searchText()); 0159 } 0160 0161 void IncrementalSearchBar::updateButtonsAccordingToReverseSearchSetting() 0162 { 0163 Q_ASSERT(_reverseSearch); 0164 if (_reverseSearch->isChecked()) { 0165 _searchFromButton->setToolTip(i18nc("@info:tooltip", "Search for the current search phrase from the bottom")); 0166 _searchFromButton->setIcon(QIcon::fromTheme(QStringLiteral("go-bottom"))); 0167 _findNextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); 0168 _findPreviousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); 0169 } else { 0170 _searchFromButton->setToolTip(i18nc("@info:tooltip", "Search for the current search phrase from the top")); 0171 _searchFromButton->setIcon(QIcon::fromTheme(QStringLiteral("go-top"))); 0172 _findNextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); 0173 _findPreviousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); 0174 } 0175 } 0176 0177 QString IncrementalSearchBar::searchText() 0178 { 0179 return _searchEdit->text(); 0180 } 0181 0182 void IncrementalSearchBar::setSearchText(const QString &text) 0183 { 0184 if (text != searchText()) { 0185 _searchEdit->setText(text); 0186 } 0187 } 0188 0189 bool IncrementalSearchBar::eventFilter(QObject *watched, QEvent *event) 0190 { 0191 if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { 0192 auto *keyEvent = static_cast<QKeyEvent *>(event); 0193 QToolButton *toolButton = nullptr; 0194 0195 if (keyEvent->key() == Qt::Key_Return) { 0196 if (watched == _searchEdit && event->type() == QEvent::KeyPress) { 0197 if (keyEvent->modifiers() == Qt::NoModifier) { 0198 _findNextButton->click(); 0199 return true; 0200 } 0201 if (keyEvent->modifiers() == Qt::ShiftModifier) { 0202 _findPreviousButton->click(); 0203 return true; 0204 } 0205 if (keyEvent->modifiers() == Qt::ControlModifier) { 0206 _searchFromButton->click(); 0207 return true; 0208 } 0209 } else if ((toolButton = qobject_cast<QToolButton *>(watched)) != nullptr) { 0210 if (event->type() == QEvent::KeyPress && !toolButton->isDown()) { 0211 toolButton->setDown(true); 0212 toolButton->pressed(); 0213 } else if (toolButton->isDown()) { 0214 toolButton->setDown(keyEvent->isAutoRepeat()); 0215 toolButton->released(); 0216 toolButton->click(); 0217 } 0218 return true; 0219 } 0220 } 0221 } 0222 return QWidget::eventFilter(watched, event); 0223 } 0224 0225 void IncrementalSearchBar::keyPressEvent(QKeyEvent *event) 0226 { 0227 static auto movementKeysToPassAlong = QSet<int>{Qt::Key_PageUp, Qt::Key_PageDown, Qt::Key_Up, Qt::Key_Down}; 0228 0229 if (movementKeysToPassAlong.contains(event->key()) && (event->modifiers() == Qt::ShiftModifier)) { 0230 Q_EMIT unhandledMovementKeyPressed(event); 0231 } 0232 0233 if (event->key() == Qt::Key_Escape) { 0234 Q_EMIT closeClicked(); 0235 } 0236 } 0237 0238 void IncrementalSearchBar::setVisible(bool visible) 0239 { 0240 QWidget::setVisible(visible); 0241 0242 if (visible) { 0243 focusLineEdit(); 0244 } 0245 } 0246 0247 void IncrementalSearchBar::setFoundMatch(bool match) 0248 { 0249 if (_searchEdit->text().isEmpty()) { 0250 clearLineEdit(); 0251 return; 0252 } 0253 0254 const auto backgroundBrush = KStatefulBrush(KColorScheme::View, match ? KColorScheme::PositiveBackground : KColorScheme::NegativeBackground); 0255 0256 const auto matchStyleSheet = QStringLiteral("QLineEdit{ background-color:%1 }").arg(backgroundBrush.brush(_searchEdit->palette()).color().name()); 0257 0258 _searchEdit->setStyleSheet(matchStyleSheet); 0259 } 0260 0261 void IncrementalSearchBar::clearLineEdit() 0262 { 0263 _searchEdit->setStyleSheet(QString()); 0264 } 0265 0266 void IncrementalSearchBar::focusLineEdit() 0267 { 0268 _searchEdit->setFocus(Qt::ActiveWindowFocusReason); 0269 _searchEdit->selectAll(); 0270 } 0271 0272 const QBitArray IncrementalSearchBar::optionsChecked() 0273 { 0274 QBitArray options(4, false); 0275 options.setBit(MatchCase, _caseSensitive->isChecked()); 0276 options.setBit(RegExp, _regExpression->isChecked()); 0277 options.setBit(HighlightMatches, _highlightMatches->isChecked()); 0278 options.setBit(ReverseSearch, _reverseSearch->isChecked()); 0279 return options; 0280 } 0281 0282 void IncrementalSearchBar::setOptions() 0283 { 0284 _caseSensitive->setChecked(KonsoleSettings::searchCaseSensitive()); 0285 _regExpression->setChecked(KonsoleSettings::searchRegExpression()); 0286 _highlightMatches->setChecked(KonsoleSettings::searchHighlightMatches()); 0287 _reverseSearch->setChecked(KonsoleSettings::searchReverseSearch()); 0288 } 0289 0290 #include "moc_IncrementalSearchBar.cpp"