2675fab85d22ddf23af61d9a274c5396dfa9548d
[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
54 /** Render an area of the canvas.
55  *  @param area Area in canvas coordinates.
56  *  @param context Cairo context to render to.
57  */
58 void
59 Canvas::render (Rect const & area, Cairo::RefPtr<Cairo::Context> const & context) const
60 {
61 #ifdef CANVAS_DEBUG
62         if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
63                 cerr << "RENDER: " << area << endl;
64                 //cerr << "CANVAS @ " << this << endl;
65                 //dump (cerr);
66                 //cerr << "-------------------------\n";
67         }
68 #endif
69
70         render_count = 0;
71         
72         boost::optional<Rect> root_bbox = _root.bounding_box();
73         if (!root_bbox) {
74                 /* the root has no bounding box, so there's nothing to render */
75                 return;
76         }
77
78         boost::optional<Rect> draw = root_bbox->intersection (area);
79         if (draw) {
80                 /* there's a common area between the root and the requested
81                    area, so render it.
82                 */
83
84                 _root.render (*draw, context);
85         }
86
87 #if 0
88         /* debug render area */
89         Rect r = _root.item_to_window (area);
90         context->rectangle (r.x0, r.y0, r.x1 - r.x0, r.y1 - r.y0);
91         context->set_source_rgba (1.0, 0.0, 0.0, 1.0);
92         context->stroke ();
93 #endif
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         : _current_item (0)
246         , _grabbed_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         /* find the items at the given position */
306
307         vector<Item const *> items;
308         _root.add_items_at_point (point, items);
309
310         GdkEventCrossing enter_event;
311         enter_event.type = GDK_ENTER_NOTIFY;
312         enter_event.window = get_window()->gobj();
313         enter_event.send_event = 0;
314         enter_event.subwindow = 0;
315         enter_event.mode = GDK_CROSSING_NORMAL;
316         enter_event.detail = GDK_NOTIFY_NONLINEAR;
317         enter_event.focus = FALSE;
318         enter_event.state = state;
319         enter_event.x = point.x;
320         enter_event.y = point.y;
321
322         GdkEventCrossing leave_event = enter_event;
323         leave_event.type = GDK_LEAVE_NOTIFY;
324         leave_event.detail = GDK_NOTIFY_ANCESTOR;
325         leave_event.subwindow = 0;
326
327         if (items.empty()) {
328                 if (_current_item) {
329                         /* leave event */
330                         cerr << "E/L: left item " << _current_item->whatami() << '/' << _current_item->name << " for ... nada" << endl;
331                         _current_item->Event (reinterpret_cast<GdkEvent*> (&leave_event));
332                         _current_item = 0;
333                 }
334                 return;
335         }
336
337         /* items is sorted from top to bottom, so reverse through it from bottom
338          * to top to find the lowest, first event-sensitive item and notify that
339          * we have entered it
340          */
341
342         cerr << "E/L: " << items.size() << " to check at " << point << endl;
343 #ifdef CANVAS_DEBUG
344         for (vector<Item const*>::const_reverse_iterator i = items.rbegin(); i != items.rend(); ++i) {
345                 cerr << '\t' << (*i)->whatami() << ' ' << (*i)->name << " ignore ? " << (*i)->ignore_events() << " current ? " << (_current_item == (*i)) << endl;
346         }
347 #endif
348         cerr << "------------\n";
349
350         for (vector<Item const*>::const_reverse_iterator i = items.rbegin(); i != items.rend(); ++i) {
351
352                 Item const *  new_item = *i;
353 #ifdef CANVAS_DEBUG
354                 cerr << "\tE/L check out " << new_item->whatami() << ' ' << new_item->name << " ignore ? " << new_item->ignore_events() << " current ? " << (_current_item == new_item) << endl;
355 #endif
356                 if (new_item->ignore_events()) {
357                         // cerr << "continue1\n";
358                         continue;
359                 }
360
361                 if (_current_item == new_item) {
362                         // cerr << "continue2\n";
363                         continue;
364                 }
365
366                 if (_current_item) {
367                         /* leave event */
368                         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("Leave %1 %2\n", _current_item->whatami(), _current_item->name));
369                         _current_item->Event (reinterpret_cast<GdkEvent*> (&leave_event));
370                         queue_draw ();
371                 }
372
373                 if (new_item && _current_item != new_item) {
374                         /* enter event */
375                         _current_item = new_item;
376                         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("Enter %1 %2\n", _current_item->whatami(), _current_item->name));
377                         _current_item->Event (reinterpret_cast<GdkEvent*> (&enter_event));
378                         queue_draw ();
379                         break;
380                 }
381
382                 // cerr << "Loop around again\n";
383         }
384 }
385
386 /** Deliver an event to the appropriate item; either the grabbed item, or
387  *  one of the items underneath the event.
388  *  @param point Position that the event has occurred at, in canvas coordinates.
389  *  @param event The event.
390  */
391 bool
392 GtkCanvas::deliver_event (Duple point, GdkEvent* event)
393 {
394         /* Point in in canvas coordinate space */
395
396         if (_grabbed_item) {
397                 /* we have a grabbed item, so everything gets sent there */
398                 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
399                                                                        _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
400                 return _grabbed_item->Event (event);
401         }
402
403         /* find the items that exist at the event's position */
404         vector<Item const *> items;
405         _root.add_items_at_point (point, items);
406
407         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 possible items to deliver event to\n", items.size()));
408
409         /* run through the items under the event, from top to bottom, until one claims the event */
410         vector<Item const *>::const_reverse_iterator i = items.rbegin ();
411         while (i != items.rend()) {
412
413                 if ((*i)->ignore_events ()) {
414                         DEBUG_TRACE (
415                                 PBD::DEBUG::CanvasEvents,
416                                 string_compose ("canvas event ignored by %1 %2\n", (*i)->whatami(), (*i)->name.empty() ? "[unknown]" : (*i)->name)
417                                 );
418                         ++i;
419                         continue;
420                 }
421                 
422                 if ((*i)->Event (event)) {
423                         /* this item has just handled the event */
424                         DEBUG_TRACE (
425                                 PBD::DEBUG::CanvasEvents,
426                                 string_compose ("canvas event handled by %1 %2\n", (*i)->whatami(), (*i)->name.empty() ? "[unknown]" : (*i)->name)
427                                 );
428                         
429                         return true;
430                 }
431                 
432                 DEBUG_TRACE (
433                         PBD::DEBUG::CanvasEvents,
434                         string_compose ("canvas event left unhandled by %1 %2\n", (*i)->whatami(), (*i)->name.empty() ? "[unknown]" : (*i)->name)
435                         );
436                 
437                 ++i;
438         }
439
440         /* debugging */
441         if (PBD::debug_bits & PBD::DEBUG::CanvasEvents) {
442                 while (i != items.rend()) {
443                         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas event not seen by %1\n", (*i)->name.empty() ? "[unknown]" : (*i)->name));
444                         ++i;
445                 }
446         }
447         
448         return false;
449 }
450
451 /** Called when an item is being destroyed.
452  *  @param item Item being destroyed.
453  *  @param bounding_box Last known bounding box of the item.
454  */
455 void
456 GtkCanvas::item_going_away (Item* item, boost::optional<Rect> bounding_box)
457 {
458         if (bounding_box) {
459                 queue_draw_item_area (item, bounding_box.get ());
460         }
461         
462         if (_current_item == item) {
463                 _current_item = 0;
464                 queue_draw ();
465         }
466
467         if (_grabbed_item == item) {
468                 _grabbed_item = 0;
469         }
470
471         enter_leave_items (0); // no mouse state
472         
473 }
474
475 /** Handler for GDK expose events.
476  *  @param ev Event.
477  *  @return true if the event was handled.
478  */
479 bool
480 GtkCanvas::on_expose_event (GdkEventExpose* ev)
481 {
482         Cairo::RefPtr<Cairo::Context> c = get_window()->create_cairo_context ();
483
484         render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), c);
485
486 #if 1
487         if (_current_item) {
488                 boost::optional<Rect> orect = _current_item->bounding_box();
489                 if (orect) {
490                         Rect r = _current_item->item_to_window (orect.get());
491                         c->rectangle (r.x0, r.y0, r.x1 - r.x0, r.y1 - r.y0);
492                         c->set_source_rgba (1.0, 0.0, 0.0, 1.0);
493                         c->stroke ();
494                 }
495         }
496 #endif
497
498
499         return true;
500 }
501
502 /** @return Our Cairo context, or 0 if we don't have one */
503 Cairo::RefPtr<Cairo::Context>
504 GtkCanvas::context ()
505 {
506         Glib::RefPtr<Gdk::Window> w = get_window ();
507         if (!w) {
508                 return Cairo::RefPtr<Cairo::Context> ();
509         }
510
511         return w->create_cairo_context ();
512 }
513
514 /** Handler for GDK button press events.
515  *  @param ev Event.
516  *  @return true if the event was handled.
517  */
518 bool
519 GtkCanvas::on_button_press_event (GdkEventButton* ev)
520 {
521         /* translate event coordinates from window to canvas */
522
523         Duple where = window_to_canvas (Duple (ev->x, ev->y));
524                                  
525         /* Coordinates in the event will be canvas coordinates, correctly adjusted
526            for scroll if this GtkCanvas is in a GtkCanvasViewport.
527         */
528
529         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press @ %1, %2 => %3\n", ev->x, ev->y, where));
530         return deliver_event (where, reinterpret_cast<GdkEvent*>(ev));
531 }
532
533 /** Handler for GDK button release events.
534  *  @param ev Event.
535  *  @return true if the event was handled.
536  */
537 bool
538 GtkCanvas::on_button_release_event (GdkEventButton* ev)
539 {       
540         /* translate event coordinates from window to canvas */
541
542         Duple where = window_to_canvas (Duple (ev->x, ev->y));
543
544         /* Coordinates in the event will be canvas coordinates, correctly adjusted
545            for scroll if this GtkCanvas is in a GtkCanvasViewport.
546         */
547
548         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release @ %1, %2 => %3\n", ev->x, ev->y, where));
549         return deliver_event (where, reinterpret_cast<GdkEvent*>(ev));
550 }
551
552 /** Handler for GDK motion events.
553  *  @param ev Event.
554  *  @return true if the event was handled.
555  */
556 bool
557 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
558 {
559         /* translate event coordinates from window to canvas */
560
561         GdkEvent copy = *((GdkEvent*)ev);
562         Duple where = window_to_canvas (Duple (ev->x, ev->y));
563
564         copy.motion.x = where.x;
565         copy.motion.y = where.y;
566
567         /* Coordinates in the event will be canvas coordinates, correctly adjusted
568            for scroll if this GtkCanvas is in a GtkCanvasViewport.
569         */
570         return motion_notify_handler ((GdkEventMotion*) &copy);
571 }
572
573 /** Called to request a redraw of our canvas.
574  *  @param area Area to redraw, in canvas coordinates.
575  */
576 void
577 GtkCanvas::request_redraw (Rect const & request)
578 {
579         Rect area = canvas_to_window (request);
580         // cerr << this << " Invalidate " << request << " TRANSLATE AS " << area << endl;
581         queue_draw_area (floor (area.x0), floor (area.y0), ceil (area.x1) - floor (area.x0), ceil (area.y1) - floor (area.y0));
582 }
583
584 /** Called to request that we try to get a particular size for ourselves.
585  *  @param size Size to request, in pixels.
586  */
587 void
588 GtkCanvas::request_size (Duple size)
589 {
590         Duple req = size;
591
592         if (req.x > INT_MAX) {
593                 req.x = INT_MAX;
594         }
595
596         if (req.y > INT_MAX) {
597                 req.y = INT_MAX;
598         }
599
600         set_size_request (req.x, req.y);
601 }
602
603 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
604  *  This is typically used for dragging items around, so that they are grabbed during
605  *  the drag.
606  *  @param item Item to grab.
607  */
608 void
609 GtkCanvas::grab (Item* item)
610 {
611         /* XXX: should this be doing gdk_pointer_grab? */
612         _grabbed_item = item;
613 }
614
615 /** `Ungrab' any item that was previously grabbed */
616 void
617 GtkCanvas::ungrab ()
618 {
619         /* XXX: should this be doing gdk_pointer_ungrab? */
620         _grabbed_item = 0;
621 }
622
623 /** @return The visible area of the canvas, in canvas coordinates */
624 Rect
625 GtkCanvas::visible_area () const
626 {
627         Distance const xo = _scroll_offset_x;
628         Distance const yo = _scroll_offset_y;
629         return Rect (xo, yo, xo + get_allocation().get_width (), yo + get_allocation().get_height ());
630 }
631
632 /** Create a GtkCanvaSViewport.
633  *  @param hadj Adjustment to use for horizontal scrolling.
634  *  @param vadj Adjustment to use for vertica scrolling.
635  */
636 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
637         : Alignment (0, 0, 1.0, 1.0)
638         , hadjustment (hadj)
639         , vadjustment (vadj)
640 {
641         add (_canvas);
642
643         hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
644         vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
645 }
646
647 void
648 GtkCanvasViewport::scrolled ()
649 {
650         _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
651         queue_draw ();
652 }
653
654 /** Handler for when GTK asks us what minimum size we want.
655  *  @param req Requsition to fill in.
656  */
657 void
658 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
659 {
660         /* force the canvas to size itself */
661         // _canvas.root()->bounding_box(); 
662
663         req->width = 16;
664         req->height = 16;
665 }
666