Commit 246b2319 authored by Tobias Fella's avatar Tobias Fella Committed by Nate Graham

Port the Component Chooser KCM to QML

What's missing from the old KCM is the application's icons in the comboboxes,
since that's not supported by QtQuick ComboBoxes without using adding a custom
delegate. However there's work on supporting it and the KCM does expose the
icons to qml, so it's easy to add once supported.
parent f53807b4
# KI18N Translation Domain for this library
add_definitions(-DTRANSLATION_DOMAIN=\"kcm5_componentchooser\")
add_subdirectory( componentservices )
########### next target ###############
set(kcm_componentchooser_SRCS
componentchooser.cpp
componentchooserbrowser.cpp
componentchooserfilemanager.cpp
componentchooseremail.cpp
componentchooserterminal.cpp
kcm_componentchooser.cpp
)
kconfig_add_kcfg_files(kcm_componentchooser_SRCS browser_settings.kcfgc GENERATE_MOC)
kconfig_add_kcfg_files(kcm_componentchooser_SRCS terminal_settings.kcfgc GENERATE_MOC)
ki18n_wrap_ui(kcm_componentchooser_SRCS
componentchooser_ui.ui
set(componentchooser_SRCS
kcm_componentchooser.cpp
componentchooser.cpp
componentchooserbrowser.cpp
componentchooseremail.cpp
componentchooserterminal.cpp
componentchooserfilemanager.cpp
)
add_library(kcm_componentchooser MODULE ${kcm_componentchooser_SRCS})
kconfig_add_kcfg_files(componentchooser_SRCS browser_settings.kcfgc GENERATE_MOC)
kconfig_add_kcfg_files(componentchooser_SRCS terminal_settings.kcfgc GENERATE_MOC)
add_library(kcm_componentchooser MODULE ${componentchooser_SRCS})
target_link_libraries(kcm_componentchooser
KF5::I18n
KF5::Completion
KF5::ConfigWidgets
KF5::Service
KF5::KIOWidgets
KF5::KDELibs4Support
Qt5::DBus
Qt5::X11Extras
)
install(TARGETS kcm_componentchooser DESTINATION ${KDE_INSTALL_PLUGINDIR} )
Qt5::Core
KF5::CoreAddons
KF5::I18n
KF5::QuickAddons
KF5::Service
KF5::KIOWidgets
KF5::ConfigGui
)
########### install files ###############
kcoreaddons_desktop_to_json(kcm_componentchooser "package/metadata.desktop")
install( FILES componentchooser.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} )
install(FILES browser_settings.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR})
install(FILES terminal_settings.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR})
install(TARGETS kcm_componentchooser DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms)
install(FILES package/metadata.desktop RENAME componentchooser.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR})
kpackage_install_package(package kcm_componentchooser kcms)
/***************************************************************************
componentchooser.cpp - description
-------------------
copyright : (C) 2002 by Joseph Wenninger <jowenn@kde.org>
copyright : (C) 2020 by Méven Car <meven.car@enioka.com>
***************************************************************************/
/***************************************************************************
* Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org> *
* Copyright (C) 2020 Méven Car <meven.car@kdemail.net> *
* Copyright (C) 2020 Tobias Fella <fella@posteo.de> *
* *
* 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 *
* 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) 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; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA *
***************************************************************************/
#include "componentchooser.h"
#include "componentchooserbrowser.h"
#include "componentchooseremail.h"
#include "componentchooserfilemanager.h"
#ifdef Q_OS_UNIX
#include "componentchooserterminal.h"
#endif
#include <QDir>
#include <QDBusConnection>
#include <QDBusMessage>
#include <KConfig>
#include <KApplicationTrader>
#include <KConfigGroup>
#include <KGlobal>
#include <QLabel>
#include <KLocalizedString>
#include <KBuildSycocaProgressDialog>
#include <KOpenWithDialog>
#include <KQuickAddons/ConfigModule>
#include <KService>
#include <KSharedConfig>
ComponentChooser::ComponentChooser(QObject *parent, const QString &mimeType, const QString &type, const QString &defaultApplication, const QString &dialogText)
: QObject(parent)
, m_mimeType(mimeType)
, m_type(type)
, m_defaultApplication(defaultApplication)
, m_dialogText(dialogText)
{
}
ComponentChooser::ComponentChooser(QWidget *parent):
QWidget(parent), Ui::ComponentChooser_UI()
void ComponentChooser::defaults()
{
setupUi(this);
const QString directory = QStandardPaths::locate(
QStandardPaths::GenericDataLocation, QStringLiteral("kcm_componentchooser"), QStandardPaths::LocateDirectory);
QStringList services;
const QDir dir(directory);
for (const auto &f: dir.entryList(QStringList("*.desktop"))) {
services += dir.absoluteFilePath(f);
if (m_defaultIndex) {
select(*m_defaultIndex);
}
}
for (const QString &service : qAsConst(services))
{
KConfig cfg(service, KConfig::SimpleConfig);
KConfigGroup cg = cfg.group(QByteArray());
// fill the form layout
const auto name = cg.readEntry("Name", i18n("Unknown"));
CfgPlugin *loadedConfigWidget = loadConfigWidget(cfg.group(QByteArray()).readEntry("configurationType"));
QLabel *label = new QLabel(i18nc("The label for the combobox: browser, terminal emulator...)", "%1:", name), this);
label->setToolTip(cfg.group(QByteArray()).readEntry("Comment", QString()));
void ComponentChooser::load()
{
m_applications.clear();
formLayout->addRow(label, loadedConfigWidget);
bool preferredServiceAdded = false;
connect(loadedConfigWidget, &CfgPlugin::changed, this, &ComponentChooser::emitChanged);
KService::Ptr preferredService = KApplicationTrader::preferredService(m_mimeType);
configWidgetMap.insert(service, loadedConfigWidget);
KApplicationTrader::query([&preferredServiceAdded, preferredService, this](const KService::Ptr &service) {
if (service->exec().isEmpty() || !service->categories().contains(m_type) || (!service->serviceTypes().contains(m_mimeType))) {
return false;
}
QVariantMap application;
application["name"] = service->name();
application["icon"] = service->icon();
application["storageId"] = service->storageId();
m_applications += application;
if ((preferredService && preferredService->storageId() == service->storageId())) {
m_index = m_applications.length() - 1;
preferredServiceAdded = true;
}
if (service->storageId() == m_defaultApplication) {
m_defaultIndex = m_applications.length() - 1;
}
return false;
});
if (preferredService && !preferredServiceAdded) {
// standard application was specified by the user
QVariantMap application;
application["name"] = preferredService->name();
application["icon"] = preferredService->icon();
application["storageId"] = preferredService->storageId();
m_applications += application;
m_index = m_applications.length() - 1;
}
QVariantMap application;
application["name"] = i18n("Other...");
application["icon"] = QStringLiteral("application-x-shellscript");
application["storageId"] = QLatin1String("");
m_applications += application;
if (m_index == -1) {
m_index = 0;
}
m_previousApplication = m_applications[m_index].toMap()["storageId"].toString();
Q_EMIT applicationsChanged();
Q_EMIT indexChanged();
}
CfgPlugin *ComponentChooser::loadConfigWidget(const QString &cfgType)
void ComponentChooser::select(int index)
{
CfgPlugin *loadedConfigWidget = nullptr;
if (cfgType == QLatin1String("internal_email")) {
loadedConfigWidget = new CfgEmailClient(this);
}
#ifdef Q_OS_UNIX
else if (cfgType == QLatin1String("internal_terminal")) {
loadedConfigWidget = new CfgTerminalEmulator(this);
}
#endif
else if (cfgType == QLatin1String("internal_filemanager")) {
loadedConfigWidget = new CfgFileManager(this);
} else if (cfgType == QLatin1String("internal_browser")) {
loadedConfigWidget = new CfgBrowser(this);
if (m_index == index) {
return;
}
if (index == m_applications.length() - 1) {
KOpenWithDialog *dialog = new KOpenWithDialog(QList<QUrl>(), m_mimeType, m_dialogText, QString());
dialog->setSaveNewApplications(true);
dialog->setAttribute(Qt::WA_DeleteOnClose);
connect(dialog, &KOpenWithDialog::finished, this, [this, dialog] (int result) {
if (result == QDialog::Rejected) {
Q_EMIT indexChanged();
return;
}
const KService::Ptr service = dialog->service();
// Check if the selected application is already in the list
for (int i = 0; i < m_applications.length(); i++) {
if (m_applications[i].toMap()["storageId"] == service->storageId()) {
m_index = i;
Q_EMIT indexChanged();
return;
}
}
const QString icon = !service->icon().isEmpty() ? service->icon() : QStringLiteral("application-x-shellscript");
QVariantMap application;
application["name"] = service->name();
application["icon"] = icon;
application["storageId"] = service->storageId();
application["execLine"] = service->exec();
m_applications.insert(m_applications.length() - 1, application);
m_index = m_applications.length() - 2;
Q_EMIT applicationsChanged();
Q_EMIT indexChanged();
});
dialog->open();
} else {
Q_ASSERT_X(false, "loadConfigWidget", "cfgType no supported");
m_index = index;
}
loadedConfigWidget->setSizeAdjustPolicy(QComboBox::AdjustToContents);
loadedConfigWidget->setMinimumContentsLength(18);
return loadedConfigWidget;
Q_EMIT indexChanged();
}
void ComponentChooser::emitChanged()
void ComponentChooser::saveMimeTypeAssociation(const QString &mime, const QString &storageId)
{
bool somethingChanged = false;
bool isDefaults = true;
// check if another plugin has changed and default status
for (CfgPlugin *plugin: qAsConst(configWidgetMap)) {
somethingChanged |= plugin->hasChanged();
isDefaults &= plugin->isDefaults();
KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation);
if (profile->isConfigWritable(true)) {
KConfigGroup defaultApp(profile, "Default Applications");
defaultApp.writeXdgListEntry(mime, QStringList(storageId));
KConfigGroup addedApps(profile, QStringLiteral("Added Associations"));
QStringList apps = addedApps.readXdgListEntry(mime);
apps.removeAll(storageId);
apps.prepend(storageId); // make it the preferred app, i.e first in list
addedApps.writeXdgListEntry(mime, apps);
profile->sync();
QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.klauncher5"), QStringLiteral("/KLauncher"), QStringLiteral("org.kde.KLauncher"), QStringLiteral("reparseConfiguration"));
QDBusConnection::sessionBus().send(message);
}
emit changed(somethingChanged);
emit defaulted(isDefaults);
m_previousApplication = m_applications[m_index].toMap()["storageId"].toString();
}
ComponentChooser::~ComponentChooser()
bool ComponentChooser::isDefaults() const
{
for (QWidget *configWidget : qAsConst(configWidgetMap)) {
delete configWidget;
}
}
void ComponentChooser::load() {
for (auto it = configWidgetMap.constBegin(); it != configWidgetMap.constEnd(); ++it) {
const auto service = it.key();
const auto widget = it.value();
CfgPlugin *plugin = dynamic_cast<CfgPlugin*>(widget);
if (plugin) {
KConfig cfg(service, KConfig::SimpleConfig);
plugin->load( &cfg );
}
}
return !m_defaultIndex.has_value() || *m_defaultIndex == m_index;
}
void ComponentChooser::save() {
for (auto it = configWidgetMap.constBegin(); it != configWidgetMap.constEnd(); ++it) {
const auto service = it.key();
const auto widget = it.value();
CfgPlugin *plugin = dynamic_cast<CfgPlugin*>(widget);
if (plugin) {
KConfig cfg(service, KConfig::SimpleConfig);
plugin->save( &cfg );
}
}
// refresh System configuration cache
KBuildSycocaProgressDialog::rebuildKSycoca(this);
}
void ComponentChooser::restoreDefault() {
for (CfgPlugin *plugin : qAsConst(configWidgetMap)) {
plugin->defaults();
emitChanged();
}
bool ComponentChooser::isSaveNeeded() const
{
return !m_applications.isEmpty() && (m_previousApplication != m_applications[m_index].toMap()["storageId"].toString());
}
// vim: sw=4 ts=4 noet
This diff is collapsed.
/***************************************************************************
componentchooser.h - description
-------------------
copyright : (C) 2002 by Joseph Wenninger <jowenn@kde.org>
copyright : (C) 2020 by Méven Car <meven.car@enioka.com>
***************************************************************************/
/***************************************************************************
* Copyright (C) 2020 Tobias Fella <fella@posteo.de> *
* *
* 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 *
* 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) 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; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA *
***************************************************************************/
#ifndef _COMPONENTCHOOSER_H_
#define _COMPONENTCHOOSER_H_
#include "ui_componentchooser_ui.h"
#include <QHash>
#ifndef COMPONENTCHOOSER_H
#define COMPONENTCHOOSER_H
#include <QComboBox>
#include <QString>
#include <QVariant>
#include <KService>
#include <optional>
class QWidget;
class KConfig;
#include <KLocalizedString>
class CfgPlugin : public QComboBox
class ComponentChooser : public QObject
{
Q_OBJECT
Q_PROPERTY(QVariantList applications MEMBER m_applications NOTIFY applicationsChanged)
Q_PROPERTY(int index MEMBER m_index NOTIFY indexChanged)
public:
CfgPlugin(QWidget *parent): QComboBox(parent) {}
virtual ~CfgPlugin(){}
virtual void load(KConfig *cfg)=0;
virtual void save(KConfig *cfg)=0;
bool hasChanged() const
{
return count() > 1 && m_currentIndex != currentIndex();
}
ComponentChooser(QObject *parent, const QString &mimeType, const QString &type, const QString &defaultApplication, const QString &dialogText);
void defaults()
{
if (m_defaultIndex != -1) {
setCurrentIndex(m_defaultIndex);
}
}
void defaults();
virtual void load();
bool isDefaults() const;
bool isSaveNeeded() const;
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;
}
Q_INVOKABLE void select(int index);
bool isDefaults() const
{
return m_defaultIndex == -1 || m_defaultIndex == currentIndex();
}
Q_SIGNALS:
void changed(bool);
virtual void save() = 0;
void saveMimeTypeAssociation(const QString &mime, const QString &storageId);
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<QString, CfgPlugin*> configWidgetMap;
CfgPlugin *loadConfigWidget(const QString &cfgType);
protected Q_SLOTS:
void emitChanged();
Q_SIGNALS:
void changed(bool);
void defaulted(bool);
};
void applicationsChanged();
void indexChanged();
protected:
QVariantList m_applications;
int m_index;
std::optional<int> m_defaultIndex;
QString m_mimeType;
QString m_type;
QString m_defaultApplication;
QString m_previousApplication;
QString m_dialogText;
};
#endif
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ComponentChooser_UI</class>
<widget class="QWidget" name="ComponentChooser_UI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>427</width>
<height>192</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QFormLayout" name="formLayout"/>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Preferred</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
/***************************************************************************
componentchooserbrowser.cpp
-------------------
copyright : (C) 2002 by Joseph Wenninger <jowenn@kde.org>
copyright : (C) 2020 by Méven Car <meven.car@enioka.com>
***************************************************************************/