2 Copyright (C) 2011-2013 Paul Davis
3 Author: Carl Hetherington <cth@carlh.net>
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.
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.
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.
20 #include "pbd/compose.h"
21 #include "pbd/demangle.h"
22 #include "pbd/convert.h"
24 #include "ardour/utils.h"
26 #include "canvas/canvas.h"
27 #include "canvas/debug.h"
28 #include "canvas/item.h"
29 #include "canvas/scroll_group.h"
33 using namespace ArdourCanvas;
35 int Item::default_items_per_cell = 64;
37 Item::Item (Canvas* canvas)
44 , _bounding_box_dirty (true)
46 , _ignore_events (false)
48 DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
51 Item::Item (Item* parent)
54 , _canvas (parent->canvas())
58 , _bounding_box_dirty (true)
60 , _ignore_events (false)
62 DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
68 find_scroll_parent ();
71 Item::Item (Item* parent, Duple const& p)
74 , _canvas (parent->canvas())
79 , _bounding_box_dirty (true)
81 , _ignore_events (false)
83 DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
89 find_scroll_parent ();
96 _parent->remove (this);
100 _canvas->item_going_away (this, _bounding_box);
108 Item::visible() const
110 Item const * i = this;
113 if (!i->self_visible()) {
123 Item::canvas_origin () const
125 return item_to_canvas (Duple (0,0));
129 Item::window_origin () const
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.
136 return _parent->item_to_window (_position);
143 Item::item_to_parent (ArdourCanvas::Rect const & r) const
145 return r.translate (_position);
149 Item::scroll_offset () const
151 if (_scroll_parent) {
152 return _scroll_parent->scroll_offset();
158 Item::position_offset() const
160 Item const * i = this;
164 offset = offset.translate (i->position());
172 Item::item_to_canvas (ArdourCanvas::Rect const & r) const
174 return r.translate (position_offset());
178 Item::item_to_canvas (ArdourCanvas::Duple const & d) const
180 return d.translate (position_offset());
184 Item::canvas_to_item (ArdourCanvas::Duple const & r) const
186 return r.translate (-position_offset());
190 Item::canvas_to_item (ArdourCanvas::Rect const & r) const
192 return r.translate (-position_offset());
196 Item::item_to_canvas (Coord& x, Coord& y) const
198 Duple d = item_to_canvas (Duple (x, y));
205 Item::canvas_to_item (Coord& x, Coord& y) const
207 Duple d = canvas_to_item (Duple (x, y));
215 Item::item_to_window (ArdourCanvas::Duple const & d, bool rounded) const
217 Duple ret = item_to_canvas (d).translate (-scroll_offset());
220 ret.x = round (ret.x);
221 ret.y = round (ret.y);
228 Item::window_to_item (ArdourCanvas::Duple const & d) const
230 return canvas_to_item (d.translate (scroll_offset()));
234 Item::item_to_window (ArdourCanvas::Rect const & r, bool rounded) const
236 Rect ret = item_to_canvas (r).translate (-scroll_offset());
239 ret.x0 = round (ret.x0);
240 ret.x1 = round (ret.x1);
241 ret.y0 = round (ret.y0);
242 ret.y1 = round (ret.y1);
249 Item::window_to_item (ArdourCanvas::Rect const & r) const
251 return canvas_to_item (r.translate (scroll_offset()));
254 /** Set the position of this item in the parent's coordinates */
256 Item::set_position (Duple p)
258 if (p == _position) {
262 ArdourCanvas::Rect bbox = bounding_box ();
263 ArdourCanvas::Rect pre_change_parent_bounding_box;
266 /* see the comment in Canvas::item_moved() to understand
267 * why we use the parent's bounding box here.
269 pre_change_parent_bounding_box = item_to_parent (bbox);
274 /* only update canvas and parent if visible. Otherwise, this
275 will be done when ::show() is called.
279 _canvas->item_moved (this, pre_change_parent_bounding_box);
283 _parent->child_changed ();
289 Item::set_x_position (Coord x)
291 set_position (Duple (x, _position.y));
295 Item::set_y_position (Coord y)
297 set_position (Duple (_position.x, y));
301 Item::raise_to_top ()
304 _parent->raise_child_to_top (this);
309 Item::raise (int levels)
312 _parent->raise_child (this, levels);
317 Item::lower_to_bottom ()
320 _parent->lower_child_to_bottom (this);
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
336 BUT ... current item in canvas might be one of our children,
337 which is now hidden. So propagate away.
340 for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ++i) {
342 if ((*i)->self_visible()) {
343 /* item was visible but is now hidden because
344 we (its parent) are hidden
346 (*i)->propagate_show_hide ();
351 propagate_show_hide ();
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),
367 (*i)->propagate_show_hide ();
371 propagate_show_hide ();
376 Item::propagate_show_hide ()
378 /* bounding box may have changed while we were hidden */
381 _parent->child_changed ();
384 _canvas->item_shown_or_hidden (this);
388 Item::item_to_parent (Duple const & d) const
390 return d.translate (_position);
394 Item::parent_to_item (Duple const & d) const
396 return d.translate (- _position);
400 Item::parent_to_item (ArdourCanvas::Rect const & d) const
402 return d.translate (- _position);
413 Item::reparent (Item* new_parent, bool already_added)
415 if (new_parent == _parent) {
419 assert (_canvas == new_parent->canvas());
422 _parent->remove (this);
427 _parent = new_parent;
428 _canvas = _parent->canvas ();
430 find_scroll_parent ();
432 if (!already_added) {
438 Item::find_scroll_parent ()
440 Item const * i = this;
441 ScrollGroup const * last_scroll_group = 0;
443 /* Don't allow a scroll group to find itself as its own scroll parent
449 ScrollGroup const * sg = dynamic_cast<ScrollGroup const *> (i);
451 last_scroll_group = sg;
456 _scroll_parent = const_cast<ScrollGroup*> (last_scroll_group);
460 Item::common_ancestor_within (uint32_t limit, const Item& other) const
462 uint32_t d1 = depth();
463 uint32_t d2 = other.depth();
464 const Item* i1 = this;
465 const Item* i2 = &other;
467 /* move towards root until we are at the same level
492 /* now see if there is a common parent */
512 Item::closest_ancestor_with (const Item& other) const
514 uint32_t d1 = depth();
515 uint32_t d2 = other.depth();
516 const Item* i1 = this;
517 const Item* i2 = &other;
519 /* move towards root until we are at the same level
539 /* now see if there is a common parent */
554 Item::is_descendant_of (const Item& candidate) const
556 Item const * i = _parent;
559 if (i == &candidate) {
575 Item::size_allocate (Rect const & r)
580 /** @return Bounding box in this item's coordinates */
582 Item::bounding_box (bool for_own_purposes) const
584 if (_bounding_box_dirty) {
585 compute_bounding_box ();
586 assert (!_bounding_box_dirty);
587 add_child_bounding_boxes ();
590 if (!for_own_purposes) {
596 return _bounding_box;
600 Item::height () const
602 ArdourCanvas::Rect bb = bounding_box();
613 ArdourCanvas::Rect bb = bounding_box();
623 Item::redraw () const
625 if (visible() && _bounding_box && _canvas) {
626 _canvas->request_redraw (item_to_window (_bounding_box));
631 Item::begin_change ()
633 _pre_change_bounding_box = bounding_box ();
640 _canvas->item_changed (this, _pre_change_bounding_box);
643 _parent->child_changed ();
649 Item::begin_visual_change ()
654 Item::end_visual_change ()
657 _canvas->item_visual_property_changed (this);
662 Item::move (Duple movement)
664 set_position (position() + movement);
671 _canvas->grab (this);
682 Item::set_data (string const & key, void* data)
688 Item::get_data (string const & key) const
690 map<string, void*>::const_iterator i = _data.find (key);
691 if (i == _data.end ()) {
699 Item::set_ignore_events (bool ignore)
701 _ignore_events = ignore;
705 Item::whatami () const
707 std::string type = demangle (typeid (*this).name());
708 return type.substr (type.find_last_of (':') + 1);
724 Item::covers (Duple const & point) const
726 Duple p = window_to_item (point);
728 if (_bounding_box_dirty) {
729 compute_bounding_box ();
732 Rect r = bounding_box();
738 return r.contains (p);
741 /* nesting/grouping API */
744 Item::render_children (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
746 if (_items.empty()) {
751 std::vector<Item*> items = _lut->get (area);
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,
763 for (std::vector<Item*>::const_iterator i = items.begin(); i != items.end(); ++i) {
765 if (!(*i)->visible ()) {
767 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
768 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] invisible - skipped\n";
774 Rect item_bbox = (*i)->bounding_box ();
778 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
779 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] empty - skipped\n";
785 Rect item = (*i)->item_to_window (item_bbox, false);
786 Rect d = item.intersection (area);
790 if (draw.width() && draw.height()) {
792 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
793 if (dynamic_cast<Container*>(*i) == 0) {
794 cerr << _canvas->render_indent() << "render "
814 (*i)->render (area, context);
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);
834 Item::prepare_for_render_children (Rect const & area) const
836 if (_items.empty()) {
841 std::vector<Item*> items = _lut->get (area);
843 for (std::vector<Item*>::const_iterator i = items.begin(); i != items.end(); ++i) {
845 if (!(*i)->visible ()) {
849 Rect item_bbox = (*i)->bounding_box ();
855 Rect item = (*i)->item_to_window (item_bbox, false);
856 Rect d = item.intersection (area);
860 if (draw.width() && draw.height()) {
861 (*i)->prepare_for_render (area);
865 // Item does not intersect with visible canvas area
871 Item::add_child_bounding_boxes (bool include_hidden) const
875 bool have_one = false;
878 bbox = _bounding_box;
882 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
884 if (!(*i)->visible() && !include_hidden) {
888 Rect item_bbox = (*i)->bounding_box ();
894 Rect group_bbox = (*i)->item_to_parent (item_bbox);
896 bbox = bbox.extend (group_bbox);
904 _bounding_box = Rect ();
906 _bounding_box = bbox;
913 /* XXX should really notify canvas about this */
915 _items.push_back (i);
916 i->reparent (this, true);
918 _bounding_box_dirty = true;
922 Item::add_front (Item* i)
924 /* XXX should really notify canvas about this */
926 _items.push_front (i);
927 i->reparent (this, true);
929 _bounding_box_dirty = true;
933 Item::remove (Item* i)
936 if (i->parent() != this) {
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()
947 _pre_change_bounding_box = _bounding_box;
949 _pre_change_bounding_box = Rect();
955 _bounding_box_dirty = true;
961 Item::clear (bool with_delete)
965 clear_items (with_delete);
968 _bounding_box_dirty = true;
974 Item::clear_items (bool with_delete)
976 for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ) {
978 list<Item*>::iterator tmp = i;
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.
1000 Item::raise_child_to_top (Item* i)
1002 if (!_items.empty()) {
1003 if (_items.back() == i) {
1009 _items.push_back (i);
1016 Item::raise_child (Item* i, int levels)
1018 list<Item*>::iterator j = find (_items.begin(), _items.end(), i);
1019 assert (j != _items.end ());
1024 while (levels > 0 && j != _items.end ()) {
1029 _items.insert (j, i);
1035 Item::lower_child_to_bottom (Item* i)
1037 if (!_items.empty()) {
1038 if (_items.front() == i) {
1043 _items.push_front (i);
1049 Item::ensure_lut () const
1052 _lut = new DumbLookupTable (*this);
1057 Item::invalidate_lut () const
1064 Item::child_changed ()
1067 _bounding_box_dirty = true;
1070 _parent->child_changed ();
1075 Item::add_items_at_point (Duple const point, vector<Item const *>& items) const
1077 Rect const bbox = bounding_box ();
1079 /* Point is in window coordinate system */
1081 if (!bbox || !item_to_window (bbox).contains (point)) {
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.
1090 vector<Item*> our_items;
1092 if (!_items.empty() && visible() && !_ignore_events) {
1094 our_items = _lut->items_at_point (point);
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);
1102 for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
1103 (*i)->add_items_at_point (point, items);
1108 Item::set_tooltip (const std::string& s)
1114 Item::start_tooltip_timeout ()
1116 if (!_tooltip.empty()) {
1117 _canvas->start_tooltip_timeout (this);
1122 Item::stop_tooltip_timeout ()
1124 _canvas->stop_tooltip_timeout ();
1128 Item::dump (ostream& o) const
1130 ArdourCanvas::Rect bb = bounding_box();
1132 o << _canvas->indent() << whatami() << ' ' << this << " self-Visible ? " << self_visible() << " visible ? " << visible();
1133 o << " @ " << position();
1136 if (!name.empty()) {
1142 o << endl << _canvas->indent() << "\tbbox: " << bb;
1143 o << endl << _canvas->indent() << "\tCANVAS bbox: " << item_to_canvas (bb);
1150 if (!_items.empty()) {
1153 o << _canvas->indent();
1154 o << " @ " << position();
1155 o << " Items: " << _items.size();
1156 o << " Self-Visible ? " << self_visible();
1157 o << " Visible ? " << visible();
1159 Rect bb = bounding_box();
1162 o << endl << _canvas->indent() << " bbox: " << bb;
1163 o << endl << _canvas->indent() << " CANVAS bbox: " << item_to_canvas (bb);
1171 ArdourCanvas::dump_depth++;
1173 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
1177 ArdourCanvas::dump_depth--;
1182 ArdourCanvas::operator<< (ostream& o, const Item& i)