Merge branch 'master' into themes

# Conflicts:
#	rtv/__main__.py
#	rtv/page.py
#	rtv/submission_page.py
#	rtv/subreddit_page.py
#	rtv/subscription_page.py
#	rtv/terminal.py
#	rtv/theme.py
This commit is contained in:
Michael Lazar
2017-09-10 22:26:06 -04:00
13 changed files with 176 additions and 120 deletions

View File

@@ -193,23 +193,23 @@ class OAuthHelper(object):
# If an exception is raised it will be seen by the thread
# so we don't need to explicitly shutdown() the server
_logger.exception(e)
self.term.show_notification('Browser Error')
self.term.show_notification('Browser Error', style='error')
else:
self.server.shutdown()
finally:
thread.join()
if self.params['error'] == 'access_denied':
self.term.show_notification('Denied access')
self.term.show_notification('Denied access', style='error')
return
elif self.params['error']:
self.term.show_notification('Authentication error')
self.term.show_notification('Authentication error', style='error')
return
elif self.params['state'] is None:
# Something went wrong but it's not clear what happened
return
elif self.params['state'] != state:
self.term.show_notification('UUID mismatch')
self.term.show_notification('UUID mismatch', style='error')
return
with self.term.loader('Logging in'):

View File

@@ -4,7 +4,6 @@ from __future__ import unicode_literals
import os
import sys
import time
import curses
import logging
from functools import wraps
@@ -60,7 +59,7 @@ class Page(object):
def refresh_content(self, order=None, name=None):
raise NotImplementedError
def _draw_item(self, window, data, inverted, highlight):
def _draw_item(self, window, data, inverted):
raise NotImplementedError
def get_selected_item(self):
@@ -467,7 +466,8 @@ class Page(object):
if self.content.order is not None:
order = self.content.order.split('-')[0]
col = text.find(order) - 3
window.chgat(0, col, 3, self.term.attr('order_bar', True))
attr = self.term.theme.get('order_bar', modifier='selected')
window.chgat(0, col, 3, attr)
self._row += 1
@@ -536,12 +536,15 @@ class Page(object):
# Now that the windows are setup, we can take a second pass through
# to draw the content
for index, (win, data, inverted) in enumerate(self._subwindows):
highlight = (index == self.nav.cursor_index)
if highlight:
win.bkgd(str(' '), self.term.attr('@highlight'))
if index == self.nav.cursor_index:
# This lets the theme know to invert the cursor
modifier = 'selected'
else:
win.bkgd(str(' '), self.term.attr('@normal'))
self._draw_item(win, data, inverted, highlight)
modifier = None
with self.term.theme.set_modifier(modifier):
win.bkgd(str(' '), self.term.attr('normal'))
self._draw_item(win, data, inverted)
self._row += win_n_rows

View File

@@ -3,7 +3,6 @@ from __future__ import unicode_literals
import re
import time
import curses
from . import docs
from .content import SubmissionContent, SubredditContent
@@ -264,16 +263,18 @@ class SubmissionPage(Page):
self.clear_input_queue()
def _draw_item(self, win, data, inverted, highlight):
def _draw_item(self, win, data, inverted):
if data['type'] in ('MoreComments', 'HiddenComment'):
self._draw_more_comments(win, data, highlight)
if data['type'] == 'MoreComments':
return self._draw_more_comments(win, data)
elif data['type'] == 'HiddenComment':
return self._draw_more_comments(win, data)
elif data['type'] == 'Comment':
self._draw_comment(win, data, inverted, highlight)
return self._draw_comment(win, data, inverted)
else:
self._draw_submission(win, data, highlight)
return self._draw_submission(win, data)
def _draw_comment(self, win, data, inverted, highlight):
def _draw_comment(self, win, data, inverted):
n_rows, n_cols = win.getmaxyx()
n_cols -= 1
@@ -287,7 +288,7 @@ class SubmissionPage(Page):
split_body = data['split_body']
if data['n_rows'] > n_rows:
# Only when there is a single comment on the page and not inverted
if not inverted and len(self._subwindows) == 0:
if not inverted and len(self._subwindows) == 1:
cutoff = data['n_rows'] - n_rows + 1
split_body = split_body[:-cutoff]
split_body.append('(Not enough space to display)')
@@ -295,104 +296,104 @@ class SubmissionPage(Page):
row = offset
if row in valid_rows:
if data['is_author']:
attr = self.term.attr('comment_author_self', highlight)
attr = self.term.attr('comment_author_self')
text = '{author} [S]'.format(**data)
else:
attr = self.term.attr('comment_author', highlight)
attr = self.term.attr('comment_author')
text = '{author}'.format(**data)
self.term.add_line(win, text, row, 1, attr)
if data['flair']:
attr = self.term.attr('user_flair', highlight)
attr = self.term.attr('user_flair')
self.term.add_space(win)
self.term.add_line(win, '{flair}'.format(**data), attr=attr)
arrow, attr = self.term.get_arrow(data['likes'], highlight)
arrow, attr = self.term.get_arrow(data['likes'])
self.term.add_space(win)
self.term.add_line(win, arrow, attr=attr)
attr = self.term.attr('score', highlight)
attr = self.term.attr('score')
self.term.add_space(win)
self.term.add_line(win, '{score}'.format(**data), attr=attr)
attr = self.term.attr('created', highlight)
attr = self.term.attr('created')
self.term.add_space(win)
self.term.add_line(win, '{created}'.format(**data), attr=attr)
if data['gold']:
attr = self.term.attr('gold', highlight)
attr = self.term.attr('gold')
self.term.add_space(win)
self.term.add_line(win, self.term.guilded, attr=attr)
if data['stickied']:
attr = self.term.attr('stickied', highlight)
attr = self.term.attr('stickied')
self.term.add_space(win)
self.term.add_line(win, '[stickied]', attr=attr)
if data['saved']:
attr = self.term.attr('saved', highlight)
attr = self.term.attr('saved')
self.term.add_space(win)
self.term.add_line(win, '[saved]', attr=attr)
for row, text in enumerate(split_body, start=offset+1):
attr = self.term.attr('comment_text', highlight)
attr = self.term.attr('comment_text')
if row in valid_rows:
self.term.add_line(win, text, row, 1, attr=attr)
# Unfortunately vline() doesn't support custom color so we have to
# build it one segment at a time.
index = data['level'] % len(self.term.theme.BAR_LEVELS)
attr = self.term.attr(self.term.theme.BAR_LEVELS[index], highlight)
attr = self.term.attr(self.term.theme.BAR_LEVELS[index])
for y in range(n_rows):
self.term.addch(win, y, 0, self.term.vline, attr)
def _draw_more_comments(self, win, data, highlight):
def _draw_more_comments(self, win, data):
n_rows, n_cols = win.getmaxyx()
n_cols -= 1
attr = self.term.attr('hidden_comment_text', highlight)
attr = self.term.attr('hidden_comment_text')
self.term.add_line(win, '{body}'.format(**data), 0, 1, attr=attr)
attr = self.term.attr('hidden_comment_expand', highlight)
attr = self.term.attr('hidden_comment_expand')
self.term.add_space(win)
self.term.add_line(win, '[{count}]'.format(**data), attr=attr)
index = data['level'] % len(self.term.theme.BAR_LEVELS)
attr = self.term.attr(self.term.theme.BAR_LEVELS[index], highlight)
attr = self.term.attr(self.term.theme.BAR_LEVELS[index])
self.term.addch(win, 0, 0, self.term.vline, attr)
def _draw_submission(self, win, data, highlight):
def _draw_submission(self, win, data):
n_rows, n_cols = win.getmaxyx()
n_cols -= 3 # one for each side of the border + one for offset
attr = self.term.attr('submission_title', highlight)
attr = self.term.attr('submission_title')
for row, text in enumerate(data['split_title'], start=1):
self.term.add_line(win, text, row, 1, attr)
row = len(data['split_title']) + 1
attr = self.term.attr('submission_author', highlight)
attr = self.term.attr('submission_author')
self.term.add_line(win, '{author}'.format(**data), row, 1, attr)
if data['flair']:
attr = self.term.attr('submission_flair', highlight)
attr = self.term.attr('submission_flair')
self.term.add_space(win)
self.term.add_line(win, '{flair}'.format(**data), attr=attr)
attr = self.term.attr('created', highlight)
attr = self.term.attr('created')
self.term.add_space(win)
self.term.add_line(win, '{created}'.format(**data), attr=attr)
attr = self.term.attr('submission_subreddit', highlight)
attr = self.term.attr('submission_subreddit')
self.term.add_space(win)
self.term.add_line(win, '/r/{subreddit}'.format(**data), attr=attr)
row = len(data['split_title']) + 2
if data['url_full'] in self.config.history:
attr = self.term.attr('url_seen', highlight)
attr = self.term.attr('url_seen')
else:
attr = self.term.attr('url', highlight)
attr = self.term.attr('url')
self.term.add_line(win, '{url}'.format(**data), row, 1, attr)
offset = len(data['split_title']) + 3
@@ -404,34 +405,34 @@ class SubmissionPage(Page):
split_text = split_text[:-cutoff]
split_text.append('(Not enough space to display)')
attr = self.term.attr('submission_text', highlight)
attr = self.term.attr('submission_text')
for row, text in enumerate(split_text, start=offset):
self.term.add_line(win, text, row, 1, attr=attr)
row = len(data['split_title']) + len(split_text) + 3
attr = self.term.attr('score', highlight)
attr = self.term.attr('score')
self.term.add_line(win, '{score}'.format(**data), row, 1, attr=attr)
arrow, attr = self.term.get_arrow(data['likes'], highlight)
arrow, attr = self.term.get_arrow(data['likes'])
self.term.add_space(win)
self.term.add_line(win, arrow, attr=attr)
attr = self.term.attr('comment_count', highlight)
attr = self.term.attr('comment_count')
self.term.add_space(win)
self.term.add_line(win, '{comments}'.format(**data), attr=attr)
if data['gold']:
attr = self.term.attr('gold', highlight)
attr = self.term.attr('gold')
self.term.add_space(win)
self.term.add_line(win, self.term.guilded, attr=attr)
if data['nsfw']:
attr = self.term.attr('nsfw', highlight)
attr = self.term.attr('nsfw')
self.term.add_space(win)
self.term.add_line(win, 'NSFW', attr=attr)
if data['saved']:
attr = self.term.attr('saved', highlight)
attr = self.term.attr('saved')
self.term.add_space(win)
self.term.add_line(win, '[saved]', attr=attr)

View File

@@ -253,7 +253,7 @@ class SubredditPage(Page):
self.content = page.selected_subreddit
self.nav = Navigator(self.content.get)
def _draw_item(self, win, data, inverted, highlight):
def _draw_item(self, win, data, inverted):
n_rows, n_cols = win.getmaxyx()
n_cols -= 1 # Leave space for the cursor in the first column
@@ -264,75 +264,75 @@ class SubredditPage(Page):
n_title = len(data['split_title'])
for row, text in enumerate(data['split_title'], start=offset):
attr = self.term.attr('submission_title', highlight)
attr = self.term.attr('submission_title')
if row in valid_rows:
self.term.add_line(win, text, row, 1, attr)
row = n_title + offset
if row in valid_rows:
if data['url_full'] in self.config.history:
attr = self.term.attr('url_seen', highlight)
attr = self.term.attr('url_seen')
else:
attr = self.term.attr('url', highlight)
attr = self.term.attr('url')
self.term.add_line(win, '{url}'.format(**data), row, 1, attr)
row = n_title + offset + 1
if row in valid_rows:
attr = self.term.attr('score', highlight)
attr = self.term.attr('score')
self.term.add_line(win, '{score}'.format(**data), row, 1, attr)
self.term.add_space(win)
arrow, attr = self.term.get_arrow(data['likes'], highlight)
arrow, attr = self.term.get_arrow(data['likes'])
self.term.add_line(win, arrow, attr=attr)
self.term.add_space(win)
attr = self.term.attr('created', highlight)
attr = self.term.attr('created')
self.term.add_line(win, '{created}'.format(**data), attr=attr)
if data['comments'] is not None:
attr = self.term.attr('separator', highlight)
attr = self.term.attr('separator')
self.term.add_space(win)
self.term.add_line(win, '-', attr=attr)
attr = self.term.attr('comment_count', highlight)
attr = self.term.attr('comment_count')
self.term.add_space(win)
self.term.add_line(win, '{comments}'.format(**data), attr=attr)
if data['saved']:
attr = self.term.attr('saved', highlight)
attr = self.term.attr('saved')
self.term.add_space(win)
self.term.add_line(win, '[saved]', attr=attr)
if data['stickied']:
attr = self.term.attr('stickied', highlight)
attr = self.term.attr('stickied')
self.term.add_space(win)
self.term.add_line(win, '[stickied]', attr=attr)
if data['gold']:
attr = self.term.attr('gold', highlight)
attr = self.term.attr('gold')
self.term.add_space(win)
self.term.add_line(win, self.term.guilded, attr=attr)
if data['nsfw']:
attr = self.term.attr('nsfw', highlight)
attr = self.term.attr('nsfw')
self.term.add_space(win)
self.term.add_line(win, 'NSFW', attr=attr)
row = n_title + offset + 2
if row in valid_rows:
attr = self.term.attr('submission_author', highlight)
attr = self.term.attr('submission_author')
self.term.add_line(win, '{author}'.format(**data), row, 1, attr)
self.term.add_space(win)
attr = self.term.attr('submission_subreddit', highlight)
attr = self.term.attr('submission_subreddit')
self.term.add_line(win, '/r/{subreddit}'.format(**data), attr=attr)
if data['flair']:
attr = self.term.attr('submission_flair', highlight)
attr = self.term.attr('submission_flair')
self.term.add_space(win)
self.term.add_line(win, '{flair}'.format(**data), attr=attr)
attr = self.term.attr('cursor', highlight)
attr = self.term.attr('cursor')
for y in range(n_rows):
self.term.addch(win, y, 0, str(' '), attr)

View File

@@ -83,7 +83,7 @@ class SubscriptionPage(Page):
# Subscriptions can't be sorted, so disable showing the order menu
pass
def _draw_item(self, win, data, inverted, highlight):
def _draw_item(self, win, data, inverted):
n_rows, n_cols = win.getmaxyx()
n_cols -= 1 # Leave space for the cursor in the first column
@@ -94,20 +94,20 @@ class SubscriptionPage(Page):
row = offset
if row in valid_rows:
if data['type'] == 'Multireddit':
attr = self.term.attr('multireddit_name', highlight)
attr = self.term.attr('multireddit_name')
else:
attr = self.term.attr('subscription_name', highlight)
attr = self.term.attr('subscription_name')
self.term.add_line(win, '{name}'.format(**data), row, 1, attr)
row = offset + 1
for row, text in enumerate(data['split_title'], start=row):
if row in valid_rows:
if data['type'] == 'Multireddit':
attr = self.term.attr('multireddit_text', highlight)
attr = self.term.attr('multireddit_text')
else:
attr = self.term.attr('subscription_text', highlight)
attr = self.term.attr('subscription_text')
self.term.add_line(win, text, row, 1, attr)
attr = self.term.attr('cursor', highlight)
attr = self.term.attr('cursor')
for y in range(n_rows):
self.term.addch(win, y, 0, str(' '), attr)

View File

@@ -183,7 +183,7 @@ class Terminal(object):
finally:
self.stdscr.nodelay(0)
def get_arrow(self, likes, highlight=False):
def get_arrow(self, likes):
"""
Curses does define constants for symbols (e.g. curses.ACS_BULLET).
However, they rely on using the curses.addch() function, which has been
@@ -193,11 +193,11 @@ class Terminal(object):
"""
if likes is None:
return self.neutral_arrow, self.attr('neutral_vote', highlight)
return self.neutral_arrow, self.attr('neutral_vote')
elif likes:
return self.up_arrow, self.attr('upvote', highlight)
return self.up_arrow, self.attr('upvote')
else:
return self.down_arrow, self.attr('downvote', highlight)
return self.down_arrow, self.attr('downvote')
def clean(self, string, n_cols=None):
"""
@@ -282,7 +282,8 @@ class Terminal(object):
row, col = window.getyx()
_, max_cols = window.getmaxyx()
if max_cols - col - 1 <= 0:
n_cols = max_cols - col - 1
if n_cols <= 0:
# Trying to draw outside of the screen bounds
return
@@ -300,6 +301,8 @@ class Terminal(object):
notification window
"""
assert style in ('info', 'warning', 'error', 'success')
if isinstance(message, six.string_types):
message = message.splitlines()
@@ -396,7 +399,7 @@ class Terminal(object):
_logger.warning(stderr)
self.show_notification(
'Program exited with status={0}\n{1}'.format(
code, stderr.strip()))
code, stderr.strip()), style='error')
else:
# Non-blocking, open a background process
@@ -821,17 +824,18 @@ class Terminal(object):
else:
self.stdscr.clearok(True)
def attr(self, element, highlight=False):
def attr(self, element):
"""
Shortcut for fetching the color + attribute code for an element.
"""
return self.theme.get(element, highlight=highlight)
return self.theme.get(element)
def set_theme(self, theme=None):
"""
Check that the terminal supports the provided theme, and applies
the theme to the terminal if possible.
If the terminal doesn't support the theme, this falls back to the
default theme. The default theme only requires 8 colors so it
should be compatible with any terminal that supports basic colors.
@@ -862,4 +866,4 @@ class Terminal(object):
# Apply the default color to the whole screen
self.stdscr.bkgd(str(' '), theme.get('@normal'))
self.theme = theme
self.theme = theme

View File

@@ -1,8 +1,9 @@
import os
import codecs
import configparser
import curses
import logging
import os
import configparser
from contextlib import contextmanager
from .config import THEMES, DEFAULT_THEMES
from .exceptions import ConfigError
@@ -116,6 +117,7 @@ class Theme(object):
self.monochrome = monochrome
self._color_pair_map = None
self._attribute_map = None
self._modifier = None
self.required_color_pairs = 0
self.required_colors = 0
@@ -206,7 +208,7 @@ class Theme(object):
self._attribute_map[element] = attrs
def get(self, val, highlight=False):
def get(self, element, modifier=None):
"""
Returns the curses attribute code for the given element.
"""
@@ -214,10 +216,25 @@ class Theme(object):
raise RuntimeError('Attempted to access theme attribute before '
'calling initialize_curses_theme()')
if highlight:
val = val + '.highlight'
modifier = modifier or self._modifier
if modifier:
modified_element = '{0}.{1}'.format(element, modifier)
if modified_element in self._elements:
return self._elements[modified_element]
return self._attribute_map[val]
return self._attribute_map[element]
@contextmanager
def set_modifier(self, modifier=None):
# This case is undefined if the context manager is nested
assert self._modifier is None
self._modifier = modifier
try:
yield
finally:
self._modifier = None
@classmethod
def list_themes(cls, path=THEMES):