blob: ae0082ce9b7f11eedfcaf335cf5574e0f1e564c0 [file] [log] [blame]
Zachary Turnerc1b7cd72015-11-05 19:22:28 +00001from __future__ import absolute_import
Zachary Turner19474e12015-11-03 19:20:39 +00002
Zachary Turnerc1b7cd72015-11-05 19:22:28 +00003# System modules
4import curses
5import curses.panel
Greg Clayton87349242015-09-22 00:35:20 +00006import sys
Kate Stoneb9c1b512016-09-06 20:57:50 +00007import time
Greg Clayton1827fc22015-09-19 00:39:09 +00008
Zachary Turnerc1b7cd72015-11-05 19:22:28 +00009# Third-party modules
10import six
11
12# LLDB modules
13
Kate Stoneb9c1b512016-09-06 20:57:50 +000014
Greg Clayton1827fc22015-09-19 00:39:09 +000015class Point(object):
Kate Stoneb9c1b512016-09-06 20:57:50 +000016
Greg Clayton1827fc22015-09-19 00:39:09 +000017 def __init__(self, x, y):
18 self.x = x
19 self.y = y
20
21 def __repr__(self):
22 return str(self)
23
24 def __str__(self):
25 return "(x=%u, y=%u)" % (self.x, self.y)
Greg Claytonc12cc592015-10-16 00:34:18 +000026
27 def __eq__(self, rhs):
28 return self.x == rhs.x and self.y == rhs.y
29
30 def __ne__(self, rhs):
31 return self.x != rhs.x or self.y != rhs.y
Kate Stoneb9c1b512016-09-06 20:57:50 +000032
Greg Clayton1827fc22015-09-19 00:39:09 +000033 def is_valid_coordinate(self):
34 return self.x >= 0 and self.y >= 0
Kate Stoneb9c1b512016-09-06 20:57:50 +000035
36
Greg Clayton1827fc22015-09-19 00:39:09 +000037class Size(object):
Kate Stoneb9c1b512016-09-06 20:57:50 +000038
Greg Clayton1827fc22015-09-19 00:39:09 +000039 def __init__(self, w, h):
40 self.w = w
41 self.h = h
42
43 def __repr__(self):
44 return str(self)
45
46 def __str__(self):
47 return "(w=%u, h=%u)" % (self.w, self.h)
48
Greg Claytonc12cc592015-10-16 00:34:18 +000049 def __eq__(self, rhs):
50 return self.w == rhs.w and self.h == rhs.h
51
52 def __ne__(self, rhs):
53 return self.w != rhs.w or self.h != rhs.h
54
Kate Stoneb9c1b512016-09-06 20:57:50 +000055
Greg Clayton1827fc22015-09-19 00:39:09 +000056class Rect(object):
Kate Stoneb9c1b512016-09-06 20:57:50 +000057
Greg Clayton1827fc22015-09-19 00:39:09 +000058 def __init__(self, x=0, y=0, w=0, h=0):
59 self.origin = Point(x, y)
60 self.size = Size(w, h)
Kate Stoneb9c1b512016-09-06 20:57:50 +000061
Greg Clayton1827fc22015-09-19 00:39:09 +000062 def __repr__(self):
63 return str(self)
64
65 def __str__(self):
66 return "{ %s, %s }" % (str(self.origin), str(self.size))
67
68 def get_min_x(self):
69 return self.origin.x
70
71 def get_max_x(self):
72 return self.origin.x + self.size.w
73
74 def get_min_y(self):
75 return self.origin.y
76
77 def get_max_y(self):
78 return self.origin.y + self.size.h
Kate Stoneb9c1b512016-09-06 20:57:50 +000079
Greg Clayton1827fc22015-09-19 00:39:09 +000080 def contains_point(self, pt):
81 if pt.x < self.get_max_x():
82 if pt.y < self.get_max_y():
83 if pt.x >= self.get_min_y():
84 return pt.y >= self.get_min_y()
85 return False
86
Greg Claytonc12cc592015-10-16 00:34:18 +000087 def __eq__(self, rhs):
88 return self.origin == rhs.origin and self.size == rhs.size
89
90 def __ne__(self, rhs):
91 return self.origin != rhs.origin or self.size != rhs.size
92
Kate Stoneb9c1b512016-09-06 20:57:50 +000093
Greg Clayton5ea44832015-10-15 00:49:36 +000094class QuitException(Exception):
Kate Stoneb9c1b512016-09-06 20:57:50 +000095
Greg Clayton5ea44832015-10-15 00:49:36 +000096 def __init__(self):
97 super(QuitException, self).__init__('QuitException')
98
Kate Stoneb9c1b512016-09-06 20:57:50 +000099
Greg Clayton1827fc22015-09-19 00:39:09 +0000100class Window(object):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000101
102 def __init__(self, window, delegate=None, can_become_first_responder=True):
Greg Clayton1827fc22015-09-19 00:39:09 +0000103 self.window = window
Greg Clayton87349242015-09-22 00:35:20 +0000104 self.parent = None
105 self.delegate = delegate
106 self.children = list()
Greg Clayton37191a22015-10-07 20:00:28 +0000107 self.first_responders = list()
Greg Clayton87349242015-09-22 00:35:20 +0000108 self.can_become_first_responder = can_become_first_responder
Greg Clayton414dba52015-09-24 00:19:42 +0000109 self.key_actions = dict()
Kate Stoneb9c1b512016-09-06 20:57:50 +0000110
Greg Clayton87349242015-09-22 00:35:20 +0000111 def add_child(self, window):
112 self.children.append(window)
113 window.parent = self
Kate Stoneb9c1b512016-09-06 20:57:50 +0000114
Greg Claytonc12cc592015-10-16 00:34:18 +0000115 def resize(self, size):
116 self.window.resize(size.h, size.w)
Greg Clayton414dba52015-09-24 00:19:42 +0000117
Greg Claytonc12cc592015-10-16 00:34:18 +0000118 def resize_child(self, child, delta_size, adjust_neighbors):
119 if child in self.children:
120 frame = self.get_frame()
121 orig_frame = child.get_frame()
Kate Stoneb9c1b512016-09-06 20:57:50 +0000122 new_frame = Rect(
123 x=orig_frame.origin.x,
124 y=orig_frame.origin.y,
125 w=orig_frame.size.w +
126 delta_size.w,
127 h=orig_frame.size.h +
128 delta_size.h)
Greg Claytonc12cc592015-10-16 00:34:18 +0000129 old_child_max_x = orig_frame.get_max_x()
130 new_child_max_x = new_frame.get_max_x()
131 window_max_x = frame.get_max_x()
132 if new_child_max_x < window_max_x:
133 child.resize(new_frame.size)
134 if old_child_max_x == window_max_x:
135 new_frame.origin.x += window_max_x - new_child_max_x
136 child.set_position(new_frame.origin)
137 elif new_child_max_x > window_max_x:
138 new_frame.origin.x -= new_child_max_x - window_max_x
139 child.set_position(new_frame.origin)
140 child.resize(new_frame.size)
Kate Stoneb9c1b512016-09-06 20:57:50 +0000141
Greg Claytonc12cc592015-10-16 00:34:18 +0000142 if adjust_neighbors:
Zachary Turner35d017f2015-10-23 17:04:29 +0000143 #print('orig_frame = %s\r\n' % (str(orig_frame)), end='')
Greg Claytonc12cc592015-10-16 00:34:18 +0000144 for curr_child in self.children:
145 if curr_child is child:
146 continue
147 curr_child_frame = curr_child.get_frame()
148 if delta_size.w != 0:
Zachary Turner35d017f2015-10-23 17:04:29 +0000149 #print('curr_child_frame = %s\r\n' % (str(curr_child_frame)), end='')
Kate Stoneb9c1b512016-09-06 20:57:50 +0000150 if curr_child_frame.get_min_x() == orig_frame.get_max_x():
Greg Claytonc12cc592015-10-16 00:34:18 +0000151 curr_child_frame.origin.x += delta_size.w
152 curr_child_frame.size.w -= delta_size.w
Zachary Turner35d017f2015-10-23 17:04:29 +0000153 #print('adjusted curr_child_frame = %s\r\n' % (str(curr_child_frame)), end='')
Kate Stoneb9c1b512016-09-06 20:57:50 +0000154 curr_child.resize(curr_child_frame.size)
155 curr_child.slide_position(
156 Size(w=delta_size.w, h=0))
Greg Claytonc12cc592015-10-16 00:34:18 +0000157 elif curr_child_frame.get_max_x() == orig_frame.get_min_x():
158 curr_child_frame.size.w -= delta_size.w
Zachary Turner35d017f2015-10-23 17:04:29 +0000159 #print('adjusted curr_child_frame = %s\r\n' % (str(curr_child_frame)), end='')
Kate Stoneb9c1b512016-09-06 20:57:50 +0000160 curr_child.resize(curr_child_frame.size)
161
Greg Clayton414dba52015-09-24 00:19:42 +0000162 def add_key_action(self, arg, callback, decription):
163 if isinstance(arg, list):
164 for key in arg:
165 self.add_key_action(key, callback, description)
166 else:
Zachary Turnerf67f7e32015-10-26 16:51:09 +0000167 if isinstance(arg, six.integer_types):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000168 key_action_dict = {'key': arg,
169 'callback': callback,
170 'description': decription}
Greg Clayton414dba52015-09-24 00:19:42 +0000171 self.key_actions[arg] = key_action_dict
Kate Stoneb9c1b512016-09-06 20:57:50 +0000172 elif isinstance(arg, basestring):
Greg Clayton414dba52015-09-24 00:19:42 +0000173 key_integer = ord(arg)
Kate Stoneb9c1b512016-09-06 20:57:50 +0000174 key_action_dict = {'key': key_integer,
175 'callback': callback,
176 'description': decription}
Greg Clayton414dba52015-09-24 00:19:42 +0000177 self.key_actions[key_integer] = key_action_dict
178 else:
179 raise ValueError
Greg Clayton72d51442015-10-13 23:16:29 +0000180
181 def draw_title_box(self, title):
182 is_in_first_responder_chain = self.is_in_first_responder_chain()
183 if is_in_first_responder_chain:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000184 self.attron(curses.A_REVERSE)
Greg Clayton72d51442015-10-13 23:16:29 +0000185 self.box()
186 if is_in_first_responder_chain:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000187 self.attroff(curses.A_REVERSE)
Greg Clayton72d51442015-10-13 23:16:29 +0000188 if title:
189 self.addstr(Point(x=2, y=0), ' ' + title + ' ')
Kate Stoneb9c1b512016-09-06 20:57:50 +0000190
Greg Clayton87349242015-09-22 00:35:20 +0000191 def remove_child(self, window):
192 self.children.remove(window)
Kate Stoneb9c1b512016-09-06 20:57:50 +0000193
Greg Clayton37191a22015-10-07 20:00:28 +0000194 def get_first_responder(self):
195 if len(self.first_responders):
196 return self.first_responders[-1]
197 else:
198 return None
199
Greg Clayton87349242015-09-22 00:35:20 +0000200 def set_first_responder(self, window):
201 if window.can_become_first_responder:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000202 if six.callable(
203 getattr(
204 window,
205 "hidden",
206 None)) and window.hidden():
Greg Clayton87349242015-09-22 00:35:20 +0000207 return False
Kate Stoneb9c1b512016-09-06 20:57:50 +0000208 if window not in self.children:
Greg Clayton87349242015-09-22 00:35:20 +0000209 self.add_child(window)
Greg Clayton37191a22015-10-07 20:00:28 +0000210 # See if we have a current first responder, and if we do, let it know that
Kate Stoneb9c1b512016-09-06 20:57:50 +0000211 # it will be resigning as first responder
Greg Clayton37191a22015-10-07 20:00:28 +0000212 first_responder = self.get_first_responder()
213 if first_responder:
214 first_responder.relinquish_first_responder()
215 # Now set the first responder to "window"
216 if len(self.first_responders) == 0:
217 self.first_responders.append(window)
218 else:
219 self.first_responders[-1] = window
Greg Clayton87349242015-09-22 00:35:20 +0000220 return True
221 else:
222 return False
Kate Stoneb9c1b512016-09-06 20:57:50 +0000223
Greg Clayton37191a22015-10-07 20:00:28 +0000224 def push_first_responder(self, window):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000225 # Only push the window as the new first responder if the window isn't
226 # already the first responder
Greg Clayton37191a22015-10-07 20:00:28 +0000227 if window != self.get_first_responder():
228 self.first_responders.append(window)
Kate Stoneb9c1b512016-09-06 20:57:50 +0000229
230 def pop_first_responder(self, window):
231 # Only pop the window from the first responder list if it is the first
232 # responder
Greg Clayton37191a22015-10-07 20:00:28 +0000233 if window == self.get_first_responder():
234 old_first_responder = self.first_responders.pop()
235 old_first_responder.relinquish_first_responder()
236 return True
237 else:
238 return False
Kate Stoneb9c1b512016-09-06 20:57:50 +0000239
Greg Clayton37191a22015-10-07 20:00:28 +0000240 def relinquish_first_responder(self):
241 '''Override if there is something that you need to do when you lose first responder status.'''
Kate Stoneb9c1b512016-09-06 20:57:50 +0000242 pass
243
244 # def resign_first_responder(self, remove_from_parent, new_first_responder):
Greg Clayton37191a22015-10-07 20:00:28 +0000245 # success = False
246 # if self.parent:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000247 # if self.is_first_responder():
Greg Clayton37191a22015-10-07 20:00:28 +0000248 # self.relinquish_first_responder()
249 # if len(self.parent.first_responder):
250 # self.parent.first_responder = None
251 # success = True
252 # if remove_from_parent:
253 # self.parent.remove_child(self)
254 # if new_first_responder:
255 # self.parent.set_first_responder(new_first_responder)
256 # else:
257 # self.parent.select_next_first_responder()
258 # return success
Greg Clayton1827fc22015-09-19 00:39:09 +0000259
Greg Clayton87349242015-09-22 00:35:20 +0000260 def is_first_responder(self):
261 if self.parent:
Greg Clayton37191a22015-10-07 20:00:28 +0000262 return self.parent.get_first_responder() == self
263 else:
264 return False
265
266 def is_in_first_responder_chain(self):
267 if self.parent:
268 return self in self.parent.first_responders
Greg Clayton87349242015-09-22 00:35:20 +0000269 else:
270 return False
271
272 def select_next_first_responder(self):
Greg Clayton37191a22015-10-07 20:00:28 +0000273 if len(self.first_responders) > 1:
274 self.pop_first_responder(self.first_responders[-1])
275 else:
276 num_children = len(self.children)
277 if num_children == 1:
278 return self.set_first_responder(self.children[0])
Kate Stoneb9c1b512016-09-06 20:57:50 +0000279 for (i, window) in enumerate(self.children):
Greg Clayton37191a22015-10-07 20:00:28 +0000280 if window.is_first_responder():
281 break
282 if i < num_children:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000283 for i in range(i + 1, num_children):
Greg Clayton37191a22015-10-07 20:00:28 +0000284 if self.set_first_responder(self.children[i]):
285 return True
286 for i in range(0, i):
287 if self.set_first_responder(self.children[i]):
288 return True
Kate Stoneb9c1b512016-09-06 20:57:50 +0000289
Greg Clayton1827fc22015-09-19 00:39:09 +0000290 def point_in_window(self, pt):
291 size = self.get_size()
292 return pt.x >= 0 and pt.x < size.w and pt.y >= 0 and pt.y < size.h
Kate Stoneb9c1b512016-09-06 20:57:50 +0000293
Greg Clayton72d51442015-10-13 23:16:29 +0000294 def addch(self, c):
295 try:
296 self.window.addch(c)
297 except:
298 pass
299
300 def addch_at_point(self, pt, c):
Greg Clayton37191a22015-10-07 20:00:28 +0000301 try:
302 self.window.addch(pt.y, pt.x, c)
303 except:
304 pass
Greg Clayton1827fc22015-09-19 00:39:09 +0000305
306 def addstr(self, pt, str):
307 try:
308 self.window.addstr(pt.y, pt.x, str)
309 except:
310 pass
311
Greg Clayton72d51442015-10-13 23:16:29 +0000312 def addnstr_at_point(self, pt, str, n):
Greg Clayton1827fc22015-09-19 00:39:09 +0000313 try:
314 self.window.addnstr(pt.y, pt.x, str, n)
315 except:
316 pass
Kate Stoneb9c1b512016-09-06 20:57:50 +0000317
Greg Clayton72d51442015-10-13 23:16:29 +0000318 def addnstr(self, str, n):
319 try:
320 self.window.addnstr(str, n)
321 except:
322 pass
Greg Clayton1827fc22015-09-19 00:39:09 +0000323
Greg Clayton87349242015-09-22 00:35:20 +0000324 def attron(self, attr):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000325 return self.window.attron(attr)
Greg Clayton87349242015-09-22 00:35:20 +0000326
327 def attroff(self, attr):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000328 return self.window.attroff(attr)
Greg Clayton87349242015-09-22 00:35:20 +0000329
330 def box(self, vertch=0, horch=0):
331 if vertch == 0:
332 vertch = curses.ACS_VLINE
Kate Stoneb9c1b512016-09-06 20:57:50 +0000333 if horch == 0:
Greg Clayton87349242015-09-22 00:35:20 +0000334 horch = curses.ACS_HLINE
335 self.window.box(vertch, horch)
Greg Clayton1827fc22015-09-19 00:39:09 +0000336
Kate Stoneb9c1b512016-09-06 20:57:50 +0000337 def get_contained_rect(
338 self,
339 top_inset=0,
340 bottom_inset=0,
341 left_inset=0,
342 right_inset=0,
343 height=-1,
344 width=-1):
Greg Clayton1827fc22015-09-19 00:39:09 +0000345 '''Get a rectangle based on the top "height" lines of this window'''
346 rect = self.get_frame()
347 x = rect.origin.x + left_inset
348 y = rect.origin.y + top_inset
349 if height == -1:
350 h = rect.size.h - (top_inset + bottom_inset)
351 else:
352 h = height
353 if width == -1:
354 w = rect.size.w - (left_inset + right_inset)
355 else:
356 w = width
Kate Stoneb9c1b512016-09-06 20:57:50 +0000357 return Rect(x=x, y=y, w=w, h=h)
Greg Clayton1827fc22015-09-19 00:39:09 +0000358
359 def erase(self):
360 self.window.erase()
Kate Stoneb9c1b512016-09-06 20:57:50 +0000361
Greg Clayton72d51442015-10-13 23:16:29 +0000362 def get_cursor(self):
363 (y, x) = self.window.getyx()
364 return Point(x=x, y=y)
Greg Clayton1827fc22015-09-19 00:39:09 +0000365
366 def get_frame(self):
367 position = self.get_position()
368 size = self.get_size()
369 return Rect(x=position.x, y=position.y, w=size.w, h=size.h)
370
Greg Claytonc12cc592015-10-16 00:34:18 +0000371 def get_frame_in_parent(self):
372 position = self.get_position_in_parent()
373 size = self.get_size()
374 return Rect(x=position.x, y=position.y, w=size.w, h=size.h)
Kate Stoneb9c1b512016-09-06 20:57:50 +0000375
Greg Claytonc12cc592015-10-16 00:34:18 +0000376 def get_position_in_parent(self):
377 (y, x) = self.window.getparyx()
378 return Point(x, y)
Kate Stoneb9c1b512016-09-06 20:57:50 +0000379
Greg Clayton1827fc22015-09-19 00:39:09 +0000380 def get_position(self):
381 (y, x) = self.window.getbegyx()
382 return Point(x, y)
383
384 def get_size(self):
385 (y, x) = self.window.getmaxyx()
386 return Size(w=x, h=y)
Greg Clayton37191a22015-10-07 20:00:28 +0000387
Greg Clayton72d51442015-10-13 23:16:29 +0000388 def move(self, pt):
389 self.window.move(pt.y, pt.x)
390
Greg Clayton1827fc22015-09-19 00:39:09 +0000391 def refresh(self):
Greg Clayton87349242015-09-22 00:35:20 +0000392 self.update()
Greg Clayton1827fc22015-09-19 00:39:09 +0000393 curses.panel.update_panels()
Greg Clayton72d51442015-10-13 23:16:29 +0000394 self.move(Point(x=0, y=0))
Greg Clayton1827fc22015-09-19 00:39:09 +0000395 return self.window.refresh()
Kate Stoneb9c1b512016-09-06 20:57:50 +0000396
Greg Clayton1827fc22015-09-19 00:39:09 +0000397 def resize(self, size):
Greg Clayton87349242015-09-22 00:35:20 +0000398 return self.window.resize(size.h, size.w)
Kate Stoneb9c1b512016-09-06 20:57:50 +0000399
Greg Clayton87349242015-09-22 00:35:20 +0000400 def timeout(self, timeout_msec):
401 return self.window.timeout(timeout_msec)
402
403 def handle_key(self, key, check_parent=True):
404 '''Handle a key press in this window.'''
Kate Stoneb9c1b512016-09-06 20:57:50 +0000405
Greg Clayton87349242015-09-22 00:35:20 +0000406 # First try the first responder if this window has one, but don't allow
407 # it to check with its parent (False second parameter) so we don't recurse
408 # and get a stack overflow
Greg Clayton37191a22015-10-07 20:00:28 +0000409 for first_responder in reversed(self.first_responders):
410 if first_responder.handle_key(key, False):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000411 return True
Greg Clayton414dba52015-09-24 00:19:42 +0000412
413 # Check our key map to see if we have any actions. Actions don't take
414 # any arguments, they must be callable
415 if key in self.key_actions:
416 key_action = self.key_actions[key]
417 key_action['callback']()
418 return True
419 # Check if there is a wildcard key for any key
420 if -1 in self.key_actions:
421 key_action = self.key_actions[-1]
422 key_action['callback']()
423 return True
Greg Clayton87349242015-09-22 00:35:20 +0000424 # Check if the window delegate wants to handle this key press
Kate Stoneb9c1b512016-09-06 20:57:50 +0000425 if self.delegate:
Zachary Turnercd236b82015-10-26 18:48:24 +0000426 if six.callable(getattr(self.delegate, "handle_key", None)):
Greg Clayton87349242015-09-22 00:35:20 +0000427 if self.delegate.handle_key(self, key):
428 return True
429 if self.delegate(self, key):
430 return True
Kate Stoneb9c1b512016-09-06 20:57:50 +0000431 # Check if we have a parent window and if so, let the parent
Greg Clayton87349242015-09-22 00:35:20 +0000432 # window handle the key press
433 if check_parent and self.parent:
434 return self.parent.handle_key(key, True)
435 else:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000436 return False # Key not handled
Greg Clayton87349242015-09-22 00:35:20 +0000437
438 def update(self):
439 for child in self.children:
440 child.update()
Kate Stoneb9c1b512016-09-06 20:57:50 +0000441
Greg Clayton5ea44832015-10-15 00:49:36 +0000442 def quit_action(self):
443 raise QuitException
Greg Clayton87349242015-09-22 00:35:20 +0000444
Greg Clayton3fd1f742015-10-16 23:34:40 +0000445 def get_key(self, timeout_msec=-1):
446 self.timeout(timeout_msec)
447 done = False
448 c = self.window.getch()
449 if c == 27:
450 self.timeout(0)
451 escape_key = 0
452 while True:
453 escape_key = self.window.getch()
454 if escape_key == -1:
455 break
456 else:
457 c = c << 8 | escape_key
458 self.timeout(timeout_msec)
Kate Stoneb9c1b512016-09-06 20:57:50 +0000459 return c
460
Zachary Turnerda3dea62015-10-26 16:51:20 +0000461 def key_event_loop(self, timeout_msec=-1, n=sys.maxsize):
Greg Clayton87349242015-09-22 00:35:20 +0000462 '''Run an event loop to receive key presses and pass them along to the
463 responder chain.
Kate Stoneb9c1b512016-09-06 20:57:50 +0000464
Greg Clayton87349242015-09-22 00:35:20 +0000465 timeout_msec is the timeout it milliseconds. If the value is -1, an
466 infinite wait will be used. It the value is zero, a non-blocking mode
467 will be used, and if greater than zero it will wait for a key press
468 for timeout_msec milliseconds.
Kate Stoneb9c1b512016-09-06 20:57:50 +0000469
Greg Clayton87349242015-09-22 00:35:20 +0000470 n is the number of times to go through the event loop before exiting'''
Greg Clayton5ea44832015-10-15 00:49:36 +0000471 done = False
472 while not done and n > 0:
Greg Clayton258c1642015-10-21 21:55:16 +0000473 c = self.get_key(timeout_msec)
Greg Clayton87349242015-09-22 00:35:20 +0000474 if c != -1:
Greg Clayton37191a22015-10-07 20:00:28 +0000475 try:
476 self.handle_key(c)
Greg Clayton5ea44832015-10-15 00:49:36 +0000477 except QuitException:
478 done = True
Greg Clayton87349242015-09-22 00:35:20 +0000479 n -= 1
480
Kate Stoneb9c1b512016-09-06 20:57:50 +0000481
Greg Clayton1827fc22015-09-19 00:39:09 +0000482class Panel(Window):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000483
484 def __init__(self, frame, delegate=None, can_become_first_responder=True):
485 window = curses.newwin(
486 frame.size.h,
487 frame.size.w,
488 frame.origin.y,
489 frame.origin.x)
490 super(
491 Panel,
492 self).__init__(
493 window,
494 delegate,
495 can_become_first_responder)
Greg Clayton1827fc22015-09-19 00:39:09 +0000496 self.panel = curses.panel.new_panel(window)
497
Greg Clayton87349242015-09-22 00:35:20 +0000498 def hide(self):
499 return self.panel.hide()
500
501 def hidden(self):
502 return self.panel.hidden()
503
504 def show(self):
505 return self.panel.show()
506
Greg Clayton1827fc22015-09-19 00:39:09 +0000507 def top(self):
Greg Clayton87349242015-09-22 00:35:20 +0000508 return self.panel.top()
Kate Stoneb9c1b512016-09-06 20:57:50 +0000509
Greg Clayton1827fc22015-09-19 00:39:09 +0000510 def set_position(self, pt):
511 self.panel.move(pt.y, pt.x)
Kate Stoneb9c1b512016-09-06 20:57:50 +0000512
Greg Claytonc12cc592015-10-16 00:34:18 +0000513 def slide_position(self, size):
Greg Clayton1827fc22015-09-19 00:39:09 +0000514 new_position = self.get_position()
Greg Claytonc12cc592015-10-16 00:34:18 +0000515 new_position.x = new_position.x + size.w
516 new_position.y = new_position.y + size.h
Greg Clayton1827fc22015-09-19 00:39:09 +0000517 self.set_position(new_position)
518
Kate Stoneb9c1b512016-09-06 20:57:50 +0000519
Greg Clayton1827fc22015-09-19 00:39:09 +0000520class BoxedPanel(Panel):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000521
522 def __init__(self, frame, title, delegate=None,
523 can_become_first_responder=True):
524 super(
525 BoxedPanel,
526 self).__init__(
527 frame,
528 delegate,
529 can_become_first_responder)
Greg Clayton1827fc22015-09-19 00:39:09 +0000530 self.title = title
531 self.lines = list()
532 self.first_visible_idx = 0
Greg Clayton87349242015-09-22 00:35:20 +0000533 self.selected_idx = -1
Kate Stoneb9c1b512016-09-06 20:57:50 +0000534 self.add_key_action(
535 curses.KEY_UP,
536 self.select_prev,
537 "Select the previous item")
538 self.add_key_action(
539 curses.KEY_DOWN,
540 self.select_next,
541 "Select the next item")
542 self.add_key_action(
543 curses.KEY_HOME,
544 self.scroll_begin,
545 "Go to the beginning of the list")
546 self.add_key_action(
547 curses.KEY_END,
548 self.scroll_end,
549 "Go to the end of the list")
550 self.add_key_action(
551 0x1b4f48,
552 self.scroll_begin,
553 "Go to the beginning of the list")
554 self.add_key_action(
555 0x1b4f46,
556 self.scroll_end,
557 "Go to the end of the list")
558 self.add_key_action(
559 curses.KEY_PPAGE,
560 self.scroll_page_backward,
561 "Scroll to previous page")
562 self.add_key_action(
563 curses.KEY_NPAGE,
564 self.scroll_page_forward,
565 "Scroll to next forward")
Greg Clayton1827fc22015-09-19 00:39:09 +0000566 self.update()
567
Greg Claytond13c4fb2015-09-22 17:18:15 +0000568 def clear(self, update=True):
569 self.lines = list()
570 self.first_visible_idx = 0
571 self.selected_idx = -1
572 if update:
573 self.update()
Kate Stoneb9c1b512016-09-06 20:57:50 +0000574
Greg Clayton1827fc22015-09-19 00:39:09 +0000575 def get_usable_width(self):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000576 '''Valid usable width is 0 to (width - 3) since the left and right lines display the box around
Greg Clayton1827fc22015-09-19 00:39:09 +0000577 this frame and we skip a leading space'''
578 w = self.get_size().w
579 if w > 3:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000580 return w - 3
Greg Clayton1827fc22015-09-19 00:39:09 +0000581 else:
582 return 0
Kate Stoneb9c1b512016-09-06 20:57:50 +0000583
Greg Clayton1827fc22015-09-19 00:39:09 +0000584 def get_usable_height(self):
585 '''Valid line indexes are 0 to (height - 2) since the top and bottom lines display the box around this frame.'''
586 h = self.get_size().h
587 if h > 2:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000588 return h - 2
Greg Clayton1827fc22015-09-19 00:39:09 +0000589 else:
590 return 0
591
592 def get_point_for_line(self, global_line_idx):
593 '''Returns the point to use when displaying a line whose index is "line_idx"'''
594 line_idx = global_line_idx - self.first_visible_idx
595 num_lines = self.get_usable_height()
596 if line_idx < num_lines:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000597 return Point(x=2, y=1 + line_idx)
Greg Clayton1827fc22015-09-19 00:39:09 +0000598 else:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000599 # return an invalid coordinate if the line index isn't valid
600 return Point(x=-1, y=-1)
601
602 def set_title(self, title, update=True):
Greg Clayton1827fc22015-09-19 00:39:09 +0000603 self.title = title
604 if update:
605 self.update()
606
Kate Stoneb9c1b512016-09-06 20:57:50 +0000607 def scroll_to_line(self, idx):
Greg Claytonc12cc592015-10-16 00:34:18 +0000608 if idx < len(self.lines):
609 self.selected_idx = idx
Kate Stoneb9c1b512016-09-06 20:57:50 +0000610 max_visible_lines = self.get_usable_height()
Greg Claytonc12cc592015-10-16 00:34:18 +0000611 if idx < self.first_visible_idx or idx >= self.first_visible_idx + max_visible_lines:
612 self.first_visible_idx = idx
613 self.refresh()
614
Kate Stoneb9c1b512016-09-06 20:57:50 +0000615 def scroll_begin(self):
Greg Clayton87349242015-09-22 00:35:20 +0000616 self.first_visible_idx = 0
617 if len(self.lines) > 0:
618 self.selected_idx = 0
619 else:
620 self.selected_idx = -1
621 self.update()
622
Kate Stoneb9c1b512016-09-06 20:57:50 +0000623 def scroll_end(self):
Greg Clayton87349242015-09-22 00:35:20 +0000624 max_visible_lines = self.get_usable_height()
625 num_lines = len(self.lines)
Greg Clayton414dba52015-09-24 00:19:42 +0000626 if num_lines > max_visible_lines:
Greg Clayton87349242015-09-22 00:35:20 +0000627 self.first_visible_idx = num_lines - max_visible_lines
628 else:
629 self.first_visible_idx = 0
Kate Stoneb9c1b512016-09-06 20:57:50 +0000630 self.selected_idx = num_lines - 1
Greg Clayton87349242015-09-22 00:35:20 +0000631 self.update()
Kate Stoneb9c1b512016-09-06 20:57:50 +0000632
Greg Clayton37191a22015-10-07 20:00:28 +0000633 def scroll_page_backward(self):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000634 num_lines = len(self.lines)
635 max_visible_lines = self.get_usable_height()
Greg Clayton37191a22015-10-07 20:00:28 +0000636 new_index = self.first_visible_idx - max_visible_lines
637 if new_index < 0:
638 self.first_visible_idx = 0
639 else:
640 self.first_visible_idx = new_index
641 self.refresh()
Kate Stoneb9c1b512016-09-06 20:57:50 +0000642
Greg Clayton37191a22015-10-07 20:00:28 +0000643 def scroll_page_forward(self):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000644 max_visible_lines = self.get_usable_height()
Greg Clayton37191a22015-10-07 20:00:28 +0000645 self.first_visible_idx += max_visible_lines
646 self._adjust_first_visible_line()
647 self.refresh()
648
Kate Stoneb9c1b512016-09-06 20:57:50 +0000649 def select_next(self):
Greg Clayton87349242015-09-22 00:35:20 +0000650 self.selected_idx += 1
651 if self.selected_idx >= len(self.lines):
652 self.selected_idx = len(self.lines) - 1
Greg Clayton37191a22015-10-07 20:00:28 +0000653 self.refresh()
Kate Stoneb9c1b512016-09-06 20:57:50 +0000654
655 def select_prev(self):
Greg Clayton87349242015-09-22 00:35:20 +0000656 self.selected_idx -= 1
657 if self.selected_idx < 0:
658 if len(self.lines) > 0:
659 self.selected_idx = 0
660 else:
661 self.selected_idx = -1
Greg Clayton37191a22015-10-07 20:00:28 +0000662 self.refresh()
Greg Clayton87349242015-09-22 00:35:20 +0000663
664 def get_selected_idx(self):
665 return self.selected_idx
Kate Stoneb9c1b512016-09-06 20:57:50 +0000666
Greg Clayton1827fc22015-09-19 00:39:09 +0000667 def _adjust_first_visible_line(self):
668 num_lines = len(self.lines)
669 max_visible_lines = self.get_usable_height()
Kate Stoneb9c1b512016-09-06 20:57:50 +0000670 if (self.first_visible_idx >= num_lines) or (
671 num_lines - self.first_visible_idx) > max_visible_lines:
Greg Clayton1827fc22015-09-19 00:39:09 +0000672 self.first_visible_idx = num_lines - max_visible_lines
Kate Stoneb9c1b512016-09-06 20:57:50 +0000673
Greg Clayton1827fc22015-09-19 00:39:09 +0000674 def append_line(self, s, update=True):
675 self.lines.append(s)
676 self._adjust_first_visible_line()
677 if update:
678 self.update()
679
680 def set_line(self, line_idx, s, update=True):
681 '''Sets a line "line_idx" within the boxed panel to be "s"'''
682 if line_idx < 0:
683 return
684 while line_idx >= len(self.lines):
685 self.lines.append('')
686 self.lines[line_idx] = s
687 self._adjust_first_visible_line()
688 if update:
689 self.update()
Kate Stoneb9c1b512016-09-06 20:57:50 +0000690
Greg Clayton1827fc22015-09-19 00:39:09 +0000691 def update(self):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000692 self.erase()
Greg Clayton72d51442015-10-13 23:16:29 +0000693 self.draw_title_box(self.title)
Greg Clayton1827fc22015-09-19 00:39:09 +0000694 max_width = self.get_usable_width()
695 for line_idx in range(self.first_visible_idx, len(self.lines)):
696 pt = self.get_point_for_line(line_idx)
697 if pt.is_valid_coordinate():
Greg Clayton87349242015-09-22 00:35:20 +0000698 is_selected = line_idx == self.selected_idx
699 if is_selected:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000700 self.attron(curses.A_REVERSE)
Greg Claytonc12cc592015-10-16 00:34:18 +0000701 self.move(pt)
702 self.addnstr(self.lines[line_idx], max_width)
Greg Clayton87349242015-09-22 00:35:20 +0000703 if is_selected:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000704 self.attroff(curses.A_REVERSE)
Greg Clayton1827fc22015-09-19 00:39:09 +0000705 else:
706 return
707
Greg Claytonc12cc592015-10-16 00:34:18 +0000708 def load_file(self, path):
709 f = open(path)
710 if f:
711 self.lines = f.read().splitlines()
712 for (idx, line) in enumerate(self.lines):
713 # Remove any tabs from lines since they hose up the display
714 if "\t" in line:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000715 self.lines[idx] = (8 * ' ').join(line.split('\t'))
Greg Claytonc12cc592015-10-16 00:34:18 +0000716 self.selected_idx = 0
717 self.first_visible_idx = 0
718 self.refresh()
719
Kate Stoneb9c1b512016-09-06 20:57:50 +0000720
Greg Clayton37191a22015-10-07 20:00:28 +0000721class Item(object):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000722
Greg Clayton37191a22015-10-07 20:00:28 +0000723 def __init__(self, title, action):
724 self.title = title
725 self.action = action
Greg Clayton72d51442015-10-13 23:16:29 +0000726
Kate Stoneb9c1b512016-09-06 20:57:50 +0000727
Greg Clayton5ea44832015-10-15 00:49:36 +0000728class TreeItemDelegate(object):
729
730 def might_have_children(self):
731 return False
732
733 def update_children(self, item):
734 '''Return a list of child Item objects'''
735 return None
736
737 def draw_item_string(self, tree_window, item, s):
738 pt = tree_window.get_cursor()
739 width = tree_window.get_size().w - 1
740 if width > pt.x:
741 tree_window.addnstr(s, width - pt.x)
742
743 def draw_item(self, tree_window, item):
744 self.draw_item_string(tree_window, item, item.title)
Kate Stoneb9c1b512016-09-06 20:57:50 +0000745
Greg Claytonc12cc592015-10-16 00:34:18 +0000746 def do_action(self):
747 pass
Greg Clayton5ea44832015-10-15 00:49:36 +0000748
Kate Stoneb9c1b512016-09-06 20:57:50 +0000749
Greg Clayton72d51442015-10-13 23:16:29 +0000750class TreeItem(object):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000751
752 def __init__(
753 self,
754 delegate,
755 parent=None,
756 title=None,
757 action=None,
758 is_expanded=False):
Greg Clayton72d51442015-10-13 23:16:29 +0000759 self.parent = parent
760 self.title = title
Kate Stoneb9c1b512016-09-06 20:57:50 +0000761 self.action = action
Greg Clayton72d51442015-10-13 23:16:29 +0000762 self.delegate = delegate
Kate Stoneb9c1b512016-09-06 20:57:50 +0000763 self.is_expanded = not parent or is_expanded
Greg Clayton5ea44832015-10-15 00:49:36 +0000764 self._might_have_children = None
Greg Clayton72d51442015-10-13 23:16:29 +0000765 self.children = None
Greg Clayton5ea44832015-10-15 00:49:36 +0000766 self._children_might_have_children = False
Kate Stoneb9c1b512016-09-06 20:57:50 +0000767
Greg Clayton72d51442015-10-13 23:16:29 +0000768 def get_children(self):
769 if self.is_expanded and self.might_have_children():
770 if self.children is None:
Greg Clayton5ea44832015-10-15 00:49:36 +0000771 self._children_might_have_children = False
Greg Clayton72d51442015-10-13 23:16:29 +0000772 self.children = self.update_children()
Greg Clayton5ea44832015-10-15 00:49:36 +0000773 for child in self.children:
774 if child.might_have_children():
775 self._children_might_have_children = True
776 break
Greg Clayton72d51442015-10-13 23:16:29 +0000777 else:
Greg Clayton5ea44832015-10-15 00:49:36 +0000778 self._children_might_have_children = False
Greg Clayton72d51442015-10-13 23:16:29 +0000779 self.children = None
780 return self.children
Kate Stoneb9c1b512016-09-06 20:57:50 +0000781
Greg Clayton72d51442015-10-13 23:16:29 +0000782 def append_visible_items(self, items):
783 items.append(self)
784 children = self.get_children()
785 if children:
786 for child in children:
787 child.append_visible_items(items)
788
789 def might_have_children(self):
Greg Clayton5ea44832015-10-15 00:49:36 +0000790 if self._might_have_children is None:
Greg Clayton72d51442015-10-13 23:16:29 +0000791 if not self.parent:
792 # Root item always might have children
Greg Clayton5ea44832015-10-15 00:49:36 +0000793 self._might_have_children = True
Greg Clayton72d51442015-10-13 23:16:29 +0000794 else:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000795 # Check with the delegate to see if the item might have
796 # children
Greg Clayton5ea44832015-10-15 00:49:36 +0000797 self._might_have_children = self.delegate.might_have_children()
798 return self._might_have_children
799
800 def children_might_have_children(self):
801 return self._children_might_have_children
802
Greg Clayton72d51442015-10-13 23:16:29 +0000803 def update_children(self):
804 if self.is_expanded and self.might_have_children():
805 self.children = self.delegate.update_children(self)
806 for child in self.children:
807 child.update_children()
Kate Stoneb9c1b512016-09-06 20:57:50 +0000808 else:
Greg Clayton72d51442015-10-13 23:16:29 +0000809 self.children = None
810 return self.children
Kate Stoneb9c1b512016-09-06 20:57:50 +0000811
Greg Clayton72d51442015-10-13 23:16:29 +0000812 def get_num_visible_rows(self):
813 rows = 1
814 if self.is_expanded:
815 children = self.get_children()
Greg Claytonc12cc592015-10-16 00:34:18 +0000816 if children:
817 for child in children:
818 rows += child.get_num_visible_rows()
Greg Clayton72d51442015-10-13 23:16:29 +0000819 return rows
Kate Stoneb9c1b512016-09-06 20:57:50 +0000820
Greg Clayton72d51442015-10-13 23:16:29 +0000821 def draw(self, tree_window, row):
822 display_row = tree_window.get_display_row(row)
823 if display_row >= 0:
824 tree_window.move(tree_window.get_item_draw_point(row))
825 if self.parent:
826 self.parent.draw_tree_for_child(tree_window, self, 0)
827 if self.might_have_children():
Kate Stoneb9c1b512016-09-06 20:57:50 +0000828 tree_window.addch(curses.ACS_DIAMOND)
829 tree_window.addch(curses.ACS_HLINE)
Greg Clayton5ea44832015-10-15 00:49:36 +0000830 elif self.parent and self.parent.children_might_have_children():
Greg Claytonc12cc592015-10-16 00:34:18 +0000831 if self.parent.parent:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000832 tree_window.addch(curses.ACS_HLINE)
833 tree_window.addch(curses.ACS_HLINE)
Greg Claytonc12cc592015-10-16 00:34:18 +0000834 else:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000835 tree_window.addch(' ')
836 tree_window.addch(' ')
Greg Clayton72d51442015-10-13 23:16:29 +0000837 is_selected = tree_window.is_selected(row)
838 if is_selected:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000839 tree_window.attron(curses.A_REVERSE)
Greg Clayton72d51442015-10-13 23:16:29 +0000840 self.delegate.draw_item(tree_window, self)
841 if is_selected:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000842 tree_window.attroff(curses.A_REVERSE)
843
844 def draw_tree_for_child(self, tree_window, child, reverse_depth):
Greg Clayton72d51442015-10-13 23:16:29 +0000845 if self.parent:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000846 self.parent.draw_tree_for_child(
847 tree_window, self, reverse_depth + 1)
Greg Clayton72d51442015-10-13 23:16:29 +0000848 if self.children[-1] == child:
849 # Last child
850 if reverse_depth == 0:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000851 tree_window.addch(curses.ACS_LLCORNER)
852 tree_window.addch(curses.ACS_HLINE)
Greg Clayton72d51442015-10-13 23:16:29 +0000853 else:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000854 tree_window.addch(' ')
855 tree_window.addch(' ')
Greg Clayton72d51442015-10-13 23:16:29 +0000856 else:
857 # Middle child
858 if reverse_depth == 0:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000859 tree_window.addch(curses.ACS_LTEE)
860 tree_window.addch(curses.ACS_HLINE)
Greg Clayton72d51442015-10-13 23:16:29 +0000861 else:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000862 tree_window.addch(curses.ACS_VLINE)
863 tree_window.addch(' ')
864
865 def was_selected(self):
Greg Claytonc12cc592015-10-16 00:34:18 +0000866 self.delegate.do_action()
Kate Stoneb9c1b512016-09-06 20:57:50 +0000867
868
Greg Clayton72d51442015-10-13 23:16:29 +0000869class TreePanel(Panel):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000870
Greg Clayton72d51442015-10-13 23:16:29 +0000871 def __init__(self, frame, title, root_item):
872 self.root_item = root_item
873 self.title = title
874 self.first_visible_idx = 0
875 self.selected_idx = 0
876 self.items = None
Kate Stoneb9c1b512016-09-06 20:57:50 +0000877 super(TreePanel, self).__init__(frame)
878 self.add_key_action(
879 curses.KEY_UP,
880 self.select_prev,
881 "Select the previous item")
882 self.add_key_action(
883 curses.KEY_DOWN,
884 self.select_next,
885 "Select the next item")
886 self.add_key_action(
887 curses.KEY_RIGHT,
888 self.right_arrow,
889 "Expand an item")
890 self.add_key_action(
891 curses.KEY_LEFT,
892 self.left_arrow,
893 "Unexpand an item or navigate to parent")
894 self.add_key_action(
895 curses.KEY_HOME,
896 self.scroll_begin,
897 "Go to the beginning of the tree")
898 self.add_key_action(
899 curses.KEY_END,
900 self.scroll_end,
901 "Go to the end of the tree")
902 self.add_key_action(
903 0x1b4f48,
904 self.scroll_begin,
905 "Go to the beginning of the tree")
906 self.add_key_action(
907 0x1b4f46,
908 self.scroll_end,
909 "Go to the end of the tree")
910 self.add_key_action(
911 curses.KEY_PPAGE,
912 self.scroll_page_backward,
913 "Scroll to previous page")
914 self.add_key_action(
915 curses.KEY_NPAGE,
916 self.scroll_page_forward,
917 "Scroll to next forward")
Greg Clayton72d51442015-10-13 23:16:29 +0000918
919 def get_selected_item(self):
920 if self.selected_idx < len(self.items):
921 return self.items[self.selected_idx]
922 else:
923 return None
Kate Stoneb9c1b512016-09-06 20:57:50 +0000924
Greg Clayton72d51442015-10-13 23:16:29 +0000925 def select_item(self, item):
926 if self.items and item in self.items:
927 self.selected_idx = self.items.index(item)
928 return True
929 else:
930 return False
Kate Stoneb9c1b512016-09-06 20:57:50 +0000931
Greg Clayton72d51442015-10-13 23:16:29 +0000932 def get_visible_items(self):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000933 # Clear self.items when you want to update all chidren
Greg Clayton72d51442015-10-13 23:16:29 +0000934 if self.items is None:
935 self.items = list()
936 children = self.root_item.get_children()
937 if children:
938 for child in children:
939 child.append_visible_items(self.items)
940 return self.items
Kate Stoneb9c1b512016-09-06 20:57:50 +0000941
Greg Clayton72d51442015-10-13 23:16:29 +0000942 def update(self):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000943 self.erase()
944 self.draw_title_box(self.title)
Greg Clayton72d51442015-10-13 23:16:29 +0000945 visible_items = self.get_visible_items()
946 for (row, child) in enumerate(visible_items):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000947 child.draw(self, row)
Greg Clayton72d51442015-10-13 23:16:29 +0000948
Kate Stoneb9c1b512016-09-06 20:57:50 +0000949 def get_item_draw_point(self, row):
Greg Clayton72d51442015-10-13 23:16:29 +0000950 display_row = self.get_display_row(row)
951 if display_row >= 0:
952 return Point(2, display_row + 1)
953 else:
954 return Point(-1, -1)
955
956 def get_display_row(self, row):
957 if row >= self.first_visible_idx:
958 display_row = row - self.first_visible_idx
Kate Stoneb9c1b512016-09-06 20:57:50 +0000959 if display_row < self.get_size().h - 2:
960 return display_row
Greg Clayton72d51442015-10-13 23:16:29 +0000961 return -1
962
963 def is_selected(self, row):
964 return row == self.selected_idx
965
Kate Stoneb9c1b512016-09-06 20:57:50 +0000966 def get_num_lines(self):
Greg Claytonc12cc592015-10-16 00:34:18 +0000967 self.get_visible_items()
968 return len(self.items)
Kate Stoneb9c1b512016-09-06 20:57:50 +0000969
Greg Clayton72d51442015-10-13 23:16:29 +0000970 def get_num_visible_lines(self):
Kate Stoneb9c1b512016-09-06 20:57:50 +0000971 return self.get_size().h - 2
972
973 def select_next(self):
974 self.selected_idx += 1
Greg Clayton72d51442015-10-13 23:16:29 +0000975 num_lines = self.get_num_lines()
976 if self.selected_idx >= num_lines:
Kate Stoneb9c1b512016-09-06 20:57:50 +0000977 self.selected_idx = num_lines - 1
Greg Claytonc12cc592015-10-16 00:34:18 +0000978 self._selection_changed()
Greg Clayton72d51442015-10-13 23:16:29 +0000979 self.refresh()
980
Kate Stoneb9c1b512016-09-06 20:57:50 +0000981 def select_prev(self):
Greg Clayton72d51442015-10-13 23:16:29 +0000982 self.selected_idx -= 1
983 if self.selected_idx < 0:
984 num_lines = self.get_num_lines()
985 if num_lines > 0:
986 self.selected_idx = 0
987 else:
988 self.selected_idx = -1
Greg Claytonc12cc592015-10-16 00:34:18 +0000989 self._selection_changed()
Greg Clayton72d51442015-10-13 23:16:29 +0000990 self.refresh()
991
Kate Stoneb9c1b512016-09-06 20:57:50 +0000992 def scroll_begin(self):
Greg Clayton72d51442015-10-13 23:16:29 +0000993 self.first_visible_idx = 0
994 num_lines = self.get_num_lines()
995 if num_lines > 0:
996 self.selected_idx = 0
997 else:
998 self.selected_idx = -1
Greg Claytonc12cc592015-10-16 00:34:18 +0000999 self.refresh()
Kate Stoneb9c1b512016-09-06 20:57:50 +00001000
Greg Clayton72d51442015-10-13 23:16:29 +00001001 def redisplay_tree(self):
1002 self.items = None
1003 self.refresh()
1004
Kate Stoneb9c1b512016-09-06 20:57:50 +00001005 def right_arrow(self):
Greg Clayton72d51442015-10-13 23:16:29 +00001006 selected_item = self.get_selected_item()
1007 if selected_item and selected_item.is_expanded == False:
1008 selected_item.is_expanded = True
1009 self.redisplay_tree()
Kate Stoneb9c1b512016-09-06 20:57:50 +00001010
Greg Clayton72d51442015-10-13 23:16:29 +00001011 def left_arrow(self):
1012 selected_item = self.get_selected_item()
1013 if selected_item:
Kate Stoneb9c1b512016-09-06 20:57:50 +00001014 if selected_item.is_expanded:
Greg Clayton72d51442015-10-13 23:16:29 +00001015 selected_item.is_expanded = False
1016 self.redisplay_tree()
1017 elif selected_item.parent:
1018 if self.select_item(selected_item.parent):
1019 self.refresh()
Greg Clayton72d51442015-10-13 23:16:29 +00001020
Kate Stoneb9c1b512016-09-06 20:57:50 +00001021 def scroll_end(self):
Greg Clayton72d51442015-10-13 23:16:29 +00001022 num_visible_lines = self.get_num_visible_lines()
Greg Claytonc12cc592015-10-16 00:34:18 +00001023 num_lines = self.get_num_lines()
Greg Clayton72d51442015-10-13 23:16:29 +00001024 if num_lines > num_visible_lines:
1025 self.first_visible_idx = num_lines - num_visible_lines
1026 else:
1027 self.first_visible_idx = 0
Kate Stoneb9c1b512016-09-06 20:57:50 +00001028 self.selected_idx = num_lines - 1
Greg Claytonc12cc592015-10-16 00:34:18 +00001029 self.refresh()
Greg Clayton72d51442015-10-13 23:16:29 +00001030
1031 def scroll_page_backward(self):
1032 num_visible_lines = self.get_num_visible_lines()
Greg Claytonc12cc592015-10-16 00:34:18 +00001033 new_index = self.selected_idx - num_visible_lines
Greg Clayton72d51442015-10-13 23:16:29 +00001034 if new_index < 0:
Greg Claytonc12cc592015-10-16 00:34:18 +00001035 self.selected_idx = 0
Greg Clayton72d51442015-10-13 23:16:29 +00001036 else:
Kate Stoneb9c1b512016-09-06 20:57:50 +00001037 self.selected_idx = new_index
Greg Claytonc12cc592015-10-16 00:34:18 +00001038 self._selection_changed()
Greg Clayton72d51442015-10-13 23:16:29 +00001039 self.refresh()
1040
1041 def scroll_page_forward(self):
Greg Claytonc12cc592015-10-16 00:34:18 +00001042 num_lines = self.get_num_lines()
Greg Clayton72d51442015-10-13 23:16:29 +00001043 num_visible_lines = self.get_num_visible_lines()
Greg Claytonc12cc592015-10-16 00:34:18 +00001044 new_index = self.selected_idx + num_visible_lines
1045 if new_index >= num_lines:
1046 new_index = num_lines - 1
1047 self.selected_idx = new_index
1048 self._selection_changed()
Greg Clayton72d51442015-10-13 23:16:29 +00001049 self.refresh()
1050
Kate Stoneb9c1b512016-09-06 20:57:50 +00001051 def _selection_changed(self):
1052 num_lines = self.get_num_lines()
Greg Clayton72d51442015-10-13 23:16:29 +00001053 num_visible_lines = self.get_num_visible_lines()
Greg Claytonc12cc592015-10-16 00:34:18 +00001054 last_visible_index = self.first_visible_idx + num_visible_lines
1055 if self.selected_idx >= last_visible_index:
Kate Stoneb9c1b512016-09-06 20:57:50 +00001056 self.first_visible_idx += (self.selected_idx -
1057 last_visible_index + 1)
Greg Claytonc12cc592015-10-16 00:34:18 +00001058 if self.selected_idx < self.first_visible_idx:
Kate Stoneb9c1b512016-09-06 20:57:50 +00001059 self.first_visible_idx = self.selected_idx
Greg Claytonc12cc592015-10-16 00:34:18 +00001060 if self.selected_idx >= 0 and self.selected_idx < len(self.items):
1061 item = self.items[self.selected_idx]
1062 item.was_selected()
Greg Clayton72d51442015-10-13 23:16:29 +00001063
1064
Greg Clayton37191a22015-10-07 20:00:28 +00001065class Menu(BoxedPanel):
Kate Stoneb9c1b512016-09-06 20:57:50 +00001066
Greg Clayton37191a22015-10-07 20:00:28 +00001067 def __init__(self, title, items):
1068 max_title_width = 0
1069 for item in items:
1070 if max_title_width < len(item.title):
1071 max_title_width = len(item.title)
Kate Stoneb9c1b512016-09-06 20:57:50 +00001072 frame = Rect(x=0, y=0, w=max_title_width + 4, h=len(items) + 2)
1073 super(
1074 Menu,
1075 self).__init__(
1076 frame,
1077 title=None,
1078 delegate=None,
1079 can_become_first_responder=True)
Greg Clayton37191a22015-10-07 20:00:28 +00001080 self.selected_idx = 0
1081 self.title = title
1082 self.items = items
1083 for (item_idx, item) in enumerate(items):
1084 self.set_line(item_idx, item.title)
1085 self.hide()
Kate Stoneb9c1b512016-09-06 20:57:50 +00001086
Greg Clayton37191a22015-10-07 20:00:28 +00001087 def update(self):
1088 super(Menu, self).update()
1089
1090 def relinquish_first_responder(self):
1091 if not self.hidden():
Kate Stoneb9c1b512016-09-06 20:57:50 +00001092 self.hide()
1093
1094 def perform_action(self):
Greg Clayton37191a22015-10-07 20:00:28 +00001095 selected_idx = self.get_selected_idx()
1096 if selected_idx < len(self.items):
1097 action = self.items[selected_idx].action
1098 if action:
1099 action()
Kate Stoneb9c1b512016-09-06 20:57:50 +00001100
1101
Greg Clayton37191a22015-10-07 20:00:28 +00001102class MenuBar(Panel):
Kate Stoneb9c1b512016-09-06 20:57:50 +00001103
Greg Clayton37191a22015-10-07 20:00:28 +00001104 def __init__(self, frame):
1105 super(MenuBar, self).__init__(frame, can_become_first_responder=True)
1106 self.menus = list()
1107 self.selected_menu_idx = -1
Kate Stoneb9c1b512016-09-06 20:57:50 +00001108 self.add_key_action(
1109 curses.KEY_LEFT,
1110 self.select_prev,
1111 "Select the previous menu")
1112 self.add_key_action(
1113 curses.KEY_RIGHT,
1114 self.select_next,
1115 "Select the next menu")
1116 self.add_key_action(
1117 curses.KEY_DOWN,
1118 lambda: self.select(0),
1119 "Select the first menu")
1120 self.add_key_action(
1121 27,
1122 self.relinquish_first_responder,
1123 "Hide current menu")
1124 self.add_key_action(
1125 curses.KEY_ENTER,
1126 self.perform_action,
1127 "Select the next menu item")
1128 self.add_key_action(
1129 10,
1130 self.perform_action,
1131 "Select the next menu item")
Greg Clayton37191a22015-10-07 20:00:28 +00001132
Zachary Turnerda3dea62015-10-26 16:51:20 +00001133 def insert_menu(self, menu, index=sys.maxsize):
Greg Clayton37191a22015-10-07 20:00:28 +00001134 if index >= len(self.menus):
1135 self.menus.append(menu)
1136 else:
1137 self.menus.insert(index, menu)
1138 pt = self.get_position()
1139 for menu in self.menus:
1140 menu.set_position(pt)
1141 pt.x += len(menu.title) + 5
Kate Stoneb9c1b512016-09-06 20:57:50 +00001142
1143 def perform_action(self):
Greg Clayton37191a22015-10-07 20:00:28 +00001144 '''If no menu is visible, show the first menu. If a menu is visible, perform the action
1145 associated with the selected menu item in the menu'''
1146 menu_visible = False
1147 for menu in self.menus:
1148 if not menu.hidden():
1149 menu_visible = True
1150 break
1151 if menu_visible:
1152 menu.perform_action()
1153 self.selected_menu_idx = -1
1154 self._selected_menu_changed()
1155 else:
1156 self.select(0)
Kate Stoneb9c1b512016-09-06 20:57:50 +00001157
Greg Clayton37191a22015-10-07 20:00:28 +00001158 def relinquish_first_responder(self):
1159 if self.selected_menu_idx >= 0:
1160 self.selected_menu_idx = -1
1161 self._selected_menu_changed()
Kate Stoneb9c1b512016-09-06 20:57:50 +00001162
Greg Clayton37191a22015-10-07 20:00:28 +00001163 def _selected_menu_changed(self):
1164 for (menu_idx, menu) in enumerate(self.menus):
1165 is_hidden = menu.hidden()
Kate Stoneb9c1b512016-09-06 20:57:50 +00001166 if menu_idx != self.selected_menu_idx:
Greg Clayton37191a22015-10-07 20:00:28 +00001167 if not is_hidden:
1168 if self.parent.pop_first_responder(menu) == False:
1169 menu.hide()
1170 for (menu_idx, menu) in enumerate(self.menus):
1171 is_hidden = menu.hidden()
Kate Stoneb9c1b512016-09-06 20:57:50 +00001172 if menu_idx == self.selected_menu_idx:
Greg Clayton37191a22015-10-07 20:00:28 +00001173 if is_hidden:
1174 menu.show()
1175 self.parent.push_first_responder(menu)
1176 menu.top()
1177 self.parent.refresh()
1178
1179 def select(self, index):
1180 if index < len(self.menus):
1181 self.selected_menu_idx = index
1182 self._selected_menu_changed()
Kate Stoneb9c1b512016-09-06 20:57:50 +00001183
1184 def select_next(self):
Greg Clayton37191a22015-10-07 20:00:28 +00001185 num_menus = len(self.menus)
1186 if self.selected_menu_idx == -1:
1187 if num_menus > 0:
1188 self.selected_menu_idx = 0
1189 self._selected_menu_changed()
1190 else:
1191 if self.selected_menu_idx + 1 < num_menus:
1192 self.selected_menu_idx += 1
1193 else:
1194 self.selected_menu_idx = -1
1195 self._selected_menu_changed()
1196
Kate Stoneb9c1b512016-09-06 20:57:50 +00001197 def select_prev(self):
Greg Clayton37191a22015-10-07 20:00:28 +00001198 num_menus = len(self.menus)
1199 if self.selected_menu_idx == -1:
1200 if num_menus > 0:
1201 self.selected_menu_idx = num_menus - 1
1202 self._selected_menu_changed()
1203 else:
1204 if self.selected_menu_idx - 1 >= 0:
1205 self.selected_menu_idx -= 1
1206 else:
1207 self.selected_menu_idx = -1
1208 self._selected_menu_changed()
1209
1210 def update(self):
1211 self.erase()
1212 is_in_first_responder_chain = self.is_in_first_responder_chain()
1213 if is_in_first_responder_chain:
Kate Stoneb9c1b512016-09-06 20:57:50 +00001214 self.attron(curses.A_REVERSE)
Greg Clayton37191a22015-10-07 20:00:28 +00001215 pt = Point(x=0, y=0)
Kate Stoneb9c1b512016-09-06 20:57:50 +00001216 for menu in self.menus:
Greg Clayton37191a22015-10-07 20:00:28 +00001217 self.addstr(pt, '| ' + menu.title + ' ')
1218 pt.x += len(menu.title) + 5
Kate Stoneb9c1b512016-09-06 20:57:50 +00001219 self.addstr(pt, '|')
Greg Clayton37191a22015-10-07 20:00:28 +00001220 width = self.get_size().w
1221 while pt.x < width:
Greg Clayton72d51442015-10-13 23:16:29 +00001222 self.addch_at_point(pt, ' ')
Kate Stoneb9c1b512016-09-06 20:57:50 +00001223 pt.x += 1
Greg Clayton37191a22015-10-07 20:00:28 +00001224 if is_in_first_responder_chain:
Kate Stoneb9c1b512016-09-06 20:57:50 +00001225 self.attroff(curses.A_REVERSE)
Greg Clayton37191a22015-10-07 20:00:28 +00001226
1227 for menu in self.menus:
1228 menu.update()
Kate Stoneb9c1b512016-09-06 20:57:50 +00001229
1230
Greg Clayton1827fc22015-09-19 00:39:09 +00001231class StatusPanel(Panel):
Kate Stoneb9c1b512016-09-06 20:57:50 +00001232
Greg Clayton1827fc22015-09-19 00:39:09 +00001233 def __init__(self, frame):
Kate Stoneb9c1b512016-09-06 20:57:50 +00001234 super(
1235 StatusPanel,
1236 self).__init__(
1237 frame,
1238 delegate=None,
1239 can_become_first_responder=False)
Greg Clayton1827fc22015-09-19 00:39:09 +00001240 self.status_items = list()
1241 self.status_dicts = dict()
1242 self.next_status_x = 1
Kate Stoneb9c1b512016-09-06 20:57:50 +00001243
Greg Clayton1827fc22015-09-19 00:39:09 +00001244 def add_status_item(self, name, title, format, width, value, update=True):
Kate Stoneb9c1b512016-09-06 20:57:50 +00001245 status_item_dict = {'name': name,
1246 'title': title,
1247 'width': width,
1248 'format': format,
1249 'value': value,
1250 'x': self.next_status_x}
Greg Clayton1827fc22015-09-19 00:39:09 +00001251 index = len(self.status_items)
1252 self.status_items.append(status_item_dict)
1253 self.status_dicts[name] = index
Kate Stoneb9c1b512016-09-06 20:57:50 +00001254 self.next_status_x += width + 2
Greg Clayton1827fc22015-09-19 00:39:09 +00001255 if update:
1256 self.update()
1257
1258 def increment_status(self, name, update=True):
1259 if name in self.status_dicts:
1260 status_item_idx = self.status_dicts[name]
1261 status_item_dict = self.status_items[status_item_idx]
1262 status_item_dict['value'] = status_item_dict['value'] + 1
1263 if update:
1264 self.update()
Kate Stoneb9c1b512016-09-06 20:57:50 +00001265
Greg Clayton1827fc22015-09-19 00:39:09 +00001266 def update_status(self, name, value, update=True):
1267 if name in self.status_dicts:
1268 status_item_idx = self.status_dicts[name]
1269 status_item_dict = self.status_items[status_item_idx]
1270 status_item_dict['value'] = status_item_dict['format'] % (value)
1271 if update:
1272 self.update()
Kate Stoneb9c1b512016-09-06 20:57:50 +00001273
Greg Clayton1827fc22015-09-19 00:39:09 +00001274 def update(self):
Kate Stoneb9c1b512016-09-06 20:57:50 +00001275 self.erase()
Greg Clayton1827fc22015-09-19 00:39:09 +00001276 for status_item_dict in self.status_items:
Kate Stoneb9c1b512016-09-06 20:57:50 +00001277 self.addnstr_at_point(
1278 Point(
1279 x=status_item_dict['x'],
1280 y=0),
1281 '%s: %s' %
1282 (status_item_dict['title'],
1283 status_item_dict['value']),
1284 status_item_dict['width'])
Greg Clayton1827fc22015-09-19 00:39:09 +00001285
1286stdscr = None
1287
Kate Stoneb9c1b512016-09-06 20:57:50 +00001288
Greg Clayton1827fc22015-09-19 00:39:09 +00001289def intialize_curses():
1290 global stdscr
1291 stdscr = curses.initscr()
1292 curses.noecho()
1293 curses.cbreak()
1294 stdscr.keypad(1)
1295 try:
1296 curses.start_color()
1297 except:
1298 pass
1299 return Window(stdscr)
1300
Kate Stoneb9c1b512016-09-06 20:57:50 +00001301
Greg Clayton1827fc22015-09-19 00:39:09 +00001302def terminate_curses():
1303 global stdscr
1304 if stdscr:
1305 stdscr.keypad(0)
1306 curses.echo()
1307 curses.nocbreak()
1308 curses.endwin()