diff --git a/src/elisa_core.kcfg b/src/elisa_core.kcfg index 08b8f2d2..6502b3e6 100644 --- a/src/elisa_core.kcfg +++ b/src/elisa_core.kcfg @@ -1,17 +1,25 @@ + + true + + + + + false + diff --git a/src/elisaapplication.cpp b/src/elisaapplication.cpp index f5799021..8256ae27 100644 --- a/src/elisaapplication.cpp +++ b/src/elisaapplication.cpp @@ -1,553 +1,561 @@ /* * 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 "mediaplaylist.h" #include "mediaplaylistproxymodel.h" #include "audiowrapper.h" #include "manageaudioplayer.h" #include "managemediaplayercontrol.h" #include "manageheaderbar.h" #include "databaseinterface.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 #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(); mConfigFileWatcher.addPath(configurationFileName); } #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND KActionCollection mCollection; #endif ElisaUtils::EntryDataList mArguments; std::unique_ptr mMusicManager; std::unique_ptr mMediaPlayList; std::unique_ptr mMediaPlayListProxyModel; std::unique_ptr mAudioWrapper; std::unique_ptr mAudioControl; std::unique_ptr mPlayerControl; std::unique_ptr mManageHeaderBar; QQmlApplicationEngine *mEngine = nullptr; QFileSystemWatcher mConfigFileWatcher; }; ElisaApplication::ElisaApplication(QObject *parent) : QObject(parent), d(std::make_unique(this)) { connect(Elisa::ElisaConfiguration::self(), &Elisa::ElisaConfiguration::configChanged, this, &ElisaApplication::configChanged); connect(&d->mConfigFileWatcher, &QFileSystemWatcher::fileChanged, this, &ElisaApplication::configChanged); } ElisaApplication::~ElisaApplication() = default; void ElisaApplication::setupActions(const QString &actionName) { #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND if (actionName == QLatin1String("file_quit")) { auto quitAction = KStandardAction::quit(QCoreApplication::instance(), &QCoreApplication::quit, &d->mCollection); d->mCollection.addAction(actionName, quitAction); } if (actionName == QLatin1String("help_contents") && KAuthorized::authorizeAction(actionName)) { auto handBookAction = KStandardAction::helpContents(this, &ElisaApplication::appHelpActivated, &d->mCollection); d->mCollection.addAction(handBookAction->objectName(), handBookAction); } if (actionName == QLatin1String("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 == QLatin1String("help_about_app") && KAuthorized::authorizeAction(actionName)) { auto aboutAppAction = KStandardAction::aboutApp(this, &ElisaApplication::aboutApplication, this); d->mCollection.addAction(aboutAppAction->objectName(), aboutAppAction); } if (actionName == QLatin1String("options_configure") && KAuthorized::authorizeAction(actionName)) { auto preferencesAction = KStandardAction::preferences(this, &ElisaApplication::configureElisa, this); d->mCollection.addAction(preferencesAction->objectName(), preferencesAction); } if (actionName == QLatin1String("options_configure_keybinding") && KAuthorized::authorizeAction(actionName)) { auto keyBindingsAction = KStandardAction::keyBindings(this, &ElisaApplication::configureShortcuts, this); d->mCollection.addAction(keyBindingsAction->objectName(), keyBindingsAction); } if (actionName == QLatin1String("go_back") && KAuthorized::authorizeAction(actionName)) { auto goBackAction = KStandardAction::back(this, &ElisaApplication::goBack, this); d->mCollection.addAction(goBackAction->objectName(), goBackAction); } if (actionName == QLatin1String("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 == QLatin1String("Seek") && KAuthorized::authorizeAction(actionName)) { auto seekAction = d->mCollection.addAction(actionName, this, &ElisaApplication::seek); d->mCollection.setDefaultShortcut(seekAction, QKeySequence(Qt::SHIFT + Qt::Key_Right)); } if (actionName == QLatin1String("Scrub") && KAuthorized::authorizeAction(actionName)) { auto scrubAction = d->mCollection.addAction(actionName, this, &ElisaApplication::scrub); d->mCollection.setDefaultShortcut(scrubAction, QKeySequence(Qt::SHIFT + Qt::Key_Left)); } if (actionName == QLatin1String("Play-Pause") && KAuthorized::authorizeAction(actionName)) { auto playPauseAction = d->mCollection.addAction(actionName, this, &ElisaApplication::playPause); d->mCollection.setDefaultShortcut(playPauseAction, QKeySequence(Qt::Key_Space)); } if (actionName == QLatin1String("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 ElisaUtils::EntryDataList &newArguments) { if (d->mArguments == newArguments) { return; } d->mArguments = checkFileListAndMakeAbsolute(newArguments, QDir::currentPath()); Q_EMIT argumentsChanged(); if (!d->mArguments.isEmpty()) { Q_EMIT enqueue(d->mArguments, ElisaUtils::FileName, ElisaUtils::PlayListEnqueueMode::AppendPlayList, ElisaUtils::PlayListEnqueueTriggerPlay::TriggerPlay); } } void ElisaApplication::activateActionRequested(const QString &actionName, const QVariant ¶meter) { Q_UNUSED(actionName) Q_UNUSED(parameter) } void ElisaApplication::activateRequested(const QStringList &arguments, const QString &workingDirectory) { if (arguments.size() > 1) { auto realArguments = ElisaUtils::EntryDataList{}; bool isFirst = true; for (const auto &oneArgument : arguments) { if (isFirst) { isFirst = false; continue; } realArguments.push_back(ElisaUtils::EntryData{{}, {}, QUrl(oneArgument)}); } Q_EMIT enqueue(checkFileListAndMakeAbsolute(realArguments, workingDirectory), ElisaUtils::FileName, ElisaUtils::PlayListEnqueueMode::AppendPlayList, ElisaUtils::PlayListEnqueueTriggerPlay::TriggerPlay); } } 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 (!d->mEngine) { return; } d->mEngine->load(QUrl(QStringLiteral("qrc:/qml/ElisaConfigurationDialog.qml"))); } void ElisaApplication::goBack() {} void ElisaApplication::find() {} void ElisaApplication::togglePlaylist() {} void ElisaApplication::seek() {} void ElisaApplication::scrub() {} void ElisaApplication::playPause() {} void ElisaApplication::configChanged() { auto currentConfiguration = Elisa::ElisaConfiguration::self(); d->mConfigFileWatcher.addPath(currentConfiguration->config()->name()); currentConfiguration->load(); currentConfiguration->read(); Q_EMIT showProgressOnTaskBarChanged(); + Q_EMIT showSystemTrayIconChanged(); } ElisaUtils::EntryDataList ElisaApplication::checkFileListAndMakeAbsolute(const ElisaUtils::EntryDataList &filesList, const QString &workingDirectory) const { auto filesToOpen = ElisaUtils::EntryDataList{}; for (const auto &oneFile : filesList) { if (std::get<2>(oneFile).scheme().isEmpty() || std::get<2>(oneFile).isLocalFile()) { auto newFile = QFileInfo(std::get<2>(oneFile).toLocalFile()); if (std::get<2>(oneFile).scheme().isEmpty()) { newFile = QFileInfo(std::get<2>(oneFile).toString()); } if (newFile.isRelative()) { if (std::get<2>(oneFile).scheme().isEmpty()) { newFile.setFile(workingDirectory, std::get<2>(oneFile).toString()); } else { newFile.setFile(workingDirectory, std::get<2>(oneFile).toLocalFile()); } } if (newFile.exists()) { filesToOpen.push_back(ElisaUtils::EntryData{{}, {}, QUrl::fromLocalFile(newFile.canonicalFilePath())}); } } else { filesToOpen.push_back(ElisaUtils::EntryData{{}, {}, std::get<2>(oneFile)}); } } return filesToOpen; } void ElisaApplication::initialize() { initializeModels(); initializePlayer(); Q_EMIT initializationDone(); } void ElisaApplication::setQmlEngine(QQmlApplicationEngine *engine) { d->mEngine = engine; } void ElisaApplication::initializeModels() { d->mMusicManager = std::make_unique(); Q_EMIT musicManagerChanged(); d->mMediaPlayList = std::make_unique(); d->mMusicManager->subscribeForTracks(d->mMediaPlayList.get()); Q_EMIT mediaPlayListChanged(); d->mMediaPlayListProxyModel = std::make_unique(); d->mMediaPlayListProxyModel->setPlayListModel(d->mMediaPlayList.get()); Q_EMIT mediaPlayListProxyModelChanged(); d->mMusicManager->setElisaApplication(this); QObject::connect(this, &ElisaApplication::enqueue, d->mMediaPlayListProxyModel.get(), static_cast(&MediaPlayListProxyModel::enqueue)); } 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->mMediaPlayListProxyModel.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::saveUndoPositionInAudioWrapper, d->mAudioWrapper.get(), &AudioWrapper::saveUndoPosition); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::restoreUndoPositionInAudioWrapper, d->mAudioWrapper.get(), &AudioWrapper::restoreUndoPosition); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::skipNextTrack, d->mMediaPlayListProxyModel.get(), &MediaPlayListProxyModel::skipNextTrack); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::sourceInError, d->mMediaPlayListProxyModel.get(), &MediaPlayListProxyModel::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->mAudioControl.get(), &ManageAudioPlayer::startedPlayingTrack, d->mMusicManager->viewDatabase(), &DatabaseInterface::trackHasStartedPlaying); QObject::connect(d->mAudioControl.get(), &ManageAudioPlayer::updateData, d->mMediaPlayList.get(), &MediaPlayList::setData); QObject::connect(d->mMediaPlayListProxyModel.get(), &MediaPlayListProxyModel::ensurePlay, d->mAudioControl.get(), &ManageAudioPlayer::ensurePlay); QObject::connect(d->mMediaPlayListProxyModel.get(), &MediaPlayListProxyModel::playListFinished, d->mAudioControl.get(), &ManageAudioPlayer::playListFinished); QObject::connect(d->mMediaPlayListProxyModel.get(), &MediaPlayListProxyModel::currentTrackChanged, d->mAudioControl.get(), &ManageAudioPlayer::setCurrentTrack); QObject::connect(d->mMediaPlayListProxyModel.get(), &MediaPlayListProxyModel::clearPlayListPlayer, d->mAudioControl.get(), &ManageAudioPlayer::saveForUndoClearPlaylist); QObject::connect(d->mMediaPlayListProxyModel.get(), &MediaPlayListProxyModel::undoClearPlayListPlayer, d->mAudioControl.get(), &ManageAudioPlayer::restoreForUndoClearPlaylist); 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); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::currentPlayingForRadiosChanged, d->mAudioControl.get(), &ManageAudioPlayer::setCurrentPlayingForRadios); QObject::connect(d->mMediaPlayListProxyModel.get(), &MediaPlayListProxyModel::currentTrackChanged, d->mPlayerControl.get(), &ManageMediaPlayerControl::setCurrentTrack); QObject::connect(d->mMediaPlayListProxyModel.get(), &MediaPlayListProxyModel::previousTrackChanged, d->mPlayerControl.get(), &ManageMediaPlayerControl::setPreviousTrack); QObject::connect(d->mMediaPlayListProxyModel.get(), &MediaPlayListProxyModel::nextTrackChanged, d->mPlayerControl.get(), &ManageMediaPlayerControl::setNextTrack); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::playing, d->mPlayerControl.get(), &ManageMediaPlayerControl::playerPlaying); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::paused, d->mPlayerControl.get(), &ManageMediaPlayerControl::playerPausedOrStopped); QObject::connect(d->mAudioWrapper.get(), &AudioWrapper::stopped, d->mPlayerControl.get(), &ManageMediaPlayerControl::playerPausedOrStopped); d->mManageHeaderBar->setTitleRole(MediaPlayList::TitleRole); 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->setTrackTypeRole(MediaPlayList::ElementTypeRole); d->mManageHeaderBar->setAlbumIdRole(MediaPlayList::AlbumIdRole); d->mManageHeaderBar->setIsValidRole(MediaPlayList::IsValidRole); QObject::connect(d->mMediaPlayListProxyModel.get(), &MediaPlayListProxyModel::currentTrackChanged, d->mManageHeaderBar.get(), &ManageHeaderBar::setCurrentTrack); QObject::connect(d->mMediaPlayListProxyModel.get(), &MediaPlayListProxyModel::currentTrackDataChanged, d->mManageHeaderBar.get(), &ManageHeaderBar::updateCurrentTrackData); if (!d->mArguments.isEmpty()) { Q_EMIT enqueue(d->mArguments, ElisaUtils::FileName, ElisaUtils::PlayListEnqueueMode::AppendPlayList, ElisaUtils::PlayListEnqueueTriggerPlay::TriggerPlay); } } 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(); } void ElisaApplication::installKeyEventFilter(QObject *object) { if(!object) { return; } object->installEventFilter(this); } bool ElisaApplication::eventFilter(QObject *object, QEvent *event) { Q_UNUSED(object); QKeyEvent *keyEvent = static_cast(event); auto playPauseAction = d->mCollection.action(tr("Play-Pause")); if (keyEvent->key() == Qt::Key_Space && playPauseAction->shortcut()[0] == Qt::Key_Space) { return true; } return false; } const ElisaUtils::EntryDataList &ElisaApplication::arguments() const { return d->mArguments; } MusicListenersManager *ElisaApplication::musicManager() const { return d->mMusicManager.get(); } MediaPlayList *ElisaApplication::mediaPlayList() const { return d->mMediaPlayList.get(); } MediaPlayListProxyModel *ElisaApplication::mediaPlayListProxyModel() const { return d->mMediaPlayListProxyModel.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(); } bool ElisaApplication::showProgressOnTaskBar() const { auto currentConfiguration = Elisa::ElisaConfiguration::self(); return currentConfiguration->showProgressOnTaskBar(); } +bool ElisaApplication::showSystemTrayIcon() const +{ + auto currentConfiguration = Elisa::ElisaConfiguration::self(); + + return currentConfiguration->showSystemTrayIcon(); +} + #include "moc_elisaapplication.cpp" diff --git a/src/elisaapplication.h b/src/elisaapplication.h index 41667e5c..6d02824a 100644 --- a/src/elisaapplication.h +++ b/src/elisaapplication.h @@ -1,203 +1,211 @@ /* * Copyright 2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef ELISAAPPLICATION_H #define ELISAAPPLICATION_H #include "elisaLib_export.h" #include "config-upnp-qt.h" #include "elisautils.h" #include #include #include class QIcon; class QAction; class MusicListenersManager; class MediaPlayList; class MediaPlayListProxyModel; class AudioWrapper; class ManageAudioPlayer; class ManageMediaPlayerControl; class ManageHeaderBar; class QQmlApplicationEngine; class ElisaApplicationPrivate; class ELISALIB_EXPORT ElisaApplication : public QObject { Q_OBJECT Q_PROPERTY(ElisaUtils::EntryDataList arguments READ arguments WRITE setArguments NOTIFY argumentsChanged) Q_PROPERTY(MusicListenersManager *musicManager READ musicManager NOTIFY musicManagerChanged) Q_PROPERTY(MediaPlayList *mediaPlayList READ mediaPlayList NOTIFY mediaPlayListChanged) Q_PROPERTY(MediaPlayListProxyModel *mediaPlayListProxyModel READ mediaPlayListProxyModel NOTIFY mediaPlayListProxyModelChanged) Q_PROPERTY(AudioWrapper *audioPlayer READ audioPlayer NOTIFY audioPlayerChanged) Q_PROPERTY(ManageAudioPlayer *audioControl READ audioControl NOTIFY audioControlChanged) Q_PROPERTY(ManageMediaPlayerControl *playerControl READ playerControl NOTIFY playerControlChanged) Q_PROPERTY(ManageHeaderBar *manageHeaderBar READ manageHeaderBar NOTIFY manageHeaderBarChanged) Q_PROPERTY(bool showProgressOnTaskBar READ showProgressOnTaskBar NOTIFY showProgressOnTaskBarChanged) + Q_PROPERTY(bool showSystemTrayIcon + READ showSystemTrayIcon + NOTIFY showSystemTrayIconChanged) + public: explicit ElisaApplication(QObject *parent = nullptr); ~ElisaApplication() override; Q_INVOKABLE QAction* action(const QString& name); Q_INVOKABLE QString iconName(const QIcon& icon); Q_INVOKABLE void installKeyEventFilter(QObject *object); bool eventFilter(QObject *object, QEvent *event) override; const ElisaUtils::EntryDataList &arguments() const; MusicListenersManager *musicManager() const; MediaPlayList *mediaPlayList() const; MediaPlayListProxyModel *mediaPlayListProxyModel() const; AudioWrapper *audioPlayer() const; ManageAudioPlayer *audioControl() const; ManageMediaPlayerControl *playerControl() const; ManageHeaderBar *manageHeaderBar() const; bool showProgressOnTaskBar() const; + bool showSystemTrayIcon() const; + Q_SIGNALS: void argumentsChanged(); void musicManagerChanged(); void mediaPlayListChanged(); void mediaPlayListProxyModelChanged(); void audioPlayerChanged(); void audioControlChanged(); void playerControlChanged(); void manageHeaderBarChanged(); void enqueue(const ElisaUtils::EntryDataList &newEntries, ElisaUtils::PlayListEntryType databaseIdType, ElisaUtils::PlayListEnqueueMode enqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay); void initializationDone(); void showProgressOnTaskBarChanged(); + void showSystemTrayIconChanged(); + public Q_SLOTS: void appHelpActivated(); void aboutApplication(); void reportBug(); void configureShortcuts(); void configureElisa(); void setArguments(const ElisaUtils::EntryDataList &newArguments); void activateActionRequested(const QString &actionName, const QVariant ¶meter); void activateRequested(const QStringList &arguments, const QString &workingDirectory); void openRequested(const QList< QUrl > &uris); void initialize(); public: void setQmlEngine(QQmlApplicationEngine *engine); private Q_SLOTS: void goBack(); void find(); void togglePlaylist(); void seek(); void scrub(); void playPause(); void configChanged(); private: void initializeModels(); void initializePlayer(); void setupActions(const QString &actionName); ElisaUtils::EntryDataList checkFileListAndMakeAbsolute(const ElisaUtils::EntryDataList &filesList, const QString &workingDirectory) const; std::unique_ptr d; }; #endif // ELISAAPPLICATION_H diff --git a/src/localFileConfiguration/elisaconfigurationdialog.cpp b/src/localFileConfiguration/elisaconfigurationdialog.cpp index f7e2517f..c8229a54 100644 --- a/src/localFileConfiguration/elisaconfigurationdialog.cpp +++ b/src/localFileConfiguration/elisaconfigurationdialog.cpp @@ -1,133 +1,147 @@ /* * Copyright 2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "elisaconfigurationdialog.h" #include "elisa-version.h" #include "elisa_settings.h" #include #include ElisaConfigurationDialog::ElisaConfigurationDialog(QObject* parent) : QObject(parent) { connect(Elisa::ElisaConfiguration::self(), &Elisa::ElisaConfiguration::configChanged, this, &ElisaConfigurationDialog::configChanged); connect(&mConfigFileWatcher, &QFileSystemWatcher::fileChanged, this, &ElisaConfigurationDialog::configChanged); setRootPath(Elisa::ElisaConfiguration::rootPath()); setShowProgressInTaskBar(Elisa::ElisaConfiguration::showProgressOnTaskBar()); + setShowSystemTrayIcon(Elisa::ElisaConfiguration::showSystemTrayIcon()); setForceUsageOfFastFileSearch(Elisa::ElisaConfiguration::forceUsageOfFastFileSearch()); save(); mConfigFileWatcher.addPath(Elisa::ElisaConfiguration::self()->config()->name()); } ElisaConfigurationDialog::~ElisaConfigurationDialog() = default; QStringList ElisaConfigurationDialog::rootPath() const { return mRootPath; } void ElisaConfigurationDialog::setRootPath(const QStringList &rootPath) { if (mRootPath == rootPath && !mRootPath.isEmpty()) { return; } mRootPath.clear(); for (const auto &onePath : rootPath) { auto workPath = onePath; if (workPath.startsWith(QLatin1String("file:/"))) { auto urlPath = QUrl{workPath}; workPath = urlPath.toLocalFile(); } QFileInfo pathFileInfo(workPath); auto directoryPath = pathFileInfo.canonicalFilePath(); if (!directoryPath.isEmpty()) { mRootPath.push_back(directoryPath); } } if (mRootPath.isEmpty()) { auto systemMusicPaths = QStandardPaths::standardLocations(QStandardPaths::MusicLocation); for (const auto &musicPath : qAsConst(systemMusicPaths)) { mRootPath.push_back(musicPath); } } Q_EMIT rootPathChanged(mRootPath); setDirty(); } void ElisaConfigurationDialog::save() { Elisa::ElisaConfiguration::setRootPath(mRootPath); Elisa::ElisaConfiguration::setShowProgressOnTaskBar(mShowProgressInTaskBar); + Elisa::ElisaConfiguration::setShowSystemTrayIcon(mShowSystemTrayIcon); Elisa::ElisaConfiguration::setForceUsageOfFastFileSearch(mForceUsageOfFastFileSearch); Elisa::ElisaConfiguration::self()->save(); mIsDirty = false; Q_EMIT isDirtyChanged(); } void ElisaConfigurationDialog::setShowProgressInTaskBar(bool showProgressInTaskBar) { if (mShowProgressInTaskBar == showProgressInTaskBar) { return; } mShowProgressInTaskBar = showProgressInTaskBar; Q_EMIT showProgressInTaskBarChanged(); setDirty(); } +void ElisaConfigurationDialog::setShowSystemTrayIcon(bool showSystemTrayIcon) +{ + if (mShowSystemTrayIcon == showSystemTrayIcon) { + return; + } + + mShowSystemTrayIcon = showSystemTrayIcon; + Q_EMIT showSystemTrayIconChanged(); + + setDirty(); +} + void ElisaConfigurationDialog::setForceUsageOfFastFileSearch(bool forceUsageOfFastFileSearch) { if (mForceUsageOfFastFileSearch == forceUsageOfFastFileSearch) { return; } mForceUsageOfFastFileSearch = forceUsageOfFastFileSearch; Q_EMIT forceUsageOfFastFileSearchChanged(); setDirty(); } void ElisaConfigurationDialog::configChanged() { setRootPath(Elisa::ElisaConfiguration::rootPath()); } void ElisaConfigurationDialog::setDirty() { mIsDirty = true; Q_EMIT isDirtyChanged(); } #include "moc_elisaconfigurationdialog.cpp" diff --git a/src/localFileConfiguration/elisaconfigurationdialog.h b/src/localFileConfiguration/elisaconfigurationdialog.h index da17678b..b236db9b 100644 --- a/src/localFileConfiguration/elisaconfigurationdialog.h +++ b/src/localFileConfiguration/elisaconfigurationdialog.h @@ -1,111 +1,128 @@ /* * Copyright 2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #if !defined ELISACONFIGURATIONDIALOG_H_ #define ELISACONFIGURATIONDIALOG_H_ #include "elisaLib_export.h" #include #include class ELISALIB_EXPORT ElisaConfigurationDialog : public QObject { Q_OBJECT Q_PROPERTY(QStringList rootPath READ rootPath WRITE setRootPath NOTIFY rootPathChanged) Q_PROPERTY(bool forceUsageOfFastFileSearch READ forceUsageOfFastFileSearch WRITE setForceUsageOfFastFileSearch NOTIFY forceUsageOfFastFileSearchChanged) Q_PROPERTY(bool showProgressInTaskBar READ showProgressInTaskBar WRITE setShowProgressInTaskBar NOTIFY showProgressInTaskBarChanged) + Q_PROPERTY(bool showSystemTrayIcon + READ showSystemTrayIcon + WRITE setShowSystemTrayIcon + NOTIFY showSystemTrayIconChanged) + Q_PROPERTY(bool isDirty READ isDirty NOTIFY isDirtyChanged) public: explicit ElisaConfigurationDialog(QObject *parent = nullptr); ~ElisaConfigurationDialog() override; QStringList rootPath() const; bool isDirty() const { return mIsDirty; } bool showProgressInTaskBar() const { return mShowProgressInTaskBar; } + bool showSystemTrayIcon() const + { + return mShowSystemTrayIcon; + } + bool forceUsageOfFastFileSearch() const { return mForceUsageOfFastFileSearch; } Q_SIGNALS: void rootPathChanged(const QStringList &rootPath); void isDirtyChanged(); void showProgressInTaskBarChanged(); + void showSystemTrayIconChanged(); + void forceUsageOfFastFileSearchChanged(); public Q_SLOTS: void setRootPath(const QStringList &rootPath); void save(); void setShowProgressInTaskBar(bool showProgressInTaskBar); + void setShowSystemTrayIcon(bool showSystemTrayIcon); + void setForceUsageOfFastFileSearch(bool forceUsageOfFastFileSearch); private Q_SLOTS: void configChanged(); private: void setDirty(); QStringList mRootPath; QFileSystemWatcher mConfigFileWatcher; bool mIsDirty = false; bool mShowProgressInTaskBar = true; - bool mForceUsageOfFastFileSearch; + + bool mShowSystemTrayIcon = false; + + bool mForceUsageOfFastFileSearch = true; }; #endif diff --git a/src/musiclistenersmanager.cpp b/src/musiclistenersmanager.cpp index 9fa3cc88..6b61b13b 100644 --- a/src/musiclistenersmanager.cpp +++ b/src/musiclistenersmanager.cpp @@ -1,571 +1,572 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "musiclistenersmanager.h" #include "config-upnp-qt.h" #include "indexersManager.h" #if defined UPNPQT_FOUND && UPNPQT_FOUND #include "upnp/upnplistener.h" #endif #if defined KF5Baloo_FOUND && KF5Baloo_FOUND #include "baloo/baloolistener.h" #include "baloo/baloodetector.h" #endif #if defined Qt5AndroidExtras_FOUND && Qt5AndroidExtras_FOUND #include "android/androidmusiclistener.h" #endif #include "databaseinterface.h" #include "mediaplaylist.h" #include "file/filelistener.h" #include "file/localfilelisting.h" #include "trackslistener.h" #include "elisaapplication.h" #include "elisa_settings.h" #include "modeldataloader.h" #include #include #include #include #include #include #include #include class MusicListenersManagerPrivate { public: QThread mDatabaseThread; QThread mListenerThread; #if defined UPNPQT_FOUND && UPNPQT_FOUND UpnpListener mUpnpListener; #endif #if defined KF5Baloo_FOUND && KF5Baloo_FOUND BalooDetector mBalooDetector; BalooListener mBalooListener; #endif FileListener mFileListener; #if defined Qt5AndroidExtras_FOUND && Qt5AndroidExtras_FOUND std::unique_ptr mAndroidMusicListener; #endif DatabaseInterface mDatabaseInterface; std::unique_ptr mTracksListener; QFileSystemWatcher mConfigFileWatcher; QStringList mPreviousRootPathValue; ElisaApplication *mElisaApplication = nullptr; int mImportedTracksCount = 0; bool mIndexerBusy = false; bool mFileSystemIndexerActive = false; bool mBalooIndexerActive = false; bool mBalooIndexerAvailable = false; bool mAndroidIndexerActive = false; bool mAndroidIndexerAvailable = false; }; MusicListenersManager::MusicListenersManager(QObject *parent) : QObject(parent), d(std::make_unique()) { connect(&d->mDatabaseInterface, &DatabaseInterface::tracksAdded, this, &MusicListenersManager::increaseImportedTracksCount); #if defined KF5Baloo_FOUND && KF5Baloo_FOUND connect(&d->mBalooDetector, &BalooDetector::balooAvailabilityChanged, this, &MusicListenersManager::balooAvailabilityChanged); #endif connect(&d->mDatabaseInterface, &DatabaseInterface::requestsInitDone, this, &MusicListenersManager::databaseReady); connect(this, &MusicListenersManager::clearDatabase, &d->mDatabaseInterface, &DatabaseInterface::clearData); connect(&d->mDatabaseInterface, &DatabaseInterface::cleanedDatabase, this, &MusicListenersManager::cleanedDatabase); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &MusicListenersManager::applicationAboutToQuit); connect(&d->mConfigFileWatcher, &QFileSystemWatcher::fileChanged, this, &MusicListenersManager::configChanged); d->mListenerThread.start(); d->mDatabaseThread.start(); d->mDatabaseInterface.moveToThread(&d->mDatabaseThread); const auto &localDataPaths = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); auto databaseFileName = QString(); if (!localDataPaths.isEmpty()) { QDir myDataDirectory; myDataDirectory.mkpath(localDataPaths.first()); databaseFileName = localDataPaths.first() + QStringLiteral("/elisaDatabase.db"); } QMetaObject::invokeMethod(&d->mDatabaseInterface, "init", Qt::QueuedConnection, Q_ARG(QString, QStringLiteral("listeners")), Q_ARG(QString, databaseFileName)); qCInfo(orgKdeElisaIndexersManager) << "Local file system indexer is inactive"; qCInfo(orgKdeElisaIndexersManager) << "Baloo indexer is unavailable"; qCInfo(orgKdeElisaIndexersManager) << "Baloo indexer is inactive"; } MusicListenersManager::~MusicListenersManager() = default; DatabaseInterface *MusicListenersManager::viewDatabase() const { return &d->mDatabaseInterface; } void MusicListenersManager::subscribeForTracks(MediaPlayList *client) { createTracksListener(); connect(d->mTracksListener.get(), &TracksListener::trackHasChanged, client, &MediaPlayList::trackChanged); connect(d->mTracksListener.get(), &TracksListener::trackHasBeenRemoved, client, &MediaPlayList::trackRemoved); connect(d->mTracksListener.get(), &TracksListener::tracksListAdded, client, &MediaPlayList::tracksListAdded); connect(client, &MediaPlayList::newEntryInList, d->mTracksListener.get(), &TracksListener::newEntryInList); connect(client, &MediaPlayList::newUrlInList, d->mTracksListener.get(), &TracksListener::newUrlInList); connect(client, &MediaPlayList::newTrackByNameInList, d->mTracksListener.get(), &TracksListener::trackByNameInList); } int MusicListenersManager::importedTracksCount() const { return d->mImportedTracksCount; } ElisaApplication *MusicListenersManager::elisaApplication() const { return d->mElisaApplication; } TracksListener *MusicListenersManager::tracksListener() const { return d->mTracksListener.get(); } bool MusicListenersManager::indexerBusy() const { return d->mIndexerBusy; } bool MusicListenersManager::fileSystemIndexerActive() const { return d->mFileSystemIndexerActive; } bool MusicListenersManager::balooIndexerActive() const { return d->mBalooIndexerActive; } bool MusicListenersManager::balooIndexerAvailable() const { return d->mBalooIndexerAvailable; } bool MusicListenersManager::androidIndexerActive() const { return d->mAndroidIndexerActive; } bool MusicListenersManager::androidIndexerAvailable() const { return d->mAndroidIndexerAvailable; } auto MusicListenersManager::initializeRootPath() { auto initialRootPath = QStringList{}; auto systemMusicPaths = QStandardPaths::standardLocations(QStandardPaths::MusicLocation); for (const auto &musicPath : qAsConst(systemMusicPaths)) { initialRootPath.push_back(musicPath); } Elisa::ElisaConfiguration::setRootPath(initialRootPath); Elisa::ElisaConfiguration::setShowProgressOnTaskBar(true); + Elisa::ElisaConfiguration::setShowSystemTrayIcon(false); Elisa::ElisaConfiguration::setForceUsageOfFastFileSearch(true); Elisa::ElisaConfiguration::self()->save(); return initialRootPath; } void MusicListenersManager::databaseReady() { auto initialRootPath = Elisa::ElisaConfiguration::rootPath(); if (initialRootPath.isEmpty()) { initializeRootPath(); } d->mConfigFileWatcher.addPath(Elisa::ElisaConfiguration::self()->config()->name()); configChanged(); } void MusicListenersManager::applicationAboutToQuit() { d->mDatabaseInterface.applicationAboutToQuit(); Q_EMIT applicationIsTerminating(); d->mDatabaseThread.exit(); d->mDatabaseThread.wait(); d->mListenerThread.exit(); d->mListenerThread.wait(); } void MusicListenersManager::showConfiguration() { auto configureAction = d->mElisaApplication->action(QStringLiteral("options_configure")); configureAction->trigger(); } void MusicListenersManager::setElisaApplication(ElisaApplication *elisaApplication) { if (d->mElisaApplication == elisaApplication) { return; } d->mElisaApplication = elisaApplication; emit elisaApplicationChanged(); } void MusicListenersManager::playBackError(const QUrl &sourceInError, QMediaPlayer::Error playerError) { qCDebug(orgKdeElisaIndexersManager) << "MusicListenersManager::playBackError" << sourceInError; if (playerError == QMediaPlayer::ResourceError) { Q_EMIT removeTracksInError({sourceInError}); if (sourceInError.isLocalFile()) { Q_EMIT displayTrackError(sourceInError.toLocalFile()); } else { Q_EMIT displayTrackError(sourceInError.toString()); } } } void MusicListenersManager::deleteElementById(ElisaUtils::PlayListEntryType entryType, qulonglong databaseId) { switch(entryType) { case ElisaUtils::Radio: QMetaObject::invokeMethod(&d->mDatabaseInterface, "removeRadio", Qt::QueuedConnection, Q_ARG(qulonglong, databaseId)); break; case ElisaUtils::Album: case ElisaUtils::Artist: case ElisaUtils::Genre: case ElisaUtils::Lyricist: case ElisaUtils::Composer: case ElisaUtils::Track: case ElisaUtils::FileName: case ElisaUtils::Unknown: break; } } void MusicListenersManager::connectModel(ModelDataLoader *dataLoader) { dataLoader->moveToThread(&d->mDatabaseThread); } void MusicListenersManager::resetMusicData() { Q_EMIT clearDatabase(); } void MusicListenersManager::configChanged() { auto currentConfiguration = Elisa::ElisaConfiguration::self(); d->mConfigFileWatcher.addPath(currentConfiguration->config()->name()); currentConfiguration->load(); currentConfiguration->read(); bool configurationHasChanged = false; #if defined KF5Baloo_FOUND && KF5Baloo_FOUND if (d->mBalooIndexerAvailable && d->mBalooIndexerActive && d->mBalooListener.canHandleRootPaths() && !currentConfiguration->forceUsageOfFastFileSearch()) { configurationHasChanged = true; } else if (d->mBalooIndexerAvailable && !d->mBalooIndexerActive && d->mBalooListener.canHandleRootPaths() && currentConfiguration->forceUsageOfFastFileSearch()) { configurationHasChanged = true; } #endif auto inputRootPath = currentConfiguration->rootPath(); configurationHasChanged = configurationHasChanged || (d->mPreviousRootPathValue != inputRootPath); if (configurationHasChanged) { d->mPreviousRootPathValue = inputRootPath; } else { qCDebug(orgKdeElisaIndexersManager()) << "music paths configuration and scanning has not changed"; return; } //resolve symlinks QStringList allRootPaths; for (const auto &onePath : inputRootPath) { auto workPath = onePath; if (workPath.startsWith(QLatin1String("file:/"))) { auto urlPath = QUrl{workPath}; workPath = urlPath.toLocalFile(); } QFileInfo pathFileInfo(workPath); auto directoryPath = pathFileInfo.canonicalFilePath(); if (!directoryPath.isEmpty()) { if (directoryPath.rightRef(1) != QLatin1Char('/')) { directoryPath.append(QLatin1Char('/')); } allRootPaths.push_back(directoryPath); } } if (allRootPaths.isEmpty()) { allRootPaths = initializeRootPath(); } d->mFileListener.setAllRootPaths(allRootPaths); #if defined KF5Baloo_FOUND && KF5Baloo_FOUND d->mBalooListener.setAllRootPaths(allRootPaths); #endif if (!d->mBalooIndexerActive && !d->mFileSystemIndexerActive) { testBalooIndexerAvailability(); } #if defined KF5Baloo_FOUND && KF5Baloo_FOUND if (d->mBalooIndexerAvailable && !d->mBalooIndexerActive && d->mBalooListener.canHandleRootPaths() && currentConfiguration->forceUsageOfFastFileSearch()) { qCDebug(orgKdeElisaIndexersManager()) << "trigger start of baloo file indexer"; QMetaObject::invokeMethod(d->mFileListener.fileListing(), "stop", Qt::BlockingQueuedConnection); d->mFileSystemIndexerActive = false; startBalooIndexing(); } else if ((!d->mFileSystemIndexerActive && d->mBalooIndexerActive && !d->mBalooListener.canHandleRootPaths()) || !currentConfiguration->forceUsageOfFastFileSearch()) { if (d->mBalooIndexerActive) { qCDebug(orgKdeElisaIndexersManager()) << "trigger stop of baloo file indexer"; QMetaObject::invokeMethod(d->mBalooListener.fileListing(), "stop", Qt::BlockingQueuedConnection); } d->mBalooIndexerActive = false; startLocalFileSystemIndexing(); } #endif if (d->mBalooIndexerActive) { qCInfo(orgKdeElisaIndexersManager()) << "trigger init of baloo file indexer"; #if defined KF5Baloo_FOUND && KF5Baloo_FOUND QMetaObject::invokeMethod(d->mBalooListener.fileListing(), "init", Qt::QueuedConnection); #endif } else if (d->mFileSystemIndexerActive) { qCInfo(orgKdeElisaIndexersManager()) << "trigger init of local file indexer"; QMetaObject::invokeMethod(d->mFileListener.fileListing(), "init", Qt::QueuedConnection); } #if defined UPNPQT_FOUND && UPNPQT_FOUND d->mUpnpListener.setDatabaseInterface(&d->mDatabaseInterface); d->mUpnpListener.moveToThread(&d->mDatabaseThread); connect(this, &MusicListenersManager::applicationIsTerminating, &d->mUpnpListener, &UpnpListener::applicationAboutToQuit, Qt::DirectConnection); #endif #if defined Qt5AndroidExtras_FOUND && Qt5AndroidExtras_FOUND if (!d->mAndroidMusicListener) { d->mAndroidMusicListener = std::make_unique(); d->mAndroidMusicListener->moveToThread(&d->mListenerThread); d->mAndroidMusicListener->setDatabaseInterface(&d->mDatabaseInterface); connect(this, &MusicListenersManager::applicationIsTerminating, d->mAndroidMusicListener.get(), &AndroidMusicListener::applicationAboutToQuit, Qt::DirectConnection); connect(d->mAndroidMusicListener.get(), &AndroidMusicListener::indexingStarted, this, &MusicListenersManager::monitorStartingListeners); connect(d->mAndroidMusicListener.get(), &AndroidMusicListener::indexingFinished, this, &MusicListenersManager::monitorEndingListeners); } #endif } void MusicListenersManager::increaseImportedTracksCount(const DataTypes::ListTrackDataType &allTracks) { d->mImportedTracksCount += allTracks.size(); Q_EMIT importedTracksCountChanged(); } void MusicListenersManager::decreaseImportedTracksCount() { --d->mImportedTracksCount; Q_EMIT importedTracksCountChanged(); } void MusicListenersManager::monitorStartingListeners() { d->mIndexerBusy = true; Q_EMIT indexerBusyChanged(); } void MusicListenersManager::monitorEndingListeners() { d->mIndexerBusy = false; Q_EMIT indexerBusyChanged(); } void MusicListenersManager::cleanedDatabase() { d->mImportedTracksCount = 0; Q_EMIT importedTracksCountChanged(); Q_EMIT clearedDatabase(); } void MusicListenersManager::balooAvailabilityChanged() { #if defined KF5Baloo_FOUND && KF5Baloo_FOUND if (!d->mBalooDetector.balooAvailability() || !d->mBalooListener.canHandleRootPaths()) { if (d->mBalooDetector.balooAvailability()) { qCInfo(orgKdeElisaIndexersManager) << "Baloo indexer is available"; d->mBalooIndexerAvailable = true; } #else if (true) { #endif #if defined KF5Baloo_FOUND && KF5Baloo_FOUND if (!d->mBalooListener.canHandleRootPaths() && d->mBalooDetector.balooAvailability()) { qCInfo(orgKdeElisaIndexersManager()) << "Baloo cannot handle all configured paths: falling back to plain filex indexer"; } #endif if (!d->mFileSystemIndexerActive) { startLocalFileSystemIndexing(); } return; } qCInfo(orgKdeElisaIndexersManager) << "Baloo indexer is available"; d->mBalooIndexerAvailable = true; Q_EMIT balooIndexerAvailableChanged(); startBalooIndexing(); } void MusicListenersManager::testBalooIndexerAvailability() { #if defined KF5Baloo_FOUND && KF5Baloo_FOUND d->mBalooDetector.checkBalooAvailability(); #else qCInfo(orgKdeElisaIndexersManager) << "Baloo indexer is unavailable"; d->mBalooIndexerAvailable = false; Q_EMIT balooIndexerAvailableChanged(); qCInfo(orgKdeElisaIndexersManager) << "Baloo indexer is inactive"; d->mBalooIndexerActive = false; Q_EMIT balooIndexerActiveChanged(); startLocalFileSystemIndexing(); #endif } void MusicListenersManager::startLocalFileSystemIndexing() { if (d->mFileSystemIndexerActive) { return; } d->mFileListener.setDatabaseInterface(&d->mDatabaseInterface); d->mFileListener.moveToThread(&d->mListenerThread); connect(this, &MusicListenersManager::applicationIsTerminating, &d->mFileListener, &FileListener::applicationAboutToQuit, Qt::DirectConnection); connect(&d->mFileListener, &FileListener::indexingStarted, this, &MusicListenersManager::monitorStartingListeners); connect(&d->mFileListener, &FileListener::indexingFinished, this, &MusicListenersManager::monitorEndingListeners); qCInfo(orgKdeElisaIndexersManager) << "Local file system indexer is active"; d->mFileSystemIndexerActive = true; Q_EMIT fileSystemIndexerActiveChanged(); } void MusicListenersManager::startBalooIndexing() { #if defined KF5Baloo_FOUND && KF5Baloo_FOUND d->mBalooListener.moveToThread(&d->mListenerThread); d->mBalooListener.setDatabaseInterface(&d->mDatabaseInterface); connect(this, &MusicListenersManager::applicationIsTerminating, &d->mBalooListener, &BalooListener::applicationAboutToQuit, Qt::DirectConnection); connect(&d->mBalooListener, &BalooListener::indexingStarted, this, &MusicListenersManager::monitorStartingListeners); connect(&d->mBalooListener, &BalooListener::indexingFinished, this, &MusicListenersManager::monitorEndingListeners); connect(&d->mBalooListener, &BalooListener::clearDatabase, &d->mDatabaseInterface, &DatabaseInterface::clearData); qCInfo(orgKdeElisaIndexersManager) << "Baloo indexer is active"; d->mBalooIndexerActive = true; Q_EMIT balooIndexerActiveChanged(); #endif } void MusicListenersManager::createTracksListener() { if (!d->mTracksListener) { d->mTracksListener = std::make_unique(&d->mDatabaseInterface); d->mTracksListener->moveToThread(&d->mDatabaseThread); connect(this, &MusicListenersManager::removeTracksInError, &d->mDatabaseInterface, &DatabaseInterface::removeTracksList); connect(&d->mDatabaseInterface, &DatabaseInterface::trackRemoved, d->mTracksListener.get(), &TracksListener::trackRemoved); connect(&d->mDatabaseInterface, &DatabaseInterface::tracksAdded, d->mTracksListener.get(), &TracksListener::tracksAdded); connect(&d->mDatabaseInterface, &DatabaseInterface::trackModified, d->mTracksListener.get(), &TracksListener::trackModified); Q_EMIT tracksListenerChanged(); } } #include "moc_musiclistenersmanager.cpp" diff --git a/src/qml/ElisaMainWindow.qml b/src/qml/ElisaMainWindow.qml index 6bdaef25..8811caab 100644 --- a/src/qml/ElisaMainWindow.qml +++ b/src/qml/ElisaMainWindow.qml @@ -1,336 +1,339 @@ /* * Copyright 2016-2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 import Qt.labs.settings 1.0 +import Qt.labs.platform 1.1 ApplicationWindow { id: mainWindow visible: true minimumWidth: 590 property int minHeight: 320 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft LayoutMirroring.childrenInherit: true x: persistentSettings.x y: persistentSettings.y width: persistentSettings.width height: persistentSettings.height title: i18n("Elisa") Accessible.role: Accessible.Application Accessible.name: title property var goBackAction: elisa.action("go_back") property var seekAction: elisa.action("Seek") property var scrubAction: elisa.action("Scrub") property var playPauseAction: elisa.action("Play-Pause") property var findAction: elisa.action("edit_find") Action { shortcut: goBackAction.shortcut onTriggered: contentView.goBack() } Action { shortcut: seekAction.shortcut onTriggered: elisa.audioControl.seek(headerBar.playerControl.position + 10000) } Action { shortcut: scrubAction.shortcut onTriggered: elisa.audioControl.seek(headerBar.playerControl.position - 10000) } Action { shortcut: playPauseAction.shortcut onTriggered: elisa.audioControl.playPause() } Action { shortcut: findAction.shortcut onTriggered: persistentSettings.expandedFilterView = !persistentSettings.expandedFilterView } ApplicationMenu { id: applicationMenu } SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } Settings { id: persistentSettings property int x property int y property int width : 900 property int height : 650 property var playListState property var audioPlayerState property double playControlItemVolume : 100.0 property bool playControlItemMuted : false property bool expandedFilterView: false property bool showPlaylist: true property bool headerBarIsMaximized: false } Connections { target: headerBar.playerControl onOpenMenu: { if (applicationMenu.visible) { applicationMenu.close() } else { applicationMenu.popup(mainWindow.width - applicationMenu.width, headerBar.height) } } } Connections { target: Qt.application onAboutToQuit: { persistentSettings.x = mainWindow.x; persistentSettings.y = mainWindow.y; persistentSettings.width = mainWindow.width; persistentSettings.height = mainWindow.height; persistentSettings.playListState = elisa.mediaPlayListProxyModel.persistentState; persistentSettings.audioPlayerState = elisa.audioControl.persistentState persistentSettings.playControlItemVolume = headerBar.playerControl.volume persistentSettings.playControlItemMuted = headerBar.playerControl.muted persistentSettings.showPlaylist = contentView.showPlaylist persistentSettings.headerBarIsMaximized = headerBar.isMaximized } } Loader { id: mprisloader active: false sourceComponent: PlatformIntegration { id: platformInterface playListModel: elisa.mediaPlayListProxyModel audioPlayerManager: elisa.audioControl player: elisa.audioPlayer headerBarManager: elisa.manageHeaderBar manageMediaPlayerControl: elisa.playerControl showProgressOnTaskBar: elisa.showProgressOnTaskBar + showSystemTrayIcon: elisa.showSystemTrayIcon + elisaMainWindow: mainWindow onRaisePlayer: { - mainWindow.show() + mainWindow.visible = true mainWindow.raise() mainWindow.requestActivate() } } } Connections { target: elisa.audioPlayer onVolumeChanged: headerBar.playerControl.volume = elisa.audioPlayer.volume onMutedChanged: headerBar.playerControl.muted = elisa.audioPlayer.muted } Rectangle { color: myPalette.base anchors.fill: parent ColumnLayout { anchors.fill: parent spacing: 0 HeaderBar { id: headerBar focus: true Layout.minimumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.maximumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.fillWidth: true tracksCount: elisa.mediaPlayListProxyModel.remainingTracks album: (elisa.manageHeaderBar.album !== undefined ? elisa.manageHeaderBar.album : '') title: elisa.manageHeaderBar.title artist: (elisa.manageHeaderBar.artist !== undefined ? elisa.manageHeaderBar.artist : '') albumArtist: (elisa.manageHeaderBar.albumArtist !== undefined ? elisa.manageHeaderBar.albumArtist : '') image: elisa.manageHeaderBar.image albumID: elisa.manageHeaderBar.albumId ratingVisible: false playerControl.duration: elisa.audioPlayer.duration playerControl.seekable: elisa.audioPlayer.seekable playerControl.volume: persistentSettings.playControlItemVolume playerControl.muted: persistentSettings.playControlItemMuted playerControl.position: elisa.audioPlayer.position playerControl.skipBackwardEnabled: elisa.playerControl.skipBackwardControlEnabled playerControl.skipForwardEnabled: elisa.playerControl.skipForwardControlEnabled playerControl.playEnabled: elisa.playerControl.playControlEnabled playerControl.isPlaying: elisa.playerControl.musicPlaying playerControl.repeat: elisa.mediaPlayListProxyModel.repeatPlay playerControl.shuffle: elisa.mediaPlayListProxyModel.shufflePlayList playerControl.onSeek: elisa.audioPlayer.seek(position) playerControl.onPlay: elisa.audioControl.playPause() playerControl.onPause: elisa.audioControl.playPause() playerControl.onPlayPrevious: elisa.mediaPlayListProxyModel.skipPreviousTrack() playerControl.onPlayNext: elisa.mediaPlayListProxyModel.skipNextTrack() playerControl.isMaximized: persistentSettings.headerBarIsMaximized onOpenArtist: { contentView.openArtist(artist) } onOpenNowPlaying: { contentView.openNowPlaying() } onOpenAlbum: { contentView.openAlbum(album, albumArtist, image, albumID) } TrackImportNotification { id: importedTracksCountNotification anchors { right: headerBar.right top: headerBar.top rightMargin: Kirigami.Units.largeSpacing * 2 topMargin: Kirigami.Units.largeSpacing * 3 } } Binding { id: indexerBusyBinding target: importedTracksCountNotification property: 'indexingRunning' value: elisa.musicManager.indexerBusy when: elisa.musicManager !== undefined } Binding { target: importedTracksCountNotification property: 'importedTracksCount' value: elisa.musicManager.importedTracksCount when: elisa.musicManager !== undefined } } Rectangle { Layout.fillWidth: true height: 1 color: myPalette.mid } ContentView { id: contentView Layout.fillHeight: true Layout.fillWidth: true showPlaylist: persistentSettings.showPlaylist showExpandedFilterView: persistentSettings.expandedFilterView } } } StateGroup { id: mainWindowState states: [ State { name: "headerBarIsNormal" when: !headerBar.isMaximized changes: [ PropertyChanges { target: mainWindow minimumHeight: mainWindow.minHeight * 1.5 explicit: true }, PropertyChanges { target: headerBar Layout.minimumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.maximumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight } ] }, State { name: "headerBarIsMaximized" when: headerBar.isMaximized changes: [ PropertyChanges { target: mainWindow minimumHeight: mainWindow.minHeight explicit: true }, PropertyChanges { target: headerBar Layout.minimumHeight: mainWindow.height Layout.maximumHeight: mainWindow.height } ] } ] transitions: Transition { NumberAnimation { properties: "Layout.minimumHeight, Layout.maximumHeight, minimumHeight" easing.type: Easing.InOutQuad duration: Kirigami.Units.longDuration } } } Component.onCompleted: { elisa.initialize() if (persistentSettings.playListState) { elisa.mediaPlayListProxyModel.persistentState = persistentSettings.playListState } if (persistentSettings.audioPlayerState) { elisa.audioControl.persistentState = persistentSettings.audioPlayerState } elisa.mediaPlayListProxyModel.shufflePlayList = Qt.binding(function() { return headerBar.playerControl.shuffle }) elisa.mediaPlayListProxyModel.repeatPlay = Qt.binding(function() { return headerBar.playerControl.repeat }) elisa.audioPlayer.muted = Qt.binding(function() { return headerBar.playerControl.muted }) elisa.audioPlayer.volume = Qt.binding(function() { return headerBar.playerControl.volume }) mprisloader.active = true } } diff --git a/src/qml/GeneralConfiguration.qml b/src/qml/GeneralConfiguration.qml index c5ad86fa..8564d4c9 100644 --- a/src/qml/GeneralConfiguration.qml +++ b/src/qml/GeneralConfiguration.qml @@ -1,37 +1,45 @@ /* * Copyright 2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.11 import QtQuick.Controls 2.4 import QtQuick.Layouts 1.3 ColumnLayout { spacing: 0 SystemPalette { id: myPalette colorGroup: SystemPalette.Active } CheckBox { checked: config.showProgressInTaskBar text: i18n("Show progress on Task Manager entries") onCheckedChanged: config.showProgressInTaskBar = checked } + + CheckBox { + checked: config.showSystemTrayIcon + + text: i18n("Keep running in System Tray when main window is closed") + + onToggled: config.showSystemTrayIcon = checked + } } diff --git a/src/qml/PlatformIntegration.qml b/src/qml/PlatformIntegration.qml index 4b3fe671..6867edee 100644 --- a/src/qml/PlatformIntegration.qml +++ b/src/qml/PlatformIntegration.qml @@ -1,49 +1,81 @@ /* * Copyright 2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 -import Qt.labs.platform 1.0 as NativeMenu +import Qt.labs.platform 1.1 as NativeMenu import org.kde.elisa 1.0 Item { id: rootItem property alias playListModel: mpris2Interface.playListModel property alias audioPlayerManager: mpris2Interface.audioPlayerManager property alias player: mpris2Interface.audioPlayer property alias headerBarManager: mpris2Interface.headerBarManager property alias manageMediaPlayerControl: mpris2Interface.manageMediaPlayerControl property alias showProgressOnTaskBar: mpris2Interface.showProgressOnTaskBar + property bool showSystemTrayIcon + property var elisaMainWindow signal raisePlayer() + Connections { + target: elisaMainWindow + + onClosing: { + if (systemTrayIcon.available && showSystemTrayIcon) { + close.accepted = false + elisaMainWindow.hide() + } + } + } + NativeMenu.MenuBar { NativeApplicationMenu { + id: globalMenu } } Mpris2 { id: mpris2Interface playerName: 'elisa' onRaisePlayer: { rootItem.raisePlayer() } } + + NativeMenu.SystemTrayIcon { + id: systemTrayIcon + + icon.name: 'elisa' + tooltip: mainWindow.title + visible: available && showSystemTrayIcon && !mainWindow.visible + + menu: globalMenu + + onActivated: { + if (reason === NativeMenu.SystemTrayIcon.Trigger && !elisaMainWindow.visible) { + elisaMainWindow.visible = true + } else if (reason === NativeMenu.SystemTrayIcon.Trigger && elisaMainWindow.visible) { + raisePlayer() + } + } + } }