1 /*
2 SuperCollider Qt IDE
3 Copyright (c) 2012 Jakob Leben & Tim Blechmann
4 http://www.audiosynth.com
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include "serialization.hpp"
22
23 #include <yaml-cpp/yaml.h>
24
25 #include <QDebug>
26 #include <QStringList>
27 #include <QKeySequence>
28 #include <boost/iostreams/concepts.hpp>
29 #include <boost/iostreams/stream.hpp>
30 #include <iostream>
31
32 namespace ScIDE { namespace Settings {
33
34 typedef QSettings::SettingsMap::const_iterator SettingsIterator;
35
36 struct IODeviceSource : boost::iostreams::source {
IODeviceSourceScIDE::Settings::IODeviceSource37 IODeviceSource(QIODevice* dev): mDev(dev) {}
readScIDE::Settings::IODeviceSource38 std::streamsize read(char* s, std::streamsize n) {
39 // Read up to n characters from the input
40 // sequence into the buffer s, returning
41 // the number of characters read, or -1
42 // to indicate end-of-sequence.
43
44 qint64 ret = mDev->read(s, n);
45 if (ret == 0)
46 ret = -1;
47 return ret;
48 }
49
50 QIODevice* mDev;
51 };
52
parseTextFormat(const YAML::Node & node)53 static QVariant parseTextFormat(const YAML::Node& node) {
54 using namespace YAML;
55
56 if (node.Type() != NodeType::Map) {
57 qWarning("YAML parsing: a node tagged 'textFormat' has wrong type (not a map)");
58 return QVariant();
59 }
60
61 std::string val;
62 QTextCharFormat fm;
63
64 const Node ncolor = node["color"];
65 if (ncolor && ncolor.IsScalar()) {
66 val = ncolor.as<std::string>();
67 fm.setForeground(QColor(val.c_str()));
68 }
69
70 const Node nbg = node["background"];
71 if (nbg && nbg.IsScalar()) {
72 val = nbg.as<std::string>();
73 QColor color(val.c_str());
74 if (color.isValid())
75 fm.setBackground(color);
76 }
77
78 const Node nbold = node["bold"];
79 if (nbold && nbold.IsScalar()) {
80 bool bold = nbold.as<bool>();
81 if (bold)
82 fm.setFontWeight(QFont::Bold);
83 }
84
85 const Node nitalic = node["italic"];
86 if (nitalic && nitalic.IsScalar()) {
87 bool italic = nitalic.as<bool>();
88 fm.setFontItalic(italic);
89 }
90
91 const Node nunder = node["underline"];
92 if (nunder && nunder.IsScalar()) {
93 bool underline = nunder.as<bool>();
94 fm.setFontUnderline(underline);
95 }
96
97 return QVariant::fromValue<QTextCharFormat>(fm);
98 }
99
parseScalar(const YAML::Node & node)100 static QVariant parseScalar(const YAML::Node& node) {
101 using namespace YAML;
102
103 switch (node.Type()) {
104 case NodeType::Scalar: {
105 std::string val = node.as<std::string>();
106 return QVariant(QString::fromUtf8(val.c_str()));
107 }
108
109 case NodeType::Sequence: {
110 QVariantList list;
111 for (auto const& element : node)
112 list.append(parseScalar(element));
113 return QVariant::fromValue<QVariantList>(list);
114 }
115
116 case NodeType::Map: {
117 QVariantMap map;
118 for (auto const& element : node) {
119 std::string key = element.first.as<std::string>();
120 QVariant value = parseScalar(element.second);
121 map.insert(QString(key.c_str()), value);
122 }
123 return QVariant::fromValue<QVariantMap>(map);
124 }
125
126 case NodeType::Null:
127 return QVariant();
128
129 default:
130 qWarning("YAML parsing: unsupported node type.");
131 return QVariant();
132 }
133 }
134
parseNode(const YAML::Node & node,const QString & parentKey,QSettings::SettingsMap & map)135 static void parseNode(const YAML::Node& node, const QString& parentKey, QSettings::SettingsMap& map) {
136 using namespace YAML;
137
138 static const std::string textFormatTag("!textFormat");
139 static const std::string qVariantListTag("!QVariantList");
140 static const std::string qVariantMapTag("!QVariantMap");
141
142 Q_ASSERT(node.Type() == NodeType::Map);
143 for (auto const& element : node) {
144 std::string key = element.first.as<std::string>();
145 QString childKey(parentKey);
146 if (!childKey.isEmpty())
147 childKey += "/";
148 childKey += key.c_str();
149
150 const YAML::Node& childNode = element.second;
151 const std::string& childTag = childNode.Tag();
152
153 if (childTag == textFormatTag)
154 map.insert(childKey, parseTextFormat(childNode));
155 else if (childTag == qVariantListTag || childTag == qVariantMapTag || childNode.Type() != NodeType::Map)
156 map.insert(childKey, parseScalar(childNode));
157 else if (childNode.Type() == NodeType::Map)
158 parseNode(childNode, childKey, map);
159 }
160 }
161
readSettings(QIODevice & device,QSettings::SettingsMap & map)162 bool readSettings(QIODevice& device, QSettings::SettingsMap& map) {
163 using namespace YAML;
164
165 try {
166 boost::iostreams::stream<IODeviceSource> in(&device);
167 Node doc = Load(in);
168 if (doc) {
169 if (doc.IsMap()) {
170 QString key;
171 parseNode(doc, key, map);
172 }
173 }
174
175 return true;
176 } catch (std::exception& e) {
177 qWarning() << "Exception when parsing YAML config file:" << e.what();
178 return false;
179 }
180 }
181
writeTextFormat(const QTextCharFormat & fm,YAML::Emitter & out)182 static void writeTextFormat(const QTextCharFormat& fm, YAML::Emitter& out) {
183 out << YAML::LocalTag("textFormat");
184 out << YAML::BeginMap;
185
186 if (fm.hasProperty(QTextFormat::ForegroundBrush)) {
187 out << YAML::Key << "color";
188 out << YAML::Value << fm.foreground().color().name().toStdString();
189 }
190
191 if (fm.hasProperty(QTextFormat::BackgroundBrush)) {
192 out << YAML::Key << "background";
193 out << YAML::Value << fm.background().color().name().toStdString();
194 }
195
196 if (fm.hasProperty(QTextFormat::FontWeight)) {
197 out << YAML::Key << "bold";
198 out << YAML::Value << (fm.fontWeight() == QFont::Bold);
199 }
200
201 if (fm.hasProperty(QTextFormat::FontItalic)) {
202 out << YAML::Key << "italic";
203 out << YAML::Value << fm.fontItalic();
204 }
205
206 if (fm.hasProperty(QTextFormat::TextUnderlineStyle)) {
207 qDebug("saving underline");
208 out << YAML::Key << "underline";
209 out << YAML::Value << fm.fontUnderline();
210 }
211
212 out << YAML::EndMap;
213 }
214
writeValue(const QVariant & var,YAML::Emitter & out)215 static void writeValue(const QVariant& var, YAML::Emitter& out) {
216 switch (var.type()) {
217 case QVariant::Invalid: {
218 out << YAML::Null;
219 break;
220 }
221 case QVariant::KeySequence: {
222 QKeySequence kseq = var.value<QKeySequence>();
223
224 out << kseq.toString(QKeySequence::PortableText).toUtf8().constData();
225
226 break;
227 }
228 case QVariant::List: {
229 out << YAML::LocalTag("QVariantList") << YAML::BeginSeq;
230
231 QVariantList list = var.value<QVariantList>();
232 foreach (const QVariant& var, list)
233 writeValue(var, out);
234
235 out << YAML::EndSeq;
236
237 break;
238 }
239 case QVariant::Map: {
240 out << YAML::LocalTag("QVariantMap") << YAML::BeginMap;
241
242 QVariantMap map = var.value<QVariantMap>();
243 QVariantMap::iterator it;
244 for (it = map.begin(); it != map.end(); ++it) {
245 out << YAML::Key << it.key().toStdString();
246 out << YAML::Value;
247 writeValue(it.value(), out);
248 }
249
250 out << YAML::EndMap;
251
252 break;
253 }
254 case QVariant::UserType: {
255 int utype = var.userType();
256
257 if (utype == qMetaTypeId<QTextCharFormat>()) {
258 writeTextFormat(var.value<QTextCharFormat>(), out);
259 } else {
260 out << var.toString().toUtf8().constData();
261 }
262 break;
263 }
264 default: {
265 out << var.toString().toUtf8().constData();
266 }
267 }
268 }
269
writeGroup(const QString & groupKey,YAML::Emitter & out,SettingsIterator & it,const QSettings::SettingsMap & map)270 static void writeGroup(const QString& groupKey, YAML::Emitter& out, SettingsIterator& it,
271 const QSettings::SettingsMap& map) {
272 out << YAML::BeginMap;
273
274 int groupKeyLen = groupKey.size();
275
276 while (it != map.end()) {
277 QString key(it.key());
278
279 if (!key.startsWith(groupKey))
280 break;
281
282 int i_separ = key.indexOf("/", groupKeyLen);
283 if (i_separ != -1) {
284 // There is child nodes
285 key.truncate(i_separ + 1);
286
287 QString yamlKey(key);
288 yamlKey.remove(0, groupKeyLen);
289 yamlKey.chop(1);
290
291 out << YAML::Key << yamlKey.toStdString();
292 out << YAML::Value;
293
294 writeGroup(key, out, it, map);
295 } else {
296 // There is no child nodes
297 key.remove(0, groupKeyLen);
298
299 out << YAML::Key << key.toStdString();
300 out << YAML::Value;
301
302 writeValue(it.value(), out);
303
304 ++it;
305 }
306 }
307
308 out << YAML::EndMap;
309 }
310
writeSettings(QIODevice & device,const QSettings::SettingsMap & map)311 bool writeSettings(QIODevice& device, const QSettings::SettingsMap& map) {
312 try {
313 YAML::Emitter out;
314 SettingsIterator it = map.begin();
315 writeGroup("", out, it, map);
316 device.write(out.c_str());
317 return true;
318 } catch (std::exception& e) {
319 qWarning() << "Exception when writing YAML config file:" << e.what();
320 return false;
321 }
322 }
323
serializationFormat()324 QSettings::Format serializationFormat() {
325 static QSettings::Format format = QSettings::registerFormat("yaml", readSettings, writeSettings);
326
327 if (format == QSettings::InvalidFormat)
328 qWarning("Could not register settings format");
329
330 return format;
331 }
332
printSettings(const QSettings * settings)333 void printSettings(const QSettings* settings) {
334 using namespace std;
335
336 cout << "config filename: " << settings->fileName().toStdString() << endl;
337 QStringList keys = settings->allKeys();
338 cout << "num keys: " << keys.count() << endl;
339 Q_FOREACH (QString key, keys) {
340 QVariant var = settings->value(key);
341 if (var.type() == QVariant::Invalid)
342 cout << key.toStdString() << ": <null>" << endl;
343 else if (var.type() == QVariant::String)
344 cout << key.toStdString() << ": " << var.toString().toStdString() << endl;
345 else
346 cout << key.toStdString() << ": <unknown value type>" << endl;
347 }
348 }
349
350 }} // namespace ScIDE::Settings
351