diff options
| author | Felix Ernst <[email protected]> | 2026-04-23 18:22:49 +0200 |
|---|---|---|
| committer | Felix Ernst <[email protected]> | 2026-04-27 13:26:59 +0200 |
| commit | 1e13c6abb6fc179fa8da32fe62df89560a801b3d (patch) | |
| tree | a154114e7144f5b6678e7b394bf7c7423b56051b /src | |
| parent | 2d7dac81ad66672aa2332cc37e3439755fe1f930 (diff) | |
Show type-ahead typing feedback in the status bar
The typed keys are displayed in the status bar while also displaying
which file name they were auto-completed to (i.e. which file was
selected because of the typing).
This commit contains some refactoring to keep the original status bar
functionality working as expected.
This commit also separates DolphinMainWindow from DolphinStatusBar which
is great news architecture-wise. The status bar is encapsulated within
the DolphinViewContainer.
Diffstat (limited to 'src')
| -rw-r--r-- | src/dolphinviewcontainer.cpp | 13 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistcontroller.cpp | 1 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistcontroller.h | 7 | ||||
| -rw-r--r-- | src/statusbar/dolphinstatusbar.cpp | 101 | ||||
| -rw-r--r-- | src/statusbar/dolphinstatusbar.h | 42 | ||||
| -rw-r--r-- | src/views/dolphinview.cpp | 19 | ||||
| -rw-r--r-- | src/views/dolphinview.h | 5 |
7 files changed, 121 insertions, 67 deletions
diff --git a/src/dolphinviewcontainer.cpp b/src/dolphinviewcontainer.cpp index 3db6ca553..968919b63 100644 --- a/src/dolphinviewcontainer.cpp +++ b/src/dolphinviewcontainer.cpp @@ -149,10 +149,10 @@ DolphinViewContainer::DolphinViewContainer(const QUrl &url, QWidget *parent) m_statusBar->setZoomLevel(m_view->zoomLevel()); connect(m_view, &DolphinView::urlChanged, m_statusBar, &DolphinStatusBar::setUrl); connect(m_view, &DolphinView::zoomLevelChanged, m_statusBar, &DolphinStatusBar::setZoomLevel); - connect(m_view, &DolphinView::infoMessage, m_statusBar, &DolphinStatusBar::setText); - connect(m_view, &DolphinView::operationCompletedMessage, m_statusBar, &DolphinStatusBar::setText); + connect(m_view, &DolphinView::infoMessage, m_statusBar, &DolphinStatusBar::setTemporaryRichText); + connect(m_view, &DolphinView::operationCompletedMessage, m_statusBar, &DolphinStatusBar::setTemporaryRichText); + connect(m_view, &DolphinView::showTypeAheadFeedback, m_statusBar, &DolphinStatusBar::setTemporaryRichText); connect(m_view, &DolphinView::statusBarTextChanged, m_statusBar, &DolphinStatusBar::setDefaultText); - connect(m_view, &DolphinView::statusBarTextChanged, m_statusBar, &DolphinStatusBar::resetToDefaultText); connect(m_view, &DolphinView::directoryLoadingProgress, m_statusBar, [this](int percent) { m_statusBar->showProgress(i18nc("@info:progress", "Loading folder…"), percent); }); @@ -696,7 +696,7 @@ void DolphinViewContainer::slotDirectoryLoadingCompleted() if (isSearchUrl(url()) && m_view->itemsCount() == 0) { // The dir lister has been completed on a Baloo-URI and no items have been found. Instead // of showing the default status bar information ("0 items") a more helpful information is given: - m_statusBar->setText(i18nc("@info:status", "No items found.")); + m_statusBar->setDefaultText(i18nc("@info:status", "No items found.")); } else { updateStatusBar(); } @@ -720,7 +720,6 @@ void DolphinViewContainer::slotDirectoryLoadingCompleted() void DolphinViewContainer::slotDirectoryLoadingCanceled() { m_statusBar->showProgress(QString(), 100); - m_statusBar->setText(QString()); } void DolphinViewContainer::slotUrlIsFileError(const QUrl &url) @@ -839,9 +838,9 @@ void DolphinViewContainer::slotItemsActivated(const KFileItemList &items) void DolphinViewContainer::showItemInfo(const KFileItem &item) { if (item.isNull()) { - m_statusBar->resetToDefaultText(); + m_statusBar->setHoveredItemText(QString()); } else { - m_statusBar->setText(item.getStatusBarInfo()); + m_statusBar->setHoveredItemText(item.getStatusBarInfo()); } } diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp index ef0f9dc14..e87ed3c18 100644 --- a/src/kitemviews/kitemlistcontroller.cpp +++ b/src/kitemviews/kitemlistcontroller.cpp @@ -547,6 +547,7 @@ void KItemListController::slotChangeCurrentItem(const QString &text, bool search m_view->scrollToItem(index, KItemListView::ViewItemPosition::Beginning); *found = true; } + Q_EMIT typeAheadUsed(text, index >= 0 ? std::make_optional<int>(index) : std::nullopt); } void KItemListController::slotAutoActivationTimeout() diff --git a/src/kitemviews/kitemlistcontroller.h b/src/kitemviews/kitemlistcontroller.h index 48a518610..6379acbd8 100644 --- a/src/kitemviews/kitemlistcontroller.h +++ b/src/kitemviews/kitemlistcontroller.h @@ -190,6 +190,13 @@ Q_SIGNALS: void aboveItemDropEvent(int index, QGraphicsSceneDragDropEvent *event); /** + * Emits the keys the user typed for searching so they can be displayed back to the user. + * @param typedString A string of basic interpretation of key presses e.g. "qwert". + * @param foundIndex The index of the item that was marked as current in response to this search. + */ + void typeAheadUsed(const QString &typedString, std::optional<int> foundIndex); + + /** * Is emitted if the Escape key is pressed. */ void escapePressed(); diff --git a/src/statusbar/dolphinstatusbar.cpp b/src/statusbar/dolphinstatusbar.cpp index 5d7e8e674..4b2bb6f0f 100644 --- a/src/statusbar/dolphinstatusbar.cpp +++ b/src/statusbar/dolphinstatusbar.cpp @@ -24,17 +24,24 @@ #include <QProgressBar> #include <QSlider> #include <QStyleOption> +#include <QTextDocumentFragment> #include <QTimer> #include <QToolButton> +#include <chrono> + +using namespace std::chrono_literals; + namespace { -const int UpdateDelay = 50; +constexpr std::chrono::milliseconds minimumTimeBetweenTextChanges = 50ms; +constexpr std::chrono::seconds temporaryRichTextTimeout = 1s; } DolphinStatusBar::DolphinStatusBar(QWidget *parent) : AnimatedHeightWidget(parent) - , m_text() + , m_temporaryRichText() + , m_hoveredItemText() , m_defaultText() , m_label(nullptr) , m_zoomLabel(nullptr) @@ -44,8 +51,8 @@ DolphinStatusBar::DolphinStatusBar(QWidget *parent) , m_stopButton(nullptr) , m_progress(100) , m_showProgressBarTimer(nullptr) - , m_delayUpdateTimer(nullptr) - , m_textTimestamp() + , m_clearTemporaryRichTextTimer(nullptr) + , m_updateLabelTextTimer(nullptr) { setProperty("_breeze_statusbar_separator", true); @@ -53,7 +60,7 @@ DolphinStatusBar::DolphinStatusBar(QWidget *parent) contentsContainer->setContentsMargins(0, 0, 0, 0); // Initialize text label - m_label = new KSqueezedTextLabel(m_text, contentsContainer); + m_label = new KSqueezedTextLabel{contentsContainer}; m_label->setTextFormat(Qt::PlainText); m_label->setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard); // for accessibility but also to allow copy-pasting this text. @@ -101,11 +108,16 @@ DolphinStatusBar::DolphinStatusBar(QWidget *parent) m_showProgressBarTimer->setSingleShot(true); connect(m_showProgressBarTimer, &QTimer::timeout, this, &DolphinStatusBar::updateProgressInfo); - // initialize text updater delay timer - m_delayUpdateTimer = new QTimer(this); - m_delayUpdateTimer->setInterval(UpdateDelay); - m_delayUpdateTimer->setSingleShot(true); - connect(m_delayUpdateTimer, &QTimer::timeout, this, &DolphinStatusBar::updateLabelText); + // initialize timers for delayed replacing which text is shown + m_clearTemporaryRichTextTimer = new QTimer(this); + m_clearTemporaryRichTextTimer->setInterval(temporaryRichTextTimeout); + m_clearTemporaryRichTextTimer->setSingleShot(true); + connect(m_clearTemporaryRichTextTimer, &QTimer::timeout, this, &DolphinStatusBar::clearTemporaryRichText); + + m_updateLabelTextTimer = new QTimer(this); + m_updateLabelTextTimer->setInterval(minimumTimeBetweenTextChanges); + m_updateLabelTextTimer->setSingleShot(true); + connect(m_updateLabelTextTimer, &QTimer::timeout, this, &DolphinStatusBar::updateLabelText); // Initialize top layout and size policies const int fontHeight = QFontMetrics(m_label->font()).height(); @@ -153,24 +165,6 @@ DolphinStatusBar::DolphinStatusBar(QWidget *parent) DolphinStatusBar::~DolphinStatusBar() = default; -void DolphinStatusBar::setText(const QString &text) -{ - if (m_text == text) { - return; - } - - m_textTimestamp = QTime::currentTime(); - - m_text = text; - // will update status bar text in 50ms - m_delayUpdateTimer->start(); -} - -QString DolphinStatusBar::text() const -{ - return m_text; -} - void DolphinStatusBar::showProgress(const QString ¤tlyRunningTaskTitle, int progressPercent, CancelLoading cancelLoading) { m_cancelLoading = cancelLoading; @@ -215,27 +209,28 @@ int DolphinStatusBar::progress() const return m_progress; } -void DolphinStatusBar::resetToDefaultText() +void DolphinStatusBar::setTemporaryRichText(const QString &temporaryRichText) { - m_text.clear(); - - QTime currentTime; - if (currentTime.msecsTo(m_textTimestamp) < UpdateDelay) { - m_delayUpdateTimer->start(); - } else { - updateLabelText(); + if (m_temporaryRichText == temporaryRichText) { + return; } + + m_temporaryRichText = temporaryRichText; + updateLabelText(); // Show the text instantly because we only show it temporarily anyway. + m_clearTemporaryRichTextTimer->start(); } -void DolphinStatusBar::setDefaultText(const QString &text) +void DolphinStatusBar::setHoveredItemText(const QString &hoveredItemText) { - m_defaultText = text; - updateLabelText(); + m_hoveredItemText = hoveredItemText; + m_updateLabelTextTimer->start(); } -QString DolphinStatusBar::defaultText() const +void DolphinStatusBar::setDefaultText(const QString &text) { - return m_defaultText; + m_defaultText = text; + m_hoveredItemText.clear(); // We want to show the new default text instead of whatever was hovered. + m_updateLabelTextTimer->start(); } void DolphinStatusBar::setUrl(const QUrl &url) @@ -280,7 +275,9 @@ void DolphinStatusBar::updateWidthToContent() QStyleOptionSlider opt; opt.initFrom(this); opt.orientation = Qt::Vertical; - const QSize labelSize = QFontMetrics(font()).size(Qt::TextSingleLine, m_label->fullText()); + const QSize labelSize = m_label->textFormat() == Qt::PlainText + ? QFontMetrics(font()).size(Qt::TextSingleLine, m_label->fullText()) + : QFontMetrics(font()).size(Qt::TextSingleLine, QTextDocumentFragment::fromHtml(m_label->fullText()).toPlainText()); // Make sure minimum height takes clipping into account. setMinimumHeight(m_label->height() + clippingAmount()); const int scrollbarWidth = style()->pixelMetric(QStyle::PM_ScrollBarExtent, &opt, this); @@ -389,10 +386,26 @@ void DolphinStatusBar::updateProgressInfo() updateWidthToContent(); } +void DolphinStatusBar::clearTemporaryRichText() +{ + if (m_clearTemporaryRichTextTimer->isActive()) { + return; + } + m_temporaryRichText.clear(); + m_updateLabelTextTimer->start(); +} + void DolphinStatusBar::updateLabelText() { - const QString text = m_text.isEmpty() ? m_defaultText : m_text; - m_label->setText(text); + if (!m_temporaryRichText.isEmpty()) { + m_label->setTextFormat(Qt::RichText); + m_label->setTextElideMode(Qt::ElideNone); + m_label->setText(m_temporaryRichText); + } else { + m_label->setTextFormat(Qt::PlainText); + m_label->setTextElideMode(Qt::ElideMiddle); + m_label->setText(m_hoveredItemText.isEmpty() ? m_defaultText : m_hoveredItemText); + } updateWidthToContent(); } diff --git a/src/statusbar/dolphinstatusbar.h b/src/statusbar/dolphinstatusbar.h index b4ddcd95e..232a4ba07 100644 --- a/src/statusbar/dolphinstatusbar.h +++ b/src/statusbar/dolphinstatusbar.h @@ -11,8 +11,6 @@ #include <KMessageWidget> -#include <QTime> - class QUrl; class StatusBarSpaceInfo; class QLabel; @@ -37,8 +35,6 @@ public: explicit DolphinStatusBar(QWidget *parent); ~DolphinStatusBar() override; - QString text() const; - enum class CancelLoading { Allowed, Disallowed @@ -62,18 +58,21 @@ public: int progress() const; /** - * Replaces the text set by setText() by the text that - * has been set by setDefaultText(). DolphinStatusBar::text() - * will return an empty string after the reset has been done. + * Sets a text that is shown with priority as a Qt::RichText for a short amount of time. */ - void resetToDefaultText(); - + void setTemporaryRichText(const QString &temporaryRichText); + /** + * Sets a text describing the hovered item. This text is immediately shown if no m_temporaryRichText is currently shown. + * When no item is hovered, call this method with an empty string so the m_defaultText is shown. + * @see setTemporaryRichText() + * @see setDefaultText() + */ + void setHoveredItemText(const QString &hoveredItemText); /** - * Sets the default text, which is shown if the status bar - * is rest by DolphinStatusBar::resetToDefaultText(). + * Sets the default text. This text is immediately shown if no m_temporaryRichText is currently shown. + * @see setTemporaryRichText() */ void setDefaultText(const QString &text); - QString defaultText() const; QUrl url() const; int zoomLevel() const; @@ -105,7 +104,6 @@ public: int clippingAmount() const; public Q_SLOTS: - void setText(const QString &text); void setUrl(const QUrl &url); void setZoomLevel(int zoomLevel); @@ -147,6 +145,12 @@ private Q_SLOTS: void updateProgressInfo(); /** + * Replaces the text set by setTemporaryRichText() by the text set by setHoveredItemText() or setDefaultText(). + * Is only called when m_clearTemporaryRichTextTimer times out. + */ + void clearTemporaryRichText(); + + /** * Updates the text for m_label and does an eliding in * case if the text does not fit into the available width. */ @@ -173,7 +177,11 @@ private: int preferredHeight() const override; private: - QString m_text; + /** @see setTemporaryRichText() */ + QString m_temporaryRichText; + /** @see setHoveredItemText() */ + QString m_hoveredItemText; + /** @see setDefaultText() */ QString m_defaultText; KSqueezedTextLabel *m_label; QLabel *m_zoomLabel; @@ -188,8 +196,10 @@ private: int m_progress; QTimer *m_showProgressBarTimer; - QTimer *m_delayUpdateTimer; - QTime m_textTimestamp; + /** Clears the temporary rich text from the status bar and shows a non-temporary text instead. */ + QTimer *m_clearTemporaryRichTextTimer; + /** Very frequent updates to the status bar text look ugly. Most updates go through this timer to avoid this. */ + QTimer *m_updateLabelTextTimer; QHBoxLayout *m_topLayout; }; diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp index 794223861..3c973618c 100644 --- a/src/views/dolphinview.cpp +++ b/src/views/dolphinview.cpp @@ -130,6 +130,25 @@ DolphinView::DolphinView(const QUrl &url, QWidget *parent) KItemListController *controller = new KItemListController(m_model, m_view, this); controller->setAutoActivationEnabled(GeneralSettings::autoExpandFolders()); connect(controller, &KItemListController::doubleClickViewBackground, this, &DolphinView::doubleClickViewBackground); + connect(controller, &KItemListController::typeAheadUsed, this, [this](const QString &typedString, std::optional<int> foundIndex) { + if (foundIndex.has_value()) { + const KFileItem item = m_model->fileItem(foundIndex.value()); + if (item.isNull()) { + return; + } + const KColorScheme colorScheme = KColorScheme(QPalette::Normal, KColorScheme::Tooltip); + const QColor autoCompleteTextColor = colorScheme.foreground(KColorScheme::InactiveText).color(); + + Q_EMIT showTypeAheadFeedback(QStringLiteral("%1<font color=\"%2\">%3</font>") + .arg(typedString.toHtmlEscaped()) + .arg(autoCompleteTextColor.name()) + .arg(item.name().toHtmlEscaped().mid(typedString.size()))); + } else { + const KColorScheme colorScheme = KColorScheme(QPalette::Normal, KColorScheme::Tooltip); + const QColor noMatchTextColor = colorScheme.foreground(KColorScheme::NegativeText).color(); + Q_EMIT showTypeAheadFeedback(QStringLiteral("<font color=\"%1\">%2</font>").arg(noMatchTextColor.name()).arg(typedString.toHtmlEscaped())); + } + }); // The EnlargeSmallPreviews setting can only be changed after the model // has been set in the view by KItemListController. diff --git a/src/views/dolphinview.h b/src/views/dolphinview.h index 6aa5b595d..9b068f92e 100644 --- a/src/views/dolphinview.h +++ b/src/views/dolphinview.h @@ -635,6 +635,11 @@ Q_SIGNALS: void operationCompletedMessage(const QString &msg); /** + * Is emitted so the \a typeAheadFeedback is displayed to the user. Beware: \a typeAheadFeedback is HTML-escaped rich text. + */ + void showTypeAheadFeedback(const QString &typeAheadFeedback); + + /** * Is emitted after DolphinView::setUrl() has been invoked and * the current directory is loaded. If this signal is emitted, * it is assured that the view contains already the correct root |
