comparison flax/CueFaders.py @ 151:990a9474d0e7

early cue stuff. the CueList will supply the CueFader with the cues to early cue stuff. the CueList will supply the CueFader with the cues to work with, and will do crossfading sooner or later. the format of cues is still very open. cuelist1 is a bogus cuelist for testing.
author dmcc
date Sun, 06 Jul 2003 16:33:06 +0000
parents 45b12307c695
children 4c3060ceddcc
comparison
equal deleted inserted replaced
150:1e4814221a64 151:990a9474d0e7
1 from __future__ import division, nested_scopes 1 from __future__ import division, nested_scopes
2 import Tix as Tk 2 import Tix as Tk
3 import time 3 import time
4 from TreeDict import TreeDict, allow_class_to_be_pickled 4 from TreeDict import TreeDict, allow_class_to_be_pickled
5 from TLUtility import enumerate
6 import Submaster, dmxclient
7
8 cue_state_indicator_colors = {
9 # bg fg
10 'prev' : ('blue', 'white'),
11 'cur' : ('yellow', 'black'),
12 'next' : ('red', 'white'),
13 }
14
15 # TODO
16 # FIXE pause fades, set new time to be remaining about of time in the fade so
17 # fade can continue properly
18 # FIXE make fades work properly: the set_next / prev bug
19 # WONT find cue by page ("not necessawy!")
20 # WONT CueFader controls KeyboardController? unlikely
21 # FIXE AutoSave loop
22 5
23 class LabelledScale(Tk.Frame): 6 class LabelledScale(Tk.Frame):
24 """Scale with two labels: a name and current value""" 7 """Scale with two labels: a name and current value"""
25 def __init__(self, master, label, **opts): 8 def __init__(self, master, label, **opts):
26 Tk.Frame.__init__(self, master, bd=2, relief='raised', bg='black') 9 Tk.Frame.__init__(self, master, bd=2, relief='raised')
27 self.labelformatter = opts.get('labelformatter')
28 try:
29 del opts['labelformatter']
30 except KeyError:
31 pass
32
33 opts.setdefault('variable', Tk.DoubleVar()) 10 opts.setdefault('variable', Tk.DoubleVar())
34 opts.setdefault('showvalue', 0) 11 opts.setdefault('showvalue', 0)
35
36 self.normaltrough = opts.get('troughcolor', 'black')
37 self.flashtrough = opts.get('flashtroughcolor', 'red')
38 try:
39 del opts['flashtroughcolor']
40 except KeyError:
41 pass
42
43 self.scale_var = opts['variable'] 12 self.scale_var = opts['variable']
44 self.scale = Tk.Scale(self, **opts) 13 self.scale = Tk.Scale(self, **opts)
45 self.scale.pack(side='top', expand=1, fill='both') 14 self.scale.pack(side='top', expand=1, fill='both')
46 self.name = Tk.Label(self, text=label, bg='black', fg='white') 15 self.name = Tk.Label(self, text=label)
47 self.name.pack(side='bottom') 16 self.name.pack(side='bottom')
48 self.scale_value = Tk.Label(self, bg='black', fg='white') 17 self.scale_value = Tk.Label(self, width=6)
49 self.scale_value.pack(side='bottom') 18 self.scale_value.pack(side='bottom')
50 self.scale_var.trace('w', self.update_value_label) 19 self.scale_var.trace('w', self.update_value_label)
51 self.update_value_label() 20 self.update_value_label()
52 self.disabled = (self.scale['state'] == 'disabled')
53 def set_label(self, label): 21 def set_label(self, label):
54 self.name['text'] = label 22 self.name['text'] = label
55 def update_value_label(self, *args): 23 def update_value_label(self, *args):
56 val = self.scale_var.get() * 100 24 val = self.scale_var.get() * 100
57 if self.labelformatter: 25 self.scale_value['text'] = "%0.2f" % val
58 format = self.labelformatter(val)
59 else:
60 format = "%0.2f" % val
61 self.scale_value['text'] = format
62 if val != 0:
63 self.scale['troughcolor'] = self.flashtrough
64 else:
65 self.scale['troughcolor'] = self.normaltrough
66 def disable(self):
67 if not self.disabled:
68 self.scale['state'] = 'disabled'
69 self.scale_var.set(0)
70 self.disabled = 1
71 def enable(self):
72 if self.disabled:
73 self.scale['state'] = 'normal'
74 self.disabled = 0
75 26
76 class TimedGoButton(Tk.Frame): 27 class TimedGoButton(Tk.Frame):
77 """Go button, fade time entry, and time fader""" 28 """Go button, fade time entry, and time fader"""
78 def __init__(self, master, name, scale_to_fade, **kw): 29 def __init__(self, master, name, scale_to_fade):
79 Tk.Frame.__init__(self, master, bg='black') 30 Tk.Frame.__init__(self, master)
80 self.name = name 31 self.name = name
81 self.scale_to_fade = scale_to_fade 32 self.scale_to_fade = scale_to_fade
82 self.button = Tk.Button(self, text=name, command=self.start_fade, **kw) 33 self.button = Tk.Button(self, text=name, command=self.start_fade)
83 self.button.pack(fill='both', expand=1, side='left') 34 self.button.pack(fill='both', expand=1, side='left')
84 35 self.timer_var = Tk.StringVar()
85 self.timer_var = Tk.DoubleVar() 36 self.timer_entry = Tk.Entry(self, textvariable=self.timer_var, width=5)
86 self.timer_entry = Tk.Control(self, step=0.5, min=0, integer=0,
87 variable=self.timer_var, selectmode='immediate')
88 for widget in (self.timer_entry, self.timer_entry.entry,
89 self.timer_entry.incr, self.timer_entry.decr, self.button, self):
90 widget.bind("<4>", self.wheelscroll)
91 widget.bind("<5>", self.wheelscroll)
92 self.timer_entry.entry.configure(width=5, bg='black', fg='white')
93 self.timer_entry.pack(fill='y', side='left') 37 self.timer_entry.pack(fill='y', side='left')
94 self.timer_var.set(2) 38 self.timer_var.set("2")
95 self.disabled = (self.button['state'] == 'disabled')
96 self.start_time = 0
97 self.fading = 0
98 self.last_after_key = 0
99 def manual_override(self, *args):
100 self.end_fade()
101 def wheelscroll(self, event):
102 """Mouse wheel increments or decrements timer."""
103 if event.num == 4: # scroll up
104 self.timer_entry.increment()
105 else: # scroll down
106 self.timer_entry.decrement()
107 def start_fade(self, end_level=1): 39 def start_fade(self, end_level=1):
108 self.last_start_time = self.start_time 40 try:
41 fade_time = float(self.timer_var.get())
42 except ValueError:
43 # TODO figure out how to handle this
44 print "can't fade -- bad time"
45 return
46
109 self.start_time = time.time() 47 self.start_time = time.time()
110 self.start_level = self.scale_to_fade.scale_var.get() 48 self.start_level = self.scale_to_fade.scale_var.get()
111 self.end_level = end_level 49 self.end_level = end_level
112 50 self.fade_length = fade_time
113 if self.fading == 1: # if we're already fading 51 self.do_fade()
114 self.fading = 'paused'
115 # new fade should be as long as however much was left
116 self.fade_length = self.fade_length - \
117 (time.time() - self.last_start_time)
118 self.button['text'] = 'Unpause'
119 self.after_cancel(self.last_after_key)
120 else:
121 try:
122 fade_time = float(self.timer_var.get())
123 except ValueError:
124 # since we use a TixControl now, i don't think we need to worry
125 # about validation any more.
126 print ">>> Can't fade -- bad time", self.timer_var.get()
127 return
128
129 # if we're not already fading, we get our time from the entry
130 if self.fading != 'paused':
131 self.fade_length = fade_time
132
133 self.button['text'] = 'Pause'
134 self.fading = 1
135 self.do_fade()
136 def do_fade(self): 52 def do_fade(self):
137 diff = time.time() - self.start_time 53 diff = time.time() - self.start_time
138 if diff < self.fade_length: 54 if diff < self.fade_length:
139 percent = diff / self.fade_length 55 percent = diff / self.fade_length
140 newlevel = self.start_level + \ 56 newlevel = self.start_level + \
141 (percent * (self.end_level - self.start_level)) 57 (percent * (self.end_level - self.start_level))
142 self.scale_to_fade.scale_var.set(newlevel) 58 self.scale_to_fade.scale_var.set(newlevel)
143 59
144 if newlevel != self.end_level: 60 if newlevel != self.end_level:
145 self.last_after_key = self.after(10, self.do_fade) 61 self.after(10, self.do_fade)
146 else:
147 self.end_fade()
148 else: 62 else:
149 self.scale_to_fade.scale_var.set(self.end_level) 63 self.scale_to_fade.scale_var.set(self.end_level)
150 self.end_fade()
151 def end_fade(self):
152 self.button['text'] = self.name
153 self.fading = 0
154 self.after_cancel(self.last_after_key)
155 def disable(self):
156 if not self.disabled:
157 self.button['state'] = 'disabled'
158 self.disabled = 1
159 def enable(self):
160 if self.disabled:
161 self.button['state'] = 'normal'
162 self.disabled = 0
163 def set_time(self, time):
164 self.timer_var.set(time)
165 def get_time(self):
166 return self.timer_var.get()
167 def is_fading(self):
168 return self.fading
169 64
170 class CueFader(Tk.Frame): 65 class CueFader(Tk.Frame):
171 def __init__(self, master, cuelist): 66 def __init__(self, master, cuelist):
172 Tk.Frame.__init__(self, master, bg='black') 67 Tk.Frame.__init__(self, master)
173 self.cuelist = cuelist 68 self.cuelist = cuelist
174 self.cuelist.set_fader(self) 69 self.auto_shift = 0
175
176 self.last_levels_sent = 0
177 self.current_dmx_levels = [0] * 68
178 self.after(0, self.send_dmx_levels_loop) # start DMX sending loop
179
180 # this is a mechanism to stop Tk from autoshifting too much.
181 # if this variable is true, the mouse button is down. we don't want
182 # to shift until they release it. when it is released, we will
183 # set it to false and then call autoshift.
184 self.no_shifts_until_release = 0
185 70
186 self.scales = {} 71 self.scales = {}
187 self.shift_buttons = {} 72 self.shift_buttons = {}
188 self.go_buttons = {} 73
74 topframe = Tk.Frame(self)
75 self.current_cues = Tk.Label(topframe)
76 self.current_cues.pack()
77 self.update_cue_display()
78 topframe.pack()
189 79
190 topframe = Tk.Frame(self, bg='black') 80 for name, start, end, side in (('Prev', 1, 0, 'left'),
191 81 ('Next', 0, 1, 'right')):
192 self.set_prev_button = Tk.Button(topframe, text='Set Prev', 82
193 command=lambda: cuelist.set_selection_as_prev(), 83 shift = Tk.Button(self, text="Shift %s" % name, state='disabled',
194 fg='white', bg='blue') 84 command=lambda name=name: self.shift(name))
195 self.set_prev_button.pack(side='left') 85 shift.pack(side=side, fill='both', expand=1)
196 86
197 self.auto_shift = Tk.IntVar() 87 frame = Tk.Frame(self)
198 self.auto_shift.set(1)
199
200 self.auto_shift_checkbutton = Tk.Checkbutton(topframe,
201 variable=self.auto_shift, text='Autoshift',
202 command=self.toggle_autoshift, bg='black', fg='white',
203 highlightbackground='black')
204 self.auto_shift_checkbutton.pack(fill='both', side='left')
205
206 self.auto_load_times = Tk.IntVar()
207 self.auto_load_times.set(1)
208
209 self.auto_load_times_checkbutton = Tk.Checkbutton(topframe,
210 variable=self.auto_load_times, text='Autoload Times',
211 bg='black', fg='white',
212 highlightbackground='black')
213 self.auto_load_times_checkbutton.pack(fill='both', side='left')
214
215 self.mute = Tk.IntVar()
216 self.mute.set(0)
217
218 self.mutebutton = Tk.Checkbutton(topframe,
219 variable=self.mute, text='Mute',
220 bg='black', fg='white',
221 highlightbackground='black',
222 command=self.send_dmx_levels)
223 self.mutebutton.pack(fill='both', side='left')
224
225 self.set_next_button = Tk.Button(topframe, text='Set Next',
226 command=lambda: cuelist.set_selection_as_next(),
227 fg='white', bg='red')
228 self.set_next_button.pack(side='left')
229
230 topframe.pack(side='top')
231
232 faderframe = Tk.Frame(self, bg='black')
233 self.direction_info = (('Prev', 1, 0, 'left', 'blue'),
234 ('Next', 0, 1, 'right', 'red'))
235 for name, start, end, side, color in self.direction_info:
236 frame = Tk.Frame(self, bg='black')
237 scale = LabelledScale(frame, name, from_=start, to_=end, 88 scale = LabelledScale(frame, name, from_=start, to_=end,
238 res=0.0001, orient='horiz', flashtroughcolor=color, 89 res=0.01, orient='horiz')
239 labelformatter=lambda val, name=name: self.get_scale_desc(val, 90 scale.pack(fill='both', expand=1)
240 name)) 91 go = TimedGoButton(frame, 'Go %s' % name, scale)
241 scale.pack(fill='x', expand=0)
242 go = TimedGoButton(frame, 'Go %s' % name, scale, bg=color,
243 fg='white', width=10)
244 go.pack(fill='both', expand=1) 92 go.pack(fill='both', expand=1)
245 frame.pack(side=side, fill='both', expand=1) 93 frame.pack(side=side, fill='both', expand=1)
246 94
247 shift = Tk.Button(frame, text="Shift %s" % name, state='disabled',
248 command=lambda name=name: self.shift(name), fg=color,
249 bg='black')
250
251 self.scales[name] = scale 95 self.scales[name] = scale
252 self.shift_buttons[name] = shift 96 self.shift_buttons[name] = shift
253 self.go_buttons[name] = go
254 97
255 scale.scale_var.trace('w', \ 98 scale.scale_var.trace('w', \
256 lambda x, y, z, name=name, scale=scale: self.xfade(name, scale)) 99 lambda x, y, z, name=name, scale=scale: self.xfade(name, scale))
257 go.timer_var.trace('w', 100 def shift(self, name):
258 lambda x, y, z, scale=scale: scale.update_value_label()) 101 for scale in self.scales.values():
259 102 scale.scale_var.set(0)
260 def button_press(event, name=name, scale=scale): 103 if name == 'Next': scale.update()
261 self.no_shifts_until_release = 1 # prevent shifts until release 104 print "shift", name
262 def button_release(event, name=name, scale=scale): 105 self.cuelist.shift((-1, 1)[name == 'Next'])
263 self.no_shifts_until_release = 0 106 self.update_cue_display()
264 self.autoshift(name, scale) 107 def update_cue_display(self):
265 108 current_cues = [cue.name for cue in self.cuelist.get_current_cues()]
266 scale.scale.bind("<ButtonPress>", button_press) 109 self.current_cues['text'] = ', '.join(current_cues)
267 scale.scale.bind("<ButtonRelease>", button_release) 110 def xfade(self, name, scale):
268 faderframe.pack(side='bottom', fill='both', expand=1) 111 scale_val = scale.scale_var.get()
269 112
270 self.current_dir = 'Next' 113 if scale_val == 1:
271 self.cues_as_subs = {} 114 if self.auto_shift:
272 self.update_cue_cache() 115 self.shift(name)
273 def reload_cue_times(self):
274 prev, cur, next = self.cuelist.get_current_cues()
275 self.go_buttons['Next'].set_time(next.time)
276 def update_cue_cache(self, compute_dmx_levels=1):
277 """Rebuilds subs from the current cues. As this is expensive, we don't
278 do it unless necessary (i.e. whenever we shift or a cue is edited)"""
279 # print "update_cue_cache"
280 # load the subs to fade between
281 for cue, name in zip(self.cuelist.get_current_cues(),
282 ('Prev', 'Cur', 'Next')):
283 self.cues_as_subs[name] = cue.get_levels_as_sub()
284 if compute_dmx_levels:
285 self.compute_dmx_levels()
286 def compute_dmx_levels(self):
287 """Compute the DMX levels to send. This should get called whenever the
288 DMX levels could change: either during a crossfade or when a cue is
289 edited. Since this is called when we know that a change might occur,
290 we will send the new levels too."""
291 cur_sub = self.cues_as_subs.get('Cur')
292 if cur_sub:
293 scale = self.scales[self.current_dir]
294 scale_val = scale.scale_var.get()
295
296 other_sub = self.cues_as_subs[self.current_dir]
297 current_levels_as_sub = cur_sub.crossfade(other_sub, scale_val)
298 self.current_dmx_levels = current_levels_as_sub.get_dmx_list()
299 self.send_dmx_levels()
300
301 # print "compute_dmx_levels: fade at", scale_val
302 # print "between", cur_sub.name,
303 # print "and", other_sub.name
304 # print
305 def send_dmx_levels(self, *args):
306 # print "send_dmx_levels", self.current_dmx_levels
307 if self.mute.get():
308 dmxclient.outputlevels([0] * 68)
309 else:
310 dmxclient.outputlevels(self.current_dmx_levels)
311 self.last_levels_sent = time.time()
312 def send_dmx_levels_loop(self):
313 diff = time.time() - self.last_levels_sent
314 if diff >= 2: # too long since last send
315 self.send_dmx_levels()
316 self.after(200, self.send_dmx_levels_loop)
317 else:
318 self.after(int((2 - diff) * 100), self.send_dmx_levels_loop)
319 def get_scale_desc(self, val, name):
320 """Returns a description to the TimedGoButton"""
321 go_button = self.go_buttons.get(name)
322 if go_button:
323 time = go_button.get_time()
324 return "%0.2f%%, %0.1fs left" % (val, time - ((val / 100.0) * time))
325 else:
326 return "%0.2f%%" % val
327 def toggle_autoshift(self):
328 for name, button in self.shift_buttons.items():
329 if not self.auto_shift.get():
330 button.pack(side='bottom', fill='both', expand=1)
331 else: 116 else:
332 button.pack_forget()
333 def shift(self, name):
334 # to prevent overshifting
335 if self.no_shifts_until_release:
336 return
337 # print "shift", name
338
339 self.cuelist.shift((-1, 1)[name == 'Next'])
340 self.update_cue_cache(compute_dmx_levels=0)
341 for scale_name, scale in self.scales.items():
342 # print "shift: setting scale to 0", scale_name
343 scale.scale.set(0)
344 self.go_buttons[scale_name].manual_override()
345 self.update_cue_cache(compute_dmx_levels=1)
346
347 if self.auto_load_times.get():
348 self.reload_cue_times()
349 def autoshift(self, name, scale):
350 scale_val = scale.scale_var.get()
351
352 if scale_val == 1:
353 if self.auto_shift.get():
354 self.shift(name)
355 def xfade(self, name, scale):
356 if self.auto_shift.get():
357 self.autoshift(name, scale)
358 scale_val = scale.scale_var.get()
359 else:
360 scale_val = scale.scale_var.get()
361 if scale_val == 1:
362 self.shift_buttons[name]['state'] = 'normal' 117 self.shift_buttons[name]['state'] = 'normal'
363 else: 118 else:
364 # disable any dangerous shifting 119 # disable any dangerous shifting
365 self.shift_buttons[name]['state'] = 'disabled' 120 self.shift_buttons[name]['state'] = 'disabled'
366 121
367 d = self.opposite_direction(name)
368 if scale_val != 0: 122 if scale_val != 0:
369 # disable illegal three part crossfades 123 # disable illegal three part crossfades
370 self.scales[d].disable() 124 # TODO:
371 self.go_buttons[d].disable() 125 # if name == 'Next':
372 else: 126 # disable go_prev button and slider, lock slider at 0
373 # undo above work 127 pass
374 self.scales[d].enable() 128 else:
375 self.go_buttons[d].enable() 129 # undo above changes
376 130
377 self.current_dir = name 131 # Actually, TimedGoButton and LabelledScale can have enable/disable
378 self.compute_dmx_levels() 132 # methods which will only do the Tk calls if necessary
379 def opposite_direction(self, d): 133 pass
380 if d == 'Next':
381 return 'Prev'
382 else:
383 return 'Next'
384 134
385 class Cue: 135 class Cue:
386 """A Cue has a name, a time, and any number of other attributes.""" 136 """A Cue has a name, a time, and any number of other attributes."""
387 def __init__(self, name, time=3, sub_levels='', **attrs): 137 def __init__(self, name, time=3, **attrs):
388 self.name = name 138 self.name = name
389 self.time = time 139 self.time = time
390 self.sub_levels = sub_levels
391 self.__dict__.update(attrs) 140 self.__dict__.update(attrs)
392 def __repr__(self): 141 def __repr__(self):
393 return "<Cue %s, length %s>" % (self.name, self.time) 142 return "<Cue %s, length %s>" % (self.name, self.time)
394 def get_levels_as_sub(self):
395 """Get this Cue as a combined Submaster, normalized. This method
396 should not be called constantly, since it is somewhat expensive. It
397 will reload the submasters from disk, combine all subs together, and
398 then compute the normalized form."""
399 subdict = {}
400 for line in self.sub_levels.split(','):
401 try:
402 line = line.strip()
403 if not line:
404 continue
405 sub, scale = line.split(':')
406 sub = sub.strip()
407 scale = float(scale)
408 subdict[sub] = scale
409 except ValueError:
410 print "Parsing error for '%s' in %s" % (self.sub_levels, self)
411
412 s = Submaster.Submasters()
413 newsub = Submaster.sub_maxes(*[s[sub] * scale
414 for sub, scale in subdict.items()])
415 return newsub.get_normalized_copy()
416 143
417 empty_cue = Cue('empty') 144 empty_cue = Cue('empty')
418 145
419 allow_class_to_be_pickled(Cue) 146 allow_class_to_be_pickled(Cue)
420 147
426 try: 153 try:
427 self.treedict.load(filename) 154 self.treedict.load(filename)
428 except IOError: 155 except IOError:
429 self.treedict.cues = [] 156 self.treedict.cues = []
430 self.cues = self.treedict.cues 157 self.cues = self.treedict.cues
431 self.current_cue_index = -1 158 self.current_cue_index = 0
432 self.next_pointer = 0 159 self.next_pointer = None
433 self.prev_pointer = None 160 self.prev_pointer = None
434 161
435 import atexit 162 import atexit
436 atexit.register(self.save) 163 atexit.register(self.save)
437 def add_cue(self, cue, index=None): 164 def add_cue(self, cue, index=None):
458 def set_next(self, index): 185 def set_next(self, index):
459 self.next_pointer = index 186 self.next_pointer = index
460 def set_prev(self, index): 187 def set_prev(self, index):
461 self.prev_pointer = index 188 self.prev_pointer = index
462 def bound_index(self, index): 189 def bound_index(self, index):
463 if not self.cues or index < 0: 190 if not self.cues:
464 return None 191 return None
465 else: 192 else:
466 return min(index, len(self.cues) - 1) 193 return max(0, min(index, len(self.cues)))
467 def get_current_cue_indices(self): 194 def get_current_cue_indices(self):
468 """Returns a list of the indices of three cues: the previous cue,
469 the current cue, and the next cue."""
470 cur = self.current_cue_index 195 cur = self.current_cue_index
471 return [self.bound_index(index) for index in 196 return [self.bound_index(index) for index in
472 (self.prev_pointer or cur - 1, 197 (self.prev_pointer or cur - 1,
473 cur, 198 cur,
474 self.next_pointer or cur + 1)] 199 self.next_pointer or cur + 1)]
475 def get_current_cues(self): 200 def get_current_cues(self):
476 """Returns a list of three cues: the previous cue, the current cue, 201 # print "get_current_cue_indices", self.get_current_cue_indices()
477 and the next cue."""
478 return [self.get_cue_by_index(index) 202 return [self.get_cue_by_index(index)
479 for index in self.get_current_cue_indices()] 203 for index in self.get_current_cue_indices()]
480 def get_cue_by_index(self, index): 204 def get_cue_by_index(self, index):
481 try: 205 # print "get_cue_by_index", index
206 if index:
482 return self.cues[self.bound_index(index)] 207 return self.cues[self.bound_index(index)]
483 except TypeError: 208 else:
484 return empty_cue 209 return empty_cue
485 def __del__(self): 210 def __del__(self):
486 self.save() 211 self.save()
487 def save(self, backup=0): 212 def save(self):
488 if backup: 213 self.treedict.save(self.filename)
489
490 backupfilename = "%s-backup" % self.filename
491 print time.asctime(), "Saving backup version of cues to", \
492 backupfilename
493 self.treedict.save(backupfilename)
494 else:
495 print time.asctime(), "Saving cues to", self.filename
496 self.treedict.save(self.filename)
497 def reload(self):
498 # TODO: we probably will need to make sure that indices still make
499 # sense, etc.
500 self.treedict.load(self.filename)
501
502 class TkCueList(CueList, Tk.Frame):
503 def __init__(self, master, filename):
504 CueList.__init__(self, filename)
505 Tk.Frame.__init__(self, master, bg='black')
506 self.fader = None
507
508 self.edit_tl = Tk.Toplevel()
509 self.editor = CueEditron(self.edit_tl,
510 changed_callback=self.cue_changed)
511 self.editor.pack(fill='both', expand=1)
512
513 def edit_cue(index):
514 index = int(index)
515 self.editor.set_cue_to_edit(self.cues[index])
516
517 self.columns = ('name', 'time', 'page', 'cuenum', 'desc', 'sub_levels')
518 self.scrolled_hlist = Tk.ScrolledHList(self,
519 options='hlist.columns %d hlist.header 1' % len(self.columns))
520 self.hlist = self.scrolled_hlist.hlist
521 self.hlist.configure(fg='white', bg='black',
522 command=self.select_callback, browsecmd=edit_cue)
523 self.hlist.bind("<4>", self.wheelscroll)
524 self.hlist.bind("<5>", self.wheelscroll)
525 self.scrolled_hlist.pack(fill='both', expand=1)
526
527 boldfont = self.tk.call('tix', 'option', 'get',
528 'bold_font')
529 header_style = Tk.DisplayStyle('text', refwindow=self,
530 anchor='center', padx=8, pady=2, font=boldfont)
531
532 for count, header in enumerate(self.columns):
533 self.hlist.header_create(count, itemtype='text',
534 text=header, style=header_style)
535
536 self.cue_label_windows = {}
537 for count, cue in enumerate(self.cues):
538 self.display_cue(count, cue)
539 self.update_cue_indicators()
540 self.save_loop()
541 def set_fader(self, fader):
542 self.fader = fader
543 def wheelscroll(self, evt):
544 """Perform mouse wheel scrolling"""
545 if evt.num == 4: # scroll down
546 amount = -2
547 else: # scroll up
548 amount = 2
549 self.hlist.yview('scroll', amount, 'units')
550 def cue_changed(self, cue):
551 path = self.cues.index(cue)
552 for col, header in enumerate(self.columns):
553 try:
554 text = getattr(cue, header)
555 except AttributeError:
556 text = ''
557
558 if col == 0:
559 self.cue_label_windows[path]['text'] = text
560 else:
561 self.hlist.item_configure(path, col, text=text)
562
563 if cue in self.get_current_cues() and self.fader:
564 self.fader.update_cue_cache()
565 self.fader.reload_cue_times()
566 def display_cue(self, path, cue):
567 for col, header in enumerate(self.columns):
568 try:
569 text = getattr(cue, header)
570 except AttributeError:
571 text = ''
572
573 if col == 0:
574 lab = Tk.Label(self.hlist, text=text, fg='white', bg='black')
575 def select_and_highlight(event):
576 self.select_callback(path)
577 self.hlist.selection_clear()
578 self.hlist.selection_set(path)
579
580 lab.bind("<Double-1>", select_and_highlight)
581 self.hlist.add(path, itemtype='window', window=lab)
582 self.cue_label_windows[path] = lab
583 else:
584 self.hlist.item_create(path, col, text=text)
585 def reset_cue_indicators(self, cue_indices=None):
586 """If cue_indices is None, we'll reset all of them."""
587 cue_indices = cue_indices or self.cue_label_windows.keys()
588 for key in cue_indices:
589 if key is None:
590 continue
591 window = self.cue_label_windows[key]
592 window.configure(fg='white', bg='black')
593 def update_cue_indicators(self):
594 states = dict(zip(self.get_current_cue_indices(),
595 ('prev', 'cur', 'next')))
596
597 for count, state in states.items():
598 if count is None:
599 continue
600 window = self.cue_label_windows[count]
601 bg, fg = cue_state_indicator_colors[state]
602 window.configure(bg=bg, fg=fg)
603 def shift(self, diff):
604 self.reset_cue_indicators(self.get_current_cue_indices())
605 CueList.shift(self, diff)
606 self.update_cue_indicators()
607 # try to see all indices, but next takes priority over all, and cur
608 # over prev
609 for index in self.get_current_cue_indices():
610 if index is not None:
611 self.hlist.see(index)
612 def select_callback(self, index):
613 new_next = int(index)
614 self.set_next(new_next)
615 def set_next(self, index):
616 prev, cur, next = self.get_current_cue_indices()
617 self.reset_cue_indicators((next,))
618 CueList.set_next(self, index)
619 self.update_cue_indicators()
620
621 if self.fader: # XXX this is untested
622 self.fader.update_cue_cache()
623 def set_prev(self, index):
624 prev, cur, next = self.get_current_cue_indices()
625 self.reset_cue_indicators((prev,))
626 CueList.set_prev(self, index)
627 self.update_cue_indicators()
628
629 if self.fader: # XXX this is untested
630 self.fader.update_cue_cache()
631 def set_selection_as_prev(self):
632 sel = self.hlist.info_selection()
633 if sel:
634 self.set_prev(int(sel[0]))
635 def set_selection_as_next(self):
636 sel = self.hlist.info_selection()
637 if sel:
638 self.set_next(int(sel[0]))
639 def save_loop(self):
640 """This saves the CueList every minute."""
641 self.save(backup=1)
642 self.after(60000, self.save_loop)
643
644 class CueEditron(Tk.Frame):
645 def __init__(self, master, changed_callback=None, cue=None):
646 Tk.Frame.__init__(self, master, bg='black')
647 self.master = master
648 self.cue = cue
649 self.changed_callback = changed_callback
650 self.enable_callbacks = 1
651
652 self.setup_editing_forms()
653 self.set_cue_to_edit(cue)
654 def set_cue_to_edit(self, cue):
655 if cue != self.cue:
656 self.cue = cue
657 self.fill_in_cue_info()
658 self.set_title()
659 def set_title(self):
660 try:
661 self.master.title("Editing '%s'" % self.cue.name)
662 except AttributeError:
663 pass
664 def setup_editing_forms(self):
665 self.variables = {}
666 for row, field in enumerate(('name', 'time', 'page', 'cuenum', 'desc',
667 'sub_levels')):
668 lab = Tk.Label(self, text=field, fg='white', bg='black')
669 lab.grid(row=row, column=0, sticky='nsew')
670
671 entryvar = Tk.StringVar()
672 entry = Tk.Entry(self, fg='white', bg='black',
673 textvariable=entryvar, insertbackground='white',
674 highlightcolor='red') # TODO this red/black is backwards
675 entry.grid(row=row, column=1, sticky='nsew')
676
677 self.variables[field] = entryvar
678
679 def field_changed(x, y, z, field=field, entryvar=entryvar):
680 if self.cue:
681 setattr(self.cue, field, entryvar.get())
682 if self.enable_callbacks and self.changed_callback:
683 self.changed_callback(self.cue)
684 if field == 'name':
685 self.set_title()
686
687 entryvar.trace('w', field_changed)
688 self.columnconfigure(1, weight=1)
689 def fill_in_cue_info(self):
690 self.enable_callbacks = 0
691 for row, field in enumerate(('name', 'time', 'page', 'cuenum', 'desc',
692 'sub_levels')):
693 text = ''
694 if self.cue:
695 try:
696 text = getattr(self.cue, field)
697 except AttributeError:
698 pass
699 self.variables[field].set(text)
700 self.enable_callbacks = 1
701 214
702 if __name__ == "__main__": 215 if __name__ == "__main__":
703 root = Tk.Tk() 216 if 0:
704 root.title("ShowMaster 9000") 217 z = CueList('cues/cuelist1')
705 root.geometry("600x670") 218 z.add_cue(Cue('cue %s' % time.asctime(), time=2, a=7, b=8))
706 cl = TkCueList(root, 'cues/dolly') 219 print 'cues', z.cues
707 cl.pack(fill='both', expand=1) 220 else:
708 221 cl = CueList('cues/cuelist1')
709 fader = CueFader(root, cl) 222
710 fader.pack(fill='both', expand=1) 223 # to populate cue list
711 try: 224 if 0:
225 for x in range(20):
226 cl.add_cue(Cue('cue %d' % x, time=x, some_attribute=3))
227
228 root = Tk.Tk()
229 fader = CueFader(root, cl)
230 fader.pack(fill='both', expand=1)
712 Tk.mainloop() 231 Tk.mainloop()
713 except KeyboardInterrupt:
714 root.destroy()