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