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