diff --git a/libs/ui/kis_action.cpp b/libs/ui/kis_action.cpp index bbb51004a9..9cc5da7869 100644 --- a/libs/ui/kis_action.cpp +++ b/libs/ui/kis_action.cpp @@ -1,156 +1,159 @@ /* * Copyright (c) 2013 Sven Langkamp * * 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 "kis_action.h" #include "kis_action_manager.h" #include class Q_DECL_HIDDEN KisAction::Private { public: Private() : flags(NONE), conditions(NO_CONDITION), actionManager(0) {} ActivationFlags flags; ActivationConditions conditions; QStringList excludedNodeTypes; QString operationID; KisActionManager* actionManager; }; KisAction::KisAction(QObject* parent) : QWidgetAction(parent) , d(new Private) { connect(this, SIGNAL(changed()), SLOT(slotChanged())); } KisAction::KisAction(const QString& text, QObject* parent) : QWidgetAction(parent) , d(new KisAction::Private) { QAction::setText(text); connect(this, SIGNAL(changed()), SLOT(slotChanged())); } KisAction::KisAction(const QIcon &icon, const QString& text, QObject* parent) : QWidgetAction(parent) , d(new Private) { QAction::setIcon(icon); QAction::setText(text); connect(this, SIGNAL(changed()), SLOT(slotChanged())); } KisAction::~KisAction() { delete d; } KisAction *makeKisAction(QString name, QObject *parent) { KisAction* a = new KisAction(parent); KisActionRegistry::instance()->propertizeAction(name, a); KisActionRegistry::instance()->addAction(name, a); // TODO: Add other static data (activationFlags, etc.) using getActionXml() return a; } // Using a dynamic QObject property is done for compatibility with KAction and // XmlGui. We may merge KisAction into the XmlGui code to make this unnecessary, // but that is probably a lot of work for little benefit. We currently store a // single default shortcut, but the old system used a list (to store default // primary/alternate shortcuts for local and global settings) so we marshal it // for compatibility. void KisAction::setDefaultShortcut(const QKeySequence &shortcut) { QList listifiedShortcut; - listifiedShortcut.append(shortcut); + // Use the empty list to represent no shortcut + if (shortcut != QKeySequence("")) { + listifiedShortcut.append(shortcut); + } setProperty("defaultShortcuts", qVariantFromValue(listifiedShortcut)); } QKeySequence KisAction::defaultShortcut() const { auto listifiedShortcut = property("defaultShortcuts").value >(); if (listifiedShortcut.isEmpty()) { return QKeySequence(); } else { return listifiedShortcut.first(); } } void KisAction::setActivationFlags(KisAction::ActivationFlags flags) { d->flags = flags; } KisAction::ActivationFlags KisAction::activationFlags() { return d->flags; } void KisAction::setActivationConditions(KisAction::ActivationConditions conditions) { d->conditions = conditions; } KisAction::ActivationConditions KisAction::activationConditions() { return d->conditions; } void KisAction::setExcludedNodeTypes(const QStringList &nodeTypes) { d->excludedNodeTypes = nodeTypes; } const QStringList& KisAction::excludedNodeTypes() const { return d->excludedNodeTypes; } void KisAction::setActionEnabled(bool enabled) { setEnabled(enabled); } void KisAction::setActionManager(KisActionManager* actionManager) { d->actionManager = actionManager; } void KisAction::setOperationID(const QString& id) { d->operationID = id; connect(this, SIGNAL(triggered()), this, SLOT(slotTriggered())); } void KisAction::slotTriggered() { if (d->actionManager && !d->operationID.isEmpty()) { d->actionManager->runOperation(d->operationID); } } void KisAction::slotChanged() { emit sigEnableSlaves(isEnabled()); } diff --git a/libs/widgetutils/kis_action_registry.cpp b/libs/widgetutils/kis_action_registry.cpp index 4d00eb0dec..417993a925 100644 --- a/libs/widgetutils/kis_action_registry.cpp +++ b/libs/widgetutils/kis_action_registry.cpp @@ -1,471 +1,476 @@ /* * Copyright (c) 2015 Michael Abrahams * * 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 3 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 #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "KoResourcePaths.h" #include "kis_icon_utils.h" #include "kactioncollection.h" #include "kactioncategory.h" #include "kis_action_registry.h" #include "kshortcutschemeshelper_p.h" namespace { /** * We associate several pieces of information with each shortcut. The first * piece of information is a QDomElement, containing the raw data from the * .action XML file. The second and third are QKeySequences, the first of * which is the default shortcut, the last of which is any custom shortcut. * The last two are the KActionCollection and KActionCategory used to * organize the shortcut editor. */ struct ActionInfoItem { QDomElement xmlData; QKeySequence defaultShortcut; QKeySequence customShortcut; QString collectionName; QString categoryName; }; // Convenience macros to extract text of a child node. QString getChildContent(QDomElement xml, QString node) { return xml.firstChildElement(node).text(); }; ActionInfoItem emptyActionInfo; // Used as default return value QString quietlyTranslate(const QString &s) { if (s.isEmpty()) { return s; } if (i18n(s.toUtf8().constData()).isEmpty()) { dbgAction << "No translation found for" << s; return s; } return i18n(s.toUtf8().constData()); }; QKeySequence preferredShortcut(ActionInfoItem action) { if (action.customShortcut.isEmpty()) { return action.defaultShortcut; } else { return action.customShortcut; } }; }; class Q_DECL_HIDDEN KisActionRegistry::Private { public: Private(KisActionRegistry *_q) : q(_q) {}; // This is the main place containing ActionInfoItems. QMap actionInfoList; void loadActionFiles(); void loadActionCollections(); void loadCustomShortcuts(QString filename = QStringLiteral("kritashortcutsrc")); ActionInfoItem &actionInfo(const QString &name) { if (!actionInfoList.contains(name)) { dbgAction << "Tried to look up info for unknown action" << name; } return actionInfoList[name]; }; KisActionRegistry *q; KActionCollection * defaultActionCollection; QMap actionCollections; }; Q_GLOBAL_STATIC(KisActionRegistry, s_instance); KisActionRegistry *KisActionRegistry::instance() { return s_instance; }; KisActionRegistry::KisActionRegistry() : d(new KisActionRegistry::Private(this)) { d->loadActionFiles(); KConfigGroup cg = KSharedConfig::openConfig()->group("Shortcut Schemes"); QString schemeName = cg.readEntry("Current Scheme", "Default"); loadShortcutScheme(schemeName); loadCustomShortcuts(); } QKeySequence KisActionRegistry::getCustomShortcut(const QString &name) { return d->actionInfo(name).customShortcut; }; QKeySequence KisActionRegistry::getPreferredShortcut(const QString &name) { return preferredShortcut(d->actionInfo(name)); }; QKeySequence KisActionRegistry::getCategory(const QString &name) { return d->actionInfo(name).categoryName; }; QStringList KisActionRegistry::allActions() { return d->actionInfoList.keys(); }; KActionCollection * KisActionRegistry::getDefaultCollection() { return d->actionCollections.value("Krita"); }; void KisActionRegistry::addAction(const QString &name, QAction *a) { auto info = d->actionInfo(name); KActionCollection *collection = d->actionCollections.value(info.collectionName); if (!collection) { dbgAction << "No collection found for action" << name; return; } if (collection->action(name)) { dbgAction << "duplicate action" << name << "in collection" << collection->componentName(); } else { } collection->addCategorizedAction(name, a, info.categoryName); }; void KisActionRegistry::notifySettingsUpdated() { d->loadCustomShortcuts(); }; void KisActionRegistry::loadCustomShortcuts(const QString &path) { if (path.isEmpty()) { d->loadCustomShortcuts(); } else { d->loadCustomShortcuts(path); } }; void KisActionRegistry::loadShortcutScheme(const QString &schemeName) { // Load scheme file if (schemeName != QStringLiteral("Default")) { QString schemeFileName = KShortcutSchemesHelper::schemeFileLocations().value(schemeName); if (schemeFileName.isEmpty()) { // qDebug() << "No configuration file found for scheme" << schemeName; return; } KConfig schemeConfig(schemeFileName, KConfig::SimpleConfig); applyShortcutScheme(&schemeConfig); } else { // Apply default scheme, updating KisActionRegistry data applyShortcutScheme(); } } QAction * KisActionRegistry::makeQAction(const QString &name, QObject *parent) { QAction * a = new QAction(parent); if (!d->actionInfoList.contains(name)) { dbgAction << "Warning: requested data for unknown action" << name; return a; } propertizeAction(name, a); return a; }; void KisActionRegistry::setupDialog(KisShortcutsDialog *dlg) { for (auto i = d->actionCollections.constBegin(); i != d->actionCollections.constEnd(); i++ ) { dlg->addCollection(i.value(), i.key()); } } void KisActionRegistry::settingsPageSaved() { // For now, custom shortcuts are dealt with by writing to file and reloading. loadCustomShortcuts(); // Announce UI should reload current shortcuts. emit shortcutsUpdated(); } void KisActionRegistry::applyShortcutScheme(const KConfigBase *config) { // First, update the things in KisActionRegistry if (config == 0) { // Use default shortcut scheme. Simplest just to reload everything. d->actionInfoList.clear(); d->loadActionFiles(); loadCustomShortcuts(); } else { const auto schemeEntries = config->group(QStringLiteral("Shortcuts")).entryMap(); // Load info item for each shortcut, reset custom shortcuts auto it = schemeEntries.constBegin(); while (it != schemeEntries.end()) { ActionInfoItem &info = d->actionInfo(it.key()); info.defaultShortcut = it.value(); it++; } } } void KisActionRegistry::updateShortcut(const QString &name, QAction *action) { const ActionInfoItem info = d->actionInfo(name); action->setShortcut(preferredShortcut(info)); - auto propertizedShortcut = qVariantFromValue(QList() << info.defaultShortcut); - action->setProperty("defaultShortcuts", propertizedShortcut); + + auto defaultShortcutsList = QList(); + if (info.defaultShortcut != QKeySequence("")) { + // Use the empty list to represent no shortcut + defaultShortcutsList << info.defaultShortcut; + } + action->setProperty("defaultShortcuts", qVariantFromValue(defaultShortcutsList)); } bool KisActionRegistry::propertizeAction(const QString &name, QAction * a) { const ActionInfoItem info = d->actionInfo(name); QDomElement actionXml = info.xmlData; if (actionXml.text().isEmpty()) { dbgAction << "No XML data found for action" << name; return false; } // i18n requires converting format from QString. auto getChildContent_i18n = [=](QString node){return quietlyTranslate(getChildContent(actionXml, node));}; // Note: the fields in the .action documents marked for translation are determined by extractrc. QString icon = getChildContent(actionXml, "icon"); QString text = getChildContent(actionXml, "text"); QString whatsthis = getChildContent_i18n("whatsThis"); QString toolTip = getChildContent_i18n("toolTip"); QString statusTip = getChildContent_i18n("statusTip"); QString iconText = getChildContent_i18n("iconText"); bool isCheckable = getChildContent(actionXml, "isCheckable") == QString("true"); a->setObjectName(name); // This is helpful, should be added more places in Krita a->setIcon(KisIconUtils::loadIcon(icon.toLatin1())); a->setText(text); a->setObjectName(name); a->setWhatsThis(whatsthis); a->setToolTip(toolTip); a->setStatusTip(statusTip); a->setIconText(iconText); a->setCheckable(isCheckable); updateShortcut(name, a); // TODO: check for colliding shortcuts, either here, or in loading code #if 0 QMap existingShortcuts; Q_FOREACH (QAction* action, actionCollection->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } if (existingShortcuts.contains(action->shortcut())) { dbgAction << QString("Actions %1 and %2 have the same shortcut: %3") \ .arg(action->text()) \ .arg(existingShortcuts[action->shortcut()]->text()) \ .arg(action->shortcut()); } else { existingShortcuts[action->shortcut()] = action; } } #endif return true; } QString KisActionRegistry::getActionProperty(const QString &name, const QString &property) { ActionInfoItem info = d->actionInfo(name); QDomElement actionXml = info.xmlData; if (actionXml.text().isEmpty()) { dbgAction << "No XML data found for action" << name; return QString(); } return getChildContent(actionXml, property); } void KisActionRegistry::writeCustomShortcuts(KConfigBase *config) const { KConfigGroup cg; if (config == 0) { cg = KConfigGroup(KSharedConfig::openConfig("kritashortcutsrc"), QStringLiteral("Shortcuts")); } else { cg = KConfigGroup(config, QStringLiteral("Shortcuts")); } for (auto it = d->actionInfoList.constBegin(); it != d->actionInfoList.constEnd(); ++it) { QString actionName = it.key(); QString s = it.value().customShortcut.toString(); if (s.isEmpty()) { cg.deleteEntry(actionName, KConfigGroup::Persistent); } else { cg.writeEntry(actionName, s, KConfigGroup::Persistent); } } cg.sync(); } void KisActionRegistry::Private::loadActionFiles() { auto searchType = KoResourcePaths::Recursive | KoResourcePaths::NoDuplicates; QStringList actionDefinitions = KoResourcePaths::findAllResources("kis_actions", "*.action", searchType); // Extract actions all XML .action files. Q_FOREACH (const QString &actionDefinition, actionDefinitions) { QDomDocument doc; QFile f(actionDefinition); f.open(QFile::ReadOnly); doc.setContent(f.readAll()); QDomElement base = doc.documentElement(); // "ActionCollection" outer group QString collectionName = base.attribute("name"); QString version = base.attribute("version"); if (version != "2") { errAction << ".action XML file" << actionDefinition << "has incorrect version; skipping."; continue; } KActionCollection *actionCollection; if (!actionCollections.contains(collectionName)) { actionCollection = new KActionCollection(q, collectionName); actionCollections.insert(collectionName, actionCollection); dbgAction << "Adding a new action collection " << collectionName; } else { actionCollection = actionCollections.value(collectionName); } // Loop over nodes. Each of these corresponds to a // KActionCategory, producing a group of actions in the shortcut dialog. QDomElement actions = base.firstChild().toElement(); while (!actions.isNull()) { // field QDomElement categoryTextNode = actions.firstChild().toElement(); QString categoryName = quietlyTranslate(categoryTextNode.text()); // KActionCategory *category = actionCollection->getCategory(categoryName); // dbgAction << "Using category" << categoryName; // tags QDomElement actionXml = categoryTextNode.nextSiblingElement(); // Loop over individual actions while (!actionXml.isNull()) { if (actionXml.tagName() == "Action") { // Read name from format QString name = actionXml.attribute("name"); // Bad things if (name.isEmpty()) { errAction << "Unnamed action in definitions file " << actionDefinition; } else if (actionInfoList.contains(name)) { // errAction << "NOT COOL: Duplicated action name from xml data: " << name; } else { ActionInfoItem info; info.xmlData = actionXml; info.defaultShortcut = getChildContent(actionXml, "shortcut"); info.customShortcut = QKeySequence(); info.categoryName = categoryName; info.collectionName = collectionName; // dbgAction << "default shortcut for" << name << " - " << info.defaultShortcut; actionInfoList.insert(name,info); } } actionXml = actionXml.nextSiblingElement(); } actions = actions.nextSiblingElement(); } } }; void KisActionRegistry::Private::loadCustomShortcuts(QString filename) { const KConfigGroup localShortcuts(KSharedConfig::openConfig(filename), QStringLiteral("Shortcuts")); if (!localShortcuts.exists()) { return; } for (auto i = actionInfoList.begin(); i != actionInfoList.end(); ++i) { if (localShortcuts.hasKey(i.key())) { QString entry = localShortcuts.readEntry(i.key(), QString()); i.value().customShortcut = QKeySequence(entry); } else { i.value().customShortcut = QKeySequence(); } } }; diff --git a/libs/widgetutils/xmlgui/kactioncollection.cpp b/libs/widgetutils/xmlgui/kactioncollection.cpp index 88664db6db..92598a104f 100644 --- a/libs/widgetutils/xmlgui/kactioncollection.cpp +++ b/libs/widgetutils/xmlgui/kactioncollection.cpp @@ -1,762 +1,755 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Reginald Stadlbauer (C) 1999 Simon Hausmann (C) 2000 Nicolas Hadacek (C) 2000 Kurt Granroth (C) 2000 Michael Koch (C) 2001 Holger Freyther (C) 2002 Ellis Whitehead (C) 2002 Joseph Wenninger (C) 2005-2007 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "kactioncollection.h" #include "config-xmlgui.h" #include "kactioncategory.h" #include "kxmlguiclient.h" #include "kxmlguifactory.h" #include "kactioncategory.h" #include "kis_action_registry.h" #include #include #include #include #include #include #include #include #include #include #include #include class KActionCollectionPrivate { public: KActionCollectionPrivate() : m_parentGUIClient(0L), configGroup(QStringLiteral("Shortcuts")), connectTriggered(false), connectHovered(false), q(0) { } void setComponentForAction(QAction *action) { Q_UNUSED(action) } static QList s_allCollections; void _k_associatedWidgetDestroyed(QObject *obj); void _k_actionDestroyed(QObject *obj); bool writeKXMLGUIConfigFile(); QString m_componentName; QString m_componentDisplayName; //! Remove a action from our internal bookkeeping. Returns 0 if the //! action doesn't belong to us. QAction *unlistAction(QAction *); QMap actionByName; QList actions; const KXMLGUIClient *m_parentGUIClient; QString configGroup; bool configIsGlobal : 1; bool connectTriggered : 1; bool connectHovered : 1; KActionCollection *q; QList associatedWidgets; }; QList KActionCollectionPrivate::s_allCollections; KActionCollection::KActionCollection(QObject *parent, const QString &cName) : QObject(parent) , d(new KActionCollectionPrivate) { d->q = this; KActionCollectionPrivate::s_allCollections.append(this); setComponentName(cName); } KActionCollection::KActionCollection(const KXMLGUIClient *parent) : QObject(0) , d(new KActionCollectionPrivate) { d->q = this; KActionCollectionPrivate::s_allCollections.append(this); d->m_parentGUIClient = parent; d->m_componentName = parent->componentName(); } KActionCollection::~KActionCollection() { KActionCollectionPrivate::s_allCollections.removeAll(this); delete d; } QList KActionCollection::categories() const { return this->findChildren(); } KActionCategory *KActionCollection::getCategory(const QString &name) { KActionCategory *category = 0; foreach (KActionCategory *c, categories()) { if (c->text() == name) { category = c; } } if (category == 0) { category = new KActionCategory(name, this); } return category; }; void KActionCollection::clear() { d->actionByName.clear(); qDeleteAll(d->actions); d->actions.clear(); } QAction *KActionCollection::action(const QString &name) const { QAction *action = 0L; if (!name.isEmpty()) { action = d->actionByName.value(name); } return action; } QAction *KActionCollection::action(int index) const { // ### investigate if any apps use this at all return actions().value(index); } int KActionCollection::count() const { return d->actions.count(); } bool KActionCollection::isEmpty() const { return count() == 0; } void KActionCollection::setComponentName(const QString &cName) { if (count() > 0) { // Its component name is part of an action's signature in the context of // global shortcuts and the semantics of changing an existing action's // signature are, as it seems, impossible to get right. // As of now this only matters for global shortcuts. We could // thus relax the requirement and only refuse to change the component data // if we have actions with global shortcuts in this collection. qWarning() << "this does not work on a KActionCollection containing actions!"; } if (!cName.isEmpty()) { d->m_componentName = cName; } else { d->m_componentName = QCoreApplication::applicationName(); } } QString KActionCollection::componentName() const { return d->m_componentName; } void KActionCollection::setComponentDisplayName(const QString &displayName) { d->m_componentDisplayName = displayName; } QString KActionCollection::componentDisplayName() const { if (!d->m_componentDisplayName.isEmpty()) { return d->m_componentDisplayName; } if (!QGuiApplication::applicationDisplayName().isEmpty()) { return QGuiApplication::applicationDisplayName(); } return QCoreApplication::applicationName(); } const KXMLGUIClient *KActionCollection::parentGUIClient() const { return d->m_parentGUIClient; } QList KActionCollection::actions() const { return d->actions; } const QList< QAction * > KActionCollection::actionsWithoutGroup() const { QList ret; Q_FOREACH (QAction *action, d->actions) if (!action->actionGroup()) { ret.append(action); } return ret; } const QList< QActionGroup * > KActionCollection::actionGroups() const { QSet set; Q_FOREACH (QAction *action, d->actions) if (action->actionGroup()) { set.insert(action->actionGroup()); } return set.toList(); } QAction *KActionCollection::addCategorizedAction(const QString &name, QAction *action, const QString &categoryName) { return getCategory(categoryName)->addAction(name, action); } QAction *KActionCollection::addAction(const QString &name, QAction *action) { if (!action) { return action; } const QString objectName = action->objectName(); QString indexName = name; if (indexName.isEmpty()) { // No name provided. Use the objectName. indexName = objectName; } else { // Set the new name action->setObjectName(indexName); } // No name provided and the action had no name. Make one up. This will not // work when trying to save shortcuts. if (indexName.isEmpty()) { indexName = indexName.sprintf("unnamed-%p", (void *)action); action->setObjectName(indexName); } // From now on the objectName has to have a value. Else we cannot safely // remove actions. Q_ASSERT(!action->objectName().isEmpty()); // look if we already have THIS action under THIS name ;) if (d->actionByName.value(indexName, 0) == action) { // This is not a multi map! Q_ASSERT(d->actionByName.count(indexName) == 1); return action; } if (!KAuthorized::authorizeKAction(indexName)) { // Disable this action action->setEnabled(false); action->setVisible(false); action->blockSignals(true); } // Check if we have another action under this name if (QAction *oldAction = d->actionByName.value(indexName)) { takeAction(oldAction); } // Check if we have this action under a different name. // Not using takeAction because we don't want to remove it from categories, // and because it has the new name already. const int oldIndex = d->actions.indexOf(action); if (oldIndex != -1) { d->actionByName.remove(d->actionByName.key(action)); d->actions.removeAt(oldIndex); } // Add action to our lists. d->actionByName.insert(indexName, action); d->actions.append(action); Q_FOREACH (QWidget *widget, d->associatedWidgets) { widget->addAction(action); } connect(action, SIGNAL(destroyed(QObject*)), SLOT(_k_actionDestroyed(QObject*))); d->setComponentForAction(action); if (d->connectHovered) { connect(action, SIGNAL(hovered()), SLOT(slotActionHovered())); } if (d->connectTriggered) { connect(action, SIGNAL(triggered(bool)), SLOT(slotActionTriggered())); } emit inserted(action); return action; } void KActionCollection::addActions(const QList &actions) { Q_FOREACH (QAction *action, actions) { addAction(action->objectName(), action); } } void KActionCollection::removeAction(QAction *action) { delete takeAction(action); } QAction *KActionCollection::takeAction(QAction *action) { if (!d->unlistAction(action)) { return 0; } // Remove the action from all widgets Q_FOREACH (QWidget *widget, d->associatedWidgets) { widget->removeAction(action); } action->disconnect(this); emit removed(action); //deprecated return action; } QAction *KActionCollection::addAction(KStandardAction::StandardAction actionType, const QObject *receiver, const char *member) { QAction *action = KStandardAction::create(actionType, receiver, member, this); return action; } QAction *KActionCollection::addAction(KStandardAction::StandardAction actionType, const QString &name, const QObject *receiver, const char *member) { // pass 0 as parent, because if the parent is a KActionCollection KStandardAction::create automatically // adds the action to it under the default name. We would trigger the // warning about renaming the action then. QAction *action = KStandardAction::create(actionType, receiver, member, 0); // Give it a parent for gc. action->setParent(this); // Remove the name to get rid of the "rename action" warning above action->setObjectName(name); // And now add it with the desired name. return addAction(name, action); } QAction *KActionCollection::addAction(const QString &name, const QObject *receiver, const char *member) { QAction *a = new QAction(this); if (receiver && member) { connect(a, SIGNAL(triggered(bool)), receiver, member); } return addAction(name, a); } QKeySequence KActionCollection::defaultShortcut(QAction *action) const { const QList shortcuts = defaultShortcuts(action); return shortcuts.isEmpty() ? QKeySequence() : shortcuts.first(); } QList KActionCollection::defaultShortcuts(QAction *action) const { return action->property("defaultShortcuts").value >(); } void KActionCollection::setDefaultShortcut(QAction *action, const QKeySequence &shortcut) { setDefaultShortcuts(action, QList() << shortcut); } void KActionCollection::setDefaultShortcuts(QAction *action, const QList &shortcuts) { action->setShortcuts(shortcuts); action->setProperty("defaultShortcuts", QVariant::fromValue(shortcuts)); } bool KActionCollection::isShortcutsConfigurable(QAction *action) const { // Considered as true by default const QVariant value = action->property("isShortcutConfigurable"); return value.isValid() ? value.toBool() : true; } void KActionCollection::setShortcutsConfigurable(QAction *action, bool configurable) { action->setProperty("isShortcutConfigurable", configurable); } QString KActionCollection::configGroup() const { return d->configGroup; } void KActionCollection::setConfigGroup(const QString &group) { d->configGroup = group; } void KActionCollection::updateShortcuts() { auto actionRegistry = KisActionRegistry::instance(); for (QMap::ConstIterator it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) { actionRegistry->updateShortcut(it.key(), it.value()); } } void KActionCollection::readSettings() { auto ar = KisActionRegistry::instance(); ar->loadCustomShortcuts(); for (QMap::ConstIterator it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) { QAction *action = it.value(); if (!action) { continue; } if (isShortcutsConfigurable(action)) { QString actionName = it.key(); ar->updateShortcut(actionName, action); } } } bool KActionCollectionPrivate::writeKXMLGUIConfigFile() { const KXMLGUIClient *kxmlguiClient = q->parentGUIClient(); // return false if there is no KXMLGUIClient if (!kxmlguiClient || kxmlguiClient->xmlFile().isEmpty()) { return false; } QString attrShortcut = QStringLiteral("shortcut"); // Read XML file QString sXml(KXMLGUIFactory::readConfigFile(kxmlguiClient->xmlFile(), q->componentName())); QDomDocument doc; doc.setContent(sXml); // Process XML data // Get hold of ActionProperties tag QDomElement elem = KXMLGUIFactory::actionPropertiesElement(doc); // now, iterate through our actions for (QMap::ConstIterator it = actionByName.constBegin(); it != actionByName.constEnd(); ++it) { QAction *action = it.value(); if (!action) { continue; } QString actionName = it.key(); // If the action name starts with unnamed- spit out a warning and ignore // it. That name will change at will and will break loading writing if (actionName.startsWith(QLatin1String("unnamed-"))) { qCritical() << "Skipped writing shortcut for action " << actionName << "(" << action->text() << ")!"; continue; } bool bSameAsDefault = (action->shortcuts() == q->defaultShortcuts(action)); // now see if this element already exists // and create it if necessary (unless bSameAsDefault) QDomElement act_elem = KXMLGUIFactory::findActionByName(elem, actionName, !bSameAsDefault); if (act_elem.isNull()) { continue; } if (bSameAsDefault) { act_elem.removeAttribute(attrShortcut); if (act_elem.attributes().count() == 1) { elem.removeChild(act_elem); } } else { act_elem.setAttribute(attrShortcut, QKeySequence::listToString(action->shortcuts())); } } // Write back to XML file KXMLGUIFactory::saveConfigFile(doc, kxmlguiClient->localXMLFile(), q->componentName()); return true; } void KActionCollection::writeSettings(KConfigGroup *config, bool writeScheme, QAction *oneAction) const { // If the caller didn't provide a config group we try to save the KXMLGUI // Configuration file. (This will work if the parentGUI was set and has a // valid configuration file.) if (config == 0 && d->writeKXMLGUIConfigFile()) { return; } KConfigGroup cg(KSharedConfig::openConfig(), configGroup()); if (!config) { config = &cg; } QList writeActions; if (oneAction) { writeActions.append(oneAction); } else { writeActions = actions(); } for (QMap::ConstIterator it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) { QAction *action = it.value(); if (!action) { continue; } QString actionName = it.key(); // If the action name starts with unnamed- spit out a warning and ignore // it. That name will change at will and will break loading writing if (actionName.startsWith(QLatin1String("unnamed-"))) { qCritical() << "Skipped saving shortcut for action without name " \ << action->text() << "!"; continue; } // Write the shortcut if (isShortcutsConfigurable(action)) { bool bConfigHasAction = !config->readEntry(actionName, QString()).isEmpty(); bool bSameAsDefault = (action->shortcuts() == defaultShortcuts(action)); // If the current shortcut differs from the default, we want to write. KConfigGroup::WriteConfigFlags flags = KConfigGroup::Persistent; if (writeScheme || !bSameAsDefault) { // We are instructed to write all shortcuts or the shortcut is // not set to its default value. Write it QString s = QKeySequence::listToString(action->shortcuts()); if (s.isEmpty()) { - if (writeScheme) { - // A scheme file should explicitly set "none" for the shortcut - s = QStringLiteral("none"); - config->writeEntry(actionName, s, flags); - } else { - config->deleteEntry(actionName, flags); - } - } else { - config->writeEntry(actionName, s, flags); + s = QStringLiteral("none"); } + config->writeEntry(actionName, s, flags); } else if (bConfigHasAction) { // This key is the same as default but exists in config file. // Remove it. config->deleteEntry(actionName, flags); } } } config->sync(); } void KActionCollection::slotActionTriggered() { QAction *action = qobject_cast(sender()); if (action) { emit actionTriggered(action); } } void KActionCollection::slotActionHighlighted() { slotActionHovered(); } void KActionCollection::slotActionHovered() { QAction *action = qobject_cast(sender()); if (action) { emit actionHighlighted(action); emit actionHovered(action); } } void KActionCollectionPrivate::_k_actionDestroyed(QObject *obj) { // obj isn't really a QAction anymore. So make sure we don't do fancy stuff // with it. QAction *action = static_cast(obj); if (!unlistAction(action)) { return; } //HACK the object we emit is partly destroyed emit q->removed(action); //deprecated. remove in KDE5 } void KActionCollection::connectNotify(const QMetaMethod &signal) { if (d->connectHovered && d->connectTriggered) { return; } if (signal.methodSignature() == "actionHighlighted(QAction*)" || signal.methodSignature() == "actionHovered(QAction*)") { if (!d->connectHovered) { d->connectHovered = true; Q_FOREACH (QAction *action, actions()) { connect(action, SIGNAL(hovered()), SLOT(slotActionHovered())); } } } else if (signal.methodSignature() == "actionTriggered(QAction*)") { if (!d->connectTriggered) { d->connectTriggered = true; Q_FOREACH (QAction *action, actions()) { connect(action, SIGNAL(triggered(bool)), SLOT(slotActionTriggered())); } } } QObject::connectNotify(signal); } const QList< KActionCollection * > &KActionCollection::allCollections() { return KActionCollectionPrivate::s_allCollections; } void KActionCollection::associateWidget(QWidget *widget) const { Q_FOREACH (QAction *action, actions()) { if (!widget->actions().contains(action)) { widget->addAction(action); } } } void KActionCollection::addAssociatedWidget(QWidget *widget) { if (!d->associatedWidgets.contains(widget)) { widget->addActions(actions()); d->associatedWidgets.append(widget); connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(_k_associatedWidgetDestroyed(QObject*))); } } void KActionCollection::removeAssociatedWidget(QWidget *widget) { Q_FOREACH (QAction *action, actions()) { widget->removeAction(action); } d->associatedWidgets.removeAll(widget); disconnect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(_k_associatedWidgetDestroyed(QObject*))); } QAction *KActionCollectionPrivate::unlistAction(QAction *action) { // ATTENTION: // This method is called with an QObject formerly known as a QAction // during _k_actionDestroyed(). So don't do fancy stuff here that needs a // real QAction! // Get the index for the action int index = actions.indexOf(action); // Action not found. if (index == -1) { return 0; } // An action collection can't have the same action twice. Q_ASSERT(actions.indexOf(action, index + 1) == -1); // Get the actions name const QString name = action->objectName(); // Remove the action actionByName.remove(name); actions.removeAt(index); // Remove the action from the categories. Should be only one QList categories = q->findChildren(); Q_FOREACH (KActionCategory *category, categories) { category->unlistAction(action); } return action; } QList< QWidget * > KActionCollection::associatedWidgets() const { return d->associatedWidgets; } void KActionCollection::clearAssociatedWidgets() { Q_FOREACH (QWidget *widget, d->associatedWidgets) Q_FOREACH (QAction *action, actions()) { widget->removeAction(action); } d->associatedWidgets.clear(); } void KActionCollectionPrivate::_k_associatedWidgetDestroyed(QObject *obj) { associatedWidgets.removeAll(static_cast(obj)); } #include "moc_kactioncollection.cpp"