openMSX
OSDWidget.cc
Go to the documentation of this file.
1 #include "OSDWidget.hh"
2 #include "OutputSurface.hh"
3 #include "CommandException.hh"
4 #include "TclObject.hh"
5 #include "StringOp.hh"
6 #include "GLUtil.hh"
7 #include "openmsx.hh"
8 #include "unreachable.hh"
9 #include "memory.hh"
10 #include <SDL.h>
11 #include <algorithm>
12 #include <limits>
13 
14 using std::string;
15 using std::vector;
16 using std::shared_ptr;
17 
18 namespace openmsx {
19 
20 // intersect two rectangles
21 static void intersect(int xa, int ya, int wa, int ha,
22  int xb, int yb, int wb, int hb,
23  int& x, int& y, int& w, int& h)
24 {
25  int x1 = std::max<int>(xa, xb);
26  int y1 = std::max<int>(ya, yb);
27  int x2 = std::min<int>(xa + wa, xb + wb);
28  int y2 = std::min<int>(ya + ha, yb + hb);
29  x = x1;
30  y = y1;
31  w = std::max(0, x2 - x1);
32  h = std::max(0, y2 - y1);
33 }
34 
36 
37 static void normalize(int& x, int& w)
38 {
39  if (w < 0) {
40  w = -w;
41  x -= w;
42  }
43 }
44 
46 {
47 public:
48  SDLScopedClip(OutputSurface& output, int x, int y, int w, int h);
50 private:
51  SDL_Surface* surface;
52  SDL_Rect origClip;
53 };
54 
55 
56 SDLScopedClip::SDLScopedClip(OutputSurface& output, int x, int y, int w, int h)
57  : surface(output.getSDLWorkSurface())
58 {
59  normalize(x, w); normalize(y, h);
60  SDL_GetClipRect(surface, &origClip);
61 
62  int xn, yn, wn, hn;
63  intersect(origClip.x, origClip.y, origClip.w, origClip.h,
64  x, y, w, h,
65  xn, yn, wn, hn);
66  SDL_Rect newClip = { Sint16(xn), Sint16(yn), Uint16(wn), Uint16(hn) };
67  SDL_SetClipRect(surface, &newClip);
68 }
69 
71 {
72  SDL_SetClipRect(surface, &origClip);
73 }
74 
76 
77 #if COMPONENT_GL
78 
80 {
81 public:
82  GLScopedClip(OutputSurface& output, int x, int y, int w, int h);
83  ~GLScopedClip();
84 private:
85  GLint xo, yo, wo, ho; // order is important
86  GLboolean wasEnabled;
87 };
88 
89 
90 GLScopedClip::GLScopedClip(OutputSurface& output, int x, int y, int w, int h)
91 {
92  normalize(x, w); normalize(y, h);
93  y = output.getHeight() - y - h; // openGL sets (0,0) in LOWER-left corner
94 
95  wasEnabled = glIsEnabled(GL_SCISSOR_TEST);
96  if (wasEnabled == GL_TRUE) {
97  glGetIntegerv(GL_SCISSOR_BOX, &xo);
98  int xn, yn, wn, hn;
99  intersect(xo, yo, wo, ho,
100  x, y, w, h,
101  xn, yn, wn, hn);
102  glScissor(xn, yn, wn, hn);
103  } else {
104  glScissor(x, y, w, h);
105  glEnable(GL_SCISSOR_TEST);
106  }
107 }
108 
110 {
111  if (wasEnabled == GL_TRUE) {
112  glScissor(xo, yo, wo, ho);
113  } else {
114  glDisable(GL_SCISSOR_TEST);
115  }
116 }
117 
118 #endif
119 
121 
122 OSDWidget::OSDWidget(const string& name_)
123  : parent(nullptr)
124  , name(name_)
125  , x(0.0), y(0.0), z(0.0)
126  , relx(0.0), rely(0.0)
127  , scaled(false)
128  , clip(false)
129  , suppressErrors(false)
130 {
131 }
132 
134 {
135 }
136 
137 const string& OSDWidget::getName() const
138 {
139  return name;
140 }
141 
143 {
144  return parent;
145 }
146 
148 {
149  return parent;
150 }
151 
152 void OSDWidget::setParent(OSDWidget* parent_)
153 {
154  parent = parent_;
155 }
156 
158 {
159  if (name.empty()) {
160  return this;
161  }
162  string_ref first, last;
163  StringOp::splitOnFirst(name, '.', first, last);
164  auto it = subWidgetsMap.find(first);
165  return it == subWidgetsMap.end() ? nullptr : it->second->findSubWidget(last);
166 }
167 
169 {
170  return const_cast<OSDWidget*>(this)->findSubWidget(name);
171 }
172 
173 void OSDWidget::addWidget(const shared_ptr<OSDWidget>& widget)
174 {
175  widget->setParent(this);
176  subWidgetsMap[widget->getName()] = widget.get();
177 
178  // Insert the new widget in the correct place (sorted on ascending Z)
179  // heuristic: often we have either
180  // - many widgets with all the same Z
181  // - only a few total number of subwidgets (possibly with different Z)
182  // In the former case we can simply append at the end. In the latter
183  // case a linear search is probably faster than a binary search. Only
184  // when there are many sub-widgets with not all the same Z (and not
185  // created in sorted Z-order) a binary search would be faster.
186  double z = widget->getZ();
187  if (subWidgets.empty() || (subWidgets.back()->getZ() <= z)) {
188  subWidgets.push_back(widget);
189  } else {
190  auto it = subWidgets.begin();
191  while ((*it)->getZ() <= z) ++it;
192  subWidgets.insert(it, widget);
193 
194  }
195 }
196 
198 {
199  string widgetName = widget.getName();
200  for (auto it = subWidgets.begin(); it != subWidgets.end(); ++it) {
201  if (it->get() == &widget) {
202  subWidgets.erase(it);
203  auto existed = subWidgetsMap.erase(widgetName);
204  assert(existed); (void)existed;
205  return;
206  }
207  }
208  UNREACHABLE;
209 }
210 
211 #ifdef DEBUG
212 struct AscendingZ {
213  bool operator()(const shared_ptr<OSDWidget>& lhs,
214  const shared_ptr<OSDWidget>& rhs) const {
215  return lhs->getZ() < rhs->getZ();
216  }
217 };
218 #endif
219 void OSDWidget::resortUp(OSDWidget* elem)
220 {
221  // z-coordinate was increased, first search for elements current position
222  auto it1 = subWidgets.begin();
223  while (it1->get() != elem) ++it1;
224  // next search for the position were it belongs
225  double z = elem->getZ();
226  auto it2 = it1;
227  ++it2;
228  while ((it2 != subWidgets.end()) && ((*it2)->getZ() < z)) ++it2;
229  // now move elements to correct position
230  rotate(it1, it1 + 1, it2);
231 #ifdef DEBUG
232  assert(std::is_sorted(subWidgets.begin(), subWidgets.end(), AscendingZ()));
233 #endif
234 }
235 void OSDWidget::resortDown(OSDWidget* elem)
236 {
237  // z-coordinate was decreased, first search for new position
238  auto it1 = subWidgets.begin();
239  double z = elem->getZ();
240  while ((*it1)->getZ() <= z) {
241  ++it1;
242  if (it1 == subWidgets.end()) return;
243  }
244  // next search for the elements current position
245  auto it2 = it1;
246  if ((it2 != subWidgets.begin()) && ((it2 - 1)->get() == elem)) return;
247  while (it2->get() != elem) ++it2;
248  // now move elements to correct position
249  rotate(it1, it2, it2 + 1);
250 #ifdef DEBUG
251  assert(std::is_sorted(subWidgets.begin(), subWidgets.end(), AscendingZ()));
252 #endif
253 }
254 
255 vector<string_ref> OSDWidget::getProperties() const
256 {
257  static const char* const vals[] = {
258  "-type", "-x", "-y", "-z", "-relx", "-rely", "-scaled",
259  "-clip", "-mousecoord", "-suppressErrors",
260  };
261  return vector<string_ref>(std::begin(vals), std::end(vals));
262 }
263 
265 {
266  if (name == "-type") {
267  throw CommandException("-type property is readonly");
268  } else if (name == "-mousecoord") {
269  throw CommandException("-mousecoord property is readonly");
270  } else if (name == "-x") {
271  x = value.getDouble();
272  } else if (name == "-y") {
273  y = value.getDouble();
274  } else if (name == "-z") {
275  double z2 = value.getDouble();
276  if (z != z2) {
277  bool up = z2 > z; // was z increased?
278  z = z2;
279  if (OSDWidget* parent = getParent()) {
280  // TODO no need for a full sort: instead remove and re-insert in the correct place
281  if (up) {
282  parent->resortUp(this);
283  } else {
284  parent->resortDown(this);
285  }
286  }
287  }
288  } else if (name == "-relx") {
289  relx = value.getDouble();
290  } else if (name == "-rely") {
291  rely = value.getDouble();
292  } else if (name == "-scaled") {
293  bool scaled2 = value.getBoolean();
294  if (scaled != scaled2) {
295  scaled = scaled2;
297  }
298  } else if (name == "-clip") {
299  clip = value.getBoolean();
300  } else if (name == "-suppressErrors") {
301  suppressErrors = value.getBoolean();
302  } else {
303  throw CommandException("No such property: " + name);
304  }
305 }
306 
307 void OSDWidget::getProperty(string_ref name, TclObject& result) const
308 {
309  if (name == "-type") {
310  result.setString(getType());
311  } else if (name == "-x") {
312  result.setDouble(x);
313  } else if (name == "-y") {
314  result.setDouble(y);
315  } else if (name == "-z") {
316  result.setDouble(z);
317  } else if (name == "-relx") {
318  result.setDouble(relx);
319  } else if (name == "-rely") {
320  result.setDouble(rely);
321  } else if (name == "-scaled") {
322  result.setBoolean(scaled);
323  } else if (name == "-clip") {
324  result.setBoolean(clip);
325  } else if (name == "-mousecoord") {
326  double x, y;
327  getMouseCoord(x, y);
328  result.addListElement(x);
329  result.addListElement(y);
330  } else if (name == "-suppressErrors") {
331  result.setBoolean(suppressErrors);
332  } else {
333  throw CommandException("No such property: " + name);
334  }
335 }
336 
338 {
339  return 1.0; // fully opaque
340 }
341 
343 {
344  invalidateLocal();
346 }
347 
349 {
350  for (auto& s : subWidgets) {
351  s->invalidateRecursive();
352  }
353 }
354 
356 {
357  if (suppressErrors) return true;
358  if (const OSDWidget* parent = getParent()) {
359  return parent->needSuppressErrors();
360  }
361  return false;
362 }
363 
365 {
366  paintSDL(output);
367 
368  std::unique_ptr<SDLScopedClip> scopedClip;
369  if (clip) {
370  int x, y, w, h;
371  getBoundingBox(output, x, y, w, h);
372  scopedClip = make_unique<SDLScopedClip>(output, x, y, w, h);
373  }
374 
375  // Iterate over a copy because drawing could cause errors (e.g. can't
376  // load font or image), those error are (indirectly via CliComm) passed
377  // to a Tcl callback and that callback can destroy or create extra
378  // widgets.
379  auto copy = subWidgets;
380  for (auto& s : copy) {
381  s->paintSDLRecursive(output);
382  }
383 }
384 
386 {
387  (void)output;
388 #if COMPONENT_GL
389  paintGL(output);
390 
391  std::unique_ptr<GLScopedClip> scopedClip;
392  if (clip) {
393  int x, y, w, h;
394  getBoundingBox(output, x, y, w, h);
395  scopedClip = make_unique<GLScopedClip>(output, x, y, w, h);
396  }
397 
398  auto copy = subWidgets;
399  for (auto& s : copy) {
400  s->paintGLRecursive(output);
401  }
402 #endif
403 }
404 
406 {
407  if (scaled) {
408  return output.getOutputWidth() / 320;;
409  } else if (getParent()) {
410  return getParent()->getScaleFactor(output);
411  } else {
412  return 1;
413  }
414 }
415 
417  double x, double y, double relx, double rely,
418  double& outx, double& outy) const
419 {
420  double width, height;
421  getWidthHeight(output, width, height);
422  int factor = getScaleFactor(output);
423  outx = x + factor * getX() + relx * width;
424  outy = y + factor * getY() + rely * height;
425  if (const OSDWidget* parent = getParent()) {
426  parent->transformXY(output, outx, outy, getRelX(), getRelY(),
427  outx, outy);
428  }
429 }
430 
431 void OSDWidget::transformReverse(
432  const OutputRectangle& output, double x, double y,
433  double& outx, double& outy) const
434 {
435  if (const OSDWidget* parent = getParent()) {
436  parent->transformReverse(output, x, y, x, y);
437  double width, height;
438  parent->getWidthHeight(output, width, height);
439  int factor = getScaleFactor(output);
440  outx = x - (getRelX() * width ) - (getX() * factor);
441  outy = y - (getRelY() * height) - (getY() * factor);
442  } else {
443  outx = x;
444  outy = y;
445  }
446 }
447 
448 void OSDWidget::getMouseCoord(double& outx, double& outy) const
449 {
450  if (SDL_ShowCursor(SDL_QUERY) == SDL_DISABLE) {
451  // Host cursor is not visible. Return dummy mouse coords for
452  // the OSD cursor position.
453  // The reason for doing this is that otherwise (e.g. when using
454  // the mouse in an MSX program) it's possible to accidentally
455  // click on the reversebar. This will also block the OSD mouse
456  // in other Tcl scripts (e.g. vampier's nemesis script), but
457  // almost always those scripts will also not be useful when the
458  // host mouse cursor is not visible.
459  //
460  // We need to return coordinates that lay outside any
461  // reasonable range. Initially we returned (NaN, NaN). But for
462  // some reason that didn't work on dingoo: Dingoo uses
463  // softfloat, in c++ NaN seems to behave as expected, but maybe
464  // there's a problem on the tcl side? Anyway, when we return
465  // +inf instead of NaN it does work.
466  outx = std::numeric_limits<double>::infinity();
467  outy = std::numeric_limits<double>::infinity();
468  return;
469  }
470 
471  SDL_Surface* surface = SDL_GetVideoSurface();
472  if (!surface) {
473  throw CommandException(
474  "Can't get mouse coordinates: no window visible");
475  }
476  DummyOutputRectangle output(surface->w, surface->h);
477 
478  int mouseX, mouseY;
479  SDL_GetMouseState(&mouseX, &mouseY);
480 
481  transformReverse(output, mouseX, mouseY, outx, outy);
482 
483  double width, height;
484  getWidthHeight(output, width, height);
485  if ((width == 0) || (height == 0)) {
486  throw CommandException(
487  "-can't get mouse coordinates: "
488  "widget has zero width or height");
489  }
490  outx /= width;
491  outy /= height;
492 }
493 
495  int& x, int& y, int& w, int& h)
496 {
497  double x1, y1, x2, y2;
498  transformXY(output, 0.0, 0.0, 0.0, 0.0, x1, y1);
499  transformXY(output, 0.0, 0.0, 1.0, 1.0, x2, y2);
500  x = int(x1 + 0.5);
501  y = int(y1 + 0.5);
502  w = int(x2 - x1 + 0.5);
503  h = int(y2 - y1 + 0.5);
504 }
505 
506 void OSDWidget::listWidgetNames(const string& parentName, vector<string>& result) const
507 {
508  string pname = parentName;
509  if (!pname.empty()) pname += '.';
510  for (auto& s : subWidgets) {
511  string name = pname + s->getName();
512  result.push_back(name);
513  s->listWidgetNames(name, result);
514  }
515 }
516 
517 } // namespace openmsx