From c5c6e29a97b7a1d848d48170a254f8a37713b0b3 Mon Sep 17 00:00:00 2001 From: Michael Thompson Date: Sat, 22 Jan 2011 17:43:54 +0000 Subject: [PATCH] Revert "Merge remove-qtreactor-2130" This reverts parts of commit 5e1c1a661a6aa80f42f97ac8441a2bc9c8f1efe6. Reintegrating the Qt Reactor --- doc/core/examples/qtdemo.py | 98 ++++++++++++++ twisted/internet/qtreactor.py | 193 ++++++++++++++++++++++++++-- twisted/plugins/twisted_qtstub.py | 45 ------- twisted/plugins/twisted_reactors.py | 3 + 4 files changed, 280 insertions(+), 59 deletions(-) create mode 100644 doc/core/examples/qtdemo.py delete mode 100644 twisted/plugins/twisted_qtstub.py diff --git a/doc/core/examples/qtdemo.py b/doc/core/examples/qtdemo.py new file mode 100644 index 000000000..7d38ed13a --- /dev/null +++ b/doc/core/examples/qtdemo.py @@ -0,0 +1,98 @@ +# Copyright (c) 2001-2004 Twisted Matrix Laboratories. +# See LICENSE for details. + + +"""Qt demo. + +Fetch a URL's contents. +""" + +import sys, urlparse +from qt import * + +from twisted.internet import qtreactor, protocol +app = QApplication([]) +qtreactor.install(app) + +from twisted.web import http + + +class TwistzillaClient(http.HTTPClient): + def __init__(self, edit, urls): + self.urls = urls + self.edit = edit + + def connectionMade(self): + print 'Connected.' + + self.sendCommand('GET', self.urls[2]) + self.sendHeader('Host', '%s:%d' % (self.urls[0], self.urls[1]) ) + self.sendHeader('User-Agent', 'Twistzilla') + self.endHeaders() + + def handleResponse(self, data): + print 'Got response.' + self.edit.setText(data) + + + +class TwistzillaWindow(QMainWindow): + def __init__(self, *args): + QMainWindow.__init__(self, *args) + + self.setCaption("Twistzilla") + + vbox = QVBox(self) + vbox.setMargin(2) + vbox.setSpacing(3) + + hbox = QHBox(vbox) + label = QLabel("Address: ", hbox) + + self.line = QLineEdit("http://www.twistedmatrix.com/", hbox) + self.connect(self.line, SIGNAL('returnPressed()'), self.fetchURL) + + self.edit = QMultiLineEdit(vbox) + self.edit.setEdited(0) + + self.setCentralWidget(vbox) + + def fetchURL(self): + u = urlparse.urlparse(str(self.line.text())) + + pos = u[1].find(':') + + if pos == -1: + host, port = u[1], 80 + else: + host, port = u[1][:pos], int(u[1][pos+1:]) + + if u[2] == '': + file = '/' + else: + file = u[2] + + print 'Connecting to.' + from twisted.internet import reactor + protocol.ClientCreator(reactor, TwistzillaClient, self.edit, (host, port, file)).connectTCP(host, port) + + +def main(): + """Run application.""" + # hook up Qt application to Twisted + from twisted.internet import reactor + + win = TwistzillaWindow() + win.show() + + # make sure stopping twisted event also shuts down QT + reactor.addSystemEventTrigger('after', 'shutdown', app.quit ) + + # shutdown twisted when window is closed + app.connect(app, SIGNAL("lastWindowClosed()"), reactor.stop) + + reactor.run() + + +if __name__ == '__main__': + main() diff --git a/twisted/internet/qtreactor.py b/twisted/internet/qtreactor.py index abbd3baa7..926cb4045 100644 --- a/twisted/internet/qtreactor.py +++ b/twisted/internet/qtreactor.py @@ -1,19 +1,184 @@ -# -*- test-case-name: twisted.internet.test.test_qtreactor -*- -# Copyright (c) 2001-2009 Twisted Matrix Laboratories. +# Copyright (c) 2001-2004 Twisted Matrix Laboratories. # See LICENSE for details. -try: - # 'import qtreactor' would have imported this file instead of the - # top-level qtreactor. __import__ does the right thing - # (kids, don't repeat this at home) - install = __import__('qtreactor').install -except ImportError: - from twisted.plugins.twisted_qtstub import errorMessage - raise ImportError(errorMessage) -else: - import warnings - warnings.warn("Please use qtreactor instead of twisted.internet.qtreactor", - category=DeprecationWarning) + +""" +This module provides support for Twisted to interact with the PyQt mainloop. + +In order to use this support, simply do the following:: + + | from twisted.internet import qtreactor + | qtreactor.install() + +Then use twisted.internet APIs as usual. The other methods here are not +intended to be called directly. + +API Stability: stable + +Maintainer: U{Itamar Shtull-Trauring} +""" __all__ = ['install'] +# System Imports +from qt import QSocketNotifier, QObject, SIGNAL, QTimer, QApplication +import sys + +# Twisted Imports +from twisted.python import log, failure +from twisted.internet import posixbase + +reads = {} +writes = {} +hasReader = reads.has_key +hasWriter = writes.has_key + + +class TwistedSocketNotifier(QSocketNotifier): + '''Connection between an fd event and reader/writer callbacks''' + + def __init__(self, reactor, watcher, type): + QSocketNotifier.__init__(self, watcher.fileno(), type) + self.reactor = reactor + self.watcher = watcher + self.fn = None + if type == QSocketNotifier.Read: + self.fn = self.read + elif type == QSocketNotifier.Write: + self.fn = self.write + QObject.connect(self, SIGNAL("activated(int)"), self.fn) + + def shutdown(self): + QObject.disconnect(self, SIGNAL("activated(int)"), self.fn) + self.setEnabled(0) + self.fn = self.watcher = None + + def read(self, sock): + why = None + w = self.watcher + try: + why = w.doRead() + except: + why = sys.exc_info()[1] + log.msg('Error in %s.doRead()' % w) + log.deferr() + if why: + self.reactor._disconnectSelectable(w, why, True) + self.reactor.simulate() + + def write(self, sock): + why = None + w = self.watcher + self.setEnabled(0) + try: + why = w.doWrite() + except: + why = sys.exc_value + log.msg('Error in %s.doWrite()' % w) + log.deferr() + if why: + self.reactor.removeReader(w) + self.reactor.removeWriter(w) + try: + w.connectionLost(failure.Failure(why)) + except: + log.deferr() + elif self.watcher: + self.setEnabled(1) + self.reactor.simulate() + + +class QTReactor(posixbase.PosixReactorBase): + """Qt based reactor.""" + + # Reference to a DelayedCall for self.crash() when the reactor is + # entered through .iterate() + _crashCall = None + + _timer = None + + def __init__(self, app=None): + self.running = 0 + posixbase.PosixReactorBase.__init__(self) + if app is None: + app = QApplication([]) + self.qApp = app + self.addSystemEventTrigger('after', 'shutdown', self.cleanup) + + def addReader(self, reader): + if not hasReader(reader): + reads[reader] = TwistedSocketNotifier(self, reader, QSocketNotifier.Read) + + def addWriter(self, writer): + if not hasWriter(writer): + writes[writer] = TwistedSocketNotifier(self, writer, QSocketNotifier.Write) + + def removeReader(self, reader): + if hasReader(reader): + reads[reader].shutdown() + del reads[reader] + + def removeWriter(self, writer): + if hasWriter(writer): + writes[writer].shutdown() + del writes[writer] + + def removeAll(self): + return self._removeAll(reads, writes) + + def simulate(self): + if self._timer is not None: + self._timer.stop() + self._timer = None + + if not self.running: + self.running = 1 + self.qApp.exit_loop() + return + self.runUntilCurrent() + + if self._crashCall is not None: + self._crashCall.reset(0) + + # gah + timeout = self.timeout() + if timeout is None: + timeout = 1.0 + timeout = min(timeout, 0.1) * 1010 + + if self._timer is None: + self._timer = QTimer() + QObject.connect(self._timer, SIGNAL("timeout()"), self.simulate) + self._timer.start(timeout, 1) + + def cleanup(self): + if self._timer is not None: + self._timer.stop() + self._timer = None + + def iterate(self, delay=0.0): + log.msg(channel='system', event='iteration', reactor=self) + self._crashCall = self.callLater(delay, self.crash) + self.run() + + def run(self, installSignalHandlers=1): + self.running = 1 + self.startRunning(installSignalHandlers=installSignalHandlers) + self.simulate() + self.qApp.enter_loop() + + def crash(self): + if self._crashCall is not None: + if self._crashCall.active(): + self._crashCall.cancel() + self._crashCall = None + self.running = 0 + + +def install(app=None): + """Configure the twisted mainloop to be run inside the qt mainloop. + """ + from twisted.internet import main + + reactor = QTReactor(app=app) + main.installReactor(reactor) diff --git a/twisted/plugins/twisted_qtstub.py b/twisted/plugins/twisted_qtstub.py deleted file mode 100644 index 1b9b08a03..000000000 --- a/twisted/plugins/twisted_qtstub.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2006 Twisted Matrix Laboratories. -# See LICENSE for details. - -""" -Backwards-compatibility plugin for the Qt reactor. - -This provides a Qt reactor plugin named C{qt} which emits a deprecation -warning and a pointer to the separately distributed Qt reactor plugins. -""" - -import warnings - -from twisted.application.reactors import Reactor, NoSuchReactor - -wikiURL = 'http://twistedmatrix.com/trac/wiki/QTReactor' -errorMessage = ('qtreactor is no longer a part of Twisted due to licensing ' - 'issues. Please see %s for details.' % (wikiURL,)) - -class QTStub(Reactor): - """ - Reactor plugin which emits a deprecation warning on the successful - installation of its reactor or a pointer to further information if an - ImportError occurs while attempting to install it. - """ - def __init__(self): - super(QTStub, self).__init__( - 'qt', 'qtreactor', 'QT integration reactor') - - - def install(self): - """ - Install the Qt reactor with a deprecation warning or try to point - the user to further information if it cannot be installed. - """ - try: - super(QTStub, self).install() - except (ValueError, ImportError): - raise NoSuchReactor(errorMessage) - else: - warnings.warn( - "Please use -r qt3 to import qtreactor", - category=DeprecationWarning) - - -qt = QTStub() diff --git a/twisted/plugins/twisted_reactors.py b/twisted/plugins/twisted_reactors.py index 428e96c40..8b58c8094 100644 --- a/twisted/plugins/twisted_reactors.py +++ b/twisted/plugins/twisted_reactors.py @@ -36,3 +36,6 @@ iocp = Reactor( 'iocp', 'twisted.internet.iocpreactor', 'Win32 IO Completion Ports-based reactor.') +qt = Reactor( + 'qt', 'twisted.internet.qtreactor', + 'Qt based reactor using PySide or PyQt')