diff --git a/kcms/componentchooser/componentchooser.h b/kcms/componentchooser/componentchooser.h index 6b0a18561..f27d334f9 100644 --- a/kcms/componentchooser/componentchooser.h +++ b/kcms/componentchooser/componentchooser.h @@ -1,91 +1,100 @@ /*************************************************************************** componentchooser.h - description ------------------- copyright : (C) 2002 by Joseph Wenninger copyright : (C) 2020 by Méven Car ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation * * * ***************************************************************************/ #ifndef _COMPONENTCHOOSER_H_ #define _COMPONENTCHOOSER_H_ #include "ui_componentchooser_ui.h" #include #include #include class QWidget; class KConfig; class CfgPlugin : public QComboBox { Q_OBJECT public: CfgPlugin(QWidget *parent): QComboBox(parent) {} virtual ~CfgPlugin(){} virtual void load(KConfig *cfg)=0; virtual void save(KConfig *cfg)=0; + bool hasChanged() const { - return m_currentIndex != -1 && m_currentIndex != currentIndex(); + return count() > 1 && m_currentIndex != currentIndex(); } void defaults() { if (m_defaultIndex != -1) { setCurrentIndex(m_defaultIndex); } } + int validLastCurrentIndex() const + { + // m_currentIndex == -1 means there are no previously saved value + // or maybe there were no choices in the combobox + // return 0 in those cases + return m_currentIndex == -1 ? 0 : m_currentIndex; + } + bool isDefaults() const { return m_defaultIndex == -1 || m_defaultIndex == currentIndex(); } Q_SIGNALS: void changed(bool); protected: // the currently saved selected option int m_currentIndex = -1; // the index default of the default option int m_defaultIndex = -1; }; class ComponentChooser : public QWidget, public Ui::ComponentChooser_UI { Q_OBJECT public: ComponentChooser(QWidget *parent=nullptr); ~ComponentChooser() override; void load(); void save(); void restoreDefault(); private: QMap configWidgetMap; CfgPlugin *loadConfigWidget(const QString &cfgType); protected Q_SLOTS: void emitChanged(); Q_SIGNALS: void changed(bool); void defaulted(bool); }; #endif diff --git a/kcms/componentchooser/componentchooserbrowser.cpp b/kcms/componentchooser/componentchooserbrowser.cpp index 1e0e282c5..43c506f58 100644 --- a/kcms/componentchooser/componentchooserbrowser.cpp +++ b/kcms/componentchooser/componentchooserbrowser.cpp @@ -1,131 +1,136 @@ /*************************************************************************** componentchooserbrowser.cpp ------------------- copyright : (C) 2002 by Joseph Wenninger copyright : (C) 2020 by Méven Car ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation * * * ***************************************************************************/ #include "componentchooserbrowser.h" #include #include "browser_settings.h" #include #include #include #include #include #include CfgBrowser::CfgBrowser(QWidget *parent) : CfgPlugin(parent) { connect(this, static_cast(&QComboBox::activated), this, &CfgBrowser::selectBrowser); } CfgBrowser::~CfgBrowser() { } void CfgBrowser::selectBrowser(int index) { if (index == count() -1) { QList urlList; KOpenWithDialog dlg(QStringLiteral("x-scheme-handler/http"), QString(), this); dlg.setSaveNewApplications(true); if (dlg.exec() != QDialog::Accepted) { - setCurrentIndex(m_currentIndex); + setCurrentIndex(validLastCurrentIndex()); return; } const auto service = dlg.service(); // check if the selected service is already in the list const auto matching = model()->match(model()->index(0,0), Qt::UserRole, service->storageId()); if (!matching.isEmpty()) { const int index = matching.at(0).row(); setCurrentIndex(index); emit changed(index != m_currentIndex); } else { const QString icon = !service->icon().isEmpty() ? service->icon() : QStringLiteral("application-x-shellscript"); insertItem(count() -1, QIcon::fromTheme(icon), service->name(), service->storageId()); setCurrentIndex(count() - 2); emit changed(true); } } else { emit changed(index != m_currentIndex); } } void CfgBrowser::load(KConfig *) { const auto browser = KMimeTypeTrader::self()->preferredService("x-scheme-handler/http"); clear(); m_currentIndex = -1; m_defaultIndex = -1; const auto constraint = QStringLiteral("'WebBrowser' in Categories and" " ('x-scheme-handler/http' in ServiceTypes or 'x-scheme-handler/https' in ServiceTypes)"); const auto browsers = KServiceTypeTrader::self()->query(QStringLiteral("Application"), constraint); for (const auto &service : browsers) { addItem(QIcon::fromTheme(service->icon()), service->name(), service->storageId()); if (browser->storageId() == service->storageId()) { setCurrentIndex(count() - 1); m_currentIndex = count() - 1; } if (service->storageId() == QStringLiteral("org.kde.falkon.desktop")) { m_defaultIndex = count() - 1; } } if (browser && m_currentIndex == -1) { // we have a browser specified by the user addItem(QIcon::fromTheme(QStringLiteral("application-x-shellscript")), browser->name(), browser->storageId()); setCurrentIndex(count() - 1); m_currentIndex = count() - 1; } // add a other option to add a new browser addItem(QIcon::fromTheme(QStringLiteral("application-x-shellscript")), i18n("Other...")); emit changed(false); } void CfgBrowser::save(KConfig *) { + if (currentIndex() == count() - 1) { + // no browser installed, nor selected + return; + } + const QString browserStorageId = currentData().toString(); BrowserSettings settings; settings.setBrowserApplication(browserStorageId); settings.save(); // Save the default browser as scheme handler for http(s) in mimeapps.list KSharedConfig::Ptr mimeAppList = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); if (mimeAppList->isConfigWritable(true /*warn user if not writable*/)) { KConfigGroup defaultApp(mimeAppList, "Default Applications"); defaultApp.writeXdgListEntry(QStringLiteral("x-scheme-handler/http"), QStringList(browserStorageId)); defaultApp.writeXdgListEntry(QStringLiteral("x-scheme-handler/https"), QStringList(browserStorageId)); mimeAppList->sync(); QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.klauncher5"), QStringLiteral("/KLauncher"), QStringLiteral("org.kde.KLauncher"), QStringLiteral("reparseConfiguration")); QDBusConnection::sessionBus().send(message); m_currentIndex = currentIndex(); emit changed(false); } } diff --git a/kcms/componentchooser/componentchooseremail.cpp b/kcms/componentchooser/componentchooseremail.cpp index a583a5109..d06ac95cb 100644 --- a/kcms/componentchooser/componentchooseremail.cpp +++ b/kcms/componentchooser/componentchooseremail.cpp @@ -1,176 +1,185 @@ /*************************************************************************** componentchooseremail.cpp ------------------- copyright : (C) 2002 by Joseph Wenninger copyright : (C) 2020 by Méven Car ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation * * * ***************************************************************************/ #include "componentchooseremail.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { static const char s_AddedAssociations[] = "Added Associations"; static const auto s_mimetype = QStringLiteral("x-scheme-handler/mailto"); } CfgEmailClient::CfgEmailClient(QWidget *parent) : CfgPlugin(parent) { pSettings = new KEMailSettings(); connect(this, static_cast(&QComboBox::activated), this, &CfgEmailClient::selectEmailClient); } CfgEmailClient::~CfgEmailClient() { delete pSettings; } void CfgEmailClient::load(KConfig *) { const KService::Ptr emailClientService = KMimeTypeTrader::self()->preferredService(s_mimetype); const auto emailClients = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("'Email' in Categories and 'x-scheme-handler/mailto' in ServiceTypes")); clear(); m_currentIndex = -1; m_defaultIndex = -1; for (const auto &service : emailClients) { addItem(QIcon::fromTheme(service->icon()), service->name(), service->storageId()); if (emailClientService && emailClientService->storageId() == service->storageId()) { setCurrentIndex(count() - 1); m_currentIndex = count() - 1; } if (service->storageId() == QStringLiteral("org.kde.kmail2.desktop") || service->storageId() == QStringLiteral("org.kde.kmail.desktop")) { m_defaultIndex = count() - 1; } } // add the Added association to x-scheme-handler/mailto from the mimeapps.list file const KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); const KConfigGroup addedApps(profile, s_AddedAssociations); const auto addedList = addedApps.readXdgListEntry(s_mimetype); for (const auto &addedApp : addedList) { // without .desktop extension auto service = KService::serviceByStorageId(addedApp.mid(0, addedApp.length() -8)); if (!service) { service = KService::serviceByStorageId(addedApp); } if (!service) { continue; } // avoid duplicates entry when email clients are present in mimeapps.list's Added Associations too const bool isServiceAlreadyInserted = std::none_of(emailClients.constBegin(), emailClients.constEnd(), [service] (const KService::Ptr &serv) { return service->storageId() == serv->storageId(); }); if (isServiceAlreadyInserted) { const auto icon = QIcon::fromTheme(!service->icon().isEmpty() ? service->icon() : QStringLiteral("application-x-shellscript")); addItem(icon, service->name() + " (" + KShell::tildeCollapse(service->entryPath()) + ")", service->storageId()); if (emailClientService && emailClientService->storageId() == service->storageId()) { setCurrentIndex(count() - 1); m_currentIndex = count() - 1; } } } // add a other option to add a new email client with KOpenWithDialog addItem(QIcon::fromTheme(QStringLiteral("application-x-shellscript")), i18n("Other..."), QStringLiteral()); emit changed(false); } void CfgEmailClient::selectEmailClient(int index) { if (index == count() -1) { // Other option KOpenWithDialog dlg(s_mimetype, QString(), this); dlg.setSaveNewApplications(true); if (dlg.exec() != QDialog::Accepted) { // restore previous setting - setCurrentIndex(m_currentIndex); + setCurrentIndex(validLastCurrentIndex()); emit changed(false); } else { const auto service = dlg.service(); const auto icon = QIcon::fromTheme(!service->icon().isEmpty() ? service->icon() : QStringLiteral("application-x-shellscript")); insertItem(count() - 1, icon, service->name() + " (" + KShell::tildeCollapse(service->entryPath()) + ")", service->storageId()); // select newly inserted email client setCurrentIndex(count() - 2); emit changed(true); return; } } else { emit changed(m_currentIndex != index); } } void CfgEmailClient::save(KConfig *) { + if (currentIndex() == count() - 1) { + // no email client installed, nor selected + return; + } + const QString &storageId = currentData().toString(); const KService::Ptr emailClientService = KService::serviceByStorageId(storageId); + if (!emailClientService) { + // double checking, the selected email client might have been removed + return; + } const bool kmailSelected = m_defaultIndex != -1 && currentIndex() == m_defaultIndex; if (kmailSelected) { pSettings->setSetting(KEMailSettings::ClientProgram, QString()); pSettings->setSetting(KEMailSettings::ClientTerminal, QStringLiteral("false")); } else { pSettings->setSetting(KEMailSettings::ClientProgram, emailClientService->storageId()); pSettings->setSetting(KEMailSettings::ClientTerminal, emailClientService->terminal() ? QStringLiteral("true") : QStringLiteral("false")); } // Save the default email client in mimeapps.list KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); - if (profile->isConfigWritable(true) && emailClientService) { + if (profile->isConfigWritable(true)) { KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); // Save the default application according to mime-apps-spec 1.0 KConfigGroup defaultApp(profile, "Default Applications"); defaultApp.writeXdgListEntry(s_mimetype, {emailClientService->storageId()}); KConfigGroup addedApps(profile, "Added Associations"); QStringList apps = addedApps.readXdgListEntry(s_mimetype); apps.removeAll(emailClientService->storageId()); apps.prepend(emailClientService->storageId()); // make it the preferred app, i.e first in list addedApps.writeXdgListEntry(s_mimetype, apps); profile->sync(); m_currentIndex = currentIndex(); emit changed(false); } } diff --git a/kcms/componentchooser/componentchooserfilemanager.cpp b/kcms/componentchooser/componentchooserfilemanager.cpp index fa2278733..0d8b3d7fa 100644 --- a/kcms/componentchooser/componentchooserfilemanager.cpp +++ b/kcms/componentchooser/componentchooserfilemanager.cpp @@ -1,138 +1,141 @@ /* This file is part of the KDE project Copyright (C) 2008 David Faure Copyright (C) 2020 Méven Car This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "componentchooserfilemanager.h" #include #include #include #include #include #include #include CfgFileManager::CfgFileManager(QWidget *parent) : CfgPlugin(parent) { connect(this, static_cast(&QComboBox::activated), this, &CfgFileManager::selectFileManager); } CfgFileManager::~CfgFileManager() { } void CfgFileManager::selectFileManager(int index) { if (index == count() -1) { KOpenWithDialog dlg({}, i18n("Select preferred file manager:"), QString(), this); dlg.setSaveNewApplications(true); if (dlg.exec() != QDialog::Accepted) { - setCurrentIndex(m_currentIndex); + setCurrentIndex(validLastCurrentIndex()); return; } const auto service = dlg.service(); // if the selected service is already in the list const auto matching = model()->match(model()->index(0,0), Qt::UserRole, service->storageId()); if (!matching.isEmpty()) { const int index = matching.at(0).row(); setCurrentIndex(index); changed(index != m_currentIndex); } else { const QString icon = !service->icon().isEmpty() ? service->icon() : QStringLiteral("application-x-shellscript"); insertItem(count() -1, QIcon::fromTheme(icon), service->name(), service->storageId()); setCurrentIndex(count() - 2); changed(true); } } else { changed(index != m_currentIndex); } } static const QString mime = QStringLiteral("inode/directory"); void CfgFileManager::load(KConfig *) { clear(); m_currentIndex = -1; m_defaultIndex = -1; const KService::Ptr fileManager = KMimeTypeTrader::self()->preferredService(mime); const auto constraint = QStringLiteral("'FileManager' in Categories and 'inode/directory' in ServiceTypes"); const KService::List fileManagers = KServiceTypeTrader::self()->query(QStringLiteral("Application"), constraint); for (const KService::Ptr &service : fileManagers) { addItem(QIcon::fromTheme(service->icon()), service->name(), service->storageId()); if (fileManager->storageId() == service->storageId()) { setCurrentIndex(count() -1); m_currentIndex = count() -1; } if (service->storageId() == QStringLiteral("org.kde.dolphin.desktop")) { m_defaultIndex = count() -1; } } // in case of a service not associated with FileManager Category if (m_currentIndex == -1 && !fileManager->storageId().isEmpty()) { const KService::Ptr service = KService::serviceByStorageId(fileManager->storageId()); const QString icon = !service->icon().isEmpty() ? service->icon() : QStringLiteral("application-x-shellscript"); addItem(QIcon::fromTheme(icon), service->name(), service->storageId()); setCurrentIndex(count() -1); m_currentIndex = count() -1; } // add a other option to add a new file manager with KOpenWithDialog addItem(QIcon::fromTheme(QStringLiteral("application-x-shellscript")), i18n("Other..."), QStringLiteral()); emit changed(false); } static const char s_DefaultApplications[] = "Default Applications"; static const char s_AddedAssociations[] = "Added Associations"; void CfgFileManager::save(KConfig *) { + if (currentIndex() == count() - 1) { + // no filemanager installed, nor selected + return; + } + const QString storageId = currentData().toString(); - if (!storageId.isEmpty()) { - m_currentIndex = currentIndex(); + m_currentIndex = currentIndex(); - // This is taken from filetypes/mimetypedata.cpp - KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); - if (!profile->isConfigWritable(true)) // warn user if mimeapps.list is root-owned (#155126/#94504) - return; - KConfigGroup addedApps(profile, s_AddedAssociations); - QStringList userApps = addedApps.readXdgListEntry(mime); - userApps.removeAll(storageId); // remove if present, to make it first in the list - userApps.prepend(storageId); - addedApps.writeXdgListEntry(mime, userApps); + // This is taken from filetypes/mimetypedata.cpp + KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); + if (!profile->isConfigWritable(true)) // warn user if mimeapps.list is root-owned (#155126/#94504) + return; + KConfigGroup addedApps(profile, s_AddedAssociations); + QStringList userApps = addedApps.readXdgListEntry(mime); + userApps.removeAll(storageId); // remove if present, to make it first in the list + userApps.prepend(storageId); + addedApps.writeXdgListEntry(mime, userApps); - // Save the default file manager as per mime-apps spec 1.0.1 - KConfigGroup defaultApp(profile, s_DefaultApplications); - defaultApp.writeXdgListEntry(mime, QStringList(storageId)); + // Save the default file manager as per mime-apps spec 1.0.1 + KConfigGroup defaultApp(profile, s_DefaultApplications); + defaultApp.writeXdgListEntry(mime, QStringList(storageId)); - profile->sync(); + profile->sync(); - emit changed(false); - } + emit changed(false); } diff --git a/kcms/componentchooser/componentchooserterminal.cpp b/kcms/componentchooser/componentchooserterminal.cpp index 309c78f87..0feb60075 100644 --- a/kcms/componentchooser/componentchooserterminal.cpp +++ b/kcms/componentchooser/componentchooserterminal.cpp @@ -1,137 +1,141 @@ /*************************************************************************** componentchooser.cpp - description ------------------- copyright : (C) 2002 by Joseph Wenninger copyright : (C) 2020 by Méven Car ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation * * * ***************************************************************************/ #include "componentchooserterminal.h" #include "terminal_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include CfgTerminalEmulator::CfgTerminalEmulator(QWidget *parent) : CfgPlugin(parent) { connect(this, static_cast(&QComboBox::activated), this, &CfgTerminalEmulator::selectTerminalEmulator); } CfgTerminalEmulator::~CfgTerminalEmulator() { } void CfgTerminalEmulator::selectTerminalEmulator(int index) { if (index == count() - 1) { selectTerminalApp(); } else { emit changed(m_currentIndex != index); } } void CfgTerminalEmulator::load(KConfig *) { TerminalSettings settings; const QString terminal = settings.terminalApplication(); clear(); m_currentIndex = -1; m_defaultIndex = -1; const auto constraint = QStringLiteral("'TerminalEmulator' in Categories AND (not exist NoDisplay OR NoDisplay == false)"); const auto terminalEmulators = KServiceTypeTrader::self()->query(QStringLiteral("Application"), constraint); for (const auto &service : terminalEmulators) { addItem(QIcon::fromTheme(service->icon()), service->name(), service->exec()); if (!terminal.isEmpty() && service->exec() == terminal) { setCurrentIndex(count() - 1); m_currentIndex = count() - 1; } if (service->exec() == QStringLiteral("konsole")) { m_defaultIndex = count() - 1; } } if (!terminal.isEmpty() && m_currentIndex == -1) { // we have a terminal specified by the user addItem(QIcon::fromTheme(QStringLiteral("application-x-shellscript")), terminal, terminal); setCurrentIndex(count() - 1); m_currentIndex = count() - 1; } // add a other option to add a new terminal emulator with KOpenWithDialog addItem(QIcon::fromTheme(QStringLiteral("application-x-shellscript")), i18n("Other..."), QStringLiteral()); - emit changed(false); + emit changed(false); } void CfgTerminalEmulator::save(KConfig *) { + if (currentIndex() == count() - 1) { + // no terminal installed, nor selected + return; + } + const QString terminal = currentData().toString(); - m_currentIndex = currentIndex(); - TerminalSettings settings; + TerminalSettings settings; settings.setTerminalApplication(terminal); - settings.save(); + settings.save(); m_currentIndex = currentIndex(); - QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.klauncher5"), + QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.klauncher5"), QStringLiteral("/KLauncher"), QStringLiteral("org.kde.KLauncher"), QStringLiteral("reparseConfiguration")); QDBusConnection::sessionBus().send(message); emit changed(false); } void CfgTerminalEmulator::selectTerminalApp() { QList urlList; KOpenWithDialog dlg(urlList, i18n("Select preferred terminal application:"), QString(), this); // hide "Run in &terminal" here, we don't need it for a Terminal Application dlg.hideRunInTerminal(); dlg.setSaveNewApplications(true); if (dlg.exec() != QDialog::Accepted) { - setCurrentIndex(m_currentIndex); + setCurrentIndex(validLastCurrentIndex()); return; } const auto service = dlg.service(); // if the selected service is already in the list const auto matching = model()->match(model()->index(0,0), Qt::DisplayRole, service->exec()); if (!matching.isEmpty()) { const int index = matching.at(0).row(); setCurrentIndex(index); changed(index != m_currentIndex); } else { const QString icon = !service->icon().isEmpty() ? service->icon() : QStringLiteral("application-x-shellscript"); insertItem(count() -1, QIcon::fromTheme(icon), service->name(), service->exec()); setCurrentIndex(count() - 2); changed(true); } } // vim: sw=4 ts=4 noet