From 0da04f3bab547c11e3ede773df822c76a3324787 Mon Sep 17 00:00:00 2001 From: Ben Connors Date: Sun, 1 Dec 2019 13:44:30 -0500 Subject: Implement joins in interface --- interface/chaserview.py | 15 ++++--- interface/input/parsers.py | 25 +++++++++++ interface/interface.py | 109 +++++++++++++++++++++++++++++++++++---------- 3 files changed, 120 insertions(+), 29 deletions(-) diff --git a/interface/chaserview.py b/interface/chaserview.py index 7a6a8db..2c94240 100755 --- a/interface/chaserview.py +++ b/interface/chaserview.py @@ -4,7 +4,7 @@ import curses import math import threading -from blc2.constants import INFTY, MANUAL +from blc2.constants import INFTY, MANUAL, JOIN, CHASERSTEP CURSES_LOCK = threading.RLock() @@ -80,7 +80,7 @@ class ChaserView: c = self._chaser w = self._width - 2 - self.win.addstr(1, 1, self.fit(("%d: "% c.id) + c.name, w, True), curses.A_REVERSE if self._highlight else 0) + self.win.addstr(1, 1, self.fit(("%d: "% c.id) + c.name + " (%s)" % ("Join" if c.type == JOIN else c.advance_mode), w, True), curses.A_REVERSE if self._highlight else 0) maxsteps = self._height - 4 if maxsteps < len(c.steps): @@ -97,19 +97,22 @@ class ChaserView: last = len(c.steps) steps = c.steps[first:last] for n, s in enumerate(steps, 1): - if s.index == self._selected: + if first+n-1 == self._selected: attrs = curses.A_REVERSE else: attrs = 0 - if s.function is not None: + if s.type == CHASERSTEP and s.function is not None: ft = s.function.type[0].upper() fid = str(s.function.id) + elif s.type != CHASERSTEP: + ft = s.type[0].upper() + fid = str(s.id) else: ft = "-" fid = "---" - t = "%s%3s%s|%s:%s:%s" % (ft, fid, '*' if s.duration_mode == MANUAL else ' ', format_time(s.fade_in), format_time(s.duration), format_time(s.fade_out)) - self.win.addstr(n+2, 1, self.fit((self._numformat % (s.index+1)) + ": " + s.name, w-len(t), pad=True)+t, attrs) + t = "%s%3s%s|%s:%s:%s" % (ft, fid, '*' if (s.type == CHASERSTEP and s.duration_mode == MANUAL) else ' ', format_time(s.fade_in), format_time(s.duration), format_time(s.fade_out)) + self.win.addstr(n+2, 1, self.fit((self._numformat % (first+n)) + ": " + s.name, w-len(t), pad=True)+t, attrs) if first > 0: self.win.addch(3, self._width//2, '⯅') diff --git a/interface/input/parsers.py b/interface/input/parsers.py index 7dc7d82..64ba7c4 100755 --- a/interface/input/parsers.py +++ b/interface/input/parsers.py @@ -101,6 +101,30 @@ def parse_num(s): return (None if not buff else int(buff)), s, buff +def parse_numlist(s): + if not s: + return None, s, None + + rs = [] + disp = [] + tc = False + while s: + r, s, d = parse_num(s) + if r is None: + break + tc = False + rs.append(r) + disp.append(d) + if s and s[0] == ',': + s = s[1:] + tc = True + else: + break + + if not rs: + return None, s, None + return tuple(rs), s, ", ".join(disp) + (", " if tc else "") + _POSTFIXES = "mcisahkegtp" def parse_time(s): @@ -201,4 +225,5 @@ PARSE_MAP = { "$quoted_string": parse_quotedstring, "$letter": parse_any_letter, "$time": parse_time, + "$numlist": parse_numlist, } diff --git a/interface/interface.py b/interface/interface.py index 48f3d24..500728c 100644 --- a/interface/interface.py +++ b/interface/interface.py @@ -10,7 +10,8 @@ from blc2.functions.audio import Audio from blc2.functions.scene import Scene from blc2.functions.chaser import Chaser from blc2.functions.chaserstep import ChaserStep -from blc2.constants import SCENE, CHASER, AUDIO, MANUAL, INHERIT, INFTY +from blc2.functions.join import Join +from blc2.constants import SCENE, CHASER, AUDIO, MANUAL, INHERIT, INFTY, CHASERSTEP, JOIN, ONESHOT, LOOP from blc2.topology import Fixture @@ -31,7 +32,7 @@ def wrap_curses(f): return curses.wrapper(lambda stdscr: f(*args, stdscr, **kwargs)) return inner -__version__ = "v0.0.2" +__version__ = "v0.0.3" class Interface: def _compute_sizes(self, height, width): @@ -140,7 +141,7 @@ class Interface: self.handle_show(f) self.channel_bank.highlight = True self.input.context = self.context_scene - elif f.type == CHASER: + elif f.type in (CHASER, JOIN): self.chaser = f cv = [c for c in self.chaser_views if c.chaser == f] if not cv: @@ -153,7 +154,10 @@ class Interface: cv = cv[0] self.current_cv = cv self.current_cv.highlight = True - self.input.context = self.context_chaser + if f.type == CHASER: + self.input.context = self.context_chaser + elif f.type == JOIN: + self.input.context = self.context_join elif f.type == AUDIO: self.primitive = f self.handle_show(f) @@ -168,7 +172,7 @@ class Interface: if fid not in self.w.functions: return "No such function" f = self.w.functions[fid] - if f.type != CHASER: + if f.type not in (CHASER, JOIN): return "Can only add chasers!" with CURSES_LOCK: if True in (c.chaser.id for c in self.chaser_views): @@ -343,7 +347,10 @@ class Interface: self.current_cv.highlight = True self.current_cv.selected = sel - self.input.context = self.context_chaser + if c.type == CHASER: + self.input.context = self.context_chaser + else: + self.input.context = self.context_join self.chaser = c else: if isinstance(self.channel_bank, AudioView): @@ -423,8 +430,8 @@ class Interface: self.current_cv.selected = num - 1 s = self.current_cv.chaser.steps[num-1] - if s.function is not None and s.function.type in (SCENE, AUDIO,): - self.handle_show(s.function) + if (s.type == CHASERSTEP and s.function is not None and s.function.type in (SCENE, AUDIO,)) or s.type in (SCENE, AUDIO,): + self.handle_show(s.function if s.type == CHASERSTEP else s) def chaser_edit(self, num=None): with self.w_lock: @@ -436,10 +443,10 @@ class Interface: return "No step selected" c = self.current_cv.chaser.steps[self.current_cv.selected] - if c.function is None: + if c.type == CHASERSTEP and c.function is None: return "No function on step" - - self.handle_enter(c.function.id) + + self.handle_enter(c.function.id if c.type == CHASERSTEP else c.id) def chaser_delete(self, num=None): with self.w_lock: @@ -452,15 +459,22 @@ class Interface: c = self.current_cv.chaser s = c.steps[self.current_cv.selected] + sel = self.current_cv.selected with CURSES_LOCK: - if askyesnocancel(self.stdscr, "Really delete step %d?" % (s.index+1), resize=self._resize): - sel = s.index - s.delete() - if not c.steps: - sel = None + if c.type == CHASER: + if askyesnocancel(self.stdscr, "Really delete step %d?" % (s.index+1), resize=self._resize): + sel = s.index + s.delete() else: - sel = max(0, sel-1) - self.current_cv.set_chaser(c, sel) + return + else: + c.delete_step(s) + + if not c.steps: + sel = None + else: + sel = max(0, sel-1) + self.current_cv.set_chaser(c, sel) self._resize() def chaser_fade(self, t, out=False): @@ -501,19 +515,30 @@ class Interface: if fid not in self.w.functions: return "No such function" f = self.w.functions[fid] - if f.type not in (SCENE, AUDIO, CHASER): + if f.type not in (SCENE, AUDIO, CHASER, JOIN): return "Invalid function" else: f = None c = self.current_cv.chaser - s = ChaserStep(c, index=index, name=name, function=f) - self.current_cv.set_chaser(c, s.index) + if c.type == CHASER: + if name is None and f is not None: + name = "Step \"%s\"" % f.name + s = ChaserStep(c, index=index, name=name, function=f) + self.current_cv.set_chaser(c, s.index) + else: + if f.id in (i.id for i in c.steps): + return "Already added" + c.add_step(f) + self.current_cv.set_chaser(c, len(c.steps)-1) #self.chaser_edit(s.index+1) def chaser_new_new(self, index, name, fname, type_): with self.w_lock: + if name is None and f is not None: + name = "Step \"%s\"" % f.name + f = type_(self.w, name=fname) return self.chaser_new(index, name, fid=f.id) @@ -603,7 +628,7 @@ class Interface: self.channel_bank.set_values(values) self.channel_bank.title = "LIVE: %7.2fs" % t - for d, cv in zip(self.renderer._data, self.chaser_views): + for d, cv in zip(self.renderer._data, (i for i in self.chaser_views if i.chaser.type == CHASER)): cv.selected = d.steps[-1].index if d.steps else None self.input.win.move(*syx) @@ -617,7 +642,7 @@ class Interface: self.channel_bank.highlight = True self.rendering = True - self.renderer.set_functions(*((cv.chaser, cv.chaser.get_data(cv.selected)) for cv in self.chaser_views)) + self.renderer.set_functions(*((cv.chaser, cv.chaser.get_data(cv.selected)) for cv in self.chaser_views if cv.chaser.type == CHASER)) self.renderer.start() self.input.context = self.context_run @@ -695,6 +720,11 @@ class Interface: "- Fade out: %7.3f" % (f.fade_out/1000), )) + def chaser_mode(self, mode): + with self.w_lock: + self.current_cv.chaser.advance_mode = mode + self.current_cv.set_chaser(self.current_cv.chaser, self.current_cv.selected) + def __init__(self, path, output): ## Have to do most of the actual initialization in the main method, as curses isn't ## ready yet. @@ -728,6 +758,7 @@ class Interface: ("new scene $quoted_string", lambda n: self.base_new(n, Scene)), ("new chaser $quoted_string", lambda n: self.base_new(n, Chaser)), ("new audio $quoted_string", lambda n: self.base_new(n, Audio)), + ("new join $quoted_string", lambda n: self.base_new(n, Join)), ("add $num", self.base_add), ("subtract $letter", lambda n: self.base_remove(n, True)), @@ -742,6 +773,7 @@ class Interface: ("list scenes", lambda: self.list_functions(SCENE)), ("list chasers", lambda: self.list_functions(CHASER)), ("list audio", lambda: self.list_functions(AUDIO)), + ("list joins", lambda: self.list_functions(JOIN)), ("currentstatus", self.current_status), @@ -792,6 +824,8 @@ class Interface: ("move $num", self.chaser_move), ("set fade in $time", self.chaser_fade), ("set fade out $time", lambda t: self.chaser_fade(t, True)), + ("set mode oneshot", lambda: self.chaser_mode(ONESHOT)), + ("set mode loop", lambda: self.chaser_mode(LOOP)), ("set length $time", self.chaser_duration), ("unbind", self.chaser_unset), ("bind $num", self.chaser_bind), @@ -800,6 +834,7 @@ class Interface: ("list scenes", lambda: self.list_functions(SCENE)), ("list chasers", lambda: self.list_functions(CHASER)), ("list audio", lambda: self.list_functions(AUDIO)), + ("list joins", lambda: self.list_functions(JOIN)), ("info", lambda: self.chaser_info(None)), ("info $num", self.chaser_info), @@ -838,6 +873,7 @@ class Interface: ("list scenes", lambda: self.list_functions(SCENE)), ("list chasers", lambda: self.list_functions(CHASER)), ("list audio", lambda: self.list_functions(AUDIO)), + ("list joins", lambda: self.list_functions(JOIN)), ("rename $quoted_string", self.primitive_rename), @@ -902,6 +938,33 @@ class Interface: "currentstatus" : "Display information about the system's current status." }, self.help) + self.context_join = Input.parse_context(( + ("choose $num", self.chaser_select), + + ("edit", self.chaser_edit), + ("edit $num", self.chaser_edit), + + ("delete", self.chaser_delete), + ("delete $num", self.chaser_delete), + + ("add", lambda: self.chaser_new(-1, "")), + ("add from $num", lambda s: self.chaser_new(-1, "", s)), + ("add from new scene $quoted_string", lambda s: self.chaser_new_new(-1, "", s, Scene)), + ("add from new audio $quoted_string", lambda s: self.chaser_new_new(-1, "", s, Audio)), + + ("rename $quoted_string", self.chaser_rename_self), + + ("list fixtures", self.list_fixtures), + ("list scenes", lambda: self.list_functions(SCENE)), + ("list chasers", lambda: self.list_functions(CHASER)), + ("list audio", lambda: self.list_functions(AUDIO)), + ("list joins", lambda: self.list_functions(JOIN)), + + ("pager page", self.page), + ("pager clear", self.page_clear), + ("quit", self.handle_exit), + )) + self.output = output self.renderer = Renderer(self.w, self.w_lock, self.output, self._render_callback) self.rendering = False -- cgit v1.2.3