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 |