#!/usr/bin/env python3 import curses import math import threading from blc2.constants import INFTY, MANUAL, JOIN, CHASERSTEP CURSES_LOCK = threading.RLock() def format_time(n): if n == INFTY: return "∞s" elif n == 0: return "0s" postfixes = "mcisahkegtp" idx = 0 multiple = 10 while n >= multiple: idx += 1 multiple *= 10 return str(n)[0]+postfixes[idx] def format_long(n): if n == INFTY: return " ∞s" elif n == 0: return " 0s" elif n < 10000: return "%4d" % n else: n = str(n/1000)[:3] if n[-1] == '.': n = n[:-1] return "%4s" % n class ChaserView: def set_dim(self, height, width): if height < 5 or width < 10: raise ValueError("Size too small") with self._lock: if (height, width) != (self._height, self._width): self.win.erase() self.win.noutrefresh() self.win.resize(height, width) self.win.redrawwin() self._height = height self._width = width self._redraw() @staticmethod def fit(s, l, pad=False): ## TODO: Try shortening by words first? if len(s) > l: return s[:l-1] + '…' elif len(s) < l and pad: return s + ' '*(l-len(s)) return s def set_pos(self, y, x): with self._lock: if (y, x) != (self._y, self._x): self.win.mvwin(y, x) self._y = y self._x = x self.win.noutrefresh() @property def highlight(self): return self._highlight @highlight.setter def highlight(self, value): with self._lock: if self._highlight != value: self._highlight = value self._redraw() def _redraw(self): self.win.erase() self.win.border() self.win.hline(2, 1, curses.ACS_HLINE, self._width-2) self.win.addch(2, 0, curses.ACS_LTEE) self.win.addch(2, self._width-1, curses.ACS_RTEE) if self._chaser is None: self.win.refresh() return c = self._chaser w = self._width - 2 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 first = 0 if maxsteps < len(c.steps): if self._selected is None: first = 0 last = maxsteps else: last = min(self._selected + (maxsteps // 2), len(c.steps)) first = last - maxsteps if first < 0: last -= first first = 0 else: first = 0 last = len(c.steps) steps = c.steps[first:last] for n, s in enumerate(steps, 1): if first+n-1 == self._selected: attrs = curses.A_REVERSE else: attrs = 0 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.type == CHASERSTEP and s.duration_mode == MANUAL) else ' ', format_long(s.fade_in), format_long(s.duration if s.type != CHASERSTEP else s.length), format_long(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(2, self._width//2, '⯅') if last < len(c.steps): self.win.addch(self._height-1, self._width//2, '⯆') self.win.refresh() @property def selected(self): with self._lock: return self._selected @selected.setter def selected(self, value): with self._lock: if value != self._selected: self._selected = value ## TODO: Clean this up if possible? self._redraw() def set_chaser(self, chaser, selected=None): with self._lock: self._chaser = chaser self._selected = selected if chaser is not None and chaser.steps: self._numformat = "%%%dd" % math.ceil(math.log10(len(chaser.steps))) self._redraw() @property def chaser(self): with self._lock: return self._chaser def __init__(self, y, x, height, width): with CURSES_LOCK: self.win = curses.newwin(height, width, y, x) self.win.leaveok(True) self.win.keypad(True) self._lock = threading.RLock() self._height = height self._width = width self._y = -1 self._x = -1 self._chaser = None self._highlight = False self._numformat = "" self._selected = -1 self.set_pos(y, x) self.set_dim(height, width)