Example #1
0
    def rebuild(self):
        try:
            self.thread.refresh()
        except NonexistantObjectError:
            self.body = urwid.SolidFill()
            self.message_count = 0
            return

        self._tree = ThreadTree(self.thread)

        bars_att = settings.get_theming_attribute('thread', 'arrow_bars')
        heads_att = settings.get_theming_attribute('thread', 'arrow_heads')
        A = ArrowTree(self._tree,
                      indent=2,
                      childbar_offset=0,
                      arrow_tip_att=heads_att,
                      arrow_att=bars_att,
                      )
        self._nested_tree = NestedTree(A, interpret_covered=True)
        self.body = TreeBox(self._nested_tree)
        self.message_count = self.thread.get_total_messages()
Example #2
0
    def rebuild(self):
        try:
            self.thread.refresh()
        except NonexistantObjectError:
            self.body = urwid.SolidFill()
            self.message_count = 0
            return

        self._tree = ThreadTree(self.thread)

        bars_att = settings.get_theming_attribute('thread', 'arrow_bars')
        heads_att = settings.get_theming_attribute('thread', 'arrow_heads')
        A = ArrowTree(self._tree,
                      indent=2,
                      childbar_offset=0,
                      arrow_tip_att=heads_att,
                      arrow_att=bars_att,
                      )
        self._nested_tree = NestedTree(A, interpret_covered=True)
        self.body = TreeBox(self._nested_tree)
        self.message_count = self.thread.get_total_messages()
Example #3
0
class ThreadBuffer(Buffer):
    """displays a thread as a tree of messages"""

    modename = 'thread'

    def __init__(self, ui, thread):
        """
        :param ui: main UI
        :type ui: :class:`~alot.ui.UI`
        :param thread: thread to display
        :type thread: :class:`~alot.db.Thread`
        """
        self.message_count = thread.get_total_messages()
        self.thread = thread
        self.rebuild()
        Buffer.__init__(self, ui, self.body)

    def __str__(self):
        return '[thread] %s (%d message%s)' % (self.thread.get_subject(),
                                               self.message_count,
                                               's' * (self.message_count > 1))

    def get_info(self):
        info = {}
        info['subject'] = self.thread.get_subject()
        info['authors'] = self.thread.get_authors_string()
        info['tid'] = self.thread.get_thread_id()
        info['message_count'] = self.message_count
        return info

    def get_selected_thread(self):
        """returns the displayed :class:`~alot.db.Thread`"""
        return self.thread

    def rebuild(self):
        try:
            self.thread.refresh()
        except NonexistantObjectError:
            self.body = urwid.SolidFill()
            self.message_count = 0
            return

        self._tree = ThreadTree(self.thread)

        bars_att = settings.get_theming_attribute('thread', 'arrow_bars')
        heads_att = settings.get_theming_attribute('thread', 'arrow_heads')
        A = ArrowTree(self._tree,
                      indent=2,
                      childbar_offset=0,
                      arrow_tip_att=heads_att,
                      arrow_att=bars_att,
                      )
        self._nested_tree = NestedTree(A, interpret_covered=True)
        self.body = TreeBox(self._nested_tree)
        self.message_count = self.thread.get_total_messages()

    def get_selected_mid(self):
        """returns Message ID of focussed message"""
        return self.body.get_focus()[1][0]

    def get_selected_message_position(self):
        """returns position of focussed message in the thread tree"""
        return self._sanitize_position((self.get_selected_mid(),))

    def get_selected_messagetree(self):
        """returns currently focussed :class:`MessageTree`"""
        return self._nested_tree[self.body.get_focus()[1][:1]]

    def get_selected_message(self):
        """returns focussed :class:`~alot.db.message.Message`"""
        return self.get_selected_messagetree()._message

    def get_messagetree_positions(self):
        """
        returns a Generator to walk through all positions of
        :class:`MessageTree` in the :class:`ThreadTree` of this buffer.
        """
        return [(pos,) for pos in self._tree.positions()]

    def messagetrees(self):
        """
        returns a Generator of all :class:`MessageTree` in the
        :class:`ThreadTree` of this buffer.
        """
        for pos in self._tree.positions():
            yield self._tree[pos]

    def refresh(self):
        """refresh and flushe caches of Thread tree"""
        self.body.refresh()

    # needed for ui.get_deep_focus..
    def get_focus(self):
        return self.body.get_focus()

    def set_focus(self, pos):
        logging.debug('setting focus to %s ' % str(pos))
        self.body.set_focus(pos)

    def focus_first(self):
        """set focus to first message of thread"""
        self.body.set_focus(self._nested_tree.root)

    def _sanitize_position(self, pos):
        return self._nested_tree._sanitize_position(pos,
                                                    self._nested_tree._tree)

    def focus_selected_message(self):
        """focus the summary line of currently focussed message"""
        # move focus to summary (root of current MessageTree)
        self.set_focus(self.get_selected_message_position())

    def focus_parent(self):
        """move focus to parent of currently focussed message"""
        mid = self.get_selected_mid()
        newpos = self._tree.parent_position(mid)
        if newpos is not None:
            newpos = self._sanitize_position((newpos,))
            self.body.set_focus(newpos)

    def focus_first_reply(self):
        """move focus to first reply to currently focussed message"""
        mid = self.get_selected_mid()
        newpos = self._tree.first_child_position(mid)
        if newpos is not None:
            newpos = self._sanitize_position((newpos,))
            self.body.set_focus(newpos)

    def focus_last_reply(self):
        """move focus to last reply to currently focussed message"""
        mid = self.get_selected_mid()
        newpos = self._tree.last_child_position(mid)
        if newpos is not None:
            newpos = self._sanitize_position((newpos,))
            self.body.set_focus(newpos)

    def focus_next_sibling(self):
        """focus next sibling of currently focussed message in thread tree"""
        mid = self.get_selected_mid()
        newpos = self._tree.next_sibling_position(mid)
        if newpos is not None:
            newpos = self._sanitize_position((newpos,))
            self.body.set_focus(newpos)

    def focus_prev_sibling(self):
        """
        focus previous sibling of currently focussed message in thread tree
        """
        mid = self.get_selected_mid()
        localroot = self._sanitize_position((mid,))
        if localroot == self.get_focus()[1]:
            newpos = self._tree.prev_sibling_position(mid)
            if newpos is not None:
                newpos = self._sanitize_position((newpos,))
        else:
            newpos = localroot
        if newpos is not None:
            self.body.set_focus(newpos)

    def focus_next(self):
        """focus next message in depth first order"""
        mid = self.get_selected_mid()
        newpos = self._tree.next_position(mid)
        if newpos is not None:
            newpos = self._sanitize_position((newpos,))
            self.body.set_focus(newpos)

    def focus_prev(self):
        """focus previous message in depth first order"""
        mid = self.get_selected_mid()
        localroot = self._sanitize_position((mid,))
        if localroot == self.get_focus()[1]:
            newpos = self._tree.prev_position(mid)
            if newpos is not None:
                newpos = self._sanitize_position((newpos,))
        else:
            newpos = localroot
        if newpos is not None:
            self.body.set_focus(newpos)

    def expand(self, msgpos):
        """expand message at given position"""
        MT = self._tree[msgpos]
        MT.expand(MT.root)

    def messagetree_at_position(self, pos):
        """get :class:`MessageTree` for given position"""
        return self._tree[pos[0]]

    def expand_and_remove_unread(self, pos):
        messagetree = self.messagetree_at_position(pos)
        msg = messagetree._message
        messagetree.expand(messagetree.root)
        if 'unread' in msg.get_tags():
            msg.remove_tags(['unread'])
            self.ui.apply_command(commands.globals.FlushCommand())

    def expand_all(self):
        """expand all messages in thread"""
        for MT in self.messagetrees():
            MT.expand(MT.root)

    def collapse(self, msgpos):
        """collapse message at given position"""
        MT = self._tree[msgpos]
        MT.collapse(MT.root)
        self.focus_selected_message()

    def collapse_all(self):
        """collapse all messages in thread"""
        for MT in self.messagetrees():
            MT.collapse(MT.root)
        self.focus_selected_message()

    def unfold_matching(self, querystring, focus_first=True):
        """
        expand all messages that match a given querystring.

        :param querystring: query to match
        :type querystring: str
        :param focus_first: set the focus to the first matching message
        :type focus_first: bool
        """
        first = None
        for MT in self.messagetrees():
            msg = MT._message
            if msg.matches(querystring):
                MT.expand(MT.root)
                if first is None:
                    first = (self._tree.position_of_messagetree(MT), MT.root)
                    self.body.set_focus(first)
                if 'unread' in msg.get_tags():
                    msg.remove_tags(['unread'])
                    self.ui.apply_command(commands.globals.FlushCommand())
            else:
                MT.collapse(MT.root)
        self.body.refresh()
Example #4
0
class ThreadBuffer(Buffer):
    """displays a thread as a tree of messages"""

    modename = 'thread'

    def __init__(self, ui, thread):
        """
        :param ui: main UI
        :type ui: :class:`~alot.ui.UI`
        :param thread: thread to display
        :type thread: :class:`~alot.db.Thread`
        """
        self.thread = thread
        self.message_count = thread.get_total_messages()

        # two semaphores for auto-removal of unread tag
        self._auto_unread_dont_touch_mids = set([])
        self._auto_unread_writing = False

        self.rebuild()
        Buffer.__init__(self, ui, self.body)

    def __str__(self):
        return '[thread] %s (%d message%s)' % (self.thread.get_subject(),
                                               self.message_count,
                                               's' * (self.message_count > 1))

    def get_info(self):
        info = {}
        info['subject'] = self.thread.get_subject()
        info['authors'] = self.thread.get_authors_string()
        info['tid'] = self.thread.get_thread_id()
        info['message_count'] = self.message_count
        return info

    def get_selected_thread(self):
        """returns the displayed :class:`~alot.db.Thread`"""
        return self.thread

    def rebuild(self):
        try:
            self.thread.refresh()
        except NonexistantObjectError:
            self.body = urwid.SolidFill()
            self.message_count = 0
            return

        self._tree = ThreadTree(self.thread)

        bars_att = settings.get_theming_attribute('thread', 'arrow_bars')
        heads_att = settings.get_theming_attribute('thread', 'arrow_heads')
        A = ArrowTree(self._tree,
                      indent=2,
                      childbar_offset=0,
                      arrow_tip_att=heads_att,
                      arrow_att=bars_att,
                      )
        self._nested_tree = NestedTree(A, interpret_covered=True)
        self.body = TreeBox(self._nested_tree)
        self.message_count = self.thread.get_total_messages()

    def render(self, size, focus=False):
        if settings.get('auto_remove_unread'):
            logging.debug('Tbuffer: auto remove unread tag from msg?')
            msg = self.get_selected_message()
            mid = msg.get_message_id()
            focus_pos = self.body.get_focus()[1]
            summary_pos = (self.body.get_focus()[1][0], (0,))
            cursor_on_non_summary = (focus_pos != summary_pos)
            if cursor_on_non_summary:
                if mid not in self._auto_unread_dont_touch_mids:
                    if 'unread' in msg.get_tags():
                        logging.debug('Tbuffer: removing unread')

                        def clear():
                            self._auto_unread_writing = False

                        self._auto_unread_dont_touch_mids.add(mid)
                        self._auto_unread_writing = True
                        msg.remove_tags(['unread'], afterwards=clear)
                        fcmd = commands.globals.FlushCommand(silent=True)
                        self.ui.apply_command(fcmd)
                    else:
                        logging.debug('Tbuffer: No, msg not unread')
                else:
                    logging.debug('Tbuffer: No, mid locked for autorm-unread')
            else:
                if not self._auto_unread_writing and \
                   mid in self._auto_unread_dont_touch_mids:
                    self._auto_unread_dont_touch_mids.remove(mid)
                logging.debug('Tbuffer: No, cursor on summary')
        return self.body.render(size, focus)

    def get_selected_mid(self):
        """returns Message ID of focussed message"""
        return self.body.get_focus()[1][0]

    def get_selected_message_position(self):
        """returns position of focussed message in the thread tree"""
        return self._sanitize_position((self.get_selected_mid(),))

    def get_selected_messagetree(self):
        """returns currently focussed :class:`MessageTree`"""
        return self._nested_tree[self.body.get_focus()[1][:1]]

    def get_selected_message(self):
        """returns focussed :class:`~alot.db.message.Message`"""
        return self.get_selected_messagetree()._message

    def get_messagetree_positions(self):
        """
        returns a Generator to walk through all positions of
        :class:`MessageTree` in the :class:`ThreadTree` of this buffer.
        """
        return [(pos,) for pos in self._tree.positions()]

    def messagetrees(self):
        """
        returns a Generator of all :class:`MessageTree` in the
        :class:`ThreadTree` of this buffer.
        """
        for pos in self._tree.positions():
            yield self._tree[pos]

    def refresh(self):
        """refresh and flushe caches of Thread tree"""
        self.body.refresh()

    # needed for ui.get_deep_focus..
    def get_focus(self):
        return self.body.get_focus()

    def set_focus(self, pos):
        logging.debug('setting focus to %s ' % str(pos))
        self.body.set_focus(pos)

    def focus_first(self):
        """set focus to first message of thread"""
        self.body.set_focus(self._nested_tree.root)

    def focus_last(self):
        self.body.set_focus(next(self._nested_tree.positions(reverse=True)))

    def _sanitize_position(self, pos):
        return self._nested_tree._sanitize_position(pos,
                                                    self._nested_tree._tree)

    def focus_selected_message(self):
        """focus the summary line of currently focussed message"""
        # move focus to summary (root of current MessageTree)
        self.set_focus(self.get_selected_message_position())

    def focus_parent(self):
        """move focus to parent of currently focussed message"""
        mid = self.get_selected_mid()
        newpos = self._tree.parent_position(mid)
        if newpos is not None:
            newpos = self._sanitize_position((newpos,))
            self.body.set_focus(newpos)

    def focus_first_reply(self):
        """move focus to first reply to currently focussed message"""
        mid = self.get_selected_mid()
        newpos = self._tree.first_child_position(mid)
        if newpos is not None:
            newpos = self._sanitize_position((newpos,))
            self.body.set_focus(newpos)

    def focus_last_reply(self):
        """move focus to last reply to currently focussed message"""
        mid = self.get_selected_mid()
        newpos = self._tree.last_child_position(mid)
        if newpos is not None:
            newpos = self._sanitize_position((newpos,))
            self.body.set_focus(newpos)

    def focus_next_sibling(self):
        """focus next sibling of currently focussed message in thread tree"""
        mid = self.get_selected_mid()
        newpos = self._tree.next_sibling_position(mid)
        if newpos is not None:
            newpos = self._sanitize_position((newpos,))
            self.body.set_focus(newpos)

    def focus_prev_sibling(self):
        """
        focus previous sibling of currently focussed message in thread tree
        """
        mid = self.get_selected_mid()
        localroot = self._sanitize_position((mid,))
        if localroot == self.get_focus()[1]:
            newpos = self._tree.prev_sibling_position(mid)
            if newpos is not None:
                newpos = self._sanitize_position((newpos,))
        else:
            newpos = localroot
        if newpos is not None:
            self.body.set_focus(newpos)

    def focus_next(self):
        """focus next message in depth first order"""
        mid = self.get_selected_mid()
        newpos = self._tree.next_position(mid)
        if newpos is not None:
            newpos = self._sanitize_position((newpos,))
            self.body.set_focus(newpos)

    def focus_prev(self):
        """focus previous message in depth first order"""
        mid = self.get_selected_mid()
        localroot = self._sanitize_position((mid,))
        if localroot == self.get_focus()[1]:
            newpos = self._tree.prev_position(mid)
            if newpos is not None:
                newpos = self._sanitize_position((newpos,))
        else:
            newpos = localroot
        if newpos is not None:
            self.body.set_focus(newpos)

    def focus_property(self, prop, direction):
        """does a walk in the given direction and focuses the
        first message tree that matches the given property"""
        newpos = self.get_selected_mid()
        newpos = direction(newpos)
        while newpos is not None:
            MT = self._tree[newpos]
            if prop(MT):
                newpos = self._sanitize_position((newpos,))
                self.body.set_focus(newpos)
                break
            newpos = direction(newpos)

    def focus_next_matching(self, querystring):
        """focus next matching message in depth first order"""
        self.focus_property(lambda x: x._message.matches(querystring),
                            self._tree.next_position)

    def focus_prev_matching(self, querystring):
        """focus previous matching message in depth first order"""
        self.focus_property(lambda x: x._message.matches(querystring),
                            self._tree.prev_position)

    def focus_next_unfolded(self):
        """focus next unfolded message in depth first order"""
        self.focus_property(lambda x: not x.is_collapsed(x.root),
                            self._tree.next_position)

    def focus_prev_unfolded(self):
        """focus previous unfolded message in depth first order"""
        self.focus_property(lambda x: not x.is_collapsed(x.root),
                            self._tree.prev_position)

    def expand(self, msgpos):
        """expand message at given position"""
        MT = self._tree[msgpos]
        MT.expand(MT.root)

    def messagetree_at_position(self, pos):
        """get :class:`MessageTree` for given position"""
        return self._tree[pos[0]]

    def expand_all(self):
        """expand all messages in thread"""
        for MT in self.messagetrees():
            MT.expand(MT.root)

    def collapse(self, msgpos):
        """collapse message at given position"""
        MT = self._tree[msgpos]
        MT.collapse(MT.root)
        self.focus_selected_message()

    def collapse_all(self):
        """collapse all messages in thread"""
        for MT in self.messagetrees():
            MT.collapse(MT.root)
        self.focus_selected_message()

    def unfold_matching(self, querystring, focus_first=True):
        """
        expand all messages that match a given querystring.

        :param querystring: query to match
        :type querystring: str
        :param focus_first: set the focus to the first matching message
        :type focus_first: bool
        """
        first = None
        for MT in self.messagetrees():
            msg = MT._message
            if msg.matches(querystring):
                MT.expand(MT.root)
                if first is None:
                    first = (self._tree.position_of_messagetree(MT), MT.root)
                    self.body.set_focus(first)
            else:
                MT.collapse(MT.root)
        self.body.refresh()
Example #5
0
class ThreadBuffer(Buffer):
    """displays a thread as a tree of messages"""

    modename = 'thread'

    def __init__(self, ui, thread):
        """
        :param ui: main UI
        :type ui: :class:`~alot.ui.UI`
        :param thread: thread to display
        :type thread: :class:`~alot.db.Thread`
        """
        self.thread = thread
        self.message_count = thread.get_total_messages()

        # two semaphores for auto-removal of unread tag
        self._auto_unread_dont_touch_mids = set([])
        self._auto_unread_writing = False

        self.rebuild()
        Buffer.__init__(self, ui, self.body)

    def __str__(self):
        return '[thread] %s (%d message%s)' % (self.thread.get_subject(),
                                               self.message_count, 's' *
                                               (self.message_count > 1))

    def get_info(self):
        info = {}
        info['subject'] = self.thread.get_subject()
        info['authors'] = self.thread.get_authors_string()
        info['tid'] = self.thread.get_thread_id()
        info['message_count'] = self.message_count
        return info

    def get_selected_thread(self):
        """returns the displayed :class:`~alot.db.Thread`"""
        return self.thread

    def rebuild(self):
        try:
            self.thread.refresh()
        except NonexistantObjectError:
            self.body = urwid.SolidFill()
            self.message_count = 0
            return

        self._tree = ThreadTree(self.thread)

        bars_att = settings.get_theming_attribute('thread', 'arrow_bars')
        heads_att = settings.get_theming_attribute('thread', 'arrow_heads')
        A = ArrowTree(
            self._tree,
            indent=2,
            childbar_offset=0,
            arrow_tip_att=heads_att,
            arrow_att=bars_att,
        )
        self._nested_tree = NestedTree(A, interpret_covered=True)
        self.body = TreeBox(self._nested_tree)
        self.message_count = self.thread.get_total_messages()

    def render(self, size, focus=False):
        if settings.get('auto_remove_unread'):
            logging.debug('Tbuffer: auto remove unread tag from msg?')
            msg = self.get_selected_message()
            mid = msg.get_message_id()
            focus_pos = self.body.get_focus()[1]
            summary_pos = (self.body.get_focus()[1][0], (0, ))
            cursor_on_non_summary = (focus_pos != summary_pos)
            if cursor_on_non_summary:
                if not mid in self._auto_unread_dont_touch_mids:
                    if 'unread' in msg.get_tags():
                        logging.debug('Tbuffer: removing unread')

                        def clear():
                            self._auto_unread_writing = False

                        self._auto_unread_dont_touch_mids.add(mid)
                        self._auto_unread_writing = True
                        msg.remove_tags(['unread'], afterwards=clear)
                        fcmd = commands.globals.FlushCommand(silent=True)
                        self.ui.apply_command(fcmd)
                    else:
                        logging.debug('Tbuffer: No, msg not unread')
                else:
                    logging.debug('Tbuffer: No, mid locked for autorm-unread')
            else:
                if not self._auto_unread_writing and \
                   mid in self._auto_unread_dont_touch_mids:
                    self._auto_unread_dont_touch_mids.remove(mid)
                logging.debug('Tbuffer: No, cursor on summary')
        return self.body.render(size, focus)

    def get_selected_mid(self):
        """returns Message ID of focussed message"""
        return self.body.get_focus()[1][0]

    def get_selected_message_position(self):
        """returns position of focussed message in the thread tree"""
        return self._sanitize_position((self.get_selected_mid(), ))

    def get_selected_messagetree(self):
        """returns currently focussed :class:`MessageTree`"""
        return self._nested_tree[self.body.get_focus()[1][:1]]

    def get_selected_message(self):
        """returns focussed :class:`~alot.db.message.Message`"""
        return self.get_selected_messagetree()._message

    def get_messagetree_positions(self):
        """
        returns a Generator to walk through all positions of
        :class:`MessageTree` in the :class:`ThreadTree` of this buffer.
        """
        return [(pos, ) for pos in self._tree.positions()]

    def messagetrees(self):
        """
        returns a Generator of all :class:`MessageTree` in the
        :class:`ThreadTree` of this buffer.
        """
        for pos in self._tree.positions():
            yield self._tree[pos]

    def refresh(self):
        """refresh and flushe caches of Thread tree"""
        self.body.refresh()

    # needed for ui.get_deep_focus..
    def get_focus(self):
        return self.body.get_focus()

    def set_focus(self, pos):
        logging.debug('setting focus to %s ' % str(pos))
        self.body.set_focus(pos)

    def focus_first(self):
        """set focus to first message of thread"""
        self.body.set_focus(self._nested_tree.root)

    def focus_last(self):
        self.body.set_focus(next(self._nested_tree.positions(reverse=True)))

    def _sanitize_position(self, pos):
        return self._nested_tree._sanitize_position(pos,
                                                    self._nested_tree._tree)

    def focus_selected_message(self):
        """focus the summary line of currently focussed message"""
        # move focus to summary (root of current MessageTree)
        self.set_focus(self.get_selected_message_position())

    def focus_parent(self):
        """move focus to parent of currently focussed message"""
        mid = self.get_selected_mid()
        newpos = self._tree.parent_position(mid)
        if newpos is not None:
            newpos = self._sanitize_position((newpos, ))
            self.body.set_focus(newpos)

    def focus_first_reply(self):
        """move focus to first reply to currently focussed message"""
        mid = self.get_selected_mid()
        newpos = self._tree.first_child_position(mid)
        if newpos is not None:
            newpos = self._sanitize_position((newpos, ))
            self.body.set_focus(newpos)

    def focus_last_reply(self):
        """move focus to last reply to currently focussed message"""
        mid = self.get_selected_mid()
        newpos = self._tree.last_child_position(mid)
        if newpos is not None:
            newpos = self._sanitize_position((newpos, ))
            self.body.set_focus(newpos)

    def focus_next_sibling(self):
        """focus next sibling of currently focussed message in thread tree"""
        mid = self.get_selected_mid()
        newpos = self._tree.next_sibling_position(mid)
        if newpos is not None:
            newpos = self._sanitize_position((newpos, ))
            self.body.set_focus(newpos)

    def focus_prev_sibling(self):
        """
        focus previous sibling of currently focussed message in thread tree
        """
        mid = self.get_selected_mid()
        localroot = self._sanitize_position((mid, ))
        if localroot == self.get_focus()[1]:
            newpos = self._tree.prev_sibling_position(mid)
            if newpos is not None:
                newpos = self._sanitize_position((newpos, ))
        else:
            newpos = localroot
        if newpos is not None:
            self.body.set_focus(newpos)

    def focus_next(self):
        """focus next message in depth first order"""
        mid = self.get_selected_mid()
        newpos = self._tree.next_position(mid)
        if newpos is not None:
            newpos = self._sanitize_position((newpos, ))
            self.body.set_focus(newpos)

    def focus_prev(self):
        """focus previous message in depth first order"""
        mid = self.get_selected_mid()
        localroot = self._sanitize_position((mid, ))
        if localroot == self.get_focus()[1]:
            newpos = self._tree.prev_position(mid)
            if newpos is not None:
                newpos = self._sanitize_position((newpos, ))
        else:
            newpos = localroot
        if newpos is not None:
            self.body.set_focus(newpos)

    def focus_property(self, prop, direction):
        """does a walk in the given direction and focuses the
        first message tree that matches the given property"""
        newpos = self.get_selected_mid()
        newpos = direction(newpos)
        while newpos is not None:
            MT = self._tree[newpos]
            if prop(MT):
                newpos = self._sanitize_position((newpos, ))
                self.body.set_focus(newpos)
                break
            newpos = direction(newpos)

    def focus_next_matching(self, querystring):
        """focus next matching message in depth first order"""
        self.focus_property(lambda x: x._message.matches(querystring),
                            self._tree.next_position)

    def focus_prev_matching(self, querystring):
        """focus previous matching message in depth first order"""
        self.focus_property(lambda x: x._message.matches(querystring),
                            self._tree.prev_position)

    def focus_next_unfolded(self):
        """focus next unfolded message in depth first order"""
        self.focus_property(lambda x: not x.is_collapsed(x.root),
                            self._tree.next_position)

    def focus_prev_unfolded(self):
        """focus previous unfolded message in depth first order"""
        self.focus_property(lambda x: not x.is_collapsed(x.root),
                            self._tree.prev_position)

    def expand(self, msgpos):
        """expand message at given position"""
        MT = self._tree[msgpos]
        MT.expand(MT.root)

    def messagetree_at_position(self, pos):
        """get :class:`MessageTree` for given position"""
        return self._tree[pos[0]]

    def expand_all(self):
        """expand all messages in thread"""
        for MT in self.messagetrees():
            MT.expand(MT.root)

    def collapse(self, msgpos):
        """collapse message at given position"""
        MT = self._tree[msgpos]
        MT.collapse(MT.root)
        self.focus_selected_message()

    def collapse_all(self):
        """collapse all messages in thread"""
        for MT in self.messagetrees():
            MT.collapse(MT.root)
        self.focus_selected_message()

    def unfold_matching(self, querystring, focus_first=True):
        """
        expand all messages that match a given querystring.

        :param querystring: query to match
        :type querystring: str
        :param focus_first: set the focus to the first matching message
        :type focus_first: bool
        """
        first = None
        for MT in self.messagetrees():
            msg = MT._message
            if msg.matches(querystring):
                MT.expand(MT.root)
                if first is None:
                    first = (self._tree.position_of_messagetree(MT), MT.root)
                    self.body.set_focus(first)
            else:
                MT.collapse(MT.root)
        self.body.refresh()