diff options
-rw-r--r-- | blc2/functions/chaser.py | 2 | ||||
-rw-r--r-- | blc2/workspace.py | 4 | ||||
-rwxr-xr-x | interface/chaserview.py | 2 | ||||
-rw-r--r-- | interface/dialog.py | 64 | ||||
-rw-r--r-- | interface/interface.py | 132 |
5 files changed, 191 insertions, 13 deletions
diff --git a/blc2/functions/chaser.py b/blc2/functions/chaser.py index ea37ca0..c617b4f 100644 --- a/blc2/functions/chaser.py +++ b/blc2/functions/chaser.py @@ -134,7 +134,7 @@ class Chaser(Function): def _fix_indices(self): for i, s in enumerate(self._steps): if s.index != i: - s.index._set_index(i) #pylint: disable=protected-access + s._set_index(i) #pylint: disable=protected-access def register_step(self, step): """Register a new step. diff --git a/blc2/workspace.py b/blc2/workspace.py index 5ec5247..8abf8a1 100644 --- a/blc2/workspace.py +++ b/blc2/workspace.py @@ -278,9 +278,9 @@ class Workspace(XMLSerializable): all_f = list(self.functions.values()) while all_f: f = all_f.pop(0) - if f.type in (CHASERSTEP): + if f.type in (CHASERSTEP,): continue - elif f.type in (AUDIO, SCENE): + elif f.type in (AUDIO, SCENE,): f_order.append(f) done.add(f.id) elif f.type == CHASER: diff --git a/interface/chaserview.py b/interface/chaserview.py index e5b0c2b..6a80241 100755 --- a/interface/chaserview.py +++ b/interface/chaserview.py @@ -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-2, True), curses.A_REVERSE if self._highlight else 0) + self.win.addstr(1, 1, self.fit(("%d: "% c.id) + c.name, w, True), curses.A_REVERSE if self._highlight else 0) maxsteps = self._height - 4 if maxsteps < len(c.steps): diff --git a/interface/dialog.py b/interface/dialog.py new file mode 100644 index 0000000..18ecb34 --- /dev/null +++ b/interface/dialog.py @@ -0,0 +1,64 @@ +import curses + +from .globals import CURSES_LOCK + +def split_words(s, l): + lines = [] + while len(s) > l: + work = s[:l] + for i in reversed(range(l)): + if work[i] in ' -_': + lines.append(s[:i+1]) + s = s[i+1:] + break + else: + lines.append(work) + s = s[l:] + if s: + lines.append(s) + + return lines + +def askyesnocancel(stdscr, msg, title="Confirm", resize=None): + height, width = stdscr.getmaxyx() + + thiswidth = min(width, 40) + + posx = (width // 2) - (thiswidth // 2) + lines = split_words(msg, thiswidth-2) + thisheight = len(lines) + 3 + posy = (height // 2) - (thisheight // 2) + + if thisheight > height: + raise ValueError("Not enough room") + + win = curses.newwin(thisheight, thiswidth, posy, posx) + win.border() + win.addstr(0, (thiswidth // 2) - (len(title) // 2), title) + win.keypad(True) + + for n, l in enumerate(lines, 1): + win.addstr(n, 1, l) + + win.addstr(thisheight-2, 1, "[Y]es, [N]o, [C]ancel") + + win.refresh() + + curses.curs_set(0) + + while True: + l = win.getch() + + if l == curses.KEY_RESIZE: + if resize is not None: + resize() + return askyesnocancel(stdscr, msg, title=title, resize=resize) + elif l in (ord('y'), ord('Y')): + win.erase() + return True + elif l in (ord('n'), ord('N')): + win.erase() + return False + elif l in (ord('c'), ord('C')): + win.erase() + return None diff --git a/interface/interface.py b/interface/interface.py index 57624d8..d7d3de4 100644 --- a/interface/interface.py +++ b/interface/interface.py @@ -20,6 +20,7 @@ from .input.tabcomp import Input from .channelbank import ChannelBank from .chaserview import ChaserView from .pager import Pager +from .dialog import askyesnocancel def wrap_curses(f): def inner(*args, **kwargs): @@ -74,7 +75,7 @@ class Interface: todisp = [ "=== Welcome to BLC2!", - "=== Currently running lib %s, int %s" % (blc2.__version__, __version__), + "=== Currently running library %s, interface %s" % (blc2.__version__, __version__), "", ] @@ -87,6 +88,13 @@ class Interface: self.pager.display_many(todisp) self.input.main(self._resize) + def handle_show(self, f): + with self.w_lock: + if f.type == SCENE: + self.channel_bank.set_scope(f.scope) + self.channel_bank.set_values(f.values.items()) + self.channel_bank.title = f.type + ' "%s"' % f.name + def handle_enter(self, fid): with self.w_lock: if fid not in self.w.functions: @@ -103,9 +111,7 @@ class Interface: if f.type == SCENE: 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.handle_show(f) self.channel_bank.highlight = True self.input.context = self.context_scene elif f.type == CHASER: @@ -208,9 +214,7 @@ class Interface: def scene_exit(self): self.primitive = None - self.channel_bank.set_scope(()) - self.channel_bank.title = "Channels" - + self.handle_exit() def handle_exit(self): @@ -248,6 +252,9 @@ class Interface: self.input.context = self.context_chaser self.chaser = c else: + self.channel_bank.set_scope(()) + self.channel_bank.title = "Channels" + self.input.context = self.context_base self.chaser = None self.current_cv = None @@ -312,7 +319,14 @@ class Interface: with self.w_lock: if num < 1 or num > len(self.current_cv.chaser.steps): return "Out of range" + + self.channel_bank.set_scope(()) + self.channel_bank.title = "Channels" + 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,): + self.handle_show(s.function) def chaser_edit(self, num=None): with self.w_lock: @@ -329,6 +343,28 @@ class Interface: self.handle_enter(c.function.id) + def chaser_delete(self, num=None): + with self.w_lock: + if num is not None: + r = self.chaser_select(num) + if r is not None: + return r + elif self.current_cv.selected is None: + return "No step selected" + + c = self.current_cv.chaser + s = c.steps[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 + else: + sel = max(0, sel-1) + self.current_cv.set_chaser(c, sel) + self._resize() + def chaser_fade(self, t, out=False): with self.w_lock: if self.current_cv.selected is None: @@ -361,6 +397,62 @@ class Interface: s.duration_mode = INHERIT self.current_cv.set_chaser(c, s.index) + def chaser_new(self, index, name, fid=None): + with self.w_lock: + if fid is not None: + if fid not in self.w.functions: + return "No such function" + f = self.w.functions[fid] + if f.type not in (SCENE, AUDIO, CHASER): + raise ValueError("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) + self.chaser_edit(s.index+1) + + def chaser_new_new(self, index, name, fname, type_): + with self.w_lock: + f = type_(self.w, name=fname) + + return self.chaser_new(index, name, fid=f.id) + + def chaser_rename(self, name): + with self.w_lock: + if self.current_cv.selected is None: + return "No step selected" + c = self.current_cv.chaser + s = c.steps[self.current_cv.selected] + s.name = name + + self.current_cv.set_chaser(c, s.index) + + def chaser_move(self, a, b = None): + with self.w_lock: + cursel = self.current_cv.selected + if cursel is not None: + cursel = self.current_cv.chaser.steps[cursel] + if b is not None: + sel = a - 1 + if sel < 0 or sel >= len(self.current_cv.chaser.steps): + return "Invalid selection" + to = b - 1 + else: + to = a - 1 + if self.current_cv.selected is None: + return "No step selected" + sel = self.current_cv.selected + if to < 0 or to >= len(self.current_cv.chaser.steps): + return "Invalid destination" + c = self.current_cv.chaser + s = c.steps[sel] + s.index = to + + self.current_cv.set_chaser(c, cursel.index if cursel is not None else None) + def __init__(self, path): ## Have to do most of the actual initialization in the main method, as curses isn't ## ready yet. @@ -425,19 +517,41 @@ class Interface: ("edit", self.chaser_edit), ("edit $num", self.chaser_edit), + ("delete", self.chaser_delete), + ("delete $num", self.chaser_delete), + + ("append $quoted_string", lambda n: self.chaser_new(-1, n)), + ("append $quoted_string from $num", lambda n, s: self.chaser_new(-1, n)), + ("append $quoted_string from new scene $quoted_string", lambda n, s: self.chaser_new_new(-1, n, s, Scene)), + + ("new $num $quoted_string", self.chaser_new), + ("new $num $quoted_string from $num", self.chaser_new), + ("new $num $quoted_string from new scene $quoted_string", lambda i, n, s: self.chaser_new_new(i, n, s, Scene)), + + ("rename $quoted_string", self.chaser_rename), + ("move $num to $num", self.chaser_move), + ("move $num", self.chaser_move), ("fade in $time", self.chaser_fade), ("fade out $time", lambda t: self.chaser_fade(t, True)), - ("duration $time", self.chaser_duration), + ("length $time", self.chaser_duration), ("unset", self.chaser_unset), + ("pager page", self.page), + ("pager clear", self.page_clear), ("quit", self.handle_exit), ), { None: "This mode is for editing chasers. All functions (excepting select) which take a number as an argument may be called without to act on the currently selected step.", "edit": "Edit the given step.", "select": "Select a step.", "fade": "Change the fade durations for the selected step.", - "duration": "Set the duration for the selected step.", + "length": "Set the duration for the selected step.", + "new": "Create a new step at the given position with the given name. Can also create a new function to use for the scene. Immediately enters edit mode for that step.", + "delete": "Remove the given step.", + "rename": "Rename the current step.", + "append": "Create a new step at the end of the chaser. See 'new' for details.", "unset": "Unset the duration of the selected step, inheriting the step's duration from its function.", + "pager": "Control the pager. In page mode, arrow keys, page up/down, home/end, and j/k can be used to scroll, q exits.", + "move": "Move the given step to the given position", "quit": "Return to the previous mode.", }, self.help) |