change event propagation to be based on parent/child lineage, not z-axis stacking, plus some more alterations to try to get enter/leave working

This commit is contained in:
Paul Davis 2013-12-12 10:03:33 -05:00
parent 9fb3247350
commit 88732abd01
8 changed files with 217 additions and 132 deletions

View file

@ -22,6 +22,7 @@
* @brief Implementation of the main canvas classes. * @brief Implementation of the main canvas classes.
*/ */
#include <list>
#include <cassert> #include <cassert>
#include <gtkmm/adjustment.h> #include <gtkmm/adjustment.h>
#include <gtkmm/label.h> #include <gtkmm/label.h>
@ -256,7 +257,9 @@ Canvas::queue_draw_item_area (Item* item, Rect area)
/** Construct a GtkCanvas */ /** Construct a GtkCanvas */
GtkCanvas::GtkCanvas () GtkCanvas::GtkCanvas ()
: _grabbed_item (0) : _current_item (0)
, _new_current_item (0)
, _grabbed_item (0)
, _focused_item (0) , _focused_item (0)
{ {
/* these are the events we want to know about */ /* these are the events we want to know about */
@ -299,12 +302,10 @@ GtkCanvas::enter_leave_items (Duple const & point, int state)
enter_event.send_event = 0; enter_event.send_event = 0;
enter_event.subwindow = 0; enter_event.subwindow = 0;
enter_event.mode = GDK_CROSSING_NORMAL; enter_event.mode = GDK_CROSSING_NORMAL;
enter_event.detail = GDK_NOTIFY_NONLINEAR;
enter_event.focus = FALSE; enter_event.focus = FALSE;
enter_event.state = state; enter_event.state = state;
enter_event.x = point.x; enter_event.x = point.x;
enter_event.y = point.y; enter_event.y = point.y;
enter_event.detail = GDK_NOTIFY_UNKNOWN;
GdkEventCrossing leave_event = enter_event; GdkEventCrossing leave_event = enter_event;
leave_event.type = GDK_LEAVE_NOTIFY; leave_event.type = GDK_LEAVE_NOTIFY;
@ -314,72 +315,86 @@ GtkCanvas::enter_leave_items (Duple const & point, int state)
vector<Item const *> items; vector<Item const *> items;
_root.add_items_at_point (point, 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 /* put all items at point that are event-sensitive and visible and NOT
is a new addition, also put them into newly_entered for later deliver of enter events. groups into within_items. Note that items is sorted from bottom to
top, but we're going to reverse that for within_items so that its
first item is the upper-most item that can be chosen as _current_item.
*/ */
vector<Item const *>::const_iterator i; vector<Item const *>::const_iterator i;
vector<Item const *> newly_entered; list<Item const *> within_items;
Item const * new_item;
for (i = items.begin(); i != items.end(); ++i) { for (i = items.begin(); i != items.end(); ++i) {
new_item = *i; Item const * new_item = *i;
if (new_item->ignore_events() || !new_item->visible()) { if (new_item->ignore_events() || dynamic_cast<Group const *>(new_item) != 0) {
continue; continue;
} }
pair<set<Item const *>::iterator,bool> res = within_items.insert (new_item); within_items.push_front (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, if (within_items.empty()) {
send a leave event, and remove them from "within_items" /* no items at point */
*/ if (_current_item) {
leave_event.detail = GDK_NOTIFY_UNKNOWN;
for (set<Item const *>::const_iterator i = within_items.begin(); i != within_items.end(); ) { DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("Leave %1 %2\n", _current_item->whatami(), _current_item->name));
deliver_event (reinterpret_cast<GdkEvent*> (&leave_event));
set<Item const *>::const_iterator tmp = i; _current_item = 0;
++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;
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);
}
} }
return;
i = tmp;
} }
/* for every item in "newly_entered", send an enter event (and propagate it up the if (within_items.front() == _current_item) {
item tree until it is handled /* uppermost item at point is already _current_item */
*/ return;
for (vector<Item const*>::const_iterator i = newly_entered.begin(); i != newly_entered.end(); ++i) {
new_item = *i;
new_item->Event (reinterpret_cast<GdkEvent*> (&enter_event));
DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("Enter %1 %2\n", new_item->whatami(), new_item->name));
} }
#if 1 _new_current_item = within_items.front();
cerr << "Within:\n";
for (set<Item const *>::const_iterator i = within_items.begin(); i != within_items.end(); ++i) { if (_current_item) {
cerr << '\t' << (*i)->whatami() << '/' << (*i)->name << endl;
if (_new_current_item->is_descendant_of (*_current_item)) {
leave_event.detail = GDK_NOTIFY_INFERIOR;
enter_event.detail = GDK_NOTIFY_ANCESTOR;
} else if (_current_item->is_descendant_of (*_new_current_item)) {
leave_event.detail = GDK_NOTIFY_ANCESTOR;
enter_event.detail = GDK_NOTIFY_INFERIOR;
} else {
leave_event.detail = GDK_NOTIFY_UNKNOWN;
enter_event.detail = GDK_NOTIFY_UNKNOWN;
}
std::cerr << "CROSS from "
<< _current_item->whatami() << '/' << _current_item->name
<< " to "
<< _new_current_item->whatami() << '/' << _new_current_item->name
<< " detail = " << leave_event.detail
<< '\n';
DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("Leave %1 %2\n", _current_item->whatami(), _current_item->name));
deliver_event (reinterpret_cast<GdkEvent*> (&leave_event));
_current_item = 0;
} else {
enter_event.detail = GDK_NOTIFY_UNKNOWN;
} }
cerr << "----\n";
#endif /* _new_current_item could potentially have been reset when handling
* the leave event. if it has, there is nothing to do here.
*/
if (!_new_current_item) {
return;
}
_current_item = _new_current_item;
deliver_event (reinterpret_cast<GdkEvent*> (&enter_event));
DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("Enter %1 %2\n", _current_item->whatami(), _current_item->name));
} }
/** Deliver an event to the appropriate item; either the grabbed item, or /** Deliver an event to the appropriate item; either the grabbed item, or
@ -388,7 +403,7 @@ GtkCanvas::enter_leave_items (Duple const & point, int state)
* @param event The event. * @param event The event.
*/ */
bool bool
GtkCanvas::deliver_event (Duple point, GdkEvent* event) GtkCanvas::deliver_event (GdkEvent* event)
{ {
/* Point in in canvas coordinate space */ /* Point in in canvas coordinate space */
@ -399,51 +414,35 @@ GtkCanvas::deliver_event (Duple point, GdkEvent* event)
return _grabbed_item->Event (event); return _grabbed_item->Event (event);
} }
/* find the items that exist at the event's position */ if (!_current_item) {
vector<Item const *> items; return false;
_root.add_items_at_point (point, items); }
DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("%1 possible items at %2 to deliver event to\n", items.size(), point)); /* run through the items from child to parent, until one claims the event */
/* run through the items under the event, from top to bottom, until one claims the event */ for (Item* item = const_cast<Item*> (_current_item); item; item = item->parent()) {
vector<Item const *>::const_reverse_iterator i = items.rbegin ();
while (i != items.rend()) {
if ((*i)->ignore_events ()) { if (item->ignore_events ()) {
// DEBUG_TRACE ( // DEBUG_TRACE (
// PBD::DEBUG::CanvasEvents, // PBD::DEBUG::CanvasEvents,
// string_compose ("canvas event ignored by %1 %2\n", (*i)->whatami(), (*i)->name.empty() ? "[unknown]" : (*i)->name) // string_compose ("canvas event ignored by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
// ); // );
++i;
continue; continue;
} }
if ((*i)->Event (event)) { if (item->Event (event)) {
/* this item has just handled the event */ /* this item has just handled the event */
DEBUG_TRACE ( DEBUG_TRACE (
PBD::DEBUG::CanvasEvents, PBD::DEBUG::CanvasEvents,
string_compose ("canvas event handled by %1 %2\n", (*i)->whatami(), (*i)->name.empty() ? "[unknown]" : (*i)->name) string_compose ("canvas event handled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name)
); );
return true; return true;
} }
DEBUG_TRACE ( DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas event left unhandled by %1 %2\n", item->whatami(), item->name.empty() ? "[unknown]" : item->name));
PBD::DEBUG::CanvasEvents,
string_compose ("canvas event left unhandled by %1 %2\n", (*i)->whatami(), (*i)->name.empty() ? "[unknown]" : (*i)->name)
);
++i;
} }
/* debugging */
if (PBD::debug_bits & PBD::DEBUG::CanvasEvents) {
while (i != items.rend()) {
DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas event not seen by %1\n", (*i)->name.empty() ? "[unknown]" : (*i)->name));
++i;
}
}
return false; return false;
} }
@ -461,7 +460,13 @@ GtkCanvas::item_going_away (Item* item, boost::optional<Rect> bounding_box)
/* no need to send a leave event to this item, since it is going away /* no need to send a leave event to this item, since it is going away
*/ */
within_items.erase (item); if (_new_current_item == item) {
_new_current_item = 0;
}
if (_current_item == item) {
_current_item = 0;
}
if (_grabbed_item == item) { if (_grabbed_item == item) {
_grabbed_item = 0; _grabbed_item = 0;
@ -520,8 +525,9 @@ GtkCanvas::on_button_press_event (GdkEventButton* ev)
for scroll if this GtkCanvas is in a GtkCanvasViewport. for scroll if this GtkCanvas is in a GtkCanvasViewport.
*/ */
enter_leave_items (where, ev->state);
DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press @ %1, %2 => %3\n", ev->x, ev->y, where)); DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press @ %1, %2 => %3\n", ev->x, ev->y, where));
return deliver_event (where, reinterpret_cast<GdkEvent*>(&copy)); return deliver_event (reinterpret_cast<GdkEvent*>(&copy));
} }
/** Handler for GDK button release events. /** Handler for GDK button release events.
@ -545,8 +551,9 @@ GtkCanvas::on_button_release_event (GdkEventButton* ev)
for scroll if this GtkCanvas is in a GtkCanvasViewport. for scroll if this GtkCanvas is in a GtkCanvasViewport.
*/ */
enter_leave_items (where, ev->state);
DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release @ %1, %2 => %3\n", ev->x, ev->y, where)); DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button release @ %1, %2 => %3\n", ev->x, ev->y, where));
return deliver_event (where, reinterpret_cast<GdkEvent*>(&copy)); return deliver_event (reinterpret_cast<GdkEvent*>(&copy));
} }
/** Handler for GDK motion events. /** Handler for GDK motion events.
@ -568,7 +575,7 @@ GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
/* Coordinates in "copy" will be canvas coordinates, /* Coordinates in "copy" will be canvas coordinates,
*/ */
DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas motion @ %1, %2\n", ev->x, ev->y)); // DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas motion @ %1, %2\n", ev->x, ev->y));
if (_grabbed_item) { if (_grabbed_item) {
/* if we have a grabbed item, it gets just the motion event, /* if we have a grabbed item, it gets just the motion event,
@ -587,7 +594,7 @@ GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
recompute the list in deliver_event. recompute the list in deliver_event.
*/ */
return deliver_event (point, reinterpret_cast<GdkEvent*> (&copy)); return deliver_event (reinterpret_cast<GdkEvent*> (&copy));
} }
bool bool
@ -601,10 +608,23 @@ GtkCanvas::on_enter_notify_event (GdkEventCrossing* ev)
bool bool
GtkCanvas::on_leave_notify_event (GdkEventCrossing* /*ev*/) GtkCanvas::on_leave_notify_event (GdkEventCrossing* /*ev*/)
{ {
within_items.clear (); _current_item = 0;
_new_current_item = 0;
return true; return true;
} }
bool
GtkCanvas::on_key_press_event (GdkEventKey* ev)
{
return false;
}
bool
GtkCanvas::on_key_release_event (GdkEventKey* ev)
{
return false;
}
/** Called to request a redraw of our canvas. /** Called to request a redraw of our canvas.
* @param area Area to redraw, in canvas coordinates. * @param area Area to redraw, in canvas coordinates.
*/ */

View file

@ -150,10 +150,12 @@ protected:
bool on_motion_notify_event (GdkEventMotion *); bool on_motion_notify_event (GdkEventMotion *);
bool on_enter_notify_event (GdkEventCrossing*); bool on_enter_notify_event (GdkEventCrossing*);
bool on_leave_notify_event (GdkEventCrossing*); bool on_leave_notify_event (GdkEventCrossing*);
bool on_key_press_event (GdkEventKey*);
bool on_key_release_event (GdkEventKey*);
bool button_handler (GdkEventButton *); bool button_handler (GdkEventButton *);
bool motion_notify_handler (GdkEventMotion *); bool motion_notify_handler (GdkEventMotion *);
bool deliver_event (Duple, GdkEvent *); bool deliver_event (GdkEvent *);
void enter_leave_items (int state); void enter_leave_items (int state);
void enter_leave_items (Duple const &, int state); void enter_leave_items (Duple const &, int state);
@ -162,8 +164,10 @@ private:
void item_going_away (Item *, boost::optional<Rect>); void item_going_away (Item *, boost::optional<Rect>);
bool send_leave_event (Item const *, double, double) const; bool send_leave_event (Item const *, double, double) const;
/** Items that the pointer is currently within */ /** Item currently chosen for event delivery based on pointer position */
std::set<Item const *> within_items; Item const * _current_item;
/** Item pending as _current_item */
Item const * _new_current_item;
/** the item that is currently grabbed, or 0 */ /** the item that is currently grabbed, or 0 */
Item const * _grabbed_item; Item const * _grabbed_item;
/** the item that currently has key focus or 0 */ /** the item that currently has key focus or 0 */

View file

@ -44,7 +44,7 @@ public:
std::list<Item*> const & items () const { std::list<Item*> const & items () const {
return _items; return _items;
} }
void raise_child_to_top (Item *); void raise_child_to_top (Item *);
void raise_child (Item *, int); void raise_child (Item *, int);
void lower_child_to_bottom (Item *); void lower_child_to_bottom (Item *);

View file

@ -34,50 +34,53 @@ class Group;
class LookupTable class LookupTable
{ {
public: public:
LookupTable (Group const &); LookupTable (Group const &);
virtual ~LookupTable (); virtual ~LookupTable ();
virtual std::vector<Item*> get (Rect const &) = 0; virtual std::vector<Item*> get (Rect const &) = 0;
virtual std::vector<Item*> items_at_point (Duple) const = 0; virtual std::vector<Item*> items_at_point (Duple const &) const = 0;
virtual bool has_item_at_point (Duple const & point) const = 0;
protected: protected:
Group const & _group; Group const & _group;
}; };
class DumbLookupTable : public LookupTable class DumbLookupTable : public LookupTable
{ {
public: public:
DumbLookupTable (Group const &); DumbLookupTable (Group const &);
std::vector<Item*> get (Rect const &); std::vector<Item*> get (Rect const &);
std::vector<Item*> items_at_point (Duple) const; std::vector<Item*> items_at_point (Duple const &) const;
bool has_item_at_point (Duple const & point) const;
}; };
class OptimizingLookupTable : public LookupTable class OptimizingLookupTable : public LookupTable
{ {
public: public:
OptimizingLookupTable (Group const &, int); OptimizingLookupTable (Group const &, int);
~OptimizingLookupTable (); ~OptimizingLookupTable ();
std::vector<Item*> get (Rect const &); std::vector<Item*> get (Rect const &);
std::vector<Item*> items_at_point (Duple) const; std::vector<Item*> items_at_point (Duple const &) const;
bool has_item_at_point (Duple const & point) const;
static int default_items_per_cell;
static int default_items_per_cell;
private:
private:
void area_to_indices (Rect const &, int &, int &, int &, int &) const;
void point_to_indices (Duple, int &, int &) const; void area_to_indices (Rect const &, int &, int &, int &, int &) const;
void point_to_indices (Duple, int &, int &) const;
friend class ::OptimizingLookupTableTest;
friend class ::OptimizingLookupTableTest;
typedef std::vector<Item*> Cell;
int _items_per_cell; typedef std::vector<Item*> Cell;
int _dimension; int _items_per_cell;
Duple _cell_size; int _dimension;
Duple _offset; Duple _cell_size;
Cell** _cells; Duple _offset;
bool _added; Cell** _cells;
bool _added;
}; };
} }

View file

@ -225,7 +225,6 @@ Curve::covers (Duple const & pc) const
const Coord dy2 = dy * dy; const Coord dy2 = dy * dy;
if ((dx2 < 2.0 && dy2 < 2.0) || (dx2 + dy2 < 4.0)) { if ((dx2 < 2.0 && dy2 < 2.0) || (dx2 + dy2 < 4.0)) {
std::cerr << whatami() << '/' << name << " COVERS " << point << '\n';
return true; return true;
} }
} }

View file

@ -293,22 +293,24 @@ Group::child_changed ()
void void
Group::add_items_at_point (Duple const point, vector<Item const *>& items) const Group::add_items_at_point (Duple const point, vector<Item const *>& items) const
{ {
/* Point is in canvas coordinate system */
boost::optional<Rect> const bbox = bounding_box (); boost::optional<Rect> const bbox = bounding_box ();
/* Point is in canvas coordinate system */
if (!bbox || !item_to_canvas (bbox.get()).contains (point)) { if (!bbox || !item_to_canvas (bbox.get()).contains (point)) {
return; return;
} }
/* this adds this group itself to the list of items at point */
Item::add_items_at_point (point, items);
/* now recurse and add any items within our group that contain point */ /* now recurse and add any items within our group that contain point */
ensure_lut (); ensure_lut ();
vector<Item*> our_items = _lut->items_at_point (point); vector<Item*> our_items = _lut->items_at_point (point);
if (!our_items.empty()) {
/* this adds this group itself to the list of items at point */
Item::add_items_at_point (point, items);
}
for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) { for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
(*i)->add_items_at_point (point, items); (*i)->add_items_at_point (point, items);
} }

View file

@ -50,7 +50,26 @@ DumbLookupTable::get (Rect const &)
} }
vector<Item *> vector<Item *>
DumbLookupTable::items_at_point (Duple point) const DumbLookupTable::items_at_point (Duple const & point) const
{
/* Point is in canvas coordinate system */
list<Item *> const & items (_group.items ());
vector<Item *> vitems;
for (list<Item *>::const_iterator i = items.begin(); i != items.end(); ++i) {
if ((*i)->covers (point)) {
// std::cerr << "\t\t" << (*i)->whatami() << '/' << (*i)->name << " covers " << point << std::endl;
vitems.push_back (*i);
}
}
return vitems;
}
bool
DumbLookupTable::has_item_at_point (Duple const & point) const
{ {
/* Point is in canvas coordinate system */ /* Point is in canvas coordinate system */
@ -64,12 +83,13 @@ DumbLookupTable::items_at_point (Duple point) const
} }
if ((*i)->covers (point)) { if ((*i)->covers (point)) {
std::cerr << "\t\t" << (*i)->whatami() << '/' << (*i)->name << " covers " << point << std::endl; // std::cerr << "\t\t" << (*i)->whatami() << '/' << (*i)->name << " covers " << point << std::endl;
vitems.push_back (*i); return true;
} }
} }
return vitems; return false;
} }
OptimizingLookupTable::OptimizingLookupTable (Group const & group, int items_per_cell) OptimizingLookupTable::OptimizingLookupTable (Group const & group, int items_per_cell)
@ -191,7 +211,7 @@ OptimizingLookupTable::point_to_indices (Duple point, int& x, int& y) const
} }
vector<Item*> vector<Item*>
OptimizingLookupTable::items_at_point (Duple point) const OptimizingLookupTable::items_at_point (Duple const & point) const
{ {
int x; int x;
int y; int y;
@ -226,6 +246,43 @@ OptimizingLookupTable::items_at_point (Duple point) const
return items; return items;
} }
bool
OptimizingLookupTable::has_item_at_point (Duple const & point) const
{
int x;
int y;
point_to_indices (point, x, y);
if (x >= _dimension) {
cout << "WARNING: x=" << x << ", dim=" << _dimension << ", px=" << point.x << " cellsize=" << _cell_size << "\n";
}
if (y >= _dimension) {
cout << "WARNING: y=" << y << ", dim=" << _dimension << ", py=" << point.y << " cellsize=" << _cell_size << "\n";
}
/* XXX: hmm */
x = min (_dimension - 1, x);
y = min (_dimension - 1, y);
assert (x >= 0);
assert (y >= 0);
Cell const & cell = _cells[x][y];
vector<Item*> items;
for (Cell::const_iterator i = cell.begin(); i != cell.end(); ++i) {
boost::optional<Rect> const item_bbox = (*i)->bounding_box ();
if (item_bbox) {
Rect parent_bbox = (*i)->item_to_parent (item_bbox.get ());
if (parent_bbox.contains (point)) {
return true;
}
}
}
return false;
}
/** @param area Area in our owning group's coordinates */ /** @param area Area in our owning group's coordinates */
vector<Item*> vector<Item*>

View file

@ -118,7 +118,7 @@ Polygon::covers (Duple const & point) const
} }
j = i; j = i;
} }
return oddNodes; return oddNodes;
} }