File indexing completed on 2024-04-21 15:05:54

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 1999, 2000 Simon Hausmann <hausmann@kde.org>
0004     SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "config-xmlgui.h"
0010 
0011 #include "kxmlguifactory.h"
0012 
0013 #include "debug.h"
0014 #include "kactioncollection.h"
0015 #include "kshortcutschemeshelper_p.h"
0016 #include "kshortcutsdialog.h"
0017 #include "kxmlguibuilder.h"
0018 #include "kxmlguiclient.h"
0019 #include "kxmlguifactory_p.h"
0020 
0021 #include <QAction>
0022 #include <QCoreApplication>
0023 #include <QDir>
0024 #include <QDomDocument>
0025 #include <QFile>
0026 #include <QStandardPaths>
0027 #include <QTextStream>
0028 #include <QVariant>
0029 #include <QWidget>
0030 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0031 #include <QTextCodec>
0032 #endif
0033 
0034 #include <KConfigGroup>
0035 #include <KSharedConfig>
0036 #if HAVE_GLOBALACCEL
0037 #include <KGlobalAccel>
0038 #endif
0039 
0040 Q_DECLARE_METATYPE(QList<QKeySequence>)
0041 
0042 using namespace KXMLGUI;
0043 
0044 class KXMLGUIFactoryPrivate : public BuildState
0045 {
0046 public:
0047     enum ShortcutOption { SetActiveShortcut = 1, SetDefaultShortcut = 2 };
0048 
0049     KXMLGUIFactoryPrivate()
0050     {
0051         m_rootNode = new ContainerNode(nullptr, QString(), QString());
0052         attrName = QStringLiteral("name");
0053     }
0054     ~KXMLGUIFactoryPrivate()
0055     {
0056         delete m_rootNode;
0057     }
0058 
0059     void pushState()
0060     {
0061         m_stateStack.push(*this);
0062     }
0063 
0064     void popState()
0065     {
0066         BuildState::operator=(m_stateStack.pop());
0067     }
0068 
0069     bool emptyState() const
0070     {
0071         return m_stateStack.isEmpty();
0072     }
0073 
0074     QWidget *findRecursive(KXMLGUI::ContainerNode *node, bool tag);
0075     QList<QWidget *> findRecursive(KXMLGUI::ContainerNode *node, const QString &tagName);
0076     void applyActionProperties(const QDomElement &element, ShortcutOption shortcutOption = KXMLGUIFactoryPrivate::SetActiveShortcut);
0077     void configureAction(QAction *action, const QDomNamedNodeMap &attributes, ShortcutOption shortcutOption = KXMLGUIFactoryPrivate::SetActiveShortcut);
0078     void configureAction(QAction *action, const QDomAttr &attribute, ShortcutOption shortcutOption = KXMLGUIFactoryPrivate::SetActiveShortcut);
0079 
0080     void applyShortcutScheme(const QString &schemeName, KXMLGUIClient *client, const QList<QAction *> &actions);
0081     void refreshActionProperties(KXMLGUIClient *client, const QList<QAction *> &actions, const QDomDocument &doc);
0082     void saveDefaultActionProperties(const QList<QAction *> &actions);
0083 
0084     ContainerNode *m_rootNode;
0085 
0086     /*
0087      * Contains the container which is searched for in ::container .
0088      */
0089     QString m_containerName;
0090 
0091     /*
0092      * List of all clients
0093      */
0094     QList<KXMLGUIClient *> m_clients;
0095 
0096     QString attrName;
0097 
0098     BuildStateStack m_stateStack;
0099 };
0100 
0101 QString KXMLGUIFactory::readConfigFile(const QString &filename, const QString &_componentName)
0102 {
0103     QString componentName = _componentName.isEmpty() ? QCoreApplication::applicationName() : _componentName;
0104     QString xml_file;
0105 
0106     if (!QDir::isRelativePath(filename)) {
0107         xml_file = filename;
0108     } else {
0109         // KF >= 5.1 (KDE_INSTALL_KXMLGUI5DIR)
0110         xml_file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kxmlgui5/") + componentName + QLatin1Char('/') + filename);
0111         if (!QFile::exists(xml_file)) {
0112             // KF >= 5.4 (resource file)
0113             xml_file = QLatin1String(":/kxmlgui5/") + componentName + QLatin1Char('/') + filename;
0114         }
0115 
0116         bool warn = false;
0117         if (!QFile::exists(xml_file)) {
0118             // kdelibs4 / KF 5.0 solution
0119             xml_file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, componentName + QLatin1Char('/') + filename);
0120             warn = true;
0121         }
0122 
0123         if (!QFile::exists(xml_file)) {
0124             // kdelibs4 / KF 5.0 solution, and the caller includes the component name
0125             // This was broken (lead to component/component/ in kdehome) and unnecessary
0126             // (they can specify it with setComponentName instead)
0127             xml_file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, filename);
0128             warn = true;
0129         }
0130 
0131         if (warn && !xml_file.isEmpty()) {
0132             qCWarning(DEBUG_KXMLGUI) << "KXMLGUI file found at deprecated location" << xml_file
0133                                      << "-- please use ${KDE_INSTALL_KXMLGUI5DIR} to install these files instead.";
0134         }
0135     }
0136 
0137     QFile file(xml_file);
0138     if (xml_file.isEmpty() || !file.open(QIODevice::ReadOnly)) {
0139         qCCritical(DEBUG_KXMLGUI) << "No such XML file" << filename;
0140         return QString();
0141     }
0142 
0143     QByteArray buffer(file.readAll());
0144     return QString::fromUtf8(buffer.constData(), buffer.size());
0145 }
0146 
0147 bool KXMLGUIFactory::saveConfigFile(const QDomDocument &doc, const QString &filename, const QString &_componentName)
0148 {
0149     QString componentName = _componentName.isEmpty() ? QCoreApplication::applicationName() : _componentName;
0150     QString xml_file(filename);
0151 
0152     if (QDir::isRelativePath(xml_file)) {
0153         xml_file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui5/%1/%2").arg(componentName, filename);
0154     }
0155 
0156     QFileInfo fileInfo(xml_file);
0157     QDir().mkpath(fileInfo.absolutePath());
0158     QFile file(xml_file);
0159     if (xml_file.isEmpty() || !file.open(QIODevice::WriteOnly)) {
0160         qCCritical(DEBUG_KXMLGUI) << "Could not write to" << filename;
0161         return false;
0162     }
0163 
0164     // write out our document
0165     QTextStream ts(&file);
0166     // The default in Qt6 is UTF-8
0167 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0168     ts.setCodec(QTextCodec::codecForName("UTF-8"));
0169 #endif
0170     ts << doc;
0171 
0172     file.close();
0173     return true;
0174 }
0175 
0176 KXMLGUIFactory::KXMLGUIFactory(KXMLGUIBuilder *builder, QObject *parent)
0177     : QObject(parent)
0178     , d(new KXMLGUIFactoryPrivate)
0179 {
0180     Q_INIT_RESOURCE(kxmlgui);
0181 
0182     d->builder = builder;
0183     d->guiClient = nullptr;
0184     if (d->builder) {
0185         d->builderContainerTags = d->builder->containerTags();
0186         d->builderCustomTags = d->builder->customTags();
0187     }
0188 }
0189 
0190 KXMLGUIFactory::~KXMLGUIFactory()
0191 {
0192     for (KXMLGUIClient *client : std::as_const(d->m_clients)) {
0193         client->setFactory(nullptr);
0194     }
0195 }
0196 
0197 void KXMLGUIFactory::addClient(KXMLGUIClient *client)
0198 {
0199     // qCDebug(DEBUG_KXMLGUI) << client;
0200     if (client->factory()) {
0201         if (client->factory() == this) {
0202             return;
0203         } else {
0204             client->factory()->removeClient(client); // just in case someone does stupid things ;-)
0205         }
0206     }
0207 
0208     if (d->emptyState()) {
0209         Q_EMIT makingChanges(true);
0210     }
0211     d->pushState();
0212 
0213     //    QTime dt; dt.start();
0214 
0215     d->guiClient = client;
0216 
0217     // add this client to our client list
0218     if (!d->m_clients.contains(client)) {
0219         d->m_clients.append(client);
0220     }
0221     // else
0222     // qCDebug(DEBUG_KXMLGUI) << "XMLGUI client already added " << client;
0223 
0224     // Tell the client that plugging in is process and
0225     //  let it know what builder widget its mainwindow shortcuts
0226     //  should be attached to.
0227     client->beginXMLPlug(d->builder->widget());
0228 
0229     // try to use the build document for building the client's GUI, as the build document
0230     // contains the correct container state information (like toolbar positions, sizes, etc.) .
0231     // if there is non available, then use the "real" document.
0232     QDomDocument doc = client->xmlguiBuildDocument();
0233     if (doc.documentElement().isNull()) {
0234         doc = client->domDocument();
0235     }
0236 
0237     QDomElement docElement = doc.documentElement();
0238 
0239     d->m_rootNode->index = -1;
0240 
0241     // cache some variables
0242 
0243     d->clientName = docElement.attribute(d->attrName);
0244     d->clientBuilder = client->clientBuilder();
0245 
0246     if (d->clientBuilder) {
0247         d->clientBuilderContainerTags = d->clientBuilder->containerTags();
0248         d->clientBuilderCustomTags = d->clientBuilder->customTags();
0249     } else {
0250         d->clientBuilderContainerTags.clear();
0251         d->clientBuilderCustomTags.clear();
0252     }
0253 
0254     // load shortcut schemes, user-defined shortcuts and other action properties
0255     d->saveDefaultActionProperties(client->actionCollection()->actions());
0256     if (!doc.isNull()) {
0257         d->refreshActionProperties(client, client->actionCollection()->actions(), doc);
0258     }
0259 
0260     BuildHelper(*d, d->m_rootNode).build(docElement);
0261 
0262     // let the client know that we built its GUI.
0263     client->setFactory(this);
0264 
0265     // call the finalizeGUI method, to fix up the positions of toolbars for example.
0266     // Note: the client argument is ignored
0267     d->builder->finalizeGUI(d->guiClient);
0268 
0269     // reset some variables, for safety
0270     d->BuildState::reset();
0271 
0272     client->endXMLPlug();
0273 
0274     d->popState();
0275 
0276     Q_EMIT clientAdded(client);
0277 
0278     // build child clients
0279     const auto children = client->childClients();
0280     for (KXMLGUIClient *child : children) {
0281         addClient(child);
0282     }
0283 
0284     if (d->emptyState()) {
0285         Q_EMIT makingChanges(false);
0286     }
0287     /*
0288         QString unaddedActions;
0289         Q_FOREACH (KActionCollection* ac, KActionCollection::allCollections())
0290           Q_FOREACH (QAction* action, ac->actions())
0291             if (action->associatedWidgets().isEmpty())
0292               unaddedActions += action->objectName() + ' ';
0293 
0294         if (!unaddedActions.isEmpty())
0295           qCWarning(DEBUG_KXMLGUI) << "The following actions are not plugged into the gui (shortcuts will not work): " << unaddedActions;
0296     */
0297 
0298     //    qCDebug(DEBUG_KXMLGUI) << "addClient took " << dt.elapsed();
0299 }
0300 
0301 void KXMLGUIFactory::refreshActionProperties()
0302 {
0303     for (KXMLGUIClient *client : std::as_const(d->m_clients)) {
0304         d->guiClient = client;
0305         QDomDocument doc = client->xmlguiBuildDocument();
0306         if (doc.documentElement().isNull()) {
0307             client->reloadXML();
0308             doc = client->domDocument();
0309         }
0310         d->refreshActionProperties(client, client->actionCollection()->actions(), doc);
0311     }
0312     d->guiClient = nullptr;
0313 }
0314 
0315 static QString currentShortcutScheme()
0316 {
0317     const KConfigGroup cg = KSharedConfig::openConfig()->group("Shortcut Schemes");
0318     return cg.readEntry("Current Scheme", "Default");
0319 }
0320 
0321 // Find the right ActionProperties element, otherwise return null element
0322 static QDomElement findActionPropertiesElement(const QDomDocument &doc)
0323 {
0324     const QLatin1String tagActionProp("ActionProperties");
0325     const QString schemeName = currentShortcutScheme();
0326     QDomElement e = doc.documentElement().firstChildElement();
0327     for (; !e.isNull(); e = e.nextSiblingElement()) {
0328         if (QString::compare(e.tagName(), tagActionProp, Qt::CaseInsensitive) == 0
0329             && (e.attribute(QStringLiteral("scheme"), QStringLiteral("Default")) == schemeName)) {
0330             return e;
0331         }
0332     }
0333     return QDomElement();
0334 }
0335 
0336 void KXMLGUIFactoryPrivate::refreshActionProperties(KXMLGUIClient *client, const QList<QAction *> &actions, const QDomDocument &doc)
0337 {
0338     // try to find and apply shortcuts schemes
0339     const QString schemeName = KShortcutSchemesHelper::currentShortcutSchemeName();
0340     // qCDebug(DEBUG_KXMLGUI) << client->componentName() << ": applying shortcut scheme" << schemeName;
0341 
0342     if (schemeName != QLatin1String("Default")) {
0343         applyShortcutScheme(schemeName, client, actions);
0344     } else {
0345         // apply saved default shortcuts
0346         for (QAction *action : actions) {
0347             QVariant savedDefaultShortcut = action->property("_k_DefaultShortcut");
0348             if (savedDefaultShortcut.isValid()) {
0349                 QList<QKeySequence> shortcut = savedDefaultShortcut.value<QList<QKeySequence>>();
0350                 action->setShortcuts(shortcut);
0351                 action->setProperty("defaultShortcuts", QVariant::fromValue(shortcut));
0352                 // qCDebug(DEBUG_KXMLGUI) << "scheme said" << action->shortcut().toString() << "for action" << action->objectName();
0353             } else {
0354                 action->setShortcuts(QList<QKeySequence>());
0355             }
0356         }
0357     }
0358 
0359     // try to find and apply user-defined shortcuts
0360     const QDomElement actionPropElement = findActionPropertiesElement(doc);
0361     if (!actionPropElement.isNull()) {
0362         applyActionProperties(actionPropElement);
0363     }
0364 }
0365 
0366 void KXMLGUIFactoryPrivate::saveDefaultActionProperties(const QList<QAction *> &actions)
0367 {
0368     // This method is called every time the user activated a new
0369     // kxmlguiclient. We only want to execute the following code only once in
0370     // the lifetime of an action.
0371     for (QAction *action : actions) {
0372         // Skip nullptr actions or those we have seen already.
0373         if (!action || action->property("_k_DefaultShortcut").isValid()) {
0374             continue;
0375         }
0376 
0377         // Check if the default shortcut is set
0378         QList<QKeySequence> defaultShortcut = action->property("defaultShortcuts").value<QList<QKeySequence>>();
0379         QList<QKeySequence> activeShortcut = action->shortcuts();
0380         // qCDebug(DEBUG_KXMLGUI) << action->objectName() << "default=" << defaultShortcut.toString() << "active=" << activeShortcut.toString();
0381 
0382         // Check if we have an empty default shortcut and an non empty
0383         // custom shortcut. Print out a warning and correct the mistake.
0384         if ((!activeShortcut.isEmpty()) && defaultShortcut.isEmpty()) {
0385             qCCritical(DEBUG_KXMLGUI) << "Shortcut for action " << action->objectName() << action->text()
0386                                       << "set with QAction::setShortcut()! Use KActionCollection::setDefaultShortcut(s) instead.";
0387             action->setProperty("_k_DefaultShortcut", QVariant::fromValue(activeShortcut));
0388         } else {
0389             action->setProperty("_k_DefaultShortcut", QVariant::fromValue(defaultShortcut));
0390         }
0391     }
0392 }
0393 
0394 void KXMLGUIFactory::changeShortcutScheme(const QString &scheme)
0395 {
0396     qCDebug(DEBUG_KXMLGUI) << "Changing shortcut scheme to" << scheme;
0397     KConfigGroup cg = KSharedConfig::openConfig()->group("Shortcut Schemes");
0398     cg.writeEntry("Current Scheme", scheme);
0399 
0400     refreshActionProperties();
0401 }
0402 
0403 void KXMLGUIFactory::forgetClient(KXMLGUIClient *client)
0404 {
0405     d->m_clients.erase(std::remove(d->m_clients.begin(), d->m_clients.end(), client), d->m_clients.end());
0406 }
0407 
0408 void KXMLGUIFactory::removeClient(KXMLGUIClient *client)
0409 {
0410     // qCDebug(DEBUG_KXMLGUI) << client;
0411 
0412     // don't try to remove the client's GUI if we didn't build it
0413     if (!client || client->factory() != this) {
0414         return;
0415     }
0416 
0417     if (d->emptyState()) {
0418         Q_EMIT makingChanges(true);
0419     }
0420 
0421     // remove this client from our client list
0422     forgetClient(client);
0423 
0424     // remove child clients first (create a copy of the list just in case the
0425     // original list is modified directly or indirectly in removeClient())
0426     const QList<KXMLGUIClient *> childClients(client->childClients());
0427     for (KXMLGUIClient *child : childClients) {
0428         removeClient(child);
0429     }
0430 
0431     // qCDebug(DEBUG_KXMLGUI) << "calling removeRecursive";
0432 
0433     d->pushState();
0434 
0435     // cache some variables
0436 
0437     d->guiClient = client;
0438     d->clientName = client->domDocument().documentElement().attribute(d->attrName);
0439     d->clientBuilder = client->clientBuilder();
0440 
0441     client->setFactory(nullptr);
0442 
0443     // if we don't have a build document for that client, yet, then create one by
0444     // cloning the original document, so that saving container information in the
0445     // DOM tree does not touch the original document.
0446     QDomDocument doc = client->xmlguiBuildDocument();
0447     if (doc.documentElement().isNull()) {
0448         doc = client->domDocument().cloneNode(true).toDocument();
0449         client->setXMLGUIBuildDocument(doc);
0450     }
0451 
0452     d->m_rootNode->destruct(doc.documentElement(), *d);
0453 
0454     // reset some variables
0455     d->BuildState::reset();
0456 
0457     // This will destruct the KAccel object built around the given widget.
0458     client->prepareXMLUnplug(d->builder->widget());
0459 
0460     d->popState();
0461 
0462     if (d->emptyState()) {
0463         Q_EMIT makingChanges(false);
0464     }
0465 
0466     Q_EMIT clientRemoved(client);
0467 }
0468 
0469 QList<KXMLGUIClient *> KXMLGUIFactory::clients() const
0470 {
0471     return d->m_clients;
0472 }
0473 
0474 QWidget *KXMLGUIFactory::container(const QString &containerName, KXMLGUIClient *client, bool useTagName)
0475 {
0476     d->pushState();
0477     d->m_containerName = containerName;
0478     d->guiClient = client;
0479 
0480     QWidget *result = d->findRecursive(d->m_rootNode, useTagName);
0481 
0482     d->guiClient = nullptr;
0483     d->m_containerName.clear();
0484 
0485     d->popState();
0486 
0487     return result;
0488 }
0489 
0490 QList<QWidget *> KXMLGUIFactory::containers(const QString &tagName)
0491 {
0492     return d->findRecursive(d->m_rootNode, tagName);
0493 }
0494 
0495 void KXMLGUIFactory::reset()
0496 {
0497     d->m_rootNode->reset();
0498 
0499     d->m_rootNode->clearChildren();
0500 }
0501 
0502 void KXMLGUIFactory::resetContainer(const QString &containerName, bool useTagName)
0503 {
0504     if (containerName.isEmpty()) {
0505         return;
0506     }
0507 
0508     ContainerNode *container = d->m_rootNode->findContainer(containerName, useTagName);
0509     if (container && container->parent) {
0510         container->parent->removeChild(container);
0511     }
0512 }
0513 
0514 QWidget *KXMLGUIFactoryPrivate::findRecursive(KXMLGUI::ContainerNode *node, bool tag)
0515 {
0516     if (((!tag && node->name == m_containerName) || (tag && node->tagName == m_containerName)) //
0517         && (!guiClient || node->client == guiClient)) {
0518         return node->container;
0519     }
0520 
0521     for (ContainerNode *child : std::as_const(node->children)) {
0522         QWidget *cont = findRecursive(child, tag);
0523         if (cont) {
0524             return cont;
0525         }
0526     }
0527 
0528     return nullptr;
0529 }
0530 
0531 // Case insensitive equality without calling toLower which allocates a new string
0532 static inline bool equals(const QString &str1, const char *str2)
0533 {
0534     return str1.compare(QLatin1String(str2), Qt::CaseInsensitive) == 0;
0535 }
0536 static inline bool equals(const QString &str1, const QString &str2)
0537 {
0538     return str1.compare(str2, Qt::CaseInsensitive) == 0;
0539 }
0540 
0541 QList<QWidget *> KXMLGUIFactoryPrivate::findRecursive(KXMLGUI::ContainerNode *node, const QString &tagName)
0542 {
0543     QList<QWidget *> res;
0544 
0545     if (equals(node->tagName, tagName)) {
0546         res.append(node->container);
0547     }
0548 
0549     for (KXMLGUI::ContainerNode *child : std::as_const(node->children)) {
0550         res << findRecursive(child, tagName);
0551     }
0552 
0553     return res;
0554 }
0555 
0556 void KXMLGUIFactory::plugActionList(KXMLGUIClient *client, const QString &name, const QList<QAction *> &actionList)
0557 {
0558     d->pushState();
0559     d->guiClient = client;
0560     d->actionListName = name;
0561     d->actionList = actionList;
0562     d->clientName = client->domDocument().documentElement().attribute(d->attrName);
0563 
0564     d->m_rootNode->plugActionList(*d);
0565 
0566     // Load shortcuts for these new actions
0567     d->saveDefaultActionProperties(actionList);
0568     d->refreshActionProperties(client, actionList, client->domDocument());
0569 
0570     d->BuildState::reset();
0571     d->popState();
0572 }
0573 
0574 void KXMLGUIFactory::unplugActionList(KXMLGUIClient *client, const QString &name)
0575 {
0576     d->pushState();
0577     d->guiClient = client;
0578     d->actionListName = name;
0579     d->clientName = client->domDocument().documentElement().attribute(d->attrName);
0580 
0581     d->m_rootNode->unplugActionList(*d);
0582 
0583     d->BuildState::reset();
0584     d->popState();
0585 }
0586 
0587 void KXMLGUIFactoryPrivate::applyActionProperties(const QDomElement &actionPropElement, ShortcutOption shortcutOption)
0588 {
0589     for (QDomElement e = actionPropElement.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
0590         if (!equals(e.tagName(), "action")) {
0591             continue;
0592         }
0593 
0594         QAction *action = guiClient->action(e);
0595         if (!action) {
0596             continue;
0597         }
0598 
0599         configureAction(action, e.attributes(), shortcutOption);
0600     }
0601 }
0602 
0603 void KXMLGUIFactoryPrivate::configureAction(QAction *action, const QDomNamedNodeMap &attributes, ShortcutOption shortcutOption)
0604 {
0605     for (int i = 0; i < attributes.length(); i++) {
0606         QDomAttr attr = attributes.item(i).toAttr();
0607         if (attr.isNull()) {
0608             continue;
0609         }
0610 
0611         configureAction(action, attr, shortcutOption);
0612     }
0613 }
0614 
0615 void KXMLGUIFactoryPrivate::configureAction(QAction *action, const QDomAttr &attribute, ShortcutOption shortcutOption)
0616 {
0617     QString attrName = attribute.name();
0618     // If the attribute is a deprecated "accel", change to "shortcut".
0619     if (equals(attrName, "accel")) {
0620         attrName = QStringLiteral("shortcut");
0621     }
0622 
0623     // No need to re-set name, particularly since it's "objectName" in Qt4
0624     if (equals(attrName, "name")) {
0625         return;
0626     }
0627 
0628     if (equals(attrName, "icon")) {
0629         action->setIcon(QIcon::fromTheme(attribute.value()));
0630         return;
0631     }
0632 
0633     QVariant propertyValue;
0634 
0635     QVariant::Type propertyType = action->property(attrName.toLatin1().constData()).type();
0636     bool isShortcut = (propertyType == QVariant::KeySequence);
0637 
0638     if (propertyType == QVariant::Int) {
0639         propertyValue = QVariant(attribute.value().toInt());
0640     } else if (propertyType == QVariant::UInt) {
0641         propertyValue = QVariant(attribute.value().toUInt());
0642     } else if (isShortcut) {
0643         // Setting the shortcut by property also sets the default shortcut (which is incorrect), so we have to do it directly
0644         if (attrName == QLatin1String("globalShortcut")) {
0645 #if HAVE_GLOBALACCEL
0646             KGlobalAccel::self()->setShortcut(action, QKeySequence::listFromString(attribute.value()));
0647 #endif
0648         } else {
0649             action->setShortcuts(QKeySequence::listFromString(attribute.value()));
0650         }
0651         if (shortcutOption & KXMLGUIFactoryPrivate::SetDefaultShortcut) {
0652             action->setProperty("defaultShortcuts", QVariant::fromValue(QKeySequence::listFromString(attribute.value())));
0653         }
0654     } else {
0655         propertyValue = QVariant(attribute.value());
0656     }
0657     if (!isShortcut && !action->setProperty(attrName.toLatin1().constData(), propertyValue)) {
0658         qCWarning(DEBUG_KXMLGUI) << "Error: Unknown action property " << attrName << " will be ignored!";
0659     }
0660 }
0661 
0662 void KXMLGUIFactoryPrivate::applyShortcutScheme(const QString &schemeName, KXMLGUIClient *client, const QList<QAction *> &actions)
0663 {
0664     // First clear all existing shortcuts
0665     for (QAction *action : actions) {
0666         action->setShortcuts(QList<QKeySequence>());
0667         // We clear the default shortcut as well because the shortcut scheme will set its own defaults
0668         action->setProperty("defaultShortcuts", QVariant::fromValue(QList<QKeySequence>()));
0669     }
0670 
0671     // Find the document for the shortcut scheme using the current application path.
0672     // This allows to install a single XML file for a shortcut scheme for kdevelop
0673     // rather than 10.
0674     // Also look for the current xmlguiclient path.
0675     // Per component xml files make sense for making kmail shortcuts available in kontact.
0676     QString schemeFileName = KShortcutSchemesHelper::shortcutSchemeFileName(client->componentName(), schemeName);
0677     if (schemeFileName.isEmpty()) {
0678         schemeFileName = KShortcutSchemesHelper::applicationShortcutSchemeFileName(schemeName);
0679     }
0680     if (schemeFileName.isEmpty()) {
0681         qCWarning(DEBUG_KXMLGUI) << client->componentName() << ": shortcut scheme file not found:" << schemeName << "after trying"
0682                                  << QCoreApplication::applicationName() << "and" << client->componentName();
0683         return;
0684     }
0685 
0686     QDomDocument scheme;
0687     QFile schemeFile(schemeFileName);
0688     if (schemeFile.open(QIODevice::ReadOnly)) {
0689         qCDebug(DEBUG_KXMLGUI) << client->componentName() << ": found shortcut scheme XML" << schemeFileName;
0690         scheme.setContent(&schemeFile);
0691     }
0692 
0693     if (scheme.isNull()) {
0694         return;
0695     }
0696 
0697     QDomElement docElement = scheme.documentElement();
0698     QDomElement actionPropElement = docElement.namedItem(QStringLiteral("ActionProperties")).toElement();
0699 
0700     // Check if we really have the shortcut configuration here
0701     if (!actionPropElement.isNull()) {
0702         // qCDebug(DEBUG_KXMLGUI) << "Applying shortcut scheme for XMLGUI client" << client->componentName();
0703 
0704         // Apply all shortcuts we have
0705         applyActionProperties(actionPropElement, KXMLGUIFactoryPrivate::SetDefaultShortcut);
0706         //} else {
0707         // qCDebug(DEBUG_KXMLGUI) << "Invalid shortcut scheme file";
0708     }
0709 }
0710 
0711 #if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 84)
0712 int KXMLGUIFactory::configureShortcuts(bool letterCutsOk, bool bSaveSettings)
0713 {
0714     auto *dlg = new KShortcutsDialog(KShortcutsEditor::AllActions,
0715                                      letterCutsOk ? KShortcutsEditor::LetterShortcutsAllowed : KShortcutsEditor::LetterShortcutsDisallowed,
0716                                      qobject_cast<QWidget *>(parent()));
0717     for (KXMLGUIClient *client : std::as_const(d->m_clients)) {
0718         if (client) {
0719             qCDebug(DEBUG_KXMLGUI) << "Adding collection from client" << client->componentName() << "with" << client->actionCollection()->count() << "actions";
0720             dlg->addCollection(client->actionCollection(), client->componentName());
0721         }
0722     }
0723     connect(dlg, &KShortcutsDialog::saved, this, &KXMLGUIFactory::shortcutsSaved);
0724     return dlg->configure(bSaveSettings);
0725 }
0726 #endif
0727 
0728 void KXMLGUIFactory::showConfigureShortcutsDialog()
0729 {
0730     auto *dlg = new KShortcutsDialog(qobject_cast<QWidget *>(parent()));
0731     dlg->setAttribute(Qt::WA_DeleteOnClose);
0732 
0733     for (KXMLGUIClient *client : std::as_const(d->m_clients)) {
0734         if (client) {
0735             qCDebug(DEBUG_KXMLGUI) << "Adding collection from client" << client->componentName() << "with" << client->actionCollection()->count() << "actions";
0736 
0737             dlg->addCollection(client->actionCollection(), client->componentName());
0738         }
0739     }
0740 
0741     connect(dlg, &KShortcutsDialog::saved, this, &KXMLGUIFactory::shortcutsSaved);
0742     dlg->configure(true /*save settings on accept*/);
0743 }
0744 
0745 // Find or create
0746 QDomElement KXMLGUIFactory::actionPropertiesElement(QDomDocument &doc)
0747 {
0748     // first, lets see if we have existing properties
0749     QDomElement elem = findActionPropertiesElement(doc);
0750 
0751     // if there was none, create one
0752     if (elem.isNull()) {
0753         elem = doc.createElement(QStringLiteral("ActionProperties"));
0754         elem.setAttribute(QStringLiteral("scheme"), currentShortcutScheme());
0755         doc.documentElement().appendChild(elem);
0756     }
0757     return elem;
0758 }
0759 
0760 QDomElement KXMLGUIFactory::findActionByName(QDomElement &elem, const QString &sName, bool create)
0761 {
0762     const QLatin1String attrName("name");
0763     for (QDomNode it = elem.firstChild(); !it.isNull(); it = it.nextSibling()) {
0764         QDomElement e = it.toElement();
0765         if (e.attribute(attrName) == sName) {
0766             return e;
0767         }
0768     }
0769 
0770     if (create) {
0771         QDomElement act_elem = elem.ownerDocument().createElement(QStringLiteral("Action"));
0772         act_elem.setAttribute(attrName, sName);
0773         elem.appendChild(act_elem);
0774         return act_elem;
0775     }
0776     return QDomElement();
0777 }
0778 
0779 #include "moc_kxmlguifactory.cpp"