593e7b4316a783af4fc521744765e13c453ec01f
[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                 if (!(*i)->visible()) {
781                         continue;
782                 }
783
784                 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
785
786                 if (!item_bbox) {
787                         continue;
788                 }
789
790                 Rect group_bbox = (*i)->item_to_parent (item_bbox.get ());
791                 if (have_one) {
792                         bbox = bbox.extend (group_bbox);
793                 } else {
794                         bbox = group_bbox;
795                         have_one = true;
796                 }
797         }
798
799         if (!have_one) {
800                 _bounding_box = boost::optional<Rect> ();
801         } else {
802                 _bounding_box = bbox;
803         }
804 }
805
806 void
807 Item::add (Item* i)
808 {
809         /* XXX should really notify canvas about this */
810
811         _items.push_back (i);
812         i->reparent (this);
813         invalidate_lut ();
814         _bounding_box_dirty = true;
815 }
816
817 void
818 Item::remove (Item* i)
819 {
820
821         if (i->parent() != this) {
822                 return;
823         }
824
825         /* we cannot call bounding_box() here because that will iterate over
826            _items, one of which (the argument, i) may be in the middle of
827            deletion, making it impossible to call compute_bounding_box()
828            on it.
829         */
830
831         if (_bounding_box) {
832                 _pre_change_bounding_box = _bounding_box;
833         } else {
834                 _pre_change_bounding_box = Rect();
835         }
836
837         i->unparent ();
838         _items.remove (i);
839         invalidate_lut ();
840         _bounding_box_dirty = true;
841         
842         end_change ();
843 }
844
845 void
846 Item::clear (bool with_delete)
847 {
848         begin_change ();
849
850         clear_items (with_delete);
851
852         invalidate_lut ();
853         _bounding_box_dirty = true;
854
855         end_change ();
856 }
857
858 void
859 Item::clear_items (bool with_delete)
860 {
861         for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ) {
862
863                 list<Item*>::iterator tmp = i;
864                 Item *item = *i;
865
866                 ++tmp;
867
868                 /* remove from list before doing anything else, because we
869                  * don't want to find the item in _items during any activity
870                  * driven by unparent-ing or deletion.
871                  */
872
873                 _items.erase (i);
874                 item->unparent ();
875                 
876                 if (with_delete) {
877                         delete item;
878                 }
879
880                 i = tmp;
881         }
882 }
883
884 void
885 Item::raise_child_to_top (Item* i)
886 {
887         if (!_items.empty()) {
888                 if (_items.back() == i) {
889                         return;
890                 }
891         }
892
893         _items.remove (i);
894         _items.push_back (i);
895         invalidate_lut ();
896 }
897
898 void
899 Item::raise_child (Item* i, int levels)
900 {
901         list<Item*>::iterator j = find (_items.begin(), _items.end(), i);
902         assert (j != _items.end ());
903
904         ++j;
905         _items.remove (i);
906
907         while (levels > 0 && j != _items.end ()) {
908                 ++j;
909                 --levels;
910         }
911
912         _items.insert (j, i);
913         invalidate_lut ();
914 }
915
916 void
917 Item::lower_child_to_bottom (Item* i)
918 {
919         if (!_items.empty()) {
920                 if (_items.front() == i) {
921                         return;
922                 }
923         }
924         _items.remove (i);
925         _items.push_front (i);
926         invalidate_lut ();
927 }
928
929 void
930 Item::ensure_lut () const
931 {
932         if (!_lut) {
933                 _lut = new DumbLookupTable (*this);
934         }
935 }
936
937 void
938 Item::invalidate_lut () const
939 {
940         delete _lut;
941         _lut = 0;
942 }
943
944 void
945 Item::child_changed ()
946 {
947         invalidate_lut ();
948         _bounding_box_dirty = true;
949
950         if (_parent) {
951                 _parent->child_changed ();
952         }
953 }
954
955 void
956 Item::add_items_at_point (Duple const point, vector<Item const *>& items) const
957 {
958         boost::optional<Rect> const bbox = bounding_box ();
959
960         /* Point is in window coordinate system */
961
962         if (!bbox || !item_to_window (bbox.get()).contains (point)) {
963                 return;
964         }
965
966         /* recurse and add any items within our group that contain point.
967            Our children are only considered visible if we are, and similarly
968            only if we do not ignore events.
969         */
970
971         vector<Item*> our_items;
972
973         if (!_items.empty() && visible() && !_ignore_events) {
974                 ensure_lut ();
975                 our_items = _lut->items_at_point (point);
976         }
977
978         if (!our_items.empty() || covers (point)) {
979                 /* this adds this item itself to the list of items at point */
980                 items.push_back (this);
981         }
982
983         for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
984                 (*i)->add_items_at_point (point, items);
985         }
986 }
987
988 void
989 Item::set_tooltip (const std::string& s)
990 {
991         _tooltip = s;
992 }
993
994 void
995 Item::start_tooltip_timeout ()
996 {
997         if (!_tooltip.empty()) {
998                 _canvas->start_tooltip_timeout (this);
999         }
1000 }
1001
1002 void
1003 Item::stop_tooltip_timeout ()
1004 {
1005         _canvas->stop_tooltip_timeout ();
1006 }
1007
1008 void
1009 Item::dump (ostream& o) const
1010 {
1011         boost::optional<ArdourCanvas::Rect> bb = bounding_box();
1012
1013         o << _canvas->indent() << whatami() << ' ' << this << " Visible ? " << _visible;
1014         o << " @ " << position();
1015         
1016 #ifdef CANVAS_DEBUG
1017         if (!name.empty()) {
1018                 o << ' ' << name;
1019         }
1020 #endif
1021
1022         if (bb) {
1023                 o << endl << _canvas->indent() << "\tbbox: " << bb.get();
1024                 o << endl << _canvas->indent() << "\tCANVAS bbox: " << item_to_canvas (bb.get());
1025         } else {
1026                 o << " bbox unset";
1027         }
1028
1029         o << endl;
1030
1031         if (!_items.empty()) {
1032
1033 #ifdef CANVAS_DEBUG
1034                 o << _canvas->indent();
1035                 o << " @ " << position();
1036                 o << " Items: " << _items.size();
1037                 o << " Visible ? " << _visible;
1038                 
1039                 boost::optional<Rect> bb = bounding_box();
1040                 
1041                 if (bb) {
1042                         o << endl << _canvas->indent() << "  bbox: " << bb.get();
1043                         o << endl << _canvas->indent() << "  CANVAS bbox: " << item_to_canvas (bb.get());
1044                 } else {
1045                         o << "  bbox unset";
1046                 }
1047                 
1048                 o << endl;
1049 #endif
1050                 
1051                 ArdourCanvas::dump_depth++;
1052                 
1053                 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
1054                         o << **i;
1055                 }
1056                 
1057                 ArdourCanvas::dump_depth--;
1058         }
1059 }
1060
1061 ostream&
1062 ArdourCanvas::operator<< (ostream& o, const Item& i)
1063 {
1064         i.dump (o);
1065         return o;
1066 }
1067