1 #include "library/autodj/dlgautodj.h"
2 
3 #include <QMessageBox>
4 
5 #include "library/playlisttablemodel.h"
6 #include "library/trackcollectionmanager.h"
7 #include "moc_dlgautodj.cpp"
8 #include "track/track.h"
9 #include "util/assert.h"
10 #include "util/compatibility.h"
11 #include "util/duration.h"
12 #include "widget/wlibrary.h"
13 #include "widget/wtracktableview.h"
14 
15 namespace {
16 const char* kPreferenceGroupName = "[Auto DJ]";
17 const char* kRepeatPlaylistPreference = "Requeue";
18 } // anonymous namespace
19 
DlgAutoDJ(WLibrary * parent,UserSettingsPointer pConfig,Library * pLibrary,AutoDJProcessor * pProcessor,KeyboardEventFilter * pKeyboard)20 DlgAutoDJ::DlgAutoDJ(
21         WLibrary* parent,
22         UserSettingsPointer pConfig,
23         Library* pLibrary,
24         AutoDJProcessor* pProcessor,
25         KeyboardEventFilter* pKeyboard)
26         : QWidget(parent),
27           Ui::DlgAutoDJ(),
28           m_pConfig(pConfig),
29           m_pAutoDJProcessor(pProcessor),
30           m_pTrackTableView(new WTrackTableView(this, m_pConfig,
31                                                 pLibrary->trackCollections(),
32                                                 parent->getTrackTableBackgroundColorOpacity(),
33                                                 /*no sorting*/ false)),
34           m_bShowButtonText(parent->getShowButtonText()),
35           m_pAutoDJTableModel(nullptr) {
36     setupUi(this);
37 
38     m_pTrackTableView->installEventFilter(pKeyboard);
39 
40     connect(m_pTrackTableView,
41             &WTrackTableView::loadTrack,
42             this,
43             &DlgAutoDJ::loadTrack);
44     connect(m_pTrackTableView,
45             &WTrackTableView::loadTrackToPlayer,
46             this,
47             &DlgAutoDJ::loadTrackToPlayer);
48     connect(m_pTrackTableView,
49             &WTrackTableView::trackSelected,
50             this,
51             &DlgAutoDJ::trackSelected);
52     connect(m_pTrackTableView,
53             &WTrackTableView::trackSelected,
54             this,
55             &DlgAutoDJ::updateSelectionInfo);
56 
57     connect(pLibrary,
58             &Library::setTrackTableFont,
59             m_pTrackTableView,
60             &WTrackTableView::setTrackTableFont);
61     connect(pLibrary,
62             &Library::setTrackTableRowHeight,
63             m_pTrackTableView,
64             &WTrackTableView::setTrackTableRowHeight);
65     connect(pLibrary,
66             &Library::setSelectedClick,
67             m_pTrackTableView,
68             &WTrackTableView::setSelectedClick);
69 
70     QBoxLayout* box = qobject_cast<QBoxLayout*>(layout());
71     VERIFY_OR_DEBUG_ASSERT(box) { //Assumes the form layout is a QVBox/QHBoxLayout!
72     } else {
73         box->removeWidget(m_pTrackTablePlaceholder);
74         m_pTrackTablePlaceholder->hide();
75         box->insertWidget(1, m_pTrackTableView);
76     }
77 
78     // We do _NOT_ take ownership of this from AutoDJProcessor.
79     m_pAutoDJTableModel = m_pAutoDJProcessor->getTableModel();
80     m_pTrackTableView->loadTrackModel(m_pAutoDJTableModel);
81 
82     // Override some playlist-view properties:
83 
84     // Do not set this because it disables auto-scrolling
85     //m_pTrackTableView->setDragDropMode(QAbstractItemView::InternalMove);
86 
87     connect(pushButtonAutoDJ,
88             &QPushButton::clicked,
89             this,
90             &DlgAutoDJ::toggleAutoDJButton);
91 
92     setupActionButton(pushButtonFadeNow, &DlgAutoDJ::fadeNowButton, tr("Fade"));
93     setupActionButton(pushButtonSkipNext, &DlgAutoDJ::skipNextButton, tr("Skip"));
94     setupActionButton(pushButtonShuffle, &DlgAutoDJ::shufflePlaylistButton, tr("Shuffle"));
95     setupActionButton(pushButtonAddRandom, &DlgAutoDJ::addRandomButton, tr("Random"));
96 
97     m_enableBtnTooltip = tr(
98             "Enable Auto DJ\n"
99             "\n"
100             "Shortcut: Shift+F12");
101     m_disableBtnTooltip = tr(
102             "Disable Auto DJ\n"
103             "\n"
104             "Shortcut: Shift+F12");
105     QString fadeBtnTooltip = tr(
106             "Trigger the transition to the next track\n"
107             "\n"
108             "Shortcut: Shift+F11");
109     QString skipBtnTooltip = tr(
110             "Skip the next track in the Auto DJ queue\n"
111             "\n"
112             "Shortcut: Shift+F10");
113     QString shuffleBtnTooltip = tr(
114             "Shuffle the content of the Auto DJ queue\n"
115             "\n"
116             "Shortcut: Shift+F9");
117     QString addRandomBtnTooltip = tr(
118             "Adds a random track from track sources (crates) to the Auto DJ queue.\n"
119             "If no track sources are configured, the track is added from the library instead.");
120     QString repeatBtnTooltip = tr(
121             "Repeat the playlist");
122     QString spinBoxTransitionTooltip = tr(
123             "Determines the duration of the transition");
124     QString labelTransitionTooltip = tr(
125             // "sec" as in seconds
126             "Seconds");
127     QString fadeModeTooltip = tr(
128             "Auto DJ Fade Modes\n"
129             "\n"
130             "Full Intro + Outro:\n"
131             "Play the full intro and outro. Use the intro or outro length as the\n"
132             "crossfade time, whichever is shorter. If no intro or outro are marked,\n"
133             "use the selected crossfade time.\n"
134             "\n"
135             "Fade At Outro Start:\n"
136             "Start crossfading at the outro start. If the outro is longer than the\n"
137             "intro, cut off the end of the outro. Use the intro or outro length as\n"
138             "the crossfade time, whichever is shorter. If no intro or outro are\n"
139             "marked, use the selected crossfade time.\n"
140             "\n"
141             "Full Track:\n"
142             "Play the whole track. Begin crossfading from the selected number of\n"
143             "seconds before the end of the track. A negative crossfade time adds\n"
144             "silence between tracks.\n"
145             "\n"
146             "Skip Silence:\n"
147             "Play the whole track except for silence at the beginning and end.\n"
148             "Begin crossfading from the selected number of seconds before the\n"
149             "last sound.");
150 
151     pushButtonFadeNow->setToolTip(fadeBtnTooltip);
152     pushButtonSkipNext->setToolTip(skipBtnTooltip);
153     pushButtonShuffle->setToolTip(shuffleBtnTooltip);
154     pushButtonAddRandom->setToolTip(addRandomBtnTooltip);
155     pushButtonRepeatPlaylist->setToolTip(repeatBtnTooltip);
156     spinBoxTransition->setToolTip(spinBoxTransitionTooltip);
157     labelTransitionAppendix->setToolTip(labelTransitionTooltip);
158     fadeModeCombobox->setToolTip(fadeModeTooltip);
159 
160     // Prevent the interactive widgets from being focused with Tab or Shift+Tab
161     fadeModeCombobox->setFocusPolicy(Qt::ClickFocus);
162     spinBoxTransition->setFocusPolicy(Qt::ClickFocus);
163     // work around QLineEdit being protected
164     spinBoxTransition->findChild<QLineEdit*>()->setFocusPolicy(Qt::ClickFocus);
165 
166     connect(spinBoxTransition,
167             QOverload<int>::of(&QSpinBox::valueChanged),
168             this,
169             &DlgAutoDJ::transitionSliderChanged);
170 
171     fadeModeCombobox->addItem(tr("Full Intro + Outro"),
172             static_cast<int>(AutoDJProcessor::TransitionMode::FullIntroOutro));
173     fadeModeCombobox->addItem(tr("Fade At Outro Start"),
174             static_cast<int>(AutoDJProcessor::TransitionMode::FadeAtOutroStart));
175     fadeModeCombobox->addItem(tr("Full Track"),
176             static_cast<int>(AutoDJProcessor::TransitionMode::FixedFullTrack));
177     fadeModeCombobox->addItem(tr("Skip Silence"),
178             static_cast<int>(AutoDJProcessor::TransitionMode::FixedSkipSilence));
179     fadeModeCombobox->setCurrentIndex(
180             fadeModeCombobox->findData(static_cast<int>(m_pAutoDJProcessor->getTransitionMode())));
181     connect(fadeModeCombobox,
182             QOverload<int>::of(&QComboBox::currentIndexChanged),
183             this,
184             &DlgAutoDJ::slotTransitionModeChanged);
185 
186     connect(pushButtonRepeatPlaylist,
187             &QPushButton::clicked,
188             this,
189             &DlgAutoDJ::slotRepeatPlaylistChanged);
190     if (m_bShowButtonText) {
191         pushButtonRepeatPlaylist->setText(tr("Repeat"));
192     }
193     bool repeatPlaylist = m_pConfig->getValue<bool>(
194             ConfigKey(kPreferenceGroupName, kRepeatPlaylistPreference));
195     pushButtonRepeatPlaylist->setChecked(repeatPlaylist);
196     slotRepeatPlaylistChanged(repeatPlaylist);
197 
198     // Setup DlgAutoDJ UI based on the current AutoDJProcessor state. Keep in
199     // mind that AutoDJ may already be active when DlgAutoDJ is created (due to
200     // skin changes, etc.).
201     spinBoxTransition->setValue(static_cast<int>(m_pAutoDJProcessor->getTransitionTime()));
202     connect(m_pAutoDJProcessor,
203             &AutoDJProcessor::transitionTimeChanged,
204             this,
205             &DlgAutoDJ::transitionTimeChanged);
206 
207     connect(m_pAutoDJProcessor,
208             &AutoDJProcessor::autoDJStateChanged,
209             this,
210             &DlgAutoDJ::autoDJStateChanged);
211     autoDJStateChanged(m_pAutoDJProcessor->getState());
212 
213     updateSelectionInfo();
214 }
215 
~DlgAutoDJ()216 DlgAutoDJ::~DlgAutoDJ() {
217     qDebug() << "~DlgAutoDJ()";
218 
219     // Delete m_pTrackTableView before the table model. This is because the
220     // table view saves the header state using the model.
221     delete m_pTrackTableView;
222 }
223 
setupActionButton(QPushButton * pButton,void (DlgAutoDJ::* pSlot)(bool),const QString & fallbackText)224 void DlgAutoDJ::setupActionButton(QPushButton* pButton,
225         void (DlgAutoDJ::*pSlot)(bool),
226         const QString& fallbackText) {
227     connect(pButton, &QPushButton::clicked, this, pSlot);
228     if (m_bShowButtonText) {
229         pButton->setText(fallbackText);
230     }
231 }
232 
onShow()233 void DlgAutoDJ::onShow() {
234     m_pAutoDJTableModel->select();
235 }
236 
onSearch(const QString & text)237 void DlgAutoDJ::onSearch(const QString& text) {
238     // Do not allow filtering the Auto DJ playlist, because
239     // Auto DJ will work from the filtered table
240     Q_UNUSED(text);
241 }
242 
loadSelectedTrack()243 void DlgAutoDJ::loadSelectedTrack() {
244     m_pTrackTableView->loadSelectedTrack();
245 }
246 
loadSelectedTrackToGroup(const QString & group,bool play)247 void DlgAutoDJ::loadSelectedTrackToGroup(const QString& group, bool play) {
248     m_pTrackTableView->loadSelectedTrackToGroup(group, play);
249 }
250 
moveSelection(int delta)251 void DlgAutoDJ::moveSelection(int delta) {
252     m_pTrackTableView->moveSelection(delta);
253 }
254 
shufflePlaylistButton(bool)255 void DlgAutoDJ::shufflePlaylistButton(bool) {
256     QModelIndexList indexList = m_pTrackTableView->selectionModel()->selectedRows();
257 
258     // Activate regardless of button being checked
259     m_pAutoDJProcessor->shufflePlaylist(indexList);
260 }
261 
skipNextButton(bool)262 void DlgAutoDJ::skipNextButton(bool) {
263     // Activate regardless of button being checked
264     m_pAutoDJProcessor->skipNext();
265 }
266 
fadeNowButton(bool)267 void DlgAutoDJ::fadeNowButton(bool) {
268     // Activate regardless of button being checked
269     m_pAutoDJProcessor->fadeNow();
270 }
271 
toggleAutoDJButton(bool enable)272 void DlgAutoDJ::toggleAutoDJButton(bool enable) {
273     AutoDJProcessor::AutoDJError error = m_pAutoDJProcessor->toggleAutoDJ(enable);
274     switch (error) {
275         case AutoDJProcessor::ADJ_BOTH_DECKS_PLAYING:
276             QMessageBox::warning(nullptr,
277                     tr("Auto DJ"),
278                     tr("One deck must be stopped to enable Auto DJ mode."),
279                     QMessageBox::Ok);
280             // Make sure the button becomes unpushed.
281             pushButtonAutoDJ->setChecked(false);
282             break;
283         case AutoDJProcessor::ADJ_DECKS_3_4_PLAYING:
284             QMessageBox::warning(nullptr,
285                     tr("Auto DJ"),
286                     tr("Decks 3 and 4 must be stopped to enable Auto DJ mode."),
287                     QMessageBox::Ok);
288             pushButtonAutoDJ->setChecked(false);
289             break;
290         case AutoDJProcessor::ADJ_OK:
291         default:
292             break;
293     }
294 }
295 
transitionTimeChanged(int time)296 void DlgAutoDJ::transitionTimeChanged(int time) {
297     spinBoxTransition->setValue(time);
298 }
299 
transitionSliderChanged(int value)300 void DlgAutoDJ::transitionSliderChanged(int value) {
301     m_pAutoDJProcessor->setTransitionTime(value);
302 }
303 
autoDJStateChanged(AutoDJProcessor::AutoDJState state)304 void DlgAutoDJ::autoDJStateChanged(AutoDJProcessor::AutoDJState state) {
305     if (state == AutoDJProcessor::ADJ_DISABLED) {
306         pushButtonAutoDJ->setChecked(false);
307         pushButtonAutoDJ->setToolTip(m_enableBtnTooltip);
308         if (m_bShowButtonText) {
309             pushButtonAutoDJ->setText(tr("Enable"));
310         }
311         pushButtonFadeNow->setEnabled(false);
312         pushButtonSkipNext->setEnabled(false);
313     } else {
314         // No matter the mode, you can always disable once it is enabled.
315         pushButtonAutoDJ->setChecked(true);
316         pushButtonAutoDJ->setToolTip(m_disableBtnTooltip);
317         if (m_bShowButtonText) {
318             pushButtonAutoDJ->setText(tr("Disable"));
319         }
320 
321         // If fading, you can't hit fade now.
322         if (state == AutoDJProcessor::ADJ_LEFT_FADING ||
323                 state == AutoDJProcessor::ADJ_RIGHT_FADING ||
324                 state == AutoDJProcessor::ADJ_ENABLE_P1LOADED) {
325             pushButtonFadeNow->setEnabled(false);
326         } else {
327             pushButtonFadeNow->setEnabled(true);
328         }
329 
330         // You can always skip the next track if we are enabled.
331         pushButtonSkipNext->setEnabled(true);
332     }
333 }
334 
slotTransitionModeChanged(int comboboxIndex)335 void DlgAutoDJ::slotTransitionModeChanged(int comboboxIndex) {
336     m_pAutoDJProcessor->setTransitionMode(static_cast<AutoDJProcessor::TransitionMode>(
337             fadeModeCombobox->itemData(comboboxIndex).toInt()));
338 }
339 
slotRepeatPlaylistChanged(int checkState)340 void DlgAutoDJ::slotRepeatPlaylistChanged(int checkState) {
341     bool checked = static_cast<bool>(checkState);
342     m_pConfig->setValue(ConfigKey(kPreferenceGroupName, kRepeatPlaylistPreference),
343             checked);
344 }
345 
updateSelectionInfo()346 void DlgAutoDJ::updateSelectionInfo() {
347     double duration = 0.0;
348 
349     QModelIndexList indices = m_pTrackTableView->selectionModel()->selectedRows();
350 
351     for (int i = 0; i < indices.size(); ++i) {
352         TrackPointer pTrack = m_pAutoDJTableModel->getTrack(indices.at(i));
353         if (pTrack) {
354             duration += pTrack->getDuration();
355         }
356     }
357 
358     QString label;
359 
360     if (!indices.isEmpty()) {
361         label.append(mixxx::DurationBase::formatTime(duration));
362         label.append(QString(" (%1)").arg(indices.size()));
363         labelSelectionInfo->setToolTip(tr("Displays the duration and number of selected tracks."));
364         labelSelectionInfo->setText(label);
365         labelSelectionInfo->setEnabled(true);
366     } else {
367         labelSelectionInfo->setText("");
368         labelSelectionInfo->setEnabled(false);
369     }
370 }
371 
hasFocus() const372 bool DlgAutoDJ::hasFocus() const {
373     return m_pTrackTableView->hasFocus();
374 }
375