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.h b/src/databaseinterface.h --- a/src/databaseinterface.h +++ b/src/databaseinterface.h @@ -85,6 +85,7 @@ PlayCounter, PlayFrequency, ElementTypeRole, + LyricsRole, }; Q_ENUM(ColumnsRoles) @@ -186,6 +187,11 @@ return operator[](key_type::LyricistRole).toString(); } + QString lyrics() const + { + return operator[](key_type::LyricsRole).toString(); + } + QString comment() const { return operator[](key_type::CommentRole).toString(); 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.h b/src/filescanner.h --- a/src/filescanner.h +++ b/src/filescanner.h @@ -18,6 +18,8 @@ #ifndef FILESCANNER_H #define FILESCANNER_H +#include "elisaLib_export.h" + #include "musicaudiotrack.h" #include @@ -40,7 +42,7 @@ class FileScannerPrivate; -class FileScanner +class ELISALIB_EXPORT FileScanner { public: diff --git a/src/filescanner.cpp b/src/filescanner.cpp --- a/src/filescanner.cpp +++ b/src/filescanner.cpp @@ -123,6 +123,7 @@ auto yearProperty = d->mAllProperties.find(KFileMetaData::Property::ReleaseYear); auto composerProperty = d->mAllProperties.find(KFileMetaData::Property::Composer); auto lyricistProperty = d->mAllProperties.find(KFileMetaData::Property::Lyricist); + auto lyricsProperty = d->mAllProperties.find(KFileMetaData::Property::Lyrics); auto channelsProperty = d->mAllProperties.find(KFileMetaData::Property::Channels); auto bitRateProperty = d->mAllProperties.find(KFileMetaData::Property::BitRate); auto sampleRateProperty = d->mAllProperties.find(KFileMetaData::Property::SampleRate); @@ -154,8 +155,6 @@ if (discNumberProperty != d->mAllProperties.end()) { trackData.setDiscNumber(discNumberProperty->toInt()); - } else { - trackData.setDiscNumber(1); } if (albumArtistProperty != d->mAllProperties.end()) { @@ -190,6 +189,10 @@ trackData.setLyricist(lyricistProperty->toStringList().join(QStringLiteral(", "))); } + if (lyricsProperty != d->mAllProperties.end()) { + trackData.setLyrics(lyricsProperty->toString()); + } + if (trackData.artist().isEmpty()) { trackData.setArtist(trackData.albumArtist()); } 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 @@ -23,9 +23,12 @@ #include "elisautils.h" #include "databaseinterface.h" #include "modeldataloader.h" +#include "filescanner.h" #include #include +#include +#include class MusicListenersManager; @@ -41,6 +44,11 @@ READ fileUrl NOTIFY fileUrlChanged) + Q_PROPERTY(MusicListenersManager* manager + READ manager + WRITE setManager + NOTIFY managerChanged) + public: enum ColumnRoles @@ -55,14 +63,17 @@ IntegerEntry, RatingEntry, DateEntry, + LongTextEntry, }; Q_ENUM(ItemType) using TrackDataType = DatabaseInterface::TrackDataType; explicit TrackMetadataModel(QObject *parent = nullptr); + ~TrackMetadataModel() override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; @@ -72,16 +83,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 +99,43 @@ 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 setDatabase(DatabaseInterface *trackDatabase); + +protected: + + void fillDataFromTrackData(const TrackMetadataModel::TrackDataType &trackData); - void initializeByTrackFileName(MusicListenersManager *manager, const QUrl &fileName); + virtual void filterDataFromTrackData(); + + void removeMetaData(DatabaseInterface::ColumnsRoles metaData); + + TrackDataType::mapped_type dataFromType(TrackDataType::key_type metaData) const; + +private Q_SLOTS: + + void lyricsValueIsReady(); private: + void initialize(MusicListenersManager *newManager, + DatabaseInterface *trackDatabase); + + void fetchLyrics(); + + TrackDataType mFullData; + TrackDataType mTrackData; QUrl mCoverImage; @@ -112,6 +146,14 @@ ModelDataLoader mDataLoader; + MusicListenersManager *mManager = nullptr; + + FileScanner mFileScanner; + + QMimeDatabase mMimeDatabase; + + QFutureWatcher mLyricsValueWatcher; + }; #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 @@ -21,9 +21,20 @@ #include +#include + TrackMetadataModel::TrackMetadataModel(QObject *parent) : QAbstractListModel(parent) { + connect(&mLyricsValueWatcher, &QFutureWatcher::finished, + this, &TrackMetadataModel::lyricsValueIsReady); +} + +TrackMetadataModel::~TrackMetadataModel() +{ + if (mLyricsValueWatcher.isRunning() && !mLyricsValueWatcher.isFinished()) { + mLyricsValueWatcher.waitForFinished(); + } } int TrackMetadataModel::rowCount(const QModelIndex &parent) const @@ -50,58 +61,61 @@ 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::LyricsRole: + result = i18nc("Lyrics label for track metadata view", "Lyrics"); break; case DatabaseInterface::SecondaryTextRole: case DatabaseInterface::ImageUrlRole: @@ -171,6 +185,9 @@ case DatabaseInterface::PlayCounter: result = IntegerEntry; break; + case DatabaseInterface::LyricsRole: + result = LongTextEntry; + break; case DatabaseInterface::DurationRole: case DatabaseInterface::SampleRateRole: case DatabaseInterface::BitRateRole: @@ -223,47 +240,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,8 +287,11 @@ mTrackData[role] = trackData[role]; } } + filterDataFromTrackData(); endResetModel(); + fetchLyrics(); + mCoverImage = trackData[DatabaseInterface::ImageUrlRole].toUrl(); Q_EMIT coverUrlChanged(); @@ -296,31 +305,104 @@ 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); +} + +TrackMetadataModel::TrackDataType::mapped_type TrackMetadataModel::dataFromType(TrackDataType::key_type metaData) const +{ + return mFullData[metaData]; +} + +void TrackMetadataModel::lyricsValueIsReady() +{ + if (!mLyricsValueWatcher.result().isEmpty()) { + beginInsertRows({}, mTrackData.size(), mTrackData.size()); + mTrackKeys.push_back(DatabaseInterface::LyricsRole); + mTrackData[DatabaseInterface::LyricsRole] = mLyricsValueWatcher.result(); + mFullData[DatabaseInterface::LyricsRole] = mLyricsValueWatcher.result(); + endInsertRows(); + } } -void TrackMetadataModel::initializeByTrackFileName(MusicListenersManager *manager, const QUrl &fileName) +void TrackMetadataModel::initialize(MusicListenersManager *newManager, DatabaseInterface *trackDatabase) { - mDataLoader.setDatabase(manager->viewDatabase()); - manager->connectModel(&mDataLoader); + 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::fetchLyrics() +{ + auto lyricicsValue = QtConcurrent::run(QThreadPool::globalInstance(), [=]() { + auto trackData = mFileScanner.scanOneFile(mFullData[DatabaseInterface::ResourceRole].toUrl(), mMimeDatabase); + if (!trackData.lyrics().isEmpty()) { + return trackData.lyrics(); + } + return QString{}; + }); + + mLyricsValueWatcher.setFuture(lyricicsValue); +} + +void TrackMetadataModel::initializeByTrackId(qulonglong databaseId) +{ + mFullData.clear(); + mTrackData.clear(); + mCoverImage.clear(); + mFileUrl.clear(); + + Q_EMIT needDataByDatabaseId(ElisaUtils::Track, databaseId); +} + +void TrackMetadataModel::initializeByTrackFileName(const QUrl &fileName) +{ + mFullData.clear(); + mTrackData.clear(); + mCoverImage.clear(); + mFileUrl.clear(); 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/musicaudiotrack.h b/src/musicaudiotrack.h --- a/src/musicaudiotrack.h +++ b/src/musicaudiotrack.h @@ -113,6 +113,10 @@ QString lyricist() const; + void setLyrics(const QString &value); + + QString lyrics() const; + void setComment(const QString &value); QString comment() const; diff --git a/src/musicaudiotrack.cpp b/src/musicaudiotrack.cpp --- a/src/musicaudiotrack.cpp +++ b/src/musicaudiotrack.cpp @@ -38,7 +38,7 @@ mTitle(std::move(aTitle)), mArtist(std::move(aArtist)), mAlbumName(std::move(aAlbumName)), mAlbumArtist(std::move(aAlbumArtist)), mGenre(std::move(aGenre)), mComposer(std::move(aComposer)), mLyricist(std::move(aLyricist)), - mResourceURI(std::move(aResourceURI)), mAlbumCover(std::move(aAlbumCover)), + mLyrics(), mResourceURI(std::move(aResourceURI)), mAlbumCover(std::move(aAlbumCover)), mFileModificationTime(std::move(fileModificationTime)), mDuration(aDuration), mTrackNumber(aTrackNumber), mDiscNumber(aDiscNumber), mRating(rating), mIsValid(aValid), mIsSingleDiscAlbum(aIsSingleDiscAlbum), mHasBooleanCover(aHasEmbeddedCover), @@ -64,6 +64,8 @@ QString mLyricist; + QString mLyrics; + QString mComment; QUrl mResourceURI; @@ -155,7 +157,8 @@ d->mFileModificationTime == other.d->mFileModificationTime && d->mAlbumCover == other.d->mAlbumCover && d->mRating == other.d->mRating && d->mGenre == other.d->mGenre && d->mComposer == other.d->mComposer && - d->mLyricist == other.d->mLyricist && d->mComment == other.d->mComment && + d->mLyricist == other.d->mLyricist && d->mLyrics == other.d->mLyrics && + d->mComment == other.d->mComment && d->mYear == other.d->mYear && d->mChannels == other.d->mChannels && d->mBitRate == other.d->mBitRate && d->mSampleRate == other.d->mSampleRate; @@ -170,7 +173,8 @@ d->mFileModificationTime != other.d->mFileModificationTime || d->mAlbumCover != other.d->mAlbumCover || d->mRating != other.d->mRating || d->mGenre != other.d->mGenre || d->mComposer != other.d->mComposer || - d->mLyricist != other.d->mLyricist || d->mComment != other.d->mComment || + d->mLyricist != other.d->mLyricist || d->mLyrics != other.d->mLyrics || + d->mComment != other.d->mComment || d->mYear != other.d->mYear || d->mChannels != other.d->mChannels || d->mBitRate != other.d->mBitRate || d->mSampleRate != other.d->mSampleRate; } @@ -187,6 +191,7 @@ result.setGenre(data.genre()); result.setComposer(data.composer()); result.setLyricist(data.lyricist()); + result.setLyrics(data.lyrics()); result.setComment(data.comment()); result.setAlbumCover(data.albumCover()); result.setTrackNumber(data.trackNumber()); @@ -342,6 +347,16 @@ return d->mLyricist; } +void MusicAudioTrack::setLyrics(const QString &value) +{ + d->mLyrics = value; +} + +QString MusicAudioTrack::lyrics() const +{ + return d->mLyrics; +} + void MusicAudioTrack::setComment(const QString &value) { d->mComment = value; 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,195 @@ * 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 } - Item { - Layout.fillHeight: true - } + ListView { + id: trackData - RowLayout { Layout.fillWidth: true - Layout.bottomMargin: elisaTheme.layoutVerticalMargin * 2 + Layout.fillHeight: true + + ScrollBar.vertical: ScrollBar { + id: scrollBar + } + boundsBehavior: Flickable.StopAtBounds + clip: true spacing: 0 - Image { - id: artistJumpIcon + model: metaDataModel - source: Qt.resolvedUrl(elisaTheme.defaultArtistImage) + delegate: MetaDataDelegate { + width: scrollBar.visible ? (!LayoutMirroring.enabled ? trackData.width - scrollBar.width : trackData.width) : trackData.width + } + } - 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 + Row { + Layout.alignment: Qt.AlignLeft | Qt.AlignBottom + Layout.topMargin: elisaTheme.layoutVerticalMargin + Layout.bottomMargin: elisaTheme.layoutVerticalMargin - visible: artistName !== undefined - width: elisaTheme.smallImageSize - height: elisaTheme.smallImageSize + spacing: elisaTheme.layoutHorizontalMargin - sourceSize.width: elisaTheme.smallImageSize - sourceSize.height: elisaTheme.smallImageSize + Image { + sourceSize.width: fileNameLabel.height + sourceSize.height: fileNameLabel.height - fillMode: Image.PreserveAspectFit + source: elisaTheme.folderIcon } LabelWithToolTip { - text: if (artistName !== undefined) - artistName - else - '' + id: fileNameLabel - font.weight: Font.Normal - color: myPalette.text + text: fileUrl - horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight } } } + + onDatabaseIdChanged: { + metaDataModel.initializeByTrackId(databaseId) + } + + Connections { + target: elisa + + 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 @@ -85,6 +87,7 @@ Layout.fillWidth: true Layout.fillHeight: true + Layout.leftMargin: 2 * elisaTheme.layoutHorizontalMargin focus: true @@ -94,15 +97,16 @@ boundsBehavior: Flickable.StopAtBounds clip: true - ScrollHelper { id: scrollHelper flickable: trackData anchors.fill: trackData } model: realModel - delegate: metadataDelegate + delegate: MetaDataDelegate { + width: scrollBar.visible ? (!LayoutMirroring.enabled ? trackData.width - scrollBar.width : trackData.width) : trackData.width + } } } @@ -146,97 +150,24 @@ } } - Component { - id: metadataDelegate - - RowLayout { - id: delegateRow - spacing: 0 - - width: scrollBar.visible ? trackData.width - scrollBar.width : trackData.width - - Label { - id: metaDataLabels - text: model.name - color: myPalette.text - horizontalAlignment: Text.AlignRight - - Layout.preferredWidth: 0.8 * elisaTheme.coverImageSize - Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - } - - Loader { - active: 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 - - 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 - - Layout.fillWidth: true - - sourceComponent: RatingStar { - starRating: model.display - starSize: elisaTheme.ratingStarSize - - readOnly: true - - anchors { - left: parent.left - top: parent.top - bottom: parent.bottom - } - } - } - } - } - Connections { target: elisa 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,133 @@ +/* + * 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 + + height: (model.type === TrackMetadataModel.LongTextEntry ? longTextDisplayLoader.height : metaDataLabelMetric.height) + (elisaTheme.layoutVerticalMargin / 2) + + TextMetrics { + id: metaDataLabelMetric + + text: 'Metadata Name' + + font.weight: Font.Bold + } + + Label { + id: metaDataLabels + + text: model.name + + font.weight: Font.Bold + + horizontalAlignment: Text.AlignLeft + + Layout.alignment: Qt.AlignTop + Layout.preferredWidth: 0.8 * elisaTheme.coverImageSize + Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin * 2 : 0 + Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin * 2 : 0 + } + + Loader { + id: textDisplayLoader + + active: model.type === TrackMetadataModel.TextEntry || model.type === TrackMetadataModel.IntegerEntry + visible: model.type === TrackMetadataModel.TextEntry || model.type === TrackMetadataModel.IntegerEntry + + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + + sourceComponent: LabelWithToolTip { + text: model.display + + horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight + + anchors.fill: parent + } + } + + Loader { + id: longTextDisplayLoader + + active: model.type === TrackMetadataModel.LongTextEntry + visible: model.type === TrackMetadataModel.LongTextEntry + + Layout.fillWidth: true + Layout.maximumWidth: delegateRow.width - (0.8 * elisaTheme.coverImageSize + elisaTheme.layoutHorizontalMargin * 2) + Layout.alignment: Qt.AlignTop + + sourceComponent: Label { + text: model.display + + horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight + + anchors.fill: parent + + wrapMode: Text.WordWrap + } + } + + Loader { + active: model.type === TrackMetadataModel.DateEntry + visible: model.type === TrackMetadataModel.DateEntry + + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + + 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 + Layout.alignment: Qt.AlignTop + + 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