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::string_view;
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(display_)
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(span<const TclObject> tokens, TclObject& result)
43 {
44  checkNumArgs(tokens, AtLeast{2}, "subcommand ?arg ...?");
45  executeSubCommand(tokens[1].getString(),
46  "create", [&]{ create(tokens, result); },
47  "destroy", [&]{ destroy(tokens, result); },
48  "info", [&]{ info(tokens, result); },
49  "exists", [&]{ exists(tokens, result); },
50  "configure", [&]{ configure(tokens, result); });
51 }
52 
53 void OSDGUI::OSDCommand::create(span<const TclObject> tokens, TclObject& result)
54 {
55  checkNumArgs(tokens, AtLeast{4}, Prefix{2}, "type name ?property value ...?");
56  string_view type = tokens[2].getString();
57  auto& fullname = tokens[3];
58  auto fullnameStr = fullname.getString();
59 
60  auto& gui = OUTER(OSDGUI, osdCommand);
61  auto& top = gui.getTopWidget();
62  if (top.findByName(fullnameStr)) {
63  throw CommandException(
64  "There already exists a widget with this name: ",
65  fullnameStr);
66  }
67 
68  auto [parentname, childName] = StringOp::splitOnLast(fullnameStr, '.');
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& name) const
86 {
87  auto& gui = OUTER(OSDGUI, osdCommand);
88  if (type == "rectangle") {
89  return std::make_unique<OSDRectangle>(gui.display, name);
90  } else if (type == "text") {
91  return std::make_unique<OSDText>(gui.display, name);
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 
149 void OSDGUI::OSDCommand::exists(span<const TclObject> tokens, TclObject& result)
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 constexpr 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 constexpr 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 name) const
276 {
277  auto& gui = OUTER(OSDGUI, osdCommand);
278  auto* widget = gui.getTopWidget().findByName(name);
279  if (!widget) {
280  throw CommandException("No widget with name ", name);
281  }
282  return *widget;
283 }
284 
285 } // namespace openmsx
StringOp::splitOnLast
std::pair< string_view, string_view > splitOnLast(string_view str, string_view chars)
Definition: StringOp.cc:174
OSDRectangle.hh
openmsx::CommandController
Definition: CommandController.hh:17
Display.hh
TclObject.hh
OSDGUI.hh
openmsx::Display::repaintDelayed
void repaintDelayed(uint64_t delta)
Definition: Display.cc:367
span
Definition: span.hh:34
openmsx::IDEDeviceFactory::create
std::unique_ptr< IDEDevice > create(const DeviceConfig &config)
Definition: IDEDeviceFactory.cc:11
OUTER
#define OUTER(type, member)
Definition: outer.hh:38
openmsx::FileOperations::exists
bool exists(string_view filename)
Definition: FileOperations.cc:675
OSDWidget.hh
outer.hh
StringOp.hh
openmsx::Display
Represents the output window/screen of openMSX.
Definition: Display.hh:31
openmsx::Command
Definition: Command.hh:40
openmsx::OSDGUI::OSDGUI
OSDGUI(CommandController &commandController, Display &display)
Definition: OSDGUI.cc:22
openmsx::OSDGUI::refresh
void refresh() const
Definition: OSDGUI.cc:29
OSDText.hh
CommandException.hh
openmsx::OSDGUI::getDisplay
Display & getDisplay() const
Definition: OSDGUI.hh:18
openmsx
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5