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