Source code for campy.graphics.gevents

#!/usr/bin/env python3 -tt
"""
This file defines the event types used in the Stanford Portable Graphics Libraries.
The structure of this package is adapted from the Java event model.

EventClassType
==============
This _enumeration definest the vent classes. The element values are each a single bit
and can be added or ORed together to generate an event mask. The CLICK_EVENT class
responds only to the MOUSE_CLICKED event type. The ANY_EVENT class selects any event.

    - NULL_EVENT
    - ACTION_EVENT
    - KEY_EVENT
    - TIMER_EVENT
    - WINDOW_EVENT
    - MOUSE_EVENT
    - CLICK_EVENT
    - ANY_EVENT

EventType
=========
This _enumeration type defines the event types for all events.

    - WINDOW_CLOSED
    - WINDOW_RESIZED
    - ACTION_PERFORMED
    - MOUSE_CLICKED
    - MOUSE_PRESSED
    - MOUSE_RELEASED
    - MOUSE_MOVED
    - MOUSE_DRAGGED
    - KEY_PRESSED
    - KEY_RELEASED
    - KEY_TYPED
    - TIMER_TICKED


ModifierCodes
=============
This _enumeration type defines a set of constants used to check whether
modifiers are in effect.

    - SHIFT_DOWN
    - CTRL_DOWN
    - META_DOWN
    - ALT_DOWN
    - ALT_GRAPH_DOWN
    - BUTTON1_DOWN
    - BUTTON2_DOWN
    - BUTTON3_DOWN

KeyCodes
========
This type defines the names of the key codes returned in a key event

    - BACKSPACE_KEY
    - TAB_KEY
    - ENTER_KEY
    - CLEAR_KEY
    - ESCAPE_KEY
    - PAGE_UP_KEY
    - PAGE_DOWN_KEY
    - END_KEY
    - HOME_KEY
    - LEFT_ARROW_KEY
    - UP_ARROW_KEY
    - RIGHT_ARROW_KEY
    - DOWN_ARROW_KEY
    - F1_KEY
    - F2_KEY
    - F3_KEY
    - F4_KEY
    - F5_KEY
    - F6_KEY
    - F7_KEY
    - F8_KEY
    - F9_KEY
    - F10_KEY
    - F11_KEY
    - F12_KEY
    - DELETE_KEY
    - HELP_KEY

TODO:
    Reconsider using EventClassType. Instead use inheritance?
    Deduplicate events that capture a GWindow.
    Check for validity on property access.
"""
import enum as _enum


[docs]@_enum.unique class EventClassType(_enum.Enum): NULL_EVENT = 0x000 ACTION_EVENT = 0x010 KEY_EVENT = 0x020 TIMER_EVENT = 0x040 WINDOW_EVENT = 0x080 MOUSE_EVENT = 0x100 CLICK_EVENT = 0x200 ANY_EVENT = 0x3F0
[docs]@_enum.unique class EventType(_enum.Enum): WINDOW_CLOSED = EventClassType.WINDOW_EVENT.value + 1 WINDOW_RESIZED = EventClassType.WINDOW_EVENT.value + 2 CONSOLE_CLOSED = EventClassType.WINDOW_EVENT.value + 3 ACTION_PERFORMED = EventClassType.ACTION_EVENT.value + 1 MOUSE_CLICKED = EventClassType.MOUSE_EVENT.value + 1 MOUSE_PRESSED = EventClassType.MOUSE_EVENT.value + 2 MOUSE_RELEASED = EventClassType.MOUSE_EVENT.value + 3 MOUSE_MOVED = EventClassType.MOUSE_EVENT.value + 4 MOUSE_DRAGGED = EventClassType.MOUSE_EVENT.value + 5 KEY_PRESSED = EventClassType.KEY_EVENT.value + 1 KEY_RELEASED = EventClassType.KEY_EVENT.value + 2 KEY_TYPED = EventClassType.KEY_EVENT.value + 3 TIMER_TICKED = EventClassType.TIMER_EVENT.value + 1
[docs]@_enum.unique class ModifierCodes(_enum.Enum): SHIFT_DOWN = 1 << 0 CTRL_DOWN = 1 << 1 META_DOWN = 1 << 2 ALT_DOWN = 1 << 3 ALT_GRAPH_DOWN = 1 << 4 BUTTON1_DOWN = 1 << 5 BUTTON2_DOWN = 1 << 6 BUTTON3_DOWN = 1 << 7
[docs]@_enum.unique class KeyCodes(_enum.Enum): BACKSPACE_KEY = 8 TAB_KEY = 9 ENTER_KEY = 10 CLEAR_KEY = 12 ESCAPE_KEY = 27 PAGE_UP_KEY = 33 PAGE_DOWN_KEY = 34 END_KEY = 35 HOME_KEY = 36 LEFT_ARROW_KEY = 37 UP_ARROW_KEY = 38 RIGHT_ARROW_KEY = 39 DOWN_ARROW_KEY = 40 F1_KEY = 112 F2_KEY = 113 F3_KEY = 114 F4_KEY = 115 F5_KEY = 116 F6_KEY = 117 F7_KEY = 118 F8_KEY = 119 F9_KEY = 120 F10_KEY = 121 F11_KEY = 122 F12_KEY = 123 DELETE_KEY = 127 HELP_KEY = 156
[docs]class GEvent: """ This class is the root of the hierarchy for all events. The standard paradigm for using GEvent is illustrated by the following program, which allows the user to draw lines on the graphics window:: gw = _gwindow.GWindow() print("This program lets the user draw lines by dragging.") while(True): e = gevents.waitForEvent(gevents.EventClassType.MOUSE_EVENT) if(e.getEventType() == gevents.EventType.MOUSE_PRESSED): line = gobjects.GLine(e.getX(), e.getY(), e.getX(), e.getY()); gw.add(line); elif (e.getEventType() == gevents.EventType.MOUSE_DRAGGED) line.setEndPoint(e.getrX(), e.getY()); Attributes: event_class [EventClassType]: Enumerated type constant indicating the class of the event. event_type [EventType]: Enumerated type constant corresponding to the specific event type. time [float]: System time in milliseconds at which the event occurred. valid [bool]: Whether this event represents a fully-initialized valid event. modifiers [int]: Integer whose bits indicate what modifiers are in effect. Notes: To ensure portability among systems that represent time in different ways, this library uses a float to represent time, which is always encoded as the number of milliseconds that have elapsed since 00:00:00 UTC on January 1, 1970, which is the conventional zero point for computer-based time systems. To check whether the shift key is down, for example, one could use the following code:: if e.modifiers & SHIFT_DOWN: ... """ def __init__(self): ''' Ensures that an event is properly initialized to a NULL event. @rtype: void ''' self._event_class = EventClassType.NULL_EVENT self._event_type = None self._valid = False self._time = None self._modifiers = 0 # "Enforce" read-only properties. # TODO(sredmond): This is somewhat gross and ill-advised. @property def event_class(self): return self._event_class @property def event_type(self): return self._event_type @property def valid(self): return self._valid @property def time(self): return self._time @property def modifiers(self): return self._modifiers def __str__(self): return "GEvent(NULL)";
[docs]class GWindowEvent(GEvent): ''' This event subclass represents a window event. Each GWindowEvent keeps track of the event type (WINDOW_CLOSED, WINDOW_RESIZED) along with the identity of the window. Attributes: gwindow [GWindow]: Reference to the GWindow in which this event took place ''' def __init__(self, event_type=None, gwindow=None): ''' Creates a GWindowEvent using the specified parameters @type type: EventType @param type: type of event @type gw: GWindow @param gw: GWindow event took place in ''' super().__init__() if event_type != None and gwindow != None: self._event_class = EventClassType.WINDOW_EVENT self._event_type = event_type self._valid = True self._gwindow = gwindow @property def gwindow(self): # TODO(sredmond): Do we have to recreate a new GWindow with the existing data? return self._gwindow # def getGWindow(self): # ''' # Returns the graphics window in which this event occurred. # @rtype: L{GWindow} # ''' # import campy.graphics.gwindow as _gwindow # return _gwindow.GWindow(gwd = self.gwd) def __str__(self): if not self.valid: return "GWindowEvent(?)" return "GWindowEvent({})".format(self.event_type.name)
[docs]class GActionEvent(GEvent): ''' This event subclass represents an action event. Action events are generated by the classes in the L{GInteractor} hierarchy. As an example, the following program displays a button that, when pushed, generates the message &ldquo;Please do not press this button again&rdquo; (with thanks to Douglas Adams&rsquo;s Hitchhiker&rsquo;s Guide to the Galaxy):: gw = _gwindow.GWindow button = ginteractors.GButton("RED"); gw.addToRegion(button, "SOUTH"); while(True): e = gevents.waitForEvent(ACTION_EVENT | CLICK_EVENT); if(e.getEventType() == MOUSE_CLICKED): break; print("Please do not press this button again.") Attributes: source [GObject]: GInteractor from which event originated. action_command [string]: GInteractor command. ''' def __init__(self, event_type=None, source=None, action_command=None): ''' Creates a GActionEvent using the specified parameters. @type type: EventType @param type: type of event @type source: GObject @param source: interactor event originated from @type actionCommand: string @param actionCommand: interactor command @rtype: void ''' super().__init__() if event_type != None and source != None and action_command != None: self._event_class = EventClassType.ACTION_EVENT self._event_type = event_type self._valid = True self._source = source self._action_command = action_command @property def source(self): return self._source @property def action_command(self): return self._action_command def __str__(self): if not valid: return "GActionEvent(?)" return "GActionEvent({}, {})".format(self.event_type.name, self.action_command)
[docs]class GMouseEvent(GEvent): ''' This event subclass represents a mouse event. Each mouse event records the event type (MOUSE_PRESSED, MOUSE_RELEASED, MOUSE_CLICKED, MOUSE_MOVED, MOUSE_DRAGGED) along with the coordinates of the event. Clicking the mouse generates three events in the following order: MOUSE_PRESSED, MOUSE_RELEASED, MOUSE_CLICKED. As an example, the following program uses mouse events to let the user draw rectangles on the graphics window. The only complexity in this code is the use of the library functions min and abs to ensure that the dimensions of the rectangle are positive:: gw = _gwindow.GWindow print("This program lets the user draw rectangles.") while(True): e = gevents.waitForEvent(); if (e.getEventType() == MOUSE_PRESSED): startX = e.getX(); startY = e.getY(); rect = gobjects.GRect(startX, startY, 0, 0); rect.setFilled(True); gw.add(rect); elif(e.getEventType() == MOUSE_DRAGGED): x = min(e.getX(), startX); y = min(e.getY(), startY); width = abs(e.getX() - startX); height = abs(e.getY() - startY); rect.setBounds(x, y, width, height); Attributes: gwindow [GWindow]: GWindow in which MouseEvent occurred. x [float]: The x-coordinate at which the event occurred. y [float]: The y-coordinate at which the event occurred. Notes: The x- and y-coordinates are given relative to the window origin at the upper left corner of the window. ''' def __init__(self, event_type=None, gwindow=None, x=None, y=None): ''' Creates a GMouseEvent using the specified parameters. @type type: EventType @param type: type of event @type gw: GWindow @param gw: window event took place in @type x: float @param x: x location of event @type y: float @param y: y location of event @rtype: void ''' super().__init__() if type != None and gwindow != None and x != None and y != None: self._event_class = EventClassType.MOUSE_EVENT self._event_type = event_type self._valid = True self._gwindow = gwindow self._x = x self._y = y @property def gwindow(self): # TODO(sredmond): Do we have to recreate a new GWindow with the existing data? return self._gwindow @property def x(self): return self._x @property def y(self): return self._y def __str__(self): if not valid: return "GMouseEvent(?)" return "GMouseEvent({}, x={}, y={})".format(self.event_type.name, self.x, self.y)
[docs]class GKeyEvent(GEvent): ''' This event subclass represents a key event. Each key event records the event type along with two representations of the key. The getKeyChar function is more generally useful and returns the character after taking into account modifier keys. The getKeyCode function returns an integer identifying the key, which can be a function key as well as a standard key. The codes return by getKeyCode are listed in the KeyCodes _enumeration. Attributes: gwindow [GWindow]: GWindow in which MouseEvent occurred. key_char [int]: Value of key. key_code [int]: Code of key. Notes: Returns the character represented by the keystroke, taking the modifier keys into account. For example, if the user types the 'a' key with the shift key down, getKeyChar will return 'A'. If the key code in the event does not correspond to a character, getKeyChar returns the null character. ''' def __init__(self, event_type=None, gwindow=None, key_char=None, key_code=None): ''' Creates a GKeyEvent using the specified parameters. @type type: EventType @param type: type of event @type gw: GWindow @param gw: window event takes place in @type keyChar: int @param keyChar: value of key @type keyCode: KeyCodes @param keyCode: code of key @rtype: void ''' super().__init__() if event_type != None and gwindow != None and key_char != None and key_code != None: self._event_class = EventClassType.KEY_EVENT self._event_type = event_type self._valid = True self._gwindow = gwindow self._key_char = key_char self._key_code = key_code @property def gwindow(self): # TODO(sredmond): Do we have to recreate a new GWindow with the existing data? return self._gwindow @property def key_char(self): return self._key_char @property def key_code(self): return self._key_code def __str__(self): if not valid: return "GKeyEvent(?)" # TODO(sredmond): The C++ libs use key_char for KEY_TYPED for some reason. # Investigate further. return "GKeyEvent({}, {})".format(self.event_type.name, chr(self.key_code))
[docs]class GTimerEvent(GEvent): ''' This event subclass represents a timer event. Timer events are generated by a GTimer object, which produces a new event at a fixed interval measured in milliseconds. As an example, the following program generates a timer event every two seconds, stopping when the user clicks somewhere in the window:: print("This program generates timer events.") timer = gtimer.GTimer(2000) timer.start() while(True): e = gevents.waitForEvent(CLICK_EVENT | TIMER_EVENT); if (e.getEventType() == MOUSE_CLICKED): break; print("Timer ticked") Attributes: timer [GTimer]: GTimer from which this event originated. ''' def __init__(self, event_type=None, timer=None): ''' Creates a GTimerEvent for the specified timer. @type type: EventType @param type: the event type @type timer: GTimer @param timer: timer associated with event @rtype: void ''' super().__init__() if event_type != None and timer != None: self._event_class = EventClassType.TIMER_EVENT self._event_type = event_type self._valid = True self._timer = timer @property def timer(self): return self._timer def __str__(): ''' Converts the event to a human-readable representation of the event. @rtype: string ''' if not valid: return "GTimerEvent(?)" return "GTimerEvent({})".format(self.event_type.name)
[docs]def wait_for_click(): ''' Waits for a mouse click in any window, discarding any other events. @rtype: void ''' wait_for_event(EventClassType.CLICK_EVENT)
[docs]def wait_for_event(mask=EventClassType.ANY_EVENT): ''' Dismisses the process until an event occurs whose type is covered by the event mask. The mask parameter is a combination of the events of interest. For example, to wait for a mouse event or an action event, clients can use the following call: e = waitForEvent(MOUSE_EVENT + ACTION_EVENT); The mask parameter is optional. If it is missing, waitForEvent accepts any event. As a more sophisticated example, the following code is the canonical event loop for an animated application that needs to respond to mouse, key, and timer events:: timer = gtimer.GTimer(ANIMATION_DELAY_IN_MILLISECONDS); timer.start() while(True): e = gevents.waitForEvent(TIMER_EVENT + MOUSE_EVENT + KEY_EVENT) if(e.getEventClass == gevents.EventClassType.TIMER_EVENT): takeAnimationStep() elif(e.getEventClass == gevents.EventClassType.MOUSE_EVENT): handleMouseEvent(e) elif(e.getEventClass == gevents.EventClassType.KEY_EVENT): handleKeyEvent(e) @type mask: EventClassType @param mask: EventClassType to wait for, defaults to ANY_EVENT @rtype: GEvent ''' import campy.private.platform as _platform return _platform.Platform().waitForEvent(mask)
[docs]def get_next_event(mask=EventClassType.ANY_EVENT): ''' Checks to see if there are any events of the desired type waiting on the event queue. If so, this function returns the event in exactly the same fashion as waitForEvent; if not, getNextEvent returns an invalid event. The mask parameter is optional. If it is missing, getNextEvent accepts any event. @type mask: EventClassType @param mask: EventClasstype to check for, defaults to ANY_EVENT @rtype: GEvent ''' import campy.private.platform as _platform return _platform.Platform().getNextEvent(mask)