diff --git a/src/library/librarycontrol.cpp b/src/library/librarycontrol.cpp index 4b9e49db36a..bb25d82e372 100644 --- a/src/library/librarycontrol.cpp +++ b/src/library/librarycontrol.cpp @@ -828,7 +828,8 @@ FocusWidget LibraryControl::getFocusedWidget() { // WTrackMenuClassWindow = WTrackMenu + submenus // QMenuClassWindow = e.g. sidebar context menu // qt_edit_menuWindow = QLineEdit/QCombobox context menu - // QComboBoxListView of WEffectSelector, WSearchLineEdit, ... + // QComboBoxPrivateContainerClassWindow + // = QComboBoxListView of WEffectSelector, WSearchLineEdit, ... return FocusWidget::ContextMenu; } else if (focusWindow->type() == Qt::Dialog) { // DlgPreferencesDlgWindow diff --git a/src/mixer/basetrackplayer.cpp b/src/mixer/basetrackplayer.cpp index 14598cd521b..d6add741e3b 100644 --- a/src/mixer/basetrackplayer.cpp +++ b/src/mixer/basetrackplayer.cpp @@ -1,6 +1,7 @@ #include "mixer/basetrackplayer.h" #include +#include #include "control/controlobject.h" #include "engine/channels/enginedeck.h" @@ -707,6 +708,37 @@ void BaseTrackPlayerImpl::loadTrackFromGroup(const QString& group) { slotLoadTrack(pTrack, false); } +bool BaseTrackPlayerImpl::isTrackMenuControlAvailable() { + if (m_pShowTrackMenuControl == nullptr) { + // Create the control and return true so LegacySkinParser knows it should + // connect our signal to WTrackProperty. + m_pShowTrackMenuControl = std::make_unique( + ConfigKey(getGroup(), "show_track_menu")); + m_pShowTrackMenuControl->connectValueChangeRequest( + this, + [this](double value) { + emit trackMenuChangeRequest(value > 0); + }); + return true; + } else if (isSignalConnected( + QMetaMethod::fromSignal(&BaseTrackPlayer::trackMenuChangeRequest))) { + // Control exists and we're already connected. + // This means the request was made while creating the 2nd or later WTrackProperty. + return false; + } else { + // Control already exists but signal is not connected, which is the case + // after loading a skin. Return true so LegacySkinParser makes a new connection. + return true; + } +} + +void BaseTrackPlayerImpl::slotSetAndConfirmTrackMenuControl(bool visible) { + VERIFY_OR_DEBUG_ASSERT(m_pShowTrackMenuControl) { + return; + } + m_pShowTrackMenuControl->setAndConfirm(visible ? 1.0 : 0.0); +} + void BaseTrackPlayerImpl::slotSetReplayGain(mixxx::ReplayGain replayGain) { // Do not change replay gain when track is playing because // this may lead to an unexpected volume change. diff --git a/src/mixer/basetrackplayer.h b/src/mixer/basetrackplayer.h index 8a22cd9ea47..5ff8f840298 100644 --- a/src/mixer/basetrackplayer.h +++ b/src/mixer/basetrackplayer.h @@ -37,6 +37,9 @@ class BaseTrackPlayer : public BasePlayer { virtual TrackPointer getLoadedTrack() const = 0; virtual void setupEqControls() = 0; + virtual bool isTrackMenuControlAvailable() { + return false; + }; public slots: virtual void slotLoadTrack(TrackPointer pTrack, bool bPlay = false) = 0; @@ -44,6 +47,7 @@ class BaseTrackPlayer : public BasePlayer { virtual void slotCloneDeck() = 0; virtual void slotEjectTrack(double) = 0; virtual void slotSetTrackRating(int rating) = 0; + virtual void slotSetAndConfirmTrackMenuControl(bool){}; signals: void newTrackLoaded(TrackPointer pLoadedTrack); @@ -52,6 +56,7 @@ class BaseTrackPlayer : public BasePlayer { void playerEmpty(); void noVinylControlInputConfigured(); void trackRatingChanged(int rating); + void trackMenuChangeRequest(bool show); }; class BaseTrackPlayerImpl : public BaseTrackPlayer { @@ -76,7 +81,13 @@ class BaseTrackPlayerImpl : public BaseTrackPlayer { void setupEqControls() final; - // For testing, loads a fake track. + /// Returns true if PushButton has been created and no slot is currently + /// connected to trackMenuChangeRequest(). + /// PushButtons persist skin reload, connected widgets don't, i.e. the + /// connection is removed on skin reload and available again afterwards. + bool isTrackMenuControlAvailable() final; + + /// For testing, loads a fake track. TrackPointer loadFakeTrack(bool bPlay, double filebpm); public slots: @@ -92,6 +103,8 @@ class BaseTrackPlayerImpl : public BaseTrackPlayer { void slotAdjustReplayGain(mixxx::ReplayGain replayGain); void slotSetTrackColor(const mixxx::RgbColor::optional_t& color); void slotSetTrackRating(int rating) final; + /// Called via signal from WTrackProperty. Just set and confirm as requested. + void slotSetAndConfirmTrackMenuControl(bool visible) final; void slotPlayToggled(double); private slots: @@ -164,6 +177,8 @@ class BaseTrackPlayerImpl : public BaseTrackPlayer { std::unique_ptr m_pShiftCuesLaterSmall; std::unique_ptr m_pShiftCues; + std::unique_ptr m_pShowTrackMenuControl; + std::unique_ptr m_pUpdateReplayGainFromPregain; parented_ptr m_pReplayGain; diff --git a/src/skin/legacy/legacyskinparser.cpp b/src/skin/legacy/legacyskinparser.cpp index 424c479a610..c18209b5a1f 100644 --- a/src/skin/legacy/legacyskinparser.cpp +++ b/src/skin/legacy/legacyskinparser.cpp @@ -1088,6 +1088,24 @@ QWidget* LegacySkinParser::parseTrackProperty(const QDomElement& node) { group); setupLabelWidget(node, pTrackProperty); + // Ensure 'show_track_menu' control is created for each main deck and + // valueChangeRequest hook is set up. + // Only the first WTrackProperty that is created connects the signals. + if (PlayerManager::isDeckGroup(group)) { + if (pPlayer->isTrackMenuControlAvailable()) { + connect(pPlayer, + &BaseTrackPlayer::trackMenuChangeRequest, + pTrackProperty, + &WTrackProperty::slotShowTrackMenuChangeRequest, + Qt::DirectConnection); + connect(pTrackProperty, + &WTrackProperty::setAndConfirmTrackMenuControl, + pPlayer, + &BaseTrackPlayer::slotSetAndConfirmTrackMenuControl, + Qt::DirectConnection); + } + } + connect(pPlayer, &BaseTrackPlayer::newTrackLoaded, pTrackProperty, diff --git a/src/track/track_decl.h b/src/track/track_decl.h index 67074d8889d..bcc5d66e783 100644 --- a/src/track/track_decl.h +++ b/src/track/track_decl.h @@ -28,4 +28,7 @@ enum class ExportTrackMetadataResult { Skipped, }; +// key for control to open/close the decks' track menus +const QString kShowTrackMenuKey = QStringLiteral("show_track_menu"); + Q_DECLARE_METATYPE(TrackPointer); diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index 0b51f595ad4..e10fa4b4c7f 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -95,7 +95,15 @@ int WTrackMenu::getTrackCount() const { } } +const QString WTrackMenu::getDeckGroup() const { + return m_deckGroup; +} + void WTrackMenu::closeEvent(QCloseEvent* event) { + // Unfortunately, trackMenuVisible(false) is emitted before the menu is effectively + // closed, which causes issues in WTrackProperty::slotShowTrackMenuChangeRequest. + // Explicitly hide() to avoid this. + hide(); // Actually the event is accepted by default. doing it explicitly doesn't hurt. // If it's not accepted the menu remains open and entire GUI will be blocked! event->accept(); diff --git a/src/widget/wtrackmenu.h b/src/widget/wtrackmenu.h index c7ecc79f5f5..5d5ff6b7cdb 100644 --- a/src/widget/wtrackmenu.h +++ b/src/widget/wtrackmenu.h @@ -87,6 +87,7 @@ class WTrackMenu : public QMenu { void showDlgTrackInfo(const QString& property = QString()); // Library management void slotRemoveFromDisk(); + const QString getDeckGroup() const; signals: void loadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play = false); diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index babc4a61c70..b290b4a252c 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -1,7 +1,10 @@ #include "widget/wtrackproperty.h" +#include #include +#include +#include "control/controlpushbutton.h" #include "moc_wtrackproperty.cpp" #include "skin/legacy/skincontext.h" #include "track/track.h" @@ -129,7 +132,7 @@ void WTrackProperty::contextMenuEvent(QContextMenuEvent* event) { if (m_pCurrentTrack) { ensureTrackMenuIsCreated(); m_pTrackMenu->loadTrack(m_pCurrentTrack, m_group); - // Create the right-click menu + // Show the right-click menu m_pTrackMenu->popup(event->globalPos()); } } @@ -138,5 +141,71 @@ void WTrackProperty::ensureTrackMenuIsCreated() { if (m_pTrackMenu.get() == nullptr) { m_pTrackMenu = make_parented( this, m_pConfig, m_pLibrary, kTrackMenuFeatures); + + // When a track menu for this deck is shown/hidden via contextMenuEvent + // or pushbutton, it emits trackMenuVisible(bool). + // The pushbutton is created in BaseTrackPlayer which, on value change requests, + // also emits a signal which is connected to our slotShowTrackMenuChangeRequest(). + connect(m_pTrackMenu, + &WTrackMenu::trackMenuVisible, + this, + [this](bool visible) { + ControlObject::set(ConfigKey(m_group, kShowTrackMenuKey), + visible ? 1.0 : 0.0); + }); + } +} + +/// This slot handles show/hide requests originating from both pushbutton changes +/// and WTrackMenu's show/hide signals. +/// If the request matches the menu state we only set the control value accordingly. +/// Otherwise, we show/hide the menu as requested. This will result in another +/// change request originating from the menu, then it's a match and we setAndConfirm() +void WTrackProperty::slotShowTrackMenuChangeRequest(bool show) { + // Ignore no-op + if ((ControlObject::get(ConfigKey(m_group, kShowTrackMenuKey)) > 0) == show) { + return; + } + + // Check for any open track menu. + // If this is a show request, hide all other menus (decks and library). + // Assumes there can only be one visible menu per deck + bool confirmShow = false; + const QWidgetList topLevelWidgets = QApplication::topLevelWidgets(); + for (QWidget* pWidget : topLevelWidgets) { + // Ignore other popups and hidden track menus + WTrackMenu* pTrackMenu = qobject_cast(pWidget); + if (pTrackMenu && pTrackMenu->isVisible()) { + if (show) { + if (pTrackMenu->getDeckGroup() == m_group) { + // Don't return, yet, maybe we still need to hide other menus. + confirmShow = true; + } else { + // Hide other menus + pTrackMenu->close(); + } + } else if (pTrackMenu->getDeckGroup() == m_group) { + // Hide this deck's menu, ignore other menus + pTrackMenu->close(); + return; + } + } + } + + // If we reach this, this is either a hide request but no menu was found for + // this deck, or this is a show request and we've found an open menu. + if (!show || confirmShow) { + emit setAndConfirmTrackMenuControl(show); + return; } + + // This is a show request and there was no open menu found for this deck. + // Pop up menu as if we right-clicked at the center of this widget + // Note: this widget may be hidden so the position may be unexpected, + // though this is okay as long as all variants of deckN are on the same + // side of the mixer. + QContextMenuEvent event(QContextMenuEvent::Mouse, + QPoint(), + mapToGlobal(rect().center())); + contextMenuEvent(&event); } diff --git a/src/widget/wtrackproperty.h b/src/widget/wtrackproperty.h index da0a1de6e80..286a2d539d7 100644 --- a/src/widget/wtrackproperty.h +++ b/src/widget/wtrackproperty.h @@ -7,6 +7,7 @@ #include "widget/trackdroptarget.h" #include "widget/wlabel.h" +class ControlPushButton; class Library; class WTrackMenu; @@ -25,10 +26,12 @@ class WTrackProperty : public WLabel, public TrackDropTarget { signals: void trackDropped(const QString& filename, const QString& group) override; void cloneDeck(const QString& sourceGroup, const QString& targetGroup) override; + void setAndConfirmTrackMenuControl(bool visible); public slots: void slotTrackLoaded(TrackPointer pTrack); void slotLoadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack); + void slotShowTrackMenuChangeRequest(bool show); protected: void contextMenuEvent(QContextMenuEvent* event) override; @@ -37,7 +40,8 @@ class WTrackProperty : public WLabel, public TrackDropTarget { void mouseMoveEvent(QMouseEvent* event) override; void mouseDoubleClickEvent(QMouseEvent* event) override; - virtual void updateLabel(); + private: + void updateLabel(); void ensureTrackMenuIsCreated(); diff --git a/src/widget/wtrackwidgetgroup.cpp b/src/widget/wtrackwidgetgroup.cpp index 7887ba1667b..9ef83de9fad 100644 --- a/src/widget/wtrackwidgetgroup.cpp +++ b/src/widget/wtrackwidgetgroup.cpp @@ -2,6 +2,7 @@ #include +#include "control/controlobject.h" #include "moc_wtrackwidgetgroup.cpp" #include "skin/legacy/skincontext.h" #include "track/track.h" @@ -133,5 +134,14 @@ void WTrackWidgetGroup::ensureTrackMenuIsCreated() { if (m_pTrackMenu.get() == nullptr) { m_pTrackMenu = make_parented( this, m_pConfig, m_pLibrary, kTrackMenuFeatures); + + // See WTrackProperty for info + connect(m_pTrackMenu, + &WTrackMenu::trackMenuVisible, + this, + [this](bool visible) { + ControlObject::set(ConfigKey(m_group, kShowTrackMenuKey), + visible ? 1.0 : 0.0); + }); } }