2015-09-04 13:51:10 -05:00
// 4 september 2015
# include "area.h"
struct areaPrivate {
uiArea * a ;
uiAreaHandler * ah ;
GtkAdjustment * ha ;
GtkAdjustment * va ;
2015-09-06 15:20:37 -05:00
// TODO get rid of the need for these
2015-09-04 13:51:10 -05:00
int clientWidth ;
int clientHeight ;
// needed for GtkScrollable
GtkScrollablePolicy hpolicy , vpolicy ;
2015-09-11 11:38:37 -05:00
clickCounter cc ;
2015-09-04 13:51:10 -05:00
} ;
static void areaWidget_scrollable_init ( GtkScrollable * ) ;
2015-09-04 20:12:41 -05:00
G_DEFINE_TYPE_WITH_CODE ( areaWidget , areaWidget , GTK_TYPE_DRAWING_AREA ,
2015-09-04 13:51:10 -05:00
G_IMPLEMENT_INTERFACE ( GTK_TYPE_SCROLLABLE , areaWidget_scrollable_init ) )
2015-09-05 18:07:56 -05:00
/*
lower and upper are the bounds of the adjusment , in units
step_increment is the number of units scrolled when using the arrow keys or the buttons on an old - style scrollbar
page_incremenet is the number of page_size units scrolled with the Page Up / Down keys
according to baedert , the other condition is that upper > = page_size , and the effect is that the largest possible value is upper - page_size
2015-09-05 19:05:48 -05:00
unfortunately , everything in GTK + assumes 1 unit = 1 pixel
let ' s do the same : /
2015-09-05 18:07:56 -05:00
*/
2015-09-04 13:51:10 -05:00
static void updateScroll ( areaWidget * a )
{
struct areaPrivate * ap = a - > priv ;
2015-09-05 19:05:48 -05:00
uintmax_t count ;
2015-09-04 13:51:10 -05:00
// don't call if too early
if ( ap - > ha = = NULL | | ap - > va = = NULL )
return ;
2015-09-05 19:05:48 -05:00
count = ( * ( ap - > ah - > HScrollMax ) ) ( ap - > ah , ap - > a ) ;
2015-09-04 13:51:10 -05:00
gtk_adjustment_configure ( ap - > ha ,
gtk_adjustment_get_value ( ap - > ha ) ,
0 ,
count ,
1 ,
2015-09-05 19:05:48 -05:00
ap - > clientWidth ,
2015-09-05 14:51:55 -05:00
MIN ( count , ap - > clientWidth ) ) ;
2015-09-04 13:51:10 -05:00
2015-09-05 19:05:48 -05:00
count = ( * ( ap - > ah - > VScrollMax ) ) ( ap - > ah , ap - > a ) ;
2015-09-04 13:51:10 -05:00
gtk_adjustment_configure ( ap - > va ,
gtk_adjustment_get_value ( ap - > va ) ,
0 ,
count ,
1 ,
2015-09-05 19:05:48 -05:00
ap - > clientHeight ,
2015-09-05 14:51:55 -05:00
MIN ( count , ap - > clientHeight ) ) ;
2015-09-04 13:51:10 -05:00
// TODO notify adjustment changes?
2015-09-04 20:12:41 -05:00
// g_object_notify(G_OBJECT(a), "hadjustment");
// g_object_notify(G_OBJECT(a), "vadjustment");
2015-09-04 13:51:10 -05:00
}
static void areaWidget_init ( areaWidget * a )
{
a - > priv = G_TYPE_INSTANCE_GET_PRIVATE ( a , areaWidgetType , struct areaPrivate ) ;
// for events
gtk_widget_add_events ( GTK_WIDGET ( a ) ,
GDK_POINTER_MOTION_MASK |
GDK_BUTTON_MOTION_MASK |
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_KEY_PRESS_MASK |
GDK_KEY_RELEASE_MASK ) ;
// for scrolling
// TODO do we need GDK_TOUCH_MASK?
gtk_widget_add_events ( GTK_WIDGET ( a ) ,
GDK_SCROLL_MASK |
GDK_TOUCH_MASK |
GDK_SMOOTH_SCROLL_MASK ) ;
gtk_widget_set_can_focus ( GTK_WIDGET ( a ) , TRUE ) ;
2015-09-11 11:38:37 -05:00
clickCounterReset ( & ( a - > priv - > cc ) ) ;
2015-09-04 13:51:10 -05:00
}
static void areaWidget_dispose ( GObject * obj )
{
struct areaPrivate * ap = areaWidget ( obj ) - > priv ;
if ( ap - > ha ! = NULL ) {
g_object_unref ( ap - > ha ) ;
ap - > ha = NULL ;
}
if ( ap - > va ! = NULL ) {
g_object_unref ( ap - > va ) ;
ap - > va = NULL ;
}
G_OBJECT_CLASS ( areaWidget_parent_class ) - > dispose ( obj ) ;
}
static void areaWidget_finalize ( GObject * obj )
{
G_OBJECT_CLASS ( areaWidget_parent_class ) - > finalize ( obj ) ;
}
static void areaWidget_size_allocate ( GtkWidget * w , GtkAllocation * allocation )
{
struct areaPrivate * ap = areaWidget ( w ) - > priv ;
// GtkDrawingArea has a size_allocate() implementation; we need to call it
// this will call gtk_widget_set_allocation() for us
GTK_WIDGET_CLASS ( areaWidget_parent_class ) - > size_allocate ( w , allocation ) ;
ap - > clientWidth = allocation - > width ;
ap - > clientHeight = allocation - > height ;
updateScroll ( areaWidget ( w ) ) ;
2015-09-10 20:17:00 -05:00
if ( ( * ( ap - > ah - > RedrawOnResize ) ) ( ap - > ah , ap - > a ) )
gtk_widget_queue_resize ( w ) ;
2015-09-04 13:51:10 -05:00
}
2015-09-04 20:50:49 -05:00
static gboolean areaWidget_draw ( GtkWidget * w , cairo_t * cr )
{
areaWidget * a = areaWidget ( w ) ;
struct areaPrivate * ap = a - > priv ;
2015-09-06 15:20:37 -05:00
uiAreaDrawParams dp ;
double clipX0 , clipY0 , clipX1 , clipY1 ;
2015-09-06 19:02:01 -05:00
dp . Context = newContext ( cr ) ;
2015-09-06 15:20:37 -05:00
dp . ClientWidth = ap - > clientWidth ;
dp . ClientHeight = ap - > clientHeight ;
cairo_clip_extents ( cr , & clipX0 , & clipY0 , & clipX1 , & clipY1 ) ;
dp . ClipX = clipX0 ;
dp . ClipY = clipY0 ;
dp . ClipWidth = clipX1 - clipX0 ;
dp . ClipHeight = clipY1 - clipY0 ;
2015-09-06 15:55:43 -05:00
// on GTK+ you're not supposed to care about high-DPI scaling
// instead, pango handles scaled text rendering for us
// this doesn't handle non-text cases, but neither do other GTK+ programs, so :/
// wayland and mir GDK are hardcoded to 96dpi; X11 uses this as a fallback
// thanks to hergertme in irc.gimp.net/#gtk+ for clarifying things
dp . DPIX = 96 ;
dp . DPIY = 96 ;
2015-09-06 15:20:37 -05:00
dp . HScrollPos = gtk_adjustment_get_value ( ap - > ha ) ;
dp . VScrollPos = gtk_adjustment_get_value ( ap - > va ) ;
( * ( ap - > ah - > Draw ) ) ( ap - > ah , ap - > a , & dp ) ;
2015-09-04 20:50:49 -05:00
2015-09-06 19:02:01 -05:00
g_free ( dp . Context ) ;
2015-09-04 20:50:49 -05:00
return FALSE ;
}
2015-09-04 13:51:10 -05:00
// TODO preferred height/width
2015-09-11 11:38:37 -05:00
// TODO merge with toModifiers?
static guint translateModifiers ( guint state , GdkWindow * window )
{
GdkModifierType statetype ;
// GDK doesn't initialize the modifier flags fully; we have to explicitly tell it to (thanks to Daniel_S and daniels (two different people) in irc.gimp.net/#gtk+)
statetype = state ;
gdk_keymap_add_virtual_modifiers (
gdk_keymap_get_for_display ( gdk_window_get_display ( window ) ) ,
& statetype ) ;
return statetype ;
}
static uiModifiers toModifiers ( guint state )
{
uiModifiers m ;
m = 0 ;
2015-09-11 12:07:27 -05:00
if ( ( state & GDK_CONTROL_MASK ) ! = 0 )
2015-09-11 11:38:37 -05:00
m | = uiModifierCtrl ;
if ( ( state & GDK_META_MASK ) ! = 0 )
m | = uiModifierAlt ;
if ( ( state & GDK_MOD1_MASK ) ! = 0 ) // GTK+ itself requires this to be Alt (just read through gtkaccelgroup.c)
m | = uiModifierAlt ;
if ( ( state & GDK_SHIFT_MASK ) ! = 0 )
m | = uiModifierShift ;
if ( ( state & GDK_SUPER_MASK ) ! = 0 )
m | = uiModifierSuper ;
return m ;
}
static void finishMouseEvent ( struct areaPrivate * ap , uiAreaMouseEvent * me , guint mb , gdouble x , gdouble y , guint state , GdkWindow * window )
{
// on GTK+, mouse buttons 4-7 are for scrolling; if we got here, that's a mistake
if ( mb > = 4 & & mb < = 7 )
return ;
// if the button ID >= 8, continue counting from 4, as in the MouseEvent spec
if ( me - > Down > = 8 )
me - > Down - = 4 ;
if ( me - > Up > = 8 )
me - > Up - = 4 ;
state = translateModifiers ( state , window ) ;
me - > Modifiers = toModifiers ( state ) ;
// the mb != # checks exclude the Up/Down button from Held
me - > Held1To64 = 0 ;
if ( mb ! = 1 & & ( state & GDK_BUTTON1_MASK ) ! = 0 )
me - > Held1To64 | = 1 < < 0 ;
if ( mb ! = 2 & & ( state & GDK_BUTTON2_MASK ) ! = 0 )
me - > Held1To64 | = 1 < < 1 ;
if ( mb ! = 3 & & ( state & GDK_BUTTON3_MASK ) ! = 0 )
me - > Held1To64 | = 1 < < 2 ;
// don't check GDK_BUTTON4_MASK or GDK_BUTTON5_MASK because those are for the scrolling buttons mentioned above
// GDK expressly does not support any more buttons in the GdkModifierType; see https://git.gnome.org/browse/gtk+/tree/gdk/x11/gdkdevice-xi2.c#n763 (thanks mclasen in irc.gimp.net/#gtk+)
me - > X = x ;
me - > Y = y ;
// do not cap to the area bounds in the case of captures
me - > HScrollPos = gtk_adjustment_get_value ( ap - > ha ) ;
me - > VScrollPos = gtk_adjustment_get_value ( ap - > va ) ;
( * ( ap - > ah - > MouseEvent ) ) ( ap - > ah , ap - > a , me ) ;
}
static gboolean areaWidget_button_press_event ( GtkWidget * w , GdkEventButton * e )
{
struct areaPrivate * ap = areaWidget ( w ) - > priv ;
gint maxTime , maxDistance ;
GtkSettings * settings ;
uiAreaMouseEvent me ;
// clicking doesn't automatically transfer keyboard focus; we must do so manually (thanks tristan in irc.gimp.net/#gtk+)
gtk_widget_grab_focus ( w ) ;
// we handle multiple clicks ourselves here, in the same way as we do on Windows
if ( e - > type ! = GDK_BUTTON_PRESS )
// ignore GDK's generated double-clicks and beyond
return GDK_EVENT_PROPAGATE ;
settings = gtk_widget_get_settings ( w ) ;
g_object_get ( settings ,
" gtk-double-click-time " , & maxTime ,
" gtk-double-click-distance " , & maxDistance ,
NULL ) ;
// TODO unref settings?
me . Count = clickCounterClick ( & ( ap - > cc ) , me . Down ,
e - > x , e - > y ,
e - > time , maxTime ,
maxDistance , maxDistance ) ;
me . Down = e - > button ;
me . Up = 0 ;
finishMouseEvent ( ap , & me , e - > button , e - > x , e - > y , e - > state , e - > window ) ;
return GDK_EVENT_PROPAGATE ;
}
static gboolean areaWidget_button_release_event ( GtkWidget * w , GdkEventButton * e )
{
struct areaPrivate * ap = areaWidget ( w ) - > priv ;
uiAreaMouseEvent me ;
me . Down = 0 ;
me . Up = e - > button ;
me . Count = 0 ;
finishMouseEvent ( ap , & me , e - > button , e - > x , e - > y , e - > state , e - > window ) ;
return GDK_EVENT_PROPAGATE ;
}
static gboolean areaWidget_motion_notify_event ( GtkWidget * w , GdkEventMotion * e )
{
struct areaPrivate * ap = areaWidget ( w ) - > priv ;
uiAreaMouseEvent me ;
me . Down = 0 ;
me . Up = 0 ;
me . Count = 0 ;
finishMouseEvent ( ap , & me , 0 , e - > x , e - > y , e - > state , e - > window ) ;
return GDK_EVENT_PROPAGATE ;
}
// we want switching away from the control to reset the double-click counter, like with WM_ACTIVATE on Windows
// according to tristan in irc.gimp.net/#gtk+, doing this on enter-notify-event and leave-notify-event is correct (and it seems to be true in my own tests; plus the events DO get sent when switching programs with the keyboard (just pointing that out))
// differentiating between enter-notify-event and leave-notify-event is unimportant
2015-09-11 12:07:27 -05:00
gboolean areaWidget_enterleave_notify_event ( GtkWidget * w , GdkEventCrossing * e )
2015-09-11 11:38:37 -05:00
{
struct areaPrivate * ap = areaWidget ( w ) - > priv ;
clickCounterReset ( & ( ap - > cc ) ) ;
return GDK_EVENT_PROPAGATE ;
}
// TODO key events
2015-09-04 13:51:10 -05:00
enum {
2015-09-04 20:12:41 -05:00
// normal properties must come before override properties
// thanks gregier in irc.gimp.net/#gtk+
pAreaHandler = 1 ,
pHAdjustment ,
2015-09-04 13:51:10 -05:00
pVAdjustment ,
pHScrollPolicy ,
pVScrollPolicy ,
nProps ,
} ;
2015-09-04 20:12:41 -05:00
static GParamSpec * pspecAreaHandler ;
2015-09-04 13:51:10 -05:00
static void onValueChanged ( GtkAdjustment * a , gpointer data )
{
2015-09-06 15:20:37 -05:00
// there's no way to scroll the contents of a widget, so we have to redraw the entire thing
2015-09-04 13:51:10 -05:00
gtk_widget_queue_draw ( GTK_WIDGET ( data ) ) ;
}
static void replaceAdjustment ( areaWidget * a , GtkAdjustment * * adj , const GValue * value )
{
if ( * adj ! = NULL ) {
2015-09-04 16:56:13 -05:00
g_signal_handlers_disconnect_by_func ( * adj , G_CALLBACK ( onValueChanged ) , a ) ;
2015-09-04 13:51:10 -05:00
g_object_unref ( * adj ) ;
}
* adj = GTK_ADJUSTMENT ( g_value_get_object ( value ) ) ;
if ( * adj ! = NULL )
g_object_ref_sink ( * adj ) ;
else
* adj = gtk_adjustment_new ( 0 , 0 , 0 , 0 , 0 , 0 ) ;
g_signal_connect ( * adj , " value-changed " , G_CALLBACK ( onValueChanged ) , a ) ;
updateScroll ( a ) ;
}
static void areaWidget_set_property ( GObject * obj , guint prop , const GValue * value , GParamSpec * pspec )
{
areaWidget * a = areaWidget ( obj ) ;
struct areaPrivate * ap = a - > priv ;
switch ( prop ) {
case pHAdjustment :
replaceAdjustment ( a , & ( ap - > ha ) , value ) ;
return ;
case pVAdjustment :
replaceAdjustment ( a , & ( ap - > va ) , value ) ;
return ;
case pHScrollPolicy :
ap - > hpolicy = g_value_get_enum ( value ) ;
return ;
case pVScrollPolicy :
ap - > vpolicy = g_value_get_enum ( value ) ;
return ;
2015-09-04 20:12:41 -05:00
case pAreaHandler :
ap - > ah = ( uiAreaHandler * ) g_value_get_pointer ( value ) ;
return ;
2015-09-04 13:51:10 -05:00
}
G_OBJECT_WARN_INVALID_PROPERTY_ID ( obj , prop , pspec ) ;
}
static void areaWidget_get_property ( GObject * obj , guint prop , GValue * value , GParamSpec * pspec )
{
areaWidget * a = areaWidget ( obj ) ;
struct areaPrivate * ap = a - > priv ;
switch ( prop ) {
case pHAdjustment :
g_value_set_object ( value , ap - > ha ) ;
return ;
case pVAdjustment :
g_value_set_object ( value , ap - > va ) ;
return ;
case pHScrollPolicy :
g_value_set_enum ( value , ap - > hpolicy ) ;
return ;
case pVScrollPolicy :
g_value_set_enum ( value , ap - > vpolicy ) ;
return ;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID ( obj , prop , pspec ) ;
}
static void areaWidget_class_init ( areaWidgetClass * class )
{
G_OBJECT_CLASS ( class ) - > dispose = areaWidget_dispose ;
G_OBJECT_CLASS ( class ) - > finalize = areaWidget_finalize ;
G_OBJECT_CLASS ( class ) - > set_property = areaWidget_set_property ;
G_OBJECT_CLASS ( class ) - > get_property = areaWidget_get_property ;
GTK_WIDGET_CLASS ( class ) - > size_allocate = areaWidget_size_allocate ;
2015-09-04 20:50:49 -05:00
GTK_WIDGET_CLASS ( class ) - > draw = areaWidget_draw ;
2015-09-04 13:51:10 -05:00
// GTK_WIDGET_CLASS(class)->get_preferred_height = areaWidget_get_preferred_height;
// GTK_WIDGET_CLASS(class)->get_preferred_width = areaWidget_get_preferred_width;
2015-09-11 11:38:37 -05:00
GTK_WIDGET_CLASS ( class ) - > button_press_event = areaWidget_button_press_event ;
GTK_WIDGET_CLASS ( class ) - > button_release_event = areaWidget_button_release_event ;
GTK_WIDGET_CLASS ( class ) - > motion_notify_event = areaWidget_motion_notify_event ;
GTK_WIDGET_CLASS ( class ) - > enter_notify_event = areaWidget_enterleave_notify_event ;
GTK_WIDGET_CLASS ( class ) - > leave_notify_event = areaWidget_enterleave_notify_event ;
2015-09-04 13:51:10 -05:00
// GTK_WIDGET_CLASS(class)->key_press_event = areaWidget_key_press_event;
2015-09-11 11:38:37 -05:00
// GTK_WIDGET_CLASS(class)->key_release_event = areaWidget_key_release_event;
2015-09-04 13:51:10 -05:00
2015-09-04 20:12:41 -05:00
g_type_class_add_private ( G_OBJECT_CLASS ( class ) , sizeof ( struct areaPrivate ) ) ;
pspecAreaHandler = g_param_spec_pointer ( " area-handler " ,
" area-handler " ,
" Area handler. " ,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS ) ;
g_object_class_install_property ( G_OBJECT_CLASS ( class ) , pAreaHandler , pspecAreaHandler ) ;
2015-09-04 13:51:10 -05:00
// this is the actual interface implementation
g_object_class_override_property ( G_OBJECT_CLASS ( class ) , pHAdjustment , " hadjustment " ) ;
g_object_class_override_property ( G_OBJECT_CLASS ( class ) , pVAdjustment , " vadjustment " ) ;
g_object_class_override_property ( G_OBJECT_CLASS ( class ) , pHScrollPolicy , " hscroll-policy " ) ;
g_object_class_override_property ( G_OBJECT_CLASS ( class ) , pVScrollPolicy , " vscroll-policy " ) ;
}
static void areaWidget_scrollable_init ( GtkScrollable * iface )
{
// no need to do anything; the interface only has properties
}
2015-09-04 20:12:41 -05:00
GtkWidget * newArea ( uiAreaHandler * ah )
{
return GTK_WIDGET ( g_object_new ( areaWidgetType ,
" area-handler " , ah ,
NULL ) ) ;
}
2015-09-04 20:27:32 -05:00
void areaUpdateScroll ( GtkWidget * area )
{
updateScroll ( areaWidget ( area ) ) ;
}