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