OXYGENHUB
◆ main openmsx / src / settings / UserSettings.cc
9.2 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "UserSettings.hh"
2
3
#include "BooleanSetting.hh"
4
#include "EnumSetting.hh"
5
#include "FloatSetting.hh"
6
#include "IntegerSetting.hh"
7
#include "SettingsManager.hh"
8
#include "StringSetting.hh"
9
10
#include "CommandException.hh"
11
#include "GlobalCommandController.hh"
12
#include "TclObject.hh"
13
14
#include "checked_cast.hh"
15
16
#include <algorithm>
17
#include <cassert>
18
#include <memory>
19
#include <ranges>
20
21
namespace openmsx {
22
23
// class UserSettings
24
25
UserSettings::UserSettings(CommandController& commandController_)
26
	: userSettingCommand(commandController_)
27
{
28
}
29
30
void UserSettings::addSetting(Info&& info)
31
{
32
	assert(!findSetting(info.setting->getFullName()));
33
	settings.push_back(std::move(info));
34
}
35
36
void UserSettings::deleteSetting(Setting& setting)
37
{
38
	move_pop_back(settings, rfind_unguarded(settings, &setting,
39
		[](auto& info) { return info.setting.get(); }));
40
}
41
42
Setting* UserSettings::findSetting(std::string_view name) const
43
{
44
	auto it = std::ranges::find(settings, name, [](auto& info) {
45
		return info.setting->getFullName(); });
46
	return (it != end(settings)) ? it->setting.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(std::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
66
void UserSettings::Cmd::create(std::span<const TclObject> tokens, TclObject& result)
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
	auto getInfo = [&] {
79
		if (type == "string") {
80
			return createString(tokens);
81
		} else if (type == "boolean") {
82
			return createBoolean(tokens);
83
		} else if (type == "integer") {
84
			return createInteger(tokens);
85
		} else if (type == "float") {
86
			return createFloat(tokens);
87
		} else if (type == "enum") {
88
			return createEnum(tokens);
89
		} else {
90
			throw CommandException(
91
				"Invalid setting type '", type, "', expected "
92
				"'string', 'boolean', 'integer', 'float' or 'enum'.");
93
		}
94
	};
95
	auto& userSettings = OUTER(UserSettings, userSettingCommand);
96
	userSettings.addSetting(getInfo());
97
98
	result = tokens[3]; // name
99
}
100
101
UserSettings::Info UserSettings::Cmd::createString(std::span<const TclObject> tokens) const
102
{
103
	checkNumArgs(tokens, 6, Prefix{3}, "name description initial-value");
104
	const auto& sName   = tokens[3].getString();
105
	const auto& desc    = tokens[4].getString();
106
	const auto& initVal = tokens[5].getString();
107
108
	auto [storage, view] = make_string_storage(desc);
109
	return {.setting = std::make_unique<StringSetting>(getCommandController(), sName, view, initVal),
110
	        .description = std::move(storage)};
111
}
112
113
UserSettings::Info UserSettings::Cmd::createBoolean(std::span<const TclObject> tokens) const
114
{
115
	checkNumArgs(tokens, 6, Prefix{3}, "name description initial-value");
116
	const auto& sName   = tokens[3].getString();
117
	const auto& desc    = tokens[4].getString();
118
	const auto& initVal = tokens[5].getBoolean(getInterpreter());
119
120
	auto [storage, view] = make_string_storage(desc);
121
	return {.setting = std::make_unique<BooleanSetting>(getCommandController(), sName, view, initVal),
122
	        .description = std::move(storage)};
123
}
124
125
UserSettings::Info UserSettings::Cmd::createInteger(std::span<const TclObject> tokens) const
126
{
127
	checkNumArgs(tokens, 8, Prefix{3}, "name description initial-value min-value max-value");
128
	auto& interp = getInterpreter();
129
	const auto& sName   = tokens[3].getString();
130
	const auto& desc    = tokens[4].getString();
131
	const auto& initVal = tokens[5].getInt(interp);
132
	const auto& minVal  = tokens[6].getInt(interp);
133
	const auto& maxVal  = tokens[7].getInt(interp);
134
135
	auto [storage, view] = make_string_storage(desc);
136
	return {.setting = std::make_unique<IntegerSetting>(getCommandController(), sName, view, initVal, minVal, maxVal),
137
	        .description = std::move(storage)};
138
}
139
140
UserSettings::Info UserSettings::Cmd::createFloat(std::span<const TclObject> tokens) const
141
{
142
	checkNumArgs(tokens, 8, Prefix{3}, "name description initial-value min-value max-value");
143
	auto& interp = getInterpreter();
144
	const auto& sName   = tokens[3].getString();
145
	const auto& desc    = tokens[4].getString();
146
	const auto& initVal = tokens[5].getDouble(interp);
147
	const auto& minVal  = tokens[6].getDouble(interp);
148
	const auto& maxVal  = tokens[7].getDouble(interp);
149
150
	auto [storage, view] = make_string_storage(desc);
151
	return {.setting = std::make_unique<FloatSetting>(getCommandController(), sName, view, initVal, minVal, maxVal),
152
	        .description = std::move(storage)};
153
}
154
155
UserSettings::Info UserSettings::Cmd::createEnum(std::span<const TclObject> tokens) const
156
{
157
	checkNumArgs(tokens, 7, Prefix{3}, "name description initial-value allowed-values-list");
158
	const auto& sName   = tokens[3].getString();
159
	const auto& desc    = tokens[4].getString();
160
	const auto& initStr = tokens[5].getString();
161
	const auto& list    = tokens[6];
162
163
	int initVal = -1;
164
	int i = 0;
165
	auto map = to_vector(std::views::transform(list, [&](const auto& s) {
166
		if (s == initStr) initVal = i;
167
		return EnumSettingBase::MapEntry{std::string(s), i++};
168
	}));
169
	if (initVal == -1) {
170
		throw CommandException(
171
			"Initial value '", initStr, "' "
172
			"must be one of the allowed values '",
173
			list.getString(), '\'');
174
	}
175
176
	auto [storage, view] = make_string_storage(desc);
177
	return {.setting = std::make_unique<EnumSetting<int>>( getCommandController(), sName, view, initVal, std::move(map)),
178
	        .description = std::move(storage)};
179
}
180
181
void 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
195
void UserSettings::Cmd::info(std::span<const TclObject> /*tokens*/, TclObject& result) const
196
{
197
	result.addListElements(getSettingNames());
198
}
199
200
std::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
255
void 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