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