Created
September 25, 2017 23:08
-
-
Save geier/e3860942549bbb1d1b42b07582150381 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# coding: utf-8 | |
# Copyright (C) 2013 Patrick Totzke <[email protected]> | |
# This file is released under the GNU GPL, version 3 or a later revision. | |
# | |
# adapted from example1.py and example3.collapse.py from the urwidtrees examples | |
from urwidtrees.tree import SimpleTree | |
from urwidtrees.decoration import CollapseIconMixin, DecoratedTree | |
from urwidtrees.nested import NestedTree | |
from urwidtrees.widgets import TreeBox | |
import urwid | |
import re | |
palette = [ | |
('body', 'black', 'light gray'), | |
('focus', 'light gray', 'dark blue', 'standout'), | |
('bars', 'dark blue', 'light gray', ''), | |
('arrowtip', 'light blue', 'light gray', ''), | |
('connectors', 'light red', 'light gray', ''), | |
] | |
class CollapsibleIconTree(CollapseIconMixin, DecoratedTree): | |
""" | |
Indent collapsible tree nodes according to their depth in the tree and | |
display icons indicating collapse-status in the gaps. | |
""" | |
def __init__(self, walker, icon_offset=1, **kwargs): | |
""" | |
:param walker: tree of widgets to be displayed | |
:type walker: Tree | |
:param indent: indentation width | |
:type indent: int | |
:param icon_offset: distance from icon to the eginning of the tree | |
node. | |
:type icon_offset: int | |
""" | |
self._icon_offset = icon_offset | |
DecoratedTree.__init__(self, walker) | |
CollapseIconMixin.__init__(self, | |
is_collapsed=lambda _: False, | |
icon_focussed_att='focus', | |
icon_frame_left_char=None, | |
icon_frame_right_char=None, | |
icon_expanded_char='▼', | |
icon_collapsed_char='▶', | |
icon_offset=0, | |
) | |
def decorate(self, pos, widget, is_first=True): | |
iwidth, icon = self._construct_collapse_icon(pos) | |
cols = [] | |
void = urwid.SolidFill(' ') | |
line = None | |
# add icon only for non-leafs | |
is_leaf = self._tree.is_leaf(pos) | |
if not is_leaf: | |
if icon is not None: | |
# space to the left | |
cols.append((1, urwid.SolidFill(' '))) | |
# icon | |
icon_pile = urwid.Pile([void, ('pack', icon)]) | |
cols.append((iwidth, icon_pile)) | |
cols.append((self._icon_offset, urwid.SolidFill(' '))) | |
else: # otherwise just add another spacer | |
cols.append((4, urwid.SolidFill(' '))) | |
cols.append(widget) # original widget ] | |
# construct a Columns, defining all spacer as Box widgets | |
line = urwid.Columns(cols, box_columns=range(len(cols))[:-1]) | |
return line | |
class FocusableText(urwid.WidgetWrap): | |
"""Selectable Text used for nodes in our example""" | |
def __init__(self, txt, att, att_focus): | |
t = urwid.Text(txt) | |
w = urwid.AttrMap(t, att, att_focus) | |
urwid.WidgetWrap.__init__(self, w) | |
def selectable(self): | |
return True | |
def keypress(self, size, key): | |
return key | |
@property | |
def text(self): | |
return self._wrapped_widget._original_widget.text | |
class TextlinesList(SimpleTree): | |
def __init__(self, content, attr=None, attr_focus=None): | |
""" | |
:class:`SimpleTree` that contains a list of all-level-0 Text widgets | |
for each line in content. | |
""" | |
structure = [] | |
# depending on this config setting, we either add individual lines | |
# or the complete context as focusable objects. | |
for line in content.splitlines(): | |
structure.append((FocusableText(line, attr, attr_focus), None)) | |
SimpleTree.__init__(self, structure) | |
def selectable(self): | |
return True | |
mail = [ | |
"No problem. 6pm it is then. --Jim", | |
"", | |
"At 10.01am Wednesday, Danny wrote:", | |
"> Whoa! I need to email a report at 5:30.", | |
"> Could you push it back an hour? --Danny", | |
">", | |
"> At 9.40am Wednesday, Jim wrote:", | |
">", | |
">> I'm going to suspend the mail service for approx. thirty", | |
">> minutes tonight, starting at 5pm. --Jim", | |
">>", | |
">", | |
"> On the 16th, Alice wrote:", | |
">> Hello,", | |
">> how are you?", | |
">> I'm good.", | |
">> Bye", | |
">", | |
"", | |
"-- ", | |
"Jim Doe", | |
"Chief of Nothing", | |
"Nothing Inc.", | |
"", | |
"", | |
"", | |
"------------------------------------------------------", | |
"something mailing list", | |
"[email protected]", | |
"http://mail.list.org", | |
] | |
# (?: ) is a non-capturing group | |
reg = re.compile(r'^((?:> ?)+)?(.*)') | |
def get_reply_levels(lines): | |
for num, line in enumerate(lines): | |
groups = reg.match(line).groups() | |
indendation = groups[0] or '' | |
level = indendation.count('>') | |
yield level | |
def build_tree(): | |
levels = list(get_reply_levels(mail)) | |
oldlevel = last = 0 | |
subtrees = [] | |
for num, level in enumerate(levels): | |
while level > oldlevel: | |
new = TextlinesList('\n'.join(mail[last:num]), 'body', 'focus') | |
#new = FocusableText('\n'.join(mail[last:num]), 'body', 'focus') | |
subtrees.append(new) | |
last = num | |
oldlevel += 1 | |
new = TextlinesList('\n'.join(mail[last:num]), 'body', 'focus') | |
subtrees.append(new) | |
last_tree = None | |
for subtree in subtrees[::-1]: | |
last_tree = [(subtree, last_tree)] | |
return CollapsibleIconTree(last_tree) | |
def unhandled_input(key): | |
if isinstance(key, str) and key.lower() == 'q': | |
raise urwid.ExitMainLoop() | |
if __name__ == "__main__": | |
tree = CollapsibleIconTree(build_tree()) | |
treebox = TreeBox(NestedTree(tree)) | |
rootwidget = urwid.AttrMap(treebox, 'body') | |
footer = urwid.AttrMap(urwid.Text('Q to quit'), 'focus') | |
urwid.MainLoop( | |
urwid.Frame(rootwidget, footer=footer), | |
palette, | |
unhandled_input=unhandled_input, | |
).run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment