Example #1
0
class AddCharmDialog(Overlay):
    """ Adding charm dialog """

    def __init__(self, underlying, juju_state, destroy, command_runner=None):
        import cloudinstall.charms
        charm_modules = [import_module('cloudinstall.charms.' + mname)
                         for (_, mname, _) in
                         pkgutil.iter_modules(cloudinstall.charms.__path__)]
        charm_classes = [m.__charm_class__ for m in charm_modules
                         if m.__charm_class__.allow_multi_units]

        self.cr = command_runner
        self.underlying = underlying
        self.destroy = destroy

        self.boxes = []
        self.bgroup = []
        first_index = 0
        for i, charm_class in enumerate(charm_classes):
            charm = charm_class(juju_state=juju_state)
            if charm.name() and not first_index:
                first_index = i
            r = RadioButton(self.bgroup, charm.name())
            r.text_label = charm.name()
            self.boxes.append(r)

        self.count_editor = IntEdit("Number of units to add: ", 1)
        self.boxes.append(self.count_editor)
        wrapped_boxes = _wrap_focus(self.boxes)

        bs = [Button("Ok", self.yes), Button("Cancel", self.no)]
        wrapped_buttons = _wrap_focus(bs)
        self.buttons = Columns(wrapped_buttons)
        self.items = ListBox(wrapped_boxes)
        self.items.set_focus(first_index)
        ba = BoxAdapter(self.items, height=len(wrapped_boxes))
        self.lb = ListBox([ba, Text(""), self.buttons])
        self.w = LineBox(self.lb, title="Add unit")
        self.w = AttrMap(self.w, "dialog")
        Overlay.__init__(self, self.w, self.underlying,
                         'center', 45, 'middle', len(wrapped_boxes) + 4)

    def yes(self, button):
        selected = [r for r in self.boxes if
                    r is not self.count_editor
                    and r.get_state()][0]
        _charm_to_deploy = selected.label
        n = self.count_editor.value()
        log.info("Adding {n} units of {charm}".format(
            n=n, charm=_charm_to_deploy))
        self.cr.add_unit(_charm_to_deploy, count=int(n))
        self.destroy()

    def no(self, button):
        self.destroy()
Example #2
0
class ChangeStateDialog(Overlay):
    def __init__(self, underlying, juju_state, on_success, on_cancel):
        import cloudinstall.charms
        charm_modules = [import_module('cloudinstall.charms.' + mname)
                         for (_, mname, _) in
                         pkgutil.iter_modules(cloudinstall.charms.__path__)]
        charm_classes = sorted([m.__charm_class__ for m in charm_modules],
                               key=attrgetter('deploy_priority'))

        self.boxes = []
        first_index = 0
        for i, charm_class in enumerate(charm_classes):
            charm = charm_class(juju_state=juju_state)
            if charm.name() and not first_index:
                first_index = i
            r = CheckBox(charm.name())
            r.text_label = charm.name()
            self.boxes.append(r)
        wrapped_boxes = _wrap_focus(self.boxes)

        def ok(button):
            selected = filter(lambda r: r.get_state(), self.boxes)
            on_success([s.text_label for s in selected])

        def cancel(button):
            on_cancel()

        bs = [Button("Ok", ok), Button("Cancel", cancel)]
        wrapped_buttons = _wrap_focus(bs)
        self.buttons = Columns(wrapped_buttons)
        self.items = ListBox(wrapped_boxes)
        self.items.set_focus(first_index)
        ba = BoxAdapter(self.items, height=len(wrapped_boxes))
        self.lb = ListBox([ba, self.count_editor, self.buttons])
        root = LineBox(self.lb, title="Select new charm")
        root = AttrMap(root, "dialog")

        Overlay.__init__(self, root, underlying, 'center', 30, 'middle',
                         len(wrapped_boxes) + 4)

    def keypress(self, size, key):
        if key == 'tab':
            if self.lb.get_focus()[0] == self.buttons:
                self.keypress(size, 'page up')
            else:
                self.keypress(size, 'page down')
        return Overlay.keypress(self, size, key)
Example #3
0
    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)))
Example #4
0
    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)))
Example #5
0
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
Example #6
0
class TreeBox(WidgetWrap):
    """
    A widget that displays a given :class:`Tree`.
    This is essentially a :class:`ListBox` with the ability to move the focus
    based on directions in the Tree and to collapse/expand subtrees if
    possible.

    TreeBox interprets `left/right` as well as `page up/`page down` to move the
    focus to parent/first child and next/previous sibling respectively. All
    other keys are passed to the underlying ListBox.
    """
    def __init__(self, tree, focus=None):
        """
        :param tree: tree of widgets to be displayed.
        :type tree: Tree
        :param focus: initially focussed position
        """
        self._tree = tree
        self._walker = TreeListWalker(tree)
        self._outer_list = ListBox(self._walker)
        if focus is not None:
            self._outer_list.set_focus(focus)
        self.__super.__init__(self._outer_list)

    # Widget API
    def get_focus(self):
        return self._outer_list.get_focus()

    def set_focus(self, pos):
        return self._outer_list.set_focus(pos)

    def refresh(self):
        self._walker.clear_cache()
        signals.emit_signal(self._walker, "modified")

    def keypress(self, size, key):
        key = self._outer_list.keypress(size, key)
        if key in [
                'left',
                'right',
                '[',
                ']',
                '-',
                '+',
                'C',
                'E',
        ]:
            if key == 'left':
                self.focus_parent()
            elif key == 'right':
                self.focus_first_child()
            elif key == '[':
                self.focus_prev_sibling()
            elif key == ']':
                self.focus_next_sibling()
            elif key == '-':
                self.collapse_focussed()
            elif key == '+':
                self.expand_focussed()
            elif key == 'C':
                self.collapse_all()
            elif key == 'E':
                self.expand_all()
            # This is a hack around ListBox misbehaving:
            # it seems impossible to set the focus without calling keypress as
            # otherwise the change becomes visible only after the next render()
            return self._outer_list.keypress(size, None)
        else:
            return self._outer_list.keypress(size, key)

    # Collapse operations
    def collapse_focussed(self):
        """
        Collapse currently focussed position; works only if the underlying
        tree allows it.
        """
        if implementsCollapseAPI(self._tree):
            w, focuspos = self.get_focus()
            self._tree.collapse(focuspos)
            self._walker.clear_cache()
            self.refresh()

    def expand_focussed(self):
        """
        Expand currently focussed position; works only if the underlying
        tree allows it.
        """
        if implementsCollapseAPI(self._tree):
            w, focuspos = self.get_focus()
            self._tree.expand(focuspos)
            self._walker.clear_cache()
            self.refresh()

    def collapse_all(self):
        """
        Collapse all positions; works only if the underlying tree allows it.
        """
        if implementsCollapseAPI(self._tree):
            self._tree.collapse_all()
            self.set_focus(self._tree.root)
            self._walker.clear_cache()
            self.refresh()

    def expand_all(self):
        """
        Expand all positions; works only if the underlying tree allows it.
        """
        if implementsCollapseAPI(self._tree):
            self._tree.expand_all()
            self._walker.clear_cache()
            self.refresh()

    # Tree based focus movement
    def focus_parent(self):
        """move focus to parent node of currently focussed one"""
        w, focuspos = self.get_focus()
        parent = self._tree.parent_position(focuspos)
        if parent is not None:
            self.set_focus(parent)

    def focus_first_child(self):
        """move focus to first child of currently focussed one"""
        w, focuspos = self.get_focus()
        child = self._tree.first_child_position(focuspos)
        if child is not None:
            self.set_focus(child)

    def focus_last_child(self):
        """move focus to last child of currently focussed one"""
        w, focuspos = self.get_focus()
        child = self._tree.last_child_position(focuspos)
        if child is not None:
            self.set_focus(child)

    def focus_next_sibling(self):
        """move focus to next sibling of currently focussed one"""
        w, focuspos = self.get_focus()
        sib = self._tree.next_sibling_position(focuspos)
        if sib is not None:
            self.set_focus(sib)

    def focus_prev_sibling(self):
        """move focus to previous sibling of currently focussed one"""
        w, focuspos = self.get_focus()
        sib = self._tree.prev_sibling_position(focuspos)
        if sib is not None:
            self.set_focus(sib)

    def focus_next(self):
        """move focus to next position (DFO)"""
        w, focuspos = self.get_focus()
        next = self._tree.next_position(focuspos)
        if next is not None:
            self.set_focus(next)

    def focus_prev(self):
        """move focus to previous position (DFO)"""
        w, focuspos = self.get_focus()
        prev = self._tree.prev_position(focuspos)
        if prev is not None:
            self.set_focus(prev)
Example #7
0
class TreeBox(WidgetWrap):
    """
    A widget representing something in a nested tree display.
    This is essentially a ListBox with the ability to move the focus based on
    directions in the Tree.

    TreeBox interprets `left/right` as well as page `up/down` to move the focus
    to parent/first child and next/previous sibling respectively. All other
    keys are passed to the underlying ListBox.
    """
    _selectable = True

    def __init__(self, walker, **kwargs):
        """
        :param walker: tree of widgets to be displayed.
            In case we are given a raw `TreeWalker`, it will be used though
            `TreeListWalker` which means no decoration.
        :type walker: TreeWalker or TreeListWalker
        """
        if not isinstance(walker, TreeListWalker):
            walker = TreeListWalker(walker)
        self._walker = walker
        self._outer_list = ListBox(walker)
        self.__super.__init__(self._outer_list)

    # Widget API
    def get_focus(self):
        return self._outer_list.get_focus()

    def keypress(self, size, key):
        key = self._outer_list.keypress(size, key)
        if key in ['left', 'right', '[', ']', '-', '+', 'C', 'E']:
            if key == 'left':
                self.focus_parent()
            elif key == 'right':
                self.focus_first_child()
            elif key == '[':
                self.focus_prev_sibling()
            elif key == ']':
                self.focus_next_sibling()
            if isinstance(self._walker, CollapseMixin):
                if key == '-':
                    w, focuspos = self._walker.get_focus()
                    self._walker.collapse(focuspos)
                elif key == '+':
                    w, focuspos = self._walker.get_focus()
                    self._walker.expand(focuspos)
                elif key == 'C':
                    self._walker.collapse_all()
                elif key == 'E':
                    self._walker.expand_all()
            # This is a hack around ListBox misbehaving:
            # it seems impossible to set the focus without calling keypress as
            # otherwise the change becomes visible only after the next render()
            return self._outer_list.keypress(size, None)
        else:
            return self._outer_list.keypress(size, key)

    # Tree based focus movement
    def focus_parent(self):
        w, focuspos = self._walker.get_focus()
        parent = self._walker.parent_position(focuspos)
        if parent is not None:
            self._outer_list.set_focus(parent)

    def focus_first_child(self):
        w, focuspos = self._walker.get_focus()
        child = self._walker.first_child_position(focuspos)
        if child is not None:
            self._outer_list.set_focus(child)

    def focus_next_sibling(self):
        w, focuspos = self._walker.get_focus()
        sib = self._walker.next_sibling_position(focuspos)
        if sib is not None:
            self._outer_list.set_focus(sib)

    def focus_prev_sibling(self):
        w, focuspos = self._walker.get_focus()
        sib = self._walker.prev_sibling_position(focuspos)
        if sib is not None:
            self._outer_list.set_focus(sib)
Example #8
0
class TreeBox(WidgetWrap):
    """
    A widget representing something in a nested tree display.
    This is essentially a ListBox with the ability to move the focus based on
    directions in the Tree.

    TreeBox interprets `left/right` as well as page `up/down` to move the focus
    to parent/first child and next/previous sibling respectively. All other
    keys are passed to the underlying ListBox.
    """
    _selectable = True

    def __init__(self, walker, **kwargs):
        """
        :param walker: tree of widgets to be displayed.
            In case we are given a raw `TreeWalker`, it will be used though
            `TreeListWalker` which means no decoration.
        :type walker: TreeWalker or TreeListWalker
        """
        if not isinstance(walker, TreeListWalker):
            walker = TreeListWalker(walker)
        self._walker = walker
        self._outer_list = ListBox(walker)
        self.__super.__init__(self._outer_list)

    # Widget API
    def get_focus(self):
        return self._outer_list.get_focus()

    def keypress(self, size, key):
        key = self._outer_list.keypress(size, key)
        if key in ['left', 'right', '[', ']', '-', '+', 'C', 'E']:
            if key == 'left':
                self.focus_parent()
            elif key == 'right':
                self.focus_first_child()
            elif key == '[':
                self.focus_prev_sibling()
            elif key == ']':
                self.focus_next_sibling()
            if isinstance(self._walker, CollapseMixin):
                if key == '-':
                    w, focuspos = self._walker.get_focus()
                    self._walker.collapse(focuspos)
                elif key == '+':
                    w, focuspos = self._walker.get_focus()
                    self._walker.expand(focuspos)
                elif key == 'C':
                    self._walker.collapse_all()
                elif key == 'E':
                    self._walker.expand_all()
            # This is a hack around ListBox misbehaving:
            # it seems impossible to set the focus without calling keypress as
            # otherwise the change becomes visible only after the next render()
            return self._outer_list.keypress(size, None)
        else:
            return self._outer_list.keypress(size, key)

    # Tree based focus movement
    def focus_parent(self):
        w, focuspos = self._walker.get_focus()
        parent = self._walker.parent_position(focuspos)
        if parent is not None:
            self._outer_list.set_focus(parent)

    def focus_first_child(self):
        w, focuspos = self._walker.get_focus()
        child = self._walker.first_child_position(focuspos)
        if child is not None:
            self._outer_list.set_focus(child)

    def focus_next_sibling(self):
        w, focuspos = self._walker.get_focus()
        sib = self._walker.next_sibling_position(focuspos)
        if sib is not None:
            self._outer_list.set_focus(sib)

    def focus_prev_sibling(self):
        w, focuspos = self._walker.get_focus()
        sib = self._walker.prev_sibling_position(focuspos)
        if sib is not None:
            self._outer_list.set_focus(sib)
Example #9
0
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)
Example #10
0
class TreeBox(WidgetWrap):
    """
    A widget that displays a given :class:`Tree`.
    This is essentially a :class:`ListBox` with the ability to move the focus
    based on directions in the Tree and to collapse/expand subtrees if
    possible.

    TreeBox interprets `left/right` as well as `page up/`page down` to move the
    focus to parent/first child and next/previous sibling respectively. All
    other keys are passed to the underlying ListBox.
    """

    def __init__(self, tree, focus=None):
        """
        :param tree: tree of widgets to be displayed.
        :type tree: Tree
        :param focus: initially focussed position
        """
        self._tree = tree
        self._walker = TreeListWalker(tree)
        self._outer_list = ListBox(self._walker)
        if focus is not None:
            self._outer_list.set_focus(focus)
        self.__super.__init__(self._outer_list)

    # Widget API
    def get_focus(self):
        return self._outer_list.get_focus()

    def set_focus(self, pos):
        return self._outer_list.set_focus(pos)

    def refresh(self):
        self._walker.clear_cache()
        signals.emit_signal(self._walker, "modified")

    def keypress(self, size, key):
        key = self._outer_list.keypress(size, key)
        if key in ["left", "right", "[", "]", "-", "+", "C", "E"]:
            if key == "left":
                self.focus_parent()
            elif key == "right":
                self.focus_first_child()
            elif key == "[":
                self.focus_prev_sibling()
            elif key == "]":
                self.focus_next_sibling()
            elif key == "-":
                self.collapse_focussed()
            elif key == "+":
                self.expand_focussed()
            elif key == "C":
                self.collapse_all()
            elif key == "E":
                self.expand_all()
            # This is a hack around ListBox misbehaving:
            # it seems impossible to set the focus without calling keypress as
            # otherwise the change becomes visible only after the next render()
            return self._outer_list.keypress(size, None)
        else:
            return self._outer_list.keypress(size, key)

    # Collapse operations
    def collapse_focussed(self):
        """
        Collapse currently focussed position; works only if the underlying
        tree allows it.
        """
        if implementsCollapseAPI(self._tree):
            w, focuspos = self.get_focus()
            self._tree.collapse(focuspos)
            self._walker.clear_cache()
            self.refresh()

    def expand_focussed(self):
        """
        Expand currently focussed position; works only if the underlying
        tree allows it.
        """
        if implementsCollapseAPI(self._tree):
            w, focuspos = self.get_focus()
            self._tree.expand(focuspos)
            self._walker.clear_cache()
            self.refresh()

    def collapse_all(self):
        """
        Collapse all positions; works only if the underlying tree allows it.
        """
        if implementsCollapseAPI(self._tree):
            self._tree.collapse_all()
            self.set_focus(self._tree.root)
            self._walker.clear_cache()
            self.refresh()

    def expand_all(self):
        """
        Expand all positions; works only if the underlying tree allows it.
        """
        if implementsCollapseAPI(self._tree):
            self._tree.expand_all()
            self._walker.clear_cache()
            self.refresh()

    # Tree based focus movement
    def focus_parent(self):
        """move focus to parent node of currently focussed one"""
        w, focuspos = self.get_focus()
        parent = self._tree.parent_position(focuspos)
        if parent is not None:
            self.set_focus(parent)

    def focus_first_child(self):
        """move focus to first child of currently focussed one"""
        w, focuspos = self.get_focus()
        child = self._tree.first_child_position(focuspos)
        if child is not None:
            self.set_focus(child)

    def focus_last_child(self):
        """move focus to last child of currently focussed one"""
        w, focuspos = self.get_focus()
        child = self._tree.last_child_position(focuspos)
        if child is not None:
            self.set_focus(child)

    def focus_next_sibling(self):
        """move focus to next sibling of currently focussed one"""
        w, focuspos = self.get_focus()
        sib = self._tree.next_sibling_position(focuspos)
        if sib is not None:
            self.set_focus(sib)

    def focus_prev_sibling(self):
        """move focus to previous sibling of currently focussed one"""
        w, focuspos = self.get_focus()
        sib = self._tree.prev_sibling_position(focuspos)
        if sib is not None:
            self.set_focus(sib)

    def focus_next(self):
        """move focus to next position (DFO)"""
        w, focuspos = self.get_focus()
        next = self._tree.next_position(focuspos)
        if next is not None:
            self.set_focus(next)

    def focus_prev(self):
        """move focus to previous position (DFO)"""
        w, focuspos = self.get_focus()
        prev = self._tree.prev_position(focuspos)
        if prev is not None:
            self.set_focus(prev)
Example #11
0
 def _set_current_rotation(self, lb: urwid.ListBox) -> None:
     rots = [n for n, title in rotation.get_rotations()]
     cur_rot = self.shell.settings.get("core.screen_orientation",
                                       str) or "0"
     focus_pos = rots.index(cur_rot)
     lb.set_focus(focus_pos, "above")
Example #12
0
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)
Example #13
0
class LogDisplay(BoxWidget):
    def __init__(self, parser, encoding):
        self.parser = parser
        self.encoding = encoding
        self.find = {'ref': '', 'text': ''}
        self.isolate_filter = (None, None)
        self.line_collection = None
        self.jump_stack = []
        self.expansions = {}
        self.show_sip_level = 2
        self.show_ladder = False
        self.show_only_filtered = False
        self.show_verbose = True
        self.show_channel = True
        self.line_no_before_isolate = 0
        self.showing_help = False

        self.walker = LogLineWalker([('Select call on the left side', None)],
                                    self.jump, self.expand, self.isolate)
        self.header = AttrWrap(Text([('key', 'F6'), ' Log']), 'bar')
        self.listbox = ListBox(self.walker)
        self.frame = Frame(self.listbox, header=self.header)

    def render(self, size, focus=False):
        return self.frame.render(size, focus)

    def keypress(self, size, key):
        return self.frame.keypress(size, key)

    def set_line_collection(self, line_collection):
        self.jump_stack = []
        self.line_collection = line_collection
        self.walker.lines = line_collection.lines
        self.walker.set_focus(0)
        lc, mc = len(line_collection.lines), line_collection.match_count
        self.header.set_text([
            ('key', 'F6'),
            ' Log lines: %d, matches: %s | ' % (lc, mc),
            ('key', 's'),
            '/',
            ('key', 'S'),
            ' toggle sip messages/ladders | ',
            ('key', 'n'),
            '/',
            ('key', 'N'),
            ' next/previous | ',
            ('key', 'f'),
            ' toggle filter | ',
            ('key', 'i'),
            '/',
            ('key', 'I'),
            ' isolate/un-isolate | ',
            ('key', 'enter'),
            '/',
            ('key', 'backspace'),
            ' jump/back | ',
            ('key', 'space'),
            ' expand/collapse',
        ])
        self.line_collection.set_filter(self.show_only_filtered)

    def help(self):
        if self.showing_help:
            self.refresh_log()
        else:
            lc = LineCollection({})
            for markup in HELP:
                lc.add(markup)
            self.set_line_collection(lc)
        self.showing_help = not self.showing_help

    @property
    def compiled_find(self):
        find_text = self.find['text']
        if find_text and len(find_text) > 2:
            try:
                find_text = re.compile(find_text)
            except Exception as e:
                log.debug('Invalid RE %r: %s', find_text, e)

        # noinspection PyDictCreation
        find_map = {self.find['ref']: 'find1'}
        # possibly override
        find_map[find_text] = {'style': 'find2', 'jump': True}
        return find_map

    def load_result(self, ref):
        self.isolate_filter = (None, None)
        self.find['ref'] = ref
        self.refresh_log()

    def toggle_filter(self):
        if not self.line_collection or self.listbox.get_focus()[0] is None:
            return
        self.jump_stack = []
        self.expansions = {}
        self.show_only_filtered = not self.show_only_filtered
        self.listbox.set_focus(0)
        self.line_collection.set_filter(self.show_only_filtered)
        self.listbox.set_focus(0)

    def toggle_sip(self):
        self.jump_stack = []
        self.expansions = {}
        self.show_sip_level = (self.show_sip_level + 1) % 3

    def jump(self, where):
        cur = self.listbox.get_focus()[1]
        if where == 'home':
            self.listbox.set_focus(0)
        elif where == 'end':
            self.listbox.set_focus(len(self.line_collection.lines) - 1)
        elif where == 'next':
            if self.show_only_filtered:
                return
            for line_no in self.line_collection.line_numbers_with_needle:
                pos = self.line_collection.line_number_map.get(line_no)  # + 1
                if pos > cur:
                    self.listbox.set_focus(pos)
                    break
        elif where == 'previous':
            if self.show_only_filtered:
                return
            collection = self.line_collection.line_numbers_with_needle
            for i in range(len(collection) - 1, -1, -1):
                line_no = collection[i]
                pos = self.line_collection.line_number_map.get(line_no)  # + 1
                if pos < cur:
                    self.listbox.set_focus(pos)
                    break
        elif where == -1:
            # Jump back from where we came
            if self.jump_stack:
                pos = self.jump_stack.pop()
                self.listbox.set_focus(pos)
        else:
            pos = self.line_collection.line_number_map.get(where)
            if pos and pos != cur:
                self.jump_stack.append(cur)
                self.listbox.set_focus(pos)

    def expand(self, tag):
        if self.show_ladder:
            return  # No effect when showing all ladders

        cur = self.listbox.get_focus()[1]

        ref_type, ref = ref_tag(tag)
        # Collapse
        if ref in self.expansions:
            old, markups = self.expansions[ref]
            self.line_collection.remove(old, len(markups))
            del self.expansions[ref]
            self.listbox.set_focus(cur)
            return

        # Expand
        markups = []
        if ref_type == 'call_id':
            dialog = self.parser.find_obj(ref_type, ref)
            markups = message_sequence_chart(dialog)
            markups = [[' ' * 9] + m for m in markups]  # indent
        elif ref_type == 'sip_ref':
            sip = self.parser.find_obj(ref_type, ref)
            instructions = sip_payload_instructions(sip)
            markups = [(s, m) for (_l, s, m) in instructions]
        elif ref_type == 'chan':
            channel = self.parser.find_obj(ref_type, ref)
            markups = dial_chart(channel)
            markups = [[' ' * 9] + m for m in markups]  # indent

        if markups:
            self.expansions[ref] = (cur + 1, markups)
            self.line_collection.insert(cur + 1, markups)
            self.listbox.set_focus(cur)

    def isolate(self, tag):
        ref_type, ref = ref_tag(tag)
        if ref_type and ref:
            self.line_no_before_isolate = self.listbox.get_focus()[1]
            self.isolate_filter = (ref_type, ref)
            self.refresh_log()
        else:
            self.isolate_filter = (None, None)
            self.refresh_log(focus_line=self.line_no_before_isolate)

    def refresh_log(self, focus_line=False):

        groups, objects = self.parser.get_linked_objects(
            self.find['ref'], self.isolate_filter)

        old_pos = self.listbox.get_focus()[1]

        warning_count = 0
        error_count = 0

        lc = LineCollection(self.compiled_find)
        if objects:

            # Prepare markup for "Overview" section

            lc.add([('section', 'Overview ')])  # must be a list
            for group in groups:
                tab, tab2 = '= ', '   '
                for line_no, what, obj in group.overview:
                    markup = obj
                    dialog = None
                    tag = None
                    if what == 'dialog':
                        sip = obj
                        dialog = sip.dialog
                        style = get_sip_style(sip)
                        if dialog:
                            dialog_status = dialog.dialog_status or ''
                            dialog_ack = dialog.dialog_ack or ''
                            dialog_bye = dialog.bye_addr or ''
                        else:
                            dialog_status = ''
                            dialog_ack = ''
                            dialog_bye = ''
                        markup = [
                            ('', ' '),
                            ('line-no', '%07d' % (line_no + 1)),
                            ('', ' '),
                            ('mute', '[%s] ' % sip.when),
                            (style, sip.request or sip.status or '?'),
                            ('', ' %s -> %s ' % (sip.from_num, sip.to_num)),
                            ('sip-status', '%s' % (dialog_status or ' ')),
                            ('', ' '),
                            ('sip-method', '%s' % (dialog_ack or ' ')),
                        ]
                        if dialog and dialog.timeout:
                            markup.extend([('', ' '),
                                           ('sip-timeout', 'TIMEOUT')])
                        if dialog_bye:
                            markup.extend([
                                ('', ' '),
                                ('sip-bye2', 'BYE'),
                                ('mute', ' from '),
                                ('mute', dialog_bye),
                            ])
                        markup.append(('sip-call-id', '  %s' % sip.call_id))
                        tag = {'call_id': sip.call_id}
                    elif what == 'channel':
                        channel = obj
                        markup = [('line-no', '%07d' % (line_no + 1)),
                                  ('', ' '), ('mute', '[%s] ' % channel.when),
                                  ('channel-name', channel.name)]
                        for phone in set(channel.extensions):
                            markup.append(('', ' '))
                            markup.append(('', phone))
                        if channel.clid_num:
                            markup.append(('channel-phone',
                                           ' (clid:%s)' % channel.clid_num))
                        for i, app in enumerate(channel.apps):
                            dial_style = get_dial_status_style(app.status)
                            markup.append(('mute', '; ' if i else ': '))
                            markup.append(('mute', '%s ' % app.app_name))
                            markup.append(('', app.data or ' '))
                            markup.append(('mute', ' '))
                            markup.append((dial_style, app.status))
                        tag = {'chan': channel.name}
                    elif what == 'astcall':
                        acall = obj
                        markup = [('line-no', '%07d' % (line_no + 1)),
                                  ('', ' '), ('mute', '[%s] ' % acall.when),
                                  ('acall-id', acall.acall_id)]
                        tag = {'acall_id': acall.acall_id}
                    lc.add(['%s%s%s ' % (tab, what, tab2), markup or '?'],
                           tag=tag)

                    if dialog and dialog.timeout:
                        timeout_line_no, timeout_when = dialog.timeout
                        markup = [('', ' '),
                                  ('line-no', '%07d' % (timeout_line_no + 1)),
                                  ('mute', ' [%s] ' % timeout_when),
                                  ('sip-timeout', 'TIMEOUT')]
                        lc.add(['%s%s%s ' % (tab, '      ', tab2), markup])

                    if dialog and self.show_ladder:
                        for markup in message_sequence_chart(dialog):
                            lc.add([' ' * 9] + markup)
                    tab, tab2 = '  - ', ' '
            lc.add('')

            # Prepare markup for "Log" section

            flat = objects.items()
            flat.sort()

            lc.add(('section', 'Log'))
            old_line_no = None
            for line_no, (style, obj) in flat:
                # Text is kept as bytes, convert to unicode only when displaying
                if isinstance(obj, _bytes3):
                    obj = obj.decode(self.encoding, errors='replace')
                if old_line_no is not None and old_line_no + 1 != line_no:
                    # If Missing single unimportant line, omit "..."
                    if not (style == 'sip' and old_line_no + 2 == line_no):
                        lc.add(('mute', '...'))
                old_line_no = line_no
                if style == 'sip':
                    if self.show_sip_level == 0:
                        continue
                    sip = obj
                    line_no = sip.line_no
                    tag = None
                    markup = [('sip-intro', '==== %s ' % sip.when),
                              ('elapsed', '(ela: %s) ' % sip.elapsed_sec)]
                    if self.show_sip_level == 1:
                        if sip.request:
                            markup.append((get_sip_style(sip), str(sip)))
                        else:
                            markup.append(('sip-status', str(sip)))
                        # Adding tag also allows message expansion
                        tag = {'sip_ref': sip.ref}
                    if sip.attempt_no:
                        markup.append(
                            ('sip-retransmit', 'ATTEMPT #%s' % sip.attempt_no))
                    markup.append(('', ' %s ' % sip.direction))
                    # markup.append(('sip-direction', ' '))
                    if sip.direction == 'IN':
                        markup.append(('sip-addr', '%s' % sip.recipient_addr))
                        markup.append(('', ' <- '))
                        markup.append(('sip-addr', '%s' % sip.sender_addr))
                    else:
                        markup.append(('sip-addr', '%s' % sip.sender_addr))
                        markup.append(('', ' -> '))
                        markup.append(('sip-addr', '%s' % sip.recipient_addr))
                    lc.add(markup, tag=tag)
                    if self.show_sip_level == 1:
                        continue
                    instructions = sip_payload_instructions(sip)
                    # We use line_no again on purpose
                    # noinspection PyAssignmentToLoopOrWithParameter
                    for line_no, sty2, markup in instructions:
                        lc.add((sty2, markup), line_no)
                elif style == 'channel':
                    if self.show_channel:
                        lc.add((style, obj), line_no, {
                            'Dial': 'asterisk-app',
                            'Queue': 'asterisk-app',
                        })
                elif self.show_verbose:
                    if b'WARNING' in obj:
                        lc.add(('warning', obj), line_no)
                        warning_count += 1
                    if b'ERROR' in obj:
                        lc.add(('error', obj), line_no)
                        error_count += 1
                    else:
                        lc.add((style, obj), line_no)
                old_line_no = line_no

        if warning_count:
            lc.lines[0][0].append(
                ('warning', ' Warnings: %d ' % warning_count))
        if error_count:
            lc.lines[0][0].append(('error', ' Errors: %d ' % error_count))

        self.set_line_collection(lc)

        if focus_line:
            focus_line = focus_line if isinstance(focus_line, int) else old_pos
            try:
                self.listbox.set_focus(focus_line, 'above')
            except IndexError:
                pass
Example #14
0
class NetworkView(BaseView):
    def __init__(self, model, signal):
        self.model = model
        self.signal = signal
        self.items = []
        self.error = Text("", align='center')
        self.body = [
            Padding.center_79(self._build_model_inputs()),
            Padding.line_break(""),
            Padding.center_79(self._build_additional_options()),
            Padding.line_break(""),
            Padding.center_79(Color.info_error(self.error)),
            Padding.line_break(""),
            Padding.fixed_10(self._build_buttons()),
        ]
        # FIXME determine which UX widget should have focus
        self.lb = ListBox(self.body)
        self.lb.set_focus(4)  # _build_buttons
        super().__init__(self.lb)

    def show_overlay(self, overlay_widget):
        self.orig_w = self._w
        self._w = Overlay(top_w=overlay_widget,
                          bottom_w=self._w,
                          align='center',
                          width=('relative', 60),
                          min_width=80,
                          valign='middle',
                          height='pack')

    def remove_overlay(self, overlay_widget):
        # urwid note: we could also get orig_w as
        # self._w.contents[0][0], but this is clearer:
        self._w = self.orig_w

    def _build_buttons(self):
        cancel = Color.button(cancel_btn(on_press=self.cancel),
                              focus_map='button focus')
        done = Color.button(done_btn(on_press=self.done),
                            focus_map='button focus')
        self.default_focus = done

        buttons = [done, cancel]
        return Pile(buttons, focus_item=done)

    def _build_model_inputs(self):
        ifaces = self.model.get_all_interface_names()
        ifname_width = 8  # default padding
        if ifaces:
            ifname_width += len(max(ifaces, key=len))
            if ifname_width > 20:
                ifname_width = 20

        iface_menus = []
        
        # Display each interface -- name in first column, then configured IPs
        # in the second.
        log.debug('interfaces: {}'.format(ifaces))
        for iface in ifaces:
            col_1 = []
            col_2 = []

            col_1.append(
                Color.info_major(
                    menu_btn(label=iface,
                             on_press=self.on_net_dev_press),
                    focus_map='button focus'))

            interface = self.model.get_interface(iface)
            ip_status = {
                'ipv4_addresses': interface.ipv4_addresses,
                'ipv6_addresses': interface.ipv6_addresses,
                'dhcp4_addresses': interface.dhcp4_addresses,
                'dhcp6_addresses': interface.dhcp6_addresses,
                'dhcp4': interface.dhcp4,
                'dhcp6': interface.dhcp6,
            }

            for addr in ip_status['dhcp4_addresses']:
                template = '{} (dhcp)'.format(addr[0])
                col_1.append(Text("")) 
                col_2.append(Color.info_primary(Text(template)))

            for addr in ip_status['ipv4_addresses']:
                template = '{} (manual)'.format(addr)
                col_1.append(Text("")) 
                col_2.append(Color.info_primary(Text(template)))

            for addr in ip_status['dhcp6_addresses']:
                template = '{} (dhcp)'.format(addr[0])
                col_1.append(Text("")) 
                col_2.append(Color.info_primary(Text(template)))

            for addr in ip_status['ipv6_addresses']:
                template = '{} (manual)'.format(addr)
                col_1.append(Text("")) 
                col_2.append(Color.info_primary(Text(template)))

            template = None
            if ( not ip_status['dhcp4'] and not ip_status['dhcp6'] ) \
                    and len(ip_status['ipv4_addresses']) == 0 and \
                    len(ip_status['ipv6_addresses']) == 0:
                if interface.type == 'eth':
                    if interface.is_connected():
                        template = "Not configured"
                    else:
                        template = "Not connected"
                else:
                    template = "Not configured"

            if ip_status['dhcp4'] and ip_status['dhcp6'] and \
                    len(ip_status['ipv4_addresses']) == 0 and \
                    len(ip_status['dhcp4_addresses']) == 0 and \
                    len(ip_status['ipv6_addresses']) == 0 and \
                    len(ip_status['dhcp6_addresses']) == 0:
                template = "DHCP is enabled"
            elif ip_status['dhcp4'] and \
                    len(ip_status['ipv4_addresses']) == 0 and \
                    len(ip_status['dhcp4_addresses']) == 0:
                template = "DHCPv4 is enabled"
            elif ip_status['dhcp6'] and \
                    len(ip_status['ipv6_addresses']) == 0 and \
                    len(ip_status['dhcp6_addresses']) == 0:
                template = "DHCPv6 is enabled"

            if template is not None:
                col_1.append(Text("")) 
                col_2.append(Color.info_primary(Text(template)))

            if interface.iftype == 'wlan':
                if interface.essid is not None:
                    col_2.append(Text("Associated to '" + interface.essid + "'"))
                else:
                    col_2.append(Text("Not associated."))

            # Other device info (MAC, vendor/model, speed)
            info = self.model.get_iface_info(iface)
            hwaddr = self.model.get_hw_addr(iface)
            log.debug('iface info:{}'.format(info))
            template = ''
            if hwaddr:
                template += '{} '.format(hwaddr)
            if info['bond_slave']:
                template += '(Bonded) '
            if not info['vendor'].lower().startswith('unknown'):
                vendor = textwrap.wrap(info['vendor'], 15)[0]
                template += '{} '.format(vendor)
            if not info['model'].lower().startswith('unknown'):
                model = textwrap.wrap(info['model'], 20)[0]
                template += '{} '.format(model)
            if info['speed']:
                template += '({speed})'.format(**info)
            #log.debug('template: {}', template)
            log.debug('hwaddr:{}, {}'.format(hwaddr, template))

            col_2.append(Color.info_minor(Text(template)))
            iface_menus.append(Columns([(ifname_width, Pile(col_1)), Pile(col_2)], 2))

        return Pile(iface_menus)

    def _build_additional_options(self):
        labels = []
        ifaces = self.model.get_all_interface_names()

        # Display default route status
        if self.model.default_v4_gateway is not None:
            v4_route_source = "via " + self.model.default_v4_gateway

            default_v4_route_w = Color.info_minor(
                Text("  IPv4 default route " + v4_route_source + "."))
            labels.append(default_v4_route_w)
            
        if self.model.default_v6_gateway is not None:
            v6_route_source = "via " + self.model.default_v6_gateway

            default_v6_route_w = Color.info_minor(
                Text("  IPv6 default route " + v6_route_source + "."))
            labels.append(default_v6_route_w)

        max_btn_len = 0
        buttons = []
        for opt, sig in self.model.get_menu():
            if ':set-default-route' in sig:
                if len(ifaces) < 2:
                    log.debug('Skipping default route menu option'
                              ' (only one nic)')
                    continue
            if ':bond-interfaces' in sig:
                not_bonded = [iface for iface in ifaces
                              if not self.model.iface_is_bonded(iface)]
                if len(not_bonded) < 2:
                    log.debug('Skipping bonding menu option'
                              ' (not enough available nics)')
                    continue

            if len(opt) > max_btn_len:
                max_btn_len = len(opt)

            buttons.append(
                Color.menu_button(
                    menu_btn(label=opt,
                             on_press=self.additional_menu_select,
                             user_data=sig),
                    focus_map='button focus'))

        padding = getattr(Padding, 'left_{}'.format(max_btn_len + 10))
        buttons = [ padding(button) for button in buttons ]
        return Pile(labels + buttons)

    def additional_menu_select(self, result, sig):
        self.signal.emit_signal(sig)

    def on_net_dev_press(self, result):
        log.debug("Selected network dev: {}".format(result.label))
        self.signal.emit_signal('menu:network:main:configure-interface',
                                result.label)

    def show_network_error(self, action):
        if action == 'generate':
            self.error.set_text("Network configuration failed; " + \
                                "please verify your settings.")
        elif action == 'apply':
            self.error.set_text("Network configuration could not be applied; " + \
                                "please verify your settings.")
        elif action == 'timeout':
            self.error.set_text("Network configuration timed out; " + \
                                "please verify your settings.")
        elif action == 'canceled':
            self.error.set_text("Network configuration canceled.")
        else:
            self.error.set_text("An unexpected error has occurred; " + \
                                "please verify your settings.")

    def done(self, result):
        self.signal.emit_signal('network:finish', self.model.render())

    def cancel(self, button):
        self.signal.emit_signal('prev-screen')