+ if (within_items.empty()) {
+
+ /* no items at point, just send leave event below */
+ _new_current_item = 0;
+
+ } else {
+
+ if (within_items.front() == _current_item) {
+ /* uppermost item at point is already _current_item */
+ DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
+ return;
+ }
+
+ _new_current_item = const_cast<Item*> (within_items.front());
+ }
+
+ if (_new_current_item != _current_item) {
+ deliver_enter_leave (point, state);
+ }
+
+ if (_current_item) {
+ DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("CURRENT ITEM %1/%2\n", _new_current_item->whatami(), _current_item->name));
+ } else {
+ DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, "--- no current item\n");
+ }
+
+}
+
+/** Deliver a series of enter & leave events based on the pointer position being at window
+ * coordinate @param point, and pointer @param state (modifier keys, etc)
+ */
+void
+GtkCanvas::deliver_enter_leave (Duple const & point, int state)
+{
+ /* setup enter & leave event structures */
+
+ Glib::RefPtr<Gdk::Window> win = get_window();
+
+ if (!win) {
+ return;
+ }
+
+ GdkEventCrossing enter_event;
+ enter_event.type = GDK_ENTER_NOTIFY;
+ enter_event.window = win->gobj();
+ enter_event.send_event = 0;
+ enter_event.subwindow = 0;
+ enter_event.mode = GDK_CROSSING_NORMAL;
+ enter_event.focus = FALSE;
+ enter_event.state = state;
+
+ /* Events delivered to canvas items are expected to be in canvas
+ * coordinates but @param point is in window coordinates.
+ */
+
+ Duple c = window_to_canvas (point);
+ enter_event.x = c.x;
+ enter_event.y = c.y;
+
+ GdkEventCrossing leave_event = enter_event;
+ leave_event.type = GDK_LEAVE_NOTIFY;
+
+ Item* i;
+ GdkNotifyType enter_detail = GDK_NOTIFY_UNKNOWN;
+ GdkNotifyType leave_detail = GDK_NOTIFY_UNKNOWN;
+ vector<Item*> items_to_leave_virtual;
+ vector<Item*> items_to_enter_virtual;
+
+ if (_new_current_item == 0) {
+
+ leave_detail = GDK_NOTIFY_UNKNOWN;
+
+ if (_current_item) {
+
+ /* no current item, so also send virtual leave events to the
+ * entire heirarchy for the current item
+ */
+
+ for (i = _current_item->parent(); i ; i = i->parent()) {
+ items_to_leave_virtual.push_back (i);
+ }
+ }
+
+ } else if (_current_item == 0) {
+
+ enter_detail = GDK_NOTIFY_UNKNOWN;
+
+ /* no current item, so also send virtual enter events to the
+ * entire heirarchy for the new item
+ */
+
+ for (i = _new_current_item->parent(); i ; i = i->parent()) {
+ items_to_enter_virtual.push_back (i);
+ }
+
+ } else if (_current_item->is_descendant_of (*_new_current_item)) {
+
+ /* move from descendant to ancestor (X: "_current_item is an
+ * inferior ("child") of _new_current_item")
+ *
+ * Deliver "virtual" leave notifications to all items in the
+ * heirarchy between current and new_current.
+ */
+
+ for (i = _current_item->parent(); i && i != _new_current_item; i = i->parent()) {
+ items_to_leave_virtual.push_back (i);
+ }
+
+ enter_detail = GDK_NOTIFY_INFERIOR;
+ leave_detail = GDK_NOTIFY_ANCESTOR;
+
+ } else if (_new_current_item->is_descendant_of (*_current_item)) {
+ /* move from ancestor to descendant (X: "_new_current_item is
+ * an inferior ("child") of _current_item")
+ *
+ * Deliver "virtual" enter notifications to all items in the
+ * heirarchy between current and new_current.
+ */
+
+ for (i = _new_current_item->parent(); i && i != _current_item; i = i->parent()) {
+ items_to_enter_virtual.push_back (i);
+ }
+
+ enter_detail = GDK_NOTIFY_ANCESTOR;
+ leave_detail = GDK_NOTIFY_INFERIOR;
+
+ } else {
+
+ Item const * common_ancestor = _current_item->closest_ancestor_with (*_new_current_item);
+
+ /* deliver virtual leave events to everything between _current
+ * and common_ancestor.
+ */
+
+ for (i = _current_item->parent(); i && i != common_ancestor; i = i->parent()) {
+ items_to_leave_virtual.push_back (i);
+ }
+
+ /* deliver virtual enter events to everything between
+ * _new_current and common_ancestor.
+ */
+
+ for (i = _new_current_item->parent(); i && i != common_ancestor; i = i->parent()) {
+ items_to_enter_virtual.push_back (i);
+ }
+
+ enter_detail = GDK_NOTIFY_NONLINEAR;
+ leave_detail = GDK_NOTIFY_NONLINEAR;
+ }
+
+
+ if (_current_item && !_current_item->ignore_events ()) {
+ leave_event.detail = leave_detail;
+ _current_item->Event ((GdkEvent*)&leave_event);
+ DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("LEAVE %1/%2\n", _current_item->whatami(), _current_item->name));
+ }
+
+ leave_event.detail = GDK_NOTIFY_VIRTUAL;
+ enter_event.detail = GDK_NOTIFY_VIRTUAL;
+
+ for (vector<Item*>::iterator it = items_to_leave_virtual.begin(); it != items_to_leave_virtual.end(); ++it) {
+ if (!(*it)->ignore_events()) {
+ DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("leave %1/%2\n", (*it)->whatami(), (*it)->name));
+ (*it)->Event ((GdkEvent*)&leave_event);
+ }
+ }
+
+ for (vector<Item*>::iterator it = items_to_enter_virtual.begin(); it != items_to_enter_virtual.end(); ++it) {
+ if (!(*it)->ignore_events()) {
+ DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("enter %1/%2\n", (*it)->whatami(), (*it)->name));
+ (*it)->Event ((GdkEvent*)&enter_event);
+ // std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
+ }
+ }
+
+ if (_new_current_item && !_new_current_item->ignore_events()) {
+ enter_event.detail = enter_detail;
+ DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("ENTER %1/%2\n", _new_current_item->whatami(), _new_current_item->name));
+ start_tooltip_timeout (_new_current_item);
+ _new_current_item->Event ((GdkEvent*)&enter_event);
+ }
+
+ _current_item = _new_current_item;