Krita Source Code Documentation
Loading...
Searching...
No Matches
kxmlguiversionhandler.cpp
Go to the documentation of this file.
1/* This file is part of the KDE libraries
2 SPDX-FileCopyrightText: 2000 Simon Hausmann <hausmann@kde.org>
3 SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
4 SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
10
11#include "kxmlguifactory.h"
12
13#include <QFile>
14#include <QDomDocument>
15#include <QDomElement>
16#include <QStandardPaths>
17#include <QMap>
18
19struct DocStruct {
20 QString file;
21 QString data;
22};
23
24static QList<QDomElement> extractToolBars(const QDomDocument &doc)
25{
26 QList<QDomElement> toolbars;
27 QDomElement parent = doc.documentElement();
28 for (QDomElement e = parent.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
29 if (e.tagName().compare(QStringLiteral("ToolBar"), Qt::CaseInsensitive) == 0) {
30 toolbars.append(e);
31 }
32 }
33 return toolbars;
34}
35
37{
38 QStringList names;
39 names.reserve(toolBars.count());
40 for (const QDomElement &e : toolBars) {
41 names.append(e.attribute(QStringLiteral("name")));
42 }
43 return names;
44}
45
46static void removeToolBars(QDomDocument &doc, const QStringList &toolBarNames)
47
48{
49 QDomElement parent = doc.documentElement();
50 const QList<QDomElement> toolBars = extractToolBars(doc);
51 Q_FOREACH (const QDomElement &e, toolBars) {
52 if (toolBarNames.contains(e.attribute(QStringLiteral("name")))) {
53 parent.removeChild(e);
54 }
55
56 }
57}
58
59static void insertToolBars(QDomDocument &doc, const QList<QDomElement> &toolBars)
60{
61 QDomElement parent = doc.documentElement();
62 QDomElement menuBar = parent.namedItem(QStringLiteral("MenuBar")).toElement();
63 QDomElement insertAfter = menuBar;
64 if (menuBar.isNull()) {
65 insertAfter = parent.firstChildElement(); // if null, insertAfter will do an append
66 }
67 Q_FOREACH (const QDomElement &e, toolBars) {
68 QDomNode result = parent.insertAfter(e, insertAfter);
69 Q_ASSERT(!result.isNull());
70 }
71}
72
73//
74
75typedef QMap<QString, QMap<QString, QString> > ActionPropertiesMap;
76
77static ActionPropertiesMap extractActionProperties(const QDomDocument &doc)
78{
79 ActionPropertiesMap properties;
80
81 QDomElement actionPropElement = doc.documentElement().namedItem(QStringLiteral("ActionProperties")).toElement();
82
83 if (actionPropElement.isNull()) {
84 return properties;
85 }
86
87 QDomNode n = actionPropElement.firstChild();
88 while (!n.isNull()) {
89 QDomElement e = n.toElement();
90 n = n.nextSibling(); // Advance now so that we can safely delete e
91 if (e.isNull()) {
92 continue;
93 }
94
95 if (e.tagName().compare(QStringLiteral("action"), Qt::CaseInsensitive) != 0) {
96 continue;
97 }
98
99 const QString actionName = e.attribute(QStringLiteral("name"));
100 if (actionName.isEmpty()) {
101 continue;
102 }
103
104 QMap<QString, QMap<QString, QString> >::Iterator propIt = properties.find(actionName);
105 if (propIt == properties.end()) {
106 propIt = properties.insert(actionName, QMap<QString, QString>());
107 }
108
109 const QDomNamedNodeMap attributes = e.attributes();
110 const uint attributeslength = attributes.length();
111
112 for (uint i = 0; i < attributeslength; ++i) {
113 const QDomAttr attr = attributes.item(i).toAttr();
114
115 if (attr.isNull()) {
116 continue;
117 }
118
119 const QString name = attr.name();
120
121 if (name == QStringLiteral("name") || name.isEmpty()) {
122 continue;
123 }
124
125 (*propIt)[ name ] = attr.value();
126 }
127
128 }
129
130 return properties;
131}
132
133static void storeActionProperties(QDomDocument &doc,
134 const ActionPropertiesMap &properties)
135{
136 QDomElement actionPropElement = doc.documentElement().namedItem(QStringLiteral("ActionProperties")).toElement();
137
138 if (actionPropElement.isNull()) {
139 actionPropElement = doc.createElement(QStringLiteral("ActionProperties"));
140 doc.documentElement().appendChild(actionPropElement);
141 }
142
143//Remove only those ActionProperties entries from the document, that are present
144//in the properties argument. In real life this means that local ActionProperties
145//takes precedence over global ones, if they exists (think local override of shortcuts).
146 QDomNode actionNode = actionPropElement.firstChild();
147 while (!actionNode.isNull()) {
148 if (properties.contains(actionNode.toElement().attribute(QStringLiteral("name")))) {
149 QDomNode nextNode = actionNode.nextSibling();
150 actionPropElement.removeChild(actionNode);
151 actionNode = nextNode;
152 } else {
153 actionNode = actionNode.nextSibling();
154 }
155 }
156
157 ActionPropertiesMap::ConstIterator it = properties.begin();
158 const ActionPropertiesMap::ConstIterator end = properties.end();
159 for (; it != end; ++it) {
160 QDomElement action = doc.createElement(QStringLiteral("Action"));
161 action.setAttribute(QStringLiteral("name"), it.key());
162 actionPropElement.appendChild(action);
163
164 const QMap<QString, QString> attributes = (*it);
165 QMap<QString, QString>::ConstIterator attrIt = attributes.begin();
166 const QMap<QString, QString>::ConstIterator attrEnd = attributes.end();
167 for (; attrIt != attrEnd; ++attrIt) {
168 action.setAttribute(attrIt.key(), attrIt.value());
169 }
170 }
171}
172
174{
175 enum { ST_START, ST_AFTER_OPEN, ST_AFTER_GUI,
176 ST_EXPECT_VERSION, ST_VERSION_NUM
177 } state = ST_START;
178 const int length = xml.length();
179 for (int pos = 0; pos < length; pos++) {
180 switch (state) {
181 case ST_START:
182 if (xml[pos] == QLatin1Char('<')) {
183 state = ST_AFTER_OPEN;
184 }
185 break;
186 case ST_AFTER_OPEN: {
187 //Jump to gui..
188 const int guipos = xml.indexOf(QStringLiteral("gui"), pos, Qt::CaseInsensitive);
189 if (guipos == -1) {
190 return QString(); //Reject
191 }
192
193 pos = guipos + 2; //Position at i, so we're moved ahead to the next character by the ++;
194 state = ST_AFTER_GUI;
195 break;
196 }
197 case ST_AFTER_GUI:
198 state = ST_EXPECT_VERSION;
199 break;
200 case ST_EXPECT_VERSION: {
201 const int verpos = xml.indexOf(QStringLiteral("version"), pos, Qt::CaseInsensitive);
202 if (verpos == -1) {
203 return QString(); //Reject
204 }
205 pos = verpos + 7; // strlen("version") is 7
206 while (xml.at(pos).isSpace()) {
207 ++pos;
208 }
209 if (xml.at(pos++) != QLatin1Char('=')) {
210 return QString(); //Reject
211 }
212 while (xml.at(pos).isSpace()) {
213 ++pos;
214 }
215
216 state = ST_VERSION_NUM;
217 break;
218 }
219 case ST_VERSION_NUM: {
220 int endpos;
221 for (endpos = pos; endpos < length; endpos++) {
222 const ushort ch = xml[endpos].unicode();
223 if (ch >= QLatin1Char('0') && ch <= QLatin1Char('9')) {
224 continue; //Number..
225 }
226 if (ch == QLatin1Char('"')) { //End of parameter
227 break;
228 } else { //This shouldn't be here..
229 endpos = length;
230 }
231 }
232
233 if (endpos != pos && endpos < length) {
234 const QString matchCandidate = xml.mid(pos, endpos - pos); //Don't include " ".
235 return matchCandidate;
236 }
237
238 state = ST_EXPECT_VERSION; //Try to match a well-formed version..
239 break;
240 } //case..
241 } //switch
242 } //for
243
244 return QString();
245}
246
248{
249 Q_ASSERT(!files.isEmpty());
250
251 if (files.count() == 1) {
252 // No need to parse version numbers if there's only one file anyway
253 m_file = files.first();
255 return;
256 }
257
258 QList<DocStruct> allDocuments;
259
260 Q_FOREACH (const QString &file, files) {
261 DocStruct d;
262 d.file = file;
264 allDocuments.append(d);
265 }
266
267 QList<DocStruct>::iterator best = allDocuments.end();
268 uint bestVersion = 0;
269
270 QList<DocStruct>::iterator docIt = allDocuments.begin();
271 const QList<DocStruct>::iterator docEnd = allDocuments.end();
272 for (; docIt != docEnd; ++docIt) {
273 const QString versionStr = findVersionNumber((*docIt).data);
274 if (versionStr.isEmpty()) {
275 //qDebug(260) << "found no version in" << (*docIt).file;
276 continue;
277 }
278
279 bool ok = false;
280 uint version = versionStr.toUInt(&ok);
281 if (!ok) {
282 continue;
283 }
284 //qDebug(260) << "found version" << version << "for" << (*docIt).file;
285
286 if (version > bestVersion) {
287 best = docIt;
288 //qDebug(260) << "best version is now " << version;
289 bestVersion = version;
290 }
291 }
292
293 if (best != docEnd) {
294 if (best != allDocuments.begin()) {
295 QList<DocStruct>::iterator local = allDocuments.begin();
296
297 if ((*local).file.startsWith(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))) {
298 // load the local document and extract the action properties
299 QDomDocument localDocument;
300 localDocument.setContent((*local).data);
301
302 const ActionPropertiesMap properties = extractActionProperties(localDocument);
303 const QList<QDomElement> toolbars = extractToolBars(localDocument);
304
305 // in case the document has a ActionProperties section
306 // we must not delete it but copy over the global doc
307 // to the local and insert the ActionProperties section
308
309 // TODO: kedittoolbar should mark toolbars as modified so that
310 // we don't keep old toolbars just because the user defined a shortcut
311
312 if (!properties.isEmpty() || !toolbars.isEmpty()) {
313 // now load the global one with the higher version number
314 // into memory
315 QDomDocument document;
316 document.setContent((*best).data);
317 // and store the properties in there
318 storeActionProperties(document, properties);
319 if (!toolbars.isEmpty()) {
320 // remove application toolbars present in the user file
321 // (not others, that the app might have added since)
322 removeToolBars(document, toolBarNames(toolbars));
323
324 // add user toolbars
325 insertToolBars(document, toolbars);
326 }
327
328 (*local).data = document.toString();
329 // make sure we pick up the new local doc, when we return later
330 best = local;
331
332 // write out the new version of the local document
333 QFile f((*local).file);
334 if (f.open(QIODevice::WriteOnly)) {
335 const QByteArray utf8data = (*local).data.toUtf8();
336 f.write(utf8data.constData(), utf8data.length());
337 f.close();
338 }
339 } else {
340 // Move away the outdated local file, to speed things up next time
341 const QString f = (*local).file;
342 const QString backup = f + QStringLiteral(".backup");
343 QFile::rename(f, backup);
344 }
345 }
346 }
347 m_doc = (*best).data;
348 m_file = (*best).file;
349 } else {
350 //qDebug(260) << "returning first one...";
351 m_doc = allDocuments.first().data;
352 m_file = allDocuments.first().file;
353 }
354}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
unsigned int uint
KXmlGuiVersionHandler(const QStringList &files)
static QString findVersionNumber(const QString &xml)
static QString readConfigFile(const QString &filename, const QString &componentName=QString())
static ActionPropertiesMap extractActionProperties(const QDomDocument &doc)
static void insertToolBars(QDomDocument &doc, const QList< QDomElement > &toolBars)
static void removeToolBars(QDomDocument &doc, const QStringList &toolBarNames)
QMap< QString, QMap< QString, QString > > ActionPropertiesMap
static QStringList toolBarNames(const QList< QDomElement > &toolBars)
static QList< QDomElement > extractToolBars(const QDomDocument &doc)
static void storeActionProperties(QDomDocument &doc, const ActionPropertiesMap &properties)