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