openMSX
SettingsConfig.cc
Go to the documentation of this file.
1#include "SettingsConfig.hh"
2#include "MSXException.hh"
3#include "StringOp.hh"
4#include "File.hh"
5#include "FileContext.hh"
6#include "FileException.hh"
7#include "FileOperations.hh"
8#include "MemBuffer.hh"
9#include "CliComm.hh"
10#include "HotKey.hh"
11#include "CommandException.hh"
13#include "TclObject.hh"
14#include "XMLOutputStream.hh"
15#include "outer.hh"
16#include "rapidsax.hh"
17#include "unreachable.hh"
18
19using std::string;
20
21namespace openmsx {
22
24{
25 void start(std::string_view tag);
26 void attribute(std::string_view name, std::string_view value);
27 void text(std::string_view txt);
28 void stop();
29 void doctype(std::string_view txt);
30
31 // parse result
32 struct Setting {
33 std::string_view name;
34 std::string_view value;
35 };
36 std::vector<Setting> settings;
37 struct Bind {
38 std::string_view key;
39 std::string_view cmd;
40 bool repeat = false;
41 bool event = false;
42 };
43 std::vector<Bind> binds;
44 std::vector<std::string_view> unbinds;
45 std::string_view systemID;
46
47 // parse state
48 unsigned unknownLevel = 0;
61 std::string_view currentUnbind;
62};
63
65 GlobalCommandController& globalCommandController,
66 HotKey& hotKey_)
67 : commandController(globalCommandController)
68 , saveSettingsCommand(commandController)
69 , loadSettingsCommand(commandController)
70 , settingsManager(globalCommandController)
71 , hotKey(hotKey_)
72{
73}
74
76{
77 if (mustSaveSettings) {
78 try {
80 } catch (FileException& e) {
81 commandController.getCliComm().printWarning(
82 "Auto-saving of settings failed: ", e.getMessage());
83 }
84 }
85}
86
87void SettingsConfig::loadSetting(const FileContext& context, std::string_view filename)
88{
89 string resolved = context.resolve(filename);
90
92 try {
93 File file(resolved);
94 auto size = file.getSize();
96 file.read(std::span{buf.data(), size});
97 buf[size] = 0;
98 } catch (FileException& e) {
99 throw MSXException("Failed to read settings file '", filename,
100 "': ", e.getMessage());
101 }
102 SettingsParser parser;
103 rapidsax::parse<0>(parser, buf.data());
104 if (parser.systemID != "settings.dtd") {
105 throw MSXException(
106 "Failed to parser settings file '", filename,
107 "': systemID doesn't match (expected 'settings.dtd' got '",
108 parser.systemID, "')\n"
109 "You're probably using an old incompatible file format.");
110 }
111
112 settingValues.clear();
113 settingValues.reserve(unsigned(parser.settings.size()));
114 for (const auto& [name, value] : parser.settings) {
115 settingValues.emplace(name, value);
116 }
117
118 hotKey.loadInit();
119 for (const auto& [key, cmd, repeat, event] : parser.binds) {
120 try {
121 hotKey.loadBind(key, cmd, repeat, event);
122 } catch (MSXException& e) {
123 commandController.getCliComm().printWarning(
124 "Couldn't restore key-binding: ", e.getMessage());
125 }
126 }
127 for (const auto& key : parser.unbinds) {
128 try {
129 hotKey.loadUnbind(key);
130 } catch (MSXException& e) {
131 commandController.getCliComm().printWarning(
132 "Couldn't restore key-binding: ", e.getMessage());
133 }
134 }
135
137
138 // only set saveName after file was successfully parsed
139 saveName = resolved;
140}
141
142void SettingsConfig::setSaveFilename(const FileContext& context, std::string_view filename)
143{
144 saveName = context.resolveCreate(filename);
145}
146
147const std::string* SettingsConfig::getValueForSetting(std::string_view setting) const
148{
149 return lookup(settingValues, setting);
150}
151
152void SettingsConfig::setValueForSetting(std::string_view setting, std::string_view value)
153{
154 settingValues.insert_or_assign(setting, value);
155}
156
158{
159 settingValues.erase(setting);
160}
161
162void SettingsConfig::saveSetting(std::string filename)
163{
164 if (filename.empty()) filename = saveName;
165 if (filename.empty()) return;
166
167 struct SettingsWriter {
168 SettingsWriter(std::string filename)
169 : file(std::move(filename), File::TRUNCATE)
170 {
171 std::string_view header =
172 "<!DOCTYPE settings SYSTEM 'settings.dtd'>\n";
173 write(header);
174 }
175
176 void write(std::span<const char> buf) {
177 file.write(buf);
178 }
179 void write1(char c) {
180 file.write(std::span{&c, 1});
181 }
182 void check(bool condition) const {
183 assert(condition); (void)condition;
184 }
185
186 private:
187 File file;
188 };
189
190 SettingsWriter writer(std::move(filename));
191 XMLOutputStream xml(writer);
192 xml.begin("settings");
193 {
194 xml.begin("settings");
195 for (const auto& [name, value] : settingValues) {
196 xml.begin("setting");
197 xml.attribute("id", name);
198 xml.data(value);
199 xml.end("setting");
200 }
201 xml.end("settings");
202 }
203 {
204 hotKey.saveBindings(xml);
205 }
206 xml.end("settings");
207}
208
209
210// class SaveSettingsCommand
211
212SettingsConfig::SaveSettingsCommand::SaveSettingsCommand(
213 CommandController& commandController_)
214 : Command(commandController_, "save_settings")
215{
216}
217
218void SettingsConfig::SaveSettingsCommand::execute(
219 std::span<const TclObject> tokens, TclObject& /*result*/)
220{
221 checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "?filename?");
222 auto& settingsConfig = OUTER(SettingsConfig, saveSettingsCommand);
223 try {
224 switch (tokens.size()) {
225 case 1:
226 settingsConfig.saveSetting();
227 break;
228 case 2:
229 settingsConfig.saveSetting(FileOperations::expandTilde(
230 string(tokens[1].getString())));
231 break;
232 }
233 } catch (FileException& e) {
234 throw CommandException(std::move(e).getMessage());
235 }
236}
237
238string SettingsConfig::SaveSettingsCommand::help(std::span<const TclObject> /*tokens*/) const
239{
240 return "Save the current settings.";
241}
242
243void SettingsConfig::SaveSettingsCommand::tabCompletion(std::vector<string>& tokens) const
244{
245 if (tokens.size() == 2) {
246 completeFileName(tokens, systemFileContext());
247 }
248}
249
250
251// class LoadSettingsCommand
252
253SettingsConfig::LoadSettingsCommand::LoadSettingsCommand(
254 CommandController& commandController_)
255 : Command(commandController_, "load_settings")
256{
257}
258
259void SettingsConfig::LoadSettingsCommand::execute(
260 std::span<const TclObject> tokens, TclObject& /*result*/)
261{
262 checkNumArgs(tokens, 2, "filename");
263 auto& settingsConfig = OUTER(SettingsConfig, loadSettingsCommand);
264 settingsConfig.loadSetting(systemFileContext(), tokens[1].getString());
265}
266
267string SettingsConfig::LoadSettingsCommand::help(std::span<const TclObject> /*tokens*/) const
268{
269 return "Load settings from given file.";
270}
271
272void SettingsConfig::LoadSettingsCommand::tabCompletion(std::vector<string>& tokens) const
273{
274 if (tokens.size() == 2) {
275 completeFileName(tokens, systemFileContext());
276 }
277}
278
279
280void SettingsParser::start(std::string_view tag)
281{
282 if (unknownLevel) {
283 ++unknownLevel;
284 return;
285 }
286 switch (state) {
287 case START:
288 if (tag == "settings") {
289 state = TOP;
290 return;
291 }
292 throw MSXException("Expected <settings> as root tag.");
293 case TOP:
294 if (tag == "settings") {
295 state = SETTINGS;
296 return;
297 } else if (tag == "bindings") {
298 state = BINDINGS;
299 return;
300 }
301 break;
302 case SETTINGS:
303 if (tag == "setting") {
304 state = SETTING;
306 return;
307 }
308 break;
309 case BINDINGS:
310 if (tag == "bind") {
311 state = BIND;
312 currentBind = Bind{};
313 return;
314 } else if (tag == "unbind") {
315 state = UNBIND;
316 currentUnbind = std::string_view{};
317 return;
318 }
319 break;
320 case SETTING:
321 case BIND:
322 case UNBIND:
323 break;
324 case END:
325 throw MSXException("Unexpected opening tag: ", tag);
326 default:
328 }
329
330 ++unknownLevel;
331}
332
333void SettingsParser::attribute(std::string_view name, std::string_view value)
334{
335 if (unknownLevel) return;
336
337 switch (state) {
338 case SETTING:
339 if (name == "id") {
340 currentSetting.name = value;
341 }
342 break;
343 case BIND:
344 if (name == "key") {
345 currentBind.key = value;
346 } else if (name == "repeat") {
348 } else if (name == "event") {
350 }
351 break;
352 case UNBIND:
353 if (name == "key") {
354 currentUnbind = value;
355 }
356 break;
357 default:
358 break; //nothing
359 }
360}
361
362void SettingsParser::text(std::string_view txt)
363{
364 if (unknownLevel) return;
365
366 switch (state) {
367 case SETTING:
368 currentSetting.value = txt;
369 break;
370 case BIND:
371 currentBind.cmd = txt;
372 break;
373 default:
374 break; //nothing
375 }
376}
377
379{
380 if (unknownLevel) {
381 --unknownLevel;
382 return;
383 }
384
385 switch (state) {
386 case TOP:
387 state = END;
388 break;
389 case SETTINGS:
390 state = TOP;
391 break;
392 case BINDINGS:
393 state = TOP;
394 break;
395 case SETTING:
396 if (!currentSetting.name.empty()) {
397 settings.push_back(currentSetting);
398 }
399 state = SETTINGS;
400 break;
401 case BIND:
402 if (!currentBind.key.empty()) {
403 binds.push_back(currentBind);
404 }
405 state = BINDINGS;
406 break;
407 case UNBIND:
408 if (!currentUnbind.empty()) {
409 unbinds.push_back(currentUnbind);
410 }
411 state = BINDINGS;
412 break;
413 case START:
414 case END:
415 throw MSXException("Unexpected closing tag");
416 default:
418 }
419}
420
421void SettingsParser::doctype(std::string_view txt)
422{
423 auto pos1 = txt.find(" SYSTEM ");
424 if (pos1 == std::string_view::npos) return;
425 if ((pos1 + 8) >= txt.size()) return;
426 char q = txt[pos1 + 8];
427 if (q != one_of('"', '\'')) return;
428 auto t = txt.substr(pos1 + 9);
429 auto pos2 = t.find(q);
430 if (pos2 == std::string_view::npos) return;
431
432 systemID = t.substr(0, pos2);
433}
434
435} // namespace openmsx
BaseSetting * setting
TclObject t
'XMLOutputStream' is a helper to write an XML file in a streaming way.
void attribute(std::string_view name, std::string_view value)
void begin(std::string_view tag)
void end(std::string_view tag)
void data(std::string_view value)
std::pair< iterator, bool > insert_or_assign(K &&key, V &&value)
Definition hash_map.hh:99
std::pair< iterator, bool > emplace(Args &&... args)
Definition hash_set.hh:457
void clear()
Definition hash_set.hh:529
void reserve(unsigned count)
Definition hash_set.hh:601
bool erase(const K &key)
Definition hash_set.hh:478
void printWarning(std::string_view message)
Definition CliComm.cc:10
virtual CliComm & getCliComm()=0
void read(std::span< uint8_t > buffer)
Read from file.
Definition File.cc:92
size_t getSize()
Returns the size of this file.
Definition File.cc:112
void loadUnbind(std::string_view key)
Definition HotKey.cc:177
void saveBindings(XmlStream &xml) const
Definition HotKey.hh:50
void loadBind(std::string_view key, std::string_view cmd, bool repeat, bool event)
Definition HotKey.cc:171
void loadInit()
Definition HotKey.cc:163
This class manages the lifetime of a block of memory.
Definition MemBuffer.hh:29
void resize(size_t size)
Grow or shrink the memory block.
Definition MemBuffer.hh:111
const T * data() const
Returns pointer to the start of the memory buffer.
Definition MemBuffer.hh:81
SettingsConfig(GlobalCommandController &globalCommandController, HotKey &hotKey)
void saveSetting(std::string filename={})
void loadSetting(const FileContext &context, std::string_view filename)
SettingsManager & getSettingsManager()
void setSaveFilename(const FileContext &context, std::string_view filename)
void removeValueForSetting(std::string_view setting)
void setValueForSetting(std::string_view setting, std::string_view value)
const std::string * getValueForSetting(std::string_view setting) const
void loadSettings(const SettingsConfig &config)
const Value * lookup(const hash_map< Key, Value, Hasher, Equal > &map, const Key2 &key)
Definition hash_map.hh:118
bool stringToBool(string_view str)
Definition StringOp.cc:12
string expandTilde(string path)
Expand the '~' character to the users home directory.
This file implemented 3 utility functions:
Definition Autofire.cc:9
const FileContext & systemFileContext()
constexpr size_t EXTRA_BUFFER_SPACE
Definition rapidsax.hh:42
#define OUTER(type, member)
Definition outer.hh:41
std::vector< Bind > binds
void start(std::string_view tag)
std::string_view systemID
enum openmsx::SettingsParser::State state
std::vector< Setting > settings
void attribute(std::string_view name, std::string_view value)
std::vector< std::string_view > unbinds
void text(std::string_view txt)
void doctype(std::string_view txt)
std::string_view currentUnbind
#define UNREACHABLE
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
Definition xrange.hh:147