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