Daniel Brötzmann pushed to branch master at gajim / gajim
Commits: 5581a800 by wurstsalat at 2021-12-21T19:20:05+01:00 CSSConfig: Typing
- - - - - 2a337add by wurstsalat at 2021-12-21T19:20:05+01:00 Themes: Typing
- - - - -
2 changed files:
- gajim/gtk/css_config.py - gajim/gtk/themes.py
Changes:
===================================== gajim/gtk/css_config.py ===================================== @@ -15,8 +15,14 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see http://www.gnu.org/licenses/.
+from __future__ import annotations +from typing import Optional +from typing import Union +from typing import Tuple + import math import logging +from pathlib import Path
from gi.repository import Gtk from gi.repository import Gdk @@ -33,8 +39,8 @@ settings = Gtk.Settings.get_default()
-class CSSConfig(): - def __init__(self): +class CSSConfig: + def __init__(self) -> None: """ CSSConfig handles loading and storing of all relevant Gajim style files
@@ -69,24 +75,24 @@ def __init__(self): css_parser.ser.prefs.keepEmptyRules = False
# Holds the currently selected theme in the Theme Editor - self._pre_css = None - self._pre_css_path = None + self._pre_css: Optional[css_parser.css.CSSStyleSheet] = None + self._pre_css_path: Optional[Path] = None
# Holds the default theme, its used if values are not found # in the selected theme - self._default_css = None - self._default_css_path = None + self._default_css: Optional[css_parser.css.CSSStyleSheet] = None + self._default_css_path: Optional[Path] = None
# Holds the currently selected theme - self._css = None - self._css_path = None + self._css: Optional[css_parser.css.CSSStyleSheet] = None + self._css_path: Optional[Path] = None
# User Theme CSS Provider self._provider = Gtk.CssProvider()
# Used for dynamic classes like account colors self._dynamic_provider = Gtk.CssProvider() - self._dynamic_dict = {} + self._dynamic_dict: dict[str, str] = {} self.refresh()
Gtk.StyleContext.add_provider_for_screen( @@ -95,10 +101,10 @@ def __init__(self): CSSPriority.APPLICATION)
# Cache of recently requested values - self._cache = {} + self._cache: dict[str, Union[str, Pango.FontDescription, None]] = {}
# Holds all currently available themes - self.themes = [] + self.themes: list[str] = []
self.set_dark_theme() self._load_css() @@ -113,7 +119,7 @@ def __init__(self): CSSPriority.USER_THEME)
@property - def prefer_dark(self): + def prefer_dark(self) -> bool: setting = app.settings.get('dark_theme') if setting == Theme.SYSTEM: if settings is None: @@ -121,7 +127,7 @@ def prefer_dark(self): return settings.get_property('gtk-application-prefer-dark-theme') return setting == Theme.DARK
- def set_dark_theme(self, value=None): + def set_dark_theme(self, value: Optional[int] = None) -> None: if value is None: value = app.settings.get('dark_theme') else: @@ -135,7 +141,7 @@ def set_dark_theme(self, value=None): settings.set_property('gtk-application-prefer-dark-theme', bool(value)) self._load_css()
- def _load_css(self): + def _load_css(self) -> None: self._load_css_from_file('gajim.css', CSSPriority.APPLICATION) if self.prefer_dark: self._load_css_from_file('gajim-dark.css', @@ -146,7 +152,10 @@ def _load_css(self): self._load_css_from_file('default-dark.css', CSSPriority.DEFAULT_THEME_DARK)
- def _load_css_from_file(self, filename, priority): + def _load_css_from_file(self, + filename: str, + priority: CSSPriority + ) -> None: path = configpaths.get('STYLE') / filename try: with open(path, "r", encoding='utf8') as file_: @@ -156,7 +165,7 @@ def _load_css_from_file(self, filename, priority): return self._activate_css(css, priority)
- def _activate_css(self, css, priority): + def _activate_css(self, css: str, priority: CSSPriority) -> None: try: provider = Gtk.CssProvider() provider.load_from_data(bytes(css.encode('utf-8'))) @@ -170,7 +179,7 @@ def _activate_css(self, css, priority): log.exception('Error loading application css')
@staticmethod - def _pango_to_css_weight(number): + def _pango_to_css_weight(number: str) -> int: # Pango allows for weight values between 100 and 1000 # CSS allows only full hundred numbers like 100, 200 .. number = int(number) @@ -180,14 +189,14 @@ def _pango_to_css_weight(number): return 900 return int(math.ceil(number / 100.0)) * 100
- def _gather_available_themes(self): + def _gather_available_themes(self) -> None: files = configpaths.get('MY_THEME').iterdir() self.themes = [file.stem for file in files if file.suffix == '.css'] if 'default' in self.themes: # Ignore user created themes that are named 'default' self.themes.remove('default')
- def get_theme_path(self, theme, user=True): + def get_theme_path(self, theme: str, user: bool = True) -> Path: if theme == 'default' and self.prefer_dark: theme = 'default-dark'
@@ -195,7 +204,7 @@ def get_theme_path(self, theme, user=True): return configpaths.get('MY_THEME') / f'{theme}.css' return configpaths.get('STYLE') / f'{theme}.css'
- def _determine_theme_path(self): + def _determine_theme_path(self) -> Path: # Gets the path of the currently active theme. # If it does not exist, it falls back to the default theme theme = app.settings.get('roster_theme') @@ -211,23 +220,23 @@ def _determine_theme_path(self): log.info('Use Theme: %s', theme) return theme_path
- def _load_selected(self, new_path=None): + def _load_selected(self, new_path: Optional[Path] = None) -> None: if new_path is None: self._css_path = self._determine_theme_path() else: self._css_path = new_path self._css = css_parser.parseFile(self._css_path)
- def _load_default(self): + def _load_default(self) -> None: self._default_css_path = self.get_theme_path('default', user=False) self._default_css = css_parser.parseFile(self._default_css_path)
- def _load_pre(self, theme): + def _load_pre(self, theme: str) -> None: log.info('Preload theme %s', theme) self._pre_css_path = self.get_theme_path(theme) self._pre_css = css_parser.parseFile(self._pre_css_path)
- def _write(self, pre): + def _write(self, pre: bool) -> None: path = self._css_path css = self._css if pre: @@ -241,10 +250,16 @@ def _write(self, pre): self._load_selected() self._activate_theme()
- def set_value(self, selector, attr, value, pre=False): + def set_value(self, + selector: str, + attr: StyleAttr, + value: Union[str, Pango.FontDescription], + pre: bool = False + ) -> None: if attr == StyleAttr.FONT: # forward to set_font() for convenience - return self.set_font(selector, value, pre) + self.set_font(selector, value, pre) + return
if isinstance(attr, StyleAttr): attr = attr.value @@ -261,7 +276,7 @@ def set_value(self, selector, attr, value, pre=False): if not pre: self._add_to_cache(selector, attr, value) self._write(pre) - return None + return
# The rule was not found, so we add it to this theme log.info('Set %s %s %s', selector, attr, value) @@ -269,9 +284,12 @@ def set_value(self, selector, attr, value, pre=False): rule.style[attr] = value css.add(rule) self._write(pre) - return None
- def set_font(self, selector, description, pre=False): + def set_font(self, + selector: str, + description: Pango.FontDescription, + pre: bool = False + ) -> None: css = self._css if pre: css = self._pre_css @@ -285,7 +303,7 @@ def set_font(self, selector, description, pre=False): selector, family, size, style, weight) rule.style['font-family'] = family rule.style['font-style'] = style - rule.style['font-size'] = '%spt' % size + rule.style['font-size'] = f'{size}pt' rule.style['font-weight'] = weight
if not pre: @@ -300,19 +318,24 @@ def set_font(self, selector, description, pre=False): rule = css_parser.css.CSSStyleRule(selectorText=selector) rule.style['font-family'] = family rule.style['font-style'] = style - rule.style['font-size'] = '%spt' % size + rule.style['font-size'] = f'{size}pt' rule.style['font-weight'] = weight css.add(rule) self._write(pre)
- def _get_attr_from_description(self, description): + def _get_attr_from_description(self, + description: Pango.FontDescription + ) -> Tuple[Optional[str], int, str, int]: size = description.get_size() / Pango.SCALE style = self._get_string_from_pango_style(description.get_style()) - weight = self._pango_to_css_weight(int(description.get_weight())) + weight = self._pango_to_css_weight(description.get_weight()) family = description.get_family() return family, size, style, weight
- def _get_default_rule(self, selector, _attr): + def _get_default_rule(self, + selector: str, + _attr: StyleAttr + ) -> Optional[css_parser.css.CSSStyleRule]: for rule in self._default_css: if rule.type != rule.STYLE_RULE: continue @@ -321,7 +344,10 @@ def _get_default_rule(self, selector, _attr): return rule return None
- def get_font(self, selector, pre=False): + def get_font(self, + selector: str, + pre: bool = False + ) -> Optional[Pango.FontDescription]: if pre: css = self._pre_css else: @@ -353,7 +379,12 @@ def get_font(self, selector, pre=False): self._add_to_cache(selector, 'fontdescription', None) return None
- def _get_description_from_css(self, family, size, style, weight): + def _get_description_from_css(self, + family: Optional[str], + size: Optional[str], + style: Optional[str], + weight: Optional[str] + ) -> Optional[Pango.FontDescription]: if family is None: return None desc = Pango.FontDescription() @@ -367,7 +398,7 @@ def _get_description_from_css(self, family, size, style, weight): return desc
@staticmethod - def _get_pango_style_from_string(style: str) -> int: + def _get_pango_style_from_string(style: str) -> Pango.Style: if style == 'normal': return Pango.Style(0) if style == 'oblique': @@ -384,7 +415,11 @@ def _get_string_from_pango_style(style: Pango.Style) -> str: # Pango.Style.OBLIQUE: return 'oblique'
- def get_value(self, selector, attr, pre=False): + def get_value(self, + selector: str, + attr: StyleAttr, + pre: bool = False + ) -> Union[str, Pango.FontDescription, None]: if attr == StyleAttr.FONT: # forward to get_font() for convenience return self.get_font(selector, pre) @@ -422,10 +457,15 @@ def get_value(self, selector, attr, pre=False): return value return None
- def remove_value(self, selector, attr, pre=False): + def remove_value(self, + selector: str, + attr: StyleAttr, + pre: bool = False + ) -> None: if attr == StyleAttr.FONT: # forward to remove_font() for convenience - return self.remove_font(selector, pre) + self.remove_font(selector, pre) + return
if isinstance(attr, StyleAttr): attr = attr.value @@ -441,9 +481,8 @@ def remove_value(self, selector, attr, pre=False): rule.style.removeProperty(attr) break self._write(pre) - return None
- def remove_font(self, selector, pre=False): + def remove_font(self, selector: str, pre: bool = False) -> None: css = self._css if pre: css = self._pre_css @@ -460,7 +499,7 @@ def remove_font(self, selector, pre=False): break self._write(pre)
- def change_theme(self, theme): + def change_theme(self, theme: str) -> bool: user = not theme == 'default' theme_path = self.get_theme_path(theme, user=user) if not theme_path.exists(): @@ -472,7 +511,7 @@ def change_theme(self, theme): log.info('Change Theme: Successful switched to %s', theme) return True
- def change_preload_theme(self, theme): + def change_preload_theme(self, theme: str) -> bool: theme_path = self.get_theme_path(theme) if not theme_path.exists(): log.error('Change Preload Theme: Theme %s does not exist', @@ -482,7 +521,7 @@ def change_preload_theme(self, theme): log.info('Successful switched to %s', theme) return True
- def rename_theme(self, old_theme, new_theme): + def rename_theme(self, old_theme: str, new_theme: str) -> bool: if old_theme not in self.themes: log.error('Rename Theme: Old theme %s not found', old_theme) return False @@ -501,12 +540,12 @@ def rename_theme(self, old_theme, new_theme): old_theme, new_theme) return True
- def _activate_theme(self): + def _activate_theme(self) -> None: log.info('Activate theme') self._invalidate_cache() self._provider.load_from_data(self._css.cssText)
- def add_new_theme(self, theme): + def add_new_theme(self, theme: str) -> bool: theme_path = self.get_theme_path(theme) if theme_path.exists(): log.error('Add Theme: %s exists already', theme_path) @@ -517,32 +556,39 @@ def add_new_theme(self, theme): log.info('Add Theme: Successful added theme %s', theme) return True
- def remove_theme(self, theme): + def remove_theme(self, theme: str) -> None: theme_path = self.get_theme_path(theme) if theme_path.exists(): theme_path.unlink() self.themes.remove(theme) log.info('Remove Theme: Successful removed theme %s', theme)
- def _add_to_cache(self, selector, attr, value): + def _add_to_cache(self, + selector: str, + attr: StyleAttr, + value: Union[str, Pango.FontDescription, None] + ) -> None: self._cache[selector + attr] = value
- def _get_from_cache(self, selector, attr): + def _get_from_cache(self, + selector: str, + attr: StyleAttr + ) -> Union[str, Pango.FontDescription, None]: return self._cache[selector + attr]
- def _invalidate_cache(self): + def _invalidate_cache(self) -> None: self._cache = {}
- def refresh(self): + def refresh(self) -> None: css = '' accounts = app.settings.get_accounts() for index, account in enumerate(accounts): color = app.settings.get_account_setting(account, 'account_color') - css_class = 'gajim_class_%s' % index + css_class = f'gajim_class_{index}' css += '.%s { background-color: %s }\n' % (css_class, color) self._dynamic_dict[account] = css_class
self._dynamic_provider.load_from_data(css.encode())
- def get_dynamic_class(self, name): + def get_dynamic_class(self, name: str) -> str: return self._dynamic_dict[name]
===================================== gajim/gtk/themes.py ===================================== @@ -14,11 +14,16 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see http://www.gnu.org/licenses/.
-from collections import namedtuple +from __future__ import annotations +from typing import NamedTuple +from typing import Any +from typing import Union + from enum import IntEnum
from gi.repository import Gtk from gi.repository import Gdk +from gi.repository import Pango
from gajim.common import app from gajim.common.nec import NetworkEvent @@ -31,9 +36,14 @@ from .builder import get_builder from .util import get_app_window
-StyleOption = namedtuple('StyleOption', 'label selector attr')
-CSS_STYLE_OPTIONS = [ +class StyleOption(NamedTuple): + label: str + selector: str + attr: StyleAttr + + +CSS_STYLE_OPTIONS: list[StyleOption] = [ StyleOption(_('Conversation: Text Font'), '.gajim-conversation-text', StyleAttr.FONT), @@ -129,7 +139,7 @@ class Column(IntEnum):
class Themes(Gtk.ApplicationWindow): - def __init__(self, transient): + def __init__(self, transient: Gtk.Window) -> None: Gtk.ApplicationWindow.__init__(self) self.set_application(app.app) self.set_title(_('Gajim Themes')) @@ -154,11 +164,14 @@ def __init__(self, transient):
self._fill_choose_listbox()
- def _on_key_press(self, _widget, event): + def _on_key_press(self, + _widget: Gtk.Widget, + event: Gdk.EventKey + ) -> None: if event.keyval == Gdk.KEY_Escape: self.destroy()
- def _get_themes(self): + def _get_themes(self) -> None: current_theme = app.settings.get('roster_theme') for theme in app.css_config.themes: if theme == current_theme: @@ -166,7 +179,10 @@ def _get_themes(self): continue self._ui.theme_store.append([theme])
- def _on_theme_name_edit(self, _renderer, path, new_name): + def _on_theme_name_edit(self, + _renderer: Gtk.CellRendererText, + path: str, + new_name: str) -> None: iter_ = self._ui.theme_store.get_iter(path) old_name = self._ui.theme_store[iter_][Column.THEME]
@@ -194,10 +210,10 @@ def _on_theme_name_edit(self, _renderer, path, new_name): app.settings.set('roster_theme', new_name) self._ui.theme_store.set_value(iter_, Column.THEME, new_name)
- def _select_theme_row(self, iter_): + def _select_theme_row(self, iter_: Gtk.TreeIter) -> None: self._ui.theme_treeview.get_selection().select_iter(iter_)
- def _on_theme_selected(self, tree_selection): + def _on_theme_selected(self, tree_selection: Gtk.TreeSelection) -> None: store, iter_ = tree_selection.get_selected() if iter_ is None: self._clear_options() @@ -210,7 +226,7 @@ def _on_theme_selected(self, tree_selection): self._apply_theme(theme) app.nec.push_incoming_event(NetworkEvent('style-changed'))
- def _load_options(self): + def _load_options(self) -> None: self._ui.option_listbox.foreach(self._remove_option) for option in CSS_STYLE_OPTIONS: value = app.css_config.get_value( @@ -222,7 +238,7 @@ def _load_options(self): row = Option(option, value) self._ui.option_listbox.add(row)
- def _add_option(self, _listbox, row): + def _add_option(self, _listbox: Gtk.ListBox, row: Gtk.ListBoxRow) -> None: # Add theme if there is none store, _ = self._ui.theme_treeview.get_selection().get_selected() first = store.get_iter_first() @@ -242,18 +258,18 @@ def _add_option(self, _listbox, row): self._ui.option_listbox.add(row) self._ui.option_popover.popdown()
- def _clear_options(self): + def _clear_options(self) -> None: self._ui.option_listbox.foreach(self._remove_option)
- def _fill_choose_listbox(self): + def _fill_choose_listbox(self) -> None: for option in CSS_STYLE_OPTIONS: self._ui.choose_option_listbox.add(ChooseOption(option))
- def _remove_option(self, row): + def _remove_option(self, row: Gtk.ListBoxRow) -> None: self._ui.option_listbox.remove(row) row.destroy()
- def _on_add_new_theme(self, *args): + def _on_add_new_theme(self, *args: Any) -> None: name = self._create_theme_name() if not app.css_config.add_new_theme(name): return @@ -265,7 +281,7 @@ def _on_add_new_theme(self, *args): self._select_theme_row(iter_) self._apply_theme(name)
- def reload_roster_theme(self): + def reload_roster_theme(self) -> None: tree_selection = self._ui.theme_treeview.get_selection() store, iter_ = tree_selection.get_selected() if iter_ is None: @@ -274,25 +290,25 @@ def reload_roster_theme(self): self._apply_theme(theme)
@staticmethod - def _apply_theme(theme): + def _apply_theme(theme: str) -> None: app.settings.set('roster_theme', theme) app.css_config.change_theme(theme) app.nec.push_incoming_event(NetworkEvent('theme-update'))
@staticmethod - def _update_preferences_window(): + def _update_preferences_window() -> None: window = get_app_window('Preferences') if window is not None: window.update_theme_list()
@staticmethod - def _create_theme_name(): - i = 0 - while 'newtheme%s' % i in app.css_config.themes: - i += 1 - return 'newtheme%s' % i + def _create_theme_name() -> str: + index = 0 + while f'newtheme{index}' in app.css_config.themes: + index += 1 + return f'newtheme{index}'
- def _on_remove_theme(self, *args): + def _on_remove_theme(self, *args: Any) -> None: store, iter_ = self._ui.theme_treeview.get_selection().get_selected() if iter_ is None: return @@ -329,7 +345,10 @@ def _remove_theme():
class Option(Gtk.ListBoxRow): - def __init__(self, option, value): + def __init__(self, + option: StyleOption, + value: Union[str, Pango.FontDescription, None] + ) -> None: Gtk.ListBoxRow.__init__(self) self.option = option self._box = Gtk.Box(spacing=12) @@ -355,7 +374,7 @@ def __init__(self, option, value): self.add(self._box) self.show_all()
- def _init_color(self, color): + def _init_color(self, color: str) -> None: color_button = Gtk.ColorButton() if color is not None: rgba = Gdk.RGBA() @@ -365,7 +384,7 @@ def _init_color(self, color): color_button.connect('color-set', self._on_color_set) self._box.add(color_button)
- def _init_font(self, desc): + def _init_font(self, desc: Pango.FontDescription) -> None: font_button = Gtk.FontButton() if desc is not None: font_button.set_font_desc(desc) @@ -373,7 +392,7 @@ def _init_font(self, desc): font_button.connect('font-set', self._on_font_set) self._box.add(font_button)
- def _on_color_set(self, color_button): + def _on_color_set(self, color_button: Gtk.ColorButton) -> None: color = color_button.get_rgba() color_string = color.to_string() app.css_config.set_value( @@ -381,13 +400,13 @@ def _on_color_set(self, color_button): app.nec.push_incoming_event(NetworkEvent('style-changed')) self.get_toplevel().reload_roster_theme()
- def _on_font_set(self, font_button): + def _on_font_set(self, font_button: Gtk.FontButton) -> None: desc = font_button.get_font_desc() app.css_config.set_font(self.option.selector, desc, pre=True) app.nec.push_incoming_event(NetworkEvent('style-changed')) self.get_toplevel().reload_roster_theme()
- def _on_remove(self, *args): + def _on_remove(self, *args: Any) -> None: self.get_parent().remove(self) app.css_config.remove_value( self.option.selector, self.option.attr, pre=True) @@ -401,7 +420,7 @@ def __eq__(self, other):
class ChooseOption(Gtk.ListBoxRow): - def __init__(self, option): + def __init__(self, option: StyleOption) -> None: Gtk.ListBoxRow.__init__(self) self.option = option label = Gtk.Label(label=option.label)
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/compare/84474aae9c1320d0880396261bbb79b8...