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/stacktrace.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) const
236 Rect ret = item_to_canvas (r).translate (-scroll_offset());
238 ret.x0 = round (ret.x0);
239 ret.x1 = round (ret.x1);
240 ret.y0 = round (ret.y0);
241 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 boost::optional<ArdourCanvas::Rect> bbox = bounding_box ();
261 boost::optional<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.get());
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)
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 ();
434 Item::find_scroll_parent ()
436 Item const * i = this;
437 ScrollGroup const * last_scroll_group = 0;
439 /* Don't allow a scroll group to find itself as its own scroll parent
445 ScrollGroup const * sg = dynamic_cast<ScrollGroup const *> (i);
447 last_scroll_group = sg;
452 _scroll_parent = const_cast<ScrollGroup*> (last_scroll_group);
456 Item::common_ancestor_within (uint32_t limit, const Item& other) const
458 uint32_t d1 = depth();
459 uint32_t d2 = other.depth();
460 const Item* i1 = this;
461 const Item* i2 = &other;
463 /* move towards root until we are at the same level
488 /* now see if there is a common parent */
508 Item::closest_ancestor_with (const Item& other) const
510 uint32_t d1 = depth();
511 uint32_t d2 = other.depth();
512 const Item* i1 = this;
513 const Item* i2 = &other;
515 /* move towards root until we are at the same level
535 /* now see if there is a common parent */
550 Item::is_descendant_of (const Item& candidate) const
552 Item const * i = _parent;
555 if (i == &candidate) {
570 /** @return Bounding box in this item's coordinates */
571 boost::optional<ArdourCanvas::Rect>
572 Item::bounding_box () const
574 if (_bounding_box_dirty) {
575 compute_bounding_box ();
576 assert (!_bounding_box_dirty);
577 add_child_bounding_boxes ();
580 return _bounding_box;
584 Item::height () const
586 boost::optional<ArdourCanvas::Rect> bb = bounding_box();
589 return bb->height ();
597 boost::optional<ArdourCanvas::Rect> bb = bounding_box().get();
607 Item::redraw () const
609 if (visible() && _bounding_box && _canvas) {
610 _canvas->request_redraw (item_to_window (_bounding_box.get()));
615 Item::begin_change ()
617 _pre_change_bounding_box = bounding_box ();
624 _canvas->item_changed (this, _pre_change_bounding_box);
627 _parent->child_changed ();
633 Item::begin_visual_change ()
638 Item::end_visual_change ()
641 _canvas->item_visual_property_changed (this);
646 Item::move (Duple movement)
648 set_position (position() + movement);
655 _canvas->grab (this);
666 Item::set_data (string const & key, void* data)
672 Item::get_data (string const & key) const
674 map<string, void*>::const_iterator i = _data.find (key);
675 if (i == _data.end ()) {
683 Item::set_ignore_events (bool ignore)
685 _ignore_events = ignore;
689 Item::whatami () const
691 std::string type = demangle (typeid (*this).name());
692 return type.substr (type.find_last_of (':') + 1);
708 Item::covers (Duple const & point) const
710 Duple p = window_to_item (point);
712 if (_bounding_box_dirty) {
713 compute_bounding_box ();
716 boost::optional<Rect> r = bounding_box();
722 return r.get().contains (p);
725 /* nesting/grouping API */
728 Item::render_children (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
730 if (_items.empty()) {
735 std::vector<Item*> items = _lut->get (area);
738 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
739 cerr << string_compose ("%1%7 %2 @ %7 render %5 @ %6 %3 items out of %4\n",
740 _canvas->render_indent(), (name.empty() ? string ("[unnamed]") : name), items.size(), _items.size(), area, _position, this,
747 for (std::vector<Item*>::const_iterator i = items.begin(); i != items.end(); ++i) {
749 if (!(*i)->visible ()) {
751 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
752 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] invisible - skipped\n";
758 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
762 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
763 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] empty - skipped\n";
769 Rect item = (*i)->item_to_window (item_bbox.get());
770 boost::optional<Rect> d = item.intersection (area);
774 if (draw.width() && draw.height()) {
776 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
777 if (dynamic_cast<Container*>(*i) == 0) {
778 cerr << _canvas->render_indent() << "render "
798 (*i)->render (area, context);
805 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
806 cerr << string_compose ("%1skip render of %2 %3, no intersection between %4 and %5\n", _canvas->render_indent(), (*i)->whatami(),
807 (*i)->name, item, area);
818 Item::add_child_bounding_boxes() const
820 boost::optional<Rect> self;
822 bool have_one = false;
825 bbox = _bounding_box.get();
829 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
831 if (!(*i)->visible()) {
835 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
841 Rect group_bbox = (*i)->item_to_parent (item_bbox.get ());
843 bbox = bbox.extend (group_bbox);
851 _bounding_box = boost::optional<Rect> ();
853 _bounding_box = bbox;
860 /* XXX should really notify canvas about this */
862 _items.push_back (i);
865 _bounding_box_dirty = true;
869 Item::remove (Item* i)
872 if (i->parent() != this) {
876 /* we cannot call bounding_box() here because that will iterate over
877 _items, one of which (the argument, i) may be in the middle of
878 deletion, making it impossible to call compute_bounding_box()
883 _pre_change_bounding_box = _bounding_box;
885 _pre_change_bounding_box = Rect();
891 _bounding_box_dirty = true;
897 Item::clear (bool with_delete)
901 clear_items (with_delete);
904 _bounding_box_dirty = true;
910 Item::clear_items (bool with_delete)
912 for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ) {
914 list<Item*>::iterator tmp = i;
919 /* remove from list before doing anything else, because we
920 * don't want to find the item in _items during any activity
921 * driven by unparent-ing or deletion.
936 Item::raise_child_to_top (Item* i)
938 if (!_items.empty()) {
939 if (_items.back() == i) {
945 _items.push_back (i);
952 Item::raise_child (Item* i, int levels)
954 list<Item*>::iterator j = find (_items.begin(), _items.end(), i);
955 assert (j != _items.end ());
960 while (levels > 0 && j != _items.end ()) {
965 _items.insert (j, i);
971 Item::lower_child_to_bottom (Item* i)
973 if (!_items.empty()) {
974 if (_items.front() == i) {
979 _items.push_front (i);
985 Item::ensure_lut () const
988 _lut = new DumbLookupTable (*this);
993 Item::invalidate_lut () const
1000 Item::child_changed ()
1003 _bounding_box_dirty = true;
1006 _parent->child_changed ();
1011 Item::add_items_at_point (Duple const point, vector<Item const *>& items) const
1013 boost::optional<Rect> const bbox = bounding_box ();
1015 /* Point is in window coordinate system */
1017 if (!bbox || !item_to_window (bbox.get()).contains (point)) {
1021 /* recurse and add any items within our group that contain point.
1022 Our children are only considered visible if we are, and similarly
1023 only if we do not ignore events.
1026 vector<Item*> our_items;
1028 if (!_items.empty() && visible() && !_ignore_events) {
1030 our_items = _lut->items_at_point (point);
1033 if (!our_items.empty() || covers (point)) {
1034 /* this adds this item itself to the list of items at point */
1035 items.push_back (this);
1038 for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
1039 (*i)->add_items_at_point (point, items);
1044 Item::set_tooltip (const std::string& s)
1050 Item::start_tooltip_timeout ()
1052 if (!_tooltip.empty()) {
1053 _canvas->start_tooltip_timeout (this);
1058 Item::stop_tooltip_timeout ()
1060 _canvas->stop_tooltip_timeout ();
1064 Item::dump (ostream& o) const
1066 boost::optional<ArdourCanvas::Rect> bb = bounding_box();
1068 o << _canvas->indent() << whatami() << ' ' << this << " self-Visible ? " << self_visible() << " visible ? " << visible();
1069 o << " @ " << position();
1072 if (!name.empty()) {
1078 o << endl << _canvas->indent() << "\tbbox: " << bb.get();
1079 o << endl << _canvas->indent() << "\tCANVAS bbox: " << item_to_canvas (bb.get());
1086 if (!_items.empty()) {
1089 o << _canvas->indent();
1090 o << " @ " << position();
1091 o << " Items: " << _items.size();
1092 o << " Self-Visible ? " << self_visible();
1093 o << " Visible ? " << visible();
1095 boost::optional<Rect> bb = bounding_box();
1098 o << endl << _canvas->indent() << " bbox: " << bb.get();
1099 o << endl << _canvas->indent() << " CANVAS bbox: " << item_to_canvas (bb.get());
1107 ArdourCanvas::dump_depth++;
1109 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
1113 ArdourCanvas::dump_depth--;
1118 ArdourCanvas::operator<< (ostream& o, const Item& i)