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

@ -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 ();
}
}

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

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

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

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