diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,11 @@ DESCRIPTION "Integration of QML and KDE work spaces." TYPE RECOMMENDED) +find_package(KF5Notifications ${REQUIRED_KF5_VERSION} CONFIG QUIET) +set_package_properties(KF5Notifications PROPERTIES + DESCRIPTION "Abstraction for system notifications." + TYPE RECOMMENDED) + find_package(KF5CoreAddons ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5CoreAddons PROPERTIES DESCRIPTION "Qt addon library with a collection of non-GUI utilities." @@ -182,5 +187,7 @@ ki18n_install(po) endif() +install(FILES elisa.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR}) + feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/elisa.notifyrc b/elisa.notifyrc new file mode 100644 --- /dev/null +++ b/elisa.notifyrc @@ -0,0 +1,9 @@ +[Global] +IconName=elisa +Comment=Notification from the Elisa player +Name=elisa + +[Event/undoClearPlaylist] +Name=Playlist cleared +Comment=You cleared your playlist +Action=Popup diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -137,7 +137,8 @@ LINK_PRIVATE Qt5::Core Qt5::Sql Qt5::Widgets Qt5::Concurrent Qt5::Qml KF5::I18n KF5::CoreAddons - KF5::ConfigCore KF5::ConfigGui) + KF5::ConfigCore KF5::ConfigGui + KF5::Notifications) if (KF5FileMetaData_FOUND) target_link_libraries(elisaLib @@ -376,6 +377,7 @@ elisaLib Qt5::Widgets Qt5::QuickControls2 KF5::I18n KF5::CoreAddons KF5::ConfigCore KF5::ConfigGui + KF5::Notifications ) if (ANDROID) diff --git a/src/audiowrapper.h b/src/audiowrapper.h --- a/src/audiowrapper.h +++ b/src/audiowrapper.h @@ -142,6 +142,8 @@ void setPosition(qint64 position); + void savePosition(qint64 position); + void play(); void pause(); diff --git a/src/audiowrapper_libvlc.cpp b/src/audiowrapper_libvlc.cpp --- a/src/audiowrapper_libvlc.cpp +++ b/src/audiowrapper_libvlc.cpp @@ -259,17 +259,22 @@ } if (d->mMediaDuration == -1 || d->mMediaDuration == 0) { - if (!d->mHasSavedPosition) { - d->mHasSavedPosition = true; - d->mSavedPosition = position; - qDebug() << "AudioWrapper::setPosition" << "restore old position" << d->mSavedPosition; - } + savePosition(position); return; } libvlc_media_player_set_position(d->mPlayer, static_cast(position) / d->mMediaDuration); } +void AudioWrapper::savePosition(qint64 position) +{ + if (!d->mHasSavedPosition) { + d->mHasSavedPosition = true; + d->mSavedPosition = position; + qDebug() << "AudioWrapper::setPosition" << "restore old position" << d->mSavedPosition; + } +} + void AudioWrapper::play() { if (!d->mPlayer) { diff --git a/src/elisaapplication.cpp b/src/elisaapplication.cpp --- a/src/elisaapplication.cpp +++ b/src/elisaapplication.cpp @@ -353,6 +353,7 @@ QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::playerPause, d->mAudioWrapper.get(), &AudioWrapper::pause); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::playerStop, d->mAudioWrapper.get(), &AudioWrapper::stop); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::seek, d->mAudioWrapper.get(), &AudioWrapper::seek); + QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::savePositionInAudioWrapper, d->mAudioWrapper.get(), &AudioWrapper::savePosition); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::skipNextTrack, d->mMediaPlayList.get(), &MediaPlayList::skipNextTrack); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::sourceInError, d->mMediaPlayList.get(), &MediaPlayList::trackInError); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::sourceInError, d->mMusicManager.get(), &MusicListenersManager::playBackError); @@ -363,6 +364,9 @@ QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::ensurePlay, d->mAudioControl.get(), &ManageAudioPlayer::ensurePlay); QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::playListFinished, d->mAudioControl.get(), &ManageAudioPlayer::playListFinished); QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::currentTrackChanged, d->mAudioControl.get(), &ManageAudioPlayer::setCurrentTrack); + QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::clearPlayListPlayer, d->mAudioControl.get(), &ManageAudioPlayer::saveForUndoClearPlaylist); + QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::undoClearPlayListPlayer, d->mAudioControl.get(), &ManageAudioPlayer::restoreForUndoClearPlaylist); + QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::playbackStateChanged, d->mAudioControl.get(), &ManageAudioPlayer::setPlayerPlaybackState); diff --git a/src/manageaudioplayer.h b/src/manageaudioplayer.h --- a/src/manageaudioplayer.h +++ b/src/manageaudioplayer.h @@ -187,6 +187,8 @@ void seek(qint64 position); + void savePositionInAudioWrapper(qint64 position); + void titleRoleChanged(); void artistNameRoleChanged(); @@ -203,6 +205,10 @@ void setCurrentTrack(const QPersistentModelIndex ¤tTrack); + void saveForUndoClearPlaylist(); + + void restoreForUndoClearPlaylist(); + void setPlayListModel(QAbstractItemModel* aPlayListModel); void setUrlRole(int value); @@ -295,6 +301,10 @@ QVariantMap mPersistentState; + bool mUndoPlayingState = false; + + qint64 mUndoPlayerPosition = 0; + }; #endif // MANAGEAUDIOPLAYER_H diff --git a/src/manageaudioplayer.cpp b/src/manageaudioplayer.cpp --- a/src/manageaudioplayer.cpp +++ b/src/manageaudioplayer.cpp @@ -166,6 +166,20 @@ } } +void ManageAudioPlayer::saveForUndoClearPlaylist(){ + mUndoPlayingState = mPlayingState; + + mUndoPlayerPosition = mPlayerPosition; + Q_EMIT savePositionInAudioWrapper(mUndoPlayerPosition); +} + +void ManageAudioPlayer::restoreForUndoClearPlaylist(){ + mPlayerPosition = mUndoPlayerPosition; + Q_EMIT seek(mPlayerPosition); + + mPlayingState = mUndoPlayingState; +} + void ManageAudioPlayer::setPlayListModel(QAbstractItemModel *aPlayListModel) { if (mPlayListModel == aPlayListModel) { diff --git a/src/mediaplaylist.h b/src/mediaplaylist.h --- a/src/mediaplaylist.h +++ b/src/mediaplaylist.h @@ -186,6 +186,10 @@ void currentTrackChanged(QPersistentModelIndex currentTrack); + void clearPlayListPlayer(); + + void undoClearPlayListPlayer(); + void currentTrackRowChanged(); void randomPlayChanged(); @@ -256,6 +260,8 @@ void trackInError(const QUrl &sourceInError, QMediaPlayer::Error playerError); + Q_SCRIPTABLE void undoClearPlayList(); + private Q_SLOTS: void loadPlayListLoaded(); @@ -284,8 +290,13 @@ void enqueueMultipleEntries(const ElisaUtils::EntryDataList &entriesData, ElisaUtils::PlayListEntryType type); + void copyD(); + + void notificationClearPlaylist(); + std::unique_ptr d; + std::unique_ptr dOld; }; class MediaPlayListEntry @@ -297,9 +308,9 @@ explicit MediaPlayListEntry(qulonglong id) : mId(id), mIsValid(true) { } - MediaPlayListEntry(QString title, QString artist, QString album, int trackNumber, int discNumber) + MediaPlayListEntry(QString title, QString artist, QString album, int trackNumber, int discNumber, ElisaUtils::PlayListEntryType entryType = ElisaUtils::Unknown) : mTitle(std::move(title)), mAlbum(std::move(album)), mArtist(std::move(artist)), - mTrackNumber(trackNumber), mDiscNumber(discNumber) { + mTrackNumber(trackNumber), mDiscNumber(discNumber), mEntryType(entryType) { } explicit MediaPlayListEntry(const MusicAudioTrack &track) diff --git a/src/mediaplaylist.cpp b/src/mediaplaylist.cpp --- a/src/mediaplaylist.cpp +++ b/src/mediaplaylist.cpp @@ -29,8 +29,12 @@ #include #include +#include +#include + #include + class MediaPlayListPrivate { public: @@ -55,7 +59,7 @@ }; -MediaPlayList::MediaPlayList(QObject *parent) : QAbstractListModel(parent), d(new MediaPlayListPrivate) +MediaPlayList::MediaPlayList(QObject *parent) : QAbstractListModel(parent), d(new MediaPlayListPrivate), dOld(new MediaPlayListPrivate) { connect(&d->mLoadPlaylist, &QMediaPlaylist::loaded, this, &MediaPlayList::loadPlayListLoaded); connect(&d->mLoadPlaylist, &QMediaPlaylist::loadFailed, this, &MediaPlayList::loadPlayListLoadFailed); @@ -355,8 +359,11 @@ void MediaPlayList::enqueueArtist(const QString &artistName) { + auto newEntry = MediaPlayListEntry{artistName}; + newEntry.mEntryType = ElisaUtils::Artist; + beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size()); - d->mData.push_back(MediaPlayListEntry{artistName}); + d->mData.push_back(newEntry); d->mTrackData.push_back({}); endInsertRows(); @@ -366,15 +373,16 @@ } Q_EMIT tracksCountChanged(); - Q_EMIT newEntryInList(0, artistName, ElisaUtils::Artist); + Q_EMIT newEntryInList(0, artistName, newEntry.mEntryType); Q_EMIT persistentStateChanged(); } void MediaPlayList::enqueueFilesList(const ElisaUtils::EntryDataList &newEntries) { beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + newEntries.size() - 1); for (const auto &oneTrackUrl : newEntries) { auto newEntry = MediaPlayListEntry(QUrl::fromLocalFile(std::get<1>(oneTrackUrl))); + newEntry.mEntryType = ElisaUtils::FileName; d->mData.push_back(newEntry); d->mTrackData.push_back({}); if (newEntry.mTrackUrl.isValid()) { @@ -385,7 +393,7 @@ if (newTrackFile.exists()) { d->mData.last().mIsValid = true; } - Q_EMIT newEntryInList(0, entryString, ElisaUtils::FileName); + Q_EMIT newEntryInList(0, entryString, newEntry.mEntryType); } } } @@ -406,9 +414,10 @@ { beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + newEntries.size() - 1); for (const auto &newTrack : newEntries) { - d->mData.push_back(MediaPlayListEntry{std::get<0>(newTrack)}); + auto newMediaPlayListEntry = MediaPlayListEntry{std::get<0>(newTrack), std::get<1>(newTrack), ElisaUtils::Track}; + d->mData.push_back(newMediaPlayListEntry); d->mTrackData.push_back({}); - Q_EMIT newEntryInList(std::get<0>(newTrack), std::get<1>(newTrack), ElisaUtils::Track); + Q_EMIT newEntryInList(newMediaPlayListEntry.mId, newMediaPlayListEntry.mTitle.toString(), newMediaPlayListEntry.mEntryType); } endInsertRows(); @@ -461,6 +470,11 @@ return; } + Q_EMIT clearPlayListPlayer(); + this->copyD(); + + this->notificationClearPlaylist(); + beginRemoveRows({}, 0, d->mData.count() - 1); d->mData.clear(); d->mTrackData.clear(); @@ -474,6 +488,79 @@ Q_EMIT persistentStateChanged(); } +void MediaPlayList::notificationClearPlaylist() +{ + KNotification *notification= new KNotification ( QStringLiteral("undoClearPlaylist")); + notification->setText( i18n("The playlist has been cleared.")); + + notification->setIconName(QStringLiteral("dialog-information")); + notification->setComponentName(QStringLiteral("elisa")); + notification->setActions(QStringList() << i18n("Undo")); + + connect(notification, &KNotification::action1Activated, this, &MediaPlayList::undoClearPlayList); + notification->sendEvent(); +} + +void MediaPlayList::undoClearPlayList() +{ + beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + dOld->mData.size() - 1); + for (auto &newTrack : dOld->mData) { + d->mData.push_back(newTrack); + d->mTrackData.push_back({}); + + if (ElisaUtils::FileName == newTrack.mEntryType && newTrack.mTrackUrl.isValid()) { + auto entryURL = newTrack.mTrackUrl.toUrl(); + if (entryURL.isLocalFile()) { + auto entryString = entryURL.toLocalFile(); + QFileInfo newTrackFile(entryString); + if (newTrackFile.exists()) { + d->mData.last().mIsValid = true; + } + Q_EMIT newEntryInList(0, entryString, newTrack.mEntryType); + } + } + else if(ElisaUtils::Artist == newTrack.mEntryType){ + Q_EMIT newEntryInList(0, newTrack.mArtist.toString(), newTrack.mEntryType); + } + else{ + Q_EMIT newEntryInList(newTrack.mId, newTrack.mTitle.toString(), newTrack.mEntryType); + } + } + endInsertRows(); + + d->mMusicListenersManager = dOld->mMusicListenersManager; + d->mPersistentState = dOld->mPersistentState; + d->mCurrentPlayListPosition = dOld->mCurrentPlayListPosition; + d->mRandomPlay = dOld->mRandomPlay; + d->mRepeatPlay = dOld->mRepeatPlay; + + auto candidateTrack = index(dOld->mCurrentPlayListPosition, 0); + + if (candidateTrack.isValid() && candidateTrack.data(ColumnsRoles::IsValidRole).toBool()) { + d->mCurrentTrack = candidateTrack; + notifyCurrentTrackChanged(); + } + + Q_EMIT tracksCountChanged(); + Q_EMIT persistentStateChanged(); + + Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0), {MediaPlayList::IsPlayingRole}); + + Q_EMIT undoClearPlayListPlayer(); +} + +void MediaPlayList::copyD() +{ + dOld->mData = d->mData; + dOld->mTrackData = d->mTrackData; + dOld->mMusicListenersManager = d->mMusicListenersManager; + dOld->mCurrentTrack = d->mCurrentTrack; + dOld->mPersistentState = d->mPersistentState; + dOld->mCurrentPlayListPosition = d->mCurrentPlayListPosition; + dOld->mRandomPlay = d->mRandomPlay; + dOld->mRepeatPlay = d->mRepeatPlay; +} + void MediaPlayList::loadPlaylist(const QUrl &fileName) { d->mLoadPlaylist.clear(); @@ -598,6 +685,7 @@ oneData.push_back(oneTrack.album()); oneData.push_back(QString::number(oneTrack.trackNumber())); oneData.push_back(QString::number(oneTrack.discNumber())); + oneData.push_back(QString::number(oneEntry.mEntryType)); result.push_back(QVariant(oneData)); } @@ -655,7 +743,7 @@ for (auto &oneData : persistentState) { auto trackData = oneData.toStringList(); - if (trackData.size() != 5) { + if (trackData.size() != 6) { continue; } @@ -665,7 +753,9 @@ auto restoredTrackNumber = trackData[3].toInt(); auto restoredDiscNumber = trackData[4].toInt(); - enqueueRestoredEntry({restoredTitle, restoredArtist, restoredAlbum, restoredTrackNumber, restoredDiscNumber}); + ElisaUtils::PlayListEntryType mEntryType = static_cast(trackData[5].toInt()); + + enqueueRestoredEntry({restoredTitle, restoredArtist, restoredAlbum, restoredTrackNumber, restoredDiscNumber, mEntryType}); } restorePlayListPosition(); @@ -717,7 +807,9 @@ if (tracks.size() > 1) { beginInsertRows(QModelIndex(), playListIndex + 1, playListIndex - 1 + tracks.size()); for (int trackIndex = 1; trackIndex < tracks.size(); ++trackIndex) { - d->mData.push_back(MediaPlayListEntry{tracks[trackIndex].databaseId()}); + auto newEntry = MediaPlayListEntry{tracks[trackIndex]}; + newEntry.mEntryType = ElisaUtils::Track; + d->mData.push_back(newEntry); d->mTrackData.push_back(tracks[trackIndex]); } endInsertRows();