def update_widgets(self, update_dir=True, update_file=True, update_select=True): """ Update self.dir_widget, self.file_widget or self.select_widget, corresponding to which of the paramters are set to True. """ if update_dir or update_file: (dirlist, filelist) = self._dirfiles(self.directory) if update_dir: # Directory widget: widget_list = [ AttrWrap(SelText(dir), None, self.attr[1]) for dir in dirlist ] self.dir_widget.box_widget.body = SimpleListWalker(widget_list) if update_file: # File widget: widget_list = [ AttrWrap(SelText(dir), None, self.attr[1]) for dir in filelist ] self.file_widget.box_widget.body = SimpleListWalker(widget_list) if update_select: # Selection widget: selected_file = join(self.directory, self.file) self.select_widget.set_edit_text(selected_file)
def __init__(self, choice_callback=None, command_callback=None, help_callback=None): self.palette = [ ('brick', 'light red', 'black'), ('rubble', 'yellow', 'black'), ('wood', 'light green', 'black'), ('concrete', 'white', 'black'), ('stone', 'light cyan', 'black'), ('marble', 'light magenta', 'black'), ('jack', 'dark gray', 'white'), ('msg_info', 'white', 'black'), ('msg_err', 'light red', 'black'), ('msg_debug', 'light green', 'black'), ] self.choice_callback = choice_callback self.command_callback = command_callback self.help_callback = help_callback self.screen = None self.loop = None self.called_loop_stop = False self.reactor_stop_fired = False self.quit_flag = False self.edit_msg = "Make selection ('q' to quit): " self.roll_list = SimpleListWalker([]) self.game_log_list = SimpleListWalker([]) self.choices_list = SimpleListWalker([]) self.state_text = SimpleListWalker([Text('Connecting...')]) self.edit_widget = Edit(self.edit_msg) self.roll = ListBox(self.roll_list) self.game_log = ListBox(self.game_log_list) self.choices = ListBox(self.choices_list) self.state = ListBox(self.state_text) self.left_frame = Pile([ LineBox(self.state), (13, LineBox(self.choices)), ]) self.right_frame = Pile([ LineBox(self.game_log), LineBox(self.roll) ]) self.state.set_focus(len(self.state_text)-1) self.columns = Columns([('weight', 0.75, self.left_frame), ('weight', 0.25, self.right_frame) ]) self.frame_widget = Frame(footer=self.edit_widget, body=self.columns, focus_part='footer') self.exc_info = None
class Sidebar(BoxWidget): signals = ['select', 'search'] def __init__(self, phones): self.phone_edit = EnterEdit('Phone:', '') self.chan_edit = EnterEdit('Chanl:', '') self.call_id_edit = EnterEdit('SipID:', '') self.text_edit = EnterEdit('Find:', '') connect_signal(self.phone_edit, 'enter', self.on_change, 'phone') connect_signal(self.chan_edit, 'enter', self.on_change, 'chan') connect_signal(self.call_id_edit, 'enter', self.on_change, 'call_id') connect_signal(self.text_edit, 'enter', self.on_change, 'text') self.phones_text = Text([('key', 'F4'), ' Phones']) self.head = Pile([ AttrWrap(Text([('key', 'F3'), ' Search']), 'bar'), AttrWrap(self.phone_edit, 'no', 'selection'), AttrWrap(self.chan_edit, 'no', 'selection'), AttrWrap(self.call_id_edit, 'no', 'selection'), Divider('-'), AttrWrap(self.text_edit, 'no', 'selection'), AttrWrap(self.phones_text, 'bar'), ]) self.items = SimpleListWalker([]) self.set_results(phones) self.listbox = ListBox(self.items) self.frame = Frame(self.listbox, header=self.head) def set_results(self, results): self.phones_text.set_text([('key', 'F4'), ' Results (%s)' % len(results)]) self.items[:] = [] group = [] for ref in results: item = RadioButton(group, ref, state=False) connect_signal(item, 'change', self.on_select_phone, ref) item = AttrWrap(item, 'no', 'selection') self.items.append(item) def render(self, size, focus=False): return self.frame.render(size, focus) def keypress(self, size, key): return self.frame.keypress(size, key) # noinspection PyUnusedLocal def on_select_phone(self, button, new_state, call_id): if new_state: self._emit('select', call_id) # noinspection PyUnusedLocal def on_change(self, widget, value, field_name): self._emit('search', field_name, value)
class Menu(Dialog):#{{{ def __init__(self, menu_items, focus_item=0, **kwargs):#{{{ # menu items: ( (text, attr), (callback, args), shortcut ) self.item_list = SimpleListWalker([]) listbox = ListBox(self.item_list) self.__super.__init__(listbox, keypress=self.on_keypress, **kwargs) self.attr_style = 'dialog.menu' self.title_attr_style = 'dialog.menu.title' self.shortcuts = {} def wrap(callback): if callback: def w(*args): self.install_callback(callback, args) self.quit() w.__name__ = callback.__name__ w.__doc__ = callback.__doc__ return w return None for item in menu_items: tag_attr = item[0] if len(item) > 1: ca = item[1] if isinstance(ca, tuple): if len(ca) == 2: cb, args = ca else: cb, args = ca, () else: cb = ca args = () if not callable(cb): raise ValueError("You must pass a callable callback") else: cb, args = None, () self.item_list.append(AttrMap(MenuItem(tag_attr, wrap(cb), args), 'dialog.menu.item', 'dialog.menu.item.focus')) if len(item) > 2: self.shortcuts[item[2]] = len(self.item_list) - 1 self.item_list.set_focus(focus_item) #}}} def on_keypress(self, key):#{{{ if key == "esc": self.quit() elif key in self.shortcuts: self.item_list[self.shortcuts[key]].original_widget.activate() return key
def __init__(self, name): self.name = name self._connected = False self._parent = None self.status = {} self.children = [] self.input_history = None self.output_walker = SimpleListWalker([]) self.input_text = "" self.auto_scroll = True self.read_line_index = -1
def __init__(self, num_rows=20, w=(14, 14, 18, 16, 16, 16, 20)): """ @method __init__ Initializes the widget """ self.m_process_list = ProcessList(w) self.prev_sort_item = None self.w_status = HeaderButton('Status', 'status', self.handle_click) self.w_pid = HeaderButton('PID', 'pid', self.handle_click) self.w_name = HeaderButton('Name', 'name', self.handle_click) self.w_cpu = HeaderButton('CPU %', 'cpu_perc', self.handle_click) self.w_mem = HeaderButton('MEM %', 'mem_perc', self.handle_click) self.w_up = HeaderButton('Uptime', 'uptime', self.handle_click) self.w_pname = HeaderButton('Process', 'pname', self.handle_click) self.w_cpu.activate() self.prev_sort_item = self.w_cpu self.header_buttons = h = [ self.w_status, self.w_pid, self.w_name, self.w_cpu, self.w_mem, self.w_up, self.w_pname ] m_header = AttrMap( Columns([('fixed', w[i], h[i]) for i in range(0, len(h))]), 'invert') m_lb = ListBox( SimpleListWalker( [m_header, BoxAdapter(self.m_process_list, num_rows)])) super(ProcessTable, self).__init__(m_lb, None) self.update()
def create_interface(self): self.screen = Screen() self.screen.start() self.screen.register_palette([ ("title", "white", "dark blue", "standout"), ("line", "light gray", "black"), ("help", "white", "dark blue")] ) self.body = ListBox(SimpleListWalker([])) self.lines = self.body.body self.title = Text(MAIN_TITLE) self.header = AttrWrap(self.title, "title") self.help = AttrWrap( Text(HELP_STRINGS["main"]), "help" ) self.input = Edit(caption="%s> " % self.ircchannel) self.footer = Pile([self.help, self.input]) self.top = Frame(self.body, self.header, self.footer)
def _build_widget(self, **kwargs): total_items = [] for _item in self.radio_items.keys(): desc = AttrWrap( Text(" {}".format( self.radio_items[_item][1])), 'input', 'input focus') total_items.append( AttrWrap(self.radio_items[_item][0], 'input', 'input focus')) total_items.append(AttrWrap(desc, 'input')) total_items.append(Divider('-')) self.input_lbox = ListBox(SimpleListWalker(total_items[:-1])) self.add_buttons() self.container_box_adapter = BoxAdapter(self.input_lbox, len(total_items)) self.container_lbox = ListBox( [self.container_box_adapter, Divider(), self.btn_pile]) return LineBox( BoxAdapter(self.container_lbox, height=len(total_items) + 3), title=self.title)
def __init__(self, choice_callback=None, command_callback=None, help_callback=None): self.palette = [ ('brick', 'light red', 'black'), ('rubble', 'yellow', 'black'), ('wood', 'light green', 'black'), ('concrete', 'white', 'black'), ('stone', 'light cyan', 'black'), ('marble', 'light magenta', 'black'), ('jack', 'dark gray', 'white'), ('msg_info', 'white', 'black'), ('msg_err', 'light red', 'black'), ('msg_debug', 'light green', 'black'), ] self.choice_callback = choice_callback self.command_callback = command_callback self.help_callback = help_callback self.screen = None self.loop = None self.called_loop_stop = False self.reactor_stop_fired = False self.quit_flag = False self.edit_msg = "Make selection ('q' to quit): " self.roll_list = SimpleListWalker([]) self.game_log_list = SimpleListWalker([]) self.choices_list = SimpleListWalker([]) self.state_text = SimpleListWalker([Text('Connecting...')]) self.edit_widget = Edit(self.edit_msg) self.roll = ListBox(self.roll_list) self.game_log = ListBox(self.game_log_list) self.choices = ListBox(self.choices_list) self.state = ListBox(self.state_text) self.left_frame = Pile([ LineBox(self.state), (13, LineBox(self.choices)), ]) self.right_frame = Pile([LineBox(self.game_log), LineBox(self.roll)]) self.state.set_focus(len(self.state_text) - 1) self.columns = Columns([('weight', 0.75, self.left_frame), ('weight', 0.25, self.right_frame)]) self.frame_widget = Frame(footer=self.edit_widget, body=self.columns, focus_part='footer') self.exc_info = None
def render_component(self, props): def make_todo(id, completed, text): def on_click(): props['on_todo_click'](id) return Todo( on_click=on_click, completed=completed, text=text, ) return ListBox( SimpleListWalker([make_todo(**todo) for todo in props['todos']]))
def __init__(self, contents, offset=1): """ Arguments: `contents` is a list with the elements contained in the `ScrollableListBox`. `offset` is the number of position that `scroll_up` and `scroll_down` shift the cursor. """ self.offset = offset ListBox.__init__(self, SimpleListWalker(contents))
def __init__(self, phones): self.phone_edit = EnterEdit('Phone:', '') self.chan_edit = EnterEdit('Chanl:', '') self.call_id_edit = EnterEdit('SipID:', '') self.text_edit = EnterEdit('Find:', '') connect_signal(self.phone_edit, 'enter', self.on_change, 'phone') connect_signal(self.chan_edit, 'enter', self.on_change, 'chan') connect_signal(self.call_id_edit, 'enter', self.on_change, 'call_id') connect_signal(self.text_edit, 'enter', self.on_change, 'text') self.phones_text = Text([('key', 'F4'), ' Phones']) self.head = Pile([ AttrWrap(Text([('key', 'F3'), ' Search']), 'bar'), AttrWrap(self.phone_edit, 'no', 'selection'), AttrWrap(self.chan_edit, 'no', 'selection'), AttrWrap(self.call_id_edit, 'no', 'selection'), Divider('-'), AttrWrap(self.text_edit, 'no', 'selection'), AttrWrap(self.phones_text, 'bar'), ]) self.items = SimpleListWalker([]) self.set_results(phones) self.listbox = ListBox(self.items) self.frame = Frame(self.listbox, header=self.head)
def _insert_charm_selections(self): first_index = 0 bgroup = [] for i, charm_class in enumerate(self.charms): charm = charm_class if charm.name() and not first_index: first_index = i r = RadioButton(bgroup, charm.name()) r.text_label = charm.name() self.boxes.append(r) # Add input widget for specifying NumUnits self.count_editor = IntEdit("Number of units to add: ", 1) self.boxes.append(self.count_editor) wrapped_boxes = self._wrap_focus(self.boxes) items = ListBox(SimpleListWalker(wrapped_boxes)) items.set_focus(first_index) return (len(self.boxes), BoxAdapter(items, len(self.boxes)))
def _build_widget(self, **kwargs): total_items = [] for _item in self.input_items.keys(): total_items.append( AttrWrap(self.input_items[_item], 'input', 'input focus')) self.input_lbox = ListBox(SimpleListWalker(total_items)) # Add buttons self.add_buttons() self.container_box_adapter = BoxAdapter(self.input_lbox, len(total_items)) self.container_lbox = ListBox( [self.container_box_adapter, Divider(), self.btn_pile]) return LineBox(BoxAdapter(self.container_lbox, height=len(total_items) + 1 + len(self.btn_pile.contents)), title=self.title)
def __init__(self, **kwargs):#{{{ self.search_box = SearchBox() self.items_count = Text("", align='right') self.search_items = SimpleListWalker(self._null_list_item()) connect_signal(self.search_items, "modified", self._update_items_count) self.search_list = ListBox(self.search_items) connect_signal(self.search_box, "edit-done", self.on_edit_done) connect_signal(self.search_box, "edit-cancel", lambda w: self.quit()) connect_signal(self.search_box, "change", lambda sb, term: self.set_search_term(term)) self._constr = self.get_item_constructor() self.multiple_selection = kwargs.pop('multiple_selection', False) self._selected_items = OrderedSet([]) opts = { 'height': kwargs.get('height', None), 'width': kwargs.get('width', ('relative', 90)), 'title': kwargs.get('title', self.title), 'subtitle': kwargs.get('subtitle', self.subtitle), 'compact_header': kwargs.get('compact_header', True), } kwargs.update(opts) self.pile = Pile([ ('fixed', 15, AttrMap(self.search_list, 'dialog.search.item')), Columns([ AttrMap(self.search_box, 'dialog.search.input'), ('fixed', 1, AttrMap(Divider(), 'dialog.search.input')), ('fixed', 4, AttrMap(self.items_count, 'dialog.search.input')), ]), ], focus_item=0) self.__super.__init__(self.pile, **kwargs) self.attr_style = "dialog.search" self.title_attr_style = "dialog.search.title" self.subtitle_attr_style = "dialog.search.subtitle"
class CursesGUI(object): def __init__(self, choice_callback=None, command_callback=None, help_callback=None): self.palette = [ ('brick', 'light red', 'black'), ('rubble', 'yellow', 'black'), ('wood', 'light green', 'black'), ('concrete', 'white', 'black'), ('stone', 'light cyan', 'black'), ('marble', 'light magenta', 'black'), ('jack', 'dark gray', 'white'), ('msg_info', 'white', 'black'), ('msg_err', 'light red', 'black'), ('msg_debug', 'light green', 'black'), ] self.choice_callback = choice_callback self.command_callback = command_callback self.help_callback = help_callback self.screen = None self.loop = None self.called_loop_stop = False self.reactor_stop_fired = False self.quit_flag = False self.edit_msg = "Make selection ('q' to quit): " self.roll_list = SimpleListWalker([]) self.game_log_list = SimpleListWalker([]) self.choices_list = SimpleListWalker([]) self.state_text = SimpleListWalker([Text('Connecting...')]) self.edit_widget = Edit(self.edit_msg) self.roll = ListBox(self.roll_list) self.game_log = ListBox(self.game_log_list) self.choices = ListBox(self.choices_list) self.state = ListBox(self.state_text) self.left_frame = Pile([ LineBox(self.state), (13, LineBox(self.choices)), ]) self.right_frame = Pile([LineBox(self.game_log), LineBox(self.roll)]) self.state.set_focus(len(self.state_text) - 1) self.columns = Columns([('weight', 0.75, self.left_frame), ('weight', 0.25, self.right_frame)]) self.frame_widget = Frame(footer=self.edit_widget, body=self.columns, focus_part='footer') self.exc_info = None def register_loggers(self): """Gets the global loggers and sets up log handlers. """ self.game_logger_handler = RollLogHandler(self._roll_write) self.logger_handler = RollLogHandler(self._roll_write) self.game_logger = logging.getLogger('gtr.game') self.logger = logging.getLogger('gtr') self.logger.addHandler(self.logger_handler) self.game_logger.addHandler(self.game_logger_handler) #self.set_log_level(logging.INFO) def unregister_loggers(self): self.game_logger.removeHandler(self.game_logger_handler) self.logger.removeHandler(self.logger_handler) def fail_safely(f): """Wraps functions in this class to catch arbitrary exceptions, shut down the event loop and reset the terminal to a normal state. It then re-raises the exception. """ @wraps(f) def wrapper(self, *args, **kwargs): retval = None try: retval = f(self, *args, **kwargs) except urwid.ExitMainLoop: from twisted.internet import reactor if not self.reactor_stop_fired and reactor.running: # Make sure to call reactor.stop once reactor.stop() self.reactor_stop_fired = True except: #pdb.set_trace() from twisted.internet import reactor if not self.reactor_stop_fired and reactor.running: # Make sure to call reactor.stop once reactor.stop() self.reactor_stop_fired = True if not self.called_loop_stop: self.called_loop_stop = True self.loop.stop() # Save exception info for printing later outside the GUI. self.exc_info = sys.exc_info() raise return retval return wrapper def set_log_level(self, level): """Set the log level as per the standard library logging module. Default is logging.INFO. """ logging.getLogger('gtr.game').setLevel(level) logging.getLogger('gtr').setLevel(level) def run(self): loop = MainLoop(self.frame_widget, unhandled_input=self.handle_input) loop.run() def run_twisted(self): from twisted.internet import reactor evloop = urwid.TwistedEventLoop(reactor, manage_reactor=False) self.screen = urwid.raw_display.Screen() self.screen.register_palette(self.palette) self.loop = MainLoop(self.frame_widget, unhandled_input=self.handle_input, screen=self.screen, event_loop=evloop) self.loop.set_alarm_in(0.1, lambda loop, _: loop.draw_screen()) self.loop.start() # The loggers get a Handler that writes to the screen. We want this to only # happen if the screen exists, so de-register them after the reactor stops. reactor.addSystemEventTrigger('after', 'startup', self.register_loggers) reactor.addSystemEventTrigger('before', 'shutdown', self.unregister_loggers) reactor.run() # We might have stopped the screen already, and the stop() method # doesn't check for stopping twice. if self.called_loop_stop: self.logger.warn('Internal error!') else: self.loop.stop() self.called_loop_stop = True @fail_safely def handle_input(self, key): if key == 'enter': text = self.edit_widget.edit_text if text in ['q', 'Q']: self.handle_quit_request() else: self.quit_flag = False try: i = int(text) except ValueError: i = None self.handle_invalid_choice(text) if i is not None: self.handle_choice(i) self.edit_widget.set_edit_text('') def _roll_write(self, line, attr=None): """Add a line to the roll with palette attributes 'attr'. If no attr is specified, None is used. Default attr is plain text """ text = Text((attr, '* ' + line)) self.roll_list.append(text) self.roll_list.set_focus(len(self.roll_list) - 1) self._modified() @fail_safely def update_state(self, state): """Sets the game state window via one large string. """ self.logger.debug('Drawing game state.') self.state_text[:] = [self.colorize(s) for s in state.split('\n')] self._modified() @fail_safely def update_game_log(self, log): """Sets the game log window via one large string. """ self.logger.debug('Drawing game log.') self.game_log_list[:] = [self.colorize(s) for s in log.split('\n')] self.game_log_list.set_focus(len(self.game_log_list) - 1) self._modified() @fail_safely def update_choices(self, choices): """Update choices list. """ self.choices_list[:] = [self.colorize(str(c)) for c in choices] self._modified() length = len([c for c in choices if c[2] == '[']) i = randint(1, length) if length else 0 self.choices_list.append( self.colorize('\nPicking random choice: {0} in 1s'.format(i))) self._modified() #from twisted.internet import reactor #reactor.callLater(1, self.handle_choice, i) @fail_safely def update_prompt(self, prompt): """Set the prompt for the input field. """ self.edit_widget.set_caption(prompt) self._modified() def _modified(self): if self.loop: self.loop.draw_screen() @fail_safely def quit(self): """Quit the program. """ #import pdb; pdb.set_trace() #raise TypeError('Artificial TypeError') raise urwid.ExitMainLoop() def handle_invalid_choice(self, s): if len(s): text = 'Invalid choice: "' + s + '". Please enter an integer.' self.logger.warn(text) def handle_quit_request(self): if True or self.quit_flag: self.quit() else: self.quit_flag = True text = 'Are you sure you want to quit? Press Q again to confirm.' self.logger.warn(text) def handle_choice(self, i): if self.choice_callback: self.choice_callback(i) def colorize(self, s): """Applies color to roles found in a string. A string with attributes applied looks like Text([('attr1', 'some text'), 'some more text']) so we need to split into a list of tuples of text. """ regex_color_dict = { r'\b([Ll]egionaries|[Ll]egionary|[Ll]eg|LEGIONARIES|LEGIONARY|LEG)\b': 'brick', r'\b([Ll]aborers?|[Ll]ab|LABORERS?|LAB)\b': 'rubble', r'\b([Cc]raftsmen|[Cc]raftsman|[Cc]ra|CRAFTSMEN|CRAFTSMAN|CRA)\b': 'wood', r'\b([Aa]rchitects?|[Aa]rc|ARCHITECTS?|ARC)\b': 'concrete', r'\b([Mm]erchants?|[Mm]er|MERCHANTS?|MER)\b': 'stone', r'\b([Pp]atrons?|[Pp]at|PATRONS?|PAT)\b': 'marble', r'\b([Jj]acks?|JACKS?)\b': 'jack', r'\b([Bb]ricks?|[Bb]ri|BRICKS?|BRI)\b': 'brick', r'\b([Rr]ubble|[Rr]ub|RUBBLE|RUB)\b': 'rubble', r'\b([Ww]ood|[Ww]oo|WOOD|WOO)\b': 'wood', r'\b([Cc]oncrete|[Cc]on|CONCRETE|CON)\b': 'concrete', r'\b([Ss]tone|[Ss]to|STONE|STO)\b': 'stone', r'\b([Mm]arble|[Mm]ar|MARBLE|MAR)\b': 'marble', } def _colorize(s, regex, attr): """s is a tuple of ('attr', 'text'). This splits based on the regex and adds attr to any matches. Returns a list of tuples [('attr1', 'text1'), ('attr2', 'text2'), ('attr3','text3')] with some attributes being None if they aren't colored. """ output = [] a, t = s # Make a list of all tokens, split by matches tokens = re.split(regex, t) for tok in tokens: m = re.match(regex, tok) if m: # matches get the new attributes output.append((attr, tok)) else: # non-matches keep the old ones output.append((a, tok)) return output output = [(None, s)] for k, v in regex_color_dict.items(): new_output = [] for token in output: new_output.extend(_colorize(token, k, v)) output[:] = new_output return Text(output)
def __init__(self, content): self.walker = SimpleListWalker(content) super(SimpleListBox, self).__init__(self.walker)
""" return datetime.fromtimestamp(self.pget_uptime()).strftime(f) """ Testing """ if __name__ == '__main__': def exit(p): if p is 'q': raise ExitMainLoop() def refresh(loop, data): p1.update() p2.update() loop.set_alarm_in(1, refresh) p1 = Process(1, lambda x: x, lambda: x) p2 = Process(os.getpid(), lambda x: x, lambda: x) lb = ListBox(SimpleListWalker([p1, p2])) m = MainLoop(lb, palette=[('reversed', 'standout', ''), ('popbg', 'white', 'dark blue')], pop_ups=True, unhandled_input=exit) m.set_alarm_in(1, refresh) m.run()
def _build_widget(self): lw = SimpleListWalker([x for x in self.contents]) return ListBox(lw)
class SearchDialog(Dialog):#{{{ title = "SearchDialog" subtitle = "GenericWindow" _items = [] def __init__(self, **kwargs):#{{{ self.search_box = SearchBox() self.items_count = Text("", align='right') self.search_items = SimpleListWalker(self._null_list_item()) connect_signal(self.search_items, "modified", self._update_items_count) self.search_list = ListBox(self.search_items) connect_signal(self.search_box, "edit-done", self.on_edit_done) connect_signal(self.search_box, "edit-cancel", lambda w: self.quit()) connect_signal(self.search_box, "change", lambda sb, term: self.set_search_term(term)) self._constr = self.get_item_constructor() self.multiple_selection = kwargs.pop('multiple_selection', False) self._selected_items = OrderedSet([]) opts = { 'height': kwargs.get('height', None), 'width': kwargs.get('width', ('relative', 90)), 'title': kwargs.get('title', self.title), 'subtitle': kwargs.get('subtitle', self.subtitle), 'compact_header': kwargs.get('compact_header', True), } kwargs.update(opts) self.pile = Pile([ ('fixed', 15, AttrMap(self.search_list, 'dialog.search.item')), Columns([ AttrMap(self.search_box, 'dialog.search.input'), ('fixed', 1, AttrMap(Divider(), 'dialog.search.input')), ('fixed', 4, AttrMap(self.items_count, 'dialog.search.input')), ]), ], focus_item=0) self.__super.__init__(self.pile, **kwargs) self.attr_style = "dialog.search" self.title_attr_style = "dialog.search.title" self.subtitle_attr_style = "dialog.search.subtitle" #}}} def keypress(self, key):#{{{ if key == 'insert' and self.multiple_selection: if self.pile.get_focus().original_widget is self.search_list: wid, pos = self.search_list.get_focus() else: pos = 0 current_item = self.search_items[pos] article = self.get_data_for(pos) current_item.original_widget.selected = not current_item.original_widget.selected if current_item.original_widget.selected: current_item.attr_map = {None: 'dialog.search.item.selected'} current_item.focus_map = {None: 'dialog.search.item.focus.selected'} self._selected_items.add(article) else: current_item.attr_map = {None: 'dialog.search.item'} current_item.focus_map = {None: 'dialog.search.item.focus'} self._selected_items.discard(article) self.search_list.set_focus(pos+1) self._update_items_count() #}}} def on_edit_done(self, widget, text):#{{{ result = [] if self.pile.get_focus().original_widget is self.search_list: wid, pos = self.search_list.get_focus() else: pos = 0 if self.multiple_selection: result = list(self._selected_items) if len(result) < 1: if self.get_data_for(pos): result = [self.get_data_for(pos)] self.dialog_result = result self.quit() #}}} def set_search_term(self, term):#{{{ self._clear_search_items() query = self.get_query(term) if query is not None: self._items = tuple(query[:150]) if len(self._items) > 0: l_items = map(self._constr, self._items) for i in l_items: i.set_search_box(self.search_box) self.search_items.extend([AttrMap(i, 'dialog.search.item',\ 'dialog.search.item.focus') for i in l_items]) if self.multiple_selection: for a in (self._selected_items & set(self._items)): idx = self._items.index(a) self.search_items[idx].attr_map = {None: 'dialog.search.item.selected'} self.search_items[idx].focus_map = {None: 'dialog.search.item.focus.selected'} self.search_items[idx].original_widget.selected = True return self.search_items.extend(self._null_list_item()) #}}} def _clear_search_items(self):#{{{ self.search_items[:] = [] self._update_items_count() #}}} def _null_list_item(self):#{{{ null = SearchListItem([Text("")]) null.set_search_box(self.search_box) return [null] #}}} def _update_items_count(self):#{{{ if len(self.search_items) > 149: self.items_count.set_text("+150") else: self.items_count.set_text("") selected_count = len(self._selected_items) if selected_count > 0: self._title_widget.set_text(self.title + (" (+%d)" % selected_count)) else: self._title_widget.set_text(self.title) #}}} def get_data_for(self, index):#{{{ try: return self._items[index] except IndexError as e: # index out of range return None #}}} def get_query(self, term):#{{{ raise NotImplementedError("This must be implemented by subclass") #}}} def get_item_constructor(self):#{{{ return None
def __init__(self, contents): self.list_walker = SimpleListWalker(contents) ListBox.__init__(self, self.list_walker)
def __init__(self, tags): tag_widgets = self._create_tag_widgets(tags) self.list_content = SimpleListWalker(tag_widgets) ListBox.__init__(self, self.list_content)
class Tab(object): __metaclass__ = MetaSignals signals = ["child_added", "child_removed", "remove", "connected"] _valid_stati = [ "informative", "actions", "actions_highlight", "actions_own", "messages", "messages_highlight", "messages_own" ] def __repr__(self): return "<tab: %s:%s:%s>" % (type(self).__name__, self._parent, self.name) def __str__(self): return self.name # Properties @types(switch=bool) def set_connected(self, switch): self._connected = switch for child in self.children: child.set_connected(switch) urwid.emit_signal(self, "connected", switch) connected = property(lambda x: x._connected, set_connected) def set_input_text(self, text): self._input_text = text input_text = property(lambda x: x._input_text, set_input_text) # Methods @types(name=(String, str, unicode)) def __init__(self, name): self.name = name self._connected = False self._parent = None self.status = {} self.children = [] self.input_history = None self.output_walker = SimpleListWalker([]) self.input_text = "" self.auto_scroll = True self.read_line_index = -1 def set_readline(self): if self.read_line_index != -1: try: del self.output_walker[self.read_line_index] except IndexError: pass line = Text("-" * 30) line.set_align_mode("center") self.output_walker.append(line) self.read_line_index = len(self.output_walker) - 1 def child_added(self, child): urwid.emit_signal(self, "child_added", self, child) child.set_connected(self.connected) self.children.append(child) def child_removed(self, child): urwid.emit_signal(self, "child_removed", self, child) try: i = self.children.index(child) except ValueError: pass else: del self.children[i] def set_parent(self, parent): if parent in self.children: print "Loop detected in '%s'.set_parent(%s)" % (self, parent) return try: self._parent.child_removed(self) except AttributeError: pass self._parent = parent try: self._parent.child_added(self) except AttributeError: pass parent = property(lambda x: x._parent, set_parent) def remove(self): """ emit remove signals """ for child in self.children: child.remove() self.child_removed(child) urwid.emit_signal(self, "remove") self.set_parent(None) @types(name=str) def add_status(self, name): if name in self._valid_stati: self.status[name] = True @types(status=str) def has_status(self, status): return self.status.has_key(status) @types(name=str) def remove_status(self, name): try: del self.status[name] except KeyError: pass def reset_status(self): self.status = {} def print_last_log(self, lines=0): """ Fetch the given amount of lines of history for the channel on the given server and print it to the channel's textview. """ lines = UInt64(lines or config.get("chatting", "last_log_lines", "0")) if type(self) == Server: server = self.name channel = "" else: server = self.parent.name channel = self.name for line in connection.sushi.log(server, channel, lines): self.output_walker.append(Text(unicode(line)))
def parse_tag_from_node(node): from .tags import parse_tag_from_node body = [parse_tag_from_node(PyQuery(child)) for child in node.children()] walker = SimpleListWalker(body) return ListBox(walker)
class CursesGUI(object): def __init__(self, choice_callback=None, command_callback=None, help_callback=None): self.palette = [ ('brick', 'light red', 'black'), ('rubble', 'yellow', 'black'), ('wood', 'light green', 'black'), ('concrete', 'white', 'black'), ('stone', 'light cyan', 'black'), ('marble', 'light magenta', 'black'), ('jack', 'dark gray', 'white'), ('msg_info', 'white', 'black'), ('msg_err', 'light red', 'black'), ('msg_debug', 'light green', 'black'), ] self.choice_callback = choice_callback self.command_callback = command_callback self.help_callback = help_callback self.screen = None self.loop = None self.called_loop_stop = False self.reactor_stop_fired = False self.quit_flag = False self.edit_msg = "Make selection ('q' to quit): " self.roll_list = SimpleListWalker([]) self.game_log_list = SimpleListWalker([]) self.choices_list = SimpleListWalker([]) self.state_text = SimpleListWalker([Text('Connecting...')]) self.edit_widget = Edit(self.edit_msg) self.roll = ListBox(self.roll_list) self.game_log = ListBox(self.game_log_list) self.choices = ListBox(self.choices_list) self.state = ListBox(self.state_text) self.left_frame = Pile([ LineBox(self.state), (13, LineBox(self.choices)), ]) self.right_frame = Pile([ LineBox(self.game_log), LineBox(self.roll) ]) self.state.set_focus(len(self.state_text)-1) self.columns = Columns([('weight', 0.75, self.left_frame), ('weight', 0.25, self.right_frame) ]) self.frame_widget = Frame(footer=self.edit_widget, body=self.columns, focus_part='footer') self.exc_info = None def register_loggers(self): """Gets the global loggers and sets up log handlers. """ self.game_logger_handler = RollLogHandler(self._roll_write) self.logger_handler = RollLogHandler(self._roll_write) self.game_logger = logging.getLogger('gtr.game') self.logger = logging.getLogger('gtr') self.logger.addHandler(self.logger_handler) self.game_logger.addHandler(self.game_logger_handler) #self.set_log_level(logging.INFO) def unregister_loggers(self): self.game_logger.removeHandler(self.game_logger_handler) self.logger.removeHandler(self.logger_handler) def fail_safely(f): """Wraps functions in this class to catch arbitrary exceptions, shut down the event loop and reset the terminal to a normal state. It then re-raises the exception. """ @wraps(f) def wrapper(self, *args, **kwargs): retval = None try: retval = f(self, *args, **kwargs) except urwid.ExitMainLoop: from twisted.internet import reactor if not self.reactor_stop_fired and reactor.running: # Make sure to call reactor.stop once reactor.stop() self.reactor_stop_fired = True except: #pdb.set_trace() from twisted.internet import reactor if not self.reactor_stop_fired and reactor.running: # Make sure to call reactor.stop once reactor.stop() self.reactor_stop_fired = True if not self.called_loop_stop: self.called_loop_stop = True self.loop.stop() # Save exception info for printing later outside the GUI. self.exc_info = sys.exc_info() raise return retval return wrapper def set_log_level(self, level): """Set the log level as per the standard library logging module. Default is logging.INFO. """ logging.getLogger('gtr.game').setLevel(level) logging.getLogger('gtr').setLevel(level) def run(self): loop = MainLoop(self.frame_widget, unhandled_input=self.handle_input) loop.run() def run_twisted(self): from twisted.internet import reactor evloop = urwid.TwistedEventLoop(reactor, manage_reactor=False) self.screen = urwid.raw_display.Screen() self.screen.register_palette(self.palette) self.loop = MainLoop(self.frame_widget, unhandled_input=self.handle_input, screen = self.screen, event_loop = evloop) self.loop.set_alarm_in(0.1, lambda loop, _: loop.draw_screen()) self.loop.start() # The loggers get a Handler that writes to the screen. We want this to only # happen if the screen exists, so de-register them after the reactor stops. reactor.addSystemEventTrigger('after','startup', self.register_loggers) reactor.addSystemEventTrigger('before','shutdown', self.unregister_loggers) reactor.run() # We might have stopped the screen already, and the stop() method # doesn't check for stopping twice. if self.called_loop_stop: self.logger.warn('Internal error!') else: self.loop.stop() self.called_loop_stop = True @fail_safely def handle_input(self, key): if key == 'enter': text = self.edit_widget.edit_text if text in ['q', 'Q']: self.handle_quit_request() else: self.quit_flag = False try: i = int(text) except ValueError: i = None self.handle_invalid_choice(text) if i is not None: self.handle_choice(i) self.edit_widget.set_edit_text('') def _roll_write(self, line, attr=None): """Add a line to the roll with palette attributes 'attr'. If no attr is specified, None is used. Default attr is plain text """ text = Text((attr, '* ' + line)) self.roll_list.append(text) self.roll_list.set_focus(len(self.roll_list)-1) self._modified() @fail_safely def update_state(self, state): """Sets the game state window via one large string. """ self.logger.debug('Drawing game state.') self.state_text[:] = [self.colorize(s) for s in state.split('\n')] self._modified() @fail_safely def update_game_log(self, log): """Sets the game log window via one large string. """ self.logger.debug('Drawing game log.') self.game_log_list[:] = [self.colorize(s) for s in log.split('\n')] self.game_log_list.set_focus(len(self.game_log_list)-1) self._modified() @fail_safely def update_choices(self, choices): """Update choices list. """ self.choices_list[:] = [self.colorize(str(c)) for c in choices] self._modified() length = len([c for c in choices if c[2] == '[']) i = randint(1,length) if length else 0 self.choices_list.append(self.colorize('\nPicking random choice: {0} in 1s'.format(i))) self._modified() #from twisted.internet import reactor #reactor.callLater(1, self.handle_choice, i) @fail_safely def update_prompt(self, prompt): """Set the prompt for the input field. """ self.edit_widget.set_caption(prompt) self._modified() def _modified(self): if self.loop: self.loop.draw_screen() @fail_safely def quit(self): """Quit the program. """ #import pdb; pdb.set_trace() #raise TypeError('Artificial TypeError') raise urwid.ExitMainLoop() def handle_invalid_choice(self, s): if len(s): text = 'Invalid choice: "' + s + '". Please enter an integer.' self.logger.warn(text) def handle_quit_request(self): if True or self.quit_flag: self.quit() else: self.quit_flag = True text = 'Are you sure you want to quit? Press Q again to confirm.' self.logger.warn(text) def handle_choice(self, i): if self.choice_callback: self.choice_callback(i) def colorize(self, s): """Applies color to roles found in a string. A string with attributes applied looks like Text([('attr1', 'some text'), 'some more text']) so we need to split into a list of tuples of text. """ regex_color_dict = { r'\b([Ll]egionaries|[Ll]egionary|[Ll]eg|LEGIONARIES|LEGIONARY|LEG)\b' : 'brick', r'\b([Ll]aborers?|[Ll]ab|LABORERS?|LAB)\b' : 'rubble', r'\b([Cc]raftsmen|[Cc]raftsman|[Cc]ra|CRAFTSMEN|CRAFTSMAN|CRA)\b' : 'wood', r'\b([Aa]rchitects?|[Aa]rc|ARCHITECTS?|ARC)\b' : 'concrete', r'\b([Mm]erchants?|[Mm]er|MERCHANTS?|MER)\b' : 'stone', r'\b([Pp]atrons?|[Pp]at|PATRONS?|PAT)\b' : 'marble', r'\b([Jj]acks?|JACKS?)\b' : 'jack', r'\b([Bb]ricks?|[Bb]ri|BRICKS?|BRI)\b' : 'brick', r'\b([Rr]ubble|[Rr]ub|RUBBLE|RUB)\b' : 'rubble', r'\b([Ww]ood|[Ww]oo|WOOD|WOO)\b' : 'wood', r'\b([Cc]oncrete|[Cc]on|CONCRETE|CON)\b' : 'concrete', r'\b([Ss]tone|[Ss]to|STONE|STO)\b' : 'stone', r'\b([Mm]arble|[Mm]ar|MARBLE|MAR)\b' : 'marble', } def _colorize(s, regex, attr): """s is a tuple of ('attr', 'text'). This splits based on the regex and adds attr to any matches. Returns a list of tuples [('attr1', 'text1'), ('attr2', 'text2'), ('attr3','text3')] with some attributes being None if they aren't colored. """ output = [] a, t = s # Make a list of all tokens, split by matches tokens = re.split(regex, t) for tok in tokens: m = re.match(regex, tok) if m: # matches get the new attributes output.append( (attr,tok) ) else: # non-matches keep the old ones output.append( (a, tok) ) return output output = [ (None, s) ] for k,v in regex_color_dict.items(): new_output = [] for token in output: new_output.extend(_colorize(token, k, v)) output[:] = new_output return Text(output)
class Tab(object): __metaclass__ = MetaSignals signals = ["child_added", "child_removed", "remove", "connected"] _valid_stati = [ "informative", "actions", "actions_highlight","actions_own", "messages","messages_highlight","messages_own"] def __repr__(self): return "<tab: %s:%s:%s>" % ( type(self).__name__, self._parent, self.name) def __str__(self): return self.name # Properties @types(switch = bool) def set_connected(self, switch): self._connected = switch for child in self.children: child.set_connected(switch) urwid.emit_signal(self, "connected", switch) connected = property(lambda x: x._connected, set_connected) def set_input_text(self, text): self._input_text = text input_text = property(lambda x: x._input_text, set_input_text) # Methods @types(name = (String, str, unicode)) def __init__(self, name): self.name = name self._connected = False self._parent = None self.status = {} self.children = [] self.input_history = None self.output_walker = SimpleListWalker([]) self.input_text = "" self.auto_scroll = True self.read_line_index = -1 def set_readline(self): if self.read_line_index != -1: try: del self.output_walker[self.read_line_index] except IndexError: pass line = Text("-"*30) line.set_align_mode ("center") self.output_walker.append(line) self.read_line_index = len(self.output_walker)-1 def child_added(self, child): urwid.emit_signal(self, "child_added", self, child) child.set_connected(self.connected) self.children.append(child) def child_removed(self, child): urwid.emit_signal(self, "child_removed", self, child) try: i = self.children.index(child) except ValueError: pass else: del self.children[i] def set_parent(self, parent): if parent in self.children: print "Loop detected in '%s'.set_parent(%s)" % ( self, parent) return try: self._parent.child_removed(self) except AttributeError: pass self._parent = parent try: self._parent.child_added(self) except AttributeError: pass parent = property(lambda x: x._parent, set_parent) def remove(self): """ emit remove signals """ for child in self.children: child.remove() self.child_removed(child) urwid.emit_signal(self, "remove") self.set_parent(None) @types(name = str) def add_status(self, name): if name in self._valid_stati: self.status[name] = True @types(status = str) def has_status(self, status): return self.status.has_key(status) @types(name = str) def remove_status(self, name): try: del self.status[name] except KeyError: pass def reset_status(self): self.status = {} def print_last_log(self, lines=0): """ Fetch the given amount of lines of history for the channel on the given server and print it to the channel's textview. """ lines = UInt64(lines or config.get( "chatting", "last_log_lines", "0")) if type(self) == Server: server = self.name channel = "" else: server = self.parent.name channel = self.name for line in connection.sushi.log(server, channel, lines): self.output_walker.append(Text(unicode(line)))