diff --git a/src/library/autodj/autodjfeature.cpp b/src/library/autodj/autodjfeature.cpp index 5766387b2cfd..af34ed790757 100644 --- a/src/library/autodj/autodjfeature.cpp +++ b/src/library/autodj/autodjfeature.cpp @@ -37,6 +37,31 @@ int findOrCreateAutoDjPlaylistId(PlaylistDAO& playlistDAO) { } return playlistId; } + +/// Create a title for the Auto DJ node +QString createAutoDjTitle(const QString& name, + int count, + mixxx::Duration duration, + bool showCountRemaining, + bool showTimeRemaining) { + QString result(name); + + // Show duration and track count only if Auto DJ queue has tracks + if (count > 0 && showCountRemaining) { + result.append(QStringLiteral(" (")); + result.append(QString::number(count)); + result.append(QStringLiteral(")")); + } + + if (count > 0 && showTimeRemaining) { + result.append(QStringLiteral(" ")); + result.append(mixxx::Duration::formatTime( + duration.toDoubleSeconds(), + mixxx::Duration::Precision::SECONDS)); + } + + return result; +} } // anonymous namespace AutoDJFeature::AutoDJFeature(Library* pLibrary, @@ -66,6 +91,13 @@ AutoDJFeature::AutoDJFeature(Library* pLibrary, &LibraryFeature::loadTrackToPlayer, Qt::QueuedConnection); + // Update the title of the "Auto DJ" node when the + // list of queued tracks or their properties have changed. + connect(m_pAutoDJProcessor, + &AutoDJProcessor::queueDurationChanged, + this, + &AutoDJFeature::slotRemainingQueueDurationChanged); + m_playlistDao.setAutoDJProcessor(m_pAutoDJProcessor); // Create the "Crates" tree-item under the root item. @@ -135,7 +167,20 @@ AutoDJFeature::~AutoDJFeature() { } QVariant AutoDJFeature::title() { - return tr("Auto DJ"); + return createAutoDjTitle(tr("Auto DJ"), + m_pAutoDJProcessor->getQueueTrackCount(), + m_pAutoDJProcessor->getQueueDuration(), + true, + true); +} + +void AutoDJFeature::slotRemainingQueueDurationChanged(int numTracks, mixxx::Duration duration) { + Q_UNUSED(numTracks); + Q_UNUSED(duration); + + // As documented by the code docs for featureIsLoading, + // it is intended to indicate when the title() has changed. + emit featureIsLoading(this, false); } void AutoDJFeature::bindLibraryWidget( diff --git a/src/library/autodj/autodjfeature.h b/src/library/autodj/autodjfeature.h index 3b30b7ec2f1e..751a1073dda2 100644 --- a/src/library/autodj/autodjfeature.h +++ b/src/library/autodj/autodjfeature.h @@ -10,6 +10,7 @@ #include "library/libraryfeature.h" #include "library/trackset/crate/crate.h" #include "preferences/usersettings.h" +#include "util/duration.h" #include "util/parented_ptr.h" class DlgAutoDJ; @@ -107,4 +108,9 @@ class AutoDJFeature : public LibraryFeature { // Adds a random track from the queue upon hitting minimum number // of tracks in the playlist void slotRandomQueue(int numTracksToAdd); + + // Updates the title of the "Auto DJ" node with the number of tracks + // and remaining duration when tracks are added to or removed from + // the Auto DJ queue. + void slotRemainingQueueDurationChanged(int numTracks, mixxx::Duration duration); }; diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 466e75ab1f4d..cbfe04081fdb 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -131,7 +131,9 @@ AutoDJProcessor::AutoDJProcessor( m_skipNext(ConfigKey(kControlGroup, QStringLiteral("skip_next"))), m_addRandomTrack(ConfigKey(kControlGroup, QStringLiteral("add_random_track"))), m_fadeNow(ConfigKey(kControlGroup, QStringLiteral("fade_now"))), - m_enabledAutoDJ(ConfigKey(kControlGroup, QStringLiteral("enabled"))) { + m_enabledAutoDJ(ConfigKey(kControlGroup, QStringLiteral("enabled"))), + m_queueRemainingTracks(ConfigKey(kControlGroup, QStringLiteral("queue_tracks"))), + m_queueRemainingDuration(ConfigKey(kControlGroup, QStringLiteral("queue_duration"))) { m_pAutoDJTableModel = make_parented( this, pTrackCollectionManager, "mixxx.db.model.autodj"); m_pAutoDJTableModel->selectPlaylist(iAutoDJPlaylistId); @@ -151,6 +153,11 @@ AutoDJProcessor::AutoDJProcessor( m_enabledAutoDJ.connectValueChangeRequest(this, &AutoDJProcessor::controlEnableChangeRequest); + connect(m_pAutoDJTableModel, + &PlaylistTableModel::playlistTracksChanged, + this, + &AutoDJProcessor::playlistTracksChanged); + connect(pPlayerManager, &PlayerManagerInterface::numberOfDecksChanged, this, @@ -166,6 +173,9 @@ AutoDJProcessor::AutoDJProcessor( m_transitionMode = m_pConfig->getValue( ConfigKey(kPreferenceGroup, kTransitionModePreferenceName), TransitionMode::FullIntroOutro); + + // Calculate the initial values for track count and time remaining + playlistTracksChanged(); } void AutoDJProcessor::slotNumberOfDecksChanged(int decks) { @@ -196,6 +206,18 @@ void AutoDJProcessor::setCrossfader(double value) { m_coCrossfader.set(value); } +void AutoDJProcessor::playlistTracksChanged() { + const int numTracksInQueue = m_pAutoDJTableModel->rowCount(); + m_queueRemainingTracks.set(numTracksInQueue); + m_queueDuration = m_pAutoDJTableModel->getTotalDuration(); + m_queueRemainingDuration.set(m_queueDuration.toDoubleSeconds()); + emit queueDurationChanged(numTracksInQueue, m_queueDuration); +} + +int AutoDJProcessor::getQueueTrackCount() const { + return m_pAutoDJTableModel->rowCount(); +} + AutoDJProcessor::AutoDJError AutoDJProcessor::shufflePlaylist( const QModelIndexList& selectedIndices) { QModelIndex exclude; diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index 61e7dc9acc50..b049f1f44741 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -196,6 +196,15 @@ class AutoDJProcessor : public QObject { return m_pAutoDJTableModel; } + /// Gets the total remaining duration of tracks in the AutoDJ playlist, + /// excluding the track that is currently playing already. + mixxx::Duration getQueueDuration() const { + return m_queueDuration; + } + + /// Gets the number of tracks remaining in the Auto DJ queue. + int getQueueTrackCount() const; + bool nextTrackLoaded(); void setTransitionTime(int seconds); @@ -218,6 +227,7 @@ class AutoDJProcessor : public QObject { #endif void autoDJStateChanged(AutoDJProcessor::AutoDJState state); void autoDJError(AutoDJProcessor::AutoDJError error); + void queueDurationChanged(int numTracks, mixxx::Duration duration); void transitionTimeChanged(int time); void randomTrackRequested(int tracksToAdd); @@ -236,6 +246,8 @@ class AutoDJProcessor : public QObject { void playerOrientationChanged(DeckAttributes* pDeck); void playlistFirstTrackChanged(); + void playlistTracksChanged(); + void controlEnableChangeRequest(double value); void controlFadeNow(double value); void controlShuffle(double value); @@ -320,5 +332,9 @@ class AutoDJProcessor : public QObject { ControlPushButton m_fadeNow; ControlPushButton m_enabledAutoDJ; + ControlObject m_queueRemainingTracks; + ControlObject m_queueRemainingDuration; + mixxx::Duration m_queueDuration; + DISALLOW_COPY_AND_ASSIGN(AutoDJProcessor); }; diff --git a/src/library/autodj/dlgautodj.cpp b/src/library/autodj/dlgautodj.cpp index 7b9f31552765..98f77c23922a 100644 --- a/src/library/autodj/dlgautodj.cpp +++ b/src/library/autodj/dlgautodj.cpp @@ -212,6 +212,11 @@ DlgAutoDJ::DlgAutoDJ(WLibrary* parent, this, &DlgAutoDJ::transitionTimeChanged); + connect(m_pAutoDJProcessor, + &AutoDJProcessor::queueDurationChanged, + this, + &DlgAutoDJ::queueDurationChanged); + connect(m_pAutoDJProcessor, &AutoDJProcessor::autoDJError, this, @@ -310,6 +315,12 @@ void DlgAutoDJ::transitionSliderChanged(int value) { m_pAutoDJProcessor->setTransitionTime(value); } +void DlgAutoDJ::queueDurationChanged(int numTracks, mixxx::Duration duration) { + Q_UNUSED(numTracks); + Q_UNUSED(duration); + updateSelectionInfo(); +} + void DlgAutoDJ::autoDJStateChanged(AutoDJProcessor::AutoDJState state) { if (state == AutoDJProcessor::ADJ_DISABLED) { pushButtonAutoDJ->setChecked(false); @@ -355,24 +366,34 @@ void DlgAutoDJ::slotRepeatPlaylistChanged(bool checked) { } void DlgAutoDJ::updateSelectionInfo() { + // Obtain the total duration of the whole remaining Auto DJ queue + // from the Auto DJ processor. The calculated time is exact and + // takes transition times, intros, outros etc. into account. + mixxx::Duration totalDuration = m_pAutoDJProcessor->getQueueDuration(); + int totalTracks = m_pAutoDJTableModel->rowCount(); + + // Derive total duration of the selected tracks from the table model. + // This is much faster than getting the duration from individual track + // objects (but does not take transition times into account...) QModelIndexList indices = m_pTrackTableView->selectionModel()->selectedRows(); + mixxx::Duration selectedDuration = m_pAutoDJTableModel->getTotalDuration(indices); + int selectedTracks = indices.size(); - // Derive total duration from the table model. This is much faster than - // getting the duration from individual track objects. - mixxx::Duration duration = m_pAutoDJTableModel->getTotalDuration(indices); - + // Selected tracks QString label; - if (!indices.isEmpty()) { - label.append(mixxx::DurationBase::formatTime(duration.toDoubleSeconds())); - label.append(QString(" (%1)").arg(indices.size())); - labelSelectionInfo->setToolTip(tr("Displays the duration and number of selected tracks.")); - labelSelectionInfo->setText(label); - labelSelectionInfo->setEnabled(true); - } else { - labelSelectionInfo->setText(""); - labelSelectionInfo->setEnabled(false); + label.append(mixxx::DurationBase::formatTime(selectedDuration.toDoubleSeconds())); + label.append(QString(" (%1)").arg(selectedTracks)); + label.append(tr(" / ")); } + + // Total tracks + label.append(mixxx::DurationBase::formatTime(totalDuration.toDoubleSeconds())); + label.append(QString(" (%1)").arg(totalTracks)); + + labelSelectionInfo->setToolTip(tr("Displays the duration and number of selected tracks.")); + labelSelectionInfo->setText(label); + labelSelectionInfo->setEnabled(true); } bool DlgAutoDJ::hasFocus() const { diff --git a/src/library/autodj/dlgautodj.h b/src/library/autodj/dlgautodj.h index 1e6d396f8b54..856a7e1b7130 100644 --- a/src/library/autodj/dlgautodj.h +++ b/src/library/autodj/dlgautodj.h @@ -41,6 +41,7 @@ class DlgAutoDJ : public QWidget, public Ui::DlgAutoDJ, public LibraryView { void autoDJError(AutoDJProcessor::AutoDJError error); void transitionTimeChanged(int time); void transitionSliderChanged(int value); + void queueDurationChanged(int numTracks, mixxx::Duration duration); void autoDJStateChanged(AutoDJProcessor::AutoDJState state); void updateSelectionInfo(); void slotTransitionModeChanged(int comboboxIndex); diff --git a/src/library/playlisttablemodel.cpp b/src/library/playlisttablemodel.cpp index ffdc7b49147c..5e9b18c16501 100644 --- a/src/library/playlisttablemodel.cpp +++ b/src/library/playlisttablemodel.cpp @@ -373,6 +373,19 @@ const QList PlaylistTableModel::getSelectedPositions(const QModelIndexList& return positions; } +mixxx::Duration PlaylistTableModel::getTotalDuration() { + double durationTotal = 0.0; + const int durationColumnIndex = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DURATION); + int numOfTracks = rowCount(); + for (int i = 0; i < numOfTracks; i++) { + durationTotal += index(i, durationColumnIndex) + .data(Qt::EditRole) + .toDouble(); + } + + return mixxx::Duration::fromSeconds(durationTotal); +} + mixxx::Duration PlaylistTableModel::getTotalDuration(const QModelIndexList& indices) { if (indices.isEmpty()) { return mixxx::Duration::empty(); @@ -455,5 +468,6 @@ QString PlaylistTableModel::modelKey(bool noSearch) const { void PlaylistTableModel::playlistsChanged(const QSet& playlistIds) { if (playlistIds.contains(m_iPlaylistId)) { select(); // Repopulate the data model. + emit playlistTracksChanged(); } } diff --git a/src/library/playlisttablemodel.h b/src/library/playlisttablemodel.h index d8b98b5fa022..3a0e5c353369 100644 --- a/src/library/playlisttablemodel.h +++ b/src/library/playlisttablemodel.h @@ -35,6 +35,9 @@ class PlaylistTableModel final : public TrackSetTableModel { int* pOutInsertionPos) final; bool isLocked() final; + /// Get the total duration of all tracks in the selected playlist + mixxx::Duration getTotalDuration(); + /// Get the total duration of all tracks referenced by the given model indices mixxx::Duration getTotalDuration(const QModelIndexList& indices); const QList getSelectedPositions(const QModelIndexList& indices) const override; @@ -43,6 +46,9 @@ class PlaylistTableModel final : public TrackSetTableModel { QString modelKey(bool noSearch) const override; + signals: + void playlistTracksChanged(); + private slots: void playlistsChanged(const QSet& playlistIds);