comparison bin/keyboardcomposer @ 799:fcf95ff23cc5

PersistentSubmaster split. keyboardcomposer now notices submaster changes Ignore-this: 2ea847e25af9b784cfec6aa4335dcc70
author drewp@bigasterisk.com
date Mon, 16 Jul 2012 21:51:04 +0000
parents 62f99e2a00ac
children caeaa88430b8
comparison
equal deleted inserted replaced
798:5c158d37f1ce 799:fcf95ff23cc5
2 2
3 from __future__ import division, nested_scopes 3 from __future__ import division, nested_scopes
4 import cgi, os, sys, time, subprocess, logging 4 import cgi, os, sys, time, subprocess, logging
5 from optparse import OptionParser 5 from optparse import OptionParser
6 import webcolors, colorsys 6 import webcolors, colorsys
7 7 from louie import dispatcher
8 from twisted.internet import reactor, tksupport 8 from twisted.internet import reactor, tksupport
9 from twisted.web import xmlrpc, server, resource 9 from twisted.web import xmlrpc, server, resource
10 from Tix import * 10 from Tix import *
11 import Tix as tk 11 import Tix as tk
12 import pickle 12 import pickle
17 from light9.subclient import SubClient 17 from light9.subclient import SubClient
18 from light9 import dmxclient, showconfig, networking, prof 18 from light9 import dmxclient, showconfig, networking, prof
19 from light9.uihelpers import toplevelat, bindkeys 19 from light9.uihelpers import toplevelat, bindkeys
20 from light9.namespaces import L9 20 from light9.namespaces import L9
21 from light9.tkdnd import initTkdnd, dragSourceRegister 21 from light9.tkdnd import initTkdnd, dragSourceRegister
22 from light9.rdfdb.syncedgraph import SyncedGraph
23
22 from bcf2000 import BCF2000 24 from bcf2000 import BCF2000
23 25
24 nudge_keys = { 26 nudge_keys = {
25 'up' : list('qwertyui'), 27 'up' : list('qwertyui'),
26 'down' : list('asdfghjk') 28 'down' : list('asdfghjk')
53 else: 55 else:
54 self['troughcolor'] = 'blue' 56 self['troughcolor'] = 'blue'
55 57
56 class SubmasterTk(Frame): 58 class SubmasterTk(Frame):
57 def __init__(self, master, sub, current_level): 59 def __init__(self, master, sub, current_level):
60 self.sub = sub
58 bg = sub.graph.value(sub.uri, L9.color, default='#000000') 61 bg = sub.graph.value(sub.uri, L9.color, default='#000000')
59 rgb = webcolors.hex_to_rgb(bg) 62 rgb = webcolors.hex_to_rgb(bg)
60 hsv = colorsys.rgb_to_hsv(*[x/255 for x in rgb]) 63 hsv = colorsys.rgb_to_hsv(*[x/255 for x in rgb])
61 darkBg = webcolors.rgb_to_hex(tuple([x * 255 for x in colorsys.hsv_to_rgb( 64 darkBg = webcolors.rgb_to_hex(tuple([x * 255 for x in colorsys.hsv_to_rgb(
62 hsv[0], hsv[1], .3)])) 65 hsv[0], hsv[1], .3)]))
63 Frame.__init__(self, master, bd=1, relief='raised', bg=bg) 66 Frame.__init__(self, master, bd=1, relief='raised', bg=bg)
64 self.name = sub.name 67 self.name = sub.name
65 self.slider_var = DoubleVar() 68 self.slider_var = DoubleVar()
66 self.slider_var.set(current_level) 69 self.slider_var.set(current_level)
67 self.scale = SubScale(self, variable=self.slider_var, width=20) 70 self.scale = SubScale(self, variable=self.slider_var, width=20)
68 namelabel = Label(self, text=sub.name, font="Arial 7", bg=darkBg, 71
72 self.namelabel = Label(self, font="Arial 7", bg=darkBg,
69 fg='white', pady=0) 73 fg='white', pady=0)
70 namelabel.pack(side=TOP) 74 self.sub.graph.addHandler(self.updateName)
75
76 self.namelabel.pack(side=TOP)
71 levellabel = Label(self, textvariable=self.slider_var, font="Arial 7", 77 levellabel = Label(self, textvariable=self.slider_var, font="Arial 7",
72 bg='black', fg='white', pady=0) 78 bg='black', fg='white', pady=0)
73 levellabel.pack(side=TOP) 79 levellabel.pack(side=TOP)
74 self.scale.pack(side=BOTTOM, expand=1, fill=BOTH) 80 self.scale.pack(side=BOTTOM, expand=1, fill=BOTH)
75 bindkeys(self, "<Control-Key-l>", self.launch_subcomposer) 81 bindkeys(self, "<Control-Key-l>", self.launch_subcomposer)
76 82
77 for w in [self, namelabel, levellabel]: 83 for w in [self, self.namelabel, levellabel]:
78 dragSourceRegister(w, 'copy', 'text/uri-list', sub.uri) 84 dragSourceRegister(w, 'copy', 'text/uri-list', sub.uri)
85
86 def updateName(self):
87 self.namelabel.config(text=self.sub.graph.label(self.sub.uri))
79 88
80 def launch_subcomposer(self, *args): 89 def launch_subcomposer(self, *args):
81 subprocess.Popen(["bin/subcomposer", "--no-geometry", self.name]) 90 subprocess.Popen(["bin/subcomposer", "--no-geometry", self.name])
82 91
83 class KeyboardComposer(Frame, SubClient): 92 class KeyboardComposer(Frame, SubClient):
84 def __init__(self, root, graph, submasters, current_sub_levels=None, 93 def __init__(self, root, graph, current_sub_levels=None,
85 hw_sliders=True): 94 hw_sliders=True):
86 Frame.__init__(self, root, bg='black') 95 Frame.__init__(self, root, bg='black')
87 SubClient.__init__(self) 96 SubClient.__init__(self)
88 self.graph = graph 97 self.graph = graph
89 self.submasters = submasters 98 self.submasters = Submasters(graph)
90 self.name_to_subtk = {} 99 self.name_to_subtk = {}
91 self.current_sub_levels = {} 100 self.current_sub_levels = {}
92 self.current_row = 0 101 self.current_row = 0
93 if current_sub_levels is not None: 102 if current_sub_levels is not None:
94 self.current_sub_levels = current_sub_levels 103 self.current_sub_levels = current_sub_levels
99 except IOError: 108 except IOError:
100 pass 109 pass
101 110
102 self.use_hw_sliders = hw_sliders 111 self.use_hw_sliders = hw_sliders
103 self.connect_to_hw(hw_sliders) 112 self.connect_to_hw(hw_sliders)
104 self.draw_ui() 113
114 self.make_key_hints()
115 self.make_buttons()
116
117 self.graph.addHandler(self.redraw_sliders)
105 self.send_levels_loop() 118 self.send_levels_loop()
106 119
107 def draw_ui(self): 120 def make_buttons(self):
108 self.rows = [] # this holds Tk Frames for each row
109 self.slider_vars = {} # this holds subname:sub Tk vars
110 self.slider_table = {} # this holds coords:sub Tk vars
111 self.name_to_subtk.clear() # subname : SubmasterTk instance
112
113 self.make_key_hints()
114 self.draw_sliders()
115 if len(self.rows):
116 self.change_row(self.current_row)
117 self.rows[self.current_row].focus()
118
119 self.buttonframe = Frame(self, bg='black') 121 self.buttonframe = Frame(self, bg='black')
120 self.buttonframe.pack(side=BOTTOM) 122 self.buttonframe.pack(side=BOTTOM)
121 123
122 self.sliders_status_var = IntVar() 124 self.sliders_status_var = IntVar()
123 self.sliders_status_var.set(self.use_hw_sliders) 125 self.sliders_status_var.set(self.use_hw_sliders)
129 131
130 self.alltozerobutton = Button(self.buttonframe, text="All to Zero", 132 self.alltozerobutton = Button(self.buttonframe, text="All to Zero",
131 command=self.alltozero, bg='black', fg='white') 133 command=self.alltozero, bg='black', fg='white')
132 self.alltozerobutton.pack(side='left') 134 self.alltozerobutton.pack(side='left')
133 135
134 self.refreshbutton = Button(self.buttonframe, text="Refresh",
135 command=self.refresh, bg='black', fg='white')
136 self.refreshbutton.pack(side=LEFT)
137
138 self.save_stage_button = Button(self.buttonframe, text="Save", 136 self.save_stage_button = Button(self.buttonframe, text="Save",
139 command=lambda: self.save_current_stage(self.sub_name.get()), 137 command=lambda: self.save_current_stage(self.sub_name.get()),
140 bg='black', fg='white') 138 bg='black', fg='white')
141 self.save_stage_button.pack(side=LEFT) 139 self.save_stage_button.pack(side=LEFT)
142 self.sub_name = Entry(self.buttonframe, bg='black', fg='white') 140 self.sub_name = Entry(self.buttonframe, bg='black', fg='white')
143 self.sub_name.pack(side=LEFT) 141 self.sub_name.pack(side=LEFT)
144 142
143
144 def redraw_sliders(self):
145 self.slider_vars = {} # this holds subname:sub Tk vars
146 self.slider_table = {} # this holds coords:sub Tk vars
147 self.name_to_subtk.clear() # subname : SubmasterTk instance
148
149 self.graph.addHandler(self.draw_sliders)
150 if len(self.rows):
151 self.change_row(self.current_row)
152 self.rows[self.current_row].focus()
153
145 self.stop_frequent_update_time = 0 154 self.stop_frequent_update_time = 0
146 155
156 def onNewSub(self, sub):
157 log.info("new %s", sub)
158 self.graph.addHandler(self.draw_sliders)
159
160 def onLostSub(self, subUri):
161 log.info("lost %s", subUri)
162 self.graph.addHandler(self.draw_sliders)
163
147 def draw_sliders(self): 164 def draw_sliders(self):
165
166
167 if hasattr(self, 'rows'):
168 for r in self.rows:
169 r.destroy()
170 self.rows = [] # this holds Tk Frames for each row
171
172
148 self.tk_focusFollowsMouse() 173 self.tk_focusFollowsMouse()
149 174
150 rowcount = -1 175 rowcount = -1
151 col = 0 176 col = 0
152 last_group = None 177 last_group = None
178
179 # there are unlikely to be any subs at startup because we
180 # probably haven't been called back with the graph data yet
181
182 #read get_all_subs then watch 'new submaster' 'lost submaster' signals
153 withgroups = sorted((self.graph.value(sub.uri, L9['group']), 183 withgroups = sorted((self.graph.value(sub.uri, L9['group']),
154 self.graph.value(sub.uri, L9['order']), 184 self.graph.value(sub.uri, L9['order']),
155 sub) 185 sub)
156 for sub in self.submasters.get_all_subs()) 186 for sub in self.submasters.get_all_subs())
187 dispatcher.connect(self.onNewSub, "new submaster")
188 dispatcher.connect(self.onLostSub, "lost submaster")
189 log.info("withgroups %s", withgroups)
157 190
158 for group, order, sub in withgroups: 191 for group, order, sub in withgroups:
159 group = self.graph.value(sub.uri, L9['group']) 192 group = self.graph.value(sub.uri, L9['group'])
160 193
161 if col == 0 or group != last_group: # make new row 194 if col == 0 or group != last_group: # make new row
201 def connect_to_hw(self, hw_sliders): 234 def connect_to_hw(self, hw_sliders):
202 if hw_sliders: 235 if hw_sliders:
203 try: 236 try:
204 self.sliders = Sliders(self) 237 self.sliders = Sliders(self)
205 except IOError: 238 except IOError:
206 print "Couldn't actually find any sliders (but really, it's no problem)" 239 log.info("no hardware sliders")
207 self.sliders = DummySliders() 240 self.sliders = DummySliders()
208 self.use_hw_sliders = False 241 self.use_hw_sliders = False
209 else: 242 else:
210 self.sliders = DummySliders() 243 self.sliders = DummySliders()
211 244
254 def change_row_cb(self, event): 287 def change_row_cb(self, event):
255 diff = 1 288 diff = 1
256 if event.keysym in ('Prior', 'p', 'bracketright'): 289 if event.keysym in ('Prior', 'p', 'bracketright'):
257 diff = -1 290 diff = -1
258 self.change_row(self.current_row + diff) 291 self.change_row(self.current_row + diff)
292
259 def change_row(self, row): 293 def change_row(self, row):
260 old_row = self.current_row 294 old_row = self.current_row
261 self.current_row = row 295 self.current_row = row
262 self.current_row = max(0, self.current_row) 296 self.current_row = max(0, self.current_row)
263 self.current_row = min(len(self.rows) - 1, self.current_row) 297 self.current_row = min(len(self.rows) - 1, self.current_row)
326 return row 360 return row
327 361
328 def highlight_row(self, row): 362 def highlight_row(self, row):
329 row = self.rows[row] 363 row = self.rows[row]
330 row['bg'] = 'red' 364 row['bg'] = 'red'
365
331 def unhighlight_row(self, row): 366 def unhighlight_row(self, row):
332 row = self.rows[row] 367 row = self.rows[row]
333 row['bg'] = 'black' 368 row['bg'] = 'black'
369
334 def get_levels(self): 370 def get_levels(self):
335 return dict([(name, slidervar.get()) 371 return dict([(name, slidervar.get())
336 for name, slidervar in self.slider_vars.items()]) 372 for name, slidervar in self.slider_vars.items()])
373
337 def get_levels_as_sub(self): 374 def get_levels_as_sub(self):
338 scaledsubs = [self.submasters.get_sub_by_name(sub) * level \ 375 scaledsubs = [self.submasters.get_sub_by_name(sub) * level \
339 for sub, level in self.get_levels().items() if level > 0.0] 376 for sub, level in self.get_levels().items() if level > 0.0]
340 377
341 maxes = sub_maxes(*scaledsubs) 378 maxes = sub_maxes(*scaledsubs)
342 return maxes 379 return maxes
380
343 def save_current_stage(self, subname): 381 def save_current_stage(self, subname):
344 print "saving current levels as", subname 382 log.info("saving current levels as %s", subname)
345 sub = self.get_levels_as_sub() 383 sub = self.get_levels_as_sub()
346 sub.name = subname 384 sub.name = subname
347 sub.temporary = 0 385 sub.temporary = 0
348 sub.save() 386 sub.save()
349 387
353 def send_frequent_updates(self): 391 def send_frequent_updates(self):
354 """called when we get a fade -- send events as quickly as possible""" 392 """called when we get a fade -- send events as quickly as possible"""
355 if time.time() <= self.stop_frequent_update_time: 393 if time.time() <= self.stop_frequent_update_time:
356 self.send_levels() 394 self.send_levels()
357 self.after(10, self.send_frequent_updates) 395 self.after(10, self.send_frequent_updates)
358
359 def refresh(self):
360 self.save()
361 graph = showconfig.getGraph()
362 self.submasters = Submasters(graph)
363 self.current_sub_levels, self.current_row = \
364 pickle.load(file('.keyboardcomposer.savedlevels'))
365 for r in self.rows:
366 r.destroy()
367 self.keyhints.destroy()
368 self.buttonframe.destroy()
369 self.draw_ui()
370 # possibly paranoia (but possibly not)
371 self.change_row(self.current_row)
372 396
373 def alltozero(self): 397 def alltozero(self):
374 for name, subtk in self.name_to_subtk.items(): 398 for name, subtk in self.name_to_subtk.items():
375 if subtk.scale.scale_var.get() != 0: 399 if subtk.scale.scale_var.get() != 0:
376 subtk.scale.fade(value=0.0, length=0) 400 subtk.scale.fade(value=0.0, length=0)
461 help="don't attach to hardware sliders") 485 help="don't attach to hardware sliders")
462 parser.add_option('-v', action='store_true', help="log info level") 486 parser.add_option('-v', action='store_true', help="log info level")
463 opts, args = parser.parse_args() 487 opts, args = parser.parse_args()
464 488
465 logging.basicConfig(level=logging.INFO if opts.v else logging.WARN) 489 logging.basicConfig(level=logging.INFO if opts.v else logging.WARN)
466 log = logging.getLogger() 490 log = logging.getLogger('keyboardcomposer')
467 491
468 graph = showconfig.getGraph() 492 graph = SyncedGraph("keyboardcomposer")
469 s = Submasters(graph)
470 493
471 root = Tk() 494 root = Tk()
472 initTkdnd(root.tk, 'tkdnd/trunk/') 495 initTkdnd(root.tk, 'tkdnd/trunk/')
473 496
474 tl = toplevelat("Keyboard Composer", existingtoplevel=root) 497 tl = toplevelat("Keyboard Composer", existingtoplevel=root)
475 498
476 startLevels = None 499 startLevels = None
477 if opts.nonpersistent: 500 if opts.nonpersistent:
478 startLevels = {} 501 startLevels = {}
479 kc = KeyboardComposer(tl, graph, s, startLevels, 502 kc = KeyboardComposer(tl, graph, startLevels,
480 hw_sliders=not opts.no_sliders) 503 hw_sliders=not opts.no_sliders)
481 kc.pack(fill=BOTH, expand=1) 504 kc.pack(fill=BOTH, expand=1)
482 505
483 for helpline in ["Bindings: B3 mute; C-l edit levels in subcomposer"]: 506 for helpline in ["Bindings: B3 mute; C-l edit levels in subcomposer"]:
484 tk.Label(root,text=helpline, font="Helvetica -12 italic", 507 tk.Label(root,text=helpline, font="Helvetica -12 italic",
487 import twisted.internet 510 import twisted.internet
488 try: 511 try:
489 reactor.listenTCP(networking.keyboardComposer.port, 512 reactor.listenTCP(networking.keyboardComposer.port,
490 server.Site(LevelServerHttp(kc.name_to_subtk))) 513 server.Site(LevelServerHttp(kc.name_to_subtk)))
491 except twisted.internet.error.CannotListenError, e: 514 except twisted.internet.error.CannotListenError, e:
492 print "Can't (and won't!) start level server:" 515 log.warn("Can't (and won't!) start level server:")
493 print e 516 log.warn(e)
494 517
495 root.protocol('WM_DELETE_WINDOW', reactor.stop) 518 root.protocol('WM_DELETE_WINDOW', reactor.stop)
496 if not opts.nonpersistent: 519 if not opts.nonpersistent:
497 reactor.addSystemEventTrigger('after', 'shutdown', kc.save) 520 reactor.addSystemEventTrigger('after', 'shutdown', kc.save)
498 521
499 tksupport.install(root,ms=10) 522 tksupport.install(root,ms=10)
500 523
501 524
502 # prof.watchPoint("/usr/lib/python2.4/site-packages/rdflib-2.3.3-py2.4-linux-i686.egg/rdflib/Graph.py", 615) 525 # prof.watchPoint("/usr/lib/python2.4/site-packages/rdflib-2.3.3-py2.4-linux-i686.egg/rdflib/Graph.py", 615)
503 526
504 prof.run(reactor.run, profile=False) 527 prof.run(reactor.run, profile=False)