diff --git a/kcms/componentchooser/componentchooser.cpp b/kcms/componentchooser/componentchooser.cpp index 9cdabb4f0..5a72f94ef 100644 --- a/kcms/componentchooser/componentchooser.cpp +++ b/kcms/componentchooser/componentchooser.cpp @@ -1,273 +1,274 @@ /*************************************************************************** componentchooser.cpp - description ------------------- copyright : (C) 2002 by Joseph Wenninger email : jowenn@kde.org ***************************************************************************/ /*************************************************************************** * * * 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 "componentchooser.h" #include "componentchooserbrowser.h" #include "componentchooseremail.h" #include "componentchooserfilemanager.h" #ifdef Q_OS_UNIX #include "componentchooserterminal.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include //BEGIN General kpart based Component selection CfgComponent::CfgComponent(QWidget *parent) : QWidget(parent), Ui::ComponentConfig_UI(), CfgPlugin() { setupUi( this ); connect(ComponentSelector,SIGNAL(activated(const QString&)),this,SLOT(slotComponentChanged(const QString&))); } CfgComponent::~CfgComponent() { } void CfgComponent::slotComponentChanged(const QString&) { emit changed(true); } void CfgComponent::save(KConfig *cfg) { // yes, this can happen if there are NO KTrader offers for this component if (!m_lookupDict.contains(ComponentSelector->currentText())) return; KConfigGroup mainGroup = cfg->group(QByteArray()); QString serviceTypeToConfigure=mainGroup.readEntry("ServiceTypeToConfigure"); KConfig store(mainGroup.readPathEntry("storeInFile", QStringLiteral("null"))); KConfigGroup cg(&store, mainGroup.readEntry("valueSection")); cg.writePathEntry(mainGroup.readEntry("valueName", "kcm_componenchooser_null"), m_lookupDict.value(ComponentSelector->currentText())); store.sync(); emit changed(false); } void CfgComponent::load(KConfig *cfg) { ComponentSelector->clear(); m_lookupDict.clear(); m_revLookupDict.clear(); const KConfigGroup mainGroup = cfg->group(QByteArray()); const QString serviceTypeToConfigure = mainGroup.readEntry("ServiceTypeToConfigure"); const KService::List offers = KServiceTypeTrader::self()->query(serviceTypeToConfigure); for (const auto &service: offers) { ComponentSelector->addItem(service->name()); m_lookupDict.insert(service->name(), service->desktopEntryName()); m_revLookupDict.insert(service->desktopEntryName(), service->name()); } KConfig store(mainGroup.readPathEntry("storeInFile",QStringLiteral("null"))); const KConfigGroup group(&store, mainGroup.readEntry("valueSection")); QString setting = group.readEntry(mainGroup.readEntry("valueName","kcm_componenchooser_null"), QString()); if (setting.isEmpty()) setting = mainGroup.readEntry("defaultImplementation", QString()); QString tmp = m_revLookupDict.value(setting); if (!tmp.isEmpty()) { for (int i=0;icount();i++) if (tmp==ComponentSelector->itemText(i)) { ComponentSelector->setCurrentIndex(i); break; } } emit changed(false); } void CfgComponent::defaults() { //todo } bool CfgComponent::isDefaults() const { return false; } //END General kpart based Component selection ComponentChooser::ComponentChooser(QWidget *parent): QWidget(parent), Ui::ComponentChooser_UI(), somethingChanged(false), configWidget(nullptr) { setupUi(this); static_cast(layout())->setRowStretch(1, 1); const QStringList directories = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kcm_componentchooser"), QStandardPaths::LocateDirectory); QStringList services; for(const QString &directory : directories) { const QDir dir(directory); for(const QString &f: dir.entryList(QStringList("*.desktop"))) { services += dir.absoluteFilePath(f); } } for (const QString &service : qAsConst(services)) { KConfig cfg(service, KConfig::SimpleConfig); KConfigGroup cg = cfg.group(QByteArray()); QListWidgetItem *item = new QListWidgetItem( QIcon::fromTheme(cg.readEntry("Icon",QStringLiteral("preferences-desktop-default-applications"))), cg.readEntry("Name",i18n("Unknown"))); item->setData(Qt::UserRole, service); ServiceChooser->addItem(item); loadConfigWidget(service, cfg.group(QByteArray()).readEntry("configurationType"), item->text()); } ServiceChooser->setFixedWidth(ServiceChooser->sizeHintForColumn(0) + 20); ServiceChooser->sortItems(); connect(ServiceChooser,&QListWidget::currentItemChanged,this,&ComponentChooser::slotServiceSelected); ServiceChooser->setCurrentRow(0); } void ComponentChooser::loadConfigWidget(const QString &service, const QString &cfgType, const QString &name) { QWidget *loadedConfigWidget = nullptr; if (cfgType.isEmpty() || (cfgType == QLatin1String("component"))) { loadedConfigWidget = new CfgComponent(configContainer); static_cast(loadedConfigWidget)->ChooserDocu->setText(i18n("Choose from the list below which component should be used by default for the %1 service.", name)); } else if (cfgType==QLatin1String("internal_email")) { loadedConfigWidget = new CfgEmailClient(configContainer); } #ifdef Q_OS_UNIX else if (cfgType==QLatin1String("internal_terminal")) { loadedConfigWidget = new CfgTerminalEmulator(configContainer); } #endif else if (cfgType==QLatin1String("internal_filemanager")) { loadedConfigWidget = new CfgFileManager(configContainer); } else if (cfgType==QLatin1String("internal_browser")) { loadedConfigWidget = new CfgBrowser(configContainer); } if (loadedConfigWidget) { configWidgetMap.insert(service, loadedConfigWidget); configContainer->addWidget(loadedConfigWidget); connect(loadedConfigWidget, SIGNAL(changed(bool)), this, SLOT(emitChanged(bool))); } } void ComponentChooser::slotServiceSelected(QListWidgetItem* it) { if (!it) return; if (somethingChanged) { if (KMessageBox::questionYesNo(this,i18n("You changed the default component of your choice, do want to save that change now ?"),QString(),KStandardGuiItem::save(),KStandardGuiItem::discard())==KMessageBox::Yes) save(); } const QString &service = it->data(Qt::UserRole).toString(); KConfig cfg(service, KConfig::SimpleConfig); ComponentDescription->setText(cfg.group(QByteArray()).readEntry("Comment",i18n("No description available"))); ComponentDescription->setMinimumSize(ComponentDescription->sizeHint()); configWidget = configWidgetMap.value(service); if (configWidget) { configContainer->setCurrentWidget(configWidget); const auto plugin = dynamic_cast(configWidget); + headerGroupBox->setTitle(it->text()); plugin->load(&cfg); emit defaulted(plugin->isDefaults()); } emitChanged(false); latestEditedService = service; } void ComponentChooser::emitChanged(bool val) { somethingChanged=val; emit changed(val); CfgPlugin *plugin = dynamic_cast( configWidget ); emit defaulted(plugin->isDefaults()); } ComponentChooser::~ComponentChooser() { for (QWidget *configWidget : configWidgetMap) { delete configWidget; } } void ComponentChooser::load() { if( configWidget ) { CfgPlugin * plugin = dynamic_cast( configWidget ); if( plugin ) { KConfig cfg(latestEditedService, KConfig::SimpleConfig); plugin->load( &cfg ); } } } void ComponentChooser::save() { if( configWidget ) { CfgPlugin* plugin = dynamic_cast( configWidget ); if( plugin ) { KConfig cfg(latestEditedService, KConfig::SimpleConfig); plugin->save( &cfg ); } } } void ComponentChooser::restoreDefault() { if (configWidget) { dynamic_cast(configWidget)->defaults(); emitChanged(true); } /* txtEMailClient->setText("kmail"); chkRunTerminal->setChecked(false); // Check if -e is needed, I do not think so terminalLE->setText("xterm"); //No need for i18n terminalCB->setChecked(true); emitChanged(false); */ } // vim: sw=4 ts=4 noet diff --git a/kcms/componentchooser/componentchooser_ui.ui b/kcms/componentchooser/componentchooser_ui.ui index 444eec98c..6117386a8 100644 --- a/kcms/componentchooser/componentchooser_ui.ui +++ b/kcms/componentchooser/componentchooser_ui.ui @@ -1,71 +1,71 @@ ComponentChooser_UI 0 0 427 192 0 - + 0 0 Here you can change the component program. Components are programs that handle basic tasks, like the terminal emulator, the text editor and the email client. Different KDE applications sometimes need to invoke a console emulator, send a mail or display some text. To do so consistently, these applications always call the same components. You can choose here which programs these components are. Default Component <qt> <p>This list shows the configurable component types. Click the component you want to configure.</p> <p>In this dialog you can change KDE default components. Components are programs that handle basic tasks, like the terminal emulator, the text editor and the email client. Different KDE applications sometimes need to invoke a console emulator, send a mail or display some text. To do so consistently, these applications always call the same components. Here you can select which programs these components are.</p> </qt> true 0 0 Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true diff --git a/kcms/componentchooser/componentchooseremail.cpp b/kcms/componentchooser/componentchooseremail.cpp index 60c3e6f70..9697c25ce 100644 --- a/kcms/componentchooser/componentchooseremail.cpp +++ b/kcms/componentchooser/componentchooseremail.cpp @@ -1,153 +1,200 @@ /*************************************************************************** componentchooseremail.cpp ------------------- - copyright : (C) 2002 by Joseph Wenninger - email : jowenn@kde.org + 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 #include -// for chmod: -#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) : QWidget(parent), Ui::EmailClientConfig_UI(), CfgPlugin() { setupUi( this ); pSettings = new KEMailSettings(); - connect(kmailCB, &QRadioButton::toggled, this, &CfgEmailClient::configChanged); - connect(txtEMailClient, &KLineEdit::textChanged, this, &CfgEmailClient::configChanged); -#ifdef Q_OS_UNIX - connect(chkRunTerminal, &QCheckBox::clicked, this, &CfgEmailClient::configChanged); -#else - chkRunTerminal->hide(); -#endif - connect(btnSelectEmail, &QToolButton::clicked, this, &CfgEmailClient::selectEmailClient); + connect(emailClientsCombo, static_cast(&QComboBox::activated), this, &CfgEmailClient::selectEmailClient); } CfgEmailClient::~CfgEmailClient() { delete pSettings; } void CfgEmailClient::defaults() { - kmailCB->setChecked(true); - txtEMailClient->clear(); - chkRunTerminal->setChecked(false); + // select kmail if installed + if (m_kmailIndex != -1) { + emailClientsCombo->setCurrentIndex(m_kmailIndex); + } } bool CfgEmailClient::isDefaults() const { - return kmailCB->isChecked(); + // if kmail is installed and is selected + if (m_kmailIndex != -1) { + return emailClientsCombo->currentIndex() == m_kmailIndex; + } + + return true; } void CfgEmailClient::load(KConfig *) { - QString emailClient = pSettings->getSetting(KEMailSettings::ClientProgram); - bool useKMail = (emailClient.isEmpty()); + const KService::Ptr emailClientService = KMimeTypeTrader::self()->preferredService(s_mimetype); - kmailCB->setChecked(useKMail); - otherCB->setChecked(!useKMail); - txtEMailClient->setText(emailClient); - txtEMailClient->setFixedHeight(txtEMailClient->sizeHint().height()); - chkRunTerminal->setChecked((pSettings->getSetting(KEMailSettings::ClientTerminal) == QLatin1String("true"))); + const auto emailClients = KServiceTypeTrader::self()->query(QStringLiteral("Application"), + QStringLiteral("'Email' in Categories and 'x-scheme-handler/mailto' in ServiceTypes")); - emit changed(false); + emailClientsCombo->clear(); + m_kmailIndex = -1; + m_currentIndex = -1; -} + for (const auto &service : emailClients) { -void CfgEmailClient::configChanged() -{ - emit changed(true); + emailClientsCombo->addItem(QIcon::fromTheme(service->icon()), service->name(), service->storageId()); + + if (emailClientService && emailClientService->storageId() == service->storageId()) { + emailClientsCombo->setCurrentIndex(emailClientsCombo->count() - 1); + m_currentIndex = emailClientsCombo->count() - 1; + } + if (service->storageId() == QStringLiteral("org.kde.kmail2.desktop") || + service->storageId() == QStringLiteral("org.kde.kmail.desktop")) { + m_kmailIndex = emailClientsCombo->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")); + emailClientsCombo->addItem(icon, service->name() + " (" + KShell::tildeCollapse(service->entryPath()) + ")", service->storageId()); + + if (emailClientService && emailClientService->storageId() == service->storageId()) { + emailClientsCombo->setCurrentIndex(emailClientsCombo->count() - 1); + m_currentIndex = emailClientsCombo->count() - 1; + } + } + } + + // add a other option to add a new email client with KOpenWithDialog + emailClientsCombo->addItem(QIcon::fromTheme(QStringLiteral("application-x-shellscript")), i18n("Other..."), QStringLiteral()); + + emit changed(false); } -void CfgEmailClient::selectEmailClient() +void CfgEmailClient::selectEmailClient(int index) { - QList urlList; - KOpenWithDialog dlg(urlList, i18n("Select preferred email client:"), QString(), this); - // hide "Do not &close when command exits" here, we don't need it for a mail client - dlg.hideNoCloseOnExit(); - if (dlg.exec() != QDialog::Accepted) return; - QString client = dlg.text(); - m_emailClientService = dlg.service(); - - // get the preferred Terminal Application - KConfigGroup confGroup( KSharedConfig::openConfig(), QStringLiteral("General") ); - QString preferredTerminal = confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole")); - preferredTerminal += QLatin1String(" -e "); - - int len = preferredTerminal.length(); - bool b = client.left(len) == preferredTerminal; - if (b) client = client.mid(len); - if (!client.isEmpty()) - { - chkRunTerminal->setChecked(b); - txtEMailClient->setText(client); + if (index == emailClientsCombo->count() -1) { + // Other option + + KOpenWithDialog dlg(s_mimetype, QString(), this); + dlg.setSaveNewApplications(true); + + if (dlg.exec() != QDialog::Accepted) { + // restore previous setting + emailClientsCombo->setCurrentIndex(m_currentIndex); + emit changed(false); + } else { + const auto service = dlg.service(); + + const auto icon = QIcon::fromTheme(!service->icon().isEmpty() ? service->icon() : QStringLiteral("application-x-shellscript")); + emailClientsCombo->insertItem(emailClientsCombo->count() - 1, icon, service->name() + " (" + KShell::tildeCollapse(service->entryPath()) + ")", service->storageId()); + + // select newly inserted email client + emailClientsCombo->setCurrentIndex(emailClientsCombo->count() - 2); + + emit changed(true); + return; + } + } else { + emit changed(m_currentIndex != index); } } - void CfgEmailClient::save(KConfig *) { - if (kmailCB->isChecked()) - { + const QString &storageId = emailClientsCombo->currentData().toString(); + const KService::Ptr emailClientService = KService::serviceByStorageId(storageId); + + const bool kmailSelected = m_kmailIndex != -1 && emailClientsCombo->currentIndex() == m_kmailIndex; + if (kmailSelected) { pSettings->setSetting(KEMailSettings::ClientProgram, QString()); pSettings->setSetting(KEMailSettings::ClientTerminal, QStringLiteral("false")); - } - else - { - pSettings->setSetting(KEMailSettings::ClientProgram, txtEMailClient->text()); - pSettings->setSetting(KEMailSettings::ClientTerminal, (chkRunTerminal->isChecked()) ? "true" : "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 into the group [Default Applications] + // Save the default email client in mimeapps.list KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); - if (profile->isConfigWritable(true)) { + if (profile->isConfigWritable(true) && emailClientService) { + + 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"); - if (kmailCB->isChecked()) { - QString kmailDesktop = QStringLiteral("org.kde.kmail.desktop"); - if (KService::serviceByDesktopName(QStringLiteral("org.kde.kmail2"))) { - kmailDesktop = QStringLiteral("org.kde.kmail2.desktop"); - } - defaultApp.writeXdgListEntry("x-scheme-handler/mailto", QStringList(kmailDesktop)); - } else if (m_emailClientService) { - defaultApp.writeXdgListEntry("x-scheme-handler/mailto", QStringList(m_emailClientService->storageId())); - } + 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(); - } - // insure proper permissions -- contains sensitive data - QString cfgName(QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("emails"))); - if (!cfgName.isEmpty()) - ::chmod(QFile::encodeName(cfgName), 0600); - QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/Component"), QStringLiteral("org.kde.Kcontrol"), QStringLiteral("KDE_emailSettingsChanged") ); - QDBusConnection::sessionBus().send(message); - emit changed(false); + m_currentIndex = emailClientsCombo->currentIndex(); + + // refresh cache + KBuildSycocaProgressDialog::rebuildKSycoca(this); + + emit changed(false); + } } diff --git a/kcms/componentchooser/componentchooseremail.h b/kcms/componentchooser/componentchooseremail.h index 7fc5a04fc..6112e924f 100644 --- a/kcms/componentchooser/componentchooseremail.h +++ b/kcms/componentchooser/componentchooseremail.h @@ -1,46 +1,46 @@ /*************************************************************************** componentchooseremail.h ------------------- copyright : (C) 2002 by Joseph Wenninger email : jowenn@kde.org ***************************************************************************/ /*************************************************************************** * * * 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 Foundationi * * * ***************************************************************************/ #ifndef COMPONENTCHOOSEREMAIL_H #define COMPONENTCHOOSEREMAIL_H class KEMailSettings; #include "ui_emailclientconfig_ui.h" #include "componentchooser.h" class CfgEmailClient: public QWidget, public Ui::EmailClientConfig_UI, public CfgPlugin { Q_OBJECT public: CfgEmailClient(QWidget *parent); ~CfgEmailClient() override; void load(KConfig *cfg) override; void save(KConfig *cfg) override; void defaults() override; bool isDefaults() const override; private: KEMailSettings *pSettings; - KService::Ptr m_emailClientService; + int m_currentIndex = -1; + int m_kmailIndex = -1; protected Q_SLOTS: - void selectEmailClient(); - void configChanged(); + void selectEmailClient(int index); Q_SIGNALS: void changed(bool); }; #endif diff --git a/kcms/componentchooser/emailclientconfig_ui.ui b/kcms/componentchooser/emailclientconfig_ui.ui index f5fd8e6a9..21a8a3d5f 100644 --- a/kcms/componentchooser/emailclientconfig_ui.ui +++ b/kcms/componentchooser/emailclientconfig_ui.ui @@ -1,184 +1,69 @@ EmailClientConfig_UI + + + 0 + 0 + 240 + 148 + + - + + 0 + + + 0 + + + 0 + + 0 - - - - Kmail is the standard Mail program for the Plasma desktop. - - - &Use KMail as preferred email client - - - - - - - Select this option if you want to use any other mail program. - - - Use a different &email client: - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 20 - 0 - - - - - - - - false - - - Optional placeholders: <ul> <li>%t: Recipient's address</li> <li>%s: Subject</li> <li>%c: Carbon Copy (CC)</li> <li>%b: Blind Carbon Copy (BCC)</li> <li>%B: Template body text</li> <li>%A: Attachment </li> <li>%u: Full mailto: URL </li></ul> - - - Press this button to select your favorite email client. Please note that the file you select has to have the executable attribute set in order to be accepted.<br/> You can also use several placeholders which will be replaced with the actual values when the email client is called:<ul> <li>%t: Recipient's address</li> <li>%s: Subject</li> <li>%c: Carbon Copy (CC)</li> <li>%b: Blind Carbon Copy (BCC)</li> <li>%B: Template body text</li> <li>%A: Attachment </li> </ul> - - - - - - - false - - - Click here to browse for the mail program file. - - - ... - - - - - Qt::Horizontal QSizePolicy::Fixed 20 0 - - - false - - - Activate this option if you want the selected email client to be executed in a terminal (e.g. <em>Konsole</em>). - - - &Run in terminal - - + Qt::Vertical QSizePolicy::Expanding 0 20 - - - KLineEdit - QLineEdit -
KLineEdit
-
-
- - - otherCB - toggled(bool) - chkRunTerminal - setEnabled(bool) - - - 20 - 20 - - - 20 - 20 - - - - - otherCB - toggled(bool) - txtEMailClient - setEnabled(bool) - - - 20 - 20 - - - 20 - 20 - - - - - otherCB - toggled(bool) - btnSelectEmail - setEnabled(bool) - - - 20 - 20 - - - 20 - 20 - - - - +