Changeset - 295b867fd810
[Not reviewed]
default
0 8 0
drewp@bigasterisk.com - 12 years ago 2013-01-15 21:02:08
drewp@bigasterisk.com
just whitespace (hopefully)
Ignore-this: a364fab649d9e795f703cfe794f07ca6
8 files changed with 92 insertions and 88 deletions:
0 comments (0 inline, 0 general)
bin/keyboardcomposer
Show inline comments
 
@@ -67,57 +67,57 @@ class SubmasterBox(Frame):
 
        self.sub = sub
 
        self.session = session
 
        bg = sub.graph.value(sub.uri, L9.color, default='#000000')
 
        rgb = webcolors.hex_to_rgb(bg)
 
        hsv = colorsys.rgb_to_hsv(*[x/255 for x in rgb])
 
        darkBg = webcolors.rgb_to_hex(tuple([x * 255 for x in colorsys.hsv_to_rgb(
 
            hsv[0], hsv[1], .3)]))
 
        Frame.__init__(self, master, bd=1, relief='raised', bg=bg)
 
        self.name = sub.name
 
        self.slider_var = DoubleVar()
 
        self.pauseTrace = False
 
        self.scale = SubScale(self, variable=self.slider_var, width=20)
 
        
 

	
 
        self.namelabel = Label(self, font="Arial 7", bg=darkBg,
 
            fg='white', pady=0)
 
        self.sub.graph.addHandler(self.updateName)
 
        
 

	
 
        self.namelabel.pack(side=TOP)
 
        levellabel = Label(self, textvariable=self.slider_var, font="Arial 7",
 
            bg='black', fg='white', pady=0)
 
        levellabel.pack(side=TOP)
 
        self.scale.pack(side=BOTTOM, expand=1, fill=BOTH)
 
        bindkeys(self, "<Control-Key-l>", self.launch_subcomposer)
 

	
 
        for w in [self, self.namelabel, levellabel]:
 
            dragSourceRegister(w, 'copy', 'text/uri-list', sub.uri)
 

	
 
        self._slider_var_trace = self.slider_var.trace('w', self.slider_changed)
 

	
 
        sub.graph.addHandler(self.updateLevelFromGraph)
 

	
 
        # initial position
 
#        self.send_to_hw(sub.name, col + 1) # needs fix
 

	
 
    def cleanup(self):
 
        self.slider_var.trace_vdelete('w', self._slider_var_trace)
 
    
 

	
 
    def slider_changed(self, *args):
 
        self.scale.draw_indicator_colors()
 

	
 
        if self.pauseTrace:
 
            return
 
        self.updateGraphWithLevel(self.sub.uri, self.slider_var.get())
 
        # dispatcher.send("level changed") # in progress
 
        ###self.send_levels() # use dispatcher? 
 
        ###self.send_levels() # use dispatcher?
 

	
 
        # needs fixing: plan is to use dispatcher or a method call to tell a hardware-mapping object who changed, and then it can make io if that's a current hw slider
 
        #if rowcount == self.current_row:
 
        #    self.send_to_hw(sub.name, col + 1)
 

	
 
    def updateGraphWithLevel(self, uri, level):
 
        """in our per-session graph, we maintain SubSetting objects like this:
 

	
 
           ?session :subSetting [a :SubSetting; :sub ?s; :level ?l]
 
        """
 
        # move to syncedgraph patchMapping
 
        with self.sub.graph.currentState() as graph:
 
@@ -162,119 +162,119 @@ class SubmasterBox(Frame):
 

	
 
class KeyboardComposer(Frame, SubClient):
 
    def __init__(self, root, graph, session,
 
                 hw_sliders=True):
 
        Frame.__init__(self, root, bg='black')
 
        SubClient.__init__(self)
 
        self.graph = graph
 
        self.session = session
 
        self.submasters = Submasters(graph)
 
        self.subbox = {} # sub uri : SubmasterBox
 
        self.slider_table = {} # coords : SubmasterBox
 
        self.rows = [] # this holds Tk Frames for each row
 
        
 

	
 
        self.current_row = 0 # should come from session graph
 

	
 
        self.use_hw_sliders = hw_sliders
 
        self.connect_to_hw(hw_sliders)
 

	
 
        self.make_key_hints()
 
        self.make_buttons()
 

	
 
        self.graph.addHandler(self.redraw_sliders)
 
        self.send_levels_loop()
 
        self.graph.addHandler(self.rowFromGraph)
 

	
 
    def make_buttons(self):
 
        self.buttonframe = Frame(self, bg='black')
 
        self.buttonframe.pack(side=BOTTOM)
 

	
 
        self.sliders_status_var = IntVar()
 
        self.sliders_status_var.set(self.use_hw_sliders)
 
        self.sliders_checkbutton = Checkbutton(self.buttonframe, 
 
        self.sliders_checkbutton = Checkbutton(self.buttonframe,
 
            text="Sliders", variable=self.sliders_status_var,
 
            command=lambda: self.toggle_slider_connectedness(),
 
            bg='black', fg='white')
 
        self.sliders_checkbutton.pack(side=LEFT)
 

	
 
        self.alltozerobutton = Button(self.buttonframe, text="All to Zero", 
 
        self.alltozerobutton = Button(self.buttonframe, text="All to Zero",
 
            command=self.alltozero, bg='black', fg='white')
 
        self.alltozerobutton.pack(side='left')
 

	
 
        self.save_stage_button = Button(self.buttonframe, text="Save", 
 
            command=lambda: self.save_current_stage(self.sub_name.get()), 
 
        self.save_stage_button = Button(self.buttonframe, text="Save",
 
            command=lambda: self.save_current_stage(self.sub_name.get()),
 
            bg='black', fg='white')
 
        self.save_stage_button.pack(side=LEFT)
 
        self.sub_name = Entry(self.buttonframe, bg='black', fg='white')
 
        self.sub_name.pack(side=LEFT)
 

	
 
    def redraw_sliders(self):
 
        self.graph.addHandler(self.draw_sliders)
 
        if len(self.rows):
 
            self.change_row(self.current_row)
 
            self.rows[self.current_row].focus()
 

	
 
        self.stop_frequent_update_time = 0
 

	
 
    def onNewSub(self, sub):
 
        log.info("new %s", sub)
 
        self.graph.addHandler(self.draw_sliders)
 

	
 
    def onLostSub(self, subUri):
 
        log.info("lost %s", subUri)
 
        self.graph.addHandler(self.draw_sliders)
 
    
 

	
 
    def draw_sliders(self):
 
        for r in self.rows:
 
            r.destroy()
 
        self.rows = []
 
        for b in self.subbox.values():
 
            b.cleanup()
 
        self.subbox.clear()
 
        self.slider_table.clear()
 
        
 

	
 
        self.tk_focusFollowsMouse()
 

	
 
        rowcount = -1
 
        col = 0
 
        last_group = None
 

	
 
        # there are unlikely to be any subs at startup because we
 
        # probably haven't been called back with the graph data yet
 
        
 
        withgroups = sorted((self.graph.value(sub.uri, L9['group']), 
 
                             self.graph.value(sub.uri, L9['order']), 
 

	
 
        withgroups = sorted((self.graph.value(sub.uri, L9['group']),
 
                             self.graph.value(sub.uri, L9['order']),
 
                             sub)
 
            for sub in self.submasters.get_all_subs())
 
        dispatcher.connect(self.onNewSub, "new submaster")
 
        dispatcher.connect(self.onLostSub, "lost submaster")
 
        log.info("withgroups %s", withgroups)
 

	
 
        for group, order, sub in withgroups:
 
            group = self.graph.value(sub.uri, L9['group'])
 

	
 
            if col == 0 or group != last_group:
 
                row = self.make_row()
 
                rowcount += 1
 
                col = 0
 

	
 
            subbox = SubmasterBox(row, sub, self.session)
 
            subbox.place(relx=col / 8, rely=0, relwidth=1 / 8, relheight=1)
 
            self.subbox[sub.uri] = self.slider_table[(rowcount, col)] = subbox
 

	
 
            self.setup_key_nudgers(subbox.scale)
 

	
 
            col = (col + 1) % 8
 
            last_group = group
 
            
 

	
 
    def toggle_slider_connectedness(self):
 
        self.use_hw_sliders = not self.use_hw_sliders
 
        if self.use_hw_sliders:
 
            self.sliders.reopen()
 
        else:
 
            self.sliders.close()
 
        self.change_row(self.current_row)
 
        self.rows[self.current_row].focus()
 

	
 
    def connect_to_hw(self, hw_sliders):
 
        if hw_sliders:
 
            try:
 
@@ -288,25 +288,25 @@ class KeyboardComposer(Frame, SubClient)
 

	
 
    def make_key_hints(self):
 
        keyhintrow = Frame(self)
 

	
 
        col = 0
 
        for upkey, downkey in zip(nudge_keys['up'],
 
                                  nudge_keys['down']):
 
            # what a hack!
 
            downkey = downkey.replace('semicolon', ';')
 
            upkey, downkey = (upkey.upper(), downkey.upper())
 

	
 
            # another what a hack!
 
            keylabel = Label(keyhintrow, text='%s\n%s' % (upkey, downkey), 
 
            keylabel = Label(keyhintrow, text='%s\n%s' % (upkey, downkey),
 
                width=1, font=('Arial', 10), bg='red', fg='white', anchor='c')
 
            keylabel.pack(side=LEFT, expand=1, fill=X)
 
            col += 1
 

	
 
        keyhintrow.pack(fill=X, expand=0)
 
        self.keyhints = keyhintrow
 

	
 
    def setup_key_nudgers(self, tkobject):
 
        for d, keys in nudge_keys.items():
 
            for key in keys:
 
                # lowercase makes full=0
 
                keysym = "<KeyPress-%s>" % key
 
@@ -362,25 +362,25 @@ class KeyboardComposer(Frame, SubClient)
 

	
 
        for col in range(1, 9):
 
            try:
 
                subbox = self.slider_table[(self.current_row, col - 1)]
 
                self.sliders.valueOut("button-upper%d" % col, True)
 
            except KeyError:
 
                # unfilled bottom row has holes (plus rows with incomplete
 
                # groups
 
                self.sliders.valueOut("button-upper%d" % col, False)
 
                self.sliders.valueOut("slider%d" % col, 0)
 
                continue
 
            self.send_to_hw(subbox.name, col)
 
            
 

	
 
    def got_nudger(self, number, direction, full=0):
 
        try:
 
            subbox = self.slider_table[(self.current_row, number)]
 
        except KeyError:
 
            return
 

	
 
        if direction == 'up':
 
            if full:
 
                subbox.scale.fade(1)
 
            else:
 
                subbox.scale.increase()
 
        else:
 
@@ -391,53 +391,53 @@ class KeyboardComposer(Frame, SubClient)
 

	
 
    def hw_slider_moved(self, col, value):
 
        value = int(value * 100) / 100
 
        try:
 
            subbox = self.slider_table[(self.current_row, col)]
 
        except KeyError:
 
            return # no slider assigned at that column
 
        subbox.scale.set(value)
 

	
 
    def send_to_hw(self, subUri, hwNum):
 
        if isinstance(self.sliders, DummySliders):
 
            return
 
            
 

	
 
        v = round(127 * self.slider_vars[subUri].get())
 
        chan = "slider%s" % hwNum
 
        
 

	
 
        # workaround for some rounding issue, where we receive one
 
        # value and then decide to send back a value that's one step
 
        # lower.  -5 is a fallback for having no last value.  hopefully
 
        # we won't really see it
 
        if abs(v - self.sliders.lastValue.get(chan, -5)) <= 1:
 
            return
 
        self.sliders.valueOut(chan, v)
 
            
 

	
 
    def make_row(self):
 
        row = Frame(self, bd=2, bg='black')
 
        row.pack(expand=1, fill=BOTH)
 
        self.setup_key_nudgers(row)
 
        self.rows.append(row)
 
        return row
 

	
 
    def highlight_row(self, row):
 
        row = self.rows[row]
 
        row['bg'] = 'red'
 

	
 
    def unhighlight_row(self, row):
 
        row = self.rows[row]
 
        row['bg'] = 'black'
 

	
 
    def get_levels(self):
 
        return dict([(uri, box.slider_var.get()) 
 
        return dict([(uri, box.slider_var.get())
 
            for uri, box in self.subbox.items()])
 

	
 
    def get_levels_as_sub(self):
 
        scaledsubs = [self.submasters.get_sub_by_uri(sub) * level
 
            for sub, level in self.get_levels().items() if level > 0.0]
 
        maxes = sub_maxes(*scaledsubs)
 
        return maxes
 

	
 
    def save_current_stage(self, subname):
 
        log.info("saving current levels as %s", subname)
 
        sub = self.get_levels_as_sub()
 
        sub.name = subname
 
@@ -470,25 +470,25 @@ def postArgGetter(request):
 
        except KeyError:
 
            return fields[n].value
 
    return getArg
 

	
 

	
 
class LevelServerHttp(resource.Resource):
 
    isLeaf = True
 
    def __init__(self,name_to_subbox):
 
        self.name_to_subbox = name_to_subbox
 

	
 
    def render_POST(self, request):
 
        arg = postArgGetter(request)
 
        
 

	
 
        if request.path == '/fadesub':
 
            # fadesub?subname=scoop&level=0&secs=.2
 
            self.name_to_subbox[arg('subname')].scale.fade(
 
                float(arg('level')),
 
                float(arg('secs')))
 
            return "set %s to %s" % (arg('subname'), arg('level'))
 
        else:
 
            raise NotImplementedError(repr(request))
 

	
 
class Sliders(BCF2000):
 
    def __init__(self, kc):
 
        devices = ['/dev/snd/midiC1D0', '/dev/snd/midiC2D0', '/dev/snd/midiC3D0']
 
@@ -537,24 +537,27 @@ if __name__ == "__main__":
 
    parser = OptionParser()
 
    parser.add_option('--no-sliders', action='store_true',
 
                      help="don't attach to hardware sliders")
 
    clientsession.add_option(parser)
 
    parser.add_option('-v', action='store_true', help="log info level")
 
    opts, args = parser.parse_args()
 

	
 
    logging.basicConfig(level=logging.INFO if opts.v else logging.WARN)
 
    log = logging.getLogger('keyboardcomposer')
 

	
 
    graph = SyncedGraph("keyboardcomposer")
 

	
 
    # i think this also needs delayed start (like subcomposer has), to have a valid graph
 
    # before setting any stuff from the ui
 
    
 
    root = Tk()
 
    initTkdnd(root.tk, 'tkdnd/trunk/')
 

	
 
    session = clientsession.getUri('keyboardcomposer', opts)
 

	
 
    tl = toplevelat("Keyboard Composer - %s" % opts.session,
 
                    existingtoplevel=root, graph=graph, session=session)
 

	
 
    kc = KeyboardComposer(tl, graph, session,
 
                          hw_sliders=not opts.no_sliders)
 
    kc.pack(fill=BOTH, expand=1)
 

	
 
@@ -563,19 +566,19 @@ if __name__ == "__main__":
 
                 anchor='w').pack(side='top',fill='x')
 

	
 
    if 0: # needs fixing, or maybe it's obsolete because other progs can just patch the rdf graph
 
        import twisted.internet
 
        try:
 
            reactor.listenTCP(networking.keyboardComposer.port,
 
                              server.Site(LevelServerHttp(kc.name_to_subbox)))
 
        except twisted.internet.error.CannotListenError, e:
 
            log.warn("Can't (and won't!) start level server:")
 
            log.warn(e)
 

	
 
    root.protocol('WM_DELETE_WINDOW', reactor.stop)
 
    
 

	
 
    tksupport.install(root,ms=10)
 

	
 

	
 
#    prof.watchPoint("/usr/lib/python2.4/site-packages/rdflib-2.3.3-py2.4-linux-i686.egg/rdflib/Graph.py", 615)
 

	
 
    prof.run(reactor.run, profile=False)
bin/rdfdb
Show inline comments
 
@@ -142,25 +142,25 @@ class Client(object):
 
        self.sendAll()
 

	
 
    def __repr__(self):
 
        return "<%s client at %s>" % (self.label, self.updateUri)
 

	
 
    def sendAll(self):
 
        """send the client the whole graph contents"""
 
        log.info("sending all graphs to %s at %s" %
 
                 (self.label, self.updateUri))
 
        self.sendPatch(Patch(
 
            addQuads=self.db.graph.quads(ALLSTMTS),
 
            delQuads=[]))
 
        
 

	
 
    def sendPatch(self, p):
 
        return syncedgraph.sendPatch(self.updateUri, p)
 

	
 
class Db(object):
 
    """
 
    the master graph, all the connected clients, all the files we're watching
 
    """
 
    def __init__(self):
 
        # files from cwd become uris starting with this. *should* be
 
        # building uris from the show uri in $LIGHT9_SHOW/URI
 
        # instead. Who wants to keep their data in the same dir tree
 
        # as the source code?!
 
@@ -185,45 +185,45 @@ class Db(object):
 
                ]
 

	
 
            for topdir in dirs:
 
                for dirpath, dirnames, filenames in os.walk(topdir):
 
                    for base in filenames:
 
                        self.watchFile(os.path.join(dirpath, base))
 
                # todo: also notice new files in this dir
 

	
 
            self.watchFile("show/dance2012/config.n3")
 
            self.watchFile("show/dance2012/patch.n3")
 
        finally:
 
            self.initialLoad = False
 
            
 

	
 
        self.summarizeToLog()
 

	
 
    def uriFromFile(self, filename):
 
        if filename.endswith('.n3'):
 
            # some legacy files don't end with n3. when we write them
 
            # back this might not go so well
 
            filename = filename[:-len('.n3')]
 
        return URIRef(self.topUri + filename)
 
        
 

	
 
    def fileForUri(self, ctx):
 
        if not ctx.startswith(self.topUri):
 
            raise ValueError("don't know what filename to use for %s" % ctx)
 
        return ctx[len(self.topUri):] + ".n3"
 

	
 
    def watchFile(self, inFile):
 
        ctx = self.uriFromFile(inFile)
 
        gf = GraphFile(self.notifier, inFile, ctx, self.patch, self.getSubgraph)
 
        self.graphFiles[ctx] = gf
 
        gf.reread()
 
        
 

	
 
    def patch(self, p, dueToFileChange=False):
 
        """
 
        apply this patch to the master graph then notify everyone about it
 

	
 
        dueToFileChange if this is a patch describing an edit we read
 
        *from* the file (such that we shouldn't write it back to the file)
 

	
 
        if p has a senderUpdateUri attribute, we won't send this patch
 
        back to the sender with that updateUri
 
        """
 
        ctx = p.getContext()
 
        log.info("patching graph %s -%d +%d" % (
 
@@ -250,102 +250,102 @@ class Db(object):
 
        for a new file we have to create, or it might be for a
 
        transient context that we're not going to save
 

	
 
        if it's a ctx with no file, error
 
        """
 
        for ctx in ctxs:
 
            g = self.getSubgraph(ctx)
 

	
 
            if ctx not in self.graphFiles:
 
                outFile = self.fileForUri(ctx)
 
                self.graphFiles[ctx] = GraphFile(self.notifier, outFile, ctx,
 
                                                 self.patch, self.getSubgraph)
 
            
 

	
 
            self.graphFiles[ctx].dirty(g)
 

	
 
    def clientErrored(self, err, c):
 
        err.trap(twisted.internet.error.ConnectError)
 
        log.info("connection error- dropping client %r" % c)
 
        self.clients.remove(c)
 
        self.sendClientsToAllLivePages()        
 
        self.sendClientsToAllLivePages()
 

	
 
    def summarizeToLog(self):
 
        log.info("contexts in graph (%s total stmts):" % len(self.graph))
 
        for c in self.graph.contexts():
 
            log.info("  %s: %s statements" %
 
                     (c.identifier, len(self.getSubgraph(c.identifier))))
 

	
 
    def getSubgraph(self, uri):
 
        """
 
        this is meant to return a live view of the given subgraph, but
 
        if i'm still working around an rdflib bug, it might return a
 
        copy
 

	
 
        and it's returning triples, but I think quads would be better
 
        """
 
        # this is returning an empty Graph :(
 
        #return self.graph.get_context(uri)
 

	
 
        g = Graph()
 
        for s in self.graph.triples(ALLSTMTS, uri):
 
            g.add(s)
 
        return g
 
    
 

	
 
    def addClient(self, updateUri, label):
 
        [self.clients.remove(c)
 
         for c in self.clients if c.updateUri == updateUri]
 

	
 
        log.info("new client %s at %s" % (label, updateUri))
 
        self.clients.append(Client(updateUri, label, self))
 
        self.sendClientsToAllLivePages()
 

	
 
    def sendClientsToAllLivePages(self):
 
        sendToLiveClients({"clients":[
 
            dict(updateUri=c.updateUri, label=c.label)
 
            for c in self.clients]})        
 
            for c in self.clients]})
 

	
 
class GraphResource(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def get(self):
 
        pass
 
    
 
        self.write(self.settings.db.graph.serialize(format='n3'))
 

	
 
class Patches(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def __init__(self, *args, **kw):
 
        cyclone.web.RequestHandler.__init__(self, *args, **kw)
 
        p = syncedgraph.makePatchEndpointPutMethod(self.settings.db.patch)
 
        self.put = lambda: p(self)
 

	
 
    def get(self):
 
        pass
 

	
 
class GraphClients(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def get(self):
 
        pass
 
    
 

	
 
    def post(self):
 
        upd = self.get_argument("clientUpdate")
 
        try:
 
            self.settings.db.addClient(upd, self.get_argument("label"))
 
        except:
 
            import traceback
 
            traceback.print_exc()
 
            raise
 

	
 
liveClients = set()
 
def sendToLiveClients(d=None, asJson=None):
 
    j = asJson or json.dumps(d)
 
    for c in liveClients:
 
        c.sendMessage(j)
 

	
 
class Live(cyclone.websocket.WebSocketHandler):
 
    
 

	
 
    def connectionMade(self, *args, **kwargs):
 
        log.info("websocket opened")
 
        liveClients.add(self)
 
        self.settings.db.sendClientsToAllLivePages()
 

	
 
    def connectionLost(self, reason):
 
        log.info("websocket closed")
 
        liveClients.remove(self)
 

	
 
    def messageReceived(self, message):
 
        log.info("got message %s" % message)
 
        self.sendMessage(message)
 
@@ -366,28 +366,28 @@ if __name__ == "__main__":
 
        help='show URI, like http://light9.bigasterisk.com/show/dance2008',
 
                      default=showconfig.showUri())
 
    parser.add_option("-v", "--verbose", action="store_true",
 
                      help="logging.DEBUG")
 
    (options, args) = parser.parse_args()
 

	
 
    log.setLevel(logging.DEBUG if options.verbose else logging.INFO)
 

	
 
    if not options.show:
 
        raise ValueError("missing --show http://...")
 

	
 
    db = Db()
 
    
 

	
 
    from twisted.python import log as twlog
 
    twlog.startLogging(sys.stdout)
 
            
 

	
 
    port = 8051
 
    reactor.listenTCP(port, cyclone.web.Application(handlers=[
 
        (r'/live', Live),
 
        (r'/graph', GraphResource),
 
        (r'/patches', Patches),
 
        (r'/graphClients', GraphClients),
 

	
 
        (r'/(.*)', NoExts,
 
         {"path" : "light9/rdfdb/web",
 
          "default_filename" : "index.xhtml"}),
 

	
 
        ], debug=True, db=db))
light9/Submaster.py
Show inline comments
 
@@ -2,162 +2,162 @@ from __future__ import division
 
import os, logging, time
 
from rdflib import Graph, RDF
 
from rdflib import RDFS, Literal, BNode
 
from light9.namespaces import L9, XSD
 
from light9.TLUtility import dict_scale, dict_max
 
from light9 import Patch, showconfig
 
from louie import dispatcher
 
log = logging.getLogger('submaster')
 

	
 
class Submaster(object):
 
    "Contain a dictionary of levels, but you didn't need to know that"
 
    def __init__(self, name, levels):
 
        """this sub has a name just for debugging. It doesn't get persisted. 
 
        """this sub has a name just for debugging. It doesn't get persisted.
 
        See PersistentSubmaster.
 

	
 
        levels is a dict
 
        """
 
        self.name = name
 
        self.levels = levels
 

	
 
        self.temporary = True
 
        
 

	
 
        if not self.temporary:
 
            # obsolete
 
            dispatcher.connect(log.error, 'reload all subs')
 
            
 

	
 
        log.debug("%s initial levels %s", self.name, self.levels)
 

	
 
    def _editedLevels(self):
 
        pass
 
    
 

	
 
    def set_level(self, channelname, level, save=True):
 
        self.levels[Patch.resolve_name(channelname)] = level
 
        self._editedLevels()
 

	
 
    def set_all_levels(self, leveldict):
 
        self.levels.clear()
 
        for k, v in leveldict.items():
 
            # this may call _editedLevels too many times
 
            self.set_level(k, v, save=0)
 

	
 
    def get_levels(self):
 
        return self.levels
 
    
 

	
 
    def no_nonzero(self):
 
        return (not self.levels.values()) or not (max(self.levels.values()) > 0)
 

	
 
    def __mul__(self, scalar):
 
        return Submaster("%s*%s" % (self.name, scalar), 
 
        return Submaster("%s*%s" % (self.name, scalar),
 
                         levels=dict_scale(self.levels, scalar))
 
    __rmul__ = __mul__
 
    def max(self, *othersubs):
 
        return sub_maxes(self, *othersubs)
 

	
 
    def __add__(self, other):
 
        return self.max(other)
 

	
 
    def ident(self):
 
        return (self.name, tuple(sorted(self.levels.items())))
 

	
 
    def __repr__(self):
 
        items = getattr(self, 'levels', {}).items()
 
        items.sort()
 
        levels = ' '.join(["%s:%.2f" % item for item in items])
 
        return "<'%s': [%s]>" % (getattr(self, 'name', 'no name yet'), levels)
 
    
 

	
 
    def __cmp__(self, other):
 
        # not sure how useful this is
 
        return cmp(self.ident(), other.ident())
 
    
 

	
 
    def __hash__(self):
 
        return hash(self.ident())
 

	
 
    def get_dmx_list(self):
 
        leveldict = self.get_levels() # gets levels of sub contents
 

	
 
        levels = []
 
        for k, v in leveldict.items():
 
            if v == 0:
 
                continue
 
            try:
 
                dmxchan = Patch.get_dmx_channel(k) - 1
 
            except ValueError:
 
                log.error("error trying to compute dmx levels for submaster %s" % self.name)
 
                raise
 
            if dmxchan >= len(levels):
 
                levels.extend([0] * (dmxchan - len(levels) + 1))
 
            levels[dmxchan] = max(v, levels[dmxchan])
 

	
 
        return levels
 
    
 

	
 
    def normalize_patch_names(self):
 
        """Use only the primary patch names."""
 
        # possibly busted -- don't use unless you know what you're doing
 
        self.set_all_levels(self.levels.copy())
 

	
 
    def get_normalized_copy(self):
 
        """Get a copy of this sumbaster that only uses the primary patch 
 
        """Get a copy of this sumbaster that only uses the primary patch
 
        names.  The levels will be the same."""
 
        newsub = Submaster("%s (normalized)" % self.name, {})
 
        newsub.set_all_levels(self.levels)
 
        return newsub
 
    
 

	
 
    def crossfade(self, othersub, amount):
 
        """Returns a new sub that is a crossfade between this sub and
 
        another submaster.  
 
        
 
        another submaster.
 

	
 
        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, 
 
            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):
 
        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
 

	
 
    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)
 
        
 

	
 
    def setLevelsFromGraph(self):
 
        if hasattr(self, 'levels'):
 
            self.levels.clear()
 
        else:
 
            self.levels = {}
 
        for lev in self.graph.objects(self.uri, L9['lightLevel']):
 
            chan = self.graph.value(lev, L9['channel'])
 

	
 
            name = self.graph.label(chan)
 
            if not name:
 
                log.error("sub %r has channel %r with no name- leaving out that channel" % (self.name, chan))
 
                continue
 
@@ -177,25 +177,25 @@ class PersistentSubmaster(Submaster):
 
                chanUri = Patch.get_channel_uri(chan)
 
            except KeyError:
 
                log.error("saving dmx channels with no :Channel node is not supported yet. Give channel %s a URI for it to be saved. Omitting this channel from the sub." % chan)
 
                continue
 
            lev = BNode()
 
            graph.add((subUri, L9['lightLevel'], lev))
 
            graph.add((lev, L9['channel'], chanUri))
 
            graph.add((lev, L9['level'],
 
                       Literal(self.levels[chan], datatype=XSD['decimal'])))
 

	
 
        graph.serialize(showconfig.subFile(self.name), format="nt")
 

	
 
                                            
 

	
 
def linear_fade(start, end, amount):
 
    """Fades between two floats by an amount.  amount is a float between
 
    0 and 1.  If amount is 0, it will return the start value.  If it is 1,
 
    the end value will be returned."""
 
    level = start + (amount * (end - start))
 
    return level
 

	
 
def sub_maxes(*subs):
 
    nonzero_subs = [s for s in subs if not s.no_nonzero()]
 
    name = "max(%s)" % ", ".join([repr(s) for s in nonzero_subs])
 
    return Submaster(name,
 
                     levels=dict_max(*[sub.levels for sub in nonzero_subs]))
 
@@ -212,25 +212,25 @@ def combine_subdict(subdict, name=None, 
 
    if name:
 
        maxes.name = name
 
    if permanent:
 
        maxes.temporary = False
 

	
 
    return maxes
 

	
 
class Submasters:
 
    "Collection o' Submaster objects"
 
    def __init__(self, graph):
 
        self.submasters = {}
 
        self.graph = graph
 
        
 

	
 
        graph.addHandler(self.findSubs)
 

	
 
    def findSubs(self):
 
        current = set()
 

	
 
        for s in self.graph.subjects(RDF.type, L9['Submaster']):
 
            log.info("found sub %s", s)
 
            if s not in self.submasters:
 
                sub = self.submasters[s] = PersistentSubmaster(self.graph, s)
 
                dispatcher.send("new submaster", sub=sub)
 
                current.add(s)
 
        for s in set(self.submasters.keys()) - current:
light9/dmxchanedit.py
Show inline comments
 
@@ -42,52 +42,52 @@ class Onelevel(tk.Frame):
 
        self.graph = graph
 
        self.uri = channelUri
 
        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(self.channelnum),
 
                                width=3, bg='grey40', 
 
                                width=3, bg='grey40',
 
                                fg='white',
 
                                font=stdfont,
 
                                padx=0, pady=0, bd=0, height=1)
 
        self.num_lab.pack(side='left')
 

	
 
        # text description of channel
 
        self.desc_lab=tk.Label(self,
 
                               width=14,
 
                               font=stdfont,
 
                               anchor='w',
 
                               padx=0, pady=0, bd=0, 
 
                               padx=0, pady=0, bd=0,
 
                 height=1, bg='black', fg='white')
 
        self.graph.addHandler(self.updateLabel)
 
        self.desc_lab.pack(side='left')
 

	
 
        # current level of channel, shows intensity with color
 
        self.level_lab = tk.Label(self, width=3, bg='lightBlue',
 
                                  anchor='e', font=stdfont,
 
                                  padx=1, pady=0, bd=0, height=1)
 
        self.level_lab.pack(side='left')
 

	
 
        self.setlevel(0)
 
        self.setupmousebindings()
 

	
 
    def updateLabel(self):
 
         self.desc_lab.config(text=self.graph.label(self.uri))
 
        
 

	
 
    def setupmousebindings(self):
 
        def b1down(ev):
 
            self.desc_lab.config(bg='cyan')
 
            self._start_y=ev.y
 
            self._start_lev=self.currentlevel
 
        def b1motion(ev):
 
            delta=self._start_y-ev.y
 
            self.setlevel(self._start_lev+delta*.005)
 
        def b1up(ev):
 
            self.desc_lab.config(bg='black')
 
        def b3up(ev):
 
            self.setlevel(0.0)
 
@@ -97,25 +97,25 @@ class Onelevel(tk.Frame):
 
            self.setlevel(1.0)
 

	
 
        # make the buttons work in the child windows
 
        for w in self.winfo_children():
 
            for e,func in (('<ButtonPress-1>',b1down),
 
                           ('<B1-Motion>',b1motion),
 
                           ('<ButtonRelease-1>',b1up),
 
                           ('<ButtonPress-2>', b2down),
 
                           ('<ButtonRelease-3>', b3up),
 
                           ('<ButtonPress-3>', b3down)):
 

	
 
                w.bind(e,func)
 
        
 

	
 
    def colorlabel(self):
 
        """color the level label based on its own text (which is 0..100)"""
 
        txt=self.level_lab['text'] or "0"
 
        lev=float(txt)/100
 
        self.level_lab.config(bg=gradient(lev))
 

	
 
    def setlevel(self, newlev):
 
        """the main program is telling us to change our
 
        display. newlev is 0..1"""
 
        self.currentlevel = min(1, max(0, newlev))
 
        newlev = "%d" % (self.currentlevel * 100)
 
        olddisplay=self.level_lab.cget('text')
 
@@ -137,25 +137,25 @@ class Levelbox(tk.Frame):
 
        [ch.destroy() for ch in self.winfo_children()]
 
        self.levels = [] # Onelevel objects
 

	
 
        chans = list(self.graph.subjects(RDF.type, L9.Channel))
 
        chans.sort(key=lambda c: int(self.graph.value(c, L9.output).rsplit('/c')[-1]))
 
        cols = 2
 
        rows = len(chans) // cols
 

	
 
        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(columnFrames[i // rows], self.graph, channel)
 

	
 
            self.levels.append(f)
 
            f.pack(side='top')
 
        #dispatcher.connect(setalevel,"setlevel")
 

	
 
    def setlevels(self,newlevels):
 
        """sets levels to the new list of dmx levels (0..1). list can
light9/rdfdb/rdflibpatch.py
Show inline comments
 
@@ -59,25 +59,25 @@ def graphFromNQuad(text):
 
        #g2.get_context(c).add((s,p,o))
 
        g2.store.add((s,p,o), c)
 
    #import pprint; pprint.pprint(g2.store.__dict__)
 
    return g2
 

	
 
from rdflib.plugins.serializers.nt import _xmlcharref_encode
 
def serializeQuad(g):
 
    """replacement for graph.serialize(format='nquads')"""
 
    out = ""
 
    for s,p,o,c in g.quads((None,None,None)):
 
        out += u"%s %s %s %s .\n" % (s.n3(),
 
                                p.n3(),
 
                                _xmlcharref_encode(o.n3()), 
 
                                _xmlcharref_encode(o.n3()),
 
                                c.n3())
 
    return out
 

	
 
def inContext(graph, newContext):
 
    """
 
    make a ConjunctiveGraph where all the triples in the given graph
 
    are in newContext
 
    """
 
    return graphFromQuads([(s,p,o,newContext) for s,p,o in graph])
 

	
 
def contextsForStatement(graph, triple):
 
    return [q[3] for q in graph.quads(triple)]
 
@@ -101,62 +101,62 @@ class TestGraphFromQuads(unittest.TestCa
 
    nqOut = '<http://example.com/> <http://example.com/> <http://example.com/> <http://example.com/> .\n'
 
    def testSerializes(self):
 
        n = U("http://example.com/")
 
        g = graphFromQuads([(n,n,n,n)])
 
        out = serializeQuad(g)
 
        self.assertEqual(out.strip(), self.nqOut.strip())
 

	
 
    def testNquadParserSerializes(self):
 
        g = graphFromNQuad(self.nqOut)
 
        self.assertEqual(len(g), 1)
 
        out = serializeQuad(g)
 
        self.assertEqual(out.strip(), self.nqOut.strip())
 
        
 

	
 

	
 
stmt1 = U('http://a'), U('http://b'), U('http://c'), U('http://ctx1')
 
stmt2 = U('http://a'), U('http://b'), U('http://c'), U('http://ctx2')
 
class TestPatchQuads(unittest.TestCase):
 
    def testAddsToNewContext(self):
 
        g = ConjunctiveGraph()
 
        patchQuads(g, [], [stmt1])
 
        self.assert_(len(g), 1)
 
        quads = list(g.quads((None,None,None)))
 
        self.assertEqual(quads, [stmt1])
 

	
 
    def testDeletes(self):
 
        g = ConjunctiveGraph()
 
        patchQuads(g, [], [stmt1])
 
        patchQuads(g, [stmt1], [])
 
        quads = list(g.quads((None,None,None)))
 
        self.assertEqual(quads, [])
 

	
 
    def testDeleteRunsBeforeAdd(self):
 
        g = ConjunctiveGraph()
 
        patchQuads(g, [stmt1], [stmt1])
 
        quads = list(g.quads((None,None,None)))
 
        self.assertEqual(quads, [stmt1])
 
        
 

	
 
    def testPerfectAddRejectsExistingStmt(self):
 
        g = ConjunctiveGraph()
 
        patchQuads(g, [], [stmt1])
 
        self.assertRaises(ValueError, patchQuads, g, [], [stmt1], perfect=True)
 

	
 
    def testPerfectAddAllowsExistingStmtInNewContext(self):
 
        g = ConjunctiveGraph()
 
        patchQuads(g, [], [stmt1])
 
        patchQuads(g, [], [stmt2], perfect=True)
 
        self.assertEqual(len(list(g.quads((None,None,None)))), 2)
 

	
 
    def testPerfectDeleteRejectsAbsentStmt(self):
 
        g = ConjunctiveGraph()
 
        self.assertRaises(ValueError, patchQuads, g, [stmt1], [], perfect=True)
 
        
 

	
 
    def testPerfectDeleteAllowsRemovalOfStmtInMultipleContexts(self):
 
        g = ConjunctiveGraph()
 
        patchQuads(g, [], [stmt1, stmt2])
 
        patchQuads(g, [stmt1], [], perfect=True)
 

	
 
    def testRedundantStmtOkForAddOrDelete(self):
 
        g = ConjunctiveGraph()
 
        patchQuads(g, [], [stmt1, stmt1], perfect=True)
 
        patchQuads(g, [stmt1, stmt1], [], perfect=True)
 
        
 

	
light9/rdfdb/syncedgraph.py
Show inline comments
 
@@ -62,48 +62,48 @@ class GraphWatchers(object):
 
        self._handlersPo.setdefault((p, o), set()).add(func)
 

	
 
    def whoCares(self, patch):
 
        """what handler functions would care about the changes in this patch?
 

	
 
        this removes the handlers that it gives you
 
        """
 
        #self.dependencies()
 
        affectedSubjPreds = set([(s, p) for s, p, o, c in patch.addQuads]+
 
                                [(s, p) for s, p, o, c in patch.delQuads])
 
        affectedPredObjs = set([(p, o) for s, p, o, c in patch.addQuads]+
 
                                [(p, o) for s, p, o, c in patch.delQuads])
 
        
 

	
 
        ret = set()
 
        for (s, p), funcs in self._handlersSp.iteritems():
 
            if (s, p) in affectedSubjPreds:
 
                ret.update(funcs)
 
                funcs.clear()
 
                
 

	
 
        for (p, o), funcs in self._handlersPo.iteritems():
 
            if (p, o) in affectedPredObjs:
 
                ret.update(funcs)
 
                funcs.clear()
 

	
 
        return ret
 

	
 
    def dependencies(self):
 
        """
 
        for debugging, make a list of all the active handlers and what
 
        data they depend on. This is meant for showing on the web ui
 
        for browsing.
 
        """
 
        log.info("whocares:")
 
        from pprint import pprint
 
        pprint(self._handlersSp)
 
        
 

	
 

	
 
class PatchSender(object):
 
    """
 
    SyncedGraph may generate patches faster than we can send
 
    them. This object buffers and may even collapse patches before
 
    they go the server
 
    """
 
    def __init__(self, target, myUpdateResource):
 
        self.target = target
 
        self.myUpdateResource = myUpdateResource
 
        self._patchesToSend = []
 
        self._currentSendPatchRequest = None
 
@@ -125,25 +125,25 @@ class PatchSender(object):
 
            if 0:
 
                p = self._patchesToSend[0].concat(self._patchesToSend[1:])
 
                print "concat down to"
 
                print 'dels'
 
                for q in p.delQuads: print q
 
                print 'adds'
 
                for q in p.addQuads: print q
 
                print "----"
 
            else:
 
                p, sendResult = self._patchesToSend.pop(0)
 
        else:
 
            p, sendResult = self._patchesToSend.pop(0)
 
            
 

	
 
        self._currentSendPatchRequest = sendPatch(
 
            self.target, p, senderUpdateUri=self.myUpdateResource)
 
        self._currentSendPatchRequest.addCallbacks(self._sendPatchDone,
 
                                                   self._sendPatchErr)
 
        self._currentSendPatchRequest.chainDeferred(sendResult)
 

	
 
    def _sendPatchDone(self, result):
 
        self._currentSendPatchRequest = None
 
        self._continueSending()
 

	
 
    def _sendPatchErr(self, e):
 
        self._currentSendPatchRequest = None
 
@@ -157,46 +157,46 @@ class PatchSender(object):
 
        # if someday we are folding pending patches together, this
 
        # would be the time to UNDO that and attempt the original
 
        # separate patches again
 

	
 
        # this should screen for 409 conflict responses and raise a
 
        # special exception for that, so SyncedGraph.sendFailed can
 
        # screen for only that type
 

	
 
        # this code is going away; we're going to raise an exception that contains all the pending patches
 
        log.error("_sendPatchErr")
 
        log.error(e)
 
        self._continueSending()
 
        
 

	
 

	
 
class SyncedGraph(object):
 
    """
 
    graph for clients to use. Changes are synced with the master graph
 
    in the rdfdb process.
 

	
 
    This api is like rdflib.Graph but it can also call you back when
 
    there are graph changes to the parts you previously read.
 

	
 
    If we get out of sync, we abandon our local graph (even any
 
    pending local changes) and get the data again from the
 
    server.
 
    """
 
    def __init__(self, label):
 
        """
 
        label is a string that the server will display in association
 
        with your connection
 
        """
 
        _graph = self._graph = ConjunctiveGraph()
 
        self._watchers = GraphWatchers()
 
        
 

	
 
        def onPatch(p):
 
            """
 
            central server has sent us a patch
 
            """
 
            patchQuads(_graph, p.delQuads, p.addQuads, perfect=True)
 
            log.info("graph now has %s statements" % len(_graph))
 
            try:
 
                self.updateOnPatch(p)
 
            except Exception:
 
                # don't reflect this back to the server; we did
 
                # receive its patch correctly.
 
                traceback.print_exc()
 
@@ -331,64 +331,64 @@ class SyncedGraph(object):
 

	
 
    def currentState(self, context=None):
 
        """
 
        a graph you can read without being in an addHandler
 
        """
 
        class Mgr(object):
 
            def __enter__(self2):
 
                # this should be a readonly view of the existing graph
 
                g = Graph()
 
                for s in self._graph.triples((None, None, None), context):
 
                    g.add(s)
 
                return g
 
            
 

	
 
            def __exit__(self, type, val, tb):
 
                return
 

	
 
        return Mgr()
 

	
 
    def _getCurrentFunc(self):
 
        if not self.currentFuncs:
 
            # this may become a warning later
 
            raise ValueError("asked for graph data outside of a handler")
 

	
 
        # we add the watcher to the deepest function, since that
 
        # should be the cheapest way to update when this part of the
 
        # data changes
 
        return self.currentFuncs[-1]
 

	
 
    # these just call through to triples() so it might be possible to
 
    # watch just that one.
 

	
 
    # if you get a bnode in your response, maybe the answer to
 
    # dependency tracking is to say that you depended on the triple
 
    # that got you that bnode, since it is likely to change to another
 
    # bnode later. This won't work if the receiver stores bnodes
 
    # between calls, but probably most of them don't do that (they
 
    # work from a starting uri)
 
    
 

	
 
    def value(self, subject=None, predicate=RDF.value, object=None,
 
              default=None, any=True):
 
        if object is not None:
 
            raise NotImplementedError()
 
        func = self._getCurrentFunc()
 
        self._watchers.addSubjPredWatcher(func, subject, predicate)
 
        return self._graph.value(subject, predicate, object=object,
 
                                 default=default, any=any)
 

	
 
    def objects(self, subject=None, predicate=None):
 
        func = self._getCurrentFunc()
 
        self._watchers.addSubjPredWatcher(func, subject, predicate)
 
        return self._graph.objects(subject, predicate)
 
    
 

	
 
    def label(self, uri):
 
        return self.value(uri, RDFS.label)
 

	
 
    def subjects(self, predicate=None, object=None):
 
        func = self._getCurrentFunc()
 
        self._watchers.addPredObjWatcher(func, predicate, object)
 
        return self._graph.subjects(predicate, object)
 

	
 
    # i find myself wanting 'patch' versions of these calls that tell
 
    # you only what results have just appeared or disappeared. I think
 
    # I'm going to be repeating that logic a lot. Maybe just for the
 
    # subjects(RDF.type, t) call
light9/tkdnd.py
Show inline comments
 
@@ -113,25 +113,25 @@ def dropTargetRegister(widget, typeList=
 

	
 
    if hoverStyle is not None:
 
        hover = Hover(widget, hoverStyle)
 
        def wrappedDrop(ev):
 
            hover.restore(ev)
 
            if onDrop:
 
                return onDrop(ev)
 
        return dropTargetRegister(widget, typeList=typeList,
 
                                  onDropEnter=hover.set,
 
                                  onDropLeave=hover.restore,
 
                                  onDropPosition=onDropPosition,
 
                                  onDrop=wrappedDrop)
 
    
 

	
 
    if typeList is None:
 
        typeList = ['*']
 
    widget.tk.call(*(['tkdnd::drop_target', 'register', widget._w]+typeList))
 

	
 
    for sequence, handler in [
 
        ('<<DropEnter>>', onDropEnter),
 
        ('<<DropPosition>>', onDropPosition),
 
        ('<<DropLeave>>', onDropLeave),
 
        ('<<Drop>>', onDrop),
 
        ]:
 
        if not handler:
 
            continue
light9/uihelpers.py
Show inline comments
 
@@ -46,24 +46,25 @@ def toplevelat(name, existingtoplevel=No
 
        """
 
        if setOnce[0]:
 
            return
 
        geo = graph.value(session, L9.windowGeometry)
 

	
 
        if geo is not None and geo != lastSaved[0]:
 
            setOnce[0] = True
 
            tl.geometry(geo)
 
            lastSaved[0] = geo
 

	
 
    def savePos():
 
        geo = tl.geometry()
 

	
 
        # todo: need a way to filter out the startup window sizes that
 
        # weren't set by the user
 
        if geo.startswith("1x1") or geo.startswith(("378x85", "378x86")):
 
            return
 
        if geo == lastSaved[0]:
 
            return
 
        lastSaved[0] = geo
 
        graph.patchObject(session, session, L9.windowGeometry, Literal(geo))
 

	
 
    if graph is not None and session is not None:
 
        graph.addHandler(setPosFromGraphOnce)
 

	
 
@@ -71,42 +72,42 @@ def toplevelat(name, existingtoplevel=No
 
        tl.geometry(positionOnCurrentDesktop(windowlocations[name]))
 

	
 
    if graph is not None:
 
        tl._toplevelat_funcid = tl.bind("<Configure>",lambda ev,tl=tl,name=name: savePos())
 

	
 
    return tl
 

	
 
def positionOnCurrentDesktop(xform, screenWidth=1920, screenHeight=1440):
 
    size, x, y = xform.split('+')
 
    x = int(x) % screenWidth
 
    y = int(y) % screenHeight
 
    return "%s+%s+%s" % (size, x, y)
 
    
 

	
 

	
 
def toggle_slider(s):
 
    if s.get() == 0:
 
        s.set(100)
 
    else:
 
        s.set(0)
 

	
 
# for lambda callbacks    
 
# for lambda callbacks
 
def printout(t):
 
    print t
 

	
 
def printevent(ev):
 
    for k in dir(ev):
 
        if not k.startswith('__'):
 
            print k,getattr(ev,k)
 
    print ""
 
    
 

	
 
def eventtoparent(ev,sequence):
 
    "passes an event to the parent, screws up TixComboBoxes"
 

	
 
    wid_class = str(ev.widget.__class__)
 
    if wid_class == 'Tix.ComboBox' or wid_class == 'Tix.TixSubWidget':
 
        return
 

	
 
    evdict={}
 
    for x in ['state', 'time', 'y', 'x', 'serial']:
 
        evdict[x]=getattr(ev,x)
 
#    evdict['button']=ev.num
 
    par=ev.widget.winfo_parent()
 
@@ -153,31 +154,31 @@ class Togglebutton(Button):
 
        if self._variable:
 
            self._variable.trace('w',self._varchanged)
 
            self._setstate(self._variable.get())
 
        else:
 
            self._setstate(0)
 

	
 
        self.bind("<Return>",self.invoke)
 
        self.bind("<1>",self.invoke)
 
        self.bind("<space>",self.invoke)
 

	
 
    def _varchanged(self,*args):
 
        self._setstate(self._variable.get())
 
        
 

	
 
    def invoke(self,*ev):
 
        if self._variable:
 
            self._variable.set(not self.state)
 
        else:
 
            self._setstate(not self.state)
 
        
 

	
 
        if self.oldcommand and self.state: # call command only when state goes to 1
 
            self.oldcommand()
 
        return "break"
 

	
 
    def _setstate(self,newstate):
 
        self.state = newstate
 
        if newstate: # set
 
            self.config(bg=self.downcolor,relief='sunken')
 
        else: # unset
 
            self.config(bg=self._origbkg,relief='raised')
 
        return "break"
 

	
 
@@ -189,57 +190,57 @@ class FancyDoubleVar(DoubleVar):
 
        self.namedtraces = {} # name : cbname
 
    def trace_variable(self,mode,callback):
 
        """Define a trace callback for the variable.
 

	
 
        MODE is one of "r", "w", "u" for read, write, undefine.
 
        CALLBACK must be a function which is called when
 
        the variable is read, written or undefined.
 

	
 
        Return the name of the callback.
 
        """
 
        cbname = self._master._register(callback)
 
        self._tk.call("trace", "variable", self._name, mode, cbname)
 
        
 

	
 
        # we build a list of the trace callbacks (the py functrions and the tcl functionnames)
 
        self.callbacklist[cbname] = mode
 
#        print "added trace:",callback,cbname
 
        
 

	
 
        return cbname
 
    trace=trace_variable
 
    def disable_traces(self):
 
        for cb,mode in self.callbacklist.items():
 
#            DoubleVar.trace_vdelete(self,v[0],k)
 
            self._tk.call("trace", "vdelete", self._name, mode,cb)
 
            # but no master delete!
 
            
 

	
 
    def recreate_traces(self):
 
        for cb,mode in self.callbacklist.items():
 
#            self.trace_variable(v[0],v[1])
 
            self._tk.call("trace", "variable", self._name, mode,cb)
 

	
 
    def trace_named(self, name, callback):
 
        if name in self.namedtraces:
 
            print "FancyDoubleVar: already had a trace named %s - replacing it" % name
 
            self.delete_named(name)
 

	
 
        cbname = self.trace_variable('w',callback) # this will register in self.callbacklist too
 
        
 

	
 
        self.namedtraces[name] = cbname
 
        return cbname
 
        
 

	
 
    def delete_named(self, name):
 
        if name in self.namedtraces:
 

	
 
            cbname = self.namedtraces[name]
 
            
 

	
 
            self.trace_vdelete('w',cbname)
 
	    #self._tk.call("trace","vdelete",self._name,'w',cbname)
 
            print "FancyDoubleVar: successfully deleted trace named %s" % name
 
        else:
 
            print "FancyDoubleVar: attempted to delete named %s which wasn't set to any function" % name
 

	
 
def get_selection(listbox):
 
    'Given a listbox, returns first selection as integer'
 
    selection = int(listbox.curselection()[0]) # blech
 
    return selection
 

	
 
if __name__=='__main__':
0 comments (0 inline, 0 general)