openMSX
SettingsConfig.cc
Go to the documentation of this file.
1#include "SettingsConfig.hh"
2
3#include "CliComm.hh"
4#include "CommandException.hh"
5#include "File.hh"
6#include "FileContext.hh"
7#include "FileException.hh"
8#include "FileOperations.hh"
10#include "HotKey.hh"
11#include "MSXException.hh"
12#include "Shortcuts.hh"
13#include "TclObject.hh"
14#include "XMLOutputStream.hh"
15
16#include "MemBuffer.hh"
17#include "StringOp.hh"
18#include "outer.hh"
19#include "rapidsax.hh"
20#include "unreachable.hh"
21
22using std::string;
23
24namespace openmsx {
25
27{
28 void start(std::string_view tag);
29 void attribute(std::string_view name, std::string_view value);
30 void text(std::string_view txt);
31 void stop();
32 void doctype(std::string_view txt);
33
34 // parse result
35 struct Setting {
36 std::string_view name;
37 std::string_view value;
38 };
39 std::vector<Setting> settings;
40 std::vector<HotKey::Data> binds;
41 std::vector<std::string_view> unbinds;
42
47 std::vector<ShortcutItem> shortcutItems;
48
49 std::string_view systemID;
50
51 // parse state
52 unsigned unknownLevel = 0;
67 std::string_view currentUnbind;
69};
70
72 GlobalCommandController& globalCommandController,
73 HotKey& hotKey_, Shortcuts& shortcuts_)
74 : commandController(globalCommandController)
75 , saveSettingsCommand(commandController)
76 , loadSettingsCommand(commandController)
77 , settingsManager(globalCommandController)
78 , hotKey(hotKey_)
79 , shortcuts(shortcuts_)
80{
81}
82
84{
85 if (mustSaveSettings) {
86 try {
88 } catch (FileException& e) {
89 commandController.getCliComm().printWarning(
90 "Auto-saving of settings failed: ", e.getMessage());
91 }
92 }
93}
94
95void SettingsConfig::loadSetting(const FileContext& context, std::string_view filename)
96{
97 string resolved = context.resolve(filename);
98
100 try {
101 File file(resolved);
102 auto size = file.getSize();
104 file.read(std::span{buf.data(), size});
105 buf[size] = 0;
106 } catch (FileException& e) {
107 throw MSXException("Failed to read settings file '", filename,
108 "': ", e.getMessage());
109 }
110 SettingsParser parser;
111 rapidsax::parse<0>(parser, buf.data());
112 if (parser.systemID != "settings.dtd") {
113 throw MSXException(
114 "Failed to parser settings file '", filename,
115 "': systemID doesn't match (expected 'settings.dtd' got '",
116 parser.systemID, "')\n"
117 "You're probably using an old incompatible file format.");
118 }
119
120 settingValues.clear();
121 settingValues.reserve(unsigned(parser.settings.size()));
122 for (const auto& [name, value] : parser.settings) {
123 settingValues.emplace(name, value);
124 }
125
126 hotKey.loadInit();
127 for (const auto& bind : parser.binds) {
128 try {
129 hotKey.loadBind(bind);
130 } catch (MSXException& e) {
131 commandController.getCliComm().printWarning(
132 "Couldn't restore key-binding: ", e.getMessage());
133 }
134 }
135 for (const auto& key : parser.unbinds) {
136 try {
137 hotKey.loadUnbind(key);
138 } catch (MSXException& e) {
139 commandController.getCliComm().printWarning(
140 "Couldn't restore key-binding: ", e.getMessage());
141 }
142 }
143
144 shortcuts.setDefaultShortcuts();
145 for (const auto& item : parser.shortcutItems) {
146 shortcuts.setShortcut(item.id, item.shortcut);
147 }
148
150
151 // only set saveName after file was successfully parsed
152 saveName = resolved;
153}
154
155void SettingsConfig::setSaveFilename(const FileContext& context, std::string_view filename)
156{
157 saveName = context.resolveCreate(filename);
158}
159
160const std::string* SettingsConfig::getValueForSetting(std::string_view setting) const
161{
162 return lookup(settingValues, setting);
163}
164
165void SettingsConfig::setValueForSetting(std::string_view setting, std::string_view value)
166{
167 settingValues.insert_or_assign(setting, value);
168}
169
171{
172 settingValues.erase(setting);
173}
174
175void SettingsConfig::saveSetting(std::string filename)
176{
177 if (filename.empty()) filename = saveName;
178 if (filename.empty()) return;
179
180 struct SettingsWriter {
181 explicit SettingsWriter(std::string filename)
182 : file(std::move(filename), File::OpenMode::TRUNCATE)
183 {
184 std::string_view header =
185 "<!DOCTYPE settings SYSTEM 'settings.dtd'>\n";
186 write(header);
187 }
188
189 void write(std::span<const char> buf) {
190 file.write(buf);
191 }
192 void write1(char c) {
193 file.write(std::span{&c, 1});
194 }
195 void check(bool condition) const {
196 assert(condition); (void)condition;
197 }
198
199 private:
200 File file;
201 };
202
203 SettingsWriter writer(std::move(filename));
204 XMLOutputStream xml(writer);
205 xml.with_tag("settings", [&]{
206 xml.with_tag("settings", [&]{
207 for (const auto& [name_, value_] : settingValues) {
208 const auto& name = name_; // clang-15 workaround
209 const auto& value = value_; // fixed in clang-16
210 xml.with_tag("setting", [&]{
211 xml.attribute("id", name);
212 xml.data(value);
213 });
214 }
215 });
216 hotKey.saveBindings(xml);
217 shortcuts.saveShortcuts(xml);
218 });
219}
220
221
222// class SaveSettingsCommand
223
224SettingsConfig::SaveSettingsCommand::SaveSettingsCommand(
225 CommandController& commandController_)
226 : Command(commandController_, "save_settings")
227{
228}
229
230void SettingsConfig::SaveSettingsCommand::execute(
231 std::span<const TclObject> tokens, TclObject& /*result*/)
232{
233 checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "?filename?");
234 auto& settingsConfig = OUTER(SettingsConfig, saveSettingsCommand);
235 try {
236 switch (tokens.size()) {
237 case 1:
238 settingsConfig.saveSetting();
239 break;
240 case 2:
241 settingsConfig.saveSetting(FileOperations::expandTilde(
242 string(tokens[1].getString())));
243 break;
244 }
245 } catch (FileException& e) {
246 throw CommandException(std::move(e).getMessage());
247 }
248}
249
250string SettingsConfig::SaveSettingsCommand::help(std::span<const TclObject> /*tokens*/) const
251{
252 return "Save the current settings.";
253}
254
255void SettingsConfig::SaveSettingsCommand::tabCompletion(std::vector<string>& tokens) const
256{
257 if (tokens.size() == 2) {
258 completeFileName(tokens, systemFileContext());
259 }
260}
261
262
263// class LoadSettingsCommand
264
265SettingsConfig::LoadSettingsCommand::LoadSettingsCommand(
266 CommandController& commandController_)
267 : Command(commandController_, "load_settings")
268{
269}
270
271void SettingsConfig::LoadSettingsCommand::execute(
272 std::span<const TclObject> tokens, TclObject& /*result*/)
273{
274 checkNumArgs(tokens, 2, "filename");
275 auto& settingsConfig = OUTER(SettingsConfig, loadSettingsCommand);
276 settingsConfig.loadSetting(systemFileContext(), tokens[1].getString());
277}
278
279string SettingsConfig::LoadSettingsCommand::help(std::span<const TclObject> /*tokens*/) const
280{
281 return "Load settings from given file.";
282}
283
284void SettingsConfig::LoadSettingsCommand::tabCompletion(std::vector<string>& tokens) const
285{
286 if (tokens.size() == 2) {
287 completeFileName(tokens, systemFileContext());
288 }
289}
290
291
292void SettingsParser::start(std::string_view tag)
293{
294 if (unknownLevel) {
295 ++unknownLevel;
296 return;
297 }
298 switch (state) {
299 case START:
300 if (tag == "settings") {
301 state = TOP;
302 return;
303 }
304 throw MSXException("Expected <settings> as root tag.");
305 case TOP:
306 if (tag == "settings") {
307 state = SETTINGS;
308 return;
309 } else if (tag == "bindings") {
310 state = BINDINGS;
311 return;
312 } else if (tag == "shortcuts") {
314 return;
315 }
316 break;
317 case SETTINGS:
318 if (tag == "setting") {
319 state = SETTING;
321 return;
322 }
323 break;
324 case BINDINGS:
325 if (tag == "bind") {
326 state = BIND;
328 return;
329 } else if (tag == "unbind") {
330 state = UNBIND;
331 currentUnbind = std::string_view{};
332 return;
333 }
334 break;
335 case SHORTCUTS:
336 if (tag == "shortcut") {
337 state = SHORTCUT;
339 return;
340 }
341 break;
342 case SETTING:
343 case BIND:
344 case UNBIND:
345 case SHORTCUT:
346 break;
347 case END:
348 throw MSXException("Unexpected opening tag: ", tag);
349 default:
351 }
352
353 ++unknownLevel;
354}
355
356void SettingsParser::attribute(std::string_view name, std::string_view value)
357{
358 if (unknownLevel) return;
359
360 switch (state) {
361 case SETTING:
362 if (name == "id") {
363 currentSetting.name = value;
364 }
365 break;
366 case SHORTCUT:
367 if (name == "key") {
368 if (auto keyChord = parseKeyChord(value)) {
370 } else {
371 std::cerr << "Parse error: invalid shortcut key \"" << value << "\"\n";
372 }
373 } else if (name == "type") {
374 if (auto type = Shortcuts::parseType(value)) {
376 } else {
377 std::cerr << "Parse error: invalid shortcut type \"" << value << "\"\n";
378 }
379 }
380 break;
381 case BIND:
382 if (name == "key") {
383 currentBind.key = value;
384 } else if (name == "repeat") {
386 } else if (name == "event") {
388 } else if (name == "msx") {
390 }
391 break;
392 case UNBIND:
393 if (name == "key") {
394 currentUnbind = value;
395 }
396 break;
397 default:
398 break; //nothing
399 }
400}
401
402void SettingsParser::text(std::string_view txt)
403{
404 if (unknownLevel) return;
405
406 switch (state) {
407 case SETTING:
408 currentSetting.value = txt;
409 break;
410 case BIND:
411 currentBind.cmd = txt;
412 break;
413 case SHORTCUT:
414 if (auto value = Shortcuts::parseShortcutName(txt)) {
415 currentShortcut.id = *value;
416 } else {
417 std::cerr << "Parse error: invalid shortcut \"" << txt << "\"\n";
418 }
419 break;
420 default:
421 break; //nothing
422 }
423}
424
426{
427 if (unknownLevel) {
428 --unknownLevel;
429 return;
430 }
431
432 switch (state) {
433 case TOP:
434 state = END;
435 break;
436 case SETTINGS:
437 state = TOP;
438 break;
439 case BINDINGS:
440 state = TOP;
441 break;
442 case SHORTCUTS:
443 state = TOP;
444 break;
445 case SETTING:
446 if (!currentSetting.name.empty()) {
447 settings.push_back(currentSetting);
448 }
449 state = SETTINGS;
450 break;
451 case BIND:
452 if (!currentBind.key.empty()) {
453 binds.push_back(currentBind);
454 }
455 state = BINDINGS;
456 break;
457 case UNBIND:
458 if (!currentUnbind.empty()) {
459 unbinds.push_back(currentUnbind);
460 }
461 state = BINDINGS;
462 break;
463 case SHORTCUT:
466 }
468 break;
469 case START:
470 case END:
471 throw MSXException("Unexpected closing tag");
472 default:
474 }
475}
476
477void SettingsParser::doctype(std::string_view txt)
478{
479 auto pos1 = txt.find(" SYSTEM ");
480 if (pos1 == std::string_view::npos) return;
481 if ((pos1 + 8) >= txt.size()) return;
482 char q = txt[pos1 + 8];
483 if (q != one_of('"', '\'')) return;
484 auto t = txt.substr(pos1 + 9);
485 auto pos2 = t.find(q);
486 if (pos2 == std::string_view::npos) return;
487
488 systemID = t.substr(0, pos2);
489}
490
491} // 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 with_tag(std::string_view tag, std::invocable auto next)
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:462
void clear()
Definition hash_set.hh:531
void reserve(unsigned count)
Definition hash_set.hh:601
bool erase(const K &key)
Definition hash_set.hh:483
void printWarning(std::string_view message)
Definition CliComm.cc:12
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:155
void saveBindings(XmlStream &xml) const
Definition HotKey.hh:57
void loadBind(const Data &data)
Definition HotKey.cc:149
void loadInit()
Definition HotKey.cc:141
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, Shortcuts &shortcuts)
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
void setDefaultShortcuts()
Definition Shortcuts.cc:80
void saveShortcuts(XmlStream &xml) const
Definition Shortcuts.hh:73
static std::optional< ID > parseShortcutName(std::string_view name)
Definition Shortcuts.cc:115
static std::optional< Type > parseType(std::string_view name)
Definition Shortcuts.cc:122
void setShortcut(ID id, const Shortcut &shortcut)
Definition Shortcuts.cc:95
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:16
string expandTilde(string path)
Expand the '~' character to the users home directory.
This file implemented 3 utility functions:
Definition Autofire.cc:11
const FileContext & systemFileContext()
std::optional< ImGuiKeyChord > parseKeyChord(std::string_view name)
constexpr size_t EXTRA_BUFFER_SPACE
Definition rapidsax.hh:42
#define OUTER(type, member)
Definition outer.hh:42
std::string_view key
Definition HotKey.hh:27
std::string_view cmd
Definition HotKey.hh:28
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::vector< HotKey::Data > binds
std::vector< ShortcutItem > shortcutItems
std::string_view currentUnbind
#define UNREACHABLE