Source code for ooodev.theme.theme

from __future__ import annotations
from typing import cast, NamedTuple, Dict, TYPE_CHECKING
from abc import ABC
from enum import Enum

from ooodev.loader.lo import Lo
from ooodev.utils.info import Info
from ooodev.utils.sys_info import SysInfo
from ooodev.utils import color as mColor
from ooodev.io.log.named_logger import NamedLogger
from ooodev.exceptions import ex as mEx

if TYPE_CHECKING:
    from com.sun.star.awt import XWindow
    from com.sun.star.frame import XFrame
    from com.sun.star.awt import XStyleSettings


[docs]class ThemeKind(Enum): """Theme Kind Lookup""" AUTOMATIC = "COLOR_SCHEME_LIBREOFFICE_AUTOMATIC" LIBRE_OFFICE = "LibreOffice" LIBRE_OFFICE_DARK = "LibreOffice Dark" def __str__(self) -> str: return self.value
# https://pypi.org/project/darkdetect/ # <item oor:path="/org.openoffice.Office.Common/Misc"><prop oor:name="ApplicationAppearance" oor:op="fuse"><value>1</value></prop></item> # ApplicationAppearance = 1 Light # ApplicationAppearance = 2 Dark # ApplicationAppearance = 0 System
[docs]class ThemeColorKind(Enum): UNKNOWN = -1 SYSTEM = 0 LIGHT = 1 DARK = 2
class ThemeDefault(NamedTuple): """Theme Defaults""" prop_name: str light_color: int dark_color: int visible: bool = True DEFAULT_THEME_DETAILS: Dict[str, ThemeDefault] = { # Calc "CalcGrid": ThemeDefault("grid_color", 0xFFFFFF, 0x1C1C1C), "CalcPageBreak": ThemeDefault("page_break_color", 0xFFFFFF, 0x000000), "CalcPageBreakManual": ThemeDefault("page_break_manual_color", 0x2300DC, 0x2300DC), "CalcPageBreakAutomatic": ThemeDefault("page_break_auto_color", 0x666666, 0x666666), "CalcHiddenColRow": ThemeDefault("hidden_col_row_color", 0x2300DC, 0x2300DC, False), "CalcTextOverflow": ThemeDefault("text_overflow_color", 0xFF0000, 0xFF0000, True), "CalcComments": ThemeDefault("comments_color", 0xFF00FF, 0xFF00FF), "CalcDetective": ThemeDefault("detective_color", 0x0000FF, 0x355269), "CalcDetectiveError": ThemeDefault("detective_error_color", 0xFF0000, 0xC9211E), "CalcReference": ThemeDefault("reference_color", 0xEF0FFF, 0x0D23D5), "CalcNotesBackground": ThemeDefault("notes_background_color", 0xFFFFC0, 0xE8A202), "CalcValue": ThemeDefault("value_color", 0x0000FF, 0x729FCF), "CalcFormula": ThemeDefault("formula_color", 0x008000, 0x77BC65), "CalcText": ThemeDefault("text_color", 0x000000, 0xEEEEEE), "CalcProtectedBackground": ThemeDefault("protected_background_color", 0xC0C0C0, 0x1C1C1C), # Draw "DrawGrid": ThemeDefault("grid_color", 0x666666, 0x666666, True), # General "DocColor": ThemeDefault("doc_background_color", 0xFFFFFF, 0x1C1C1C), "DocBoundaries": ThemeDefault("doc_boundaries_color", 0xC0C0C0, 0x808080, True), "AppBackground": ThemeDefault("background_color", 0xFFFFFF, 0xF7F7F7), "ObjectBoundaries": ThemeDefault("object_boundaries_color", 0xC0C0C0, 0x808080, True), "TableBoundaries": ThemeDefault("table_boundaries_visible", 0xC0C0C0, 0x1C1C1C, True), "FontColor": ThemeDefault("font_color", 0x000000, 0x000000), "Links": ThemeDefault("links_color", 0x007AA6, 0x007AA6, False), "LinksVisited": ThemeDefault("links_visited_color", 0x002F40, 0x002F40, False), "Spell": ThemeDefault("spell_color", 0xFF0000, 0xC9211E), "SmartTags": ThemeDefault("smart_tags_color", 0xFF00FF, 0x780373), "Shadow": ThemeDefault("shadow_color", 0x808080, 0x1C1C1C, True), # HTML "HTMLSGML": ThemeDefault("sgml_color", 0x0000FF, 0x0000FF), "HTMLComment": ThemeDefault("comment_color", 0x00FF00, 0x00FF00), "HTMLKeyword": ThemeDefault("keyword_color", 0xFF0000, 0xFF0000), "HTMLUnknown": ThemeDefault("unknown_color", 0x808080, 0x808080), # Report Builder "Detail": ThemeDefault("detail_color", 0xF1C4A2, 0xF1C4A2), "PageFooter": ThemeDefault("page_footer_color", 0xF0C158, 0xF0C158), "PageHeader": ThemeDefault("page_header_color", 0xF0C158, 0xF0C158), "GroupFooter": ThemeDefault("group_footer_color", 0xAAC1D2, 0xAAC1D2), "GroupHeader": ThemeDefault("group_header_color", 0xAAC1D2, 0xAAC1D2), "ColumnFooter": ThemeDefault("column_footer_color", 0xAAC1D2, 0xAAC1D2), "ColumnHeader": ThemeDefault("column_header_color", 0xAAC1D2, 0xAAC1D2), "ReportFooter": ThemeDefault("report_footer_color", 0x7FA04C, 0x7FA04C), "ReportHeader": ThemeDefault("report_header_color", 0x7FA04C, 0x7FA04C), "OverlappedControl": ThemeDefault("overlap_control_color", 0xFF3366, 0xFF3366), "TextBoxBoundContent": ThemeDefault("text_box_bound_content_color", 0x000000, 0x000000), # SQL "SQLIdentifier": ThemeDefault("identifier_color", 0x009900, 0x009900), "SQLNumber": ThemeDefault("number_color", 0x000000, 0x000000), "SQLString": ThemeDefault("string_color", 0xCE7B00, 0xCE7B00), "SQLOperator": ThemeDefault("operator_color", 0x000000, 0x000000), "SQLKeyword": ThemeDefault("keyword_color", 0x0000E6, 0x0000E6), "SQLParameter": ThemeDefault("parameter_color", 0x259D9D, 0x259D9D), "SQLComment": ThemeDefault("comment_color", 0x808080, 0x808080), # Writer "WriterTextGrid": ThemeDefault("grid_color", 0xC0C0C0, 0x808080), "WriterFieldShadings": ThemeDefault("field_shadings_color", 0xC0C0C0, 0xC0C0C0, True), "WriterIdxShadings": ThemeDefault("index_table_shadings_color", 0xC0C0C0, 0x1C1C1C, True), "Grammar": ThemeDefault("grammar_color", 0x0000FF, 0x729FCF), "WriterScriptIndicator": ThemeDefault("script_indicator_color", 0x008000, 0x1E6A39), "WriterSectionBoundaries": ThemeDefault("section_boundaries_color", 0xC0C0C0, 0x666666, True), "WriterHeaderFooterMark": ThemeDefault("header_footer_mark_color", 0x0369A3, 0xB4C7DC), "WriterPageBreaks": ThemeDefault("page_columns_breaks_color", 0x000080, 0x729FCF), "WriterDirectCursor": ThemeDefault("direct_cursor_color", 0x000000, 0x000000, True), } class ThemeBase(ABC): def __init__(self, theme_name: ThemeKind | str = "") -> None: """ Constructor Args: theme_name (ThemeKind | str, optional): Theme Name. If omitted then the current LibreOffice Theme is used. Returns: None: """ self._log = NamedLogger(self.__class__.__name__) self._log.debug("Init") self._automatic_theme = False if theme_name == "" or theme_name == ThemeKind.AUTOMATIC: theme_name = Info.get_office_theme() self._automatic_theme = True if not theme_name: self._log.error("No theme name has been found.") raise ValueError("No theme name has been found,") self._theme_name = str(theme_name) if self._automatic_theme is False: if Info.is_office_theme(self._theme_name) is False: self._log.error(f"Invalid theme name: {self._theme_name}") raise ValueError(f"Invalid theme name: {self._theme_name}") self._log.debug(f"Theme Name: {theme_name}") self._theme_color_kind = None self._actual_theme_color_kind = None def _get_color(self, prop_name: str) -> int: global DEFAULT_THEME_DETAILS try: # search the ExtendedColorScheme first. try: val = Info.get_config( node_str="Color", node_path=f"/org.openoffice.Office.ExtendedColorScheme/ExtendedColorScheme/ColorSchemes/{self._theme_name}/{prop_name}", ) self._log.debug(f"Color for {prop_name} is {val} and was found in ExtendedColorScheme configuration") except mEx.ConfigError: self._log.debug(f"Color for {prop_name} not in ExtendedColorScheme configuration") val = None if val is None: try: val = Info.get_config( node_str="Color", node_path=f"/org.openoffice.Office.UI/ColorScheme/ColorSchemes/{self._theme_name}/{prop_name}", ) self._log.debug(f"Color for {prop_name} is {val} and was found in ColorSchemes configuration") except mEx.ConfigError: self._log.debug(f"Color for {prop_name} not in ColorSchemes configuration") val = None if val is None: if prop_name in DEFAULT_THEME_DETAILS: if self.actual_theme_color_kind == ThemeColorKind.DARK: return DEFAULT_THEME_DETAILS[prop_name].dark_color else: return DEFAULT_THEME_DETAILS[prop_name].light_color return -1 return int(val) except Exception: self._log.exception(f"Error getting color for {prop_name}") return -1 def _get_visible(self, prop_name: str) -> bool: global DEFAULT_THEME_DETAILS try: # search the ExtendedColorScheme first. try: val = Info.get_config( node_str="IsVisible", node_path=f"/org.openoffice.Office.ExtendedColorScheme/ExtendedColorScheme/ColorSchemes/{self._theme_name}/{prop_name}", ) self._log.debug(f"Color for {prop_name} is {val} and was found in ExtendedColorScheme configuration") except mEx.ConfigError: self._log.debug(f"Color for {prop_name} not in ExtendedColorScheme configuration") val = None if val is None: try: val = Info.get_config( node_str="IsVisible", node_path=f"/org.openoffice.Office.UI/ColorScheme/ColorSchemes/{self._theme_name}/{prop_name}", ) self._log.debug(f"Visible for {prop_name} is {val} and was found in configuration") except mEx.ConfigError: self._log.debug(f"Visible for {prop_name} not in configuration") val = None if val is None: if prop_name in DEFAULT_THEME_DETAILS: return DEFAULT_THEME_DETAILS[prop_name].visible return False return bool(val) except Exception: return False def get_actual_theme_color_kind(self) -> ThemeColorKind: """ Get Theme Color Kind from configuration or current Document. On some version of Linux the color is not correct when LibreOffice is set to use the system color scheme. This method will return the actual color scheme being used. Returns: ThemeColorKind: Theme Color Kind """ if self._actual_theme_color_kind is not None: return self._actual_theme_color_kind try: current_kind = self.get_theme_color_kind() if current_kind == ThemeColorKind.DARK or current_kind == ThemeColorKind.LIGHT: self._actual_theme_color_kind = current_kind return self._actual_theme_color_kind if SysInfo.get_platform() != SysInfo.PlatformEnum.LINUX: self._actual_theme_color_kind = current_kind else: if self.is_document_dark(): self._actual_theme_color_kind = ThemeColorKind.DARK else: self._actual_theme_color_kind = ThemeColorKind.LIGHT except Exception: self._actual_theme_color_kind = ThemeColorKind.UNKNOWN return self._actual_theme_color_kind def get_theme_color_kind(self) -> ThemeColorKind: """ Get Theme Color Kind from configuration. Returns: ThemeColorKind: Theme Color Kind """ if self._theme_color_kind is not None: return self._theme_color_kind try: val = Info.get_config( node_str="ApplicationAppearance", node_path="org.openoffice.Office.Common/Misc", ) if val == 1: self._theme_color_kind = ThemeColorKind.LIGHT elif val == 2: self._theme_color_kind = ThemeColorKind.DARK elif val == 0: self._theme_color_kind = ThemeColorKind.SYSTEM else: self._theme_color_kind = ThemeColorKind.UNKNOWN except Exception: self._theme_color_kind = ThemeColorKind.UNKNOWN return self._theme_color_kind def is_document_dark(self) -> bool: """ Is Document set to a dark color scheme. This method attempts to get the ``LightColor`` from the current document and determine if the document is dark. Returns: bool: ``True`` if document is dark, ``False`` otherwise. """ try: doc = Lo.current_doc controller = cast("XFrame", doc.get_current_controller()) window = cast("XWindow", controller.ComponentWindow) # type: ignore style_settings = cast("XStyleSettings", window.StyleSettings) # type: ignore rgb_color = mColor.RGB.from_color(style_settings.LightColor) # type: ignore return rgb_color.is_dark() except Exception as err: self._log.exception(f"Error getting document color scheme {err}.") return True @property def actual_theme_color_kind(self) -> ThemeColorKind: """ Get the actual theme color kind. On MacOS and Windows this will be the same as the ``theme_color_kind``. On Linux this may be different when LibreOffice is set to use system color scheme. This is because Linux does not always return the correct color scheme when LibreOffice is set to use the system color scheme. For this reason the color scheme is determined by the actual document color scheme. """ return self.get_actual_theme_color_kind() @property def theme_name(self) -> str: """Get theme name""" return self._theme_name @property def theme_color_kind(self) -> ThemeColorKind: """ This is the theme color kind that may not be the actual color kind being used. Due to inconsistencies in the color scheme detection on Linux for System Theme this may not always be the actual color scheme reported in the configuration. If the current LibreOffice Theme is not set to System then this will be the same as the actual color scheme. """ return self.get_theme_color_kind()