da34a73c211953d434685b09fa96a5492b91fce2
[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/xml++.h"
30 #include "pbd/compose.h"
31 #include "pbd/stacktrace.h"
32
33 #include "canvas/canvas.h"
34 #include "canvas/debug.h"
35
36 using namespace std;
37 using namespace ArdourCanvas;
38
39 /** Construct a new Canvas */
40 Canvas::Canvas ()
41         : _root (this)
42         , _log_renders (true)
43         , _scroll_offset_x (0)
44         , _scroll_offset_y (0)
45 {
46         set_epoch ();
47 }
48
49 /** Construct a new Canvas from an XML tree
50  *  @param tree XML Tree.
51  */
52 Canvas::Canvas (XMLTree const * tree)
53         : _root (this)
54         , _log_renders (true)
55         , _scroll_offset_x (0)
56         , _scroll_offset_y (0)
57 {
58         set_epoch ();
59         
60         /* XXX: little bit hacky */
61         _root.set_state (tree->root()->child ("Group"));
62
63         XMLNodeList const & children = tree->root()->children ();
64         for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
65                 if ((*i)->name() == ("Render")) {
66                         _renders.push_back (
67                                 Rect (
68                                         atof ((*i)->property ("x0")->value().c_str()),
69                                         atof ((*i)->property ("y0")->value().c_str()),
70                                         atof ((*i)->property ("x1")->value().c_str()),
71                                         atof ((*i)->property ("x1")->value().c_str())
72                                         )
73                                 );
74                 }
75         }
76 }
77
78 void
79 Canvas::scroll_to (Coord x, Coord y)
80 {
81         _scroll_offset_x = x;
82         _scroll_offset_y = y;
83 }
84
85 /** Render an area of the canvas.
86  *  @param area Area in canvas coordinates.
87  *  @param context Cairo context to render to.
88  */
89 void
90 Canvas::render (Rect const & area, Cairo::RefPtr<Cairo::Context> const & context) const
91 {
92 #ifdef CANVAS_DEBUG
93         if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
94                 cerr << "CANVAS @ " << this << endl;
95                 dump (cerr);
96                 cerr << "-------------------------\n";
97         }
98 #endif
99
100         // checkpoint ("render", "-> render");
101         render_count = 0;
102         
103         context->save ();
104
105         /* clip to the requested area */
106         context->rectangle (area.x0, area.y0, area.width(), area.height());
107         context->clip ();
108
109         boost::optional<Rect> root_bbox = _root.bounding_box();
110         if (!root_bbox) {
111                 /* the root has no bounding box, so there's nothing to render */
112                 // checkpoint ("render", "no root bbox");
113                 context->restore ();
114                 return;
115         }
116
117         boost::optional<Rect> draw = root_bbox.get().intersection (area);
118         if (draw) {
119                 /* there's a common area between the root and the requested
120                    area, so render it.
121                 */
122                 // checkpoint ("render", "... root");
123                 context->stroke ();
124
125                 _root.render (*draw, context);
126         }
127
128         if (_log_renders) {
129                 _renders.push_back (area);
130         }
131
132         context->restore ();
133
134 #ifdef CANVAS_DEBUG
135         if (getenv ("ARDOUR_HARLEQUIN_CANVAS")) {
136                 /* light up the canvas to show redraws */
137                 context->set_source_rgba (random()%255 / 255.0,
138                                           random()%255 / 255.0,
139                                           random()%255 / 255.0,
140                                           255);
141                 context->rectangle (area.x0, area.y0, area.width(), area.height());
142                 context->fill ();
143         }
144 #endif
145         // checkpoint ("render", "<- render");
146 }
147
148 ostream&
149 operator<< (ostream& o, Canvas& c)
150 {
151         c.dump (o);
152         return o;
153 }
154
155 std::string
156 Canvas::indent() const
157
158         string s;
159
160         for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
161                 s += '\t';
162         }
163
164         return s;
165 }
166
167 std::string
168 Canvas::render_indent() const
169
170         string s;
171
172         for (int n = 0; n < ArdourCanvas::render_depth; ++n) {
173                 s += ' ';
174         }
175
176         return s;
177 }
178
179 void
180 Canvas::dump (ostream& o) const
181 {
182         dump_depth = 0;
183         _root.dump (o);
184 }       
185
186 /** Called when an item has been shown or hidden.
187  *  @param item Item that has been shown or hidden.
188  */
189 void
190 Canvas::item_shown_or_hidden (Item* item)
191 {
192         boost::optional<Rect> bbox = item->bounding_box ();
193         if (bbox) {
194                 queue_draw_item_area (item, bbox.get ());
195         }
196 }
197
198 /** Called when an item has changed, but not moved.
199  *  @param item Item that has changed.
200  *  @param pre_change_bounding_box The bounding box of item before the change,
201  *  in the item's coordinates.
202  */
203 void
204 Canvas::item_changed (Item* item, boost::optional<Rect> pre_change_bounding_box)
205 {
206         if (pre_change_bounding_box) {
207                 /* request a redraw of the item's old bounding box */
208                 queue_draw_item_area (item, pre_change_bounding_box.get ());
209         }
210
211         boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
212         if (post_change_bounding_box) {
213                 /* request a redraw of the item's new bounding box */
214                 queue_draw_item_area (item, post_change_bounding_box.get ());
215         }
216 }
217
218 /** Called when an item has moved.
219  *  @param item Item that has moved.
220  *  @param pre_change_parent_bounding_box The bounding box of the item before
221  *  the move, in its parent's coordinates.
222  */
223 void
224 Canvas::item_moved (Item* item, boost::optional<Rect> pre_change_parent_bounding_box)
225 {
226         if (pre_change_parent_bounding_box) {
227                 /* request a redraw of where the item used to be. The box has
228                  * to be in parent coordinate space since the bounding box of
229                  * an item does not change when moved. If we use
230                  * item->item_to_canvas() on the old bounding box, we will be
231                  * using the item's new position, and so will compute the wrong
232                  * invalidation area. If we use the parent (which has not
233                  * moved, then this will work.
234                  */
235
236                 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box.get ());
237         }
238
239         boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
240         if (post_change_bounding_box) {
241                 /* request a redraw of where the item now is */
242                 queue_draw_item_area (item, post_change_bounding_box.get ());
243         }
244 }
245
246 /** Request a redraw of a particular area in an item's coordinates.
247  *  @param item Item.
248  *  @param area Area to redraw in the item's coordinates.
249  */
250 void
251 Canvas::queue_draw_item_area (Item* item, Rect area)
252 {
253         ArdourCanvas::Rect canvas_area = item->item_to_canvas (area);
254         // cerr << "CANVAS Invalidate " << area << " TRANSLATE AS " << canvas_area << endl;
255         request_redraw (canvas_area);
256 }
257
258 /** @return An XML description of the canvas and its objects */
259 XMLTree *
260 Canvas::get_state () const
261 {
262         XMLTree* tree = new XMLTree ();
263         XMLNode* node = new XMLNode ("Canvas");
264         node->add_child_nocopy (*_root.get_state ());
265
266         for (list<Rect>::const_iterator i = _renders.begin(); i != _renders.end(); ++i) {
267                 XMLNode* render = new XMLNode ("Render");
268                 render->add_property ("x0", string_compose ("%1", i->x0));
269                 render->add_property ("y0", string_compose ("%1", i->y0));
270                 render->add_property ("x1", string_compose ("%1", i->x1));
271                 render->add_property ("y1", string_compose ("%1", i->y1));
272                 node->add_child_nocopy (*render);
273         }
274
275         tree->set_root (node);
276         return tree;
277 }
278
279 /** Construct a GtkCanvas */
280 GtkCanvas::GtkCanvas ()
281         : _current_item (0)
282         , _grabbed_item (0)
283 {
284         /* these are the events we want to know about */
285         add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK);
286 }
287
288 /** Construct a GtkCanvas from an XML tree.
289  *  @param tree XML Tree.
290  */
291 GtkCanvas::GtkCanvas (XMLTree const * tree)
292         : Canvas (tree)
293         , _current_item (0)
294         , _grabbed_item (0)
295 {
296         /* these are the events we want to know about */
297         add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK);
298 }
299
300 /** Handler for button presses on the canvas.
301  *  @param ev GDK event.
302  */
303 bool
304 GtkCanvas::button_handler (GdkEventButton* ev)
305 {
306         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button %3 %1 %1\n", ev->x, ev->y, (ev->type == GDK_BUTTON_PRESS ? "press" : "release")));
307         /* The Duple that we are passing in here is in canvas coordinates */
308         return deliver_event (Duple (ev->x, ev->y), reinterpret_cast<GdkEvent*> (ev));
309 }
310
311 /** Handler for pointer motion events on the canvas.
312  *  @param ev GDK event.
313  *  @return true if the motion event was handled, otherwise false.
314  */
315 bool
316 GtkCanvas::motion_notify_handler (GdkEventMotion* ev)
317 {
318         if (_grabbed_item) {
319                 /* if we have a grabbed item, it gets just the motion event,
320                    since no enter/leave events can have happened.
321                 */
322                 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send MOTION event there\n",
323                                                                        _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
324                 return _grabbed_item->Event (reinterpret_cast<GdkEvent*> (ev));
325         }
326
327         /* This is in canvas coordinates */
328         Duple point (ev->x, ev->y);
329
330         /* find the items at the new mouse position */
331         vector<Item const *> items;
332         _root.add_items_at_point (point, items);
333
334         Item const * new_item = items.empty() ? 0 : items.back ();
335
336         if (_current_item && _current_item != new_item) {
337                 /* leave event */
338                 GdkEventCrossing leave_event;
339                 leave_event.type = GDK_LEAVE_NOTIFY;
340                 leave_event.x = ev->x;
341                 leave_event.y = ev->y;
342                 _current_item->Event (reinterpret_cast<GdkEvent*> (&leave_event));
343         }
344
345         if (new_item && _current_item != new_item) {
346                 /* enter event */
347                 GdkEventCrossing enter_event;
348                 enter_event.type = GDK_ENTER_NOTIFY;
349                 enter_event.x = ev->x;
350                 enter_event.y = ev->y;
351                 new_item->Event (reinterpret_cast<GdkEvent*> (&enter_event));
352         }
353
354         _current_item = new_item;
355
356         /* Now deliver the motion event.  It may seem a little inefficient
357            to recompute the items under the event, but the enter notify/leave
358            events may have deleted canvas items so it is important to
359            recompute the list in deliver_event.
360         */
361         return deliver_event (point, reinterpret_cast<GdkEvent*> (ev));
362 }
363
364 /** Deliver an event to the appropriate item; either the grabbed item, or
365  *  one of the items underneath the event.
366  *  @param point Position that the event has occurred at, in canvas coordinates.
367  *  @param event The event.
368  */
369 bool
370 GtkCanvas::deliver_event (Duple point, GdkEvent* event)
371 {
372         if (_grabbed_item) {
373                 /* we have a grabbed item, so everything gets sent there */
374                 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 %2 (%3) was grabbed, send event there\n",
375                                                                        _grabbed_item, _grabbed_item->whatami(), _grabbed_item->name));
376                 return _grabbed_item->Event (event);
377         }
378
379         /* find the items that exist at the event's position */
380         vector<Item const *> items;
381         _root.add_items_at_point (point, items);
382
383         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 possible items to deliver event to\n", items.size()));
384
385         /* run through the items under the event, from top to bottom, until one claims the event */
386         vector<Item const *>::const_reverse_iterator i = items.rbegin ();
387         while (i != items.rend()) {
388
389                 if ((*i)->ignore_events ()) {
390                         DEBUG_TRACE (
391                                 PBD::DEBUG::CanvasEvents,
392                                 string_compose ("canvas event ignored by %1 %2\n", (*i)->whatami(), (*i)->name.empty() ? "[unknown]" : (*i)->name)
393                                 );
394                         ++i;
395                         continue;
396                 }
397                 
398                 if ((*i)->Event (event)) {
399                         /* this item has just handled the event */
400                         DEBUG_TRACE (
401                                 PBD::DEBUG::CanvasEvents,
402                                 string_compose ("canvas event handled by %1 %2\n", (*i)->whatami(), (*i)->name.empty() ? "[unknown]" : (*i)->name)
403                                 );
404                         
405                         return true;
406                 }
407                 
408                 DEBUG_TRACE (
409                         PBD::DEBUG::CanvasEvents,
410                         string_compose ("canvas event left unhandled by %1 %2\n", (*i)->whatami(), (*i)->name.empty() ? "[unknown]" : (*i)->name)
411                         );
412                 
413                 ++i;
414         }
415
416         /* debugging */
417         if (PBD::debug_bits & PBD::DEBUG::CanvasEvents) {
418                 while (i != items.rend()) {
419                         DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas event not seen by %1\n", (*i)->name.empty() ? "[unknown]" : (*i)->name));
420                         ++i;
421                 }
422         }
423         
424         return false;
425 }
426
427 /** Called when an item is being destroyed.
428  *  @param item Item being destroyed.
429  *  @param bounding_box Last known bounding box of the item.
430  */
431 void
432 GtkCanvas::item_going_away (Item* item, boost::optional<Rect> bounding_box)
433 {
434         if (bounding_box) {
435                 queue_draw_item_area (item, bounding_box.get ());
436         }
437         
438         if (_current_item == item) {
439                 _current_item = 0;
440         }
441
442         if (_grabbed_item == item) {
443                 _grabbed_item = 0;
444         }
445 }
446
447 /** Handler for GDK expose events.
448  *  @param ev Event.
449  *  @return true if the event was handled.
450  */
451 bool
452 GtkCanvas::on_expose_event (GdkEventExpose* ev)
453 {
454
455         Cairo::RefPtr<Cairo::Context> c = get_window()->create_cairo_context ();
456         
457         /* WINDOW  CANVAS
458          * 0,0     _scroll_offset_x, _scroll_offset_y
459          */
460
461         /* render using canvas coordinates */
462
463         Rect canvas_area (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height);
464         canvas_area  = canvas_area.translate (Duple (_scroll_offset_x, _scroll_offset_y));
465
466         /* things are going to render to the cairo surface with canvas
467          * coordinates:
468          *
469          *  an item at window/cairo 0,0 will have canvas_coords _scroll_offset_x,_scroll_offset_y
470          *
471          * let them render at their natural coordinates by using cairo_translate()
472          */
473
474         c->translate (-_scroll_offset_x, -_scroll_offset_y);
475
476         render (canvas_area, c);
477
478         return true;
479 }
480
481 /** @return Our Cairo context, or 0 if we don't have one */
482 Cairo::RefPtr<Cairo::Context>
483 GtkCanvas::context ()
484 {
485         Glib::RefPtr<Gdk::Window> w = get_window ();
486         if (!w) {
487                 return Cairo::RefPtr<Cairo::Context> ();
488         }
489
490         return w->create_cairo_context ();
491 }
492
493 /** Handler for GDK button press events.
494  *  @param ev Event.
495  *  @return true if the event was handled.
496  */
497 bool
498 GtkCanvas::on_button_press_event (GdkEventButton* ev)
499 {
500         /* translate event coordinates from window to canvas */
501
502         GdkEvent copy = *((GdkEvent*)ev);
503         Duple where = window_to_canvas (Duple (ev->x, ev->y));
504
505         copy.button.x = where.x;
506         copy.button.y = where.y;
507                                  
508         /* Coordinates in the event will be canvas coordinates, correctly adjusted
509            for scroll if this GtkCanvas is in a GtkCanvasViewport.
510         */
511         return button_handler ((GdkEventButton*) &copy);
512 }
513
514 /** Handler for GDK button release events.
515  *  @param ev Event.
516  *  @return true if the event was handled.
517  */
518 bool
519 GtkCanvas::on_button_release_event (GdkEventButton* ev)
520 {       
521         /* translate event coordinates from window to canvas */
522
523         GdkEvent copy = *((GdkEvent*)ev);
524         Duple where = window_to_canvas (Duple (ev->x, ev->y));
525
526         copy.button.x = where.x;
527         copy.button.y = where.y;
528
529         /* Coordinates in the event will be canvas coordinates, correctly adjusted
530            for scroll if this GtkCanvas is in a GtkCanvasViewport.
531         */
532         return button_handler ((GdkEventButton*) &copy);
533 }
534
535 /** Handler for GDK motion events.
536  *  @param ev Event.
537  *  @return true if the event was handled.
538  */
539 bool
540 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
541 {
542         /* translate event coordinates from window to canvas */
543
544         GdkEvent copy = *((GdkEvent*)ev);
545         Duple where = window_to_canvas (Duple (ev->x, ev->y));
546
547         copy.motion.x = where.x;
548         copy.motion.y = where.y;
549
550         /* Coordinates in the event will be canvas coordinates, correctly adjusted
551            for scroll if this GtkCanvas is in a GtkCanvasViewport.
552         */
553         return motion_notify_handler ((GdkEventMotion*) &copy);
554 }
555
556 /** Called to request a redraw of our canvas.
557  *  @param area Area to redraw, in canvas coordinates.
558  */
559 void
560 GtkCanvas::request_redraw (Rect const & request)
561 {
562         Rect area = canvas_to_window (request);
563         // cerr << "Invalidate " << request << " TRANSLATE AS " << area << endl;
564         queue_draw_area (floor (area.x0), floor (area.y0), ceil (area.x1) - floor (area.x0), ceil (area.y1) - floor (area.y0));
565 }
566
567 /** Called to request that we try to get a particular size for ourselves.
568  *  @param size Size to request, in pixels.
569  */
570 void
571 GtkCanvas::request_size (Duple size)
572 {
573         Duple req = size;
574
575         if (req.x > INT_MAX) {
576                 req.x = INT_MAX;
577         }
578
579         if (req.y > INT_MAX) {
580                 req.y = INT_MAX;
581         }
582
583         set_size_request (req.x, req.y);
584 }
585
586 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
587  *  This is typically used for dragging items around, so that they are grabbed during
588  *  the drag.
589  *  @param item Item to grab.
590  */
591 void
592 GtkCanvas::grab (Item* item)
593 {
594         /* XXX: should this be doing gdk_pointer_grab? */
595         _grabbed_item = item;
596 }
597
598 /** `Ungrab' any item that was previously grabbed */
599 void
600 GtkCanvas::ungrab ()
601 {
602         /* XXX: should this be doing gdk_pointer_ungrab? */
603         _grabbed_item = 0;
604 }
605
606 /** Create a GtkCanvaSViewport.
607  *  @param hadj Adjustment to use for horizontal scrolling.
608  *  @param vadj Adjustment to use for vertica scrolling.
609  */
610 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
611         : Alignment (0, 0, 1.0, 1.0)
612         , hadjustment (hadj)
613         , vadjustment (vadj)
614 {
615         add (_canvas);
616
617         hadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
618         vadj.signal_value_changed().connect (sigc::mem_fun (*this, &GtkCanvasViewport::scrolled));
619 }
620
621 void
622 GtkCanvasViewport::scrolled ()
623 {
624         _canvas.scroll_to (hadjustment.get_value(), vadjustment.get_value());
625         queue_draw ();
626 }
627
628 Duple
629 GtkCanvas::window_to_canvas (Duple const & d) const
630 {
631         return d.translate (Duple (_scroll_offset_x, _scroll_offset_y));
632 }
633
634 Duple
635 GtkCanvas::canvas_to_window (Duple const & d) const
636 {
637         return d.translate (Duple (-_scroll_offset_x, -_scroll_offset_y));
638 }       
639
640 Rect
641 GtkCanvas::window_to_canvas (Rect const & r) const
642 {
643         return r.translate (Duple (_scroll_offset_x, _scroll_offset_y));
644 }
645
646 Rect
647 GtkCanvas::canvas_to_window (Rect const & r) const
648 {
649         return r.translate (Duple (-_scroll_offset_x, -_scroll_offset_y));
650 }       
651
652 /** Handler for when GTK asks us what minimum size we want.
653  *  @param req Requsition to fill in.
654  */
655 void
656 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
657 {
658         /* force the canvas to size itself */
659         // _canvas.root()->bounding_box(); 
660
661         req->width = 16;
662         req->height = 16;
663 }
664
665 /** Convert window coordinates to canvas coordinates by taking into account
666  *  where we are scrolled to.
667  *  @param wx Window x.
668  *  @param wy Window y.
669  *  @param cx Filled in with canvas x.
670  *  @param cy Filled in with canvas y.
671  */
672 void
673 GtkCanvasViewport::window_to_canvas (int wx, int wy, Coord& cx, Coord& cy) const
674 {
675         Duple d = _canvas.window_to_canvas (Duple (wx, wy));
676         cx = d.x;
677         cy = d.y;
678 }
679
680 /** @return The visible area of the canvas, in canvas coordinates */
681 Rect
682 GtkCanvasViewport::visible_area () const
683 {
684         Distance const xo = hadjustment.get_value ();
685         Distance const yo = vadjustment.get_value ();
686         return Rect (xo, yo, xo + get_allocation().get_width (), yo + get_allocation().get_height ());
687 }