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