Skip to content

Commit

Permalink
Merge pull request #1264 from pazz/refactor-threadline
Browse files Browse the repository at this point in the history
refactor threadline widget
  • Loading branch information
dcbaker committed Jul 24, 2018
2 parents 62b7781 + 51f109a commit bf891ce
Showing 1 changed file with 169 additions and 130 deletions.
299 changes: 169 additions & 130 deletions alot/widgets/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,136 +28,40 @@ def __init__(self, tid, dbman):
focussed = self.structure['focus']
urwid.AttrMap.__init__(self, self.columns, normal, focussed)

def _build_part(self, name, struct, minw, maxw, align):
def pad(string, shorten=None):
if maxw:
if len(string) > maxw:
if shorten:
string = shorten(string, maxw)
else:
string = string[:maxw]
if minw:
if len(string) < minw:
if align == 'left':
string = string.ljust(minw)
elif align == 'center':
string = string.center(minw)
else:
string = string.rjust(minw)
return string

part = None
width = None
if name == 'date':
newest = None
datestring = ''
if self.thread:
newest = self.thread.get_newest_date()
if newest is not None:
datestring = settings.represent_datetime(newest)
datestring = pad(datestring)
width = len(datestring)
part = AttrFlipWidget(urwid.Text(datestring), struct['date'])

elif name == 'mailcount':
if self.thread:
mailcountstring = "(%d)" % self.thread.get_total_messages()
else:
mailcountstring = "(?)"
mailcountstring = pad(mailcountstring)
width = len(mailcountstring)
mailcount_w = AttrFlipWidget(urwid.Text(mailcountstring),
struct['mailcount'])
part = mailcount_w
elif name == 'authors':
if self.thread:
authors = self.thread.get_authors_string() or '(None)'
else:
authors = '(None)'
authorsstring = pad(authors, shorten_author_string)
authors_w = AttrFlipWidget(urwid.Text(authorsstring),
struct['authors'])
width = len(authorsstring)
part = authors_w

elif name == 'subject':
if self.thread:
subjectstring = self.thread.get_subject() or ' '
else:
subjectstring = ' '
# sanitize subject string:
subjectstring = subjectstring.replace('\n', ' ')
subjectstring = subjectstring.replace('\r', '')
subjectstring = pad(subjectstring)

subject_w = AttrFlipWidget(urwid.Text(subjectstring, wrap='clip'),
struct['subject'])
if subjectstring:
width = len(subjectstring)
part = subject_w

elif name == 'content':
if self.thread:
msgs = self.thread.get_messages().keys()
else:
msgs = []
# sort the most recent messages first
msgs.sort(key=lambda msg: msg.get_date(), reverse=True)
lastcontent = ' '.join([m.get_text_content() for m in msgs])
contentstring = pad(lastcontent.replace('\n', ' ').strip())
content_w = AttrFlipWidget(urwid.Text(contentstring, wrap='clip'),
struct['content'])
width = len(contentstring)
part = content_w
elif name == 'tags':
if self.thread:
fallback_normal = struct[name]['normal']
fallback_focus = struct[name]['focus']
tag_widgets = sorted(
TagWidget(t, fallback_normal, fallback_focus)
for t in self.thread.get_tags())
else:
tag_widgets = []
cols = []
length = -1
for tag_widget in tag_widgets:
if not tag_widget.hidden:
wrapped_tagwidget = tag_widget
tag_width = tag_widget.width()
cols.append(('fixed', tag_width, wrapped_tagwidget))
length += tag_width + 1
if cols:
part = urwid.Columns(cols, dividechars=1)
width = length
return width, part

def rebuild(self):
self.thread = self.dbman.get_thread(self.tid)
self.widgets = []
columns = []
self.structure = settings.get_threadline_theming(self.thread)
for partname in self.structure['parts']:
minw = maxw = None

columns = []

# combine width info and widget into an urwid.Column entry
def add_column(width, part):
width_tuple = self.structure[partname]['width']
if width_tuple is not None:
if width_tuple[0] == 'fit':
minw, maxw = width_tuple[1:]
align_mode = self.structure[partname]['alignment']
width, part = self._build_part(partname, self.structure,
minw, maxw, align_mode)
if part is not None:
if isinstance(part, urwid.Columns):
if width_tuple[0] == 'weight':
columnentry = width_tuple + (part,)
else:
columnentry = ('fixed', width, part)
columns.append(columnentry)

# create a column for every part of the threadline
for partname in self.structure['parts']:
# build widget(s) around this part's content and remember them so
# that self.render() may change local attributes.
if partname == 'tags':
width, part = build_tags_part(self.thread.get_tags(),
self.structure['tags']['normal'],
self.structure['tags']['focus'])
if part:
add_column(width, part)
for w in part.widget_list:
self.widgets.append(w)
else:
self.widgets.append(part)

# compute width and align
if width_tuple[0] == 'weight':
columnentry = width_tuple + (part,)
else:
columnentry = ('fixed', width, part)
columns.append(columnentry)
else:
width, part = build_text_part(partname, self.thread,
self.structure[partname])
add_column(width, part)
self.widgets.append(part)

self.columns = urwid.Columns(columns, dividechars=1)
self.original_widget = self.columns

Expand All @@ -175,11 +79,146 @@ def keypress(self, size, key):
def get_thread(self):
return self.thread

@staticmethod
def _get_theme(component, focus=False):
path = ['search', 'threadline', component]
if focus:
path.append('focus')

def build_tags_part(tags, attr_normal, attr_focus):
"""
create an urwid.Columns widget (wrapped in approproate Attributes)
to display a list of tag strings, as part of a threadline.
:param tags: list of tag strings to include
:type tags: list of str
:param attr_normal: urwid attribute to use if unfocussed
:param attr_focus: urwid attribute to use if focussed
:return: overall width in characters and a Columns widget.
:rtype: tuple[int, urwid.Columns]
"""
part_w = None
width = None
tag_widgets = []
cols = []
width = -1

# create individual TagWidgets and sort them
tag_widgets = [TagWidget(t, attr_normal, attr_focus) for t in tags]
tag_widgets = sorted(tag_widgets)

for tag_widget in tag_widgets:
if not tag_widget.hidden:
wrapped_tagwidget = tag_widget
tag_width = tag_widget.width()
cols.append(('fixed', tag_width, wrapped_tagwidget))
width += tag_width + 1
if cols:
part_w = urwid.Columns(cols, dividechars=1)
return width, part_w


def build_text_part(name, thread, struct):
"""
create an urwid.Text widget (wrapped in approproate Attributes)
to display a plain text parts in a threadline.
create an urwid.Columns widget (wrapped in approproate Attributes)
to display a list of tag strings, as part of a threadline.
:param name: id of part to build
:type name: str
:param thread: the thread to get local info for
:type thread: :class:`alot.db.thread.Thread`
:param struct: theming attributes for this part, as provided by
:class:`alot.settings.theme.Theme.get_threadline_theming`
:type struct: dict
:return: overall width (in characters) and a widget.
:rtype: tuple[int, AttrFliwWidget]
"""

part_w = None
width = None

# extract min and max allowed width from theme
minw = 0
maxw = None
width_tuple = struct['width']
if width_tuple is not None:
if width_tuple[0] == 'fit':
minw, maxw = width_tuple[1:]

content = prepare_string(name, thread, maxw)

# pad content if not long enough
if minw:
alignment = struct['alignment']
if alignment == 'left':
content = content.ljust(minw)
elif alignment == 'center':
content = content.center(minw)
else:
content = content.rjust(minw)

# define width and part_w
text = urwid.Text(content, wrap='clip')
width = text.pack()[0]
part_w = AttrFlipWidget(text, struct)

return width, part_w


def prepare_date_string(thread):
newest = None
newest = thread.get_newest_date()
if newest is not None:
datestring = settings.represent_datetime(newest)
return datestring


def prepare_mailcount_string(thread):
return "(%d)" % thread.get_total_messages()


def prepare_authors_string(thread):
return thread.get_authors_string() or '(None)'


def prepare_subject_string(thread):
return thread.get_subject() or ' '


def prepare_content_string(thread):
msgs = sorted(thread.get_messages().keys(),
key=lambda msg: msg.get_date(), reverse=True)
lastcontent = ' '.join(m.get_text_content() for m in msgs)
return lastcontent


def prepare_string(partname, thread, maxw):
"""
extract a content string for part 'partname' from 'thread' of maximal
length 'maxw'.
"""
# map part names to function extracting content string and custom shortener
prep = {
'mailcount': (prepare_mailcount_string, None),
'date': (prepare_date_string, None),
'authors': (prepare_authors_string, shorten_author_string),
'subject': (prepare_subject_string, None),
'content': (prepare_content_string, None),
}

s = ' ' # fallback value
if thread:
# get extractor and shortener
content, shortener = prep[partname]

# get string
s = content(thread)

# sanitize
s = s.replace('\n', ' ')
s = s.replace('\r', '')

# shorten if max width is requested
if maxw:
if len(s) > maxw and shortener:
s = shortener(s, maxw)
else:
path.append('normal')
return settings.get_theming_attribute(path)
s = s[:maxw]
return s

0 comments on commit bf891ce

Please sign in to comment.