Compare commits

...

8 Commits

Author SHA1 Message Date
Carlos Garnacho
d5dda9a2f0 Add gdk_event_[gs]et_source_device().
This function may be used to know the hardware device that triggered
an event, it could resort to the master device in the few cases there's
not a direct hardware device to relate to the event (i.e.: crossing events
due to grabs)
2010-07-02 13:43:47 +02:00
Carlos Garnacho
ab8242c522 Add test applications for multidevice events. 2010-07-02 13:43:46 +02:00
Carlos Garnacho
80bf8c3999 Add GtkDeviceGroup and ::multidevice-event
The former allows to create several per-widget device groups, so the latter
is emitted on these containing motion events for all devices in the reported
group.
2010-07-02 13:43:46 +02:00
Carlos Garnacho
4212943079 Turn on multidevice support by default.
gdk_enable_multidevice() has been replaced with gdk_disable_multidevice(), although
there would be usually little reasons to call that function, unless the application
is doing X calls itself that count on old fashioned core devices.
2010-07-02 13:43:46 +02:00
Carlos Garnacho
0ac8f1686f GdkWindow: Make gdk_window_set_support_multidevice() enable touchpoints. 2010-07-02 13:43:46 +02:00
Carlos Garnacho
a771703f0f Add gdk_window_[gs]et_source_events()
This function will enable events for all devices of a given GdkInputSource,
both current and future devices.
2010-07-02 13:43:46 +02:00
Carlos Garnacho
e3a3ba57f0 Make GdkDeviceManagerXI2 recognize touchscreen devices. 2010-07-02 13:43:45 +02:00
Carlos Garnacho
712672f1d1 Add GDK_SOURCE_TOUCH.
This kind of device would represent either a single-touch touchscreen, or
individual touchpoints in a multitouch touchscreen.
2010-07-02 13:43:45 +02:00
33 changed files with 2419 additions and 52 deletions

View File

@@ -1693,6 +1693,7 @@ demos/Makefile
demos/gtk-demo/Makefile
demos/gtk-demo/geninclude.pl
tests/Makefile
tests/multidevice/Makefile
docs/Makefile
docs/reference/Makefile
docs/reference/gdk/Makefile

View File

@@ -706,6 +706,8 @@ gdk_window_get_device_cursor
gdk_window_set_device_cursor
gdk_window_get_device_events
gdk_window_set_device_events
gdk_window_get_source_events
gdk_window_set_source_events
<SUBSECTION>
GdkPointerHooks

View File

@@ -788,26 +788,26 @@ gdk_set_program_class (const char *program_class)
}
/**
* gdk_enable_multidevice:
* gdk_disable_multidevice:
*
* Enables multidevice support in GDK. This call must happen prior
* Disables multidevice support in GDK. This call must happen prior
* to gdk_display_open(), gtk_init(), gtk_init_with_args() or
* gtk_init_check() in order to take effect.
*
* Note that individual #GdkWindow<!-- -->s still need to explicitly
* enable multidevice awareness through gdk_window_set_support_multidevice().
*
* This function must be called before initializing GDK.
* Most common GTK+ applications won't ever need to call this. Only
* applications that do mixed GDK/Xlib calls could want to disable
* multidevice support if such Xlib code deals with input devices in
* any way and doesn't observe the presence of XInput 2.
*
* Since: 3.0
**/
void
gdk_enable_multidevice (void)
gdk_disable_multidevice (void)
{
if (gdk_initialized)
return;
_gdk_enable_multidevice = TRUE;
_gdk_disable_multidevice = TRUE;
}
#define __GDK_C__

View File

@@ -66,7 +66,7 @@ G_BEGIN_DECLS
/* Initialization, exit and events
*/
#define GDK_PRIORITY_EVENTS (G_PRIORITY_DEFAULT)
void gdk_enable_multidevice (void);
void gdk_disable_multidevice (void);
void gdk_parse_args (gint *argc,
gchar ***argv);
void gdk_init (gint *argc,

View File

@@ -177,7 +177,7 @@ gdk_get_display_arg_name
gdk_get_program_class
gdk_init
gdk_init_check
gdk_enable_multidevice
gdk_disable_multidevice
gdk_pre_parse_libgtk_only
gdk_parse_args
gdk_set_program_class
@@ -738,6 +738,8 @@ gdk_window_get_device_events
gdk_window_set_device_cursor
gdk_window_get_device_cursor
gdk_window_get_device_position
gdk_window_set_source_events
gdk_window_get_source_events
#endif
#endif

View File

@@ -66,6 +66,7 @@ typedef enum
* of a stylus on a graphics tablet.
* @GDK_SOURCE_CURSOR: the device is a graphics tablet "puck" or similar device.
* @GDK_SOURCE_KEYBOARD: the device is a keyboard.
* @GDK_SOURCE_TOUCH: the device is a (multi)touch capable device.
*
* An enumeration describing the type of an input device in general terms.
*/
@@ -75,7 +76,8 @@ typedef enum
GDK_SOURCE_PEN,
GDK_SOURCE_ERASER,
GDK_SOURCE_CURSOR,
GDK_SOURCE_KEYBOARD
GDK_SOURCE_KEYBOARD,
GDK_SOURCE_TOUCH
} GdkInputSource;
/**

View File

@@ -34,12 +34,13 @@
* additional features such as sub-pixel positioning information and additional
* device-dependent information.
* @Title: GdkDeviceManager
* @See_also: #GdkDevice, #GdkEvent, gdk_enable_multidevice()
* @See_also: #GdkDevice, #GdkEvent, gdk_disable_multidevice()
*
* By default, GDK supports the traditional single keyboard/pointer input scheme (Plus additional
* special input devices such as tablets. In short, backwards compatible with 2.X). Since version 3.0,
* if gdk_enable_multidevice() is called before gdk_display_open() and the platform supports it, GDK
* will be aware of multiple keyboard/pointer pairs interacting simultaneously with the user interface.
* By default, and if the platform supports it, GDK is aware of multiple keyboard/pointer pairs
* and multitouch devices, this behavior can be changed by calling gdk_disable_multidevice()
* before gdk_display_open(), although there would be rarely a reason to do that. For a widget
* or window to be dealt as multipointer aware, gdk_window_set_support_multidevice() or
* gtk_widget_set_support_multidevice() must have been called on it.
*
* Conceptually, in multidevice mode there are 2 device types, virtual devices (or master devices)
* are represented by the pointer cursors and keyboard foci that are seen on the screen. physical

View File

@@ -1250,6 +1250,7 @@ _gdk_display_add_device_grab (GdkDisplay *display,
static void
synthesize_crossing_events (GdkDisplay *display,
GdkDevice *device,
GdkDevice *source_device,
GdkWindow *src_window,
GdkWindow *dest_window,
GdkCrossingMode crossing_mode,
@@ -1285,7 +1286,7 @@ synthesize_crossing_events (GdkDisplay *display,
_gdk_synthesize_crossing_events (display,
src_window,
dest_window,
device,
device, source_device,
crossing_mode,
x, y, state,
time,
@@ -1299,7 +1300,7 @@ synthesize_crossing_events (GdkDisplay *display,
_gdk_synthesize_crossing_events (display,
src_window,
NULL,
device,
device, source_device,
crossing_mode,
x, y, state,
time,
@@ -1314,7 +1315,7 @@ synthesize_crossing_events (GdkDisplay *display,
_gdk_synthesize_crossing_events (display,
src_window,
NULL,
device,
device, source_device,
crossing_mode,
x, y, state,
time,
@@ -1325,7 +1326,7 @@ synthesize_crossing_events (GdkDisplay *display,
_gdk_synthesize_crossing_events (display,
NULL,
dest_window,
device,
device, source_device,
crossing_mode,
x, y, state,
time,
@@ -1362,6 +1363,7 @@ get_current_toplevel (GdkDisplay *display,
static void
switch_to_pointer_grab (GdkDisplay *display,
GdkDevice *device,
GdkDevice *source_device,
GdkDeviceGrabInfo *grab,
GdkDeviceGrabInfo *last_grab,
guint32 time,
@@ -1397,7 +1399,7 @@ switch_to_pointer_grab (GdkDisplay *display,
src_window = info->window_under_pointer;
if (src_window != grab->window)
synthesize_crossing_events (display, device,
synthesize_crossing_events (display, device, source_device,
src_window, grab->window,
GDK_CROSSING_GRAB, time, serial);
@@ -1448,7 +1450,7 @@ switch_to_pointer_grab (GdkDisplay *display,
}
if (pointer_window != last_grab->window)
synthesize_crossing_events (display, device,
synthesize_crossing_events (display, device, source_device,
last_grab->window, pointer_window,
GDK_CROSSING_UNGRAB, time, serial);
@@ -1463,6 +1465,7 @@ switch_to_pointer_grab (GdkDisplay *display,
void
_gdk_display_device_grab_update (GdkDisplay *display,
GdkDevice *device,
GdkDevice *source_device,
gulong current_serial)
{
GdkDeviceGrabInfo *current_grab, *next_grab;
@@ -1487,7 +1490,7 @@ _gdk_display_device_grab_update (GdkDisplay *display,
if (!current_grab->activated)
{
if (device->source != GDK_SOURCE_KEYBOARD)
switch_to_pointer_grab (display, device, current_grab, NULL, time, current_serial);
switch_to_pointer_grab (display, device, source_device, current_grab, NULL, time, current_serial);
}
break;
@@ -1515,7 +1518,7 @@ _gdk_display_device_grab_update (GdkDisplay *display,
g_hash_table_insert (display->device_grabs, device, grabs);
if (device->source != GDK_SOURCE_KEYBOARD)
switch_to_pointer_grab (display, device,
switch_to_pointer_grab (display, device, source_device,
next_grab, current_grab,
time, current_serial);

View File

@@ -1053,6 +1053,66 @@ gdk_event_get_device (const GdkEvent *event)
}
}
/**
* gdk_event_set_source_device:
* @event: a #GdkEvent
* @device: a #GdkDevice
*
* Sets the slave device for @event to @device. The event
* must have been allocated by GTK+, for instance, by
* gdk_event_copy().
*
* Since: 3.0
**/
void
gdk_event_set_source_device (GdkEvent *event,
GdkDevice *device)
{
GdkEventPrivate *private;
g_return_if_fail (gdk_event_is_allocated (event));
g_return_if_fail (GDK_IS_DEVICE (device));
private = (GdkEventPrivate *) event;
private->source_device = device;
}
/**
* gdk_event_get_source_device:
* @event: a #GdkEvent
*
* This function returns the hardware (slave) #GdkDevice that has triggered the event,
* falling back to the virtual (master) device (as in gdk_event_get_device()) if the
* event wasn't caused by interaction with a hardware device. This may happen for
* example in synthesized crossing events after a #GdkWindow updates its geometry or
* a grab is acquired/released.
*
* If the event does not contain device field, this function will return %NULL.
*
* Returns: a #GdkDevice, or %NULL.
*
* Since: 3.0
**/
GdkDevice *
gdk_event_get_source_device (const GdkEvent *event)
{
GdkEventPrivate *private;
g_return_val_if_fail (event != NULL, NULL);
if (!gdk_event_is_allocated (event))
return NULL;
private = (GdkEventPrivate *) event;
if (private->source_device)
return private->source_device;
/* Fallback to event device */
return gdk_event_get_device (event);
}
/**
* gdk_event_request_motions:
* @event: a valid #GdkEvent

View File

@@ -543,6 +543,9 @@ gboolean gdk_event_get_axis (const GdkEvent *event,
void gdk_event_set_device (GdkEvent *event,
GdkDevice *device);
GdkDevice* gdk_event_get_device (const GdkEvent *event);
void gdk_event_set_source_device (GdkEvent *event,
GdkDevice *device);
GdkDevice* gdk_event_get_source_device (const GdkEvent *event);
void gdk_event_request_motions (const GdkEventMotion *event);
gboolean gdk_events_get_distance (GdkEvent *event1,

View File

@@ -40,7 +40,7 @@ gchar *_gdk_display_name = NULL;
gint _gdk_screen_number = -1;
gchar *_gdk_display_arg_name = NULL;
gboolean _gdk_native_windows = FALSE;
gboolean _gdk_enable_multidevice = FALSE;
gboolean _gdk_disable_multidevice = FALSE;
GSList *_gdk_displays = NULL;

View File

@@ -158,6 +158,7 @@ struct _GdkEventPrivate
GdkScreen *screen;
gpointer windowing_data;
GdkDevice *device;
GdkDevice *source_device;
};
/* Tracks information about the pointer grab on this display */
@@ -273,6 +274,9 @@ struct _GdkWindowObject
GList *devices_inside;
GHashTable *device_events;
GHashTable *source_event_masks;
gulong device_added_handler_id;
};
#define GDK_WINDOW_TYPE(d) (((GdkWindowObject*)(GDK_WINDOW (d)))->window_type)
@@ -286,7 +290,7 @@ extern GSList *_gdk_displays;
extern gchar *_gdk_display_name;
extern gint _gdk_screen_number;
extern gchar *_gdk_display_arg_name;
extern gboolean _gdk_enable_multidevice;
extern gboolean _gdk_disable_multidevice;
void _gdk_events_queue (GdkDisplay *display);
GdkEvent* _gdk_event_unqueue (GdkDisplay *display);
@@ -594,6 +598,7 @@ void _gdk_windowing_launch_failed (GAppLaunchContext *context,
void _gdk_display_device_grab_update (GdkDisplay *display,
GdkDevice *device,
GdkDevice *source_device,
gulong current_serial);
GdkDeviceGrabInfo *_gdk_display_get_last_device_grab (GdkDisplay *display,
GdkDevice *device);
@@ -659,6 +664,7 @@ void _gdk_synthesize_crossing_events (GdkDisplay *display,
GdkWindow *src,
GdkWindow *dest,
GdkDevice *device,
GdkDevice *source_device,
GdkCrossingMode mode,
gint toplevel_x,
gint toplevel_y,

View File

@@ -702,6 +702,9 @@ gdk_window_finalize (GObject *object)
if (obj->device_events)
g_hash_table_destroy (obj->device_events);
if (obj->source_event_masks)
g_hash_table_destroy (obj->source_event_masks);
if (obj->devices_inside)
g_list_free (obj->devices_inside);
@@ -10077,6 +10080,7 @@ gdk_window_set_support_multidevice (GdkWindow *window,
gboolean support_multidevice)
{
GdkWindowObject *private = (GdkWindowObject *) window;
GdkEventMask event_mask = 0;
g_return_if_fail (GDK_IS_WINDOW (window));
@@ -10088,6 +10092,12 @@ gdk_window_set_support_multidevice (GdkWindow *window,
private->support_multidevice = support_multidevice;
if (support_multidevice)
event_mask = gdk_window_get_events (window);
/* Enable events for slave touchscreen devices */
gdk_window_set_source_events (window, GDK_SOURCE_TOUCH, event_mask);
/* FIXME: What to do if called when some pointers are inside the window ? */
}
@@ -10345,6 +10355,7 @@ send_crossing_event (GdkDisplay *display,
GdkNotifyType notify_type,
GdkWindow *subwindow,
GdkDevice *device,
GdkDevice *source_device,
gint toplevel_x,
gint toplevel_y,
GdkModifierType mask,
@@ -10404,6 +10415,10 @@ send_crossing_event (GdkDisplay *display,
{
event = _gdk_make_event ((GdkWindow *)window, type, event_in_queue, TRUE);
gdk_event_set_device (event, device);
if (source_device)
gdk_event_set_source_device (event, source_device);
event->crossing.time = time_;
event->crossing.subwindow = subwindow;
if (subwindow)
@@ -10431,6 +10446,7 @@ _gdk_synthesize_crossing_events (GdkDisplay *display,
GdkWindow *src,
GdkWindow *dest,
GdkDevice *device,
GdkDevice *source_device,
GdkCrossingMode mode,
gint toplevel_x,
gint toplevel_y,
@@ -10486,7 +10502,7 @@ _gdk_synthesize_crossing_events (GdkDisplay *display,
a, GDK_LEAVE_NOTIFY,
mode,
notify_type,
NULL, device,
NULL, device, source_device,
toplevel_x, toplevel_y,
mask, time_,
event_in_queue,
@@ -10508,7 +10524,7 @@ _gdk_synthesize_crossing_events (GdkDisplay *display,
mode,
notify_type,
(GdkWindow *)last,
device,
device, source_device,
toplevel_x, toplevel_y,
mask, time_,
event_in_queue,
@@ -10555,7 +10571,7 @@ _gdk_synthesize_crossing_events (GdkDisplay *display,
mode,
notify_type,
(GdkWindow *)next,
device,
device, source_device,
toplevel_x, toplevel_y,
mask, time_,
event_in_queue,
@@ -10577,7 +10593,7 @@ _gdk_synthesize_crossing_events (GdkDisplay *display,
mode,
notify_type,
NULL,
device,
device, source_device,
toplevel_x, toplevel_y,
mask, time_,
event_in_queue,
@@ -10911,6 +10927,123 @@ gdk_window_geometry_changed (GdkWindow *window)
_gdk_synthesize_crossing_events_for_geometry_change (window);
}
static void
source_events_device_added (GdkDeviceManager *device_manager,
GdkDevice *device,
gpointer user_data)
{
GdkWindow *window;
GdkWindowObject *private;
GdkEventMask event_mask;
GdkInputSource source;
if (gdk_device_get_device_type (device) != GDK_DEVICE_TYPE_FLOATING)
return;
window = user_data;
private = (GdkWindowObject *) window;
source = gdk_device_get_source (device);
event_mask = GPOINTER_TO_UINT (g_hash_table_lookup (private->source_event_masks,
GUINT_TO_POINTER (source)));
if (event_mask)
{
gdk_window_set_device_events (window, device, event_mask);
gdk_device_set_mode (device, GDK_MODE_SCREEN);
}
}
/**
* gdk_window_set_source_events:
* @window: a #GdkWindow
* @source: a #GdkInputSource to define the source class.
* @event_mask: event mask for @window
*
* Sets the event mask for any floating device (i.e. not attached to any
* visible pointer) that has the source defined as @source. This event
* mask will be applied both to currently existing and newly added devices
* after this call.
**/
void
gdk_window_set_source_events (GdkWindow *window,
GdkInputSource source,
GdkEventMask event_mask)
{
GdkDeviceManager *device_manager;
GdkWindowObject *private;
GdkDisplay *display;
GList *devices, *d;
guint size;
g_return_if_fail (GDK_IS_WINDOW (window));
display = gdk_drawable_get_display (GDK_DRAWABLE (window));
device_manager = gdk_display_get_device_manager (display);
private = (GdkWindowObject *) window;
devices = gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_FLOATING);
/* Set event mask for existing devices */
for (d = devices; d; d = d->next)
{
GdkDevice *device = d->data;
if (source == gdk_device_get_source (device))
{
gdk_window_set_device_events (window, device, event_mask);
gdk_device_set_mode (device, GDK_MODE_SCREEN);
}
}
/* Update accounting */
if (G_UNLIKELY (!private->source_event_masks))
private->source_event_masks = g_hash_table_new (NULL, NULL);
if (event_mask)
g_hash_table_insert (private->source_event_masks,
GUINT_TO_POINTER (source),
GUINT_TO_POINTER (event_mask));
else
g_hash_table_remove (private->source_event_masks,
GUINT_TO_POINTER (source));
size = g_hash_table_size (private->source_event_masks);
/* Update handler if needed */
if (!private->device_added_handler_id && size > 0)
{
private->device_added_handler_id =
g_signal_connect (device_manager, "device-added",
G_CALLBACK (source_events_device_added), window);
}
else if (private->device_added_handler_id && size == 0)
g_signal_handler_disconnect (device_manager, private->device_added_handler_id);
}
/**
* gdk_window_get_source_events:
* @window: a #GdkWindow
* @source: a #GdkInputSource to define the source class.
*
* Returns the event mask for @window corresponding to the device class specified
* by @source.
*
* Returns: source event mask for @window
**/
GdkEventMask
gdk_window_get_source_events (GdkWindow *window,
GdkInputSource source)
{
GdkWindowObject *private;
g_return_val_if_fail (GDK_IS_WINDOW (window), 0);
private = (GdkWindowObject *) window;
return GPOINTER_TO_UINT (g_hash_table_lookup (private->source_event_masks,
GUINT_TO_POINTER (source)));
}
static gboolean
do_synthesize_crossing_event (gpointer data)
{
@@ -10952,7 +11085,7 @@ do_synthesize_crossing_event (gpointer data)
_gdk_synthesize_crossing_events (display,
pointer_info->window_under_pointer,
new_window_under_pointer,
device,
device, NULL,
GDK_CROSSING_NORMAL,
pointer_info->toplevel_x,
pointer_info->toplevel_y,
@@ -11072,7 +11205,7 @@ proxy_pointer_event (GdkDisplay *display,
GdkWindow *toplevel_window, *event_window;
GdkWindow *pointer_window;
GdkPointerWindowInfo *pointer_info;
GdkDevice *device;
GdkDevice *device, *source_device;
GdkEvent *event;
guint state;
gdouble toplevel_x, toplevel_y;
@@ -11084,6 +11217,7 @@ proxy_pointer_event (GdkDisplay *display,
gdk_event_get_state (source_event, &state);
time_ = gdk_event_get_time (source_event);
device = gdk_event_get_device (source_event);
source_device = gdk_event_get_source_device (source_event);
pointer_info = _gdk_display_get_pointer_info (display, device);
toplevel_window = convert_native_coords_to_toplevel (event_window,
toplevel_x, toplevel_y,
@@ -11117,7 +11251,7 @@ proxy_pointer_event (GdkDisplay *display,
_gdk_synthesize_crossing_events (display,
pointer_info->window_under_pointer,
event_window,
device,
device, source_device,
source_event->crossing.mode,
toplevel_x, toplevel_y,
state, time_,
@@ -11133,7 +11267,7 @@ proxy_pointer_event (GdkDisplay *display,
source_event->crossing.mode,
source_event->crossing.detail,
NULL,
device,
device, source_device,
toplevel_x, toplevel_y,
state, time_,
source_event,
@@ -11163,7 +11297,7 @@ proxy_pointer_event (GdkDisplay *display,
source_event->crossing.mode,
source_event->crossing.detail,
NULL,
device,
device, source_device,
toplevel_x, toplevel_y,
state, time_,
source_event,
@@ -11173,7 +11307,7 @@ proxy_pointer_event (GdkDisplay *display,
_gdk_synthesize_crossing_events (display,
event_window,
pointer_window,
device,
device, source_device,
source_event->crossing.mode,
toplevel_x, toplevel_y,
state, time_,
@@ -11192,7 +11326,7 @@ proxy_pointer_event (GdkDisplay *display,
_gdk_synthesize_crossing_events (display,
pointer_info->window_under_pointer,
pointer_window,
device,
device, source_device,
GDK_CROSSING_NORMAL,
toplevel_x, toplevel_y,
state, time_,
@@ -11282,7 +11416,7 @@ proxy_button_event (GdkEvent *source_event,
gdouble toplevel_x, toplevel_y;
GdkDisplay *display;
GdkWindowObject *w;
GdkDevice *device;
GdkDevice *device, *source_device;
type = source_event->any.type;
event_window = source_event->any.window;
@@ -11290,6 +11424,7 @@ proxy_button_event (GdkEvent *source_event,
gdk_event_get_state (source_event, &state);
time_ = gdk_event_get_time (source_event);
device = gdk_event_get_device (source_event);
source_device = gdk_event_get_source_device (source_event);
display = gdk_drawable_get_display (source_event->any.window);
toplevel_window = convert_native_coords_to_toplevel (event_window,
toplevel_x, toplevel_y,
@@ -11326,7 +11461,7 @@ proxy_button_event (GdkEvent *source_event,
serial,
time_,
TRUE);
_gdk_display_device_grab_update (display, device, serial);
_gdk_display_device_grab_update (display, device, source_device, serial);
}
pointer_window = get_pointer_window (display, toplevel_window, device,
@@ -11473,20 +11608,21 @@ _gdk_windowing_got_event (GdkDisplay *display,
guint old_state, old_button;
GdkDeviceGrabInfo *button_release_grab;
GdkPointerWindowInfo *pointer_info;
GdkDevice *device;
GdkDevice *device, *source_device;
gboolean is_toplevel;
if (gdk_event_get_time (event) != GDK_CURRENT_TIME)
display->last_event_time = gdk_event_get_time (event);
device = gdk_event_get_device (event);
source_device = gdk_event_get_source_device (event);
if (device)
{
GdkInputMode mode;
g_object_get (device, "input-mode", &mode, NULL);
_gdk_display_device_grab_update (display, device, serial);
_gdk_display_device_grab_update (display, device, source_device, serial);
if (mode == GDK_MODE_DISABLED ||
!_gdk_display_check_grab_ownership (display, device, serial))
@@ -11532,7 +11668,7 @@ _gdk_windowing_got_event (GdkDisplay *display,
serial,
gdk_event_get_time (event),
TRUE);
_gdk_display_device_grab_update (display, device, serial);
_gdk_display_device_grab_update (display, device, source_device, serial);
}
if (event->type == GDK_BUTTON_RELEASE &&
!event->any.send_event)
@@ -11545,7 +11681,7 @@ _gdk_windowing_got_event (GdkDisplay *display,
{
button_release_grab->serial_end = serial;
button_release_grab->implicit_ungrab = FALSE;
_gdk_display_device_grab_update (display, device, serial);
_gdk_display_device_grab_update (display, device, source_device, serial);
}
}
@@ -11667,7 +11803,7 @@ _gdk_windowing_got_event (GdkDisplay *display,
{
button_release_grab->serial_end = serial;
button_release_grab->implicit_ungrab = FALSE;
_gdk_display_device_grab_update (display, device, serial);
_gdk_display_device_grab_update (display, device, source_device, serial);
}
}

View File

@@ -779,6 +779,12 @@ void gdk_window_set_device_events (GdkWindow *window,
GdkEventMask gdk_window_get_device_events (GdkWindow *window,
GdkDevice *device);
void gdk_window_set_source_events (GdkWindow *window,
GdkInputSource source,
GdkEventMask event_mask);
GdkEventMask gdk_window_get_source_events (GdkWindow *window,
GdkInputSource source);
void gdk_window_set_icon_list (GdkWindow *window,
GList *pixbufs);
void gdk_window_set_icon (GdkWindow *window,

View File

@@ -48,7 +48,7 @@ _gdk_device_manager_new (GdkDisplay *display)
major = 2;
minor = 0;
if (_gdk_enable_multidevice &&
if (!_gdk_disable_multidevice &&
XIQueryVersion (xdisplay, &major, &minor) != BadRequest)
{
GdkDeviceManagerXI2 *device_manager_xi2;

View File

@@ -205,6 +205,9 @@ create_device (GdkDeviceManager *device_manager,
else if (strstr (tmp_name, "wacom") ||
strstr (tmp_name, "pen"))
input_source = GDK_SOURCE_PEN;
else if (strstr (tmp_name, "touchscreen") ||
strstr (tmp_name, "touchpoint"))
input_source = GDK_SOURCE_TOUCH;
else
input_source = GDK_SOURCE_MOUSE;
@@ -634,6 +637,7 @@ translate_keyboard_string (GdkEventKey *event)
static void
generate_focus_event (GdkWindow *window,
GdkDevice *device,
GdkDevice *source_device,
gboolean in)
{
GdkEvent *event;
@@ -643,6 +647,7 @@ generate_focus_event (GdkWindow *window,
event->focus_change.send_event = FALSE;
event->focus_change.in = in;
gdk_event_set_device (event, device);
gdk_event_set_source_device (event, source_device);
gdk_event_put (event);
gdk_event_free (event);
@@ -651,6 +656,7 @@ generate_focus_event (GdkWindow *window,
static void
handle_focus_change (GdkWindow *window,
GdkDevice *device,
GdkDevice *source_device,
gint detail,
gint mode,
gboolean in)
@@ -710,7 +716,7 @@ handle_focus_change (GdkWindow *window,
}
if (HAS_FOCUS (toplevel) != had_focus)
generate_focus_event (window, device, (in) ? TRUE : FALSE);
generate_focus_event (window, device, source_device, (in) ? TRUE : FALSE);
}
static gdouble *
@@ -908,7 +914,7 @@ gdk_device_manager_xi2_translate_event (GdkEventTranslator *translator,
XIDeviceEvent *xev = (XIDeviceEvent *) ev;
GdkKeymap *keymap = gdk_keymap_get_for_display (display);
GdkModifierType consumed, state;
GdkDevice *device;
GdkDevice *device, *source_device;
event->key.type = xev->evtype == XI_KeyPress ? GDK_KEY_PRESS : GDK_KEY_RELEASE;
@@ -925,6 +931,10 @@ gdk_device_manager_xi2_translate_event (GdkEventTranslator *translator,
GUINT_TO_POINTER (xev->deviceid));
gdk_event_set_device (event, device);
source_device = g_hash_table_lookup (device_manager->id_table,
GUINT_TO_POINTER (xev->sourceid));
gdk_event_set_source_device (event, source_device);
event->key.keyval = GDK_VoidSymbol;
gdk_keymap_translate_keyboard_state (keymap,
@@ -953,6 +963,7 @@ gdk_device_manager_xi2_translate_event (GdkEventTranslator *translator,
case XI_ButtonRelease:
{
XIDeviceEvent *xev = (XIDeviceEvent *) ev;
GdkDevice *source_device;
switch (xev->detail)
{
@@ -981,6 +992,10 @@ gdk_device_manager_xi2_translate_event (GdkEventTranslator *translator,
event->scroll.device = g_hash_table_lookup (device_manager->id_table,
GUINT_TO_POINTER (xev->deviceid));
source_device = g_hash_table_lookup (device_manager->id_table,
GUINT_TO_POINTER (xev->sourceid));
gdk_event_set_source_device (event, source_device);
event->scroll.state = gdk_device_xi2_translate_state (&xev->mods, &xev->buttons);
break;
default:
@@ -996,6 +1011,10 @@ gdk_device_manager_xi2_translate_event (GdkEventTranslator *translator,
event->button.device = g_hash_table_lookup (device_manager->id_table,
GUINT_TO_POINTER (xev->deviceid));
source_device = g_hash_table_lookup (device_manager->id_table,
GUINT_TO_POINTER (xev->sourceid));
gdk_event_set_source_device (event, source_device);
event->button.axes = translate_axes (event->button.device,
event->button.x,
event->button.y,
@@ -1028,6 +1047,7 @@ gdk_device_manager_xi2_translate_event (GdkEventTranslator *translator,
case XI_Motion:
{
XIDeviceEvent *xev = (XIDeviceEvent *) ev;
GdkDevice *source_device;
event->motion.type = GDK_MOTION_NOTIFY;
@@ -1042,6 +1062,10 @@ gdk_device_manager_xi2_translate_event (GdkEventTranslator *translator,
event->motion.device = g_hash_table_lookup (device_manager->id_table,
GINT_TO_POINTER (xev->deviceid));
source_device = g_hash_table_lookup (device_manager->id_table,
GUINT_TO_POINTER (xev->sourceid));
gdk_event_set_source_device (event, source_device);
event->motion.state = gdk_device_xi2_translate_state (&xev->mods, &xev->buttons);
/* There doesn't seem to be motion hints in XI */
@@ -1067,7 +1091,7 @@ gdk_device_manager_xi2_translate_event (GdkEventTranslator *translator,
case XI_Leave:
{
XIEnterEvent *xev = (XIEnterEvent *) ev;
GdkDevice *device;
GdkDevice *device, *source_device;
event->crossing.type = (ev->evtype == XI_Enter) ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY;
@@ -1085,6 +1109,10 @@ gdk_device_manager_xi2_translate_event (GdkEventTranslator *translator,
GINT_TO_POINTER (xev->deviceid));
gdk_event_set_device (event, device);
source_device = g_hash_table_lookup (device_manager->id_table,
GUINT_TO_POINTER (xev->sourceid));
gdk_event_set_source_device (event, source_device);
event->crossing.mode = translate_crossing_mode (xev->mode);
event->crossing.detail = translate_notify_type (xev->detail);
event->crossing.state = gdk_device_xi2_translate_state (&xev->mods, &xev->buttons);
@@ -1094,12 +1122,16 @@ gdk_device_manager_xi2_translate_event (GdkEventTranslator *translator,
case XI_FocusOut:
{
XIEnterEvent *xev = (XIEnterEvent *) ev;
GdkDevice *device;
GdkDevice *device, *source_device;
device = g_hash_table_lookup (device_manager->id_table,
GINT_TO_POINTER (xev->deviceid));
handle_focus_change (window, device, xev->detail, xev->mode,
source_device = g_hash_table_lookup (device_manager->id_table,
GUINT_TO_POINTER (xev->sourceid));
handle_focus_change (window, device, source_device,
xev->detail, xev->mode,
(ev->evtype == XI_FocusIn) ? TRUE : FALSE);
return_val = FALSE;

View File

@@ -1630,7 +1630,7 @@ device_ungrab_callback (GdkDisplay *display,
{
GdkDevice *device = data;
_gdk_display_device_grab_update (display, device, serial);
_gdk_display_device_grab_update (display, device, NULL, serial);
}

View File

@@ -135,7 +135,7 @@ has_pointer_grab_callback (GdkDisplay *display,
{
GdkDevice *device = data;
_gdk_display_device_grab_update (display, device, serial);
_gdk_display_device_grab_update (display, device, NULL, serial);
}
GdkGrabStatus

View File

@@ -200,6 +200,7 @@ gtk_public_h_sources = \
gtkcomboboxentry.h \
gtkcontainer.h \
gtkdebug.h \
gtkdevicegroup.h \
gtkdialog.h \
gtkdnd.h \
gtkdrawingarea.h \
@@ -458,6 +459,7 @@ gtk_base_c_sources = \
gtkcombobox.c \
gtkcomboboxentry.c \
gtkcontainer.c \
gtkdevicegroup.c \
gtkdialog.c \
gtkdrawingarea.c \
gtkeditable.c \

View File

@@ -4487,10 +4487,22 @@ gtk_widget_set_mapped
gtk_widget_get_mapped
gtk_widget_get_support_multidevice
gtk_widget_set_support_multidevice
gtk_widget_create_device_group
gtk_widget_remove_device_group
gtk_widget_get_group_for_device
gtk_widget_device_is_shadowed
#endif
#endif
#if IN_HEADER(__GTK_DEVICE_GROUP_H__)
#if IN_FILE(__GTK_DEVICE_GROUP_C__)
gtk_device_group_get_type G_GNUC_CONST
gtk_device_group_add_device
gtk_device_group_remove_device
gtk_device_group_get_devices
#endif
#endif
#if IN_HEADER(__GTK_WINDOW_H__)
#if IN_FILE(__GTK_WINDOW_C__)
gtk_window_activate_default

215
gtk/gtkdevicegroup.c Normal file
View File

@@ -0,0 +1,215 @@
/* GTK - The GIMP Toolkit
* Copyright (C) 2009 Carlos Garnacho <carlosg@gnome.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include "gtkintl.h"
#include "gtkdevicegroup.h"
#include "gtkalias.h"
/**
* SECTION:gtkdevicegroup
* @Short_description: Group of input devices for multidevice events.
* @Title: Device groups
* @See_also: #GtkMultiDeviceEvent
*
* #GtkDeviceGroup defines a group of devices, they are created through
* gtk_widget_create_device_group() and destroyed through
* gtk_widget_remove_device_group(). Device groups are used by its
* corresponding #GtkWidget in order to issue #GtkMultiDeviceEvent<!-- -->s
* whenever any of the contained devices emits a #GDK_MOTION_NOTIFY
* event, or any device enters or leaves the group.
*/
typedef struct GtkDeviceGroupPrivate GtkDeviceGroupPrivate;
struct GtkDeviceGroupPrivate
{
GList *devices;
};
#define GTK_DEVICE_GROUP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_TYPE_DEVICE_GROUP, GtkDeviceGroupPrivate))
static void gtk_device_group_class_init (GtkDeviceGroupClass *klass);
static void gtk_device_group_init (GtkDeviceGroup *group);
static void gtk_device_group_finalize (GObject *object);
enum {
DEVICE_ADDED,
DEVICE_REMOVED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE (GtkDeviceGroup, gtk_device_group, G_TYPE_OBJECT)
static void
gtk_device_group_class_init (GtkDeviceGroupClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gtk_device_group_finalize;
/**
* GtkDeviceGroup::device-added:
* @device_group: the object that received the signal
* @device: the device that was just added
*
* This signal is emitted right after a #GdkDevice is added
* to @device_group.
*/
signals[DEVICE_ADDED] =
g_signal_new (I_("device-added"),
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkDeviceGroupClass, device_added),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, GDK_TYPE_DEVICE);
/**
* GtkDeviceGroup::device-removed:
* @device_group: the object that received the signal
* @device: the device that was just removed
*
* This signal is emitted right after a #GdkDevice is removed
* from @device_group.
*/
signals[DEVICE_REMOVED] =
g_signal_new (I_("device-removed"),
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkDeviceGroupClass, device_removed),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, GDK_TYPE_DEVICE);
g_type_class_add_private (object_class, sizeof (GtkDeviceGroupPrivate));
}
static void
gtk_device_group_init (GtkDeviceGroup *group)
{
}
static void
gtk_device_group_finalize (GObject *object)
{
GtkDeviceGroupPrivate *priv;
priv = GTK_DEVICE_GROUP_GET_PRIVATE (object);
g_list_foreach (priv->devices, (GFunc) g_object_unref, NULL);
g_list_free (priv->devices);
G_OBJECT_CLASS (gtk_device_group_parent_class)->finalize (object);
}
/**
* gtk_device_group_add_device:
* @group: a #GtkDeviceGroup
* @device: a #GdkDevice
*
* Adds @device to @group, so events coming from this device will
* trigger #GtkWidget::multidevice-event<!-- -->s for @group. Adding
* devices with source %GDK_SOURCE_KEYBOARD is not allowed.
*
* Since: 3.0
**/
void
gtk_device_group_add_device (GtkDeviceGroup *group,
GdkDevice *device)
{
GtkDeviceGroupPrivate *priv;
g_return_if_fail (GTK_IS_DEVICE_GROUP (group));
g_return_if_fail (GDK_IS_DEVICE (device));
g_return_if_fail (device->source != GDK_SOURCE_KEYBOARD);
priv = GTK_DEVICE_GROUP_GET_PRIVATE (group);
if (g_list_find (priv->devices, device))
return;
priv->devices = g_list_prepend (priv->devices,
g_object_ref (device));
g_signal_emit (group, signals[DEVICE_ADDED], 0, device);
}
/**
* gtk_device_group_remove_device:
* @group: a #GtkDeviceGroup
* @device: a #GdkDevice
*
* Removes @device from @group, if it was there.
*
* Since: 3.0
**/
void
gtk_device_group_remove_device (GtkDeviceGroup *group,
GdkDevice *device)
{
GtkDeviceGroupPrivate *priv;
GList *dev;
g_return_if_fail (GTK_IS_DEVICE_GROUP (group));
g_return_if_fail (GDK_IS_DEVICE (device));
priv = GTK_DEVICE_GROUP_GET_PRIVATE (group);
dev = g_list_find (priv->devices, device);
if (!dev)
return;
priv->devices = g_list_remove_link (priv->devices, dev);
g_signal_emit (group, signals[DEVICE_REMOVED], 0, device);
g_object_unref (dev->data);
g_list_free_1 (dev);
}
/**
* gtk_device_group_get_devices:
* @group: a #GtkDeviceGroup
*
* Returns a #GList of #GdkDevices with the devices contained in @group.
*
* Returns: a list of #GdkDevices. This list and its elements are owned
* by group, and must not be freed or unref'ed.
*
* Since: 3.0
**/
GList *
gtk_device_group_get_devices (GtkDeviceGroup *group)
{
GtkDeviceGroupPrivate *priv;
g_return_val_if_fail (GTK_IS_DEVICE_GROUP (group), NULL);
priv = GTK_DEVICE_GROUP_GET_PRIVATE (group);
return priv->devices;
}
#define __GTK_DEVICE_GROUP_C__
#include "gtkaliasdef.c"

71
gtk/gtkdevicegroup.h Normal file
View File

@@ -0,0 +1,71 @@
/* GTK - The GIMP Toolkit
* Copyright (C) 2009 Carlos Garnacho <carlosg@gnome.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#if defined(GTK_DISABLE_SINGLE_INCLUDES) && !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#ifndef __GTK_DEVICE_GROUP_H__
#define __GTK_DEVICE_GROUP_H__
#include <gdk/gdk.h>
G_BEGIN_DECLS
typedef struct GtkDeviceGroup GtkPointerGroup;
#define GTK_TYPE_DEVICE_GROUP (gtk_device_group_get_type ())
#define GTK_DEVICE_GROUP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_DEVICE_GROUP, GtkDeviceGroup))
#define GTK_DEVICE_GROUP_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), GTK_TYPE_DEVICE_GROUP, GtkDeviceGroupClass))
#define GTK_IS_DEVICE_GROUP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_DEVICE_GROUP))
#define GTK_IS_DEVICE_GROUP_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), GTK_TYPE_DEVICE_GROUP))
#define GTK_DEVICE_GROUP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_DEVICE_GROUP, GtkDeviceGroupClass))
typedef struct GtkDeviceGroup GtkDeviceGroup;
typedef struct GtkDeviceGroupClass GtkDeviceGroupClass;
struct GtkDeviceGroup
{
GObject parent_instance;
};
struct GtkDeviceGroupClass
{
GObjectClass parent_class;
void (* device_added) (GtkDeviceGroup *group,
GdkDevice *device);
void (* device_removed) (GtkDeviceGroup *group,
GdkDevice *device);
};
GType gtk_device_group_get_type (void) G_GNUC_CONST;
void gtk_device_group_add_device (GtkDeviceGroup *group,
GdkDevice *device);
void gtk_device_group_remove_device (GtkDeviceGroup *group,
GdkDevice *device);
GList * gtk_device_group_get_devices (GtkDeviceGroup *group);
G_END_DECLS
#endif /* __GTK_DEVICE_GROUP_H__ */

View File

@@ -200,6 +200,7 @@ enum {
KEYNAV_FAILED,
DRAG_FAILED,
DAMAGE_EVENT,
MULTIDEVICE_EVENT,
LAST_SIGNAL
};
@@ -231,6 +232,7 @@ enum {
};
typedef struct _GtkStateData GtkStateData;
typedef struct _GtkMultiDeviceData GtkMultiDeviceData;
struct _GtkStateData
{
@@ -240,6 +242,12 @@ struct _GtkStateData
guint use_forall : 1;
};
struct _GtkMultiDeviceData
{
GList *groups;
GHashTable *by_dev;
};
/* --- prototypes --- */
static void gtk_widget_class_init (GtkWidgetClass *klass);
static void gtk_widget_base_class_finalize (GtkWidgetClass *klass);
@@ -395,6 +403,8 @@ static GQuark quark_mnemonic_labels = 0;
static GQuark quark_tooltip_markup = 0;
static GQuark quark_has_tooltip = 0;
static GQuark quark_tooltip_window = 0;
static GQuark quark_multidevice_events = 0;
static GQuark quark_multidevice_data = 0;
GParamSpecPool *_gtk_widget_child_property_pool = NULL;
GObjectNotifyContext *_gtk_widget_child_property_notify_context = NULL;
@@ -492,6 +502,8 @@ gtk_widget_class_init (GtkWidgetClass *klass)
quark_tooltip_markup = g_quark_from_static_string ("gtk-tooltip-markup");
quark_has_tooltip = g_quark_from_static_string ("gtk-has-tooltip");
quark_tooltip_window = g_quark_from_static_string ("gtk-tooltip-window");
quark_multidevice_events = g_quark_from_static_string ("gtk-multidevice-events");
quark_multidevice_data = g_quark_from_static_string ("gtk-multidevice-data");
style_property_spec_pool = g_param_spec_pool_new (FALSE);
_gtk_widget_child_property_pool = g_param_spec_pool_new (TRUE);
@@ -2423,6 +2435,29 @@ gtk_widget_class_init (GtkWidgetClass *klass)
_gtk_marshal_BOOLEAN__UINT,
G_TYPE_BOOLEAN, 1, G_TYPE_UINT);
/**
* GtkWidget::multidevice-event:
* @widget: the object which received the signal
* @device_group: the #GtkDeviceGroup that was updated by a
* device event
* @event: a #GtkMultiDeviceEvent containing event information
* for all devices in @device_group.
*
* This signal is emitted right after an input device that is
* contained in @device_group emits one event, or whenever a
* #GdkDevice is added or removed from @device_group.
*
* Since: 3.0
*/
widget_signals[MULTIDEVICE_EVENT] =
g_signal_new (I_("multidevice-event"),
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
0, NULL, NULL,
_gtk_marshal_VOID__OBJECT_POINTER,
G_TYPE_NONE, 2,
GTK_TYPE_DEVICE_GROUP, G_TYPE_POINTER);
binding_set = gtk_binding_set_by_class (klass);
gtk_binding_entry_add_signal (binding_set, GDK_F10, GDK_SHIFT_MASK,
"popup-menu", 0);
@@ -4806,6 +4841,85 @@ event_window_is_still_viewable (GdkEvent *event)
}
}
static void
compose_multidevice_event (GtkWidget *widget,
GdkDevice *device,
GdkEventMotion *new_event)
{
GHashTable *multidevice_events;
GtkMultiDeviceEventType type;
GtkDeviceGroup *group;
GtkMultiDeviceData *data;
GtkMultiDeviceEvent event;
GdkEvent *updated_event;
GList *devices;
gint i = 0;
data = g_object_get_qdata ((GObject *) widget,
quark_multidevice_data);
if (!data)
return;
group = g_hash_table_lookup (data->by_dev, device);
if (!group)
return;
multidevice_events = g_object_get_qdata ((GObject *) widget,
quark_multidevice_events);
if (G_UNLIKELY (!multidevice_events))
{
multidevice_events = g_hash_table_new_full (g_direct_hash,
g_direct_equal,
(GDestroyNotify) g_object_unref,
(GDestroyNotify) gdk_event_free);
g_object_set_qdata_full ((GObject *) widget,
quark_multidevice_events,
multidevice_events,
(GDestroyNotify) g_hash_table_destroy);
}
if (!new_event)
{
g_hash_table_remove (multidevice_events, device);
type = GTK_EVENT_DEVICE_REMOVED;
updated_event = NULL;
}
else
{
if (g_hash_table_lookup (multidevice_events, device) == NULL)
type = GTK_EVENT_DEVICE_ADDED;
else
type = GTK_EVENT_DEVICE_UPDATED;
updated_event = gdk_event_copy ((GdkEvent *) new_event);
g_hash_table_insert (multidevice_events,
g_object_ref (device),
updated_event);
}
devices = gtk_device_group_get_devices (group);
/* Compose event */
event.type = type;
event.n_events = g_list_length (devices);
event.events = g_new0 (GdkEventMotion *, event.n_events);
event.updated_event = (GdkEventMotion *) updated_event;
event.updated_device = device;
while (devices)
{
event.events[i] = g_hash_table_lookup (multidevice_events, devices->data);
devices = devices->next;
i++;
}
g_signal_emit (widget, widget_signals[MULTIDEVICE_EVENT], 0, group, &event);
g_free (event.events);
}
static gint
gtk_widget_event_internal (GtkWidget *widget,
GdkEvent *event)
@@ -4928,6 +5042,15 @@ gtk_widget_event_internal (GtkWidget *widget,
}
if (signal_num != -1)
g_signal_emit (widget, widget_signals[signal_num], 0, event, &return_val);
if (event->type == GDK_MOTION_NOTIFY &&
(GTK_WIDGET_FLAGS (widget) & GTK_MULTIDEVICE) != 0)
{
GdkEventMotion *event_motion;
event_motion = (GdkEventMotion *) event;
compose_multidevice_event (widget, event_motion->device, event_motion);
}
}
if (WIDGET_REALIZED_FOR_EVENT (widget, event))
g_signal_emit (widget, widget_signals[EVENT_AFTER], 0, event);
@@ -11553,6 +11676,263 @@ gtk_widget_set_support_multidevice (GtkWidget *widget,
gdk_window_set_support_multidevice (widget->window, support_multidevice);
}
static GdkEventMotion *
convert_event_to_motion (GdkEvent *event)
{
GdkEventMotion *new_event;
new_event = (GdkEventMotion *) gdk_event_new (GDK_MOTION_NOTIFY);
switch (event->type)
{
case GDK_BUTTON_PRESS:
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
new_event->window = g_object_ref (event->button.window);
new_event->send_event = TRUE;
new_event->time = event->button.time;
new_event->x = event->button.x;
new_event->y = event->button.y;
if (event->button.axes)
new_event->axes = g_memdup (event->button.axes,
sizeof (gdouble) * event->button.device->num_axes);
new_event->state = 0; /* FIXME */
new_event->is_hint = FALSE;
new_event->device = event->button.device;
new_event->x_root = event->button.x_root;
new_event->y_root = event->button.y_root;
break;
case GDK_ENTER_NOTIFY:
case GDK_LEAVE_NOTIFY:
new_event->window = g_object_ref (event->crossing.window);
new_event->send_event = TRUE;
new_event->time = event->crossing.time;
new_event->x = event->crossing.x;
new_event->y = event->crossing.y;
new_event->axes = NULL; /* FIXME: not ideal for non-mice */
new_event->state = 0; /* FIXME */
new_event->is_hint = FALSE;
new_event->x_root = event->crossing.x_root;
new_event->y_root = event->crossing.y_root;
gdk_event_set_device ((GdkEvent *) new_event, gdk_event_get_device ((GdkEvent *) event));
break;
default:
g_warning ("Event with type %d can not be transformed to GdkEventMotion", event->type);
gdk_event_free ((GdkEvent *) new_event);
new_event = NULL;
}
return new_event;
}
static void
device_group_device_added (GtkDeviceGroup *group,
GdkDevice *device,
GtkWidget *widget)
{
GtkMultiDeviceData *data;
GtkDeviceGroup *old_group;
GdkEventMotion *new_event = NULL;
GtkWidget *event_widget;
GdkEvent *event;
data = g_object_get_qdata (G_OBJECT (widget), quark_multidevice_data);
if (G_UNLIKELY (!data))
return;
/* Remove device from old group, if any */
old_group = g_hash_table_lookup (data->by_dev, device);
if (old_group)
gtk_device_group_remove_device (old_group, device);
g_hash_table_insert (data->by_dev,
g_object_ref (device),
g_object_ref (group));
event = gtk_get_current_event ();
if (!event)
return;
if (event->type == GDK_MOTION_NOTIFY)
new_event = (GdkEventMotion *) event;
else
{
event_widget = gtk_get_event_widget (event);
if (widget == event_widget)
new_event = convert_event_to_motion (event);
gdk_event_free (event);
}
if (new_event)
{
gtk_widget_event_internal (widget, (GdkEvent *) new_event);
gdk_event_free ((GdkEvent *) new_event);
}
}
static void
device_group_device_removed (GtkDeviceGroup *group,
GdkDevice *device,
GtkWidget *widget)
{
GtkMultiDeviceData *data;
data = g_object_get_qdata (G_OBJECT (widget), quark_multidevice_data);
g_assert (data != NULL);
compose_multidevice_event (widget, device, NULL);
g_hash_table_remove (data->by_dev, device);
}
static void
free_multidevice_data (GtkMultiDeviceData *data)
{
g_list_foreach (data->groups, (GFunc) g_object_unref, NULL);
g_list_free (data->groups);
g_hash_table_destroy (data->by_dev);
g_slice_free (GtkMultiDeviceData, data);
}
/**
* gtk_widget_create_device_group:
* @widget: a #GtkWidget
*
* Creates a new #GtkDeviceGroup for @widget. devices can be added
* later through gtk_device_group_add_device(). Note that
* #GdkDevice<!-- -->s can only pertain to one #GtkDeviceGroup for a
* given #GtkWidget, so adding it to a new group will remove it from
* its previous one.
*
* Returns: a newly created #GtkDeviceGroup. This object is owned by
* @widget and must be destroyed through
* gtk_widget_remove_device_group().
*
* Since: 3.0
**/
GtkDeviceGroup *
gtk_widget_create_device_group (GtkWidget *widget)
{
GtkMultiDeviceData *data;
GtkDeviceGroup *group;
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
group = g_object_new (GTK_TYPE_DEVICE_GROUP, NULL);
g_signal_connect (group, "device-added",
G_CALLBACK (device_group_device_added), widget);
g_signal_connect (group, "device-removed",
G_CALLBACK (device_group_device_removed), widget);
data = g_object_get_qdata (G_OBJECT (widget), quark_multidevice_data);
if (G_UNLIKELY (!data))
{
data = g_slice_new0 (GtkMultiDeviceData);
data->by_dev = g_hash_table_new_full (g_direct_hash,
g_direct_equal,
(GDestroyNotify) g_object_unref,
(GDestroyNotify) g_object_unref);
g_object_set_qdata_full (G_OBJECT (widget),
quark_multidevice_data,
data,
(GDestroyNotify) free_multidevice_data);
}
data->groups = g_list_prepend (data->groups, group);
return group;
}
/**
* gtk_widget_remove_device_group:
* @widget: a #GtkWidget
* @group: a #GtkDeviceGroup
*
* If @group pertains to @widget, @group will be destroyed so no further
* #GtkWidget::multidevice-event<!-- -->s are emitted for it.
*
* Since: 3.0
**/
void
gtk_widget_remove_device_group (GtkWidget *widget,
GtkDeviceGroup *group)
{
GtkMultiDeviceData *data;
GList *devices, *g;
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (GTK_IS_DEVICE_GROUP (group));
data = g_object_get_qdata (G_OBJECT (widget), quark_multidevice_data);
if (G_UNLIKELY (!data))
return;
g = g_list_find (data->groups, group);
if (G_UNLIKELY (!g))
return;
devices = gtk_device_group_get_devices (group);
/* Free per-device data */
while (devices)
{
g_hash_table_remove (data->by_dev, devices->data);
devices = devices->next;
}
/* Free group */
data->groups = g_list_remove_link (data->groups, g);
g_object_unref (g->data);
g_list_free_1 (g);
}
/**
* gtk_widget_get_group_for_device:
* @widget: a #GtkWidget
* @device: a #GdkDevice
*
* Returns the #GtkDeviceGroup containing the #GdkDevice, or %NULL if
* there is none.
*
* Returns: a #GtkDeviceGroup, or %NULL.
*
* Since: 3.0
**/
GtkDeviceGroup *
gtk_widget_get_group_for_device (GtkWidget *widget,
GdkDevice *device)
{
GtkMultiDeviceData *data;
GtkDeviceGroup *group = NULL;
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
data = g_object_get_qdata (G_OBJECT (widget), quark_multidevice_data);
if (!data)
return NULL;
group = g_hash_table_lookup (data->by_dev, device);
return group;
}
static void
_gtk_widget_set_has_focus (GtkWidget *widget,
gboolean has_focus)

View File

@@ -37,6 +37,7 @@
#include <gtk/gtkadjustment.h>
#include <gtk/gtkstyle.h>
#include <gtk/gtksettings.h>
#include <gtk/gtkdevicegroup.h>
#include <atk/atk.h>
G_BEGIN_DECLS
@@ -202,6 +203,7 @@ typedef struct _GtkWidgetShapeInfo GtkWidgetShapeInfo;
typedef struct _GtkClipboard GtkClipboard;
typedef struct _GtkTooltip GtkTooltip;
typedef struct _GtkWindow GtkWindow;
typedef struct _GtkMultiDeviceEvent GtkMultiDeviceEvent;
/**
@@ -533,6 +535,43 @@ struct _GtkWidgetClass
void (*_gtk_reserved7) (void);
};
/**
* GtkMultiDeviceEventType:
* @GTK_EVENT_DEVICE_ADDED: A device was added to the device group.
* @GTK_EVENT_DEVICE_REMOVED: A device was removed from the device group.
* @GTK_EVENT_DEVICE_UPDATED: A device in the device group has updated its
* state.
*
* Provides a hint about the change that initiated the
* #GtkWidget::multidevice-event.
*/
typedef enum
{
GTK_EVENT_DEVICE_ADDED,
GTK_EVENT_DEVICE_REMOVED,
GTK_EVENT_DEVICE_UPDATED
} GtkMultiDeviceEventType;
/**
* GtkMultiDeviceEvent:
* @type: the event type.
* @n_events: number of device events contained in this #GtkMultiDeviceEvent
* @events: an array of #GdkEventMotion structs.
* @updated_event: latest updated event, or %NULL if @type is %GDK_EVENT_DEVICE_REMOVED.
* @updated_device: device that triggered the event.
*
* The #GtkMultiDeviceEvent struct contains information about latest state of
* multiple device pointers. These devices are contained in a #GtkDeviceGroup.
*/
struct _GtkMultiDeviceEvent
{
GtkMultiDeviceEventType type;
guint n_events;
GdkEventMotion **events;
GdkEventMotion *updated_event;
GdkDevice *updated_device;
};
struct _GtkWidgetAuxInfo
{
gint x;
@@ -766,6 +805,11 @@ GdkPixmap * gtk_widget_get_snapshot (GtkWidget *widget,
gboolean gtk_widget_get_support_multidevice (GtkWidget *widget);
void gtk_widget_set_support_multidevice (GtkWidget *widget,
gboolean support_multidevice);
GtkDeviceGroup * gtk_widget_get_group_for_device (GtkWidget *widget,
GdkDevice *device);
GtkDeviceGroup * gtk_widget_create_device_group (GtkWidget *widget);
void gtk_widget_remove_device_group (GtkWidget *widget,
GtkDeviceGroup *group);
/* Accessibility support */
AtkObject* gtk_widget_get_accessible (GtkWidget *widget);

View File

@@ -1,6 +1,8 @@
## Makefile.am for gtk+/tests
include $(top_srcdir)/Makefile.decl
SUBDIRS = multidevice
INCLUDES = \
-I$(top_srcdir) \
-I$(top_builddir)/gdk \

View File

@@ -0,0 +1,47 @@
## Makefile.am for gtk+/tests/mpx
include $(top_srcdir)/Makefile.decl
INCLUDES = \
-I$(top_srcdir) \
-I$(top_builddir)/gdk \
-I$(top_srcdir)/gdk \
-DGDK_DISABLE_DEPRECATED \
-DGTK_DISABLE_DEPRECATED \
$(GTK_DEBUG_FLAGS) \
$(GTK_DEP_CFLAGS)
DEPS = \
$(top_builddir)/gdk-pixbuf/libgdk_pixbuf-$(GTK_API_VERSION).la \
$(top_builddir)/gdk/$(gdktargetlib) \
$(top_builddir)/gtk/$(gtktargetlib)
LDADDS = \
$(top_builddir)/gdk-pixbuf/libgdk_pixbuf-$(GTK_API_VERSION).la \
$(top_builddir)/gdk/$(gdktargetlib) \
$(top_builddir)/gtk/$(gtktargetlib)
noinst_PROGRAMS = \
testangle \
testcoordinates \
testphotoalbum
testangle_DEPENDENCIES = $(TEST_DEPS)
testcoordinates_DEPENDENCIES = $(TEST_DEPS)
testphotoalbum_DEPENDENCIES = $(TEST_DEPS)
testangle_LDADD = $(LDADDS)
testcoordinates_LDADD = $(LDADDS)
testphotoalbum_LDADD = $(LDADDS)
testangle_SOURCES = \
testangle.c
testcoordinates_SOURCES = \
testcoordinatewidget.c \
testcoordinatewidget.h \
testcoordinates.c
testphotoalbum_SOURCES = \
testphotoalbumwidget.c \
testphotoalbumwidget.h \
testphotoalbum.c

View File

@@ -0,0 +1,197 @@
/*
* Copyright (C) 2009 Carlos Garnacho <carlosg@gnome.org>
*
* This work is provided "as is"; redistribution and modification
* in whole or in part, in any medium, physical or electronic is
* permitted without restriction.
*
* This work 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.
*
* In no event shall the authors or contributors be liable for any
* direct, indirect, incidental, special, exemplary, or consequential
* damages (including, but not limited to, procurement of substitute
* goods or services; loss of use, data, or profits; or business
* interruption) however caused and on any theory of liability, whether
* in contract, strict liability, or tort (including negligence or
* otherwise) arising in any way out of the use of this software, even
* if advised of the possibility of such damage.
*/
#include <gtk/gtk.h>
typedef struct DeviceData DeviceData;
typedef struct Data Data;
struct Data
{
GtkDeviceGroup *group;
gdouble x;
gdouble y;
gdouble angle;
gdouble distance;
};
static gboolean
button_press_cb (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
Data *data;
GList *devices;
data = (Data *) user_data;
devices = gtk_device_group_get_devices (data->group);
if (g_list_length (devices) < 2)
gtk_device_group_add_device (data->group, gdk_event_get_device (event));
return FALSE;
}
static gboolean
button_release_cb (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
Data *data;
data = (Data *) user_data;
gtk_device_group_remove_device (data->group, gdk_event_get_device (event));
return FALSE;
}
static void
multidevice_cb (GtkWidget *widget,
GtkDeviceGroup *group,
GtkMultiDeviceEvent *event,
gpointer user_data)
{
Data *data;
data = user_data;
if (event->n_events > 0)
{
data->x = event->events[0]->x;
data->y = event->events[0]->y;
}
if (event->n_events > 1)
{
gdk_events_get_distance ((GdkEvent *) event->events[0],
(GdkEvent *) event->events[1],
&data->distance);
gdk_events_get_angle ((GdkEvent *) event->events[0],
(GdkEvent *) event->events[1],
&data->angle);
}
else
{
data->distance = 0;
data->angle = 0;
}
gtk_widget_queue_draw (widget);
}
static gboolean
expose_cb (GtkWidget *widget,
GdkEventExpose *event,
gpointer user_data)
{
Data *data;
cairo_t *cr;
GList *devices;
data = (Data *) user_data;
devices = gtk_device_group_get_devices (data->group);
if (!devices)
return TRUE;
cr = gdk_cairo_create (widget->window);
cairo_translate (cr, data->x, data->y);
cairo_save (cr);
cairo_set_source_rgb (cr, 0., 0., 0.);
cairo_move_to (cr, 0, 0);
cairo_rel_line_to (cr, 1000, 0);
cairo_stroke (cr);
cairo_restore (cr);
if (data->angle > 0)
{
cairo_save (cr);
cairo_set_source_rgb (cr, 0, 0, 1);
cairo_move_to (cr, 0, 0);
cairo_arc (cr,
0.,
0.,
MAX (10., data->distance),
0.,
data->angle);
cairo_close_path (cr);
cairo_fill (cr);
cairo_restore (cr);
cairo_set_source_rgb (cr, 1., 0., 0.);
cairo_rotate (cr, data->angle);
cairo_move_to (cr, 0, 0);
cairo_rel_line_to (cr, data->distance, 0);
cairo_stroke (cr);
}
cairo_destroy (cr);
return TRUE;
}
int
main (int argc, gchar *argv[])
{
GtkWidget *window;
Data data = { 0 };
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_set_app_paintable (window, TRUE);
data.group = gtk_widget_create_device_group (window);
gtk_widget_add_events (window,
(GDK_POINTER_MOTION_MASK |
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_BUTTON_MOTION_MASK));
gtk_widget_set_support_multidevice (window, TRUE);
g_signal_connect (window, "button-press-event",
G_CALLBACK (button_press_cb), &data);
g_signal_connect (window, "button-release-event",
G_CALLBACK (button_release_cb), &data);
g_signal_connect (window, "multidevice-event",
G_CALLBACK (multidevice_cb), &data);
g_signal_connect (window, "expose-event",
G_CALLBACK (expose_cb), &data);
gtk_widget_show (window);
gtk_main ();
return 0;
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2009 Carlos Garnacho <carlosg@gnome.org>
*
* This work is provided "as is"; redistribution and modification
* in whole or in part, in any medium, physical or electronic is
* permitted without restriction.
*
* This work 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.
*
* In no event shall the authors or contributors be liable for any
* direct, indirect, incidental, special, exemplary, or consequential
* damages (including, but not limited to, procurement of substitute
* goods or services; loss of use, data, or profits; or business
* interruption) however caused and on any theory of liability, whether
* in contract, strict liability, or tort (including negligence or
* otherwise) arising in any way out of the use of this software, even
* if advised of the possibility of such damage.
*/
#include <gtk/gtk.h>
#include "testcoordinatewidget.h"
int
main (int argc, char *argv[])
{
GtkWidget *window, *widget;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
widget = test_coordinate_widget_new ();
gtk_container_add (GTK_CONTAINER (window), widget);
g_signal_connect (window, "delete-event",
G_CALLBACK (gtk_main_quit), NULL);
gtk_widget_show_all (window);
gtk_main ();
return 0;
}

View File

@@ -0,0 +1,251 @@
/*
* Copyright (C) 2009 Carlos Garnacho <carlosg@gnome.org>
*
* This work is provided "as is"; redistribution and modification
* in whole or in part, in any medium, physical or electronic is
* permitted without restriction.
*
* This work 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.
*
* In no event shall the authors or contributors be liable for any
* direct, indirect, incidental, special, exemplary, or consequential
* damages (including, but not limited to, procurement of substitute
* goods or services; loss of use, data, or profits; or business
* interruption) however caused and on any theory of liability, whether
* in contract, strict liability, or tort (including negligence or
* otherwise) arising in any way out of the use of this software, even
* if advised of the possibility of such damage.
*/
#include "testcoordinatewidget.h"
#define TEST_COORDINATE_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TEST_TYPE_COORDINATE_WIDGET, TestCoordinateWidgetPrivate))
typedef struct TestCoordinateWidgetPrivate TestCoordinateWidgetPrivate;
struct TestCoordinateWidgetPrivate
{
GtkDeviceGroup *group;
GHashTable *coordinates;
};
static void test_coordinate_widget_class_init (TestCoordinateWidgetClass *klass);
static void test_coordinate_widget_init (TestCoordinateWidget *coordinate);
static void test_coordinate_widget_destroy (GtkObject *object);
static gboolean test_coordinate_widget_button_press (GtkWidget *widget,
GdkEventButton *event);
static gboolean test_coordinate_widget_button_release (GtkWidget *widget,
GdkEventButton *event);
static gboolean test_coordinate_widget_expose (GtkWidget *widget,
GdkEventExpose *event);
static void test_coordinate_widget_multidevice_event (GtkWidget *widget,
GtkDeviceGroup *group,
GtkMultiDeviceEvent *event);
G_DEFINE_TYPE (TestCoordinateWidget, test_coordinate_widget, GTK_TYPE_DRAWING_AREA)
static void
test_coordinate_widget_class_init (TestCoordinateWidgetClass *klass)
{
GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->destroy = test_coordinate_widget_destroy;
widget_class->button_press_event = test_coordinate_widget_button_press;
widget_class->button_release_event = test_coordinate_widget_button_release;
widget_class->expose_event = test_coordinate_widget_expose;
g_type_class_add_private (klass, sizeof (TestCoordinateWidgetPrivate));
}
static void
test_coordinate_widget_init (TestCoordinateWidget *coord)
{
TestCoordinateWidgetPrivate *priv;
GtkWidget *widget;
priv = TEST_COORDINATE_WIDGET_GET_PRIVATE (coord);
widget = GTK_WIDGET (coord);
priv->coordinates = g_hash_table_new_full (g_direct_hash,
g_direct_equal,
(GDestroyNotify) g_object_unref,
(GDestroyNotify) g_free);
gtk_widget_add_events (widget,
(GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_POINTER_MOTION_MASK |
GDK_ENTER_NOTIFY_MASK |
GDK_LEAVE_NOTIFY_MASK));
priv->group = gtk_widget_create_device_group (GTK_WIDGET (coord));
gtk_widget_set_support_multidevice (widget, TRUE);
/* Multidevice events are not exposed through GtkWidgetClass */
g_signal_connect (coord, "multidevice-event",
G_CALLBACK (test_coordinate_widget_multidevice_event), NULL);
}
static void
test_coordinate_widget_destroy (GtkObject *object)
{
TestCoordinateWidgetPrivate *priv;
priv = TEST_COORDINATE_WIDGET_GET_PRIVATE (object);
if (priv->coordinates)
{
g_hash_table_destroy (priv->coordinates);
priv->coordinates = NULL;
}
if (priv->group)
{
gtk_widget_remove_device_group (GTK_WIDGET (object), priv->group);
priv->group = NULL;
}
GTK_OBJECT_CLASS (test_coordinate_widget_parent_class)->destroy (object);
}
static gboolean
test_coordinate_widget_button_press (GtkWidget *widget,
GdkEventButton *event)
{
TestCoordinateWidgetPrivate *priv;
GdkDevice *device;
priv = TEST_COORDINATE_WIDGET_GET_PRIVATE (widget);
device = gdk_event_get_device ((GdkEvent *) event);
gtk_device_group_add_device (priv->group, device);
return FALSE;
}
static gboolean
test_coordinate_widget_button_release (GtkWidget *widget,
GdkEventButton *event)
{
TestCoordinateWidgetPrivate *priv;
GdkDevice *device;
priv = TEST_COORDINATE_WIDGET_GET_PRIVATE (widget);
device = gdk_event_get_device ((GdkEvent *) event);
gtk_device_group_remove_device (priv->group, device);
return FALSE;
}
static void
test_coordinate_widget_multidevice_event (GtkWidget *widget,
GtkDeviceGroup *group,
GtkMultiDeviceEvent *event)
{
TestCoordinateWidgetPrivate *priv;
GdkEventMotion *ev;
GdkPoint *point;
priv = TEST_COORDINATE_WIDGET_GET_PRIVATE (widget);
if (event->type == GTK_EVENT_DEVICE_REMOVED)
g_hash_table_remove (priv->coordinates, event->updated_device);
else
{
point = g_hash_table_lookup (priv->coordinates, event->updated_device);
ev = event->updated_event;
if (G_UNLIKELY (!point))
{
point = g_new (GdkPoint, 1);
g_hash_table_insert (priv->coordinates,
g_object_ref (event->updated_device),
point);
}
point->x = ev->x;
point->y = ev->y;
}
gtk_widget_queue_draw (widget);
}
static gboolean
test_coordinate_widget_expose (GtkWidget *widget,
GdkEventExpose *event)
{
TestCoordinateWidgetPrivate *priv;
GList *coords, *c;
cairo_t *cr;
priv = TEST_COORDINATE_WIDGET_GET_PRIVATE (widget);
cr = gdk_cairo_create (widget->window);
coords = g_hash_table_get_values (priv->coordinates);
cairo_set_source_rgb (cr, 1., 1., 1.);
cairo_rectangle (cr,
widget->allocation.x,
widget->allocation.y,
widget->allocation.width,
widget->allocation.height);
cairo_fill (cr);
cairo_set_source_rgb (cr, 0.8, 0.8, 0.8);
for (c = coords; c; c = c->next)
{
GdkPoint *point;
point = c->data;
cairo_move_to (cr, point->x, 0);
cairo_rel_line_to (cr, 0, widget->allocation.height);
cairo_move_to (cr, 0, point->y);
cairo_rel_line_to (cr, widget->allocation.width, 0);
}
cairo_stroke (cr);
cairo_set_source_rgba (cr, 0.5, 0., 0., 0.5);
if (g_list_length (coords) > 1)
{
for (c = coords; c; c = c->next)
{
GdkPoint *point;
point = c->data;
cairo_line_to (cr, point->x, point->y);
}
cairo_close_path (cr);
cairo_fill_preserve (cr);
cairo_set_source_rgba (cr, 0., 0., 0.5, 0.5);
cairo_stroke (cr);
}
cairo_destroy (cr);
g_list_free (coords);
return TRUE;
}
GtkWidget *
test_coordinate_widget_new (void)
{
return g_object_new (TEST_TYPE_COORDINATE_WIDGET, NULL);
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2009 Carlos Garnacho <carlosg@gnome.org>
*
* This work is provided "as is"; redistribution and modification
* in whole or in part, in any medium, physical or electronic is
* permitted without restriction.
*
* This work 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.
*
* In no event shall the authors or contributors be liable for any
* direct, indirect, incidental, special, exemplary, or consequential
* damages (including, but not limited to, procurement of substitute
* goods or services; loss of use, data, or profits; or business
* interruption) however caused and on any theory of liability, whether
* in contract, strict liability, or tort (including negligence or
* otherwise) arising in any way out of the use of this software, even
* if advised of the possibility of such damage.
*/
#ifndef __TEST_COORDINATE_WIDGET_H__
#define __TEST_COORDINATE_WIDGET_H__
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define TEST_TYPE_COORDINATE_WIDGET (test_coordinate_widget_get_type ())
#define TEST_COORDINATE_WIDGET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TEST_TYPE_COORDINATE_WIDGET, TestCoordinateWidget))
#define TEST_COORDINATE_WIDGET_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), TEST_TYPE_COORDINATE_WIDGET, TestCoordinateWidgetClass))
#define TEST_IS_COORDINATE_WIDGET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TEST_TYPE_COORDINATE_WIDGET))
#define TEST_IS_COORDINATE_WIDGET_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), TEST_TYPE_COORDINATE_WIDGET))
#define TEST_COORDINATE_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TEST_TYPE_COORDINATE_WIDGET, TestCoordinateWidgetClass))
typedef struct TestCoordinateWidget TestCoordinateWidget;
typedef struct TestCoordinateWidgetClass TestCoordinateWidgetClass;
struct TestCoordinateWidget
{
GtkDrawingArea parent_instance;
};
struct TestCoordinateWidgetClass
{
GtkDrawingAreaClass parent_class;
};
GType test_coordinate_widget_get_type (void) G_GNUC_CONST;
GtkWidget * test_coordinate_widget_new (void);
G_END_DECLS
#endif /* __TEST_COORDINATE_WIDGET_H__ */

View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2009 Carlos Garnacho <carlosg@gnome.org>
*
* This work is provided "as is"; redistribution and modification
* in whole or in part, in any medium, physical or electronic is
* permitted without restriction.
*
* This work 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.
*
* In no event shall the authors or contributors be liable for any
* direct, indirect, incidental, special, exemplary, or consequential
* damages (including, but not limited to, procurement of substitute
* goods or services; loss of use, data, or profits; or business
* interruption) however caused and on any theory of liability, whether
* in contract, strict liability, or tort (including negligence or
* otherwise) arising in any way out of the use of this software, even
* if advised of the possibility of such damage.
*/
#include <gtk/gtk.h>
#include "testphotoalbumwidget.h"
static void
add_image (TestPhotoAlbumWidget *album,
const gchar *image_path)
{
GdkPixbuf *pixbuf;
pixbuf = gdk_pixbuf_new_from_file_at_size (image_path,
200, -1,
NULL);
if (pixbuf)
{
test_photo_album_widget_add_image (album, pixbuf);
g_object_unref (pixbuf);
}
}
static void
read_directory (GFile *directory,
TestPhotoAlbumWidget *album)
{
GFileEnumerator *enumerator;
GError *error = NULL;
GFileInfo *info;
enumerator = g_file_enumerate_children (directory,
G_FILE_ATTRIBUTE_STANDARD_NAME,
G_FILE_QUERY_INFO_NONE,
NULL,
&error);
if (error)
{
g_warning ("%s\n", error->message);
g_error_free (error);
return;
}
while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL)
{
GFile *file;
gchar *path;
file = g_file_get_child (directory, g_file_info_get_name (info));
path = g_file_get_path (file);
add_image (album, path);
g_free (path);
g_object_unref (file);
}
g_file_enumerator_close (enumerator, NULL, NULL);
g_object_unref (enumerator);
}
int
main (int argc, char *argv[])
{
GtkWidget *window, *widget;
GFile *dir;
if (argc != 2)
{
g_print ("USAGE: %s <path-to-directory-with-images>\n", argv[0]);
return -1;
}
gtk_init (&argc, &argv);
dir = g_file_new_for_commandline_arg (argv[1]);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
widget = test_photo_album_widget_new ();
gtk_container_add (GTK_CONTAINER (window), widget);
g_signal_connect (window, "delete-event",
G_CALLBACK (gtk_main_quit), NULL);
read_directory (dir, TEST_PHOTO_ALBUM_WIDGET (widget));
gtk_widget_show_all (window);
gtk_main ();
return 0;
}

View File

@@ -0,0 +1,619 @@
/*
* Copyright (C) 2009 Carlos Garnacho <carlosg@gnome.org>
*
* This work is provided "as is"; redistribution and modification
* in whole or in part, in any medium, physical or electronic is
* permitted without restriction.
*
* This work 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.
*
* In no event shall the authors or contributors be liable for any
* direct, indirect, incidental, special, exemplary, or consequential
* damages (including, but not limited to, procurement of substitute
* goods or services; loss of use, data, or profits; or business
* interruption) however caused and on any theory of liability, whether
* in contract, strict liability, or tort (including negligence or
* otherwise) arising in any way out of the use of this software, even
* if advised of the possibility of such damage.
*/
#include "testphotoalbumwidget.h"
#include <math.h>
#define TEST_PHOTO_ALBUM_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TEST_TYPE_PHOTO_ALBUM_WIDGET, TestPhotoAlbumWidgetPrivate))
typedef struct TestPhotoAlbumWidgetPrivate TestPhotoAlbumWidgetPrivate;
typedef struct TestPhoto TestPhoto;
struct TestPhoto
{
GtkDeviceGroup *group;
gdouble center_x;
gdouble center_y;
gdouble x;
gdouble y;
gdouble angle;
gdouble zoom;
cairo_surface_t *surface;
GdkPoint points[4];
gdouble base_zoom;
gdouble base_angle;
gdouble initial_distance;
gdouble initial_angle;
};
struct TestPhotoAlbumWidgetPrivate
{
GPtrArray *photos;
};
static GQuark quark_group_photo = 0;
static void test_photo_album_widget_class_init (TestPhotoAlbumWidgetClass *klass);
static void test_photo_album_widget_init (TestPhotoAlbumWidget *album);
static void test_photo_album_widget_destroy (GtkObject *object);
static gboolean test_photo_album_widget_button_press (GtkWidget *widget,
GdkEventButton *event);
static gboolean test_photo_album_widget_button_release (GtkWidget *widget,
GdkEventButton *event);
static void test_photo_album_widget_multidevice_event (GtkWidget *widget,
GtkDeviceGroup *group,
GtkMultiDeviceEvent *event);
static gboolean test_photo_album_widget_expose (GtkWidget *widget,
GdkEventExpose *event);
G_DEFINE_TYPE (TestPhotoAlbumWidget, test_photo_album_widget, GTK_TYPE_DRAWING_AREA)
static void
test_photo_album_widget_class_init (TestPhotoAlbumWidgetClass *klass)
{
GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->destroy = test_photo_album_widget_destroy;
widget_class->button_press_event = test_photo_album_widget_button_press;
widget_class->button_release_event = test_photo_album_widget_button_release;
widget_class->expose_event = test_photo_album_widget_expose;
g_type_class_add_private (klass, sizeof (TestPhotoAlbumWidgetPrivate));
quark_group_photo = g_quark_from_static_string ("group-photo");
}
static void
test_photo_album_widget_init (TestPhotoAlbumWidget *album)
{
TestPhotoAlbumWidgetPrivate *priv;
GtkWidget *widget;
priv = TEST_PHOTO_ALBUM_WIDGET_GET_PRIVATE (album);
widget = GTK_WIDGET (album);
priv->photos = g_ptr_array_new ();
gtk_widget_add_events (widget,
(GDK_POINTER_MOTION_MASK |
GDK_BUTTON_MOTION_MASK |
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK));
gtk_widget_set_support_multidevice (widget, TRUE);
/* Multidevice events are not exposed through GtkWidgetClass */
g_signal_connect (album, "multidevice-event",
G_CALLBACK (test_photo_album_widget_multidevice_event), NULL);
}
static void
calculate_rotated_point (gdouble angle,
gdouble zoom,
gdouble center_x,
gdouble center_y,
gdouble point_x,
gdouble point_y,
gdouble *ret_x,
gdouble *ret_y)
{
gdouble distance, xd, yd, ang;
if (angle == 0)
{
*ret_x = point_x;
*ret_y = point_y;
return;
}
xd = center_x - point_x;
yd = center_y - point_y;
if (xd == 0 && yd == 0)
{
*ret_x = center_x;
*ret_y = center_y;
return;
}
distance = sqrt ((xd * xd) + (yd * yd));
distance *= zoom;
ang = atan2 (xd, yd);
/* Invert angle */
ang = (2 * G_PI) - ang;
/* Shift it 270° */
ang += 3 * (G_PI / 2);
/* And constraint it to 0°-360° */
ang = fmod (ang, 2 * G_PI);
ang += angle;
*ret_x = center_x + (distance * cos (ang));
*ret_y = center_y + (distance * sin (ang));
}
static void
test_photo_bounding_rect (TestPhoto *photo,
GdkRectangle *rect)
{
gint i, left, right, top, bottom;
left = top = G_MAXINT;
right = bottom = 0;
for (i = 0; i < 4; i++)
{
if (photo->points[i].x < left)
left = photo->points[i].x;
if (photo->points[i].x > right)
right = photo->points[i].x;
if (photo->points[i].y < top)
top = photo->points[i].y;
if (photo->points[i].y > bottom)
bottom = photo->points[i].y;
}
rect->x = left - 20;
rect->y = top - 20;
rect->width = right - left + 40;
rect->height = bottom - top + 40;
}
static void
allocate_photo_region (TestPhoto *photo)
{
gint width, height, i;
width = cairo_image_surface_get_width (photo->surface);
height = cairo_image_surface_get_height (photo->surface);
/* Top/left */
photo->points[0].x = photo->x - photo->center_x;
photo->points[0].y = photo->y - photo->center_y;
/* Top/right */
photo->points[1].x = photo->x - photo->center_x + width;
photo->points[1].y = photo->y - photo->center_y;
/* Bottom/right */
photo->points[2].x = photo->x - photo->center_x + width;
photo->points[2].y = photo->y - photo->center_y + height;
/* Bottom/left */
photo->points[3].x = photo->x - photo->center_x;
photo->points[3].y = photo->y - photo->center_y + height;
for (i = 0; i < 4; i++)
{
gdouble ret_x, ret_y;
calculate_rotated_point (photo->angle,
photo->zoom,
photo->x,
photo->y,
(gdouble) photo->points[i].x,
(gdouble) photo->points[i].y,
&ret_x,
&ret_y);
photo->points[i].x = (gint) ret_x;
photo->points[i].y = (gint) ret_y;
}
}
static TestPhoto *
test_photo_new (TestPhotoAlbumWidget *album,
GdkPixbuf *pixbuf)
{
TestPhoto *photo;
static gdouble angle = 0;
gint width, height;
cairo_t *cr;
photo = g_slice_new0 (TestPhoto);
photo->group = gtk_widget_create_device_group (GTK_WIDGET (album));
g_object_set_qdata (G_OBJECT (photo->group), quark_group_photo, photo);
photo->center_x = 0;
photo->center_y = 0;
photo->x = 0;
photo->y = 0;
photo->angle = angle;
photo->zoom = 1.0;
angle += 0.2;
width = gdk_pixbuf_get_width (pixbuf);
height = gdk_pixbuf_get_height (pixbuf);
/* Put the pixbuf into an image surface */
photo->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
cr = cairo_create (photo->surface);
gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
cairo_paint (cr);
cairo_set_source_rgb (cr, 0., 0., 0.);
cairo_rectangle (cr, 0, 0, width, height);
cairo_stroke (cr);
allocate_photo_region (photo);
return photo;
}
static void
test_photo_free (TestPhoto *photo,
TestPhotoAlbumWidget *album)
{
gtk_widget_remove_device_group (GTK_WIDGET (album), photo->group);
cairo_surface_destroy (photo->surface);
g_slice_free (TestPhoto, photo);
}
static void
test_photo_raise (TestPhoto *photo,
TestPhotoAlbumWidget *album)
{
TestPhotoAlbumWidgetPrivate *priv;
priv = TEST_PHOTO_ALBUM_WIDGET_GET_PRIVATE (album);
g_ptr_array_remove (priv->photos, photo);
g_ptr_array_add (priv->photos, photo);
}
static gboolean
test_photo_point_in (TestPhoto *photo,
gint x,
gint y)
{
GdkPoint *left, *right, *top, *bottom;
gint i;
left = right = top = bottom = NULL;
for (i = 0; i < 4; i++)
{
GdkPoint *p = &photo->points[i];
if (!left ||
p->x < left->x ||
(p->x == left->x && p->y > left->y))
left = p;
if (!right ||
p->x > right->x ||
(p->x == right->x && p->y < right->y))
right = p;
}
for (i = 0; i < 4; i++)
{
GdkPoint *p = &photo->points[i];
if (p == left || p == right)
continue;
if (!top ||
p->y < top->y)
top = p;
if (!bottom ||
p->y > bottom->y)
bottom = p;
}
g_assert (left && right && top && bottom);
if (x < left->x ||
x > right->x ||
y < top->y ||
y > bottom->y)
return FALSE;
/* Check whether point is above the sides
* between leftmost and topmost, and
* topmost and rightmost corners.
*/
if (x <= top->x)
{
if (left->y - ((left->y - top->y) * (((gdouble) x - left->x) / (top->x - left->x))) > y)
return FALSE;
}
else
{
if (top->y + ((right->y - top->y) * (((gdouble) x - top->x) / (right->x - top->x))) > y)
return FALSE;
}
/* Check whether point is below the sides
* between leftmost and bottom, and
* bottom and rightmost corners.
*/
if (x <= bottom->x)
{
if (left->y + ((bottom->y - left->y) * (((gdouble) x - left->x) / (bottom->x - left->x))) < y)
return FALSE;
}
else
{
if (bottom->y - ((bottom->y - right->y) * (((gdouble) x - bottom->x) / (right->x - bottom->x))) < y)
return FALSE;
}
return TRUE;
}
static void
test_photo_album_widget_destroy (GtkObject *object)
{
TestPhotoAlbumWidgetPrivate *priv;
priv = TEST_PHOTO_ALBUM_WIDGET_GET_PRIVATE (object);
if (priv->photos)
{
g_ptr_array_foreach (priv->photos, (GFunc) test_photo_free, object);
g_ptr_array_free (priv->photos, TRUE);
priv->photos = NULL;
}
GTK_OBJECT_CLASS (test_photo_album_widget_parent_class)->destroy (object);
}
static TestPhoto *
find_photo_at_position (TestPhotoAlbumWidget *album,
gdouble x,
gdouble y)
{
TestPhotoAlbumWidgetPrivate *priv;
TestPhoto *photo;
gint i;
priv = TEST_PHOTO_ALBUM_WIDGET_GET_PRIVATE (album);
for (i = priv->photos->len - 1; i >= 0; i--)
{
photo = g_ptr_array_index (priv->photos, i);
if (test_photo_point_in (photo, (gint) x, (gint) y))
return photo;
}
return NULL;
}
static gboolean
test_photo_album_widget_button_press (GtkWidget *widget,
GdkEventButton *event)
{
TestPhoto *photo;
photo = find_photo_at_position (TEST_PHOTO_ALBUM_WIDGET (widget), event->x, event->y);
if (!photo)
return FALSE;
test_photo_raise (photo, TEST_PHOTO_ALBUM_WIDGET (widget));
gtk_device_group_add_device (photo->group, event->device);
return TRUE;
}
static gboolean
test_photo_album_widget_button_release (GtkWidget *widget,
GdkEventButton *event)
{
GtkDeviceGroup *group;
group = gtk_widget_get_group_for_device (widget, event->device);
if (group)
gtk_device_group_remove_device (group, event->device);
return TRUE;
}
static void
test_photo_album_widget_multidevice_event (GtkWidget *widget,
GtkDeviceGroup *group,
GtkMultiDeviceEvent *event)
{
TestPhotoAlbumWidgetPrivate *priv;
cairo_region_t *region;
TestPhoto *photo;
gboolean new_center = FALSE;
gboolean new_position = FALSE;
gdouble event_x, event_y;
GdkRectangle rect;
priv = TEST_PHOTO_ALBUM_WIDGET_GET_PRIVATE (widget);
photo = g_object_get_qdata (G_OBJECT (group), quark_group_photo);
test_photo_bounding_rect (photo, &rect);
region = cairo_region_create_rectangle ((cairo_rectangle_int_t *) &rect);
if (event->n_events == 1)
{
if (event->type == GTK_EVENT_DEVICE_REMOVED)
{
/* Device was just removed, unset zoom/angle info */
photo->base_zoom = 0;
photo->base_angle = 0;
photo->initial_angle = 0;
photo->initial_distance = 0;
new_center = TRUE;
}
else if (event->type == GTK_EVENT_DEVICE_ADDED)
new_center = TRUE;
event_x = event->events[0]->x;
event_y = event->events[0]->y;
new_position = TRUE;
}
else if (event->n_events == 2)
{
gdouble distance, angle;
gdk_events_get_center ((GdkEvent *) event->events[0],
(GdkEvent *) event->events[1],
&event_x, &event_y);
gdk_events_get_distance ((GdkEvent *) event->events[0],
(GdkEvent *) event->events[1],
&distance);
gdk_events_get_angle ((GdkEvent *) event->events[0],
(GdkEvent *) event->events[1],
&angle);
if (event->type == GTK_EVENT_DEVICE_ADDED)
{
photo->base_zoom = photo->zoom;
photo->base_angle = photo->angle;
photo->initial_angle = angle;
photo->initial_distance = distance;
new_center = TRUE;
}
photo->zoom = photo->base_zoom * (distance / photo->initial_distance);
photo->angle = photo->base_angle + (angle - photo->initial_angle);
new_position = TRUE;
}
if (new_center)
{
gdouble origin_x, origin_y;
origin_x = photo->x - photo->center_x;
origin_y = photo->y - photo->center_y;
calculate_rotated_point (- photo->angle,
1 / photo->zoom,
photo->x - origin_x,
photo->y - origin_y,
event_x - origin_x,
event_y - origin_y,
&photo->center_x,
&photo->center_y);
}
if (new_position)
{
photo->x = event_x;
photo->y = event_y;
}
allocate_photo_region (photo);
test_photo_bounding_rect (photo, &rect);
cairo_region_union_rectangle (region, (cairo_rectangle_int_t *) &rect);
gdk_window_invalidate_region (widget->window, region, FALSE);
}
static gboolean
test_photo_album_widget_expose (GtkWidget *widget,
GdkEventExpose *event)
{
TestPhotoAlbumWidgetPrivate *priv;
cairo_t *cr;
gint i;
priv = TEST_PHOTO_ALBUM_WIDGET_GET_PRIVATE (widget);
cr = gdk_cairo_create (widget->window);
gdk_cairo_region (cr, event->region);
cairo_clip (cr);
for (i = 0; i < priv->photos->len; i++)
{
TestPhoto *photo = g_ptr_array_index (priv->photos, i);
GdkRectangle rect;
test_photo_bounding_rect (photo, &rect);
if (!gdk_rectangle_intersect (&rect, &event->area, NULL))
continue;
cairo_save (cr);
cairo_translate (cr, photo->points[0].x, photo->points[0].y);
cairo_scale (cr, photo->zoom, photo->zoom);
cairo_rotate (cr, photo->angle);
cairo_set_source_surface (cr, photo->surface, 0, 0);
cairo_paint (cr);
cairo_restore (cr);
}
cairo_destroy (cr);
return TRUE;
}
GtkWidget *
test_photo_album_widget_new (void)
{
return g_object_new (TEST_TYPE_PHOTO_ALBUM_WIDGET, NULL);
}
void
test_photo_album_widget_add_image (TestPhotoAlbumWidget *album,
GdkPixbuf *pixbuf)
{
TestPhotoAlbumWidgetPrivate *priv;
TestPhoto *photo;
g_return_if_fail (TEST_IS_PHOTO_ALBUM_WIDGET (album));
g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
priv = TEST_PHOTO_ALBUM_WIDGET_GET_PRIVATE (album);
photo = test_photo_new (album, pixbuf);
g_ptr_array_add (priv->photos, photo);
if (gtk_widget_get_realized (GTK_WIDGET (album)) &&
gtk_widget_is_drawable (GTK_WIDGET (album)))
{
GdkRectangle rect;
test_photo_bounding_rect (photo, &rect);
gdk_window_invalidate_rect (GTK_WIDGET (album)->window, &rect, FALSE);
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2009 Carlos Garnacho <carlosg@gnome.org>
*
* This work is provided "as is"; redistribution and modification
* in whole or in part, in any medium, physical or electronic is
* permitted without restriction.
*
* This work 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.
*
* In no event shall the authors or contributors be liable for any
* direct, indirect, incidental, special, exemplary, or consequential
* damages (including, but not limited to, procurement of substitute
* goods or services; loss of use, data, or profits; or business
* interruption) however caused and on any theory of liability, whether
* in contract, strict liability, or tort (including negligence or
* otherwise) arising in any way out of the use of this software, even
* if advised of the possibility of such damage.
*/
#ifndef __TEST_PHOTO_ALBUM_WIDGET_H__
#define __TEST_PHOTO_ALBUM_WIDGET_H__
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define TEST_TYPE_PHOTO_ALBUM_WIDGET (test_photo_album_widget_get_type ())
#define TEST_PHOTO_ALBUM_WIDGET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TEST_TYPE_PHOTO_ALBUM_WIDGET, TestPhotoAlbumWidget))
#define TEST_PHOTO_ALBUM_WIDGET_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), TEST_TYPE_PHOTO_ALBUM_WIDGET, TestPhotoAlbumWidgetClass))
#define TEST_IS_PHOTO_ALBUM_WIDGET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TEST_TYPE_PHOTO_ALBUM_WIDGET))
#define TEST_IS_PHOTO_ALBUM_WIDGET_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), TEST_TYPE_PHOTO_ALBUM_WIDGET))
#define TEST_PHOTO_ALBUM_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TEST_TYPE_PHOTO_ALBUM_WIDGET, TestPhotoAlbumWidgetClass))
typedef struct TestPhotoAlbumWidget TestPhotoAlbumWidget;
typedef struct TestPhotoAlbumWidgetClass TestPhotoAlbumWidgetClass;
struct TestPhotoAlbumWidget
{
GtkDrawingArea parent_instance;
};
struct TestPhotoAlbumWidgetClass
{
GtkDrawingAreaClass parent_class;
};
GType test_photo_album_widget_get_type (void) G_GNUC_CONST;
GtkWidget * test_photo_album_widget_new (void);
void test_photo_album_widget_add_image (TestPhotoAlbumWidget *album,
GdkPixbuf *pixbuf);
G_END_DECLS
#endif /* __TEST_PHOTO_ALBUM_WIDGET_H__ */