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 boost::optional<ArdourCanvas::Rect> bbox = bounding_box ();
263 boost::optional<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.get());
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)
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 ();
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) {
572 /** @return Bounding box in this item's coordinates */
573 boost::optional<ArdourCanvas::Rect>
574 Item::bounding_box () const
576 if (_bounding_box_dirty) {
577 compute_bounding_box ();
578 assert (!_bounding_box_dirty);
579 add_child_bounding_boxes ();
582 return _bounding_box;
586 Item::height () const
588 boost::optional<ArdourCanvas::Rect> bb = bounding_box();
591 return bb->height ();
599 boost::optional<ArdourCanvas::Rect> bb = bounding_box();
609 Item::redraw () const
611 if (visible() && _bounding_box && _canvas) {
612 _canvas->request_redraw (item_to_window (_bounding_box.get()));
617 Item::begin_change ()
619 _pre_change_bounding_box = bounding_box ();
626 _canvas->item_changed (this, _pre_change_bounding_box);
629 _parent->child_changed ();
635 Item::begin_visual_change ()
640 Item::end_visual_change ()
643 _canvas->item_visual_property_changed (this);
648 Item::move (Duple movement)
650 set_position (position() + movement);
657 _canvas->grab (this);
668 Item::set_data (string const & key, void* data)
674 Item::get_data (string const & key) const
676 map<string, void*>::const_iterator i = _data.find (key);
677 if (i == _data.end ()) {
685 Item::set_ignore_events (bool ignore)
687 _ignore_events = ignore;
691 Item::whatami () const
693 std::string type = demangle (typeid (*this).name());
694 return type.substr (type.find_last_of (':') + 1);
710 Item::covers (Duple const & point) const
712 Duple p = window_to_item (point);
714 if (_bounding_box_dirty) {
715 compute_bounding_box ();
718 boost::optional<Rect> r = bounding_box();
724 return r.get().contains (p);
727 /* nesting/grouping API */
730 Item::render_children (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
732 if (_items.empty()) {
737 std::vector<Item*> items = _lut->get (area);
740 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
741 cerr << string_compose ("%1%7 %2 @ %7 render %5 @ %6 %3 items out of %4\n",
742 _canvas->render_indent(), (name.empty() ? string ("[unnamed]") : name), items.size(), _items.size(), area, _position, this,
749 for (std::vector<Item*>::const_iterator i = items.begin(); i != items.end(); ++i) {
751 if (!(*i)->visible ()) {
753 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
754 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] invisible - skipped\n";
760 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
764 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
765 cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] empty - skipped\n";
771 Rect item = (*i)->item_to_window (item_bbox.get(), false);
772 boost::optional<Rect> d = item.intersection (area);
776 if (draw.width() && draw.height()) {
778 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
779 if (dynamic_cast<Container*>(*i) == 0) {
780 cerr << _canvas->render_indent() << "render "
800 (*i)->render (area, context);
807 if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
808 cerr << string_compose ("%1skip render of %2 %3, no intersection between %4 and %5\n", _canvas->render_indent(), (*i)->whatami(),
809 (*i)->name, item, area);
820 Item::add_child_bounding_boxes() const
822 boost::optional<Rect> self;
824 bool have_one = false;
827 bbox = _bounding_box.get();
831 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
833 if (!(*i)->visible()) {
837 boost::optional<Rect> item_bbox = (*i)->bounding_box ();
843 Rect group_bbox = (*i)->item_to_parent (item_bbox.get ());
845 bbox = bbox.extend (group_bbox);
853 _bounding_box = boost::optional<Rect> ();
855 _bounding_box = bbox;
862 /* XXX should really notify canvas about this */
864 _items.push_back (i);
867 _bounding_box_dirty = true;
871 Item::remove (Item* i)
874 if (i->parent() != this) {
878 /* we cannot call bounding_box() here because that will iterate over
879 _items, one of which (the argument, i) may be in the middle of
880 deletion, making it impossible to call compute_bounding_box()
885 _pre_change_bounding_box = _bounding_box;
887 _pre_change_bounding_box = Rect();
893 _bounding_box_dirty = true;
899 Item::clear (bool with_delete)
903 clear_items (with_delete);
906 _bounding_box_dirty = true;
912 Item::clear_items (bool with_delete)
914 for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ) {
916 list<Item*>::iterator tmp = i;
921 /* remove from list before doing anything else, because we
922 * don't want to find the item in _items during any activity
923 * driven by unparent-ing or deletion.
938 Item::raise_child_to_top (Item* i)
940 if (!_items.empty()) {
941 if (_items.back() == i) {
947 _items.push_back (i);
954 Item::raise_child (Item* i, int levels)
956 list<Item*>::iterator j = find (_items.begin(), _items.end(), i);
957 assert (j != _items.end ());
962 while (levels > 0 && j != _items.end ()) {
967 _items.insert (j, i);
973 Item::lower_child_to_bottom (Item* i)
975 if (!_items.empty()) {
976 if (_items.front() == i) {
981 _items.push_front (i);
987 Item::ensure_lut () const
990 _lut = new DumbLookupTable (*this);
995 Item::invalidate_lut () const
1002 Item::child_changed ()
1005 _bounding_box_dirty = true;
1008 _parent->child_changed ();
1013 Item::add_items_at_point (Duple const point, vector<Item const *>& items) const
1015 boost::optional<Rect> const bbox = bounding_box ();
1017 /* Point is in window coordinate system */
1019 if (!bbox || !item_to_window (bbox.get()).contains (point)) {
1023 /* recurse and add any items within our group that contain point.
1024 Our children are only considered visible if we are, and similarly
1025 only if we do not ignore events.
1028 vector<Item*> our_items;
1030 if (!_items.empty() && visible() && !_ignore_events) {
1032 our_items = _lut->items_at_point (point);
1035 if (!our_items.empty() || covers (point)) {
1036 /* this adds this item itself to the list of items at point */
1037 items.push_back (this);
1040 for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
1041 (*i)->add_items_at_point (point, items);
1046 Item::set_tooltip (const std::string& s)
1052 Item::start_tooltip_timeout ()
1054 if (!_tooltip.empty()) {
1055 _canvas->start_tooltip_timeout (this);
1060 Item::stop_tooltip_timeout ()
1062 _canvas->stop_tooltip_timeout ();
1066 Item::dump (ostream& o) const
1068 boost::optional<ArdourCanvas::Rect> bb = bounding_box();
1070 o << _canvas->indent() << whatami() << ' ' << this << " self-Visible ? " << self_visible() << " visible ? " << visible();
1071 o << " @ " << position();
1074 if (!name.empty()) {
1080 o << endl << _canvas->indent() << "\tbbox: " << bb.get();
1081 o << endl << _canvas->indent() << "\tCANVAS bbox: " << item_to_canvas (bb.get());
1088 if (!_items.empty()) {
1091 o << _canvas->indent();
1092 o << " @ " << position();
1093 o << " Items: " << _items.size();
1094 o << " Self-Visible ? " << self_visible();
1095 o << " Visible ? " << visible();
1097 boost::optional<Rect> bb = bounding_box();
1100 o << endl << _canvas->indent() << " bbox: " << bb.get();
1101 o << endl << _canvas->indent() << " CANVAS bbox: " << item_to_canvas (bb.get());
1109 ArdourCanvas::dump_depth++;
1111 for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
1115 ArdourCanvas::dump_depth--;
1120 ArdourCanvas::operator<< (ostream& o, const Item& i)