diff --git doc/core/examples/qtdemo.py doc/core/examples/qtdemo.py
new file mode 100644
index 0000000..b16709f
--- /dev/null
+++ doc/core/examples/qtdemo.py
@@ -0,0 +1,97 @@
+# Copyright (c) 2001-20011 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Qt demo.
+
+Fetch a URL's contents and display it in a Webkit window.
+"""
+
+import sys, urlparse
+
+from PySide import QtGui, QtCore
+from PySide.QtWebKit import QWebView
+
+from twisted.internet import protocol
+
+app = QtGui.QApplication(sys.argv)
+from twisted.internet import qtreactor
+qtreactor.install()
+
+# The reactor must be installed before this import
+from twisted.web import http
+
+
+class TwistzillaClient(http.HTTPClient):
+    def __init__(self, web, urls):
+        self.urls = urls
+        self.web = web
+
+    def connectionMade(self):
+        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):
+        self.web.setHtml(data)
+
+
+class TwistzillaWindow(QtGui.QMainWindow):
+    def __init__(self, *args):
+        QtGui.QMainWindow.__init__(self, *args)
+
+        self.centralwidget = QtGui.QWidget(self)
+
+        vbox = QtGui.QVBoxLayout(self.centralwidget)
+
+        hbox = QtGui.QHBoxLayout()
+        label = QtGui.QLabel("Address: ")
+        self.line  = QtGui.QLineEdit("http://www.twistedmatrix.com/")
+        self.connect(self.line, QtCore.SIGNAL('returnPressed()'), self.fetchURL)
+        hbox.addWidget(label)
+        hbox.addWidget(self.line)
+
+        self.web = QWebView()
+
+        vbox.addLayout(hbox)
+        vbox.addWidget(self.web)
+        vbox.setMargin(2)
+        vbox.setSpacing(3)
+
+        self.setCentralWidget(self.centralwidget)
+        self.fetchURL()
+
+    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]
+
+        from twisted.internet import reactor
+        protocol.ClientCreator(reactor, TwistzillaClient, self.web, (host, port, file)).connectTCP(host, port)
+
+    def closeEvent(self, event=None):
+        from twisted.internet import reactor
+        reactor.stop()
+
+
+def main():
+    win = TwistzillaWindow()
+    win.show()
+
+    from twisted.internet import reactor
+    sys.exit(reactor.run())
+
+if __name__ == '__main__':
+    main()
diff --git twisted/internet/qtreactor.py twisted/internet/qtreactor.py
index abbd3ba..a235b1f 100644
--- twisted/internet/qtreactor.py
+++ twisted/internet/qtreactor.py
@@ -1,19 +1,287 @@
-# -*- test-case-name: twisted.internet.test.test_qtreactor -*-
-# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# Copyright (c) 2001-2011 Twisted Matrix Laboratories.
 # See LICENSE for details.
 
+
+"""
+This module provides support for Twisted to be driven by the Qt mainloop.
+
+In order to use this support, simply do the following::
+    |  app = QApplication(sys.argv) # your code to init Qt
+    |  import qt4reactor
+    |  qt4reactor.install()
+    
+alternatively:
+
+    |  from twisted.application import reactors
+    |  reactors.installReactor('qt4')
+
+Then use twisted.internet APIs as usual.  The other methods here are not
+intended to be called directly.
+
+If you don't instantiate a QApplication or QCoreApplication prior to
+installing the reactor, a QCoreApplication will be constructed
+by the reactor.  QCoreApplication does not require a GUI so trial testing
+can occur normally.
+
+Twisted can be initialized after QApplication.exec_() with a call to
+reactor.runReturn().  calling reactor.stop() will unhook twisted but
+leave your Qt application running
+"""
+
+import sys
+from zope.interface import implements
+from twisted.internet.interfaces import IReactorFDSet
+from twisted.python import log
+from twisted.internet import posixbase
+
 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
+    from PySide.QtCore import QSocketNotifier, QObject, SIGNAL, QTimer, QCoreApplication
+    from PySide.QtCore import QEventLoop
 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)
-
-__all__ = ['install']
+    from PyQt4.QtCore import QSocketNotifier, QObject, SIGNAL, QTimer, QCoreApplication
+    from PyQt4.QtCore import QEventLoop
+
+
+
+class TwistedSocketNotifier(QObject):
+    """
+    Connection between an fd event and reader/writer callbacks.
+    """
+
+    def __init__(self, parent, reactor, watcher, socketType):
+        QObject.__init__(self, parent)
+        self.reactor = reactor
+        self.watcher = watcher
+        fd = watcher.fileno()
+        self.notifier = QSocketNotifier(fd, socketType, parent)
+        self.notifier.setEnabled(True)
+        if socketType == QSocketNotifier.Read:
+            self.fn = self.read
+        else:
+            self.fn = self.write
+        QObject.connect(self.notifier, SIGNAL("activated(int)"), self.fn)
+
+
+    def shutdown(self):
+        self.notifier.setEnabled(False)
+        self.disconnect(self.notifier, SIGNAL("activated(int)"), self.fn)
+        self.fn = self.watcher = None
+        self.notifier.deleteLater()
+        self.deleteLater()
+
+
+    def read(self, fd):
+        if not self.watcher:
+            return
+        w = self.watcher
+        # doRead can cause self.shutdown to be called so keep a reference to self.watcher
+        def _read():
+            #Don't call me again, until the data has been read
+            self.notifier.setEnabled(False)
+            why = None
+            try:
+                why = w.doRead()
+                inRead = True
+            except:
+                inRead = False
+                log.err()
+                why = sys.exc_info()[1]
+            if why:
+                self.reactor._disconnectSelectable(w, why, inRead)
+            elif self.watcher:
+                self.notifier.setEnabled(True) # Re enable notification following sucessfull read
+            self.reactor._iterate(fromqt=True)
+        log.callWithLogger(w, _read)
+
+
+    def write(self, sock):
+        if not self.watcher:
+            return
+        w = self.watcher
+        def _write():
+            why = None
+            self.notifier.setEnabled(False)
+            
+            try:
+                why = w.doWrite()
+            except:
+                log.err()
+                why = sys.exc_info()[1]
+            if why:
+                self.reactor._disconnectSelectable(w, why, False)
+            elif self.watcher:
+                self.notifier.setEnabled(True)
+            self.reactor._iterate(fromqt=True)
+        log.callWithLogger(w, _write)
+
+
+
+class QtReactor(posixbase.PosixReactorBase):
+    implements(IReactorFDSet)
+
+
+    def __init__(self):
+        self._reads = {}
+        self._writes = {}
+        self._notifiers = {}
+        self._timer = QTimer()
+        self._timer.setSingleShot(True)
+        QObject.connect(self._timer, SIGNAL("timeout()"), self.iterate)
+
+        if QCoreApplication.startingUp():
+            # Application Object has not been started yet
+            self.qApp=QCoreApplication([])
+            self._ownApp=True
+        else:
+            self.qApp = QCoreApplication.instance()
+            self._ownApp=False
+        self._blockApp = None
+        posixbase.PosixReactorBase.__init__(self)
+
+
+    def _add(self, xer, primary, type):
+        """
+        Private method for adding a descriptor from the event loop.
+
+        It takes care of adding it if  new or modifying it if already added
+        for another state (read -> read/write for example).
+        """
+        if xer not in primary:
+            primary[xer] = TwistedSocketNotifier(None, self, xer, type)
+
+
+    def addReader(self, reader):
+        """
+        Add a FileDescriptor for notification of data available to read.
+        """
+        self._add(reader, self._reads, QSocketNotifier.Read)
+
+
+    def addWriter(self, writer):
+        """
+        Add a FileDescriptor for notification of data available to write.
+        """
+        self._add(writer, self._writes, QSocketNotifier.Write)
+
+
+    def _remove(self, xer, primary):
+        """
+        Private method for removing a descriptor from the event loop.
+
+        It does the inverse job of _add, and also add a check in case of the fd
+        has gone away.
+        """
+        if xer in primary:
+            notifier = primary.pop(xer)
+            notifier.shutdown()
+
+        
+    def removeReader(self, reader):
+        """
+        Remove a Selectable for notification of data available to read.
+        """
+        self._remove(reader, self._reads)
+
+
+    def removeWriter(self, writer):
+        """
+        Remove a Selectable for notification of data available to write.
+        """
+        self._remove(writer, self._writes)
+
+
+    def removeAll(self):
+        """
+        Remove all selectables, and return a list of them.
+        """
+        rv = self._removeAll(self._reads, self._writes)
+        return rv
+
+
+    def getReaders(self):
+        return self._reads.keys()
+
+
+    def getWriters(self):
+        return self._writes.keys()
+
+
+    def callLater(self,howlong, *args, **kargs):
+        rval = super(QtReactor,self).callLater(howlong, *args, **kargs)
+        self.reactorInvocation()
+        return rval
+
+
+    def reactorInvocation(self):
+        self._timer.stop()
+        self._timer.setInterval(0)
+        self._timer.start()
+        
+
+    def _iterate(self, delay=None, fromqt=False):
+        """
+        See twisted.internet.interfaces.IReactorCore.iterate.
+        """
+        self.runUntilCurrent()
+        self.doIteration(delay, fromqt)
+
+
+    iterate = _iterate
+
+
+    def doIteration(self, delay=None, fromqt=False):
+        """
+        This method is called by a Qt timer or by network activity on
+        a file descriptor. 
+
+        If called becuase of network activiy then control should not
+        be handed back to Qt as this would cause recursion.
+        """
+        
+        if not self.running and self._blockApp:
+            self._blockApp.quit()
+
+        self._timer.stop()
+        delay = max(delay, 1)
+        if not fromqt:
+            self.qApp.processEvents(QEventLoop.AllEvents, delay * 1000)
+        if self.timeout() is None:
+            timeout = 0.1
+        elif self.timeout() == 0:
+            timeout = 0
+        else:
+            timeout = self.timeout()
+        self._timer.setInterval(timeout * 1000)
+        self._timer.start()
+
+
+    def runReturn(self, installSignalHandlers=True):
+        self.startRunning(installSignalHandlers=installSignalHandlers)
+        self.reactorInvocation()
+
+
+    def stop(self):
+        super(QtReactor, self).stop()
+        self.iterate(0)
+
+
+    def run(self, installSignalHandlers=True):
+        if self._ownApp:
+            self._blockApp = self.qApp
+        else:
+            self._blockApp = QEventLoop()
+        self.runReturn()
+        self._blockApp.exec_()
+
+
+
+def install():
+    """
+    Install the Qt reactor.
+    """
+    p = QtReactor()
+    from twisted.internet.main import installReactor
+    installReactor(p)
+
+__all__ = ["install"]
 
diff --git twisted/internet/test/reactormixins.py twisted/internet/test/reactormixins.py
index 1ba5dbc..0fa10cd 100644
--- twisted/internet/test/reactormixins.py
+++ twisted/internet/test/reactormixins.py
@@ -51,7 +51,8 @@ class ReactorBuilder:
                  "twisted.internet.kqreactor.KQueueReactor",
                  "twisted.internet.win32eventreactor.Win32Reactor",
                  "twisted.internet.iocpreactor.reactor.IOCPReactor",
-                 "twisted.internet.cfreactor.CFReactor"]
+                 "twisted.internet.cfreactor.CFReactor",
+                 "twisted.internet.qtreactor.QtReactor"]
 
     reactorFactory = None
     originalHandler = None
diff --git twisted/plugins/twisted_qtstub.py twisted/plugins/twisted_qtstub.py
deleted file mode 100644
index 1b9b08a..0000000
--- 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 twisted/plugins/twisted_reactors.py twisted/plugins/twisted_reactors.py
index 428e96c..8b58c80 100644
--- twisted/plugins/twisted_reactors.py
+++ twisted/plugins/twisted_reactors.py
@@ -36,3 +36,6 @@ kqueue = Reactor(
 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')
