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