Changeset - 167a61d3cfbf
[Not reviewed]
default
0 4 0
Drew Perttula - 12 years ago 2013-06-06 08:23:17
drewp@bigasterisk.com
subcomposer put local subs into the graph with a type edge.
Ignore-this: a0c1ef69684786e4758b9ecf10927d3b
4 files changed with 20 insertions and 10 deletions:
0 comments (0 inline, 0 general)
bin/run_local.py
Show inline comments
 
@@ -9,24 +9,25 @@ from twisted.python.failure import Failu
 

	
 
import Tkinter
 
def rce(self, exc, val, tb):
 
    sys.stderr.write("Exception in Tkinter callback\n")
 
    if True:
 
        sys.excepthook(exc, val, tb)
 
    else:
 
        Failure(val, exc, tb).printDetailedTraceback()
 
Tkinter.Tk.report_callback_exception = rce
 

	
 
import coloredlogs, logging, time
 
log = logging.getLogger()
 

	
 
class CSH(coloredlogs.ColoredStreamHandler):
 
    def render_timestamp(self, created):
 
        return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(created)) + (
 
            "%.3f" % (created % 1)).lstrip('0')
 

	
 
    def render_name(self, name):
 
        return name
 

	
 
log.addHandler(CSH(show_hostname=False, show_name=True))
 

	
 

	
 

	
bin/subcomposer
Show inline comments
 
#!bin/python
 
"""
 
subcomposer
 
  session
 
  observable(currentSub), a Submaster which tracks the graph
 

	
 
    EditChoice widget
 
      can change currentSub to another sub
 

	
 
    Levelbox widget
 
      watch observable(currentSub) for a new sub, and also watch currentSub for edits to push to the OneLevel widgets
 

	
 
        OneLevel widget
 
          UI edits are caught here and go all the way back to currentSub
 

	
 

	
 
"""
 
from __future__ import division, nested_scopes
 
import time, logging
 
from optparse import OptionParser
 
import logging
 
import Tkinter as tk
 
import louie as dispatcher
 
from twisted.internet import reactor, tksupport, task
 
from rdflib import URIRef
 
from rdflib import URIRef, RDF
 

	
 
from run_local import log
 
log.setLevel(logging.DEBUG)
 

	
 
from light9.dmxchanedit import Levelbox
 
from light9 import dmxclient, Patch, Submaster, prof
 
from light9 import dmxclient, Submaster, prof
 
from light9.Patch import get_channel_name
 
from light9.uihelpers import toplevelat
 
from light9.rdfdb.syncedgraph import SyncedGraph
 
from light9.rdfdb import clientsession
 
from light9.tkdnd import initTkdnd
 
from light9.namespaces import L9
 
from light9.rdfdb.patch import Patch
 
from light9.observable import Observable
 
from light9.editchoice import EditChoice, Local
 

	
 

	
 
class Subcomposer(tk.Frame):
 
    """
 
    <session> l9:currentSub ?sub is the URI of the sub we're tied to for displaying and
 
    editing. If we don't have a currentSub, then we're actually
 
    editing a session-local sub called <session> l9:currentSub <sessionLocalSub>
 

	
 
    I'm not sure that Locals should even be PersistentSubmaster with
 
    uri and graph storage, but I think that way is making fewer
 
    special cases.
 

	
 
    Contains an EditChoice widget
 

	
 
    Dependencies:
 

	
 
      graph (?session :currentSub ?s) -> self.currentSub
 
      self.currentSub -> graph
 
      self.currentSub -> self._currentChoice (which might be Local)
 
      self._currentChoice (which might be Local) -> self.currentSub
 

	
 
      inside the current sub:
 
      graph -> Submaster levels (handled in Submaster)
 
      Submaster levels -> OneLevel widget
 
      OneLevel widget -> Submaster.editLevel
 
      Submaster.editLevel -> graph (handled in Submaster)
 

	
 
    """
 
    def __init__(self, master, graph, session):
 
        tk.Frame.__init__(self, master, bg='black')
 
        self.graph = graph
 
        self.session = session
 

	
 
        # this is a URIRef or Local
 
        self._currentChoice = Observable(Local)
 

	
 
        # this is a PersistentSubmaster (even for local)
 
        self.currentSub = Observable(Submaster.PersistentSubmaster(
 
                    graph, self.makeLocal()))
 

	
 
        def pc(val):
 
            log.info("change viewed sub to %s", val)
 
        self._currentChoice.subscribe(pc)
 

	
 
        EditChoice(self, self.graph, self._currentChoice).frame.pack(side='top')
 
        self.setupSubChoiceLinks()
 
        self.setupLevelboxUi()
 
        
 
    def setupSubChoiceLinks(self):
 

	
 
        graph = self.graph
 
        def ann():
 
            print "currently: session=%s currentSub=%r _currentChoice=%r" % (
 
                self.session, self.currentSub(), self._currentChoice())
 

	
 
        @graph.addHandler
 
        def graphChanged():
 
            s = graph.value(self.session, L9['currentSub'])
 
            if s is None:
 
                s = self.makeLocal()
 
            self.currentSub(Submaster.PersistentSubmaster(graph, s))
 

	
 
        @self.currentSub.subscribe
 
        def subChanged(newSub):
 
            if newSub is None:
 
                graph.patchObject(self.session,
 
                                  self.session, L9['currentSub'], None)
 
                return
 
            self.sendupdate()
 
            graph.patchObject(self.session,
 
                              self.session, L9['currentSub'], newSub.uri)
 

	
 
            if newSub and 'local' in newSub.uri: # wrong- use rdf:type or something?
 
                self._currentChoice(Local)
 
            else:
 
                # i think right here is the point that the last local
 
                # becomes garbage, and we could clean it up. 
 
                self._currentChoice(newSub.uri)
 

	
 
        dispatcher.connect(self.levelsChanged, "sub levels changed")
 
            
 
        @self._currentChoice.subscribe
 
        def choiceChanged(newChoice):
 
            if newChoice is Local:
 
                newChoice = self.makeLocal()
 
            if newChoice is not None:
 
                self.currentSub(Submaster.PersistentSubmaster(
 
                    graph, newChoice))
 

	
 
    def levelsChanged(self, sub):
 
        if sub == self.currentSub():
 
            self.sendupdate()
 
        
 
    def makeLocal(self):
 
        # todo: put a type on this, so subChanged can identify it right
 
        # todo: where will these get stored, or are they local to this
 
        # subcomposer process and don't use PersistentSubmaster at all?
 
        return URIRef("http://local/%s" % time.time())
 

	
 
        new = URIRef("http://local/%s" % time.time())
 
        self.graph.patch(Patch(addQuads=[(new, RDF.type, L9['Submaster'], self.session)]))
 
        
 
        return new
 
        
 
    def setupLevelboxUi(self):
 
        self.levelbox = Levelbox(self, graph, self.currentSub)
 
        self.levelbox = Levelbox(self, self.graph, self.currentSub)
 
        self.levelbox.pack(side='top')
 

	
 
        tk.Button(self, text="All to zero",
 
             command=lambda *args: self.currentSub().clear()).pack(side='top')
 

	
 
    def savenewsub(self, subname):
 
        leveldict={}
 
        for i,lev in zip(range(len(self.levels)),self.levels):
 
            if lev!=0:
 
                leveldict[Patch.get_channel_name(i+1)]=lev
 
                leveldict[get_channel_name(i+1)]=lev
 

	
 
        s=Submaster.Submaster(subname,leveldict=leveldict)
 
        s.save()
 

	
 
    def sendupdate(self):
 
        d = self.currentSub().get_dmx_list()
 
        dmxclient.outputlevels(d, twisted=True)
 

	
 

	
 
def launch(opts, args, root, graph, session):
 
    if not opts.no_geometry:
 
        toplevelat("subcomposer - %s" % opts.session, root, graph, session)
 

	
 
    sc = Subcomposer(root, graph, session)
 
    sc.pack()
 

	
 
    tk.Label(root,text="Bindings: B1 adjust level; B2 set full; B3 instant bump",
 
             font="Helvetica -12 italic",anchor='w').pack(side='top',fill='x')
 

	
 
    if len(args) == 1:
 
        # it might be a little too weird that cmdline arg to this
 
        # process changes anything else in the same session. But also
 
        # I'm not sure who would ever make 2 subcomposers of the same
 
        # session (except when quitting and restarting, to get the
 
        # same window pos), so maybe it doesn't matter. But still,
 
        # this tool should probably default to making new sessions
 
        # usually instead of loading the same one
 
        graph.patchObject(session,
 
                          session, L9['currentSub'], URIRef(args[0]))
 

	
 
    task.LoopingCall(sc.sendupdate).start(10)
 

	
 

	
 
#############################
 

	
 
if __name__ == "__main__":
 
    parser = OptionParser(usage="%prog [suburi]")
 
    parser.add_option('--no-geometry', action='store_true',
 
                      help="don't save/restore window geometry")
 
    clientsession.add_option(parser)
 
    opts, args = parser.parse_args()
 

	
 
    logging.basicConfig(level=logging.DEBUG)
 

	
 
    root=tk.Tk()
 
    root.config(bg='black')
 
    root.tk_setPalette("#004633")
 

	
 
    initTkdnd(root.tk, 'tkdnd/trunk/')
 

	
 
    graph = SyncedGraph("subcomposer")
 
    session = clientsession.getUri('subcomposer', opts)
 

	
 
    graph.initiallySynced.addCallback(lambda _: launch(opts, args, root, graph, session))
 

	
 
    root.protocol('WM_DELETE_WINDOW', reactor.stop)
 
    tksupport.install(root,ms=10)
 
    prof.run(reactor.run, profile=False)
light9/Submaster.py
Show inline comments
 
@@ -112,52 +112,53 @@ class Submaster(object):
 
        NOTE: You should only crossfade between normalized submasters."""
 
        otherlevels = othersub.get_levels()
 
        keys_set = {}
 
        for k in self.levels.keys() + otherlevels.keys():
 
            keys_set[k] = 1
 
        all_keys = keys_set.keys()
 

	
 
        xfaded_sub = Submaster("xfade", {})
 
        for k in all_keys:
 
            xfaded_sub.set_level(k,
 
                                 linear_fade(self.levels.get(k, 0),
 
                                             otherlevels.get(k, 0),
 
                                             amount))
 

	
 
        return xfaded_sub
 

	
 
class PersistentSubmaster(Submaster):
 
    def __init__(self, graph, uri):
 
        if uri is None:
 
            raise TypeError("uri must be URIRef")
 
        self.graph, self.uri = graph, uri
 
        self.graph.addHandler(self.setName)
 
        self.graph.addHandler(self.setLevels)
 
        Submaster.__init__(self, self.name, self.levels)
 
        self.graph = graph
 
        self.uri = uri
 
        self.temporary = False
 

	
 
        # error faster if we won't be able to find where to save
 
        self.saveContext()
 

	
 
    def ident(self):
 
        return self.uri
 

	
 
    def _editedLevels(self):
 
        self.save()
 

	
 
    def setName(self):
 
        log.info("sub update name %s %s", self.uri, self.graph.label(self.uri))
 
        self.name = self.graph.label(self.uri)
 

	
 
    def setLevels(self):
 
        log.info("sub update levels")
 
        oldLevels = getattr(self, 'levels', {}).copy()
 
        self.setLevelsFromGraph()
 
        if oldLevels != self.levels:
 
            log.info("sub %s changed" % self.name)
 
            # dispatcher too? this would help subcomposer
 
            dispatcher.send("sub levels changed", sub=self)
 

	
 
    def setLevelsFromGraph(self):
 
        if hasattr(self, 'levels'):
 
            self.levels.clear()
 
        else:
 
            self.levels = {}
light9/rdfdb/graphfile.py
Show inline comments
 
@@ -31,48 +31,49 @@ class GraphFile(object):
 
            iolog.info("created %s", path)
 

	
 
        self.flushDelay = 2 # seconds until we have to call flush() when dirty
 
        self.writeCall = None # or DelayedCall
 
        self.lastWriteTimestamp = 0 # mtime from the last time _we_ wrote
 

	
 
        # emacs save comes in as IN_MOVE_SELF, maybe
 
        
 
        # I was hoping not to watch IN_CHANGED and get lots of
 
        # half-written files, but emacs doesn't close its files after
 
        # a write, so there's no other event. I could try to sleep
 
        # until after all the writes are done, but I think the only
 
        # bug left is that we'll retry too agressively on a file
 
        # that's being written
 

	
 
        from twisted.internet.inotify import IN_CLOSE_WRITE, IN_MOVED_FROM, IN_MODIFY, IN_DELETE, IN_DELETE_SELF, IN_CHANGED
 

	
 
        notifier.watch(FilePath(path),
 
                       mask=IN_CLOSE_WRITE | IN_MOVED_FROM | IN_DELETE | IN_DELETE_SELF | IN_CHANGED | 16383,
 
                       callbacks=[self.notify])
 
      
 
    def notify(self, notifier, filepath, mask):
 
        maskNames = humanReadableMask(mask)
 
        if maskNames[0] in ['open', 'access', 'close_nowrite', 'attrib', 'delete_self']:
 
            log.debug("file %s changed, ignoring %s" % (filepath, maskNames))
 
            return
 

	
 
        try:
 
            if filepath.getModificationTime() == self.lastWriteTimestamp:
 
                log.debug("file %s changed, but we did this write", filepath)
 
                return
 
        except OSError as e:
 
            log.error("watched file %s: %r" % (filepath, e))
 
            return
 
            
 
        log.info("file %s changed (%s)", filepath, maskNames)
 
        try:
 
            self.reread()
 
        except Exception:
 
            traceback.print_exc()
 

	
 
    def reread(self):
 
        """update the graph with any diffs from this file
 

	
 
        n3 parser fails on "1.e+0" even though rdflib was emitting that itself
 
        """
 
        old = self.getSubgraph(self.uri)
 
        new = Graph()
 
        try:
0 comments (0 inline, 0 general)