2013-04-04 00:32:52 -04:00
/*
Copyright ( C ) 2011 Paul Davis
Author : Carl Hetherington < cth @ carlh . net >
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 2 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
/** @file canvas/canvas.cc
* @ brief Implementation of the main canvas classes .
*/
2013-12-12 10:03:33 -05:00
# include <list>
2013-04-04 00:32:52 -04:00
# include <cassert>
# include <gtkmm/adjustment.h>
2013-04-11 20:19:22 -04:00
# include <gtkmm/label.h>
2013-04-05 11:27:26 -04:00
2013-04-04 00:32:52 -04:00
# include "pbd/compose.h"
2013-04-05 11:27:26 -04:00
# include "pbd/stacktrace.h"
2013-04-04 00:32:52 -04:00
# include "canvas/canvas.h"
# include "canvas/debug.h"
2014-01-10 14:35:36 -05:00
# include "canvas/line.h"
2014-05-21 10:25:28 -04:00
# include "canvas/scroll_group.h"
2013-04-04 00:32:52 -04:00
using namespace std ;
using namespace ArdourCanvas ;
/** Construct a new Canvas */
Canvas : : Canvas ( )
: _root ( this )
{
set_epoch ( ) ;
}
2013-04-11 20:19:22 -04:00
void
Canvas : : scroll_to ( Coord x , Coord y )
{
2014-05-21 10:25:28 -04:00
/* We do things this way because we do not want to recurse through
the canvas for every scroll . In the presence of large MIDI
tracks this means traversing item lists that include
thousands of items ( notes ) .
2014-05-20 23:08:15 -04:00
2014-05-21 10:25:28 -04:00
This design limits us to moving only those items ( groups , typically )
that should move in certain ways as we scroll . In other terms , it
becomes O ( 1 ) rather than O ( N ) .
*/
for ( list < ScrollGroup * > : : iterator i = scrollers . begin ( ) ; i ! = scrollers . end ( ) ; + + i ) {
2014-06-09 08:39:38 -04:00
( * i ) - > scroll_to ( Duple ( x , y ) ) ;
2014-05-21 10:25:28 -04:00
}
2013-10-31 03:10:18 -04:00
2013-12-12 20:44:04 -05:00
pick_current_item ( 0 ) ; // no current mouse position
2013-10-31 03:10:18 -04:00
}
2014-05-18 12:22:23 -04:00
void
2014-05-21 10:25:28 -04:00
Canvas : : add_scroller ( ScrollGroup & i )
2014-05-18 12:22:23 -04:00
{
2014-05-21 10:25:28 -04:00
scrollers . push_back ( & i ) ;
2014-05-18 12:22:23 -04:00
}
2013-10-31 03:10:18 -04:00
void
Canvas : : zoomed ( )
{
2013-12-12 20:44:04 -05:00
pick_current_item ( 0 ) ; // no current mouse position
2013-04-11 20:19:22 -04:00
}
2013-04-04 00:32:52 -04:00
/** Render an area of the canvas.
2014-05-22 23:05:18 -04:00
* @ param area Area in window coordinates .
2013-04-04 00:32:52 -04:00
* @ param context Cairo context to render to .
*/
void
Canvas : : render ( Rect const & area , Cairo : : RefPtr < Cairo : : Context > const & context ) const
{
2013-04-09 14:22:58 -04:00
# ifdef CANVAS_DEBUG
if ( DEBUG_ENABLED ( PBD : : DEBUG : : CanvasRender ) ) {
2014-01-10 14:35:36 -05:00
cerr < < this < < " RENDER: " < < area < < endl ;
2014-05-20 23:08:15 -04:00
//cerr << "CANVAS @ " << this << endl;
//dump (cerr);
//cerr << "-------------------------\n";
2013-04-09 14:22:58 -04:00
}
# endif
2013-04-05 11:27:26 -04:00
2013-04-04 00:32:52 -04:00
render_count = 0 ;
boost : : optional < Rect > root_bbox = _root . bounding_box ( ) ;
if ( ! root_bbox ) {
/* the root has no bounding box, so there's nothing to render */
return ;
}
2013-06-18 08:23:06 -04:00
boost : : optional < Rect > draw = root_bbox - > intersection ( area ) ;
2013-04-04 00:32:52 -04:00
if ( draw ) {
2013-12-23 15:35:49 -05:00
2013-04-04 00:32:52 -04:00
/* there's a common area between the root and the requested
area , so render it .
*/
2013-06-24 16:28:53 -04:00
2013-04-04 00:32:52 -04:00
_root . render ( * draw , context ) ;
2013-12-23 15:35:49 -05:00
// This outlines the rect being rendered, after it has been drawn.
// context->rectangle (draw->x0, draw->y0, draw->x1 - draw->x0, draw->y1 - draw->y0);
// context->set_source_rgba (1.0, 0, 0, 1.0);
// context->stroke ();
2013-04-04 00:32:52 -04:00
}
2013-10-31 11:49:36 -04:00
2013-04-04 00:32:52 -04:00
}
2013-04-05 11:27:26 -04:00
ostream &
operator < < ( ostream & o , Canvas & c )
{
c . dump ( o ) ;
return o ;
}
std : : string
Canvas : : indent ( ) const
{
string s ;
for ( int n = 0 ; n < ArdourCanvas : : dump_depth ; + + n ) {
s + = ' \t ' ;
}
return s ;
}
2013-04-09 14:22:58 -04:00
std : : string
Canvas : : render_indent ( ) const
{
string s ;
for ( int n = 0 ; n < ArdourCanvas : : render_depth ; + + n ) {
s + = ' ' ;
}
return s ;
}
2013-04-05 11:27:26 -04:00
void
Canvas : : dump ( ostream & o ) const
{
dump_depth = 0 ;
_root . dump ( o ) ;
}
2013-04-04 00:32:52 -04:00
/** Called when an item has been shown or hidden.
* @ param item Item that has been shown or hidden .
*/
void
Canvas : : item_shown_or_hidden ( Item * item )
2013-04-16 10:07:52 -04:00
{
boost : : optional < Rect > bbox = item - > bounding_box ( ) ;
if ( bbox ) {
queue_draw_item_area ( item , bbox . get ( ) ) ;
}
}
/** Called when an item has a change to its visual properties
* that do NOT affect its bounding box .
* @ param item Item that has been modified .
*/
void
Canvas : : item_visual_property_changed ( Item * item )
2013-04-04 00:32:52 -04:00
{
boost : : optional < Rect > bbox = item - > bounding_box ( ) ;
if ( bbox ) {
queue_draw_item_area ( item , bbox . get ( ) ) ;
}
}
/** Called when an item has changed, but not moved.
* @ param item Item that has changed .
* @ param pre_change_bounding_box The bounding box of item before the change ,
* in the item ' s coordinates .
*/
void
Canvas : : item_changed ( Item * item , boost : : optional < Rect > pre_change_bounding_box )
{
if ( pre_change_bounding_box ) {
/* request a redraw of the item's old bounding box */
queue_draw_item_area ( item , pre_change_bounding_box . get ( ) ) ;
}
boost : : optional < Rect > post_change_bounding_box = item - > bounding_box ( ) ;
if ( post_change_bounding_box ) {
/* request a redraw of the item's new bounding box */
queue_draw_item_area ( item , post_change_bounding_box . get ( ) ) ;
}
}
2013-04-17 10:53:17 -04:00
Duple
Canvas : : window_to_canvas ( Duple const & d ) const
{
2014-06-03 15:57:56 -04:00
/* Find the scroll group that covers d (a window coordinate). Scroll groups are only allowed
* as children of the root group , so we just scan its first level
* children and see what we can find .
*/
std : : list < Item * > const & root_children ( _root . items ( ) ) ;
ScrollGroup * sg = 0 ;
for ( std : : list < Item * > : : const_iterator i = root_children . begin ( ) ; i ! = root_children . end ( ) ; + + i ) {
if ( ( ( sg = dynamic_cast < ScrollGroup * > ( * i ) ) ! = 0 ) & & sg - > covers_window ( d ) ) {
break ;
}
}
if ( sg ) {
return d . translate ( sg - > scroll_offset ( ) ) ;
}
2014-06-09 08:39:38 -04:00
return d ;
2013-04-17 10:53:17 -04:00
}
Duple
2014-05-29 21:04:02 -04:00
Canvas : : canvas_to_window ( Duple const & d , bool rounded ) const
2013-04-17 10:53:17 -04:00
{
2014-06-03 15:57:56 -04:00
/* Find the scroll group that covers d (a canvas coordinate). Scroll groups are only allowed
* as children of the root group , so we just scan its first level
* children and see what we can find .
*/
std : : list < Item * > const & root_children ( _root . items ( ) ) ;
ScrollGroup * sg = 0 ;
Duple wd ;
for ( std : : list < Item * > : : const_iterator i = root_children . begin ( ) ; i ! = root_children . end ( ) ; + + i ) {
if ( ( ( sg = dynamic_cast < ScrollGroup * > ( * i ) ) ! = 0 ) & & sg - > covers_canvas ( d ) ) {
break ;
}
}
if ( sg ) {
wd = d . translate ( - sg - > scroll_offset ( ) ) ;
} else {
2014-06-09 08:39:38 -04:00
wd = d ;
2014-06-03 15:57:56 -04:00
}
2014-01-08 10:31:14 -05:00
2014-05-29 21:04:02 -04:00
/* Note that this intentionally almost always returns integer coordinates */
2014-05-20 23:08:15 -04:00
2014-05-29 21:04:02 -04:00
if ( rounded ) {
wd . x = round ( wd . x ) ;
wd . y = round ( wd . y ) ;
}
2014-01-08 10:31:14 -05:00
2013-10-31 11:49:36 -04:00
return wd ;
2014-05-20 23:08:15 -04:00
}
2013-04-17 10:53:17 -04:00
2013-04-04 00:32:52 -04:00
/** Called when an item has moved.
* @ param item Item that has moved .
* @ param pre_change_parent_bounding_box The bounding box of the item before
* the move , in its parent ' s coordinates .
*/
void
Canvas : : item_moved ( Item * item , boost : : optional < Rect > pre_change_parent_bounding_box )
{
if ( pre_change_parent_bounding_box ) {
2013-04-15 10:38:12 -04:00
/* request a redraw of where the item used to be. The box has
* to be in parent coordinate space since the bounding box of
* an item does not change when moved . If we use
* item - > item_to_canvas ( ) on the old bounding box , we will be
2013-10-24 17:14:12 -04:00
2013-04-15 10:38:12 -04:00
* using the item ' s new position , and so will compute the wrong
* invalidation area . If we use the parent ( which has not
* moved , then this will work .
*/
2013-04-04 00:32:52 -04:00
queue_draw_item_area ( item - > parent ( ) , pre_change_parent_bounding_box . get ( ) ) ;
}
boost : : optional < Rect > post_change_bounding_box = item - > bounding_box ( ) ;
if ( post_change_bounding_box ) {
/* request a redraw of where the item now is */
queue_draw_item_area ( item , post_change_bounding_box . get ( ) ) ;
}
}
/** Request a redraw of a particular area in an item's coordinates.
* @ param item Item .
* @ param area Area to redraw in the item ' s coordinates .
*/
void
Canvas : : queue_draw_item_area ( Item * item , Rect area )
{
2014-05-22 23:05:18 -04:00
request_redraw ( item - > item_to_window ( area ) ) ;
2013-04-04 00:32:52 -04:00
}
/** Construct a GtkCanvas */
GtkCanvas : : GtkCanvas ( )
2013-12-12 10:03:33 -05:00
: _current_item ( 0 )
, _new_current_item ( 0 )
, _grabbed_item ( 0 )
2013-10-30 23:36:30 -04:00
, _focused_item ( 0 )
2013-04-04 00:32:52 -04:00
{
/* these are the events we want to know about */
2013-10-31 16:43:35 -04:00
add_events ( Gdk : : BUTTON_PRESS_MASK | Gdk : : BUTTON_RELEASE_MASK | Gdk : : POINTER_MOTION_MASK |
Gdk : : ENTER_NOTIFY_MASK | Gdk : : LEAVE_NOTIFY_MASK ) ;
2013-04-04 00:32:52 -04:00
}
2013-04-20 16:09:43 -04:00
void
2013-12-12 20:44:04 -05:00
GtkCanvas : : pick_current_item ( int state )
2013-04-20 16:09:43 -04:00
{
int x ;
int y ;
2013-04-24 15:42:14 -04:00
2013-12-12 20:44:04 -05:00
/* this version of ::pick_current_item() is called after an item is
2013-04-24 15:42:14 -04:00
* added or removed , so we have no coordinates to work from as is the
* case with a motion event . Find out where the mouse is and use that .
*/
2013-04-20 16:09:43 -04:00
Glib : : RefPtr < const Gdk : : Window > pointer_window = Gdk : : Display : : get_default ( ) - > get_window_at_pointer ( x , y ) ;
if ( pointer_window ! = get_window ( ) ) {
return ;
}
2013-04-04 00:32:52 -04:00
2014-05-23 22:05:08 -04:00
pick_current_item ( Duple ( x , y ) , state ) ;
2013-04-20 16:09:43 -04:00
}
2014-06-08 11:24:28 -04:00
/** Given @param point (a position in window coordinates)
* and mouse state @ param state , check to see if _current_item
* ( which will be used to deliver events ) should change .
*/
2013-04-20 16:09:43 -04:00
void
2013-12-12 20:44:04 -05:00
GtkCanvas : : pick_current_item ( Duple const & point , int state )
2013-04-20 16:09:43 -04:00
{
2013-10-30 23:36:30 -04:00
/* we do not enter/leave items during a drag/grab */
2013-04-24 15:42:14 -04:00
2013-10-30 23:36:30 -04:00
if ( _grabbed_item ) {
return ;
}
2013-04-04 00:32:52 -04:00
2014-06-08 11:24:28 -04:00
/* find the items at the given window position */
2013-10-30 23:36:30 -04:00
vector < Item const * > items ;
_root . add_items_at_point ( point , items ) ;
2014-02-25 16:07:51 -05:00
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEnterLeave , string_compose ( " %1 covers %2 items \n " , point , items . size ( ) ) ) ;
# ifndef NDEBUG
if ( DEBUG_ENABLED ( PBD : : DEBUG : : CanvasEnterLeave ) ) {
for ( vector < Item const * > : : const_iterator it = items . begin ( ) ; it ! = items . end ( ) ; + + it ) {
# ifdef CANVAS_DEBUG
std : : cerr < < " \t Item " < < ( * it ) - > whatami ( ) < < ' / ' < < ( * it ) - > name < < std : : endl ;
# else
std : : cerr < < " \t Item " < < ( * it ) - > whatami ( ) < < std : : endl ;
# endif
}
}
# endif
2013-12-12 10:03:33 -05:00
/* put all items at point that are event-sensitive and visible and NOT
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 .
2013-10-30 23:36:30 -04:00
*/
2013-10-28 16:36:11 -04:00
2013-10-30 23:36:30 -04:00
vector < Item const * > : : const_iterator i ;
2013-12-12 10:03:33 -05:00
list < Item const * > within_items ;
2013-08-08 14:04:29 -04:00
2013-10-30 23:36:30 -04:00
for ( i = items . begin ( ) ; i ! = items . end ( ) ; + + i ) {
2013-04-24 15:42:14 -04:00
2013-12-12 10:03:33 -05:00
Item const * new_item = * i ;
2013-11-03 10:07:00 -05:00
2013-12-30 14:02:43 -05:00
/* We ignore invisible items, groups and items that ignore events */
2013-12-12 20:44:04 -05:00
2013-12-30 14:02:43 -05:00
if ( ! new_item - > visible ( ) | | new_item - > ignore_events ( ) | | dynamic_cast < Group const * > ( new_item ) ! = 0 ) {
2013-04-24 15:42:14 -04:00
continue ;
}
2013-12-12 10:03:33 -05:00
within_items . push_front ( new_item ) ;
}
2013-04-24 18:31:00 -04:00
2013-12-12 10:03:33 -05:00
if ( within_items . empty ( ) ) {
2013-12-12 20:44:04 -05:00
/* no items at point, just send leave event below */
2014-02-25 16:08:36 -05:00
_new_current_item = 0 ;
2013-12-12 20:44:04 -05:00
} else {
2014-01-10 11:02:05 -05:00
2013-12-12 20:44:04 -05:00
if ( within_items . front ( ) = = _current_item ) {
/* uppermost item at point is already _current_item */
return ;
2013-04-24 22:57:23 -04:00
}
2014-02-25 16:07:51 -05:00
2013-12-12 20:44:04 -05:00
_new_current_item = const_cast < Item * > ( within_items . front ( ) ) ;
2013-10-30 23:36:30 -04:00
}
2014-01-10 11:02:05 -05:00
if ( _new_current_item ! = _current_item ) {
deliver_enter_leave ( point , state ) ;
}
2013-12-12 20:44:04 -05:00
}
2014-05-23 22:05:08 -04:00
/** 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 )
*/
2013-12-12 20:44:04 -05:00
void
GtkCanvas : : deliver_enter_leave ( Duple const & point , int state )
{
/* setup enter & leave event structures */
GdkEventCrossing enter_event ;
enter_event . type = GDK_ENTER_NOTIFY ;
enter_event . window = get_window ( ) - > gobj ( ) ;
enter_event . send_event = 0 ;
enter_event . subwindow = 0 ;
enter_event . mode = GDK_CROSSING_NORMAL ;
enter_event . focus = FALSE ;
enter_event . state = state ;
2014-06-08 11:24:28 -04:00
/* 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 ;
2013-12-12 20:44:04 -05:00
GdkEventCrossing leave_event = enter_event ;
leave_event . type = GDK_LEAVE_NOTIFY ;
Item * i ;
GdkNotifyType enter_detail ;
GdkNotifyType leave_detail ;
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 ) ;
}
2013-12-12 10:03:33 -05:00
}
2013-10-30 23:36:30 -04:00
2013-12-12 20:44:04 -05:00
} 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
2014-01-10 11:02:05 -05:00
* inferior ( " child " ) of _new_current_item " )
2013-12-12 20:44:04 -05:00
*
* 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
2014-01-10 11:02:05 -05:00
* an inferior ( " child " ) of _current_item " )
2013-12-12 20:44:04 -05:00
*
* 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 ;
2013-10-30 23:36:30 -04:00
2013-12-12 10:03:33 -05:00
} else {
2013-10-30 23:36:30 -04:00
2013-12-12 20:44:04 -05:00
Item const * common_ancestor = _current_item - > closest_ancestor_with ( * _new_current_item ) ;
2013-04-24 15:42:14 -04:00
2013-12-12 20:44:04 -05:00
/* 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 ;
2013-10-30 23:36:30 -04:00
}
2013-12-12 20:44:04 -05:00
if ( _current_item & & ! _current_item - > ignore_events ( ) ) {
leave_event . detail = leave_detail ;
_current_item - > Event ( ( GdkEvent * ) & leave_event ) ;
2014-02-25 16:07:51 -05:00
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEnterLeave , string_compose ( " LEAVE %1/%2 \n " , _current_item - > whatami ( ) , _current_item - > name ) ) ;
2013-10-30 23:36:30 -04:00
}
2013-08-08 14:04:29 -04:00
2013-12-12 20:44:04 -05:00
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 ( ) ) {
2014-02-25 16:07:51 -05:00
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEnterLeave , string_compose ( " leave %1/%2 \n " , ( * it ) - > whatami ( ) , ( * it ) - > name ) ) ;
2013-12-12 20:44:04 -05:00
( * 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 ( ) ) {
2014-02-25 16:07:51 -05:00
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEnterLeave , string_compose ( " enter %1/%2 \n " , ( * it ) - > whatami ( ) , ( * it ) - > name ) ) ;
2013-12-12 20:44:04 -05:00
( * it ) - > Event ( ( GdkEvent * ) & enter_event ) ;
2013-12-30 14:02:43 -05:00
// std::cerr << "enter " << (*it)->whatami() << '/' << (*it)->name << std::endl;
2013-12-12 20:44:04 -05:00
}
}
if ( _new_current_item & & ! _new_current_item - > ignore_events ( ) ) {
enter_event . detail = enter_detail ;
2014-02-25 16:07:51 -05:00
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEnterLeave , string_compose ( " ENTER %1/%2 \n " , _new_current_item - > whatami ( ) , _new_current_item - > name ) ) ;
2013-12-12 20:44:04 -05:00
_new_current_item - > Event ( ( GdkEvent * ) & enter_event ) ;
}
2013-12-12 10:03:33 -05:00
2013-12-12 20:44:04 -05:00
_current_item = _new_current_item ;
2013-04-04 00:32:52 -04:00
}
2013-12-12 20:44:04 -05:00
2013-04-04 00:32:52 -04:00
/** Deliver an event to the appropriate item; either the grabbed item, or
* one of the items underneath the event .
* @ param point Position that the event has occurred at , in canvas coordinates .
* @ param event The event .
*/
bool
2013-12-12 10:03:33 -05:00
GtkCanvas : : deliver_event ( GdkEvent * event )
2013-04-04 00:32:52 -04:00
{
2013-10-28 12:25:41 -04:00
/* Point in in canvas coordinate space */
2014-02-11 22:49:10 -05:00
const Item * event_item ;
2013-04-04 00:32:52 -04:00
if ( _grabbed_item ) {
/* we have a grabbed item, so everything gets sent there */
2013-04-10 11:09:16 -04:00
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEvents , string_compose ( " %1 %2 (%3) was grabbed, send event there \n " ,
_grabbed_item , _grabbed_item - > whatami ( ) , _grabbed_item - > name ) ) ;
2014-02-11 22:49:10 -05:00
event_item = _grabbed_item ;
} else {
event_item = _current_item ;
2013-04-04 00:32:52 -04:00
}
2014-02-11 22:49:10 -05:00
if ( ! event_item ) {
2013-12-12 10:03:33 -05:00
return false ;
}
2013-04-04 00:32:52 -04:00
2013-12-12 10:03:33 -05:00
/* run through the items from child to parent, until one claims the event */
2013-04-10 11:09:16 -04:00
2014-02-11 22:49:10 -05:00
Item * item = const_cast < Item * > ( event_item ) ;
2013-12-12 20:44:04 -05:00
while ( item ) {
2013-04-04 00:32:52 -04:00
2013-12-12 20:44:04 -05:00
Item * parent = item - > parent ( ) ;
if ( ! item - > ignore_events ( ) & &
item - > Event ( event ) ) {
2013-04-04 00:32:52 -04:00
/* this item has just handled the event */
DEBUG_TRACE (
PBD : : DEBUG : : CanvasEvents ,
2013-12-12 10:03:33 -05:00
string_compose ( " canvas event handled by %1 %2 \n " , item - > whatami ( ) , item - > name . empty ( ) ? " [unknown] " : item - > name )
2013-04-04 00:32:52 -04:00
) ;
return true ;
}
2014-01-16 10:52:34 -05:00
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEvents , string_compose ( " canvas event %3 left unhandled by %1 %2 \n " , item - > whatami ( ) , item - > name . empty ( ) ? " [unknown] " : item - > name , event_type_string ( event - > type ) ) ) ;
2013-12-12 20:44:04 -05:00
if ( ( item = parent ) = = 0 ) {
break ;
}
2013-04-04 00:32:52 -04:00
}
return false ;
}
/** Called when an item is being destroyed.
* @ param item Item being destroyed .
* @ param bounding_box Last known bounding box of the item .
*/
void
GtkCanvas : : item_going_away ( Item * item , boost : : optional < Rect > bounding_box )
{
if ( bounding_box ) {
queue_draw_item_area ( item , bounding_box . get ( ) ) ;
}
2013-12-12 10:03:33 -05:00
if ( _new_current_item = = item ) {
_new_current_item = 0 ;
}
2013-04-04 00:32:52 -04:00
if ( _grabbed_item = = item ) {
_grabbed_item = 0 ;
}
2013-04-20 16:09:43 -04:00
2013-10-30 23:36:30 -04:00
if ( _focused_item = = item ) {
_focused_item = 0 ;
}
2014-05-21 10:25:28 -04:00
ScrollGroup * sg = dynamic_cast < ScrollGroup * > ( item ) ;
if ( sg ) {
scrollers . remove ( sg ) ;
}
2014-01-20 10:59:44 -05:00
if ( _current_item = = item ) {
/* no need to send a leave event to this item, since it is going away
*/
_current_item = 0 ;
2014-01-20 10:53:58 -05:00
pick_current_item ( 0 ) ; // no mouse state
}
2013-04-20 16:09:43 -04:00
2013-04-04 00:32:52 -04:00
}
/** Handler for GDK expose events.
* @ param ev Event .
* @ return true if the event was handled .
*/
bool
GtkCanvas : : on_expose_event ( GdkEventExpose * ev )
{
2014-01-10 14:35:36 -05:00
Cairo : : RefPtr < Cairo : : Context > cairo_context = get_window ( ) - > create_cairo_context ( ) ;
2014-03-05 13:12:18 -05:00
render ( Rect ( ev - > area . x , ev - > area . y , ev - > area . x + ev - > area . width , ev - > area . y + ev - > area . height ) , cairo_context ) ;
2013-04-04 00:32:52 -04:00
return true ;
}
/** @return Our Cairo context, or 0 if we don't have one */
Cairo : : RefPtr < Cairo : : Context >
GtkCanvas : : context ( )
{
Glib : : RefPtr < Gdk : : Window > w = get_window ( ) ;
if ( ! w ) {
return Cairo : : RefPtr < Cairo : : Context > ( ) ;
}
return w - > create_cairo_context ( ) ;
}
/** Handler for GDK button press events.
* @ param ev Event .
* @ return true if the event was handled .
*/
bool
GtkCanvas : : on_button_press_event ( GdkEventButton * ev )
{
2013-04-11 20:19:22 -04:00
/* translate event coordinates from window to canvas */
2013-10-31 16:43:35 -04:00
GdkEvent copy = * ( ( GdkEvent * ) ev ) ;
2014-05-23 22:05:08 -04:00
Duple winpos = Duple ( ev - > x , ev - > y ) ;
Duple where = window_to_canvas ( winpos ) ;
pick_current_item ( winpos , ev - > state ) ;
2013-10-31 16:43:35 -04:00
copy . button . x = where . x ;
copy . button . y = where . y ;
2013-04-04 00:32:52 -04:00
/* Coordinates in the event will be canvas coordinates, correctly adjusted
for scroll if this GtkCanvas is in a GtkCanvasViewport .
*/
2013-10-28 12:25:41 -04:00
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEvents , string_compose ( " canvas button press @ %1, %2 => %3 \n " , ev - > x , ev - > y , where ) ) ;
2013-12-12 10:03:33 -05:00
return deliver_event ( reinterpret_cast < GdkEvent * > ( & copy ) ) ;
2013-04-04 00:32:52 -04:00
}
/** Handler for GDK button release events.
* @ param ev Event .
* @ return true if the event was handled .
*/
bool
GtkCanvas : : on_button_release_event ( GdkEventButton * ev )
2013-04-11 20:19:22 -04:00
{
/* translate event coordinates from window to canvas */
2013-10-31 16:43:35 -04:00
GdkEvent copy = * ( ( GdkEvent * ) ev ) ;
2014-05-23 22:05:08 -04:00
Duple winpos = Duple ( ev - > x , ev - > y ) ;
Duple where = window_to_canvas ( winpos ) ;
2013-11-04 11:56:10 -05:00
2014-05-23 22:05:08 -04:00
pick_current_item ( winpos , ev - > state ) ;
2013-04-11 20:19:22 -04:00
2013-10-31 16:43:35 -04:00
copy . button . x = where . x ;
copy . button . y = where . y ;
2013-04-04 00:32:52 -04:00
/* Coordinates in the event will be canvas coordinates, correctly adjusted
for scroll if this GtkCanvas is in a GtkCanvasViewport .
*/
2013-10-28 12:25:41 -04:00
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEvents , string_compose ( " canvas button release @ %1, %2 => %3 \n " , ev - > x , ev - > y , where ) ) ;
2013-12-12 10:03:33 -05:00
return deliver_event ( reinterpret_cast < GdkEvent * > ( & copy ) ) ;
2013-04-04 00:32:52 -04:00
}
/** Handler for GDK motion events.
* @ param ev Event .
* @ return true if the event was handled .
*/
bool
GtkCanvas : : on_motion_notify_event ( GdkEventMotion * ev )
{
2013-04-11 20:19:22 -04:00
/* translate event coordinates from window to canvas */
GdkEvent copy = * ( ( GdkEvent * ) ev ) ;
2013-11-04 11:56:10 -05:00
Duple point ( ev - > x , ev - > y ) ;
Duple where = window_to_canvas ( point ) ;
2013-04-11 20:19:22 -04:00
copy . motion . x = where . x ;
copy . motion . y = where . y ;
2013-11-04 11:56:10 -05:00
/* Coordinates in "copy" will be canvas coordinates,
*/
2014-06-08 11:24:28 -04:00
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEvents , string_compose ( " canvas motion @ %1, %2 canvas @ %3, %4 \n " , ev - > x , ev - > y , copy . motion . x , copy . motion . y ) ) ;
2013-11-04 11:56:10 -05:00
2014-05-23 22:05:08 -04:00
pick_current_item ( point , ev - > state ) ;
2013-11-04 11:56:10 -05:00
/* Now deliver the motion event. It may seem a little inefficient
to recompute the items under the event , but the enter notify / leave
events may have deleted canvas items so it is important to
recompute the list in deliver_event .
2013-04-04 00:32:52 -04:00
*/
2013-11-04 11:56:10 -05:00
2013-12-12 10:03:33 -05:00
return deliver_event ( reinterpret_cast < GdkEvent * > ( & copy ) ) ;
2013-04-04 00:32:52 -04:00
}
2013-10-30 23:36:30 -04:00
bool
GtkCanvas : : on_enter_notify_event ( GdkEventCrossing * ev )
{
2014-05-23 22:05:08 -04:00
pick_current_item ( Duple ( ev - > x , ev - > y ) , ev - > state ) ;
2013-10-30 23:36:30 -04:00
return true ;
}
bool
2013-12-12 20:44:04 -05:00
GtkCanvas : : on_leave_notify_event ( GdkEventCrossing * ev )
2013-10-30 23:36:30 -04:00
{
2013-12-12 10:03:33 -05:00
_new_current_item = 0 ;
2014-05-23 22:05:08 -04:00
deliver_enter_leave ( Duple ( ev - > x , ev - > y ) , ev - > state ) ;
2013-10-30 23:36:30 -04:00
return true ;
}
2013-04-04 00:32:52 -04:00
/** Called to request a redraw of our canvas.
2014-05-22 23:05:18 -04:00
* @ param area Area to redraw , in window coordinates .
2013-04-04 00:32:52 -04:00
*/
void
2013-04-11 20:19:22 -04:00
GtkCanvas : : request_redraw ( Rect const & request )
2013-04-04 00:32:52 -04:00
{
2014-06-05 14:42:46 -04:00
Rect real_area ;
Coord const w = width ( ) ;
Coord const h = height ( ) ;
/* clamp area requested to actual visible window */
real_area . x0 = max ( 0.0 , min ( w , request . x0 ) ) ;
real_area . x1 = max ( 0.0 , min ( w , request . x1 ) ) ;
real_area . y0 = max ( 0.0 , min ( h , request . y0 ) ) ;
real_area . y1 = max ( 0.0 , min ( h , request . y1 ) ) ;
queue_draw_area ( real_area . x0 , real_area . y0 , real_area . width ( ) , real_area . height ( ) ) ;
2013-04-04 00:32:52 -04:00
}
/** Called to request that we try to get a particular size for ourselves.
* @ param size Size to request , in pixels .
*/
void
GtkCanvas : : request_size ( Duple size )
{
Duple req = size ;
if ( req . x > INT_MAX ) {
req . x = INT_MAX ;
}
if ( req . y > INT_MAX ) {
req . y = INT_MAX ;
}
2013-04-05 11:27:26 -04:00
2013-04-04 00:32:52 -04:00
set_size_request ( req . x , req . y ) ;
}
/** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
* This is typically used for dragging items around , so that they are grabbed during
* the drag .
* @ param item Item to grab .
*/
void
GtkCanvas : : grab ( Item * item )
{
/* XXX: should this be doing gdk_pointer_grab? */
_grabbed_item = item ;
}
2013-10-30 23:36:30 -04:00
2013-04-04 00:32:52 -04:00
/** `Ungrab' any item that was previously grabbed */
void
GtkCanvas : : ungrab ( )
{
/* XXX: should this be doing gdk_pointer_ungrab? */
_grabbed_item = 0 ;
}
2013-10-30 23:36:30 -04:00
/** 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 ;
}
}
2013-04-17 10:53:17 -04:00
/** @return The visible area of the canvas, in canvas coordinates */
Rect
GtkCanvas : : visible_area ( ) const
{
2014-06-05 14:42:46 -04:00
return Rect ( 0 , 0 , get_allocation ( ) . get_width ( ) , get_allocation ( ) . get_height ( ) ) ;
}
Coord
GtkCanvas : : width ( ) const
{
return get_allocation ( ) . get_width ( ) ;
}
Coord
GtkCanvas : : height ( ) const
{
return get_allocation ( ) . get_height ( ) ;
2013-04-17 10:53:17 -04:00
}
2013-04-04 00:32:52 -04:00
/** Create a GtkCanvaSViewport.
* @ param hadj Adjustment to use for horizontal scrolling .
* @ param vadj Adjustment to use for vertica scrolling .
*/
GtkCanvasViewport : : GtkCanvasViewport ( Gtk : : Adjustment & hadj , Gtk : : Adjustment & vadj )
2013-04-11 20:19:22 -04:00
: Alignment ( 0 , 0 , 1.0 , 1.0 )
, hadjustment ( hadj )
, vadjustment ( vadj )
2013-04-04 00:32:52 -04:00
{
add ( _canvas ) ;
2013-04-11 20:19:22 -04:00
hadj . signal_value_changed ( ) . connect ( sigc : : mem_fun ( * this , & GtkCanvasViewport : : scrolled ) ) ;
vadj . signal_value_changed ( ) . connect ( sigc : : mem_fun ( * this , & GtkCanvasViewport : : scrolled ) ) ;
2013-04-04 00:32:52 -04:00
}
2013-04-11 20:19:22 -04:00
void
GtkCanvasViewport : : scrolled ( )
{
_canvas . scroll_to ( hadjustment . get_value ( ) , vadjustment . get_value ( ) ) ;
queue_draw ( ) ;
}
2013-04-04 00:32:52 -04:00
/** Handler for when GTK asks us what minimum size we want.
* @ param req Requsition to fill in .
*/
void
GtkCanvasViewport : : on_size_request ( Gtk : : Requisition * req )
{
2013-04-11 20:19:22 -04:00
/* force the canvas to size itself */
// _canvas.root()->bounding_box();
2013-04-04 00:32:52 -04:00
req - > width = 16 ;
req - > height = 16 ;
}