From 9602e95e5e82a61878d868c3ddbd85db096442d9 Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Fri, 23 Jun 2023 23:44:59 +0200 Subject: [PATCH 01/20] initial support for config based ikhal theming --- khal/settings/khal.spec | 18 ++++++++++++++++++ khal/settings/utils.py | 19 +++++++++++++++++++ khal/ui/__init__.py | 15 +++++++++++++-- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/khal/settings/khal.spec b/khal/settings/khal.spec index fb9e50f64..1be9f0ddd 100644 --- a/khal/settings/khal.spec +++ b/khal/settings/khal.spec @@ -324,3 +324,21 @@ multiple_on_overflow = boolean(default=False) # actually disables highlighting for events that should use the # default color. default_color = color(default='') + +# Override ikhal's color theme with a custom palette. This is useful to style +# certain elements of ikhal individually. +# Palette entries take the form of `key = foreground, background, mono, +# foreground_high, background_high` where foreground and background are used in +# "low color mode" and foreground_high and background_high are used in "high +# color mode" and mono if only monocolor is supported. If you don't want to set +# a value for a certain color, use an empty string (`''`). +# Valid entries for low color mode are listed on the urwid website_. +# _http://urwid.org/manual/displayattributes.html#standard-foreground-colors For +# high color mode you can use any valid 24-bit color value, e.g. `'#ff0000'`. +# NOTE: 24-bit colors must be enclosed in single quotes to be parsed correctly, +# otherwise the `#` will be interpreted as a comment. +# Most modern terminals should support high color mode. +# Example entry: `header = light red, default, default, #ff0000, default` +# See the default palettes in `khal/ui/colors.py` for all available keys. +# If you can't theme an element in ikhal, please open an issue on github. +[palette] diff --git a/khal/settings/utils.py b/khal/settings/utils.py index f01c26fdf..b175ab652 100644 --- a/khal/settings/utils.py +++ b/khal/settings/utils.py @@ -215,6 +215,17 @@ def get_vdir_type(_: str) -> str: # TODO implement return 'calendar' +def validate_palette_entry(attr, definition: str) -> bool: + if len(definition) not in (2, 3, 5): + logging.error('Invalid color definition for %s: %s, must be of length, 2, 3, or 5', + attr, definition) + return False + if (definition[0] not in COLORS and definition[0] != '') or \ + (definition[1] not in COLORS and definition[1] != ''): + logging.error('Invalid color definition for %s: %s, must be one of %s', + attr, definition, COLORS.keys()) + return False + return True def config_checks( config, @@ -263,3 +274,11 @@ def config_checks( if config['calendars'][calendar]['color'] == 'auto': config['calendars'][calendar]['color'] = \ _get_color_from_vdir(config['calendars'][calendar]['path']) + + # check palette settings + valid_palette = True + for attr in config.get('palette', []): + valid_palette = valid_palette and validate_palette_entry(attr, config['palette'][attr]) + if not valid_palette: + logger.fatal('Invalid palette entry') + raise InvalidSettingsError() diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py index 1a93f78a8..ea3919d45 100644 --- a/khal/ui/__init__.py +++ b/khal/ui/__init__.py @@ -1342,8 +1342,19 @@ def emit(self, record): logger.addHandler(header_handler) frame.open(pane, callback) - palette = _add_calendar_colors( - getattr(colors, pane._conf['view']['theme']), pane.collection) + palette = _add_calendar_colors(getattr(colors, pane._conf['view']['theme']), pane.collection) + + def merge_palettes(pallete_a, pallete_b) -> List[Tuple[str, str, str, str, str]]: + """Merge two palettes together, with the second palette taking priority.""" + merged = {} + for entry in pallete_a: + merged[entry[0]] = entry + for entry in pallete_b: + merged[entry[0]] = entry + return list(merged.values()) + + overwrite = [(key, *values) for key, values in pane._conf['palette'].items()] + palette = merge_palettes(palette, overwrite) loop = urwid.MainLoop( widget=frame, palette=palette, From c182b1c55ed92c5e3e15eb0338afe0bdeadad8ff Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Sat, 24 Jun 2023 00:05:53 +0200 Subject: [PATCH 02/20] ikhal: switch from 256 to 2**24 colors --- khal/ui/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py index ea3919d45..27e6ee4ee 100644 --- a/khal/ui/__init__.py +++ b/khal/ui/__init__.py @@ -1387,9 +1387,9 @@ def check_for_updates(loop, pane): loop.set_alarm_in(60, check_for_updates, pane) loop.set_alarm_in(60, check_for_updates, pane) - # Make urwid use 256 color mode. + # Make urwid use 2**24 color mode. loop.screen.set_terminal_properties( - colors=256, bright_is_bold=pane._conf['view']['bold_for_light_color']) + colors=2**24, bright_is_bold=pane._conf['view']['bold_for_light_color']) def ctrl_c(signum, f): raise urwid.ExitMainLoop() From 3ce53aaf3aaf1db0f3360dbce4635958d07058de Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Sat, 24 Jun 2023 00:07:03 +0200 Subject: [PATCH 03/20] drive by typehinting --- khal/settings/utils.py | 6 ++---- khal/ui/__init__.py | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/khal/settings/utils.py b/khal/settings/utils.py index b175ab652..4dac92987 100644 --- a/khal/settings/utils.py +++ b/khal/settings/utils.py @@ -69,11 +69,10 @@ def is_timedelta(string: str) -> dt.timedelta: raise VdtValueError(f"Invalid timedelta: {string}") -def weeknumber_option(option: str) -> Union[str, Literal[False]]: +def weeknumber_option(option: str) -> Union[Literal['left', 'right'], Literal[False]]: """checks if *option* is a valid value :param option: the option the user set in the config file - :type option: str :returns: 'off', 'left', 'right' or False """ option = option.lower() @@ -89,11 +88,10 @@ def weeknumber_option(option: str) -> Union[str, Literal[False]]: "'off', 'left' or 'right'") -def monthdisplay_option(option: str) -> str: +def monthdisplay_option(option: str) -> Literal['firstday', 'firstfullweek']: """checks if *option* is a valid value :param option: the option the user set in the config file - :returns: firstday, firstfullweek """ option = option.lower() if option == 'firstday': diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py index 27e6ee4ee..56a425fd1 100644 --- a/khal/ui/__init__.py +++ b/khal/ui/__init__.py @@ -1202,8 +1202,7 @@ def new_event(self, date, end): self.eventscolumn.original_widget.new(date, end) -def _urwid_palette_entry( - name: str, color: str, hmethod: str) -> Tuple[str, str, str, str, str, str]: +def _urwid_palette_entry(name: str, color: str, hmethod: str) -> Tuple[str, str, str, str, str, str]: """Create an urwid compatible palette entry. :param name: name of the new attribute in the palette From 7c748646972d1c2cefd9138919e43371f6af9172 Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Sat, 24 Jun 2023 23:53:26 +0200 Subject: [PATCH 04/20] fix: make FocusLineBoxWidth themeable with `frame` and `frame focus` --- khal/ui/widgets.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/khal/ui/widgets.py b/khal/ui/widgets.py index 7be6413b9..a3f6fd434 100644 --- a/khal/ui/widgets.py +++ b/khal/ui/widgets.py @@ -582,29 +582,31 @@ def changed(self): class FocusLineBoxWidth(urwid.WidgetDecoration, urwid.WidgetWrap): def __init__(self, widget) -> None: - hline = urwid.Divider('─') - hline_focus = urwid.Divider('━') - self._vline = urwid.SolidFill('│') - self._vline_focus = urwid.SolidFill('┃') + # we cheat here with the attrs, if we use thick dividers we apply the + # focus attr group. We probably should fix this in render() + hline = urwid.AttrMap(urwid.Divider('─'), 'frame') + hline_focus = urwid.AttrMap(urwid.Divider('━'), 'frame focus') + self._vline = urwid.AttrMap(urwid.SolidFill('│'), 'frame') + self._vline_focus = urwid.AttrMap(urwid.SolidFill('┃'), 'frame focus') self._topline = urwid.Columns([ - ('fixed', 1, urwid.Text('┌')), + ('fixed', 1, urwid.AttrMap(urwid.Text('┌'), 'frame')), hline, - ('fixed', 1, urwid.Text('┐')), + ('fixed', 1, urwid.AttrMap(urwid.Text('┐'), 'frame')), ]) self._topline_focus = urwid.Columns([ - ('fixed', 1, urwid.Text('┏')), + ('fixed', 1, urwid.AttrMap(urwid.Text('┏'), 'frame focus')), hline_focus, - ('fixed', 1, urwid.Text('┓')), + ('fixed', 1, urwid.AttrMap(urwid.Text('┓'), 'frame focus')), ]) self._bottomline = urwid.Columns([ - ('fixed', 1, urwid.Text('└')), + ('fixed', 1, urwid.AttrMap(urwid.Text('└'), 'frame')), hline, - ('fixed', 1, urwid.Text('┘')), + ('fixed', 1, urwid.AttrMap(urwid.Text('┘'), 'frame')), ]) self._bottomline_focus = urwid.Columns([ - ('fixed', 1, urwid.Text('┗')), + ('fixed', 1, urwid.AttrMap(urwid.Text('┗'), 'frame focus')), hline_focus, - ('fixed', 1, urwid.Text('┛')), + ('fixed', 1, urwid.AttrMap(urwid.Text('┛'), 'frame focus')), ]) self._middle = urwid.Columns( [('fixed', 1, self._vline), widget, ('fixed', 1, self._vline)], From fc953f7e64313a72770811de9b91318b1972e78b Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Sun, 25 Jun 2023 23:58:47 +0200 Subject: [PATCH 05/20] CAttrMap exposes all properties --- khal/ui/widgets.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/khal/ui/widgets.py b/khal/ui/widgets.py index a3f6fd434..e19fc5866 100644 --- a/khal/ui/widgets.py +++ b/khal/ui/widgets.py @@ -712,14 +712,9 @@ def button(*args, class CAttrMap(urwid.AttrMap): - """A variant of AttrMap that exposes the some properties of the original widget""" - @property - def active(self): - return self.original_widget.active - - @property - def changed(self): - return self.original_widget.changed + """A variant of AttrMap that exposes all properties of the original widget""" + def __getattr__(self, name): + return getattr(self.original_widget, name) class CPadding(urwid.Padding): From 30320065e8003fc7ed271c0da7ba9724c5235ddf Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Sun, 25 Jun 2023 23:59:24 +0200 Subject: [PATCH 06/20] ikhal: make calendar and event list theme-able --- khal/ui/__init__.py | 8 ++++---- khal/ui/colors.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py index 56a425fd1..123f1b1ef 100644 --- a/khal/ui/__init__.py +++ b/khal/ui/__init__.py @@ -35,7 +35,7 @@ from . import colors from .base import Pane, Window from .editor import EventEditor, ExportDialog -from .widgets import CalendarWidget, NColumns, NPile, button, linebox +from .widgets import CalendarWidget, CAttrMap, NColumns, NPile, button, linebox from .widgets import ExtendedEdit as Edit logger = logging.getLogger('khal') @@ -1076,8 +1076,8 @@ def __init__(self, collection, conf=None, title: str='', description: str='') -> toggle_delete_instance=self.toggle_delete_instance, dynamic_days=self._conf['view']['dynamic_days'], ) - self.eventscolumn = ContainerWidget(EventColumn(pane=self, elistbox=elistbox)) - calendar = CalendarWidget( + self.eventscolumn = ContainerWidget(CAttrMap(EventColumn(pane=self, elistbox=elistbox), 'eventcolumn', 'eventcolumn focus')) + calendar = CAttrMap(CalendarWidget( on_date_change=self.eventscolumn.original_widget.set_focus_date, keybindings=self._conf['keybindings'], on_press={key: self.new_event for key in self._conf['keybindings']['new']}, @@ -1085,7 +1085,7 @@ def __init__(self, collection, conf=None, title: str='', description: str='') -> weeknumbers=self._conf['locale']['weeknumbers'], monthdisplay=self._conf['view']['monthdisplay'], get_styles=collection.get_styles - ) + ), 'calendar', 'calendar focus') if self._conf['view']['dynamic_days']: elistbox.set_focus_date_callback = calendar.set_focus_date else: diff --git a/khal/ui/colors.py b/khal/ui/colors.py index f218dd93f..2da115f53 100644 --- a/khal/ui/colors.py +++ b/khal/ui/colors.py @@ -52,6 +52,11 @@ ('frame focus color', 'dark blue', 'black'), ('frame focus top', 'dark magenta', 'black'), + ('eventcolumn', '', '', ''), + ('eventcolumn focus', '', '', ''), + ('calendar', '', '', ''), + ('calendar focus', '', '', ''), + ('editbx', 'light gray', 'dark blue'), ('editcp', 'black', 'light gray', 'standout'), ('popupbg', 'white', 'black', 'bold'), @@ -90,6 +95,11 @@ ('frame focus color', 'dark blue', 'white'), ('frame focus top', 'dark magenta', 'white'), + ('eventcolumn', '', '', ''), + ('eventcolumn focus', '', '', ''), + ('calendar', '', '', ''), + ('calendar focus', '', '', ''), + ('editbx', 'light gray', 'dark blue'), ('editcp', 'black', 'light gray', 'standout'), ('popupbg', 'white', 'black', 'bold'), From 3faa5452ab893aaff5b0247949db19b48e903782 Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Mon, 26 Jun 2023 23:39:48 +0200 Subject: [PATCH 07/20] urwid_palette_entry can apply additional background/foreground colors --- khal/ui/__init__.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py index 123f1b1ef..7382a47e1 100644 --- a/khal/ui/__init__.py +++ b/khal/ui/__init__.py @@ -24,7 +24,7 @@ import signal import sys from enum import IntEnum -from typing import Dict, List, Optional, Tuple +from typing import Dict, List, Literal, Optional, Tuple import click import urwid @@ -1202,11 +1202,20 @@ def new_event(self, date, end): self.eventscolumn.original_widget.new(date, end) -def _urwid_palette_entry(name: str, color: str, hmethod: str) -> Tuple[str, str, str, str, str, str]: +def _urwid_palette_entry( + name: str, color: str, hmethod: str, color_mode: Literal['256color', 'rgb'], + foreground: str = '', background: str = '', +) -> Tuple[str, str, str, str, str, str]: """Create an urwid compatible palette entry. :param name: name of the new attribute in the palette :param color: color for the new attribute + :param hmethod: which highlighting mode to use, foreground or background + :param color_mode: which color mode we are in, if we are in 256-color mode, + we transform 24-bit/RGB colors to a (somewhat) matching 256-color set color + :param foreground: the foreground color to apply if we use background highlighting method + :param background: the background color to apply if we use foreground highlighting method + :returns: an urwid palette entry """ from ..terminal import COLORS @@ -1217,9 +1226,8 @@ def _urwid_palette_entry(name: str, color: str, hmethod: str) -> Tuple[str, str, # Colors from the 256 color palette need to be prefixed with h in # urwid. color = 'h' + color - else: - # 24-bit colors are not supported by urwid. - # Convert it to some color on the 256 color palette that might resemble + elif color_mode == '256color': + # Convert to some color on the 256 color palette that might resemble # the 24-bit color. # First, generate the palette (indices 16-255 only). This assumes, that # the terminal actually uses the same palette, which may or may not be @@ -1262,9 +1270,9 @@ def _urwid_palette_entry(name: str, color: str, hmethod: str) -> Tuple[str, str, # We unconditionally add the color to the high color slot. It seems to work # in lower color terminals as well. if hmethod in ['fg', 'foreground']: - return (name, '', '', '', color, '') + return (name, '', '', '', color, background) else: - return (name, '', '', '', '', color) + return (name, '', '', '', foreground, color) def _add_calendar_colors(palette: List, collection: CalendarCollection) -> List: From ba20dbb6b90a03780acc9525e010ccbf49336bf5 Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Mon, 26 Jun 2023 23:41:36 +0200 Subject: [PATCH 08/20] add_calendar_colors also adds background and foreground colors --- khal/ui/__init__.py | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py index 7382a47e1..982b8ba53 100644 --- a/khal/ui/__init__.py +++ b/khal/ui/__init__.py @@ -1275,29 +1275,44 @@ def _urwid_palette_entry( return (name, '', '', '', foreground, color) -def _add_calendar_colors(palette: List, collection: CalendarCollection) -> List: +def _add_calendar_colors( + palette: List[Tuple[str, ...]], + collection: 'CalendarCollection', + color_mode: Literal['256colors', 'rgb'], + base: Optional[str] = None, + attr_template: str = 'calendar {}', +) -> List[Tuple[str, ...]]: """Add the colors for the defined calendars to the palette. + We support setting a fixed background or foreground color that we extract + from a giving attribute + :param palette: the base palette :param collection: + :param color_mode: which color mode we are in + :param base: the attribute to extract the background and foreground color from + :param attr_template: the template to use for the attribute name :returns: the modified palette """ + bg_color, fg_color = '', '' + for attr in palette: + if base and attr[0] == base: + bg_color = attr[5] + fg_color = attr[4] + for cal in collection.calendars: if cal['color'] == '': # No color set for this calendar, use default_color instead. color = collection.default_color else: color = cal['color'] - palette.append(_urwid_palette_entry('calendar ' + cal['name'], color, - collection.hmethod)) - palette.append(_urwid_palette_entry('highlight_days_color', - collection.color, collection.hmethod)) - palette.append(_urwid_palette_entry('highlight_days_multiple', - collection.multiple, collection.hmethod)) + palette.append(_urwid_palette_entry('calendar ' + cal['name'], color, collection.hmethod, color_mode=color_mode, foreground=fg_color, background=bg_color)) + palette.append(_urwid_palette_entry('highlight_days_color', collection.color, collection.hmethod, color_mode=color_mode, foreground=fg_color, background=bg_color)) + palette.append(_urwid_palette_entry('highlight_days_multiple', collection.multiple, collection.hmethod, color_mode=color_mode, foreground=fg_color, background=bg_color)) return palette -def start_pane(pane, callback, program_info='', quit_keys=None): +def start_pane(pane, callback, program_info='', quit_keys=None, color_mode: Literal['rgb', '256colors']='rgb'): """Open the user interface with the given initial pane.""" quit_keys = quit_keys or ['q'] @@ -1349,7 +1364,7 @@ def emit(self, record): logger.addHandler(header_handler) frame.open(pane, callback) - palette = _add_calendar_colors(getattr(colors, pane._conf['view']['theme']), pane.collection) + palette = _add_calendar_colors(getattr(colors, pane._conf['view']['theme']), pane.collection, color_mode=color_mode) def merge_palettes(pallete_a, pallete_b) -> List[Tuple[str, str, str, str, str]]: """Merge two palettes together, with the second palette taking priority.""" @@ -1394,9 +1409,9 @@ def check_for_updates(loop, pane): loop.set_alarm_in(60, check_for_updates, pane) loop.set_alarm_in(60, check_for_updates, pane) - # Make urwid use 2**24 color mode. - loop.screen.set_terminal_properties( - colors=2**24, bright_is_bold=pane._conf['view']['bold_for_light_color']) + + colors_ = 2**24 if color_mode == 'rgb' else 256 + loop.screen.set_terminal_properties(colors=colors_, bright_is_bold=pane._conf['view']['bold_for_light_color']) def ctrl_c(signum, f): raise urwid.ExitMainLoop() From ff5a2bd31a8d9528f865332482c5ade4f1c167ac Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Tue, 27 Jun 2023 00:48:26 +0200 Subject: [PATCH 09/20] aply `edit focus` focus_map to all edit fields --- khal/ui/__init__.py | 2 +- khal/ui/colors.py | 6 ++---- khal/ui/editor.py | 12 ++++++------ 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py index 982b8ba53..3582eb832 100644 --- a/khal/ui/__init__.py +++ b/khal/ui/__init__.py @@ -772,7 +772,7 @@ def update_colors(new_start: dt.date, new_end: dt.date, everything: bool=False): ) else: self.editor = True - editor = EventEditor(self.pane, event, update_colors, always_save=always_save) + editor = CAttrMap(EventEditor(self.pane, event, update_colors, always_save=always_save), 'editor', 'editor focus') ContainerWidget = linebox[self.pane._conf['view']['frame']] new_pane = urwid.Columns([ diff --git a/khal/ui/colors.py b/khal/ui/colors.py index 2da115f53..415f81ff5 100644 --- a/khal/ui/colors.py +++ b/khal/ui/colors.py @@ -29,7 +29,7 @@ ('list', 'black', 'white'), ('list focused', 'white', 'light blue', 'bold'), ('edit', 'black', 'white'), - ('edit focused', 'white', 'light blue', 'bold'), + ('edit focus', 'white', 'light blue', 'bold'), ('button', 'black', 'dark cyan'), ('button focused', 'white', 'light blue', 'bold'), @@ -44,7 +44,6 @@ ('dayname', 'light gray', ''), ('monthname', 'light gray', ''), ('weeknumber_right', 'light gray', ''), - ('edit', 'white', 'dark blue'), ('alert', 'white', 'dark red'), ('mark', 'white', 'dark green'), ('frame', 'white', 'black'), @@ -72,7 +71,7 @@ ('list', 'black', 'white'), ('list focused', 'white', 'light blue', 'bold'), ('edit', 'black', 'white'), - ('edit focused', 'white', 'light blue', 'bold'), + ('edit focus', 'white', 'light blue', 'bold'), ('button', 'black', 'dark cyan'), ('button focused', 'white', 'light blue', 'bold'), @@ -87,7 +86,6 @@ ('dayname', 'dark gray', 'white'), ('monthname', 'dark gray', 'white'), ('weeknumber_right', 'dark gray', 'white'), - ('edit', 'white', 'dark blue'), ('alert', 'white', 'dark red'), ('mark', 'white', 'dark green'), ('frame', 'dark gray', 'white'), diff --git a/khal/ui/editor.py b/khal/ui/editor.py index fa6498f13..4430515b4 100644 --- a/khal/ui/editor.py +++ b/khal/ui/editor.py @@ -369,7 +369,7 @@ def __init__(self, pane, event, save_callback=None, always_save=False) -> None: self.event.recurobject, self._conf, event.start_local, ) self.summary = urwid.AttrMap(ExtendedEdit( - caption=('caption', 'Title: '), edit_text=event.summary), 'edit' + caption=('caption', 'Title: '), edit_text=event.summary), 'edit', 'edit focus', ) divider = urwid.Divider(' ') @@ -388,13 +388,13 @@ def decorate_choice(c): edit_text=self.description, multiline=True ), - 'edit' + 'edit', 'edit focus', ) self.location = urwid.AttrMap(ExtendedEdit( - caption=('caption', 'Location: '), edit_text=self.location), 'edit' + caption=('caption', 'Location: '), edit_text=self.location), 'edit', 'edit focus', ) self.categories = urwid.AttrMap(ExtendedEdit( - caption=('caption', 'Categories: '), edit_text=self.categories), 'edit' + caption=('caption', 'Categories: '), edit_text=self.categories), 'edit', 'edit focus', ) self.attendees = urwid.AttrMap( ExtendedEdit( @@ -402,10 +402,10 @@ def decorate_choice(c): edit_text=self.attendees, multiline=True ), - 'edit' + 'edit', 'edit focus', ) self.url = urwid.AttrMap(ExtendedEdit( - caption=('caption', 'URL: '), edit_text=self.url), 'edit' + caption=('caption', 'URL: '), edit_text=self.url), 'edit', 'edit focus', ) self.alarms = AlarmsEditor(self.event) self.pile = NListBox(urwid.SimpleFocusListWalker([ From 4a0315a85bb6a74fc50f9e5c8db846317c7b79d5 Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Tue, 27 Jun 2023 23:52:06 +0200 Subject: [PATCH 10/20] apply more attributes --- khal/ui/__init__.py | 8 ++++---- khal/ui/editor.py | 6 ++++-- khal/ui/widgets.py | 7 ++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py index 3582eb832..483c034e3 100644 --- a/khal/ui/__init__.py +++ b/khal/ui/__init__.py @@ -772,13 +772,13 @@ def update_colors(new_start: dt.date, new_end: dt.date, everything: bool=False): ) else: self.editor = True - editor = CAttrMap(EventEditor(self.pane, event, update_colors, always_save=always_save), 'editor', 'editor focus') + editor = EventEditor(self.pane, event, update_colors, always_save=always_save) ContainerWidget = linebox[self.pane._conf['view']['frame']] new_pane = urwid.Columns([ - ('weight', 2, ContainerWidget(editor)), - ('weight', 1, ContainerWidget(self.dlistbox)) - ], dividechars=2, focus_column=0) + ('weight', 2, CAttrMap(ContainerWidget(editor), 'editor', 'editor focus')), + ('weight', 1, CAttrMap(ContainerWidget(self.dlistbox), 'reveal focus')), + ], dividechars=0, focus_column=0) new_pane.title = editor.title def teardown(data): diff --git a/khal/ui/editor.py b/khal/ui/editor.py index 4430515b4..65c7dbe93 100644 --- a/khal/ui/editor.py +++ b/khal/ui/editor.py @@ -43,6 +43,7 @@ button, ) +from typing import Dict, List, Tuple class StartEnd: @@ -88,7 +89,8 @@ def on_change(new_date): weeknumbers=self._weeknumbers, monthdisplay=self._monthdisplay, initial=initial_date) - pop_up = urwid.LineBox(pop_up) + pop_up = CAttrMap(pop_up, 'calendar', ' calendar focus') + pop_up = CAttrMap(urwid.LineBox(pop_up), 'calendar', 'calendar focus') return pop_up def get_pop_up_parameters(self): @@ -125,7 +127,7 @@ def __init__( on_date_change=on_date_change) wrapped = CalendarPopUp(self._edit, on_date_change, weeknumbers, firstweekday, monthdisplay, keybindings) - padded = urwid.Padding(wrapped, align='left', width=datewidth, left=0, right=1) + padded = CAttrMap(urwid.Padding(wrapped, align='left', width=datewidth, left=0, right=1), 'calendar', 'calendar focus') super().__init__(padded) def _validate(self, text): diff --git a/khal/ui/widgets.py b/khal/ui/widgets.py index e19fc5866..8b0819c73 100644 --- a/khal/ui/widgets.py +++ b/khal/ui/widgets.py @@ -252,6 +252,7 @@ def __init__(self, parent, callback=lambda: None) -> None: button( parent._decorate(c), attr_map='popupbg', + focus_map='popupbg focus', on_press=self.set_choice, user_data=c, ) @@ -533,7 +534,7 @@ def __init__(self, alarm, delete_handler) -> None: (21, self.duration), (14, urwid.Padding(self.direction, right=1)), self.description, - (10, urwid.Button('Delete', on_press=delete_handler, user_data=self)), + (10, button('Delete', on_press=delete_handler, user_data=self)), ]) urwid.WidgetWrap.__init__(self, self.columns) @@ -552,7 +553,7 @@ def __init__(self, event) -> None: self.pile = NPile( [urwid.Text('Alarms:')] + [self.AlarmEditor(a, self.remove_alarm) for a in event.alarms] + - [urwid.Columns([(12, urwid.Button('Add', on_press=self.add_alarm))])]) + [urwid.Columns([(12, button('Add', on_press=self.add_alarm))])]) urwid.WidgetWrap.__init__(self, self.pile) @@ -701,7 +702,7 @@ def render(self, size, focus): } def button(*args, - attr_map: str='button', focus_map='button focused', + attr_map: str='button', focus_map='button focus', padding_left=0, padding_right=0, **kwargs): """wrapping an urwid button in attrmap and padding""" From 2778db5980346b1af0152ab834955db1c2623fc1 Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Tue, 27 Jun 2023 23:53:38 +0200 Subject: [PATCH 11/20] drive-by typing --- khal/ui/__init__.py | 91 +++++++++++++++++++++++++++++++++------------ khal/ui/editor.py | 17 +++++---- khal/ui/widgets.py | 14 +++---- 3 files changed, 84 insertions(+), 38 deletions(-) diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py index 483c034e3..498f9c918 100644 --- a/khal/ui/__init__.py +++ b/khal/ui/__init__.py @@ -84,13 +84,13 @@ class DateConversionError(Exception): class SelectableText(urwid.Text): - def selectable(self): + def selectable(self) -> bool: return True - def keypress(self, size, key): + def keypress(self, size: Tuple[int], key: Optional[str]) -> Optional[str]: return key - def get_cursor_coords(self, size): + def get_cursor_coords(self, size: Tuple[int]) -> Tuple[int, int]: return 0, 0 def render(self, size, focus=False): @@ -140,7 +140,7 @@ def relative_day(self, day: dt.date, dtformat: str) -> str: return f'{weekday}, {daystr} ({approx_delta})' - def keypress(self, _, key: str) -> str: + def keypress(self, size: Tuple[int], key: Optional[str]) -> Optional[str]: binds = self._conf['keybindings'] if key in binds['left']: key = 'left' @@ -219,7 +219,7 @@ def set_title(self, mark: str=' ') -> None: self.set_text(mark + ' ' + text.replace('\n', newline)) - def keypress(self, _, key: str) -> str: + def keypress(self, size: Tuple[int], key: Optional[str]) -> Optional[str]: binds = self._conf['keybindings'] if key in binds['left']: key = 'left' @@ -250,7 +250,7 @@ def __init__( self.set_focus_date_callback = set_focus_date_callback super().__init__(*args, **kwargs) - def keypress(self, size, key): + def keypress(self, size: Tuple[int], key: Optional[str]) -> Optional[str]: return super().keypress(size, key) @property @@ -306,7 +306,7 @@ def ensure_date(self, day: dt.date) -> None: self.body.ensure_date(day) self.clean() - def keypress(self, size, key): + def keypress(self, size: Tuple[int], key: Optional[str]) -> Optional[str]: if key in self._conf['keybindings']['up']: key = 'up' if key in self._conf['keybindings']['down']: @@ -610,7 +610,7 @@ def set_selected_date(self, day: dt.date) -> None: title.set_text(title.get_text()[0]) @property - def focus_event(self): + def focus_event(self) -> Optional[U_Event]: if self.body.focus == 0: return None else: @@ -649,7 +649,7 @@ def __init__(self, elistbox, pane) -> None: urwid.WidgetWrap.__init__(self, self.container) @property - def focus_event(self): + def focus_event(self) -> Optional[U_Event]: """returns the event currently in focus""" return self.dlistbox.focus_event @@ -677,7 +677,7 @@ def focus_date(self, date: dt.date) -> None: self._last_focused_date = date self.dlistbox.ensure_date(date) - def update(self, min_date, max_date, everything): + def update(self, min_date, max_date: dt.date, everything: bool): """update DateListBox if `everything` is True, reset all displayed dates, else only those between @@ -692,7 +692,7 @@ def update(self, min_date, max_date, everything): max_date = self.dlistbox.body.last_date self.dlistbox.body.update_range(min_date, max_date) - def refresh_titles(self, min_date, max_date, everything): + def refresh_titles(self, min_date: dt.date, max_date: dt.date, everything: bool) -> None: """refresh titles in DateListBoxes if `everything` is True, reset all displayed dates, else only those between @@ -700,7 +700,7 @@ def refresh_titles(self, min_date, max_date, everything): """ self.dlistbox.refresh_titles(min_date, max_date, everything) - def update_date_line(self): + def update_date_line(self) -> None: """refresh titles in DateListBoxes""" self.dlistbox.update_date_line() @@ -859,6 +859,8 @@ def duplicate(self) -> None: # because their title is determined by X-BIRTHDAY and X-FNAME properties # which are also copied. If the events' summary is edited it will show # up on disk but not be displayed in khal + if self.focus_event is None: + return None event = self.focus_event.event.duplicate() try: self.pane.collection.insert(event) @@ -910,7 +912,7 @@ def new(self, date: dt.date, end: Optional[dt.date]=None) -> None: def selectable(self): return True - def keypress(self, size, key): + def keypress(self, size: Tuple[int], key: Optional[str]) -> Optional[str]: prev_shown = self._eventshown self._eventshown = False self.clear_event_view() @@ -1021,7 +1023,7 @@ def __init__(self, search_func, abort_func) -> None: class Search(Edit): - def keypress(self, size, key): + def keypress(self, size: Tuple[int], key: Optional[str]) -> Optional[str]: if key == 'enter': search_func(self.text) else: @@ -1076,7 +1078,12 @@ def __init__(self, collection, conf=None, title: str='', description: str='') -> toggle_delete_instance=self.toggle_delete_instance, dynamic_days=self._conf['view']['dynamic_days'], ) - self.eventscolumn = ContainerWidget(CAttrMap(EventColumn(pane=self, elistbox=elistbox), 'eventcolumn', 'eventcolumn focus')) + self.eventscolumn = ContainerWidget( + CAttrMap(EventColumn(pane=self, elistbox=elistbox), + 'eventcolumn', + 'eventcolumn focus', + ), + ) calendar = CAttrMap(CalendarWidget( on_date_change=self.eventscolumn.original_widget.set_focus_date, keybindings=self._conf['keybindings'], @@ -1140,7 +1147,7 @@ def cleanup(self, data): event = self.collection.delete_instance(href, etag, account, rec_id) updated_etags[event.href] = event.etag - def keypress(self, size, key: str): + def keypress(self, size: Tuple[int], key: Optional[str]) -> Optional[str]: binds = self._conf['keybindings'] if key in binds['search']: self.search() @@ -1203,7 +1210,7 @@ def new_event(self, date, end): def _urwid_palette_entry( - name: str, color: str, hmethod: str, color_mode: Literal['256color', 'rgb'], + name: str, color: str, hmethod: str, color_mode: Literal['256colors', 'rgb'], foreground: str = '', background: str = '', ) -> Tuple[str, str, str, str, str, str]: """Create an urwid compatible palette entry. @@ -1306,13 +1313,43 @@ def _add_calendar_colors( color = collection.default_color else: color = cal['color'] - palette.append(_urwid_palette_entry('calendar ' + cal['name'], color, collection.hmethod, color_mode=color_mode, foreground=fg_color, background=bg_color)) - palette.append(_urwid_palette_entry('highlight_days_color', collection.color, collection.hmethod, color_mode=color_mode, foreground=fg_color, background=bg_color)) - palette.append(_urwid_palette_entry('highlight_days_multiple', collection.multiple, collection.hmethod, color_mode=color_mode, foreground=fg_color, background=bg_color)) + entry = _urwid_palette_entry( + attr_template.format(cal['name']), + color, + collection.hmethod, + color_mode=color_mode, + foreground=fg_color, + background=bg_color, + ) + palette.append(entry) + + entry = _urwid_palette_entry( + 'highlight_days_color', + collection.color, + collection.hmethod, + color_mode=color_mode, + foreground=fg_color, + background=bg_color, + ) + palette.append(entry) + entry = _urwid_palette_entry('highlight_days_multiple', + collection.multiple, + collection.hmethod, + color_mode=color_mode, + foreground=fg_color, + background=bg_color) + palette.append(entry) + return palette -def start_pane(pane, callback, program_info='', quit_keys=None, color_mode: Literal['rgb', '256colors']='rgb'): +def start_pane( + pane, + callback, + program_info='', + quit_keys=None, + color_mode: Literal['rgb', '256colors']='rgb', +): """Open the user interface with the given initial pane.""" quit_keys = quit_keys or ['q'] @@ -1364,9 +1401,17 @@ def emit(self, record): logger.addHandler(header_handler) frame.open(pane, callback) - palette = _add_calendar_colors(getattr(colors, pane._conf['view']['theme']), pane.collection, color_mode=color_mode) + theme = getattr(colors, pane._conf['view']['theme']) + palette = _add_calendar_colors( + theme, pane.collection, color_mode=color_mode, + base='calendar', attr_template='calendar {}', + ) + palette = _add_calendar_colors( + palette, pane.collection, color_mode=color_mode, + base='popupbg', attr_template='calendar {} popup', + ) - def merge_palettes(pallete_a, pallete_b) -> List[Tuple[str, str, str, str, str]]: + def merge_palettes(pallete_a, pallete_b) -> List[Tuple[str, ...]]: """Merge two palettes together, with the second palette taking priority.""" merged = {} for entry in pallete_a: diff --git a/khal/ui/editor.py b/khal/ui/editor.py index 65c7dbe93..713eca0f6 100644 --- a/khal/ui/editor.py +++ b/khal/ui/editor.py @@ -20,7 +20,7 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import datetime as dt -from typing import Callable, Dict, List, Literal, Optional +from typing import Callable, Dict, List, Literal, Optional, Tuple import urwid @@ -43,7 +43,6 @@ button, ) -from typing import Dict, List, Tuple class StartEnd: @@ -56,7 +55,7 @@ def __init__(self, startdate, starttime, enddate, endtime) -> None: class CalendarPopUp(urwid.PopUpLauncher): - def __init__(self, widget, on_date_change, weeknumbers=False, + def __init__(self, widget, on_date_change, weeknumbers: Literal['left', 'right', False]=False, firstweekday=0, monthdisplay='firstday', keybindings=None) -> None: self._on_date_change = on_date_change self._weeknumbers = weeknumbers @@ -127,10 +126,13 @@ def __init__( on_date_change=on_date_change) wrapped = CalendarPopUp(self._edit, on_date_change, weeknumbers, firstweekday, monthdisplay, keybindings) - padded = CAttrMap(urwid.Padding(wrapped, align='left', width=datewidth, left=0, right=1), 'calendar', 'calendar focus') + padded = CAttrMap( + urwid.Padding(wrapped, align='left', width=datewidth, left=0, right=1), + 'calendar', 'calendar focus', + ) super().__init__(padded) - def _validate(self, text): + def _validate(self, text: str): try: _date = dt.datetime.strptime(text, self._dateformat).date() except ValueError: @@ -376,8 +378,9 @@ def __init__(self, pane, event, save_callback=None, always_save=False) -> None: divider = urwid.Divider(' ') - def decorate_choice(c): - return ('calendar ' + c['name'], c['name']) + def decorate_choice(c) -> Tuple[str, str]: + return ('calendar ' + c['name'] + ' popup', c['name']) + self.calendar_chooser= CAttrMap(Choice( [self.collection._calendars[c] for c in self.collection.writable_names], self.collection._calendars[self.event.calendar], diff --git a/khal/ui/widgets.py b/khal/ui/widgets.py index 8b0819c73..9f12dcc09 100644 --- a/khal/ui/widgets.py +++ b/khal/ui/widgets.py @@ -26,7 +26,7 @@ """ import datetime as dt import re -from typing import Optional, Tuple +from typing import List, Optional, Tuple import urwid @@ -77,7 +77,7 @@ def goto_end_of_line(text): class ExtendedEdit(urwid.Edit): """A text editing widget supporting some more editing commands""" - def keypress(self, size, key: str) -> Optional[Tuple[Tuple[int, int], str]]: + def keypress(self, size: Tuple[int], key: Optional[str]) -> Optional[str]: if key == 'ctrl w': self._delete_word() elif key == 'ctrl u': @@ -201,7 +201,8 @@ def _get_current_value(self): class Choice(urwid.PopUpLauncher): def __init__( - self, choices, active, decorate_func=None, overlay_width=32, callback=lambda: None, + self, choices: List[str], active: str, + decorate_func=None, overlay_width: int=32, callback=lambda: None, ) -> None: self.choices = choices self._callback = callback @@ -211,9 +212,7 @@ def __init__( def create_pop_up(self): pop_up = ChoiceList(self, callback=self._callback) - urwid.connect_signal( - pop_up, 'close', lambda button: self.close_pop_up(), - ) + urwid.connect_signal(pop_up, 'close', lambda button: self.close_pop_up()) return pop_up def get_pop_up_parameters(self): @@ -235,8 +234,7 @@ def active(self, val): self._active = val self.button = urwid.Button(self._decorate(self._active)) urwid.PopUpLauncher.__init__(self, self.button) - urwid.connect_signal(self.button, 'click', - lambda button: self.open_pop_up()) + urwid.connect_signal(self.button, 'click', lambda button: self.open_pop_up()) class ChoiceList(urwid.WidgetWrap): From 39abc408767e7bbfb3cd712b193220af27946020 Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Wed, 5 Jul 2023 10:23:34 +0200 Subject: [PATCH 12/20] fix type for EventColumn.dlistbox --- khal/ui/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py index 498f9c918..5d77cd366 100644 --- a/khal/ui/__init__.py +++ b/khal/ui/__init__.py @@ -644,7 +644,7 @@ def __init__(self, elistbox, pane) -> None: self.delete_status = pane.delete_status self.toggle_delete_all = pane.toggle_delete_all self.toggle_delete_instance = pane.toggle_delete_instance - self.dlistbox: DateListBox = elistbox + self.dlistbox: DListBox = elistbox self.container = urwid.Pile([self.dlistbox]) urwid.WidgetWrap.__init__(self, self.container) From f8a3176f04a555a2bcd4252a85e8beb6ea87a97e Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Tue, 24 Oct 2023 23:36:05 +0200 Subject: [PATCH 13/20] more typing --- khal/ui/editor.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/khal/ui/editor.py b/khal/ui/editor.py index 713eca0f6..41bf5d79d 100644 --- a/khal/ui/editor.py +++ b/khal/ui/editor.py @@ -161,14 +161,15 @@ def date(self, date): class StartEndEditor(urwid.WidgetWrap): """Widget for editing start and end times (of an event).""" - def __init__(self, start, end, conf, + def __init__(self, + start: dt.datetime, + end: dt.datetime, + conf, on_start_date_change=lambda x: None, on_end_date_change=lambda x: None, ) -> None: """ - :type start: datetime.datetime - :type end: datetime.datetime :param on_start_date_change: a callable that gets called everytime a new start date is entered, with that new date as an argument :param on_end_date_change: same as for on_start_date_change, just for the @@ -260,7 +261,7 @@ def _end_date_change(self, date): self._enddt = self.localize_end(dt.datetime.combine(date, self._end_time)) self.on_end_date_change(date) - def toggle(self, checkbox, state): + def toggle(self, checkbox, state: bool): """change from allday to datetime event :param checkbox: the checkbox instance that is used for toggling, gets @@ -268,7 +269,6 @@ def toggle(self, checkbox, state): :type checkbox: checkbox :param state: state the event will toggle to; True if allday event, False if datetime - :type state: bool """ if self.allday is True and state is False: @@ -340,14 +340,12 @@ def validate(self): class EventEditor(urwid.WidgetWrap): """Widget that allows Editing one `Event()`""" - def __init__(self, pane, event, save_callback=None, always_save=False) -> None: + def __init__(self, pane, event: 'khal.event.Event', save_callback=None, always_save: bool=False) -> None: """ - :type event: khal.event.Event :param save_callback: call when saving event with new start and end dates and recursiveness of original and edited event as parameters :type save_callback: callable :param always_save: save event even if it has not changed - :type always_save: bool """ self.pane = pane self.event = event @@ -559,7 +557,7 @@ def save(self, button): self._abort_confirmed = False self.pane.window.backtrack() - def keypress(self, size, key): + def keypress(self, size: Tuple[int], key: str) -> Optional[str]: if key in ['esc'] and self.changed and not self._abort_confirmed: self.pane.window.alert( ('light red', 'Unsaved changes! Hit ESC again to discard.')) From 8e37a96a5f5a7f433cfa795b4c8cab2afff3dfad Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Tue, 24 Oct 2023 23:38:25 +0200 Subject: [PATCH 14/20] make save button in editor themeable --- khal/ui/editor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/khal/ui/editor.py b/khal/ui/editor.py index 41bf5d79d..0e91a9e4d 100644 --- a/khal/ui/editor.py +++ b/khal/ui/editor.py @@ -808,8 +808,8 @@ def __init__(self, this_func, abort_func, event) -> None: caption='Location: ', edit_text="~/%s.ics" % event.summary.strip()) lines.append(export_location) lines.append(urwid.Divider(' ')) - lines.append( - urwid.Button('Save', on_press=this_func, user_data=export_location) - ) + lines.append(CAttrMap( + urwid.Button('Save', on_press=this_func, user_data=export_location), + 'button', 'button focus')) content = NPile(lines) urwid.WidgetWrap.__init__(self, urwid.LineBox(content)) From 223646d4b4c694f893929e9ebd73f9afde89a787 Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Tue, 24 Oct 2023 23:41:45 +0200 Subject: [PATCH 15/20] flake8ing --- khal/ui/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py index 5d77cd366..fde718f2e 100644 --- a/khal/ui/__init__.py +++ b/khal/ui/__init__.py @@ -1456,7 +1456,9 @@ def check_for_updates(loop, pane): loop.set_alarm_in(60, check_for_updates, pane) colors_ = 2**24 if color_mode == 'rgb' else 256 - loop.screen.set_terminal_properties(colors=colors_, bright_is_bold=pane._conf['view']['bold_for_light_color']) + loop.screen.set_terminal_properties( + colors=colors_, bright_is_bold=pane._conf['view']['bold_for_light_color'], + ) def ctrl_c(signum, f): raise urwid.ExitMainLoop() From ee47f754e0428c0147ef887af79c84b222c5d1f5 Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Tue, 24 Oct 2023 23:46:40 +0200 Subject: [PATCH 16/20] more typing for ui.color.py --- khal/ui/colors.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/khal/ui/colors.py b/khal/ui/colors.py index 415f81ff5..5cffb116d 100644 --- a/khal/ui/colors.py +++ b/khal/ui/colors.py @@ -19,13 +19,14 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +from typing import Dict, List, Tuple dark = [ ('header', 'white', 'black'), ('footer', 'white', 'black'), ('line header', 'black', 'white', 'bold'), ('alt header', 'white', '', 'bold'), - ('bright', 'dark blue', 'white', ('bold', 'standout')), + ('bright', 'dark blue', 'white', 'bold,standout'), ('list', 'black', 'white'), ('list focused', 'white', 'light blue', 'bold'), ('edit', 'black', 'white'), @@ -67,7 +68,7 @@ ('footer', 'black', 'white'), ('line header', 'black', 'white', 'bold'), ('alt header', 'black', '', 'bold'), - ('bright', 'dark blue', 'white', ('bold', 'standout')), + ('bright', 'dark blue', 'white', 'bold,standout'), ('list', 'black', 'white'), ('list focused', 'white', 'light blue', 'bold'), ('edit', 'black', 'white'), @@ -80,7 +81,7 @@ ('today', 'black', 'light gray'), ('date header', '', 'white'), - ('date header focused', 'white', 'dark gray', ('bold', 'standout')), + ('date header focused', 'white', 'dark gray', 'bold,standout'), ('date header selected', 'dark gray', 'light cyan'), ('dayname', 'dark gray', 'white'), @@ -104,3 +105,5 @@ ('popupper', 'black', 'light gray'), ('caption', 'black', '', ''), ] + +themes: Dict[str, List[Tuple[str, ...]]] = {'light': light, 'dark': dark} From a02202bb4cda03b1f628ebe7ad47764a0464220f Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Wed, 25 Oct 2023 14:49:28 +0200 Subject: [PATCH 17/20] deal with short urwid color attributes --- khal/ui/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py index fde718f2e..24d50fd35 100644 --- a/khal/ui/__init__.py +++ b/khal/ui/__init__.py @@ -1304,8 +1304,12 @@ def _add_calendar_colors( bg_color, fg_color = '', '' for attr in palette: if base and attr[0] == base: - bg_color = attr[5] - fg_color = attr[4] + if color_mode == 'rgb' and len(attr) >= 5: + bg_color = attr[5] + fg_color = attr[4] + else: + bg_color = attr[2] + fg_color = attr[1] for cal in collection.calendars: if cal['color'] == '': From 7dea132449510fcb7ec55debeb5b4d36437b519f Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Thu, 26 Oct 2023 08:49:28 +0200 Subject: [PATCH 18/20] make mypy happy --- khal/ui/__init__.py | 5 +++-- khal/ui/editor.py | 24 ++++++++++++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py index 24d50fd35..806501b43 100644 --- a/khal/ui/__init__.py +++ b/khal/ui/__init__.py @@ -639,7 +639,7 @@ def __init__(self, elistbox, pane) -> None: self.divider = urwid.Divider('─') self.editor = False self._last_focused_date: Optional[dt.date] = None - self._eventshown = False + self._eventshown: Optional[Tuple[str, str]] = None self.event_width = int(self.pane._conf['view']['event_view_weighting']) self.delete_status = pane.delete_status self.toggle_delete_all = pane.toggle_delete_all @@ -914,7 +914,7 @@ def selectable(self): def keypress(self, size: Tuple[int], key: Optional[str]) -> Optional[str]: prev_shown = self._eventshown - self._eventshown = False + self._eventshown = None self.clear_event_view() if key in self._conf['keybindings']['new']: @@ -1026,6 +1026,7 @@ class Search(Edit): def keypress(self, size: Tuple[int], key: Optional[str]) -> Optional[str]: if key == 'enter': search_func(self.text) + return None else: return super().keypress(size, key) diff --git a/khal/ui/editor.py b/khal/ui/editor.py index 0e91a9e4d..00a1a097d 100644 --- a/khal/ui/editor.py +++ b/khal/ui/editor.py @@ -20,7 +20,7 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import datetime as dt -from typing import Callable, Dict, List, Literal, Optional, Tuple +from typing import TYPE_CHECKING, Callable, Dict, List, Literal, Optional, Tuple import urwid @@ -43,6 +43,8 @@ button, ) +if TYPE_CHECKING: + import khal.khalendar.event class StartEnd: @@ -177,8 +179,10 @@ def __init__(self, """ self.allday = not isinstance(start, dt.datetime) self.conf = conf - self._startdt, self._original_start = start, start - self._enddt, self._original_end = end, end + self._startdt: dt.date = start + self._original_start: dt.date = start + self._enddt: dt.date = end + self._original_end: dt.date = end self.on_start_date_change = on_start_date_change self.on_end_date_change = on_end_date_change self._datewidth = len(start.strftime(self.conf['locale']['longdateformat'])) @@ -275,6 +279,8 @@ def toggle(self, checkbox, state: bool): self._startdt = dt.datetime.combine(self._startdt, dt.datetime.min.time()) self._enddt = dt.datetime.combine(self._enddt, dt.datetime.min.time()) elif self.allday is False and state is True: + assert isinstance(self._startdt, dt.datetime) + assert isinstance(self._enddt, dt.datetime) self._startdt = self._startdt.date() self._enddt = self._enddt.date() self.allday = state @@ -340,7 +346,13 @@ def validate(self): class EventEditor(urwid.WidgetWrap): """Widget that allows Editing one `Event()`""" - def __init__(self, pane, event: 'khal.event.Event', save_callback=None, always_save: bool=False) -> None: + def __init__( + self, + pane, + event: 'khal.khalendar.event.Event', + save_callback=None, + always_save: bool=False, + ) -> None: """ :param save_callback: call when saving event with new start and end dates and recursiveness of original and edited event as parameters @@ -562,12 +574,12 @@ def keypress(self, size: Tuple[int], key: str) -> Optional[str]: self.pane.window.alert( ('light red', 'Unsaved changes! Hit ESC again to discard.')) self._abort_confirmed = True - return + return None else: self._abort_confirmed = False if key in self.pane._conf['keybindings']['save']: self.save(None) - return + return None return super().keypress(size, key) From 9d6144e50252cc74510b24041062a86fb9d7132a Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Thu, 26 Oct 2023 09:41:15 +0200 Subject: [PATCH 19/20] CHANGELOG entry --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 789168ec5..273190075 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,8 @@ not released yet * optimization in ikhal when editing events in the far future or past * FIX an issue in ikhal with updating the view of the event list after editing an event +* NEW properties of ikhal themes (dark and light) can now be overriden from the + config file (via the new [palette] section, check the documenation) 0.11.2 ====== From d7265002ca47705390a1f609945b23f49278a7a9 Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Thu, 26 Oct 2023 09:41:24 +0200 Subject: [PATCH 20/20] better docstring --- khal/settings/khal.spec | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/khal/settings/khal.spec b/khal/settings/khal.spec index 1be9f0ddd..ec4e8eefd 100644 --- a/khal/settings/khal.spec +++ b/khal/settings/khal.spec @@ -257,7 +257,7 @@ blank_line_before_day = boolean(default=False) # `khal/settings/khal.spec` in the section `[default]` of the property `theme`. # # __ http://urwid.org/manual/displayattributes.html -# .. _github: # https://github.com/pimutils/khal/issues +# .. _github: https://github.com/pimutils/khal/issues theme = option('dark', 'light', default='dark') # Whether to show a visible frame (with *box drawing* characters) around some @@ -332,13 +332,28 @@ default_color = color(default='') # "low color mode" and foreground_high and background_high are used in "high # color mode" and mono if only monocolor is supported. If you don't want to set # a value for a certain color, use an empty string (`''`). -# Valid entries for low color mode are listed on the urwid website_. -# _http://urwid.org/manual/displayattributes.html#standard-foreground-colors For +# Valid entries for low color mode are listed on the `urwid website +# `_. For # high color mode you can use any valid 24-bit color value, e.g. `'#ff0000'`. -# NOTE: 24-bit colors must be enclosed in single quotes to be parsed correctly, -# otherwise the `#` will be interpreted as a comment. +# +# .. note:: +# 24-bit colors must be enclosed in single quotes to be parsed correctly, +# otherwise the `#` will be interpreted as a comment. +# # Most modern terminals should support high color mode. -# Example entry: `header = light red, default, default, #ff0000, default` +# +# Example entry (particular ugly): +# +# .. highlight:: ini +# +# :: +# +# [palette] +# header = light red, default, default, '#ff0000', default +# edit = '', '', 'bold', '#FF00FF', '#12FF14' +# footer = '', '', '', '#121233', '#656599' +# # See the default palettes in `khal/ui/colors.py` for all available keys. -# If you can't theme an element in ikhal, please open an issue on github. +# If you can't theme an element in ikhal, please open an issue on `github +# `_. [palette]