changeset 463:60b49f6c2027

start porting lightsim to qt
author drewp@bigasterisk.com
date Mon, 01 Sep 2008 00:41:29 +0000
parents cd4d7b878550
children c901cff64987
files bin/lightsim lib/qt4reactor.py lightsim/openglsim.py
diffstat 3 files changed, 310 insertions(+), 68 deletions(-) [+]
line wrap: on
line diff
--- a/bin/lightsim	Wed Jun 18 07:19:53 2008 +0000
+++ b/bin/lightsim	Mon Sep 01 00:41:29 2008 +0000
@@ -2,31 +2,30 @@
 
 from __future__ import division
 import run_local
+import xmlrpclib, sys, logging
+
+sys.path.append("lib")
+import qt4reactor
+qt4reactor.install()
 
 from rdflib import Literal, URIRef
-from twisted.internet import reactor, tksupport
+from twisted.internet import reactor
 from twisted.internet.task import LoopingCall
 from twisted.web.xmlrpc import Proxy
-import xmlrpclib, sys, logging
+from louie import dispatcher
+from PyQt4 import QtCore, QtGui, QtOpenGL
+from OpenGL.GL import *
+from OpenGL.GLU import *
+
+from light9 import networking, Patch, showconfig, dmxclient, updatefreq, prof
+from light9.namespaces import L9
+from lightsim.openglsim import Surface
+
 log = logging.getLogger()
 logging.basicConfig(format="%(asctime)s %(levelname)-5s %(name)s %(filename)s:%(lineno)d: %(message)s")
 log.setLevel(logging.DEBUG)
-import Tkinter as tk
-from light9 import networking, Patch, showconfig, dmxclient, updatefreq, prof
-from light9.namespaces import L9
-from louie import dispatcher
 
-try:
-    from OpenGL import Tk as Togl
-    from OpenGL.GL import *
-except ImportError:
-    sys.path.append("/usr/lib/python2.4/site-packages/OpenGL/Tk/linux2-tk8.4")
-    from OpenGL.GL import *
-    import Togl
-  
-from lightsim.openglsim import Surface
-
-def poll(graph, serv, pollFreq):
+def poll(graph, serv, pollFreq, oglSurface):
     pollFreq.update()
     dispatcher.send("status", key="pollFreq", value=str(pollFreq))
     d = serv.callRemote("currentlevels", dmxclient._id)
@@ -45,11 +44,11 @@
                 for imgPath in graph.objects(lyr, L9['path']):
                     level[str(imgPath)] = lev
 
-        ogl.newLevels(levels=level)
+        oglSurface.newLevels(levels=level)
     d.addCallback(received)
     return d
 
-class StatusKeys(tk.Frame):
+class StatusKeys(object):
     # watch out- this might be an accidental redo of what curvecalc
     # has. Or, maybe CC should use this obj
     def __init__(self, master):
@@ -69,31 +68,51 @@
             row, lab = self.row[key]
             lab.config(text=value)
 
-root = tk.Frame()
-root.pack(expand=True, fill='both')
-pollFreq = updatefreq.Updatefreq(samples=5)
-graph = showconfig.getGraph()
-filenames = []
-for lyr in graph.objects(None, L9['previewLayer']):
-    for p in graph.objects(lyr, L9['path']):
-        filenames.append(str(p))
-
-ogl = Surface(root, filenames, width=120, height=80, imgRescaleTo=128)
-ogl.pack(side='top', expand=True, fill='both')
-
-sk = StatusKeys(root)
-sk.pack(side='top', fill='x')
 
 
+class Window(QtGui.QMainWindow):
+    def __init__(self, filenames):
+        QtGui.QMainWindow.__init__(self, None)
 
-serv = Proxy(networking.dmxServerUrl())
-LoopingCall(poll, graph, serv, pollFreq).start(.1)
+        self.w = QtGui.QWidget()
+        self.setCentralWidget(self.w)
+        
+        self.glWidget = Surface(self, filenames,
+                                width=120*3, height=80*3,
+                                imgRescaleTo=128)
+
+        self.glWidget.newLevels(levels={
+            "lightsim/skyline/front-left.png" : .4,
+            "lightsim/skyline/front-right.png" : .8})
+
+        mainLayout = QtGui.QHBoxLayout()
+        mainLayout.addWidget(self.glWidget)
+        self.w.setLayout(mainLayout)
+
+        self.setWindowTitle(dmxclient._id)
 
-top = root.winfo_toplevel()
-top.wm_title(dmxclient._id)
-top.bind("<Control-Key-q>",lambda ev: reactor.stop)
-top.bind("<Destroy>",lambda ev: reactor.stop)
-top.protocol('WM_DELETE_WINDOW', reactor.stop)
-tksupport.install(ogl, ms=20)
+if __name__ == '__main__':
+    app = reactor.qApp
+
+    graph = showconfig.getGraph()
+    filenames = []
+    for lyr in graph.objects(None, L9['previewLayer']):
+        for p in graph.objects(lyr, L9['path']):
+            filenames.append(str(p))
+    filenames.append("lightsim/skyline/front-left.png") # test
+    filenames.append("lightsim/skyline/front-right.png") # test
 
-prof.run(reactor.run, profile=False)
+    window = Window(filenames)
+    window.show()
+
+    serv = Proxy(networking.dmxServerUrl())
+    pollFreq = updatefreq.Updatefreq(samples=5)
+    LoopingCall(poll, graph, serv, pollFreq, window.glWidget).start(.1)
+
+    reactor.run()
+
+
+######################################################
+#sk = StatusKeys(root)
+#sk.pack(side='top', fill='x')
+#prof.run(reactor.run, profile=False)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/qt4reactor.py	Mon Sep 01 00:41:29 2008 +0000
@@ -0,0 +1,213 @@
+# http://twistedmatrix.com/trac/browser/sandbox/therve/qt4reactor.py
+# with some fixes by drewp
+
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+This module provides support for Twisted to interact with the PyQt mainloop.
+
+In order to use this support, simply do the following::
+
+    |  import qt4reactor
+    |  qt4reactor.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<mailto:twisted@itamarst.org>}
+Port to QT4: U{Gabe Rudy<mailto:rudy@goldenhelix.com>}
+"""
+
+__all__ = ['install']
+
+
+import sys
+
+from zope.interface import implements
+
+from PyQt4.QtCore import QSocketNotifier, QObject, SIGNAL, QTimer
+from PyQt4.QtGui import QApplication
+
+from twisted.internet.interfaces import IReactorFDSet
+from twisted.python import log
+from twisted.internet.posixbase import PosixReactorBase
+
+
+
+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):
+        w = self.watcher
+        def _read():
+            why = None
+            try:
+                why = w.doRead()
+            except:
+                log.err()
+                why = sys.exc_info()[1]
+            if why:
+                self.reactor._disconnectSelectable(w, why, True)
+        log.callWithLogger(w, _read)
+        self.reactor.simulate()
+
+
+    def write(self, sock):
+        w = self.watcher
+        def _write():
+            why = None
+            self.setEnabled(0)
+            try:
+                why = w.doWrite()
+            except:
+                log.err()
+                why = sys.exc_info()[1]
+            if why:
+                self.reactor._disconnectSelectable(w, why, False)
+            elif self.watcher:
+                self.setEnabled(1)
+        log.callWithLogger(w, _write)
+        self.reactor.simulate()
+
+
+
+class QTReactor(PosixReactorBase):
+    """
+    Qt based reactor.
+    """
+    implements(IReactorFDSet)
+
+    # Reference to a DelayedCall for self.crash() when the reactor is
+    # entered through .iterate()
+    _crashCall = None
+
+    _timer = None
+
+    def __init__(self, app=None):
+        self._reads = {}
+        self._writes = {}
+        if app is None:
+            app = QApplication([])
+        self.qApp = app
+        PosixReactorBase.__init__(self)
+        self.addSystemEventTrigger('after', 'shutdown', self.cleanup)
+            
+
+
+    def addReader(self, reader):
+        if not reader in self._reads:
+            self._reads[reader] = TwistedSocketNotifier(self, reader,
+                                                       QSocketNotifier.Read)
+
+
+    def addWriter(self, writer):
+        if not writer in self._writes:
+            self._writes[writer] = TwistedSocketNotifier(self, writer,
+                                                        QSocketNotifier.Write)
+
+
+    def removeReader(self, reader):
+        if reader in self._reads:
+            self._reads[reader].shutdown()
+            del self._reads[reader]
+
+
+    def removeWriter(self, writer):
+        if writer in self._writes:
+            self._writes[writer].shutdown()
+            del self._writes[writer]
+
+
+    def removeAll(self):
+        return self._removeAll(self._reads, self._writes)
+
+
+    def getReaders(self):
+        return self._reads.keys()
+
+
+    def getWriters(self):
+        return self._writes.keys()
+
+
+    def simulate(self):
+        self._lastTimer = self._timer # put off the __del__
+        if self._timer is not None:
+            self._timer.stop()
+            self._timer = None
+
+        if not self.running:
+            self.qApp.exit()
+            return
+        self.runUntilCurrent()
+
+        if self._crashCall is not None:
+            self._crashCall.reset(0)
+
+        timeout = self.timeout()
+        if timeout is None:
+            timeout = 1.0
+        timeout = min(timeout, 0.01) * 1010
+
+        if self._timer is None:
+            self._timer = QTimer()
+            self._timer.setObjectName("simulateTimer")
+            QObject.connect(self._timer, SIGNAL("timeout()"), self.simulate)
+        self._timer.start(timeout)
+
+    def cleanup(self):
+        if self._timer is not None:
+            self._timer.stop()
+            self._timer = None
+
+
+    def iterate(self, delay=0.0):
+        self._crashCall = self.callLater(delay, self._crash)
+        self.run()
+
+
+    def mainLoop(self):
+        self.simulate()
+        self.qApp.exec_()
+
+
+    def _crash(self):
+        if self._crashCall is not None:
+            if self._crashCall.active():
+                self._crashCall.cancel()
+            self._crashCall = None
+        self.running = False
+
+
+
+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)
--- a/lightsim/openglsim.py	Wed Jun 18 07:19:53 2008 +0000
+++ b/lightsim/openglsim.py	Mon Sep 01 00:41:29 2008 +0000
@@ -7,14 +7,9 @@
 import Tkinter as tk
 import Image
 from louie import dispatcher
-try:
-    from OpenGL import Tk as Togl
-    from OpenGL.GL import *
-except ImportError:
-    sys.path.append("/usr/lib/python2.4/site-packages/OpenGL/Tk/linux2-tk8.4")
-    from OpenGL.GL import *
-    import Togl
-  
+
+from PyQt4 import QtCore, QtOpenGL
+from OpenGL.GL import *
 
 def xxxdrawWithAlpha(imgString, w, h, alpha):
     # this one should be faster because GL does the alpha adjust, but
@@ -26,22 +21,21 @@
     #glBlendColor(1, 1, 1, mag) # needs ARB_imaging
     glDrawPixels(w, h, GL_RGBA, GL_UNSIGNED_BYTE, imgString)
 
-class Surface(Togl.Opengl):
+class Surface(QtOpenGL.QGLWidget):
     """widget that adds multiple image files together with adjustable scales"""
-    def __init__(self, master, filenames, width=512, height=270,
+    def __init__(self, parent, filenames, width=512, height=270,
                  imgRescaleTo=None):
         """
         imgRescaleTo can be a length of pixels to reduce all the input
         images into. Try 64 for a low res drawing.
         """
-        Togl.Opengl.__init__(self, master=master, width=width,
-                             height=height, double=True, depth=0)
-        self.width, self.height = width, height
+        QtOpenGL.QGLWidget.__init__(self, parent)
 
         self.levels = {} # filename : brightness
   
         self.image = {} # filename : imgstr
         for filename in filenames:
+            print "open", filename
             im = Image.open(filename)
             if imgRescaleTo:
                 im.thumbnail((imgRescaleTo, imgRescaleTo))
@@ -50,20 +44,22 @@
             self.imageHeight = im.size[1]
             self.image[filename] = im.convert("RGBA").tostring()
   
-        self.set_centerpoint(0, 0, 0)
-  
+        #self.set_centerpoint(0, 0, 0)
+
+    def initializeGL(self): 
         glDisable(GL_CULL_FACE)
         glShadeModel(GL_FLAT)
         print 'GL_ARB_imaging', 'GL_ARB_imaging' in glGetString(GL_EXTENSIONS)
         import OpenGL
         print OpenGL.__version__
-
-        self.bind("<Configure>", self.configure)
+       
+#    def minimumSizeHint(self):
+#        return QtCore.QSize(512, 512)
 
-    def configure(self, ev):
-        self.width, self.height = ev.width, ev.height
-  
-    def redraw(self, event=None):
+#    def sizeHint(self):
+#        return QtCore.QSize(512, 512)
+
+    def paintGL(self):
         """you set self.levels to dict and call tkRedraw"""
         assert 'GL_ARB_imaging' in glGetString(GL_EXTENSIONS).split()
         start = time.time()
@@ -80,7 +76,6 @@
         # drawing to glAccum might be good
         layerTimes = []
         for filename, mag in self.levels.items():
-            #print "pic %s at %f" % (filename, mag)
             t = time.time()
             self.drawWithAlpha(self.image[filename],
                                self.imageWidth, self.imageHeight, mag)
@@ -109,8 +104,8 @@
         # this right now.
         #glBlendFunc(GL_CONSTANT_COLOR, GL_ONE)
         #glBlendColor(.8, .5, .5, .5)
-        
-        glPixelZoom(self.width / w, self.height / h)
+
+        glPixelZoom(self.width() / w, self.height() / h)
         glDrawPixels(w, h,
                      GL_RGBA, GL_UNSIGNED_BYTE, ar.tostring())
         #print "  draw", time.time() - t
@@ -118,8 +113,23 @@
     def newLevels(self, event=None, levels=None):
         if levels != self.levels:
             self.levels = levels
-            self.tkRedraw()
-  
+            self.updateGL()
+
+
+##     def mousePressEvent(self, event):
+##         self.lastPos = QtCore.QPoint(event.pos())
+
+##     def mouseMoveEvent(self, event):
+##         dx = event.x() - self.lastPos.x()
+##         dy = event.y() - self.lastPos.y()
+##         rot = (.25*dy, .25*dx, 0)
+##         if event.buttons() & QtCore.Qt.LeftButton:
+##             self.emit(QtCore.SIGNAL("rotationChanged"), rot)
+##         elif event.buttons() & QtCore.Qt.MidButton:
+##             self.emit(QtCore.SIGNAL("camDistChanged"), .01*dy)            
+
+##         self.lastPos = QtCore.QPoint(event.pos())
+
 def main():
     root = tk.Frame()
     root.pack(expand=True, fill='both')