1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of Qt Creator.
7**
8** Commercial License Usage
9** Licensees holding valid commercial Qt licenses may use this file in
10** accordance with the commercial license agreement provided with the
11** Software or, alternatively, in accordance with the terms contained in
12** a written agreement between you and The Qt Company. For licensing terms
13** and conditions see https://www.qt.io/terms-conditions. For further
14** information use the contact form at https://www.qt.io/contact-us.
15**
16** GNU General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU
18** General Public License version 3 as published by the Free Software
19** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20** included in the packaging of this file. Please review the following
21** information to ensure the GNU General Public License requirements will
22** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23**
24****************************************************************************/
25
26#include "persistentsettings.h"
27
28#include <QDebug>
29#include <QDir>
30#include <QStack>
31#include <QXmlStreamAttributes>
32#include <QXmlStreamReader>
33#include <QXmlStreamWriter>
34#include <QDateTime>
35#include <QTextStream>
36#include <QRegExp>
37#include <QRect>
38
39#ifdef QT_GUI_LIB
40#include <QMessageBox>
41#endif
42
43#include <utils/qtcassert.h>
44
45// Read and write rectangle in X11 resource syntax "12x12+4+3"
46static QString rectangleToString(const QRect &r)
47{
48 QString result;
49 QTextStream(&result) << r.width() << 'x' << r.height() << forcesign << r.x() << r.y();
50 return result;
51}
52
53static QRect stringToRectangle(const QString &v)
54{
55 static QRegExp pattern(QLatin1String("(\\d+)x(\\d+)([-+]\\d+)([-+]\\d+)"));
56 Q_ASSERT(pattern.isValid());
57 return pattern.exactMatch(v) ?
58 QRect(QPoint(pattern.cap(3).toInt(), pattern.cap(4).toInt()),
59 QSize(pattern.cap(1).toInt(), pattern.cap(2).toInt())) :
60 QRect();
61}
62
63/*!
64 \class Utils::PersistentSettingsReader
65
66 \brief The PersistentSettingsReader class reads a QVariantMap of arbitrary,
67 nested data structures from an XML file.
68
69 Handles all string-serializable simple types and QVariantList and QVariantMap. Example:
70 \code
71<qtcreator>
72 <data>
73 <variable>ProjectExplorer.Project.ActiveTarget</variable>
74 <value type="int">0</value>
75 </data>
76 <data>
77 <variable>ProjectExplorer.Project.EditorSettings</variable>
78 <valuemap type="QVariantMap">
79 <value type="bool" key="EditorConfiguration.AutoIndent">true</value>
80 </valuemap>
81 </data>
82 \endcode
83
84 When parsing the structure, a parse stack of ParseValueStackEntry is used for each
85 <data> element. ParseValueStackEntry is a variant/union of:
86 \list
87 \li simple value
88 \li map
89 \li list
90 \endlist
91
92 You can register string-serialize functions for custom types by registering them in the Qt Meta
93 type system. Example:
94 \code
95 QMetaType::registerConverter(&MyCustomType::toString);
96 QMetaType::registerConverter<QString, MyCustomType>(&myCustomTypeFromString);
97 \endcode
98
99 When entering a value element ( \c <value> / \c <valuelist> , \c <valuemap> ), entry is pushed
100 accordingly. When leaving the element, the QVariant-value of the entry is taken off the stack
101 and added to the stack entry below (added to list or inserted into map). The first element
102 of the stack is the value of the <data> element.
103
104 \sa Utils::PersistentSettingsWriter
105*/
106
107namespace Utils {
108
109struct Context // Basic context containing element name string constants.
110{
111 Context() {}
112 const QString qtCreatorElement = QString("qtcreator");
113 const QString dataElement = QString("data");
114 const QString variableElement = QString("variable");
115 const QString typeAttribute = QString("type");
116 const QString valueElement = QString("value");
117 const QString valueListElement = QString("valuelist");
118 const QString valueMapElement = QString("valuemap");
119 const QString keyAttribute = QString("key");
120};
121
122struct ParseValueStackEntry
123{
124 explicit ParseValueStackEntry(QVariant::Type t = QVariant::Invalid, const QString &k = QString()) : type(t), key(k) {}
125 explicit ParseValueStackEntry(const QVariant &aSimpleValue, const QString &k);
126
127 QVariant value() const;
128 void addChild(const QString &key, const QVariant &v);
129
130 QVariant::Type type;
131 QString key;
132 QVariant simpleValue;
133 QVariantList listValue;
134 QVariantMap mapValue;
135};
136
137ParseValueStackEntry::ParseValueStackEntry(const QVariant &aSimpleValue, const QString &k) :
138 type(aSimpleValue.type()), key(k), simpleValue(aSimpleValue)
139{
140 QTC_ASSERT(simpleValue.isValid(), return);
141}
142
143QVariant ParseValueStackEntry::value() const
144{
145 switch (type) {
146 case QVariant::Invalid:
147 return QVariant();
148 case QVariant::Map:
149 return QVariant(mapValue);
150 case QVariant::List:
151 return QVariant(listValue);
152 default:
153 break;
154 }
155 return simpleValue;
156}
157
158void ParseValueStackEntry::addChild(const QString &key, const QVariant &v)
159{
160 switch (type) {
161 case QVariant::Map:
162 mapValue.insert(key, v);
163 break;
164 case QVariant::List:
165 listValue.push_back(v);
166 break;
167 default:
168 qWarning() << "ParseValueStackEntry::Internal error adding " << key << v << " to "
169 << QVariant::typeToName(type) << value();
170 break;
171 }
172}
173
174class ParseContext : public Context
175{
176public:
177 QVariantMap parse(QFile &file);
178
179private:
180 enum Element { QtCreatorElement, DataElement, VariableElement,
181 SimpleValueElement, ListValueElement, MapValueElement, UnknownElement };
182
183 Element element(const QStringRef &r) const;
184 static inline bool isValueElement(Element e)
185 { return e == SimpleValueElement || e == ListValueElement || e == MapValueElement; }
186 QVariant readSimpleValue(QXmlStreamReader &r, const QXmlStreamAttributes &attributes) const;
187
188 bool handleStartElement(QXmlStreamReader &r);
189 bool handleEndElement(const QStringRef &name);
190
191 static QString formatWarning(const QXmlStreamReader &r, const QString &message);
192
193 QStack<ParseValueStackEntry> m_valueStack;
194 QVariantMap m_result;
195 QString m_currentVariableName;
196};
197
198QVariantMap ParseContext::parse(QFile &file)
199{
200 QXmlStreamReader r(&file);
201
202 m_result.clear();
203 m_currentVariableName.clear();
204
205 while (!r.atEnd()) {
206 switch (r.readNext()) {
207 case QXmlStreamReader::StartElement:
208 if (handleStartElement(r))
209 return m_result;
210 break;
211 case QXmlStreamReader::EndElement:
212 if (handleEndElement(r.name()))
213 return m_result;
214 break;
215 case QXmlStreamReader::Invalid:
216 qWarning("Error reading %s:%d: %s", qPrintable(file.fileName()),
217 int(r.lineNumber()), qPrintable(r.errorString()));
218 return QVariantMap();
219 default:
220 break;
221 } // switch token
222 } // while (!r.atEnd())
223 return m_result;
224}
225
226bool ParseContext::handleStartElement(QXmlStreamReader &r)
227{
228 const QStringRef name = r.name();
229 const Element e = element(name);
230 if (e == VariableElement) {
231 m_currentVariableName = r.readElementText();
232 return false;
233 }
234 if (!ParseContext::isValueElement(e))
235 return false;
236
237 const QXmlStreamAttributes attributes = r.attributes();
238 const QString key = attributes.hasAttribute(keyAttribute) ?
239 attributes.value(keyAttribute).toString() : QString();
240 switch (e) {
241 case SimpleValueElement: {
242 // This reads away the end element, so, handle end element right here.
243 const QVariant v = readSimpleValue(r, attributes);
244 if (!v.isValid()) {
245 qWarning() << ParseContext::formatWarning(r, QString::fromLatin1("Failed to read element \"%1\".").arg(name.toString()));
246 return false;
247 }
248 m_valueStack.push_back(ParseValueStackEntry(v, key));
249 return handleEndElement(name);
250 }
251 case ListValueElement:
252 m_valueStack.push_back(ParseValueStackEntry(QVariant::List, key));
253 break;
254 case MapValueElement:
255 m_valueStack.push_back(ParseValueStackEntry(QVariant::Map, key));
256 break;
257 default:
258 break;
259 }
260 return false;
261}
262
263bool ParseContext::handleEndElement(const QStringRef &name)
264{
265 const Element e = element(name);
266 if (ParseContext::isValueElement(e)) {
267 QTC_ASSERT(!m_valueStack.isEmpty(), return true);
268 const ParseValueStackEntry top = m_valueStack.pop();
269 if (m_valueStack.isEmpty()) { // Last element? -> Done with that variable.
270 QTC_ASSERT(!m_currentVariableName.isEmpty(), return true);
271 m_result.insert(m_currentVariableName, top.value());
272 m_currentVariableName.clear();
273 return false;
274 }
275 m_valueStack.top().addChild(top.key, top.value());
276 }
277 return e == QtCreatorElement;
278}
279
280QString ParseContext::formatWarning(const QXmlStreamReader &r, const QString &message)
281{
282 QString result = QLatin1String("Warning reading ");
283 if (const QIODevice *device = r.device())
284 if (const auto file = qobject_cast<const QFile *>(device))
285 result += QDir::toNativeSeparators(file->fileName()) + QLatin1Char(':');
286 result += QString::number(r.lineNumber());
287 result += QLatin1String(": ");
288 result += message;
289 return result;
290}
291
292ParseContext::Element ParseContext::element(const QStringRef &r) const
293{
294 if (r == valueElement)
295 return SimpleValueElement;
296 if (r == valueListElement)
297 return ListValueElement;
298 if (r == valueMapElement)
299 return MapValueElement;
300 if (r == qtCreatorElement)
301 return QtCreatorElement;
302 if (r == dataElement)
303 return DataElement;
304 if (r == variableElement)
305 return VariableElement;
306 return UnknownElement;
307}
308
309QVariant ParseContext::readSimpleValue(QXmlStreamReader &r, const QXmlStreamAttributes &attributes) const
310{
311 // Simple value
312 const QStringRef type = attributes.value(typeAttribute);
313 const QString text = r.readElementText();
314 if (type == QLatin1String("QChar")) { // Workaround: QTBUG-12345
315 QTC_ASSERT(text.size() == 1, return QVariant());
316 return QVariant(QChar(text.at(0)));
317 }
318 if (type == QLatin1String("QRect")) {
319 const QRect rectangle = stringToRectangle(text);
320 return rectangle.isValid() ? QVariant(rectangle) : QVariant();
321 }
322 QVariant value;
323 value.setValue(text);
324 value.convert(QMetaType::type(type.toLatin1().constData()));
325 return value;
326}
327
328// =================================== PersistentSettingsReader
329
330PersistentSettingsReader::PersistentSettingsReader() = default;
331
332QVariant PersistentSettingsReader::restoreValue(const QString &variable, const QVariant &defaultValue) const
333{
334 if (m_valueMap.contains(variable))
335 return m_valueMap.value(variable);
336 return defaultValue;
337}
338
339QVariantMap PersistentSettingsReader::restoreValues() const
340{
341 return m_valueMap;
342}
343
344bool PersistentSettingsReader::load(const FilePath &fileName)
345{
346 m_valueMap.clear();
347
348 QFile file(fileName.toString());
349 if (!file.open(QIODevice::ReadOnly|QIODevice::Text))
350 return false;
351 ParseContext ctx;
352 m_valueMap = ctx.parse(file);
353 file.close();
354 return true;
355}
356
357/*!
358 \class Utils::PersistentSettingsWriter
359
360 \brief The PersistentSettingsWriter class serializes a QVariantMap of
361 arbitrary, nested data structures to an XML file.
362 \sa Utils::PersistentSettingsReader
363*/
364
365static void writeVariantValue(QXmlStreamWriter &w, const Context &ctx,
366 const QVariant &variant, const QString &key = QString())
367{
368 switch (static_cast<int>(variant.type())) {
369 case static_cast<int>(QVariant::StringList):
370 case static_cast<int>(QVariant::List):
371 w.writeStartElement(ctx.valueListElement);
372 w.writeAttribute(ctx.typeAttribute, QLatin1String(QVariant::typeToName(QVariant::List)));
373 if (!key.isEmpty())
374 w.writeAttribute(ctx.keyAttribute, key);
375 foreach (const QVariant &var, variant.toList())
376 writeVariantValue(w, ctx, var);
377 w.writeEndElement();
378 break;
379 case static_cast<int>(QVariant::Map): {
380 w.writeStartElement(ctx.valueMapElement);
381 w.writeAttribute(ctx.typeAttribute, QLatin1String(QVariant::typeToName(QVariant::Map)));
382 if (!key.isEmpty())
383 w.writeAttribute(ctx.keyAttribute, key);
384 const QVariantMap varMap = variant.toMap();
385 const QVariantMap::const_iterator cend = varMap.constEnd();
386 for (QVariantMap::const_iterator i = varMap.constBegin(); i != cend; ++i)
387 writeVariantValue(w, ctx, i.value(), i.key());
388 w.writeEndElement();
389 }
390 break;
391 case static_cast<int>(QMetaType::QObjectStar): // ignore QObjects!
392 case static_cast<int>(QMetaType::VoidStar): // ignore void pointers!
393 break;
394 default:
395 w.writeStartElement(ctx.valueElement);
396 w.writeAttribute(ctx.typeAttribute, QLatin1String(variant.typeName()));
397 if (!key.isEmpty())
398 w.writeAttribute(ctx.keyAttribute, key);
399 switch (variant.type()) {
400 case QVariant::Rect:
401 w.writeCharacters(rectangleToString(variant.toRect()));
402 break;
403 default:
404 w.writeCharacters(variant.toString());
405 break;
406 }
407 w.writeEndElement();
408 break;
409 }
410}
411
412PersistentSettingsWriter::PersistentSettingsWriter(const FilePath &fileName, const QString &docType) :
413 m_fileName(fileName), m_docType(docType)
414{ }
415
416PersistentSettingsWriter::~PersistentSettingsWriter()
417{
418 write(m_savedData, nullptr);
419}
420
421bool PersistentSettingsWriter::save(const QVariantMap &data, QString *errorString) const
422{
423 if (data == m_savedData)
424 return true;
425 return write(data, errorString);
426}
427
428#ifdef QT_GUI_LIB
429bool PersistentSettingsWriter::save(const QVariantMap &data, QWidget *parent) const
430{
431 QString errorString;
432 const bool success = save(data, &errorString);
433 if (!success)
434 QMessageBox::critical(parent,
435 QCoreApplication::translate("Utils::FileSaverBase", "File Error"),
436 errorString);
437 return success;
438}
439#endif // QT_GUI_LIB
440
441FilePath PersistentSettingsWriter::fileName() const
442{ return m_fileName; }
443
444//** * @brief Set contents of file (e.g. from data read from it). */
445void PersistentSettingsWriter::setContents(const QVariantMap &data)
446{
447 m_savedData = data;
448}
449
450bool PersistentSettingsWriter::write(const QVariantMap &data, QString *errorString) const
451{
452 QDir tmp;
453 tmp.mkpath(m_fileName.toFileInfo().path());
454 FileSaver saver(m_fileName.toString(), QIODevice::Text);
455 if (!saver.hasError()) {
456 const Context ctx;
457 QXmlStreamWriter w(saver.file());
458 w.setAutoFormatting(true);
459 w.setAutoFormattingIndent(1); // Historical, used to be QDom.
460 w.writeStartDocument();
461 w.writeDTD(QLatin1String("<!DOCTYPE ") + m_docType + QLatin1Char('>'));
462 w.writeComment(QString::fromLatin1(" Written by %1 %2, %3. ").
463 arg(QCoreApplication::applicationName(),
464 QCoreApplication::applicationVersion(),
465 QDateTime::currentDateTime().toString(Qt::ISODate)));
466 w.writeStartElement(ctx.qtCreatorElement);
467 const QVariantMap::const_iterator cend = data.constEnd();
468 for (QVariantMap::const_iterator it = data.constBegin(); it != cend; ++it) {
469 w.writeStartElement(ctx.dataElement);
470 w.writeTextElement(ctx.variableElement, it.key());
471 writeVariantValue(w, ctx, it.value());
472 w.writeEndElement();
473 }
474 w.writeEndDocument();
475
476 saver.setResult(&w);
477 }
478 bool ok = saver.finalize();
479 if (ok) {
480 m_savedData = data;
481 } else if (errorString) {
482 m_savedData.clear();
483 *errorString = saver.errorString();
484 }
485
486 return ok;
487}
488
489} // namespace Utils
490