GNU Octave 7.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-2022 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"
48
49#include "builtin-defun-decls.h"
50#include "graphics.h"
51#include "interpreter.h"
52#include "oct-opengl.h"
53
54namespace octave
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
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
181 Canvas::updateCurrentPoint (const graphics_object& fig,
182 const graphics_object& obj, QMouseEvent *event)
183 {
184 gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
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
221 Canvas::updateCurrentPoint (const graphics_object& fig,
222 const graphics_object& obj)
223 {
224 gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
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
263 autoscale_axes (gh_manager& gh_mgr, axes::properties& ap)
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 {
280 gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
281
282 octave::autolock guard (gh_mgr.graphics_lock ());
283
284 draw (m_handle);
285
288 }
289 }
290
291 static bool
292 pan_enabled (const graphics_object figObj)
293 {
294 // Getting pan mode property:
295 octave_value ov_pm
296 = Utils::properties<figure> (figObj).get___pan_mode__ ();
297
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
311
312 return pm.contents ("Motion").string_value ();
313 }
314
315 static bool
316 zoom_enabled (const graphics_object figObj)
317 {
318 // Getting zoom mode property:
319 octave_value ov_zm
320 = Utils::properties<figure> (figObj).get___zoom_mode__ ();
321
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
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
348 gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
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 bool rect_contains_pos = r.contains (event->localPos ());
375 if (rect_contains_pos)
376 {
377 currentObj = childObj;
378 break;
379 }
380 }
381 }
382
383 if (axes_only)
384 {
385 QPoint pt = event->pos ();
386
387 for (QList<graphics_object>::ConstIterator it = axesList.begin ();
388 it != axesList.end (); ++it)
389 {
390 const axes::properties& ap =
391 dynamic_cast<const axes::properties&> ((*it).get_properties ());
392
393 ColumnVector p0 = ap.pixel2coord (pt.x (), pt.y ());
394 Matrix xlim = ap.get_xlim ().matrix_value ();
395 Matrix ylim = ap.get_ylim ().matrix_value ();
396
397 if (xlim(0) < p0(0) && xlim(1) > p0(0)
398 && ylim(0) < p0(1) && ylim(1) > p0(1))
399 {
400 axesObj = *it;
401 return;
402 }
403 }
404 }
405 else if (! currentObj)
406 {
407 for (QList<graphics_object>::ConstIterator it = axesList.begin ();
408 it != axesList.end (); ++it)
409 {
410 graphics_object go = selectFromAxes (*it, event->pos ());
411
412 if (go)
413 {
414 currentObj = go;
415 axesObj = *it;
416 }
417 // FIXME: is this really necessary? the axes object should
418 // have been selected through selectFromAxes anyway
419 else if (it->get_properties ().is_hittest ())
420 {
421 Matrix bb = it->get_properties ().get_boundingbox (true);
422 QRectF r (bb(0), bb(1), bb(2), bb(3));
423
424 // Allow a rectangle (e.g., Zoom box) to be slightly outside
425 // the axes and still select it.
426 r.adjust (-20, -20, 20, 20);
427
428 bool rect_contains_pos = r.contains (event->localPos ());
429 if (rect_contains_pos)
430 axesObj = *it;
431 }
432
433 if (axesObj && currentObj)
434 break;
435 }
436 }
437 }
438
439 void
440 Canvas::canvasMouseMoveEvent (QMouseEvent *event)
441 {
442 gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
443
444 octave::autolock guard (gh_mgr.graphics_lock ());
445
446 graphics_object ax = gh_mgr.get_object (m_mouseAxes);
447
448 if (m_mouseMode != NoMode && (ax.valid_object () || m_mouseMode == TextMode))
449 {
450 switch (m_mouseMode)
451 {
452 case RotateMode:
453 {
454 axes::properties& ap = Utils::properties<axes> (ax);
455
456 ap.rotate3d (m_mouseCurrent.x (), event->x (),
457 m_mouseCurrent.y (), event->y ());
458
459 // Update current mouse position
460 m_mouseCurrent = event->pos ();
461
462 // Force immediate redraw
463 redraw (true);
464 }
465 break;
466 case TextMode:
467 case ZoomInMode:
468 case ZoomOutMode:
469 m_mouseCurrent = event->pos ();
470 redraw (true);
471 break;
472
473 case PanMode:
474 {
475 axes::properties& ap = Utils::properties<axes> (ax);
476
477 graphics_object figObj (ax.get_ancestor ("figure"));
478
479 std::string mode = pan_mode (figObj);
480
481 ColumnVector p0 = ap.pixel2coord (m_mouseCurrent.x (),
482 m_mouseCurrent.y ());
483 ColumnVector p1 = ap.pixel2coord (event->x (),
484 event->y ());
485
486 ap.translate_view (mode, p0(0), p1(0), p0(1), p1(1));
487
488 // Update current mouse position
489 m_mouseCurrent = event->pos ();
490
491 // Force immediate redraw
492 redraw (true);
493 }
494
495 default:
496 break;
497 }
498 }
499 else if (m_mouseMode == NoMode)
500 {
501 graphics_object obj = gh_mgr.get_object (m_handle);
502
503 if (obj.valid_object ())
504 {
505 graphics_object figObj (obj.get_ancestor ("figure"));
506
507 if (figObj.valid_object ()
508 && ! figObj.get ("windowbuttonmotionfcn").isempty ())
509 {
510 updateCurrentPoint (figObj, obj, event);
511 emit gh_callback_event (figObj.get_handle (),
512 "windowbuttonmotionfcn");
513 }
514 }
515 }
516
517 // Update mouse coordinates in the figure window status bar
518 graphics_object obj = gh_mgr.get_object (m_handle);
519 graphics_object figObj = obj.get_ancestor ("figure");
520
521 if (figObj.valid_object () && obj.valid_object ())
522 {
523 graphics_object currentObj, axesObj;
524 std::vector<std::string> omit = {"legend", "colorbar", "scribeoverlay"};
525 select_object (obj, event, currentObj, axesObj, true, omit);
526
527 if (axesObj.valid_object ())
528 {
529 // FIXME: should we use signal/slot mechanism instead of
530 // directly calling parent fig methods
531 Figure *fig =
532 dynamic_cast<Figure *> (qt_graphics_toolkit::toolkitObject (figObj));
533 axes::properties& ap = Utils::properties<axes> (axesObj);
534
535 if (fig)
536 fig->updateStatusBar (ap.pixel2coord (event->x (), event->y ()));
537 }
538 }
539 }
540
541 void
543 {
544 // same processing as normal click, but event type is MouseButtonDblClick
545 canvasMousePressEvent (event);
546 }
547
548 static double
549 button_number (QMouseEvent *event)
550 {
551 double retval = 0;
552
553 switch (event->button ())
554 {
555 case Qt::LeftButton:
556 retval = 1;
557 break;
558
559 case Qt::MiddleButton:
560 retval = 2;
561 break;
562
563 case Qt::RightButton:
564 retval = 3;
565 break;
566
567 default:
568 break;
569 }
570
571 return retval;
572 }
573
574 void
575 Canvas::canvasMousePressEvent (QMouseEvent *event)
576 {
577 gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
578
579 octave::autolock guard (gh_mgr.graphics_lock ());
580
581 graphics_object obj = gh_mgr.get_object (m_handle);
582
583 bool isdblclick = (event->type () == QEvent::MouseButtonDblClick);
584
585 if (obj.valid_object ())
586 {
587 graphics_object figObj (obj.get_ancestor ("figure"));
588
589 // Any click in a figure canvas makes it current
590 if (figObj)
591 {
592 graphics_object root = gh_mgr.get_object (0);
593 Utils::properties<root_figure> (root)
594 .set_currentfigure (figObj.get_handle ().as_octave_value ());
595 }
596
597 graphics_object currentObj, axesObj;
598
599 // Retrieve selected object.
600 select_object (obj, event, currentObj, axesObj);
601
602 // currentObj may be invalid if, e.g., all objects under the mouse
603 // click had "hittest" -> "off" or "pickableparts" -> "none". In that
604 // case, replace with underlying figObj which always accepts mouse
605 // clicks.
606 if (! currentObj.valid_object ())
607 currentObj = figObj;
608 else if (! currentObj.get_properties ().is_hittest ())
609 {
610 // Objects with "hittest"->"off" pass the mouse event to their
611 // parent and so on.
612 graphics_object tmpgo;
613 tmpgo = gh_mgr.get_object (currentObj.get_parent ());
614 while (tmpgo && ! tmpgo.get_properties ().is_hittest ())
615 tmpgo = gh_mgr.get_object (tmpgo.get_parent ());
616
617 if (tmpgo && tmpgo.get_handle () != 0.0)
618 currentObj = tmpgo;
619 else
620 currentObj = graphics_object ();
621 }
622
623 // Make selected axes current
624 bool valid_axes = axesObj.valid_object ()
625 && axesObj.get_properties ().handlevisibility_is ("on")
626 && axesObj.get_properties ().get_tag () != "legend"
627 && axesObj.get_properties ().get_tag () != "colorbar";
628
629 if (valid_axes)
630 Utils::properties<figure> (figObj)
631 .set_currentaxes (axesObj.get_handle ().as_octave_value ());
632
633 Figure *fig = dynamic_cast<Figure *> (qt_graphics_toolkit::toolkitObject (figObj));
634
635 MouseMode newMouseMode = NoMode;
636
637 if (fig)
638 newMouseMode = fig->mouseMode ();
639
640 switch (newMouseMode)
641 {
642 case NoMode:
643 {
644 // Update the figure "currentobject"
645 auto& fprop = Utils::properties<figure> (figObj);
646
647 if (currentObj
648 && currentObj.get_properties ().handlevisibility_is ("on"))
649 fprop.set_currentobject (currentObj.get_handle ()
650 .as_octave_value ());
651 else
652 fprop.set_currentobject (Matrix ());
653
654 // Update figure "selectiontype" and "currentpoint"
655 emit gh_set_event (figObj.get_handle (), "selectiontype",
656 Utils::figureSelectionType (event, isdblclick),
657 false);
658
659 updateCurrentPoint (figObj, obj, event);
660
661 emit gh_callback_event (figObj.get_handle (),
662 "windowbuttondownfcn",
663 button_number (event));
664
665 // Execute the "buttondownfcn" of the selected object. If the
666 // latter is empty then execute the figure "buttondownfcn"
667 if (currentObj && ! currentObj.get ("buttondownfcn").isempty ())
668 emit gh_callback_event (currentObj.get_handle (),
669 "buttondownfcn", button_number (event));
670 else if (figObj && ! figObj.get ("buttondownfcn").isempty ())
671 emit gh_callback_event (figObj.get_handle (),
672 "buttondownfcn", button_number (event));
673
674 // Show context menu of the selected object
675 if (currentObj && event->button () == Qt::RightButton)
677 currentObj.get_properties (),
678 event->globalPos ());
679 }
680 break;
681
682 case TextMode:
683 {
684 if (event->modifiers () == Qt::NoModifier)
685 {
686 switch (event->buttons ())
687 {
688 case Qt::LeftButton:
689 m_mouseAnchor = m_mouseCurrent = event->pos ();
690 m_mouseMode = newMouseMode;
691 m_rectMode = true;
692 }
693 }
694 redraw (false);
695 }
696 break;
697
698 case PanMode:
699 case RotateMode:
700 case ZoomInMode:
701 case ZoomOutMode:
702 if (valid_axes)
703 {
704 bool redraw_figure = true;
705
706 if (isdblclick)
707 {
708 if (event->button () == Qt::LeftButton)
709 {
710 axes::properties& ap = Utils::properties<axes> (axesObj);
711
712 autoscale_axes (gh_mgr, ap);
713 }
714 else
715 {
716 redraw_figure = false;
717 }
718 }
719 else if (event->modifiers () == Qt::NoModifier)
720 {
721 switch (event->buttons ())
722 {
723 case Qt::LeftButton:
724 m_mouseAnchor = m_mouseCurrent = event->pos ();
725 m_mouseAxes = axesObj.get_handle ();
726 m_mouseMode = newMouseMode;
727 m_clickMode = newMouseMode == ZoomInMode;
728 break;
729
730 case Qt::RightButton:
731 if (newMouseMode == ZoomInMode)
732 {
733 m_mouseAnchor = m_mouseCurrent = event->pos ();
734 m_mouseAxes = axesObj.get_handle ();
735 m_mouseMode = newMouseMode;
736 m_clickMode = false;
737 }
738
739 break;
740
741 case Qt::MiddleButton:
742 {
743 axes::properties& ap =
744 Utils::properties<axes> (axesObj);
745
746 autoscale_axes (gh_mgr, ap);
747 }
748 break;
749
750 default:
751 redraw_figure = false;
752 break;
753 }
754 }
755 else if (event->modifiers () == Qt::ShiftModifier)
756 {
757 switch (event->buttons ())
758 {
759 case Qt::LeftButton:
760 if (newMouseMode == ZoomInMode)
761 {
762 m_mouseAnchor = m_mouseCurrent = event->pos ();
763 m_mouseAxes = axesObj.get_handle ();
764 m_mouseMode = newMouseMode;
765 m_clickMode = false;
766 }
767 break;
768
769 default:
770 redraw_figure = false;
771 break;
772 }
773 }
774
775 if (redraw_figure)
776 redraw (false);
777 }
778 break;
779
780 default:
781 break;
782 }
783 }
784
785 }
786
787 void
789 {
790 gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
791
793 && m_mouseAxes.ok ())
794 {
795 octave::autolock guard (gh_mgr.graphics_lock ());
796
797 graphics_object ax = gh_mgr.get_object (m_mouseAxes);
798
799 if (ax.valid_object ())
800 {
801 axes::properties& ap = Utils::properties<axes> (ax);
802
803 graphics_object obj = gh_mgr.get_object (m_handle);
804
805 graphics_object figObj (obj.get_ancestor ("figure"));
806
807 std::string zm = zoom_mode (figObj);
808
809 if (m_mouseAnchor == event->pos ())
810 {
811 double factor = (m_clickMode ? 2.0 : 0.5);
812
813 ColumnVector p1 = ap.pixel2coord (event->x (), event->y ());
814
815 ap.zoom_about_point (zm, p1(0), p1(1), factor);
816 }
817 else if (m_mouseMode == ZoomInMode)
818 {
819 ColumnVector p0 = ap.pixel2coord (m_mouseAnchor.x (),
820 m_mouseAnchor.y ());
821 ColumnVector p1 = ap.pixel2coord (event->x (),
822 event->y ());
823
824 Matrix xl (1, 2, 0.0);
825 Matrix yl (1, 2, 0.0);
826
827 xl(0) = std::min (p0(0), p1(0));
828 xl(1) = std::max (p0(0), p1(0));
829 yl(0) = std::min (p0(1), p1(1));
830 yl(1) = std::max (p0(1), p1(1));
831
832 ap.zoom (zm, xl, yl);
833 }
834
835 redraw (false);
836 }
837 }
838 else if (m_mouseMode == NoMode)
839 {
840 octave::autolock guard (gh_mgr.graphics_lock ());
841
842 graphics_object obj = gh_mgr.get_object (m_handle);
843
844 if (obj.valid_object ())
845 {
846 graphics_object figObj (obj.get_ancestor ("figure"));
847
848 updateCurrentPoint (figObj, obj, event);
849 emit gh_callback_event (figObj.get_handle (), "windowbuttonupfcn");
850 }
851 }
852 else if (m_mouseMode == TextMode)
853 {
854 octave::autolock guard (gh_mgr.graphics_lock ());
855
856 graphics_object figObj
857 = gh_mgr.get_object (m_handle).get_ancestor ("figure");
858
859 if (figObj.valid_object ())
860 {
861 QWidget *w = qWidget ();
862 if (w)
863 {
864 Matrix bb = figObj.get ("position").matrix_value ();
865 bb(0) = m_mouseAnchor.x () / bb(2);
866 bb(1) = 1.0 - (m_mouseAnchor.y () / bb(3));
867 bb(2) = (event->x () - m_mouseAnchor.x ()) / bb(2);
868 bb(3) = (m_mouseAnchor.y () - event->y ()) / bb(3);
869
870 octave_value_list props = ovl ("textbox", bb);
871
872 annotation_dialog anno_dlg (m_octave_qobj, w, props);
873
874 if (anno_dlg.exec () == QDialog::Accepted)
875 {
876 props = anno_dlg.get_properties ();
877 props.prepend (figObj.get_handle ().as_octave_value ());
878
880 ([=] (octave::interpreter& interp)
881 {
882 // INTERPRETER THREAD
883
884 interp.feval ("annotation", props);
885
886 redraw ();
887 });
888 }
889 }
890 }
891 }
892 m_rectMode = false;
895 }
896
897 void
898 Canvas::canvasWheelEvent (QWheelEvent *event)
899 {
900 gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
901
902 octave::autolock guard (gh_mgr.graphics_lock ());
903
904 graphics_object obj = gh_mgr.get_object (m_handle);
905
906 if (obj.valid_object ())
907 {
908 std::string mode;
909
910 graphics_object figObj (obj.get_ancestor ("figure"));
911
912 graphics_object axesObj;
913
914 Matrix children = obj.get_properties ().get_children ();
915 octave_idx_type num_children = children.numel ();
916
917 for (int i = 0; i < num_children; i++)
918 {
919 graphics_object childObj (gh_mgr.get_object (children(i)));
920
921 if (childObj.isa ("axes"))
922 {
923#if defined (HAVE_QWHEELEVENT_POSITION)
924 QPoint pos = event->position().toPoint ();
925#else
926 QPoint pos = event->pos ();
927#endif
928 graphics_object go = selectFromAxes (childObj, pos);
929
930 if (go)
931 {
932 axesObj = childObj;
933 break;
934 }
935 }
936 }
937
938 if (axesObj)
939 {
940 MouseMode newMouseMode = NoMode;
941
942 Figure *fig = dynamic_cast<Figure *> (qt_graphics_toolkit::toolkitObject (figObj));
943
944 if (fig)
945 newMouseMode = fig->mouseMode ();
946
947 if (axesObj.get_properties ().handlevisibility_is ("on"))
948 {
949 Utils::properties<figure> (figObj)
950 .set_currentaxes (axesObj.get_handle ().as_octave_value ());
951
952 if (zoom_enabled (figObj))
953 {
954#if defined (HAVE_QWHEELEVENT_ANGLEDELTA)
955 if (event->angleDelta().y () > 0)
956#else
957 if (event->delta () > 0)
958#endif
959 newMouseMode = ZoomInMode;
960 else
961 newMouseMode = ZoomOutMode;
962
963 mode = zoom_mode (figObj);
964 }
965 else if (pan_enabled (figObj))
966 {
967 newMouseMode = PanMode;
968
969 mode = pan_mode (figObj);
970 }
971 }
972
973 bool redrawFigure = true;
974
975 switch (newMouseMode)
976 {
977 case ZoomInMode:
978 case ZoomOutMode:
979 {
980 axes::properties& ap = Utils::properties<axes> (axesObj);
981
982 // Control how fast to zoom when using scroll wheel.
983 double wheel_zoom_speed = ap.get_mousewheelzoom ();
984
985 // Determine if we're zooming in or out.
986 double factor = (newMouseMode == ZoomInMode
987 ? 1 / (1.0 - wheel_zoom_speed)
988 : 1.0 - wheel_zoom_speed);
989
990 // FIXME: should we zoom about point for 2D plots?
991
992 ap.zoom (mode, factor);
993 }
994 break;
995
996 case PanMode:
997 {
998 axes::properties& ap = Utils::properties<axes> (axesObj);
999
1000#if defined (HAVE_QWHEELEVENT_ANGLEDELTA)
1001 double factor = (event->angleDelta().y () > 0 ? 0.1 : -0.1);
1002#else
1003 double factor = (event->delta () > 0 ? 0.1 : -0.1);
1004#endif
1005
1006 if (event->modifiers () == Qt::NoModifier
1007 && mode != "horizontal")
1008 ap.pan ("vertical", factor);
1009 else if (event->modifiers () == Qt::ShiftModifier
1010 && mode != "vertical")
1011 ap.pan ("horizontal", factor);
1012 }
1013 break;
1014
1015 default:
1016 redrawFigure = false;
1017 break;
1018 }
1019
1020 if (redrawFigure)
1021 redraw (false);
1022 }
1023
1024 if (! figObj.get ("windowscrollwheelfcn").isempty ())
1025 {
1027 emit gh_callback_event (m_handle, "windowscrollwheelfcn",
1028 eventData);
1029 }
1030 }
1031 }
1032
1033 bool
1035 {
1036 if (m_eventMask & KeyPress)
1037 {
1038 gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
1039
1040 octave::autolock guard (gh_mgr.graphics_lock ());
1041
1042 graphics_object obj = gh_mgr.get_object (m_handle);
1043
1044 if (obj.valid_object ())
1045 {
1046 graphics_object figObj (obj.get_ancestor ("figure"));
1047
1048 updateCurrentPoint (figObj, obj);
1049
1050 octave_scalar_map eventData = Utils::makeKeyEventStruct (event);
1051
1052 emit gh_set_event (figObj.get_handle (), "currentcharacter",
1053 eventData.getfield ("Character"), false);
1054 emit gh_callback_event (figObj.get_handle (), "keypressfcn",
1055 eventData);
1056 }
1057
1058 return true;
1059 }
1060
1061 return false;
1062 }
1063
1064 bool
1066 {
1067 if (! event->isAutoRepeat () && (m_eventMask & KeyRelease))
1068 {
1069 gh_manager& gh_mgr = m_interpreter.get_gh_manager ();
1070
1071 octave::autolock guard (gh_mgr.graphics_lock ());
1072
1073 graphics_object obj = gh_mgr.get_object (m_handle);
1074
1075 if (obj.valid_object ())
1076 {
1077 graphics_object figObj (obj.get_ancestor ("figure"));
1078 emit gh_callback_event (figObj.get_handle (), "keyreleasefcn",
1080 }
1081
1082 return true;
1083 }
1084
1085 return false;
1086 }
1087
1088 Canvas *
1089 Canvas::create (octave::base_qobject& oct_qobj, octave::interpreter& interp,
1090 const graphics_handle& handle, QWidget *parent,
1091 const std::string& /* name */)
1092 {
1093 // Only OpenGL
1094 return new GLCanvas (oct_qobj, interp, handle, parent);
1095 }
1096
1097}
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:411
Definition: dMatrix.h:42
octave_value_list get_properties() const
bool canvasKeyPressEvent(QKeyEvent *event)
Definition: Canvas.cc:1034
void canvasMouseReleaseEvent(QMouseEvent *event)
Definition: Canvas.cc:788
bool canvasKeyReleaseEvent(QKeyEvent *event)
Definition: Canvas.cc:1065
void gh_callback_event(const graphics_handle &h, const std::string &name)
void canvasPaintEvent(void)
Definition: Canvas.cc:276
void updateCurrentPoint(const graphics_object &fig, const graphics_object &obj, QMouseEvent *event)
Definition: Canvas.cc:181
static Canvas * create(octave::base_qobject &oct_qobj, octave::interpreter &interp, const graphics_handle &handle, QWidget *parent, const std::string &name)
Definition: Canvas.cc:1089
bool m_rectMode
Definition: Canvas.h:171
void blockRedraw(bool block=true)
Definition: Canvas.cc:72
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 graphics_object selectFromAxes(const graphics_object &ax, const QPoint &pt)=0
void canvasMouseMoveEvent(QMouseEvent *event)
Definition: Canvas.cc:440
void canvasMousePressEvent(QMouseEvent *event)
Definition: Canvas.cc:575
graphics_handle m_handle
Definition: Canvas.h:163
QPoint m_mouseAnchor
Definition: Canvas.h:167
octave::interpreter & m_interpreter
Definition: Canvas.h:157
QPoint m_mouseCurrent
Definition: Canvas.h:168
octave::base_qobject & m_octave_qobj
Definition: Canvas.h:156
void setCursor(MouseMode mode, std::string fallback, QImage cdata, Matrix hotspot)
Definition: Canvas.cc:88
void redraw(bool sync=false)
Definition: Canvas.cc:58
QCursor make_cursor(const QString &name, int hot_x=-1, int hot_y=-1)
Definition: Canvas.cc:78
MouseMode m_mouseMode
Definition: Canvas.h:165
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
virtual void drawZoomBox(const QPoint &p1, const QPoint &p2)=0
virtual void draw(const graphics_handle &handle)=0
int m_eventMask
Definition: Canvas.h:170
void interpreter_event(const octave::fcn_callback &fcn)
void canvasMouseDoubleClickEvent(QMouseEvent *event)
Definition: Canvas.cc:542
bool m_redrawBlocked
Definition: Canvas.h:164
virtual QWidget * qWidget(void)=0
bool m_clickMode
Definition: Canvas.h:166
void canvasWheelEvent(QWheelEvent *event)
Definition: Canvas.cc:898
static void executeAt(octave::interpreter &interp, const base_properties &props, const QPoint &pt)
Definition: ContextMenu.cc:123
MouseMode mouseMode(void)
Definition: Figure.cc:224
void updateStatusBar(ColumnVector pt)
Definition: Figure.cc:586
Base class for Octave interfaces that use Qt.
resource_manager & get_resource_manager(void)
static Object * toolkitObject(const graphics_object &go)
QIcon icon(const QString &icon_name, bool fallback=true)
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
octave_handle graphics_handle
QString name
class OCTAVE_API Matrix
Definition: mx-fwd.h:31
std::complex< double > w(std::complex< double > z, double relerr=0)
octave_scalar_map makeScrollEventStruct(QWheelEvent *event)
std::string figureSelectionType(QMouseEvent *event, bool isDoubleClick)
T::properties & properties(graphics_object obj)
octave_scalar_map makeKeyEventStruct(QKeyEvent *event)
Matrix figureCurrentPoint(const graphics_object &fig, QMouseEvent *event)
static std::string pan_mode(const graphics_object figObj)
Definition: Canvas.cc:304
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
MouseMode
Definition: Figure.h:50
@ PanMode
Definition: Figure.h:55
@ ZoomOutMode
Definition: Figure.h:54
@ RotateMode
Definition: Figure.h:52
@ NoMode
Definition: Figure.h:51
@ TextMode
Definition: Figure.h:57
@ ZoomInMode
Definition: Figure.h:53
@ SelectMode
Definition: Figure.h:56
static std::string zoom_mode(const graphics_object figObj)
Definition: Canvas.cc:328
static bool pan_enabled(const graphics_object figObj)
Definition: Canvas.cc:292
static double button_number(QMouseEvent *event)
Definition: Canvas.cc:549
octave_value_list ovl(const OV_Args &... args)
Construct an octave_value_list with less typing.
Definition: ovl.h:211