Update drivers: Rename setcolor to defcolor, add real setcolor which ignores color stack.
Application: Add overridable startup() method.
Fix Widget draw clipping.
# -*- coding: utf-8 -*-
"""Event emitter.
There are two sorts of events - those which are emitted by user or system
and others which are emitted by Widgets. We will not distinguish them.
This module implements universal mechanism for event emitting and handling.
"""
import logging
import inspect
class Event:
def __init__(self):
self.event_name = None
self.originator = None
def __getitem__(self, key):
return self.__dict__[key]
def __repr__(self):
return 'Event()'
class DrawEvent(Event):
def __init__(self, driver, x, y):
Event.__init__(self)
self.driver = driver
self.x = x
self.y = y
def __repr__(self):
return 'DrawEvent(x={0.x},y={0.y})'.format(self)
class FocusEvent(Event):
def __init__(self, old=None, new=None):
Event.__init__(self)
#: Former focused widget.
self.old = old
#: Current focused widget.
self.new = new
def __repr__(self):
return 'FocusEvent(old={0.old},new={0.new})'.format(self)
class KeyboardEvent(Event):
MOD_SHIFT = 1<<0
MOD_ALT = 1<<1
MOD_CTRL = 1<<2
MOD_META = 1<<3
def __init__(self, keyname, char, mod=0):
Event.__init__(self)
self.keyname = keyname
self.char = char
self.mod = mod
def __repr__(self):
return 'KeyboardEvent(keyname={0.keyname},char={0.char},mod={0.mod})'.format(self)
class MouseEvent(Event):
def __init__(self, x=0, y=0, button=0):
Event.__init__(self)
self.x = x # global coordinates
self.y = y
self.wx = x # local widget coordinates
self.wy = y
self.px = 0 # parent coordinates
self.py = 0
#: Mouse button: left=1, middle=2, right=3, wheelup=4, wheeldown=5
self.button = button
def make_child_event(self, container, child):
ev = MouseEvent(self.x, self.y, self.button)
ev.originator = self.originator
ev.event_name = self.event_name
# original local coordinates are new parent coordinates
ev.px = self.wx
ev.py = self.wy
# update local coordinates
ev.wx = self.wx - child.x
ev.wy = self.wy - child.y
return ev
def __repr__(self):
return 'MouseEvent(x={0.x},y={0.y},button={0.button})'.format(self)
class GenericEvent(Event):
"""Generic event which carries an object with additional data."""
def __init__(self, data):
self.data = data
class Emitter:
"""Event emitter mixin class."""
def add_events(self, *events):
"""Add events which may be emitted on instances of this class.
This should be called only by subclasses.
It also serves as initializer, other methods of Emitter
will not work if add_events was not called.
events -- Arguments must be given in pairs.
Each pair consists of event_name, event_class:
event_name -- a string used in add_handler(), emit() etc.
event_class -- class of event payload
It will also inspect base classes of instance on which add_events()
is called, adding all methods meeting naming convention to handler list.
The convention for method names is "on_event_name", "after_event_name".
Prefix on/after determines whether the handler is added as first or last.
"""
if len(events) % 2:
raise ValueError('add_events(): Event names and classes must be passed in pairs.')
if not hasattr(self, '_event_handlers'):
self._event_handlers = dict()
self._event_class = dict()
for event_name, event_class in zip(events[::2], events[1::2]):
if not isinstance(event_name, str):
raise TypeError('add_events(): Event name must be a string: %r.' % event_name)
if not issubclass(event_class, Event):
raise TypeError('add_events(): Class %s does not inherit from Event.' % event_class)
self._event_handlers[event_name] = []
self._event_class[event_name] = event_class
self._add_default_handlers(event_name)
def add_handler(self, event_name, handler, last=False):
"""Add handler to event name.
last=False -- Add handler as first in handler list.
last=True -- Add handler to the end of handler list.
"""
if event_name in self._event_handlers:
if last:
self._event_handlers[event_name].append(handler)
else:
self._event_handlers[event_name].insert(0, handler)
else:
raise KeyError('Unknown event: %s', event_name)
def remove_handler(self, event_name, handler):
"""Remove event handler from the list."""
if event_name in self._event_handlers:
self._event_handlers[event_name].remove(handler)
else:
raise KeyError('Unknown event: %s', event_name)
def remove_all_handlers(self, event_name):
"""Remove all handlers for event."""
if event_name in self._event_handlers:
self._event_handlers[event_name][:] = []
else:
raise KeyError('Unknown event: %s', event_name)
def has_handlers(self, event_name):
"""Test if any handlers are attached to event name.
Return True if event handler list is not empty,
False otherwise.
"""
if event_name in self._event_handlers:
return bool(self._event_handlers[event_name])
else:
raise KeyError('Unknown event: %s', event_name)
def emit(self, event_name, *args, **kwargs):
"""Emit the event.
Call all handlers from event's handler list,
starting from last added handler, going to those added
before, ending with those added with last=True.
Stop if any handler returns True.
Return True when one of the handlers returns True,
False otherwise.
Creates new instance of event_class given to
add_events() and passes all arguments after event_name
to its __init__ method.
Unless first of these arguments is Event instance
in which case no object is created and the Event instance
is passed to handlers.
"""
# create event from specified event class, or use first argument
if len(args) == 1 and isinstance(args[0], Event):
event = args[0]
else:
event = self._event_class[event_name](*args, **kwargs)
# set originator to instance on which emit() was called
event.originator = self
event.event_name = event_name
# log
logging.getLogger('tuikit').debug('Emit %r %r on %s %s',
event_name, event,
self.__class__.__name__,
getattr(self, 'name', None) or id(self))
# call handlers from first to last, stop if satisfied
for handler in self._event_handlers[event_name]:
handled = handler(event)
if handled:
return True
return False
def _add_default_handlers(self, event_name):
"""Add default handlers from the instance and its base classes.
Handlers are looked up in methods by their names.
Method name is composited from prefix and event_name.
Prefix is one of "on_" or "after_".
Depending on prefix the handler is added to top or bottom of handler stack.
See _add_default_handler method for more information.
"""
for cls in reversed(inspect.getmro(self.__class__)):
self._add_default_handler(cls, 'on_', event_name, last=False)
self._add_default_handler(cls, 'after_', event_name, last=True)
def _add_default_handler(self, cls, prefix, event_name, last):
"""Add handler from one of base classes to handler list.
cls -- one of self's base classes
prefix -- method name prefix
event_name -- event name
last -- if True, add handler to end of handler list (will be called last)
Look for method of name prefix + event_name in class 'cls',
if exists, add it to handler list.
"""
method_name = prefix + event_name
if method_name in cls.__dict__:
unbound_handler = cls.__dict__[method_name]
if callable(unbound_handler):
handler = unbound_handler.__get__(self, cls)
self.add_handler(event_name, handler, last)