| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2001-2006 Twisted Matrix Laboratories. | |
| 2 # See LICENSE for details. | |
| 3 | |
| 4 """ | |
| 5 This module provides wxPython event loop support for Twisted. | |
| 6 | |
| 7 In order to use this support, simply do the following:: | |
| 8 | |
| 9 | from twisted.internet import wxreactor | |
| 10 | wxreactor.install() | |
| 11 | |
| 12 Then, when your root wxApp has been created:: | |
| 13 | |
| 14 | from twisted.internet import reactor | |
| 15 | reactor.registerWxApp(yourApp) | |
| 16 | reactor.run() | |
| 17 | |
| 18 Then use twisted.internet APIs as usual. Stop the event loop using | |
| 19 reactor.stop(), not yourApp.ExitMainLoop(). | |
| 20 | |
| 21 IMPORTANT: tests will fail when run under this reactor. This is | |
| 22 expected and probably does not reflect on the reactor's ability to run | |
| 23 real applications. | |
| 24 | |
| 25 Maintainer: U{Itamar Shtull-Trauring<mailto:twisted@itamarst.org>} | |
| 26 """ | |
| 27 | |
| 28 import Queue | |
| 29 try: | |
| 30 from wx import PySimpleApp as wxPySimpleApp, CallAfter as wxCallAfter, \ | |
| 31 Timer as wxTimer | |
| 32 except ImportError: | |
| 33 # older version of wxPython: | |
| 34 from wxPython.wx import wxPySimpleApp, wxCallAfter, wxTimer | |
| 35 | |
| 36 from twisted.python import log, runtime | |
| 37 from twisted.internet import _threadedselect | |
| 38 | |
| 39 | |
| 40 class ProcessEventsTimer(wxTimer): | |
| 41 """ | |
| 42 Timer that tells wx to process pending events. | |
| 43 | |
| 44 This is necessary on OS X, probably due to a bug in wx, if we want | |
| 45 wxCallAfters to be handled when modal dialogs, menus, etc. are open. | |
| 46 """ | |
| 47 def __init__(self, wxapp): | |
| 48 wxTimer.__init__(self) | |
| 49 self.wxapp = wxapp | |
| 50 | |
| 51 | |
| 52 def Notify(self): | |
| 53 """ | |
| 54 Called repeatedly by wx event loop. | |
| 55 """ | |
| 56 self.wxapp.ProcessPendingEvents() | |
| 57 | |
| 58 | |
| 59 | |
| 60 class WxReactor(_threadedselect.ThreadedSelectReactor): | |
| 61 """ | |
| 62 wxPython reactor. | |
| 63 | |
| 64 wxPython drives the event loop, select() runs in a thread. | |
| 65 """ | |
| 66 | |
| 67 _stopping = False | |
| 68 | |
| 69 def registerWxApp(self, wxapp): | |
| 70 """ | |
| 71 Register wxApp instance with the reactor. | |
| 72 """ | |
| 73 self.wxapp = wxapp | |
| 74 | |
| 75 def _installSignalHandlersAgain(self): | |
| 76 """ | |
| 77 wx sometimes removes our own signal handlers, so re-add them. | |
| 78 """ | |
| 79 try: | |
| 80 # make _handleSignals happy: | |
| 81 import signal | |
| 82 signal.signal(signal.SIGINT, signal.default_int_handler) | |
| 83 except ImportError: | |
| 84 return | |
| 85 self._handleSignals() | |
| 86 | |
| 87 def stop(self): | |
| 88 """ | |
| 89 Stop the reactor. | |
| 90 """ | |
| 91 if self._stopping: | |
| 92 return | |
| 93 self._stopping = True | |
| 94 _threadedselect.ThreadedSelectReactor.stop(self) | |
| 95 | |
| 96 def _runInMainThread(self, f): | |
| 97 """ | |
| 98 Schedule function to run in main wx/Twisted thread. | |
| 99 | |
| 100 Called by the select() thread. | |
| 101 """ | |
| 102 if hasattr(self, "wxapp"): | |
| 103 wxCallAfter(f) | |
| 104 else: | |
| 105 # wx shutdown but twisted hasn't | |
| 106 self._postQueue.put(f) | |
| 107 | |
| 108 def _stopWx(self): | |
| 109 """ | |
| 110 Stop the wx event loop if it hasn't already been stopped. | |
| 111 | |
| 112 Called during Twisted event loop shutdown. | |
| 113 """ | |
| 114 if hasattr(self, "wxapp"): | |
| 115 self.wxapp.ExitMainLoop() | |
| 116 | |
| 117 def run(self, installSignalHandlers=True): | |
| 118 """ | |
| 119 Start the reactor. | |
| 120 """ | |
| 121 self._postQueue = Queue.Queue() | |
| 122 if not hasattr(self, "wxapp"): | |
| 123 log.msg("registerWxApp() was not called on reactor, " | |
| 124 "registering my own wxApp instance.") | |
| 125 self.registerWxApp(wxPySimpleApp()) | |
| 126 | |
| 127 # start select() thread: | |
| 128 self.interleave(self._runInMainThread, | |
| 129 installSignalHandlers=installSignalHandlers) | |
| 130 if installSignalHandlers: | |
| 131 self.callLater(0, self._installSignalHandlersAgain) | |
| 132 | |
| 133 # add cleanup events: | |
| 134 self.addSystemEventTrigger("after", "shutdown", self._stopWx) | |
| 135 self.addSystemEventTrigger("after", "shutdown", | |
| 136 lambda: self._postQueue.put(None)) | |
| 137 | |
| 138 # On Mac OS X, work around wx bug by starting timer to ensure | |
| 139 # wxCallAfter calls are always processed. We don't wake up as | |
| 140 # often as we could since that uses too much CPU. | |
| 141 if runtime.platform.isMacOSX(): | |
| 142 t = ProcessEventsTimer(self.wxapp) | |
| 143 t.Start(2) # wake up every 2ms | |
| 144 | |
| 145 self.wxapp.MainLoop() | |
| 146 wxapp = self.wxapp | |
| 147 del self.wxapp | |
| 148 | |
| 149 if not self._stopping: | |
| 150 # wx event loop exited without reactor.stop() being | |
| 151 # called. At this point events from select() thread will | |
| 152 # be added to _postQueue, but some may still be waiting | |
| 153 # unprocessed in wx, thus the ProcessPendingEvents() | |
| 154 # below. | |
| 155 self.stop() | |
| 156 wxapp.ProcessPendingEvents() # deal with any queued wxCallAfters | |
| 157 while 1: | |
| 158 try: | |
| 159 f = self._postQueue.get(timeout=0.01) | |
| 160 except Queue.Empty: | |
| 161 continue | |
| 162 else: | |
| 163 if f is None: | |
| 164 break | |
| 165 try: | |
| 166 f() | |
| 167 except: | |
| 168 log.err() | |
| 169 | |
| 170 | |
| 171 def install(): | |
| 172 """ | |
| 173 Configure the twisted mainloop to be run inside the wxPython mainloop. | |
| 174 """ | |
| 175 reactor = WxReactor() | |
| 176 from twisted.internet.main import installReactor | |
| 177 installReactor(reactor) | |
| 178 return reactor | |
| 179 | |
| 180 | |
| 181 __all__ = ['install'] | |
| OLD | NEW |