Multiple inputs (for example, a stylus with a point and eraser)
If we examine the full definition of,
for example, the EventMotion attributes, we see that it has fields to support
extended device information.
type window time x y pressure xtilt ytilt state is_hint source deviceid
pressure gives the pressure as
a floating point number between 0 and 1. xtilt
and ytilt can take on values
between -1 and 1, corresponding to the degree of tilt in each direction.
and deviceid specify the
device for which the event occurred in two different ways. source
gives some simple information about the type of device. It can take the
SOURCE_MOUSE SOURCE_PEN SOURCE_ERASER SOURCE_CURSOR
deviceid specifies a unique numeric ID for the device.
By giving the value EXTENSION_EVENTS_CURSOR (defined in GDK.py) we say that we are interested in extension events, but only if we don't have to draw our own cursor. See the section Further Sophistications below for more information about drawing the cursor. We could also give the values EXTENSION_EVENTS_ALL if we were willing to draw our own cursor, or EXTENSION_EVENTS_NONE to revert back to the default condition.
This is not completely the end of the
story however. By default, no extension devices are enabled. We need a
mechanism to allow users to enable and configure their extension devices.
GTK provides the InputDialog widget to automate this process. The following
procedure manages an InputDialog widget. It creates the dialog if it isn't
present, and raises it to the top otherwise.
92 def input_dialog_destroy(w): 93 global inputd 94 inputd = None 95 96 def create_input_dialog(widget): 97 global inputd 98 99 if not inputd: 100 inputd = gtk.GtkInputDialog() 101 inputd.connect("destroy", input_dialog_destroy) 102 inputd.close_button.connect_object("clicked", inputd.hide, inputd) 103 inputd.save_button.hide() 104 inputd.show() 105 else: 106 if not (inputd.flags() & gtk.MAPPED): 107 inputd.show() 108 else: 109 inputd.get_window()._raise()
(You might want to take note of the way we handle this dialog. By connecting to the "destroy" signal, we make sure that we don't keep a reference to inputd around after it is destroyed - that could lead to a segfault.)
The InputDialog has two buttons "Close" and "Save", which by default have no actions assigned to them. In the above function we make "Close" hide the dialog, and we hide the "Save" button, since we don't implement saving of XInput options in this program.
Once change we do have to make is to call
instead of using the pointer.
and pointer_state attributes.
This is necessary because they don'treturn the extended device information.
x, y, pressure, xtilt, ytilt, mask = window.input_get_pointer(deviceid)
When calling this function, we need to specify the device ID as well as the window. Usually, we'll get the device ID from the deviceid field of an event structure. Again, this function will return reasonable values when extension events are not enabled. (In this case, event.deviceid will have the value CORE_POINTER).
So the basic structure of our button-press
and motion event handlers doesn't change much - we just need to add code
to deal with the extended information.
71 def button_press_event(widget, event): 72 print_button_press(event.deviceid) 73 74 if event.button == 1 and pixmap != None: 75 draw_brush (widget, event.source, event.x, event.y, event.pressure) 76 return gtk.TRUE 77 78 def motion_notify_event(widget, event): 79 if event.is_hint: 80 x,y,pressure,d,d,state = event.window.input_get_pointer(event.deviceid) 81 else: 82 x = event.x 83 y = event.y 84 pressure = event.pressure 85 state = event.state 86 87 if state & GDK.BUTTON1_MASK and pixmap != None: 88 draw_brush(widget, event.source, x, y, pressure) 89 90 return gtk.TRUE
We also need to do something with the
new information. Our new
function draws with a different color for each event.source
and changes the brush size depending on the pressure.
50 # Draw a rectangle on the screen, size depending on pressure, 51 # and color on the type of device 52 def draw_brush(widget, source, x, y, pressure): 53 if source == GDK.SOURCE_MOUSE: 54 gc = widget.get_style().dark_gc[gtk.STATE_NORMAL] 55 elif source == GDK.SOURCE_PEN: 56 gc = widget.get_style().black_gc 57 elif source == GDK.SOURCE_ERASER: 58 gc = widget.get_style().white_gc 59 else: 60 gc = widget.get_style().light_gc[gtk.STATE_NORMAL] 61 62 rect = (x-10*pressure, y-10*pressure, 20*pressure, 20*pressure) 63 gtk.draw_rectangle (pixmap, gc, gtk.TRUE, 64 rect, rect, rect, rect) 65 widget.draw(rect)
67 def print_button_press(deviceid): 68 print "Button press on device '%s'" % deviceid 69 return
That completes the changes to "XInputize" our program; the result is scribblexinput.py.
An application that draws its own cursor needs to do two things: determine if the current device needs a cursor drawn or not, and determine if the current device is in proximity. (If the current device is a drawing tablet, it's a nice touch to make the cursor disappear when the stylus is lifted from the tablet. When the device is touching the stylus, that is called "in proximity.") The first is done by searching the device list, as we did to find out the device name. The second is achieved by selecting "proximity_out" events.