| 1 | /* |
| 2 | This file is part of the KDE libraries |
| 3 | SPDX-FileCopyrightText: 2008 Alexander Dymo <adymo@kdevelop.org> |
| 4 | |
| 5 | SPDX-License-Identifier: LGPL-2.0-or-later |
| 6 | */ |
| 7 | #include "kshortcutsdialog_p.h" |
| 8 | |
| 9 | #include <QComboBox> |
| 10 | #include <QDir> |
| 11 | #include <QDomDocument> |
| 12 | #include <QFile> |
| 13 | #include <QFileDialog> |
| 14 | #include <QInputDialog> |
| 15 | #include <QLabel> |
| 16 | #include <QMenu> |
| 17 | #include <QPushButton> |
| 18 | #include <QStandardPaths> |
| 19 | #include <QTextStream> |
| 20 | |
| 21 | #include <KConfigGroup> |
| 22 | #include <KMessageBox> |
| 23 | #include <KSharedConfig> |
| 24 | |
| 25 | #include "kactioncollection.h" |
| 26 | #include "kshortcutschemeshelper_p.h" |
| 27 | #include "kshortcutsdialog.h" |
| 28 | #include "kxmlguiclient.h" |
| 29 | #include <debug.h> |
| 30 | |
| 31 | KShortcutSchemesEditor::KShortcutSchemesEditor(KShortcutsDialog *parent) |
| 32 | : QGroupBox(i18nc("@title:group" , "Shortcut Schemes" ), parent) |
| 33 | , m_dialog(parent) |
| 34 | { |
| 35 | QHBoxLayout *l = new QHBoxLayout(this); |
| 36 | |
| 37 | QLabel *schemesLabel = new QLabel(i18n("Current scheme:" ), this); |
| 38 | l->addWidget(schemesLabel); |
| 39 | |
| 40 | m_schemesList = new QComboBox(this); |
| 41 | m_schemesList->setEditable(false); |
| 42 | refreshSchemes(); |
| 43 | m_schemesList->setSizeAdjustPolicy(QComboBox::AdjustToContents); |
| 44 | schemesLabel->setBuddy(m_schemesList); |
| 45 | l->addWidget(m_schemesList); |
| 46 | |
| 47 | m_newScheme = new QPushButton(QIcon::fromTheme(QStringLiteral("document-new" )), i18nc("@action:button" , "New…" )); |
| 48 | l->addWidget(m_newScheme); |
| 49 | |
| 50 | m_deleteScheme = new QPushButton(QIcon::fromTheme(QStringLiteral("edit-delete" )), i18nc("@action:button" , "Delete" )); |
| 51 | l->addWidget(m_deleteScheme); |
| 52 | |
| 53 | QPushButton *moreActions = new QPushButton(QIcon::fromTheme(QStringLiteral("view-more-symbolic" )), i18nc("@action:button" , "More Actions" )); |
| 54 | l->addWidget(moreActions); |
| 55 | |
| 56 | m_moreActionsMenu = new QMenu(this); |
| 57 | m_moreActionsMenu->addAction(icon: QIcon::fromTheme(QStringLiteral("document-save" )), |
| 58 | i18nc("@action:inmenu" , "Save shortcuts to scheme" ), |
| 59 | args: this, |
| 60 | args: &KShortcutSchemesEditor::saveAsDefaultsForScheme); |
| 61 | m_moreActionsMenu->addAction(icon: QIcon::fromTheme(QStringLiteral("document-export" )), |
| 62 | i18nc("@action:inmenu" , "Export Scheme…" ), |
| 63 | args: this, |
| 64 | args: &KShortcutSchemesEditor::exportShortcutsScheme); |
| 65 | m_moreActionsMenu->addAction(icon: QIcon::fromTheme(QStringLiteral("document-import" )), |
| 66 | i18nc("@action:inmenu" , "Import Scheme…" ), |
| 67 | args: this, |
| 68 | args: &KShortcutSchemesEditor::importShortcutsScheme); |
| 69 | moreActions->setMenu(m_moreActionsMenu); |
| 70 | |
| 71 | l->addStretch(stretch: 1); |
| 72 | |
| 73 | connect(sender: m_schemesList, signal: &QComboBox::textActivated, context: this, slot: &KShortcutSchemesEditor::shortcutsSchemeChanged); |
| 74 | connect(sender: m_newScheme, signal: &QPushButton::clicked, context: this, slot: &KShortcutSchemesEditor::newScheme); |
| 75 | connect(sender: m_deleteScheme, signal: &QPushButton::clicked, context: this, slot: &KShortcutSchemesEditor::deleteScheme); |
| 76 | updateDeleteButton(); |
| 77 | } |
| 78 | |
| 79 | void KShortcutSchemesEditor::refreshSchemes() |
| 80 | { |
| 81 | QStringList schemes; |
| 82 | schemes << QStringLiteral("Default" ); |
| 83 | // List files in the shortcuts subdir, each one is a scheme. See KShortcutSchemesHelper::{shortcutSchemeFileName,exportActionCollection} |
| 84 | const QStringList shortcutsDirs = QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, |
| 85 | fileName: QCoreApplication::applicationName() + QLatin1String("/shortcuts" ), |
| 86 | options: QStandardPaths::LocateDirectory); |
| 87 | qCDebug(DEBUG_KXMLGUI) << "shortcut scheme dirs:" << shortcutsDirs; |
| 88 | for (const QString &dir : shortcutsDirs) { |
| 89 | const auto files = QDir(dir).entryList(filters: QDir::Files | QDir::NoDotAndDotDot); |
| 90 | for (const QString &file : files) { |
| 91 | qCDebug(DEBUG_KXMLGUI) << "shortcut scheme file:" << file; |
| 92 | schemes << file; |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | m_schemesList->clear(); |
| 97 | m_schemesList->addItems(texts: schemes); |
| 98 | |
| 99 | KConfigGroup group(KSharedConfig::openConfig(), QStringLiteral("Shortcut Schemes" )); |
| 100 | const QString currentScheme = group.readEntry(key: "Current Scheme" , aDefault: "Default" ); |
| 101 | qCDebug(DEBUG_KXMLGUI) << "Current Scheme" << currentScheme; |
| 102 | |
| 103 | const int schemeIdx = m_schemesList->findText(text: currentScheme); |
| 104 | if (schemeIdx > -1) { |
| 105 | m_schemesList->setCurrentIndex(schemeIdx); |
| 106 | } else { |
| 107 | qCWarning(DEBUG_KXMLGUI) << "Current scheme" << currentScheme << "not found in" << shortcutsDirs; |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | void KShortcutSchemesEditor::newScheme() |
| 112 | { |
| 113 | bool ok; |
| 114 | const QString newName = |
| 115 | QInputDialog::getText(parent: this, i18nc("@title:window" , "Name for New Scheme" ), i18n("Name for new scheme:" ), echo: QLineEdit::Normal, i18n("New Scheme" ), ok: &ok); |
| 116 | if (!ok) { |
| 117 | return; |
| 118 | } |
| 119 | |
| 120 | if (m_schemesList->findText(text: newName) != -1) { |
| 121 | KMessageBox::error(parent: this, i18n("A scheme with this name already exists." )); |
| 122 | return; |
| 123 | } |
| 124 | |
| 125 | const QString newSchemeFileName = KShortcutSchemesHelper::writableApplicationShortcutSchemeFileName(schemeName: newName); |
| 126 | QDir().mkpath(dirPath: QFileInfo(newSchemeFileName).absolutePath()); |
| 127 | QFile schemeFile(newSchemeFileName); |
| 128 | if (!schemeFile.open(flags: QFile::WriteOnly | QFile::Truncate)) { |
| 129 | qCWarning(DEBUG_KXMLGUI) << "Couldn't write to" << newSchemeFileName; |
| 130 | return; |
| 131 | } |
| 132 | |
| 133 | QDomDocument doc; |
| 134 | QDomElement docElem = doc.createElement(QStringLiteral("gui" )); |
| 135 | doc.appendChild(newChild: docElem); |
| 136 | QDomElement elem = doc.createElement(QStringLiteral("ActionProperties" )); |
| 137 | docElem.appendChild(newChild: elem); |
| 138 | |
| 139 | QTextStream out(&schemeFile); |
| 140 | out << doc.toString(indent: 4); |
| 141 | |
| 142 | m_schemesList->addItem(atext: newName); |
| 143 | m_schemesList->setCurrentIndex(m_schemesList->findText(text: newName)); |
| 144 | updateDeleteButton(); |
| 145 | Q_EMIT shortcutsSchemeChanged(newName); |
| 146 | } |
| 147 | |
| 148 | void KShortcutSchemesEditor::deleteScheme() |
| 149 | { |
| 150 | if (KMessageBox::questionTwoActions(parent: this, |
| 151 | i18n("Do you really want to delete the scheme %1?\n\ |
| 152 | Note that this will not remove any system wide shortcut schemes." , |
| 153 | currentScheme()), |
| 154 | title: QString(), |
| 155 | primaryAction: KStandardGuiItem::del(), |
| 156 | secondaryAction: KStandardGuiItem::cancel()) |
| 157 | == KMessageBox::SecondaryAction) { |
| 158 | return; |
| 159 | } |
| 160 | |
| 161 | // delete the scheme for the app itself |
| 162 | QFile::remove(fileName: KShortcutSchemesHelper::writableApplicationShortcutSchemeFileName(schemeName: currentScheme())); |
| 163 | |
| 164 | // delete all scheme files we can find for xmlguiclients in the user directories |
| 165 | const auto dialogCollections = m_dialog->actionCollections(); |
| 166 | for (KActionCollection *collection : dialogCollections) { |
| 167 | const KXMLGUIClient *client = collection->parentGUIClient(); |
| 168 | if (!client) { |
| 169 | continue; |
| 170 | } |
| 171 | QFile::remove(fileName: KShortcutSchemesHelper::writableShortcutSchemeFileName(componentName: client->componentName(), schemeName: currentScheme())); |
| 172 | } |
| 173 | |
| 174 | m_schemesList->removeItem(index: m_schemesList->findText(text: currentScheme())); |
| 175 | updateDeleteButton(); |
| 176 | Q_EMIT shortcutsSchemeChanged(currentScheme()); |
| 177 | } |
| 178 | |
| 179 | QString KShortcutSchemesEditor::currentScheme() |
| 180 | { |
| 181 | return m_schemesList->currentText(); |
| 182 | } |
| 183 | |
| 184 | void KShortcutSchemesEditor::exportShortcutsScheme() |
| 185 | { |
| 186 | // ask user about dir |
| 187 | QString path = QFileDialog::getSaveFileName(parent: this, i18nc("@title:window" , "Export Shortcuts" ), dir: QDir::currentPath(), i18n("Shortcuts (*.shortcuts)" )); |
| 188 | if (path.isEmpty()) { |
| 189 | return; |
| 190 | } |
| 191 | |
| 192 | m_dialog->exportConfiguration(path); |
| 193 | } |
| 194 | |
| 195 | void KShortcutSchemesEditor::importShortcutsScheme() |
| 196 | { |
| 197 | // ask user about dir |
| 198 | QString path = QFileDialog::getOpenFileName(parent: this, i18nc("@title:window" , "Import Shortcuts" ), dir: QDir::currentPath(), i18n("Shortcuts (*.shortcuts)" )); |
| 199 | if (path.isEmpty()) { |
| 200 | return; |
| 201 | } |
| 202 | |
| 203 | m_dialog->importConfiguration(path); |
| 204 | } |
| 205 | |
| 206 | void KShortcutSchemesEditor::saveAsDefaultsForScheme() |
| 207 | { |
| 208 | if (KShortcutSchemesHelper::saveShortcutScheme(collections: m_dialog->actionCollections(), schemeName: currentScheme())) { |
| 209 | KMessageBox::information(parent: this, i18n("Shortcut scheme successfully saved." )); |
| 210 | } else { |
| 211 | // We'd need to return to return more than a bool, to show more details here. |
| 212 | KMessageBox::error(parent: this, i18n("Error saving the shortcut scheme." )); |
| 213 | } |
| 214 | } |
| 215 | |
| 216 | void KShortcutSchemesEditor::updateDeleteButton() |
| 217 | { |
| 218 | m_deleteScheme->setEnabled(m_schemesList->count() >= 1); |
| 219 | } |
| 220 | |
| 221 | void KShortcutSchemesEditor::(QAction *action) |
| 222 | { |
| 223 | m_moreActionsMenu->addAction(action); |
| 224 | } |
| 225 | |