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