comparison libgui/graphics/Canvas.cc @ 19697:dfea01b3425f

more mouse interaction features for Qt plotting widget * graphics.in.h, graphics.cc (figure::properties::__mouse_mode__, figure::properties::__pan_mode__, figure::properties::__rotate_mode__, figure::properties::__zoom_mode__): New properties. (figure::properties::set___mouse_mode__): New function. (axes::properties::pan, axes::properties::rotate3d, axes::properties::zoom): New functions. Handle zoom, and pan modes. (axes::properties::clear_zoom_stack): New arg, do_unzoom. Conditionally call unzoom. (axes::properties::unzoom): Also restore view property. (axes::properties::rotate_view): Conditionall save state to zoom stack. (axes::properties::push_zoom_stack): New function. (axes::properties::pan, axes::properties::rotate3d): Delete properties. (axes::properties::update_xlim, axes::properties::update_ylim, axes::properties::update_zlim): Don't clear zoom stack. Delete do_clr_zoom argument. (axes::properties::set_pan, axes::properties::set_rotate3d): Delete. (F__zoom__): New function. * Canvas.h, Canvas.cc (Canvas::toggleAxes, Canvas::toggleGrid): New pure virtual functions. (Canvas::setCursor, Canvas::canvasToggleAxes, Canvas::canvasToggleGrid, Canvas::canvasMouseDoubleClickEvent, Canvas::canvasWheelEvent): New functions. (zoom_enabled, pan_enabled, pan_mode, rotate_enabled, rotate_mode): New static functions. (Canvas::canvasMouseMoveEvent): Call axes::properties::rotate3d to do rotation. Handle panning. (Canvas::canvasMousePressEvent): Also handle "unzoom" action when in pan and rotate modes. (Canvas::canvasMouseReleaseEvent): Zoom by factor if mouse has not moved from mouse press event. * Figure.h, Figure.cc (MouseMode): New enum value, TextMode. (Figure::m_mouseMode, Figure::m_lastMouseMode): Delete member variables. (Figure::mouseMode, Figure::setMouseMode): Get info from and save info to figure properties. (Figure::updateFigureToolBarAndMenuBar, Figure::toggleAxes, Figure::toggleGrid): New functions. (Figure::m_mouseModeGroup): New member variable. (Figure::createFigureToolBarAndMenuBar): Add actions for toggling Axes and Grid. Maintain pointer to MouseModeActionGroup. (mouse_mode_to_string, mouse_mode_from_string): New functions. * GLCanvas.h, GLCanvas.cc (GLCanvas::toggleAxes, GLCanvas::ToggleGrid, GLCanvas::mouseDoubleClickEvent, GLCanvas::wheelEvent): New functions. * MouseModeActionGroup.h, MouseModeActionGroup.cc (MouseModeActionGroup::mouseMode): Delete. (MouseModeActionGroup::setMode): New function. (MouseModeActionGroup::MouseModeActionGroup): Also include action to insert text in the list. * figure.m: Set default pan, rotate, and zoom mode properties for the figure object. * pan.m, rotate3d.m, zoom.m: Improve compatibility with Matlab. Set mode properties for figure. * __init_fltk__.cc: Cope with changes to graphics properties.
author John W. Eaton <jwe@octave.org>
date Fri, 06 Feb 2015 13:06:54 -0500
parents fe0e34be5576
children 35bca657d74d
comparison
equal deleted inserted replaced
19696:c728ae4d1790 19697:dfea01b3425f
23 #ifdef HAVE_CONFIG_H 23 #ifdef HAVE_CONFIG_H
24 #include <config.h> 24 #include <config.h>
25 #endif 25 #endif
26 26
27 #include <QApplication> 27 #include <QApplication>
28 #include <QBitmap>
29 #include <QCursor>
28 #include <QList> 30 #include <QList>
29 #include <QMouseEvent> 31 #include <QMouseEvent>
32 #include <QWheelEvent>
30 #include <QRectF> 33 #include <QRectF>
31 34
32 #include "Backend.h" 35 #include "Backend.h"
33 #include "Canvas.h" 36 #include "Canvas.h"
34 #include "ContextMenu.h" 37 #include "ContextMenu.h"
49 void Canvas::blockRedraw (bool block) 52 void Canvas::blockRedraw (bool block)
50 { 53 {
51 m_redrawBlocked = block; 54 m_redrawBlocked = block;
52 } 55 }
53 56
57 void Canvas::setCursor (MouseMode mode)
58 {
59 QWidget *w = qWidget ();
60
61 if (w)
62 {
63 static QCursor origCursor = w->cursor ();
64
65 switch (mode)
66 {
67 case PanMode:
68 case RotateMode:
69 w->setCursor (Qt::OpenHandCursor);
70 break;
71
72 case ZoomMode:
73 // FIXME: distinguish zoom in/out.
74 w->setCursor (QBitmap (":/images/zoom.png"));
75 break;
76
77 default:
78 w->setCursor (origCursor);
79 break;
80 }
81 }
82 }
83
54 void Canvas::updateCurrentPoint(const graphics_object& fig, 84 void Canvas::updateCurrentPoint(const graphics_object& fig,
55 const graphics_object& obj, QMouseEvent* event) 85 const graphics_object& obj, QMouseEvent* event)
56 { 86 {
57 gh_manager::post_set (fig.get_handle (), "currentpoint", 87 gh_manager::post_set (fig.get_handle (), "currentpoint",
58 Utils::figureCurrentPoint (fig, event), false); 88 Utils::figureCurrentPoint (fig, event), false);
84 false); 114 false);
85 } 115 }
86 } 116 }
87 } 117 }
88 118
119 void Canvas::canvasToggleAxes (const graphics_handle& handle)
120 {
121 gh_manager::auto_lock lock;
122
123 graphics_object go = gh_manager::get_object (handle);
124
125 if (go.valid_object ())
126 {
127 figure::properties& fp = Utils::properties<figure> (go);
128
129 graphics_handle ah = fp.get_currentaxes ();
130
131 graphics_object ax = gh_manager::get_object (ah);
132
133 if (ax.valid_object ())
134 {
135 axes::properties& ap = Utils::properties<axes> (ax);
136
137 if (ap.handlevisibility_is ("on"))
138 {
139 ap.set_visible (! ap.is_visible ());
140
141 redraw (true);
142 }
143 }
144 }
145 }
146
147 void Canvas::canvasToggleGrid (const graphics_handle& handle)
148 {
149 gh_manager::auto_lock lock;
150
151 graphics_object go = gh_manager::get_object (handle);
152
153 if (go.valid_object ())
154 {
155 figure::properties& fp = Utils::properties<figure> (go);
156
157 graphics_handle ah = fp.get_currentaxes ();
158
159 graphics_object ax = gh_manager::get_object (ah);
160
161 if (ax.valid_object ())
162 {
163 axes::properties& ap = Utils::properties<axes> (ax);
164
165 if (ap.handlevisibility_is ("on") && ap.is_visible ())
166 {
167 std::string tmp;
168
169 // If any grid is off, then turn them all on. If they are all
170 // on, then turn them off.
171
172 std::string state = ((ap.get_xgrid () == "off"
173 || ap.get_ygrid () == "off"
174 || ap.get_zgrid () == "off")
175 ? "on" : "off");
176
177 ap.set_xgrid (state);
178 ap.set_ygrid (state);
179 ap.set_zgrid (state);
180
181 redraw (true);
182 }
183 }
184 }
185 }
186
89 void Canvas::canvasPaintEvent (void) 187 void Canvas::canvasPaintEvent (void)
90 { 188 {
91 if (! m_redrawBlocked) 189 if (! m_redrawBlocked)
92 { 190 {
93 gh_manager::auto_lock lock; 191 gh_manager::auto_lock lock;
109 axes::properties& ap = Utils::properties<axes> (ax); 207 axes::properties& ap = Utils::properties<axes> (ax);
110 208
111 switch (m_mouseMode) 209 switch (m_mouseMode)
112 { 210 {
113 case RotateMode: 211 case RotateMode:
114 { 212 {
115 Matrix bb = ap.get_boundingbox (true); 213 ap.rotate3d (m_mouseCurrent.x (), event->x (),
116 Matrix view = ap.get_view ().matrix_value (); 214 m_mouseCurrent.y (), event->y ());
117 215
118 // Compute new view angles 216 // Update current mouse position
119 view(0) += ((m_mouseCurrent.x () - event->x ()) 217 m_mouseCurrent = event->pos ();
120 * (180.0 / bb(2))); 218
121 view(1) += ((event->y () - m_mouseCurrent.y ()) 219 // Force immediate redraw
122 * (180.0 / bb(3))); 220 redraw (true);
123 221 }
124 // Clipping 222 break;
125 view(1) = std::min (view(1), 90.0); 223
126 view(1) = std::max (view(1), -90.0);
127 if (view(0) > 180.0)
128 view(0) -= 360.0;
129 else if (view(0) < -180.0)
130 view(0) += 360.0;
131
132 // Snapping
133 double snapMargin = 1.0;
134 for (int a = -90; a <= 90; a += 90)
135 if ((a - snapMargin) < view(1)
136 && view(1) < (a + snapMargin))
137 {
138 view(1) = a;
139 break;
140 }
141 for (int a = -180; a <= 180; a += 180)
142 if ((a - snapMargin) < view(0)
143 && view(0) < (a + snapMargin))
144 {
145 if (a == 180)
146 view(0) = -180;
147 else
148 view(0) = a;
149 break;
150 }
151
152 // Update axes properties
153 ap.set_view (view);
154
155 // Update current mouse position
156 m_mouseCurrent = event->pos ();
157
158 // Force immediate redraw
159 redraw (true);
160 }
161 break;
162 case ZoomMode: 224 case ZoomMode:
163 m_mouseCurrent = event->pos(); 225 m_mouseCurrent = event->pos ();
164 redraw (true); 226 redraw (true);
165 break; 227 break;
228
166 case PanMode: 229 case PanMode:
167 break; 230 {
231 ColumnVector p0 = ap.pixel2coord (m_mouseCurrent.x (),
232 m_mouseCurrent.y ());
233 ColumnVector p1 = ap.pixel2coord (event->x (),
234 event->y ());
235
236 ap.translate_view ("both", p0(0), p1(0), p0(1), p1(1));
237
238 // Update current mouse position
239 m_mouseCurrent = event->pos ();
240
241 // Force immediate redraw
242 redraw (true);
243 }
244
168 default: 245 default:
169 break; 246 break;
170 } 247 }
171 } 248 }
172 else if (m_mouseMode == NoMode) 249 else if (m_mouseMode == NoMode)
178 graphics_object figObj (obj.get_ancestor ("figure")); 255 graphics_object figObj (obj.get_ancestor ("figure"));
179 256
180 updateCurrentPoint (figObj, obj, event); 257 updateCurrentPoint (figObj, obj, event);
181 gh_manager::post_callback (figObj.get_handle (), 258 gh_manager::post_callback (figObj.get_handle (),
182 "windowbuttonmotionfcn"); 259 "windowbuttonmotionfcn");
260 }
261 }
262 }
263
264 static bool
265 pan_enabled (const graphics_object figObj)
266 {
267 // Getting pan mode property:
268 octave_value ov_pm
269 = Utils::properties<figure> (figObj).get___pan_mode__ ();
270
271 octave_scalar_map pm = ov_pm.scalar_map_value ();
272
273 return pm.contents ("Enable").string_value () == "on";
274 }
275
276 static std::string
277 pan_mode (const graphics_object figObj)
278 {
279 // Getting pan mode property:
280 octave_value ov_pm
281 = Utils::properties<figure> (figObj).get___pan_mode__ ();
282
283 octave_scalar_map pm = ov_pm.scalar_map_value ();
284
285 return pm.contents ("Motion").string_value ();
286 }
287
288 static bool
289 rotate_enabled (const graphics_object figObj)
290 {
291 // Getting rotate mode property:
292 octave_value ov_rm
293 = Utils::properties<figure> (figObj).get___rotate_mode__ ();
294
295 octave_scalar_map rm = ov_rm.scalar_map_value ();
296
297 return rm.contents ("Enable").string_value () == "on";
298 }
299
300 static bool
301 zoom_enabled (const graphics_object figObj)
302 {
303 // Getting zoom mode property:
304 octave_value ov_zm
305 = Utils::properties<figure> (figObj).get___zoom_mode__ ();
306
307 octave_scalar_map zm = ov_zm.scalar_map_value ();
308
309 return zm.contents ("Enable").string_value () == "on";
310 }
311
312 static std::string
313 zoom_mode (const graphics_object figObj)
314 {
315 // Getting zoom mode property:
316 octave_value ov_zm
317 = Utils::properties<figure> (figObj).get___zoom_mode__ ();
318
319 octave_scalar_map zm = ov_zm.scalar_map_value ();
320
321 return zm.contents ("Motion").string_value ();
322 }
323
324 static std::string
325 zoom_direction (const graphics_object figObj)
326 {
327 // Getting zoom mode property:
328 octave_value ov_zm
329 = Utils::properties<figure> (figObj).get___zoom_mode__ ();
330
331 octave_scalar_map zm = ov_zm.scalar_map_value ();
332
333 return zm.contents ("Direction").string_value ();
334 }
335
336 void Canvas::canvasMouseDoubleClickEvent (QMouseEvent* event)
337 {
338 if (event->buttons () != Qt::LeftButton)
339 return;
340
341 gh_manager::auto_lock lock;
342 graphics_object obj = gh_manager::get_object (m_handle);
343
344 if (obj.valid_object ())
345 {
346 graphics_object axesObj;
347
348 Matrix children = obj.get_properties ().get_children ();
349 octave_idx_type num_children = children.numel ();
350
351 for (int i = 0; i < num_children; i++)
352 {
353 graphics_object childObj (gh_manager::get_object (children(i)));
354
355 if (childObj.isa ("axes"))
356 {
357 graphics_object go = selectFromAxes (childObj, event->pos ());
358
359 if (go)
360 {
361 axesObj = childObj;
362 break;
363 }
364 }
365 }
366
367 bool redrawFigure = true;
368
369 if (axesObj)
370 {
371 graphics_object figObj (obj.get_ancestor ("figure"));
372
373 if (axesObj.get_properties ().handlevisibility_is ("on"))
374 {
375 Utils::properties<figure> (figObj)
376 .set_currentaxes (axesObj.get_handle ().as_octave_value ());
377
378 if (pan_enabled (figObj) || rotate_enabled (figObj)
379 || zoom_enabled (figObj))
380 {
381 axes::properties& ap =
382 Utils::properties<axes> (axesObj);
383
384 ap.clear_zoom_stack ();
385 ap.set_xlimmode ("auto");
386 ap.set_ylimmode ("auto");
387 ap.set_zlimmode ("auto");
388 }
389 }
390
391 if (redrawFigure)
392 redraw (false);
183 } 393 }
184 } 394 }
185 } 395 }
186 396
187 void Canvas::canvasMousePressEvent (QMouseEvent* event) 397 void Canvas::canvasMousePressEvent (QMouseEvent* event)
283 "buttondownfcn"); 493 "buttondownfcn");
284 if (event->button () == Qt::RightButton) 494 if (event->button () == Qt::RightButton)
285 ContextMenu::executeAt (currentObj.get_properties (), 495 ContextMenu::executeAt (currentObj.get_properties (),
286 event->globalPos ()); 496 event->globalPos ());
287 break; 497 break;
498
499 case TextMode:
500 // Handle text insertion here.
501 break;
502
503 case PanMode:
288 case RotateMode: 504 case RotateMode:
289 case ZoomMode: 505 case ZoomMode:
290 case PanMode:
291 if (axesObj) 506 if (axesObj)
292 { 507 {
293 if (event->buttons () == Qt::LeftButton 508 if (event->buttons () == Qt::LeftButton
294 && event->modifiers () == Qt::NoModifier) 509 && event->modifiers () == Qt::NoModifier)
295 { 510 {
296 m_mouseAnchor = m_mouseCurrent = event->pos (); 511 m_mouseAnchor = m_mouseCurrent = event->pos ();
297 m_mouseAxes = axesObj.get_handle (); 512 m_mouseAxes = axesObj.get_handle ();
298 m_mouseMode = newMouseMode; 513 m_mouseMode = newMouseMode;
299 } 514 }
300 else if (newMouseMode == ZoomMode 515 else if (event->modifiers () == Qt::NoModifier)
301 && event->modifiers () == Qt::NoModifier)
302 { 516 {
303 switch (event->buttons ()) 517 switch (event->buttons ())
304 { 518 {
305 case Qt::RightButton: 519 case Qt::RightButton:
306 Utils::properties<axes> (axesObj).unzoom (); 520 Utils::properties<axes> (axesObj).unzoom ();
327 } 541 }
328 } 542 }
329 543
330 void Canvas::canvasMouseReleaseEvent (QMouseEvent* event) 544 void Canvas::canvasMouseReleaseEvent (QMouseEvent* event)
331 { 545 {
332 if (m_mouseMode == ZoomMode 546 if (m_mouseMode == ZoomMode && m_mouseAxes.ok ())
333 && m_mouseAxes.ok ()
334 && m_mouseAnchor != event->pos ())
335 { 547 {
336 gh_manager::auto_lock lock; 548 gh_manager::auto_lock lock;
337 graphics_object ax = gh_manager::get_object (m_mouseAxes); 549 graphics_object ax = gh_manager::get_object (m_mouseAxes);
338 550
339 if (ax.valid_object ()) 551 if (ax.valid_object ())
340 { 552 {
341 axes::properties& ap = Utils::properties<axes> (ax); 553 axes::properties& ap = Utils::properties<axes> (ax);
342 554
343 ColumnVector p0 = ap.pixel2coord (m_mouseAnchor.x (), 555 graphics_object obj = gh_manager::get_object (m_handle);
344 m_mouseAnchor.y ()); 556
345 ColumnVector p1 = ap.pixel2coord (event->x (), 557 graphics_object figObj (obj.get_ancestor ("figure"));
346 event->y ()); 558
347 559 std::string zm = zoom_mode (figObj);
348 Matrix xl (1, 2, 0.0); 560
349 Matrix yl (1, 2, 0.0); 561 if (m_mouseAnchor == event->pos ())
350 562 {
351 xl(0) = std::min (p0(0), p1(0)); 563 // FIXME: check direction here.
352 xl(1) = std::max (p0(0), p1(0)); 564
353 yl(0) = std::min (p0(1), p1(1)); 565 double factor = 2.0;
354 yl(1) = std::max (p0(1), p1(1)); 566
355 567 ap.zoom (zm, factor);
356 ap.zoom (xl, yl); 568 }
569 else
570 {
571 ColumnVector p0 = ap.pixel2coord (m_mouseAnchor.x (),
572 m_mouseAnchor.y ());
573 ColumnVector p1 = ap.pixel2coord (event->x (),
574 event->y ());
575
576 Matrix xl (1, 2, 0.0);
577 Matrix yl (1, 2, 0.0);
578
579 xl(0) = std::min (p0(0), p1(0));
580 xl(1) = std::max (p0(0), p1(0));
581 yl(0) = std::min (p0(1), p1(1));
582 yl(1) = std::max (p0(1), p1(1));
583
584 ap.zoom (zm, xl, yl);
585 }
357 586
358 redraw (false); 587 redraw (false);
359 } 588 }
360 } 589 }
361 else if (m_mouseMode == NoMode) 590 else if (m_mouseMode == NoMode)
375 604
376 m_mouseAxes = graphics_handle (); 605 m_mouseAxes = graphics_handle ();
377 m_mouseMode = NoMode; 606 m_mouseMode = NoMode;
378 } 607 }
379 608
609 void Canvas::canvasWheelEvent (QWheelEvent* event)
610 {
611 gh_manager::auto_lock lock;
612 graphics_object obj = gh_manager::get_object (m_handle);
613
614 if (obj.valid_object ())
615 {
616 std::string mode;
617
618 graphics_object axesObj;
619
620 Matrix children = obj.get_properties ().get_children ();
621 octave_idx_type num_children = children.numel ();
622
623 for (int i = 0; i < num_children; i++)
624 {
625 graphics_object childObj (gh_manager::get_object (children(i)));
626
627 if (childObj.isa ("axes"))
628 {
629 graphics_object go = selectFromAxes (childObj, event->pos ());
630
631 if (go)
632 {
633 axesObj = childObj;
634 break;
635 }
636 }
637 }
638
639 if (axesObj)
640 {
641 MouseMode newMouseMode = NoMode;
642
643 graphics_object figObj (obj.get_ancestor ("figure"));
644
645 Figure* fig = dynamic_cast<Figure*> (Backend::toolkitObject (figObj));
646
647 if (fig)
648 newMouseMode = fig->mouseMode ();
649
650 if (axesObj.get_properties ().handlevisibility_is ("on"))
651 {
652 Utils::properties<figure> (figObj)
653 .set_currentaxes (axesObj.get_handle ().as_octave_value ());
654
655 if (zoom_enabled (figObj))
656 {
657 newMouseMode = ZoomMode;
658
659 mode = zoom_mode (figObj);
660 }
661 else if (pan_enabled (figObj))
662 {
663 newMouseMode = PanMode;
664
665 mode = pan_mode (figObj);
666 }
667 }
668
669 bool redrawFigure = true;
670
671 switch (newMouseMode)
672 {
673 case ZoomMode:
674 {
675 axes::properties& ap = Utils::properties<axes> (axesObj);
676
677 double factor = event->delta () > 0 ? 2.0 : 0.5;
678
679 ap.zoom (mode, factor);
680
681 #if 0
682 Matrix view = ap.get_view ().matrix_value ();
683 if (view(1) != 90)
684 {
685 Matrix zlimits = ap.get_zlim ().matrix_value ();
686 zlimits = factor * zlimits;
687 ap.set_zlim (zlimits);
688 }
689 #endif
690 }
691 break;
692
693 case PanMode:
694 {
695 axes::properties& ap = Utils::properties<axes> (axesObj);
696
697 double factor = event->delta () > 0 ? 0.1 : -0.1;
698
699 ap.pan (mode, factor);
700 }
701 break;
702
703 default:
704 redrawFigure = false;
705 break;
706 }
707
708 if (redrawFigure)
709 redraw (false);
710 }
711 }
712 }
713
380 bool Canvas::canvasKeyPressEvent (QKeyEvent* event) 714 bool Canvas::canvasKeyPressEvent (QKeyEvent* event)
381 { 715 {
382 if (m_eventMask & KeyPress) 716 if (m_eventMask & KeyPress)
383 { 717 {
384 octave_scalar_map eventData = Utils::makeKeyEventStruct (event); 718 octave_scalar_map eventData = Utils::makeKeyEventStruct (event);