]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wxPython/lib/evtmgr.py
Call wxRichTextModuleInit
[wxWidgets.git] / wxPython / wxPython / lib / evtmgr.py
index 4ea2795342926839d57586e373a9c48cf092ec4a..d0799b04eccb23518b2bbf94fab989079808a052 100644 (file)
-#---------------------------------------------------------------------------
-# Name:        wxPython.lib.evtmgr
-# Purpose:     An easier, more "Pythonic" and more OO method of registering
-#              handlers for wxWindows events using the Publish/Subscribe
-#              pattern.
-#
-# Author:      Robb Shecter and Robin Dunn
-#
-# Created:     12-December-2002
-# RCS-ID:      $Id$
-# Copyright:   (c) 2003 by db-X Corporation
-# Licence:     wxWindows license
-#---------------------------------------------------------------------------
+## This file imports items from the wx package into the wxPython package for
+## backwards compatibility.  Some names will also have a 'wx' added on if
+## that is how they used to be named in the old wxPython package.
 
-"""
-A module that allows multiple handlers to respond to single wxWindows
-events.  This allows true NxN Observer/Observable connections: One
-event can be received by multiple handlers, and one handler can
-receive multiple events.
+import wx.lib.evtmgr
 
-There are two ways to register event handlers.  The first way is
-similar to standard wxPython handler registration:
+__doc__ =  wx.lib.evtmgr.__doc__
 
-    from wxPython.lib.evtmgr import eventManager
-    eventManager.Register(handleEvents, EVT_BUTTON, win=frame, id=101)
-
-There's also a new object-oriented way to register for events.  This
-invocation is equivalent to the one above, but does not require the
-programmer to declare or track control ids or parent containers:
-
-    eventManager.Register(handleEvents, EVT_BUTTON, myButton)
-
-This module is Python 2.1+ compatible.
-
-"""
-from wxPython import wx
-import pubsub
-
-#---------------------------------------------------------------------------
-
-
-class EventManager:
-    """
-    This is the main class in the module, and is the only class that
-    the application programmer needs to use.  There is a pre-created
-    instance of this class called 'eventManager'.  It should not be
-    necessary to create other instances.
-    """
-    def __init__(self):
-        self.eventAdapterDict    = {}
-        self.messageAdapterDict  = {}
-        self.windowTopicLookup   = {}
-        self.listenerTopicLookup = {}
-        self.__publisher         = pubsub.Publisher()
-        self.EMPTY_LIST          = []
-
-
-    def Register(self, listener, event, source=None, win=None, id=None):
-        """
-        Registers a listener function (or any callable object) to
-        receive events of type event coming from the source window.
-        For example:
-
-            eventManager.Register(self.OnButton, EVT_BUTTON, theButton)
-
-        Alternatively, the specific window where the event is
-        delivered, and/or the ID of the event source can be specified.
-        For example:
-
-            eventManager.Register(self.OnButton, EVT_BUTTON, win=self, id=ID_BUTTON)
-        or
-            eventManager.Register(self.OnButton, EVT_BUTTON, theButton, self)
-        """
-
-        # 1. Check if the 'event' is actually one of the multi-
-        #    event macros.
-        if _macroInfo.isMultiEvent(event):
-            raise 'Cannot register the macro, '+`event`+'.  Register instead the individual events.'
-
-        # Support a more OO API.  This allows the GUI widget itself to
-        # be specified, and the id to be retrieved from the system,
-        # instead of kept track of explicitly by the programmer.
-        # (Being used to doing GUI work with Java, this seems to me to be
-        # the natural way of doing things.)
-        if source is not None:
-            id  = source.GetId()
-        if win is None:
-            # Some widgets do not function as their own windows.
-            win = self._determineWindow(source)
-        topic = (event, win, id)
-
-        #  Create an adapter from the PS system back to wxEvents, and
-        #  possibly one from wxEvents:
-        if not self.__haveMessageAdapter(listener, topic):
-            messageAdapter = MessageAdapter(eventHandler=listener, topicPattern=topic)
-            try:
-                self.messageAdapterDict[topic][listener] = messageAdapter
-            except KeyError:
-                self.messageAdapterDict[topic] = {}
-                self.messageAdapterDict[topic][listener] = messageAdapter
-
-            if not self.eventAdapterDict.has_key(topic):
-                self.eventAdapterDict[topic] = EventAdapter(event, win, id)
-        else:
-            # Throwing away a duplicate request
-            pass
-
-        # For time efficiency when deregistering by window:
-        try:
-            self.windowTopicLookup[win].append(topic)
-        except KeyError:
-            self.windowTopicLookup[win] = []
-            self.windowTopicLookup[win].append(topic)
-
-        # For time efficiency when deregistering by listener:
-        try:
-            self.listenerTopicLookup[listener].append(topic)
-        except KeyError:
-            self.listenerTopicLookup[listener] = []
-            self.listenerTopicLookup[listener].append(topic)
-
-        # See if the source understands the listeningFor protocol.
-        # This is a bit of a test I'm working on - it allows classes
-        # to know when their events are being listened to.  I use
-        # it to enable chaining events from contained windows only
-        # when needed.
-        if source is not None:
-            try:
-                # Let the source know that we're listening  for this
-                # event.
-                source.listeningFor(event)
-            except AttributeError:
-                pass
-
-    # Some aliases for Register, just for kicks
-    Bind = Register
-    Subscribe = Register
-
-
-    def DeregisterWindow(self, win):
-        """
-        Deregister all events coming from the given window.
-        """
-        win    = self._determineWindow(win)
-        topics = self.__getTopics(win)
-        if topics:
-            for aTopic in topics:
-                self.__deregisterTopic(aTopic)
-            del self.windowTopicLookup[win]
-
-
-    def DeregisterListener(self, listener):
-        """
-        Deregister all event notifications for the given listener.
-        """
-        try:
-            topicList = self.listenerTopicLookup[listener]
-        except KeyError:
-            return
-
-        for topic in topicList:
-            topicDict = self.messageAdapterDict[topic]
-            if topicDict.has_key(listener):
-                topicDict[listener].Destroy()
-                del topicDict[listener]
-                if len(topicDict) == 0:
-                    self.eventAdapterDict[topic].Destroy()
-                    del self.eventAdapterDict[topic]
-                    del self.messageAdapterDict[topic]
-        del self.listenerTopicLookup[listener]
-
-
-    def GetStats(self):
-        """
-        Return a dictionary with data about my state.
-        """
-        stats = {}
-        stats['Adapters: Message'] = reduce(lambda x,y: x+y, [0] + map(len, self.messageAdapterDict.values()))
-        stats['Adapters: Event']   = len(self.eventAdapterDict)
-        stats['Topics: Total']     = len(self.__getTopics())
-        stats['Topics: Dead']      = len(self.GetDeadTopics())
-        return stats
-
-
-    def DeregisterDeadTopics(self):
-        """
-        Deregister any entries relating to dead
-        wxPython objects.  Not sure if this is an
-        important issue; 1) My app code always de-registers
-        listeners it doesn't need.  2) I don't think
-        that lingering references to these dead objects
-        is a problem.
-        """
-        for topic in self.GetDeadTopics():
-            self.__deregisterTopic(topic)
-
-
-    def GetDeadTopics(self):
-        """
-        Return a list of topics relating to dead wxPython
-        objects.
-        """
-        return filter(self.__isDeadTopic, self.__getTopics())
-
-
-    def __winString(self, aWin):
-        """
-        A string rep of a window for debugging
-        """
-        try:
-            name = aWin.GetClassName()
-            i    = id(aWin)
-            return '%s #%d' % (name, i)
-        except wx.wxPyDeadObjectError:
-            return '(dead wxObject)'
-
-
-    def __topicString(self, aTopic):
-        """
-        A string rep of a topic for debugging
-        """
-        return '[%-26s %s]' % (aTopic[0].__name__, self.winString(aTopic[1]))
-
-
-    def __listenerString(self, aListener):
-        """
-        A string rep of a listener for debugging
-        """
-        try:
-            return aListener.im_class.__name__ + '.' + aListener.__name__
-        except:
-            return 'Function ' + aListener.__name__
-
-
-    def __deregisterTopic(self, aTopic):
-        try:
-            messageAdapterList = self.messageAdapterDict[aTopic].values()
-        except KeyError:
-            # This topic isn't valid.  Probably because it was deleted
-            # by listener.
-            return
-        for messageAdapter in messageAdapterList:
-            messageAdapter.Destroy()
-        self.eventAdapterDict[aTopic].Destroy()
-        del self.messageAdapterDict[aTopic]
-        del self.eventAdapterDict[aTopic]
-
-
-    def __getTopics(self, win=None):
-        if win is None:
-            return self.messageAdapterDict.keys()
-        if win is not None:
-            try:
-                return self.windowTopicLookup[win]
-            except KeyError:
-                return self.EMPTY_LIST
-
-
-    def __isDeadWxObject(self, anObject):
-        return isinstance(anObject, wx._wxPyDeadObject)
-
-
-    def __isDeadTopic(self, aTopic):
-        return self.__isDeadWxObject(aTopic[1])
-
-
-    def __haveMessageAdapter(self, eventHandler, topicPattern):
-        """
-        Return True if there's already a message adapter
-        with these specs.
-        """
-        try:
-            return self.messageAdapterDict[topicPattern].has_key(eventHandler)
-        except KeyError:
-            return 0
-
-
-    def _determineWindow(self, aComponent):
-        """
-        Return the window that corresponds to this component.
-        A window is something that supports the Connect protocol.
-        Most things registered with the event manager are a window,
-        but there are apparently some exceptions.  If more are
-        discovered, the implementation can be changed to a dictionary
-        lookup along the lines of class : function-to-get-window.
-        """
-        if isinstance(aComponent, wx.wxMenuItem):
-            return aComponent.GetMenu()
-        else:
-            return aComponent
-
-
-
-#---------------------------------------------------------------------------
-# From here down is implementaion and support classes, although you may
-# find some of them useful in other contexts.
-#---------------------------------------------------------------------------
-
-
-class EventMacroInfo:
-    """
-    A class that provides information about event macros.
-    """
-    def __init__(self):
-        self.lookupTable = {}
-
-
-    def getEventTypes(self, eventMacro):
-        """
-        Return the list of event types that the given
-        macro corresponds to.
-        """
-        try:
-            return self.lookupTable[eventMacro]
-        except KeyError:
-            win = FakeWindow()
-            try:
-                eventMacro(win, None, None)
-            except TypeError:
-                eventMacro(win, None)
-            self.lookupTable[eventMacro] = win.eventTypes
-            return win.eventTypes
-
-
-    def eventIsA(self, event, macroList):
-        """
-        Return True if the event is one of the given
-        macros.
-        """
-        eventType = event.GetEventType()
-        for macro in macroList:
-            if eventType in self.getEventTypes(macro):
-                return 1
-        return 0
-
-
-    def macroIsA(self, macro, macroList):
-        """
-        Return True if the macro is in the macroList.
-        The added value of this method is that it takes
-        multi-events into account.  The macroList parameter
-        will be coerced into a sequence if needed.
-        """
-        if callable(macroList):
-            macroList = (macroList,)
-        testList  = self.getEventTypes(macro)
-        eventList = []
-        for m in macroList:
-            eventList.extend(self.getEventTypes(m))
-        # Return True if every element in testList is in eventList
-        for element in testList:
-            if element not in eventList:
-                return 0
-        return 1
-
-
-    def isMultiEvent(self, macro):
-        """
-        Return True if the given macro actually causes
-        multiple events to be registered.
-        """
-        return len(self.getEventTypes(macro)) > 1
-
-
-#---------------------------------------------------------------------------
-
-class FakeWindow:
-    """
-    Used internally by the EventMacroInfo class.  The FakeWindow is
-    the most important component of the macro-info utility: it
-    implements the Connect() protocol of wxWindow, but instead of
-    registering for events, it keeps track of what parameters were
-    passed to it.
-    """
-    def __init__(self):
-        self.eventTypes = []
-
-    def Connect(self, id1, id2, eventType, handlerFunction):
-        self.eventTypes.append(eventType)
-
-
-#---------------------------------------------------------------------------
-
-class EventAdapter:
-    """
-    A class that adapts incoming wxWindows events to
-    Publish/Subscribe messages.
-
-    In other words, this is the object that's seen by the
-    wxWindows system.  Only one of these registers for any
-    particular wxWindows event.  It then relays it into the
-    PS system, which lets many listeners respond.
-    """
-    def __init__(self, func, win, id):
-        """
-        Instantiate a new adapter. Pre-compute my Publish/Subscribe
-        topic, which is constant, and register with wxWindows.
-        """
-        self.publisher = pubsub.Publisher()
-        self.topic     = ((func, win, id),)
-        self.id        = id
-        self.win       = win
-        self.eventType = _macroInfo.getEventTypes(func)[0]
-
-        # Register myself with the wxWindows event system
-        try:
-            func(win, id, self.handleEvent)
-            self.callStyle = 3
-        except TypeError:
-            func(win, self.handleEvent)
-            self.callStyle = 2
-
-
-    def disconnect(self):
-        if self.callStyle == 3:
-            return self.win.Disconnect(self.id, -1, self.eventType)
-        else:
-            return self.win.Disconnect(-1, -1, self.eventType)
-
-
-    def handleEvent(self, event):
-        """
-        In response to a wxWindows event, send a PS message
-        """
-        self.publisher.sendMessage(topic=self.topic, data=event)
-
-
-    def Destroy(self):
-        try:
-            if not self.disconnect():
-                print 'disconnect failed'
-        except wx.wxPyDeadObjectError:
-            print 'disconnect failed: dead object'              ##????
-
-
-#---------------------------------------------------------------------------
-
-class MessageAdapter:
-    """
-    A class that adapts incoming Publish/Subscribe messages
-    to wxWindows event calls.
-
-    This class works opposite the EventAdapter, and
-    retrieves the information an EventAdapter has sent in a message.
-    Strictly speaking, this class is not required: Event listeners
-    could pull the original wxEvent object out of the PS Message
-    themselves.
-
-    However, by pairing an instance of this class with each wxEvent
-    handler, the handlers can use the standard API: they receive an
-    event as a parameter.
-    """
-    def __init__(self, eventHandler, topicPattern):
-        """
-        Instantiate a new MessageAdapter that send wxEvents to the
-        given eventHandler.
-        """
-        self.eventHandler = eventHandler
-        pubsub.Publisher().subscribe(listener=self.deliverEvent, topic=(topicPattern,))
-
-    def deliverEvent(self, message):
-        event = message.data        # Extract the wxEvent
-        self.eventHandler(event)    # Perform the call as wxWindows would
-
-    def Destroy(self):
-        pubsub.Publisher().unsubscribe(listener=self.deliverEvent)
-
-
-#---------------------------------------------------------------------------
-# Create globals
-
-_macroInfo    = EventMacroInfo()
-
-# For now a singleton is not enforced.  Should it be or can we trust
-# the programmers?
-eventManager  = EventManager()
-
-
-#---------------------------------------------------------------------------
-# simple test code
-
-
-if __name__ == '__main__':
-    from wxPython.wx import wxPySimpleApp, wxFrame, wxToggleButton, wxBoxSizer, wxHORIZONTAL, EVT_MOTION, EVT_LEFT_DOWN, EVT_TOGGLEBUTTON, wxALL
-    app    = wxPySimpleApp()
-    frame  = wxFrame(None, -1, 'Event Test', size=(300,300))
-    button = wxToggleButton(frame, -1, 'Listen for Mouse Events')
-    sizer  = wxBoxSizer(wxHORIZONTAL)
-    sizer.Add(button, 0, 0 | wxALL, 10)
-    frame.SetAutoLayout(1)
-    frame.SetSizer(sizer)
-
-    #
-    # Demonstrate 1) register/deregister, 2) Multiple listeners receiving
-    # one event, and 3) Multiple events going to one listener.
-    #
-
-    def printEvent(event):
-        print 'Name:',event.GetClassName(),'Timestamp',event.GetTimestamp()
-
-    def enableFrameEvents(event):
-        # Turn the output of mouse events on and off
-        if event.IsChecked():
-            print '\nEnabling mouse events...'
-            eventManager.Register(printEvent, EVT_MOTION,    frame)
-            eventManager.Register(printEvent, EVT_LEFT_DOWN, frame)
-        else:
-            print '\nDisabling mouse events...'
-            eventManager.DeregisterWindow(frame)
-
-    # Send togglebutton events to both the on/off code as well
-    # as the function that prints to stdout.
-    eventManager.Register(printEvent,        EVT_TOGGLEBUTTON, button)
-    eventManager.Register(enableFrameEvents, EVT_TOGGLEBUTTON, button)
-
-    frame.CenterOnScreen()
-    frame.Show(1)
-    app.MainLoop()
+EventAdapter = wx.lib.evtmgr.EventAdapter
+EventMacroInfo = wx.lib.evtmgr.EventMacroInfo
+EventManager = wx.lib.evtmgr.EventManager
+FakeWindow = wx.lib.evtmgr.FakeWindow
+MessageAdapter = wx.lib.evtmgr.MessageAdapter
+eventManager  = wx.lib.evtmgr.eventManager