# Creating a Graphics Event Handler Class

You can create a custom event handler to handle events in a graphics WINDOW or a WIDGET_WINDOW. The event handler class must be a subclass of GraphicsEventAdapter. Your subclass can then override any of the following methods: ::MouseDown, ::MouseUp, ::MouseMotion, ::MouseWheel, ::KeyHandler, and ::SelectChange.

Each function method must return a value of 0 to turn off default event handling or 1 to perform default event handling.

See the WINDOW example and WIDGET_WINDOW example, below.

## Syntax

The syntax of the various event handler methods are similar, as the following list shows.

Result = obj.MouseDown(Window, X, Y, Button, KeyMods, Clicks)

Result = obj.MouseMotion(Window, X, Y, KeyMods)

Result = obj.MouseUp(Window, X, Y, Button)

Result = obj.MouseWheel(Window, X, Y, Delta, KeyMods)

Result = obj.KeyHandler(Window, IsASCII, Character, KeyValue, X, Y, Press, Release, KeyMods)

Result = obj.SelectChange(Window, Graphic, Mode, WasSelected)

## Arguments

### Window

The object reference of the window in which the event occurred.

### X

The x-coordinate location of the mouse cursor (in device coordinates) at the time of the event.

### Y

The y-coordinate of the mouse cursor (in device coordinates) at the time of the event.

### Button

The value of the clicked button. Possible values are:

Value | Mouse Button
--- | ---
1 | Left
2 | Middle
4 | Right

### Clicks

The value indicating how many button clicks occurred. The value is 1 for a single click and 2 for a double click.

### Delta

The value indicating the direction and number of movements of the mouse wheel.

Pushing the wheel generates positive values, pulling the wheel generates negative values. The magnitude of the value depends on the device setting for the individual mouse, but is usually limited to small integer values such as +1, -1, +2, -2, etc.

### KeyMods

The value containing a bitwise mask indicating which modifier keys are active at the time the mouse event happens. Possible values are:

Value | Modifier Key
1 | Shift
2 | Control
4 | Caps lock
8 | Alt

### IsASCII

A scalar byte value that indicates whether the keyboard event corresponds to an ASCII character. If IsASCII is non-zero, the Character argument will be set to a byte value corresponding to the character of the pressed key. If IsASCII is zero, the KeySymbol argument will be set to a numeric value indicating the key that was pressed.

### Character

If IsASCII is non-zero, this argument is set to a byte value corresponding to the ASCII character of the key that was pressed. Otherwise, this argument is set to zero.

### KeyValue

If IsASCII is zero, this argument is set to a value that indicates the key that was pressed. Otherwise, this argument is set to zero. Possible values are:

Value | Key
--- | ---
1 | Shift
2 | Control
3 | Caps lock
4 | Alt
5 | Left
6 | Right
7 | Up
8 | Down
9 | Page up
10 | Page down
11 | Home
12 | End

Note: On East Asian (Chinese, Japanese, Korean) localized Windows operating systems with an Asian language pack installed, characters entered in the Windows Input Method Manager (IMM)composition window are returned in the the KeyValue argument as unsigned integers representing a Wide character (Unicode value). The I18N_WIDECHARTOMULTIBYTE routine can convert these characters to multibyte strings. For more information, see Internationalizing Code.

### Press

The value indicating that this event represents a key press. This argument is non-zero if the event is the result of pressing the key.

### Release

The value indicating that this event represents a key release. This argument is non-zero if the event is the result of releasing the key.

### Graphic

The graphic to be selected or unselected.

### Mode

A value representing the mode that was used for the current selection. Possible values are:

Value | Selection Type
0 | Unselect
1 | Select
2 | Toggled selection
3 | Additive

### WasSelected

A value indicating if the graphic item was already selected prior to this event. A value of 1 indicates an item was previously selected; 0 if not selected.

## GraphicsEventAdapter Class

The GraphicsEventAdapter class provides a default implementation for all of the event handler methods. The default implementation simply returns a value of 1 for each of the methods. For convenience, you should create your event handler as a subclass of GraphicsEventAdapter and only override the methods that you need.

The GraphicsEventAdapter class is written in the IDL language. Its source code can be found in the file `graphicseventadapter__define.pro` in the `lib/graphics` subdirectory of the IDL distribution.

## Example Using WINDOW

In the following example code, the RBBox class create a "rubber-band" box. The `RBBox::MouseDown` method creates a polygon and caches the start point of a drag operation. In the `RBBox::MouseMotion` method the polygon is updated with the current location of the cursor. In the `RBBox::MouseUp` method, the polygon is again updated and the color of the polygon is modified. Copy this code into a new IDL file and run it. To test the code, drag and drop the mouse to draw a box in the graphic window.

FUNCTION RBBox::MouseDown, oWin, $

x, y, iButton, KeyMods, nClicks

if (~ISA(self.poly)) then begin

; Add a hidden polygon for the rubber-band box.

self.poly = POLYGON([0,0,0],[0,0,0], /DEVICE, $

LINESTYLE='--', /HIDE, $

FILL_TRANSPARENCY=90, FILL_BACKGROUND = 1, FILL_COLOR='red')

endif

self.x0 = x

self.y0 = y

self.buttonDown = 1

self.poly.HIDE = 0

self.poly.SetData, [0,0,0], [0,0,0]

self.poly.LINESTYLE='--'

RETURN, 0 ; Skip default event handling

END

FUNCTION RBBox::MouseMotion, oWin, x, y, KeyMods

IF self.buttonDown then begin

x0 = self.x0

y0 = self.y0

xVector=[x0,x0,x,x,x0]

yVector=[y0,y,y,y0,y0]

xy = self.poly.ConvertCoord(xVector, yVector, /DEVICE, /TO_NORMAL)

self.poly.SetData, REFORM(xy[0,*]), REFORM(xy[1,*])

ENDIF

RETURN, 0 ; Skip default event handling

END

FUNCTION RBBox::MouseUp, oWin, x, y, iButton

IF (~self.buttonDown) THEN RETURN, 0

x0 = self.x0

y0 = self.y0

xVector=[x0,x0,x,x,x0]

yVector=[y0,y,y,y0,y0]

xy = self.poly.ConvertCoord(xVector, yVector, /DEVICE, /TO_NORMAL)

self.poly.SetData, REFORM(xy[0,*]), REFORM(xy[1,*])

self.poly.LINESTYLE='-'

self.buttonDown=0

; Clear the current selections

oSelect = oWin.GetSelect()

FOREACH oVis, oSelect do oVis.Select, /UNSELECT

; Do a hit test and select new items.

oVisList = oWin.HitTest(x0+(x-x0)/2, y0+(y-y0)/2, $

DIMENSIONS=ABS([x-x0, y-y0]) > 10)

FOREACH vis, oVisList do begin

if vis ne self.poly then vis.Select, /ADD

ENDFOREACH

RETURN, 0 ; Skip default event handling

END

PRO RBBox__define

; We must subclass from GraphicsEventAdapter.

void = {RBBox, inherits GraphicsEventAdapter, $

X0: 0, Y0:0, BUTTONDOWN:0, POLY:OBJ_NEW()}

END

PRO RBBoxEventsTest

x = Findgen(200)

y = Sin(x*2*!PI/25.0)*Exp(-0.01*x)

p = PLOT(x, y, TITLE='Click, hold mouse down and drag, release to draw box')

p.window.EVENT_HANDLER=Obj_New('RBBox')

END

## Example Using WIDGET_WINDOW

In this example, only the MouseMotion event method is used. The event coordinates (in device coordinates) are converted to data coordinates and displayed in a label widget below the draw widget. A crosshair is also drawn at the nearest plot data point. Save all of the code in a file, `exwidgetwindowevents.pro`, and then run the `ExWidgetWindowEvents` procedure.

FUNCTION ExWidWin::Init, plot, label, crosshair

self.plot = plot

self.label = label

self.crosshair = crosshair

return, 1

END

FUNCTION ExWidWin::MouseMotion, oWin, x, y, KeyMods

; Convert from screen coordinates to data coordinates.

xy = self.plot.ConvertCoord(x, y, /DEVICE, /TO_DATA)

; "Snap" the location to the nearest plot point.

xy = self.plot.GetValueAtLocation(xy[0])

; Update the crosshair location and the label

self.crosshair.LOCATION = xy

probe = STRING(xy[0:1], FORMAT='(2F9.2)')

WIDGET_CONTROL, self.label, SET_VALUE=probe

return, 1 ; Perform default event handling

END

PRO ExWidWin__define

void = {ExWidWin, inherits GraphicsEventAdapter, $

plot: OBJ_NEW(), label: 0L, crosshair: OBJ_NEW()}

END

PRO ExWidgetWindowEvents_event, event

; Be sure to process the internal window event first.

; This handles selection, translation, rotation, etc.

w = WIDGET_EVENT(/NOWAIT)

CASE TAG_NAMES(event, /STRUCTURE_NAME) of

'WIDGET_BASE': BEGIN

; Handle base resize events. Retrieve our cached padding,

; and our new size.

WIDGET_CONTROL, event.id, GET_UVALUE=pad, TLB_GET_SIZE=newSize

wDraw = WIDGET_INFO(event.top, FIND_BY_UNAME='ex_widwin_window')

; Change the draw widget to match the new size, minus padding.

xy = newSize - pad

WIDGET_CONTROL, wDraw, $

DRAW_XSIZE=xy[0], DRAW_YSIZE=xy[1], $

SCR_XSIZE=xy[0], SCR_YSIZE=xy[1]

END

ELSE: ; do nothing

ENDCASE

END

PRO ExWidgetWindowEvents

; Create the widgets (start unmapped)

wBase = WIDGET_BASE(/COLUMN, /TLB_SIZE_EVENTS, MAP=0)

wDraw = WIDGET_WINDOW(wBase, $

UNAME='ex_widwin_window', $

X_SCROLL_SIZE=640, Y_SCROLL_SIZE=512)

w1 = WIDGET_LABEL(wBase, /ALIGN_LEFT, /DYNAMIC)

WIDGET_CONTROL, wBase, /REALIZE

; Cache the padding between the base and the draw

WIDGET_CONTROL, wBase, TLB_GET_SIZE=basesize

padding = basesize - [640, 512]

WIDGET_CONTROL, wBase, SET_UVALUE=padding

; Retrieve the newly-created Window object.

WIDGET_CONTROL, wDraw, GET_VALUE=win

; Make sure this is the current window

win.Select

p = PLOT(/TEST, /CURRENT)

; Change crosshair to "manual" mode and set some properties.

c = p.CROSSHAIR

c.STYLE = 'Manual'

c.COLOR = 'Red'

c.LINESTYLE = '-'

; Cache the graphics references for the event handlers

handler = OBJ_NEW('ExWidWin', p, w1, c)

win.EVENT_HANDLER = handler

; Draw the widgets and start the event processing

WIDGET_CONTROL, wBase, /MAP

XMANAGER, 'ExWidgetWindowEvents', wBase, /NO_BLOCK

END