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 .
*/
2015-05-01 22:54:59 -04:00
# if !defined USE_CAIRO_IMAGE_SURFACE && !defined NDEBUG
# define OPTIONAL_CAIRO_IMAGE_SURFACE
# endif
2013-04-04 00:32:52 -04:00
/** @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>
2014-09-25 21:43:15 -04:00
# include <gtkmm/window.h>
2013-04-05 11:27:26 -04:00
2016-04-26 19:24:37 -04:00
# include "gtkmm2ext/persistent_tooltip.h"
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"
2014-11-10 11:46:17 -05:00
# include "canvas/colors.h"
2013-04-04 00:32:52 -04:00
# 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"
2014-11-10 10:35:10 -05:00
# include "canvas/utils.h"
2013-04-04 00:32:52 -04:00
2017-03-19 17:40:58 -04:00
# ifdef __APPLE__
2017-03-19 21:15:15 -04:00
# include <gdk/gdk.h>
2017-03-19 23:21:08 -04:00
# include "gtkmm2ext/nsglview.h"
2017-03-19 17:40:58 -04:00
# endif
2013-04-04 00:32:52 -04:00
using namespace std ;
using namespace ArdourCanvas ;
2014-09-26 12:24:47 -04:00
uint32_t Canvas : : tooltip_timeout_msecs = 750 ;
2013-04-04 00:32:52 -04:00
/** Construct a new Canvas */
Canvas : : Canvas ( )
: _root ( this )
2017-03-17 11:03:59 -04:00
, _bg_color ( rgba_to_color ( 0 , 1.0 , 0.0 , 1.0 ) )
2013-04-04 00:32:52 -04:00
{
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
2015-10-04 14:51:05 -04: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
{
2017-03-13 06:45:13 -04:00
PreRender ( ) ; // emit signal
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 ;
2015-10-05 10:17:49 -04:00
2017-01-19 14:54:24 -05:00
Rect root_bbox = _root . bounding_box ( ) ;
2013-04-04 00:32:52 -04:00
if ( ! root_bbox ) {
/* the root has no bounding box, so there's nothing to render */
return ;
}
2017-01-19 14:54:24 -05:00
Rect draw = root_bbox . intersection ( area ) ;
2013-04-04 00:32:52 -04:00
if ( draw ) {
2015-10-05 10:17:49 -04: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
2017-01-19 14:54:24 -05:00
_root . render ( draw , context ) ;
2013-12-23 15:35:49 -05:00
2014-09-30 12:51:31 -04:00
# if defined CANVAS_DEBUG && !PLATFORM_WINDOWS
2014-06-27 10:27:04 -04:00
if ( getenv ( " CANVAS_HARLEQUIN_DEBUGGING " ) ) {
// This transparently colors the rect being rendered, after it has been drawn.
double r = ( random ( ) % 65536 ) / 65536.0 ;
double g = ( random ( ) % 65536 ) / 65536.0 ;
double b = ( random ( ) % 65536 ) / 65536.0 ;
2017-01-19 14:54:24 -05:00
context - > rectangle ( draw . x0 , draw . y0 , draw . x1 - draw . x0 , draw . y1 - draw . y0 ) ;
2014-06-27 10:27:04 -04:00
context - > set_source_rgba ( r , g , b , 0.25 ) ;
context - > fill ( ) ;
}
2014-06-17 11:34:10 -04:00
# endif
2013-04-04 00:32:52 -04:00
}
2013-10-31 11:49:36 -04:00
2013-04-04 00:32:52 -04:00
}
2017-04-01 09:02:49 -04:00
void
Canvas : : prepare_for_render ( Rect const & area ) const
{
Rect root_bbox = _root . bounding_box ( ) ;
if ( ! root_bbox ) {
/* the root has no bounding box, so there's nothing to render */
return ;
}
Rect draw = root_bbox . intersection ( area ) ;
if ( draw ) {
_root . prepare_for_render ( draw ) ;
}
}
2013-04-05 11:27:26 -04:00
ostream &
operator < < ( ostream & o , Canvas & c )
{
c . dump ( o ) ;
return o ;
}
std : : string
Canvas : : indent ( ) const
2015-10-04 14:51:05 -04:00
{
2013-04-05 11:27:26 -04:00
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
2015-10-04 14:51:05 -04:00
{
2013-04-09 14:22:58 -04:00
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 ) ;
2015-10-05 10:17:49 -04:00
}
2013-04-05 11:27:26 -04:00
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
{
2017-01-19 14:54:24 -05:00
Rect bbox = item - > bounding_box ( ) ;
2013-04-16 10:07:52 -04:00
if ( bbox ) {
2017-01-25 10:54:29 -05:00
if ( item - > item_to_window ( bbox ) . intersection ( visible_area ( ) ) ) {
2017-01-19 14:54:24 -05:00
queue_draw_item_area ( item , bbox ) ;
2014-06-18 10:06:32 -04:00
}
2013-04-16 10:07:52 -04:00
}
}
/** 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
{
2017-01-19 14:54:24 -05:00
Rect bbox = item - > bounding_box ( ) ;
2013-04-04 00:32:52 -04:00
if ( bbox ) {
2017-01-25 10:56:31 -05:00
if ( item - > item_to_window ( bbox ) . intersection ( visible_area ( ) ) ) {
2017-01-19 14:54:24 -05:00
queue_draw_item_area ( item , bbox ) ;
2014-06-18 10:06:32 -04:00
}
2013-04-04 00:32:52 -04:00
}
}
/** 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
2017-01-19 14:54:24 -05:00
Canvas : : item_changed ( Item * item , Rect pre_change_bounding_box )
2013-04-04 00:32:52 -04:00
{
2014-06-18 10:06:32 -04:00
Rect window_bbox = visible_area ( ) ;
2013-04-04 00:32:52 -04:00
if ( pre_change_bounding_box ) {
2017-01-19 14:54:24 -05:00
if ( item - > item_to_window ( pre_change_bounding_box ) . intersection ( window_bbox ) ) {
2014-06-18 10:06:32 -04:00
/* request a redraw of the item's old bounding box */
2017-01-19 14:54:24 -05:00
queue_draw_item_area ( item , pre_change_bounding_box ) ;
2014-06-18 10:06:32 -04:00
}
2013-04-04 00:32:52 -04:00
}
2017-01-19 14:54:24 -05:00
Rect post_change_bounding_box = item - > bounding_box ( ) ;
2015-10-05 10:17:49 -04:00
2017-01-19 14:54:24 -05:00
if ( post_change_bounding_box ) {
2017-04-01 09:02:49 -04:00
Rect const window_intersection =
item - > item_to_window ( post_change_bounding_box ) . intersection ( window_bbox ) ;
if ( window_intersection ) {
2014-06-18 10:06:32 -04:00
/* request a redraw of the item's new bounding box */
2017-01-19 14:54:24 -05:00
queue_draw_item_area ( item , post_change_bounding_box ) ;
2017-04-01 09:02:49 -04:00
// Allow item to do any work necessary to prepare for being rendered.
item - > prepare_for_render ( window_intersection ) ;
} else {
// No intersection with visible window area
2014-06-18 10:06:32 -04:00
}
2013-04-04 00:32:52 -04:00
}
}
2013-04-17 10:53:17 -04:00
Duple
Canvas : : window_to_canvas ( Duple const & d ) const
{
2015-01-16 19:09:47 -05:00
ScrollGroup * best_group = 0 ;
2015-01-24 14:07:36 -05:00
ScrollGroup * sg = 0 ;
2014-06-03 15:57:56 -04:00
2014-06-27 10:19:21 -04:00
/* if the coordinates are negative, clamp to zero and find the item
* that covers that " edge " position .
*/
Duple in_window ( d ) ;
if ( in_window . x < 0 ) {
in_window . x = 0 ;
}
if ( in_window . y < 0 ) {
in_window . y = 0 ;
}
2015-01-24 14:07:36 -05:00
for ( list < ScrollGroup * > : : const_iterator s = scrollers . begin ( ) ; s ! = scrollers . end ( ) ; + + s ) {
if ( ( * s ) - > covers_window ( in_window ) ) {
sg = * s ;
/* XXX January 22nd 2015: leaving this in place for now
* but I think it fixes a bug that really should be
* fixed in a different way ( and will be ) by my next
2015-10-04 14:51:05 -04:00
* commit . But it may still be relevant .
2015-01-24 14:07:36 -05:00
*/
/* If scroll groups overlap, choose the one with the highest sensitivity,
that is , choose an HV scroll group over an H or V
2015-10-04 14:51:05 -04:00
only group .
2015-01-24 14:07:36 -05:00
*/
if ( ! best_group | | sg - > sensitivity ( ) > best_group - > sensitivity ( ) ) {
best_group = sg ;
if ( sg - > sensitivity ( ) = = ( ScrollGroup : : ScrollsVertically | ScrollGroup : : ScrollsHorizontally ) ) {
/* Can't do any better than this. */
break ;
}
2015-01-16 19:09:47 -05:00
}
2014-06-03 15:57:56 -04:00
}
}
2015-01-16 19:09:47 -05:00
if ( best_group ) {
return d . translate ( best_group - > scroll_offset ( ) ) ;
2014-06-03 15:57:56 -04:00
}
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
2017-01-19 14:54:24 -05:00
Canvas : : item_moved ( Item * item , Rect pre_change_parent_bounding_box )
2013-04-04 00:32:52 -04:00
{
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 .
*/
2017-01-19 14:54:24 -05:00
queue_draw_item_area ( item - > parent ( ) , pre_change_parent_bounding_box ) ;
2013-04-04 00:32:52 -04:00
}
2017-01-19 14:54:24 -05:00
Rect post_change_bounding_box = item - > bounding_box ( ) ;
2013-04-04 00:32:52 -04:00
if ( post_change_bounding_box ) {
/* request a redraw of where the item now is */
2017-01-19 14:54:24 -05:00
queue_draw_item_area ( item , post_change_bounding_box ) ;
2013-04-04 00:32:52 -04:00
}
}
/** 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
}
2014-09-26 12:24:47 -04:00
void
Canvas : : set_tooltip_timeout ( uint32_t msecs )
{
tooltip_timeout_msecs = msecs ;
}
2014-11-10 10:35:10 -05:00
void
Canvas : : set_background_color ( Color c )
{
2017-03-17 11:03:59 -04:00
_bg_color = c ;
2014-11-10 10:35:10 -05:00
2017-03-17 11:03:59 -04:00
Rect r = _root . bounding_box ( ) ;
2014-11-10 10:35:10 -05:00
2017-03-17 11:03:59 -04:00
if ( r ) {
request_redraw ( _root . item_to_window ( r ) ) ;
}
2014-11-10 10:35:10 -05:00
}
2014-07-07 07:53:17 -04:00
void
GtkCanvas : : re_enter ( )
{
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEnterLeave , " re-enter canvas by request \n " ) ;
_current_item = 0 ;
pick_current_item ( 0 ) ;
}
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 )
2015-02-12 12:22:55 -05:00
, _single_exposure ( 1 )
2014-09-25 21:43:15 -04:00
, current_tooltip_item ( 0 )
, tooltip_window ( 0 )
2015-11-01 23:15:55 -05:00
, _in_dtor ( false )
2017-03-19 17:40:58 -04:00
, _nsglview ( 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 |
2014-11-15 02:04:28 -05:00
Gdk : : SCROLL_MASK | Gdk : : ENTER_NOTIFY_MASK | Gdk : : LEAVE_NOTIFY_MASK |
Gdk : : KEY_PRESS_MASK | Gdk : : KEY_RELEASE_MASK ) ;
2017-03-21 00:37:44 -04:00
}
2017-03-19 17:40:58 -04:00
2017-03-21 00:37:44 -04:00
void
GtkCanvas : : use_nsglview ( )
{
assert ( ! _nsglview ) ;
assert ( ! is_realized ( ) ) ;
2017-03-19 21:15:15 -04:00
# ifdef ARDOUR_CANVAS_NSVIEW_TAG // patched gdkquartz.h
2017-03-19 23:21:08 -04:00
_nsglview = Gtkmm2ext : : nsglview_create ( this ) ;
2017-03-19 17:40:58 -04:00
# endif
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 .
*/
2015-10-05 10:17:49 -04:00
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
2014-06-30 16:32:01 -04:00
std : : cerr < < " \t Item " < < ( * it ) - > whatami ( ) < < ' / ' < < ( * it ) - > name < < " ignore events ? " < < ( * it ) - > ignore_events ( ) < < " vis ? " < < ( * it ) - > visible ( ) < < std : : endl ;
2014-02-25 16:07:51 -05:00
# else
2014-06-30 16:32:01 -04:00
std : : cerr < < " \t Item " < < ( * it ) - > whatami ( ) < < ' / ' < < " ignore events ? " < < ( * it ) - > ignore_events ( ) < < " vis ? " < < ( * it ) - > visible ( ) < < std : : endl ;
2014-02-25 16:07:51 -05:00
# 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
*/
2015-10-05 10:17:49 -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
2014-06-22 09:29:16 -04:00
Item const * possible_item = * i ;
2013-11-03 10:07:00 -05:00
2014-06-22 09:29:16 -04:00
/* We ignore invisible items, containers and items that ignore events */
2013-12-12 20:44:04 -05:00
2014-06-22 09:29:16 -04:00
if ( ! possible_item - > visible ( ) | | possible_item - > ignore_events ( ) | | dynamic_cast < ArdourCanvas : : Container const * > ( possible_item ) ! = 0 ) {
2013-04-24 15:42:14 -04:00
continue ;
}
2014-06-22 09:29:16 -04:00
within_items . push_front ( possible_item ) ;
2013-12-12 10:03:33 -05:00
}
2013-04-24 18:31:00 -04:00
2014-06-30 08:31:20 -04:00
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEnterLeave , string_compose ( " after filtering insensitive + containers, we have %1 items \n " , within_items . size ( ) ) ) ;
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 */
2014-07-18 10:43:46 -04:00
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEnterLeave , string_compose ( " CURRENT ITEM %1/%2 \n " , _new_current_item - > whatami ( ) , _current_item - > name ) ) ;
2013-12-12 20:44:04 -05:00
return ;
2013-04-24 22:57:23 -04:00
}
2015-10-05 10:17:49 -04: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 ) ;
}
2014-07-18 10:43:46 -04:00
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 " ) ;
}
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 */
2014-07-07 08:00:20 -04:00
Glib : : RefPtr < Gdk : : Window > win = get_window ( ) ;
if ( ! win ) {
return ;
}
2013-12-12 20:44:04 -05:00
GdkEventCrossing enter_event ;
enter_event . type = GDK_ENTER_NOTIFY ;
2014-07-07 08:00:20 -04:00
enter_event . window = win - > gobj ( ) ;
2013-12-12 20:44:04 -05:00
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 .
*/
2015-10-05 10:17:49 -04:00
2014-06-08 11:24:28 -04:00
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 ;
2014-12-31 17:32:21 -05:00
GdkNotifyType enter_detail = GDK_NOTIFY_UNKNOWN ;
GdkNotifyType leave_detail = GDK_NOTIFY_UNKNOWN ;
2013-12-12 20:44:04 -05:00
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
2015-10-04 14:51:05 -04:00
* entire heirarchy for the new item
2013-12-12 20:44:04 -05:00
*/
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
2015-10-04 14:51:05 -04: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 .
*/
2015-10-05 10:17:49 -04:00
2013-12-12 20:44:04 -05:00
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
}
2015-10-05 10:17:49 -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 ) ) ;
2014-09-26 12:24:47 -04:00
start_tooltip_timeout ( _new_current_item ) ;
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 ) ;
2015-10-05 10:17:49 -04:00
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 ( ) ;
2015-10-04 14:51:05 -04:00
if ( ! item - > ignore_events ( ) & &
2013-12-12 20:44:04 -05:00
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
) ;
2015-10-05 10:17:49 -04:00
2013-04-04 00:32:52 -04:00
return true ;
}
2015-10-05 10:17:49 -04:00
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 ;
}
2017-04-16 19:45:25 -04:00
void
GtkCanvas : : item_shown_or_hidden ( Item * item )
{
if ( item = = current_tooltip_item ) {
stop_tooltip_timeout ( ) ;
}
Canvas : : item_shown_or_hidden ( item ) ;
}
2013-04-04 00:32:52 -04:00
/** Called when an item is being destroyed.
* @ param item Item being destroyed .
* @ param bounding_box Last known bounding box of the item .
*/
void
2017-01-19 14:54:24 -05:00
GtkCanvas : : item_going_away ( Item * item , Rect bounding_box )
2013-04-04 00:32:52 -04:00
{
if ( bounding_box ) {
2017-01-19 14:54:24 -05:00
queue_draw_item_area ( item , bounding_box ) ;
2013-04-04 00:32:52 -04:00
}
2015-10-05 10:17:49 -04:00
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-09-25 21:43:15 -04:00
if ( current_tooltip_item ) {
current_tooltip_item = 0 ;
stop_tooltip_timeout ( ) ;
}
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 ) {
2015-10-04 14:51:05 -04:00
/* no need to send a leave event to this item, since it is going away
2014-01-20 10:59:44 -05:00
*/
_current_item = 0 ;
2014-01-20 10:53:58 -05:00
pick_current_item ( 0 ) ; // no mouse state
}
2015-10-05 10:17:49 -04:00
2013-04-04 00:32:52 -04:00
}
2017-03-19 17:40:58 -04:00
void
GtkCanvas : : on_realize ( )
{
Gtk : : EventBox : : on_realize ( ) ;
# ifdef __APPLE__
2017-03-19 22:37:37 -04:00
if ( _nsglview ) {
2017-03-19 23:21:08 -04:00
Gtkmm2ext : : nsglview_overlay ( _nsglview , get_window ( ) - > gobj ( ) ) ;
2017-03-19 22:37:37 -04:00
}
2017-03-19 17:40:58 -04:00
# endif
}
2014-10-27 21:15:10 -04:00
void
GtkCanvas : : on_size_allocate ( Gtk : : Allocation & a )
{
EventBox : : on_size_allocate ( a ) ;
2015-05-01 22:54:59 -04:00
# ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
if ( getenv ( " ARDOUR_IMAGE_SURFACE " ) ) {
# endif
# if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
2014-10-27 21:15:10 -04:00
/* allocate an image surface as large as the canvas itself */
canvas_image . clear ( ) ;
canvas_image = Cairo : : ImageSurface : : create ( Cairo : : FORMAT_ARGB32 , a . get_width ( ) , a . get_height ( ) ) ;
# endif
2015-05-01 22:54:59 -04:00
# ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
}
# endif
2017-03-19 17:40:58 -04:00
# ifdef __APPLE__
if ( _nsglview ) {
gint xx , yy ;
gtk_widget_translate_coordinates (
GTK_WIDGET ( gobj ( ) ) ,
GTK_WIDGET ( get_toplevel ( ) - > gobj ( ) ) ,
0 , 0 , & xx , & yy ) ;
2017-03-19 23:21:08 -04:00
Gtkmm2ext : : nsglview_resize ( _nsglview , xx , yy , a . get_width ( ) , a . get_height ( ) ) ;
2017-03-19 17:40:58 -04:00
}
# endif
2014-10-27 21:15:10 -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 )
{
2015-11-01 23:15:55 -05:00
if ( _in_dtor ) {
return true ;
}
2017-03-19 17:40:58 -04:00
# ifdef __APPLE__
if ( _nsglview ) {
2017-03-19 23:21:08 -04:00
Gtkmm2ext : : nsglview_queue_draw ( _nsglview , ev - > area . x , ev - > area . y , ev - > area . width , ev - > area . height ) ;
2017-03-19 17:40:58 -04:00
return true ;
}
# endif
2015-11-01 23:15:55 -05:00
2017-03-17 11:54:16 -04:00
# ifdef CANVAS_PROFILE
const int64_t start = g_get_monotonic_time ( ) ;
# endif
2015-05-01 22:54:59 -04:00
# ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
Cairo : : RefPtr < Cairo : : Context > draw_context ;
Cairo : : RefPtr < Cairo : : Context > window_context ;
if ( getenv ( " ARDOUR_IMAGE_SURFACE " ) ) {
if ( ! canvas_image ) {
canvas_image = Cairo : : ImageSurface : : create ( Cairo : : FORMAT_ARGB32 , get_width ( ) , get_height ( ) ) ;
}
draw_context = Cairo : : Context : : create ( canvas_image ) ;
window_context = get_window ( ) - > create_cairo_context ( ) ;
} else {
draw_context = get_window ( ) - > create_cairo_context ( ) ;
}
# elif defined USE_CAIRO_IMAGE_SURFACE
2014-10-27 21:15:10 -04:00
if ( ! canvas_image ) {
canvas_image = Cairo : : ImageSurface : : create ( Cairo : : FORMAT_ARGB32 , get_width ( ) , get_height ( ) ) ;
}
2017-03-17 11:03:59 -04:00
Cairo : : RefPtr < Cairo : : Context > draw_context = Cairo : : Context : : create ( canvas_image ) ;
2014-11-10 10:35:10 -05:00
Cairo : : RefPtr < Cairo : : Context > window_context = get_window ( ) - > create_cairo_context ( ) ;
2015-10-04 14:51:05 -04:00
# else
2014-11-10 10:35:10 -05:00
Cairo : : RefPtr < Cairo : : Context > draw_context = get_window ( ) - > create_cairo_context ( ) ;
# endif
2014-10-27 21:15:10 -04:00
2017-03-17 11:54:16 -04:00
draw_context - > rectangle ( ev - > area . x , ev - > area . y , ev - > area . width , ev - > area . height ) ;
draw_context - > clip ( ) ;
# ifdef __APPLE__
/* group calls cairo_quartz_surface_create() which
* effectively uses a CGBitmapContext + image - surface
*
* This avoids expensive argb32_image_mark_image ( ) during drawing .
* Although the final paint ( ) operation still takes the slow path
* through image_mark_image instead of ColorMaskCopyARGB888_sse : (
*
* profiling indicates a speed up of factor 2. ( ~ 5 - 10 ms render time ,
* instead of 10 - 20 ms , which is still slow compared to XCB and win32 surfaces ( ~ 0.2 ms )
*
* Fixing this for good likely involves changes to GdkQuartzWindow , GdkQuartzView
*/
draw_context - > push_group ( ) ;
# endif
2015-10-04 14:51:05 -04:00
2017-03-17 11:54:16 -04:00
/* draw background color */
2017-03-17 11:03:59 -04:00
draw_context - > rectangle ( ev - > area . x , ev - > area . y , ev - > area . width , ev - > area . height ) ;
set_source_rgba ( draw_context , _bg_color ) ;
draw_context - > fill ( ) ;
2015-10-04 14:51:05 -04:00
2017-03-17 11:03:59 -04:00
/* render canvas */
if ( _single_exposure ) {
2015-10-05 10:17:49 -04:00
2017-03-19 23:21:08 -04:00
Canvas : : render ( Rect ( ev - > area . x , ev - > area . y , ev - > area . x + ev - > area . width , ev - > area . y + ev - > area . height ) , draw_context ) ;
2014-11-10 10:35:10 -05:00
2017-03-17 11:03:59 -04:00
} else {
GdkRectangle * rects ;
gint nrects ;
2015-10-05 10:17:49 -04:00
2017-03-17 11:03:59 -04:00
gdk_region_get_rectangles ( ev - > region , & rects , & nrects ) ;
for ( gint n = 0 ; n < nrects ; + + n ) {
draw_context - > set_identity_matrix ( ) ; //reset the cairo matrix, just in case someone left it transformed after drawing ( cough )
2017-03-19 23:21:08 -04:00
Canvas : : render ( Rect ( rects [ n ] . x , rects [ n ] . y , rects [ n ] . x + rects [ n ] . width , rects [ n ] . y + rects [ n ] . height ) , draw_context ) ;
2015-02-12 12:22:55 -05:00
}
2017-03-17 11:03:59 -04:00
g_free ( rects ) ;
}
2015-10-05 10:17:49 -04:00
2017-03-17 11:54:16 -04:00
# ifdef __APPLE__
draw_context - > pop_group_to_source ( ) ;
draw_context - > paint ( ) ;
# endif
2015-05-01 22:54:59 -04:00
# ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
if ( getenv ( " ARDOUR_IMAGE_SURFACE " ) ) {
# endif
# if defined USE_CAIRO_IMAGE_SURFACE || defined OPTIONAL_CAIRO_IMAGE_SURFACE
2017-03-17 11:03:59 -04:00
/* now blit our private surface back to the GDK one */
2014-10-27 21:15:10 -04:00
2017-03-17 11:03:59 -04:00
window_context - > rectangle ( ev - > area . x , ev - > area . y , ev - > area . width , ev - > area . height ) ;
window_context - > clip ( ) ;
window_context - > set_source ( canvas_image , 0 , 0 ) ;
window_context - > set_operator ( Cairo : : OPERATOR_SOURCE ) ;
window_context - > paint ( ) ;
2014-10-27 21:15:10 -04:00
# endif
2015-05-01 22:54:59 -04:00
# ifdef OPTIONAL_CAIRO_IMAGE_SURFACE
}
# endif
2013-04-04 00:32:52 -04:00
2017-03-17 11:54:16 -04:00
# ifdef CANVAS_PROFILE
const int64_t end = g_get_monotonic_time ( ) ;
const int64_t elapsed = end - start ;
printf ( " GtkCanvas::on_expose_event %f ms \n " , elapsed / 1000.f ) ;
# endif
2014-10-27 21:15:10 -04:00
return true ;
2013-04-04 00:32:52 -04:00
}
2017-04-01 09:02:49 -04:00
void
GtkCanvas : : prepare_for_render ( ) const
{
Rect window_bbox = visible_area ( ) ;
Canvas : : prepare_for_render ( window_bbox ) ;
}
2014-06-24 11:32:41 -04:00
/** Handler for GDK scroll events.
* @ param ev Event .
* @ return true if the event was handled .
*/
bool
GtkCanvas : : on_scroll_event ( GdkEventScroll * ev )
{
/* translate event coordinates from window to canvas */
GdkEvent copy = * ( ( GdkEvent * ) ev ) ;
Duple winpos = Duple ( ev - > x , ev - > y ) ;
Duple where = window_to_canvas ( winpos ) ;
2015-10-05 10:17:49 -04:00
2014-06-24 11:32:41 -04:00
pick_current_item ( winpos , ev - > state ) ;
copy . button . x = where . x ;
copy . button . y = where . y ;
2015-10-05 10:17:49 -04:00
2014-06-24 11:32:41 -04:00
/* Coordinates in the event will be canvas coordinates, correctly adjusted
for scroll if this GtkCanvas is in a GtkCanvasViewport .
*/
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEvents , string_compose ( " canvas scroll @ %1, %2 => %3 \n " , ev - > x , ev - > y , where ) ) ;
return deliver_event ( reinterpret_cast < GdkEvent * > ( & copy ) ) ;
}
2014-11-15 02:04:28 -05:00
/** Handler for GDK key press events.
* @ param ev Event .
* @ return true if the event was handled .
*/
bool
GtkCanvas : : on_key_press_event ( GdkEventKey * ev )
{
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEvents , " canvas key press \n " ) ;
return deliver_event ( reinterpret_cast < GdkEvent * > ( ev ) ) ;
}
/** Handler for GDK key release events.
* @ param ev Event .
* @ return true if the event was handled .
*/
bool
GtkCanvas : : on_key_release_event ( GdkEventKey * ev )
{
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEvents , " canvas key release \n " ) ;
return deliver_event ( reinterpret_cast < GdkEvent * > ( ev ) ) ;
}
2013-04-04 00:32:52 -04:00
/** 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 ) ;
2015-10-05 10:17:49 -04:00
2014-05-23 22:05:08 -04:00
pick_current_item ( winpos , ev - > state ) ;
2013-10-31 16:43:35 -04:00
copy . button . x = where . x ;
copy . button . y = where . y ;
2015-10-05 10:17:49 -04:00
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
2015-10-03 07:54:50 -04:00
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEvents , string_compose ( " canvas button press %1 @ %2, %3 => %4 \n " , ev - > button , 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 )
2015-10-05 10:17:49 -04:00
{
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 ) ;
2015-10-05 10:17:49 -04: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
2015-10-03 07:54:50 -04:00
DEBUG_TRACE ( PBD : : DEBUG : : CanvasEvents , string_compose ( " canvas button release %1 @ %2, %3 => %4 \n " , ev - > button , 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
}
2014-06-26 15:07:12 -04:00
bool
GtkCanvas : : get_mouse_position ( Duple & winpos ) const
{
int x ;
int y ;
Gdk : : ModifierType mask ;
Glib : : RefPtr < Gdk : : Window > self = Glib : : RefPtr < Gdk : : Window > : : cast_const ( get_window ( ) ) ;
if ( ! self ) {
std : : cerr < < " no self window \n " ;
winpos = Duple ( 0 , 0 ) ;
return false ;
}
Glib : : RefPtr < Gdk : : Window > win = self - > get_pointer ( x , y , mask ) ;
winpos . x = x ;
winpos . y = y ;
return true ;
}
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 )
{
2014-09-25 21:43:15 -04:00
hide_tooltip ( ) ;
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 ;
2015-10-04 14:51:05 -04:00
/* Coordinates in "copy" will be canvas coordinates,
2013-11-04 11:56:10 -05:00
*/
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-06-26 15:07:12 -04:00
MouseMotion ( point ) ; /* EMIT SIGNAL */
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
{
2014-09-26 17:06:38 -04:00
switch ( ev - > detail ) {
case GDK_NOTIFY_ANCESTOR :
case GDK_NOTIFY_UNKNOWN :
case GDK_NOTIFY_VIRTUAL :
case GDK_NOTIFY_NONLINEAR :
case GDK_NOTIFY_NONLINEAR_VIRTUAL :
/* leaving window, cancel any tooltips */
stop_tooltip_timeout ( ) ;
hide_tooltip ( ) ;
break ;
2014-09-27 18:09:16 -04:00
default :
/* we don't care about any other kind
of leave event ( notably GDK_NOTIFY_INFERIOR )
*/
break ;
2014-09-26 17:06:38 -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 ;
}
2017-03-20 11:31:42 -04:00
void
GtkCanvas : : on_map ( )
{
Gtk : : EventBox : : on_map ( ) ;
# ifdef __APPLE__
if ( _nsglview ) {
Gtkmm2ext : : nsglview_set_visible ( _nsglview , true ) ;
Gtk : : Allocation a = get_allocation ( ) ;
gint xx , yy ;
gtk_widget_translate_coordinates (
GTK_WIDGET ( gobj ( ) ) ,
GTK_WIDGET ( get_toplevel ( ) - > gobj ( ) ) ,
0 , 0 , & xx , & yy ) ;
Gtkmm2ext : : nsglview_resize ( _nsglview , xx , yy , a . get_width ( ) , a . get_height ( ) ) ;
}
# endif
}
void
GtkCanvas : : on_unmap ( )
{
2017-04-16 19:45:25 -04:00
stop_tooltip_timeout ( ) ;
2017-03-20 11:31:42 -04:00
Gtk : : EventBox : : on_unmap ( ) ;
# ifdef __APPLE__
if ( _nsglview ) {
Gtkmm2ext : : nsglview_set_visible ( _nsglview , false ) ;
}
# endif
}
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
{
2015-11-01 23:15:55 -05:00
if ( _in_dtor ) {
return ;
}
2014-06-05 14:42:46 -04:00
/* clamp area requested to actual visible window */
2017-04-01 09:33:16 -04:00
Rect real_area = request . intersection ( visible_area ( ) ) ;
if ( real_area ) {
if ( real_area . width ( ) & & real_area . height ( ) ) {
// Item intersects with visible canvas area
queue_draw_area ( real_area . x0 , real_area . y0 , real_area . width ( ) , real_area . height ( ) ) ;
}
2014-06-05 14:42:46 -04:00
2017-04-01 09:33:16 -04:00
} else {
// Item does not intersect with visible canvas area
}
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 ;
}
}
2014-06-15 12:18:47 -04:00
/** @return The visible area of the canvas, in window coordinates */
2017-03-19 17:40:58 -04:00
ArdourCanvas : : Rect
2013-04-17 10:53:17 -04:00
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
}
2014-09-25 21:43:15 -04:00
void
GtkCanvas : : start_tooltip_timeout ( Item * item )
{
stop_tooltip_timeout ( ) ;
2016-04-26 19:24:37 -04:00
if ( item & & Gtkmm2ext : : PersistentTooltip : : tooltips_enabled ( ) ) {
2014-09-25 21:43:15 -04:00
current_tooltip_item = item ;
/* wait for the first idle that happens after this is
called . this means that we ' ve stopped processing events , which
in turn implies that the user has stopped doing stuff for a
little while .
*/
Glib : : signal_idle ( ) . connect ( sigc : : mem_fun ( * this , & GtkCanvas : : really_start_tooltip_timeout ) ) ;
}
}
bool
GtkCanvas : : really_start_tooltip_timeout ( )
{
2016-02-22 15:01:23 -05:00
/* an idle has occurred since we entered a tooltip-bearing widget. Now
2014-09-25 21:43:15 -04:00
* wait 1 second and if the timeout isn ' t cancelled , show the tooltip .
*/
if ( current_tooltip_item ) {
2014-09-26 12:24:47 -04:00
tooltip_timeout_connection = Glib : : signal_timeout ( ) . connect ( sigc : : mem_fun ( * this , & GtkCanvas : : show_tooltip ) , tooltip_timeout_msecs ) ;
2014-09-25 21:43:15 -04:00
}
return false ; /* this is called from an idle callback, don't call it again */
}
void
GtkCanvas : : stop_tooltip_timeout ( )
{
current_tooltip_item = 0 ;
2014-09-26 12:24:47 -04:00
tooltip_timeout_connection . disconnect ( ) ;
2014-09-25 21:43:15 -04:00
}
bool
GtkCanvas : : show_tooltip ( )
{
2014-09-26 11:05:24 -04:00
Rect tooltip_item_bbox ;
if ( ! current_tooltip_item | | current_tooltip_item - > tooltip ( ) . empty ( ) | | ! current_tooltip_item - > bounding_box ( ) ) {
return false ;
}
if ( ! tooltip_window ) {
tooltip_window = new Gtk : : Window ( Gtk : : WINDOW_POPUP ) ;
tooltip_label = manage ( new Gtk : : Label ) ;
tooltip_label - > show ( ) ;
tooltip_window - > add ( * tooltip_label ) ;
2015-04-09 10:46:12 -04:00
tooltip_window - > set_border_width ( 1 ) ;
2014-09-26 11:05:24 -04:00
tooltip_window - > set_name ( " tooltip " ) ;
2014-09-25 21:43:15 -04:00
}
2014-09-26 11:05:24 -04:00
tooltip_label - > set_text ( current_tooltip_item - > tooltip ( ) ) ;
/* figure out where to position the tooltip */
Gtk : : Widget * toplevel = get_toplevel ( ) ;
assert ( toplevel ) ;
int pointer_x , pointer_y ;
Gdk : : ModifierType mask ;
( void ) toplevel - > get_window ( ) - > get_pointer ( pointer_x , pointer_y , mask ) ;
2014-09-26 12:24:47 -04:00
Duple tooltip_window_origin ( pointer_x , pointer_y ) ;
2015-10-05 10:17:49 -04:00
2014-09-26 11:05:24 -04:00
/* convert to root window coordinates */
int win_x , win_y ;
dynamic_cast < Gtk : : Window * > ( toplevel ) - > get_position ( win_x , win_y ) ;
2015-10-05 10:17:49 -04:00
2014-09-26 12:24:47 -04:00
tooltip_window_origin = tooltip_window_origin . translate ( Duple ( win_x , win_y ) ) ;
/* we don't want the pointer to be inside the window when it is
* displayed , because then we generate a leave / enter event pair when
* the window is displayed then hidden - the enter event will
* trigger a new tooltip timeout .
*
* So move the window right of the pointer position by just a enough
* to get it away from the pointer .
*/
2015-04-09 10:46:12 -04:00
tooltip_window_origin . x + = 30 ;
tooltip_window_origin . y + = 45 ;
2014-09-26 11:05:24 -04:00
/* move the tooltip window into position */
2014-09-26 12:24:47 -04:00
tooltip_window - > move ( tooltip_window_origin . x , tooltip_window_origin . y ) ;
2014-09-26 11:05:24 -04:00
/* ready to show */
tooltip_window - > present ( ) ;
2015-10-05 10:17:49 -04:00
2014-09-25 21:43:15 -04:00
/* called from a timeout handler, don't call it again */
2014-09-26 11:05:24 -04:00
2014-09-25 21:43:15 -04:00
return false ;
}
void
GtkCanvas : : hide_tooltip ( )
{
2014-09-26 12:24:47 -04:00
/* hide it if its there */
2014-09-25 21:43:15 -04:00
if ( tooltip_window ) {
tooltip_window - > hide ( ) ;
2015-04-09 10:42:00 -04:00
// Delete the tooltip window so it'll get re-created
// (i.e. properly re-sized) on the next usage.
delete tooltip_window ;
tooltip_window = NULL ;
2014-09-25 21:43:15 -04:00
}
}
2016-10-13 17:11:38 -04:00
Glib : : RefPtr < Pango : : Context >
GtkCanvas : : get_pango_context ( )
{
return Glib : : wrap ( gdk_pango_context_get ( ) ) ;
}
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 */
2015-10-04 14:51:05 -04:00
// _canvas.root()->bounding_box();
2013-04-11 20:19:22 -04:00
2013-04-04 00:32:52 -04:00
req - > width = 16 ;
req - > height = 16 ;
}