summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--blc2/functions/chaser.py2
-rw-r--r--blc2/workspace.py4
-rwxr-xr-xinterface/chaserview.py2
-rw-r--r--interface/dialog.py64
-rw-r--r--interface/interface.py132
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)