2013-04-04 00:32:52 -04:00
/*
2019-08-02 22:40:09 -04:00
* Copyright ( C ) 2012 Carl Hetherington < carl @ carlh . net >
* Copyright ( C ) 2013 - 2017 Paul Davis < paul @ linuxaudiosystems . com >
* Copyright ( C ) 2014 - 2015 Ben Loftis < ben @ harrisonconsoles . com >
* Copyright ( C ) 2014 - 2015 David Robillard < d @ drobilla . net >
* Copyright ( C ) 2014 - 2017 Robin Gareus < robin @ gareus . org >
* Copyright ( C ) 2015 - 2017 Tim Mayberry < mojofunk @ gmail . com >
* Copyright ( C ) 2015 John Emmas < john @ creativepost . co . uk >
*
* 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 . ,
* 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA .
*/
2013-04-04 00:32:52 -04:00
2023-02-04 12:19:04 -05:00
//#define CANVAS_PROFILE
2023-01-31 19:25:27 -05:00
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
2013-04-04 00:32:52 -04:00
# include "canvas/canvas.h"
2017-07-17 12:34:35 -04:00
# include "gtkmm2ext/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"
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 )
2022-03-29 10:52:17 -04:00
, _queue_draw_frozen ( 0 )
2017-07-17 12:34:35 -04:00
, _bg_color ( Gtkmm2ext : : rgba_to_color ( 0 , 1.0 , 0.0 , 1.0 ) )
2022-09-03 18:18:02 -04:00
, _debug_render ( false )
2017-03-28 00:05:03 -04:00
, _last_render_start_timestamp ( 0 )
2020-01-03 18:29:48 -05:00
, _use_intermediate_surface ( false )
2013-04-04 00:32:52 -04:00
{
2020-01-03 18:29:48 -05:00
# ifdef __APPLE__
2019-12-26 17:55:44 -05:00
_use_intermediate_surface = true ;
2019-12-26 12:19:21 -05:00
# else
2020-01-03 18:29:48 -05:00
_use_intermediate_surface = NULL ! = g_getenv ( " ARDOUR_INTERMEDIATE_SURFACE " ) ;
2019-12-26 12:19:21 -05:00
# endif
2024-03-22 15:39:03 -04:00
if ( g_getenv ( " ARDOUR_ITEM_CAIRO_SAVE_RESTORE " ) ) {
item_save_restore = true ;
} else {
item_save_restore = false ;
}
2013-04-04 00:32:52 -04:00
set_epoch ( ) ;
}
2019-12-26 17:55:44 -05:00
void
Canvas : : use_intermediate_surface ( bool yn )
{
2020-01-03 18:29:48 -05:00
if ( _use_intermediate_surface = = yn ) {
return ;
}
2019-12-26 17:55:44 -05:00
_use_intermediate_surface = yn ;
}
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
}
2022-06-21 19:35:01 -04:00
# ifndef NDEBUG
# ifdef CANVAS_DEBUG
# undef CANVAS_DEBUG
2020-06-24 20:11:37 -04:00
# define CANVAS_DEBUG
2022-06-21 19:35:01 -04:00
# endif
# endif
2020-06-24 20:11:37 -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
{
2023-01-31 19:25:27 -05:00
# ifdef CANVAS_PROFILE
const int64_t start = g_get_monotonic_time ( ) ;
# endif
2023-01-21 11:34:57 -05:00
2017-03-13 06:45:13 -04:00
PreRender ( ) ; // emit signal
2017-03-28 00:05:03 -04:00
_last_render_start_timestamp = g_get_monotonic_time ( ) ;
2013-04-09 14:22:58 -04:00
# ifdef CANVAS_DEBUG
2022-09-03 18:18:02 -04:00
if ( _debug_render | | DEBUG_ENABLED ( PBD : : DEBUG : : CanvasRender ) ) {
2014-01-10 14:35:36 -05:00
cerr < < this < < " RENDER: " < < area < < endl ;
2021-08-04 01:21:55 -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 */
2020-06-24 20:11:37 -04:00
cerr < < " no bbox \n " ;
2013-04-04 00:32:52 -04:00
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
2023-01-31 19:25:27 -05:00
# ifdef CANVAS_PROFILE
2023-02-04 12:19:04 -05:00
const int64_t end = g_get_monotonic_time ( ) ;
const int64_t elapsed = end - start ;
std : : cout < < " GtkCanvas::render " < < area < < " " < < ( elapsed / 1000.f ) < < " ms \n " ;
2023-01-31 19:25:27 -05:00
# endif
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 ) ;
}
}
2017-03-28 01:52:28 -04:00
gint64
Canvas : : get_microseconds_since_render_start ( ) const
{
gint64 timestamp = g_get_monotonic_time ( ) ;
if ( _last_render_start_timestamp = = 0 | | timestamp < = _last_render_start_timestamp ) {
return 0 ;
}
return timestamp - _last_render_start_timestamp ;
}
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
2022-03-29 10:52:17 -04:00
void
Canvas : : freeze_queue_draw ( )
{
_queue_draw_frozen + + ;
}
void
Canvas : : thaw_queue_draw ( )
{
if ( _queue_draw_frozen ) {
_queue_draw_frozen - - ;
if ( _queue_draw_frozen = = 0 & & ! frozen_area . empty ( ) ) {
request_redraw ( frozen_area ) ;
frozen_area = Rect ( ) ;
}
}
}
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 ) {
2022-03-29 10:52:17 -04:00
if ( _queue_draw_frozen ) {
frozen_area = frozen_area . extend ( compute_draw_item_area ( item , bbox ) ) ;
return ;
}
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 ( ) ) {
2021-12-10 20:17:58 -05:00
2015-01-24 14:07:36 -05:00
best_group = sg ;
2021-12-10 20:17:58 -05:00
2015-01-24 14:07:36 -05:00
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
}
}
2022-01-20 11:45:47 -05:00
if ( best_group & & ( ! have_grab ( ) | | grab_can_translate ( ) ) ) {
2015-01-16 19:09:47 -05:00
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 )
{
2022-03-29 10:52:17 -04:00
request_redraw ( compute_draw_item_area ( item , area ) ) ;
}
Rect
Canvas : : compute_draw_item_area ( Item * item , Rect area )
{
Rect r ;
2018-11-25 19:07:41 -05:00
if ( ( area . width ( ) ) > 1.0 & & ( area . height ( ) > 1.0 ) ) {
/* item has a rectangular bounding box, which may fall
* on non - integer locations . Expand it appropriately .
*/
2022-03-29 10:52:17 -04:00
r = item - > item_to_window ( area , false ) ;
2018-11-25 19:07:41 -05:00
r . x0 = floor ( r . x0 ) ;
r . y0 = floor ( r . y0 ) ;
r . x1 = ceil ( r . x1 ) ;
r . y1 = ceil ( r . y1 ) ;
//std::cerr << "redraw box, adjust from " << area << " to " << r << std::endl;
} else if ( area . width ( ) > 1.0 & & area . height ( ) = = 1.0 ) {
/* horizontal line, which may fall on non-integer
* coordinates .
*/
2022-03-29 10:52:17 -04:00
r = item - > item_to_window ( area , false ) ;
2018-11-25 19:07:41 -05:00
r . y0 = floor ( r . y0 ) ;
r . y1 = ceil ( r . y1 ) ;
//std::cerr << "redraw HLine, adjust from " << area << " to " << r << std::endl;
} else if ( area . width ( ) = = 1.0 & & area . height ( ) > 1.0 ) {
/* vertical single pixel line, which may fall on non-integer
* coordinates
*/
2022-03-29 10:52:17 -04:00
r = item - > item_to_window ( area , false ) ;
2018-11-25 19:07:41 -05:00
r . x0 = floor ( r . x0 ) ;
r . x1 = ceil ( r . x1 ) ;
//std::cerr << "redraw VLine, adjust from " << area << " to " << r << std::endl;
2022-03-29 10:52:17 -04:00
2018-11-25 19:07:41 -05:00
} else {
/* impossible? one of width or height must be zero ... */
//std::cerr << "redraw IMPOSSIBLE of " << area << std::endl;
2022-03-29 10:52:17 -04:00
r = item - > item_to_window ( area , false ) ;
2018-11-25 19:07:41 -05:00
}
2022-03-29 10:52:17 -04:00
return r ;
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
2017-07-17 12:34:35 -04:00
Canvas : : set_background_color ( Gtkmm2ext : : Color c )
2014-11-10 10:35:10 -05:00
{
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 )
2020-01-03 18:29:48 -05:00
, _single_exposure ( true )
, _use_image_surface ( false )
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 )
2018-11-19 11:31:09 -05:00
, resize_queued ( false )
2017-03-19 17:40:58 -04:00
, _nsglview ( 0 )
2013-04-04 00:32:52 -04:00
{
2020-01-03 18:29:48 -05:00
# ifdef USE_CAIRO_IMAGE_SURFACE /* usually Windows builds */
2020-01-05 05:29:35 -05:00
_use_image_surface = true ;
2020-01-03 18:29:48 -05:00
# else
_use_image_surface = NULL ! = g_getenv ( " ARDOUR_IMAGE_SURFACE " ) ;
# endif
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
2024-03-18 13:28:16 -04:00
void
GtkCanvas : : set_single_exposure ( bool yn )
{
if ( g_getenv ( " ARDOUR_CANVAS_SINGLE_EXPOSE_ALWAYS " ) ) {
yn = true ;
}
_single_exposure = yn ;
}
2017-03-21 00:37:44 -04:00
void
2023-02-04 16:25:52 -05:00
GtkCanvas : : use_nsglview ( bool retina )
2017-03-21 00:37:44 -04:00
{
assert ( ! _nsglview ) ;
2022-01-26 15:22:00 -05:00
assert ( ! get_realized ( ) ) ;
2017-03-19 21:15:15 -04:00
# ifdef ARDOUR_CANVAS_NSVIEW_TAG // patched gdkquartz.h
2023-02-04 16:25:52 -05:00
_nsglview = Gtkmm2ext : : nsglview_create ( this , retina ) ;
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
2022-10-30 20:44:28 -04:00
/** Given @p point (a position in window coordinates)
* and mouse state @ p state , check to see if _current_item
2014-06-08 11:24:28 -04:00
* ( 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 ) ) {
2023-03-11 22:32:21 -05:00
for ( auto const & item : items ) {
2023-03-11 22:49:28 -05:00
std : : cerr < < " \t Item " < < item - > whoami ( ) < < " ignore events ? " < < item - > ignore_events ( ) < < " vis ? " < < item - > visible ( ) < < std : : endl ;
2014-02-25 16:07:51 -05:00
}
}
# 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
2023-04-30 10:09:20 -04:00
/* no items at point, do not send a LEAVE event in this case */
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
2023-04-30 10:09:20 -04:00
if ( _new_current_item ! = _current_item ) {
deliver_enter_leave ( point , state ) ;
}
2014-01-10 11:02:05 -05:00
}
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
2022-10-30 20:44:28 -04:00
* coordinate @ p point , and pointer @ p state ( modifier keys , etc )
2014-05-23 22:05:08 -04:00
*/
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
2022-10-30 20:44:28 -04:00
* coordinates but @ p point is in window coordinates .
2014-06-08 11:24:28 -04:00
*/
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
2022-04-08 14:09:37 -04:00
* entire hierarchy for the current item
2013-12-12 20:44:04 -05:00
*/
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
2022-04-08 14:09:37 -04:00
* entire hierarchy 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
2022-04-08 14:09:37 -04:00
* hierarchy between current and new_current .
2013-12-12 20:44:04 -05:00
*/
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
2022-04-08 14:09:37 -04:00
* hierarchy between current and new_current .
2013-12-12 20:44:04 -05:00
*/
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
2022-04-04 14:50:31 -04:00
if ( _current_item = = current_tooltip_item ) {
hide_tooltip ( ) ;
}
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
2020-06-19 15:59:50 -04:00
_root . set_fill ( false ) ;
_root . set_outline ( false ) ;
2017-03-19 17:40:58 -04:00
}
2014-10-27 21:15:10 -04:00
void
GtkCanvas : : on_size_allocate ( Gtk : : Allocation & a )
{
EventBox : : on_size_allocate ( a ) ;
2017-03-19 17:40:58 -04:00
2020-01-03 18:29:48 -05:00
if ( _use_image_surface ) {
_canvas_image . clear ( ) ;
_canvas_image = Cairo : : ImageSurface : : create ( Cairo : : FORMAT_ARGB32 , a . get_width ( ) , a . get_height ( ) ) ;
}
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
2024-03-18 12:49:15 -04:00
/* call to ensure that entire canvas is marked in the invalidation region */
queue_draw ( ) ;
2021-10-03 21:47:18 -04:00
/* x, y in a are relative to the parent. When passing this down to the
root group , this origin is effectively 0 , 0
*/
Rect r ( 0. , 0. , a . get_width ( ) , a . get_height ( ) ) ;
2020-06-10 17:26:34 -04:00
_root . size_allocate ( r ) ;
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 ) {
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
2020-01-03 18:29:48 -05:00
Cairo : : RefPtr < Cairo : : Context > draw_context ;
if ( _use_image_surface ) {
if ( ! _canvas_image ) {
_canvas_image = Cairo : : ImageSurface : : create ( Cairo : : FORMAT_ARGB32 , get_width ( ) , get_height ( ) ) ;
}
draw_context = Cairo : : Context : : create ( _canvas_image ) ;
} else {
draw_context = get_window ( ) - > create_cairo_context ( ) ;
}
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 ( ) ;
2019-12-26 17:55:44 -05:00
/* (this comment applies to macOS, but is other platforms
* also benefit from using CPU - rendering on a image - surface
* with a final bitblt ) .
*
* group calls cairo_quartz_surface_create ( ) which
2017-03-17 11:54:16 -04:00
* 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
*/
2020-01-03 18:29:48 -05:00
if ( _use_intermediate_surface & & ! _use_image_surface ) {
2019-12-26 17:55:44 -05:00
draw_context - > push_group ( ) ;
}
2015-10-04 14:51:05 -04:00
2017-03-17 11:03:59 -04:00
/* render canvas */
2020-01-03 18:29:48 -05:00
if ( _single_exposure ) {
2015-10-05 10:17:49 -04:00
2024-03-18 12:47:31 -04:00
/* draw background color */
draw_context - > rectangle ( ev - > area . x , ev - > area . y , ev - > area . width , ev - > area . height ) ;
Gtkmm2ext : : set_source_rgba ( draw_context , _bg_color ) ;
draw_context - > fill ( ) ;
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 ) ;
2024-03-18 12:47:31 -04:00
2017-03-17 11:03:59 -04:00
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 )
2024-03-18 12:47:31 -04:00
/* draw background color */
draw_context - > rectangle ( rects [ n ] . x , rects [ n ] . y , rects [ n ] . x + rects [ n ] . width , rects [ n ] . y + rects [ n ] . height ) ;
Gtkmm2ext : : set_source_rgba ( draw_context , _bg_color ) ;
draw_context - > fill ( ) ;
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
}
2024-03-18 12:47:31 -04:00
2017-03-17 11:03:59 -04:00
g_free ( rects ) ;
}
2015-10-05 10:17:49 -04:00
2020-01-03 18:29:48 -05:00
if ( _use_image_surface ) {
_canvas_image - > flush ( ) ;
Cairo : : RefPtr < Cairo : : Context > window_context = get_window ( ) - > create_cairo_context ( ) ;
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 ( ) ;
} else if ( _use_intermediate_surface ) {
2019-12-26 17:55:44 -05:00
draw_context - > pop_group_to_source ( ) ;
draw_context - > paint ( ) ;
2015-05-01 22:54:59 -04:00
}
2013-04-04 00:32:52 -04:00
2020-01-03 18:29:48 -05:00
2017-03-17 11:54:16 -04:00
# ifdef CANVAS_PROFILE
2023-02-04 12:19:04 -05:00
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 ) ;
2017-03-17 11:54:16 -04:00
# 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 ) ) ;
}
2024-03-18 12:49:15 -04:00
void
GtkCanvas : : on_style_changed ( const Glib : : RefPtr < Gtk : : Style > & style )
{
EventBox : : on_style_changed ( style ) ;
/* call to ensure that entire canvas is marked in the invalidation region */
queue_draw ( ) ;
}
bool
GtkCanvas : : on_visibility_notify_event ( GdkEventVisibility * ev )
{
bool ret = EventBox : : on_visibility_notify_event ( ev ) ;
/* call to ensure that entire canvas is marked in the invalidation region */
queue_draw ( ) ;
return ret ;
}
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 ) {
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 )
{
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
2017-07-18 15:34:03 -04:00
}
void
GtkCanvas : : queue_draw ( )
{
# ifdef __APPLE__
if ( _nsglview ) {
Gtkmm2ext : : nsglview_queue_draw ( _nsglview , 0 , 0 , get_width ( ) , get_height ( ) ) ;
return ;
}
# endif
Gtk : : Widget : : queue_draw ( ) ;
}
void
GtkCanvas : : queue_draw_area ( int x , int y , int width , int height )
{
# ifdef __APPLE__
if ( _nsglview ) {
Gtkmm2ext : : nsglview_queue_draw ( _nsglview , x , y , width , height ) ;
return ;
}
# endif
Gtk : : Widget : : queue_draw_area ( x , y , width , height ) ;
2017-03-20 11:31:42 -04:00
}
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 ( ) ;
2022-04-04 14:50:31 -04:00
if ( item & & ! item - > tooltip ( ) . empty ( ) & & 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 ( ) ) ;
}
2018-11-19 11:31:09 -05:00
void
GtkCanvas : : queue_resize ( )
{
if ( ! resize_queued ) {
Glib : : signal_idle ( ) . connect ( sigc : : mem_fun ( * this , & GtkCanvas : : resize_handler ) ) ;
resize_queued = true ;
}
}
bool
GtkCanvas : : resize_handler ( )
{
resize_queued = false ;
_root . layout ( ) ;
return false ;
}
2021-12-10 20:17:58 -05:00
bool
2022-01-20 11:45:47 -05:00
GtkCanvas : : grab_can_translate ( ) const
2021-12-10 20:17:58 -05:00
{
if ( ! _grabbed_item ) {
2022-01-20 11:45:47 -05:00
/* weird, but correct! */
return true ;
2021-12-10 20:17:58 -05:00
}
2022-01-20 11:45:47 -05:00
return _grabbed_item - > scroll_translation ( ) ;
2021-12-10 20:17:58 -05:00
}
2024-03-18 12:50:36 -04:00
void
GtkCanvas : : render ( Cairo : : RefPtr < Cairo : : Context > const & ctx , cairo_rectangle_t * r )
{
ArdourCanvas : : Rect rect ( r - > x , r - > y , r - > width + r - > x , r - > height + r - > y ) ;
Canvas : : render ( rect , ctx ) ;
}
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 )
{
2021-08-01 23:18:35 -04:00
Distance width ;
Distance height ;
2013-04-11 20:19:22 -04:00
2021-08-01 23:18:35 -04:00
_canvas . root ( ) - > size_request ( width , height ) ;
_canvas . request_size ( Duple ( width , height ) ) ;
2020-06-10 21:40:17 -04:00
2022-03-09 15:13:27 -05:00
/* special case ArdourCanvas::COORD_MAX (really: no size constraint),
* also limit to cairo constraints determined by coordinates of things
* sent to pixman being in 16.16 format . */
if ( width > 32767 ) {
width = 0 ;
}
if ( height > 32767 ) {
height = 0 ;
}
req - > width = std : : max < int > ( 1 , width ) ;
req - > height = std : : max < int > ( 1 , height ) ;
2021-12-10 20:17:58 -05:00
2022-03-09 15:13:27 -05:00
}