Krita Source Code Documentation
Loading...
Searching...
No Matches
kxmlguifactory.cpp
Go to the documentation of this file.
1/* This file is part of the KDE libraries
2 SPDX-FileCopyrightText: 1999, 2000 Simon Hausmann <hausmann@kde.org>
3 SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kxmlguifactory.h"
9
10#include "config-xmlgui.h"
11
12#include "kxmlguifactory_p.h"
13#include "kxmlguiclient.h"
14#include "kxmlguibuilder.h"
15#include "KisShortcutsDialog.h"
16#include "kactioncollection.h"
17
18#include <QAction>
19#include <QDir>
20#include <QDomDocument>
21#include <QFile>
22#include <QCoreApplication>
23#include <QTextStream>
24#include <QWidget>
25#include <QDate>
26#include <QVariant>
27#include <QTextCodec>
28#include <QStandardPaths>
29#include <QDebug>
30
31#include <ksharedconfig.h>
32#include <kconfiggroup.h>
33
34#include <kis_icon_utils.h>
35#include <WidgetUtilsDebug.h>
36
37#include <KisPortingUtils.h>
38
40
41using namespace KisKXMLGUI;
42
44{
45public:
47
49 {
50 m_rootNode = new ContainerNode(0L, QString(), QString());
51 m_defaultMergingName = QStringLiteral("<default>");
52 tagActionList = QStringLiteral("actionlist");
53 attrName = QStringLiteral("name");
54 }
56 {
57 delete m_rootNode;
58 }
59
60 void pushState()
61 {
62 m_stateStack.push(*this);
63 }
64
65 void popState()
66 {
67 BuildState::operator=(m_stateStack.pop());
68 }
69
70 bool emptyState() const
71 {
72 return m_stateStack.isEmpty();
73 }
74
75 QWidget *findRecursive(KisKXMLGUI::ContainerNode *node, bool tag);
76 QList<QWidget *> findRecursive(KisKXMLGUI::ContainerNode *node, const QString &tagName);
77 void applyActionProperties(const QDomElement &element,
79 void configureAction(QAction *action, const QDomNamedNodeMap &attributes,
81 void configureAction(QAction *action, const QDomAttr &attribute,
83
84
85 void refreshActionProperties(KisKXMLGUIClient *client, const QList<QAction *> &actions, const QDomDocument &doc);
87
89
91
92 /*
93 * Contains the container which is searched for in ::container .
94 */
96
97 /*
98 * List of all clients
99 */
101
103
104 QString attrName;
105
107};
108
109QString KisKXMLGUIFactory::readConfigFile(const QString &filename, const QString &_componentName)
110{
111 QString componentName = _componentName.isEmpty() ? QCoreApplication::applicationName() : _componentName;
112 QString xml_file;
113
114 if (!QDir::isRelativePath(filename)) {
115 xml_file = filename;
116 } else {
117 // KF >= 5.1 (KisKXMLGUI_INSTALL_DIR)
118 xml_file = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kxmlgui5/") + componentName + QLatin1Char('/') + filename);
119 if (!QFile::exists(xml_file)) {
120 // KF >= 5.4 (resource file)
121 xml_file = QStringLiteral(":/kxmlgui5/") + componentName + QLatin1Char('/') + filename;
122 }
123
124 bool warn = false;
125 if (!QFile::exists(xml_file)) {
126 // kdelibs4 / KF 5.0 solution
127 xml_file = QStandardPaths::locate(QStandardPaths::AppDataLocation, componentName + QLatin1Char('/') + filename);
128 warn = true;
129 }
130
131 if (!QFile::exists(xml_file)) {
132 // kdelibs4 / KF 5.0 solution, and the caller includes the component name
133 // This was broken (lead to component/component/ in kdehome) and unnecessary
134 // (they can specify it with setComponentName instead)
135 xml_file = QStandardPaths::locate(QStandardPaths::AppDataLocation, filename);
136 warn = true;
137 }
138
139 if (warn) {
140 qWarning() << "kxmlguifactory: KisKXMLGUI file found at deprecated location" << xml_file << "-- please use ${KisKXMLGUI_INSTALL_DIR} to install these files instead.";
141 }
142 }
143
144 QFile file(xml_file);
145 if (xml_file.isEmpty() || !file.open(QIODevice::ReadOnly)) {
146 qCritical() << "No such XML file" << filename;
147 return QString();
148 }
149
150 QByteArray buffer(file.readAll());
151 return QString::fromUtf8(buffer.constData(), buffer.size());
152}
153
154bool KisKXMLGUIFactory::saveConfigFile(const QDomDocument &doc,
155 const QString &filename, const QString &_componentName)
156{
157 QString componentName = _componentName.isEmpty() ? QCoreApplication::applicationName() : _componentName;
158 QString xml_file(filename);
159
160 if (QDir::isRelativePath(xml_file))
161 xml_file = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) +
162 QStringLiteral("/kxmlgui5/") + componentName + QLatin1Char('/') + filename;
163
164 QFileInfo fileInfo(xml_file);
165 QDir().mkpath(fileInfo.absolutePath());
166 QFile file(xml_file);
167 if (xml_file.isEmpty() || !file.open(QIODevice::WriteOnly)) {
168 qCritical() << "Could not write to" << filename;
169 return false;
170 }
171
172 // write out our document
173
174 QTextStream ts(&file);
176 ts << doc;
177
178 file.close();
179 return true;
180}
181
185/*
186static void removeDOMComments(QDomNode &node)
187{
188 QDomNode n = node.firstChild();
189 while (!n.isNull()) {
190 if (n.nodeType() == QDomNode::CommentNode) {
191 QDomNode tmp = n;
192 n = n.nextSibling();
193 node.removeChild(tmp);
194 } else {
195 QDomNode tmp = n;
196 n = n.nextSibling();
197 removeDOMComments(tmp);
198 }
199 }
200}*/
201
203 : QObject(parent), d(new KisKXMLGUIFactoryPrivate)
204{
205 d->builder = builder;
206 d->guiClient = 0;
207 if (d->builder) {
210 }
211}
212
214{
215 Q_FOREACH (KisKXMLGUIClient *client, d->m_clients) {
216 client->setFactory(0L);
217 }
218 delete d;
219}
220
222{
223 debugWidgetUtils << client;
224 if (client->factory()) {
225 if (client->factory() == this) {
226 return;
227 } else {
228 client->factory()->removeClient(client); //just in case someone does stupid things ;-)
229 }
230 }
231
232 if (d->emptyState()) {
233 Q_EMIT makingChanges(true);
234 }
235 d->pushState();
236
237// QTime dt; dt.start();
238
239 d->guiClient = client;
240
241 // add this client to our client list
242 if (!d->m_clients.contains(client)) {
243 d->m_clients.append(client);
244 }
245 else {
246 debugWidgetUtils << "XMLGUI client already added " << client;
247 }
248 // Tell the client that plugging in is process and
249 // let it know what builder widget its mainwindow shortcuts
250 // should be attached to.
251 client->beginXMLPlug(d->builder->widget());
252
253 // try to use the build document for building the client's GUI, as the build document
254 // contains the correct container state information (like toolbar positions, sizes, etc.) .
255 // if there is non available, then use the "real" document.
256 QDomDocument doc = client->xmlguiBuildDocument();
257 if (doc.documentElement().isNull()) {
258 doc = client->domDocument();
259 }
260
261 QDomElement docElement = doc.documentElement();
262
263 d->m_rootNode->index = -1;
264
265 // cache some variables
266
267 d->clientName = docElement.attribute(d->attrName);
268 d->clientBuilder = client->clientBuilder();
269
270 if (d->clientBuilder) {
273 } else {
275 d->clientBuilderCustomTags.clear();
276 }
277
278 // load user-defined shortcuts and other action properties
280 if (!doc.isNull()) {
281 d->refreshActionProperties(client, client->actionCollection()->actions(), doc);
282 }
283
284 BuildHelper(*d, d->m_rootNode).build(docElement);
285
286 // let the client know that we built its GUI.
287 client->setFactory(this);
288
289 // call the finalizeGUI method, to fix up the positions of toolbars for example.
290 // ### FIXME : obey client builder
291 // --- Well, toolbars have a bool "positioned", so it doesn't really matter,
292 // if we call positionYourself on all of them each time. (David)
294
295 // reset some variables, for safety
296 d->BuildState::reset();
297
298 client->endXMLPlug();
299
300 d->popState();
301
302 Q_EMIT clientAdded(client);
303
304 // build child clients
305 Q_FOREACH (KisKXMLGUIClient *child, client->childClients()) {
306 addClient(child);
307 }
308
309 if (d->emptyState()) {
310 Q_EMIT makingChanges(false);
311 }
312 /*
313 QString unaddedActions;
314 Q_FOREACH (KisKActionCollection* ac, KisKActionCollection::allCollections())
315 Q_FOREACH (QAction* action, ac->actions())
316 if (action->associatedWidgets().isEmpty())
317 unaddedActions += action->objectName() + ' ';
318
319 if (!unaddedActions.isEmpty())
320 qWarning() << "The following actions are not plugged into the gui (shortcuts will not work): " << unaddedActions;
321 */
322
323// qDebug() << "addClient took " << dt.elapsed();
324}
325
326// Find the right ActionProperties element, otherwise return null element
327static QDomElement findActionPropertiesElement(const QDomDocument &doc)
328{
329 const QLatin1String tagActionProp("ActionProperties");
330 QDomElement e = doc.documentElement().firstChildElement();
331 for (; !e.isNull(); e = e.nextSiblingElement()) {
332 if (QString::compare(e.tagName(), tagActionProp, Qt::CaseInsensitive) == 0) {
333 return e;
334 }
335 }
336 return QDomElement();
337}
338
339void KisKXMLGUIFactoryPrivate::refreshActionProperties(KisKXMLGUIClient *client, const QList<QAction *> &actions, const QDomDocument &doc)
340{
341 // These were used for applyShortcutScheme() but not for applyActionProperties()??
342 Q_UNUSED(client);
343 Q_UNUSED(actions);
344
345
346 // try to find and apply user-defined shortcuts
347 const QDomElement actionPropElement = findActionPropertiesElement(doc);
348 if (!actionPropElement.isNull()) {
349 applyActionProperties(actionPropElement);
350 }
351}
352
354{
355 // This method is called every time the user activated a new
356 // kxmlguiclient. We only want to execute the following code only once in
357 // the lifetime of an action.
358 Q_FOREACH (QAction *action, actions) {
359 // Skip 0 actions or those we have seen already.
360 if (!action || action->property("_k_DefaultShortcut").isValid()) {
361 continue;
362 }
363
364 // Check if the default shortcut is set
365 QList<QKeySequence> defaultShortcut = action->property("defaultShortcuts").value<QList<QKeySequence> >();
366 QList<QKeySequence> activeShortcut = action->shortcuts();
367 //qDebug() << action->objectName() << "default=" << defaultShortcut.toString() << "active=" << activeShortcut.toString();
368
369 // Check if we have an empty default shortcut and an non empty
370 // custom shortcut. Print out a warning and correct the mistake.
371 if ((!activeShortcut.isEmpty()) && defaultShortcut.isEmpty()) {
372 action->setProperty("_k_DefaultShortcut", QVariant::fromValue(activeShortcut));
373 } else {
374 action->setProperty("_k_DefaultShortcut", QVariant::fromValue(defaultShortcut));
375 }
376 }
377}
378
380{
381 d->m_clients.removeAll(client);
382}
383
385{
386 //qDebug(260) << client;
387
388 // don't try to remove the client's GUI if we didn't build it
389 if (!client || client->factory() != this) {
390 return;
391 }
392
393 if (d->emptyState()) {
394 Q_EMIT makingChanges(true);
395 }
396
397 // remove this client from our client list
398 d->m_clients.removeAll(client);
399
400 // remove child clients first (create a copy of the list just in case the
401 // original list is modified directly or indirectly in removeClient())
402 const QList<KisKXMLGUIClient *> childClients(client->childClients());
403 Q_FOREACH (KisKXMLGUIClient *child, childClients) {
404 removeClient(child);
405 }
406
407 //qDebug(260) << "calling removeRecursive";
408
409 d->pushState();
410
411 // cache some variables
412
413 d->guiClient = client;
414 d->clientName = client->domDocument().documentElement().attribute(d->attrName);
415 d->clientBuilder = client->clientBuilder();
416
417 client->setFactory(0L);
418
419 // if we don't have a build document for that client, yet, then create one by
420 // cloning the original document, so that saving container information in the
421 // DOM tree does not touch the original document.
422 QDomDocument doc = client->xmlguiBuildDocument();
423 if (doc.documentElement().isNull()) {
424 doc = client->domDocument().cloneNode(true).toDocument();
425 client->setXMLGUIBuildDocument(doc);
426 }
427
428 d->m_rootNode->destruct(doc.documentElement(), *d);
429
430 // reset some variables
431 d->BuildState::reset();
432
433 // This will destruct the KAccel object built around the given widget.
434 client->prepareXMLUnplug(d->builder->widget());
435
436 d->popState();
437
438 if (d->emptyState()) {
439 Q_EMIT makingChanges(false);
440 }
441
442 Q_EMIT clientRemoved(client);
443}
444
449
450QWidget *KisKXMLGUIFactory::container(const QString &containerName, KisKXMLGUIClient *client,
451 bool useTagName)
452{
453 d->pushState();
454 d->m_containerName = containerName;
455 d->guiClient = client;
456
457 QWidget *result = d->findRecursive(d->m_rootNode, useTagName);
458
459 d->guiClient = 0L;
460 d->m_containerName.clear();
461
462 d->popState();
463
464 return result;
465}
466
468{
469 return d->findRecursive(d->m_rootNode, tagName);
470}
471
473{
474 d->m_rootNode->reset();
475
477}
478
479void KisKXMLGUIFactory::resetContainer(const QString &containerName, bool useTagName)
480{
481 if (containerName.isEmpty()) {
482 return;
483 }
484
485 ContainerNode *container = d->m_rootNode->findContainer(containerName, useTagName);
486
487 if (!container) {
488 return;
489 }
490
491 ContainerNode *parent = container->parent;
492 if (!parent) {
493 return;
494 }
495
496 // resetInternal( container );
497
498 parent->removeChild(container);
499}
500
502{
503 if (((!tag && node->name == m_containerName) ||
504 (tag && node->tagName == m_containerName)) &&
505 (!guiClient || node->client == guiClient)) {
506 return node->container;
507 }
508
509 Q_FOREACH (ContainerNode *child, node->children) {
510 QWidget *cont = findRecursive(child, tag);
511 if (cont) {
512 return cont;
513 }
514 }
515
516 return 0L;
517}
518
519// Case insensitive equality without calling toLower which allocates a new string
520static inline bool equals(const QString &str1, const char *str2)
521{
522 return str1.compare(QLatin1String(str2), Qt::CaseInsensitive) == 0;
523}
524static inline bool equals(const QString &str1, const QString &str2)
525{
526 return str1.compare(str2, Qt::CaseInsensitive) == 0;
527}
528
530 const QString &tagName)
531{
533
534 if (equals(node->tagName, tagName)) {
535 res.append(node->container);
536 }
537
538 Q_FOREACH (KisKXMLGUI::ContainerNode *child, node->children) {
539 res << findRecursive(child, tagName);
540 }
541
542 return res;
543}
544
545void KisKXMLGUIFactory::plugActionList(KisKXMLGUIClient *client, const QString &name,
546 const QList<QAction *> &actionList)
547{
548 d->pushState();
549 d->guiClient = client;
550 d->actionListName = name;
551 d->actionList = actionList;
552 d->clientName = client->domDocument().documentElement().attribute(d->attrName);
553
555
556 // Load shortcuts for these new actions
557 d->saveDefaultActionProperties(actionList);
558 d->refreshActionProperties(client, actionList, client->domDocument());
559
560 d->BuildState::reset();
561 d->popState();
562}
563
565{
566 d->pushState();
567 d->guiClient = client;
568 d->actionListName = name;
569 d->clientName = client->domDocument().documentElement().attribute(d->attrName);
570
572
573 d->BuildState::reset();
574 d->popState();
575}
576
577void KisKXMLGUIFactoryPrivate::applyActionProperties(const QDomElement &actionPropElement,
578 ShortcutOption shortcutOption)
579{
580 for (QDomElement e = actionPropElement.firstChildElement();
581 !e.isNull(); e = e.nextSiblingElement()) {
582 if (!equals(e.tagName(), "action")) {
583 continue;
584 }
585
586 QAction *action = guiClient->action(e);
587 if (!action) {
588 continue;
589 }
590
591 configureAction(action, e.attributes(), shortcutOption);
592 }
593}
594
595void KisKXMLGUIFactoryPrivate::configureAction(QAction *action, const QDomNamedNodeMap &attributes,
596 ShortcutOption shortcutOption)
597{
598 for (int i = 0; i < attributes.length(); i++) {
599 QDomAttr attr = attributes.item(i).toAttr();
600 if (attr.isNull()) {
601 continue;
602 }
603
604 configureAction(action, attr, shortcutOption);
605 }
606}
607
608void KisKXMLGUIFactoryPrivate::configureAction(QAction *action, const QDomAttr &attribute,
609 ShortcutOption shortcutOption)
610{
611 QString attrName = attribute.name();
612 // If the attribute is a deprecated "accel", change to "shortcut".
613 if (equals(attrName, "accel")) {
614 attrName = QStringLiteral("shortcut");
615 }
616
617 // No need to re-set name, particularly since it's "objectName" in Qt4
618 if (equals(attrName, "name")) {
619 return;
620 }
621
622 if (equals(attrName, "icon")) {
623 action->setIcon(KisIconUtils::loadIcon(attribute.value()));
624 return;
625 }
626
627 QVariant propertyValue;
628
629 QVariant::Type propertyType = action->property(attrName.toLatin1().constData()).type();
630 bool isShortcut = (propertyType == QVariant::KeySequence);
631
632 if (propertyType == QVariant::Int) {
633 propertyValue = QVariant(attribute.value().toInt());
634 } else if (propertyType == QVariant::UInt) {
635 propertyValue = QVariant(attribute.value().toUInt());
636 } else if (isShortcut) {
637 // Setting the shortcut by property also sets the default shortcut
638 // (which is incorrect), so we have to do it directly
639 action->setShortcuts(QKeySequence::listFromString(attribute.value()));
641 action->setProperty("defaultShortcuts",
642 QVariant::fromValue(QKeySequence::listFromString(attribute.value())));
643 }
644 } else {
645 propertyValue = QVariant(attribute.value());
646 }
647 if (!isShortcut && !action->setProperty(attrName.toLatin1().constData(), propertyValue)) {
648 qWarning() << "Error: Unknown action property " << attrName << " will be ignored!";
649 }
650}
651
652// Find or create
653QDomElement KisKXMLGUIFactory::actionPropertiesElement(QDomDocument &doc)
654{
655 // first, lets see if we have existing properties
656 QDomElement elem = findActionPropertiesElement(doc);
657
658 // if there was none, create one
659 if (elem.isNull()) {
660 elem = doc.createElement(QStringLiteral("ActionProperties"));
661 doc.documentElement().appendChild(elem);
662 }
663 return elem;
664}
665
666QDomElement KisKXMLGUIFactory::findActionByName(QDomElement &elem, const QString &sName, bool create)
667{
668 const QLatin1String attrName("name");
669 for (QDomNode it = elem.firstChild(); !it.isNull(); it = it.nextSibling()) {
670 QDomElement e = it.toElement();
671 if (e.attribute(attrName) == sName) {
672 return e;
673 }
674 }
675
676 if (create) {
677 QDomElement act_elem = elem.ownerDocument().createElement(QStringLiteral("Action"));
678 act_elem.setAttribute(attrName, sName);
679 elem.appendChild(act_elem);
680 return act_elem;
681 }
682 return QDomElement();
683}
684
#define debugWidgetUtils
QList< QAction * > actions() const
virtual void finalizeGUI(KisKXMLGUIClient *client)
virtual QStringList customTags() const
virtual QStringList containerTags() const
void setFactory(KisKXMLGUIFactory *factory)
void beginXMLPlug(QWidget *)
QDomDocument xmlguiBuildDocument() const
void setXMLGUIBuildDocument(const QDomDocument &doc)
KisKXMLGUIBuilder * clientBuilder() const
virtual QDomDocument domDocument() const
QAction * action(const char *name) const
void prepareXMLUnplug(QWidget *)
KisKXMLGUIFactory * factory() const
QList< KisKXMLGUIClient * > childClients()
virtual KisKActionCollection * actionCollection() const
QWidget * findRecursive(KisKXMLGUI::ContainerNode *node, bool tag)
void applyActionProperties(const QDomElement &element, ShortcutOption shortcutOption=KisKXMLGUIFactoryPrivate::SetActiveShortcut)
void configureAction(QAction *action, const QDomNamedNodeMap &attributes, ShortcutOption shortcutOption=KisKXMLGUIFactoryPrivate::SetActiveShortcut)
void saveDefaultActionProperties(const QList< QAction * > &actions)
void refreshActionProperties(KisKXMLGUIClient *client, const QList< QAction * > &actions, const QDomDocument &doc)
QList< KisKXMLGUIClient * > m_clients
friend class KisKXMLGUI::BuildHelper
~KisKXMLGUIFactory() override
static QDomElement actionPropertiesElement(QDomDocument &doc)
static bool saveConfigFile(const QDomDocument &doc, const QString &filename, const QString &componentName=QString())
static QString readConfigFile(const QString &filename, const QString &componentName=QString())
void resetContainer(const QString &containerName, bool useTagName=false)
void clientRemoved(KisKXMLGUIClient *client)
void removeClient(KisKXMLGUIClient *client)
void addClient(KisKXMLGUIClient *client)
void clientAdded(KisKXMLGUIClient *client)
KisKXMLGUIFactoryPrivate *const d
static QDomElement findActionByName(QDomElement &elem, const QString &sName, bool create)
void plugActionList(KisKXMLGUIClient *client, const QString &name, const QList< QAction * > &actionList)
void unplugActionList(KisKXMLGUIClient *client, const QString &name)
QWidget * container(const QString &containerName, KisKXMLGUIClient *client, bool useTagName=false)
KisKXMLGUIFactory(KisKXMLGUIBuilder *builder, QObject *parent=0)
void makingChanges(bool)
void forgetClient(KisKXMLGUIClient *client)
Internal, called by KisKXMLGUIClient destructor.
QList< QWidget * > containers(const QString &tagName)
QList< KisKXMLGUIClient * > clients() const
Q_DECLARE_METATYPE(KisPaintopLodLimitations)
static QDomElement findActionPropertiesElement(const QDomDocument &doc)
static bool equals(const QString &str1, const char *str2)
QIcon loadIcon(const QString &name)
void setUtf8OnStream(QTextStream &stream)
QStringList clientBuilderContainerTags
QStringList clientBuilderCustomTags
KisKXMLGUIBuilder * builder
KisKXMLGUIClient * guiClient
KisKXMLGUIBuilder * clientBuilder
void unplugActionList(BuildState &state)
void plugActionList(BuildState &state)
ContainerNode * findContainer(const QString &_name, bool tag)
QList< ContainerNode * > children
bool destruct(QDomElement element, BuildState &state)