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