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;
49 enum State {
57 END
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 hotKey.loadBind(key, cmd, repeat, event);
121 }
122 for (const auto& key : parser.unbinds) {
123 hotKey.loadUnbind(key);
124 }
125
127
128 // only set saveName after file was successfully parsed
129 saveName = resolved;
130}
131
132void SettingsConfig::setSaveFilename(const FileContext& context, std::string_view filename)
133{
134 saveName = context.resolveCreate(filename);
135}
136
137const std::string* SettingsConfig::getValueForSetting(std::string_view setting) const
138{
139 return lookup(settingValues, setting);
140}
141
142void SettingsConfig::setValueForSetting(std::string_view setting, std::string_view value)
143{
144 settingValues.insert_or_assign(setting, value);
145}
146
148{
149 settingValues.erase(setting);
150}
151
152void SettingsConfig::saveSetting(std::string filename)
153{
154 if (filename.empty()) filename = saveName;
155 if (filename.empty()) return;
156
157 struct SettingsWriter {
158 SettingsWriter(std::string filename)
159 : file(std::move(filename), File::TRUNCATE)
160 {
161 std::string_view header =
162 "<!DOCTYPE settings SYSTEM 'settings.dtd'>\n";
163 write(header);
164 }
165
166 void write(std::span<const char> buf) {
167 file.write(buf);
168 }
169 void write1(char c) {
170 file.write(std::span{&c, 1});
171 }
172 void check(bool condition) const {
173 assert(condition); (void)condition;
174 }
175
176 private:
177 File file;
178 };
179
180 SettingsWriter writer(std::move(filename));
181 XMLOutputStream xml(writer);
182 xml.begin("settings");
183 {
184 xml.begin("settings");
185 for (const auto& [name, value] : settingValues) {
186 xml.begin("setting");
187 xml.attribute("id", name);
188 xml.data(value);
189 xml.end("setting");
190 }
191 xml.end("settings");
192 }
193 {
194 hotKey.saveBindings(xml);
195 }
196 xml.end("settings");
197}
198
199
200// class SaveSettingsCommand
201
202SettingsConfig::SaveSettingsCommand::SaveSettingsCommand(
203 CommandController& commandController_)
204 : Command(commandController_, "save_settings")
205{
206}
207
208void SettingsConfig::SaveSettingsCommand::execute(
209 std::span<const TclObject> tokens, TclObject& /*result*/)
210{
211 checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "?filename?");
212 auto& settingsConfig = OUTER(SettingsConfig, saveSettingsCommand);
213 try {
214 switch (tokens.size()) {
215 case 1:
216 settingsConfig.saveSetting();
217 break;
218 case 2:
219 settingsConfig.saveSetting(FileOperations::expandTilde(
220 string(tokens[1].getString())));
221 break;
222 }
223 } catch (FileException& e) {
224 throw CommandException(std::move(e).getMessage());
225 }
226}
227
228string SettingsConfig::SaveSettingsCommand::help(std::span<const TclObject> /*tokens*/) const
229{
230 return "Save the current settings.";
231}
232
233void SettingsConfig::SaveSettingsCommand::tabCompletion(std::vector<string>& tokens) const
234{
235 if (tokens.size() == 2) {
236 completeFileName(tokens, systemFileContext());
237 }
238}
239
240
241// class LoadSettingsCommand
242
243SettingsConfig::LoadSettingsCommand::LoadSettingsCommand(
244 CommandController& commandController_)
245 : Command(commandController_, "load_settings")
246{
247}
248
249void SettingsConfig::LoadSettingsCommand::execute(
250 std::span<const TclObject> tokens, TclObject& /*result*/)
251{
252 checkNumArgs(tokens, 2, "filename");
253 auto& settingsConfig = OUTER(SettingsConfig, loadSettingsCommand);
254 settingsConfig.loadSetting(systemFileContext(), tokens[1].getString());
255}
256
257string SettingsConfig::LoadSettingsCommand::help(std::span<const TclObject> /*tokens*/) const
258{
259 return "Load settings from given file.";
260}
261
262void SettingsConfig::LoadSettingsCommand::tabCompletion(std::vector<string>& tokens) const
263{
264 if (tokens.size() == 2) {
265 completeFileName(tokens, systemFileContext());
266 }
267}
268
269
270void SettingsParser::start(std::string_view tag)
271{
272 if (unknownLevel) {
273 ++unknownLevel;
274 return;
275 }
276 switch (state) {
277 case START:
278 if (tag == "settings") {
279 state = TOP;
280 return;
281 }
282 throw MSXException("Expected <settings> as root tag.");
283 case TOP:
284 if (tag == "settings") {
285 state = SETTINGS;
286 return;
287 } else if (tag == "bindings") {
288 state = BINDINGS;
289 return;
290 }
291 break;
292 case SETTINGS:
293 if (tag == "setting") {
294 state = SETTING;
296 return;
297 }
298 break;
299 case BINDINGS:
300 if (tag == "bind") {
301 state = BIND;
302 currentBind = Bind{};
303 return;
304 } else if (tag == "unbind") {
305 state = UNBIND;
306 currentUnbind = std::string_view{};
307 return;
308 }
309 break;
310 case SETTING:
311 case BIND:
312 case UNBIND:
313 break;
314 case END:
315 throw MSXException("Unexpected opening tag: ", tag);
316 default:
318 }
319
320 ++unknownLevel;
321}
322
323void SettingsParser::attribute(std::string_view name, std::string_view value)
324{
325 if (unknownLevel) return;
326
327 switch (state) {
328 case SETTING:
329 if (name == "id") {
330 currentSetting.name = value;
331 }
332 break;
333 case BIND:
334 if (name == "key") {
335 currentBind.key = value;
336 } else if (name == "repeat") {
338 } else if (name == "event") {
340 }
341 break;
342 case UNBIND:
343 if (name == "key") {
344 currentUnbind = value;
345 }
346 break;
347 default:
348 break; //nothing
349 }
350}
351
352void SettingsParser::text(std::string_view txt)
353{
354 if (unknownLevel) return;
355
356 switch (state) {
357 case SETTING:
358 currentSetting.value = txt;
359 break;
360 case BIND:
361 currentBind.cmd = txt;
362 break;
363 default:
364 break; //nothing
365 }
366}
367
369{
370 if (unknownLevel) {
371 --unknownLevel;
372 return;
373 }
374
375 switch (state) {
376 case TOP:
377 state = END;
378 break;
379 case SETTINGS:
380 state = TOP;
381 break;
382 case BINDINGS:
383 state = TOP;
384 break;
385 case SETTING:
386 if (!currentSetting.name.empty()) {
387 settings.push_back(currentSetting);
388 }
389 state = SETTINGS;
390 break;
391 case BIND:
392 if (!currentBind.key.empty()) {
393 binds.push_back(currentBind);
394 }
395 state = BINDINGS;
396 break;
397 case UNBIND:
398 if (!currentUnbind.empty()) {
399 unbinds.push_back(currentUnbind);
400 }
401 state = BINDINGS;
402 break;
403 case START:
404 case END:
405 throw MSXException("Unexpected closing tag");
406 default:
408 }
409}
410
411void SettingsParser::doctype(std::string_view txt)
412{
413 auto pos1 = txt.find(" SYSTEM ");
414 if (pos1 == std::string_view::npos) return;
415 if ((pos1 + 8) >= txt.size()) return;
416 char q = txt[pos1 + 8];
417 if (q != one_of('"', '\'')) return;
418 auto t = txt.substr(pos1 + 9);
419 auto pos2 = t.find(q);
420 if (pos2 == std::string_view::npos) return;
421
422 systemID = t.substr(0, pos2);
423}
424
425} // namespace openmsx
BaseSetting * setting
Definition: Interpreter.cc:28
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:462
void clear()
Definition: hash_set.hh:534
void reserve(unsigned count)
Definition: hash_set.hh:606
bool erase(const K &key)
Definition: hash_set.hh:483
Definition: one_of.hh:7
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
@ TRUNCATE
Definition: File.hh:20
void loadUnbind(std::string_view key)
Definition: HotKey.cc:190
void saveBindings(XmlStream &xml) const
Definition: HotKey.hh:47
void loadBind(std::string_view key, std::string_view cmd, bool repeat, bool event)
Definition: HotKey.cc:184
void loadInit()
Definition: HotKey.cc:176
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
constexpr double e
Definition: Math.hh:20
bool stringToBool(string_view str)
Definition: StringOp.cc:12
std::optional< Context > context
Definition: GLContext.cc:10
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()
Definition: FileContext.cc:155
constexpr size_t EXTRA_BUFFER_SPACE
Definition: rapidsax.hh:42
size_t size(std::string_view utf8)
#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
Definition: unreachable.hh:38
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
Definition: xrange.hh:148