Compare commits

...

145 Commits

Author SHA1 Message Date
Carlos Garnacho 3c53063e2f viewport: set GDK_SMOOTH_SCROLL_MASK
This is so smooth scroll events are send/handled by the
parent GtkScrolledWindow if any.
2012-02-18 18:07:45 +01:00
Carlos Garnacho 7e08f94c9a treeview: set GDK_SMOOTH_SCROLL_MASK
This is so smooth scroll events are send/handled by the
parent GtkScrolledWindow if any.
2012-02-18 18:07:45 +01:00
Carlos Garnacho f549b02a09 textview: set GDK_SMOOTH_SCROLL_MASK
This is so smooth scroll events are send/handled by the
parent GtkScrolledWindow if any.
2012-02-18 18:07:44 +01:00
Carlos Garnacho 08111aea4c layout: set GDK_SMOOTH_SCROLL_MASK
This is so smooth scroll events are send/handled by the
parent GtkScrolledWindow if any.
2012-02-18 18:07:44 +01:00
Carlos Garnacho 975a3424e7 iconview: set GDK_SMOOTH_SCROLL_MASK
This is so smooth scroll events are send/handled by the
parent GtkScrolledWindow if any.
2012-02-18 18:07:44 +01:00
Michael Natterer 00eab88efc gtk: Implement smooth scrolling in scrolledwindow/range
If delta_x/y information is provided in scroll events, use it
to modify the underlying adjustment in steps proportional to
the deltas provided.

If the child widget of a scrolledwindow doesn't set
GDK_SMOOTH_SCROLL_MASK, regular scroll events will be dispatched,
and still handled by these 2 widgets.
2012-02-18 18:07:44 +01:00
Carlos Garnacho dd498d825f devicemanager,xi2: Implement smooth scrolling
XInput >= 2.1 allows for implementing smooth scrolling,
reporting the different scrolling axes as valuators.
Any change in those will be reported as GdkEventScroll
events with delta_x/y information.

the older kind of scroll events is still handled, and
emulated in devices able to provide smooth scrolling,
setting _gdk_event_set_pointer_emulated() in that case.
2012-02-18 18:07:44 +01:00
Carlos Garnacho 88a1d2584a devicemanager,x11: Initialize event->scroll.delta_x/y to 0 on core events 2012-02-18 18:07:44 +01:00
Michael Natterer acad37d372 quartz: Implement smooth scrolling
nsevent scrollingDeltaX/Y (available on OSX >= Lion) is used to
provide the smooth scrolling values. In any case, old fashioned
events are still sent, setting _gdk_event_set_pointer_emulated()
if the event contains both smooth and non-smooth values.
2012-02-18 18:07:44 +01:00
Carlos Garnacho c5ae193124 gdk: update csw event mask filter to handle smooth scroll
Events of type GDK_SCROLL will be received if the client side window
event mask has either GDK_SCROLL_MASK or GDK_SMOOTH_SCROLL_MASK.

GDK_BUTTON_PRESS_MASK has been removed from type_masks[GDK_SCROLL]
as that bit is often set for other-than-scrolling purposes, and
yet have the window receive scroll events. In GTK+, this forces
non-smooth events bubbling, even if the widgets above want smooth
events, and legitimately set GDK_[SMOOTH_]SCROLL_MASK.
2012-02-18 18:07:44 +01:00
Carlos Garnacho 535e6c5c6e gdk: Filter out either smooth or non-smooth event depending on the evmask
If a device provides both smooth and non-smooth events, the latter will be
flagged with _gdk_event_set_pointer_emulated() so the client side window
receives one or the other. If a device is only able to deliver non-smooth
events, those will be sent, so both direction/deltas may need to be handled.
2012-02-18 18:07:44 +01:00
Carlos Garnacho 251f318ff8 gdk: Add GDK_SMOOTH_SCROLL_MASK
By setting this event, a GdkWindow will receive scroll events
that provide delta values.
2012-02-18 18:07:44 +01:00
Michael Natterer 811019f261 gdk: transfer event->scroll.delta_x/y through csw 2012-02-18 18:07:44 +01:00
Carlos Garnacho aeaa762b7e gdk: deal with GDK_SMOOTH_SCROLL events as not having a direction
gdk_event_get_scroll_direction() will return FALSE on these, so
gdk_event_get_scroll_deltas() has to be used to retrieve dx/dy
2012-02-18 18:07:44 +01:00
Michael Natterer 992cfb285c gdk: Add delta_x/y to scroll events
gdk_event_get_scroll_deltas() can be used to retrieve those
values on smooth scroll events.
2012-02-18 18:07:44 +01:00
Carlos Garnacho c7327d2bc5 gdk: Add GDK_SCROLL_SMOOTH to GdkScrollDirection
This value will be used for smooth scroll events, as they'll
express the scrolling direction in terms of dx/dy.
2012-02-18 18:07:44 +01:00
Matthias Clasen 5b2be2f6f1 GdkTouchCluster Clarify doc language
'Transient' is what was meant here, not 'transitive'.
2012-02-18 18:05:00 +01:00
Matthias Clasen b51662cafa GdkCrossingMode: Remove duplicated doc entry 2012-02-18 18:05:00 +01:00
Carlos Garnacho a328313885 gtk: Release captured events down the hierarchy
Instead of releasing directly onto the target widget,
release down the hierarchy as if uncaptured.
2012-02-18 18:05:00 +01:00
Carlos Garnacho 8528385c80 touchcluster: use an array to store touch IDs
A GList doesn't make much sense for storing guints, so
now a GArray is used internally.

gdk_touch_cluster_get_touches() has changed to return an
allocated guint*, and gdk_touch_cluster_get_n_touches()
has been added too.
2012-02-18 18:05:00 +01:00
Carlos Garnacho c7eba11400 scrolledwindow: store whether the last button press was valid independently
Use a separate boolean instead of coding it up in the last button press
coordinates. This incidentally fixes 0-threshold on kinetic scrolling,
allowing the child widget to get button releases before it prematurely
gets ::grab-broken.
2012-02-18 17:48:54 +01:00
Carlos Garnacho 2cb50c9af2 scrolledwindow: Do not handle dnd-threshold=0
There's no need for it as the GtkSettings property has a lower
limit of 1.
2012-02-18 17:48:54 +01:00
Carlos Garnacho bc5e401de4 docs: Add section about multitouch and other interaction patterns
This is a ripoff of http://live.gnome.org/GTK%2B/DeviceInteractionPatterns,
with better phrasing in general, some more factual points, and multitouch
explained.
2012-02-18 17:48:54 +01:00
Carlos Garnacho fb99b5eeb4 improve docs for multitouch features 2012-02-18 17:48:53 +01:00
Carlos Garnacho d6e23b0924 gtk,gestures: Only match 0-length strokes with homologous stock ones
Avoids spurious stroke matching with fleeting touches.
2012-02-18 17:48:53 +01:00
Carlos Garnacho 42c0d95791 gtk,gestures: Handle multitouch gestures
Gestures are only compared with stock gestures with the same number
of strokes. If a gesture consists of several strokes, the first stroke
is compared (and any possible angle skew is gotten from it), and then
the other strokes are correlated and also compared.

If there's no feasible correlation of strokes, or the gestures yield
an accumulated confidence that's below the threshold, the gesture is
not considered to match.

testgestures has been also added a simple 2-finger gesture for testing.
2012-02-18 17:48:53 +01:00
Carlos Garnacho 6d7ac9e87e textview: Set background on the widget's window
The widget window is usually covered by the bin_window,
this is just necessary so the parent scrolled window
picks the right color for drawing the overshoot area.
2012-02-18 17:48:53 +01:00
Carlos Garnacho 607a1e8fc5 iconview: Set background on the widget's window
The widget window is usually covered by the bin_window,
this is just necessary so the parent scrolled window
picks the right color for drawing the overshoot area.
2012-02-18 17:48:53 +01:00
Carlos Garnacho 02eeed70bd treeview: Set background on the widget's window
The widget window is usually covered by the bin_window,
this is just necessary so the parent scrolled window
picks the right color for drawing the overshoot area.
2012-02-18 17:48:53 +01:00
Carlos Garnacho 849661a573 scrolledwindow: Use the child widget's window bg color for the overshoot area
This makes the overshoot area seamless, if the child plays along.
2012-02-18 17:48:53 +01:00
Carlos Garnacho ca84ac7a6a tests: only handle gestures on testgestures after the last stroke is lifted 2012-02-18 17:48:53 +01:00
Carlos Garnacho 8d421b2af2 gtkwidget: only emit ::gesture when the last stroke is lifted 2012-02-18 17:48:53 +01:00
Carlos Garnacho b2776581da gtk,gestures: Add gtk_gestures_interpreter_get_n_active_strokes
This call gets the number of currently active strokes, finished
strokes (i.e. those that already had a GDK_BUTTON/TOUCH_RELEASE
processed) don't count.
2012-02-18 17:48:53 +01:00
Carlos Garnacho f7eb9edb9b entry: Only allow press-and-hold on the text_area window
This avoids oddities in spinbuttons, press-and-hold shouldn't
be triggered on the buttons.
2012-02-18 17:48:53 +01:00
Carlos Garnacho fe87311f51 gtk,pah: Don't cancel a ::press-and-hold that's scheduled to be activated
There was a slight window where the operation could be cancelled, causing
an assert as the p-a-h data is NULL.
2012-02-18 17:48:52 +01:00
Carlos Garnacho c38159d4d7 gdkwindow: Add device parameter to gdk_window_create_touch_cluster()
gdk_touch_cluster_set_device() had to be called right after anyway,
so set the device directly within this call, and favor the most common
scenario where there's just 1 single multitouch device behind 1 master
device.
2012-02-18 17:48:52 +01:00
Carlos Garnacho 075911ccfd demos: Demonstrate further features in the multitouch demo
Now that GtkButton and GtkScale are multitouch approved, it's
sufficient to enable GDK_TOUCH_MASK on them to have them deal
with multitouch devices.

The scales set rgba of the currently selected rectangle, and
the button adds more rectangles, those can be manipulated
simultaneously together with the rectangles, the only limit
is the number of touches the touchscreen hw is able to detect.
2012-02-18 17:48:52 +01:00
Carlos Garnacho 20a5364a5f gdk: Listen to touch events by default on the native window
GDK will only receive touch events when dealing with a multitouch
device, so these must be transformed to pointer events if the
client-side window receiving the event doesn't listen to touch
events, and the touch sequence the event is from does emulate
the pointer.

If a sequence emulates pointer events, it will result in a
button-press, N motions with GDK_BUTTON1_MASK set and a
button-release event, and it will deliver crossing events
as specified by the current device grab.
2012-02-18 17:48:52 +01:00
Carlos Garnacho 4a3037574d gdk,devicemanager: Mark touch events emulating the pointer as such 2012-02-18 17:48:52 +01:00
Carlos Garnacho fee9840d52 gdk: Only trigger motion hints machinery on motion events
Touch events have no need for it, plus the concept behind
gdk_event_request_motions() doesn't wrap around multiple
touches within a device.
2012-02-18 17:48:52 +01:00
Carlos Garnacho 10bd4f8a72 gdk: Have touch grabs behave like the implicit grab wrt crossing events
These are equivalent to an implicit grab (with !owner_events), so
if the touch leaves or enters the grab window, the other window
won't receive the corresponding counter-event.
2012-02-18 17:48:52 +01:00
Carlos Garnacho 1d986aa8a4 gtk,button: Handle touch events
Touch events don't generate crossing events themselves, so
do not rely on these to determine whether the button release
happened within the event window.
2012-02-18 17:48:52 +01:00
Carlos Garnacho 33790437ce gtk,range: Don't perform a GTK+ grab
The implicit grab on priv->event_window already warrants that this
widget is the only one getting events while the button is pressed,
so don't spare the GTK+ grab here.
2012-02-18 17:48:52 +01:00
Carlos Garnacho 239f52d543 gtk: rewrite touch events wrt the window group too
Likewise to pointer events, have touch events during a device
grab with owner_events=TRUE be delivered normally as long as
the widget pertains to the same window group.
2012-02-18 17:48:52 +01:00
Carlos Garnacho f2d71d3da5 gdk,csw: handle implicit touch grabs
If the touch sequence happens on a window with GDK_TOUCH_MASK set,
a GdkTouchGrabInfo is created to back it up. Else a device grab is
only created if the sequence emulates the pointer.

If both a device and a touch grab are present on a window, the later
of them both is obeyed, Any grab on the device happening after a
touch grab generates grab broken on all the windows an implicit
touch grab was going on, and all touches would be automatically
removed from every touch cluster.
2012-02-18 17:48:52 +01:00
Carlos Garnacho 61e625d9a6 gdk,display: Add internal API to deal with touch implicit grabs
The necessary information about a touch implicit grab is stored in
GdkTouchGrabInfo structs, these are meant to be transient to the
touch sequence
2012-02-18 17:48:52 +01:00
Carlos Garnacho 9e3330c90b gdk,csw: Don't change window_under_pointer for pure touch events
Only touch events that emulate the pointer do change it.
2012-02-18 17:48:52 +01:00
Carlos Garnacho 108c831e23 gdk,xi2: set pointer emulated flags on events with XIPointerEmulated 2012-02-18 17:48:51 +01:00
Carlos Garnacho 77f071b5ff gdk: Add internal API to set "pointer emulated" flag on events
This flag will be used for non-pointer events that are emulated
from eg. touch events, or pointer events being emulated.
2012-02-18 17:48:51 +01:00
Carlos Garnacho fb35e29a19 gdk,xi2: Set GDK_BUTTON1_MASK on touch events
GTK+ handles both touch and pointer events through the same
event handlers, so enable this modifier on touch events to
avoid much special casing.
2012-02-18 17:48:51 +01:00
Carlos Garnacho b1f4497697 gdk,xi2: Only enable multitouch if the server reports XI2.2
This enables multitouch-enabled GTK+ to run on older X servers.
2012-02-18 17:48:51 +01:00
Carlos Garnacho 29569e6049 gdk,xi2: Update to latest XInput2.2 spec 2012-02-18 17:48:51 +01:00
Carlos Garnacho c201145d02 gtk,pah: Handle press-and-hold on touch devices 2012-02-18 17:48:51 +01:00
Carlos Garnacho eb214aae74 gdk: Add gdk_event_get_touch_area()
If given an event coming from a touch devices,
this functions will read the MT major/minor/orientation
axes and return a cairo_region_t with the touch shape.
2012-02-18 17:48:51 +01:00
Carlos Garnacho fbef4384aa gestures: Add some unit tests 2012-02-18 17:48:51 +01:00
Carlos Garnacho 88909ac9b2 gtk: Document helper GtkWidget gestures API 2012-02-18 17:48:51 +01:00
Carlos Garnacho 84d9872a26 gestures: Add API documentation 2012-02-18 17:48:51 +01:00
Carlos Garnacho 576ba1a03d gtk: Add helper API to handle gestures
Although GtkGesturesInterpreter can be used standalone, GtkWidget
deserves helper API to enable support for gestures. gestures can
be enabled/disabled, and the ::gesture signal can be used to get
the gesture ID.

The internal GtkWidget gestures interpreter is only handled by
touch events, if gestures are needed for other devices, a
standalone gestures interpreter must be used.
2012-02-18 17:48:51 +01:00
Carlos Garnacho bb448f619c tests: Add testgestures
This testcase handles all gestures offered by GtkGestureType,
and also creates and registers a custom M-shaped GtkGesture.
2012-02-18 17:48:51 +01:00
Carlos Garnacho 9827003e06 gestures: Implement gestures interpretation
The algorithm works on 2 GtkGestures, it finds out the similarity
of these by bending the user provided gesture so it equals a stock
one, the differences in areas are used to determine the level of
confidence on both gestures being similar.
2012-02-18 17:48:51 +01:00
Carlos Garnacho 14100b867c Add a gestures interpreter base implementation
This object is currently shallow, the API is defined but no gesture
interpretation is performed. Additional API is declared to create
and define gestures, which may consist of several strokes with an
offset.
2012-02-18 17:48:51 +01:00
Carlos Garnacho 5926e3cfec xi2: Use GDK_SOURCE_TOUCH for multitouch devices
Any device with XITouchValuatorClassInfo classes qualify as
multitouch now.
2012-02-18 17:48:50 +01:00
Carlos Garnacho e09d7b5456 gdk: Update touch events to latest spec 2012-02-18 17:48:50 +01:00
Carlos Garnacho c9b8f866df gtk-demo: Add multitouch demo
Pretty much ignores pointer events currently, so you're out
of luck to see anything happening at all if you don't have
a multitouch device...
2012-02-18 17:48:50 +01:00
Carlos Garnacho eea525ac89 Add multitouch-event signal and vfunc to GtkWidget 2012-02-18 17:48:50 +01:00
Carlos Garnacho 76e7a9e5b7 Add machinery to emit GdkEventMultiTouch events
These events are created from GDK_TOUCH_MOTION/PRESS/RELEASE
events, if the touch ID generating the event is within a
touch cluster, that event is stored and not pushed to the queue,
so a touch ID can only emit GDK_TOUCH_* or GDK_MULTITOUCH_* events
at the same time.
2012-02-18 17:48:50 +01:00
Carlos Garnacho 5a523e500b Define GdkEventMultiTouch and its related event types.
This event will gather all touches within a GdkTouchCluster,
including an array of the latest GDK_TOUCH_MOTION events for
the touch IDs contained in there.
2012-02-18 17:48:50 +01:00
Carlos Garnacho 33ea7850c9 Add gdk_window_[create|remove]_touch_cluster()
These are the functions to create/destroy a GdkTouchCluster,
as they are associated to GdkWindows.
2012-02-18 17:48:50 +01:00
Carlos Garnacho 9856a2faeb Introduce GdkTouchCluster
This is a per-window/device object to gather a group of touch IDs
as a single entity.
2012-02-18 17:48:50 +01:00
Carlos Garnacho a19c4b207c Add gdk_event_get_touch_id()
Just a helper function to get the touch ID from touch events, it
returns FALSE in any other case.
2012-02-18 17:48:50 +01:00
Carlos Garnacho 264739e6ad Make touch events go through csw/widget event handling.
In GtkWidget, touch events go through the same handler
than motion events, with the difference that touch_id
will have something meaningful there.

Touch events need to be explicitly selected, so if this
is enabled, the possibility of different motion streams
with different touch IDs must be handled in some way.
2012-02-18 17:48:50 +01:00
Carlos Garnacho 10b4eaa10b Handle TouchBegin/End events
These are translated into GDK_TOUCH_PRESS/RELEASE GdkEvents,
which use the GdkEventButton struct, a touch_id parameter
has been added there to cope with touches.
2012-02-18 17:48:50 +01:00
Carlos Garnacho be21cc53bc Add initial handling of TouchMotion events.
GdkDeviceManagerXI2 now handles TouchMotion and TouchMotionUnowned
events, which are translated to GDK_TOUCH_MOTION events.
2012-02-18 17:48:49 +01:00
Carlos Garnacho cda35f142f Add touch motion event type and mask.
These events' struct is the same than GdkEventMotion, which has been
added a touch_id parameter. The gdk_event_* functions have been modified
to also handle this event type.
2012-02-18 17:48:49 +01:00
Carlos Garnacho 5c98ea081e configure: Detect XInput 2.1 2012-02-18 17:48:49 +01:00
Carlos Garnacho 78866ca13d gdk,xi2: Add major/minor properties to XI2 device manager
This may be used to turn on/off the features that are added to
new XInput2 revisions.
2012-02-18 17:48:49 +01:00
Carlos Garnacho d4382300b4 gtk,range: Have slider jump to the pointer coordinates on touch devices
This widget is too narrow to make touch interaction tricky enough, so
don't add the penalty of having the slider run farther from the touch
coordinates if it happens to miss the slider.
2012-02-18 17:48:49 +01:00
Carlos Garnacho 7ef01e70f7 gtk,scrolledwindow: Ensure the view snaps back when overshooting
Instead of just stopping the acceleration source ID, check whether
it's overshooting, and let it snap back if needed after ::grab-notify
and ::button-release-event
2012-02-18 17:48:49 +01:00
Carlos Garnacho 10316b6ab2 gtk,scrolledwindow: set slower inverse acceleration on the overshoot area
This is so snapping back is more fluid and noticeable.
2012-02-18 17:48:49 +01:00
Carlos Garnacho e11d9904bc gtk,scrolledwindow: Improve initial velocity calculation
Velocity calculation has been refactored out of the captured motion events
handler, and also has more tolerance defore determining that a drag is actually
still.
2012-02-18 17:48:49 +01:00
Carlos Garnacho f1d33f76c7 gtk,menu: Don't popdown submenus on button release for touch devices
This is so submenus stay open as the parent menu item is pressed/released,
as the user would typically lift the finger in order to select a submenu
item.
2012-02-18 17:48:49 +01:00
Carlos Garnacho fe72a8192d gtk,settings: Deprecate gtk-touchscreen-mode
It's not used anywhere in GTK+ anymore.
2012-02-18 17:48:49 +01:00
Carlos Garnacho bf543ac8f4 gtk,range: Remove gtk-touchscreen-mode usage
Emulated crossing events with mode GDK_CROSSING_TOUCH_PRESS/RELEASE
already catter dinamically for the "don't prelight on touch devices"
usecase.
2012-02-18 17:48:49 +01:00
Carlos Garnacho 93f7a97f01 gtk,togglebutton: Remove gtk-touchcreen-mode usage
Emulated crossing events with mode GDK_CROSSING_TOUCH_PRESS/RELEASE
already catter dinamically for the "don't prelight on touch devices"
usecase.
2012-02-18 17:48:49 +01:00
Carlos Garnacho cc188dffa9 gtk,menushell: Remove gtk-touchscreen-mode usage
This usage in a keybinding signal is hardly related to touchscreens,
so just remove it.
2012-02-18 17:48:49 +01:00
Carlos Garnacho 33e3b9ddbb gtk,menu: Remove gtk-touchscreen-mode from scrolling code
scrolling is handled via ::captured-event dynamically, so remove
this now unused code.
2012-02-18 17:48:49 +01:00
Carlos Garnacho 8c4b0b66b8 gtk,menu: Select the first item for touch devices
This was done through gtk-touchscreen-mode, now is handled
dynamically on the current event source device.
2012-02-18 17:48:49 +01:00
Carlos Garnacho 2781c9544b gtk,menu: Implement scrolling through ::captured-event for touch devices
This makes overflown menus scrollable via direct manipulation. Once past
the threshold, the item below the pointer is unselected and scrolling
starts.
2012-02-18 17:48:48 +01:00
Carlos Garnacho 4765dde5cf gtk,menu: handle item selection for touch devices dynamically
Instead of using gtk-touchscreen-mode, the behavior changes depending
on the source device in use.
2012-02-18 17:48:48 +01:00
Carlos Garnacho 658b0e8092 gtk,textview: Pop up context menu on press-and-hold 2012-02-18 17:48:48 +01:00
Carlos Garnacho 0e5d05f8e0 gtk,textview: Also cancel DnD on ::grab-notify
If is about to start when the drag device is grabbed
somewhere else, unset drag start x/y so DnD doesn't
happen anyway.
2012-02-18 17:48:48 +01:00
Carlos Garnacho f1570c5c92 gtk,entry: Pop up menu on press-and-hold 2012-02-18 17:48:48 +01:00
Carlos Garnacho e042c53caa gtk,label: Pop up menu on press-and-hold 2012-02-18 17:48:48 +01:00
Carlos Garnacho a7b6859561 xi2: Get the effective group state by ORing the XIGroupState values 2012-02-18 17:48:48 +01:00
Carlos Garnacho 14235c9c17 gdk: Ensure that GdkPointerWindowInfo is only generated for pointers 2012-02-18 17:48:48 +01:00
Carlos Garnacho 6a6bed8e04 gtk: Only set widget under device on non-virtual crossing events
_gtk_widget_set_device_window() is suppose to make accounting of
the topmost widget under the device at each time, so avoid setting
it on virtual crossing events as the device is already in another
window.
2012-02-18 17:48:48 +01:00
Carlos Garnacho 7a07995944 gtk,scrolledwindow: remove scrollbars auto-hide
This is in a really sorry state, and scrollbars need to be
thought out for the new design anyway.
2012-02-18 17:48:48 +01:00
Carlos Garnacho 62afe5cda6 gtk,scrolledwindow: capture crossing events when dragging
Also, instead of connecting 2 more times to ::captured-event,
have it all go through a single handler.
2012-02-18 17:48:48 +01:00
Carlos Garnacho 95f6b7fa82 gtk,scrolledwindow: Add GtkKineticScrollingFlags
gtk_scrolled_window_set_kinetic_scrolling() now takes a set of flags,
GTK_KINETIC_SCROLLING_CAPTURE_BUTTON_PRESS makes the "capture button
press and maybe replay later" vs "let button press go through, but
trust in ::grab-broken to undo things" an opt-in, by default that
flag is set, which is the most conservative approach.
2012-02-18 17:48:48 +01:00
Carlos Garnacho ae2438df90 gtk,scrolledwindow: Grab only after starting drag
This is so the grab doesn't break the implicit grab on the
child widget's window, which avoids that the button press and
release are possibly sent to different windows, and after the
grab was actually broken.
2012-02-18 17:48:47 +01:00
Carlos Garnacho fafb78e38e gdk: Generate crossing events around touch devices' press/release
Anytime a touch device interacts, the crossing events generation
will change to a touch mode where only events with mode
GDK_CROSSING_TOUCH_PRESS/RELEASE are handled, and those are sent
around button press/release. Those are virtual as the master
device may still stay on the window.

Whenever there is a switch of slave device (the user starts
using another non-touch device), a crossing event with mode
GDK_CROSSING_DEVICE_SWITCH may generated if needed, and the normal
crossing event handling is resumed.
2012-02-18 17:48:47 +01:00
Carlos Garnacho 4068758bb8 gdk: Keep track of the last slave device used
This last slave device (stored per master) is used to fill
in the missing slave device in synthesized crossing events
not directly caused by a device event (ie. due to configure
events or grabs)
2012-02-18 17:48:47 +01:00
Carlos Garnacho bb87595220 gtk,tooltips: Use the source device instead of gtk-touchscreen-mode
This makes tooltips behavior dynamic based on the interacting device.
2012-02-18 17:48:47 +01:00
Carlos Garnacho 001a1517da gtk,scrolledwindow: Clamp early overshooting when snapping back
Fixes the situation where overshooting in scrolled windows with a
small viewport could end up overshooting right to the opposite
side, and then back again indefinitely.
2012-02-18 17:48:47 +01:00
Carlos Garnacho 76b285f475 gtk: Handle motion hints for ::captured-event
Request automatically more motion events in behalf of
the original widget if it listens to motion hints. So
the capturing widget doesn't need to handle such
implementation details.
2012-02-18 17:48:47 +01:00
Carlos Garnacho 0b796299c1 gtk,scrolledwindow: capture motions until the kinetic scrolling cancellation
If the widget which events are captured listens to motion hints, there
are situations where neither the scrolled window or the child widget
request more motions.
2012-02-18 17:48:47 +01:00
Carlos Garnacho 5d2af3e13d gtk,pah: Show a bigger press-and-hold indicator on touchscreens
On touch devices it uses a 2.5cm diameter via gdk_screen_get_width_mm()
in the hope that it'll pop out of the finger area.
2012-02-18 17:48:47 +01:00
Carlos Garnacho 15d2f1697d gtk,scrolledwindow: Unset dragging device on ::grab-notify
The child widget may still call gtk_(device_)grab_add, which left
the scrolled window in an inconsistent state.
2012-02-18 17:48:47 +01:00
Carlos Garnacho 460a54ce15 gtk,scrolledwindow: Rework physics behind kinetic scrolling
The maths being used didn't resemble much about velocities or
friction/deceleration, so reimplement it in terms of velocity
vectors and decelerations, measured in pixels/ms^2.

Overshooting is also handled within the deceleration effect,
turning into a constant acceleration vector in the opposite
direction so it returns elastically within the boundaries.
2012-02-18 17:48:47 +01:00
Carlos Garnacho 3ab0ccf643 gtk,scrolledwindow: Implement overshooting
An extra GdkWindow has been added, this window is the parent
of the child widget, and is the one getting resized/moved when
overshooting.

The unclamped adjustments' values are also stored in
GtkScrolledWindowPrivate as a separate value, overshooting is
pretty specific to GtkScrolledWindow and it isn't worth to
expose API in GtkAlignment for this single purpose.

This method allows GtkScrollable children to be blissfully
unaware of overshooting, as otherwise they'd have to handle
rather odd adjustment values themselves.
2012-02-18 17:48:47 +01:00
Carlos Garnacho cebd79e7eb gtk,scrolledwindow: Add window for overshooting
This window is the child widget's parent window, and will
be the one that's moved when overshooting.
2012-02-18 17:48:47 +01:00
Carlos Garnacho 4e321365cf scrolledwindow: Handle nested scrolled windows in kinetic scrolling
The innermost scrolled window always gets to capture the events, all
scrolled windows above it just let the event go through. Ideally
reaching a limit on the innermost scrolled window would propagate
the dragging up the hierarchy in order to keep following the touch
coords, although that'd involve rather evil hacks just to cater
for broken UIs.
2012-02-18 17:48:47 +01:00
Carlos Garnacho 1b088606f7 gtk: Add event storing/replaying to GtkWidget::captured-event
It now returns a GtkCapturedEventFlags which tells whether the
widget captured the event, and additionally whether the event
should be stored for later replay.

gtk_widget_release_captured_events() has been added too so
all stored events are released onto the widget that was initially
to receive the events.
2012-02-18 17:48:46 +01:00
Carlos Garnacho 7bf410f927 scrolledwindow: Use event times when calculating deceleration
Using g_get_current_time() isn't going to be realistic on lagging
events.
2012-02-18 17:48:46 +01:00
Carlos Garnacho 5476f09d35 scrolledwindow: bypass kinetic scrolling if no scrollbars are shown 2012-02-18 17:48:46 +01:00
Carlos Garnacho a49c9a27fb scrolledwindow: Remove priv->event_window
It looks like a leftover from pre-captured-event iterations of
the patch, it is completely unnecessary now.
2012-02-18 17:48:46 +01:00
Carlos Garnacho e5a3cf5a36 scrolledwindow: Don't use p-a-h for the "let event go through" timeout
Just use a timeout there, the press-and-hold feedback is undesirable
here.
2012-02-18 17:48:46 +01:00
Carlos Garnacho 59a0f62079 gtk,pah: Hook directly into gtk_main_do_event()
Press and hold couldn't reasonably work if nested widgets
handle ::captured-event, once the widget inits press-and-hold,
it'd better also handle possible cancellation on motion and
button release, which isn't guaranteed with ::capture-event.

Also, tentatively start press-and-hold by default on the
grab_widget, and before event capturing happens, this avoids
awkward situations like the scrolled window preventing/delaying
press-and-hold to happen on the child textview for example.
2012-02-18 17:48:46 +01:00
Carlos Garnacho fbbd6b2c2e scrolledwindow: Enable kinetic scrolling by default 2012-02-18 17:48:46 +01:00
Carlos Garnacho d77977aa62 scrolledwindow: add another shortcut to bypass event capture
When clicked again close to the previous button press location
(assuming it had ~0 movement), the scrolled window wil allow
the child to handle the events immediately.

This is so the user doesn't have to wait to the p-a-h timeout
in order to operate on the scrolledwindow child.
2012-02-18 17:48:46 +01:00
Carlos Garnacho da35350ee3 scrolledwindow: Only do kinetic scrolling on touch devices
This is sort of meaningless on pointer devices, besides it implies
a different input event handling on child widgets that's unnecessary
there.
2012-02-18 17:48:46 +01:00
Carlos Garnacho 40996af9c1 scrolledwindow: Don't crash on 0-sized motion buffer 2012-02-18 17:48:46 +01:00
Carlos Garnacho 7e29b38bd4 scrolledwindow: Set also a GTK+ grab on p-a-h scrolling
This is so the widget is ensured to receive the events
regardless of the pointer position.
2012-02-18 17:48:46 +01:00
Carlos Garnacho c1b86a5eab scrolledwindow: Add GdkDevice parameter to ::press-and-hold handler 2012-02-18 17:48:46 +01:00
Carlos Garnacho 68587c203c gtk: Handle press-and-hold for touch devices
Also, only react to the first button
2012-02-18 17:48:46 +01:00
Carlos Garnacho 80e08d1b1b gtk: Add a GdkDevice parameter to ::press-and-hold
This would be useful when popping up menus, just so we
know what device to trigger it for.
2012-02-18 17:48:46 +01:00
Carlos Garnacho 0a05318277 gtk: Clean up press-and-hold code
The press and hold animation now fully relies on style context
transitions, finishing the p-a-h operation right after it
finishes. There's also no need to connect to ::drag-begin as
::grab-notify will also tell when a grab begins.
2012-02-18 17:48:45 +01:00
Carlos Garnacho 5c187245a4 tests: Add an entry to testpressandhold
Useful for checking behavior while selecting,
starting drags, subwindows...
2012-02-18 17:48:45 +01:00
Carlos Garnacho 739059cc91 entry: Handle ::grab-notify
Store the device, and unset private fields whenever the device
is shadowed by another GTK+ grab, so popping up menus while
selecting (i.e. press-and-hold) doesn't leave it in a confused
state.
2012-02-18 17:48:45 +01:00
Carlos Garnacho 86e966b944 gtk: Do not use deprecated APIs on press-and-hold 2012-02-18 17:48:45 +01:00
Carlos Garnacho ef08d3975c tests: Update testkineticscrolling to use GtkGrid 2012-02-18 17:48:45 +01:00
Carlos Garnacho c50007b33a gtk: connect to grab-notify for press and hold
This is so press and hold is cancelled if a click actually
causes an inner widget to do a GTK+ grab.
2012-02-18 17:48:45 +01:00
Carlos Garnacho 9f20e16683 gtk: Propagate ::captured-event up the hierarchy for crossing events 2012-02-18 17:48:45 +01:00
Carlos Garnacho 4f2ce2f7b6 gtk: emit ::captured-event starting from the GTK grab widget 2012-02-18 17:48:45 +01:00
Carlos Garcia Campos a404a56254 scrolledwindow: Allow selections and drag-and-drop when kinetic scrolling is enabled
If the scrolling doesn't start after a long press, the scrolling is
cancelled and events are handled by child widget normally.
2012-02-18 17:48:45 +01:00
Carlos Garcia Campos 7a59b443ee Add GtkWidget::press-and-hold signal
Press-and-hold signal is emitted when the mouse button is pressed for a
given amount of time, specified in the new "press-and-hold-timeout"
GtkSetting. It's commonly used in mobile platforms to emulate a right
click to show a context menu. This patch is based on previous patches by
Kristian Rietveld and Danielle Madeley.

https://bugzilla.gnome.org/show_bug.cgi?id=315645
2012-02-18 17:48:45 +01:00
Carlos Garnacho 127bfd71d7 gtksettings: Set animation for press-and-hold through GtkStyleProvider
The "gtk-press-and-hold-timeout" setting has also been added, to control
its duration.
2012-02-18 17:48:45 +01:00
Carlos Garnacho 35341511b9 themingengine: Implement press-and-hold notification renderer
gtk_render_activity() uses it for the GTK_STYLE_CLASS_PRESS_AND_HOLD
style class.
2012-02-18 17:48:45 +01:00
Carlos Garcia Campos 65888ac515 scrolledwindow: Add auto-hide-scrollbars style property
To hide the scrollbars in kinetic mode when not scrolling.
2012-02-18 17:48:45 +01:00
Carlos Garcia Campos 313f51d1f8 tests: Add new test for kinetic scrolling 2012-02-18 17:48:44 +01:00
Carlos Garcia Campos 5ddf0f4e5f test: Add checkbox to enable/disable kinetic scrolling in scrolled window test 2012-02-18 17:48:44 +01:00
Carlos Garcia Campos 5e726db14e scrolledwindow: Initial kinetic scrolling support 2012-02-18 17:48:44 +01:00
Carlos Garcia Campos 705dde471f timeline: Add _gtk_timeline_get_elapsed_time()
To get the time in milliseconds since the last frame
2012-02-18 17:48:44 +01:00
Carlos Garcia Campos d83640dc34 Add GtkWidget::captured-event signal
https://bugzilla.gnome.org/show_bug.cgi?id=641836
2012-02-18 17:48:44 +01:00
Carlos Garnacho ed20c3255b gdk: Add GDK_SOURCE_TOUCH
This device source applies to touch capable devices, most
notably touchscreens.
2012-02-18 17:48:44 +01:00
76 changed files with 9403 additions and 749 deletions
+5 -1
View File
@@ -935,7 +935,7 @@ if test "x$enable_x11_backend" = xyes; then
have_base_x_pc=true
X_PACKAGES="$X_PACKAGES x11 xext"
x_libs="`$PKG_CONFIG --libs x11 xext`"
X_CFLAGS="`$PKG_CONFIG --cflags x11 xext`"
X_CFLAGS="`$PKG_CONFIG --cflags x11 xext` -DXINPUT2_1_USE_UNSTABLE_PROTOCOL -DXINPUT2_2_USE_UNSTABLE_PROTOCOL"
# Strip out any .la files that pkg-config might give us (this happens
# with -uninstalled.pc files)
@@ -1126,6 +1126,10 @@ if test "x$enable_x11_backend" = xyes; then
AC_DEFINE(XINPUT_2, 1, [Define to 1 if XInput 2.0 is available]),
X_EXTENSIONS="$X_EXTENSIONS XInput")
gtk_save_LIBS="$LIBS"
LIBS="$LIBS -lXi"
AC_CHECK_FUNC(XIAllowTouchEvents, AC_DEFINE(XINPUT_2_2, 1, [Define to 1 if XInput 2.2 is available]))
LIBS="$gtk_save_LIBS"
else
AC_DEFINE(XINPUT_NONE, 1,
[Define to 1 if no XInput should be used])
+1
View File
@@ -29,6 +29,7 @@ demos = \
links.c \
list_store.c \
menus.c \
multitouch.c \
offscreen_window.c \
offscreen_window2.c \
overlay.c \
+698
View File
@@ -0,0 +1,698 @@
/* Multitouch
*
* Demonstrates some general multitouch event handling,
* using GdkTouchCluster in order to get grouped motion
* events for the touches within a cluster. Each of the
* created rectangles has one of those GdkTouchCluster
* objects.
*
* Touch events are also enabled on additional widgets,
* enabling simultaneous touch interaction on those. Not
* all widgets are prepared for multitouch interaction,
* as there are constraints that not all widgets may
* apply to.
*/
#include <math.h>
#include <gtk/gtk.h>
#include "demo-common.h"
#define RECT_BORDER_WIDTH 6
static GtkWidget *window = NULL;
static GtkWidget *area = NULL;
static GtkWidget *red = NULL;
static GtkWidget *green = NULL;
static GtkWidget *blue = NULL;
static GtkWidget *alpha = NULL;
static GQueue *shapes = NULL;
typedef struct {
GdkTouchCluster *cluster;
GdkRGBA color;
gdouble angle;
gdouble zoom;
gdouble center_x;
gdouble center_y;
gdouble x;
gdouble y;
gdouble width;
gdouble height;
gdouble base_zoom;
gdouble base_angle;
gdouble initial_distance;
gdouble initial_angle;
GdkPoint points[4];
} ShapeInfo;
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
shape_info_allocate_input_rect (ShapeInfo *info)
{
gint width, height, i;
width = info->width;
height = info->height;
/* Top/left */
info->points[0].x = info->x - info->center_x;
info->points[0].y = info->y - info->center_y;
/* Top/right */
info->points[1].x = info->x - info->center_x + width;
info->points[1].y = info->y - info->center_y;
/* Bottom/right */
info->points[2].x = info->x - info->center_x + width;
info->points[2].y = info->y - info->center_y + height;
/* Bottom/left */
info->points[3].x = info->x - info->center_x;
info->points[3].y = info->y - info->center_y + height;
for (i = 0; i < 4; i++)
{
gdouble ret_x, ret_y;
calculate_rotated_point (info->angle,
info->zoom,
info->x,
info->y,
(gdouble) info->points[i].x,
(gdouble) info->points[i].y,
&ret_x,
&ret_y);
info->points[i].x = (gint) ret_x;
info->points[i].y = (gint) ret_y;
}
}
static void
shape_info_bounding_rect (ShapeInfo *info,
GdkRectangle *rect)
{
gint i, left, right, top, bottom;
left = top = G_MAXINT;
right = bottom = 0;
for (i = 0; i < 4; i++)
{
if (info->points[i].x < left)
left = info->points[i].x;
if (info->points[i].x > right)
right = info->points[i].x;
if (info->points[i].y < top)
top = info->points[i].y;
if (info->points[i].y > bottom)
bottom = info->points[i].y;
}
rect->x = left - 20;
rect->y = top - 20;
rect->width = right - left + 40;
rect->height = bottom - top + 40;
}
static gboolean
shape_info_point_in (ShapeInfo *info,
gint x,
gint y)
{
GdkPoint *left, *right, *top, *bottom;
gint i;
left = right = top = bottom = NULL;
for (i = 0; i < 4; i++)
{
GdkPoint *p = &info->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 = &info->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 ShapeInfo *
shape_info_new (gdouble x,
gdouble y,
gdouble width,
gdouble height,
GdkRGBA *color)
{
ShapeInfo *info;
info = g_slice_new0 (ShapeInfo);
info->cluster = NULL;
info->color = *color;
info->x = x;
info->y = y;
info->width = width;
info->height = height;
info->angle = 0;
info->zoom = 1;
info->base_zoom = 1;
info->base_angle = 0;
info->initial_distance = 0;
info->initial_angle = 0;
shape_info_allocate_input_rect (info);
g_queue_push_tail (shapes, info);
return info;
}
static void
shape_info_free (ShapeInfo *info)
{
g_slice_free (ShapeInfo, info);
}
static void
shape_info_draw (cairo_t *cr,
ShapeInfo *info)
{
cairo_save (cr);
cairo_translate (cr,
info->points[0].x + RECT_BORDER_WIDTH / 2,
info->points[0].y + RECT_BORDER_WIDTH / 2);
cairo_scale (cr, info->zoom, info->zoom);
cairo_rotate (cr, info->angle);
cairo_rectangle (cr, 0, 0,
info->width - RECT_BORDER_WIDTH,
info->height - RECT_BORDER_WIDTH);
gdk_cairo_set_source_rgba (cr, &info->color);
cairo_fill_preserve (cr);
cairo_set_line_width (cr, RECT_BORDER_WIDTH);
cairo_set_source_rgb (cr, 0, 0, 0);
cairo_stroke (cr);
cairo_restore (cr);
}
static void
shape_update_scales (ShapeInfo *info)
{
gtk_range_set_value (GTK_RANGE (red), info->color.red);
gtk_range_set_value (GTK_RANGE (green), info->color.green);
gtk_range_set_value (GTK_RANGE (blue), info->color.blue);
gtk_range_set_value (GTK_RANGE (alpha), info->color.alpha);
}
static void
range_value_changed_cb (GtkRange *range,
gpointer user_data)
{
GtkWidget *widget;
GdkRectangle rect;
ShapeInfo *shape;
gdouble value;
widget = GTK_WIDGET (range);
shape = g_queue_peek_head (shapes);
if (!shape)
return;
value = gtk_range_get_value (range);
if (widget == red)
shape->color.red = value;
else if (widget == green)
shape->color.green = value;
else if (widget == blue)
shape->color.blue = value;
else if (widget == alpha)
shape->color.alpha = value;
shape_info_bounding_rect (shape, &rect);
gdk_window_invalidate_rect (gtk_widget_get_window (area),
&rect, FALSE);
}
static gboolean
draw_cb (GtkWidget *widget,
cairo_t *cr,
gpointer user_data)
{
GList *l;
cairo_save (cr);
cairo_set_source_rgb (cr, 1, 1, 1);
cairo_paint (cr);
for (l = shapes->tail; l; l = l->prev)
shape_info_draw (cr, l->data);
cairo_restore (cr);
return FALSE;
}
static gboolean
button_press_cb (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
ShapeInfo *shape = NULL;
guint touch_id;
if (gdk_event_get_touch_id (event, &touch_id))
{
GList *l;
for (l = shapes->tail; l; l = l->prev)
{
ShapeInfo *info = l->data;
if (shape_info_point_in (info,
(gint) event->button.x,
(gint) event->button.y))
shape = info;
}
if (!shape)
return FALSE;
/* Put on top */
g_queue_remove (shapes, shape);
g_queue_push_head (shapes, shape);
shape_update_scales (shape);
if (!shape->cluster)
shape->cluster = gdk_window_create_touch_cluster (gtk_widget_get_window (widget),
gdk_event_get_device (event));
else if (gdk_touch_cluster_get_n_touches (shape->cluster) == 0)
{
/* Only change cluster device if there were no touches */
gdk_touch_cluster_set_device (shape->cluster,
gdk_event_get_device (event));
}
gdk_touch_cluster_add_touch (shape->cluster, touch_id);
return TRUE;
}
return FALSE;
}
static gboolean
multitouch_cb (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
ShapeInfo *info = NULL;
gboolean new_center = FALSE;
gboolean new_position = FALSE;
gdouble event_x, event_y;
cairo_region_t *region;
GdkRectangle rect;
GList *l;
for (l = shapes->head; l; l = l->next)
{
ShapeInfo *shape = l->data;
if (event->multitouch.group == shape->cluster)
{
info = shape;
break;
}
}
if (!info)
return FALSE;
shape_info_bounding_rect (info, &rect);
region = cairo_region_create_rectangle ((cairo_rectangle_int_t *) &rect);
if (event->multitouch.n_events == 1)
{
/* Update center if we just got to
* this situation from either way */
if (event->type == GDK_MULTITOUCH_ADDED ||
event->type == GDK_MULTITOUCH_REMOVED)
new_center = TRUE;
event_x = event->multitouch.events[0]->x;
event_y = event->multitouch.events[0]->y;
new_position = TRUE;
}
else if (event->multitouch.n_events == 2)
{
gdouble distance, angle;
gdk_events_get_center ((GdkEvent *) event->multitouch.events[0],
(GdkEvent *) event->multitouch.events[1],
&event_x, &event_y);
gdk_events_get_distance ((GdkEvent *) event->multitouch.events[0],
(GdkEvent *) event->multitouch.events[1],
&distance);
gdk_events_get_angle ((GdkEvent *) event->multitouch.events[0],
(GdkEvent *) event->multitouch.events[1],
&angle);
if (event->type == GDK_MULTITOUCH_ADDED)
{
/* Second touch was just added, update base zoom/angle */
info->base_zoom = info->zoom;
info->base_angle = info->angle;
info->initial_angle = angle;
info->initial_distance = distance;
new_center = TRUE;
}
info->zoom = MAX (info->base_zoom * (distance / info->initial_distance), 1.0);
info->angle = info->base_angle + (angle - info->initial_angle);
new_position = TRUE;
}
if (new_center)
{
gdouble origin_x, origin_y;
origin_x = info->x - info->center_x;
origin_y = info->y - info->center_y;
calculate_rotated_point (- info->angle,
1 / info->zoom,
info->x - origin_x,
info->y - origin_y,
event_x - origin_x,
event_y - origin_y,
&info->center_x,
&info->center_y);
}
if (new_position)
{
info->x = event_x;
info->y = event_y;
}
shape_info_allocate_input_rect (info);
shape_info_bounding_rect (info, &rect);
cairo_region_union_rectangle (region, (cairo_rectangle_int_t *) &rect);
gdk_window_invalidate_region (gtk_widget_get_window (widget), region, FALSE);
return TRUE;
}
static void
window_destroyed_cb (GtkWidget *widget,
gpointer user_data)
{
g_queue_foreach (shapes, (GFunc) shape_info_free, NULL);
g_queue_free (shapes);
shapes = NULL;
window = NULL;
}
static void
new_rectangle_clicked_cb (GtkButton *button,
gpointer user_data)
{
GdkRectangle rect;
ShapeInfo *info;
GdkRGBA color;
color.red = color.green = color.blue = color.alpha = 0.5;
info = shape_info_new (0, 0, 100, 150, &color);
shape_info_bounding_rect (info, &rect);
gdk_window_invalidate_rect (gtk_widget_get_window (area), &rect, FALSE);
}
GtkWidget *
create_drawing_area (void)
{
area = gtk_drawing_area_new ();
gtk_widget_add_events (area,
GDK_TOUCH_MASK |
GDK_POINTER_MOTION_MASK |
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK);
gtk_widget_set_size_request (area, 600, 600);
g_signal_connect (area, "draw",
G_CALLBACK (draw_cb), NULL);
g_signal_connect (area, "button-press-event",
G_CALLBACK (button_press_cb), NULL);
g_signal_connect (area, "multitouch-event",
G_CALLBACK (multitouch_cb), NULL);
return area;
}
GtkWidget *
create_scale (void)
{
GtkWidget *scale;
scale = gtk_scale_new_with_range (GTK_ORIENTATION_VERTICAL, 0, 1, 0.01);
gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
gtk_widget_set_vexpand (scale, TRUE);
gtk_widget_set_margin_left (scale, 15);
gtk_widget_set_margin_right (scale, 15);
gtk_widget_add_events (scale, GDK_TOUCH_MASK);
g_signal_connect (scale, "value-changed",
G_CALLBACK (range_value_changed_cb), NULL);
return scale;
}
GtkWidget *
create_window (void)
{
GtkWidget *grid, *label, *button;
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Multitouch demo");
g_signal_connect (window, "destroy",
G_CALLBACK (window_destroyed_cb), NULL);
grid = gtk_grid_new ();
gtk_container_add (GTK_CONTAINER (window), grid);
area = create_drawing_area ();
gtk_grid_attach (GTK_GRID (grid),
area, 0, 0, 1, 3);
gtk_widget_set_hexpand (area, TRUE);
gtk_widget_set_vexpand (area, TRUE);
/* "red" label/scale */
label = gtk_label_new ("Red");
gtk_widget_set_vexpand (label, FALSE);
gtk_grid_attach (GTK_GRID (grid),
label, 1, 0, 1, 1);
red = create_scale ();
gtk_grid_attach (GTK_GRID (grid),
red, 1, 1, 1, 1);
/* "green" label/scale */
label = gtk_label_new ("Green");
gtk_widget_set_vexpand (label, FALSE);
gtk_grid_attach (GTK_GRID (grid),
label, 2, 0, 1, 1);
green = create_scale ();
gtk_grid_attach (GTK_GRID (grid),
green, 2, 1, 1, 1);
/* "blue" label/scale */
label = gtk_label_new ("Blue");
gtk_widget_set_vexpand (label, FALSE);
gtk_grid_attach (GTK_GRID (grid),
label, 3, 0, 1, 1);
blue = create_scale ();
gtk_grid_attach (GTK_GRID (grid),
blue, 3, 1, 1, 1);
/* "alpha" label/scale */
label = gtk_label_new ("Alpha");
gtk_widget_set_vexpand (label, FALSE);
gtk_grid_attach (GTK_GRID (grid),
label, 4, 0, 1, 1);
alpha = create_scale ();
gtk_grid_attach (GTK_GRID (grid),
alpha, 4, 1, 1, 1);
/* button */
button = gtk_button_new_from_stock (GTK_STOCK_NEW);
gtk_widget_add_events (button, GDK_TOUCH_MASK);
gtk_grid_attach (GTK_GRID (grid),
button, 1, 2, 4, 1);
gtk_widget_set_vexpand (button, FALSE);
g_signal_connect (button, "clicked",
G_CALLBACK (new_rectangle_clicked_cb), NULL);
gtk_widget_show_all (grid);
return window;
}
GtkWidget *
do_multitouch (GtkWidget *do_widget)
{
if (!shapes)
shapes = g_queue_new ();
if (!window)
window = create_window ();
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
{
gtk_widget_destroy (window);
window = NULL;
g_queue_foreach (shapes, (GFunc) shape_info_free, NULL);
g_queue_free (shapes);
shapes = NULL;
}
return window;
}
+1
View File
@@ -33,6 +33,7 @@
<xi:include href="xml/windows.xml" />
<xi:include href="xml/events.xml" />
<xi:include href="xml/event_structs.xml" />
<xi:include href="xml/touchcluster.xml" />
<xi:include href="xml/keys.xml" />
<xi:include href="xml/selections.xml" />
<xi:include href="xml/dnd.xml" />
+26
View File
@@ -781,11 +781,13 @@ gdk_event_get_root_coords
gdk_event_get_scroll_direction
gdk_event_get_state
gdk_event_get_time
gdk_event_get_touch_id
gdk_event_request_motions
gdk_events_get_angle
gdk_events_get_center
gdk_events_get_distance
gdk_event_triggers_context_menu
gdk_event_get_touch_id
<SUBSECTION>
gdk_event_handler_set
@@ -833,6 +835,7 @@ GdkEventWindowState
GdkEventSetting
GdkEventOwnerChange
GdkEventGrabBroken
GdkEventMultiTouch
<SUBSECTION>
GdkScrollDirection
@@ -860,6 +863,29 @@ gdk_event_get_type
gdk_owner_change_get_type
</SECTION>
<SECTION>
<TITLE>Multitouch</TITLE>
<FILE>touchcluster</FILE>
GdkTouchCluster
gdk_touch_cluster_add_touch
gdk_touch_cluster_remove_touch
gdk_touch_cluster_remove_all
gdk_touch_cluster_set_device
gdk_touch_cluster_get_device
gdk_touch_cluster_get_touches
<SUBSECTION>
gdk_window_create_touch_cluster
gdk_window_remove_touch_cluster
<SUBSECTION Standard>
GDK_TYPE_TOUCH_CLUSTER
<SUBSECTION Private>
gdk_touch_cluster_get_type
</SECTION>
<SECTION>
<TITLE>Cursors</TITLE>
<FILE>cursors</FILE>
+1
View File
@@ -9,5 +9,6 @@ gdk_display_manager_get_type
gdk_drag_context_get_type
gdk_keymap_get_type
gdk_screen_get_type
gdk_touch_cluster_get_type
gdk_visual_get_type
gdk_window_get_type
+2
View File
@@ -119,6 +119,7 @@ content_files = \
running.sgml \
building.sgml \
compiling.sgml \
device-interaction-patterns.xml \
drawing-model.xml \
glossary.xml \
migrating-2to3.xml \
@@ -142,6 +143,7 @@ content_files = \
overview.xml
expand_content_files = \
device-interaction-patterns.xml \
drawing-model.xml \
getting_started.xml \
glossary.xml \
@@ -0,0 +1,578 @@
<?xml version="1.0"?>
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
"http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" [
]>
<chapter id="gtk-device-interaction-patterns">
<title>Multitouch and other device interaction patterns</title>
<para>
Depending on the platform, GTK+ is able to handle a wide range of input
devices. Those are offered to applications in a 2-level hierarchy, with
virtual devices (or master devices) representing the visual cursors
displayed in the screen, which are each controlled by a number of physical
devices (or slave devices). Those devices can respectively be retrieved
from an input event with gdk_event_get_device() and
gdk_event_get_source_device().
</para>
<para>
In X11, GTK+ uses XInput2 for input events, which caters for a fully dynamic
device hierarchy, and support for multiple virtual pointer/keyboard pairs.
</para>
<example>
<title>Listing and modifying the device hierarchy</title>
<programlisting>
carlos@sacarino:~$ xinput list
⎡ Virtual core pointer id=2 [master pointer (3)]
⎜ ↳ Virtual core XTEST pointer id=4 [slave pointer (2)]
⎜ ↳ Wacom ISDv4 E6 Pen stylus id=10 [slave pointer (2)]
⎜ ↳ Wacom ISDv4 E6 Finger touch id=11 [slave pointer (2)]
⎜ ↳ SynPS/2 Synaptics TouchPad id=13 [slave pointer (2)]
⎜ ↳ TPPS/2 IBM TrackPoint id=14 [slave pointer (2)]
⎜ ↳ Wacom ISDv4 E6 Pen eraser id=16 [slave pointer (2)]
⎣ Virtual core keyboard id=3 [master keyboard (2)]
↳ Virtual core XTEST keyboard id=5 [slave keyboard (3)]
↳ Power Button id=6 [slave keyboard (3)]
↳ Video Bus id=7 [slave keyboard (3)]
↳ Sleep Button id=8 [slave keyboard (3)]
↳ Integrated Camera id=9 [slave keyboard (3)]
↳ AT Translated Set 2 keyboard id=12 [slave keyboard (3)]
↳ ThinkPad Extra Buttons id=15 [slave keyboard (3)]
carlos@sacarino:~$ xinput create-master eek
carlos@sacarino:~$ xinput list
...
⎡ eek pointer id=17 [master pointer (18)]
⎜ ↳ eek XTEST pointer id=19 [slave pointer (17)]
⎣ eek keyboard id=18 [master keyboard (17)]
↳ eek XTEST keyboard id=20 [slave keyboard (18)]
carlos@sacarino:~$ xinput reattach 10 17
carlos@sacarino:~$ xinput list
...
⎡ eek pointer id=17 [master pointer (18)]
⎜ ↳ Wacom ISDv4 E6 Pen stylus id=10 [slave pointer (17)]
⎜ ↳ eek XTEST pointer id=19 [slave pointer (17)]
⎣ eek keyboard id=18 [master keyboard (17)]
↳ eek XTEST keyboard id=20 [slave keyboard (18)]
</programlisting>
</example>
<para>
Anytime a virtual device is added or removed, or a physical device
is attached to another virtual device, or left floating (detached
from any virtual device), #GdkDeviceManager will emit the corresponding
#GdkDeviceManager::device-added, #GdkDeviceManager::device-removed, or
#GdkDeviceManager::device-changed signals.
</para>
<section id="gtk-device-patterns-client-pointer">
<title>The client pointer</title>
<para>
In X11, Under the presence of multiple virtual pointers, GDK and XInput2
use the "client pointer" principle to allow several legacy applications
to interact simultaneously with different virtual pointer/keyboard pairs,
it would be usually set by the window manager for a focused window, so
different application windows could operate on different client pointers.
gdk_device_manager_get_client_pointer() may be called to get the client
pointer #GdkDevice
</para>
<para>
Under the hood, X11 uses the client pointer (or its paired keyboard) to
satisfy core calls such as XGrabPointer/Keyboard, XQueryPointer and
others that have been superseded by XInput2.
</para>
<para>
In platforms without multidevice features, gdk_device_manager_get_client_pointer()
will return the only virtual pointer available.
</para>
</section>
<section id="gtk-device-patterns-simple">
<title>Simple device handling</title>
<para>
There are applications that could have little gain in knowing about
multiple devices, although there are situations where a device could
be needed (i.e. popping up a menu on the pointer coordinates).
</para>
<para>
For such applications, the client pointer may be a good enough
approximation for these operations. Under the presence of multiple
device pairs, this gives a behavior that is most similar to that
of legacy applications (i.e. gtk+2).
</para>
<example>
<title>Getting the client pointer and keyboard</title>
<programlisting>
GdkDisplay *display;
GdkDeviceManager *device_manager;
GdkDevice *client_pointer, client_keyboard;
display = gdk_display_get_default ();
device_manager = gdk_display_get_device_manager (display);
client_pointer = gdk_device_manager_get_client_pointer (device_manager);
/* Or if we need a keyboard too */
client_keyboard = gdk_device_get_associated_device (client_pointer);
</programlisting>
</example>
</section>
<section id="gtk-device-patterns-multiple-devices">
<title>Dealing with multiple devices</title>
<para>
There may be several usecases to deal with multiple devices, including,
but not limited to:
</para>
<orderedlist>
<listitem>
Retrieving advanced information from an input device: i.e. stylus pressure/tilt
in drawing applications.
</listitem>
<listitem>
Receiving events from a dedicated input device: i.e. joysticks in games.
</listitem>
<listitem>
Collaborative interfaces, handling simultaneous input from multiple users.
</listitem>
</orderedlist>
<para>
However, the patterns to make them work are very similar.
</para>
<section>
<title>Event handling</title>
<para>
Each device will emit its own event stream, this means
that you will need to check the GdkEvent you get in
your event handlers
</para>
<example>
<title>Reacting differently to devices</title>
<programlisting>
static gboolean
my_widget_motion_notify (GtkWidget *widget,
GdkEventMotion *event)
{
GdkDevice *device, *source_device;
device = gdk_event_get_device ((GdkEvent *) event);
source_device = gdk_event_get_source_device ((GdkEvent *) event);
g_print ("Motion event by '%s', coming from HW device '%s'\n",
gdk_device_get_name (device),
gdk_device_get_name (source_device));
/* Handle touch devices differently */
if (gdk_device_get_source (source_device) == GDK_SOURCE_TOUCH)
{
...
}
else
{
...
}
return TRUE;
}
</programlisting>
</example>
<para>
The mechanism above could also be used for fine grained event discarding
(i.e. so rubberband selection doesn't jump to another pointer entering
the widget for example)
</para>
<example>
<title>Reacting differently to devices</title>
<programlisting>
static gboolean
my_widget_button_press (Gtkwidget *widget,
GdkEventButton *event)
{
GET_PRIV(widget)->current_pointer = gdk_event_get_device ((GdkEvent *) event);
...
}
static gboolean
my_widget_button_release (Gtkwidget *widget,
GdkEventButton *event)
{
GET_PRIV(widget)->current_pointer = NULL;
...
}
static gboolean
my_widget_motion_notify (Gtkwidget *widget,
GdkEventMotion *event)
{
if (gdk_event_get_device (event) !=
GET_PRIV(widget)->current_pointer)
return FALSE;
...
}
</programlisting>
</example>
</section>
<section>
<title>Grabs</title>
<para>
Grabs are a mechanism to coerce a device into sending events to
a window, but with multidevice there's an other side of the coin,
how other devices are supposed to interact while the grab is in
effect.
</para>
<para>
The GdkGrabOwnership enum passed to gdk_device_grab() may be used
to block other devices' interaction. %GDK_OWNERSHIP_NONE applies
no restrictions, allowing other devices to interact, even with
the grab window. %GDK_OWNERSHIP_WINDOW blocks other devices from
interacting with the grab window, but they'll still be able to
interact with the rest of the application, whereas
%GDK_OWNERSHIP_APPLICATION will render the whole application
insensitive to input from other devices. Different devices may
have simultaneous grabs on the same or different windows.
</para>
<example>
<title>Grabbing as a result of an input event</title>
<programlisting>
gboolean
my_widget_button_press (GtkWidget *widget,
GdkEventButton *event)
{
GdkDevice *pointer, *keyboard;
pointer = gdk_event_get_device ((GdkEvent *) event);
keyboard = gdk_device_get_associated_device (pointer);
/* Grab both keyboard/pointer, other devices will be
* unable to interact with the widget window meanwhile
*/
gdk_device_grab (pointer,
gtk_widget_get_window (widget),
GDK_OWNERSHIP_WINDOW,
...);
gdk_device_grab (keyboard,
gtk_widget_get_window (widget),
GDK_OWNERSHIP_WINDOW,
...);
return FALSE;
}
</programlisting>
</example>
<para>
For GTK+ grabs, there's only a boolean value, equivalent to
%GDK_OWNERSHIP_NONE and %GDK_OWNERSHIP_WINDOW, but the mechanism
is quite similar.
</para>
<para>
Once the device is grabbed, there may be different situations
that could break the grabs, so the widget needs to listen to
#GdkGrabBrokenEvent and the #GtkWidget::grab-notify signal to
handle these situations.
</para>
<example>
<title>Handling broken grabs</title>
<programlisting>
static gboolean
my_widget_grab_broken (GtkWidget *widget,
GdkEventGrabBroken *event)
{
MyWidgetPrivate *priv = GET_PRIV (widget);
if (gdk_event_get_device (event) == priv->grab_pointer)
{
/* Undo state */
...
priv->grab_pointer = NULL;
return TRUE;
}
return FALSE;
}
static void
my_widget_grab_notify (GtkWidget *widget,
gboolean was_grabbed)
{
MyWidgetPrivate *priv = GET_PRIV (widget);
if (gtk_widget_device_is_shadowed (widget, priv->grab_device))
{
/* Device was "shadowed" by another widget's grab,
* release and undo state
*/
...
priv->grab_pointer = NULL;
}
}
</programlisting>
</example>
</section>
<section>
<title>Handling multipointer</title>
<para>
Widgets do react by default to every virtual device, although
by default they are set in a compatibility mode that makes them
behave better with multiple pointers, without necessarily
being multipointer aware.
</para>
<para>
This compatibility mode most notably disables per-device
enter/leave events, so these are stacked, and the crossing
events are only emitted when the first pointer enters the
window, and after the last pointer leaves it. This behavior
is controlled through gtk_widget_set_support_multidevice()
</para>
</section>
<section>
<title>Reading device axis values</title>
<para>
Button and motion events provide further information about
the device axes' current state. Note the device axes are
hardware and driver dependent, therefore the set of axes
is not set in stone, although there are a few more common ones.
</para>
<example>
<title>Getting to know the axes provided by a device</title>
<programlisting>
carlos@sacarino:~$ xinput list "Wacom ISDv4 E6 Pen stylus" |grep "Label"
Label: Abs X
Label: Abs Y
Label: Abs Pressure
Label: Abs Tilt X
Label: Abs Tilt Y
Label: Abs Wheel
</programlisting>
</example>
<example>
<title>Getting an axis value</title>
<programlisting>
gboolean
my_widget_motion_notify (GtkWidget *widget,
GdkEventMotion *event)
{
GdkAtom *label_atom;
gdouble pressure;
label_atom = gdk_atom_intern_static_string ("Abs Pressure");
gdk_device_get_axis_value (gdk_event_get_device ((GdkEvent *) event),
event->axes, label_atom, &amp;pressure);
/* Do something with pressure */
...
return TRUE;
}
</programlisting>
</example>
<para>
All pointer devices report axes information, master and slave. to
achieve this, master pointers modify their list of axes at runtime
to reflect those of the currently routed slave, emitting
#GdkDevice::changed as the routed slave device changes.
</para>
</section>
<section>
<title>Dealing with slave (or floating) devices</title>
<para>
By default, GTK+ listens to all master devices, and typically
all slave devices will be attached to a master device. so
gdk_event_get_source_device() is the recommended way to deal
with the physical device triggering the event.
</para>
<para>
In more specialized setups, some devices could be floating
(i.e. tablets that don't route events through any virtual
pointer, but are expected to interact with drawing applications).
In that case, such specialized applications could want to interact
directly with the device. To do so, the device must be enabled,
and the widget wanting its events needs to add the event mask.
</para>
<example>
<title>Enabling events for a slave device</title>
<programlisting>
GdkDevice *device;
/* Gets the first device found with the given GdkInputSource */
device = get_device (gtk_widget_get_display (widget),
GDK_SOURCE_PEN);
gdk_device_set_mode (device, GDK_MODE_SCREEN);
gtk_widget_add_device_events (widget, device,
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_POINTER_MOTION_MASK);
</programlisting>
</example>
<para>
After these calls, the widget would specifically receive events
from the physical device, regardless of it being floating or
connected to a master device. In this second case, and if you
want exclusive control of the device, you can temporarily detach
the stylus device from its master by doing a GDK grab on it.
</para>
<para>
For events coming directly from slave devices, both
gdk_event_get_device() and gdk_event_get_source_device() will
return the same device of type %GDK_DEVICE_TYPE_SLAVE or
%GDK_DEVICE_TYPE_FLOATING.
</para>
<note>
This is less useful than it used to be in GTK+2/XInput1, at least
for attached slaves, as there is gdk_event_get_source_device(),
and master devices' events provide axes information.
</note>
</section>
</section>
<section id="gtk-device-patterns-multitouch-widgets">
<title>Multitouch in GTK+ widgets</title>
<para>
Since version 3.4, GTK+ offers support for multitouch devices through a new
set of events and higher level tools like #GdkTouchCluster and
#GtkGesturesInterpreter.
</para>
<para>
If the widget does not have %GDK_TOUCH_MASK set in the event mask, it will
only be allowed to interact with the touch emulating pointer events, and will
only receive pointer events.
</para>
<para>
If the widget does have %GDK_TOUCH_MASK enabled, it will be able to receive
events of type %GDK_TOUCH_PRESS, %GDK_TOUCH_RELEASE and %GDK_TOUCH_MOTION,
which will be respectively emitted through the #GtkWidget::button-press-event,
#GtkWidget::button-release-event and #GtkWidget::motion-notify-event signals.
There may be multiple, simultaneous sequences of events, those will be
recognized and referenced by their touch ID. See gdk_event_get_touch_id().
</para>
<para>
#GtkWidget<!-- -->s may create GdkTouchCluster<!-- -->s via
gdk_window_create_touch_cluster(), those may be used to group
touch events together, which are notified through #GdkEventMultitouch,
this event will be emitted in #GtkWidget<!-- -->s through the
#GtkWidget::multitouch-event signal.
</para>
<para>
Widgets may also handle gestures being performed on them,
gtk_widget_enable_gesture() and gtk_widget_disable_gesture() are
provided as a simple API, although widgets may also create a
#GtkGesturesInterpreter and feed it events directly.
</para>
</section>
<section id="gtk-device-patterns-multitouch">
<title>Multitouch across a widget hierarchy</title>
<para>
Fully touch driven applications might not want to confine multitouch
operations within a single widget, but rather offer simultaneous
interaction with multiple widgets.
</para>
<para>
GTK+ is able to provide such experience, although it does not enable
%GDK_TOUCH_MASK by default on its stock widgets. If a widget meets the
following requirements, it is ready to be used in a multitouch UI:
</para>
<orderedlist>
<listitem>
The widget handles #GtkWidget::button-press-event, #GtkWidget::button-release-event
and #GtkWidget::motion-notify-event, and does something meaningful while the button 1
is pressed. If any explicit check on the event type being %GDK_BUTTON_PRESS,
%GDK_BUTTON_RELEASE or %GDK_MOTION_NOTIFY is performed, the event types
%GDK_TOUCH_PRESS, %GDK_TOUCH_RELEASE or %GDK_TOUCH_MOTION also need to be handled.
</listitem>
<listitem>
The widget relies on the implicit grab as long as the button press/touch is active,
GDK or GTK+ grabs would break the implicit grabs other touch sequences may have on
other widgets.
</listitem>
<listitem>
The widget does not require (or opts out) keyboard interaction while a touch is
active on it. Touch interaction does not necessarily bring the keyboard focus with it.
</listitem>
<listitem>
If the widget is only meant to interact with one touch sequence at a time (i.e.
buttons), it has to be able to discern and reject operations on any later touch
sequence as long as the touch it is interacting with remains active.
</listitem>
</orderedlist>
<para>
If a widget meets those requirements, enabling %GDK_TOUCH_MASK on it will suffice
to make it handle multitouch events in a way that doesn't disrupt other touch
operations.
</para>
<example>
<title>Enabling touch events on a widget</title>
<programlisting>
gtk_widget_add_events (widget, GDK_TOUCH_MASK);
</programlisting>
</example>
<note>
Not all GTK+ stock widgets are immediately suitable for handling touch
events, there could be even design reasons on some of those which render
them unsuitable.
</note>
</section>
<section id="gtk-device-patterns-recommendations">
<title>Recommendations</title>
<orderedlist>
<listitem>
Device operations often come up as a result of input events, favor
gdk_event_get_device() and gtk_get_current_event_device() before
gdk_device_manager_get_client_pointer().
</listitem>
<listitem>
Store the devices the widget is currently interacting with, handle
GdkEventGrabBroken and #GtkWidget::grab-notify to undo/nullify these.
</listitem>
</orderedlist>
</section>
</chapter>
+6
View File
@@ -68,6 +68,12 @@
<xi:include href="xml/gtkstyle.xml" />
</part>
<part id="multitouch-and-multidevice">
<title>Interacting with input devices</title>
<xi:include href="xml/device-interaction-patterns.xml" />
<xi:include href="xml/gtkgesturesinterpreter.xml" />
</part>
<part id="gtkobjects">
<title>GTK+ Widgets and Objects</title>
+54
View File
@@ -1579,6 +1579,51 @@ GtkFramePrivate
gtk_frame_get_type
</SECTION>
<SECTION>
<FILE>gtkgesturesinterpreter</FILE>
<TITLE>GtkGesturesInterpreter</TITLE>
GtkGesturesInterpreter
GtkGestureType
gtk_gestures_interpreter_new
gtk_gestures_interpreter_add_gesture
gtk_gestures_interpreter_remove_gesture
gtk_gestures_interpreter_feed_event
gtk_gestures_interpreter_finish
gtk_gestures_interpreter_get_n_active_strokes
<SUBSECTION Gestures>
GtkGestureStroke
gtk_gesture_stroke_new
gtk_gesture_stroke_copy
gtk_gesture_stroke_free
gtk_gesture_stroke_append_vector
gtk_gesture_stroke_get_n_vectors
gtk_gesture_stroke_get_vector
GtkGesture
GtkGestureFlags
gtk_gesture_new
gtk_gesture_copy
gtk_gesture_free
gtk_gesture_add_stroke
gtk_gesture_get_n_strokes
gtk_gesture_get_stroke
gtk_gesture_get_flags
gtk_gesture_register
gtk_gesture_register_static
gtk_gesture_lookup
<SUBSECTION Standard>
GTK_GESTURES_INTERPRETER
GTK_IS_GESTURES_INTERPRETER
GTK_TYPE_GESTURES_INTERPRETER
GTK_IS_GESTURES_INTERPRETER_CLASS
GTK_GESTURES_INTERPRETER_GET_CLASS
<SUBSECTION Private>
gtk_gestures_interpreter_get_type
gtk_gesture_stroke_get_type
gtk_gesture_get_type
</SECTION>
<SECTION>
<FILE>gtkhandlebox</FILE>
<TITLE>GtkHandleBox</TITLE>
@@ -2939,6 +2984,9 @@ gtk_scrolled_window_get_min_content_width
gtk_scrolled_window_set_min_content_width
gtk_scrolled_window_get_min_content_height
gtk_scrolled_window_set_min_content_height
GtkKineticScrollingFlags
gtk_scrolled_window_set_kinetic_scrolling
gtk_scrolled_window_get_kinetic_scrolling
<SUBSECTION Standard>
GTK_SCROLLED_WINDOW
@@ -5028,6 +5076,7 @@ GtkAllocation
GtkSelectionData
GtkWidgetAuxInfo
GtkWidgetHelpType
GtkCapturedEventFlags
gtk_widget_new
gtk_widget_destroy
gtk_widget_in_destruction
@@ -5213,6 +5262,7 @@ gtk_widget_get_mapped
gtk_widget_get_requisition
gtk_widget_device_is_shadowed
gtk_widget_get_modifier_mask
gtk_widget_release_captured_events
<SUBSECTION>
gtk_widget_get_path
@@ -5262,6 +5312,10 @@ gtk_widget_set_vexpand_set
gtk_widget_queue_compute_expand
gtk_widget_compute_expand
<SUBSECTION Gestures>
gtk_widget_enable_gesture
gtk_widget_disable_gesture
<SUBSECTION Standard>
GTK_WIDGET
GTK_IS_WIDGET
+1
View File
@@ -77,6 +77,7 @@ gtk_font_chooser_widget_get_type
gtk_font_selection_dialog_get_type
gtk_font_selection_get_type
gtk_frame_get_type
gtk_gestures_interpreter_get_type
gtk_grid_get_type
gtk_handle_box_get_type
gtk_hbox_get_type
+2
View File
@@ -88,6 +88,7 @@ gdk_public_h_sources = \
gdkselection.h \
gdktestutils.h \
gdkthreads.h \
gdktouchcluster.h \
gdktypes.h \
gdkvisual.h \
gdkwindow.h
@@ -130,6 +131,7 @@ gdk_c_sources = \
gdkrgba.c \
gdkscreen.c \
gdkselection.c \
gdktouchcluster.c \
gdkvisual.c \
gdkwindow.c \
gdkwindowimpl.c
+1
View File
@@ -53,6 +53,7 @@
#include <gdk/gdkselection.h>
#include <gdk/gdktestutils.h>
#include <gdk/gdkthreads.h>
#include <gdk/gdktouchcluster.h>
#include <gdk/gdktypes.h>
#include <gdk/gdkvisual.h>
#include <gdk/gdkwindow.h>
+6
View File
@@ -168,6 +168,7 @@ gdk_event_get_scroll_direction
gdk_event_get_source_device
gdk_event_get_state
gdk_event_get_time
gdk_event_get_touch_id
gdk_event_get_type
gdk_event_handler_set
gdk_event_mask_get_type
@@ -327,6 +328,11 @@ gdk_threads_enter
gdk_threads_init
gdk_threads_leave
gdk_threads_set_lock_functions
gdk_touch_cluster_add_touch
gdk_touch_cluster_get_device
gdk_touch_cluster_get_type G_GNUC_CONST
gdk_touch_cluster_list_touches
gdk_touch_cluster_remove_touch
gdk_unicode_to_keyval
gdk_utf8_to_string_target
gdk_visibility_state_get_type
+3 -1
View File
@@ -61,6 +61,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 touch capable device.
*
* An enumeration describing the type of an input device in general terms.
*/
@@ -70,7 +71,8 @@ typedef enum
GDK_SOURCE_PEN,
GDK_SOURCE_ERASER,
GDK_SOURCE_CURSOR,
GDK_SOURCE_KEYBOARD
GDK_SOURCE_KEYBOARD,
GDK_SOURCE_TOUCH
} GdkInputSource;
/**
+135 -13
View File
@@ -188,6 +188,7 @@ gdk_display_init (GdkDisplay *display)
display->double_click_time = 250;
display->double_click_distance = 5;
display->touch_implicit_grabs = g_array_new (FALSE, FALSE, sizeof (GdkTouchGrabInfo));
display->device_grabs = g_hash_table_new (NULL, NULL);
display->motion_hint_info = g_hash_table_new_full (NULL, NULL, NULL,
(GDestroyNotify) g_free);
@@ -236,6 +237,8 @@ gdk_display_finalize (GObject *object)
NULL);
g_hash_table_destroy (display->device_grabs);
g_array_free (display->touch_implicit_grabs, TRUE);
g_hash_table_destroy (display->motion_hint_info);
g_hash_table_destroy (display->pointers_info);
g_hash_table_destroy (display->multiple_click_info);
@@ -694,6 +697,81 @@ _gdk_display_add_device_grab (GdkDisplay *display,
return info;
}
static void
_gdk_display_break_touch_grabs (GdkDisplay *display,
GdkDevice *device,
GdkWindow *new_grab_window)
{
guint i = 0;
while (i < display->touch_implicit_grabs->len)
{
GdkTouchGrabInfo *info;
info = &g_array_index (display->touch_implicit_grabs,
GdkTouchGrabInfo, i);
if (info->device == device &&
info->window != new_grab_window)
{
generate_grab_broken_event (GDK_WINDOW (info->window),
device, TRUE, new_grab_window);
_gdk_window_finish_touch_id (info->window, device, info->touch_id);
g_array_remove_index_fast (display->touch_implicit_grabs, i);
}
else
i++;
}
}
void
_gdk_display_add_touch_grab (GdkDisplay *display,
GdkDevice *device,
guint touch_id,
GdkWindow *window,
GdkWindow *native_window,
GdkEventMask event_mask,
unsigned long serial,
guint32 time)
{
GdkTouchGrabInfo info;
info.device = device;
info.touch_id = touch_id;
info.window = g_object_ref (window);
info.native_window = g_object_ref (native_window);
info.serial = serial;
info.event_mask = event_mask;
info.time = time;
g_array_append_val (display->touch_implicit_grabs, info);
}
gboolean
_gdk_display_end_touch_grab (GdkDisplay *display,
GdkDevice *device,
guint touch_id)
{
guint i;
for (i = 0; i < display->touch_implicit_grabs->len; i++)
{
GdkTouchGrabInfo *info;
info = &g_array_index (display->touch_implicit_grabs,
GdkTouchGrabInfo, i);
if (info->device == device &&
info->touch_id == touch_id)
{
g_array_remove_index_fast (display->touch_implicit_grabs, i);
return TRUE;
}
}
return FALSE;
}
/* _gdk_synthesize_crossing_events only works inside one toplevel.
This function splits things into two calls if needed, converting the
coordinates to the right toplevel */
@@ -897,15 +975,25 @@ switch_to_pointer_grab (GdkDisplay *display,
if (grab == NULL) /* Ungrabbed, send events */
{
pointer_window = NULL;
if (new_toplevel)
{
/* Find (possibly virtual) child window */
pointer_window =
_gdk_window_find_descendant_at (new_toplevel,
x, y,
NULL, NULL);
}
/* If the source device is a touch device, do not
* propagate any enter event yet, until one is
* synthesized when needed.
*/
if (source_device &&
gdk_device_get_source (source_device) == GDK_SOURCE_TOUCH)
info->need_touch_press_enter = TRUE;
pointer_window = NULL;
if (new_toplevel &&
!info->need_touch_press_enter)
{
/* Find (possibly virtual) child window */
pointer_window =
_gdk_window_find_descendant_at (new_toplevel,
x, y,
NULL, NULL);
}
if (pointer_window != last_grab->window)
synthesize_crossing_events (display, device, source_device,
@@ -964,12 +1052,15 @@ _gdk_display_device_grab_update (GdkDisplay *display,
next_grab = NULL; /* Actually its not yet active */
}
if (next_grab)
_gdk_display_break_touch_grabs (display, device, next_grab->window);
if ((next_grab == NULL && current_grab->implicit_ungrab) ||
(next_grab != NULL && current_grab->window != next_grab->window))
generate_grab_broken_event (GDK_WINDOW (current_grab->window),
(next_grab != NULL && current_grab->window != next_grab->window))
generate_grab_broken_event (GDK_WINDOW (current_grab->window),
device,
current_grab->implicit,
next_grab? next_grab->window : NULL);
current_grab->implicit,
next_grab? next_grab->window : NULL);
/* Remove old grab */
grabs = g_list_delete_link (grabs, grabs);
@@ -1028,6 +1119,34 @@ _gdk_display_has_device_grab (GdkDisplay *display,
return NULL;
}
GdkTouchGrabInfo *
_gdk_display_has_touch_grab (GdkDisplay *display,
GdkDevice *device,
guint touch_id,
gulong serial)
{
guint i;
for (i = 0; i < display->touch_implicit_grabs->len; i++)
{
GdkTouchGrabInfo *info;
info = &g_array_index (display->touch_implicit_grabs,
GdkTouchGrabInfo, i);
if (info->device == device &&
info->touch_id == touch_id)
{
if (serial >= info->serial)
return info;
else
return NULL;
}
}
return NULL;
}
/* Returns true if last grab was ended
* If if_child is non-NULL, end the grab only if the grabbed
* window is the same as if_child or a descendant of it */
@@ -1120,6 +1239,9 @@ _gdk_display_get_pointer_info (GdkDisplay *display,
{
GdkPointerWindowInfo *info;
if (device && gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
device = gdk_device_get_associated_device (device);
if (G_UNLIKELY (!device))
return NULL;
+31
View File
@@ -60,6 +60,19 @@ typedef struct
guint implicit : 1;
} GdkDeviceGrabInfo;
/* Tracks information about a touch implicit grab on this display */
typedef struct
{
GdkDevice *device;
guint touch_id;
GdkWindow *window;
GdkWindow *native_window;
gulong serial;
guint event_mask;
guint32 time;
} GdkTouchGrabInfo;
/* Tracks information about which window and position the pointer last was in.
* This is useful when we need to synthesize events later.
* Note that we track toplevel_under_pointer using enter/leave events,
@@ -75,6 +88,8 @@ typedef struct
gdouble toplevel_x, toplevel_y;
guint32 state;
guint32 button;
GdkDevice *last_slave;
guint need_touch_press_enter : 1;
} GdkPointerWindowInfo;
typedef struct
@@ -103,6 +118,7 @@ struct _GdkDisplay
guint closed : 1; /* Whether this display has been closed */
guint ignore_core_events : 1; /* Don't send core motion and button event */
GArray *touch_implicit_grabs;
GHashTable *device_grabs;
GHashTable *motion_hint_info;
GdkDeviceManager *device_manager;
@@ -260,6 +276,21 @@ gboolean _gdk_display_end_device_grab (GdkDisplay *display
gboolean _gdk_display_check_grab_ownership (GdkDisplay *display,
GdkDevice *device,
gulong serial);
void _gdk_display_add_touch_grab (GdkDisplay *display,
GdkDevice *device,
guint touch_id,
GdkWindow *window,
GdkWindow *native_window,
GdkEventMask event_mask,
unsigned long serial_start,
guint32 time);
GdkTouchGrabInfo * _gdk_display_has_touch_grab (GdkDisplay *display,
GdkDevice *device,
guint touch_id,
gulong serial);
gboolean _gdk_display_end_touch_grab (GdkDisplay *display,
GdkDevice *device,
guint touch_id);
void _gdk_display_enable_motion_hints (GdkDisplay *display,
GdkDevice *device);
GdkPointerWindowInfo * _gdk_display_get_pointer_info (GdkDisplay *display,
+298 -5
View File
@@ -445,6 +445,7 @@ gdk_event_new (GdkEventType type)
switch (type)
{
case GDK_MOTION_NOTIFY:
case GDK_TOUCH_MOTION:
new_event->motion.x = 0.;
new_event->motion.y = 0.;
new_event->motion.x_root = 0.;
@@ -454,6 +455,8 @@ gdk_event_new (GdkEventType type)
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
case GDK_TOUCH_PRESS:
case GDK_TOUCH_RELEASE:
new_event->button.x = 0.;
new_event->button.y = 0.;
new_event->button.x_root = 0.;
@@ -464,6 +467,8 @@ gdk_event_new (GdkEventType type)
new_event->scroll.y = 0.;
new_event->scroll.x_root = 0.;
new_event->scroll.y_root = 0.;
new_event->scroll.delta_x = 0.;
new_event->scroll.delta_y = 0.;
break;
case GDK_ENTER_NOTIFY:
case GDK_LEAVE_NOTIFY:
@@ -487,7 +492,31 @@ gdk_event_is_allocated (const GdkEvent *event)
return FALSE;
}
void
_gdk_event_set_pointer_emulated (GdkEvent *event,
gboolean emulated)
{
if (gdk_event_is_allocated (event))
{
GdkEventPrivate *private = (GdkEventPrivate *) event;
if (emulated)
private->flags |= GDK_EVENT_POINTER_EMULATED;
else
private->flags &= ~(GDK_EVENT_POINTER_EMULATED);
}
}
gboolean
_gdk_event_get_pointer_emulated (GdkEvent *event)
{
if (gdk_event_is_allocated (event))
return (((GdkEventPrivate *) event)->flags & GDK_EVENT_POINTER_EMULATED) != 0;
return FALSE;
}
/**
* gdk_event_copy:
* @event: a #GdkEvent
@@ -558,12 +587,15 @@ gdk_event_copy (const GdkEvent *event)
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
case GDK_TOUCH_PRESS:
case GDK_TOUCH_RELEASE:
if (event->button.axes)
new_event->button.axes = g_memdup (event->button.axes,
sizeof (gdouble) * gdk_device_get_n_axes (event->button.device));
break;
case GDK_MOTION_NOTIFY:
case GDK_TOUCH_MOTION:
if (event->motion.axes)
new_event->motion.axes = g_memdup (event->motion.axes,
sizeof (gdouble) * gdk_device_get_n_axes (event->motion.device));
@@ -583,6 +615,22 @@ gdk_event_copy (const GdkEvent *event)
g_object_ref (new_event->selection.requestor);
break;
case GDK_MULTITOUCH_ADDED:
case GDK_MULTITOUCH_REMOVED:
case GDK_MULTITOUCH_UPDATED:
{
GdkEventMotion **motion_events;
guint i;
motion_events = g_new0 (GdkEventMotion*, event->multitouch.n_events);
for (i = 0; i < event->multitouch.n_events; i++)
motion_events[i] = (GdkEventMotion *) gdk_event_copy ((GdkEvent *) event->multitouch.events[i]);
new_event->multitouch.events = motion_events;
}
break;
default:
break;
}
@@ -639,6 +687,8 @@ gdk_event_free (GdkEvent *event)
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
case GDK_TOUCH_PRESS:
case GDK_TOUCH_RELEASE:
g_free (event->button.axes);
break;
@@ -649,6 +699,7 @@ gdk_event_free (GdkEvent *event)
break;
case GDK_MOTION_NOTIFY:
case GDK_TOUCH_MOTION:
g_free (event->motion.axes);
break;
@@ -668,6 +719,20 @@ gdk_event_free (GdkEvent *event)
g_object_unref (event->selection.requestor);
break;
case GDK_MULTITOUCH_ADDED:
case GDK_MULTITOUCH_REMOVED:
case GDK_MULTITOUCH_UPDATED:
if (event->multitouch.events)
{
guint i;
for (i = 0; i < event->multitouch.n_events; i++)
gdk_event_free ((GdkEvent *) event->multitouch.events[i]);
g_free (event->multitouch.events);
}
break;
default:
break;
}
@@ -696,11 +761,14 @@ gdk_event_get_time (const GdkEvent *event)
switch (event->type)
{
case GDK_MOTION_NOTIFY:
case GDK_TOUCH_MOTION:
return event->motion.time;
case GDK_BUTTON_PRESS:
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
case GDK_TOUCH_PRESS:
case GDK_TOUCH_RELEASE:
return event->button.time;
case GDK_SCROLL:
return event->scroll.time;
@@ -726,6 +794,10 @@ gdk_event_get_time (const GdkEvent *event)
case GDK_DROP_START:
case GDK_DROP_FINISHED:
return event->dnd.time;
case GDK_MULTITOUCH_ADDED:
case GDK_MULTITOUCH_REMOVED:
case GDK_MULTITOUCH_UPDATED:
return event->multitouch.time;
case GDK_CLIENT_EVENT:
case GDK_VISIBILITY_NOTIFY:
case GDK_CONFIGURE:
@@ -771,12 +843,15 @@ gdk_event_get_state (const GdkEvent *event,
switch (event->type)
{
case GDK_MOTION_NOTIFY:
case GDK_TOUCH_MOTION:
*state = event->motion.state;
return TRUE;
case GDK_BUTTON_PRESS:
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
case GDK_TOUCH_PRESS:
case GDK_TOUCH_RELEASE:
*state = event->button.state;
return TRUE;
case GDK_SCROLL:
@@ -790,6 +865,11 @@ gdk_event_get_state (const GdkEvent *event,
case GDK_LEAVE_NOTIFY:
*state = event->crossing.state;
return TRUE;
case GDK_MULTITOUCH_ADDED:
case GDK_MULTITOUCH_REMOVED:
case GDK_MULTITOUCH_UPDATED:
*state = event->multitouch.state;
return TRUE;
case GDK_PROPERTY_NOTIFY:
case GDK_VISIBILITY_NOTIFY:
case GDK_CLIENT_EVENT:
@@ -865,10 +945,13 @@ gdk_event_get_coords (const GdkEvent *event,
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
case GDK_TOUCH_PRESS:
case GDK_TOUCH_RELEASE:
x = event->button.x;
y = event->button.y;
break;
case GDK_MOTION_NOTIFY:
case GDK_TOUCH_MOTION:
x = event->motion.x;
y = event->motion.y;
break;
@@ -908,6 +991,7 @@ gdk_event_get_root_coords (const GdkEvent *event,
switch (event->type)
{
case GDK_MOTION_NOTIFY:
case GDK_TOUCH_MOTION:
x = event->motion.x_root;
y = event->motion.y_root;
break;
@@ -919,6 +1003,8 @@ gdk_event_get_root_coords (const GdkEvent *event,
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
case GDK_TOUCH_PRESS:
case GDK_TOUCH_RELEASE:
x = event->button.x_root;
y = event->button.y_root;
break;
@@ -1122,7 +1208,10 @@ gdk_event_get_scroll_direction (const GdkEvent *event,
switch (event->type)
{
case GDK_SCROLL:
dir = event->scroll.direction;
if (event->scroll.direction == GDK_SCROLL_SMOOTH)
fetched = FALSE;
else
dir = event->scroll.direction;
break;
default:
fetched = FALSE;
@@ -1135,6 +1224,52 @@ gdk_event_get_scroll_direction (const GdkEvent *event,
return fetched;
}
/**
* gdk_event_get_scroll_deltas:
* @event: a #GdkEvent
* @delta_x: return location for X delta
* @delta_y: return location for Y delta
*
* Retrieves the scroll deltas from a #GdkEvent
*
* Returns: %TRUE if the event contains smooth scroll information
*
* Since: 3.4
**/
gboolean
gdk_event_get_scroll_deltas (const GdkEvent *event,
gdouble *delta_x,
gdouble *delta_y)
{
gboolean fetched = TRUE;
gdouble dx = 0.0;
gdouble dy = 0.0;
switch (event->type)
{
case GDK_SCROLL:
if (event->scroll.direction == GDK_SCROLL_SMOOTH)
{
dx = event->scroll.delta_x;
dy = event->scroll.delta_y;
}
else
fetched = FALSE;
break;
default:
fetched = FALSE;
break;
}
if (delta_x)
*delta_x = dx;
if (delta_y)
*delta_y = dy;
return fetched;
}
/**
* gdk_event_get_axis:
* @event: a #GdkEvent
@@ -1162,7 +1297,8 @@ gdk_event_get_axis (const GdkEvent *event,
switch (event->type)
{
case GDK_MOTION_NOTIFY:
case GDK_MOTION_NOTIFY:
case GDK_TOUCH_MOTION:
x = event->motion.x;
y = event->motion.y;
break;
@@ -1172,6 +1308,8 @@ gdk_event_get_axis (const GdkEvent *event,
break;
case GDK_BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
case GDK_TOUCH_PRESS:
case GDK_TOUCH_RELEASE:
x = event->button.x;
y = event->button.y;
break;
@@ -1193,12 +1331,15 @@ gdk_event_get_axis (const GdkEvent *event,
return TRUE;
}
else if (event->type == GDK_BUTTON_PRESS ||
event->type == GDK_BUTTON_RELEASE)
event->type == GDK_BUTTON_RELEASE ||
event->type == GDK_TOUCH_PRESS ||
event->type == GDK_TOUCH_RELEASE)
{
device = event->button.device;
axes = event->button.axes;
}
else if (event->type == GDK_MOTION_NOTIFY)
else if (event->type == GDK_MOTION_NOTIFY ||
event->type == GDK_TOUCH_MOTION)
{
device = event->motion.device;
axes = event->motion.axes;
@@ -1235,12 +1376,15 @@ gdk_event_set_device (GdkEvent *event,
switch (event->type)
{
case GDK_MOTION_NOTIFY:
case GDK_TOUCH_MOTION:
event->motion.device = device;
break;
case GDK_BUTTON_PRESS:
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
case GDK_TOUCH_PRESS:
case GDK_TOUCH_RELEASE:
event->button.device = device;
break;
case GDK_SCROLL:
@@ -1250,6 +1394,10 @@ gdk_event_set_device (GdkEvent *event,
case GDK_PROXIMITY_OUT:
event->proximity.device = device;
break;
case GDK_MULTITOUCH_ADDED:
case GDK_MULTITOUCH_REMOVED:
case GDK_MULTITOUCH_UPDATED:
event->multitouch.device = device;
default:
break;
}
@@ -1282,17 +1430,24 @@ gdk_event_get_device (const GdkEvent *event)
switch (event->type)
{
case GDK_MOTION_NOTIFY:
case GDK_TOUCH_MOTION:
return event->motion.device;
case GDK_BUTTON_PRESS:
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
case GDK_TOUCH_PRESS:
case GDK_TOUCH_RELEASE:
return event->button.device;
case GDK_SCROLL:
return event->scroll.device;
case GDK_PROXIMITY_IN:
case GDK_PROXIMITY_OUT:
return event->proximity.device;
case GDK_MULTITOUCH_ADDED:
case GDK_MULTITOUCH_REMOVED:
case GDK_MULTITOUCH_UPDATED:
return event->multitouch.device;
default:
break;
}
@@ -1301,10 +1456,13 @@ gdk_event_get_device (const GdkEvent *event)
switch (event->type)
{
case GDK_MOTION_NOTIFY:
case GDK_TOUCH_MOTION:
case GDK_BUTTON_PRESS:
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
case GDK_TOUCH_PRESS:
case GDK_TOUCH_RELEASE:
case GDK_ENTER_NOTIFY:
case GDK_LEAVE_NOTIFY:
case GDK_FOCUS_CHANGE:
@@ -1320,6 +1478,9 @@ gdk_event_get_device (const GdkEvent *event)
case GDK_GRAB_BROKEN:
case GDK_KEY_PRESS:
case GDK_KEY_RELEASE:
case GDK_MULTITOUCH_ADDED:
case GDK_MULTITOUCH_REMOVED:
case GDK_MULTITOUCH_UPDATED:
{
GdkDisplay *display;
GdkDeviceManager *device_manager;
@@ -1686,6 +1847,138 @@ gdk_event_get_screen (const GdkEvent *event)
return NULL;
}
/**
* gdk_event_get_touch_id:
* @event: a #GdkEvent
* @touch_id: return location of the touch ID of a touch event
*
* If @event if of type %GDK_TOUCH_MOTION, %GDK_TOUCH_PRESS or
* %GDK_TOUCH_RELEASE, fills in @touch_id and returns %TRUE,
* else it returns %FALSE.
*
* Returns: %TRUE if the touch ID can be extracted from @event.
*
* Since: 3.4
**/
gboolean
gdk_event_get_touch_id (const GdkEvent *event,
guint *touch_id)
{
if (!event)
return FALSE;
if (event->type == GDK_TOUCH_MOTION)
{
if (touch_id)
*touch_id = event->motion.touch_id;
return TRUE;
}
else if (event->type == GDK_TOUCH_PRESS ||
event->type == GDK_TOUCH_RELEASE)
{
if (touch_id)
*touch_id = event->button.touch_id;
return TRUE;
}
else
{
if (touch_id)
*touch_id = 0;
return FALSE;
}
}
/**
* gdk_event_get_touch_area:
* @event: a #GdkEvent
*
* This function takes a #GdkEvent coming from a touch device
* (eg. gdk_event_get_source_device() returns a device of type
* %GDK_SOURCE_TOUCH), and returns the area covered by the touch
* as a #cairo_region_t. or %NULL if the device doesn't provide
* this information, or the touch area information couldn't be
* extracted from the event.
*
* <note><warning>Not all touch capable devices provide this
* information, so provide fallbacks to this function returning
* %NULL, even if the window receiving events is only meant
* to react to touch events.</warning></note>
*
* Returns: (transfer full): the touch region, or %NULL if unavailable
*
* Since: 3.4
**/
cairo_region_t *
gdk_event_get_touch_area (GdkEvent *event)
{
gdouble *axes, minor_axis, major_axis, orientation_axis;
GdkAtom major, minor, orientation;
GdkDevice *device;
g_return_val_if_fail (event != NULL, NULL);
device = gdk_event_get_source_device (event);
if (!device)
return NULL;
if (event->type == GDK_MOTION_NOTIFY ||
event->type == GDK_TOUCH_MOTION)
axes = event->motion.axes;
else if (event->type == GDK_BUTTON_PRESS ||
event->type == GDK_2BUTTON_PRESS ||
event->type == GDK_3BUTTON_PRESS ||
event->type == GDK_BUTTON_RELEASE)
axes = event->button.axes;
else
return NULL;
major = gdk_atom_intern_static_string ("Abs MT Touch Major");
minor = gdk_atom_intern_static_string ("Abs MT Touch Minor");
orientation = gdk_atom_intern_static_string ("Abs MT Orientation");
if (gdk_device_get_axis_value (device, axes, major, &major_axis) &&
gdk_device_get_axis_value (device, axes, minor, &minor_axis) &&
gdk_device_get_axis_value (device, axes, orientation, &orientation_axis))
{
cairo_rectangle_int_t rect;
GdkScreen *screen;
gdouble x, y;
/* FIXME: We're assuming the device is mapped to a single screen,
* could lead to stretched/shrinked shapes in multimonitor, although
* that'd be an unusual setup for touchscreens.
*/
screen = gdk_window_get_screen (event->any.window);
gdk_event_get_coords (event, &x, &y);
if (orientation_axis == 0)
{
/* Orientation is horizontal */
rect.width = (gint) gdk_screen_get_width (screen) * major_axis;
rect.height = (gint) gdk_screen_get_height (screen) * minor_axis;
}
else
{
/* Orientation is vertical */
rect.height = (gint) gdk_screen_get_height (screen) * major_axis;
rect.width = (gint) gdk_screen_get_width (screen) * minor_axis;
}
/* Something is wrong here */
if (rect.width == 0 ||
rect.height == 0)
return NULL;
rect.x = x - rect.width / 2;
rect.y = y - rect.height / 2;
return cairo_region_create_rectangle (&rect);
}
return NULL;
}
/**
* gdk_set_show_events:
* @show_events: %TRUE to output event debugging information.
+114 -5
View File
@@ -35,6 +35,7 @@
#include <gdk/gdktypes.h>
#include <gdk/gdkdnd.h>
#include <gdk/gdkdevice.h>
#include <gdk/gdktouchcluster.h>
G_BEGIN_DECLS
@@ -143,6 +144,7 @@ typedef struct _GdkEventDND GdkEventDND;
typedef struct _GdkEventWindowState GdkEventWindowState;
typedef struct _GdkEventSetting GdkEventSetting;
typedef struct _GdkEventGrabBroken GdkEventGrabBroken;
typedef struct _GdkEventMultiTouch GdkEventMultiTouch;
typedef union _GdkEvent GdkEvent;
@@ -263,6 +265,18 @@ typedef GdkFilterReturn (*GdkFilterFunc) (GdkXEvent *xevent,
* was added in 2.8.
* @GDK_DAMAGE: the content of the window has been changed. This event type
* was added in 2.14.
* @GDK_TOUCH_MOTION: A touch device has been updated. This event type was
* added in 3.4.
* @GDK_TOUCH_PRESS: A new touch stream has just started. This event type was
* added in 3.4.
* @GDK_TOUCH_RELEASE: A touch stream has finished. This event type was
* added in 3.4.
* @GDK_MULTITOUCH_ADDED: A touch ID was added to a #GdkTouchCluster. This
* event type was added in 3.4.
* @GDK_MULTITOUCH_UPDATED: A touch within a #GdkTouchCluster has been updated.
* This event type was added in 3.4.
* @GDK_MULTITOUCH_REMOVED: A touch ID was removed from a #GdkTouchCluster. This
* event type was added in 3.4.
* @GDK_EVENT_LAST: marks the end of the GdkEventType enumeration. Added in 2.18
*
* Specifies the type of the event.
@@ -310,6 +324,12 @@ typedef enum
GDK_OWNER_CHANGE = 34,
GDK_GRAB_BROKEN = 35,
GDK_DAMAGE = 36,
GDK_TOUCH_MOTION = 37,
GDK_TOUCH_PRESS = 38,
GDK_TOUCH_RELEASE = 39,
GDK_MULTITOUCH_ADDED = 40,
GDK_MULTITOUCH_UPDATED = 41,
GDK_MULTITOUCH_REMOVED = 42,
GDK_EVENT_LAST /* helper variable for decls */
} GdkEventType;
@@ -334,6 +354,8 @@ typedef enum
* @GDK_SCROLL_DOWN: the window is scrolled down.
* @GDK_SCROLL_LEFT: the window is scrolled to the left.
* @GDK_SCROLL_RIGHT: the window is scrolled to the right.
* @GDK_SCROLL_SMOOTH: the scrolling is determined by the delta values
* in #GdkEventScroll. See gdk_event_get_scroll_deltas().
*
* Specifies the direction for #GdkEventScroll.
*/
@@ -342,7 +364,8 @@ typedef enum
GDK_SCROLL_UP,
GDK_SCROLL_DOWN,
GDK_SCROLL_LEFT,
GDK_SCROLL_RIGHT
GDK_SCROLL_RIGHT,
GDK_SCROLL_SMOOTH
} GdkScrollDirection;
/**
@@ -385,6 +408,13 @@ typedef enum
* @GDK_CROSSING_GTK_UNGRAB: crossing because a GTK+ grab is deactivated.
* @GDK_CROSSING_STATE_CHANGED: crossing because a GTK+ widget changed
* state (e.g. sensitivity).
* @GDK_CROSSING_TOUCH_PRESS: crossing because a touch device was pressed,
* this event is synthetic as the pointer might have not left the window.
* @GDK_CROSSING_TOUCH_RELEASE: crossing because a touch device was released.
* this event is synthetic as the pointer might have not left the window.
* @GDK_CROSSING_DEVICE_SWITCH: crossing because of a device switch (i.e.
* a mouse taking control of the pointer after a touch device), this event
* is synthetic as the pointer didn't leave the window.
*
* Specifies the crossing mode for #GdkEventCrossing.
*/
@@ -395,7 +425,10 @@ typedef enum
GDK_CROSSING_UNGRAB,
GDK_CROSSING_GTK_GRAB,
GDK_CROSSING_GTK_UNGRAB,
GDK_CROSSING_STATE_CHANGED
GDK_CROSSING_STATE_CHANGED,
GDK_CROSSING_TOUCH_PRESS,
GDK_CROSSING_TOUCH_RELEASE,
GDK_CROSSING_DEVICE_SWITCH
} GdkCrossingMode;
/**
@@ -552,8 +585,13 @@ struct _GdkEventVisibility
* screen.
* @y_root: the y coordinate of the pointer relative to the root of the
* screen.
* @touch_id: touch ID, only meaningful if event is of type %GDK_TOUCH_MOTION.
*
* Generated when the pointer moves.
* Generated when the pointer/touch moves.
*
* If the event has a type of %GDK_TOUCH_MOTION, this event will
* pertain to a sequence identified by gdk_event_get_touch_id().
* With multitouch devices, there may be several ongoing sequences.
*/
struct _GdkEventMotion
{
@@ -568,12 +606,63 @@ struct _GdkEventMotion
gint16 is_hint;
GdkDevice *device;
gdouble x_root, y_root;
guint touch_id;
};
/**
* GdkEventMultiTouch:
* @type: the type of the event (%GDK_MULTITOUCH_ADDED, %GDK_MULTITOUCH_UPDATED
* or %GDK_MULTITOUCH_REMOVED).
* @window: the window which received the event.
* @send_event: %TRUE if the event was sent explicitly (e.g. using
* <function>XSendEvent</function>).
* @time: the time of the event in milliseconds.
* @state: (type GdkModifierType): a bit-mask representing the state of
* the modifier keys (e.g. Control, Shift and Alt) and the pointer
* buttons. See #GdkModifierType.
* @device: the device where the event originated.
* @group: the #GdkTouchCluster containing the touches that generated this event
* @events: an array of events of type %GDK_TOUCH_MOTION for the touches in @group
* @updated_touch_id: the touch ID that caused this event to be generated
* @n_events: the number of events in @events
* @n_updated_event: the index in @events of the event corresponding to
* @updated_touch_id, or -1 for %GDK_MULTITOUCH_REMOVED events.
*
* Used for multitouch events. The @type field will be one of
* %GDK_MULTITOUCH_ADDED, %GDK_MULTITOUCH_UPDATED or
* %GDK_MULTITOUCH_REMOVED.
*
* Multitouch events group the events from the touches in a
* #GdkTouchCluster, so one of these events is generated
* whenever a touch ID generates a new event, or a touch ID
* is added or removed.
*
* For any given touch ID, %GDK_MULTITOUCH_ADDED and
* %GDK_MULTITOUCH_REMOVED events are always paired,
* with any number of %GDK_MULTITOUCH_UPDATED
* events in between. The minimum event stream is an
* added/removed pair.
*/
struct _GdkEventMultiTouch
{
GdkEventType type;
GdkWindow *window;
gint8 send_event;
guint32 time;
guint state;
GdkDevice *device;
GdkTouchCluster *group;
GdkEventMotion **events;
guint updated_touch_id;
gint8 n_events;
gint8 n_updated_event;
};
/**
* GdkEventButton:
* @type: the type of the event (%GDK_BUTTON_PRESS, %GDK_2BUTTON_PRESS,
* %GDK_3BUTTON_PRESS or %GDK_BUTTON_RELEASE).
* %GDK_3BUTTON_PRESS, %GDK_BUTTON_RELEASE, %GDK_TOUCH_PRESS or
* %GDK_TOUCH_RELEASE).
* @window: the window which received the event.
* @send_event: %TRUE if the event was sent explicitly (e.g. using
* <function>XSendEvent</function>).
@@ -594,10 +683,13 @@ struct _GdkEventMotion
* screen.
* @y_root: the y coordinate of the pointer relative to the root of the
* screen.
* @touch_id: touch ID, only meaningful if event is of type %GDK_TOUCH_PRESS
* or %GDK_TOUCH_RELEASE.
*
* Used for button press and button release events. The
* @type field will be one of %GDK_BUTTON_PRESS,
* %GDK_2BUTTON_PRESS, %GDK_3BUTTON_PRESS, and %GDK_BUTTON_RELEASE.
* %GDK_2BUTTON_PRESS, %GDK_3BUTTON_PRESS, %GDK_BUTTON_RELEASE,
* %GDK_TOUCH_PRESS and %GDK_TOUCH_RELEASE.
*
* Double and triple-clicks result in a sequence of events being received.
* For double-clicks the order of events will be:
@@ -629,6 +721,11 @@ struct _GdkEventMotion
* For a double click to occur, the second button press must occur within
* 1/4 of a second of the first. For a triple click to occur, the third
* button press must also occur within 1/2 second of the first button press.
*
* If the event has a type of %GDK_TOUCH_PRESS or %GDK_TOUCH_RELEASE,
* this event will pertain to a sequence identified by
* gdk_event_get_touch_id(). With multitouch devices, there may be
* several ongoing sequences.
*/
struct _GdkEventButton
{
@@ -643,6 +740,7 @@ struct _GdkEventButton
guint button;
GdkDevice *device;
gdouble x_root, y_root;
guint touch_id;
};
/**
@@ -681,6 +779,8 @@ struct _GdkEventScroll
GdkScrollDirection direction;
GdkDevice *device;
gdouble x_root, y_root;
gdouble delta_x;
gdouble delta_y;
};
/**
@@ -1086,6 +1186,7 @@ union _GdkEvent
GdkEventWindowState window_state;
GdkEventSetting setting;
GdkEventGrabBroken grab_broken;
GdkEventMultiTouch multitouch;
};
GType gdk_event_get_type (void) G_GNUC_CONST;
@@ -1119,6 +1220,10 @@ gboolean gdk_event_get_keycode (const GdkEvent *event,
guint16 *keycode);
gboolean gdk_event_get_scroll_direction (const GdkEvent *event,
GdkScrollDirection *direction);
gboolean gdk_event_get_scroll_deltas (const GdkEvent *event,
gdouble *delta_x,
gdouble *delta_y);
gboolean gdk_event_get_axis (const GdkEvent *event,
GdkAxisUse axis_use,
gdouble *value);
@@ -1150,6 +1255,10 @@ void gdk_event_set_screen (GdkEvent *event,
GdkScreen *screen);
GdkScreen *gdk_event_get_screen (const GdkEvent *event);
gboolean gdk_event_get_touch_id (const GdkEvent *event,
guint *touch_id);
cairo_region_t * gdk_event_get_touch_area (GdkEvent *event);
void gdk_set_show_events (gboolean show_events);
gboolean gdk_get_show_events (void);
+20 -1
View File
@@ -150,7 +150,13 @@ typedef enum
/* Following flag is set for events on the event queue during
* translation and cleared afterwards.
*/
GDK_EVENT_PENDING = 1 << 0
GDK_EVENT_PENDING = 1 << 0,
/* The following flag is set for:
* 1) touch events emulating pointer events
* 2) pointer events being emulated by a touch sequence.
*/
GDK_EVENT_POINTER_EMULATED = 1 << 1
} GdkEventFlags;
struct _GdkEventPrivate
@@ -260,6 +266,12 @@ struct _GdkWindow
gulong device_changed_handler_id;
guint num_offscreen_children;
/* Store of latest per-touch events, keys are
* GdkDevices, values are hashtables of touchID/info
*/
GHashTable *touch_event_tracker;
GList *touch_clusters;
};
#define GDK_WINDOW_TYPE(d) (((GDK_WINDOW (d)))->window_type)
@@ -275,6 +287,10 @@ GdkEvent* _gdk_event_unqueue (GdkDisplay *display);
void _gdk_event_filter_unref (GdkWindow *window,
GdkEventFilter *filter);
void _gdk_event_set_pointer_emulated (GdkEvent *event,
gboolean emulated);
gboolean _gdk_event_get_pointer_emulated (GdkEvent *event);
void _gdk_event_emit (GdkEvent *event);
GList* _gdk_event_queue_find_first (GdkDisplay *display);
void _gdk_event_queue_remove_link (GdkDisplay *display,
@@ -318,6 +334,9 @@ gboolean _gdk_window_update_viewable (GdkWindow *window);
void _gdk_window_process_updates_recurse (GdkWindow *window,
cairo_region_t *expose_region);
gboolean _gdk_window_finish_touch_id (GdkWindow *window,
GdkDevice *device,
guint touch_id);
void _gdk_screen_close (GdkScreen *screen);
+448
View File
@@ -0,0 +1,448 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 2011 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 "gdktouchcluster.h"
#include "gdkintl.h"
/**
* SECTION:touchcluster
* @Short_description: Multitouch handling
* @Title: Multitouch
* @See_also: #GdkEventMultiTouch
*
* #GdkTouchCluster is an object that gathers touch IDs from a
* #GdkDevice, in order to send #GdkEventMultiTouch events
* whenever a touch ID that is contained in the cluster sends
* an event.
*
* #GdkTouchCluster<!-- -->s are always associated to a window,
* you need to create them through gdk_window_create_touch_cluster(),
* and free them through gdk_window_remove_touch_cluster().
*
* Touch IDs from devices can be obtained from %GDK_TOUCH_PRESS,
* %GDK_TOUCH_MOTION or %GDK_TOUCH_RELEASE events through
* gdk_event_get_touch_id(), and then be added via
* gdk_touch_cluster_add_touch(). Note that touch IDs are
* very transient, and they must be dealt with as such.
* touch IDs must not be stored after a GDK_TOUCH_RELEASE,
* and should always be retrieved from the events being
* currently received.
*
* <example>
* <title>Adding touch IDs to a cluster in a GTK+ widget</title>
* <programlisting>
* static gboolean
* widget_button_press (GtkWidget *widget,
* GdkEvent *event)
* {
* guint touch_id;
*
* if (gdk_event_get_touch_id (event, &touch_id))
* {
* /<!-- -->* It is a touch event, delegate processing
* * to the multitouch event handler
* *<!-- -->/
* gdk_touch_cluster_add_touch (priv->touch_cluster, touch_id);
* return TRUE;
* }
*
* /<!-- -->* Normal button processing *<!-- -->/
* ...
* }
* </programlisting>
* </example>
*
* Anytime a touch ID is within a cluster, no %GDK_TOUCH_PRESS,
* %GDK_TOUCH_MOTION or %GDK_TOUCH_RELEASE events will happen
* for the individual touch. The event will be available instead
* as part of the #GdkMultitouchEvent that will be emitted. This
* will hold true until gdk_touch_cluster_remove_touch() is
* called for it. Note that GTK+ will automatically take a
* touch ID out of any cluster if %GDK_TOUCH_RELEASE is gotten
* internally.
*
* <example>
* <title>Typical multitouch event handler</title>
* <programlisting>
* static gboolean
* widget_multitouch_event (GtkWidget *widget,
* GdkEvent *event)
* {
* if (event->type == GDK_MULTITOUCH_ADDED ||
* event->type == GDK_MULTITOUCH_REMOVED)
* {
* /<!-- -->* Update control mode based
* * on the current number of touches
* *<!-- -->/
* priv->control_mode = update_control_mode (event->multitouch.n_events);
* }
* else
* {
* /<!-- -->* A touch ID in the cluster has updated
* * its coordinates, update widget based on the
* * current control mode.
* *<!-- -->/
* update_view (widget, priv->control_mode,
* event->multitouch.events,
* event->multitouch.n_events);
* }
*
* return TRUE;
* }
* </programlisting>
* </example>
*/
typedef struct GdkTouchClusterPrivate GdkTouchClusterPrivate;
struct GdkTouchClusterPrivate
{
GdkDevice *device;
GArray *touches;
};
enum {
PROP_0,
PROP_DEVICE
};
enum {
TOUCH_ADDED,
TOUCH_REMOVED,
LAST_SIGNAL
};
static guint signals [LAST_SIGNAL] = { 0 };
static void gdk_touch_cluster_finalize (GObject *object);
static void gdk_touch_cluster_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void gdk_touch_cluster_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
G_DEFINE_TYPE (GdkTouchCluster, gdk_touch_cluster, G_TYPE_OBJECT)
static void
gdk_touch_cluster_class_init (GdkTouchClusterClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gdk_touch_cluster_finalize;
object_class->get_property = gdk_touch_cluster_get_property;
object_class->set_property = gdk_touch_cluster_set_property;
g_object_class_install_property (object_class,
PROP_DEVICE,
g_param_spec_object ("device",
P_("Device"),
P_("Device attached to the cluster"),
GDK_TYPE_DEVICE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
signals[TOUCH_ADDED] =
g_signal_new (g_intern_static_string ("touch-added"),
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GdkTouchClusterClass, touch_added),
NULL, NULL,
g_cclosure_marshal_VOID__UINT,
G_TYPE_NONE, 1, G_TYPE_UINT);
signals[TOUCH_REMOVED] =
g_signal_new (g_intern_static_string ("touch-removed"),
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GdkTouchClusterClass, touch_removed),
NULL, NULL,
g_cclosure_marshal_VOID__UINT,
G_TYPE_NONE, 1, G_TYPE_UINT);
g_type_class_add_private (object_class, sizeof (GdkTouchClusterPrivate));
}
static void
gdk_touch_cluster_init (GdkTouchCluster *cluster)
{
GdkTouchClusterPrivate *priv;
priv = cluster->priv = G_TYPE_INSTANCE_GET_PRIVATE (cluster,
GDK_TYPE_TOUCH_CLUSTER,
GdkTouchClusterPrivate);
priv->touches = g_array_new (FALSE, FALSE, sizeof (guint));
}
static void
gdk_touch_cluster_finalize (GObject *object)
{
GdkTouchClusterPrivate *priv;
priv = GDK_TOUCH_CLUSTER (object)->priv;
g_array_free (priv->touches, TRUE);
G_OBJECT_CLASS (gdk_touch_cluster_parent_class)->finalize (object);
}
static void
gdk_touch_cluster_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
switch (prop_id)
{
case PROP_DEVICE:
gdk_touch_cluster_set_device (GDK_TOUCH_CLUSTER (object),
g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gdk_touch_cluster_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GdkTouchClusterPrivate *priv;
priv = GDK_TOUCH_CLUSTER (object)->priv;
switch (prop_id)
{
case PROP_DEVICE:
g_value_set_object (value, priv->device);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/**
* gdk_touch_cluster_add_touch:
* @cluster: a #GdkTouchCluster
* @touch_id: a touch ID from a touch event
*
* Adds a touch ID to @cluster, so it will generate a
* %GDK_MULTITOUCH_ADDED event, followed by %GDK_MULTITOUCH_UPDATED
* events whenever this touch ID is updated.
*
* If @touch_id already pertained to another #GdkTouchCluster, it
* will be removed from it, generating a %GDK_MULTITOUCH_REMOVED
* for that another cluster.
*
* Since: 3.4
**/
void
gdk_touch_cluster_add_touch (GdkTouchCluster *cluster,
guint touch_id)
{
GdkTouchClusterPrivate *priv;
gint i;
g_return_if_fail (GDK_IS_TOUCH_CLUSTER (cluster));
priv = cluster->priv;
for (i = 0; i < priv->touches->len; i++)
{
if (touch_id == g_array_index (priv->touches, guint, i))
return;
}
g_array_append_val (priv->touches, touch_id);
g_signal_emit (cluster, signals [TOUCH_ADDED], 0, touch_id);
}
/**
* gdk_touch_cluster_remove_touch:
* @cluster: a #GdkTouchCluster
* @touch_id: a touch ID from a touch event
*
* Removes a touch ID from @cluster, generating a %GDK_MULTITOUCH_REMOVED
* event for @cluster, and causing any further input from @touch_id
* to be reported trough %GDK_TOUCH_MOTION events.
*
* <note><para>
* Note that GTK+ automatically removes a touch ID from any cluster
* if a %GDK_TOUCH_RELEASE event is gotten internally.
* </para></note>
*
* Since: 3.4
**/
void
gdk_touch_cluster_remove_touch (GdkTouchCluster *cluster,
guint touch_id)
{
GdkTouchClusterPrivate *priv;
gint i;
g_return_if_fail (GDK_IS_TOUCH_CLUSTER (cluster));
priv = cluster->priv;
for (i = 0; i < priv->touches->len; i++)
{
if (touch_id == g_array_index (priv->touches, guint, i))
{
g_array_remove_index_fast (priv->touches, i);
g_signal_emit (cluster, signals [TOUCH_REMOVED], 0, touch_id);
return;
}
}
}
/**
* gdk_touch_cluster_remove_all:
* @cluster: a #GdkTouchCluster
*
* Removes all touch IDs from @cluster.
*
* Since: 3.4
**/
void
gdk_touch_cluster_remove_all (GdkTouchCluster *cluster)
{
GdkTouchClusterPrivate *priv;
guint touch_id;
gint i;
g_return_if_fail (GDK_IS_TOUCH_CLUSTER (cluster));
priv = cluster->priv;
for (i = priv->touches->len - 1; i >= 0; i--)
{
touch_id = g_array_index (priv->touches, guint, i);
g_signal_emit (cluster, signals [TOUCH_REMOVED], 0, touch_id);
g_array_remove_index_fast (priv->touches, i);
}
}
/**
* gdk_touch_cluster_get_touches:
* @cluster: a #GdkTouchCluster
* @length: return location for the number of touches returned
*
* Returns the list of touches as an array of @guint.
*
* Returns: (transfer full) (array zero-terminated=0 length=length) (element-type uint): A list of touch IDs.
*
* Since: 3.4
**/
guint *
gdk_touch_cluster_get_touches (GdkTouchCluster *cluster,
gint *len)
{
GdkTouchClusterPrivate *priv;
g_return_val_if_fail (GDK_IS_TOUCH_CLUSTER (cluster), NULL);
priv = cluster->priv;
if (len)
*len = (gint) priv->touches->len;
return g_memdup (priv->touches->data,
sizeof (guint) * priv->touches->len);
}
/**
* gdk_touch_cluster_get_n_touches:
* @cluster: a #GdkTouchCluster
*
* Returns the number of touches contained in @cluster.
*
* Returns: The number of touches.
*
* Since: 3.4
**/
gint
gdk_touch_cluster_get_n_touches (GdkTouchCluster *cluster)
{
GdkTouchClusterPrivate *priv;
g_return_val_if_fail (GDK_IS_TOUCH_CLUSTER (cluster), 0);
priv = cluster->priv;
return (gint) priv->touches->len;
}
/**
* gdk_touch_cluster_set_device:
* @cluster: a #GdkTouchCluster
* @device: a #GdkDevice
*
* Sets the current device associated to @cluster, all contained
* touch IDs must pertain to this device. As a consequence,
* gdk_touch_cluster_remove_all() will be called on @cluster
* if the current device changes.
*
* Since: 3.4
**/
void
gdk_touch_cluster_set_device (GdkTouchCluster *cluster,
GdkDevice *device)
{
GdkTouchClusterPrivate *priv;
g_return_if_fail (GDK_IS_TOUCH_CLUSTER (cluster));
g_return_if_fail (!device || GDK_IS_DEVICE (device));
priv = cluster->priv;
if (priv->device != device)
gdk_touch_cluster_remove_all (cluster);
priv->device = device;
}
/**
* gdk_touch_cluster_get_device:
* @cluster: a #GdkTouchCluster
*
* Returns the slave/floating device this touch cluster pertains to,
* only touch IDs from this device can be included in @cluster.
* the #GdkDevice will typically have the %GDK_SOURCE_TOUCH input source.
*
* Returns: (transfer none): The #GdkDevice generating the contained touch IDs
*
* Since: 3.4
**/
GdkDevice *
gdk_touch_cluster_get_device (GdkTouchCluster *cluster)
{
GdkTouchClusterPrivate *priv;
g_return_val_if_fail (GDK_IS_TOUCH_CLUSTER (cluster), NULL);
priv = cluster->priv;
return priv->device;
}
+71
View File
@@ -0,0 +1,71 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 2011 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.
*/
#ifndef __GDK_TOUCH_CLUSTER_H__
#define __GDK_TOUCH_CLUSTER_H__
#include <glib-object.h>
#include <gdk/gdkdevice.h>
G_BEGIN_DECLS
#define GDK_TYPE_TOUCH_CLUSTER (gdk_touch_cluster_get_type ())
#define GDK_TOUCH_CLUSTER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDK_TYPE_TOUCH_CLUSTER, GdkTouchCluster))
#define GDK_IS_TOUCH_CLUSTER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDK_TYPE_TOUCH_CLUSTER))
typedef struct _GdkTouchCluster GdkTouchCluster;
typedef struct _GdkTouchClusterClass GdkTouchClusterClass;
struct _GdkTouchCluster
{
GObject parent_instance;
gpointer priv;
};
struct _GdkTouchClusterClass
{
GObjectClass parent_class;
void (* touch_added) (GdkTouchCluster *cluster,
guint touch_id);
void (* touch_removed) (GdkTouchCluster *cluster,
guint touch_id);
gpointer padding[16];
};
GType gdk_touch_cluster_get_type (void) G_GNUC_CONST;
void gdk_touch_cluster_add_touch (GdkTouchCluster *cluster,
guint touch_id);
void gdk_touch_cluster_remove_touch (GdkTouchCluster *cluster,
guint touch_id);
void gdk_touch_cluster_remove_all (GdkTouchCluster *cluster);
guint * gdk_touch_cluster_get_touches (GdkTouchCluster *cluster,
gint *length);
gint gdk_touch_cluster_get_n_touches (GdkTouchCluster *cluster);
void gdk_touch_cluster_set_device (GdkTouchCluster *cluster,
GdkDevice *device);
GdkDevice * gdk_touch_cluster_get_device (GdkTouchCluster *cluster);
G_END_DECLS
#endif /* __GDK_TOUCH_CLUSTER_H__ */
+12 -1
View File
@@ -350,6 +350,8 @@ typedef enum
* @GDK_SUBSTRUCTURE_MASK: receive events about window configuration changes of
* child windows
* @GDK_SCROLL_MASK: receive scroll events
* @GDK_TOUCH_MASK: receive (multi)touch events
* @GDK_SMOOTH_SCROLL_MASK: receive smooth scrolling events
* @GDK_ALL_EVENTS_MASK: the combination of all the above event masks.
*
* A set of bit-flags to indicate which events a window is to receive.
@@ -365,6 +367,13 @@ typedef enum
* some of which are marked as a hint (the is_hint member is %TRUE).
* To receive more motion events after a motion hint event, the application
* needs to asks for more, by calling gdk_event_request_motions().
*
* If %GDK_TOUCH_MASK is enabled, the window will receive (multi)touch events
* from touch-enabled devices. Those will come as sequences #GdkEventMotion
* with type %GDK_TOUCH_MOTION, enclosed by 2 #GdkEventButton events with
* type %GDK_TOUCH_PRESS / %GDK_TOUCH_RELEASE. gdk_event_get_touch_id() will
* return the touch ID on those events, so different sequences may be
* distinguished.
*/
typedef enum
{
@@ -389,7 +398,9 @@ typedef enum
GDK_PROXIMITY_OUT_MASK = 1 << 19,
GDK_SUBSTRUCTURE_MASK = 1 << 20,
GDK_SCROLL_MASK = 1 << 21,
GDK_ALL_EVENTS_MASK = 0x3FFFFE
GDK_TOUCH_MASK = 1 << 22,
GDK_SMOOTH_SCROLL_MASK = 1 << 23,
GDK_ALL_EVENTS_MASK = 0x7FFFFF
} GdkEventMask;
/**
+733 -75
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -33,6 +33,7 @@
#include <gdk/gdktypes.h>
#include <gdk/gdkevents.h>
#include <gdk/gdktouchcluster.h>
G_BEGIN_DECLS
@@ -878,6 +879,12 @@ void gdk_window_set_support_multidevice (GdkWindow *window,
gboolean support_multidevice);
gboolean gdk_window_get_support_multidevice (GdkWindow *window);
/* Multitouch support */
GdkTouchCluster * gdk_window_create_touch_cluster (GdkWindow *window,
GdkDevice *device);
void gdk_window_remove_touch_cluster (GdkWindow *window,
GdkTouchCluster *cluster);
G_END_DECLS
#endif /* __GDK_WINDOW_H__ */
+48 -8
View File
@@ -870,6 +870,8 @@ fill_scroll_event (GdkWindow *window,
gint y,
gint x_root,
gint y_root,
gdouble delta_x,
gdouble delta_y,
GdkScrollDirection direction)
{
NSPoint point;
@@ -886,6 +888,8 @@ fill_scroll_event (GdkWindow *window,
event->scroll.state = get_keyboard_modifiers_from_ns_event (nsevent);
event->scroll.direction = direction;
event->scroll.device = _gdk_display->core_pointer;
event->scroll.delta_x = delta_x;
event->scroll.delta_y = delta_y;
}
static void
@@ -1320,28 +1324,64 @@ gdk_event_translate (GdkEvent *event,
case NSScrollWheel:
{
float dx = [nsevent deltaX];
float dy = [nsevent deltaY];
GdkScrollDirection direction;
GdkScrollDirection direction;
float dx;
float dy;
if (dy != 0)
if (gdk_quartz_osx_version() >= GDK_OSX_LION &&
[nsevent hasPreciseScrollingDeltas])
{
dx = [nsevent scrollingDeltaX];
dy = [nsevent scrollingDeltaY];
direction = GDK_SCROLL_SMOOTH;
fill_scroll_event (window, event, nsevent, x, y, x_root, y_root,
-dx, -dy, direction);
/* Fall through for scroll buttons emulation */
}
dx = [nsevent deltaX];
dy = [nsevent deltaY];
if (dy != 0.0)
{
if (dy < 0.0)
direction = GDK_SCROLL_DOWN;
else
direction = GDK_SCROLL_UP;
fill_scroll_event (window, event, nsevent, x, y, x_root, y_root, direction);
dy = fabs (dy);
dx = 0.0;
}
if (dx != 0)
else if (dx != 0.0)
{
if (dx < 0.0)
direction = GDK_SCROLL_RIGHT;
else
direction = GDK_SCROLL_LEFT;
fill_scroll_event (window, event, nsevent, x, y, x_root, y_root, direction);
dx = fabs (dx);
dy = 0.0;
}
if (dx != 0.0 || dy != 0.0)
{
if ([nsevent hasPreciseScrollingDeltas])
{
GdkEvent *emulated_event;
emulated_event = gdk_event_new (GDK_SCROLL);
_gdk_event_set_pointer_emulated (emulated_event, TRUE);
fill_scroll_event (window, emulated_event, nsevent,
x, y, x_root, y_root,
dx, dy, direction);
append_event (emulated_event, TRUE);
}
else
fill_scroll_event (window, event, nsevent,
x, y, x_root, y_root,
dx, dy, direction);
}
}
break;
+2
View File
@@ -7,6 +7,8 @@ libgdkx11includedir = $(includedir)/gtk-3.0/gdk/x11
AM_CPPFLAGS = \
-DG_LOG_DOMAIN=\"Gdk\" \
-DGDK_COMPILATION \
-DXINPUT2_2_USE_UNSTABLE_PROTOCOL \
-DXINPUT2_1_USE_UNSTABLE_PROTOCOL \
-I$(top_srcdir) \
-I$(top_srcdir)/gdk \
-I$(top_builddir)/gdk \
+130 -5
View File
@@ -35,11 +35,21 @@
#endif
typedef struct _ScrollValuator ScrollValuator;
struct _ScrollValuator
{
guint n_valuator : 4;
guint direction : 4;
gdouble last_value;
};
struct _GdkX11DeviceXI2
{
GdkDevice parent_instance;
gint device_id;
GArray *scroll_valuators;
};
struct _GdkX11DeviceXI2Class
@@ -51,6 +61,7 @@ G_DEFINE_TYPE (GdkX11DeviceXI2, gdk_x11_device_xi2, GDK_TYPE_DEVICE)
#ifdef XINPUT_2
static void gdk_x11_device_xi2_finalize (GObject *object);
static void gdk_x11_device_xi2_get_property (GObject *object,
guint prop_id,
GValue *value,
@@ -112,6 +123,7 @@ gdk_x11_device_xi2_class_init (GdkX11DeviceXI2Class *klass)
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GdkDeviceClass *device_class = GDK_DEVICE_CLASS (klass);
object_class->finalize = gdk_x11_device_xi2_finalize;
object_class->get_property = gdk_x11_device_xi2_get_property;
object_class->set_property = gdk_x11_device_xi2_set_property;
@@ -136,6 +148,17 @@ gdk_x11_device_xi2_class_init (GdkX11DeviceXI2Class *klass)
static void
gdk_x11_device_xi2_init (GdkX11DeviceXI2 *device)
{
device->scroll_valuators = g_array_new (FALSE, FALSE, sizeof (ScrollValuator));
}
static void
gdk_x11_device_xi2_finalize (GObject *object)
{
GdkX11DeviceXI2 *device = GDK_X11_DEVICE_XI2 (object);
g_array_free (device->scroll_valuators, TRUE);
G_OBJECT_CLASS (gdk_x11_device_xi2_parent_class)->finalize (object);
}
static void
@@ -388,6 +411,7 @@ gdk_x11_device_xi2_grab (GdkDevice *device,
guint32 time_)
{
GdkX11DeviceXI2 *device_xi2 = GDK_X11_DEVICE_XI2 (device);
GdkX11DeviceManagerXI2 *device_manager_xi2;
GdkDisplay *display;
XIEventMask mask;
Window xwindow;
@@ -395,6 +419,7 @@ gdk_x11_device_xi2_grab (GdkDevice *device,
gint status;
display = gdk_device_get_display (device);
device_manager_xi2 = GDK_X11_DEVICE_MANAGER_XI2 (gdk_display_get_device_manager (display));
/* FIXME: confine_to is actually unused */
@@ -409,7 +434,9 @@ gdk_x11_device_xi2_grab (GdkDevice *device,
}
mask.deviceid = device_xi2->device_id;
mask.mask = _gdk_x11_device_xi2_translate_event_mask (event_mask, &mask.mask_len);
mask.mask = _gdk_x11_device_xi2_translate_event_mask (device_manager_xi2,
event_mask,
&mask.mask_len);
#ifdef G_ENABLE_DEBUG
if (_gdk_debug_flags & GDK_DEBUG_NOGRABS)
@@ -625,10 +652,17 @@ gdk_x11_device_xi2_select_window_events (GdkDevice *device,
GdkEventMask event_mask)
{
GdkX11DeviceXI2 *device_xi2 = GDK_X11_DEVICE_XI2 (device);
GdkX11DeviceManagerXI2 *device_manager_xi2;
GdkDisplay *display;
XIEventMask evmask;
display = gdk_device_get_display (device);
device_manager_xi2 = GDK_X11_DEVICE_MANAGER_XI2 (gdk_display_get_device_manager (display));
evmask.deviceid = device_xi2->device_id;
evmask.mask = _gdk_x11_device_xi2_translate_event_mask (event_mask, &evmask.mask_len);
evmask.mask = _gdk_x11_device_xi2_translate_event_mask (device_manager_xi2,
event_mask,
&evmask.mask_len);
XISelectEvents (GDK_WINDOW_XDISPLAY (window),
GDK_WINDOW_XID (window),
@@ -638,10 +672,14 @@ gdk_x11_device_xi2_select_window_events (GdkDevice *device,
}
guchar *
_gdk_x11_device_xi2_translate_event_mask (GdkEventMask event_mask,
gint *len)
_gdk_x11_device_xi2_translate_event_mask (GdkX11DeviceManagerXI2 *device_manager_xi2,
GdkEventMask event_mask,
gint *len)
{
guchar *mask;
gint minor;
g_object_get (device_manager_xi2, "minor", &minor, NULL);
*len = XIMaskLen (XI_LASTEVENT);
mask = g_new0 (guchar, *len);
@@ -690,6 +728,17 @@ _gdk_x11_device_xi2_translate_event_mask (GdkEventMask event_mask,
XISetMask (mask, XI_FocusOut);
}
#ifdef XINPUT_2_2
/* XInput 2.2 includes multitouch support */
if (minor >= 2 &&
event_mask & GDK_TOUCH_MASK)
{
XISetMask (mask, XI_TouchBegin);
XISetMask (mask, XI_TouchUpdate);
XISetMask (mask, XI_TouchEnd);
}
#endif /* XINPUT_2_2 */
return mask;
}
@@ -742,7 +791,7 @@ _gdk_x11_device_xi2_translate_state (XIModifierState *mods_state,
{
gint group;
group = group_state->base + group_state->latched + group_state->locked;
group = group_state->base | group_state->latched | group_state->locked;
/* FIXME: do we need the XKB complications for this ? */
group = CLAMP(group, 0, 3);
@@ -753,6 +802,82 @@ _gdk_x11_device_xi2_translate_state (XIModifierState *mods_state,
return state;
}
void
_gdk_x11_device_xi2_add_scroll_valuator (GdkX11DeviceXI2 *device,
guint n_valuator,
GdkScrollDirection direction,
gdouble current_value)
{
ScrollValuator scroll;
g_return_if_fail (GDK_IS_X11_DEVICE_XI2 (device));
g_return_if_fail (n_valuator < gdk_device_get_n_axes (GDK_DEVICE (device)));
scroll.n_valuator = n_valuator;
scroll.direction = direction;
scroll.last_value = current_value;
g_array_append_val (device->scroll_valuators, scroll);
}
gboolean
_gdk_x11_device_xi2_is_scroll_valuator (GdkX11DeviceXI2 *device,
guint n_valuator,
GdkScrollDirection *direction)
{
guint i;
g_return_val_if_fail (GDK_IS_X11_DEVICE_XI2 (device), FALSE);
g_return_val_if_fail (n_valuator < gdk_device_get_n_axes (GDK_DEVICE (device)), FALSE);
for (i = 0; i < device->scroll_valuators->len; i++)
{
ScrollValuator *scroll;
scroll = &g_array_index (device->scroll_valuators, ScrollValuator, i);
if (scroll->n_valuator == n_valuator)
{
if (*direction)
*direction = scroll->direction;
return TRUE;
}
}
return FALSE;
}
gdouble
_gdk_x11_device_xi2_get_scroll_delta (GdkX11DeviceXI2 *device,
guint n_valuator,
gdouble valuator_value)
{
guint i;
g_return_val_if_fail (GDK_IS_X11_DEVICE_XI2 (device), 0);
g_return_val_if_fail (n_valuator < gdk_device_get_n_axes (GDK_DEVICE (device)), 0);
for (i = 0; i < device->scroll_valuators->len; i++)
{
ScrollValuator *scroll;
scroll = &g_array_index (device->scroll_valuators, ScrollValuator, i);
if (scroll->n_valuator == n_valuator)
{
gdouble delta;
delta = valuator_value - scroll->last_value;
scroll->last_value = valuator_value;
return delta;
}
}
return 0;
}
gint
_gdk_x11_device_xi2_get_id (GdkX11DeviceXI2 *device)
{
+3
View File
@@ -471,6 +471,9 @@ gdk_x11_device_manager_core_translate_event (GdkEventTranslator *translator,
event->scroll.state = (GdkModifierType) xevent->xbutton.state;
event->scroll.device = device_manager->core_pointer;
event->scroll.delta_x = 0;
event->scroll.delta_y = 0;
if (!set_screen_from_root (display, event, xevent->xbutton.root))
{
return_val = FALSE;
+6
View File
@@ -54,7 +54,11 @@ _gdk_x11_device_manager_new (GdkDisplay *display)
int major, minor;
major = 2;
#ifdef XINPUT_2_2
minor = 2;
#else
minor = 0;
#endif /* XINPUT_2_2 */
if (!_gdk_disable_multidevice &&
XIQueryVersion (xdisplay, &major, &minor) != BadRequest)
@@ -66,6 +70,8 @@ _gdk_x11_device_manager_new (GdkDisplay *display)
device_manager_xi2 = g_object_new (GDK_TYPE_X11_DEVICE_MANAGER_XI2,
"display", display,
"opcode", opcode,
"major", major,
"minor", minor,
NULL);
return GDK_DEVICE_MANAGER (device_manager_xi2);
+266 -63
View File
@@ -29,6 +29,7 @@
#include "gdkprivate-x11.h"
#include "gdkintl.h"
#include "gdkkeysyms.h"
#include "gdkinternals.h"
#ifdef XINPUT_2
@@ -49,6 +50,8 @@ struct _GdkX11DeviceManagerXI2
GList *devices;
gint opcode;
gint major;
gint minor;
};
struct _GdkX11DeviceManagerXI2Class
@@ -96,7 +99,9 @@ static GdkWindow * gdk_x11_device_manager_xi2_get_window (GdkEventTra
enum {
PROP_0,
PROP_OPCODE
PROP_OPCODE,
PROP_MAJOR,
PROP_MINOR
};
static void
@@ -120,6 +125,20 @@ gdk_x11_device_manager_xi2_class_init (GdkX11DeviceManagerXI2Class *klass)
P_("Opcode for XInput2 requests"),
0, G_MAXINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (object_class,
PROP_MAJOR,
g_param_spec_int ("major",
P_("Major"),
P_("Major version number"),
0, G_MAXINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (object_class,
PROP_MINOR,
g_param_spec_int ("minor",
P_("Minor"),
P_("Minor version number"),
0, G_MAXINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
}
static void
@@ -148,8 +167,10 @@ _gdk_x11_device_manager_xi2_select_events (GdkDeviceManager *device_manager,
static void
translate_valuator_class (GdkDisplay *display,
GdkDevice *device,
XIValuatorClassInfo *info,
gint n_valuator)
Atom valuator_label,
gdouble min,
gdouble max,
gdouble resolution)
{
static gboolean initialized = FALSE;
static Atom label_atoms [GDK_AXIS_LAST] = { 0 };
@@ -170,24 +191,19 @@ translate_valuator_class (GdkDisplay *display,
for (i = GDK_AXIS_IGNORE; i < GDK_AXIS_LAST; i++)
{
if (label_atoms[i] == info->label)
if (label_atoms[i] == valuator_label)
{
use = i;
break;
}
}
if (info->label != None)
label = gdk_x11_xatom_to_atom_for_display (display, info->label);
if (valuator_label != None)
label = gdk_x11_xatom_to_atom_for_display (display, valuator_label);
else
label = GDK_NONE;
_gdk_device_add_axis (device,
label,
use,
info->min,
info->max,
info->resolution);
_gdk_device_add_axis (device, label, use, min, max, resolution);
}
static void
@@ -196,7 +212,7 @@ translate_device_classes (GdkDisplay *display,
XIAnyClassInfo **classes,
guint n_classes)
{
gint i, n_valuator = 0;
gint i;
g_object_freeze_notify (G_OBJECT (device));
@@ -218,11 +234,41 @@ translate_device_classes (GdkDisplay *display,
}
break;
case XIValuatorClass:
translate_valuator_class (display, device,
(XIValuatorClassInfo *) class_info,
n_valuator);
n_valuator++;
{
XIValuatorClassInfo *valuator_info = (XIValuatorClassInfo *) class_info;
translate_valuator_class (display, device,
valuator_info->label,
valuator_info->min,
valuator_info->max,
valuator_info->resolution);
}
break;
#ifdef XINPUT_2_2
case XIScrollClass:
{
XIScrollClassInfo *scroll_info = (XIScrollClassInfo *) class_info;
XIValuatorClassInfo *scroll_valuator_info = NULL;
GdkScrollDirection direction;
gint j;
for (j = 0; j < n_classes; j++)
{
if (classes[j]->type == XIValuatorClass &&
((XIValuatorClassInfo *) classes[j])->number == scroll_info->number)
scroll_valuator_info = (XIValuatorClassInfo *) classes[j];
}
if (scroll_info->scroll_type == XIScrollTypeVertical)
direction = GDK_SCROLL_DOWN;
else
direction = GDK_SCROLL_RIGHT;
_gdk_x11_device_xi2_add_scroll_valuator (GDK_X11_DEVICE_XI2 (device),
scroll_info->number,
direction,
scroll_valuator_info->value);
}
#endif /* XINPUT_2_2 */
default:
/* Ignore */
break;
@@ -232,6 +278,27 @@ translate_device_classes (GdkDisplay *display,
g_object_thaw_notify (G_OBJECT (device));
}
static gint
count_device_touches (XIAnyClassInfo **classes,
guint n_classes)
{
#ifdef XINPUT_2_2
guint i;
for (i = 0; i < n_classes; i++)
{
XITouchClassInfo *valuator_info = (XITouchClassInfo *) classes[i];
if (valuator_info->type != XITouchClass)
continue;
return valuator_info->num_touches;
}
#endif
return 0;
}
static GdkDevice *
create_device (GdkDeviceManager *device_manager,
GdkDisplay *display,
@@ -244,6 +311,9 @@ create_device (GdkDeviceManager *device_manager,
if (dev->use == XIMasterKeyboard || dev->use == XISlaveKeyboard)
input_source = GDK_SOURCE_KEYBOARD;
else if (dev->use == XISlavePointer &&
count_device_touches (dev->classes, dev->num_classes) > 0)
input_source = GDK_SOURCE_TOUCH;
else
{
gchar *tmp_name;
@@ -254,6 +324,10 @@ create_device (GdkDeviceManager *device_manager,
input_source = GDK_SOURCE_ERASER;
else if (strstr (tmp_name, "cursor"))
input_source = GDK_SOURCE_CURSOR;
else if (strstr (tmp_name, "finger") ||
(strstr (tmp_name, "touch") &&
!strstr (tmp_name, "touchpad")))
input_source = GDK_SOURCE_TOUCH;
else if (strstr (tmp_name, "wacom") ||
strstr (tmp_name, "pen"))
input_source = GDK_SOURCE_PEN;
@@ -408,6 +482,8 @@ gdk_x11_device_manager_xi2_constructed (GObject *object)
display = gdk_device_manager_get_display (GDK_DEVICE_MANAGER (object));
xdisplay = GDK_DISPLAY_XDISPLAY (display);
g_assert (device_manager->major == 2);
masters = g_hash_table_new (NULL, NULL);
slaves = g_hash_table_new (NULL, NULL);
@@ -533,6 +609,12 @@ gdk_x11_device_manager_xi2_set_property (GObject *object,
case PROP_OPCODE:
device_manager->opcode = g_value_get_int (value);
break;
case PROP_MAJOR:
device_manager->major = g_value_get_int (value);
break;
case PROP_MINOR:
device_manager->minor = g_value_get_int (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -554,6 +636,12 @@ gdk_x11_device_manager_xi2_get_property (GObject *object,
case PROP_OPCODE:
g_value_set_int (value, device_manager->opcode);
break;
case PROP_MAJOR:
g_value_set_int (value, device_manager->major);
break;
case PROP_MINOR:
g_value_set_int (value, device_manager->minor);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -823,6 +911,11 @@ get_event_window (GdkEventTranslator *translator,
case XI_ButtonPress:
case XI_ButtonRelease:
case XI_Motion:
#ifdef XINPUT_2_2
case XI_TouchUpdate:
case XI_TouchBegin:
case XI_TouchEnd:
#endif /* XINPUT_2_2 */
{
XIDeviceEvent *xev = (XIDeviceEvent *) ev;
@@ -941,6 +1034,47 @@ gdk_x11_device_manager_xi2_translate_core_event (GdkEventTranslator *translator,
return TRUE;
}
static gboolean
scroll_valuators_changed (GdkX11DeviceXI2 *device,
XIValuatorState *valuators,
gdouble *dx,
gdouble *dy)
{
gdouble has_scroll_valuators = FALSE;
GdkScrollDirection direction;
guint n_axes, i, n_val;
gdouble *vals;
n_axes = gdk_device_get_n_axes (GDK_DEVICE (device));
vals = valuators->values;
*dx = *dy = 0;
n_val = 0;
for (i = 0; i < MIN (valuators->mask_len * 8, n_axes); i++)
{
if (!XIMaskIsSet (valuators->mask, i))
continue;
if (_gdk_x11_device_xi2_is_scroll_valuator (device, i, &direction))
{
gdouble delta;
has_scroll_valuators = TRUE;
delta = _gdk_x11_device_xi2_get_scroll_delta (device, i, vals[n_val]);
if (direction == GDK_SCROLL_UP ||
direction == GDK_SCROLL_DOWN)
*dy = delta;
else
*dx = delta;
}
n_val++;
}
return has_scroll_valuators;
}
static gboolean
gdk_x11_device_manager_xi2_translate_event (GdkEventTranslator *translator,
GdkDisplay *display,
@@ -1043,56 +1177,60 @@ gdk_x11_device_manager_xi2_translate_event (GdkEventTranslator *translator,
break;
case XI_ButtonPress:
case XI_ButtonRelease:
#ifdef XINPUT_2_2
case XI_TouchBegin:
case XI_TouchEnd:
#endif /* XINPUT_2_2 */
{
XIDeviceEvent *xev = (XIDeviceEvent *) ev;
GdkDevice *source_device;
switch (xev->detail)
if (ev->evtype == XI_ButtonPress &&
(xev->detail >= 4 && xev->detail <= 7))
{
case 4:
case 5:
case 6:
case 7:
/* Button presses of button 4-7 are scroll events */
if (ev->evtype == XI_ButtonPress)
{
event->scroll.type = GDK_SCROLL;
/* Button presses of button 4-7 are scroll events */
event->scroll.type = GDK_SCROLL;
if (xev->detail == 4)
event->scroll.direction = GDK_SCROLL_UP;
else if (xev->detail == 5)
event->scroll.direction = GDK_SCROLL_DOWN;
else if (xev->detail == 6)
event->scroll.direction = GDK_SCROLL_LEFT;
else
event->scroll.direction = GDK_SCROLL_RIGHT;
if (xev->detail == 4)
event->scroll.direction = GDK_SCROLL_UP;
else if (xev->detail == 5)
event->scroll.direction = GDK_SCROLL_DOWN;
else if (xev->detail == 6)
event->scroll.direction = GDK_SCROLL_LEFT;
else
event->scroll.direction = GDK_SCROLL_RIGHT;
event->scroll.window = window;
event->scroll.time = xev->time;
event->scroll.x = (gdouble) xev->event_x;
event->scroll.y = (gdouble) xev->event_y;
event->scroll.x_root = (gdouble) xev->root_x;
event->scroll.y_root = (gdouble) xev->root_y;
event->scroll.window = window;
event->scroll.time = xev->time;
event->scroll.x = (gdouble) xev->event_x;
event->scroll.y = (gdouble) xev->event_y;
event->scroll.x_root = (gdouble) xev->root_x;
event->scroll.y_root = (gdouble) xev->root_y;
event->scroll.delta_x = 0;
event->scroll.delta_y = 0;
event->scroll.device = g_hash_table_lookup (device_manager->id_table,
GUINT_TO_POINTER (xev->deviceid));
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);
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_x11_device_xi2_translate_state (&xev->mods, &xev->buttons, &xev->group);
break;
}
/* Button presses of button 4-7 are scroll events, so ignore the release */
else if (ev->evtype == XI_ButtonRelease)
{
return_val = FALSE;
break;
}
/* else (XI_ButtonRelease) fall thru */
default:
event->button.type = (ev->evtype == XI_ButtonPress) ? GDK_BUTTON_PRESS : GDK_BUTTON_RELEASE;
event->scroll.state = _gdk_x11_device_xi2_translate_state (&xev->mods, &xev->buttons, &xev->group);
if (xev->flags & XIPointerEmulated)
_gdk_event_set_pointer_emulated (event, TRUE);
}
else
{
#ifdef XINPUT_2_2
if (ev->evtype == XI_TouchBegin)
event->button.type = GDK_TOUCH_PRESS;
else if (ev->evtype == XI_TouchEnd)
event->button.type = GDK_TOUCH_RELEASE;
else
#endif /* XINPUT_2_2 */
event->button.type = (ev->evtype == XI_ButtonPress) ? GDK_BUTTON_PRESS : GDK_BUTTON_RELEASE;
event->button.window = window;
event->button.time = xev->time;
@@ -1124,9 +1262,25 @@ gdk_x11_device_manager_xi2_translate_event (GdkEventTranslator *translator,
}
event->button.state = _gdk_x11_device_xi2_translate_state (&xev->mods, &xev->buttons, &xev->group);
event->button.button = xev->detail;
if (ev->evtype == XI_TouchBegin)
event->button.state |= GDK_BUTTON1_MASK;
#ifdef XINPUT_2_2
if (ev->evtype == XI_TouchBegin ||
ev->evtype == XI_TouchEnd)
{
event->button.button = 1;
event->button.touch_id = xev->detail;
}
else
#endif /* XINPUT_2_2 */
event->button.button = xev->detail;
}
if (xev->flags & (XIPointerEmulated | XITouchEmulatingPointer))
_gdk_event_set_pointer_emulated (event, TRUE);
if (return_val == FALSE)
break;
@@ -1142,11 +1296,53 @@ gdk_x11_device_manager_xi2_translate_event (GdkEventTranslator *translator,
break;
}
case XI_Motion:
#ifdef XINPUT_2_2
case XI_TouchUpdate:
#endif /* XINPUT_2_2 */
{
XIDeviceEvent *xev = (XIDeviceEvent *) ev;
GdkDevice *source_device;
gdouble delta_x, delta_y;
event->motion.type = GDK_MOTION_NOTIFY;
source_device = g_hash_table_lookup (device_manager->id_table,
GUINT_TO_POINTER (xev->sourceid));
if (scroll_valuators_changed (GDK_X11_DEVICE_XI2 (source_device),
&xev->valuators, &delta_x, &delta_y))
{
event->scroll.type = GDK_SCROLL;
event->scroll.direction = GDK_SCROLL_SMOOTH;
event->scroll.window = window;
event->scroll.time = xev->time;
event->scroll.x = (gdouble) xev->event_x;
event->scroll.y = (gdouble) xev->event_y;
event->scroll.x_root = (gdouble) xev->root_x;
event->scroll.y_root = (gdouble) xev->root_y;
event->scroll.delta_x = delta_x;
event->scroll.delta_y = delta_y;
event->scroll.device = g_hash_table_lookup (device_manager->id_table,
GUINT_TO_POINTER (xev->deviceid));
gdk_event_set_source_device (event, source_device);
event->scroll.state = _gdk_x11_device_xi2_translate_state (&xev->mods, &xev->buttons, &xev->group);
break;
}
if (ev->evtype == XI_Motion)
{
event->motion.touch_id = 0;
event->motion.type = GDK_MOTION_NOTIFY;
}
#ifdef XINPUT_2_2
else
{
event->motion.touch_id = xev->detail;
event->motion.type = GDK_TOUCH_MOTION;
}
#endif
event->motion.window = window;
@@ -1159,12 +1355,16 @@ gdk_x11_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_x11_device_xi2_translate_state (&xev->mods, &xev->buttons, &xev->group);
if (ev->evtype == XI_TouchUpdate)
event->motion.state |= GDK_BUTTON1_MASK;
if (xev->flags & (XIPointerEmulated | XITouchEmulatingPointer))
_gdk_event_set_pointer_emulated (event, TRUE);
/* There doesn't seem to be motion hints in XI */
event->motion.is_hint = FALSE;
@@ -1280,7 +1480,8 @@ gdk_x11_device_manager_xi2_get_handled_events (GdkEventTranslator *translator)
GDK_BUTTON2_MOTION_MASK |
GDK_BUTTON3_MOTION_MASK |
GDK_BUTTON_MOTION_MASK |
GDK_FOCUS_CHANGE_MASK);
GDK_FOCUS_CHANGE_MASK |
GDK_TOUCH_MASK);
}
static void
@@ -1294,7 +1495,9 @@ gdk_x11_device_manager_xi2_select_window_events (GdkEventTranslator *translator,
device_manager = GDK_DEVICE_MANAGER (translator);
event_mask.deviceid = XIAllMasterDevices;
event_mask.mask = _gdk_x11_device_xi2_translate_event_mask (evmask, &event_mask.mask_len);
event_mask.mask = _gdk_x11_device_xi2_translate_event_mask (GDK_X11_DEVICE_MANAGER_XI2 (device_manager),
evmask,
&event_mask.mask_len);
_gdk_x11_device_manager_xi2_select_events (device_manager, window, &event_mask);
g_free (event_mask.mask);
+4 -1
View File
@@ -1579,9 +1579,12 @@ device_grab_update_callback (GdkDisplay *display,
gpointer data,
gulong serial)
{
GdkPointerWindowInfo *pointer_info;
GdkDevice *device = data;
_gdk_display_device_grab_update (display, device, NULL, serial);
pointer_info = _gdk_display_get_pointer_info (display, device);
_gdk_display_device_grab_update (display, device,
pointer_info->last_slave, serial);
}
#define XSERVER_TIME_IS_LATER(time1, time2) \
+13 -2
View File
@@ -247,8 +247,9 @@ void _gdk_x11_device_xi_translate_axes (GdkDevice *device,
#endif
#ifdef XINPUT_2
guchar * _gdk_x11_device_xi2_translate_event_mask (GdkEventMask event_mask,
gint *len);
guchar * _gdk_x11_device_xi2_translate_event_mask (GdkX11DeviceManagerXI2 *device_manager_xi2,
GdkEventMask event_mask,
gint *len);
guint _gdk_x11_device_xi2_translate_state (XIModifierState *mods_state,
XIButtonState *buttons_state,
XIGroupState *group_state);
@@ -256,6 +257,16 @@ gint _gdk_x11_device_xi2_get_id (GdkX11DeviceXI2 *device);
GdkDevice * _gdk_x11_device_manager_xi2_lookup (GdkX11DeviceManagerXI2 *device_manager_xi2,
gint device_id);
void _gdk_x11_device_xi2_add_scroll_valuator (GdkX11DeviceXI2 *device,
guint n_valuator,
GdkScrollDirection direction,
gdouble current_value);
gboolean _gdk_x11_device_xi2_is_scroll_valuator (GdkX11DeviceXI2 *device,
guint n_valuator,
GdkScrollDirection *direction);
gdouble _gdk_x11_device_xi2_get_scroll_delta (GdkX11DeviceXI2 *device,
guint n_valuator,
gdouble valuator_value);
#endif
+2
View File
@@ -249,6 +249,7 @@ gtk_public_h_sources = \
gtkfontchooserdialog.h \
gtkfontchooserwidget.h \
gtkframe.h \
gtkgesturesinterpreter.h \
gtkgradient.h \
gtkgrid.h \
gtkiconfactory.h \
@@ -657,6 +658,7 @@ gtk_base_c_sources = \
gtkfontchooserutils.c \
gtkfontchooserwidget.c \
gtkframe.c \
gtkgesturesinterpreter.c \
gtkgradient.c \
gtkgrid.c \
gtkiconcache.c \
+6
View File
@@ -362,3 +362,9 @@ GtkCalendar.button:hover {
border-width: 0;
padding: 2px;
}
.press-and-hold {
background-color: alpha (@bg_color, 0.5);
color: alpha (lighter (@selected_bg_color), 0.8);
border-width: 10;
}
+1
View File
@@ -109,6 +109,7 @@
#include <gtk/gtkfontchooserdialog.h>
#include <gtk/gtkfontchooserwidget.h>
#include <gtk/gtkframe.h>
#include <gtk/gtkgesturesinterpreter.h>
#include <gtk/gtkgradient.h>
#include <gtk/gtkgrid.h>
#include <gtk/gtkiconfactory.h>
+38 -2
View File
@@ -1813,7 +1813,8 @@ gtk_button_button_press (GtkWidget *widget,
GtkButton *button;
GtkButtonPrivate *priv;
if (event->type == GDK_BUTTON_PRESS)
if (event->type == GDK_BUTTON_PRESS ||
event->type == GDK_TOUCH_PRESS)
{
button = GTK_BUTTON (widget);
priv = button->priv;
@@ -1932,6 +1933,40 @@ gtk_real_button_pressed (GtkButton *button)
gtk_button_update_state (button);
}
static gboolean
touch_release_in_button (GtkButton *button)
{
GtkButtonPrivate *priv;
gint width, height;
GdkEvent *event;
gdouble x, y;
priv = button->priv;
event = gtk_get_current_event ();
if (!event)
return FALSE;
if (event->type != GDK_TOUCH_RELEASE ||
event->button.window != priv->event_window)
{
gdk_event_free (event);
return FALSE;
}
gdk_event_get_coords (event, &x, &y);
width = gdk_window_get_width (priv->event_window);
height = gdk_window_get_height (priv->event_window);
gdk_event_free (event);
if (x >= 0 && x <= width &&
y >= 0 && y <= height)
return TRUE;
return FALSE;
}
static void
gtk_real_button_released (GtkButton *button)
{
@@ -1944,7 +1979,8 @@ gtk_real_button_released (GtkButton *button)
if (priv->activate_timeout)
return;
if (priv->in_button)
if (priv->in_button ||
touch_release_in_button (button))
gtk_button_clicked (button);
gtk_button_update_state (button);
+101 -26
View File
@@ -147,6 +147,8 @@ struct _GtkEntryPrivate
GtkShadowType shadow_type;
GtkWidget *popup_menu;
GdkDevice *device;
GdkDevice *completion_device;
GdkWindow *text_area;
@@ -481,6 +483,11 @@ static void gtk_entry_toggle_overwrite (GtkEntry *entry);
static void gtk_entry_select_all (GtkEntry *entry);
static void gtk_entry_real_activate (GtkEntry *entry);
static gboolean gtk_entry_popup_menu (GtkWidget *widget);
static gboolean gtk_entry_press_and_hold (GtkWidget *widget,
GdkDevice *device,
GtkPressAndHoldAction action,
gint x,
gint y);
static void keymap_direction_changed (GdkKeymap *keymap,
GtkEntry *entry);
@@ -544,9 +551,13 @@ static void gtk_entry_paste (GtkEntry *entry,
GdkAtom selection);
static void gtk_entry_update_primary_selection (GtkEntry *entry);
static void gtk_entry_do_popup (GtkEntry *entry,
GdkEventButton *event);
GdkDevice *device,
guint32 _time,
guint button);
static gboolean gtk_entry_mnemonic_activate (GtkWidget *widget,
gboolean group_cycling);
static void gtk_entry_grab_notify (GtkWidget *widget,
gboolean was_grabbed);
static void gtk_entry_check_cursor_blink (GtkEntry *entry);
static void gtk_entry_pend_cursor_blink (GtkEntry *entry);
static void gtk_entry_reset_blink_time (GtkEntry *entry);
@@ -691,6 +702,7 @@ gtk_entry_class_init (GtkEntryClass *class)
widget_class->state_flags_changed = gtk_entry_state_flags_changed;
widget_class->screen_changed = gtk_entry_screen_changed;
widget_class->mnemonic_activate = gtk_entry_mnemonic_activate;
widget_class->grab_notify = gtk_entry_grab_notify;
widget_class->drag_drop = gtk_entry_drag_drop;
widget_class->drag_motion = gtk_entry_drag_motion;
@@ -700,6 +712,7 @@ gtk_entry_class_init (GtkEntryClass *class)
widget_class->drag_data_delete = gtk_entry_drag_data_delete;
widget_class->popup_menu = gtk_entry_popup_menu;
widget_class->press_and_hold = gtk_entry_press_and_hold;
class->move_cursor = gtk_entry_move_cursor;
class->insert_at_cursor = gtk_entry_insert_at_cursor;
@@ -3810,7 +3823,8 @@ gtk_entry_button_press (GtkWidget *widget,
gtk_entry_reset_blink_time (entry);
priv->button = event->button;
priv->device = gdk_event_get_device ((GdkEvent *) event);
if (!gtk_widget_has_focus (widget))
{
priv->in_click = TRUE;
@@ -3822,8 +3836,10 @@ gtk_entry_button_press (GtkWidget *widget,
if (gdk_event_triggers_context_menu ((GdkEvent *) event))
{
gtk_entry_do_popup (entry, event);
gtk_entry_do_popup (entry, event->device,
event->time, event->button);
priv->button = 0; /* Don't wait for release, since the menu will gtk_grab_add */
priv->device = NULL;
return TRUE;
}
@@ -4001,9 +4017,10 @@ gtk_entry_button_release (GtkWidget *widget,
priv->in_drag = 0;
}
priv->button = 0;
priv->device = NULL;
gtk_entry_update_primary_selection (entry);
return TRUE;
@@ -4112,7 +4129,8 @@ gtk_entry_motion_notify (GtkWidget *widget,
priv->in_drag = FALSE;
priv->button = 0;
priv->device = NULL;
gtk_target_list_unref (target_list);
}
}
@@ -8553,6 +8571,26 @@ gtk_entry_mnemonic_activate (GtkWidget *widget,
return TRUE;
}
static void
gtk_entry_grab_notify (GtkWidget *widget,
gboolean was_grabbed)
{
GtkEntryPrivate *priv;
priv = GTK_ENTRY (widget)->priv;
if (priv->device &&
gtk_widget_device_is_shadowed (widget, priv->device))
{
/* Unset button so we don't expect
* a button release anymore
*/
priv->button = 0;
priv->device = NULL;
priv->in_drag = FALSE;
}
}
static void
append_action_signal (GtkEntry *entry,
GtkWidget *menu,
@@ -8755,15 +8793,17 @@ popup_targets_received (GtkClipboard *clipboard,
info_entry_priv->popup_menu);
if (info->device)
if (gdk_device_get_source (info->device) != GDK_SOURCE_KEYBOARD)
gtk_menu_popup_for_device (GTK_MENU (info_entry_priv->popup_menu),
info->device, NULL, NULL, NULL, NULL, NULL,
info->button, info->time);
else
{
gtk_menu_popup (GTK_MENU (info_entry_priv->popup_menu), NULL, NULL,
popup_position_func, entry,
0, gtk_get_current_event_time ());
gtk_menu_popup_for_device (GTK_MENU (info_entry_priv->popup_menu),
info->device, NULL, NULL,
popup_position_func,
entry, NULL,
0, info->time);
gtk_menu_shell_select_first (GTK_MENU_SHELL (info_entry_priv->popup_menu), FALSE);
}
}
@@ -8771,10 +8811,12 @@ popup_targets_received (GtkClipboard *clipboard,
g_object_unref (entry);
g_slice_free (PopupInfo, info);
}
static void
gtk_entry_do_popup (GtkEntry *entry,
GdkEventButton *event)
GdkDevice *device,
guint32 _time,
guint button)
{
PopupInfo *info = g_slice_new (PopupInfo);
@@ -8783,19 +8825,10 @@ gtk_entry_do_popup (GtkEntry *entry,
* we get them, then we actually pop up the menu.
*/
info->entry = g_object_ref (entry);
if (event)
{
info->button = event->button;
info->time = event->time;
info->device = event->device;
}
else
{
info->button = 0;
info->time = gtk_get_current_event_time ();
info->device = NULL;
}
info->button = button;
info->time = _time;
info->device = device;
gtk_clipboard_request_contents (gtk_widget_get_clipboard (GTK_WIDGET (entry), GDK_SELECTION_CLIPBOARD),
gdk_atom_intern_static_string ("TARGETS"),
@@ -8806,7 +8839,49 @@ gtk_entry_do_popup (GtkEntry *entry,
static gboolean
gtk_entry_popup_menu (GtkWidget *widget)
{
gtk_entry_do_popup (GTK_ENTRY (widget), NULL);
gtk_entry_do_popup (GTK_ENTRY (widget),
gtk_get_current_event_device (),
gtk_get_current_event_time (),
0);
return TRUE;
}
static gboolean
gtk_entry_press_and_hold (GtkWidget *widget,
GdkDevice *device,
GtkPressAndHoldAction action,
gint x,
gint y)
{
if (action == GTK_PRESS_AND_HOLD_TRIGGER)
gtk_entry_do_popup (GTK_ENTRY (widget),
device, GDK_CURRENT_TIME, 1);
else if (action == GTK_PRESS_AND_HOLD_QUERY)
{
GtkEntryPrivate *priv;
GdkDevice *source_device;
GdkEvent *event;
priv = GTK_ENTRY (widget)->priv;
event = gtk_get_current_event ();
if (!event)
return FALSE;
source_device = gdk_event_get_source_device (event);
if (gdk_device_get_source (source_device) != GDK_SOURCE_TOUCH ||
(event->type != GDK_BUTTON_PRESS &&
event->type != GDK_TOUCH_PRESS) ||
event->button.window != priv->text_area)
{
gdk_event_free (event);
return FALSE;
}
gdk_event_free (event);
}
return TRUE;
}
+72
View File
@@ -932,6 +932,78 @@ typedef enum {
GTK_BORDER_STYLE_RIDGE
} GtkBorderStyle;
/**
* GtkCapturedEventFlags:
* @GTK_CAPTURED_EVENT_NONE: Event goes uncaptured
* @GTK_CAPTURED_EVENT_HANDLED: The event was handled
* @GTK_CAPTURED_EVENT_STORE: Store for later propagation, see
* gtk_widget_release_captured_events()
*
* Describes how an event in the #GtkWidget::captured-event handler
* is handled.
*/
typedef enum {
GTK_CAPTURED_EVENT_NONE = 0,
GTK_CAPTURED_EVENT_HANDLED = 1 << 0,
GTK_CAPTURED_EVENT_STORE = 1 << 1
} GtkCapturedEventFlags;
/**
* GtkKineticScrollingFlags:
* @GTK_KINETIC_SCROLLING_NONE: No kinetic scrolling.
* @GTK_KINETIC_SCROLLING_ENABLED: Kinetic scrolling is enabled.
* @GTK_KINETIC_SCROLLING_CAPTURE_BUTTON_PRESS: The first button
* press is captured by the scrolled window, and then replayed
* if the button press is meant to go to the child widget. This
* flag should be enabled if the child widget(s) perform
* non-reversible actions on #GtkWidget::button-press-event.
* If the widget does not do so, and handles
* #GtkWidget::grab-broken-event, it might be better off without
* this flag.
*
* Describes the kinetic scrolling behavior of a #GtkScrolledWindow
*/
typedef enum {
GTK_KINETIC_SCROLLING_NONE = 0,
GTK_KINETIC_SCROLLING_ENABLED = 1 << 0,
GTK_KINETIC_SCROLLING_CAPTURE_BUTTON_PRESS = 1 << 1
} GtkKineticScrollingFlags;
/**
* GtkGestureType:
* @GTK_GESTURE_SWIPE_RIGHT: A swipe from left to right
* @GTK_GESTURE_SWIPE_LEFT: A swipe from right to left
* @GTK_GESTURE_SWIPE_UP: A swipe from bottom to top
* @GTK_GESTURE_SWIPE_DOWN: A swipe from top to bottom
* @GTK_GESTURE_CIRCULAR_CLOCKWISE: A circular clockwise movement
* @GTK_GESTURE_CIRCULAR_COUNTERCLOCKWISE: A circular counterclockwise movement
*
* Describes the stock gestures handled by GTK+.
*/
typedef enum {
GTK_GESTURE_SWIPE_RIGHT = 1,
GTK_GESTURE_SWIPE_LEFT,
GTK_GESTURE_SWIPE_UP,
GTK_GESTURE_SWIPE_DOWN,
GTK_GESTURE_CIRCULAR_CLOCKWISE,
GTK_GESTURE_CIRCULAR_COUNTERCLOCKWISE
} GtkGestureType;
/**
* GtkGestureFlags:
* @GTK_GESTURE_FLAG_NONE: Gesture is interpreted as-is.
* @GTK_GESTURE_FLAG_IGNORE_INITIAL_ORIENTATION:
* The initial orientation is ignored in comparisons,
* so the gesture doesn't have a sense of direction,
* this is useful for i.e. circular strokes.
*
* Flags accepted by gtk_gesture_new()
*/
typedef enum {
GTK_GESTURE_FLAG_NONE = 0,
GTK_GESTURE_FLAG_IGNORE_INITIAL_ORIENTATION = 1 << 1
} GtkGestureFlags;
G_END_DECLS
File diff suppressed because it is too large Load Diff
+121
View File
@@ -0,0 +1,121 @@
/* GTK - The GIMP Toolkit
* Copyright (C) 2011 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_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#ifndef __GTK_GESTURES_INTERPRETER_H__
#define __GTK_GESTURES_INTERPRETER_H__
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define GTK_TYPE_GESTURE (gtk_gesture_get_type ())
#define GTK_TYPE_GESTURE_STROKE (gtk_gesture_stroke_get_type ())
#define GTK_TYPE_GESTURES_INTERPRETER (gtk_gestures_interpreter_get_type ())
#define GTK_GESTURES_INTERPRETER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_GESTURES_INTERPRETER, GtkGesturesInterpreter))
#define GTK_GESTURES_INTERPRETER_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), GTK_TYPE_GESTURES_INTERPRETER, GtkGesturesInterpreterClass))
#define GTK_IS_GESTURES_INTERPRETER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_GESTURES_INTERPRETER))
#define GTK_IS_GESTURES_INTERPRETER_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), GTK_TYPE_GESTURES_INTERPRETER))
#define GTK_GESTURES_INTERPRETER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_GESTURES_INTERPRETER, GtkGesturesInterpreterClass))
typedef struct _GtkGestureStroke GtkGestureStroke;
typedef struct _GtkGesture GtkGesture;
typedef struct _GtkGesturesInterpreter GtkGesturesInterpreter;
typedef struct _GtkGesturesInterpreterClass GtkGesturesInterpreterClass;
struct _GtkGesturesInterpreter
{
GObject parent_object;
gpointer priv;
};
struct _GtkGesturesInterpreterClass
{
GObjectClass parent_class;
void (* events_vectorized) (GtkGesturesInterpreter *interpreter,
GtkGesture *gesture);
void (* gesture_detected) (GtkGesturesInterpreter *interpreter,
guint gesture_id,
gdouble confidence);
};
/* Gesture stroke */
GType gtk_gesture_stroke_get_type (void) G_GNUC_CONST;
GtkGestureStroke * gtk_gesture_stroke_new (void);
GtkGestureStroke * gtk_gesture_stroke_copy (const GtkGestureStroke *stroke);
void gtk_gesture_stroke_free (GtkGestureStroke *stroke);
void gtk_gesture_stroke_append_vector (GtkGestureStroke *stroke,
gdouble angle,
guint length);
guint gtk_gesture_stroke_get_n_vectors (const GtkGestureStroke *stroke);
gboolean gtk_gesture_stroke_get_vector (const GtkGestureStroke *stroke,
guint n_vector,
gdouble *angle,
guint *length,
gdouble *relative_length);
/* Gesture */
GType gtk_gesture_get_type (void) G_GNUC_CONST;
GtkGesture * gtk_gesture_new (const GtkGestureStroke *stroke,
GtkGestureFlags flags);
void gtk_gesture_add_stroke (GtkGesture *gesture,
const GtkGestureStroke *stroke,
gint dx,
gint dy);
GtkGesture * gtk_gesture_copy (const GtkGesture *gesture);
void gtk_gesture_free (GtkGesture *gesture);
GtkGestureFlags gtk_gesture_get_flags (const GtkGesture *gesture);
guint gtk_gesture_get_n_strokes (const GtkGesture *gesture);
const GtkGestureStroke * gtk_gesture_get_stroke (const GtkGesture *gesture,
guint n_stroke,
gint *dx,
gint *dy);
guint gtk_gesture_register (const GtkGesture *gesture);
guint gtk_gesture_register_static (const GtkGesture *gesture);
const GtkGesture * gtk_gesture_lookup (guint gesture_id);
/* Gestures interpreter */
GType gtk_gestures_interpreter_get_type (void) G_GNUC_CONST;
GtkGesturesInterpreter * gtk_gestures_interpreter_new (void);
gboolean gtk_gestures_interpreter_add_gesture (GtkGesturesInterpreter *interpreter,
guint gesture_id);
void gtk_gestures_interpreter_remove_gesture (GtkGesturesInterpreter *interpreter,
guint gesture_id);
guint gtk_gestures_interpreter_get_n_active_strokes (GtkGesturesInterpreter *interpreter);
gboolean gtk_gestures_interpreter_feed_event (GtkGesturesInterpreter *interpreter,
GdkEvent *event);
gboolean gtk_gestures_interpreter_finish (GtkGesturesInterpreter *interpreter,
guint *gesture_id);
G_END_DECLS
#endif /* __GTK_GESTURES_INTERPRETER_H__ */
+8 -6
View File
@@ -1285,12 +1285,13 @@ gtk_icon_view_realize (GtkWidget *widget)
attributes.width = MAX (icon_view->priv->width, allocation.width);
attributes.height = MAX (icon_view->priv->height, allocation.height);
attributes.event_mask = (GDK_EXPOSURE_MASK |
GDK_SCROLL_MASK |
GDK_POINTER_MOTION_MASK |
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_KEY_PRESS_MASK |
GDK_KEY_RELEASE_MASK) |
GDK_SCROLL_MASK |
GDK_SMOOTH_SCROLL_MASK |
GDK_POINTER_MOTION_MASK |
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_KEY_PRESS_MASK |
GDK_KEY_RELEASE_MASK) |
gtk_widget_get_events (widget);
icon_view->priv->bin_window = gdk_window_new (window,
@@ -1302,6 +1303,7 @@ gtk_icon_view_realize (GtkWidget *widget)
gtk_style_context_save (context);
gtk_style_context_add_class (context, GTK_STYLE_CLASS_VIEW);
gtk_style_context_set_background (context, icon_view->priv->bin_window);
gtk_style_context_set_background (context, window);
gtk_style_context_restore (context);
gdk_window_show (icon_view->priv->bin_window);
+56 -13
View File
@@ -437,6 +437,11 @@ static void gtk_label_hierarchy_changed (GtkWidget *widget,
static void gtk_label_screen_changed (GtkWidget *widget,
GdkScreen *old_screen);
static gboolean gtk_label_popup_menu (GtkWidget *widget);
static gboolean gtk_label_press_and_hold (GtkWidget *widget,
GdkDevice *device,
GtkPressAndHoldAction action,
gint x,
gint y);
static void gtk_label_create_window (GtkLabel *label);
static void gtk_label_destroy_window (GtkLabel *label);
@@ -490,7 +495,9 @@ static void gtk_label_move_cursor (GtkLabel *label,
static void gtk_label_copy_clipboard (GtkLabel *label);
static void gtk_label_select_all (GtkLabel *label);
static void gtk_label_do_popup (GtkLabel *label,
GdkEventButton *event);
GdkDevice *device,
guint32 _time,
guint button);
static gint gtk_label_move_forward_word (GtkLabel *label,
gint start);
static gint gtk_label_move_backward_word (GtkLabel *label,
@@ -585,6 +592,7 @@ gtk_label_class_init (GtkLabelClass *class)
widget_class->drag_data_get = gtk_label_drag_data_get;
widget_class->grab_focus = gtk_label_grab_focus;
widget_class->popup_menu = gtk_label_popup_menu;
widget_class->press_and_hold = gtk_label_press_and_hold;
widget_class->focus = gtk_label_focus;
widget_class->get_request_mode = gtk_label_get_request_mode;
widget_class->get_preferred_width = gtk_label_get_preferred_width;
@@ -4638,7 +4646,8 @@ gtk_label_button_press (GtkWidget *widget,
if (gdk_event_triggers_context_menu ((GdkEvent *) event))
{
info->link_clicked = 1;
gtk_label_do_popup (label, event);
gtk_label_do_popup (label, event->device,
event->time, event->button);
return TRUE;
}
else if (event->button == GDK_BUTTON_PRIMARY)
@@ -4656,7 +4665,8 @@ gtk_label_button_press (GtkWidget *widget,
if (gdk_event_triggers_context_menu ((GdkEvent *) event))
{
gtk_label_do_popup (label, event);
gtk_label_do_popup (label, event->device,
event->time, event->button);
return TRUE;
}
@@ -4921,6 +4931,8 @@ gtk_label_motion (GtkWidget *widget,
if ((event->state & GDK_BUTTON1_MASK) == 0)
return FALSE;
gdk_event_request_motions (event);
if (info->in_drag)
{
if (gtk_drag_check_threshold (widget,
@@ -6170,14 +6182,43 @@ copy_link_activate_cb (GtkMenuItem *menu_item,
static gboolean
gtk_label_popup_menu (GtkWidget *widget)
{
gtk_label_do_popup (GTK_LABEL (widget), NULL);
gtk_label_do_popup (GTK_LABEL (widget),
gtk_get_current_event_device (),
gtk_get_current_event_time (),
0);
return TRUE;
}
static gboolean
gtk_label_press_and_hold (GtkWidget *widget,
GdkDevice *device,
GtkPressAndHoldAction action,
gint x,
gint y)
{
if (action == GTK_PRESS_AND_HOLD_TRIGGER)
gtk_label_do_popup (GTK_LABEL (widget),
device, GDK_CURRENT_TIME, 1);
else if (action == GTK_PRESS_AND_HOLD_QUERY)
{
GdkDevice *source_device;
GdkEvent *event;
event = gtk_get_current_event ();
source_device = gdk_event_get_source_device (event);
if (gdk_device_get_source (source_device) != GDK_SOURCE_TOUCH)
return FALSE;
}
return TRUE;
}
static void
gtk_label_do_popup (GtkLabel *label,
GdkEventButton *event)
GdkDevice *device,
guint32 _time,
guint button)
{
GtkLabelPrivate *priv = label->priv;
GtkWidget *menuitem;
@@ -6199,7 +6240,7 @@ gtk_label_do_popup (GtkLabel *label,
have_selection =
priv->select_info->selection_anchor != priv->select_info->selection_end;
if (event)
if (gdk_device_get_source (device) != GDK_SOURCE_KEYBOARD)
{
if (priv->select_info->link_clicked)
link = priv->select_info->active_link;
@@ -6259,15 +6300,17 @@ gtk_label_do_popup (GtkLabel *label,
g_signal_emit (label, signals[POPULATE_POPUP], 0, menu);
if (event)
gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
NULL, NULL,
event->button, event->time);
if (gdk_device_get_source (device) != GDK_SOURCE_KEYBOARD)
gtk_menu_popup_for_device (GTK_MENU (menu), device,
NULL, NULL, NULL, NULL, NULL,
button, _time);
else
{
gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
popup_position_func, label,
0, gtk_get_current_event_time ());
gtk_menu_popup_for_device (GTK_MENU (menu),
device, NULL, NULL,
popup_position_func,
label, NULL,
0, _time);
gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
}
}
+2 -1
View File
@@ -894,7 +894,8 @@ gtk_layout_realize (GtkWidget *widget)
attributes.y = - gtk_adjustment_get_value (priv->vadjustment);
attributes.width = MAX (priv->width, allocation.width);
attributes.height = MAX (priv->height, allocation.height);
attributes.event_mask = GDK_EXPOSURE_MASK | GDK_SCROLL_MASK |
attributes.event_mask = GDK_EXPOSURE_MASK | GDK_SCROLL_MASK |
GDK_SMOOTH_SCROLL_MASK |
gtk_widget_get_events (widget);
priv->bin_window = gdk_window_new (window,
+210 -83
View File
@@ -1332,11 +1332,14 @@ rewrite_event_for_window (GdkEvent *event,
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
case GDK_TOUCH_PRESS:
case GDK_TOUCH_RELEASE:
rewrite_events_translate (event->any.window,
new_window,
&event->button.x, &event->button.y);
break;
case GDK_MOTION_NOTIFY:
case GDK_TOUCH_MOTION:
rewrite_events_translate (event->any.window,
new_window,
&event->motion.x, &event->motion.y);
@@ -1386,6 +1389,9 @@ rewrite_event_for_grabs (GdkEvent *event)
case GDK_PROXIMITY_OUT:
case GDK_KEY_PRESS:
case GDK_KEY_RELEASE:
case GDK_TOUCH_PRESS:
case GDK_TOUCH_RELEASE:
case GDK_TOUCH_MOTION:
display = gdk_window_get_display (event->any.window);
device = gdk_event_get_device (event);
@@ -1473,6 +1479,7 @@ gtk_main_do_event (GdkEvent *event)
{
GtkWidget *event_widget;
GtkWidget *grab_widget = NULL;
GtkWidget *topmost_widget = NULL;
GtkWindowGroup *window_group;
GdkEvent *rewritten_event = NULL;
GdkDevice *device;
@@ -1532,6 +1539,14 @@ gtk_main_do_event (GdkEvent *event)
if (!grab_widget)
grab_widget = gtk_window_group_get_current_grab (window_group);
/* Find out the topmost widget where captured event propagation
* should start, which is the widget holding the GTK+ grab
* if any, otherwise it's left NULL and events are emitted
* from the toplevel (or topmost parentless parent).
*/
if (grab_widget)
topmost_widget = grab_widget;
/* If the grab widget is an ancestor of the event widget
* then we send the event to the original event widget.
* This is the key to implementing modality.
@@ -1628,14 +1643,28 @@ gtk_main_do_event (GdkEvent *event)
case GDK_WINDOW_STATE:
case GDK_GRAB_BROKEN:
case GDK_DAMAGE:
gtk_widget_event (event_widget, event);
if (!_gtk_widget_captured_event (event_widget, event))
gtk_widget_event (event_widget, event);
break;
case GDK_SCROLL:
case GDK_BUTTON_PRESS:
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
gtk_propagate_event (grab_widget, event);
case GDK_TOUCH_PRESS:
if ((event->type == GDK_BUTTON_PRESS ||
event->type == GDK_TOUCH_PRESS) &&
event->button.button == 1)
{
/* Handle press and hold on the grab widget before propagating up,
* so a parent capturing events doesn't delay nor prevent a child
* from doing the press-and-hold action.
*/
_gtk_widget_press_and_hold_check_start (grab_widget, &event->button);
}
if (!_gtk_propagate_captured_event (grab_widget, event, topmost_widget))
gtk_propagate_event (grab_widget, event);
break;
case GDK_KEY_PRESS:
@@ -1684,22 +1713,43 @@ gtk_main_do_event (GdkEvent *event)
case GDK_BUTTON_RELEASE:
case GDK_PROXIMITY_IN:
case GDK_PROXIMITY_OUT:
gtk_propagate_event (grab_widget, event);
case GDK_TOUCH_MOTION:
case GDK_TOUCH_RELEASE:
case GDK_MULTITOUCH_ADDED:
case GDK_MULTITOUCH_REMOVED:
case GDK_MULTITOUCH_UPDATED:
if ((event->type == GDK_BUTTON_RELEASE ||
event->type == GDK_TOUCH_RELEASE) &&
event->button.button == 1)
_gtk_widget_press_and_hold_check_cancel (grab_widget, &event->button);
else if (event->type == GDK_MOTION_NOTIFY ||
event->type == GDK_TOUCH_MOTION)
_gtk_widget_press_and_hold_check_threshold (grab_widget,
&event->motion);
if (!_gtk_propagate_captured_event (grab_widget, event, topmost_widget))
gtk_propagate_event (grab_widget, event);
break;
case GDK_ENTER_NOTIFY:
_gtk_widget_set_device_window (event_widget,
gdk_event_get_device (event),
event->any.window);
if (gtk_widget_is_sensitive (grab_widget))
if (event->crossing.detail != GDK_NOTIFY_VIRTUAL &&
event->crossing.detail != GDK_NOTIFY_NONLINEAR_VIRTUAL)
_gtk_widget_set_device_window (event_widget,
gdk_event_get_device (event),
event->any.window);
if (gtk_widget_is_sensitive (grab_widget) &&
!_gtk_propagate_captured_event (grab_widget, event, topmost_widget))
gtk_widget_event (grab_widget, event);
break;
case GDK_LEAVE_NOTIFY:
_gtk_widget_set_device_window (event_widget,
gdk_event_get_device (event),
NULL);
if (gtk_widget_is_sensitive (grab_widget))
if (event->crossing.detail != GDK_NOTIFY_VIRTUAL &&
event->crossing.detail != GDK_NOTIFY_NONLINEAR_VIRTUAL)
_gtk_widget_set_device_window (event_widget,
gdk_event_get_device (event),
NULL);
if (gtk_widget_is_sensitive (grab_widget) &&
!_gtk_propagate_captured_event (grab_widget, event, topmost_widget))
gtk_widget_event (grab_widget, event);
break;
@@ -1727,11 +1777,22 @@ gtk_main_do_event (GdkEvent *event)
|| event->type == GDK_DRAG_ENTER
|| event->type == GDK_GRAB_BROKEN
|| event->type == GDK_MOTION_NOTIFY
|| event->type == GDK_TOUCH_MOTION
|| event->type == GDK_SCROLL)
{
_gtk_tooltip_handle_event (event);
}
/* Handle gestures for touch events */
if (gdk_event_get_touch_id (event, NULL))
{
_gtk_widget_gesture_stroke (grab_widget, event);
if (event->type == GDK_BUTTON_RELEASE ||
event->type == GDK_TOUCH_RELEASE)
_gtk_widget_gesture_finish (grab_widget);
}
tmp_list = current_events;
current_events = g_list_remove_link (current_events, tmp_list);
g_list_free_1 (tmp_list);
@@ -2329,6 +2390,135 @@ gtk_get_event_widget (GdkEvent *event)
return widget;
}
static gboolean
propagate_event_up (GtkWidget *widget,
GdkEvent *event,
GtkWidget *topmost)
{
gboolean handled_event = FALSE;
/* Propagate event up the widget tree so that
* parents can see the button and motion
* events of the children.
*/
while (TRUE)
{
GtkWidget *tmp;
g_object_ref (widget);
/* Scroll events are special cased here because it
* feels wrong when scrolling a GtkViewport, say,
* to have children of the viewport eat the scroll
* event
*/
if (!gtk_widget_is_sensitive (widget))
handled_event = event->type != GDK_SCROLL;
else
handled_event = gtk_widget_event (widget, event);
tmp = gtk_widget_get_parent (widget);
g_object_unref (widget);
if (widget == topmost)
break;
widget = tmp;
if (handled_event || !widget)
break;
}
return handled_event;
}
static gboolean
propagate_event_down (GtkWidget *widget,
GdkEvent *event,
GtkWidget *topmost)
{
gint handled_event = FALSE;
GList *widgets = NULL;
GList *l;
widgets = g_list_prepend (widgets, g_object_ref (widget));
while (widget && widget != topmost)
{
widget = gtk_widget_get_parent (widget);
if (!widget)
break;
widgets = g_list_prepend (widgets, g_object_ref (widget));
if (widget == topmost)
break;
}
for (l = widgets; l && !handled_event; l = g_list_next (l))
{
widget = (GtkWidget *)l->data;
if (!gtk_widget_is_sensitive (widget))
handled_event = TRUE;
else
handled_event = _gtk_widget_captured_event (widget, event);
}
g_list_free_full (widgets, (GDestroyNotify)g_object_unref);
return handled_event;
}
static gboolean
propagate_event (GtkWidget *widget,
GdkEvent *event,
gboolean captured,
GtkWidget *topmost)
{
gboolean handled_event = FALSE;
gboolean (* propagate_func) (GtkWidget *widget, GdkEvent *event);
propagate_func = captured ? _gtk_widget_captured_event : gtk_widget_event;
if (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE)
{
/* Only send key events within Window widgets to the Window
* The Window widget will in turn pass the
* key event on to the currently focused widget
* for that window.
*/
GtkWidget *window;
window = gtk_widget_get_toplevel (widget);
if (GTK_IS_WINDOW (window))
{
g_object_ref (widget);
/* If there is a grab within the window, give the grab widget
* a first crack at the key event
*/
if (widget != window && gtk_widget_has_grab (widget))
handled_event = propagate_func (widget, event);
if (!handled_event)
{
window = gtk_widget_get_toplevel (widget);
if (GTK_IS_WINDOW (window))
{
if (gtk_widget_is_sensitive (window))
handled_event = propagate_func (window, event);
}
}
g_object_unref (widget);
return handled_event;
}
}
/* Other events get propagated up/down the widget tree */
return captured ?
propagate_event_down (widget, event, topmost) :
propagate_event_up (widget, event, topmost);
}
/**
* gtk_propagate_event:
* @widget: a #GtkWidget
@@ -2357,79 +2547,16 @@ void
gtk_propagate_event (GtkWidget *widget,
GdkEvent *event)
{
gint handled_event;
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (event != NULL);
handled_event = FALSE;
g_object_ref (widget);
if ((event->type == GDK_KEY_PRESS) ||
(event->type == GDK_KEY_RELEASE))
{
/* Only send key events within Window widgets to the Window
* The Window widget will in turn pass the
* key event on to the currently focused widget
* for that window.
*/
GtkWidget *window;
window = gtk_widget_get_toplevel (widget);
if (GTK_IS_WINDOW (window))
{
/* If there is a grab within the window, give the grab widget
* a first crack at the key event
*/
if (widget != window && gtk_widget_has_grab (widget))
handled_event = gtk_widget_event (widget, event);
if (!handled_event)
{
window = gtk_widget_get_toplevel (widget);
if (GTK_IS_WINDOW (window))
{
if (gtk_widget_is_sensitive (window))
gtk_widget_event (window, event);
}
}
handled_event = TRUE; /* don't send to widget */
}
}
/* Other events get propagated up the widget tree
* so that parents can see the button and motion
* events of the children.
*/
if (!handled_event)
{
while (TRUE)
{
GtkWidget *tmp;
/* Scroll events are special cased here because it
* feels wrong when scrolling a GtkViewport, say,
* to have children of the viewport eat the scroll
* event
*/
if (!gtk_widget_is_sensitive (widget))
handled_event = event->type != GDK_SCROLL;
else
handled_event = gtk_widget_event (widget, event);
tmp = gtk_widget_get_parent (widget);
g_object_unref (widget);
widget = tmp;
if (!handled_event && widget)
g_object_ref (widget);
else
break;
}
}
else
g_object_unref (widget);
propagate_event (widget, event, FALSE, NULL);
}
gboolean
_gtk_propagate_captured_event (GtkWidget *widget,
GdkEvent *event,
GtkWidget *topmost)
{
return propagate_event (widget, event, TRUE, topmost);
}
+3
View File
@@ -36,6 +36,7 @@ BOOLEAN:OBJECT,BOXED,BOXED
BOOLEAN:OBJECT,OBJECT,OBJECT
BOOLEAN:OBJECT,STRING,STRING
BOOLEAN:OBJECT,ENUM
BOOLEAN:OBJECT,ENUM,INT,INT
BOOLEAN:INT
BOOLEAN:INT,INT
BOOLEAN:INT,INT,INT
@@ -47,6 +48,7 @@ BOOLEAN:BOOLEAN,BOOLEAN,BOOLEAN
BOOLEAN:STRING
ENUM:ENUM
ENUM:VOID
FLAGS:BOXED
INT:POINTER
OBJECT:VOID
STRING:DOUBLE
@@ -109,6 +111,7 @@ VOID:STRING,STRING,STRING
VOID:STRING,INT,POINTER
VOID:STRING,UINT,FLAGS
VOID:STRING,UINT,FLAGS,UINT
VOID:UINT,DOUBLE
VOID:UINT,FLAGS,BOXED
VOID:UINT,UINT
VOID:UINT,STRING
+230 -276
View File
@@ -110,6 +110,7 @@
#include "gtksettings.h"
#include "gtkprivate.h"
#include "gtkwidgetprivate.h"
#include "gtkdnd.h"
#include "gtkintl.h"
#include "gtktypebuiltins.h"
@@ -227,12 +228,14 @@ static void gtk_menu_scroll_to (GtkMenu *menu,
gint offset);
static void gtk_menu_grab_notify (GtkWidget *widget,
gboolean was_grabbed);
static GtkCapturedEventFlags
gtk_menu_captured_event (GtkWidget *widget,
GdkEvent *event);
static void gtk_menu_stop_scrolling (GtkMenu *menu);
static void gtk_menu_remove_scroll_timeout (GtkMenu *menu);
static gboolean gtk_menu_scroll_timeout (gpointer data);
static gboolean gtk_menu_scroll_timeout_initial (gpointer data);
static void gtk_menu_start_scrolling (GtkMenu *menu);
static void gtk_menu_scroll_item_visible (GtkMenuShell *menu_shell,
GtkWidget *menu_item);
@@ -510,6 +513,7 @@ gtk_menu_class_init (GtkMenuClass *class)
widget_class->get_preferred_width = gtk_menu_get_preferred_width;
widget_class->get_preferred_height = gtk_menu_get_preferred_height;
widget_class->get_preferred_height_for_width = gtk_menu_get_preferred_height_for_width;
widget_class->captured_event = gtk_menu_captured_event;
container_class->remove = gtk_menu_remove;
container_class->get_child_property = gtk_menu_get_child_property;
@@ -1057,6 +1061,7 @@ gtk_menu_init (GtkMenu *menu)
priv->needs_destruction_ref = TRUE;
priv->monitor_num = -1;
priv->drag_start_y = -1;
context = gtk_widget_get_style_context (GTK_WIDGET (menu));
gtk_style_context_add_class (context, GTK_STYLE_CLASS_MENU);
@@ -1465,7 +1470,7 @@ gtk_menu_popup_for_device (GtkMenu *menu,
GtkMenuShell *menu_shell;
gboolean grab_keyboard;
GtkWidget *parent_toplevel;
GdkDevice *keyboard, *pointer;
GdkDevice *keyboard, *pointer, *source_device = NULL;
g_return_if_fail (GTK_IS_MENU (menu));
g_return_if_fail (device == NULL || GDK_IS_DEVICE (device));
@@ -1602,6 +1607,7 @@ gtk_menu_popup_for_device (GtkMenu *menu,
(current_event->type != GDK_ENTER_NOTIFY))
menu_shell->priv->ignore_enter = TRUE;
source_device = gdk_event_get_source_device (current_event);
gdk_event_free (current_event);
}
else
@@ -1671,17 +1677,9 @@ gtk_menu_popup_for_device (GtkMenu *menu,
gtk_menu_scroll_to (menu, priv->scroll_offset);
/* if no item is selected, select the first one */
if (!menu_shell->priv->active_menu_item)
{
gboolean touchscreen_mode;
g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
"gtk-touchscreen-mode", &touchscreen_mode,
NULL);
if (touchscreen_mode)
gtk_menu_shell_select_first (menu_shell, TRUE);
}
if (!menu_shell->priv->active_menu_item &&
source_device && gdk_device_get_source (source_device) == GDK_SOURCE_TOUCH)
gtk_menu_shell_select_first (menu_shell, TRUE);
/* Once everything is set up correctly, map the toplevel */
gtk_widget_show (priv->toplevel);
@@ -3316,34 +3314,6 @@ gtk_menu_get_preferred_height_for_width (GtkWidget *widget,
g_free (nat_heights);
}
static gboolean
gtk_menu_button_scroll (GtkMenu *menu,
GdkEventButton *event)
{
GtkMenuPrivate *priv = menu->priv;
if (priv->upper_arrow_prelight || priv->lower_arrow_prelight)
{
gboolean touchscreen_mode;
g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
"gtk-touchscreen-mode", &touchscreen_mode,
NULL);
if (touchscreen_mode)
gtk_menu_handle_scrolling (menu,
event->x_root, event->y_root,
event->type == GDK_BUTTON_PRESS,
FALSE);
return TRUE;
}
return FALSE;
}
static gboolean
pointer_in_menu_window (GtkWidget *widget,
gdouble x_root,
@@ -3380,13 +3350,16 @@ static gboolean
gtk_menu_button_press (GtkWidget *widget,
GdkEventButton *event)
{
GdkDevice *source_device;
GtkWidget *event_widget;
GtkMenu *menu;
if (event->type != GDK_BUTTON_PRESS)
return FALSE;
/* Don't pass down to menu shell for presses over scroll arrows
*/
if (gtk_menu_button_scroll (GTK_MENU (widget), event))
return TRUE;
source_device = gdk_event_get_source_device ((GdkEvent *) event);
event_widget = gtk_get_event_widget ((GdkEvent *) event);
menu = GTK_MENU (widget);
/* Don't pass down to menu shell if a non-menuitem part of the menu
* was clicked. The check for the event_widget being a GtkMenuShell
@@ -3395,10 +3368,16 @@ gtk_menu_button_press (GtkWidget *widget,
* the menu or on its border are delivered relative to
* menu_shell->window.
*/
if (GTK_IS_MENU_SHELL (gtk_get_event_widget ((GdkEvent *) event)) &&
if (GTK_IS_MENU_SHELL (event_widget) &&
pointer_in_menu_window (widget, event->x_root, event->y_root))
return TRUE;
if (GTK_IS_MENU_ITEM (event_widget) &&
gdk_device_get_source (source_device) == GDK_SOURCE_TOUCH &&
GTK_MENU_ITEM (event_widget)->priv->submenu != NULL &&
!gtk_widget_is_drawable (GTK_MENU_ITEM (event_widget)->priv->submenu))
menu->priv->ignore_button_release = TRUE;
return GTK_WIDGET_CLASS (gtk_menu_parent_class)->button_press_event (widget, event);
}
@@ -3417,11 +3396,6 @@ gtk_menu_button_release (GtkWidget *widget,
if (event->type != GDK_BUTTON_RELEASE)
return FALSE;
/* Don't pass down to menu shell for releases over scroll arrows
*/
if (gtk_menu_button_scroll (GTK_MENU (widget), event))
return TRUE;
/* Don't pass down to menu shell if a non-menuitem part of the menu
* was clicked (see comment in button_press()).
*/
@@ -3665,10 +3639,14 @@ gtk_menu_motion_notify (GtkWidget *widget,
GtkMenu *menu;
GtkMenuShell *menu_shell;
GtkWidget *parent;
GdkDevice *source_device;
gboolean need_enter;
if (GTK_IS_MENU (widget))
source_device = gdk_event_get_source_device ((GdkEvent *) event);
if (GTK_IS_MENU (widget) &&
gdk_device_get_source (source_device) != GDK_SOURCE_TOUCH)
{
GtkMenuPrivate *priv = GTK_MENU(widget)->priv;
@@ -3832,90 +3810,17 @@ gtk_menu_scroll_by (GtkMenu *menu,
gtk_menu_scroll_to (menu, offset);
}
static void
gtk_menu_do_timeout_scroll (GtkMenu *menu,
gboolean touchscreen_mode)
{
GtkMenuPrivate *priv = menu->priv;
gboolean upper_visible;
gboolean lower_visible;
upper_visible = priv->upper_arrow_visible;
lower_visible = priv->lower_arrow_visible;
gtk_menu_scroll_by (menu, priv->scroll_step);
if (touchscreen_mode &&
(upper_visible != priv->upper_arrow_visible ||
lower_visible != priv->lower_arrow_visible))
{
/* We are about to hide a scroll arrow while the mouse is pressed,
* this would cause the uncovered menu item to be activated on button
* release. Therefore we need to ignore button release here
*/
GTK_MENU_SHELL (menu)->priv->ignore_enter = TRUE;
priv->ignore_button_release = TRUE;
}
}
static gboolean
gtk_menu_scroll_timeout (gpointer data)
{
GtkMenu *menu;
gboolean touchscreen_mode;
menu = GTK_MENU (data);
g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
"gtk-touchscreen-mode", &touchscreen_mode,
NULL);
gtk_menu_do_timeout_scroll (menu, touchscreen_mode);
gtk_menu_scroll_by (menu, menu->priv->scroll_step);
return TRUE;
}
static gboolean
gtk_menu_scroll_timeout_initial (gpointer data)
{
GtkMenu *menu;
guint timeout;
gboolean touchscreen_mode;
menu = GTK_MENU (data);
g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
"gtk-timeout-repeat", &timeout,
"gtk-touchscreen-mode", &touchscreen_mode,
NULL);
gtk_menu_do_timeout_scroll (menu, touchscreen_mode);
gtk_menu_remove_scroll_timeout (menu);
menu->priv->scroll_timeout =
gdk_threads_add_timeout (timeout, gtk_menu_scroll_timeout, menu);
return FALSE;
}
static void
gtk_menu_start_scrolling (GtkMenu *menu)
{
guint timeout;
gboolean touchscreen_mode;
g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
"gtk-timeout-repeat", &timeout,
"gtk-touchscreen-mode", &touchscreen_mode,
NULL);
gtk_menu_do_timeout_scroll (menu, touchscreen_mode);
menu->priv->scroll_timeout =
gdk_threads_add_timeout (timeout, gtk_menu_scroll_timeout_initial, menu);
}
static gboolean
gtk_menu_scroll (GtkWidget *widget,
GdkEventScroll *event)
@@ -4039,14 +3944,9 @@ gtk_menu_handle_scrolling (GtkMenu *menu,
gboolean in_arrow;
gboolean scroll_fast = FALSE;
gint top_x, top_y;
gboolean touchscreen_mode;
menu_shell = GTK_MENU_SHELL (menu);
g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
"gtk-touchscreen-mode", &touchscreen_mode,
NULL);
gdk_window_get_position (gtk_widget_get_window (priv->toplevel),
&top_x, &top_y);
x -= top_x;
@@ -4064,82 +3964,44 @@ gtk_menu_handle_scrolling (GtkMenu *menu,
in_arrow = TRUE;
}
if (touchscreen_mode)
priv->upper_arrow_prelight = in_arrow;
if ((priv->upper_arrow_state & GTK_STATE_FLAG_INSENSITIVE) == 0)
{
gboolean arrow_pressed = FALSE;
if (priv->upper_arrow_visible && !priv->tearoff_active)
{
if (touchscreen_mode)
scroll_fast = (y < rect.y + MENU_SCROLL_FAST_ZONE);
if (enter && in_arrow &&
(!priv->upper_arrow_prelight ||
priv->scroll_fast != scroll_fast))
{
if (enter && priv->upper_arrow_prelight)
{
if (priv->scroll_timeout == 0)
{
/* Deselect the active item so that
* any submenus are popped down
*/
gtk_menu_shell_deselect (menu_shell);
priv->upper_arrow_prelight = TRUE;
priv->scroll_fast = scroll_fast;
gtk_menu_remove_scroll_timeout (menu);
priv->scroll_step = -MENU_SCROLL_STEP2; /* always fast */
/* Deselect the active item so that
* any submenus are popped down
*/
gtk_menu_shell_deselect (menu_shell);
if (!motion)
{
/* Only do stuff on click. */
gtk_menu_start_scrolling (menu);
arrow_pressed = TRUE;
}
}
else
{
arrow_pressed = TRUE;
}
}
else if (!enter)
{
gtk_menu_stop_scrolling (menu);
}
gtk_menu_remove_scroll_timeout (menu);
priv->scroll_step = scroll_fast
? -MENU_SCROLL_STEP2
: -MENU_SCROLL_STEP1;
priv->scroll_timeout =
gdk_threads_add_timeout (scroll_fast
? MENU_SCROLL_TIMEOUT2
: MENU_SCROLL_TIMEOUT1,
gtk_menu_scroll_timeout, menu);
}
else /* !touchscreen_mode */
else if (!enter && !in_arrow && priv->upper_arrow_prelight)
{
scroll_fast = (y < rect.y + MENU_SCROLL_FAST_ZONE);
if (enter && in_arrow &&
(!priv->upper_arrow_prelight ||
priv->scroll_fast != scroll_fast))
{
priv->upper_arrow_prelight = TRUE;
priv->scroll_fast = scroll_fast;
/* Deselect the active item so that
* any submenus are popped down
*/
gtk_menu_shell_deselect (menu_shell);
gtk_menu_remove_scroll_timeout (menu);
priv->scroll_step = scroll_fast
? -MENU_SCROLL_STEP2
: -MENU_SCROLL_STEP1;
priv->scroll_timeout =
gdk_threads_add_timeout (scroll_fast
? MENU_SCROLL_TIMEOUT2
: MENU_SCROLL_TIMEOUT1,
gtk_menu_scroll_timeout, menu);
}
else if (!enter && !in_arrow && priv->upper_arrow_prelight)
{
gtk_menu_stop_scrolling (menu);
}
gtk_menu_stop_scrolling (menu);
}
}
/* gtk_menu_start_scrolling() might have hit the top of the
* menu, so check if the button isn't insensitive before
/* check if the button isn't insensitive before
* changing it to something else.
*/
if ((priv->upper_arrow_state & GTK_STATE_FLAG_INSENSITIVE) == 0)
@@ -4174,82 +4036,44 @@ gtk_menu_handle_scrolling (GtkMenu *menu,
in_arrow = TRUE;
}
if (touchscreen_mode)
priv->lower_arrow_prelight = in_arrow;
if ((priv->lower_arrow_state & GTK_STATE_FLAG_INSENSITIVE) == 0)
{
gboolean arrow_pressed = FALSE;
if (priv->lower_arrow_visible && !priv->tearoff_active)
{
if (touchscreen_mode)
scroll_fast = (y > rect.y + rect.height - MENU_SCROLL_FAST_ZONE);
if (enter && in_arrow &&
(!priv->lower_arrow_prelight ||
priv->scroll_fast != scroll_fast))
{
if (enter && priv->lower_arrow_prelight)
{
if (priv->scroll_timeout == 0)
{
/* Deselect the active item so that
* any submenus are popped down
*/
gtk_menu_shell_deselect (menu_shell);
priv->lower_arrow_prelight = TRUE;
priv->scroll_fast = scroll_fast;
gtk_menu_remove_scroll_timeout (menu);
priv->scroll_step = MENU_SCROLL_STEP2; /* always fast */
/* Deselect the active item so that
* any submenus are popped down
*/
gtk_menu_shell_deselect (menu_shell);
if (!motion)
{
/* Only do stuff on click. */
gtk_menu_start_scrolling (menu);
arrow_pressed = TRUE;
}
}
else
{
arrow_pressed = TRUE;
}
}
else if (!enter)
{
gtk_menu_stop_scrolling (menu);
}
gtk_menu_remove_scroll_timeout (menu);
priv->scroll_step = scroll_fast
? MENU_SCROLL_STEP2
: MENU_SCROLL_STEP1;
priv->scroll_timeout =
gdk_threads_add_timeout (scroll_fast
? MENU_SCROLL_TIMEOUT2
: MENU_SCROLL_TIMEOUT1,
gtk_menu_scroll_timeout, menu);
}
else /* !touchscreen_mode */
else if (!enter && !in_arrow && priv->lower_arrow_prelight)
{
scroll_fast = (y > rect.y + rect.height - MENU_SCROLL_FAST_ZONE);
if (enter && in_arrow &&
(!priv->lower_arrow_prelight ||
priv->scroll_fast != scroll_fast))
{
priv->lower_arrow_prelight = TRUE;
priv->scroll_fast = scroll_fast;
/* Deselect the active item so that
* any submenus are popped down
*/
gtk_menu_shell_deselect (menu_shell);
gtk_menu_remove_scroll_timeout (menu);
priv->scroll_step = scroll_fast
? MENU_SCROLL_STEP2
: MENU_SCROLL_STEP1;
priv->scroll_timeout =
gdk_threads_add_timeout (scroll_fast
? MENU_SCROLL_TIMEOUT2
: MENU_SCROLL_TIMEOUT1,
gtk_menu_scroll_timeout, menu);
}
else if (!enter && !in_arrow && priv->lower_arrow_prelight)
{
gtk_menu_stop_scrolling (menu);
}
gtk_menu_stop_scrolling (menu);
}
}
/* gtk_menu_start_scrolling() might have hit the bottom of the
* menu, so check if the button isn't insensitive before
/* check if the button isn't insensitive before
* changing it to something else.
*/
if ((priv->lower_arrow_state & GTK_STATE_FLAG_INSENSITIVE) == 0)
@@ -4279,19 +4103,18 @@ gtk_menu_enter_notify (GtkWidget *widget,
{
GtkWidget *menu_item;
GtkWidget *parent;
gboolean touchscreen_mode;
GdkDevice *source_device;
if (event->mode == GDK_CROSSING_GTK_GRAB ||
event->mode == GDK_CROSSING_GTK_UNGRAB ||
event->mode == GDK_CROSSING_STATE_CHANGED)
return TRUE;
g_object_get (gtk_widget_get_settings (widget),
"gtk-touchscreen-mode", &touchscreen_mode,
NULL);
source_device = gdk_event_get_source_device ((GdkEvent *) event);
menu_item = gtk_get_event_widget ((GdkEvent*) event);
if (GTK_IS_MENU (widget))
if (GTK_IS_MENU (widget) &&
gdk_device_get_source (source_device) != GDK_SOURCE_TOUCH)
{
GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
@@ -4300,7 +4123,8 @@ gtk_menu_enter_notify (GtkWidget *widget,
event->x_root, event->y_root, TRUE, TRUE);
}
if (!touchscreen_mode && GTK_IS_MENU_ITEM (menu_item))
if (gdk_device_get_source (source_device) != GDK_SOURCE_TOUCH &&
GTK_IS_MENU_ITEM (menu_item))
{
GtkWidget *menu = gtk_widget_get_parent (menu_item);
@@ -4357,6 +4181,7 @@ gtk_menu_leave_notify (GtkWidget *widget,
GtkMenu *menu;
GtkMenuItem *menu_item;
GtkWidget *event_widget;
GdkDevice *source_device;
if (event->mode == GDK_CROSSING_GTK_GRAB ||
event->mode == GDK_CROSSING_GTK_UNGRAB ||
@@ -4369,7 +4194,10 @@ gtk_menu_leave_notify (GtkWidget *widget,
if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
return TRUE;
gtk_menu_handle_scrolling (menu, event->x_root, event->y_root, FALSE, TRUE);
source_device = gdk_event_get_source_device ((GdkEvent *) event);
if (gdk_device_get_source (source_device) != GDK_SOURCE_TOUCH)
gtk_menu_handle_scrolling (menu, event->x_root, event->y_root, FALSE, TRUE);
event_widget = gtk_get_event_widget ((GdkEvent*) event);
@@ -4404,6 +4232,138 @@ gtk_menu_leave_notify (GtkWidget *widget,
return GTK_WIDGET_CLASS (gtk_menu_parent_class)->leave_notify_event (widget, event);
}
static gboolean
pointer_on_menu_widget (GtkMenu *menu,
gdouble x_root,
gdouble y_root)
{
GtkMenuPrivate *priv = menu->priv;
GtkAllocation allocation;
gint window_x, window_y;
gtk_widget_get_allocation (GTK_WIDGET (menu), &allocation);
gdk_window_get_position (gtk_widget_get_window (priv->toplevel),
&window_x, &window_y);
if (x_root >= window_x && x_root < window_x + allocation.width &&
y_root >= window_y && y_root < window_y + allocation.height)
return TRUE;
return FALSE;
}
static GtkCapturedEventFlags
gtk_menu_captured_event (GtkWidget *widget,
GdkEvent *event)
{
GdkDevice *source_device;
GtkCapturedEventFlags flags;
GtkMenuPrivate *priv;
GtkMenu *menu;
menu = GTK_MENU (widget);
priv = menu->priv;
flags = GTK_CAPTURED_EVENT_NONE;
if (!priv->upper_arrow_visible && !priv->lower_arrow_visible)
return flags;
source_device = gdk_event_get_source_device (event);
switch (event->type)
{
case GDK_BUTTON_PRESS:
if (event->button.button == 1 &&
gdk_device_get_source (source_device) == GDK_SOURCE_TOUCH &&
pointer_on_menu_widget (menu, event->button.x_root, event->button.y_root))
{
priv->drag_start_y = event->button.y_root;
priv->initial_drag_offset = priv->scroll_offset;
priv->drag_scroll_started = FALSE;
}
else
priv->drag_start_y = -1;
priv->drag_already_pressed = TRUE;
break;
case GDK_BUTTON_RELEASE:
if (priv->drag_scroll_started)
{
flags = GTK_CAPTURED_EVENT_HANDLED;
priv->drag_scroll_started = FALSE;
priv->drag_start_y = -1;
priv->drag_already_pressed = FALSE;
}
break;
case GDK_MOTION_NOTIFY:
if (event->motion.state & GDK_BUTTON1_MASK &&
gdk_device_get_source (source_device) == GDK_SOURCE_TOUCH)
{
if (!priv->drag_already_pressed)
{
if (pointer_on_menu_widget (menu,
event->motion.x_root,
event->motion.y_root))
{
priv->drag_start_y = event->motion.y_root;
priv->initial_drag_offset = priv->scroll_offset;
priv->drag_scroll_started = FALSE;
}
else
priv->drag_start_y = -1;
priv->drag_already_pressed = TRUE;
}
if (priv->drag_start_y < 0 &&
!priv->drag_scroll_started)
break;
if (priv->drag_scroll_started)
{
gint offset, view_height;
GtkBorder arrow_border;
gdouble y_diff;
y_diff = event->motion.y_root - priv->drag_start_y;
offset = priv->initial_drag_offset - y_diff;
view_height = gdk_window_get_height (gtk_widget_get_window (widget));
get_arrows_border (menu, &arrow_border);
if (priv->upper_arrow_visible)
view_height -= arrow_border.top;
if (priv->lower_arrow_visible)
view_height -= arrow_border.bottom;
offset = CLAMP (offset, 0, priv->requested_height - view_height);
gtk_menu_scroll_to (menu, offset);
flags = GTK_CAPTURED_EVENT_HANDLED;
}
else if (gtk_drag_check_threshold (widget,
0, priv->drag_start_y,
0, event->motion.y_root))
{
priv->drag_scroll_started = TRUE;
flags = GTK_CAPTURED_EVENT_HANDLED;
gtk_menu_shell_deselect (GTK_MENU_SHELL (menu));
}
}
break;
case GDK_ENTER_NOTIFY:
case GDK_LEAVE_NOTIFY:
if (priv->drag_scroll_started)
flags = GTK_CAPTURED_EVENT_HANDLED;
break;
default:
break;
}
return flags;
}
static void
gtk_menu_stop_navigating_submenu (GtkMenu *menu)
{
@@ -4846,19 +4806,10 @@ static void
gtk_menu_stop_scrolling (GtkMenu *menu)
{
GtkMenuPrivate *priv = menu->priv;
gboolean touchscreen_mode;
gtk_menu_remove_scroll_timeout (menu);
g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
"gtk-touchscreen-mode", &touchscreen_mode,
NULL);
if (!touchscreen_mode)
{
priv->upper_arrow_prelight = FALSE;
priv->lower_arrow_prelight = FALSE;
}
priv->upper_arrow_prelight = FALSE;
priv->lower_arrow_prelight = FALSE;
}
static void
@@ -5664,7 +5615,6 @@ gtk_menu_real_move_scroll (GtkMenu *menu,
}
}
/**
* gtk_menu_set_monitor:
* @menu: a #GtkMenu
@@ -5741,11 +5691,13 @@ static void
gtk_menu_grab_notify (GtkWidget *widget,
gboolean was_grabbed)
{
GtkMenu *menu;
GtkWidget *toplevel;
GtkWindowGroup *group;
GtkWidget *grab;
GdkDevice *pointer;
menu = GTK_MENU (widget);
pointer = _gtk_menu_shell_get_grab_device (GTK_MENU_SHELL (widget));
if (!pointer ||
@@ -5762,6 +5714,8 @@ gtk_menu_grab_notify (GtkWidget *widget,
if (GTK_MENU_SHELL (widget)->priv->active && !GTK_IS_MENU_SHELL (grab))
gtk_menu_shell_cancel (GTK_MENU_SHELL (widget));
menu->priv->drag_scroll_started = FALSE;
}
/**
+12 -5
View File
@@ -1690,13 +1690,20 @@ static void
gtk_real_menu_item_select (GtkMenuItem *menu_item)
{
GtkMenuItemPrivate *priv = menu_item->priv;
gboolean touchscreen_mode;
GdkDevice *source_device = NULL;
GdkEvent *current_event;
g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu_item)),
"gtk-touchscreen-mode", &touchscreen_mode,
NULL);
current_event = gtk_get_current_event ();
if (!touchscreen_mode && priv->submenu &&
if (current_event)
{
source_device = gdk_event_get_source_device (current_event);
gdk_event_free (current_event);
}
if ((!source_device ||
gdk_device_get_source (source_device) != GDK_SOURCE_TOUCH) &&
priv->submenu &&
(!gtk_widget_get_mapped (priv->submenu) ||
GTK_MENU (priv->submenu)->priv->tearoff_active))
{
+5
View File
@@ -100,6 +100,8 @@ struct _GtkMenuPrivate
guint seen_item_enter : 1;
guint ignore_button_release : 1;
guint no_toggle_size : 1;
guint drag_already_pressed : 1;
guint drag_scroll_started : 1;
/* info used for the table */
guint *heights;
@@ -126,6 +128,9 @@ struct _GtkMenuPrivate
gint navigation_height;
guint navigation_timeout;
gdouble drag_start_y;
gint initial_drag_offset;
};
G_END_DECLS
+4 -32
View File
@@ -1084,13 +1084,11 @@ gtk_menu_shell_enter_notify (GtkWidget *widget,
if (!gtk_widget_get_visible (GTK_MENU_ITEM (menu_item)->priv->submenu))
{
gboolean touchscreen_mode;
GdkDevice *source_device;
g_object_get (gtk_widget_get_settings (widget),
"gtk-touchscreen-mode", &touchscreen_mode,
NULL);
source_device = gdk_event_get_source_device ((GdkEvent *) event);
if (touchscreen_mode)
if (gdk_device_get_source (source_device) == GDK_SOURCE_TOUCH)
_gtk_menu_item_popup_submenu (menu_item, TRUE);
}
}
@@ -1612,45 +1610,19 @@ gtk_real_menu_shell_move_current (GtkMenuShell *menu_shell,
GtkMenuShellPrivate *priv = menu_shell->priv;
GtkMenuShell *parent_menu_shell = NULL;
gboolean had_selection;
gboolean touchscreen_mode;
priv->in_unselectable_item = FALSE;
had_selection = priv->active_menu_item != NULL;
g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu_shell)),
"gtk-touchscreen-mode", &touchscreen_mode,
NULL);
if (priv->parent_menu_shell)
parent_menu_shell = GTK_MENU_SHELL (priv->parent_menu_shell);
switch (direction)
{
case GTK_MENU_DIR_PARENT:
if (touchscreen_mode &&
priv->active_menu_item &&
GTK_MENU_ITEM (priv->active_menu_item)->priv->submenu &&
gtk_widget_get_visible (GTK_MENU_ITEM (priv->active_menu_item)->priv->submenu))
if (parent_menu_shell)
{
/* if we are on a menu item that has an open submenu but the
* focus is not in that submenu (e.g. because it's empty or
* has only insensitive items), close that submenu instead of
* running into the code below which would close *this* menu.
*/
_gtk_menu_item_popdown_submenu (priv->active_menu_item);
_gtk_menu_shell_update_mnemonics (menu_shell);
}
else if (parent_menu_shell)
{
if (touchscreen_mode)
{
/* close menu when returning from submenu. */
_gtk_menu_item_popdown_submenu (GTK_MENU (menu_shell)->priv->parent_menu_item);
_gtk_menu_shell_update_mnemonics (parent_menu_shell);
break;
}
if (GTK_MENU_SHELL_GET_CLASS (parent_menu_shell)->submenu_placement ==
GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement)
gtk_menu_shell_deselect (menu_shell);
+17
View File
@@ -32,6 +32,7 @@
#include "gdk/gdk.h"
#include "gtkprivate.h"
#include "gtkenums.h"
#if !defined G_OS_WIN32 && !(defined GDK_WINDOWING_QUARTZ && defined QUARTZ_RELOCATION)
@@ -159,6 +160,22 @@ _gtk_single_string_accumulator (GSignalInvocationHint *ihint,
return continue_emission;
}
gboolean
_gtk_captured_enum_accumulator (GSignalInvocationHint *ihint,
GValue *return_accu,
const GValue *handler_return,
gpointer dummy)
{
gboolean continue_emission;
GtkCapturedEventFlags flags;
flags = g_value_get_flags (handler_return);
g_value_set_flags (return_accu, flags);
continue_emission = (flags & GTK_CAPTURED_EVENT_HANDLED) == 0;
return continue_emission;
}
GdkModifierType
_gtk_replace_virtual_modifiers (GdkKeymap *keymap,
GdkModifierType modifiers)
+8
View File
@@ -59,6 +59,10 @@ gboolean _gtk_single_string_accumulator (GSignalInvocationHint *ihint,
GValue *return_accu,
const GValue *handler_return,
gpointer dummy);
gboolean _gtk_captured_enum_accumulator (GSignalInvocationHint *ihint,
GValue *return_accu,
const GValue *handler_return,
gpointer dummy);
GdkModifierType _gtk_replace_virtual_modifiers (GdkKeymap *keymap,
GdkModifierType modifiers);
@@ -74,6 +78,10 @@ gboolean _gtk_translate_keyboard_accel_state (GdkKeymap *keymap,
gint *level,
GdkModifierType *consumed_modifiers);
gboolean _gtk_propagate_captured_event (GtkWidget *widget,
GdkEvent *event,
GtkWidget *topmost);
G_END_DECLS
#endif /* __GTK_PRIVATE_H__ */
+69 -40
View File
@@ -1730,9 +1730,11 @@ gtk_range_realize (GtkWidget *widget)
attributes.wclass = GDK_INPUT_ONLY;
attributes.event_mask = gtk_widget_get_events (widget);
attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_ENTER_NOTIFY_MASK |
GDK_LEAVE_NOTIFY_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_SCROLL_MASK |
GDK_SMOOTH_SCROLL_MASK |
GDK_ENTER_NOTIFY_MASK |
GDK_LEAVE_NOTIFY_MASK |
GDK_POINTER_MOTION_MASK |
GDK_POINTER_MOTION_HINT_MASK);
@@ -2009,15 +2011,11 @@ gtk_range_draw (GtkWidget *widget,
GtkStateFlags widget_state;
gint focus_line_width = 0;
gint focus_padding = 0;
gboolean touchscreen;
gboolean draw_trough = TRUE;
gboolean draw_slider = TRUE;
GtkStyleContext *context;
context = gtk_widget_get_style_context (widget);
g_object_get (gtk_widget_get_settings (widget),
"gtk-touchscreen-mode", &touchscreen,
NULL);
if (GTK_IS_SCALE (widget) &&
gtk_adjustment_get_upper (priv->adjustment) == gtk_adjustment_get_lower (priv->adjustment))
@@ -2279,7 +2277,7 @@ gtk_range_draw (GtkWidget *widget,
state &= ~(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE);
if (!touchscreen && priv->mouse_location == MOUSE_SLIDER && !(state & GTK_STATE_FLAG_INSENSITIVE))
if (priv->mouse_location == MOUSE_SLIDER && !(state & GTK_STATE_FLAG_INSENSITIVE))
state |= GTK_STATE_FLAG_PRELIGHT;
if (priv->grab_location == MOUSE_SLIDER)
@@ -2309,28 +2307,28 @@ gtk_range_draw (GtkWidget *widget,
draw_stepper (range, STEPPER_A, cr,
priv->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
priv->grab_location == MOUSE_STEPPER_A,
!touchscreen && priv->mouse_location == MOUSE_STEPPER_A,
priv->mouse_location == MOUSE_STEPPER_A,
widget_state);
if (priv->has_stepper_b)
draw_stepper (range, STEPPER_B, cr,
priv->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
priv->grab_location == MOUSE_STEPPER_B,
!touchscreen && priv->mouse_location == MOUSE_STEPPER_B,
priv->mouse_location == MOUSE_STEPPER_B,
widget_state);
if (priv->has_stepper_c)
draw_stepper (range, STEPPER_C, cr,
priv->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
priv->grab_location == MOUSE_STEPPER_C,
!touchscreen && priv->mouse_location == MOUSE_STEPPER_C,
priv->mouse_location == MOUSE_STEPPER_C,
widget_state);
if (priv->has_stepper_d)
draw_stepper (range, STEPPER_D, cr,
priv->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
priv->grab_location == MOUSE_STEPPER_D,
!touchscreen && priv->mouse_location == MOUSE_STEPPER_D,
priv->mouse_location == MOUSE_STEPPER_D,
widget_state);
return FALSE;
@@ -2347,15 +2345,11 @@ range_grab_add (GtkRange *range,
if (device == priv->grab_device)
return;
if (priv->grab_device != NULL)
{
g_warning ("GtkRange already had a grab device, releasing device grab");
gtk_device_grab_remove (GTK_WIDGET (range), priv->grab_device);
}
/* we don't actually gdk_grab, since a button is down */
gtk_device_grab_add (GTK_WIDGET (range), device, TRUE);
/* Don't perform any GDK/GTK+ grab here. Since a button
* is down, there's an ongoing implicit grab on
* priv->event_window, which pretty much guarantees this
* is the only widget receiving the pointer events.
*/
priv->grab_location = location;
priv->grab_button = button;
priv->grab_device = device;
@@ -2532,7 +2526,8 @@ gtk_range_button_press (GtkWidget *widget,
{
GtkRange *range = GTK_RANGE (widget);
GtkRangePrivate *priv = range->priv;
GdkDevice *device;
GdkDevice *device, *source_device;
GdkInputSource source;
if (!gtk_widget_has_focus (widget))
gtk_widget_grab_focus (widget);
@@ -2542,6 +2537,9 @@ gtk_range_button_press (GtkWidget *widget,
return FALSE;
device = gdk_event_get_device ((GdkEvent *) event);
source_device = gdk_event_get_source_device ((GdkEvent *) event);
source = gdk_device_get_source (source_device);
priv->mouse_x = event->x;
priv->mouse_y = event->y;
@@ -2558,7 +2556,8 @@ gtk_range_button_press (GtkWidget *widget,
return TRUE;
}
if (priv->mouse_location == MOUSE_TROUGH &&
if (source != GDK_SOURCE_TOUCH &&
priv->mouse_location == MOUSE_TROUGH &&
event->button == GDK_BUTTON_PRIMARY)
{
/* button 1 steps by page increment, as with button 2 on a stepper
@@ -2609,17 +2608,17 @@ gtk_range_button_press (GtkWidget *widget,
return TRUE;
}
else if ((priv->mouse_location == MOUSE_TROUGH &&
event->button == GDK_BUTTON_MIDDLE) ||
(source == GDK_SOURCE_TOUCH || event->button == GDK_BUTTON_MIDDLE)) ||
priv->mouse_location == MOUSE_SLIDER)
{
gboolean need_value_update = FALSE;
/* Any button can be used to drag the slider, but you can start
* dragging the slider with a trough click using button 2;
* On button 2 press, we warp the slider to mouse position,
* then begin the slider drag.
* On button 2 press and touch devices, we warp the slider to
* mouse position, then begin the slider drag.
*/
if (event->button == GDK_BUTTON_MIDDLE)
if (event->button == GDK_BUTTON_MIDDLE || source == GDK_SOURCE_TOUCH)
{
gdouble slider_low_value, slider_high_value, new_value;
@@ -2789,7 +2788,7 @@ gtk_range_button_release (GtkWidget *widget,
/**
* _gtk_range_get_wheel_delta:
* @range: a #GtkRange
* @direction: A #GdkScrollDirection
* @event: A #GdkEventScroll
*
* Returns a good step value for the mouse wheel.
*
@@ -2798,28 +2797,58 @@ gtk_range_button_release (GtkWidget *widget,
* Since: 2.4
**/
gdouble
_gtk_range_get_wheel_delta (GtkRange *range,
GdkScrollDirection direction)
_gtk_range_get_wheel_delta (GtkRange *range,
GdkEventScroll *event)
{
GtkRangePrivate *priv = range->priv;
GtkAdjustment *adjustment = priv->adjustment;
gdouble dx, dy;
gdouble delta;
gdouble page_size;
if (GTK_IS_SCROLLBAR (range))
delta = pow (gtk_adjustment_get_page_size (adjustment), 2.0 / 3.0);
page_size = gtk_adjustment_get_page_size (adjustment);
if (gdk_event_get_scroll_deltas ((GdkEvent *) event, &dx, &dy))
{
GtkAllocation allocation;
gtk_widget_get_allocation (GTK_WIDGET (range), &allocation);
if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL)
{
if (GTK_IS_SCROLLBAR (range) && page_size > 0)
delta = dx * page_size / allocation.width;
else
delta = dx * (gtk_adjustment_get_upper (adjustment) -
gtk_adjustment_get_lower (adjustment)) / allocation.width;
}
else
{
if (GTK_IS_SCROLLBAR (range) && page_size > 0)
delta = dy * page_size / allocation.height;
else
delta = dy * (gtk_adjustment_get_upper (adjustment) -
gtk_adjustment_get_lower (adjustment)) / allocation.height;
}
}
else
delta = gtk_adjustment_get_step_increment (adjustment) * 2;
if (direction == GDK_SCROLL_UP ||
direction == GDK_SCROLL_LEFT)
delta = - delta;
{
if (GTK_IS_SCROLLBAR (range))
delta = pow (page_size, 2.0 / 3.0);
else
delta = gtk_adjustment_get_page_increment (adjustment) * 2;
if (event->direction == GDK_SCROLL_UP ||
event->direction == GDK_SCROLL_LEFT)
delta = - delta;
}
if (priv->inverted)
delta = - delta;
return delta;
}
static gboolean
gtk_range_scroll_event (GtkWidget *widget,
GdkEventScroll *event)
@@ -2832,7 +2861,7 @@ gtk_range_scroll_event (GtkWidget *widget,
gdouble delta;
gboolean handled;
delta = _gtk_range_get_wheel_delta (range, event->direction);
delta = _gtk_range_get_wheel_delta (range, event);
g_signal_emit (range, signals[CHANGE_VALUE], 0,
GTK_SCROLL_JUMP, gtk_adjustment_get_value (priv->adjustment) + delta,
+2 -2
View File
@@ -148,8 +148,8 @@ void gtk_range_set_round_digits (GtkRange *range
gint gtk_range_get_round_digits (GtkRange *range);
/* internal API */
gdouble _gtk_range_get_wheel_delta (GtkRange *range,
GdkScrollDirection direction);
gdouble _gtk_range_get_wheel_delta (GtkRange *range,
GdkEventScroll *event);
void _gtk_range_set_has_origin (GtkRange *range,
gboolean has_origin);
gboolean _gtk_range_get_has_origin (GtkRange *range);
+1071 -23
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -117,6 +117,9 @@ void gtk_scrolled_window_set_min_content_width (GtkScrolledWindow *sc
gint gtk_scrolled_window_get_min_content_height (GtkScrolledWindow *scrolled_window);
void gtk_scrolled_window_set_min_content_height (GtkScrolledWindow *scrolled_window,
gint height);
void gtk_scrolled_window_set_kinetic_scrolling (GtkScrolledWindow *scrolled_window,
GtkKineticScrollingFlags flags);
GtkKineticScrollingFlags gtk_scrolled_window_get_kinetic_scrolling (GtkScrolledWindow *scrolled_window);
gint _gtk_scrolled_window_get_scrollbar_spacing (GtkScrolledWindow *scrolled_window);
+58 -5
View File
@@ -32,8 +32,10 @@
#include "gtkwidget.h"
#include "gtkprivate.h"
#include "gtkcssproviderprivate.h"
#include "gtkcssstylepropertyprivate.h"
#include "gtkstyleproviderprivate.h"
#include "gtksymboliccolor.h"
#include "gtkanimationdescription.h"
#include "gtktypebuiltins.h"
#include "gtkversion.h"
@@ -97,9 +99,10 @@
*/
#define DEFAULT_TIMEOUT_INITIAL 200
#define DEFAULT_TIMEOUT_REPEAT 20
#define DEFAULT_TIMEOUT_EXPAND 500
#define DEFAULT_TIMEOUT_INITIAL 200
#define DEFAULT_TIMEOUT_REPEAT 20
#define DEFAULT_TIMEOUT_EXPAND 500
#define DEFAULT_TIMEOUT_PRESS_AND_HOLD 800
typedef struct _GtkSettingsPropertyValue GtkSettingsPropertyValue;
typedef struct _GtkSettingsValuePrivate GtkSettingsValuePrivate;
@@ -207,7 +210,8 @@ enum {
PROP_IM_PREEDIT_STYLE,
PROP_IM_STATUS_STYLE,
PROP_SHELL_SHOWS_APP_MENU,
PROP_SHELL_SHOWS_MENUBAR
PROP_SHELL_SHOWS_MENUBAR,
PROP_PRESS_AND_HOLD_TIMEOUT
};
/* --- prototypes --- */
@@ -701,13 +705,16 @@ gtk_settings_class_init (GtkSettingsClass *class)
* functionality.
*
* Since: 2.10
*
* Deprecated: 3.4. Generally the behavior touchscreen input should be
* performed dynamically based on gdk_event_get_source_device().
*/
result = settings_install_property_parser (class,
g_param_spec_boolean ("gtk-touchscreen-mode",
P_("Enable Touchscreen Mode"),
P_("When TRUE, there are no motion notify events delivered on this screen"),
FALSE,
GTK_PARAM_READWRITE),
GTK_PARAM_READWRITE | G_PARAM_DEPRECATED),
NULL);
g_assert (result == PROP_TOUCHSCREEN_MODE);
@@ -1350,6 +1357,24 @@ gtk_settings_class_init (GtkSettingsClass *class)
NULL);
g_assert (result == PROP_SHELL_SHOWS_MENUBAR);
/**
* GtkSettings:gtk-press-and-hold-timeout:
*
* The amount of time, in milliseconds, a button has to be pressed
* before the press-and-hold signal with the trigger action is emitted.
*
* Since: 3.2
*/
result = settings_install_property_parser (class,
g_param_spec_int ("gtk-press-and-hold-timeout",
P_("Press And Hold Timeout"),
P_("Timeout before press-and-hold action activates"),
0, G_MAXINT,
DEFAULT_TIMEOUT_PRESS_AND_HOLD,
GTK_PARAM_READWRITE),
NULL);
g_assert (result == PROP_PRESS_AND_HOLD_TIMEOUT);
g_type_class_add_private (class, sizeof (GtkSettingsPrivate));
}
@@ -1479,6 +1504,34 @@ gtk_settings_style_provider_lookup (GtkStyleProviderPrivate *provider,
path,
state,
lookup);
/* Set animation for press and hold */
if (gtk_widget_path_iter_has_class (path, 0, GTK_STYLE_CLASS_PRESS_AND_HOLD))
{
GtkAnimationDescription *anim_desc;
static GValue anim_value = { 0 };
GtkStyleProperty *prop;
gint duration;
if (!G_IS_VALUE (&anim_value))
{
g_object_get (settings,
"gtk-press-and-hold-timeout", &duration,
NULL);
anim_desc = _gtk_animation_description_new (duration,
GTK_TIMELINE_PROGRESS_LINEAR,
FALSE);
g_value_init (&anim_value, GTK_TYPE_ANIMATION_DESCRIPTION);
g_value_take_boxed (&anim_value, anim_desc);
}
prop = _gtk_style_property_lookup ("transition");
_gtk_css_lookup_set (lookup,
_gtk_css_style_property_get_id (GTK_CSS_STYLE_PROPERTY (prop)),
NULL, &anim_value);
}
}
static void
+7
View File
@@ -648,6 +648,13 @@ struct _GtkStyleContextClass
*/
#define GTK_STYLE_CLASS_RIGHT "right"
/**
* GTK_STYLE_CLASS_PRESS_AND_HOLD:
*
* A CSS class for the press and hold activity indicator.
*/
#define GTK_STYLE_CLASS_PRESS_AND_HOLD "press-and-hold"
/* Predefined set of widget regions */
/**
+72 -35
View File
@@ -378,6 +378,11 @@ static void gtk_text_view_drag_data_received (GtkWidget *widget,
guint time);
static gboolean gtk_text_view_popup_menu (GtkWidget *widget);
static gboolean gtk_text_view_press_and_hold (GtkWidget *widget,
GdkDevice *device,
GtkPressAndHoldAction action,
gint x,
gint y);
static void gtk_text_view_move_cursor (GtkTextView *text_view,
GtkMovementStep step,
@@ -463,7 +468,9 @@ static void gtk_text_view_set_virtual_cursor_pos (GtkTextView *text_view,
gint y);
static void gtk_text_view_do_popup (GtkTextView *text_view,
GdkEventButton *event);
GdkDevice *device,
guint32 _time,
guint button);
static void cancel_pending_scroll (GtkTextView *text_view);
static void gtk_text_view_queue_scroll (GtkTextView *text_view,
@@ -631,7 +638,8 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
widget_class->drag_data_received = gtk_text_view_drag_data_received;
widget_class->popup_menu = gtk_text_view_popup_menu;
widget_class->press_and_hold = gtk_text_view_press_and_hold;
container_class->add = gtk_text_view_add;
container_class->remove = gtk_text_view_remove;
container_class->forall = gtk_text_view_forall;
@@ -3984,12 +3992,10 @@ gtk_text_view_realize (GtkWidget *widget)
GtkTextView *text_view;
GtkTextViewPrivate *priv;
GtkStyleContext *context;
GtkStateFlags state;
GdkWindow *window;
GdkWindowAttr attributes;
gint attributes_mask;
GSList *tmp_list;
GdkRGBA color;
text_view = GTK_TEXT_VIEW (widget);
priv = text_view->priv;
@@ -4015,10 +4021,11 @@ gtk_text_view_realize (GtkWidget *widget)
gdk_window_set_user_data (window, widget);
context = gtk_widget_get_style_context (widget);
state = gtk_widget_get_state_flags (widget);
gtk_style_context_get_background_color (context, state, &color);
gdk_window_set_background_rgba (window, &color);
gtk_style_context_save (context);
gtk_style_context_add_class (context, GTK_STYLE_CLASS_VIEW);
gtk_style_context_set_background (context, window);
gtk_style_context_restore (context);
text_window_realize (priv->text_window, widget);
@@ -4118,16 +4125,14 @@ gtk_text_view_set_background (GtkTextView *text_view)
gtk_style_context_save (context);
gtk_style_context_add_class (context, GTK_STYLE_CLASS_VIEW);
gtk_style_context_get_background_color (context, state, &color);
gdk_window_set_background_rgba (priv->text_window->bin_window, &color);
gtk_style_context_set_background (context, priv->text_window->bin_window);
gtk_style_context_set_background (context, gtk_widget_get_window (widget));
gtk_style_context_restore (context);
/* Set lateral panes background */
gtk_style_context_get_background_color (context, state, &color);
gdk_window_set_background_rgba (gtk_widget_get_window (widget), &color);
if (priv->left_window)
gdk_window_set_background_rgba (priv->left_window->bin_window, &color);
@@ -4273,8 +4278,15 @@ gtk_text_view_grab_notify (GtkWidget *widget,
if (priv->grab_device &&
gtk_widget_device_is_shadowed (widget, priv->grab_device))
{
if (priv->drag_start_x >= 0)
{
priv->drag_start_x = -1;
priv->drag_start_y = -1;
}
gtk_text_view_end_selection_drag (GTK_TEXT_VIEW (widget));
gtk_text_view_unobscure_mouse_cursor (GTK_TEXT_VIEW (widget));
priv->grab_device = NULL;
}
}
@@ -4552,7 +4564,8 @@ gtk_text_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
if (gdk_event_triggers_context_menu ((GdkEvent *) event))
{
gtk_text_view_do_popup (text_view, event);
gtk_text_view_do_popup (text_view, event->device,
event->time, event->button);
return TRUE;
}
else if (event->button == GDK_BUTTON_PRIMARY)
@@ -4575,6 +4588,7 @@ gtk_text_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
gtk_widget_get_modifier_mask (widget,
GDK_MODIFIER_INTENT_EXTEND_SELECTION)))
{
priv->grab_device = event->device;
priv->drag_start_x = event->x;
priv->drag_start_y = event->y;
priv->pending_place_cursor_button = event->button;
@@ -8315,16 +8329,18 @@ popup_targets_received (GtkClipboard *clipboard,
signals[POPULATE_POPUP],
0,
priv->popup_menu);
if (info->device)
gtk_menu_popup_for_device (GTK_MENU (priv->popup_menu),
info->device, NULL, NULL, NULL, NULL, NULL,
info->button, info->time);
if (gdk_device_get_source (info->device) != GDK_SOURCE_KEYBOARD)
gtk_menu_popup_for_device (GTK_MENU (priv->popup_menu),
info->device, NULL, NULL, NULL, NULL, NULL,
info->button, info->time);
else
{
gtk_menu_popup (GTK_MENU (priv->popup_menu), NULL, NULL,
popup_position_func, text_view,
0, gtk_get_current_event_time ());
gtk_menu_popup_for_device (GTK_MENU (priv->popup_menu),
info->device, NULL, NULL,
popup_position_func,
text_view, NULL,
0, info->time);
gtk_menu_shell_select_first (GTK_MENU_SHELL (priv->popup_menu), FALSE);
}
}
@@ -8335,7 +8351,9 @@ popup_targets_received (GtkClipboard *clipboard,
static void
gtk_text_view_do_popup (GtkTextView *text_view,
GdkEventButton *event)
GdkDevice *device,
guint32 _time,
guint button)
{
PopupInfo *info = g_new (PopupInfo, 1);
@@ -8344,19 +8362,9 @@ gtk_text_view_do_popup (GtkTextView *text_view,
* we get them, then we actually pop up the menu.
*/
info->text_view = g_object_ref (text_view);
if (event)
{
info->button = event->button;
info->time = event->time;
info->device = event->device;
}
else
{
info->button = 0;
info->time = gtk_get_current_event_time ();
info->device = NULL;
}
info->button = button;
info->time = _time;
info->device = device;
gtk_clipboard_request_contents (gtk_widget_get_clipboard (GTK_WIDGET (text_view),
GDK_SELECTION_CLIPBOARD),
@@ -8368,7 +8376,35 @@ gtk_text_view_do_popup (GtkTextView *text_view,
static gboolean
gtk_text_view_popup_menu (GtkWidget *widget)
{
gtk_text_view_do_popup (GTK_TEXT_VIEW (widget), NULL);
gtk_text_view_do_popup (GTK_TEXT_VIEW (widget),
gtk_get_current_event_device (),
gtk_get_current_event_time (),
0);
return TRUE;
}
static gboolean
gtk_text_view_press_and_hold (GtkWidget *widget,
GdkDevice *device,
GtkPressAndHoldAction action,
gint x,
gint y)
{
if (action == GTK_PRESS_AND_HOLD_TRIGGER)
gtk_text_view_do_popup (GTK_TEXT_VIEW (widget), device,
GDK_CURRENT_TIME, 1);
else if (action == GTK_PRESS_AND_HOLD_QUERY)
{
GdkDevice *source_device;
GdkEvent *event;
event = gtk_get_current_event ();
source_device = gdk_event_get_source_device (event);
if (gdk_device_get_source (source_device) != GDK_SOURCE_TOUCH)
return FALSE;
}
return TRUE;
}
@@ -8446,6 +8482,7 @@ text_window_realize (GtkTextWindow *win,
attributes.height = win->allocation.height;
attributes.event_mask = (GDK_EXPOSURE_MASK |
GDK_SCROLL_MASK |
GDK_SMOOTH_SCROLL_MASK |
GDK_KEY_PRESS_MASK |
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
+61
View File
@@ -2792,6 +2792,63 @@ render_spinner (GtkThemingEngine *engine,
cairo_restore (cr);
}
static void
render_press_and_hold (GtkThemingEngine *engine,
cairo_t *cr,
gdouble x,
gdouble y,
gdouble width,
gdouble height)
{
gdouble progress, radius, border_width;
GdkRGBA color, bg_color;
GtkStateFlags flags;
GtkBorder border;
cairo_save (cr);
if (!gtk_theming_engine_state_is_running (engine,
GTK_STATE_FLAG_ACTIVE,
&progress))
progress = 0;
flags = gtk_theming_engine_get_state (engine);
gtk_theming_engine_get_background_color (engine, flags, &bg_color);
gtk_theming_engine_get_color (engine, flags, &color);
gtk_theming_engine_get_border (engine, flags, &border);
border_width = (gdouble) MAX (MAX (border.top, border.bottom),
MAX (border.left, border.right));
radius = MIN (width, height) / 2;
if (border_width == 0 ||
border_width >= radius - border_width)
border_width = MAX (1, radius / 4);
cairo_set_line_width (cr, border_width);
radius -= border_width;
/* Arcs start from the negative Y axis */
cairo_arc (cr,
width / 2, height / 2,
radius,
- G_PI_2, - G_PI_2 + (2 * G_PI));
gdk_cairo_set_source_rgba (cr, &bg_color);
cairo_stroke (cr);
cairo_arc (cr,
width / 2, height / 2,
radius,
- G_PI_2,
- G_PI_2 + (2 * G_PI * progress));
gdk_cairo_set_source_rgba (cr, &color);
cairo_stroke (cr);
cairo_restore (cr);
}
static void
gtk_theming_engine_render_activity (GtkThemingEngine *engine,
cairo_t *cr,
@@ -2804,6 +2861,10 @@ gtk_theming_engine_render_activity (GtkThemingEngine *engine,
{
render_spinner (engine, cr, x, y, width, height);
}
else if (gtk_theming_engine_has_class (engine, GTK_STYLE_CLASS_PRESS_AND_HOLD))
{
render_press_and_hold (engine, cr, x, y, width, height);
}
else
{
gtk_theming_engine_render_background (engine, cr, x, y, width, height);
+22 -3
View File
@@ -35,6 +35,7 @@ struct GtkTimelinePriv
guint source_id;
GTimer *timer;
gdouble elapsed_time;
gdouble progress;
gdouble last_progress;
@@ -309,19 +310,18 @@ gtk_timeline_run_frame (GtkTimeline *timeline)
{
GtkTimelinePriv *priv;
gdouble delta_progress, progress;
guint elapsed_time;
/* the user may unref us during the signals, so save ourselves */
g_object_ref (timeline);
priv = timeline->priv;
elapsed_time = (guint) (g_timer_elapsed (priv->timer, NULL) * 1000);
priv->elapsed_time = (guint) (g_timer_elapsed (priv->timer, NULL) * 1000);
g_timer_start (priv->timer);
if (priv->animations_enabled)
{
delta_progress = (gdouble) elapsed_time / priv->duration;
delta_progress = (gdouble) priv->elapsed_time / priv->duration;
progress = priv->last_progress;
if (priv->direction == GTK_TIMELINE_DIRECTION_BACKWARD)
@@ -515,6 +515,25 @@ _gtk_timeline_is_running (GtkTimeline *timeline)
return (priv->source_id != 0);
}
/**
* gtk_timeline_get_elapsed_time:
* @timeline: A #GtkTimeline
*
* Returns the elapsed time since the last GtkTimeline::frame signal
*
* Return Value: elapsed time in milliseconds since the last frame
**/
guint
_gtk_timeline_get_elapsed_time (GtkTimeline *timeline)
{
GtkTimelinePriv *priv;
g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 0);
priv = timeline->priv;
return priv->elapsed_time;
}
/**
* gtk_timeline_get_fps:
* @timeline: A #GtkTimeline
+1
View File
@@ -84,6 +84,7 @@ void _gtk_timeline_pause (GtkTimeline
void _gtk_timeline_rewind (GtkTimeline *timeline);
gboolean _gtk_timeline_is_running (GtkTimeline *timeline);
guint _gtk_timeline_get_elapsed_time (GtkTimeline *timeline);
guint _gtk_timeline_get_fps (GtkTimeline *timeline);
void _gtk_timeline_set_fps (GtkTimeline *timeline,
+2 -6
View File
@@ -658,13 +658,9 @@ gtk_toggle_button_update_state (GtkButton *button)
{
GtkToggleButton *toggle_button = GTK_TOGGLE_BUTTON (button);
GtkToggleButtonPrivate *priv = toggle_button->priv;
gboolean depressed, touchscreen;
gboolean depressed;
GtkStateFlags new_state = 0;
g_object_get (gtk_widget_get_settings (GTK_WIDGET (button)),
"gtk-touchscreen-mode", &touchscreen,
NULL);
new_state = gtk_widget_get_state_flags (GTK_WIDGET (button)) &
~(GTK_STATE_FLAG_INCONSISTENT |
GTK_STATE_FLAG_PRELIGHT |
@@ -680,7 +676,7 @@ gtk_toggle_button_update_state (GtkButton *button)
else
depressed = priv->active;
if (!touchscreen && button->priv->in_button && (!button->priv->button_down || priv->draw_indicator))
if (button->priv->in_button && (!button->priv->button_down || priv->draw_indicator))
new_state |= GTK_STATE_FLAG_PRELIGHT;
if (depressed)
+17 -5
View File
@@ -1537,22 +1537,34 @@ _gtk_tooltip_hide (GtkWidget *widget)
}
static gboolean
tooltips_enabled (GdkWindow *window)
tooltips_enabled (GdkEvent *event)
{
GdkDevice *source_device;
GdkInputSource source;
GdkWindow *window;
gboolean enabled;
gboolean touchscreen;
GdkScreen *screen;
GtkSettings *settings;
window = event->any.window;
source_device = gdk_event_get_source_device (event);
if (!source_device)
return FALSE;
source = gdk_device_get_source (source_device);
screen = gdk_window_get_screen (window);
settings = gtk_settings_get_for_screen (screen);
g_object_get (settings,
"gtk-touchscreen-mode", &touchscreen,
"gtk-enable-tooltips", &enabled,
NULL);
return (!touchscreen && enabled);
if (enabled &&
source != GDK_SOURCE_TOUCH)
return TRUE;
return FALSE;
}
void
@@ -1564,7 +1576,7 @@ _gtk_tooltip_handle_event (GdkEvent *event)
GdkDisplay *display;
GtkTooltip *current_tooltip;
if (!tooltips_enabled (event->any.window))
if (!tooltips_enabled (event))
return;
/* Returns coordinates relative to has_tooltip_widget's allocation. */
+2
View File
@@ -2193,6 +2193,7 @@ gtk_tree_view_ensure_background (GtkTreeView *tree_view)
gtk_style_context_save (context);
gtk_style_context_add_class (context, GTK_STYLE_CLASS_VIEW);
gtk_style_context_set_background (context, tree_view->priv->bin_window);
gtk_style_context_set_background (context, gtk_widget_get_window (GTK_WIDGET (tree_view)));
gtk_style_context_restore (context);
gtk_style_context_set_background (context, tree_view->priv->header_window);
@@ -2238,6 +2239,7 @@ gtk_tree_view_realize (GtkWidget *widget)
attributes.height = allocation.height;
attributes.event_mask = (GDK_EXPOSURE_MASK |
GDK_SCROLL_MASK |
GDK_SMOOTH_SCROLL_MASK |
GDK_POINTER_MOTION_MASK |
GDK_ENTER_NOTIFY_MASK |
GDK_LEAVE_NOTIFY_MASK |
+2 -1
View File
@@ -684,7 +684,8 @@ gtk_viewport_realize (GtkWidget *widget)
event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
/* We select on button_press_mask so that button 4-5 scrolls are trapped.
*/
attributes.event_mask = event_mask | GDK_BUTTON_PRESS_MASK;
attributes.event_mask = event_mask |
GDK_BUTTON_PRESS_MASK | GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK;
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
+831
View File
@@ -60,6 +60,7 @@
#include "gtkcssprovider.h"
#include "gtkanimationdescription.h"
#include "gtkmodifierstyle.h"
#include "gtkgesturesinterpreter.h"
#include "gtkversion.h"
#include "gtkdebug.h"
#include "gtkplug.h"
@@ -403,6 +404,9 @@ struct _GtkWidgetPrivate
/* The widget's parent */
GtkWidget *parent;
GSList *captured_events;
GArray *gestures;
#ifdef G_ENABLE_DEBUG
/* Number of gtk_widget_push_verify_invariants () */
guint verifying_invariants_count;
@@ -484,6 +488,10 @@ enum {
QUERY_TOOLTIP,
DRAG_FAILED,
STYLE_UPDATED,
CAPTURED_EVENT,
PRESS_AND_HOLD,
MULTITOUCH_EVENT,
GESTURE,
LAST_SIGNAL
};
@@ -539,6 +547,25 @@ struct _GtkStateData
guint operation : 2;
};
typedef struct
{
/* The widget */
GtkWidget *widget;
/* animation */
GtkWidget *popup;
guint delay_animation_id;
guint trigger_id;
guint size;
gint start_x;
gint start_y;
gint current_x;
gint current_y;
GdkDevice *device;
} PressAndHoldData;
/* --- prototypes --- */
static void gtk_widget_base_class_init (gpointer g_class);
static void gtk_widget_class_init (GtkWidgetClass *klass);
@@ -705,6 +732,10 @@ static void gtk_widget_set_device_enabled_internal (GtkWidget *widget,
GdkDevice *device,
gboolean recurse,
gboolean enabled);
static gboolean event_window_is_still_viewable (GdkEvent *event);
static gboolean gtk_widget_press_and_hold_cancel (GtkWidget *widget);
/* --- variables --- */
static gpointer gtk_widget_parent_class = NULL;
@@ -733,6 +764,8 @@ static GQuark quark_visual = 0;
static GQuark quark_modifier_style = 0;
static GQuark quark_enabled_devices = 0;
static GQuark quark_size_groups = 0;
static GQuark quark_press_and_hold = 0;
static GQuark quark_gestures_interpreter = 0;
GParamSpecPool *_gtk_widget_child_property_pool = NULL;
GObjectNotifyContext *_gtk_widget_child_property_notify_context = NULL;
@@ -857,6 +890,8 @@ gtk_widget_class_init (GtkWidgetClass *klass)
quark_modifier_style = g_quark_from_static_string ("gtk-widget-modifier-style");
quark_enabled_devices = g_quark_from_static_string ("gtk-widget-enabled-devices");
quark_size_groups = g_quark_from_static_string ("gtk-widget-size-groups");
quark_press_and_hold = g_quark_from_static_string ("gtk-widget-press-and-hold");
quark_gestures_interpreter = g_quark_from_static_string ("gtk-widget-gestures-interpreter");
style_property_spec_pool = g_param_spec_pool_new (FALSE);
_gtk_widget_child_property_pool = g_param_spec_pool_new (TRUE);
@@ -935,6 +970,7 @@ gtk_widget_class_init (GtkWidgetClass *klass)
klass->grab_broken_event = NULL;
klass->query_tooltip = gtk_widget_real_query_tooltip;
klass->style_updated = gtk_widget_real_style_updated;
klass->press_and_hold = NULL;
klass->show_help = gtk_widget_real_show_help;
@@ -1798,6 +1834,9 @@ gtk_widget_class_init (GtkWidgetClass *klass)
* #GtkWidget::key-press-event) and finally a generic
* #GtkWidget::event-after signal.
*
* An event can be captured before ::event signal is emitted by connecting to
* ::captured-event event signal.
*
* Returns: %TRUE to stop other handlers from being invoked for the event
* and to cancel the emission of the second specific ::event signal.
* %FALSE to propagate the event further and to allow the emission of
@@ -1834,6 +1873,54 @@ gtk_widget_class_init (GtkWidgetClass *klass)
G_TYPE_NONE, 1,
GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
/**
* GtkWidget::captured-event:
* @widget: the object which received the signal.
* @event: the #GdkEvent which triggered this signal
*
* The #GtkWidget::captured-event signal is emitted before the
* #GtkWidget::event signal to allow capturing an event before the
* specialized events are emitted. The event is propagated starting
* from the top-level container to the widget that received the event
* going down the hierarchy.
*
* This signal returns a #GtkCapturedEventFlags with the handling
* status of the event, if %GTK_CAPTURED_EVENT_HANDLED is enabled,
* the event will be considered to be handled, and thus not propagated
* further.
*
* If, additionally, %GTK_CAPTURED_EVENT_STORE is enabled, the event will
* be stored so it can possibly be re-sent at a later stage. See
* gtk_widget_release_captured_events().
*
* If no flags are enabled, the captured event will be propagated even
* further, eventually triggering the emission of the #GtkWidget::event
* signal on the widget that received the event if no parent widgets
* captured it.
*
* <note><para>Enabling %GTK_CAPTURED_EVENT_STORE without
* %GTK_CAPTURED_EVENT_HANDLED is not allowed to avoid doubly
* event emission.</para></note>
*
* <warning><para>%GTK_CAPTURED_EVENT_STORE will not keep any track of
* event parity (eg. ensuring that button/key presses and releases
* are paired, or focus/crossing events) nor consistency, so use with
* discretion.</para></warning>
*
* Returns: a #GtkCapturedEventFlags specifying what to do with the event.
*
* Since: 3.4
*/
widget_signals[CAPTURED_EVENT] =
g_signal_new (I_("captured-event"),
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkWidgetClass, captured_event),
_gtk_captured_enum_accumulator, NULL,
_gtk_marshal_FLAGS__BOXED,
GTK_TYPE_CAPTURED_EVENT_FLAGS, 1,
GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
/**
* GtkWidget::button-press-event:
* @widget: the object which received the signal.
@@ -2874,6 +2961,15 @@ gtk_widget_class_init (GtkWidgetClass *klass)
G_TYPE_BOOLEAN, 1,
GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
widget_signals[MULTITOUCH_EVENT] =
g_signal_new (I_("multitouch-event"),
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkWidgetClass, multitouch_event),
_gtk_boolean_handled_accumulator, NULL,
_gtk_marshal_BOOLEAN__BOXED,
G_TYPE_BOOLEAN, 1,
GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
/**
* GtkWidget::query-tooltip:
* @widget: the object which received the signal
@@ -3005,6 +3101,86 @@ gtk_widget_class_init (GtkWidgetClass *klass)
_gtk_marshal_BOOLEAN__UINT,
G_TYPE_BOOLEAN, 1, G_TYPE_UINT);
/**
* GtkWidget::press-and-hold:
* @widget: the object which received the signal
* @action: a #GtkPressAndHoldAction specifying the action
* @x: if the action is not %GTK_PRESS_AND_HOLD_CANCEL, the x coordinate
* of the cursor position where the request has been emitted, relative
* to widget->window, otherwise undefined
* @y: if the action is not %GTK_PRESS_AND_HOLD_CANCEL, the y coordinate
* of the cursor position where the request has been emitted, relative
* to widget->window, otherwise undefined
*
* Connect to this signal and correctly handle all of its actions if you
* want your widget to support the press-n-hold operation. The
* press-and-hold operation is defined as keeping a mouse button pressed
* for a given amount of time (specified in the "press-and-hold-timeout"
* GtkSetting); during this time the mouse is only allowed to move a little
* bit (not past the drag threshold), else the press-and-hold operation will
* be terminated.
*
* From the above passage we can distill three actions for which this
* signal will be emitted: query, emitted when the mouse button goes
* down; trigger, emitted if the mouse button has been kept down for the
* specified amount of time and movements did not pass the drag threshold;
* and cancel, emitted when the press-and-hold operation has been terminated
* before the trigger action has been emitted.
*
* For query, @action will be set to %GTK_PRESS_AND_HOLD_QUERY, @x and @y
* will be set to the cursor position.
* A return value of %FALSE means no press-and-hold action should occur
* for these coordinates on the given widget, when %TRUE is returned
* a trigger action may be emitted later on.
*
* The trigger action is emitted by setting @action to be
* %GTK_PRESS_AND_HOLD_TRIGGER, the @x and @y coordinates are set to the
* cursor's current location (this includes any movements made between
* the original query and this trigger). In this case the return value
* is ignored.
*
* When @action is %GTK_WIDGET_PRESS_AND_HOLD_CANCEL, @x and @y are both
* undefined. The return value is ignored too as
* this action is only there for informational purposes.
*
* Returns: a boolean indicating how to proceed based on the value of
* @action, as described above.
*
* Since: 3.2
*/
widget_signals[PRESS_AND_HOLD] =
g_signal_new (I_("press-and-hold"),
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkWidgetClass, press_and_hold),
_gtk_boolean_handled_accumulator, NULL,
_gtk_marshal_BOOLEAN__OBJECT_ENUM_INT_INT,
G_TYPE_BOOLEAN, 4,
GDK_TYPE_DEVICE,
GTK_TYPE_PRESS_AND_HOLD_ACTION,
G_TYPE_INT,
G_TYPE_INT);
/**
* GtkWidget::gesture:
* @widget: the object which received the signal
* @gesture_id: gesture recognized
*
* This signal is emitted whenever a device with source
* #GDK_SOURCE_TOUCH finishes a gesture that resembles
* one of those added through gtk_widget_enable_gesture().
*
* Since: 3.4
*/
widget_signals[GESTURE] =
g_signal_new (I_("gesture"),
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkWidgetClass, gesture),
NULL, NULL,
g_cclosure_marshal_VOID__UINT,
G_TYPE_NONE, 1, G_TYPE_UINT);
binding_set = gtk_binding_set_by_class (klass);
gtk_binding_entry_add_signal (binding_set, GDK_KEY_F10, GDK_SHIFT_MASK,
"popup-menu", 0);
@@ -4429,6 +4605,12 @@ gtk_widget_realize (GtkWidget *widget)
_gtk_widget_enable_device_events (widget);
gtk_widget_update_devices_mask (widget, TRUE);
/* Enable button motion events for press and hold */
if (!gtk_widget_get_has_window (widget))
gdk_window_set_events (priv->window,
gdk_window_get_events (priv->window) | GDK_BUTTON_MOTION_MASK);
gtk_widget_add_events (widget, GDK_BUTTON_MOTION_MASK);
gtk_widget_pop_verify_invariants (widget);
}
}
@@ -5870,6 +6052,68 @@ gtk_widget_event (GtkWidget *widget,
return gtk_widget_event_internal (widget, event);
}
gboolean
_gtk_widget_captured_event (GtkWidget *widget,
GdkEvent *event)
{
GtkCapturedEventFlags flags;
GtkWidgetPrivate *priv;
gboolean return_val;
g_return_val_if_fail (GTK_IS_WIDGET (widget), TRUE);
g_return_val_if_fail (WIDGET_REALIZED_FOR_EVENT (widget, event), TRUE);
priv = widget->priv;
if (event->type == GDK_EXPOSE)
{
g_warning ("Events of type GDK_EXPOSE cannot be synthesized. To get "
"the same effect, call gdk_window_invalidate_rect/region(), "
"followed by gdk_window_process_updates().");
return TRUE;
}
if (!event_window_is_still_viewable (event))
return TRUE;
g_object_ref (widget);
g_signal_emit (widget, widget_signals[CAPTURED_EVENT], 0, event, &flags);
/* Only store events that have been handled, so we don't end up
* sending twice the same event.
*/
if (flags & GTK_CAPTURED_EVENT_STORE)
{
if ((flags & GTK_CAPTURED_EVENT_HANDLED) != 0)
priv->captured_events = g_slist_prepend (priv->captured_events,
gdk_event_copy (event));
else
g_warning ("Captured events can only be stored if they are claimed "
"to be handled by the capturing widget. The use of "
"GTK_CAPTURED_EVENT_HANDLED is mandatory if using "
"GTK_CAPTURED_EVENT_STORE.");
}
/* The widget that was originally to receive the event
* handles motion hints, but the capturing widget might
* not, so ensure we get further motion events.
*/
if ((flags & GTK_CAPTURED_EVENT_HANDLED) != 0 &&
event->type == GDK_MOTION_NOTIFY &&
event->motion.is_hint &&
(gdk_window_get_events (event->any.window) &
GDK_POINTER_MOTION_HINT_MASK) != 0)
gdk_event_request_motions (&event->motion);
return_val = (flags & GTK_CAPTURED_EVENT_HANDLED) != 0 ||
!WIDGET_REALIZED_FOR_EVENT (widget, event);
g_object_unref (widget);
return return_val;
}
/* Returns TRUE if a translation should be done */
gboolean
_gtk_widget_get_translation_to_window (GtkWidget *widget,
@@ -6069,15 +6313,18 @@ gtk_widget_event_internal (GtkWidget *widget,
case GDK_BUTTON_PRESS:
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
case GDK_TOUCH_PRESS:
signal_num = BUTTON_PRESS_EVENT;
break;
case GDK_SCROLL:
signal_num = SCROLL_EVENT;
break;
case GDK_BUTTON_RELEASE:
case GDK_TOUCH_RELEASE:
signal_num = BUTTON_RELEASE_EVENT;
break;
case GDK_MOTION_NOTIFY:
case GDK_TOUCH_MOTION:
signal_num = MOTION_NOTIFY_EVENT;
break;
case GDK_DELETE:
@@ -6145,6 +6392,11 @@ gtk_widget_event_internal (GtkWidget *widget,
case GDK_DAMAGE:
signal_num = DAMAGE_EVENT;
break;
case GDK_MULTITOUCH_ADDED:
case GDK_MULTITOUCH_REMOVED:
case GDK_MULTITOUCH_UPDATED:
signal_num = MULTITOUCH_EVENT;
break;
default:
g_warning ("gtk_widget_event(): unhandled event type: %d", event->type);
signal_num = -1;
@@ -6407,6 +6659,14 @@ void
_gtk_widget_grab_notify (GtkWidget *widget,
gboolean was_grabbed)
{
PressAndHoldData *data;
data = g_object_get_qdata (G_OBJECT (widget), quark_press_and_hold);
if (data && data->device &&
gtk_widget_device_is_shadowed (widget, data->device))
gtk_widget_press_and_hold_cancel (widget);
g_signal_emit (widget, widget_signals[GRAB_NOTIFY], 0, was_grabbed);
}
@@ -6721,6 +6981,353 @@ gtk_widget_has_focus (GtkWidget *widget)
return widget->priv->has_focus;
}
/* --- Press and hold --- */
static inline PressAndHoldData *
gtk_widget_peek_press_and_hold_data (GtkWidget *widget)
{
return g_object_get_qdata (G_OBJECT (widget), quark_press_and_hold);
}
static void
press_and_hold_data_free (PressAndHoldData *data)
{
if (data->popup)
gtk_widget_destroy (data->popup);
if (data->delay_animation_id)
g_source_remove (data->delay_animation_id);
if (data->trigger_id)
g_source_remove (data->trigger_id);
g_slice_free (PressAndHoldData, data);
}
static inline void
gtk_widget_set_press_and_hold_data (GtkWidget *widget,
PressAndHoldData *data)
{
g_object_set_qdata_full (G_OBJECT (widget),
quark_press_and_hold,
data,
(GDestroyNotify) press_and_hold_data_free);
}
static inline PressAndHoldData *
gtk_widget_get_press_and_hold_data (GtkWidget *widget)
{
PressAndHoldData *data;
data = gtk_widget_peek_press_and_hold_data (widget);
if (!data)
{
data = g_slice_new0 (PressAndHoldData);
data->widget = widget;
gtk_widget_set_press_and_hold_data (widget, data);
}
return data;
}
static gboolean
gtk_widget_press_and_hold_cancel (GtkWidget *widget)
{
PressAndHoldData *data;
gboolean return_value;
data = gtk_widget_peek_press_and_hold_data (widget);
g_assert (data != NULL);
g_signal_emit (widget, widget_signals[PRESS_AND_HOLD],
0,
data->device,
GTK_PRESS_AND_HOLD_CANCEL,
-1, -1,
&return_value);
gtk_widget_set_press_and_hold_data (widget, NULL);
return FALSE;
}
gboolean
_gtk_widget_press_and_hold_check_cancel (GtkWidget *widget,
GdkEventButton *event)
{
PressAndHoldData *data;
if (event->type != GDK_BUTTON_RELEASE &&
event->type != GDK_TOUCH_RELEASE)
return FALSE;
data = gtk_widget_peek_press_and_hold_data (widget);
if (data &&
data->trigger_id == 0 &&
data->device == gdk_event_get_device ((GdkEvent *) event))
{
gtk_widget_press_and_hold_cancel (widget);
return TRUE;
}
return FALSE;
}
gboolean
_gtk_widget_press_and_hold_check_threshold (GtkWidget *widget,
GdkEventMotion *event)
{
PressAndHoldData *data;
GdkDevice *device;
if (event->type != GDK_MOTION_NOTIFY &&
event->type != GDK_TOUCH_MOTION)
return FALSE;
data = gtk_widget_peek_press_and_hold_data (widget);
if (!data)
return FALSE;
device = gdk_event_get_device ((GdkEvent *) event);
if (data->device != device)
return FALSE;
_gtk_widget_find_at_coords (event->window, event->x, event->y,
&data->current_x, &data->current_y);
/* Stop press-and-hold if we dragged too far from the starting point */
if (gtk_drag_check_threshold (widget, data->start_x, data->start_y,
data->current_x, data->current_y))
{
gtk_widget_press_and_hold_cancel (widget);
return TRUE;
}
if (data->popup)
gtk_window_move (GTK_WINDOW (data->popup),
event->x_root - data->size / 2,
event->y_root - data->size / 2);
return FALSE;
}
static gboolean
gtk_widget_press_and_hold_timeout (gpointer user_data)
{
gboolean return_value;
GtkWidget *widget = GTK_WIDGET (user_data);
PressAndHoldData *data;
data = gtk_widget_peek_press_and_hold_data (widget);
g_assert (data != NULL);
/* Done, clean up and emit the trigger signal */
g_signal_emit (widget, widget_signals[PRESS_AND_HOLD],
0,
data->device,
GTK_PRESS_AND_HOLD_TRIGGER,
data->current_x, data->current_y,
&return_value);
gtk_widget_set_press_and_hold_data (widget, NULL);
return FALSE;
}
static gboolean
press_and_hold_animation_draw (GtkWidget *widget,
cairo_t *cr,
gpointer user_data)
{
PressAndHoldData *data;
GtkStyleContext *context;
GtkStateFlags state;
gint width, height;
data = gtk_widget_peek_press_and_hold_data (GTK_WIDGET (user_data));
g_assert (data != NULL);
width = gtk_widget_get_allocated_width (widget);
height = gtk_widget_get_allocated_height (widget);
context = gtk_widget_get_style_context (widget);
state = gtk_widget_get_state_flags (widget);
gtk_style_context_set_state (context, state);
if (!gtk_style_context_state_is_running (context, GTK_STATE_FLAG_ACTIVE, NULL))
{
/* The animation just finished, so hide the widget
* and finish the press and hold operation.
*/
data->trigger_id =
gdk_threads_add_idle (gtk_widget_press_and_hold_timeout,
user_data);
gtk_widget_hide (widget);
return FALSE;
}
if (!gtk_widget_is_composited (widget))
{
cairo_t *mask_cr;
cairo_region_t *region;
cairo_surface_t *mask;
mask = cairo_image_surface_create (CAIRO_FORMAT_A1, width, height);
mask_cr = cairo_create (mask);
gtk_render_activity (context, mask_cr, 0, 0, width, height);
cairo_destroy (mask_cr);
region = gdk_cairo_region_create_from_surface (mask);
gdk_window_shape_combine_region (gtk_widget_get_window (widget), region, 0, 0);
cairo_region_destroy (region);
cairo_surface_destroy (mask);
}
else
{
cairo_save (cr);
cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
cairo_paint (cr);
cairo_restore (cr);
}
gtk_render_activity (context, cr, 0, 0, width, height);
return FALSE;
}
static gboolean
gtk_widget_press_and_hold_begin_animation_timeout (gpointer user_data)
{
GtkWidget *widget = GTK_WIDGET (user_data);
PressAndHoldData *data;
gint x, y;
data = gtk_widget_peek_press_and_hold_data (widget);
g_assert (data != NULL);
gdk_window_get_device_position (gdk_screen_get_root_window (gtk_widget_get_screen (widget)),
data->device, &x, &y, NULL);
gtk_window_move (GTK_WINDOW (data->popup),
x - data->size / 2,
y - data->size / 2);
gtk_widget_show (data->popup);
gtk_widget_set_state_flags (GTK_WIDGET (data->popup),
GTK_STATE_FLAG_ACTIVE, FALSE);
return FALSE;
}
static gboolean
gtk_widget_press_and_hold_query (GtkWidget *widget,
GdkDevice *device,
gint x,
gint y)
{
gboolean return_value = FALSE;
g_signal_emit (widget, widget_signals[PRESS_AND_HOLD],
0,
device,
GTK_PRESS_AND_HOLD_QUERY,
x, y,
&return_value);
return return_value;
}
gboolean
_gtk_widget_press_and_hold_check_start (GtkWidget *widget,
GdkEventButton *event)
{
PressAndHoldData *data = gtk_widget_peek_press_and_hold_data (widget);
if (event->type != GDK_BUTTON_PRESS &&
event->type != GDK_TOUCH_PRESS)
return FALSE;
/* Press and hold already in process? */
if (data)
return FALSE;
data = gtk_widget_get_press_and_hold_data (widget);
data->device = gdk_event_get_device ((GdkEvent *) event);
if (gtk_widget_press_and_hold_query (widget, data->device,
event->x, event->y))
{
gint timeout, begin_ani_timeout;
GdkScreen *screen;
GdkVisual *visual;
GtkStyleContext *context;
cairo_region_t *region;
GdkDevice *source_device;
GdkInputSource source;
_gtk_widget_find_at_coords (event->window,
event->x, event->y,
&data->start_x, &data->start_y);
data->current_x = data->start_x;
data->current_y = data->start_y;
g_object_get (gtk_widget_get_settings (GTK_WIDGET (widget)),
"gtk-press-and-hold-timeout", &timeout,
"gtk-timeout-initial", &begin_ani_timeout,
NULL);
screen = gtk_widget_get_screen (widget);
visual = gdk_screen_get_rgba_visual (screen);
data->popup = gtk_window_new (GTK_WINDOW_POPUP);
gtk_window_set_screen (GTK_WINDOW (data->popup), screen);
if (visual)
gtk_widget_set_visual (data->popup, visual);
gtk_widget_set_app_paintable (data->popup, TRUE);
gtk_widget_realize (data->popup);
context = gtk_widget_get_style_context (data->popup);
gtk_style_context_add_class (context, GTK_STYLE_CLASS_PRESS_AND_HOLD);
g_signal_connect (data->popup, "draw",
G_CALLBACK (press_and_hold_animation_draw),
widget);
source_device = gdk_event_get_source_device ((GdkEvent *) event);
source = gdk_device_get_source (source_device);
if (source == GDK_SOURCE_TOUCH)
{
/* Have an indicator with 2.5cm of diameter */
data->size = (25 * gdk_screen_get_width (screen)) /
gdk_screen_get_width_mm (screen);
}
else
data->size = gdk_display_get_default_cursor_size (gtk_widget_get_display (widget));
gtk_window_resize (GTK_WINDOW (data->popup), data->size, data->size);
region = cairo_region_create ();
gdk_window_input_shape_combine_region (gtk_widget_get_window (data->popup), region, 0, 0);
cairo_region_destroy (region);
/* delay loading the animation by the double click timeout */
data->delay_animation_id =
gdk_threads_add_timeout (begin_ani_timeout,
gtk_widget_press_and_hold_begin_animation_timeout,
widget);
}
else
gtk_widget_set_press_and_hold_data (widget, NULL);
return FALSE;
}
/**
* gtk_widget_has_visible_focus:
* @widget: a #GtkWidget
@@ -10180,6 +10787,15 @@ gtk_widget_finalize (GObject *object)
_gtk_widget_free_cached_sizes (widget);
if (priv->captured_events)
{
g_slist_foreach (priv->captured_events, (GFunc) gdk_event_free, NULL);
g_slist_free (priv->captured_events);
}
if (priv->gestures)
g_array_free (priv->gestures, TRUE);
if (g_object_is_floating (object))
g_warning ("A floating object was finalized. This means that someone\n"
"called g_object_unref() on an object that had only a floating\n"
@@ -13950,3 +14566,218 @@ _gtk_widget_set_style (GtkWidget *widget,
{
widget->priv->style = style;
}
/**
* gtk_widget_release_captured_events:
* @widget: the #GtkWidget holding the events
* @emit: #TRUE if the events must be emitted on the targed widget
*
* Releases the events that a widget has captured and stored
* in the #GtkWidget::captured-event signal. if @emit is #TRUE,
* event emission will continue as if uncaptured (i.e. the
* capturing phase will continue down the hierarchy to the event
* widget, followed by the event being propagated up again).
*
* Since: 3.4
**/
void
gtk_widget_release_captured_events (GtkWidget *widget,
gboolean emit)
{
GtkWidgetPrivate *priv;
GSList *l;
g_return_if_fail (GTK_IS_WIDGET (widget));
priv = widget->priv;
if (emit)
{
priv->captured_events = g_slist_reverse (priv->captured_events);
for (l = priv->captured_events; l; l = l->next)
{
GtkWidget *event_widget;
GdkEvent *event = l->data;
gboolean propagated = FALSE;
event_widget = gtk_get_event_widget (event);
/* Find out the next widget handling the captured event,
* if event_widget == widget, the capturing phase for
* this event has finished, so the next thing is bubbling
* it up.
*/
if (event_widget != widget &&
gtk_widget_is_ancestor (event_widget, widget))
{
GtkWidget *parent, *intermediate;
intermediate = event_widget;
while (intermediate)
{
parent = gtk_widget_get_parent (intermediate);
/* Found the next intermediate that should handle
* the captured event.
*/
if (parent == widget)
break;
intermediate = parent;
}
if (intermediate)
propagated = _gtk_propagate_captured_event (event_widget,
event,
intermediate);
}
if (propagated)
continue;
switch (event->type)
{
case GDK_PROPERTY_NOTIFY:
case GDK_FOCUS_CHANGE:
case GDK_CONFIGURE:
case GDK_MAP:
case GDK_UNMAP:
case GDK_SELECTION_CLEAR:
case GDK_SELECTION_REQUEST:
case GDK_SELECTION_NOTIFY:
case GDK_CLIENT_EVENT:
case GDK_VISIBILITY_NOTIFY:
case GDK_WINDOW_STATE:
case GDK_GRAB_BROKEN:
case GDK_DAMAGE:
/* These events are capturable, but don't bubble up */
gtk_widget_event (event_widget, event);
break;
default:
/* All other capturable events do bubble up */
gtk_propagate_event (event_widget, event);
break;
}
}
}
g_slist_foreach (priv->captured_events, (GFunc) gdk_event_free, NULL);
g_slist_free (priv->captured_events);
priv->captured_events = NULL;
}
void
_gtk_widget_gesture_stroke (GtkWidget *widget,
GdkEvent *event)
{
GtkGesturesInterpreter *interpreter;
GtkWidgetPrivate *priv;
priv = widget->priv;
if (!priv->gestures ||
priv->gestures->len == 0)
return;
interpreter = g_object_get_qdata (G_OBJECT (widget), quark_gestures_interpreter);
g_assert (interpreter != NULL);
gtk_gestures_interpreter_feed_event (interpreter, event);
}
void
_gtk_widget_gesture_finish (GtkWidget *widget)
{
GtkGesturesInterpreter *interpreter;
GtkWidgetPrivate *priv;
guint gesture;
priv = widget->priv;
if (!priv->gestures ||
priv->gestures->len == 0)
return;
interpreter = g_object_get_qdata (G_OBJECT (widget), quark_gestures_interpreter);
g_assert (interpreter != NULL);
if (gtk_gestures_interpreter_get_n_active_strokes (interpreter) != 0)
return;
if (gtk_gestures_interpreter_finish (interpreter, &gesture))
g_signal_emit (widget, widget_signals[GESTURE], 0, gesture);
}
/**
* gtk_widget_enable_gesture:
* @widget: a #GtkWidget
* @gesture_id: gesture ID to handle
*
* Enables @gesture_id to be recognized in @widget.
**/
void
gtk_widget_enable_gesture (GtkWidget *widget,
guint gesture_id)
{
GtkGesturesInterpreter *interpreter;
GtkWidgetPrivate *priv;
g_return_if_fail (GTK_IS_WIDGET (widget));
priv = widget->priv;
interpreter = g_object_get_qdata (G_OBJECT (widget), quark_gestures_interpreter);
if (!interpreter)
{
interpreter = gtk_gestures_interpreter_new ();
g_object_set_qdata_full (G_OBJECT (widget), quark_gestures_interpreter,
interpreter, (GDestroyNotify) g_object_unref);
}
if (!gtk_gestures_interpreter_add_gesture (interpreter, gesture_id))
return;
if (!priv->gestures)
priv->gestures = g_array_new (FALSE, FALSE, sizeof (guint));
g_array_append_val (priv->gestures, gesture_id);
}
/**
* gtk_widget_disable_gesture:
* @widget: a #GtkWidget
* @gesture_id: Gesture ID to stop handling
*
* Disables a gesture previously added through gtk_widget_enable_gesture()
**/
void
gtk_widget_disable_gesture (GtkWidget *widget,
guint gesture_id)
{
GtkGesturesInterpreter *interpreter;
GtkWidgetPrivate *priv;
guint i;
g_return_if_fail (GTK_IS_WIDGET (widget));
priv = widget->priv;
if (!priv->gestures ||
priv->gestures->len == 0)
return;
interpreter = g_object_get_qdata (G_OBJECT (widget), quark_gestures_interpreter);
g_assert (interpreter != NULL);
for (i = 0; i < priv->gestures->len; i++)
{
if (gesture_id == g_array_index (priv->gestures, guint, i))
{
g_array_remove_index (priv->gestures, i);
gtk_gestures_interpreter_remove_gesture (interpreter, gesture_id);
break;
}
}
}
+30 -4
View File
@@ -49,6 +49,13 @@ typedef enum
GTK_WIDGET_HELP_WHATS_THIS
} GtkWidgetHelpType;
typedef enum
{
GTK_PRESS_AND_HOLD_QUERY,
GTK_PRESS_AND_HOLD_TRIGGER,
GTK_PRESS_AND_HOLD_CANCEL
} GtkPressAndHoldAction;
/* Macro for casting a pointer to a GtkWidget or GtkWidgetClass pointer.
* Macros for testing whether `widget' or `klass' are of type GTK_TYPE_WIDGET.
*/
@@ -425,6 +432,21 @@ struct _GtkWidgetClass
void (* style_updated) (GtkWidget *widget);
GtkCapturedEventFlags (* captured_event) (GtkWidget *widget,
GdkEvent *event);
gboolean (* press_and_hold) (GtkWidget *widget,
GdkDevice *device,
GtkPressAndHoldAction action,
gint x,
gint y);
gboolean (* multitouch_event) (GtkWidget *widget,
GdkEventMultiTouch *event);
void (* gesture) (GtkWidget *widget,
guint gesture_id);
/*< private >*/
GtkWidgetClassPrivate *priv;
@@ -433,10 +455,6 @@ struct _GtkWidgetClass
void (*_gtk_reserved2) (void);
void (*_gtk_reserved3) (void);
void (*_gtk_reserved4) (void);
void (*_gtk_reserved5) (void);
void (*_gtk_reserved6) (void);
void (*_gtk_reserved7) (void);
void (*_gtk_reserved8) (void);
};
struct _GtkWidgetAuxInfo
@@ -892,6 +910,14 @@ GtkWidgetPath * gtk_widget_get_path (GtkWidget *widget);
GdkModifierType gtk_widget_get_modifier_mask (GtkWidget *widget,
GdkModifierIntent intent);
void gtk_widget_release_captured_events (GtkWidget *widget,
gboolean emit);
/* Gestures */
void gtk_widget_enable_gesture (GtkWidget *widget,
guint gesture_id);
void gtk_widget_disable_gesture (GtkWidget *widget,
guint gesture_id);
G_END_DECLS
+14
View File
@@ -168,6 +168,20 @@ void _gtk_widget_set_style (GtkWidget *widget,
GtkStyle *style);
gboolean _gtk_widget_captured_event (GtkWidget *widget,
GdkEvent *event);
gboolean _gtk_widget_press_and_hold_check_start (GtkWidget *widget,
GdkEventButton *event);
gboolean _gtk_widget_press_and_hold_check_cancel (GtkWidget *widget,
GdkEventButton *event);
gboolean _gtk_widget_press_and_hold_check_threshold (GtkWidget *widget,
GdkEventMotion *event);
void _gtk_widget_gesture_stroke (GtkWidget *widget,
GdkEvent *event);
void _gtk_widget_gesture_finish (GtkWidget *widget);
G_END_DECLS
#endif /* __GTK_WIDGET_PRIVATE_H__ */
+4
View File
@@ -143,6 +143,10 @@ TEST_PROGS += regression-tests
regression_tests_SOURCES = regression-tests.c
regression_tests_LDADD = $(progs_ldadd)
TEST_PROGS += gestures
gestures_SOURCES = gestures.c
gestures_LDADD = $(progs_ldadd)
EXTRA_DIST += \
file-chooser-test-dir/empty \
file-chooser-test-dir/text.txt
+253
View File
@@ -0,0 +1,253 @@
/* gestures tests.
*
* Copyright (C) 2011 Carlos Garnacho <carlos@lanedo.com>
* Authors: Carlos Garnacho <carlos@lanedo.com>
*
* 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 <gtk/gtk.h>
static void
gesture_detected_cb (GtkGesturesInterpreter *interpreter,
guint gesture_id,
gdouble confidence,
gpointer user_data)
{
gdouble *confidence_ret = user_data;
*confidence_ret = confidence;
}
static void
append_event_to_interpreter (GtkGesturesInterpreter *interpreter,
gdouble x,
gdouble y)
{
GdkDeviceManager *device_manager;
GdkDevice *client_pointer;
GdkDisplay *display;
GdkEvent *event;
display = gdk_display_get_default ();
device_manager = gdk_display_get_device_manager (display);
client_pointer = gdk_device_manager_get_client_pointer (device_manager);
event = gdk_event_new (GDK_MOTION_NOTIFY);
event->motion.x = x;
event->motion.y = y;
event->motion.x_root = x;
event->motion.y_root = y;
gdk_event_set_device (event, client_pointer);
gdk_event_set_source_device (event, client_pointer);
gtk_gestures_interpreter_feed_event (interpreter, event);
gdk_event_free (event);
}
static void
test_empty_interpreter (void)
{
GtkGesturesInterpreter *interpreter;
gboolean gesture_detected;
gdouble confidence = 0;
interpreter = gtk_gestures_interpreter_new ();
g_signal_connect (interpreter, "gesture-detected",
G_CALLBACK (gesture_detected_cb),
&confidence);
/* Feed some events */
append_event_to_interpreter (interpreter, 0, 0);
append_event_to_interpreter (interpreter, 100, 0);
append_event_to_interpreter (interpreter, 100, 100);
append_event_to_interpreter (interpreter, 0, 100);
gesture_detected = gtk_gestures_interpreter_finish (interpreter, NULL);
g_assert (gesture_detected == FALSE);
g_assert_cmpfloat (confidence, ==, 0);
g_object_unref (interpreter);
}
static void
test_equality (void)
{
GtkGesturesInterpreter *interpreter;
gboolean gesture_detected;
gdouble confidence = 0;
guint gesture_id;
interpreter = gtk_gestures_interpreter_new ();
g_signal_connect (interpreter, "gesture-detected",
G_CALLBACK (gesture_detected_cb),
&confidence);
gtk_gestures_interpreter_add_gesture (interpreter,
GTK_GESTURE_SWIPE_RIGHT);
/* Feed the events */
append_event_to_interpreter (interpreter, 0, 0);
append_event_to_interpreter (interpreter, 100, 0);
gesture_detected = gtk_gestures_interpreter_finish (interpreter, &gesture_id);
g_assert (gesture_detected != FALSE);
g_assert_cmpuint (gesture_id, ==, GTK_GESTURE_SWIPE_RIGHT);
g_assert_cmpfloat (confidence, ==, 1);
g_object_unref (interpreter);
}
static void
test_events_ubiquity (void)
{
GtkGesturesInterpreter *interpreter;
gboolean gesture_detected;
gdouble confidence = 0;
guint gesture_id;
interpreter = gtk_gestures_interpreter_new ();
g_signal_connect (interpreter, "gesture-detected",
G_CALLBACK (gesture_detected_cb),
&confidence);
gtk_gestures_interpreter_add_gesture (interpreter,
GTK_GESTURE_SWIPE_RIGHT);
/* Feed the events, huge scale */
append_event_to_interpreter (interpreter, 0, 0);
append_event_to_interpreter (interpreter, 1000, 0);
gesture_detected = gtk_gestures_interpreter_finish (interpreter, &gesture_id);
g_assert (gesture_detected != FALSE);
g_assert_cmpuint (gesture_id, ==, GTK_GESTURE_SWIPE_RIGHT);
g_assert_cmpfloat (confidence, ==, 1);
/* Feed other events, displaced somewhere, and
* at a different scale, it's the X displacement
* to the right what counts.
*/
append_event_to_interpreter (interpreter, 500, 120);
append_event_to_interpreter (interpreter, 600, 120);
gesture_detected = gtk_gestures_interpreter_finish (interpreter, &gesture_id);
g_assert (gesture_detected != FALSE);
g_assert_cmpuint (gesture_id, ==, GTK_GESTURE_SWIPE_RIGHT);
g_assert_cmpfloat (confidence, ==, 1);
g_object_unref (interpreter);
}
static void
test_opposite_gesture (void)
{
GtkGesturesInterpreter *interpreter;
gboolean gesture_detected;
gdouble confidence = 1;
interpreter = gtk_gestures_interpreter_new ();
g_signal_connect (interpreter, "gesture-detected",
G_CALLBACK (gesture_detected_cb),
&confidence);
gtk_gestures_interpreter_add_gesture (interpreter,
GTK_GESTURE_SWIPE_RIGHT);
/* Feed the events, swipe to left */
append_event_to_interpreter (interpreter, 100, 0);
append_event_to_interpreter (interpreter, 0, 0);
gesture_detected = gtk_gestures_interpreter_finish (interpreter, NULL);
g_assert (gesture_detected == FALSE);
g_assert_cmpfloat (confidence, ==, 0);
g_object_unref (interpreter);
}
static void
test_ignore_initial_orientation (void)
{
GtkGesturesInterpreter *interpreter;
gdouble initial_confidence, conf;
gdouble confidence = 1;
interpreter = gtk_gestures_interpreter_new ();
g_signal_connect (interpreter, "gesture-detected",
G_CALLBACK (gesture_detected_cb),
&confidence);
/* Use a circular gesture, which ignores initial orientation.
* Stroke square rectangles at different orientations, which should
* yield the same (lack of) confidence about the stroke.
*/
gtk_gestures_interpreter_add_gesture (interpreter,
GTK_GESTURE_CIRCULAR_CLOCKWISE);
/* First rectangle */
append_event_to_interpreter (interpreter, 0, 0);
append_event_to_interpreter (interpreter, 100, 0);
append_event_to_interpreter (interpreter, 100, 100);
append_event_to_interpreter (interpreter, 0, 100);
append_event_to_interpreter (interpreter, 0, 0);
gtk_gestures_interpreter_finish (interpreter, NULL);
initial_confidence = confidence;
/* Second rectangle, completely flipped over */
append_event_to_interpreter (interpreter, 100, 100);
append_event_to_interpreter (interpreter, 0, 100);
append_event_to_interpreter (interpreter, 0, 0);
append_event_to_interpreter (interpreter, 100, 0);
append_event_to_interpreter (interpreter, 100, 100);
gtk_gestures_interpreter_finish (interpreter, NULL);
conf = confidence;
g_assert_cmpfloat (initial_confidence, ==, conf);
/* Third rectangle, 45º degrees rotation, different scale */
append_event_to_interpreter (interpreter, 50, 0);
append_event_to_interpreter (interpreter, 100, 50);
append_event_to_interpreter (interpreter, 50, 100);
append_event_to_interpreter (interpreter, 0, 50);
append_event_to_interpreter (interpreter, 50, 0);
gtk_gestures_interpreter_finish (interpreter, NULL);
conf = confidence;
g_assert_cmpfloat (initial_confidence, ==, conf);
g_object_unref (interpreter);
}
int
main (int argc, char *argv[])
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/gestures/empty-interpreter", test_empty_interpreter);
g_test_add_func ("/gestures/equality", test_equality);
g_test_add_func ("/gestures/events-ubiquity", test_events_ubiquity);
g_test_add_func ("/gestures/opposite-gesture", test_opposite_gesture);
g_test_add_func ("/gestures/ignore-initial-orientation", test_ignore_initial_orientation);
return g_test_run ();
}
+17
View File
@@ -55,6 +55,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \
testfontchooserdialog \
testframe \
testgeometry \
testgestures \
testgiconpixbuf \
testgrid \
testgtk \
@@ -64,6 +65,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \
testicontheme \
testimage \
testinput \
testkineticscrolling \
testlockbutton \
testmenubars \
testmountoperation \
@@ -77,6 +79,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \
testorientable \
testoverlay \
testprint \
testpressandhold \
testrecentchooser \
testrecentchoosermenu \
testrichtext \
@@ -176,11 +179,13 @@ testfontchooser_DEPENDENCIES = $(TEST_DEPS)
testfontchooserdialog_DEPENDENCIES = $(TEST_DEPS)
testframe_DEPENDENCIES = $(TEST_DEPS)
testgeometry_DEPENDENCIES = $(TEST_DEPS)
testgestures_DEPENDENCIES = $(TEST_DEPS)
testgiconpixbuf = $(TEST_DEPS)
testgrid_DEPENDENCIES = $(TEST_DEPS)
testgtk_DEPENDENCIES = $(TEST_DEPS)
testinput_DEPENDENCIES = $(TEST_DEPS)
testimage_DEPENDENCIES = $(TEST_DEPS)
testkineticscrolling_DEPENDENCIES = $(TEST_DEPS)
testlockbutton_DEPENDENCIES = $(TEST_DEPS)
testmenubars_DEPENDENCIES = $(TEST_DEPS)
testmountoperation_DEPENDENCIES = $(TEST_DEPS)
@@ -196,6 +201,7 @@ testappchooserbutton_DEPENDENCIES = $(TEST_DEPS)
testorientable_DEPENDENCIES = $(TEST_DEPS)
testoverlay_DEPENDENCIES = $(TEST_DEPS)
testprint_DEPENDENCIES = $(TEST_DEPS)
testpressandhold_DEPENDENCIES = $(TEST_DEPS)
testrecentchooser_DEPENDENCIES = $(TEST_DEPS)
testrecentchoosermenu_DEPENDENCIES = $(TEST_DEPS)
testrichtext_DEPENDENCIES = $(TEST_DEPS)
@@ -271,6 +277,7 @@ testfontchooser_LDADD = $(LDADDS)
testfontchooserdialog_LDADD = $(LDADDS)
testframe_LDADD = $(LDADDS)
testgeometry_LDADD = $(LDADDS)
testgestures_LDADD = $(LDADDS)
testgiconpixbuf_LDADD = $(LDADDS)
testgrid_LDADD = $(LDADDS)
testgtk_LDADD = $(LDADDS)
@@ -280,6 +287,7 @@ testiconview_LDADD = $(LDADDS)
testiconview_keynav_LDADD = $(LDADDS)
testinput_LDADD = $(LDADDS)
testimage_LDADD = $(LDADDS)
testkineticscrolling_LDADD = $(LDADDS)
testlockbutton_LDADD = $(LDADDS)
testmenubars_LDADD = $(LDADDS)
testmountoperation_LDADD = $(LDADDS)
@@ -295,6 +303,7 @@ testappchooserbutton_LDADD = $(LDADDS)
testorientable_LDADD = $(LDADDS)
testoverlay_LDADD = $(LDADDS)
testprint_LDADD = $(LDADDS)
testpressandhold_LDADD = $(LDADDS)
testrecentchooser_LDADD = $(LDADDS)
testrecentchoosermenu_LDADD = $(LDADDS)
testrichtext_LDADD = $(LDADDS)
@@ -408,6 +417,9 @@ testprint_SOURCES = \
testprintfileoperation.h \
testprintfileoperation.c
testpressandhold_SOURCES = \
testpressandhold.c
testsocket_SOURCES = \
testsocket.c \
testsocket_common.c
@@ -441,6 +453,9 @@ testframe_SOURCES = \
testgeometry_SOURCES = \
testgeometry.c
testgestures_SOURCES = \
testgestures.c
testgiconpixbuf_SOURCES = \
testgiconpixbuf.c
@@ -524,6 +539,8 @@ testpixbuf_save_SOURCES = testpixbuf-save.c
testcolorchooser_SOURCES = testcolorchooser.c
testkineticscrolling_SOURCES = testkineticscrolling.c
EXTRA_DIST += \
gradient1.png \
prop-editor.h \
+391
View File
@@ -0,0 +1,391 @@
#include <gtk/gtk.h>
#define GTK_COMPILATION
#include <gtk/gtkgesturesinterpreter.h>
#undef GTK_COMPILATION
static GHashTable *strokes;
static guint shown_gesture = 0;
static guint timeout_id = 0;
static guint m_shaped_gesture_id = 0;
static guint two_touches_upwards_gesture_id = 0;
static void
free_stroke (GArray *array)
{
g_array_free (array, TRUE);
}
static gboolean
hide_gesture_cb (gpointer user_data)
{
timeout_id = 0;
shown_gesture = 0;
g_hash_table_remove_all (strokes);
gtk_widget_queue_draw (GTK_WIDGET (user_data));
return FALSE;
}
static gboolean
draw_cb (GtkWidget *widget,
cairo_t *cr,
gpointer user_data)
{
GdkPoint *point;
GHashTableIter iter;
gpointer value;
gint i;
if (shown_gesture != 0)
{
gint width, height, size;
cairo_save (cr);
width = gdk_window_get_width (gtk_widget_get_window (widget));
height = gdk_window_get_height (gtk_widget_get_window (widget));
size = MIN (width, height);
size -= size / 4;
cairo_translate (cr, width / 2, height / 2);
cairo_set_line_width (cr, 10);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_set_source_rgb (cr, 0.8, 0.8, 0.8);
switch (shown_gesture)
{
case GTK_GESTURE_SWIPE_LEFT:
case GTK_GESTURE_SWIPE_RIGHT:
case GTK_GESTURE_SWIPE_UP:
case GTK_GESTURE_SWIPE_DOWN:
if (shown_gesture == GTK_GESTURE_SWIPE_RIGHT)
cairo_rotate (cr, (G_PI * 3) / 2);
else if (shown_gesture == GTK_GESTURE_SWIPE_LEFT)
cairo_rotate (cr, G_PI / 2);
else if (shown_gesture == GTK_GESTURE_SWIPE_UP)
cairo_rotate (cr, G_PI);
cairo_move_to (cr, 0, - size / 2);
cairo_line_to (cr, 0, size / 2);
cairo_move_to (cr, 0, size / 2);
cairo_rel_line_to (cr, -size / 4, -size / 4);
cairo_move_to (cr, 0, size / 2);
cairo_rel_line_to (cr, size / 4, -size / 4);
cairo_stroke (cr);
break;
case GTK_GESTURE_CIRCULAR_COUNTERCLOCKWISE:
cairo_move_to (cr, 0, - size / 2);
cairo_rel_line_to (cr, size / 8, - size / 8);
cairo_move_to (cr, 0, - size / 2);
cairo_rel_line_to (cr, size / 8, size / 8);
cairo_new_sub_path (cr);
cairo_arc (cr, 0, 0, size / 2, (3 * G_PI) / 2, (5 * G_PI) / 4);
cairo_stroke (cr);
break;
case GTK_GESTURE_CIRCULAR_CLOCKWISE:
cairo_move_to (cr, 0, - size / 2);
cairo_rel_line_to (cr, - size / 8, - size / 8);
cairo_move_to (cr, 0, - size / 2);
cairo_rel_line_to (cr, - size / 8, size / 8);
cairo_new_sub_path (cr);
cairo_arc_negative (cr, 0, 0, size / 2, (3 * G_PI) / 2, (7* G_PI) / 4);
cairo_stroke (cr);
break;
default:
if (shown_gesture == m_shaped_gesture_id ||
shown_gesture == two_touches_upwards_gesture_id)
{
PangoAttrList *attr_list;
PangoLayout *layout;
if (shown_gesture == two_touches_upwards_gesture_id)
layout = gtk_widget_create_pango_layout (widget, "II");
else
layout = gtk_widget_create_pango_layout (widget, "M");
attr_list = pango_layout_get_attributes (layout);
if (!attr_list)
attr_list = pango_attr_list_new ();
pango_attr_list_insert (attr_list,
pango_attr_size_new_absolute (200 * PANGO_SCALE));
pango_layout_set_attributes (layout, attr_list);
cairo_move_to (cr, -100, -100);
pango_cairo_show_layout (cr, layout);
}
break;
}
cairo_restore (cr);
}
g_hash_table_iter_init (&iter, strokes);
while (g_hash_table_iter_next (&iter, NULL, &value))
{
GArray *coords = value;
if (coords->len > 0)
{
cairo_save (cr);
cairo_set_line_width (cr, 2);
point = &g_array_index (coords, GdkPoint, 0);
cairo_move_to (cr, point->x, point->y);
for (i = 0; i < coords->len; i++)
{
point = &g_array_index (coords, GdkPoint, i);
cairo_line_to (cr, point->x, point->y);
}
cairo_stroke (cr);
cairo_restore (cr);
}
}
return FALSE;
}
static gboolean
append_event_coordinates (GdkEvent *event)
{
GdkPoint point;
gdouble x, y;
guint touch_id;
GArray *coords;
if (!gdk_event_get_coords (event, &x, &y))
return FALSE;
if (!gdk_event_get_touch_id (event, &touch_id))
touch_id = 0;
coords = g_hash_table_lookup (strokes,
GUINT_TO_POINTER (touch_id));
if (!coords)
return FALSE;
point.x = x;
point.y = y;
g_array_append_val (coords, point);
return TRUE;
}
static gboolean
motion_notify_cb (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
GtkGesturesInterpreter *interpreter;
GdkModifierType state;
interpreter = user_data;
if (!gdk_event_get_state (event, &state))
return FALSE;
if ((state & GDK_BUTTON1_MASK) == 0)
return FALSE;
append_event_coordinates (event);
gtk_gestures_interpreter_feed_event (interpreter, event);
gtk_widget_queue_draw (widget);
return FALSE;
}
static gboolean
button_release_cb (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
GtkGesturesInterpreter *interpreter;
guint gesture_id;
interpreter = user_data;
if (timeout_id != 0)
g_source_remove (timeout_id);
timeout_id = g_timeout_add (500, hide_gesture_cb, widget);
append_event_coordinates (event);
gtk_gestures_interpreter_feed_event (interpreter, event);
if (gtk_gestures_interpreter_get_n_active_strokes (interpreter) == 0)
{
if (gtk_gestures_interpreter_finish (interpreter, &gesture_id))
shown_gesture = gesture_id;
gtk_widget_queue_draw (widget);
}
return FALSE;
}
static gboolean
button_press_cb (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
GtkGesturesInterpreter *interpreter;
GArray *coords;
guint touch_id;
interpreter = user_data;
if (timeout_id != 0)
{
g_hash_table_remove_all (strokes);
g_source_remove (timeout_id);
timeout_id = 0;
}
shown_gesture = 0;
if (!gdk_event_get_touch_id (event, &touch_id))
touch_id = 0;
coords = g_array_new (FALSE, FALSE, sizeof (GdkPoint));
g_hash_table_insert (strokes,
GUINT_TO_POINTER (touch_id),
coords);
append_event_coordinates (event);
gtk_gestures_interpreter_feed_event (interpreter, event);
gtk_widget_queue_draw (widget);
return FALSE;
}
static GtkWidget *
create_window (GtkGesturesInterpreter *interpreter)
{
GtkWidget *window;
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_set_app_paintable (window, TRUE);
gtk_widget_set_size_request (window, 400, 400);
gtk_window_set_title (GTK_WINDOW (window), "Gestures demo");
gtk_widget_add_events (window,
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_POINTER_MOTION_MASK |
GDK_TOUCH_MASK);
g_signal_connect (window, "destroy",
G_CALLBACK (gtk_main_quit), NULL);
g_signal_connect (window, "motion-notify-event",
G_CALLBACK (motion_notify_cb), interpreter);
g_signal_connect (window, "button-release-event",
G_CALLBACK (button_release_cb), interpreter);
g_signal_connect (window, "button-press-event",
G_CALLBACK (button_press_cb), interpreter);
g_signal_connect (window, "draw",
G_CALLBACK (draw_cb), NULL);
gtk_widget_show (window);
return window;
}
static GtkGesture *
create_m_shaped_gesture (void)
{
GtkGestureStroke *stroke;
GtkGesture *gesture;
stroke = gtk_gesture_stroke_new ();
gtk_gesture_stroke_append_vector (stroke, 0, 100);
gtk_gesture_stroke_append_vector (stroke, 3 * G_PI_4, 50);
gtk_gesture_stroke_append_vector (stroke, G_PI_4, 50);
gtk_gesture_stroke_append_vector (stroke, G_PI, 100);
gesture = gtk_gesture_new (stroke, 0);
gtk_gesture_stroke_free (stroke);
return gesture;
}
static GtkGesture *
create_two_touches_upwards_gesture (void)
{
GtkGestureStroke *stroke;
GtkGesture *gesture;
stroke = gtk_gesture_stroke_new ();
gtk_gesture_stroke_append_vector (stroke, 0, 100);
gesture = gtk_gesture_new (stroke, 0); //GTK_GESTURE_FLAG_IGNORE_INITIAL_ORIENTATION);
/* Add again the same stroke, with an offset */
gtk_gesture_add_stroke (gesture, stroke, 50, 0);
gtk_gesture_stroke_free (stroke);
return gesture;
}
static GtkGesturesInterpreter *
create_interpreter (void)
{
GtkGesturesInterpreter *interpreter;
GtkGesture *gesture;
interpreter = gtk_gestures_interpreter_new ();
gtk_gestures_interpreter_add_gesture (interpreter, GTK_GESTURE_SWIPE_RIGHT);
gtk_gestures_interpreter_add_gesture (interpreter, GTK_GESTURE_SWIPE_LEFT);
gtk_gestures_interpreter_add_gesture (interpreter, GTK_GESTURE_SWIPE_UP);
gtk_gestures_interpreter_add_gesture (interpreter, GTK_GESTURE_SWIPE_DOWN);
gtk_gestures_interpreter_add_gesture (interpreter, GTK_GESTURE_CIRCULAR_CLOCKWISE);
gtk_gestures_interpreter_add_gesture (interpreter, GTK_GESTURE_CIRCULAR_COUNTERCLOCKWISE);
gesture = create_m_shaped_gesture ();
m_shaped_gesture_id = gtk_gesture_register_static (gesture);
gtk_gestures_interpreter_add_gesture (interpreter, m_shaped_gesture_id);
gesture = create_two_touches_upwards_gesture ();
two_touches_upwards_gesture_id = gtk_gesture_register_static (gesture);
gtk_gestures_interpreter_add_gesture (interpreter,
two_touches_upwards_gesture_id);
return interpreter;
}
int
main (int argc, char *argv[])
{
GtkGesturesInterpreter *interpreter;
GtkWidget *window;
gtk_init (&argc, &argv);
strokes = g_hash_table_new_full (NULL, NULL, NULL,
(GDestroyNotify) free_stroke);
interpreter = create_interpreter ();
window = create_window (interpreter);
gtk_main ();
gtk_widget_destroy (window);
g_object_unref (interpreter);
return 0;
}
+150
View File
@@ -0,0 +1,150 @@
#include <gtk/gtk.h>
enum
{
TARGET_GTK_TREE_MODEL_ROW
};
static GtkTargetEntry row_targets[] =
{
{ "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, TARGET_GTK_TREE_MODEL_ROW }
};
static void
on_button_clicked (GtkWidget *widget, gpointer data)
{
g_print ("Button %d clicked\n", GPOINTER_TO_INT (data));
}
static void
kinetic_scrolling (void)
{
GtkWidget *window, *swindow, *grid;
GtkWidget *label;
GtkWidget *button_grid, *button;
GtkWidget *treeview;
GtkCellRenderer *renderer;
GtkListStore *store;
GtkWidget *textview;
gint i;
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_container_set_border_width (GTK_CONTAINER (window), 5);
gtk_window_set_default_size (GTK_WINDOW (window), 400, 400);
g_signal_connect (window, "delete_event",
G_CALLBACK (gtk_main_quit), NULL);
grid = gtk_grid_new ();
label = gtk_label_new ("Non scrollable widget using viewport");
gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1);
gtk_widget_set_hexpand (label, TRUE);
gtk_widget_show (label);
label = gtk_label_new ("Scrollable widget: TreeView");
gtk_grid_attach (GTK_GRID (grid), label, 1, 0, 1, 1);
gtk_widget_set_hexpand (label, TRUE);
gtk_widget_show (label);
label = gtk_label_new ("Scrollable widget: TextView");
gtk_grid_attach (GTK_GRID (grid), label, 2, 0, 1, 1);
gtk_widget_set_hexpand (label, TRUE);
gtk_widget_show (label);
button_grid = gtk_grid_new ();
for (i = 0; i < 80; i++)
{
gchar *label = g_strdup_printf ("Button number %d", i);
button = gtk_button_new_with_label (label);
gtk_grid_attach (GTK_GRID (button_grid), button, 0, i, 1, 1);
gtk_widget_set_hexpand (button, TRUE);
gtk_widget_show (button);
g_signal_connect (button, "clicked",
G_CALLBACK (on_button_clicked),
GINT_TO_POINTER (i));
g_free (label);
}
swindow = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_kinetic_scrolling (GTK_SCROLLED_WINDOW (swindow),
GTK_KINETIC_SCROLLING_ENABLED);
gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (swindow), button_grid);
gtk_widget_show (button_grid);
gtk_grid_attach (GTK_GRID (grid), swindow, 0, 1, 1, 1);
gtk_widget_show (swindow);
treeview = gtk_tree_view_new ();
gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (treeview),
GDK_BUTTON1_MASK,
row_targets,
G_N_ELEMENTS (row_targets),
GDK_ACTION_MOVE | GDK_ACTION_COPY);
gtk_tree_view_enable_model_drag_dest (GTK_TREE_VIEW (treeview),
row_targets,
G_N_ELEMENTS (row_targets),
GDK_ACTION_MOVE | GDK_ACTION_COPY);
renderer = gtk_cell_renderer_text_new ();
g_object_set (renderer, "editable", TRUE, NULL);
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
0, "Title",
renderer,
"text", 0,
NULL);
store = gtk_list_store_new (1, G_TYPE_STRING);
for (i = 0; i < 80; i++)
{
GtkTreeIter iter;
gchar *label = g_strdup_printf ("Row number %d", i);
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, 0, label, -1);
g_free (label);
}
gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), GTK_TREE_MODEL (store));
g_object_unref (store);
swindow = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_kinetic_scrolling (GTK_SCROLLED_WINDOW (swindow),
GTK_KINETIC_SCROLLING_ENABLED |
GTK_KINETIC_SCROLLING_CAPTURE_BUTTON_PRESS);
gtk_container_add (GTK_CONTAINER (swindow), treeview);
gtk_widget_show (treeview);
gtk_grid_attach (GTK_GRID (grid), swindow, 1, 1, 1, 1);
gtk_widget_set_hexpand (swindow, TRUE);
gtk_widget_set_vexpand (swindow, TRUE);
gtk_widget_show (swindow);
textview = gtk_text_view_new ();
swindow = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_kinetic_scrolling (GTK_SCROLLED_WINDOW (swindow),
GTK_KINETIC_SCROLLING_ENABLED |
GTK_KINETIC_SCROLLING_CAPTURE_BUTTON_PRESS);
gtk_container_add (GTK_CONTAINER (swindow), textview);
gtk_widget_show (textview);
gtk_grid_attach (GTK_GRID (grid), swindow, 2, 1, 1, 1);
gtk_widget_set_hexpand (swindow, TRUE);
gtk_widget_set_vexpand (swindow, TRUE);
gtk_widget_show (swindow);
gtk_container_add (GTK_CONTAINER (window), grid);
gtk_widget_show (grid);
gtk_widget_show (window);
}
int
main (int argc, char **argv)
{
gtk_init (NULL, NULL);
kinetic_scrolling ();
gtk_main ();
return 0;
}
+162
View File
@@ -0,0 +1,162 @@
/* testpressandhold.c: Test application for GTK+ >= 3.2 press-n-hold code
*
* Copyright (C) 2007,2008 Imendio AB
* Contact: Kristian Rietveld <kris@imendio.com>
*
* 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>
static void
press_and_hold_show_menu (GtkWidget *widget,
GdkDevice *device)
{
GtkWidget *menu;
GtkWidget *item;
menu = gtk_menu_new ();
item = gtk_menu_item_new_with_label ("Test 1");
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
gtk_widget_show (item);
item = gtk_menu_item_new_with_label ("Test 2");
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
gtk_widget_show (item);
item = gtk_menu_item_new_with_label ("Test 3");
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
gtk_widget_show (item);
gtk_menu_popup_for_device (GTK_MENU (menu), device,
NULL, NULL, NULL, NULL, NULL,
1,
GDK_CURRENT_TIME);
}
static gboolean
press_and_hold (GtkWidget *widget,
GdkDevice *device,
GtkPressAndHoldAction action,
gint x,
gint y,
gpointer user_data)
{
switch (action)
{
case GTK_PRESS_AND_HOLD_QUERY:
g_print ("press-and-hold-query on %s\n", gtk_widget_get_name (widget));
break;
case GTK_PRESS_AND_HOLD_TRIGGER:
g_print ("press-and-hold-trigger on %s\n", gtk_widget_get_name (widget));
press_and_hold_show_menu (widget, device);
break;
case GTK_PRESS_AND_HOLD_CANCEL:
g_print ("press-and-hold-cancel on %s\n", gtk_widget_get_name (widget));
break;
}
return TRUE;
}
static GtkTreeModel *
create_model (void)
{
GtkTreeStore *store;
GtkTreeIter iter;
store = gtk_tree_store_new (1, G_TYPE_STRING);
/* A tree store with some random words ... */
gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
0, "File Manager", -1);
gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
0, "Gossip", -1);
gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
0, "System Settings", -1);
gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
0, "The GIMP", -1);
gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
0, "Terminal", -1);
gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
0, "Word Processor", -1);
return GTK_TREE_MODEL (store);
}
int
main (int argc, char **argv)
{
GtkWidget *window;
GtkWidget *box;
GtkWidget *label, *checkbutton, *tree_view, *entry;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Press and Hold test");
gtk_container_set_border_width (GTK_CONTAINER (window), 10);
g_signal_connect (window, "delete_event",
G_CALLBACK (gtk_main_quit), NULL);
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
gtk_container_add (GTK_CONTAINER (window), box);
label = gtk_button_new_with_label ("Press-n-hold me!");
g_signal_connect (label, "press-and-hold",
G_CALLBACK (press_and_hold), NULL);
gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
label = gtk_button_new_with_label ("No press and hold");
gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
checkbutton = gtk_check_button_new_with_label ("Checkable check button");
g_signal_connect (checkbutton, "press-and-hold",
G_CALLBACK (press_and_hold), NULL);
gtk_box_pack_start (GTK_BOX (box), checkbutton, FALSE, FALSE, 0);
tree_view = gtk_tree_view_new_with_model (create_model ());
gtk_widget_set_size_request (tree_view, 200, 240);
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree_view),
0, "Test",
gtk_cell_renderer_text_new (),
"text", 0,
NULL);
g_signal_connect (tree_view, "press-and-hold",
G_CALLBACK (press_and_hold), NULL);
gtk_box_pack_start (GTK_BOX (box), tree_view, FALSE, FALSE, 0);
entry = gtk_entry_new ();
gtk_entry_set_text (GTK_ENTRY (entry), "Press and hold me");
g_signal_connect (entry, "press-and-hold",
G_CALLBACK (press_and_hold), NULL);
gtk_box_pack_start (GTK_BOX (box), entry, FALSE, FALSE, 0);
gtk_widget_show_all (window);
gtk_main ();
return 0;
}
+17
View File
@@ -53,6 +53,16 @@ content_height_changed (GtkSpinButton *spin_button,
gtk_scrolled_window_set_min_content_height (swindow, (gint)value);
}
static void
kinetic_scrolling_changed (GtkToggleButton *toggle_button,
gpointer data)
{
GtkScrolledWindow *swindow = data;
gboolean enabled = gtk_toggle_button_get_active (toggle_button);
gtk_scrolled_window_set_kinetic_scrolling (swindow, enabled);
}
static void
scrollable_policy (void)
{
@@ -199,6 +209,13 @@ scrollable_policy (void)
g_signal_connect (G_OBJECT (widget), "changed",
G_CALLBACK (label_flip_changed), label);
/* Add Kinetic scrolling control here */
widget = gtk_check_button_new_with_label ("Kinetic scrolling");
gtk_widget_show (widget);
gtk_box_pack_start (GTK_BOX (cntl), widget, TRUE, TRUE, 0);
g_signal_connect (G_OBJECT (widget), "toggled",
G_CALLBACK (kinetic_scrolling_changed), swindow);
gtk_widget_show (window);
}