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