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 "canvas/canvas.h"
25 #include "canvas/debug.h"
26 #include "canvas/item.h"
27 #include "canvas/scroll_group.h"
31 using namespace ArdourCanvas;
33 int Item::default_items_per_cell = 64;
35 Item::Item (Canvas* canvas)
42 , _bounding_box_dirty (true)
44 , _ignore_events (false)
46 DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
49 Item::Item (Item* parent)
52 , _canvas (parent->canvas())
56 , _bounding_box_dirty (true)
58 , _ignore_events (false)
60 DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
66 find_scroll_parent ();
69 Item::Item (Item* parent, Duple const& p)
72 , _canvas (parent->canvas())
77 , _bounding_box_dirty (true)
79 , _ignore_events (false)
81 DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
87 find_scroll_parent ();
94 _parent->remove (this);
98 _canvas->item_going_away (this, _bounding_box);
106 Item::visible() const
108 Item const * i = this;
111 if (!i->self_visible()) {
121 Item::canvas_origin () const
123 return item_to_canvas (Duple (0,0));
127 Item::window_origin () const
129 /* This is slightly subtle. Our _position is in the coordinate space of
130 our parent. So to find out where that is in window coordinates, we
131 have to ask our parent.
134 return _parent->item_to_window (_position);
141 Item::item_to_parent (ArdourCanvas::Rect const & r) const
143 return r.translate (_position);
147 Item::scroll_offset () const
149 if (_scroll_parent) {
150 return _scroll_parent->scroll_offset();
156 Item::position_offset() const
158 Item const * i = this;
162 offset = offset.translate (i->position());
170 Item::item_to_canvas (ArdourCanvas::Rect const & r) const
172 return r.translate (position_offset());
176 Item::item_to_canvas (ArdourCanvas::Duple const & d) const
178 return d.translate (position_offset());
182 Item::canvas_to_item (ArdourCanvas::Duple const & r) const
184 return r.translate (-position_offset());
188 Item::canvas_to_item (ArdourCanvas::Rect const & r) const
190 return r.translate (-position_offset());
194 Item::item_to_canvas (Coord& x, Coord& y) const
196 Duple d = item_to_canvas (Duple (x, y));
203 Item::canvas_to_item (Coord& x, Coord& y) const
205 Duple d = canvas_to_item (Duple (x, y));
213 Item::item_to_window (ArdourCanvas::Duple const & d, bool rounded) const
215 Duple ret = item_to_canvas (d).translate (-scroll_offset());
218 ret.x = round (ret.x);
219 ret.y = round (ret.y);
226 Item::window_to_item (ArdourCanvas::Duple const & d) const
228 return canvas_to_item (d.translate (scroll_offset()));
232 Item::item_to_window (ArdourCanvas::Rect const & r, bool rounded) const
234 Rect ret = item_to_canvas (r).translate (-scroll_offset());
237 ret.x0 = round (ret.x0);
238 ret.x1 = round (ret.x1);
239 ret.y0 = round (ret.y0);
240 ret.y1 = round (ret.y1);
247 Item::window_to_item (ArdourCanvas::Rect const & r) const
249 return canvas_to_item (r.translate (scroll_offset()));
252 /** Set the position of this item in the parent's coordinates */
254 Item::set_position (Duple p)
256 if (p == _position) {
260 ArdourCanvas::Rect bbox = bounding_box ();
261 ArdourCanvas::Rect pre_change_parent_bounding_box;
264 /* see the comment in Canvas::item_moved() to understand
265 * why we use the parent's bounding box here.
267 pre_change_parent_bounding_box = item_to_parent (bbox);
272 /* only update canvas and parent if visible. Otherwise, this
273 will be done when ::show() is called.
277 _canvas->item_moved (this, pre_change_parent_bounding_box);
281 _parent->child_changed ();
287 Item::set_x_position (Coord x)
289 set_position (Duple (x, _position.y));
293 Item::set_y_position (Coord y)
295 set_position (Duple (_position.x, y));
299 Item::raise_to_top ()
302 _parent->raise_child_to_top (this);
307 Item::raise (int levels)
310 _parent->raise_child (this, levels);
315 Item::lower_to_bottom ()
318 _parent->lower_child_to_bottom (this);
328 /* children are all hidden because we are hidden, no need
329 to propagate change because our bounding box necessarily
330 includes them all already. thus our being hidden results
331 in (a) a redraw of the entire bounding box (b) no children
334 BUT ... current item in canvas might be one of our children,
335 which is now hidden. So propagate away.
338 for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ++i) {
340 if ((*i)->self_visible()) {
341 /* item was visible but is now hidden because
342 we (its parent) are hidden
344 (*i)->propagate_show_hide ();
349 propagate_show_hide ();
360 for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ++i) {
361 if ((*i)->self_visible()) {
362 /* item used to be hidden by us (its parent),
365 (*i)->propagate_show_hide ();
369 propagate_show_hide ();
374 Item::propagate_show_hide ()
376 /* bounding box may have changed while we were hidden */
379 _parent->child_changed ();
382 _canvas->item_shown_or_hidden (this);
386 Item::item_to_parent (Duple const & d) const
388 return d.translate (_position);
392 Item::parent_to_item (Duple const & d) const
394 return d.translate (- _position);
398 Item::parent_to_item (ArdourCanvas::Rect const & d) const
400 return d.translate (- _position);
411 Item::reparent (Item* new_parent, bool already_added)
413 if (new_parent == _parent) {
417 assert (_canvas == new_parent->canvas());
420 _parent->remove (this);
425 _parent = new_parent;
426 _canvas = _parent->canvas ();
428 find_scroll_parent ();
430 if (!already_added) {
436 Item::find_scroll_parent ()
438 Item const * i = this;
439 ScrollGroup const * last_scroll_group = 0;
441 /* Don't allow a scroll group to find itself as its own scroll parent
447 ScrollGroup const * sg = dynamic_cast<ScrollGroup const *> (i);
449 last_scroll_group = sg;
454 _scroll_parent = const_cast<ScrollGroup*> (last_scroll_group);
458 Item::common_ancestor_within (uint32_t limit, const Item& other) const
460 uint32_t d1 = depth();
461 uint32_t d2 = other.depth();
462 const Item* i1 = this;
463 const Item* i2 = &other;
465 /* move towards root until we are at the same level
490 /* now see if there is a common parent */
510 Item::closest_ancestor_with (const Item& other) const
512 uint32_t d1 = depth();
513 uint32_t d2 = other.depth();
514 const Item* i1 = this;
515 const Item* i2 = &other;
517 /* move towards root until we are at the same level
537 /* now see if there is a common parent */
552 Item::is_descendant_of (const Item& candidate) const
554 Item const * i = _parent;
557 if (i == &candidate) {
573 Item::size_allocate (Rect const & r)
578 /** @return Bounding box in this item's coordinates */
580 Item::bounding_box (bool for_own_purposes) const
582 if (_bounding_box_dirty) {
583 compute_bounding_box ();
584 assert (!_bounding_box_dirty);
585 add_child_bounding_boxes ();
588 if (!for_own_purposes) {
594 return _bounding_box;
598 Item::height () const
600 ArdourCanvas::Rect bb = bounding_box();
611 ArdourCanvas::Rect bb = bounding_box();
621 Item::redraw () const
623 if (visible() && _bounding_box && _canvas) {
624 _canvas->request_redraw (item_to_window (_bounding_box));
629 Item::begin_change ()
631 _pre_change_bounding_box = bounding_box ();
638 _canvas->item_changed (this, _pre_change_bounding_box);
641 _parent->child_changed ();
647 Item::begin_visual_change ()
652 Item::end_visual_change ()
655 _canvas->item_visual_property_changed (this);
660 Item::move (Duple movement)
662 set_position (position() + movement);
669 _canvas->grab (this);
680 Item::set_data (string const & key, void* data)
686 Item::get_data (string const & key) const
688 map<string, void*>::const_iterator i = _data.find (key);
689 if (i == _data.end ()) {
697 Item::set_ignore_events (bool ignore)
699 _ignore_events = ignore;
703 Item::whatami () const
705 std::string type = demangle (typeid (*this).name());
706 return type.substr (type.find_last_of (':') + 1);
722 Item::covers (Duple const & point) const
724 Duple p = window_to_item (point);
726 if (_bounding_box_dirty) {
727 compute_bounding_box ();
730 Rect r = bounding_box();
736 return r.contains (p);
739 /* nesting/grouping API */
742 Item::render_children (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
744 if (_items.empty()) {
749 std::vector<Item*> items = _lut->get (area);
752 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
753 cerr << string_compose ("%1%7 %2 @ %7 render %5 @ %6 %3 items out of %4\n",
754 _canvas->render_indent(), (name.empty() ? string ("[unnamed]") : name), items.size(), _items.size(), area, _position, this,
761 for (std::vector<Item*>::const_iterator i = items.begin(); i != items.end(); ++i) {
763 if (!(*i)->visible ()) {
765 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
766 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] invisible - skipped\n";
772 Rect item_bbox = (*i)->bounding_box ();
776 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
777 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] empty - skipped\n";
783 Rect item = (*i)->item_to_window (item_bbox, false);
784 Rect d = item.intersection (area);
788 if (draw.width() && draw.height()) {
790 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
791 if (dynamic_cast<Container*>(*i) == 0) {
792 cerr << _canvas->render_indent() << "render "
812 (*i)->render (area, context);
819 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
820 cerr << string_compose ("%1skip render of %2 %3, no intersection between %4 and %5\n", _canvas->render_indent(), (*i)->whatami(),
821 (*i)->name, item, area);
832 Item::prepare_for_render_children (Rect const & area) const
834 if (_items.empty()) {
839 std::vector<Item*> items = _lut->get (area);
841 for (std::vector<Item*>::const_iterator i = items.begin(); i != items.end(); ++i) {
843 if (!(*i)->visible ()) {
847 Rect item_bbox = (*i)->bounding_box ();
853 Rect item = (*i)->item_to_window (item_bbox, false);
854 Rect d = item.intersection (area);
858 if (draw.width() && draw.height()) {
859 (*i)->prepare_for_render (area);
863 // Item does not intersect with visible canvas area
869 Item::add_child_bounding_boxes (bool include_hidden) const
873 bool have_one = false;
876 bbox = _bounding_box;
880 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
882 if (!(*i)->visible() && !include_hidden) {
886 Rect item_bbox = (*i)->bounding_box ();
892 Rect group_bbox = (*i)->item_to_parent (item_bbox);
894 bbox = bbox.extend (group_bbox);
902 _bounding_box = Rect ();
904 _bounding_box = bbox;
911 /* XXX should really notify canvas about this */
913 _items.push_back (i);
914 i->reparent (this, true);
916 _bounding_box_dirty = true;
920 Item::add_front (Item* i)
922 /* XXX should really notify canvas about this */
924 _items.push_front (i);
925 i->reparent (this, true);
927 _bounding_box_dirty = true;
931 Item::remove (Item* i)
934 if (i->parent() != this) {
938 /* we cannot call bounding_box() here because that will iterate over
939 _items, one of which (the argument, i) may be in the middle of
940 deletion, making it impossible to call compute_bounding_box()
945 _pre_change_bounding_box = _bounding_box;
947 _pre_change_bounding_box = Rect();
953 _bounding_box_dirty = true;
959 Item::clear (bool with_delete)
963 clear_items (with_delete);
966 _bounding_box_dirty = true;
972 Item::clear_items (bool with_delete)
974 for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ) {
976 list<Item*>::iterator tmp = i;
981 /* remove from list before doing anything else, because we
982 * don't want to find the item in _items during any activity
983 * driven by unparent-ing or deletion.
998 Item::raise_child_to_top (Item* i)
1000 if (!_items.empty()) {
1001 if (_items.back() == i) {
1007 _items.push_back (i);
1014 Item::raise_child (Item* i, int levels)
1016 list<Item*>::iterator j = find (_items.begin(), _items.end(), i);
1017 assert (j != _items.end ());
1022 while (levels > 0 && j != _items.end ()) {
1027 _items.insert (j, i);
1033 Item::lower_child_to_bottom (Item* i)
1035 if (!_items.empty()) {
1036 if (_items.front() == i) {
1041 _items.push_front (i);
1047 Item::ensure_lut () const
1050 _lut = new DumbLookupTable (*this);
1055 Item::invalidate_lut () const
1062 Item::child_changed ()
1065 _bounding_box_dirty = true;
1068 _parent->child_changed ();
1073 Item::add_items_at_point (Duple const point, vector<Item const *>& items) const
1075 Rect const bbox = bounding_box ();
1077 /* Point is in window coordinate system */
1079 if (!bbox || !item_to_window (bbox).contains (point)) {
1083 /* recurse and add any items within our group that contain point.
1084 Our children are only considered visible if we are, and similarly
1085 only if we do not ignore events.
1088 vector<Item*> our_items;
1090 if (!_items.empty() && visible() && !_ignore_events) {
1092 our_items = _lut->items_at_point (point);
1095 if (!our_items.empty() || covers (point)) {
1096 /* this adds this item itself to the list of items at point */
1097 items.push_back (this);
1100 for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
1101 (*i)->add_items_at_point (point, items);
1106 Item::set_tooltip (const std::string& s)
1112 Item::start_tooltip_timeout ()
1114 if (!_tooltip.empty()) {
1115 _canvas->start_tooltip_timeout (this);
1120 Item::stop_tooltip_timeout ()
1122 _canvas->stop_tooltip_timeout ();
1126 Item::dump (ostream& o) const
1128 ArdourCanvas::Rect bb = bounding_box();
1130 o << _canvas->indent() << whatami() << ' ' << this << " self-Visible ? " << self_visible() << " visible ? " << visible();
1131 o << " @ " << position();
1134 if (!name.empty()) {
1140 o << endl << _canvas->indent() << "\tbbox: " << bb;
1141 o << endl << _canvas->indent() << "\tCANVAS bbox: " << item_to_canvas (bb);
1148 if (!_items.empty()) {
1151 o << _canvas->indent();
1152 o << " @ " << position();
1153 o << " Items: " << _items.size();
1154 o << " Self-Visible ? " << self_visible();
1155 o << " Visible ? " << visible();
1157 Rect bb = bounding_box();
1160 o << endl << _canvas->indent() << " bbox: " << bb;
1161 o << endl << _canvas->indent() << " CANVAS bbox: " << item_to_canvas (bb);
1169 ArdourCanvas::dump_depth++;
1171 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
1175 ArdourCanvas::dump_depth--;
1180 ArdourCanvas::operator<< (ostream& o, const Item& i)