Changeset - c0f7fc86f89b
[Not reviewed]
default
0 1 0
drewp@bigasterisk.com - 12 years ago 2013-06-15 06:09:57
drewp@bigasterisk.com
fix up hardware sliders in KC. big mess now
Ignore-this: 28956846058bf05ad44d9eeea2796c75
1 file changed with 23 insertions and 12 deletions:
0 comments (0 inline, 0 general)
bin/keyboardcomposer
Show inline comments
 
@@ -19,145 +19,144 @@ from light9.Patch import get_channel_uri
 
from light9.subclient import SubClient
 
from light9 import dmxclient, showconfig, networking, prof
 
from light9.uihelpers import toplevelat, bindkeys
 
from light9.namespaces import L9
 
from light9.tkdnd import initTkdnd, dragSourceRegister, dropTargetRegister
 
from light9.rdfdb import clientsession
 
from light9.rdfdb.syncedgraph import SyncedGraph
 
from light9.rdfdb.patch import Patch
 

	
 
from bcf2000 import BCF2000
 

	
 
nudge_keys = {
 
    'up'   : list('qwertyui'),
 
    'down' : list('asdfghjk')
 
}
 

	
 
class DummySliders:
 
    def valueOut(self, name, value):
 
        pass
 
    def close(self):
 
        pass
 
    def reopen(self):
 
        pass
 

	
 
class SubScale(Scale, Fadable):
 
    def __init__(self, master, *args, **kw):
 
        self.scale_var = kw.get('variable') or DoubleVar()
 
        kw.update({'variable' : self.scale_var,
 
                   'from' : 1, 'to' : 0, 'showvalue' : 0,
 
                   'sliderlength' : 15, 'res' : 0.01,
 
                   'width' : 40, 'troughcolor' : 'black', 'bg' : 'grey40',
 
                   'highlightthickness' : 1, 'bd' : 1,
 
                   'highlightcolor' : 'red', 'highlightbackground' : 'black',
 
                   'activebackground' : 'red'})
 
        Scale.__init__(self, master, *args, **kw)
 
        Fadable.__init__(self, var=self.scale_var, wheel_step=0.05)
 
        self.draw_indicator_colors()
 
    def draw_indicator_colors(self):
 
        if self.scale_var.get() == 0:
 
            self['troughcolor'] = 'black'
 
        else:
 
            self['troughcolor'] = 'blue'
 

	
 
class SubmasterBox(Frame):
 
    """
 
    this object owns the level of the submaster (the rdf graph is the
 
    real authority)
 
    """
 
    def __init__(self, master, sub, session):
 
    def __init__(self, master, sub, session, col, row):
 
        self.sub = sub
 
        self.session = session
 
        self.col, self.row = col, row
 
        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)
 

	
 
        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
 
        # stil need? dispatcher.send("send_to_hw", sub=sub.uri, hwCol=col + 1)
 

	
 
    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?
 

	
 
        # 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)
 
        dispatcher.send("send_to_hw", sub=self.sub.uri, hwCol=self.col + 1,
 
                        boxRow=self.row)
 

	
 
    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
 

	
 
        self.sub.graph.patchMapping(context=self.session,
 
                                    subject=self.session,
 
                                    predicate=L9['subSetting'],
 
                                    nodeClass=L9['SubSetting'],
 
                                    keyPred=L9['sub'], newKey=uri,
 
                                    valuePred=L9['level'], newValue=Literal(level))
 

	
 
    def updateLevelFromGraph(self):
 
        """read rdf level, write it to subbox.slider_var"""
 
        # move this to syncedgraph readMapping
 
        graph = self.sub.graph
 

	
 
        for setting in graph.objects(self.session, L9['subSetting']):
 
            if graph.value(setting, L9['sub']) == self.sub.uri:
 
                self.pauseTrace = True # don't bounce this update back to server
 
                try:
 
                    self.slider_var.set(graph.value(setting, L9['level']))
 
                finally:
 
                    self.pauseTrace = False
 

	
 
    def updateName(self):
 
        def shortUri(u):
 
            return '.../' + u.split('/')[-1]
 
        self.namelabel.config(text=self.sub.graph.label(self.sub.uri) or shortUri(self.sub.uri))
 

	
 
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
 
@@ -191,242 +190,254 @@ class KeyboardComposer(Frame, SubClient)
 
            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 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()
 

	
 
        self.submasters.findSubs() # trigger graph load, but we read
 
                                   # from get_all_subs, below
 
        
 
        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']),
 
                             sub)
 
            for sub in self.submasters.get_all_subs())
 
        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(group)
 
                rowcount += 1
 
                col = 0
 

	
 
            subbox = SubmasterBox(row, sub, self.session)
 
            subbox = SubmasterBox(row, sub, self.session, col, rowcount)
 
            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:
 
                self.sliders = Sliders(self)
 
            except IOError:
 
                log.info("no hardware sliders")
 
                self.sliders = DummySliders()
 
                self.use_hw_sliders = False
 
            dispatcher.connect(self.send_to_hw, 'send_to_hw')
 
        else:
 
            self.sliders = DummySliders()
 

	
 
    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),
 
                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
 
                tkobject.bind(keysym, \
 
                    lambda evt, num=keys.index(key), d=d: \
 
                        self.got_nudger(num, d))
 

	
 
                # uppercase makes full=1
 
                keysym = "<KeyPress-%s>" % key.upper()
 
                keysym = keysym.replace('SEMICOLON', 'colon')
 
                tkobject.bind(keysym, \
 
                    lambda evt, num=keys.index(key), d=d: \
 
                        self.got_nudger(num, d, full=1))
 

	
 
        # Row changing:
 
        # Page dn, C-n, and ] do down
 
        # Page up, C-p, and ' do up
 
        for key in '<Prior> <Next> <Control-n> <Control-p> ' \
 
                   '<Key-bracketright> <Key-apostrophe>'.split():
 
            tkobject.bind(key, self.change_row_cb)
 

	
 
    def change_row_cb(self, event):
 
        diff = 1
 
        if event.keysym in ('Prior', 'p', 'bracketright'):
 
            diff = -1
 
        self.change_row(self.current_row + diff)
 

	
 
    def rowFromGraph(self):
 
        self.change_row(int(self.graph.value(self.session, L9['currentRow'], default=0)), fromGraph=True)
 

	
 
    def change_row(self, row, fromGraph=False):
 
        old_row = self.current_row
 
        self.current_row = row
 
        self.current_row = max(0, self.current_row)
 
        self.current_row = min(len(self.rows) - 1, self.current_row)
 
        try:
 
            row = self.rows[self.current_row]
 
        except IndexError:
 
            # if we're mid-load, this row might still appear soon. If
 
            # we changed interactively, the user is out of bounds and
 
            # needs to be brought back in
 
            if fromGraph:
 
                return
 
            raise
 

	
 
        self.unhighlight_row(old_row)
 
        self.highlight_row(self.current_row)
 
        self.keyhints.pack_configure(before=row)
 

	
 
        if not fromGraph:
 
            self.graph.patchObject(self.session, self.session, L9['currentRow'],
 
                                   Literal(self.current_row))
 

	
 
        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)
 
            self.send_to_hw(sub=subbox.sub.uri, hwCol=col,
 
                            boxRow=self.current_row)
 

	
 
    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:
 
            if full:
 
                subbox.scale.fade(0)
 
            else:
 
                subbox.scale.decrease()
 

	
 
    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)
 
        subbox.slider_var.set(value)
 

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

	
 
        v = round(127 * self.slider_vars[subUri].get())
 
        chan = "slider%s" % hwNum
 
        assert isinstance(sub, URIRef), repr(subUri)
 

	
 
        if boxRow != self.current_row:
 
            return
 
        
 
        try:
 
            level = self.get_levels()[sub]
 
        except KeyError:
 
            log.warn("%r not in %r", sub, self.get_levels())
 
            raise
 
        v = round(127 * level)
 
        chan = "slider%s" % hwCol
 

	
 
        # 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, group):
 
        """group is a URI or None"""
 
        row = Frame(self, bd=2, bg='black')
 
        row.subGroup = group
 
        def onDrop(ev):
 
            self.change_group(sub=URIRef(ev.data), row=row)
 
            return "link"
 
        
 
        dropTargetRegister(row, onDrop=onDrop, typeList=['*'],
 
                           hoverStyle=dict(background="#555500"))
 
        
 
        row.pack(expand=1, fill=BOTH)
 
        self.setup_key_nudgers(row)
 
        self.rows.append(row)
 
        return row
 

	
 
    def change_group(self, sub, row):
 
        """update this sub's group, and maybe other sub groups as needed, so
 
        this sub displays in this row"""
 
        group = row.subGroup
 
        self.graph.patchObject(
 
            context=self.session,
 
            subject=sub, predicate=L9['group'], newObject=group)
 

	
 
    def subs_in_row(self, row):
 
        """uris of the submasters displayed in this row. untested"""
 
        rowNum = self.rows.index(row) + 1
 
        ret = set()
 
        for coords, subbox in self.slider_table.items():
 
            if coords[0] == rowNum:
 
                ret.add(subbox.sub.uri)
 
        return ret
 
            
 
    def highlight_row(self, row):
 
        row = self.rows[row]
 
        row['bg'] = 'red'
 

	
 
    def unhighlight_row(self, row):
 
        row = self.rows[row]
0 comments (0 inline, 0 general)