notable changes to try to improve most of enter/leave handling for canvas items

This commit is contained in:
Paul Davis 2013-10-30 23:36:30 -04:00
parent 006ba7cd36
commit 7bbd28aa08
9 changed files with 310 additions and 111 deletions

View 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;
/* 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.
*/
vector<Item const *>::const_iterator i;
vector<Item const *> newly_entered;
Item const * new_item;
for (i = items.begin(); i != items.end(); ++i) {
new_item = *i;
if (new_item->ignore_events() || !new_item->visible()) {
continue;
}
pair<set<Item const *>::iterator,bool> res = within_items.insert (new_item);
if (res.second) {
newly_entered.push_back (new_item);
}
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
*/
/* 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"
*/
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);
}
}
i = tmp;
}
// 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";
/* 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_reverse_iterator i = items.rbegin(); i != items.rend(); ++i) {
for (vector<Item const*>::const_iterator i = newly_entered.begin(); i != newly_entered.end(); ++i) {
new_item = *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";
continue;
}
if (_current_item == new_item) {
// cerr << "continue2\n";
continue;
}
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 ();
}
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 ();
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

View file

@ -24,11 +24,14 @@
#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

View 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);

View 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

View 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;
}