diff --git a/src/elisaapplication.cpp b/src/elisaapplication.cpp index 05d86a1c..6c8f9435 100644 --- a/src/elisaapplication.cpp +++ b/src/elisaapplication.cpp @@ -1,547 +1,551 @@ /* * Copyright 2017 Matthieu Gallien * Copyright (C) 2012 Aleix Pol Gonzalez * * 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 "elisaapplication.h" #include "musiclistenersmanager.h" #include "models/allalbumsproxymodel.h" #include "models/alltracksproxymodel.h" #include "models/allartistsproxymodel.h" #include "models/singleartistproxymodel.h" #include "models/singlealbumproxymodel.h" #if defined KF5KIO_FOUND && KF5KIO_FOUND #include "models/filebrowserproxymodel.h" #endif #include "mediaplaylist.h" #include "audiowrapper.h" #include "manageaudioplayer.h" #include "managemediaplayercontrol.h" #include "manageheaderbar.h" #include "elisa_settings.h" #include #if defined KF5ConfigWidgets_FOUND && KF5ConfigWidgets_FOUND #include #endif #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND #include #include #include #include #include #endif #if defined KF5KCMUtils_FOUND && KF5KCMUtils_FOUND #include #endif #include #include #include #include #include #include #include #include #include #include #include #include class ElisaApplicationPrivate { public: explicit ElisaApplicationPrivate(QObject *parent) #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND : mCollection(parent) #endif { Q_UNUSED(parent) auto configurationFileName = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); configurationFileName += QStringLiteral("/elisarc"); Elisa::ElisaConfiguration::instance(configurationFileName); Elisa::ElisaConfiguration::self()->load(); Elisa::ElisaConfiguration::self()->save(); } #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND KActionCollection mCollection; #endif QStringList mArguments; std::unique_ptr mMusicManager; std::unique_ptr mAllAlbumsProxyModel; std::unique_ptr mAllArtistsProxyModel; std::unique_ptr mAllTracksProxyModel; std::unique_ptr mAllGenresProxyModel; std::unique_ptr mAllComposersProxyModel; std::unique_ptr mAllLyricistsProxyModel; std::unique_ptr mSingleArtistProxyModel; std::unique_ptr mSingleAlbumProxyModel; #if defined KF5KIO_FOUND && KF5KIO_FOUND std::unique_ptr mFileBrowserProxyModel; #endif std::unique_ptr mMediaPlayList; std::unique_ptr mAudioWrapper; std::unique_ptr mAudioControl; std::unique_ptr mPlayerControl; std::unique_ptr mManageHeaderBar; }; ElisaApplication::ElisaApplication(QObject *parent) : QObject(parent), d(std::make_unique(this)) { } ElisaApplication::~ElisaApplication() = default; void ElisaApplication::setupActions(const QString &actionName) { #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND if (actionName == QStringLiteral("file_quit")) { auto quitAction = KStandardAction::quit(QCoreApplication::instance(), &QCoreApplication::quit, &d->mCollection); d->mCollection.addAction(actionName, quitAction); } if (actionName == QStringLiteral("help_contents") && KAuthorized::authorizeAction(actionName)) { auto handBookAction = KStandardAction::helpContents(this, &ElisaApplication::appHelpActivated, &d->mCollection); d->mCollection.addAction(handBookAction->objectName(), handBookAction); } if (actionName == QStringLiteral("help_report_bug") && KAuthorized::authorizeAction(actionName) && !KAboutData::applicationData().bugAddress().isEmpty()) { auto reportBugAction = KStandardAction::reportBug(this, &ElisaApplication::reportBug, &d->mCollection); d->mCollection.addAction(reportBugAction->objectName(), reportBugAction); } if (actionName == QStringLiteral("help_about_app") && KAuthorized::authorizeAction(actionName)) { auto aboutAppAction = KStandardAction::aboutApp(this, &ElisaApplication::aboutApplication, this); d->mCollection.addAction(aboutAppAction->objectName(), aboutAppAction); } if (actionName == QStringLiteral("options_configure") && KAuthorized::authorizeAction(actionName)) { auto preferencesAction = KStandardAction::preferences(this, &ElisaApplication::configureElisa, this); d->mCollection.addAction(preferencesAction->objectName(), preferencesAction); } if (actionName == QStringLiteral("options_configure_keybinding") && KAuthorized::authorizeAction(actionName)) { auto keyBindingsAction = KStandardAction::keyBindings(this, &ElisaApplication::configureShortcuts, this); d->mCollection.addAction(keyBindingsAction->objectName(), keyBindingsAction); } if (actionName == QStringLiteral("go_back") && KAuthorized::authorizeAction(actionName)) { auto goBackAction = KStandardAction::back(this, &ElisaApplication::goBack, this); d->mCollection.addAction(goBackAction->objectName(), goBackAction); } if (actionName == QStringLiteral("toggle_playlist") && KAuthorized::authorizeAction(actionName)) { auto togglePlaylistAction = d->mCollection.addAction(actionName, this, &ElisaApplication::togglePlaylist); togglePlaylistAction->setShortcut(QKeySequence(Qt::Key_F9)); togglePlaylistAction->setText(QStringLiteral("Toggle Playlist")); } if (actionName == QStringLiteral("edit_find") && KAuthorized::authorizeAction(actionName)) { auto findAction = KStandardAction::find(this, &ElisaApplication::find, this); d->mCollection.addAction(findAction->objectName(), findAction); } d->mCollection.readSettings(); #endif } void ElisaApplication::setArguments(const QStringList &newArguments) { if (d->mArguments == newArguments) { return; } d->mArguments = checkFileListAndMakeAbsolute(newArguments, QDir::currentPath()); Q_EMIT argumentsChanged(); if (!d->mArguments.isEmpty()) { Q_EMIT enqueue(d->mArguments); } } void ElisaApplication::activateActionRequested(const QString &actionName, const QVariant ¶meter) { Q_UNUSED(actionName) Q_UNUSED(parameter) } void ElisaApplication::activateRequested(const QStringList &arguments, const QString &workingDirectory) { auto realArguments = arguments; if (realArguments.size() > 1) { realArguments.removeFirst(); Q_EMIT enqueue(checkFileListAndMakeAbsolute(realArguments, workingDirectory)); } } void ElisaApplication::openRequested(const QList &uris) { Q_UNUSED(uris) } void ElisaApplication::appHelpActivated() { QDesktopServices::openUrl(QUrl(QStringLiteral("help:/"))); } void ElisaApplication::aboutApplication() { #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND static QPointer dialog; if (!dialog) { dialog = new KAboutApplicationDialog(KAboutData::applicationData(), nullptr); dialog->setAttribute(Qt::WA_DeleteOnClose); } dialog->show(); #endif } void ElisaApplication::reportBug() { #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND static QPointer dialog; if (!dialog) { dialog = new KBugReport(KAboutData::applicationData(), nullptr); dialog->setAttribute(Qt::WA_DeleteOnClose); } dialog->show(); #endif } void ElisaApplication::configureShortcuts() { #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, nullptr); dlg.setModal(true); dlg.addCollection(&d->mCollection); dlg.configure(); #endif } void ElisaApplication::configureElisa() { #if defined KF5KCMUtils_FOUND && KF5KCMUtils_FOUND KCMultiDialog configurationDialog; configurationDialog.addModule(QStringLiteral("kcm_elisa_local_file")); configurationDialog.setModal(true); configurationDialog.exec(); #endif } void ElisaApplication::goBack() {} void ElisaApplication::find() {} void ElisaApplication::togglePlaylist() {} QStringList ElisaApplication::checkFileListAndMakeAbsolute(const QStringList &filesList, const QString &workingDirectory) const { QStringList filesToOpen; for (const auto &oneFile : filesList) { auto newFile = QFileInfo(oneFile); if (newFile.isRelative()) { newFile = QFileInfo(workingDirectory + QStringLiteral("/") + oneFile); } if (newFile.exists()) { filesToOpen.push_back(newFile.canonicalFilePath()); } } return filesToOpen; } void ElisaApplication::initialize() { initializeModels(); initializePlayer(); Q_EMIT initializationDone(); } void ElisaApplication::initializeModels() { d->mMusicManager = std::make_unique(); Q_EMIT musicManagerChanged(); d->mAllAlbumsProxyModel = std::make_unique(); Q_EMIT allAlbumsProxyModelChanged(); d->mAllArtistsProxyModel = std::make_unique(); Q_EMIT allArtistsProxyModelChanged(); d->mAllGenresProxyModel = std::make_unique(); Q_EMIT allGenresProxyModelChanged(); d->mAllComposersProxyModel = std::make_unique(); Q_EMIT allComposersProxyModelChanged(); d->mAllLyricistsProxyModel = std::make_unique(); Q_EMIT allLyricistsProxyModelChanged(); d->mAllTracksProxyModel = std::make_unique(); Q_EMIT allTracksProxyModelChanged(); d->mSingleArtistProxyModel = std::make_unique(); Q_EMIT singleArtistProxyModelChanged(); d->mSingleAlbumProxyModel = std::make_unique(); Q_EMIT singleAlbumProxyModelChanged(); #if defined KF5KIO_FOUND && KF5KIO_FOUND d->mFileBrowserProxyModel = std::make_unique(); Q_EMIT fileBrowserProxyModelChanged(); #endif d->mMediaPlayList = std::make_unique(); Q_EMIT mediaPlayListChanged(); d->mMusicManager->setElisaApplication(this); d->mMediaPlayList->setMusicListenersManager(d->mMusicManager.get()); QObject::connect(this, &ElisaApplication::enqueue, d->mMediaPlayList.get(), &MediaPlayList::enqueueAndPlay); d->mAllAlbumsProxyModel->setSourceModel(d->mMusicManager->allAlbumsModel()); d->mAllArtistsProxyModel->setSourceModel(d->mMusicManager->allArtistsModel()); d->mAllGenresProxyModel->setSourceModel(d->mMusicManager->allGenresModel()); d->mAllComposersProxyModel->setSourceModel(d->mMusicManager->allComposersModel()); d->mAllLyricistsProxyModel->setSourceModel(d->mMusicManager->allLyricistsModel()); d->mAllTracksProxyModel->setSourceModel(d->mMusicManager->allTracksModel()); d->mSingleArtistProxyModel->setSourceModel(d->mMusicManager->allAlbumsModel()); d->mSingleAlbumProxyModel->setSourceModel(d->mMusicManager->albumModel()); QObject::connect(d->mAllAlbumsProxyModel.get(), &AllAlbumsProxyModel::albumToEnqueue, d->mMediaPlayList.get(), static_cast &, ElisaUtils::PlayListEnqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay)>(&MediaPlayList::enqueue)); QObject::connect(d->mAllArtistsProxyModel.get(), &AllArtistsProxyModel::artistToEnqueue, d->mMediaPlayList.get(), &MediaPlayList::enqueueArtists); QObject::connect(d->mAllTracksProxyModel.get(), &AllTracksProxyModel::trackToEnqueue, d->mMediaPlayList.get(), static_cast &, ElisaUtils::PlayListEnqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay)>(&MediaPlayList::enqueue)); QObject::connect(d->mSingleArtistProxyModel.get(), &SingleArtistProxyModel::albumToEnqueue, d->mMediaPlayList.get(), static_cast &, ElisaUtils::PlayListEnqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay)>(&MediaPlayList::enqueue)); QObject::connect(d->mSingleAlbumProxyModel.get(), &SingleAlbumProxyModel::trackToEnqueue, d->mMediaPlayList.get(), static_cast &, ElisaUtils::PlayListEnqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay)>(&MediaPlayList::enqueue)); #if defined KF5KIO_FOUND && KF5KIO_FOUND + QObject::connect(d->mFileBrowserProxyModel.get(), &FileBrowserProxyModel::replaceAndPlayFileByUrl, + d->mMediaPlayList.get(), static_cast(&MediaPlayList::replaceAndPlay)); + QObject::connect(d->mFileBrowserProxyModel.get(), &FileBrowserProxyModel::loadPlayListFromUrl, + d->mMediaPlayList.get(), &MediaPlayList::loadPlaylist); QObject::connect(d->mFileBrowserProxyModel.get(), &FileBrowserProxyModel::filesToEnqueue, d->mMediaPlayList.get(), static_cast &, ElisaUtils::PlayListEnqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay)>(&MediaPlayList::enqueue)); #endif } void ElisaApplication::initializePlayer() { d->mAudioWrapper = std::make_unique(); Q_EMIT audioPlayerChanged(); d->mAudioControl = std::make_unique(); Q_EMIT audioControlChanged(); d->mPlayerControl = std::make_unique(); Q_EMIT playerControlChanged(); d->mManageHeaderBar = std::make_unique(); Q_EMIT manageHeaderBarChanged(); d->mAudioControl->setAlbumNameRole(MediaPlayList::AlbumRole); d->mAudioControl->setArtistNameRole(MediaPlayList::ArtistRole); d->mAudioControl->setTitleRole(MediaPlayList::TitleRole); d->mAudioControl->setUrlRole(MediaPlayList::ResourceRole); d->mAudioControl->setIsPlayingRole(MediaPlayList::IsPlayingRole); d->mAudioControl->setPlayListModel(d->mMediaPlayList.get()); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::playerPlay, d->mAudioWrapper.get(), &AudioWrapper::play); 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::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); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::playerSourceChanged, d->mAudioWrapper.get(), &AudioWrapper::setSource); 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->mAudioWrapper.get(), &AudioWrapper::playbackStateChanged, d->mAudioControl.get(), &ManageAudioPlayer::setPlayerPlaybackState); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::statusChanged, d->mAudioControl.get(), &ManageAudioPlayer::setPlayerStatus); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::errorChanged, d->mAudioControl.get(), &ManageAudioPlayer::setPlayerError); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::durationChanged, d->mAudioControl.get(), &ManageAudioPlayer::setAudioDuration); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::seekableChanged, d->mAudioControl.get(), &ManageAudioPlayer::setPlayerIsSeekable); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::positionChanged, d->mAudioControl.get(), &ManageAudioPlayer::setPlayerPosition); d->mPlayerControl->setPlayListModel(d->mMediaPlayList.get()); QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::currentTrackChanged, d->mPlayerControl.get(), &ManageMediaPlayerControl::setCurrentTrack); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::playing, d->mPlayerControl.get(), &ManageMediaPlayerControl::playerPlaying); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::paused, d->mPlayerControl.get(), &ManageMediaPlayerControl::playerPaused); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::stopped, d->mPlayerControl.get(), &ManageMediaPlayerControl::playerStopped); d->mManageHeaderBar->setTitleRole(MediaPlayList::TitleRole); d->mManageHeaderBar->setAlbumRole(MediaPlayList::AlbumRole); d->mManageHeaderBar->setArtistRole(MediaPlayList::ArtistRole); d->mManageHeaderBar->setImageRole(MediaPlayList::ImageRole); d->mManageHeaderBar->setAlbumIdRole(MediaPlayList::AlbumIdRole); d->mManageHeaderBar->setIsValidRole(MediaPlayList::IsValidRole); d->mManageHeaderBar->setPlayListModel(d->mMediaPlayList.get()); QObject::connect(d->mMediaPlayList.get(), &MediaPlayList::currentTrackChanged, d->mManageHeaderBar.get(), &ManageHeaderBar::setCurrentTrack); if (!d->mArguments.isEmpty()) { Q_EMIT enqueue(d->mArguments); } } QAction * ElisaApplication::action(const QString& name) { #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND auto resultAction = d->mCollection.action(name); if (!resultAction) { setupActions(name); resultAction = d->mCollection.action(name); } return resultAction; #else Q_UNUSED(name); return new QAction(); #endif } QString ElisaApplication::iconName(const QIcon& icon) { return icon.name(); } const QStringList &ElisaApplication::arguments() const { return d->mArguments; } MusicListenersManager *ElisaApplication::musicManager() const { return d->mMusicManager.get(); } QSortFilterProxyModel *ElisaApplication::allAlbumsProxyModel() const { return d->mAllAlbumsProxyModel.get(); } QSortFilterProxyModel *ElisaApplication::allArtistsProxyModel() const { return d->mAllArtistsProxyModel.get(); } QSortFilterProxyModel *ElisaApplication::allGenresProxyModel() const { return d->mAllGenresProxyModel.get(); } QSortFilterProxyModel *ElisaApplication::allComposersProxyModel() const { return d->mAllComposersProxyModel.get(); } QSortFilterProxyModel *ElisaApplication::allLyricistsProxyModel() const { return d->mAllLyricistsProxyModel.get(); } QSortFilterProxyModel *ElisaApplication::allTracksProxyModel() const { return d->mAllTracksProxyModel.get(); } QSortFilterProxyModel *ElisaApplication::singleArtistProxyModel() const { return d->mSingleArtistProxyModel.get(); } QSortFilterProxyModel *ElisaApplication::singleAlbumProxyModel() const { return d->mSingleAlbumProxyModel.get(); } QSortFilterProxyModel *ElisaApplication::fileBrowserProxyModel() const { #if defined KF5KIO_FOUND && KF5KIO_FOUND return d->mFileBrowserProxyModel.get(); #else return nullptr; #endif } MediaPlayList *ElisaApplication::mediaPlayList() const { return d->mMediaPlayList.get(); } AudioWrapper *ElisaApplication::audioPlayer() const { return d->mAudioWrapper.get(); } ManageAudioPlayer *ElisaApplication::audioControl() const { return d->mAudioControl.get(); } ManageMediaPlayerControl *ElisaApplication::playerControl() const { return d->mPlayerControl.get(); } ManageHeaderBar *ElisaApplication::manageHeaderBar() const { return d->mManageHeaderBar.get(); } #include "moc_elisaapplication.cpp" diff --git a/src/models/filebrowsermodel.cpp b/src/models/filebrowsermodel.cpp index c9e89b1c..d6b89f33 100644 --- a/src/models/filebrowsermodel.cpp +++ b/src/models/filebrowsermodel.cpp @@ -1,119 +1,128 @@ /* * Copyright 2018 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "filebrowsermodel.h" #include #include #include #include #include FileBrowserModel::FileBrowserModel(QObject *parent) : KDirModel(parent) { QMimeDatabase db; QList mimeList = db.allMimeTypes(); QStringList mimeTypes; mimeTypes << QStringLiteral("inode/directory"); foreach (const QMimeType &mime, mimeList) { if (mime.name().startsWith(QStringLiteral("audio/"))) { mimeTypes << mime.name(); } } dirLister()->setMimeFilter(mimeTypes); } FileBrowserModel::~FileBrowserModel() = default; QString FileBrowserModel::url() const { return dirLister()->url().toString(); } void FileBrowserModel::setUrl(const QString &url) { QString path = QUrl(url).path(); path = QUrl::fromLocalFile(path).toString(); if (dirLister()->url().path() == QUrl(path).path()) { dirLister()->updateDirectory(QUrl(path)); return; } beginResetModel(); dirLister()->openUrl(QUrl(path)); endResetModel(); emit urlChanged(); } QHash FileBrowserModel::roleNames() const { auto roles = KDirModel::roleNames(); roles[static_cast(ColumnsRoles::NameRole)] = "name"; roles[static_cast(ColumnsRoles::ContainerDataRole)] = "containerData"; roles[static_cast(ColumnsRoles::ImageUrlRole)] = "imageUrl"; roles[static_cast(ColumnsRoles::DirectoryRole)] = "directory"; + roles[static_cast(ColumnsRoles::IsPlayListRole)] = "isPlaylist"; return roles; } QVariant FileBrowserModel::data(const QModelIndex &index, int role) const { auto result = QVariant(); if (role < ColumnsRoles::NameRole) { result = KDirModel::data(index,role); } switch(role) { case ColumnsRoles::NameRole: { KFileItem item = itemForIndex(index); result = item.name(); break; } case ColumnsRoles::ContainerDataRole: { KFileItem item = itemForIndex(index); result = item.url(); break; } case ColumnsRoles::ImageUrlRole: { KFileItem item = itemForIndex(index); if (item.isDir()) { result = QUrl(QStringLiteral("image://icon/folder")); } else { result = QUrl(QStringLiteral("image://icon/audio-x-generic")); } break; } case ColumnsRoles::DirectoryRole: + { KFileItem item = itemForIndex(index); result = item.isDir(); break; } + case ColumnsRoles::IsPlayListRole: + { + KFileItem item = itemForIndex(index); + result = (item.currentMimeType().inherits(QStringLiteral("audio/x-mpegurl"))); + break; + } + } return result; } #include "moc_filebrowsermodel.cpp" diff --git a/src/models/filebrowsermodel.h b/src/models/filebrowsermodel.h index 0d561dc8..3176cc71 100644 --- a/src/models/filebrowsermodel.h +++ b/src/models/filebrowsermodel.h @@ -1,61 +1,62 @@ /* * Copyright 2018 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef FILEBROWSERMODEL_H #define FILEBROWSERMODEL_H #include "elisaLib_export.h" #include #include class MusicAudioTrack; class ELISALIB_EXPORT FileBrowserModel : public KDirModel { Q_OBJECT public: enum ColumnsRoles { NameRole = Qt::UserRole + 1, ContainerDataRole = Qt::UserRole + 2, ImageUrlRole = Qt::UserRole + 3, - DirectoryRole = Qt::UserRole + 4 + DirectoryRole = Qt::UserRole + 4, + IsPlayListRole = Qt::UserRole + 5 }; Q_ENUM(ColumnsRoles) explicit FileBrowserModel(QObject *parent = nullptr); ~FileBrowserModel() override; QString url() const; QHash roleNames() const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; void setUrl(const QString &url); Q_SIGNALS: void urlChanged(); }; #endif //FILEBROWSERMODEL_H diff --git a/src/models/filebrowserproxymodel.cpp b/src/models/filebrowserproxymodel.cpp index f10cbac8..f760eeb7 100644 --- a/src/models/filebrowserproxymodel.cpp +++ b/src/models/filebrowserproxymodel.cpp @@ -1,190 +1,198 @@ /* * Copyright 2016-2017 Matthieu Gallien * Copyright 2018 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "filebrowserproxymodel.h" #include "filebrowsermodel.h" #include #include #include #include #include "elisautils.h" FileBrowserProxyModel::FileBrowserProxyModel(QObject *parent) : KDirSortFilterProxyModel(parent) { setFilterCaseSensitivity(Qt::CaseInsensitive); mThreadPool.setMaxThreadCount(1); mFileModel = std::make_unique(); setSourceModel(mFileModel.get()); setSortFoldersFirst(true); sort(Qt::AscendingOrder); connect(mFileModel.get(), &FileBrowserModel::urlChanged,this, &FileBrowserProxyModel::urlChanged); mTopFolder = QDir::homePath(); openFolder(mTopFolder, true); } FileBrowserProxyModel::~FileBrowserProxyModel() = default; QString FileBrowserProxyModel::filterText() const { return mFilterText; } void FileBrowserProxyModel::setFilterText(const QString &filterText) { QWriteLocker writeLocker(&mDataLock); if (mFilterText == filterText) return; mFilterText = filterText; mFilterExpression.setPattern(mFilterText); mFilterExpression.setPatternOptions(QRegularExpression::CaseInsensitiveOption); mFilterExpression.optimize(); invalidate(); Q_EMIT filterTextChanged(mFilterText); } bool FileBrowserProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { bool result = false; for (int column = 0, columnCount = sourceModel()->columnCount(source_parent); column < columnCount; ++column) { auto currentIndex = sourceModel()->index(source_row, column, source_parent); const auto &nameValue = sourceModel()->data(currentIndex, FileBrowserModel::NameRole).toString(); if (mFilterExpression.match(nameValue).hasMatch()) { result = true; continue; } if (result) { continue; } if (!result) { break; } } return result; } void FileBrowserProxyModel::enqueueToPlayList() { - qDebug() << "enqueue"; QtConcurrent::run(&mThreadPool, [=] () { QReadLocker locker(&mDataLock); auto allTrackUrls = QList(); for (int rowIndex = 0, maxRowCount = rowCount(); rowIndex < maxRowCount; ++rowIndex) { auto currentIndex = index(rowIndex, 0); if (!data(currentIndex, FileBrowserModel::DirectoryRole).toBool()) { allTrackUrls.push_back(data(currentIndex, FileBrowserModel::ContainerDataRole).toUrl()); } } Q_EMIT filesToEnqueue(allTrackUrls, ElisaUtils::AppendPlayList, ElisaUtils::DoNotTriggerPlay); }); } void FileBrowserProxyModel::replaceAndPlayOfPlayList() { - qDebug() << "replace"; QtConcurrent::run(&mThreadPool, [=] () { QReadLocker locker(&mDataLock); auto allTrackUrls = QList(); for (int rowIndex = 0, maxRowCount = rowCount(); rowIndex < maxRowCount; ++rowIndex) { auto currentIndex = index(rowIndex, 0); if (!data(currentIndex, FileBrowserModel::DirectoryRole).toBool()) { allTrackUrls.push_back(data(currentIndex, FileBrowserModel::ContainerDataRole).toUrl()); } } Q_EMIT filesToEnqueue(allTrackUrls, ElisaUtils::ReplacePlayList, ElisaUtils::TriggerPlay); }); } +void FileBrowserProxyModel::replaceAndPlayOfUrl(const QUrl &fileUrl) +{ + if (mMimeDb.mimeTypeForUrl(fileUrl).inherits(QStringLiteral("audio/x-mpegurl"))) + { + Q_EMIT loadPlayListFromUrl(fileUrl); + } else { + Q_EMIT replaceAndPlayFileByUrl(fileUrl); + } +} + QString FileBrowserProxyModel::parentFolder() const { //return to the top folder if parent directory does not exist QDir dir(mFileModel->dirLister()->url().toLocalFile()); if (dir.cdUp()) { return dir.path(); } else { return mTopFolder; } } void FileBrowserProxyModel::openParentFolder() { if (canGoBack()) { QString parent = parentFolder(); mFileModel->setUrl(parent); if (parent == mTopFolder) { Q_EMIT canGoBackChanged(); } } } bool FileBrowserProxyModel::canGoBack() const { return mFileModel->dirLister()->url().toLocalFile() != mTopFolder; } void FileBrowserProxyModel::openFolder(const QString &folder, bool isDisplayRoot) { if (folder.isEmpty()) { return; } mFileModel->setUrl(folder); if (!isDisplayRoot) { Q_EMIT canGoBackChanged(); } } MusicAudioTrack FileBrowserProxyModel::loadMetaDataFromUrl(const QUrl &url) { auto newTrack = mFileScanner.scanOneFile(url, mMimeDb); qDebug() << "loaded metadata " << url << newTrack; return newTrack; } QString FileBrowserProxyModel::url() const { return mFileModel->dirLister()->url().toLocalFile(); } bool FileBrowserProxyModel::sortedAscending() const { return sortOrder() ? false : true; } void FileBrowserProxyModel::sortModel(Qt::SortOrder order) { this->sort(0,order); Q_EMIT sortedAscendingChanged(); } #include "moc_filebrowserproxymodel.cpp" diff --git a/src/models/filebrowserproxymodel.h b/src/models/filebrowserproxymodel.h index 5f1232a3..2750b943 100644 --- a/src/models/filebrowserproxymodel.h +++ b/src/models/filebrowserproxymodel.h @@ -1,128 +1,133 @@ /* * Copyright 2016-2018 Matthieu Gallien * Copyright 2018 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef FILEBROWSERPROXYMODEL_H #define FILEBROWSERPROXYMODEL_H #include "elisaLib_export.h" #include "filebrowsermodel.h" #include "musicaudiotrack.h" #include "filescanner.h" #include "elisautils.h" #include #include #include #include #include class ELISALIB_EXPORT FileBrowserProxyModel : public KDirSortFilterProxyModel { Q_OBJECT Q_PROPERTY(QString filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged) Q_PROPERTY(bool canGoBack READ canGoBack NOTIFY canGoBackChanged) Q_PROPERTY(QString url READ url NOTIFY urlChanged) Q_PROPERTY(bool sortedAscending READ sortedAscending NOTIFY sortedAscendingChanged) public: explicit FileBrowserProxyModel(QObject *parent = nullptr); ~FileBrowserProxyModel() override; QString filterText() const; QString url() const; bool canGoBack() const; Q_INVOKABLE MusicAudioTrack loadMetaDataFromUrl(const QUrl &url); bool sortedAscending() const; public Q_SLOTS: void enqueueToPlayList(); void replaceAndPlayOfPlayList(); void setFilterText(const QString &filterText); void openParentFolder(); void openFolder(const QString &folder, bool isDisplayRoot = false); void sortModel(Qt::SortOrder order); + void replaceAndPlayOfUrl(const QUrl &fileUrl); + Q_SIGNALS: void filesToEnqueue(QList newFiles, ElisaUtils::PlayListEnqueueMode enqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay); void urlChanged(); void canGoBackChanged(); void filterTextChanged(const QString &filterText); void sortedAscendingChanged(); -protected: + void replaceAndPlayFileByUrl(const QUrl &fileUrl); + void loadPlayListFromUrl(const QUrl &playListUrl); + +protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; private: QString parentFolder() const; QString mTopFolder; FileScanner mFileScanner; QMimeDatabase mMimeDb; QString mFilterText; QRegularExpression mFilterExpression; QReadWriteLock mDataLock; QThreadPool mThreadPool; std::unique_ptr mFileModel; }; #endif // FILEBROWSERPROXYMODEL_H diff --git a/src/qml/FileBrowserDelegate.qml b/src/qml/FileBrowserDelegate.qml index 4913ce8f..5d976530 100644 --- a/src/qml/FileBrowserDelegate.qml +++ b/src/qml/FileBrowserDelegate.qml @@ -1,281 +1,284 @@ /* * Copyright 2016-2018 Matthieu Gallien * Copyright 2018 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Controls 1.4 as Controls1 import QtQuick.Layouts 1.2 import org.kde.elisa 1.0 FocusScope { id: fileDelegate property var fileName property var fileUrl property var imageUrl property var contentModel property bool isDirectory + property bool isPlayList signal enqueue(var data) signal replaceAndPlay(var data) + signal loadPlayList(var data) signal open(var data) signal selected() Controls1.Action { id: replaceAndPlayAction text: i18nc("Clear play list and enqueue current track", "Play Now and Replace Play List") iconName: "media-playback-start" onTriggered: replaceAndPlay(fileUrl) } Controls1.Action { id: enqueueAction text: i18nc("Enqueue current track", "Enqueue") iconName: "media-track-add-amarok" onTriggered: enqueue(fileUrl) } Controls1.Action { id: openAction text: i18nc("Enqueue current track", "Enqueue") iconName: 'document-open-folder' onTriggered: open(fileUrl) } Controls1.Action { id: viewDetailsAction text: i18nc("Show track metadata", "View Details") iconName: "help-about" onTriggered: { if (metadataLoader.active === false) { metadataLoader.active = true metadataLoader.item.trackDataHelper.trackData = contentModel.loadMetaDataFromUrl(fileUrl) } else { metadataLoader.item.close(); metadataLoader.active = false } } } Loader { id: metadataLoader active: false onLoaded: item.show() sourceComponent: MediaTrackMetadataView { trackDataHelper: TrackDataHelper { id: dataHelper } onRejected: metadataLoader.active = false; } } - Keys.onReturnPressed: isDirectory ? fileDelegate.open(fileUrl) : fileDelegate.enqueue(fileUrl) - Keys.onEnterPressed: isDirectory ? fileDelegate.open(fileUrl) : fileDelegate.enqueue(fileUrl) + Keys.onReturnPressed: fileDelegate.enqueue(fileUrl) + Keys.onEnterPressed: fileDelegate.enqueue(fileUrl) ColumnLayout { anchors.fill: parent spacing: 0 MouseArea { id: hoverArea hoverEnabled: true acceptedButtons: Qt.LeftButton Layout.preferredHeight: fileDelegate.width * 0.85 + elisaTheme.layoutVerticalMargin * 0.5 + mainLabelSize.height Layout.fillWidth: true onClicked: fileDelegate.selected() - onDoubleClicked: isDirectory ? fileDelegate.open(fileUrl) : fileDelegate.enqueue(fileUrl) + onDoubleClicked: fileDelegate.enqueue(fileUrl) TextMetrics { id: mainLabelSize font: mainLabel.font text: mainLabel.text } ColumnLayout { id: mainData spacing: 0 anchors.fill: parent Item { Layout.preferredHeight: fileDelegate.width * 0.85 Layout.preferredWidth: fileDelegate.width * 0.85 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Loader { id: hoverLoader active: false anchors.centerIn: parent z: 1 opacity: 0 sourceComponent: Row { Controls1.ToolButton { id: detailsButton Layout.preferredHeight: elisaTheme.delegateHeight Layout.preferredWidth: elisaTheme.delegateHeight action: viewDetailsAction - visible: !isDirectory + visible: !isDirectory && !isPlayList } Controls1.ToolButton { id: enqueueOpenButton Layout.preferredHeight: elisaTheme.delegateHeight Layout.preferredWidth: elisaTheme.delegateHeight action: isDirectory ? openAction : enqueueAction + visible: !isPlayList } Controls1.ToolButton { id: replaceAndPlayButton Layout.preferredHeight: elisaTheme.delegateHeight Layout.preferredWidth: elisaTheme.delegateHeight scale: LayoutMirroring.enabled ? -1 : 1 action: replaceAndPlayAction visible: !isDirectory } } } Image { id: icon anchors.fill: parent sourceSize.width: parent.width sourceSize.height: parent.height fillMode: Image.PreserveAspectFit smooth: true source: imageUrl asynchronous: true } } LabelWithToolTip { id: mainLabel font.weight: Font.Bold color: myPalette.text horizontalAlignment: Text.AlignLeft Layout.topMargin: elisaTheme.layoutVerticalMargin * 0.5 Layout.preferredWidth: fileDelegate.width * 0.85 Layout.maximumHeight: mainLabelSize.height * 2 Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom text: fileName wrapMode: Label.Wrap elide: Text.ElideRight } Item { Layout.fillHeight: true } } } Item { Layout.fillHeight: true } } states: [ State { name: 'notSelected' when: !fileDelegate.activeFocus && !hoverArea.containsMouse PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } PropertyChanges { target: icon opacity: 1 } }, State { name: 'hoveredOrSelected' when: fileDelegate.activeFocus || hoverArea.containsMouse PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } PropertyChanges { target: icon opacity: 0.2 } } ] transitions: [ Transition { to: 'hoveredOrSelected' SequentialAnimation { PropertyAction { properties: "active" } NumberAnimation { properties: "opacity" easing.type: Easing.InOutQuad duration: 100 } } }, Transition { to: 'notSelected' SequentialAnimation { NumberAnimation { properties: "opacity" easing.type: Easing.InOutQuad duration: 100 } PropertyAction { properties: "active" } } } ] } diff --git a/src/qml/FileBrowserView.qml b/src/qml/FileBrowserView.qml index b1d94542..06ac3f4c 100644 --- a/src/qml/FileBrowserView.qml +++ b/src/qml/FileBrowserView.qml @@ -1,158 +1,159 @@ /* * Copyright 2016-2018 Matthieu Gallien * Copyright 2018 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQml.Models 2.2 import QtQuick.Layouts 1.2 import org.kde.elisa 1.0 FocusScope { id: fileView property bool isSubPage: false property alias contentModel: contentDirectoryView.model property alias expandedFilterView: navigationBar.expandedFilterView function goBack() { contentModel.openParentFolder() } function loadFolderAndClear(data) { contentModel.openFolder(data) navigationBar.filterText = "" } SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } MouseArea { anchors.fill: parent hoverEnabled: false acceptedButtons: Qt.BackButton onClicked: contentModel.openParentFolder() } ColumnLayout { anchors.fill: parent spacing: 0 NavigationActionBar { id: navigationBar mainTitle: i18nc("Title of the file browser view", "Files") image: elisaTheme.folderIcon secondaryTitle: contentModel.url enableGoBack: contentModel.canGoBack sortOrder: contentModel.sortedAscending showRating: false height: elisaTheme.navigationBarHeight Layout.preferredHeight: height Layout.minimumHeight: height Layout.maximumHeight: height Layout.fillWidth: true Binding { target: contentModel property: 'filterText' value: navigationBar.filterText } onEnqueue: contentModel.enqueueToPlayList() onReplaceAndPlay: contentModel.replaceAndPlayOfPlayList() onGoBack: contentModel.openParentFolder() onSort: contentModel.sortModel(order) } Rectangle { color: myPalette.base Layout.fillHeight: true Layout.fillWidth: true clip: true GridView { id: contentDirectoryView anchors.topMargin: 20 anchors.fill: parent focus: true ScrollBar.vertical: ScrollBar { id: scrollBar } boundsBehavior: Flickable.StopAtBounds ScrollHelper { id: scrollHelper flickable: contentDirectoryView anchors.fill: contentDirectoryView } add: Transition { PropertyAnimation { property: "opacity" from: 0 to: 1 duration: 100 } } remove: Transition { PropertyAnimation { property: "opacity" from: 0 to: 1 duration: 100 } } cellWidth: elisaTheme.gridDelegateWidth cellHeight:elisaTheme.gridDelegateHeight delegate: FileBrowserDelegate { width: contentDirectoryView.cellWidth height: contentDirectoryView.cellHeight focus: true isDirectory: model.directory + isPlayList: model.isPlaylist fileName: model.name fileUrl: model.containerData imageUrl: model.imageUrl contentModel: fileView.contentModel onEnqueue: elisa.mediaPlayList.enqueue(data) - onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) + onReplaceAndPlay: contentModel.replaceAndPlayOfUrl(data) onSelected: { forceActiveFocus() contentDirectoryView.currentIndex = model.index } onOpen: loadFolderAndClear(data) } } } } }