#!/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
“Please do not press this button again”
(with thanks to Douglas Adams’s Hitchhiker’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)