File indexing completed on 2025-02-16 13:12:20
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"