diff --git a/CMakeLists.txt b/CMakeLists.txt index e26d77ce9f1..7c7cee24f47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -722,6 +722,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/widget/wstarrating.cpp src/widget/wstatuslight.cpp src/widget/wtime.cpp + src/widget/wtrackmenu.cpp src/widget/wtrackproperty.cpp src/widget/wtracktableview.cpp src/widget/wtracktableviewheader.cpp diff --git a/build/depends.py b/build/depends.py index 0d41ee7da9e..bad5ca59e9a 100644 --- a/build/depends.py +++ b/build/depends.py @@ -1023,6 +1023,7 @@ def sources(self, build): "src/widget/wlibrarytableview.cpp", "src/widget/wanalysislibrarytableview.cpp", "src/widget/wlibrarytextbrowser.cpp", + "src/widget/wtrackmenu.cpp", "src/database/mixxxdb.cpp", "src/database/schemamanager.cpp", diff --git a/res/skins/Deere/style.qss b/res/skins/Deere/style.qss index d4dff089bf0..2aea423158e 100644 --- a/res/skins/Deere/style.qss +++ b/res/skins/Deere/style.qss @@ -1831,7 +1831,8 @@ QToolTip, WBeatSpinBox QMenu, WCueMenuPopup, WCueMenuPopup QMenu, -WCoverArtMenu { +WCoverArtMenu, +WTrackMenu QMenu { padding: 2px; } WEffectSelector QAbstractScrollArea, @@ -1856,10 +1857,16 @@ WCueMenuPopup QMenu, WCueMenuPopup QLabel, WCueMenuPopup QMenu::item, WCoverArtMenu, -WCoverArtMenu::item { - color: #c1cabe; - background-color: #201f1f; - } +WCoverArtMenu::item, +WTrackMenu, +WTrackMenu::item, +WTrackMenu QCheckBox, +WTrackMenu QMenu, +WTrackMenu QMenu::item, +WTrackMenu QMenu QCheckBox { + color: #c1cabe; + background-color: #201f1f; +} WEffectSelector QAbstractScrollArea, #fadeModeCombobox QAbstractScrollArea, QToolTip, @@ -1867,19 +1874,29 @@ QToolTip, WBeatSpinBox QMenu, WCueMenuPopup, WCueMenuPopup QMenu, -WCoverArtMenu { +WCoverArtMenu, +WTrackMenu, +WTrackMenu QMenu { border-width: 1px; border-style: solid; border-color: #aaa; border-radius: 1px; - } +} /* hovered items */ WEffectSelector::item:selected, #fadeModeCombobox::item:selected, - #LibraryContainer QMenu::item:selected, WBeatSpinBox QMenu::item:selected, WCueMenuPopup QMenu::item:selected, WCoverArtMenu::item:selected, + WTrackMenu::item:selected, + WTrackMenu QCheckBox:selected, + WTrackMenu QCheckBox:focus, + WTrackMenu QCheckBox:hover, + WTrackMenu QMenu::item:selected, + WTrackMenu QMenu QCheckBox:selected, + WTrackMenu QMenu QCheckBox:focus, + WTrackMenu QMenu QCheckBox:hover, + #LibraryContainer QMenu::item:selected, #LibraryContainer QMenu QCheckBox:selected, #LibraryContainer QMenu QCheckBox:focus, /* selected by keyboard */ #LibraryContainer QMenu QCheckBox:hover /* mouse hover */ { @@ -1959,7 +1976,10 @@ WEffectSelector { WBeatSpinBox QMenu::item, WCueMenuPopup QMenu::item, WCoverArtMenu::item, - #LibraryContainer QMenu QCheckBox { + WTrackMenu::item, + WTrackMenu QCheckBox, + WTrackMenu QMenu::item, + WTrackMenu QMenu QCheckBox { padding: 0px; margin: 0px; image: none; @@ -1969,7 +1989,9 @@ WEffectSelector { #LibraryContainer QMenu::separator, WBeatSpinBox QMenu::separator, WCueMenuPopup QMenu::separator, - WCoverArtMenu::separator { + WCoverArtMenu::separator, + WTrackMenu::separator, + WTrackMenu QMenu::separator { height: 0px; border-top: 1px solid #0a0a0a; margin: 4px; @@ -1977,7 +1999,9 @@ WEffectSelector { #LibraryContainer QMenu::item, WBeatSpinBox QMenu::item, WCueMenuPopup QMenu::item, - WCoverArtMenu::item { + WCoverArtMenu::item, + WTrackMenu::item, + WTrackMenu QMenu::item { /* right padding creates a margin to the menu expand arrow, left padding should be bigger than menu icon width + menu icon margin */ @@ -1985,20 +2009,32 @@ WEffectSelector { } /* icons in editline menu (searchbox, editable track properties) */ #LibraryContainer QMenu::icon, + #LibraryContainer QMenu QCheckBox::indicator, + #LibraryContainer QMenu::indicator, WBeatSpinBox QMenu::icon, WCueMenuPopup QMenu::icon, WCoverArtMenu::icon, - #LibraryContainer QMenu QCheckBox::indicator, - #LibraryContainer QMenu::indicator, + WTrackMenu::icon, + WTrackMenu QCheckBox::indicator, + WTrackMenu::indicator, + WTrackMenu QMenu::icon, + WTrackMenu QMenu QCheckBox::indicator, + WTrackMenu QMenu::indicator, WCoverArtMenu::indicator { margin: 0px 4px 0px 2px; padding: 1px; } - #LibraryContainer QMenu QCheckBox { + #LibraryContainer QMenu QCheckBox, + WTrackMenu QCheckBox, + WTrackMenu QMenu QCheckBox { padding: 2px 10px 2px 3px; } #LibraryContainer QMenu QCheckBox::indicator, - #LibraryContainer QMenu::indicator { + #LibraryContainer QMenu::indicator, + WTrackMenu QCheckBox::indicator, + WTrackMenu::indicator, + WTrackMenu QMenu QCheckBox::indicator, + WTrackMenu QMenu::indicator { width: 13px; height: 13px; border: 1px solid #555; @@ -2008,11 +2044,19 @@ WEffectSelector { outline: none; } #LibraryContainer QMenu QCheckBox::indicator:selected, - #LibraryContainer QMenu::indicator:selected { + #LibraryContainer QMenu::indicator:selected, + WTrackMenu QCheckBox::indicator:selected, + WTrackMenu::indicator:selected, + WTrackMenu QMenu QCheckBox::indicator:selected, + WTrackMenu QMenu::indicator:selected { border: 1px solid #999; } #LibraryContainer QMenu QCheckBox::indicator:checked, - #LibraryContainer QMenu::indicator:checked { + #LibraryContainer QMenu::indicator:checked, + WTrackMenu QCheckBox::indicator:checked, + WTrackMenu::indicator:checked, + WTrackMenu QMenu QCheckBox::indicator:checked, + WTrackMenu QMenu::indicator:checked { image: url(skin:/icon/ic_library_checkmark.svg); } /* disabled menu item and checkbox */ @@ -2020,20 +2064,34 @@ WEffectSelector { WBeatSpinBox QMenu::item:!enabled, WCueMenuPopup QMenu::item:!enabled, WCoverArtMenu::item:!enabled, + WTrackMenu::item:!enabled, + WTrackMenu QCheckBox:!enabled, + WTrackMenu QMenu::item:!enabled, + WTrackMenu QMenu QCheckBox:!enabled, #LibraryContainer QMenu QCheckBox:!enabled { color: #555; } - #LibraryContainer QMenu QCheckBox::indicator:!enabled { + #LibraryContainer QMenu QCheckBox::indicator:!enabled, + WTrackMenu QCheckBox::indicator:!enabled, + WTrackMenu QMenu QCheckBox::indicator:!enabled { border: 1px solid #222; background-color: #333; } #LibraryContainer QMenu QCheckBox::indicator:!enabled:checked, #LibraryContainer QMenu QCheckBox::indicator:indeterminate, - #LibraryContainer QCheckBox::indicator:indeterminate:!enabled { + #LibraryContainer QCheckBox::indicator:indeterminate:!enabled, + WTrackMenu QCheckBox::indicator:!enabled:checked, + WTrackMenu QCheckBox::indicator:indeterminate, + WTrackMenu QCheckBox::indicator:indeterminate:!enabled, + WTrackMenu QMenu QCheckBox::indicator:!enabled:checked, + WTrackMenu QMenu QCheckBox::indicator:indeterminate, + WTrackMenu QMenu QCheckBox::indicator:indeterminate:!enabled { image: url(skin:/icon/ic_library_checkmark_grey.svg); } - #LibraryContainer QMenu::right-arrow { + #LibraryContainer QMenu::right-arrow, + WTrackMenu::right-arrow, + WTrackMenu QMenu::right-arrow { width: 16px; height: 16px; image: url(skin:/icon/ic_chevron_right_48px.svg); @@ -2042,7 +2100,15 @@ WEffectSelector { #LibraryContainer QHeaderView QMenu::indicator:checked, #LibraryContainer QHeaderView QMenu::indicator:checked:hover, #LibraryContainer QHeaderView QMenu::indicator:!checked, - #LibraryContainer QHeaderView QMenu::indicator:!checked:hover { + #LibraryContainer QHeaderView QMenu::indicator:!checked:hover, + WTrackMenu::indicator:checked, + WTrackMenu::indicator:checked:hover, + WTrackMenu::indicator:!checked, + WTrackMenu::indicator:!checked:hover, + WTrackMenu QMenu::indicator:checked, + WTrackMenu QMenu::indicator:checked:hover, + WTrackMenu QMenu::indicator:!checked, + WTrackMenu QMenu::indicator:!checked:hover { width: 12px; height: 10px; margin-left: 2px; diff --git a/res/skins/LateNight/style.qss b/res/skins/LateNight/style.qss index 12eae2b8da2..9f768293225 100644 --- a/res/skins/LateNight/style.qss +++ b/res/skins/LateNight/style.qss @@ -21,6 +21,8 @@ QToolTip, WCueMenuPopup, WCueMenuPopup QMenu, WCoverArtMenu, +WTrackMenu, +WTrackMenu QMenu, WOverview /* Hotcue labels in the overview */ { font-family: "Open Sans"; text-transform: uppercase; @@ -35,6 +37,8 @@ QToolTip, WCueMenuPopup, WCueMenuPopup QMenu, WCoverArtMenu, +WTrackMenu, +WTrackMenu QMenu, WBeatSpinBox QMenu { font-weight: normal; } @@ -50,6 +54,10 @@ WCueMenuPopup, WCueMenuPopup QMenu, WCoverArtMenu, WBeatSpinBox QMenu, +WTrackMenu, +WTrackMenu QCheckBox, +WTrackMenu QMenu, +WTrackMenu QMenu QCheckBox, WLibrary QHeaderView { text-transform: none; } @@ -224,6 +232,10 @@ WCueMenuPopup QMenu, WCueMenuPopup QLabel, #CueLabelEdit, WCoverArtMenu, +WTrackMenu, +WTrackMenu QCheckBox, +WTrackMenu QMenu, +WTrackMenu QMenu QCheckBox, #LatencyLabel, WTime { color: #f0bb2b; } @@ -2446,6 +2458,8 @@ WBeatSpinBox QMenu, WCueMenuPopup, WCueMenuPopup QMenu, WCoverArtMenu, +WTrackMenu, +WTrackMenu QMenu, #SkinSettings { padding: 3px; border: 1px solid #888; @@ -2465,12 +2479,18 @@ WBeatSpinBox QMenu, #LibraryContainer QMenu, WCueMenuPopup QMenu, WCoverArtMenu, + WTrackMenu, + WTrackMenu QMenu, WBeatSpinBox QMenu::item, #LibraryContainer QMenu::item, WCueMenuPopup QMenu::item, WCueMenuPopup QLabel, WCoverArtMenu::item, + WTrackMenu::item, + WTrackMenu QMenu::item, #LibraryContainer QMenu QCheckBox, +WTrackMenu QCheckBox, +WTrackMenu QMenu QCheckBox, WBeatSpinBox, #spinBoxTransition, #SkinSettings, @@ -2496,9 +2516,17 @@ WEffectSelector, WEffectSelector QAbstractScrollArea, #LibraryContainer QMenu::item:selected, WCueMenuPopup QMenu::item:selected, WCoverArtMenu::item:selected, + WTrackMenu::item:selected, + WTrackMenu QMenu::item:selected, #LibraryContainer QMenu QCheckBox:selected, #LibraryContainer QMenu QCheckBox:focus, /* selected by keyboard */ #LibraryContainer QMenu QCheckBox:hover, /* mouse hover */ + WTrackMenu QCheckBox:selected, + WTrackMenu QCheckBox:focus, + WTrackMenu QCheckBox:hover, + WTrackMenu QMenu QCheckBox:selected, + WTrackMenu QMenu QCheckBox:focus, + WTrackMenu QMenu QCheckBox:hover, #SkinSettingsButton:hover, #SkinSettingsLabelButton:hover { background-color: #5E4507; @@ -2569,7 +2597,11 @@ WEffectSelector, #LibraryContainer QMenu::item, WCueMenuPopup QMenu::item, WCoverArtMenu::item, - #LibraryContainer QMenu QCheckBox { + WTrackMenu::item, + WTrackMenu QMenu::item, + #LibraryContainer QMenu QCheckBox, + WTrackMenu QCheckBox, + WTrackMenu QMenu QCheckBox { padding: 0px; margin: 0px; image: none; @@ -2579,13 +2611,17 @@ WEffectSelector, WBeatSpinBox QMenu::separator, #LibraryContainer QMenu::separator, WCueMenuPopup QMenu::separator, + WTrackMenu::separator, + WTrackMenu QMenu::separator, #SkinSettingsSeparator { border-top: 1px solid #000; border-bottom: 1px solid #222; } WBeatSpinBox QMenu::separator, #LibraryContainer QMenu::separator, - WCueMenuPopup QMenu::separator { + WCueMenuPopup QMenu::separator, + WTrackMenu::separator, + WTrackMenu QMenu::separator { height: 0px; margin: 4px; } @@ -2595,7 +2631,9 @@ WEffectSelector, WBeatSpinBox QMenu::item, #LibraryContainer QMenu::item, WCueMenuPopup QMenu::item, - WCoverArtMenu::item { + WCoverArtMenu::item, + WTrackMenu::item, + WTrackMenu QMenu::item { /* Right padding creates a margin to the menu expand arrow. Left padding should be bigger than menu icon width + menu icon left/right margin */ @@ -2606,18 +2644,28 @@ WEffectSelector, WBeatSpinBox QMenu::icon, #LibraryContainer QMenu::icon, WCueMenuPopup QMenu::icon, + WTrackMenu::icon, + WTrackMenu QMenu::icon, /* checkbox in Crate name context menu: "[ ] Auto DJ Track Source" */ - #LibraryContainer QMenu::indicator { + #LibraryContainer QMenu::indicator, + WTrackMenu::indicator, + WTrackMenu QMenu::indicator { margin: 0px 4px 0px 5px; } /* items in Crate sub menu */ - #LibraryContainer QMenu QCheckBox { + #LibraryContainer QMenu QCheckBox, + WTrackMenu QCheckBox, + WTrackMenu QMenu QCheckBox { padding: 3px 10px 3px 5px; } #LibraryContainer QMenu QCheckBox::indicator, #LibraryContainer QMenu::indicator, - WCueMenuPopup QMenu::indicator { + WCueMenuPopup QMenu::indicator, + WTrackMenu QCheckBox::indicator, + WTrackMenu::indicator, + WTrackMenu QMenu QCheckBox::indicator, + WTrackMenu QMenu::indicator { width: 13px; height: 13px; border: 1px solid #333; @@ -2628,39 +2676,61 @@ WEffectSelector, } #LibraryContainer QMenu QCheckBox::indicator:checked, #LibraryContainer QMenu::indicator:checked, + WTrackMenu QCheckBox::indicator:checked, + WTrackMenu::indicator:checked, + WTrackMenu QMenu QCheckBox::indicator:checked, + WTrackMenu QMenu::indicator:checked, WCueMenuPopup QMenu::indicator:checked { image: url(skin:/buttons_classic/btn__lib_checkmark_orange.svg); } /* disabled menu item and checkbox */ #LibraryContainer QMenu QCheckBox:!enabled, #LibraryContainer QMenu::item:!enabled, + WTrackMenu QCheckBox:!enabled, + WTrackMenu::item:!enabled, + WTrackMenu QMenu QCheckBox:!enabled, + WTrackMenu QMenu::item:!enabled, WCueMenuPopup QMenu::item:!enabled, WCoverArtMenu::item:!enabled, - #LibraryContainer QMenu QCheckBox::indicator:!enabled { + #LibraryContainer QMenu QCheckBox::indicator:!enabled, + WTrackMenu QCheckBox::indicator:!enabled, + WTrackMenu QMenu QCheckBox::indicator:!enabled { color: #494949; } #LibraryContainer QMenu QCheckBox::indicator:!enabled:!checked, #LibraryContainer QMenu::indicator:!enabled:!checked, + WTrackMenu QCheckBox::indicator:!enabled:!checked, + WTrackMenu::indicator:!enabled:!checked, + WTrackMenu QMenu QCheckBox::indicator:!enabled:!checked, + WTrackMenu QMenu::indicator:!enabled:!checked, WCueMenuPopup QMenu::indicator:!enabled:!checked { border: 1px solid #222; background-color: #222; } - #LibraryContainer QMenu QCheckBox::indicator:!enabled:checked { + #LibraryContainer QMenu QCheckBox::indicator:!enabled:checked, + WTrackMenu QCheckBox::indicator:!enabled:checked, + WTrackMenu QMenu QCheckBox::indicator:!enabled:checked { image: url(skin:/buttons_classic/btn__lib_checkmark_grey.svg); border: 1px solid #222; background-color: #222; } #LibraryContainer QMenu QCheckBox::indicator:indeterminate, - #LibraryContainer QCheckBox::indicator:indeterminate:!enabled { + #LibraryContainer QCheckBox::indicator:indeterminate:!enabled, + WTrackMenu QCheckBox::indicator:indeterminate, + WTrackMenu QMenu QCheckBox::indicator:indeterminate { image: url(skin:/buttons_classic/btn__lib_checkmark_grey.svg); } - #LibraryContainer QMenu::right-arrow { + #LibraryContainer QMenu::right-arrow, + WTrackMenu::right-arrow, + WTrackMenu QMenu::right-arrow { width: 10px; height: 10px; image: url(skin:/style_classic/menu_arrow_yellow.svg); } - #LibraryContainer QMenu::right-arrow:selected { + #LibraryContainer QMenu::right-arrow:selected, + WTrackMenu::right-arrow:selected, + WTrackMenu QMenu::right-arrow:selected { image: url(skin:/style_classic/menu_arrow_white.svg); } diff --git a/res/skins/LateNight/style_flip.qss b/res/skins/LateNight/style_flip.qss index 302d155d9b5..c64ac9df9f2 100644 --- a/res/skins/LateNight/style_flip.qss +++ b/res/skins/LateNight/style_flip.qss @@ -47,6 +47,10 @@ QToolTip, WBeatSpinBox QMenu, #LibraryContainer QMenu, #LibraryContainer QMenu QCheckBox, +WTrackMenu, +WTrackMenu QCheckBox, +WTrackMenu QMenu, +WTrackMenu QMenu QCheckBox, WCoverArtMenu, WCueMenuPopup, WCueMenuPopup QMenu, @@ -95,6 +99,14 @@ WSearchLineEdit, WBeatSpinBox QMenu::item:selected, #LibraryContainer QMenu::item:selected, WCoverArtMenu::item:selected, + WTrackMenu::item:selected, + WTrackMenu QMenu::item:selected, + WTrackMenu QCheckBox:selected, + WTrackMenu QCheckBox:focus, + WTrackMenu QCheckBox:hover, + WTrackMenu QMenu QCheckBox:selected, + WTrackMenu QMenu QCheckBox:focus, + WTrackMenu QMenu QCheckBox:hover, #LibraryContainer QMenu QCheckBox:selected, #LibraryContainer QMenu QCheckBox:focus, /* selected by keyboard */ #LibraryContainer QMenu QCheckBox:hover /* mouse hover */ { @@ -108,10 +120,14 @@ WSearchLineEdit, border: 1px solid #c88500; background-color: #050504; } - #LibraryContainer QMenu::right-arrow { + #LibraryContainer QMenu::right-arrow, + WTrackMenu::right-arrow, + WTrackMenu QMenu::right-arrow { image: url(skin:/style_classic/menu_arrow_gold.svg); } - #LibraryContainer QMenu::right-arrow:selected { + #LibraryContainer QMenu::right-arrow:selected, + WTrackMenu::right-arrow:selected, + WTrackMenu QMenu::right-arrow:selected { image: url(skin:/style_classic/menu_arrow_black.svg); } /* BPM lock icon */ diff --git a/res/skins/Shade/style.qss b/res/skins/Shade/style.qss index 623624f6b90..0b77aeb12b5 100644 --- a/res/skins/Shade/style.qss +++ b/res/skins/Shade/style.qss @@ -78,6 +78,12 @@ WBeatSpinBox QMenu, #LibraryContainer QMenu, #LibraryContainer QMenu::item, #LibraryContainer QMenu QCheckBox, +WTrackMenu, +WTrackMenu QMenu, +WTrackMenu::item, +WTrackMenu QMenu::item, +WTrackMenu QCheckBox, +WTrackMenu QMenu QCheckBox, WCueMenuPopup, WCueMenuPopup QMenu, WCueMenuPopup QMenu::item, @@ -124,6 +130,8 @@ WEffectSelector::down-arrow, QToolTip, WBeatSpinBox QMenu, #LibraryContainer QMenu, +WTrackMenu, +WTrackMenu QMenu, WCueMenuPopup, WCueMenuPopup QMenu, WCoverArtMenu { @@ -167,9 +175,13 @@ WCoverArtMenu { WEffectSelector::indicator:!checked, WBeatSpinBox QMenu::item, #LibraryContainer QMenu::item, + WTrackMenu::item, + WTrackMenu QMenu::item, WCueMenuPopup QMenu::item, WCoverArtMenu::item, - #LibraryContainer QMenu QCheckBox { + #LibraryContainer QMenu QCheckBox, + WTrackMenu QCheckBox, + WTrackMenu QMenu QCheckBox { padding: 0px; margin: 0px; image: none; @@ -190,9 +202,17 @@ WCoverArtMenu { #LibraryContainer QMenu::item:selected, WCueMenuPopup QMenu::item:selected, WCoverArtMenu::item:selected, + WTrackMenu::item:selected, + WTrackMenu QMenu::item:selected, #LibraryContainer QMenu QCheckBox:selected, #LibraryContainer QMenu QCheckBox:focus, /* selected by keyboard */ - #LibraryContainer QMenu QCheckBox:hover /* mouse hover */ { + #LibraryContainer QMenu QCheckBox:hover, /* mouse hover */ + WTrackMenu QCheckBox:selected, + WTrackMenu QCheckBox:focus, + WTrackMenu QCheckBox:hover, + WTrackMenu QMenu QCheckBox:selected, + WTrackMenu QMenu QCheckBox:focus, + WTrackMenu QMenu QCheckBox:hover { background-color: lightgray; color: #000; /* remove OS focus indicator */ @@ -205,8 +225,12 @@ WCoverArtMenu { } /* checked checkbox */ #LibraryContainer QMenu QCheckBox::indicator:checked, + WTrackMenu QCheckBox::indicator:checked, + WTrackMenu QMenu QCheckBox::indicator:checked, /* checkbox in Crate name context menu: "[ ] Auto DJ Track Source" */ #LibraryContainer QMenu::indicator:checked, + WTrackMenu::indicator:checked, + WTrackMenu QMenu::indicator:checked, WEffectSelector::indicator:checked, #fadeModeCombobox::indicator:checked { border-color: #1a2025; @@ -214,12 +238,18 @@ WCoverArtMenu { } WEffectSelector::indicator:checked, #fadeModeCombobox::indicator:checked, - #LibraryContainer QMenu::indicator:checked { + #LibraryContainer QMenu::indicator:checked, + WTrackMenu::indicator:checked, + WTrackMenu QMenu::indicator:checked { background-color: #f90562; } /* unchecked menu checkbox */ #LibraryContainer QMenu QCheckBox::indicator:enabled:!checked, - #LibraryContainer QMenu::indicator:!checked { + #LibraryContainer QMenu::indicator:!checked, + WTrackMenu QCheckBox::indicator:enabled:!checked, + WTrackMenu::indicator:!checked, + WTrackMenu QMenu QCheckBox::indicator:enabled:!checked, + WTrackMenu QMenu::indicator:!checked { border-color: #1a2025;/* background-color: #7e868b; remove OS focus indicator */ @@ -228,22 +258,34 @@ WCoverArtMenu { /* disabled menu item & checkbox */ WBeatSpinBox QMenu::item:!enabled, #LibraryContainer QMenu::item:!enabled, + WTrackMenu::item:!enabled, + WTrackMenu QMenu::item:!enabled, WCueMenuPopup QMenu::item:!enabled, WCoverArtMenu::item:!enabled, - #LibraryContainer QMenu QCheckBox:!enabled { + #LibraryContainer QMenu QCheckBox:!enabled, + WTrackMenu QCheckBox:!enabled, + WTrackMenu QMenu QCheckBox:!enabled { color: #666; } - #LibraryContainer QMenu QCheckBox::indicator:!enabled { + #LibraryContainer QMenu QCheckBox::indicator:!enabled, + WTrackMenu QCheckBox::indicator:!enabled, + WTrackMenu QMenu QCheckBox::indicator:!enabled { border-color: #666; } - #LibraryContainer QMenu QCheckBox::indicator:!enabled:checked { + #LibraryContainer QMenu QCheckBox::indicator:!enabled:checked, + WTrackMenu QCheckBox::indicator:!enabled:checked, + WTrackMenu QMenu QCheckBox::indicator:!enabled:checked { image: url(skin:/btn/btn_lib_checkmark_grey.svg); } - #LibraryContainer QMenu QCheckBox::indicator:indeterminate { + #LibraryContainer QMenu QCheckBox::indicator:indeterminate, + WTrackMenu QCheckBox::indicator:indeterminate, + WTrackMenu QMenu QCheckBox::indicator:indeterminate { background-color: #cf0a57; } #LibraryContainer QMenu QCheckBox::indicator:indeterminate, - #LibraryContainer QCheckBox::indicator:indeterminate:!enabled { + #LibraryContainer QCheckBox::indicator:indeterminate:!enabled, + WTrackMenu QCheckBox::indicator:indeterminate, + WTrackMenu QMenu QCheckBox::indicator:indeterminate{ image: url(skin:/btn/btn_lib_checkmark_dark_grey.svg); } @@ -251,6 +293,8 @@ WCoverArtMenu { WBeatSpinBox QMenu::item, #LibraryContainer QMenu::item, +WTrackMenu::item, +WTrackMenu QMenu::item, WCueMenuPopup QMenu::item, WCoverArtMenu::item { /* right padding creates a margin to the menu expand arrow, @@ -261,13 +305,19 @@ WCoverArtMenu::item { /* icons in editline menu (searchbox, editable track properties) */ WBeatSpinBox QMenu::icon, #LibraryContainer QMenu::icon, + WTrackMenu::icon, + WTrackMenu QMenu::icon, WCueMenuPopup QMenu::icon, WCoverArtMenu::icon, - #LibraryContainer QMenu::indicator { + #LibraryContainer QMenu::indicator, + WTrackMenu::indicator, + WTrackMenu QMenu::indicator { margin: 0px 4px 0px 4px; } WBeatSpinBox QMenu::separator, #LibraryContainer QMenu::separator, + WTrackMenu::separator, + WTrackMenu QMenu::separator, WCueMenuPopup QMenu::separator, WCoverArtMenu::separator { height: 0px; @@ -275,17 +325,25 @@ WCoverArtMenu::item { margin: 4px 0px; } #LibraryContainer QMenu QCheckBox, + WTrackMenu QCheckBox, + WTrackMenu QMenu QCheckBox, WCoverArtMenu QCheckBox { padding: 2px 10px 2px 3px; } #LibraryContainer QMenu QCheckBox::indicator, - #LibraryContainer QMenu::indicator { + #LibraryContainer QMenu::indicator, + WTrackMenu QCheckBox::indicator, + WTrackMenu::indicator, + WTrackMenu QMenu QCheckBox::indicator, + WTrackMenu QMenu::indicator { width: 13px; height: 13px; border-width: 1px; border-style: solid; } - #LibraryContainer QMenu::right-arrow { + #LibraryContainer QMenu::right-arrow, + WTrackMenu::right-arrow, + WTrackMenu QMenu::right-arrow { width: 10px; height: 10px; image: url(skin:/style/menu_arrow.svg); diff --git a/res/skins/Shade/style_dark.qss b/res/skins/Shade/style_dark.qss index 00e54960c1d..ef414cc51d8 100644 --- a/res/skins/Shade/style_dark.qss +++ b/res/skins/Shade/style_dark.qss @@ -12,6 +12,12 @@ WBeatSpinBox QMenu, #LibraryContainer QMenu, #LibraryContainer QMenu::item, #LibraryContainer QMenu QCheckBox, +WTrackMenu, +WTrackMenu::item, +WTrackMenu QCheckBox, +WTrackMenu QMenu, +WTrackMenu QMenu::item, +WTrackMenu QMenu QCheckBox, WCueMenuPopup, WCueMenuPopup QMenu, WCueMenuPopup QMenu::item, @@ -34,11 +40,19 @@ WCoverArtMenu::item { #fadeModeCombobox::indicator:selected, WBeatSpinBox QMenu::item:selected, #LibraryContainer QMenu::item:selected, + WTrackMenu::item:selected, + WTrackMenu QMenu::item:selected, WCueMenuPopup QMenu::item:selected, WCoverArtMenu::item:selected, #LibraryContainer QMenu QCheckBox:selected, #LibraryContainer QMenu QCheckBox:focus, /* selected by keyboard */ - #LibraryContainer QMenu QCheckBox:hover /* mouse hover */ { + #LibraryContainer QMenu QCheckBox:hover, /* mouse hover */ + WTrackMenu QCheckBox:selected, + WTrackMenu QCheckBox:focus, + WTrackMenu QCheckBox:hover, + WTrackMenu QMenu QCheckBox:selected, + WTrackMenu QMenu QCheckBox:focus, + WTrackMenu QMenu QCheckBox:hover { background-color: #999; } /* hover over checked effect */ @@ -49,30 +63,42 @@ WCoverArtMenu::item { WBeatSpinBox QMenu::separator, #LibraryContainer QMenu::separator, + WTrackMenu::separator, + WTrackMenu QMenu::separator, WCueMenuPopup QMenu::separator, WCoverArtMenu::separator { border-top: 1px solid #3F3041; } /* checked effect and menu checkbox */ #LibraryContainer QMenu QCheckBox::indicator:checked, + WTrackMenu QCheckBox::indicator:checked, + WTrackMenu QMenu QCheckBox::indicator:checked, /* checkbox in Crate name context menu: "[ ] Auto DJ Track Source" */ #LibraryContainer QMenu::indicator:checked, + WTrackMenu::indicator:checked, + WTrackMenu QMenu::indicator:checked, WEffectSelector::indicator:checked, #fadeModeCombobox::indicator:checked { border-color: #111; background-color: #897300; } /* unchecked menu checkbox */ - #LibraryContainer QMenu QCheckBox::indicator:!enabled { + #LibraryContainer QMenu QCheckBox::indicator:!enabled, + WTrackMenu QCheckBox::indicator:!enabled, + WTrackMenu QMenu QCheckBox::indicator:!enabled { border-color: #444; background-color: #666; } /* disabled menu item & checkbox */ WBeatSpinBox QMenu::item:!enabled, #LibraryContainer QMenu::item:!enabled, + WTrackMenu::item:!enabled, + WTrackMenu QMenu::item:!enabled, WCueMenuPopup QMenu::item:!enabled, WCoverArtMenu::item:!enabled, - #LibraryContainer QMenu QCheckBox:!enabled { + #LibraryContainer QMenu QCheckBox:!enabled, + WTrackMenu QCheckBox:!enabled, + WTrackMenu QMenu QCheckBox:!enabled { border-color: #444; color: #444; } diff --git a/res/skins/Shade/style_summer_sunset.qss b/res/skins/Shade/style_summer_sunset.qss index d76bebff838..da4119c9a9f 100644 --- a/res/skins/Shade/style_summer_sunset.qss +++ b/res/skins/Shade/style_summer_sunset.qss @@ -12,6 +12,12 @@ WBeatSpinBox QMenu, #LibraryContainer QMenu, #LibraryContainer QMenu::item, #LibraryContainer QMenu QCheckBox, +WTrackMenu, +WTrackMenu::item, +WTrackMenu QCheckBox, +WTrackMenu QMenu, +WTrackMenu QMenu::item, +WTrackMenu QMenu QCheckBox, WCueMenuPopup, WCueMenuPopup QMenu, WCueMenuPopup QMenu::item, @@ -29,6 +35,8 @@ WCoverArtMenu::item { } WBeatSpinBox QMenu::separator, #LibraryContainer QMenu::separator, + WTrackMenu::separator, + WTrackMenu QMenu::separator, WCueMenuPopup QMenu::separator, WCoverArtMenu::separator { border-top: 1px solid #222; @@ -40,11 +48,19 @@ WCoverArtMenu::item { #fadeModeCombobox::indicator:selected, WBeatSpinBox QMenu::item:selected, #LibraryContainer QMenu::item:selected, + WTrackMenu::item:selected, + WTrackMenu QMenu::item:selected, WCueMenuPopup QMenu::item:selected, WCoverArtMenu::item:selected, #LibraryContainer QMenu QCheckBox:selected, #LibraryContainer QMenu QCheckBox:focus, /* selected by keyboard */ - #LibraryContainer QMenu QCheckBox:hover /* mouse hover */ { + #LibraryContainer QMenu QCheckBox:hover, /* mouse hover */ + WTrackMenu QCheckBox:selected, + WTrackMenu QCheckBox:focus, + WTrackMenu QCheckBox:hover, + WTrackMenu QMenu QCheckBox:selected, + WTrackMenu QMenu QCheckBox:focus, + WTrackMenu QMenu QCheckBox:hover { background-color: #d9c663; } @@ -55,21 +71,31 @@ WCoverArtMenu::item { } /* unchecked menu checkbox */ #LibraryContainer QMenu QCheckBox::indicator:enabled:!checked, - #LibraryContainer QMenu::indicator:!checked { + #LibraryContainer QMenu::indicator:!checked, + WTrackMenu QCheckBox::indicator:enabled:!checked, + WTrackMenu::indicator:!checked, + WTrackMenu QMenu QCheckBox::indicator:enabled:!checked, + WTrackMenu QMenu::indicator:!checked { border-color: #222; } /* checked effect and menu checkbox */ #LibraryContainer QMenu QCheckBox::indicator:enabled:checked, + WTrackMenu QCheckBox::indicator:enabled:checked, + WTrackMenu QMenu QCheckBox::indicator:enabled:checked, /* checkbox in Crate name context menu: "[ ] Auto DJ Track Source" */ #LibraryContainer QMenu::indicator:checked, + WTrackMenu::indicator:checked, + WTrackMenu QMenu::indicator:checked, WEffectSelector::indicator:checked, #fadeModeCombobox::indicator:checked { border-color: #222; background-color: #4bcf09; } /* disabled checked box */ - #LibraryContainer QMenu QCheckBox::indicator:!enabled:checked { + #LibraryContainer QMenu QCheckBox::indicator:!enabled:checked, + WTrackMenu QCheckBox::indicator:!enabled:checked, + WTrackMenu QMenu QCheckBox::indicator:!enabled:checked { border-color: #666; background-color: #888; } diff --git a/res/skins/Tango/style.qss b/res/skins/Tango/style.qss index fd41769564f..5b6bcaf39b9 100644 --- a/res/skins/Tango/style.qss +++ b/res/skins/Tango/style.qss @@ -20,7 +20,6 @@ WWidget, WLabel, WNumber, -WTrackProperty, WBeatSpinBox, WEffectSelector, WEffectSelector QAbstractScrollArea, WSearchLineEdit, @@ -2080,11 +2079,17 @@ WCueMenuPopup, WCueMenuPopup QMenu, WCueMenuPopup QLabel, WCoverArtMenu, +WTrackMenu, +WTrackMenu QMenu, #LibraryContainer QMenu::item, WBeatSpinBox QMenu::item, WCueMenuPopup QMenu::item, WCoverArtMenu::item, +WTrackMenu::item, +WTrackMenu QMenu::item, #LibraryContainer QMenu QCheckBox, +WTrackMenu QCheckBox, +WTrackMenu QMenu QCheckBox, #SkinSettings, WEffectSelector, WEffectSelector QAbstractScrollArea, #fadeModeCombobox, #fadeModeCombobox QAbstractScrollArea, @@ -2098,6 +2103,8 @@ WBeatSpinBox QMenu, WCueMenuPopup, WCueMenuPopup QMenu, WCoverArtMenu, +WTrackMenu, +WTrackMenu QMenu, #SkinSettings, WEffectSelector QAbstractScrollArea, #fadeModeCombobox QAbstractScrollArea { @@ -2115,6 +2122,8 @@ WEffectSelector QAbstractScrollArea, WBeatSpinBox QMenu::separator, WCueMenuPopup QMenu::separator, WCoverArtMenu::separator, + WTrackMenu::separator, + WTrackMenu QMenu::separator, WSpinny QMenu::separator { height: 0px; border-top: 1px solid #000; @@ -2125,6 +2134,10 @@ WEffectSelector QAbstractScrollArea, WBeatSpinBox QMenu::item, WCueMenuPopup QMenu::item, WCoverArtMenu::item, + WTrackMenu::item, + WTrackMenu QCheckBox, + WTrackMenu QMenu::item, + WTrackMenu QMenu QCheckBox, #LibraryContainer QMenu QCheckBox, #SkinSettingsLabel, #SkinSettingsButton, @@ -2137,13 +2150,17 @@ WEffectSelector QAbstractScrollArea, #LibraryContainer QMenu::item, WBeatSpinBox QMenu::item, WCueMenuPopup QMenu::item, - WCoverArtMenu::item { + WCoverArtMenu::item, + WTrackMenu::item, + WTrackMenu QMenu::item { /* right padding creates a margin to the menu expand arrow, left padding should be bigger than menu icon width + menu icon margin */ padding: 5px 12px 5px 25px; } - #LibraryContainer QMenu QCheckBox { + #LibraryContainer QMenu QCheckBox, + WTrackMenu QCheckBox, + WTrackMenu QMenu QCheckBox { padding: 2px 10px 2px 3px; } /* icons in editline menu (searchbox, editable track properties) @@ -2153,16 +2170,26 @@ WEffectSelector QAbstractScrollArea, WCueMenuPopup QMenu::icon, /* checkbox in Crate name context menu: "[ ] Auto DJ Track Source" */ - #LibraryContainer QMenu::indicator { + #LibraryContainer QMenu::indicator, + WTrackMenu::indicator, + WTrackMenu QMenu::indicator { margin: 0px 4px 0px 2px; } #LibraryContainer QMenu::item:selected, WBeatSpinBox QMenu::item:selected, WCueMenuPopup QMenu::item:selected, WCoverArtMenu::item:selected, + WTrackMenu::item:selected, + WTrackMenu QMenu::item:selected, #LibraryContainer QMenu QCheckBox:selected, WBeatSpinBox QMenu QCheckBox:focus, /* selected by keyboard */ #LibraryContainer QMenu QCheckBox:hover, /* mouse hover */ + WTrackMenu QCheckBox:selected, + WTrackMenu QCheckBox:focus, + WTrackMenu QCheckBox:hover, + WTrackMenu QMenu QCheckBox:selected, + WTrackMenu QMenu QCheckBox:focus, + WTrackMenu QMenu QCheckBox:hover, #SkinSettingsButton:hover, #SkinSettingsCategoryButton:hover, WEffectSelector::item:selected, @@ -2173,7 +2200,11 @@ WEffectSelector QAbstractScrollArea, outline: none; } #LibraryContainer QMenu QCheckBox::indicator, -#LibraryContainer QMenu::indicator { +WTrackMenu QCheckBox::indicator, +WTrackMenu QMenu QCheckBox::indicator, +#LibraryContainer QMenu::indicator, +WTrackMenu::indicator, +WTrackMenu QMenu::indicator{ width: 13px; height: 13px; background-color: #0f0f0f; @@ -2183,16 +2214,24 @@ WEffectSelector QAbstractScrollArea, outline: none; } #LibraryContainer QMenu QCheckBox::indicator:checked, - #LibraryContainer QMenu::indicator:checked { + WTrackMenu QCheckBox::indicator:checked, + WTrackMenu QMenu QCheckBox::indicator:checked, + #LibraryContainer QMenu::indicator:checked, + WTrackMenu::indicator:checked, + WTrackMenu QMenu::indicator:checked { image: url(skin:/buttons/btn_lib_checkmark.svg); border: 1px solid #1e1e1e; } /* disabled menu checkbox */ - #LibraryContainer QMenu QCheckBox::indicator:!enabled { + #LibraryContainer QMenu QCheckBox::indicator:!enabled, + WTrackMenu QCheckBox::indicator:!enabled, + WTrackMenu QMenu QCheckBox::indicator:!enabled { background-color: #333; border: 1px solid #222; } - #LibraryContainer QMenu QCheckBox::indicator:!enabled:checked { + #LibraryContainer QMenu QCheckBox::indicator:!enabled:checked, + WTrackMenu QCheckBox::indicator:!enabled:checked, + WTrackMenu QMenu QCheckBox::indicator:!enabled:checked { image: url(skin:/buttons/btn_lib_checkmark_grey.svg); } @@ -2200,6 +2239,10 @@ WEffectSelector QAbstractScrollArea, WBeatSpinBox QMenu::item:!enabled, WCueMenuPopup QMenu::item:!enabled, WCoverArtMenu::item:!enabled, +WTrackMenu::item:!enabled, +WTrackMenu QCheckBox:!enabled, +WTrackMenu QMenu::item:!enabled, +WTrackMenu QMenu QCheckBox:!enabled, #LibraryContainer QMenu QCheckBox:!enabled { color: #555; } @@ -2211,13 +2254,17 @@ WCoverArtMenu::item:!enabled, #LibraryContainer QCheckBox::indicator:indeterminate:!enabled { image: url(skin:/buttons/btn_lib_checkmark_grey.svg); } -#LibraryContainer QMenu::right-arrow { +#LibraryContainer QMenu::right-arrow, +WTrackMenu::right-arrow, +WTrackMenu QMenu::right-arrow { width: 8px; height: 16px; margin-right: 4px; image: url(skin:/buttons/btn_arrow_right.svg); } -#LibraryContainer QMenu::right-arrow { +#LibraryContainer QMenu::right-arrow, +WTrackMenu::right-arrow, +WTrackMenu QMenu::right-arrow { image: url(skin:/buttons/btn_arrow_right_hover.svg); } diff --git a/src/library/basetracktablemodel.cpp b/src/library/basetracktablemodel.cpp index 688887db738..95c2980c2cd 100644 --- a/src/library/basetracktablemodel.cpp +++ b/src/library/basetracktablemodel.cpp @@ -795,3 +795,8 @@ TrackPointer BaseTrackTableModel::getTrackByRef( const TrackRef& trackRef) const { return m_pTrackCollectionManager->internalCollection()->getTrackByRef(trackRef); } + +bool BaseTrackTableModel::hasCapabilities( + TrackModel::CapabilitiesFlags capabilities) const { + return (getCapabilities() & capabilities) == capabilities; +} diff --git a/src/library/basetracktablemodel.h b/src/library/basetracktablemodel.h index 9228edd2be0..bf40a595d43 100644 --- a/src/library/basetracktablemodel.h +++ b/src/library/basetracktablemodel.h @@ -89,6 +89,9 @@ class BaseTrackTableModel : public QAbstractTableModel, public TrackModel { TrackPointer getTrackByRef( const TrackRef& trackRef) const override; + bool hasCapabilities( + TrackModel::CapabilitiesFlags capabilities) const final; + protected: static constexpr int defaultColumnWidth() { return 50; diff --git a/src/library/dlgtagfetcher.cpp b/src/library/dlgtagfetcher.cpp index c7394dea4c8..17049b1db2c 100644 --- a/src/library/dlgtagfetcher.cpp +++ b/src/library/dlgtagfetcher.cpp @@ -54,20 +54,26 @@ void addTrack( } // anonymous namespace -DlgTagFetcher::DlgTagFetcher(QWidget* parent) +DlgTagFetcher::DlgTagFetcher(QWidget* parent, const TrackModel* trackModel) : QDialog(parent), m_tagFetcher(parent), - m_networkResult(NetworkResult::Ok) { + m_networkResult(NetworkResult::Ok), + m_pTrackModel(trackModel) { init(); } void DlgTagFetcher::init() { setupUi(this); + if (m_pTrackModel) { + connect(btnPrev, &QPushButton::clicked, this, &DlgTagFetcher::slotPrev); + connect(btnNext, &QPushButton::clicked, this, &DlgTagFetcher::slotNext); + } else { + btnNext->hide(); + btnPrev->hide(); + } connect(btnApply, &QPushButton::clicked, this, &DlgTagFetcher::apply); connect(btnQuit, &QPushButton::clicked, this, &DlgTagFetcher::quit); - connect(btnPrev, &QPushButton::clicked, this, &DlgTagFetcher::previous); - connect(btnNext, &QPushButton::clicked, this, &DlgTagFetcher::next); connect(results, &QTreeWidget::currentItemChanged, this, &DlgTagFetcher::resultSelected); connect(&m_tagFetcher, &TagFetcher::resultAvailable, this, &DlgTagFetcher::fetchTagFinished); @@ -83,7 +89,25 @@ void DlgTagFetcher::init() { results->setColumnWidth(5, 160); // Artist column } -void DlgTagFetcher::loadTrack(const TrackPointer& track) { +void DlgTagFetcher::slotNext() { + QModelIndex nextRow = m_currentTrackIndex.sibling( + m_currentTrackIndex.row() + 1, m_currentTrackIndex.column()); + if (nextRow.isValid()) { + loadTrack(nextRow); + emit next(); + } +} + +void DlgTagFetcher::slotPrev() { + QModelIndex prevRow = m_currentTrackIndex.sibling( + m_currentTrackIndex.row() - 1, m_currentTrackIndex.column()); + if (prevRow.isValid()) { + loadTrack(prevRow); + emit previous(); + } +} + +void DlgTagFetcher::loadTrackInternal(const TrackPointer& track) { if (!track) { return; } @@ -107,6 +131,22 @@ void DlgTagFetcher::loadTrack(const TrackPointer& track) { updateStack(); } +void DlgTagFetcher::loadTrack(const TrackPointer& track) { + VERIFY_OR_DEBUG_ASSERT(!m_pTrackModel) { + return; + } + loadTrackInternal(track); +} + +void DlgTagFetcher::loadTrack(const QModelIndex& index) { + VERIFY_OR_DEBUG_ASSERT(m_pTrackModel) { + return; + } + TrackPointer pTrack = m_pTrackModel->getTrack(index); + m_currentTrackIndex = index; + loadTrackInternal(pTrack); +} + void DlgTagFetcher::slotTrackChanged(TrackId trackId) { if (m_track && m_track->getId() == trackId) { updateStack(); diff --git a/src/library/dlgtagfetcher.h b/src/library/dlgtagfetcher.h index 341f7807deb..c15230cd3e4 100644 --- a/src/library/dlgtagfetcher.h +++ b/src/library/dlgtagfetcher.h @@ -4,21 +4,28 @@ #include #include +#include "library/trackmodel.h" #include "library/ui_dlgtagfetcher.h" -#include "track/track.h" #include "musicbrainz/tagfetcher.h" +#include "track/track.h" +/// A dialog box to fetch track metadata from MusicBrainz. +/// Use TrackPointer to load a track into the dialog or +/// QModelIndex along with TrackModel to enable previous and next buttons +/// to switch tracks within the context of the TrackModel. class DlgTagFetcher : public QDialog, public Ui::DlgTagFetcher { Q_OBJECT public: - explicit DlgTagFetcher(QWidget *parent); + // TODO: Remove dependency on TrackModel + explicit DlgTagFetcher(QWidget* parent, const TrackModel* trackModel = nullptr); ~DlgTagFetcher() override = default; void init(); public slots: void loadTrack(const TrackPointer& track); + void loadTrack(const QModelIndex& index); signals: void next(); @@ -34,8 +41,11 @@ class DlgTagFetcher : public QDialog, public Ui::DlgTagFetcher { void slotTrackChanged(TrackId trackId); void apply(); void quit(); + void slotNext(); + void slotPrev(); private: + void loadTrackInternal(const TrackPointer& track); void updateStack(); void addDivider(const QString& text, QTreeWidget* parent) const; @@ -58,4 +68,6 @@ class DlgTagFetcher : public QDialog, public Ui::DlgTagFetcher { UnknownError, }; NetworkResult m_networkResult; + const TrackModel* m_pTrackModel; + QModelIndex m_currentTrackIndex; }; diff --git a/src/library/dlgtrackinfo.cpp b/src/library/dlgtrackinfo.cpp index 4de6b7905e9..4f6a3b35e5f 100644 --- a/src/library/dlgtrackinfo.cpp +++ b/src/library/dlgtrackinfo.cpp @@ -24,12 +24,15 @@ const int kMinBpm = 30; // Maximum allowed interval between beats (calculated from kMinBpm). const mixxx::Duration kMaxInterval = mixxx::Duration::fromMillis(1000.0 * (60.0 / kMinBpm)); -DlgTrackInfo::DlgTrackInfo(UserSettingsPointer pConfig, QWidget* parent) +DlgTrackInfo::DlgTrackInfo(QWidget* parent, + UserSettingsPointer pConfig, + const TrackModel* trackModel) : QDialog(parent), m_pTapFilter(new TapFilter(this, kFilterLength, kMaxInterval)), m_dLastTapedBpm(-1.), m_pWCoverArtLabel(new WCoverArtLabel(this)), - m_pConfig(pConfig) { + m_pConfig(pConfig), + m_pTrackModel(trackModel) { init(); } @@ -42,11 +45,47 @@ void DlgTrackInfo::init() { coverBox->insertWidget(1, m_pWCoverArtLabel); - connect(btnNext, &QPushButton::clicked, this, &DlgTrackInfo::slotNext); - connect(btnPrev, &QPushButton::clicked, this, &DlgTrackInfo::slotPrev); - connect(btnApply, &QPushButton::clicked, this, &DlgTrackInfo::slotApply); - connect(btnOK, &QPushButton::clicked, this, &DlgTrackInfo::slotOk); - connect(btnCancel, &QPushButton::clicked, this, &DlgTrackInfo::slotCancel); + m_pTagFetcher.reset(new DlgTagFetcher(this, m_pTrackModel)); + if (m_pTrackModel) { + connect(btnNext, + &QPushButton::clicked, + this, + &DlgTrackInfo::slotNextButton); + + connect(btnPrev, + &QPushButton::clicked, + this, + &DlgTrackInfo::slotPrevButton); + + connect(m_pTagFetcher.data(), + &DlgTagFetcher::next, + this, + &DlgTrackInfo::slotNextDlgTagFetcher); + + connect(m_pTagFetcher.data(), + &DlgTagFetcher::previous, + this, + &DlgTrackInfo::slotPrevDlgTagFetcher); + + } else { + btnNext->hide(); + btnPrev->hide(); + } + + connect(btnApply, + &QPushButton::clicked, + this, + &DlgTrackInfo::slotApply); + + connect(btnOK, + &QPushButton::clicked, + this, + &DlgTrackInfo::slotOk); + + connect(btnCancel, + &QPushButton::clicked, + this, + &DlgTrackInfo::slotCancel); connect(bpmDouble, &QPushButton::clicked, @@ -105,10 +144,12 @@ void DlgTrackInfo::init() { &QPushButton::clicked, this, &DlgTrackInfo::slotImportMetadataFromFile); + connect(btnImportMetadataFromMusicBrainz, &QPushButton::clicked, this, &DlgTrackInfo::slotImportMetadataFromMusicBrainz); + connect(btnOpenFileBrowser, &QPushButton::clicked, this, @@ -149,12 +190,46 @@ void DlgTrackInfo::trackUpdated() { } -void DlgTrackInfo::slotNext() { - emit next(); +void DlgTrackInfo::slotNextButton() { + loadNextTrack(); + if (m_pTagFetcher->isVisible()) { + m_pTagFetcher->loadTrack(m_currentTrackIndex); + } } -void DlgTrackInfo::slotPrev() { - emit previous(); +void DlgTrackInfo::slotPrevButton() { + loadPrevTrack(); + if (m_pTagFetcher->isVisible()) { + m_pTagFetcher->loadTrack(m_currentTrackIndex); + } +} + +void DlgTrackInfo::slotNextDlgTagFetcher() { + loadNextTrack(); + // Do not load track back into DlgTagFetcher since + // it will cause a reload of the same track. +} + +void DlgTrackInfo::slotPrevDlgTagFetcher() { + loadPrevTrack(); +} + +void DlgTrackInfo::loadNextTrack() { + auto nextRow = m_currentTrackIndex.sibling( + m_currentTrackIndex.row() + 1, m_currentTrackIndex.column()); + if (nextRow.isValid()) { + loadTrack(nextRow); + emit next(); + } +} + +void DlgTrackInfo::loadPrevTrack() { + QModelIndex prevRow = m_currentTrackIndex.sibling( + m_currentTrackIndex.row() - 1, m_currentTrackIndex.column()); + if (prevRow.isValid()) { + loadTrack(prevRow); + emit previous(); + } } void DlgTrackInfo::populateFields(const Track& track) { @@ -176,7 +251,8 @@ void DlgTrackInfo::populateFields(const Track& track) { txtDuration->setText(track.getDurationText(mixxx::Duration::Precision::SECONDS)); txtLocation->setText(QDir::toNativeSeparators(track.getLocation())); txtType->setText(track.getType()); - txtBitrate->setText(QString(track.getBitrateText()) + (" ") + tr(mixxx::audio::Bitrate::unit())); + txtBitrate->setText(QString(track.getBitrateText()) + (" ") + + tr(mixxx::audio::Bitrate::unit())); txtBpm->setText(track.getBpmText()); m_keysClone = track.getKeys(); txtKey->setText(KeyUtils::getGlobalKeyText(m_keysClone)); @@ -212,7 +288,7 @@ void DlgTrackInfo::reloadTrackBeats(const Track& track) { } } -void DlgTrackInfo::loadTrack(TrackPointer pTrack) { +void DlgTrackInfo::loadTrackInternal(const TrackPointer& pTrack) { clear(); if (!pTrack) { @@ -232,6 +308,22 @@ void DlgTrackInfo::loadTrack(TrackPointer pTrack) { &DlgTrackInfo::slotTrackChanged); } +void DlgTrackInfo::loadTrack(TrackPointer pTrack) { + VERIFY_OR_DEBUG_ASSERT(!m_pTrackModel) { + return; + } + loadTrackInternal(pTrack); +} + +void DlgTrackInfo::loadTrack(QModelIndex index) { + VERIFY_OR_DEBUG_ASSERT(m_pTrackModel) { + return; + } + TrackPointer pTrack = m_pTrackModel->getTrack(index); + m_currentTrackIndex = index; + loadTrackInternal(pTrack); +} + void DlgTrackInfo::slotCoverFound( const QObject* pRequestor, const CoverInfo& coverInfo, @@ -521,5 +613,10 @@ void DlgTrackInfo::slotTrackChanged(TrackId trackId) { } void DlgTrackInfo::slotImportMetadataFromMusicBrainz() { - emit showTagFetcher(m_pLoadedTrack); + if (m_pTrackModel) { + m_pTagFetcher->loadTrack(m_currentTrackIndex); + } else { + m_pTagFetcher->loadTrack(m_pLoadedTrack); + } + m_pTagFetcher->show(); } diff --git a/src/library/dlgtrackinfo.h b/src/library/dlgtrackinfo.h index 64276564a1b..5a21acbfcfe 100644 --- a/src/library/dlgtrackinfo.h +++ b/src/library/dlgtrackinfo.h @@ -2,38 +2,49 @@ #define DLGTRACKINFO_H #include -#include #include #include +#include #include +#include "library/coverart.h" +#include "library/dlgtagfetcher.h" +#include "library/trackmodel.h" #include "library/ui_dlgtrackinfo.h" #include "track/track.h" -#include "library/coverart.h" #include "util/tapfilter.h" #include "util/types.h" #include "widget/wcoverartlabel.h" #include "widget/wcoverartmenu.h" +/// A dialog box to display and edit track properties. +/// Use TrackPointer to load a track into the dialog or +/// QModelIndex along with TrackModel to enable previous and next buttons +/// to switch tracks within the context of the TrackModel. class DlgTrackInfo : public QDialog, public Ui::DlgTrackInfo { Q_OBJECT public: - DlgTrackInfo(UserSettingsPointer pConfig, QWidget* parent); + // TODO: Remove dependency on TrackModel + DlgTrackInfo(QWidget* parent, + UserSettingsPointer pConfig, + const TrackModel* trackModel = nullptr); virtual ~DlgTrackInfo(); public slots: // Not thread safe. Only invoke via AutoConnection or QueuedConnection, not // directly! void loadTrack(TrackPointer pTrack); + void loadTrack(QModelIndex index); signals: void next(); void previous(); - void showTagFetcher(TrackPointer pTrack); private slots: - void slotNext(); - void slotPrev(); + void slotNextButton(); + void slotPrevButton(); + void slotNextDlgTagFetcher(); + void slotPrevDlgTagFetcher(); void slotOk(); void slotApply(); void slotCancel(); @@ -69,6 +80,9 @@ class DlgTrackInfo : public QDialog, public Ui::DlgTrackInfo { void slotReloadCoverArt(); private: + void loadNextTrack(); + void loadPrevTrack(); + void loadTrackInternal(const TrackPointer& pTrack); void populateFields(const Track& track); void reloadTrackBeats(const Track& track); void saveTrack(); @@ -81,11 +95,15 @@ class DlgTrackInfo : public QDialog, public Ui::DlgTrackInfo { bool m_trackHasBeatMap; QScopedPointer m_pTapFilter; + QScopedPointer m_pTagFetcher; double m_dLastTapedBpm; CoverInfo m_loadedCoverInfo; WCoverArtLabel* m_pWCoverArtLabel; UserSettingsPointer m_pConfig; + + const TrackModel* m_pTrackModel; + QModelIndex m_currentTrackIndex; }; #endif /* DLGTRACKINFO_H */ diff --git a/src/library/trackmodel.h b/src/library/trackmodel.h index 4516630c28d..f717fe6e440 100644 --- a/src/library/trackmodel.h +++ b/src/library/trackmodel.h @@ -147,6 +147,10 @@ class TrackModel { virtual TrackModel::CapabilitiesFlags getCapabilities() const { return TRACKMODELCAPS_NONE; } + virtual bool hasCapabilities(TrackModel::CapabilitiesFlags flags) const { + Q_UNUSED(flags); + return false; + } virtual QString getModelSetting(QString name) { SettingsDAO settings(m_db); QString key = m_settingsNamespace + "." + name; diff --git a/src/skin/legacyskinparser.cpp b/src/skin/legacyskinparser.cpp index 30cd1c3ae7d..ac367d6da42 100644 --- a/src/skin/legacyskinparser.cpp +++ b/src/skin/legacyskinparser.cpp @@ -1010,7 +1010,10 @@ QWidget* LegacySkinParser::parseText(const QDomElement& node) { if (!pPlayer) return NULL; - WTrackText* p = new WTrackText(pSafeChannelStr, m_pConfig, m_pParent); + WTrackText* p = new WTrackText(m_pParent, + m_pConfig, + m_pLibrary->trackCollections(), + pSafeChannelStr); setupLabelWidget(node, p); connect(pPlayer, SIGNAL(newTrackLoaded(TrackPointer)), @@ -1039,7 +1042,10 @@ QWidget* LegacySkinParser::parseTrackProperty(const QDomElement& node) { if (!pPlayer) return NULL; - WTrackProperty* p = new WTrackProperty(pSafeChannelStr, m_pConfig, m_pParent); + WTrackProperty* p = new WTrackProperty(m_pParent, + m_pConfig, + m_pLibrary->trackCollections(), + pSafeChannelStr); setupLabelWidget(node, p); connect(pPlayer, SIGNAL(newTrackLoaded(TrackPointer)), diff --git a/src/track/track.h b/src/track/track.h index e3247b5b0f2..dc44420ed4e 100644 --- a/src/track/track.h +++ b/src/track/track.h @@ -6,6 +6,7 @@ #include #include "audio/streaminfo.h" +#include "sources/metadatasource.h" #include "track/beats.h" #include "track/cue.h" #include "track/trackfile.h" @@ -14,13 +15,12 @@ #include "util/sandbox.h" #include "waveform/waveform.h" -#include "sources/metadatasource.h" - // forward declaration(s) class Track; typedef std::shared_ptr TrackPointer; typedef std::weak_ptr TrackWeakPointer; +typedef QList TrackPointerList; Q_DECLARE_METATYPE(TrackPointer); diff --git a/src/track/trackid.h b/src/track/trackid.h index e0f85ba5329..a9d4162369f 100644 --- a/src/track/trackid.h +++ b/src/track/trackid.h @@ -14,5 +14,6 @@ class TrackId: public DbId { Q_DECLARE_TYPEINFO(TrackId, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(TrackId) +typedef QList TrackIdList; #endif // TRACKID_H diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp new file mode 100644 index 00000000000..a81222d031f --- /dev/null +++ b/src/widget/wtrackmenu.cpp @@ -0,0 +1,1237 @@ +#include "widget/wtrackmenu.h" + +#include +#include +#include + +#include "control/controlobject.h" +#include "control/controlproxy.h" +#include "library/coverartutils.h" +#include "library/crate/cratefeaturehelper.h" +#include "library/dao/trackdao.h" +#include "library/dao/trackschema.h" +#include "library/dlgtrackmetadataexport.h" +#include "library/externaltrackcollection.h" +#include "library/library.h" +#include "library/librarytablemodel.h" +#include "library/trackcollection.h" +#include "library/trackcollectionmanager.h" +#include "library/trackmodel.h" +#include "mixer/playermanager.h" +#include "preferences/colorpalettesettings.h" +#include "sources/soundsourceproxy.h" +#include "track/trackref.h" +#include "util/desktophelper.h" +#include "util/parented_ptr.h" +#include "widget/wcolorpickeraction.h" +#include "widget/wskincolor.h" +#include "widget/wwidget.h" + +WTrackMenu::WTrackMenu(QWidget* parent, + UserSettingsPointer pConfig, + TrackCollectionManager* pTrackCollectionManager, + Features flags, + TrackModel* trackModel) + : QMenu(parent), + m_pTrackModel(trackModel), + m_pConfig(pConfig), + m_pTrackCollectionManager(pTrackCollectionManager), + m_bPlaylistMenuLoaded(false), + m_bCrateMenuLoaded(false), + m_eActiveFeatures(flags), + m_eTrackModelFeatures(Feature::TrackModelFeatures) { + m_pNumSamplers = new ControlProxy( + "[Master]", "num_samplers", this); + m_pNumDecks = new ControlProxy( + "[Master]", "num_decks", this); + m_pNumPreviewDecks = new ControlProxy( + "[Master]", "num_preview_decks", this); + + // Warn if any of the chosen features depend on a TrackModel + VERIFY_OR_DEBUG_ASSERT(trackModel || (m_eTrackModelFeatures & flags) == 0) { + // Remove unsupported features + m_eActiveFeatures &= !m_eTrackModelFeatures; + } + + createMenus(); + createActions(); + setupActions(); +} + +void WTrackMenu::popup(const QPoint& pos, QAction* at) { + if (getTrackPointers().empty()) { + return; + } + QMenu::popup(pos, at); +} + +void WTrackMenu::createMenus() { + if (featureIsEnabled(Feature::LoadTo)) { + m_pLoadToMenu = new QMenu(this); + m_pLoadToMenu->setTitle(tr("Load to")); + m_pDeckMenu = new QMenu(m_pLoadToMenu); + m_pDeckMenu->setTitle(tr("Deck")); + m_pSamplerMenu = new QMenu(m_pLoadToMenu); + m_pSamplerMenu->setTitle(tr("Sampler")); + } + + if (featureIsEnabled(Feature::Playlist)) { + m_pPlaylistMenu = new QMenu(this); + m_pPlaylistMenu->setTitle(tr("Add to Playlist")); + connect(m_pPlaylistMenu, &QMenu::aboutToShow, this, &WTrackMenu::slotPopulatePlaylistMenu); + } + + if (featureIsEnabled(Feature::Crate)) { + m_pCrateMenu = new QMenu(this); + m_pCrateMenu->setTitle(tr("Crates")); + connect(m_pCrateMenu, &QMenu::aboutToShow, this, &WTrackMenu::slotPopulateCrateMenu); + } + + if (featureIsEnabled(Feature::Metadata)) { + m_pMetadataMenu = new QMenu(this); + m_pMetadataMenu->setTitle(tr("Metadata")); + + m_pMetadataUpdateExternalCollectionsMenu = new QMenu(m_pMetadataMenu); + m_pMetadataUpdateExternalCollectionsMenu->setTitle(tr("Update external collections")); + + m_pCoverMenu = new WCoverArtMenu(m_pMetadataMenu); + m_pCoverMenu->setTitle(tr("Cover Art")); + connect(m_pCoverMenu, &WCoverArtMenu::coverInfoSelected, this, &WTrackMenu::slotCoverInfoSelected); + connect(m_pCoverMenu, &WCoverArtMenu::reloadCoverArt, this, &WTrackMenu::slotReloadCoverArt); + } + + if (featureIsEnabled(Feature::BPM)) { + m_pBPMMenu = new QMenu(this); + m_pBPMMenu->setTitle(tr("Adjust BPM")); + } + + if (featureIsEnabled(Feature::Color)) { + m_pColorMenu = new QMenu(this); + m_pColorMenu->setTitle(tr("Select Color")); + } + + if (featureIsEnabled(Feature::Reset)) { + m_pClearMetadataMenu = new QMenu(this); + //: Reset metadata in right click track context menu in library + m_pClearMetadataMenu->setTitle(tr("Reset")); + } +} + +void WTrackMenu::createActions() { + if (featureIsEnabled(Feature::AutoDJ)) { + m_pAutoDJBottomAct = new QAction(tr("Add to Auto DJ Queue (bottom)"), this); + connect(m_pAutoDJBottomAct, &QAction::triggered, this, &WTrackMenu::slotAddToAutoDJBottom); + + m_pAutoDJTopAct = new QAction(tr("Add to Auto DJ Queue (top)"), this); + connect(m_pAutoDJTopAct, &QAction::triggered, this, &WTrackMenu::slotAddToAutoDJTop); + + m_pAutoDJReplaceAct = new QAction(tr("Add to Auto DJ Queue (replace)"), this); + connect(m_pAutoDJReplaceAct, &QAction::triggered, this, &WTrackMenu::slotAddToAutoDJReplace); + } + + if (featureIsEnabled(Feature::LoadTo)) { + m_pAddToPreviewDeck = new QAction(tr("Preview Deck"), m_pLoadToMenu); + // currently there is only one preview deck so just map it here. + QString previewDeckGroup = PlayerManager::groupForPreviewDeck(0); + connect(m_pAddToPreviewDeck, &QAction::triggered, this, [this, previewDeckGroup] { loadSelectionToGroup(previewDeckGroup); }); + } + + if (featureIsEnabled(Feature::Remove)) { + m_pRemoveAct = new QAction(tr("Remove"), this); + connect(m_pRemoveAct, &QAction::triggered, this, &WTrackMenu::slotRemove); + + m_pRemovePlaylistAct = new QAction(tr("Remove from Playlist"), this); + connect(m_pRemovePlaylistAct, &QAction::triggered, this, &WTrackMenu::slotRemove); + + m_pRemoveCrateAct = new QAction(tr("Remove from Crate"), this); + connect(m_pRemoveCrateAct, &QAction::triggered, this, &WTrackMenu::slotRemove); + } + + if (featureIsEnabled(Feature::HideUnhidePurge)) { + m_pHideAct = new QAction(tr("Hide from Library"), this); + connect(m_pHideAct, &QAction::triggered, this, &WTrackMenu::slotHide); + + m_pUnhideAct = new QAction(tr("Unhide from Library"), this); + connect(m_pUnhideAct, &QAction::triggered, this, &WTrackMenu::slotUnhide); + + m_pPurgeAct = new QAction(tr("Purge from Library"), this); + connect(m_pPurgeAct, &QAction::triggered, this, &WTrackMenu::slotPurge); + } + + if (featureIsEnabled(Feature::Properties)) { + m_pPropertiesAct = new QAction(tr("Properties"), this); + connect(m_pPropertiesAct, &QAction::triggered, this, &WTrackMenu::slotShowTrackInfo); + } + + if (featureIsEnabled(Feature::FileBrowser)) { + m_pFileBrowserAct = new QAction(tr("Open in File Browser"), this); + connect(m_pFileBrowserAct, &QAction::triggered, this, &WTrackMenu::slotOpenInFileBrowser); + } + + if (featureIsEnabled(Feature::Metadata)) { + m_pImportMetadataFromFileAct = new QAction(tr("Import From File Tags"), m_pMetadataMenu); + connect(m_pImportMetadataFromFileAct, &QAction::triggered, this, &WTrackMenu::slotImportTrackMetadataFromFileTags); + + m_pImportMetadataFromMusicBrainzAct = new QAction(tr("Import From MusicBrainz"), m_pMetadataMenu); + connect(m_pImportMetadataFromMusicBrainzAct, &QAction::triggered, this, &WTrackMenu::slotShowDlgTagFetcher); + + // Give a nullptr parent because otherwise it inherits our style which can + // make it unreadable. Bug #673411 + m_pTagFetcher.reset(new DlgTagFetcher(nullptr, m_pTrackModel)); + + m_pExportMetadataAct = new QAction(tr("Export To File Tags"), m_pMetadataMenu); + connect(m_pExportMetadataAct, &QAction::triggered, this, &WTrackMenu::slotExportTrackMetadataIntoFileTags); + + for (const auto& externalTrackCollection : m_pTrackCollectionManager->externalCollections()) { + UpdateExternalTrackCollection updateInExternalTrackCollection; + updateInExternalTrackCollection.externalTrackCollection = externalTrackCollection; + updateInExternalTrackCollection.action = new QAction(externalTrackCollection->name(), m_pMetadataMenu); + updateInExternalTrackCollection.action->setToolTip(externalTrackCollection->description()); + m_updateInExternalTrackCollections += updateInExternalTrackCollection; + auto externalTrackCollectionPtr = updateInExternalTrackCollection.externalTrackCollection; + connect(updateInExternalTrackCollection.action, &QAction::triggered, this, [this, externalTrackCollectionPtr] { + slotUpdateExternalTrackCollection(externalTrackCollectionPtr); + }); + } + } + + if (featureIsEnabled(Feature::Reset)) { + // Clear metadata actions + m_pClearBeatsAction = new QAction(tr("BPM and Beatgrid"), m_pClearMetadataMenu); + connect(m_pClearBeatsAction, &QAction::triggered, this, &WTrackMenu::slotClearBeats); + + m_pClearPlayCountAction = new QAction(tr("Play Count"), m_pClearMetadataMenu); + connect(m_pClearPlayCountAction, &QAction::triggered, this, &WTrackMenu::slotClearPlayCount); + + m_pClearMainCueAction = new QAction(tr("Cue Point"), m_pClearMetadataMenu); + connect(m_pClearMainCueAction, &QAction::triggered, this, &WTrackMenu::slotClearMainCue); + + m_pClearHotCuesAction = new QAction(tr("Hotcues"), m_pClearMetadataMenu); + connect(m_pClearHotCuesAction, &QAction::triggered, this, &WTrackMenu::slotClearHotCues); + + m_pClearIntroCueAction = new QAction(tr("Intro"), m_pClearMetadataMenu); + connect(m_pClearIntroCueAction, &QAction::triggered, this, &WTrackMenu::slotClearIntroCue); + + m_pClearOutroCueAction = new QAction(tr("Outro"), m_pClearMetadataMenu); + connect(m_pClearOutroCueAction, &QAction::triggered, this, &WTrackMenu::slotClearOutroCue); + + m_pClearLoopAction = new QAction(tr("Loop"), m_pClearMetadataMenu); + connect(m_pClearLoopAction, &QAction::triggered, this, &WTrackMenu::slotClearLoop); + + m_pClearKeyAction = new QAction(tr("Key"), m_pClearMetadataMenu); + connect(m_pClearKeyAction, &QAction::triggered, this, &WTrackMenu::slotClearKey); + + m_pClearReplayGainAction = new QAction(tr("ReplayGain"), m_pClearMetadataMenu); + connect(m_pClearReplayGainAction, &QAction::triggered, this, &WTrackMenu::slotClearReplayGain); + + m_pClearWaveformAction = new QAction(tr("Waveform"), m_pClearMetadataMenu); + connect(m_pClearWaveformAction, &QAction::triggered, this, &WTrackMenu::slotClearWaveform); + + m_pClearAllMetadataAction = new QAction(tr("All"), m_pClearMetadataMenu); + connect(m_pClearAllMetadataAction, &QAction::triggered, this, &WTrackMenu::slotClearAllMetadata); + } + + if (featureIsEnabled(Feature::BPM)) { + m_pBpmLockAction = new QAction(tr("Lock BPM"), m_pBPMMenu); + m_pBpmUnlockAction = new QAction(tr("Unlock BPM"), m_pBPMMenu); + connect(m_pBpmLockAction, &QAction::triggered, this, &WTrackMenu::slotLockBpm); + connect(m_pBpmUnlockAction, &QAction::triggered, this, &WTrackMenu::slotUnlockBpm); + + //BPM edit actions + m_pBpmDoubleAction = new QAction(tr("Double BPM"), m_pBPMMenu); + m_pBpmHalveAction = new QAction(tr("Halve BPM"), m_pBPMMenu); + m_pBpmTwoThirdsAction = new QAction(tr("2/3 BPM"), m_pBPMMenu); + m_pBpmThreeFourthsAction = new QAction(tr("3/4 BPM"), m_pBPMMenu); + m_pBpmFourThirdsAction = new QAction(tr("4/3 BPM"), m_pBPMMenu); + m_pBpmThreeHalvesAction = new QAction(tr("3/2 BPM"), m_pBPMMenu); + + connect(m_pBpmDoubleAction, &QAction::triggered, this, [this] { slotScaleBpm(Beats::DOUBLE); }); + connect(m_pBpmHalveAction, &QAction::triggered, this, [this] { slotScaleBpm(Beats::HALVE); }); + connect(m_pBpmTwoThirdsAction, &QAction::triggered, this, [this] { slotScaleBpm(Beats::TWOTHIRDS); }); + connect(m_pBpmThreeFourthsAction, &QAction::triggered, this, [this] { slotScaleBpm(Beats::THREEFOURTHS); }); + connect(m_pBpmFourThirdsAction, &QAction::triggered, this, [this] { slotScaleBpm(Beats::FOURTHIRDS); }); + connect(m_pBpmThreeHalvesAction, &QAction::triggered, this, [this] { slotScaleBpm(Beats::THREEHALVES); }); + } + + if (featureIsEnabled(Feature::Color)) { + ColorPaletteSettings colorPaletteSettings(m_pConfig); + m_pColorPickerAction = new WColorPickerAction(WColorPicker::Option::AllowNoColor, + colorPaletteSettings.getTrackColorPalette(), + m_pColorMenu); + m_pColorPickerAction->setObjectName("TrackColorPickerAction"); + connect(m_pColorPickerAction, + &WColorPickerAction::colorPicked, + this, + &WTrackMenu::slotColorPicked); + } + + if (featureIsEnabled(Feature::Properties)) { + // Give a nullptr parent because otherwise it inherits our style which can + // make it unreadable. Bug #673411 + m_pTrackInfo.reset(new DlgTrackInfo(nullptr, m_pConfig, m_pTrackModel)); + } +} + +void WTrackMenu::setupActions() { + if (featureIsEnabled(Feature::AutoDJ)) { + addAction(m_pAutoDJBottomAct); + addAction(m_pAutoDJTopAct); + addAction(m_pAutoDJReplaceAct); + addSeparator(); + } + + if (featureIsEnabled(Feature::LoadTo)) { + m_pLoadToMenu->addMenu(m_pDeckMenu); + + m_pLoadToMenu->addMenu(m_pSamplerMenu); + + if (m_pNumPreviewDecks->get() > 0.0) { + m_pLoadToMenu->addAction(m_pAddToPreviewDeck); + } + + addMenu(m_pLoadToMenu); + addSeparator(); + } + + if (featureIsEnabled(Feature::Playlist)) { + addMenu(m_pPlaylistMenu); + } + + if (featureIsEnabled(Feature::Crate)) { + addMenu(m_pCrateMenu); + } + + if (featureIsEnabled(Feature::Remove)) { + if (m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE)) { + addAction(m_pRemoveAct); + } + if (m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_PLAYLIST)) { + addAction(m_pRemovePlaylistAct); + } + if (m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_CRATE)) { + addAction(m_pRemoveCrateAct); + } + } + + addSeparator(); + + if (featureIsEnabled(Feature::Metadata)) { + m_pMetadataMenu->addAction(m_pImportMetadataFromFileAct); + m_pMetadataMenu->addAction(m_pImportMetadataFromMusicBrainzAct); + m_pMetadataMenu->addAction(m_pExportMetadataAct); + + for (const auto& updateInExternalTrackCollection : m_updateInExternalTrackCollections) { + ExternalTrackCollection* externalTrackCollection = + updateInExternalTrackCollection.externalTrackCollection; + if (externalTrackCollection) { + updateInExternalTrackCollection.action->setEnabled( + externalTrackCollection->isConnected()); + m_pMetadataUpdateExternalCollectionsMenu->addAction( + updateInExternalTrackCollection.action); + } + } + if (!m_pMetadataUpdateExternalCollectionsMenu->isEmpty()) { + m_pMetadataMenu->addMenu(m_pMetadataUpdateExternalCollectionsMenu); + } + + m_pMetadataMenu->addMenu(m_pCoverMenu); + addMenu(m_pMetadataMenu); + } + + if (featureIsEnabled(Feature::Reset)) { + m_pClearMetadataMenu->addAction(m_pClearBeatsAction); + m_pClearMetadataMenu->addAction(m_pClearPlayCountAction); + // FIXME: Why is clearing the loop not working? + m_pClearMetadataMenu->addAction(m_pClearMainCueAction); + m_pClearMetadataMenu->addAction(m_pClearHotCuesAction); + m_pClearMetadataMenu->addAction(m_pClearIntroCueAction); + m_pClearMetadataMenu->addAction(m_pClearOutroCueAction); + //m_pClearMetadataMenu->addAction(m_pClearLoopAction); + m_pClearMetadataMenu->addAction(m_pClearKeyAction); + m_pClearMetadataMenu->addAction(m_pClearReplayGainAction); + m_pClearMetadataMenu->addAction(m_pClearWaveformAction); + m_pClearMetadataMenu->addSeparator(); + m_pClearMetadataMenu->addAction(m_pClearAllMetadataAction); + addMenu(m_pClearMetadataMenu); + } + + if (featureIsEnabled(Feature::BPM)) { + m_pBPMMenu->addAction(m_pBpmDoubleAction); + m_pBPMMenu->addAction(m_pBpmHalveAction); + m_pBPMMenu->addAction(m_pBpmTwoThirdsAction); + m_pBPMMenu->addAction(m_pBpmThreeFourthsAction); + m_pBPMMenu->addAction(m_pBpmFourThirdsAction); + m_pBPMMenu->addAction(m_pBpmThreeHalvesAction); + m_pBPMMenu->addSeparator(); + m_pBPMMenu->addAction(m_pBpmLockAction); + m_pBPMMenu->addAction(m_pBpmUnlockAction); + m_pBPMMenu->addSeparator(); + + addMenu(m_pBPMMenu); + } + + if (featureIsEnabled(Feature::Color)) { + m_pColorMenu->addAction(m_pColorPickerAction); + addMenu(m_pColorMenu); + } + + addSeparator(); + if (featureIsEnabled(Feature::HideUnhidePurge)) { + if (m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_HIDE)) { + addAction(m_pHideAct); + } + if (m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_UNHIDE)) { + addAction(m_pUnhideAct); + } + if (m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_PURGE)) { + addAction(m_pPurgeAct); + } + } + + if (featureIsEnabled(Feature::FileBrowser)) { + addAction(m_pFileBrowserAct); + } + + if (featureIsEnabled(Feature::Properties)) { + addSeparator(); + addAction(m_pPropertiesAct); + } +} + +void WTrackMenu::updateMenus() { + const auto trackIds = getTrackIds(); + const auto trackPointers = getTrackPointers(); + + // Gray out some stuff if multiple songs were selected. + bool oneSongSelected = trackPointers.size() == 1; + + if (featureIsEnabled(Feature::LoadTo)) { + int iNumDecks = m_pNumDecks->get(); + m_pDeckMenu->clear(); + if (iNumDecks > 0) { + for (int i = 1; i <= iNumDecks; ++i) { + // PlayerManager::groupForDeck is 0-indexed. + QString deckGroup = PlayerManager::groupForDeck(i - 1); + bool deckPlaying = ControlObject::get( + ConfigKey(deckGroup, "play")) > 0.0; + bool loadTrackIntoPlayingDeck = m_pConfig->getValue( + ConfigKey("[Controls]", "AllowTrackLoadToPlayingDeck")); + bool deckEnabled = (!deckPlaying || loadTrackIntoPlayingDeck) && oneSongSelected; + QAction* pAction = new QAction(tr("Deck %1").arg(i), this); + pAction->setEnabled(deckEnabled); + m_pDeckMenu->addAction(pAction); + connect(pAction, &QAction::triggered, this, [this, deckGroup] { loadSelectionToGroup(deckGroup); }); + } + } + + int iNumSamplers = m_pNumSamplers->get(); + if (iNumSamplers > 0) { + m_pSamplerMenu->clear(); + for (int i = 1; i <= iNumSamplers; ++i) { + // PlayerManager::groupForSampler is 0-indexed. + QString samplerGroup = PlayerManager::groupForSampler(i - 1); + bool samplerPlaying = ControlObject::get( + ConfigKey(samplerGroup, "play")) > 0.0; + bool samplerEnabled = !samplerPlaying && oneSongSelected; + QAction* pAction = new QAction(tr("Sampler %1").arg(i), m_pSamplerMenu); + pAction->setEnabled(samplerEnabled); + m_pSamplerMenu->addAction(pAction); + connect(pAction, &QAction::triggered, this, [this, samplerGroup] { loadSelectionToGroup(samplerGroup); }); + } + } + } + + if (featureIsEnabled(Feature::Playlist)) { + // Playlist menu is lazy loaded on hover by slotPopulatePlaylistMenu + // to avoid unnecessary database queries + m_bPlaylistMenuLoaded = false; + } + + if (featureIsEnabled(Feature::Crate)) { + // Crate menu is lazy loaded on hover by slotPopulateCrateMenu + // to avoid unnecessary database queries + m_bCrateMenuLoaded = false; + } + + if (featureIsEnabled(Feature::Remove)) { + bool locked = m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_LOCKED); + if (m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE)) { + m_pRemoveAct->setEnabled(!locked); + } + if (m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_PLAYLIST)) { + m_pRemovePlaylistAct->setEnabled(!locked); + } + if (m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_CRATE)) { + m_pRemoveCrateAct->setEnabled(!locked); + } + } + + if (featureIsEnabled(Feature::Metadata)) { + m_pImportMetadataFromMusicBrainzAct->setEnabled(oneSongSelected); + + // We load a single track to get the necessary context for the cover (we use + // last to be consistent with selectionChanged above). + TrackPointer last = trackPointers.last(); + CoverInfo info; + info.source = last->getCoverInfo().source; + info.type = last->getCoverInfo().type; + info.hash = last->getCoverHash(); + info.trackLocation = last->getCoverInfoWithLocation().trackLocation; + info.coverLocation = last->getCoverInfoWithLocation().coverLocation; + m_pCoverMenu->setCoverArt(info); + } + + if (featureIsEnabled(Feature::Reset)) { + bool allowClear = true; + for (int i = 0; i < trackPointers.size() && allowClear; ++i) { + if (trackPointers.at(0)->isBpmLocked()) { + allowClear = false; + } + } + m_pClearBeatsAction->setEnabled(allowClear); + } + + if (featureIsEnabled(Feature::BPM)) { + bool anyLocked = false; //true if any of the selected items are locked + for (int i = 0; i < trackPointers.size() && !anyLocked; ++i) { + if (trackPointers.at(i)->isBpmLocked()) { + anyLocked = true; + } + } + if (anyLocked) { + m_pBpmUnlockAction->setEnabled(true); + m_pBpmLockAction->setEnabled(false); + m_pBpmDoubleAction->setEnabled(false); + m_pBpmHalveAction->setEnabled(false); + m_pBpmTwoThirdsAction->setEnabled(false); + m_pBpmThreeFourthsAction->setEnabled(false); + m_pBpmFourThirdsAction->setEnabled(false); + m_pBpmThreeHalvesAction->setEnabled(false); + } else { + m_pBpmUnlockAction->setEnabled(false); + m_pBpmLockAction->setEnabled(true); + m_pBpmDoubleAction->setEnabled(true); + m_pBpmHalveAction->setEnabled(true); + m_pBpmTwoThirdsAction->setEnabled(true); + m_pBpmThreeFourthsAction->setEnabled(true); + m_pBpmFourThirdsAction->setEnabled(true); + m_pBpmThreeHalvesAction->setEnabled(true); + } + } + + if (featureIsEnabled(Feature::Color)) { + m_pColorPickerAction->setColorPalette( + ColorPaletteSettings(m_pConfig).getTrackColorPalette()); + + // Get color of first selected track + TrackPointer pTrack = trackPointers.at(0); + auto trackColor = pTrack->getColor(); + + // Check if all other selected tracks have the same color + bool multipleTrackColors = false; + for (int i = 1; i < trackPointers.size(); ++i) { + if (trackColor != trackPointers.at(i)->getColor()) { + trackColor = mixxx::RgbColor::nullopt(); + multipleTrackColors = true; + break; + } + } + + if (multipleTrackColors) { + m_pColorPickerAction->resetSelectedColor(); + } else { + m_pColorPickerAction->setSelectedColor(trackColor); + } + } + + if (featureIsEnabled(Feature::HideUnhidePurge)) { + bool locked = m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_LOCKED); + if (m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_HIDE)) { + m_pHideAct->setEnabled(!locked); + } + if (m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_UNHIDE)) { + m_pUnhideAct->setEnabled(!locked); + } + if (m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_PURGE)) { + m_pPurgeAct->setEnabled(!locked); + } + } + + if (featureIsEnabled(Feature::Properties)) { + m_pPropertiesAct->setEnabled(oneSongSelected); + } +} + +void WTrackMenu::loadTracks(TrackIdList trackIdList) { + // Clean all forms of track store + clearTrackSelection(); + + // This asserts that this function is only accessible when a track model is not set, + // thus maintaining only the TrackPointerList in state and avoiding storing + // duplicate state with TrackIdList and QModelIndexList. + DEBUG_ASSERT(!m_pTrackModel); + + TrackPointerList trackPointers; + trackPointers.reserve(trackIdList.size()); + for (const auto& trackId : trackIdList) { + TrackPointer pTrack = m_pTrackCollectionManager->internalCollection()->getTrackById(trackId); + if (pTrack) { + trackPointers.push_back(pTrack); + } + } + m_pTrackPointerList = trackPointers; + + if (!m_pTrackPointerList.empty()) { + updateMenus(); + } +} + +void WTrackMenu::loadTracks(QModelIndexList indexList) { + // Clean all forms of track store + clearTrackSelection(); + + // This asserts that this function is only accessible when a track model is set, + // thus maintaining only the QModelIndexList in state and avoiding storing + // duplicate state with TrackIdList and TrackPointerList. + VERIFY_OR_DEBUG_ASSERT(m_pTrackModel) { + return; + } + + QModelIndexList indices; + indices.reserve(indexList.size()); + for (const auto& index : indexList) { + TrackPointer pTrack = m_pTrackModel->getTrack(index); + // Checking if passed indexList is valid + if (pTrack) { + indices.push_back(index); + } + } + m_pTrackIndexList = indices; + + if (!m_pTrackIndexList.empty()) { + updateMenus(); + } +} + +void WTrackMenu::loadTrack(TrackId trackId) { + // Create a QList of single track to maintain common functions + // for single and multi track selection. + TrackIdList singleItemTrackIdList; + singleItemTrackIdList.push_back(trackId); + // Use setTrackIds to set a list of single element. + loadTracks(singleItemTrackIdList); +} + +void WTrackMenu::loadTrack(QModelIndex index) { + QModelIndexList singleItemTrackIndexList; + singleItemTrackIndexList.push_back(index); + loadTracks(singleItemTrackIndexList); +} + +TrackIdList WTrackMenu::getTrackIds() const { + TrackIdList trackIds; + if (m_pTrackModel) { + const QModelIndexList indices = getTrackIndices(); + trackIds.reserve(indices.size()); + for (const auto& index : indices) { + trackIds.push_back(m_pTrackModel->getTrackId(index)); + } + } else { + const TrackPointerList trackPointers = getTrackPointers(); + for (const auto& pTrack : trackPointers) { + trackIds.push_back(pTrack->getId()); + } + } + return trackIds; +} + +TrackPointerList WTrackMenu::getTrackPointers() const { + if (m_pTrackModel) { + const QModelIndexList indices = getTrackIndices(); + TrackPointerList trackPointers; + trackPointers.reserve(indices.size()); + for (const auto& index : indices) { + trackPointers.push_back(m_pTrackModel->getTrack(index)); + } + return trackPointers; + } + return m_pTrackPointerList; +} + +QModelIndexList WTrackMenu::getTrackIndices() const { + // Indices are associated with a TrackModel. Can only be obtained + // if a TrackModel is available. + DEBUG_ASSERT(m_pTrackModel); + return m_pTrackIndexList; +} + +void WTrackMenu::slotOpenInFileBrowser() { + TrackPointerList trackPointerList = getTrackPointers(); + QStringList locations; + for (const TrackPointer& trackPointer : trackPointerList) { + locations << trackPointer->getLocation(); + } + mixxx::DesktopHelper::openInFileBrowser(locations); +} + +void WTrackMenu::slotImportTrackMetadataFromFileTags() { + auto trackPointers = getTrackPointers(); + + for (const auto& pTrack : trackPointers) { + if (pTrack) { + // The user has explicitly requested to reload metadata from the file + // to override the information within Mixxx! Custom cover art must be + // reloaded separately. + SoundSourceProxy(pTrack).updateTrackFromSource( + SoundSourceProxy::ImportTrackMetadataMode::Again); + } + } +} + +void WTrackMenu::slotExportTrackMetadataIntoFileTags() { + auto trackPointers = getTrackPointers(); + + mixxx::DlgTrackMetadataExport::showMessageBoxOncePerSession(); + + for (const auto& pTrack : trackPointers) { + if (pTrack) { + // Export of metadata is deferred until all references to the + // corresponding track object have been dropped. Otherwise + // writing to files that are still used for playback might + // cause crashes or at least audible glitches! + mixxx::DlgTrackMetadataExport::showMessageBoxOncePerSession(); + pTrack->markForMetadataExport(); + } + } +} + +void WTrackMenu::slotUpdateExternalTrackCollection( + ExternalTrackCollection* externalTrackCollection) { + VERIFY_OR_DEBUG_ASSERT(externalTrackCollection) { + return; + } + auto trackPointers = getTrackPointers(); + + QList trackRefs; + trackRefs.reserve(trackPointers.size()); + for (const auto& pTrack : trackPointers) { + trackRefs.append( + TrackRef::fromFileInfo( + pTrack->getLocation(), + pTrack->getId())); + } + + externalTrackCollection->updateTracks(std::move(trackRefs)); +} + +void WTrackMenu::slotPopulatePlaylistMenu() { + // The user may open the Playlist submenu, move their cursor away, then + // return to the Playlist submenu before exiting the track context menu. + // Avoid querying the database multiple times in that case. + if (m_bPlaylistMenuLoaded) { + return; + } + m_pPlaylistMenu->clear(); + PlaylistDAO& playlistDao = m_pTrackCollectionManager->internalCollection()->getPlaylistDAO(); + QMap playlists; + int numPlaylists = playlistDao.playlistCount(); + for (int i = 0; i < numPlaylists; ++i) { + int iPlaylistId = playlistDao.getPlaylistId(i); + playlists.insert(playlistDao.getPlaylistName(iPlaylistId), iPlaylistId); + } + QMapIterator it(playlists); + while (it.hasNext()) { + it.next(); + if (!playlistDao.isHidden(it.value())) { + // No leak because making the menu the parent means they will be + // auto-deleted + auto pAction = new QAction(it.key(), m_pPlaylistMenu); + bool locked = playlistDao.isPlaylistLocked(it.value()); + pAction->setEnabled(!locked); + m_pPlaylistMenu->addAction(pAction); + int iPlaylistId = it.value(); + connect(pAction, &QAction::triggered, this, [this, iPlaylistId] { addSelectionToPlaylist(iPlaylistId); }); + } + } + m_pPlaylistMenu->addSeparator(); + QAction* newPlaylistAction = new QAction(tr("Create New Playlist"), m_pPlaylistMenu); + m_pPlaylistMenu->addAction(newPlaylistAction); + connect(newPlaylistAction, &QAction::triggered, this, [this] { addSelectionToPlaylist(-1); }); + m_bPlaylistMenuLoaded = true; +} + +void WTrackMenu::addSelectionToPlaylist(int iPlaylistId) { + const TrackIdList trackIds = getTrackIds(); + if (trackIds.isEmpty()) { + qWarning() << "No tracks selected for playlist"; + return; + } + + PlaylistDAO& playlistDao = m_pTrackCollectionManager->internalCollection()->getPlaylistDAO(); + + if (iPlaylistId == -1) { // i.e. a new playlist is suppose to be created + QString name; + bool validNameGiven = false; + + do { + bool ok = false; + name = QInputDialog::getText(nullptr, + tr("Create New Playlist"), + tr("Enter name for new playlist:"), + QLineEdit::Normal, + tr("New Playlist"), + &ok) + .trimmed(); + if (!ok) { + return; + } + if (playlistDao.getPlaylistIdFromName(name) != -1) { + QMessageBox::warning(nullptr, + tr("Playlist Creation Failed"), + tr("A playlist by that name already exists.")); + } else if (name.isEmpty()) { + QMessageBox::warning(nullptr, + tr("Playlist Creation Failed"), + tr("A playlist cannot have a blank name.")); + } else { + validNameGiven = true; + } + } while (!validNameGiven); + iPlaylistId = playlistDao.createPlaylist(name); //-1 is changed to the new playlist ID return from the DAO + if (iPlaylistId == -1) { + QMessageBox::warning(nullptr, + tr("Playlist Creation Failed"), + tr("An unknown error occurred while creating playlist: ") + name); + return; + } + } + + // TODO(XXX): Care whether the append succeeded. + m_pTrackCollectionManager->unhideTracks(trackIds); + playlistDao.appendTracksToPlaylist(trackIds, iPlaylistId); +} + +void WTrackMenu::slotPopulateCrateMenu() { + // The user may open the Crate submenu, move their cursor away, then + // return to the Crate submenu before exiting the track context menu. + // Avoid querying the database multiple times in that case. + if (m_bCrateMenuLoaded) { + return; + } + m_pCrateMenu->clear(); + const TrackIdList trackIds = getTrackIds(); + + CrateSummarySelectResult allCrates(m_pTrackCollectionManager->internalCollection()->crates().selectCratesWithTrackCount(trackIds)); + + CrateSummary crate; + while (allCrates.populateNext(&crate)) { + auto pAction = make_parented(m_pCrateMenu); + auto pCheckBox = make_parented(m_pCrateMenu); + + pCheckBox->setText(crate.getName()); + pCheckBox->setProperty("crateId", + QVariant::fromValue(crate.getId())); + pCheckBox->setEnabled(!crate.isLocked()); + // Strangely, the normal styling of QActions does not automatically + // apply to QWidgetActions. The :selected pseudo-state unfortunately + // does not work with QWidgetAction. :hover works for selecting items + // with the mouse, but not with the keyboard. :focus works for the + // keyboard but with the mouse, the last clicked item keeps the style + // after the mouse cursor is moved to hover over another item. + + // ronso0 Disabling this stylesheet allows to override the OS style + // of the :hover and :focus state. + // pCheckBox->setStyleSheet( + // QString("QCheckBox {color: %1;}").arg( + // pCheckBox->palette().text().color().name()) + "\n" + + // QString("QCheckBox:hover {background-color: %1;}").arg( + // pCheckBox->palette().highlight().color().name())); + pAction->setEnabled(!crate.isLocked()); + pAction->setDefaultWidget(pCheckBox.get()); + + if (crate.getTrackCount() == 0) { + pCheckBox->setChecked(false); + } else if (crate.getTrackCount() == (uint)trackIds.length()) { + pCheckBox->setChecked(true); + } else { + pCheckBox->setTristate(true); + pCheckBox->setCheckState(Qt::PartiallyChecked); + } + + m_pCrateMenu->addAction(pAction.get()); + connect(pAction.get(), &QAction::triggered, this, [this, pCheckBox{pCheckBox.get()}] { updateSelectionCrates(pCheckBox); }); + connect(pCheckBox.get(), &QCheckBox::stateChanged, this, [this, pCheckBox{pCheckBox.get()}] { updateSelectionCrates(pCheckBox); }); + } + m_pCrateMenu->addSeparator(); + QAction* newCrateAction = new QAction(tr("Add to New Crate"), m_pCrateMenu); + m_pCrateMenu->addAction(newCrateAction); + connect(newCrateAction, &QAction::triggered, this, &WTrackMenu::addSelectionToNewCrate); + m_bCrateMenuLoaded = true; +} + +void WTrackMenu::updateSelectionCrates(QWidget* pWidget) { + auto pCheckBox = qobject_cast(pWidget); + VERIFY_OR_DEBUG_ASSERT(pCheckBox) { + qWarning() << "crateId is not of CrateId type"; + return; + } + CrateId crateId = pCheckBox->property("crateId").value(); + + const TrackIdList trackIds = getTrackIds(); + + if (trackIds.isEmpty()) { + qWarning() << "No tracks selected for crate"; + return; + } + + // we need to disable tristate again as the mixed state will now be gone and can't be brought back + pCheckBox->setTristate(false); + if (!pCheckBox->isChecked()) { + if (crateId.isValid()) { + m_pTrackCollectionManager->internalCollection()->removeCrateTracks(crateId, trackIds); + } + } else { + if (!crateId.isValid()) { // i.e. a new crate is suppose to be created + crateId = CrateFeatureHelper( + m_pTrackCollectionManager->internalCollection(), m_pConfig) + .createEmptyCrate(); + } + if (crateId.isValid()) { + m_pTrackCollectionManager->unhideTracks(trackIds); + m_pTrackCollectionManager->internalCollection()->addCrateTracks(crateId, trackIds); + } + } +} + +void WTrackMenu::addSelectionToNewCrate() { + const TrackIdList trackIds = getTrackIds(); + + if (trackIds.isEmpty()) { + qWarning() << "No tracks selected for crate"; + return; + } + + CrateId crateId = CrateFeatureHelper( + m_pTrackCollectionManager->internalCollection(), m_pConfig) + .createEmptyCrate(); + + if (crateId.isValid()) { + m_pTrackCollectionManager->unhideTracks(trackIds); + m_pTrackCollectionManager->internalCollection()->addCrateTracks(crateId, trackIds); + } +} + +void WTrackMenu::slotLockBpm() { + lockBpm(true); +} + +void WTrackMenu::slotUnlockBpm() { + lockBpm(false); +} + +void WTrackMenu::slotScaleBpm(int scale) { + const TrackPointerList trackPointers = getTrackPointers(); + for (const auto& pTrack : trackPointers) { + if (!pTrack->isBpmLocked()) { + BeatsPointer pBeats = pTrack->getBeats(); + if (pBeats) { + pBeats->scale(static_cast(scale)); + } + } + } +} + +void WTrackMenu::lockBpm(bool lock) { + const TrackPointerList trackPointers = getTrackPointers(); + // TODO: This should be done in a thread for large selections + for (const auto& pTrack : trackPointers) { + pTrack->setBpmLocked(lock); + } +} + +void WTrackMenu::slotColorPicked(mixxx::RgbColor::optional_t color) { + const TrackPointerList trackPointers = getTrackPointers(); + // TODO: This should be done in a thread for large selections + for (const auto& pTrack : trackPointers) { + pTrack->setColor(color); + } + + hide(); +} + +void WTrackMenu::loadSelectionToGroup(QString group, bool play) { + const TrackPointerList trackPointers = getTrackPointers(); + if (trackPointers.empty()) { + return; + } + // If the track load override is disabled, check to see if a track is + // playing before trying to load it + if (!(m_pConfig->getValueString( + ConfigKey("[Controls]", "AllowTrackLoadToPlayingDeck")) + .toInt())) { + // TODO(XXX): Check for other than just the first preview deck. + if (group != "[PreviewDeck1]" && + ControlObject::get(ConfigKey(group, "play")) > 0.0) { + return; + } + } + + TrackPointer pTrack = trackPointers.at(0); + if (pTrack) { + // TODO: load track from this class without depending on + // external slot to load track + emit loadTrackToPlayer(pTrack, group, play); + } +} + +//slot for reset played count, sets count to 0 of one or more tracks +void WTrackMenu::slotClearPlayCount() { + const TrackPointerList trackPointers = getTrackPointers(); + for (const auto& pTrack : trackPointers) { + pTrack->resetPlayCounter(); + } +} + +void WTrackMenu::slotClearBeats() { + const TrackPointerList trackPointers = getTrackPointers(); + // TODO: This should be done in a thread for large selections + for (const auto& pTrack : trackPointers) { + if (!pTrack->isBpmLocked()) { + pTrack->setBeats(BeatsPointer()); + } + } +} + +void WTrackMenu::slotClearMainCue() { + const TrackPointerList trackPointers = getTrackPointers(); + for (const auto& pTrack : trackPointers) { + pTrack->removeCuesOfType(mixxx::CueType::MainCue); + } +} + +void WTrackMenu::slotClearOutroCue() { + const TrackPointerList trackPointers = getTrackPointers(); + for (const auto& pTrack : trackPointers) { + pTrack->removeCuesOfType(mixxx::CueType::Outro); + } +} + +void WTrackMenu::slotClearIntroCue() { + const TrackPointerList trackPointers = getTrackPointers(); + for (const auto& pTrack : trackPointers) { + pTrack->removeCuesOfType(mixxx::CueType::Intro); + } +} + +void WTrackMenu::slotClearKey() { + const TrackPointerList trackPointers = getTrackPointers(); + for (const auto& pTrack : trackPointers) { + pTrack->resetKeys(); + } +} + +void WTrackMenu::slotClearReplayGain() { + const TrackPointerList trackPointers = getTrackPointers(); + for (const auto& pTrack : trackPointers) { + pTrack->setReplayGain(mixxx::ReplayGain()); + } +} + +void WTrackMenu::slotClearWaveform() { + const TrackPointerList trackPointers = getTrackPointers(); + AnalysisDao& analysisDao = m_pTrackCollectionManager->internalCollection()->getAnalysisDAO(); + for (const auto& pTrack : trackPointers) { + analysisDao.deleteAnalysesForTrack(pTrack->getId()); + pTrack->setWaveform(WaveformPointer()); + pTrack->setWaveformSummary(WaveformPointer()); + } +} + +void WTrackMenu::slotClearLoop() { + const TrackPointerList trackPointers = getTrackPointers(); + for (const auto& pTrack : trackPointers) { + pTrack->removeCuesOfType(mixxx::CueType::Loop); + } +} + +void WTrackMenu::slotClearHotCues() { + const TrackPointerList trackPointers = getTrackPointers(); + for (const auto& pTrack : trackPointers) { + pTrack->removeCuesOfType(mixxx::CueType::HotCue); + } +} + +void WTrackMenu::slotClearAllMetadata() { + slotClearBeats(); + slotClearPlayCount(); + slotClearMainCue(); + slotClearHotCues(); + slotClearIntroCue(); + slotClearOutroCue(); + slotClearLoop(); + slotClearKey(); + slotClearReplayGain(); + slotClearWaveform(); +} + +void WTrackMenu::slotShowTrackInfo() { + const auto trackPointers = getTrackPointers(); + if (trackPointers.empty()) { + return; + } + if (m_pTrackModel) { + const auto indices = getTrackIndices(); + m_pTrackInfo->loadTrack(indices.at(0)); + } else { + m_pTrackInfo->loadTrack(trackPointers.at(0)); + } + m_pTrackInfo->show(); +} + +void WTrackMenu::slotShowDlgTagFetcher() { + const auto trackPointers = getTrackPointers(); + if (trackPointers.empty()) { + return; + } + if (m_pTrackModel) { + const auto indices = getTrackIndices(); + m_pTagFetcher->loadTrack(indices.at(0)); + } else { + m_pTagFetcher->loadTrack(trackPointers.at(0)); + } + m_pTagFetcher->show(); +} + +void WTrackMenu::slotAddToAutoDJBottom() { + // append to auto DJ + addToAutoDJ(PlaylistDAO::AutoDJSendLoc::BOTTOM); +} + +void WTrackMenu::slotAddToAutoDJTop() { + addToAutoDJ(PlaylistDAO::AutoDJSendLoc::TOP); +} + +void WTrackMenu::slotAddToAutoDJReplace() { + addToAutoDJ(PlaylistDAO::AutoDJSendLoc::REPLACE); +} + +void WTrackMenu::addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc) { + const TrackIdList trackIds = getTrackIds(); + if (trackIds.empty()) { + qWarning() << "No tracks selected for AutoDJ"; + return; + } + + PlaylistDAO& playlistDao = m_pTrackCollectionManager->internalCollection()->getPlaylistDAO(); + + // TODO(XXX): Care whether the append succeeded. + m_pTrackCollectionManager->unhideTracks(trackIds); + playlistDao.addTracksToAutoDJQueue(trackIds, loc); +} + +void WTrackMenu::slotCoverInfoSelected(const CoverInfoRelative& coverInfo) { + const TrackPointerList trackPointers = getTrackPointers(); + for (const auto& pTrack : trackPointers) { + pTrack->setCoverInfo(coverInfo); + } +} + +void WTrackMenu::slotReloadCoverArt() { + const TrackPointerList trackPointers = getTrackPointers(); + if (!trackPointers.empty()) { + guessTrackCoverInfoConcurrently(trackPointers); + } +} + +void WTrackMenu::slotRemove() { + if (m_pTrackModel) { + const QModelIndexList indices = getTrackIndices(); + if (!indices.empty()) { + m_pTrackModel->removeTracks(indices); + } + } +} + +void WTrackMenu::slotHide() { + if (m_pTrackModel) { + const QModelIndexList indices = getTrackIndices(); + if (!indices.empty()) { + m_pTrackModel->hideTracks(indices); + } + } +} + +void WTrackMenu::slotUnhide() { + if (m_pTrackModel) { + const QModelIndexList indices = getTrackIndices(); + if (!indices.empty()) { + m_pTrackModel->unhideTracks(indices); + } + } +} + +void WTrackMenu::slotPurge() { + if (m_pTrackModel) { + const QModelIndexList indices = getTrackIndices(); + if (!indices.empty()) { + m_pTrackModel->purgeTracks(indices); + } + } +} + +void WTrackMenu::clearTrackSelection() { + m_pTrackPointerList.clear(); + m_pTrackIndexList.clear(); +} + +bool WTrackMenu::featureIsEnabled(Feature flag) const { + bool optionIsSelected = m_eActiveFeatures.testFlag(flag); + if (!optionIsSelected) { + return false; + } + + if (m_pTrackModel) { + if (flag == Feature::AutoDJ) { + return m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ); + } else if (flag == Feature::LoadTo) { + return m_pTrackModel->hasCapabilities( + TrackModel::TRACKMODELCAPS_LOADTODECK) || + m_pTrackModel->hasCapabilities( + TrackModel::TRACKMODELCAPS_LOADTOSAMPLER) || + m_pTrackModel->hasCapabilities( + TrackModel::TRACKMODELCAPS_LOADTOPREVIEWDECK); + } else if (flag == Feature::Playlist) { + return m_pTrackModel->hasCapabilities( + TrackModel::TRACKMODELCAPS_ADDTOPLAYLIST); + } else if (flag == Feature::Crate) { + return m_pTrackModel->hasCapabilities( + TrackModel::TRACKMODELCAPS_ADDTOCRATE); + } else if (flag == Feature::Remove) { + return m_pTrackModel->hasCapabilities( + TrackModel::TRACKMODELCAPS_REMOVE) || + m_pTrackModel->hasCapabilities( + TrackModel::TRACKMODELCAPS_REMOVE_PLAYLIST) || + m_pTrackModel->hasCapabilities( + TrackModel::TRACKMODELCAPS_REMOVE_CRATE); + } else if (flag == Feature::Metadata) { + return m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA); + } else if (flag == Feature::Reset) { + return m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA) && + m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_RESETPLAYED); + } else if (flag == Feature::BPM) { + return m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA); + } else if (flag == Feature::Color) { + return m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA); + } else if (flag == Feature::HideUnhidePurge) { + return m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_HIDE) || + m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_UNHIDE) || + m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_PURGE); + } else if (flag == Feature::Properties) { + return m_pTrackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA); + } else { + return true; + } + } + + return !m_eTrackModelFeatures.testFlag(flag); +} diff --git a/src/widget/wtrackmenu.h b/src/widget/wtrackmenu.h new file mode 100644 index 00000000000..baeeb2d4668 --- /dev/null +++ b/src/widget/wtrackmenu.h @@ -0,0 +1,244 @@ +#pragma once + +#include + +#include "library/dao/playlistdao.h" +#include "library/dlgtagfetcher.h" +#include "library/dlgtrackinfo.h" +#include "library/trackmodel.h" + +class ControlProxy; +class DlgTagFetcher; +class DlgTrackInfo; +class ExternalTrackCollection; +class QAction; +class QWidget; +class TrackCollectionManager; +class WColorPickerAction; +class WCoverArtMenu; + +/// A context menu for track(s). +/// Can be used with individual track type widgets based on TrackPointer +/// or list/table type track widgets based on QModelIndexList and TrackModel. +/// Desired menu features can be selected by passing Feature enum flags +/// in constructor. +class WTrackMenu : public QMenu { + Q_OBJECT + public: + enum Feature { + AutoDJ = 1, + // The loadTrackToPlayer signal emitted from this class must be handled to make LoadTo work. + LoadTo = 1 << 1, + Playlist = 1 << 2, + Crate = 1 << 3, + Remove = 1 << 4, + Metadata = 1 << 5, + Reset = 1 << 6, + BPM = 1 << 7, + Color = 1 << 8, + HideUnhidePurge = 1 << 9, + FileBrowser = 1 << 10, + Properties = 1 << 11, + TrackModelFeatures = Remove | HideUnhidePurge, + All = AutoDJ | LoadTo | Playlist | Crate | Remove | Metadata | Reset | + BPM | Color | HideUnhidePurge | FileBrowser | Properties + }; + Q_DECLARE_FLAGS(Features, Feature) + + WTrackMenu(QWidget* parent, + UserSettingsPointer pConfig, + TrackCollectionManager* pTrackCollectionManager, + Features flags = Feature::All, + TrackModel* trackModel = nullptr); + + ~WTrackMenu() override = default; + + void loadTrack(TrackId trackId); + void loadTrack(QModelIndex index); + void loadTracks(TrackIdList trackList); + void loadTracks(QModelIndexList indexList); + + // WARNING: This function hides non-virtual QMenu::popup(). + // This has been done on purpose to ensure menu doesn't popup without loaded track(s). + void popup(const QPoint& pos, QAction* at = nullptr); + + signals: + void loadTrackToPlayer(TrackPointer pTrack, QString group, bool play = false); + + private slots: + // File + void slotOpenInFileBrowser(); + + // Row color + void slotColorPicked(mixxx::RgbColor::optional_t color); + + // Reset + void slotClearBeats(); + void slotClearPlayCount(); + void slotClearMainCue(); + void slotClearHotCues(); + void slotClearIntroCue(); + void slotClearOutroCue(); + void slotClearLoop(); + void slotClearKey(); + void slotClearReplayGain(); + void slotClearWaveform(); + void slotClearAllMetadata(); + + // BPM + void slotLockBpm(); + void slotUnlockBpm(); + void slotScaleBpm(int); + + // Info and metadata + void slotShowTrackInfo(); + void slotShowDlgTagFetcher(); + void slotImportTrackMetadataFromFileTags(); + void slotExportTrackMetadataIntoFileTags(); + void slotUpdateExternalTrackCollection(ExternalTrackCollection* externalTrackCollection); + + // Playlist and crate + void slotPopulatePlaylistMenu(); + void slotPopulateCrateMenu(); + void addSelectionToNewCrate(); + + // Auto DJ + void slotAddToAutoDJBottom(); + void slotAddToAutoDJTop(); + void slotAddToAutoDJReplace(); + + // Cover + void slotCoverInfoSelected(const CoverInfoRelative& coverInfo); + void slotReloadCoverArt(); + + // Library management + void slotRemove(); + void slotHide(); + void slotUnhide(); + void slotPurge(); + + private: + // These getters make sure the required lists are + // derived from a single source in state, which is the + // m_pTrackIndexList if m_pTrackModel is set and + // m_pTrackPointerList if m_pTrackModel is not set. + TrackIdList getTrackIds() const; + TrackPointerList getTrackPointers() const; + QModelIndexList getTrackIndices() const; + + void createMenus(); + void createActions(); + void setupActions(); + void updateMenus(); + + bool featureIsEnabled(Feature flag) const; + + void addSelectionToPlaylist(int iPlaylistId); + void updateSelectionCrates(QWidget* pWidget); + + void addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc); + + void lockBpm(bool lock); + + void loadSelectionToGroup(QString group, bool play = false); + void clearTrackSelection(); + + // Source of track list when TrackModel is not set. + TrackPointerList m_pTrackPointerList; + // Source of track list when TrackModel is set. + QModelIndexList m_pTrackIndexList; + + TrackModel* m_pTrackModel{}; + + const ControlProxy* m_pNumSamplers{}; + const ControlProxy* m_pNumDecks{}; + const ControlProxy* m_pNumPreviewDecks{}; + + // Submenus + QMenu* m_pLoadToMenu{}; + QMenu* m_pDeckMenu{}; + QMenu* m_pSamplerMenu{}; + QMenu* m_pPlaylistMenu{}; + QMenu* m_pCrateMenu{}; + QMenu* m_pMetadataMenu{}; + QMenu* m_pMetadataUpdateExternalCollectionsMenu{}; + QMenu* m_pClearMetadataMenu{}; + QMenu* m_pBPMMenu{}; + QMenu* m_pColorMenu{}; + WCoverArtMenu* m_pCoverMenu{}; + + // Reload Track Metadata Action: + QAction* m_pImportMetadataFromFileAct{}; + QAction* m_pImportMetadataFromMusicBrainzAct{}; + + // Save Track Metadata Action: + QAction* m_pExportMetadataAct{}; + + // Load Track to PreviewDeck + QAction* m_pAddToPreviewDeck{}; + + // Send to Auto-DJ Action + QAction* m_pAutoDJBottomAct{}; + QAction* m_pAutoDJTopAct{}; + QAction* m_pAutoDJReplaceAct{}; + + // Remove from table + QAction* m_pRemoveAct{}; + QAction* m_pRemovePlaylistAct{}; + QAction* m_pRemoveCrateAct{}; + QAction* m_pHideAct{}; + QAction* m_pUnhideAct{}; + QAction* m_pPurgeAct{}; + + // Show track-editor action + QAction* m_pPropertiesAct{}; + + // Open file in default file browser + QAction* m_pFileBrowserAct{}; + + // BPM feature + QAction* m_pBpmLockAction{}; + QAction* m_pBpmUnlockAction{}; + QAction* m_pBpmDoubleAction{}; + QAction* m_pBpmHalveAction{}; + QAction* m_pBpmTwoThirdsAction{}; + QAction* m_pBpmThreeFourthsAction{}; + QAction* m_pBpmFourThirdsAction{}; + QAction* m_pBpmThreeHalvesAction{}; + + // Track color + WColorPickerAction* m_pColorPickerAction{}; + + // Clear track metadata actions + QAction* m_pClearBeatsAction{}; + QAction* m_pClearPlayCountAction{}; + QAction* m_pClearMainCueAction{}; + QAction* m_pClearHotCuesAction{}; + QAction* m_pClearIntroCueAction{}; + QAction* m_pClearOutroCueAction{}; + QAction* m_pClearLoopAction{}; + QAction* m_pClearWaveformAction{}; + QAction* m_pClearKeyAction{}; + QAction* m_pClearReplayGainAction{}; + QAction* m_pClearAllMetadataAction{}; + + const UserSettingsPointer m_pConfig; + TrackCollectionManager* const m_pTrackCollectionManager; + + QScopedPointer m_pTrackInfo; + QScopedPointer m_pTagFetcher; + + struct UpdateExternalTrackCollection { + QPointer externalTrackCollection; + QAction* action{}; + }; + QList m_updateInExternalTrackCollections; + + bool m_bPlaylistMenuLoaded; + bool m_bCrateMenuLoaded; + + Features m_eActiveFeatures; + const Features m_eTrackModelFeatures; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(WTrackMenu::Features) diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index 419ac95025c..b657b5db196 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -1,4 +1,3 @@ - #include #include @@ -6,12 +5,27 @@ #include "widget/wtrackproperty.h" #include "util/dnd.h" -WTrackProperty::WTrackProperty(const char* group, - UserSettingsPointer pConfig, - QWidget* pParent) +namespace { +const WTrackMenu::Features trackMenuFeatures = + WTrackMenu::Feature::Playlist | + WTrackMenu::Feature::Crate | + WTrackMenu::Feature::Metadata | + WTrackMenu::Feature::Reset | + WTrackMenu::Feature::BPM | + WTrackMenu::Feature::Color | + WTrackMenu::Feature::FileBrowser | + WTrackMenu::Feature::Properties; +} + +WTrackProperty::WTrackProperty(QWidget* pParent, + UserSettingsPointer pConfig, + TrackCollectionManager* pTrackCollectionManager, + const char* group) : WLabel(pParent), m_pGroup(group), - m_pConfig(pConfig) { + m_pConfig(pConfig), + m_pTrackMenu(make_parented( + this, pConfig, pTrackCollectionManager, trackMenuFeatures)) { setAcceptDrops(true); } @@ -71,3 +85,11 @@ void WTrackProperty::dragEnterEvent(QDragEnterEvent *event) { void WTrackProperty::dropEvent(QDropEvent *event) { DragAndDropHelper::handleTrackDropEvent(event, *this, m_pGroup, m_pConfig); } + +void WTrackProperty::contextMenuEvent(QContextMenuEvent *event) { + if (m_pCurrentTrack) { + m_pTrackMenu->loadTrack(m_pCurrentTrack->getId()); + // Create the right-click menu + m_pTrackMenu->popup(event->globalPos()); + } +} diff --git a/src/widget/wtrackproperty.h b/src/widget/wtrackproperty.h index 8b35fc52a8c..47b7f9ce232 100644 --- a/src/widget/wtrackproperty.h +++ b/src/widget/wtrackproperty.h @@ -8,17 +8,25 @@ #include "preferences/usersettings.h" #include "skin/skincontext.h" #include "track/track.h" +#include "util/parented_ptr.h" #include "widget/trackdroptarget.h" #include "widget/wlabel.h" +#include "widget/wtrackmenu.h" class WTrackProperty : public WLabel, public TrackDropTarget { Q_OBJECT public: - WTrackProperty(const char* group, UserSettingsPointer pConfig, QWidget* pParent); + WTrackProperty( + QWidget* pParent, + UserSettingsPointer pConfig, + TrackCollectionManager* pTrackCollectionManager, + const char* group); + + ~WTrackProperty() override = default; void setup(const QDomNode& node, const SkinContext& context) override; - signals: +signals: void trackDropped(QString filename, QString group) override; void cloneDeck(QString source_group, QString target_group) override; @@ -28,6 +36,7 @@ class WTrackProperty : public WLabel, public TrackDropTarget { private slots: void slotTrackChanged(TrackId); + void contextMenuEvent(QContextMenuEvent* event) override; private: void dragEnterEvent(QDragEnterEvent *event) override; @@ -37,9 +46,11 @@ class WTrackProperty : public WLabel, public TrackDropTarget { void updateLabel(); const char* m_pGroup; - UserSettingsPointer m_pConfig; + const UserSettingsPointer m_pConfig; TrackPointer m_pCurrentTrack; QString m_property; + + const parented_ptr m_pTrackMenu; }; diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 04c692f1b9b..90fded6833c 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -1,124 +1,45 @@ #include "widget/wtracktableview.h" -#include -#include #include -#include #include #include #include #include #include -#include #include "control/controlobject.h" -#include "control/controlproxy.h" -#include "library/coverartutils.h" -#include "library/crate/cratefeaturehelper.h" #include "library/dao/trackschema.h" -#include "library/dlgtagfetcher.h" -#include "library/dlgtrackinfo.h" -#include "library/dlgtrackmetadataexport.h" -#include "library/externaltrackcollection.h" #include "library/librarytablemodel.h" #include "library/trackcollection.h" #include "library/trackcollectionmanager.h" #include "mixer/playermanager.h" -#include "preferences/colorpalettesettings.h" #include "preferences/dialog/dlgpreflibrary.h" #include "sources/soundsourceproxy.h" #include "track/track.h" #include "track/trackref.h" #include "util/assert.h" -#include "util/desktophelper.h" #include "util/dnd.h" -#include "util/parented_ptr.h" #include "util/time.h" -#include "waveform/guitick.h" -#include "widget/wcolorpickeraction.h" -#include "widget/wcoverartmenu.h" -#include "widget/wskincolor.h" #include "widget/wtracktableviewheader.h" -#include "widget/wwidget.h" WTrackTableView::WTrackTableView(QWidget* parent, - UserSettingsPointer pConfig, - TrackCollectionManager* pTrackCollectionManager, - double backgroundColorOpacity, - bool sorting) - : WLibraryTableView(parent, pConfig, - ConfigKey(LIBRARY_CONFIGVALUE, - WTRACKTABLEVIEW_VSCROLLBARPOS_KEY)), + UserSettingsPointer pConfig, + TrackCollectionManager* pTrackCollectionManager, + double backgroundColorOpacity, + bool sorting) + : WLibraryTableView(parent, + pConfig, + ConfigKey(LIBRARY_CONFIGVALUE, + WTRACKTABLEVIEW_VSCROLLBARPOS_KEY)), m_pConfig(pConfig), m_pTrackCollectionManager(pTrackCollectionManager), m_backgroundColorOpacity(backgroundColorOpacity), m_sorting(sorting), - m_iCoverSourceColumn(-1), - m_iCoverTypeColumn(-1), - m_iCoverLocationColumn(-1), - m_iCoverHashColumn(-1), - m_iCoverColumn(-1), m_selectionChangedSinceLastGuiTick(true), - m_loadCachedOnly(false), - m_bPlaylistMenuLoaded(false), - m_bCrateMenuLoaded(false) { - - m_pNumSamplers = new ControlProxy( - "[Master]", "num_samplers", this); - m_pNumDecks = new ControlProxy( - "[Master]", "num_decks", this); - m_pNumPreviewDecks = new ControlProxy( - "[Master]", "num_preview_decks", this); - - m_pMenu = new QMenu(this); - - m_pLoadToMenu = new QMenu(this); - m_pLoadToMenu->setTitle(tr("Load to")); - m_pDeckMenu = new QMenu(this); - m_pDeckMenu->setTitle(tr("Deck")); - m_pSamplerMenu = new QMenu(this); - m_pSamplerMenu->setTitle(tr("Sampler")); - - m_pPlaylistMenu = new QMenu(this); - m_pPlaylistMenu->setTitle(tr("Add to Playlist")); - connect(m_pPlaylistMenu, SIGNAL(aboutToShow()), - this, SLOT(slotPopulatePlaylistMenu())); - m_pCrateMenu = new QMenu(this); - m_pCrateMenu->setTitle(tr("Crates")); - connect(m_pCrateMenu, SIGNAL(aboutToShow()), - this, SLOT(slotPopulateCrateMenu())); - - m_pMetadataMenu = new QMenu(this); - m_pMetadataMenu->setTitle(tr("Metadata")); - - m_pMetadataUpdateExternalCollectionsMenu = new QMenu(this); - m_pMetadataUpdateExternalCollectionsMenu->setTitle(tr("Update external collections")); - - m_pBPMMenu = new QMenu(this); - m_pBPMMenu->setTitle(tr("Adjust BPM")); - - m_pColorMenu = new QMenu(this); - m_pColorMenu->setTitle(tr("Select Color")); - - m_pClearMetadataMenu = new QMenu(this); - //: Reset metadata in right click track context menu in library - m_pClearMetadataMenu->setTitle(tr("Reset")); - - m_pCoverMenu = new WCoverArtMenu(this); - m_pCoverMenu->setTitle(tr("Cover Art")); - - connect(m_pCoverMenu, SIGNAL(coverInfoSelected(const CoverInfoRelative&)), - this, SLOT(slotCoverInfoSelected(const CoverInfoRelative&))); - connect(m_pCoverMenu, SIGNAL(reloadCoverArt()), - this, SLOT(slotReloadCoverArt())); - - // Create all the context m_pMenu->actions (stuff that shows up when you - // right-click) - createActions(); - + m_loadCachedOnly(false) { // Connect slots and signals to make the world go 'round. - connect(this, SIGNAL(doubleClicked(const QModelIndex &)), - this, SLOT(slotMouseDoubleClicked(const QModelIndex &))); + connect(this, &WTrackTableView::doubleClicked, + this, &WTrackTableView::slotMouseDoubleClicked); m_pCOTGuiTick = new ControlProxy("[Master]", "guiTick50ms", this); m_pCOTGuiTick->connectValueChanged(this, &WTrackTableView::slotGuiTick50ms); @@ -131,13 +52,13 @@ WTrackTableView::WTrackTableView(QWidget* parent, m_pSortOrder = new ControlProxy("[Library]", "sort_order", this); m_pSortOrder->connectValueChanged(this, &WTrackTableView::applySortingIfVisible); - connect(this, SIGNAL(scrollValueChanged(int)), - this, SLOT(slotScrollValueChanged(int))); + connect(this, &WTrackTableView::scrollValueChanged, + this, &WTrackTableView::slotScrollValueChanged); QShortcut *setFocusShortcut = new QShortcut( QKeySequence(tr("ESC", "Focus")), this); - connect(setFocusShortcut, SIGNAL(activated()), - this, SLOT(setFocus())); + connect(setFocusShortcut, &QShortcut::activated, + this, qOverload<>(&WTrackTableView::setFocus)); } WTrackTableView::~WTrackTableView() { @@ -146,51 +67,6 @@ WTrackTableView::~WTrackTableView() { if (pHeader) { pHeader->saveHeaderState(); } - - delete m_pImportMetadataFromFileAct; - delete m_pImportMetadataFromMusicBrainzAct; - delete m_pExportMetadataAct; - delete m_pAddToPreviewDeck; - delete m_pAutoDJBottomAct; - delete m_pAutoDJTopAct; - delete m_pAutoDJReplaceAct; - delete m_pRemoveAct; - delete m_pRemovePlaylistAct; - delete m_pRemoveCrateAct; - delete m_pHideAct; - delete m_pUnhideAct; - delete m_pPropertiesAct; - delete m_pMenu; - delete m_pLoadToMenu; - delete m_pDeckMenu; - delete m_pSamplerMenu; - delete m_pPlaylistMenu; - delete m_pCrateMenu; - delete m_pMetadataMenu; - delete m_pClearMetadataMenu; - delete m_pCoverMenu; - delete m_pBpmLockAction; - delete m_pBpmUnlockAction; - delete m_pBpmDoubleAction; - delete m_pBpmHalveAction; - delete m_pBpmTwoThirdsAction; - delete m_pBpmThreeFourthsAction; - delete m_pBpmFourThirdsAction; - delete m_pBpmThreeHalvesAction; - delete m_pBPMMenu; - delete m_pColorMenu; - delete m_pClearBeatsAction; - delete m_pClearPlayCountAction; - delete m_pClearMainCueAction; - delete m_pClearHotCuesAction; - delete m_pClearIntroCueAction; - delete m_pClearOutroCueAction; - delete m_pClearLoopAction; - delete m_pClearReplayGainAction; - delete m_pClearWaveformAction; - delete m_pClearAllMetadataAction; - delete m_pPurgeAct; - delete m_pFileBrowserAct; } void WTrackTableView::enableCachedOnly() { @@ -284,12 +160,7 @@ void WTrackTableView::loadTrackModel(QAbstractItemModel *model) { // by slotLoadCoverArt(). As this value will not change when the model // still the same, we must avoid doing hundreds of "fieldIndex" calls // when it is completely unnecessary... - m_iCoverSourceColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_SOURCE); - m_iCoverTypeColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_TYPE); - m_iCoverLocationColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_LOCATION); - m_iCoverHashColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_HASH); - m_iCoverColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART); - m_iTrackLocationColumn = trackModel->fieldIndex(TRACKLOCATIONSTABLE_LOCATION); + setVisible(false); @@ -369,8 +240,11 @@ void WTrackTableView::loadTrackModel(QAbstractItemModel *model) { if (m_sorting) { // NOTE: Should be a UniqueConnection but that requires Qt 4.6 - connect(horizontalHeader(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), - this, SLOT(slotSortingChanged(int, Qt::SortOrder)), Qt::AutoConnection); + // But Qt::UniqueConnections do not work for lambdas, non-member functions + // and functors; they only apply to connecting to member functions. + // https://doc.qt.io/qt-5/qobject.html#connect + connect(horizontalHeader(), &QHeaderView::sortIndicatorChanged, + this, &WTrackTableView::slotSortingChanged, Qt::AutoConnection); int sortColumn; Qt::SortOrder sortOrder; @@ -408,7 +282,7 @@ void WTrackTableView::loadTrackModel(QAbstractItemModel *model) { // this.) setDragEnabled(true); - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_RECEIVEDROPS)) { + if (trackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_RECEIVEDROPS)) { setDragDropMode(QAbstractItemView::DragDrop); setDropIndicatorShown(true); setAcceptDrops(true); @@ -426,177 +300,42 @@ void WTrackTableView::loadTrackModel(QAbstractItemModel *model) { restoreVScrollBarPos(newModel); // restoring scrollBar position using model pointer as key // scrollbar positions with respect to different models are backed by map + initTrackMenu(); } -void WTrackTableView::createActions() { - DEBUG_ASSERT(m_pMenu); - DEBUG_ASSERT(m_pSamplerMenu); - - m_pRemoveAct = new QAction(tr("Remove"), this); - connect(m_pRemoveAct, SIGNAL(triggered()), this, SLOT(slotRemove())); - - m_pRemovePlaylistAct = new QAction(tr("Remove from Playlist"), this); - connect(m_pRemovePlaylistAct, SIGNAL(triggered()), this, SLOT(slotRemove())); - - m_pRemoveCrateAct = new QAction(tr("Remove from Crate"), this); - connect(m_pRemoveCrateAct, SIGNAL(triggered()), this, SLOT(slotRemove())); - - m_pHideAct = new QAction(tr("Hide from Library"), this); - connect(m_pHideAct, SIGNAL(triggered()), this, SLOT(slotHide())); - - m_pUnhideAct = new QAction(tr("Unhide from Library"), this); - connect(m_pUnhideAct, SIGNAL(triggered()), this, SLOT(slotUnhide())); - - m_pPurgeAct = new QAction(tr("Purge from Library"), this); - connect(m_pPurgeAct, SIGNAL(triggered()), this, SLOT(slotPurge())); - - m_pPropertiesAct = new QAction(tr("Properties"), this); - connect(m_pPropertiesAct, SIGNAL(triggered()), - this, SLOT(slotShowTrackInfo())); - - m_pFileBrowserAct = new QAction(tr("Open in File Browser"), this); - connect(m_pFileBrowserAct, SIGNAL(triggered()), - this, SLOT(slotOpenInFileBrowser())); - - m_pAutoDJBottomAct = new QAction(tr("Add to Auto DJ Queue (bottom)"), this); - connect(m_pAutoDJBottomAct, SIGNAL(triggered()), - this, SLOT(slotAddToAutoDJBottom())); - - m_pAutoDJTopAct = new QAction(tr("Add to Auto DJ Queue (top)"), this); - connect(m_pAutoDJTopAct, SIGNAL(triggered()), - this, SLOT(slotAddToAutoDJTop())); - - m_pAutoDJReplaceAct = new QAction(tr("Add to Auto DJ Queue (replace)"), this); - connect(m_pAutoDJReplaceAct, SIGNAL(triggered()), - this, SLOT(slotAddToAutoDJReplace())); - - m_pImportMetadataFromFileAct = new QAction(tr("Import From File Tags"), this); - connect(m_pImportMetadataFromFileAct, SIGNAL(triggered()), - this, SLOT(slotImportTrackMetadataFromFileTags())); - - m_pImportMetadataFromMusicBrainzAct = new QAction(tr("Import From MusicBrainz"),this); - connect(m_pImportMetadataFromMusicBrainzAct, SIGNAL(triggered()), - this, SLOT(slotShowDlgTagFetcher())); +void WTrackTableView::initTrackMenu() { + auto trackModel = getTrackModel(); + DEBUG_ASSERT(trackModel); - m_pExportMetadataAct = new QAction(tr("Export To File Tags"), this); - connect(m_pExportMetadataAct, SIGNAL(triggered()), - this, SLOT(slotExportTrackMetadataIntoFileTags())); - - for (const auto& externalTrackCollection : m_pTrackCollectionManager->externalCollections()) { - UpdateExternalTrackCollection updateInExternalTrackCollection; - updateInExternalTrackCollection.externalTrackCollection = externalTrackCollection; - updateInExternalTrackCollection.action = new QAction(externalTrackCollection->name(), this); - updateInExternalTrackCollection.action->setToolTip(externalTrackCollection->description()); - m_updateInExternalTrackCollections += updateInExternalTrackCollection; - auto externalTrackCollectionPtr = updateInExternalTrackCollection.externalTrackCollection; - connect(updateInExternalTrackCollection.action, &QAction::triggered, - this, [this, externalTrackCollectionPtr] { - slotUpdateExternalTrackCollection(externalTrackCollectionPtr); - }); - } + if (m_pTrackMenu) { + m_pTrackMenu->deleteLater(); + } - m_pAddToPreviewDeck = new QAction(tr("Preview Deck"), this); - // currently there is only one preview deck so just map it here. - QString previewDeckGroup = PlayerManager::groupForPreviewDeck(0); - connect(m_pAddToPreviewDeck, &QAction::triggered, - this, [this, previewDeckGroup] { loadSelectionToGroup(previewDeckGroup); }); - - - // Clear metadata actions - m_pClearBeatsAction = new QAction(tr("BPM and Beatgrid"), this); - connect(m_pClearBeatsAction, SIGNAL(triggered()), - this, SLOT(slotClearBeats())); - - m_pClearPlayCountAction = new QAction(tr("Play Count"), this); - connect(m_pClearPlayCountAction, SIGNAL(triggered()), - this, SLOT(slotClearPlayCount())); - - m_pClearMainCueAction = new QAction(tr("Cue Point"), this); - connect(m_pClearMainCueAction, SIGNAL(triggered()), - this, SLOT(slotClearMainCue())); - - m_pClearHotCuesAction = new QAction(tr("Hotcues"), this); - connect(m_pClearHotCuesAction, SIGNAL(triggered()), - this, SLOT(slotClearHotCues())); - - m_pClearIntroCueAction = new QAction(tr("Intro"), this); - connect(m_pClearIntroCueAction, SIGNAL(triggered()), - this, SLOT(slotClearIntroCue())); - - m_pClearOutroCueAction = new QAction(tr("Outro"), this); - connect(m_pClearOutroCueAction, SIGNAL(triggered()), - this, SLOT(slotClearOutroCue())); - - m_pClearLoopAction = new QAction(tr("Loop"), this); - connect(m_pClearLoopAction, SIGNAL(triggered()), - this, SLOT(slotClearLoop())); - - m_pClearKeyAction = new QAction(tr("Key"), this); - connect(m_pClearKeyAction, SIGNAL(triggered()), - this, SLOT(slotClearKey())); - - m_pClearReplayGainAction = new QAction(tr("ReplayGain"), this); - connect(m_pClearReplayGainAction, SIGNAL(triggered()), - this, SLOT(slotClearReplayGain())); - - m_pClearWaveformAction = new QAction(tr("Waveform"), this); - connect(m_pClearWaveformAction, SIGNAL(triggered()), - this, SLOT(slotClearWaveform())); - - m_pClearAllMetadataAction = new QAction(tr("All"), this); - connect(m_pClearAllMetadataAction, SIGNAL(triggered()), - this, SLOT(slotClearAllMetadata())); - - - m_pBpmLockAction = new QAction(tr("Lock BPM"), this); - m_pBpmUnlockAction = new QAction(tr("Unlock BPM"), this); - connect(m_pBpmLockAction, SIGNAL(triggered()), - this, SLOT(slotLockBpm())); - connect(m_pBpmUnlockAction, SIGNAL(triggered()), - this, SLOT(slotUnlockBpm())); - - //BPM edit actions - m_pBpmDoubleAction = new QAction(tr("Double BPM"), this); - m_pBpmHalveAction = new QAction(tr("Halve BPM"), this); - m_pBpmTwoThirdsAction = new QAction(tr("2/3 BPM"), this); - m_pBpmThreeFourthsAction = new QAction(tr("3/4 BPM"), this); - m_pBpmFourThirdsAction = new QAction(tr("4/3 BPM"), this); - m_pBpmThreeHalvesAction = new QAction(tr("3/2 BPM"), this); - - connect(m_pBpmDoubleAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::DOUBLE); }); - connect(m_pBpmHalveAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::HALVE); }); - connect(m_pBpmTwoThirdsAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::TWOTHIRDS); }); - connect(m_pBpmThreeFourthsAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::THREEFOURTHS); }); - connect(m_pBpmFourThirdsAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::FOURTHIRDS); }); - connect(m_pBpmThreeHalvesAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::THREEHALVES); }); - - ColorPaletteSettings colorPaletteSettings(m_pConfig); - m_pColorPickerAction = new WColorPickerAction(WColorPicker::Option::AllowNoColor, colorPaletteSettings.getTrackColorPalette(), this); - m_pColorPickerAction->setObjectName("TrackColorPickerAction"); - connect(m_pColorPickerAction, - &WColorPickerAction::colorPicked, + m_pTrackMenu = make_parented(this, + m_pConfig, + m_pTrackCollectionManager, + WTrackMenu::Feature::All, + trackModel); + connect(m_pTrackMenu.get(), + &WTrackMenu::loadTrackToPlayer, this, - &WTrackTableView::slotColorPicked); + &WTrackTableView::loadTrackToPlayer); } // slot -void WTrackTableView::slotMouseDoubleClicked(const QModelIndex &index) { +void WTrackTableView::slotMouseDoubleClicked(const QModelIndex& index) { // Read the current TrackLoadAction settings - int doubleClickActionConfigValue = m_pConfig->getValue( - ConfigKey("[Library]","TrackLoadAction"), - static_cast(DlgPrefLibrary::LOAD_TO_DECK)); + int doubleClickActionConfigValue = + m_pConfig->getValue(ConfigKey("[Library]", "TrackLoadAction"), + static_cast(DlgPrefLibrary::LOAD_TO_DECK)); DlgPrefLibrary::TrackDoubleClickAction doubleClickAction = - static_cast(doubleClickActionConfigValue); + static_cast( + doubleClickActionConfigValue); - if (doubleClickAction == DlgPrefLibrary::LOAD_TO_DECK - && modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTODECK)) { - TrackModel* trackModel = getTrackModel(); + auto trackModel = getTrackModel(); + if (doubleClickAction == DlgPrefLibrary::LOAD_TO_DECK && + trackModel->hasCapabilities( + TrackModel::TRACKMODELCAPS_LOADTODECK)) { VERIFY_OR_DEBUG_ASSERT(trackModel) { return; } @@ -605,48 +344,17 @@ void WTrackTableView::slotMouseDoubleClicked(const QModelIndex &index) { if (pTrack) { emit loadTrack(pTrack); } - } else if (doubleClickAction == DlgPrefLibrary::ADD_TO_AUTODJ_BOTTOM - && modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { + } else if (doubleClickAction == DlgPrefLibrary::ADD_TO_AUTODJ_BOTTOM && + trackModel->hasCapabilities( + TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { addToAutoDJ(PlaylistDAO::AutoDJSendLoc::BOTTOM); - } else if (doubleClickAction == DlgPrefLibrary::ADD_TO_AUTODJ_TOP - && modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { + } else if (doubleClickAction == DlgPrefLibrary::ADD_TO_AUTODJ_TOP && + trackModel->hasCapabilities( + TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { addToAutoDJ(PlaylistDAO::AutoDJSendLoc::TOP); } } -void WTrackTableView::loadSelectionToGroup(QString group, bool play) { - QModelIndexList indices = selectionModel()->selectedRows(); - if (indices.size() > 0) { - // If the track load override is disabled, check to see if a track is - // playing before trying to load it - if (!(m_pConfig->getValueString( - ConfigKey("[Controls]","AllowTrackLoadToPlayingDeck")).toInt())) { - // TODO(XXX): Check for other than just the first preview deck. - if (group != "[PreviewDeck1]" && - ControlObject::get(ConfigKey(group, "play")) > 0.0) { - return; - } - } - QModelIndex index = indices.at(0); - TrackModel* trackModel = getTrackModel(); - TrackPointer pTrack; - if (trackModel && - (pTrack = trackModel->getTrack(index))) { - emit loadTrackToPlayer(pTrack, group, play); - } - } -} - -void WTrackTableView::slotRemove() { - QModelIndexList indices = selectionModel()->selectedRows(); - if (indices.size() > 0) { - TrackModel* trackModel = getTrackModel(); - if (trackModel) { - trackModel->removeTracks(indices); - } - } -} - void WTrackTableView::slotPurge() { QModelIndexList indices = selectionModel()->selectedRows(); if (indices.size() > 0) { @@ -657,34 +365,6 @@ void WTrackTableView::slotPurge() { } } -void WTrackTableView::slotOpenInFileBrowser() { - TrackModel* trackModel = getTrackModel(); - if (!trackModel) { - return; - } - - const QModelIndexList indices = selectionModel()->selectedRows(); - - QStringList locations; - for (const QModelIndex& index : indices) { - if (!index.isValid()) { - continue; - } - locations << trackModel->getTrackLocation(index); - } - mixxx::DesktopHelper::openInFileBrowser(locations); -} - -void WTrackTableView::slotHide() { - QModelIndexList indices = selectionModel()->selectedRows(); - if (indices.size() > 0) { - TrackModel* trackModel = getTrackModel(); - if (trackModel) { - trackModel->hideTracks(indices); - } - } -} - void WTrackTableView::slotUnhide() { QModelIndexList indices = selectionModel()->selectedRows(); @@ -696,455 +376,16 @@ void WTrackTableView::slotUnhide() { } } -void WTrackTableView::slotTrackInfoClosed() { - DlgTrackInfo* pTrackInfo = m_pTrackInfo.take(); - // We are in a slot directly invoked from DlgTrackInfo. Delete it - // later. - if (pTrackInfo != nullptr) { - pTrackInfo->deleteLater(); - } -} - -void WTrackTableView::slotTagFetcherClosed() { - DlgTagFetcher* pTagFetcher = m_pTagFetcher.take(); - // We are in a slot directly invoked from DlgTagFetcher. Delete it - // later. - if (pTagFetcher != nullptr) { - pTagFetcher->deleteLater(); - } -} - -void WTrackTableView::slotShowTrackInfo() { - QModelIndexList indices = selectionModel()->selectedRows(); - - if (indices.size() > 0) { - showTrackInfo(indices[0]); - } -} - -void WTrackTableView::slotNextTrackInfo() { - QModelIndex nextRow = currentTrackInfoIndex.sibling( - currentTrackInfoIndex.row()+1, currentTrackInfoIndex.column()); - if (nextRow.isValid()) { - showTrackInfo(nextRow); - if (!m_pTagFetcher.isNull()) { - showDlgTagFetcher(nextRow); - } - } -} - -void WTrackTableView::slotPrevTrackInfo() { - QModelIndex prevRow = currentTrackInfoIndex.sibling( - currentTrackInfoIndex.row()-1, currentTrackInfoIndex.column()); - if (prevRow.isValid()) { - showTrackInfo(prevRow); - if (!m_pTagFetcher.isNull()) { - showDlgTagFetcher(prevRow); - } - } -} - -void WTrackTableView::showTrackInfo(QModelIndex index) { - TrackModel* trackModel = getTrackModel(); - - if (!trackModel) { - return; - } - - if (m_pTrackInfo.isNull()) { - // Give a NULL parent because otherwise it inherits our style which can - // make it unreadable. Bug #673411 - m_pTrackInfo.reset(new DlgTrackInfo(m_pConfig, nullptr)); - - connect(m_pTrackInfo.data(), SIGNAL(next()), - this, SLOT(slotNextTrackInfo())); - connect(m_pTrackInfo.data(), SIGNAL(previous()), - this, SLOT(slotPrevTrackInfo())); - connect(m_pTrackInfo.data(), SIGNAL(showTagFetcher(TrackPointer)), - this, SLOT(slotShowTrackInTagFetcher(TrackPointer))); - connect(m_pTrackInfo.data(), SIGNAL(finished(int)), - this, SLOT(slotTrackInfoClosed())); - } - TrackPointer pTrack = trackModel->getTrack(index); - m_pTrackInfo->loadTrack(pTrack); // NULL is fine. - currentTrackInfoIndex = index; - m_pTrackInfo->show(); -} - -void WTrackTableView::slotNextDlgTagFetcher() { - QModelIndex nextRow = currentTrackInfoIndex.sibling( - currentTrackInfoIndex.row()+1, currentTrackInfoIndex.column()); - if (nextRow.isValid()) { - showDlgTagFetcher(nextRow); - if (!m_pTrackInfo.isNull()) { - showTrackInfo(nextRow); - } - } -} - -void WTrackTableView::slotPrevDlgTagFetcher() { - QModelIndex prevRow = currentTrackInfoIndex.sibling( - currentTrackInfoIndex.row()-1, currentTrackInfoIndex.column()); - if (prevRow.isValid()) { - showDlgTagFetcher(prevRow); - if (!m_pTrackInfo.isNull()) { - showTrackInfo(prevRow); - } - } -} - -void WTrackTableView::showDlgTagFetcher(QModelIndex index) { - TrackModel* trackModel = getTrackModel(); - - if (!trackModel) { - return; - } - - TrackPointer pTrack = trackModel->getTrack(index); - currentTrackInfoIndex = index; - slotShowTrackInTagFetcher(pTrack); -} - -void WTrackTableView::slotShowTrackInTagFetcher(TrackPointer pTrack) { - if (m_pTagFetcher.isNull()) { - m_pTagFetcher.reset(new DlgTagFetcher(nullptr)); - connect(m_pTagFetcher.data(), SIGNAL(next()), - this, SLOT(slotNextDlgTagFetcher())); - connect(m_pTagFetcher.data(), SIGNAL(previous()), - this, SLOT(slotPrevDlgTagFetcher())); - connect(m_pTagFetcher.data(), SIGNAL(finished(int)), - this, SLOT(slotTagFetcherClosed())); - } - - // NULL is fine - m_pTagFetcher->loadTrack(pTrack); - m_pTagFetcher->show(); -} - -void WTrackTableView::slotShowDlgTagFetcher() { - QModelIndexList indices = selectionModel()->selectedRows(); - - if (indices.size() > 0) { - showDlgTagFetcher(indices[0]); - } -} - void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { - QModelIndexList indices = selectionModel()->selectedRows(); - - // Gray out some stuff if multiple songs were selected. - bool oneSongSelected = indices.size() == 1; - TrackModel* trackModel = getTrackModel(); - - m_pMenu->clear(); - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { - m_pMenu->clear(); - m_pMenu->addAction(m_pAutoDJBottomAct); - m_pMenu->addAction(m_pAutoDJTopAct); - m_pMenu->addAction(m_pAutoDJReplaceAct); - m_pMenu->addSeparator(); - } - - m_pLoadToMenu->clear(); - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTODECK)) { - int iNumDecks = m_pNumDecks->get(); - m_pDeckMenu->clear(); - if (iNumDecks > 0) { - for (int i = 1; i <= iNumDecks; ++i) { - // PlayerManager::groupForDeck is 0-indexed. - QString deckGroup = PlayerManager::groupForDeck(i - 1); - bool deckPlaying = ControlObject::get( - ConfigKey(deckGroup, "play")) > 0.0; - bool loadTrackIntoPlayingDeck = m_pConfig->getValue( - ConfigKey("[Controls]", "AllowTrackLoadToPlayingDeck")); - bool deckEnabled = (!deckPlaying || loadTrackIntoPlayingDeck) && oneSongSelected; - QAction* pAction = new QAction(tr("Deck %1").arg(i), m_pMenu); - pAction->setEnabled(deckEnabled); - m_pDeckMenu->addAction(pAction); - connect(pAction, &QAction::triggered, - this, [this, deckGroup] { loadSelectionToGroup(deckGroup); }); - } - } - m_pLoadToMenu->addMenu(m_pDeckMenu); - } - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTOSAMPLER)) { - int iNumSamplers = m_pNumSamplers->get(); - if (iNumSamplers > 0) { - m_pSamplerMenu->clear(); - for (int i = 1; i <= iNumSamplers; ++i) { - // PlayerManager::groupForSampler is 0-indexed. - QString samplerGroup = PlayerManager::groupForSampler(i - 1); - bool samplerPlaying = ControlObject::get( - ConfigKey(samplerGroup, "play")) > 0.0; - bool samplerEnabled = !samplerPlaying && oneSongSelected; - QAction* pAction = new QAction(tr("Sampler %1").arg(i), m_pSamplerMenu); - pAction->setEnabled(samplerEnabled); - m_pSamplerMenu->addAction(pAction); - connect(pAction, &QAction::triggered, - this, [this, samplerGroup] {loadSelectionToGroup(samplerGroup); } ); - - } - m_pLoadToMenu->addMenu(m_pSamplerMenu); - } - } - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTOPREVIEWDECK) && - m_pNumPreviewDecks->get() > 0.0) { - m_pLoadToMenu->addAction(m_pAddToPreviewDeck); - } - - m_pMenu->addMenu(m_pLoadToMenu); - m_pMenu->addSeparator(); - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOPLAYLIST)) { - // Playlist menu is lazy loaded on hover by slotPopulatePlaylistMenu - // to avoid unnecessary database queries - m_bPlaylistMenuLoaded = false; - m_pMenu->addMenu(m_pPlaylistMenu); - } - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOCRATE)) { - // Crate menu is lazy loaded on hover by slotPopulateCrateMenu - // to avoid unnecessary database queries - m_bCrateMenuLoaded = false; - m_pMenu->addMenu(m_pCrateMenu); - } - - // REMOVE and HIDE should not be at the first menu position to avoid accidental clicks - bool locked = modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOCKED); - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE)) { - m_pRemoveAct->setEnabled(!locked); - m_pMenu->addAction(m_pRemoveAct); - } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_PLAYLIST)) { - m_pRemovePlaylistAct->setEnabled(!locked); - m_pMenu->addAction(m_pRemovePlaylistAct); - } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_CRATE)) { - m_pRemoveCrateAct->setEnabled(!locked); - m_pMenu->addAction(m_pRemoveCrateAct); - } - - m_pMenu->addSeparator(); - m_pMetadataMenu->clear(); - m_pMetadataUpdateExternalCollectionsMenu->clear(); - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { - m_pMetadataMenu->addAction(m_pImportMetadataFromFileAct); - m_pImportMetadataFromMusicBrainzAct->setEnabled(oneSongSelected); - m_pMetadataMenu->addAction(m_pImportMetadataFromMusicBrainzAct); - m_pMetadataMenu->addAction(m_pExportMetadataAct); - - for (const auto& updateInExternalTrackCollection : m_updateInExternalTrackCollections) { - ExternalTrackCollection* externalTrackCollection = - updateInExternalTrackCollection.externalTrackCollection; - if (externalTrackCollection) { - updateInExternalTrackCollection.action->setEnabled( - externalTrackCollection->isConnected()); - m_pMetadataUpdateExternalCollectionsMenu->addAction( - updateInExternalTrackCollection.action); - } - } - if (!m_pMetadataUpdateExternalCollectionsMenu->isEmpty()) { - m_pMetadataMenu->addMenu(m_pMetadataUpdateExternalCollectionsMenu); - } - - for (const auto& updateInExternalTrackCollection : m_updateInExternalTrackCollections) { - ExternalTrackCollection* externalTrackCollection = - updateInExternalTrackCollection.externalTrackCollection; - if (externalTrackCollection) { - updateInExternalTrackCollection.action->setEnabled( - externalTrackCollection->isConnected()); - m_pMetadataUpdateExternalCollectionsMenu->addAction( - updateInExternalTrackCollection.action); - } - } - if (!m_pMetadataUpdateExternalCollectionsMenu->isEmpty()) { - m_pMetadataMenu->addMenu(m_pMetadataUpdateExternalCollectionsMenu); - } - - m_pClearMetadataMenu->clear(); - - if (trackModel == nullptr) { - return; - } - bool allowClear = true; - int column = trackModel->fieldIndex(LIBRARYTABLE_BPM_LOCK); - for (int i = 0; i < indices.size() && allowClear; ++i) { - int row = indices.at(i).row(); - QModelIndex index = indices.at(i).sibling(row,column); - if (index.data().toBool()) { - allowClear = false; - } - } - m_pClearBeatsAction->setEnabled(allowClear); - m_pClearMetadataMenu->addAction(m_pClearBeatsAction); - } - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_RESETPLAYED)) { - m_pClearMetadataMenu->addAction(m_pClearPlayCountAction); - } - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { - // FIXME: Why is clearing the loop not working? - m_pClearMetadataMenu->addAction(m_pClearMainCueAction); - m_pClearMetadataMenu->addAction(m_pClearHotCuesAction); - m_pClearMetadataMenu->addAction(m_pClearIntroCueAction); - m_pClearMetadataMenu->addAction(m_pClearOutroCueAction); - //m_pClearMetadataMenu->addAction(m_pClearLoopAction); - m_pClearMetadataMenu->addAction(m_pClearKeyAction); - m_pClearMetadataMenu->addAction(m_pClearReplayGainAction); - m_pClearMetadataMenu->addAction(m_pClearWaveformAction); - m_pClearMetadataMenu->addSeparator(); - m_pClearMetadataMenu->addAction(m_pClearAllMetadataAction); - - // Cover art menu only applies if at least one track is selected. - if (indices.size()) { - // We load a single track to get the necessary context for the cover (we use - // last to be consistent with selectionChanged above). - QModelIndex last = indices.last(); - CoverInfo info; - info.source = static_cast( - last.sibling(last.row(), m_iCoverSourceColumn).data().toInt()); - info.type = static_cast( - last.sibling(last.row(), m_iCoverTypeColumn).data().toInt()); - info.hash = last.sibling(last.row(), m_iCoverHashColumn).data().toUInt(); - info.trackLocation = last.sibling( - last.row(), m_iTrackLocationColumn).data().toString(); - info.coverLocation = last.sibling( - last.row(), m_iCoverLocationColumn).data().toString(); - m_pCoverMenu->setCoverArt(info); - m_pMetadataMenu->addMenu(m_pCoverMenu); - } - - m_pMenu->addMenu(m_pMetadataMenu); - m_pMenu->addMenu(m_pClearMetadataMenu); - - m_pBPMMenu->addAction(m_pBpmDoubleAction); - m_pBPMMenu->addAction(m_pBpmHalveAction); - m_pBPMMenu->addAction(m_pBpmTwoThirdsAction); - m_pBPMMenu->addAction(m_pBpmThreeFourthsAction); - m_pBPMMenu->addAction(m_pBpmFourThirdsAction); - m_pBPMMenu->addAction(m_pBpmThreeHalvesAction); - m_pBPMMenu->addSeparator(); - m_pBPMMenu->addAction(m_pBpmLockAction); - m_pBPMMenu->addAction(m_pBpmUnlockAction); - m_pBPMMenu->addSeparator(); - if (oneSongSelected) { - if (trackModel == nullptr) { - return; - } - int column = trackModel->fieldIndex(LIBRARYTABLE_BPM_LOCK); - QModelIndex index = indices.at(0).sibling(indices.at(0).row(),column); - if (index.data().toBool()) { //BPM is locked - m_pBpmUnlockAction->setEnabled(true); - m_pBpmLockAction->setEnabled(false); - m_pBpmDoubleAction->setEnabled(false); - m_pBpmHalveAction->setEnabled(false); - m_pBpmTwoThirdsAction->setEnabled(false); - m_pBpmThreeFourthsAction->setEnabled(false); - m_pBpmFourThirdsAction->setEnabled(false); - m_pBpmThreeHalvesAction->setEnabled(false); - } else { //BPM is not locked - m_pBpmUnlockAction->setEnabled(false); - m_pBpmLockAction->setEnabled(true); - m_pBpmDoubleAction->setEnabled(true); - m_pBpmHalveAction->setEnabled(true); - m_pBpmTwoThirdsAction->setEnabled(true); - m_pBpmThreeFourthsAction->setEnabled(true); - m_pBpmFourThirdsAction->setEnabled(true); - m_pBpmThreeHalvesAction->setEnabled(true); - } - } else { - bool anyLocked = false; //true if any of the selected items are locked - int column = trackModel->fieldIndex(LIBRARYTABLE_BPM_LOCK); - for (int i = 0; i < indices.size() && !anyLocked; ++i) { - int row = indices.at(i).row(); - QModelIndex index = indices.at(i).sibling(row,column); - if (index.data().toBool()) { - anyLocked = true; - } - } - if (anyLocked) { - m_pBpmLockAction->setEnabled(false); - m_pBpmUnlockAction->setEnabled(true); - m_pBpmDoubleAction->setEnabled(false); - m_pBpmHalveAction->setEnabled(false); - m_pBpmTwoThirdsAction->setEnabled(false); - m_pBpmThreeFourthsAction->setEnabled(false); - m_pBpmFourThirdsAction->setEnabled(false); - m_pBpmThreeHalvesAction->setEnabled(false); - } else { - m_pBpmLockAction->setEnabled(true); - m_pBpmUnlockAction->setEnabled(false); - m_pBpmDoubleAction->setEnabled(true); - m_pBpmHalveAction->setEnabled(true); - m_pBpmTwoThirdsAction->setEnabled(true); - m_pBpmThreeFourthsAction->setEnabled(true); - m_pBpmFourThirdsAction->setEnabled(true); - m_pBpmThreeHalvesAction->setEnabled(true); - } - } - m_pMenu->addMenu(m_pBPMMenu); - - // Track color menu only appears if at least one track is selected - if (indices.size()) { - m_pColorPickerAction->setColorPalette( - ColorPaletteSettings(m_pConfig).getTrackColorPalette()); - - // Get color of first selected track - int column = trackModel->fieldIndex(LIBRARYTABLE_COLOR); - QModelIndex index = indices.at(0).sibling(indices.at(0).row(), column); - auto trackColor = mixxx::RgbColor::fromQVariant(index.data()); - - // Check if all other selected tracks have the same color - bool multipleTrackColors = false; - for (int i = 1; i < indices.size(); ++i) { - int row = indices.at(i).row(); - QModelIndex index = indices.at(i).sibling(row, column); - - if (trackColor != mixxx::RgbColor::fromQVariant(index.data())) { - trackColor = mixxx::RgbColor::nullopt(); - multipleTrackColors = true; - break; - } - } - - if (multipleTrackColors) { - m_pColorPickerAction->resetSelectedColor(); - } else { - m_pColorPickerAction->setSelectedColor(trackColor); - } - m_pColorMenu->addAction(m_pColorPickerAction); - m_pMenu->addMenu(m_pColorMenu); - } - } - - m_pMenu->addSeparator(); - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_HIDE)) { - m_pHideAct->setEnabled(!locked); - m_pMenu->addAction(m_pHideAct); - } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_UNHIDE)) { - m_pUnhideAct->setEnabled(!locked); - m_pMenu->addAction(m_pUnhideAct); - } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_PURGE)) { - m_pPurgeAct->setEnabled(!locked); - m_pMenu->addAction(m_pPurgeAct); - } - m_pMenu->addAction(m_pFileBrowserAct); - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { - m_pMenu->addSeparator(); - m_pPropertiesAct->setEnabled(oneSongSelected); - m_pMenu->addAction(m_pPropertiesAct); + VERIFY_OR_DEBUG_ASSERT(m_pTrackMenu.get()) { + initTrackMenu(); } + // Update track indices in context menu + QModelIndexList indices = selectionModel()->selectedRows(); + m_pTrackMenu->loadTracks(indices); //Create the right-click menu - m_pMenu->popup(event->globalPos()); + m_pTrackMenu->popup(event->globalPos()); } void WTrackTableView::onSearch(const QString& text) { @@ -1197,10 +438,11 @@ void WTrackTableView::mouseMoveEvent(QMouseEvent* pEvent) { // Drag enter event, happens when a dragged item hovers over the track table view void WTrackTableView::dragEnterEvent(QDragEnterEvent * event) { + auto trackModel = getTrackModel(); //qDebug() << "dragEnterEvent" << event->mimeData()->formats(); if (event->mimeData()->hasUrls()) { if (event->source() == this) { - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REORDER)) { + if (trackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_REORDER)) { event->acceptProposedAction(); return; } @@ -1217,6 +459,7 @@ void WTrackTableView::dragEnterEvent(QDragEnterEvent * event) { // It changes the drop handle to a "+" when the drag content is acceptable. // Without it, the following drop is ignored. void WTrackTableView::dragMoveEvent(QDragMoveEvent * event) { + auto trackModel = getTrackModel(); // Needed to allow auto-scrolling WLibraryTableView::dragMoveEvent(event); @@ -1224,7 +467,7 @@ void WTrackTableView::dragMoveEvent(QDragMoveEvent * event) { if (event->mimeData()->hasUrls()) { if (event->source() == this) { - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REORDER)) { + if (trackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_REORDER)) { event->acceptProposedAction(); } else { event->ignore(); @@ -1271,7 +514,8 @@ void WTrackTableView::dropEvent(QDropEvent * event) { //qDebug() << "destIndex.row() is" << destIndex.row(); // Drag and drop within this widget (track reordering) - if (event->source() == this && modelHasCapabilities(TrackModel::TRACKMODELCAPS_REORDER)) { + if (event->source() == this && + trackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_REORDER)) { // Note the above code hides an ambiguous case when a // playlist is empty. For that reason, we can't factor that // code out to be common for both internal reordering @@ -1411,7 +655,7 @@ void WTrackTableView::dropEvent(QDropEvent * event) { // Create the selection, but only if the track model supports // reordering. (eg. crates don't support reordering/indexes) - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REORDER)) { + if (trackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_REORDER)) { for (int i = selectionStartRow; i < selectionStartRow + numNewRows; i++) { this->selectionModel()->select(model()->index(i, 0), QItemSelectionModel::Select | @@ -1430,12 +674,6 @@ TrackModel* WTrackTableView::getTrackModel() const { return trackModel; } -bool WTrackTableView::modelHasCapabilities(TrackModel::CapabilitiesFlags capabilities) const { - TrackModel* trackModel = getTrackModel(); - return trackModel && - (trackModel->getCapabilities() & capabilities) == capabilities; -} - void WTrackTableView::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Return) { // It is not a good idea if 'key_return' @@ -1447,30 +685,6 @@ void WTrackTableView::keyPressEvent(QKeyEvent* event) { } } -void WTrackTableView::loadSelectedTrack() { - QModelIndexList indexes = selectionModel()->selectedRows(); - if (indexes.size() > 0) { - slotMouseDoubleClicked(indexes.at(0)); - } -} - -void WTrackTableView::loadSelectedTrackToGroup(QString group, bool play) { - loadSelectionToGroup(group, play); -} - -void WTrackTableView::slotAddToAutoDJBottom() { - // append to auto DJ - addToAutoDJ(PlaylistDAO::AutoDJSendLoc::BOTTOM); -} - -void WTrackTableView::slotAddToAutoDJTop() { - addToAutoDJ(PlaylistDAO::AutoDJSendLoc::TOP); -} - -void WTrackTableView::slotAddToAutoDJReplace() { - addToAutoDJ(PlaylistDAO::AutoDJSendLoc::REPLACE); -} - QList WTrackTableView::getSelectedTrackIds() const { QList trackIds; @@ -1526,9 +740,9 @@ void WTrackTableView::setSelectedTracks(const QList& trackIds) { } } - void WTrackTableView::addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc) { - if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { + auto trackModel = getTrackModel(); + if (!trackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { return; } @@ -1545,312 +759,6 @@ void WTrackTableView::addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc) { playlistDao.addTracksToAutoDJQueue(trackIds, loc); } -void WTrackTableView::slotImportTrackMetadataFromFileTags() { - if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { - return; - } - - const QModelIndexList indices = selectionModel()->selectedRows(); - - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - // The user has explicitly requested to reload metadata from the file - // to override the information within Mixxx! Custom cover art must be - // reloaded separately. - SoundSourceProxy(pTrack).updateTrackFromSource( - SoundSourceProxy::ImportTrackMetadataMode::Again); - } - } -} - -void WTrackTableView::slotExportTrackMetadataIntoFileTags() { - if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { - return; - } - - TrackModel* pTrackModel = getTrackModel(); - if (!pTrackModel) { - return; - } - - const QModelIndexList indices = selectionModel()->selectedRows(); - if (indices.isEmpty()) { - return; - } - - mixxx::DlgTrackMetadataExport::showMessageBoxOncePerSession(); - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = pTrackModel->getTrack(index); - if (pTrack) { - // Export of metadata is deferred until all references to the - // corresponding track object have been dropped. Otherwise - // writing to files that are still used for playback might - // cause crashes or at least audible glitches! - mixxx::DlgTrackMetadataExport::showMessageBoxOncePerSession(); - pTrack->markForMetadataExport(); - } - } -} - -void WTrackTableView::slotUpdateExternalTrackCollection( - ExternalTrackCollection* externalTrackCollection) { - VERIFY_OR_DEBUG_ASSERT(externalTrackCollection) { - return; - } - - if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { - return; - } - - TrackModel* pTrackModel = getTrackModel(); - if (!pTrackModel) { - return; - } - - const QModelIndexList indices = selectionModel()->selectedRows(); - if (indices.isEmpty()) { - return; - } - - QList trackRefs; - trackRefs.reserve(indices.size()); - for (const QModelIndex& index : indices) { - trackRefs.append( - TrackRef::fromFileInfo( - pTrackModel->getTrackLocation(index), - pTrackModel->getTrackId(index))); - } - - externalTrackCollection->updateTracks(std::move(trackRefs)); -} - -//slot for reset played count, sets count to 0 of one or more tracks -void WTrackTableView::slotClearPlayCount() { - const QModelIndexList indices = selectionModel()->selectedRows(); - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->resetPlayCounter(); - } - } -} - -void WTrackTableView::slotPopulatePlaylistMenu() { - // The user may open the Playlist submenu, move their cursor away, then - // return to the Playlist submenu before exiting the track context menu. - // Avoid querying the database multiple times in that case. - if (m_bPlaylistMenuLoaded) { - return; - } - m_pPlaylistMenu->clear(); - PlaylistDAO& playlistDao = m_pTrackCollectionManager->internalCollection()->getPlaylistDAO(); - QMap playlists; - int numPlaylists = playlistDao.playlistCount(); - for (int i = 0; i < numPlaylists; ++i) { - int iPlaylistId = playlistDao.getPlaylistId(i); - playlists.insert(playlistDao.getPlaylistName(iPlaylistId), iPlaylistId); - } - QMapIterator it(playlists); - while (it.hasNext()) { - it.next(); - if (!playlistDao.isHidden(it.value())) { - // No leak because making the menu the parent means they will be - // auto-deleted - auto pAction = new QAction(it.key(), m_pPlaylistMenu); - bool locked = playlistDao.isPlaylistLocked(it.value()); - pAction->setEnabled(!locked); - m_pPlaylistMenu->addAction(pAction); - int iPlaylistId = it.value(); - connect(pAction, &QAction::triggered, - this, [this, iPlaylistId] { addSelectionToPlaylist(iPlaylistId); }); - - } - } - m_pPlaylistMenu->addSeparator(); - QAction* newPlaylistAction = new QAction(tr("Create New Playlist"), m_pPlaylistMenu); - m_pPlaylistMenu->addAction(newPlaylistAction); - connect(newPlaylistAction, &QAction::triggered, - this, [this] { addSelectionToPlaylist(-1); }); - m_bPlaylistMenuLoaded = true; -} - -void WTrackTableView::addSelectionToPlaylist(int iPlaylistId) { - const QList trackIds = getSelectedTrackIds(); - if (trackIds.isEmpty()) { - qWarning() << "No tracks selected for playlist"; - return; - } - - PlaylistDAO& playlistDao = m_pTrackCollectionManager->internalCollection()->getPlaylistDAO(); - - if (iPlaylistId == -1) { // i.e. a new playlist is suppose to be created - QString name; - bool validNameGiven = false; - - do { - bool ok = false; - name = QInputDialog::getText(nullptr, - tr("Create New Playlist"), - tr("Enter name for new playlist:"), - QLineEdit::Normal, - tr("New Playlist"), - &ok).trimmed(); - if (!ok) { - return; - } - if (playlistDao.getPlaylistIdFromName(name) != -1) { - QMessageBox::warning(nullptr, - tr("Playlist Creation Failed"), - tr("A playlist by that name already exists.")); - } else if (name.isEmpty()) { - QMessageBox::warning(nullptr, - tr("Playlist Creation Failed"), - tr("A playlist cannot have a blank name.")); - } else { - validNameGiven = true; - } - } while (!validNameGiven); - iPlaylistId = playlistDao.createPlaylist(name);//-1 is changed to the new playlist ID return from the DAO - if (iPlaylistId == -1) { - QMessageBox::warning(nullptr, - tr("Playlist Creation Failed"), - tr("An unknown error occurred while creating playlist: ") - +name); - return; - } - } - - // TODO(XXX): Care whether the append succeeded. - m_pTrackCollectionManager->unhideTracks(trackIds); - playlistDao.appendTracksToPlaylist(trackIds, iPlaylistId); -} - -void WTrackTableView::slotPopulateCrateMenu() { - // The user may open the Crate submenu, move their cursor away, then - // return to the Crate submenu before exiting the track context menu. - // Avoid querying the database multiple times in that case. - if (m_bCrateMenuLoaded) { - return; - } - m_pCrateMenu->clear(); - const QList trackIds = getSelectedTrackIds(); - - CrateSummarySelectResult allCrates(m_pTrackCollectionManager->internalCollection()->crates().selectCratesWithTrackCount(trackIds)); - - CrateSummary crate; - while (allCrates.populateNext(&crate)) { - auto pAction = make_parented(m_pCrateMenu); - auto pCheckBox = make_parented(m_pCrateMenu); - - pCheckBox->setText(crate.getName()); - pCheckBox->setProperty("crateId", - QVariant::fromValue(crate.getId())); - pCheckBox->setEnabled(!crate.isLocked()); - // Strangely, the normal styling of QActions does not automatically - // apply to QWidgetActions. The :selected pseudo-state unfortunately - // does not work with QWidgetAction. :hover works for selecting items - // with the mouse, but not with the keyboard. :focus works for the - // keyboard but with the mouse, the last clicked item keeps the style - // after the mouse cursor is moved to hover over another item. - - // ronso0 Disabling this stylesheet allows to override the OS style - // of the :hover and :focus state. -// pCheckBox->setStyleSheet( -// QString("QCheckBox {color: %1;}").arg( -// pCheckBox->palette().text().color().name()) + "\n" + -// QString("QCheckBox:hover {background-color: %1;}").arg( -// pCheckBox->palette().highlight().color().name())); - pAction->setEnabled(!crate.isLocked()); - pAction->setDefaultWidget(pCheckBox.get()); - - if (crate.getTrackCount() == 0) { - pCheckBox->setChecked(false); - } else if (crate.getTrackCount() == (uint)trackIds.length()) { - pCheckBox->setChecked(true); - } else { - pCheckBox->setTristate(true); - pCheckBox->setCheckState(Qt::PartiallyChecked); - } - - m_pCrateMenu->addAction(pAction.get()); - connect(pAction.get(), &QAction::triggered, - this, [this, pCheckBox{pCheckBox.get()}] { updateSelectionCrates(pCheckBox); }); - connect(pCheckBox.get(), &QCheckBox::stateChanged, - this, [this, pCheckBox{pCheckBox.get()}] { updateSelectionCrates(pCheckBox); }); - - } - m_pCrateMenu->addSeparator(); - QAction* newCrateAction = new QAction(tr("Create New Crate"), m_pCrateMenu); - m_pCrateMenu->addAction(newCrateAction); - connect(newCrateAction, SIGNAL(triggered()), this, SLOT(addSelectionToNewCrate())); - m_bCrateMenuLoaded = true; -} - -void WTrackTableView::updateSelectionCrates(QWidget* pWidget) { - auto pCheckBox = qobject_cast(pWidget); - VERIFY_OR_DEBUG_ASSERT(pCheckBox) { - qWarning() << "crateId is not of CrateId type"; - return; - } - CrateId crateId = pCheckBox->property("crateId").value(); - - const QList trackIds = getSelectedTrackIds(); - - if (trackIds.isEmpty()) { - qWarning() << "No tracks selected for crate"; - return; - } - - // we need to disable tristate again as the mixed state will now be gone and can't be brought back - pCheckBox->setTristate(false); - if(!pCheckBox->isChecked()) { - if (crateId.isValid()) { - m_pTrackCollectionManager->internalCollection()->removeCrateTracks(crateId, trackIds); - } - } else { - if (!crateId.isValid()) { // i.e. a new crate is suppose to be created - crateId = CrateFeatureHelper( - m_pTrackCollectionManager->internalCollection(), m_pConfig).createEmptyCrate(); - } - if (crateId.isValid()) { - m_pTrackCollectionManager->unhideTracks(trackIds); - m_pTrackCollectionManager->internalCollection()->addCrateTracks(crateId, trackIds); - } - } -} - -void WTrackTableView::addSelectionToNewCrate() { - const QList trackIds = getSelectedTrackIds(); - - if (trackIds.isEmpty()) { - qWarning() << "No tracks selected for crate"; - return; - } - - CrateId crateId = CrateFeatureHelper( - m_pTrackCollectionManager->internalCollection(), m_pConfig).createEmptyCrate(); - - if (crateId.isValid()) { - m_pTrackCollectionManager->unhideTracks(trackIds); - m_pTrackCollectionManager->internalCollection()->addCrateTracks(crateId, trackIds); - } - -} - void WTrackTableView::doSortByColumn(int headerSection, Qt::SortOrder sortOrder) { TrackModel* trackModel = getTrackModel(); QAbstractItemModel* itemModel = model(); @@ -1933,259 +841,6 @@ void WTrackTableView::applySorting() { doSortByColumn(sortColumn, sortOrder); } -void WTrackTableView::slotLockBpm() { - lockBpm(true); -} - -void WTrackTableView::slotUnlockBpm() { - lockBpm(false); -} - -void WTrackTableView::slotScaleBpm(int scale) { - TrackModel* trackModel = getTrackModel(); - if (trackModel == nullptr) { - return; - } - - const QModelIndexList selectedTrackIndices = selectionModel()->selectedRows(); - for (const auto& index : selectedTrackIndices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack && !pTrack->isBpmLocked()) { - BeatsPointer pBeats = pTrack->getBeats(); - if (pBeats) { - pBeats->scale(static_cast(scale)); - } - } - } -} - -void WTrackTableView::lockBpm(bool lock) { - TrackModel* trackModel = getTrackModel(); - if (trackModel == nullptr) { - return; - } - - const QModelIndexList selectedTrackIndices = selectionModel()->selectedRows(); - // TODO: This should be done in a thread for large selections - for (const auto& index : selectedTrackIndices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->setBpmLocked(lock); - } - } -} - -void WTrackTableView::slotColorPicked(mixxx::RgbColor::optional_t color) { - TrackModel* trackModel = getTrackModel(); - if (trackModel == nullptr) { - return; - } - - const QModelIndexList selectedTrackIndices = selectionModel()->selectedRows(); - // TODO: This should be done in a thread for large selections - for (const auto& index : selectedTrackIndices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->setColor(color); - } - } - - m_pMenu->hide(); -} - -void WTrackTableView::slotClearBeats() { - TrackModel* trackModel = getTrackModel(); - if (trackModel == nullptr) { - return; - } - - const QModelIndexList selectedTrackIndices = selectionModel()->selectedRows(); - // TODO: This should be done in a thread for large selections - for (const auto& index : selectedTrackIndices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack && !pTrack->isBpmLocked()) { - pTrack->setBeats(BeatsPointer()); - } - } -} - -void WTrackTableView::slotClearMainCue() { - const QModelIndexList indices = selectionModel()->selectedRows(); - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->removeCuesOfType(mixxx::CueType::MainCue); - } - } -} - -void WTrackTableView::slotClearHotCues() { - const QModelIndexList indices = selectionModel()->selectedRows(); - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->removeCuesOfType(mixxx::CueType::HotCue); - } - } -} - -void WTrackTableView::slotClearIntroCue() { - const QModelIndexList indices = selectionModel()->selectedRows(); - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->removeCuesOfType(mixxx::CueType::Intro); - } - } -} - -void WTrackTableView::slotClearOutroCue() { - const QModelIndexList indices = selectionModel()->selectedRows(); - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->removeCuesOfType(mixxx::CueType::Outro); - } - } -} - -void WTrackTableView::slotClearLoop() { - const QModelIndexList indices = selectionModel()->selectedRows(); - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->removeCuesOfType(mixxx::CueType::Loop); - } - } -} - -void WTrackTableView::slotClearKey() { - const QModelIndexList indices = selectionModel()->selectedRows(); - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->resetKeys(); - } - } -} - -void WTrackTableView::slotClearReplayGain() { - const QModelIndexList indices = selectionModel()->selectedRows(); - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->setReplayGain(mixxx::ReplayGain()); - } - } -} - -void WTrackTableView::slotClearWaveform() { - TrackModel* trackModel = getTrackModel(); - if (trackModel == nullptr) { - return; - } - - AnalysisDao& analysisDao = m_pTrackCollectionManager->internalCollection()->getAnalysisDAO(); - const QModelIndexList indices = selectionModel()->selectedRows(); - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (!pTrack) { - continue; - } - analysisDao.deleteAnalysesForTrack(pTrack->getId()); - pTrack->setWaveform(WaveformPointer()); - pTrack->setWaveformSummary(WaveformPointer()); - } -} - -void WTrackTableView::slotClearAllMetadata() { - slotClearBeats(); - slotClearMainCue(); - slotClearHotCues(); - slotClearIntroCue(); - slotClearOutroCue(); - slotClearLoop(); - slotClearKey(); - slotClearReplayGain(); - slotClearWaveform(); -} - -void WTrackTableView::slotCoverInfoSelected(const CoverInfoRelative& coverInfo) { - TrackModel* trackModel = getTrackModel(); - if (trackModel == nullptr) { - return; - } - const QModelIndexList selection = selectionModel()->selectedRows(); - for (const QModelIndex& index : selection) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->setCoverInfo(coverInfo); - } - } -} - -void WTrackTableView::slotReloadCoverArt() { - TrackModel* trackModel = getTrackModel(); - if (!trackModel) { - return; - } - const QModelIndexList selection = selectionModel()->selectedRows(); - if (selection.isEmpty()) { - return; - } - QList selectedTracks; - selectedTracks.reserve(selection.size()); - for (const QModelIndex& index : selection) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - selectedTracks.append(pTrack); - } - } - guessTrackCoverInfoConcurrently(selectedTracks); -} - void WTrackTableView::slotSortingChanged(int headerSection, Qt::SortOrder order) { double sortOrder = static_cast(order); diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index e842473ced2..f185d461656 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -4,27 +4,25 @@ #include #include -#include "preferences/usersettings.h" #include "control/controlproxy.h" #include "library/dao/playlistdao.h" #include "library/trackmodel.h" // Can't forward declare enums +#include "preferences/usersettings.h" #include "track/track.h" #include "util/duration.h" -#include "widget/wcolorpickeraction.h" +#include "util/parented_ptr.h" #include "widget/wlibrarytableview.h" +#include "widget/wtrackmenu.h" class ControlProxy; class DlgTagFetcher; class DlgTrackInfo; class TrackCollectionManager; -class WCoverArtMenu; - class ExternalTrackCollection; const QString WTRACKTABLEVIEW_VSCROLLBARPOS_KEY = "VScrollBarPos"; /** ConfigValue key for QTable vertical scrollbar position */ const QString LIBRARY_CONFIGVALUE = "[Library]"; /** ConfigValue "value" (wtf) for library stuff */ - class WTrackTableView : public WLibraryTableView { Q_OBJECT public: @@ -40,8 +38,6 @@ class WTrackTableView : public WLibraryTableView { void onShow() override; bool hasFocus() const override; void keyPressEvent(QKeyEvent* event) override; - void loadSelectedTrack() override; - void loadSelectedTrackToGroup(QString group, bool play) override; QList getSelectedTrackIds() const; void setSelectedTracks(const QList& tracks); void saveCurrentVScrollBarPos(); @@ -56,71 +52,24 @@ class WTrackTableView : public WLibraryTableView { void slotMouseDoubleClicked(const QModelIndex &); void slotUnhide(); void slotPurge(); - void slotAddToAutoDJBottom() override; - void slotAddToAutoDJTop() override; - void slotAddToAutoDJReplace() override; private slots: - void slotRemove(); - void slotHide(); - void slotOpenInFileBrowser(); - void slotShowTrackInfo(); - void slotShowDlgTagFetcher(); - void slotNextTrackInfo(); - void slotNextDlgTagFetcher(); - void slotPrevTrackInfo(); - void slotPrevDlgTagFetcher(); - void slotShowTrackInTagFetcher(TrackPointer track); - void slotImportTrackMetadataFromFileTags(); - void slotExportTrackMetadataIntoFileTags(); - void slotUpdateExternalTrackCollection(ExternalTrackCollection*); - void slotPopulatePlaylistMenu(); - void addSelectionToPlaylist(int iPlaylistId); - void updateSelectionCrates(QWidget* qc); - void slotPopulateCrateMenu(); - void addSelectionToNewCrate(); - void loadSelectionToGroup(QString group, bool play = false); void doSortByColumn(int headerSection, Qt::SortOrder sortOrder); void applySortingIfVisible(); void applySorting(); - void slotLockBpm(); - void slotUnlockBpm(); - void slotScaleBpm(int); - void slotColorPicked(mixxx::RgbColor::optional_t color); - - void slotClearBeats(); - void slotClearPlayCount(); - void slotClearMainCue(); - void slotClearHotCues(); - void slotClearIntroCue(); - void slotClearOutroCue(); - void slotClearLoop(); - void slotClearKey(); - void slotClearReplayGain(); - void slotClearWaveform(); - void slotClearAllMetadata(); // Signalled 20 times per second (every 50ms) by GuiTick. void slotGuiTick50ms(double); void slotScrollValueChanged(int); - void slotCoverInfoSelected(const CoverInfoRelative& coverInfo); - void slotReloadCoverArt(); - void slotTrackInfoClosed(); - void slotTagFetcherClosed(); void slotSortingChanged(int headerSection, Qt::SortOrder order); void keyNotationChanged(); private: - void createActions(); - void addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc); - void showTrackInfo(QModelIndex index); - void showDlgTagFetcher(QModelIndex index); void dragMoveEvent(QDragMoveEvent * event) override; void dragEnterEvent(QDragEnterEvent * event) override; void dropEvent(QDropEvent * event) override; - void lockBpm(bool lock); void enableCachedOnly(); void selectionChanged(const QItemSelection &selected, @@ -132,116 +81,23 @@ class WTrackTableView : public WLibraryTableView { // Returns the current TrackModel, or returns NULL if none is set. TrackModel* getTrackModel() const; - bool modelHasCapabilities(TrackModel::CapabilitiesFlags capabilities) const; - const UserSettingsPointer m_pConfig; + void initTrackMenu(); + const UserSettingsPointer m_pConfig; TrackCollectionManager* const m_pTrackCollectionManager; - const double m_backgroundColorOpacity; - - QScopedPointer m_pTrackInfo; - QScopedPointer m_pTagFetcher; - - QModelIndex currentTrackInfoIndex; - - ControlProxy* m_pNumSamplers; - ControlProxy* m_pNumDecks; - ControlProxy* m_pNumPreviewDecks; - - // Context menu machinery - QMenu *m_pMenu; - - QMenu *m_pLoadToMenu; - QMenu *m_pDeckMenu; - QMenu *m_pSamplerMenu; - - QMenu *m_pPlaylistMenu; - QMenu *m_pCrateMenu; - QMenu *m_pMetadataMenu; - QMenu *m_pMetadataUpdateExternalCollectionsMenu; - QMenu *m_pClearMetadataMenu; - QMenu *m_pBPMMenu; - QMenu *m_pColorMenu; - - - WCoverArtMenu* m_pCoverMenu; - - // Reload Track Metadata Action: - QAction *m_pImportMetadataFromFileAct; - QAction *m_pImportMetadataFromMusicBrainzAct; - - // Save Track Metadata Action: - QAction *m_pExportMetadataAct; - - // Load Track to PreviewDeck - QAction* m_pAddToPreviewDeck; - - // Send to Auto-DJ Action - QAction *m_pAutoDJBottomAct; - QAction *m_pAutoDJTopAct; - QAction *m_pAutoDJReplaceAct; - - // Remove from table - QAction *m_pRemoveAct; - QAction *m_pRemovePlaylistAct; - QAction *m_pRemoveCrateAct; - QAction *m_pHideAct; - QAction *m_pUnhideAct; - QAction *m_pPurgeAct; - - // Show track-editor action - QAction *m_pPropertiesAct; - QAction *m_pFileBrowserAct; - - // BPM feature - QAction *m_pBpmLockAction; - QAction *m_pBpmUnlockAction; - QAction *m_pBpmDoubleAction; - QAction *m_pBpmHalveAction; - QAction *m_pBpmTwoThirdsAction; - QAction *m_pBpmThreeFourthsAction; - QAction *m_pBpmFourThirdsAction; - QAction *m_pBpmThreeHalvesAction; - - // Track color - WColorPickerAction *m_pColorPickerAction; - - // Clear track metadata actions - QAction* m_pClearBeatsAction; - QAction* m_pClearPlayCountAction; - QAction* m_pClearMainCueAction; - QAction* m_pClearHotCuesAction; - QAction* m_pClearIntroCueAction; - QAction* m_pClearOutroCueAction; - QAction* m_pClearLoopAction; - QAction* m_pClearWaveformAction; - QAction* m_pClearKeyAction; - QAction* m_pClearReplayGainAction; - QAction* m_pClearAllMetadataAction; - - struct UpdateExternalTrackCollection { - QPointer externalTrackCollection; - QAction* action; - }; - QList m_updateInExternalTrackCollections; + // Context menu container + parented_ptr m_pTrackMenu; + const double m_backgroundColorOpacity; bool m_sorting; - // Column numbers - int m_iCoverSourceColumn; // cover art source - int m_iCoverTypeColumn; // cover art type - int m_iCoverLocationColumn; // cover art location - int m_iCoverHashColumn; // cover art hash - int m_iCoverColumn; // visible cover art - int m_iTrackLocationColumn; - // Control the delay to load a cover art. mixxx::Duration m_lastUserAction; bool m_selectionChangedSinceLastGuiTick; bool m_loadCachedOnly; - bool m_bPlaylistMenuLoaded; - bool m_bCrateMenuLoaded; + ControlProxy* m_pCOTGuiTick; ControlProxy* m_pKeyNotation; ControlProxy* m_pSortColumn; diff --git a/src/widget/wtracktext.cpp b/src/widget/wtracktext.cpp index 821d59c9e9e..359f98412cb 100644 --- a/src/widget/wtracktext.cpp +++ b/src/widget/wtracktext.cpp @@ -6,10 +6,27 @@ #include "widget/wtracktext.h" #include "util/dnd.h" -WTrackText::WTrackText(const char *group, UserSettingsPointer pConfig, QWidget* pParent) +namespace { +const WTrackMenu::Features trackMenuFeatures = + WTrackMenu::Feature::Playlist | + WTrackMenu::Feature::Crate | + WTrackMenu::Feature::Metadata | + WTrackMenu::Feature::Reset | + WTrackMenu::Feature::BPM | + WTrackMenu::Feature::Color | + WTrackMenu::Feature::FileBrowser | + WTrackMenu::Feature::Properties; +} + +WTrackText::WTrackText(QWidget* pParent, + UserSettingsPointer pConfig, + TrackCollectionManager* pTrackCollectionManager, + const char* group) : WLabel(pParent), m_pGroup(group), - m_pConfig(pConfig) { + m_pConfig(pConfig), + m_pTrackMenu(make_parented( + this, pConfig, pTrackCollectionManager, trackMenuFeatures)) { setAcceptDrops(true); } @@ -60,3 +77,11 @@ void WTrackText::dragEnterEvent(QDragEnterEvent *event) { void WTrackText::dropEvent(QDropEvent *event) { DragAndDropHelper::handleTrackDropEvent(event, *this, m_pGroup, m_pConfig); } + +void WTrackText::contextMenuEvent(QContextMenuEvent* event) { + if (m_pCurrentTrack) { + m_pTrackMenu->loadTrack(m_pCurrentTrack->getId()); + // Create the right-click menu + m_pTrackMenu->popup(event->globalPos()); + } +} diff --git a/src/widget/wtracktext.h b/src/widget/wtracktext.h index c6e5f79a961..c726d63fd08 100644 --- a/src/widget/wtracktext.h +++ b/src/widget/wtracktext.h @@ -7,13 +7,19 @@ #include "preferences/usersettings.h" #include "track/track.h" +#include "util/parented_ptr.h" #include "widget/trackdroptarget.h" #include "widget/wlabel.h" +#include "widget/wtrackmenu.h" class WTrackText : public WLabel, public TrackDropTarget { Q_OBJECT public: - WTrackText(const char* group, UserSettingsPointer pConfig, QWidget *pParent); + WTrackText( + QWidget* pParent, + UserSettingsPointer pConfig, + TrackCollectionManager* pTrackCollectionManager, + const char* group); signals: void trackDropped(QString fileName, QString group) override; @@ -25,6 +31,7 @@ class WTrackText : public WLabel, public TrackDropTarget { private slots: void slotTrackChanged(TrackId); + void contextMenuEvent(QContextMenuEvent* event) override; private: void dragEnterEvent(QDragEnterEvent *event) override; @@ -36,6 +43,7 @@ class WTrackText : public WLabel, public TrackDropTarget { const char* m_pGroup; UserSettingsPointer m_pConfig; TrackPointer m_pCurrentTrack; + const parented_ptr m_pTrackMenu; };