1 /*
2 * Copyright (C) 2012 Carl Hetherington <carl@carlh.net>
3 * Copyright (C) 2013-2017 Paul Davis <paul@linuxaudiosystems.com>
4 * Copyright (C) 2014-2017 Robin Gareus <robin@gareus.org>
5 * Copyright (C) 2015-2017 Tim Mayberry <mojofunk@gmail.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22 #include "pbd/compose.h"
23 #include "pbd/demangle.h"
24 #include "pbd/convert.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
Item(Canvas * canvas)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
Item(Item * parent)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
Item(Item * parent,Duple const & p)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
~Item()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 bool
visible() const108 Item::visible() const
109 {
110 Item const * i = this;
111
112 while (i) {
113 if (!i->self_visible()) {
114 return false;
115 }
116 i = i->parent();
117 }
118
119 return true;
120 }
121
122 Duple
canvas_origin() const123 Item::canvas_origin () const
124 {
125 return item_to_canvas (Duple (0,0));
126 }
127
128 Duple
window_origin() const129 Item::window_origin () const
130 {
131 /* This is slightly subtle. Our _position is in the coordinate space of
132 our parent. So to find out where that is in window coordinates, we
133 have to ask our parent.
134 */
135 if (_parent) {
136 return _parent->item_to_window (_position);
137 } else {
138 return _position;
139 }
140 }
141
142 ArdourCanvas::Rect
item_to_parent(ArdourCanvas::Rect const & r) const143 Item::item_to_parent (ArdourCanvas::Rect const & r) const
144 {
145 return r.translate (_position);
146 }
147
148 Duple
scroll_offset() const149 Item::scroll_offset () const
150 {
151 if (_scroll_parent) {
152 return _scroll_parent->scroll_offset();
153 }
154 return Duple (0,0);
155 }
156
157 Duple
position_offset() const158 Item::position_offset() const
159 {
160 Item const * i = this;
161 Duple offset;
162
163 while (i) {
164 offset = offset.translate (i->position());
165 i = i->parent();
166 }
167
168 return offset;
169 }
170
171 ArdourCanvas::Rect
item_to_canvas(ArdourCanvas::Rect const & r) const172 Item::item_to_canvas (ArdourCanvas::Rect const & r) const
173 {
174 return r.translate (position_offset());
175 }
176
177 ArdourCanvas::Duple
item_to_canvas(ArdourCanvas::Duple const & d) const178 Item::item_to_canvas (ArdourCanvas::Duple const & d) const
179 {
180 return d.translate (position_offset());
181 }
182
183 ArdourCanvas::Duple
canvas_to_item(ArdourCanvas::Duple const & r) const184 Item::canvas_to_item (ArdourCanvas::Duple const & r) const
185 {
186 return r.translate (-position_offset());
187 }
188
189 ArdourCanvas::Rect
canvas_to_item(ArdourCanvas::Rect const & r) const190 Item::canvas_to_item (ArdourCanvas::Rect const & r) const
191 {
192 return r.translate (-position_offset());
193 }
194
195 void
item_to_canvas(Coord & x,Coord & y) const196 Item::item_to_canvas (Coord& x, Coord& y) const
197 {
198 Duple d = item_to_canvas (Duple (x, y));
199
200 x = d.x;
201 y = d.y;
202 }
203
204 void
canvas_to_item(Coord & x,Coord & y) const205 Item::canvas_to_item (Coord& x, Coord& y) const
206 {
207 Duple d = canvas_to_item (Duple (x, y));
208
209 x = d.x;
210 y = d.y;
211 }
212
213
214 Duple
item_to_window(ArdourCanvas::Duple const & d,bool rounded) const215 Item::item_to_window (ArdourCanvas::Duple const & d, bool rounded) const
216 {
217 Duple ret = item_to_canvas (d).translate (-scroll_offset());
218
219 if (rounded) {
220 ret.x = round (ret.x);
221 ret.y = round (ret.y);
222 }
223
224 return ret;
225 }
226
227 Duple
window_to_item(ArdourCanvas::Duple const & d) const228 Item::window_to_item (ArdourCanvas::Duple const & d) const
229 {
230 return canvas_to_item (d.translate (scroll_offset()));
231 }
232
233 ArdourCanvas::Rect
item_to_window(ArdourCanvas::Rect const & r,bool rounded) const234 Item::item_to_window (ArdourCanvas::Rect const & r, bool rounded) const
235 {
236 Rect ret = item_to_canvas (r).translate (-scroll_offset());
237
238 if (rounded) {
239 ret.x0 = round (ret.x0);
240 ret.x1 = round (ret.x1);
241 ret.y0 = round (ret.y0);
242 ret.y1 = round (ret.y1);
243 }
244
245 return ret;
246 }
247
248 ArdourCanvas::Rect
window_to_item(ArdourCanvas::Rect const & r) const249 Item::window_to_item (ArdourCanvas::Rect const & r) const
250 {
251 return canvas_to_item (r.translate (scroll_offset()));
252 }
253
254 /** Set the position of this item in the parent's coordinates */
255 void
set_position(Duple p)256 Item::set_position (Duple p)
257 {
258 if (p == _position) {
259 return;
260 }
261
262 ArdourCanvas::Rect bbox = bounding_box ();
263 ArdourCanvas::Rect pre_change_parent_bounding_box;
264
265 if (bbox) {
266 /* see the comment in Canvas::item_moved() to understand
267 * why we use the parent's bounding box here.
268 */
269 pre_change_parent_bounding_box = item_to_parent (bbox);
270 }
271
272 _position = p;
273
274 /* only update canvas and parent if visible. Otherwise, this
275 will be done when ::show() is called.
276 */
277
278 if (visible()) {
279 _canvas->item_moved (this, pre_change_parent_bounding_box);
280
281
282 if (_parent) {
283 _parent->child_changed ();
284 }
285 }
286 }
287
288 void
set_x_position(Coord x)289 Item::set_x_position (Coord x)
290 {
291 set_position (Duple (x, _position.y));
292 }
293
294 void
set_y_position(Coord y)295 Item::set_y_position (Coord y)
296 {
297 set_position (Duple (_position.x, y));
298 }
299
300 void
raise_to_top()301 Item::raise_to_top ()
302 {
303 if (_parent) {
304 _parent->raise_child_to_top (this);
305 }
306 }
307
308 void
raise(int levels)309 Item::raise (int levels)
310 {
311 if (_parent) {
312 _parent->raise_child (this, levels);
313 }
314 }
315
316 void
lower_to_bottom()317 Item::lower_to_bottom ()
318 {
319 if (_parent) {
320 _parent->lower_child_to_bottom (this);
321 }
322 }
323
324 void
hide()325 Item::hide ()
326 {
327 if (_visible) {
328 _visible = false;
329
330 /* children are all hidden because we are hidden, no need
331 to propagate change because our bounding box necessarily
332 includes them all already. thus our being hidden results
333 in (a) a redraw of the entire bounding box (b) no children
334 will be drawn.
335
336 BUT ... current item in canvas might be one of our children,
337 which is now hidden. So propagate away.
338 */
339
340 for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ++i) {
341
342 if ((*i)->self_visible()) {
343 /* item was visible but is now hidden because
344 we (its parent) are hidden
345 */
346 (*i)->propagate_show_hide ();
347 }
348 }
349
350
351 propagate_show_hide ();
352 }
353 }
354
355 void
show()356 Item::show ()
357 {
358 if (!_visible) {
359
360 _visible = true;
361
362 for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ++i) {
363 if ((*i)->self_visible()) {
364 /* item used to be hidden by us (its parent),
365 but is now visible
366 */
367 (*i)->propagate_show_hide ();
368 }
369 }
370
371 propagate_show_hide ();
372 }
373 }
374
375 void
propagate_show_hide()376 Item::propagate_show_hide ()
377 {
378 /* bounding box may have changed while we were hidden */
379
380 if (_parent) {
381 _parent->child_changed ();
382 }
383
384 _canvas->item_shown_or_hidden (this);
385 }
386
387 Duple
item_to_parent(Duple const & d) const388 Item::item_to_parent (Duple const & d) const
389 {
390 return d.translate (_position);
391 }
392
393 Duple
parent_to_item(Duple const & d) const394 Item::parent_to_item (Duple const & d) const
395 {
396 return d.translate (- _position);
397 }
398
399 ArdourCanvas::Rect
parent_to_item(ArdourCanvas::Rect const & d) const400 Item::parent_to_item (ArdourCanvas::Rect const & d) const
401 {
402 return d.translate (- _position);
403 }
404
405 void
unparent()406 Item::unparent ()
407 {
408 _parent = 0;
409 _scroll_parent = 0;
410 }
411
412 void
reparent(Item * new_parent,bool already_added)413 Item::reparent (Item* new_parent, bool already_added)
414 {
415 if (new_parent == _parent) {
416 return;
417 }
418
419 assert (_canvas == new_parent->canvas());
420
421 if (_parent) {
422 _parent->remove (this);
423 }
424
425 assert (new_parent);
426
427 _parent = new_parent;
428 _canvas = _parent->canvas ();
429
430 find_scroll_parent ();
431
432 if (!already_added) {
433 _parent->add (this);
434 }
435 }
436
437 void
find_scroll_parent()438 Item::find_scroll_parent ()
439 {
440 Item const * i = this;
441 ScrollGroup const * last_scroll_group = 0;
442
443 /* Don't allow a scroll group to find itself as its own scroll parent
444 */
445
446 i = i->parent ();
447
448 while (i) {
449 ScrollGroup const * sg = dynamic_cast<ScrollGroup const *> (i);
450 if (sg) {
451 last_scroll_group = sg;
452 }
453 i = i->parent();
454 }
455
456 _scroll_parent = const_cast<ScrollGroup*> (last_scroll_group);
457 }
458
459 bool
common_ancestor_within(uint32_t limit,const Item & other) const460 Item::common_ancestor_within (uint32_t limit, const Item& other) const
461 {
462 uint32_t d1 = depth();
463 uint32_t d2 = other.depth();
464 const Item* i1 = this;
465 const Item* i2 = &other;
466
467 /* move towards root until we are at the same level
468 for both items
469 */
470
471 while (d1 != d2) {
472 if (d1 > d2) {
473 if (!i1) {
474 return false;
475 }
476 i1 = i1->parent();
477 d1--;
478 limit--;
479 } else {
480 if (!i2) {
481 return false;
482 }
483 i2 = i2->parent();
484 d2--;
485 limit--;
486 }
487 if (limit == 0) {
488 return false;
489 }
490 }
491
492 /* now see if there is a common parent */
493
494 while (i1 != i2) {
495 if (i1) {
496 i1 = i1->parent();
497 }
498 if (i2) {
499 i2 = i2->parent ();
500 }
501
502 limit--;
503 if (limit == 0) {
504 return false;
505 }
506 }
507
508 return true;
509 }
510
511 const Item*
closest_ancestor_with(const Item & other) const512 Item::closest_ancestor_with (const Item& other) const
513 {
514 uint32_t d1 = depth();
515 uint32_t d2 = other.depth();
516 const Item* i1 = this;
517 const Item* i2 = &other;
518
519 /* move towards root until we are at the same level
520 for both items
521 */
522
523 while (d1 != d2) {
524 if (d1 > d2) {
525 if (!i1) {
526 return 0;
527 }
528 i1 = i1->parent();
529 d1--;
530 } else {
531 if (!i2) {
532 return 0;
533 }
534 i2 = i2->parent();
535 d2--;
536 }
537 }
538
539 /* now see if there is a common parent */
540
541 while (i1 != i2) {
542 if (i1) {
543 i1 = i1->parent();
544 }
545 if (i2) {
546 i2 = i2->parent ();
547 }
548 }
549
550 return i1;
551 }
552
553 bool
is_descendant_of(const Item & candidate) const554 Item::is_descendant_of (const Item& candidate) const
555 {
556 Item const * i = _parent;
557
558 while (i) {
559 if (i == &candidate) {
560 return true;
561 }
562 i = i->parent();
563 }
564
565 return false;
566 }
567
568 void
grab_focus()569 Item::grab_focus ()
570 {
571 /* XXX */
572 }
573
574 void
size_allocate(Rect const & r)575 Item::size_allocate (Rect const & r)
576 {
577 _allocation = r;
578 }
579
580 /** @return Bounding box in this item's coordinates */
581 ArdourCanvas::Rect
bounding_box(bool for_own_purposes) const582 Item::bounding_box (bool for_own_purposes) const
583 {
584 if (_bounding_box_dirty) {
585 compute_bounding_box ();
586 assert (!_bounding_box_dirty);
587 add_child_bounding_boxes ();
588 }
589
590 if (!for_own_purposes) {
591 if (_allocation) {
592 return _allocation;
593 }
594 }
595
596 return _bounding_box;
597 }
598
599 Coord
height() const600 Item::height () const
601 {
602 ArdourCanvas::Rect bb = bounding_box();
603
604 if (bb) {
605 return bb.height ();
606 }
607 return 0;
608 }
609
610 Coord
width() const611 Item::width () const
612 {
613 ArdourCanvas::Rect bb = bounding_box();
614
615 if (bb) {
616 return bb.width ();
617 }
618
619 return 0;
620 }
621
622 void
redraw() const623 Item::redraw () const
624 {
625 if (visible() && _bounding_box && _canvas) {
626 _canvas->request_redraw (item_to_window (_bounding_box));
627 }
628 }
629
630 void
begin_change()631 Item::begin_change ()
632 {
633 _pre_change_bounding_box = bounding_box ();
634 }
635
636 void
end_change()637 Item::end_change ()
638 {
639 if (visible()) {
640 _canvas->item_changed (this, _pre_change_bounding_box);
641
642 if (_parent) {
643 _parent->child_changed ();
644 }
645 }
646 }
647
648 void
begin_visual_change()649 Item::begin_visual_change ()
650 {
651 }
652
653 void
end_visual_change()654 Item::end_visual_change ()
655 {
656 if (visible()) {
657 _canvas->item_visual_property_changed (this);
658 }
659 }
660
661 void
move(Duple movement)662 Item::move (Duple movement)
663 {
664 set_position (position() + movement);
665 }
666
667 void
grab()668 Item::grab ()
669 {
670 assert (_canvas);
671 _canvas->grab (this);
672 }
673
674 void
ungrab()675 Item::ungrab ()
676 {
677 assert (_canvas);
678 _canvas->ungrab ();
679 }
680
681 void
set_data(string const & key,void * data)682 Item::set_data (string const & key, void* data)
683 {
684 _data[key] = data;
685 }
686
687 void *
get_data(string const & key) const688 Item::get_data (string const & key) const
689 {
690 map<string, void*>::const_iterator i = _data.find (key);
691 if (i == _data.end ()) {
692 return 0;
693 }
694
695 return i->second;
696 }
697
698 void
set_ignore_events(bool ignore)699 Item::set_ignore_events (bool ignore)
700 {
701 _ignore_events = ignore;
702 }
703
704 std::string
whatami() const705 Item::whatami () const
706 {
707 std::string type = demangle (typeid (*this).name());
708 return type.substr (type.find_last_of (':') + 1);
709 }
710
711 uint32_t
depth() const712 Item::depth () const
713 {
714 Item* i = _parent;
715 int d = 0;
716 while (i) {
717 ++d;
718 i = i->parent();
719 }
720 return d;
721 }
722
723 bool
covers(Duple const & point) const724 Item::covers (Duple const & point) const
725 {
726 Duple p = window_to_item (point);
727
728 if (_bounding_box_dirty) {
729 compute_bounding_box ();
730 }
731
732 Rect r = bounding_box();
733
734 if (!r) {
735 return false;
736 }
737
738 return r.contains (p);
739 }
740
741 /* nesting/grouping API */
742
743 void
render_children(Rect const & area,Cairo::RefPtr<Cairo::Context> context) const744 Item::render_children (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
745 {
746 if (_items.empty()) {
747 return;
748 }
749
750 ensure_lut ();
751 std::vector<Item*> items = _lut->get (area);
752
753 #ifdef CANVAS_DEBUG
754 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
755 cerr << string_compose ("%1%7 %2 @ %7 render %5 @ %6 %3 items out of %4\n",
756 _canvas->render_indent(), (name.empty() ? string ("[unnamed]") : name), items.size(), _items.size(), area, _position, this,
757 whatami());
758 }
759 #endif
760
761 ++render_depth;
762
763 for (std::vector<Item*>::const_iterator i = items.begin(); i != items.end(); ++i) {
764
765 if (!(*i)->visible ()) {
766 #ifdef CANVAS_DEBUG
767 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
768 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] invisible - skipped\n";
769 }
770 #endif
771 continue;
772 }
773
774 Rect item_bbox = (*i)->bounding_box ();
775
776 if (!item_bbox) {
777 #ifdef CANVAS_DEBUG
778 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
779 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] empty - skipped\n";
780 }
781 #endif
782 continue;
783 }
784
785 Rect item = (*i)->item_to_window (item_bbox, false);
786 Rect d = item.intersection (area);
787
788 if (d) {
789 Rect draw = d;
790 if (draw.width() && draw.height()) {
791 #ifdef CANVAS_DEBUG
792 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
793 if (dynamic_cast<Container*>(*i) == 0) {
794 cerr << _canvas->render_indent() << "render "
795 << ' '
796 << (*i)
797 << ' '
798 << (*i)->whatami()
799 << ' '
800 << (*i)->name
801 << " item "
802 << item_bbox
803 << " window = "
804 << item
805 << " intersect = "
806 << draw
807 << " @ "
808 << _position
809 << endl;
810 }
811 }
812 #endif
813
814 (*i)->render (area, context);
815 ++render_count;
816 }
817
818 } else {
819
820 #ifdef CANVAS_DEBUG
821 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
822 cerr << string_compose ("%1skip render of %2 %3, no intersection between %4 and %5\n", _canvas->render_indent(), (*i)->whatami(),
823 (*i)->name, item, area);
824 }
825 #endif
826
827 }
828 }
829
830 --render_depth;
831 }
832
833 void
prepare_for_render_children(Rect const & area) const834 Item::prepare_for_render_children (Rect const & area) const
835 {
836 if (_items.empty()) {
837 return;
838 }
839
840 ensure_lut ();
841 std::vector<Item*> items = _lut->get (area);
842
843 for (std::vector<Item*>::const_iterator i = items.begin(); i != items.end(); ++i) {
844
845 if (!(*i)->visible ()) {
846 continue;
847 }
848
849 Rect item_bbox = (*i)->bounding_box ();
850
851 if (!item_bbox) {
852 continue;
853 }
854
855 Rect item = (*i)->item_to_window (item_bbox, false);
856 Rect d = item.intersection (area);
857
858 if (d) {
859 Rect draw = d;
860 if (draw.width() && draw.height()) {
861 (*i)->prepare_for_render (area);
862 }
863
864 } else {
865 // Item does not intersect with visible canvas area
866 }
867 }
868 }
869
870 void
add_child_bounding_boxes(bool include_hidden) const871 Item::add_child_bounding_boxes (bool include_hidden) const
872 {
873 Rect self;
874 Rect bbox;
875 bool have_one = false;
876
877 if (_bounding_box) {
878 bbox = _bounding_box;
879 have_one = true;
880 }
881
882 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
883
884 if (!(*i)->visible() && !include_hidden) {
885 continue;
886 }
887
888 Rect item_bbox = (*i)->bounding_box ();
889
890 if (!item_bbox) {
891 continue;
892 }
893
894 Rect group_bbox = (*i)->item_to_parent (item_bbox);
895 if (have_one) {
896 bbox = bbox.extend (group_bbox);
897 } else {
898 bbox = group_bbox;
899 have_one = true;
900 }
901 }
902
903 if (!have_one) {
904 _bounding_box = Rect ();
905 } else {
906 _bounding_box = bbox;
907 }
908 }
909
910 void
add(Item * i)911 Item::add (Item* i)
912 {
913 /* XXX should really notify canvas about this */
914
915 _items.push_back (i);
916 i->reparent (this, true);
917 invalidate_lut ();
918 _bounding_box_dirty = true;
919 }
920
921 void
add_front(Item * i)922 Item::add_front (Item* i)
923 {
924 /* XXX should really notify canvas about this */
925
926 _items.push_front (i);
927 i->reparent (this, true);
928 invalidate_lut ();
929 _bounding_box_dirty = true;
930 }
931
932 void
remove(Item * i)933 Item::remove (Item* i)
934 {
935
936 if (i->parent() != this) {
937 return;
938 }
939
940 /* we cannot call bounding_box() here because that will iterate over
941 _items, one of which (the argument, i) may be in the middle of
942 deletion, making it impossible to call compute_bounding_box()
943 on it.
944 */
945
946 if (_bounding_box) {
947 _pre_change_bounding_box = _bounding_box;
948 } else {
949 _pre_change_bounding_box = Rect();
950 }
951
952 i->unparent ();
953 _items.remove (i);
954 invalidate_lut ();
955 _bounding_box_dirty = true;
956
957 end_change ();
958 }
959
960 void
clear(bool with_delete)961 Item::clear (bool with_delete)
962 {
963 begin_change ();
964
965 clear_items (with_delete);
966
967 invalidate_lut ();
968 _bounding_box_dirty = true;
969
970 end_change ();
971 }
972
973 void
clear_items(bool with_delete)974 Item::clear_items (bool with_delete)
975 {
976 for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ) {
977
978 list<Item*>::iterator tmp = i;
979 Item *item = *i;
980
981 ++tmp;
982
983 /* remove from list before doing anything else, because we
984 * don't want to find the item in _items during any activity
985 * driven by unparent-ing or deletion.
986 */
987
988 _items.erase (i);
989 item->unparent ();
990
991 if (with_delete) {
992 delete item;
993 }
994
995 i = tmp;
996 }
997 }
998
999 void
raise_child_to_top(Item * i)1000 Item::raise_child_to_top (Item* i)
1001 {
1002 if (!_items.empty()) {
1003 if (_items.back() == i) {
1004 return;
1005 }
1006 }
1007
1008 _items.remove (i);
1009 _items.push_back (i);
1010
1011 invalidate_lut ();
1012 redraw ();
1013 }
1014
1015 void
raise_child(Item * i,int levels)1016 Item::raise_child (Item* i, int levels)
1017 {
1018 list<Item*>::iterator j = find (_items.begin(), _items.end(), i);
1019 assert (j != _items.end ());
1020
1021 ++j;
1022 _items.remove (i);
1023
1024 while (levels > 0 && j != _items.end ()) {
1025 ++j;
1026 --levels;
1027 }
1028
1029 _items.insert (j, i);
1030 invalidate_lut ();
1031 redraw ();
1032 }
1033
1034 void
lower_child_to_bottom(Item * i)1035 Item::lower_child_to_bottom (Item* i)
1036 {
1037 if (!_items.empty()) {
1038 if (_items.front() == i) {
1039 return;
1040 }
1041 }
1042 _items.remove (i);
1043 _items.push_front (i);
1044 invalidate_lut ();
1045 redraw ();
1046 }
1047
1048 void
ensure_lut() const1049 Item::ensure_lut () const
1050 {
1051 if (!_lut) {
1052 _lut = new DumbLookupTable (*this);
1053 }
1054 }
1055
1056 void
invalidate_lut() const1057 Item::invalidate_lut () const
1058 {
1059 delete _lut;
1060 _lut = 0;
1061 }
1062
1063 void
child_changed()1064 Item::child_changed ()
1065 {
1066 invalidate_lut ();
1067 _bounding_box_dirty = true;
1068
1069 if (_parent) {
1070 _parent->child_changed ();
1071 }
1072 }
1073
1074 void
add_items_at_point(Duple const point,vector<Item const * > & items) const1075 Item::add_items_at_point (Duple const point, vector<Item const *>& items) const
1076 {
1077 Rect const bbox = bounding_box ();
1078
1079 /* Point is in window coordinate system */
1080
1081 if (!bbox || !item_to_window (bbox).contains (point)) {
1082 return;
1083 }
1084
1085 /* recurse and add any items within our group that contain point.
1086 Our children are only considered visible if we are, and similarly
1087 only if we do not ignore events.
1088 */
1089
1090 vector<Item*> our_items;
1091
1092 if (!_items.empty() && visible() && !_ignore_events) {
1093 ensure_lut ();
1094 our_items = _lut->items_at_point (point);
1095 }
1096
1097 if (!our_items.empty() || covers (point)) {
1098 /* this adds this item itself to the list of items at point */
1099 items.push_back (this);
1100 }
1101
1102 for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
1103 (*i)->add_items_at_point (point, items);
1104 }
1105 }
1106
1107 void
set_tooltip(const std::string & s)1108 Item::set_tooltip (const std::string& s)
1109 {
1110 _tooltip = s;
1111 }
1112
1113 void
start_tooltip_timeout()1114 Item::start_tooltip_timeout ()
1115 {
1116 if (!_tooltip.empty()) {
1117 _canvas->start_tooltip_timeout (this);
1118 }
1119 }
1120
1121 void
stop_tooltip_timeout()1122 Item::stop_tooltip_timeout ()
1123 {
1124 _canvas->stop_tooltip_timeout ();
1125 }
1126
1127 void
dump(ostream & o) const1128 Item::dump (ostream& o) const
1129 {
1130 ArdourCanvas::Rect bb = bounding_box();
1131
1132 o << _canvas->indent() << whatami() << ' ' << this << " self-Visible ? " << self_visible() << " visible ? " << visible();
1133 o << " @ " << position();
1134
1135 #ifdef CANVAS_DEBUG
1136 if (!name.empty()) {
1137 o << ' ' << name;
1138 }
1139 #endif
1140
1141 if (bb) {
1142 o << endl << _canvas->indent() << "\tbbox: " << bb;
1143 o << endl << _canvas->indent() << "\tCANVAS bbox: " << item_to_canvas (bb);
1144 } else {
1145 o << " bbox unset";
1146 }
1147
1148 o << endl;
1149
1150 if (!_items.empty()) {
1151
1152 #ifdef CANVAS_DEBUG
1153 o << _canvas->indent();
1154 o << " @ " << position();
1155 o << " Items: " << _items.size();
1156 o << " Self-Visible ? " << self_visible();
1157 o << " Visible ? " << visible();
1158
1159 Rect bb = bounding_box();
1160
1161 if (bb) {
1162 o << endl << _canvas->indent() << " bbox: " << bb;
1163 o << endl << _canvas->indent() << " CANVAS bbox: " << item_to_canvas (bb);
1164 } else {
1165 o << " bbox unset";
1166 }
1167
1168 o << endl;
1169 #endif
1170
1171 ArdourCanvas::dump_depth++;
1172
1173 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
1174 o << **i;
1175 }
1176
1177 ArdourCanvas::dump_depth--;
1178 }
1179 }
1180
1181 ostream&
operator <<(ostream & o,const Item & i)1182 ArdourCanvas::operator<< (ostream& o, const Item& i)
1183 {
1184 i.dump (o);
1185 return o;
1186 }
1187
1188