persistentsettings.cpp 15.5 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8
9
10
11
12
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
Eike Ziller's avatar
Eike Ziller committed
13
14
** conditions see http://www.qt.io/licensing.  For further information
** use the contact form at http://www.qt.io/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18
19
20
21
22
23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
25
26
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
27
28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
hjk's avatar
hjk committed
30

con's avatar
con committed
31
32
#include "persistentsettings.h"

33
#include <QDebug>
34
#include <QDir>
35
36
37
38
39
#include <QStack>
#include <QXmlStreamAttributes>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QDateTime>
40
41
42
#include <QTextStream>
#include <QRegExp>
#include <QRect>
43
44
45

#include <utils/qtcassert.h>

46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// Read and write rectangle in X11 resource syntax "12x12+4+3"
static QString rectangleToString(const QRect &r)
{
    QString result;
    QTextStream(&result) << r.width() << 'x' << r.height() << forcesign << r.x() << r.y();
    return result;
}

static QRect stringToRectangle(const QString &v)
{
    static QRegExp pattern(QLatin1String("(\\d+)x(\\d+)([-+]\\d+)([-+]\\d+)"));
    Q_ASSERT(pattern.isValid());
    return pattern.exactMatch(v) ?
        QRect(QPoint(pattern.cap(3).toInt(), pattern.cap(4).toInt()),
              QSize(pattern.cap(1).toInt(), pattern.cap(2).toInt())) :
        QRect();
}
Jarek Kobus's avatar
Jarek Kobus committed
63

64
/*!
Jarek Kobus's avatar
Jarek Kobus committed
65
    \class Utils::PersistentSettingsReader
66

67
68
    \brief The PersistentSettingsReader class reads a QVariantMap of arbitrary,
    nested data structures from an XML file.
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

    Handles all string-serializable simple types and QVariantList and QVariantMap. Example:
    \code
<qtcreator>
    <data>
        <variable>ProjectExplorer.Project.ActiveTarget</variable>
        <value type="int">0</value>
    </data>
    <data>
        <variable>ProjectExplorer.Project.EditorSettings</variable>
        <valuemap type="QVariantMap">
            <value type="bool" key="EditorConfiguration.AutoIndent">true</value>
        </valuemap>
    </data>
    \endcode

    When parsing the structure, a parse stack of ParseValueStackEntry is used for each
    <data> element. ParseValueStackEntry is a variant/union of:
    \list
88
89
90
    \li simple value
    \li map
    \li list
91
92
93
94
95
96
97
    \endlist

    When entering a value element ( \c <value> / \c <valuelist> , \c <valuemap> ), entry is pushed
    accordingly. When leaving the element, the QVariant-value of the entry is taken off the stack
    and added to the stack entry below (added to list or inserted into map). The first element
    of the stack is the value of the <data> element.

Jarek Kobus's avatar
Jarek Kobus committed
98
    \sa Utils::PersistentSettingsWriter
99
100
*/

Jarek Kobus's avatar
Jarek Kobus committed
101
namespace Utils {
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132

struct Context // Basic context containing element name string constants.
{
    Context();

    const QString qtCreatorElement;
    const QString dataElement;
    const QString variableElement;
    const QString typeAttribute;
    const QString valueElement;
    const QString valueListElement;
    const QString valueMapElement;
    const QString keyAttribute;
};

Context::Context() :
    qtCreatorElement(QLatin1String("qtcreator")),
    dataElement(QLatin1String("data")),
    variableElement(QLatin1String("variable")),
    typeAttribute(QLatin1String("type")),
    valueElement(QLatin1String("value")),
    valueListElement(QLatin1String("valuelist")),
    valueMapElement(QLatin1String("valuemap")),
    keyAttribute(QLatin1String("key"))
{
}

struct ParseValueStackEntry
{
    explicit ParseValueStackEntry(QVariant::Type t = QVariant::Invalid, const QString &k = QString()) : type(t), key(k) {}
    explicit ParseValueStackEntry(const QVariant &aSimpleValue, const QString &k);
con's avatar
con committed
133

134
135
    QVariant value() const;
    void addChild(const QString &key, const QVariant &v);
con's avatar
con committed
136

137
138
139
140
141
142
    QVariant::Type type;
    QString key;
    QVariant simpleValue;
    QVariantList listValue;
    QVariantMap mapValue;
};
con's avatar
con committed
143

144
145
ParseValueStackEntry::ParseValueStackEntry(const QVariant &aSimpleValue, const QString &k) :
    type(aSimpleValue.type()), key(k), simpleValue(aSimpleValue)
con's avatar
con committed
146
{
147
    QTC_ASSERT(simpleValue.isValid(), return);
con's avatar
con committed
148
149
}

150
QVariant ParseValueStackEntry::value() const
con's avatar
con committed
151
{
152
153
154
155
156
157
158
159
160
161
162
    switch (type) {
    case QVariant::Invalid:
        return QVariant();
    case QVariant::Map:
        return QVariant(mapValue);
    case QVariant::List:
        return QVariant(listValue);
    default:
        break;
    }
    return simpleValue;
con's avatar
con committed
163
164
}

165
void ParseValueStackEntry::addChild(const QString &key, const QVariant &v)
166
{
167
168
169
170
171
172
173
174
175
176
177
178
    switch (type) {
    case QVariant::Map:
        mapValue.insert(key, v);
        break;
    case QVariant::List:
        listValue.push_back(v);
        break;
    default:
        qWarning() << "ParseValueStackEntry::Internal error adding " << key << v << " to "
                 << QVariant::typeToName(type) << value();
        break;
    }
179
180
}

181
class ParseContext : public Context
con's avatar
con committed
182
{
183
184
public:
    QVariantMap parse(QFile &file);
con's avatar
con committed
185

186
187
188
private:
    enum Element { QtCreatorElement, DataElement, VariableElement,
                   SimpleValueElement, ListValueElement, MapValueElement, UnknownElement };
con's avatar
con committed
189

190
191
192
193
    Element element(const QStringRef &r) const;
    static inline bool isValueElement(Element e)
        { return e == SimpleValueElement || e == ListValueElement || e == MapValueElement; }
    QVariant readSimpleValue(QXmlStreamReader &r, const QXmlStreamAttributes &attributes) const;
con's avatar
con committed
194

195
196
    bool handleStartElement(QXmlStreamReader &r);
    bool handleEndElement(const QStringRef &name);
con's avatar
con committed
197

198
199
    static QString formatWarning(const QXmlStreamReader &r, const QString &message);

200
201
202
203
    QStack<ParseValueStackEntry> m_valueStack;
    QVariantMap m_result;
    QString m_currentVariableName;
};
con's avatar
con committed
204

205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
QVariantMap ParseContext::parse(QFile &file)
{
    QXmlStreamReader r(&file);

    m_result.clear();
    m_currentVariableName.clear();

    while (!r.atEnd()) {
        switch (r.readNext()) {
        case QXmlStreamReader::StartElement:
            if (handleStartElement(r))
                return m_result;
            break;
        case QXmlStreamReader::EndElement:
            if (handleEndElement(r.name()))
                return m_result;
            break;
        case QXmlStreamReader::Invalid:
            qWarning("Error reading %s:%d: %s", qPrintable(file.fileName()),
                     int(r.lineNumber()), qPrintable(r.errorString()));
            return QVariantMap();
            break;
        default:
            break;
        } // switch token
    } // while (!r.atEnd())
    return m_result;
con's avatar
con committed
232
233
}

234
bool ParseContext::handleStartElement(QXmlStreamReader &r)
con's avatar
con committed
235
{
236
237
238
239
240
241
242
243
    const QStringRef name = r.name();
    const Element e = element(name);
    if (e == VariableElement) {
        m_currentVariableName = r.readElementText();
        return false;
    }
    if (!ParseContext::isValueElement(e))
        return false;
con's avatar
con committed
244

245
246
247
248
    const QXmlStreamAttributes attributes = r.attributes();
    const QString key = attributes.hasAttribute(keyAttribute) ?
                attributes.value(keyAttribute).toString() : QString();
    switch (e) {
249
    case SimpleValueElement: {
250
        // This reads away the end element, so, handle end element right here.
251
252
253
254
255
256
        const QVariant v = readSimpleValue(r, attributes);
        if (!v.isValid()) {
            qWarning() << ParseContext::formatWarning(r, QString::fromLatin1("Failed to read element \"%1\".").arg(name.toString()));
            return false;
        }
        m_valueStack.push_back(ParseValueStackEntry(v, key));
257
        return handleEndElement(name);
258
    }
259
260
261
262
263
264
265
266
    case ListValueElement:
        m_valueStack.push_back(ParseValueStackEntry(QVariant::List, key));
        break;
    case MapValueElement:
        m_valueStack.push_back(ParseValueStackEntry(QVariant::Map, key));
        break;
    default:
        break;
con's avatar
con committed
267
    }
268
269
    return false;
}
con's avatar
con committed
270

271
272
273
274
bool ParseContext::handleEndElement(const QStringRef &name)
{
    const Element e = element(name);
    if (ParseContext::isValueElement(e)) {
275
        QTC_ASSERT(!m_valueStack.isEmpty(), return true);
276
277
        const ParseValueStackEntry top = m_valueStack.pop();
        if (m_valueStack.isEmpty()) { // Last element? -> Done with that variable.
278
            QTC_ASSERT(!m_currentVariableName.isEmpty(), return true);
279
280
281
282
283
284
285
            m_result.insert(m_currentVariableName, top.value());
            m_currentVariableName.clear();
            return false;
        }
        m_valueStack.top().addChild(top.key, top.value());
    }
    return e == QtCreatorElement;
con's avatar
con committed
286
287
}

288
289
290
291
292
293
294
295
296
297
298
299
QString ParseContext::formatWarning(const QXmlStreamReader &r, const QString &message)
{
    QString result = QLatin1String("Warning reading ");
    if (const QIODevice *device = r.device())
        if (const QFile *file = qobject_cast<const QFile *>(device))
            result += QDir::toNativeSeparators(file->fileName()) + QLatin1Char(':');
    result += QString::number(r.lineNumber());
    result += QLatin1String(": ");
    result += message;
    return result;
}

300
ParseContext::Element ParseContext::element(const QStringRef &r) const
con's avatar
con committed
301
{
302
303
304
305
306
307
308
309
310
311
312
313
314
315
    if (r == valueElement)
        return SimpleValueElement;
    if (r == valueListElement)
        return ListValueElement;
    if (r == valueMapElement)
        return MapValueElement;
    if (r == qtCreatorElement)
        return QtCreatorElement;
    if (r == dataElement)
        return DataElement;
    if (r == variableElement)
        return VariableElement;
    return UnknownElement;
}
con's avatar
con committed
316

317
318
319
QVariant ParseContext::readSimpleValue(QXmlStreamReader &r, const QXmlStreamAttributes &attributes) const
{
    // Simple value
320
    const QStringRef type = attributes.value(typeAttribute);
321
322
    const QString text = r.readElementText();
    if (type == QLatin1String("QChar")) { // Workaround: QTBUG-12345
323
        QTC_ASSERT(text.size() == 1, return QVariant());
324
        return QVariant(QChar(text.at(0)));
con's avatar
con committed
325
    }
326
327
328
329
    if (type == QLatin1String("QRect")) {
        const QRect rectangle = stringToRectangle(text);
        return rectangle.isValid() ? QVariant(rectangle) : QVariant();
    }
330
331
332
333
334
    QVariant value;
    value.setValue(text);
    value.convert(QVariant::nameToType(type.toLatin1().data()));
    return value;
}
con's avatar
con committed
335

336
337
338
339
// =================================== PersistentSettingsReader

PersistentSettingsReader::PersistentSettingsReader()
{
con's avatar
con committed
340
341
}

342
QVariant PersistentSettingsReader::restoreValue(const QString &variable, const QVariant &defaultValue) const
343
344
345
{
    if (m_valueMap.contains(variable))
        return m_valueMap.value(variable);
346
    return defaultValue;
347
}
con's avatar
con committed
348

349
QVariantMap PersistentSettingsReader::restoreValues() const
con's avatar
con committed
350
{
351
352
    return m_valueMap;
}
con's avatar
con committed
353

354
bool PersistentSettingsReader::load(const FileName &fileName)
355
356
357
{
    m_valueMap.clear();

358
    QFile file(fileName.toString());
359
360
361
362
363
364
    if (!file.open(QIODevice::ReadOnly|QIODevice::Text))
        return false;
    ParseContext ctx;
    m_valueMap = ctx.parse(file);
    file.close();
    return true;
con's avatar
con committed
365
366
}

367
/*!
Jarek Kobus's avatar
Jarek Kobus committed
368
    \class Utils::PersistentSettingsWriter
369

370
371
    \brief The PersistentSettingsWriter class serializes a QVariantMap of
    arbitrary, nested data structures to an XML file.
Jarek Kobus's avatar
Jarek Kobus committed
372
    \sa Utils::PersistentSettingsReader
373
374
375
376
377
*/

static void writeVariantValue(QXmlStreamWriter &w, const Context &ctx,
                              const QVariant &variant, const QString &key = QString())
{
Tobias Hunger's avatar
Tobias Hunger committed
378
379
380
    switch (static_cast<int>(variant.type())) {
    case static_cast<int>(QVariant::StringList):
    case static_cast<int>(QVariant::List):
381
        w.writeStartElement(ctx.valueListElement);
382
        w.writeAttribute(ctx.typeAttribute, QLatin1String(QVariant::typeToName(QVariant::List)));
383
384
385
386
387
388
        if (!key.isEmpty())
            w.writeAttribute(ctx.keyAttribute, key);
        foreach (const QVariant &var, variant.toList())
            writeVariantValue(w, ctx, var);
        w.writeEndElement();
        break;
Tobias Hunger's avatar
Tobias Hunger committed
389
    case static_cast<int>(QVariant::Map): {
390
        w.writeStartElement(ctx.valueMapElement);
391
        w.writeAttribute(ctx.typeAttribute, QLatin1String(QVariant::typeToName(QVariant::Map)));
392
393
394
395
396
397
398
399
400
        if (!key.isEmpty())
            w.writeAttribute(ctx.keyAttribute, key);
        const QVariantMap varMap = variant.toMap();
        const QVariantMap::const_iterator cend = varMap.constEnd();
        for (QVariantMap::const_iterator i = varMap.constBegin(); i != cend; ++i)
            writeVariantValue(w, ctx, i.value(), i.key());
        w.writeEndElement();
    }
    break;
Tobias Hunger's avatar
Tobias Hunger committed
401
402
403
    case static_cast<int>(QMetaType::QObjectStar): // ignore QObjects!
    case static_cast<int>(QMetaType::VoidStar): // ignore void pointers!
        break;
404
405
406
407
408
    default:
        w.writeStartElement(ctx.valueElement);
        w.writeAttribute(ctx.typeAttribute, QLatin1String(variant.typeName()));
        if (!key.isEmpty())
            w.writeAttribute(ctx.keyAttribute, key);
409
410
411
412
413
414
415
416
        switch (variant.type()) {
        case QVariant::Rect:
            w.writeCharacters(rectangleToString(variant.toRect()));
            break;
        default:
            w.writeCharacters(variant.toString());
            break;
        }
417
418
        w.writeEndElement();
        break;
con's avatar
con committed
419
420
421
    }
}

Tobias Hunger's avatar
Tobias Hunger committed
422
PersistentSettingsWriter::PersistentSettingsWriter(const FileName &fileName, const QString &docType) :
423
    m_fileName(fileName), m_docType(docType)
Tobias Hunger's avatar
Tobias Hunger committed
424
425
{ }

Tobias Hunger's avatar
Tobias Hunger committed
426
427
428
429
430
PersistentSettingsWriter::~PersistentSettingsWriter()
{
    write(m_savedData, 0);
}

431
bool PersistentSettingsWriter::save(const QVariantMap &data, QWidget *parent) const
con's avatar
con committed
432
{
433
    if (data == m_savedData)
Tobias Hunger's avatar
Tobias Hunger committed
434
435
        return true;

Tobias Hunger's avatar
Tobias Hunger committed
436
437
438
439
440
441
442
443
    return write(data, parent);
}

FileName PersistentSettingsWriter::fileName() const
{ return m_fileName; }

bool PersistentSettingsWriter::write(const QVariantMap &data, QWidget *parent) const
{
444
    QDir tmp;
Tobias Hunger's avatar
Tobias Hunger committed
445
    tmp.mkpath(m_fileName.toFileInfo().path());
446
    FileSaver saver(m_fileName.toString(), QIODevice::Text);
447
448
449
450
451
452
    if (!saver.hasError()) {
        const Context ctx;
        QXmlStreamWriter w(saver.file());
        w.setAutoFormatting(true);
        w.setAutoFormattingIndent(1); // Historical, used to be QDom.
        w.writeStartDocument();
Tobias Hunger's avatar
Tobias Hunger committed
453
        w.writeDTD(QLatin1String("<!DOCTYPE ") + m_docType + QLatin1Char('>'));
454
        w.writeComment(QString::fromLatin1(" Written by %1 %2, %3. ").
455
456
                       arg(QCoreApplication::applicationName(),
                           QCoreApplication::applicationVersion(),
457
458
                           QDateTime::currentDateTime().toString(Qt::ISODate)));
        w.writeStartElement(ctx.qtCreatorElement);
459
460
        const QVariantMap::const_iterator cend = data.constEnd();
        for (QVariantMap::const_iterator it =  data.constBegin(); it != cend; ++it) {
461
462
463
464
465
466
            w.writeStartElement(ctx.dataElement);
            w.writeTextElement(ctx.variableElement, it.key());
            writeVariantValue(w, ctx, it.value());
            w.writeEndElement();
        }
        w.writeEndDocument();
con's avatar
con committed
467

468
        saver.setResult(&w);
con's avatar
con committed
469
    }
Tobias Hunger's avatar
Tobias Hunger committed
470
471
    bool ok = saver.finalize(parent);
    if (ok)
472
        m_savedData = data;
Tobias Hunger's avatar
Tobias Hunger committed
473
    return ok;
con's avatar
con committed
474
}
Tobias Hunger's avatar
Tobias Hunger committed
475

Jarek Kobus's avatar
Jarek Kobus committed
476
} // namespace Utils