From 6554e66c364e1924c7e6894919c4f683eceb0597 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 18 Oct 2021 22:38:15 +0200 Subject: [PATCH 1/8] Search: fix popup highlight --- src/widget/wsearchlineedit.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/widget/wsearchlineedit.cpp b/src/widget/wsearchlineedit.cpp index 82c6486e914..88b88c438fd 100644 --- a/src/widget/wsearchlineedit.cpp +++ b/src/widget/wsearchlineedit.cpp @@ -464,6 +464,14 @@ void WSearchLineEdit::showPopup() { setCurrentIndex(cIndex); } QComboBox::showPopup(); + // When (empty) index -1 is selected in the combobox and the list view is opened + // index 0 is auto-set but not highlighted. + // Select first index manually (only in the list). + if (cIndex == -1) { + view()->selectionModel()->select( + view()->currentIndex(), + QItemSelectionModel::Select); + } } void WSearchLineEdit::updateEditBox(const QString& text) { From ac0c5daf076fbeb5deebab6b70411b32f1709b8e Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 18 Oct 2021 22:40:48 +0200 Subject: [PATCH 2/8] Search: Del or Backspace in popup deletes query --- src/widget/wsearchlineedit.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/widget/wsearchlineedit.cpp b/src/widget/wsearchlineedit.cpp index 88b88c438fd..357014d4b29 100644 --- a/src/widget/wsearchlineedit.cpp +++ b/src/widget/wsearchlineedit.cpp @@ -219,7 +219,8 @@ void WSearchLineEdit::setup(const QDomNode& node, const SkinContext& context) { tr("Use operators like bpm:115-128, artist:BooFar, -year:1990") + "\n" + tr("For more information see User Manual > Mixxx Library") + "\n\n" + - tr("Shortcut") + ": \n" + tr("Ctrl+F") + " " + + tr("Shortcuts") + ": \n" + + tr("Ctrl+F") + " " + tr("Focus", "Give search bar input focus") + "\n" + tr("Ctrl+Backspace") + " " + tr("Clear input", "Clear the search bar input field") + "\n" + @@ -227,6 +228,7 @@ void WSearchLineEdit::setup(const QDomNode& node, const SkinContext& context) { tr("Toggle search history", "Shows/hides the search history entries") + "\n" + + tr("Delete or Backspace") + " " + tr("Delete query from history") + "\n" + tr("Esc") + " " + tr("Exit search", "Exit search bar and leave focus")); } @@ -298,6 +300,18 @@ bool WSearchLineEdit::eventFilter(QObject* obj, QEvent* event) { slotSaveSearch(); } } + } else { + if (keyEvent->key() == Qt::Key_Backspace || + keyEvent->key() == Qt::Key_Delete) { + // remove the highlighted item from the list + QComboBox::removeItem(view()->currentIndex().row()); + // ToDo Resize the list to new item size + // Close the popup if all items were removed + if (count() == 0) { + hidePopup(); + } + return true; + } } if (keyEvent->key() == Qt::Key_Enter) { if (findCurrentTextIndex() == -1) { From d5f0398b6272dbade1d0962f0a610166a576f239 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Tue, 19 Oct 2021 00:47:12 +0200 Subject: [PATCH 3/8] Search: add ControlPushButton to remove selected query --- src/library/librarycontrol.cpp | 13 ++++++++++++ src/library/librarycontrol.h | 1 + src/widget/wsearchlineedit.cpp | 37 ++++++++++++++++++++++++++++------ src/widget/wsearchlineedit.h | 3 +++ 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/library/librarycontrol.cpp b/src/library/librarycontrol.cpp index b548a5f537b..1197c0ae693 100644 --- a/src/library/librarycontrol.cpp +++ b/src/library/librarycontrol.cpp @@ -288,6 +288,19 @@ LibraryControl::LibraryControl(Library* pLibrary) m_pSearchbox->slotClearSearch(); } }); + m_pDeleteSearchQuery = std::make_unique( + ConfigKey("[Library]", "delete_search_query")); + connect(m_pDeleteSearchQuery.get(), + &ControlPushButton::valueChanged, + this, + [this](double value) { + VERIFY_OR_DEBUG_ASSERT(m_pSearchbox) { + return; + } + if (value > 0.0) { + m_pSearchbox->slotDeleteCurrentItem(); + } + }); /// Deprecated controls m_pSelectNextTrack = std::make_unique(ConfigKey("[Playlist]", "SelectNextTrack")); diff --git a/src/library/librarycontrol.h b/src/library/librarycontrol.h index 32204fa4f58..7ab00b3983d 100644 --- a/src/library/librarycontrol.h +++ b/src/library/librarycontrol.h @@ -150,6 +150,7 @@ class LibraryControl : public QObject { std::unique_ptr m_pSelectHistoryPrev; std::unique_ptr m_pSelectHistorySelect; std::unique_ptr m_pClearSearch; + std::unique_ptr m_pDeleteSearchQuery; // Font sizes std::unique_ptr m_pFontSizeIncrement; diff --git a/src/widget/wsearchlineedit.cpp b/src/widget/wsearchlineedit.cpp index 357014d4b29..97db2e52a10 100644 --- a/src/widget/wsearchlineedit.cpp +++ b/src/widget/wsearchlineedit.cpp @@ -304,12 +304,7 @@ bool WSearchLineEdit::eventFilter(QObject* obj, QEvent* event) { if (keyEvent->key() == Qt::Key_Backspace || keyEvent->key() == Qt::Key_Delete) { // remove the highlighted item from the list - QComboBox::removeItem(view()->currentIndex().row()); - // ToDo Resize the list to new item size - // Close the popup if all items were removed - if (count() == 0) { - hidePopup(); - } + deleteSelectedListItem(); return true; } } @@ -457,6 +452,36 @@ void WSearchLineEdit::slotMoveSelectedHistory(int steps) { m_saveTimer.stop(); } +void WSearchLineEdit::slotDeleteCurrentItem() { + if (!isEnabled()) { + return; + } + if (view()->isVisible()) { + deleteSelectedListItem(); + } else { + deleteSelectedComboboxItem(); + } +} + +void WSearchLineEdit::deleteSelectedComboboxItem() { + int cIndex = currentIndex(); + if (cIndex == -1) { + return; + } else { + slotClearSearch(); + QComboBox::removeItem(cIndex); + } +} + +void WSearchLineEdit::deleteSelectedListItem() { + QComboBox::removeItem(view()->currentIndex().row()); + // ToDo Resize the list to new item size + // Close the popup if all items were removed + if (count() == 0) { + hidePopup(); + } +} + void WSearchLineEdit::refreshState() { #if ENABLE_TRACE_LOG kLogger.trace() diff --git a/src/widget/wsearchlineedit.h b/src/widget/wsearchlineedit.h index 1250bc0b591..9c22cb35eb1 100644 --- a/src/widget/wsearchlineedit.h +++ b/src/widget/wsearchlineedit.h @@ -55,6 +55,7 @@ class WSearchLineEdit : public QComboBox, public WBaseWidget { /// entry in the history and executes the search. /// The parameter specifies the distance in steps (positive/negative = downward/upward) void slotMoveSelectedHistory(int steps); + void slotDeleteCurrentItem(); private slots: void slotSetShortcutFocus(); @@ -78,6 +79,8 @@ class WSearchLineEdit : public QComboBox, public WBaseWidget { void updateEditBox(const QString& text); void updateClearButton(const QString& text); void updateStyleMetrics(); + void deleteSelectedComboboxItem(); + void deleteSelectedListItem(); inline int findCurrentTextIndex() { return findData(currentText(), Qt::DisplayRole); From 40f33d621b60938bd7b8e8c3a3dea950947bcaba Mon Sep 17 00:00:00 2001 From: ronso0 Date: Tue, 19 Oct 2021 00:48:43 +0200 Subject: [PATCH 4/8] Search: add isEnabled() tests --- src/widget/wsearchlineedit.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/widget/wsearchlineedit.cpp b/src/widget/wsearchlineedit.cpp index 97db2e52a10..8d8448e55f4 100644 --- a/src/widget/wsearchlineedit.cpp +++ b/src/widget/wsearchlineedit.cpp @@ -442,6 +442,9 @@ void WSearchLineEdit::slotSaveSearch() { } void WSearchLineEdit::slotMoveSelectedHistory(int steps) { + if (!isEnabled()) { + return; + } int nIndex = currentIndex() + steps; // at the top we manually wrap around to the last entry. // at the bottom wrap-around happens automatically due to invalid nIndex. @@ -567,7 +570,9 @@ void WSearchLineEdit::slotClearSearch() { kLogger.trace() << "slotClearSearch"; #endif // ENABLE_TRACE_LOG - DEBUG_ASSERT(isEnabled()); + if (!isEnabled()) { + return; + } // select the last entry as current before cleaning the text field // so arrow keys will work as expected setCurrentIndex(-1); From d3f24fa301ac6fea990e02c165f0a08f2af5c3ad Mon Sep 17 00:00:00 2001 From: ronso0 Date: Tue, 19 Oct 2021 00:49:59 +0200 Subject: [PATCH 5/8] Search: save queries across restarts and when switching skins --- src/skin/legacy/legacyskinparser.cpp | 2 +- src/widget/wsearchlineedit.cpp | 49 +++++++++++++++++++++++++++- src/widget/wsearchlineedit.h | 9 +++-- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/skin/legacy/legacyskinparser.cpp b/src/skin/legacy/legacyskinparser.cpp index e0a0cfa73f7..8668ff3705c 100644 --- a/src/skin/legacy/legacyskinparser.cpp +++ b/src/skin/legacy/legacyskinparser.cpp @@ -1299,7 +1299,7 @@ QWidget* LegacySkinParser::parseSearchBox(const QDomElement& node) { WSearchLineEdit::kDefaultDebouncingTimeoutMillis); WSearchLineEdit::setDebouncingTimeoutMillis(searchDebouncingTimeoutMillis); - WSearchLineEdit* pLineEditSearch = new WSearchLineEdit(m_pParent); + WSearchLineEdit* pLineEditSearch = new WSearchLineEdit(m_pParent, m_pConfig); commonWidgetSetup(node, pLineEditSearch, false); pLineEditSearch->setup(node, *m_pContext); diff --git a/src/widget/wsearchlineedit.cpp b/src/widget/wsearchlineedit.cpp index 8d8448e55f4..0d81e680af0 100644 --- a/src/widget/wsearchlineedit.cpp +++ b/src/widget/wsearchlineedit.cpp @@ -24,6 +24,8 @@ const QColor kDefaultBackgroundColor = QColor(0, 0, 0); const QString kDisabledText = QStringLiteral("- - -"); +const QString kSavedQueriesConfigGroup = QStringLiteral("[SearchQueries]"); + constexpr int kClearButtonClearence = 1; inline QString clearButtonStyleSheet(int pxPadding, Qt::LayoutDirection direction) { @@ -74,9 +76,10 @@ void WSearchLineEdit::setDebouncingTimeoutMillis(int debouncingTimeoutMillis) { s_debouncingTimeoutMillis = verifyDebouncingTimeoutMillis(debouncingTimeoutMillis); } -WSearchLineEdit::WSearchLineEdit(QWidget* pParent) +WSearchLineEdit::WSearchLineEdit(QWidget* pParent, UserSettingsPointer pConfig) : QComboBox(pParent), WBaseWidget(this), + m_pConfig(pConfig), m_clearButton(make_parented(this)) { qRegisterMetaType("FocusWidget"); setAcceptDrops(false); @@ -147,9 +150,15 @@ WSearchLineEdit::WSearchLineEdit(QWidget* pParent) clearButtonSize.width() + m_frameWidth + kClearButtonClearence, layoutDirection())); + loadQueriesFromConfig(); + refreshState(); } +WSearchLineEdit::~WSearchLineEdit() { + saveQueriesInConfig(); +} + void WSearchLineEdit::setup(const QDomNode& node, const SkinContext& context) { auto backgroundColor = kDefaultBackgroundColor; QString bgColorName; @@ -232,6 +241,44 @@ void WSearchLineEdit::setup(const QDomNode& node, const SkinContext& context) { tr("Esc") + " " + tr("Exit search", "Exit search bar and leave focus")); } +void WSearchLineEdit::loadQueriesFromConfig() { + if (!m_pConfig) { + return; + } + const QList queryKeys = + m_pConfig->getKeysWithGroup(kSavedQueriesConfigGroup); + QSet queryStrings; + for (const auto& queryKey : queryKeys) { + const auto& queryString = m_pConfig->getValueString(queryKey); + if (queryString.isEmpty() || queryStrings.contains(queryString)) { + // Don't add duplicate and remove it from the config immediately + m_pConfig->remove(queryKey); + } else { + // Restore query + addItem(queryString); + queryStrings.insert(queryString); + } + } +} + +void WSearchLineEdit::saveQueriesInConfig() { + if (!m_pConfig) { + return; + } + // Delete saved queries in case the list was cleared + const QList queryKeys = + m_pConfig->getKeysWithGroup(kSavedQueriesConfigGroup); + for (const auto& queryKey : queryKeys) { + m_pConfig->remove(queryKey); + } + // Store queries + for (int index = 0; index < count(); index++) { + m_pConfig->setValue( + ConfigKey(kSavedQueriesConfigGroup, QString::number(index)), + itemText(index)); + } +} + void WSearchLineEdit::updateStyleMetrics() { QStyleOptionComboBox styleArrow; styleArrow.initFrom(this); diff --git a/src/widget/wsearchlineedit.h b/src/widget/wsearchlineedit.h index 9c22cb35eb1..800d4a2c437 100644 --- a/src/widget/wsearchlineedit.h +++ b/src/widget/wsearchlineedit.h @@ -7,6 +7,7 @@ #include #include "library/library_decl.h" +#include "preferences/usersettings.h" #include "util/parented_ptr.h" #include "widget/wbasewidget.h" @@ -26,8 +27,8 @@ class WSearchLineEdit : public QComboBox, public WBaseWidget { static void setDebouncingTimeoutMillis(int debouncingTimeoutMillis); virtual void showPopup() override; - explicit WSearchLineEdit(QWidget* pParent); - ~WSearchLineEdit() override = default; + explicit WSearchLineEdit(QWidget* pParent, UserSettingsPointer pConfig = nullptr); + ~WSearchLineEdit(); void setup(const QDomNode& node, const SkinContext& context); @@ -91,6 +92,10 @@ class WSearchLineEdit : public QComboBox, public WBaseWidget { // Update the displayed text without (re-)starting the timer void setTextBlockSignals(const QString& text); + UserSettingsPointer m_pConfig; + void loadQueriesFromConfig(); + void saveQueriesInConfig(); + parented_ptr const m_clearButton; int m_frameWidth; From c3f328ee9fc65ad13a82e1e84512bb3f47e7f3d5 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Tue, 19 Oct 2021 21:50:22 +0200 Subject: [PATCH 6/8] Search: don't store duplicates in slotSaveSearch() --- src/widget/wsearchlineedit.cpp | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/widget/wsearchlineedit.cpp b/src/widget/wsearchlineedit.cpp index 0d81e680af0..a48d411890f 100644 --- a/src/widget/wsearchlineedit.cpp +++ b/src/widget/wsearchlineedit.cpp @@ -464,23 +464,32 @@ void WSearchLineEdit::slotTriggerSearch() { /// saves the current query as selection void WSearchLineEdit::slotSaveSearch() { - int cIndex = findCurrentTextIndex(); #if ENABLE_TRACE_LOG kLogger.trace() << "save search. Index: " << cIndex; #endif // ENABLE_TRACE_LOG m_saveTimer.stop(); - // entry already exists and is on top - if (cIndex == 0) { + if (currentText().isEmpty() || !isEnabled()) { return; } - if (!currentText().isEmpty() && isEnabled()) { - // we remove the existing item and add a new one at the top - if (cIndex != -1) { - removeItem(cIndex); - } - insertItem(0, currentText()); + QString cText = currentText(); + if (currentIndex() == -1) { + removeItem(-1); + } + // Check if the text is already listed + QSet querySet; + for (int index = 0; index < count(); index++) { + querySet.insert(itemText(index)); + } + if (querySet.contains(cText)) { + // If query exists clear the box and use its index to set the currentIndex + int cIndex = findCurrentTextIndex(); + setCurrentIndex(cIndex); + return; + } else { + // Else add it at the top + insertItem(0, cText); setCurrentIndex(0); while (count() > kMaxSearchEntries) { removeItem(kMaxSearchEntries); From c5be3f3e06cd8fc16f4226d7a91e0ada3e1d21df Mon Sep 17 00:00:00 2001 From: ronso0 Date: Wed, 20 Oct 2021 03:03:18 +0200 Subject: [PATCH 7/8] Search: also save search on focus out --- src/widget/wsearchlineedit.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/widget/wsearchlineedit.cpp b/src/widget/wsearchlineedit.cpp index a48d411890f..049256c76bf 100644 --- a/src/widget/wsearchlineedit.cpp +++ b/src/widget/wsearchlineedit.cpp @@ -391,6 +391,7 @@ void WSearchLineEdit::focusOutEvent(QFocusEvent* event) { kLogger.trace() << "focusOutEvent"; #endif // ENABLE_TRACE_LOG + slotSaveSearch(); QComboBox::focusOutEvent(event); emit searchbarFocusChange(FocusWidget::None); if (m_debouncingTimer.isActive()) { From a93b6215d76145772026e5eb2b4f9dd549207dd5 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Wed, 20 Oct 2021 03:33:42 +0200 Subject: [PATCH 8/8] Search: don't update empty combobox when removing list items --- src/widget/wsearchlineedit.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/widget/wsearchlineedit.cpp b/src/widget/wsearchlineedit.cpp index 049256c76bf..377641a021d 100644 --- a/src/widget/wsearchlineedit.cpp +++ b/src/widget/wsearchlineedit.cpp @@ -534,9 +534,16 @@ void WSearchLineEdit::deleteSelectedComboboxItem() { } void WSearchLineEdit::deleteSelectedListItem() { + bool wasEmpty = currentIndex() == -1 ? true : false; QComboBox::removeItem(view()->currentIndex().row()); // ToDo Resize the list to new item size // Close the popup if all items were removed + + // When an item is removed the combobox would pick a sibling and trigger a + // search. Avoid this if the box was empty when the popup was opened. + if (wasEmpty) { + QComboBox::setCurrentIndex(-1); + } if (count() == 0) { hidePopup(); }