1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
"""
Define the base window object and the constants/"globals" used
by the file of this module.
A window is a little part of the screen, for example the input window,
the text window, the roster window, etc.
A Tab (see the poezio.tabs module) is composed of multiple Windows
"""
from __future__ import annotations
import curses
import logging
import string
from contextlib import contextmanager
from typing import Optional, Tuple, TYPE_CHECKING, cast
from poezio.theming import to_curses_attr, read_tuple
from poezio.ui.consts import FORMAT_CHAR
log = logging.getLogger(__name__)
if TYPE_CHECKING:
from _curses import _CursesWindow # pylint: disable=E0611
class Win:
__slots__ = ('_win', 'height', 'width', 'y', 'x')
width: int
height: int
x: int
y: int
def __init__(self) -> None:
if TAB_WIN is None:
raise ValueError
self._win: _CursesWindow = TAB_WIN
self.height, self.width = 0, 0
def _resize(self, height: int, width: int, y: int, x: int) -> None:
if height == 0 or width == 0:
self.height, self.width = height, width
return
self.height, self.width, self.x, self.y = height, width, x, y
try:
if TAB_WIN is None:
raise ValueError('TAB_WIN is None')
self._win = TAB_WIN.derwin(height, width, y, x)
except:
log.debug('DEBUG: mvwin returned ERR. Please investigate')
def resize(self, height: int, width: int, y: int, x: int) -> None:
"""
Override if something has to be done on resize
"""
self._resize(height, width, y, x)
def _refresh(self) -> None:
self._win.noutrefresh()
def addnstr(self, *args) -> None:
"""
Safe call to addnstr
"""
try:
self._win.addnstr(*args)
except:
# this actually mostly returns ERR, but works.
# more specifically, when the added string reaches the end
# of the screen.
pass
@contextmanager
def colored_text(self, color: Optional[Tuple]=None, attr: Optional[int]=None):
"""Context manager which sets up an attr/color when inside"""
if color is None and attr is None:
yield None
return
mode: int
if color is not None:
mode = to_curses_attr(color)
if attr is not None:
mode = mode | attr
else:
# attr cannot be none here
mode = cast(int, attr)
self._win.attron(mode)
yield None
self._win.attroff(mode)
def addstr(self, *args) -> None:
"""
Safe call to addstr
"""
try:
self._win.addstr(*args)
except:
pass
def move(self, y: int, x: int) -> None:
try:
self._win.move(y, x)
except:
pass
def addstr_colored(self, text: str, y: Optional[int] = None, x: Optional[int] = None) -> None:
"""
Write a string on the window, setting the
attributes as they are in the string.
For example:
\x19bhello → hello in bold
\x191}Bonj\x192}our → 'Bonj' in red and 'our' in green
next_attr_char is the \x19 delimiter
attr_char is the char following it, it can be
one of 'u', 'b', 'i', 'c[0-9]'
"""
if y is not None and x is not None:
self.move(y, x)
next_attr_char = text.find(FORMAT_CHAR)
attr_italic = curses.A_ITALIC if hasattr(
curses, 'A_ITALIC') else curses.A_REVERSE
while next_attr_char != -1 and text:
if next_attr_char + 1 < len(text):
attr_char = text[next_attr_char + 1].lower()
else:
attr_char = str()
if next_attr_char != 0:
self.addstr(text[:next_attr_char])
if attr_char == 'o':
self._win.attrset(0)
elif attr_char == 'u':
self._win.attron(curses.A_UNDERLINE)
elif attr_char == 'b':
self._win.attron(curses.A_BOLD)
elif attr_char == 'i':
self._win.attron(attr_italic)
if (attr_char in string.digits
or attr_char == '-') and attr_char != '':
color_str = text[next_attr_char +
1:text.find('}', next_attr_char)]
if ',' in color_str:
tup, char = read_tuple(color_str)
self._win.attron(to_curses_attr(tup))
if char:
if char == 'o':
self._win.attrset(0)
elif char == 'u':
self._win.attron(curses.A_UNDERLINE)
elif char == 'b':
self._win.attron(curses.A_BOLD)
elif char == 'i':
self._win.attron(attr_italic)
else:
# this will reset previous bold/uderline sequences if any was used
self._win.attroff(curses.A_UNDERLINE)
self._win.attroff(curses.A_BOLD)
elif color_str:
self._win.attron(to_curses_attr((int(color_str), -1)))
text = text[next_attr_char + len(color_str) + 2:]
else:
text = text[next_attr_char + 2:]
next_attr_char = text.find(FORMAT_CHAR)
self.addstr(text)
def finish_line(self, color: Optional[Tuple] = None) -> None:
"""
Write colored spaces until the end of line
"""
(y, x) = self._win.getyx()
size = self.width - x
if color:
self.addnstr(' ' * size, size, to_curses_attr(color))
else:
self.addnstr(' ' * size, size)
TAB_WIN: Optional[_CursesWindow] = None
|