GNU Octave  8.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
Canvas.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2011-2023 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING. If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 #if defined (HAVE_CONFIG_H)
27 # include "config.h"
28 #endif
29 
30 #include <QApplication>
31 #include <QBitmap>
32 #include <QCursor>
33 #include <QInputDialog>
34 #include <QList>
35 #include <QMouseEvent>
36 #include <QWheelEvent>
37 #include <QRectF>
38 
39 #include "Canvas.h"
40 #include "ContextMenu.h"
41 #include "GLCanvas.h"
42 #include "QtHandlesUtils.h"
43 #include "qt-graphics-toolkit.h"
44 
45 #include "annotation-dialog.h"
46 #include "octave-qobject.h"
47 #include "qt-interpreter-events.h"
48 
49 #include "builtin-defun-decls.h"
50 #include "graphics.h"
51 #include "interpreter.h"
52 #include "oct-opengl.h"
53 
55 
56 void
57 Canvas::redraw (bool sync)
58 {
59  QWidget *w = qWidget ();
60 
61  if (w)
62  {
63  if (sync)
64  w->repaint ();
65  else
66  w->update ();
67  }
68 }
69 
70 void
71 Canvas::blockRedraw (bool block)
72 {
73  m_redrawBlocked = block;
74 }
75 
76 QCursor
77 Canvas::make_cursor (const QString& name, int hot_x, int hot_y)
78 {
79  octave::resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
80 
81  QIcon icon = rmgr.icon (name);
82 
83  return QCursor (icon.pixmap (22, 22), hot_x, hot_y);
84 }
85 
86 void
87 Canvas::setCursor (MouseMode mode, std::string fallback,
88  QImage cdata, Matrix hotspot)
89 {
90  QWidget *w = qWidget ();
91  QCursor cursor = Qt::ArrowCursor;
92  if (w)
93  {
94  switch (mode)
95  {
96  case NoMode:
97  {
98  cursor = Qt::ArrowCursor;
99 
100  if (fallback == "arrow")
101  cursor = Qt::ArrowCursor;
102  else if (fallback == "botl")
103  cursor = make_cursor ("bottom_left_corner", 5, 16);
104  else if (fallback == "botr")
105  cursor = make_cursor ("bottom_right_corner", 16, 16);
106  else if (fallback == "bottom")
107  cursor = make_cursor ("bottom_side", 11, 16);
108  else if (fallback == "circle")
109  cursor = make_cursor ("circle", 10, 10);
110  else if (fallback == "cross" || fallback == "crosshair")
111  cursor = make_cursor ("cross", 10, 10);
112  else if (fallback == "custom")
113  {
114  if (hotspot(0) > cdata.width () || hotspot(0) < 1.0
115  || hotspot(1) > cdata.height () || hotspot(1) < 1.0)
116  hotspot = Matrix (1, 2, 1);
117 
118  cursor = QCursor (QPixmap::fromImage (cdata),
119  static_cast<int> (hotspot(1) - 1),
120  static_cast<int> (hotspot(0) - 1));
121  }
122  else if (fallback == "fleur")
123  cursor = make_cursor ("fleur", 10, 4);
124  else if (fallback == "hand")
125  cursor = make_cursor ("hand2", 7, 3);
126  else if (fallback == "ibeam")
127  cursor = Qt::IBeamCursor;
128  else if (fallback == "left")
129  cursor = make_cursor ("left_side", 4, 10);
130  else if (fallback == "right")
131  cursor = make_cursor ("right_side", 17, 10);
132  else if (fallback == "top")
133  cursor = make_cursor ("top_side", 11, 4);
134  else if (fallback == "topl")
135  cursor = make_cursor ("top_left_corner", 4, 4);
136  else if (fallback == "topr")
137  cursor = make_cursor ("top_right_corner", 16, 4);
138  else if (fallback == "watch")
139  cursor = Qt::BusyCursor;
140  }
141  break;
142  case SelectMode:
143  cursor = Qt::ArrowCursor;
144  break;
145 
146  case PanMode:
147  cursor = make_cursor ("figure-pan");
148  break;
149 
150  case RotateMode:
151  cursor = make_cursor ("figure-rotate");
152  break;
153 
154  case TextMode:
155  cursor = Qt::IBeamCursor;
156  break;
157 
158  case ZoomInMode:
159  cursor = make_cursor ("figure-zoom-in", 9, 9);
160  break;
161 
162  case ZoomOutMode:
163  cursor = make_cursor ("figure-zoom-out", 9, 9);
164  break;
165 
166  default:
167  cursor = Qt::ArrowCursor;
168  break;
169  }
170  w->setCursor (cursor);
171  }
172 }
173 
174 /*
175  Two updateCurrentPoint() routines are required:
176  1) Used for QMouseEvents where cursor position data is in callback from Qt.
177  2) Used for QKeyEvents where cursor position must be determined.
178 */
179 void
180 Canvas::updateCurrentPoint (const graphics_object& fig,
181  const graphics_object& obj, QMouseEvent *event)
182 {
183  gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
184 
185  octave::autolock guard (gh_mgr.graphics_lock ());
186 
187  emit gh_set_event (fig.get_handle (), "currentpoint",
188  Utils::figureCurrentPoint (fig, event), false);
189 
190  Matrix children = obj.get_properties ().get_children ();
191  octave_idx_type num_children = children.numel ();
192 
193  for (int i = 0; i < num_children; i++)
194  {
195  graphics_object childObj (gh_mgr.get_object (children(i)));
196 
197  if (childObj.isa ("axes"))
198  {
199  axes::properties& ap = Utils::properties<axes> (childObj);
200  Matrix x_zlim = ap.get_transform_zlim ();
201  graphics_xform x_form = ap.get_transform ();
202 
203  ColumnVector p1 = x_form.untransform (event->x (), event->y (),
204  x_zlim(0));
205  ColumnVector p2 = x_form.untransform (event->x (), event->y (),
206  x_zlim(1));
207 
208  Matrix cp (2, 3, 0.0);
209 
210  cp(0, 0) = p1(0); cp(0, 1) = p1(1); cp(0, 2) = p1(2);
211  cp(1, 0) = p2(0); cp(1, 1) = p2(1); cp(1, 2) = p2(2);
212 
213  emit gh_set_event (childObj.get_handle (), "currentpoint", cp,
214  false);
215  }
216  }
217 }
218 
219 void
220 Canvas::updateCurrentPoint (const graphics_object& fig,
221  const graphics_object& obj)
222 {
223  gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
224 
225  octave::autolock guard (gh_mgr.graphics_lock ());
226 
227  emit gh_set_event (fig.get_handle (), "currentpoint",
228  Utils::figureCurrentPoint (fig), false);
229 
230  Matrix children = obj.get_properties ().get_children ();
231  octave_idx_type num_children = children.numel ();
232 
233  for (int i = 0; i < num_children; i++)
234  {
235  graphics_object childObj (gh_mgr.get_object (children(i)));
236 
237  if (childObj.isa ("axes"))
238  {
239  // FIXME: QCursor::pos() may give inaccurate results with
240  // asynchronous window systems like X11 over ssh.
241  QWidget *w = qWidget ();
242  QPoint p = w->mapFromGlobal (QCursor::pos ());
243  axes::properties& ap = Utils::properties<axes> (childObj);
244  Matrix x_zlim = ap.get_transform_zlim ();
245  graphics_xform x_form = ap.get_transform ();
246 
247  ColumnVector p1 = x_form.untransform (p.x (), p.y (), x_zlim(0));
248  ColumnVector p2 = x_form.untransform (p.x (), p.y (), x_zlim(1));
249 
250  Matrix cp (2, 3, 0.0);
251 
252  cp(0, 0) = p1(0); cp(0, 1) = p1(1); cp(0, 2) = p1(2);
253  cp(1, 0) = p2(0); cp(1, 1) = p2(1); cp(1, 2) = p2(2);
254 
255  emit gh_set_event (childObj.get_handle (), "currentpoint", cp,
256  false);
257  }
258  }
259 }
260 
261 static void
262 autoscale_axes (gh_manager& gh_mgr, axes::properties& ap)
263 {
264  octave::autolock guard (gh_mgr.graphics_lock ());
265 
266  // Reset zoom stack
267  ap.clear_zoom_stack (false);
268 
269  ap.set_xlimmode ("auto");
270  ap.set_ylimmode ("auto");
271  ap.set_zlimmode ("auto");
272 }
273 
274 void
276 {
277  if (! m_redrawBlocked)
278  {
279  gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
280 
281  octave::autolock guard (gh_mgr.graphics_lock ());
282 
283  draw (m_handle);
284 
285  if ((m_mouseMode == ZoomInMode && m_mouseAxes.ok ()) || m_rectMode)
287  }
288 }
289 
290 static bool
291 pan_enabled (const graphics_object figObj)
292 {
293  // Getting pan mode property:
294  octave_value ov_pm
295  = Utils::properties<figure> (figObj).get___pan_mode__ ();
296 
297  octave_scalar_map pm = ov_pm.scalar_map_value ();
298 
299  return pm.contents ("Enable").string_value () == "on";
300 }
301 
302 static std::string
303 pan_mode (const graphics_object figObj)
304 {
305  // Getting pan mode property:
306  octave_value ov_pm
307  = Utils::properties<figure> (figObj).get___pan_mode__ ();
308 
309  octave_scalar_map pm = ov_pm.scalar_map_value ();
310 
311  return pm.contents ("Motion").string_value ();
312 }
313 
314 static bool
315 zoom_enabled (const graphics_object figObj)
316 {
317  // Getting zoom mode property:
318  octave_value ov_zm
319  = Utils::properties<figure> (figObj).get___zoom_mode__ ();
320 
321  octave_scalar_map zm = ov_zm.scalar_map_value ();
322 
323  return zm.contents ("Enable").string_value () == "on";
324 }
325 
326 static std::string
327 zoom_mode (const graphics_object figObj)
328 {
329  // Getting zoom mode property:
330  octave_value ov_zm
331  = Utils::properties<figure> (figObj).get___zoom_mode__ ();
332 
333  octave_scalar_map zm = ov_zm.scalar_map_value ();
334 
335  return zm.contents ("Motion").string_value ();
336 }
337 
338 void
339 Canvas::select_object (graphics_object obj, QMouseEvent *event,
340  graphics_object& currentObj, graphics_object& axesObj,
341  bool axes_only, std::vector<std::string> omit)
342 {
343  QList<graphics_object> axesList;
344  Matrix children = obj.get_properties ().get_all_children ();
345  octave_idx_type num_children = children.numel ();
346 
347  gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
348 
349  for (int i = 0; i < num_children; i++)
350  {
351  graphics_object childObj (gh_mgr.get_object (children(i)));
352 
353  if (childObj.isa ("axes"))
354  {
355  auto p = omit.begin ();
356  bool omitfound = false;
357  while (p != omit.end () && ! omitfound)
358  {
359  omitfound = (childObj.get ("tag").string_value () == *p);
360  p++;
361  }
362  if (! omitfound)
363  axesList.append (childObj);
364  }
365  else if (childObj.isa ("uicontrol") || childObj.isa ("uipanel")
366  || childObj.isa ("uibuttongroup") || childObj.isa ("uitable"))
367  {
368  Matrix bb = childObj.get_properties ().get_boundingbox (false);
369  QRectF r (bb(0), bb(1), bb(2), bb(3));
370 
371  r.adjust (-5, -5, 5, 5);
372 
373  bool rect_contains_pos = r.contains (event->localPos ());
374  if (rect_contains_pos)
375  {
376  currentObj = childObj;
377  break;
378  }
379  }
380  }
381 
382  if (axes_only)
383  {
384  QPoint pt = event->pos ();
385 
386  for (QList<graphics_object>::ConstIterator it = axesList.begin ();
387  it != axesList.end (); ++it)
388  {
389  const axes::properties& ap =
390  dynamic_cast<const axes::properties&> ((*it).get_properties ());
391 
392  ColumnVector p0 = ap.pixel2coord (pt.x (), pt.y ());
393  Matrix xlim = ap.get_xlim ().matrix_value ();
394  Matrix ylim = ap.get_ylim ().matrix_value ();
395 
396  if (xlim(0) < p0(0) && xlim(1) > p0(0)
397  && ylim(0) < p0(1) && ylim(1) > p0(1))
398  {
399  axesObj = *it;
400  return;
401  }
402  }
403  }
404  else if (! currentObj)
405  {
406  for (QList<graphics_object>::ConstIterator it = axesList.begin ();
407  it != axesList.end (); ++it)
408  {
409  graphics_object go = selectFromAxes (*it, event->pos ());
410 
411  if (go)
412  {
413  currentObj = go;
414  axesObj = *it;
415  }
416  // FIXME: is this really necessary? the axes object should
417  // have been selected through selectFromAxes anyway
418  else if (it->get_properties ().is_hittest ())
419  {
420  Matrix bb = it->get_properties ().get_boundingbox (true);
421  QRectF r (bb(0), bb(1), bb(2), bb(3));
422 
423  // Allow a rectangle (e.g., Zoom box) to be slightly outside
424  // the axes and still select it.
425  r.adjust (-20, -20, 20, 20);
426 
427  bool rect_contains_pos = r.contains (event->localPos ());
428  if (rect_contains_pos)
429  axesObj = *it;
430  }
431 
432  if (axesObj && currentObj)
433  break;
434  }
435  }
436 }
437 
438 void
439 Canvas::canvasMouseMoveEvent (QMouseEvent *event)
440 {
441  gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
442 
443  octave::autolock guard (gh_mgr.graphics_lock ());
444 
445  graphics_object ax = gh_mgr.get_object (m_mouseAxes);
446 
447  if (m_mouseMode != NoMode && (ax.valid_object () || m_mouseMode == TextMode))
448  {
449  switch (m_mouseMode)
450  {
451  case RotateMode:
452  {
453  axes::properties& ap = Utils::properties<axes> (ax);
454 
455  ap.rotate3d (m_mouseCurrent.x (), event->x (),
456  m_mouseCurrent.y (), event->y ());
457 
458  // Update current mouse position
459  m_mouseCurrent = event->pos ();
460 
461  // Force immediate redraw
462  redraw (true);
463  }
464  break;
465  case TextMode:
466  case ZoomInMode:
467  case ZoomOutMode:
468  m_mouseCurrent = event->pos ();
469  redraw (true);
470  break;
471 
472  case PanMode:
473  {
474  axes::properties& ap = Utils::properties<axes> (ax);
475 
476  graphics_object figObj (ax.get_ancestor ("figure"));
477 
478  std::string mode = pan_mode (figObj);
479 
480  ColumnVector p0 = ap.pixel2coord (m_mouseCurrent.x (),
481  m_mouseCurrent.y ());
482  ColumnVector p1 = ap.pixel2coord (event->x (),
483  event->y ());
484 
485  ap.translate_view (mode, p0(0), p1(0), p0(1), p1(1));
486 
487  // Update current mouse position
488  m_mouseCurrent = event->pos ();
489 
490  // Force immediate redraw
491  redraw (true);
492  }
493 
494  default:
495  break;
496  }
497  }
498  else if (m_mouseMode == NoMode)
499  {
500  graphics_object obj = gh_mgr.get_object (m_handle);
501 
502  if (obj.valid_object ())
503  {
504  graphics_object figObj (obj.get_ancestor ("figure"));
505 
506  if (figObj.valid_object ()
507  && ! figObj.get ("windowbuttonmotionfcn").isempty ())
508  {
509  updateCurrentPoint (figObj, obj, event);
510  emit gh_callback_event (figObj.get_handle (),
511  "windowbuttonmotionfcn");
512  }
513  }
514  }
515 
516  // Update mouse coordinates in the figure window status bar
517  graphics_object obj = gh_mgr.get_object (m_handle);
518  graphics_object figObj = obj.get_ancestor ("figure");
519 
520  if (figObj.valid_object () && obj.valid_object ())
521  {
522  graphics_object currentObj, axesObj;
523  std::vector<std::string> omit = {"legend", "colorbar", "scribeoverlay"};
524  select_object (obj, event, currentObj, axesObj, true, omit);
525 
526  if (axesObj.valid_object ())
527  {
528  // FIXME: should we use signal/slot mechanism instead of
529  // directly calling parent fig methods
530  Figure *fig =
531  dynamic_cast<Figure *> (qt_graphics_toolkit::toolkitObject (figObj));
532  axes::properties& ap = Utils::properties<axes> (axesObj);
533 
534  if (fig)
535  fig->updateStatusBar (ap.pixel2coord (event->x (), event->y ()));
536  }
537  }
538 }
539 
540 void
542 {
543  // same processing as normal click, but event type is MouseButtonDblClick
544  canvasMousePressEvent (event);
545 }
546 
547 static double
548 button_number (QMouseEvent *event)
549 {
550  double retval = 0;
551 
552  switch (event->button ())
553  {
554  case Qt::LeftButton:
555  retval = 1;
556  break;
557 
558  case Qt::MiddleButton:
559  retval = 2;
560  break;
561 
562  case Qt::RightButton:
563  retval = 3;
564  break;
565 
566  default:
567  break;
568  }
569 
570  return retval;
571 }
572 
573 void
574 Canvas::canvasMousePressEvent (QMouseEvent *event)
575 {
576  gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
577 
578  octave::autolock guard (gh_mgr.graphics_lock ());
579 
580  graphics_object obj = gh_mgr.get_object (m_handle);
581 
582  bool isdblclick = (event->type () == QEvent::MouseButtonDblClick);
583 
584  if (obj.valid_object ())
585  {
586  graphics_object figObj (obj.get_ancestor ("figure"));
587 
588  // Any click in a figure canvas makes it current
589  if (figObj)
590  {
591  graphics_object root = gh_mgr.get_object (0);
592  Utils::properties<root_figure> (root)
593  .set_currentfigure (figObj.get_handle ().as_octave_value ());
594  }
595 
596  graphics_object currentObj, axesObj;
597 
598  // Retrieve selected object.
599  select_object (obj, event, currentObj, axesObj);
600 
601  // currentObj may be invalid if, e.g., all objects under the mouse
602  // click had "hittest" -> "off" or "pickableparts" -> "none". In that
603  // case, replace with underlying figObj which always accepts mouse
604  // clicks.
605  if (! currentObj.valid_object ())
606  currentObj = figObj;
607  else if (! currentObj.get_properties ().is_hittest ())
608  {
609  // Objects with "hittest"->"off" pass the mouse event to their
610  // parent and so on.
611  graphics_object tmpgo;
612  tmpgo = gh_mgr.get_object (currentObj.get_parent ());
613  while (tmpgo && ! tmpgo.get_properties ().is_hittest ())
614  tmpgo = gh_mgr.get_object (tmpgo.get_parent ());
615 
616  if (tmpgo && tmpgo.get_handle () != 0.0)
617  currentObj = tmpgo;
618  else
619  currentObj = graphics_object ();
620  }
621 
622  // Make selected axes current
623  bool valid_axes = axesObj.valid_object ()
624  && axesObj.get_properties ().handlevisibility_is ("on")
625  && axesObj.get_properties ().get_tag () != "legend"
626  && axesObj.get_properties ().get_tag () != "colorbar";
627 
628  if (valid_axes)
629  Utils::properties<figure> (figObj)
630  .set_currentaxes (axesObj.get_handle ().as_octave_value ());
631 
632  Figure *fig = dynamic_cast<Figure *> (qt_graphics_toolkit::toolkitObject (figObj));
633 
634  MouseMode newMouseMode = NoMode;
635 
636  if (fig)
637  newMouseMode = fig->mouseMode ();
638 
639  switch (newMouseMode)
640  {
641  case NoMode:
642  {
643  // Update the figure "currentobject"
644  auto& fprop = Utils::properties<figure> (figObj);
645 
646  if (currentObj
647  && currentObj.get_properties ().handlevisibility_is ("on"))
648  fprop.set_currentobject (currentObj.get_handle ()
649  .as_octave_value ());
650  else
651  fprop.set_currentobject (Matrix ());
652 
653  // Update figure "selectiontype" and "currentpoint"
654  emit gh_set_event (figObj.get_handle (), "selectiontype",
655  Utils::figureSelectionType (event, isdblclick),
656  false);
657 
658  updateCurrentPoint (figObj, obj, event);
659 
660  emit gh_callback_event (figObj.get_handle (),
661  "windowbuttondownfcn",
662  button_number (event));
663 
664  // Execute the "buttondownfcn" of the selected object. If the
665  // latter is empty then execute the figure "buttondownfcn"
666  if (currentObj && ! currentObj.get ("buttondownfcn").isempty ())
667  emit gh_callback_event (currentObj.get_handle (),
668  "buttondownfcn", button_number (event));
669  else if (figObj && ! figObj.get ("buttondownfcn").isempty ())
670  emit gh_callback_event (figObj.get_handle (),
671  "buttondownfcn", button_number (event));
672 
673  // Show context menu of the selected object
674  if (currentObj && event->button () == Qt::RightButton)
676  currentObj.get_properties (),
677  event->globalPos ());
678  }
679  break;
680 
681  case TextMode:
682  {
683  if (event->modifiers () == Qt::NoModifier)
684  {
685  switch (event->buttons ())
686  {
687  case Qt::LeftButton:
688  m_mouseAnchor = m_mouseCurrent = event->pos ();
689  m_mouseMode = newMouseMode;
690  m_rectMode = true;
691  }
692  }
693  redraw (false);
694  }
695  break;
696 
697  case PanMode:
698  case RotateMode:
699  case ZoomInMode:
700  case ZoomOutMode:
701  if (valid_axes)
702  {
703  bool redraw_figure = true;
704 
705  if (isdblclick)
706  {
707  if (event->button () == Qt::LeftButton)
708  {
709  axes::properties& ap = Utils::properties<axes> (axesObj);
710 
711  autoscale_axes (gh_mgr, ap);
712  }
713  else
714  {
715  redraw_figure = false;
716  }
717  }
718  else if (event->modifiers () == Qt::NoModifier)
719  {
720  switch (event->buttons ())
721  {
722  case Qt::LeftButton:
723  m_mouseAnchor = m_mouseCurrent = event->pos ();
724  m_mouseAxes = axesObj.get_handle ();
725  m_mouseMode = newMouseMode;
726  m_clickMode = newMouseMode == ZoomInMode;
727  break;
728 
729  case Qt::RightButton:
730  if (newMouseMode == ZoomInMode)
731  {
732  m_mouseAnchor = m_mouseCurrent = event->pos ();
733  m_mouseAxes = axesObj.get_handle ();
734  m_mouseMode = newMouseMode;
735  m_clickMode = false;
736  }
737 
738  break;
739 
740  case Qt::MiddleButton:
741  {
742  axes::properties& ap =
743  Utils::properties<axes> (axesObj);
744 
745  autoscale_axes (gh_mgr, ap);
746  }
747  break;
748 
749  default:
750  redraw_figure = false;
751  break;
752  }
753  }
754  else if (event->modifiers () == Qt::ShiftModifier)
755  {
756  switch (event->buttons ())
757  {
758  case Qt::LeftButton:
759  if (newMouseMode == ZoomInMode)
760  {
761  m_mouseAnchor = m_mouseCurrent = event->pos ();
762  m_mouseAxes = axesObj.get_handle ();
763  m_mouseMode = newMouseMode;
764  m_clickMode = false;
765  }
766  break;
767 
768  default:
769  redraw_figure = false;
770  break;
771  }
772  }
773 
774  if (redraw_figure)
775  redraw (false);
776  }
777  break;
778 
779  default:
780  break;
781  }
782  }
783 
784 }
785 
786 void
788 {
789  gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
790 
792  && m_mouseAxes.ok ())
793  {
794  octave::autolock guard (gh_mgr.graphics_lock ());
795 
796  graphics_object ax = gh_mgr.get_object (m_mouseAxes);
797 
798  if (ax.valid_object ())
799  {
800  axes::properties& ap = Utils::properties<axes> (ax);
801 
802  graphics_object obj = gh_mgr.get_object (m_handle);
803 
804  graphics_object figObj (obj.get_ancestor ("figure"));
805 
806  std::string zm = zoom_mode (figObj);
807 
808  if (m_mouseAnchor == event->pos ())
809  {
810  double factor = (m_clickMode ? 2.0 : 0.5);
811 
812  ColumnVector p1 = ap.pixel2coord (event->x (), event->y ());
813 
814  ap.zoom_about_point (zm, p1(0), p1(1), factor);
815  }
816  else if (m_mouseMode == ZoomInMode)
817  {
818  ColumnVector p0 = ap.pixel2coord (m_mouseAnchor.x (),
819  m_mouseAnchor.y ());
820  ColumnVector p1 = ap.pixel2coord (event->x (),
821  event->y ());
822 
823  Matrix xl (1, 2, 0.0);
824  Matrix yl (1, 2, 0.0);
825 
826  xl(0) = std::min (p0(0), p1(0));
827  xl(1) = std::max (p0(0), p1(0));
828  yl(0) = std::min (p0(1), p1(1));
829  yl(1) = std::max (p0(1), p1(1));
830 
831  ap.zoom (zm, xl, yl);
832  }
833 
834  redraw (false);
835  }
836  }
837  else if (m_mouseMode == NoMode)
838  {
839  octave::autolock guard (gh_mgr.graphics_lock ());
840 
841  graphics_object obj = gh_mgr.get_object (m_handle);
842 
843  if (obj.valid_object ())
844  {
845  graphics_object figObj (obj.get_ancestor ("figure"));
846 
847  updateCurrentPoint (figObj, obj, event);
848  emit gh_callback_event (figObj.get_handle (), "windowbuttonupfcn");
849  }
850  }
851  else if (m_mouseMode == TextMode)
852  {
853  octave::autolock guard (gh_mgr.graphics_lock ());
854 
855  graphics_object figObj
856  = gh_mgr.get_object (m_handle).get_ancestor ("figure");
857 
858  if (figObj.valid_object ())
859  {
860  QWidget *w = qWidget ();
861  if (w)
862  {
863  Matrix bb = figObj.get ("position").matrix_value ();
864  bb(0) = m_mouseAnchor.x () / bb(2);
865  bb(1) = 1.0 - (m_mouseAnchor.y () / bb(3));
866  bb(2) = (event->x () - m_mouseAnchor.x ()) / bb(2);
867  bb(3) = (m_mouseAnchor.y () - event->y ()) / bb(3);
868 
869  octave_value_list props = ovl ("textbox", bb);
870 
871  annotation_dialog anno_dlg (m_octave_qobj, w, props);
872 
873  if (anno_dlg.exec () == QDialog::Accepted)
874  {
875  props = anno_dlg.get_properties ();
876  props.prepend (figObj.get_handle ().as_octave_value ());
877 
878  emit interpreter_event
879  ([=] (octave::interpreter& interp)
880  {
881  // INTERPRETER THREAD
882 
883  interp.feval ("annotation", props);
884 
885  redraw ();
886  });
887  }
888  }
889  }
890  }
891  m_rectMode = false;
894 }
895 
896 void
897 Canvas::canvasWheelEvent (QWheelEvent *event)
898 {
899  gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
900 
901  octave::autolock guard (gh_mgr.graphics_lock ());
902 
903  graphics_object obj = gh_mgr.get_object (m_handle);
904 
905  if (obj.valid_object ())
906  {
907  std::string mode;
908 
909  graphics_object figObj (obj.get_ancestor ("figure"));
910 
911  graphics_object axesObj;
912 
913  Matrix children = obj.get_properties ().get_children ();
914  octave_idx_type num_children = children.numel ();
915 
916  for (int i = 0; i < num_children; i++)
917  {
918  graphics_object childObj (gh_mgr.get_object (children(i)));
919 
920  if (childObj.isa ("axes"))
921  {
922 #if defined (HAVE_QWHEELEVENT_POSITION)
923  QPoint pos = event->position().toPoint ();
924 #else
925  QPoint pos = event->pos ();
926 #endif
927  graphics_object go = selectFromAxes (childObj, pos);
928 
929  if (go)
930  {
931  axesObj = childObj;
932  break;
933  }
934  }
935  }
936 
937  if (axesObj)
938  {
939  MouseMode newMouseMode = NoMode;
940 
941  Figure *fig = dynamic_cast<Figure *> (qt_graphics_toolkit::toolkitObject (figObj));
942 
943  if (fig)
944  newMouseMode = fig->mouseMode ();
945 
946  if (axesObj.get_properties ().handlevisibility_is ("on"))
947  {
948  Utils::properties<figure> (figObj)
949  .set_currentaxes (axesObj.get_handle ().as_octave_value ());
950 
951  if (zoom_enabled (figObj))
952  {
953 #if defined (HAVE_QWHEELEVENT_ANGLEDELTA)
954  if (event->angleDelta().y () > 0)
955 #else
956  if (event->delta () > 0)
957 #endif
958  newMouseMode = ZoomInMode;
959  else
960  newMouseMode = ZoomOutMode;
961 
962  mode = zoom_mode (figObj);
963  }
964  else if (pan_enabled (figObj))
965  {
966  newMouseMode = PanMode;
967 
968  mode = pan_mode (figObj);
969  }
970  }
971 
972  bool redrawFigure = true;
973 
974  switch (newMouseMode)
975  {
976  case ZoomInMode:
977  case ZoomOutMode:
978  {
979  axes::properties& ap = Utils::properties<axes> (axesObj);
980 
981  // Control how fast to zoom when using scroll wheel.
982  double wheel_zoom_speed = ap.get_mousewheelzoom ();
983 
984  // Determine if we're zooming in or out.
985  double factor = (newMouseMode == ZoomInMode
986  ? 1 / (1.0 - wheel_zoom_speed)
987  : 1.0 - wheel_zoom_speed);
988 
989  // FIXME: should we zoom about point for 2D plots?
990 
991  ap.zoom (mode, factor);
992  }
993  break;
994 
995  case PanMode:
996  {
997  axes::properties& ap = Utils::properties<axes> (axesObj);
998 
999 #if defined (HAVE_QWHEELEVENT_ANGLEDELTA)
1000  double factor = (event->angleDelta().y () > 0 ? 0.1 : -0.1);
1001 #else
1002  double factor = (event->delta () > 0 ? 0.1 : -0.1);
1003 #endif
1004 
1005  if (event->modifiers () == Qt::NoModifier
1006  && mode != "horizontal")
1007  ap.pan ("vertical", factor);
1008  else if (event->modifiers () == Qt::ShiftModifier
1009  && mode != "vertical")
1010  ap.pan ("horizontal", factor);
1011  }
1012  break;
1013 
1014  default:
1015  redrawFigure = false;
1016  break;
1017  }
1018 
1019  if (redrawFigure)
1020  redraw (false);
1021  }
1022 
1023  if (! figObj.get ("windowscrollwheelfcn").isempty ())
1024  {
1025  octave_scalar_map eventData = Utils::makeScrollEventStruct (event);
1026  emit gh_callback_event (m_handle, "windowscrollwheelfcn",
1027  eventData);
1028  }
1029  }
1030 }
1031 
1032 bool
1034 {
1035  if (m_eventMask & KeyPress)
1036  {
1037  gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
1038 
1039  octave::autolock guard (gh_mgr.graphics_lock ());
1040 
1041  graphics_object obj = gh_mgr.get_object (m_handle);
1042 
1043  if (obj.valid_object ())
1044  {
1045  graphics_object figObj (obj.get_ancestor ("figure"));
1046 
1047  updateCurrentPoint (figObj, obj);
1048 
1049  octave_scalar_map eventData = Utils::makeKeyEventStruct (event);
1050 
1051  emit gh_set_event (figObj.get_handle (), "currentcharacter",
1052  eventData.getfield ("Character"), false);
1053  emit gh_callback_event (figObj.get_handle (), "keypressfcn",
1054  eventData);
1055  }
1056 
1057  return true;
1058  }
1059 
1060  return false;
1061 }
1062 
1063 bool
1065 {
1066  if (! event->isAutoRepeat () && (m_eventMask & KeyRelease))
1067  {
1068  gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
1069 
1070  octave::autolock guard (gh_mgr.graphics_lock ());
1071 
1072  graphics_object obj = gh_mgr.get_object (m_handle);
1073 
1074  if (obj.valid_object ())
1075  {
1076  graphics_object figObj (obj.get_ancestor ("figure"));
1077  emit gh_callback_event (figObj.get_handle (), "keyreleasefcn",
1078  Utils::makeKeyEventStruct (event));
1079  }
1080 
1081  return true;
1082  }
1083 
1084  return false;
1085 }
1086 
1087 Canvas *
1088 Canvas::create (octave::base_qobject& oct_qobj, octave::interpreter& interp,
1089  const graphics_handle& handle, QWidget *parent,
1090  const std::string& /* name */)
1091 {
1092  // Only OpenGL
1093  return new GLCanvas (oct_qobj, interp, handle, parent);
1094 }
1095 
OCTAVE_END_NAMESPACE(octave)
static std::string pan_mode(const graphics_object figObj)
Definition: Canvas.cc:303
static bool pan_enabled(const graphics_object figObj)
Definition: Canvas.cc:291
static double button_number(QMouseEvent *event)
Definition: Canvas.cc:548
static bool zoom_enabled(const graphics_object figObj)
Definition: Canvas.cc:315
static std::string zoom_mode(const graphics_object figObj)
Definition: Canvas.cc:327
static void autoscale_axes(gh_manager &gh_mgr, axes::properties &ap)
Definition: Canvas.cc:262
MouseMode
Definition: Figure.h:46
@ TextMode
Definition: Figure.h:53
@ PanMode
Definition: Figure.h:51
@ NoMode
Definition: Figure.h:47
@ SelectMode
Definition: Figure.h:52
@ RotateMode
Definition: Figure.h:48
@ ZoomOutMode
Definition: Figure.h:50
@ ZoomInMode
Definition: Figure.h:49
charNDArray max(char d, const charNDArray &m)
Definition: chNDArray.cc:230
charNDArray min(char d, const charNDArray &m)
Definition: chNDArray.cc:207
OCTARRAY_OVERRIDABLE_FUNC_API octave_idx_type numel(void) const
Number of elements in the array.
Definition: Array.h:414
Definition: Canvas.h:50
QPoint m_mouseAnchor
Definition: Canvas.h:163
void select_object(graphics_object obj, QMouseEvent *event, graphics_object &currentObj, graphics_object &axesObj, bool axes_only=false, std::vector< std::string > omit=std::vector< std::string >())
Definition: Canvas.cc:339
static Canvas * create(octave::base_qobject &oct_qobj, octave::interpreter &interp, const graphics_handle &handle, QWidget *parent, const std::string &name)
Definition: Canvas.cc:1088
QCursor make_cursor(const QString &name, int hot_x=-1, int hot_y=-1)
Definition: Canvas.cc:77
bool m_redrawBlocked
Definition: Canvas.h:160
bool m_rectMode
Definition: Canvas.h:167
int m_eventMask
Definition: Canvas.h:166
octave::base_qobject & m_octave_qobj
Definition: Canvas.h:152
void canvasMouseReleaseEvent(QMouseEvent *event)
Definition: Canvas.cc:787
octave::interpreter & m_interpreter
Definition: Canvas.h:153
graphics_handle m_handle
Definition: Canvas.h:159
void canvasPaintEvent(void)
Definition: Canvas.cc:275
void canvasMouseDoubleClickEvent(QMouseEvent *event)
Definition: Canvas.cc:541
bool canvasKeyReleaseEvent(QKeyEvent *event)
Definition: Canvas.cc:1064
virtual void draw(const graphics_handle &handle)=0
bool m_clickMode
Definition: Canvas.h:162
void redraw(bool sync=false)
Definition: Canvas.cc:57
void blockRedraw(bool block=true)
Definition: Canvas.cc:71
void interpreter_event(const octave::fcn_callback &fcn)
virtual void drawZoomBox(const QPoint &p1, const QPoint &p2)=0
void canvasWheelEvent(QWheelEvent *event)
Definition: Canvas.cc:897
QPoint m_mouseCurrent
Definition: Canvas.h:164
void setCursor(MouseMode mode, std::string fallback, QImage cdata, Matrix hotspot)
Definition: Canvas.cc:87
@ KeyRelease
Definition: Canvas.h:57
@ KeyPress
Definition: Canvas.h:56
graphics_handle m_mouseAxes
Definition: Canvas.h:165
void gh_set_event(const graphics_handle &h, const std::string &name, const octave_value &value)
MouseMode m_mouseMode
Definition: Canvas.h:161
void gh_callback_event(const graphics_handle &h, const std::string &name)
virtual graphics_object selectFromAxes(const graphics_object &ax, const QPoint &pt)=0
void updateCurrentPoint(const graphics_object &fig, const graphics_object &obj, QMouseEvent *event)
Definition: Canvas.cc:180
virtual QWidget * qWidget(void)=0
void canvasMousePressEvent(QMouseEvent *event)
Definition: Canvas.cc:574
void canvasMouseMoveEvent(QMouseEvent *event)
Definition: Canvas.cc:439
bool canvasKeyPressEvent(QKeyEvent *event)
Definition: Canvas.cc:1033
static void executeAt(octave::interpreter &interp, const base_properties &props, const QPoint &pt)
Definition: ContextMenu.cc:122
Definition: Figure.h:65
void updateStatusBar(ColumnVector pt)
Definition: Figure.cc:584
MouseMode mouseMode(void)
Definition: Figure.cc:222
Definition: dMatrix.h:42
octave_value_list get_properties() const
bool ok(void) const
Definition: oct-handle.h:113
const octave_value & contents(const_iterator p) const
Definition: oct-map.h:205
octave_value getfield(const std::string &key) const
Definition: oct-map.cc:183
octave_value_list & prepend(const octave_value &val)
Definition: ovl.cc:80
OCTINTERP_API octave_scalar_map scalar_map_value(void) const
std::string string_value(bool force=false) const
Definition: ov.h:1019
static Object * toolkitObject(const graphics_object &go)
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
octave_handle graphics_handle
class OCTAVE_API Matrix
Definition: mx-fwd.h:31
T * r
Definition: mx-inlines.cc:773
std::complex< double > w(std::complex< double > z, double relerr=0)
Matrix figureCurrentPoint(const graphics_object &fig, QMouseEvent *event)
octave_scalar_map makeKeyEventStruct(QKeyEvent *event)
std::string figureSelectionType(QMouseEvent *event, bool isDoubleClick)
octave_scalar_map makeScrollEventStruct(QWheelEvent *event)
T::properties & properties(graphics_object obj)
octave_value_list ovl(const OV_Args &... args)
Construct an octave_value_list with less typing.
Definition: ovl.h:211