diff --git a/kcms/keys/CMakeLists.txt b/kcms/keys/CMakeLists.txt --- a/kcms/keys/CMakeLists.txt +++ b/kcms/keys/CMakeLists.txt @@ -1,57 +1,57 @@ # KI18N Translation Domain for this library -add_definitions(-DTRANSLATION_DOMAIN=\"kcmkeys\") +add_definitions(-DTRANSLATION_DOMAIN=\"kcm_keys\") -########### next target ############### - -set(kcm_keys_PART_SRCS - kglobalshortcutseditor.cpp - globalshortcuts.cpp - select_scheme_dialog.cpp - kglobalaccel_interface.cpp - kglobalaccel_component_interface.cpp - export_scheme_dialog.cpp - ) - -ki18n_wrap_ui( kcm_keys_PART_SRCS - export_scheme_dialog.ui - kglobalshortcutseditor.ui - select_application.ui - select_scheme_dialog.ui ) +set(kcm_keys_SRCS + kcm_keys.cpp + filteredmodel.cpp + shortcutsmodel.cpp + ) set(kglobalaccel_xml ${KGLOBALACCEL_DBUS_INTERFACES_DIR}/kf5_org.kde.KGlobalAccel.xml) -set_source_files_properties(${kglobalaccel_xml} PROPERTIES INCLUDE "kglobalshortcutinfo.h") -qt5_add_dbus_interface(kdeui_LIB_SRCS ${kglobalaccel_xml} kglobalaccel_interface ) +set_source_files_properties(${kglobalaccel_xml} PROPERTIES + INCLUDE "kglobalshortcutinfo.h" + NO_NAMESPACE TRUE + CLASSNAME "KGlobalAccelInterface" +) +qt5_add_dbus_interface(kcm_keys_SRCS ${kglobalaccel_xml} kglobalaccel_interface) set(kglobalaccel_component_xml ${KGLOBALACCEL_DBUS_INTERFACES_DIR}/kf5_org.kde.kglobalaccel.Component.xml) -set_source_files_properties(${kglobalaccel_component_xml} PROPERTIES INCLUDE "kglobalshortcutinfo.h") -qt5_add_dbus_interface(kdeui_LIB_SRCS ${kglobalaccel_component_xml} kglobalaccel_component_interface ) +set_source_files_properties(${kglobalaccel_component_xml} PROPERTIES + INCLUDE "kglobalshortcutinfo.h" + NO_NAMESPACE TRUE + CLASSNAME "KGlobalAccelComponentInterface" +) +qt5_add_dbus_interface(kcm_keys_SRCS ${kglobalaccel_component_xml} kglobalaccel_component_interface) + + +ecm_qt_declare_logging_category(kcm_keys_SRCS + HEADER kcmkeys_debug.h + IDENTIFIER KCMKEYS + CATEGORY_NAME org.kde.kcm_keys + EXPORT KCMKEYS + DESCRIPTION "System Settings - Global Shortcuts" +) + +ecm_qt_install_logging_categories( + EXPORT KCMKEYS + DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} +) + + +add_library(kcm_keys MODULE ${kcm_keys_SRCS}) -add_library(kcm_keys MODULE ${kcm_keys_PART_SRCS}) target_link_libraries(kcm_keys Qt5::DBus - KF5::KCMUtils KF5::GlobalAccel KF5::I18n KF5::KIOWidgets - KF5::XmlGui - KF5::ItemModels - KF5::ItemViews + KF5::QuickAddons ) -install(TARGETS kcm_keys DESTINATION ${KDE_INSTALL_PLUGINDIR} ) - - -########### install files ############### +kcoreaddons_desktop_to_json(kcm_keys "kcm_keys.desktop") -install( FILES keys.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) -install( FILES - schemes/kde3.kksrc - schemes/kde4.kksrc - schemes/mac4.kksrc - schemes/unix3.kksrc - schemes/win3.kksrc - schemes/win4.kksrc - schemes/wm3.kksrc - DESTINATION ${KDE_INSTALL_DATADIR}/kcmkeys ) +install(FILES kcm_keys.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) +install(TARGETS kcm_keys DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms) +kpackage_install_package(package kcm_keys kcms) diff --git a/kcms/keys/ChangeLog b/kcms/keys/ChangeLog deleted file mode 100644 --- a/kcms/keys/ChangeLog +++ /dev/null @@ -1,29 +0,0 @@ -2005-09-30 Benjamin Meyer - * Removed Command Shortcuts, it is already in kmenuedit - -1999-08-19 Duncan Haldane - * removed left-over commented out code from change - decribed below, and adjusted help doc names to - index-1.html - -1999-02-28 Duncan Haldane - * commented out those unnecessary debug calls. - in keyconfig.cpp - -1998-12-19 Duncan Haldane - * Converted global.cpp, global.h to keyconfig.cpp, - keyconfig.h, that can now be used to configure both - the standard keys and the global keys - in the same sophisticated manner as - global.cpp did for just the global keys. - * converted main.cpp to use keyconfig.cpp rather than - global.cpp and standard.cpp for standard and globall - key configuration. KGlobalConfig and KStdConfig disappear. - (KGlobalConfig is renamed to KKeyConfig) - * appropriate changes to Makefile.am. - * standard key binding are now #include'd from - stdbindings.cpp - * standard.cpp, standard.h are left here for now. - The entries that used to use them in main.cpp and - Makefile.am are just commented out for now. - diff --git a/kcms/keys/Messages.sh b/kcms/keys/Messages.sh --- a/kcms/keys/Messages.sh +++ b/kcms/keys/Messages.sh @@ -1,7 +1,2 @@ #! /usr/bin/env bash -# customkeys=`grep "^.include .\.\." keyconfig.cpp | sed -e "s#.*\"\(.*\)\"#\1#"` -# $XGETTEXT *.cpp $customkeys -o $podir/kcmkeys.pot -$EXTRACTRC *.ui >> rc.cpp -$XGETTEXT *.cpp -o $podir/kcmkeys.pot -rm -f rc.cpp - +$XGETTEXT `find . -name "*.cpp" -o -name "*.qml"` -o $podir/kcm_keys.pot diff --git a/kcms/keys/README b/kcms/keys/README deleted file mode 100644 --- a/kcms/keys/README +++ /dev/null @@ -1,7 +0,0 @@ -CHANGES V0.2 -- Global keys stored by default in ~/.kde/share/config/kdeglobals - [Global Keys] group -- KKeyDialog checks new key choices against exising bindings for the widget -and against entries in .kderc [Global Keys] -- kcmkeys now has two standard kcontrol pages - one for standard desktop -accelerators and one for global keybindings. diff --git a/kcms/keys/export_scheme_dialog.h b/kcms/keys/export_scheme_dialog.h deleted file mode 100644 --- a/kcms/keys/export_scheme_dialog.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef EXPORT_SCHEME_DIALOG_H -#define EXPORT_SCHEME_DIALOG_H -/** - * Copyright (C) 2009 Michael Jansen - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include "ui_export_scheme_dialog.h" - -#include -#include - -/** - * @author Michael Jansen - */ -class ExportSchemeDialog : public QDialog - { - Q_OBJECT - -public: - - ExportSchemeDialog (QStringList components, QWidget *parent=nullptr); - - ~ExportSchemeDialog() override; - - // Get the list of currently selected components - QStringList selectedComponents() const; - -private: - - Ui::ExportSchemeDialog ui; - - // list of components to choose from - QStringList mComponents; - - // list of buttons for the components - QButtonGroup mButtons; - - }; // ExportSchemeDialog - - - - -#endif /* EXPORT_SCHEME_DIALOG_H */ - diff --git a/kcms/keys/export_scheme_dialog.cpp b/kcms/keys/export_scheme_dialog.cpp deleted file mode 100644 --- a/kcms/keys/export_scheme_dialog.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (C) 2009 Michael Jansen - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include "export_scheme_dialog.h" - -#include -#include -#include -#include -#include - - -ExportSchemeDialog::ExportSchemeDialog(QStringList components, QWidget *parent) - : QDialog(parent), - ui(), - mComponents(components) -{ - QWidget *w = new QWidget(this); - ui.setupUi(w); - QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->addWidget(w); - - // We allow to check more than one button - mButtons.setExclusive(false); - - // A grid layout for the buttons - QGridLayout *vb = new QGridLayout(this); - - int item=0; - Q_FOREACH(QString component, mComponents) - { - QCheckBox *cb = new QCheckBox(component); - vb->addWidget(cb, item / 2, item % 2); - mButtons.addButton(cb, item); - ++item; - } - - ui.components->setLayout(vb); - - QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); - QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); - okButton->setDefault(true); - okButton->setShortcut(Qt::CTRL | Qt::Key_Return); - connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - mainLayout->addWidget(buttonBox); -} - - -ExportSchemeDialog::~ExportSchemeDialog() -{} - - -QStringList ExportSchemeDialog::selectedComponents() const -{ - QStringList rc; - Q_FOREACH(QAbstractButton const *button, mButtons.buttons()) - { - if (button->isChecked()) - { - // Remove the '&' added by KAcceleratorManager magically - rc.append(KLocalizedString::removeAcceleratorMarker(button->text())); - } - } - return rc; -} - - -#include "moc_export_scheme_dialog.cpp" diff --git a/kcms/keys/export_scheme_dialog.ui b/kcms/keys/export_scheme_dialog.ui deleted file mode 100644 --- a/kcms/keys/export_scheme_dialog.ui +++ /dev/null @@ -1,35 +0,0 @@ - - - ExportSchemeDialog - - - - 0 - 0 - 400 - 300 - - - - - - - Select the Components to Export - - - Qt::AlignCenter - - - - - - - Components - - - - - - - - diff --git a/kcms/keys/filteredmodel.h b/kcms/keys/filteredmodel.h new file mode 100644 --- /dev/null +++ b/kcms/keys/filteredmodel.h @@ -0,0 +1,46 @@ +/* + * Copyright 2020 David Redondo + * + * 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 any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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, see . + */ + +#ifndef FILTEREDMODEL_H +#define FILTEREDMODEL_H + +#include + +class FilteredShortcutsModel : public QSortFilterProxyModel +{ + Q_OBJECT + + Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged) + +public: + explicit FilteredShortcutsModel(QObject *parent); + + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + + QString filter() const; + void setFilter(const QString &filter); + +Q_SIGNALS: + void filterChanged(); + +private: + QString m_filter; +}; +#endif diff --git a/kcms/keys/filteredmodel.cpp b/kcms/keys/filteredmodel.cpp new file mode 100644 --- /dev/null +++ b/kcms/keys/filteredmodel.cpp @@ -0,0 +1,82 @@ +/* + * Copyright 2020 David Redondo + * + * 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 any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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, see . + */ + +#include "filteredmodel.h" + +#include + +#include "shortcutsmodel.h" + +FilteredShortcutsModel::FilteredShortcutsModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + setRecursiveFilteringEnabled(true); +} + +bool FilteredShortcutsModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + if (m_filter.isEmpty()) { + return true; + } + + const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); + const bool displayMatches = index.data(Qt::DisplayRole).toString().contains(m_filter, Qt::CaseInsensitive); + if (!source_parent.isValid() || displayMatches) { + return displayMatches; + } + + if (index.parent().data(Qt::DisplayRole).toString().contains(m_filter, Qt::CaseInsensitive)) { + return true; + } + + const auto &defaultShortcuts = index.data(ShortcutsModel::DefaultShortcutsRole).value>(); + for (const auto& shortcut : defaultShortcuts) { + if (shortcut.toString(QKeySequence::NativeText).contains(m_filter, Qt::CaseInsensitive)) { + return true; + } + } + + const auto &shortcuts = index.data(ShortcutsModel::CustomShortcutsRole).value>(); + for (const auto& shortcut : shortcuts) { + if (shortcut.toString(QKeySequence::NativeText).contains(m_filter, Qt::CaseInsensitive)) { + return true; + } + } + return false; + +} + +QString FilteredShortcutsModel::filter() const +{ + return m_filter; +} + +void FilteredShortcutsModel::setFilter(const QString &filter) +{ + if (filter == m_filter) { + return; + } + m_filter = filter; + invalidateFilter(); + emit filterChanged(); +} + + + diff --git a/kcms/keys/globalshortcuts.h b/kcms/keys/globalshortcuts.h deleted file mode 100644 --- a/kcms/keys/globalshortcuts.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2007 Andreas Pakulat - * Copyright 2008 Michael Jansen - * - * 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) 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 GLOBAL_SHORTCUTS_H -#define GLOBAL_SHORTCUTS_H - -#include -#include - -class KGlobalShortcutsEditor; - -class GlobalShortcutsModule : public KCModule -{ - Q_OBJECT -public: - GlobalShortcutsModule(QWidget *parent, const QVariantList &args); - ~GlobalShortcutsModule() override; - - void save() override; - void load() override; - void defaults() override; - -private: - KGlobalShortcutsEditor *editor = nullptr; -}; - - -#endif diff --git a/kcms/keys/globalshortcuts.cpp b/kcms/keys/globalshortcuts.cpp deleted file mode 100644 --- a/kcms/keys/globalshortcuts.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2007 Andreas Pakulat - * Copyright 2008 Michael Jansen - * - * 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) 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 "globalshortcuts.h" - -#include "kglobalshortcutseditor.h" - -#include -#include -#include - - -#include - - -K_PLUGIN_FACTORY(GlobalShortcutsModuleFactory, registerPlugin();) - -GlobalShortcutsModule::GlobalShortcutsModule(QWidget *parent, const QVariantList &args) - : KCModule(parent, args), - editor(nullptr) -{ - KCModule::setButtons(KCModule::Buttons(KCModule::Default | KCModule::Apply | KCModule::Help)); - - - // Create the kglobaleditor - editor = new KGlobalShortcutsEditor(this, KShortcutsEditor::GlobalAction); - connect(editor, SIGNAL(changed(bool)), this, SIGNAL(changed(bool))); - - // Layout the hole bunch - QVBoxLayout *global = new QVBoxLayout; - global->addWidget(editor); - setLayout(global); -} - -GlobalShortcutsModule::~GlobalShortcutsModule() -{} - - -void GlobalShortcutsModule::load() -{ - editor->load(); -} - - -void GlobalShortcutsModule::defaults() -{ - switch (KMessageBox::questionYesNoCancel( - this, - i18n("You are about to reset all shortcuts to their default values."), - i18n("Reset to defaults"), - KGuiItem(i18n("Current Component")), - KGuiItem(i18n("All Components")))) - { - case KMessageBox::Yes: - editor->defaults(KGlobalShortcutsEditor::CurrentComponent); - break; - - case KMessageBox::No: - editor->defaults(KGlobalShortcutsEditor::AllComponents); - break; - - default: - return; - } -} - - -void GlobalShortcutsModule::save() -{ - editor->save(); -} - - -#include "globalshortcuts.moc" diff --git a/kcms/keys/kcm_keys.h b/kcms/keys/kcm_keys.h new file mode 100644 --- /dev/null +++ b/kcms/keys/kcm_keys.h @@ -0,0 +1,74 @@ +/* + * Copyright 2020 David Redondo + * + * 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 any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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, see . + */ + +#ifndef KCM_KEYS_H +#define KCM_KEYS_H + +#include + +#include + +class QDBusError; + +class FilteredShortcutsModel; +class KGlobalAccelInterface; +class ShortcutsModel; + +class KCMKeys : public KQuickAddons::ConfigModule +{ + Q_OBJECT + + Q_PROPERTY(ShortcutsModel *shortcutsModel READ shortcutsModel CONSTANT) + Q_PROPERTY(FilteredShortcutsModel *filteredModel READ filteredModel CONSTANT) + Q_PROPERTY(QString lastError READ lastError NOTIFY errorOccured) + +public: + KCMKeys(QObject *parent, const QVariantList &args); + + void defaults() override; + void load() override; + void save() override; + + Q_INVOKABLE void writeScheme(const QUrl &url); + Q_INVOKABLE void loadScheme(const QUrl &url); + Q_INVOKABLE QVariantList defaultSchemes() const; + + Q_INVOKABLE void addApplication(QQuickItem *ctx); + Q_INVOKABLE void removeComponent(int index); + + Q_INVOKABLE QString keySequenceToString(const QKeySequence &keySequence) const; + + ShortcutsModel* shortcutsModel() const; + FilteredShortcutsModel* filteredModel() const; + QString lastError() const; + +Q_SIGNALS: + void errorOccured(); + +private: + void setError(const QString &errorMessage); + + QString m_lastError; + ShortcutsModel *m_shortcutsModel; + FilteredShortcutsModel *m_fileredModel; + KGlobalAccelInterface *m_globalAccelInterface; +}; + +#endif diff --git a/kcms/keys/kcm_keys.cpp b/kcms/keys/kcm_keys.cpp new file mode 100644 --- /dev/null +++ b/kcms/keys/kcm_keys.cpp @@ -0,0 +1,196 @@ +/* + * Copyright 2020 David Redondo + * + * 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 any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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, see . + */ + +#include "kcm_keys.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "filteredmodel.h" +#include "kcmkeys_debug.h" +#include "shortcutsmodel.h" + +K_PLUGIN_CLASS_WITH_JSON(KCMKeys, "kcm_keys.json") + +KCMKeys::KCMKeys(QObject *parent, const QVariantList &args) + : KQuickAddons::ConfigModule(parent, args) +{ + constexpr char uri[] = "org.kde.private.kcms.keys"; + qmlRegisterUncreatableType(uri, 2, 0, "ShortcutsModel", "Can't create ShortcutsModel"); + qmlRegisterAnonymousType(uri, 2); + qmlRegisterAnonymousType(uri, 2); + qmlProtectModule(uri, 2); + qDBusRegisterMetaType(); + qDBusRegisterMetaType>(); + qDBusRegisterMetaType>(); + KAboutData *about = new KAboutData(QStringLiteral("kcm_keys"), i18n("Global Shortcuts"), + QStringLiteral("2.0"), QString(), KAboutLicense::GPL); + about->addAuthor(i18n("David Redondo"), QString(), QStringLiteral("kde@david-redondo.de")); + setAboutData(about); + m_globalAccelInterface = new KGlobalAccelInterface(QStringLiteral("org.kde.kglobalaccel") + , QStringLiteral("/kglobalaccel"), QDBusConnection::sessionBus(), this); + if (!m_globalAccelInterface->isValid()) { + setError(i18n("Failed to communicate with global shortcuts daemon")); + qCCritical(KCMKEYS) << "Interface is not valid"; + if (m_globalAccelInterface->lastError().isValid()) { + qCCritical(KCMKEYS) << m_globalAccelInterface->lastError().name() << m_globalAccelInterface->lastError().message(); + } + } + m_shortcutsModel = new ShortcutsModel(m_globalAccelInterface, this); + m_fileredModel = new FilteredShortcutsModel(this); + m_fileredModel->setSourceModel(m_shortcutsModel); + connect(m_shortcutsModel, &QAbstractItemModel::dataChanged, this, [this] { + setNeedsSave(m_shortcutsModel->needsSave()); + setRepresentsDefaults(m_shortcutsModel->isDefault()); + }); + connect(m_shortcutsModel, &ShortcutsModel::errorOccured, this, &KCMKeys::setError); +} + +void KCMKeys::load() +{ + m_shortcutsModel->load(); + setRepresentsDefaults(m_shortcutsModel->isDefault()); + setNeedsSave(m_shortcutsModel->needsSave()); +} + +void KCMKeys::save() +{ + m_shortcutsModel->save(); +} + +void KCMKeys::defaults() +{ + m_shortcutsModel->defaults(); +} + +ShortcutsModel* KCMKeys::shortcutsModel() const +{ + return m_shortcutsModel; +} + +FilteredShortcutsModel* KCMKeys::filteredModel() const +{ + return m_fileredModel; +} + +void KCMKeys::setError(const QString &errorMessage) +{ + m_lastError = errorMessage; + emit this->errorOccured(); +} + +QString KCMKeys::lastError() const +{ + return m_lastError; +} + +void KCMKeys::writeScheme(const QUrl &url) +{ + const auto includedComponents = m_shortcutsModel->match(m_shortcutsModel->index(0, 0), ShortcutsModel::CheckedRole, true, -1); + qCDebug(KCMKEYS) << "Exporting to " << url.toLocalFile(); + KConfig file(url.toLocalFile(), KConfig::SimpleConfig); + for (const QModelIndex &componentIndex : includedComponents) { + KConfigGroup mainGroup(&file, componentIndex.data(ShortcutsModel::ComponentRole).toString()); + KConfigGroup group(&mainGroup, "Global Shortcuts"); + for (int i = 0; i < m_shortcutsModel->rowCount(componentIndex); ++i) { + const QModelIndex shortcutIndex = m_shortcutsModel->index(i, 0, componentIndex); + const auto activeShortcuts = shortcutIndex.data(ShortcutsModel::ActiveShortcutsRole).value>(); + const QList shortcutsList(activeShortcuts.cbegin(), activeShortcuts.cend()); + group.writeEntry(shortcutIndex.data(ShortcutsModel::ActionRole).toString(), QKeySequence::listToString(shortcutsList)); + } + } + file.sync(); +} + +void KCMKeys::loadScheme(const QUrl &url) +{ + qCDebug(KCMKEYS) << "Loading scheme" << url.toLocalFile(); + KConfig file(url.toLocalFile(), KConfig::SimpleConfig); + m_shortcutsModel->setShortcuts(file); +} + +QVariantList KCMKeys::defaultSchemes() const +{ + QVariantList schemes; + const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, + QStringLiteral("kcmkeys"), QStandardPaths::LocateDirectory); + for (const QString &dir : dirs) { + const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.kksrc")); + for (const QString &file : fileNames) { + const QString path = dir + QLatin1Char('/') + file; + KConfig scheme(path, KConfig::SimpleConfig); + const QString name = KConfigGroup(&scheme, "Settings").readEntry("Name", file); + schemes.append(QVariantMap({{"name", name}, {"url", QUrl::fromLocalFile(path)}})); + } + } + return schemes; +} + +void KCMKeys::addApplication(QQuickItem *ctx) +{ + auto dialog = new KOpenWithDialog; + if (ctx && ctx->window()) { + dialog->winId(); // so it creates windowHandle + dialog->windowHandle()->setTransientParent(QQuickRenderControl::renderWindowFor(ctx->window())); + dialog->setWindowModality(Qt::WindowModal); + } + dialog->hideRunInTerminal(); + dialog->open(); + connect(dialog, &KOpenWithDialog::finished, this, [this, dialog] (int result) { + if (result == QDialog::Accepted && dialog->service()) { + const KService::Ptr service = dialog->service(); + const QString desktopFileName = service->desktopEntryName() + ".desktop"; + if (m_shortcutsModel->match(m_shortcutsModel->index(0, 0), ShortcutsModel::ComponentRole, desktopFileName).isEmpty()) { + const QString destination = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/kglobalaccel/"; + QFile::copy(service->entryPath(), destination + desktopFileName); + m_shortcutsModel->addApplication(desktopFileName, service->name()); + } else { + qCDebug(KCMKEYS) << "Already have component" << service->storageId(); + } + } + dialog->deleteLater(); + }); +} + +void KCMKeys::removeComponent(int index) +{ + const QModelIndex modelIndex = m_fileredModel->mapToSource(m_fileredModel->index(index, 0)); + m_shortcutsModel->removeComponent(modelIndex); +} + +QString KCMKeys::keySequenceToString(const QKeySequence &keySequence) const +{ + return keySequence.toString(QKeySequence::NativeText); +} + +#include "kcm_keys.moc" diff --git a/kcms/keys/keys.desktop b/kcms/keys/kcm_keys.desktop rename from kcms/keys/keys.desktop rename to kcms/keys/kcm_keys.desktop --- a/kcms/keys/keys.desktop +++ b/kcms/keys/kcm_keys.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Exec=kcmshell5 keys +Exec=kcmshell5 kcm_keys Icon=preferences-desktop-keyboard-shortcut Type=Service X-KDE-ServiceTypes=KCModule diff --git a/kcms/keys/kglobalshortcutseditor.h b/kcms/keys/kglobalshortcutseditor.h deleted file mode 100644 --- a/kcms/keys/kglobalshortcutseditor.h +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2008 Michael Jansen - * - * 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) 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 KGLOBALSHORTCUTSEDITOR_H -#define KGLOBALSHORTCUTSEDITOR_H - -#include - -#include "kshortcutseditor.h" - -class KActionCollection; -class KConfig; -class QDBusObjectPath; - -/** - * Combine a KShortcutsEditor with a KComboBox. - * - * @see KShortcutsEditor - * @author Michael Jansen - */ -class KGlobalShortcutsEditor : public QWidget -{ - Q_OBJECT - -public: - - /** - * Constructor - * - * @param parent parent widget - * @param actionTypes action types - */ - KGlobalShortcutsEditor(QWidget *parent, - KShortcutsEditor::ActionTypes actionTypes = KShortcutsEditor::AllActions); - ~KGlobalShortcutsEditor() override; - - /** - * Insert an action collection, i.e. add all it's actions to the ones already associated - * with the KShortcutsEditor object. - * - * @param collection the collection to add - * @param path DBus path to the component - * @param id identifier for the component - * @param name name for the subtree in the component - */ - void addCollection(KActionCollection *, const QDBusObjectPath &path, const QString &id, const QString &name); - - /** - * Clear all collections were currently hosting. Current changes are not - * undone? Do that before calling this method. - */ - void clear(); - - - /** - * Revert all changes made since the last save. - */ - void undo(); - - - /** - * Load the shortcuts from the configuration. - */ - void importConfiguration(KConfigBase *config); - - - /** - * Save the shortcuts to the configuration. - */ - void exportConfiguration(QStringList componentsFriendly, KConfig *config) const; - - - /** - * Are the unsaved changes? - */ - bool isModified() const; - - enum ComponentScope - { - AllComponents, - CurrentComponent - }; - -Q_SIGNALS: - - /** - * Indicate that state of the modules contents has changed. - */ - void changed(bool); - - -public Q_SLOTS: - - /** - * Activate the component \a component. - * - * @param component the component - */ - void activateComponent(const QString &component); - - /** - * Set all shortcuts to none. - */ - void clearConfiguration(); - - /** - * Load/Reload the global shortcuts - */ - void load(); - - /** - * Make the changes persistent. - * - * That's function is not really saving. Global shortcuts are saved immediately. This - * prevent the undo on deleting the editor. - */ - void save(); - - /** - * Set shortcuts to their default value; - */ - void defaults(ComponentScope scope); - - virtual void importScheme(); - virtual void exportScheme(); - -private Q_SLOTS: - void _k_key_changed(); - -private: - - friend class KGlobalShortcutsEditorPrivate; - class KGlobalShortcutsEditorPrivate; - KGlobalShortcutsEditorPrivate *const d; - - Q_DISABLE_COPY(KGlobalShortcutsEditor) -}; // class KGlobalShortcutsEditor - -#endif // KGLOBALSHORTCUTSEDITOR_H diff --git a/kcms/keys/kglobalshortcutseditor.cpp b/kcms/keys/kglobalshortcutseditor.cpp deleted file mode 100644 --- a/kcms/keys/kglobalshortcutseditor.cpp +++ /dev/null @@ -1,856 +0,0 @@ -/* - * Copyright 2008 Michael Jansen - * - * 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) 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 "kglobalshortcutseditor.h" - -#include "ui_kglobalshortcutseditor.h" -#include "ui_select_application.h" -#include "export_scheme_dialog.h" -#include "select_scheme_dialog.h" -#include "globalshortcuts.h" -#include "kglobalaccel_interface.h" -#include "kglobalaccel_component_interface.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -/* - * README - * - * This class was created because the kshortcutseditor class has some shortcomings. That class uses - * QTreeWidget and therefore makes it impossible for an outsider to switch the models. But the - * global shortcuts editor did that. Each global component ( kded, krunner, kopete ... ) was - * destined to be separately edited. If you selected another component the kshortcutseditor was - * cleared and refilled. But the items take care of undoing. Therefore when switching the component - * you lost the undo history. - * - * To solve that problem this class keeps one kshortcuteditor for each component. That is easier - * than rewrite that dialog to a model/view framework. - * - * It perfectly covers a bug of KExtedableItemDelegate when clearing and refilling the associated - * model. - */ - -class ComponentData - { - -public: - - ComponentData( - const QString &uniqueName, - const QDBusObjectPath &path, - KShortcutsEditor *_editor); - - ~ComponentData(); - - QString uniqueName() const; - KShortcutsEditor *editor(); - QDBusObjectPath dbusPath(); - -private: - - QString _uniqueName; - QDBusObjectPath _path; - QPointer _editor; - }; - - -ComponentData::ComponentData( - const QString &uniqueName, - const QDBusObjectPath &path, - KShortcutsEditor *editor) - : _uniqueName(uniqueName), - _path(path), - _editor(editor) - {} - - -ComponentData::~ComponentData() - { - delete _editor; _editor = nullptr; - } - - -QString ComponentData::uniqueName() const - { - return _uniqueName; - } - - -QDBusObjectPath ComponentData::dbusPath() - { - return _path; - } - - -KShortcutsEditor *ComponentData::editor() - { - return _editor; - } - - -class KGlobalShortcutsEditor::KGlobalShortcutsEditorPrivate -{ -public: - - KGlobalShortcutsEditorPrivate(KGlobalShortcutsEditor *q) - : q(q), - bus(QDBusConnection::sessionBus()) - {} - - //! Setup the gui - void initGUI(); - - //! Load the component at @a componentPath - bool loadComponent(const QDBusObjectPath &componentPath); - - //! Return the componentPath for component - QDBusObjectPath componentPath(const QString &componentUnique); - - //! Remove the component - void removeComponent(const QString &componentUnique); - - KGlobalShortcutsEditor *q; - Ui::KGlobalShortcutsEditor ui; - Ui::SelectApplicationDialog selectApplicationDialogUi; - QDialog *selectApplicationDialog = nullptr; - QStackedWidget *stack = nullptr; - KShortcutsEditor::ActionTypes actionTypes; - QHash components; - QDBusConnection bus; - QStandardItemModel *model = nullptr; - KCategorizedSortFilterProxyModel *proxyModel = nullptr; -}; - -void loadAppsCategory(KServiceGroup::Ptr group, QStandardItemModel *model, QStandardItem *item) -{ - if (group && group->isValid()) { - KServiceGroup::List list = group->entries(); - - for( KServiceGroup::List::ConstIterator it = list.constBegin(); - it != list.constEnd(); ++it) { - const KSycocaEntry::Ptr p = (*it); - - if (p->isType(KST_KService)) { - const KService::Ptr service(static_cast(p.data())); - - if (!service->noDisplay()) { - QString genericName = service->genericName(); - if (genericName.isNull()) { - genericName = service->comment(); - } - QString description; - if (!service->genericName().isEmpty() && service->genericName() != service->name()) { - description = service->genericName(); - } else if (!service->comment().isEmpty()) { - description = service->comment(); - } - - QStandardItem *subItem = new QStandardItem(QIcon::fromTheme(service->icon()), service->name()); - subItem->setData(service->entryPath()); - if (item) { - item->appendRow(subItem); - } else { - model->appendRow(subItem); - } - } - - } else if (p->isType(KST_KServiceGroup)) { - KServiceGroup::Ptr subGroup(static_cast(p.data())); - - if (!subGroup->noDisplay() && subGroup->childCount() > 0) { - if (item) { - loadAppsCategory(subGroup, model, item); - } else { - QStandardItem *subItem = new QStandardItem(QIcon::fromTheme(subGroup->icon()), subGroup->caption()); - model->appendRow(subItem); - loadAppsCategory(subGroup, model, subItem); - } - } - } - } - } -} - -void KGlobalShortcutsEditor::KGlobalShortcutsEditorPrivate::initGUI() -{ - ui.setupUi(q); - selectApplicationDialog = new QDialog(); - selectApplicationDialogUi.setupUi(selectApplicationDialog); - // Create a stacked widget. - stack = new QStackedWidget(q); - ui.currentComponentLayout->addWidget(stack); - //HACK to make those two un-alignable components, aligned - ui.componentLabel->setMinimumHeight(ui.lineEditSpacer->sizeHint().height()); - ui.lineEditSpacer->setVisible(false); - ui.addButton->setIcon(QIcon::fromTheme("list-add")); - ui.removeButton->setIcon(QIcon::fromTheme("list-remove")); - ui.components->setCategoryDrawer(new KCategoryDrawer(ui.components)); - ui.components->setModelColumn(0); - - // Build the menu - QMenu *menu = new QMenu(q); - menu->addAction( QIcon::fromTheme(QStringLiteral("document-import")), i18n("Import Scheme..."), q, &KGlobalShortcutsEditor::importScheme); - menu->addAction( QIcon::fromTheme(QStringLiteral("document-export")), i18n("Export Scheme..."), q, &KGlobalShortcutsEditor::exportScheme); - menu->addAction( i18n("Set All Shortcuts to None"), q, &KGlobalShortcutsEditor::clearConfiguration); - - connect(ui.addButton, &QToolButton::clicked, [this]() { - if (!selectApplicationDialogUi.treeView->model()) { - QSortFilterProxyModel *filterModel = new QSortFilterProxyModel(selectApplicationDialogUi.treeView); - filterModel->setRecursiveFilteringEnabled(true); - QStandardItemModel *appModel = new QStandardItemModel(selectApplicationDialogUi.treeView); - selectApplicationDialogUi.kfilterproxysearchline->setProxy(filterModel); - filterModel->setSourceModel(appModel); - appModel->setHorizontalHeaderLabels({i18n("Applications")}); - - loadAppsCategory(KServiceGroup::root(), appModel, nullptr); - - selectApplicationDialogUi.treeView->setModel(filterModel); - } - selectApplicationDialog->show(); - }); - - connect(selectApplicationDialog, &QDialog::accepted, [this]() { - if (selectApplicationDialogUi.treeView->selectionModel()->selectedIndexes().length() == 1) { - const QString desktopPath = selectApplicationDialogUi.treeView->model()->data(selectApplicationDialogUi.treeView->selectionModel()->selectedIndexes().first(), Qt::UserRole+1).toString(); - - if (!desktopPath.isEmpty() &&QFile::exists(desktopPath) ) { - const QString desktopFile = desktopPath.split(QLatin1Char('/')).last(); - - if (!desktopPath.isEmpty()) { - KDesktopFile sourceDF(desktopPath); - KDesktopFile *destinationDF = sourceDF.copyTo(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kglobalaccel/") + desktopFile); - qWarning()<sync(); - //TODO: a DBUS call to tell the daemon to refresh desktop files - - - // Create a action collection for our current component:context - KActionCollection *col = new KActionCollection(q, desktopFile); - - foreach(const QString &actionId, sourceDF.readActions()) { - - const QString friendlyName = sourceDF.actionGroup(actionId).readEntry(QStringLiteral("Name")); - QAction *action = col->addAction(actionId); - action->setProperty("isConfigurationAction", QVariant(true)); // see KAction::~KAction - action->setProperty("componentDisplayName", friendlyName); - action->setText(friendlyName); - - KGlobalAccel::self()->setShortcut(action, QList()); - - QStringList sequencesStrings = sourceDF.actionGroup(actionId).readEntry(QStringLiteral("X-KDE-Shortcuts"), QString()).split(QLatin1Char('/')); - QList sequences; - if (!sequencesStrings.isEmpty()) { - Q_FOREACH (const QString &seqString, sequencesStrings) { - sequences.append(QKeySequence(seqString)); - } - } - - if (!sequences.isEmpty()) { - KGlobalAccel::self()->setDefaultShortcut(action, sequences); - } - } - //Global launch action - { - const QString friendlyName = i18n("Launch %1", sourceDF.readName()); - QAction *action = col->addAction(QStringLiteral("_launch")); - action->setProperty("isConfigurationAction", QVariant(true)); // see KAction::~KAction - action->setProperty("componentDisplayName", friendlyName); - action->setText(friendlyName); - - KGlobalAccel::self()->setShortcut(action, QList()); - - QStringList sequencesStrings = sourceDF.desktopGroup().readEntry(QStringLiteral("X-KDE-Shortcuts"), QString()).split(QLatin1Char('/')); - QList sequences; - if (!sequencesStrings.isEmpty()) { - Q_FOREACH (const QString &seqString, sequencesStrings) { - sequences.append(QKeySequence(seqString)); - } - } - - if (!sequences.isEmpty()) { - KGlobalAccel::self()->setDefaultShortcut(action, sequences); - } - } - q->addCollection(col, QDBusObjectPath(), desktopFile, sourceDF.readName()); - } - } - } - }); - - connect(ui.removeButton, &QToolButton::clicked, [this]() { - //TODO: different way to remove components that are desktop files - //disabled desktop files need Hidden=true key - QString name = proxyModel->data(ui.components->currentIndex()).toString(); - QString componentUnique = components.value(name)->uniqueName(); - - // The confirmation text is different when the component is active - if (KGlobalAccel::isComponentActive(componentUnique)) { - if (KMessageBox::questionYesNo( - q, - i18n("Component '%1' is currently active. Only global shortcuts currently not active will be removed from the list.\n" - "All global shortcuts will reregister themselves with their defaults when they are next started.", componentUnique), - i18n("Remove component")) != KMessageBox::Yes) { - return; - } - } else { - if (KMessageBox::questionYesNo( - q, - i18n("Are you sure you want to remove the registered shortcuts for component '%1'? " - "The component and shortcuts will reregister themselves with their default settings" - " when they are next started.", - componentUnique), - i18n("Remove component")) != KMessageBox::Yes) { - return; - } - } - - // Initiate the removing of the component. - if (KGlobalAccel::cleanComponent(componentUnique)) { - - // Get the objectPath BEFORE we delete the source of it - QDBusObjectPath oPath = components.value(name)->dbusPath(); - // Remove the component from the gui - removeComponent(componentUnique); - - // Load it again - // ############# - if (loadComponent(oPath)) { - // Active it - q->activateComponent(name); - } - } - }); - - ui.menu_button->setMenu(menu); - - proxyModel = new KCategorizedSortFilterProxyModel(q); - proxyModel->setCategorizedModel(true); - model = new QStandardItemModel(0, 1, proxyModel); - proxyModel->setSourceModel(model); - proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); - ui.components->setModel(proxyModel); - - connect(ui.components->selectionModel(), - &QItemSelectionModel::currentChanged, - q, [this](const QModelIndex &index) { - QString name = proxyModel->data(index).toString(); - q->activateComponent(name); - }); -} - - -KGlobalShortcutsEditor::KGlobalShortcutsEditor(QWidget *parent, KShortcutsEditor::ActionTypes actionTypes) - : QWidget(parent), - d(new KGlobalShortcutsEditorPrivate(this)) -{ - d->actionTypes = actionTypes; - // Setup the ui - d->initGUI(); -} - - -KGlobalShortcutsEditor::~KGlobalShortcutsEditor() -{ - // Before closing the door, undo all changes - undo(); - delete d->selectApplicationDialog; - qDeleteAll(d->components); - delete d; -} - - -void KGlobalShortcutsEditor::activateComponent(const QString &component) -{ - QHash::Iterator iter = d->components.find(component); - if (iter == d->components.end()) { - Q_ASSERT(iter != d->components.end()); - return; - } else { - QModelIndexList results = d->proxyModel->match(d->proxyModel->index(0, 0), Qt::DisplayRole, component); - Q_ASSERT(!results.isEmpty()); - if (results.first().isValid()) { - // Known component. Get it. - d->ui.components->setCurrentIndex(results.first()); - d->stack->setCurrentWidget((*iter)->editor()); - } - } -} - - -void KGlobalShortcutsEditor::addCollection( - KActionCollection *collection, - const QDBusObjectPath &objectPath, - const QString &id, - const QString &friendlyName) -{ - KShortcutsEditor *editor; - // Check if this component is known - QHash::Iterator iter = d->components.find(friendlyName); - if (iter == d->components.end()) { - // Unknown component. Create an editor. - editor = new KShortcutsEditor(this, d->actionTypes); - d->stack->addWidget(editor); - - // try to find one appropriate icon - QIcon icon = QIcon::fromTheme(id); - if (icon.isNull()) { - KService::Ptr service = KService::serviceByStorageId(id); - if (service) { - icon = QIcon::fromTheme(service->icon()); - } - } - - // if NULL icon is returned, use the F.D.O "system-run" icon - if (icon.isNull()) { - icon = QIcon::fromTheme(QStringLiteral("system-run")); - } - - // Add to the component list - QStandardItem *item = new QStandardItem(icon, friendlyName); - if (id.endsWith(QLatin1String(".desktop"))) { - item->setData(i18n("Application Launchers"), KCategorizedSortFilterProxyModel::CategoryDisplayRole); - item->setData(0, KCategorizedSortFilterProxyModel::CategorySortRole); - } else { - item->setData(i18n("Other Shortcuts"), KCategorizedSortFilterProxyModel::CategoryDisplayRole); - item->setData(1, KCategorizedSortFilterProxyModel::CategorySortRole); - } - d->model->appendRow(item); - d->proxyModel->sort(0); - - // Add to our component registry - ComponentData *cd = new ComponentData(id, objectPath, editor); - d->components.insert(friendlyName, cd); - - connect(editor, &KShortcutsEditor::keyChange, this, &KGlobalShortcutsEditor::_k_key_changed); - } else { - // Known component. - editor = (*iter)->editor(); - } - - // Add the collection to the editor of the component - editor->addCollection(collection, friendlyName); - - if (d->proxyModel->rowCount() > -1) { - d->ui.components->setCurrentIndex(d->proxyModel->index(0, 0)); - const QString name = d->proxyModel->data(d->proxyModel->index(0, 0)).toString(); - activateComponent(name); - } -} - - -void KGlobalShortcutsEditor::clearConfiguration() -{ - const QString name = d->proxyModel->data(d->ui.components->currentIndex()).toString(); - d->components[name]->editor()->clearConfiguration(); -} - - -void KGlobalShortcutsEditor::defaults(ComponentScope scope) -{ - switch (scope) - { - case AllComponents: - for (ComponentData *cd : qAsConst(d->components)) { - // The editors are responsible for the reset - cd->editor()->allDefault(); - } - break; - - case CurrentComponent: { - const QString name = d->proxyModel->data(d->ui.components->currentIndex()).toString(); - // The editors are responsible for the reset - d->components[name]->editor()->allDefault(); - } - break; - - default: - Q_ASSERT(false); - }; -} - - -void KGlobalShortcutsEditor::clear() -{ - // Remove all components and their associated editors - qDeleteAll(d->components); - d->components.clear(); - d->model->clear(); -} - - -static bool compare(const QString &a, const QString &b) - { - return a.toLower().localeAwareCompare(b.toLower()) < 0; - } - - -void KGlobalShortcutsEditor::exportScheme() -{ - QStringList keys = d->components.keys(); - std::sort(keys.begin(), keys.end(), compare); - ExportSchemeDialog dia(keys); - - if (dia.exec() != KMessageBox::Ok) { - return; - } - - const QString filenameExtension = QStringLiteral("kksrc"); - QFileDialog dialog(this); - dialog.setAcceptMode(QFileDialog::AcceptSave); - dialog.setFileMode(QFileDialog::AnyFile); - dialog.setDirectoryUrl(QUrl()); - dialog.selectFile(QStringLiteral("global_shortcuts") + QStringLiteral(".") + filenameExtension); - dialog.setDefaultSuffix(QStringLiteral(".") + filenameExtension); - - if (dialog.exec() == QFileDialog::Accepted) { - const QUrl url = dialog.selectedUrls().constFirst(); - if (!url.isEmpty()) { - KConfig config(url.path(), KConfig::SimpleConfig); - // TODO: Bug ossi to provide a method for this - Q_FOREACH(const QString &group, config.groupList()) - { - // do not overwrite the Settings group. That makes it possible to - // update the standard scheme kksrc file with the editor. - if (group == QLatin1String("Settings")) continue; - config.deleteGroup(group); - } - exportConfiguration(dia.selectedComponents(), &config); - } - } -} - - -void KGlobalShortcutsEditor::importScheme() -{ - // Check for unsaved modifications - if (isModified()) { - int choice = KMessageBox::warningContinueCancel( - this, - i18n("Your current changes will be lost if you load another scheme before saving this one"), - i18n("Load Shortcut Scheme"), - KGuiItem(i18n("Load"))); - if (choice != KMessageBox::Continue) { - return; - } - } - - SelectSchemeDialog dialog(this); - if (dialog.exec()) { - - QUrl url = dialog.selectedScheme(); - if (!url.isLocalFile()) { - KMessageBox::sorry(this, i18n("This file (%1) does not exist. You can only select local files.", - url.url())); - return; - } - KConfig config(url.path(), KConfig::SimpleConfig); - importConfiguration(&config); - } -} - - -void KGlobalShortcutsEditor::load() -{ - // Connect to kglobalaccel. If that fails there is no need to continue. - qRegisterMetaType >(); - qDBusRegisterMetaType >(); - qDBusRegisterMetaType >(); - qDBusRegisterMetaType(); - - org::kde::KGlobalAccel kglobalaccel( - QStringLiteral("org.kde.kglobalaccel"), - QStringLiteral("/kglobalaccel"), - d->bus); - - if (!kglobalaccel.isValid()) { - QString errorString; - QDBusError error = kglobalaccel.lastError(); - // The global shortcuts DBus service manages all global shortcuts and we - // can't do anything useful without it. - if (error.isValid()) { - errorString = i18n("Message: %1\nError: %2", error.message(), error.name()); - } - - KMessageBox::sorry( - this, - i18n("Failed to contact the KDE global shortcuts daemon\n") - + errorString ); - return; - } - - // Undo all changes not yet applied - undo(); - clear(); - - QDBusReply< QList > componentsRc = kglobalaccel.allComponents(); - if (!componentsRc.isValid()) - { - // Sometimes error pop up only after the first real call. - QString errorString; - QDBusError error = componentsRc.error(); - // The global shortcuts DBus service manages all global shortcuts and we - // can't do anything useful without it. - if (error.isValid()) { - errorString = i18n("Message: %1\nError: %2", error.message(), error.name()); - } - - KMessageBox::sorry( - this, - i18n("Failed to contact the KDE global shortcuts daemon\n") - + errorString ); - return; - } - QList components = componentsRc; - - Q_FOREACH(const QDBusObjectPath &componentPath, components) { - d->loadComponent(componentPath); - } // Q_FOREACH(component) -} - - -void KGlobalShortcutsEditor::save() -{ - // The editors are responsible for the saving - //qDebug() << "Save the changes"; - Q_FOREACH (ComponentData *cd, d->components) { - cd->editor()->commit(); - } -} - - -void KGlobalShortcutsEditor::importConfiguration(KConfigBase *config) -{ - //qDebug() << config->groupList(); - - // In a first step clean out the current configurations. We do this - // because we want to minimize the chance of conflicts. - Q_FOREACH (ComponentData *cd, d->components) { - KConfigGroup group(config, cd->uniqueName()); - //qDebug() << cd->uniqueName() << group.name(); - if (group.exists()) { - //qDebug() << "Removing" << cd->uniqueName(); - cd->editor()->clearConfiguration(); - } - } - - // Now import the new configurations. - Q_FOREACH (ComponentData *cd, d->components) { - KConfigGroup group(config, cd->uniqueName()); - if (group.exists()) { - //qDebug() << "Importing" << cd->uniqueName(); - cd->editor()->importConfiguration(&group); - } - } -} - -void KGlobalShortcutsEditor::exportConfiguration(QStringList components, KConfig *config) const - { - Q_FOREACH (const QString &componentFriendly, components) - { - QHash::Iterator iter = d->components.find(componentFriendly); - if (iter == d->components.end()) - { - Q_ASSERT(iter != d->components.end()); - continue; - } - else - { - KConfigGroup group(config, (*iter)->uniqueName()); - (*iter)->editor()->exportConfiguration(&group); - } - } - } - - -void KGlobalShortcutsEditor::undo() -{ - // The editors are responsible for the undo - //qDebug() << "Undo the changes"; - Q_FOREACH (ComponentData *cd, d->components) { - cd->editor()->undoChanges(); - } -} - - -bool KGlobalShortcutsEditor::isModified() const -{ - Q_FOREACH (ComponentData *cd, d->components) { - if (cd->editor()->isModified()) { - return true; - } - } - return false; -} - - -void KGlobalShortcutsEditor::_k_key_changed() -{ - emit changed(isModified()); -} - - -QDBusObjectPath KGlobalShortcutsEditor::KGlobalShortcutsEditorPrivate::componentPath(const QString &componentUnique) -{ - return QDBusObjectPath(QStringLiteral("/component/") + componentUnique); -} - - -bool KGlobalShortcutsEditor::KGlobalShortcutsEditorPrivate::loadComponent(const QDBusObjectPath &componentPath) -{ - // Get the component - org::kde::kglobalaccel::Component component( - QStringLiteral("org.kde.kglobalaccel"), - componentPath.path(), - bus); - if (!component.isValid()) { - //qDebug() << "Component " << componentPath.path() << "not valid! Skipping!"; - return false; - } - - // Get the shortcut contexts. - QDBusReply shortcutContextsRc = component.getShortcutContexts(); - if (!shortcutContextsRc.isValid()) { - //qDebug() << "Failed to get contexts for component " - //<< componentPath.path() <<"! Skipping!"; - //qDebug() << shortcutContextsRc.error(); - return false; - } - QStringList shortcutContexts = shortcutContextsRc; - - // We add the shortcuts for all shortcut contexts to the editor. This - // way the user keeps full control of it's shortcuts. - Q_FOREACH (const QString &shortcutContext, shortcutContexts) { - - QDBusReply< QList > shortcutsRc = - component.allShortcutInfos(shortcutContext); - if (!shortcutsRc.isValid()) - { - //qDebug() << "allShortcutInfos() failed for " << componentPath.path() << shortcutContext; - continue; - } - QList shortcuts = shortcutsRc; - // Shouldn't happen. But you never know - if (shortcuts.isEmpty()) { - //qDebug() << "Got shortcut context" << shortcutContext << "without shortcuts for" - //<< componentPath.path(); - continue; - } - - // It's safe now - const QString componentUnique = shortcuts[0].componentUniqueName(); - QString componentContextId = componentUnique; - // kglobalaccel knows that '|' is our separator between - // component and context - if (shortcutContext != QLatin1String("default")) { - componentContextId += QLatin1String("|") + shortcutContext; - } - - // Create a action collection for our current component:context - KActionCollection* col = new KActionCollection( - q, - componentContextId); - - // Now add the shortcuts. - Q_FOREACH (const KGlobalShortcutInfo &shortcut, shortcuts) { - - const QString &objectName = shortcut.uniqueName(); - QAction *action = col->addAction(objectName); - action->setProperty("isConfigurationAction", QVariant(true)); // see KAction::~KAction - action->setProperty("componentDisplayName", shortcut.componentFriendlyName()); - action->setText(shortcut.friendlyName()); - - // Always call this to enable global shortcuts for the action. The editor widget - // checks it. - // Also actually loads the shortcut using the KAction::Autoloading mechanism. - // Avoid setting the default shortcut; it would just be written to the global - // configuration so we would not get the real one below. - KGlobalAccel::self()->setShortcut(action, QList()); - - // The default shortcut will never be loaded because it's pointless in a real - // application. There are no scarce resources [i.e. physical keys] to manage - // so applications can set them at will and there's no autoloading. - QList sc = shortcut.defaultKeys(); - if (sc.count()>0) { - KGlobalAccel::self()->setDefaultShortcut(action, sc); - } - } // Q_FOREACH(shortcut) - - QString componentFriendlyName = shortcuts[0].componentFriendlyName(); - - if (shortcuts[0].contextUniqueName() != QLatin1String("default")) - { - componentFriendlyName += - QString('[') + shortcuts[0].contextFriendlyName() + QString(']'); - } - - q->addCollection(col, componentPath, componentContextId, componentFriendlyName ); - - } // Q_FOREACH(context) - - return true; -} - - -void KGlobalShortcutsEditor::KGlobalShortcutsEditorPrivate::removeComponent( - const QString &componentUnique ) - { - // TODO: Remove contexts too. - - Q_FOREACH (const QString &text, components.keys()) - { - if (components.value(text)->uniqueName() == componentUnique) - { - // Remove from QComboBox - QModelIndexList results = proxyModel->match(proxyModel->index(0, 0), Qt::DisplayRole, text); - Q_ASSERT(!results.isEmpty()); - model->removeRow(proxyModel->mapToSource(results.first()).row()); - - // Remove from QStackedWidget - stack->removeWidget(components[text]->editor()); - - // Remove the componentData - delete components.take(text); - } - } - } - - diff --git a/kcms/keys/kglobalshortcutseditor.ui b/kcms/keys/kglobalshortcutseditor.ui deleted file mode 100644 --- a/kcms/keys/kglobalshortcutseditor.ui +++ /dev/null @@ -1,130 +0,0 @@ - - - KGlobalShortcutsEditor - - - - 0 - 0 - 660 - 572 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - 0 - 0 - - - - Component: - - - - - - - - 0 - 0 - - - - - - - - - - - - - - 0 - 0 - - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::ScrollPerPixel - - - - - - - - - Add a new shortcut to an Application... - - - ... - - - - - - - Remove the selected component - - - ... - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - File - - - - - - - - - - KCategorizedView - QListView -
kcategorizedview.h
-
-
- - menu_button - - - -
diff --git a/kcms/keys/package/contents/ui/ShortcutActionDelegate.qml b/kcms/keys/package/contents/ui/ShortcutActionDelegate.qml new file mode 100644 --- /dev/null +++ b/kcms/keys/package/contents/ui/ShortcutActionDelegate.qml @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2020 David Redondo + * + * 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) 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. + */ + +import QtQuick 2.12 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.3 as QQC2 + +import org.kde.kirigami 2.10 as Kirigami +import org.kde.kquickcontrols 2.0 +import org.kde.kcm 1.2 as KCM + +Loader { + id: loader + property int oldHeight: height + sourceComponent: ListView.isCurrentItem ? editRepresentation : compactRepresentation + ListView.onIsCurrentItemChanged: { + oldHeight = height + } + clip: true + onSourceComponentChanged: { + animation.running = true + } + NumberAnimation { + target: loader.item + id: animation + property: "height" + from: oldHeight + to: loader.height + duration: Kirigami.Units.shortDuration + } + Component { + id:compactRepresentation + Kirigami.AbstractListItem { + width: shortcutsList.width + onClicked: shortcutsList.currentIndex = index + contentItem: Item { + implicitHeight: itemLayout.implicitHeight + RowLayout { + id: itemLayout + width: parent.width + spacing: Kirigami.Units.smallSpacing + QQC2.Label { + Layout.alignment: Qt.AlignTop + Layout.preferredHeight: expandButton.height + text: i18nc("%1 is the name action that is triggered by the key sequences following after :", "%1:", model.display) + } + QQC2.Label { + Layout.alignment: Qt.AlignTop + Layout.preferredHeight: expandButton.height + Layout.fillWidth: true + color: model.activeShortcuts.length != 0 ? Kirigami.Theme.textColor : Kirigami.Theme.disabledTextColor + elide: Text.ElideRight + horizontalAlignment: Text.AlignRight + text: { + if (model.activeShortcuts.length != 0) { + return model.activeShortcuts.map(s => kcm.keySequenceToString(s)).join(", ") + } else { + return i18n("No active shortcuts") + } + } + } + QQC2.ToolButton { + id: expandButton + Layout.alignment: Qt.AlignRight | Qt.AlignTop + icon.name: "expand" + onClicked: { + shortcutsList.currentIndex = index + } + } + } + MouseArea { + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.NoButton + anchors.fill: parent + } + } + } + } + Component { + id: editRepresentation + Kirigami.AbstractListItem { + hoverEnabled: false + width: shortcutsList.width + readonly property var originalIndex : kcm.filteredModel.mapToSource(dm.modelIndex(index)) + contentItem: ColumnLayout { + spacing: 0 + RowLayout { + Kirigami.Heading { + Layout.alignment: Qt.AlignTop + level: 3 + text: model.display + } + Item { + Layout.fillWidth: true + } + QQC2.ToolButton { + Layout.alignment: Qt.AlignRight + icon.name: "collapse" + onClicked: { + shortcutsList.currentIndex = -1 + } + } + } + RowLayout { + spacing: 0 + ColumnLayout { + Layout.alignment: Qt.AlignTop + Layout.preferredWidth: parent.width * 0.5 + Kirigami.Heading { + level: 4 + text: defaultShortcuts.length == 0 ? i18n("No default shortcuts") : + i18ncp("%1 decides if singular or plural will be used", "Default shortcut", + "Default shortcuts", defaultShortcuts.length) + } + Kirigami.Separator { + Layout.fillWidth: true + } + Repeater { + model: defaultShortcuts + QQC2.CheckBox { + checked: activeShortcuts.indexOf(modelData) != -1 + text: modelData + onToggled: kcm.shortcutsModel.toggleDefaultShortcut(originalIndex, modelData, checked) + } + } + } + ColumnLayout { + Layout.preferredWidth: parent.width * 0.5 + Layout.alignment: Qt.AlignTop + Kirigami.Heading { + level: 4 + text: i18n("Custom shortcuts") + } + Kirigami.Separator { + Layout.fillWidth: true + } + Repeater { + model: customShortcuts + RowLayout { + KeySequenceItem { + keySequence: modelData + showClearButton: false + onCaptureFinished: { + kcm.shortcutsModel.changeShortcut(originalIndex, modelData, keySequence) + } + } + QQC2.Button { + icon.name: "edit-delete" + onClicked: kcm.shortcutsModel.disableShortcut(originalIndex, modelData) + QQC2.ToolTip { + text: i18n("Delete this shortcut") + } + } + } + } + QQC2.Button { + text: i18n("Add custom shortcut") + icon.name: "list-add" + onClicked: { + this.visible = false + var newKeySequenceItem = newKeySequenceComponent.createObject(parent) + for (var i = 0; i < newKeySequenceItem.children.length; i++) { + if (newKeySequenceItem.children[i] instanceof KeySequenceItem) { + var keySequenceItem = newKeySequenceItem.children[i] + } + } + newKeySequenceItem.finished.connect(() => { + newKeySequenceItem.destroy() + this.visible = true + }) + keySequenceItem.startCapturing() + } + } + + Component { + id: newKeySequenceComponent + RowLayout { + id: newKeySequenceLayout + signal finished + KeySequenceItem { + showClearButton: false + onCaptureFinished: { + kcm.shortcutsModel.addShortcut(originalIndex, keySequence) + parent.finished() + } + } + QQC2.Button { + icon.name: "dialog-cancel" + onClicked: parent.finished() + QQC2.ToolTip { + text: i18n("Cancel capturing of new shortcut") + } + } + } + } + } + } + } + } + } + } diff --git a/kcms/keys/package/contents/ui/main.qml b/kcms/keys/package/contents/ui/main.qml new file mode 100644 --- /dev/null +++ b/kcms/keys/package/contents/ui/main.qml @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2020 David Redondo + * + * 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) 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. + */ + +import QtQuick 2.14 +import QtQuick.Dialogs 1.3 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.3 as QQC2 +import QtQml 2.14 +import QtQml.Models 2.3 + +import org.kde.kirigami 2.10 as Kirigami +import org.kde.kcm 1.2 as KCM +import org.kde.private.kcms.keys 2.0 as Private + +KCM.SimpleKCM { + id: root + implicitWidth: 800 + implicitHeight: 600 + enabled: kcm.lastError == "" + property alias exportActive: exportInfo.visible + ColumnLayout { + anchors.fill: parent + Kirigami.InlineMessage { + Layout.fillWidth: true + visible: kcm.lastError !== "" + text: kcm.lastError + type: Kirigami.MessageType.Error + } + Kirigami.InlineMessage { + id: exportWarning + Layout.fillWidth: true + text: i18n("Cannot export scheme while there are unsaved changes") + type: Kirigami.MessageType.Warning + showCloseButton: true + Binding on visible { + when: exportWarning.visible + value: kcm.needsSave + restoreMode: Binding.RestoreNone + } + } + Kirigami.InlineMessage { + id: exportInfo + Layout.fillWidth: true + text: i18n("Select the components below that should be included in the exported scheme") + type: Kirigami.MessageType.Information + showCloseButton: true + actions: [ + Kirigami.Action { + iconName: "document-save" + text: i18n("Save scheme") + onTriggered: { + fileDialogLoader.save = true + fileDialogLoader.active = true + exportActive = false + } + } + ] + } + Kirigami.SearchField { + id: search + enabled: !exportActive + Layout.fillWidth: true + Binding { + target: kcm.filteredModel + property: "filter" + value: search.text + } + } + GridLayout { + columns: 2 + QQC2.ScrollView { + Component.onCompleted: background.visible = true + Layout.preferredWidth: 300 + Layout.fillHeight:true + ListView { + id: components + clip: true + model: kcm.filteredModel + delegate: Kirigami.AbstractListItem { + id: componentDelegate + readonly property color foregroundColor: ListView.isCurrentItem ? activeTextColor : textColor + RowLayout { + Kirigami.Icon { + id: appIcon + source: model.decoration + Layout.preferredWidth: Kirigami.Units.iconSizes.small + Layout.preferredHeight: Layout.preferredWidth + color: foregroundColor + } + QQC2.Label { + Layout.fillWidth: true + text: model.display + color: foregroundColor + } + QQC2.ToolButton { + Layout.preferredHeight: Kirigami.Units.iconSizes.small + Kirigami.Units.largeSpacing + Layout.preferredWidth: Layout.preferredHeight + visible: !exportActive + opacity: componentDelegate.containsMouse || componentDelegate.ListView.isCurrentItem ? 1 : 0 + enabled: opacity + icon.name: "edit-delete" + icon.width: Kirigami.Units.iconSizes.small + onClicked: kcm.removeComponent(index) + } + QQC2.CheckBox { + id: checkbox + checked: model.checked + visible: exportActive + onToggled: model.checked = checked + } + } + } + section.property: "section" + section.delegate: Kirigami.ListSectionHeader { + label: section + QQC2.CheckBox { + id: sectionCheckbox + Layout.alignment: Qt.AlignRight + visible: exportActive + onToggled: { + const checked = sectionCheckbox.checked + const startIndex = kcm.shortcutsModel.index(0, 0) + const indices = kcm.shortcutsModel.match(startIndex, Private.ShortcutsModel.SectionRole, section, -1) + for (const index of indices) { + kcm.shortcutsModel.setData(index, checked, Private.ShortcutsModel.CheckedRole) + } + } + Connections { + enabled: exportActive + target: kcm.shortcutsModel + function onDataChanged (topLeft, bottomRight, roles) { + const startIndex = kcm.shortcutsModel.index(0, 0) + const indices = kcm.shortcutsModel.match(startIndex, Private.ShortcutsModel.SectionRole, section, -1) + sectionCheckbox.checked = indices.reduce((acc, index) => acc && kcm.shortcutsModel.data(index,Private.ShortcutsModel.CheckedRole), true) + } + } + } + } + } + } + QQC2.ScrollView { + property var rootIndex : kcm.filteredModel.index(components.currentIndex, 0) + enabled: !exportActive + id: shortcutsScroll + Layout.fillHeight: true + Layout.fillWidth: true + Component.onCompleted: background.visible = true + onRootIndexChanged: shortcutsList.currentIndex = -1 + ListView { + clip:true + id: shortcutsList + model: DelegateModel { + id: dm + rootIndex: shortcutsScroll.rootIndex + model: rootIndex.valid ? kcm.filteredModel : undefined + delegate: ShortcutActionDelegate {} + } + } + } + QQC2.Button { + enabled: !exportActive + Layout.alignment: Qt.AlignRight + icon.name: "list-add" + text: i18n("Add Application...") + onClicked: { + kcm.addApplication(this) + } + } + RowLayout { + Layout.alignment: Qt.AlignRight + QQC2.Button { + enabled: !exportActive + icon.name: "document-import" + text: i18n("Import Scheme...") + onClicked: importSheet.open() + } + QQC2.Button { + icon.name: exportActive ? "dialog-cancel" : "document-export" + text: exportActive ? i18n("Cancel Export") : i18n("Export Scheme...") + onClicked: { + if (exportActive) { + exportActive = false + } else if (kcm.needsSave) { + exportWarning.visible = true + } else { + search.text = "" + exportActive = true + } + } + } + } + } + } + Loader { + id: fileDialogLoader + active: false + property bool save + sourceComponent: FileDialog { + id: fileDialog + title: save ? i18n("Export Shortcut Scheme") : i18n("Import Shortcut Scheme") + folder: shortcuts.home + nameFilters: [ i18nc("Template for file dialog","Schortcut Scheme (*.kksrc)") ] + defaultSuffix: ".kksrc" + selectExisting: !fileDialogLoader.save + Component.onCompleted: open() + onAccepted: { + save ? kcm.writeScheme(fileUrls[0]) : kcm.loadScheme(fileUrls[0]) + fileDialogLoader.active = false + } + onRejected: fileDialogLoader.active = false + } + } + Kirigami.OverlaySheet { + id: importSheet + header: Kirigami.Heading { + text: i18n("Import Shortcut Scheme") + } + + ColumnLayout { + anchors.centerIn: parent + QQC2.Label { + text: i18n("Select the scheme to import:") + } + RowLayout { + QQC2.ComboBox { + id: schemeBox + readonly property bool customSchemeSelected: currentIndex == count - 1 + property string url: "" + currentIndex: count - 1 + textRole: "name" + onActivated: url = model[index]["url"] + Component.onCompleted: { + var defaultSchemes = kcm.defaultSchemes() + defaultSchemes.push({name: i18n("Custom Scheme"), url: "unused"}) + model = defaultSchemes + } + } + QQC2.Button { + text: schemeBox.customSchemeSelected ? i18n("Select File...") : i18n("Import") + onClicked: { + if (schemeBox.customSchemeSelected) { + fileDialogLoader.save = false; + fileDialogLoader.active = true; + } else { + kcm.loadScheme(schemeBox.defaultSchemes[schemeBox.url]) + } + importSheet.close() + } + } + } + } + } +} + diff --git a/kcms/keys/package/metadata.desktop b/kcms/keys/package/metadata.desktop new file mode 100644 --- /dev/null +++ b/kcms/keys/package/metadata.desktop @@ -0,0 +1,17 @@ +[Desktop Entry] +Name=Global Shortcuts + +Comment=Global Keyboard Shortcuts + +Icon=preferences-desktop-keyboard-shortcut +Type=Service +X-KDE-PluginInfo-Author=David Redondo +X-KDE-PluginInfo-Email=kde@david-redondo.de +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-Name=kcm_keys +X-KDE-PluginInfo-Version= +X-KDE-PluginInfo-Website= +X-KDE-ServiceTypes=Plasma/Generic +X-Plasma-API=declarativeappletscript + +X-Plasma-MainScript=ui/main.qml diff --git a/kcms/keys/select_application.ui b/kcms/keys/select_application.ui deleted file mode 100644 --- a/kcms/keys/select_application.ui +++ /dev/null @@ -1,77 +0,0 @@ - - - SelectApplicationDialog - - - - 0 - 0 - 410 - 421 - - - - Select Application - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - KFilterProxySearchLine - QWidget -
kfilterproxysearchline.h
-
-
- - - - buttonBox - accepted() - SelectApplicationDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - SelectApplicationDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - -
diff --git a/kcms/keys/select_scheme_dialog.h b/kcms/keys/select_scheme_dialog.h deleted file mode 100644 --- a/kcms/keys/select_scheme_dialog.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2008 Michael Jansen - * - * 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) 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 SELECT_SCHEME_DIALOG_H -#define SELECT_SCHEME_DIALOG_H - -#include "QDialog" -#include -#include - -namespace Ui -{ -class SelectSchemeDialog; -} - -class SelectSchemeDialog : public QDialog -{ - Q_OBJECT -public: - SelectSchemeDialog(QWidget *parent = nullptr); - ~SelectSchemeDialog() override; - - QUrl selectedScheme() const; - -private Q_SLOTS: - void schemeActivated(int index); - void slotUrlChanged(const QString &); - -private: - Ui::SelectSchemeDialog *ui; - QStringList m_schemes; - QPushButton *mOkButton; -}; // SelectSchemeDialog - - - -#endif diff --git a/kcms/keys/select_scheme_dialog.cpp b/kcms/keys/select_scheme_dialog.cpp deleted file mode 100644 --- a/kcms/keys/select_scheme_dialog.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2008 Michael Jansen - * - * 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) 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 "select_scheme_dialog.h" -#include "ui_select_scheme_dialog.h" - - -#include "QDialog" -#include -#include -#include -#include -#include -#include -#include - -SelectSchemeDialog::SelectSchemeDialog(QWidget *parent) - : QDialog(parent), - ui(new Ui::SelectSchemeDialog) -{ - const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, - QStringLiteral("kcmkeys"), QStandardPaths::LocateDirectory); - for (const QString &dir : dirs) { - const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.kksrc")); - for (const QString &file : fileNames) { - if (m_schemes.contains(file)) { - continue; - } - m_schemes.append(dir + QLatin1Char('/') + file); - } - } - QVBoxLayout *mainLayout = new QVBoxLayout(this); - QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, this); - mOkButton = buttonBox->button(QDialogButtonBox::Ok); - mOkButton->setDefault(true); - mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return); - connect(buttonBox, &QDialogButtonBox::accepted, this, &SelectSchemeDialog::accept); - connect(buttonBox, &QDialogButtonBox::rejected, this, &SelectSchemeDialog::reject); - - ui->setupUi(this); - mainLayout->addWidget(ui->layoutWidget); - mainLayout->addWidget(buttonBox); - - for (const QString &res : qAsConst(m_schemes)) { - KConfig config(res, KConfig::SimpleConfig); - KConfigGroup group(&config, "Settings"); - QString name = group.readEntry("Name"); - - if (name.isEmpty()) { - name = res; - } - ui->m_schemes->addItem(name); - } - - ui->m_schemes->setCurrentIndex(-1); - - ui->m_url->setMode(KFile::LocalOnly | KFile::ExistingOnly); - - connect(ui->m_schemes, SIGNAL(activated(int)), - this, SLOT(schemeActivated(int))); - connect(ui->m_url->lineEdit(), &QLineEdit::textChanged, - this, &SelectSchemeDialog::slotUrlChanged); - mOkButton->setEnabled(false); -} - - -SelectSchemeDialog::~SelectSchemeDialog() -{ - delete ui; -} - -void SelectSchemeDialog::schemeActivated(int index) -{ - ui->m_url->setUrl(QUrl(m_schemes[index])); -} - - -QUrl SelectSchemeDialog::selectedScheme() const -{ - return ui->m_url->url(); -} - -void SelectSchemeDialog::slotUrlChanged(const QString &_text) -{ - mOkButton->setEnabled(!_text.isEmpty()); -} - -#include "moc_select_scheme_dialog.cpp" diff --git a/kcms/keys/select_scheme_dialog.ui b/kcms/keys/select_scheme_dialog.ui deleted file mode 100644 --- a/kcms/keys/select_scheme_dialog.ui +++ /dev/null @@ -1,150 +0,0 @@ - - - Michael Jansen - SelectSchemeDialog - - - - 0 - 0 - 717 - 224 - - - - Select Shortcut Scheme - - - true - - - - - 32 - 12 - 671 - 71 - - - - - - - - 0 - 50 - - - - Select one of the standard KDE shortcut schemes - - - &Standard scheme: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - m_schemes - - - - - - - - 0 - 0 - - - - - 0 - 50 - - - - true - - - - - - - - 150 - 0 - - - - - 1 - 0 - - - - - 0 - 50 - - - - Select a shortcut scheme file - - - &Path: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - m_url - - - - - - - - 3 - 0 - - - - - 0 - 50 - - - - *.kksrc - - - Select Shortcut Scheme - - - - - - - - - KComboBox - QComboBox -
kcombobox.h
-
- - QDialog - QDialog -
kdialog.h
- 1 -
- - KUrlRequester - QFrame -
kurlrequester.h
-
-
- - -
diff --git a/kcms/keys/shortcutsmodel.h b/kcms/keys/shortcutsmodel.h new file mode 100644 --- /dev/null +++ b/kcms/keys/shortcutsmodel.h @@ -0,0 +1,110 @@ +/* + * Copyright 2020 David Redondo + * + * 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 any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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, see . + */ + +#ifndef SHORTCUTSMODEL_H +#define SHORTCUTSMODEL_H + +#include +#include +#include +#include +#include +#include + + +class QDBusError; +class QDBusObjectPath; + +class KConfigBase; +class KGlobalAccelInterface; +class KGlobalShortcutInfo; + +class FilteredShortcutsModel; + +struct Shortcut { + QString uniqueName; + QString friendlyName; + QSet activeShortcuts; + QSet defaultShortcuts; + QSet initialShortcuts; +}; + +struct Component { + QString uniqueName; + QString friendlyName; + QString type; + QString icon; + QVector shortcuts; + bool checked; +}; + +class ShortcutsModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum Roles { + SectionRole = Qt::UserRole, + ComponentRole, + ActionRole, + ActiveShortcutsRole, + DefaultShortcutsRole, + CustomShortcutsRole, + CheckedRole + }; + Q_ENUM(Roles) + + ShortcutsModel(KGlobalAccelInterface *interface, QObject *parent = nullptr); + + Q_INVOKABLE void toggleDefaultShortcut(const QModelIndex &index, const QKeySequence &shortcut, bool enabled); + Q_INVOKABLE void addShortcut(const QModelIndex &index, const QKeySequence &shortcut); + Q_INVOKABLE void disableShortcut(const QModelIndex &index, const QKeySequence &shortcut); + Q_INVOKABLE void changeShortcut(const QModelIndex &index, const QKeySequence &oldShortcut, const QKeySequence &newShortcut); + + void setShortcuts(const KConfigBase &config); + void addApplication(const QString &desktopFileName, const QString &displayName); + void removeComponent(const QModelIndex &index); + + void load(); + void defaults(); + void save(); + bool needsSave() const; + bool isDefault() const; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + QHash roleNames() const override; + +Q_SIGNALS: + void errorOccured(const QString&); + +private: + Component loadComponent(const QList &info); + void genericErrorOccured(const QString &description, const QDBusError &error); + + KGlobalAccelInterface *m_globalAccelInterface; + QVector m_components; +}; + +#endif // SHORTCUTSMODEL_H diff --git a/kcms/keys/shortcutsmodel.cpp b/kcms/keys/shortcutsmodel.cpp new file mode 100644 --- /dev/null +++ b/kcms/keys/shortcutsmodel.cpp @@ -0,0 +1,473 @@ +/* + * Copyright (C) 2020 David Redondo + * + * 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) 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 "shortcutsmodel.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kcmkeys_debug.h" + +static QStringList buildActionId(const QString &componentUnique, const QString &componentFriendly, + const QString &actionUnique, const QString &actionFriendly) +{ + QStringList actionId{"", "", "", ""}; + actionId[KGlobalAccel::ComponentUnique] = componentUnique; + actionId[KGlobalAccel::ComponentFriendly] = componentFriendly; + actionId[KGlobalAccel::ActionUnique] = actionUnique; + actionId[KGlobalAccel::ActionFriendly] = actionFriendly; + return actionId; +} + + +ShortcutsModel::ShortcutsModel(KGlobalAccelInterface *interface, QObject *parent) + : QAbstractItemModel(parent) + , m_globalAccelInterface{interface} +{ +} + +void ShortcutsModel::load() +{ + if (!m_globalAccelInterface->isValid()) { + return; + } + beginResetModel(); + m_components.clear(); + auto componentsWatcher = new QDBusPendingCallWatcher( m_globalAccelInterface->allComponents()); + connect(componentsWatcher, &QDBusPendingCallWatcher::finished, this, [this] (QDBusPendingCallWatcher *componentsWatcher) { + QDBusPendingReply> componentsReply = *componentsWatcher; + componentsWatcher->deleteLater(); + if (componentsReply.isError()) { + genericErrorOccured(QStringLiteral("Error while calling allComponents()"), componentsReply.error()); + endResetModel(); + return; + } + const QList componentPaths = componentsReply.value(); + int *pendingCalls = new int; + *pendingCalls = componentPaths.size(); + qDebug() << pendingCalls << *pendingCalls; + for (const auto &componentPath : componentPaths) { + const QString path = componentPath.path(); + KGlobalAccelComponentInterface component(m_globalAccelInterface->service(), path, m_globalAccelInterface->connection()); + auto watcher = new QDBusPendingCallWatcher(component.allShortcutInfos()); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [path, pendingCalls, this] (QDBusPendingCallWatcher *watcher){ + QDBusPendingReply> reply = *watcher; + if (reply.isError()) { + genericErrorOccured(QStringLiteral("Error while calling allShortCutInfos of") + path, reply.error()); + } else { + m_components.push_back(loadComponent(reply.value())); + } + watcher->deleteLater(); + if (--*pendingCalls == 0) { + QCollator collator; + std::sort(m_components.begin(), m_components.end(), [&](const Component &c1, const Component &c2){ + return c1.type != c2.type ? c1.type < c2.type : collator.compare(c1.friendlyName, c2.friendlyName) < 0; + }); + endResetModel(); + delete pendingCalls; + } + }); + } + }); +} + +Component ShortcutsModel::loadComponent(const QList &info) +{ + const QString &componentUnique = info[0].componentUniqueName(); + const QString &componentFriendly = info[0].componentFriendlyName(); + KService::Ptr service = KService::serviceByStorageId(componentUnique); + if (!service) { + // Do we have an application with that name? + const KService::List apps = KServiceTypeTrader::self()->query(QStringLiteral("Application"), + QStringLiteral("Name == '%1' or Name == '%2'").arg(componentUnique, componentFriendly)); + if(!apps.isEmpty()) { + service = apps[0]; + } + } + const QString type = service && service->isApplication() ? i18n("Applications") : i18n("System Services"); + const QString icon = service && !service->icon().isEmpty() ? service->icon() : componentUnique; + Component c{componentUnique, componentFriendly, type, icon, QVector(), false}; + for (const auto &action : info) { + const QString &actionUnique = action.uniqueName(); + const QString &actionFriendly = action.friendlyName(); + Shortcut shortcut; + shortcut.uniqueName = actionUnique; + shortcut.friendlyName = actionFriendly; + const QList defaultShortcuts = action.defaultKeys(); + for (const auto &keySequence : defaultShortcuts) { + if (!keySequence.isEmpty()) { + shortcut.defaultShortcuts.insert(keySequence); + } + } + const QList activeShortcuts = action.keys(); + for (const QKeySequence &keySequence : activeShortcuts) { + if (!keySequence.isEmpty()) { + shortcut.activeShortcuts.insert(keySequence); + } + } + shortcut.initialShortcuts = shortcut.activeShortcuts; + c.shortcuts.push_back(shortcut); + } + QCollator collator; + std::sort(c.shortcuts.begin(), c.shortcuts.end(), [&] (const Shortcut &s1, const Shortcut &s2) { + return collator.compare(s1.friendlyName, s2.friendlyName) < 0; + }); + return c; +} + +void ShortcutsModel::save() +{ + for (auto& component : m_components) { + for (auto& shortcut : component.shortcuts) { + if (shortcut.initialShortcuts != shortcut.activeShortcuts) { + const QStringList actionId = buildActionId(component.uniqueName, component.friendlyName, + shortcut.uniqueName, shortcut.friendlyName); + //operator int of QKeySequence + QList keys(shortcut.activeShortcuts.cbegin(), shortcut.activeShortcuts.cend()); + qCDebug(KCMKEYS) << "Saving" << actionId << shortcut.activeShortcuts << keys; + auto watcher = new QDBusPendingCallWatcher(m_globalAccelInterface->setForeignShortcut(actionId, keys)); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [&] { + QDBusPendingReply reply = *watcher; + if (!reply.isValid()) { + qCCritical(KCMKEYS) << "Error while saving"; + if (reply.error().isValid()) { + qCCritical(KCMKEYS) << reply.error().name() << reply.error().message(); + } + emit errorOccured(i18nc("%1 is the name of the component, %2 is the action for which saving failed", + "Error while saving shortcut %1: %2", component.friendlyName, shortcut.friendlyName)); + } else { + shortcut.initialShortcuts = shortcut.activeShortcuts; + } + watcher->deleteLater(); + }); + } + } + } +} + +void ShortcutsModel::defaults() { + for (auto component = m_components.begin(); component != m_components.end(); ++component) { + auto componentIndex = index(component - m_components.begin(), 0); + for (auto shortcut = component->shortcuts.begin(); shortcut != component->shortcuts.end(); ++shortcut) { + shortcut->activeShortcuts = shortcut->defaultShortcuts; + } + emit dataChanged(index(0, 0, componentIndex), index(component->shortcuts.size(), 0, componentIndex), + {ActiveShortcutsRole, CustomShortcutsRole}); + } +} + +bool ShortcutsModel::needsSave() const +{ + for (const auto& component : m_components) { + for (const auto& shortcut : component.shortcuts) { + if (shortcut.initialShortcuts != shortcut.activeShortcuts) { + return true; + } + } + } + return false; +} + +bool ShortcutsModel::isDefault() const +{ + for (const auto& component : m_components) { + for (const auto& shortcut : component.shortcuts) { + if (shortcut.defaultShortcuts != shortcut.activeShortcuts) { + return false; + } + } + } + return true; +} + +QModelIndex ShortcutsModel::index(int row, int column, const QModelIndex &parent) const +{ + if (row < 0 || column != 0) { + return QModelIndex(); + } + if (parent.isValid() && row < rowCount(parent) && column == 0) { + return createIndex(row, column, parent.row() + 1); + } else if (column == 0 && row < m_components.size()) { + return createIndex(row, column, nullptr); + } + return QModelIndex(); +} + +QModelIndex ShortcutsModel::parent(const QModelIndex &child) const +{ + if (child.internalId()) { + return createIndex(child.internalId() - 1, 0, nullptr); + } + return QModelIndex(); +} + +int ShortcutsModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + if (parent.parent().isValid()) { + return 0; + } + return m_components[parent.row()].shortcuts.size(); + } + return m_components.size(); +} + +int ShortcutsModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +QVariant ShortcutsModel::data(const QModelIndex &index, int role) const +{ + if (!checkIndex(index)) { + return QVariant(); + } + + if (index.parent().isValid()) { + const Shortcut &shortcut = m_components[index.parent().row()].shortcuts[index.row()]; + switch (role) { + case Qt::DisplayRole: + return shortcut.friendlyName.isEmpty() ? shortcut.uniqueName : shortcut.friendlyName; + case ActionRole: + return shortcut.uniqueName; + case ActiveShortcutsRole: + return QVariant::fromValue(shortcut.activeShortcuts); + case DefaultShortcutsRole: + return QVariant::fromValue(shortcut.defaultShortcuts); + case CustomShortcutsRole: { + auto shortcuts = shortcut.activeShortcuts; + return QVariant::fromValue(shortcuts.subtract(shortcut.defaultShortcuts)); + } + } + return QVariant(); + } + const Component &component = m_components[index.row()]; + switch(role) { + case Qt::DisplayRole: + return component.friendlyName; + case Qt::DecorationRole: + return component.icon; + case SectionRole: + return component.type; + case ComponentRole: + return component.uniqueName; + case CheckedRole: + return component.checked; + } + return QVariant(); +} + +bool ShortcutsModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!checkIndex(index) || index.parent().isValid() || role != CheckedRole) { + return false; + } + m_components[index.row()].checked = value.toBool(); + emit dataChanged(index, index, {CheckedRole}); + return true; +} + +QHash ShortcutsModel::roleNames() const +{ + return { + {Qt::DisplayRole, QByteArrayLiteral("display")}, + {Qt::DecorationRole, QByteArrayLiteral("decoration")}, + {SectionRole, QByteArrayLiteral("section")}, + {ComponentRole, QByteArrayLiteral("component")}, + {ActiveShortcutsRole, QByteArrayLiteral("activeShortcuts")}, + {DefaultShortcutsRole, QByteArrayLiteral("defaultShortcuts")}, + {CustomShortcutsRole, QByteArrayLiteral("customShortcuts")}, + {CheckedRole, QByteArrayLiteral("checked")} + }; +} + +void ShortcutsModel::toggleDefaultShortcut(const QModelIndex &index, const QKeySequence &shortcut, bool enabled) +{ + if (!checkIndex(index) || !index.parent().isValid()) { + return; + } + qCDebug(KCMKEYS) << "Default shortcut" << index << shortcut << enabled; + Shortcut &s = m_components[index.parent().row()].shortcuts[index.row()]; + if (enabled) { + s.activeShortcuts.insert(shortcut); + } else { + s.activeShortcuts.remove(shortcut); + } + emit dataChanged(index, index, {ActiveShortcutsRole, DefaultShortcutsRole}); +} + +void ShortcutsModel::addShortcut(const QModelIndex &index, const QKeySequence &shortcut) +{ + if (!checkIndex(index) || !index.parent().isValid()) { + return; + } + if (shortcut.isEmpty()) { + return; + } + qCDebug(KCMKEYS) << "Adding shortcut" << index << shortcut; + Shortcut &s = m_components[index.parent().row()].shortcuts[index.row()]; + s.activeShortcuts.insert(shortcut); + emit dataChanged(index, index, {ActiveShortcutsRole, CustomShortcutsRole}); +} + +void ShortcutsModel::disableShortcut(const QModelIndex &index, const QKeySequence &shortcut) +{ + if (!checkIndex(index) || !index.parent().isValid()) { + return; + } + qCDebug(KCMKEYS) << "Disabling shortcut" << index << shortcut; + Shortcut &s = m_components[index.parent().row()].shortcuts[index.row()]; + s.activeShortcuts.remove(shortcut); + emit dataChanged(index, index, {ActiveShortcutsRole, CustomShortcutsRole}); + +} + +void ShortcutsModel::changeShortcut(const QModelIndex &index, const QKeySequence &oldShortcut, const QKeySequence &newShortcut) +{ + if (!checkIndex(index) || !index.parent().isValid()) { + return; + } + if (newShortcut.isEmpty()) { + return; + } + qCDebug(KCMKEYS) << "Changing Shortcut" << index << oldShortcut << " to " << newShortcut; + Shortcut &s = m_components[index.parent().row()].shortcuts[index.row()]; + s.activeShortcuts.remove(oldShortcut); + s.activeShortcuts.insert(newShortcut); + emit dataChanged(index, index, {ActiveShortcutsRole, CustomShortcutsRole}); +} + +void ShortcutsModel::setShortcuts(const KConfigBase &config) +{ + for (const auto componentGroupName : config.groupList()) { + auto component = std::find_if(m_components.begin(), m_components.end(), [&] (const Component &c) { + return c.uniqueName == componentGroupName; + }); + if (component == m_components.end()) { + qCWarning(KCMKEYS) << "Ignoring unknown component" << componentGroupName; + continue; + } + KConfigGroup componentGroup(&config, componentGroupName); + if (!componentGroup.hasGroup("Global Shortcuts")) { + qCWarning(KCMKEYS) << "Group" << componentGroupName << "has no shortcuts group"; + continue; + } + KConfigGroup shortcutsGroup(&componentGroup, "Global Shortcuts"); + for (const auto& key : shortcutsGroup.keyList()) { + auto shortcut = std::find_if(component->shortcuts.begin(), component->shortcuts.end(), [&] (const Shortcut &s) { + return s.uniqueName == key; + }); + if (shortcut == component->shortcuts.end()) { + qCWarning(KCMKEYS) << "Ignoring unknown action" << key; + } + const auto shortcuts = QKeySequence::listFromString(shortcutsGroup.readEntry(key)); + shortcut->activeShortcuts = QSet(shortcuts.cbegin(), shortcuts.cend()); + } + } + emit dataChanged(index(0, 0), index(0, rowCount()), {ActiveShortcutsRole, CustomShortcutsRole}); +} + +void ShortcutsModel::addApplication(const QString &desktopFileName, const QString &displayName) +{ + // Register a dummy action to trigger kglobalaccel to parse the desktop file + QStringList actionId = buildActionId(desktopFileName, displayName, QString(), QString()); + m_globalAccelInterface->doRegister(actionId); + m_globalAccelInterface->unRegister(actionId); + QCollator collator; + auto pos = std::lower_bound(m_components.begin(), m_components.end(), displayName, [&] (const Component &c, const QString &name) { + return c.type != i18n("System Services") && collator.compare(c.friendlyName, name) < 0; + }); + auto watcher = new QDBusPendingCallWatcher(m_globalAccelInterface->getComponent(desktopFileName)); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [=] { + QDBusPendingReply reply = *watcher; + watcher->deleteLater(); + if (!reply.isValid()) { + genericErrorOccured(QStringLiteral("Error while calling objectPath of added application") + desktopFileName, reply.error()); + return; + } + KGlobalAccelComponentInterface component(m_globalAccelInterface->service(), reply.value().path(), m_globalAccelInterface->connection()); + auto infoWatcher = new QDBusPendingCallWatcher(component.allShortcutInfos()); + connect(infoWatcher, &QDBusPendingCallWatcher::finished, this, [=] { + QDBusPendingReply> infoReply = *infoWatcher; + infoWatcher->deleteLater(); + if (!infoReply.isValid()) { + genericErrorOccured(QStringLiteral("Error while calling allShortCutInfos on new component") + desktopFileName, infoReply.error()); + return; + } + qCDebug(KCMKEYS) << "inserting at " << pos - m_components.begin(); + emit beginInsertRows(QModelIndex(), pos - m_components.begin(), pos - m_components.begin()); + Component c = loadComponent(infoReply.value()); + m_components.insert(pos, c); + emit endInsertRows(); + }); + }); +} + +void ShortcutsModel::removeComponent(const QModelIndex &index) +{ + if (!checkIndex(index) || index.parent().isValid()) { + qCWarning(KCMKEYS) << "Cannot remove component - index doesn't refer to a valid component" << index; + return; + } + const QString &uniqueName = m_components[index.row()].uniqueName); + auto watcher = new QDBusPendingCallWatcher(m_globalAccelInterface->getComponent(uniqueName)); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [=] { + QDBusPendingReply reply = *watcher; + watcher->deleteLater(); + if (!reply.isValid()) { + genericErrorOccured(QStringLiteral("Error while calling objectPath of component") + uniqueName, reply.error()); + return; + } + KGlobalAccelComponentInterface component(m_globalAccelInterface->service(), reply.value().path(), m_globalAccelInterface->connection()); + qCDebug(KCMKEYS) << "Cleaning up component at" << objectPath.value(); + auto cleanUpWatcher = new QDBusPendingCallWatcher(component.cleanUp()); + connect(cleanUpWatcher, &QDBusPendingCallWatcher::finished, this, [=] { + QDBusPendingReply> reply = *cleanUpWatcher; + cleanUpWatcher->deleteLater(); + if (!reply.isValid()) { + genericErrorOccured(QStringLiteral("Error while calling cleanUp of component") + uniqueName, reply.error()); + return; + } + beginRemoveRows(QModelIndex(), index.row(), index.row()); + m_components.remove(index.row()); + endRemoveRows(); + }); + }); +} + +void ShortcutsModel::genericErrorOccured(const QString &description, const QDBusError &error) +{ + qCCritical(KCMKEYS) << description; + if (error.isValid()) { + qCCritical(KCMKEYS) << error.name() << error.message(); + } + emit this->errorOccured(i18n("Error while communicating with the global shortcuts service")); +}