Chapter 5 - Event Handling

Aside from rendering, the most important function of the UI is to handle events. An event can be triggered by any of the following:

  • The mouse pointer moved or a button on the mouse was pressed

  • A key on the keyboard was pressed or released

  • The window needs to be re-rendered

  • A file being watched was changed

  • The user became active or inactive

  • A supported device (like the apple remote control) did something

  • An internal event like a new source or session being created has occurred

Each specific event has a name may also have extra data associated with it in the form of an event object. To see the name of an event (at least for keyboard and mouse pointer events) you can select the Help → Describe… which will let you interactively see the event name as you hit keys or move the mouse. You can also use Help → Describe Key.. to see what a specific key is bound to by pressing it.Table 5.1 shows the basic event type prefixes.

Event Prefix

Description

key-down

Key is being pressed on the keyboard

key-up

Key is being released on the keyboard

pointer

The mouse moved, button was pressed, or the pointer entered (or left) the window

dragdrop

Something was dragged onto the window (file icon, etc)

render

The window needs updating

user

The user’s state changed (active or inactive, etc)

remote

A network event

Table 5.1: Event Prefixes for Basic Device Events

When an event is generated in RV, the application will look for a matching event name in its bindings. The bindings are tables of functions which are assigned to certain event names. The tables form a stack which can be pushed and popped. Once a matching binding is found, RV will execute the function.When receiving an event, all of the relevant information is in the Event object. This object has a number of methods which return information depending on the kind of event.

Method

Events

Description

pointer (Vec2;)

pointer-* dragdrop-*

Returns the location of the pointer relative to the view.

relativePointer (Vec2;)

pointer-* dragdrop-*

Returns the location of the pointer relative to the current widget or view if there is none.

reference (Vec2;)

pointer-* dragdrop-*

Returns the location of initial button mouse down during dragging.

domain (Vec2;)

pointer-* render-* dragdrop-*

Returns the size of the view.

subDomain (Vec2;)

pointer-* render-* dragdrop-*

Returns the size of the current widget if there is one. relativePointer() is positioned in the subDomain().

buttons (int;)

pointer-* dragdrop-*

Returns an int or’d from the symbols: Button1, Button2, and Button3.

modifiers (int;)

pointer-* key-* dragdrop-*

Returns an int or’d from the symbols: None, Shift, Control, Alt, Meta, Super, CapLock, NumLock, ScrollLock.

key (int;)

key-*

Returns the “keysym” value for the key as an int

name (string;)

any

Returns the name of the event

contents (string;)

internal eventsdragdrop-*

Returns the string content of the event if it has any. This is normally the case with internal events like new-source, new-session, etc. Pointer, key, and other device events do not have a contents() and will throw if it’s called on them. Drag and drop events return the data associated with them. Some render events have contents() indicating the type of render occurring.

contentsArray (string[];)

internal events

Same as contents(), but in the case of some internal events ancillary information may be present which can be used to avoid calling additional commands.

sender (string;)

any

Returns the name of the sender

contentType (int;)

dragdrop-*

Returns an int describing the contents() of a drag and drop event. One of: UnknownObject, BadObject, FileObject, URLObject, TextObject.

timeStamp (float;)

any

Returns a float value in seconds indicating when the event occurred

reject (void;)

any

Calling this function will cause the event to be send to the next binding found in the event table stack. Not calling this function stops the propagation of the event.

setReturnContents (void; string)

internal events

Events which have a contents may also have return content. This is used by the remote network events which can have a response.

Table 5.2:Event Object Methods. Python methods have the same names and return the same value types.

5.1 Binding an Event

In Mu (or Python) you can bind an event using any of the bind() functions. The most basic version of bind() takes the name of the event and a function to call when the event occurs as arguments. The function argument (which is called when the event occurs) should take an Event object as an argument and return nothing (void). Here’s a function that prints hello in the console every time the ``j’’ key is pressed:

Note: If this is the first time you’ve seen this syntax, it’s defining a Mu function. The first two characters \: indicate a function definition follows. The name comes next. The arguments and return type are contained in the parenthesis. The first identifier is the return type followed by a semicolon, followed by an argument list.

E.g, : add (int; int a, int b) { return a + b; }

\: my_event_function (void; Event event)
{
    print("Hello!\n");
}

bind("key-down--j", my_event_function); 

or in Python:

 def my_event_function (event):
    print ("Hello!")

bind("default", "global", "key-down--j", my_event_function); 

There are more complicated bind() functions to address binding functions in specific event tables (the Python example above is using the most general of these). Currently RV’s user interface has one default global event table an couple of other tables which implement the parameter edit mode and help modes.Many events provide additional information in the event object. Our example above doesn’t even use the event object, but we can change it to print out the key that was pressed by changing the function like so:

 \: my_event_function (void; Event event)
{
    let c = char(event.key());
    print("Key pressed = %c\n" % c);
} 

or in Python:

 def my_event_function (event):
    c = event.key()
    print ("Key pressed = %s\n" % c) 

In this case, the Event object’s key() function is being called to retrieve the key pressed. To use the return value as a key it must be cast to a char. In Mu, the char type holds a single unicode character. In Python, a string is unicode. See the section on the Event class to find out how to retrieve information from it. At this point we have not talked about where you would bind an event; that will be addressed in the customization sections.

5.2 Keyboard Events

There are two keyboard events: key-down and key-up. Normally the key-down events are bound to functions. The key-up events are necessary only in special cases.The specific form for key down events is key-down– something where something uniquely identifies both the key pressed and any modifiers that were active at the time.So if the ``a’’ key was pressed the event would be called: key-down–a. If the control key were held down while hitting the ``a’’ key the event would be called key-down–control–a.There are five modifiers that may appear in the event name: alt, caplock, control, meta, numlock, scrolllock, and shift in that order. The shift modifier is a bit different than the others. If a key is pressed with the shift modifier down and it would result in a different character being generated, then the shift modifier will not appear in the event and instead the result key will. This may sound complicated but these examples should explain it:For control + shift + A the event name would be key-down–control–A. For the ``*’’ key (shift + 8 on American keyboards) the event would be key-down–*. Notice that the shift modifier does not appear in any of these. However, if you hold down shift and hit enter on most keyboards you will get key-down–shift–enter since there is no character associated with that key sequence.Some keys may have a special name (like enter above). These will typically be spelled out. For example pressing the ``home’’ key on most keyboards will result in the event key-down–home. The only way to make sure you have the correct event name for keys is to start RV and use the Help → Describe… facility to see the true name. Sometimes keyboards will label a key and produce an unexpected event. There will be some keyboards which will not produce an event all for some keys or will produce a unicode character sequence (which you can see via the help mechanism).

5.3 Pointer (Mouse) Events

The mouse (called pointer from here on) can produce events when it is moved, one of its buttons is pressed, an attached scroll wheel is rotated, or the pointer enters or leaves the window.The basic pointer events are move, enter, leave, wheelup, wheeldown, push, drag, and release. All but enter and leave will also indicate any keyboard modifiers that are being pressed along with any buttons on the mouse that are being held down. The buttons are numbered 1 through 5. For example if you hold down the left mouse button and movie the mouse the events generated are:

pointer-1--push
pointer-1--drag
pointer-1--drag
...
pointer-1-release 

Pointer events involving buttons and modifiers always come in there parts: push, drag and release. So for example if you press the left mouse, move the mouse, press the shift key, move the mouse, release everything you get:

pointer-1--push
pointer-1--drag
pointer-1--drag
...
pointer-1-release
pointer-1--shift--push
pointer-1--shift--drag
pointer-1--shift--drag
...
pointer-1--shift--release 

Notice how the first group without the shift is released before starting the second group with the shift even though you never released the mouse button. For any combination of buttons and modifiers, there will be a push-drag-release sequence that is cleanly terminated.It is also possible to hold multiple mouse buttons and modifiers down at the same time. When multiple buttons are held (for example, button 1 and 2) they are simply both included (like the modifiers) so for buttons 1 and 2 the name would be pointer-1-2–push to start the sequence.The mouse wheel behaves more like a button: when the wheel moves you get only a wheelup or wheeldown event indicating which direction the wheel was rotated. The buttons and modifiers will be applied to the event name if they are held down. Usually the motion of the wheel on a mouse will not be smooth and the event will be emitted whenever the wheel ``clicks’’. However, this is completely a function of the hardware so you may need to experiment with any particular mouse.There are three more pointer events that can be generated. When the mouse moves with no modifiers or buttons held down it will generate the event pointer–move. When the pointer enters the view pointer–enter is generated and when it leaves pointer–leave. Something to keep in mind: when the pointer leaves the view and the device is no longer in focus on the RV window, any modifiers or buttons the user presses will not be known to RV and will not generate events. When the pointer returns to the view it may have modifiers that became active when out-of-focus. Since RV cannot know about these modifiers and track them in a consistent manner (at least on X Windows) RV will assume they do not exist.Pointer events have additional information associated with them like the coordinates of the pointer or where a push was made. These will be discussed later.

5.4 The Render Event

The UI will get a render event whenever it needs to be updated. When handling the render event, a GL context is set up and you can call any GL function to draw to the screen. The event supplies additional information about the view so you can set up a projection.At the time the render event occurs, RV has already rendered whatever images need to be displayed. The UI is then called in order to add additional visual objects like an on-screen widget or annotation.Here’s a render function that draws a red polygon in the middle of the view right on top of your image.Listing 5.1:Example Render Function

 \: my_render (void; Event event)
{
    let domain = event.domain(),
        w      = domain.x,
        h      = domain.y,
        margin = 100;

    use gl;
    use glu;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0.0, w, 0, h);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // Big red polygon
    glColor(Color(1,0,0,1));
    glBegin(GL_POLYGON);
    glVertex(margin, margin);
    glVertex(w-margin, margin);
    glVertex(w-margin, h-margin);
    glVertex(margin, h-margin);
    glEnd();
} 

Note that for Python, you will need to use the PyOpenGL module or bind the symbols in the gl Mu module manually in order to draw in the render event.The UI code already has a function called render() bound the render event; so this function basically turns off existing UI rendering.

5.5 Remote Networking Events

RV’s networking generates a number of events indicating the status of the network. In addition, once a connection has been established, the UI may generate sent to remote programs, or remote programs may send events to RV. These are typically uniquely named events which are specific to the application that is generating and receiving them.For example the sync mechanism generates a number of events which are all named remote-sync-something.

5.6 Internal Events

Some events will originate from RV itself. These include things like new-source or new-session which include information about what changed. The most useful of these is new-source which can be used to manage color and other image settings between the time a file is loaded and the time it is first displayed. (See Color Management Section). Other internal events are functional, but are placeholders which will become useful with future features.The current internal events are listed in table 5.3 .

Event

Event.(data/contents)

Ancillary Data (contentsArray)

Description

render

Main view render

pre-render

Before rendering

post-render

After rendering

per-render-event-processing

Qt Event processing between renders (a “safe” time to edit the graph)

layout

Main view layout used to handle view margin changes

new-source

nodename;;RVSource;;filename

DEPRECATED A new source node was added (or media was reset)

source-group-complete

group nodename;;action_type

A new or modified source group is complete

source-modified

nodename;;RVSource;;filename

An existing source was changed

media-relocated

nodename;;oldmedia;;newmedia

A movie, image sequence, audio file was swapped out

source-media-set

nodename;;tag

before-session-read

filename

Session file is about to be read

after-session-read

filename

Session file was read

before-session-write

filename

Session file is about to be written

after-session-write

filename

Session file was just written

before-session-write-copy

filename

A copy of the session is about to be written

after-session-write-copy

filename

A copy of a session was just written

before-session-deletion

The session is about to be deleted

before-graph-view-change

nodename

The current view node is about to change.

after-graph-view-change

nodename

The current view node changed.

new-node

nodename

A new view node was created.

graph-new-node

nodename

nodename protocol version groupname

A new node of any kind was created.

before-progressive-loading

Loading will start

after-progressive-loading

Loading is complete (sent immediately if no files will be loaded)

graph-layer-change

DEPRECATED use after-graph-view-change

frame-changed

The current frame changed

fps-changed

Playback FPS changed

play-start

Playback started

play-stop

Playback stopped

incoming-source-path

infilename;;tag

A file was selected by the user for loading.

missing-image

An image could not be loaded for rendering

cache-mode-changed

buffer or region or off

Caching mode changed

view-size-changed

The viewing area size changed

new-in-point

frame

The in point changed

new-out-point

frame

The out point changed

before-source-delete

nodename

Source node will be deleted

after-source-delete

nodename

Source node was deleted

before-node-delete

nodename

View node will be deleted

after-node-delete

nodename

View node was deleted

after-clear-session

The session was just cleared

after-preferences-write

Preferences file was written by the Preferences GUI

state-initialized

Mu/Python init files read

session-initialized

All modes toggled, command-line processed, etc.

realtime-play-mode

Playback mode changed to realtime

play-all-frames-mode

Playback mode changed to play-all-frames

before-play-start

Play mode will start

mark-frame

frame

Frame was marked

unmark-frame

frame

Frame was unmarked

pixel-block

Event.data()

A block of pixels was received from a remote connection

graph-state-change

A property in the image processing graph changed

graph-node-inputs-changed

nodename

Inputs of a top-level node added/removed/re-ordered

range-changed

The time range changed

narrowed-range-changed

The narrowed time range changed

margins-changed

left right top bottom

View margins changed

view-resized

old-w new-w

old-h new-h

preferences-show

Pref dialog will be shown

preferences-hide

Pref dialog was hidden

read-cdl-complete

cdl_filename;;cdl_nodename

CDL file has been loaded

read-lut-complete

lut_filename;;lut_nodename

LUT file has been loaded

remote-eval

code

Request to evaluate external Mu code

remote-pyeval

code

Request to evaluate external Python code

remote-pyexec

code

Request to execute external Python code

remote-network-start

Remote networking started

remote-network-stop

Remote networking stopped

remote-connection-start

contact-name

A new remote connection has been made

remote-connection-stop

contact-name

A remote connection has died

remote-contact-error

contact-name

A remote connection error occurred while being established

Table 5.3:Internal Events

5.6.1 File Changed Event

It is possible to watch a file from the UI. If the watched file changes in any way (modified, deleted, moved, etc) a file-changed event will be generated. The event object will contain the name of the watched file that changed. A function bound to file-changed might look something like this:

 \: my_file_changed (void; Event event)
{
    let file = event.contents();
    print("%s changed on disk\n" % file);
} 

In order to have a file-changed event generated, you must first have called the command function watchFile().

5.6.2 Incoming Source Path Event

This event is sent when the user has selected a file or sequence to load from the UI or command line. The event contains the name of the file or sequence. A function bound to this event can change the file or sequence that RV actually loads by setting the return contents of the event. For example, you can cause RV to check and see if a single file is part of a larger sequence and if so load the whole sequence like so:

 \: load_whole_sequence (void; Event event)
{
    let file        = event.contents(),
        (seq,frame) = sequenceOfFile(event.contents());

    if (seq != "") event.setReturnContent(seq);
}

bind("incoming-source-path", load_whole_sequence); 

or in Python:

 def load_whole_sequence (event):

    file = event.contents();
    (seq,frame) = rv.commands.sequenceOfFile(event.contents());

    if seq != "":
         event.setReturnContent(seq);


bind("default", "global", "incoming-source-path", load_whole_sequence, "Doc string"); 

5.6.3 Missing Images

Sometimes an image is not available on disk when RV tries to read. This is often the case when looking at an image sequence while a render or composite is ongoing. By default, RV will find a nearby frame to represent the missing frame if possible. The missing-image event will be sent once for each image which was expected but not found. The function bound to this event can render information on on the screen indicating that the original image was missing. The default binding display a message in the feedback area.The missing-image event contains the domain in which rendering can occur (the window width and height) as well as a string of the form ``frame;source’’ which can be obtained by calling the contents() function on the event object.The default binding looks like this:

 \: missingImage (void; Event event)
{
    let contents = event.contents(),
        parts = contents.split(";"),
        media = io.path.basename(sourceMedia(parts[1])._0);

    displayFeedback("MISSING: frame %s of %s"
                     % (parts[0], media), 1, drawXGlyph);
}

bind("missing-image", missingImage);