import curses import os import datetime as dt import threading import blc2 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 from blc2.topology import Fixture from blc2.workspace import Workspace from .globals import CURSES_LOCK from .input.tabcomp import Input from .channelbank import ChannelBank from .chaserview import ChaserView from .pager import Pager def wrap_curses(f): def inner(*args, **kwargs): return curses.wrapper(lambda stdscr: f(*args, stdscr, **kwargs)) return inner __version__ = "v0.0.1" class Interface: @staticmethod def _compute_sizes(height, width): hb = height // 2 ht = height - hb wr = width // 2 wl = width - wr - 1 return ( (ht, width), (0, 0), (4, wr), (height-4, wl+1), (hb - 4, wr), (ht, wl+1), (hb, wl//2 - 1), (ht, 0), (hb, wl - (wl//2)), (ht, wl//2) ) def _resize(self): for a, f in zip(self._compute_sizes(*self.stdscr.getmaxyx()), (self.channel_bank.set_dim, self.channel_bank.set_pos, self.input.set_dim, self.input.set_pos, self.pager.set_dim, self.pager.set_pos, self.chaser_views[0].set_dim, self.chaser_views[0].set_pos, self.chaser_views[1].set_dim, self.chaser_views[1].set_pos)): f(*a) @wrap_curses def main(self, stdscr): height, width = stdscr.getmaxyx() self.stdscr = stdscr cbd, cbp, ind, inp, pgd, pgp, cv0d, cv0p, cv1d, cv1p = self._compute_sizes(height, width) self.channel_bank = ChannelBank(*cbp, *cbd) self.input = Input(*inp, *ind) self.input.context = self.context_base self.pager = Pager(*pgp, *pgd) self.chaser_views[0] = ChaserView(*cv0p, *cv0d) self.chaser_views[1] = ChaserView(*cv1p, *cv1d) todisp = [ "=== Welcome to BLC2!", "=== Currently running lib %s, int %s" % (blc2.__version__, __version__), "", ] if self._w_created: todisp.append("Created a new workspace") else: todisp.append("Loaded workspace \"%s\" from \"%s\"" % (self.w.name, self.path)) todisp.append("Authored by %s, last modified at %s" % (self.w.author, self.w.modified.strftime("%Y-%m-%d %H:%M:%S"))) self.pager.display_many(todisp) self.input.main(self._resize) def base_edit(self, fid): with self.w_lock: if fid not in self.w.functions: return "No such function" f = self.w.functions[fid] if f.type != SCENE: ## FIXME return "Can only edit scenes so far" with CURSES_LOCK: self.primitive = f self.channel_bank.set_scope(f.scope) self.channel_bank.set_values(f.values.items()) self.channel_bank.title = f.type + ' "%s"' % f.name self.input.context = self.context_scene def base_delete(self, fid): with self.w_lock: if fid not in self.w.functions: return "No such function" f = self.w.functions[fid] f.delete() def base_new_scene(self, name): with self.w_lock: f = Scene(self.w, name=name) self.base_edit(f.id) def _gather_channels(self, cr): with self.w_lock: ## Gather the affected channels channels = [] for f in self.w.fixtures.values(): for s, e in cr[0]: if (s <= f.id <= e) or (e == -1 and f.id >= s): break else: continue for n, c in enumerate(f.channels, 1): for s, e in cr[1]: if (s <= n <= e) or (e == -1 and n >= s): channels.append(c) break return channels def scene_set(self, cr, v): with self.w_lock: channels = self._gather_channels(cr) ## Set the values self.primitive.update({c: v for c in channels}) ## Update the display self.channel_bank.set_scope(self.primitive.scope) if v is not None: self.channel_bank.set_values(((c, v) for c in channels)) def scene_clear(self, cr): self.scene_set(cr, None) def scene_exit(self): self.input.context = self.context_base self.primitive = None self.channel_bank.set_scope(()) self.channel_bank.title = "Channels" def base_save(self, path=None): with self.w_lock: if path is not None: self.path = path if self.path is None: return "No path set" self.w.modified = dt.datetime.now() self.w.save(self.path) return "Saved to "+self.path def base_quit(self): quit() def list_fixtures(self): with self.w_lock: td = ["FIXTURES:"] for f in sorted(self.w.fixtures.values(), key=lambda a: a.id): td.append("- %03d: %s" % (f.id, f.name)) for c in f.channels: td.append(" %03d-%03d: %s" % (f.id, c.id, c.name)) self.pager.display_many(td, split=True) def list_functions(self, typ): with self.w_lock: td = [typ.upper()+"S:"] for f in sorted(self.w.functions.values(), key=lambda a: a.id): if f.type == typ: td.append("- %03d: %s" % (f.id, f.name)) self.pager.display_many(td, split=True) def page(self): self.pager.user_page() def page_clear(self): self.pager.clear() def help(self, name, commands, help_): if name is None: dname = "MODE HELP:" else: dname = name.upper() + " COMMAND:" todisp = [dname, ""] if commands: if name is None: todisp.append("Available commands:") else: todisp.append("Available forms:") todisp.extend(("- "+i for i in commands)) todisp.append("") todisp.extend(help_.split('\n')) self.pager.display_many(todisp, split=True) def __init__(self, path): ## Have to do most of the actual initialization in the main method, as curses isn't ## ready yet. self.channel_bank = None self.input = None self.pager = None self.stdscr = None self.primitive = None self.chasers = [] self.path = path self._w_created = False if path is None or not os.path.isfile(path): self.w = Workspace("", "", 0, dt.datetime.now()) self._w_created = True else: self.w = Workspace.load(path) self.w_lock = threading.RLock() self.chaser_views = [None, None] self.context_base = Input.parse_context(( ("edit $num", self.base_edit), ("delete $num", self.base_delete), ("new scene $quoted_string", self.base_new_scene), ("save", self.base_save), ("save $quoted_string", self.base_save), ("list fixtures", self.list_fixtures), ("list scenes", lambda: self.list_functions(SCENE)), ("page", self.page), ("clrpage", self.page_clear), ("quit", self.base_quit), ), { None: "This is the base edit mode for editing functions.", "edit": "Edit the specified function.", "delete": "Delete the specified function.", "new": "Create a new function of the given type.", "save": "Save the workspace. The path is implicitly the one loaded from if not given.", "list": "List available fixtures or functions.", "page": "Page through the output window. Arrow keys, page up/down, home/end, and j/k can be used to scroll, q exits.", "clrpage": "Clear the output window.", "quit": "Exit BLC." }, self.help) self.context_scene = Input.parse_context(( ("set $channel_range to $value", self.scene_set), ("reset $channel_range", self.scene_clear), ("list fixtures", self.list_fixtures), ("list scenes", lambda: self.list_functions(SCENE)), ("page", self.page), ("clrpage", self.page_clear), ("quit", self.scene_exit), ), { None: "This mode is for editing scene primitives for fixed lighting.", "set": "Set the given channel range to the given value (0 <= value <= 255).", "reset": "Remove the given channel range from the scene", "list": "List available fixtures or functions.", "page": "Page through the output window. Arrow keys, page up/down, home/end, and j/k can be used to scroll, q exits.", "clrpage": "Clear the output window.", "quit": "Return to the previous mode." }, self.help)