blob: f1530c295439bc52ed904ea3dfe629e20c33a260 [file] [log] [blame]
Greg Clayton1827fc22015-09-19 00:39:09 +00001import time
2import curses, curses.panel
3
4class Point(object):
5 def __init__(self, x, y):
6 self.x = x
7 self.y = y
8
9 def __repr__(self):
10 return str(self)
11
12 def __str__(self):
13 return "(x=%u, y=%u)" % (self.x, self.y)
14
15 def is_valid_coordinate(self):
16 return self.x >= 0 and self.y >= 0
17
18class Size(object):
19 def __init__(self, w, h):
20 self.w = w
21 self.h = h
22
23 def __repr__(self):
24 return str(self)
25
26 def __str__(self):
27 return "(w=%u, h=%u)" % (self.w, self.h)
28
29class Rect(object):
30 def __init__(self, x=0, y=0, w=0, h=0):
31 self.origin = Point(x, y)
32 self.size = Size(w, h)
33
34 def __repr__(self):
35 return str(self)
36
37 def __str__(self):
38 return "{ %s, %s }" % (str(self.origin), str(self.size))
39
40 def get_min_x(self):
41 return self.origin.x
42
43 def get_max_x(self):
44 return self.origin.x + self.size.w
45
46 def get_min_y(self):
47 return self.origin.y
48
49 def get_max_y(self):
50 return self.origin.y + self.size.h
51
52 def contains_point(self, pt):
53 if pt.x < self.get_max_x():
54 if pt.y < self.get_max_y():
55 if pt.x >= self.get_min_y():
56 return pt.y >= self.get_min_y()
57 return False
58
59class Window(object):
60 def __init__(self, window):
61 self.window = window
62
63 def point_in_window(self, pt):
64 size = self.get_size()
65 return pt.x >= 0 and pt.x < size.w and pt.y >= 0 and pt.y < size.h
66
67 def addstr(self, pt, str):
68 try:
69 self.window.addstr(pt.y, pt.x, str)
70 except:
71 pass
72
73 def addnstr(self, pt, str, n):
74 try:
75 self.window.addnstr(pt.y, pt.x, str, n)
76 except:
77 pass
78
79 def box(self):
80 self.window.box()
81
82 def get_contained_rect(self, top_inset=0, bottom_inset=0, left_inset=0, right_inset=0, height=-1, width=-1):
83 '''Get a rectangle based on the top "height" lines of this window'''
84 rect = self.get_frame()
85 x = rect.origin.x + left_inset
86 y = rect.origin.y + top_inset
87 if height == -1:
88 h = rect.size.h - (top_inset + bottom_inset)
89 else:
90 h = height
91 if width == -1:
92 w = rect.size.w - (left_inset + right_inset)
93 else:
94 w = width
95 return Rect (x = x, y = y, w = w, h = h)
96
97 def erase(self):
98 self.window.erase()
99
100 def get_frame(self):
101 position = self.get_position()
102 size = self.get_size()
103 return Rect(x=position.x, y=position.y, w=size.w, h=size.h)
104
105 def get_position(self):
106 (y, x) = self.window.getbegyx()
107 return Point(x, y)
108
109 def get_size(self):
110 (y, x) = self.window.getmaxyx()
111 return Size(w=x, h=y)
112
113 def refresh(self):
114 curses.panel.update_panels()
115 return self.window.refresh()
116
117 def resize(self, size):
118 return window.resize(size.h, size.w)
119
120class Panel(Window):
121 def __init__(self, frame):
122 window = curses.newwin(frame.size.h,frame.size.w, frame.origin.y, frame.origin.x)
123 super(Panel, self).__init__(window)
124 self.panel = curses.panel.new_panel(window)
125
126 def top(self):
127 self.panel.top()
128
129 def set_position(self, pt):
130 self.panel.move(pt.y, pt.x)
131
132 def slide_position(self, pt):
133 new_position = self.get_position()
134 new_position.x = new_position.x + pt.x
135 new_position.y = new_position.y + pt.y
136 self.set_position(new_position)
137
138class BoxedPanel(Panel):
139 def __init__(self, frame, title):
140 super(BoxedPanel, self).__init__(frame)
141 self.title = title
142 self.lines = list()
143 self.first_visible_idx = 0
144 self.update()
145
146 def get_usable_width(self):
147 '''Valid usable width is 0 to (width - 3) since the left and right lines display the box around
148 this frame and we skip a leading space'''
149 w = self.get_size().w
150 if w > 3:
151 return w-3
152 else:
153 return 0
154
155 def get_usable_height(self):
156 '''Valid line indexes are 0 to (height - 2) since the top and bottom lines display the box around this frame.'''
157 h = self.get_size().h
158 if h > 2:
159 return h-2
160 else:
161 return 0
162
163 def get_point_for_line(self, global_line_idx):
164 '''Returns the point to use when displaying a line whose index is "line_idx"'''
165 line_idx = global_line_idx - self.first_visible_idx
166 num_lines = self.get_usable_height()
167 if line_idx < num_lines:
168 return Point(x=2, y=1+line_idx)
169 else:
170 return Point(x=-1, y=-1) # return an invalid coordinate if the line index isn't valid
171
172 def set_title (self, title, update=True):
173 self.title = title
174 if update:
175 self.update()
176
177 def _adjust_first_visible_line(self):
178 num_lines = len(self.lines)
179 max_visible_lines = self.get_usable_height()
180 if (num_lines - self.first_visible_idx) > max_visible_lines:
181 self.first_visible_idx = num_lines - max_visible_lines
182
183 def append_line(self, s, update=True):
184 self.lines.append(s)
185 self._adjust_first_visible_line()
186 if update:
187 self.update()
188
189 def set_line(self, line_idx, s, update=True):
190 '''Sets a line "line_idx" within the boxed panel to be "s"'''
191 if line_idx < 0:
192 return
193 while line_idx >= len(self.lines):
194 self.lines.append('')
195 self.lines[line_idx] = s
196 self._adjust_first_visible_line()
197 if update:
198 self.update()
199
200 def update(self):
201 self.erase()
202 self.box()
203 if self.title:
204 self.addstr(Point(x=2, y=0), ' ' + self.title + ' ')
205 max_width = self.get_usable_width()
206 for line_idx in range(self.first_visible_idx, len(self.lines)):
207 pt = self.get_point_for_line(line_idx)
208 if pt.is_valid_coordinate():
209 self.addnstr(pt, self.lines[line_idx], max_width)
210 else:
211 return
212
213class StatusPanel(Panel):
214 def __init__(self, frame):
215 super(StatusPanel, self).__init__(frame)
216 self.status_items = list()
217 self.status_dicts = dict()
218 self.next_status_x = 1
219
220 def add_status_item(self, name, title, format, width, value, update=True):
221 status_item_dict = { 'name': name,
222 'title' : title,
223 'width' : width,
224 'format' : format,
225 'value' : value,
226 'x' : self.next_status_x }
227 index = len(self.status_items)
228 self.status_items.append(status_item_dict)
229 self.status_dicts[name] = index
230 self.next_status_x += width + 2;
231 if update:
232 self.update()
233
234 def increment_status(self, name, update=True):
235 if name in self.status_dicts:
236 status_item_idx = self.status_dicts[name]
237 status_item_dict = self.status_items[status_item_idx]
238 status_item_dict['value'] = status_item_dict['value'] + 1
239 if update:
240 self.update()
241
242 def update_status(self, name, value, update=True):
243 if name in self.status_dicts:
244 status_item_idx = self.status_dicts[name]
245 status_item_dict = self.status_items[status_item_idx]
246 status_item_dict['value'] = status_item_dict['format'] % (value)
247 if update:
248 self.update()
249 def update(self):
250 self.erase();
251 for status_item_dict in self.status_items:
252 self.addnstr(Point(x=status_item_dict['x'], y=0), '%s: %s' % (status_item_dict['title'], status_item_dict['value']), status_item_dict['width'])
253
254stdscr = None
255
256def intialize_curses():
257 global stdscr
258 stdscr = curses.initscr()
259 curses.noecho()
260 curses.cbreak()
261 stdscr.keypad(1)
262 try:
263 curses.start_color()
264 except:
265 pass
266 return Window(stdscr)
267
268def terminate_curses():
269 global stdscr
270 if stdscr:
271 stdscr.keypad(0)
272 curses.echo()
273 curses.nocbreak()
274 curses.endwin()
275