~ghtdak/qt4reactor/trunk

1 by glenn tarbox
init
1
# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
2
# See LICENSE for details.
3
4
5
"""
6
This module provides support for Twisted to be driven by the Qt mainloop.
7
8
In order to use this support, simply do the following::
9
    |  app = QApplication(sys.argv) # your code to init Qt
10
    |  import qt4reactor
11
    |  qt4reactor.install()
12
    
13
alternatively:
14
15
    |  from twisted.application import reactors
16
    |  reactors.installReactor('qt4')
17
18
Then use twisted.internet APIs as usual.  The other methods here are not
19
intended to be called directly.
20
21
If you don't instantiate a QApplication or QCoreApplication prior to
22
installing the reactor, a QCoreApplication will be constructed
23
by the reactor.  QCoreApplication does not require a GUI so trial testing
24
can occur normally.
25
26
Twisted can be initialized after QApplication.exec_() with a call to
27
reactor.runReturn().  calling reactor.stop() will unhook twisted but
28
leave your Qt application running
29
30
API Stability: stable
31
32
Maintainer: U{Glenn H Tarbox, PhD<mailto:glenn@tarbox.org>}
33
34
Previous maintainer: U{Itamar Shtull-Trauring<mailto:twisted@itamarst.org>}
35
Original port to QT4: U{Gabe Rudy<mailto:rudy@goldenhelix.com>}
36
Subsequent port by therve
37
"""
38
39
__all__ = ['install']
40
41
42
import sys, time
43
44
from zope.interface import implements
45
46
from PyQt4.QtCore import QSocketNotifier, QObject, SIGNAL, QTimer, QCoreApplication
47
from PyQt4.QtCore import QEventLoop
48
49
from twisted.internet.interfaces import IReactorFDSet
50
from twisted.python import log
51
from twisted.internet.posixbase import PosixReactorBase
52
53
class TwistedSocketNotifier(QSocketNotifier):
54
    """
55
    Connection between an fd event and reader/writer callbacks.
56
    """
57
58
    def __init__(self, reactor, watcher, type):
59
        QSocketNotifier.__init__(self, watcher.fileno(), type)
60
        self.reactor = reactor
61
        self.watcher = watcher
62
        self.fn = None
63
        if type == QSocketNotifier.Read:
64
            self.fn = self.read
65
        elif type == QSocketNotifier.Write:
66
            self.fn = self.write
67
        QObject.connect(self, SIGNAL("activated(int)"), self.fn)
68
69
70
    def shutdown(self):
71
        QObject.disconnect(self, SIGNAL("activated(int)"), self.fn)
72
        self.setEnabled(False)
73
        self.fn = self.watcher = None
2 by glenn tarbox
fixes for Qt 4.4
74
        self.deleteLater()
1 by glenn tarbox
init
75
76
77
    def read(self, sock):
78
        w = self.watcher
79
        #self.setEnabled(False)    # ??? do I need this?            
80
        def _read():
81
            why = None
82
            try:
83
                why = w.doRead()
84
            except:
85
                log.err()
86
                why = sys.exc_info()[1]
87
            if why:
88
                self.reactor._disconnectSelectable(w, why, True)
89
            elif self.watcher:
90
                pass
91
                #self.setEnabled(True)
92
        log.callWithLogger(w, _read)
93
        self.reactor.reactorInvocation()
94
95
    def write(self, sock):
96
        w = self.watcher
97
        self.setEnabled(False)
98
        def _write():
99
            why = None
100
            try:
101
                why = w.doWrite()
102
            except:
103
                log.err()
104
                why = sys.exc_info()[1]
105
            if why:
106
                self.reactor._disconnectSelectable(w, why, False)
107
            elif self.watcher:
108
                self.setEnabled(True)
109
        log.callWithLogger(w, _write)
110
        self.reactor.reactorInvocation()
111
112
class fakeApplication(QEventLoop):
113
    def __init__(self):
114
        QEventLoop.__init__(self)
115
        
116
    def exec_(self):
117
        QEventLoop.exec_(self)
118
        
119
class QTReactor(PosixReactorBase):
120
    """
121
    Qt based reactor.
122
    """
123
    implements(IReactorFDSet)
124
125
    _timer = None
126
127
    def __init__(self):
128
        self._reads = {}
129
        self._writes = {}
130
        self._timer=QTimer()
131
        self._timer.setSingleShot(True)
132
        if QCoreApplication.startingUp():
133
            self.qApp=QCoreApplication([])
134
            self._ownApp=True
135
        else:
136
            self.qApp = QCoreApplication.instance()
137
            self._ownApp=False
138
        self._blockApp = None
139
        self._readWriteQ=[]
140
        
141
        """ some debugging instrumentation """
142
        self._doSomethingCount=0
143
        
144
        PosixReactorBase.__init__(self)
145
146
    def addReader(self, reader):
147
        if not reader in self._reads:
148
            self._reads[reader] = TwistedSocketNotifier(self, reader,
149
                                                       QSocketNotifier.Read)
150
151
152
    def addWriter(self, writer):
153
        if not writer in self._writes:
154
            self._writes[writer] = TwistedSocketNotifier(self, writer,
155
                                                        QSocketNotifier.Write)
156
157
158
    def removeReader(self, reader):
159
        if reader in self._reads:
2 by glenn tarbox
fixes for Qt 4.4
160
            #self._reads[reader].shutdown()
161
            #del self._reads[reader]
162
            self._reads.pop(reader).shutdown()
1 by glenn tarbox
init
163
164
    def removeWriter(self, writer):
165
        if writer in self._writes:
166
            self._writes[writer].shutdown()
2 by glenn tarbox
fixes for Qt 4.4
167
            #del self._writes[writer]
168
            self._writes.pop(writer)
1 by glenn tarbox
init
169
170
171
    def removeAll(self):
172
        return self._removeAll(self._reads, self._writes)
173
174
175
    def getReaders(self):
176
        return self._reads.keys()
177
178
179
    def getWriters(self):
180
        return self._writes.keys()
181
    
182
    def callLater(self,howlong, *args, **kargs):
183
        rval = super(QTReactor,self).callLater(howlong, *args, **kargs)
184
        self.reactorInvocation()
185
        return rval
186
    
187
    def crash(self):
188
        super(QTReactor,self).crash()
189
        
190
    def iterate(self,delay=0.0):
191
        t=self.running # not sure I entirely get the state of running
192
        self.running=True
193
        self._timer.stop() # in case its not (rare?)
194
        try:
195
            if delay == 0.0:
196
                self.reactorInvokePrivate()
197
                self._timer.stop() # supports multiple invocations
198
            else:
199
                endTime = delay + time.time()
200
                self.reactorInvokePrivate()
201
                while True:
202
                    t = endTime - time.time()
203
                    if t <= 0.0: return
204
                    self.qApp.processEvents(QEventLoop.AllEvents | 
205
                                      QEventLoop.WaitForMoreEvents,t*1010)
206
        finally:
207
            self.running=t
208
            
209
    def addReadWrite(self,t):
210
        self._readWriteQ.append(t)
211
        
212
    def runReturn(self, installSignalHandlers=True):
213
        QObject.connect(self._timer, SIGNAL("timeout()"), 
214
                        self.reactorInvokePrivate)
215
        self.startRunning(installSignalHandlers=installSignalHandlers)
216
        self._timer.start(0)
217
        
218
    def run(self, installSignalHandlers=True):
219
        try:
220
            if self._ownApp:
221
                self._blockApp=self.qApp
222
            else:
223
                self._blockApp = fakeApplication()
224
            self.runReturn(installSignalHandlers)
225
            self._blockApp.exec_()
226
        finally:
227
            self._timer.stop() # should already be stopped
228
229
    def reactorInvocation(self):
230
        self._timer.setInterval(0)
231
        
232
    def reactorInvokePrivate(self):
233
        if not self.running:
234
            self._blockApp.quit()
235
        self._doSomethingCount += 1
236
        self.runUntilCurrent()
237
        t = self.timeout()
238
        if t is None: t=0.1
239
        else: t = min(t,0.1)
240
        self._timer.setInterval(t*1010)
241
        self.qApp.processEvents() # could change interval
242
        self._timer.start()
243
                
244
    def doIteration(self):
245
        assert False, "doiteration is invalid call"
246
            
247
def install():
248
    """
249
    Configure the twisted mainloop to be run inside the qt mainloop.
250
    """
251
    from twisted.internet import main
252
    reactor = QTReactor()
253
    main.installReactor(reactor)