Changeset - 0b2fdbc10fd3
[Not reviewed]
default
0 1 0
Drew Perttula - 6 years ago 2019-05-27 12:01:56
drewp@bigasterisk.com
KC support data reloading better (but still has leaks)
Ignore-this: 941800c3a73ced176dd9ee39b7e849c4
1 file changed with 61 insertions and 35 deletions:
0 comments (0 inline, 0 general)
bin/keyboardcomposer
Show inline comments
 
#!bin/python
 

	
 
from run_local import log
 
import cgi, time, logging
 

	
 
from optparse import OptionParser
 
import webcolors, colorsys
 
from typing import Any, Dict, Tuple, List
 
import cgi, time, logging
 
import imp
 
import tkinter.tix as tk
 

	
 
from louie import dispatcher
 
from rdflib import URIRef, Literal
 
from twisted.internet import reactor, tksupport
 
from twisted.web import resource
 
from rdflib import URIRef, Literal
 
import tkinter.tix as tk
 
from typing import Any, Dict, Tuple, List
 

	
 
from light9.Fadable import Fadable
 
from light9.subclient import SubClient
 
from light9 import showconfig, networking, prof
 
from light9.uihelpers import toplevelat
 
from light9.namespaces import L9, RDF, RDFS
 
from light9.tkdnd import initTkdnd, dragSourceRegister, dropTargetRegister
 
from light9 import clientsession
 
from rdfdb.syncedgraph import SyncedGraph
 
from light9.effect.sequencer import CodeWatcher
 
import light9.effect.effecteval
 
from light9.effect.settings import DeviceSettings
 
from rdfdb.patch import Patch
 
from light9.effect.simple_outputs import SimpleOutputs
 
import webcolors, colorsys
 

	
 
from bcf2000 import BCF2000
 
import imp
 
from light9 import clientsession
 
from light9 import showconfig, networking, prof
 
from light9.Fadable import Fadable
 
from light9.effect.sequencer import CodeWatcher
 
from light9.effect.settings import DeviceSettings
 
from light9.effect.simple_outputs import SimpleOutputs
 
from light9.namespaces import L9, RDF, RDFS
 
from light9.subclient import SubClient
 
from light9.tkdnd import initTkdnd, dragSourceRegister, dropTargetRegister
 
from light9.uihelpers import toplevelat
 
from rdfdb.patch import Patch
 
from rdfdb.syncedgraph import SyncedGraph
 
import light9.effect.effecteval
 

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

	
 

	
 
class DummySliders:
 

	
 
    def valueOut(self, name, value):
 
        pass
 

	
 
    def close(self):
 
        pass
 

	
 
@@ -69,80 +70,99 @@ class SubScale(tk.Scale, Fadable):
 

	
 
    def draw_indicator_colors(self):
 
        if self.scale_var.get() == 0:
 
            self['troughcolor'] = 'black'
 
        else:
 
            self['troughcolor'] = 'blue'
 

	
 

	
 
class SubmasterBox(tk.Frame):
 
    """
 
    this object owns the level of the submaster (the rdf graph is the
 
    real authority)
 

	
 
    This leaks handlers or DoubleVars or something and tries to just
 
    skip the obsolete ones. It'll get slower and bigger over
 
    time. todo: make aa web version.
 
    """
 

	
 
    def __init__(self, master, graph, sub, session, col, row):
 
        self.graph = graph
 
        self.sub = sub
 
        self.session = session
 
        self.col, self.row = col, row
 
        bg = self.graph.value(sub, 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([
 
                int(x * 255) for x in colorsys.hsv_to_rgb(hsv[0], hsv[1], .2)
 
            ]))
 
        tk.Frame.__init__(self, master, bd=1, relief='raised', bg=bg)
 
        self.name = self.graph.label(sub)
 
        self._val = 0.0
 
        self.slider_var = tk.DoubleVar()
 
        self.pauseTrace = False
 
        self.scale = SubScale(self, variable=self.slider_var, width=20)
 

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

	
 
        self.namelabel.pack(side=tk.TOP)
 
        levellabel = tk.Label(self,
 
        self.levellabel = tk.Label(self,
 
                              textvariable=self.slider_var,
 
                              font="Arial 6",
 
                              bg='black',
 
                              fg='white',
 
                              pady=0)
 
        levellabel.pack(side=tk.TOP)
 
        self.levellabel.pack(side=tk.TOP)
 
        self.scale.pack(side=tk.BOTTOM, expand=1, fill=tk.BOTH)
 

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

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

	
 
        self.graph.addHandler(self.updateLevelFromGraph)
 

	
 
        # initial position
 
        # stil need? dispatcher.send("send_to_hw", sub=sub.uri, hwCol=col + 1)
 

	
 
    def getVal(self) -> float:
 
        return self._val
 
    
 
    def setVal(self, newVal: float) -> None:
 
        if self.scale is None:
 
            return
 
        try:
 
            self.scale.set(newVal)
 
            self.levellabel.config(text=str(newVal))
 
        except Exception:
 
            log.warn("disabling handlers on broken subbox")
 
            self.scale = None
 
        
 
    def cleanup(self):
 
        self.slider_var.trace_vdelete('w', self._slider_var_trace)
 

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

	
 
        if self.pauseTrace:
 
            return
 
        self.updateGraphWithLevel(self.sub, self.slider_var.get())
 
        self.updateGraphWithLevel(self.sub, self.getVal())
 

	
 
        # 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
 
        dispatcher.send("send_to_hw",
 
                        sub=self.sub,
 
                        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]
 
        """
 
@@ -157,35 +177,41 @@ class SubmasterBox(tk.Frame):
 
                                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.graph
 

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

	
 
                    
 
    def updateName(self):
 

	
 
        if self.scale is None:
 
            return
 
        
 
        def shortUri(u):
 
            return '.../' + u.split('/')[-1]
 

	
 
        self.namelabel.config(
 
            text=self.graph.label(self.sub) or shortUri(self.sub))
 
        try:
 
            self.namelabel.config(
 
                text=self.graph.label(self.sub) or shortUri(self.sub))
 
        except Exception:
 
            log.warn("disabling handlers on broken subbox")
 
            self.scale = None
 

	
 

	
 
class KeyboardComposer(tk.Frame, SubClient):
 

	
 
    def __init__(self,
 
                 root: tk.Tk,
 
                 graph: SyncedGraph,
 
                 session: URIRef,
 
                 hw_sliders=True):
 
        tk.Frame.__init__(self, root, bg='black')
 
        SubClient.__init__(self)
 
        self.graph = graph
 
@@ -433,25 +459,25 @@ class KeyboardComposer(tk.Frame, SubClie
 
        value = int(value * 100) / 100
 
        try:
 
            subbox = self.slider_table[(self.current_row, col)]
 
        except KeyError:
 
            return  # no slider assigned at that column
 

	
 
        if hasattr(self, 'pendingHwSet'):
 
            import twisted.internet.error
 
            try:
 
                self.pendingHwSet.cancel()
 
            except twisted.internet.error.AlreadyCalled:
 
                pass
 
        self.pendingHwSet = reactor.callLater(.01, subbox.slider_var.set, value)
 
        self.pendingHwSet = reactor.callLater(.01, subbox.safeSliderSet, value)
 

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

	
 
        assert isinstance(sub, URIRef), repr(sub)
 

	
 
        if boxRow != self.current_row:
 
            return
 

	
 
        try:
 
            level = self.get_levels()[sub]
 
@@ -497,25 +523,25 @@ class KeyboardComposer(tk.Frame, SubClie
 
                               predicate=L9['group'],
 
                               newObject=group)
 

	
 
    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.getVal())
 
                     for uri, box in list(self.subbox.items())])
 

	
 
    def get_output_settings(self, _graph=None):
 
        _graph = _graph or self.graph
 
        outputSettings = []
 
        for setting in _graph.objects(self.session, L9['subSetting']):
 
            effect = _graph.value(setting, L9['sub'])
 
            strength = _graph.value(setting, L9['level'])
 
            if strength:
 
                now = time.time()
 
                out, report = self.effectEval[effect].outputFromEffect(
 
                    [(L9['strength'], strength)],
 
@@ -611,29 +637,28 @@ class Sliders(BCF2000):
 
        if name.startswith("slider"):
 
            kc.hw_slider_moved(int(name[6:]) - 1, value / 127)
 
        elif name.startswith("button-upper"):
 
            kc.change_row(kc.current_row)
 
        elif name.startswith("button-lower"):
 
            col = int(name[12:]) - 1
 
            self.valueOut(name, 0)
 
            try:
 
                tkslider = kc.slider_table[(kc.current_row, col)]
 
            except KeyError:
 
                return
 

	
 
            slider_var = tkslider.slider_var
 
            if slider_var.get() == 1:
 
                slider_var.set(0)
 
            if tkslider.getVal() == 1.0:
 
                tkslider.setVal(0.0)
 
            else:
 
                slider_var.set(1)
 
                tkslider.setVal(1.0)
 
        elif name.startswith("button-corner"):
 
            button_num = int(name[13:]) - 1
 
            if button_num == 1:
 
                diff = -1
 
            elif button_num == 3:
 
                diff = 1
 
            else:
 
                return
 

	
 
            kc.change_row(kc.current_row + diff)
 
            self.valueOut(name, 0)
 

	
 
@@ -653,24 +678,25 @@ def launch(opts: Any, root: tk.Tk, graph
 

	
 

	
 
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()
 

	
 
    log.setLevel(logging.DEBUG if opts.v else logging.WARN)
 
    logging.getLogger('colormath').setLevel(logging.INFO)
 

	
 
    graph = SyncedGraph(networking.rdfdb.url, "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.Tk()
 
    initTkdnd(root.tk, 'tkdnd/trunk/')
 

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

	
 
    graph.initiallySynced.addCallback(lambda _: launch(opts, root, graph,
0 comments (0 inline, 0 general)