handle enter/leave items when zooming and scrolling occur
[ardour.git] / libs / canvas / canvas.cc
1 /*
2     Copyright (C) 2011 Paul Davis
3     Author: Carl Hetherington <cth@carlh.net>
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 /** @file  canvas/canvas.cc
22  *  @brief Implementation of the main canvas classes.
23  */
24
25 #include <cassert>
26 #include <gtkmm/adjustment.h>
27 #include <gtkmm/label.h>
28
29 #include "pbd/compose.h"
30 #include "pbd/stacktrace.h"
31
32 #include "canvas/canvas.h"
33 #include "canvas/debug.h"
34
35 using namespace std;
36 using namespace ArdourCanvas;
37
38 /** Construct a new Canvas */
39 Canvas::Canvas ()
40         : _root (this)
41         , _scroll_offset_x (0)
42         , _scroll_offset_y (0)
43 {
44         set_epoch ();
45 }
46
47 void
48 Canvas::scroll_to (Coord x, Coord y)
49 {
50         _scroll_offset_x = x;
51         _scroll_offset_y = y;
52
53         enter_leave_items (0); // no current mouse position 
54 }
55
56 void
57 Canvas::zoomed ()
58 {
59         enter_leave_items (0); // no current mouse position
60 }
61
62 /** Render an area of the canvas.
63  *  @param area Area in canvas coordinates.
64  *  @param context Cairo context to render to.
65  */
66 void
67 Canvas::render (Rect const & area, Cairo::RefPtr<Cairo::Context> const & context) const
68 {
69 #ifdef CANVAS_DEBUG
70         if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
71                 cerr << "RENDER: " << area << endl;
72                 //cerr << "CANVAS @ " << this << endl;
73                 //dump (cerr);
74                 //cerr << "-------------------------\n";
75         }
76 #endif
77
78         render_count = 0;
79         
80         boost::optional<Rect> root_bbox = _root.bounding_box();
81         if (!root_bbox) {
82                 /* the root has no bounding box, so there's nothing to render */
83                 return;
84         }
85
86         boost::optional<Rect> draw = root_bbox->intersection (area);
87         if (draw) {
88                 /* there's a common area between the root and the requested
89                    area, so render it.
90                 */
91
92                 _root.render (*draw, context);
93         }
94 }
95
96 ostream&
97 operator<< (ostream& o, Canvas& c)
98 {
99         c.dump (o);
100         return o;
101 }
102
103 std::string
104 Canvas::indent() const
105
106         string s;
107
108         for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
109                 s += '\t';
110         }
111
112         return s;
113 }
114
115 std::string
116 Canvas::render_indent() const
117
118         string s;
119
120         for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
121                 s += ' ';
122         }
123
124         return s;
125 }
126
127 void
128 Canvas::dump (ostream& o) const
129 {
130         dump_depth = 0;
131         _root.dump (o);
132 }       
133
134 /** Called when an item has been shown or hidden.
135  *  @param item Item that has been shown or hidden.
136  */
137 void
138 Canvas::item_shown_or_hidden (Item* item)
139 {
140         boost::optional<Rect> bbox = item->bounding_box ();
141         if (bbox) {
142                 queue_draw_item_area (item, bbox.get ());
143         }
144 }
145
146 /** Called when an item has a change to its visual properties
147  *  that do NOT affect its bounding box.
148  *  @param item Item that has been modified.
149  */
150 void
151 Canvas::item_visual_property_changed (Item* item)
152 {
153         boost::optional<Rect> bbox = item->bounding_box ();
154         if (bbox) {
155                 queue_draw_item_area (item, bbox.get ());
156         }
157 }
158
159 /** Called when an item has changed, but not moved.
160  *  @param item Item that has changed.
161  *  @param pre_change_bounding_box The bounding box of item before the change,
162  *  in the item's coordinates.
163  */
164 void
165 Canvas::item_changed (Item* item, boost::optional<Rect> pre_change_bounding_box)
166 {
167         if (pre_change_bounding_box) {
168                 /* request a redraw of the item's old bounding box */
169                 queue_draw_item_area (item, pre_change_bounding_box.get ());
170         }
171
172         boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
173         if (post_change_bounding_box) {
174                 /* request a redraw of the item's new bounding box */
175                 queue_draw_item_area (item, post_change_bounding_box.get ());
176         }
177 }
178
179 Duple
180 Canvas::window_to_canvas (Duple const & d) const
181 {
182         return d.translate (Duple (_scroll_offset_x, _scroll_offset_y));
183 }
184
185 Duple
186 Canvas::canvas_to_window (Duple const & d) const
187 {
188         return d.translate (Duple (-_scroll_offset_x, -_scroll_offset_y));
189 }       
190
191 Rect
192 Canvas::window_to_canvas (Rect const & r) const
193 {
194         return r.translate (Duple (_scroll_offset_x, _scroll_offset_y));
195 }
196
197 Rect
198 Canvas::canvas_to_window (Rect const & r) const
199 {
200         return r.translate (Duple (-_scroll_offset_x, -_scroll_offset_y));
201 }       
202
203 /** Called when an item has moved.
204  *  @param item Item that has moved.
205  *  @param pre_change_parent_bounding_box The bounding box of the item before
206  *  the move, in its parent's coordinates.
207  */
208 void
209 Canvas::item_moved (Item* item, boost::optional<Rect> pre_change_parent_bounding_box)
210 {
211         if (pre_change_parent_bounding_box) {
212                 /* request a redraw of where the item used to be. The box has
213                  * to be in parent coordinate space since the bounding box of
214                  * an item does not change when moved. If we use
215                  * item->item_to_canvas() on the old bounding box, we will be
216
217                  * using the item's new position, and so will compute the wrong
218                  * invalidation area. If we use the parent (which has not
219                  * moved, then this will work.
220                  */
221                 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box.get ());
222         }
223
224         boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
225         if (post_change_bounding_box) {
226                 /* request a redraw of where the item now is */
227                 queue_draw_item_area (item, post_change_bounding_box.get ());
228         }
229 }
230
231 /** Request a redraw of a particular area in an item's coordinates.
232  *  @param item Item.
233  *  @param area Area to redraw in the item's coordinates.
234  */
235 void
236 Canvas::queue_draw_item_area (Item* item, Rect area)
237 {
238         ArdourCanvas::Rect canvas_area = item->item_to_canvas (area);
239         // cerr << "CANVAS " << this << " for " << item->whatami() << ' ' << item->name << " invalidate " << area << " TRANSLATE AS " << canvas_area << endl;
240         request_redraw (canvas_area);
241 }
242
243 /** Construct a GtkCanvas */
244 GtkCanvas::GtkCanvas ()
245         : _grabbed_item (0)
246         , _focused_item (0)
247 {
248         /* these are the events we want to know about */
249         add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK);
250 }
251
252 /** Handler for pointer motion events on the canvas.
253  *  @param ev GDK event.
254  *  @return true if the motion event was handled, otherwise false.
255  */
256 bool
257 GtkCanvas::motion_notify_handler (GdkEventMotion* ev)
258 {
259         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas motion @ %1, %2\n", ev->x, ev->y));
260
261         if (_grabbed_item) {
262                 /* if we have a grabbed item, it gets just the motion event,
263                    since no enter/leave events can have happened.
264                 */
265                 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send MOTION event there\n",
266                                                                        _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
267                 return _grabbed_item->Event (reinterpret_cast<GdkEvent*> (ev));
268         }
269
270         Duple point (ev->x, ev->y);
271         
272         enter_leave_items (point, ev->state);
273
274         /* Now deliver the motion event.  It may seem a little inefficient
275            to recompute the items under the event, but the enter notify/leave
276            events may have deleted canvas items so it is important to
277            recompute the list in deliver_event.
278         */
279         return deliver_event (point, reinterpret_cast<GdkEvent*> (ev));
280 }
281
282 void
283 GtkCanvas::enter_leave_items (int state)
284 {
285         int x;
286         int y;
287
288         /* this version of ::enter_leave_items() is called after an item is
289          * added or removed, so we have no coordinates to work from as is the
290          * case with a motion event. Find out where the mouse is and use that.
291          */
292              
293         Glib::RefPtr<const Gdk::Window> pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y);
294
295         if (pointer_window != get_window()) {
296                 return;
297         }
298
299         enter_leave_items (window_to_canvas (Duple (x, y)), state);
300 }
301                 
302 void
303 GtkCanvas::enter_leave_items (Duple const & point, int state)
304 {
305         /* we do not enter/leave items during a drag/grab */
306
307         if (_grabbed_item) {
308                 return;
309         }
310
311         GdkEventCrossing enter_event;
312         enter_event.type = GDK_ENTER_NOTIFY;
313         enter_event.window = get_window()->gobj();
314         enter_event.send_event = 0;
315         enter_event.subwindow = 0;
316         enter_event.mode = GDK_CROSSING_NORMAL;
317         enter_event.detail = GDK_NOTIFY_NONLINEAR;
318         enter_event.focus = FALSE;
319         enter_event.state = state;
320         enter_event.x = point.x;
321         enter_event.y = point.y;
322         enter_event.detail = GDK_NOTIFY_UNKNOWN;
323
324         GdkEventCrossing leave_event = enter_event;
325         leave_event.type = GDK_LEAVE_NOTIFY;
326
327         /* find the items at the given position */
328
329         vector<Item const *> items;
330         _root.add_items_at_point (point, items);
331
332         /* put all items at point that are event-sensitive and visible into within_items, and if this
333            is a new addition, also put them into newly_entered for later deliver of enter events.
334         */
335         
336         vector<Item const *>::const_iterator i;
337         vector<Item const *> newly_entered;
338         Item const * new_item;
339
340         for (i = items.begin(); i != items.end(); ++i) {
341
342                 new_item = *i;
343                 
344                 if (new_item->ignore_events() || !new_item->visible()) {
345                         continue;
346                 }
347
348                 pair<set<Item const *>::iterator,bool> res = within_items.insert (new_item);
349
350                 if (res.second) {
351                         newly_entered.push_back (new_item);
352                 }
353         }
354
355         /* for every item in "within_items", check that we are still within them. if not,
356            send a leave event, and remove them from "within_items"
357         */
358
359         for (set<Item const *>::const_iterator i = within_items.begin(); i != within_items.end(); ) {
360
361                 set<Item const *>::const_iterator tmp = i;
362                 ++tmp;
363
364                 new_item = *i;
365
366                 boost::optional<Rect> bbox = new_item->bounding_box();
367
368
369                 if (bbox) {
370                         if (!new_item->item_to_canvas (bbox.get()).contains (point)) {
371                                 leave_event.detail = GDK_NOTIFY_UNKNOWN;
372                                 cerr << string_compose ("\tLeave %1 %2\n", new_item->whatami(), new_item->name);
373                                 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("Leave %1 %2\n", new_item->whatami(), new_item->name));
374                                 (*i)->Event (reinterpret_cast<GdkEvent*> (&leave_event));
375                                 within_items.erase (i);
376                         }
377                 }
378
379                 i = tmp;
380         }
381         
382         /* for every item in "newly_entered", send an enter event (and propagate it up the
383            item tree until it is handled 
384         */
385
386         for (vector<Item const*>::const_iterator i = newly_entered.begin(); i != newly_entered.end(); ++i) {
387                 new_item = *i;
388
389
390                 if (new_item->Event (reinterpret_cast<GdkEvent*> (&enter_event))) {
391                         cerr << string_compose ("\tEntered %1 %2\n", new_item->whatami(), new_item->name);
392                         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("Enter %1 %2\n", new_item->whatami(), new_item->name));
393                         break;
394                 }
395         }
396
397 #if 0
398         cerr << "Within:\n";
399         for (set<Item const *>::const_iterator i = within_items.begin(); i != within_items.end(); ++i) {
400                 cerr << '\t' << (*i)->whatami() << '/' << (*i)->name << endl;
401         }
402         cerr << "----\n";
403 #endif
404 }
405
406 /** Deliver an event to the appropriate item; either the grabbed item, or
407  *  one of the items underneath the event.
408  *  @param point Position that the event has occurred at, in canvas coordinates.
409  *  @param event The event.
410  */
411 bool
412 GtkCanvas::deliver_event (Duple point, GdkEvent* event)
413 {
414         /* Point in in canvas coordinate space */
415
416         if (_grabbed_item) {
417                 /* we have a grabbed item, so everything gets sent there */
418                 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
419                                                                        _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
420                 return _grabbed_item->Event (event);
421         }
422
423         /* find the items that exist at the event's position */
424         vector<Item const *> items;
425         _root.add_items_at_point (point, items);
426
427         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 possible items to deliver event to\n", items.size()));
428
429         /* run through the items under the event, from top to bottom, until one claims the event */
430         vector<Item const *>::const_reverse_iterator i = items.rbegin ();
431         while (i != items.rend()) {
432
433                 if ((*i)->ignore_events ()) {
434                         DEBUG_TRACE (
435                                 PBD::DEBUG::CanvasEvents,
436                                 string_compose ("canvas event ignored by %1 %2\n", (*i)->whatami(), (*i)->name.empty() ? "[unknown]" : (*i)->name)
437                                 );
438                         ++i;
439                         continue;
440                 }
441                 
442                 if ((*i)->Event (event)) {
443                         /* this item has just handled the event */
444                         DEBUG_TRACE (
445                                 PBD::DEBUG::CanvasEvents,
446                                 string_compose ("canvas event handled by %1 %2\n", (*i)->whatami(), (*i)->name.empty() ? "[unknown]" : (*i)->name)
447                                 );
448                         
449                         return true;
450                 }
451                 
452                 DEBUG_TRACE (
453                         PBD::DEBUG::CanvasEvents,
454                         string_compose ("canvas event left unhandled by %1 %2\n", (*i)->whatami(), (*i)->name.empty() ? "[unknown]" : (*i)->name)
455                         );
456                 
457                 ++i;
458         }
459
460         /* debugging */
461         if (PBD::debug_bits & PBD::DEBUG::CanvasEvents) {
462                 while (i != items.rend()) {
463                         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas event not seen by %1\n", (*i)->name.empty() ? "[unknown]" : (*i)->name));
464                         ++i;
465                 }
466         }
467         
468         return false;
469 }
470
471 /** Called when an item is being destroyed.
472  *  @param item Item being destroyed.
473  *  @param bounding_box Last known bounding box of the item.
474  */
475 void
476 GtkCanvas::item_going_away (Item* item, boost::optional<Rect> bounding_box)
477 {
478         if (bounding_box) {
479                 queue_draw_item_area (item, bounding_box.get ());
480         }
481         
482         /* no need to send a leave event to this item, since it is going away 
483          */
484
485         within_items.erase (item);
486
487         if (_grabbed_item == item) {
488                 _grabbed_item = 0;
489         }
490
491         if (_focused_item == item) {
492                 _focused_item = 0;
493         }
494
495         enter_leave_items (0); // no mouse state
496         
497 }
498
499 /** Handler for GDK expose events.
500  *  @param ev Event.
501  *  @return true if the event was handled.
502  */
503 bool
504 GtkCanvas::on_expose_event (GdkEventExpose* ev)
505 {
506         Cairo::RefPtr<Cairo::Context> c = get_window()->create_cairo_context ();
507
508         render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), c);
509
510         return true;
511 }
512
513 /** @return Our Cairo context, or 0 if we don't have one */
514 Cairo::RefPtr<Cairo::Context>
515 GtkCanvas::context ()
516 {
517         Glib::RefPtr<Gdk::Window> w = get_window ();
518         if (!w) {
519                 return Cairo::RefPtr<Cairo::Context> ();
520         }
521
522         return w->create_cairo_context ();
523 }
524
525 /** Handler for GDK button press events.
526  *  @param ev Event.
527  *  @return true if the event was handled.
528  */
529 bool
530 GtkCanvas::on_button_press_event (GdkEventButton* ev)
531 {
532         /* translate event coordinates from window to canvas */
533
534         Duple where = window_to_canvas (Duple (ev->x, ev->y));
535                                  
536         /* Coordinates in the event will be canvas coordinates, correctly adjusted
537            for scroll if this GtkCanvas is in a GtkCanvasViewport.
538         */
539
540         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press @ %1, %2 => %3\n", ev->x, ev->y, where));
541         return deliver_event (where, reinterpret_cast<GdkEvent*>(ev));
542 }
543
544 /** Handler for GDK button release events.
545  *  @param ev Event.
546  *  @return true if the event was handled.
547  */
548 bool
549 GtkCanvas::on_button_release_event (GdkEventButton* ev)
550 {       
551         /* translate event coordinates from window to canvas */
552
553         Duple where = window_to_canvas (Duple (ev->x, ev->y));
554
555         /* Coordinates in the event will be canvas coordinates, correctly adjusted
556            for scroll if this GtkCanvas is in a GtkCanvasViewport.
557         */
558
559         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release @ %1, %2 => %3\n", ev->x, ev->y, where));
560         return deliver_event (where, reinterpret_cast<GdkEvent*>(ev));
561 }
562
563 /** Handler for GDK motion events.
564  *  @param ev Event.
565  *  @return true if the event was handled.
566  */
567 bool
568 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
569 {
570         /* translate event coordinates from window to canvas */
571
572         GdkEvent copy = *((GdkEvent*)ev);
573         Duple where = window_to_canvas (Duple (ev->x, ev->y));
574
575         copy.motion.x = where.x;
576         copy.motion.y = where.y;
577
578         /* Coordinates in the event will be canvas coordinates, correctly adjusted
579            for scroll if this GtkCanvas is in a GtkCanvasViewport.
580         */
581         return motion_notify_handler ((GdkEventMotion*) &copy);
582 }
583
584 bool
585 GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
586 {
587         Duple where = window_to_canvas (Duple (ev->x, ev->y));
588         enter_leave_items (where, ev->state);
589         return true;
590 }
591
592 bool
593 GtkCanvas::on_leave_notify_event (GdkEventCrossing* /*ev*/)
594 {
595         cerr << "Clear all within items as we leave\n";
596         within_items.clear ();
597         return true;
598 }
599
600 /** Called to request a redraw of our canvas.
601  *  @param area Area to redraw, in canvas coordinates.
602  */
603 void
604 GtkCanvas::request_redraw (Rect const & request)
605 {
606         Rect area = canvas_to_window (request);
607         // cerr << this << " Invalidate " << request << " TRANSLATE AS " << area << endl;
608         queue_draw_area (floor (area.x0), floor (area.y0), ceil (area.x1) - floor (area.x0), ceil (area.y1) - floor (area.y0));
609 }
610
611 /** Called to request that we try to get a particular size for ourselves.
612  *  @param size Size to request, in pixels.
613  */
614 void
615 GtkCanvas::request_size (Duple size)
616 {
617         Duple req = size;
618
619         if (req.x > INT_MAX) {
620                 req.x = INT_MAX;
621         }
622
623         if (req.y > INT_MAX) {
624                 req.y = INT_MAX;
625         }
626
627         set_size_request (req.x, req.y);
628 }
629
630 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
631  *  This is typically used for dragging items around, so that they are grabbed during
632  *  the drag.
633  *  @param item Item to grab.
634  */
635 void
636 GtkCanvas::grab (Item* item)
637 {
638         /* XXX: should this be doing gdk_pointer_grab? */
639         _grabbed_item = item;
640 }
641
642
643 /** `Ungrab' any item that was previously grabbed */
644 void
645 GtkCanvas::ungrab ()
646 {
647         /* XXX: should this be doing gdk_pointer_ungrab? */
648         _grabbed_item = 0;
649 }
650
651 /** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
652  *  moves elsewhere.
653  *  @param item Item to grab.
654  */
655 void
656 GtkCanvas::focus (Item* item)
657 {
658         _focused_item = item;
659 }
660
661 void
662 GtkCanvas::unfocus (Item* item)
663 {
664         if (item == _focused_item) {
665                 _focused_item = 0;
666         }
667 }
668
669 /** @return The visible area of the canvas, in canvas coordinates */
670 Rect
671 GtkCanvas::visible_area () const
672 {
673         Distance const xo = _scroll_offset_x;
674         Distance const yo = _scroll_offset_y;
675         return Rect (xo, yo, xo + get_allocation().get_width (), yo + get_allocation().get_height ());
676 }
677
678 /** Create a GtkCanvaSViewport.
679  *  @param hadj Adjustment to use for horizontal scrolling.
680  *  @param vadj Adjustment to use for vertica scrolling.
681  */
682 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
683         : Alignment (0, 0, 1.0, 1.0)
684         , hadjustment (hadj)
685         , vadjustment (vadj)
686 {
687         add (_canvas);
688
689         hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
690         vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
691 }
692
693 void
694 GtkCanvasViewport::scrolled ()
695 {
696         _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
697         queue_draw ();
698 }
699
700 /** Handler for when GTK asks us what minimum size we want.
701  *  @param req Requsition to fill in.
702  */
703 void
704 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
705 {
706         /* force the canvas to size itself */
707         // _canvas.root()->bounding_box(); 
708
709         req->width = 16;
710         req->height = 16;
711 }
712