summaryrefslogtreecommitdiff
path: root/interface/interface.py
diff options
context:
space:
mode:
Diffstat (limited to 'interface/interface.py')
-rw-r--r--interface/interface.py223
1 files changed, 223 insertions, 0 deletions
diff --git a/interface/interface.py b/interface/interface.py
new file mode 100644
index 0000000..c9f2a6e
--- /dev/null
+++ b/interface/interface.py
@@ -0,0 +1,223 @@
+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 .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),
+ )
+
+ 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)):
+ f(*a)
+
+ @wrap_curses
+ def main(self, stdscr):
+ height, width = stdscr.getmaxyx()
+ self.stdscr = stdscr
+ cbd, cbp, ind, inp, pgd, pgp = 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)
+
+ 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; %3dc: %s" % (f.id, len(f.channels), f.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 __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.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),
+ ))
+
+ 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),
+ ))