Skip to content

Instantly share code, notes, and snippets.

@geier
Created September 25, 2017 23:08
Show Gist options
  • Save geier/e3860942549bbb1d1b42b07582150381 to your computer and use it in GitHub Desktop.
Save geier/e3860942549bbb1d1b42b07582150381 to your computer and use it in GitHub Desktop.
#!/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