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