Fixed up some first responder issues and added menubar support that isn't in use yet.

llvm-svn: 249600
This commit is contained in:
Greg Clayton 2015-10-07 20:00:28 +00:00
parent d1ac50ecaa
commit 37191a2c2d
2 changed files with 262 additions and 46 deletions

View File

@ -29,7 +29,6 @@ class Curses(test_results.ResultsFormatter):
self.jobs = [None] * 64
self.job_tests = [None] * 64
self.results = list()
self.saved_first_responder = None
try:
self.main_window = lldbcurses.intialize_curses()
self.main_window.add_key_action('\t', self.main_window.select_next_first_responder, "Switch between views that can respond to keyboard input")
@ -82,8 +81,7 @@ class Curses(test_results.ResultsFormatter):
else:
self.info_panel.show()
self.saved_first_responder = self.main_window.first_responder
self.main_window.set_first_responder(self.info_panel)
self.main_window.push_first_responder(self.info_panel)
test_start = self.results[selected_idx][0]
test_result = self.results[selected_idx][1]
self.info_panel.set_line(0, "File: %s" % (test_start['test_filename']))
@ -92,9 +90,8 @@ class Curses(test_results.ResultsFormatter):
self.info_panel.set_line(3, "Status: %s" % (test_result['status']))
def hide_info_panel(self):
self.info_panel.resign_first_responder(remove_from_parent=True, new_first_responder=self.saved_first_responder)
self.main_window.pop_first_responder(self.info_panel)
self.info_panel.hide()
self.saved_first_responder = None
self.main_window.refresh()
def toggle_status(self, status):

View File

@ -63,7 +63,7 @@ class Window(object):
self.parent = None
self.delegate = delegate
self.children = list()
self.first_responder = None
self.first_responders = list()
self.can_become_first_responder = can_become_first_responder
self.key_actions = dict()
@ -92,56 +92,106 @@ class Window(object):
def remove_child(self, window):
self.children.remove(window)
def get_first_responder(self):
if len(self.first_responders):
return self.first_responders[-1]
else:
return None
def set_first_responder(self, window):
if window.can_become_first_responder:
if callable(getattr(window, "hidden", None)) and window.hidden():
return False
if not window in self.children:
self.add_child(window)
self.first_responder = window
# See if we have a current first responder, and if we do, let it know that
# it will be resigning as first responder
first_responder = self.get_first_responder()
if first_responder:
first_responder.relinquish_first_responder()
# Now set the first responder to "window"
if len(self.first_responders) == 0:
self.first_responders.append(window)
else:
self.first_responders[-1] = window
return True
else:
return False
def resign_first_responder(self, remove_from_parent, new_first_responder):
success = False
if self.parent:
if self.is_first_responder():
self.parent.first_responder = None
success = True
if remove_from_parent:
self.parent.remove_child(self)
if new_first_responder:
self.parent.set_first_responder(new_first_responder)
else:
self.parent.select_next_first_responder()
return success
def push_first_responder(self, window):
# Only push the window as the new first responder if the window isn't already the first responder
if window != self.get_first_responder():
self.first_responders.append(window)
def pop_first_responder(self, window):
# Only pop the window from the first responder list if it is the first responder
if window == self.get_first_responder():
old_first_responder = self.first_responders.pop()
old_first_responder.relinquish_first_responder()
return True
else:
return False
def relinquish_first_responder(self):
'''Override if there is something that you need to do when you lose first responder status.'''
pass
# def resign_first_responder(self, remove_from_parent, new_first_responder):
# success = False
# if self.parent:
# if self.is_first_responder():
# self.relinquish_first_responder()
# if len(self.parent.first_responder):
# self.parent.first_responder = None
# success = True
# if remove_from_parent:
# self.parent.remove_child(self)
# if new_first_responder:
# self.parent.set_first_responder(new_first_responder)
# else:
# self.parent.select_next_first_responder()
# return success
def is_first_responder(self):
if self.parent:
return self.parent.first_responder == self
return self.parent.get_first_responder() == self
else:
return False
def is_in_first_responder_chain(self):
if self.parent:
return self in self.parent.first_responders
else:
return False
def select_next_first_responder(self):
num_children = len(self.children)
if num_children == 1:
return self.set_first_responder(self.children[0])
for (i,window) in enumerate(self.children):
if window.is_first_responder():
break
if i < num_children:
for i in range(i+1,num_children):
if self.set_first_responder(self.children[i]):
return True
for i in range(0, i):
if self.set_first_responder(self.children[i]):
return True
if len(self.first_responders) > 1:
self.pop_first_responder(self.first_responders[-1])
else:
num_children = len(self.children)
if num_children == 1:
return self.set_first_responder(self.children[0])
for (i,window) in enumerate(self.children):
if window.is_first_responder():
break
if i < num_children:
for i in range(i+1,num_children):
if self.set_first_responder(self.children[i]):
return True
for i in range(0, i):
if self.set_first_responder(self.children[i]):
return True
def point_in_window(self, pt):
size = self.get_size()
return pt.x >= 0 and pt.x < size.w and pt.y >= 0 and pt.y < size.h
def addch(self, pt, c):
try:
self.window.addch(pt.y, pt.x, c)
except:
pass
def addstr(self, pt, str):
try:
@ -198,7 +248,7 @@ class Window(object):
def get_size(self):
(y, x) = self.window.getmaxyx()
return Size(w=x, h=y)
def refresh(self):
self.update()
curses.panel.update_panels()
@ -216,8 +266,8 @@ class Window(object):
# First try the first responder if this window has one, but don't allow
# it to check with its parent (False second parameter) so we don't recurse
# and get a stack overflow
if self.first_responder:
if self.first_responder.handle_key(key, False):
for first_responder in reversed(self.first_responders):
if first_responder.handle_key(key, False):
return True
# Check our key map to see if we have any actions. Actions don't take
@ -263,7 +313,10 @@ class Window(object):
while n > 0:
c = self.window.getch()
if c != -1:
self.handle_key(c)
try:
self.handle_key(c)
except:
break
n -= 1
class Panel(Window):
@ -300,6 +353,12 @@ class BoxedPanel(Panel):
self.lines = list()
self.first_visible_idx = 0
self.selected_idx = -1
self.add_key_action(curses.KEY_UP, self.select_prev, "Select the previous item")
self.add_key_action(curses.KEY_DOWN, self.select_next, "Select the next item")
self.add_key_action(curses.KEY_HOME, self.scroll_begin, "Go to the beginning of the list")
self.add_key_action(curses.KEY_END, self.scroll_end, "Go to the end of the list")
self.add_key_action(curses.KEY_PPAGE, self.scroll_page_backward, "Scroll to previous page")
self.add_key_action(curses.KEY_NPAGE, self.scroll_page_forward, "Scroll to next forward")
self.update()
def clear(self, update=True):
@ -357,12 +416,28 @@ class BoxedPanel(Panel):
self.first_visible_idx = 0
self.selected_idx = num_lines-1
self.update()
def scroll_page_backward(self):
num_lines = len(self.lines)
max_visible_lines = self.get_usable_height()
new_index = self.first_visible_idx - max_visible_lines
if new_index < 0:
self.first_visible_idx = 0
else:
self.first_visible_idx = new_index
self.refresh()
def scroll_page_forward(self):
max_visible_lines = self.get_usable_height()
self.first_visible_idx += max_visible_lines
self._adjust_first_visible_line()
self.refresh()
def select_next (self):
self.selected_idx += 1
if self.selected_idx >= len(self.lines):
self.selected_idx = len(self.lines) - 1
self.update()
self.refresh()
def select_prev (self):
self.selected_idx -= 1
@ -371,7 +446,7 @@ class BoxedPanel(Panel):
self.selected_idx = 0
else:
self.selected_idx = -1
self.update()
self.refresh()
def get_selected_idx(self):
return self.selected_idx
@ -379,7 +454,7 @@ class BoxedPanel(Panel):
def _adjust_first_visible_line(self):
num_lines = len(self.lines)
max_visible_lines = self.get_usable_height()
if (num_lines - self.first_visible_idx) > max_visible_lines:
if (self.first_visible_idx >= num_lines) or (num_lines - self.first_visible_idx) > max_visible_lines:
self.first_visible_idx = num_lines - max_visible_lines
def append_line(self, s, update=True):
@ -401,11 +476,11 @@ class BoxedPanel(Panel):
def update(self):
self.erase()
is_first_responder = self.is_first_responder()
if is_first_responder:
is_in_first_responder_chain = self.is_in_first_responder_chain()
if is_in_first_responder_chain:
self.attron (curses.A_REVERSE)
self.box()
if is_first_responder:
if is_in_first_responder_chain:
self.attroff (curses.A_REVERSE)
if self.title:
self.addstr(Point(x=2, y=0), ' ' + self.title + ' ')
@ -422,6 +497,150 @@ class BoxedPanel(Panel):
else:
return
class Item(object):
def __init__(self, title, action):
self.title = title
self.action = action
class Menu(BoxedPanel):
def __init__(self, title, items):
max_title_width = 0
for item in items:
if max_title_width < len(item.title):
max_title_width = len(item.title)
frame = Rect(x=0, y=0, w=max_title_width+4, h=len(items)+2)
super(Menu, self).__init__(frame, title=None, delegate=None, can_become_first_responder=True)
self.selected_idx = 0
self.title = title
self.items = items
for (item_idx, item) in enumerate(items):
self.set_line(item_idx, item.title)
self.hide()
def update(self):
super(Menu, self).update()
def relinquish_first_responder(self):
if not self.hidden():
self.hide()
def perform_action(self):
selected_idx = self.get_selected_idx()
if selected_idx < len(self.items):
action = self.items[selected_idx].action
if action:
action()
class MenuBar(Panel):
def __init__(self, frame):
super(MenuBar, self).__init__(frame, can_become_first_responder=True)
self.menus = list()
self.selected_menu_idx = -1
self.add_key_action(curses.KEY_LEFT, self.select_prev, "Select the previous menu")
self.add_key_action(curses.KEY_RIGHT, self.select_next, "Select the next menu")
self.add_key_action(curses.KEY_DOWN, lambda: self.select(0), "Select the first menu")
self.add_key_action(27, self.relinquish_first_responder, "Hide current menu")
self.add_key_action(curses.KEY_ENTER, self.perform_action, "Select the next menu item")
self.add_key_action(10, self.perform_action, "Select the next menu item")
def insert_menu(self, menu, index=sys.maxint):
if index >= len(self.menus):
self.menus.append(menu)
else:
self.menus.insert(index, menu)
pt = self.get_position()
for menu in self.menus:
menu.set_position(pt)
pt.x += len(menu.title) + 5
def perform_action(self):
'''If no menu is visible, show the first menu. If a menu is visible, perform the action
associated with the selected menu item in the menu'''
menu_visible = False
for menu in self.menus:
if not menu.hidden():
menu_visible = True
break
if menu_visible:
menu.perform_action()
self.selected_menu_idx = -1
self._selected_menu_changed()
else:
self.select(0)
def relinquish_first_responder(self):
if self.selected_menu_idx >= 0:
self.selected_menu_idx = -1
self._selected_menu_changed()
def _selected_menu_changed(self):
for (menu_idx, menu) in enumerate(self.menus):
is_hidden = menu.hidden()
if menu_idx != self.selected_menu_idx:
if not is_hidden:
if self.parent.pop_first_responder(menu) == False:
menu.hide()
for (menu_idx, menu) in enumerate(self.menus):
is_hidden = menu.hidden()
if menu_idx == self.selected_menu_idx:
if is_hidden:
menu.show()
self.parent.push_first_responder(menu)
menu.top()
self.parent.refresh()
def select(self, index):
if index < len(self.menus):
self.selected_menu_idx = index
self._selected_menu_changed()
def select_next (self):
num_menus = len(self.menus)
if self.selected_menu_idx == -1:
if num_menus > 0:
self.selected_menu_idx = 0
self._selected_menu_changed()
else:
if self.selected_menu_idx + 1 < num_menus:
self.selected_menu_idx += 1
else:
self.selected_menu_idx = -1
self._selected_menu_changed()
def select_prev (self):
num_menus = len(self.menus)
if self.selected_menu_idx == -1:
if num_menus > 0:
self.selected_menu_idx = num_menus - 1
self._selected_menu_changed()
else:
if self.selected_menu_idx - 1 >= 0:
self.selected_menu_idx -= 1
else:
self.selected_menu_idx = -1
self._selected_menu_changed()
def update(self):
self.erase()
is_in_first_responder_chain = self.is_in_first_responder_chain()
if is_in_first_responder_chain:
self.attron (curses.A_REVERSE)
pt = Point(x=0, y=0)
for menu in self.menus:
self.addstr(pt, '| ' + menu.title + ' ')
pt.x += len(menu.title) + 5
self.addstr(pt, '|')
width = self.get_size().w
while pt.x < width:
self.addch(pt, ' ')
pt.x += 1
if is_in_first_responder_chain:
self.attroff (curses.A_REVERSE)
for menu in self.menus:
menu.update()
class StatusPanel(Panel):
def __init__(self, frame):
super(StatusPanel, self).__init__(frame, delegate=None, can_become_first_responder=False)