You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
318 lines
12 KiB
318 lines
12 KiB
/* |
|
Copyright 2006-2008 by Robert Knight <robertknight@gmail.com> |
|
|
|
This program is free software; you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation; either version 2 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program; if not, write to the Free Software |
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
02110-1301 USA. |
|
*/ |
|
|
|
// Own |
|
#include "IncrementalSearchBar.h" |
|
|
|
// Qt |
|
#include <QHBoxLayout> |
|
#include <QKeyEvent> |
|
#include <QTimer> |
|
#include <QToolButton> |
|
#include <QMenu> |
|
#include <QApplication> |
|
|
|
// KDE |
|
#include <KColorScheme> |
|
#include <QLineEdit> |
|
#include <KLocalizedString> |
|
#include "KonsoleSettings.h" |
|
|
|
using namespace Konsole; |
|
|
|
IncrementalSearchBar::IncrementalSearchBar(QWidget *parent) : |
|
QWidget(parent), |
|
_searchEdit(nullptr), |
|
_caseSensitive(nullptr), |
|
_regExpression(nullptr), |
|
_highlightMatches(nullptr), |
|
_reverseSearch(nullptr), |
|
_findNextButton(nullptr), |
|
_findPreviousButton(nullptr), |
|
_searchFromButton(nullptr), |
|
_searchTimer(nullptr) |
|
{ |
|
setPalette(qApp->palette()); |
|
setAutoFillBackground(true); |
|
|
|
// SubWindow flag limits tab focus switching to this widget |
|
setWindowFlags(windowFlags() | Qt::SubWindow); |
|
|
|
_searchEdit = new QLineEdit(this); |
|
_searchEdit->setClearButtonEnabled(true); |
|
_searchEdit->installEventFilter(this); |
|
_searchEdit->setPlaceholderText(i18nc("@label:textbox", "Find...")); |
|
_searchEdit->setObjectName(QStringLiteral("search-edit")); |
|
_searchEdit->setToolTip(i18nc("@info:tooltip", "Enter the text to search for here")); |
|
_searchEdit->setCursor(Qt::IBeamCursor); |
|
_searchEdit->setStyleSheet(QString()); |
|
_searchEdit->setFont(QApplication::font()); |
|
// When the widget focus is set, focus input box instead |
|
setFocusProxy(_searchEdit); |
|
|
|
setCursor(Qt::ArrowCursor); |
|
// text box may be a minimum of 6 characters wide and a maximum of 10 characters wide |
|
// (since the maxWidth metric is used here, more characters probably will fit in than 6 |
|
// and 10) |
|
QFontMetrics metrics(_searchEdit->font()); |
|
int maxWidth = metrics.maxWidth(); |
|
_searchEdit->setMinimumWidth(maxWidth * 6); |
|
_searchEdit->setMaximumWidth(maxWidth * 10); |
|
|
|
_searchTimer = new QTimer(this); |
|
_searchTimer->setInterval(250); |
|
_searchTimer->setSingleShot(true); |
|
connect(_searchTimer, &QTimer::timeout, this, |
|
&Konsole::IncrementalSearchBar::notifySearchChanged); |
|
connect(_searchEdit, &QLineEdit::textChanged, _searchTimer, |
|
static_cast<void (QTimer::*)()>(&QTimer::start)); |
|
|
|
_findNextButton = new QToolButton(this); |
|
_findNextButton->setObjectName(QStringLiteral("find-next-button")); |
|
_findNextButton->setText(i18nc("@action:button Go to the next phrase", "Next")); |
|
_findNextButton->setToolButtonStyle(Qt::ToolButtonIconOnly); |
|
_findNextButton->setAutoRaise(true); |
|
_findNextButton->setToolTip(i18nc("@info:tooltip", |
|
"Find the next match for the current search phrase")); |
|
_findNextButton->installEventFilter(this); |
|
connect(_findNextButton, &QToolButton::clicked, this, |
|
&Konsole::IncrementalSearchBar::findNextClicked); |
|
|
|
_findPreviousButton = new QToolButton(this); |
|
_findPreviousButton->setAutoRaise(true); |
|
_findPreviousButton->setObjectName(QStringLiteral("find-previous-button")); |
|
_findPreviousButton->setText(i18nc("@action:button Go to the previous phrase", "Previous")); |
|
_findPreviousButton->setToolButtonStyle(Qt::ToolButtonIconOnly); |
|
_findPreviousButton->setToolTip(i18nc("@info:tooltip", |
|
"Find the previous match for the current search phrase")); |
|
_findPreviousButton->installEventFilter(this); |
|
connect(_findPreviousButton, &QToolButton::clicked, this, |
|
&Konsole::IncrementalSearchBar::findPreviousClicked); |
|
|
|
_searchFromButton = new QToolButton(this); |
|
_searchFromButton->setAutoRaise(true); |
|
_searchFromButton->setObjectName(QStringLiteral("search-from-button")); |
|
_searchFromButton->installEventFilter(this); |
|
connect(_searchFromButton, &QToolButton::clicked, this, |
|
&Konsole::IncrementalSearchBar::searchFromClicked); |
|
|
|
auto optionsButton = new QToolButton(this); |
|
optionsButton->setObjectName(QStringLiteral("find-options-button")); |
|
optionsButton->setCheckable(false); |
|
optionsButton->setPopupMode(QToolButton::InstantPopup); |
|
optionsButton->setToolButtonStyle(Qt::ToolButtonIconOnly); |
|
optionsButton->setToolTip(i18nc("@info:tooltip", "Display the options menu")); |
|
optionsButton->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); |
|
optionsButton->setAutoRaise(true); |
|
optionsButton->installEventFilter(this); |
|
|
|
auto closeButton = new QToolButton(this); |
|
closeButton->setObjectName(QStringLiteral("close-button")); |
|
closeButton->setToolTip(i18nc("@info:tooltip", "Close the search bar")); |
|
closeButton->setAutoRaise(true); |
|
closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); |
|
closeButton->installEventFilter(this); |
|
connect(closeButton, &QToolButton::clicked, this, &Konsole::IncrementalSearchBar::closeClicked); |
|
|
|
// Fill the options menu |
|
auto optionsMenu = new QMenu(this); |
|
optionsButton->setMenu(optionsMenu); |
|
|
|
_caseSensitive = optionsMenu->addAction(i18nc("@item:inmenu", "Case sensitive")); |
|
_caseSensitive->setCheckable(true); |
|
_caseSensitive->setToolTip(i18nc("@info:tooltip", "Sets whether the search is case sensitive")); |
|
connect(_caseSensitive, &QAction::toggled, this, |
|
&Konsole::IncrementalSearchBar::matchCaseToggled); |
|
|
|
_regExpression = optionsMenu->addAction(i18nc("@item:inmenu", "Match regular expression")); |
|
_regExpression->setCheckable(true); |
|
connect(_regExpression, &QAction::toggled, this, |
|
&Konsole::IncrementalSearchBar::matchRegExpToggled); |
|
|
|
_highlightMatches = optionsMenu->addAction(i18nc("@item:inmenu", "Highlight all matches")); |
|
_highlightMatches->setCheckable(true); |
|
_highlightMatches->setToolTip(i18nc("@info:tooltip", |
|
"Sets whether matching text should be highlighted")); |
|
connect(_highlightMatches, &QAction::toggled, this, |
|
&Konsole::IncrementalSearchBar::highlightMatchesToggled); |
|
|
|
_reverseSearch = optionsMenu->addAction(i18nc("@item:inmenu", "Search backwards")); |
|
_reverseSearch->setCheckable(true); |
|
_reverseSearch->setToolTip(i18nc("@info:tooltip", |
|
"Sets whether search should start from the bottom")); |
|
connect(_reverseSearch, &QAction::toggled, this, |
|
&Konsole::IncrementalSearchBar::updateButtonsAccordingToReverseSearchSetting); |
|
updateButtonsAccordingToReverseSearchSetting(); |
|
setOptions(); |
|
|
|
auto barLayout = new QHBoxLayout(this); |
|
barLayout->addWidget(_searchEdit); |
|
barLayout->addWidget(_findNextButton); |
|
barLayout->addWidget(_findPreviousButton); |
|
barLayout->addWidget(_searchFromButton); |
|
barLayout->addWidget(optionsButton); |
|
barLayout->addWidget(closeButton); |
|
barLayout->setContentsMargins(4, 4, 4, 4); |
|
barLayout->setSpacing(0); |
|
|
|
setLayout(barLayout); |
|
adjustSize(); |
|
clearLineEdit(); |
|
} |
|
|
|
void IncrementalSearchBar::notifySearchChanged() |
|
{ |
|
emit searchChanged(searchText()); |
|
} |
|
|
|
void IncrementalSearchBar::updateButtonsAccordingToReverseSearchSetting() |
|
{ |
|
Q_ASSERT(_reverseSearch); |
|
if (_reverseSearch->isChecked()) { |
|
_searchFromButton->setToolTip(i18nc("@info:tooltip", |
|
"Search for the current search phrase from the bottom")); |
|
_searchFromButton->setIcon(QIcon::fromTheme(QStringLiteral("go-bottom"))); |
|
_findNextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); |
|
_findPreviousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); |
|
} else { |
|
_searchFromButton->setToolTip(i18nc("@info:tooltip", |
|
"Search for the current search phrase from the top")); |
|
_searchFromButton->setIcon(QIcon::fromTheme(QStringLiteral("go-top"))); |
|
_findNextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); |
|
_findPreviousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); |
|
} |
|
} |
|
|
|
QString IncrementalSearchBar::searchText() |
|
{ |
|
return _searchEdit->text(); |
|
} |
|
|
|
void IncrementalSearchBar::setSearchText(const QString &text) |
|
{ |
|
if (text != searchText()) { |
|
_searchEdit->setText(text); |
|
} |
|
} |
|
|
|
bool IncrementalSearchBar::eventFilter(QObject *watched, QEvent *event) |
|
{ |
|
if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { |
|
auto *keyEvent = static_cast<QKeyEvent *>(event); |
|
QToolButton *toolButton = nullptr; |
|
|
|
if (keyEvent->key() == Qt::Key_Return) { |
|
if (watched == _searchEdit && event->type() == QEvent::KeyPress) { |
|
if (keyEvent->modifiers() == Qt::NoModifier) { |
|
_findNextButton->click(); |
|
return true; |
|
} |
|
if (keyEvent->modifiers() == Qt::ShiftModifier) { |
|
_findPreviousButton->click(); |
|
return true; |
|
} |
|
if (keyEvent->modifiers() == Qt::ControlModifier) { |
|
_searchFromButton->click(); |
|
return true; |
|
} |
|
} else if ((toolButton = qobject_cast<QToolButton *>(watched)) != nullptr) { |
|
if(event->type() == QEvent::KeyPress && !toolButton->isDown()) { |
|
toolButton->setDown(true); |
|
toolButton->pressed(); |
|
} else if (toolButton->isDown()) { |
|
toolButton->setDown(keyEvent->isAutoRepeat()); |
|
toolButton->released(); |
|
toolButton->click(); |
|
} |
|
return true; |
|
} |
|
} |
|
} |
|
return QWidget::eventFilter(watched, event); |
|
} |
|
|
|
void IncrementalSearchBar::keyPressEvent(QKeyEvent *event) |
|
{ |
|
static auto movementKeysToPassAlong = QSet<int>{ |
|
Qt::Key_PageUp, Qt::Key_PageDown, Qt::Key_Up, Qt::Key_Down |
|
}; |
|
|
|
if (movementKeysToPassAlong.contains(event->key()) |
|
&& (event->modifiers() == Qt::ShiftModifier)) { |
|
emit unhandledMovementKeyPressed(event); |
|
} |
|
|
|
if (event->key() == Qt::Key_Escape) { |
|
emit closeClicked(); |
|
} |
|
} |
|
|
|
void IncrementalSearchBar::setVisible(bool visible) |
|
{ |
|
QWidget::setVisible(visible); |
|
|
|
if (visible) { |
|
focusLineEdit(); |
|
} |
|
} |
|
|
|
void IncrementalSearchBar::setFoundMatch(bool match) |
|
{ |
|
if (_searchEdit->text().isEmpty()) { |
|
clearLineEdit(); |
|
return; |
|
} |
|
|
|
const auto backgroundBrush = KStatefulBrush(KColorScheme::View, |
|
match ? KColorScheme::PositiveBackground : KColorScheme::NegativeBackground); |
|
|
|
const auto matchStyleSheet = QStringLiteral("QLineEdit{ background-color:%1 }") |
|
.arg(backgroundBrush.brush(_searchEdit).color().name()); |
|
|
|
_searchEdit->setStyleSheet(matchStyleSheet); |
|
} |
|
|
|
void IncrementalSearchBar::clearLineEdit() |
|
{ |
|
_searchEdit->setStyleSheet(QString()); |
|
} |
|
|
|
void IncrementalSearchBar::focusLineEdit() |
|
{ |
|
_searchEdit->setFocus(Qt::ActiveWindowFocusReason); |
|
_searchEdit->selectAll(); |
|
} |
|
|
|
const QBitArray IncrementalSearchBar::optionsChecked() |
|
{ |
|
QBitArray options(4, false); |
|
options.setBit(MatchCase, _caseSensitive->isChecked()); |
|
options.setBit(RegExp, _regExpression->isChecked()); |
|
options.setBit(HighlightMatches, _highlightMatches->isChecked()); |
|
options.setBit(ReverseSearch, _reverseSearch->isChecked()); |
|
return options; |
|
} |
|
|
|
void IncrementalSearchBar::setOptions() |
|
{ |
|
_caseSensitive->setChecked(KonsoleSettings::searchCaseSensitive()); |
|
_regExpression->setChecked(KonsoleSettings::searchRegExpression()); |
|
_highlightMatches->setChecked(KonsoleSettings::searchHighlightMatches()); |
|
_reverseSearch->setChecked(KonsoleSettings::searchReverseSearch()); |
|
}
|
|
|