diff --git a/src/util/reverse_iterable.h b/src/util/reverse_iterable.h new file mode 100644 index 00000000000..51db4e27408 --- /dev/null +++ b/src/util/reverse_iterable.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +// Reversed iterable to for use in range-for loops. +template +struct reverse_iterable { + T& iterable; +}; + +template +auto begin(reverse_iterable w) { + return std::rbegin(w.iterable); +} + +template +auto end(reverse_iterable w) { + return std::rend(w.iterable); +} + +template +reverse_iterable make_reverse_iterable(T&& iterable) { + return {iterable}; +} diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index 402f9024b86..346a33b86f6 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -3,10 +3,12 @@ #include #include #include +#include #include #include #include #include +#include #include "analyzer/analyzerscheduledtrack.h" #include "analyzer/analyzersilence.h" @@ -48,6 +50,7 @@ #include "widget/wfindonwebmenu.h" #include "widget/wsearchrelatedtracksmenu.h" // WStarRating is required for DlgTrackInfo +#include "util/reverse_iterable.h" #include "widget/wstarrating.h" #include "widget/wstarratingaction.h" @@ -378,6 +381,18 @@ void WTrackMenu::createActions() { } if (featureIsEnabled(Feature::Crate)) { + auto placeholderText = tr("Start typing to filter crates..."); + m_pFilterCratesEdit = new QLineEdit(this); + m_pFilterCratesEdit->setPlaceholderText(placeholderText); + m_pFilterCratesEdit->setToolTip(placeholderText); + connect(m_pFilterCratesEdit, + &QLineEdit::textChanged, + this, + &WTrackMenu::slotCrateFilterTextChanged); + + m_pFilterCratesAct = new QWidgetAction(this); + m_pFilterCratesAct->setDefaultWidget(m_pFilterCratesEdit); + m_pAddToNewCrateAct = new QAction(tr("Add to New Crate"), this); connect(m_pAddToNewCrateAct, &QAction::triggered, @@ -1486,6 +1501,7 @@ void WTrackMenu::slotPopulateCrateMenu() { return; } m_pCrateMenu->clear(); + m_pCrateMenu->addAction(m_pFilterCratesAct); const TrackIdList trackIds = getTrackIds(); CrateSummarySelectResult allCrates( @@ -1555,6 +1571,33 @@ void WTrackMenu::slotPopulateCrateMenu() { m_bCrateMenuLoaded = true; } +void WTrackMenu::slotCrateFilterTextChanged(const QString& newText) { + // Iterate in reverse order so that we can hide sections + // that do not contain any visible items. + bool sectionHasVisibleItems = false; + for (QAction* pAction : make_reverse_iterable(m_pCrateMenu->actions())) { + auto crateId = pAction->property("crateId").value(); + auto folderId = pAction->property("folderId").value(); + + if (crateId.isValid()) { + // pAction has a valid crateId property => This actions represents a crate checkbox + // Visibility depends on whether it matches the specified search text. + // TODO(cr7pt0gr4ph7): Also match against the folder path? + // TODO(cr7pt0gr4ph7): Check whether we use the correct text comparison mode + bool isVisible = newText.isEmpty() || pAction->text().contains(newText); + pAction->setVisible(isVisible); + sectionHasVisibleItems |= isVisible; + } else if (folderId.isValid()) { + // pAction has a valid folderId => This action represents a folder section + // Visibility depends on the visibility of the crates contained in this folder. + pAction->setVisible(sectionHasVisibleItems); + sectionHasVisibleItems = false; + } else { + // Other actions, just ignore these. + } + } +} + void WTrackMenu::updateSelectionCrates(QWidget* pWidget) { auto* pCheckBox = qobject_cast(pWidget); VERIFY_OR_DEBUG_ASSERT(pCheckBox) { diff --git a/src/widget/wtrackmenu.h b/src/widget/wtrackmenu.h index dddf0d05edb..90b04063695 100644 --- a/src/widget/wtrackmenu.h +++ b/src/widget/wtrackmenu.h @@ -22,6 +22,8 @@ class DlgTrackInfoMulti; //class DlgDeleteFilesConfirmation; class ExternalTrackCollection; class Library; +class QLineEdit; +class QWidgetAction; class TrackModel; class WColorPickerAction; class WCoverArtMenu; @@ -163,6 +165,7 @@ class WTrackMenu : public QMenu { // Playlist and crate void slotPopulatePlaylistMenu(); void slotPopulateCrateMenu(); + void slotCrateFilterTextChanged(const QString& newText); void addSelectionToNewCrate(); // Auto DJ @@ -270,6 +273,8 @@ class WTrackMenu : public QMenu { #endif // Crate Submenu Actions + QWidgetAction* m_pFilterCratesAct{}; + QLineEdit* m_pFilterCratesEdit{}; QAction* m_pAddToNewCrateAct{}; // Update ReplayGain from Track