Mercurial > enso_core
changeset 2:e6944c6abe1e
Added a few files, none of which I've even attempted to import.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Thu, 21 Feb 2008 21:33:05 -0600 |
parents | 2c79ac766f60 |
children | 071c48f03b5e |
files | enso/__init__.py enso/config.py enso/ui/__init__.py enso/ui/events.py |
diffstat | 4 files changed, 508 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/enso/__init__.py Thu Feb 21 18:54:11 2008 -0600 +++ b/enso/__init__.py Thu Feb 21 21:33:05 2008 -0600 @@ -26,5 +26,15 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ---------------------------------------------------------------------------- +# +# enso +# +# ---------------------------------------------------------------------------- + def run(): + """ + Initializes and runs Enso. + """ + raise NotImplementedError()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/enso/config.py Thu Feb 21 21:33:05 2008 -0600 @@ -0,0 +1,7 @@ +# Configuration settings for Enso. Eventually this will take +# localization into account too (or we can make a separate module for +# such strings). + +QUASIMODE_KEYCODE = 1 + +OPENING_MSG_XML = "<p>Welcome to Enso.</p>"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/enso/ui/__init__.py Thu Feb 21 21:33:05 2008 -0600 @@ -0,0 +1,158 @@ +# Copyright (c) 2008, Humanized, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the <organization> nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Humanized, Inc. ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Humanized, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# ---------------------------------------------------------------------------- +# +# enso.ui +# +# ---------------------------------------------------------------------------- + +""" + The UserInterface module. + + This module encapsulates all parts of the Enso user interface, + including (but not limited to) the quasimode. +""" + +# ---------------------------------------------------------------------------- +# Imports +# ---------------------------------------------------------------------------- + +from enso.ui import events +from enso.ui import messages +from enso.ui import quasimode +from enso.ui import commands +import enso.config + + +# ---------------------------------------------------------------------------- +# Functions +# ---------------------------------------------------------------------------- + +_isUserInterfaceInited = False + +def _onEnterEventLoop(): + """ + Executed as soon as we enter the main event loop; at this point, + we know that the event manager has intialized and is running + properly. This means that we can now start any threads and perform + any actions that require the event manager to be running. + """ + + msgXml = enso.config.OPENING_MSG_XML + + if msgXml != "None": + openingMsg = messages.Message( isPrimary = True, + isMini = False, + fullXml = msgXml ) + messages.getMessageManager().newMessage( openingMsg ) + + +def _onExitEventLoop(): + """ + Executed as soon as we leave the main event loop. + """ + + pass + + +def run(): + """ + Runs the user interface and does not return until the user + interface crashes or finishes properly. + """ + + # UI threads must be started upon the event manager's + # initialization to ensure that the event manager is already + # initialized and running when the other UI threads start. + events.eventManager.registerResponder( + _onEnterEventLoop, + "init" + ) + + try: + events.eventManager.run() + finally: + _onExitEventLoop() + + +def init(): + """ + Initializes the user interface module. Do not call any functions + in this module until this function has been called. + """ + + global _isUserInterfaceInited + + # Events must be initialized first, because other singletons may + # need to register responders. + events.init( enso.config.QUASIMODE_KEYCODE ) + + commands.init() + messages.init() + quasimode.init() + + _isUserInterfaceInited = True + + +def isInitialized(): + """ + Returns whether or not this module is initialized. + """ + + return _isUserInterfaceInited + + +def stop(): + """ + Stops the user interface. This can be called multiple times, and + is also thread-safe. + """ + + events.eventManager.stop() + + +def shutdown(): + """ + Shuts down the user interface module. Call this once you are done + using any functions in this module. + """ + + assert isInitialized(), ".shutdown() called before .init() in "\ + "UserInterface!" + + # Events must be shutdown first, because it may contain lingering + # references to singletons that registered event responders. + events.shutdown() + + quasimode.shutdown() + messages.shutdown() + commands.shutdown() + + global _isUserInterfaceInited + _isUserInterfaceInited = False
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/enso/ui/events.py Thu Feb 21 21:33:05 2008 -0600 @@ -0,0 +1,333 @@ +# Copyright (c) 2008, Humanized, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the <organization> nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Humanized, Inc. ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Humanized, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# ---------------------------------------------------------------------------- +# +# enso.ui.events +# +# ---------------------------------------------------------------------------- + +""" + The central event clearinghouse. + + Wraps the InputManager to provide event handling for the user + interface. User interface code, and indeed any client Python + code, can register event responders of several types (listed + below). These responders can be added and removed in real time, + allowing the user interface to respond to events differently based + on system state; for example, a timer event handler that draws the + quasimode might be removed when not in the quasimode, to reduce + overhead when timer events occur and improve system performance. + + The event manager implemented here does not implement a main event + loop; that is implemented the InputManager. Calling the + run method of the event manager enters a main event loop that + calls the various on<event>() methods when events occur. These + methods in turn call any responder functions registered for the + appropriate type of event. +""" + +# ---------------------------------------------------------------------------- +# Imports +# ---------------------------------------------------------------------------- + +import logging +import Input + + +# ---------------------------------------------------------------------------- +# Constants +# ---------------------------------------------------------------------------- + +# A list of all possible types of events that event responders can be +# registered for. +EVENT_TYPES = [ + "key", + "timer", + # LONGTERM TODO: Is "click" ever used? Doesn't seem to be... + "click", + "dismissal", + "traymenu", + "idle", + "init", + "mousemove", + "somekey" + ] + +# Enso will consider the system idle after the following number of seconds. +IDLE_TIMEOUT = 60*5 + + +# ---------------------------------------------------------------------------- +# Enso's Main Application Loop Class +# ---------------------------------------------------------------------------- + +class _TheEventManager( Input.InputManager ): + """ + This class is the event-handling singleton, inheriting from the + input manager class. It creates a dictionary of event responders, + and overrides the input manager's on<eventtype>() methods to call + every registered responder for <eventtype>. + """ + + def __init__( self, quasimodeKeycode ): + """ + Initializes the event manager, creates an internal dictionary + of responders. + """ + + Input.InputManager.__init__( self, quasimodeKeycode ) + + # Copy the core event types to the dynamic event types list, + # which can be extended with the createEventType() method. + self._dynamicEventTypes = EVENT_TYPES[:] + + self.__responders = {} + for evt in self._dynamicEventTypes: + self.__responders[evt] = [] + + + self.__currIdleTime = 0 + + def createEventType( self, typeName ): + """ + Creates a new event type to be responded to. + + Implemented to allow for 'startQuasimode' and 'endQuasimode' + event types to be registered; it seems to be the logical way + for all event types to be dealt with. + """ + + assert typeName not in self._dynamicEventTypes + self.__responders[typeName] = [] + self._dynamicEventTypes.append( typeName ) + + def triggerEvent( self, eventType, *args, **kwargs ): + """ + Used to (artificially or really) trigger an event type. + """ + + assert eventType in self._dynamicEventTypes + for func in self.__responders[ eventType ]: + func( *args, **kwargs ) + + + def getResponders( self, eventType ): + """ + Returns a list of all responders of the given type. + """ + + assert eventType in self._dynamicEventTypes + return self.__responders[eventType] + + + def registerResponder( self, responderFunc, eventType ): + """ + Registers a responder for event type eventType. + """ + + assert eventType in self._dynamicEventTypes + assert responderFunc not in self.getResponders( eventType ) + + responderList = self.__responders[ eventType ] + logging.debug( "Added a responder function!" ) + + # If this is a dismissal responder and we don't currently have + # any registered, enable mouse events so we're actually + # notified of dismissals via mouse input. + if eventType in ["dismissal","mousemove"]: + self.enableMouseEvents( True ) + + responderList.append( responderFunc ) + + + def removeResponder( self, responderFunc ): + """ + Removes responderFunc from the internal responder dictionary. + + NOTE: Removes responderFunc from responding to ALL types of events. + """ + + for eventType in self.__responders.keys(): + responderList = self.__responders[ eventType ] + if responderFunc in responderList: + logging.debug( "Removed a responder function!" ) + responderList.remove( responderFunc ) + + if eventType in ["dismissal","mousemove"]: + # If we're removing our only dismissal responder, + # disable mouse events since we only need to know + # about them for the purposes of dismissal events. + numMouseResponders = len( self.__responders[ "mousemove" ] ) + numDismissResponders = len( self.__responders[ "dismissal" ] ) + if (numMouseResponders+numDismissResponders) == 0: + self.enableMouseEvents( False ) + + + def run( self ): + """ + Runs Enso's main event loop. + + NOTE: This call enters a C++ "while" loop that does not stop + until some code (inside of an event responder) calls the + stop() method. + + This method releases the Global Enso Lock so that other + threads can use Enso while we're waiting for events; as soon + as an event is triggered and we re-enter Python code via a + low-level event handler, we re-acquire the Global Enso Lock. + """ + + Input.InputManager.run( self ) + + + # ---------------------------------------------------------------------- + # Functions for transfering the existing event handlers to the more + # robust registerResponder method outlined above. + # ---------------------------------------------------------------------- + + def _onIdle( self ): + """ + High-level event handler called whenever we haven't received + any useful input events for IDLE_TIMEOUT seconds. + """ + + self.__currIdleTime = 0 + for func in self.__responders[ "idle" ]: + func() + + def onInit( self ): + """ + Low-level event handler called as soon as the event manager + starts running. + """ + + for func in self.__responders[ "init" ]: + func() + + def onExitRequested( self ): + """ + Called when another process wants us to exit gracefully. + """ + + logging.info( "Exit request received." ) + self.stop() + + def onTick( self, msPassed ): + """ + Low-level event handler called at a regular interval. The + number of milliseconds passed since the last onTick() call is + passed in, although this value may not be 100% accurate. + """ + + self.__currIdleTime += msPassed + + if self.__currIdleTime >= 1000*IDLE_TIMEOUT: + self._onIdle() + for func in self.__responders[ "timer" ]: + func( msPassed ) + + def onTrayMenuItem( self, menuId ): + """ + Low-level event handler called whenever the user selects a + menu item on the popup menu of the Tray Icon. + """ + + self._onDismissalEvent() + for func in self.__responders[ "traymenu" ]: + func( menuId ) + + def _onDismissalEvent( self ): + """ + High-level event handler called whenever a keypress, mouse + movement, or mouse button click is made. + """ + + self.__currIdleTime = 0 + for func in self.__responders[ "dismissal" ]: + func() + + def onKeypress( self, eventType, keyCode ): + """ + Low-level event handler called whenever a quasimodal keypress + is made. + """ + + self.__currIdleTime = 0 + self._onDismissalEvent() + for func in self.__responders[ "key" ]: + func( eventType, keyCode ) + + # The following message may be used by system tests. + logging.debug( "onKeypress: %s, %s" % (eventType, keyCode) ) + + def onMouseMove( self, x, y ): + """ + Low-level event handler that deals with any mouse movement + event. The absolute position of the mouse cursor on-screen is + passed in. + """ + + self._onDismissalEvent() + for func in self.__responders[ "mousemove" ]: + func( x, y ) + + def onSomeMouseButton( self ): + """ + Low-level event handler called whenever any mouse button is + pressed. + """ + + self._onDismissalEvent() + + def onSomeKey( self ): + """ + Low-level event handler called whenever a non-quasimodal + keypress is made. + """ + + for func in self.__responders[ "somekey" ]: + func() + self._onDismissalEvent() + + +# ---------------------------------------------------------------------------- +# Module Initialization +# ---------------------------------------------------------------------------- + +eventManager = None + +def init( quasimodeKeycode ): + global eventManager + + eventManager = _TheEventManager( quasimodeKeycode ) + +def shutdown(): + global eventManager + + eventManager = None