diff --git a/src/models/datamodel.cpp b/src/models/datamodel.cpp index 595f7116..6e361929 100644 --- a/src/models/datamodel.cpp +++ b/src/models/datamodel.cpp @@ -1,840 +1,844 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "datamodel.h" #include "modeldataloader.h" #include "musiclistenersmanager.h" #include #include #include #include #include #include class DataModelPrivate { public: DataModel::ListTrackDataType mAllTrackData; DataModel::ListRadioDataType mAllRadiosData; DataModel::ListAlbumDataType mAllAlbumData; DataModel::ListArtistDataType mAllArtistData; DataModel::ListGenreDataType mAllGenreData; ModelDataLoader mDataLoader; ElisaUtils::PlayListEntryType mModelType = ElisaUtils::Unknown; ElisaUtils::FilterType mFilterType = ElisaUtils::UnknownFilter; QString mArtist; QString mAlbumTitle; QString mAlbumArtist; QString mGenre; qulonglong mDatabaseId = 0; bool mIsBusy = false; }; DataModel::DataModel(QObject *parent) : QAbstractListModel(parent), d(std::make_unique()) { } DataModel::~DataModel() = default; int DataModel::rowCount(const QModelIndex &parent) const { auto dataCount = 0; if (parent.isValid()) { return dataCount; } dataCount = d->mAllTrackData.size() + d->mAllAlbumData.size() + d->mAllArtistData.size() + d->mAllGenreData.size(); return dataCount; } QHash DataModel::roleNames() const { auto roles = QAbstractListModel::roleNames(); roles[static_cast(DatabaseInterface::ColumnsRoles::TitleRole)] = "title"; roles[static_cast(DatabaseInterface::ColumnsRoles::SecondaryTextRole)] = "secondaryText"; roles[static_cast(DatabaseInterface::ColumnsRoles::ImageUrlRole)] = "imageUrl"; roles[static_cast(DatabaseInterface::ColumnsRoles::DatabaseIdRole)] = "databaseId"; roles[static_cast(DatabaseInterface::ColumnsRoles::ElementTypeRole)] = "dataType"; roles[static_cast(DatabaseInterface::ColumnsRoles::ArtistRole)] = "artist"; roles[static_cast(DatabaseInterface::ColumnsRoles::AllArtistsRole)] = "allArtists"; roles[static_cast(DatabaseInterface::ColumnsRoles::HighestTrackRating)] = "highestTrackRating"; roles[static_cast(DatabaseInterface::ColumnsRoles::GenreRole)] = "genre"; roles[static_cast(DatabaseInterface::ColumnsRoles::AlbumRole)] = "album"; roles[static_cast(DatabaseInterface::ColumnsRoles::AlbumArtistRole)] = "albumArtist"; roles[static_cast(DatabaseInterface::ColumnsRoles::DurationRole)] = "duration"; roles[static_cast(DatabaseInterface::ColumnsRoles::TrackNumberRole)] = "trackNumber"; roles[static_cast(DatabaseInterface::ColumnsRoles::DiscNumberRole)] = "discNumber"; roles[static_cast(DatabaseInterface::ColumnsRoles::RatingRole)] = "rating"; roles[static_cast(DatabaseInterface::ColumnsRoles::IsSingleDiscAlbumRole)] = "isSingleDiscAlbum"; return roles; } Qt::ItemFlags DataModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return Qt::NoItemFlags; } return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } QVariant DataModel::data(const QModelIndex &index, int role) const { auto result = QVariant(); if (!index.isValid()) { return result; } const auto dataCount = d->mModelType == ElisaUtils::Radio ? d->mAllRadiosData.size() : d->mAllTrackData.size() + d->mAllAlbumData.size() + d->mAllArtistData.size() + d->mAllGenreData.size(); Q_ASSERT(index.isValid()); Q_ASSERT(index.column() == 0); Q_ASSERT(!index.parent().isValid()); Q_ASSERT(index.model() == this); Q_ASSERT(index.internalId() == 0); Q_ASSERT(index.row() >= 0 && index.row() < dataCount); switch(role) { case Qt::DisplayRole: switch(d->mModelType) { case ElisaUtils::Track: result = d->mAllTrackData[index.row()][TrackDataType::key_type::TitleRole]; break; case ElisaUtils::Album: result = d->mAllAlbumData[index.row()][AlbumDataType::key_type::TitleRole]; break; case ElisaUtils::Artist: result = d->mAllArtistData[index.row()][ArtistDataType::key_type::TitleRole]; break; case ElisaUtils::Genre: result = d->mAllGenreData[index.row()][GenreDataType::key_type::TitleRole]; break; case ElisaUtils::Radio: result = d->mAllRadiosData[index.row()][GenreDataType::key_type::TitleRole]; break; case ElisaUtils::Lyricist: case ElisaUtils::Composer: case ElisaUtils::FileName: case ElisaUtils::Unknown: break; } break; case DatabaseInterface::ColumnsRoles::DurationRole: { switch (d->mModelType) { case ElisaUtils::Track: { auto trackDuration = d->mAllTrackData[index.row()][TrackDataType::key_type::DurationRole].toTime(); if (trackDuration.hour() == 0) { result = trackDuration.toString(QStringLiteral("mm:ss")); } else { result = trackDuration.toString(); } break; } case ElisaUtils::Album: case ElisaUtils::Artist: case ElisaUtils::Genre: case ElisaUtils::Lyricist: case ElisaUtils::Composer: case ElisaUtils::FileName: case ElisaUtils::Radio: case ElisaUtils::Unknown: break; } break; } case DatabaseInterface::ColumnsRoles::IsSingleDiscAlbumRole: { switch (d->mModelType) { case ElisaUtils::Track: + result = d->mAllTrackData[index.row()][TrackDataType::key_type::IsSingleDiscAlbumRole]; + break; case ElisaUtils::Radio: result = false; break; case ElisaUtils::Album: + result = d->mAllAlbumData[index.row()][AlbumDataType::key_type::IsSingleDiscAlbumRole]; + break; case ElisaUtils::Artist: case ElisaUtils::Genre: case ElisaUtils::Lyricist: case ElisaUtils::Composer: case ElisaUtils::FileName: case ElisaUtils::Unknown: break; } break; } default: switch(d->mModelType) { case ElisaUtils::Track: result = d->mAllTrackData[index.row()][static_cast(role)]; break; case ElisaUtils::Album: result = d->mAllAlbumData[index.row()][static_cast(role)]; break; case ElisaUtils::Artist: result = d->mAllArtistData[index.row()][static_cast(role)]; break; case ElisaUtils::Genre: result = d->mAllGenreData[index.row()][static_cast(role)]; break; case ElisaUtils::Radio: result = d->mAllRadiosData[index.row()][static_cast(role)]; break; case ElisaUtils::Lyricist: case ElisaUtils::Composer: case ElisaUtils::FileName: case ElisaUtils::Unknown: break; } } return result; } QModelIndex DataModel::index(int row, int column, const QModelIndex &parent) const { auto result = QModelIndex(); if (column != 0) { return result; } if (parent.isValid()) { return result; } result = createIndex(row, column); return result; } QModelIndex DataModel::parent(const QModelIndex &child) const { Q_UNUSED(child) auto result = QModelIndex(); return result; } QString DataModel::title() const { return d->mAlbumTitle; } QString DataModel::author() const { return d->mAlbumArtist; } bool DataModel::isBusy() const { return d->mIsBusy; } void DataModel::initialize(MusicListenersManager *manager, DatabaseInterface *database, ElisaUtils::PlayListEntryType modelType, ElisaUtils::FilterType filter, const QString &genre, const QString &artist, qulonglong databaseId) { d->mDatabaseId = databaseId; d->mGenre = genre; d->mArtist = artist; initializeModel(manager, database, modelType, filter); } void DataModel::setBusy(bool value) { if (d->mIsBusy == value) { return; } d->mIsBusy = value; Q_EMIT isBusyChanged(); } void DataModel::initializeModel(MusicListenersManager *manager, DatabaseInterface *database, ElisaUtils::PlayListEntryType modelType, DataModel::FilterType type) { d->mModelType = modelType; d->mFilterType = type; if (manager) { manager->connectModel(&d->mDataLoader); } if (manager) { connectModel(manager->viewDatabase()); } else if (database) { connectModel(database); } else { return; } switch(d->mFilterType) { case ElisaUtils::NoFilter: connect(this, &DataModel::needData, &d->mDataLoader, &ModelDataLoader::loadData); break; case ElisaUtils::FilterById: connect(this, &DataModel::needDataById, &d->mDataLoader, &ModelDataLoader::loadDataByAlbumId); break; case ElisaUtils::FilterByGenre: connect(this, &DataModel::needDataByGenre, &d->mDataLoader, &ModelDataLoader::loadDataByGenre); break; case ElisaUtils::FilterByArtist: connect(this, &DataModel::needDataByArtist, &d->mDataLoader, &ModelDataLoader::loadDataByArtist); break; case ElisaUtils::FilterByGenreAndArtist: connect(this, &DataModel::needDataByGenreAndArtist, &d->mDataLoader, &ModelDataLoader::loadDataByGenreAndArtist); break; case ElisaUtils::FilterByRecentlyPlayed: connect(this, &DataModel::needRecentlyPlayedData, &d->mDataLoader, &ModelDataLoader::loadRecentlyPlayedData); break; case ElisaUtils::FilterByFrequentlyPlayed: connect(this, &DataModel::needFrequentlyPlayedData, &d->mDataLoader, &ModelDataLoader::loadFrequentlyPlayedData); break; case ElisaUtils::UnknownFilter: break; } setBusy(true); askModelData(); } void DataModel::askModelData() { switch(d->mFilterType) { case ElisaUtils::NoFilter: Q_EMIT needData(d->mModelType); break; case ElisaUtils::FilterById: Q_EMIT needDataById(d->mModelType, d->mDatabaseId); break; case ElisaUtils::FilterByGenre: Q_EMIT needDataByGenre(d->mModelType, d->mGenre); break; case ElisaUtils::FilterByArtist: Q_EMIT needDataByArtist(d->mModelType, d->mArtist); break; case ElisaUtils::FilterByGenreAndArtist: Q_EMIT needDataByGenreAndArtist(d->mModelType, d->mGenre, d->mArtist); break; case ElisaUtils::FilterByRecentlyPlayed: Q_EMIT needRecentlyPlayedData(d->mModelType); break; case ElisaUtils::FilterByFrequentlyPlayed: Q_EMIT needFrequentlyPlayedData(d->mModelType); break; case ElisaUtils::UnknownFilter: break; } } int DataModel::indexFromId(qulonglong id) const { int result; DataModel::ListTrackDataType mAllData = d->mModelType == ElisaUtils::Radio ? d->mAllRadiosData: d->mAllTrackData; for (result = 0; result < mAllData.size(); ++result) { if (mAllData[result].databaseId() == id) { return result; } } result = -1; return result; } void DataModel::connectModel(DatabaseInterface *database) { d->mDataLoader.setDatabase(database); connect(&d->mDataLoader, &ModelDataLoader::allTracksData, this, &DataModel::tracksAdded); connect(&d->mDataLoader, &ModelDataLoader::allRadiosData, this, &DataModel::radiosAdded); connect(&d->mDataLoader, &ModelDataLoader::allAlbumsData, this, &DataModel::albumsAdded); connect(&d->mDataLoader, &ModelDataLoader::allArtistsData, this, &DataModel::artistsAdded); connect(&d->mDataLoader, &ModelDataLoader::allGenresData, this, &DataModel::genresAdded); connect(&d->mDataLoader, &ModelDataLoader::genresAdded, this, &DataModel::genresAdded); connect(&d->mDataLoader, &ModelDataLoader::albumsAdded, this, &DataModel::albumsAdded); connect(&d->mDataLoader, &ModelDataLoader::albumModified, this, &DataModel::albumModified); connect(&d->mDataLoader, &ModelDataLoader::albumRemoved, this, &DataModel::albumRemoved); connect(&d->mDataLoader, &ModelDataLoader::tracksAdded, this, &DataModel::tracksAdded); connect(&d->mDataLoader, &ModelDataLoader::trackModified, this, &DataModel::trackModified); connect(&d->mDataLoader, &ModelDataLoader::trackRemoved, this, &DataModel::trackRemoved); connect(&d->mDataLoader, &ModelDataLoader::artistsAdded, this, &DataModel::artistsAdded); connect(&d->mDataLoader, &ModelDataLoader::artistRemoved, this, &DataModel::artistRemoved); connect(&d->mDataLoader, &ModelDataLoader::radioAdded, this, &DataModel::radioAdded); connect(&d->mDataLoader, &ModelDataLoader::radioModified, this, &DataModel::radioModified); connect(&d->mDataLoader, &ModelDataLoader::radioRemoved, this, &DataModel::radioRemoved); } void DataModel::tracksAdded(ListTrackDataType newData) { if (newData.isEmpty() && d->mModelType == ElisaUtils::Track) { setBusy(false); } if (newData.isEmpty() || d->mModelType != ElisaUtils::Track) { return; } if (d->mFilterType == ElisaUtils::FilterById && !d->mAllTrackData.isEmpty()) { for (const auto &newTrack : newData) { auto trackIndex = indexFromId(newTrack.databaseId()); if (trackIndex != -1) { continue; } bool trackInserted = false; for (int trackIndex = 0; trackIndex < d->mAllTrackData.count(); ++trackIndex) { const auto &oneTrack = d->mAllTrackData[trackIndex]; if (oneTrack.discNumber() >= newTrack.discNumber() && oneTrack.trackNumber() > newTrack.trackNumber()) { beginInsertRows({}, trackIndex, trackIndex); d->mAllTrackData.insert(trackIndex, newTrack); endInsertRows(); if (d->mAllTrackData.size() == 1) { setBusy(false); } trackInserted = true; break; } } if (!trackInserted) { beginInsertRows({}, d->mAllTrackData.count(), d->mAllTrackData.count()); d->mAllTrackData.insert(d->mAllTrackData.count(), newTrack); endInsertRows(); if (d->mAllTrackData.size() == 1) { setBusy(false); } } } } else { if (d->mAllTrackData.isEmpty()) { beginInsertRows({}, 0, newData.size() - 1); d->mAllTrackData.swap(newData); endInsertRows(); setBusy(false); } else { beginInsertRows({}, d->mAllTrackData.size(), d->mAllTrackData.size() + newData.size() - 1); d->mAllTrackData.append(newData); endInsertRows(); } } } void DataModel::radiosAdded(ListRadioDataType newData) { if (newData.isEmpty() && d->mModelType == ElisaUtils::Radio) { setBusy(false); } if (newData.isEmpty() || d->mModelType != ElisaUtils::Radio) { return; } if (d->mFilterType == ElisaUtils::FilterById && !d->mAllRadiosData.isEmpty()) { for (const auto &newTrack : newData) { auto trackIndex = indexFromId(newTrack.databaseId()); if (trackIndex != -1) { continue; } bool trackInserted = false; for (int trackIndex = 0; trackIndex < d->mAllRadiosData.count(); ++trackIndex) { const auto &oneTrack = d->mAllRadiosData[trackIndex]; if (oneTrack.trackNumber() > newTrack.trackNumber()) { beginInsertRows({}, trackIndex, trackIndex); d->mAllRadiosData.insert(trackIndex, newTrack); endInsertRows(); if (d->mAllRadiosData.size() == 1) { setBusy(false); } trackInserted = true; break; } } if (!trackInserted) { beginInsertRows({}, d->mAllRadiosData.count(), d->mAllRadiosData.count()); d->mAllRadiosData.insert(d->mAllRadiosData.count(), newTrack); endInsertRows(); if (d->mAllRadiosData.size() == 1) { setBusy(false); } } } } else { if (d->mAllRadiosData.isEmpty()) { beginInsertRows({}, 0, newData.size() - 1); d->mAllRadiosData.swap(newData); endInsertRows(); setBusy(false); } else { beginInsertRows({}, d->mAllRadiosData.size(), d->mAllRadiosData.size() + newData.size() - 1); d->mAllRadiosData.append(newData); endInsertRows(); } } } void DataModel::trackModified(const TrackDataType &modifiedTrack) { if (d->mModelType != ElisaUtils::Track) { return; } if (!d->mAlbumTitle.isEmpty() && !d->mAlbumArtist.isEmpty()) { if (modifiedTrack.album() != d->mAlbumTitle) { return; } auto trackIndex = indexFromId(modifiedTrack.databaseId()); if (trackIndex == -1) { return; } d->mAllTrackData[trackIndex] = modifiedTrack; Q_EMIT dataChanged(index(trackIndex, 0), index(trackIndex, 0)); } else { auto itTrack = std::find_if(d->mAllTrackData.begin(), d->mAllTrackData.end(), [modifiedTrack](auto track) { return track.databaseId() == modifiedTrack.databaseId(); }); if (itTrack == d->mAllTrackData.end()) { return; } auto position = itTrack - d->mAllTrackData.begin(); d->mAllTrackData[position] = modifiedTrack; Q_EMIT dataChanged(index(position, 0), index(position, 0)); } } void DataModel::radioModified(const TrackDataType &modifiedRadio) { if (d->mModelType != ElisaUtils::Radio) { return; } auto trackIndex = indexFromId(modifiedRadio.databaseId()); if (trackIndex == -1) { return; } d->mAllRadiosData[trackIndex] = modifiedRadio; Q_EMIT dataChanged(index(trackIndex, 0), index(trackIndex, 0)); } void DataModel::trackRemoved(qulonglong removedTrackId) { if (d->mModelType != ElisaUtils::Track) { return; } if (!d->mAlbumTitle.isEmpty() && !d->mAlbumArtist.isEmpty()) { auto trackIndex = indexFromId(removedTrackId); if (trackIndex == -1) { return; } beginRemoveRows({}, trackIndex, trackIndex); d->mAllTrackData.removeAt(trackIndex); endRemoveRows(); } else { auto itTrack = std::find_if(d->mAllTrackData.begin(), d->mAllTrackData.end(), [removedTrackId](auto track) {return track.databaseId() == removedTrackId;}); if (itTrack == d->mAllTrackData.end()) { return; } auto position = itTrack - d->mAllTrackData.begin(); beginRemoveRows({}, position, position); d->mAllTrackData.erase(itTrack); endRemoveRows(); } } void DataModel::radioRemoved(qulonglong removedRadioId) { if (d->mModelType != ElisaUtils::Radio) { return; } auto itRadio = std::find_if(d->mAllRadiosData.begin(), d->mAllRadiosData.end(), [removedRadioId](auto track) {return track.databaseId() == removedRadioId;}); if (itRadio == d->mAllRadiosData.end()) { return; } auto position = itRadio - d->mAllRadiosData.begin(); beginRemoveRows({}, position, position); d->mAllRadiosData.erase(itRadio); endRemoveRows(); } void DataModel::radioAdded(const DataModel::TrackDataType radioData) { if (d->mModelType != ElisaUtils::Radio) { return; } ListRadioDataType list; list.append(radioData); radiosAdded(list); } void DataModel::removeRadios() { if (d->mModelType != ElisaUtils::Radio) { return; } beginRemoveRows({}, 0, d->mAllRadiosData.size()); d->mAllRadiosData.clear(); endRemoveRows(); } void DataModel::genresAdded(DataModel::ListGenreDataType newData) { if (newData.isEmpty() && d->mModelType == ElisaUtils::Genre) { setBusy(false); } if (newData.isEmpty() || d->mModelType != ElisaUtils::Genre) { return; } if (d->mAllGenreData.isEmpty()) { beginInsertRows({}, d->mAllGenreData.size(), newData.size() - 1); d->mAllGenreData.swap(newData); endInsertRows(); setBusy(false); } else { beginInsertRows({}, d->mAllGenreData.size(), d->mAllGenreData.size() + newData.size() - 1); d->mAllGenreData.append(newData); endInsertRows(); } } void DataModel::artistsAdded(DataModel::ListArtistDataType newData) { if (newData.isEmpty() && d->mModelType == ElisaUtils::Artist) { setBusy(false); } if (newData.isEmpty() || d->mModelType != ElisaUtils::Artist) { return; } if (d->mAllArtistData.isEmpty()) { beginInsertRows({}, d->mAllArtistData.size(), newData.size() - 1); d->mAllArtistData.swap(newData); endInsertRows(); setBusy(false); } else { beginInsertRows({}, d->mAllArtistData.size(), d->mAllArtistData.size() + newData.size() - 1); d->mAllArtistData.append(newData); endInsertRows(); } } void DataModel::artistRemoved(qulonglong removedDatabaseId) { if (d->mModelType != ElisaUtils::Artist) { return; } auto removedDataIterator = d->mAllArtistData.end(); removedDataIterator = std::find_if(d->mAllArtistData.begin(), d->mAllArtistData.end(), [removedDatabaseId](auto album) {return album.databaseId() == removedDatabaseId;}); if (removedDataIterator == d->mAllArtistData.end()) { return; } int dataIndex = removedDataIterator - d->mAllArtistData.begin(); beginRemoveRows({}, dataIndex, dataIndex); d->mAllArtistData.erase(removedDataIterator); endRemoveRows(); } void DataModel::albumsAdded(DataModel::ListAlbumDataType newData) { if (newData.isEmpty() && d->mModelType == ElisaUtils::Album) { setBusy(false); } if (newData.isEmpty() || d->mModelType != ElisaUtils::Album) { return; } if (d->mAllAlbumData.isEmpty()) { beginInsertRows({}, d->mAllAlbumData.size(), newData.size() - 1); d->mAllAlbumData.swap(newData); endInsertRows(); setBusy(false); } else { beginInsertRows({}, d->mAllAlbumData.size(), d->mAllAlbumData.size() + newData.size() - 1); d->mAllAlbumData.append(newData); endInsertRows(); } } void DataModel::albumRemoved(qulonglong removedDatabaseId) { if (d->mModelType != ElisaUtils::Album) { return; } auto removedDataIterator = d->mAllAlbumData.end(); removedDataIterator = std::find_if(d->mAllAlbumData.begin(), d->mAllAlbumData.end(), [removedDatabaseId](auto album) {return album.databaseId() == removedDatabaseId;}); if (removedDataIterator == d->mAllAlbumData.end()) { return; } int dataIndex = removedDataIterator - d->mAllAlbumData.begin(); beginRemoveRows({}, dataIndex, dataIndex); d->mAllAlbumData.erase(removedDataIterator); endRemoveRows(); } void DataModel::albumModified(const DataModel::AlbumDataType &modifiedAlbum) { if (d->mModelType != ElisaUtils::Album) { return; } auto modifiedAlbumIterator = std::find_if(d->mAllAlbumData.begin(), d->mAllAlbumData.end(), [modifiedAlbum](auto album) { return album.databaseId() == modifiedAlbum.databaseId(); }); if (modifiedAlbumIterator == d->mAllAlbumData.end()) { return; } auto albumIndex = modifiedAlbumIterator - d->mAllAlbumData.begin(); Q_EMIT dataChanged(index(albumIndex, 0), index(albumIndex, 0)); } void DataModel::cleanedDatabase() { beginResetModel(); d->mAllAlbumData.clear(); d->mAllGenreData.clear(); d->mAllTrackData.clear(); d->mAllArtistData.clear(); endResetModel(); } #include "moc_datamodel.cpp" diff --git a/src/qml/DataListView.qml b/src/qml/DataListView.qml index b181922e..3afbff64 100644 --- a/src/qml/DataListView.qml +++ b/src/qml/DataListView.qml @@ -1,219 +1,218 @@ /* * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.10 import QtQuick.Controls 2.3 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { id: viewHeader property var viewType property var filterType property alias isSubPage: listView.isSubPage property alias mainTitle: listView.mainTitle property alias secondaryTitle: listView.secondaryTitle property int databaseId property alias showSection: listView.showSection property alias expandedFilterView: listView.expandedFilterView property alias image: listView.image property var modelType property alias sortRole: proxyModel.sortRole property var sortAscending property bool displaySingleAlbum: false property alias radioCase: listView.showCreateRadioButton function openMetaDataView(databaseId){ metadataLoader.setSource("MediaTrackMetadataView.qml", {"databaseId": databaseId, "isRadio": viewHeader.radioCase}); metadataLoader.active = true } DataModel { id: realModel } AllTracksProxyModel { id: proxyModel sourceModel: realModel onEntriesToEnqueue: elisa.mediaPlayList.enqueue(newEntries, databaseIdType, enqueueMode, triggerPlay) } Loader { id: metadataLoader active: false onLoaded: item.show() } Component { - id: singleAlbumDelegate + id: albumDelegate ListBrowserDelegate { id: entry width: listView.delegateWidth - height: ((true && !true) ? elisaTheme.delegateHeight*2 : elisaTheme.delegateHeight) + height: elisaTheme.delegateHeight focus: true databaseId: model.databaseId title: model.title ? model.title : '' artist: model.artist ? model.artist : '' album: model.album ? model.album : '' albumArtist: model.albumArtist ? model.albumArtist : '' duration: model.duration ? model.duration : '' imageUrl: model.imageUrl ? model.imageUrl : '' trackNumber: model.trackNumber ? model.trackNumber : -1 discNumber: model.discNumber ? model.discNumber : -1 rating: model.rating - isSingleDiscAlbum: true isSelected: listView.currentIndex === index isAlternateColor: (index % 2) === 1 detailedView: false onEnqueue: elisa.mediaPlayList.enqueue(databaseId, name, ElisaUtils.Track, ElisaUtils.AppendPlayList, ElisaUtils.DoNotTriggerPlay) onReplaceAndPlay: elisa.mediaPlayList.enqueue(databaseId, name, ElisaUtils.Track, ElisaUtils.ReplacePlayList, ElisaUtils.TriggerPlay) onClicked: listView.currentIndex = index onActiveFocusChanged: { if (activeFocus && listView.currentIndex !== index) { listView.currentIndex = index } } onCallOpenMetaDataView: { openMetaDataView(databaseId) } } } Component { - id: multipleDiscDelegate + id: detailedTrackDelegate ListBrowserDelegate { id: entry width: listView.delegateWidth height: elisaTheme.trackDelegateHeight focus: true databaseId: model.databaseId title: model.title ? model.title : '' artist: model.artist ? model.artist : '' album: model.album ? model.album : '' albumArtist: model.albumArtist ? model.albumArtist : '' duration: model.duration ? model.duration : '' imageUrl: model.imageUrl ? model.imageUrl : '' trackNumber: model.trackNumber ? model.trackNumber : -1 discNumber: model.discNumber ? model.discNumber : -1 rating: model.rating - isSingleDiscAlbum: model.isSingleDiscAlbum + hideDiscNumber: model.isSingleDiscAlbum isSelected: listView.currentIndex === index isAlternateColor: (index % 2) === 1 onEnqueue: elisa.mediaPlayList.enqueue(databaseId, name, modelType, ElisaUtils.AppendPlayList, ElisaUtils.DoNotTriggerPlay) onReplaceAndPlay: elisa.mediaPlayList.enqueue(databaseId, name, modelType, ElisaUtils.ReplacePlayList, ElisaUtils.TriggerPlay) onClicked: { listView.currentIndex = index entry.forceActiveFocus() } onCallOpenMetaDataView: { openMetaDataView(databaseId) } } } ListBrowserView { id: listView focus: true anchors.fill: parent contentModel: proxyModel - delegate: (displaySingleAlbum ? singleAlbumDelegate : multipleDiscDelegate) + delegate: (displaySingleAlbum ? albumDelegate : detailedTrackDelegate) enableSorting: !displaySingleAlbum allowArtistNavigation: isSubPage onShowArtist: { viewManager.openChildView(secondaryTitle, '', elisaTheme.artistIcon, 0, ElisaUtils.Artist, ViewManager.NoDiscHeaders) } onGoBack: viewManager.goBack() Loader { anchors.centerIn: parent height: Kirigami.Units.gridUnit * 5 width: height visible: realModel.isBusy active: realModel.isBusy sourceComponent: BusyIndicator { anchors.centerIn: parent } } } Connections { target: elisa onMusicManagerChanged: realModel.initialize(elisa.musicManager, elisa.musicManager.viewDatabase, modelType) } Connections { target: listView.navigationBar onCreateRadio: { openMetaDataView(-1) } } Component.onCompleted: { if (elisa.musicManager) { realModel.initialize(elisa.musicManager, elisa.musicManager.viewDatabase, modelType, filterType, mainTitle, secondaryTitle, databaseId) } if (sortAscending === ViewManager.SortAscending) { proxyModel.sortModel(Qt.AscendingOrder) } else if (sortAscending === ViewManager.SortDescending) { proxyModel.sortModel(Qt.DescendingOrder) } } } diff --git a/src/qml/ListBrowserDelegate.qml b/src/qml/ListBrowserDelegate.qml index 78824775..e4ae2475 100644 --- a/src/qml/ListBrowserDelegate.qml +++ b/src/qml/ListBrowserDelegate.qml @@ -1,444 +1,444 @@ /* * Copyright 2016-2017 Matthieu Gallien * Copyright 2017 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.3 import QtQuick.Window 2.2 import QtGraphicalEffects 1.0 import org.kde.elisa 1.0 FocusScope { id: mediaTrack property var databaseId property string title property string artist property string album property string albumArtist property string duration property url imageUrl property int trackNumber property int discNumber property int rating - property bool isSingleDiscAlbum + property bool hideDiscNumber property bool isSelected property bool isAlternateColor property bool detailedView: true signal clicked() signal enqueue(var databaseId, var name) signal replaceAndPlay(var databaseId, var name) signal callOpenMetaDataView(var databaseId) Accessible.role: Accessible.ListItem Accessible.name: title Accessible.description: title Action { id: enqueueAction text: i18nc("Enqueue current track", "Enqueue") icon.name: "list-add" onTriggered: enqueue(databaseId, title) } Action { id: viewDetailsAction text: i18nc("Show track metadata", "View Details") icon.name: "help-about" onTriggered: { callOpenMetaDataView(databaseId) } } Action { id: replaceAndPlayAction text: i18nc("Clear play list and enqueue current track", "Play Now and Replace Play List") icon.name: "media-playback-start" onTriggered: replaceAndPlay(databaseId, title) } Keys.onReturnPressed: enqueue(databaseId, title) Keys.onEnterPressed: enqueue(databaseId, title) Rectangle { id: rowRoot anchors.fill: parent z: 1 color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) } MouseArea { id: hoverArea anchors.fill: parent z: 2 hoverEnabled: true acceptedButtons: Qt.LeftButton onClicked: { mediaTrack.clicked() } onDoubleClicked: enqueue(databaseId, title) RowLayout { anchors.fill: parent spacing: 0 LabelWithToolTip { id: mainLabel visible: !detailedView text: { if (trackNumber !== 0) { if (artist !== albumArtist) return i18nc("%1: track number. %2: track title. %3: artist name", "%1 - %2 - %3", trackNumber.toLocaleString(Qt.locale(), 'f', 0), title, artist); else return i18nc("%1: track number. %2: track title.", "%1 - %2", trackNumber.toLocaleString(Qt.locale(), 'f', 0), title); } else { if (artist !== albumArtist) return i18nc("%1: track title. %2: artist name", "%1 - %2", title, artist); else return i18nc("%1: track title", "%1", title); } } elide: Text.ElideRight horizontalAlignment: Text.AlignLeft color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.fillWidth: true Layout.leftMargin: { if (!LayoutMirroring.enabled) - return (!isSingleDiscAlbum ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) + return (!hideDiscNumber ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) else return 0 } Layout.rightMargin: { if (LayoutMirroring.enabled) - return (!isSingleDiscAlbum ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) + return (!hideDiscNumber ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) else return 0 } } Item { Layout.preferredHeight: mediaTrack.height * 0.9 Layout.preferredWidth: mediaTrack.height * 0.9 Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter visible: detailedView Image { id: coverImageElement anchors.fill: parent sourceSize.width: mediaTrack.height * 0.9 sourceSize.height: mediaTrack.height * 0.9 fillMode: Image.PreserveAspectFit smooth: true source: (imageUrl != '' ? imageUrl : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) asynchronous: true layer.enabled: imageUrl != '' layer.effect: DropShadow { source: coverImageElement radius: 10 spread: 0.1 samples: 21 color: myPalette.shadow } } } ColumnLayout { visible: detailedView Layout.fillWidth: true Layout.fillHeight: true Layout.alignment: Qt.AlignLeft spacing: 0 LabelWithToolTip { id: mainLabelDetailed text: { if (trackNumber >= 0) { return i18nc("%1: track number. %2: track title", "%1 - %2", trackNumber.toLocaleString(Qt.locale(), 'f', 0), title); } else { return title; } } horizontalAlignment: Text.AlignLeft font.weight: Font.Bold color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.fillWidth: true Layout.topMargin: elisaTheme.layoutVerticalMargin / 2 elide: Text.ElideRight } Item { Layout.fillHeight: true } LabelWithToolTip { id: artistLabel text: { var labelText = "" if (artist) { labelText += artist } if (album !== '') { labelText += ' - ' + album - if (!isSingleDiscAlbum && discNumber !== -1) { + if (!hideDiscNumber && discNumber !== -1) { labelText += ' - CD ' + discNumber } } return labelText; } horizontalAlignment: Text.AlignLeft font.weight: Font.Light font.italic: true color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignBottom Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.fillWidth: true Layout.bottomMargin: elisaTheme.layoutVerticalMargin / 2 elide: Text.ElideRight } } Loader { id: hoverLoader active: false Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.rightMargin: 10 z: 1 opacity: 0 sourceComponent: Row { anchors.centerIn: parent FlatButtonWithToolTip { id: detailsButton height: elisaTheme.delegateHeight width: elisaTheme.delegateHeight icon.height: elisaTheme.smallControlButtonSize icon.width: elisaTheme.smallControlButtonSize action: viewDetailsAction Accessible.onPressAction: action.trigger() } FlatButtonWithToolTip { id: enqueueButton height: elisaTheme.delegateHeight width: elisaTheme.delegateHeight icon.height: elisaTheme.smallControlButtonSize icon.width: elisaTheme.smallControlButtonSize action: enqueueAction Accessible.onPressAction: action.trigger() } FlatButtonWithToolTip { id: clearAndEnqueueButton scale: LayoutMirroring.enabled ? -1 : 1 height: elisaTheme.delegateHeight width: elisaTheme.delegateHeight icon.height: elisaTheme.smallControlButtonSize icon.width: elisaTheme.smallControlButtonSize action: replaceAndPlayAction Accessible.onPressAction: action.trigger() } } } RatingStar { id: ratingWidget starSize: elisaTheme.ratingStarSize starRating: rating Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.leftMargin: elisaTheme.layoutHorizontalMargin Layout.rightMargin: elisaTheme.layoutHorizontalMargin } LabelWithToolTip { id: durationLabel text: duration font.weight: Font.Light color: myPalette.text horizontalAlignment: Text.AlignRight Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } } } states: [ State { name: 'notSelected' when: !mediaTrack.activeFocus && !hoverArea.containsMouse && !mediaTrack.isSelected PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 0.0 } PropertyChanges { target: rowRoot color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) } PropertyChanges { target: rowRoot opacity: 1 } }, State { name: 'hovered' when: !mediaTrack.activeFocus && hoverArea.containsMouse PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } PropertyChanges { target: rowRoot color: myPalette.highlight } PropertyChanges { target: rowRoot opacity: 0.2 } }, State { name: 'selected' when: !mediaTrack.activeFocus && mediaTrack.isSelected PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } PropertyChanges { target: rowRoot color: myPalette.mid } PropertyChanges { target: rowRoot opacity: 1. } }, State { name: 'focused' when: mediaTrack.activeFocus PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } PropertyChanges { target: rowRoot color: myPalette.highlight } PropertyChanges { target: rowRoot opacity: 0.6 } } ] }