self.scope = scope
self._log = log
super().__init__(**kwargs)
-
- @property
- def _title(self) -> str:
- return f'{self.client_id} '\
- + f':{"SERVER" if self.scope == LogScope.SERVER else "RAW"}'
+ self._title = f'{self.client_id} '\
+ + f':{"SERVER" if self.scope == LogScope.SERVER else "RAW"}'
def _send_msg(self, verb: str, params: tuple[str, ...], **kwargs) -> None:
self._client_trigger('send', msg=IrcMessage(verb=verb, params=params),
class _PrivmsgWindow(_ClientWindow):
prompt: _PrivmsgPromptWidget
- @property
- def _title(self) -> str:
- return f'{self.client_id} {self.nickname}'
-
def __init__(self, nickname: str, **kwargs) -> None:
self.nickname = nickname
super().__init__(**kwargs)
+ self._title = f'{self.client_id} {self.nickname}'
def cmd__chat(self, msg: str) -> None:
'PRIVMSG to target identified by .nickname.'
class _HistoryWidget(_ScrollableWidget):
+ _last_read: int = 0
_y_pgscroll: int
def __init__(self, wrap: Callable[[str], list[str]], **kwargs) -> None:
to_write += [self._wrapped[self._wrapped_idx][1]]
for i, line in enumerate(to_write):
self._write(line, i)
+ self._last_read = len(self._history)
+
+ @property
+ def n_lines_unread(self) -> int:
+ 'How many new lines have been logged since last focus.'
+ return len(self._history) - self._last_read
def _scroll(self, up: bool = True) -> None:
super()._scroll(up)
class _StatusLine(_Widget):
- def __init__(self, write: Callable, status_title: str, **kwargs) -> None:
+ def __init__(self, write: Callable, windows: list['Window'], **kwargs
+ ) -> None:
super().__init__(**kwargs)
+ self.idx_focus = 0
+ self._windows = windows
self._write = write
- self._status_title = status_title
def _draw(self) -> None:
- title_box = f'{self._status_title}]'
- status_line = title_box + '=' * (self._sizes.x - len(title_box))
- self._write(status_line, self._sizes.y)
+ listed = []
+ focused = None
+ for w in self._windows:
+ item = str(w.idx)
+ if (n := w.history.n_lines_unread):
+ item = f'({item}:{n})'
+ if w.idx == self.idx_focus:
+ focused = w
+ item = f'[{item}]'
+ listed += [item]
+ assert isinstance(focused, Window)
+ left = f'{focused.title})'
+ right = f'({" ".join(listed)}'
+ width_gap = max(1, (self._sizes.x - len(left) - len(right)))
+ self._write(left + '=' * width_gap + right, self._sizes.y)
class Window:
_y_status: int
_drawable = False
prompt: PromptWidget
+ _title = ':start'
def __init__(self, idx: int, term: 'Terminal', **kwargs) -> None:
super().__init__(**kwargs)
self._term = term
self.history = _HistoryWidget(wrap=self._term.wrap,
write=self._term.write)
- self._status_line = _StatusLine(write=self._term.write,
- status_title=self.status_title)
self.prompt = self.__annotations__['prompt'](write=self._term.write)
if hasattr(self._term, 'size'):
self.set_geometry()
def taint(self) -> None:
self.history.taint()
- self._status_line.taint()
self.prompt.taint()
@property
def tainted(self) -> bool:
- return (self._status_line.tainted or self.history.tainted
- or self.prompt.tainted)
+ return self.history.tainted or self.prompt.tainted
def set_geometry(self) -> None:
self._drawable = False
if self._term.size.y < _MIN_HEIGHT or self._term.size.x < _MIN_WIDTH:
- for widget in (self.history, self._status_line, self.prompt):
+ for widget in (self.history, self.prompt):
widget.set_geometry(_YX(-1, -1))
return
self._y_status = self._term.size.y - 2
self.history.set_geometry(_YX(self._y_status, self._term.size.x))
- self._status_line.set_geometry(_YX(self._y_status, self._term.size.x))
self.prompt.set_geometry(_YX(self._term.size.y - 1, self._term.size.x))
self._drawable = True
@property
- def _title(self) -> str:
- return ':start'
-
- @property
- def status_title(self) -> str:
+ def title(self) -> str:
'Window title to display in status line.'
- return f'{self.idx}) {self._title}'
+ return self._title
def draw_tainted(self) -> None:
if self._drawable:
- for widget in [
- w for w in (self.history, self.prompt, self._status_line)
- if w.tainted]:
+ for widget in [w for w in (self.history, self.prompt)
+ if w.tainted]:
widget.draw()
elif self._term.size.x > 0:
lines = ['']
self._term = term
self._window_idx = 0
self._windows: list[Window] = []
+ self._status_line = _StatusLine(write=self._term.write,
+ windows=self._windows)
self._new_window()
self._set_screen()
signal(SIGWINCH, lambda *_: self._set_screen())
msg = f'{str(datetime.now())[11:19]} {prefix} {msg}'
for win in self._log_target_wins(**kwargs):
win.history.append(msg)
+ if win != self.window:
+ self._status_line.taint()
def _new_window(self, win_class=Window, **kwargs) -> Window:
new_idx = len(self._windows)
win = win_class(idx=new_idx, term=self._term, **kwargs)
self._windows += [win]
- # FIXME below code responsible for log messages to streams without
- # window auto-switching into that newly created, which can be annoying
- if new_idx != self._window_idx:
- self._switch_window(new_idx)
return win
def redraw_affected(self) -> None:
'On focused window call .draw, then flush screen.'
self.window.draw_tainted()
+ if self._status_line.tainted:
+ self._status_line.draw()
self._term.flush()
def _set_screen(self) -> None:
self._term.calc_geometry()
for window in self._windows:
window.set_geometry()
+ self._status_line.set_geometry(_YX(self._term.size.y - 2,
+ self._term.size.x))
self.redraw_affected()
@property
def _switch_window(self, idx: int) -> None:
self.window.taint()
- self._window_idx = idx
+ self._status_line.idx_focus = self._window_idx = idx
+ self._status_line.taint()
@property
def _commands(self) -> dict[str, tuple[Callable[..., None | Optional[str]],
'List available windows.'
self._log('windows available via /window:')
for win in self._windows:
- self._log(f' {win.status_title}')
+ self._log(f' {win.idx}) {win.title}')
def cmd__quit(self) -> None:
'Trigger program exit.'