463
|
1 # http://twistedmatrix.com/trac/browser/sandbox/therve/qt4reactor.py
|
|
2 # with some fixes by drewp
|
|
3
|
|
4 # Copyright (c) 2001-2008 Twisted Matrix Laboratories.
|
|
5 # See LICENSE for details.
|
|
6
|
|
7
|
|
8 """
|
|
9 This module provides support for Twisted to interact with the PyQt mainloop.
|
|
10
|
|
11 In order to use this support, simply do the following::
|
|
12
|
|
13 | import qt4reactor
|
|
14 | qt4reactor.install()
|
|
15
|
|
16 Then use twisted.internet APIs as usual. The other methods here are not
|
|
17 intended to be called directly.
|
|
18
|
|
19 API Stability: stable
|
|
20
|
|
21 Maintainer: U{Itamar Shtull-Trauring<mailto:twisted@itamarst.org>}
|
|
22 Port to QT4: U{Gabe Rudy<mailto:rudy@goldenhelix.com>}
|
|
23 """
|
|
24
|
|
25 __all__ = ['install']
|
|
26
|
|
27
|
|
28 import sys
|
|
29
|
|
30 from zope.interface import implements
|
|
31
|
|
32 from PyQt4.QtCore import QSocketNotifier, QObject, SIGNAL, QTimer
|
|
33 from PyQt4.QtGui import QApplication
|
|
34
|
|
35 from twisted.internet.interfaces import IReactorFDSet
|
|
36 from twisted.python import log
|
|
37 from twisted.internet.posixbase import PosixReactorBase
|
|
38
|
|
39
|
|
40
|
|
41 class TwistedSocketNotifier(QSocketNotifier):
|
|
42 """
|
|
43 Connection between an fd event and reader/writer callbacks.
|
|
44 """
|
|
45
|
|
46 def __init__(self, reactor, watcher, type):
|
|
47 QSocketNotifier.__init__(self, watcher.fileno(), type)
|
|
48 self.reactor = reactor
|
|
49 self.watcher = watcher
|
|
50 self.fn = None
|
|
51 if type == QSocketNotifier.Read:
|
|
52 self.fn = self.read
|
|
53 elif type == QSocketNotifier.Write:
|
|
54 self.fn = self.write
|
|
55 QObject.connect(self, SIGNAL("activated(int)"), self.fn)
|
|
56
|
|
57
|
|
58 def shutdown(self):
|
|
59 QObject.disconnect(self, SIGNAL("activated(int)"), self.fn)
|
|
60 self.setEnabled(0)
|
|
61 self.fn = self.watcher = None
|
|
62
|
|
63
|
|
64 def read(self, sock):
|
|
65 w = self.watcher
|
|
66 def _read():
|
|
67 why = None
|
|
68 try:
|
|
69 why = w.doRead()
|
|
70 except:
|
|
71 log.err()
|
|
72 why = sys.exc_info()[1]
|
|
73 if why:
|
|
74 self.reactor._disconnectSelectable(w, why, True)
|
|
75 log.callWithLogger(w, _read)
|
|
76 self.reactor.simulate()
|
|
77
|
|
78
|
|
79 def write(self, sock):
|
|
80 w = self.watcher
|
|
81 def _write():
|
|
82 why = None
|
|
83 self.setEnabled(0)
|
|
84 try:
|
|
85 why = w.doWrite()
|
|
86 except:
|
|
87 log.err()
|
|
88 why = sys.exc_info()[1]
|
|
89 if why:
|
|
90 self.reactor._disconnectSelectable(w, why, False)
|
|
91 elif self.watcher:
|
|
92 self.setEnabled(1)
|
|
93 log.callWithLogger(w, _write)
|
|
94 self.reactor.simulate()
|
|
95
|
|
96
|
|
97
|
|
98 class QTReactor(PosixReactorBase):
|
|
99 """
|
|
100 Qt based reactor.
|
|
101 """
|
|
102 implements(IReactorFDSet)
|
|
103
|
|
104 # Reference to a DelayedCall for self.crash() when the reactor is
|
|
105 # entered through .iterate()
|
|
106 _crashCall = None
|
|
107
|
|
108 _timer = None
|
|
109
|
|
110 def __init__(self, app=None):
|
|
111 self._reads = {}
|
|
112 self._writes = {}
|
|
113 if app is None:
|
|
114 app = QApplication([])
|
|
115 self.qApp = app
|
|
116 PosixReactorBase.__init__(self)
|
|
117 self.addSystemEventTrigger('after', 'shutdown', self.cleanup)
|
|
118
|
|
119
|
|
120
|
|
121 def addReader(self, reader):
|
|
122 if not reader in self._reads:
|
|
123 self._reads[reader] = TwistedSocketNotifier(self, reader,
|
|
124 QSocketNotifier.Read)
|
|
125
|
|
126
|
|
127 def addWriter(self, writer):
|
|
128 if not writer in self._writes:
|
|
129 self._writes[writer] = TwistedSocketNotifier(self, writer,
|
|
130 QSocketNotifier.Write)
|
|
131
|
|
132
|
|
133 def removeReader(self, reader):
|
|
134 if reader in self._reads:
|
|
135 self._reads[reader].shutdown()
|
|
136 del self._reads[reader]
|
|
137
|
|
138
|
|
139 def removeWriter(self, writer):
|
|
140 if writer in self._writes:
|
|
141 self._writes[writer].shutdown()
|
|
142 del self._writes[writer]
|
|
143
|
|
144
|
|
145 def removeAll(self):
|
|
146 return self._removeAll(self._reads, self._writes)
|
|
147
|
|
148
|
|
149 def getReaders(self):
|
|
150 return self._reads.keys()
|
|
151
|
|
152
|
|
153 def getWriters(self):
|
|
154 return self._writes.keys()
|
|
155
|
|
156
|
|
157 def simulate(self):
|
|
158 self._lastTimer = self._timer # put off the __del__
|
|
159 if self._timer is not None:
|
|
160 self._timer.stop()
|
|
161 self._timer = None
|
|
162
|
|
163 if not self.running:
|
|
164 self.qApp.exit()
|
|
165 return
|
|
166 self.runUntilCurrent()
|
|
167
|
|
168 if self._crashCall is not None:
|
|
169 self._crashCall.reset(0)
|
|
170
|
|
171 timeout = self.timeout()
|
|
172 if timeout is None:
|
|
173 timeout = 1.0
|
|
174 timeout = min(timeout, 0.01) * 1010
|
|
175
|
|
176 if self._timer is None:
|
|
177 self._timer = QTimer()
|
|
178 self._timer.setObjectName("simulateTimer")
|
|
179 QObject.connect(self._timer, SIGNAL("timeout()"), self.simulate)
|
|
180 self._timer.start(timeout)
|
|
181
|
|
182 def cleanup(self):
|
|
183 if self._timer is not None:
|
|
184 self._timer.stop()
|
|
185 self._timer = None
|
|
186
|
|
187
|
|
188 def iterate(self, delay=0.0):
|
|
189 self._crashCall = self.callLater(delay, self._crash)
|
|
190 self.run()
|
|
191
|
|
192
|
|
193 def mainLoop(self):
|
|
194 self.simulate()
|
|
195 self.qApp.exec_()
|
|
196
|
|
197
|
|
198 def _crash(self):
|
|
199 if self._crashCall is not None:
|
|
200 if self._crashCall.active():
|
|
201 self._crashCall.cancel()
|
|
202 self._crashCall = None
|
|
203 self.running = False
|
|
204
|
|
205
|
|
206
|
|
207 def install(app=None):
|
|
208 """
|
|
209 Configure the twisted mainloop to be run inside the qt mainloop.
|
|
210 """
|
|
211 from twisted.internet import main
|
|
212 reactor = QTReactor(app=app)
|
|
213 main.installReactor(reactor)
|