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

channelbank.py (5981B) - raw


      1 import curses
      2 
      3 from .globals import CURSES_LOCK
      4 
      5 _progress = ["    "]
      6 for i in range(8):
      7     pos = i // 2
      8     s = _progress[-1]
      9     _progress.append(s[:pos] + ('\u2588' if i%2 else '\u258c') + s[pos+1:])
     10 
     11 def get_progress(v):
     12     if v == 0:
     13         return _progress[0]
     14     elif v <= 36:
     15         return _progress[1]
     16     elif v <= 73:
     17         return _progress[2]
     18     elif v <= 109:
     19         return _progress[3]
     20     elif v <= 145:
     21         return _progress[4]
     22     elif v <= 181:
     23         return _progress[5]
     24     elif v <= 218:
     25         return _progress[6]
     26     elif v <= 254:
     27         return _progress[7]
     28     return _progress[8]
     29 
     30 class ChannelView:
     31     width = 9
     32     height = 3
     33 
     34     def _refresh_channel(self):
     35         with CURSES_LOCK:
     36             self.win.addstr(0, 0, "%03d.%03d" % (self.c.f.id, self.c.id), curses.A_UNDERLINE if self._active else 0)
     37             self.win.refresh()
     38 
     39     def _refresh_value(self):
     40         with CURSES_LOCK:
     41             self.win.addstr(1, 0, get_progress(self._value))
     42             self.win.addstr(1, 4, "%03d" % self._value, (curses.A_ITALIC|curses.A_BOLD if self._held else 0))
     43             self.win.refresh()
     44 
     45     def refresh(self):
     46         self._refresh_value()
     47         self._refresh_channel()
     48 
     49     @property
     50     def active(self):
     51         return self._active
     52 
     53     @active.setter
     54     def active(self, v):
     55         if v != self._active:
     56             self._active = v
     57             self._refresh_channel()
     58 
     59     @property
     60     def value(self):
     61         return self._value
     62 
     63     @value.setter
     64     def value(self, v):
     65         if v != self._value:
     66             self._value = v
     67             self._refresh_value()
     68 
     69     @property
     70     def held(self):
     71         return self._held
     72 
     73     @held.setter
     74     def held(self, v):
     75         if v != self._held:
     76             self._held = v
     77             self._refresh_value()
     78 
     79     def set_pos(self, y, x):
     80         self.y = self.y
     81         self.x = self.x
     82         with CURSES_LOCK:
     83             self.win.refresh()
     84             self.win.mvwin(y, x)
     85         self.refresh()
     86 
     87     def __init__(self, root, c, y, x, value=0, held=False, active=False):
     88         self._active = active
     89         self._held = held
     90         self._value = value
     91         self.c = c
     92         
     93         self.y = y
     94         self.x = x
     95 
     96         self.root = root
     97         with CURSES_LOCK:
     98             self.win = root.subpad(self.height, self.width, self.y, self.x)
     99             self.win.leaveok(True)
    100 
    101         self._refresh_value()
    102         self._refresh_channel()
    103 
    104 class ChannelBank:
    105     def set_dim(self, height, width):
    106         if ((height-2)//ChannelView.height) * ((width-4)//ChannelView.width) < len(self.scope):
    107             raise NotImplementedError("Screen size to small")
    108 
    109 
    110         with CURSES_LOCK:
    111             if (height, width) != (self._height, self._width):
    112                 self.win.erase()
    113                 self.win.refresh()
    114 
    115                 self.win.resize(height, width)
    116                 self.win.redrawwin()
    117 
    118             self._height = height
    119             self._width = width
    120             self._refresh_scope()
    121             self._put_title()
    122             self.win.refresh()
    123 
    124     def _refresh_scope(self):
    125         with CURSES_LOCK:
    126             self.win.erase()
    127             ncv = {}
    128             cols = (self._width-4)//ChannelView.width
    129             self._sscope = sorted(self.scope, key=lambda a: (a.f.id, a.id))
    130             for n, c in enumerate(self._sscope):
    131                 row = (n // cols)*ChannelView.height + 1
    132                 col = (n % cols)*ChannelView.width + 2
    133                 if c in self._cv:
    134                     active = self._cv[c].active
    135                     held = self._cv[c].held
    136                     value = self._cv[c].value
    137                 else:
    138                     active = False
    139                     held = False
    140                     value = 0
    141                 ncv[c] = ChannelView(self.win, c, row, col, value=value, active=active, held=held)
    142             self._cv = ncv
    143             self._put_title()
    144             self.win.refresh()
    145 
    146     def set_pos(self, y, x):
    147         if (y, x) != (self._y, self._x):
    148             with CURSES_LOCK:
    149                 self.win.mvwin(y, x)
    150                 self._put_title()
    151                 self.win.refresh()
    152 
    153     def set_active(self, channels, v=True):
    154         for c in channels:
    155             self._cv[c].active = v
    156 
    157     def set_held(self, channels, v=True):
    158         for c in channels:
    159             self._cv[c].held = v
    160 
    161     def set_values(self, cv):
    162         if isinstance(cv, dict):
    163             cv = cv.items()
    164         for c, v in cv:
    165             self._cv[c].value = v
    166 
    167     def set_scope(self, scope):
    168         self.scope = frozenset(scope)
    169         self._refresh_scope()
    170 
    171     @property
    172     def title(self):
    173         return self._title
    174 
    175     def _put_title(self):
    176         self.win.border()
    177         pos = min(self._width-2-len(self._title), (3*self._width)//4 - (len(self._title) // 2))
    178         self.win.addstr(self._height-1, pos, self._title, curses.A_REVERSE if self._highlight else 0)
    179 
    180     @property
    181     def highlight(self):
    182         return self._highlight
    183 
    184     @highlight.setter
    185     def highlight(self, value):
    186         if value != self._highlight:
    187             self._highlight = value
    188             with CURSES_LOCK:
    189                 self._put_title()
    190                 self.win.refresh()
    191 
    192     @title.setter
    193     def title(self, v):
    194         if v != self._title:
    195             with CURSES_LOCK:
    196                 self._title = v
    197                 self.win.border()
    198                 self._put_title()
    199                 self.win.refresh()
    200             
    201     def __init__(self, y, x, height, width, scope=frozenset()):
    202         with CURSES_LOCK:
    203             self.win = curses.newwin(height, width, y, x)
    204             self.win.keypad(True)
    205         self._highlight = False
    206         self.scope = frozenset(scope)
    207         self._sscope = []
    208         self._title = "Channels"
    209         self._cv = {}
    210         self._height = height
    211         self._width = width
    212         self._y = -1
    213         self._x = -1
    214         self.set_pos(y, x)
    215         self.set_dim(height, width)