openMSX
UserSettings.cc
Go to the documentation of this file.
1#include "UserSettings.hh"
3#include "SettingsManager.hh"
4#include "CommandException.hh"
5#include "TclObject.hh"
6#include "StringSetting.hh"
7#include "BooleanSetting.hh"
8#include "EnumSetting.hh"
9#include "IntegerSetting.hh"
10#include "FloatSetting.hh"
11#include "checked_cast.hh"
12#include "ranges.hh"
13#include <cassert>
14#include <memory>
15
16namespace openmsx {
17
18// class UserSettings
19
21 : userSettingCommand(commandController_)
22{
23}
24
26{
27 assert(!findSetting(info.setting->getFullName()));
28 settings.push_back(std::move(info));
29}
30
32{
33 move_pop_back(settings, rfind_unguarded(settings, &setting,
34 [](auto& info) { return info.setting.get(); }));
35}
36
37Setting* UserSettings::findSetting(std::string_view name) const
38{
39 auto it = ranges::find(settings, name, [](auto& info) {
40 return info.setting->getFullName(); });
41 return (it != end(settings)) ? it->setting.get() : nullptr;
42}
43
44
45// class UserSettings::Cmd
46
47UserSettings::Cmd::Cmd(CommandController& commandController_)
48 : Command(commandController_, "user_setting")
49{
50}
51
52void UserSettings::Cmd::execute(std::span<const TclObject> tokens, TclObject& result)
53{
54 checkNumArgs(tokens, AtLeast{2}, "subcommand ?arg ...?");
55 executeSubCommand(tokens[1].getString(),
56 "create", [&]{ create(tokens, result); },
57 "destroy", [&]{ destroy(tokens, result); },
58 "info", [&]{ info(tokens, result); });
59}
60
61void UserSettings::Cmd::create(std::span<const TclObject> tokens, TclObject& result)
62{
63 checkNumArgs(tokens, AtLeast{5}, Prefix{2}, "type name ?arg ...?");
64 const auto& type = tokens[2].getString();
65 const auto& settingName = tokens[3].getString();
66
67 auto& controller = checked_cast<GlobalCommandController&>(getCommandController());
68 if (controller.getSettingsManager().findSetting(settingName)) {
69 throw CommandException(
70 "There already exists a setting with this name: ", settingName);
71 }
72
73 auto getInfo = [&] {
74 if (type == "string") {
75 return createString(tokens);
76 } else if (type == "boolean") {
77 return createBoolean(tokens);
78 } else if (type == "integer") {
79 return createInteger(tokens);
80 } else if (type == "float") {
81 return createFloat(tokens);
82 } else if (type == "enum") {
83 return createEnum(tokens);
84 } else {
85 throw CommandException(
86 "Invalid setting type '", type, "', expected "
87 "'string', 'boolean', 'integer', 'float' or 'enum'.");
88 }
89 };
90 auto& userSettings = OUTER(UserSettings, userSettingCommand);
91 userSettings.addSetting(getInfo());
92
93 result = tokens[3]; // name
94}
95
96UserSettings::Info UserSettings::Cmd::createString(std::span<const TclObject> tokens)
97{
98 checkNumArgs(tokens, 6, Prefix{3}, "name description initial-value");
99 const auto& sName = tokens[3].getString();
100 const auto& desc = tokens[4].getString();
101 const auto& initVal = tokens[5].getString();
102
103 auto [storage, view] = make_string_storage(desc);
104 return {std::make_unique<StringSetting>(getCommandController(), sName,
105 view, initVal),
106 std::move(storage)};
107}
108
109UserSettings::Info UserSettings::Cmd::createBoolean(std::span<const TclObject> tokens)
110{
111 checkNumArgs(tokens, 6, Prefix{3}, "name description initial-value");
112 const auto& sName = tokens[3].getString();
113 const auto& desc = tokens[4].getString();
114 const auto& initVal = tokens[5].getBoolean(getInterpreter());
115
116 auto [storage, view] = make_string_storage(desc);
117 return {std::make_unique<BooleanSetting>(getCommandController(), sName,
118 view, initVal),
119 std::move(storage)};
120}
121
122UserSettings::Info UserSettings::Cmd::createInteger(std::span<const TclObject> tokens)
123{
124 checkNumArgs(tokens, 8, Prefix{3}, "name description initial-value min-value max-value");
125 auto& interp = getInterpreter();
126 const auto& sName = tokens[3].getString();
127 const auto& desc = tokens[4].getString();
128 const auto& initVal = tokens[5].getInt(interp);
129 const auto& minVal = tokens[6].getInt(interp);
130 const auto& maxVal = tokens[7].getInt(interp);
131
132 auto [storage, view] = make_string_storage(desc);
133 return {std::make_unique<IntegerSetting>(getCommandController(), sName,
134 view, initVal, minVal, maxVal),
135 std::move(storage)};
136}
137
138UserSettings::Info UserSettings::Cmd::createFloat(std::span<const TclObject> tokens)
139{
140 checkNumArgs(tokens, 8, Prefix{3}, "name description initial-value min-value max-value");
141 auto& interp = getInterpreter();
142 const auto& sName = tokens[3].getString();
143 const auto& desc = tokens[4].getString();
144 const auto& initVal = tokens[5].getDouble(interp);
145 const auto& minVal = tokens[6].getDouble(interp);
146 const auto& maxVal = tokens[7].getDouble(interp);
147
148 auto [storage, view] = make_string_storage(desc);
149 return {std::make_unique<FloatSetting>(getCommandController(), sName,
150 view, initVal, minVal, maxVal),
151 std::move(storage)};
152}
153
154UserSettings::Info UserSettings::Cmd::createEnum(std::span<const TclObject> tokens)
155{
156 checkNumArgs(tokens, 7, Prefix{3}, "name description initial-value allowed-values-list");
157 const auto& sName = tokens[3].getString();
158 const auto& desc = tokens[4].getString();
159 const auto& initStr = tokens[5].getString();
160 const auto& list = tokens[6];
161
162 int initVal = -1;
163 int i = 0;
164 auto map = to_vector(view::transform(list, [&](const auto& s) {
165 if (s == initStr) initVal = i;
166 return EnumSettingBase::MapEntry{std::string(s), i++};
167 }));
168 if (initVal == -1) {
169 throw CommandException(
170 "Initial value '", initStr, "' "
171 "must be one of the allowed values '",
172 list.getString(), '\'');
173 }
174
175 auto [storage, view] = make_string_storage(desc);
176 return {std::make_unique<EnumSetting<int>>(
177 getCommandController(), sName, view, initVal, std::move(map)),
178 std::move(storage)};
179}
180
181void UserSettings::Cmd::destroy(std::span<const TclObject> tokens, TclObject& /*result*/)
182{
183 checkNumArgs(tokens, 3, "name");
184 const auto& settingName = tokens[2].getString();
185
186 auto& userSettings = OUTER(UserSettings, userSettingCommand);
187 auto* setting = userSettings.findSetting(settingName);
188 if (!setting) {
189 throw CommandException(
190 "There is no user setting with this name: ", settingName);
191 }
192 userSettings.deleteSetting(*setting);
193}
194
195void UserSettings::Cmd::info(std::span<const TclObject> /*tokens*/, TclObject& result)
196{
197 result.addListElements(getSettingNames());
198}
199
200std::string UserSettings::Cmd::help(std::span<const TclObject> tokens) const
201{
202 if (tokens.size() < 2) {
203 return
204 "Manage user-defined settings.\n"
205 "\n"
206 "User defined settings are mainly used in Tcl scripts "
207 "to create variables (=settings) that are persistent over "
208 "different openMSX sessions.\n"
209 "\n"
210 " user_setting create <type> <name> <description> <init-value> [<min-value> <max-value>]\n"
211 " user_setting destroy <name>\n"
212 " user_setting info\n"
213 "\n"
214 "Use 'help user_setting <subcommand>' to see more info "
215 "on a specific subcommand.";
216 }
217 assert(tokens.size() >= 2);
218 if (tokens[1] == "create") {
219 return
220 "user_setting create <type> <name> <description> <init-value> [<min-value> <max-value> | <value-list>]\n"
221 "\n"
222 "Create a user defined setting. The extra arguments have the following meaning:\n"
223 " <type> The type for the setting, must be 'string', 'boolean', 'integer' or 'float'.\n"
224 " <name> The name for the setting.\n"
225 " <description> A (short) description for this setting.\n"
226 " This text can be queried via 'help set <setting>'.\n"
227 " <init-value> The initial value for the setting.\n"
228 " This value is only used the very first time the setting is created, otherwise the value is taken from previous openMSX sessions.\n"
229 " <min-value> This parameter is only required for 'integer' and 'float' setting types.\n"
230 " Together with max-value this parameter defines the range of valid values.\n"
231 " <max-value> See min-value.\n"
232 " <value-list> Enum settings have no min and max but instead have a list of possible values";
233
234 } else if (tokens[1] == "destroy") {
235 return
236 "user_setting destroy <name>\n"
237 "\n"
238 "Remove a previously defined user setting. This only "
239 "removes the setting from the current openMSX session, "
240 "the value of this setting is still preserved for "
241 "future sessions.";
242
243 } else if (tokens[1] == "info") {
244 return
245 "user_setting info\n"
246 "\n"
247 "Returns a list of all user defined settings that are "
248 "active in this openMSX session.";
249
250 } else {
251 return "No such subcommand, see 'help user_setting'.";
252 }
253}
254
255void UserSettings::Cmd::tabCompletion(std::vector<std::string>& tokens) const
256{
257 using namespace std::literals;
258 if (tokens.size() == 2) {
259 static constexpr std::array cmds = {
260 "create"sv, "destroy"sv, "info"sv,
261 };
262 completeString(tokens, cmds);
263 } else if ((tokens.size() == 3) && (tokens[1] == "create")) {
264 static constexpr std::array types = {
265 "string"sv, "boolean"sv, "integer"sv, "float"sv, "enum"sv
266 };
267 completeString(tokens, types);
268 } else if ((tokens.size() == 3) && (tokens[1] == "destroy")) {
269 completeString(tokens, getSettingNames());
270 }
271}
272
273} // namespace openmsx
BaseSetting * setting
void deleteSetting(Setting &setting)
void addSetting(Info &&info)
Setting * findSetting(std::string_view name) const
UserSettings(CommandController &commandController)
std::unique_ptr< IDEDevice > create(const DeviceConfig &config)
This file implemented 3 utility functions:
Definition Autofire.cc:9
auto find(InputRange &&range, const T &value)
Definition ranges.hh:160
Definition view.hh:15
constexpr auto transform(Range &&range, UnaryOp op)
Definition view.hh:520
#define OUTER(type, member)
Definition outer.hh:41
auto make_string_storage(std::string_view sv)
Take a string_view, make a copy of it, and return a pair of.
auto to_vector(Range &&range) -> std::vector< detail::ToVectorType< T, decltype(std::begin(range))> >
Definition stl.hh:275
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition stl.hh:134
auto rfind_unguarded(RANGE &range, const VAL &val, Proj proj={})
Similar to the find(_if)_unguarded functions above, but searches from the back to front.
Definition stl.hh:109
constexpr auto end(const zstring_view &x)