Merge branch 'ripple-mode-cc' into cairocanvas
[ardour.git] / libs / canvas / item.cc
1 /*
2     Copyright (C) 2011-2013 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 #include "pbd/compose.h"
21 #include "pbd/stacktrace.h"
22 #include "pbd/convert.h"
23
24 #include "ardour/utils.h"
25
26 #include "canvas/canvas.h"
27 #include "canvas/debug.h"
28 #include "canvas/item.h"
29 #include "canvas/scroll_group.h"
30
31 using namespace std;
32 using namespace PBD;
33 using namespace ArdourCanvas;
34
35 int Item::default_items_per_cell = 64;
36
37 Item::Item (Canvas* canvas)
38         : Fill (*this)
39         , Outline (*this)
40         ,  _canvas (canvas)
41         , _parent (0)
42         , _scroll_parent (0)
43         , _visible (true)
44         , _bounding_box_dirty (true)
45         , _lut (0)
46         , _ignore_events (false)
47 {
48         DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
49 }       
50
51 Item::Item (Item* parent)
52         : Fill (*this)
53         , Outline (*this)
54         ,  _canvas (parent->canvas())
55         , _parent (parent)
56         , _scroll_parent (0)
57         , _visible (true)
58         , _bounding_box_dirty (true)
59         , _lut (0)
60         , _ignore_events (false)
61 {
62         DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
63
64         if (parent) {
65                 _parent->add (this);
66         }
67
68         find_scroll_parent ();
69 }       
70
71 Item::Item (Item* parent, Duple const& p)
72         : Fill (*this)
73         , Outline (*this)
74         ,  _canvas (parent->canvas())
75         , _parent (parent)
76         , _scroll_parent (0)
77         , _position (p)
78         , _visible (true)
79         , _bounding_box_dirty (true)
80         , _lut (0)
81         , _ignore_events (false)
82 {
83         DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
84
85         if (parent) {
86                 _parent->add (this);
87         }
88
89         find_scroll_parent ();
90
91 }       
92
93 Item::~Item ()
94 {
95         if (_parent) {
96                 _parent->remove (this);
97         }
98
99         if (_canvas) {
100                 _canvas->item_going_away (this, _bounding_box);
101         }
102
103         clear_items (true);
104         delete _lut;
105 }
106
107 Duple
108 Item::canvas_origin () const
109 {
110         return item_to_canvas (Duple (0,0));
111 }
112
113 Duple
114 Item::window_origin () const 
115 {
116         /* This is slightly subtle. Our _position is in the coordinate space of 
117            our parent. So to find out where that is in window coordinates, we
118            have to ask our parent.
119         */
120         if (_parent) {
121                 return _parent->item_to_window (_position);
122         } else {
123                 return _position;
124         }
125 }
126
127 ArdourCanvas::Rect
128 Item::item_to_parent (ArdourCanvas::Rect const & r) const
129 {
130         return r.translate (_position);
131 }
132
133 Duple
134 Item::scroll_offset () const
135 {
136         if (_scroll_parent) {
137                 return _scroll_parent->scroll_offset();
138         } 
139         return Duple (0,0);
140 }
141
142 Duple
143 Item::position_offset() const
144 {
145         Item const * i = this;
146         Duple offset;
147
148         while (i) {
149                 offset = offset.translate (i->position());
150                 i = i->parent();
151         }
152
153         return offset;
154 }
155
156 ArdourCanvas::Rect
157 Item::item_to_canvas (ArdourCanvas::Rect const & r) const
158 {
159         return r.translate (position_offset());
160 }
161
162 ArdourCanvas::Duple
163 Item::item_to_canvas (ArdourCanvas::Duple const & d) const
164 {
165         return d.translate (position_offset());
166 }
167
168 ArdourCanvas::Duple
169 Item::canvas_to_item (ArdourCanvas::Duple const & r) const
170 {
171         return r.translate (-position_offset());
172 }
173
174 ArdourCanvas::Rect
175 Item::canvas_to_item (ArdourCanvas::Rect const & r) const
176 {
177         return r.translate (-position_offset());
178 }
179
180 void
181 Item::item_to_canvas (Coord& x, Coord& y) const
182 {
183         Duple d = item_to_canvas (Duple (x, y));
184                 
185         x = d.x;
186         y = d.y;
187 }
188
189 void
190 Item::canvas_to_item (Coord& x, Coord& y) const
191 {
192         Duple d = canvas_to_item (Duple (x, y));
193
194         x = d.x;
195         y = d.y;
196 }
197
198
199 Duple
200 Item::item_to_window (ArdourCanvas::Duple const & d, bool rounded) const
201 {
202         Duple ret = item_to_canvas (d).translate (-scroll_offset());
203
204         if (rounded) {
205                 ret.x = round (ret.x);
206                 ret.y = round (ret.y);
207         }
208
209         return ret;
210 }
211
212 Duple
213 Item::window_to_item (ArdourCanvas::Duple const & d) const
214 {
215         return canvas_to_item (d.translate (scroll_offset()));
216 }
217
218 ArdourCanvas::Rect
219 Item::item_to_window (ArdourCanvas::Rect const & r) const
220 {
221         Rect ret = item_to_canvas (r).translate (-scroll_offset());
222
223         ret.x0 = round (ret.x0);
224         ret.x1 = round (ret.x1);
225         ret.y0 = round (ret.y0);
226         ret.y1 = round (ret.y1);
227
228         return ret;
229 }
230
231 ArdourCanvas::Rect
232 Item::window_to_item (ArdourCanvas::Rect const & r) const
233 {
234         return canvas_to_item (r.translate (scroll_offset()));
235 }
236
237 /** Set the position of this item in the parent's coordinates */
238 void
239 Item::set_position (Duple p)
240 {
241         if (p == _position) {
242                 return;
243         }
244
245         boost::optional<ArdourCanvas::Rect> bbox = bounding_box ();
246         boost::optional<ArdourCanvas::Rect> pre_change_parent_bounding_box;
247
248         if (bbox) {
249                 /* see the comment in Canvas::item_moved() to understand
250                  * why we use the parent's bounding box here.
251                  */
252                 pre_change_parent_bounding_box = item_to_parent (bbox.get());
253         }
254         
255         _position = p;
256
257         _canvas->item_moved (this, pre_change_parent_bounding_box);
258
259         if (_parent) {
260                 _parent->child_changed ();
261         }
262 }
263
264 void
265 Item::set_x_position (Coord x)
266 {
267         set_position (Duple (x, _position.y));
268 }
269
270 void
271 Item::set_y_position (Coord y)
272 {
273         set_position (Duple (_position.x, y));
274 }
275
276 void
277 Item::raise_to_top ()
278 {
279         if (_parent) {
280                 _parent->raise_child_to_top (this);
281         }
282 }
283
284 void
285 Item::raise (int levels)
286 {
287         if (_parent) {
288                 _parent->raise_child (this, levels);
289         }
290 }
291
292 void
293 Item::lower_to_bottom ()
294 {
295         if (_parent) {
296                 _parent->lower_child_to_bottom (this);
297         }
298 }
299
300 void
301 Item::hide ()
302 {
303         if (_visible) {
304                 _visible = false;
305
306                 /* recompute parent bounding box, which may alter now that this
307                  * child is hidden.
308                  */
309
310                 if (_parent) {
311                         _parent->child_changed ();
312                 }
313
314                 _canvas->item_shown_or_hidden (this);
315         }
316 }
317
318 void
319 Item::show ()
320 {
321         if (!_visible) {
322                 _visible = true;
323
324                 /* bounding box may have changed while we were hidden */
325
326                 if (_parent) {
327                         _parent->child_changed ();
328                 }
329
330                 _canvas->item_shown_or_hidden (this);
331         }
332 }
333
334 Duple
335 Item::item_to_parent (Duple const & d) const
336 {
337         return d.translate (_position);
338 }
339
340 Duple
341 Item::parent_to_item (Duple const & d) const
342 {
343         return d.translate (- _position);
344 }
345
346 ArdourCanvas::Rect
347 Item::parent_to_item (ArdourCanvas::Rect const & d) const
348 {
349         return d.translate (- _position);
350 }
351
352 void
353 Item::unparent ()
354 {
355         _parent = 0;
356         _scroll_parent = 0;
357 }
358
359 void
360 Item::reparent (Item* new_parent)
361 {
362         if (new_parent == _parent) {
363                 return;
364         }
365
366         assert (_canvas == new_parent->canvas());
367
368         if (_parent) {
369                 _parent->remove (this);
370         }
371
372         assert (new_parent);
373
374         _parent = new_parent;
375         _canvas = _parent->canvas ();
376
377         find_scroll_parent ();
378
379         _parent->add (this);
380 }
381
382 void
383 Item::find_scroll_parent ()
384 {
385         Item const * i = this;
386         ScrollGroup const * last_scroll_group = 0;
387
388         /* Don't allow a scroll group to find itself as its own scroll parent
389          */
390
391         i = i->parent ();
392
393         while (i) {
394                 ScrollGroup const * sg = dynamic_cast<ScrollGroup const *> (i);
395                 if (sg) {
396                         last_scroll_group = sg;
397                 }
398                 i = i->parent();
399         }
400         
401         _scroll_parent = const_cast<ScrollGroup*> (last_scroll_group);
402 }
403
404 bool
405 Item::common_ancestor_within (uint32_t limit, const Item& other) const
406 {
407         uint32_t d1 = depth();
408         uint32_t d2 = other.depth();
409         const Item* i1 = this;
410         const Item* i2 = &other;
411         
412         /* move towards root until we are at the same level
413            for both items
414         */
415
416         while (d1 != d2) {
417                 if (d1 > d2) {
418                         if (!i1) {
419                                 return false;
420                         }
421                         i1 = i1->parent();
422                         d1--;
423                         limit--;
424                 } else {
425                         if (!i2) {
426                                 return false;
427                         }
428                         i2 = i2->parent();
429                         d2--;
430                         limit--;
431                 }
432                 if (limit == 0) {
433                         return false;
434                 }
435         }
436
437         /* now see if there is a common parent */
438
439         while (i1 != i2) {
440                 if (i1) {
441                         i1 = i1->parent();
442                 }
443                 if (i2) {
444                         i2 = i2->parent ();
445                 }
446
447                 limit--;
448                 if (limit == 0) {
449                         return false;
450                 }
451         }
452         
453         return true;
454 }
455
456 const Item*
457 Item::closest_ancestor_with (const Item& other) const
458 {
459         uint32_t d1 = depth();
460         uint32_t d2 = other.depth();
461         const Item* i1 = this;
462         const Item* i2 = &other;
463
464         /* move towards root until we are at the same level
465            for both items
466         */
467
468         while (d1 != d2) {
469                 if (d1 > d2) {
470                         if (!i1) {
471                                 return 0;
472                         }
473                         i1 = i1->parent();
474                         d1--;
475                 } else {
476                         if (!i2) {
477                                 return 0;
478                         }
479                         i2 = i2->parent();
480                         d2--;
481                 }
482         }
483
484         /* now see if there is a common parent */
485
486         while (i1 != i2) {
487                 if (i1) {
488                         i1 = i1->parent();
489                 }
490                 if (i2) {
491                         i2 = i2->parent ();
492                 }
493         }
494         
495         return i1;
496 }
497
498 bool
499 Item::is_descendant_of (const Item& candidate) const
500 {
501         Item const * i = _parent;
502
503         while (i) {
504                 if (i == &candidate) {
505                         return true;
506                 }
507                 i = i->parent();
508         }
509
510         return false;
511 }
512
513 void
514 Item::grab_focus ()
515 {
516         /* XXX */
517 }
518
519 /** @return Bounding box in this item's coordinates */
520 boost::optional<ArdourCanvas::Rect>
521 Item::bounding_box () const
522 {
523         if (_bounding_box_dirty) {
524                 compute_bounding_box ();
525                 assert (!_bounding_box_dirty);
526                 add_child_bounding_boxes ();
527         }
528
529         return _bounding_box;
530 }
531
532 Coord
533 Item::height () const 
534 {
535         boost::optional<ArdourCanvas::Rect> bb  = bounding_box();
536
537         if (bb) {
538                 return bb->height ();
539         }
540         return 0;
541 }
542
543 Coord
544 Item::width () const 
545 {
546         boost::optional<ArdourCanvas::Rect> bb = bounding_box().get();
547
548         if (bb) {
549                 return bb->width ();
550         }
551
552         return 0;
553 }
554
555 void
556 Item::redraw () const
557 {
558         if (_visible && _bounding_box && _canvas) {
559                 _canvas->request_redraw (item_to_window (_bounding_box.get()));
560         }
561 }       
562
563 void
564 Item::begin_change ()
565 {
566         _pre_change_bounding_box = bounding_box ();
567 }
568
569 void
570 Item::end_change ()
571 {
572         if (_visible) {
573                 _canvas->item_changed (this, _pre_change_bounding_box);
574                 
575                 if (_parent) {
576                         _parent->child_changed ();
577                 }
578         }
579 }
580
581 void
582 Item::begin_visual_change ()
583 {
584 }
585
586 void
587 Item::end_visual_change ()
588 {
589         if (_visible) {
590                 _canvas->item_visual_property_changed (this);
591         }
592 }
593
594 void
595 Item::move (Duple movement)
596 {
597         set_position (position() + movement);
598 }
599
600 void
601 Item::grab ()
602 {
603         assert (_canvas);
604         _canvas->grab (this);
605 }
606
607 void
608 Item::ungrab ()
609 {
610         assert (_canvas);
611         _canvas->ungrab ();
612 }
613
614 void
615 Item::set_data (string const & key, void* data)
616 {
617         _data[key] = data;
618 }
619
620 void *
621 Item::get_data (string const & key) const
622 {
623         map<string, void*>::const_iterator i = _data.find (key);
624         if (i == _data.end ()) {
625                 return 0;
626         }
627         
628         return i->second;
629 }
630
631 void
632 Item::set_ignore_events (bool ignore)
633 {
634         _ignore_events = ignore;
635 }
636
637 std::string
638 Item::whatami () const 
639 {
640         std::string type = demangle (typeid (*this).name());
641         return type.substr (type.find_last_of (':') + 1);
642 }
643
644 uint32_t
645 Item::depth () const
646 {
647         Item* i = _parent;
648         int d = 0;
649         while (i) {
650                 ++d;
651                 i = i->parent();
652         }
653         return d;
654 }
655
656 bool
657 Item::covers (Duple const & point) const
658 {
659         Duple p = window_to_item (point);
660
661         if (_bounding_box_dirty) {
662                 compute_bounding_box ();
663         }
664
665         boost::optional<Rect> r = bounding_box();
666
667         if (!r) {
668                 return false;
669         }
670
671         return r.get().contains (p);
672 }
673
674 /* nesting/grouping API */
675
676 void
677 Item::render_children (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
678 {
679         if (_items.empty()) {
680                 return;
681         }
682
683         ensure_lut ();
684         std::vector<Item*> items = _lut->get (area);
685
686 #ifdef CANVAS_DEBUG
687         if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
688                 cerr << string_compose ("%1%7 %2 @ %7 render %5 @ %6 %3 items out of %4\n", 
689                                         _canvas->render_indent(), (name.empty() ? string ("[unnamed]") : name), items.size(), _items.size(), area, _position, this,
690                                         whatami());
691         }
692 #endif
693
694         ++render_depth;
695                 
696         for (std::vector<Item*>::const_iterator i = items.begin(); i != items.end(); ++i) {
697
698                 if (!(*i)->visible ()) {
699 #ifdef CANVAS_DEBUG
700                         if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
701                                 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] invisible - skipped\n";
702                         }
703 #endif
704                         continue;
705                 }
706                 
707                 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
708
709                 if (!item_bbox) {
710 #ifdef CANVAS_DEBUG
711                         if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
712                                 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] empty - skipped\n";
713                         }
714 #endif
715                         continue;
716                 }
717                 
718                 Rect item = (*i)->item_to_window (item_bbox.get());
719                 boost::optional<Rect> d = item.intersection (area);
720                 
721                 if (d) {
722                         Rect draw = d.get();
723                         if (draw.width() && draw.height()) {
724 #ifdef CANVAS_DEBUG
725                                 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
726                                         if (dynamic_cast<Container*>(*i) == 0) {
727                                                 cerr << _canvas->render_indent() << "render "
728                                                      << ' ' 
729                                                      << (*i)
730                                                      << ' '
731                                                      << (*i)->whatami()
732                                                      << ' '
733                                                      << (*i)->name
734                                                      << " item "
735                                                      << item_bbox.get()
736                                                      << " window = " 
737                                                      << item
738                                                      << " intersect = "
739                                                      << draw
740                                                      << " @ " 
741                                                      << _position
742                                                      << endl;
743                                         }
744                                 }
745 #endif
746
747                                 (*i)->render (area, context);
748                                 ++render_count;
749                         }
750
751                 } else {
752
753 #ifdef CANVAS_DEBUG
754                         if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
755                                 cerr << string_compose ("%1skip render of %2 %3, no intersection between %4 and %5\n", _canvas->render_indent(), (*i)->whatami(),
756                                                         (*i)->name, item, area);
757                         }
758 #endif
759
760                 }
761         }
762
763         --render_depth;
764 }
765
766 void
767 Item::add_child_bounding_boxes() const
768 {
769         boost::optional<Rect> self;
770         Rect bbox;
771         bool have_one = false;
772
773         if (_bounding_box) {
774                 bbox = _bounding_box.get();
775                 have_one = true;
776         }
777
778         for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
779
780                 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
781
782                 if (!item_bbox) {
783                         continue;
784                 }
785
786                 Rect group_bbox = (*i)->item_to_parent (item_bbox.get ());
787                 if (have_one) {
788                         bbox = bbox.extend (group_bbox);
789                 } else {
790                         bbox = group_bbox;
791                         have_one = true;
792                 }
793         }
794
795         if (!have_one) {
796                 _bounding_box = boost::optional<Rect> ();
797         } else {
798                 _bounding_box = bbox;
799         }
800 }
801
802 void
803 Item::add (Item* i)
804 {
805         /* XXX should really notify canvas about this */
806
807         _items.push_back (i);
808         i->reparent (this);
809         invalidate_lut ();
810         _bounding_box_dirty = true;
811 }
812
813 void
814 Item::remove (Item* i)
815 {
816
817         if (i->parent() != this) {
818                 return;
819         }
820
821         /* we cannot call bounding_box() here because that will iterate over
822            _items, one of which (the argument, i) may be in the middle of
823            deletion, making it impossible to call compute_bounding_box()
824            on it.
825         */
826
827         if (_bounding_box) {
828                 _pre_change_bounding_box = _bounding_box;
829         } else {
830                 _pre_change_bounding_box = Rect();
831         }
832
833         i->unparent ();
834         _items.remove (i);
835         invalidate_lut ();
836         _bounding_box_dirty = true;
837         
838         end_change ();
839 }
840
841 void
842 Item::clear (bool with_delete)
843 {
844         begin_change ();
845
846         clear_items (with_delete);
847
848         invalidate_lut ();
849         _bounding_box_dirty = true;
850
851         end_change ();
852 }
853
854 void
855 Item::clear_items (bool with_delete)
856 {
857         for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ) {
858
859                 list<Item*>::iterator tmp = i;
860                 Item *item = *i;
861
862                 ++tmp;
863
864                 /* remove from list before doing anything else, because we
865                  * don't want to find the item in _items during any activity
866                  * driven by unparent-ing or deletion.
867                  */
868
869                 _items.erase (i);
870                 item->unparent ();
871                 
872                 if (with_delete) {
873                         delete item;
874                 }
875
876                 i = tmp;
877         }
878 }
879
880 void
881 Item::raise_child_to_top (Item* i)
882 {
883         if (!_items.empty()) {
884                 if (_items.back() == i) {
885                         return;
886                 }
887         }
888
889         _items.remove (i);
890         _items.push_back (i);
891         invalidate_lut ();
892 }
893
894 void
895 Item::raise_child (Item* i, int levels)
896 {
897         list<Item*>::iterator j = find (_items.begin(), _items.end(), i);
898         assert (j != _items.end ());
899
900         ++j;
901         _items.remove (i);
902
903         while (levels > 0 && j != _items.end ()) {
904                 ++j;
905                 --levels;
906         }
907
908         _items.insert (j, i);
909         invalidate_lut ();
910 }
911
912 void
913 Item::lower_child_to_bottom (Item* i)
914 {
915         if (!_items.empty()) {
916                 if (_items.front() == i) {
917                         return;
918                 }
919         }
920         _items.remove (i);
921         _items.push_front (i);
922         invalidate_lut ();
923 }
924
925 void
926 Item::ensure_lut () const
927 {
928         if (!_lut) {
929                 _lut = new DumbLookupTable (*this);
930         }
931 }
932
933 void
934 Item::invalidate_lut () const
935 {
936         delete _lut;
937         _lut = 0;
938 }
939
940 void
941 Item::child_changed ()
942 {
943         invalidate_lut ();
944         _bounding_box_dirty = true;
945
946         if (_parent) {
947                 _parent->child_changed ();
948         }
949 }
950
951 void
952 Item::add_items_at_point (Duple const point, vector<Item const *>& items) const
953 {
954         boost::optional<Rect> const bbox = bounding_box ();
955
956         /* Point is in window coordinate system */
957
958         if (!bbox || !item_to_window (bbox.get()).contains (point)) {
959                 return;
960         }
961
962         /* recurse and add any items within our group that contain point.
963            Our children are only considered visible if we are, and similarly
964            only if we do not ignore events.
965         */
966
967         vector<Item*> our_items;
968
969         if (!_items.empty() && visible() && !_ignore_events) {
970                 ensure_lut ();
971                 our_items = _lut->items_at_point (point);
972         }
973
974         if (!our_items.empty() || covers (point)) {
975                 /* this adds this item itself to the list of items at point */
976                 items.push_back (this);
977         }
978
979         for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
980                 (*i)->add_items_at_point (point, items);
981         }
982 }
983
984 void
985 Item::dump (ostream& o) const
986 {
987         boost::optional<ArdourCanvas::Rect> bb = bounding_box();
988
989         o << _canvas->indent() << whatami() << ' ' << this << " Visible ? " << _visible;
990         o << " @ " << position();
991         
992 #ifdef CANVAS_DEBUG
993         if (!name.empty()) {
994                 o << ' ' << name;
995         }
996 #endif
997
998         if (bb) {
999                 o << endl << _canvas->indent() << "\tbbox: " << bb.get();
1000                 o << endl << _canvas->indent() << "\tCANVAS bbox: " << item_to_canvas (bb.get());
1001         } else {
1002                 o << " bbox unset";
1003         }
1004
1005         o << endl;
1006
1007         if (!_items.empty()) {
1008
1009 #ifdef CANVAS_DEBUG
1010                 o << _canvas->indent();
1011                 o << " @ " << position();
1012                 o << " Items: " << _items.size();
1013                 o << " Visible ? " << _visible;
1014                 
1015                 boost::optional<Rect> bb = bounding_box();
1016                 
1017                 if (bb) {
1018                         o << endl << _canvas->indent() << "  bbox: " << bb.get();
1019                         o << endl << _canvas->indent() << "  CANVAS bbox: " << item_to_canvas (bb.get());
1020                 } else {
1021                         o << "  bbox unset";
1022                 }
1023                 
1024                 o << endl;
1025 #endif
1026                 
1027                 ArdourCanvas::dump_depth++;
1028                 
1029                 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
1030                         o << **i;
1031                 }
1032                 
1033                 ArdourCanvas::dump_depth--;
1034         }
1035 }
1036
1037 ostream&
1038 ArdourCanvas::operator<< (ostream& o, const Item& i)
1039 {
1040         i.dump (o);
1041         return o;
1042 }
1043