openMSX
OSDGUI.cc
Go to the documentation of this file.
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
18namespace openmsx {
19
20// class OSDGUI
21
22OSDGUI::OSDGUI(CommandController& commandController, Display& display_)
23 : display(display_)
24 , osdCommand(commandController)
25 , topWidget(display_)
26{
27}
28
29void OSDGUI::refresh() const
30{
31 getDisplay().repaintDelayed(40000); // 25 fps
32}
33
34
35// class OSDCommand
36
37OSDGUI::OSDCommand::OSDCommand(CommandController& commandController_)
38 : Command(commandController_, "osd")
39{
40}
41
42void 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
53void 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
87std::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
102void 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
129void 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
154void 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
162void 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
173void 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
188std::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
251void 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
286OSDWidget& 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
Represents the output window/screen of openMSX.
Definition Display.hh:31
void repaintDelayed(uint64_t delta)
Definition Display.cc:380
OSDGUI(CommandController &commandController, Display &display)
Definition OSDGUI.cc:22
Display & getDisplay() const
Definition OSDGUI.hh:19
void refresh() const
Definition OSDGUI.cc:29
std::pair< string_view, string_view > splitOnLast(string_view str, string_view chars)
Definition StringOp.cc:112
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:11
#define OUTER(type, member)
Definition outer.hh:42