blc2 - Python library and frontend for running theatrical lighting and SFX. Written for the English 2041F fall 2019 theatre production of "The Cenci".

git clone https://benconnors.ca/git-repos/blc2

Log | Files | Refs

chaserview.py (5313B) - raw


      1 #!/usr/bin/env python3
      2 
      3 import curses
      4 import math
      5 import threading 
      6 
      7 from blc2.constants import INFTY, MANUAL, JOIN, CHASERSTEP
      8 
      9 CURSES_LOCK = threading.RLock()
     10 
     11 def format_time(n):
     12     if n == INFTY:
     13         return "∞s"
     14     elif n == 0:
     15         return "0s"
     16     postfixes = "mcisahkegtp"
     17     idx = 0
     18     multiple = 10
     19     while n >= multiple:
     20         idx += 1
     21         multiple *= 10
     22 
     23     return str(n)[0]+postfixes[idx]
     24 
     25 def format_long(n):
     26     if n == INFTY:
     27         return "  ∞s"
     28     elif n == 0:
     29         return "  0s"
     30     elif n < 10000:
     31         return "%4d" % n
     32     else:
     33         n = str(n/1000)[:3]
     34         if n[-1] == '.':
     35             n = n[:-1]
     36         return "%4s" % n
     37 
     38 class ChaserView:
     39     def set_dim(self, height, width):
     40         if height < 5 or width < 10:
     41             raise ValueError("Size too small")
     42 
     43         with self._lock:
     44             if (height, width) != (self._height, self._width):
     45                 self.win.erase()
     46                 self.win.noutrefresh()
     47 
     48                 self.win.resize(height, width)
     49                 self.win.redrawwin()
     50 
     51             self._height = height
     52             self._width = width
     53             self._redraw()
     54 
     55     @staticmethod
     56     def fit(s, l, pad=False):
     57         ## TODO: Try shortening by words first?
     58         if len(s) > l:
     59             return s[:l-1] + '…'
     60         elif len(s) < l and pad:
     61             return s + ' '*(l-len(s))
     62         return s
     63         
     64     def set_pos(self, y, x):
     65         with self._lock:
     66             if (y, x) != (self._y, self._x):
     67                 self.win.mvwin(y, x)
     68                 self._y = y
     69                 self._x = x
     70                 self.win.noutrefresh()
     71 
     72     @property
     73     def highlight(self):
     74         return self._highlight
     75 
     76     @highlight.setter
     77     def highlight(self, value):
     78         with self._lock:
     79             if self._highlight != value:
     80                 self._highlight = value
     81                 self._redraw()
     82 
     83     def _redraw(self):
     84         self.win.erase()
     85         self.win.border()
     86         self.win.hline(2, 1, curses.ACS_HLINE, self._width-2)
     87         self.win.addch(2, 0, curses.ACS_LTEE)
     88         self.win.addch(2, self._width-1, curses.ACS_RTEE)
     89 
     90         if self._chaser is None:
     91             self.win.refresh()
     92             return
     93 
     94         c = self._chaser
     95         w = self._width - 2
     96         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)
     97 
     98         maxsteps = self._height - 4
     99         first = 0
    100         if maxsteps < len(c.steps):
    101             if self._selected is None:
    102                 first = 0
    103                 last = maxsteps
    104             else:
    105                 last = min(self._selected + (maxsteps // 2), len(c.steps))
    106                 first = last - maxsteps
    107                 if first < 0:
    108                     last -= first
    109                     first = 0
    110         else:
    111             first = 0
    112             last = len(c.steps)
    113         steps = c.steps[first:last]
    114         for n, s in enumerate(steps, 1):
    115             if first+n-1 == self._selected:
    116                 attrs = curses.A_REVERSE
    117             else: 
    118                 attrs = 0
    119             if s.type == CHASERSTEP and s.function is not None:
    120                 ft = s.function.type[0].upper()
    121                 fid = str(s.function.id)
    122             elif s.type != CHASERSTEP:
    123                 ft = s.type[0].upper()
    124                 fid = str(s.id)
    125             else:
    126                 ft = "-"
    127                 fid = "---"
    128 
    129             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))
    130             self.win.addstr(n+2, 1, self.fit((self._numformat % (first+n)) + ": " + s.name, w-len(t), pad=True)+t, attrs)
    131 
    132         if first > 0:
    133             self.win.addch(2, self._width//2, '⯅')
    134         if last < len(c.steps):
    135             self.win.addch(self._height-1, self._width//2, '⯆')
    136         
    137         self.win.refresh()
    138 
    139     @property
    140     def selected(self):
    141         with self._lock:
    142             return self._selected
    143 
    144     @selected.setter
    145     def selected(self, value):
    146         with self._lock:
    147             if value != self._selected:
    148                 self._selected = value
    149                 ## TODO: Clean this up if possible?
    150                 self._redraw()
    151 
    152     def set_chaser(self, chaser, selected=None):
    153         with self._lock:
    154             self._chaser = chaser
    155             self._selected = selected
    156             if chaser is not None and chaser.steps:
    157                 self._numformat = "%%%dd" % math.ceil(math.log10(len(chaser.steps)))
    158             self._redraw()
    159 
    160     @property
    161     def chaser(self):
    162         with self._lock:
    163             return self._chaser
    164 
    165     def __init__(self, y, x, height, width):
    166         with CURSES_LOCK:
    167             self.win = curses.newwin(height, width, y, x)
    168             self.win.leaveok(True)
    169             self.win.keypad(True)
    170         self._lock = threading.RLock()
    171         self._height = height
    172         self._width = width
    173         self._y = -1
    174         self._x = -1
    175 
    176         self._chaser = None
    177         self._highlight = False
    178         self._numformat = ""
    179         self._selected = -1
    180 
    181         self.set_pos(y, x)
    182         self.set_dim(height, width)