Refactoring the monochrome stuff
This commit is contained in:
@@ -17,6 +17,8 @@ Basic Commands
|
||||
:``q``/``Q``: Quit/Force quit
|
||||
:``y``: Copy submission permalink to clipboard
|
||||
:``Y``: Copy submission link to clipboard
|
||||
:``F2``: Cycle to the previous color theme
|
||||
:``F3``: Cycle to the next color theme
|
||||
|
||||
----------------------
|
||||
Authenticated Commands
|
||||
|
||||
@@ -174,12 +174,15 @@ def main():
|
||||
try:
|
||||
with curses_session() as stdscr:
|
||||
|
||||
if config['theme']:
|
||||
theme = Theme.from_name(config['theme'], config['monochrome'])
|
||||
else:
|
||||
theme = Theme(monochrome=config['monochrome'])
|
||||
term = Terminal(stdscr, config)
|
||||
|
||||
if config['theme']:
|
||||
theme = Theme.from_name(config['theme'])
|
||||
else:
|
||||
theme = None
|
||||
|
||||
term.set_theme(theme, monochrome=config['monochrome'])
|
||||
|
||||
term = Terminal(stdscr, config, theme)
|
||||
|
||||
with term.loader('Initializing', catch_exception=False):
|
||||
reddit = praw.Reddit(user_agent=user_agent,
|
||||
|
||||
@@ -60,6 +60,8 @@ https://github.com/michael-lazar/rtv
|
||||
b : Display urls with urlview
|
||||
y : Copy submission permalink to clipboard
|
||||
Y : Copy submission link to clipboard
|
||||
F2 : Cycle to previous theme
|
||||
F3 : Cycle to next theme
|
||||
|
||||
[Prompt]
|
||||
The `/` prompt accepts subreddits in the following formats
|
||||
@@ -87,7 +89,6 @@ BANNER_SEARCH = """
|
||||
[1]relevance [2]top [3]comments [4]new
|
||||
"""
|
||||
|
||||
|
||||
FOOTER_SUBREDDIT = """
|
||||
[?]Help [q]Quit [l]Comments [/]Prompt [u]Login [o]Open [c]Post [a/z]Vote
|
||||
"""
|
||||
|
||||
40
rtv/page.py
40
rtv/page.py
@@ -11,6 +11,7 @@ import six
|
||||
from kitchen.text.display import textual_width
|
||||
|
||||
from . import docs
|
||||
from .theme import Theme
|
||||
from .objects import Controller, Command
|
||||
from .clipboard import copy
|
||||
from .exceptions import TemporaryFileError, ProgramError
|
||||
@@ -54,7 +55,6 @@ 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
|
||||
@@ -91,23 +91,19 @@ class Page(object):
|
||||
def force_exit(self):
|
||||
sys.exit()
|
||||
|
||||
@PageController.register(Command('PREVIOUS_THEME'))
|
||||
def previous_theme(self):
|
||||
theme = Theme()
|
||||
self.term.set_theme(theme)
|
||||
self.draw()
|
||||
self.term.show_notification(theme.name, timeout=1)
|
||||
|
||||
@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)
|
||||
theme = Theme()
|
||||
self.term.set_theme(theme)
|
||||
self.draw()
|
||||
self.term.show_notification(new_theme.name, timeout=1)
|
||||
self.term.show_notification(theme.name, timeout=1)
|
||||
|
||||
@PageController.register(Command('HELP'))
|
||||
def show_help(self):
|
||||
@@ -466,7 +462,7 @@ class Page(object):
|
||||
if self.content.order is not None:
|
||||
order = self.content.order.split('-')[0]
|
||||
col = text.find(order) - 3
|
||||
attr = self.term.theme.get('order_bar', modifier='selected')
|
||||
attr = self.term.theme.get('order_bar', modifier='highlight')
|
||||
window.chgat(0, col, 3, attr)
|
||||
|
||||
self._row += 1
|
||||
@@ -537,13 +533,13 @@ class Page(object):
|
||||
# to draw the content
|
||||
for index, (win, data, inverted) in enumerate(self._subwindows):
|
||||
if index == self.nav.cursor_index:
|
||||
# This lets the theme know to invert the cursor
|
||||
modifier = 'selected'
|
||||
win.bkgd(str(' '), self.term.attr('@highlight'))
|
||||
# This lets the theme know to invert the cursor color and
|
||||
# apply any other special highlighting effects to the window
|
||||
with self.term.theme.set_modifier('highlight'):
|
||||
self._draw_item(win, data, inverted)
|
||||
else:
|
||||
modifier = None
|
||||
|
||||
with self.term.theme.set_modifier(modifier):
|
||||
win.bkgd(str(' '), self.term.attr('normal'))
|
||||
win.bkgd(str(' '), self.term.attr('@normal'))
|
||||
self._draw_item(win, data, inverted)
|
||||
|
||||
self._row += win_n_rows
|
||||
|
||||
@@ -43,6 +43,11 @@ max_comment_cols = 120
|
||||
; Hide username if logged in, display "Logged in" instead
|
||||
hide_username = False
|
||||
|
||||
; Color theme, use "rtv --list-themes" to view a list of valid options.
|
||||
; This can be an absolute filepath, or the name of a theme file that has
|
||||
; been installed into either the custom of default theme paths.
|
||||
;theme = monokai
|
||||
|
||||
################
|
||||
# OAuth Settings
|
||||
################
|
||||
@@ -110,6 +115,7 @@ SORT_NEW = 4
|
||||
SORT_CONTROVERSIAL = 5
|
||||
MOVE_UP = k, <KEY_UP>
|
||||
MOVE_DOWN = j, <KEY_DOWN>
|
||||
PREVIOUS_THEME = <KEY_F2>
|
||||
NEXT_THEME = <KEY_F3>
|
||||
PAGE_UP = m, <KEY_PPAGE>, <NAK>
|
||||
PAGE_DOWN = n, <KEY_NPAGE>, <EOT>
|
||||
|
||||
@@ -51,14 +51,12 @@ class Terminal(object):
|
||||
RETURN = 10
|
||||
SPACE = 32
|
||||
|
||||
def __init__(self, stdscr, config, theme=None):
|
||||
def __init__(self, stdscr, config):
|
||||
|
||||
self.stdscr = stdscr
|
||||
self.config = config
|
||||
self.loader = LoadScreen(self)
|
||||
|
||||
self.theme = None
|
||||
self.set_theme(theme)
|
||||
self.theme = None # Initialized by term.set_theme()
|
||||
|
||||
self._display = None
|
||||
self._mailcap_dict = mailcap.getcaps()
|
||||
@@ -831,7 +829,7 @@ class Terminal(object):
|
||||
|
||||
return self.theme.get(element)
|
||||
|
||||
def set_theme(self, theme=None):
|
||||
def set_theme(self, theme=None, monochrome=False):
|
||||
"""
|
||||
Check that the terminal supports the provided theme, and applies
|
||||
the theme to the terminal if possible.
|
||||
@@ -839,11 +837,25 @@ class Terminal(object):
|
||||
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.
|
||||
|
||||
Using ``monochrome=True`` will force loading the current theme
|
||||
without any color support. The intention is that this be used as
|
||||
a fallback for the default theme to support the old --monochrome
|
||||
command line flag.
|
||||
"""
|
||||
monochrome = (not curses.has_colors())
|
||||
|
||||
if not monochrome and curses.has_colors():
|
||||
terminal_colors = curses.COLORS
|
||||
else:
|
||||
terminal_colors = 0
|
||||
|
||||
if theme is None:
|
||||
theme = Theme(monochrome=monochrome)
|
||||
theme = Theme()
|
||||
|
||||
elif monochrome:
|
||||
# No need to display a warning message if the user has
|
||||
# explicitly turned off support for colors
|
||||
pass
|
||||
|
||||
elif theme.required_color_pairs > curses.COLOR_PAIRS:
|
||||
_logger.warning(
|
||||
@@ -851,17 +863,17 @@ class Terminal(object):
|
||||
'supports %s color pairs, switching to default theme',
|
||||
theme.name, theme.required_color_pairs, self._term,
|
||||
curses.COLOR_PAIRS)
|
||||
theme = Theme(monochrome=monochrome)
|
||||
theme = Theme()
|
||||
|
||||
elif theme.required_colors > curses.COLORS:
|
||||
elif theme.required_colors > terminal_colors:
|
||||
_logger.warning(
|
||||
'Theme %s requires %s colors, but TERM %s only '
|
||||
'supports %s colors, switching to default theme',
|
||||
theme.name, theme.required_colors, self._term,
|
||||
curses.COLORS)
|
||||
theme = Theme(monochrome=monochrome)
|
||||
theme = Theme()
|
||||
|
||||
theme.bind_curses()
|
||||
theme.bind_curses(use_color=bool(terminal_colors))
|
||||
|
||||
# Apply the default color to the whole screen
|
||||
self.stdscr.bkgd(str(' '), theme.get('@normal'))
|
||||
|
||||
59
rtv/theme.py
59
rtv/theme.py
@@ -15,7 +15,8 @@ class Theme(object):
|
||||
|
||||
ATTRIBUTE_CODES = {
|
||||
'-': None,
|
||||
'': curses.A_NORMAL,
|
||||
'': None,
|
||||
'normal': curses.A_NORMAL,
|
||||
'bold': curses.A_BOLD,
|
||||
'reverse': curses.A_REVERSE,
|
||||
'underline': curses.A_UNDERLINE,
|
||||
@@ -48,10 +49,11 @@ class Theme(object):
|
||||
COLOR_CODES['ansi_{0}'.format(i)] = i
|
||||
|
||||
# For compatibility with as many terminals as possible, the default theme
|
||||
# can only use the 8 basic colors with the default background.
|
||||
# can only use the 8 basic colors with the default color as the background
|
||||
DEFAULT_THEME = {
|
||||
'@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),
|
||||
@@ -103,18 +105,15 @@ class Theme(object):
|
||||
|
||||
BAR_LEVELS = ['bar_level_1', 'bar_level_2', 'bar_level_3', 'bar_level_4']
|
||||
|
||||
def __init__(self, name='default', elements=None, monochrome=False):
|
||||
def __init__(self, name='default', elements=None):
|
||||
"""
|
||||
Params:
|
||||
name (str): A unique string that describes the theme
|
||||
elements (dict): The theme's element map, should be in the same
|
||||
format as Theme.DEFAULT_THEME.
|
||||
monochrome (bool): If true, force all color pairs to use the
|
||||
terminal's default foreground/background color.
|
||||
"""
|
||||
|
||||
self.name = name
|
||||
self.monochrome = monochrome
|
||||
self._color_pair_map = None
|
||||
self._attribute_map = None
|
||||
self._modifier = None
|
||||
@@ -125,19 +124,24 @@ class Theme(object):
|
||||
if elements is None:
|
||||
elements = self.DEFAULT_THEME.copy()
|
||||
|
||||
# Fill in missing elements
|
||||
# Fill in any keywords that are defined in the default theme but were
|
||||
# not passed into the elements dictionary.
|
||||
for key in self.DEFAULT_THEME.keys():
|
||||
|
||||
# Set undefined modifiers to the system default
|
||||
# The "@normal"/"@highlight" are special elements that act as
|
||||
# fallbacks for all of the other elements. They must always be
|
||||
# defined and can't have the colors/attribute empty by setting
|
||||
# them to "-" or None.
|
||||
if key.startswith('@'):
|
||||
if key not in elements:
|
||||
elements[key] = self.DEFAULT_THEME[key]
|
||||
continue
|
||||
|
||||
# Modifiers are handled below
|
||||
if key.endswith('.highlight'):
|
||||
continue
|
||||
|
||||
# Set undefined elements to bubble up to the modifier
|
||||
# Set undefined elements to fallback to the default color
|
||||
if key not in elements:
|
||||
elements[key] = (None, None, None)
|
||||
|
||||
@@ -146,7 +150,9 @@ class Theme(object):
|
||||
if modifier_key not in elements:
|
||||
elements[modifier_key] = elements[key]
|
||||
|
||||
# Replace ``None`` attributes with their default modifiers
|
||||
# At this point all of the possible keys should exist in the element map.
|
||||
# Now we can "bubble up" the undefined attributes to copy the default
|
||||
# of the @normal and @highlight modifiers.
|
||||
for key, val in elements.items():
|
||||
if key.endswith('.highlight'):
|
||||
default = elements['@highlight']
|
||||
@@ -160,33 +166,35 @@ class Theme(object):
|
||||
|
||||
self.elements = elements
|
||||
|
||||
if not self.monochrome:
|
||||
# Pre-calculate how many colors / color pairs the theme will need
|
||||
colors, color_pairs = set(), set()
|
||||
for fg, bg, _ in self.elements.values():
|
||||
colors.add(fg)
|
||||
colors.add(bg)
|
||||
color_pairs.add((fg, bg))
|
||||
|
||||
# Don't count the default fg/bg as a color pair
|
||||
# Don't count the default (-1, -1) as a color pair because it doesn't
|
||||
# need to be initialized by curses.init_pair().
|
||||
color_pairs.discard((-1, -1))
|
||||
self.required_color_pairs = len(color_pairs)
|
||||
|
||||
# Determine which color set the terminal needs to
|
||||
# support in order to be able to use the theme
|
||||
# Determine how many colors the terminal needs to support in order to
|
||||
# be able to use the theme. This uses the common breakpoints that 99%
|
||||
# of terminals follow and doesn't take into account 88 color themes.
|
||||
self.required_colors = None
|
||||
for marker in [0, 8, 16, 256]:
|
||||
if max(colors) < marker:
|
||||
self.required_colors = marker
|
||||
break
|
||||
|
||||
def bind_curses(self):
|
||||
def bind_curses(self, use_color=True):
|
||||
"""
|
||||
Bind the theme's colors to curses's internal color pair map.
|
||||
|
||||
This method must be called once (after curses has been initialized)
|
||||
before any element attributes can be accessed. Color codes and other
|
||||
special attributes will be mixed bitwise into a single value that
|
||||
can be understood by curses.
|
||||
can be passed into curses draw functions.
|
||||
"""
|
||||
self._color_pair_map = {}
|
||||
self._attribute_map = {}
|
||||
@@ -195,7 +203,7 @@ class Theme(object):
|
||||
fg, bg, attrs = item
|
||||
|
||||
color_pair = (fg, bg)
|
||||
if not self.monochrome and color_pair != (-1, -1):
|
||||
if use_color and color_pair != (-1, -1):
|
||||
# Curses limits the number of available color pairs, so we
|
||||
# need to reuse them if there are multiple elements with the
|
||||
# same foreground and background.
|
||||
@@ -211,16 +219,17 @@ class Theme(object):
|
||||
def get(self, element, modifier=None):
|
||||
"""
|
||||
Returns the curses attribute code for the given element.
|
||||
|
||||
If element is None, return the background code (e.g. @normal).
|
||||
"""
|
||||
if self._attribute_map is None:
|
||||
raise RuntimeError('Attempted to access theme attribute before '
|
||||
'calling initialize_curses_theme()')
|
||||
|
||||
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]
|
||||
|
||||
if modifier and not element.startswith('@'):
|
||||
element = element + '.' + modifier
|
||||
|
||||
return self._attribute_map[element]
|
||||
|
||||
@@ -298,7 +307,7 @@ class Theme(object):
|
||||
print('')
|
||||
|
||||
@classmethod
|
||||
def from_name(cls, name, monochrome=False, path=THEMES):
|
||||
def from_name(cls, name, path=THEMES):
|
||||
"""
|
||||
Search for the given theme on the filesystem and attempt to load it.
|
||||
|
||||
@@ -313,12 +322,12 @@ class Theme(object):
|
||||
|
||||
for filename in filenames:
|
||||
if os.path.isfile(filename):
|
||||
return cls.from_file(filename, monochrome)
|
||||
return cls.from_file(filename)
|
||||
|
||||
raise ConfigError('Could not find theme named "{0}"'.format(name))
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, filename, monochrome=False):
|
||||
def from_file(cls, filename):
|
||||
"""
|
||||
Load a theme from the specified configuration file.
|
||||
"""
|
||||
@@ -347,7 +356,7 @@ class Theme(object):
|
||||
continue
|
||||
elements[element] = cls._parse_line(element, line, filename)
|
||||
|
||||
return cls(theme_name, elements, monochrome)
|
||||
return cls(theme_name, elements)
|
||||
|
||||
@classmethod
|
||||
def _parse_line(cls, element, line, filename=None):
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
# RTV theme
|
||||
|
||||
[theme]
|
||||
;<element> = <foreground> <background> <attributes>
|
||||
@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
|
||||
@@ -1,50 +0,0 @@
|
||||
# RTV theme
|
||||
|
||||
[theme]
|
||||
;<element> = <foreground> <background> <attributes>
|
||||
@normal = default default
|
||||
@highlight = default default reverse
|
||||
|
||||
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
|
||||
@@ -19,8 +19,8 @@
|
||||
|
||||
[theme]
|
||||
;<element> = <foreground> <background> <attributes>
|
||||
@normal = ansi_244 ansi_234
|
||||
@highlight = ansi_244 ansi_235
|
||||
@normal = ansi_244 ansi_234 normal
|
||||
@highlight = ansi_244 ansi_235 normal
|
||||
|
||||
bar_level_1 = ansi_125 -
|
||||
bar_level_1.highlight = ansi_125 - reverse
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
|
||||
[theme]
|
||||
;<element> = <foreground> <background> <attributes>
|
||||
@normal = ansi_241 ansi_230
|
||||
@highlight = ansi_241 ansi_254
|
||||
@normal = ansi_241 ansi_230 normal
|
||||
@highlight = ansi_241 ansi_254 normal
|
||||
|
||||
bar_level_1 = ansi_125 -
|
||||
bar_level_1.highlight = ansi_125 - reverse
|
||||
|
||||
Reference in New Issue
Block a user