changeset 803:ce4fffe8e413

update SC to read rdf graph Ignore-this: 7f6788bae887723c9ac12644c1a382da
author drewp@bigasterisk.com
date Wed, 18 Jul 2012 09:59:10 +0000
parents 5442f5d8979a
children 317c2d2e22da
files bin/rdfdb bin/subcomposer light9/dmxchanedit.py light9/uihelpers.py
diffstat 4 files changed, 93 insertions(+), 126 deletions(-) [+]
line wrap: on
line diff
--- a/bin/rdfdb	Mon Jul 16 22:32:51 2012 +0000
+++ b/bin/rdfdb	Wed Jul 18 09:59:10 2012 +0000
@@ -74,6 +74,11 @@
 and keep updating the same data (e.g. a stream of changes as the user
 drags a slider) and collapse them into a single edit for clarity.
 
+proposed rule feature:
+rdfdb should be able to watch a pair of (sourceFile, rulesFile) and
+rerun the rules when either one changes. Should the sourceFile be able
+to specify its own rules file? That would be easier configuration.
+
 """
 from twisted.internet import reactor
 import twisted.internet.error
--- a/bin/subcomposer	Mon Jul 16 22:32:51 2012 +0000
+++ b/bin/subcomposer	Wed Jul 18 09:59:10 2012 +0000
@@ -5,26 +5,21 @@
 from optparse import OptionParser
 import Tkinter as tk
 import louie as dispatcher
+from twisted.internet import reactor, tksupport, task
 
 import run_local
 from light9.dmxchanedit import Levelbox
-from light9 import dmxclient, Patch, Submaster, showconfig
+from light9 import dmxclient, Patch, Submaster, showconfig, prof
 from light9.uihelpers import toplevelat
+from light9.rdfdb.syncedgraph import SyncedGraph
 
 class Subcomposer(tk.Frame):
-    def __init__(self, master, levelboxopts=None, dmxdummy=0, numchannels=72,
-        use_persistentlevels=0):
+    def __init__(self, master, graph):
         tk.Frame.__init__(self, master, bg='black')
-        self.dmxdummy = dmxdummy
-        self.numchannels = numchannels
+        self.graph = graph
 
-        self.levels = [0]*numchannels # levels should never get overwritten, just edited
-
-        self.levelbox = Levelbox(self, num_channels=numchannels)
+        self.levelbox = Levelbox(self, graph)
         self.levelbox.pack(side='top')
-        # the dmx levels we edit and output, range is 0..1 (dmx chan 1 is
-        # the 0 element)
-        self.levelbox.setlevels(self.levels)
 
         self.savebox = EntryCommand(self, cmd=self.savenewsub)
         self.savebox.pack(side='top')
@@ -33,31 +28,17 @@
         self.loadbox.pack(side='top')
 
         def alltozero():
-            self.set_levels([0] * self.numchannels)
-            dispatcher.send("levelchanged")
+            for lev in self.levelbox.levels:
+                lev.setlevel(0)
 
         tk.Button(self, text="all to zero", command=alltozero).pack(side='top')
 
-        dispatcher.connect(self.levelchanged,"levelchanged")
-        dispatcher.connect(self.sendupdate,"levelchanged")
-
-        if use_persistentlevels:
-            self.persistentlevels()
-
-        self.lastupdate=0 # time we last sent to dmx
-
-        self.lastsent=[] # copy of levels
+        dispatcher.connect(self.sendupdate, "levelchanged")
 
     def fill_both_boxes(self, subname):
         for box in [self.savebox, self.loadbox]:
             box.set(subname)
 
-    def persistentlevels(self):
-        """adjusts levels from subcomposer.savedlevels, if possible; and
-        arranges to save the levels in that file upon exit"""
-        self.load_levels()
-        atexit.register(self.save_levels)
-
     def save_levels(self, *args):
         levelfile = file("subcomposer.savedlevels","w")
         levelfile.write(" ".join(map(str, self.levels)))
@@ -70,13 +51,6 @@
         except IOError:
             pass
 
-    def levelchanged(self, channel=None, newlevel=None):
-        if channel is not None and newlevel is not None:
-            if channel>len(self.levels):
-                return
-            self.levels[channel-1]=max(0,min(1,float(newlevel)))
-        self.levelbox.setlevels(self.levels)
-
     def savenewsub(self, subname):
         leveldict={}
         for i,lev in zip(range(len(self.levels)),self.levels):
@@ -92,23 +66,20 @@
         self.set_levels(s.get_dmx_list())
         dispatcher.send("levelchanged")
 
+    def toDmxLevels(self):
+        # the dmx levels we edit and output, range is 0..1 (dmx chan 1 is
+        # the 0 element)
+        out = {}
+        for lev in self.levelbox.levels:
+            out[lev.channelnum] = lev.currentlevel
+        if not out:
+            return []
+        
+        return [out.get(i, 0) for i in range(max(out.keys()) + 1)]
+     
     def sendupdate(self):
-        if not self.dmxdummy:
-            dmxclient.outputlevels(self.levels)
-            self.lastupdate = time.time()
-            self.lastsent = self.levels[:]
+        dmxclient.outputlevels(self.toDmxLevels(), twisted=True)
 
-    def considersendupdate(self, use_after_loop=0):
-        """If use_after_loop is true, it is the period of the after loop."""
-        if self.lastsent != self.levels or time.time() > self.lastupdate + 1:
-            self.sendupdate()
-        if use_after_loop:
-            self.after(use_after_loop, self.considersendupdate, use_after_loop)
-
-    def set_levels(self, levels):
-        oldLen = len(self.levels)
-        self.levels[:] = levels + [0] * (oldLen - len(levels))
-        dispatcher.send("levelchanged")
 
 class EntryCommand(tk.Frame):
     def __init__(self, master, verb="Save", cmd=None):
@@ -131,23 +102,6 @@
         self.entry.insert(0, text)
 
 
-def open_sub_editing_window(subname, use_mainloop=1, dmxdummy=0):
-    if use_mainloop:
-        toplevel = tk.Tk()
-    else:
-        toplevel = tk.Toplevel()
-    if dmxdummy:
-        dummy_str = ' (dummy)'
-    else:
-        dummy_str = ''
-    toplevel.title("Subcomposer: %s%s" % (subname, dummy_str))
-    sc = Subcomposer(toplevel, use_persistentlevels=0, dmxdummy=dmxdummy)
-    sc.pack(fill='both', expand=1)
-    sc.loadsub(subname)
-    sc.considersendupdate(use_after_loop=10)
-    if use_mainloop:
-        tk.mainloop()
-
 #############################
 
 if __name__ == "__main__":
@@ -162,10 +116,9 @@
     if not opts.no_geometry:
         toplevelat("subcomposer", root)
 
-    sc = Subcomposer(root, dmxdummy=0,
-                     #numchannels=276 # use this to see all the skyline dims
-                     #numchannels=118
-                     )
+    graph = SyncedGraph("subcomposer")
+
+    sc = Subcomposer(root, graph)
     sc.pack()
 
     tk.Label(root,text="Bindings: B1 adjust level; B2 set full; B3 instant bump",
@@ -176,11 +129,7 @@
         sc.loadsub(args[0])
         sc.fill_both_boxes(args[0])
 
-    while 1:
-        try:
-            root.update()
-        except tk.TclError:
-            break
+    task.LoopingCall(sc.sendupdate).start(1)
 
-        sc.considersendupdate()
-        time.sleep(.01)
+    tksupport.install(root,ms=10)
+    prof.run(reactor.run, profile=False)
--- a/light9/dmxchanedit.py	Mon Jul 16 22:32:51 2012 +0000
+++ b/light9/dmxchanedit.py	Wed Jul 18 09:59:10 2012 +0000
@@ -3,11 +3,23 @@
 widget to show all dmx channel levels and allow editing. levels might
 not actually match what dmxserver is outputting.
 
+proposal for new focus and edit system:
+- rows can be selected
+- the chan number or label can be used to select rows. dragging over rows brings all of them into or out of the current selection
+- numbers drag up and down (like today)
+- if you drag a number in a selected row, all the selected numbers change
+- if you start dragging a number in an unselected row, your row becomes the new selection and then the edit works
+
+
+proposal for new attribute system:
+- we always want to plan some attributes for each light: where to center; what stage to cover; what color gel to apply; whether the light is burned out
+- we have to stop packing these into the names. Names should be like 'b33' or 'blue3' or just '44'. maybe 'blacklight'.
+
 """
 from __future__ import nested_scopes,division
 import Tkinter as tk
-from light9 import Patch
-from light9.uihelpers import make_frame, colorlabel, eventtoparent
+from rdflib import RDF
+from light9.namespaces import L9
 import louie as dispatcher
 
 stdfont = ('Arial', 9)
@@ -18,18 +30,27 @@
      return col
 
 class Onelevel(tk.Frame):
-    """a name/level pair"""
-    def __init__(self, parent, channelnum):
-        """channelnum is 1..68, like the real dmx"""
+    """a name/level pair
+
+    source data is like this:
+        ch:b11-c     a :Channel;
+         :output dmx:c54;
+         rdfs:label "b11-c" .
+    """
+    def __init__(self, parent, graph, channelUri):
         tk.Frame.__init__(self,parent, height=20)
+        self.graph = graph
+        self.uri = channelUri
+        self.currentlevel = 0 # the level we're displaying, 0..1
 
-        self.channelnum=channelnum
-        self.currentlevel=0 # the level we're displaying, 0..1
-        
+        # no statement yet
+        self.channelnum = int(
+             self.graph.value(self.uri, L9['output']).rsplit('/c')[-1])
+
         # 3 widgets, left-to-right:
 
         # channel number -- will turn yellow when being altered
-        self.num_lab = tk.Label(self, text=str(channelnum),
+        self.num_lab = tk.Label(self, text=str(self.channelnum),
                                 width=3, bg='grey40', 
                                 fg='white',
                                 font=stdfont,
@@ -37,7 +58,7 @@
         self.num_lab.pack(side='left')
 
         # text description of channel
-        self.desc_lab=tk.Label(self, text=Patch.get_channel_name(channelnum),
+        self.desc_lab=tk.Label(self, text=self.graph.label(self.uri),
                                width=14,
                                font=stdfont,
                                anchor='w',
@@ -47,16 +68,9 @@
 
         # current level of channel, shows intensity with color
         self.level_lab = tk.Label(self, width=3, bg='lightBlue',
-                                  anchor='e', 
+                                  anchor='e', font=stdfont,
                                   padx=1, pady=0, bd=0, height=1)
         self.level_lab.pack(side='left')
-        # setting the font in the label somehow makes tk run a low
-        # slower. Magically, startup is much faster if tk can layout
-        # the window with some standard font in the rows (so the row
-        # heights are all fixed and taller?), and then I replace the
-        # last font. Tk resizes the window faster than you can see,
-        # but startup is still fast. Very weird.
-        self.after(1, lambda: self.level_lab.config(font=stdfont))
 
         self.setlevel(0)
         self.setupmousebindings()
@@ -68,15 +82,15 @@
             self._start_lev=self.currentlevel
         def b1motion(ev):
             delta=self._start_y-ev.y
-            self.changelevel(self._start_lev+delta*.005)
+            self.setlevel(self._start_lev+delta*.005)
         def b1up(ev):
             self.desc_lab.config(bg='black')
         def b3up(ev):
-            self.changelevel(0.0)
+            self.setlevel(0.0)
         def b3down(ev):
-            self.changelevel(1.0)
+            self.setlevel(1.0)
         def b2down(ev): # same thing for now
-            self.changelevel(1.0)
+            self.setlevel(1.0)
 
         # make the buttons work in the child windows
         for w in self.winfo_children():
@@ -88,7 +102,6 @@
                            ('<ButtonPress-3>', b3down)):
 
                 w.bind(e,func)
-#                w.bind(e,lambda ev,e=e: eventtoparent(ev,e))
         
     def colorlabel(self):
         """color the level label based on its own text (which is 0..100)"""
@@ -96,39 +109,44 @@
         lev=float(txt)/100
         self.level_lab.config(bg=gradient(lev))
 
-    def setlevel(self,newlev):
+    def setlevel(self, newlev):
         """the main program is telling us to change our
         display. newlev is 0..1"""
-        self.currentlevel=newlev
-        newlev="%d"%(newlev*100)
+        self.currentlevel = min(1, max(0, newlev))
+        newlev = "%d" % (self.currentlevel * 100)
         olddisplay=self.level_lab.cget('text')
         if newlev!=olddisplay:
             self.level_lab.config(text=newlev)
             self.colorlabel()
+        dispatcher.send("levelchanged", channel=self.uri, newlevel=newlev)
 
-    def getlevel(self):
-        """returns currently displayed level, 0..1"""
-        return self.currentlevel
-
-    def changelevel(self,newlev):
+class Levelbox(tk.Frame):
+    def __init__(self, parent, graph):
+        tk.Frame.__init__(self,parent)
 
-        """the user is adjusting the level on this widget.  the main
-        program needs to hear about it. then the main program will
-        call setlevel()"""
+        self.graph = graph
+        graph.addHandler(self.updateChannels)
 
-        dispatcher.send("levelchanged",channel=self.channelnum,newlevel=newlev)
-    
-class Levelbox(tk.Frame):
-    def __init__(self, parent, num_channels=68):
-        tk.Frame.__init__(self,parent)
+    def updateChannels(self):
+        """(re)make Onelevel boxes for the defined channels"""
+
+        [ch.destroy() for ch in self.winfo_children()]
         self.levels = [] # Onelevel objects
 
-        rows = 37
-        frames = [make_frame(self) for x in range((num_channels // rows) + 1)]
+        chans = list(self.graph.subjects(RDF.type, L9.Channel))
+        cols = 2
+        rows = len(chans) // cols
 
-        for channel in range(1, num_channels+1):
+        def make_frame(parent):
+             f = tk.Frame(parent, bd=0, bg='black')
+             f.pack(side='left')
+             return f
+        
+        columnFrames = [make_frame(self) for x in range(cols)]
+
+        for i, channel in enumerate(chans): # sort?
             # frame for this channel
-            f = Onelevel(frames[channel // rows],channel)
+            f = Onelevel(columnFrames[i // rows], self.graph, channel)
 
             self.levels.append(f)
             f.pack(side='top')
--- a/light9/uihelpers.py	Mon Jul 16 22:32:51 2012 +0000
+++ b/light9/uihelpers.py	Wed Jul 18 09:59:10 2012 +0000
@@ -15,11 +15,6 @@
     'scenes' : '504x198+462+12',
 }
 
-def make_frame(parent):
-    f = Frame(parent, bd=0, bg='black')
-    f.pack(side='left')
-    return f
-
 def bindkeys(root,key, func):
     root.bind(key, func)
     for w in root.winfo_children():