notable changes to try to improve most of enter/leave handling for canvas items
authorPaul Davis <paul@linuxaudiosystems.com>
Thu, 31 Oct 2013 03:36:30 +0000 (23:36 -0400)
committerPaul Davis <paul@linuxaudiosystems.com>
Thu, 31 Oct 2013 03:36:30 +0000 (23:36 -0400)
gtk2_ardour/audio_region_view.cc
gtk2_ardour/automation_line.cc
gtk2_ardour/editor_canvas_events.cc
gtk2_ardour/time_axis_view_item.cc
libs/canvas/canvas.cc
libs/canvas/canvas/canvas.h
libs/canvas/canvas/item.h
libs/canvas/group.cc
libs/canvas/item.cc

index 0159022f87c0276999dc3f8783a2f1e69153bbbc..0857945669f2425b5b5519ffb3fc28df4f0f8e6a 100644 (file)
@@ -172,17 +172,17 @@ AudioRegionView::init (Gdk::Color const & basic_color, bool wfd)
        if (!_recregion) {
                fade_in_handle = new ArdourCanvas::Rectangle (group);
                CANVAS_DEBUG_NAME (fade_in_handle, string_compose ("fade in handle for %1", region()->name()));
-               fade_in_handle->set_fill_color (UINT_RGBA_CHANGE_A (fill_color, 0));
-               fade_in_handle->set_outline_color (RGBA_TO_UINT (0, 0, 0, 0));
-
+               fade_in_handle->set_outline_color (RGBA_TO_UINT (0, 0, 0, 255));
+               fade_in_handle->set_fill_color (UINT_RGBA_CHANGE_A (fade_color, 255));
                fade_in_handle->set_data ("regionview", this);
+               fade_in_handle->hide ();
 
                fade_out_handle = new ArdourCanvas::Rectangle (group);
                CANVAS_DEBUG_NAME (fade_out_handle, string_compose ("fade out handle for %1", region()->name()));
-               fade_out_handle->set_fill_color (UINT_RGBA_CHANGE_A (fill_color, 0));
-               fade_out_handle->set_outline_color (RGBA_TO_UINT (0, 0, 0, 0));
-
+               fade_out_handle->set_outline_color (RGBA_TO_UINT (0, 0, 0, 255));
+               fade_out_handle->set_fill_color (UINT_RGBA_CHANGE_A (fade_color, 255)); 
                fade_out_handle->set_data ("regionview", this);
+               fade_out_handle->hide ();
        }
 
        setup_fade_handle_positions ();
@@ -410,10 +410,9 @@ AudioRegionView::reset_width_dependent_items (double pixel_width)
                if (pixel_width <= 6.0 || _height < 5.0 || !trackview.session()->config.get_show_region_fades()) {
                        fade_in_handle->hide();
                        fade_out_handle->hide();
-               }
-               else {
-                       fade_in_handle->show();
-                       fade_out_handle->show();
+               } else {
+                       //fade_in_handle->show();
+                       //fade_out_handle->show();
                }
        }
 
@@ -533,8 +532,6 @@ AudioRegionView::reset_fade_in_shape_width (boost::shared_ptr<AudioRegion> ar, f
                return;
        }
 
-       fade_in_handle->show ();
-
        /* smallest size for a fade is 64 frames */
 
        width = std::max ((framecnt_t) 64, width);
@@ -629,8 +626,6 @@ AudioRegionView::reset_fade_out_shape_width (boost::shared_ptr<AudioRegion> ar,
                return;
        }
 
-       fade_out_handle->show ();
-
        /* smallest size for a fade is 64 frames */
 
        width = std::max ((framecnt_t) 64, width);
@@ -1374,11 +1369,13 @@ AudioRegionView::entered (bool internal_editing)
                gain_line->add_visibility (AutomationLine::ControlPoints);
        }
 
+       cerr << "Entered! ARV for " << _region->name() << endl;
+
        if (fade_in_handle && !internal_editing) {
-               fade_in_handle->set_outline_color (RGBA_TO_UINT (0, 0, 0, 255));
-               fade_in_handle->set_fill_color (UINT_RGBA_CHANGE_A (fade_color, 255));
-               fade_out_handle->set_outline_color (RGBA_TO_UINT (0, 0, 0, 255));
-               fade_out_handle->set_fill_color (UINT_RGBA_CHANGE_A (fade_color, 255));
+               fade_in_handle->show ();
+               fade_out_handle->show ();
+               fade_out_handle->raise_to_top ();
+               fade_in_handle->raise_to_top ();
        }
 }
 
@@ -1392,11 +1389,11 @@ AudioRegionView::exited ()
                gain_line->remove_visibility (AutomationLine::ControlPoints);
        }
 
+       cerr << "Left! ARV for " << _region->name() << endl;
+
        if (fade_in_handle) {
-               fade_in_handle->set_outline_color (RGBA_TO_UINT (0, 0, 0, 0));
-               fade_in_handle->set_fill_color (UINT_RGBA_CHANGE_A (fade_color, 0));
-               fade_out_handle->set_outline_color (RGBA_TO_UINT (0, 0, 0, 0));
-               fade_out_handle->set_fill_color (UINT_RGBA_CHANGE_A (fade_color, 0));
+               fade_in_handle->hide ();
+               fade_out_handle->hide ();
        }
 }
 
index 17e07924778ff40e45aa38eefa35a0f2f1b4e6ff..eb6a54e1a3e1f8915f6611a0a6a69f65a2af58bf 100644 (file)
@@ -35,6 +35,8 @@
 
 #include "evoral/Curve.hpp"
 
+#include "canvas/debug.h"
+
 #include "automation_line.h"
 #include "control_point.h"
 #include "gui_thread.h"
@@ -90,8 +92,10 @@ AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanv
        _height = 0;
 
        group = new ArdourCanvas::Group (&parent);
+       CANVAS_DEBUG_NAME (group, "region gain envelope group");
 
        line = new ArdourCanvas::Curve (group);
+       CANVAS_DEBUG_NAME (line, "region gain envelope line");
        line->set_data ("line", this);
        line->set_outline_width (2.0);
 
index 5bd0060d231d8eda915d4ec02464a1319bc2d0d9..b119b989d1cbbc8aced0f528df37c207633cb5b0 100644 (file)
@@ -267,12 +267,11 @@ Editor::canvas_region_view_event (GdkEvent *event, ArdourCanvas::Item* item, Reg
                break;
 
        case GDK_ENTER_NOTIFY:
-               set_entered_track (&rv->get_time_axis_view ());
                set_entered_regionview (rv);
+               ret = true;
                break;
 
        case GDK_LEAVE_NOTIFY:
-               set_entered_track (0);
                set_entered_regionview (0);
                break;
 
@@ -309,6 +308,7 @@ Editor::canvas_stream_view_event (GdkEvent *event, ArdourCanvas::Item* item, Rou
 
        case GDK_ENTER_NOTIFY:
                set_entered_track (tv);
+               ret = true;
                break;
 
        case GDK_LEAVE_NOTIFY:
@@ -462,9 +462,13 @@ Editor::canvas_fade_in_event (GdkEvent *event, ArdourCanvas::Item* item, AudioRe
 
        }
 
-       /* proxy for the regionview */
+       /* proxy for the regionview, except enter/leave events */
 
-       return canvas_region_view_event (event, rv->get_canvas_group(), rv);
+       if (event->type == GDK_ENTER_NOTIFY || event->type == GDK_LEAVE_NOTIFY) {
+               return true;
+       } else {
+               return canvas_region_view_event (event, rv->get_canvas_group(), rv);
+       }
 }
 
 bool
@@ -497,12 +501,10 @@ Editor::canvas_fade_in_handle_event (GdkEvent *event, ArdourCanvas::Item* item,
                break;
 
        case GDK_ENTER_NOTIFY:
-               set_entered_regionview (rv);
                ret = enter_handler (item, event, FadeInHandleItem);
                break;
 
        case GDK_LEAVE_NOTIFY:
-               set_entered_regionview (0);
                ret = leave_handler (item, event, FadeInHandleItem);
                break;
 
@@ -544,9 +546,13 @@ Editor::canvas_fade_out_event (GdkEvent *event, ArdourCanvas::Item* item, AudioR
 
        }
 
-       /* proxy for the regionview */
+       /* proxy for the regionview, except enter/leave events */
 
-       return canvas_region_view_event (event, rv->get_canvas_group(), rv);
+       if (event->type == GDK_ENTER_NOTIFY || event->type == GDK_LEAVE_NOTIFY) {
+               return true;
+       } else {
+               return canvas_region_view_event (event, rv->get_canvas_group(), rv);
+       }
 }
 
 bool
@@ -579,12 +585,10 @@ Editor::canvas_fade_out_handle_event (GdkEvent *event, ArdourCanvas::Item* item,
                break;
 
        case GDK_ENTER_NOTIFY:
-               set_entered_regionview (rv);
                ret = enter_handler (item, event, FadeOutHandleItem);
                break;
 
        case GDK_LEAVE_NOTIFY:
-               set_entered_regionview (0);
                ret = leave_handler (item, event, FadeOutHandleItem);
                break;
 
@@ -783,12 +787,10 @@ Editor::canvas_frame_handle_event (GdkEvent* event, ArdourCanvas::Item* item, Re
                ret = motion_handler (item, event);
                break;
        case GDK_ENTER_NOTIFY:
-               set_entered_regionview (rv);
                ret = enter_handler (item, event, type);
                break;
 
        case GDK_LEAVE_NOTIFY:
-               set_entered_regionview (0);
                ret = leave_handler (item, event, type);
                break;
 
@@ -827,12 +829,10 @@ Editor::canvas_region_view_name_highlight_event (GdkEvent* event, ArdourCanvas::
                ret = true; // force this to avoid progagating the event into the regionview
                break;
        case GDK_ENTER_NOTIFY:
-               set_entered_regionview (rv);
                ret = enter_handler (item, event, RegionViewNameHighlight);
                break;
 
        case GDK_LEAVE_NOTIFY:
-               set_entered_regionview (0);
                ret = leave_handler (item, event, RegionViewNameHighlight);
                break;
 
@@ -869,12 +869,10 @@ Editor::canvas_region_view_name_event (GdkEvent *event, ArdourCanvas::Item* item
                ret = motion_handler (item, event);
                break;
        case GDK_ENTER_NOTIFY:
-               set_entered_regionview (rv);
                ret = enter_handler (item, event, RegionViewName);
                break;
 
        case GDK_LEAVE_NOTIFY:
-               set_entered_regionview (0);
                ret = leave_handler (item, event, RegionViewName);
                break;
 
index 6a898855bd70f8237fbffe722d5d1590087088f2..a200b71f9f93e054060d4f49a2165b2c3ef4ca6c 100644 (file)
@@ -184,6 +184,7 @@ TimeAxisViewItem::init (ArdourCanvas::Group* parent, double fpp, Gdk::Color cons
                                                     ArdourCanvas::Rect (0.0, 1.0, 
                                                                         trackview.editor().sample_to_pixel(duration), 
                                                                         trackview.current_height()));
+
                CANVAS_DEBUG_NAME (frame, string_compose ("frame for %1", get_item_name()));
 
                if (_recregion) {
index 30ef25e944353def853b1fccb47e8eeb62aac358..b616e6cf591611f9e72bb77b3d69e473ed89c0cc 100644 (file)
@@ -234,8 +234,8 @@ Canvas::queue_draw_item_area (Item* item, Rect area)
 
 /** Construct a GtkCanvas */
 GtkCanvas::GtkCanvas ()
-       : _current_item (0)
-       , _grabbed_item (0)
+       : _grabbed_item (0)
+       , _focused_item (0)
 {
        /* these are the events we want to know about */
        add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK);
@@ -294,10 +294,11 @@ GtkCanvas::enter_leave_items (int state)
 void
 GtkCanvas::enter_leave_items (Duple const & point, int state)
 {
-       /* find the items at the given position */
+       /* we do not enter/leave items during a drag/grab */
 
-       vector<Item const *> items;
-       _root.add_items_at_point (point, items);
+       if (_grabbed_item) {
+               return;
+       }
 
        GdkEventCrossing enter_event;
        enter_event.type = GDK_ENTER_NOTIFY;
@@ -310,69 +311,88 @@ GtkCanvas::enter_leave_items (Duple const & point, int state)
        enter_event.state = state;
        enter_event.x = point.x;
        enter_event.y = point.y;
+       enter_event.detail = GDK_NOTIFY_UNKNOWN;
 
        GdkEventCrossing leave_event = enter_event;
        leave_event.type = GDK_LEAVE_NOTIFY;
-       leave_event.detail = GDK_NOTIFY_ANCESTOR;
-       leave_event.subwindow = 0;
-
-       if (items.empty()) {
-               if (_current_item) {
-                       /* leave event */
-                       // cerr << "E/L: left item " << _current_item->whatami() << '/' << _current_item->name << " for ... nada" << endl;
-                       _current_item->Event (reinterpret_cast<GdkEvent*> (&leave_event));
-                       _current_item = 0;
-               }
-               return;
-       }
 
-       /* items is sorted from top to bottom, so reverse through it from bottom
-        * to top to find the lowest, first event-sensitive item and notify that
-        * we have entered it
-        */
+       /* find the items at the given position */
+
+       vector<Item const *> items;
+       _root.add_items_at_point (point, items);
+
+       /* put all items at point that are event-sensitive and visible into within_items, and if this
+          is a new addition, also put them into newly_entered for later deliver of enter events.
+       */
        
-       // cerr << "E/L: " << items.size() << " to check at " << point << endl;
-#ifdef CANVAS_DEBUG
-       // for (vector<Item const*>::const_reverse_iterator i = items.rbegin(); i != items.rend(); ++i) {
-       // cerr << '\t' << (*i)->whatami() << ' ' << (*i)->name << " ignore ? " << (*i)->ignore_events() << " current ? " << (_current_item == (*i)) << endl;
-        // }
-#endif
-       // cerr << "------------\n";
+       vector<Item const *>::const_iterator i;
+       vector<Item const *> newly_entered;
+       Item const * new_item;
 
-       for (vector<Item const*>::const_reverse_iterator i = items.rbegin(); i != items.rend(); ++i) {
+       for (i = items.begin(); i != items.end(); ++i) {
 
-               Item const *  new_item = *i;
-#ifdef CANVAS_DEBUG
-               // cerr << "\tE/L check out " << new_item->whatami() << ' ' << new_item->name << " ignore ? " << new_item->ignore_events() << " current ? " << (_current_item == new_item) << endl;
-#endif
-               if (new_item->ignore_events()) {
-                       // cerr << "continue1\n";
+               new_item = *i;
+               
+               if (new_item->ignore_events() || !new_item->visible()) {
                        continue;
                }
 
-               if (_current_item == new_item) {
-                       // cerr << "continue2\n";
-                       continue;
+               pair<set<Item const *>::iterator,bool> res = within_items.insert (new_item);
+
+               if (res.second) {
+                       newly_entered.push_back (new_item);
                }
+       }
+
+       /* for every item in "within_items", check that we are still within them. if not,
+          send a leave event, and remove them from "within_items"
+       */
 
-               if (_current_item) {
-                       /* leave event */
-                       DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("Leave %1 %2\n", _current_item->whatami(), _current_item->name));
-                       _current_item->Event (reinterpret_cast<GdkEvent*> (&leave_event));
-                       queue_draw ();
+       for (set<Item const *>::const_iterator i = within_items.begin(); i != within_items.end(); ) {
+
+               set<Item const *>::const_iterator tmp = i;
+               ++tmp;
+
+               new_item = *i;
+
+               boost::optional<Rect> bbox = new_item->bounding_box();
+
+
+               if (bbox) {
+                       if (!new_item->item_to_canvas (bbox.get()).contains (point)) {
+                               leave_event.detail = GDK_NOTIFY_UNKNOWN;
+                               cerr << string_compose ("\tLeave %1 %2\n", new_item->whatami(), new_item->name);
+                               DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("Leave %1 %2\n", new_item->whatami(), new_item->name));
+                               (*i)->Event (reinterpret_cast<GdkEvent*> (&leave_event));
+                               within_items.erase (i);
+                       }
                }
 
-               if (new_item && _current_item != new_item) {
-                       /* enter event */
-                       _current_item = new_item;
-                       DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("Enter %1 %2\n", _current_item->whatami(), _current_item->name));
-                       _current_item->Event (reinterpret_cast<GdkEvent*> (&enter_event));
-                       queue_draw ();
+               i = tmp;
+       }
+       
+       /* for every item in "newly_entered", send an enter event (and propagate it up the
+          item tree until it is handled 
+       */
+
+       for (vector<Item const*>::const_iterator i = newly_entered.begin(); i != newly_entered.end(); ++i) {
+               new_item = *i;
+
+
+               if (new_item->Event (reinterpret_cast<GdkEvent*> (&enter_event))) {
+                       cerr << string_compose ("\tEntered %1 %2\n", new_item->whatami(), new_item->name);
+                       DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("Enter %1 %2\n", new_item->whatami(), new_item->name));
                        break;
                }
+       }
 
-               // cerr << "Loop around again\n";
+#if 0
+       cerr << "Within:\n";
+       for (set<Item const *>::const_iterator i = within_items.begin(); i != within_items.end(); ++i) {
+               cerr << '\t' << (*i)->whatami() << '/' << (*i)->name << endl;
        }
+       cerr << "----\n";
+#endif
 }
 
 /** Deliver an event to the appropriate item; either the grabbed item, or
@@ -451,15 +471,19 @@ GtkCanvas::item_going_away (Item* item, boost::optional<Rect> bounding_box)
                queue_draw_item_area (item, bounding_box.get ());
        }
        
-       if (_current_item == item) {
-               _current_item = 0;
-               queue_draw ();
-       }
+       /* no need to send a leave event to this item, since it is going away 
+        */
+
+       within_items.erase (item);
 
        if (_grabbed_item == item) {
                _grabbed_item = 0;
        }
 
+       if (_focused_item == item) {
+               _focused_item = 0;
+       }
+
        enter_leave_items (0); // no mouse state
        
 }
@@ -549,6 +573,22 @@ GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
        return motion_notify_handler ((GdkEventMotion*) &copy);
 }
 
+bool
+GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
+{
+       Duple where = window_to_canvas (Duple (ev->x, ev->y));
+       enter_leave_items (where, ev->state);
+       return true;
+}
+
+bool
+GtkCanvas::on_leave_notify_event (GdkEventCrossing* /*ev*/)
+{
+       cerr << "Clear all within items as we leave\n";
+       within_items.clear ();
+       return true;
+}
+
 /** Called to request a redraw of our canvas.
  *  @param area Area to redraw, in canvas coordinates.
  */
@@ -591,6 +631,7 @@ GtkCanvas::grab (Item* item)
        _grabbed_item = item;
 }
 
+
 /** `Ungrab' any item that was previously grabbed */
 void
 GtkCanvas::ungrab ()
@@ -599,6 +640,24 @@ GtkCanvas::ungrab ()
        _grabbed_item = 0;
 }
 
+/** Set keyboard focus on an item, so that all keyboard events are sent to that item until the focus
+ *  moves elsewhere.
+ *  @param item Item to grab.
+ */
+void
+GtkCanvas::focus (Item* item)
+{
+       _focused_item = item;
+}
+
+void
+GtkCanvas::unfocus (Item* item)
+{
+       if (item == _focused_item) {
+               _focused_item = 0;
+       }
+}
+
 /** @return The visible area of the canvas, in canvas coordinates */
 Rect
 GtkCanvas::visible_area () const
index e65abf6b2708e8bb8371356f293521fc9fe85b06..05756858324fff8d9448a1d0667251c123fc9d2a 100644 (file)
 #ifndef __CANVAS_CANVAS_H__
 #define __CANVAS_CANVAS_H__
 
+#include <set>
+
 #include <gdkmm/window.h>
 #include <gtkmm/eventbox.h>
 #include <gtkmm/alignment.h>
 #include <cairomm/surface.h>
 #include <cairomm/context.h>
+
 #include "pbd/signals.h"
 #include "canvas/root_group.h"
 
@@ -63,6 +66,11 @@ public:
        /** called to ask the canvas' host to `ungrab' any grabbed item */
        virtual void ungrab () = 0;
 
+       /** called to ask the canvas' host to keyboard focus on an item */
+       virtual void focus (Item *) = 0;
+       /** called to ask the canvas' host to drop keyboard focus on an item */
+       virtual void unfocus (Item*) = 0;
+
        void render (Rect const &, Cairo::RefPtr<Cairo::Context> const &) const;
 
        /** @return root group */
@@ -126,6 +134,8 @@ public:
        void request_size (Duple);
        void grab (Item *);
        void ungrab ();
+       void focus (Item *);
+       void unfocus (Item*);
 
        Cairo::RefPtr<Cairo::Context> context ();
 
@@ -136,6 +146,8 @@ protected:
        bool on_button_press_event (GdkEventButton *);
        bool on_button_release_event (GdkEventButton* event);
        bool on_motion_notify_event (GdkEventMotion *);
+        bool on_enter_notify_event (GdkEventCrossing*);
+        bool on_leave_notify_event (GdkEventCrossing*);
        
        bool button_handler (GdkEventButton *);
        bool motion_notify_handler (GdkEventMotion *);
@@ -148,11 +160,12 @@ private:
        void item_going_away (Item *, boost::optional<Rect>);
        bool send_leave_event (Item const *, double, double) const;
 
-
-       /** the item that the mouse is currently over, or 0 */
-       Item const * _current_item;
+        /** Items that the pointer is currently within */
+        std::set<Item const *> within_items;
        /** the item that is currently grabbed, or 0 */
        Item const * _grabbed_item;
+        /** the item that currently has key focus or 0 */
+       Item const * _focused_item;
 };
 
 /** A GTK::Alignment with a GtkCanvas inside it plus some Gtk::Adjustments for
index 538ac8bb79bf115454c0059bcc9dfbbd1ac147b9..7cad50e8838f1fa61d6378055709a55afc8f7c4f 100644 (file)
@@ -88,6 +88,21 @@ public:
        Group* parent () const {
                return _parent;
        }
+    
+        uint32_t depth() const;
+        const Item* closest_ancestor_with (const Item& other) const;
+        bool common_ancestor_within (uint32_t, const Item& other) const;
+
+        /** returns true if this item is an ancestor of @param candidate,
+        * and false otherwise. 
+        */
+        bool is_ancestor_of (const Item& candidate) const {
+               return candidate.is_descendant_of (*this);
+       }
+        /** returns true if this Item is a descendant of @param candidate,
+        * and false otherwise. 
+        */
+        bool is_descendant_of (const Item& candidate) const;
 
        void set_position (Duple);
        void set_x_position (Coord);
index 63ce48fdf29adc65ec144a7f60634094741bdbf3..0e2c1dbf94b1f6f70d77e44af179ccee70c2b229 100644 (file)
@@ -74,8 +74,6 @@ Group::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
        ensure_lut ();
        vector<Item*> items = _lut->get (area);
 
-       ++render_depth;
-               
 #ifdef CANVAS_DEBUG
        if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
                cerr << string_compose ("%1GROUP %2 render %5 %3 items out of %4\n", 
@@ -83,6 +81,8 @@ Group::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
        }
 #endif
 
+       ++render_depth;
+               
        for (vector<Item*>::const_iterator i = items.begin(); i != items.end(); ++i) {
 
                if (!(*i)->visible ()) {
@@ -112,16 +112,18 @@ Group::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
                if (draw) {
 #ifdef CANVAS_DEBUG
                        if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
-                               cerr << _canvas->render_indent() << " render "
-                                    << ' ' 
-                                    << (*i)->whatami()
-                                    << ' '
-                                    << (*i)->name
-                                    << " item = " 
-                                    << item
-                                    << " intersect = "
-                                    << draw.get()
-                                    << endl;
+                               if (dynamic_cast<Group*>(*i) == 0) {
+                                       cerr << _canvas->render_indent() << "render "
+                                            << ' ' 
+                                            << (*i)->whatami()
+                                            << ' '
+                                            << (*i)->name
+                                            << " item = " 
+                                            << item
+                                            << " intersect = "
+                                            << draw.get()
+                                            << endl;
+                               }
                        }
 #endif
 
index 79351846db88e4218bf29df56c9de68029c81bce..534ae6d52e214f9c94728a0259ba6de08276af35 100644 (file)
@@ -287,6 +287,103 @@ Item::reparent (Group* new_parent)
        _parent->add (this);
 }
 
+bool
+Item::common_ancestor_within (uint32_t limit, const Item& other) const
+{
+       uint32_t d1 = depth();
+       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) {
+                       i1 = i1->parent();
+                       d1--;
+                       limit--;
+               } else {
+                       i2 = i2->parent();
+                       d2--;
+                       limit--;
+               }
+               if (limit == 0) {
+                       return false;
+               }
+       }
+
+       /* now see if there is a common parent */
+
+       while (i1 != i2) {
+               if (i1) {
+                       i1 = i1->parent();
+               }
+               if (i2) {
+                       i2 = i2->parent ();
+               }
+
+               limit--;
+               if (limit == 0) {
+                       return false;
+               }
+       }
+       
+       return true;
+}
+
+const Item*
+Item::closest_ancestor_with (const Item& other) const
+{
+       uint32_t d1 = depth();
+       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) {
+                       i1 = i1->parent();
+                       d1--;
+               } else {
+                       i2 = i2->parent();
+                       d2--;
+               }
+       }
+
+       /* now see if there is a common parent */
+
+       while (i1 != i2) {
+               if (i1) {
+                       i1 = i1->parent();
+               }
+               if (i2) {
+                       i2 = i2->parent ();
+               }
+       }
+       
+       return i1;
+}
+
+bool
+Item::is_descendant_of (const Item& candidate) const
+{
+       Item const * i = _parent;
+
+       while (i) {
+               if (i == &candidate) {
+                       return true;
+               }
+               i = i->parent();
+       }
+
+       return false;
+}
+
 void
 Item::grab_focus ()
 {
@@ -437,9 +534,22 @@ Item::whatami () const
        return type.substr (type.find_last_of (':') + 1);
 }
 
+uint32_t
+Item::depth () const
+{
+       Item* i = _parent;
+       int d = 0;
+       while (i) {
+               ++d;
+               i = i->parent();
+       }
+       return d;
+}
+
 ostream&
 ArdourCanvas::operator<< (ostream& o, const Item& i)
 {
        i.dump (o);
        return o;
 }
+