Merge pull request #506 from andrewcsmith/patch-2
[supercollider.git] / editors / sc-ide / core / settings / serialization.cpp
blob430573f31acc8e28ce2f0d14114965e4e14c5c5b
1 /*
2 SuperCollider Qt IDE
3 Copyright (c) 2012 Jakob Leben & Tim Blechmann
4 http://www.audiosynth.com
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.
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.
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
21 #include "serialization.hpp"
23 #include "yaml-cpp/yaml.h"
25 #include <QDebug>
26 #include <QStringList>
27 #include <QKeySequence>
28 #include <boost/iostreams/concepts.hpp>
29 #include <boost/iostreams/stream.hpp>
30 #include <iostream>
32 namespace ScIDE { namespace Settings {
34 typedef QSettings::SettingsMap::const_iterator SettingsIterator;
36 struct IODeviceSource : boost::iostreams::source {
38 IODeviceSource ( QIODevice *dev ) : mDev(dev) {}
39 std::streamsize read(char* s, std::streamsize n)
41 // Read up to n characters from the input
42 // sequence into the buffer s, returning
43 // the number of characters read, or -1
44 // to indicate end-of-sequence.
46 qint64 ret = mDev->read(s, n);
47 if( ret == 0 ) ret = -1;
48 return ret;
51 QIODevice *mDev;
54 static QVariant parseTextFormat( const YAML::Node & node )
56 using namespace YAML;
58 if(node.Type() != NodeType::Map) {
59 qWarning("YAML parsing: a node tagged 'textFormat' has wrong type (not a map)");
60 return QVariant();
63 const Node *n;
64 std::string val;
65 QTextCharFormat fm;
67 n = node.FindValue("color");
68 if(n && n->Type() == NodeType::Scalar) {
69 n->GetScalar(val);
70 fm.setForeground(QColor(val.c_str()));
73 n = node.FindValue("background");
74 if(n && n->Type() == NodeType::Scalar) {
75 n->GetScalar(val);
76 QColor color(val.c_str());
77 if(color.isValid())
78 fm.setBackground(color);
81 n = node.FindValue("bold");
82 if(n && n->Type() == NodeType::Scalar) {
83 bool bold = n->to<bool>();
84 if(bold) fm.setFontWeight(QFont::Bold);
87 n = node.FindValue("italic");
88 if(n && n->Type() == NodeType::Scalar) {
89 bool italic = n->to<bool>();
90 fm.setFontItalic(italic);
93 n = node.FindValue("underline");
94 if(n && n->Type() == NodeType::Scalar) {
95 bool underline = n->to<bool>();
96 fm.setFontUnderline(underline);
99 return QVariant::fromValue<QTextCharFormat>(fm);
102 static QVariant parseScalar( const YAML::Node & node )
104 using namespace YAML;
106 switch (node.Type())
108 case NodeType::Scalar:
110 std::string val;
111 node >> val;
112 return QVariant( QString::fromUtf8(val.c_str()) );
115 case NodeType::Sequence:
117 QVariantList list;
118 YAML::Iterator it;
119 for(it = node.begin(); it != node.end(); ++it)
120 list.append( parseScalar( *it ) );
121 return QVariant::fromValue<QVariantList>( list );
124 case NodeType::Map:
126 QVariantMap map;
127 YAML::Iterator it;
128 for(it = node.begin(); it != node.end(); ++it)
130 std::string key;
131 it.first() >> key;
132 QVariant value = parseScalar( it.second() );
133 map.insert( QString(key.c_str()), value );
135 return QVariant::fromValue<QVariantMap>( map );
138 case NodeType::Null:
139 return QVariant();
141 default:
142 qWarning("YAML parsing: unsupported node type.");
143 return QVariant();
147 static void parseNode( const YAML::Node &node, const QString &parentKey, QSettings::SettingsMap &map )
149 using namespace YAML;
151 static const std::string textFormatTag("!textFormat");
152 static const std::string qVariantListTag("!QVariantList");
153 static const std::string qVariantMapTag("!QVariantMap");
155 Q_ASSERT(node.Type() == NodeType::Map);
157 YAML::Iterator it;
158 for(it = node.begin(); it != node.end(); ++it) {
159 std::string key;
160 it.first() >> key;
161 QString childKey( parentKey );
162 if (!childKey.isEmpty()) childKey += "/";
163 childKey += key.c_str();
165 const YAML::Node & childNode = it.second();
166 const std::string & childTag = childNode.Tag();
168 if (childTag == textFormatTag)
169 map.insert( childKey, parseTextFormat(childNode) );
170 else if (childTag == qVariantListTag || childTag == qVariantMapTag || childNode.Type() != NodeType::Map)
171 map.insert( childKey, parseScalar( childNode ) );
172 else if (childNode.Type() == NodeType::Map)
173 parseNode( childNode, childKey, map );
177 bool readSettings(QIODevice &device, QSettings::SettingsMap &map)
179 using namespace YAML;
181 try {
182 boost::iostreams::stream<IODeviceSource> in( &device );
183 Parser parser(in);
184 Node doc;
185 while(parser.GetNextDocument(doc)) {
186 if( doc.Type() != NodeType::Map ) continue;
187 QString key;
188 parseNode( doc, key, map );
191 return true;
193 catch (std::exception & e) {
194 qWarning() << "Exception when parsing YAML config file:" << e.what();
195 return false;
199 static void writeTextFormat( const QTextCharFormat &fm, YAML::Emitter &out )
201 out << YAML::LocalTag("textFormat");
202 out << YAML::BeginMap;
204 if (fm.hasProperty(QTextFormat::ForegroundBrush)) {
205 out << YAML::Key << "color";
206 out << YAML::Value << fm.foreground().color().name().toStdString();
209 if (fm.hasProperty(QTextFormat::BackgroundBrush)) {
210 out << YAML::Key << "background";
211 out << YAML::Value << fm.background().color().name().toStdString();
214 if (fm.hasProperty(QTextFormat::FontWeight)) {
215 out << YAML::Key << "bold";
216 out << YAML::Value << (fm.fontWeight() == QFont::Bold);
219 if (fm.hasProperty(QTextFormat::FontItalic)) {
220 out << YAML::Key << "italic";
221 out << YAML::Value << fm.fontItalic();
224 if (fm.hasProperty(QTextFormat::TextUnderlineStyle)) {
225 qDebug("saving underline");
226 out << YAML::Key << "underline";
227 out << YAML::Value << fm.fontUnderline();
230 out << YAML::EndMap;
233 static void writeValue( const QVariant &var, YAML::Emitter &out )
235 switch(var.type()) {
236 case QVariant::Invalid:
238 out << YAML::Null;
239 break;
241 case QVariant::KeySequence:
243 QKeySequence kseq = var.value<QKeySequence>();
245 out << kseq.toString( QKeySequence::PortableText ).toUtf8().constData();
247 break;
249 case QVariant::List:
251 out << YAML::LocalTag("QVariantList") << YAML::BeginSeq;
253 QVariantList list = var.value<QVariantList>();
254 foreach (const QVariant & var, list)
255 writeValue( var, out );
257 out << YAML::EndSeq;
259 break;
261 case QVariant::Map:
263 out << YAML::LocalTag("QVariantMap") << YAML::BeginMap;
265 QVariantMap map = var.value<QVariantMap>();
266 QVariantMap::iterator it;
267 for (it = map.begin(); it != map.end(); ++it)
269 out << YAML::Key << it.key().toStdString();
270 out << YAML::Value;
271 writeValue( it.value(), out );
274 out << YAML::EndMap;
276 break;
278 case QVariant::UserType:
280 int utype = var.userType();
282 if (utype == qMetaTypeId<QTextCharFormat>())
284 writeTextFormat( var.value<QTextCharFormat>(), out );
286 else
288 out << var.toString().toUtf8().constData();
290 break;
292 default:
294 out << var.toString().toUtf8().constData();
299 static void writeGroup( const QString &groupKey, YAML::Emitter &out,
300 SettingsIterator &it, const QSettings::SettingsMap &map )
302 out << YAML::BeginMap;
304 int groupKeyLen = groupKey.size();
306 while(it != map.end())
308 QString key( it.key() );
310 if (!key.startsWith(groupKey)) break;
312 int i_separ = key.indexOf("/", groupKeyLen);
313 if (i_separ != -1) {
314 // There is child nodes
315 key.truncate( i_separ + 1 );
317 QString yamlKey(key);
318 yamlKey.remove(0, groupKeyLen);
319 yamlKey.chop(1);
321 out << YAML::Key << yamlKey.toStdString();
322 out << YAML::Value;
324 writeGroup( key, out, it, map );
326 else
328 // There is no child nodes
329 key.remove(0, groupKeyLen);
331 out << YAML::Key << key.toStdString();
332 out << YAML::Value;
334 writeValue( it.value(), out );
336 ++it;
340 out << YAML::EndMap;
343 bool writeSettings(QIODevice &device, const QSettings::SettingsMap &map)
345 try {
346 YAML::Emitter out;
347 SettingsIterator it = map.begin();
348 writeGroup( "", out, it, map );
349 device.write(out.c_str());
350 return true;
352 catch (std::exception & e) {
353 qWarning() << "Exception when writing YAML config file:" << e.what();
354 return false;
358 QSettings::Format serializationFormat() {
359 static QSettings::Format format =
360 QSettings::registerFormat( "yaml", readSettings, writeSettings );
362 if( format == QSettings::InvalidFormat )
363 qWarning("Could not register settings format");
365 return format;
368 void printSettings (const QSettings * settings)
370 using namespace std;
372 cout << "config filename: " << settings->fileName().toStdString() << endl;
373 QStringList keys = settings->allKeys();
374 cout << "num keys: " << keys.count() << endl;
375 Q_FOREACH( QString key, keys ) {
376 QVariant var = settings->value(key);
377 if (var.type() == QVariant::Invalid)
378 cout << key.toStdString() << ": <null>" << endl;
379 else if (var.type() == QVariant::String)
380 cout << key.toStdString() << ": " << var.toString().toStdString() << endl;
381 else
382 cout << key.toStdString() << ": <unknown value type>" << endl;
386 }} // namespace ScIDE::Settings