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>
2014-09-25 21:43:15 -04:00
# include <gtkmm/window.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"
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
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 )
2014-11-10 10:35:10 -05: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
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
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 ;
context - > rectangle ( draw - > x0 , draw - > y0 , draw - > x1 - draw - > x0 , draw - > y1 - draw - > y0 ) ;
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
}
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 ) {
2014-06-18 10:06:32 -04:00
if ( item - > item_to_window ( * bbox ) . intersection ( visible_area ( ) ) ) {
queue_draw_item_area ( item , bbox . get ( ) ) ;
}
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
{
boost : : optional < Rect > bbox = item - > bounding_box ( ) ;
if ( bbox ) {
2014-06-18 10:06:32 -04:00
if ( item - > item_to_window ( * bbox ) . intersection ( visible_area ( ) ) ) {
queue_draw_item_area ( item , bbox . get ( ) ) ;
}
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
Canvas : : item_changed ( Item * item , boost : : optional < Rect > pre_change_bounding_box )
{
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 ) {
2014-06-18 10:06:32 -04:00
if ( item - > item_to_window ( * pre_change_bounding_box ) . intersection ( window_bbox ) ) {
/* request a redraw of the item's old bounding box */
queue_draw_item_area ( item , pre_change_bounding_box . get ( ) ) ;
}
2013-04-04 00:32:52 -04:00
}
boost : : optional < Rect > post_change_bounding_box = item - > bounding_box ( ) ;
if ( post_change_bounding_box ) {
2014-06-18 10:06:32 -04:00
if ( item - > item_to_window ( * post_change_bounding_box ) . intersection ( window_bbox ) ) {
/* request a redraw of the item's new bounding box */
queue_draw_item_area ( item , post_change_bounding_box . get ( ) ) ;
}
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
{
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 ;
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 ;
}
2014-06-03 15:57:56 -04:00
for ( std : : list < Item * > : : const_iterator i = root_children . begin ( ) ; i ! = root_children . end ( ) ; + + i ) {
2014-06-27 10:19:21 -04:00
if ( ( ( sg = dynamic_cast < ScrollGroup * > ( * i ) ) ! = 0 ) & & sg - > covers_window ( in_window ) ) {
2014-06-03 15:57:56 -04:00
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
}
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 )
{
_bg_color = c ;
boost : : optional < Rect > r = _root . bounding_box ( ) ;
if ( r ) {
request_redraw ( _root . item_to_window ( r . get ( ) ) ) ;
}
}
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 )
2014-09-25 21:43:15 -04:00
, current_tooltip_item ( 0 )
, tooltip_window ( 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 ) ;
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
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
*/
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
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
}
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 ) ;
}
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 .
*/
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
* 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 ) ) ;
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 ) ;
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-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 ) {
/* 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
}
2014-10-27 21:15:10 -04:00
void
GtkCanvas : : on_size_allocate ( Gtk : : Allocation & a )
{
EventBox : : on_size_allocate ( a ) ;
# ifdef USE_CAIRO_IMAGE_SURFACE
/* 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
}
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-10-27 21:15:10 -04:00
# ifdef USE_CAIRO_IMAGE_SURFACE
if ( ! canvas_image ) {
canvas_image = Cairo : : ImageSurface : : create ( Cairo : : FORMAT_ARGB32 , get_width ( ) , get_height ( ) ) ;
}
2014-11-10 10:35:10 -05:00
Cairo : : RefPtr < Cairo : : Context > draw_context = Cairo : : Context : : create ( canvas_image ) ;
Cairo : : RefPtr < Cairo : : Context > window_context = get_window ( ) - > create_cairo_context ( ) ;
# else
Cairo : : RefPtr < Cairo : : Context > draw_context = get_window ( ) - > create_cairo_context ( ) ;
# endif
2014-10-27 21:15:10 -04:00
2014-11-10 10:35:10 -05:00
/* draw background color */
draw_context - > rectangle ( ev - > area . x , ev - > area . y , ev - > area . width , ev - > area . height ) ;
2014-11-10 20:34:20 -05:00
draw_context - > clip_preserve ( ) ;
2014-11-10 10:35:10 -05:00
set_source_rgba ( draw_context , _bg_color ) ;
draw_context - > fill ( ) ;
/* render 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-13 13:05:58 -05:00
# ifdef USE_CAIRO_IMAGE_SURFACE
2014-10-27 21:15:10 -04:00
/* now blit our private surface back to the GDK one */
2014-11-10 10:35:10 -05: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
2013-04-04 00:32:52 -04:00
2014-10-27 21:15:10 -04:00
return true ;
2013-04-04 00:32:52 -04:00
}
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 ) ;
pick_current_item ( winpos , ev - > state ) ;
copy . button . x = where . x ;
copy . button . y = where . y ;
/* 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 ) ;
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
}
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 ;
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-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 ;
}
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 ;
}
}
2014-06-15 12:18:47 -04:00
/** @return The visible area of the canvas, in window coordinates */
2013-04-17 10:53:17 -04:00
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
}
2014-09-25 21:43:15 -04:00
void
GtkCanvas : : start_tooltip_timeout ( Item * item )
{
stop_tooltip_timeout ( ) ;
if ( item ) {
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 ( )
{
/* an idle has occured since we entered a tooltip-bearing widget. Now
* 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 ) ;
tooltip_window - > set_border_width ( 6 ) ;
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 ) ;
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 ) ;
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 .
*/
tooltip_window_origin . x + = 20 ;
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 ( ) ;
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 ( ) ;
}
}
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 ;
}