diff --git a/kcms/keys/CMakeLists.txt b/kcms/keys/CMakeLists.txt index 871fc305b..b127db5af 100644 --- a/kcms/keys/CMakeLists.txt +++ b/kcms/keys/CMakeLists.txt @@ -1,57 +1,67 @@ # 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} ) +kcoreaddons_desktop_to_json(kcm_keys "kcm_keys.desktop") +install(FILES kcm_keys.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) +install(TARGETS kcm_keys DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms) -########### install files ############### +kpackage_install_package(package kcm_keys kcms) -install( FILES keys.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) -install( FILES +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 ) - - + DESTINATION ${KDE_INSTALL_DATADIR}/kcmkeys +) diff --git a/kcms/keys/ChangeLog b/kcms/keys/ChangeLog deleted file mode 100644 index 67c21160d..000000000 --- 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 index 78a9c48f2..4229f44ac 100644 --- 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 index 14e09f73e..000000000 --- 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.cpp b/kcms/keys/export_scheme_dialog.cpp deleted file mode 100644 index 50d26d737..000000000 --- a/kcms/keys/export_scheme_dialog.cpp +++ /dev/null @@ -1,84 +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; - for (const QString &component : qAsConst(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; - const auto buttons = mButtons.buttons(); - for (const QAbstractButton *button : 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.h b/kcms/keys/export_scheme_dialog.h deleted file mode 100644 index 7c8a2a925..000000000 --- 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.ui b/kcms/keys/export_scheme_dialog.ui deleted file mode 100644 index 4e8651ee6..000000000 --- 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.cpp b/kcms/keys/filteredmodel.cpp new file mode 100644 index 000000000..47c6de82e --- /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/filteredmodel.h b/kcms/keys/filteredmodel.h new file mode 100644 index 000000000..189c5d329 --- /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/globalshortcuts.cpp b/kcms/keys/globalshortcuts.cpp deleted file mode 100644 index 37437fa7d..000000000 --- a/kcms/keys/globalshortcuts.cpp +++ /dev/null @@ -1,89 +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, &KGlobalShortcutsEditor::changed, this, &GlobalShortcutsModule::markAsChanged); - - // 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/globalshortcuts.h b/kcms/keys/globalshortcuts.h deleted file mode 100644 index af1c612f5..000000000 --- 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/kcm_keys.cpp b/kcms/keys/kcm_keys.cpp new file mode 100644 index 000000000..3f3531398 --- /dev/null +++ b/kcms/keys/kcm_keys.cpp @@ -0,0 +1,198 @@ +/* + * 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, &QAbstractItemModel::modelReset, this, [this] { + setNeedsSave(false); + setRepresentsDefaults(m_shortcutsModel->isDefault()); + }); + + connect(m_shortcutsModel, &ShortcutsModel::errorOccured, this, &KCMKeys::setError); +} + +void KCMKeys::load() +{ + m_shortcutsModel->load(); +} + +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(); + }); +} + +QString KCMKeys::keySequenceToString(const QKeySequence &keySequence) const +{ + return keySequence.toString(QKeySequence::NativeText); +} + +QString KCMKeys::urlFilename(const QUrl &url) +{ + return url.fileName(); +} + +#include "kcm_keys.moc" diff --git a/kcms/keys/keys.desktop b/kcms/keys/kcm_keys.desktop similarity index 99% rename from kcms/keys/keys.desktop rename to kcms/keys/kcm_keys.desktop index b16fb0a3a..e9a9b9742 100644 --- a/kcms/keys/keys.desktop +++ b/kcms/keys/kcm_keys.desktop @@ -1,153 +1,153 @@ [Desktop Entry] -Exec=kcmshell5 keys +Exec=kcmshell5 kcm_keys Icon=preferences-desktop-keyboard-shortcut Type=Service X-KDE-ServiceTypes=KCModule X-DocPath=kcontrol/keys/index.html X-KDE-Library=kcm_keys X-KDE-ParentApp=kcontrol X-KDE-System-Settings-Parent-Category=shortcuts X-KDE-Weight=50 Name=Global Shortcuts Name[ast]=Atayos globales Name[ca]=Dreceres globals Name[ca@valencia]=Dreceres globals Name[cs]=Globální zkratky Name[da]=Globale genveje Name[de]=Globale Kurzbefehle Name[el]=Καθολικές συντομεύσεις πληκτρολογίου Name[en_GB]=Global Shortcuts Name[es]=Accesos rápidos de teclado globales Name[et]=Globaalsed kiirklahvid Name[eu]=Lasterbide orokorrak Name[fi]=Työpöydänlaajuiset pikanäppäimet Name[fr]=Raccourcis globaux Name[gl]=Atallos globais Name[he]=קיצורי מקשים גלובליים Name[hu]=Globális billentyűparancsok Name[ia]=Vias breve Global Name[id]=Pintasan Global Name[it]=Scorciatoie globali Name[ja]=グローバルショートカット Name[ko]=전역 단축키 Name[lt]=Visuotiniai spartieji klavišai Name[nb]=Globale snarveier Name[nl]=Globale sneltoetsen Name[nn]=Globale snøggtastar Name[pa]=ਗਲੋਬਲ ਸ਼ਾਰਟਕੱਟ Name[pl]=Skróty globalne Name[pt]=Atalhos Globais Name[pt_BR]=Atalhos globais Name[ru]=Глобальные комбинации клавиш Name[sk]=Globálne skratky Name[sl]=Splošne bližnjice Name[sr]=Глобалне пречице Name[sr@ijekavian]=Глобалне пречице Name[sr@ijekavianlatin]=Globalne prečice Name[sr@latin]=Globalne prečice Name[sv]=Globala genvägar Name[tr]=Genel Kısayollar Name[uk]=Загальні скорочення Name[x-test]=xxGlobal Shortcutsxx Name[zh_CN]=全局快捷键 Name[zh_TW]=全域捷徑 Comment=Global Keyboard Shortcuts Comment[ar]=اختصارات لوحة المفاتيح العموميّة Comment[bs]=Globalne prečice sa tastature Comment[ca]=Dreceres de teclat globals Comment[ca@valencia]=Dreceres de teclat globals Comment[cs]=Globální klávesové zkratky Comment[da]=Globale tastaturgenveje Comment[de]=Globale Kurzbefehle Comment[el]=Καθολικές συντομεύσεις πληκτρολογίου Comment[en_GB]=Global Keyboard Shortcuts Comment[eo]=Ĝeneralaj Klavkombinoj Comment[es]=Accesos rápidos de teclado globales Comment[et]=Globaalsed kiirklahvid Comment[eu]=Teklatuaren laster-tekla orokorrak Comment[fi]=Työpöydänlaajuiset pikanäppäimet Comment[fr]=Raccourcis globaux de clavier Comment[gl]=Atallos de teclado globais Comment[he]=קיצורי מקשים גלובליים Comment[hu]=Globális billentyűparancsok Comment[ia]=Vias breve global de claviero Comment[id]=Pintasan Keyboard Global Comment[is]=Víðværir flýtilyklar Comment[it]=Scorciatoie globali della tastiera Comment[ja]=グローバルキーボードショートカット Comment[ko]=전역 키보드 단축키 Comment[lt]=Visuotiniai klaviatūros spartieji klavišai Comment[mr]=जागतिक कळफलक शॉर्टकट Comment[nb]=Globale hurtigtaster Comment[nds]=Globaal Tastkombinatschonen Comment[nl]=Globale sneltoetsen Comment[nn]=Globale snøggtastar Comment[pa]=ਗਲੋਬਲ ਕੀ-ਬੋਰਡ ਸ਼ਾਰਟਕੱਟ Comment[pl]=Globalne skróty klawiszowe Comment[pt]=Atalhos Globais do Teclado Comment[pt_BR]=Atalhos de teclado globais Comment[ru]=Настройка глобальных комбинаций клавиш Comment[sk]=Globálne klávesové skratky Comment[sl]=Splošne tipkovne bližnjice Comment[sr]=Глобалне пречице са тастатуре Comment[sr@ijekavian]=Глобалне пречице са тастатуре Comment[sr@ijekavianlatin]=Globalne prečice sa tastature Comment[sr@latin]=Globalne prečice sa tastature Comment[sv]=Globala snabbtangenter Comment[tr]=Genel Klavye Kısayolları Comment[uk]=Загальні клавіатурні скорочення Comment[x-test]=xxGlobal Keyboard Shortcutsxx Comment[zh_CN]=全局键盘快捷键 Comment[zh_TW]=全域鍵盤捷徑 X-KDE-Keywords=Keys,Global key bindings,Key scheme,Key bindings,shortcuts,application shortcuts,global shortcuts X-KDE-Keywords[bs]=Tipke, Globalni vezovi tipki, Šema tipki, Veze tipki, Prečice, Prečice za aplikacije, globalne Prečice X-KDE-Keywords[ca]=Tecles,Vinculació de tecles globals,Esquema de tecles,Vinculació de tecles,dreceres,dreceres d'aplicació,dreceres globals X-KDE-Keywords[ca@valencia]=Tecles,Vinculació de tecles globals,Esquema de tecles,Vinculació de tecles,dreceres,dreceres d'aplicació,dreceres globals X-KDE-Keywords[da]=Taster,globale tastebindinger,tasteskema,tastebindinger,genveje,programgenveje,globale genveje X-KDE-Keywords[de]=Tasten,Tastenzuordnung,Tastenkürzel,Kurzbefehle,Tastenschema,Tastaturlayout X-KDE-Keywords[el]=Πλήκτρα,δεσμεύσεις καθολικών πλήκτρων, σχήμα πλήκτρων, δεσμεύσεις πλήκτρων,συντομεύσεις,συντομεύσεις εφαρμογών,καθολικές συντομεύσεις X-KDE-Keywords[en_GB]=Keys,Global key bindings,Key scheme,Key bindings,shortcuts,application shortcuts,global shortcuts X-KDE-Keywords[es]=Teclas,Asociaciones globales de teclas,Esquema de teclas,Asociaciones de teclas,accesos rápidos,accesos rápidos de aplicaciones,accesos rápidos globales X-KDE-Keywords[et]=klahvid,globaalsed kiirklahvid,klaviatuuriskeem,klahviseosed,kiirklahvid,rakenduse kiirklahvid,globaalsed kiirklahvid X-KDE-Keywords[eu]=tekla,laster-tekla orokor,tekla-eskema,laster-tekla,lasterbide,aplikazioen laster-teklak X-KDE-Keywords[fi]=näppäimet,globaalit näppäinsidokset,yleiset näppäinsidokset,työpöydänlaajuiset näppäinsidokset,globaalit pikanäppäimet,työpöydänlaajuiset pikanäppäimet,globaalit näppäinyhdistelmät,työpöydänlaajuiset näppäinyhdistelmät,näppäimistön yleispikavalinnat,näppäimistön globaalit pikavalinnat,näppäinsidokset,pikanäppäimet,näppäimistön pikavalinnat, näppäinyhdistelmät,näppäinoikotiet,sovellusten pikanäppäimet,sovellusten näppäinyhdistelmät,sovellusten näppäinoikotiet X-KDE-Keywords[fr]=Touches, Association globale des touches, Schéma des touches, Association de touches, Raccourci des applications, Raccourcis globaux X-KDE-Keywords[ga]=Eochracha,Ceangail chomhchoiteanna eochracha,Scéim eochracha,Ceangail eochracha,aicearraí,aicearraí feidhmchláir,aicearraí comhchoiteanna X-KDE-Keywords[gl]=tecla, atallo, asociación, esquema de teclas, atallo de teclado, asociación de teclas, atallo de aplicación, atallos globais X-KDE-Keywords[hu]=Billentyűk,Globális gyorsbillentyűk,Billentyűséma,Gyorsbillentyűk,alkalmazás gyorsbillentyűk,globális gyorsbillentyűk X-KDE-Keywords[ia]=Claves,ligamines de clave global,schema de clave,ligamines de clave,vias breve,vias breve de application,vias breve global X-KDE-Keywords[id]=Tuts,pengikat tuts Global,skema Tuts,pengikat Tuts,pintasan,pintasan aplikasi,pintasan global X-KDE-Keywords[it]=tasti,scorciatoie da tastiera globali,schema di tasti,scorciatoie,scorciatoie delle applicazioni,scorciatoie globali X-KDE-Keywords[kk]=Keys,Global key bindings,Key scheme,Key bindings,shortcuts,application shortcuts,global shortcuts X-KDE-Keywords[km]=Keys,Global key bindings,Key scheme,Key bindings,shortcuts,application shortcuts,global shortcuts X-KDE-Keywords[ko]=Keys,Global key bindings,Key scheme,Key bindings,shortcuts,application shortcuts,키,전역 키 바인딩,키 바인딩,단축키,프로그램 단축키 X-KDE-Keywords[lt]=Klavišai,klavisai,Globalūs klavišų susiejimai,globalus klavisu susiejimai,visuotiniai klavišų susiejimai,visuotiniai klavisu susiejimai,klavisu schema,klavišų schema,klavišų susiejimai,klavisu susiejimai,trumpiniai,spartieji klavišai,spartieji klavisai,klavišų deriniai,klavisu deriniai,programos spartieji klavišai,programos spartieji klavisai,visuotiniai spartieji klavišai,visuotiniai spartieji klavisai,globalūs spartieji klavišai,globalus spartieji klavisai,visuotiniai klavišų deriniai,visuotiniai klavisu deriniai,globalūs klavišų deriniai,globalus klavisu deriniai X-KDE-Keywords[mr]=कीज, ग्लोबल की बाईंडिंगज, की स्कीम, की बाईंडिंगज, शॉर्टकटस, अप्लिकेशन शॉर्टकटस, ग्लोबल शॉर्टकटस X-KDE-Keywords[nb]=Taster,Globale tastebindinger,Tastaturutforming,Tastebindinger,snarveier,programsnarveier,globale snarveier X-KDE-Keywords[nds]=Tasten,Globaal Tasttowiesen,Tasttowiesen,Tastkombinatschonen,Programm-Tastkombinatschonen,Globaal Tastkombinatschonen X-KDE-Keywords[nl]=toetsen,globale toetsbindingen,toetsenschema,toetsbindingen,sneltoetsen,sneltoetsen van toepassing,globale sneltoetsen X-KDE-Keywords[nn]=tastar,globale tastebindinger,tastaturutforming,tastebindinger,snarvegar,snøggtastar,programsnarvegar,globale snarvegar X-KDE-Keywords[pl]=Klawisze,Globalne powiązania klawiszy,Motyw klawiszy,Powiązania klawiszy,skróty,skróty programów,globalne skróty X-KDE-Keywords[pt]=Teclas,combinações de teclas,global,esquema de teclas,atalhos de teclado,atalhos,atalhos das aplicações X-KDE-Keywords[pt_BR]=Teclas,combinações de teclas global,esquema de teclas,atalhos de teclado,atalhos,atalhos dos aplicativos, atalhos globais X-KDE-Keywords[ru]=Keys,Global key bindings,Key scheme,Key bindings,shortcuts,application shortcuts,global shortcuts,клавиши,глобальные привязки клавиш,схема,привязки клавиш,комбинации клавиш,комбинации клавиш в приложениях,глобальные привязки клавиш X-KDE-Keywords[sk]=Klávesy,Globálne klávesové skratky,Klávesová schéma,Klávesové skratky,skratky,skratky aplikácie,globálne skratky X-KDE-Keywords[sl]=tipke,splošne tipkovne bližnjice,tipkovna shema,shema tipk,tipkovne bližnjice,vezave tipk,bližnjice,programske bližnjice,splošne bližnjice X-KDE-Keywords[sr]=Keys,Global key bindings,Key scheme,Key bindings,shortcuts,application shortcuts,global shortcuts,тастери,глобалне свезе тастера,шема тастера,свезе тастера,пречице,пречице програма,глобалне пречице X-KDE-Keywords[sr@ijekavian]=Keys,Global key bindings,Key scheme,Key bindings,shortcuts,application shortcuts,global shortcuts,тастери,глобалне свезе тастера,шема тастера,свезе тастера,пречице,пречице програма,глобалне пречице X-KDE-Keywords[sr@ijekavianlatin]=Keys,Global key bindings,Key scheme,Key bindings,shortcuts,application shortcuts,global shortcuts,tasteri,globalne sveze tastera,šema tastera,sveze tastera,prečice,prečice programa,globalne prečice X-KDE-Keywords[sr@latin]=Keys,Global key bindings,Key scheme,Key bindings,shortcuts,application shortcuts,global shortcuts,tasteri,globalne sveze tastera,šema tastera,sveze tastera,prečice,prečice programa,globalne prečice X-KDE-Keywords[sv]=Tangenter,Globala tangentbindingar,Tangentschema,Tangentbindingar,genvägar,programgenvägar,globala genvägar X-KDE-Keywords[tr]=Tuşlar,Genel kısayollar,Klavye şeması,Klavye Kısayolları,kısayollar,uygulama kısayolları,genel kısayollar, kısayol tuşları X-KDE-Keywords[uk]=Keys,Global key bindings,Key scheme,Key bindings,shortcuts,application shortcuts,global shortcuts,клавіатура,скорочення,клавіатурні скорочення,схема,загальні клавіатурні скорочення,клавіатурні скорочення програми,загальні скорочення X-KDE-Keywords[vi]=Phím,tổ hợp phím toàn cục,phối hợp phím,tổ hợp phím,gõ tắt,gõ tắt cho ứng dụng X-KDE-Keywords[x-test]=xxKeysxx,xxGlobal key bindingsxx,xxKey schemexx,xxKey bindingsxx,xxshortcutsxx,xxapplication shortcutsxx,xxglobal shortcutsxx X-KDE-Keywords[zh_CN]=Keys,Global key bindings,Key scheme,Key bindings,shortcuts,application shortcuts,global shortcuts,按键,全局键绑定,按键方案,按键绑定,快捷键,应用程序,全局快捷键 X-KDE-Keywords[zh_TW]=Keys,Global key bindings,Key scheme,Key bindings,shortcuts,application shortcuts,global shortcuts Categories=Qt;KDE;X-KDE-settings-accessibility; diff --git a/kcms/keys/kcm_keys.h b/kcms/keys/kcm_keys.h new file mode 100644 index 000000000..0917bbdb5 --- /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 QString keySequenceToString(const QKeySequence &keySequence) const; + Q_INVOKABLE QString urlFilename(const QUrl &url); + + 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/kglobalshortcutseditor.cpp b/kcms/keys/kglobalshortcutseditor.cpp deleted file mode 100644 index 958b9c489..000000000 --- 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() const; - QDBusObjectPath dbusPath() const; - -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() const - { - return _path; - } - - -KShortcutsEditor *ComponentData::editor() const - { - 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); - - const auto actions = sourceDF.readActions(); - for (const QString &actionId : actions) { - - 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()); - - const QStringList sequencesStrings = sourceDF.actionGroup(actionId).readEntry(QStringLiteral("X-KDE-Shortcuts"), QString()).split(QLatin1Char('/')); - QList sequences; - if (!sequencesStrings.isEmpty()) { - for (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()); - - const QStringList sequencesStrings = sourceDF.desktopGroup().readEntry(QStringLiteral("X-KDE-Shortcuts"), QString()).split(QLatin1Char('/')); - QList sequences; - if (!sequencesStrings.isEmpty()) { - for (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 - const auto groupList = config.groupList(); - for (const QString &group : 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; - } - - const QList components = componentsRc; - for (const QDBusObjectPath &componentPath : components) { - d->loadComponent(componentPath); - } -} - - -void KGlobalShortcutsEditor::save() -{ - // The editors are responsible for the saving - //qDebug() << "Save the changes"; - for (ComponentData *cd : qAsConst(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. - for (ComponentData *cd : qAsConst(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. - for (ComponentData *cd : qAsConst(d->components)) { - KConfigGroup group(config, cd->uniqueName()); - if (group.exists()) { - //qDebug() << "Importing" << cd->uniqueName(); - cd->editor()->importConfiguration(&group); - } - } -} - -void KGlobalShortcutsEditor::exportConfiguration(const QStringList &components, KConfig *config) const - { - for (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"; - for (ComponentData *cd : qAsConst(d->components)) { - cd->editor()->undoChanges(); - } -} - - -bool KGlobalShortcutsEditor::isModified() const -{ - for (const ComponentData *cd : qAsConst(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; - } - const 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. - for (const QString &shortcutContext : shortcutContexts) { - - QDBusReply< QList > shortcutsRc = - component.allShortcutInfos(shortcutContext); - if (!shortcutsRc.isValid()) - { - //qDebug() << "allShortcutInfos() failed for " << componentPath.path() << shortcutContext; - continue; - } - const 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. - for (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); - } - } - - QString componentFriendlyName = shortcuts[0].componentFriendlyName(); - - if (shortcuts[0].contextUniqueName() != QLatin1String("default")) - { - componentFriendlyName += - QString('[') + shortcuts[0].contextFriendlyName() + QString(']'); - } - - q->addCollection(col, componentPath, componentContextId, componentFriendlyName ); - - } - - return true; -} - - -void KGlobalShortcutsEditor::KGlobalShortcutsEditorPrivate::removeComponent( - const QString &componentUnique ) -{ - // TODO: Remove contexts too. - - const auto keys = components.keys(); - for (const QString &text : 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.h b/kcms/keys/kglobalshortcutseditor.h deleted file mode 100644 index 90a3d60c1..000000000 --- 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(const 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.ui b/kcms/keys/kglobalshortcutseditor.ui deleted file mode 100644 index f389a9c9a..000000000 --- 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 index 000000000..6c30e6dbf --- /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 + +Kirigami.AbstractListItem { + id: root + highlighted: false + hoverEnabled: true + width: shortcutsList.width + onClicked: shortcutsList.selectedIndex = index + contentItem: ColumnLayout { + clip: true + Item { + Layout.alignment: Qt.AlignTop + Layout.preferredHeight: topRow.implicitHeight + Layout.fillWidth: true + RowLayout { + id: topRow + anchors.fill: parent + Kirigami.Heading { + id: displayLabel + text: i18nc("%1 is the name action that is triggered by the key sequences following after :", "%1:", model.display) + level: 5 + } + QQC2.Label { + id: keySequenceList + 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 { + Layout.alignment: Qt.AlignRight + id: expandButton + icon.name: "expand" + onClicked: { + root.state == 'expanded' ? shortcutsList.selectedIndex = -1 : shortcutsList.selectedIndex = index + } + } + } + MouseArea { + id: handMouseArea + anchors.fill: topRow + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.NoButton + } + } + Loader { + id: editLoader + active: false + visible: false + Layout.fillWidth: true + sourceComponent: RowLayout { + readonly property var originalIndex : kcm.filteredModel.mapToSource(dm.modelIndex(index)) + spacing: 0 + ColumnLayout { + Layout.alignment: Qt.AlignTop + Layout.preferredWidth: parent.width * 0.5 + Kirigami.Heading { + level: 4 + text: model.defaultShortcuts && model.defaultShortcuts.length != 0 ? + i18ncp("%1 decides if singular or plural will be used", "Default shortcut", + "Default shortcuts", model.defaultShortcuts.length) : + i18n("No default shortcuts") + } + 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 { + 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") + } + } + } + } + } + } + } + } + states: [ + State { + name: "expanded" + when: shortcutsList.selectedIndex == index || shortcutsList.count == 1 + PropertyChanges { + target: root + hoverEnabled: false + } + PropertyChanges { + target: displayLabel + level: 3 + Layout.fillWidth: true + } + PropertyChanges { + target: keySequenceList + visible: false + } + PropertyChanges { + target: expandButton + icon.name: "collapse" + } + PropertyChanges { + target: handMouseArea + cursorShape: Qt.ArrowCursor + } + PropertyChanges { + target: editLoader + active: true + visible: true + } + } + ] + Behavior on height { + NumberAnimation { + properties: "height" + duration: Kirigami.Units.shortDuration + } + } +} diff --git a/kcms/keys/package/contents/ui/main.qml b/kcms/keys/package/contents/ui/main.qml new file mode 100644 index 000000000..6fbc4baca --- /dev/null +++ b/kcms/keys/package/contents/ui/main.qml @@ -0,0 +1,302 @@ +/* + * 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.12 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 + property alias exportActive: exportInfo.visible + readonly property bool errorOccured: kcm.lastError != "" + ColumnLayout { + anchors.fill: parent + Kirigami.InlineMessage { + Layout.fillWidth: true + visible: errorOccured + 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: !errorOccured && !exportActive + Layout.fillWidth: true + Binding { + target: kcm.filteredModel + property: "filter" + value: search.text + } + } + GridLayout { + enabled: !errorOccured + 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 + opacity: model.pendingDeletion ? 0.3 : 1 + } + QQC2.Label { + Layout.fillWidth: true + text: model.display + color: foregroundColor + opacity: model.pendingDeletion ? 0.3 : 1 + } + QQC2.ToolButton { + Layout.preferredHeight: Kirigami.Units.iconSizes.small + Kirigami.Units.largeSpacing + Layout.preferredWidth: Layout.preferredHeight + visible: !exportActive && !model.pendingDeletion + opacity: componentDelegate.containsMouse || componentDelegate.ListView.isCurrentItem ? 1 : 0 + enabled: opacity + icon.name: "edit-delete" + icon.width: Kirigami.Units.iconSizes.small + onClicked: model.pendingDeletion = true + QQC2.ToolTip { + text: i18n("Remove all shortcuts for %1", model.display) + } + } + QQC2.ToolButton { + Layout.preferredHeight: Kirigami.Units.iconSizes.small + Kirigami.Units.largeSpacing + Layout.preferredWidth: Layout.preferredHeight + visible: !exportActive && model.pendingDeletion + icon.name: "edit-undo" + icon.width: Kirigami.Units.iconSizes.small + onClicked: model.pendingDeletion = false + QQC2.ToolTip { + text: i18n("Undo deletion") + } + } + 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) + } + } + } + } + onCurrentItemChanged: dm.rootIndex = kcm.filteredModel.index(currentIndex, 0) + onCurrentIndexChanged: shortcutsList.selectedIndex = -1 + } + } + Item { + Layout.fillHeight: true + Layout.fillWidth: true + QQC2.ScrollView { + enabled: !exportActive + id: shortcutsScroll + anchors.fill:parent + clip: true + Component.onCompleted: background.visible = true + ListView { + clip:true + id: shortcutsList + property int selectedIndex: -1 + model: DelegateModel { + id: dm + model: rootIndex.valid ? kcm.filteredModel : undefined + delegate: ShortcutActionDelegate {} + } + } + } + Kirigami.PlaceholderMessage { + anchors.centerIn: parent + visible: components.currentIndex == -1 + text: i18n("Select an item from the list to view its shortcuts here") + } + } + 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: { + if (save) { + kcm.writeScheme(fileUrls[0]) + } else { + var schemes = schemeBox.model + schemes.splice(schemes.length - 1, 0, {name: kcm.urlFilename(fileUrls[0]), url: fileUrls[0]}) + schemeBox.model = schemes + schemeBox.currentIndex = schemes.length - 2 + } + 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.model[schemeBox.currentIndex]["url"]) + importSheet.close() + } + } + } + } + } + } +} + diff --git a/kcms/keys/package/metadata.desktop b/kcms/keys/package/metadata.desktop new file mode 100644 index 000000000..45dd460b1 --- /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 index 74a27b428..000000000 --- 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.cpp b/kcms/keys/select_scheme_dialog.cpp deleted file mode 100644 index d13092e7f..000000000 --- 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, QOverload::of(&KComboBox::activated), - this, &SelectSchemeDialog::schemeActivated); - 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.h b/kcms/keys/select_scheme_dialog.h deleted file mode 100644 index 964ed7a6c..000000000 --- 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.ui b/kcms/keys/select_scheme_dialog.ui deleted file mode 100644 index 6d1e702ed..000000000 --- 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.cpp b/kcms/keys/shortcutsmodel.cpp new file mode 100644 index 000000000..867a01a0b --- /dev/null +++ b/kcms/keys/shortcutsmodel.cpp @@ -0,0 +1,489 @@ +/* + * 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(); + 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, 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) { + if (component.pendingDeletion) { + removeComponent(component); + } + 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, [&, watcher] { + 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) { + if (component.pendingDeletion) { + return true; + } + 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; + case PendingDeletionRole: + return component.pendingDeletion; + } + return QVariant(); +} + +bool ShortcutsModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!checkIndex(index) || index.parent().isValid()) { + return false; + } + switch (role) { + case CheckedRole: + m_components[index.row()].checked = value.toBool(); + emit dataChanged(index, index, {CheckedRole}); + return true; + case PendingDeletionRole: + m_components[index.row()].pendingDeletion = value.toBool(); + emit dataChanged(index, index, {PendingDeletionRole}); + return true; + } + return false; +} + +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")}, + {PendingDeletionRole, QByteArrayLiteral("pendingDeletion")} + }; +} + +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 Component &component) +{ + const QString &uniqueName = component.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" << reply.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; + } + auto it = std::find_if(m_components.begin(), m_components.end(), [&](const Component &c) { + return c.uniqueName == uniqueName; + }); + const int row = it - m_components.begin(); + beginRemoveRows(QModelIndex(), row, row); + m_components.remove(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")); +} diff --git a/kcms/keys/shortcutsmodel.h b/kcms/keys/shortcutsmodel.h new file mode 100644 index 000000000..ea6b4d202 --- /dev/null +++ b/kcms/keys/shortcutsmodel.h @@ -0,0 +1,112 @@ +/* + * 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; + bool pendingDeletion; +}; + +class ShortcutsModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum Roles { + SectionRole = Qt::UserRole, + ComponentRole, + ActionRole, + ActiveShortcutsRole, + DefaultShortcutsRole, + CustomShortcutsRole, + CheckedRole, + PendingDeletionRole + }; + 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 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 removeComponent(const Component &component); + void genericErrorOccured(const QString &description, const QDBusError &error); + + KGlobalAccelInterface *m_globalAccelInterface; + QVector m_components; +}; + +#endif // SHORTCUTSMODEL_H