Ticket #1770: qt4reactor.py

File qt4reactor.py, 5.1 KB (added by grudy, 15 years ago)
Line 
1# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
2# See LICENSE for details.
3
4
5"""
6This module provides support for Twisted to interact with the PyQt mainloop.
7
8In order to use this support, simply do the following::
9
10    |  from twisted.internet import qtreactor
11    |  qtreactor.install()
12
13Then use twisted.internet APIs as usual.  The other methods here are not
14intended to be called directly.
15
16API Stability: stable
17
18Maintainer: U{Itamar Shtull-Trauring<mailto:twisted@itamarst.org>}
19Port to QT4: U{Gabe Rudy<mailto:rudy@goldenhelix.com>}
20"""
21
22__all__ = ['install']
23
24# System Imports
25from PyQt4.QtCore import QSocketNotifier, QObject, SIGNAL, QTimer
26from PyQt4.QtGui import QApplication
27import sys
28
29# Twisted Imports
30from twisted.python import log, failure
31from twisted.internet import posixbase
32
33reads = {}
34writes = {}
35hasReader = reads.has_key
36hasWriter = writes.has_key
37
38
39class TwistedSocketNotifier(QSocketNotifier):
40    '''Connection between an fd event and reader/writer callbacks'''
41
42    def __init__(self, reactor, watcher, type):
43        QSocketNotifier.__init__(self, watcher.fileno(), type)
44        self.reactor = reactor
45        self.watcher = watcher
46        self.fn = None
47        if type == QSocketNotifier.Read:
48            self.fn = self.read
49        elif type == QSocketNotifier.Write:
50            self.fn = self.write
51        QObject.connect(self, SIGNAL("activated(int)"), self.fn)
52
53    def shutdown(self):
54        QObject.disconnect(self, SIGNAL("activated(int)"), self.fn)
55        self.setEnabled(0)
56        self.fn = self.watcher = None
57
58    def read(self, sock):
59        why = None
60        w = self.watcher
61        try:
62            why = w.doRead()
63        except:
64            why = sys.exc_info()[1]
65            log.msg('Error in %s.doRead()' % w)
66            log.deferr()
67        if why:
68            self.reactor._disconnectSelectable(w, why, True)
69        self.reactor.simulate()
70
71    def write(self, sock):
72        why = None
73        w = self.watcher
74        self.setEnabled(0)
75        try:
76            why = w.doWrite()
77        except:
78            why = sys.exc_value
79            log.msg('Error in %s.doWrite()' % w)
80            log.deferr()
81        if why:
82            self.reactor.removeReader(w)
83            self.reactor.removeWriter(w)
84            try:
85                w.connectionLost(failure.Failure(why))
86            except:
87                log.deferr()
88        elif self.watcher:
89            self.setEnabled(1)
90        self.reactor.simulate()
91
92
93class QTReactor(posixbase.PosixReactorBase):
94    """Qt based reactor."""
95
96    # Reference to a DelayedCall for self.crash() when the reactor is
97    # entered through .iterate()
98    _crashCall = None
99
100    _timer = None
101
102    def __init__(self, app=None):
103        self.running = 0
104        posixbase.PosixReactorBase.__init__(self)
105        if app is None:
106            app = QApplication([])
107        self.qApp = app
108        self.addSystemEventTrigger('after', 'shutdown', self.cleanup)
109
110    def addReader(self, reader):
111        if not hasReader(reader):
112            reads[reader] = TwistedSocketNotifier(self, reader, QSocketNotifier.Read)
113
114    def addWriter(self, writer):
115        if not hasWriter(writer):
116            writes[writer] = TwistedSocketNotifier(self, writer, QSocketNotifier.Write)
117
118    def removeReader(self, reader):
119        if hasReader(reader):
120            reads[reader].shutdown()
121            del reads[reader]
122
123    def removeWriter(self, writer):
124        if hasWriter(writer):
125            writes[writer].shutdown()
126            del writes[writer]
127
128    def removeAll(self):
129        return self._removeAll(reads, writes)
130
131    def simulate(self):
132        if self._timer is not None:
133            self._timer.stop()
134            self._timer = None
135
136        if not self.running:
137            self.running = 1
138            self.qApp.exit_loop()
139            return
140        self.runUntilCurrent()
141
142        if self._crashCall is not None:
143            self._crashCall.reset(0)
144
145        # gah
146        timeout = self.timeout()
147        if timeout is None:
148            timeout = 1.0
149        timeout = min(timeout, 0.1) * 1010
150
151        if self._timer is None:
152            self._timer = QTimer()
153            QObject.connect(self._timer, SIGNAL("timeout()"), self.simulate)
154        self._timer.start(timeout)
155
156    def cleanup(self):
157        if self._timer is not None:
158            self._timer.stop()
159            self._timer = None
160
161    def iterate(self, delay=0.0):
162        log.msg(channel='system', event='iteration', reactor=self)
163        self._crashCall = self.callLater(delay, self.crash)
164        self.run()
165
166    def run(self, installSignalHandlers=1):
167        self.running = 1
168        self.startRunning(installSignalHandlers=installSignalHandlers)
169        self.simulate()
170        self.qApp.exec_()
171
172    def crash(self):
173        if self._crashCall is not None:
174            if self._crashCall.active():
175                self._crashCall.cancel()
176            self._crashCall = None
177        self.running = 0
178
179
180def install(app=None):
181    """Configure the twisted mainloop to be run inside the qt mainloop.
182    """
183    from twisted.internet import main
184
185    reactor = QTReactor(app=app)
186    main.installReactor(reactor)