diff --git a/autotests/databaseinterfacetest.cpp b/autotests/databaseinterfacetest.cpp --- a/autotests/databaseinterfacetest.cpp +++ b/autotests/databaseinterfacetest.cpp @@ -4600,6 +4600,19 @@ musicDb.trackHasStartedPlaying(QUrl::fromLocalFile(QStringLiteral("/$5")), QDateTime::fromSecsSinceEpoch(1536689)); musicDb.trackHasStartedPlaying(QUrl::fromLocalFile(QStringLiteral("/$13")), QDateTime::fromSecsSinceEpoch(1537689)); + QCOMPARE(musicDb.allAlbumsData().count(), 5); + QCOMPARE(musicDb.allArtistsData().count(), 7); + QCOMPARE(musicDb.allTracksData().count(), 22); + QCOMPARE(musicDbArtistAddedSpy.count(), 1); + QCOMPARE(musicDbAlbumAddedSpy.count(), 1); + QCOMPARE(musicDbTrackAddedSpy.count(), 1); + QCOMPARE(musicDbArtistRemovedSpy.count(), 0); + QCOMPARE(musicDbAlbumRemovedSpy.count(), 0); + QCOMPARE(musicDbTrackRemovedSpy.count(), 0); + QCOMPARE(musicDbAlbumModifiedSpy.count(), 0); + QCOMPARE(musicDbTrackModifiedSpy.count(), 10); + QCOMPARE(musicDbDatabaseErrorSpy.count(), 0); + auto recentlyPlayedTracksData = musicDb.recentlyPlayedTracksData(10); QCOMPARE(recentlyPlayedTracksData.count(), 10); diff --git a/autotests/trackmetadatamodeltest.cpp b/autotests/trackmetadatamodeltest.cpp --- a/autotests/trackmetadatamodeltest.cpp +++ b/autotests/trackmetadatamodeltest.cpp @@ -16,13 +16,15 @@ */ #include "models/trackmetadatamodel.h" + #include "qabstractitemmodeltester.h" +#include "databasetestdata.h" #include #include -class TrackMetadataModelTests: public QObject +class TrackMetadataModelTests: public QObject, public DatabaseTestData { Q_OBJECT @@ -67,6 +69,77 @@ QCOMPARE(endRemovedRowsSpy.count(), 0); QCOMPARE(myModel.rowCount(), 1); } + + void modifyTrackInDatabase() + { + QTemporaryFile databaseFile; + databaseFile.open(); + + qDebug() << "addOneTrackWithoutAlbumArtist" << databaseFile.fileName(); + + DatabaseInterface musicDb; + + musicDb.init(QStringLiteral("testDb"), databaseFile.fileName()); + + musicDb.insertTracksList(mNewTracks, mNewCovers); + + TrackMetadataModel myModel; + QAbstractItemModelTester testModel(&myModel); + + QSignalSpy beginResetSpy(&myModel, &TrackMetadataModel::modelAboutToBeReset); + QSignalSpy endResetSpy(&myModel, &TrackMetadataModel::modelReset); + QSignalSpy beginInsertRowsSpy(&myModel, &TrackMetadataModel::rowsAboutToBeInserted); + QSignalSpy endInsertRowsSpy(&myModel, &TrackMetadataModel::rowsInserted); + QSignalSpy dataChangedSpy(&myModel, &TrackMetadataModel::dataChanged); + QSignalSpy beginRemovedRowsSpy(&myModel, &TrackMetadataModel::rowsAboutToBeRemoved); + QSignalSpy endRemovedRowsSpy(&myModel, &TrackMetadataModel::rowsRemoved); + + QCOMPARE(beginResetSpy.count(), 0); + QCOMPARE(endResetSpy.count(), 0); + QCOMPARE(beginInsertRowsSpy.count(), 0); + QCOMPARE(endInsertRowsSpy.count(), 0); + QCOMPARE(dataChangedSpy.count(), 0); + QCOMPARE(beginRemovedRowsSpy.count(), 0); + QCOMPARE(endRemovedRowsSpy.count(), 0); + QCOMPARE(myModel.rowCount(), 0); + + myModel.setDatabase(&musicDb); + + auto trackId = musicDb.trackIdFromFileName(QUrl::fromLocalFile(QStringLiteral("/$1"))); + + myModel.initializeByTrackId(trackId); + + QCOMPARE(beginResetSpy.count(), 1); + QCOMPARE(endResetSpy.count(), 1); + QCOMPARE(beginInsertRowsSpy.count(), 0); + QCOMPARE(endInsertRowsSpy.count(), 0); + QCOMPARE(dataChangedSpy.count(), 0); + QCOMPARE(beginRemovedRowsSpy.count(), 0); + QCOMPARE(endRemovedRowsSpy.count(), 0); + QCOMPARE(myModel.rowCount(), 11); + + musicDb.trackHasStartedPlaying(QUrl::fromLocalFile(QStringLiteral("/$2")), QDateTime::currentDateTime()); + + QCOMPARE(beginResetSpy.count(), 1); + QCOMPARE(endResetSpy.count(), 1); + QCOMPARE(beginInsertRowsSpy.count(), 0); + QCOMPARE(endInsertRowsSpy.count(), 0); + QCOMPARE(dataChangedSpy.count(), 0); + QCOMPARE(beginRemovedRowsSpy.count(), 0); + QCOMPARE(endRemovedRowsSpy.count(), 0); + QCOMPARE(myModel.rowCount(), 11); + + musicDb.trackHasStartedPlaying(QUrl::fromLocalFile(QStringLiteral("/$1")), QDateTime::currentDateTime()); + + QCOMPARE(beginResetSpy.count(), 2); + QCOMPARE(endResetSpy.count(), 2); + QCOMPARE(beginInsertRowsSpy.count(), 0); + QCOMPARE(endInsertRowsSpy.count(), 0); + QCOMPARE(dataChangedSpy.count(), 0); + QCOMPARE(beginRemovedRowsSpy.count(), 0); + QCOMPARE(endRemovedRowsSpy.count(), 0); + QCOMPARE(myModel.rowCount(), 12); + } }; QTEST_GUILESS_MAIN(TrackMetadataModelTests) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,7 @@ models/alltracksproxymodel.cpp models/singlealbumproxymodel.cpp models/trackmetadatamodel.cpp + models/trackcontextmetadatamodel.cpp models/viewsmodel.cpp ) @@ -376,6 +377,7 @@ qml/PlayListAlbumHeader.qml qml/BasicPlayListAlbumHeader.qml + qml/MetaDataDelegate.qml qml/MediaTrackDelegate.qml qml/MediaAlbumTrackDelegate.qml qml/MediaTrackMetadataView.qml diff --git a/src/databaseinterface.cpp b/src/databaseinterface.cpp --- a/src/databaseinterface.cpp +++ b/src/databaseinterface.cpp @@ -779,6 +779,10 @@ } updateTrackStatistics(fileName, time); + auto trackId = internalTrackIdFromFileName(fileName); + if (trackId != 0) { + Q_EMIT trackModified(internalOneTrackPartialData(trackId)); + } transactionResult = finishTransaction(); if (!transactionResult) { diff --git a/src/elisaapplication.cpp b/src/elisaapplication.cpp --- a/src/elisaapplication.cpp +++ b/src/elisaapplication.cpp @@ -382,7 +382,9 @@ d->mManageHeaderBar->setAlbumRole(MediaPlayList::AlbumRole); d->mManageHeaderBar->setAlbumArtistRole(MediaPlayList::AlbumArtistRole); d->mManageHeaderBar->setArtistRole(MediaPlayList::ArtistRole); + d->mManageHeaderBar->setFileNameRole(MediaPlayList::ResourceRole); d->mManageHeaderBar->setImageRole(MediaPlayList::ImageUrlRole); + d->mManageHeaderBar->setDatabaseIdRole(MediaPlayList::DatabaseIdRole); d->mManageHeaderBar->setAlbumIdRole(MediaPlayList::AlbumIdRole); d->mManageHeaderBar->setIsValidRole(MediaPlayList::IsValidRole); d->mManageHeaderBar->setPlayListModel(d->mMediaPlayList.get()); diff --git a/src/elisaqmlplugin.cpp b/src/elisaqmlplugin.cpp --- a/src/elisaqmlplugin.cpp +++ b/src/elisaqmlplugin.cpp @@ -47,6 +47,7 @@ #include "databaseinterface.h" #include "models/datamodel.h" #include "models/trackmetadatamodel.h" +#include "models/trackcontextmetadatamodel.h" #include "models/viewsmodel.h" #include "models/gridviewproxymodel.h" #include "models/alltracksproxymodel.h" @@ -123,6 +124,7 @@ qmlRegisterType(uri, 1, 0, "ViewManager"); qmlRegisterType(uri, 1, 0, "DataModel"); qmlRegisterType(uri, 1, 0, "TrackMetadataModel"); + qmlRegisterType(uri, 1, 0, "TrackContextMetaDataModel"); qmlRegisterType(uri, 1, 0, "ViewsModel"); qmlRegisterType(uri, 1, 0, "GridViewProxyModel"); qmlRegisterType(uri, 1, 0, "AllTracksProxyModel"); diff --git a/src/filescanner.cpp b/src/filescanner.cpp --- a/src/filescanner.cpp +++ b/src/filescanner.cpp @@ -154,8 +154,6 @@ if (discNumberProperty != d->mAllProperties.end()) { trackData.setDiscNumber(discNumberProperty->toInt()); - } else { - trackData.setDiscNumber(1); } if (albumArtistProperty != d->mAllProperties.end()) { diff --git a/src/manageheaderbar.h b/src/manageheaderbar.h --- a/src/manageheaderbar.h +++ b/src/manageheaderbar.h @@ -61,11 +61,21 @@ WRITE setAlbumArtistRole NOTIFY albumArtistRoleChanged) + Q_PROPERTY(int fileNameRole + READ fileNameRole + WRITE setFileNameRole + NOTIFY fileNameRoleChanged) + Q_PROPERTY(int imageRole READ imageRole WRITE setImageRole NOTIFY imageRoleChanged) + Q_PROPERTY(int databaseIdRole + READ databaseIdRole + WRITE setDatabaseIdRole + NOTIFY databaseIdRoleChanged) + Q_PROPERTY(int albumIdRole READ albumIdRole WRITE setAlbumIdRole @@ -92,10 +102,18 @@ READ albumArtist NOTIFY albumArtistChanged) + Q_PROPERTY(QString fileName + READ fileName + NOTIFY fileNameChanged) + Q_PROPERTY(QUrl image READ image NOTIFY imageChanged) + Q_PROPERTY(qulonglong databaseId + READ databaseId + NOTIFY databaseIdChanged) + Q_PROPERTY(qulonglong albumId READ albumId NOTIFY albumIdChanged) @@ -124,8 +142,12 @@ int albumArtistRole() const; + int fileNameRole() const; + int imageRole() const; + int databaseIdRole() const; + int albumIdRole() const; int isValidRole() const; @@ -138,8 +160,12 @@ QVariant albumArtist() const; + QString fileName() const; + QUrl image() const; + qulonglong databaseId() const; + qulonglong albumId() const; bool isValid() const; @@ -160,8 +186,12 @@ void albumArtistRoleChanged(); + void fileNameRoleChanged(); + void imageRoleChanged(); + void databaseIdRoleChanged(); + void albumIdRoleChanged(); void isValidRoleChanged(); @@ -174,10 +204,14 @@ void albumArtistChanged(); + void fileNameChanged(); + void imageChanged(); void remainingTracksChanged(); + void databaseIdChanged(); + void albumIdChanged(); void isValidChanged(); @@ -196,8 +230,12 @@ void setAlbumArtistRole(int value); + void setFileNameRole(int value); + void setImageRole(int value); + void setDatabaseIdRole(int databaseIdRole); + void setAlbumIdRole(int albumIdRole); void setIsValidRole(int isValidRole); @@ -224,8 +262,12 @@ void notifyAlbumArtistProperty(); + void notifyFileNameProperty(); + void notifyImageProperty(); + void notifyDatabaseIdProperty(); + void notifyAlbumIdProperty(); void notifyIsValidProperty(); @@ -244,8 +286,12 @@ int mAlbumArtistRole = Qt::DisplayRole; + int mFileNameRole = Qt::DisplayRole; + int mImageRole = Qt::DisplayRole; + int mDatabaseIdRole = Qt::DisplayRole; + int mAlbumIdRole = Qt::DisplayRole; int mIsValidRole = Qt::DisplayRole; @@ -258,8 +304,12 @@ QVariant mOldAlbumArtist; + QVariant mOldFileName; + QVariant mOldImage; + qulonglong mOldDatabaseId = 0; + qulonglong mOldAlbumId = 0; bool mOldIsValid = false; diff --git a/src/manageheaderbar.cpp b/src/manageheaderbar.cpp --- a/src/manageheaderbar.cpp +++ b/src/manageheaderbar.cpp @@ -62,6 +62,12 @@ Q_EMIT albumArtistRoleChanged(); } +void ManageHeaderBar::setFileNameRole(int value) +{ + mFileNameRole = value; + Q_EMIT fileNameRoleChanged(); +} + int ManageHeaderBar::albumRole() const { return mAlbumRole; @@ -72,17 +78,33 @@ return mAlbumArtistRole; } +int ManageHeaderBar::fileNameRole() const +{ + return mFileNameRole; +} + void ManageHeaderBar::setImageRole(int value) { mImageRole = value; Q_EMIT imageRoleChanged(); } +void ManageHeaderBar::setDatabaseIdRole(int databaseIdRole) +{ + mDatabaseIdRole = databaseIdRole; + Q_EMIT databaseIdRoleChanged(); +} + int ManageHeaderBar::imageRole() const { return mImageRole; } +int ManageHeaderBar::databaseIdRole() const +{ + return mDatabaseIdRole; +} + void ManageHeaderBar::setAlbumIdRole(int albumIdRole) { mAlbumIdRole = albumIdRole; @@ -112,6 +134,24 @@ return mCurrentTrack.data(mAlbumArtistRole); } +QString ManageHeaderBar::fileName() const +{ + QString result; + + if (!mCurrentTrack.isValid()) { + return result; + } + + auto fileNameUrl = mCurrentTrack.data(mFileNameRole).toUrl(); + if (fileNameUrl.isLocalFile()) { + result = fileNameUrl.toLocalFile(); + } else { + result = fileNameUrl.toString(); + } + + return result; +} + QVariant ManageHeaderBar::title() const { if (!mCurrentTrack.isValid()) { @@ -139,6 +179,15 @@ return mCurrentTrack.data(mImageRole).toUrl(); } +qulonglong ManageHeaderBar::databaseId() const +{ + if (!mCurrentTrack.isValid()) { + return 0; + } + + return mCurrentTrack.data(mDatabaseIdRole).toULongLong(); +} + qulonglong ManageHeaderBar::albumId() const { if (!mCurrentTrack.isValid()) { @@ -221,7 +270,9 @@ notifyTitleProperty(); notifyAlbumProperty(); notifyAlbumArtistProperty(); + notifyFileNameProperty(); notifyImageProperty(); + notifyDatabaseIdProperty(); notifyAlbumIdProperty(); notifyIsValidProperty(); } else { @@ -238,9 +289,15 @@ if (oneRole == mAlbumArtistRole) { notifyAlbumArtistProperty(); } + if (oneRole == mFileNameRole) { + notifyFileNameProperty(); + } if (oneRole == mImageRole) { notifyImageProperty(); } + if (oneRole == mDatabaseIdRole) { + notifyDatabaseIdProperty(); + } if (oneRole == mAlbumIdRole) { notifyAlbumIdProperty(); } @@ -287,7 +344,9 @@ notifyTitleProperty(); notifyAlbumProperty(); notifyAlbumArtistProperty(); + notifyFileNameProperty(); notifyImageProperty(); + notifyDatabaseIdProperty(); notifyAlbumIdProperty(); notifyIsValidProperty(); notifyRemainingTracksProperty(); @@ -338,6 +397,16 @@ } } +void ManageHeaderBar::notifyFileNameProperty() +{ + auto newFileNameValue = mCurrentTrack.data(mFileNameRole); + if (mOldFileName != newFileNameValue) { + Q_EMIT fileNameChanged(); + + mOldFileName = newFileNameValue; + } +} + void ManageHeaderBar::notifyImageProperty() { auto newImageValue = mCurrentTrack.data(mImageRole); @@ -348,6 +417,21 @@ } } +void ManageHeaderBar::notifyDatabaseIdProperty() +{ + bool conversionOk; + auto newDatabaseIdValue = mCurrentTrack.data(mDatabaseIdRole).toULongLong(&conversionOk); + if (conversionOk && mOldDatabaseId != newDatabaseIdValue) { + Q_EMIT databaseIdChanged(); + + mOldDatabaseId = newDatabaseIdValue; + } else if (!conversionOk && mOldDatabaseId != 0) { + Q_EMIT databaseIdChanged(); + + mOldDatabaseId = 0; + } +} + void ManageHeaderBar::notifyAlbumIdProperty() { bool conversionOk; @@ -409,7 +493,9 @@ notifyTitleProperty(); notifyAlbumProperty(); notifyAlbumArtistProperty(); + notifyFileNameProperty(); notifyImageProperty(); + notifyDatabaseIdProperty(); notifyAlbumIdProperty(); notifyIsValidProperty(); } diff --git a/src/models/trackcontextmetadatamodel.h b/src/models/trackcontextmetadatamodel.h new file mode 100644 --- /dev/null +++ b/src/models/trackcontextmetadatamodel.h @@ -0,0 +1,40 @@ +/* + * Copyright 2019 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 . + */ + +#ifndef TRACKCONTEXTMETADATAMODEL_H +#define TRACKCONTEXTMETADATAMODEL_H + +#include "elisaLib_export.h" + +#include "trackmetadatamodel.h" + +class ELISALIB_EXPORT TrackContextMetaDataModel : public TrackMetadataModel +{ + + Q_OBJECT + +public: + + TrackContextMetaDataModel(QObject *parent = nullptr); + +protected: + + void filterDataFromTrackData() override; + +}; + +#endif // TRACKCONTEXTMETADATAMODEL_H diff --git a/src/models/trackcontextmetadatamodel.cpp b/src/models/trackcontextmetadatamodel.cpp new file mode 100644 --- /dev/null +++ b/src/models/trackcontextmetadatamodel.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2019 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 "trackcontextmetadatamodel.h" + +TrackContextMetaDataModel::TrackContextMetaDataModel(QObject *parent) : TrackMetadataModel(parent) +{ +} + +void TrackContextMetaDataModel::filterDataFromTrackData() +{ + removeMetaData(DatabaseInterface::TitleRole); + removeMetaData(DatabaseInterface::ArtistRole); + removeMetaData(DatabaseInterface::AlbumRole); + + if (dataFromType(DatabaseInterface::IsSingleDiscAlbumRole).toBool() && + dataFromType(DatabaseInterface::DiscNumberRole).toInt() == 1) { + removeMetaData(DatabaseInterface::DiscNumberRole); + } +} + + +#include "moc_trackcontextmetadatamodel.cpp" diff --git a/src/models/trackmetadatamodel.h b/src/models/trackmetadatamodel.h --- a/src/models/trackmetadatamodel.h +++ b/src/models/trackmetadatamodel.h @@ -41,6 +41,11 @@ READ fileUrl NOTIFY fileUrlChanged) + Q_PROPERTY(MusicListenersManager* manager + READ manager + WRITE setManager + NOTIFY managerChanged) + public: enum ColumnRoles @@ -72,16 +77,12 @@ QHash roleNames() const override; - Qt::ItemFlags flags(const QModelIndex& index) const override; - - bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; - - bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; - const QUrl& coverUrl() const; QString fileUrl() const; + MusicListenersManager* manager() const; + Q_SIGNALS: void needDataByDatabaseId(ElisaUtils::PlayListEntryType dataType, qulonglong databaseId); @@ -92,16 +93,37 @@ void fileUrlChanged(); + void managerChanged(); + public Q_SLOTS: void trackData(const TrackMetadataModel::TrackDataType &trackData); - void initializeByTrackId(MusicListenersManager *manager, qulonglong databaseId); + void initializeByTrackId(qulonglong databaseId); + + void initializeByTrackFileName(const QUrl &fileName); + + void setManager(MusicListenersManager *newManager); - void initializeByTrackFileName(MusicListenersManager *manager, const QUrl &fileName); + void setDatabase(DatabaseInterface *trackDatabase); + +protected: + + void fillDataFromTrackData(const TrackMetadataModel::TrackDataType &trackData); + + virtual void filterDataFromTrackData(); + + void removeMetaData(DatabaseInterface::ColumnsRoles metaData); + + TrackDataType::mapped_type dataFromType(TrackDataType::key_type metaData) const; private: + void initialize(MusicListenersManager *newManager, + DatabaseInterface *trackDatabase); + + TrackDataType mFullData; + TrackDataType mTrackData; QUrl mCoverImage; @@ -112,6 +134,8 @@ ModelDataLoader mDataLoader; + MusicListenersManager *mManager = nullptr; + }; #endif // TRACKMETADATAMODEL_H diff --git a/src/models/trackmetadatamodel.cpp b/src/models/trackmetadatamodel.cpp --- a/src/models/trackmetadatamodel.cpp +++ b/src/models/trackmetadatamodel.cpp @@ -50,58 +50,58 @@ switch (currentKey) { case DatabaseInterface::TitleRole: - result = i18nc("Track title for track metadata view", "Title:"); + result = i18nc("Track title for track metadata view", "Title"); break; case DatabaseInterface::DurationRole: - result = i18nc("Duration label for track metadata view", "Duration:"); + result = i18nc("Duration label for track metadata view", "Duration"); break; case DatabaseInterface::ArtistRole: - result = i18nc("Track artist for track metadata view", "Artist:"); + result = i18nc("Track artist for track metadata view", "Artist"); break; case DatabaseInterface::AlbumRole: - result = i18nc("Album name for track metadata view", "Album:"); + result = i18nc("Album name for track metadata view", "Album"); break; case DatabaseInterface::AlbumArtistRole: - result = i18nc("Album artist for track metadata view", "Album Artist:"); + result = i18nc("Album artist for track metadata view", "Album Artist"); break; case DatabaseInterface::TrackNumberRole: - result = i18nc("Track number for track metadata view", "Track Number:"); + result = i18nc("Track number for track metadata view", "Track Number"); break; case DatabaseInterface::DiscNumberRole: - result = i18nc("Disc number for track metadata view", "Disc Number:"); + result = i18nc("Disc number for track metadata view", "Disc Number"); break; case DatabaseInterface::RatingRole: - result = i18nc("Rating label for information panel", "Rating:"); + result = i18nc("Rating label for information panel", "Rating"); break; case DatabaseInterface::GenreRole: - result = i18nc("Genre label for track metadata view", "Genre:"); + result = i18nc("Genre label for track metadata view", "Genre"); break; case DatabaseInterface::LyricistRole: - result = i18nc("Lyricist label for track metadata view", "Lyricist:"); + result = i18nc("Lyricist label for track metadata view", "Lyricist"); break; case DatabaseInterface::ComposerRole: - result = i18nc("Composer name for track metadata view", "Composer:"); + result = i18nc("Composer name for track metadata view", "Composer"); break; case DatabaseInterface::CommentRole: - result = i18nc("Comment label for track metadata view", "Comment:"); + result = i18nc("Comment label for track metadata view", "Comment"); break; case DatabaseInterface::YearRole: - result = i18nc("Year label for track metadata view", "Year:"); + result = i18nc("Year label for track metadata view", "Year"); break; case DatabaseInterface::ChannelsRole: - result = i18nc("Channels label for track metadata view", "Channels:"); + result = i18nc("Channels label for track metadata view", "Channels"); break; case DatabaseInterface::BitRateRole: - result = i18nc("Bit rate label for track metadata view", "Bit Rate:"); + result = i18nc("Bit rate label for track metadata view", "Bit Rate"); break; case DatabaseInterface::SampleRateRole: - result = i18nc("Sample Rate label for track metadata view", "Sample Rate:"); + result = i18nc("Sample Rate label for track metadata view", "Sample Rate"); break; case DatabaseInterface::LastPlayDate: - result = i18nc("Last play date label for track metadata view", "Last played:"); + result = i18nc("Last play date label for track metadata view", "Last played"); break; case DatabaseInterface::PlayCounter: - result = i18nc("Play counter label for track metadata view", "Play count:"); + result = i18nc("Play counter label for track metadata view", "Play count"); break; case DatabaseInterface::SecondaryTextRole: case DatabaseInterface::ImageUrlRole: @@ -223,47 +223,36 @@ return names; } -Qt::ItemFlags TrackMetadataModel::flags(const QModelIndex &index) const -{ - if (!index.isValid()) - return Qt::NoItemFlags; - - return Qt::ItemIsEditable | QAbstractListModel::flags(index); -} - QString TrackMetadataModel::fileUrl() const { return mFileUrl; } -bool TrackMetadataModel::insertRows(int row, int count, const QModelIndex &parent) +const QUrl &TrackMetadataModel::coverUrl() const { - beginInsertRows(parent, row, row + count - 1); - endInsertRows(); - - return true; + return mCoverImage; } -bool TrackMetadataModel::removeRows(int row, int count, const QModelIndex &parent) +MusicListenersManager *TrackMetadataModel::manager() const { - beginRemoveRows(parent, row, row + count - 1); - endRemoveRows(); - - return true; + return mManager; } -const QUrl &TrackMetadataModel::coverUrl() const +void TrackMetadataModel::trackData(const TrackMetadataModel::TrackDataType &trackData) { - return mCoverImage; + if (!mFullData.isEmpty() && trackData.databaseId() != mFullData.databaseId()) { + return; + } + + fillDataFromTrackData(trackData); } -void TrackMetadataModel::trackData(const TrackMetadataModel::TrackDataType &trackData) +void TrackMetadataModel::fillDataFromTrackData(const TrackMetadataModel::TrackDataType &trackData) { beginResetModel(); - if (mTrackData.isValid()) { - mTrackData.clear(); - mTrackKeys.clear(); - } + mFullData = trackData; + mTrackData.clear(); + mTrackKeys.clear(); for (auto role : {DatabaseInterface::TitleRole, DatabaseInterface::ArtistRole, DatabaseInterface::AlbumRole, DatabaseInterface::AlbumArtistRole, DatabaseInterface::TrackNumberRole, DatabaseInterface::DiscNumberRole, @@ -281,6 +270,7 @@ mTrackData[role] = trackData[role]; } } + filterDataFromTrackData(); endResetModel(); mCoverImage = trackData[DatabaseInterface::ImageUrlRole].toUrl(); @@ -296,31 +286,70 @@ Q_EMIT fileUrlChanged(); } -void TrackMetadataModel::initializeByTrackId(MusicListenersManager *manager, qulonglong databaseId) +void TrackMetadataModel::filterDataFromTrackData() { - mDataLoader.setDatabase(manager->viewDatabase()); - manager->connectModel(&mDataLoader); +} - connect(this, &TrackMetadataModel::needDataByDatabaseId, - &mDataLoader, &ModelDataLoader::loadDataByDatabaseId); - connect(&mDataLoader, &ModelDataLoader::allTrackData, - this, &TrackMetadataModel::trackData); +void TrackMetadataModel::removeMetaData(DatabaseInterface::ColumnsRoles metaData) +{ + auto itMetaData = std::find(mTrackKeys.begin(), mTrackKeys.end(), metaData); + if (itMetaData == mTrackKeys.end()) { + return; + } - Q_EMIT needDataByDatabaseId(ElisaUtils::Track, databaseId); + mTrackKeys.erase(itMetaData); + mTrackData.remove(metaData); } -void TrackMetadataModel::initializeByTrackFileName(MusicListenersManager *manager, const QUrl &fileName) +TrackMetadataModel::TrackDataType::mapped_type TrackMetadataModel::dataFromType(TrackDataType::key_type metaData) const { - mDataLoader.setDatabase(manager->viewDatabase()); - manager->connectModel(&mDataLoader); + return mFullData[metaData]; +} + +void TrackMetadataModel::initialize(MusicListenersManager *newManager, DatabaseInterface *trackDatabase) +{ + mManager = newManager; + Q_EMIT managerChanged(); + if (mManager) { + mDataLoader.setDatabase(mManager->viewDatabase()); + } else if (trackDatabase) { + mDataLoader.setDatabase(trackDatabase); + } + + if (mManager) { + mManager->connectModel(&mDataLoader); + } + + connect(this, &TrackMetadataModel::needDataByDatabaseId, + &mDataLoader, &ModelDataLoader::loadDataByDatabaseId); connect(this, &TrackMetadataModel::needDataByFileName, &mDataLoader, &ModelDataLoader::loadDataByFileName); connect(&mDataLoader, &ModelDataLoader::allTrackData, this, &TrackMetadataModel::trackData); + connect(&mDataLoader, &ModelDataLoader::trackModified, + this, &TrackMetadataModel::trackData); +} +void TrackMetadataModel::initializeByTrackId(qulonglong databaseId) +{ + Q_EMIT needDataByDatabaseId(ElisaUtils::Track, databaseId); +} + +void TrackMetadataModel::initializeByTrackFileName(const QUrl &fileName) +{ Q_EMIT needDataByFileName(ElisaUtils::FileName, fileName); } +void TrackMetadataModel::setManager(MusicListenersManager *newManager) +{ + initialize(newManager, nullptr); +} + +void TrackMetadataModel::setDatabase(DatabaseInterface *trackDatabase) +{ + initialize(nullptr, trackDatabase); +} + #include "moc_trackmetadatamodel.cpp" diff --git a/src/qml/BaseTheme.qml b/src/qml/BaseTheme.qml --- a/src/qml/BaseTheme.qml +++ b/src/qml/BaseTheme.qml @@ -65,17 +65,15 @@ } property int playListDelegateHeight: (playListTrackTextHeight.height > dp(28)) ? playListTrackTextHeight.height : dp(28) - property int playListDelegateWithHeaderHeight: playListDelegateHeight + - elisaTheme.layoutVerticalMargin * 5 + - playListAuthorTextHeight.height + - playListAlbumTextHeight.height + property int playListHeaderHeight: elisaTheme.layoutVerticalMargin * 5 + playListAuthorTextHeight.height + playListAlbumTextHeight.height property int trackDelegateHeight: dp(45) property int coverImageSize: dp(180) + property int contextCoverImageSize: dp(100) property int smallImageSize: dp(32) property int maximumMetadataWidth: dp(300) diff --git a/src/qml/ContentView.qml b/src/qml/ContentView.qml --- a/src/qml/ContentView.qml +++ b/src/qml/ContentView.qml @@ -45,9 +45,6 @@ function openNowPlaying() { viewManager.closeAllViews(); - - // This is needed to trigger the state change - listViews.currentIndex = 0; } ViewManager { @@ -361,7 +358,6 @@ Layout.fillHeight: true Layout.leftMargin: elisaTheme.layoutHorizontalMargin - Layout.rightMargin: elisaTheme.layoutHorizontalMargin Layout.minimumWidth: contentZone.width Layout.maximumWidth: contentZone.width @@ -394,20 +390,31 @@ Layout.maximumWidth: 1 } - ContextView { + Loader { id: albumContext + active: Layout.minimumWidth != 0 + + sourceComponent: ContextView { + + anchors.fill: parent + + databaseId: elisa.manageHeaderBar.databaseId + title: elisa.manageHeaderBar.title + artistName: elisa.manageHeaderBar.artist + albumName: elisa.manageHeaderBar.album + albumArtUrl: elisa.manageHeaderBar.image + fileUrl: elisa.manageHeaderBar.fileName + } + Layout.fillHeight: true - Layout.minimumWidth: contentZone.width - Layout.maximumWidth: contentZone.width - Layout.preferredWidth: contentZone.width + Layout.minimumWidth: 0 + Layout.maximumWidth: 0 + Layout.preferredWidth: 0 + Layout.leftMargin: elisaTheme.layoutHorizontalMargin * 1.5 visible: Layout.minimumWidth != 0 - - artistName: elisa.manageHeaderBar.artist - albumName: elisa.manageHeaderBar.album - albumArtUrl: elisa.manageHeaderBar.image } } } @@ -430,9 +437,9 @@ } PropertyChanges { target: playList - Layout.minimumWidth: contentZone.width / 2 - Layout.maximumWidth: contentZone.width / 2 - Layout.preferredWidth: contentZone.width / 2 + Layout.minimumWidth: contentZone.width * 2 / 5 + elisaTheme.layoutHorizontalMargin + Layout.maximumWidth: contentZone.width * 2 / 5 + elisaTheme.layoutHorizontalMargin + Layout.preferredWidth: contentZone.width * 2 / 5 + elisaTheme.layoutHorizontalMargin } PropertyChanges { target: viewSeparatorItem @@ -442,9 +449,9 @@ } PropertyChanges { target: albumContext - Layout.minimumWidth: contentZone.width / 2 - Layout.maximumWidth: contentZone.width / 2 - Layout.preferredWidth: contentZone.width / 2 + Layout.minimumWidth: contentZone.width * 3 / 5 - 2 - 3.5 * elisaTheme.layoutHorizontalMargin + Layout.maximumWidth: contentZone.width * 3 / 5 - 2 - 3.5 * elisaTheme.layoutHorizontalMargin + Layout.preferredWidth: contentZone.width * 3 / 5 - 2 - 3.5 * elisaTheme.layoutHorizontalMargin } }, State { diff --git a/src/qml/ContextView.qml b/src/qml/ContextView.qml --- a/src/qml/ContextView.qml +++ b/src/qml/ContextView.qml @@ -15,154 +15,185 @@ * along with this program. If not, see . */ -import QtQuick 2.7 +import QtQuick 2.10 import QtQuick.Window 2.2 import QtQuick.Controls 2.2 import QtQml.Models 2.2 -import org.kde.elisa 1.0 import QtQuick.Layouts 1.2 -Item { +import org.kde.elisa 1.0 + +FocusScope { id: topItem - property var albumName - property var artistName - property var tracksCount - property var albumArtUrl + property int databaseId: 0 + property alias title: titleLabel.text + property string albumName: '' + property string artistName: '' + property url albumArtUrl: '' + property string fileUrl: '' + + TrackContextMetaDataModel { + id: metaDataModel + + manager: elisa.musicManager + } ColumnLayout { anchors.fill: parent spacing: 0 - Item { - Layout.fillHeight: true + TextMetrics { + id: titleHeight + text: viewTitleHeight.text + font + { + pointSize: viewTitleHeight.font.pointSize + bold: viewTitleHeight.font.bold + } + } + + LabelWithToolTip { + id: viewTitleHeight + text: i18nc("Title of the context view related to the currently playing track", "Now Playing") + + font.pointSize: elisaTheme.defaultFontPointSize * 2 + + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.topMargin: elisaTheme.layoutVerticalMargin * 3 + Layout.bottomMargin: titleHeight.height } Image { id: albumIcon source: albumArtUrl.toString() === '' ? Qt.resolvedUrl(elisaTheme.defaultAlbumImage) : albumArtUrl - Layout.preferredWidth: elisaTheme.coverImageSize - Layout.preferredHeight: elisaTheme.coverImageSize - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - Layout.maximumWidth: elisaTheme.coverImageSize - Layout.maximumHeight: elisaTheme.coverImageSize + + Layout.fillWidth: true + Layout.maximumHeight: elisaTheme.contextCoverImageSize + Layout.preferredHeight: elisaTheme.contextCoverImageSize + Layout.bottomMargin: elisaTheme.layoutVerticalMargin - width: elisaTheme.coverImageSize - height: elisaTheme.coverImageSize + width: elisaTheme.contextCoverImageSize + height: elisaTheme.contextCoverImageSize - sourceSize.width: elisaTheme.coverImageSize - sourceSize.height: elisaTheme.coverImageSize + sourceSize.width: parent.width + sourceSize.height: elisaTheme.contextCoverImageSize asynchronous: true - fillMode: Image.PreserveAspectFit + fillMode: Image.PreserveAspectCrop } LabelWithToolTip { id: titleLabel - text: if (albumName !== undefined) - albumName - else - '' - + font.pointSize: elisaTheme.defaultFontPointSize * 2 font.weight: Font.Bold - color: myPalette.text - horizontalAlignment: Text.AlignHCenter + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.topMargin: elisaTheme.layoutVerticalMargin - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + elide: Text.ElideRight + } + + LabelWithToolTip { + id: albumArtistLabel + + text: (artistName && albumName ? i18nc('display of artist and album in context view', 'by %1 from %2', artistName, albumName) : '') + + font.pointSize: elisaTheme.defaultFontPointSize * 1.4 + + visible: artistName !== '' && albumName !== '' + + Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.bottomMargin: elisaTheme.layoutVerticalMargin elide: Text.ElideRight } LabelWithToolTip { - id: artistLabel + id: albumLabel - text: if (artistName !== undefined) - artistName - else - '' + text: (albumName ? i18nc('display of album in context view', 'from %1', albumName) : '') - font.weight: Font.Normal - color: myPalette.text + font.pointSize: elisaTheme.defaultFontPointSize * 1.4 - horizontalAlignment: Text.AlignHCenter + visible: artistName === '' && albumName !== '' - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.bottomMargin: elisaTheme.layoutVerticalMargin elide: Text.ElideRight } LabelWithToolTip { - id: numberLabel - - text: i18np("1 track", "%1 track", tracksCount) + id: artistLabel - visible: tracksCount !== undefined + text: (artistName ? i18nc('display of artist in context view', 'by %1', artistName) : '') - font.weight: Font.Light - color: myPalette.text + font.pointSize: elisaTheme.defaultFontPointSize * 1.4 - horizontalAlignment: Text.AlignHCenter + visible: artistName !== '' && albumName === '' - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.bottomMargin: elisaTheme.layoutVerticalMargin elide: Text.ElideRight } + Repeater { + model: metaDataModel + + delegate: MetaDataDelegate { + } + } + Item { Layout.fillHeight: true } - RowLayout { - Layout.fillWidth: true - Layout.bottomMargin: elisaTheme.layoutVerticalMargin * 2 + Row { + Layout.alignment: Qt.AlignLeft | Qt.AlignBottom + Layout.topMargin: elisaTheme.layoutVerticalMargin + Layout.bottomMargin: elisaTheme.layoutVerticalMargin - spacing: 0 + spacing: elisaTheme.layoutHorizontalMargin Image { - id: artistJumpIcon - - source: Qt.resolvedUrl(elisaTheme.defaultArtistImage) + sourceSize.width: fileNameLabel.height + sourceSize.height: fileNameLabel.height - Layout.preferredWidth: elisaTheme.smallImageSize - Layout.preferredHeight: elisaTheme.smallImageSize - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - Layout.maximumWidth: elisaTheme.smallImageSize - Layout.maximumHeight: elisaTheme.smallImageSize - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + source: elisaTheme.folderIcon + } - visible: artistName !== undefined - width: elisaTheme.smallImageSize - height: elisaTheme.smallImageSize + LabelWithToolTip { + id: fileNameLabel - sourceSize.width: elisaTheme.smallImageSize - sourceSize.height: elisaTheme.smallImageSize + text: fileUrl - fillMode: Image.PreserveAspectFit + elide: Text.ElideRight } + } + } - LabelWithToolTip { - text: if (artistName !== undefined) - artistName - else - '' + onDatabaseIdChanged: { + metaDataModel.initializeByTrackId(databaseId) + } - font.weight: Font.Normal - color: myPalette.text + Connections { + target: elisa - horizontalAlignment: Text.AlignLeft - } + onMusicManagerChanged: { + metaDataModel.initializeByTrackId(databaseId) + } + } + + Component.onCompleted: { + if (elisa.musicManager) { + metaDataModel.initializeByTrackId(databaseId) } } } diff --git a/src/qml/MediaTrackMetadataView.qml b/src/qml/MediaTrackMetadataView.qml --- a/src/qml/MediaTrackMetadataView.qml +++ b/src/qml/MediaTrackMetadataView.qml @@ -39,6 +39,8 @@ TrackMetadataModel { id: realModel + + manager: elisa.musicManager } modality: Qt.NonModal @@ -157,7 +159,7 @@ Label { id: metaDataLabels - text: model.name + text: i18nc('name of a property for the track metadata detailled view', '%1:', model.name) color: myPalette.text horizontalAlignment: Text.AlignRight @@ -224,19 +226,19 @@ onMusicManagerChanged: { if (databaseId !== 0) { - realModel.initializeByTrackId(elisa.musicManager, databaseId) + realModel.initializeByTrackId(databaseId) } else { - realModel.initializeByTrackFileName(elisa.musicManager, fileName) + realModel.initializeByTrackFileName(fileName) } } } Component.onCompleted: { if (elisa.musicManager) { if (databaseId !== 0) { - realModel.initializeByTrackId(elisa.musicManager, databaseId) + realModel.initializeByTrackId(databaseId) } else { - realModel.initializeByTrackFileName(elisa.musicManager, fileName) + realModel.initializeByTrackFileName(fileName) } } } diff --git a/src/qml/MetaDataDelegate.qml b/src/qml/MetaDataDelegate.qml new file mode 100644 --- /dev/null +++ b/src/qml/MetaDataDelegate.qml @@ -0,0 +1,106 @@ +/* + * Copyright 2016 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.2 +import QtQuick.Layouts 1.2 + +import org.kde.elisa 1.0 + +RowLayout { + id: delegateRow + spacing: 0 + + width: topItem.width + height: metaDataLabelMetric.height * 1.5 + + TextMetrics { + id: metaDataLabelMetric + + text: model.name + + font.weight: Font.Bold + } + + Label { + id: metaDataLabels + + text: model.name + + font.weight: Font.Bold + + horizontalAlignment: Text.AlignLeft + + Layout.preferredWidth: 0.8 * elisaTheme.coverImageSize + Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin * 2 : 0 + Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin * 2 : 0 + } + + Loader { + active: model.type === TrackMetadataModel.TextEntry || model.type === TrackMetadataModel.IntegerEntry + visible: model.type === TrackMetadataModel.TextEntry || model.type === TrackMetadataModel.IntegerEntry + + Layout.fillWidth: true + + sourceComponent: LabelWithToolTip { + text: model.display + + horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight + + anchors.fill: parent + } + } + + Loader { + active: model.type === TrackMetadataModel.DateEntry + visible: model.type === TrackMetadataModel.DateEntry + + Layout.fillWidth: true + + sourceComponent: LabelWithToolTip { + text: rawDate.toLocaleDateString() + + horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight + + anchors.fill: parent + + property date rawDate: new Date(model.display) + } + } + + Loader { + active: model.type === TrackMetadataModel.RatingEntry + visible: model.type === TrackMetadataModel.RatingEntry + + Layout.fillWidth: true + + sourceComponent: RatingStar { + starRating: model.display + starSize: elisaTheme.ratingStarSize + + readOnly: true + + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + } + } + } +} diff --git a/src/resources.qrc b/src/resources.qrc --- a/src/resources.qrc +++ b/src/resources.qrc @@ -42,6 +42,7 @@ qml/ViewSelector.qml qml/PlayListAlbumHeader.qml qml/BasicPlayListAlbumHeader.qml + qml/MetaDataDelegate.qml windows/WindowsTheme.qml