From d0fef21c3e423e82d02eb6cd9a8d8daf351d3bf2 Mon Sep 17 00:00:00 2001 From: Michael Lazar Date: Sat, 22 Jul 2017 19:35:01 -0400 Subject: [PATCH] Adding the ability to highlight --- rtv/page.py | 72 ++++++++------ rtv/submission_page.py | 83 ++++++++-------- rtv/subreddit_page.py | 36 ++++--- rtv/subscription_page.py | 14 ++- rtv/templates/rtv.cfg | 1 + rtv/terminal.py | 14 +-- rtv/theme.py | 171 +++++++++++++++++++++------------ rtv/themes/default.cfg | 93 +++++++++--------- rtv/themes/monochrome.cfg | 88 ++++++++--------- rtv/themes/solarized-dark.cfg | 92 +++++++++--------- rtv/themes/solarized-light.cfg | 92 +++++++++--------- tests/test_theme.py | 2 - 12 files changed, 416 insertions(+), 342 deletions(-) diff --git a/rtv/page.py b/rtv/page.py index 360682a..e938fc2 100644 --- a/rtv/page.py +++ b/rtv/page.py @@ -55,11 +55,12 @@ class Page(object): self.active = True self._row = 0 self._subwindows = None + self._theme_list = None def refresh_content(self, order=None, name=None): raise NotImplementedError - def _draw_item(self, window, data, inverted): + def _draw_item(self, window, data, inverted, highlight): raise NotImplementedError def get_selected_item(self): @@ -91,6 +92,24 @@ class Page(object): def force_exit(self): sys.exit() + @PageController.register(Command('NEXT_THEME')) + def next_theme(self): + if self._theme_list is None: + self._theme_list = self.term.theme.list_themes()['default'] + + names = sorted(self._theme_list.keys()) + if self.term.theme.name in self._theme_list: + index = names.index(self.term.theme.name) + 1 + if index >= len(names): + index = 0 + else: + index = 0 + + new_theme = self._theme_list[names[index]] + self.term.set_theme(new_theme) + self.draw() + self.term.show_notification(new_theme.name, timeout=1) + @PageController.register(Command('HELP')) def show_help(self): self.term.open_pager(docs.HELP.strip()) @@ -385,7 +404,6 @@ class Page(object): self._draw_banner() self._draw_content() self._draw_footer() - self._add_cursor() self.term.clear_screen() self.term.stdscr.refresh() @@ -456,7 +474,7 @@ 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_selected')) + window.chgat(0, col, 3, self.term.attr('order_bar', True)) self._row += 1 @@ -466,8 +484,7 @@ class Page(object): """ n_rows, n_cols = self.term.stdscr.getmaxyx() - window = self.term.stdscr.derwin( - n_rows - self._row - 1, n_cols, self._row, 0) + window = self.term.stdscr.derwin(n_rows - self._row - 1, n_cols, self._row, 0) window.erase() win_n_rows, win_n_cols = window.getmaxyx() @@ -494,11 +511,8 @@ class Page(object): top_item_height = None subwin_n_cols = win_n_cols - data['h_offset'] start = current_row - subwin_n_rows + 1 if inverted else current_row - subwindow = window.derwin( - subwin_n_rows, subwin_n_cols, start, data['h_offset']) - - attr = self._draw_item(subwindow, data, subwin_inverted) - self._subwindows.append((subwindow, attr)) + subwindow = window.derwin(subwin_n_rows, subwin_n_cols, start, data['h_offset']) + self._subwindows.append((subwindow, data, subwin_inverted)) available_rows -= (subwin_n_rows + 1) # Add one for the blank line current_row += step * (subwin_n_rows + 1) if available_rows <= 0: @@ -520,6 +534,22 @@ class Page(object): self.nav.flip((len(self._subwindows) - 1)) return self._draw_content() + if self.nav.cursor_index >= len(self._subwindows): + # Don't allow the cursor to go over the number of subwindows + # This could happen if the window is resized and the cursor index is + # pushed out of bounds + self.nav.cursor_index = len(self._subwindows) - 1 + + # 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')) + else: + win.bkgd(str(' '), self.term.attr('@normal')) + self._draw_item(win, data, inverted, highlight) + self._row += win_n_rows def _draw_footer(self): @@ -544,25 +574,3 @@ class Page(object): valid, redraw = self.nav.move_page(direction, len(self._subwindows)-1) if not valid: self.term.flash() - - def _add_cursor(self): - - # Don't allow the cursor to go below page index 0 - if self.nav.absolute_index < 0: - return - - # Don't allow the cursor to go over the number of subwindows - # This could happen if the window is resized and the cursor index is - # pushed out of bounds - if self.nav.cursor_index >= len(self._subwindows): - self.nav.cursor_index = len(self._subwindows) - 1 - - window, cursor_attr = self._subwindows[self.nav.cursor_index] - if cursor_attr is None: - attr = self.term.attr('cursor') - else: - attr = cursor_attr | curses.A_REVERSE - - n_rows, _ = window.getmaxyx() - for row in range(n_rows): - window.chgat(row, 0, 1, attr) diff --git a/rtv/submission_page.py b/rtv/submission_page.py index ed5f676..19ebfc2 100644 --- a/rtv/submission_page.py +++ b/rtv/submission_page.py @@ -199,18 +199,16 @@ class SubmissionPage(Page): else: self.term.flash() - def _draw_item(self, win, data, inverted): + def _draw_item(self, win, data, inverted, highlight): - if data['type'] == 'MoreComments': - return self._draw_more_comments(win, data) - elif data['type'] == 'HiddenComment': - return self._draw_more_comments(win, data) + if data['type'] in ('MoreComments', 'HiddenComment'): + self._draw_more_comments(win, data, highlight) elif data['type'] == 'Comment': - return self._draw_comment(win, data, inverted) + self._draw_comment(win, data, inverted, highlight) else: - return self._draw_submission(win, data) + self._draw_submission(win, data, highlight) - def _draw_comment(self, win, data, inverted): + def _draw_comment(self, win, data, inverted, highlight): n_rows, n_cols = win.getmaxyx() n_cols -= 1 @@ -232,105 +230,102 @@ class SubmissionPage(Page): row = offset if row in valid_rows: if data['is_author']: - attr = self.term.attr('comment_author_self') + attr = self.term.attr('comment_author_self', highlight) else: - attr = self.term.attr('comment_author') + attr = self.term.attr('comment_author', highlight) self.term.add_line(win, '{author}'.format(**data), row, 1, attr) if data['flair']: - attr = self.term.attr('user_flair') + attr = self.term.attr('user_flair', highlight) self.term.add_space(win) self.term.add_line(win, '{flair}'.format(**data), attr=attr) - arrow, attr = self.term.get_arrow(data['likes']) + arrow, attr = self.term.get_arrow(data['likes'], highlight) self.term.add_space(win) self.term.add_line(win, arrow, attr=attr) - attr = self.term.attr('score') + attr = self.term.attr('score', highlight) self.term.add_space(win) self.term.add_line(win, '{score}'.format(**data), attr=attr) - attr = self.term.attr('created') + attr = self.term.attr('created', highlight) self.term.add_space(win) self.term.add_line(win, '{created}'.format(**data), attr=attr) if data['gold']: - attr = self.term.attr('gold') + attr = self.term.attr('gold', highlight) self.term.add_space(win) self.term.add_line(win, self.term.guilded, attr=attr) if data['stickied']: - attr = self.term.attr('stickied') + attr = self.term.attr('stickied', highlight) self.term.add_space(win) self.term.add_line(win, '[stickied]', attr=attr) if data['saved']: - attr = self.term.attr('saved') + attr = self.term.attr('saved', highlight) 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') + attr = self.term.attr('comment_text', highlight) 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. - attr = self.term.theme.get_bar_level(data['level']) - x = 0 + index = data['level'] % len(self.term.theme.BAR_LEVELS) + attr = self.term.attr(self.term.theme.BAR_LEVELS[index], highlight) for y in range(n_rows): - self.term.addch(win, y, x, self.term.vline, attr) + self.term.addch(win, y, 0, self.term.vline, attr) - return attr | self.term.vline - - def _draw_more_comments(self, win, data): + def _draw_more_comments(self, win, data, highlight): n_rows, n_cols = win.getmaxyx() n_cols -= 1 - attr = self.term.attr('hidden_comment_text') + attr = self.term.attr('hidden_comment_text', highlight) self.term.add_line(win, '{body}'.format(**data), 0, 1, attr=attr) - attr = self.term.attr('hidden_comment_expand') + attr = self.term.attr('hidden_comment_expand', highlight) self.term.add_space(win) self.term.add_line(win, '[{count}]'.format(**data), attr=attr) - attr = self.term.theme.get_bar_level(data['level']) + index = data['level'] % len(self.term.theme.BAR_LEVELS) + attr = self.term.attr(self.term.theme.BAR_LEVELS[index], highlight) self.term.addch(win, 0, 0, self.term.vline, attr) - return attr | self.term.vline - - def _draw_submission(self, win, data): + def _draw_submission(self, win, data, highlight): 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') + attr = self.term.attr('submission_title', highlight) 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') + attr = self.term.attr('submission_author', highlight) self.term.add_line(win, '{author}'.format(**data), row, 1, attr) if data['flair']: - attr = self.term.attr('submission_flair') + attr = self.term.attr('submission_flair', highlight) self.term.add_space(win) self.term.add_line(win, '{flair}'.format(**data), attr=attr) - attr = self.term.attr('created') + attr = self.term.attr('created', highlight) self.term.add_space(win) self.term.add_line(win, '{created}'.format(**data), attr=attr) - attr = self.term.attr('submission_subreddit') + attr = self.term.attr('submission_subreddit', highlight) 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') + attr = self.term.attr('url_seen', highlight) else: - attr = self.term.attr('url') + attr = self.term.attr('url', highlight) self.term.add_line(win, '{url}'.format(**data), row, 1, attr) offset = len(data['split_title']) + 3 @@ -342,34 +337,34 @@ class SubmissionPage(Page): split_text = split_text[:-cutoff] split_text.append('(Not enough space to display)') - attr = self.term.attr('submission_text') + attr = self.term.attr('submission_text', highlight) 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') + attr = self.term.attr('score', highlight) self.term.add_line(win, '{score}'.format(**data), row, 1, attr=attr) - arrow, attr = self.term.get_arrow(data['likes']) + arrow, attr = self.term.get_arrow(data['likes'], highlight) self.term.add_space(win) self.term.add_line(win, arrow, attr=attr) - attr = self.term.attr('comment_count') + attr = self.term.attr('comment_count', highlight) self.term.add_space(win) self.term.add_line(win, '{comments}'.format(**data), attr=attr) if data['gold']: - attr = self.term.attr('gold') + attr = self.term.attr('gold', highlight) self.term.add_space(win) self.term.add_line(win, self.term.guilded, attr=attr) if data['nsfw']: - attr = self.term.attr('nsfw') + attr = self.term.attr('nsfw', highlight) self.term.add_space(win) self.term.add_line(win, 'NSFW', attr=attr) if data['saved']: - attr = self.term.attr('saved') + attr = self.term.attr('saved', highlight) self.term.add_space(win) self.term.add_line(win, '[saved]', attr=attr) diff --git a/rtv/subreddit_page.py b/rtv/subreddit_page.py index fde8e25..782948b 100644 --- a/rtv/subreddit_page.py +++ b/rtv/subreddit_page.py @@ -233,7 +233,7 @@ class SubredditPage(Page): self.content = page.selected_subreddit self.nav = Navigator(self.content.get) - def _draw_item(self, win, data, inverted): + def _draw_item(self, win, data, inverted, highlight): n_rows, n_cols = win.getmaxyx() n_cols -= 1 # Leave space for the cursor in the first column @@ -244,71 +244,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') + attr = self.term.attr('submission_title', highlight) 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') + attr = self.term.attr('url_seen', highlight) else: - attr = self.term.attr('url') + attr = self.term.attr('url', highlight) 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') + attr = self.term.attr('score', highlight) self.term.add_line(win, '{score}'.format(**data), row, 1, attr) self.term.add_space(win) - arrow, attr = self.term.get_arrow(data['likes']) + arrow, attr = self.term.get_arrow(data['likes'], highlight) self.term.add_line(win, arrow, attr=attr) self.term.add_space(win) - attr = self.term.attr('created') + attr = self.term.attr('created', highlight) self.term.add_line(win, '{created}'.format(**data), attr=attr) if data['comments'] is not None: - attr = self.term.attr('separator') + attr = self.term.attr('separator', highlight) self.term.add_space(win) self.term.add_line(win, '-', attr=attr) - attr = self.term.attr('comment_count') + attr = self.term.attr('comment_count', highlight) self.term.add_space(win) self.term.add_line(win, '{comments}'.format(**data), attr=attr) if data['saved']: - attr = self.term.attr('saved') + attr = self.term.attr('saved', highlight) self.term.add_space(win) self.term.add_line(win, '[saved]', attr=attr) if data['stickied']: - attr = self.term.attr('stickied') + attr = self.term.attr('stickied', highlight) self.term.add_space(win) self.term.add_line(win, '[stickied]', attr=attr) if data['gold']: - attr = self.term.attr('gold') + attr = self.term.attr('gold', highlight) self.term.add_space(win) self.term.add_line(win, self.term.guilded, attr=attr) if data['nsfw']: - attr = self.term.attr('nsfw') + attr = self.term.attr('nsfw', highlight) 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') + attr = self.term.attr('submission_author', highlight) self.term.add_line(win, '{author}'.format(**data), row, 1, attr) self.term.add_space(win) - attr = self.term.attr('submission_subreddit') + attr = self.term.attr('submission_subreddit', highlight) self.term.add_line(win, '/r/{subreddit}'.format(**data), attr=attr) if data['flair']: - attr = self.term.attr('submission_flair') + attr = self.term.attr('submission_flair', highlight) self.term.add_space(win) self.term.add_line(win, '{flair}'.format(**data), attr=attr) + + attr = self.term.attr('cursor', highlight) + for y in range(n_rows): + self.term.addch(win, y, 0, str(' '), attr) diff --git a/rtv/subscription_page.py b/rtv/subscription_page.py index 53dead5..402f350 100644 --- a/rtv/subscription_page.py +++ b/rtv/subscription_page.py @@ -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): + def _draw_item(self, win, data, inverted, highlight): n_rows, n_cols = win.getmaxyx() n_cols -= 1 # Leave space for the cursor in the first column @@ -94,16 +94,20 @@ class SubscriptionPage(Page): row = offset if row in valid_rows: if data['type'] == 'Multireddit': - attr = self.term.attr('multireddit_name') + attr = self.term.attr('multireddit_name', highlight) else: - attr = self.term.attr('subscription_name') + attr = self.term.attr('subscription_name', highlight) 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') + attr = self.term.attr('multireddit_text', highlight) else: - attr = self.term.attr('subscription_text') + attr = self.term.attr('subscription_text', highlight) self.term.add_line(win, text, row, 1, attr) + + attr = self.term.attr('cursor', highlight) + for y in range(n_rows): + self.term.addch(win, y, 0, str(' '), attr) diff --git a/rtv/templates/rtv.cfg b/rtv/templates/rtv.cfg index 36fcc7e..49291f6 100644 --- a/rtv/templates/rtv.cfg +++ b/rtv/templates/rtv.cfg @@ -105,6 +105,7 @@ SORT_NEW = 4 SORT_CONTROVERSIAL = 5 MOVE_UP = k, MOVE_DOWN = j, +NEXT_THEME = PAGE_UP = m, , PAGE_DOWN = n, , PAGE_TOP = gg diff --git a/rtv/terminal.py b/rtv/terminal.py index c325d9e..325e0fb 100644 --- a/rtv/terminal.py +++ b/rtv/terminal.py @@ -180,7 +180,7 @@ class Terminal(object): finally: self.stdscr.nodelay(0) - def get_arrow(self, likes): + def get_arrow(self, likes, highlight=False): """ Curses does define constants for symbols (e.g. curses.ACS_BULLET). However, they rely on using the curses.addch() function, which has been @@ -190,11 +190,11 @@ class Terminal(object): """ if likes is None: - return self.neutral_arrow, self.attr('neutral_vote') + return self.neutral_arrow, self.attr('neutral_vote', highlight) elif likes: - return self.up_arrow, self.attr('upvote') + return self.up_arrow, self.attr('upvote', highlight) else: - return self.down_arrow, self.attr('downvote') + return self.down_arrow, self.attr('downvote', highlight) def clean(self, string, n_cols=None): """ @@ -816,11 +816,11 @@ class Terminal(object): else: self.stdscr.clearok(True) - def attr(self, element): + def attr(self, element, highlight=False): """ Shortcut for fetching the color + attribute code for an element. """ - return self.theme.get(element) + return self.theme.get(element, highlight=highlight) def set_theme(self, theme=None): """ @@ -855,6 +855,6 @@ class Terminal(object): theme.bind_curses() # Apply the default color to the whole screen - self.stdscr.bkgd(str(' '), theme.get('default')) + self.stdscr.bkgd(str(' '), theme.get('@normal')) self.theme = theme diff --git a/rtv/theme.py b/rtv/theme.py index d926d85..187d63c 100644 --- a/rtv/theme.py +++ b/rtv/theme.py @@ -13,6 +13,7 @@ _logger = logging.getLogger(__name__) class Theme(object): ATTRIBUTE_CODES = { + '-': None, '': curses.A_NORMAL, 'bold': curses.A_BOLD, 'reverse': curses.A_REVERSE, @@ -21,6 +22,7 @@ class Theme(object): } COLOR_CODES = { + '-': None, 'default': -1, 'black': curses.COLOR_BLACK, 'red': curses.COLOR_RED, @@ -47,51 +49,59 @@ class Theme(object): # For compatibility with as many terminals as possible, the default theme # can only use the 8 basic colors with the default background. DEFAULT_THEME = { - 'bar_level_1': (curses.COLOR_MAGENTA, -1, curses.A_NORMAL), - 'bar_level_2': (curses.COLOR_CYAN, -1, curses.A_NORMAL), - 'bar_level_3': (curses.COLOR_GREEN, -1, curses.A_NORMAL), - 'bar_level_4': (curses.COLOR_YELLOW, -1, curses.A_NORMAL), - 'comment_author': (curses.COLOR_BLUE, -1, curses.A_BOLD), - 'comment_author_self': (curses.COLOR_GREEN, -1, curses.A_BOLD), - 'comment_count': (-1, -1, curses.A_NORMAL), - 'comment_text': (-1, -1, curses.A_NORMAL), - 'created': (-1, -1, curses.A_NORMAL), - 'cursor': (-1, -1, curses.A_REVERSE), - 'default': (-1, -1, curses.A_NORMAL), - 'downvote': (curses.COLOR_RED, -1, curses.A_BOLD), - 'gold': (curses.COLOR_YELLOW, -1, curses.A_BOLD), - 'help_bar': (curses.COLOR_CYAN, -1, curses.A_BOLD | curses.A_REVERSE), - 'hidden_comment_expand': (-1, -1, curses.A_BOLD), - 'hidden_comment_text': (-1, -1, curses.A_NORMAL), - 'multireddit_name': (curses.COLOR_YELLOW, -1, curses.A_BOLD), - 'multireddit_text': (-1, -1, curses.A_NORMAL), - 'neutral_vote': (-1, -1, curses.A_BOLD), - 'notice_info': (-1, -1, curses.A_NORMAL), - 'notice_loading': (-1, -1, curses.A_NORMAL), - 'notice_error': (curses.COLOR_RED, -1, curses.A_NORMAL), - 'notice_success': (curses.COLOR_GREEN, -1, curses.A_NORMAL), - 'nsfw': (curses.COLOR_RED, -1, curses.A_BOLD), - 'order_bar': (curses.COLOR_YELLOW, -1, curses.A_BOLD), - 'order_selected': (curses.COLOR_YELLOW, -1, curses.A_BOLD | curses.A_REVERSE), - 'prompt': (curses.COLOR_CYAN, -1, curses.A_BOLD | curses.A_REVERSE), - 'saved': (curses.COLOR_GREEN, -1, curses.A_NORMAL), - 'score': (-1, -1, curses.A_NORMAL), - 'separator': (-1, -1, curses.A_BOLD), - 'stickied': (curses.COLOR_GREEN, -1, curses.A_NORMAL), - 'subscription_name': (curses.COLOR_YELLOW, -1, curses.A_BOLD), - 'subscription_text': (-1, -1, curses.A_NORMAL), - 'submission_author': (curses.COLOR_GREEN, -1, curses.A_NORMAL), - 'submission_flair': (curses.COLOR_RED, -1, curses.A_NORMAL), - 'submission_subreddit': (curses.COLOR_YELLOW, -1, curses.A_NORMAL), - 'submission_text': (-1, -1, curses.A_NORMAL), - 'submission_title': (-1, -1, curses.A_BOLD), - 'title_bar': (curses.COLOR_CYAN, -1, curses.A_BOLD | curses.A_REVERSE), - 'upvote': (curses.COLOR_GREEN, -1, curses.A_BOLD), - 'url': (curses.COLOR_BLUE, -1, curses.A_UNDERLINE), - 'url_seen': (curses.COLOR_MAGENTA, -1, curses.A_UNDERLINE), - 'user_flair': (curses.COLOR_YELLOW, -1, curses.A_BOLD) + '@normal': (-1, -1, curses.A_NORMAL), + '@highlight': (-1, -1, curses.A_NORMAL), + 'bar_level_1': (curses.COLOR_MAGENTA, None, curses.A_NORMAL), + 'bar_level_1.highlight': (curses.COLOR_MAGENTA, None, curses.A_REVERSE), + 'bar_level_2': (curses.COLOR_CYAN, None, curses.A_NORMAL), + 'bar_level_2.highlight': (curses.COLOR_CYAN, None, curses.A_REVERSE), + 'bar_level_3': (curses.COLOR_GREEN, None, curses.A_NORMAL), + 'bar_level_3.highlight': (curses.COLOR_GREEN, None, curses.A_REVERSE), + 'bar_level_4': (curses.COLOR_YELLOW, None, curses.A_NORMAL), + 'bar_level_4.highlight': (curses.COLOR_YELLOW, None, curses.A_REVERSE), + 'comment_author': (curses.COLOR_BLUE, None, curses.A_BOLD), + 'comment_author_self': (curses.COLOR_GREEN, None, curses.A_BOLD), + 'comment_count': (None, None, curses.A_NORMAL), + 'comment_text': (None, None, curses.A_NORMAL), + 'created': (None, None, curses.A_NORMAL), + 'cursor': (None, None, curses.A_NORMAL), + 'cursor.highlight': (None, None, curses.A_REVERSE), + 'downvote': (curses.COLOR_RED, None, curses.A_BOLD), + 'gold': (curses.COLOR_YELLOW, None, curses.A_BOLD), + 'help_bar': (curses.COLOR_CYAN, None, curses.A_BOLD | curses.A_REVERSE), + 'hidden_comment_expand': (None, None, curses.A_BOLD), + 'hidden_comment_text': (None, None, curses.A_NORMAL), + 'multireddit_name': (curses.COLOR_YELLOW, None, curses.A_BOLD), + 'multireddit_text': (None, None, curses.A_NORMAL), + 'neutral_vote': (None, None, curses.A_BOLD), + 'notice_info': (None, None, curses.A_NORMAL), + 'notice_loading': (None, None, curses.A_NORMAL), + 'notice_error': (curses.COLOR_RED, None, curses.A_NORMAL), + 'notice_success': (curses.COLOR_GREEN, None, curses.A_NORMAL), + 'nsfw': (curses.COLOR_RED, None, curses.A_BOLD), + 'order_bar': (curses.COLOR_YELLOW, None, curses.A_BOLD), + 'order_bar.highlight': (curses.COLOR_YELLOW, None, curses.A_BOLD | curses.A_REVERSE), + 'prompt': (curses.COLOR_CYAN, None, curses.A_BOLD | curses.A_REVERSE), + 'saved': (curses.COLOR_GREEN, None, curses.A_NORMAL), + 'score': (None, None, curses.A_NORMAL), + 'separator': (None, None, curses.A_BOLD), + 'stickied': (curses.COLOR_GREEN, None, curses.A_NORMAL), + 'subscription_name': (curses.COLOR_YELLOW, None, curses.A_BOLD), + 'subscription_text': (None, None, curses.A_NORMAL), + 'submission_author': (curses.COLOR_GREEN, None, curses.A_NORMAL), + 'submission_flair': (curses.COLOR_RED, None, curses.A_NORMAL), + 'submission_subreddit': (curses.COLOR_YELLOW, None, curses.A_NORMAL), + 'submission_text': (None, None, curses.A_NORMAL), + 'submission_title': (None, None, curses.A_BOLD), + 'title_bar': (curses.COLOR_CYAN, None, curses.A_BOLD | curses.A_REVERSE), + 'upvote': (curses.COLOR_GREEN, None, curses.A_BOLD), + 'url': (curses.COLOR_BLUE, None, curses.A_UNDERLINE), + 'url_seen': (curses.COLOR_MAGENTA, None, curses.A_UNDERLINE), + 'user_flair': (curses.COLOR_YELLOW, None, curses.A_BOLD) } + BAR_LEVELS = ['bar_level_1', 'bar_level_2', 'bar_level_3', 'bar_level_4'] + def __init__(self, name='default', elements=None, monochrome=False): """ Params: @@ -102,10 +112,6 @@ class Theme(object): terminal's default foreground/background color. """ - self.elements = self.DEFAULT_THEME.copy() - if elements: - self.elements.update(elements) - self.name = name self.monochrome = monochrome self._color_pair_map = None @@ -114,6 +120,44 @@ class Theme(object): self.required_color_pairs = 0 self.required_colors = 0 + if elements is None: + elements = self.DEFAULT_THEME.copy() + + # Fill in missing elements + for key in self.DEFAULT_THEME.keys(): + + # Set undefined modifiers to the system default + if key.startswith('@'): + if key not in elements: + elements[key] = self.DEFAULT_THEME[key] + continue + + if key.endswith('.highlight'): + continue + + # Set undefined elements to bubble up to the modifier + if key not in elements: + elements[key] = (None, None, None) + + # Set undefined highlight elements to match their base element + modifier_key = key + '.highlight' + if modifier_key not in elements: + elements[modifier_key] = elements[key] + + # Replace ``None`` attributes with their default modifiers + for key, val in elements.items(): + if key.endswith('.highlight'): + default = elements['@highlight'] + else: + default = elements['@normal'] + + elements[key] = ( + default[0] if val[0] is None else val[0], + default[1] if val[1] is None else val[1], + default[2] if val[2] is None else val[2]) + + self.elements = elements + if not self.monochrome: colors, color_pairs = set(), set() for fg, bg, _ in self.elements.values(): @@ -162,24 +206,19 @@ class Theme(object): self._attribute_map[element] = attrs - def get(self, val): + def get(self, val, highlight=False): """ Returns the curses attribute code for the given element. """ if self._attribute_map is None: raise RuntimeError('Attempted to access theme attribute before ' 'calling initialize_curses_theme()') + + if highlight: + val = val + '.highlight' + return self._attribute_map[val] - def get_bar_level(self, indentation_level): - """ - Helper method for loading the bar format given the indentation level. - """ - - levels = ['bar_level_1', 'bar_level_2', 'bar_level_3', 'bar_level_4'] - level = levels[indentation_level % len(levels)] - return self.get(level) - @classmethod def list_themes(cls, path=THEMES): """ @@ -317,26 +356,30 @@ class Theme(object): if bg.startswith('#'): bg = cls.rgb_to_ansi(bg) - fg_code = cls.COLOR_CODES.get(fg) - if fg_code is None: + if fg not in cls.COLOR_CODES: raise ConfigError( 'Error loading {0}, invalid :\n' ' {1} = {2}'.format(filename, element, line)) + fg_code = cls.COLOR_CODES[fg] - bg_code = cls.COLOR_CODES.get(bg) - if bg_code is None: + if bg not in cls.COLOR_CODES: raise ConfigError( 'Error loading {0}, invalid :\n' ' {1} = {2}'.format(filename, element, line)) + bg_code = cls.COLOR_CODES[bg] attrs_code = curses.A_NORMAL for attr in attrs.split('+'): - attr_code = cls.ATTRIBUTE_CODES.get(attr) - if attr_code is None: + if attr not in cls.ATTRIBUTE_CODES: raise ConfigError( 'Error loading {0}, invalid :\n' ' {1} = {2}'.format(filename, element, line)) - attrs_code |= attr_code + attr_code = cls.ATTRIBUTE_CODES[attr] + if attr_code is None: + attrs_code = None + break + else: + attrs_code |= attr_code return fg_code, bg_code, attrs_code diff --git a/rtv/themes/default.cfg b/rtv/themes/default.cfg index a3fe78b..c8f5dfa 100644 --- a/rtv/themes/default.cfg +++ b/rtv/themes/default.cfg @@ -2,46 +2,53 @@ [theme] ; = -bar_level_1 = magenta default -bar_level_2 = cyan default -bar_level_3 = green default -bar_level_4 = yellow default -comment_author = blue default bold -comment_author_self = green default bold -comment_count = default default -comment_text = default default -created = default default -cursor = default default reverse -default = default default -downvote = red default bold -gold = yellow default bold -help_bar = cyan default bold+reverse -hidden_comment_expand = default default bold -hidden_comment_text = default default -multireddit_name = yellow default bold -multireddit_text = default default -neutral_vote = default default bold -notice_info = default default -notice_loading = default default -notice_error = red default -notice_success = green default -nsfw = red default bold -order_bar = yellow default bold -order_selected = yellow default bold+reverse -prompt = cyan default bold+reverse -saved = green default -score = default default -separator = default default bold -stickied = green default -subscription_name = yellow default bold -subscription_text = default default -submission_author = green default -submission_flair = red default -submission_subreddit = yellow default -submission_text = default default -submission_title = default default bold -title_bar = cyan default bold+reverse -upvote = green default bold -url = blue default underline -url_seen = magenta default underline -user_flair = yellow default bold +@normal = default default +@highlight = default default + +bar_level_1 = magenta - +bar_level_1.highlight = magenta - reverse +bar_level_2 = cyan - +bar_level_2.highlight = cyan - reverse +bar_level_3 = green - +bar_level_3.highlight = green - reverse +bar_level_4 = yellow - +bar_level_4.highlight = yellow - reverse +comment_author = blue - bold +comment_author_self = green - bold +comment_count = - - +comment_text = - - +created = - - +cursor = - - +cursor.highlight = - - reverse +downvote = red - bold +gold = yellow - bold +help_bar = cyan - bold+reverse +hidden_comment_expand = - - bold +hidden_comment_text = - - +multireddit_name = yellow - bold +multireddit_text = - - +neutral_vote = - - bold +notice_info = - - +notice_loading = - - +notice_error = red - +notice_success = green - +nsfw = red - bold +order_bar = yellow - bold +order_bar.highlight = yellow - bold+reverse +prompt = cyan - bold+reverse +saved = green - +score = - - +separator = - - bold +stickied = green - +subscription_name = yellow - bold +subscription_text = - - +submission_author = green - +submission_flair = red - +submission_subreddit = yellow - +submission_text = - - +submission_title = - - bold +title_bar = cyan - bold+reverse +upvote = green - bold +url = blue - underline +url_seen = magenta - underline +user_flair = yellow - bold diff --git a/rtv/themes/monochrome.cfg b/rtv/themes/monochrome.cfg index 8a5f5a6..4b8be66 100644 --- a/rtv/themes/monochrome.cfg +++ b/rtv/themes/monochrome.cfg @@ -2,47 +2,49 @@ [theme] ; = +@normal = default default +@highlight = default default reverse -bar_level_1 = default default -bar_level_2 = default default -bar_level_3 = default default -bar_level_4 = default default -comment_author = default default bold -comment_author_self = default default bold -comment_count = default default -comment_text = default default -created = default default -cursor = default default reverse -default = default default -downvote = default default bold -gold = default default bold -help_bar = default default bold+reverse -hidden_comment_expand = default default bold -hidden_comment_text = default default -multireddit_name = default default bold -multireddit_text = default default -neutral_vote = default default bold -notice_info = default default -notice_loading = default default -notice_error = default default -notice_success = default default -nsfw = default default bold -order_bar = default default bold -order_selected = default default bold+reverse -prompt = default default bold+reverse -saved = default default -score = default default -separator = default default bold -stickied = default default -subscription_name = default default bold -subscription_text = default default -submission_author = default default -submission_flair = default default bold -submission_subreddit = default default -submission_text = default default -submission_title = default default bold -title_bar = default default bold+reverse -upvote = default default bold -url = default default underline -url_seen = default default underline -user_flair = default default bold +bar_level_1 = - - +bar_level_2 = - - +bar_level_3 = - - +bar_level_4 = - - +comment_author = - - bold +comment_author_self = - - bold +comment_count = - - +comment_text = - - +created = - - +cursor = - - +cursor.highlight = - - reverse +downvote = - - bold +gold = - - bold +help_bar = - - bold+reverse +hidden_comment_expand = - - bold +hidden_comment_text = - - +multireddit_name = - - bold +multireddit_text = - - +neutral_vote = - - bold +notice_info = - - +notice_loading = - - +notice_error = - - +notice_success = - - +nsfw = - - bold +order_bar = - - bold +order_bar.highlight = - - bold+reverse +prompt = - - bold+reverse +saved = - - +score = - - +separator = - - bold +stickied = - - +subscription_name = - - bold +subscription_text = - - +submission_author = - - +submission_flair = - - bold +submission_subreddit = - - +submission_text = - - +submission_title = - - bold +title_bar = - - bold+reverse +upvote = - - bold +url = - - underline +url_seen = - - underline +user_flair = - - bold diff --git a/rtv/themes/solarized-dark.cfg b/rtv/themes/solarized-dark.cfg index a008898..1922f99 100644 --- a/rtv/themes/solarized-dark.cfg +++ b/rtv/themes/solarized-dark.cfg @@ -19,47 +19,53 @@ [theme] ; = +@normal = ansi_244 ansi_234 +@highlight = ansi_244 ansi_235 -bar_level_1 = ansi_125 ansi_234 -bar_level_2 = ansi_160 ansi_234 -bar_level_3 = ansi_61 ansi_234 -bar_level_4 = ansi_37 ansi_234 -comment_author = ansi_33 ansi_234 bold -comment_author_self = ansi_64 ansi_234 bold -comment_count = ansi_244 ansi_234 -comment_text = ansi_244 ansi_234 -created = ansi_244 ansi_234 -cursor = ansi_244 ansi_234 reverse -default = ansi_244 ansi_234 -downvote = ansi_160 ansi_234 bold -gold = ansi_136 ansi_234 bold -help_bar = ansi_37 ansi_234 bold+reverse -hidden_comment_expand = ansi_240 ansi_234 bold -hidden_comment_text = ansi_240 ansi_234 -multireddit_name = ansi_245 ansi_234 bold -multireddit_text = ansi_240 ansi_234 -neutral_vote = ansi_244 ansi_234 bold -notice_info = ansi_244 ansi_234 bold -notice_loading = ansi_244 ansi_234 bold -notice_error = ansi_160 ansi_234 bold -notice_success = ansi_64 ansi_234 bold -nsfw = ansi_125 ansi_234 bold+reverse -order_bar = ansi_240 ansi_235 bold -order_selected = ansi_240 ansi_235 bold+reverse -prompt = ansi_33 ansi_234 bold+reverse -saved = ansi_125 ansi_234 -score = ansi_244 ansi_234 -separator = ansi_244 ansi_234 bold -stickied = ansi_136 ansi_234 -subscription_name = ansi_245 ansi_234 bold -subscription_text = ansi_240 ansi_234 -submission_author = ansi_64 ansi_234 bold -submission_flair = ansi_160 ansi_234 -submission_subreddit = ansi_166 ansi_234 -submission_text = ansi_244 ansi_234 -submission_title = ansi_245 ansi_234 bold -title_bar = ansi_37 ansi_234 bold+reverse -upvote = ansi_64 ansi_234 bold -url = ansi_33 ansi_234 underline -url_seen = ansi_61 ansi_234 underline -user_flair = ansi_136 ansi_234 bold +bar_level_1 = ansi_125 - +bar_level_1.highlight = ansi_125 - reverse +bar_level_2 = ansi_160 - +bar_level_2.highlight = ansi_125 - reverse +bar_level_3 = ansi_61 - +bar_level_3.highlight = ansi_125 - reverse +bar_level_4 = ansi_37 - +bar_level_4.highlight = ansi_125 - reverse +comment_author = ansi_33 - bold +comment_author_self = ansi_64 - bold +comment_count = - - +comment_text = - - +created = - - +cursor = - - +cursor.highlight = ansi_240 - reverse +downvote = ansi_160 - bold +gold = ansi_136 - bold +help_bar = ansi_37 - bold+reverse +hidden_comment_expand = ansi_245 - bold +hidden_comment_text = ansi_245 - +multireddit_name = ansi_240 - bold +multireddit_text = ansi_245 - +neutral_vote = - - bold +notice_info = - - bold +notice_loading = - - bold +notice_error = ansi_160 - bold +notice_success = ansi_64 - bold +nsfw = ansi_125 - bold+reverse +order_bar = ansi_245 - bold +order_bar.highlight = ansi_245 - bold+reverse +prompt = ansi_33 - bold+reverse +saved = ansi_125 - +score = - - +separator = - - bold +stickied = ansi_136 - +subscription_name = ansi_240 - bold +subscription_text = ansi_245 - +submission_author = ansi_64 - bold +submission_flair = ansi_160 - +submission_subreddit = ansi_166 - +submission_text = - - +submission_title = ansi_244 - bold +title_bar = ansi_37 - bold+reverse +upvote = ansi_64 - bold +url = ansi_33 - underline +url_seen = ansi_61 - underline +user_flair = ansi_136 - bold \ No newline at end of file diff --git a/rtv/themes/solarized-light.cfg b/rtv/themes/solarized-light.cfg index 946e2a5..25cecf7 100644 --- a/rtv/themes/solarized-light.cfg +++ b/rtv/themes/solarized-light.cfg @@ -19,47 +19,53 @@ [theme] ; = +@normal = ansi_241 ansi_230 +@highlight = ansi_241 ansi_254 -bar_level_1 = ansi_125 ansi_230 -bar_level_2 = ansi_160 ansi_230 -bar_level_3 = ansi_61 ansi_230 -bar_level_4 = ansi_37 ansi_230 -comment_author = ansi_33 ansi_230 bold -comment_author_self = ansi_64 ansi_230 bold -comment_count = ansi_241 ansi_230 -comment_text = ansi_241 ansi_230 -created = ansi_241 ansi_230 -cursor = ansi_244 ansi_230 reverse -default = ansi_241 ansi_230 -downvote = ansi_160 ansi_230 bold -gold = ansi_136 ansi_230 bold -help_bar = ansi_37 ansi_230 bold+reverse -hidden_comment_expand = ansi_245 ansi_230 bold -hidden_comment_text = ansi_245 ansi_230 -multireddit_name = ansi_240 ansi_230 bold -multireddit_text = ansi_245 ansi_230 -neutral_vote = ansi_241 ansi_230 bold -notice_info = ansi_241 ansi_230 bold -notice_loading = ansi_241 ansi_230 bold -notice_error = ansi_160 ansi_230 bold -notice_success = ansi_64 ansi_230 bold -nsfw = ansi_125 ansi_230 bold+reverse -order_bar = ansi_245 ansi_254 bold -order_selected = ansi_245 ansi_254 bold+reverse -prompt = ansi_33 ansi_230 bold+reverse -saved = ansi_125 ansi_230 -score = ansi_241 ansi_230 -separator = ansi_241 ansi_230 bold -stickied = ansi_136 ansi_230 -subscription_name = ansi_240 ansi_230 bold -subscription_text = ansi_245 ansi_230 -submission_author = ansi_64 ansi_230 bold -submission_flair = ansi_160 ansi_230 -submission_subreddit = ansi_166 ansi_230 -submission_text = ansi_241 ansi_230 -submission_title = ansi_240 ansi_230 bold -title_bar = ansi_37 ansi_230 bold+reverse -upvote = ansi_64 ansi_230 bold -url = ansi_33 ansi_230 underline -url_seen = ansi_61 ansi_230 underline -user_flair = ansi_136 ansi_230 bold +bar_level_1 = ansi_125 - +bar_level_1.highlight = ansi_125 - reverse +bar_level_2 = ansi_160 - +bar_level_2.highlight = ansi_125 - reverse +bar_level_3 = ansi_61 - +bar_level_3.highlight = ansi_125 - reverse +bar_level_4 = ansi_37 - +bar_level_4.highlight = ansi_125 - reverse +comment_author = ansi_33 - bold +comment_author_self = ansi_64 - bold +comment_count = - - +comment_text = - - +created = - - +cursor = - - +cursor.highlight = ansi_245 - reverse +downvote = ansi_160 - bold +gold = ansi_136 - bold +help_bar = ansi_37 - bold+reverse +hidden_comment_expand = ansi_245 - bold +hidden_comment_text = ansi_245 - +multireddit_name = ansi_240 - bold +multireddit_text = ansi_245 - +neutral_vote = - - bold +notice_info = - - bold +notice_loading = - - bold +notice_error = ansi_160 - bold +notice_success = ansi_64 - bold +nsfw = ansi_125 - bold+reverse +order_bar = ansi_245 - bold +order_bar.highlight = ansi_245 - bold+reverse +prompt = ansi_33 - bold+reverse +saved = ansi_125 - +score = - - +separator = - - bold +stickied = ansi_136 - +subscription_name = ansi_240 - bold +subscription_text = ansi_245 - +submission_author = ansi_64 - bold +submission_flair = ansi_160 - +submission_subreddit = ansi_166 - +submission_text = - - +submission_title = ansi_240 - bold +title_bar = ansi_37 - bold+reverse +upvote = ansi_64 - bold +url = ansi_33 - underline +url_seen = ansi_61 - underline +user_flair = ansi_136 - bold diff --git a/tests/test_theme.py b/tests/test_theme.py index 4756ff7..022af54 100644 --- a/tests/test_theme.py +++ b/tests/test_theme.py @@ -144,8 +144,6 @@ def test_theme_initialize_attributes(stdscr): for element in Theme.DEFAULT_THEME: assert isinstance(theme.get(element), int) - assert theme.get_bar_level(0) == theme.get_bar_level(4) - def test_theme_initialize_attributes_monochrome(stdscr):