diff --git a/src/MediaPlayListView.qml b/src/MediaPlayListView.qml index c241d51c..033c9b66 100644 --- a/src/MediaPlayListView.qml +++ b/src/MediaPlayListView.qml @@ -1,285 +1,296 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.5 import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import QtQml.Models 2.1 import org.mgallien.QmlExtension 1.0 import QtMultimedia 5.4 Item { property StackView parentStackView property MediaPlayList playListModel property var playListControler property alias randomPlayChecked: shuffleOption.checked property alias repeatPlayChecked: repeatOption.checked id: topItem SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Component { id: rowDelegate Rectangle { height: Screen.pixelDensity * 15. anchors.fill: parent } } Action { id: clearPlayList text: i18nc("Remove all tracks from play list", "Clear Play List") iconName: "list-remove" enabled: playListModelDelegate.items.count > 0 onTriggered: { var selectedItems = [] var myGroup = playListModelDelegate.items for (var i = 0; i < myGroup.count; ++i) { var myItem = myGroup.get(i) selectedItems.push(myItem.itemsIndex) } playListModel.removeSelection(selectedItems) } } + Action { + id: showCurrentTrack + text: i18nc("Show currently played track inside playlist", "Show Current Track") + iconName: 'media-show-active-track-amarok' + enabled: playListModelDelegate.items.count > 0 + onTriggered: playListView.positionViewAtRow(playListControler.currentTrackRow, ListView.Contain) + } + ColumnLayout { anchors.fill: parent spacing: 0 Text { text: i18nc("text shown at the top of the play list", "Playlist") Layout.topMargin: Screen.pixelDensity * 1.5 Layout.leftMargin: Screen.pixelDensity * 1.5 Layout.rightMargin: Screen.pixelDensity * 1.5 Layout.bottomMargin: Screen.pixelDensity * 3.5 } RowLayout { Layout.preferredHeight: Screen.pixelDensity * 5.5 Layout.minimumHeight: Screen.pixelDensity * 5.5 Layout.maximumHeight: Screen.pixelDensity * 5.5 Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true Layout.leftMargin: Screen.pixelDensity * 1. Layout.rightMargin: Screen.pixelDensity * 1. CheckBox { id: shuffleOption text: i18n("Shuffle") Layout.alignment: Qt.AlignVCenter } Item { Layout.preferredWidth: Screen.pixelDensity * 1.5 Layout.minimumWidth: Screen.pixelDensity * 1.5 Layout.maximumWidth: Screen.pixelDensity * 1.5 } CheckBox { id: repeatOption text: i18n("Repeat") Layout.alignment: Qt.AlignVCenter } Item { Layout.fillWidth: true } Text { id: playListInfo text: i18np("1 track", "%1 tracks", playListView.rowCount) Layout.alignment: Qt.AlignRight | Qt.AlignVCenter } } Item { Layout.preferredHeight: Screen.pixelDensity * 0.4 } Rectangle { border.width: 1 border.color: myPalette.mid color: myPalette.mid Layout.fillWidth: true Layout.preferredHeight: 1 Layout.minimumHeight: 1 Layout.maximumHeight: 1 Layout.leftMargin: Screen.pixelDensity * 2.5 Layout.rightMargin: Screen.pixelDensity * 2.5 } Item { Layout.preferredHeight: Screen.pixelDensity * 0.4 } TableView { id: playListView Layout.fillWidth: true Layout.fillHeight: true flickableItem.boundsBehavior: Flickable.StopAtBounds TextEdit { readOnly: true visible: playListModelDelegate.count === 0 wrapMode: TextEdit.Wrap renderType: TextEdit.NativeRendering font.weight: Font.ExtraLight font.pointSize: 12 text: i18nc("Text shown when play list is empty", "Your play list is empty.\nIn order to start, you can explore your music library with the left menu.\nUse the available buttons to add your selection.") anchors.fill: parent anchors.margins: Screen.pixelDensity * 2. } model: DelegateModel { id: playListModelDelegate model: playListModel groups: [ DelegateModelGroup { name: "selected" } ] delegate: DraggableItem { id: item PlayListEntry { id: entry width: playListView.width index: model.index isAlternateColor: item.DelegateModel.itemsIndex % 2 hasAlbumHeader: if (model != undefined && model.hasAlbumHeader !== undefined) model.hasAlbumHeader else true title: if (model != undefined && model.title !== undefined) model.title else '' artist: if (model != undefined && model.artist !== undefined) model.artist else '' itemDecoration: if (model != undefined && model.image !== undefined) model.image else '' duration: if (model != undefined && model.duration !== undefined) model.duration else '' trackNumber: if (model != undefined && model.trackNumber !== undefined) model.trackNumber else '' discNumber: if (model != undefined && model.discNumber !== undefined) model.discNumber else '' album: if (model != undefined && model.album !== undefined) model.album else '' isPlaying: model.isPlaying isSelected: item.DelegateModel.inSelected containsMouse: item.containsMouse playListModel: topItem.playListModel playListControler: topItem.playListControler contextMenu: Menu { MenuItem { action: entry.clearPlayListAction } MenuItem { action: entry.playNowAction } } } draggedItemParent: topItem onClicked: { var myGroup = playListModelDelegate.groups[2] if (myGroup.count > 0 && !DelegateModel.inSelected) { myGroup.remove(0, myGroup.count) } DelegateModel.inSelected = !DelegateModel.inSelected } onRightClicked: contentItem.contextMenu.popup() onMoveItemRequested: { playListModel.move(from, to, 1); } } } backgroundVisible: false headerVisible: false frameVisible: false focus: true rowDelegate: rowDelegate TableViewColumn { role: "title" title: "Title" } } ToolBar { id: actionBar Layout.fillWidth: true RowLayout { anchors.fill: parent ToolButton { action: clearPlayList } + ToolButton { + action: showCurrentTrack + } Item { Layout.fillWidth: true } } } } } diff --git a/src/playlistcontroler.cpp b/src/playlistcontroler.cpp index 8610325b..fd4e19a8 100644 --- a/src/playlistcontroler.cpp +++ b/src/playlistcontroler.cpp @@ -1,375 +1,381 @@ /* * Copyright 2016 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "playlistcontroler.h" #include #include #include #include PlayListControler::PlayListControler(QObject *parent) : QObject(parent) { } void PlayListControler::setPlayListModel(QAbstractItemModel *aPlayListModel) { if (mPlayListModel) { disconnect(mPlayListModel, &QAbstractItemModel::rowsInserted, this, &PlayListControler::tracksInserted); disconnect(mPlayListModel, &QAbstractItemModel::rowsRemoved, this, &PlayListControler::tracksRemoved); disconnect(mPlayListModel, &QAbstractItemModel::dataChanged, this, &PlayListControler::tracksDataChanged); disconnect(mPlayListModel, &QAbstractItemModel::modelReset, this, &PlayListControler::playListReset); disconnect(mPlayListModel, &QAbstractItemModel::layoutChanged, this, &PlayListControler::playListLayoutChanged); } mPlayListModel = aPlayListModel; connect(mPlayListModel, &QAbstractItemModel::rowsInserted, this, &PlayListControler::tracksInserted); connect(mPlayListModel, &QAbstractItemModel::rowsRemoved, this, &PlayListControler::tracksRemoved); connect(mPlayListModel, &QAbstractItemModel::dataChanged, this, &PlayListControler::tracksDataChanged); connect(mPlayListModel, &QAbstractItemModel::modelReset, this, &PlayListControler::playListReset); connect(mPlayListModel, &QAbstractItemModel::layoutChanged, this, &PlayListControler::playListLayoutChanged); Q_EMIT playListModelChanged(); playListReset(); } QAbstractItemModel *PlayListControler::playListModel() const { return mPlayListModel; } int PlayListControler::remainingTracks() const { if (!mCurrentTrack.isValid()) { return -1; } return mPlayListModel->rowCount(mCurrentTrack.parent()) - mCurrentTrack.row() - 1; } void PlayListControler::setRandomPlay(bool value) { mRandomPlay = value; Q_EMIT randomPlayChanged(); setRandomPlayControl(mRandomPlay); } bool PlayListControler::randomPlay() const { return mRandomPlay; } void PlayListControler::setRepeatPlay(bool value) { mRepeatPlay = value; Q_EMIT repeatPlayChanged(); setRepeatPlayControl(mRepeatPlay); } bool PlayListControler::repeatPlay() const { return mRepeatPlay; } int PlayListControler::isValidRole() const { return mIsValidRole; } QVariantMap PlayListControler::persistentState() const { auto persistentStateValue = QVariantMap(); persistentStateValue[QStringLiteral("currentTrack")] = mCurrentTrack.row(); persistentStateValue[QStringLiteral("randomPlay")] = mRandomPlay; persistentStateValue[QStringLiteral("repeatPlay")] = mRepeatPlay; return persistentStateValue; } bool PlayListControler::randomPlayControl() const { return mRandomPlayControl; } bool PlayListControler::repeatPlayControl() const { return mRepeatPlayControl; } QPersistentModelIndex PlayListControler::currentTrack() const { return mCurrentTrack; } +int PlayListControler::currentTrackRow() const +{ + return mCurrentTrack.row(); +} + void PlayListControler::playListReset() { if (!mCurrentTrack.isValid()) { resetCurrentTrack(); return; } } void PlayListControler::playListLayoutChanged(const QList &parents, QAbstractItemModel::LayoutChangeHint hint) { Q_UNUSED(parents); Q_UNUSED(hint); qDebug() << "PlayListControler::playListLayoutChanged" << "not implemented"; } void PlayListControler::tracksInserted(const QModelIndex &parent, int first, int last) { Q_UNUSED(parent); Q_UNUSED(first); Q_UNUSED(last); restorePlayListPosition(); if (!mCurrentTrack.isValid()) { resetCurrentTrack(); } } void PlayListControler::tracksDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { Q_UNUSED(topLeft); Q_UNUSED(bottomRight); Q_UNUSED(roles); if (!mCurrentTrack.isValid()) { resetCurrentTrack(); } } void PlayListControler::tracksRemoved(const QModelIndex &parent, int first, int last) { if (mCurrentTrack.parent() != parent) { return; } if (!mCurrentTrack.isValid()) { if (mCurrentTrackIsValid) { mCurrentTrack = mPlayListModel->index(mCurrentPlayListPosition, 0); notifyCurrentTrackChanged(); } if (!mCurrentTrack.isValid()) { Q_EMIT playListFinished(); } return; } if (mCurrentTrack.row() < first || mCurrentTrack.row() > last) { return; } if (mPlayListModel->rowCount(parent) <= first) { resetCurrentTrack(); return; } mCurrentTrack = mPlayListModel->index(first, 0); notifyCurrentTrackChanged(); } void PlayListControler::skipNextTrack() { if (!mPlayListModel) { return; } if (!mCurrentTrack.isValid()) { return; } if (!mRandomPlay && (mCurrentTrack.row() >= (mPlayListModel->rowCount() - 1))) { if (!mRepeatPlay) { Q_EMIT playListFinished(); } resetCurrentTrack(); return; } if (mRandomPlay) { int randomValue = qrand(); randomValue = randomValue % (mPlayListModel->rowCount()); mCurrentTrack = mPlayListModel->index(randomValue, 0); } else { mCurrentTrack = mPlayListModel->index(mCurrentTrack.row() + 1, 0); } notifyCurrentTrackChanged(); } void PlayListControler::skipPreviousTrack() { if (!mPlayListModel) { return; } if (!mCurrentTrack.isValid()) { return; } if (!mRandomPlay && !mRepeatPlay && mCurrentTrack.row() <= 0) { return; } if (mRandomPlay) { int randomValue = qrand(); randomValue = randomValue % (mPlayListModel->rowCount()); mCurrentTrack = mPlayListModel->index(randomValue, 0); } else { if (mRepeatPlay) { mCurrentTrack = mPlayListModel->index(mPlayListModel->rowCount() - 1, 0); } else { mCurrentTrack = mPlayListModel->index(mCurrentTrack.row() - 1, mCurrentTrack.column(), mCurrentTrack.parent()); } } notifyCurrentTrackChanged(); } void PlayListControler::seedRandomGenerator(uint seed) { qsrand(seed); } void PlayListControler::switchTo(int row) { if (!mPlayListModel) { return; } if (!mCurrentTrack.isValid()) { return; } mCurrentTrack = mPlayListModel->index(row, 0); notifyCurrentTrackChanged(); } void PlayListControler::setIsValidRole(int isValidRole) { mIsValidRole = isValidRole; emit isValidRoleChanged(); } void PlayListControler::restorePlayListPosition() { auto playerCurrentTrack = mPersistentState.find(QStringLiteral("currentTrack")); if (playerCurrentTrack != mPersistentState.end()) { if (mPlayListModel) { auto newIndex = mPlayListModel->index(playerCurrentTrack->toInt(), 0); if (newIndex.isValid() && (newIndex != mCurrentTrack)) { mCurrentTrack = newIndex; notifyCurrentTrackChanged(); if (mCurrentTrack.isValid()) { mPersistentState.erase(playerCurrentTrack); } } } } } void PlayListControler::restoreRandomPlay() { auto randomPlayStoredValue = mPersistentState.find(QStringLiteral("randomPlay")); if (randomPlayStoredValue != mPersistentState.end()) { setRandomPlayControl(randomPlayStoredValue->toBool()); mPersistentState.erase(randomPlayStoredValue); } } void PlayListControler::restoreRepeatPlay() { auto repeatPlayStoredValue = mPersistentState.find(QStringLiteral("repeatPlay")); if (repeatPlayStoredValue != mPersistentState.end()) { setRepeatPlayControl(repeatPlayStoredValue->toBool()); mPersistentState.erase(repeatPlayStoredValue); } } void PlayListControler::notifyCurrentTrackChanged() { Q_EMIT currentTrackChanged(); + Q_EMIT currentTrackRowChanged(); mCurrentTrackIsValid = mCurrentTrack.isValid(); if (mCurrentTrackIsValid) { mCurrentPlayListPosition = mCurrentTrack.row(); } } void PlayListControler::setPersistentState(QVariantMap persistentStateValue) { if (mPersistentState == persistentStateValue) { return; } qDebug() << "PlayListControler::setPersistentState" << persistentStateValue; mPersistentState = persistentStateValue; restorePlayListPosition(); restoreRandomPlay(); restoreRepeatPlay(); Q_EMIT persistentStateChanged(); } void PlayListControler::setRandomPlayControl(bool randomPlayControl) { if (mRandomPlayControl == randomPlayControl) { return; } mRandomPlayControl = randomPlayControl; Q_EMIT randomPlayControlChanged(); } void PlayListControler::setRepeatPlayControl(bool repeatPlayControl) { if (mRepeatPlayControl == repeatPlayControl) { return; } mRepeatPlayControl = repeatPlayControl; Q_EMIT repeatPlayControlChanged(); } void PlayListControler::resetCurrentTrack() { for(int row = 0; row < mPlayListModel->rowCount(); ++row) { auto candidateTrack = mPlayListModel->index(row, 0); if (candidateTrack.isValid() && candidateTrack.data(mIsValidRole).toBool()) { mCurrentTrack = candidateTrack; notifyCurrentTrackChanged(); break; } } } #include "moc_playlistcontroler.cpp" diff --git a/src/playlistcontroler.h b/src/playlistcontroler.h index d85f5fdd..ea3f1176 100644 --- a/src/playlistcontroler.h +++ b/src/playlistcontroler.h @@ -1,186 +1,194 @@ /* * Copyright 2016 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PLAYLISTCONTROLER_H #define PLAYLISTCONTROLER_H #include #include #include #include #include #include class QDataStream; class PlayListControler : public QObject { Q_OBJECT Q_PROPERTY(QPersistentModelIndex currentTrack READ currentTrack NOTIFY currentTrackChanged) + Q_PROPERTY(int currentTrackRow + READ currentTrackRow + NOTIFY currentTrackRowChanged) + Q_PROPERTY(QAbstractItemModel* playListModel READ playListModel WRITE setPlayListModel NOTIFY playListModelChanged) Q_PROPERTY(int isValidRole READ isValidRole WRITE setIsValidRole NOTIFY isValidRoleChanged) Q_PROPERTY(bool randomPlay READ randomPlay WRITE setRandomPlay NOTIFY randomPlayChanged) Q_PROPERTY(bool randomPlayControl READ randomPlayControl WRITE setRandomPlayControl NOTIFY randomPlayControlChanged) Q_PROPERTY(bool repeatPlay READ repeatPlay WRITE setRepeatPlay NOTIFY repeatPlayChanged) Q_PROPERTY(bool repeatPlayControl READ repeatPlayControl WRITE setRepeatPlayControl NOTIFY repeatPlayControlChanged) Q_PROPERTY(QVariantMap persistentState READ persistentState WRITE setPersistentState NOTIFY persistentStateChanged) public: explicit PlayListControler(QObject *parent = 0); QPersistentModelIndex currentTrack() const; + int currentTrackRow() const; + QAbstractItemModel* playListModel() const; int isValidRole() const; int remainingTracks() const; bool randomPlay() const; bool randomPlayControl() const; bool repeatPlay() const; bool repeatPlayControl() const; QVariantMap persistentState() const; Q_SIGNALS: void currentTrackChanged(); + void currentTrackRowChanged(); + void playListModelChanged(); void isValidRoleChanged(); void randomPlayChanged(); void randomPlayControlChanged(); void repeatPlayChanged(); void repeatPlayControlChanged(); void persistentStateChanged(); void playListFinished(); public Q_SLOTS: void setPlayListModel(QAbstractItemModel* aPlayListModel); void setIsValidRole(int isValidRole); void setRandomPlay(bool value); void setRandomPlayControl(bool randomPlayControl); void setRepeatPlay(bool value); void setRepeatPlayControl(bool repeatPlayControl); void setPersistentState(QVariantMap persistentStateValue); void playListReset(); void playListLayoutChanged(const QList &parents = QList(), QAbstractItemModel::LayoutChangeHint hint = QAbstractItemModel::NoLayoutChangeHint); void tracksInserted(const QModelIndex &parent, int first, int last); void tracksRemoved(const QModelIndex & parent, int first, int last); void tracksDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector ()); void skipNextTrack(); void skipPreviousTrack(); void seedRandomGenerator(uint seed); void switchTo(int row); private: void resetCurrentTrack(); void restorePlayListPosition(); void restoreRandomPlay(); void restoreRepeatPlay(); void notifyCurrentTrackChanged(); QPersistentModelIndex mCurrentTrack; int mCurrentPlayListPosition = 0; bool mCurrentTrackIsValid = false; QAbstractItemModel *mPlayListModel = nullptr; int mIsValidRole = Qt::DisplayRole; bool mRandomPlay = false; bool mRandomPlayControl = false; bool mRepeatPlay = false; bool mRepeatPlayControl = false; QVariantMap mPersistentState; }; #endif // PLAYLISTCONTROLER_H