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