From 4f580c13cc83eccccaa59c0c643d43083ae6c7fb Mon Sep 17 00:00:00 2001 From: Jason Jerome Date: Sun, 23 Apr 2023 21:27:52 -0400 Subject: [PATCH 01/12] Implemented GUI themes - Added new error type: 'GUIError' - Added new settings category in config: 'settings.gui' - Added 'light' and 'dark' themes as default themes. - Added system argument and config option to specify gui themes file. - Updated gui TextTypes to use string values. - Updated default gui settings TextType to use 'body' instead of 'header'. - Updated gui BoxContent object to accept kwargs to update settings. - Updated gui BoxContent settings to retrieve settings from the gui theme file. - Fixed 'row_bg_color' gui settings option. - Fixed missing target user in gui command in plugin base. - Added gui theme path system argument prioritization over config option. - Added a gui init service to initialize gui settings from the gui theme file. --- config/config_template.toml | 5 +++ config/gui_themes_template.toml | 26 ++++++++++++ src/constants.py | 8 ++++ src/exceptions.py | 5 +++ src/lib/frameworks/gui/constants.py | 6 +-- src/lib/frameworks/gui/gui.py | 41 ++++++++++++++++--- src/lib/frameworks/plugins/plugin.py | 5 ++- .../client_settings_init_service.py | 5 +++ .../init_services/gui_init_service.py | 31 ++++++++++++++ .../init_services/mumimo_init_service.py | 6 +++ src/settings.py | 7 ++++ src/system_arguments.py | 11 ++--- 12 files changed, 142 insertions(+), 14 deletions(-) create mode 100644 config/gui_themes_template.toml create mode 100644 src/services/init_services/gui_init_service.py diff --git a/config/config_template.toml b/config/config_template.toml index a89385d..9f514e7 100644 --- a/config/config_template.toml +++ b/config/config_template.toml @@ -44,6 +44,11 @@ safe_mode_plugins = [] ### GUI Settings ### +[settings.gui] +enable = true +themes_path = "config/gui_theme.toml" +selected_theme = "dark" # Default themes available: "light", "dark" + [output.gui] enable = true diff --git a/config/gui_themes_template.toml b/config/gui_themes_template.toml new file mode 100644 index 0000000..af46ece --- /dev/null +++ b/config/gui_themes_template.toml @@ -0,0 +1,26 @@ +[theme.light] +text_type = "body" +text_color = "black" +text_align = "center" + +table_align = "center" +table_bg_color = "beige" + +row_align = "left" +row_bg_color = "beige" + + +[theme.dark] +text_type = "body" +text_color = "yellow" +text_align = "center" + +table_align = "center" +table_bg_color = "black" + +row_align = "left" +row_bg_color = "black" + +# Create custom themes here: +# Ensure all themes follow the naming scheme with 'theme.' +# Example: [theme.custom1] diff --git a/src/constants.py b/src/constants.py index efd8434..8074826 100644 --- a/src/constants.py +++ b/src/constants.py @@ -64,6 +64,7 @@ class SysArgs: SYS_ENV_FILE: str = "env_file" SYS_CONFIG_FILE: str = "config_file" SYS_LOG_CONFIG_FILE: str = "log_config_file" + SYS_GUI_THEMES_FILE: str = "gui_themes_file" SYS_DB_LOCALDBPATH: str = "local_database_path" SYS_DB_LOCALDBDIALECT: str = "local_database_dialect" SYS_DB_LOCALDBDRIVER: str = "local_database_driver" @@ -82,6 +83,7 @@ class SysArgs: # Config Constants DEFAULT_PATH_CONFIG_FILE: str = "config/config.toml" DEFAULT_PATH_LOGGING_CONFIG_FILE: str = "config/logging.toml" +DEFAULT_PATH_GUI_THEMES_FILE: str = "config/gui_themes.toml" class DefaultPermissionGroups: @@ -166,6 +168,7 @@ class MumimoCfgSections: SETTINGS_MEDIA_AUDIODUCKING: str = f"{SETTINGS_MEDIA}.audio_ducking" SETTINGS_MEDIA_YOUTUBEDL: str = f"{SETTINGS_MEDIA}.youtube_dl" SETTINGS_PLUGINS: str = f"{SETTINGS}.plugins" + SETTINGS_GUI: str = f"{SETTINGS}.gui" OUTPUT: str = "output" OUTPUT_GUI: str = f"{OUTPUT}.gui" @@ -225,6 +228,11 @@ class PLUGINS: DISABLED_PLUGINS: str = f"{MumimoCfgSections.SETTINGS_PLUGINS}.disabled_plugins" SAFE_MODE_PLUGINS: str = f"{MumimoCfgSections.SETTINGS_PLUGINS}.safe_mode_plugins" + class GUI: + ENABLE: str = f"{MumimoCfgSections.SETTINGS_GUI}.enable" + THEMES_PATH: str = f"{MumimoCfgSections.SETTINGS_GUI}.themes_path" + SELECTED_THEME: str = f"{MumimoCfgSections.SETTINGS_GUI}.selected_theme" + class OUTPUT: class GUI: ENABLE: str = f"{MumimoCfgSections.OUTPUT_GUI}.enable" diff --git a/src/exceptions.py b/src/exceptions.py index 57ffc54..a870adf 100644 --- a/src/exceptions.py +++ b/src/exceptions.py @@ -24,6 +24,11 @@ def __init__(self, msg: str, logger: Optional[logging.Logger] = None) -> None: super().__init__(msg, logger) +class GUIError(LoggedException): + def __init__(self, msg: str, logger: Optional[logging.Logger] = None) -> None: + super().__init__(msg, logger) + + class ServiceError(LoggedException): def __init__(self, msg: str, logger: Optional[logging.Logger] = None) -> None: super().__init__(msg, logger=logger) diff --git a/src/lib/frameworks/gui/constants.py b/src/lib/frameworks/gui/constants.py index 4cc0598..064a92d 100644 --- a/src/lib/frameworks/gui/constants.py +++ b/src/lib/frameworks/gui/constants.py @@ -2,6 +2,6 @@ class TextTypes(Enum): - TITLE = 0 - HEADER = 1 - BODY = 2 + TITLE = "title" + HEADER = "header" + BODY = "body" diff --git a/src/lib/frameworks/gui/gui.py b/src/lib/frameworks/gui/gui.py index 830a647..8112384 100644 --- a/src/lib/frameworks/gui/gui.py +++ b/src/lib/frameworks/gui/gui.py @@ -1,9 +1,15 @@ import logging -from typing import Optional, Union, List +from typing import Optional, Union, List, TYPE_CHECKING from .constants import TextTypes from .utility import FontModifiers, AlignmentModifiers from ....utils import mumble_utils +from ....settings import settings +from ....constants import MumimoCfgFields +from ....exceptions import GUIError + +if TYPE_CHECKING: + from ....config import Config logger = logging.getLogger(__name__) @@ -18,11 +24,12 @@ class ContentBox: _box_close: str _box_rows: List[str] - def __init__(self, settings: Optional["Settings"] = None) -> None: + def __init__(self, settings: Optional["Settings"] = None, **kwargs) -> None: if settings is not None: self.settings = settings else: self.settings = self.Settings() + self.settings.update(**kwargs) self.is_open = False self._box_open = "" self._box_close = "" @@ -71,7 +78,7 @@ def compile(self) -> str: return _msg class Settings: - text_type: TextTypes = TextTypes.HEADER + text_type: TextTypes = TextTypes.BODY text_color: str = "yellow" text_align: str = "center" @@ -82,10 +89,34 @@ class Settings: row_bg_color: str = "black" def __init__(self, **kwargs) -> None: + self._from_config() self.update(**kwargs) + def _from_config(self) -> None: + _themes: Optional["Config"] = settings.configs.get_gui_themes() + if not _themes: + raise GUIError("Unable to load gui themes: could not retrieve gui themes from settings.") + _config: Optional["Config"] = settings.configs.get_mumimo_config() + if not _config: + raise GUIError("Unable to load gui themes: mumimo config could not be retrieved from settings.") + + _selected_theme = _config.get(MumimoCfgFields.SETTINGS.GUI.SELECTED_THEME, None) + if not _selected_theme: + _selected_theme = "light" + logger.warning("Unable to find selected gui theme, falling back to default 'light' theme.") + + _theme = _themes.get(_selected_theme, None) + if not _theme: + raise GUIError(f"Unable to find gui theme: [{_selected_theme}], falling back to default 'light' theme.", logger=logger) + + self.update(**_theme) + def update(self, **kwargs) -> "GUIFramework.ContentBox.Settings": - self.text_type = kwargs.get("text_type", self.text_type) + _text_type = kwargs.get("text_type", self.text_type) + if not isinstance(_text_type, TextTypes): + _text_type = TextTypes(_text_type) + self.text_type = _text_type + self.text_color = kwargs.get("text_color", self.text_color) self.text_align = kwargs.get("text_align", self.text_align) @@ -93,7 +124,7 @@ def update(self, **kwargs) -> "GUIFramework.ContentBox.Settings": self.table_bg_color = kwargs.get("table_bg_color", self.table_bg_color) self.row_align = kwargs.get("row_align", self.row_align) - self.row_bg_color = kwargs.get("bg_color", self.row_bg_color) + self.row_bg_color = kwargs.get("row_bg_color", self.row_bg_color) return self @staticmethod diff --git a/src/lib/frameworks/plugins/plugin.py b/src/lib/frameworks/plugins/plugin.py index b5511bd..cd27c36 100644 --- a/src/lib/frameworks/plugins/plugin.py +++ b/src/lib/frameworks/plugins/plugin.py @@ -190,7 +190,10 @@ def verify_parameters(self, func_name: str, data: "Command") -> Optional[Dict[st return _compiled_parameters: Optional[Dict[str, Any]] = _compiled_result.result if _compiled_parameters is None: - GUIFramework.gui(f"'{data.command}' command error: parameters failed to compile.") + GUIFramework.gui( + f"'{data.command}' command error: parameters failed to compile.", + target_users=mumble_utils.get_user_by_id(data.actor), + ) return return _compiled_parameters diff --git a/src/services/init_services/client_settings_init_service.py b/src/services/init_services/client_settings_init_service.py index 079f069..105b137 100644 --- a/src/services/init_services/client_settings_init_service.py +++ b/src/services/init_services/client_settings_init_service.py @@ -113,6 +113,11 @@ def _get_prioritized_client_config_options(self, cfg_instance: "Config") -> Dict prioritized_options[SysArgs.SYS_PLUGINS_CONFIG_PATH] = plugins_config_path cfg_instance.set(MumimoCfgFields.SETTINGS.PLUGINS.PLUGINS_CONFIG_PATH, plugins_config_path) + # Prioritize gui-theme-path from system argument over config options. + gui_theme_path = self._get_sys_args().get(SysArgs.SYS_GUI_THEMES_FILE) or cfg_instance.get(MumimoCfgFields.SETTINGS.GUI.THEMES_PATH) + prioritized_options[SysArgs.SYS_GUI_THEMES_FILE] = gui_theme_path + cfg_instance.set(MumimoCfgFields.SETTINGS.GUI.THEMES_PATH, gui_theme_path) + return prioritized_options def _get_prioritized_client_env_options(self) -> Dict[str, Any]: diff --git a/src/services/init_services/gui_init_service.py b/src/services/init_services/gui_init_service.py new file mode 100644 index 0000000..0f73c6c --- /dev/null +++ b/src/services/init_services/gui_init_service.py @@ -0,0 +1,31 @@ +import logging +from typing import Dict, Optional + +from ...config import Config +from ...constants import DEFAULT_PATH_CONFIG_FILE +from ...exceptions import ConfigError +from ...settings import settings + +logger = logging.getLogger(__name__) + + +class GUIInitService: + def __init__(self, sys_args: Dict[str, str]) -> None: + self._sys_args = sys_args + + def initialize_gui(self, cfg_path: Optional[str] = None) -> "Config": + # Load in themes from custom themes file path if present. + # Otherwise load themes from default themes file path. + _cfg_instance: Optional["Config"] = settings.configs.get_gui_themes() + if _cfg_instance is None: + if not cfg_path: + cfg_path = DEFAULT_PATH_CONFIG_FILE + logger.warning("Mumimo gui config file path not provided. Reading gui config from default path.") + _cfg_instance = Config(cfg_path) + _cfg_instance.read() + settings.configs.set_gui_themes(_cfg_instance) + + _cfg_instance = settings.configs.get_gui_themes() + if _cfg_instance is None: + raise ConfigError("An unexpected error occurred where the gui config file was not read during initialization.", logger=logger) + return _cfg_instance diff --git a/src/services/init_services/mumimo_init_service.py b/src/services/init_services/mumimo_init_service.py index 6812e8c..716e2ec 100644 --- a/src/services/init_services/mumimo_init_service.py +++ b/src/services/init_services/mumimo_init_service.py @@ -6,6 +6,7 @@ from ...services.init_services.cfg_init_service import ConfigInitService from ...services.init_services.client_settings_init_service import ClientSettingsInitService from ...services.init_services.plugins_init_service import PluginsInitService +from ...services.init_services.gui_init_service import GUIInitService logger = logging.getLogger(__name__) @@ -20,6 +21,7 @@ def __init__(self, sys_args: Dict[str, str]) -> None: self._client_settings_init_service: ClientSettingsInitService = ClientSettingsInitService(sys_args) self._db_init_service: DatabaseService = DatabaseService() self._plugins_init_service: PluginsInitService = PluginsInitService(sys_args) + self._gui_init_service: GUIInitService = GUIInitService(sys_args) async def initialize(self) -> None: # Initialize the mumimo configuration file. @@ -30,6 +32,10 @@ async def initialize(self) -> None: logger.info("Initializing client settings...") self._client_settings_init_service.initialize_client_settings(cfg) logger.info("Mumimo client settings initialized.") + # Initialize mumimo gui settings. + logger.info("Initializing client gui settings...") + self._gui_init_service.initialize_gui(self._sys_args.get(SysArgs.SYS_GUI_THEMES_FILE)) + logger.info("Client gui settings initialized.") # Initialize the internal database. logger.info("Initializing internal database...") # print(cfg) diff --git a/src/settings.py b/src/settings.py index ac7f2f2..4289b0f 100644 --- a/src/settings.py +++ b/src/settings.py @@ -52,6 +52,7 @@ def set_registered_plugin(self, plugin_name: str, plugin: "PluginBase") -> None: class Configs: _mumimo_cfg: Optional["Config"] = None _log_cfg: Optional["LogConfig"] = None + _gui_themes: Optional["Config"] = None def get_mumimo_config(self) -> Optional["Config"]: return self._mumimo_cfg @@ -65,6 +66,12 @@ def get_log_config(self) -> Optional["LogConfig"]: def set_log_config(self, cfg: "LogConfig") -> None: self._log_cfg = cfg + def get_gui_themes(self) -> Optional["Config"]: + return self._gui_themes + + def set_gui_themes(self, themes: "Config") -> Optional["Config"]: + self._gui_themes = themes + class State: _client_state: Optional["ClientState"] = None diff --git a/src/system_arguments.py b/src/system_arguments.py index 0880deb..4a77a5c 100644 --- a/src/system_arguments.py +++ b/src/system_arguments.py @@ -31,10 +31,11 @@ # Other system arguments group_other = args_parser.add_argument_group("other") group_other.add_argument("-pp", "--plugins-path", help="use a custom path for plugins", type=str) +group_other.add_argument("-cpp", "--custom-plugins-path", help="specify a path for custom plugins", type=str) group_other.add_argument("-pcp", "--plugins-config-path", help="use a custom path for plugin metadata file storage", type=str) -group_other.add_argument("-cpp", "--custom-plugins-path", help="specify a path for custom plugins.", type=str) group_other.add_argument("-cf", "--config-file", help="use a custom config file from the given path", type=str) group_other.add_argument("-lcf", "--log-config-file", help="use a custom config file for logging from the given path", type=str) +group_other.add_argument("-gtf", "--gui-themes-file", help="ust a custom gui themes file for loading gui themes", type=str) group_other.add_argument("-e", "--env-file", help="use connection options from an environment file", type=str) group_other.add_argument("-v", "--verbose", help="enables verbose and debug messages to output to the console", action="count", default=0) group_other.add_argument( @@ -49,15 +50,15 @@ group_database.add_argument( "-rdb", "--use-remote-database", - help="enables usage of a custom remote database instead of the default local sqlite database.", + help="enables usage of a custom remote database instead of the default local sqlite database", action="store_true", ) -group_database.add_argument("-ldbp", "--local-database-path", help="specify the local database path to use for non-remote database usage.", type=str) +group_database.add_argument("-ldbp", "--local-database-path", help="specify the local database path to use for non-remote database usage", type=str) group_database.add_argument( - "-ldbdr", "--local-database-dialect", help="specify the local database dialect to use for non-remote database usage.", type=str + "-ldbdr", "--local-database-dialect", help="specify the local database dialect to use for non-remote database usage", type=str ) group_database.add_argument( - "-ldbdi", "--local-database-driver", help="specify the local database driver to use for non-remote database usage.", type=str + "-ldbdi", "--local-database-driver", help="specify the local database driver to use for non-remote database usage", type=str ) group_database.add_argument( From 269542d1fad93b3c1404e470726b94334cc4bb5c Mon Sep 17 00:00:00 2001 From: Jason Jerome Date: Sun, 23 Apr 2023 21:29:27 -0400 Subject: [PATCH 02/12] Updated default gui light/dark theme values --- config/gui_themes_template.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/gui_themes_template.toml b/config/gui_themes_template.toml index af46ece..69c9c8e 100644 --- a/config/gui_themes_template.toml +++ b/config/gui_themes_template.toml @@ -1,17 +1,17 @@ [theme.light] -text_type = "body" +text_type = "header" text_color = "black" text_align = "center" table_align = "center" -table_bg_color = "beige" +table_bg_color = "blanchedalmond" row_align = "left" -row_bg_color = "beige" +row_bg_color = "blanchedalmond" [theme.dark] -text_type = "body" +text_type = "header" text_color = "yellow" text_align = "center" From 7ea984e80ac017ef46f25d520ec041c32ed3bb2b Mon Sep 17 00:00:00 2001 From: Jason Jerome Date: Sun, 23 Apr 2023 21:35:40 -0400 Subject: [PATCH 03/12] Added 'highcontrast' gui theme - Fixed gui config template section names - Added new 'highcontrast' gui theme. - Updated dark theme colors --- config/config_template.toml | 2 +- config/gui_themes_template.toml | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/config/config_template.toml b/config/config_template.toml index 9f514e7..cc8cce5 100644 --- a/config/config_template.toml +++ b/config/config_template.toml @@ -47,7 +47,7 @@ safe_mode_plugins = [] [settings.gui] enable = true themes_path = "config/gui_theme.toml" -selected_theme = "dark" # Default themes available: "light", "dark" +selected_theme = "dark" # Default themes available: "light", "dark", "highcontrast" [output.gui] enable = true diff --git a/config/gui_themes_template.toml b/config/gui_themes_template.toml index 69c9c8e..e08e633 100644 --- a/config/gui_themes_template.toml +++ b/config/gui_themes_template.toml @@ -1,4 +1,4 @@ -[theme.light] +[light] text_type = "header" text_color = "black" text_align = "center" @@ -10,7 +10,19 @@ row_align = "left" row_bg_color = "blanchedalmond" -[theme.dark] +[dark] +text_type = "header" +text_color = "lightsteelblue" +text_align = "center" + +table_align = "center" +table_bg_color = "darkslategrey" + +row_align = "left" +row_bg_color = "darkslategrey" + + +[highcontrast] text_type = "header" text_color = "yellow" text_align = "center" @@ -22,5 +34,4 @@ row_align = "left" row_bg_color = "black" # Create custom themes here: -# Ensure all themes follow the naming scheme with 'theme.' -# Example: [theme.custom1] +# Example: [custom1], [custom2], ... From 110cf98e2babe29860a093cdb253fb45b8e1f6e3 Mon Sep 17 00:00:00 2001 From: Jason Jerome Date: Mon, 24 Apr 2023 00:31:54 -0400 Subject: [PATCH 04/12] Fixed default themes path in gui init service --- .../init_services/gui_init_service.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/services/init_services/gui_init_service.py b/src/services/init_services/gui_init_service.py index 0f73c6c..c8514c9 100644 --- a/src/services/init_services/gui_init_service.py +++ b/src/services/init_services/gui_init_service.py @@ -2,7 +2,7 @@ from typing import Dict, Optional from ...config import Config -from ...constants import DEFAULT_PATH_CONFIG_FILE +from ...constants import DEFAULT_PATH_GUI_THEMES_FILE from ...exceptions import ConfigError from ...settings import settings @@ -13,19 +13,19 @@ class GUIInitService: def __init__(self, sys_args: Dict[str, str]) -> None: self._sys_args = sys_args - def initialize_gui(self, cfg_path: Optional[str] = None) -> "Config": + def initialize_gui(self, themes_path: Optional[str] = None) -> "Config": # Load in themes from custom themes file path if present. # Otherwise load themes from default themes file path. - _cfg_instance: Optional["Config"] = settings.configs.get_gui_themes() - if _cfg_instance is None: - if not cfg_path: - cfg_path = DEFAULT_PATH_CONFIG_FILE - logger.warning("Mumimo gui config file path not provided. Reading gui config from default path.") - _cfg_instance = Config(cfg_path) - _cfg_instance.read() - settings.configs.set_gui_themes(_cfg_instance) + _themes_instance: Optional["Config"] = settings.configs.get_gui_themes() + if _themes_instance is None: + if not themes_path: + themes_path = DEFAULT_PATH_GUI_THEMES_FILE + logger.warning("Mumimo gui themes file path not provided. Reading gui themes from default path.") + _themes_instance = Config(themes_path) + _themes_instance.read() + settings.configs.set_gui_themes(_themes_instance) - _cfg_instance = settings.configs.get_gui_themes() - if _cfg_instance is None: - raise ConfigError("An unexpected error occurred where the gui config file was not read during initialization.", logger=logger) - return _cfg_instance + _themes_instance = settings.configs.get_gui_themes() + if _themes_instance is None: + raise ConfigError("An unexpected error occurred where the gui themes file was not read during initialization.", logger=logger) + return _themes_instance From 438ab2238f684481b30922d422a56c3b84b2f8b8 Mon Sep 17 00:00:00 2001 From: Jason Jerome Date: Mon, 24 Apr 2023 00:32:26 -0400 Subject: [PATCH 05/12] Reformatted gui messages in command processing service --- src/services/cmd_processing_service.py | 27 ++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/services/cmd_processing_service.py b/src/services/cmd_processing_service.py index 700f95f..84724cc 100644 --- a/src/services/cmd_processing_service.py +++ b/src/services/cmd_processing_service.py @@ -289,14 +289,13 @@ async def _process_cmd(self) -> None: _command_suggestions = [x[0] for x in _command_suggestions if x[1] >= 70] # Only display suggestions if there is a closely matched ratio. if _command_suggestions: - _suggestion_msgs = [] - for idx, cmd in enumerate(_command_suggestions): - _suggestion_msgs.append(f"- {cmd} ") _msgs = [ f"The command: [{_cmd_name}] could not be found. ", "Did you mean any of the following: ", - *_suggestion_msgs, ] + for idx, cmd in enumerate(_command_suggestions): + _msgs.append(f"{idx+1}) {cmd}") + GUIFramework.gui( _msgs, target_users=mumble_utils.get_user_by_id(command.actor), @@ -376,18 +375,30 @@ async def _process_cmd(self) -> None: _parameters_required = _cmd_info.get("parameters_required", False) if _parameters_required and not command.parameters: logger.warning(f"The command: [{_cmd_name}] requires parameters and no parameters were provided.") + _msgs = [ + f"Invalid '{_cmd_name}' command. This command requires the usage of parameters. ", + "Please use one of the available parameters: ", + ] + for idx, param in enumerate(_cmd_params): + _msgs.append(f"{idx+1}) {param}") GUIFramework.gui( - f"Invalid '{_cmd_name}' command. This command requires the usage of parameters. " - f"Please use one of the available parameters: {', '.join(_cmd_params)}", + text=_msgs, target_users=mumble_utils.get_user_by_id(command.actor), + table_align="left", ) return if any(param.split("=", 1)[0] not in _cmd_params for param in command.parameters): logger.warning(f"The command: [{_cmd_name}] could not be executed because one or more provided parameters do not exist.") + _msgs = [ + f"Invalid '{_cmd_name}' command. ", + "Please use one of the available parameters: ", + ] + for idx, param in enumerate(_cmd_params): + _msgs.append(f"{idx+1}) {param}") GUIFramework.gui( - f"Invalid '{_cmd_name}' command. Please use one of the available parameters: {', '.join(_cmd_params)}", + text=_msgs, target_users=mumble_utils.get_user_by_id(command.actor), - user_id=command.actor, + table_align="left", ) return From 03d9e72af671ccfbdb16dd192897e8deacce4b4a Mon Sep 17 00:00:00 2001 From: Jason Jerome Date: Mon, 24 Apr 2023 00:58:20 -0400 Subject: [PATCH 06/12] Changed default themes table align attribute --- config/gui_themes_template.toml | 6 +++--- src/lib/frameworks/gui/gui.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/gui_themes_template.toml b/config/gui_themes_template.toml index e08e633..22e2382 100644 --- a/config/gui_themes_template.toml +++ b/config/gui_themes_template.toml @@ -3,7 +3,7 @@ text_type = "header" text_color = "black" text_align = "center" -table_align = "center" +table_align = "left" table_bg_color = "blanchedalmond" row_align = "left" @@ -15,7 +15,7 @@ text_type = "header" text_color = "lightsteelblue" text_align = "center" -table_align = "center" +table_align = "left" table_bg_color = "darkslategrey" row_align = "left" @@ -27,7 +27,7 @@ text_type = "header" text_color = "yellow" text_align = "center" -table_align = "center" +table_align = "left" table_bg_color = "black" row_align = "left" diff --git a/src/lib/frameworks/gui/gui.py b/src/lib/frameworks/gui/gui.py index 8112384..fa3e705 100644 --- a/src/lib/frameworks/gui/gui.py +++ b/src/lib/frameworks/gui/gui.py @@ -82,7 +82,7 @@ class Settings: text_color: str = "yellow" text_align: str = "center" - table_align: str = "center" + table_align: str = "left" table_bg_color: str = "black" row_align: str = "left" From 3d7b402305684c55400466f1ef057e2e051df069 Mon Sep 17 00:00:00 2001 From: Jason Jerome Date: Mon, 24 Apr 2023 00:58:43 -0400 Subject: [PATCH 07/12] Fixed incorrect database value set in client settings init service --- src/services/init_services/client_settings_init_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/init_services/client_settings_init_service.py b/src/services/init_services/client_settings_init_service.py index 105b137..b43a660 100644 --- a/src/services/init_services/client_settings_init_service.py +++ b/src/services/init_services/client_settings_init_service.py @@ -99,7 +99,7 @@ def _get_prioritized_client_config_options(self, cfg_instance: "Config") -> Dict MumimoCfgFields.SETTINGS.DATABASE.LOCAL_DB_DRIVERNAME ) prioritized_options[SysArgs.SYS_DB_LOCALDBDRIVER] = db_local_drivername - cfg_instance.set(MumimoCfgFields.SETTINGS.DATABASE.LOCAL_DB_DIALECT, db_local_drivername) + cfg_instance.set(MumimoCfgFields.SETTINGS.DATABASE.LOCAL_DB_DRIVERNAME, db_local_drivername) # Prioritize plugins-path from system argument over config option. plugins_path = self._get_sys_args().get(SysArgs.SYS_PLUGINS_PATH) or cfg_instance.get(MumimoCfgFields.SETTINGS.PLUGINS.PLUGINS_PATH) From 098debd04dffc899c84e7a0ff45f75b7a7898e2b Mon Sep 17 00:00:00 2001 From: Jason Jerome Date: Mon, 24 Apr 2023 00:59:02 -0400 Subject: [PATCH 08/12] Removed redundant gui usage parameters --- src/services/cmd_processing_service.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/services/cmd_processing_service.py b/src/services/cmd_processing_service.py index 84724cc..c4ad5d2 100644 --- a/src/services/cmd_processing_service.py +++ b/src/services/cmd_processing_service.py @@ -299,7 +299,6 @@ async def _process_cmd(self) -> None: GUIFramework.gui( _msgs, target_users=mumble_utils.get_user_by_id(command.actor), - table_align="left", ) return @@ -384,7 +383,6 @@ async def _process_cmd(self) -> None: GUIFramework.gui( text=_msgs, target_users=mumble_utils.get_user_by_id(command.actor), - table_align="left", ) return if any(param.split("=", 1)[0] not in _cmd_params for param in command.parameters): @@ -398,7 +396,6 @@ async def _process_cmd(self) -> None: GUIFramework.gui( text=_msgs, target_users=mumble_utils.get_user_by_id(command.actor), - table_align="left", ) return From 5c630560bff240ae17fa4dd60849d2fc390c210a Mon Sep 17 00:00:00 2001 From: Jason Jerome Date: Mon, 24 Apr 2023 01:00:54 -0400 Subject: [PATCH 09/12] Added new command 'themes' - Added new command 'themes' - Added new parameter 'list' for command 'themes'. This parameter displays a list of the available gui themes. - Added new parameter 'switch' for command 'themes'. This parameter is used to switch the current gui theme to one of the available gui themes. - Added placeholder constants for parameters: 'new', 'delete', and 'update'. - Added new utility for managing theme-related functions. --- src/plugins/builtin_core/plugin.py | 48 +++++++++++++++++++ src/plugins/builtin_core/utility/constants.py | 17 +++++++ .../builtin_core/utility/theme_utils.py | 31 ++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 src/plugins/builtin_core/utility/theme_utils.py diff --git a/src/plugins/builtin_core/plugin.py b/src/plugins/builtin_core/plugin.py index d4da258..af43a8a 100644 --- a/src/plugins/builtin_core/plugin.py +++ b/src/plugins/builtin_core/plugin.py @@ -12,6 +12,7 @@ from src.lib.frameworks.gui.gui import GUIFramework from .utility.constants import ParameterDefinitions +from .utility import theme_utils logger = logging.getLogger(__name__) @@ -207,6 +208,53 @@ def move(self, data: "Command") -> None: _my_channel.move_in() return + @command( + parameters=ParameterDefinitions.Themes.get_definitions(), + exclusive_parameters=ParameterDefinitions.Themes.get_definitions(), + parameters_required=True, + ) + def themes(self, data: "Command") -> None: + _parameters = self.verify_parameters(self.themes.__name__, data) + if _parameters is None: + return + + _theme_list = _parameters.get(ParameterDefinitions.Themes.LIST, None) + if _theme_list: + _msgs: List[str] = ["Available themes: "] + for idx, theme in enumerate(_theme_list): + _msgs.append(f"{idx+1}) {theme}") + GUIFramework.gui( + text=_msgs, + target_users=mumble_utils.get_user_by_id(data.actor), + ) + + _switch_themes = _parameters.get(ParameterDefinitions.Themes.SWITCH, None) + if _switch_themes: + GUIFramework.gui( + text=f"Switched theme to: {data.message.strip()}", + target_users=mumble_utils.get_user_by_id(data.actor), + ) + + def _parameter_themes_list(self, data: "Command", parameter: str) -> List[str]: + return theme_utils.list_themes() + + def _parameter_themes_switch(self, data: "Command", parameter: str) -> bool: + _new_theme = data.message.strip() + _available_themes = theme_utils.list_themes() + if not _new_theme or _new_theme not in _available_themes: + _msgs: List[str] = [ + "Invalid theme. ", + "Here are the available themes to choose from: ", + ] + for idx, theme in enumerate(theme_utils.list_themes()): + _msgs.append(f"{idx+1}) {theme}") + GUIFramework.gui( + text=_msgs, + target_users=mumble_utils.get_user_by_id(data.actor), + ) + return False + return theme_utils.switch_themes(_new_theme) + def _parameter_move_to_me(self, data: "Command", parameter: str) -> Optional["User"]: return mumble_utils.get_user_by_id(data.actor) diff --git a/src/plugins/builtin_core/utility/constants.py b/src/plugins/builtin_core/utility/constants.py index 25b0436..42abd56 100644 --- a/src/plugins/builtin_core/utility/constants.py +++ b/src/plugins/builtin_core/utility/constants.py @@ -37,3 +37,20 @@ def get_definitions() -> List[str]: ParameterDefinitions.Move.TO_USER, ParameterDefinitions.Move.TO_ME, ] + + class Themes: + SWITCH: str = "switch" + LIST: str = "list" + NEW: str = "new" + DELETE: str = "delete" + UPDATE: str = "update" + + @staticmethod + def get_definitions() -> List[str]: + return [ + ParameterDefinitions.Themes.SWITCH, + ParameterDefinitions.Themes.LIST, + ParameterDefinitions.Themes.NEW, + ParameterDefinitions.Themes.DELETE, + ParameterDefinitions.Themes.UPDATE, + ] diff --git a/src/plugins/builtin_core/utility/theme_utils.py b/src/plugins/builtin_core/utility/theme_utils.py new file mode 100644 index 0000000..754ded6 --- /dev/null +++ b/src/plugins/builtin_core/utility/theme_utils.py @@ -0,0 +1,31 @@ +from typing import List, Optional, TYPE_CHECKING +from src.settings import settings +from src.exceptions import PluginError +from src.constants import MumimoCfgFields + +if TYPE_CHECKING: + from src.config import Config + + +def list_themes() -> List[str]: + _themes = settings.configs.get_gui_themes() + if _themes: + return list(_themes.keys()) + return [] + + +def switch_themes(theme: str) -> bool: + _themes: List[str] = list_themes() + if theme not in _themes: + return False + + _config: Optional["Config"] = settings.configs.get_mumimo_config() + if not _config: + raise PluginError("Unable to switch themes: mumimo config could not be retrieved from settings.") + + _success_switch: bool = _config.set(MumimoCfgFields.SETTINGS.GUI.SELECTED_THEME, theme) + _saved_data: str = _config.save( + modified_only=True, + modified_field_name=MumimoCfgFields.SETTINGS.GUI.SELECTED_THEME, + ) + return _success_switch and _saved_data is not None From d905e637340250ce13accc368ee92a07c16e97ec Mon Sep 17 00:00:00 2001 From: Jason Jerome Date: Tue, 25 Apr 2023 03:06:21 -0400 Subject: [PATCH 10/12] Moved gui themes template file --- .../plugins/builtin_core/resources}/gui_themes_template.toml | 5 +++++ 1 file changed, 5 insertions(+) rename {config => src/plugins/builtin_core/resources}/gui_themes_template.toml (90%) diff --git a/config/gui_themes_template.toml b/src/plugins/builtin_core/resources/gui_themes_template.toml similarity index 90% rename from config/gui_themes_template.toml rename to src/plugins/builtin_core/resources/gui_themes_template.toml index 22e2382..a4288c7 100644 --- a/config/gui_themes_template.toml +++ b/src/plugins/builtin_core/resources/gui_themes_template.toml @@ -1,3 +1,8 @@ +# +# This is a template file. +# Please do not modify this! +# + [light] text_type = "header" text_color = "black" From c70daf4e61ac2830c5fa026e6d35fcb356b9d7ce Mon Sep 17 00:00:00 2001 From: Jason Jerome Date: Tue, 25 Apr 2023 03:10:55 -0400 Subject: [PATCH 11/12] Added new gui parameters, fixes and refactors - Added gui custom theme template file. New themes are generated from this template. - Added new parameter: 'show'. This parameter displays the selected theme configuration options. - Added new parameter: 'new'. This parameter generates a new theme from the template with the specified name. - Added new parameter: 'delete'. This parameter deletes a specified theme from the available themes. - Added new parameter: 'update'. This parameter allows updating individual configuration options within a selected theme. - Refactored execution flow of commands and parameters in builtin_core plugin. - Updated some warning messages to instead be error messages in builtin_core plugin. --- src/lib/frameworks/gui/gui.py | 3 +- src/plugins/builtin_core/plugin.py | 400 +++++++++++------- .../resources/gui_custom_theme_template.toml | 15 + src/plugins/builtin_core/utility/constants.py | 2 + .../builtin_core/utility/theme_utils.py | 113 ++++- 5 files changed, 367 insertions(+), 166 deletions(-) create mode 100644 src/plugins/builtin_core/resources/gui_custom_theme_template.toml diff --git a/src/lib/frameworks/gui/gui.py b/src/lib/frameworks/gui/gui.py index fa3e705..ab7b892 100644 --- a/src/lib/frameworks/gui/gui.py +++ b/src/lib/frameworks/gui/gui.py @@ -101,7 +101,8 @@ def _from_config(self) -> None: raise GUIError("Unable to load gui themes: mumimo config could not be retrieved from settings.") _selected_theme = _config.get(MumimoCfgFields.SETTINGS.GUI.SELECTED_THEME, None) - if not _selected_theme: + _theme = _themes.get(_selected_theme, None) + if not _selected_theme or not _theme: _selected_theme = "light" logger.warning("Unable to find selected gui theme, falling back to default 'light' theme.") diff --git a/src/plugins/builtin_core/plugin.py b/src/plugins/builtin_core/plugin.py index af43a8a..042a39e 100644 --- a/src/plugins/builtin_core/plugin.py +++ b/src/plugins/builtin_core/plugin.py @@ -1,6 +1,6 @@ import logging import time -from typing import TYPE_CHECKING, List, Optional, Union +from typing import TYPE_CHECKING, List, Optional from pymumble_py3.channels import Channel from pymumble_py3.users import User @@ -19,6 +19,7 @@ if TYPE_CHECKING: from src.lib.command import Command + from src.config import Config class Plugin(PluginBase): @@ -56,74 +57,6 @@ def echo(self, data: "Command") -> None: if _delay and _delay > 0: time.sleep(_delay) - if _parameters.get(ParameterDefinitions.Echo.BROADCAST, False): - _all_channels: List["Channel"] = mumble_utils.get_all_channels() - if not _all_channels: - logger.warning( - f"[{LogOutputIdentifiers.PLUGINS_COMMANDS}]: '{data.command}' command warning: the channel tree could not be retrieved." - ) - GUIFramework.gui( - data.message, - target_channels=_all_channels, - user_id=data.actor, - ) - - if _parameters.get(ParameterDefinitions.Echo.ME, False): - _me = mumble_utils.get_user_by_id(data.actor) - if not _me: - logger.warning( - f"[{LogOutputIdentifiers.PLUGINS_COMMANDS}]: '{data.command}' command warning: the user was not found with the provided id." - ) - GUIFramework.gui( - data.message, - target_users=_me, - user_id=data.actor, - ) - - _user: Optional["User"] = _parameters.get(ParameterDefinitions.Echo.USER, None) - if _user: - GUIFramework.gui( - data.message, - target_users=_user, - user_id=data.actor, - ) - - _users: Optional[List["User"]] = _parameters.get(ParameterDefinitions.Echo.USERS, []) - if _users: - GUIFramework.gui( - data.message, - target_users=_users, - user_id=data.actor, - ) - - _my_channel: Optional["Channel"] = _parameters.get(ParameterDefinitions.Echo.MYCHANNEL, None) - if _my_channel: - _channel_user = mumble_utils.get_user_by_id(data.actor) - if not _channel_user: - return - _channel_obj = mumble_utils.get_channel_by_user(_channel_user) - GUIFramework.gui( - data.message, - target_channels=_channel_obj, - user_id=data.actor, - ) - - _channel: Optional["Channel"] = _parameters.get(ParameterDefinitions.Echo.CHANNEL, None) - if _channel: - GUIFramework.gui( - data.message, - target_channels=_channel, - user_id=data.actor, - ) - - _channels: Optional[List["Channel"]] = _parameters.get(ParameterDefinitions.Echo.CHANNELS, []) - if _channels: - GUIFramework.gui( - data.message, - target_channels=_channels, - user_id=data.actor, - ) - if not any(x in self.command_parameters[self.echo.__name__] for x in _parameters.keys()): if not data.message.strip(): GUIFramework.gui( @@ -132,13 +65,12 @@ def echo(self, data: "Command") -> None: user_id=data.actor, ) return - if not _channel: - _channel = mumble_utils.get_my_channel() - GUIFramework.gui( - data.message, - target_channels=_channel, - user_id=data.actor, - ) + _channel = mumble_utils.get_my_channel() + GUIFramework.gui( + data.message, + target_channels=_channel, + user_id=data.actor, + ) @command( parameters=ParameterDefinitions.Move.get_definitions(), @@ -155,7 +87,7 @@ def move(self, data: "Command") -> None: return if not any(x in self.command_parameters[self.move.__name__] for x in _parameters.keys()): - _target_channel = data.message.strip() + _target_channel = data.message.strip().replace("_", " ") if not _target_channel: GUIFramework.gui( f"'{data._command}' command error: a target channel must be specified when no parameters are used.", @@ -163,50 +95,15 @@ def move(self, data: "Command") -> None: user_id=data.actor, ) return - _parameters[ParameterDefinitions.Move.TO_CHANNEL] = _target_channel - - _channel: Optional[Union["Channel", str]] = _parameters.get(ParameterDefinitions.Move.TO_CHANNEL, None) - if isinstance(_channel, Channel): - _channel.move_in() - return - elif isinstance(_channel, str): - _channel = _channel.strip().replace("_", " ") - _search_channel: Optional["Channel"] = mumble_utils.get_channel_by_name(_channel) + _search_channel: Optional["Channel"] = mumble_utils.get_channel_by_name(_target_channel) if _search_channel is None: GUIFramework.gui( - f"'{data._command}' command error: cannot find specified channel '{_channel}'.", + f"'{data._command}' command error: cannot find specified channel '{_target_channel}'.", target_users=mumble_utils.get_user_by_id(data.actor), user_id=data.actor, ) return _search_channel.move_in() - return - - _user: Optional["User"] = _parameters.get(ParameterDefinitions.Move.TO_USER, None) - if _user: - _user_channel: Optional["Channel"] = mumble_utils.get_channel_by_user(_user) - if not _user_channel: - GUIFramework.gui( - f"'{data._command}' command error: unable to find the channel '{_user['name']}' belongs to.", - target_users=mumble_utils.get_user_by_id(data.actor), - user_id=data.actor, - ) - return - _user_channel.move_in() - return - - _me: Optional["User"] = _parameters.get(ParameterDefinitions.Move.TO_ME, None) - if _me: - _my_channel: Optional["Channel"] = mumble_utils.get_channel_by_user(_me) - if not _my_channel: - GUIFramework.gui( - f"'{data._command}' command error: unable to find the channel '{_me['name']}' belongs to.", - target_users=mumble_utils.get_user_by_id(data.actor), - user_id=data.actor, - ) - return - _my_channel.move_in() - return @command( parameters=ParameterDefinitions.Themes.get_definitions(), @@ -214,31 +111,32 @@ def move(self, data: "Command") -> None: parameters_required=True, ) def themes(self, data: "Command") -> None: + # Example: + # !themes.list -> Displays a list of the available gui themes. + # !themes.switch "theme_name" -> Switches the gui theme to the selected theme. + # !themes.new "theme_name" -> Creates a new theme from a template theme. + # !themes.delete "theme_name" -> Deletes the specified theme, and falls back to a default theme if it was in use. + # !themes.show "theme_name" -> Shows the config data for the specified theme. + # !themes.update="theme_name" item1=value1... -> Updates a specified theme with the specified new values. + _parameters = self.verify_parameters(self.themes.__name__, data) if _parameters is None: return - _theme_list = _parameters.get(ParameterDefinitions.Themes.LIST, None) - if _theme_list: - _msgs: List[str] = ["Available themes: "] - for idx, theme in enumerate(_theme_list): - _msgs.append(f"{idx+1}) {theme}") - GUIFramework.gui( - text=_msgs, - target_users=mumble_utils.get_user_by_id(data.actor), - ) - - _switch_themes = _parameters.get(ParameterDefinitions.Themes.SWITCH, None) - if _switch_themes: - GUIFramework.gui( - text=f"Switched theme to: {data.message.strip()}", - target_users=mumble_utils.get_user_by_id(data.actor), - ) - - def _parameter_themes_list(self, data: "Command", parameter: str) -> List[str]: - return theme_utils.list_themes() + def _parameter_themes_list(self, data: "Command", parameter: str) -> None: + _theme_list = theme_utils.list_themes() + if not _theme_list: + logger.error(f"[{LogOutputIdentifiers.PLUGINS_COMMANDS}]: '{data.command}' command error: the themes list could not be retrieved.") + return + _msgs: List[str] = ["Available themes: "] + for idx, theme in enumerate(_theme_list): + _msgs.append(f"{idx+1}) {theme}") + GUIFramework.gui( + text=_msgs, + target_users=mumble_utils.get_user_by_id(data.actor), + ) - def _parameter_themes_switch(self, data: "Command", parameter: str) -> bool: + def _parameter_themes_switch(self, data: "Command", parameter: str) -> None: _new_theme = data.message.strip() _available_themes = theme_utils.list_themes() if not _new_theme or _new_theme not in _available_themes: @@ -252,28 +150,181 @@ def _parameter_themes_switch(self, data: "Command", parameter: str) -> bool: text=_msgs, target_users=mumble_utils.get_user_by_id(data.actor), ) - return False - return theme_utils.switch_themes(_new_theme) + return + _switched_themes = theme_utils.switch_themes(_new_theme) + if not _switched_themes: + logger.error(f"[{LogOutputIdentifiers.PLUGINS_COMMANDS}]: '{data.command}' command error: the theme could not be switched.") + return + GUIFramework.gui( + text=f"Switched theme to: {data.message.strip()}", + target_users=mumble_utils.get_user_by_id(data.actor), + ) + + def _parameter_themes_new(self, data: "Command", parameter: str) -> None: + _new_theme = data.message.strip().replace(" ", "_") + if not theme_utils.new_theme(_new_theme): + logger.error(f"[{LogOutputIdentifiers.PLUGINS_COMMANDS}]: '{data.command}' command error: failed to create theme.") + GUIFramework.gui( + text=f"Failed to create new theme: {_new_theme}", + target_users=mumble_utils.get_user_by_id(data.actor), + ) + return + GUIFramework.gui( + text=f"Created new theme: {_new_theme}", + target_users=mumble_utils.get_user_by_id(data.actor), + ) + + def _parameter_themes_delete(self, data: "Command", parameter: str) -> None: + _delete_theme = data.message.strip().replace(" ", "_") + if not theme_utils.delete_theme(_delete_theme): + logger.error(f"[{LogOutputIdentifiers.PLUGINS_COMMANDS}]: '{data.command}' command error: failed to delete theme.") + GUIFramework.gui( + text=f"Failed to delete theme: {_delete_theme}", + target_users=mumble_utils.get_user_by_id(data.actor), + ) + return + GUIFramework.gui( + text=f"Deleted theme: {_delete_theme}", + target_users=mumble_utils.get_user_by_id(data.actor), + ) + + def _parameter_themes_update(self, data: "Command", parameter: str) -> None: + parameter_split = parameter.split("=", 1) + if not len(parameter_split) == 2: + GUIFramework.gui( + f"'{data._command}' command error: a user name was not provided.", + target_users=mumble_utils.get_user_by_id(data.actor), + ) + + _theme_name: str = parameter_split[1].strip().replace("_", " ") + if not _theme_name: + GUIFramework.gui( + f"'{data._command}' command error: an invalid theme name was provided.", + target_users=mumble_utils.get_user_by_id(data.actor), + ) + return + + _update_items = data.message.strip().replace(" ", "_") + if not _update_items: + GUIFramework.gui( + f"'{data._command}' command error: invalid update values provided. Update values must follow the format: 'item1=value1, item2=value2, ...'", + target_users=mumble_utils.get_user_by_id(data.actor), + ) + return + _update_items_split = _update_items.split(",") + _update_items_pairs = {} + for item in _update_items_split: + _pairs_split = item.split("=", 1) + if len(_pairs_split) != 2: + GUIFramework.gui( + f"'{data._command}' command error: invalid update values provided. Update values must follow the format: 'item1=value1, item2=value2, ...'", + target_users=mumble_utils.get_user_by_id(data.actor), + ) + return + _key, _value = _pairs_split[0], _pairs_split[1] + _update_items_pairs[_key] = _value + + _theme_updated: bool = theme_utils.update_theme(_theme_name, _update_items_pairs) + if not _theme_updated: + GUIFramework.gui( + f"'{data._command}' command error: unable to update theme. Ensure the theme exists and valid values are provided.", + target_users=mumble_utils.get_user_by_id(data.actor), + ) + return + GUIFramework.gui( + f"Updated theme: {_theme_name}", + target_users=mumble_utils.get_user_by_id(data.actor), + ) + + def _parameter_themes_show(self, data: "Command", parameter: str) -> None: + _theme_name = data.message.strip().replace(" ", "_") + _selected_theme = theme_utils._get_theme(_theme_name) + if not _selected_theme: + logger.error(f"[{LogOutputIdentifiers.PLUGINS_COMMANDS}]: '{data.command}' command error: failed to show theme.") + GUIFramework.gui( + text=f"Failed to show theme: {_theme_name}", + target_users=mumble_utils.get_user_by_id(data.actor), + ) + return + _msgs = [f"[{_theme_name}]"] + for key, value in _selected_theme.items(): + _msgs.append(f"{key}={value}") + GUIFramework.gui( + text=_msgs, + target_users=mumble_utils.get_user_by_id(data.actor), + ) def _parameter_move_to_me(self, data: "Command", parameter: str) -> Optional["User"]: - return mumble_utils.get_user_by_id(data.actor) + _me = mumble_utils.get_user_by_id(data.actor) + if not _me: + logger.error(f"[{LogOutputIdentifiers.PLUGINS_COMMANDS}]: '{data.command}' command error: the user could not be retrieved.") + return + _my_channel: Optional["Channel"] = mumble_utils.get_channel_by_user(_me) + if not _my_channel: + GUIFramework.gui( + f"'{data._command}' command error: unable to find the channel '{_me['name']}' belongs to.", + target_users=mumble_utils.get_user_by_id(data.actor), + user_id=data.actor, + ) + return + _my_channel.move_in() + return def _parameter_move_to_user(self, data: "Command", parameter: str) -> Optional["User"]: - return self._get_user(data, parameter) + _user = self._get_user(data, parameter) + if not _user: + logger.error(f"[{LogOutputIdentifiers.PLUGINS_COMMANDS}]: '{data.command}' command error: the user could not be retrieved.") + return + _user_channel: Optional["Channel"] = mumble_utils.get_channel_by_user(_user) + if not _user_channel: + GUIFramework.gui( + f"'{data._command}' command error: unable to find the channel '{_user['name']}' belongs to.", + target_users=mumble_utils.get_user_by_id(data.actor), + user_id=data.actor, + ) + return + _user_channel.move_in() def _parameter_move_to_channel(self, data: "Command", parameter: str) -> Optional["Channel"]: - return self._get_channel(data, parameter) + _channel = self._get_channel(data, parameter) + if not _channel: + return + _channel.move_in() - def _parameter_echo_me(self, data: "Command", parameter: str) -> bool: - return True + def _parameter_echo_me(self, data: "Command", parameter: str) -> None: + _me = mumble_utils.get_user_by_id(data.actor) + if not _me: + logger.error(f"[{LogOutputIdentifiers.PLUGINS_COMMANDS}]: '{data.command}' command error: the user was not found with the provided id.") + return + GUIFramework.gui( + data.message, + target_users=_me, + user_id=data.actor, + ) - def _parameter_echo_broadcast(self, data: "Command", parameter: str) -> bool: - return True + def _parameter_echo_broadcast(self, data: "Command", parameter: str) -> None: + _all_channels: List["Channel"] = mumble_utils.get_all_channels() + if not _all_channels: + logger.error(f"[{LogOutputIdentifiers.PLUGINS_COMMANDS}]: '{data.command}' command error: the channel tree could not be retrieved.") + return + GUIFramework.gui( + data.message, + target_channels=_all_channels, + user_id=data.actor, + ) - def _parameter_echo_user(self, data: "Command", parameter: str) -> Optional["User"]: - return self._get_user(data, parameter) + def _parameter_echo_user(self, data: "Command", parameter: str) -> None: + _user = self._get_user(data, parameter) + if not _user: + logger.error(f"[{LogOutputIdentifiers.PLUGINS_COMMANDS}]: '{data.command}' command error: the user could not be retrieved.") + return + GUIFramework.gui( + data.message, + target_users=_user, + user_id=data.actor, + ) - def _parameter_echo_users(self, data: "Command", parameter: str) -> Optional[List["User"]]: + def _parameter_echo_users(self, data: "Command", parameter: str) -> None: parameter_split = parameter.split("=", 1) if len(parameter_split) == 2: user_names = [user.strip().replace("_", " ") for user in parameter_split[1].split(",")] @@ -295,19 +346,41 @@ def _parameter_echo_users(self, data: "Command", parameter: str) -> Optional[Lis f"'{data._command}' command warning: cannot find specified user '{_user}'.", target_users=mumble_utils.get_user_by_id(data.actor), ) - return _found_users + GUIFramework.gui( + data.message, + target_users=_found_users, + user_id=data.actor, + ) + return GUIFramework.gui( f"'{data._command}' command warning: an invalid list of user names was provided.", target_users=mumble_utils.get_user_by_id(data.actor), ) - def _parameter_echo_channel(self, data: "Command", parameter: str) -> Optional["Channel"]: - return self._get_channel(data, parameter) + def _parameter_echo_channel(self, data: "Command", parameter: str) -> None: + _channel = self._get_channel(data, parameter) + if not _channel: + logger.error(f"[{LogOutputIdentifiers.PLUGINS_COMMANDS}]: '{data.command}' command error: the channel could not be retrieved.") + return + GUIFramework.gui( + data.message, + target_channels=_channel, + user_id=data.actor, + ) - def _parameter_echo_mychannel(self, data: "Command", parameter: str) -> bool: - return True + def _parameter_echo_mychannel(self, data: "Command", parameter: str) -> None: + _channel_user = mumble_utils.get_user_by_id(data.actor) + if not _channel_user: + return + _channel_obj = mumble_utils.get_channel_by_user(_channel_user) + if _channel_obj: + GUIFramework.gui( + data.message, + target_channels=_channel_obj, + user_id=data.actor, + ) - def _parameter_echo_channels(self, data: "Command", parameter: str) -> Optional[List["Channel"]]: + def _parameter_echo_channels(self, data: "Command", parameter: str) -> None: parameter_split = parameter.split("=", 1) if len(parameter_split) == 2: channel_names = [channel.strip().replace("_", " ") for channel in parameter_split[1].split(",")] @@ -329,7 +402,12 @@ def _parameter_echo_channels(self, data: "Command", parameter: str) -> Optional[ f"'{data._command}' command warning: cannot find specified channel '{_channel}'.", target_users=mumble_utils.get_user_by_id(data.actor), ) - return _found_channels + GUIFramework.gui( + data.message, + target_channels=_found_channels, + user_id=data.actor, + ) + return GUIFramework.gui( f"'{data._command}' command warning: an invalid list of channel names was provided.", target_users=mumble_utils.get_user_by_id(data.actor), @@ -360,13 +438,13 @@ def _parameter_echo_delay(self, data: "Command", parameter: str) -> Optional[int target_users=mumble_utils.get_user_by_id(data.actor), ) - def _get_user(self, data: "Command", parameter: str): + def _get_user(self, data: "Command", parameter: str) -> Optional["User"]: parameter_split = parameter.split("=", 1) if len(parameter_split) == 2: _search_term: str = parameter_split[1].strip().replace("_", " ") if not _search_term: GUIFramework.gui( - f"'{data._command}' command warning: an invalid user name was provided.", + f"'{data._command}' command error: an invalid user name was provided.", target_users=mumble_utils.get_user_by_id(data.actor), ) return @@ -374,22 +452,22 @@ def _get_user(self, data: "Command", parameter: str): if _search_user is not None: return _search_user GUIFramework.gui( - f"'{data._command}' command warning: cannot find specified user '{_search_term}'.", + f"'{data._command}' command error: cannot find specified user '{_search_term}'.", target_users=mumble_utils.get_user_by_id(data.actor), ) return GUIFramework.gui( - f"'{data._command}' command warning: a user name was not provided.", + f"'{data._command}' command error: a user name was not provided.", target_users=mumble_utils.get_user_by_id(data.actor), ) - def _get_channel(self, data: "Command", parameter: str): + def _get_channel(self, data: "Command", parameter: str) -> Optional["Channel"]: parameter_split = parameter.split("=", 1) if len(parameter_split) == 2: _search_term: str = parameter_split[1].strip().replace("_", " ") if not _search_term: GUIFramework.gui( - f"'{data._command}' command warning: an invalid channel name was provided.", + f"'{data._command}' command error: an invalid channel name was provided.", target_users=mumble_utils.get_user_by_id(data.actor), ) return @@ -397,11 +475,11 @@ def _get_channel(self, data: "Command", parameter: str): if _search_channel is not None: return _search_channel GUIFramework.gui( - f"'{data._command}' command warning: cannot find specified channel '{_search_term}'.", + f"'{data._command}' command error: cannot find specified channel '{_search_term}'.", target_users=mumble_utils.get_user_by_id(data.actor), ) return GUIFramework.gui( - f"'{data._command}' command warning: a channel name was not provided.", + f"'{data._command}' command error: a channel name was not provided.", target_users=mumble_utils.get_user_by_id(data.actor), ) diff --git a/src/plugins/builtin_core/resources/gui_custom_theme_template.toml b/src/plugins/builtin_core/resources/gui_custom_theme_template.toml new file mode 100644 index 0000000..a3f49d6 --- /dev/null +++ b/src/plugins/builtin_core/resources/gui_custom_theme_template.toml @@ -0,0 +1,15 @@ +# +# This is a template file. +# Please do not modify this! +# + +[template] +text_type = "header" +text_color = "snow" +text_align = "center" + +table_align = "left" +table_bg_color = "black" + +row_align = "left" +row_bg_color = "black" \ No newline at end of file diff --git a/src/plugins/builtin_core/utility/constants.py b/src/plugins/builtin_core/utility/constants.py index 42abd56..46f7d81 100644 --- a/src/plugins/builtin_core/utility/constants.py +++ b/src/plugins/builtin_core/utility/constants.py @@ -44,6 +44,7 @@ class Themes: NEW: str = "new" DELETE: str = "delete" UPDATE: str = "update" + SHOW: str = "show" @staticmethod def get_definitions() -> List[str]: @@ -53,4 +54,5 @@ def get_definitions() -> List[str]: ParameterDefinitions.Themes.NEW, ParameterDefinitions.Themes.DELETE, ParameterDefinitions.Themes.UPDATE, + ParameterDefinitions.Themes.SHOW, ] diff --git a/src/plugins/builtin_core/utility/theme_utils.py b/src/plugins/builtin_core/utility/theme_utils.py index 754ded6..16983a2 100644 --- a/src/plugins/builtin_core/utility/theme_utils.py +++ b/src/plugins/builtin_core/utility/theme_utils.py @@ -1,10 +1,12 @@ -from typing import List, Optional, TYPE_CHECKING +import logging +import pathlib +from typing import List, Optional, Dict, Any from src.settings import settings from src.exceptions import PluginError from src.constants import MumimoCfgFields +from src.config import Config -if TYPE_CHECKING: - from src.config import Config +logger = logging.getLogger(__name__) def list_themes() -> List[str]: @@ -21,7 +23,7 @@ def switch_themes(theme: str) -> bool: _config: Optional["Config"] = settings.configs.get_mumimo_config() if not _config: - raise PluginError("Unable to switch themes: mumimo config could not be retrieved from settings.") + raise PluginError("Unable to switch themes: mumimo config could not be retrieved from settings.", logger=logger) _success_switch: bool = _config.set(MumimoCfgFields.SETTINGS.GUI.SELECTED_THEME, theme) _saved_data: str = _config.save( @@ -29,3 +31,106 @@ def switch_themes(theme: str) -> bool: modified_field_name=MumimoCfgFields.SETTINGS.GUI.SELECTED_THEME, ) return _success_switch and _saved_data is not None + + +def delete_theme(theme: str) -> bool: + theme = theme.strip().replace(" ", "_") + + if _get_theme(theme) is None: + logger.error(f"Failed to delete gui theme: theme '{theme}' does not exist.") + return False + + _themes: Optional["Config"] = settings.configs.get_gui_themes() + if not _themes: + logger.error("Failed to delete gui theme: the gui themes could not be retrieved from settings.") + return False + + del _themes[theme] + _themes.save() + logger.debug(f"Deleted gui theme: {theme}.") + return True + + +def new_theme(theme: str) -> bool: + _template_theme = _get_template_theme() + _themes: Optional["Config"] = settings.configs.get_gui_themes() + if not _themes: + logger.error("Failed to create new gui theme: the gui themes could not be retrieved from settings.") + return False + + theme = theme.strip().replace(" ", "_") + if _themes.get(theme, None): + f"Failed to create new gui theme: theme '{theme}' already exists." + return False + + # Create new theme from default template. + _new_theme: Dict[str, Any] = {theme: _template_theme} + _themes.update(_new_theme) + # Save new theme to the toml file. + _themes.save() + logger.debug(f"Created new gui theme from template: {theme}.") + + return True + + +def update_theme(theme: str, items: Dict[str, Any]) -> bool: + _themes: Optional["Config"] = settings.configs.get_gui_themes() + if not _themes: + logger.error("Failed to update gui theme: the gui themes could not be retrieved from settings.") + return False + + theme = theme.strip().replace(" ", "_") + _selected_theme = _themes.get(theme, None) + if not _selected_theme: + logger.error(f"Failed to update gui theme: theme '{theme}' does not exist.") + return False + + for key, value in items.items(): + if key in _selected_theme.keys(): + _selected_theme[key] = value + _themes.update({theme: _selected_theme}) + + _themes.save() + logger.debug(f"Updated gui theme '{theme}' with values [{', '.join('{}={}'.format(*x) for x in items.items())}]") + + return True + + +def _get_template_theme() -> "Config": + _config: Optional["Config"] = settings.configs.get_mumimo_config() + if not _config: + raise PluginError("Unable to get template theme: mumimo config could not be retrieved from settings.", logger=logger) + + _plugin_path = _config.get(MumimoCfgFields.SETTINGS.PLUGINS.PLUGINS_PATH, None) + if not _plugin_path: + raise PluginError("Unable to get template theme: mumimo config does not have a defined plugin path.") + + _theme_template_path = pathlib.Path.cwd() / _plugin_path / "builtin_core/resources/gui_custom_theme_template.toml" + _theme_cfg: "Config" = Config(_theme_template_path) + if _theme_cfg is None: + raise PluginError(f"Unable to get template theme: gui custom theme template file is missing. Expected path: {_theme_template_path}") + _theme_cfg.read() + + _template = _theme_cfg.get("template", None) + if _template is None: + raise PluginError("Unable to get template theme: 'template' section is missing in gui custom theme template file.") + + return _template + + +def _get_theme(theme: str) -> Optional["Config"]: + if not theme: + logger.error("Failed to get gui theme: no theme name provided.") + return None + + _themes: Optional["Config"] = settings.configs.get_gui_themes() + if not _themes: + logger.error("Failed to get gui theme: the gui themes could not be retrieved from settings.") + return None + + _theme = _themes.get(theme, None) + if not _theme: + logger.error(f"Failed to get gui theme: theme '{theme}' does not exist.") + return None + + return _theme From 4f379f3aa9926e2640f823b2904e83de4f03e085 Mon Sep 17 00:00:00 2001 From: Jason Jerome Date: Tue, 25 Apr 2023 03:12:33 -0400 Subject: [PATCH 12/12] Removed unused type check import --- src/plugins/builtin_core/plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/builtin_core/plugin.py b/src/plugins/builtin_core/plugin.py index 042a39e..ce7a0f3 100644 --- a/src/plugins/builtin_core/plugin.py +++ b/src/plugins/builtin_core/plugin.py @@ -19,7 +19,6 @@ if TYPE_CHECKING: from src.lib.command import Command - from src.config import Config class Plugin(PluginBase):