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::add_child_bounding_boxes (bool include_hidden) const
838 bool have_one = false;
841 bbox = _bounding_box;
845 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
847 if (!(*i)->visible() && !include_hidden) {
851 Rect item_bbox = (*i)->bounding_box ();
857 Rect group_bbox = (*i)->item_to_parent (item_bbox);
859 bbox = bbox.extend (group_bbox);
867 _bounding_box = Rect ();
869 _bounding_box = bbox;
876 /* XXX should really notify canvas about this */
878 _items.push_back (i);
879 i->reparent (this, true);
881 _bounding_box_dirty = true;
885 Item::add_front (Item* i)
887 /* XXX should really notify canvas about this */
889 _items.push_front (i);
890 i->reparent (this, true);
892 _bounding_box_dirty = true;
896 Item::remove (Item* i)
899 if (i->parent() != this) {
903 /* we cannot call bounding_box() here because that will iterate over
904 _items, one of which (the argument, i) may be in the middle of
905 deletion, making it impossible to call compute_bounding_box()
910 _pre_change_bounding_box = _bounding_box;
912 _pre_change_bounding_box = Rect();
918 _bounding_box_dirty = true;
924 Item::clear (bool with_delete)
928 clear_items (with_delete);
931 _bounding_box_dirty = true;
937 Item::clear_items (bool with_delete)
939 for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ) {
941 list<Item*>::iterator tmp = i;
946 /* remove from list before doing anything else, because we
947 * don't want to find the item in _items during any activity
948 * driven by unparent-ing or deletion.
963 Item::raise_child_to_top (Item* i)
965 if (!_items.empty()) {
966 if (_items.back() == i) {
972 _items.push_back (i);
979 Item::raise_child (Item* i, int levels)
981 list<Item*>::iterator j = find (_items.begin(), _items.end(), i);
982 assert (j != _items.end ());
987 while (levels > 0 && j != _items.end ()) {
992 _items.insert (j, i);
998 Item::lower_child_to_bottom (Item* i)
1000 if (!_items.empty()) {
1001 if (_items.front() == i) {
1006 _items.push_front (i);
1012 Item::ensure_lut () const
1015 _lut = new DumbLookupTable (*this);
1020 Item::invalidate_lut () const
1027 Item::child_changed ()
1030 _bounding_box_dirty = true;
1033 _parent->child_changed ();
1038 Item::add_items_at_point (Duple const point, vector<Item const *>& items) const
1040 Rect const bbox = bounding_box ();
1042 /* Point is in window coordinate system */
1044 if (!bbox || !item_to_window (bbox).contains (point)) {
1048 /* recurse and add any items within our group that contain point.
1049 Our children are only considered visible if we are, and similarly
1050 only if we do not ignore events.
1053 vector<Item*> our_items;
1055 if (!_items.empty() && visible() && !_ignore_events) {
1057 our_items = _lut->items_at_point (point);
1060 if (!our_items.empty() || covers (point)) {
1061 /* this adds this item itself to the list of items at point */
1062 items.push_back (this);
1065 for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
1066 (*i)->add_items_at_point (point, items);
1071 Item::set_tooltip (const std::string& s)
1077 Item::start_tooltip_timeout ()
1079 if (!_tooltip.empty()) {
1080 _canvas->start_tooltip_timeout (this);
1085 Item::stop_tooltip_timeout ()
1087 _canvas->stop_tooltip_timeout ();
1091 Item::dump (ostream& o) const
1093 ArdourCanvas::Rect bb = bounding_box();
1095 o << _canvas->indent() << whatami() << ' ' << this << " self-Visible ? " << self_visible() << " visible ? " << visible();
1096 o << " @ " << position();
1099 if (!name.empty()) {
1105 o << endl << _canvas->indent() << "\tbbox: " << bb;
1106 o << endl << _canvas->indent() << "\tCANVAS bbox: " << item_to_canvas (bb);
1113 if (!_items.empty()) {
1116 o << _canvas->indent();
1117 o << " @ " << position();
1118 o << " Items: " << _items.size();
1119 o << " Self-Visible ? " << self_visible();
1120 o << " Visible ? " << visible();
1122 Rect bb = bounding_box();
1125 o << endl << _canvas->indent() << " bbox: " << bb;
1126 o << endl << _canvas->indent() << " CANVAS bbox: " << item_to_canvas (bb);
1134 ArdourCanvas::dump_depth++;
1136 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
1140 ArdourCanvas::dump_depth--;
1145 ArdourCanvas::operator<< (ostream& o, const Item& i)