add Item::add_front()
[ardour.git] / libs / canvas / item.cc
index ddc05a817a51a96bb3c12f1529f4b93a8dcdcd3b..711743d9f1f759240f9a15f03404f4abed1f21b6 100644 (file)
 */
 
 #include "pbd/compose.h"
-#include "pbd/stacktrace.h"
+#include "pbd/demangle.h"
 #include "pbd/convert.h"
 
 #include "ardour/utils.h"
 
 #include "canvas/canvas.h"
 #include "canvas/debug.h"
-#include "canvas/group.h"
 #include "canvas/item.h"
 #include "canvas/scroll_group.h"
 
@@ -33,43 +32,63 @@ using namespace std;
 using namespace PBD;
 using namespace ArdourCanvas;
 
+int Item::default_items_per_cell = 64;
+
 Item::Item (Canvas* canvas)
-       : _canvas (canvas)
+       : Fill (*this)
+       , Outline (*this)
+       ,  _canvas (canvas)
        , _parent (0)
+       , _scroll_parent (0)
+       , _visible (true)
+       , _bounding_box_dirty (true)
+       , _lut (0)
+       , _ignore_events (false)
 {
-       init ();
+       DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
 }
 
-Item::Item (Group* parent)
-       : _canvas (parent->canvas ())
+Item::Item (Item* parent)
+       : Fill (*this)
+       , Outline (*this)
+       ,  _canvas (parent->canvas())
        , _parent (parent)
+       , _scroll_parent (0)
+       , _visible (true)
+       , _bounding_box_dirty (true)
+       , _lut (0)
+       , _ignore_events (false)
 {
-       init ();
+       DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
+
+       if (parent) {
+               _parent->add (this);
+       }
+
+       find_scroll_parent ();
 }
 
-Item::Item (Group* parent, Duple position)
-       : _canvas (parent->canvas())
+Item::Item (Item* parent, Duple const& p)
+       : Fill (*this)
+       , Outline (*this)
+       ,  _canvas (parent->canvas())
        , _parent (parent)
-       , _position (position)
+       , _scroll_parent (0)
+       , _position (p)
+       , _visible (true)
+       , _bounding_box_dirty (true)
+       , _lut (0)
+       , _ignore_events (false)
 {
-       init ();
-}
+       DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
 
-void
-Item::init ()
-{
-       _visible = true;
-       _bounding_box_dirty = true;
-       _ignore_events = false;
-       
-       if (_parent) {
+       if (parent) {
                _parent->add (this);
        }
 
        find_scroll_parent ();
 
-       DEBUG_TRACE (DEBUG::CanvasItems, string_compose ("new canvas item %1\n", this));
-}      
+}
 
 Item::~Item ()
 {
@@ -80,19 +99,43 @@ Item::~Item ()
        if (_canvas) {
                _canvas->item_going_away (this, _bounding_box);
        }
+
+       clear_items (true);
+       delete _lut;
+}
+
+bool
+Item::visible() const
+{
+       Item const * i = this;
+
+       while (i) {
+               if (!i->self_visible()) {
+                       return false;
+               }
+               i = i->parent();
+       }
+
+       return true;
 }
 
 Duple
-Item::window_origin () const 
+Item::canvas_origin () const
 {
-       /* This is slightly subtle. Our _position is in the coordinate space of 
+       return item_to_canvas (Duple (0,0));
+}
+
+Duple
+Item::window_origin () const
+{
+       /* This is slightly subtle. Our _position is in the coordinate space of
           our parent. So to find out where that is in window coordinates, we
           have to ask our parent.
        */
        if (_parent) {
                return _parent->item_to_window (_position);
        } else {
-               return _parent->item_to_window (Duple (0,0));
+               return _position;
        }
 }
 
@@ -107,8 +150,8 @@ Item::scroll_offset () const
 {
        if (_scroll_parent) {
                return _scroll_parent->scroll_offset();
-       } 
-       return _canvas->scroll_offset();
+       }
+       return Duple (0,0);
 }
 
 Duple
@@ -153,7 +196,7 @@ void
 Item::item_to_canvas (Coord& x, Coord& y) const
 {
        Duple d = item_to_canvas (Duple (x, y));
-               
+
        x = d.x;
        y = d.y;
 }
@@ -173,8 +216,10 @@ Item::item_to_window (ArdourCanvas::Duple const & d, bool rounded) const
 {
        Duple ret = item_to_canvas (d).translate (-scroll_offset());
 
-       ret.x = round (ret.x);
-       ret.y = round (ret.y);
+       if (rounded) {
+               ret.x = round (ret.x);
+               ret.y = round (ret.y);
+       }
 
        return ret;
 }
@@ -186,14 +231,16 @@ Item::window_to_item (ArdourCanvas::Duple const & d) const
 }
 
 ArdourCanvas::Rect
-Item::item_to_window (ArdourCanvas::Rect const & r) const
+Item::item_to_window (ArdourCanvas::Rect const & r, bool rounded) const
 {
        Rect ret = item_to_canvas (r).translate (-scroll_offset());
 
-       ret.x0 = round (ret.x0);
-       ret.x1 = round (ret.x1);
-       ret.y0 = round (ret.y0);
-       ret.y1 = round (ret.y1);
+       if (rounded) {
+               ret.x0 = round (ret.x0);
+               ret.x1 = round (ret.x1);
+               ret.y0 = round (ret.y0);
+               ret.y1 = round (ret.y1);
+       }
 
        return ret;
 }
@@ -221,13 +268,20 @@ Item::set_position (Duple p)
                 */
                pre_change_parent_bounding_box = item_to_parent (bbox.get());
        }
-       
+
        _position = p;
 
-       _canvas->item_moved (this, pre_change_parent_bounding_box);
+       /* only update canvas and parent if visible. Otherwise, this
+          will be done when ::show() is called.
+       */
+
+       if (visible()) {
+               _canvas->item_moved (this, pre_change_parent_bounding_box);
 
-       if (_parent) {
-               _parent->child_changed ();
+
+               if (_parent) {
+                       _parent->child_changed ();
+               }
        }
 }
 
@@ -246,22 +300,25 @@ Item::set_y_position (Coord y)
 void
 Item::raise_to_top ()
 {
-       assert (_parent);
-       _parent->raise_child_to_top (this);
+       if (_parent) {
+               _parent->raise_child_to_top (this);
+       }
 }
 
 void
 Item::raise (int levels)
 {
-       assert (_parent);
-       _parent->raise_child (this, levels);
+       if (_parent) {
+               _parent->raise_child (this, levels);
+       }
 }
 
 void
 Item::lower_to_bottom ()
 {
-       assert (_parent);
-       _parent->lower_child_to_bottom (this);
+       if (_parent) {
+               _parent->lower_child_to_bottom (this);
+       }
 }
 
 void
@@ -269,7 +326,29 @@ Item::hide ()
 {
        if (_visible) {
                _visible = false;
-               _canvas->item_shown_or_hidden (this);
+
+               /* children are all hidden because we are hidden, no need
+                  to propagate change because our bounding box necessarily
+                  includes them all already. thus our being hidden results
+                  in (a) a redraw of the entire bounding box (b) no children
+                  will be drawn.
+
+                  BUT ... current item in canvas might be one of our children,
+                  which is now hidden. So propagate away.
+               */
+
+               for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ++i) {
+
+                       if ((*i)->self_visible()) {
+                               /* item was visible but is now hidden because
+                                  we (its parent) are hidden
+                               */
+                               (*i)->propagate_show_hide ();
+                       }
+               }
+
+
+               propagate_show_hide ();
        }
 }
 
@@ -277,9 +356,32 @@ void
 Item::show ()
 {
        if (!_visible) {
+
                _visible = true;
-               _canvas->item_shown_or_hidden (this);
+
+               for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ++i) {
+                       if ((*i)->self_visible()) {
+                               /* item used to be hidden by us (its parent),
+                                  but is now visible
+                               */
+                               (*i)->propagate_show_hide ();
+                       }
+               }
+
+               propagate_show_hide ();
+       }
+}
+
+void
+Item::propagate_show_hide ()
+{
+       /* bounding box may have changed while we were hidden */
+
+       if (_parent) {
+               _parent->child_changed ();
        }
+
+       _canvas->item_shown_or_hidden (this);
 }
 
 Duple
@@ -308,9 +410,13 @@ Item::unparent ()
 }
 
 void
-Item::reparent (Group* new_parent)
+Item::reparent (Item* new_parent)
 {
-       assert (_canvas == _parent->canvas());
+       if (new_parent == _parent) {
+               return;
+       }
+
+       assert (_canvas == new_parent->canvas());
 
        if (_parent) {
                _parent->remove (this);
@@ -344,7 +450,7 @@ Item::find_scroll_parent ()
                }
                i = i->parent();
        }
-       
+
        _scroll_parent = const_cast<ScrollGroup*> (last_scroll_group);
 }
 
@@ -355,17 +461,23 @@ Item::common_ancestor_within (uint32_t limit, const Item& other) const
        uint32_t d2 = other.depth();
        const Item* i1 = this;
        const Item* i2 = &other;
-       
+
        /* move towards root until we are at the same level
           for both items
        */
 
        while (d1 != d2) {
                if (d1 > d2) {
+                       if (!i1) {
+                               return false;
+                       }
                        i1 = i1->parent();
                        d1--;
                        limit--;
                } else {
+                       if (!i2) {
+                               return false;
+                       }
                        i2 = i2->parent();
                        d2--;
                        limit--;
@@ -390,7 +502,7 @@ Item::common_ancestor_within (uint32_t limit, const Item& other) const
                        return false;
                }
        }
-       
+
        return true;
 }
 
@@ -408,9 +520,15 @@ Item::closest_ancestor_with (const Item& other) const
 
        while (d1 != d2) {
                if (d1 > d2) {
+                       if (!i1) {
+                               return 0;
+                       }
                        i1 = i1->parent();
                        d1--;
                } else {
+                       if (!i2) {
+                               return 0;
+                       }
                        i2 = i2->parent();
                        d2--;
                }
@@ -426,7 +544,7 @@ Item::closest_ancestor_with (const Item& other) const
                        i2 = i2->parent ();
                }
        }
-       
+
        return i1;
 }
 
@@ -458,13 +576,14 @@ Item::bounding_box () const
        if (_bounding_box_dirty) {
                compute_bounding_box ();
                assert (!_bounding_box_dirty);
+               add_child_bounding_boxes ();
        }
 
        return _bounding_box;
 }
 
 Coord
-Item::height () const 
+Item::height () const
 {
        boost::optional<ArdourCanvas::Rect> bb  = bounding_box();
 
@@ -475,9 +594,9 @@ Item::height () const
 }
 
 Coord
-Item::width () const 
+Item::width () const
 {
-       boost::optional<ArdourCanvas::Rect> bb = bounding_box().get();
+       boost::optional<ArdourCanvas::Rect> bb = bounding_box();
 
        if (bb) {
                return bb->width ();
@@ -489,10 +608,10 @@ Item::width () const
 void
 Item::redraw () const
 {
-       if (_visible && _bounding_box && _canvas) {
+       if (visible() && _bounding_box && _canvas) {
                _canvas->request_redraw (item_to_window (_bounding_box.get()));
        }
-}      
+}
 
 void
 Item::begin_change ()
@@ -503,10 +622,12 @@ Item::begin_change ()
 void
 Item::end_change ()
 {
-       _canvas->item_changed (this, _pre_change_bounding_box);
-       
-       if (_parent) {
-               _parent->child_changed ();
+       if (visible()) {
+               _canvas->item_changed (this, _pre_change_bounding_box);
+
+               if (_parent) {
+                       _parent->child_changed ();
+               }
        }
 }
 
@@ -518,7 +639,9 @@ Item::begin_visual_change ()
 void
 Item::end_visual_change ()
 {
-       _canvas->item_visual_property_changed (this);
+       if (visible()) {
+               _canvas->item_visual_property_changed (this);
+       }
 }
 
 void
@@ -554,7 +677,7 @@ Item::get_data (string const & key) const
        if (i == _data.end ()) {
                return 0;
        }
-       
+
        return i->second;
 }
 
@@ -564,32 +687,8 @@ Item::set_ignore_events (bool ignore)
        _ignore_events = ignore;
 }
 
-void
-Item::dump (ostream& o) const
-{
-       boost::optional<ArdourCanvas::Rect> bb = bounding_box();
-
-       o << _canvas->indent() << whatami() << ' ' << this << " Visible ? " << _visible;
-       o << " @ " << position();
-       
-#ifdef CANVAS_DEBUG
-       if (!name.empty()) {
-               o << ' ' << name;
-       }
-#endif
-
-       if (bb) {
-               o << endl << _canvas->indent() << "\tbbox: " << bb.get();
-               o << endl << _canvas->indent() << "\tCANVAS bbox: " << item_to_canvas (bb.get());
-       } else {
-               o << " bbox unset";
-       }
-
-       o << endl;
-}
-
 std::string
-Item::whatami () const 
+Item::whatami () const
 {
        std::string type = demangle (typeid (*this).name());
        return type.substr (type.find_last_of (':') + 1);
@@ -625,6 +724,409 @@ Item::covers (Duple const & point) const
        return r.get().contains (p);
 }
 
+/* nesting/grouping API */
+
+void
+Item::render_children (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
+{
+       if (_items.empty()) {
+               return;
+       }
+
+       ensure_lut ();
+       std::vector<Item*> items = _lut->get (area);
+
+#ifdef CANVAS_DEBUG
+       if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
+               cerr << string_compose ("%1%7 %2 @ %7 render %5 @ %6 %3 items out of %4\n",
+                                       _canvas->render_indent(), (name.empty() ? string ("[unnamed]") : name), items.size(), _items.size(), area, _position, this,
+                                       whatami());
+       }
+#endif
+
+       ++render_depth;
+
+       for (std::vector<Item*>::const_iterator i = items.begin(); i != items.end(); ++i) {
+
+               if (!(*i)->visible ()) {
+#ifdef CANVAS_DEBUG
+                       if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
+                               cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] invisible - skipped\n";
+                       }
+#endif
+                       continue;
+               }
+
+               boost::optional<Rect> item_bbox = (*i)->bounding_box ();
+
+               if (!item_bbox) {
+#ifdef CANVAS_DEBUG
+                       if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
+                               cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] empty - skipped\n";
+                       }
+#endif
+                       continue;
+               }
+
+               Rect item = (*i)->item_to_window (item_bbox.get(), false);
+               boost::optional<Rect> d = item.intersection (area);
+
+               if (d) {
+                       Rect draw = d.get();
+                       if (draw.width() && draw.height()) {
+#ifdef CANVAS_DEBUG
+                               if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
+                                       if (dynamic_cast<Container*>(*i) == 0) {
+                                               cerr << _canvas->render_indent() << "render "
+                                                    << ' '
+                                                    << (*i)
+                                                    << ' '
+                                                    << (*i)->whatami()
+                                                    << ' '
+                                                    << (*i)->name
+                                                    << " item "
+                                                    << item_bbox.get()
+                                                    << " window = "
+                                                    << item
+                                                    << " intersect = "
+                                                    << draw
+                                                    << " @ "
+                                                    << _position
+                                                    << endl;
+                                       }
+                               }
+#endif
+
+                               (*i)->render (area, context);
+                               ++render_count;
+                       }
+
+               } else {
+
+#ifdef CANVAS_DEBUG
+                       if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
+                               cerr << string_compose ("%1skip render of %2 %3, no intersection between %4 and %5\n", _canvas->render_indent(), (*i)->whatami(),
+                                                       (*i)->name, item, area);
+                       }
+#endif
+
+               }
+       }
+
+       --render_depth;
+}
+
+void
+Item::add_child_bounding_boxes() const
+{
+       boost::optional<Rect> self;
+       Rect bbox;
+       bool have_one = false;
+
+       if (_bounding_box) {
+               bbox = _bounding_box.get();
+               have_one = true;
+       }
+
+       for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
+
+               if (!(*i)->visible()) {
+                       continue;
+               }
+
+               boost::optional<Rect> item_bbox = (*i)->bounding_box ();
+
+               if (!item_bbox) {
+                       continue;
+               }
+
+               Rect group_bbox = (*i)->item_to_parent (item_bbox.get ());
+               if (have_one) {
+                       bbox = bbox.extend (group_bbox);
+               } else {
+                       bbox = group_bbox;
+                       have_one = true;
+               }
+       }
+
+       if (!have_one) {
+               _bounding_box = boost::optional<Rect> ();
+       } else {
+               _bounding_box = bbox;
+       }
+}
+
+void
+Item::add (Item* i)
+{
+       /* XXX should really notify canvas about this */
+
+       _items.push_back (i);
+       i->reparent (this);
+       invalidate_lut ();
+       _bounding_box_dirty = true;
+}
+
+void
+Item::add_front (Item* i)
+{
+       /* XXX should really notify canvas about this */
+
+       _items.push_front (i);
+       i->reparent (this);
+       invalidate_lut ();
+       _bounding_box_dirty = true;
+}
+
+void
+Item::remove (Item* i)
+{
+
+       if (i->parent() != this) {
+               return;
+       }
+
+       /* we cannot call bounding_box() here because that will iterate over
+          _items, one of which (the argument, i) may be in the middle of
+          deletion, making it impossible to call compute_bounding_box()
+          on it.
+       */
+
+       if (_bounding_box) {
+               _pre_change_bounding_box = _bounding_box;
+       } else {
+               _pre_change_bounding_box = Rect();
+       }
+
+       i->unparent ();
+       _items.remove (i);
+       invalidate_lut ();
+       _bounding_box_dirty = true;
+
+       end_change ();
+}
+
+void
+Item::clear (bool with_delete)
+{
+       begin_change ();
+
+       clear_items (with_delete);
+
+       invalidate_lut ();
+       _bounding_box_dirty = true;
+
+       end_change ();
+}
+
+void
+Item::clear_items (bool with_delete)
+{
+       for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ) {
+
+               list<Item*>::iterator tmp = i;
+               Item *item = *i;
+
+               ++tmp;
+
+               /* remove from list before doing anything else, because we
+                * don't want to find the item in _items during any activity
+                * driven by unparent-ing or deletion.
+                */
+
+               _items.erase (i);
+               item->unparent ();
+
+               if (with_delete) {
+                       delete item;
+               }
+
+               i = tmp;
+       }
+}
+
+void
+Item::raise_child_to_top (Item* i)
+{
+       if (!_items.empty()) {
+               if (_items.back() == i) {
+                       return;
+               }
+       }
+
+       _items.remove (i);
+       _items.push_back (i);
+
+       invalidate_lut ();
+        redraw ();
+}
+
+void
+Item::raise_child (Item* i, int levels)
+{
+       list<Item*>::iterator j = find (_items.begin(), _items.end(), i);
+       assert (j != _items.end ());
+
+       ++j;
+       _items.remove (i);
+
+       while (levels > 0 && j != _items.end ()) {
+               ++j;
+               --levels;
+       }
+
+       _items.insert (j, i);
+       invalidate_lut ();
+        redraw ();
+}
+
+void
+Item::lower_child_to_bottom (Item* i)
+{
+       if (!_items.empty()) {
+               if (_items.front() == i) {
+                       return;
+               }
+       }
+       _items.remove (i);
+       _items.push_front (i);
+       invalidate_lut ();
+        redraw ();
+}
+
+void
+Item::ensure_lut () const
+{
+       if (!_lut) {
+               _lut = new DumbLookupTable (*this);
+       }
+}
+
+void
+Item::invalidate_lut () const
+{
+       delete _lut;
+       _lut = 0;
+}
+
+void
+Item::child_changed ()
+{
+       invalidate_lut ();
+       _bounding_box_dirty = true;
+
+       if (_parent) {
+               _parent->child_changed ();
+       }
+}
+
+void
+Item::add_items_at_point (Duple const point, vector<Item const *>& items) const
+{
+       boost::optional<Rect> const bbox = bounding_box ();
+
+       /* Point is in window coordinate system */
+
+       if (!bbox || !item_to_window (bbox.get()).contains (point)) {
+               return;
+       }
+
+       /* recurse and add any items within our group that contain point.
+          Our children are only considered visible if we are, and similarly
+          only if we do not ignore events.
+       */
+
+       vector<Item*> our_items;
+
+       if (!_items.empty() && visible() && !_ignore_events) {
+               ensure_lut ();
+               our_items = _lut->items_at_point (point);
+       }
+
+       if (!our_items.empty() || covers (point)) {
+               /* this adds this item itself to the list of items at point */
+               items.push_back (this);
+       }
+
+       for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
+               (*i)->add_items_at_point (point, items);
+       }
+}
+
+void
+Item::set_tooltip (const std::string& s)
+{
+       _tooltip = s;
+}
+
+void
+Item::start_tooltip_timeout ()
+{
+       if (!_tooltip.empty()) {
+               _canvas->start_tooltip_timeout (this);
+       }
+}
+
+void
+Item::stop_tooltip_timeout ()
+{
+       _canvas->stop_tooltip_timeout ();
+}
+
+void
+Item::dump (ostream& o) const
+{
+       boost::optional<ArdourCanvas::Rect> bb = bounding_box();
+
+       o << _canvas->indent() << whatami() << ' ' << this << " self-Visible ? " << self_visible() << " visible ? " << visible();
+       o << " @ " << position();
+
+#ifdef CANVAS_DEBUG
+       if (!name.empty()) {
+               o << ' ' << name;
+       }
+#endif
+
+       if (bb) {
+               o << endl << _canvas->indent() << "\tbbox: " << bb.get();
+               o << endl << _canvas->indent() << "\tCANVAS bbox: " << item_to_canvas (bb.get());
+       } else {
+               o << " bbox unset";
+       }
+
+       o << endl;
+
+       if (!_items.empty()) {
+
+#ifdef CANVAS_DEBUG
+               o << _canvas->indent();
+               o << " @ " << position();
+               o << " Items: " << _items.size();
+               o << " Self-Visible ? " << self_visible();
+               o << " Visible ? " << visible();
+
+               boost::optional<Rect> bb = bounding_box();
+
+               if (bb) {
+                       o << endl << _canvas->indent() << "  bbox: " << bb.get();
+                       o << endl << _canvas->indent() << "  CANVAS bbox: " << item_to_canvas (bb.get());
+               } else {
+                       o << "  bbox unset";
+               }
+
+               o << endl;
+#endif
+
+               ArdourCanvas::dump_depth++;
+
+               for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
+                       o << **i;
+               }
+
+               ArdourCanvas::dump_depth--;
+       }
+}
+
 ostream&
 ArdourCanvas::operator<< (ostream& o, const Item& i)
 {