OXYGENHUB
◆ main openmsx / src / console / OSDGUI.cc
8.6 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "OSDGUI.hh"
2
3
#include "CommandException.hh"
4
#include "Display.hh"
5
#include "OSDRectangle.hh"
6
#include "OSDText.hh"
7
#include "OSDWidget.hh"
8
#include "TclObject.hh"
9
10
#include "StringOp.hh"
11
#include "one_of.hh"
12
#include "outer.hh"
13
14
#include <array>
15
#include <memory>
16
#include <utility>
17
18
namespace openmsx {
19
20
// class OSDGUI
21
22
OSDGUI::OSDGUI(CommandController& commandController, Display& display_)
23
	: display(display_)
24
	, osdCommand(commandController)
25
	, topWidget(display_)
26
{
27
}
28
29
void OSDGUI::refresh() const
30
{
31
	getDisplay().repaintDelayed(40000); // 25 fps
32
}
33
34
35
// class OSDCommand
36
37
OSDGUI::OSDCommand::OSDCommand(CommandController& commandController_)
38
	: Command(commandController_, "osd")
39
{
40
}
41
42
void OSDGUI::OSDCommand::execute(std::span<const TclObject> tokens, TclObject& result)
43
{
44
	checkNumArgs(tokens, AtLeast{2}, "subcommand ?arg ...?");
45
	executeSubCommand(tokens[1].getString(),
46
		"create",    [&]{ create(tokens, result); },
47
		"destroy",   [&]{ destroy(tokens, result); },
48
		"info",      [&]{ info(tokens, result); },
49
		"exists",    [&]{ exists(tokens, result); },
50
		"configure", [&]{ configure(tokens, result); });
51
}
52
53
void OSDGUI::OSDCommand::create(std::span<const TclObject> tokens, TclObject& result)
54
{
55
	checkNumArgs(tokens, AtLeast{4}, Prefix{2}, "type name ?property value ...?");
56
	std::string_view type = tokens[2].getString();
57
	const auto& fullname = tokens[3];
58
	auto fullnameStr = fullname.getString();
59
60
	auto& gui = OUTER(OSDGUI, osdCommand);
61
	auto& top = gui.getTopWidget();
62
	if (top.findByName(fullnameStr)) {
63
		throw CommandException(
64
			"There already exists a widget with this name: ",
65
			fullnameStr);
66
	}
67
68
	auto [parentName, childName] = StringOp::splitOnLast(fullnameStr, '.');
69
	auto* parent = childName.empty() ? &top : top.findByName(parentName);
70
	if (!parent) {
71
		throw CommandException(
72
			"Parent widget doesn't exist yet:", parentName);
73
	}
74
75
	auto widget = create(type, fullname);
76
	const auto* widget2 = widget.get();
77
	configure(*widget, tokens.subspan(4));
78
	top.addName(*widget);
79
	parent->addWidget(std::move(widget));
80
81
	result = fullname;
82
	if (widget2->isVisible()) {
83
		gui.refresh();
84
	}
85
}
86
87
std::unique_ptr<OSDWidget> OSDGUI::OSDCommand::create(
88
	std::string_view type, const TclObject& name) const
89
{
90
	auto& gui = OUTER(OSDGUI, osdCommand);
91
	if (type == "rectangle") {
92
		return std::make_unique<OSDRectangle>(gui.display, name);
93
	} else if (type == "text") {
94
		return std::make_unique<OSDText>(gui.display, name);
95
	} else {
96
		throw CommandException(
97
			"Invalid widget type '", type, "', expected "
98
			"'rectangle' or 'text'.");
99
	}
100
}
101
102
void OSDGUI::OSDCommand::destroy(std::span<const TclObject> tokens, TclObject& result)
103
{
104
	checkNumArgs(tokens, 3, "name");
105
	auto fullname = tokens[2].getString();
106
107
	auto& gui = OUTER(OSDGUI, osdCommand);
108
	auto& top = gui.getTopWidget();
109
	auto* widget = top.findByName(fullname);
110
	if (!widget) {
111
		// widget not found, not an error
112
		result = false;
113
		return;
114
	}
115
116
	auto* parent = widget->getParent();
117
	if (!parent) {
118
		throw CommandException("Can't destroy the top widget.");
119
	}
120
121
	if (widget->isVisible()) {
122
		gui.refresh();
123
	}
124
	top.removeName(*widget);
125
	parent->deleteWidget(*widget);
126
	result = true;
127
}
128
129
void OSDGUI::OSDCommand::info(std::span<const TclObject> tokens, TclObject& result)
130
{
131
	checkNumArgs(tokens, Between{2, 4}, Prefix{2}, "?name? ?property?");
132
	auto& gui = OUTER(OSDGUI, osdCommand);
133
	switch (tokens.size()) {
134
	case 2: {
135
		// list widget names
136
		result.addListElements(gui.getTopWidget().getAllWidgetNames());
137
		break;
138
	}
139
	case 3: {
140
		// list properties for given widget
141
		const auto& widget = getWidget(tokens[2].getString());
142
		result.addListElements(widget.getProperties());
143
		break;
144
	}
145
	case 4: {
146
		// get current value for given widget/property
147
		const auto& widget = getWidget(tokens[2].getString());
148
		widget.getProperty(tokens[3].getString(), result);
149
		break;
150
	}
151
	}
152
}
153
154
void OSDGUI::OSDCommand::exists(std::span<const TclObject> tokens, TclObject& result)
155
{
156
	checkNumArgs(tokens, 3, "name");
157
	auto& gui = OUTER(OSDGUI, osdCommand);
158
	const auto* widget = gui.getTopWidget().findByName(tokens[2].getString());
159
	result = widget != nullptr;
160
}
161
162
void OSDGUI::OSDCommand::configure(std::span<const TclObject> tokens, TclObject& /*result*/)
163
{
164
	checkNumArgs(tokens, AtLeast{3}, "name ?property value ...?");
165
	auto& widget = getWidget(tokens[2].getString());
166
	configure(widget, tokens.subspan(3));
167
	if (widget.isVisible()) {
168
		const auto& gui = OUTER(OSDGUI, osdCommand);
169
		gui.refresh();
170
	}
171
}
172
173
void OSDGUI::OSDCommand::configure(OSDWidget& widget, std::span<const TclObject> tokens) const
174
{
175
	if (tokens.size() & 1) {
176
		// odd number of extra arguments
177
		throw CommandException(
178
			"Missing value for '", tokens.back().getString(), "'.");
179
	}
180
181
	auto& interp = getInterpreter();
182
	for (size_t i = 0; i < tokens.size(); i += 2) {
183
		const auto& propName = tokens[i + 0].getString();
184
		widget.setProperty(interp, propName, tokens[i + 1]);
185
	}
186
}
187
188
std::string OSDGUI::OSDCommand::help(std::span<const TclObject> tokens) const
189
{
190
	if (tokens.size() >= 2) {
191
		if (tokens[1] == "create") {
192
			return
193
			  "osd create <type> <widget-path> [<property-name> <property-value>]...\n"
194
			  "\n"
195
			  "Creates a new OSD widget of given type. Path is a "
196
			  "hierarchical name for the widget (separated by '.'). "
197
			  "The parent widget for this new widget must already "
198
			  "exist.\n"
199
			  "Optionally you can set initial values for one or "
200
			  "more properties.\n"
201
			  "This command returns the path of the newly created "
202
			  "widget. This is path is again needed to configure "
203
			  "or to remove the widget. It may be useful to assign "
204
			  "this path to a variable.";
205
		} else if (tokens[1] == "destroy") {
206
			return
207
			  "osd destroy <widget-path>\n"
208
			  "\n"
209
			  "Remove the specified OSD widget. Returns '1' on "
210
			  "success and '0' when widget couldn't be destroyed "
211
			  "because there was no widget with that name";
212
		} else if (tokens[1] == "info") {
213
			return
214
			  "osd info [<widget-path> [<property-name>]]\n"
215
			  "\n"
216
			  "Query various information about the OSD status. "
217
			  "You can call this command with 0, 1 or 2 arguments.\n"
218
			  "Without any arguments, this command returns a list "
219
			  "of all existing widget IDs.\n"
220
			  "When a path is given as argument, this command "
221
			  "returns a list of available properties for that widget.\n"
222
			  "When both path and property name arguments are "
223
			  "given, this command returns the current value of "
224
			  "that property.";
225
		} else if (tokens[1] == "exists") {
226
			return
227
			  "osd exists <widget-path>\n"
228
			  "\n"
229
			  "Test whether there exists a widget with given name. "
230
			  "This subcommand is meant to be used in scripts.";
231
		} else if (tokens[1] == "configure") {
232
			return
233
			  "osd configure <widget-path> [<property-name> <property-value>]...\n"
234
			  "\n"
235
			  "Modify one or more properties on the given widget.";
236
		} else {
237
			return "No such subcommand, see 'help osd'.";
238
		}
239
	} else {
240
		return
241
		  "Low level OSD GUI commands\n"
242
		  "  osd create <type> <widget-path> [<property-name> <property-value>]...\n"
243
		  "  osd destroy <widget-path>\n"
244
		  "  osd info [<widget-path> [<property-name>]]\n"
245
		  "  osd exists <widget-path>\n"
246
		  "  osd configure <widget-path> [<property-name> <property-value>]...\n"
247
		  "Use 'help osd <subcommand>' to see more info on a specific subcommand";
248
	}
249
}
250
251
void OSDGUI::OSDCommand::tabCompletion(std::vector<std::string>& tokens) const
252
{
253
	using namespace std::literals;
254
	auto& gui = OUTER(OSDGUI, osdCommand);
255
	if (tokens.size() == 2) {
256
		static constexpr std::array cmds = {
257
			"create"sv, "destroy"sv, "info"sv, "exists"sv, "configure"sv
258
		};
259
		completeString(tokens, cmds);
260
	} else if ((tokens.size() == 3) && (tokens[1] == "create")) {
261
		static constexpr std::array types = {"rectangle"sv, "text"sv};
262
		completeString(tokens, types);
263
	} else if ((tokens.size() == 3) ||
264
	           ((tokens.size() == 4) && (tokens[1] == "create"))) {
265
		completeString(tokens, gui.getTopWidget().getAllWidgetNames());
266
	} else {
267
		try {
268
			auto properties = [&] {
269
				if (tokens[1] == "create") {
270
					auto widget = create(tokens[2], TclObject());
271
					return widget->getProperties();
272
				} else if (tokens[1] == one_of("configure", "info")) {
273
					const auto& widget = getWidget(tokens[2]);
274
					return widget.getProperties();
275
				} else {
276
					return std::span<const std::string_view>{};
277
				}
278
			}();
279
			completeString(tokens, properties);
280
		} catch (MSXException&) {
281
			// ignore
282
		}
283
	}
284
}
285
286
OSDWidget& OSDGUI::OSDCommand::getWidget(std::string_view name) const
287
{
288
	auto& gui = OUTER(OSDGUI, osdCommand);
289
	auto* widget = gui.getTopWidget().findByName(name);
290
	if (!widget) {
291
		throw CommandException("No widget with name ", name);
292
	}
293
	return *widget;
294
}
295
296
} // namespace openmsx