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  string_ref fullname = tokens[3].getString();
76  string_ref parentname, name;
77  StringOp::splitOnLast(fullname, '.', parentname, name);
78  if (name.empty()) std::swap(parentname, name);
79 
80  auto& gui = OUTER(OSDGUI, osdCommand);
81  auto* parent = gui.getTopWidget().findSubWidget(parentname);
82  if (!parent) {
83  throw CommandException(
84  "Parent widget doesn't exist yet:" + parentname);
85  }
86  if (parent->findSubWidget(name)) {
87  throw CommandException(
88  "There already exists a widget with this name: " +
89  fullname);
90  }
91 
92  auto widget = create(type, name.str());
93  configure(*widget, tokens, 4);
94  parent->addWidget(std::move(widget));
95 
96  result.setString(fullname);
97 }
98 
99 unique_ptr<OSDWidget> OSDGUI::OSDCommand::create(
100  string_ref type, const string& name) const
101 {
102  auto& gui = OUTER(OSDGUI, osdCommand);
103  if (type == "rectangle") {
104  return make_unique<OSDRectangle>(gui, name);
105  } else if (type == "text") {
106  return make_unique<OSDText>(gui, name);
107  } else {
108  throw CommandException(
109  "Invalid widget type '" + type + "', expected "
110  "'rectangle' or 'text'.");
111  }
112 }
113 
114 void OSDGUI::OSDCommand::destroy(array_ref<TclObject> tokens, TclObject& result)
115 {
116  if (tokens.size() != 3) {
117  throw SyntaxError();
118  }
119  auto& gui = OUTER(OSDGUI, osdCommand);
120  auto* widget = gui.getTopWidget().findSubWidget(tokens[2].getString());
121  if (!widget) {
122  // widget not found, not an error
123  result.setBoolean(false);
124  return;
125  }
126  auto* parent = widget->getParent();
127  if (!parent) {
128  throw CommandException("Can't destroy the top widget.");
129  }
130  parent->deleteWidget(*widget);
131  result.setBoolean(true);
132 }
133 
134 void OSDGUI::OSDCommand::info(array_ref<TclObject> tokens, TclObject& result)
135 {
136  auto& gui = OUTER(OSDGUI, osdCommand);
137  switch (tokens.size()) {
138  case 2: {
139  // list widget names
140  vector<string> names;
141  gui.getTopWidget().listWidgetNames("", names);
142  result.addListElements(names);
143  break;
144  }
145  case 3: {
146  // list properties for given widget
147  const auto& widget = getWidget(tokens[2].getString());
148  result.addListElements(widget.getProperties());
149  break;
150  }
151  case 4: {
152  // get current value for given widget/property
153  const auto& widget = getWidget(tokens[2].getString());
154  widget.getProperty(tokens[3].getString(), result);
155  break;
156  }
157  default:
158  throw SyntaxError();
159  }
160 }
161 
162 void OSDGUI::OSDCommand::exists(array_ref<TclObject> tokens, TclObject& result)
163 {
164  if (tokens.size() != 3) {
165  throw SyntaxError();
166  }
167  auto& gui = OUTER(OSDGUI, osdCommand);
168  auto* widget = gui.getTopWidget().findSubWidget(tokens[2].getString());
169  result.setBoolean(widget != nullptr);
170 }
171 
172 void OSDGUI::OSDCommand::configure(array_ref<TclObject> tokens, TclObject& /*result*/)
173 {
174  if (tokens.size() < 3) {
175  throw SyntaxError();
176  }
177  configure(getWidget(tokens[2].getString()), tokens, 3);
178 }
179 
180 void OSDGUI::OSDCommand::configure(OSDWidget& widget, array_ref<TclObject> tokens,
181  unsigned skip)
182 {
183  assert(tokens.size() >= skip);
184  if ((tokens.size() - skip) & 1) {
185  // odd number of extra arguments
186  throw CommandException(
187  "Missing value for '" + tokens.back().getString() + "'.");
188  }
189 
190  auto& interp = getInterpreter();
191  for (size_t i = skip; i < tokens.size(); i += 2) {
192  const auto& name = tokens[i + 0].getString();
193  widget.setProperty(interp, name, tokens[i + 1]);
194  }
195 }
196 
197 string OSDGUI::OSDCommand::help(const vector<string>& tokens) const
198 {
199  if (tokens.size() >= 2) {
200  if (tokens[1] == "create") {
201  return
202  "osd create <type> <widget-path> [<property-name> <property-value>]...\n"
203  "\n"
204  "Creates a new OSD widget of given type. Path is a "
205  "hierarchical name for the widget (separated by '.'). "
206  "The parent widget for this new widget must already "
207  "exist.\n"
208  "Optionally you can set initial values for one or "
209  "more properties.\n"
210  "This command returns the path of the newly created "
211  "widget. This is path is again needed to configure "
212  "or to remove the widget. It may be useful to assign "
213  "this path to a variable.";
214  } else if (tokens[1] == "destroy") {
215  return
216  "osd destroy <widget-path>\n"
217  "\n"
218  "Remove the specified OSD widget. Returns '1' on "
219  "success and '0' when widget couldn't be destroyed "
220  "because there was no widget with that name";
221  } else if (tokens[1] == "info") {
222  return
223  "osd info [<widget-path> [<property-name>]]\n"
224  "\n"
225  "Query various information about the OSD status. "
226  "You can call this command with 0, 1 or 2 arguments.\n"
227  "Without any arguments, this command returns a list "
228  "of all existing widget IDs.\n"
229  "When a path is given as argument, this command "
230  "returns a list of available properties for that widget.\n"
231  "When both path and property name arguments are "
232  "given, this command returns the current value of "
233  "that property.";
234  } else if (tokens[1] == "exists") {
235  return
236  "osd exists <widget-path>\n"
237  "\n"
238  "Test whether there exists a widget with given name. "
239  "This subcommand is meant to be used in scripts.";
240  } else if (tokens[1] == "configure") {
241  return
242  "osd configure <widget-path> [<property-name> <property-value>]...\n"
243  "\n"
244  "Modify one or more properties on the given widget.";
245  } else {
246  return "No such subcommand, see 'help osd'.";
247  }
248  } else {
249  return
250  "Low level OSD GUI commands\n"
251  " osd create <type> <widget-path> [<property-name> <property-value>]...\n"
252  " osd destroy <widget-path>\n"
253  " osd info [<widget-path> [<property-name>]]\n"
254  " osd exists <widget-path>\n"
255  " osd configure <widget-path> [<property-name> <property-value>]...\n"
256  "Use 'help osd <subcommand>' to see more info on a specific subcommand";
257  }
258 }
259 
260 void OSDGUI::OSDCommand::tabCompletion(vector<string>& tokens) const
261 {
262  auto& gui = OUTER(OSDGUI, osdCommand);
263  if (tokens.size() == 2) {
264  static const char* const cmds[] = {
265  "create", "destroy", "info", "exists", "configure"
266  };
267  completeString(tokens, cmds);
268  } else if ((tokens.size() == 3) && (tokens[1] == "create")) {
269  static const char* const types[] = { "rectangle", "text" };
270  completeString(tokens, types);
271  } else if ((tokens.size() == 3) ||
272  ((tokens.size() == 4) && (tokens[1] == "create"))) {
273  vector<string> names;
274  gui.getTopWidget().listWidgetNames("", names);
275  completeString(tokens, names);
276  } else {
277  try {
278  vector<string_ref> properties;
279  if (tokens[1] == "create") {
280  auto widget = create(tokens[2], "");
281  properties = widget->getProperties();
282  } else if ((tokens[1] == "configure") ||
283  (tokens[1] == "info")) {
284  const auto& widget = getWidget(tokens[2]);
285  properties = widget.getProperties();
286  }
287  completeString(tokens, properties);
288  } catch (MSXException&) {
289  // ignore
290  }
291  }
292 }
293 
294 OSDWidget& OSDGUI::OSDCommand::getWidget(string_ref name) const
295 {
296  auto& gui = OUTER(OSDGUI, osdCommand);
297  auto* widget = gui.getTopWidget().findSubWidget(name);
298  if (!widget) {
299  throw CommandException("No widget with name " + name);
300  }
301  return *widget;
302 }
303 
304 } // 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
std::string str() const
Definition: string_ref.cc:12
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