From e5a68db6c24ad0f9bd5d0f0409ccc7249e35d9e5 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Thu, 9 Mar 2023 08:39:03 +0100 Subject: [PATCH 001/125] Add support for ' and " in tag names --- shortcut_composer/api_krita/wrappers/database.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shortcut_composer/api_krita/wrappers/database.py b/shortcut_composer/api_krita/wrappers/database.py index 80ca6ae0..2048b8ca 100644 --- a/shortcut_composer/api_krita/wrappers/database.py +++ b/shortcut_composer/api_krita/wrappers/database.py @@ -45,6 +45,7 @@ def _single_column_query(self, sql_query: str, value: str) -> List[Any]: def get_preset_names_from_tag(self, tag_name: str) -> List[str]: """Return list of all preset names that belong to given tag.""" + tag_name = tag_name.replace("\"", "\"\"") sql_query = f''' SELECT DISTINCT r.name AS preset FROM tags t @@ -53,7 +54,7 @@ def get_preset_names_from_tag(self, tag_name: str) -> List[str]: JOIN resources r ON r.id = rt.resource_id WHERE - t.name='{tag_name}' + t.name="{tag_name}" AND rt.active = 1 ''' return self._single_column_query(sql_query, "preset") From 170c575a292117e4ffd15596058bd25773581982 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 10 Mar 2023 14:58:36 +0100 Subject: [PATCH 002/125] Make sure deleted/renamed presets do not display error --- shortcut_composer/api_krita/wrappers/view.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/shortcut_composer/api_krita/wrappers/view.py b/shortcut_composer/api_krita/wrappers/view.py index 915fd2ef..c8edc041 100644 --- a/shortcut_composer/api_krita/wrappers/view.py +++ b/shortcut_composer/api_krita/wrappers/view.py @@ -11,11 +11,13 @@ class _KritaPreset(Protocol): """Krita `Resource` object API.""" + def name(self) -> str: ... class KritaView(Protocol): """Krita `View` object API.""" + def currentBrushPreset(self) -> _KritaPreset: ... def currentBlendingMode(self) -> str: ... def paintingOpacity(self) -> float: ... @@ -47,7 +49,8 @@ def brush_preset(self) -> str: @brush_preset.setter def brush_preset(self, preset_name: str) -> None: """Set brush preset inside this `View` using its name.""" - self.view.setCurrentBrushPreset(self.preset_map[preset_name]) + if preset_name in self.preset_map: + self.view.setCurrentBrushPreset(self.preset_map[preset_name]) @property def blending_mode(self) -> BlendingMode: From bc254a53862fe0ecb9baa86421e8b4a09e2133d3 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 003/125] POC: type aware config --- shortcut_composer/actions.py | 15 +++---- shortcut_composer/composer_utils/config.py | 44 ++++++++++++------- .../composer_utils/utils/action_values.py | 17 +++---- .../data_components/writable_values.py | 18 +++----- .../pie_menu_utils/widget_utils/edit_mode.py | 8 +--- 5 files changed, 49 insertions(+), 53 deletions(-) diff --git a/shortcut_composer/actions.py b/shortcut_composer/actions.py index 0149509b..69fe6723 100644 --- a/shortcut_composer/actions.py +++ b/shortcut_composer/actions.py @@ -14,15 +14,15 @@ from PyQt5.QtGui import QColor -from api_krita.enums import BlendingMode, Tool, Toggle, TransformMode +from api_krita.enums import Tool, Toggle from core_components import instructions, controllers from composer_utils import Config from input_adapter import ComplexAction from data_components import ( CurrentLayerStack, - PickStrategy, EnumConfigValues, TagConfigValues, + PickStrategy, Slider, Range, ) @@ -95,7 +95,7 @@ def create_actions() -> List[ComplexAction]: return [ name="Cycle selection tools", controller=controllers.ToolController(), default_value=Tool.FREEHAND_BRUSH, - values=EnumConfigValues(Config.SELECTION_TOOLS_VALUES, Tool), + values=EnumConfigValues(Config.SELECTION_TOOLS_VALUES), ), # Control undo and redo actions by sliding the cursor horizontally @@ -183,7 +183,7 @@ def create_actions() -> List[ComplexAction]: return [ templates.PieMenu( name="Pick misc tools", controller=controllers.ToolController(), - values=EnumConfigValues(Config.MISC_TOOLS_VALUES, Tool), + values=EnumConfigValues(Config.MISC_TOOLS_VALUES), pie_radius_scale=0.9 ), @@ -193,22 +193,21 @@ def create_actions() -> List[ComplexAction]: return [ name="Pick painting blending modes", controller=controllers.BlendingModeController(), instructions=[instructions.SetBrushOnNonPaintable()], - values=EnumConfigValues(Config.BLENDING_MODES_VALUES, BlendingMode), + values=EnumConfigValues(Config.BLENDING_MODES_VALUES), ), # Use pie menu to create painting layer with selected blending mode. templates.PieMenu( name="Create painting layer with blending mode", controller=controllers.CreateLayerWithBlendingController(), - values=EnumConfigValues( - Config.CREATE_BLENDING_LAYER_VALUES, BlendingMode), + values=EnumConfigValues(Config.CREATE_BLENDING_LAYER_VALUES), ), # Pick one of the transform tool modes. templates.PieMenu( name="Pick transform tool modes", controller=controllers.TransformModeController(), - values=EnumConfigValues(Config.TRANSFORM_MODES_VALUES, TransformMode), + values=EnumConfigValues(Config.TRANSFORM_MODES_VALUES), ), # Use pie menu to pick one of presets from tag specified in settings. diff --git a/shortcut_composer/composer_utils/config.py b/shortcut_composer/composer_utils/config.py index dd7960ba..d9e540ea 100644 --- a/shortcut_composer/composer_utils/config.py +++ b/shortcut_composer/composer_utils/config.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import Union, Any, TypeVar, List +from typing import Union, Any, TypeVar from enum import Enum from api_krita import Krita @@ -72,7 +72,7 @@ class Config(Enum): CREATE_BLENDING_LAYER_VALUES = "Create blending layer values" @property - def default(self) -> Union[float, int, str]: + def default(self) -> Union[float, int, str, list]: """Return default value of the field.""" return _defaults[self] @@ -91,10 +91,24 @@ def read(self) -> Any: def write(self, value: Any) -> None: """Write given value to krita config file.""" + default = self.default + if isinstance(default, list): + element = default[0] + if isinstance(element, Enum): + print(self) + print(value) + to_write = "\t".join([enum.value for enum in value]) + elif isinstance(element, str): + to_write = "\t".join(value) + else: + raise RuntimeError(f"Can't parse {value} in {self}.") + else: + to_write = value + Krita.write_setting( group="ShortcutComposer", name=self.value, - value=value + value=to_write ) @staticmethod @@ -109,10 +123,6 @@ def get_sleep_time() -> int: fps_limit = Config.FPS_LIMIT.read() return round(1000/fps_limit) if fps_limit else 1 - @staticmethod - def format_enums(enums: List[Enum]) -> str: - return "\t".join([enum.name for enum in enums]) - _defaults = { Config.SHORT_VS_LONG_PRESS_TIME: 0.3, @@ -132,19 +142,19 @@ def format_enums(enums: List[Enum]) -> str: Config.TAG_GREEN_VALUES: "", Config.TAG_BLUE_VALUES: "", - Config.SELECTION_TOOLS_VALUES: Config.format_enums([ + Config.SELECTION_TOOLS_VALUES: [ Tool.FREEHAND_SELECTION, Tool.RECTANGULAR_SELECTION, Tool.CONTIGUOUS_SELECTION, - ]), - Config.MISC_TOOLS_VALUES: Config.format_enums([ + ], + Config.MISC_TOOLS_VALUES: [ Tool.CROP, Tool.REFERENCE, Tool.GRADIENT, Tool.MULTI_BRUSH, Tool.ASSISTANTS, - ]), - Config.BLENDING_MODES_VALUES: Config.format_enums([ + ], + Config.BLENDING_MODES_VALUES: [ BlendingMode.NORMAL, BlendingMode.OVERLAY, BlendingMode.COLOR, @@ -153,8 +163,8 @@ def format_enums(enums: List[Enum]) -> str: BlendingMode.SCREEN, BlendingMode.DARKEN, BlendingMode.LIGHTEN, - ]), - Config.CREATE_BLENDING_LAYER_VALUES: Config.format_enums([ + ], + Config.CREATE_BLENDING_LAYER_VALUES: [ BlendingMode.NORMAL, BlendingMode.ERASE, BlendingMode.OVERLAY, @@ -164,14 +174,14 @@ def format_enums(enums: List[Enum]) -> str: BlendingMode.SCREEN, BlendingMode.DARKEN, BlendingMode.LIGHTEN, - ]), - Config.TRANSFORM_MODES_VALUES: Config.format_enums([ + ], + Config.TRANSFORM_MODES_VALUES: [ TransformMode.FREE, TransformMode.PERSPECTIVE, TransformMode.WARP, TransformMode.CAGE, TransformMode.LIQUIFY, TransformMode.MESH, - ]) + ], } """Maps default values to config fields.""" diff --git a/shortcut_composer/composer_utils/utils/action_values.py b/shortcut_composer/composer_utils/utils/action_values.py index 6329d15c..3cc1d210 100644 --- a/shortcut_composer/composer_utils/utils/action_values.py +++ b/shortcut_composer/composer_utils/utils/action_values.py @@ -1,7 +1,8 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import Set +from typing import Set, List +from enum import Enum from PyQt5.QtCore import Qt from PyQt5.QtWidgets import ( @@ -76,18 +77,14 @@ def apply(self): texts = [] for row in range(self.current_list.count()): texts.append(self.current_list.item(row).text()) - self.config.write("\t".join(texts)) + self.config.write(texts) def refresh(self): self.current_list.clear() - current: str = self.config.read() - current_list = current.split("\t") - if current_list == ['']: - current_list = [] - for item in current_list: - if item in self.allowed_values: - self.current_list.addItem(item) + current_list: List[Enum] = self.config.read() + text_list = [item.value for item in current_list] + self.current_list.addItems(text_list) self.available_list.clear() - allowed_items = sorted(self.allowed_values - set(current_list)) + allowed_items = sorted(self.allowed_values - set(text_list)) self.available_list.addItems(allowed_items) diff --git a/shortcut_composer/data_components/writable_values.py b/shortcut_composer/data_components/writable_values.py index 91887fce..6a1ecf6a 100644 --- a/shortcut_composer/data_components/writable_values.py +++ b/shortcut_composer/data_components/writable_values.py @@ -1,9 +1,11 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later +from typing import List + from api_krita.wrappers import Database from composer_utils import Config -from typing import Type, TypeVar +from typing import TypeVar from enum import Enum T = TypeVar('T', bound=Enum) @@ -36,8 +38,7 @@ def __init__(self, tag: Config, tag_values: Config) -> None: with Database() as database: tag_presets = database.get_preset_names_from_tag(tag.read()) - preset_string: str = tag_values.read() - preset_order = preset_string.split("\t") + preset_order: List[str] = tag_values.read() preset_order = [p for p in preset_order if p in tag_presets] missing = [p for p in tag_presets if p not in preset_order] @@ -56,13 +57,6 @@ class EnumConfigValues(list): ``` """ - def __init__(self, values: Config, enum_type: Type[T]) -> None: + def __init__(self, values: Config) -> None: self.config_to_write = values - value_string: str = values.read() - if value_string == '': - return - values_list = value_string.split("\t") - try: - self.extend([enum_type[value] for value in values_list]) - except KeyError: - print(f"{values_list} not in {enum_type}") + self.extend(values.read()) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py index d0f0c73a..566f4be3 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py @@ -1,5 +1,3 @@ -from enum import Enum -from composer_utils import Config from typing import TYPE_CHECKING if TYPE_CHECKING: from ..pie_widget import PieWidget @@ -38,7 +36,5 @@ def _write_settings(self, obj: 'PieWidget') -> None: return values = [widget.label.value for widget in obj.widget_holder] - if isinstance(values[0], Enum): - obj.config_to_write_back.write(Config.format_enums(values)) - else: - obj.config_to_write_back.write('\t'.join(values)) + print("writing") + obj.config_to_write_back.write(values) From 13d6994fff0d2f6fd5ebef63c454f395adcdd048 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 004/125] Pie has its own config made from config components --- shortcut_composer/actions.py | 116 +++++++---- shortcut_composer/composer_utils/__init__.py | 8 +- shortcut_composer/composer_utils/config.py | 182 +++--------------- .../composer_utils/internal_config.py | 104 ++++++++++ shortcut_composer/data_components/__init__.py | 3 - .../data_components/writable_values.py | 62 ------ shortcut_composer/templates/pie_menu.py | 15 +- .../templates/pie_menu_utils/__init__.py | 3 +- .../templates/pie_menu_utils/pie_widget.py | 25 ++- .../pie_menu_utils/widget_utils/edit_mode.py | 3 +- 10 files changed, 240 insertions(+), 281 deletions(-) create mode 100644 shortcut_composer/composer_utils/internal_config.py delete mode 100644 shortcut_composer/data_components/writable_values.py diff --git a/shortcut_composer/actions.py b/shortcut_composer/actions.py index 69fe6723..8c9590b8 100644 --- a/shortcut_composer/actions.py +++ b/shortcut_composer/actions.py @@ -12,16 +12,16 @@ import templates from typing import List -from PyQt5.QtGui import QColor +# from PyQt5.QtGui import QColor -from api_krita.enums import Tool, Toggle +from api_krita.enums import Tool, Toggle, BlendingMode, TransformMode from core_components import instructions, controllers -from composer_utils import Config +# from composer_utils import Config from input_adapter import ComplexAction from data_components import ( CurrentLayerStack, - EnumConfigValues, - TagConfigValues, + # EnumConfigValues, + # TagConfigValues, PickStrategy, Slider, Range, @@ -95,7 +95,11 @@ def create_actions() -> List[ComplexAction]: return [ name="Cycle selection tools", controller=controllers.ToolController(), default_value=Tool.FREEHAND_BRUSH, - values=EnumConfigValues(Config.SELECTION_TOOLS_VALUES), + values=[ + Tool.FREEHAND_SELECTION, + Tool.RECTANGULAR_SELECTION, + Tool.CONTIGUOUS_SELECTION, + ], ), # Control undo and redo actions by sliding the cursor horizontally @@ -183,7 +187,13 @@ def create_actions() -> List[ComplexAction]: return [ templates.PieMenu( name="Pick misc tools", controller=controllers.ToolController(), - values=EnumConfigValues(Config.MISC_TOOLS_VALUES), + values=[ + Tool.CROP, + Tool.REFERENCE, + Tool.GRADIENT, + Tool.MULTI_BRUSH, + Tool.ASSISTANTS, + ], pie_radius_scale=0.9 ), @@ -193,55 +203,81 @@ def create_actions() -> List[ComplexAction]: return [ name="Pick painting blending modes", controller=controllers.BlendingModeController(), instructions=[instructions.SetBrushOnNonPaintable()], - values=EnumConfigValues(Config.BLENDING_MODES_VALUES), + values=[ + BlendingMode.NORMAL, + BlendingMode.OVERLAY, + BlendingMode.COLOR, + BlendingMode.MULTIPLY, + BlendingMode.ADD, + BlendingMode.SCREEN, + BlendingMode.DARKEN, + BlendingMode.LIGHTEN, + ] ), # Use pie menu to create painting layer with selected blending mode. templates.PieMenu( name="Create painting layer with blending mode", controller=controllers.CreateLayerWithBlendingController(), - values=EnumConfigValues(Config.CREATE_BLENDING_LAYER_VALUES), + values=[ + BlendingMode.NORMAL, + BlendingMode.ERASE, + BlendingMode.OVERLAY, + BlendingMode.COLOR, + BlendingMode.MULTIPLY, + BlendingMode.ADD, + BlendingMode.SCREEN, + BlendingMode.DARKEN, + BlendingMode.LIGHTEN, + ], ), # Pick one of the transform tool modes. templates.PieMenu( name="Pick transform tool modes", controller=controllers.TransformModeController(), - values=EnumConfigValues(Config.TRANSFORM_MODES_VALUES), + values=[ + TransformMode.FREE, + TransformMode.PERSPECTIVE, + TransformMode.WARP, + TransformMode.CAGE, + TransformMode.LIQUIFY, + TransformMode.MESH, + ] ), - # Use pie menu to pick one of presets from tag specified in settings. - # Set tool to FREEHAND BRUSH if current tool does not allow to paint - templates.PieMenu( - name="Pick brush presets (red)", - controller=controllers.PresetController(), - instructions=[instructions.SetBrushOnNonPaintable()], - values=TagConfigValues(Config.TAG_RED, Config.TAG_RED_VALUES), - background_color=QColor(95, 65, 65, 190), - active_color=QColor(200, 70, 70), - ), + # # Use pie menu to pick one of presets from tag specified in settings. + # # Set tool to FREEHAND BRUSH if current tool does not allow to paint + # templates.PieMenu( + # name="Pick brush presets (red)", + # controller=controllers.PresetController(), + # instructions=[instructions.SetBrushOnNonPaintable()], + # values=TagConfigValues(Config.TAG_RED, Config.TAG_RED_VALUES), + # background_color=QColor(95, 65, 65, 190), + # active_color=QColor(200, 70, 70), + # ), - # Use pie menu to pick one of presets from tag specified in settings. - # Set tool to FREEHAND BRUSH if current tool does not allow to paint - templates.PieMenu( - name="Pick brush presets (green)", - controller=controllers.PresetController(), - instructions=[instructions.SetBrushOnNonPaintable()], - values=TagConfigValues(Config.TAG_GREEN, Config.TAG_GREEN_VALUES), - background_color=QColor(65, 95, 65, 190), - active_color=QColor(70, 200, 70), - ), + # # Use pie menu to pick one of presets from tag specified in settings. + # # Set tool to FREEHAND BRUSH if current tool does not allow to paint + # templates.PieMenu( + # name="Pick brush presets (green)", + # controller=controllers.PresetController(), + # instructions=[instructions.SetBrushOnNonPaintable()], + # values=TagConfigValues(Config.TAG_GREEN, Config.TAG_GREEN_VALUES), + # background_color=QColor(65, 95, 65, 190), + # active_color=QColor(70, 200, 70), + # ), - # Use pie menu to pick one of presets from tag specified in settings. - # Set tool to FREEHAND BRUSH if current tool does not allow to paint - templates.PieMenu( - name="Pick brush presets (blue)", - controller=controllers.PresetController(), - instructions=[instructions.SetBrushOnNonPaintable()], - values=TagConfigValues(Config.TAG_BLUE, Config.TAG_BLUE_VALUES), - background_color=QColor(70, 70, 105, 190), - active_color=QColor(110, 160, 235), - ), + # # Use pie menu to pick one of presets from tag specified in settings. + # # Set tool to FREEHAND BRUSH if current tool does not allow to paint + # templates.PieMenu( + # name="Pick brush presets (blue)", + # controller=controllers.PresetController(), + # instructions=[instructions.SetBrushOnNonPaintable()], + # values=TagConfigValues(Config.TAG_BLUE, Config.TAG_BLUE_VALUES), + # background_color=QColor(70, 70, 105, 190), + # active_color=QColor(110, 160, 235), + # ), # ....................................... # Insert your actions implementation here diff --git a/shortcut_composer/composer_utils/__init__.py b/shortcut_composer/composer_utils/__init__.py index 885f4b6f..dd649501 100644 --- a/shortcut_composer/composer_utils/__init__.py +++ b/shortcut_composer/composer_utils/__init__.py @@ -5,5 +5,11 @@ from .settings_dialog import SettingsDialog from .config import Config +from .internal_config import ( + ConfigBase, + BuiltinConfig, + EnumListConfig, + BuiltinListConfig) -__all__ = ["SettingsDialog", "Config"] +__all__ = ["SettingsDialog", "Config", "ConfigBase", + "BuiltinConfig", "EnumListConfig", "BuiltinListConfig"] diff --git a/shortcut_composer/composer_utils/config.py b/shortcut_composer/composer_utils/config.py index d9e540ea..888625a1 100644 --- a/shortcut_composer/composer_utils/config.py +++ b/shortcut_composer/composer_utils/config.py @@ -1,40 +1,18 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import Union, Any, TypeVar +from typing import TypeVar from enum import Enum -from api_krita import Krita -from api_krita.enums import Tool, BlendingMode, TransformMode +from .internal_config import ConfigBase, BuiltinConfig T = TypeVar('T', bound=Enum) -class Config(Enum): +class Config: """ Configuration fields available in the plugin. - Each field remembers its default value: - - `SHORT_VS_LONG_PRESS_TIME` = 0.3 - - `TRACKER_SENSITIVITY_SCALE` = 1.0 - - `TRACKER_DEADZONE` = 0 - - `FPS_LIMIT` = 60 - - `PIE_GLOBAL_SCALE` = 1.0 - - `PIE_ICON_GLOBAL_SCALE` = 1.0 - - `PIE_DEADZONE_GLOBAL_SCALE` = 1.0 - - `PIE_ANIMATION_TIME` = 0.2 - - `TAG_RED` = "★ My Favorites" - - `TAG_GREEN` = "RGBA" - - `TAG_BLUE` = "Erasers" - - `TAG_RED_VALUES` = ... - - `TAG_GREEN_VALUES` = ... - - `TAG_BLUE_VALUES` = ... - - `BLENDING_MODES_VALUES` = ... - - `MISC_TOOLS_VALUES` = ... - - `SELECTION_TOOLS_VALUES` = ... - - `TRANSFORM_MODES_VALUES` = ... - - `CREATE_BLENDING_LAYER_VALUES` = ... - Each field can: - return its default value - read current value from krita config file in correct type @@ -48,140 +26,28 @@ class Config(Enum): EnumConfigValues. """ - SHORT_VS_LONG_PRESS_TIME = "Short vs long press time" - TRACKER_SENSITIVITY_SCALE = "Tracker sensitivity scale" - TRACKER_DEADZONE = "Tracker deadzone" - FPS_LIMIT = "FPS limit" - PIE_GLOBAL_SCALE = "Pie global scale" - PIE_ICON_GLOBAL_SCALE = "Pie icon global scale" - PIE_DEADZONE_GLOBAL_SCALE = "Pie deadzone global scale" - PIE_ANIMATION_TIME = "pie animation time" - - TAG_RED = "Tag (red)" - TAG_GREEN = "Tag (green)" - TAG_BLUE = "Tag (blue)" - - TAG_RED_VALUES = "Tag (red) values" - TAG_GREEN_VALUES = "Tag (green) values" - TAG_BLUE_VALUES = "Tag (blue) values" - - BLENDING_MODES_VALUES = "Blending modes values" - MISC_TOOLS_VALUES = "Misc tools values" - SELECTION_TOOLS_VALUES = "Selection tools values" - TRANSFORM_MODES_VALUES = "Transform modes values" - CREATE_BLENDING_LAYER_VALUES = "Create blending layer values" - - @property - def default(self) -> Union[float, int, str, list]: - """Return default value of the field.""" - return _defaults[self] - - def read(self) -> Any: - """Read current value from krita config file.""" - setting = Krita.read_setting( - group="ShortcutComposer", - name=self.value, - default=str(self.default), - ) - try: - return type(self.default)(setting) - except ValueError: - print(f"Can't parse {setting} to {type(self.default)}") - return self.default - - def write(self, value: Any) -> None: - """Write given value to krita config file.""" - default = self.default - if isinstance(default, list): - element = default[0] - if isinstance(element, Enum): - print(self) - print(value) - to_write = "\t".join([enum.value for enum in value]) - elif isinstance(element, str): - to_write = "\t".join(value) - else: - raise RuntimeError(f"Can't parse {value} in {self}.") - else: - to_write = value - - Krita.write_setting( - group="ShortcutComposer", - name=self.value, - value=to_write - ) - - @staticmethod - def reset_defaults() -> None: + SHORT_VS_LONG_PRESS_TIME = BuiltinConfig("Short vs long press time", 0.3) + TRACKER_SENSITIVITY_SCALE = BuiltinConfig("Tracker sensitivity scale", 1.0) + TRACKER_DEADZONE = BuiltinConfig("Tracker deadzone", 0) + FPS_LIMIT = BuiltinConfig("FPS limit", 60) + PIE_GLOBAL_SCALE = BuiltinConfig("Pie global scale", 1.0) + PIE_ICON_GLOBAL_SCALE = BuiltinConfig("Pie icon global scale", 1.0) + PIE_DEADZONE_GLOBAL_SCALE = BuiltinConfig("Pie deadzone global scale", 1.0) + PIE_ANIMATION_TIME = BuiltinConfig("Pie animation time", 0.2) + + TAG_RED = BuiltinConfig("Tag (red)", "★ My Favorites") + TAG_GREEN = BuiltinConfig("Tag (green)", "RGBA") + TAG_BLUE = BuiltinConfig("Tag (blue)", "Erasers") + + @classmethod + def reset_defaults(cls) -> None: """Reset all config files.""" - for field, default in _defaults.items(): - field.write(default) + for config_field in cls.__dict__.values(): + if isinstance(config_field, ConfigBase): + config_field.reset_default() - @staticmethod - def get_sleep_time() -> int: + @classmethod + def get_sleep_time(cls) -> int: """Read sleep time from FPS_LIMIT config field.""" - fps_limit = Config.FPS_LIMIT.read() + fps_limit = cls.FPS_LIMIT.read() return round(1000/fps_limit) if fps_limit else 1 - - -_defaults = { - Config.SHORT_VS_LONG_PRESS_TIME: 0.3, - Config.TRACKER_SENSITIVITY_SCALE: 1.0, - Config.TRACKER_DEADZONE: 0, - Config.FPS_LIMIT: 60, - Config.PIE_GLOBAL_SCALE: 1.0, - Config.PIE_ICON_GLOBAL_SCALE: 1.0, - Config.PIE_DEADZONE_GLOBAL_SCALE: 1.0, - Config.PIE_ANIMATION_TIME: 0.2, - - Config.TAG_RED: "★ My Favorites", - Config.TAG_GREEN: "RGBA", - Config.TAG_BLUE: "Erasers", - - Config.TAG_RED_VALUES: "", - Config.TAG_GREEN_VALUES: "", - Config.TAG_BLUE_VALUES: "", - - Config.SELECTION_TOOLS_VALUES: [ - Tool.FREEHAND_SELECTION, - Tool.RECTANGULAR_SELECTION, - Tool.CONTIGUOUS_SELECTION, - ], - Config.MISC_TOOLS_VALUES: [ - Tool.CROP, - Tool.REFERENCE, - Tool.GRADIENT, - Tool.MULTI_BRUSH, - Tool.ASSISTANTS, - ], - Config.BLENDING_MODES_VALUES: [ - BlendingMode.NORMAL, - BlendingMode.OVERLAY, - BlendingMode.COLOR, - BlendingMode.MULTIPLY, - BlendingMode.ADD, - BlendingMode.SCREEN, - BlendingMode.DARKEN, - BlendingMode.LIGHTEN, - ], - Config.CREATE_BLENDING_LAYER_VALUES: [ - BlendingMode.NORMAL, - BlendingMode.ERASE, - BlendingMode.OVERLAY, - BlendingMode.COLOR, - BlendingMode.MULTIPLY, - BlendingMode.ADD, - BlendingMode.SCREEN, - BlendingMode.DARKEN, - BlendingMode.LIGHTEN, - ], - Config.TRANSFORM_MODES_VALUES: [ - TransformMode.FREE, - TransformMode.PERSPECTIVE, - TransformMode.WARP, - TransformMode.CAGE, - TransformMode.LIQUIFY, - TransformMode.MESH, - ], -} -"""Maps default values to config fields.""" diff --git a/shortcut_composer/composer_utils/internal_config.py b/shortcut_composer/composer_utils/internal_config.py new file mode 100644 index 00000000..e77148ea --- /dev/null +++ b/shortcut_composer/composer_utils/internal_config.py @@ -0,0 +1,104 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import TypeVar, Generic, List +from enum import Enum + +from api_krita import Krita + +T = TypeVar('T') + + +class ActionConfig: + def __init__(self) -> None: + self.some_str = BuiltinConfig("some_str", "bolek") + self.some_enum_list = EnumListConfig("some_enum_list", []) + self.some_string_list = BuiltinListConfig( + "some_string_list", ["a", "b"]) + + +class ConfigBase(Generic[T]): + def __init__(self, name: str, default: T) -> None: + self.name = name + self.default = default + + def _read_raw(self) -> str: + return Krita.read_setting( + group="ShortcutComposer", + name=self.name, + default="",) + + def reset_default(self) -> None: + self.write(self.default) + + def read(self) -> T: ... + def write(self, value: T): ... + + +class BuiltinConfig(ConfigBase, Generic[T]): + "For str, int and float" + + def __init__(self, name: str, default: T) -> None: + super().__init__(name, default) + + def read(self) -> T: + raw = self._read_raw() + + if raw == "": + return self.default + + return type(self.default)(raw) + + def write(self, value: T) -> None: + """Write given value to krita config file.""" + Krita.write_setting( + group="ShortcutComposer", + name=self.name, + value=value + ) + + +class EnumListConfig(ConfigBase): + def read(self) -> List[Enum]: + raw = self._read_raw() + + if raw == "": + return self.default + + element = self.default[0] + values_list = raw.split("\t") + + enum_type = type(element) + return [enum_type[value] for value in values_list] + + def write(self, value: List[Enum]) -> None: + """Write given value to krita config file.""" + to_write = "\t".join([enum.name for enum in value]) + + Krita.write_setting( + group="ShortcutComposer", + name=self.name, + value=to_write + ) + + +class BuiltinListConfig(ConfigBase): + def __init__(self, name: str, default: List[T]) -> None: + super().__init__(name, default) + + def read(self) -> List[T]: + raw = self._read_raw() + + if raw == "": + return self.default + + parse_type = type(self.default) + return [parse_type(item) for item in raw.split("\t")] + + def write(self, value: List[T]) -> None: + """Write given value to krita config file.""" + Krita.write_setting( + group="ShortcutComposer", + name=self.name, + value="\t".join(map(str, value)) + ) diff --git a/shortcut_composer/data_components/__init__.py b/shortcut_composer/data_components/__init__.py index 2914129c..83ebfaef 100644 --- a/shortcut_composer/data_components/__init__.py +++ b/shortcut_composer/data_components/__init__.py @@ -10,15 +10,12 @@ """ from .current_layer_stack import CurrentLayerStack -from .writable_values import TagConfigValues, EnumConfigValues from .pick_strategy import PickStrategy from .slider import Slider from .range import Range __all__ = [ "CurrentLayerStack", - "EnumConfigValues", - "TagConfigValues", "PickStrategy", "Slider", "Range", diff --git a/shortcut_composer/data_components/writable_values.py b/shortcut_composer/data_components/writable_values.py deleted file mode 100644 index 6a1ecf6a..00000000 --- a/shortcut_composer/data_components/writable_values.py +++ /dev/null @@ -1,62 +0,0 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus -# SPDX-License-Identifier: GPL-3.0-or-later - -from typing import List - -from api_krita.wrappers import Database -from composer_utils import Config -from typing import TypeVar -from enum import Enum - -T = TypeVar('T', bound=Enum) - - -class TagConfigValues(list): - """ - List-based container with preset names fetched from database. - - Created using tag's name stored in passed configuration. - Uses custom tag order stored in `tag_values` config. All the tag - values not present in the order will be added to the end. - - Values which are no longer in the tag, will not be included. - - Does not update in runtime as the tag gets edited. - - ### Example usage: - - Fetch all brush presets from tag named stored in TAG_BLUE: - ```python - TagConfigValues(Config.TAG_BLUE, Config.TAG_BLUE_VALUES) - ``` - """ - - def __init__(self, tag: Config, tag_values: Config) -> None: - self.tag = tag - self.config_to_write = tag_values - - with Database() as database: - tag_presets = database.get_preset_names_from_tag(tag.read()) - - preset_order: List[str] = tag_values.read() - preset_order = [p for p in preset_order if p in tag_presets] - - missing = [p for p in tag_presets if p not in preset_order] - self.extend(preset_order + missing) - - -class EnumConfigValues(list): - """ - List-based container with enums fetched from configuration. - - ### Example usage: - - Fetch all enums stored in `TRANSFORM_MODES_VALUES` as enum `Tool`: - ```python - EnumConfigValues(Config.TRANSFORM_MODES_VALUES, Tool) - ``` - """ - - def __init__(self, values: Config) -> None: - self.config_to_write = values - self.extend(values.read()) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 874aafb1..216c7b71 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -6,12 +6,12 @@ from PyQt5.QtGui import QColor, QPixmap, QIcon from api_krita.pyqt import Text -from composer_utils import Config from core_components import Controller, Instruction from input_adapter import ComplexAction from .pie_menu_utils import ( PieManager, PieWidget, + PieConfig, PieStyle, Label, ) @@ -91,7 +91,8 @@ def __init__( instructions=instructions) self._controller = controller - self._labels = self._create_labels(values) + self._config = PieConfig(name, values) + self._labels = self._create_labels(self._config.values.read()) self._style = PieStyle( pie_radius_scale=pie_radius_scale, icon_radius_scale=icon_radius_scale, @@ -100,8 +101,7 @@ def __init__( active_color=active_color, ) - related_config = self._get_config_to_write_back(values) - self._pie_widget = PieWidget(self._style, self._labels, related_config) + self._pie_widget = PieWidget(self._style, self._labels, self._config) self._pie_manager = PieManager(self._pie_widget) def on_key_press(self) -> None: @@ -134,10 +134,3 @@ def _get_icon_if_possible(self, value: T) \ return self._controller.get_label(value) except KeyError: return None - - def _get_config_to_write_back(self, values: List[T]) -> Optional[Config]: - """Some value lists can contain metadata with config to write back.""" - try: - return values.config_to_write # type: ignore - except AttributeError: - return None diff --git a/shortcut_composer/templates/pie_menu_utils/__init__.py b/shortcut_composer/templates/pie_menu_utils/__init__.py index 1e9c5a27..dce9ad26 100644 --- a/shortcut_composer/templates/pie_menu_utils/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/__init__.py @@ -5,7 +5,7 @@ from .label_widget import LabelWidget from .pie_manager import PieManager -from .pie_widget import PieWidget +from .pie_widget import PieWidget, PieConfig from .pie_style import PieStyle from .label import Label @@ -13,6 +13,7 @@ "LabelWidget", "PieManager", "PieWidget", + "PieConfig", "PieStyle", "Label", ] diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index 41ad3db2..e772803d 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -1,12 +1,13 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import List, Optional +from typing import List, TypeVar +from enum import Enum from PyQt5.QtCore import Qt from PyQt5.QtGui import QPaintEvent, QDragMoveEvent, QDragEnterEvent from api_krita.pyqt import Painter, AnimatedWidget, BaseWidget -from composer_utils import Config +from composer_utils import Config, EnumListConfig, BuiltinListConfig from .pie_style import PieStyle from .label import Label from .label_widget import LabelWidget @@ -20,6 +21,24 @@ from .label_widget_utils import create_label_widget +T = TypeVar('T') + + +class PieConfig: + def __init__(self, name: str, values: list) -> None: + self.name = name + self.values = self._create_values(f"{values} values") + + def _create_values(self, values: list): + if not values: + return BuiltinListConfig(self.name, [None]) + + if isinstance(values[0], Enum): + return EnumListConfig(self.name, values) + + return BuiltinListConfig(self.name, values) + + class PieWidget(AnimatedWidget, BaseWidget): """ PyQt5 widget with icons on ring that can be selected by hovering. @@ -49,7 +68,7 @@ def __init__( self, style: PieStyle, labels: List[Label], - config_to_write_back: Optional[Config] = None, + config_to_write_back: PieConfig, parent=None ): AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py index 566f4be3..254ab357 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py @@ -36,5 +36,4 @@ def _write_settings(self, obj: 'PieWidget') -> None: return values = [widget.label.value for widget in obj.widget_holder] - print("writing") - obj.config_to_write_back.write(values) + obj.config_to_write_back.values.write(values) From 8c0d69c02c8a9ff3b7591b79bb7461b14dd5bafb Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 005/125] Bring back reading default tags --- shortcut_composer/actions.py | 66 +++++++++---------- .../composer_utils/internal_config.py | 17 +---- shortcut_composer/data_components/__init__.py | 2 + shortcut_composer/data_components/tag.py | 11 ++++ .../templates/pie_menu_utils/pie_widget.py | 11 ++-- 5 files changed, 53 insertions(+), 54 deletions(-) create mode 100644 shortcut_composer/data_components/tag.py diff --git a/shortcut_composer/actions.py b/shortcut_composer/actions.py index 8c9590b8..e4b610be 100644 --- a/shortcut_composer/actions.py +++ b/shortcut_composer/actions.py @@ -12,19 +12,17 @@ import templates from typing import List -# from PyQt5.QtGui import QColor +from PyQt5.QtGui import QColor from api_krita.enums import Tool, Toggle, BlendingMode, TransformMode from core_components import instructions, controllers -# from composer_utils import Config from input_adapter import ComplexAction from data_components import ( CurrentLayerStack, - # EnumConfigValues, - # TagConfigValues, PickStrategy, Slider, Range, + Tag, ) infinity = float("inf") @@ -246,38 +244,38 @@ def create_actions() -> List[ComplexAction]: return [ ] ), - # # Use pie menu to pick one of presets from tag specified in settings. - # # Set tool to FREEHAND BRUSH if current tool does not allow to paint - # templates.PieMenu( - # name="Pick brush presets (red)", - # controller=controllers.PresetController(), - # instructions=[instructions.SetBrushOnNonPaintable()], - # values=TagConfigValues(Config.TAG_RED, Config.TAG_RED_VALUES), - # background_color=QColor(95, 65, 65, 190), - # active_color=QColor(200, 70, 70), - # ), + # Use pie menu to pick one of presets from tag specified in settings. + # Set tool to FREEHAND BRUSH if current tool does not allow to paint + templates.PieMenu( + name="Pick brush presets (red)", + controller=controllers.PresetController(), + instructions=[instructions.SetBrushOnNonPaintable()], + values=Tag("★ My Favorites"), + background_color=QColor(95, 65, 65, 190), + active_color=QColor(200, 70, 70), + ), - # # Use pie menu to pick one of presets from tag specified in settings. - # # Set tool to FREEHAND BRUSH if current tool does not allow to paint - # templates.PieMenu( - # name="Pick brush presets (green)", - # controller=controllers.PresetController(), - # instructions=[instructions.SetBrushOnNonPaintable()], - # values=TagConfigValues(Config.TAG_GREEN, Config.TAG_GREEN_VALUES), - # background_color=QColor(65, 95, 65, 190), - # active_color=QColor(70, 200, 70), - # ), + # Use pie menu to pick one of presets from tag specified in settings. + # Set tool to FREEHAND BRUSH if current tool does not allow to paint + templates.PieMenu( + name="Pick brush presets (green)", + controller=controllers.PresetController(), + instructions=[instructions.SetBrushOnNonPaintable()], + values=Tag("RGBA"), + background_color=QColor(65, 95, 65, 190), + active_color=QColor(70, 200, 70), + ), - # # Use pie menu to pick one of presets from tag specified in settings. - # # Set tool to FREEHAND BRUSH if current tool does not allow to paint - # templates.PieMenu( - # name="Pick brush presets (blue)", - # controller=controllers.PresetController(), - # instructions=[instructions.SetBrushOnNonPaintable()], - # values=TagConfigValues(Config.TAG_BLUE, Config.TAG_BLUE_VALUES), - # background_color=QColor(70, 70, 105, 190), - # active_color=QColor(110, 160, 235), - # ), + # Use pie menu to pick one of presets from tag specified in settings. + # Set tool to FREEHAND BRUSH if current tool does not allow to paint + templates.PieMenu( + name="Pick brush presets (blue)", + controller=controllers.PresetController(), + instructions=[instructions.SetBrushOnNonPaintable()], + values=Tag("Erasers"), + background_color=QColor(70, 70, 105, 190), + active_color=QColor(110, 160, 235), + ), # ....................................... # Insert your actions implementation here diff --git a/shortcut_composer/composer_utils/internal_config.py b/shortcut_composer/composer_utils/internal_config.py index e77148ea..fa6ab9aa 100644 --- a/shortcut_composer/composer_utils/internal_config.py +++ b/shortcut_composer/composer_utils/internal_config.py @@ -9,14 +9,6 @@ T = TypeVar('T') -class ActionConfig: - def __init__(self) -> None: - self.some_str = BuiltinConfig("some_str", "bolek") - self.some_enum_list = EnumListConfig("some_enum_list", []) - self.some_string_list = BuiltinListConfig( - "some_string_list", ["a", "b"]) - - class ConfigBase(Generic[T]): def __init__(self, name: str, default: T) -> None: self.name = name @@ -54,8 +46,7 @@ def write(self, value: T) -> None: Krita.write_setting( group="ShortcutComposer", name=self.name, - value=value - ) + value=value) class EnumListConfig(ConfigBase): @@ -78,8 +69,7 @@ def write(self, value: List[Enum]) -> None: Krita.write_setting( group="ShortcutComposer", name=self.name, - value=to_write - ) + value=to_write) class BuiltinListConfig(ConfigBase): @@ -100,5 +90,4 @@ def write(self, value: List[T]) -> None: Krita.write_setting( group="ShortcutComposer", name=self.name, - value="\t".join(map(str, value)) - ) + value="\t".join(map(str, value))) diff --git a/shortcut_composer/data_components/__init__.py b/shortcut_composer/data_components/__init__.py index 83ebfaef..08f211f4 100644 --- a/shortcut_composer/data_components/__init__.py +++ b/shortcut_composer/data_components/__init__.py @@ -13,10 +13,12 @@ from .pick_strategy import PickStrategy from .slider import Slider from .range import Range +from .tag import Tag __all__ = [ "CurrentLayerStack", "PickStrategy", "Slider", "Range", + "Tag", ] diff --git a/shortcut_composer/data_components/tag.py b/shortcut_composer/data_components/tag.py new file mode 100644 index 00000000..b3cd050a --- /dev/null +++ b/shortcut_composer/data_components/tag.py @@ -0,0 +1,11 @@ +from api_krita.wrappers import Database + + +class Tag(list): + def __init__(self, default_tag_name: str): + self.tag_name = default_tag_name + self.extend(self._read_brushes()) + + def _read_brushes(self): + with Database() as database: + return database.get_preset_names_from_tag(self.tag_name) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index e772803d..9c0637bb 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -27,16 +27,15 @@ class PieConfig: def __init__(self, name: str, values: list) -> None: self.name = name - self.values = self._create_values(f"{values} values") + self.values = self._create_values(values) def _create_values(self, values: list): + values_name = f"{values} values" if not values: - return BuiltinListConfig(self.name, [None]) - + return BuiltinListConfig(values_name, [None]) if isinstance(values[0], Enum): - return EnumListConfig(self.name, values) - - return BuiltinListConfig(self.name, values) + return EnumListConfig(values_name, values) + return BuiltinListConfig(values_name, values) class PieWidget(AnimatedWidget, BaseWidget): From 111116754efc04cb0cd2d055a3998104ac4b1927 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 006/125] Tag and non-tag pies read their internal configs --- shortcut_composer/composer_utils/internal_config.py | 4 ++-- shortcut_composer/data_components/tag.py | 4 ++-- .../templates/pie_menu_utils/pie_widget.py | 10 ++++++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/shortcut_composer/composer_utils/internal_config.py b/shortcut_composer/composer_utils/internal_config.py index fa6ab9aa..1978fc2a 100644 --- a/shortcut_composer/composer_utils/internal_config.py +++ b/shortcut_composer/composer_utils/internal_config.py @@ -72,7 +72,7 @@ def write(self, value: List[Enum]) -> None: value=to_write) -class BuiltinListConfig(ConfigBase): +class BuiltinListConfig(ConfigBase, Generic[T]): def __init__(self, name: str, default: List[T]) -> None: super().__init__(name, default) @@ -82,7 +82,7 @@ def read(self) -> List[T]: if raw == "": return self.default - parse_type = type(self.default) + parse_type = type(self.default[0]) return [parse_type(item) for item in raw.split("\t")] def write(self, value: List[T]) -> None: diff --git a/shortcut_composer/data_components/tag.py b/shortcut_composer/data_components/tag.py index b3cd050a..3c2a603a 100644 --- a/shortcut_composer/data_components/tag.py +++ b/shortcut_composer/data_components/tag.py @@ -2,8 +2,8 @@ class Tag(list): - def __init__(self, default_tag_name: str): - self.tag_name = default_tag_name + def __init__(self, tag_name: str): + self.tag_name = tag_name self.extend(self._read_brushes()) def _read_brushes(self): diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index 9c0637bb..32e69871 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -7,7 +7,8 @@ from PyQt5.QtCore import Qt from PyQt5.QtGui import QPaintEvent, QDragMoveEvent, QDragEnterEvent from api_krita.pyqt import Painter, AnimatedWidget, BaseWidget -from composer_utils import Config, EnumListConfig, BuiltinListConfig +from composer_utils import Config, EnumListConfig, BuiltinListConfig, BuiltinConfig +from data_components import Tag from .pie_style import PieStyle from .label import Label from .label_widget import LabelWidget @@ -27,10 +28,15 @@ class PieConfig: def __init__(self, name: str, values: list) -> None: self.name = name + if isinstance(values, Tag): + self.tag_name = BuiltinConfig(name=name, default=values.tag_name) + values = Tag(self.tag_name.read()) + else: + self.tag_name = BuiltinConfig(name=name, default="") self.values = self._create_values(values) def _create_values(self, values: list): - values_name = f"{values} values" + values_name = f"{self.name} values" if not values: return BuiltinListConfig(values_name, [None]) if isinstance(values[0], Enum): From 89b7e1bf170fa4f8ad2e43172a159aca8c5a5f8c Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 007/125] Separate configs for tag and enum pies --- shortcut_composer/templates/pie_menu.py | 9 ++-- .../templates/pie_menu_utils/__init__.py | 5 +- .../templates/pie_menu_utils/pie_config.py | 52 +++++++++++++++++++ .../templates/pie_menu_utils/pie_widget.py | 33 +++--------- .../pie_menu_utils/widget_utils/edit_mode.py | 4 +- 5 files changed, 67 insertions(+), 36 deletions(-) create mode 100644 shortcut_composer/templates/pie_menu_utils/pie_config.py diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 216c7b71..84cb0a65 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -9,9 +9,9 @@ from core_components import Controller, Instruction from input_adapter import ComplexAction from .pie_menu_utils import ( + create_pie_config, PieManager, PieWidget, - PieConfig, PieStyle, Label, ) @@ -91,15 +91,14 @@ def __init__( instructions=instructions) self._controller = controller - self._config = PieConfig(name, values) - self._labels = self._create_labels(self._config.values.read()) + self._config = create_pie_config(name, values) + self._labels = self._create_labels(self._config.values) self._style = PieStyle( pie_radius_scale=pie_radius_scale, icon_radius_scale=icon_radius_scale, icons_amount=len(self._labels), background_color=background_color, - active_color=active_color, - ) + active_color=active_color) self._pie_widget = PieWidget(self._style, self._labels, self._config) self._pie_manager = PieManager(self._pie_widget) diff --git a/shortcut_composer/templates/pie_menu_utils/__init__.py b/shortcut_composer/templates/pie_menu_utils/__init__.py index dce9ad26..0870c3e6 100644 --- a/shortcut_composer/templates/pie_menu_utils/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/__init__.py @@ -3,17 +3,18 @@ """Implementation of PieMenu main elements.""" +from .pie_config import create_pie_config from .label_widget import LabelWidget from .pie_manager import PieManager -from .pie_widget import PieWidget, PieConfig +from .pie_widget import PieWidget from .pie_style import PieStyle from .label import Label __all__ = [ + "create_pie_config", "LabelWidget", "PieManager", "PieWidget", - "PieConfig", "PieStyle", "Label", ] diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py new file mode 100644 index 00000000..2fa693c0 --- /dev/null +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import TypeVar + +from composer_utils import ( + EnumListConfig, + BuiltinListConfig, + BuiltinConfig, + ConfigBase) +from data_components import Tag + + +T = TypeVar('T') + + +def create_pie_config(name: str, values: list) -> 'PieConfig': + if isinstance(values, Tag): + return PresetPieConfig(name, values) + return EnumPieConfig(name, values) + + +class PieConfig: + name: str + order: ConfigBase + values: list + + +class PresetPieConfig(PieConfig): + def __init__(self, name: str, tag: Tag) -> None: + self.name = name + self.tag_name = BuiltinConfig(name=name, default=tag.tag_name) + self.order = BuiltinListConfig(f"{self.name} values", [""]) + + @property + def values(self): + saved_order = self.order.read() + tag_values = Tag(self.tag_name.read()) + + preset_order = [p for p in saved_order if p in tag_values] + missing = [p for p in tag_values if p not in saved_order] + return preset_order + missing + + +class EnumPieConfig(PieConfig): + def __init__(self, name: str, values: list) -> None: + self.name = name + self.order = EnumListConfig(f"{self.name} values", values) + + @property + def values(self): + return self.order.read() diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index 32e69871..854e2c41 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -2,48 +2,27 @@ # SPDX-License-Identifier: GPL-3.0-or-later from typing import List, TypeVar -from enum import Enum from PyQt5.QtCore import Qt from PyQt5.QtGui import QPaintEvent, QDragMoveEvent, QDragEnterEvent from api_krita.pyqt import Painter, AnimatedWidget, BaseWidget -from composer_utils import Config, EnumListConfig, BuiltinListConfig, BuiltinConfig -from data_components import Tag +from composer_utils import Config from .pie_style import PieStyle from .label import Label from .label_widget import LabelWidget +from .label_widget_utils import create_label_widget +from .pie_config import PieConfig from .widget_utils import ( WidgetHolder, CirclePoints, AcceptButton, PiePainter, - EditMode, -) -from .label_widget_utils import create_label_widget + EditMode) T = TypeVar('T') -class PieConfig: - def __init__(self, name: str, values: list) -> None: - self.name = name - if isinstance(values, Tag): - self.tag_name = BuiltinConfig(name=name, default=values.tag_name) - values = Tag(self.tag_name.read()) - else: - self.tag_name = BuiltinConfig(name=name, default="") - self.values = self._create_values(values) - - def _create_values(self, values: list): - values_name = f"{self.name} values" - if not values: - return BuiltinListConfig(values_name, [None]) - if isinstance(values[0], Enum): - return EnumListConfig(values_name, values) - return BuiltinListConfig(values_name, values) - - class PieWidget(AnimatedWidget, BaseWidget): """ PyQt5 widget with icons on ring that can be selected by hovering. @@ -73,14 +52,14 @@ def __init__( self, style: PieStyle, labels: List[Label], - config_to_write_back: PieConfig, + config: PieConfig, parent=None ): AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) self.setGeometry(0, 0, style.widget_radius*2, style.widget_radius*2) self._style = style - self.config_to_write_back = config_to_write_back + self.config = config self._circle_points = CirclePoints( center=self.center, radius=self._style.pie_radius) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py index 254ab357..f8598119 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py @@ -32,8 +32,8 @@ def __set__(self, obj: 'PieWidget', mode_to_set: bool) -> None: def _write_settings(self, obj: 'PieWidget') -> None: """If values were not hardcoded, but from config, write them back.""" - if not obj.labels or obj.config_to_write_back is None: + if not obj.labels or obj.config is None: return values = [widget.label.value for widget in obj.widget_holder] - obj.config_to_write_back.values.write(values) + obj.config.order.write(values) From 32a5c87caedfee44fe7fac16bae0a18685c20462 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 008/125] Temporarily disable settings --- shortcut_composer/shortcut_composer.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/shortcut_composer/shortcut_composer.py b/shortcut_composer/shortcut_composer.py index 8ec3be88..b40df459 100755 --- a/shortcut_composer/shortcut_composer.py +++ b/shortcut_composer/shortcut_composer.py @@ -3,7 +3,7 @@ from typing import List from dataclasses import dataclass -from PyQt5.QtWidgets import QWidgetAction +from PyQt5.QtWidgets import QWidgetAction, QWidget from api_krita import Krita, Extension # type: ignore from api_krita.actions import TransformModeActions @@ -53,8 +53,10 @@ def createActions(self, window) -> None: """Create window components. Called by krita for each new window.""" self._protectors.append(GarbageProtector( transform_modes=TransformModeActions(window), - settings_dialog=(settings := SettingsDialog()), - settings_action=self._create_settings_action(window, settings), + # settings_dialog=(settings := SettingsDialog()), + # settings_action=self._create_settings_action(window, settings), + settings_dialog=QWidget(), + settings_action=QWidget(), action_manager=ActionManager(window), reload_action=self._create_reload_action(window))) From 726b84d080b9771dbc1d1a180b5ee4ce5a0ccfea Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 009/125] Pie scale red from internal config --- shortcut_composer/templates/pie_menu.py | 10 ++-- .../templates/pie_menu_utils/pie_config.py | 51 +++++++++++++------ 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 84cb0a65..5046a352 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -91,11 +91,15 @@ def __init__( instructions=instructions) self._controller = controller - self._config = create_pie_config(name, values) + self._config = create_pie_config( + name, + values, + pie_radius_scale, + icon_radius_scale) self._labels = self._create_labels(self._config.values) self._style = PieStyle( - pie_radius_scale=pie_radius_scale, - icon_radius_scale=icon_radius_scale, + pie_radius_scale=self._config.pie_radius_scale.read(), + icon_radius_scale=self._config.icon_radius_scale.read(), icons_amount=len(self._labels), background_color=background_color, active_color=active_color) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 2fa693c0..e1026fd6 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -1,8 +1,6 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import TypeVar - from composer_utils import ( EnumListConfig, BuiltinListConfig, @@ -11,25 +9,44 @@ from data_components import Tag -T = TypeVar('T') - - -def create_pie_config(name: str, values: list) -> 'PieConfig': +def create_pie_config( + name: str, + values: list, + pie_radius_scale: float, + icon_radius_scale: float, +) -> 'PieConfig': + args = [name, values, pie_radius_scale, icon_radius_scale] if isinstance(values, Tag): - return PresetPieConfig(name, values) - return EnumPieConfig(name, values) + return PresetPieConfig(*args) + return EnumPieConfig(*args) class PieConfig: - name: str - order: ConfigBase values: list + order: ConfigBase + + def __init__( + self, + name: str, + values: list, + pie_radius_scale: float, + icon_radius_scale: float, + ) -> None: + self.name = name + self._default_values = values + self.pie_radius_scale = BuiltinConfig( + f"{self.name} pie scale", + pie_radius_scale) + self.icon_radius_scale = BuiltinConfig( + f"{self.name} icon scale", + icon_radius_scale) class PresetPieConfig(PieConfig): - def __init__(self, name: str, tag: Tag) -> None: - self.name = name - self.tag_name = BuiltinConfig(name=name, default=tag.tag_name) + def __init__(self, *args): + super().__init__(*args) + self._default_values: Tag + self.tag_name = BuiltinConfig(self.name, self._default_values.tag_name) self.order = BuiltinListConfig(f"{self.name} values", [""]) @property @@ -43,9 +60,11 @@ def values(self): class EnumPieConfig(PieConfig): - def __init__(self, name: str, values: list) -> None: - self.name = name - self.order = EnumListConfig(f"{self.name} values", values) + def __init__(self, *args): + super().__init__(*args) + self.order = EnumListConfig( + f"{self.name} values", + self._default_values) @property def values(self): From a9381cfd357afb003a697bc3d466ad43bf99e7f8 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 010/125] Test app for scroll area --- scroll_area.py | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 scroll_area.py diff --git a/scroll_area.py b/scroll_area.py new file mode 100644 index 00000000..af03b1ab --- /dev/null +++ b/scroll_area.py @@ -0,0 +1,97 @@ +from typing import List +from PyQt5.QtCore import QSize, Qt +from PyQt5.QtWidgets import ( + QApplication, + QWidget, + QScrollArea, + QLabel, + QGridLayout, + QHBoxLayout, +) +import sys + + +class CustomLayout(QGridLayout): + def __init__(self, cols: int): + super().__init__() + self.widgets: List[QWidget] = [] + self._max_rows = cols + self._uniques = 2*cols - 1 + + def __len__(self): + return len(self.widgets) + + def _get_position(self, index: int): + row, col = divmod(index, self._uniques) + if col < self._max_rows: + return (row*4, col*2) + return (row*4+2, (col-self._max_rows)*2+1) + + def _new_position(self): + return self._get_position(len(self.widgets)) + + def append(self, widget: QWidget): + self.widgets.append(widget) + self.addWidget(widget, *self._new_position(), 2, 2) + + def pop(self, index: int): + self.widgets.pop(index) + self._refresh() + + def insert(self, index: int, widget: QWidget): + self.widgets.insert(index, widget) + self._refresh() + + def _refresh(self): + for i, widget in enumerate(self.widgets): + self.addWidget(widget, *self._get_position(i), 2, 2) + + +class Window(QWidget): + def __init__(self, cols: int): + super().__init__() + self.setGeometry(0, 0, 400, 300) + self._layout = CustomLayout(cols) + self.icon_size = 64 + + scroll_internal = QWidget() + for i in range(25): + self.add_label(str(i)) + + blue_label = QLabel("asd") + blue_label.setFixedSize(QSize(self.icon_size, self.icon_size)) + blue_label.setStyleSheet("background-color : blue") + self._layout.insert(10, blue_label) + self._layout.pop(0) + self._layout.pop(0) + self._layout.pop(0) + + for i in range(5): + self.add_label(str(i)) + + scroll_internal.setLayout(self._layout) + + scroll_area = QScrollArea() + scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll_area.setWidget(scroll_internal) + scroll_area.setFixedWidth(scroll_internal.width() + self.icon_size//2) + scroll_area.setWidgetResizable(True) + + main_layout = QHBoxLayout(self) + main_layout.addStretch() + main_layout.addWidget(scroll_area) + main_layout.addStretch() + self.setLayout(main_layout) + + self.show() + + def add_label(self, name: str): + label = QLabel(str(name)) + label.setFixedSize(QSize(self.icon_size, self.icon_size)) + label.setStyleSheet("background-color : green") + self._layout.append(label) + + +App = QApplication(sys.argv) +window = Window(5) +sys.exit(App.exec()) From 51dbb82bf4b610182b4107443219b83909a700c6 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 011/125] Fix test application --- scroll_area.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scroll_area.py b/scroll_area.py index af03b1ab..265dea67 100644 --- a/scroll_area.py +++ b/scroll_area.py @@ -33,6 +33,7 @@ def _new_position(self): def append(self, widget: QWidget): self.widgets.append(widget) self.addWidget(widget, *self._new_position(), 2, 2) + self._refresh() def pop(self, index: int): self.widgets.pop(index) @@ -55,13 +56,14 @@ def __init__(self, cols: int): self.icon_size = 64 scroll_internal = QWidget() - for i in range(25): + for i in range(22): self.add_label(str(i)) blue_label = QLabel("asd") blue_label.setFixedSize(QSize(self.icon_size, self.icon_size)) blue_label.setStyleSheet("background-color : blue") self._layout.insert(10, blue_label) + self._layout.pop(0) self._layout.pop(0) self._layout.pop(0) From 0fb6eeff61289a4fa7bb1eb8860fe37eb1984f5e Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 012/125] Pie and Settings as two separate popups --- shortcut_composer/templates/pie_menu.py | 4 ++- .../templates/pie_menu_utils/__init__.py | 2 ++ .../templates/pie_menu_utils/pie_manager.py | 26 ++++++++++++------- .../templates/pie_menu_utils/scroll_area.py | 25 +++++++++--------- 4 files changed, 34 insertions(+), 23 deletions(-) rename scroll_area.py => shortcut_composer/templates/pie_menu_utils/scroll_area.py (85%) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 5046a352..a8cc1de7 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -10,6 +10,7 @@ from input_adapter import ComplexAction from .pie_menu_utils import ( create_pie_config, + ScrollArea, PieManager, PieWidget, PieStyle, @@ -105,7 +106,8 @@ def __init__( active_color=active_color) self._pie_widget = PieWidget(self._style, self._labels, self._config) - self._pie_manager = PieManager(self._pie_widget) + self._pie_settings = ScrollArea(4) + self._pie_manager = PieManager(self._pie_widget, self._pie_settings) def on_key_press(self) -> None: """Show widget under mouse and start manager which repaints it.""" diff --git a/shortcut_composer/templates/pie_menu_utils/__init__.py b/shortcut_composer/templates/pie_menu_utils/__init__.py index 0870c3e6..9f1b4b40 100644 --- a/shortcut_composer/templates/pie_menu_utils/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/__init__.py @@ -5,6 +5,7 @@ from .pie_config import create_pie_config from .label_widget import LabelWidget +from .scroll_area import ScrollArea from .pie_manager import PieManager from .pie_widget import PieWidget from .pie_style import PieStyle @@ -13,6 +14,7 @@ __all__ = [ "create_pie_config", "LabelWidget", + "ScrollArea", "PieManager", "PieWidget", "PieStyle", diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index 5e22dc2b..e0e434d9 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -4,9 +4,11 @@ from typing import Optional from PyQt5.QtGui import QCursor +from PyQt5.QtCore import QPoint from api_krita.pyqt import Timer from composer_utils import Config +from .scroll_area import ScrollArea from .pie_widget import PieWidget from .label_widget import LabelWidget from .widget_utils import CirclePoints @@ -21,9 +23,10 @@ class PieManager: - Asks the widget to repaint when after changing active label. """ - def __init__(self, widget: PieWidget) -> None: - self._widget = widget - self._holder = self._widget.widget_holder + def __init__(self, widget: PieWidget, pie_settings: ScrollArea) -> None: + self._pie = widget + self._pie_settings = pie_settings + self._holder = self._pie.widget_holder self._timer = Timer(self._handle_cursor, Config.get_sleep_time()) self._animator = LabelAnimator(widget) @@ -31,28 +34,31 @@ def __init__(self, widget: PieWidget) -> None: def start(self) -> None: """Show widget under the mouse and start the mouse tracking loop.""" - self._widget.move_center(QCursor().pos()) - self._widget.show() - self._circle = CirclePoints(self._widget.center_global, 0) + self._pie.move_center(QCursor().pos()) + self._pie_settings.move_center(QCursor().pos()+ QPoint(self._pie_settings.width(), 0)) + self._pie.show() + self._pie_settings.show() + self._circle = CirclePoints(self._pie.center_global, 0) self._timer.start() def stop(self) -> None: """Hide the widget and stop the mouse tracking loop.""" self._timer.stop() - for label in self._widget.labels: + for label in self._pie.labels: label.activation_progress.reset() - self._widget.hide() + self._pie.hide() + self._pie_settings.hide() def _handle_cursor(self) -> None: """Calculate zone of the cursor and mark which child is active.""" # NOTE: The widget can get hidden outside of stop() when key is # released during the drag&drop operation or when user clicked # outside the pie widget. - if not self._widget.isVisible(): + if not self._pie.isVisible(): return self.stop() cursor = QCursor().pos() - if self._circle.distance(cursor) < self._widget.deadzone: + if self._circle.distance(cursor) < self._pie.deadzone: return self._set_active_widget(None) angle = self._circle.angle_from_point(cursor) diff --git a/scroll_area.py b/shortcut_composer/templates/pie_menu_utils/scroll_area.py similarity index 85% rename from scroll_area.py rename to shortcut_composer/templates/pie_menu_utils/scroll_area.py index 265dea67..8ca117c3 100644 --- a/scroll_area.py +++ b/shortcut_composer/templates/pie_menu_utils/scroll_area.py @@ -1,17 +1,16 @@ from typing import List from PyQt5.QtCore import QSize, Qt from PyQt5.QtWidgets import ( - QApplication, QWidget, QScrollArea, QLabel, QGridLayout, QHBoxLayout, ) -import sys +from api_krita.pyqt import BaseWidget -class CustomLayout(QGridLayout): +class ScrollAreaLayout(QGridLayout): def __init__(self, cols: int): super().__init__() self.widgets: List[QWidget] = [] @@ -48,11 +47,18 @@ def _refresh(self): self.addWidget(widget, *self._get_position(i), 2, 2) -class Window(QWidget): - def __init__(self, cols: int): - super().__init__() +class ScrollArea(BaseWidget): + def __init__(self, cols: int, parent=None): + super().__init__(parent) + + self.hide() + + self.setAcceptDrops(True) + self.setWindowFlags((self.windowFlags() | Qt.Popup | Qt.NoFocus)) # type: ignore + self.setCursor(Qt.CrossCursor) + self.setGeometry(0, 0, 400, 300) - self._layout = CustomLayout(cols) + self._layout = ScrollAreaLayout(cols) self.icon_size = 64 scroll_internal = QWidget() @@ -92,8 +98,3 @@ def add_label(self, name: str): label.setFixedSize(QSize(self.icon_size, self.icon_size)) label.setStyleSheet("background-color : green") self._layout.append(label) - - -App = QApplication(sys.argv) -window = Window(5) -sys.exit(App.exec()) From 0b2f2f0d62fa7389fe97e0f4d784bf7ef2611eed Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 013/125] Fix popups hiding when another is clicked --- shortcut_composer/templates/pie_menu_utils/pie_widget.py | 2 +- shortcut_composer/templates/pie_menu_utils/scroll_area.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index 854e2c41..a66fde68 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -75,7 +75,7 @@ def __init__( self.setAcceptDrops(True) self.setWindowFlags(( self.windowFlags() | # type: ignore - Qt.Popup | + Qt.Tool | Qt.FramelessWindowHint | Qt.NoDropShadowWindowHint)) self.setAttribute(Qt.WA_TranslucentBackground) diff --git a/shortcut_composer/templates/pie_menu_utils/scroll_area.py b/shortcut_composer/templates/pie_menu_utils/scroll_area.py index 8ca117c3..0dce8065 100644 --- a/shortcut_composer/templates/pie_menu_utils/scroll_area.py +++ b/shortcut_composer/templates/pie_menu_utils/scroll_area.py @@ -51,10 +51,8 @@ class ScrollArea(BaseWidget): def __init__(self, cols: int, parent=None): super().__init__(parent) - self.hide() - self.setAcceptDrops(True) - self.setWindowFlags((self.windowFlags() | Qt.Popup | Qt.NoFocus)) # type: ignore + self.setWindowFlags((self.windowFlags() | Qt.Tool)) # type: ignore self.setCursor(Qt.CrossCursor) self.setGeometry(0, 0, 400, 300) @@ -91,7 +89,7 @@ def __init__(self, cols: int, parent=None): main_layout.addStretch() self.setLayout(main_layout) - self.show() + self.hide() def add_label(self, name: str): label = QLabel(str(name)) From 1d9d5e768ab2fe7129abdda8b51b97c45de552f8 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 014/125] Show settings only in edit mode --- shortcut_composer/templates/pie_menu.py | 6 +++++- .../templates/pie_menu_utils/pie_manager.py | 5 ++--- .../templates/pie_menu_utils/pie_widget.py | 3 +++ .../templates/pie_menu_utils/scroll_area.py | 12 ++++++++---- .../pie_menu_utils/widget_utils/edit_mode.py | 2 ++ 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index a8cc1de7..0c17491a 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -105,8 +105,12 @@ def __init__( background_color=background_color, active_color=active_color) - self._pie_widget = PieWidget(self._style, self._labels, self._config) self._pie_settings = ScrollArea(4) + self._pie_widget = PieWidget( + self._style, + self._labels, + self._config, + self._pie_settings) self._pie_manager = PieManager(self._pie_widget, self._pie_settings) def on_key_press(self) -> None: diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index e0e434d9..33169e64 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -35,9 +35,9 @@ def __init__(self, widget: PieWidget, pie_settings: ScrollArea) -> None: def start(self) -> None: """Show widget under the mouse and start the mouse tracking loop.""" self._pie.move_center(QCursor().pos()) - self._pie_settings.move_center(QCursor().pos()+ QPoint(self._pie_settings.width(), 0)) + self._pie_settings.move_center( + QCursor().pos() + QPoint(self._pie_settings.width(), 0)) self._pie.show() - self._pie_settings.show() self._circle = CirclePoints(self._pie.center_global, 0) self._timer.start() @@ -47,7 +47,6 @@ def stop(self) -> None: for label in self._pie.labels: label.activation_progress.reset() self._pie.hide() - self._pie_settings.hide() def _handle_cursor(self) -> None: """Calculate zone of the cursor and mark which child is active.""" diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index a66fde68..f79ffeee 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -8,6 +8,7 @@ from api_krita.pyqt import Painter, AnimatedWidget, BaseWidget from composer_utils import Config from .pie_style import PieStyle +from .scroll_area import ScrollArea from .label import Label from .label_widget import LabelWidget from .label_widget_utils import create_label_widget @@ -53,6 +54,7 @@ def __init__( style: PieStyle, labels: List[Label], config: PieConfig, + pie_settings: ScrollArea, parent=None ): AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) @@ -60,6 +62,7 @@ def __init__( self._style = style self.config = config + self.pie_settings = pie_settings self._circle_points = CirclePoints( center=self.center, radius=self._style.pie_radius) diff --git a/shortcut_composer/templates/pie_menu_utils/scroll_area.py b/shortcut_composer/templates/pie_menu_utils/scroll_area.py index 0dce8065..de8e2e5f 100644 --- a/shortcut_composer/templates/pie_menu_utils/scroll_area.py +++ b/shortcut_composer/templates/pie_menu_utils/scroll_area.py @@ -7,7 +7,8 @@ QGridLayout, QHBoxLayout, ) -from api_krita.pyqt import BaseWidget +from api_krita.pyqt import AnimatedWidget, BaseWidget +from composer_utils import Config class ScrollAreaLayout(QGridLayout): @@ -47,12 +48,15 @@ def _refresh(self): self.addWidget(widget, *self._get_position(i), 2, 2) -class ScrollArea(BaseWidget): +class ScrollArea(AnimatedWidget, BaseWidget): def __init__(self, cols: int, parent=None): - super().__init__(parent) + AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) self.setAcceptDrops(True) - self.setWindowFlags((self.windowFlags() | Qt.Tool)) # type: ignore + self.setWindowFlags(( + self.windowFlags() | # type: ignore + Qt.Tool | + Qt.FramelessWindowHint)) self.setCursor(Qt.CrossCursor) self.setGeometry(0, 0, 400, 300) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py index f8598119..5574aefa 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py @@ -27,8 +27,10 @@ def __set__(self, obj: 'PieWidget', mode_to_set: bool) -> None: self._edit_mode = mode_to_set if mode_to_set: obj.accept_button.show() + obj.pie_settings.show() else: obj.accept_button.hide() + obj.pie_settings.hide() def _write_settings(self, obj: 'PieWidget') -> None: """If values were not hardcoded, but from config, write them back.""" From 18e29b24d6e9eb0045332e56fcc0e3f7d7f6b11d Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 015/125] Fix pie settings reanimate on drag --- .../templates/pie_menu_utils/widget_utils/edit_mode.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py index 5574aefa..7fc548cc 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py @@ -24,7 +24,9 @@ def __set__(self, obj: 'PieWidget', mode_to_set: bool) -> None: if not mode_to_set and self._edit_mode: self._write_settings(obj) - self._edit_mode = mode_to_set + if not self._edit_mode ^ mode_to_set: + return + if mode_to_set: obj.accept_button.show() obj.pie_settings.show() @@ -32,6 +34,8 @@ def __set__(self, obj: 'PieWidget', mode_to_set: bool) -> None: obj.accept_button.hide() obj.pie_settings.hide() + self._edit_mode = mode_to_set + def _write_settings(self, obj: 'PieWidget') -> None: """If values were not hardcoded, but from config, write them back.""" if not obj.labels or obj.config is None: From 47d51b96a8f5bfaf0dee6e2831d65d9a24326465 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 016/125] Add author data to main file --- shortcut_composer/shortcut_composer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shortcut_composer/shortcut_composer.py b/shortcut_composer/shortcut_composer.py index b40df459..8df0dfca 100755 --- a/shortcut_composer/shortcut_composer.py +++ b/shortcut_composer/shortcut_composer.py @@ -1,6 +1,9 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later +__version__ = "1.1.1" +__author__ = "Wojciech Trybus" + from typing import List from dataclasses import dataclass from PyQt5.QtWidgets import QWidgetAction, QWidget From 63fcf812dda52db849801a3c9d062e08d15d29be Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 017/125] Label widgets in scroll area --- shortcut_composer/templates/pie_menu.py | 56 ++++++++++------- .../templates/pie_menu_utils/pie_manager.py | 4 +- .../templates/pie_menu_utils/pie_widget.py | 4 +- .../templates/pie_menu_utils/scroll_area.py | 61 ++++++++++--------- 4 files changed, 71 insertions(+), 54 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 0c17491a..4029ea5d 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -1,11 +1,11 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import List, TypeVar, Generic, Union, Optional +from typing import List, TypeVar, Generic, Optional +from enum import Enum -from PyQt5.QtGui import QColor, QPixmap, QIcon +from PyQt5.QtGui import QColor -from api_krita.pyqt import Text from core_components import Controller, Instruction from input_adapter import ComplexAction from .pie_menu_utils import ( @@ -91,27 +91,31 @@ def __init__( short_vs_long_press_time=short_vs_long_press_time, instructions=instructions) self._controller = controller - self._config = create_pie_config( name, values, pie_radius_scale, icon_radius_scale) - self._labels = self._create_labels(self._config.values) + self._values = self._config.values self._style = PieStyle( pie_radius_scale=self._config.pie_radius_scale.read(), icon_radius_scale=self._config.icon_radius_scale.read(), - icons_amount=len(self._labels), + icons_amount=len(self._values), background_color=background_color, active_color=active_color) - self._pie_settings = ScrollArea(4) self._pie_widget = PieWidget( - self._style, - self._labels, - self._config, - self._pie_settings) - self._pie_manager = PieManager(self._pie_widget, self._pie_settings) + style=self._style, + labels=self._create_labels(self._values), + config=self._config, + pie_settings=self._pie_settings) + self._pie_settings = ScrollArea( + cols=3, + style=self._style, + unused_values=self._create_unused_labels(self._values)) + self._pie_manager = PieManager( + widget=self._pie_widget, + pie_settings=self._pie_settings) def on_key_press(self) -> None: """Show widget under mouse and start manager which repaints it.""" @@ -124,6 +128,7 @@ def on_every_key_release(self) -> None: super().on_every_key_release() if self._pie_widget.edit_mode: return + self._pie_manager.stop() if widget := self._pie_widget.widget_holder.active: self._controller.set_value(widget.label.value) @@ -132,14 +137,23 @@ def _create_labels(self, values: List[T]) -> List[Label]: """Wrap values into paintable label objects with position info.""" label_list = [] for value in values: - if icon := self._get_icon_if_possible(value): - label_list.append(Label(value=value, display_value=icon)) + label = self._controller.get_label(value) + label_list.append(Label(value=value, display_value=label)) return label_list - def _get_icon_if_possible(self, value: T) \ - -> Union[Text, QPixmap, QIcon, None]: - """Return the paintable icon of the value or None if missing.""" - try: - return self._controller.get_label(value) - except KeyError: - return None + def _create_unused_labels(self, values: List[T]) -> List[Label]: + """Create labels of all unused values""" + return self._create_labels(self._get_unused_values(values)) + + def _get_unused_values(self, values: List[T]) -> List[T]: + """Return all unused pie values.""" + if not values: + return [] + + value_type = values[0] + if not isinstance(value_type, Enum): + return [] + + names = type(value_type)._member_names_ + all_values = [type(value_type)[name] for name in names] + return list(filter(lambda x: x not in self._config.values, all_values)) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index 33169e64..fd58add9 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -4,7 +4,6 @@ from typing import Optional from PyQt5.QtGui import QCursor -from PyQt5.QtCore import QPoint from api_krita.pyqt import Timer from composer_utils import Config @@ -35,8 +34,7 @@ def __init__(self, widget: PieWidget, pie_settings: ScrollArea) -> None: def start(self) -> None: """Show widget under the mouse and start the mouse tracking loop.""" self._pie.move_center(QCursor().pos()) - self._pie_settings.move_center( - QCursor().pos() + QPoint(self._pie_settings.width(), 0)) + self._pie_settings.move_to_pie_side() self._pie.show() self._circle = CirclePoints(self._pie.center_global, 0) self._timer.start() diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index f79ffeee..b496f846 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -68,7 +68,7 @@ def __init__( radius=self._style.pie_radius) self.labels = labels - self.children_widgets = self._create_children_holder() + self.children_widgets = self._create_children() self.widget_holder = self._put_children_in_holder() self.accept_button = AcceptButton(self._style, self) @@ -125,7 +125,7 @@ def dragMoveEvent(self, e: QDragMoveEvent) -> None: self.repaint() e.accept() - def _create_children_holder(self) -> List[LabelWidget]: + def _create_children(self) -> List[LabelWidget]: """Create LabelWidgets that represent the labels.""" children: List[LabelWidget] = [] for label in self.labels: diff --git a/shortcut_composer/templates/pie_menu_utils/scroll_area.py b/shortcut_composer/templates/pie_menu_utils/scroll_area.py index de8e2e5f..94af8d7f 100644 --- a/shortcut_composer/templates/pie_menu_utils/scroll_area.py +++ b/shortcut_composer/templates/pie_menu_utils/scroll_area.py @@ -1,14 +1,18 @@ from typing import List -from PyQt5.QtCore import QSize, Qt +from PyQt5.QtCore import QPoint, Qt +from PyQt5.QtGui import QCursor from PyQt5.QtWidgets import ( QWidget, QScrollArea, - QLabel, QGridLayout, QHBoxLayout, ) from api_krita.pyqt import AnimatedWidget, BaseWidget from composer_utils import Config +from .label import Label +from .label_widget import LabelWidget +from .label_widget_utils import create_label_widget +from .pie_style import PieStyle class ScrollAreaLayout(QGridLayout): @@ -49,42 +53,39 @@ def _refresh(self): class ScrollArea(AnimatedWidget, BaseWidget): - def __init__(self, cols: int, parent=None): + def __init__( + self, + cols: int, + unused_values: List[Label], + style: PieStyle, + parent=None + ) -> None: AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) + self.setGeometry(0, 0, style.widget_radius*2, style.widget_radius*2) + + self._style = style + self._layout = ScrollAreaLayout(cols) self.setAcceptDrops(True) self.setWindowFlags(( self.windowFlags() | # type: ignore Qt.Tool | Qt.FramelessWindowHint)) - self.setCursor(Qt.CrossCursor) - - self.setGeometry(0, 0, 400, 300) - self._layout = ScrollAreaLayout(cols) - self.icon_size = 64 + self.setCursor(Qt.ArrowCursor) scroll_internal = QWidget() - for i in range(22): - self.add_label(str(i)) + self.labels = unused_values + self.children_list = self._create_children() - blue_label = QLabel("asd") - blue_label.setFixedSize(QSize(self.icon_size, self.icon_size)) - blue_label.setStyleSheet("background-color : blue") - self._layout.insert(10, blue_label) - - self._layout.pop(0) - self._layout.pop(0) - self._layout.pop(0) - - for i in range(5): - self.add_label(str(i)) + for child in self.children_list: + child.setFixedSize(self._style.icon_radius*2, self._style.icon_radius*2) + self._layout.append(child) scroll_internal.setLayout(self._layout) scroll_area = QScrollArea() - scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll_area.setWidget(scroll_internal) - scroll_area.setFixedWidth(scroll_internal.width() + self.icon_size//2) + scroll_area.setFixedWidth(scroll_internal.width() + self._style.icon_radius) scroll_area.setWidgetResizable(True) main_layout = QHBoxLayout(self) @@ -95,8 +96,12 @@ def __init__(self, cols: int, parent=None): self.hide() - def add_label(self, name: str): - label = QLabel(str(name)) - label.setFixedSize(QSize(self.icon_size, self.icon_size)) - label.setStyleSheet("background-color : green") - self._layout.append(label) + def _create_children(self) -> List[LabelWidget]: + """Create LabelWidgets that represent the labels.""" + children: List[LabelWidget] = [] + for label in self.labels: + children.append(create_label_widget(label, self._style, self)) + return children + + def move_to_pie_side(self): + self.move_center(QCursor().pos() + QPoint(self.width(), 0)) # type: ignore From 71f7d995700e2bcca88d42c537ca7cbb36da9d3c Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 018/125] Responsive adding and removing widgets in pie --- shortcut_composer/templates/pie_menu.py | 17 +++--- .../templates/pie_menu_utils/__init__.py | 4 +- .../templates/pie_menu_utils/pie_manager.py | 4 +- .../{scroll_area.py => pie_settings.py} | 28 ++++++--- .../templates/pie_menu_utils/pie_style.py | 30 +++++----- .../templates/pie_menu_utils/pie_widget.py | 57 ++++++++++++++----- .../widget_utils/widget_holder.py | 4 ++ 7 files changed, 97 insertions(+), 47 deletions(-) rename shortcut_composer/templates/pie_menu_utils/{scroll_area.py => pie_settings.py} (77%) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 4029ea5d..26e59150 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -10,7 +10,7 @@ from input_adapter import ComplexAction from .pie_menu_utils import ( create_pie_config, - ScrollArea, + PieSettings, PieManager, PieWidget, PieStyle, @@ -97,22 +97,23 @@ def __init__( pie_radius_scale, icon_radius_scale) self._values = self._config.values + self._labels = self._create_labels(self._values) self._style = PieStyle( pie_radius_scale=self._config.pie_radius_scale.read(), icon_radius_scale=self._config.icon_radius_scale.read(), - icons_amount=len(self._values), + icons=self._labels, background_color=background_color, active_color=active_color) + self._pie_settings = PieSettings( + style=self._style, + unused_values=self._create_unused_labels(self._values), + columns=3) self._pie_widget = PieWidget( style=self._style, - labels=self._create_labels(self._values), + labels=self._labels, config=self._config, pie_settings=self._pie_settings) - self._pie_settings = ScrollArea( - cols=3, - style=self._style, - unused_values=self._create_unused_labels(self._values)) self._pie_manager = PieManager( widget=self._pie_widget, pie_settings=self._pie_settings) @@ -142,7 +143,7 @@ def _create_labels(self, values: List[T]) -> List[Label]: return label_list def _create_unused_labels(self, values: List[T]) -> List[Label]: - """Create labels of all unused values""" + """Create labels of all unused values.""" return self._create_labels(self._get_unused_values(values)) def _get_unused_values(self, values: List[T]) -> List[T]: diff --git a/shortcut_composer/templates/pie_menu_utils/__init__.py b/shortcut_composer/templates/pie_menu_utils/__init__.py index 9f1b4b40..59cfb5c6 100644 --- a/shortcut_composer/templates/pie_menu_utils/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/__init__.py @@ -5,7 +5,7 @@ from .pie_config import create_pie_config from .label_widget import LabelWidget -from .scroll_area import ScrollArea +from .pie_settings import PieSettings from .pie_manager import PieManager from .pie_widget import PieWidget from .pie_style import PieStyle @@ -14,7 +14,7 @@ __all__ = [ "create_pie_config", "LabelWidget", - "ScrollArea", + "PieSettings", "PieManager", "PieWidget", "PieStyle", diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index fd58add9..145c8386 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -7,7 +7,7 @@ from api_krita.pyqt import Timer from composer_utils import Config -from .scroll_area import ScrollArea +from .pie_settings import PieSettings from .pie_widget import PieWidget from .label_widget import LabelWidget from .widget_utils import CirclePoints @@ -22,7 +22,7 @@ class PieManager: - Asks the widget to repaint when after changing active label. """ - def __init__(self, widget: PieWidget, pie_settings: ScrollArea) -> None: + def __init__(self, widget: PieWidget, pie_settings: PieSettings) -> None: self._pie = widget self._pie_settings = pie_settings self._holder = self._pie.widget_holder diff --git a/shortcut_composer/templates/pie_menu_utils/scroll_area.py b/shortcut_composer/templates/pie_menu_utils/pie_settings.py similarity index 77% rename from shortcut_composer/templates/pie_menu_utils/scroll_area.py rename to shortcut_composer/templates/pie_menu_utils/pie_settings.py index 94af8d7f..505e5f38 100644 --- a/shortcut_composer/templates/pie_menu_utils/scroll_area.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_settings.py @@ -16,11 +16,12 @@ class ScrollAreaLayout(QGridLayout): - def __init__(self, cols: int): + def __init__(self, cols: int, owner: QWidget): super().__init__() self.widgets: List[QWidget] = [] self._max_rows = cols self._uniques = 2*cols - 1 + self._owner = owner def __len__(self): return len(self.widgets) @@ -35,6 +36,10 @@ def _new_position(self): return self._get_position(len(self.widgets)) def append(self, widget: QWidget): + if widget in self.widgets: + return + widget.setParent(self._owner) + widget.show() self.widgets.append(widget) self.addWidget(widget, *self._new_position(), 2, 2) self._refresh() @@ -43,6 +48,12 @@ def pop(self, index: int): self.widgets.pop(index) self._refresh() + def remove(self, widget: QWidget): + for index, held_widget in enumerate(self.widgets): + if held_widget == widget: + self.pop(index) + return self._refresh() + def insert(self, index: int, widget: QWidget): self.widgets.insert(index, widget) self._refresh() @@ -52,19 +63,19 @@ def _refresh(self): self.addWidget(widget, *self._get_position(i), 2, 2) -class ScrollArea(AnimatedWidget, BaseWidget): +class PieSettings(AnimatedWidget, BaseWidget): def __init__( self, - cols: int, unused_values: List[Label], style: PieStyle, + columns: int, parent=None ) -> None: AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) self.setGeometry(0, 0, style.widget_radius*2, style.widget_radius*2) self._style = style - self._layout = ScrollAreaLayout(cols) + self._layout = ScrollAreaLayout(columns, self) self.setAcceptDrops(True) self.setWindowFlags(( @@ -78,14 +89,16 @@ def __init__( self.children_list = self._create_children() for child in self.children_list: - child.setFixedSize(self._style.icon_radius*2, self._style.icon_radius*2) + child.setFixedSize(self._style.icon_radius*2, + self._style.icon_radius*2) self._layout.append(child) scroll_internal.setLayout(self._layout) scroll_area = QScrollArea() scroll_area.setWidget(scroll_internal) - scroll_area.setFixedWidth(scroll_internal.width() + self._style.icon_radius) + scroll_area.setFixedWidth( + scroll_internal.width() + self._style.icon_radius) scroll_area.setWidgetResizable(True) main_layout = QHBoxLayout(self) @@ -104,4 +117,5 @@ def _create_children(self) -> List[LabelWidget]: return children def move_to_pie_side(self): - self.move_center(QCursor().pos() + QPoint(self.width(), 0)) # type: ignore + self.move_center(QCursor().pos() + + QPoint(self.width(), 0)) # type: ignore diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index c281fa23..e7c06126 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -29,11 +29,11 @@ def __init__( self, pie_radius_scale: float, icon_radius_scale: float, - icons_amount: int, + icons: list, background_color: Optional[QColor], active_color: QColor, ) -> None: - self._icons_amount = icons_amount + self._icons = icons self._base_size = Krita.screen_size/2560 self.pie_radius_scale = pie_radius_scale @@ -50,8 +50,7 @@ def __init__( * self.pie_radius_scale * Config.PIE_GLOBAL_SCALE.read()) - self.icon_radius = self._pick_icon_radius() - self.widget_radius = self.pie_radius + self.icon_radius + self.widget_radius = self.pie_radius + self.max_icon_size self.deadzone_radius = self._pick_deadzone_radius() self.border_thickness = round(self.pie_radius*0.02) @@ -71,22 +70,27 @@ def __init__( self.font_multiplier = self.SYSTEM_FONT_SIZE[platform.system()] - def _pick_icon_radius(self) -> int: - """Icons radius depend on settings, but they have to fit in the pie.""" - icon_radius: int = round( + @property + def base_icon_radius(self) -> int: + return round( 50 * self._base_size * self.icon_radius_scale * Config.PIE_ICON_GLOBAL_SCALE.read()) - - if not self._icons_amount: - return icon_radius - max_icon_size = round(self.pie_radius * math.pi / self._icons_amount) - return min(icon_radius, max_icon_size) + @property + def max_icon_size(self) -> int: + if not self._icons: + return 1 + return round(self.pie_radius * math.pi / len(self._icons)) + + @property + def icon_radius(self) -> int: + """Icons radius depend on settings, but they have to fit in the pie.""" + return min(self.base_icon_radius, self.max_icon_size) def _pick_deadzone_radius(self) -> float: """Deadzone can be configured, but when pie is empty, becomes inf.""" - if not self._icons_amount: + if not self._icons: return float("inf") return ( 40 * self._base_size diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index b496f846..b9b7179b 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -8,7 +8,7 @@ from api_krita.pyqt import Painter, AnimatedWidget, BaseWidget from composer_utils import Config from .pie_style import PieStyle -from .scroll_area import ScrollArea +from .pie_settings import PieSettings from .label import Label from .label_widget import LabelWidget from .label_widget_utils import create_label_widget @@ -54,7 +54,7 @@ def __init__( style: PieStyle, labels: List[Label], config: PieConfig, - pie_settings: ScrollArea, + pie_settings: PieSettings, parent=None ): AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) @@ -68,8 +68,10 @@ def __init__( radius=self._style.pie_radius) self.labels = labels - self.children_widgets = self._create_children() - self.widget_holder = self._put_children_in_holder() + self.children_widgets: List[LabelWidget] = [] + self.widget_holder: WidgetHolder = WidgetHolder() + self._reset_children() + self._reset_holder() self.accept_button = AcceptButton(self._style, self) self.accept_button.move_center(self.center) @@ -110,11 +112,30 @@ def dragMoveEvent(self, e: QDragMoveEvent) -> None: """Swap children during drag when mouse is moved to another zone.""" pos = e.pos() source_widget = e.source() + distance = self._circle_points.distance(pos) - if (self._circle_points.distance(pos) < self._style.deadzone_radius - or not isinstance(source_widget, LabelWidget)): + if (not isinstance(source_widget, LabelWidget) + or distance < self._style.deadzone_radius): return e.accept() + if distance > self._style.pie_radius: + if source_widget.label in self.labels: + self.labels.remove(source_widget.label) + # self.pie_settings._layout.append(source_widget) + self._restart() + return e.accept() + + if source_widget.label not in self.labels: + self.labels.append(source_widget.label) + # self.pie_settings._layout.remove(source_widget) + self._restart() + + if source_widget not in self.children_widgets: + for widget in self.children_widgets: + if widget.label == source_widget.label: + source_widget = widget + break + # NOTE: This computation is too heavy to be done on each call angle = self._circle_points.angle_from_point(pos) widget = self.widget_holder.on_angle(angle) @@ -125,23 +146,29 @@ def dragMoveEvent(self, e: QDragMoveEvent) -> None: self.repaint() e.accept() - def _create_children(self) -> List[LabelWidget]: + def _restart(self): + for child in self.children_widgets: + child.setParent(None) # type: ignore + self.children_widgets.clear() + self.widget_holder.flush() + self._reset_children() + self._reset_holder() + + def _reset_children(self) -> None: """Create LabelWidgets that represent the labels.""" - children: List[LabelWidget] = [] for label in self.labels: - children.append(create_label_widget(label, self._style, self)) - return children + self.children_widgets.append( + create_label_widget(label, self._style, self)) - def _put_children_in_holder(self) -> WidgetHolder: + def _reset_holder(self) -> None: """Create WidgetHolder which manages child widgets angles.""" children = self.children_widgets angle_iterator = self._circle_points.iterate_over_circle(len(children)) - label_holder = WidgetHolder() for child, (angle, point) in zip(children, angle_iterator): + child.setParent(self) + child.show() child.label.angle = angle child.label.center = point child.move_to_label() - label_holder.add(child) - - return label_holder + self.widget_holder.add(child) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py index b1061f75..046b5382 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py @@ -52,6 +52,10 @@ def swap(self, _a: LabelWidget, _b: LabelWidget) -> None: _a.move_to_label() _b.move_to_label() + def flush(self): + self._widgets = {} + self.active = None + def __iter__(self) -> Iterator[LabelWidget]: """Iterate over all held LabelWidgets.""" for angle in sorted(self.angles()): From 6e2e66a93d0d7ad4d157ab971c78714edd1ac25a Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 019/125] All possible values in the enum chooser --- shortcut_composer/templates/pie_menu.py | 11 ++-- .../templates/pie_menu_utils/label.py | 5 ++ .../templates/pie_menu_utils/pie_settings.py | 4 +- .../templates/pie_menu_utils/pie_style.py | 7 ++- .../templates/pie_menu_utils/pie_widget.py | 57 ++++++++++--------- .../widget_utils/widget_holder.py | 7 +++ 6 files changed, 52 insertions(+), 39 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 26e59150..03653be3 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -107,7 +107,7 @@ def __init__( self._pie_settings = PieSettings( style=self._style, - unused_values=self._create_unused_labels(self._values), + values=self._create_all_labels(self._values), columns=3) self._pie_widget = PieWidget( style=self._style, @@ -142,11 +142,11 @@ def _create_labels(self, values: List[T]) -> List[Label]: label_list.append(Label(value=value, display_value=label)) return label_list - def _create_unused_labels(self, values: List[T]) -> List[Label]: + def _create_all_labels(self, values: List[T]) -> List[Label]: """Create labels of all unused values.""" - return self._create_labels(self._get_unused_values(values)) + return self._create_labels(self._get_all_values(values)) - def _get_unused_values(self, values: List[T]) -> List[T]: + def _get_all_values(self, values: List[T]) -> List[T]: """Return all unused pie values.""" if not values: return [] @@ -156,5 +156,4 @@ def _get_unused_values(self, values: List[T]) -> List[T]: return [] names = type(value_type)._member_names_ - all_values = [type(value_type)[name] for name in names] - return list(filter(lambda x: x not in self._config.values, all_values)) + return [type(value_type)[name] for name in names] diff --git a/shortcut_composer/templates/pie_menu_utils/label.py b/shortcut_composer/templates/pie_menu_utils/label.py index a75ec460..a19613a7 100644 --- a/shortcut_composer/templates/pie_menu_utils/label.py +++ b/shortcut_composer/templates/pie_menu_utils/label.py @@ -40,6 +40,11 @@ def swap_locations(self, other: 'Label') -> None: """Change position data with information Label.""" self.angle, other.angle = other.angle, self.angle self.center, other.center = other.center, self.center + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Label): + return False + return self.value == other.value class AnimationProgress: diff --git a/shortcut_composer/templates/pie_menu_utils/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/pie_settings.py index 505e5f38..a5c2bba5 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_settings.py @@ -66,7 +66,7 @@ def _refresh(self): class PieSettings(AnimatedWidget, BaseWidget): def __init__( self, - unused_values: List[Label], + values: List[Label], style: PieStyle, columns: int, parent=None @@ -85,7 +85,7 @@ def __init__( self.setCursor(Qt.ArrowCursor) scroll_internal = QWidget() - self.labels = unused_values + self.labels = values self.children_list = self._create_children() for child in self.children_list: diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index e7c06126..1770b1d6 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -50,7 +50,8 @@ def __init__( * self.pie_radius_scale * Config.PIE_GLOBAL_SCALE.read()) - self.widget_radius = self.pie_radius + self.max_icon_size + max_icon_size = max(self.base_icon_radius, self.secondary_icon_size) + self.widget_radius = self.pie_radius + max_icon_size self.deadzone_radius = self._pick_deadzone_radius() self.border_thickness = round(self.pie_radius*0.02) @@ -78,7 +79,7 @@ def base_icon_radius(self) -> int: * Config.PIE_ICON_GLOBAL_SCALE.read()) @property - def max_icon_size(self) -> int: + def secondary_icon_size(self) -> int: if not self._icons: return 1 return round(self.pie_radius * math.pi / len(self._icons)) @@ -86,7 +87,7 @@ def max_icon_size(self) -> int: @property def icon_radius(self) -> int: """Icons radius depend on settings, but they have to fit in the pie.""" - return min(self.base_icon_radius, self.max_icon_size) + return min(self.base_icon_radius, self.secondary_icon_size) def _pick_deadzone_radius(self) -> float: """Deadzone can be configured, but when pie is empty, becomes inf.""" diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index b9b7179b..d83a8639 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -4,7 +4,12 @@ from typing import List, TypeVar from PyQt5.QtCore import Qt -from PyQt5.QtGui import QPaintEvent, QDragMoveEvent, QDragEnterEvent +from PyQt5.QtGui import ( + QDragEnterEvent, + QDragLeaveEvent, + QDragMoveEvent, + QPaintEvent) + from api_krita.pyqt import Painter, AnimatedWidget, BaseWidget from composer_utils import Config from .pie_style import PieStyle @@ -61,18 +66,17 @@ def __init__( self.setGeometry(0, 0, style.widget_radius*2, style.widget_radius*2) self._style = style + self.labels = labels self.config = config self.pie_settings = pie_settings + self._children_widgets: List[LabelWidget] = [] + self.widget_holder: WidgetHolder = WidgetHolder() + self._last_widget = None + self._circle_points = CirclePoints( center=self.center, radius=self._style.pie_radius) - self.labels = labels - self.children_widgets: List[LabelWidget] = [] - self.widget_holder: WidgetHolder = WidgetHolder() - self._reset_children() - self._reset_holder() - self.accept_button = AcceptButton(self._style, self) self.accept_button.move_center(self.center) self.accept_button.clicked.connect(self.hide) @@ -87,6 +91,8 @@ def __init__( self.setStyleSheet("background: transparent;") self.setCursor(Qt.CrossCursor) + self._reset() + @property def deadzone(self) -> float: """Return the deadzone distance.""" @@ -112,29 +118,19 @@ def dragMoveEvent(self, e: QDragMoveEvent) -> None: """Swap children during drag when mouse is moved to another zone.""" pos = e.pos() source_widget = e.source() + self._last_widget = source_widget distance = self._circle_points.distance(pos) if (not isinstance(source_widget, LabelWidget) or distance < self._style.deadzone_radius): return e.accept() - if distance > self._style.pie_radius: - if source_widget.label in self.labels: - self.labels.remove(source_widget.label) - # self.pie_settings._layout.append(source_widget) - self._restart() - return e.accept() - if source_widget.label not in self.labels: self.labels.append(source_widget.label) - # self.pie_settings._layout.remove(source_widget) - self._restart() + return self._reset() - if source_widget not in self.children_widgets: - for widget in self.children_widgets: - if widget.label == source_widget.label: - source_widget = widget - break + if source_widget not in self._children_widgets: + source_widget = self.widget_holder.on_label(source_widget.label) # NOTE: This computation is too heavy to be done on each call angle = self._circle_points.angle_from_point(pos) @@ -146,23 +142,28 @@ def dragMoveEvent(self, e: QDragMoveEvent) -> None: self.repaint() e.accept() - def _restart(self): - for child in self.children_widgets: + def dragLeaveEvent(self, e: QDragLeaveEvent) -> None: + if self._last_widget: + if self._last_widget.label in self.labels: + self.labels.remove(self._last_widget.label) + self._reset() + return super().dragLeaveEvent(e) + + def _reset(self): + for child in self._children_widgets: child.setParent(None) # type: ignore - self.children_widgets.clear() + self._children_widgets.clear() self.widget_holder.flush() self._reset_children() self._reset_holder() def _reset_children(self) -> None: - """Create LabelWidgets that represent the labels.""" for label in self.labels: - self.children_widgets.append( + self._children_widgets.append( create_label_widget(label, self._style, self)) def _reset_holder(self) -> None: - """Create WidgetHolder which manages child widgets angles.""" - children = self.children_widgets + children = self._children_widgets angle_iterator = self._circle_points.iterate_over_circle(len(children)) for child, (angle, point) in zip(children, angle_iterator): diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py index 046b5382..1980ed2d 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py @@ -3,6 +3,7 @@ from typing import Dict, Iterator, Optional from ..label_widget import LabelWidget +from ..label import Label class WidgetHolder: @@ -37,6 +38,12 @@ def angle_difference(label_angle: float) -> float: closest = min(self.angles(), key=angle_difference) return self._widgets[closest] + def on_label(self, label: Label): + for widget in self._widgets.values(): + if widget.label == label: + return widget + raise ValueError(f"{label} not in holder.") + def angle(self, widget: LabelWidget) -> int: """Return at which angle angle is the given LabelWidget.""" for angle, held_widget in self._widgets.items(): From 1ff2daf8d484b8679ed8a068b520c86d946a509d Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 020/125] Polishing scroll code --- .../templates/pie_menu_utils/pie_settings.py | 115 +++++++++--------- .../templates/pie_menu_utils/pie_style.py | 15 +-- .../templates/pie_menu_utils/pie_widget.py | 43 +++---- .../widget_utils/widget_holder.py | 2 +- 4 files changed, 86 insertions(+), 89 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/pie_settings.py index a5c2bba5..b0378f41 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_settings.py @@ -15,6 +15,63 @@ from .pie_style import PieStyle +class PieSettings(AnimatedWidget, BaseWidget): + def __init__( + self, + values: List[Label], + style: PieStyle, + columns: int, + parent=None + ) -> None: + AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) + self.setFixedHeight(style.widget_radius*2) + + self._style = style + self.labels = values + self.children_list = self._create_children() + + self.setAcceptDrops(True) + self.setWindowFlags(( + self.windowFlags() | # type: ignore + Qt.Tool | + Qt.FramelessWindowHint)) + self.setCursor(Qt.ArrowCursor) + + self._scroll_area_layout = ScrollAreaLayout(columns, self) + radius = self._style.icon_radius*2 + for child in self.children_list: + child.setFixedSize(radius, radius) + self._scroll_area_layout.append(child) + + scroll_widget = QWidget() + scroll_widget.setLayout(self._scroll_area_layout) + + scroll_area = QScrollArea() + scroll_area.setWidget(scroll_widget) + scroll_area.setFixedWidth(self._style.icon_radius*(columns*2+1)) + scroll_area.setWidgetResizable(True) + + pop_up_layout = QHBoxLayout(self) + pop_up_layout.addStretch() + pop_up_layout.addWidget(scroll_area) + pop_up_layout.addStretch() + self.setLayout(pop_up_layout) + + self.hide() + + def _create_children(self) -> List[LabelWidget]: + """Create LabelWidgets that represent the labels.""" + children: List[LabelWidget] = [] + for label in self.labels: + children.append(create_label_widget(label, self._style, self)) + return children + + def move_to_pie_side(self): + offset = self.width()//2 + self._style.widget_radius * 1.05 + point = QPoint(round(offset),0) + self.move_center(QCursor().pos() + point) # type: ignore + + class ScrollAreaLayout(QGridLayout): def __init__(self, cols: int, owner: QWidget): super().__init__() @@ -61,61 +118,3 @@ def insert(self, index: int, widget: QWidget): def _refresh(self): for i, widget in enumerate(self.widgets): self.addWidget(widget, *self._get_position(i), 2, 2) - - -class PieSettings(AnimatedWidget, BaseWidget): - def __init__( - self, - values: List[Label], - style: PieStyle, - columns: int, - parent=None - ) -> None: - AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) - self.setGeometry(0, 0, style.widget_radius*2, style.widget_radius*2) - - self._style = style - self._layout = ScrollAreaLayout(columns, self) - - self.setAcceptDrops(True) - self.setWindowFlags(( - self.windowFlags() | # type: ignore - Qt.Tool | - Qt.FramelessWindowHint)) - self.setCursor(Qt.ArrowCursor) - - scroll_internal = QWidget() - self.labels = values - self.children_list = self._create_children() - - for child in self.children_list: - child.setFixedSize(self._style.icon_radius*2, - self._style.icon_radius*2) - self._layout.append(child) - - scroll_internal.setLayout(self._layout) - - scroll_area = QScrollArea() - scroll_area.setWidget(scroll_internal) - scroll_area.setFixedWidth( - scroll_internal.width() + self._style.icon_radius) - scroll_area.setWidgetResizable(True) - - main_layout = QHBoxLayout(self) - main_layout.addStretch() - main_layout.addWidget(scroll_area) - main_layout.addStretch() - self.setLayout(main_layout) - - self.hide() - - def _create_children(self) -> List[LabelWidget]: - """Create LabelWidgets that represent the labels.""" - children: List[LabelWidget] = [] - for label in self.labels: - children.append(create_label_widget(label, self._style, self)) - return children - - def move_to_pie_side(self): - self.move_center(QCursor().pos() + - QPoint(self.width(), 0)) # type: ignore diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index 1770b1d6..a1cd0aed 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -49,10 +49,7 @@ def __init__( 165 * self._base_size * self.pie_radius_scale * Config.PIE_GLOBAL_SCALE.read()) - - max_icon_size = max(self.base_icon_radius, self.secondary_icon_size) - self.widget_radius = self.pie_radius + max_icon_size - self.deadzone_radius = self._pick_deadzone_radius() + self.widget_radius = self.pie_radius + self.base_icon_radius self.border_thickness = round(self.pie_radius*0.02) self.area_thickness = round(self.pie_radius/self.pie_radius_scale*0.4) @@ -79,7 +76,7 @@ def base_icon_radius(self) -> int: * Config.PIE_ICON_GLOBAL_SCALE.read()) @property - def secondary_icon_size(self) -> int: + def max_icon_radius(self) -> int: if not self._icons: return 1 return round(self.pie_radius * math.pi / len(self._icons)) @@ -87,16 +84,16 @@ def secondary_icon_size(self) -> int: @property def icon_radius(self) -> int: """Icons radius depend on settings, but they have to fit in the pie.""" - return min(self.base_icon_radius, self.secondary_icon_size) + return min(self.base_icon_radius, self.max_icon_radius) - def _pick_deadzone_radius(self) -> float: + @property + def deadzone_radius(self) -> float: """Deadzone can be configured, but when pie is empty, becomes inf.""" if not self._icons: return float("inf") return ( 40 * self._base_size - * Config.PIE_DEADZONE_GLOBAL_SCALE.read() - ) + * Config.PIE_DEADZONE_GLOBAL_SCALE.read()) def _pick_background_color(self, color: Optional[QColor]) -> QColor: """Default background color depends on the app theme lightness.""" diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index d83a8639..e7513a99 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -116,44 +116,45 @@ def dragEnterEvent(self, e: QDragEnterEvent) -> None: def dragMoveEvent(self, e: QDragMoveEvent) -> None: """Swap children during drag when mouse is moved to another zone.""" - pos = e.pos() + e.accept() source_widget = e.source() - self._last_widget = source_widget + pos = e.pos() distance = self._circle_points.distance(pos) + if not isinstance(source_widget, LabelWidget): + return - if (not isinstance(source_widget, LabelWidget) - or distance < self._style.deadzone_radius): - return e.accept() + self._last_widget = source_widget + if distance > self._style.widget_radius: + return self._remove_widget(source_widget) + if distance < self._style.deadzone_radius: + return if source_widget.label not in self.labels: self.labels.append(source_widget.label) return self._reset() - if source_widget not in self._children_widgets: - source_widget = self.widget_holder.on_label(source_widget.label) - - # NOTE: This computation is too heavy to be done on each call angle = self._circle_points.angle_from_point(pos) - widget = self.widget_holder.on_angle(angle) - if widget == source_widget: - return e.accept() - - self.widget_holder.swap(widget, source_widget) - self.repaint() - e.accept() + source = self.widget_holder.on_label(source_widget.label) + held = self.widget_holder.on_angle(angle) + if held != source: + self.widget_holder.swap(held, source) + self.repaint() def dragLeaveEvent(self, e: QDragLeaveEvent) -> None: - if self._last_widget: - if self._last_widget.label in self.labels: - self.labels.remove(self._last_widget.label) - self._reset() + if self._last_widget is not None: + self._remove_widget(self._last_widget) return super().dragLeaveEvent(e) + def _remove_widget(self, widget: LabelWidget): + if widget.label in self.labels: + self.labels.remove(widget.label) + self._reset() + def _reset(self): for child in self._children_widgets: child.setParent(None) # type: ignore self._children_widgets.clear() - self.widget_holder.flush() + self.widget_holder.clear() self._reset_children() self._reset_holder() diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py index 1980ed2d..0ee704a7 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py @@ -59,7 +59,7 @@ def swap(self, _a: LabelWidget, _b: LabelWidget) -> None: _a.move_to_label() _b.move_to_label() - def flush(self): + def clear(self): self._widgets = {} self.active = None From 26c1d31bab57b94f59269de8977d77cd5f24e56b Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 021/125] Fix icon scale in settings --- shortcut_composer/templates/pie_menu.py | 7 +++++-- .../label_widget_utils/image_label_widget.py | 3 +-- .../label_widget_utils/text_label_widget.py | 6 ++---- .../templates/pie_menu_utils/pie_settings.py | 8 +++++--- .../templates/pie_menu_utils/pie_style.py | 12 +++++++----- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 03653be3..a5ce32b2 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from typing import List, TypeVar, Generic, Optional +from copy import copy from enum import Enum from PyQt5.QtGui import QColor @@ -101,12 +102,14 @@ def __init__( self._style = PieStyle( pie_radius_scale=self._config.pie_radius_scale.read(), icon_radius_scale=self._config.icon_radius_scale.read(), - icons=self._labels, background_color=background_color, active_color=active_color) + unscaled_style = copy(self._style) + self._style.set_items(self._labels) + self._pie_settings = PieSettings( - style=self._style, + style=unscaled_style, values=self._create_all_labels(self._values), columns=3) self._pie_widget = PieWidget( diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/image_label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/image_label_widget.py index fbdfd2b4..43a32bf2 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/image_label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/image_label_widget.py @@ -37,8 +37,7 @@ def paintEvent(self, event: QPaintEvent) -> None: outer_radius=( self._style.icon_radius-self._style.border_thickness//2), color=self._style.border_color, - thickness=self._style.border_thickness, - ) + thickness=self._style.border_thickness) painter.paint_pixmap(self.center, self.ready_image) def _prepare_image(self) -> QPixmap: diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/text_label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/text_label_widget.py index 46729863..e4b70903 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/text_label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/text_label_widget.py @@ -33,14 +33,12 @@ def paintEvent(self, event: QPaintEvent) -> None: painter.paint_wheel( center=self.center, outer_radius=self._style.icon_radius, - color=self._style.icon_color, - ) + color=self._style.icon_color) painter.paint_wheel( center=self.center, outer_radius=self._style.icon_radius, color=self._style.border_color, - thickness=self._style.border_thickness, - ) + thickness=self._style.border_thickness) def _create_pyqt_label(self) -> QLabel: """Create and show a new Qt5 label. Does not need redrawing.""" diff --git a/shortcut_composer/templates/pie_menu_utils/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/pie_settings.py index b0378f41..3147b88a 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_settings.py @@ -1,4 +1,5 @@ from typing import List + from PyQt5.QtCore import QPoint, Qt from PyQt5.QtGui import QCursor from PyQt5.QtWidgets import ( @@ -38,9 +39,10 @@ def __init__( self.setCursor(Qt.ArrowCursor) self._scroll_area_layout = ScrollAreaLayout(columns, self) - radius = self._style.icon_radius*2 + + diameter = self._style.icon_radius*2 for child in self.children_list: - child.setFixedSize(radius, radius) + child.setFixedSize(diameter, diameter) self._scroll_area_layout.append(child) scroll_widget = QWidget() @@ -48,7 +50,7 @@ def __init__( scroll_area = QScrollArea() scroll_area.setWidget(scroll_widget) - scroll_area.setFixedWidth(self._style.icon_radius*(columns*2+1)) + scroll_area.setFixedWidth(round(diameter*(columns+0.5))) scroll_area.setWidgetResizable(True) pop_up_layout = QHBoxLayout(self) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index a1cd0aed..d5b48cff 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -29,11 +29,10 @@ def __init__( self, pie_radius_scale: float, icon_radius_scale: float, - icons: list, background_color: Optional[QColor], active_color: QColor, ) -> None: - self._icons = icons + self._items = [None] self._base_size = Krita.screen_size/2560 self.pie_radius_scale = pie_radius_scale @@ -68,6 +67,9 @@ def __init__( self.font_multiplier = self.SYSTEM_FONT_SIZE[platform.system()] + def set_items(self, items: list): + self._items = items + @property def base_icon_radius(self) -> int: return round( @@ -77,9 +79,9 @@ def base_icon_radius(self) -> int: @property def max_icon_radius(self) -> int: - if not self._icons: + if not self._items: return 1 - return round(self.pie_radius * math.pi / len(self._icons)) + return round(self.pie_radius * math.pi / len(self._items)) @property def icon_radius(self) -> int: @@ -89,7 +91,7 @@ def icon_radius(self) -> int: @property def deadzone_radius(self) -> float: """Deadzone can be configured, but when pie is empty, becomes inf.""" - if not self._icons: + if not self._items: return float("inf") return ( 40 * self._base_size From 4988e22e99bac3aaabfd5e17014349c7852483ae Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 022/125] Separated setting window from ScrollArea widget --- .../templates/pie_menu_utils/pie_settings.py | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/pie_settings.py index 3147b88a..5b11a79f 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_settings.py @@ -25,12 +25,6 @@ def __init__( parent=None ) -> None: AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) - self.setFixedHeight(style.widget_radius*2) - - self._style = style - self.labels = values - self.children_list = self._create_children() - self.setAcceptDrops(True) self.setWindowFlags(( self.windowFlags() | # type: ignore @@ -38,41 +32,58 @@ def __init__( Qt.FramelessWindowHint)) self.setCursor(Qt.ArrowCursor) - self._scroll_area_layout = ScrollAreaLayout(columns, self) + self._style = style + self.scroll_area = ScrollArea( + values, + self._style, + columns) - diameter = self._style.icon_radius*2 - for child in self.children_list: - child.setFixedSize(diameter, diameter) - self._scroll_area_layout.append(child) + layout = QHBoxLayout(self) + layout.addWidget(self.scroll_area) + self.setLayout(layout) - scroll_widget = QWidget() - scroll_widget.setLayout(self._scroll_area_layout) + self.hide() - scroll_area = QScrollArea() - scroll_area.setWidget(scroll_widget) - scroll_area.setFixedWidth(round(diameter*(columns+0.5))) - scroll_area.setWidgetResizable(True) + def move_to_pie_side(self): + offset = self.width()//2 + self._style.widget_radius * 1.05 + point = QPoint(round(offset), 0) + self.move_center(QCursor().pos() + point) # type: ignore - pop_up_layout = QHBoxLayout(self) - pop_up_layout.addStretch() - pop_up_layout.addWidget(scroll_area) - pop_up_layout.addStretch() - self.setLayout(pop_up_layout) - self.hide() +class ScrollArea(QScrollArea): + def __init__( + self, + values: List[Label], + style: PieStyle, + columns: int, + parent=None + ) -> None: + super().__init__(parent) + self.setFixedHeight(style.widget_radius*2) + self.setFixedWidth(round(style.icon_radius*(2*columns + 1))) + self.setWidgetResizable(True) + + self._style = style + self.labels = values + + self._scroll_area_layout = ScrollAreaLayout(columns, self) + self._children_list = self._create_children() + + scroll_widget = QWidget() + scroll_widget.setLayout(self._scroll_area_layout) + self.setWidget(scroll_widget) def _create_children(self) -> List[LabelWidget]: """Create LabelWidgets that represent the labels.""" children: List[LabelWidget] = [] + diameter = self._style.icon_radius*2 + for label in self.labels: children.append(create_label_widget(label, self._style, self)) + children[-1].setFixedSize(diameter, diameter) + self._scroll_area_layout.append(children[-1]) return children - def move_to_pie_side(self): - offset = self.width()//2 + self._style.widget_radius * 1.05 - point = QPoint(round(offset),0) - self.move_center(QCursor().pos() + point) # type: ignore - class ScrollAreaLayout(QGridLayout): def __init__(self, cols: int, owner: QWidget): From 42a932a17f774617f82af56a5fe4e2b40af73731 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 023/125] Local pie settings barely working --- shortcut_composer/composer_utils/config.py | 6 +- .../layouts/combo_boxes_layout.py | 11 +- .../layouts/spin_boxes_layout.py | 10 +- .../composer_utils/settings_dialog.py | 3 +- shortcut_composer/shortcut_composer.py | 10 +- shortcut_composer/templates/pie_menu.py | 17 +-- .../templates/pie_menu_utils/__init__.py | 8 +- .../templates/pie_menu_utils/pie_config.py | 2 +- .../pie_menu_utils/pie_local_settings.py | 125 ++++++++++++++++++ .../templates/pie_menu_utils/pie_manager.py | 4 +- .../templates/pie_menu_utils/pie_settings.py | 33 ++++- .../templates/pie_menu_utils/pie_widget.py | 4 +- 12 files changed, 189 insertions(+), 44 deletions(-) create mode 100644 shortcut_composer/templates/pie_menu_utils/pie_local_settings.py diff --git a/shortcut_composer/composer_utils/config.py b/shortcut_composer/composer_utils/config.py index 888625a1..9669faab 100644 --- a/shortcut_composer/composer_utils/config.py +++ b/shortcut_composer/composer_utils/config.py @@ -35,9 +35,9 @@ class Config: PIE_DEADZONE_GLOBAL_SCALE = BuiltinConfig("Pie deadzone global scale", 1.0) PIE_ANIMATION_TIME = BuiltinConfig("Pie animation time", 0.2) - TAG_RED = BuiltinConfig("Tag (red)", "★ My Favorites") - TAG_GREEN = BuiltinConfig("Tag (green)", "RGBA") - TAG_BLUE = BuiltinConfig("Tag (blue)", "Erasers") + TAG_RED = BuiltinConfig("Pick brush presets (red)", "★ My Favorites") + TAG_GREEN = BuiltinConfig("Pick brush presets (green)", "RGBA") + TAG_BLUE = BuiltinConfig("Pick brush presets (blue)", "Erasers") @classmethod def reset_defaults(cls) -> None: diff --git a/shortcut_composer/composer_utils/layouts/combo_boxes_layout.py b/shortcut_composer/composer_utils/layouts/combo_boxes_layout.py index d30142d6..771804e1 100644 --- a/shortcut_composer/composer_utils/layouts/combo_boxes_layout.py +++ b/shortcut_composer/composer_utils/layouts/combo_boxes_layout.py @@ -11,6 +11,7 @@ from PyQt5.QtCore import Qt from api_krita.wrappers import Database +from ..internal_config import BuiltinConfig from ..config import Config @@ -20,7 +21,7 @@ class ComboBoxesLayout(QGridLayout): def __init__(self) -> None: super().__init__() self.setAlignment(Qt.AlignTop) - self._combo_boxes: Dict[Config, QComboBox] = {} + self._combo_boxes: Dict[BuiltinConfig, QComboBox] = {} self._row_counter = count() self._add_label("Preset pie-menus mapping") @@ -28,10 +29,10 @@ def __init__(self) -> None: self._add_row(Config.TAG_GREEN) self._add_row(Config.TAG_BLUE) - def _add_row(self, config: Config) -> None: + def _add_row(self, config: BuiltinConfig) -> None: """Add a combobox to the layout along with its description.""" row_id = next(self._row_counter) - label = QLabel(config.value) + label = QLabel(config.name) label.setFixedWidth(100) self.addWidget(label, row_id, 0) self.addWidget(self._create_combobox(config), row_id, 1) @@ -42,10 +43,10 @@ def _add_label(self, text: str): label.setAlignment(Qt.AlignCenter) self.addWidget(label, row_id, 0, 1, 2) - def _create_combobox(self, config: Config) -> QComboBox: + def _create_combobox(self, config: BuiltinConfig) -> QComboBox: """Store and return combobox that represents given config field.""" combo_box = QComboBox() - combo_box.setObjectName(config.value) + combo_box.setObjectName(config.name) self._combo_boxes[config] = combo_box return combo_box diff --git a/shortcut_composer/composer_utils/layouts/spin_boxes_layout.py b/shortcut_composer/composer_utils/layouts/spin_boxes_layout.py index 90a5cf00..d4fd9ce4 100644 --- a/shortcut_composer/composer_utils/layouts/spin_boxes_layout.py +++ b/shortcut_composer/composer_utils/layouts/spin_boxes_layout.py @@ -12,6 +12,7 @@ QLabel, ) +from ..internal_config import BuiltinConfig from ..config import Config SpinBox = Union[QSpinBox, QDoubleSpinBox] @@ -23,14 +24,14 @@ class SpinBoxesLayout(QFormLayout): @dataclass class ConfigParams: """Adds spinbox parametrization to the config field.""" - config: Config + config: BuiltinConfig step: float max_value: float is_int: bool def __init__(self) -> None: super().__init__() - self._forms: Dict[Config, SpinBox] = {} + self._forms: Dict[BuiltinConfig, SpinBox] = {} self._add_label("Common settings") @@ -89,7 +90,7 @@ def __init__(self) -> None: def _add_row(self, config_params: ConfigParams) -> None: """Add a spin box to the layout along with its description.""" self.addRow( - config_params.config.value, + config_params.config.name, self._create_form(config_params) ) @@ -102,7 +103,7 @@ def _add_label(self, text: str): def _create_form(self, config_params: ConfigParams) -> SpinBox: """Store and return new spin box for required type (int or float).""" form = QSpinBox() if config_params.is_int else QDoubleSpinBox() - form.setObjectName(config_params.config.value) + form.setObjectName(config_params.config.name) form.setMinimum(0) form.setMaximum(config_params.max_value) # type: ignore form.setSingleStep(config_params.step) # type: ignore @@ -119,3 +120,4 @@ def apply(self) -> None: """Write values from stored spin boxes to krita config file.""" for config, form in self._forms.items(): config.write(form.value()) + diff --git a/shortcut_composer/composer_utils/settings_dialog.py b/shortcut_composer/composer_utils/settings_dialog.py index 8395bd5f..9676d5c0 100644 --- a/shortcut_composer/composer_utils/settings_dialog.py +++ b/shortcut_composer/composer_utils/settings_dialog.py @@ -12,7 +12,7 @@ from api_krita import Krita from .config import Config from .layouts import ButtonsLayout -from .tabs import GeneralSettingsTab, ActionValuesTab +from .tabs import GeneralSettingsTab class SettingsDialog(QDialog): @@ -26,7 +26,6 @@ def __init__(self) -> None: self._tab_dict = { "General": GeneralSettingsTab(), - "Action values": ActionValuesTab(), } tab_holder = QTabWidget() for name, tab in self._tab_dict.items(): diff --git a/shortcut_composer/shortcut_composer.py b/shortcut_composer/shortcut_composer.py index 8df0dfca..ea765677 100755 --- a/shortcut_composer/shortcut_composer.py +++ b/shortcut_composer/shortcut_composer.py @@ -1,12 +1,12 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -__version__ = "1.1.1" +__version__ = "1.2.0 prealpha" __author__ = "Wojciech Trybus" from typing import List from dataclasses import dataclass -from PyQt5.QtWidgets import QWidgetAction, QWidget +from PyQt5.QtWidgets import QWidgetAction from api_krita import Krita, Extension # type: ignore from api_krita.actions import TransformModeActions @@ -56,10 +56,8 @@ def createActions(self, window) -> None: """Create window components. Called by krita for each new window.""" self._protectors.append(GarbageProtector( transform_modes=TransformModeActions(window), - # settings_dialog=(settings := SettingsDialog()), - # settings_action=self._create_settings_action(window, settings), - settings_dialog=QWidget(), - settings_action=QWidget(), + settings_dialog=(settings := SettingsDialog()), + settings_action=self._create_settings_action(window, settings), action_manager=ActionManager(window), reload_action=self._create_reload_action(window))) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index a5ce32b2..0f7cddee 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -10,8 +10,8 @@ from core_components import Controller, Instruction from input_adapter import ComplexAction from .pie_menu_utils import ( - create_pie_config, - PieSettings, + create_local_config, + PieSettingsWindow, PieManager, PieWidget, PieStyle, @@ -92,30 +92,31 @@ def __init__( short_vs_long_press_time=short_vs_long_press_time, instructions=instructions) self._controller = controller - self._config = create_pie_config( + self._local_config = create_local_config( name, values, pie_radius_scale, icon_radius_scale) - self._values = self._config.values + self._values = self._local_config.values self._labels = self._create_labels(self._values) self._style = PieStyle( - pie_radius_scale=self._config.pie_radius_scale.read(), - icon_radius_scale=self._config.icon_radius_scale.read(), + pie_radius_scale=self._local_config.pie_radius_scale.read(), + icon_radius_scale=self._local_config.icon_radius_scale.read(), background_color=background_color, active_color=active_color) unscaled_style = copy(self._style) self._style.set_items(self._labels) - self._pie_settings = PieSettings( + self._pie_settings = PieSettingsWindow( style=unscaled_style, values=self._create_all_labels(self._values), + pie_config=self._local_config, columns=3) self._pie_widget = PieWidget( style=self._style, labels=self._labels, - config=self._config, + config=self._local_config, pie_settings=self._pie_settings) self._pie_manager = PieManager( widget=self._pie_widget, diff --git a/shortcut_composer/templates/pie_menu_utils/__init__.py b/shortcut_composer/templates/pie_menu_utils/__init__.py index 59cfb5c6..70e9230a 100644 --- a/shortcut_composer/templates/pie_menu_utils/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/__init__.py @@ -3,18 +3,18 @@ """Implementation of PieMenu main elements.""" -from .pie_config import create_pie_config +from .pie_config import create_local_config from .label_widget import LabelWidget -from .pie_settings import PieSettings +from .pie_settings import PieSettingsWindow from .pie_manager import PieManager from .pie_widget import PieWidget from .pie_style import PieStyle from .label import Label __all__ = [ - "create_pie_config", + "create_local_config", "LabelWidget", - "PieSettings", + "PieSettingsWindow", "PieManager", "PieWidget", "PieStyle", diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index e1026fd6..68555392 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -9,7 +9,7 @@ from data_components import Tag -def create_pie_config( +def create_local_config( name: str, values: list, pie_radius_scale: float, diff --git a/shortcut_composer/templates/pie_menu_utils/pie_local_settings.py b/shortcut_composer/templates/pie_menu_utils/pie_local_settings.py new file mode 100644 index 00000000..d1a53fa5 --- /dev/null +++ b/shortcut_composer/templates/pie_menu_utils/pie_local_settings.py @@ -0,0 +1,125 @@ +from typing import Dict +from PyQt5.QtWidgets import ( + QDoubleSpinBox, + QFormLayout, + QVBoxLayout, + QHBoxLayout, + QSplitter, + QSpinBox, + QLabel, +) +from PyQt5.QtCore import Qt +from dataclasses import dataclass +from typing import Dict, Union +from PyQt5.QtWidgets import QWidget + +from typing import Dict, Union +from dataclasses import dataclass +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import ( + QDoubleSpinBox, + QFormLayout, + QSplitter, + QSpinBox, + QLabel, +) + +from .pie_config import PieConfig +from composer_utils import ConfigBase + +SpinBox = Union[QSpinBox, QDoubleSpinBox] + + +class LocalPieSettings(QWidget): + """Dialog which allows to change global settings of the plugin.""" + + def __init__(self, pie_config: PieConfig) -> None: + super().__init__() + self._pie_config = pie_config + + self._layouts_dict = { + "SpinBoxes": SpinBoxesLayout(pie_config), + } + layout = QVBoxLayout() + for layout_part in self._layouts_dict.values(): + layout.addLayout(layout_part) + + stretched = QHBoxLayout() + stretched.addStretch() + stretched.addLayout(layout) + stretched.addStretch() + self.setLayout(stretched) + + def apply(self) -> None: + """Ask all dialog zones to apply themselves.""" + for layout in self._layouts_dict.values(): + layout.apply() + + def refresh(self) -> None: + """Ask all dialog zones to refresh themselves. """ + for layout in self._layouts_dict.values(): + layout.refresh() + + +class SpinBoxesLayout(QFormLayout): + """Dialog zone consisting of spin boxes.""" + + @dataclass + class ConfigParams: + """Adds spinbox parametrization to the config field.""" + config: ConfigBase + step: float + max_value: float + is_int: bool + + def __init__(self, pie_config: PieConfig) -> None: + super().__init__() + self._forms: Dict[ConfigBase, SpinBox] = {} + + self._add_label("Bla") + + self._add_row(self.ConfigParams( + pie_config.pie_radius_scale, + step=0.05, + max_value=4, + is_int=False)) + self._add_row(self.ConfigParams( + pie_config.icon_radius_scale, + step=0.05, + max_value=4, + is_int=False)) + + self._add_label("Ble") + + def _add_row(self, config_params: ConfigParams) -> None: + """Add a spin box to the layout along with its description.""" + self.addRow( + config_params.config.name, + self._create_form(config_params)) + + def _add_label(self, text: str): + label = QLabel(text) + label.setAlignment(Qt.AlignCenter) + self.addRow(QSplitter(Qt.Horizontal)) + self.addRow(label) + + def _create_form(self, config_params: ConfigParams) -> SpinBox: + """Store and return new spin box for required type (int or float).""" + form = QSpinBox() if config_params.is_int else QDoubleSpinBox() + form.setObjectName(config_params.config.name) + form.setMinimum(0) + form.setMaximum(config_params.max_value) # type: ignore + form.setSingleStep(config_params.step) # type: ignore + + self._forms[config_params.config] = form + return form + + def refresh(self) -> None: + """Read values from krita config and apply them to stored boxes.""" + for config, form in self._forms.items(): + form.setValue(config.read()) # type: ignore + + def apply(self) -> None: + """Write values from stored spin boxes to krita config file.""" + for config, form in self._forms.items(): + config.write(form.value()) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index 145c8386..77be2697 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -7,7 +7,7 @@ from api_krita.pyqt import Timer from composer_utils import Config -from .pie_settings import PieSettings +from .pie_settings import PieSettingsWindow from .pie_widget import PieWidget from .label_widget import LabelWidget from .widget_utils import CirclePoints @@ -22,7 +22,7 @@ class PieManager: - Asks the widget to repaint when after changing active label. """ - def __init__(self, widget: PieWidget, pie_settings: PieSettings) -> None: + def __init__(self, widget: PieWidget, pie_settings: PieSettingsWindow) -> None: self._pie = widget self._pie_settings = pie_settings self._holder = self._pie.widget_holder diff --git a/shortcut_composer/templates/pie_menu_utils/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/pie_settings.py index 5b11a79f..85432e55 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_settings.py @@ -6,22 +6,27 @@ QWidget, QScrollArea, QGridLayout, - QHBoxLayout, + QVBoxLayout, + QTabWidget, ) +from api_krita import Krita from api_krita.pyqt import AnimatedWidget, BaseWidget from composer_utils import Config from .label import Label from .label_widget import LabelWidget from .label_widget_utils import create_label_widget from .pie_style import PieStyle +from .pie_config import PieConfig +from .pie_local_settings import LocalPieSettings -class PieSettings(AnimatedWidget, BaseWidget): +class PieSettingsWindow(AnimatedWidget, BaseWidget): def __init__( self, values: List[Label], style: PieStyle, columns: int, + pie_config: PieConfig, parent=None ) -> None: AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) @@ -33,22 +38,36 @@ def __init__( self.setCursor(Qt.ArrowCursor) self._style = style - self.scroll_area = ScrollArea( + self._pie_config = pie_config + + tab_holder = QTabWidget() + + self._local_settings = LocalPieSettings(pie_config=self._pie_config) + tab_holder.addTab(self._local_settings, "Local settings") + self._action_values = ScrollArea( values, self._style, columns) + tab_holder.addTab(self._action_values, "Action values") - layout = QHBoxLayout(self) - layout.addWidget(self.scroll_area) + layout = QVBoxLayout(self) + layout.addWidget(tab_holder) self.setLayout(layout) - self.hide() - def move_to_pie_side(self): offset = self.width()//2 + self._style.widget_radius * 1.05 point = QPoint(round(offset), 0) self.move_center(QCursor().pos() + point) # type: ignore + def show(self): + self._local_settings.refresh() + return super().show() + + def hide(self) -> None: + self._local_settings.apply() + Krita.trigger_action("Reload Shortcut Composer") + return super().hide() + class ScrollArea(QScrollArea): def __init__( diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index e7513a99..d82d254d 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -13,7 +13,7 @@ from api_krita.pyqt import Painter, AnimatedWidget, BaseWidget from composer_utils import Config from .pie_style import PieStyle -from .pie_settings import PieSettings +from .pie_settings import PieSettingsWindow from .label import Label from .label_widget import LabelWidget from .label_widget_utils import create_label_widget @@ -59,7 +59,7 @@ def __init__( style: PieStyle, labels: List[Label], config: PieConfig, - pie_settings: PieSettings, + pie_settings: PieSettingsWindow, parent=None ): AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) From 882ccd0989ef04d4c7c3bea87e919e67f76e3ca8 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 024/125] Refactored edit mode --- shortcut_composer/templates/pie_menu.py | 34 +++++++----- .../templates/pie_menu_utils/label_widget.py | 4 +- .../templates/pie_menu_utils/pie_manager.py | 31 ++++++----- .../templates/pie_menu_utils/pie_settings.py | 1 + .../templates/pie_menu_utils/pie_widget.py | 31 ++++------- .../widget_utils/accept_button.py | 13 ++++- .../pie_menu_utils/widget_utils/edit_mode.py | 55 ++++++++++++------- 7 files changed, 98 insertions(+), 71 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 0f7cddee..e01a4f27 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -15,8 +15,8 @@ PieManager, PieWidget, PieStyle, - Label, -) + Label) +from .pie_menu_utils.widget_utils import EditMode, AcceptButton T = TypeVar('T') @@ -108,34 +108,42 @@ def __init__( unscaled_style = copy(self._style) self._style.set_items(self._labels) - self._pie_settings = PieSettingsWindow( + self._edit_mode = EditMode(self) + self.pie_settings = PieSettingsWindow( style=unscaled_style, values=self._create_all_labels(self._values), pie_config=self._local_config, columns=3) - self._pie_widget = PieWidget( + self.pie_widget = PieWidget( style=self._style, labels=self._labels, - config=self._local_config, - pie_settings=self._pie_settings) - self._pie_manager = PieManager( - widget=self._pie_widget, - pie_settings=self._pie_settings) + config=self._local_config) + self.pie_manager = PieManager( + pie_widget=self.pie_widget, + pie_settings=self.pie_settings) + + self.settings_button = AcceptButton(self._style, self.pie_widget) + self.settings_button.clicked.connect(lambda: self._edit_mode.set(True)) + + self.accept_button = AcceptButton(self._style, self.pie_widget) + self.accept_button.clicked.connect(lambda: self._edit_mode.set(False)) + self.accept_button.move_center(self.pie_widget.center) + self.accept_button.hide() def on_key_press(self) -> None: """Show widget under mouse and start manager which repaints it.""" self._controller.refresh() - self._pie_manager.start() + self.pie_manager.start() super().on_key_press() def on_every_key_release(self) -> None: """Stop the widget. Set selected value if deadzone was reached.""" super().on_every_key_release() - if self._pie_widget.edit_mode: + if self._edit_mode.get(): return - self._pie_manager.stop() - if widget := self._pie_widget.widget_holder.active: + self.pie_manager.stop() + if widget := self.pie_widget.widget_holder.active: self._controller.set_value(widget.label.value) def _create_labels(self, values: List[T]) -> List[Label]: diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget.py index 15d6805f..354bbae3 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget.py @@ -21,6 +21,7 @@ def __init__( ) -> None: super().__init__(parent) self.setGeometry(0, 0, style.icon_radius*2, style.icon_radius*2) + self.draggable = True self.label = label self._style = style @@ -32,8 +33,9 @@ def move_to_label(self) -> None: def mousePressEvent(self, e: QMouseEvent) -> None: """Initiate a drag loop for this Widget, so Widgets can be swapped.""" - if e.buttons() != Qt.LeftButton: + if e.buttons() != Qt.LeftButton or not self.draggable: return + drag = QDrag(self) drag.setMimeData(QMimeData()) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index 77be2697..54cb593e 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -22,40 +22,41 @@ class PieManager: - Asks the widget to repaint when after changing active label. """ - def __init__(self, widget: PieWidget, pie_settings: PieSettingsWindow) -> None: - self._pie = widget + def __init__(self, pie_widget: PieWidget, pie_settings: PieSettingsWindow): + self._pie_widget = pie_widget self._pie_settings = pie_settings - self._holder = self._pie.widget_holder + self._holder = self._pie_widget.widget_holder self._timer = Timer(self._handle_cursor, Config.get_sleep_time()) - self._animator = LabelAnimator(widget) + self._animator = LabelAnimator(pie_widget) self._circle: CirclePoints def start(self) -> None: """Show widget under the mouse and start the mouse tracking loop.""" - self._pie.move_center(QCursor().pos()) + self._pie_widget.move_center(QCursor().pos()) + self._pie_widget.show() + self._pie_settings.move_to_pie_side() - self._pie.show() - self._circle = CirclePoints(self._pie.center_global, 0) + self._circle = CirclePoints(self._pie_widget.center_global, 0) self._timer.start() def stop(self) -> None: """Hide the widget and stop the mouse tracking loop.""" + self._pie_widget.hide() self._timer.stop() - for label in self._pie.labels: + for label in self._pie_widget.labels: label.activation_progress.reset() - self._pie.hide() def _handle_cursor(self) -> None: """Calculate zone of the cursor and mark which child is active.""" # NOTE: The widget can get hidden outside of stop() when key is # released during the drag&drop operation or when user clicked # outside the pie widget. - if not self._pie.isVisible(): + if not self._pie_widget.isVisible(): return self.stop() cursor = QCursor().pos() - if self._circle.distance(cursor) < self._pie.deadzone: + if self._circle.distance(cursor) < self._pie_widget.deadzone: return self._set_active_widget(None) angle = self._circle.angle_from_point(cursor) @@ -75,9 +76,9 @@ class LabelAnimator: Handles the whole widget at once, to prevent unnecessary repaints. """ - def __init__(self, widget: PieWidget) -> None: - self._widget = widget - self._children = widget.widget_holder + def __init__(self, pie_widget: PieWidget) -> None: + self._pie_widget = pie_widget + self._children = pie_widget.widget_holder self._timer = Timer(self._update, Config.get_sleep_time()) def start(self) -> None: @@ -92,7 +93,7 @@ def _update(self) -> None: else: widget.label.activation_progress.down() - self._widget.repaint() + self._pie_widget.repaint() for widget in self._children: if widget.label.activation_progress.value not in (0, 1): return diff --git a/shortcut_composer/templates/pie_menu_utils/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/pie_settings.py index 85432e55..9cb595f3 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_settings.py @@ -100,6 +100,7 @@ def _create_children(self) -> List[LabelWidget]: for label in self.labels: children.append(create_label_widget(label, self._style, self)) children[-1].setFixedSize(diameter, diameter) + children[-1].draggable = True self._scroll_area_layout.append(children[-1]) return children diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index d82d254d..1b4f1f95 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -13,7 +13,6 @@ from api_krita.pyqt import Painter, AnimatedWidget, BaseWidget from composer_utils import Config from .pie_style import PieStyle -from .pie_settings import PieSettingsWindow from .label import Label from .label_widget import LabelWidget from .label_widget_utils import create_label_widget @@ -21,9 +20,7 @@ from .widget_utils import ( WidgetHolder, CirclePoints, - AcceptButton, - PiePainter, - EditMode) + PiePainter) T = TypeVar('T') @@ -52,14 +49,11 @@ class PieWidget(AnimatedWidget, BaseWidget): the LabelHolder are saved in the related configuration. """ - edit_mode = EditMode() - def __init__( self, style: PieStyle, labels: List[Label], config: PieConfig, - pie_settings: PieSettingsWindow, parent=None ): AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) @@ -68,19 +62,15 @@ def __init__( self._style = style self.labels = labels self.config = config - self.pie_settings = pie_settings self._children_widgets: List[LabelWidget] = [] self.widget_holder: WidgetHolder = WidgetHolder() self._last_widget = None + self.is_edit_mode = False self._circle_points = CirclePoints( center=self.center, radius=self._style.pie_radius) - self.accept_button = AcceptButton(self._style, self) - self.accept_button.move_center(self.center) - self.accept_button.clicked.connect(self.hide) - self.setAcceptDrops(True) self.setWindowFlags(( self.windowFlags() | # type: ignore @@ -98,20 +88,13 @@ def deadzone(self) -> float: """Return the deadzone distance.""" return self._style.deadzone_radius - def hide(self): - """Leave the edit mode when the widget is hidden.""" - self.edit_mode = False - super().hide() - def paintEvent(self, event: QPaintEvent) -> None: """Paint the entire widget using the Painter wrapper.""" with Painter(self, event) as painter: - PiePainter(painter, self.labels, self._style, self.edit_mode) + PiePainter(painter, self.labels, self._style, self.is_edit_mode) def dragEnterEvent(self, e: QDragEnterEvent) -> None: """Start edit mode when one of the draggable children gets dragged.""" - self.edit_mode = True - self.repaint() e.accept() def dragMoveEvent(self, e: QDragMoveEvent) -> None: @@ -145,6 +128,10 @@ def dragLeaveEvent(self, e: QDragLeaveEvent) -> None: self._remove_widget(self._last_widget) return super().dragLeaveEvent(e) + def show(self): + self.set_draggable(False) + return super().show() + def _remove_widget(self, widget: LabelWidget): if widget.label in self.labels: self.labels.remove(widget.label) @@ -174,3 +161,7 @@ def _reset_holder(self) -> None: child.label.center = point child.move_to_label() self.widget_holder.add(child) + + def set_draggable(self, draggable: bool): + for widget in self._children_widgets: + widget.draggable = draggable diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/accept_button.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/accept_button.py index f4cffbc3..e9d7d08b 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/accept_button.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/accept_button.py @@ -1,3 +1,5 @@ +from typing import Optional + from PyQt5.QtWidgets import QWidget, QPushButton from PyQt5.QtGui import QColor from PyQt5.QtCore import Qt @@ -10,8 +12,16 @@ class AcceptButton(QPushButton, BaseWidget): """Round button with a tick icon which uses provided PieStyle.""" - def __init__(self, style: PieStyle, parent: QWidget) -> None: + def __init__(self, style: PieStyle, parent: Optional[QWidget] = None): QPushButton.__init__(self, Krita.get_icon("dialog-ok"), "", parent) + if parent is None: + self.setWindowFlags(( + self.windowFlags() | # type: ignore + Qt.Tool | + Qt.FramelessWindowHint | + Qt.NoDropShadowWindowHint)) + self.setAttribute(Qt.WA_TranslucentBackground) + self.setStyleSheet("background: transparent;") self._style = style self.hide() @@ -39,6 +49,7 @@ def __init__(self, style: PieStyle, parent: QWidget) -> None: ] """.replace('[', '{').replace(']', '}') ) + self.show() @staticmethod def _color_to_str(color: QColor) -> str: return f'''rgba( diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py index 7fc548cc..940b90ed 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py @@ -1,45 +1,58 @@ from typing import TYPE_CHECKING + if TYPE_CHECKING: - from ..pie_widget import PieWidget + from ...pie_menu import PieMenu class EditMode: - """ - Descriptor that handles the edit mode of PieWidget. - - When red from, it returns a bool telling whether the Pie is in edit mode. - When mode is changed, changes the Pie's components to reflect that. - Whe edit mode is turned off, saves the current values to settings. - """ - - def __init__(self) -> None: + def __init__(self, obj: 'PieMenu') -> None: self._edit_mode = False + self._obj = obj - def __get__(self, *_) -> bool: + def get(self) -> bool: """Return whether the Pie is in edit mode""" return self._edit_mode - def __set__(self, obj: 'PieWidget', mode_to_set: bool) -> None: + def set(self, mode_to_set: bool) -> None: """Update the mode and change Pie's content accordingly.""" if not mode_to_set and self._edit_mode: - self._write_settings(obj) + self._write_settings() if not self._edit_mode ^ mode_to_set: return if mode_to_set: - obj.accept_button.show() - obj.pie_settings.show() + self.set_edit_mode_true() else: - obj.accept_button.hide() - obj.pie_settings.hide() + self.set_edit_mode_false() self._edit_mode = mode_to_set - def _write_settings(self, obj: 'PieWidget') -> None: + def set_edit_mode_true(self): + self._obj.pie_widget.set_draggable(True) + self._obj.pie_widget.is_edit_mode = True + self._obj.pie_widget.repaint() + self._obj.pie_settings.show() + self._obj.accept_button.show() + self._obj.settings_button.hide() + + def set_edit_mode_false(self): + self._obj.pie_widget.hide() + self._obj.pie_widget.set_draggable(False) + self._obj.pie_widget.is_edit_mode = False + self._obj.pie_settings.hide() + self._obj.accept_button.hide() + self._obj.settings_button.show() + + def swap_mode(self): + self.set(not self._edit_mode) + + def _write_settings(self) -> None: """If values were not hardcoded, but from config, write them back.""" - if not obj.labels or obj.config is None: + widget = self._obj.pie_widget + + if not widget.labels or widget.config is None: return - values = [widget.label.value for widget in obj.widget_holder] - obj.config.order.write(values) + values = [widget.label.value for widget in widget.widget_holder] + widget.config.order.write(values) From 7f0399dd56f634bf53dacbcd116f2085aedd503c Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 025/125] Mostly refactored configs --- shortcut_composer/composer_utils/__init__.py | 9 +- .../composer_utils/config/__init__.py | 7 + .../composer_utils/config/config_ui.py | 125 ++++++++++++++++++ .../{internal_config.py => config/fields.py} | 76 +++++++---- .../{config.py => config/global_config.py} | 30 +++-- .../composer_utils/layouts/__init__.py | 4 +- .../layouts/combo_boxes_layout.py | 66 --------- .../layouts/spin_boxes_layout.py | 123 ----------------- .../composer_utils/settings_dialog.py | 21 ++- .../composer_utils/tabs/__init__.py | 3 +- .../composer_utils/tabs/action_values_tab.py | 2 +- .../tabs/general_settings_tab.py | 44 ------ .../composer_utils/utils/action_values.py | 2 +- shortcut_composer/data_components/slider.py | 4 +- .../input_adapter/complex_action.py | 2 +- .../mouse_tracker_utils/mouse_interpreter.py | 2 +- .../mouse_tracker_utils/slider_handler.py | 2 +- .../templates/pie_menu_utils/label.py | 2 +- .../templates/pie_menu_utils/pie_config.py | 23 ++-- .../pie_menu_utils/pie_local_settings.py | 125 ------------------ .../templates/pie_menu_utils/pie_manager.py | 2 +- .../templates/pie_menu_utils/pie_settings.py | 11 +- .../templates/pie_menu_utils/pie_style.py | 2 +- .../templates/pie_menu_utils/pie_widget.py | 2 +- 24 files changed, 249 insertions(+), 440 deletions(-) create mode 100644 shortcut_composer/composer_utils/config/__init__.py create mode 100644 shortcut_composer/composer_utils/config/config_ui.py rename shortcut_composer/composer_utils/{internal_config.py => config/fields.py} (55%) rename shortcut_composer/composer_utils/{config.py => config/global_config.py} (56%) delete mode 100644 shortcut_composer/composer_utils/layouts/combo_boxes_layout.py delete mode 100644 shortcut_composer/composer_utils/layouts/spin_boxes_layout.py delete mode 100644 shortcut_composer/composer_utils/tabs/general_settings_tab.py delete mode 100644 shortcut_composer/templates/pie_menu_utils/pie_local_settings.py diff --git a/shortcut_composer/composer_utils/__init__.py b/shortcut_composer/composer_utils/__init__.py index dd649501..e2060204 100644 --- a/shortcut_composer/composer_utils/__init__.py +++ b/shortcut_composer/composer_utils/__init__.py @@ -4,12 +4,5 @@ """Utilities specific for this plugin. Not directly reusable elsewhere.""" from .settings_dialog import SettingsDialog -from .config import Config -from .internal_config import ( - ConfigBase, - BuiltinConfig, - EnumListConfig, - BuiltinListConfig) -__all__ = ["SettingsDialog", "Config", "ConfigBase", - "BuiltinConfig", "EnumListConfig", "BuiltinListConfig"] +__all__ = ["SettingsDialog"] diff --git a/shortcut_composer/composer_utils/config/__init__.py b/shortcut_composer/composer_utils/config/__init__.py new file mode 100644 index 00000000..40d6fb20 --- /dev/null +++ b/shortcut_composer/composer_utils/config/__init__.py @@ -0,0 +1,7 @@ +from .fields import ( + FieldBase, + ImmutableField, + ImmutablesListField, + EnumsListField) +from .global_config import Config +from .config_ui import FieldUiWrapper, ConfigFormLayout, ConfigFormWidget diff --git a/shortcut_composer/composer_utils/config/config_ui.py b/shortcut_composer/composer_utils/config/config_ui.py new file mode 100644 index 00000000..4a9c04a3 --- /dev/null +++ b/shortcut_composer/composer_utils/config/config_ui.py @@ -0,0 +1,125 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import Dict, List, Union, Optional +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import ( + QDoubleSpinBox, + QFormLayout, + QVBoxLayout, + QHBoxLayout, + QSplitter, + QComboBox, + QSpinBox, + QWidget, + QLabel) + +from api_krita.wrappers import Database +from ..config.fields import ImmutableField + + +class FieldUiWrapper: + def __init__(self, ui_widget: Union[QSpinBox, QDoubleSpinBox, QComboBox]): + self.ui_widget = ui_widget + + def read(self): + if isinstance(self.ui_widget, (QSpinBox, QDoubleSpinBox)): + return self.ui_widget.value() + return self.ui_widget.currentText() + + def write(self, value): + if isinstance(self.ui_widget, (QSpinBox, QDoubleSpinBox)): + return self.ui_widget.setValue(value) + return self.ui_widget.setCurrentText(value) + + +class ConfigFormLayout(QFormLayout): + """Dialog zone consisting of spin boxes.""" + + def __init__(self, elements: List[Union[str, list, dict]]) -> None: + super().__init__() + self._forms: Dict[ImmutableField, FieldUiWrapper] = {} + + for element in elements: + if isinstance(element, str): + self._add_label(element) + elif isinstance(element, list): + self._add_row(*element) + elif isinstance(element, dict): + self._add_row(**element) + else: + raise TypeError("Unsupported arguments.") + + def _add_row( + self, + config: ImmutableField, + step: Optional[float] = None, + max_value: Optional[float] = None) -> None: + field_ui = self.create_field_ui(config, step, max_value) + self._forms[config] = field_ui + self.addRow(config.name, field_ui.ui_widget) + + def _add_label(self, text: str): + label = QLabel(text) + label.setAlignment(Qt.AlignCenter) + self.addRow(QSplitter(Qt.Horizontal)) + self.addRow(label) + + def refresh(self) -> None: + """Read values from krita config and apply them to stored boxes.""" + for config, form in self._forms.items(): + if config.type != str: + form.write(config.read()) # type: ignore + else: + # HACK: can't assume the combobox is tag chooser + with Database() as database: + combo_box = form.ui_widget + combo_box.clear() + combo_box.addItems( + sorted(database.get_brush_tags(), key=str.lower)) + combo_box.setCurrentText(config.read()) + + def apply(self) -> None: + """Write values from stored spin boxes to krita config file.""" + for config, form in self._forms.items(): + config.write(form.read()) + + @staticmethod + def create_field_ui( + config: ImmutableField, + step: Optional[float] = None, + max_value: Optional[float] = None + ): + if config.type in (float, int): + ui = QSpinBox() if config.type is int else QDoubleSpinBox() + ui.setObjectName(config.name) + ui.setMinimum(0) + ui.setMaximum(max_value) # type: ignore + ui.setSingleStep(step) # type: ignore + elif config.type is str: + ui = QComboBox() + ui.setObjectName(config.name) + else: + raise TypeError(f"{config.type} not supported.") + + wrapper = FieldUiWrapper(ui) + wrapper.write(config.read()) + return wrapper + + +class ConfigFormWidget(QWidget): + def __init__(self, elements: List[Union[str, list, dict]]) -> None: + super().__init__() + + self._layout = ConfigFormLayout(elements) + stretched = QHBoxLayout() + stretched.addStretch() + stretched.addLayout(self._layout) + stretched.addStretch() + self.setLayout(stretched) + + def apply(self) -> None: + self._layout.apply() + + def refresh(self) -> None: + self._layout.refresh() diff --git a/shortcut_composer/composer_utils/internal_config.py b/shortcut_composer/composer_utils/config/fields.py similarity index 55% rename from shortcut_composer/composer_utils/internal_config.py rename to shortcut_composer/composer_utils/config/fields.py index 1978fc2a..b78661f5 100644 --- a/shortcut_composer/composer_utils/internal_config.py +++ b/shortcut_composer/composer_utils/config/fields.py @@ -1,7 +1,8 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import TypeVar, Generic, List +from typing import TypeVar, Generic, Optional, List, Type +from abc import ABC, abstractmethod from enum import Enum from api_krita import Krita @@ -9,85 +10,106 @@ T = TypeVar('T') -class ConfigBase(Generic[T]): +class FieldBase(Generic[T], ABC): def __init__(self, name: str, default: T) -> None: self.name = name self.default = default - def _read_raw(self) -> str: - return Krita.read_setting( + def _read_raw(self) -> Optional[str]: + red_value = Krita.read_setting( group="ShortcutComposer", name=self.name, - default="",) + default="Not stored") + return None if red_value == "Not stored" else red_value def reset_default(self) -> None: self.write(self.default) + def _is_write_redundant(self, value: T): + current_value = self._read_raw() + return current_value is None and value == self.default + + @abstractmethod def read(self) -> T: ... + + @abstractmethod def write(self, value: T): ... + @property + def type(self) -> Type[T]: ... + -class BuiltinConfig(ConfigBase, Generic[T]): - "For str, int and float" +class ImmutableField(FieldBase, Generic[T]): + "For str, int, float." def __init__(self, name: str, default: T) -> None: super().__init__(name, default) def read(self) -> T: raw = self._read_raw() - - if raw == "": + if raw is None: return self.default - - return type(self.default)(raw) + return self.type(raw) def write(self, value: T) -> None: - """Write given value to krita config file.""" + if self._is_write_redundant(value): + return + Krita.write_setting( group="ShortcutComposer", name=self.name, value=value) + @property + def type(self): + return type(self.default) + -class EnumListConfig(ConfigBase): +class EnumsListField(FieldBase): def read(self) -> List[Enum]: raw = self._read_raw() - if raw == "": + if raw is None: return self.default - element = self.default[0] values_list = raw.split("\t") - - enum_type = type(element) - return [enum_type[value] for value in values_list] + return [self.type[value] for value in values_list] def write(self, value: List[Enum]) -> None: - """Write given value to krita config file.""" - to_write = "\t".join([enum.name for enum in value]) + if self._is_write_redundant(value): + return + to_write = "\t".join([enum.name for enum in value]) Krita.write_setting( group="ShortcutComposer", name=self.name, value=to_write) + @property + def type(self): + element = self.default[0] + return type(element) + -class BuiltinListConfig(ConfigBase, Generic[T]): +class ImmutablesListField(FieldBase, Generic[T]): def __init__(self, name: str, default: List[T]) -> None: super().__init__(name, default) def read(self) -> List[T]: raw = self._read_raw() - - if raw == "": + if raw is None: return self.default - - parse_type = type(self.default[0]) - return [parse_type(item) for item in raw.split("\t")] + return [self.type(item) for item in raw.split("\t")] def write(self, value: List[T]) -> None: - """Write given value to krita config file.""" + if self._is_write_redundant(value): + return + Krita.write_setting( group="ShortcutComposer", name=self.name, value="\t".join(map(str, value))) + + @property + def type(self): + return type(self.default[0]) diff --git a/shortcut_composer/composer_utils/config.py b/shortcut_composer/composer_utils/config/global_config.py similarity index 56% rename from shortcut_composer/composer_utils/config.py rename to shortcut_composer/composer_utils/config/global_config.py index 9669faab..b3ba7fa4 100644 --- a/shortcut_composer/composer_utils/config.py +++ b/shortcut_composer/composer_utils/config/global_config.py @@ -4,7 +4,7 @@ from typing import TypeVar from enum import Enum -from .internal_config import ConfigBase, BuiltinConfig +from .fields import FieldBase, ImmutableField T = TypeVar('T', bound=Enum) @@ -26,24 +26,26 @@ class Config: EnumConfigValues. """ - SHORT_VS_LONG_PRESS_TIME = BuiltinConfig("Short vs long press time", 0.3) - TRACKER_SENSITIVITY_SCALE = BuiltinConfig("Tracker sensitivity scale", 1.0) - TRACKER_DEADZONE = BuiltinConfig("Tracker deadzone", 0) - FPS_LIMIT = BuiltinConfig("FPS limit", 60) - PIE_GLOBAL_SCALE = BuiltinConfig("Pie global scale", 1.0) - PIE_ICON_GLOBAL_SCALE = BuiltinConfig("Pie icon global scale", 1.0) - PIE_DEADZONE_GLOBAL_SCALE = BuiltinConfig("Pie deadzone global scale", 1.0) - PIE_ANIMATION_TIME = BuiltinConfig("Pie animation time", 0.2) - - TAG_RED = BuiltinConfig("Pick brush presets (red)", "★ My Favorites") - TAG_GREEN = BuiltinConfig("Pick brush presets (green)", "RGBA") - TAG_BLUE = BuiltinConfig("Pick brush presets (blue)", "Erasers") + SHORT_VS_LONG_PRESS_TIME = ImmutableField("Short vs long press time", 0.3) + TRACKER_SENSITIVITY_SCALE = ImmutableField( + "Tracker sensitivity scale", 1.0) + TRACKER_DEADZONE = ImmutableField("Tracker deadzone", 0) + FPS_LIMIT = ImmutableField("FPS limit", 60) + PIE_GLOBAL_SCALE = ImmutableField("Pie global scale", 1.0) + PIE_ICON_GLOBAL_SCALE = ImmutableField("Pie icon global scale", 1.0) + PIE_DEADZONE_GLOBAL_SCALE = ImmutableField( + "Pie deadzone global scale", 1.0) + PIE_ANIMATION_TIME = ImmutableField("Pie animation time", 0.2) + + TAG_RED = ImmutableField("Pick brush presets (red)", "★ My Favorites") + TAG_GREEN = ImmutableField("Pick brush presets (green)", "RGBA") + TAG_BLUE = ImmutableField("Pick brush presets (blue)", "Erasers") @classmethod def reset_defaults(cls) -> None: """Reset all config files.""" for config_field in cls.__dict__.values(): - if isinstance(config_field, ConfigBase): + if isinstance(config_field, FieldBase): config_field.reset_default() @classmethod diff --git a/shortcut_composer/composer_utils/layouts/__init__.py b/shortcut_composer/composer_utils/layouts/__init__.py index 9a5165a9..a7cbac1e 100644 --- a/shortcut_composer/composer_utils/layouts/__init__.py +++ b/shortcut_composer/composer_utils/layouts/__init__.py @@ -3,8 +3,6 @@ """Layouts to put inside the settings dialog.""" -from .combo_boxes_layout import ComboBoxesLayout -from .spin_boxes_layout import SpinBoxesLayout from .buttons_layout import ButtonsLayout -__all__ = ["ComboBoxesLayout", "SpinBoxesLayout", "ButtonsLayout"] +__all__ = ["ButtonsLayout"] diff --git a/shortcut_composer/composer_utils/layouts/combo_boxes_layout.py b/shortcut_composer/composer_utils/layouts/combo_boxes_layout.py deleted file mode 100644 index 771804e1..00000000 --- a/shortcut_composer/composer_utils/layouts/combo_boxes_layout.py +++ /dev/null @@ -1,66 +0,0 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus -# SPDX-License-Identifier: GPL-3.0-or-later - -from typing import Dict -from itertools import count -from PyQt5.QtWidgets import ( - QGridLayout, - QComboBox, - QLabel, -) -from PyQt5.QtCore import Qt - -from api_krita.wrappers import Database -from ..internal_config import BuiltinConfig -from ..config import Config - - -class ComboBoxesLayout(QGridLayout): - """Dialog zone consisting of combo boxes.""" - - def __init__(self) -> None: - super().__init__() - self.setAlignment(Qt.AlignTop) - self._combo_boxes: Dict[BuiltinConfig, QComboBox] = {} - self._row_counter = count() - - self._add_label("Preset pie-menus mapping") - self._add_row(Config.TAG_RED) - self._add_row(Config.TAG_GREEN) - self._add_row(Config.TAG_BLUE) - - def _add_row(self, config: BuiltinConfig) -> None: - """Add a combobox to the layout along with its description.""" - row_id = next(self._row_counter) - label = QLabel(config.name) - label.setFixedWidth(100) - self.addWidget(label, row_id, 0) - self.addWidget(self._create_combobox(config), row_id, 1) - - def _add_label(self, text: str): - row_id = next(self._row_counter) - label = QLabel(text) - label.setAlignment(Qt.AlignCenter) - self.addWidget(label, row_id, 0, 1, 2) - - def _create_combobox(self, config: BuiltinConfig) -> QComboBox: - """Store and return combobox that represents given config field.""" - combo_box = QComboBox() - combo_box.setObjectName(config.name) - self._combo_boxes[config] = combo_box - return combo_box - - def refresh(self) -> None: - """Read list of tags and set it to all stored comboboxes.""" - with Database() as database: - tags = database.get_brush_tags() - - for config, combo_box in self._combo_boxes.items(): - combo_box.clear() - combo_box.addItems(sorted(tags, key=str.lower)) - combo_box.setCurrentText(config.read()) - - def apply(self) -> None: - """Write values from all stored comboboxes to krita config file.""" - for config, combo in self._combo_boxes.items(): - config.write(combo.currentText()) diff --git a/shortcut_composer/composer_utils/layouts/spin_boxes_layout.py b/shortcut_composer/composer_utils/layouts/spin_boxes_layout.py deleted file mode 100644 index d4fd9ce4..00000000 --- a/shortcut_composer/composer_utils/layouts/spin_boxes_layout.py +++ /dev/null @@ -1,123 +0,0 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus -# SPDX-License-Identifier: GPL-3.0-or-later - -from typing import Dict, Union -from dataclasses import dataclass -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import ( - QDoubleSpinBox, - QFormLayout, - QSplitter, - QSpinBox, - QLabel, -) - -from ..internal_config import BuiltinConfig -from ..config import Config - -SpinBox = Union[QSpinBox, QDoubleSpinBox] - - -class SpinBoxesLayout(QFormLayout): - """Dialog zone consisting of spin boxes.""" - - @dataclass - class ConfigParams: - """Adds spinbox parametrization to the config field.""" - config: BuiltinConfig - step: float - max_value: float - is_int: bool - - def __init__(self) -> None: - super().__init__() - self._forms: Dict[BuiltinConfig, SpinBox] = {} - - self._add_label("Common settings") - - self._add_row(self.ConfigParams( - Config.SHORT_VS_LONG_PRESS_TIME, - step=0.05, - max_value=4, - is_int=False)) - - self._add_row(self.ConfigParams( - Config.FPS_LIMIT, - step=5, - max_value=300, - is_int=True)) - - self._add_label("Cursor trackers") - - self._add_row(self.ConfigParams( - Config.TRACKER_SENSITIVITY_SCALE, - step=0.05, - max_value=4, - is_int=False)) - - self._add_row(self.ConfigParams( - Config.TRACKER_DEADZONE, - step=1, - max_value=200, - is_int=True)) - - self._add_label("Pie menus display") - - self._add_row(self.ConfigParams( - Config.PIE_GLOBAL_SCALE, - step=0.05, - max_value=4, - is_int=False)) - - self._add_row(self.ConfigParams( - Config.PIE_ICON_GLOBAL_SCALE, - step=0.05, - max_value=4, - is_int=False)) - - self._add_row(self.ConfigParams( - Config.PIE_DEADZONE_GLOBAL_SCALE, - step=0.05, - max_value=4, - is_int=False)) - - self._add_row(self.ConfigParams( - Config.PIE_ANIMATION_TIME, - step=0.01, - max_value=1, - is_int=False)) - - def _add_row(self, config_params: ConfigParams) -> None: - """Add a spin box to the layout along with its description.""" - self.addRow( - config_params.config.name, - self._create_form(config_params) - ) - - def _add_label(self, text: str): - label = QLabel(text) - label.setAlignment(Qt.AlignCenter) - self.addRow(QSplitter(Qt.Horizontal)) - self.addRow(label) - - def _create_form(self, config_params: ConfigParams) -> SpinBox: - """Store and return new spin box for required type (int or float).""" - form = QSpinBox() if config_params.is_int else QDoubleSpinBox() - form.setObjectName(config_params.config.name) - form.setMinimum(0) - form.setMaximum(config_params.max_value) # type: ignore - form.setSingleStep(config_params.step) # type: ignore - - self._forms[config_params.config] = form - return form - - def refresh(self) -> None: - """Read values from krita config and apply them to stored boxes.""" - for config, form in self._forms.items(): - form.setValue(config.read()) # type: ignore - - def apply(self) -> None: - """Write values from stored spin boxes to krita config file.""" - for config, form in self._forms.items(): - config.write(form.value()) - diff --git a/shortcut_composer/composer_utils/settings_dialog.py b/shortcut_composer/composer_utils/settings_dialog.py index 9676d5c0..4dd2f6d9 100644 --- a/shortcut_composer/composer_utils/settings_dialog.py +++ b/shortcut_composer/composer_utils/settings_dialog.py @@ -10,9 +10,8 @@ from PyQt5.QtGui import QCursor from api_krita import Krita -from .config import Config +from .config import Config, ConfigFormWidget from .layouts import ButtonsLayout -from .tabs import GeneralSettingsTab class SettingsDialog(QDialog): @@ -25,7 +24,23 @@ def __init__(self) -> None: self.setWindowTitle("Configure Shortcut Composer") self._tab_dict = { - "General": GeneralSettingsTab(), + "General": ConfigFormWidget([ + "Preset pie-menus mapping", + [Config.TAG_RED], + [Config.TAG_GREEN], + [Config.TAG_BLUE], + "Common settings", + [Config.SHORT_VS_LONG_PRESS_TIME, 0.05, 4], + [Config.FPS_LIMIT, 5, 500], + "Cursor trackers", + [Config.TRACKER_SENSITIVITY_SCALE, 0.05, 4], + [Config.TRACKER_DEADZONE, 1, 200], + "Pie menus display", + [Config.PIE_GLOBAL_SCALE, 0.05, 4], + [Config.PIE_ICON_GLOBAL_SCALE, 0.05, 4], + [Config.PIE_DEADZONE_GLOBAL_SCALE, 0.05, 4], + [Config.PIE_ANIMATION_TIME, 0.01, 1], + ]) } tab_holder = QTabWidget() for name, tab in self._tab_dict.items(): diff --git a/shortcut_composer/composer_utils/tabs/__init__.py b/shortcut_composer/composer_utils/tabs/__init__.py index 4e7a0132..4eaff5c7 100644 --- a/shortcut_composer/composer_utils/tabs/__init__.py +++ b/shortcut_composer/composer_utils/tabs/__init__.py @@ -1,8 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from .general_settings_tab import GeneralSettingsTab from .action_values_tab import ActionValuesTab -__all__ = ["GeneralSettingsTab", "ActionValuesTab"] +__all__ = ["ActionValuesTab"] diff --git a/shortcut_composer/composer_utils/tabs/action_values_tab.py b/shortcut_composer/composer_utils/tabs/action_values_tab.py index 87578597..26a8792e 100644 --- a/shortcut_composer/composer_utils/tabs/action_values_tab.py +++ b/shortcut_composer/composer_utils/tabs/action_values_tab.py @@ -9,7 +9,7 @@ from api_krita.enums import BlendingMode, Tool, TransformMode from ..utils import ActionValues -from ..config import Config +from ..config.global_config import Config class ActionValuesTab(QWidget): diff --git a/shortcut_composer/composer_utils/tabs/general_settings_tab.py b/shortcut_composer/composer_utils/tabs/general_settings_tab.py deleted file mode 100644 index b326b547..00000000 --- a/shortcut_composer/composer_utils/tabs/general_settings_tab.py +++ /dev/null @@ -1,44 +0,0 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus -# SPDX-License-Identifier: GPL-3.0-or-later - -from PyQt5.QtWidgets import ( - QVBoxLayout, - QHBoxLayout, - QWidget, -) - -from ..layouts import ( - ComboBoxesLayout, - SpinBoxesLayout, -) - - -class GeneralSettingsTab(QWidget): - """Dialog which allows to change global settings of the plugin.""" - - def __init__(self) -> None: - super().__init__() - - self._layouts_dict = { - "ComboBoxes": ComboBoxesLayout(), - "SpinBoxes": SpinBoxesLayout(), - } - layout = QVBoxLayout() - for layout_part in self._layouts_dict.values(): - layout.addLayout(layout_part) - - stretched = QHBoxLayout() - stretched.addStretch() - stretched.addLayout(layout) - stretched.addStretch() - self.setLayout(stretched) - - def apply(self) -> None: - """Ask all dialog zones to apply themselves.""" - for layout in self._layouts_dict.values(): - layout.apply() - - def refresh(self) -> None: - """Ask all dialog zones to refresh themselves. """ - for layout in self._layouts_dict.values(): - layout.refresh() diff --git a/shortcut_composer/composer_utils/utils/action_values.py b/shortcut_composer/composer_utils/utils/action_values.py index 3cc1d210..7247d0e4 100644 --- a/shortcut_composer/composer_utils/utils/action_values.py +++ b/shortcut_composer/composer_utils/utils/action_values.py @@ -14,7 +14,7 @@ ) from api_krita import Krita -from ..config import Config +from ..config.global_config import Config from .value_list import ValueList diff --git a/shortcut_composer/data_components/slider.py b/shortcut_composer/data_components/slider.py index 6daf1e89..9ea653bc 100644 --- a/shortcut_composer/data_components/slider.py +++ b/shortcut_composer/data_components/slider.py @@ -3,7 +3,7 @@ from typing import Any, List, Union, Optional -from composer_utils import Config +from composer_utils.config import Config, FieldBase from core_components import Controller from .range import Range @@ -76,7 +76,7 @@ def __init__( self.deadzone = self._read(deadzone, Config.TRACKER_DEADZONE) - def _read(self, passed: Optional[int], field: Config) -> int: + def _read(self, passed: Optional[int], field: FieldBase) -> int: if passed is not None: return passed return field.read() diff --git a/shortcut_composer/input_adapter/complex_action.py b/shortcut_composer/input_adapter/complex_action.py index 5cbf75f5..01c51d3c 100644 --- a/shortcut_composer/input_adapter/complex_action.py +++ b/shortcut_composer/input_adapter/complex_action.py @@ -3,7 +3,7 @@ from typing import List, Optional -from composer_utils import Config +from composer_utils.config import Config from core_components import InstructionHolder, Instruction diff --git a/shortcut_composer/templates/mouse_tracker_utils/mouse_interpreter.py b/shortcut_composer/templates/mouse_tracker_utils/mouse_interpreter.py index 38a3aefa..d9870263 100644 --- a/shortcut_composer/templates/mouse_tracker_utils/mouse_interpreter.py +++ b/shortcut_composer/templates/mouse_tracker_utils/mouse_interpreter.py @@ -33,7 +33,7 @@ class MouseInterpreter: start_value: Interpreted min: Interpreted max: Interpreted - pixels_in_unit: int + pixels_in_unit: float def interpret(self, mouse: MouseInput) -> Interpreted: """Return value corresponding to the `mouse`.""" diff --git a/shortcut_composer/templates/mouse_tracker_utils/slider_handler.py b/shortcut_composer/templates/mouse_tracker_utils/slider_handler.py index 89c65bef..d91e3233 100644 --- a/shortcut_composer/templates/mouse_tracker_utils/slider_handler.py +++ b/shortcut_composer/templates/mouse_tracker_utils/slider_handler.py @@ -5,7 +5,7 @@ from api_krita import Krita from api_krita.pyqt import Timer -from composer_utils import Config +from composer_utils.config import Config from data_components import Slider, Range from .new_types import MouseInput, Interpreted from .mouse_interpreter import MouseInterpreter diff --git a/shortcut_composer/templates/pie_menu_utils/label.py b/shortcut_composer/templates/pie_menu_utils/label.py index a19613a7..52e900b6 100644 --- a/shortcut_composer/templates/pie_menu_utils/label.py +++ b/shortcut_composer/templates/pie_menu_utils/label.py @@ -11,7 +11,7 @@ QIcon, ) -from composer_utils import Config +from composer_utils.config import Config @dataclass diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 68555392..36788cdd 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -1,11 +1,11 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from composer_utils import ( - EnumListConfig, - BuiltinListConfig, - BuiltinConfig, - ConfigBase) +from composer_utils.config import ( + EnumsListField, + ImmutablesListField, + ImmutableField, + FieldBase) from data_components import Tag @@ -23,7 +23,7 @@ def create_local_config( class PieConfig: values: list - order: ConfigBase + order: FieldBase def __init__( self, @@ -34,10 +34,10 @@ def __init__( ) -> None: self.name = name self._default_values = values - self.pie_radius_scale = BuiltinConfig( + self.pie_radius_scale = ImmutableField( f"{self.name} pie scale", pie_radius_scale) - self.icon_radius_scale = BuiltinConfig( + self.icon_radius_scale = ImmutableField( f"{self.name} icon scale", icon_radius_scale) @@ -46,8 +46,9 @@ class PresetPieConfig(PieConfig): def __init__(self, *args): super().__init__(*args) self._default_values: Tag - self.tag_name = BuiltinConfig(self.name, self._default_values.tag_name) - self.order = BuiltinListConfig(f"{self.name} values", [""]) + self.tag_name = ImmutableField( + self.name, self._default_values.tag_name) + self.order = ImmutablesListField(f"{self.name} values", [""]) @property def values(self): @@ -62,7 +63,7 @@ def values(self): class EnumPieConfig(PieConfig): def __init__(self, *args): super().__init__(*args) - self.order = EnumListConfig( + self.order = EnumsListField( f"{self.name} values", self._default_values) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_local_settings.py b/shortcut_composer/templates/pie_menu_utils/pie_local_settings.py deleted file mode 100644 index d1a53fa5..00000000 --- a/shortcut_composer/templates/pie_menu_utils/pie_local_settings.py +++ /dev/null @@ -1,125 +0,0 @@ -from typing import Dict -from PyQt5.QtWidgets import ( - QDoubleSpinBox, - QFormLayout, - QVBoxLayout, - QHBoxLayout, - QSplitter, - QSpinBox, - QLabel, -) -from PyQt5.QtCore import Qt -from dataclasses import dataclass -from typing import Dict, Union -from PyQt5.QtWidgets import QWidget - -from typing import Dict, Union -from dataclasses import dataclass -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import ( - QDoubleSpinBox, - QFormLayout, - QSplitter, - QSpinBox, - QLabel, -) - -from .pie_config import PieConfig -from composer_utils import ConfigBase - -SpinBox = Union[QSpinBox, QDoubleSpinBox] - - -class LocalPieSettings(QWidget): - """Dialog which allows to change global settings of the plugin.""" - - def __init__(self, pie_config: PieConfig) -> None: - super().__init__() - self._pie_config = pie_config - - self._layouts_dict = { - "SpinBoxes": SpinBoxesLayout(pie_config), - } - layout = QVBoxLayout() - for layout_part in self._layouts_dict.values(): - layout.addLayout(layout_part) - - stretched = QHBoxLayout() - stretched.addStretch() - stretched.addLayout(layout) - stretched.addStretch() - self.setLayout(stretched) - - def apply(self) -> None: - """Ask all dialog zones to apply themselves.""" - for layout in self._layouts_dict.values(): - layout.apply() - - def refresh(self) -> None: - """Ask all dialog zones to refresh themselves. """ - for layout in self._layouts_dict.values(): - layout.refresh() - - -class SpinBoxesLayout(QFormLayout): - """Dialog zone consisting of spin boxes.""" - - @dataclass - class ConfigParams: - """Adds spinbox parametrization to the config field.""" - config: ConfigBase - step: float - max_value: float - is_int: bool - - def __init__(self, pie_config: PieConfig) -> None: - super().__init__() - self._forms: Dict[ConfigBase, SpinBox] = {} - - self._add_label("Bla") - - self._add_row(self.ConfigParams( - pie_config.pie_radius_scale, - step=0.05, - max_value=4, - is_int=False)) - self._add_row(self.ConfigParams( - pie_config.icon_radius_scale, - step=0.05, - max_value=4, - is_int=False)) - - self._add_label("Ble") - - def _add_row(self, config_params: ConfigParams) -> None: - """Add a spin box to the layout along with its description.""" - self.addRow( - config_params.config.name, - self._create_form(config_params)) - - def _add_label(self, text: str): - label = QLabel(text) - label.setAlignment(Qt.AlignCenter) - self.addRow(QSplitter(Qt.Horizontal)) - self.addRow(label) - - def _create_form(self, config_params: ConfigParams) -> SpinBox: - """Store and return new spin box for required type (int or float).""" - form = QSpinBox() if config_params.is_int else QDoubleSpinBox() - form.setObjectName(config_params.config.name) - form.setMinimum(0) - form.setMaximum(config_params.max_value) # type: ignore - form.setSingleStep(config_params.step) # type: ignore - - self._forms[config_params.config] = form - return form - - def refresh(self) -> None: - """Read values from krita config and apply them to stored boxes.""" - for config, form in self._forms.items(): - form.setValue(config.read()) # type: ignore - - def apply(self) -> None: - """Write values from stored spin boxes to krita config file.""" - for config, form in self._forms.items(): - config.write(form.value()) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index 54cb593e..6253b82e 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -6,7 +6,7 @@ from PyQt5.QtGui import QCursor from api_krita.pyqt import Timer -from composer_utils import Config +from composer_utils.config import Config from .pie_settings import PieSettingsWindow from .pie_widget import PieWidget from .label_widget import LabelWidget diff --git a/shortcut_composer/templates/pie_menu_utils/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/pie_settings.py index 9cb595f3..cfb75b6b 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_settings.py @@ -11,13 +11,12 @@ ) from api_krita import Krita from api_krita.pyqt import AnimatedWidget, BaseWidget -from composer_utils import Config +from composer_utils.config import Config, ConfigFormWidget from .label import Label from .label_widget import LabelWidget from .label_widget_utils import create_label_widget from .pie_style import PieStyle from .pie_config import PieConfig -from .pie_local_settings import LocalPieSettings class PieSettingsWindow(AnimatedWidget, BaseWidget): @@ -42,7 +41,13 @@ def __init__( tab_holder = QTabWidget() - self._local_settings = LocalPieSettings(pie_config=self._pie_config) + self._local_settings = ConfigFormWidget([ + "Bla", + [pie_config.pie_radius_scale, 0.05, 4], + [pie_config.icon_radius_scale, 0.05, 4], + [Config.TAG_BLUE], + "Ble", + ]) tab_holder.addTab(self._local_settings, "Local settings") self._action_values = ScrollArea( values, diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index d5b48cff..50403a88 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -10,7 +10,7 @@ from PyQt5.QtGui import QColor from api_krita import Krita -from composer_utils import Config +from composer_utils.config import Config @dataclass diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index 1b4f1f95..4142d39c 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -11,7 +11,7 @@ QPaintEvent) from api_krita.pyqt import Painter, AnimatedWidget, BaseWidget -from composer_utils import Config +from composer_utils.config import Config from .pie_style import PieStyle from .label import Label from .label_widget import LabelWidget From 5bdb472c5b93630b93e04e4105d8609a344fadf8 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 026/125] Config based ui wrappers --- .../composer_utils/config/__init__.py | 27 ++- .../composer_utils/config/config_ui.py | 158 +++++++++++------- .../composer_utils/settings_dialog.py | 53 +++--- .../templates/pie_menu_utils/pie_settings.py | 24 ++- 4 files changed, 167 insertions(+), 95 deletions(-) diff --git a/shortcut_composer/composer_utils/config/__init__.py b/shortcut_composer/composer_utils/config/__init__.py index 40d6fb20..e6746165 100644 --- a/shortcut_composer/composer_utils/config/__init__.py +++ b/shortcut_composer/composer_utils/config/__init__.py @@ -1,7 +1,24 @@ +from .global_config import Config from .fields import ( - FieldBase, - ImmutableField, ImmutablesListField, - EnumsListField) -from .global_config import Config -from .config_ui import FieldUiWrapper, ConfigFormLayout, ConfigFormWidget + ImmutableField, + EnumsListField, + FieldBase) +from .config_ui import ( + ConfigBasedWidget, + ConfigFormLayout, + ConfigFormWidget, + ConfigComboBox, + ConfigSpinBox) + +__all__ = [ + "Config", + "ImmutablesListField", + "ImmutableField", + "EnumsListField", + "FieldBase", + "ConfigBasedWidget", + "ConfigFormLayout", + "ConfigFormWidget", + "ConfigComboBox", + "ConfigSpinBox"] diff --git a/shortcut_composer/composer_utils/config/config_ui.py b/shortcut_composer/composer_utils/config/config_ui.py index 4a9c04a3..495b16c1 100644 --- a/shortcut_composer/composer_utils/config/config_ui.py +++ b/shortcut_composer/composer_utils/config/config_ui.py @@ -1,12 +1,12 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import Dict, List, Union, Optional +from abc import abstractmethod +from typing import Any, List, Union, Final, Optional from PyQt5.QtCore import Qt from PyQt5.QtWidgets import ( QDoubleSpinBox, QFormLayout, - QVBoxLayout, QHBoxLayout, QSplitter, QComboBox, @@ -14,50 +14,109 @@ QWidget, QLabel) -from api_krita.wrappers import Database -from ..config.fields import ImmutableField +from ..config import ImmutableField -class FieldUiWrapper: - def __init__(self, ui_widget: Union[QSpinBox, QDoubleSpinBox, QComboBox]): - self.ui_widget = ui_widget +class ConfigBasedWidget: + def __init__( + self, + config_field: ImmutableField, + parent: Optional[QWidget] = None + ) -> None: + self._parent = parent + self.config_field: Final[ImmutableField] = config_field + self.widget: QWidget + + @abstractmethod + def read(self): ... + + @abstractmethod + def set(self, value): ... + + def reset(self): + self.set(self.config_field.read()) + + def save(self): + self.config_field.write(self.read()) + + +class ConfigSpinBox(ConfigBasedWidget): + def __init__( + self, + config_field: Union[ImmutableField[int], ImmutableField[float]], + parent: Optional[QWidget] = None, + step: float = 1, + max_value: float = 100, + ) -> None: + super().__init__(config_field, parent) + self._step = step + self._max_value = max_value + self._spin_box = self._init_spin_box() + self.widget: Final[Union[QSpinBox, QDoubleSpinBox]] = self._spin_box + self.reset() + + def read(self): + return self._spin_box.value() + + def set(self, value): + self._spin_box.setValue(value) + + def _init_spin_box(self): + spin_box = (QSpinBox() if self.config_field.type is int + else QDoubleSpinBox()) + spin_box.setObjectName(self.config_field.name) + spin_box.setMinimum(0) + spin_box.setSingleStep(self._step) # type: ignore + spin_box.setMaximum(self._max_value) # type: ignore + return spin_box + +class ConfigComboBox(ConfigBasedWidget): + def __init__( + self, + config_field: ImmutableField[str], + parent: Optional[QWidget] = None, + allowed_values: List[Any] = [], + ) -> None: + super().__init__(config_field, parent) + self._allowed_values = allowed_values + self._combo_box = self._init_combo_box() + self.widget: Final[QComboBox] = self._combo_box + self.reset() + + def _init_combo_box(self) -> QComboBox: + combo_box = QComboBox() + combo_box.setObjectName(self.config_field.name) + return combo_box + + def reset(self): + self._combo_box.clear() + self._combo_box.addItems(self._allowed_values) + self.set(self.config_field.read()) def read(self): - if isinstance(self.ui_widget, (QSpinBox, QDoubleSpinBox)): - return self.ui_widget.value() - return self.ui_widget.currentText() + return self._combo_box.currentText() - def write(self, value): - if isinstance(self.ui_widget, (QSpinBox, QDoubleSpinBox)): - return self.ui_widget.setValue(value) - return self.ui_widget.setCurrentText(value) + def set(self, value): + return self._combo_box.setCurrentText(value) class ConfigFormLayout(QFormLayout): """Dialog zone consisting of spin boxes.""" - def __init__(self, elements: List[Union[str, list, dict]]) -> None: + def __init__(self, elements: List[Union[ConfigBasedWidget, str]]) -> None: super().__init__() - self._forms: Dict[ImmutableField, FieldUiWrapper] = {} - + self._widgets: List[ConfigBasedWidget] = [] for element in elements: if isinstance(element, str): self._add_label(element) - elif isinstance(element, list): - self._add_row(*element) - elif isinstance(element, dict): - self._add_row(**element) + elif isinstance(element, ConfigBasedWidget): + self._add_row(element) else: raise TypeError("Unsupported arguments.") - def _add_row( - self, - config: ImmutableField, - step: Optional[float] = None, - max_value: Optional[float] = None) -> None: - field_ui = self.create_field_ui(config, step, max_value) - self._forms[config] = field_ui - self.addRow(config.name, field_ui.ui_widget) + def _add_row(self, element: ConfigBasedWidget) -> None: + self._widgets.append(element) + self.addRow(element.config_field.name, element.widget) def _add_label(self, text: str): label = QLabel(text) @@ -67,48 +126,17 @@ def _add_label(self, text: str): def refresh(self) -> None: """Read values from krita config and apply them to stored boxes.""" - for config, form in self._forms.items(): - if config.type != str: - form.write(config.read()) # type: ignore - else: - # HACK: can't assume the combobox is tag chooser - with Database() as database: - combo_box = form.ui_widget - combo_box.clear() - combo_box.addItems( - sorted(database.get_brush_tags(), key=str.lower)) - combo_box.setCurrentText(config.read()) + for element in self._widgets: + element.reset() def apply(self) -> None: """Write values from stored spin boxes to krita config file.""" - for config, form in self._forms.items(): - config.write(form.read()) - - @staticmethod - def create_field_ui( - config: ImmutableField, - step: Optional[float] = None, - max_value: Optional[float] = None - ): - if config.type in (float, int): - ui = QSpinBox() if config.type is int else QDoubleSpinBox() - ui.setObjectName(config.name) - ui.setMinimum(0) - ui.setMaximum(max_value) # type: ignore - ui.setSingleStep(step) # type: ignore - elif config.type is str: - ui = QComboBox() - ui.setObjectName(config.name) - else: - raise TypeError(f"{config.type} not supported.") - - wrapper = FieldUiWrapper(ui) - wrapper.write(config.read()) - return wrapper + for element in self._widgets: + element.save() class ConfigFormWidget(QWidget): - def __init__(self, elements: List[Union[str, list, dict]]) -> None: + def __init__(self, elements: List[Union[ConfigBasedWidget, str]]) -> None: super().__init__() self._layout = ConfigFormLayout(elements) diff --git a/shortcut_composer/composer_utils/settings_dialog.py b/shortcut_composer/composer_utils/settings_dialog.py index 4dd2f6d9..2d0dac1d 100644 --- a/shortcut_composer/composer_utils/settings_dialog.py +++ b/shortcut_composer/composer_utils/settings_dialog.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later +from typing import List + from PyQt5.QtWidgets import ( QVBoxLayout, QTabWidget, @@ -10,7 +12,8 @@ from PyQt5.QtGui import QCursor from api_krita import Krita -from .config import Config, ConfigFormWidget +from api_krita.wrappers import Database +from .config import Config, ConfigFormWidget, ConfigComboBox, ConfigSpinBox from .layouts import ButtonsLayout @@ -23,25 +26,28 @@ def __init__(self) -> None: self.setMinimumSize(QSize(300, 200)) self.setWindowTitle("Configure Shortcut Composer") - self._tab_dict = { - "General": ConfigFormWidget([ - "Preset pie-menus mapping", - [Config.TAG_RED], - [Config.TAG_GREEN], - [Config.TAG_BLUE], - "Common settings", - [Config.SHORT_VS_LONG_PRESS_TIME, 0.05, 4], - [Config.FPS_LIMIT, 5, 500], - "Cursor trackers", - [Config.TRACKER_SENSITIVITY_SCALE, 0.05, 4], - [Config.TRACKER_DEADZONE, 1, 200], - "Pie menus display", - [Config.PIE_GLOBAL_SCALE, 0.05, 4], - [Config.PIE_ICON_GLOBAL_SCALE, 0.05, 4], - [Config.PIE_DEADZONE_GLOBAL_SCALE, 0.05, 4], - [Config.PIE_ANIMATION_TIME, 0.01, 1], - ]) - } + self._tags: List[str] = [] + self._refresh_tags() + + general_tab = ConfigFormWidget([ + "Preset pie-menus mapping", + ConfigComboBox(Config.TAG_RED, self, self._tags), + ConfigComboBox(Config.TAG_GREEN, self, self._tags), + ConfigComboBox(Config.TAG_BLUE, self, self._tags), + "Common settings", + ConfigSpinBox(Config.SHORT_VS_LONG_PRESS_TIME, self, 0.05, 4), + ConfigSpinBox(Config.FPS_LIMIT, self, 5, 500), + "Cursor trackers", + ConfigSpinBox(Config.TRACKER_SENSITIVITY_SCALE, self, 0.05, 400), + ConfigSpinBox(Config.TRACKER_DEADZONE, self, 1, 200), + "Pie menus display", + ConfigSpinBox(Config.PIE_GLOBAL_SCALE, self, 0.05, 4), + ConfigSpinBox(Config.PIE_ICON_GLOBAL_SCALE, self, 0.05, 4), + ConfigSpinBox(Config.PIE_DEADZONE_GLOBAL_SCALE, self, 0.05, 4), + ConfigSpinBox(Config.PIE_ANIMATION_TIME, self, 0.01, 1), + ]) + + self._tab_dict = {"General": general_tab} tab_holder = QTabWidget() for name, tab in self._tab_dict.items(): tab_holder.addTab(tab, name) @@ -79,7 +85,14 @@ def reset(self) -> None: self.refresh() Krita.trigger_action("Reload Shortcut Composer") + def _refresh_tags(self): + with Database() as database: + tags = sorted(database.get_brush_tags(), key=str.lower) + self._tags.clear() + self._tags.extend(tags) + def refresh(self): """Ask all tabs to refresh themselves. """ + self._refresh_tags() for tab in self._tab_dict.values(): tab.refresh() diff --git a/shortcut_composer/templates/pie_menu_utils/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/pie_settings.py index cfb75b6b..828c74a1 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_settings.py @@ -10,8 +10,13 @@ QTabWidget, ) from api_krita import Krita +from api_krita.wrappers import Database from api_krita.pyqt import AnimatedWidget, BaseWidget -from composer_utils.config import Config, ConfigFormWidget +from composer_utils.config import ( + ConfigFormWidget, + ConfigComboBox, + ConfigSpinBox, + Config) from .label import Label from .label_widget import LabelWidget from .label_widget_utils import create_label_widget @@ -39,13 +44,15 @@ def __init__( self._style = style self._pie_config = pie_config - tab_holder = QTabWidget() + self._tags: List[str] = [] + self._refresh_tags() + tab_holder = QTabWidget() self._local_settings = ConfigFormWidget([ "Bla", - [pie_config.pie_radius_scale, 0.05, 4], - [pie_config.icon_radius_scale, 0.05, 4], - [Config.TAG_BLUE], + ConfigSpinBox(pie_config.pie_radius_scale, self, 0.05, 4), + ConfigSpinBox(pie_config.icon_radius_scale, self, 0.05, 4), + ConfigComboBox(Config.TAG_BLUE, self, self._tags), "Ble", ]) tab_holder.addTab(self._local_settings, "Local settings") @@ -65,6 +72,7 @@ def move_to_pie_side(self): self.move_center(QCursor().pos() + point) # type: ignore def show(self): + self._refresh_tags() self._local_settings.refresh() return super().show() @@ -72,6 +80,12 @@ def hide(self) -> None: self._local_settings.apply() Krita.trigger_action("Reload Shortcut Composer") return super().hide() + + def _refresh_tags(self): + with Database() as database: + tags = sorted(database.get_brush_tags(), key=str.lower) + self._tags.clear() + self._tags.extend(tags) class ScrollArea(QScrollArea): From 81df5001ba774aee9d516e579062a1ffdbfe00c1 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 027/125] Remove ConfigFormLayout --- .../composer_utils/config/__init__.py | 2 -- .../composer_utils/config/config_ui.py | 33 +++++++------------ 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/shortcut_composer/composer_utils/config/__init__.py b/shortcut_composer/composer_utils/config/__init__.py index e6746165..2a42021b 100644 --- a/shortcut_composer/composer_utils/config/__init__.py +++ b/shortcut_composer/composer_utils/config/__init__.py @@ -6,7 +6,6 @@ FieldBase) from .config_ui import ( ConfigBasedWidget, - ConfigFormLayout, ConfigFormWidget, ConfigComboBox, ConfigSpinBox) @@ -18,7 +17,6 @@ "EnumsListField", "FieldBase", "ConfigBasedWidget", - "ConfigFormLayout", "ConfigFormWidget", "ConfigComboBox", "ConfigSpinBox"] diff --git a/shortcut_composer/composer_utils/config/config_ui.py b/shortcut_composer/composer_utils/config/config_ui.py index 495b16c1..b4aef6d0 100644 --- a/shortcut_composer/composer_utils/config/config_ui.py +++ b/shortcut_composer/composer_utils/config/config_ui.py @@ -100,11 +100,18 @@ def set(self, value): return self._combo_box.setCurrentText(value) -class ConfigFormLayout(QFormLayout): +class ConfigFormWidget(QWidget): """Dialog zone consisting of spin boxes.""" def __init__(self, elements: List[Union[ConfigBasedWidget, str]]) -> None: super().__init__() + self._layout = QFormLayout() + stretched = QHBoxLayout() + stretched.addStretch() + stretched.addLayout(self._layout) + stretched.addStretch() + self.setLayout(stretched) + self._widgets: List[ConfigBasedWidget] = [] for element in elements: if isinstance(element, str): @@ -116,13 +123,13 @@ def __init__(self, elements: List[Union[ConfigBasedWidget, str]]) -> None: def _add_row(self, element: ConfigBasedWidget) -> None: self._widgets.append(element) - self.addRow(element.config_field.name, element.widget) + self._layout.addRow(element.config_field.name, element.widget) def _add_label(self, text: str): label = QLabel(text) label.setAlignment(Qt.AlignCenter) - self.addRow(QSplitter(Qt.Horizontal)) - self.addRow(label) + self._layout.addRow(QSplitter(Qt.Horizontal)) + self._layout.addRow(label) def refresh(self) -> None: """Read values from krita config and apply them to stored boxes.""" @@ -133,21 +140,3 @@ def apply(self) -> None: """Write values from stored spin boxes to krita config file.""" for element in self._widgets: element.save() - - -class ConfigFormWidget(QWidget): - def __init__(self, elements: List[Union[ConfigBasedWidget, str]]) -> None: - super().__init__() - - self._layout = ConfigFormLayout(elements) - stretched = QHBoxLayout() - stretched.addStretch() - stretched.addLayout(self._layout) - stretched.addStretch() - self.setLayout(stretched) - - def apply(self) -> None: - self._layout.apply() - - def refresh(self) -> None: - self._layout.refresh() From 50f82b2cf6416a089626e21bf2518dda2c7df83f Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 028/125] Revert removed check for preset icon existance --- shortcut_composer/templates/pie_menu.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index e01a4f27..e7b1b098 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -150,7 +150,10 @@ def _create_labels(self, values: List[T]) -> List[Label]: """Wrap values into paintable label objects with position info.""" label_list = [] for value in values: - label = self._controller.get_label(value) + try: + label = self._controller.get_label(value) + except KeyError: + continue label_list.append(Label(value=value, display_value=label)) return label_list From 67d3f4d58a0316d6af6da38841a008b7765e086c Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 029/125] Different setting windows for different configs --- shortcut_composer/templates/pie_menu.py | 6 +- .../templates/pie_menu_utils/__init__.py | 3 +- .../templates/pie_menu_utils/pie_config.py | 4 + .../templates/pie_menu_utils/pie_settings.py | 102 ++++++++++++++---- 4 files changed, 89 insertions(+), 26 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index e7b1b098..105b3558 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -10,6 +10,7 @@ from core_components import Controller, Instruction from input_adapter import ComplexAction from .pie_menu_utils import ( + create_settings_window, create_local_config, PieSettingsWindow, PieManager, @@ -109,11 +110,10 @@ def __init__( self._style.set_items(self._labels) self._edit_mode = EditMode(self) - self.pie_settings = PieSettingsWindow( + self.pie_settings = create_settings_window( style=unscaled_style, values=self._create_all_labels(self._values), - pie_config=self._local_config, - columns=3) + pie_config=self._local_config) self.pie_widget = PieWidget( style=self._style, labels=self._labels, diff --git a/shortcut_composer/templates/pie_menu_utils/__init__.py b/shortcut_composer/templates/pie_menu_utils/__init__.py index 70e9230a..2452f82e 100644 --- a/shortcut_composer/templates/pie_menu_utils/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/__init__.py @@ -5,13 +5,14 @@ from .pie_config import create_local_config from .label_widget import LabelWidget -from .pie_settings import PieSettingsWindow +from .pie_settings import PieSettingsWindow, create_settings_window from .pie_manager import PieManager from .pie_widget import PieWidget from .pie_style import PieStyle from .label import Label __all__ = [ + "create_settings_window", "create_local_config", "LabelWidget", "PieSettingsWindow", diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 36788cdd..3aa1cfc3 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later +# from PyQt5.QtWidgets import QWidget + from composer_utils.config import ( EnumsListField, ImmutablesListField, @@ -24,6 +26,7 @@ def create_local_config( class PieConfig: values: list order: FieldBase + # settings_window: QWidget def __init__( self, @@ -41,6 +44,7 @@ def __init__( f"{self.name} icon scale", icon_radius_scale) + # def create_pie_window(self) -> QWidget: ... class PresetPieConfig(PieConfig): def __init__(self, *args): diff --git a/shortcut_composer/templates/pie_menu_utils/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/pie_settings.py index 828c74a1..6ca68516 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_settings.py @@ -21,7 +21,20 @@ from .label_widget import LabelWidget from .label_widget_utils import create_label_widget from .pie_style import PieStyle -from .pie_config import PieConfig +from .pie_config import PieConfig, EnumPieConfig, PresetPieConfig + + +def create_settings_window( + values: List[Label], + style: PieStyle, + pie_config: PieConfig, + parent=None +) -> 'PieSettingsWindow': + if isinstance(pie_config, PresetPieConfig): + return TagPieSettingsWindow(values, style, pie_config, parent) + elif isinstance(pie_config, EnumPieConfig): + return EnumPieSettingsWindow(values, style, pie_config, parent) + raise ValueError(f"Unknown pie config {pie_config}") class PieSettingsWindow(AnimatedWidget, BaseWidget): @@ -29,7 +42,6 @@ def __init__( self, values: List[Label], style: PieStyle, - columns: int, pie_config: PieConfig, parent=None ) -> None: @@ -41,46 +53,56 @@ def __init__( Qt.FramelessWindowHint)) self.setCursor(Qt.ArrowCursor) + self._values = values self._style = style self._pie_config = pie_config + def move_to_pie_side(self): + offset = self.width()//2 + self._style.widget_radius * 1.05 + point = QPoint(round(offset), 0) + self.move_center(QCursor().pos() + point) # type: ignore + + def hide(self) -> None: + Krita.trigger_action("Reload Shortcut Composer") + super().hide() + + +class TagPieSettingsWindow(PieSettingsWindow): + def __init__( + self, + values: List[Label], + style: PieStyle, + pie_config: PresetPieConfig, + parent=None + ) -> None: + super().__init__( + values, + style, + pie_config, + parent) + self._tags: List[str] = [] self._refresh_tags() - tab_holder = QTabWidget() self._local_settings = ConfigFormWidget([ - "Bla", + ConfigComboBox(pie_config.tag_name, self, self._tags), ConfigSpinBox(pie_config.pie_radius_scale, self, 0.05, 4), ConfigSpinBox(pie_config.icon_radius_scale, self, 0.05, 4), - ConfigComboBox(Config.TAG_BLUE, self, self._tags), - "Ble", ]) - tab_holder.addTab(self._local_settings, "Local settings") - self._action_values = ScrollArea( - values, - self._style, - columns) - tab_holder.addTab(self._action_values, "Action values") layout = QVBoxLayout(self) - layout.addWidget(tab_holder) + layout.addWidget(self._local_settings) self.setLayout(layout) - def move_to_pie_side(self): - offset = self.width()//2 + self._style.widget_radius * 1.05 - point = QPoint(round(offset), 0) - self.move_center(QCursor().pos() + point) # type: ignore - def show(self): self._refresh_tags() self._local_settings.refresh() - return super().show() + super().show() def hide(self) -> None: self._local_settings.apply() - Krita.trigger_action("Reload Shortcut Composer") - return super().hide() - + super().hide() + def _refresh_tags(self): with Database() as database: tags = sorted(database.get_brush_tags(), key=str.lower) @@ -88,6 +110,42 @@ def _refresh_tags(self): self._tags.extend(tags) +class EnumPieSettingsWindow(PieSettingsWindow): + def __init__( + self, + values: List[Label], + style: PieStyle, + pie_config: EnumPieConfig, + parent=None + ) -> None: + super().__init__( + values, + style, + pie_config, + parent) + + tab_holder = QTabWidget() + self._local_settings = ConfigFormWidget([ + ConfigSpinBox(pie_config.pie_radius_scale, self, 0.05, 4), + ConfigSpinBox(pie_config.icon_radius_scale, self, 0.05, 4), + ]) + tab_holder.addTab(self._local_settings, "Local settings") + self._action_values = ScrollArea(values, self._style, 3) + tab_holder.addTab(self._action_values, "Action values") + + layout = QVBoxLayout(self) + layout.addWidget(tab_holder) + self.setLayout(layout) + + def show(self): + self._local_settings.refresh() + super().show() + + def hide(self) -> None: + self._local_settings.apply() + super().hide() + + class ScrollArea(QScrollArea): def __init__( self, From 53c6acd7b32805f336df5d4e64345835e84a7a3c Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 030/125] Changes setting classes names and moves files --- shortcut_composer/templates/pie_menu.py | 5 +- .../templates/pie_menu_utils/__init__.py | 6 +- .../templates/pie_menu_utils/dispatchers.py | 33 +++ .../templates/pie_menu_utils/pie_config.py | 16 -- .../templates/pie_menu_utils/pie_manager.py | 4 +- .../templates/pie_menu_utils/pie_settings.py | 230 ------------------ .../pie_menu_utils/settings_gui/__init__.py | 5 + .../settings_gui/enum_pie_settings.py | 46 ++++ .../settings_gui/pie_settings.py | 42 ++++ .../settings_gui/preset_pie_settings.py | 55 +++++ .../settings_gui/scroll_area.py | 95 ++++++++ 11 files changed, 282 insertions(+), 255 deletions(-) create mode 100644 shortcut_composer/templates/pie_menu_utils/dispatchers.py delete mode 100644 shortcut_composer/templates/pie_menu_utils/pie_settings.py create mode 100644 shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py create mode 100644 shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py create mode 100644 shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py create mode 100644 shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py create mode 100644 shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 105b3558..a028a963 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -10,9 +10,8 @@ from core_components import Controller, Instruction from input_adapter import ComplexAction from .pie_menu_utils import ( - create_settings_window, + create_pie_settings_window, create_local_config, - PieSettingsWindow, PieManager, PieWidget, PieStyle, @@ -110,7 +109,7 @@ def __init__( self._style.set_items(self._labels) self._edit_mode = EditMode(self) - self.pie_settings = create_settings_window( + self.pie_settings = create_pie_settings_window( style=unscaled_style, values=self._create_all_labels(self._values), pie_config=self._local_config) diff --git a/shortcut_composer/templates/pie_menu_utils/__init__.py b/shortcut_composer/templates/pie_menu_utils/__init__.py index 2452f82e..0e214fea 100644 --- a/shortcut_composer/templates/pie_menu_utils/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/__init__.py @@ -3,19 +3,17 @@ """Implementation of PieMenu main elements.""" -from .pie_config import create_local_config +from .dispatchers import create_local_config, create_pie_settings_window from .label_widget import LabelWidget -from .pie_settings import PieSettingsWindow, create_settings_window from .pie_manager import PieManager from .pie_widget import PieWidget from .pie_style import PieStyle from .label import Label __all__ = [ - "create_settings_window", + "create_pie_settings_window", "create_local_config", "LabelWidget", - "PieSettingsWindow", "PieManager", "PieWidget", "PieStyle", diff --git a/shortcut_composer/templates/pie_menu_utils/dispatchers.py b/shortcut_composer/templates/pie_menu_utils/dispatchers.py new file mode 100644 index 00000000..153382c2 --- /dev/null +++ b/shortcut_composer/templates/pie_menu_utils/dispatchers.py @@ -0,0 +1,33 @@ + +from typing import List + +from data_components import Tag +from .pie_config import PieConfig, PresetPieConfig, EnumPieConfig +from .settings_gui import PieSettings, PresetPieSettings, EnumPieSettings +from .label import Label +from .pie_style import PieStyle +from .pie_config import PieConfig, EnumPieConfig, PresetPieConfig + + +def create_local_config( + name: str, + values: list, + pie_radius_scale: float, + icon_radius_scale: float, +) -> PieConfig: + args = [name, values, pie_radius_scale, icon_radius_scale] + if isinstance(values, Tag): + return PresetPieConfig(*args) + return EnumPieConfig(*args) + +def create_pie_settings_window( + values: List[Label], + style: PieStyle, + pie_config: PieConfig, + parent=None +) -> PieSettings: + if isinstance(pie_config, PresetPieConfig): + return PresetPieSettings(values, style, pie_config, parent) + elif isinstance(pie_config, EnumPieConfig): + return EnumPieSettings(values, style, pie_config, parent) + raise ValueError(f"Unknown pie config {pie_config}") \ No newline at end of file diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 3aa1cfc3..55644c49 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -1,8 +1,6 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -# from PyQt5.QtWidgets import QWidget - from composer_utils.config import ( EnumsListField, ImmutablesListField, @@ -11,22 +9,9 @@ from data_components import Tag -def create_local_config( - name: str, - values: list, - pie_radius_scale: float, - icon_radius_scale: float, -) -> 'PieConfig': - args = [name, values, pie_radius_scale, icon_radius_scale] - if isinstance(values, Tag): - return PresetPieConfig(*args) - return EnumPieConfig(*args) - - class PieConfig: values: list order: FieldBase - # settings_window: QWidget def __init__( self, @@ -44,7 +29,6 @@ def __init__( f"{self.name} icon scale", icon_radius_scale) - # def create_pie_window(self) -> QWidget: ... class PresetPieConfig(PieConfig): def __init__(self, *args): diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index 6253b82e..bbed90b4 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -7,7 +7,7 @@ from api_krita.pyqt import Timer from composer_utils.config import Config -from .pie_settings import PieSettingsWindow +from .settings_gui import PieSettings from .pie_widget import PieWidget from .label_widget import LabelWidget from .widget_utils import CirclePoints @@ -22,7 +22,7 @@ class PieManager: - Asks the widget to repaint when after changing active label. """ - def __init__(self, pie_widget: PieWidget, pie_settings: PieSettingsWindow): + def __init__(self, pie_widget: PieWidget, pie_settings: PieSettings): self._pie_widget = pie_widget self._pie_settings = pie_settings self._holder = self._pie_widget.widget_holder diff --git a/shortcut_composer/templates/pie_menu_utils/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/pie_settings.py deleted file mode 100644 index 6ca68516..00000000 --- a/shortcut_composer/templates/pie_menu_utils/pie_settings.py +++ /dev/null @@ -1,230 +0,0 @@ -from typing import List - -from PyQt5.QtCore import QPoint, Qt -from PyQt5.QtGui import QCursor -from PyQt5.QtWidgets import ( - QWidget, - QScrollArea, - QGridLayout, - QVBoxLayout, - QTabWidget, -) -from api_krita import Krita -from api_krita.wrappers import Database -from api_krita.pyqt import AnimatedWidget, BaseWidget -from composer_utils.config import ( - ConfigFormWidget, - ConfigComboBox, - ConfigSpinBox, - Config) -from .label import Label -from .label_widget import LabelWidget -from .label_widget_utils import create_label_widget -from .pie_style import PieStyle -from .pie_config import PieConfig, EnumPieConfig, PresetPieConfig - - -def create_settings_window( - values: List[Label], - style: PieStyle, - pie_config: PieConfig, - parent=None -) -> 'PieSettingsWindow': - if isinstance(pie_config, PresetPieConfig): - return TagPieSettingsWindow(values, style, pie_config, parent) - elif isinstance(pie_config, EnumPieConfig): - return EnumPieSettingsWindow(values, style, pie_config, parent) - raise ValueError(f"Unknown pie config {pie_config}") - - -class PieSettingsWindow(AnimatedWidget, BaseWidget): - def __init__( - self, - values: List[Label], - style: PieStyle, - pie_config: PieConfig, - parent=None - ) -> None: - AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) - self.setAcceptDrops(True) - self.setWindowFlags(( - self.windowFlags() | # type: ignore - Qt.Tool | - Qt.FramelessWindowHint)) - self.setCursor(Qt.ArrowCursor) - - self._values = values - self._style = style - self._pie_config = pie_config - - def move_to_pie_side(self): - offset = self.width()//2 + self._style.widget_radius * 1.05 - point = QPoint(round(offset), 0) - self.move_center(QCursor().pos() + point) # type: ignore - - def hide(self) -> None: - Krita.trigger_action("Reload Shortcut Composer") - super().hide() - - -class TagPieSettingsWindow(PieSettingsWindow): - def __init__( - self, - values: List[Label], - style: PieStyle, - pie_config: PresetPieConfig, - parent=None - ) -> None: - super().__init__( - values, - style, - pie_config, - parent) - - self._tags: List[str] = [] - self._refresh_tags() - - self._local_settings = ConfigFormWidget([ - ConfigComboBox(pie_config.tag_name, self, self._tags), - ConfigSpinBox(pie_config.pie_radius_scale, self, 0.05, 4), - ConfigSpinBox(pie_config.icon_radius_scale, self, 0.05, 4), - ]) - - layout = QVBoxLayout(self) - layout.addWidget(self._local_settings) - self.setLayout(layout) - - def show(self): - self._refresh_tags() - self._local_settings.refresh() - super().show() - - def hide(self) -> None: - self._local_settings.apply() - super().hide() - - def _refresh_tags(self): - with Database() as database: - tags = sorted(database.get_brush_tags(), key=str.lower) - self._tags.clear() - self._tags.extend(tags) - - -class EnumPieSettingsWindow(PieSettingsWindow): - def __init__( - self, - values: List[Label], - style: PieStyle, - pie_config: EnumPieConfig, - parent=None - ) -> None: - super().__init__( - values, - style, - pie_config, - parent) - - tab_holder = QTabWidget() - self._local_settings = ConfigFormWidget([ - ConfigSpinBox(pie_config.pie_radius_scale, self, 0.05, 4), - ConfigSpinBox(pie_config.icon_radius_scale, self, 0.05, 4), - ]) - tab_holder.addTab(self._local_settings, "Local settings") - self._action_values = ScrollArea(values, self._style, 3) - tab_holder.addTab(self._action_values, "Action values") - - layout = QVBoxLayout(self) - layout.addWidget(tab_holder) - self.setLayout(layout) - - def show(self): - self._local_settings.refresh() - super().show() - - def hide(self) -> None: - self._local_settings.apply() - super().hide() - - -class ScrollArea(QScrollArea): - def __init__( - self, - values: List[Label], - style: PieStyle, - columns: int, - parent=None - ) -> None: - super().__init__(parent) - self.setFixedHeight(style.widget_radius*2) - self.setFixedWidth(round(style.icon_radius*(2*columns + 1))) - self.setWidgetResizable(True) - - self._style = style - self.labels = values - - self._scroll_area_layout = ScrollAreaLayout(columns, self) - self._children_list = self._create_children() - - scroll_widget = QWidget() - scroll_widget.setLayout(self._scroll_area_layout) - self.setWidget(scroll_widget) - - def _create_children(self) -> List[LabelWidget]: - """Create LabelWidgets that represent the labels.""" - children: List[LabelWidget] = [] - diameter = self._style.icon_radius*2 - - for label in self.labels: - children.append(create_label_widget(label, self._style, self)) - children[-1].setFixedSize(diameter, diameter) - children[-1].draggable = True - self._scroll_area_layout.append(children[-1]) - return children - - -class ScrollAreaLayout(QGridLayout): - def __init__(self, cols: int, owner: QWidget): - super().__init__() - self.widgets: List[QWidget] = [] - self._max_rows = cols - self._uniques = 2*cols - 1 - self._owner = owner - - def __len__(self): - return len(self.widgets) - - def _get_position(self, index: int): - row, col = divmod(index, self._uniques) - if col < self._max_rows: - return (row*4, col*2) - return (row*4+2, (col-self._max_rows)*2+1) - - def _new_position(self): - return self._get_position(len(self.widgets)) - - def append(self, widget: QWidget): - if widget in self.widgets: - return - widget.setParent(self._owner) - widget.show() - self.widgets.append(widget) - self.addWidget(widget, *self._new_position(), 2, 2) - self._refresh() - - def pop(self, index: int): - self.widgets.pop(index) - self._refresh() - - def remove(self, widget: QWidget): - for index, held_widget in enumerate(self.widgets): - if held_widget == widget: - self.pop(index) - return self._refresh() - - def insert(self, index: int, widget: QWidget): - self.widgets.insert(index, widget) - self._refresh() - - def _refresh(self): - for i, widget in enumerate(self.widgets): - self.addWidget(widget, *self._get_position(i), 2, 2) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py new file mode 100644 index 00000000..661dea4f --- /dev/null +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py @@ -0,0 +1,5 @@ +from .pie_settings import PieSettings +from .enum_pie_settings import EnumPieSettings +from .preset_pie_settings import PresetPieSettings + +__all__ = ["PieSettings", "EnumPieSettings", "PresetPieSettings"] diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py new file mode 100644 index 00000000..08ece91b --- /dev/null +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -0,0 +1,46 @@ +from typing import List + +from PyQt5.QtWidgets import QVBoxLayout, QTabWidget + +from composer_utils.config import ConfigFormWidget, ConfigSpinBox +from ..label import Label +from ..pie_style import PieStyle +from ..pie_config import EnumPieConfig +from .pie_settings import PieSettings +from .scroll_area import ScrollArea + + +class EnumPieSettings(PieSettings): + def __init__( + self, + values: List[Label], + style: PieStyle, + pie_config: EnumPieConfig, + parent=None + ) -> None: + super().__init__( + values, + style, + pie_config, + parent) + + tab_holder = QTabWidget() + self._local_settings = ConfigFormWidget([ + ConfigSpinBox(pie_config.pie_radius_scale, self, 0.05, 4), + ConfigSpinBox(pie_config.icon_radius_scale, self, 0.05, 4), + ]) + tab_holder.addTab(self._local_settings, "Local settings") + self._action_values = ScrollArea(values, self._style, 3) + tab_holder.addTab(self._action_values, "Action values") + + layout = QVBoxLayout(self) + layout.addWidget(tab_holder) + self.setLayout(layout) + + def show(self): + self._local_settings.refresh() + super().show() + + def hide(self) -> None: + self._local_settings.apply() + super().hide() \ No newline at end of file diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py new file mode 100644 index 00000000..3a729c1a --- /dev/null +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py @@ -0,0 +1,42 @@ +from typing import List + +from PyQt5.QtCore import QPoint, Qt +from PyQt5.QtGui import QCursor + +from api_krita import Krita +from api_krita.pyqt import AnimatedWidget, BaseWidget +from composer_utils.config import Config + +from ..label import Label +from ..pie_style import PieStyle +from ..pie_config import PieConfig + + +class PieSettings(AnimatedWidget, BaseWidget): + def __init__( + self, + values: List[Label], + style: PieStyle, + pie_config: PieConfig, + parent=None + ) -> None: + AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) + self.setAcceptDrops(True) + self.setWindowFlags(( + self.windowFlags() | # type: ignore + Qt.Tool | + Qt.FramelessWindowHint)) + self.setCursor(Qt.ArrowCursor) + + self._values = values + self._style = style + self._pie_config = pie_config + + def move_to_pie_side(self): + offset = self.width()//2 + self._style.widget_radius * 1.05 + point = QPoint(round(offset), 0) + self.move_center(QCursor().pos() + point) # type: ignore + + def hide(self) -> None: + Krita.trigger_action("Reload Shortcut Composer") + super().hide() diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py new file mode 100644 index 00000000..8ed9a3f0 --- /dev/null +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py @@ -0,0 +1,55 @@ +from typing import List + +from PyQt5.QtWidgets import QVBoxLayout +from api_krita.wrappers import Database +from composer_utils.config import ( + ConfigFormWidget, + ConfigComboBox, + ConfigSpinBox) +from ..label import Label +from ..pie_style import PieStyle +from ..pie_config import PresetPieConfig +from .pie_settings import PieSettings + + +class PresetPieSettings(PieSettings): + def __init__( + self, + values: List[Label], + style: PieStyle, + pie_config: PresetPieConfig, + parent=None + ) -> None: + super().__init__( + values, + style, + pie_config, + parent) + + self._tags: List[str] = [] + self._refresh_tags() + + self._local_settings = ConfigFormWidget([ + ConfigComboBox(pie_config.tag_name, self, self._tags), + ConfigSpinBox(pie_config.pie_radius_scale, self, 0.05, 4), + ConfigSpinBox(pie_config.icon_radius_scale, self, 0.05, 4), + ]) + + layout = QVBoxLayout(self) + layout.addWidget(self._local_settings) + self.setLayout(layout) + + def show(self): + self._refresh_tags() + self._local_settings.refresh() + super().show() + + def hide(self) -> None: + self._local_settings.apply() + super().hide() + + def _refresh_tags(self): + with Database() as database: + tags = sorted(database.get_brush_tags(), key=str.lower) + self._tags.clear() + self._tags.extend(tags) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py new file mode 100644 index 00000000..43d121e3 --- /dev/null +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py @@ -0,0 +1,95 @@ +from typing import List + +from PyQt5.QtWidgets import ( + QWidget, + QScrollArea, + QGridLayout) + +from ..label import Label +from ..label_widget import LabelWidget +from ..label_widget_utils import create_label_widget +from ..pie_style import PieStyle + + +class ScrollArea(QScrollArea): + def __init__( + self, + values: List[Label], + style: PieStyle, + columns: int, + parent=None + ) -> None: + super().__init__(parent) + self.setFixedHeight(style.widget_radius*2) + self.setFixedWidth(round(style.icon_radius*(2*columns + 1))) + self.setWidgetResizable(True) + + self._style = style + self.labels = values + + self._scroll_area_layout = ScrollAreaLayout(columns, self) + self._children_list = self._create_children() + + scroll_widget = QWidget() + scroll_widget.setLayout(self._scroll_area_layout) + self.setWidget(scroll_widget) + + def _create_children(self) -> List[LabelWidget]: + """Create LabelWidgets that represent the labels.""" + children: List[LabelWidget] = [] + diameter = self._style.icon_radius*2 + + for label in self.labels: + children.append(create_label_widget(label, self._style, self)) + children[-1].setFixedSize(diameter, diameter) + children[-1].draggable = True + self._scroll_area_layout.append(children[-1]) + return children + + +class ScrollAreaLayout(QGridLayout): + def __init__(self, cols: int, owner: QWidget): + super().__init__() + self.widgets: List[QWidget] = [] + self._max_rows = cols + self._uniques = 2*cols - 1 + self._owner = owner + + def __len__(self): + return len(self.widgets) + + def _get_position(self, index: int): + row, col = divmod(index, self._uniques) + if col < self._max_rows: + return (row*4, col*2) + return (row*4+2, (col-self._max_rows)*2+1) + + def _new_position(self): + return self._get_position(len(self.widgets)) + + def append(self, widget: QWidget): + if widget in self.widgets: + return + widget.setParent(self._owner) + widget.show() + self.widgets.append(widget) + self.addWidget(widget, *self._new_position(), 2, 2) + self._refresh() + + def pop(self, index: int): + self.widgets.pop(index) + self._refresh() + + def remove(self, widget: QWidget): + for index, held_widget in enumerate(self.widgets): + if held_widget == widget: + self.pop(index) + return self._refresh() + + def insert(self, index: int, widget: QWidget): + self.widgets.insert(index, widget) + self._refresh() + + def _refresh(self): + for i, widget in enumerate(self.widgets): + self.addWidget(widget, *self._get_position(i), 2, 2) From 1b0a792278dbc61b1de6dc73d92a411c890f8743 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 031/125] Reset pies instead of whole plugin --- shortcut_composer/templates/pie_menu.py | 12 ++++++++++-- .../pie_menu_utils/settings_gui/pie_settings.py | 5 ----- .../pie_menu_utils/widget_utils/edit_mode.py | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index a028a963..37f91823 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -97,13 +97,21 @@ def __init__( values, pie_radius_scale, icon_radius_scale) + + # TODO: colors should be in self._local_config + self._background_color = background_color + self._active_color = active_color + + self.reset() + + def reset(self): self._values = self._local_config.values self._labels = self._create_labels(self._values) self._style = PieStyle( pie_radius_scale=self._local_config.pie_radius_scale.read(), icon_radius_scale=self._local_config.icon_radius_scale.read(), - background_color=background_color, - active_color=active_color) + background_color=self._background_color, + active_color=self._active_color) unscaled_style = copy(self._style) self._style.set_items(self._labels) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py index 3a729c1a..08fcbcaf 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py @@ -3,7 +3,6 @@ from PyQt5.QtCore import QPoint, Qt from PyQt5.QtGui import QCursor -from api_krita import Krita from api_krita.pyqt import AnimatedWidget, BaseWidget from composer_utils.config import Config @@ -36,7 +35,3 @@ def move_to_pie_side(self): offset = self.width()//2 + self._style.widget_radius * 1.05 point = QPoint(round(offset), 0) self.move_center(QCursor().pos() + point) # type: ignore - - def hide(self) -> None: - Krita.trigger_action("Reload Shortcut Composer") - super().hide() diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py index 940b90ed..bb3a88aa 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py @@ -43,6 +43,7 @@ def set_edit_mode_false(self): self._obj.pie_settings.hide() self._obj.accept_button.hide() self._obj.settings_button.show() + self._obj.reset() def swap_mode(self): self.set(not self._edit_mode) From 949515e1ef26774c35dab90bac14bd745fc97884 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 032/125] Remove tag chooser from ConfigureSC --- .../composer_utils/config/global_config.py | 4 --- .../composer_utils/settings_dialog.py | 25 ++++--------------- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/shortcut_composer/composer_utils/config/global_config.py b/shortcut_composer/composer_utils/config/global_config.py index b3ba7fa4..63ce2f01 100644 --- a/shortcut_composer/composer_utils/config/global_config.py +++ b/shortcut_composer/composer_utils/config/global_config.py @@ -37,10 +37,6 @@ class Config: "Pie deadzone global scale", 1.0) PIE_ANIMATION_TIME = ImmutableField("Pie animation time", 0.2) - TAG_RED = ImmutableField("Pick brush presets (red)", "★ My Favorites") - TAG_GREEN = ImmutableField("Pick brush presets (green)", "RGBA") - TAG_BLUE = ImmutableField("Pick brush presets (blue)", "Erasers") - @classmethod def reset_defaults(cls) -> None: """Reset all config files.""" diff --git a/shortcut_composer/composer_utils/settings_dialog.py b/shortcut_composer/composer_utils/settings_dialog.py index 2d0dac1d..3b0b3d0a 100644 --- a/shortcut_composer/composer_utils/settings_dialog.py +++ b/shortcut_composer/composer_utils/settings_dialog.py @@ -3,17 +3,13 @@ from typing import List -from PyQt5.QtWidgets import ( - QVBoxLayout, - QTabWidget, - QDialog, -) +from PyQt5.QtWidgets import QVBoxLayout, QDialog from PyQt5.QtCore import QSize from PyQt5.QtGui import QCursor from api_krita import Krita from api_krita.wrappers import Database -from .config import Config, ConfigFormWidget, ConfigComboBox, ConfigSpinBox +from .config import Config, ConfigFormWidget, ConfigSpinBox from .layouts import ButtonsLayout @@ -29,11 +25,7 @@ def __init__(self) -> None: self._tags: List[str] = [] self._refresh_tags() - general_tab = ConfigFormWidget([ - "Preset pie-menus mapping", - ConfigComboBox(Config.TAG_RED, self, self._tags), - ConfigComboBox(Config.TAG_GREEN, self, self._tags), - ConfigComboBox(Config.TAG_BLUE, self, self._tags), + self._general_tab = ConfigFormWidget([ "Common settings", ConfigSpinBox(Config.SHORT_VS_LONG_PRESS_TIME, self, 0.05, 4), ConfigSpinBox(Config.FPS_LIMIT, self, 5, 500), @@ -47,13 +39,8 @@ def __init__(self) -> None: ConfigSpinBox(Config.PIE_ANIMATION_TIME, self, 0.01, 1), ]) - self._tab_dict = {"General": general_tab} - tab_holder = QTabWidget() - for name, tab in self._tab_dict.items(): - tab_holder.addTab(tab, name) - full_layout = QVBoxLayout(self) - full_layout.addWidget(tab_holder) + full_layout.addWidget(self._general_tab) full_layout.addLayout(ButtonsLayout( ok_callback=self.ok, apply_callback=self.apply, @@ -92,7 +79,5 @@ def _refresh_tags(self): self._tags.extend(tags) def refresh(self): - """Ask all tabs to refresh themselves. """ self._refresh_tags() - for tab in self._tab_dict.values(): - tab.refresh() + self._general_tab.refresh() From 85a1bf29cbd2029e402a7d2d0d7dbc0557b2c6af Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 033/125] ChildAggregator --- shortcut_composer/templates/pie_menu.py | 2 +- .../templates/pie_menu_utils/pie_manager.py | 6 +- .../templates/pie_menu_utils/pie_widget.py | 106 ++++++++++++------ .../pie_menu_utils/widget_utils/edit_mode.py | 4 +- 4 files changed, 78 insertions(+), 40 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 37f91823..a04db97f 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -150,7 +150,7 @@ def on_every_key_release(self) -> None: return self.pie_manager.stop() - if widget := self.pie_widget.widget_holder.active: + if widget := self.pie_widget.child_aggregator.widget_holder.active: self._controller.set_value(widget.label.value) def _create_labels(self, values: List[T]) -> List[Label]: diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index bbed90b4..2bde1569 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -25,7 +25,7 @@ class PieManager: def __init__(self, pie_widget: PieWidget, pie_settings: PieSettings): self._pie_widget = pie_widget self._pie_settings = pie_settings - self._holder = self._pie_widget.widget_holder + self._holder = self._pie_widget.child_aggregator.widget_holder self._timer = Timer(self._handle_cursor, Config.get_sleep_time()) self._animator = LabelAnimator(pie_widget) @@ -44,7 +44,7 @@ def stop(self) -> None: """Hide the widget and stop the mouse tracking loop.""" self._pie_widget.hide() self._timer.stop() - for label in self._pie_widget.labels: + for label in self._pie_widget.child_aggregator: label.activation_progress.reset() def _handle_cursor(self) -> None: @@ -78,7 +78,7 @@ class LabelAnimator: def __init__(self, pie_widget: PieWidget) -> None: self._pie_widget = pie_widget - self._children = pie_widget.widget_holder + self._children = pie_widget.child_aggregator.widget_holder self._timer = Timer(self._update, Config.get_sleep_time()) def start(self) -> None: diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index 4142d39c..2f9e1f3e 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -3,7 +3,8 @@ from typing import List, TypeVar -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QPoint +from PyQt5.QtWidgets import QWidget from PyQt5.QtGui import ( QDragEnterEvent, QDragLeaveEvent, @@ -22,7 +23,6 @@ CirclePoints, PiePainter) - T = TypeVar('T') @@ -59,18 +59,6 @@ def __init__( AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) self.setGeometry(0, 0, style.widget_radius*2, style.widget_radius*2) - self._style = style - self.labels = labels - self.config = config - self._children_widgets: List[LabelWidget] = [] - self.widget_holder: WidgetHolder = WidgetHolder() - self._last_widget = None - self.is_edit_mode = False - - self._circle_points = CirclePoints( - center=self.center, - radius=self._style.pie_radius) - self.setAcceptDrops(True) self.setWindowFlags(( self.windowFlags() | # type: ignore @@ -81,7 +69,21 @@ def __init__( self.setStyleSheet("background: transparent;") self.setCursor(Qt.CrossCursor) - self._reset() + self._style = style + self.config = config + + self._last_widget = None + self.is_edit_mode = False + + self._circle_points = CirclePoints( + center=self.center, + radius=self._style.pie_radius) + + self.child_aggregator = ChildAggregator( + labels, + self._style, + self._circle_points, + self) @property def deadzone(self) -> float: @@ -91,7 +93,8 @@ def deadzone(self) -> float: def paintEvent(self, event: QPaintEvent) -> None: """Paint the entire widget using the Painter wrapper.""" with Painter(self, event) as painter: - PiePainter(painter, self.labels, self._style, self.is_edit_mode) + PiePainter(painter, list(self.child_aggregator), + self._style, self.is_edit_mode) def dragEnterEvent(self, e: QDragEnterEvent) -> None: """Start edit mode when one of the draggable children gets dragged.""" @@ -108,35 +111,74 @@ def dragMoveEvent(self, e: QDragMoveEvent) -> None: self._last_widget = source_widget if distance > self._style.widget_radius: - return self._remove_widget(source_widget) + return self.child_aggregator.remove(source_widget.label) if distance < self._style.deadzone_radius: return - if source_widget.label not in self.labels: - self.labels.append(source_widget.label) - return self._reset() + if source_widget.label not in self.child_aggregator: + return self.child_aggregator.append(source_widget.label) + + self._swap_if_needed(pos, source_widget) + def _swap_if_needed(self, pos: QPoint, source_widget: LabelWidget): + holder = self.child_aggregator.widget_holder angle = self._circle_points.angle_from_point(pos) - source = self.widget_holder.on_label(source_widget.label) - held = self.widget_holder.on_angle(angle) + source = holder.on_label(source_widget.label) + held = holder.on_angle(angle) if held != source: - self.widget_holder.swap(held, source) + holder.swap(held, source) self.repaint() def dragLeaveEvent(self, e: QDragLeaveEvent) -> None: if self._last_widget is not None: - self._remove_widget(self._last_widget) + self.child_aggregator.remove(self._last_widget.label) return super().dragLeaveEvent(e) def show(self): self.set_draggable(False) return super().show() - def _remove_widget(self, widget: LabelWidget): - if widget.label in self.labels: - self.labels.remove(widget.label) + def set_draggable(self, draggable: bool): + for widget in self.child_aggregator.widgets(): + widget.draggable = draggable + + +class ChildAggregator: + def __init__( + self, + labels: List[Label], + style: PieStyle, + circle_points: CirclePoints, + owner: QWidget, + ) -> None: + self._labels = labels + self._style = style + self._circle_points = circle_points + self._owner = owner + + self._children_widgets: List[LabelWidget] = [] + self.widget_holder: WidgetHolder = WidgetHolder() + + self._reset() + + def append(self, label: Label): + self._labels.append(label) + self._reset() + + def remove(self, label: Label): + if label in self._labels: + self._labels.remove(label) self._reset() + def __iter__(self): + return iter(self._labels) + + def __bool__(self): + return bool(self._labels) + + def widgets(self): + return iter(self._children_widgets) + def _reset(self): for child in self._children_widgets: child.setParent(None) # type: ignore @@ -146,22 +188,18 @@ def _reset(self): self._reset_holder() def _reset_children(self) -> None: - for label in self.labels: + for label in self._labels: self._children_widgets.append( - create_label_widget(label, self._style, self)) + create_label_widget(label, self._style, self._owner)) def _reset_holder(self) -> None: children = self._children_widgets angle_iterator = self._circle_points.iterate_over_circle(len(children)) for child, (angle, point) in zip(children, angle_iterator): - child.setParent(self) + child.setParent(self._owner) child.show() child.label.angle = angle child.label.center = point child.move_to_label() self.widget_holder.add(child) - - def set_draggable(self, draggable: bool): - for widget in self._children_widgets: - widget.draggable = draggable diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py index bb3a88aa..37bdf248 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py @@ -52,8 +52,8 @@ def _write_settings(self) -> None: """If values were not hardcoded, but from config, write them back.""" widget = self._obj.pie_widget - if not widget.labels or widget.config is None: + if not widget.child_aggregator or widget.config is None: return - values = [widget.label.value for widget in widget.widget_holder] + values = [widget.label.value for widget in widget.child_aggregator.widget_holder] widget.config.order.write(values) From 86fa27241a769579b1fc910b1b34119716cada67 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 034/125] Refactored label and widget containers --- shortcut_composer/templates/pie_menu.py | 4 +- .../templates/pie_menu_utils/pie_manager.py | 29 ++++---- .../templates/pie_menu_utils/pie_widget.py | 67 +++++++++---------- .../pie_menu_utils/widget_utils/edit_mode.py | 4 +- .../widget_utils/widget_holder.py | 13 +--- 5 files changed, 53 insertions(+), 64 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index a04db97f..01eb6aba 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -150,8 +150,8 @@ def on_every_key_release(self) -> None: return self.pie_manager.stop() - if widget := self.pie_widget.child_aggregator.widget_holder.active: - self._controller.set_value(widget.label.value) + if label := self.pie_widget.active: + self._controller.set_value(label.value) def _create_labels(self, values: List[T]) -> List[Label]: """Wrap values into paintable label objects with position info.""" diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index 2bde1569..1c1d748b 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -9,7 +9,7 @@ from composer_utils.config import Config from .settings_gui import PieSettings from .pie_widget import PieWidget -from .label_widget import LabelWidget +from .label import Label from .widget_utils import CirclePoints @@ -25,7 +25,7 @@ class PieManager: def __init__(self, pie_widget: PieWidget, pie_settings: PieSettings): self._pie_widget = pie_widget self._pie_settings = pie_settings - self._holder = self._pie_widget.child_aggregator.widget_holder + self._holder = pie_widget.aggregator.widget_holder self._timer = Timer(self._handle_cursor, Config.get_sleep_time()) self._animator = LabelAnimator(pie_widget) @@ -44,7 +44,7 @@ def stop(self) -> None: """Hide the widget and stop the mouse tracking loop.""" self._pie_widget.hide() self._timer.stop() - for label in self._pie_widget.child_aggregator: + for label in self._pie_widget.aggregator: label.activation_progress.reset() def _handle_cursor(self) -> None: @@ -57,15 +57,15 @@ def _handle_cursor(self) -> None: cursor = QCursor().pos() if self._circle.distance(cursor) < self._pie_widget.deadzone: - return self._set_active_widget(None) + return self._set_active_label(None) angle = self._circle.angle_from_point(cursor) - self._set_active_widget(self._holder.on_angle(angle)) + self._set_active_label(self._holder.on_angle(angle).label) - def _set_active_widget(self, widget: Optional[LabelWidget]) -> None: + def _set_active_label(self, label: Optional[Label]) -> None: """Mark label as active and start animating the change.""" - if self._holder.active != widget: - self._holder.active = widget + if self._pie_widget.active != label: + self._pie_widget.active = label self._animator.start() @@ -78,7 +78,6 @@ class LabelAnimator: def __init__(self, pie_widget: PieWidget) -> None: self._pie_widget = pie_widget - self._children = pie_widget.child_aggregator.widget_holder self._timer = Timer(self._update, Config.get_sleep_time()) def start(self) -> None: @@ -87,14 +86,14 @@ def start(self) -> None: def _update(self) -> None: """Move all labels to next animation state. End animation if needed.""" - for widget in self._children: - if self._children.active == widget: - widget.label.activation_progress.up() + for label in self._pie_widget.aggregator: + if self._pie_widget.active == label: + label.activation_progress.up() else: - widget.label.activation_progress.down() + label.activation_progress.down() self._pie_widget.repaint() - for widget in self._children: - if widget.label.activation_progress.value not in (0, 1): + for label in self._pie_widget.aggregator: + if label.activation_progress.value not in (0, 1): return self._timer.stop() diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index 2f9e1f3e..b02d9336 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import List, TypeVar +from typing import List, TypeVar, Optional from PyQt5.QtCore import Qt, QPoint from PyQt5.QtWidgets import QWidget @@ -70,17 +70,19 @@ def __init__( self.setCursor(Qt.CrossCursor) self._style = style + self._labels = labels self.config = config - self._last_widget = None + self.active: Optional[Label] = None self.is_edit_mode = False + self._last_widget = None self._circle_points = CirclePoints( center=self.center, radius=self._style.pie_radius) - self.child_aggregator = ChildAggregator( - labels, + self.aggregator = ChildAggregator( + self._labels, self._style, self._circle_points, self) @@ -93,8 +95,7 @@ def deadzone(self) -> float: def paintEvent(self, event: QPaintEvent) -> None: """Paint the entire widget using the Painter wrapper.""" with Painter(self, event) as painter: - PiePainter(painter, list(self.child_aggregator), - self._style, self.is_edit_mode) + PiePainter(painter, self._labels, self._style, self.is_edit_mode) def dragEnterEvent(self, e: QDragEnterEvent) -> None: """Start edit mode when one of the draggable children gets dragged.""" @@ -111,27 +112,23 @@ def dragMoveEvent(self, e: QDragMoveEvent) -> None: self._last_widget = source_widget if distance > self._style.widget_radius: - return self.child_aggregator.remove(source_widget.label) + return self.aggregator.remove(source_widget.label) if distance < self._style.deadzone_radius: return - if source_widget.label not in self.child_aggregator: - return self.child_aggregator.append(source_widget.label) + if source_widget.label not in self.aggregator: + return self.aggregator.append(source_widget.label) - self._swap_if_needed(pos, source_widget) - - def _swap_if_needed(self, pos: QPoint, source_widget: LabelWidget): - holder = self.child_aggregator.widget_holder + _a = self.aggregator.widget_holder.on_label(source_widget.label) angle = self._circle_points.angle_from_point(pos) - source = holder.on_label(source_widget.label) - held = holder.on_angle(angle) - if held != source: - holder.swap(held, source) + _b = self.aggregator.widget_holder.on_angle(angle) + if _a != _b: + self.aggregator.swap(_a.label, _b.label) self.repaint() def dragLeaveEvent(self, e: QDragLeaveEvent) -> None: if self._last_widget is not None: - self.child_aggregator.remove(self._last_widget.label) + self.aggregator.remove(self._last_widget.label) return super().dragLeaveEvent(e) def show(self): @@ -139,7 +136,7 @@ def show(self): return super().show() def set_draggable(self, draggable: bool): - for widget in self.child_aggregator.widgets(): + for widget in self.aggregator.widgets(): widget.draggable = draggable @@ -156,9 +153,7 @@ def __init__( self._circle_points = circle_points self._owner = owner - self._children_widgets: List[LabelWidget] = [] self.widget_holder: WidgetHolder = WidgetHolder() - self._reset() def append(self, label: Label): @@ -170,6 +165,15 @@ def remove(self, label: Label): self._labels.remove(label) self._reset() + def swap(self, _a: Label, _b: Label): + _a.swap_locations(_b) + + idx_a, idx_b = self._labels.index(_a), self._labels.index(_b) + self._labels[idx_b] = _a + self._labels[idx_a] = _b + + self._reset() + def __iter__(self): return iter(self._labels) @@ -177,26 +181,21 @@ def __bool__(self): return bool(self._labels) def widgets(self): - return iter(self._children_widgets) + return iter(self.widget_holder) - def _reset(self): - for child in self._children_widgets: + def _reset(self) -> None: + for child in self.widget_holder: child.setParent(None) # type: ignore - self._children_widgets.clear() self.widget_holder.clear() - self._reset_children() - self._reset_holder() - def _reset_children(self) -> None: + children_widgets: List[LabelWidget] = [] for label in self._labels: - self._children_widgets.append( + children_widgets.append( create_label_widget(label, self._style, self._owner)) - def _reset_holder(self) -> None: - children = self._children_widgets - angle_iterator = self._circle_points.iterate_over_circle(len(children)) - - for child, (angle, point) in zip(children, angle_iterator): + angle_iterator = self._circle_points.iterate_over_circle( + len(children_widgets)) + for child, (angle, point) in zip(children_widgets, angle_iterator): child.setParent(self._owner) child.show() child.label.angle = angle diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py index 37bdf248..4e5fdd69 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py @@ -52,8 +52,8 @@ def _write_settings(self) -> None: """If values were not hardcoded, but from config, write them back.""" widget = self._obj.pie_widget - if not widget.child_aggregator or widget.config is None: + if not widget.aggregator or widget.config is None: return - values = [widget.label.value for widget in widget.child_aggregator.widget_holder] + values = [label.value for label in widget.aggregator] widget.config.order.write(values) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py index 0ee704a7..cf90396c 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py @@ -1,7 +1,8 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import Dict, Iterator, Optional +# from typing import Dict, Iterator, Optional +from typing import Dict, Iterator from ..label_widget import LabelWidget from ..label import Label @@ -16,7 +17,6 @@ class WidgetHolder: def __init__(self): self._widgets: Dict[int, LabelWidget] = {} - self.active: Optional[LabelWidget] = None def add(self, widget: LabelWidget) -> None: """Add a new LabelWidget to the holder.""" @@ -51,17 +51,8 @@ def angle(self, widget: LabelWidget) -> int: return angle raise ValueError(f"{widget} not in holder.") - def swap(self, _a: LabelWidget, _b: LabelWidget) -> None: - """Swap two LabelWidgets that are already in the holder.""" - _a.label.swap_locations(_b.label) - key_a, key_b = self.angle(_a), self.angle(_b) - self._widgets[key_a], self._widgets[key_b] = _b, _a - _a.move_to_label() - _b.move_to_label() - def clear(self): self._widgets = {} - self.active = None def __iter__(self) -> Iterator[LabelWidget]: """Iterate over all held LabelWidgets.""" From a445a112cc9d8d29d91ca20488d88dd461f2a41c Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 035/125] Renamed and moved LabelHolder to new file --- .../templates/pie_menu_utils/pie_manager.py | 8 +- .../templates/pie_menu_utils/pie_widget.py | 89 +++---------------- .../pie_menu_utils/widget_utils/__init__.py | 2 + .../pie_menu_utils/widget_utils/edit_mode.py | 4 +- .../widget_utils/label_holder.py | 76 ++++++++++++++++ 5 files changed, 95 insertions(+), 84 deletions(-) create mode 100644 shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index 1c1d748b..48c50759 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -25,7 +25,7 @@ class PieManager: def __init__(self, pie_widget: PieWidget, pie_settings: PieSettings): self._pie_widget = pie_widget self._pie_settings = pie_settings - self._holder = pie_widget.aggregator.widget_holder + self._holder = pie_widget.label_holder.widget_holder self._timer = Timer(self._handle_cursor, Config.get_sleep_time()) self._animator = LabelAnimator(pie_widget) @@ -44,7 +44,7 @@ def stop(self) -> None: """Hide the widget and stop the mouse tracking loop.""" self._pie_widget.hide() self._timer.stop() - for label in self._pie_widget.aggregator: + for label in self._pie_widget.label_holder: label.activation_progress.reset() def _handle_cursor(self) -> None: @@ -86,14 +86,14 @@ def start(self) -> None: def _update(self) -> None: """Move all labels to next animation state. End animation if needed.""" - for label in self._pie_widget.aggregator: + for label in self._pie_widget.label_holder: if self._pie_widget.active == label: label.activation_progress.up() else: label.activation_progress.down() self._pie_widget.repaint() - for label in self._pie_widget.aggregator: + for label in self._pie_widget.label_holder: if label.activation_progress.value not in (0, 1): return self._timer.stop() diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index b02d9336..08276e60 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -3,8 +3,7 @@ from typing import List, TypeVar, Optional -from PyQt5.QtCore import Qt, QPoint -from PyQt5.QtWidgets import QWidget +from PyQt5.QtCore import Qt from PyQt5.QtGui import ( QDragEnterEvent, QDragLeaveEvent, @@ -16,11 +15,10 @@ from .pie_style import PieStyle from .label import Label from .label_widget import LabelWidget -from .label_widget_utils import create_label_widget from .pie_config import PieConfig from .widget_utils import ( - WidgetHolder, CirclePoints, + LabelHolder, PiePainter) T = TypeVar('T') @@ -80,8 +78,7 @@ def __init__( self._circle_points = CirclePoints( center=self.center, radius=self._style.pie_radius) - - self.aggregator = ChildAggregator( + self.label_holder = LabelHolder( self._labels, self._style, self._circle_points, @@ -112,23 +109,23 @@ def dragMoveEvent(self, e: QDragMoveEvent) -> None: self._last_widget = source_widget if distance > self._style.widget_radius: - return self.aggregator.remove(source_widget.label) + return self.label_holder.remove(source_widget.label) if distance < self._style.deadzone_radius: return - if source_widget.label not in self.aggregator: - return self.aggregator.append(source_widget.label) + if source_widget.label not in self.label_holder: + return self.label_holder.append(source_widget.label) - _a = self.aggregator.widget_holder.on_label(source_widget.label) + _a = self.label_holder.widget_holder.on_label(source_widget.label) angle = self._circle_points.angle_from_point(pos) - _b = self.aggregator.widget_holder.on_angle(angle) + _b = self.label_holder.widget_holder.on_angle(angle) if _a != _b: - self.aggregator.swap(_a.label, _b.label) + self.label_holder.swap(_a.label, _b.label) self.repaint() def dragLeaveEvent(self, e: QDragLeaveEvent) -> None: if self._last_widget is not None: - self.aggregator.remove(self._last_widget.label) + self.label_holder.remove(self._last_widget.label) return super().dragLeaveEvent(e) def show(self): @@ -136,69 +133,5 @@ def show(self): return super().show() def set_draggable(self, draggable: bool): - for widget in self.aggregator.widgets(): + for widget in self.label_holder.widgets(): widget.draggable = draggable - - -class ChildAggregator: - def __init__( - self, - labels: List[Label], - style: PieStyle, - circle_points: CirclePoints, - owner: QWidget, - ) -> None: - self._labels = labels - self._style = style - self._circle_points = circle_points - self._owner = owner - - self.widget_holder: WidgetHolder = WidgetHolder() - self._reset() - - def append(self, label: Label): - self._labels.append(label) - self._reset() - - def remove(self, label: Label): - if label in self._labels: - self._labels.remove(label) - self._reset() - - def swap(self, _a: Label, _b: Label): - _a.swap_locations(_b) - - idx_a, idx_b = self._labels.index(_a), self._labels.index(_b) - self._labels[idx_b] = _a - self._labels[idx_a] = _b - - self._reset() - - def __iter__(self): - return iter(self._labels) - - def __bool__(self): - return bool(self._labels) - - def widgets(self): - return iter(self.widget_holder) - - def _reset(self) -> None: - for child in self.widget_holder: - child.setParent(None) # type: ignore - self.widget_holder.clear() - - children_widgets: List[LabelWidget] = [] - for label in self._labels: - children_widgets.append( - create_label_widget(label, self._style, self._owner)) - - angle_iterator = self._circle_points.iterate_over_circle( - len(children_widgets)) - for child, (angle, point) in zip(children_widgets, angle_iterator): - child.setParent(self._owner) - child.show() - child.label.angle = angle - child.label.center = point - child.move_to_label() - self.widget_holder.add(child) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py index fd578206..80bd419b 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py @@ -6,6 +6,7 @@ from .circle_points import CirclePoints from .widget_holder import WidgetHolder from .accept_button import AcceptButton +from .label_holder import LabelHolder from .pie_painter import PiePainter from .edit_mode import EditMode @@ -13,6 +14,7 @@ "CirclePoints", "WidgetHolder", "AcceptButton", + "LabelHolder", "PiePainter", "EditMode", ] diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py index 4e5fdd69..bdad3bb1 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py @@ -52,8 +52,8 @@ def _write_settings(self) -> None: """If values were not hardcoded, but from config, write them back.""" widget = self._obj.pie_widget - if not widget.aggregator or widget.config is None: + if not widget.label_holder or widget.config is None: return - values = [label.value for label in widget.aggregator] + values = [label.value for label in widget.label_holder] widget.config.order.write(values) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py new file mode 100644 index 00000000..360f1643 --- /dev/null +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py @@ -0,0 +1,76 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import List + +from PyQt5.QtWidgets import QWidget + +from ..pie_style import PieStyle +from ..label import Label +from ..label_widget import LabelWidget +from ..label_widget_utils import create_label_widget +from .widget_holder import WidgetHolder +from .circle_points import CirclePoints + + +class LabelHolder: + def __init__( + self, + labels: List[Label], + style: PieStyle, + circle_points: CirclePoints, + owner: QWidget, + ) -> None: + self._labels = labels + self._style = style + self._circle_points = circle_points + self._owner = owner + + self.widget_holder: WidgetHolder = WidgetHolder() + self._reset() + + def append(self, label: Label): + self._labels.append(label) + self._reset() + + def remove(self, label: Label): + if label in self._labels: + self._labels.remove(label) + self._reset() + + def swap(self, _a: Label, _b: Label): + _a.swap_locations(_b) + + idx_a, idx_b = self._labels.index(_a), self._labels.index(_b) + self._labels[idx_b] = _a + self._labels[idx_a] = _b + + self._reset() + + def __iter__(self): + return iter(self._labels) + + def __bool__(self): + return bool(self._labels) + + def widgets(self): + return iter(self.widget_holder) + + def _reset(self) -> None: + for child in self.widget_holder: + child.setParent(None) # type: ignore + self.widget_holder.clear() + + children_widgets: List[LabelWidget] = [] + for label in self._labels: + children_widgets.append( + create_label_widget(label, self._style, self._owner)) + + angles = self._circle_points.iterate_over_circle(len(self._labels)) + for child, (angle, point) in zip(children_widgets, angles): + child.setParent(self._owner) + child.show() + child.label.angle = angle + child.label.center = point + child.move_to_label() + self.widget_holder.add(child) From 9a2bbae2635c1fb7645d7700c059da0e1d42f54c Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 036/125] Small code clarity fixes --- shortcut_composer/templates/pie_menu_utils/pie_manager.py | 4 ++-- shortcut_composer/templates/pie_menu_utils/pie_widget.py | 2 +- .../templates/pie_menu_utils/widget_utils/label_holder.py | 3 --- .../templates/pie_menu_utils/widget_utils/widget_holder.py | 1 - 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index 48c50759..e6303e60 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -25,7 +25,6 @@ class PieManager: def __init__(self, pie_widget: PieWidget, pie_settings: PieSettings): self._pie_widget = pie_widget self._pie_settings = pie_settings - self._holder = pie_widget.label_holder.widget_holder self._timer = Timer(self._handle_cursor, Config.get_sleep_time()) self._animator = LabelAnimator(pie_widget) @@ -60,7 +59,8 @@ def _handle_cursor(self) -> None: return self._set_active_label(None) angle = self._circle.angle_from_point(cursor) - self._set_active_label(self._holder.on_angle(angle).label) + holder = self._pie_widget.label_holder.widget_holder + self._set_active_label(holder.on_angle(angle).label) def _set_active_label(self, label: Optional[Label]) -> None: """Mark label as active and start animating the change.""" diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index 08276e60..5ea814d2 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -133,5 +133,5 @@ def show(self): return super().show() def set_draggable(self, draggable: bool): - for widget in self.label_holder.widgets(): + for widget in self.label_holder.widget_holder: widget.draggable = draggable diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py index 360f1643..0af10a73 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py @@ -53,9 +53,6 @@ def __iter__(self): def __bool__(self): return bool(self._labels) - def widgets(self): - return iter(self.widget_holder) - def _reset(self) -> None: for child in self.widget_holder: child.setParent(None) # type: ignore diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py index cf90396c..8c728115 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -# from typing import Dict, Iterator, Optional from typing import Dict, Iterator from ..label_widget import LabelWidget from ..label import Label From 5bd4037a5fd5c72a217b45559017bf49f7e98a15 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 037/125] PieSettings display responsiveness fixes --- .../templates/pie_menu_utils/pie_manager.py | 3 +++ .../pie_menu_utils/settings_gui/enum_pie_settings.py | 9 +++++++-- .../pie_menu_utils/settings_gui/pie_settings.py | 1 + .../templates/pie_menu_utils/settings_gui/scroll_area.py | 1 - 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index e6303e60..1b9e0d63 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -35,7 +35,10 @@ def start(self) -> None: self._pie_widget.move_center(QCursor().pos()) self._pie_widget.show() + self._pie_settings.show() self._pie_settings.move_to_pie_side() + self._pie_settings.hide() + self._circle = CirclePoints(self._pie_widget.center_global, 0) self._timer.start() diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index 08ece91b..eef78e6b 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -1,4 +1,5 @@ from typing import List +from copy import copy from PyQt5.QtWidgets import QVBoxLayout, QTabWidget @@ -25,13 +26,17 @@ def __init__( parent) tab_holder = QTabWidget() + + new_style = copy(self._style) + new_style.icon_radius_scale = 1.0 + + self._action_values = ScrollArea(values, new_style, 3) + tab_holder.addTab(self._action_values, "Action values") self._local_settings = ConfigFormWidget([ ConfigSpinBox(pie_config.pie_radius_scale, self, 0.05, 4), ConfigSpinBox(pie_config.icon_radius_scale, self, 0.05, 4), ]) tab_holder.addTab(self._local_settings, "Local settings") - self._action_values = ScrollArea(values, self._style, 3) - tab_holder.addTab(self._action_values, "Action values") layout = QVBoxLayout(self) layout.addWidget(tab_holder) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py index 08fcbcaf..36534a09 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py @@ -20,6 +20,7 @@ def __init__( parent=None ) -> None: AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) + self.setMinimumHeight(style.widget_radius*2) self.setAcceptDrops(True) self.setWindowFlags(( self.windowFlags() | # type: ignore diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py index 43d121e3..1970d866 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py @@ -20,7 +20,6 @@ def __init__( parent=None ) -> None: super().__init__(parent) - self.setFixedHeight(style.widget_radius*2) self.setFixedWidth(round(style.icon_radius*(2*columns + 1))) self.setWidgetResizable(True) From 02ee3b9c012b9a8220bde676a87bfc729d22e046 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 038/125] Make settings button look properly --- shortcut_composer/templates/pie_menu.py | 28 ++++++++-- .../templates/pie_menu_utils/pie_style.py | 4 ++ .../pie_menu_utils/widget_utils/__init__.py | 4 +- .../widget_utils/accept_button.py | 56 ------------------- .../widget_utils/round_button.py | 52 +++++++++++++++++ 5 files changed, 82 insertions(+), 62 deletions(-) delete mode 100644 shortcut_composer/templates/pie_menu_utils/widget_utils/accept_button.py create mode 100644 shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 01eb6aba..02113b9c 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -5,8 +5,10 @@ from copy import copy from enum import Enum +from PyQt5.QtCore import QPoint from PyQt5.QtGui import QColor +from api_krita import Krita from core_components import Controller, Instruction from input_adapter import ComplexAction from .pie_menu_utils import ( @@ -16,7 +18,7 @@ PieWidget, PieStyle, Label) -from .pie_menu_utils.widget_utils import EditMode, AcceptButton +from .pie_menu_utils.widget_utils import EditMode, RoundButton T = TypeVar('T') @@ -129,10 +131,28 @@ def reset(self): pie_widget=self.pie_widget, pie_settings=self.pie_settings) - self.settings_button = AcceptButton(self._style, self.pie_widget) - self.settings_button.clicked.connect(lambda: self._edit_mode.set(True)) + default_radius = self._style.setting_button_radius + radius = self._style.deadzone_radius + radius = int(radius) if radius != float("inf") else default_radius - self.accept_button = AcceptButton(self._style, self.pie_widget) + self.settings_button = RoundButton( + radius=default_radius, + icon_scale=1.1, + style=self._style, + icon=Krita.get_icon("properties"), + parent=self.pie_widget) + self.settings_button.clicked.connect(lambda: self._edit_mode.set(True)) + self._style.pie_radius + self.settings_button.move(QPoint( + self.pie_widget.width()-self.settings_button.width(), + self.pie_widget.height()-self.settings_button.height())) + + self.accept_button = RoundButton( + radius=radius, + icon_scale=1.5, + style=self._style, + icon=Krita.get_icon("dialog-ok"), + parent=self.pie_widget) self.accept_button.clicked.connect(lambda: self._edit_mode.set(False)) self.accept_button.move_center(self.pie_widget.center) self.accept_button.hide() diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index 50403a88..da448c16 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -70,6 +70,10 @@ def __init__( def set_items(self, items: list): self._items = items + @property + def setting_button_radius(self) -> int: + return round(30 * self._base_size) + @property def base_icon_radius(self) -> int: return round( diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py index 80bd419b..3478747e 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py @@ -5,7 +5,7 @@ from .circle_points import CirclePoints from .widget_holder import WidgetHolder -from .accept_button import AcceptButton +from .round_button import RoundButton from .label_holder import LabelHolder from .pie_painter import PiePainter from .edit_mode import EditMode @@ -13,7 +13,7 @@ __all__ = [ "CirclePoints", "WidgetHolder", - "AcceptButton", + "RoundButton", "LabelHolder", "PiePainter", "EditMode", diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/accept_button.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/accept_button.py deleted file mode 100644 index e9d7d08b..00000000 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/accept_button.py +++ /dev/null @@ -1,56 +0,0 @@ -from typing import Optional - -from PyQt5.QtWidgets import QWidget, QPushButton -from PyQt5.QtGui import QColor -from PyQt5.QtCore import Qt - -from ..pie_style import PieStyle -from api_krita.pyqt import BaseWidget -from api_krita import Krita - - -class AcceptButton(QPushButton, BaseWidget): - """Round button with a tick icon which uses provided PieStyle.""" - - def __init__(self, style: PieStyle, parent: Optional[QWidget] = None): - QPushButton.__init__(self, Krita.get_icon("dialog-ok"), "", parent) - if parent is None: - self.setWindowFlags(( - self.windowFlags() | # type: ignore - Qt.Tool | - Qt.FramelessWindowHint | - Qt.NoDropShadowWindowHint)) - self.setAttribute(Qt.WA_TranslucentBackground) - self.setStyleSheet("background: transparent;") - - self._style = style - self.hide() - if self._style.deadzone_radius != float("inf"): - self.radius = int(self._style.deadzone_radius) - else: - self.radius = 1 - self.setCursor(Qt.ArrowCursor) - self.setGeometry( - 0, 0, - self.radius*2, - self.radius*2) - - self.setStyleSheet(f""" - QPushButton [ - border: {self._style.border_thickness}px - {self._color_to_str(self._style.border_color)}; - border-radius: {self.radius}px; - border-style: outset; - background: {self._color_to_str(self._style.background_color)}; - qproperty-iconSize:{round(self.radius*1.5)}px; - ] - QPushButton:hover [ - background: {self._color_to_str(self._style.active_color)}; - ] - """.replace('[', '{').replace(']', '}') - ) - self.show() - - @staticmethod - def _color_to_str(color: QColor) -> str: return f'''rgba( - {color.red()}, {color.green()}, {color.blue()}, {color.alpha()})''' diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py new file mode 100644 index 00000000..b3f17276 --- /dev/null +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py @@ -0,0 +1,52 @@ +from typing import Optional + +from PyQt5.QtWidgets import QWidget, QPushButton +from PyQt5.QtGui import QColor, QIcon +from PyQt5.QtCore import Qt + +from ..pie_style import PieStyle +from api_krita.pyqt import BaseWidget + + +class RoundButton(QPushButton, BaseWidget): + """Round button with a tick icon which uses provided PieStyle.""" + + def __init__( + self, + radius: int, + icon_scale: float, + style: PieStyle, + icon: QIcon, + parent: Optional[QWidget] = None + ): + QPushButton.__init__(self, icon, "", parent) + self.setGeometry(0, 0, radius*2, radius*2) + self.setCursor(Qt.ArrowCursor) + self.setStyleSheet(f""" + QPushButton [ + border: {style.border_thickness}px + {self._color_to_str(style.border_color)}; + border-radius: {radius}px; + border-style: outset; + background: {self._color_to_str(style.background_color)}; + qproperty-iconSize:{round(radius*icon_scale)}px; + ] + QPushButton:hover [ + background: {self._color_to_str(style.active_color)}; + ] + """.replace('[', '{').replace(']', '}')) + + if parent is None: + self.setWindowFlags(( + self.windowFlags() | # type: ignore + Qt.Tool | + Qt.FramelessWindowHint | + Qt.NoDropShadowWindowHint)) + self.setAttribute(Qt.WA_TranslucentBackground) + self.setStyleSheet("background: transparent;") + + self.show() + + @staticmethod + def _color_to_str(color: QColor) -> str: return f'''rgba( + {color.red()}, {color.green()}, {color.blue()}, {color.alpha()})''' From 7f2ded3edfff754e49d8417fcc9672cf72c787dc Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 039/125] Disable fully emptying pies --- shortcut_composer/templates/pie_menu_utils/pie_widget.py | 2 +- .../templates/pie_menu_utils/widget_utils/label_holder.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index 5ea814d2..49b4c8fc 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -113,7 +113,7 @@ def dragMoveEvent(self, e: QDragMoveEvent) -> None: if distance < self._style.deadzone_radius: return - if source_widget.label not in self.label_holder: + if source_widget.label not in self.label_holder or not self._labels: return self.label_holder.append(source_widget.label) _a = self.label_holder.widget_holder.on_label(source_widget.label) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py index 0af10a73..07a4f481 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py @@ -34,7 +34,7 @@ def append(self, label: Label): self._reset() def remove(self, label: Label): - if label in self._labels: + if label in self._labels and len(self._labels) > 1: self._labels.remove(label) self._reset() From fc6f409a611fda6def13afd7b5dc02db96c6a58d Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 040/125] Disable removing labels in tag pies --- shortcut_composer/templates/pie_menu_utils/pie_config.py | 4 ++++ shortcut_composer/templates/pie_menu_utils/pie_widget.py | 1 + .../templates/pie_menu_utils/widget_utils/label_holder.py | 6 +++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 55644c49..82a91dee 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -12,6 +12,7 @@ class PieConfig: values: list order: FieldBase + allow_remove: bool def __init__( self, @@ -37,6 +38,8 @@ def __init__(self, *args): self.tag_name = ImmutableField( self.name, self._default_values.tag_name) self.order = ImmutablesListField(f"{self.name} values", [""]) + self.allow_remove = False + @property def values(self): @@ -54,6 +57,7 @@ def __init__(self, *args): self.order = EnumsListField( f"{self.name} values", self._default_values) + self.allow_remove = True @property def values(self): diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index 49b4c8fc..f5bbafcc 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -82,6 +82,7 @@ def __init__( self._labels, self._style, self._circle_points, + self.config.allow_remove, self) @property diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py index 07a4f481..c06421e7 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py @@ -19,11 +19,13 @@ def __init__( labels: List[Label], style: PieStyle, circle_points: CirclePoints, + allow_remove: bool, owner: QWidget, ) -> None: self._labels = labels self._style = style self._circle_points = circle_points + self._allow_remove = allow_remove self._owner = owner self.widget_holder: WidgetHolder = WidgetHolder() @@ -34,7 +36,9 @@ def append(self, label: Label): self._reset() def remove(self, label: Label): - if label in self._labels and len(self._labels) > 1: + if (label in self._labels + and len(self._labels) > 1 + and self._allow_remove): self._labels.remove(label) self._reset() From fb5b1bdc6016597818dc019f8aa40c948ca93fed Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 041/125] Autoreset pies at show to refresh tags and themes --- shortcut_composer/templates/pie_menu.py | 1 + .../templates/pie_menu_utils/widget_utils/edit_mode.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 02113b9c..87d60985 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -160,6 +160,7 @@ def reset(self): def on_key_press(self) -> None: """Show widget under mouse and start manager which repaints it.""" self._controller.refresh() + self.reset() self.pie_manager.start() super().on_key_press() diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py index bdad3bb1..90a5664f 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py @@ -43,7 +43,6 @@ def set_edit_mode_false(self): self._obj.pie_settings.hide() self._obj.accept_button.hide() self._obj.settings_button.show() - self._obj.reset() def swap_mode(self): self.set(not self._edit_mode) From 2e3d41b9ec51d2fa750a8a1007407eec6c1524e8 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 042/125] Fix an exception on Configre... apply --- shortcut_composer/composer_utils/settings_dialog.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shortcut_composer/composer_utils/settings_dialog.py b/shortcut_composer/composer_utils/settings_dialog.py index 3b0b3d0a..65aacb64 100644 --- a/shortcut_composer/composer_utils/settings_dialog.py +++ b/shortcut_composer/composer_utils/settings_dialog.py @@ -57,8 +57,7 @@ def show(self) -> None: def apply(self) -> None: """Ask all dialog zones to apply themselves.""" - for tab in self._tab_dict.values(): - tab.apply() + self._general_tab.apply() Krita.trigger_action("Reload Shortcut Composer") def ok(self) -> None: From 8695b89b8478c9ccdadba4e527be73dd62b70bb4 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 043/125] Draggable label widgets shine on hover --- .../templates/pie_menu_utils/label_widget.py | 19 ++++++++++++++++++- .../label_widget_utils/image_label_widget.py | 2 +- .../label_widget_utils/text_label_widget.py | 2 +- .../templates/pie_menu_utils/pie_manager.py | 1 - .../pie_menu_utils/widget_utils/edit_mode.py | 1 + 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget.py index 354bbae3..39fa070f 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from PyQt5.QtCore import Qt, QMimeData +from PyQt5.QtCore import Qt, QMimeData, QEvent from PyQt5.QtWidgets import QWidget from PyQt5.QtGui import QDrag, QPixmap, QMouseEvent @@ -25,6 +25,7 @@ def __init__( self.label = label self._style = style + self._hovered = False self.setCursor(Qt.ArrowCursor) def move_to_label(self) -> None: @@ -44,3 +45,19 @@ def mousePressEvent(self, e: QMouseEvent) -> None: drag.setPixmap(PixmapTransform.make_pixmap_round(pixmap)) drag.exec_(Qt.MoveAction) + + def enterEvent(self, e: QEvent) -> None: + self._hovered = True + self.repaint() + return super().enterEvent(e) + + def leaveEvent(self, e: QEvent) -> None: + self._hovered = False + self.repaint() + return super().leaveEvent(e) + + @property + def _border_color(self): + if self._hovered and self.draggable: + return self._style.active_color_dark + return self._style.border_color diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/image_label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/image_label_widget.py index 43a32bf2..7a25b38f 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/image_label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/image_label_widget.py @@ -36,7 +36,7 @@ def paintEvent(self, event: QPaintEvent) -> None: center=self.center, outer_radius=( self._style.icon_radius-self._style.border_thickness//2), - color=self._style.border_color, + color=self._border_color, thickness=self._style.border_thickness) painter.paint_pixmap(self.center, self.ready_image) diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/text_label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/text_label_widget.py index e4b70903..5d1ac4ab 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/text_label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/text_label_widget.py @@ -37,7 +37,7 @@ def paintEvent(self, event: QPaintEvent) -> None: painter.paint_wheel( center=self.center, outer_radius=self._style.icon_radius, - color=self._style.border_color, + color=self._border_color, thickness=self._style.border_thickness) def _create_pyqt_label(self) -> QLabel: diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index 1b9e0d63..deaf563e 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -44,7 +44,6 @@ def start(self) -> None: def stop(self) -> None: """Hide the widget and stop the mouse tracking loop.""" - self._pie_widget.hide() self._timer.stop() for label in self._pie_widget.label_holder: label.activation_progress.reset() diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py index 90a5664f..ba299c9d 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py @@ -29,6 +29,7 @@ def set(self, mode_to_set: bool) -> None: self._edit_mode = mode_to_set def set_edit_mode_true(self): + self._obj.pie_manager.stop() self._obj.pie_widget.set_draggable(True) self._obj.pie_widget.is_edit_mode = True self._obj.pie_widget.repaint() From 6a078bd7d5792d860597b63918b131b69e09379e Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 044/125] Don't fully initialize pie menus, as this is now done on reset --- shortcut_composer/templates/pie_menu.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 87d60985..4825bd0b 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -104,8 +104,6 @@ def __init__( self._background_color = background_color self._active_color = active_color - self.reset() - def reset(self): self._values = self._local_config.values self._labels = self._create_labels(self._values) @@ -171,6 +169,7 @@ def on_every_key_release(self) -> None: return self.pie_manager.stop() + self.pie_widget.hide() if label := self.pie_widget.active: self._controller.set_value(label.value) @@ -190,7 +189,6 @@ def _create_all_labels(self, values: List[T]) -> List[Label]: return self._create_labels(self._get_all_values(values)) def _get_all_values(self, values: List[T]) -> List[T]: - """Return all unused pie values.""" if not values: return [] From b9a07f2676ac7656f15d98723da5c0e92ee8f32c Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 045/125] Simplify PiePainter as different colors no longer needed --- .../pie_menu_utils/widget_utils/pie_painter.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_painter.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_painter.py index 44cc70da..3d33a791 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_painter.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_painter.py @@ -96,21 +96,10 @@ def _paint_active_pie(self) -> None: ) def _pick_pie_color(self, label: Label) -> QColor: - """ - Pick color of pie based on widget mode and animation progress. - - In edit mode color is different to create visual distinction. - Two similar colors are merged with animated opacity to - distinguish two consequtive pies from each other. - """ - if not self.edit_mode: - return self._overlay_colors( - self.style.active_color_dark, - self.style.active_color, - opacity=label.activation_progress.value) + """Pick color of pie based on widget mode and animation progress.""" return self._overlay_colors( - self.style.icon_color, - self.style.border_color, + self.style.active_color_dark, + self.style.active_color, opacity=label.activation_progress.value) @staticmethod From 7f852c7837d9e0a0750809f1d00442f3c676caa2 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 046/125] Hide reload composer action from user --- shortcut_composer/actions.action | 4 ---- shortcut_composer/shortcut_composer.py | 1 - 2 files changed, 5 deletions(-) diff --git a/shortcut_composer/actions.action b/shortcut_composer/actions.action index 1112e449..5b262740 100755 --- a/shortcut_composer/actions.action +++ b/shortcut_composer/actions.action @@ -108,10 +108,6 @@ 0 - - - 0 - 1 diff --git a/shortcut_composer/shortcut_composer.py b/shortcut_composer/shortcut_composer.py index ea765677..d8ec44ff 100755 --- a/shortcut_composer/shortcut_composer.py +++ b/shortcut_composer/shortcut_composer.py @@ -68,7 +68,6 @@ def _create_reload_action(self, window) -> QWidgetAction: return Krita.create_action( window=window, name="Reload Shortcut Composer", - group="tools/scripts", callback=self._reload_composer) def _create_settings_action( From 30dfa6319881ad216fc1685f753493b6e5fde45c Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 047/125] Color labels in Settings and Pie based on being used --- shortcut_composer/templates/pie_menu.py | 9 +++++---- .../templates/pie_menu_utils/dispatchers.py | 9 ++++++--- .../templates/pie_menu_utils/label_widget.py | 13 +++++++++++-- .../templates/pie_menu_utils/pie_widget.py | 3 ++- .../settings_gui/enum_pie_settings.py | 19 +++++++++++++++++-- .../settings_gui/preset_pie_settings.py | 2 ++ .../pie_menu_utils/widget_utils/__init__.py | 2 ++ .../widget_utils/label_holder.py | 5 ++++- .../widget_utils/notifying_list.py | 15 +++++++++++++++ 9 files changed, 64 insertions(+), 13 deletions(-) create mode 100644 shortcut_composer/templates/pie_menu_utils/widget_utils/notifying_list.py diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 4825bd0b..38b38128 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -18,7 +18,7 @@ PieWidget, PieStyle, Label) -from .pie_menu_utils.widget_utils import EditMode, RoundButton +from .pie_menu_utils.widget_utils import EditMode, RoundButton, NotifyingList T = TypeVar('T') @@ -120,6 +120,7 @@ def reset(self): self.pie_settings = create_pie_settings_window( style=unscaled_style, values=self._create_all_labels(self._values), + used_values=self._labels, pie_config=self._local_config) self.pie_widget = PieWidget( style=self._style, @@ -173,9 +174,9 @@ def on_every_key_release(self) -> None: if label := self.pie_widget.active: self._controller.set_value(label.value) - def _create_labels(self, values: List[T]) -> List[Label]: + def _create_labels(self, values: List[T]) -> NotifyingList[Label]: """Wrap values into paintable label objects with position info.""" - label_list = [] + label_list = NotifyingList() for value in values: try: label = self._controller.get_label(value) @@ -184,7 +185,7 @@ def _create_labels(self, values: List[T]) -> List[Label]: label_list.append(Label(value=value, display_value=label)) return label_list - def _create_all_labels(self, values: List[T]) -> List[Label]: + def _create_all_labels(self, values: List[T]) -> NotifyingList[Label]: """Create labels of all unused values.""" return self._create_labels(self._get_all_values(values)) diff --git a/shortcut_composer/templates/pie_menu_utils/dispatchers.py b/shortcut_composer/templates/pie_menu_utils/dispatchers.py index 153382c2..d8635795 100644 --- a/shortcut_composer/templates/pie_menu_utils/dispatchers.py +++ b/shortcut_composer/templates/pie_menu_utils/dispatchers.py @@ -7,6 +7,7 @@ from .label import Label from .pie_style import PieStyle from .pie_config import PieConfig, EnumPieConfig, PresetPieConfig +from .widget_utils import NotifyingList def create_local_config( @@ -20,14 +21,16 @@ def create_local_config( return PresetPieConfig(*args) return EnumPieConfig(*args) + def create_pie_settings_window( values: List[Label], + used_values: NotifyingList[Label], style: PieStyle, pie_config: PieConfig, parent=None ) -> PieSettings: if isinstance(pie_config, PresetPieConfig): - return PresetPieSettings(values, style, pie_config, parent) + return PresetPieSettings(values, used_values, style, pie_config, parent) elif isinstance(pie_config, EnumPieConfig): - return EnumPieSettings(values, style, pie_config, parent) - raise ValueError(f"Unknown pie config {pie_config}") \ No newline at end of file + return EnumPieSettings(values, used_values, style, pie_config, parent) + raise ValueError(f"Unknown pie config {pie_config}") diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget.py index 39fa070f..ad9e83c3 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget.py @@ -21,10 +21,11 @@ def __init__( ) -> None: super().__init__(parent) self.setGeometry(0, 0, style.icon_radius*2, style.icon_radius*2) + self.label = label self.draggable = True - self.label = label self._style = style + self._enabled = True self._hovered = False self.setCursor(Qt.ArrowCursor) @@ -32,6 +33,12 @@ def move_to_label(self) -> None: """Move the widget by providing a new center point.""" self.move_center(self.label.center) + def set_enabled(self, value: bool): + self._enabled = value + if not value: + self.draggable = False + self.repaint() + def mousePressEvent(self, e: QMouseEvent) -> None: """Initiate a drag loop for this Widget, so Widgets can be swapped.""" if e.buttons() != Qt.LeftButton or not self.draggable: @@ -58,6 +65,8 @@ def leaveEvent(self, e: QEvent) -> None: @property def _border_color(self): - if self._hovered and self.draggable: + if not self._enabled: return self._style.active_color_dark + if self._hovered and self.draggable: + return self._style.active_color return self._style.border_color diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index f5bbafcc..105dcd9e 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -17,6 +17,7 @@ from .label_widget import LabelWidget from .pie_config import PieConfig from .widget_utils import ( + NotifyingList, CirclePoints, LabelHolder, PiePainter) @@ -50,7 +51,7 @@ class PieWidget(AnimatedWidget, BaseWidget): def __init__( self, style: PieStyle, - labels: List[Label], + labels: NotifyingList, config: PieConfig, parent=None ): diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index eef78e6b..b1f8512c 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -4,6 +4,7 @@ from PyQt5.QtWidgets import QVBoxLayout, QTabWidget from composer_utils.config import ConfigFormWidget, ConfigSpinBox +from ..widget_utils import NotifyingList from ..label import Label from ..pie_style import PieStyle from ..pie_config import EnumPieConfig @@ -15,6 +16,7 @@ class EnumPieSettings(PieSettings): def __init__( self, values: List[Label], + used_values: NotifyingList[Label], style: PieStyle, pie_config: EnumPieConfig, parent=None @@ -25,11 +27,13 @@ def __init__( pie_config, parent) + self._used_values = used_values + tab_holder = QTabWidget() new_style = copy(self._style) new_style.icon_radius_scale = 1.0 - + self._action_values = ScrollArea(values, new_style, 3) tab_holder.addTab(self._action_values, "Action values") self._local_settings = ConfigFormWidget([ @@ -42,10 +46,21 @@ def __init__( layout.addWidget(tab_holder) self.setLayout(layout) + self._used_values.register_callback(self.refresh) + + def refresh(self): + for widget in self._action_values._children_list: + if widget.label in self._used_values: + widget.set_enabled(False) + widget.draggable = False + else: + widget.set_enabled(True) + widget.draggable = True + def show(self): self._local_settings.refresh() super().show() def hide(self) -> None: self._local_settings.apply() - super().hide() \ No newline at end of file + super().hide() diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py index 8ed9a3f0..a4d3dc89 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py @@ -7,6 +7,7 @@ ConfigComboBox, ConfigSpinBox) from ..label import Label +from ..widget_utils import NotifyingList from ..pie_style import PieStyle from ..pie_config import PresetPieConfig from .pie_settings import PieSettings @@ -16,6 +17,7 @@ class PresetPieSettings(PieSettings): def __init__( self, values: List[Label], + used_values: NotifyingList[Label], style: PieStyle, pie_config: PresetPieConfig, parent=None diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py index 3478747e..a5b9ab67 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py @@ -3,6 +3,7 @@ """Additional classes used by pie menu components.""" +from .notifying_list import NotifyingList from .circle_points import CirclePoints from .widget_holder import WidgetHolder from .round_button import RoundButton @@ -11,6 +12,7 @@ from .edit_mode import EditMode __all__ = [ + "NotifyingList", "CirclePoints", "WidgetHolder", "RoundButton", diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py index c06421e7..0c4d122b 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py @@ -11,12 +11,13 @@ from ..label_widget_utils import create_label_widget from .widget_holder import WidgetHolder from .circle_points import CirclePoints +from .notifying_list import NotifyingList class LabelHolder: def __init__( self, - labels: List[Label], + labels: NotifyingList, style: PieStyle, circle_points: CirclePoints, allow_remove: bool, @@ -75,3 +76,5 @@ def _reset(self) -> None: child.label.center = point child.move_to_label() self.widget_holder.add(child) + + self._labels.notify_about_change() diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/notifying_list.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/notifying_list.py new file mode 100644 index 00000000..8afa23dd --- /dev/null +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/notifying_list.py @@ -0,0 +1,15 @@ +from typing import Callable, List, TypeVar +T = TypeVar("T") + + +class NotifyingList(List[T]): + def __init__(self, *args, **kwargs): + self._callbacks = [] + super().__init__(*args, **kwargs) + + def register_callback(self, callback: Callable[[], None]): + self._callbacks.append(callback) + + def notify_about_change(self): + for callback in self._callbacks: + callback() From a20cf8d6b7d22b943c334969c07b032592717201 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 048/125] WIP: improved generic typing --- shortcut_composer/actions.py | 3 ++- .../core_components/controller_base.py | 27 ++++++++++++++----- .../controllers/canvas_controllers.py | 6 ++--- .../controllers/core_controllers.py | 8 +++--- .../controllers/document_controllers.py | 6 ++--- .../controllers/node_controllers.py | 23 ++++++++-------- .../controllers/view_controllers.py | 13 +++++---- shortcut_composer/data_components/slider.py | 10 ++++--- shortcut_composer/templates/cursor_tracker.py | 14 +++++----- .../mouse_tracker_utils/slider_handler.py | 9 ++++--- .../templates/multiple_assignment.py | 2 +- shortcut_composer/templates/pie_menu.py | 2 +- shortcut_composer/templates/temporary_key.py | 6 ++--- 13 files changed, 74 insertions(+), 55 deletions(-) diff --git a/shortcut_composer/actions.py b/shortcut_composer/actions.py index e4b610be..0291a26e 100644 --- a/shortcut_composer/actions.py +++ b/shortcut_composer/actions.py @@ -27,7 +27,8 @@ infinity = float("inf") -def create_actions() -> List[ComplexAction]: return [ +# def create_actions() -> List[ComplexAction]: return [ +def create_actions(): return [ # Switch between FREEHAND BRUSH and the MOVE tool templates.TemporaryKey( diff --git a/shortcut_composer/core_components/controller_base.py b/shortcut_composer/core_components/controller_base.py index 2f624619..293d899b 100644 --- a/shortcut_composer/core_components/controller_base.py +++ b/shortcut_composer/core_components/controller_base.py @@ -1,17 +1,30 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import Any, Union +from typing import Optional, Union, Generic, TypeVar from PyQt5.QtGui import QPixmap, QIcon from api_krita.pyqt import Text +T = TypeVar("T") -class Controller: + +class Controller(Generic[T]): """Component that allows to get and set a specific property of krita.""" - default_value: Any = None + default_value: Optional[T] = None + + def refresh(self) -> None: + """Refresh stored krita components.""" + ... + + def get_value(self) -> T: + """Get handled value from krita.""" + ... + + def set_value(self, value: T) -> None: + """Set handled value in krita.""" + ... - def refresh(self) -> None: """Refresh stored krita components.""" - def get_value(self) -> Any: """Get handled value from krita.""" - def set_value(self, value: Any) -> None: """Set handled value in krita.""" - def get_label(self, value: Any) -> Union[Text, QPixmap, QIcon, None]: ... + def get_label(self, value: T) -> Union[Text, QPixmap, QIcon, None]: + """Get value representation that can be displayed in GUI,""" + ... diff --git a/shortcut_composer/core_components/controllers/canvas_controllers.py b/shortcut_composer/core_components/controllers/canvas_controllers.py index a66250f0..7ba2e012 100644 --- a/shortcut_composer/core_components/controllers/canvas_controllers.py +++ b/shortcut_composer/core_components/controllers/canvas_controllers.py @@ -6,7 +6,7 @@ from ..controller_base import Controller -class CanvasBasedController(Controller): +class CanvasBasedController: """Family of controllers which operate on values from active document.""" def refresh(self): @@ -14,7 +14,7 @@ def refresh(self): self.canvas = Krita.get_active_canvas() -class CanvasZoomController(CanvasBasedController): +class CanvasZoomController(CanvasBasedController, Controller[float]): """ Gives access to `zoom`. @@ -34,7 +34,7 @@ def get_label(self, value: float) -> Text: return Text(f"{round(value/100, 2)}") -class CanvasRotationController(CanvasBasedController): +class CanvasRotationController(CanvasBasedController, Controller[float]): """ Gives access to `canvas rotation` in degrees. diff --git a/shortcut_composer/core_components/controllers/core_controllers.py b/shortcut_composer/core_components/controllers/core_controllers.py index 730e80ea..b00f18cb 100644 --- a/shortcut_composer/core_components/controllers/core_controllers.py +++ b/shortcut_composer/core_components/controllers/core_controllers.py @@ -12,7 +12,7 @@ from ..controller_base import Controller -class ToolController(Controller): +class ToolController(Controller[Tool]): """ Gives access to tools from toolbox. @@ -36,7 +36,7 @@ def get_label(self, value: Tool) -> QIcon: return value.icon -class TransformModeController(Controller): +class TransformModeController(Controller[TransformMode]): """ Gives access to tools from toolbox. @@ -66,7 +66,7 @@ def get_label(self, value: Tool) -> QIcon: @dataclass -class ToggleController(Controller): +class ToggleController(Controller[bool]): """ Gives access to picked krita toggle action. @@ -86,7 +86,7 @@ def set_value(self, value: bool) -> None: @dataclass -class UndoController(Controller): +class UndoController(Controller[float]): """ Gives access to `undo` and `redo` actions. diff --git a/shortcut_composer/core_components/controllers/document_controllers.py b/shortcut_composer/core_components/controllers/document_controllers.py index 49ab0aa6..d60ba284 100644 --- a/shortcut_composer/core_components/controllers/document_controllers.py +++ b/shortcut_composer/core_components/controllers/document_controllers.py @@ -7,7 +7,7 @@ from ..controller_base import Controller -class DocumentBasedController(Controller): +class DocumentBasedController: """Family of controllers which operate on values from active document.""" def refresh(self): @@ -15,7 +15,7 @@ def refresh(self): self.document = Krita.get_active_document() -class ActiveLayerController(DocumentBasedController): +class ActiveLayerController(DocumentBasedController, Controller[Node]): """ Gives access to nodes (layers, groups, masks...) from layer stack. @@ -33,7 +33,7 @@ def set_value(self, value: Node) -> None: self.document.active_node = value -class TimeController(DocumentBasedController): +class TimeController(DocumentBasedController, Controller[int]): """ Gives access to animation timeline. diff --git a/shortcut_composer/core_components/controllers/node_controllers.py b/shortcut_composer/core_components/controllers/node_controllers.py index e317eb1d..1441efa4 100644 --- a/shortcut_composer/core_components/controllers/node_controllers.py +++ b/shortcut_composer/core_components/controllers/node_controllers.py @@ -1,13 +1,14 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later +from typing import TypeVar from api_krita import Krita from api_krita.enums import BlendingMode, NodeType from api_krita.pyqt import Text, Colorizer from ..controller_base import Controller -class NodeBasedController(Controller): +class NodeBasedController: """Family of controllers which operate on values from active node.""" def refresh(self): @@ -16,12 +17,12 @@ def refresh(self): self.active_node = self.active_document.active_node -class LayerOpacityController(NodeBasedController): +class LayerOpacityController(NodeBasedController, Controller[int]): """ - Gives access to active layers' `blending mode`. + Gives access to active layers' `opacity` in %. - - Operates on `BlendingMode` - - Defaults to `BlendingMode.NORMAL` + - Operates on `integer` in range `0 to 100` + - Defaults to `100` """ default_value: int = 100 @@ -40,12 +41,12 @@ def get_label(self, value: int) -> Text: return Text(f"{value}%", Colorizer.percentage(value)) -class LayerBlendingModeController(NodeBasedController): +class LayerBlendingModeController(NodeBasedController, Controller[BlendingMode]): """ - Gives access to active layers' `opacity` in %. + Gives access to active layers' `blending mode`. - - Operates on `integer` in range `0 to 100` - - Defaults to `100` + - Operates on `BlendingMode` + - Defaults to `BlendingMode.NORMAL` """ default_value = BlendingMode.NORMAL @@ -64,7 +65,7 @@ def get_label(self, value: BlendingMode) -> Text: return Text(value.name[:3], Colorizer.blending_mode(value)) -class LayerVisibilityController(NodeBasedController): +class LayerVisibilityController(NodeBasedController, Controller[bool]): """ Gives access to active layers' `visibility`. @@ -85,7 +86,7 @@ def set_value(self, visibility: bool) -> None: self.active_document.refresh() -class CreateLayerWithBlendingController(NodeBasedController): +class CreateLayerWithBlendingController(NodeBasedController, Controller[BlendingMode]): """Creates Paint Layer with set Blending Mode.""" default_value = BlendingMode.NORMAL diff --git a/shortcut_composer/core_components/controllers/view_controllers.py b/shortcut_composer/core_components/controllers/view_controllers.py index c46d8d70..ece0589b 100644 --- a/shortcut_composer/core_components/controllers/view_controllers.py +++ b/shortcut_composer/core_components/controllers/view_controllers.py @@ -2,14 +2,13 @@ # SPDX-License-Identifier: GPL-3.0-or-later from PyQt5.QtGui import QPixmap, QImage - from api_krita import Krita from api_krita.enums import BlendingMode from api_krita.pyqt import Text, Colorizer from ..controller_base import Controller -class ViewBasedController(Controller): +class ViewBasedController: """Family of controllers which operate on values from active view.""" def refresh(self): @@ -17,7 +16,7 @@ def refresh(self): self.view = Krita.get_active_view() -class PresetController(ViewBasedController): +class PresetController(ViewBasedController, Controller[str]): """ Gives access to `presets`. @@ -40,7 +39,7 @@ def get_label(self, value: str) -> QPixmap: return QPixmap.fromImage(image) -class BrushSizeController(ViewBasedController): +class BrushSizeController(ViewBasedController, Controller[int]): """ Gives access to `brush size`. @@ -60,7 +59,7 @@ def get_label(self, value: float) -> Text: return Text(f"{round(value)}px") -class BlendingModeController(ViewBasedController): +class BlendingModeController(ViewBasedController, Controller[BlendingMode]): """ Gives access to `brush blending mode`. @@ -82,7 +81,7 @@ def get_label(self, value: BlendingMode) -> Text: return Text(value.name[:3], Colorizer.blending_mode(value)) -class OpacityController(ViewBasedController): +class OpacityController(ViewBasedController, Controller[int]): """ Gives access to `brush opacity` in %. @@ -104,7 +103,7 @@ def get_label(self, value: int) -> Text: return Text(f"{value}%", Colorizer.percentage(value)) -class FlowController(ViewBasedController): +class FlowController(ViewBasedController, Controller[int]): """ Gives access to `brush flow` in %. diff --git a/shortcut_composer/data_components/slider.py b/shortcut_composer/data_components/slider.py index 9ea653bc..7801dbdc 100644 --- a/shortcut_composer/data_components/slider.py +++ b/shortcut_composer/data_components/slider.py @@ -1,14 +1,16 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import Any, List, Union, Optional +from typing import List, Union, Optional, Generic, TypeVar from composer_utils.config import Config, FieldBase from core_components import Controller from .range import Range +T = TypeVar("T") -class Slider: + +class Slider(Generic[T]): """ Part of CursorTracker specifying what to do on single axis movement. @@ -60,8 +62,8 @@ class Slider: def __init__( self, - controller: Controller, - values: Union[List[Any], Range], + controller: Controller[T], + values: Union[List[T], Range], sensitivity_scale: float = 1.0, deadzone: Optional[int] = None, ) -> None: diff --git a/shortcut_composer/templates/cursor_tracker.py b/shortcut_composer/templates/cursor_tracker.py index caed504e..16064e63 100644 --- a/shortcut_composer/templates/cursor_tracker.py +++ b/shortcut_composer/templates/cursor_tracker.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import List, Optional +from typing import List, Optional, Generic, TypeVar from core_components import Instruction from data_components import Slider @@ -12,8 +12,10 @@ SliderHandler, ) +T = TypeVar("T") -class CursorTracker(ComplexAction): + +class CursorTracker(ComplexAction, Generic[T]): """ Switch values with horizontal or vertical mouse movement. @@ -61,8 +63,8 @@ class CursorTracker(ComplexAction): def __new__( cls, name: str, - horizontal_slider: Optional[Slider] = None, - vertical_slider: Optional[Slider] = None, + horizontal_slider: Optional[Slider[T]] = None, + vertical_slider: Optional[Slider[T]] = None, instructions: List[Instruction] = [], ) -> ComplexAction: """ @@ -108,7 +110,7 @@ def __new__( def __init__( self, name: str, - horizontal_slider: Optional[Slider] = None, - vertical_slider: Optional[Slider] = None, + horizontal_slider: Optional[Slider[T]] = None, + vertical_slider: Optional[Slider[T]] = None, instructions: List[Instruction] = [], ) -> None: ... diff --git a/shortcut_composer/templates/mouse_tracker_utils/slider_handler.py b/shortcut_composer/templates/mouse_tracker_utils/slider_handler.py index d91e3233..1fdc6c4a 100644 --- a/shortcut_composer/templates/mouse_tracker_utils/slider_handler.py +++ b/shortcut_composer/templates/mouse_tracker_utils/slider_handler.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import Callable, Iterable +from typing import Callable, Iterable, Generic, TypeVar from api_krita import Krita from api_krita.pyqt import Timer @@ -15,10 +15,11 @@ SliderValues, ) +T = TypeVar("T") MouseGetter = Callable[[], MouseInput] -class SliderHandler: +class SliderHandler(Generic[T]): """ When started, tracks the mouse, interprets it and sets corresponding value. @@ -48,7 +49,7 @@ class SliderHandler: phase in which case main loop will never be started. """ - def __init__(self, slider: Slider, is_horizontal: bool) -> None: + def __init__(self, slider: Slider[T], is_horizontal: bool) -> None: """Store the slider configuration, create value adapter.""" self._slider = slider self._to_cycle = self._create_slider_values(slider) @@ -129,7 +130,7 @@ def _pick_mouse_getter(self) -> MouseGetter: return lambda: MouseInput(-cursor.y()) @staticmethod - def _create_slider_values(slider: Slider) -> SliderValues: + def _create_slider_values(slider: Slider[T]) -> SliderValues[T]: """Return the right values adapter based on passed data type.""" if isinstance(slider.values, Iterable): return ListSliderValues(slider.values) diff --git a/shortcut_composer/templates/multiple_assignment.py b/shortcut_composer/templates/multiple_assignment.py index a5b5d88c..e44d6a82 100644 --- a/shortcut_composer/templates/multiple_assignment.py +++ b/shortcut_composer/templates/multiple_assignment.py @@ -61,7 +61,7 @@ class MultipleAssignment(ComplexAction, Generic[T]): def __init__( self, *, name: str, - controller: Controller, + controller: Controller[T], values: List[T], default_value: Optional[T] = None, instructions: List[Instruction] = [], diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 38b38128..5e640c0a 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -80,7 +80,7 @@ class PieMenu(ComplexAction, Generic[T]): def __init__( self, *, name: str, - controller: Controller, + controller: Controller[T], values: List[T], instructions: List[Instruction] = [], pie_radius_scale: float = 1.0, diff --git a/shortcut_composer/templates/temporary_key.py b/shortcut_composer/templates/temporary_key.py index ace46f6c..6277d7e3 100644 --- a/shortcut_composer/templates/temporary_key.py +++ b/shortcut_composer/templates/temporary_key.py @@ -59,7 +59,7 @@ class TemporaryKey(ComplexAction, Generic[T]): def __init__( self, *, name: str, - controller: Controller, + controller: Controller[T], high_value: T, low_value: Optional[T] = None, instructions: List[Instruction] = [], @@ -106,6 +106,6 @@ def on_long_key_release(self) -> None: super().on_long_key_release() self._set_low() - def _read_default_value(self, value: Optional[T]): + def _read_default_value(self, value: Optional[T]) -> T: """Read value from controller if it was not given.""" - return value if value else self._controller.default_value + return value if value is not None else self._controller.default_value # type: ignore From 8a370554ece2fb415436fd0842f62016facd0adf Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 049/125] Improved generic typing --- shortcut_composer/actions.py | 2 -- .../core_components/controllers/core_controllers.py | 1 + shortcut_composer/data_components/tag.py | 3 ++- shortcut_composer/templates/cursor_tracker.py | 7 ++++--- shortcut_composer/templates/multiple_assignment.py | 5 ++++- shortcut_composer/templates/pie_menu_utils/dispatchers.py | 8 +++++--- shortcut_composer/templates/pie_menu_utils/pie_config.py | 7 +++++-- shortcut_composer/templates/temporary_key.py | 5 ++++- 8 files changed, 25 insertions(+), 13 deletions(-) diff --git a/shortcut_composer/actions.py b/shortcut_composer/actions.py index 0291a26e..8c065174 100644 --- a/shortcut_composer/actions.py +++ b/shortcut_composer/actions.py @@ -16,7 +16,6 @@ from api_krita.enums import Tool, Toggle, BlendingMode, TransformMode from core_components import instructions, controllers -from input_adapter import ComplexAction from data_components import ( CurrentLayerStack, PickStrategy, @@ -27,7 +26,6 @@ infinity = float("inf") -# def create_actions() -> List[ComplexAction]: return [ def create_actions(): return [ # Switch between FREEHAND BRUSH and the MOVE tool diff --git a/shortcut_composer/core_components/controllers/core_controllers.py b/shortcut_composer/core_components/controllers/core_controllers.py index b00f18cb..7aa64bbf 100644 --- a/shortcut_composer/core_components/controllers/core_controllers.py +++ b/shortcut_composer/core_components/controllers/core_controllers.py @@ -99,6 +99,7 @@ class UndoController(Controller[float]): """ state = 0 + default_value = 0 def get_value(self) -> int: """Return remembered position on undo stack""" diff --git a/shortcut_composer/data_components/tag.py b/shortcut_composer/data_components/tag.py index 3c2a603a..5e177b3b 100644 --- a/shortcut_composer/data_components/tag.py +++ b/shortcut_composer/data_components/tag.py @@ -1,7 +1,8 @@ +from typing import List from api_krita.wrappers import Database -class Tag(list): +class Tag(List[str]): def __init__(self, tag_name: str): self.tag_name = tag_name self.extend(self._read_brushes()) diff --git a/shortcut_composer/templates/cursor_tracker.py b/shortcut_composer/templates/cursor_tracker.py index 16064e63..06870b48 100644 --- a/shortcut_composer/templates/cursor_tracker.py +++ b/shortcut_composer/templates/cursor_tracker.py @@ -13,9 +13,10 @@ ) T = TypeVar("T") +U = TypeVar("U") -class CursorTracker(ComplexAction, Generic[T]): +class CursorTracker(ComplexAction, Generic[T, U]): """ Switch values with horizontal or vertical mouse movement. @@ -64,7 +65,7 @@ def __new__( cls, name: str, horizontal_slider: Optional[Slider[T]] = None, - vertical_slider: Optional[Slider[T]] = None, + vertical_slider: Optional[Slider[U]] = None, instructions: List[Instruction] = [], ) -> ComplexAction: """ @@ -111,6 +112,6 @@ def __init__( self, name: str, horizontal_slider: Optional[Slider[T]] = None, - vertical_slider: Optional[Slider[T]] = None, + vertical_slider: Optional[Slider[U]] = None, instructions: List[Instruction] = [], ) -> None: ... diff --git a/shortcut_composer/templates/multiple_assignment.py b/shortcut_composer/templates/multiple_assignment.py index e44d6a82..7de731f7 100644 --- a/shortcut_composer/templates/multiple_assignment.py +++ b/shortcut_composer/templates/multiple_assignment.py @@ -107,4 +107,7 @@ def _reset_iterator(self) -> None: def _read_default_value(self, value: Optional[T]) -> T: """Read value from controller if it was not given.""" - return value if value is not None else self._controller.default_value + if (default := self._controller.default_value) is None: + raise ValueError( + "{self._controller} can't be used with MultipleAssignment.") + return value if value is not None else default diff --git a/shortcut_composer/templates/pie_menu_utils/dispatchers.py b/shortcut_composer/templates/pie_menu_utils/dispatchers.py index d8635795..1d5f5226 100644 --- a/shortcut_composer/templates/pie_menu_utils/dispatchers.py +++ b/shortcut_composer/templates/pie_menu_utils/dispatchers.py @@ -1,5 +1,5 @@ -from typing import List +from typing import List, TypeVar from data_components import Tag from .pie_config import PieConfig, PresetPieConfig, EnumPieConfig @@ -9,13 +9,15 @@ from .pie_config import PieConfig, EnumPieConfig, PresetPieConfig from .widget_utils import NotifyingList +T = TypeVar("T") + def create_local_config( name: str, - values: list, + values: List[T], pie_radius_scale: float, icon_radius_scale: float, -) -> PieConfig: +) -> PieConfig[T]: args = [name, values, pie_radius_scale, icon_radius_scale] if isinstance(values, Tag): return PresetPieConfig(*args) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 82a91dee..83b8ff86 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later +from typing import List, Generic, TypeVar from composer_utils.config import ( EnumsListField, ImmutablesListField, @@ -8,9 +9,11 @@ FieldBase) from data_components import Tag +T = TypeVar("T") -class PieConfig: - values: list + +class PieConfig(Generic[T]): + values: List[T] order: FieldBase allow_remove: bool diff --git a/shortcut_composer/templates/temporary_key.py b/shortcut_composer/templates/temporary_key.py index 6277d7e3..5b398805 100644 --- a/shortcut_composer/templates/temporary_key.py +++ b/shortcut_composer/templates/temporary_key.py @@ -108,4 +108,7 @@ def on_long_key_release(self) -> None: def _read_default_value(self, value: Optional[T]) -> T: """Read value from controller if it was not given.""" - return value if value is not None else self._controller.default_value # type: ignore + if (default := self._controller.default_value) is None: + raise ValueError( + "{self._controller} can't be used with TemporaryKeys.") + return value if value is not None else default From b3dbce8a3d4613ed45759ca4fe7bdc727c7fedfd Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 050/125] Don't recreate all widgets on pie key press --- shortcut_composer/templates/pie_menu.py | 68 ++++++++++++------- .../templates/pie_menu_utils/pie_widget.py | 9 +++ .../settings_gui/pie_settings.py | 4 ++ .../widget_utils/label_holder.py | 21 +++--- .../widget_utils/round_button.py | 29 ++++---- 5 files changed, 82 insertions(+), 49 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 5e640c0a..78f3033f 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -104,22 +104,24 @@ def __init__( self._background_color = background_color self._active_color = active_color - def reset(self): - self._values = self._local_config.values - self._labels = self._create_labels(self._values) + self._labels: NotifyingList[Label] = NotifyingList() + self._all_labels: NotifyingList[Label] = NotifyingList() + self._reset_labels(self._all_labels, self._get_all_values(values)) + + self._edit_mode = EditMode(self) + self._style = PieStyle( pie_radius_scale=self._local_config.pie_radius_scale.read(), icon_radius_scale=self._local_config.icon_radius_scale.read(), background_color=self._background_color, active_color=self._active_color) - unscaled_style = copy(self._style) + self._unscaled_style = copy(self._style) self._style.set_items(self._labels) - self._edit_mode = EditMode(self) self.pie_settings = create_pie_settings_window( - style=unscaled_style, - values=self._create_all_labels(self._values), + style=self._unscaled_style, + values=self._all_labels, used_values=self._labels, pie_config=self._local_config) self.pie_widget = PieWidget( @@ -130,31 +132,50 @@ def reset(self): pie_widget=self.pie_widget, pie_settings=self.pie_settings) + self.settings_button = RoundButton( + icon=Krita.get_icon("properties"), + parent=self.pie_widget) + self.settings_button.clicked.connect(lambda: self._edit_mode.set(True)) + + self.accept_button = RoundButton( + icon=Krita.get_icon("dialog-ok"), + parent=self.pie_widget) + self.accept_button.clicked.connect(lambda: self._edit_mode.set(False)) + self.accept_button.hide() + + def reset(self): + values = self._local_config.values + self._reset_labels(self._labels, values) + + self._style = PieStyle( + pie_radius_scale=self._local_config.pie_radius_scale.read(), + icon_radius_scale=self._local_config.icon_radius_scale.read(), + background_color=self._background_color, + active_color=self._active_color) + + self._unscaled_style = copy(self._style) + self._style.set_items(self._labels) + + self.pie_widget.reset(self._style) + self.pie_settings.reset(self._unscaled_style) + default_radius = self._style.setting_button_radius radius = self._style.deadzone_radius radius = int(radius) if radius != float("inf") else default_radius - self.settings_button = RoundButton( + self.settings_button.reset( radius=default_radius, icon_scale=1.1, - style=self._style, - icon=Krita.get_icon("properties"), - parent=self.pie_widget) - self.settings_button.clicked.connect(lambda: self._edit_mode.set(True)) - self._style.pie_radius + style=self._style) self.settings_button.move(QPoint( self.pie_widget.width()-self.settings_button.width(), self.pie_widget.height()-self.settings_button.height())) - self.accept_button = RoundButton( + self.accept_button.reset( radius=radius, icon_scale=1.5, - style=self._style, - icon=Krita.get_icon("dialog-ok"), - parent=self.pie_widget) - self.accept_button.clicked.connect(lambda: self._edit_mode.set(False)) + style=self._style) self.accept_button.move_center(self.pie_widget.center) - self.accept_button.hide() def on_key_press(self) -> None: """Show widget under mouse and start manager which repaints it.""" @@ -174,20 +195,15 @@ def on_every_key_release(self) -> None: if label := self.pie_widget.active: self._controller.set_value(label.value) - def _create_labels(self, values: List[T]) -> NotifyingList[Label]: + def _reset_labels(self, label_list: List[Label], values: List[T]) -> None: """Wrap values into paintable label objects with position info.""" - label_list = NotifyingList() + label_list.clear() for value in values: try: label = self._controller.get_label(value) except KeyError: continue label_list.append(Label(value=value, display_value=label)) - return label_list - - def _create_all_labels(self, values: List[T]) -> NotifyingList[Label]: - """Create labels of all unused values.""" - return self._create_labels(self._get_all_values(values)) def _get_all_values(self, values: List[T]) -> List[T]: if not values: diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index 105dcd9e..25754ad0 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -86,6 +86,15 @@ def __init__( self.config.allow_remove, self) + def reset(self, style: PieStyle): + self._style = style + self.setGeometry(0, 0, style.widget_radius*2, style.widget_radius*2) + self._circle_points = CirclePoints( + center=self.center, + radius=self._style.pie_radius) + self.label_holder.circle_points = self._circle_points + self.label_holder.reset(self._style) + @property def deadzone(self) -> float: """Return the deadzone distance.""" diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py index 36534a09..660afe0e 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py @@ -36,3 +36,7 @@ def move_to_pie_side(self): offset = self.width()//2 + self._style.widget_radius * 1.05 point = QPoint(round(offset), 0) self.move_center(QCursor().pos() + point) # type: ignore + + def reset(self, style: PieStyle): + self._style = style + self.setMinimumHeight(style.widget_radius*2) \ No newline at end of file diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py index 0c4d122b..a52b4b7d 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import List +from typing import List, Optional from PyQt5.QtWidgets import QWidget @@ -25,23 +25,23 @@ def __init__( ) -> None: self._labels = labels self._style = style - self._circle_points = circle_points + self.circle_points = circle_points self._allow_remove = allow_remove self._owner = owner self.widget_holder: WidgetHolder = WidgetHolder() - self._reset() + self.reset() def append(self, label: Label): self._labels.append(label) - self._reset() + self.reset() def remove(self, label: Label): if (label in self._labels and len(self._labels) > 1 and self._allow_remove): self._labels.remove(label) - self._reset() + self.reset() def swap(self, _a: Label, _b: Label): _a.swap_locations(_b) @@ -50,7 +50,7 @@ def swap(self, _a: Label, _b: Label): self._labels[idx_b] = _a self._labels[idx_a] = _b - self._reset() + self.reset() def __iter__(self): return iter(self._labels) @@ -58,7 +58,10 @@ def __iter__(self): def __bool__(self): return bool(self._labels) - def _reset(self) -> None: + def reset(self, style: Optional[PieStyle] = None) -> None: + if style is not None: + self._style = style + for child in self.widget_holder: child.setParent(None) # type: ignore self.widget_holder.clear() @@ -68,7 +71,7 @@ def _reset(self) -> None: children_widgets.append( create_label_widget(label, self._style, self._owner)) - angles = self._circle_points.iterate_over_circle(len(self._labels)) + angles = self.circle_points.iterate_over_circle(len(self._labels)) for child, (angle, point) in zip(children_widgets, angles): child.setParent(self._owner) child.show() @@ -76,5 +79,5 @@ def _reset(self) -> None: child.label.center = point child.move_to_label() self.widget_holder.add(child) - + self._labels.notify_about_change() diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py index b3f17276..ed48569a 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py @@ -13,15 +13,26 @@ class RoundButton(QPushButton, BaseWidget): def __init__( self, - radius: int, - icon_scale: float, - style: PieStyle, icon: QIcon, parent: Optional[QWidget] = None ): QPushButton.__init__(self, icon, "", parent) - self.setGeometry(0, 0, radius*2, radius*2) self.setCursor(Qt.ArrowCursor) + + if parent is None: + self.setWindowFlags(( + self.windowFlags() | # type: ignore + Qt.Tool | + Qt.FramelessWindowHint | + Qt.NoDropShadowWindowHint)) + self.setAttribute(Qt.WA_TranslucentBackground) + self.setStyleSheet("background: transparent;") + + self.show() + + def reset(self, radius: int, icon_scale: float, style: PieStyle): + self.setGeometry(0, 0, radius*2, radius*2) + self.setStyleSheet(f""" QPushButton [ border: {style.border_thickness}px @@ -36,16 +47,6 @@ def __init__( ] """.replace('[', '{').replace(']', '}')) - if parent is None: - self.setWindowFlags(( - self.windowFlags() | # type: ignore - Qt.Tool | - Qt.FramelessWindowHint | - Qt.NoDropShadowWindowHint)) - self.setAttribute(Qt.WA_TranslucentBackground) - self.setStyleSheet("background: transparent;") - - self.show() @staticmethod def _color_to_str(color: QColor) -> str: return f'''rgba( From 5499fbb3e4bf9af1d79a60fb09a4936cf7d813db Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 051/125] Cursor change on draggable label widgets --- shortcut_composer/actions.py | 2 -- .../templates/pie_menu_utils/label_widget.py | 19 +++++++++++++++---- .../templates/pie_menu_utils/pie_config.py | 1 - 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/shortcut_composer/actions.py b/shortcut_composer/actions.py index 8c065174..ac3a518f 100644 --- a/shortcut_composer/actions.py +++ b/shortcut_composer/actions.py @@ -10,7 +10,6 @@ """ import templates -from typing import List from PyQt5.QtGui import QColor @@ -27,7 +26,6 @@ def create_actions(): return [ - # Switch between FREEHAND BRUSH and the MOVE tool templates.TemporaryKey( name="Temporary move tool", diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget.py index ad9e83c3..b20e0eee 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget.py @@ -22,12 +22,23 @@ def __init__( super().__init__(parent) self.setGeometry(0, 0, style.icon_radius*2, style.icon_radius*2) self.label = label - self.draggable = True + self._draggable = True + self.draggable = self._draggable self._style = style self._enabled = True self._hovered = False - self.setCursor(Qt.ArrowCursor) + + @property + def draggable(self): + return self._draggable + + @draggable.setter + def draggable(self, value: bool): + self._draggable = value + if value: + return self.setCursor(Qt.ArrowCursor) + self.setCursor(Qt.CrossCursor) def move_to_label(self) -> None: """Move the widget by providing a new center point.""" @@ -36,12 +47,12 @@ def move_to_label(self) -> None: def set_enabled(self, value: bool): self._enabled = value if not value: - self.draggable = False + self._draggable = False self.repaint() def mousePressEvent(self, e: QMouseEvent) -> None: """Initiate a drag loop for this Widget, so Widgets can be swapped.""" - if e.buttons() != Qt.LeftButton or not self.draggable: + if e.buttons() != Qt.LeftButton or not self._draggable: return drag = QDrag(self) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 83b8ff86..a5836cf9 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -43,7 +43,6 @@ def __init__(self, *args): self.order = ImmutablesListField(f"{self.name} values", [""]) self.allow_remove = False - @property def values(self): saved_order = self.order.read() From ffbfad287410dd11258e07743fea218cf1596dfb Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 052/125] Display scrollbars properly --- .../templates/pie_menu_utils/settings_gui/scroll_area.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py index 1970d866..fdde465e 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py @@ -1,5 +1,6 @@ from typing import List +from PyQt5.QtCore import Qt from PyQt5.QtWidgets import ( QWidget, QScrollArea, @@ -20,7 +21,8 @@ def __init__( parent=None ) -> None: super().__init__(parent) - self.setFixedWidth(round(style.icon_radius*(2*columns + 1))) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.setWidgetResizable(True) self._style = style From 67fda3437d457dd1b31f675f2605411c8cb3d34d Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 053/125] Add pretty names to settings --- .../composer_utils/config/config_ui.py | 18 ++++++++++++++---- .../composer_utils/settings_dialog.py | 19 +++++++++++-------- .../settings_gui/enum_pie_settings.py | 6 ++++-- .../settings_gui/preset_pie_settings.py | 8 +++++--- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/shortcut_composer/composer_utils/config/config_ui.py b/shortcut_composer/composer_utils/config/config_ui.py index b4aef6d0..42200417 100644 --- a/shortcut_composer/composer_utils/config/config_ui.py +++ b/shortcut_composer/composer_utils/config/config_ui.py @@ -21,10 +21,12 @@ class ConfigBasedWidget: def __init__( self, config_field: ImmutableField, - parent: Optional[QWidget] = None + parent: Optional[QWidget] = None, + pretty_name: Optional[str] = None, ) -> None: self._parent = parent self.config_field: Final[ImmutableField] = config_field + self.pretty_name = self._init_pretty_name(pretty_name) self.widget: QWidget @abstractmethod @@ -39,16 +41,22 @@ def reset(self): def save(self): self.config_field.write(self.read()) + def _init_pretty_name(self, pretty_name: Optional[str]) -> str: + if pretty_name is not None: + return pretty_name + return self.config_field.name + class ConfigSpinBox(ConfigBasedWidget): def __init__( self, config_field: Union[ImmutableField[int], ImmutableField[float]], parent: Optional[QWidget] = None, + pretty_name: Optional[str] = None, step: float = 1, max_value: float = 100, ) -> None: - super().__init__(config_field, parent) + super().__init__(config_field, parent, pretty_name) self._step = step self._max_value = max_value self._spin_box = self._init_spin_box() @@ -70,14 +78,16 @@ def _init_spin_box(self): spin_box.setMaximum(self._max_value) # type: ignore return spin_box + class ConfigComboBox(ConfigBasedWidget): def __init__( self, config_field: ImmutableField[str], parent: Optional[QWidget] = None, + pretty_name: Optional[str] = None, allowed_values: List[Any] = [], ) -> None: - super().__init__(config_field, parent) + super().__init__(config_field, parent, pretty_name) self._allowed_values = allowed_values self._combo_box = self._init_combo_box() self.widget: Final[QComboBox] = self._combo_box @@ -123,7 +133,7 @@ def __init__(self, elements: List[Union[ConfigBasedWidget, str]]) -> None: def _add_row(self, element: ConfigBasedWidget) -> None: self._widgets.append(element) - self._layout.addRow(element.config_field.name, element.widget) + self._layout.addRow(f"{element.pretty_name}:", element.widget) def _add_label(self, text: str): label = QLabel(text) diff --git a/shortcut_composer/composer_utils/settings_dialog.py b/shortcut_composer/composer_utils/settings_dialog.py index 65aacb64..e718fb71 100644 --- a/shortcut_composer/composer_utils/settings_dialog.py +++ b/shortcut_composer/composer_utils/settings_dialog.py @@ -27,16 +27,19 @@ def __init__(self) -> None: self._general_tab = ConfigFormWidget([ "Common settings", - ConfigSpinBox(Config.SHORT_VS_LONG_PRESS_TIME, self, 0.05, 4), - ConfigSpinBox(Config.FPS_LIMIT, self, 5, 500), + ConfigSpinBox( + Config.SHORT_VS_LONG_PRESS_TIME, self, None, 0.05, 4), + ConfigSpinBox(Config.FPS_LIMIT, self, None, 5, 500), "Cursor trackers", - ConfigSpinBox(Config.TRACKER_SENSITIVITY_SCALE, self, 0.05, 400), - ConfigSpinBox(Config.TRACKER_DEADZONE, self, 1, 200), + ConfigSpinBox( + Config.TRACKER_SENSITIVITY_SCALE, self, None, 0.05, 400), + ConfigSpinBox(Config.TRACKER_DEADZONE, self, None, 1, 200), "Pie menus display", - ConfigSpinBox(Config.PIE_GLOBAL_SCALE, self, 0.05, 4), - ConfigSpinBox(Config.PIE_ICON_GLOBAL_SCALE, self, 0.05, 4), - ConfigSpinBox(Config.PIE_DEADZONE_GLOBAL_SCALE, self, 0.05, 4), - ConfigSpinBox(Config.PIE_ANIMATION_TIME, self, 0.01, 1), + ConfigSpinBox(Config.PIE_GLOBAL_SCALE, self, None, 0.05, 4), + ConfigSpinBox(Config.PIE_ICON_GLOBAL_SCALE, self, None, 0.05, 4), + ConfigSpinBox( + Config.PIE_DEADZONE_GLOBAL_SCALE, self, None, 0.05, 4), + ConfigSpinBox(Config.PIE_ANIMATION_TIME, self, None, 0.01, 1), ]) full_layout = QVBoxLayout(self) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index b1f8512c..42b81958 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -37,8 +37,10 @@ def __init__( self._action_values = ScrollArea(values, new_style, 3) tab_holder.addTab(self._action_values, "Action values") self._local_settings = ConfigFormWidget([ - ConfigSpinBox(pie_config.pie_radius_scale, self, 0.05, 4), - ConfigSpinBox(pie_config.icon_radius_scale, self, 0.05, 4), + ConfigSpinBox( + pie_config.pie_radius_scale, self, "Pie scale", 0.05, 4), + ConfigSpinBox( + pie_config.icon_radius_scale, self, "Icon scale", 0.05, 4), ]) tab_holder.addTab(self._local_settings, "Local settings") diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py index a4d3dc89..73b0cea5 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py @@ -32,9 +32,11 @@ def __init__( self._refresh_tags() self._local_settings = ConfigFormWidget([ - ConfigComboBox(pie_config.tag_name, self, self._tags), - ConfigSpinBox(pie_config.pie_radius_scale, self, 0.05, 4), - ConfigSpinBox(pie_config.icon_radius_scale, self, 0.05, 4), + ConfigComboBox(pie_config.tag_name, self, "Tag name", self._tags), + ConfigSpinBox( + pie_config.pie_radius_scale, self, "Pie scale", 0.05, 4), + ConfigSpinBox( + pie_config.icon_radius_scale, self, "Icon scale", 0.05, 4), ]) layout = QVBoxLayout(self) From b24d760a13e985f11172d8dab29c8193d1f263c2 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 054/125] Make settings layouts more unified --- shortcut_composer/composer_utils/config/config_ui.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shortcut_composer/composer_utils/config/config_ui.py b/shortcut_composer/composer_utils/config/config_ui.py index 42200417..4c9c9925 100644 --- a/shortcut_composer/composer_utils/config/config_ui.py +++ b/shortcut_composer/composer_utils/config/config_ui.py @@ -7,7 +7,6 @@ from PyQt5.QtWidgets import ( QDoubleSpinBox, QFormLayout, - QHBoxLayout, QSplitter, QComboBox, QSpinBox, @@ -72,6 +71,7 @@ def set(self, value): def _init_spin_box(self): spin_box = (QSpinBox() if self.config_field.type is int else QDoubleSpinBox()) + spin_box.setMinimumWidth(90) spin_box.setObjectName(self.config_field.name) spin_box.setMinimum(0) spin_box.setSingleStep(self._step) # type: ignore @@ -116,11 +116,11 @@ class ConfigFormWidget(QWidget): def __init__(self, elements: List[Union[ConfigBasedWidget, str]]) -> None: super().__init__() self._layout = QFormLayout() - stretched = QHBoxLayout() - stretched.addStretch() - stretched.addLayout(self._layout) - stretched.addStretch() - self.setLayout(stretched) + self._layout.RowWrapPolicy(QFormLayout.DontWrapRows) + self._layout.setFieldGrowthPolicy(QFormLayout.FieldsStayAtSizeHint) + self._layout.setFormAlignment(Qt.AlignHCenter | Qt.AlignTop) + self._layout.setLabelAlignment(Qt.AlignRight) + self.setLayout(self._layout) self._widgets: List[ConfigBasedWidget] = [] for element in elements: From 5d2f874619220e6de76ee28e3f2e2c338cc715ca Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 055/125] Proper minimalHeights for Scroll and Settings --- .../templates/pie_menu_utils/settings_gui/enum_pie_settings.py | 2 ++ .../templates/pie_menu_utils/settings_gui/pie_settings.py | 3 +-- .../templates/pie_menu_utils/widget_utils/label_holder.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index 42b81958..8dda0349 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -35,6 +35,8 @@ def __init__( new_style.icon_radius_scale = 1.0 self._action_values = ScrollArea(values, new_style, 3) + self._action_values.setMinimumHeight(round(style.base_icon_radius*6.2)) + tab_holder.addTab(self._action_values, "Action values") self._local_settings = ConfigFormWidget([ ConfigSpinBox( diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py index 660afe0e..6754936c 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py @@ -20,7 +20,6 @@ def __init__( parent=None ) -> None: AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) - self.setMinimumHeight(style.widget_radius*2) self.setAcceptDrops(True) self.setWindowFlags(( self.windowFlags() | # type: ignore @@ -39,4 +38,4 @@ def move_to_pie_side(self): def reset(self, style: PieStyle): self._style = style - self.setMinimumHeight(style.widget_radius*2) \ No newline at end of file + self.setMinimumHeight(self._style.widget_radius*2) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py index a52b4b7d..18507e29 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py @@ -17,7 +17,7 @@ class LabelHolder: def __init__( self, - labels: NotifyingList, + labels: NotifyingList[Label], style: PieStyle, circle_points: CirclePoints, allow_remove: bool, From 177be1e2927ab6edb81d626abf251204a5f0d049 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 056/125] Simplify getting light/dark theme --- shortcut_composer/api_krita/core_api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shortcut_composer/api_krita/core_api.py b/shortcut_composer/api_krita/core_api.py index 5c5dbf74..27e8691d 100644 --- a/shortcut_composer/api_krita/core_api.py +++ b/shortcut_composer/api_krita/core_api.py @@ -111,8 +111,7 @@ def connect_callback(): def is_light_theme_active(self) -> bool: """Return if currently set theme is light using it's main color.""" main_color: QColor = qApp.palette().window().color() - average = (main_color.red()+main_color.green()+main_color.blue()) // 3 - return average > 128 + return main_color.value() > 128 class KritaWindow(Protocol): From e4533b110fb72aea9f629b63953072012ed87c68 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 057/125] PieStyle autoupdates --- shortcut_composer/templates/pie_menu.py | 27 ++-- .../templates/pie_menu_utils/pie_style.py | 128 ++++++++++++------ .../settings_gui/enum_pie_settings.py | 7 +- 3 files changed, 105 insertions(+), 57 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 78f3033f..34a6d894 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later from typing import List, TypeVar, Generic, Optional -from copy import copy from enum import Enum from PyQt5.QtCore import QPoint @@ -110,14 +109,18 @@ def __init__( self._edit_mode = EditMode(self) + self._unscaled_style = PieStyle( + pie_radius_scale=self._local_config.pie_radius_scale, + icon_radius_scale=self._local_config.icon_radius_scale, + background_color=self._background_color, + active_color=self._active_color, + items=[None]) self._style = PieStyle( - pie_radius_scale=self._local_config.pie_radius_scale.read(), - icon_radius_scale=self._local_config.icon_radius_scale.read(), + pie_radius_scale=self._local_config.pie_radius_scale, + icon_radius_scale=self._local_config.icon_radius_scale, background_color=self._background_color, - active_color=self._active_color) - - self._unscaled_style = copy(self._style) - self._style.set_items(self._labels) + active_color=self._active_color, + items=self._labels) self.pie_settings = create_pie_settings_window( style=self._unscaled_style, @@ -136,7 +139,6 @@ def __init__( icon=Krita.get_icon("properties"), parent=self.pie_widget) self.settings_button.clicked.connect(lambda: self._edit_mode.set(True)) - self.accept_button = RoundButton( icon=Krita.get_icon("dialog-ok"), parent=self.pie_widget) @@ -147,15 +149,6 @@ def reset(self): values = self._local_config.values self._reset_labels(self._labels, values) - self._style = PieStyle( - pie_radius_scale=self._local_config.pie_radius_scale.read(), - icon_radius_scale=self._local_config.icon_radius_scale.read(), - background_color=self._background_color, - active_color=self._active_color) - - self._unscaled_style = copy(self._style) - self._style.set_items(self._labels) - self.pie_widget.reset(self._style) self.pie_settings.reset(self._unscaled_style) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index da448c16..3572e342 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -3,17 +3,15 @@ import math import platform -from typing import Optional -from dataclasses import dataclass +from typing import Optional, Callable, List from copy import copy from PyQt5.QtGui import QColor from api_krita import Krita -from composer_utils.config import Config +from composer_utils.config import Config, ImmutableField -@dataclass class PieStyle: """ Holds and calculates configuration of displayed elements. @@ -27,52 +25,54 @@ class PieStyle: def __init__( self, - pie_radius_scale: float, - icon_radius_scale: float, + pie_radius_scale: ImmutableField[float], + icon_radius_scale: ImmutableField[float], background_color: Optional[QColor], active_color: QColor, + items: list, ) -> None: - self._items = [None] + self._items = items self._base_size = Krita.screen_size/2560 - self.pie_radius_scale = pie_radius_scale - self.icon_radius_scale = icon_radius_scale - self.background_color = self._pick_background_color(background_color) + self._pie_radius_scale = pie_radius_scale + self._icon_radius_scale = icon_radius_scale + self._background_color = background_color self.active_color = active_color - self.active_color_dark = QColor( - round(active_color.red()*0.8), - round(active_color.green()*0.8), - round(active_color.blue()*0.8)) - - self.pie_radius: int = round( - 165 * self._base_size - * self.pie_radius_scale - * Config.PIE_GLOBAL_SCALE.read()) - self.widget_radius = self.pie_radius + self.base_icon_radius - self.border_thickness = round(self.pie_radius*0.02) - self.area_thickness = round(self.pie_radius/self.pie_radius_scale*0.4) + self._prev_pie = pie_radius_scale.read() + self._prev_icon = icon_radius_scale.read() - self.inner_edge_radius = self.pie_radius - self.area_thickness - self.no_border_radius = self.pie_radius - self.border_thickness//2 + self._on_change_callbacks: List[Callable[[], None]] = [] - self.icon_color = copy(self.background_color) - self.icon_color.setAlpha(255) + @property + def pie_radius_scale(self): + radius = self._pie_radius_scale.read() + if radius != self._prev_pie: + self._notice_change() + self._prev_pie = radius + return radius - self.border_color = QColor( - min(self.icon_color.red()+15, 255), - min(self.icon_color.green()+15, 255), - min(self.icon_color.blue()+15, 255), - 255) + @property + def icon_radius_scale(self): + radius = self._icon_radius_scale.read() + if radius != self._prev_icon: + self._notice_change() + self._prev_icon = radius + return radius - self.font_multiplier = self.SYSTEM_FONT_SIZE[platform.system()] + def _notice_change(self): + for callback in self._on_change_callbacks: + callback() - def set_items(self, items: list): - self._items = items + def register_callback(self, callback: Callable[[], None]): + self._on_change_callbacks.append(callback) @property - def setting_button_radius(self) -> int: - return round(30 * self._base_size) + def pie_radius(self) -> int: + return round( + 165 * self._base_size + * self.pie_radius_scale + * Config.PIE_GLOBAL_SCALE.read()) @property def base_icon_radius(self) -> int: @@ -101,14 +101,64 @@ def deadzone_radius(self) -> float: 40 * self._base_size * Config.PIE_DEADZONE_GLOBAL_SCALE.read()) - def _pick_background_color(self, color: Optional[QColor]) -> QColor: + @property + def widget_radius(self): + return self.pie_radius + self.base_icon_radius + + @property + def border_thickness(self): + return round(self.pie_radius*0.02) + + @property + def area_thickness(self): + return round(self.pie_radius/self.pie_radius_scale*0.4) + + @property + def inner_edge_radius(self): + return self.pie_radius - self.area_thickness + + @property + def no_border_radius(self): + return self.pie_radius - self.border_thickness//2 + + @property + def setting_button_radius(self) -> int: + return round(30 * self._base_size) + + @property + def background_color(self) -> QColor: """Default background color depends on the app theme lightness.""" - if color is not None: - return color + if self._background_color is not None: + return self._background_color if Krita.is_light_theme_active: return QColor(210, 210, 210, 190) return QColor(75, 75, 75, 190) + @property + def active_color_dark(self): + return QColor( + round(self.active_color.red()*0.8), + round(self.active_color.green()*0.8), + round(self.active_color.blue()*0.8)) + + @property + def icon_color(self): + color = copy(self.background_color) + color.setAlpha(255) + return color + + @property + def border_color(self): + return QColor( + min(self.icon_color.red()+15, 255), + min(self.icon_color.green()+15, 255), + min(self.icon_color.blue()+15, 255), + 255) + + @property + def font_multiplier(self): + return self.SYSTEM_FONT_SIZE[platform.system()] + SYSTEM_FONT_SIZE = { "Linux": 0.175, "Windows": 0.11, diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index 8dda0349..919d64d1 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -32,7 +32,12 @@ def __init__( tab_holder = QTabWidget() new_style = copy(self._style) - new_style.icon_radius_scale = 1.0 + + # HACK! label widgets need to have "unscaled mode" + class Mock: + def read(self): return 1.0 + + new_style._icon_radius_scale = Mock() # type: ignore self._action_values = ScrollArea(values, new_style, 3) self._action_values.setMinimumHeight(round(style.base_icon_radius*6.2)) From ddc8d416ad4aae11dd2e5916317189f099019e37 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 058/125] Don't recreate label widgets if not necessary --- shortcut_composer/templates/pie_menu.py | 6 +++++- .../templates/pie_menu_utils/label.py | 19 +++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 34a6d894..f122f863 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -188,8 +188,12 @@ def on_every_key_release(self) -> None: if label := self.pie_widget.active: self._controller.set_value(label.value) - def _reset_labels(self, label_list: List[Label], values: List[T]) -> None: + def _reset_labels(self, label_list: List[Label[T]], values: List[T]): """Wrap values into paintable label objects with position info.""" + current = [label.value for label in label_list] + if current == values: + return + label_list.clear() for value in values: try: diff --git a/shortcut_composer/templates/pie_menu_utils/label.py b/shortcut_composer/templates/pie_menu_utils/label.py index 52e900b6..01598603 100644 --- a/shortcut_composer/templates/pie_menu_utils/label.py +++ b/shortcut_composer/templates/pie_menu_utils/label.py @@ -2,20 +2,19 @@ # SPDX-License-Identifier: GPL-3.0-or-later from api_krita.pyqt import Text -from typing import Union, Any +from typing import Union, Generic, TypeVar from dataclasses import dataclass from PyQt5.QtCore import QPoint -from PyQt5.QtGui import ( - QPixmap, - QIcon, -) +from PyQt5.QtGui import QPixmap, QIcon from composer_utils.config import Config +T = TypeVar("T") + @dataclass -class Label: +class Label(Generic[T]): """ Data representing a single value in PieWidget. @@ -28,7 +27,7 @@ class Label: - `activation_progress` -- state of animation in range <0-1> """ - value: Any + value: T center: QPoint = QPoint(0, 0) angle: int = 0 display_value: Union[QPixmap, QIcon, Text, None] = None @@ -36,12 +35,12 @@ class Label: def __post_init__(self) -> None: self.activation_progress = AnimationProgress(speed_scale=1, steep=1) - def swap_locations(self, other: 'Label') -> None: + def swap_locations(self, other: 'Label[T]') -> None: """Change position data with information Label.""" self.angle, other.angle = other.angle, self.angle self.center, other.center = other.center, self.center - - def __eq__(self, other: Any) -> bool: + + def __eq__(self, other: T) -> bool: if not isinstance(other, Label): return False return self.value == other.value From 06e30ae86c3d15b542da3515d0dbeb38b1cb217b Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 059/125] Use callbacks in PieStyle instead of key press to reset --- shortcut_composer/templates/pie_menu.py | 11 +++----- .../templates/pie_menu_utils/pie_style.py | 11 +++++--- .../templates/pie_menu_utils/pie_widget.py | 17 +++++------ .../settings_gui/pie_settings.py | 4 +-- .../widget_utils/label_holder.py | 28 +++++++++---------- 5 files changed, 33 insertions(+), 38 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index f122f863..2bfb44ef 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -104,6 +104,7 @@ def __init__( self._active_color = active_color self._labels: NotifyingList[Label] = NotifyingList() + self._reset_labels(self._labels, self._local_config.values) self._all_labels: NotifyingList[Label] = NotifyingList() self._reset_labels(self._all_labels, self._get_all_values(values)) @@ -145,12 +146,8 @@ def __init__( self.accept_button.clicked.connect(lambda: self._edit_mode.set(False)) self.accept_button.hide() - def reset(self): - values = self._local_config.values - self._reset_labels(self._labels, values) - - self.pie_widget.reset(self._style) - self.pie_settings.reset(self._unscaled_style) + def _reset(self): + self._reset_labels(self._labels, self._local_config.values) default_radius = self._style.setting_button_radius radius = self._style.deadzone_radius @@ -173,7 +170,7 @@ def reset(self): def on_key_press(self) -> None: """Show widget under mouse and start manager which repaints it.""" self._controller.refresh() - self.reset() + self._reset() self.pie_manager.start() super().on_key_press() diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index 3572e342..27500ae5 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -47,17 +47,20 @@ def __init__( @property def pie_radius_scale(self): radius = self._pie_radius_scale.read() - if radius != self._prev_pie: - self._notice_change() + old = self._prev_pie self._prev_pie = radius + if radius != old: + self._notice_change() return radius + # FIXME notification needs to be done immediatelly after change @property def icon_radius_scale(self): radius = self._icon_radius_scale.read() - if radius != self._prev_icon: - self._notice_change() + old = self._prev_icon self._prev_icon = radius + if radius != old: + self._notice_change() return radius def _notice_change(self): diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index 25754ad0..de6ecc20 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import List, TypeVar, Optional +from typing import TypeVar, Optional from PyQt5.QtCore import Qt from PyQt5.QtGui import ( @@ -69,6 +69,7 @@ def __init__( self.setCursor(Qt.CrossCursor) self._style = style + self._style.register_callback(self._reset) self._labels = labels self.config = config @@ -76,24 +77,20 @@ def __init__( self.is_edit_mode = False self._last_widget = None - self._circle_points = CirclePoints( - center=self.center, - radius=self._style.pie_radius) self.label_holder = LabelHolder( self._labels, self._style, - self._circle_points, self.config.allow_remove, self) + self._circle_points: CirclePoints + self._reset() - def reset(self, style: PieStyle): - self._style = style - self.setGeometry(0, 0, style.widget_radius*2, style.widget_radius*2) + def _reset(self): + radius = self._style.widget_radius*2 + self.setGeometry(0, 0, radius, radius) self._circle_points = CirclePoints( center=self.center, radius=self._style.pie_radius) - self.label_holder.circle_points = self._circle_points - self.label_holder.reset(self._style) @property def deadzone(self) -> float: diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py index 6754936c..c7b4f1c8 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py @@ -29,6 +29,7 @@ def __init__( self._values = values self._style = style + self._style.register_callback(self._reset) self._pie_config = pie_config def move_to_pie_side(self): @@ -36,6 +37,5 @@ def move_to_pie_side(self): point = QPoint(round(offset), 0) self.move_center(QCursor().pos() + point) # type: ignore - def reset(self, style: PieStyle): - self._style = style + def _reset(self): self.setMinimumHeight(self._style.widget_radius*2) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py index 18507e29..aae77b05 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py @@ -1,10 +1,9 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import List, Optional - -from PyQt5.QtWidgets import QWidget +from typing import List +from api_krita.pyqt import BaseWidget from ..pie_style import PieStyle from ..label import Label from ..label_widget import LabelWidget @@ -19,29 +18,28 @@ def __init__( self, labels: NotifyingList[Label], style: PieStyle, - circle_points: CirclePoints, allow_remove: bool, - owner: QWidget, + owner: BaseWidget, ) -> None: self._labels = labels self._style = style - self.circle_points = circle_points + self._style.register_callback(self._reset) self._allow_remove = allow_remove self._owner = owner self.widget_holder: WidgetHolder = WidgetHolder() - self.reset() + self._reset() def append(self, label: Label): self._labels.append(label) - self.reset() + self._reset() def remove(self, label: Label): if (label in self._labels and len(self._labels) > 1 and self._allow_remove): self._labels.remove(label) - self.reset() + self._reset() def swap(self, _a: Label, _b: Label): _a.swap_locations(_b) @@ -50,7 +48,7 @@ def swap(self, _a: Label, _b: Label): self._labels[idx_b] = _a self._labels[idx_a] = _b - self.reset() + self._reset() def __iter__(self): return iter(self._labels) @@ -58,10 +56,7 @@ def __iter__(self): def __bool__(self): return bool(self._labels) - def reset(self, style: Optional[PieStyle] = None) -> None: - if style is not None: - self._style = style - + def _reset(self) -> None: for child in self.widget_holder: child.setParent(None) # type: ignore self.widget_holder.clear() @@ -71,7 +66,10 @@ def reset(self, style: Optional[PieStyle] = None) -> None: children_widgets.append( create_label_widget(label, self._style, self._owner)) - angles = self.circle_points.iterate_over_circle(len(self._labels)) + circle_points = CirclePoints( + center=self._owner.center, + radius=self._style.pie_radius) + angles = circle_points.iterate_over_circle(len(self._labels)) for child, (angle, point) in zip(children_widgets, angles): child.setParent(self._owner) child.show() From f518ce437753e963be990eec32268492cee2ad45 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 060/125] Round buttons automatically react to style change --- shortcut_composer/templates/pie_menu.py | 46 +++++++------------ .../templates/pie_menu_utils/pie_style.py | 6 +++ .../widget_utils/round_button.py | 26 +++++++---- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 2bfb44ef..9ec9614c 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -105,23 +105,18 @@ def __init__( self._labels: NotifyingList[Label] = NotifyingList() self._reset_labels(self._labels, self._local_config.values) - self._all_labels: NotifyingList[Label] = NotifyingList() + self._all_labels: List[Label] = [] self._reset_labels(self._all_labels, self._get_all_values(values)) self._edit_mode = EditMode(self) - self._unscaled_style = PieStyle( - pie_radius_scale=self._local_config.pie_radius_scale, - icon_radius_scale=self._local_config.icon_radius_scale, - background_color=self._background_color, - active_color=self._active_color, - items=[None]) - self._style = PieStyle( - pie_radius_scale=self._local_config.pie_radius_scale, - icon_radius_scale=self._local_config.icon_radius_scale, - background_color=self._background_color, - active_color=self._active_color, - items=self._labels) + style_args = { + "pie_radius_scale": self._local_config.pie_radius_scale, + "icon_radius_scale": self._local_config.icon_radius_scale, + "background_color": self._background_color, + "active_color": self._active_color} + self._unscaled_style = PieStyle(items=[None], **style_args) + self._style = PieStyle(items=self._labels, **style_args) self.pie_settings = create_pie_settings_window( style=self._unscaled_style, @@ -138,35 +133,28 @@ def __init__( self.settings_button = RoundButton( icon=Krita.get_icon("properties"), - parent=self.pie_widget) + parent=self.pie_widget, + radius_callback=lambda: self._style.setting_button_radius, + icon_scale=1.1, + style=self._style) self.settings_button.clicked.connect(lambda: self._edit_mode.set(True)) self.accept_button = RoundButton( icon=Krita.get_icon("dialog-ok"), - parent=self.pie_widget) + parent=self.pie_widget, + radius_callback=lambda: self._style.accept_button_radius, + icon_scale=1.5, + style=self._style) self.accept_button.clicked.connect(lambda: self._edit_mode.set(False)) self.accept_button.hide() def _reset(self): self._reset_labels(self._labels, self._local_config.values) - default_radius = self._style.setting_button_radius - radius = self._style.deadzone_radius - radius = int(radius) if radius != float("inf") else default_radius - - self.settings_button.reset( - radius=default_radius, - icon_scale=1.1, - style=self._style) + self.accept_button.move_center(self.pie_widget.center) self.settings_button.move(QPoint( self.pie_widget.width()-self.settings_button.width(), self.pie_widget.height()-self.settings_button.height())) - self.accept_button.reset( - radius=radius, - icon_scale=1.5, - style=self._style) - self.accept_button.move_center(self.pie_widget.center) - def on_key_press(self) -> None: """Show widget under mouse and start manager which repaints it.""" self._controller.refresh() diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index 27500ae5..a74cdf8a 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -128,6 +128,12 @@ def no_border_radius(self): def setting_button_radius(self) -> int: return round(30 * self._base_size) + @property + def accept_button_radius(self) -> int: + default_radius = self.setting_button_radius + radius = self.deadzone_radius + return int(radius) if radius != float("inf") else default_radius + @property def background_color(self) -> QColor: """Default background color depends on the app theme lightness.""" diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py index ed48569a..0c0d478b 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Callable from PyQt5.QtWidgets import QWidget, QPushButton from PyQt5.QtGui import QColor, QIcon @@ -14,11 +14,19 @@ class RoundButton(QPushButton, BaseWidget): def __init__( self, icon: QIcon, - parent: Optional[QWidget] = None + radius_callback: Callable[[], int], + icon_scale: float, + style: PieStyle, + parent: Optional[QWidget] = None, ): QPushButton.__init__(self, icon, "", parent) self.setCursor(Qt.ArrowCursor) + self._radius_callback = radius_callback + self._icon_scale = icon_scale + self._style = style + self._style.register_callback(self._reset) + if parent is None: self.setWindowFlags(( self.windowFlags() | # type: ignore @@ -28,22 +36,24 @@ def __init__( self.setAttribute(Qt.WA_TranslucentBackground) self.setStyleSheet("background: transparent;") + self._reset() self.show() - def reset(self, radius: int, icon_scale: float, style: PieStyle): + def _reset(self): + radius = self._radius_callback() self.setGeometry(0, 0, radius*2, radius*2) self.setStyleSheet(f""" QPushButton [ - border: {style.border_thickness}px - {self._color_to_str(style.border_color)}; + border: {self._style.border_thickness}px + {self._color_to_str(self._style.border_color)}; border-radius: {radius}px; border-style: outset; - background: {self._color_to_str(style.background_color)}; - qproperty-iconSize:{round(radius*icon_scale)}px; + background: {self._color_to_str(self._style.background_color)}; + qproperty-iconSize:{round(radius*self._icon_scale)}px; ] QPushButton:hover [ - background: {self._color_to_str(style.active_color)}; + background: {self._color_to_str(self._style.active_color)}; ] """.replace('[', '{').replace(']', '}')) From 47285f39aebcdbb625bdc76148b6a57f8186e96b Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 061/125] Use config callbacks to notify PieStyle --- .../api_krita/pyqt/custom_widgets.py | 4 +++ .../composer_utils/config/fields.py | 15 +++++++++- .../templates/pie_menu_utils/pie_style.py | 28 ++++++------------- .../templates/pie_menu_utils/pie_widget.py | 4 +-- .../settings_gui/enum_pie_settings.py | 6 ++-- .../settings_gui/pie_settings.py | 1 + .../widget_utils/round_button.py | 3 +- 7 files changed, 33 insertions(+), 28 deletions(-) diff --git a/shortcut_composer/api_krita/pyqt/custom_widgets.py b/shortcut_composer/api_krita/pyqt/custom_widgets.py index 11d42d31..53f58c20 100644 --- a/shortcut_composer/api_krita/pyqt/custom_widgets.py +++ b/shortcut_composer/api_krita/pyqt/custom_widgets.py @@ -24,6 +24,10 @@ def move_center(self, new_center: QPoint) -> None: """Move the widget by providing a new center point.""" self.move(new_center-self.center) # type: ignore + def setGeometry(self, ax: int, ay: int, aw: int, ah: int): + center = self.center_global + super().setGeometry(ax, ay, aw, ah) + self.move_center(center) class AnimatedWidget(QWidget): """Adds the fade-in animation when the widget is shown (60 FPS).""" diff --git a/shortcut_composer/composer_utils/config/fields.py b/shortcut_composer/composer_utils/config/fields.py index b78661f5..9e23274c 100644 --- a/shortcut_composer/composer_utils/config/fields.py +++ b/shortcut_composer/composer_utils/config/fields.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import TypeVar, Generic, Optional, List, Type +from typing import TypeVar, Generic, Optional, List, Type, Callable from abc import ABC, abstractmethod from enum import Enum @@ -14,6 +14,7 @@ class FieldBase(Generic[T], ABC): def __init__(self, name: str, default: T) -> None: self.name = name self.default = default + self._on_change_callbacks: List[Callable[[], None]] = [] def _read_raw(self) -> Optional[str]: red_value = Krita.read_setting( @@ -25,10 +26,19 @@ def _read_raw(self) -> Optional[str]: def reset_default(self) -> None: self.write(self.default) + def register_callback(self, callback: Callable[[], None]): + self._on_change_callbacks.append(callback) + def _is_write_redundant(self, value: T): + if self.read() == value: + return True current_value = self._read_raw() return current_value is None and value == self.default + def _notify_subscribers(self): + for callback in self._on_change_callbacks: + callback() + @abstractmethod def read(self) -> T: ... @@ -59,6 +69,7 @@ def write(self, value: T) -> None: group="ShortcutComposer", name=self.name, value=value) + self._notify_subscribers() @property def type(self): @@ -84,6 +95,7 @@ def write(self, value: List[Enum]) -> None: group="ShortcutComposer", name=self.name, value=to_write) + self._notify_subscribers() @property def type(self): @@ -109,6 +121,7 @@ def write(self, value: List[T]) -> None: group="ShortcutComposer", name=self.name, value="\t".join(map(str, value))) + self._notify_subscribers() @property def type(self): diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index a74cdf8a..37ea666c 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -39,33 +39,21 @@ def __init__( self._background_color = background_color self.active_color = active_color - self._prev_pie = pie_radius_scale.read() - self._prev_icon = icon_radius_scale.read() - + self._pie_radius_scale.register_callback(self._notice_change) + self._icon_radius_scale.register_callback(self._notice_change) self._on_change_callbacks: List[Callable[[], None]] = [] + def _notice_change(self): + for callback in self._on_change_callbacks: + callback() + @property def pie_radius_scale(self): - radius = self._pie_radius_scale.read() - old = self._prev_pie - self._prev_pie = radius - if radius != old: - self._notice_change() - return radius + return self._pie_radius_scale.read() - # FIXME notification needs to be done immediatelly after change @property def icon_radius_scale(self): - radius = self._icon_radius_scale.read() - old = self._prev_icon - self._prev_icon = radius - if radius != old: - self._notice_change() - return radius - - def _notice_change(self): - for callback in self._on_change_callbacks: - callback() + return self._icon_radius_scale.read() def register_callback(self, callback: Callable[[], None]): self._on_change_callbacks.append(callback) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index de6ecc20..3d99ee41 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -86,8 +86,8 @@ def __init__( self._reset() def _reset(self): - radius = self._style.widget_radius*2 - self.setGeometry(0, 0, radius, radius) + widget_diameter = self._style.widget_radius*2 + self.setGeometry(0, 0, widget_diameter, widget_diameter) self._circle_points = CirclePoints( center=self.center, radius=self._style.pie_radius) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index 919d64d1..4ae02ac6 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Callable from copy import copy from PyQt5.QtWidgets import QVBoxLayout, QTabWidget @@ -31,12 +31,12 @@ def __init__( tab_holder = QTabWidget() - new_style = copy(self._style) - # HACK! label widgets need to have "unscaled mode" class Mock: def read(self): return 1.0 + def register_callback(self, callback: Callable): return + new_style = copy(self._style) new_style._icon_radius_scale = Mock() # type: ignore self._action_values = ScrollArea(values, new_style, 3) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py index c7b4f1c8..594bd7a9 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py @@ -31,6 +31,7 @@ def __init__( self._style = style self._style.register_callback(self._reset) self._pie_config = pie_config + self._reset() def move_to_pie_side(self): offset = self.width()//2 + self._style.widget_radius * 1.05 diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py index 0c0d478b..0692a9e2 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py @@ -42,7 +42,7 @@ def __init__( def _reset(self): radius = self._radius_callback() self.setGeometry(0, 0, radius*2, radius*2) - + self.setStyleSheet(f""" QPushButton [ border: {self._style.border_thickness}px @@ -57,7 +57,6 @@ def _reset(self): ] """.replace('[', '{').replace(']', '}')) - @staticmethod def _color_to_str(color: QColor) -> str: return f'''rgba( {color.red()}, {color.green()}, {color.blue()}, {color.alpha()})''' From 5cd860177dc631e5a970624ed0af694762094a92 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 062/125] Fix pie not reloading after tag was changed --- shortcut_composer/templates/pie_menu.py | 12 ++++++++---- .../pie_menu_utils/widget_utils/label_holder.py | 11 +++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 9ec9614c..4ad13661 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -105,7 +105,7 @@ def __init__( self._labels: NotifyingList[Label] = NotifyingList() self._reset_labels(self._labels, self._local_config.values) - self._all_labels: List[Label] = [] + self._all_labels: NotifyingList[Label] = NotifyingList() self._reset_labels(self._all_labels, self._get_all_values(values)) self._edit_mode = EditMode(self) @@ -173,10 +173,13 @@ def on_every_key_release(self) -> None: if label := self.pie_widget.active: self._controller.set_value(label.value) - def _reset_labels(self, label_list: List[Label[T]], values: List[T]): + def _reset_labels( + self, + label_list: NotifyingList[Label[T]], + values: List[T] + ) -> None: """Wrap values into paintable label objects with position info.""" - current = [label.value for label in label_list] - if current == values: + if [label.value for label in label_list] == values: return label_list.clear() @@ -186,6 +189,7 @@ def _reset_labels(self, label_list: List[Label[T]], values: List[T]): except KeyError: continue label_list.append(Label(value=value, display_value=label)) + label_list.notify_about_change() def _get_all_values(self, values: List[T]) -> List[T]: if not values: diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py index aae77b05..b8443590 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from typing import List +from functools import partial from api_krita.pyqt import BaseWidget from ..pie_style import PieStyle @@ -22,13 +23,14 @@ def __init__( owner: BaseWidget, ) -> None: self._labels = labels + self._labels.register_callback(partial(self._reset, False)) self._style = style - self._style.register_callback(self._reset) + self._style.register_callback(partial(self._reset, False)) self._allow_remove = allow_remove self._owner = owner self.widget_holder: WidgetHolder = WidgetHolder() - self._reset() + self._reset(False) def append(self, label: Label): self._labels.append(label) @@ -56,7 +58,7 @@ def __iter__(self): def __bool__(self): return bool(self._labels) - def _reset(self) -> None: + def _reset(self, notify: bool = True) -> None: for child in self.widget_holder: child.setParent(None) # type: ignore self.widget_holder.clear() @@ -78,4 +80,5 @@ def _reset(self) -> None: child.move_to_label() self.widget_holder.add(child) - self._labels.notify_about_change() + if notify: + self._labels.notify_about_change() From 45a3981c352417492d08afb7b826c604235a6377 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 063/125] Make the values more translatable with pretty name --- .../api_krita/enums/blending_mode.py | 135 +----------------- .../api_krita/enums/node_types.py | 14 -- shortcut_composer/api_krita/enums/toggle.py | 21 +-- shortcut_composer/api_krita/enums/tool.py | 43 +----- .../api_krita/enums/transform_mode.py | 21 +-- .../composer_utils/config/global_config.py | 5 - .../core_components/controller_base.py | 3 + .../controllers/canvas_controllers.py | 10 +- .../controllers/core_controllers.py | 8 ++ .../controllers/document_controllers.py | 6 +- .../controllers/node_controllers.py | 11 +- .../controllers/view_controllers.py | 17 ++- 12 files changed, 77 insertions(+), 217 deletions(-) diff --git a/shortcut_composer/api_krita/enums/blending_mode.py b/shortcut_composer/api_krita/enums/blending_mode.py index 686614ab..52443993 100644 --- a/shortcut_composer/api_krita/enums/blending_mode.py +++ b/shortcut_composer/api_krita/enums/blending_mode.py @@ -9,137 +9,6 @@ class BlendingMode(Enum): Contains all known blending modes in krita. Example usage: `BlendingMode.NORMAL` - - Available tools blending modes: - - `NORMAL` - - `ADD` - - `BURN` - - `COLOR` - - `DODGE` - - `DARKEN` - - `DIVIDE` - - `ERASE` - - `LIGHTEN` - - `LUMINIZE` - - `MULTIPLY` - - `OVERLAY` - - `SATURATION` - - `SCREEN` - - `SOFT_LIGHT_SVG` - - `INVERSE_SUBTRACT` - - `SUBTRACT` - - `AND` - - `CONVERSE` - - `IMPLICATION` - - `NAND` - - `NOR` - - `NOT_CONVERSE` - - `NOT_IMPLICATION` - - `OR` - - `XNOR` - - `XOR` - - `DARKER_COLOR` - - `EASY_BURN` - - `FOG_DARKEN_IFS_ILLUSIONS` - - `GAMMA_DARK` - - `SHADE_IFS_ILLUSIONS` - - `LINEAR_BURN` - - `COLOR_HSI` - - `DEC_INTENSITY` - - `DEC_SATURATION_HSI` - - `HUE_HSI` - - `INC_INTENSITY` - - `INC_SATURATION_HSI` - - `INTENSITY` - - `SATURATION_HSI` - - `DEC_LIGHTNESS` - - `COLOR_HSL` - - `DEC_SATURATION_HSL` - - `HUE_HSL` - - `INC_LIGHTNESS` - - `INC_SATURATION_HSL` - - `LIGHTNESS` - - `SATURATION_HSL` - - `COLOR_HSV` - - `DEC_SATURATION_HSV` - - `DEC_VALUE` - - `HUE_HSV` - - `INC_SATURATION_HSV` - - `INC_VALUE` - - `SATURATION_HSV` - - `VALUE` - - `DEC_SATURATION` - - `DEC_LUMINOSITY` - - `HUE` - - `INC_LUMINOSITY` - - `INC_SATURATION` - - `EASY_DODGE` - - `FLAT_LIGHT` - - `GAMMA_ILLUMINATION` - - `FOG_LIGHTEN_IFS_ILLUSIONS` - - `GAMMA_LIGHT` - - `HARD_LIGHT` - - `LIGHTER_COLOR` - - `LINEAR_DODGE` - - `LINEAR_LIGHT` - - `LUMINOSITY_SAI` - - `PNORM_A` - - `PNORM_B` - - `PIN_LIGHT` - - `SOFT_LIGHT_IFS_ILLUSIONS` - - `SOFT_LIGHT_PEGTOP_DELPHI` - - `SOFT_LIGHT` - - `SUPER_LIGHT` - - `TINT_IFS_ILLUSIONS` - - `VIVID_LIGHT` - - `BUMPMAP` - - `COMBINE_NORMAL` - - `COPY` - - `COPY_BLUE` - - `COPY_GREEN` - - `COPY_RED` - - `DISSOLVE` - - `TANGENT_NORMALMAP` - - `ALLANON` - - `ALPHADARKEN` - - `BEHIND` - - `DESTINATION_ATOP` - - `DESTINATION_IN` - - `GEOMETRIC_MEAN` - - `GRAIN_EXTRACT` - - `GRAIN_MERGE` - - `GREATER` - - `HARD_MIX` - - `HARD_MIX_PHOTOSHOP` - - `HARD_MIX_SOFTER_PHOTOSHOP` - - `HARD_OVERLAY` - - `INTERPOLATION` - - `INTERPOLATION_2X` - - `PARALLEL` - - `PENUMBRA_A` - - `PENUMBRA_B` - - `PENUMBRA_C` - - `PENUMBRA_D` - - `DIVISIVE_MODULO` - - `DIVISIVE_MODULO_CONTINUOUS` - - `MODULO_CONTINUOUS` - - `MODULO_SHIFT` - - `MODULO_SHIFT_CONTINUOUS` - - `ADDITIVE_SUBTRACTIVE` - - `ARC_TANGENT` - - `DIFF` - - `EQUIVALENCE` - - `EXCLUSION` - - `NEGATION` - - `FREEZE` - - `FREEZE_REFLECT` - - `GLOW` - - `GLOW_HEAT` - - `HEAT` - - `HEAT_GLOW` - - `HEAT_GLOW_FREEZE_REFLECT_HYBRID` - - `REFLECT` - - `REFLECT_FREEZE` """ NORMAL = "normal" @@ -271,3 +140,7 @@ class BlendingMode(Enum): HEAT_GLOW_FREEZE_REFLECT_HYBRID = "heat_glow_freeze_reflect_hybrid" REFLECT = "reflect" REFLECT_FREEZE = "reflect_freeze" + + @property + def pretty_name(self): + return self.value \ No newline at end of file diff --git a/shortcut_composer/api_krita/enums/node_types.py b/shortcut_composer/api_krita/enums/node_types.py index c6c554a0..e2f6fd94 100644 --- a/shortcut_composer/api_krita/enums/node_types.py +++ b/shortcut_composer/api_krita/enums/node_types.py @@ -11,20 +11,6 @@ class NodeType(Enum): Contains all known node types in krita. Example usage: `NodeType.PAINT_LAYER` - - Available node types: - - `PAINT_LAYER` - - `GROUP_LAYER` - - `FILE_LAYER` - - `FILTER_LAYER` - - `FILL_LAYER` - - `CLONE_LAYER` - - `VECTOR_LAYER` - - `TRANSPARENCY_MASK` - - `FILTER_MASK` - - `TRANSFORM_MASK` - - `SELECTION_MASK` - - `COLORIZE_MASK` """ PAINT_LAYER = "paintlayer" diff --git a/shortcut_composer/api_krita/enums/toggle.py b/shortcut_composer/api_krita/enums/toggle.py index 4c0a48ee..a550b289 100644 --- a/shortcut_composer/api_krita/enums/toggle.py +++ b/shortcut_composer/api_krita/enums/toggle.py @@ -3,6 +3,7 @@ from krita import Krita as Api from enum import Enum +from PyQt5.QtWidgets import QWidgetAction class Toggle(Enum): @@ -10,21 +11,6 @@ class Toggle(Enum): Contains all known actions that toggle (can be activated and deactivated). Example usage: `Toggle.ERASER` - - Available toggle actions: - - `ERASER` - - `PRESERVE_ALPHA` - - `MIRROR_CANVAS` - - `SOFT_PROOFING` - - `ISOLATE_LAYER` - - `VIEW_REFERENCE_IMAGES` - - `VIEW_ASSISTANTS` - - `VIEW_ASSISTANTS_PREVIEWS` - - `VIEW_GRID` - - `VIEW_RULER` - - `VIEW_ONION_SKIN` - - `SNAP_ASSISTANT` - - `SNAP_TO_GRID` """ ERASER = "erase_action" @@ -41,6 +27,11 @@ class Toggle(Enum): SNAP_ASSISTANT = "toggle_assistant" SNAP_TO_GRID = "view_snap_to_grid" + @property + def pretty_name(self) -> str: + action: QWidgetAction = Api.instance().action(self.value) + return action.text() + @property def state(self) -> bool: """Return state of checkable krita action called `action_name`.""" diff --git a/shortcut_composer/api_krita/enums/tool.py b/shortcut_composer/api_krita/enums/tool.py index 6a13fad5..1ceabb52 100644 --- a/shortcut_composer/api_krita/enums/tool.py +++ b/shortcut_composer/api_krita/enums/tool.py @@ -5,6 +5,7 @@ from enum import Enum from PyQt5.QtGui import QIcon +from PyQt5.QtWidgets import QWidgetAction class Tool(Enum): @@ -14,43 +15,6 @@ class Tool(Enum): Extended with modes of the transform tool. Example usage: `Tool.FREEHAND_BRUSH` - - Available tools: - - `FREEHAND_BRUSH` - - `FREEHAND_SELECTION` - - `GRADIENT` - - `LINE` - - `TRANSFORM` - - `MOVE` - - `RECTANGULAR_SELECTION` - - `CONTIGUOUS_SELECTION` - - `REFERENCE` - - `CROP` - - `BEZIER_PATH` - - `FREEHAND_PATH` - - `POLYLINE` - - `SHAPE_SELECT` - - `ASSISTANTS` - - `COLOR_SAMPLER` - - `POLYGON` - - `MEASUREMENT` - - `TEXT` - - `ELLIPSE` - - `FILL` - - `BEZIER_SELECTION` - - `DYNAMIC_BRUSH` - - `RECTANGLE` - - `PAN` - - `MULTI_BRUSH` - - `EDIT_SHAPES` - - `ELIPTICAL_SELECTION` - - `SMART_PATCH` - - `COLORIZE_MASK` - - `SIMILAR_COLOR_SELECTION` - - `ZOOM` - - `MAGNETIC_SELECTION` - - `CALLIGRAPHY` - - `POLYGONAL_SELECTION` """ FREEHAND_BRUSH = "KritaShape/KisToolBrush" @@ -104,6 +68,11 @@ def icon(self) -> QIcon: icon_name = _ICON_NAME_MAP.get(self, "edit-delete") return Api.instance().icon(icon_name) + @property + def pretty_name(self) -> str: + action: QWidgetAction = Api.instance().action(self.value) + return action.text() + _PAINTABLE = { Tool.FREEHAND_BRUSH, diff --git a/shortcut_composer/api_krita/enums/transform_mode.py b/shortcut_composer/api_krita/enums/transform_mode.py index eb0328a7..c2dd7312 100644 --- a/shortcut_composer/api_krita/enums/transform_mode.py +++ b/shortcut_composer/api_krita/enums/transform_mode.py @@ -14,14 +14,6 @@ class TransformMode(Enum): Extended with modes of the transform tool. Example usage: `Tool.FREEHAND_BRUSH` - - Available modes: - - `FREE` - - `PERSPECTIVE` - - `WARP` - - `CAGE` - - `LIQUIFY` - - `MESH` """ FREE = "Transform tool: free" @@ -46,6 +38,19 @@ def icon(self) -> QIcon: icon_name = _ICON_NAME_MAP[self] return Api.instance().icon(icon_name) + @property + def pretty_name(self) -> str: + return _PRETTY_NAME_MAP[self] + + +_PRETTY_NAME_MAP = { + TransformMode.FREE: "Transform mode: free", + TransformMode.PERSPECTIVE: "Transform mode: perspective", + TransformMode.WARP: "Transform mode: warp", + TransformMode.CAGE: "Transform mode: cage", + TransformMode.LIQUIFY: "Transform mode: liquify", + TransformMode.MESH: "Transform mode: mesh", +} _ICON_NAME_MAP = { TransformMode.FREE: "krita_tool_transform", diff --git a/shortcut_composer/composer_utils/config/global_config.py b/shortcut_composer/composer_utils/config/global_config.py index 63ce2f01..1435ce29 100644 --- a/shortcut_composer/composer_utils/config/global_config.py +++ b/shortcut_composer/composer_utils/config/global_config.py @@ -1,13 +1,8 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import TypeVar -from enum import Enum - from .fields import FieldBase, ImmutableField -T = TypeVar('T', bound=Enum) - class Config: """ diff --git a/shortcut_composer/core_components/controller_base.py b/shortcut_composer/core_components/controller_base.py index 293d899b..f2f67cd8 100644 --- a/shortcut_composer/core_components/controller_base.py +++ b/shortcut_composer/core_components/controller_base.py @@ -28,3 +28,6 @@ def set_value(self, value: T) -> None: def get_label(self, value: T) -> Union[Text, QPixmap, QIcon, None]: """Get value representation that can be displayed in GUI,""" ... + + def get_pretty_name(self, value: T) -> str: + return str(value) diff --git a/shortcut_composer/core_components/controllers/canvas_controllers.py b/shortcut_composer/core_components/controllers/canvas_controllers.py index 7ba2e012..dbe37381 100644 --- a/shortcut_composer/core_components/controllers/canvas_controllers.py +++ b/shortcut_composer/core_components/controllers/canvas_controllers.py @@ -31,7 +31,10 @@ def set_value(self, value: float) -> None: self.canvas.zoom = value def get_label(self, value: float) -> Text: - return Text(f"{round(value/100, 2)}") + return Text(self.get_pretty_name(value)) + + def get_pretty_name(self, value: float) -> str: + return f"{round(value/100, 2)}" class CanvasRotationController(CanvasBasedController, Controller[float]): @@ -52,4 +55,7 @@ def set_value(self, value: float) -> None: self.canvas.rotation = value def get_label(self, value: float) -> Text: - return Text(f"{round(value)}°") + return Text(self.get_pretty_name(value)) + + def get_pretty_name(self, value: float) -> str: + return f"{round(value)}°" diff --git a/shortcut_composer/core_components/controllers/core_controllers.py b/shortcut_composer/core_components/controllers/core_controllers.py index 7aa64bbf..a0089178 100644 --- a/shortcut_composer/core_components/controllers/core_controllers.py +++ b/shortcut_composer/core_components/controllers/core_controllers.py @@ -35,6 +35,9 @@ def set_value(value: Tool) -> None: def get_label(self, value: Tool) -> QIcon: return value.icon + def get_pretty_name(self, value: Tool) -> str: + return value.pretty_name + class TransformModeController(Controller[TransformMode]): """ @@ -64,6 +67,9 @@ def set_value(value: Optional[TransformMode]) -> None: def get_label(self, value: Tool) -> QIcon: return value.icon + def get_pretty_name(self, value: Tool) -> str: + return value.pretty_name + @dataclass class ToggleController(Controller[bool]): @@ -84,6 +90,8 @@ def get_value(self) -> bool: def set_value(self, value: bool) -> None: self.toggle.state = value + def get_pretty_name(self, value: Tool) -> str: + return value.pretty_name @dataclass class UndoController(Controller[float]): diff --git a/shortcut_composer/core_components/controllers/document_controllers.py b/shortcut_composer/core_components/controllers/document_controllers.py index d60ba284..4c3b9a71 100644 --- a/shortcut_composer/core_components/controllers/document_controllers.py +++ b/shortcut_composer/core_components/controllers/document_controllers.py @@ -31,6 +31,9 @@ def get_value(self) -> Node: def set_value(self, value: Node) -> None: """Set passed node as current.""" self.document.active_node = value + + def get_pretty_name(self, value: Node) -> str: + return value.name class TimeController(DocumentBasedController, Controller[int]): @@ -50,4 +53,5 @@ def set_value(self, value: int) -> None: self.document.current_time = value def get_label(self, value: int) -> Text: - return Text(str(value)) + return Text(self.get_pretty_name(value)) + diff --git a/shortcut_composer/core_components/controllers/node_controllers.py b/shortcut_composer/core_components/controllers/node_controllers.py index 1441efa4..711f5fc3 100644 --- a/shortcut_composer/core_components/controllers/node_controllers.py +++ b/shortcut_composer/core_components/controllers/node_controllers.py @@ -38,7 +38,10 @@ def set_value(self, opacity: int) -> None: self.active_document.refresh() def get_label(self, value: int) -> Text: - return Text(f"{value}%", Colorizer.percentage(value)) + return Text(self.get_pretty_name(value), Colorizer.percentage(value)) + + def get_pretty_name(self, value: float) -> str: + return f"{value}%" class LayerBlendingModeController(NodeBasedController, Controller[BlendingMode]): @@ -64,6 +67,9 @@ def set_value(self, blending_mode: BlendingMode) -> None: def get_label(self, value: BlendingMode) -> Text: return Text(value.name[:3], Colorizer.blending_mode(value)) + def get_pretty_name(self, value: BlendingMode) -> str: + return value.pretty_name + class LayerVisibilityController(NodeBasedController, Controller[bool]): """ @@ -106,3 +112,6 @@ def set_value(self, blending_mode: BlendingMode) -> None: def get_label(self, value: BlendingMode) -> Text: return Text("+" + value.name[:3], Colorizer.blending_mode(value)) + + def get_pretty_name(self, value: BlendingMode) -> str: + return value.pretty_name diff --git a/shortcut_composer/core_components/controllers/view_controllers.py b/shortcut_composer/core_components/controllers/view_controllers.py index ece0589b..7ba824d2 100644 --- a/shortcut_composer/core_components/controllers/view_controllers.py +++ b/shortcut_composer/core_components/controllers/view_controllers.py @@ -56,7 +56,10 @@ def set_value(self, value: float) -> None: self.view.brush_size = value def get_label(self, value: float) -> Text: - return Text(f"{round(value)}px") + return Text(self.get_pretty_name(value)) + + def get_pretty_name(self, value: float) -> str: + return f"{round(value)}px" class BlendingModeController(ViewBasedController, Controller[BlendingMode]): @@ -79,6 +82,9 @@ def set_value(self, value: BlendingMode) -> None: def get_label(self, value: BlendingMode) -> Text: return Text(value.name[:3], Colorizer.blending_mode(value)) + + def get_pretty_name(self, value: float) -> str: + return f"{round(value)}px" class OpacityController(ViewBasedController, Controller[int]): @@ -100,8 +106,10 @@ def set_value(self, value: int) -> None: self.view.opacity = value def get_label(self, value: int) -> Text: - return Text(f"{value}%", Colorizer.percentage(value)) + return Text(self.get_pretty_name(value), Colorizer.percentage(value)) + def get_pretty_name(self, value: float) -> str: + return f"{value}%" class FlowController(ViewBasedController, Controller[int]): """ @@ -120,4 +128,7 @@ def set_value(self, value: int) -> None: self.view.flow = value def get_label(self, value: int) -> Text: - return Text(f"{value}%", Colorizer.percentage(value)) + return Text(self.get_pretty_name(value), Colorizer.percentage(value)) + + def get_pretty_name(self, value: float) -> str: + return f"{value}%" From 10e0cd788db9516a74f3832dd8ddde3037803321 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 064/125] Isolate input_adapter package from any other packages --- shortcut_composer/input_adapter/__init__.py | 4 +- .../input_adapter/action_manager.py | 12 ++-- shortcut_composer/input_adapter/api_krita.py | 51 ++++++++++++++++ .../input_adapter/complex_action.py | 61 ------------------- .../input_adapter/complex_action_interface.py | 34 +++++++++++ .../input_adapter/shortcut_adapter.py | 6 +- shortcut_composer/templates/cursor_tracker.py | 10 +-- .../mouse_tracker_utils/axis_trackers.py | 6 +- .../templates/multiple_assignment.py | 4 +- shortcut_composer/templates/pie_menu.py | 4 +- .../templates/raw_instructions.py | 41 ++++++++++++- shortcut_composer/templates/temporary_key.py | 4 +- 12 files changed, 149 insertions(+), 88 deletions(-) create mode 100644 shortcut_composer/input_adapter/api_krita.py delete mode 100644 shortcut_composer/input_adapter/complex_action.py create mode 100644 shortcut_composer/input_adapter/complex_action_interface.py diff --git a/shortcut_composer/input_adapter/__init__.py b/shortcut_composer/input_adapter/__init__.py index 1ddc97ce..a9169fd8 100644 --- a/shortcut_composer/input_adapter/__init__.py +++ b/shortcut_composer/input_adapter/__init__.py @@ -18,6 +18,6 @@ """ from .action_manager import ActionManager -from .complex_action import ComplexAction +from .complex_action_interface import ComplexActionInterface -__all__ = ['ActionManager', 'ComplexAction'] +__all__ = ['ActionManager', 'ComplexActionInterface'] diff --git a/shortcut_composer/input_adapter/action_manager.py b/shortcut_composer/input_adapter/action_manager.py index 089c26f9..2d14b65f 100644 --- a/shortcut_composer/input_adapter/action_manager.py +++ b/shortcut_composer/input_adapter/action_manager.py @@ -11,8 +11,8 @@ from PyQt5.QtWidgets import QWidgetAction -from api_krita import Krita -from .complex_action import ComplexAction +from .api_krita import Krita +from .complex_action_interface import ComplexActionInterface from .event_filter import ReleaseKeyEventFilter from .shortcut_adapter import ShortcutAdapter @@ -28,7 +28,7 @@ class ActionContainer: interface at right time. """ - core_action: ComplexAction + core_action: ComplexActionInterface krita_action: QWidgetAction shortcut: ShortcutAdapter @@ -36,7 +36,7 @@ def __post_init__(self) -> None: """Bind key_press method to action 'trigger' event.""" self.krita_action.triggered.connect(self.shortcut.on_key_press) - def replace_action(self, new_action: ComplexAction) -> None: + def replace_action(self, new_action: ComplexActionInterface) -> None: """Replace plugin action managed by this container.""" self.core_action = new_action self.shortcut.action = new_action @@ -56,7 +56,7 @@ def __init__(self, window) -> None: self._event_filter = ReleaseKeyEventFilter() self._stored_actions: Dict[str, ActionContainer] = {} - def bind_action(self, action: ComplexAction) -> None: + def bind_action(self, action: ComplexActionInterface) -> None: """ Create action components and stores them together. @@ -76,7 +76,7 @@ def bind_action(self, action: ComplexAction) -> None: ) self._stored_actions[action.name] = container - def _create_adapter(self, action: ComplexAction) -> ShortcutAdapter: + def _create_adapter(self, action: ComplexActionInterface) -> ShortcutAdapter: """ Create ShortcutAdapter which runs elements of ComplexAction interface. diff --git a/shortcut_composer/input_adapter/api_krita.py b/shortcut_composer/input_adapter/api_krita.py new file mode 100644 index 00000000..6f08aef4 --- /dev/null +++ b/shortcut_composer/input_adapter/api_krita.py @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +from krita import Krita as Api +from typing import Callable, Protocol + +from PyQt5.QtWidgets import QWidgetAction +from PyQt5.QtGui import QKeySequence + + +class KritaWindow(Protocol): + """Krita window received in createActions() of main extension file.""" + + def createAction( + self, + name: str, + description: str, + menu: str, / + ) -> QWidgetAction: ... + + +class KritaInstance: + """Wraps krita API for typing, documentation and PEP8 compatibility.""" + + def __init__(self) -> None: + self.instance = Api.instance() + + def get_action_shortcut(self, action_name: str) -> QKeySequence: + """Return shortcut of krita action called `action_name`.""" + return self.instance.action(action_name).shortcut() + + def create_action( + self, + window: KritaWindow, + name: str, + group: str = "", + callback: Callable[[], None] = lambda: None + ): + """ + Create a new action in krita. + + Requires providing a krita window received in createActions() + method of the main extension file. + """ + krita_action = window.createAction(name, name, group) + krita_action.setAutoRepeat(False) + krita_action.triggered.connect(callback) + return krita_action + + +Krita = KritaInstance() diff --git a/shortcut_composer/input_adapter/complex_action.py b/shortcut_composer/input_adapter/complex_action.py deleted file mode 100644 index 01c51d3c..00000000 --- a/shortcut_composer/input_adapter/complex_action.py +++ /dev/null @@ -1,61 +0,0 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus -# SPDX-License-Identifier: GPL-3.0-or-later - -from typing import List, Optional - -from composer_utils.config import Config -from core_components import InstructionHolder, Instruction - - -class ComplexAction: - """ - Stores basic action attributes and grants main plugin action interface. - - ### Arguments: - - - `name` -- unique name of action. Must match the definition - in shortcut_composer.action file - - `instructions` -- (optional) list of additional instructions to - perform on key press and release. - - `short_vs_long_press_time` -- (optional) time [s] that specifies - if key press is short or long. - - Class is meant for creating child classes which override: - - on_key_press - - on_short_key_release - - on_long_key_release - - on_every_key_release - """ - - def __init__( - self, *, - name: str, - instructions: List[Instruction] = [], - short_vs_long_press_time: Optional[float] = None - ) -> None: - self.name = name - self.short_vs_long_press_time = _read_time(short_vs_long_press_time) - self._instructions = InstructionHolder(instructions) - - def on_key_press(self) -> None: - """Called on each press of key specified in settings.""" - self._instructions.on_key_press() - - def on_short_key_release(self) -> None: - """Called when related key was released shortly after press.""" - self._instructions.on_short_key_release() - - def on_long_key_release(self) -> None: - """Called when related key was released after a long time.""" - self._instructions.on_long_key_release() - - def on_every_key_release(self) -> None: - """Called on each release of related key, after short/long callback.""" - self._instructions.on_every_key_release() - - -def _read_time(short_vs_long_press_time: Optional[float]) -> float: - """Return the given time, or time red from krita config if not given.""" - if short_vs_long_press_time is None: - return Config.SHORT_VS_LONG_PRESS_TIME.read() - return short_vs_long_press_time diff --git a/shortcut_composer/input_adapter/complex_action_interface.py b/shortcut_composer/input_adapter/complex_action_interface.py new file mode 100644 index 00000000..78c793fd --- /dev/null +++ b/shortcut_composer/input_adapter/complex_action_interface.py @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + + +class ComplexActionInterface: + """ + Grants main plugin action interface. + + - `name` -- unique name of action. Must match the definition in + shortcut_composer.action file + - `short_vs_long_press_time` -- time [s] that specifies if key press + is short or long. + + Class is meant for creating child classes which override: + - on_key_press + - on_short_key_release + - on_long_key_release + - on_every_key_release + """ + + name: str + short_vs_long_press_time: float + + def on_key_press(self) -> None: + """Called on each press of key specified in settings.""" + + def on_short_key_release(self) -> None: + """Called when related key was released shortly after press.""" + + def on_long_key_release(self) -> None: + """Called when related key was released after a long time.""" + + def on_every_key_release(self) -> None: + """Called on each release of related key, after short/long callback.""" diff --git a/shortcut_composer/input_adapter/shortcut_adapter.py b/shortcut_composer/input_adapter/shortcut_adapter.py index c002d59b..d05906bf 100644 --- a/shortcut_composer/input_adapter/shortcut_adapter.py +++ b/shortcut_composer/input_adapter/shortcut_adapter.py @@ -5,8 +5,8 @@ from PyQt5.QtGui import QKeyEvent, QKeySequence -from api_krita import Krita -from .complex_action import ComplexAction +from .api_krita import Krita +from .complex_action_interface import ComplexActionInterface class ShortcutAdapter: @@ -24,7 +24,7 @@ class ShortcutAdapter: - on_every_key_release (called after short or long release callback) """ - def __init__(self, action: ComplexAction) -> None: + def __init__(self, action: ComplexActionInterface) -> None: self.action = action self.key_released = True self.last_press_time = time() diff --git a/shortcut_composer/templates/cursor_tracker.py b/shortcut_composer/templates/cursor_tracker.py index 06870b48..3647a22f 100644 --- a/shortcut_composer/templates/cursor_tracker.py +++ b/shortcut_composer/templates/cursor_tracker.py @@ -5,18 +5,18 @@ from core_components import Instruction from data_components import Slider -from input_adapter import ComplexAction +from input_adapter import ComplexActionInterface from .mouse_tracker_utils import ( SingleAxisTracker, DoubleAxisTracker, - SliderHandler, -) + SliderHandler) +from .raw_instructions import RawInstructions T = TypeVar("T") U = TypeVar("U") -class CursorTracker(ComplexAction, Generic[T, U]): +class CursorTracker(RawInstructions, Generic[T, U]): """ Switch values with horizontal or vertical mouse movement. @@ -67,7 +67,7 @@ def __new__( horizontal_slider: Optional[Slider[T]] = None, vertical_slider: Optional[Slider[U]] = None, instructions: List[Instruction] = [], - ) -> ComplexAction: + ) -> ComplexActionInterface: """ Pick and create correct ActionPlugin based on provided sliders. diff --git a/shortcut_composer/templates/mouse_tracker_utils/axis_trackers.py b/shortcut_composer/templates/mouse_tracker_utils/axis_trackers.py index 5ec3609b..5a064a6d 100644 --- a/shortcut_composer/templates/mouse_tracker_utils/axis_trackers.py +++ b/shortcut_composer/templates/mouse_tracker_utils/axis_trackers.py @@ -6,11 +6,11 @@ from api_krita import Krita from api_krita.pyqt import Timer from core_components import Instruction -from input_adapter import ComplexAction from .slider_handler import SliderHandler +from ..raw_instructions import RawInstructions -class SingleAxisTracker(ComplexAction): +class SingleAxisTracker(RawInstructions): """ Track the mouse along one axis to switch values. @@ -44,7 +44,7 @@ def on_every_key_release(self) -> None: self._handler.stop() -class DoubleAxisTracker(ComplexAction): +class DoubleAxisTracker(RawInstructions): """ Track the mouse along the axis which had the biggest initial movement. diff --git a/shortcut_composer/templates/multiple_assignment.py b/shortcut_composer/templates/multiple_assignment.py index 7de731f7..56527dd3 100644 --- a/shortcut_composer/templates/multiple_assignment.py +++ b/shortcut_composer/templates/multiple_assignment.py @@ -5,12 +5,12 @@ from itertools import cycle from core_components import Controller, Instruction -from input_adapter import ComplexAction +from .raw_instructions import RawInstructions T = TypeVar('T') -class MultipleAssignment(ComplexAction, Generic[T]): +class MultipleAssignment(RawInstructions, Generic[T]): """ Cycle multiple values (short press) or return to default (long press). diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 4ad13661..a8871270 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -9,7 +9,6 @@ from api_krita import Krita from core_components import Controller, Instruction -from input_adapter import ComplexAction from .pie_menu_utils import ( create_pie_settings_window, create_local_config, @@ -18,11 +17,12 @@ PieStyle, Label) from .pie_menu_utils.widget_utils import EditMode, RoundButton, NotifyingList +from .raw_instructions import RawInstructions T = TypeVar('T') -class PieMenu(ComplexAction, Generic[T]): +class PieMenu(RawInstructions, Generic[T]): """ Pick value by hovering over a pie menu widget. diff --git a/shortcut_composer/templates/raw_instructions.py b/shortcut_composer/templates/raw_instructions.py index 067affdf..b0d569ef 100644 --- a/shortcut_composer/templates/raw_instructions.py +++ b/shortcut_composer/templates/raw_instructions.py @@ -1,10 +1,14 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from input_adapter import ComplexAction +from typing import List, Optional +from composer_utils.config import Config +from core_components import InstructionHolder, Instruction +from input_adapter import ComplexActionInterface -class RawInstructions(ComplexAction): + +class RawInstructions(ComplexActionInterface): """ Temporarily toggle plugin instructions. @@ -35,3 +39,36 @@ class RawInstructions(ComplexAction): ) ``` """ + + def __init__( + self, *, + name: str, + instructions: List[Instruction] = [], + short_vs_long_press_time: Optional[float] = None + ) -> None: + self.name = name + self.short_vs_long_press_time = _read_time(short_vs_long_press_time) + self._instructions = InstructionHolder(instructions) + + def on_key_press(self) -> None: + """Called on each press of key specified in settings.""" + self._instructions.on_key_press() + + def on_short_key_release(self) -> None: + """Called when related key was released shortly after press.""" + self._instructions.on_short_key_release() + + def on_long_key_release(self) -> None: + """Called when related key was released after a long time.""" + self._instructions.on_long_key_release() + + def on_every_key_release(self) -> None: + """Called on each release of related key, after short/long callback.""" + self._instructions.on_every_key_release() + + +def _read_time(short_vs_long_press_time: Optional[float]) -> float: + """Return the given time, or time red from krita config if not given.""" + if short_vs_long_press_time is None: + return Config.SHORT_VS_LONG_PRESS_TIME.read() + return short_vs_long_press_time diff --git a/shortcut_composer/templates/temporary_key.py b/shortcut_composer/templates/temporary_key.py index 5b398805..bdb86b50 100644 --- a/shortcut_composer/templates/temporary_key.py +++ b/shortcut_composer/templates/temporary_key.py @@ -4,12 +4,12 @@ from typing import List, TypeVar, Generic, Optional from core_components import Controller, Instruction -from input_adapter import ComplexAction +from .raw_instructions import RawInstructions T = TypeVar('T') -class TemporaryKey(ComplexAction, Generic[T]): +class TemporaryKey(RawInstructions, Generic[T]): """ Temporarily activate (long press) a value or toggle it (short press). From cd6b20248a3dae6ec7ac1533e20be136fdfbbd0a Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 065/125] Temp config More work on fields Created parsers Storing progress on divided Parsers and Fields Fixed parsing errors --- .../composer_utils/config/fields.py | 145 ++++++++---------- .../composer_utils/config/parsers.py | 75 +++++++++ 2 files changed, 142 insertions(+), 78 deletions(-) create mode 100644 shortcut_composer/composer_utils/config/parsers.py diff --git a/shortcut_composer/composer_utils/config/fields.py b/shortcut_composer/composer_utils/config/fields.py index 9e23274c..df5f6ef0 100644 --- a/shortcut_composer/composer_utils/config/fields.py +++ b/shortcut_composer/composer_utils/config/fields.py @@ -1,128 +1,117 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import TypeVar, Generic, Optional, List, Type, Callable +from typing import TypeVar, Generic, Optional, List, Callable, final from abc import ABC, abstractmethod from enum import Enum from api_krita import Krita +from .parsers import Parser, BoolParser, EnumParser, BasicParser T = TypeVar('T') +ListT = TypeVar('ListT', bound=List[Enum]) + + +def field(name: str, default: T, passed_type: Optional[type] = None + ) -> 'FieldBase[T]': + if isinstance(default, list): + return ListField(name, default, passed_type) + return NonListField(name, default, passed_type) class FieldBase(Generic[T], ABC): - def __init__(self, name: str, default: T) -> None: + def __init__(self, name: str, default: T, type: Optional[type] = None): self.name = name self.default = default + self.type = self._get_type(type) + self.parser = self._get_parser() self._on_change_callbacks: List[Callable[[], None]] = [] - def _read_raw(self) -> Optional[str]: - red_value = Krita.read_setting( + def _get_type(self, passed_type: Optional[type]) -> type: + if not isinstance(self.default, list): + return type(self.default) + if not self.default: + if passed_type is None: + raise ValueError("Type not given for a list") + return passed_type + return type(self.default[0]) + + def _get_parser(self) -> Parser[T]: + if issubclass(self.type, Enum): + return EnumParser(self.type) + + return { + int: BasicParser(int), + float: BasicParser(float), + str: BasicParser(str), + bool: BoolParser() + }[self.type] # type: ignore + + @final + def write(self, value: T): + if self._is_write_redundant(value): + return + + Krita.write_setting( group="ShortcutComposer", name=self.name, - default="Not stored") - return None if red_value == "Not stored" else red_value + value=self._to_string(value)) + for callback in self._on_change_callbacks: + callback() + @abstractmethod + def read(self) -> T: ... + + @final def reset_default(self) -> None: self.write(self.default) + @final def register_callback(self, callback: Callable[[], None]): self._on_change_callbacks.append(callback) + @final + def _read_raw(self) -> Optional[str]: + red_value = Krita.read_setting( + group="ShortcutComposer", + name=self.name, + default="Not stored") + return None if red_value == "Not stored" else red_value + + @final def _is_write_redundant(self, value: T): if self.read() == value: return True current_value = self._read_raw() return current_value is None and value == self.default - def _notify_subscribers(self): - for callback in self._on_change_callbacks: - callback() - - @abstractmethod - def read(self) -> T: ... - @abstractmethod - def write(self, value: T): ... - - @property - def type(self) -> Type[T]: ... - - -class ImmutableField(FieldBase, Generic[T]): - "For str, int, float." + def _to_string(self, value: T) -> str: ... - def __init__(self, name: str, default: T) -> None: - super().__init__(name, default) +class NonListField(FieldBase, Generic[T]): def read(self) -> T: raw = self._read_raw() if raw is None: return self.default - return self.type(raw) - - def write(self, value: T) -> None: - if self._is_write_redundant(value): - return - - Krita.write_setting( - group="ShortcutComposer", - name=self.name, - value=value) - self._notify_subscribers() + return self.parser.parse_to(raw) - @property - def type(self): - return type(self.default) + def _to_string(self, value: T) -> str: + return self.parser.parse_from(value) -class EnumsListField(FieldBase): - def read(self) -> List[Enum]: +class ListField(FieldBase, Generic[ListT]): + def read(self) -> ListT: raw = self._read_raw() if raw is None: return self.default values_list = raw.split("\t") - return [self.type[value] for value in values_list] - def write(self, value: List[Enum]) -> None: - if self._is_write_redundant(value): - return + return [self.parser.parse_to(value) for value in values_list + ] # type: ignore - to_write = "\t".join([enum.name for enum in value]) - Krita.write_setting( - group="ShortcutComposer", - name=self.name, - value=to_write) - self._notify_subscribers() + def _to_string(self, value: ListT) -> str: + return "\t".join([self.parser.parse_from(item) for item in value]) - @property - def type(self): - element = self.default[0] - return type(element) - - -class ImmutablesListField(FieldBase, Generic[T]): - def __init__(self, name: str, default: List[T]) -> None: - super().__init__(name, default) - - def read(self) -> List[T]: - raw = self._read_raw() - if raw is None: - return self.default - return [self.type(item) for item in raw.split("\t")] - - def write(self, value: List[T]) -> None: - if self._is_write_redundant(value): - return - - Krita.write_setting( - group="ShortcutComposer", - name=self.name, - value="\t".join(map(str, value))) - self._notify_subscribers() - - @property - def type(self): - return type(self.default[0]) diff --git a/shortcut_composer/composer_utils/config/parsers.py b/shortcut_composer/composer_utils/config/parsers.py new file mode 100644 index 00000000..e02641f7 --- /dev/null +++ b/shortcut_composer/composer_utils/config/parsers.py @@ -0,0 +1,75 @@ +from typing import Generic, TypeVar, Type, Protocol +from enum import Enum + +T = TypeVar("T") +Basic = TypeVar("Basic", str, int, float) +EnumT = TypeVar("EnumT", bound=Enum) + + +class Parser(Generic[T], Protocol): + def parse_to(self, value: str) -> T: + ... + + def parse_from(self, value: T) -> str: + ... + + +class BasicParser(Parser[Basic]): + def __init__(self, type: Type[Basic]) -> None: + self.type = type + + def parse_to(self, value: str) -> Basic: + return self.type(value) + + def parse_from(self, value: Basic) -> str: + return str(value) + + +class BoolParser(Parser[bool]): + def parse_to(self, value: str) -> bool: + if value not in ("true", "false"): + raise ValueError(f"Cant parse {value} to bool") + return value == "true" + + def parse_from(self, value: bool) -> str: + return str(value).lower() + + +class EnumParser(Parser[EnumT]): + def __init__(self, type: Type[EnumT]) -> None: + self.type = type + + def parse_to(self, value: str) -> EnumT: + return self.type[value] + + def parse_from(self, value: EnumT) -> str: + return str(value.name) + + +# parser = BasicParser(int) +# value = parser.parse_from(10) +# print(value, type(value)) + +# value = parser.parse_to("10") +# print(value, type(value)) + +# ### +# parser = BoolParser() +# value = parser.parse_from(True) +# print(value, type(value)) + +# value = parser.parse_to("true") +# print(value, type(value)) + +# class MyEnum(Enum): +# ASD = "asd" +# QWE = "qwe" +# ZXC = "zxc" + + +# parser = EnumParser(MyEnum) +# value = parser.parse_from(MyEnum.ASD) +# print(value, type(value)) + +# value = parser.parse_to("ASD") +# print(value, type(value)) From 0d96eb37d6ae1c35da66116be02688f5f0f96400 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 066/125] Integrated refactored config system into plugin --- .../composer_utils/config/__init__.py | 10 ++-------- .../composer_utils/config/config_ui.py | 10 +++++----- .../composer_utils/config/fields.py | 3 +-- .../composer_utils/config/global_config.py | 20 +++++++++---------- .../templates/pie_menu_utils/pie_config.py | 19 ++++++------------ .../templates/pie_menu_utils/pie_style.py | 6 +++--- 6 files changed, 26 insertions(+), 42 deletions(-) diff --git a/shortcut_composer/composer_utils/config/__init__.py b/shortcut_composer/composer_utils/config/__init__.py index 2a42021b..3e8dd315 100644 --- a/shortcut_composer/composer_utils/config/__init__.py +++ b/shortcut_composer/composer_utils/config/__init__.py @@ -1,9 +1,5 @@ from .global_config import Config -from .fields import ( - ImmutablesListField, - ImmutableField, - EnumsListField, - FieldBase) +from .fields import FieldBase, field from .config_ui import ( ConfigBasedWidget, ConfigFormWidget, @@ -12,10 +8,8 @@ __all__ = [ "Config", - "ImmutablesListField", - "ImmutableField", - "EnumsListField", "FieldBase", + "field", "ConfigBasedWidget", "ConfigFormWidget", "ConfigComboBox", diff --git a/shortcut_composer/composer_utils/config/config_ui.py b/shortcut_composer/composer_utils/config/config_ui.py index 4c9c9925..c2f748eb 100644 --- a/shortcut_composer/composer_utils/config/config_ui.py +++ b/shortcut_composer/composer_utils/config/config_ui.py @@ -13,18 +13,18 @@ QWidget, QLabel) -from ..config import ImmutableField +from ..config import FieldBase class ConfigBasedWidget: def __init__( self, - config_field: ImmutableField, + config_field: FieldBase, parent: Optional[QWidget] = None, pretty_name: Optional[str] = None, ) -> None: self._parent = parent - self.config_field: Final[ImmutableField] = config_field + self.config_field: Final[FieldBase] = config_field self.pretty_name = self._init_pretty_name(pretty_name) self.widget: QWidget @@ -49,7 +49,7 @@ def _init_pretty_name(self, pretty_name: Optional[str]) -> str: class ConfigSpinBox(ConfigBasedWidget): def __init__( self, - config_field: Union[ImmutableField[int], ImmutableField[float]], + config_field: Union[FieldBase[int], FieldBase[float]], parent: Optional[QWidget] = None, pretty_name: Optional[str] = None, step: float = 1, @@ -82,7 +82,7 @@ def _init_spin_box(self): class ConfigComboBox(ConfigBasedWidget): def __init__( self, - config_field: ImmutableField[str], + config_field: FieldBase[str], parent: Optional[QWidget] = None, pretty_name: Optional[str] = None, allowed_values: List[Any] = [], diff --git a/shortcut_composer/composer_utils/config/fields.py b/shortcut_composer/composer_utils/config/fields.py index df5f6ef0..79e9fc22 100644 --- a/shortcut_composer/composer_utils/config/fields.py +++ b/shortcut_composer/composer_utils/config/fields.py @@ -38,7 +38,7 @@ def _get_type(self, passed_type: Optional[type]) -> type: def _get_parser(self) -> Parser[T]: if issubclass(self.type, Enum): - return EnumParser(self.type) + return EnumParser(self.type) # type: ignore return { int: BasicParser(int), @@ -114,4 +114,3 @@ def read(self) -> ListT: def _to_string(self, value: ListT) -> str: return "\t".join([self.parser.parse_from(item) for item in value]) - diff --git a/shortcut_composer/composer_utils/config/global_config.py b/shortcut_composer/composer_utils/config/global_config.py index 1435ce29..c7d77faa 100644 --- a/shortcut_composer/composer_utils/config/global_config.py +++ b/shortcut_composer/composer_utils/config/global_config.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from .fields import FieldBase, ImmutableField +from .fields import FieldBase, field class Config: @@ -21,16 +21,14 @@ class Config: EnumConfigValues. """ - SHORT_VS_LONG_PRESS_TIME = ImmutableField("Short vs long press time", 0.3) - TRACKER_SENSITIVITY_SCALE = ImmutableField( - "Tracker sensitivity scale", 1.0) - TRACKER_DEADZONE = ImmutableField("Tracker deadzone", 0) - FPS_LIMIT = ImmutableField("FPS limit", 60) - PIE_GLOBAL_SCALE = ImmutableField("Pie global scale", 1.0) - PIE_ICON_GLOBAL_SCALE = ImmutableField("Pie icon global scale", 1.0) - PIE_DEADZONE_GLOBAL_SCALE = ImmutableField( - "Pie deadzone global scale", 1.0) - PIE_ANIMATION_TIME = ImmutableField("Pie animation time", 0.2) + SHORT_VS_LONG_PRESS_TIME = field("Short vs long press time", 0.3) + TRACKER_SENSITIVITY_SCALE = field("Tracker sensitivity scale", 1.0) + TRACKER_DEADZONE = field("Tracker deadzone", 0) + FPS_LIMIT = field("FPS limit", 60) + PIE_GLOBAL_SCALE = field("Pie global scale", 1.0) + PIE_ICON_GLOBAL_SCALE = field("Pie icon global scale", 1.0) + PIE_DEADZONE_GLOBAL_SCALE = field("Pie deadzone global scale", 1.0) + PIE_ANIMATION_TIME = field("Pie animation time", 0.2) @classmethod def reset_defaults(cls) -> None: diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index a5836cf9..a8e30466 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -2,11 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from typing import List, Generic, TypeVar -from composer_utils.config import ( - EnumsListField, - ImmutablesListField, - ImmutableField, - FieldBase) +from composer_utils.config import FieldBase, field from data_components import Tag T = TypeVar("T") @@ -26,10 +22,10 @@ def __init__( ) -> None: self.name = name self._default_values = values - self.pie_radius_scale = ImmutableField( + self.pie_radius_scale = field( f"{self.name} pie scale", pie_radius_scale) - self.icon_radius_scale = ImmutableField( + self.icon_radius_scale = field( f"{self.name} icon scale", icon_radius_scale) @@ -38,9 +34,8 @@ class PresetPieConfig(PieConfig): def __init__(self, *args): super().__init__(*args) self._default_values: Tag - self.tag_name = ImmutableField( - self.name, self._default_values.tag_name) - self.order = ImmutablesListField(f"{self.name} values", [""]) + self.tag_name = field(self.name, self._default_values.tag_name) + self.order = field(f"{self.name} values", [""]) self.allow_remove = False @property @@ -56,9 +51,7 @@ def values(self): class EnumPieConfig(PieConfig): def __init__(self, *args): super().__init__(*args) - self.order = EnumsListField( - f"{self.name} values", - self._default_values) + self.order = field(f"{self.name} values", self._default_values) self.allow_remove = True @property diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index 37ea666c..8d6b5f79 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -9,7 +9,7 @@ from PyQt5.QtGui import QColor from api_krita import Krita -from composer_utils.config import Config, ImmutableField +from composer_utils.config import Config, FieldBase class PieStyle: @@ -25,8 +25,8 @@ class PieStyle: def __init__( self, - pie_radius_scale: ImmutableField[float], - icon_radius_scale: ImmutableField[float], + pie_radius_scale: FieldBase[float], + icon_radius_scale: FieldBase[float], background_color: Optional[QColor], active_color: QColor, items: list, From 39f8991031056947c17e5eaf05592682e62ecbb5 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 067/125] Separated protocol for Fields, better class division --- .../composer_utils/config/__init__.py | 4 +- .../composer_utils/config/config_ui.py | 12 +- .../composer_utils/config/fields.py | 106 ++++++++++-------- .../composer_utils/config/global_config.py | 7 +- shortcut_composer/data_components/slider.py | 4 +- .../templates/pie_menu_utils/pie_config.py | 4 +- .../templates/pie_menu_utils/pie_style.py | 6 +- 7 files changed, 82 insertions(+), 61 deletions(-) diff --git a/shortcut_composer/composer_utils/config/__init__.py b/shortcut_composer/composer_utils/config/__init__.py index 3e8dd315..22481f32 100644 --- a/shortcut_composer/composer_utils/config/__init__.py +++ b/shortcut_composer/composer_utils/config/__init__.py @@ -1,5 +1,5 @@ from .global_config import Config -from .fields import FieldBase, field +from .fields import Field, field from .config_ui import ( ConfigBasedWidget, ConfigFormWidget, @@ -8,7 +8,7 @@ __all__ = [ "Config", - "FieldBase", + "Field", "field", "ConfigBasedWidget", "ConfigFormWidget", diff --git a/shortcut_composer/composer_utils/config/config_ui.py b/shortcut_composer/composer_utils/config/config_ui.py index c2f748eb..5a12c3a2 100644 --- a/shortcut_composer/composer_utils/config/config_ui.py +++ b/shortcut_composer/composer_utils/config/config_ui.py @@ -13,18 +13,18 @@ QWidget, QLabel) -from ..config import FieldBase +from ..config import Field class ConfigBasedWidget: def __init__( self, - config_field: FieldBase, + config_field: Field, parent: Optional[QWidget] = None, pretty_name: Optional[str] = None, ) -> None: self._parent = parent - self.config_field: Final[FieldBase] = config_field + self.config_field: Final[Field] = config_field self.pretty_name = self._init_pretty_name(pretty_name) self.widget: QWidget @@ -49,7 +49,7 @@ def _init_pretty_name(self, pretty_name: Optional[str]) -> str: class ConfigSpinBox(ConfigBasedWidget): def __init__( self, - config_field: Union[FieldBase[int], FieldBase[float]], + config_field: Union[Field[int], Field[float]], parent: Optional[QWidget] = None, pretty_name: Optional[str] = None, step: float = 1, @@ -69,7 +69,7 @@ def set(self, value): self._spin_box.setValue(value) def _init_spin_box(self): - spin_box = (QSpinBox() if self.config_field.type is int + spin_box = (QSpinBox() if type(self.config_field.default) is int else QDoubleSpinBox()) spin_box.setMinimumWidth(90) spin_box.setObjectName(self.config_field.name) @@ -82,7 +82,7 @@ def _init_spin_box(self): class ConfigComboBox(ConfigBasedWidget): def __init__( self, - config_field: FieldBase[str], + config_field: Field[str], parent: Optional[QWidget] = None, pretty_name: Optional[str] = None, allowed_values: List[Any] = [], diff --git a/shortcut_composer/composer_utils/config/fields.py b/shortcut_composer/composer_utils/config/fields.py index 79e9fc22..faf8910a 100644 --- a/shortcut_composer/composer_utils/config/fields.py +++ b/shortcut_composer/composer_utils/config/fields.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import TypeVar, Generic, Optional, List, Callable, final +from typing import TypeVar, Generic, Optional, List, Callable, final, Protocol, Type from abc import ABC, abstractmethod from enum import Enum @@ -9,43 +9,62 @@ from .parsers import Parser, BoolParser, EnumParser, BasicParser T = TypeVar('T') +E = TypeVar('E', bound=Enum) ListT = TypeVar('ListT', bound=List[Enum]) def field(name: str, default: T, passed_type: Optional[type] = None - ) -> 'FieldBase[T]': + ) -> 'Field[T]': if isinstance(default, list): return ListField(name, default, passed_type) return NonListField(name, default, passed_type) -class FieldBase(Generic[T], ABC): +class Field(Generic[T], Protocol): + + name: str + default: T + + def write(self, value: T) -> None: ... + def read(self) -> T: ... + def register_callback(self, callback: Callable[[], None]): ... + + @final + def reset_default(self) -> None: + self.write(self.default) + + +class FieldBase(Field, ABC, Generic[T]): def __init__(self, name: str, default: T, type: Optional[type] = None): self.name = name self.default = default - self.type = self._get_type(type) - self.parser = self._get_parser() + self._type = self._get_type(type) + self._parser = self._get_parser() self._on_change_callbacks: List[Callable[[], None]] = [] - def _get_type(self, passed_type: Optional[type]) -> type: - if not isinstance(self.default, list): - return type(self.default) - if not self.default: - if passed_type is None: - raise ValueError("Type not given for a list") - return passed_type - return type(self.default[0]) + @abstractmethod + def _get_type(self, passed_type: Optional[type]) -> type: ... + + @abstractmethod + def _to_string(self, value: T) -> str: ... + + @abstractmethod + def read(self) -> T: ... + + @final + def register_callback(self, callback: Callable[[], None]): + self._on_change_callbacks.append(callback) def _get_parser(self) -> Parser[T]: - if issubclass(self.type, Enum): - return EnumParser(self.type) # type: ignore + if issubclass(self._type, Enum): + return EnumParser(self._type) # type: ignore return { int: BasicParser(int), float: BasicParser(float), str: BasicParser(str), bool: BoolParser() - }[self.type] # type: ignore + }[self._type] @final def write(self, value: T): @@ -59,16 +78,12 @@ def write(self, value: T): for callback in self._on_change_callbacks: callback() - @abstractmethod - def read(self) -> T: ... - - @final - def reset_default(self) -> None: - self.write(self.default) - @final - def register_callback(self, callback: Callable[[], None]): - self._on_change_callbacks.append(callback) + def _is_write_redundant(self, value: T): + if self.read() == value: + return True + current_value = self._read_raw() + return current_value is None and value == self.default @final def _read_raw(self) -> Optional[str]: @@ -78,39 +93,42 @@ def _read_raw(self) -> Optional[str]: default="Not stored") return None if red_value == "Not stored" else red_value - @final - def _is_write_redundant(self, value: T): - if self.read() == value: - return True - current_value = self._read_raw() - return current_value is None and value == self.default - @abstractmethod - def _to_string(self, value: T) -> str: ... +class NonListField(FieldBase, Generic[T]): + _parser: Parser[T] + + def _get_type(self, passed_type: Optional[type]) -> Type[T]: + return type(self.default) -class NonListField(FieldBase, Generic[T]): def read(self) -> T: raw = self._read_raw() if raw is None: return self.default - return self.parser.parse_to(raw) + return self._parser.parse_to(raw) def _to_string(self, value: T) -> str: - return self.parser.parse_from(value) + return self._parser.parse_from(value) -class ListField(FieldBase, Generic[ListT]): - def read(self) -> ListT: - raw = self._read_raw() +class ListField(FieldBase, Generic[E]): + + _parser: Parser[E] + def _get_type(self, passed_type: Optional[type]) -> type: + if not self.default: + if passed_type is None: + raise ValueError("Type not given for a list") + return passed_type + return type(self.default[0]) + + def read(self) -> List[E]: + raw = self._read_raw() if raw is None: return self.default values_list = raw.split("\t") + return [self._parser.parse_to(value) for value in values_list] - return [self.parser.parse_to(value) for value in values_list - ] # type: ignore - - def _to_string(self, value: ListT) -> str: - return "\t".join([self.parser.parse_from(item) for item in value]) + def _to_string(self, value: List[E]) -> str: + return "\t".join([self._parser.parse_from(item) for item in value]) diff --git a/shortcut_composer/composer_utils/config/global_config.py b/shortcut_composer/composer_utils/config/global_config.py index c7d77faa..c00d29f1 100644 --- a/shortcut_composer/composer_utils/config/global_config.py +++ b/shortcut_composer/composer_utils/config/global_config.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from .fields import FieldBase, field +from .fields import Field, field class Config: @@ -34,8 +34,11 @@ class Config: def reset_defaults(cls) -> None: """Reset all config files.""" for config_field in cls.__dict__.values(): - if isinstance(config_field, FieldBase): + try: + config_field: Field config_field.reset_default() + except AttributeError: + pass @classmethod def get_sleep_time(cls) -> int: diff --git a/shortcut_composer/data_components/slider.py b/shortcut_composer/data_components/slider.py index 7801dbdc..8e20eb48 100644 --- a/shortcut_composer/data_components/slider.py +++ b/shortcut_composer/data_components/slider.py @@ -3,7 +3,7 @@ from typing import List, Union, Optional, Generic, TypeVar -from composer_utils.config import Config, FieldBase +from composer_utils.config import Config, Field from core_components import Controller from .range import Range @@ -78,7 +78,7 @@ def __init__( self.deadzone = self._read(deadzone, Config.TRACKER_DEADZONE) - def _read(self, passed: Optional[int], field: FieldBase) -> int: + def _read(self, passed: Optional[int], field: Field) -> int: if passed is not None: return passed return field.read() diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index a8e30466..e210ed0e 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from typing import List, Generic, TypeVar -from composer_utils.config import FieldBase, field +from composer_utils.config import Field, field from data_components import Tag T = TypeVar("T") @@ -10,7 +10,7 @@ class PieConfig(Generic[T]): values: List[T] - order: FieldBase + order: Field allow_remove: bool def __init__( diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index 8d6b5f79..0713fd8f 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -9,7 +9,7 @@ from PyQt5.QtGui import QColor from api_krita import Krita -from composer_utils.config import Config, FieldBase +from composer_utils.config import Config, Field class PieStyle: @@ -25,8 +25,8 @@ class PieStyle: def __init__( self, - pie_radius_scale: FieldBase[float], - icon_radius_scale: FieldBase[float], + pie_radius_scale: Field[float], + icon_radius_scale: Field[float], background_color: Optional[QColor], active_color: QColor, items: list, From 7dcb73b12f0852c19ea3f271f77abf9193826fec Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 068/125] Make Field a dispatcher being a single class --- .../composer_utils/config/__init__.py | 3 +- .../composer_utils/config/field_base.py | 83 +++++++++++ .../config/field_implementations.py | 57 ++++++++ .../composer_utils/config/fields.py | 132 ++---------------- .../composer_utils/config/global_config.py | 18 +-- .../templates/pie_menu_utils/pie_config.py | 12 +- 6 files changed, 170 insertions(+), 135 deletions(-) create mode 100644 shortcut_composer/composer_utils/config/field_base.py create mode 100644 shortcut_composer/composer_utils/config/field_implementations.py diff --git a/shortcut_composer/composer_utils/config/__init__.py b/shortcut_composer/composer_utils/config/__init__.py index 22481f32..b090383a 100644 --- a/shortcut_composer/composer_utils/config/__init__.py +++ b/shortcut_composer/composer_utils/config/__init__.py @@ -1,5 +1,5 @@ from .global_config import Config -from .fields import Field, field +from .fields import Field from .config_ui import ( ConfigBasedWidget, ConfigFormWidget, @@ -9,7 +9,6 @@ __all__ = [ "Config", "Field", - "field", "ConfigBasedWidget", "ConfigFormWidget", "ConfigComboBox", diff --git a/shortcut_composer/composer_utils/config/field_base.py b/shortcut_composer/composer_utils/config/field_base.py new file mode 100644 index 00000000..56d791de --- /dev/null +++ b/shortcut_composer/composer_utils/config/field_base.py @@ -0,0 +1,83 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import TypeVar, Generic, Optional, Callable, final, List +from abc import ABC, abstractmethod +from enum import Enum + +from api_krita import Krita +from .parsers import Parser, BoolParser, EnumParser, BasicParser +from .fields import Field + +T = TypeVar('T') +E = TypeVar('E', bound=Enum) +ListT = TypeVar('ListT', bound=List[Enum]) + + +class FieldBase(ABC, Field, Generic[T]): + def __new__(cls, *args, **kwargs) -> 'FieldBase[T]': + obj = object.__new__(cls) + obj.__init__(*args, **kwargs) + return obj + + def __init__(self, name: str, default: T, type: Optional[type] = None): + self.name = name + self.default = default + self._type = self._get_type(type) + self._parser = self._get_parser() + self._on_change_callbacks: List[Callable[[], None]] = [] + + @abstractmethod + def _get_type(self, passed_type: Optional[type]) -> type: ... + + @abstractmethod + def _to_string(self, value: T) -> str: ... + + @abstractmethod + def read(self) -> T: ... + + @final + def register_callback(self, callback: Callable[[], None]): + self._on_change_callbacks.append(callback) + + def _get_parser(self) -> Parser[T]: + if issubclass(self._type, Enum): + return EnumParser(self._type) # type: ignore + + return { + int: BasicParser(int), + float: BasicParser(float), + str: BasicParser(str), + bool: BoolParser() + }[self._type] + + @final + def write(self, value: T): + if self._is_write_redundant(value): + return + + Krita.write_setting( + group="ShortcutComposer", + name=self.name, + value=self._to_string(value)) + for callback in self._on_change_callbacks: + callback() + + @final + def _is_write_redundant(self, value: T): + if self.read() == value: + return True + current_value = self._read_raw() + return current_value is None and value == self.default + + @final + def _read_raw(self) -> Optional[str]: + red_value = Krita.read_setting( + group="ShortcutComposer", + name=self.name, + default="Not stored") + return None if red_value == "Not stored" else red_value + + @final + def reset_default(self) -> None: + self.write(self.default) diff --git a/shortcut_composer/composer_utils/config/field_implementations.py b/shortcut_composer/composer_utils/config/field_implementations.py new file mode 100644 index 00000000..00993dad --- /dev/null +++ b/shortcut_composer/composer_utils/config/field_implementations.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import ( + TypeVar, + Generic, + Optional, + Type, + List) +from enum import Enum + +from .parsers import Parser +from .field_base import FieldBase + +T = TypeVar('T') +E = TypeVar('E', bound=Enum) +ListT = TypeVar('ListT', bound=List[Enum]) + + +class NonListField(FieldBase, Generic[T]): + + _parser: Parser[T] + + def _get_type(self, passed_type: Optional[type]) -> Type[T]: + return type(self.default) + + def read(self) -> T: + raw = self._read_raw() + if raw is None: + return self.default + return self._parser.parse_to(raw) + + def _to_string(self, value: T) -> str: + return self._parser.parse_from(value) + + +class ListField(FieldBase, Generic[E]): + + _parser: Parser[E] + + def _get_type(self, passed_type: Optional[type]) -> type: + if not self.default: + if passed_type is None: + raise ValueError("Type not given for a list") + return passed_type + return type(self.default[0]) + + def read(self) -> List[E]: + raw = self._read_raw() + if raw is None: + return self.default + + values_list = raw.split("\t") + return [self._parser.parse_to(value) for value in values_list] + + def _to_string(self, value: List[E]) -> str: + return "\t".join([self._parser.parse_from(item) for item in value]) diff --git a/shortcut_composer/composer_utils/config/fields.py b/shortcut_composer/composer_utils/config/fields.py index faf8910a..bf452254 100644 --- a/shortcut_composer/composer_utils/config/fields.py +++ b/shortcut_composer/composer_utils/config/fields.py @@ -1,26 +1,25 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import TypeVar, Generic, Optional, List, Callable, final, Protocol, Type -from abc import ABC, abstractmethod -from enum import Enum - -from api_krita import Krita -from .parsers import Parser, BoolParser, EnumParser, BasicParser +from typing import TypeVar, Generic, Optional, Callable T = TypeVar('T') -E = TypeVar('E', bound=Enum) -ListT = TypeVar('ListT', bound=List[Enum]) -def field(name: str, default: T, passed_type: Optional[type] = None - ) -> 'Field[T]': - if isinstance(default, list): - return ListField(name, default, passed_type) - return NonListField(name, default, passed_type) +class Field(Generic[T]): + def __new__( + cls, + name: str, + default: T, + passed_type: Optional[type] = None + ) -> 'Field[T]': + from .field_implementations import ListField, NonListField -class Field(Generic[T], Protocol): + cls.original = super().__new__ + if isinstance(default, list): + return ListField(name, default, passed_type) + return NonListField(name, default, passed_type) name: str default: T @@ -28,107 +27,4 @@ class Field(Generic[T], Protocol): def write(self, value: T) -> None: ... def read(self) -> T: ... def register_callback(self, callback: Callable[[], None]): ... - - @final - def reset_default(self) -> None: - self.write(self.default) - - -class FieldBase(Field, ABC, Generic[T]): - def __init__(self, name: str, default: T, type: Optional[type] = None): - self.name = name - self.default = default - self._type = self._get_type(type) - self._parser = self._get_parser() - self._on_change_callbacks: List[Callable[[], None]] = [] - - @abstractmethod - def _get_type(self, passed_type: Optional[type]) -> type: ... - - @abstractmethod - def _to_string(self, value: T) -> str: ... - - @abstractmethod - def read(self) -> T: ... - - @final - def register_callback(self, callback: Callable[[], None]): - self._on_change_callbacks.append(callback) - - def _get_parser(self) -> Parser[T]: - if issubclass(self._type, Enum): - return EnumParser(self._type) # type: ignore - - return { - int: BasicParser(int), - float: BasicParser(float), - str: BasicParser(str), - bool: BoolParser() - }[self._type] - - @final - def write(self, value: T): - if self._is_write_redundant(value): - return - - Krita.write_setting( - group="ShortcutComposer", - name=self.name, - value=self._to_string(value)) - for callback in self._on_change_callbacks: - callback() - - @final - def _is_write_redundant(self, value: T): - if self.read() == value: - return True - current_value = self._read_raw() - return current_value is None and value == self.default - - @final - def _read_raw(self) -> Optional[str]: - red_value = Krita.read_setting( - group="ShortcutComposer", - name=self.name, - default="Not stored") - return None if red_value == "Not stored" else red_value - - -class NonListField(FieldBase, Generic[T]): - - _parser: Parser[T] - - def _get_type(self, passed_type: Optional[type]) -> Type[T]: - return type(self.default) - - def read(self) -> T: - raw = self._read_raw() - if raw is None: - return self.default - return self._parser.parse_to(raw) - - def _to_string(self, value: T) -> str: - return self._parser.parse_from(value) - - -class ListField(FieldBase, Generic[E]): - - _parser: Parser[E] - - def _get_type(self, passed_type: Optional[type]) -> type: - if not self.default: - if passed_type is None: - raise ValueError("Type not given for a list") - return passed_type - return type(self.default[0]) - - def read(self) -> List[E]: - raw = self._read_raw() - if raw is None: - return self.default - - values_list = raw.split("\t") - return [self._parser.parse_to(value) for value in values_list] - - def _to_string(self, value: List[E]) -> str: - return "\t".join([self._parser.parse_from(item) for item in value]) + def reset_default(self) -> None: ... diff --git a/shortcut_composer/composer_utils/config/global_config.py b/shortcut_composer/composer_utils/config/global_config.py index c00d29f1..f7bf3093 100644 --- a/shortcut_composer/composer_utils/config/global_config.py +++ b/shortcut_composer/composer_utils/config/global_config.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from .fields import Field, field +from .fields import Field class Config: @@ -21,14 +21,14 @@ class Config: EnumConfigValues. """ - SHORT_VS_LONG_PRESS_TIME = field("Short vs long press time", 0.3) - TRACKER_SENSITIVITY_SCALE = field("Tracker sensitivity scale", 1.0) - TRACKER_DEADZONE = field("Tracker deadzone", 0) - FPS_LIMIT = field("FPS limit", 60) - PIE_GLOBAL_SCALE = field("Pie global scale", 1.0) - PIE_ICON_GLOBAL_SCALE = field("Pie icon global scale", 1.0) - PIE_DEADZONE_GLOBAL_SCALE = field("Pie deadzone global scale", 1.0) - PIE_ANIMATION_TIME = field("Pie animation time", 0.2) + SHORT_VS_LONG_PRESS_TIME = Field("Short vs long press time", 0.3) + TRACKER_SENSITIVITY_SCALE = Field("Tracker sensitivity scale", 1.0) + TRACKER_DEADZONE = Field("Tracker deadzone", 0) + FPS_LIMIT = Field("FPS limit", 60) + PIE_GLOBAL_SCALE = Field("Pie global scale", 1.0) + PIE_ICON_GLOBAL_SCALE = Field("Pie icon global scale", 1.0) + PIE_DEADZONE_GLOBAL_SCALE = Field("Pie deadzone global scale", 1.0) + PIE_ANIMATION_TIME = Field("Pie animation time", 0.2) @classmethod def reset_defaults(cls) -> None: diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index e210ed0e..b173e8c0 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from typing import List, Generic, TypeVar -from composer_utils.config import Field, field +from composer_utils.config import Field from data_components import Tag T = TypeVar("T") @@ -22,10 +22,10 @@ def __init__( ) -> None: self.name = name self._default_values = values - self.pie_radius_scale = field( + self.pie_radius_scale = Field( f"{self.name} pie scale", pie_radius_scale) - self.icon_radius_scale = field( + self.icon_radius_scale = Field( f"{self.name} icon scale", icon_radius_scale) @@ -34,8 +34,8 @@ class PresetPieConfig(PieConfig): def __init__(self, *args): super().__init__(*args) self._default_values: Tag - self.tag_name = field(self.name, self._default_values.tag_name) - self.order = field(f"{self.name} values", [""]) + self.tag_name = Field(self.name, self._default_values.tag_name) + self.order = Field(f"{self.name} values", [""]) self.allow_remove = False @property @@ -51,7 +51,7 @@ def values(self): class EnumPieConfig(PieConfig): def __init__(self, *args): super().__init__(*args) - self.order = field(f"{self.name} values", self._default_values) + self.order = Field(f"{self.name} values", self._default_values) self.allow_remove = True @property From a2be7e20370109b46debb51453a4d86aa65d8cb3 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 069/125] Moved config_system to separate, importable package --- shortcut_composer/composer_utils/__init__.py | 3 +- .../composer_utils/config/__init__.py | 15 -- .../composer_utils/config/config_ui.py | 152 ------------------ .../{config => }/global_config.py | 2 +- .../composer_utils/settings_dialog.py | 3 +- .../composer_utils/tabs/action_values_tab.py | 2 +- .../composer_utils/utils/action_values.py | 2 +- shortcut_composer/config_system/__init__.py | 6 + .../fields.py => config_system/field.py} | 0 .../config => config_system}/field_base.py | 2 +- .../field_implementations.py | 0 .../config => config_system}/parsers.py | 32 +--- .../config_system/ui/__init__.py | 13 ++ .../config_system/ui/config_based_widget.py | 38 +++++ .../config_system/ui/config_form_widget.py | 54 +++++++ shortcut_composer/config_system/ui/widgets.py | 72 +++++++++ shortcut_composer/data_components/slider.py | 3 +- .../mouse_tracker_utils/slider_handler.py | 2 +- .../templates/pie_menu_utils/label.py | 2 +- .../templates/pie_menu_utils/pie_manager.py | 2 +- .../templates/pie_menu_utils/pie_style.py | 3 +- .../templates/pie_menu_utils/pie_widget.py | 2 +- .../settings_gui/enum_pie_settings.py | 2 +- .../settings_gui/pie_settings.py | 2 +- .../settings_gui/preset_pie_settings.py | 2 +- .../templates/raw_instructions.py | 2 +- 26 files changed, 206 insertions(+), 212 deletions(-) delete mode 100644 shortcut_composer/composer_utils/config/__init__.py delete mode 100644 shortcut_composer/composer_utils/config/config_ui.py rename shortcut_composer/composer_utils/{config => }/global_config.py (96%) create mode 100644 shortcut_composer/config_system/__init__.py rename shortcut_composer/{composer_utils/config/fields.py => config_system/field.py} (100%) rename shortcut_composer/{composer_utils/config => config_system}/field_base.py (98%) rename shortcut_composer/{composer_utils/config => config_system}/field_implementations.py (100%) rename shortcut_composer/{composer_utils/config => config_system}/parsers.py (67%) create mode 100644 shortcut_composer/config_system/ui/__init__.py create mode 100644 shortcut_composer/config_system/ui/config_based_widget.py create mode 100644 shortcut_composer/config_system/ui/config_form_widget.py create mode 100644 shortcut_composer/config_system/ui/widgets.py diff --git a/shortcut_composer/composer_utils/__init__.py b/shortcut_composer/composer_utils/__init__.py index e2060204..f1eafe2d 100644 --- a/shortcut_composer/composer_utils/__init__.py +++ b/shortcut_composer/composer_utils/__init__.py @@ -4,5 +4,6 @@ """Utilities specific for this plugin. Not directly reusable elsewhere.""" from .settings_dialog import SettingsDialog +from .global_config import Config -__all__ = ["SettingsDialog"] +__all__ = ["SettingsDialog", "Config"] diff --git a/shortcut_composer/composer_utils/config/__init__.py b/shortcut_composer/composer_utils/config/__init__.py deleted file mode 100644 index b090383a..00000000 --- a/shortcut_composer/composer_utils/config/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -from .global_config import Config -from .fields import Field -from .config_ui import ( - ConfigBasedWidget, - ConfigFormWidget, - ConfigComboBox, - ConfigSpinBox) - -__all__ = [ - "Config", - "Field", - "ConfigBasedWidget", - "ConfigFormWidget", - "ConfigComboBox", - "ConfigSpinBox"] diff --git a/shortcut_composer/composer_utils/config/config_ui.py b/shortcut_composer/composer_utils/config/config_ui.py deleted file mode 100644 index 5a12c3a2..00000000 --- a/shortcut_composer/composer_utils/config/config_ui.py +++ /dev/null @@ -1,152 +0,0 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus -# SPDX-License-Identifier: GPL-3.0-or-later - -from abc import abstractmethod -from typing import Any, List, Union, Final, Optional -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import ( - QDoubleSpinBox, - QFormLayout, - QSplitter, - QComboBox, - QSpinBox, - QWidget, - QLabel) - -from ..config import Field - - -class ConfigBasedWidget: - def __init__( - self, - config_field: Field, - parent: Optional[QWidget] = None, - pretty_name: Optional[str] = None, - ) -> None: - self._parent = parent - self.config_field: Final[Field] = config_field - self.pretty_name = self._init_pretty_name(pretty_name) - self.widget: QWidget - - @abstractmethod - def read(self): ... - - @abstractmethod - def set(self, value): ... - - def reset(self): - self.set(self.config_field.read()) - - def save(self): - self.config_field.write(self.read()) - - def _init_pretty_name(self, pretty_name: Optional[str]) -> str: - if pretty_name is not None: - return pretty_name - return self.config_field.name - - -class ConfigSpinBox(ConfigBasedWidget): - def __init__( - self, - config_field: Union[Field[int], Field[float]], - parent: Optional[QWidget] = None, - pretty_name: Optional[str] = None, - step: float = 1, - max_value: float = 100, - ) -> None: - super().__init__(config_field, parent, pretty_name) - self._step = step - self._max_value = max_value - self._spin_box = self._init_spin_box() - self.widget: Final[Union[QSpinBox, QDoubleSpinBox]] = self._spin_box - self.reset() - - def read(self): - return self._spin_box.value() - - def set(self, value): - self._spin_box.setValue(value) - - def _init_spin_box(self): - spin_box = (QSpinBox() if type(self.config_field.default) is int - else QDoubleSpinBox()) - spin_box.setMinimumWidth(90) - spin_box.setObjectName(self.config_field.name) - spin_box.setMinimum(0) - spin_box.setSingleStep(self._step) # type: ignore - spin_box.setMaximum(self._max_value) # type: ignore - return spin_box - - -class ConfigComboBox(ConfigBasedWidget): - def __init__( - self, - config_field: Field[str], - parent: Optional[QWidget] = None, - pretty_name: Optional[str] = None, - allowed_values: List[Any] = [], - ) -> None: - super().__init__(config_field, parent, pretty_name) - self._allowed_values = allowed_values - self._combo_box = self._init_combo_box() - self.widget: Final[QComboBox] = self._combo_box - self.reset() - - def _init_combo_box(self) -> QComboBox: - combo_box = QComboBox() - combo_box.setObjectName(self.config_field.name) - return combo_box - - def reset(self): - self._combo_box.clear() - self._combo_box.addItems(self._allowed_values) - self.set(self.config_field.read()) - - def read(self): - return self._combo_box.currentText() - - def set(self, value): - return self._combo_box.setCurrentText(value) - - -class ConfigFormWidget(QWidget): - """Dialog zone consisting of spin boxes.""" - - def __init__(self, elements: List[Union[ConfigBasedWidget, str]]) -> None: - super().__init__() - self._layout = QFormLayout() - self._layout.RowWrapPolicy(QFormLayout.DontWrapRows) - self._layout.setFieldGrowthPolicy(QFormLayout.FieldsStayAtSizeHint) - self._layout.setFormAlignment(Qt.AlignHCenter | Qt.AlignTop) - self._layout.setLabelAlignment(Qt.AlignRight) - self.setLayout(self._layout) - - self._widgets: List[ConfigBasedWidget] = [] - for element in elements: - if isinstance(element, str): - self._add_label(element) - elif isinstance(element, ConfigBasedWidget): - self._add_row(element) - else: - raise TypeError("Unsupported arguments.") - - def _add_row(self, element: ConfigBasedWidget) -> None: - self._widgets.append(element) - self._layout.addRow(f"{element.pretty_name}:", element.widget) - - def _add_label(self, text: str): - label = QLabel(text) - label.setAlignment(Qt.AlignCenter) - self._layout.addRow(QSplitter(Qt.Horizontal)) - self._layout.addRow(label) - - def refresh(self) -> None: - """Read values from krita config and apply them to stored boxes.""" - for element in self._widgets: - element.reset() - - def apply(self) -> None: - """Write values from stored spin boxes to krita config file.""" - for element in self._widgets: - element.save() diff --git a/shortcut_composer/composer_utils/config/global_config.py b/shortcut_composer/composer_utils/global_config.py similarity index 96% rename from shortcut_composer/composer_utils/config/global_config.py rename to shortcut_composer/composer_utils/global_config.py index f7bf3093..0b480770 100644 --- a/shortcut_composer/composer_utils/config/global_config.py +++ b/shortcut_composer/composer_utils/global_config.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from .fields import Field +from shortcut_composer.config_system.field import Field class Config: diff --git a/shortcut_composer/composer_utils/settings_dialog.py b/shortcut_composer/composer_utils/settings_dialog.py index e718fb71..40c49032 100644 --- a/shortcut_composer/composer_utils/settings_dialog.py +++ b/shortcut_composer/composer_utils/settings_dialog.py @@ -9,7 +9,8 @@ from api_krita import Krita from api_krita.wrappers import Database -from .config import Config, ConfigFormWidget, ConfigSpinBox +from config_system.ui import ConfigFormWidget, ConfigSpinBox +from .global_config import Config from .layouts import ButtonsLayout diff --git a/shortcut_composer/composer_utils/tabs/action_values_tab.py b/shortcut_composer/composer_utils/tabs/action_values_tab.py index 26a8792e..794ea8a5 100644 --- a/shortcut_composer/composer_utils/tabs/action_values_tab.py +++ b/shortcut_composer/composer_utils/tabs/action_values_tab.py @@ -9,7 +9,7 @@ from api_krita.enums import BlendingMode, Tool, TransformMode from ..utils import ActionValues -from ..config.global_config import Config +from global_config import Config class ActionValuesTab(QWidget): diff --git a/shortcut_composer/composer_utils/utils/action_values.py b/shortcut_composer/composer_utils/utils/action_values.py index 7247d0e4..42106cec 100644 --- a/shortcut_composer/composer_utils/utils/action_values.py +++ b/shortcut_composer/composer_utils/utils/action_values.py @@ -14,7 +14,7 @@ ) from api_krita import Krita -from ..config.global_config import Config +from composer_utils import Config from .value_list import ValueList diff --git a/shortcut_composer/config_system/__init__.py b/shortcut_composer/config_system/__init__.py new file mode 100644 index 00000000..98033dd2 --- /dev/null +++ b/shortcut_composer/config_system/__init__.py @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +from .field import Field + +__all__ = ["Field"] diff --git a/shortcut_composer/composer_utils/config/fields.py b/shortcut_composer/config_system/field.py similarity index 100% rename from shortcut_composer/composer_utils/config/fields.py rename to shortcut_composer/config_system/field.py diff --git a/shortcut_composer/composer_utils/config/field_base.py b/shortcut_composer/config_system/field_base.py similarity index 98% rename from shortcut_composer/composer_utils/config/field_base.py rename to shortcut_composer/config_system/field_base.py index 56d791de..7f4209c6 100644 --- a/shortcut_composer/composer_utils/config/field_base.py +++ b/shortcut_composer/config_system/field_base.py @@ -7,7 +7,7 @@ from api_krita import Krita from .parsers import Parser, BoolParser, EnumParser, BasicParser -from .fields import Field +from .field import Field T = TypeVar('T') E = TypeVar('E', bound=Enum) diff --git a/shortcut_composer/composer_utils/config/field_implementations.py b/shortcut_composer/config_system/field_implementations.py similarity index 100% rename from shortcut_composer/composer_utils/config/field_implementations.py rename to shortcut_composer/config_system/field_implementations.py diff --git a/shortcut_composer/composer_utils/config/parsers.py b/shortcut_composer/config_system/parsers.py similarity index 67% rename from shortcut_composer/composer_utils/config/parsers.py rename to shortcut_composer/config_system/parsers.py index e02641f7..7a9e5356 100644 --- a/shortcut_composer/composer_utils/config/parsers.py +++ b/shortcut_composer/config_system/parsers.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + from typing import Generic, TypeVar, Type, Protocol from enum import Enum @@ -44,32 +47,3 @@ def parse_to(self, value: str) -> EnumT: def parse_from(self, value: EnumT) -> str: return str(value.name) - - -# parser = BasicParser(int) -# value = parser.parse_from(10) -# print(value, type(value)) - -# value = parser.parse_to("10") -# print(value, type(value)) - -# ### -# parser = BoolParser() -# value = parser.parse_from(True) -# print(value, type(value)) - -# value = parser.parse_to("true") -# print(value, type(value)) - -# class MyEnum(Enum): -# ASD = "asd" -# QWE = "qwe" -# ZXC = "zxc" - - -# parser = EnumParser(MyEnum) -# value = parser.parse_from(MyEnum.ASD) -# print(value, type(value)) - -# value = parser.parse_to("ASD") -# print(value, type(value)) diff --git a/shortcut_composer/config_system/ui/__init__.py b/shortcut_composer/config_system/ui/__init__.py new file mode 100644 index 00000000..1e34d1c9 --- /dev/null +++ b/shortcut_composer/config_system/ui/__init__.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +from .config_based_widget import ConfigBasedWidget +from .config_form_widget import ConfigFormWidget +from .widgets import ConfigComboBox, ConfigSpinBox + +__all__ = [ + "ConfigBasedWidget", + "ConfigFormWidget", + "ConfigComboBox", + "ConfigSpinBox" +] diff --git a/shortcut_composer/config_system/ui/config_based_widget.py b/shortcut_composer/config_system/ui/config_based_widget.py new file mode 100644 index 00000000..4f443042 --- /dev/null +++ b/shortcut_composer/config_system/ui/config_based_widget.py @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +from abc import abstractmethod +from typing import Final, Optional +from PyQt5.QtWidgets import QWidget + +from ..field import Field + + +class ConfigBasedWidget: + def __init__( + self, + config_field: Field, + parent: Optional[QWidget] = None, + pretty_name: Optional[str] = None, + ) -> None: + self._parent = parent + self.config_field: Final[Field] = config_field + self.pretty_name = self._init_pretty_name(pretty_name) + self.widget: QWidget + + @abstractmethod + def read(self): ... + + @abstractmethod + def set(self, value): ... + + def reset(self): + self.set(self.config_field.read()) + + def save(self): + self.config_field.write(self.read()) + + def _init_pretty_name(self, pretty_name: Optional[str]) -> str: + if pretty_name is not None: + return pretty_name + return self.config_field.name diff --git a/shortcut_composer/config_system/ui/config_form_widget.py b/shortcut_composer/config_system/ui/config_form_widget.py new file mode 100644 index 00000000..1c20ed34 --- /dev/null +++ b/shortcut_composer/config_system/ui/config_form_widget.py @@ -0,0 +1,54 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import List, Union +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import ( + QFormLayout, + QSplitter, + QWidget, + QLabel) + +from .config_based_widget import ConfigBasedWidget + + +class ConfigFormWidget(QWidget): + """Dialog zone consisting of spin boxes.""" + + def __init__(self, elements: List[Union[ConfigBasedWidget, str]]) -> None: + super().__init__() + self._layout = QFormLayout() + self._layout.RowWrapPolicy(QFormLayout.DontWrapRows) + self._layout.setFieldGrowthPolicy(QFormLayout.FieldsStayAtSizeHint) + self._layout.setFormAlignment(Qt.AlignHCenter | Qt.AlignTop) + self._layout.setLabelAlignment(Qt.AlignRight) + self.setLayout(self._layout) + + self._widgets: List[ConfigBasedWidget] = [] + for element in elements: + if isinstance(element, str): + self._add_label(element) + elif isinstance(element, ConfigBasedWidget): + self._add_row(element) + else: + raise TypeError("Unsupported arguments.") + + def _add_row(self, element: ConfigBasedWidget) -> None: + self._widgets.append(element) + self._layout.addRow(f"{element.pretty_name}:", element.widget) + + def _add_label(self, text: str): + label = QLabel(text) + label.setAlignment(Qt.AlignCenter) + self._layout.addRow(QSplitter(Qt.Horizontal)) + self._layout.addRow(label) + + def refresh(self) -> None: + """Read values from krita config and apply them to stored boxes.""" + for element in self._widgets: + element.reset() + + def apply(self) -> None: + """Write values from stored spin boxes to krita config file.""" + for element in self._widgets: + element.save() diff --git a/shortcut_composer/config_system/ui/widgets.py b/shortcut_composer/config_system/ui/widgets.py new file mode 100644 index 00000000..9e67dd6a --- /dev/null +++ b/shortcut_composer/config_system/ui/widgets.py @@ -0,0 +1,72 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import Any, List, Union, Final, Optional +from PyQt5.QtWidgets import QDoubleSpinBox, QComboBox, QSpinBox, QWidget + +from ..field import Field +from .config_based_widget import ConfigBasedWidget + + +class ConfigSpinBox(ConfigBasedWidget): + def __init__( + self, + config_field: Union[Field[int], Field[float]], + parent: Optional[QWidget] = None, + pretty_name: Optional[str] = None, + step: float = 1, + max_value: float = 100, + ) -> None: + super().__init__(config_field, parent, pretty_name) + self._step = step + self._max_value = max_value + self._spin_box = self._init_spin_box() + self.widget: Final[Union[QSpinBox, QDoubleSpinBox]] = self._spin_box + self.reset() + + def read(self): + return self._spin_box.value() + + def set(self, value): + self._spin_box.setValue(value) + + def _init_spin_box(self): + spin_box = (QSpinBox() if type(self.config_field.default) is int + else QDoubleSpinBox()) + spin_box.setMinimumWidth(90) + spin_box.setObjectName(self.config_field.name) + spin_box.setMinimum(0) + spin_box.setSingleStep(self._step) # type: ignore + spin_box.setMaximum(self._max_value) # type: ignore + return spin_box + + +class ConfigComboBox(ConfigBasedWidget): + def __init__( + self, + config_field: Field[str], + parent: Optional[QWidget] = None, + pretty_name: Optional[str] = None, + allowed_values: List[Any] = [], + ) -> None: + super().__init__(config_field, parent, pretty_name) + self._allowed_values = allowed_values + self._combo_box = self._init_combo_box() + self.widget: Final[QComboBox] = self._combo_box + self.reset() + + def _init_combo_box(self) -> QComboBox: + combo_box = QComboBox() + combo_box.setObjectName(self.config_field.name) + return combo_box + + def reset(self): + self._combo_box.clear() + self._combo_box.addItems(self._allowed_values) + self.set(self.config_field.read()) + + def read(self): + return self._combo_box.currentText() + + def set(self, value): + return self._combo_box.setCurrentText(value) diff --git a/shortcut_composer/data_components/slider.py b/shortcut_composer/data_components/slider.py index 8e20eb48..47467ea8 100644 --- a/shortcut_composer/data_components/slider.py +++ b/shortcut_composer/data_components/slider.py @@ -3,7 +3,8 @@ from typing import List, Union, Optional, Generic, TypeVar -from composer_utils.config import Config, Field +from composer_utils import Config +from config_system import Field from core_components import Controller from .range import Range diff --git a/shortcut_composer/templates/mouse_tracker_utils/slider_handler.py b/shortcut_composer/templates/mouse_tracker_utils/slider_handler.py index 1fdc6c4a..2b86cb85 100644 --- a/shortcut_composer/templates/mouse_tracker_utils/slider_handler.py +++ b/shortcut_composer/templates/mouse_tracker_utils/slider_handler.py @@ -5,7 +5,7 @@ from api_krita import Krita from api_krita.pyqt import Timer -from composer_utils.config import Config +from composer_utils import Config from data_components import Slider, Range from .new_types import MouseInput, Interpreted from .mouse_interpreter import MouseInterpreter diff --git a/shortcut_composer/templates/pie_menu_utils/label.py b/shortcut_composer/templates/pie_menu_utils/label.py index 01598603..da48b703 100644 --- a/shortcut_composer/templates/pie_menu_utils/label.py +++ b/shortcut_composer/templates/pie_menu_utils/label.py @@ -8,7 +8,7 @@ from PyQt5.QtCore import QPoint from PyQt5.QtGui import QPixmap, QIcon -from composer_utils.config import Config +from composer_utils import Config T = TypeVar("T") diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index deaf563e..17e0efc3 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -6,7 +6,7 @@ from PyQt5.QtGui import QCursor from api_krita.pyqt import Timer -from composer_utils.config import Config +from composer_utils import Config from .settings_gui import PieSettings from .pie_widget import PieWidget from .label import Label diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index 0713fd8f..a466c5c0 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -9,7 +9,8 @@ from PyQt5.QtGui import QColor from api_krita import Krita -from composer_utils.config import Config, Field +from composer_utils import Config +from config_system import Field class PieStyle: diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index 3d99ee41..52875ebe 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -11,7 +11,7 @@ QPaintEvent) from api_krita.pyqt import Painter, AnimatedWidget, BaseWidget -from composer_utils.config import Config +from composer_utils import Config from .pie_style import PieStyle from .label import Label from .label_widget import LabelWidget diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index 4ae02ac6..dd884679 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -3,7 +3,7 @@ from PyQt5.QtWidgets import QVBoxLayout, QTabWidget -from composer_utils.config import ConfigFormWidget, ConfigSpinBox +from config_system.ui import ConfigFormWidget, ConfigSpinBox from ..widget_utils import NotifyingList from ..label import Label from ..pie_style import PieStyle diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py index 594bd7a9..fa4f6d08 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py @@ -4,7 +4,7 @@ from PyQt5.QtGui import QCursor from api_krita.pyqt import AnimatedWidget, BaseWidget -from composer_utils.config import Config +from composer_utils import Config from ..label import Label from ..pie_style import PieStyle diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py index 73b0cea5..6c3643e8 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py @@ -2,7 +2,7 @@ from PyQt5.QtWidgets import QVBoxLayout from api_krita.wrappers import Database -from composer_utils.config import ( +from config_system.ui import ( ConfigFormWidget, ConfigComboBox, ConfigSpinBox) diff --git a/shortcut_composer/templates/raw_instructions.py b/shortcut_composer/templates/raw_instructions.py index b0d569ef..55d0cff3 100644 --- a/shortcut_composer/templates/raw_instructions.py +++ b/shortcut_composer/templates/raw_instructions.py @@ -3,7 +3,7 @@ from typing import List, Optional -from composer_utils.config import Config +from composer_utils import Config from core_components import InstructionHolder, Instruction from input_adapter import ComplexActionInterface From 30789c04dfa5512cae297f88b6a03fb30967b9fa Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 070/125] Field groups for easier config creation --- .../composer_utils/global_config.py | 19 ++++++++++--------- shortcut_composer/config_system/__init__.py | 4 ++-- shortcut_composer/config_system/field.py | 19 +++++++++++++++++-- shortcut_composer/config_system/field_base.py | 11 +++++++++-- .../templates/pie_menu_utils/pie_config.py | 13 +++++++------ 5 files changed, 45 insertions(+), 21 deletions(-) diff --git a/shortcut_composer/composer_utils/global_config.py b/shortcut_composer/composer_utils/global_config.py index 0b480770..ddcfc93d 100644 --- a/shortcut_composer/composer_utils/global_config.py +++ b/shortcut_composer/composer_utils/global_config.py @@ -1,7 +1,8 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from shortcut_composer.config_system.field import Field +from config_system import Field, FieldGroup +field = FieldGroup("ShortcutComposer") class Config: @@ -21,14 +22,14 @@ class Config: EnumConfigValues. """ - SHORT_VS_LONG_PRESS_TIME = Field("Short vs long press time", 0.3) - TRACKER_SENSITIVITY_SCALE = Field("Tracker sensitivity scale", 1.0) - TRACKER_DEADZONE = Field("Tracker deadzone", 0) - FPS_LIMIT = Field("FPS limit", 60) - PIE_GLOBAL_SCALE = Field("Pie global scale", 1.0) - PIE_ICON_GLOBAL_SCALE = Field("Pie icon global scale", 1.0) - PIE_DEADZONE_GLOBAL_SCALE = Field("Pie deadzone global scale", 1.0) - PIE_ANIMATION_TIME = Field("Pie animation time", 0.2) + SHORT_VS_LONG_PRESS_TIME = field("Short vs long press time", 0.3) + TRACKER_SENSITIVITY_SCALE = field("Tracker sensitivity scale", 1.0) + TRACKER_DEADZONE = field("Tracker deadzone", 0) + FPS_LIMIT = field("FPS limit", 60) + PIE_GLOBAL_SCALE = field("Pie global scale", 1.0) + PIE_ICON_GLOBAL_SCALE = field("Pie icon global scale", 1.0) + PIE_DEADZONE_GLOBAL_SCALE = field("Pie deadzone global scale", 1.0) + PIE_ANIMATION_TIME = field("Pie animation time", 0.2) @classmethod def reset_defaults(cls) -> None: diff --git a/shortcut_composer/config_system/__init__.py b/shortcut_composer/config_system/__init__.py index 98033dd2..e615b936 100644 --- a/shortcut_composer/config_system/__init__.py +++ b/shortcut_composer/config_system/__init__.py @@ -1,6 +1,6 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from .field import Field +from .field import Field, FieldGroup -__all__ = ["Field"] +__all__ = ["Field", "FieldGroup"] diff --git a/shortcut_composer/config_system/field.py b/shortcut_composer/config_system/field.py index bf452254..f307ae44 100644 --- a/shortcut_composer/config_system/field.py +++ b/shortcut_composer/config_system/field.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from typing import TypeVar, Generic, Optional, Callable +from dataclasses import dataclass T = TypeVar('T') @@ -10,6 +11,7 @@ class Field(Generic[T]): def __new__( cls, + config_group: str, name: str, default: T, passed_type: Optional[type] = None @@ -18,8 +20,8 @@ def __new__( cls.original = super().__new__ if isinstance(default, list): - return ListField(name, default, passed_type) - return NonListField(name, default, passed_type) + return ListField(config_group, name, default, passed_type) + return NonListField(config_group, name, default, passed_type) name: str default: T @@ -28,3 +30,16 @@ def write(self, value: T) -> None: ... def read(self) -> T: ... def register_callback(self, callback: Callable[[], None]): ... def reset_default(self) -> None: ... + + +@dataclass(frozen=True) +class FieldGroup: + config_group: str + + def __call__( + self, + name: str, + default: T, + passed_type: Optional[type] = None + ) -> Field[T]: + return Field(self.config_group, name, default, passed_type) diff --git a/shortcut_composer/config_system/field_base.py b/shortcut_composer/config_system/field_base.py index 7f4209c6..3c4631b0 100644 --- a/shortcut_composer/config_system/field_base.py +++ b/shortcut_composer/config_system/field_base.py @@ -20,7 +20,14 @@ def __new__(cls, *args, **kwargs) -> 'FieldBase[T]': obj.__init__(*args, **kwargs) return obj - def __init__(self, name: str, default: T, type: Optional[type] = None): + def __init__( + self, + config_group: str, + name: str, + default: T, + type: Optional[type] = None + ): + self.config_group = config_group self.name = name self.default = default self._type = self._get_type(type) @@ -57,7 +64,7 @@ def write(self, value: T): return Krita.write_setting( - group="ShortcutComposer", + group=self.config_group, name=self.name, value=self._to_string(value)) for callback in self._on_change_callbacks: diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index b173e8c0..9b7ba9ec 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -2,9 +2,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later from typing import List, Generic, TypeVar -from composer_utils.config import Field +from config_system import Field, FieldGroup from data_components import Tag +field = FieldGroup("ShortcutComposer") T = TypeVar("T") @@ -22,10 +23,10 @@ def __init__( ) -> None: self.name = name self._default_values = values - self.pie_radius_scale = Field( + self.pie_radius_scale = field( f"{self.name} pie scale", pie_radius_scale) - self.icon_radius_scale = Field( + self.icon_radius_scale = field( f"{self.name} icon scale", icon_radius_scale) @@ -34,8 +35,8 @@ class PresetPieConfig(PieConfig): def __init__(self, *args): super().__init__(*args) self._default_values: Tag - self.tag_name = Field(self.name, self._default_values.tag_name) - self.order = Field(f"{self.name} values", [""]) + self.tag_name = field(self.name, self._default_values.tag_name) + self.order = field(f"{self.name} values", [""]) self.allow_remove = False @property @@ -51,7 +52,7 @@ def values(self): class EnumPieConfig(PieConfig): def __init__(self, *args): super().__init__(*args) - self.order = Field(f"{self.name} values", self._default_values) + self.order = field(f"{self.name} values", self._default_values) self.allow_remove = True @property From 6d8fef11042dd9fa452ecb649f9f0df3b875dd31 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:32:59 +0100 Subject: [PATCH 071/125] Remove dependency to api_krita in config_system --- shortcut_composer/config_system/api_krita.py | 23 +++++++++++++++++++ shortcut_composer/config_system/field_base.py | 4 ++-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 shortcut_composer/config_system/api_krita.py diff --git a/shortcut_composer/config_system/api_krita.py b/shortcut_composer/config_system/api_krita.py new file mode 100644 index 00000000..a244b4ff --- /dev/null +++ b/shortcut_composer/config_system/api_krita.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +from krita import Krita as Api +from typing import Any + + +class KritaInstance: + """Wraps krita API for typing, documentation and PEP8 compatibility.""" + + def __init__(self) -> None: + self.instance = Api.instance() + + def read_setting(self, group: str, name: str, default: str) -> str: + """Read setting from .kritarc file as string.""" + return self.instance.readSetting(group, name, default) + + def write_setting(self, group: str, name: str, value: Any) -> None: + """Write setting to .kritarc file. Value type will be lost.""" + self.instance.writeSetting(group, name, str(value)) + + +Krita = KritaInstance() diff --git a/shortcut_composer/config_system/field_base.py b/shortcut_composer/config_system/field_base.py index 3c4631b0..ef6fc659 100644 --- a/shortcut_composer/config_system/field_base.py +++ b/shortcut_composer/config_system/field_base.py @@ -5,7 +5,7 @@ from abc import ABC, abstractmethod from enum import Enum -from api_krita import Krita +from .api_krita import Krita from .parsers import Parser, BoolParser, EnumParser, BasicParser from .field import Field @@ -56,7 +56,7 @@ def _get_parser(self) -> Parser[T]: float: BasicParser(float), str: BasicParser(str), bool: BoolParser() - }[self._type] + }[self._type] # type: ignore @final def write(self, value: T): From 0526f437c787745048e2b8e9d5a08992f1b8a80c Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:45:43 +0100 Subject: [PATCH 072/125] Bugfix: missing group read --- shortcut_composer/config_system/field_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shortcut_composer/config_system/field_base.py b/shortcut_composer/config_system/field_base.py index ef6fc659..74d7e69c 100644 --- a/shortcut_composer/config_system/field_base.py +++ b/shortcut_composer/config_system/field_base.py @@ -80,7 +80,7 @@ def _is_write_redundant(self, value: T): @final def _read_raw(self) -> Optional[str]: red_value = Krita.read_setting( - group="ShortcutComposer", + group=self.config_group, name=self.name, default="Not stored") return None if red_value == "Not stored" else red_value From d56210a0e110ea756bf12e2d45b4f981f07b951e Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 18:50:36 +0100 Subject: [PATCH 073/125] Separate config groups for each tool --- .../templates/pie_menu_utils/pie_config.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 9b7ba9ec..45a0edbf 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -5,7 +5,6 @@ from config_system import Field, FieldGroup from data_components import Tag -field = FieldGroup("ShortcutComposer") T = TypeVar("T") @@ -23,20 +22,18 @@ def __init__( ) -> None: self.name = name self._default_values = values - self.pie_radius_scale = field( - f"{self.name} pie scale", - pie_radius_scale) - self.icon_radius_scale = field( - f"{self.name} icon scale", - icon_radius_scale) + + self._fields = FieldGroup(f"ShortcutComposer: {name}") + self.pie_radius_scale = self._fields("Pie scale", pie_radius_scale) + self.icon_radius_scale = self._fields("Icon scale", icon_radius_scale) class PresetPieConfig(PieConfig): def __init__(self, *args): super().__init__(*args) self._default_values: Tag - self.tag_name = field(self.name, self._default_values.tag_name) - self.order = field(f"{self.name} values", [""]) + self.tag_name = self._fields("Tag", self._default_values.tag_name) + self.order = self._fields("Values", [""]) self.allow_remove = False @property @@ -52,7 +49,7 @@ def values(self): class EnumPieConfig(PieConfig): def __init__(self, *args): super().__init__(*args) - self.order = field(f"{self.name} values", self._default_values) + self.order = self._fields("Values", self._default_values) self.allow_remove = True @property From c81b68888b742a0c1ed273fe582e9ec846bcc42b Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 24 Mar 2023 19:17:44 +0100 Subject: [PATCH 074/125] Add 1.1.1 to 1.2.0 compatibility fix --- shortcut_composer/__init__.py | 2 ++ .../composer_utils/compatibility_fix.py | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 shortcut_composer/composer_utils/compatibility_fix.py diff --git a/shortcut_composer/__init__.py b/shortcut_composer/__init__.py index 3f079920..e77ec860 100755 --- a/shortcut_composer/__init__.py +++ b/shortcut_composer/__init__.py @@ -16,6 +16,8 @@ from .shortcut_composer import ShortcutComposer from .api_krita import Krita +from .composer_utils.compatibility_fix import fix_config +fix_config() Krita.add_extension(ShortcutComposer) sys.path.remove(directory) diff --git a/shortcut_composer/composer_utils/compatibility_fix.py b/shortcut_composer/composer_utils/compatibility_fix.py new file mode 100644 index 00000000..373f4e3c --- /dev/null +++ b/shortcut_composer/composer_utils/compatibility_fix.py @@ -0,0 +1,30 @@ +from api_krita import Krita + + +def fix_config(): + def fix(group: str, old_name: str, new_name: str): + value = Krita.read_setting("ShortcutComposer", old_name, "not given") + if value != "not given": + Krita.write_setting(group, new_name, value) + + data = ( + ("Pick brush presets (red)", "Tag (red)", "Tag"), + ("Pick brush presets (green)", "Tag (green)", "Tag"), + ("Pick brush presets (blue)", "Tag (blue)", "Tag"), + + ("Pick brush presets (red)", "Tag (red) values", "Values"), + ("Pick brush presets (green)", "Tag (green) values", "Values"), + ("Pick brush presets (blue)", "Tag (blue) values", "Values"), + + ("Pick painting blending modes", "Blending modes values", "Values"), + ("Pick misc tools", "Misc tools values", "Values"), + ("Cycle selection tools", "Selection tools values", "Values"), + ("Pick transform tool modes", "Transform modes values", "Values"), + ( + "Create painting layer with blending mode", + "Create blending layer values", + "Values"), + ) + + for group, old_name, new_name in data: + fix(f"ShortcutComposer: {group}", old_name, new_name) From 3ab6257f1b9321e335aed728c233c56c42a640fb Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 075/125] Bugfix: compatibility layer used to overwrite new settings --- shortcut_composer/composer_utils/compatibility_fix.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shortcut_composer/composer_utils/compatibility_fix.py b/shortcut_composer/composer_utils/compatibility_fix.py index 373f4e3c..660ca268 100644 --- a/shortcut_composer/composer_utils/compatibility_fix.py +++ b/shortcut_composer/composer_utils/compatibility_fix.py @@ -3,6 +3,8 @@ def fix_config(): def fix(group: str, old_name: str, new_name: str): + if Krita.read_setting(group, new_name, "not given") != "not given": + return value = Krita.read_setting("ShortcutComposer", old_name, "not given") if value != "not given": Krita.write_setting(group, new_name, value) From 0495c752d57140625f5960f271ccdc2de917e683 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 076/125] Field group as Field aggregate --- .../composer_utils/global_config.py | 43 ++++++--------- shortcut_composer/config_system/field.py | 27 +++++++-- .../templates/pie_menu_utils/dispatchers.py | 9 +-- .../templates/pie_menu_utils/pie_config.py | 55 +++++++++++-------- 4 files changed, 76 insertions(+), 58 deletions(-) diff --git a/shortcut_composer/composer_utils/global_config.py b/shortcut_composer/composer_utils/global_config.py index ddcfc93d..12823080 100644 --- a/shortcut_composer/composer_utils/global_config.py +++ b/shortcut_composer/composer_utils/global_config.py @@ -1,11 +1,10 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from config_system import Field, FieldGroup -field = FieldGroup("ShortcutComposer") +from config_system import FieldGroup -class Config: +class GlobalConfig(FieldGroup): """ Configuration fields available in the plugin. @@ -22,27 +21,21 @@ class Config: EnumConfigValues. """ - SHORT_VS_LONG_PRESS_TIME = field("Short vs long press time", 0.3) - TRACKER_SENSITIVITY_SCALE = field("Tracker sensitivity scale", 1.0) - TRACKER_DEADZONE = field("Tracker deadzone", 0) - FPS_LIMIT = field("FPS limit", 60) - PIE_GLOBAL_SCALE = field("Pie global scale", 1.0) - PIE_ICON_GLOBAL_SCALE = field("Pie icon global scale", 1.0) - PIE_DEADZONE_GLOBAL_SCALE = field("Pie deadzone global scale", 1.0) - PIE_ANIMATION_TIME = field("Pie animation time", 0.2) - - @classmethod - def reset_defaults(cls) -> None: - """Reset all config files.""" - for config_field in cls.__dict__.values(): - try: - config_field: Field - config_field.reset_default() - except AttributeError: - pass - - @classmethod - def get_sleep_time(cls) -> int: + def __init__(self, name: str) -> None: + super().__init__(name) + self.SHORT_VS_LONG_PRESS_TIME = self("Short vs long press time", 0.3) + self.TRACKER_SENSITIVITY_SCALE = self("Tracker sensitivity scale", 1.0) + self.TRACKER_DEADZONE = self("Tracker deadzone", 0) + self.FPS_LIMIT = self("FPS limit", 60) + self.PIE_GLOBAL_SCALE = self("Pie global scale", 1.0) + self.PIE_ICON_GLOBAL_SCALE = self("Pie icon global scale", 1.0) + self.PIE_DEADZONE_GLOBAL_SCALE = self("Pie deadzone global scale", 1.0) + self.PIE_ANIMATION_TIME = self("Pie animation time", 0.2) + + def get_sleep_time(self) -> int: """Read sleep time from FPS_LIMIT config field.""" - fps_limit = cls.FPS_LIMIT.read() + fps_limit = self.FPS_LIMIT.read() return round(1000/fps_limit) if fps_limit else 1 + + +Config = GlobalConfig("ShortcutComposer") diff --git a/shortcut_composer/config_system/field.py b/shortcut_composer/config_system/field.py index f307ae44..5dd9b907 100644 --- a/shortcut_composer/config_system/field.py +++ b/shortcut_composer/config_system/field.py @@ -1,8 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import TypeVar, Generic, Optional, Callable -from dataclasses import dataclass +from typing import TypeVar, Generic, Optional, Callable, List T = TypeVar('T') @@ -32,9 +31,11 @@ def register_callback(self, callback: Callable[[], None]): ... def reset_default(self) -> None: ... -@dataclass(frozen=True) class FieldGroup: - config_group: str + def __init__(self, name: str) -> None: + self.name = name + self._fields: List[Field] = [] + self._callbacks: List[Callable[[], None]] = [] def __call__( self, @@ -42,4 +43,20 @@ def __call__( default: T, passed_type: Optional[type] = None ) -> Field[T]: - return Field(self.config_group, name, default, passed_type) + field = Field(self.name, name, default, passed_type) + self._fields.append(field) + for callback in self._callbacks: + field.register_callback(callback) + return field + + def reset_default(self): + for field in self._fields: + field.reset_default() + + def register_callback(self, callback: Callable[[], None]): + self._callbacks.append(callback) + for field in self._fields: + field.register_callback(callback) + + def __iter__(self): + return iter(self._fields) diff --git a/shortcut_composer/templates/pie_menu_utils/dispatchers.py b/shortcut_composer/templates/pie_menu_utils/dispatchers.py index 1d5f5226..d4c2f945 100644 --- a/shortcut_composer/templates/pie_menu_utils/dispatchers.py +++ b/shortcut_composer/templates/pie_menu_utils/dispatchers.py @@ -6,7 +6,6 @@ from .settings_gui import PieSettings, PresetPieSettings, EnumPieSettings from .label import Label from .pie_style import PieStyle -from .pie_config import PieConfig, EnumPieConfig, PresetPieConfig from .widget_utils import NotifyingList T = TypeVar("T") @@ -18,7 +17,8 @@ def create_local_config( pie_radius_scale: float, icon_radius_scale: float, ) -> PieConfig[T]: - args = [name, values, pie_radius_scale, icon_radius_scale] + config_name = f"ShortcutComposer: {name}" + args = [config_name, values, pie_radius_scale, icon_radius_scale] if isinstance(values, Tag): return PresetPieConfig(*args) return EnumPieConfig(*args) @@ -31,8 +31,9 @@ def create_pie_settings_window( pie_config: PieConfig, parent=None ) -> PieSettings: + args = [values, used_values, style, pie_config, parent] if isinstance(pie_config, PresetPieConfig): - return PresetPieSettings(values, used_values, style, pie_config, parent) + return PresetPieSettings(*args) elif isinstance(pie_config, EnumPieConfig): - return EnumPieSettings(values, used_values, style, pie_config, parent) + return EnumPieSettings(*args) raise ValueError(f"Unknown pie config {pie_config}") diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 45a0edbf..5117e425 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -1,43 +1,41 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import List, Generic, TypeVar +from typing import List, Generic, TypeVar, Protocol from config_system import Field, FieldGroup from data_components import Tag T = TypeVar("T") -class PieConfig(Generic[T]): +class PieConfig(Protocol, Generic[T]): + name: str values: List[T] - order: Field + order: Field[List[T]] + pie_radius_scale: Field[float] + icon_radius_scale: Field[float] allow_remove: bool + +class PresetPieConfig(FieldGroup, PieConfig): def __init__( self, name: str, - values: list, + values: Tag, pie_radius_scale: float, icon_radius_scale: float, ) -> None: - self.name = name - self._default_values = values - - self._fields = FieldGroup(f"ShortcutComposer: {name}") - self.pie_radius_scale = self._fields("Pie scale", pie_radius_scale) - self.icon_radius_scale = self._fields("Icon scale", icon_radius_scale) - - -class PresetPieConfig(PieConfig): - def __init__(self, *args): - super().__init__(*args) - self._default_values: Tag - self.tag_name = self._fields("Tag", self._default_values.tag_name) - self.order = self._fields("Values", [""]) + super().__init__(name) self.allow_remove = False + self.pie_radius_scale = self("Pie scale", pie_radius_scale) + self.icon_radius_scale = self("Icon scale", icon_radius_scale) + self.tag_name = self("Tag", values.tag_name) + self.order = self("Values", [""]) + # self.order = self("Values", [], passed_type=str) + @property - def values(self): + def values(self) -> List[str]: saved_order = self.order.read() tag_values = Tag(self.tag_name.read()) @@ -46,12 +44,21 @@ def values(self): return preset_order + missing -class EnumPieConfig(PieConfig): - def __init__(self, *args): - super().__init__(*args) - self.order = self._fields("Values", self._default_values) +class EnumPieConfig(FieldGroup, PieConfig, Generic[T]): + def __init__( + self, + name: str, + values: List[T], + pie_radius_scale: float, + icon_radius_scale: float, + ) -> None: + super().__init__(name) self.allow_remove = True + self.pie_radius_scale = self("Pie scale", pie_radius_scale) + self.icon_radius_scale = self("Icon scale", icon_radius_scale) + self.order = self("Values", values) + @property - def values(self): + def values(self) -> List[T]: return self.order.read() From b2092937dba4e5e5caf4ef47f828fa892b36d366 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 077/125] Unified convention: config fields in upper case --- shortcut_composer/templates/pie_menu.py | 4 +-- .../templates/pie_menu_utils/pie_config.py | 27 +++++++++---------- .../settings_gui/enum_pie_settings.py | 4 +-- .../settings_gui/preset_pie_settings.py | 6 ++--- .../pie_menu_utils/widget_utils/edit_mode.py | 2 +- 5 files changed, 21 insertions(+), 22 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index a8871270..e6ffb470 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -111,8 +111,8 @@ def __init__( self._edit_mode = EditMode(self) style_args = { - "pie_radius_scale": self._local_config.pie_radius_scale, - "icon_radius_scale": self._local_config.icon_radius_scale, + "pie_radius_scale": self._local_config.PIE_RADIUS_SCALE, + "icon_radius_scale": self._local_config.ICON_RADIUS_SCALE, "background_color": self._background_color, "active_color": self._active_color} self._unscaled_style = PieStyle(items=[None], **style_args) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 5117e425..2e62be82 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -11,10 +11,10 @@ class PieConfig(Protocol, Generic[T]): name: str values: List[T] - order: Field[List[T]] - pie_radius_scale: Field[float] - icon_radius_scale: Field[float] allow_remove: bool + ORDER: Field[List[T]] + PIE_RADIUS_SCALE: Field[float] + ICON_RADIUS_SCALE: Field[float] class PresetPieConfig(FieldGroup, PieConfig): @@ -28,16 +28,15 @@ def __init__( super().__init__(name) self.allow_remove = False - self.pie_radius_scale = self("Pie scale", pie_radius_scale) - self.icon_radius_scale = self("Icon scale", icon_radius_scale) - self.tag_name = self("Tag", values.tag_name) - self.order = self("Values", [""]) - # self.order = self("Values", [], passed_type=str) + self.PIE_RADIUS_SCALE = self("Pie scale", pie_radius_scale) + self.ICON_RADIUS_SCALE = self("Icon scale", icon_radius_scale) + self.TAG_NAME = self("Tag", values.tag_name) + self.ORDER = self("Values", [], passed_type=str) @property def values(self) -> List[str]: - saved_order = self.order.read() - tag_values = Tag(self.tag_name.read()) + saved_order = self.ORDER.read() + tag_values = Tag(self.TAG_NAME.read()) preset_order = [p for p in saved_order if p in tag_values] missing = [p for p in tag_values if p not in saved_order] @@ -55,10 +54,10 @@ def __init__( super().__init__(name) self.allow_remove = True - self.pie_radius_scale = self("Pie scale", pie_radius_scale) - self.icon_radius_scale = self("Icon scale", icon_radius_scale) - self.order = self("Values", values) + self.PIE_RADIUS_SCALE = self("Pie scale", pie_radius_scale) + self.ICON_RADIUS_SCALE = self("Icon scale", icon_radius_scale) + self.ORDER = self("Values", values) @property def values(self) -> List[T]: - return self.order.read() + return self.ORDER.read() diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index dd884679..87597b9c 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -45,9 +45,9 @@ def register_callback(self, callback: Callable): return tab_holder.addTab(self._action_values, "Action values") self._local_settings = ConfigFormWidget([ ConfigSpinBox( - pie_config.pie_radius_scale, self, "Pie scale", 0.05, 4), + pie_config.PIE_RADIUS_SCALE, self, "Pie scale", 0.05, 4), ConfigSpinBox( - pie_config.icon_radius_scale, self, "Icon scale", 0.05, 4), + pie_config.ICON_RADIUS_SCALE, self, "Icon scale", 0.05, 4), ]) tab_holder.addTab(self._local_settings, "Local settings") diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py index 6c3643e8..e861fd07 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py @@ -32,11 +32,11 @@ def __init__( self._refresh_tags() self._local_settings = ConfigFormWidget([ - ConfigComboBox(pie_config.tag_name, self, "Tag name", self._tags), + ConfigComboBox(pie_config.TAG_NAME, self, "Tag name", self._tags), ConfigSpinBox( - pie_config.pie_radius_scale, self, "Pie scale", 0.05, 4), + pie_config.PIE_RADIUS_SCALE, self, "Pie scale", 0.05, 4), ConfigSpinBox( - pie_config.icon_radius_scale, self, "Icon scale", 0.05, 4), + pie_config.ICON_RADIUS_SCALE, self, "Icon scale", 0.05, 4), ]) layout = QVBoxLayout(self) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py index ba299c9d..511729ce 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py @@ -56,4 +56,4 @@ def _write_settings(self) -> None: return values = [label.value for label in widget.label_holder] - widget.config.order.write(values) + widget.config.ORDER.write(values) From a6e90eb3c7cf258c23e7964cc1c43f78e80df4f5 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 078/125] FieldGroup no longer a functor --- .../composer_utils/global_config.py | 19 +++++++++++-------- shortcut_composer/config_system/field.py | 2 +- .../templates/pie_menu_utils/pie_config.py | 14 +++++++------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/shortcut_composer/composer_utils/global_config.py b/shortcut_composer/composer_utils/global_config.py index 12823080..e2d699ff 100644 --- a/shortcut_composer/composer_utils/global_config.py +++ b/shortcut_composer/composer_utils/global_config.py @@ -23,14 +23,17 @@ class GlobalConfig(FieldGroup): def __init__(self, name: str) -> None: super().__init__(name) - self.SHORT_VS_LONG_PRESS_TIME = self("Short vs long press time", 0.3) - self.TRACKER_SENSITIVITY_SCALE = self("Tracker sensitivity scale", 1.0) - self.TRACKER_DEADZONE = self("Tracker deadzone", 0) - self.FPS_LIMIT = self("FPS limit", 60) - self.PIE_GLOBAL_SCALE = self("Pie global scale", 1.0) - self.PIE_ICON_GLOBAL_SCALE = self("Pie icon global scale", 1.0) - self.PIE_DEADZONE_GLOBAL_SCALE = self("Pie deadzone global scale", 1.0) - self.PIE_ANIMATION_TIME = self("Pie animation time", 0.2) + self.SHORT_VS_LONG_PRESS_TIME = self.field( + "Short vs long press time", 0.3) + self.TRACKER_SENSITIVITY_SCALE = self.field( + "Tracker sensitivity scale", 1.0) + self.TRACKER_DEADZONE = self.field("Tracker deadzone", 0) + self.FPS_LIMIT = self.field("FPS limit", 60) + self.PIE_GLOBAL_SCALE = self.field("Pie global scale", 1.0) + self.PIE_ICON_GLOBAL_SCALE = self.field("Pie icon global scale", 1.0) + self.PIE_DEADZONE_GLOBAL_SCALE = self.field( + "Pie deadzone global scale", 1.0) + self.PIE_ANIMATION_TIME = self.field("Pie animation time", 0.2) def get_sleep_time(self) -> int: """Read sleep time from FPS_LIMIT config field.""" diff --git a/shortcut_composer/config_system/field.py b/shortcut_composer/config_system/field.py index 5dd9b907..7f63d064 100644 --- a/shortcut_composer/config_system/field.py +++ b/shortcut_composer/config_system/field.py @@ -37,7 +37,7 @@ def __init__(self, name: str) -> None: self._fields: List[Field] = [] self._callbacks: List[Callable[[], None]] = [] - def __call__( + def field( self, name: str, default: T, diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 2e62be82..3682d586 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -28,10 +28,10 @@ def __init__( super().__init__(name) self.allow_remove = False - self.PIE_RADIUS_SCALE = self("Pie scale", pie_radius_scale) - self.ICON_RADIUS_SCALE = self("Icon scale", icon_radius_scale) - self.TAG_NAME = self("Tag", values.tag_name) - self.ORDER = self("Values", [], passed_type=str) + self.PIE_RADIUS_SCALE = self.field("Pie scale", pie_radius_scale) + self.ICON_RADIUS_SCALE = self.field("Icon scale", icon_radius_scale) + self.TAG_NAME = self.field("Tag", values.tag_name) + self.ORDER = self.field("Values", [], passed_type=str) @property def values(self) -> List[str]: @@ -54,9 +54,9 @@ def __init__( super().__init__(name) self.allow_remove = True - self.PIE_RADIUS_SCALE = self("Pie scale", pie_radius_scale) - self.ICON_RADIUS_SCALE = self("Icon scale", icon_radius_scale) - self.ORDER = self("Values", values) + self.PIE_RADIUS_SCALE = self.field("Pie scale", pie_radius_scale) + self.ICON_RADIUS_SCALE = self.field("Icon scale", icon_radius_scale) + self.ORDER = self.field("Values", values) @property def values(self) -> List[T]: From c8209f6892786a703477ca4c7099d1d17fe39131 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 079/125] WIP: Applying new config system to pies --- shortcut_composer/templates/pie_menu.py | 12 ++++----- .../templates/pie_menu_utils/pie_config.py | 8 +++--- .../templates/pie_menu_utils/pie_style.py | 25 +++++-------------- .../templates/pie_menu_utils/pie_widget.py | 4 +-- .../settings_gui/pie_settings.py | 2 +- .../widget_utils/label_holder.py | 10 ++++---- .../widget_utils/round_button.py | 5 +++- 7 files changed, 28 insertions(+), 38 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index e6ffb470..0f90fdd8 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -111,15 +111,13 @@ def __init__( self._edit_mode = EditMode(self) style_args = { - "pie_radius_scale": self._local_config.PIE_RADIUS_SCALE, - "icon_radius_scale": self._local_config.ICON_RADIUS_SCALE, + "pie_config": self._local_config, "background_color": self._background_color, "active_color": self._active_color} - self._unscaled_style = PieStyle(items=[None], **style_args) self._style = PieStyle(items=self._labels, **style_args) self.pie_settings = create_pie_settings_window( - style=self._unscaled_style, + style=PieStyle(items=[None], **style_args), values=self._all_labels, used_values=self._labels, pie_config=self._local_config) @@ -136,14 +134,16 @@ def __init__( parent=self.pie_widget, radius_callback=lambda: self._style.setting_button_radius, icon_scale=1.1, - style=self._style) + style=self._style, + config=self._local_config) self.settings_button.clicked.connect(lambda: self._edit_mode.set(True)) self.accept_button = RoundButton( icon=Krita.get_icon("dialog-ok"), parent=self.pie_widget, radius_callback=lambda: self._style.accept_button_radius, icon_scale=1.5, - style=self._style) + style=self._style, + config=self._local_config) self.accept_button.clicked.connect(lambda: self._edit_mode.set(False)) self.accept_button.hide() diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 3682d586..868ca3c7 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -1,14 +1,14 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import List, Generic, TypeVar, Protocol +from typing import List, Generic, TypeVar from config_system import Field, FieldGroup from data_components import Tag T = TypeVar("T") -class PieConfig(Protocol, Generic[T]): +class PieConfig(FieldGroup, Generic[T]): name: str values: List[T] allow_remove: bool @@ -17,7 +17,7 @@ class PieConfig(Protocol, Generic[T]): ICON_RADIUS_SCALE: Field[float] -class PresetPieConfig(FieldGroup, PieConfig): +class PresetPieConfig(PieConfig): def __init__( self, name: str, @@ -43,7 +43,7 @@ def values(self) -> List[str]: return preset_order + missing -class EnumPieConfig(FieldGroup, PieConfig, Generic[T]): +class EnumPieConfig(PieConfig, Generic[T]): def __init__( self, name: str, diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index a466c5c0..982159a7 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -3,14 +3,14 @@ import math import platform -from typing import Optional, Callable, List +from typing import Optional from copy import copy from PyQt5.QtGui import QColor from api_krita import Krita from composer_utils import Config -from config_system import Field +from .pie_config import PieConfig class PieStyle: @@ -26,8 +26,7 @@ class PieStyle: def __init__( self, - pie_radius_scale: Field[float], - icon_radius_scale: Field[float], + pie_config: PieConfig, background_color: Optional[QColor], active_color: QColor, items: list, @@ -35,29 +34,17 @@ def __init__( self._items = items self._base_size = Krita.screen_size/2560 - self._pie_radius_scale = pie_radius_scale - self._icon_radius_scale = icon_radius_scale + self._pie_config = pie_config self._background_color = background_color self.active_color = active_color - self._pie_radius_scale.register_callback(self._notice_change) - self._icon_radius_scale.register_callback(self._notice_change) - self._on_change_callbacks: List[Callable[[], None]] = [] - - def _notice_change(self): - for callback in self._on_change_callbacks: - callback() - @property def pie_radius_scale(self): - return self._pie_radius_scale.read() + return self._pie_config.PIE_RADIUS_SCALE.read() @property def icon_radius_scale(self): - return self._icon_radius_scale.read() - - def register_callback(self, callback: Callable[[], None]): - self._on_change_callbacks.append(callback) + return self._pie_config.ICON_RADIUS_SCALE.read() @property def pie_radius(self) -> int: diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index 52875ebe..adcffbf3 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -69,9 +69,9 @@ def __init__( self.setCursor(Qt.CrossCursor) self._style = style - self._style.register_callback(self._reset) self._labels = labels self.config = config + self.config.register_callback(self._reset) self.active: Optional[Label] = None self.is_edit_mode = False @@ -80,7 +80,7 @@ def __init__( self.label_holder = LabelHolder( self._labels, self._style, - self.config.allow_remove, + self.config, self) self._circle_points: CirclePoints self._reset() diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py index fa4f6d08..8a938e81 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py @@ -29,8 +29,8 @@ def __init__( self._values = values self._style = style - self._style.register_callback(self._reset) self._pie_config = pie_config + self._pie_config.register_callback(self._reset) self._reset() def move_to_pie_side(self): diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py index b8443590..962d1e9a 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py @@ -9,6 +9,7 @@ from ..label import Label from ..label_widget import LabelWidget from ..label_widget_utils import create_label_widget +from ..pie_config import PieConfig from .widget_holder import WidgetHolder from .circle_points import CirclePoints from .notifying_list import NotifyingList @@ -19,14 +20,13 @@ def __init__( self, labels: NotifyingList[Label], style: PieStyle, - allow_remove: bool, + config: PieConfig, owner: BaseWidget, ) -> None: self._labels = labels - self._labels.register_callback(partial(self._reset, False)) self._style = style - self._style.register_callback(partial(self._reset, False)) - self._allow_remove = allow_remove + self._config = config + self._config.register_callback(partial(self._reset, False)) self._owner = owner self.widget_holder: WidgetHolder = WidgetHolder() @@ -39,7 +39,7 @@ def append(self, label: Label): def remove(self, label: Label): if (label in self._labels and len(self._labels) > 1 - and self._allow_remove): + and self._config.allow_remove): self._labels.remove(label) self._reset() diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py index 0692a9e2..cd659536 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py @@ -5,6 +5,7 @@ from PyQt5.QtCore import Qt from ..pie_style import PieStyle +from ..pie_config import PieConfig from api_krita.pyqt import BaseWidget @@ -17,6 +18,7 @@ def __init__( radius_callback: Callable[[], int], icon_scale: float, style: PieStyle, + config: PieConfig, parent: Optional[QWidget] = None, ): QPushButton.__init__(self, icon, "", parent) @@ -25,7 +27,8 @@ def __init__( self._radius_callback = radius_callback self._icon_scale = icon_scale self._style = style - self._style.register_callback(self._reset) + self._config = config + self._config.register_callback(self._reset) if parent is None: self.setWindowFlags(( From c8942ed51f982ee623dbddbf4968beaf7c538d68 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 080/125] Removed notifying replaced by new config system --- shortcut_composer/templates/pie_menu.py | 13 +++++++------ .../templates/pie_menu_utils/dispatchers.py | 3 +-- .../templates/pie_menu_utils/pie_widget.py | 7 +++---- .../settings_gui/enum_pie_settings.py | 5 ++--- .../settings_gui/preset_pie_settings.py | 3 +-- .../pie_menu_utils/widget_utils/__init__.py | 2 -- .../pie_menu_utils/widget_utils/label_holder.py | 6 +++--- .../pie_menu_utils/widget_utils/notifying_list.py | 15 --------------- 8 files changed, 17 insertions(+), 37 deletions(-) delete mode 100644 shortcut_composer/templates/pie_menu_utils/widget_utils/notifying_list.py diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 0f90fdd8..65dd6c92 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -16,7 +16,7 @@ PieWidget, PieStyle, Label) -from .pie_menu_utils.widget_utils import EditMode, RoundButton, NotifyingList +from .pie_menu_utils.widget_utils import EditMode, RoundButton from .raw_instructions import RawInstructions T = TypeVar('T') @@ -103,9 +103,9 @@ def __init__( self._background_color = background_color self._active_color = active_color - self._labels: NotifyingList[Label] = NotifyingList() + self._labels: List[Label] = [] self._reset_labels(self._labels, self._local_config.values) - self._all_labels: NotifyingList[Label] = NotifyingList() + self._all_labels: List[Label] = [] self._reset_labels(self._all_labels, self._get_all_values(values)) self._edit_mode = EditMode(self) @@ -148,7 +148,9 @@ def __init__( self.accept_button.hide() def _reset(self): - self._reset_labels(self._labels, self._local_config.values) + values = self._local_config.values + self._reset_labels(self._labels, values) + self._local_config.ORDER.write(values) self.accept_button.move_center(self.pie_widget.center) self.settings_button.move(QPoint( @@ -175,7 +177,7 @@ def on_every_key_release(self) -> None: def _reset_labels( self, - label_list: NotifyingList[Label[T]], + label_list: List[Label[T]], values: List[T] ) -> None: """Wrap values into paintable label objects with position info.""" @@ -189,7 +191,6 @@ def _reset_labels( except KeyError: continue label_list.append(Label(value=value, display_value=label)) - label_list.notify_about_change() def _get_all_values(self, values: List[T]) -> List[T]: if not values: diff --git a/shortcut_composer/templates/pie_menu_utils/dispatchers.py b/shortcut_composer/templates/pie_menu_utils/dispatchers.py index d4c2f945..fd7245dc 100644 --- a/shortcut_composer/templates/pie_menu_utils/dispatchers.py +++ b/shortcut_composer/templates/pie_menu_utils/dispatchers.py @@ -6,7 +6,6 @@ from .settings_gui import PieSettings, PresetPieSettings, EnumPieSettings from .label import Label from .pie_style import PieStyle -from .widget_utils import NotifyingList T = TypeVar("T") @@ -26,7 +25,7 @@ def create_local_config( def create_pie_settings_window( values: List[Label], - used_values: NotifyingList[Label], + used_values: List[Label], style: PieStyle, pie_config: PieConfig, parent=None diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index adcffbf3..c0f24a93 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import TypeVar, Optional +from typing import TypeVar, Optional, Generic, List from PyQt5.QtCore import Qt from PyQt5.QtGui import ( @@ -17,7 +17,6 @@ from .label_widget import LabelWidget from .pie_config import PieConfig from .widget_utils import ( - NotifyingList, CirclePoints, LabelHolder, PiePainter) @@ -25,7 +24,7 @@ T = TypeVar('T') -class PieWidget(AnimatedWidget, BaseWidget): +class PieWidget(AnimatedWidget, BaseWidget, Generic[T]): """ PyQt5 widget with icons on ring that can be selected by hovering. @@ -51,7 +50,7 @@ class PieWidget(AnimatedWidget, BaseWidget): def __init__( self, style: PieStyle, - labels: NotifyingList, + labels: List[Label[T]], config: PieConfig, parent=None ): diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index 87597b9c..bd382897 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -4,7 +4,6 @@ from PyQt5.QtWidgets import QVBoxLayout, QTabWidget from config_system.ui import ConfigFormWidget, ConfigSpinBox -from ..widget_utils import NotifyingList from ..label import Label from ..pie_style import PieStyle from ..pie_config import EnumPieConfig @@ -16,7 +15,7 @@ class EnumPieSettings(PieSettings): def __init__( self, values: List[Label], - used_values: NotifyingList[Label], + used_values: List[Label], style: PieStyle, pie_config: EnumPieConfig, parent=None @@ -55,7 +54,7 @@ def register_callback(self, callback: Callable): return layout.addWidget(tab_holder) self.setLayout(layout) - self._used_values.register_callback(self.refresh) + self._pie_config.ORDER.register_callback(self.refresh) def refresh(self): for widget in self._action_values._children_list: diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py index e861fd07..d3d3673f 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py @@ -7,7 +7,6 @@ ConfigComboBox, ConfigSpinBox) from ..label import Label -from ..widget_utils import NotifyingList from ..pie_style import PieStyle from ..pie_config import PresetPieConfig from .pie_settings import PieSettings @@ -17,7 +16,7 @@ class PresetPieSettings(PieSettings): def __init__( self, values: List[Label], - used_values: NotifyingList[Label], + used_values: List[Label], style: PieStyle, pie_config: PresetPieConfig, parent=None diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py index a5b9ab67..3478747e 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py @@ -3,7 +3,6 @@ """Additional classes used by pie menu components.""" -from .notifying_list import NotifyingList from .circle_points import CirclePoints from .widget_holder import WidgetHolder from .round_button import RoundButton @@ -12,7 +11,6 @@ from .edit_mode import EditMode __all__ = [ - "NotifyingList", "CirclePoints", "WidgetHolder", "RoundButton", diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py index 962d1e9a..be94fa3c 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py @@ -12,13 +12,12 @@ from ..pie_config import PieConfig from .widget_holder import WidgetHolder from .circle_points import CirclePoints -from .notifying_list import NotifyingList class LabelHolder: def __init__( self, - labels: NotifyingList[Label], + labels: List[Label], style: PieStyle, config: PieConfig, owner: BaseWidget, @@ -81,4 +80,5 @@ def _reset(self, notify: bool = True) -> None: self.widget_holder.add(child) if notify: - self._labels.notify_about_change() + values = [label.value for label in self._labels] + self._config.ORDER.write(values) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/notifying_list.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/notifying_list.py deleted file mode 100644 index 8afa23dd..00000000 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/notifying_list.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Callable, List, TypeVar -T = TypeVar("T") - - -class NotifyingList(List[T]): - def __init__(self, *args, **kwargs): - self._callbacks = [] - super().__init__(*args, **kwargs) - - def register_callback(self, callback: Callable[[], None]): - self._callbacks.append(callback) - - def notify_about_change(self): - for callback in self._callbacks: - callback() From efea7cd77fbef612b7d28092c9346f9e041d8b17 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 081/125] PieConfig values not as property --- shortcut_composer/templates/cursor_tracker.py | 8 -------- shortcut_composer/templates/multiple_assignment.py | 2 +- shortcut_composer/templates/pie_menu.py | 4 ++-- shortcut_composer/templates/pie_menu_utils/pie_config.py | 4 +--- shortcut_composer/templates/temporary_key.py | 2 +- 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/shortcut_composer/templates/cursor_tracker.py b/shortcut_composer/templates/cursor_tracker.py index 3647a22f..c10ab6fd 100644 --- a/shortcut_composer/templates/cursor_tracker.py +++ b/shortcut_composer/templates/cursor_tracker.py @@ -107,11 +107,3 @@ def __new__( ) ) raise ValueError("At least one slider needed.") - - def __init__( - self, - name: str, - horizontal_slider: Optional[Slider[T]] = None, - vertical_slider: Optional[Slider[U]] = None, - instructions: List[Instruction] = [], - ) -> None: ... diff --git a/shortcut_composer/templates/multiple_assignment.py b/shortcut_composer/templates/multiple_assignment.py index 56527dd3..4e6f33e4 100644 --- a/shortcut_composer/templates/multiple_assignment.py +++ b/shortcut_composer/templates/multiple_assignment.py @@ -109,5 +109,5 @@ def _read_default_value(self, value: Optional[T]) -> T: """Read value from controller if it was not given.""" if (default := self._controller.default_value) is None: raise ValueError( - "{self._controller} can't be used with MultipleAssignment.") + f"{self._controller} can't be used with MultipleAssignment.") return value if value is not None else default diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 65dd6c92..95de8318 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -104,7 +104,7 @@ def __init__( self._active_color = active_color self._labels: List[Label] = [] - self._reset_labels(self._labels, self._local_config.values) + self._reset_labels(self._labels, self._local_config.values()) self._all_labels: List[Label] = [] self._reset_labels(self._all_labels, self._get_all_values(values)) @@ -148,7 +148,7 @@ def __init__( self.accept_button.hide() def _reset(self): - values = self._local_config.values + values = self._local_config.values() self._reset_labels(self._labels, values) self._local_config.ORDER.write(values) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 868ca3c7..6624a409 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -10,11 +10,11 @@ class PieConfig(FieldGroup, Generic[T]): name: str - values: List[T] allow_remove: bool ORDER: Field[List[T]] PIE_RADIUS_SCALE: Field[float] ICON_RADIUS_SCALE: Field[float] + def values(self) -> List[T]: ... class PresetPieConfig(PieConfig): @@ -33,7 +33,6 @@ def __init__( self.TAG_NAME = self.field("Tag", values.tag_name) self.ORDER = self.field("Values", [], passed_type=str) - @property def values(self) -> List[str]: saved_order = self.ORDER.read() tag_values = Tag(self.TAG_NAME.read()) @@ -58,6 +57,5 @@ def __init__( self.ICON_RADIUS_SCALE = self.field("Icon scale", icon_radius_scale) self.ORDER = self.field("Values", values) - @property def values(self) -> List[T]: return self.ORDER.read() diff --git a/shortcut_composer/templates/temporary_key.py b/shortcut_composer/templates/temporary_key.py index bdb86b50..a67249e9 100644 --- a/shortcut_composer/templates/temporary_key.py +++ b/shortcut_composer/templates/temporary_key.py @@ -110,5 +110,5 @@ def _read_default_value(self, value: Optional[T]) -> T: """Read value from controller if it was not given.""" if (default := self._controller.default_value) is None: raise ValueError( - "{self._controller} can't be used with TemporaryKeys.") + f"{self._controller} can't be used with TemporaryKeys.") return value if value is not None else default From 3a489f2fce6c85695dc04ce059d693fb88b9b116 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 082/125] Improve input adapter documentation --- shortcut_composer/input_adapter/__init__.py | 15 ++++++--------- shortcut_composer/input_adapter/action_manager.py | 4 ++-- shortcut_composer/input_adapter/api_krita.py | 3 ++- .../input_adapter/complex_action_interface.py | 11 +++-------- 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/shortcut_composer/input_adapter/__init__.py b/shortcut_composer/input_adapter/__init__.py index a9169fd8..ab128ff4 100644 --- a/shortcut_composer/input_adapter/__init__.py +++ b/shortcut_composer/input_adapter/__init__.py @@ -5,16 +5,13 @@ Module contains utilities that allow to connect custom actions to krita. ActionManager (public) allows to use custom action interface of -ComplexAction (public) that supports key pressing, releasing and counting -time to differentiate short and long presses. +ComplexAction (public) that supports: +- key pressing +- key releasing +- distinguishing between short and long key presses -Custom interface is achieved by using ShortcutAdapters (private) - each -instance is responsible for finding right time to call methods of the -ctions. - -Key pressing is achieved the usual way, by connecting a method to right -signal, but key releases require usage of EventFilter (private) common -for all actions. +It has no external dependencies, so that it can be copy-pasted to any +other krita plugin. """ from .action_manager import ActionManager diff --git a/shortcut_composer/input_adapter/action_manager.py b/shortcut_composer/input_adapter/action_manager.py index 2d14b65f..d77cc824 100644 --- a/shortcut_composer/input_adapter/action_manager.py +++ b/shortcut_composer/input_adapter/action_manager.py @@ -26,7 +26,6 @@ class ActionContainer: - `QWidgetAction` krita representation, which ComplexAction implements. - `ShortcutAdapter` manages running elements of ComplexAction interface at right time. - """ core_action: ComplexActionInterface krita_action: QWidgetAction @@ -76,7 +75,8 @@ def bind_action(self, action: ComplexActionInterface) -> None: ) self._stored_actions[action.name] = container - def _create_adapter(self, action: ComplexActionInterface) -> ShortcutAdapter: + def _create_adapter(self, action: ComplexActionInterface) \ + -> ShortcutAdapter: """ Create ShortcutAdapter which runs elements of ComplexAction interface. diff --git a/shortcut_composer/input_adapter/api_krita.py b/shortcut_composer/input_adapter/api_krita.py index 6f08aef4..379eef78 100644 --- a/shortcut_composer/input_adapter/api_krita.py +++ b/shortcut_composer/input_adapter/api_krita.py @@ -1,5 +1,6 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later +"""Required part of api_krita package, so that no dependency is needed.""" from krita import Krita as Api from typing import Callable, Protocol @@ -35,7 +36,7 @@ def create_action( name: str, group: str = "", callback: Callable[[], None] = lambda: None - ): + ) -> QWidgetAction: """ Create a new action in krita. diff --git a/shortcut_composer/input_adapter/complex_action_interface.py b/shortcut_composer/input_adapter/complex_action_interface.py index 78c793fd..192180b4 100644 --- a/shortcut_composer/input_adapter/complex_action_interface.py +++ b/shortcut_composer/input_adapter/complex_action_interface.py @@ -1,21 +1,16 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later +from typing import Protocol -class ComplexActionInterface: +class ComplexActionInterface(Protocol): """ - Grants main plugin action interface. + Interface of the main plugin action. - `name` -- unique name of action. Must match the definition in shortcut_composer.action file - `short_vs_long_press_time` -- time [s] that specifies if key press is short or long. - - Class is meant for creating child classes which override: - - on_key_press - - on_short_key_release - - on_long_key_release - - on_every_key_release """ name: str From fd95c32b87b27b8e57cef3fae9a187cf0d1d42b4 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 083/125] Refactor and improve config system documentation --- shortcut_composer/api_krita/core_api.py | 22 ++++-- shortcut_composer/config_system/api_krita.py | 21 ++++- shortcut_composer/config_system/field.py | 47 +++++++++-- shortcut_composer/config_system/field_base.py | 77 +++++++++---------- .../config_system/field_implementations.py | 56 ++++++++++---- shortcut_composer/config_system/parsers.py | 15 ++++ 6 files changed, 167 insertions(+), 71 deletions(-) diff --git a/shortcut_composer/api_krita/core_api.py b/shortcut_composer/api_krita/core_api.py index 27e8691d..e4b8fac6 100644 --- a/shortcut_composer/api_krita/core_api.py +++ b/shortcut_composer/api_krita/core_api.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from krita import Krita as Api, Extension, qApp -from typing import Callable, Protocol, Any +from typing import Callable, Protocol, Any, Optional from PyQt5.QtWidgets import QMainWindow, QDesktopWidget, QWidgetAction from PyQt5.QtGui import QKeySequence, QColor, QIcon @@ -64,9 +64,21 @@ def get_active_qwindow(self) -> QMainWindow: def get_icon(self, icon_name: str) -> QIcon: return self.instance.icon(icon_name) - def read_setting(self, group: str, name: str, default: str) -> str: - """Read setting from .kritarc file as string.""" - return self.instance.readSetting(group, name, default) + def read_setting( + self, + group: str, + name: str, + default: str = "Not stored" + ) -> Optional[str]: + """ + Read a setting from .kritarc file. + + - Return string red from file if present + - Return default if it was given + - Return None if default was not given + """ + red_value = self.instance.readSetting(group, name, default) + return None if red_value == "Not stored" else red_value def write_setting(self, group: str, name: str, value: Any) -> None: """Write setting to .kritarc file. Value type will be lost.""" @@ -78,7 +90,7 @@ def create_action( name: str, group: str = "", callback: Callable[[], None] = lambda: None - ): + ) -> QWidgetAction: """ Create a new action in krita. diff --git a/shortcut_composer/config_system/api_krita.py b/shortcut_composer/config_system/api_krita.py index a244b4ff..3862ead8 100644 --- a/shortcut_composer/config_system/api_krita.py +++ b/shortcut_composer/config_system/api_krita.py @@ -1,8 +1,9 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later +"""Required part of api_krita package, so that no dependency is needed.""" from krita import Krita as Api -from typing import Any +from typing import Any, Optional class KritaInstance: @@ -11,9 +12,21 @@ class KritaInstance: def __init__(self) -> None: self.instance = Api.instance() - def read_setting(self, group: str, name: str, default: str) -> str: - """Read setting from .kritarc file as string.""" - return self.instance.readSetting(group, name, default) + def read_setting( + self, + group: str, + name: str, + default: str = "Not stored" + ) -> Optional[str]: + """ + Read a setting from .kritarc file. + + - Return string red from file if present + - Return default if it was given + - Return None if default was not given + """ + red_value = self.instance.readSetting(group, name, default) + return None if red_value == "Not stored" else red_value def write_setting(self, group: str, name: str, value: Any) -> None: """Write setting to .kritarc file. Value type will be lost.""" diff --git a/shortcut_composer/config_system/field.py b/shortcut_composer/config_system/field.py index 7f63d064..249a2b3c 100644 --- a/shortcut_composer/config_system/field.py +++ b/shortcut_composer/config_system/field.py @@ -7,31 +7,60 @@ class Field(Generic[T]): + """ + Representation of a single value in .kritarc file. + + Fields are type aware, and allow to read and write from krita config + automatically parsing to the value type. + + Type is determined based on default value passed on initialization. + For empty, homogeneous lists, `parser_type` must be used to + determine type of list elements. + """ def __new__( cls, config_group: str, name: str, default: T, - passed_type: Optional[type] = None + parser_type: Optional[type] = None ) -> 'Field[T]': from .field_implementations import ListField, NonListField cls.original = super().__new__ if isinstance(default, list): - return ListField(config_group, name, default, passed_type) - return NonListField(config_group, name, default, passed_type) + return ListField(config_group, name, default, parser_type) + return NonListField(config_group, name, default, parser_type) + config_group: str + """Configuration section in .kritarc toml file.""" name: str + """Field name in entire config group.""" default: T + """Default value used when the field is not present in file.""" + + def write(self, value: T) -> None: + """Write a value to .kritarc file.""" - def write(self, value: T) -> None: ... - def read(self) -> T: ... - def register_callback(self, callback: Callable[[], None]): ... - def reset_default(self) -> None: ... + def read(self) -> T: + """Return value from .kritarc parsed to field type.""" + ... + + def register_callback(self, callback: Callable[[], None]) -> None: + """Register a method which will be called when field value changes.""" + + def reset_default(self) -> None: + """Write a default value to .kritarc file.""" + ... class FieldGroup: + """ + Representation of section in .kritarc toml file. + + All fields in the group must be created using `field()` method. + """ + def __init__(self, name: str) -> None: self.name = name self._fields: List[Field] = [] @@ -43,6 +72,7 @@ def field( default: T, passed_type: Optional[type] = None ) -> Field[T]: + """Create and return a new field in the group.""" field = Field(self.name, name, default, passed_type) self._fields.append(field) for callback in self._callbacks: @@ -50,13 +80,16 @@ def field( return field def reset_default(self): + """Reset values of all fields stored in this group.""" for field in self._fields: field.reset_default() def register_callback(self, callback: Callable[[], None]): + """Register a callback on every past and future field in group.""" self._callbacks.append(callback) for field in self._fields: field.register_callback(callback) def __iter__(self): + """Iterate over all fields in the group.""" return iter(self._fields) diff --git a/shortcut_composer/config_system/field_base.py b/shortcut_composer/config_system/field_base.py index 74d7e69c..436037cf 100644 --- a/shortcut_composer/config_system/field_base.py +++ b/shortcut_composer/config_system/field_base.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import TypeVar, Generic, Optional, Callable, final, List +from typing import TypeVar, Generic, Callable, List from abc import ABC, abstractmethod from enum import Enum @@ -15,6 +15,7 @@ class FieldBase(ABC, Field, Generic[T]): + """Implementation base of List, and NonList field.""" def __new__(cls, *args, **kwargs) -> 'FieldBase[T]': obj = object.__new__(cls) obj.__init__(*args, **kwargs) @@ -25,41 +26,18 @@ def __init__( config_group: str, name: str, default: T, - type: Optional[type] = None ): self.config_group = config_group self.name = name self.default = default - self._type = self._get_type(type) - self._parser = self._get_parser() self._on_change_callbacks: List[Callable[[], None]] = [] - @abstractmethod - def _get_type(self, passed_type: Optional[type]) -> type: ... - - @abstractmethod - def _to_string(self, value: T) -> str: ... - - @abstractmethod - def read(self) -> T: ... - - @final def register_callback(self, callback: Callable[[], None]): + """Store callback in internal list.""" self._on_change_callbacks.append(callback) - def _get_parser(self) -> Parser[T]: - if issubclass(self._type, Enum): - return EnumParser(self._type) # type: ignore - - return { - int: BasicParser(int), - float: BasicParser(float), - str: BasicParser(str), - bool: BoolParser() - }[self._type] # type: ignore - - @final def write(self, value: T): + """Write value to file and run callbacks if it was not redundant.""" if self._is_write_redundant(value): return @@ -70,21 +48,42 @@ def write(self, value: T): for callback in self._on_change_callbacks: callback() - @final - def _is_write_redundant(self, value: T): + @abstractmethod + def read(self) -> T: + """Return value from .kritarc parsed to field type.""" + ... + + @abstractmethod + def _to_string(self, value: T) -> str: + """Convert a value of field type to string.""" + ... + + def _is_write_redundant(self, value: T) -> bool: + """ + Return if writing a value is not necessary. + + That is when: + - the value is the same as the one stored in file + - value is a default one and it is not present in file + """ if self.read() == value: return True - current_value = self._read_raw() - return current_value is None and value == self.default + raw = Krita.read_setting(self.config_group, self.name) + return raw is None and value == self.default - @final - def _read_raw(self) -> Optional[str]: - red_value = Krita.read_setting( - group=self.config_group, - name=self.name, - default="Not stored") - return None if red_value == "Not stored" else red_value - - @final def reset_default(self) -> None: + """Write a default value to .kritarc file.""" self.write(self.default) + + @staticmethod + def _get_parser(parser_type: type) -> Parser[T]: + """Return field parser.""" + if issubclass(parser_type, Enum): + return EnumParser(parser_type) # type: ignore + + return { + int: BasicParser(int), + float: BasicParser(float), + str: BasicParser(str), + bool: BoolParser() + }[parser_type] # type: ignore diff --git a/shortcut_composer/config_system/field_implementations.py b/shortcut_composer/config_system/field_implementations.py index 00993dad..af70f068 100644 --- a/shortcut_composer/config_system/field_implementations.py +++ b/shortcut_composer/config_system/field_implementations.py @@ -5,53 +5,77 @@ TypeVar, Generic, Optional, - Type, List) -from enum import Enum +from .api_krita import Krita from .parsers import Parser from .field_base import FieldBase T = TypeVar('T') -E = TypeVar('E', bound=Enum) -ListT = TypeVar('ListT', bound=List[Enum]) class NonListField(FieldBase, Generic[T]): - - _parser: Parser[T] - - def _get_type(self, passed_type: Optional[type]) -> Type[T]: - return type(self.default) + """Config field containing a basic, non-list value.""" + def __init__( + self, + config_group: str, + name: str, + default: T, + parser_type: Optional[type] = None, + ) -> None: + super().__init__(config_group, name, default) + self._parser: Parser[T] = self._get_parser(type(self.default)) def read(self) -> T: - raw = self._read_raw() + """Return value from .kritarc parsed to field type.""" + raw = Krita.read_setting(self.config_group, self.name) if raw is None: return self.default return self._parser.parse_to(raw) def _to_string(self, value: T) -> str: + """Parse the field value to string using parser.""" return self._parser.parse_from(value) -class ListField(FieldBase, Generic[E]): - - _parser: Parser[E] +class ListField(FieldBase, Generic[T]): + """Config field containing a list value.""" + def __init__( + self, + config_group: str, + name: str, + default: List[T], + parser_type: Optional[type] = None, + ) -> None: + super().__init__(config_group, name, default) + self._parser: Parser[T] = self._get_parser(self._get_type(parser_type)) def _get_type(self, passed_type: Optional[type]) -> type: + """ + Determine parser type based on default value or passed type. + + - For non empty list, parser depends on first list element. + - For empty list, parsed type must be used directly + """ if not self.default: if passed_type is None: raise ValueError("Type not given for a list") return passed_type return type(self.default[0]) - def read(self) -> List[E]: - raw = self._read_raw() + def read(self) -> List[T]: + """ + Return value from .kritarc parsed to field type. + + Each list element requires parsing. + """ + raw = Krita.read_setting(self.config_group, self.name) if raw is None: return self.default values_list = raw.split("\t") return [self._parser.parse_to(value) for value in values_list] - def _to_string(self, value: List[E]) -> str: + def _to_string(self, value: List[T]) -> str: + """Convert list of values to string by parsing each element alone.""" return "\t".join([self._parser.parse_from(item) for item in value]) diff --git a/shortcut_composer/config_system/parsers.py b/shortcut_composer/config_system/parsers.py index 7a9e5356..ae5dd5c7 100644 --- a/shortcut_composer/config_system/parsers.py +++ b/shortcut_composer/config_system/parsers.py @@ -10,40 +10,55 @@ class Parser(Generic[T], Protocol): + """Parses from string to specific type and vice-versa.""" + def parse_to(self, value: str) -> T: + """Parse from string to specific type.""" ... def parse_from(self, value: T) -> str: + """Parse from specific type to string.""" ... class BasicParser(Parser[Basic]): + """Parses from string to basic type and vice-versa.""" + def __init__(self, type: Type[Basic]) -> None: self.type = type def parse_to(self, value: str) -> Basic: + """Parse from string to a string or number to .""" return self.type(value) def parse_from(self, value: Basic) -> str: + """Parse from string to string or number.""" return str(value) class BoolParser(Parser[bool]): + """Parses from string to bool and vice-versa.""" + def parse_to(self, value: str) -> bool: + """Parses from string to bool.""" if value not in ("true", "false"): raise ValueError(f"Cant parse {value} to bool") return value == "true" def parse_from(self, value: bool) -> str: + """Parses from bool to string.""" return str(value).lower() class EnumParser(Parser[EnumT]): + """Parses from string to enum and vice-versa.""" def __init__(self, type: Type[EnumT]) -> None: self.type = type def parse_to(self, value: str) -> EnumT: + """Parse from string to enum.""" return self.type[value] def parse_from(self, value: EnumT) -> str: + """Parse from enum to string.""" return str(value.name) From af22d760ac3d5e15a7d4bdc2f33cc7d423f3e925 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 084/125] Add unscaled mode to label widgets --- .../templates/pie_menu_utils/label_widget.py | 11 ++++++++- .../label_widget_utils/create_label_widget.py | 5 ++-- .../label_widget_utils/icon_label_widget.py | 7 ++---- .../label_widget_utils/image_label_widget.py | 21 +++++++++------- .../label_widget_utils/text_label_widget.py | 16 +++++++++---- .../templates/pie_menu_utils/pie_style.py | 24 +++++++++++++------ .../settings_gui/enum_pie_settings.py | 14 +++-------- .../settings_gui/scroll_area.py | 19 +++++++++------ 8 files changed, 71 insertions(+), 46 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget.py index b20e0eee..77ed823c 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget.py @@ -18,14 +18,17 @@ def __init__( label: Label, style: PieStyle, parent: QWidget, + is_unscaled: bool = False, ) -> None: super().__init__(parent) self.setGeometry(0, 0, style.icon_radius*2, style.icon_radius*2) self.label = label + self._style = style + self._is_unscaled = is_unscaled + self._draggable = True self.draggable = self._draggable - self._style = style self._enabled = True self._hovered = False @@ -81,3 +84,9 @@ def _border_color(self): if self._hovered and self.draggable: return self._style.active_color return self._style.border_color + + @property + def icon_radius(self): + if self._is_unscaled: + return self._style.unscaled_icon_radius + return self._style.icon_radius diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/create_label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/create_label_widget.py index 57b42140..a1004a6c 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/create_label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/create_label_widget.py @@ -21,7 +21,8 @@ def create_label_widget( label: Label, style: PieStyle, - parent: QWidget + parent: QWidget, + is_unscaled: bool = False, ) -> 'LabelWidget': """Return LabelWidget which can display this label.""" if label.display_value is None: @@ -33,4 +34,4 @@ def create_label_widget( QIcon: IconLabelWidget, }[type(label.display_value)] - return painter_type(label, style, parent) + return painter_type(label, style, parent, is_unscaled) diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/icon_label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/icon_label_widget.py index d99aa262..a44a88cd 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/icon_label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/icon_label_widget.py @@ -1,10 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from PyQt5.QtGui import ( - QPixmap, - QIcon, -) +from PyQt5.QtGui import QPixmap, QIcon from api_krita.pyqt import PixmapTransform from .image_label_widget import ImageLabelWidget @@ -20,7 +17,7 @@ def _prepare_image(self) -> QPixmap: if not isinstance(to_display, QIcon): raise TypeError("Label supposed to be QIcon.") - size = round(self._style.icon_radius*1.1) + size = round(self.icon_radius*1.1) return PixmapTransform.scale_pixmap( pixmap=to_display.pixmap(size, size), size_px=size diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/image_label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/image_label_widget.py index 7a25b38f..b6aeeda2 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/image_label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/image_label_widget.py @@ -16,8 +16,14 @@ class ImageLabelWidget(LabelWidget): """Displays a `label` which holds an image.""" - def __init__(self, label: Label, style: PieStyle, parent: QWidget) -> None: - super().__init__(label, style, parent) + def __init__( + self, + label: Label, + style: PieStyle, + parent: QWidget, + is_unscaled: bool = False, + ) -> None: + super().__init__(label, style, parent, is_unscaled) self.ready_image = self._prepare_image() def paintEvent(self, event: QPaintEvent) -> None: @@ -29,13 +35,13 @@ def paintEvent(self, event: QPaintEvent) -> None: with Painter(self, event) as painter: painter.paint_wheel( center=self.center, - outer_radius=self._style.icon_radius, - color=self._style.icon_color - ) + outer_radius=self.icon_radius, + color=self._style.icon_color) + painter.paint_wheel( center=self.center, outer_radius=( - self._style.icon_radius-self._style.border_thickness//2), + self.icon_radius-self._style.border_thickness//2), color=self._border_color, thickness=self._style.border_thickness) painter.paint_pixmap(self.center, self.ready_image) @@ -50,5 +56,4 @@ def _prepare_image(self) -> QPixmap: rounded_image = PixmapTransform.make_pixmap_round(to_display) return PixmapTransform.scale_pixmap( pixmap=rounded_image, - size_px=round(self._style.icon_radius*1.8) - ) + size_px=round(self.icon_radius*1.8)) diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/text_label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/text_label_widget.py index 5d1ac4ab..613eac69 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/text_label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/text_label_widget.py @@ -19,8 +19,14 @@ class TextLabelWidget(LabelWidget): """Displays a `label` which holds text.""" - def __init__(self, label: Label, style: PieStyle, parent: QWidget) -> None: - super().__init__(label, style, parent) + def __init__( + self, + label: Label, + style: PieStyle, + parent: QWidget, + is_unscaled: bool = False, + ) -> None: + super().__init__(label, style, parent, is_unscaled) self._pyqt_label = self._create_pyqt_label() def paintEvent(self, event: QPaintEvent) -> None: @@ -32,11 +38,11 @@ def paintEvent(self, event: QPaintEvent) -> None: with Painter(self, event) as painter: painter.paint_wheel( center=self.center, - outer_radius=self._style.icon_radius, + outer_radius=self.icon_radius, color=self._style.icon_color) painter.paint_wheel( center=self.center, - outer_radius=self._style.icon_radius, + outer_radius=self.icon_radius, color=self._border_color, thickness=self._style.border_thickness) @@ -47,7 +53,7 @@ def _create_pyqt_label(self) -> QLabel: if not isinstance(to_display, Text): raise TypeError("Label supposed to be text.") - heigth = round(self._style.icon_radius*0.8) + heigth = round(self.icon_radius*0.8) label = QLabel(self) label.setText(to_display.value) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index 982159a7..f08c6f82 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -39,37 +39,47 @@ def __init__( self.active_color = active_color @property - def pie_radius_scale(self): + def _pie_radius_scale(self): return self._pie_config.PIE_RADIUS_SCALE.read() @property - def icon_radius_scale(self): + def _icon_radius_scale(self): return self._pie_config.ICON_RADIUS_SCALE.read() @property def pie_radius(self) -> int: return round( 165 * self._base_size - * self.pie_radius_scale + * self._pie_radius_scale * Config.PIE_GLOBAL_SCALE.read()) @property def base_icon_radius(self) -> int: return round( 50 * self._base_size - * self.icon_radius_scale + * self._icon_radius_scale * Config.PIE_ICON_GLOBAL_SCALE.read()) @property - def max_icon_radius(self) -> int: + def _unscaled_base_icon_radius(self) -> int: + return round( + 50 * self._base_size + * Config.PIE_ICON_GLOBAL_SCALE.read()) + + @property + def _max_icon_radius(self) -> int: if not self._items: return 1 return round(self.pie_radius * math.pi / len(self._items)) + @property + def unscaled_icon_radius(self) -> int: + return min(self._unscaled_base_icon_radius, self._max_icon_radius) + @property def icon_radius(self) -> int: """Icons radius depend on settings, but they have to fit in the pie.""" - return min(self.base_icon_radius, self.max_icon_radius) + return min(self.base_icon_radius, self._max_icon_radius) @property def deadzone_radius(self) -> float: @@ -90,7 +100,7 @@ def border_thickness(self): @property def area_thickness(self): - return round(self.pie_radius/self.pie_radius_scale*0.4) + return round(self.pie_radius/self._pie_radius_scale*0.4) @property def inner_edge_radius(self): diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index bd382897..99d5fcd9 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -1,5 +1,4 @@ -from typing import List, Callable -from copy import copy +from typing import List from PyQt5.QtWidgets import QVBoxLayout, QTabWidget @@ -30,15 +29,7 @@ def __init__( tab_holder = QTabWidget() - # HACK! label widgets need to have "unscaled mode" - class Mock: - def read(self): return 1.0 - def register_callback(self, callback: Callable): return - - new_style = copy(self._style) - new_style._icon_radius_scale = Mock() # type: ignore - - self._action_values = ScrollArea(values, new_style, 3) + self._action_values = ScrollArea(values, self._style, 3) self._action_values.setMinimumHeight(round(style.base_icon_radius*6.2)) tab_holder.addTab(self._action_values, "Action values") @@ -55,6 +46,7 @@ def register_callback(self, callback: Callable): return self.setLayout(layout) self._pie_config.ORDER.register_callback(self.refresh) + self.refresh() def refresh(self): for widget in self._action_values._children_list: diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py index fdde465e..4b601bd1 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py @@ -38,12 +38,16 @@ def __init__( def _create_children(self) -> List[LabelWidget]: """Create LabelWidgets that represent the labels.""" children: List[LabelWidget] = [] - diameter = self._style.icon_radius*2 for label in self.labels: - children.append(create_label_widget(label, self._style, self)) - children[-1].setFixedSize(diameter, diameter) - children[-1].draggable = True + child = create_label_widget( + label=label, + style=self._style, + parent=self, + is_unscaled=True) + child.setFixedSize(child.icon_radius*2, child.icon_radius*2) + child.draggable = True + children.append(child) self._scroll_area_layout.append(children[-1]) return children @@ -78,14 +82,15 @@ def append(self, widget: QWidget): self._refresh() def pop(self, index: int): - self.widgets.pop(index) + widget = self.widgets.pop(index) + self.removeWidget(widget) self._refresh() + return widget def remove(self, widget: QWidget): for index, held_widget in enumerate(self.widgets): if held_widget == widget: - self.pop(index) - return self._refresh() + return self.pop(index) def insert(self, index: int, widget: QWidget): self.widgets.insert(index, widget) From 0276d0742232215fc70259779a29ed516137b4e7 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 085/125] Fix invalid method name --- shortcut_composer/composer_utils/settings_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shortcut_composer/composer_utils/settings_dialog.py b/shortcut_composer/composer_utils/settings_dialog.py index 40c49032..22c096cc 100644 --- a/shortcut_composer/composer_utils/settings_dialog.py +++ b/shortcut_composer/composer_utils/settings_dialog.py @@ -71,7 +71,7 @@ def ok(self) -> None: def reset(self) -> None: """Reset all config values to defaults in krita and elements.""" - Config.reset_defaults() + Config.reset_default() self.refresh() Krita.trigger_action("Reload Shortcut Composer") From 9d54ce8626ed8bf59919814a406c1862eff6ae22 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 086/125] Store background in local config --- .../templates/pie_menu_utils/dispatchers.py | 9 +++++++-- .../templates/pie_menu_utils/pie_config.py | 15 ++++++++++++++- .../templates/pie_menu_utils/pie_style.py | 14 ++++++-------- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/dispatchers.py b/shortcut_composer/templates/pie_menu_utils/dispatchers.py index fd7245dc..a18ab466 100644 --- a/shortcut_composer/templates/pie_menu_utils/dispatchers.py +++ b/shortcut_composer/templates/pie_menu_utils/dispatchers.py @@ -1,5 +1,7 @@ -from typing import List, TypeVar +from typing import List, TypeVar, Optional + +from PyQt5.QtGui import QColor from data_components import Tag from .pie_config import PieConfig, PresetPieConfig, EnumPieConfig @@ -15,9 +17,12 @@ def create_local_config( values: List[T], pie_radius_scale: float, icon_radius_scale: float, + background_color: Optional[QColor], + active_color: QColor, ) -> PieConfig[T]: config_name = f"ShortcutComposer: {name}" - args = [config_name, values, pie_radius_scale, icon_radius_scale] + args = [config_name, values, pie_radius_scale, icon_radius_scale, + background_color, active_color] if isinstance(values, Tag): return PresetPieConfig(*args) return EnumPieConfig(*args) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 6624a409..67a81c58 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -1,7 +1,8 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import List, Generic, TypeVar +from typing import List, Generic, TypeVar, Optional +from PyQt5.QtGui import QColor from config_system import Field, FieldGroup from data_components import Tag @@ -14,6 +15,8 @@ class PieConfig(FieldGroup, Generic[T]): ORDER: Field[List[T]] PIE_RADIUS_SCALE: Field[float] ICON_RADIUS_SCALE: Field[float] + background_color: Optional[QColor] + active_color: QColor def values(self) -> List[T]: ... @@ -24,6 +27,8 @@ def __init__( values: Tag, pie_radius_scale: float, icon_radius_scale: float, + background_color: Optional[QColor], + active_color: QColor, ) -> None: super().__init__(name) self.allow_remove = False @@ -33,6 +38,9 @@ def __init__( self.TAG_NAME = self.field("Tag", values.tag_name) self.ORDER = self.field("Values", [], passed_type=str) + self.background_color = background_color + self.active_color = active_color + def values(self) -> List[str]: saved_order = self.ORDER.read() tag_values = Tag(self.TAG_NAME.read()) @@ -49,6 +57,8 @@ def __init__( values: List[T], pie_radius_scale: float, icon_radius_scale: float, + background_color: Optional[QColor], + active_color: QColor, ) -> None: super().__init__(name) self.allow_remove = True @@ -57,5 +67,8 @@ def __init__( self.ICON_RADIUS_SCALE = self.field("Icon scale", icon_radius_scale) self.ORDER = self.field("Values", values) + self.background_color = background_color + self.active_color = active_color + def values(self) -> List[T]: return self.ORDER.read() diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index f08c6f82..93d55dae 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -3,7 +3,6 @@ import math import platform -from typing import Optional from copy import copy from PyQt5.QtGui import QColor @@ -27,16 +26,11 @@ class PieStyle: def __init__( self, pie_config: PieConfig, - background_color: Optional[QColor], - active_color: QColor, items: list, ) -> None: self._items = items self._base_size = Krita.screen_size/2560 - self._pie_config = pie_config - self._background_color = background_color - self.active_color = active_color @property def _pie_radius_scale(self): @@ -120,11 +114,15 @@ def accept_button_radius(self) -> int: radius = self.deadzone_radius return int(radius) if radius != float("inf") else default_radius + @property + def active_color(self): + return self._pie_config.active_color + @property def background_color(self) -> QColor: """Default background color depends on the app theme lightness.""" - if self._background_color is not None: - return self._background_color + if self._pie_config.background_color is not None: + return self._pie_config.background_color if Krita.is_light_theme_active: return QColor(210, 210, 210, 190) return QColor(75, 75, 75, 190) From 0bd70dced819157097d5f50547571d1b607412c9 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 087/125] Small doc, codestyle and typing fixes --- shortcut_composer/templates/cursor_tracker.py | 5 +- .../mouse_tracker_utils/axis_trackers.py | 10 +--- .../templates/multiple_assignment.py | 5 +- shortcut_composer/templates/pie_menu.py | 47 +++++++------------ .../templates/raw_instructions.py | 2 +- shortcut_composer/templates/temporary_key.py | 6 +-- 6 files changed, 25 insertions(+), 50 deletions(-) diff --git a/shortcut_composer/templates/cursor_tracker.py b/shortcut_composer/templates/cursor_tracker.py index c10ab6fd..68640376 100644 --- a/shortcut_composer/templates/cursor_tracker.py +++ b/shortcut_composer/templates/cursor_tracker.py @@ -5,7 +5,6 @@ from core_components import Instruction from data_components import Slider -from input_adapter import ComplexActionInterface from .mouse_tracker_utils import ( SingleAxisTracker, DoubleAxisTracker, @@ -16,7 +15,7 @@ U = TypeVar("U") -class CursorTracker(RawInstructions, Generic[T, U]): +class CursorTracker(Generic[T, U]): """ Switch values with horizontal or vertical mouse movement. @@ -67,7 +66,7 @@ def __new__( horizontal_slider: Optional[Slider[T]] = None, vertical_slider: Optional[Slider[U]] = None, instructions: List[Instruction] = [], - ) -> ComplexActionInterface: + ) -> RawInstructions: """ Pick and create correct ActionPlugin based on provided sliders. diff --git a/shortcut_composer/templates/mouse_tracker_utils/axis_trackers.py b/shortcut_composer/templates/mouse_tracker_utils/axis_trackers.py index 5a064a6d..f529fde4 100644 --- a/shortcut_composer/templates/mouse_tracker_utils/axis_trackers.py +++ b/shortcut_composer/templates/mouse_tracker_utils/axis_trackers.py @@ -26,10 +26,7 @@ def __init__( instructions: List[Instruction] = [], short_vs_long_press_time: Optional[float] = None ) -> None: - super().__init__( - name=name, - short_vs_long_press_time=short_vs_long_press_time, - instructions=instructions) + super().__init__(name, instructions, short_vs_long_press_time) self._handler = slider_handler @@ -61,10 +58,7 @@ def __init__( instructions: List[Instruction] = [], short_vs_long_press_time: Optional[float] = None ) -> None: - super().__init__( - name=name, - short_vs_long_press_time=short_vs_long_press_time, - instructions=instructions) + super().__init__(name, instructions, short_vs_long_press_time) self._horizontal_handler = horizontal_handler self._vertical_handler = vertical_handler diff --git a/shortcut_composer/templates/multiple_assignment.py b/shortcut_composer/templates/multiple_assignment.py index 4e6f33e4..4c9b136f 100644 --- a/shortcut_composer/templates/multiple_assignment.py +++ b/shortcut_composer/templates/multiple_assignment.py @@ -67,10 +67,7 @@ def __init__( instructions: List[Instruction] = [], short_vs_long_press_time: Optional[float] = None ) -> None: - super().__init__( - name=name, - short_vs_long_press_time=short_vs_long_press_time, - instructions=instructions) + super().__init__(name, instructions, short_vs_long_press_time) self._controller = controller self._values_to_cycle = values diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 95de8318..a2905319 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -29,8 +29,7 @@ class PieMenu(RawInstructions, Generic[T]): - Widget is displayed under the cursor between key press and release - Moving mouse in a direction of a value activates in on key release - When the mouse was not moved past deadzone, value is not changed - - Dragging values activates edit mode in which pie does not hide - - Applying the changes in edit mode, saves its values to settings + - Edit button activates mode in pie does not hide and can be changed ### Arguments: @@ -52,7 +51,6 @@ class PieMenu(RawInstructions, Generic[T]): Action is meant to change opacity of current layer to one of predefined values using the pie menu widget. - ```python templates.PieMenu( name="Pick active layer opacity", @@ -88,43 +86,34 @@ def __init__( active_color: QColor = QColor(100, 150, 230, 255), short_vs_long_press_time: Optional[float] = None ) -> None: - super().__init__( - name=name, - short_vs_long_press_time=short_vs_long_press_time, - instructions=instructions) + super().__init__(name, instructions, short_vs_long_press_time) self._controller = controller - self._local_config = create_local_config( - name, - values, - pie_radius_scale, - icon_radius_scale) - - # TODO: colors should be in self._local_config - self._background_color = background_color - self._active_color = active_color + self._config = create_local_config( + name=name, + values=values, + pie_radius_scale=pie_radius_scale, + icon_radius_scale=icon_radius_scale, + background_color=background_color, + active_color=active_color) self._labels: List[Label] = [] - self._reset_labels(self._labels, self._local_config.values()) + self._reset_labels(self._labels, self._config.values()) self._all_labels: List[Label] = [] self._reset_labels(self._all_labels, self._get_all_values(values)) self._edit_mode = EditMode(self) - style_args = { - "pie_config": self._local_config, - "background_color": self._background_color, - "active_color": self._active_color} - self._style = PieStyle(items=self._labels, **style_args) + self._style = PieStyle(items=self._labels, pie_config=self._config) self.pie_settings = create_pie_settings_window( - style=PieStyle(items=[None], **style_args), + style=PieStyle(items=[None], pie_config=self._config), values=self._all_labels, used_values=self._labels, - pie_config=self._local_config) + pie_config=self._config) self.pie_widget = PieWidget( style=self._style, labels=self._labels, - config=self._local_config) + config=self._config) self.pie_manager = PieManager( pie_widget=self.pie_widget, pie_settings=self.pie_settings) @@ -135,7 +124,7 @@ def __init__( radius_callback=lambda: self._style.setting_button_radius, icon_scale=1.1, style=self._style, - config=self._local_config) + config=self._config) self.settings_button.clicked.connect(lambda: self._edit_mode.set(True)) self.accept_button = RoundButton( icon=Krita.get_icon("dialog-ok"), @@ -143,14 +132,14 @@ def __init__( radius_callback=lambda: self._style.accept_button_radius, icon_scale=1.5, style=self._style, - config=self._local_config) + config=self._config) self.accept_button.clicked.connect(lambda: self._edit_mode.set(False)) self.accept_button.hide() def _reset(self): - values = self._local_config.values() + values = self._config.values() self._reset_labels(self._labels, values) - self._local_config.ORDER.write(values) + self._config.ORDER.write(values) self.accept_button.move_center(self.pie_widget.center) self.settings_button.move(QPoint( diff --git a/shortcut_composer/templates/raw_instructions.py b/shortcut_composer/templates/raw_instructions.py index 55d0cff3..36a32bb0 100644 --- a/shortcut_composer/templates/raw_instructions.py +++ b/shortcut_composer/templates/raw_instructions.py @@ -41,7 +41,7 @@ class RawInstructions(ComplexActionInterface): """ def __init__( - self, *, + self, name: str, instructions: List[Instruction] = [], short_vs_long_press_time: Optional[float] = None diff --git a/shortcut_composer/templates/temporary_key.py b/shortcut_composer/templates/temporary_key.py index a67249e9..97271b8d 100644 --- a/shortcut_composer/templates/temporary_key.py +++ b/shortcut_composer/templates/temporary_key.py @@ -65,11 +65,7 @@ def __init__( instructions: List[Instruction] = [], short_vs_long_press_time: Optional[float] = None ) -> None: - super().__init__( - name=name, - short_vs_long_press_time=short_vs_long_press_time, - instructions=instructions) - + super().__init__(name, instructions, short_vs_long_press_time) self._controller = controller self._high_value = high_value self._low_value = self._read_default_value(low_value) From 3d75af802927af3f36fb8533ab807a8d10c1bf9a Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 088/125] All widgets use single style instance --- shortcut_composer/templates/pie_menu.py | 4 +--- shortcut_composer/templates/pie_menu_utils/pie_style.py | 6 +++--- .../pie_menu_utils/settings_gui/enum_pie_settings.py | 3 ++- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index a2905319..b6692687 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -100,13 +100,11 @@ def __init__( self._reset_labels(self._labels, self._config.values()) self._all_labels: List[Label] = [] self._reset_labels(self._all_labels, self._get_all_values(values)) - self._edit_mode = EditMode(self) - self._style = PieStyle(items=self._labels, pie_config=self._config) self.pie_settings = create_pie_settings_window( - style=PieStyle(items=[None], pie_config=self._config), + style=self._style, values=self._all_labels, used_values=self._labels, pie_config=self._config) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index 93d55dae..75c83d0e 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -48,7 +48,7 @@ def pie_radius(self) -> int: * Config.PIE_GLOBAL_SCALE.read()) @property - def base_icon_radius(self) -> int: + def _base_icon_radius(self) -> int: return round( 50 * self._base_size * self._icon_radius_scale @@ -73,7 +73,7 @@ def unscaled_icon_radius(self) -> int: @property def icon_radius(self) -> int: """Icons radius depend on settings, but they have to fit in the pie.""" - return min(self.base_icon_radius, self._max_icon_radius) + return min(self._base_icon_radius, self._max_icon_radius) @property def deadzone_radius(self) -> float: @@ -86,7 +86,7 @@ def deadzone_radius(self) -> float: @property def widget_radius(self): - return self.pie_radius + self.base_icon_radius + return self.pie_radius + self._base_icon_radius @property def border_thickness(self): diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index 99d5fcd9..28d7838f 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -30,7 +30,8 @@ def __init__( tab_holder = QTabWidget() self._action_values = ScrollArea(values, self._style, 3) - self._action_values.setMinimumHeight(round(style.base_icon_radius*6.2)) + self._action_values.setMinimumHeight( + round(style.unscaled_icon_radius*6.2)) tab_holder.addTab(self._action_values, "Action values") self._local_settings = ConfigFormWidget([ From e4e794a74db72d7ccf54607b8734dbc7512e1067 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 089/125] Improved typing in config widgets --- shortcut_composer/config_system/ui/widgets.py | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/shortcut_composer/config_system/ui/widgets.py b/shortcut_composer/config_system/ui/widgets.py index 9e67dd6a..184bd56d 100644 --- a/shortcut_composer/config_system/ui/widgets.py +++ b/shortcut_composer/config_system/ui/widgets.py @@ -1,36 +1,43 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import Any, List, Union, Final, Optional +from typing import Any, List, Final, Optional, TypeVar, Generic, Protocol from PyQt5.QtWidgets import QDoubleSpinBox, QComboBox, QSpinBox, QWidget from ..field import Field from .config_based_widget import ConfigBasedWidget +F = TypeVar("F", bound=float) -class ConfigSpinBox(ConfigBasedWidget): + +class SpinBox(Protocol, Generic[F]): + def value(self) -> F: ... + def setValue(self, val: F) -> None: ... + + +class ConfigSpinBox(ConfigBasedWidget[F]): def __init__( self, - config_field: Union[Field[int], Field[float]], + config_field: Field[F], parent: Optional[QWidget] = None, pretty_name: Optional[str] = None, - step: float = 1, - max_value: float = 100, + step: F = 1, + max_value: F = 100, ) -> None: super().__init__(config_field, parent, pretty_name) self._step = step self._max_value = max_value self._spin_box = self._init_spin_box() - self.widget: Final[Union[QSpinBox, QDoubleSpinBox]] = self._spin_box + self.widget: Final[SpinBox[F]] = self._spin_box self.reset() - def read(self): + def read(self) -> F: return self._spin_box.value() - def set(self, value): + def set(self, value: F): self._spin_box.setValue(value) - def _init_spin_box(self): + def _init_spin_box(self) -> SpinBox: spin_box = (QSpinBox() if type(self.config_field.default) is int else QDoubleSpinBox()) spin_box.setMinimumWidth(90) @@ -60,13 +67,13 @@ def _init_combo_box(self) -> QComboBox: combo_box.setObjectName(self.config_field.name) return combo_box - def reset(self): + def reset(self) -> None: self._combo_box.clear() self._combo_box.addItems(self._allowed_values) self.set(self.config_field.read()) - def read(self): + def read(self) -> str: return self._combo_box.currentText() - def set(self, value): + def set(self, value: str): return self._combo_box.setCurrentText(value) From 4c88abe46a39d60bf0d6c33db09a572abeecdca3 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 090/125] Documentation of config based widgets --- shortcut_composer/actions.py | 5 ++- .../config_system/ui/config_based_widget.py | 34 +++++++++++---- .../config_system/ui/config_form_widget.py | 26 +++++++++--- shortcut_composer/config_system/ui/widgets.py | 41 ++++++++++++++----- .../controllers/core_controllers.py | 1 + 5 files changed, 82 insertions(+), 25 deletions(-) diff --git a/shortcut_composer/actions.py b/shortcut_composer/actions.py index ac3a518f..03205a69 100644 --- a/shortcut_composer/actions.py +++ b/shortcut_composer/actions.py @@ -10,6 +10,7 @@ """ import templates +from typing import List from PyQt5.QtGui import QColor @@ -25,7 +26,7 @@ infinity = float("inf") -def create_actions(): return [ +def create_actions() -> List[templates.RawInstructions]: return [ # Switch between FREEHAND BRUSH and the MOVE tool templates.TemporaryKey( name="Temporary move tool", @@ -146,7 +147,7 @@ def create_actions(): return [ templates.CursorTracker( name="Scroll brush size or opacity", horizontal_slider=Slider( - controller=controllers.BrushSizeController(), + controller=controllers.FlowController(), values=[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 20, 25, 30, 35, 40, 50, 60, 70, 80, diff --git a/shortcut_composer/config_system/ui/config_based_widget.py b/shortcut_composer/config_system/ui/config_based_widget.py index 4f443042..39db48d5 100644 --- a/shortcut_composer/config_system/ui/config_based_widget.py +++ b/shortcut_composer/config_system/ui/config_based_widget.py @@ -1,14 +1,27 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from abc import abstractmethod -from typing import Final, Optional +from abc import ABC, abstractmethod +from typing import Final, Optional, TypeVar, Generic from PyQt5.QtWidgets import QWidget from ..field import Field +T = TypeVar("T") + + +class ConfigBasedWidget(ABC, Generic[T]): + """ + Wrapper of widget linked to a configuration field. + + The widget is created based on the passed field and available as a + public attribute. + + Unifies QWidgets interface to `read()` and `set()` methods. + Additionally, allows to use the configuration field to reset the + widget to the default value and save current value to config. + """ -class ConfigBasedWidget: def __init__( self, config_field: Field, @@ -21,18 +34,25 @@ def __init__( self.widget: QWidget @abstractmethod - def read(self): ... + def read(self) -> T: + """Return the current value of the widget.""" + ... @abstractmethod - def set(self, value): ... + def set(self, value: T): + """Replace the value of the widget with passed one.""" + ... - def reset(self): + def reset(self) -> None: + """Replace the value of the widget with the default one.""" self.set(self.config_field.read()) - def save(self): + def save(self) -> None: + """Save the current value of the widget to .kritarc.""" self.config_field.write(self.read()) def _init_pretty_name(self, pretty_name: Optional[str]) -> str: + """Pick the name of the widget. Config field name if not given.""" if pretty_name is not None: return pretty_name return self.config_field.name diff --git a/shortcut_composer/config_system/ui/config_form_widget.py b/shortcut_composer/config_system/ui/config_form_widget.py index 1c20ed34..e9a02338 100644 --- a/shortcut_composer/config_system/ui/config_form_widget.py +++ b/shortcut_composer/config_system/ui/config_form_widget.py @@ -13,31 +13,45 @@ class ConfigFormWidget(QWidget): - """Dialog zone consisting of spin boxes.""" + """ + Configuration Widget with a form of ConfigBasedWidgets. + + Consists of centered titles and labelled widgets added with + `add_row()` and `add_title`. + + Alternatively, it can be initialized with a list of strings and + ConfigBasedWidgets which create titles and form rows. + + Synchronizes stored ConfigBasedWidgets by allowing to refresh and + save values to config of all stored ones. + """ def __init__(self, elements: List[Union[ConfigBasedWidget, str]]) -> None: super().__init__() self._layout = QFormLayout() self._layout.RowWrapPolicy(QFormLayout.DontWrapRows) self._layout.setFieldGrowthPolicy(QFormLayout.FieldsStayAtSizeHint) - self._layout.setFormAlignment(Qt.AlignHCenter | Qt.AlignTop) self._layout.setLabelAlignment(Qt.AlignRight) + self._layout.setFormAlignment( + Qt.AlignHCenter | Qt.AlignTop) # type: ignore self.setLayout(self._layout) self._widgets: List[ConfigBasedWidget] = [] for element in elements: if isinstance(element, str): - self._add_label(element) + self.add_title(element) elif isinstance(element, ConfigBasedWidget): - self._add_row(element) + self.add_row(element) else: raise TypeError("Unsupported arguments.") - def _add_row(self, element: ConfigBasedWidget) -> None: + def add_row(self, element: ConfigBasedWidget) -> None: + """Add a ConfigBasedWidget along with a label.""" self._widgets.append(element) self._layout.addRow(f"{element.pretty_name}:", element.widget) - def _add_label(self, text: str): + def add_title(self, text: str): + """Add a label with given text.""" label = QLabel(text) label.setAlignment(Qt.AlignCenter) self._layout.addRow(QSplitter(Qt.Horizontal)) diff --git a/shortcut_composer/config_system/ui/widgets.py b/shortcut_composer/config_system/ui/widgets.py index 184bd56d..1d466b88 100644 --- a/shortcut_composer/config_system/ui/widgets.py +++ b/shortcut_composer/config_system/ui/widgets.py @@ -16,6 +16,13 @@ def setValue(self, val: F) -> None: ... class ConfigSpinBox(ConfigBasedWidget[F]): + """ + Wrapper of SpinBox linked to a configutation field. + + Based on QSpinBox or QDoubleSpinBox depending on the config type. + Works only for fields of type: `int` or `float`. + """ + def __init__( self, config_field: Field[F], @@ -32,23 +39,33 @@ def __init__( self.reset() def read(self) -> F: + """Return the current value of the spinbox widget.""" return self._spin_box.value() def set(self, value: F): + """Replace the value of the spinbox widget with passed one.""" self._spin_box.setValue(value) def _init_spin_box(self) -> SpinBox: - spin_box = (QSpinBox() if type(self.config_field.default) is int - else QDoubleSpinBox()) + """Return the spinbox widget of type based on config field type.""" + spin_box: QDoubleSpinBox = \ + {int: QSpinBox, float: QDoubleSpinBox}[self.config_field.default]() + spin_box.setMinimumWidth(90) spin_box.setObjectName(self.config_field.name) spin_box.setMinimum(0) - spin_box.setSingleStep(self._step) # type: ignore - spin_box.setMaximum(self._max_value) # type: ignore + spin_box.setSingleStep(self._step) + spin_box.setMaximum(self._max_value) return spin_box -class ConfigComboBox(ConfigBasedWidget): +class ConfigComboBox(ConfigBasedWidget[str]): + """ + Wrapper of Combobox linked to a configutation field. + + Works only for fields of type: `str`. + """ + def __init__( self, config_field: Field[str], @@ -62,18 +79,22 @@ def __init__( self.widget: Final[QComboBox] = self._combo_box self.reset() - def _init_combo_box(self) -> QComboBox: - combo_box = QComboBox() - combo_box.setObjectName(self.config_field.name) - return combo_box - def reset(self) -> None: + """Update allowed values of the combobox and pick a default one.""" self._combo_box.clear() self._combo_box.addItems(self._allowed_values) self.set(self.config_field.read()) def read(self) -> str: + """Return the current value of the ComboBox.""" return self._combo_box.currentText() def set(self, value: str): + """Replace the value of the ComboBox with passed one.""" return self._combo_box.setCurrentText(value) + + def _init_combo_box(self) -> QComboBox: + """Return the spinbox widget.""" + combo_box = QComboBox() + combo_box.setObjectName(self.config_field.name) + return combo_box diff --git a/shortcut_composer/core_components/controllers/core_controllers.py b/shortcut_composer/core_components/controllers/core_controllers.py index a0089178..feb68afb 100644 --- a/shortcut_composer/core_components/controllers/core_controllers.py +++ b/shortcut_composer/core_components/controllers/core_controllers.py @@ -93,6 +93,7 @@ def set_value(self, value: bool) -> None: def get_pretty_name(self, value: Tool) -> str: return value.pretty_name + @dataclass class UndoController(Controller[float]): """ From e979b5a78d69b009db41affe3a11d01c9360fa39 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 091/125] Bugfix: wrong refactor change --- shortcut_composer/config_system/ui/widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shortcut_composer/config_system/ui/widgets.py b/shortcut_composer/config_system/ui/widgets.py index 1d466b88..b4d4a628 100644 --- a/shortcut_composer/config_system/ui/widgets.py +++ b/shortcut_composer/config_system/ui/widgets.py @@ -48,8 +48,8 @@ def set(self, value: F): def _init_spin_box(self) -> SpinBox: """Return the spinbox widget of type based on config field type.""" - spin_box: QDoubleSpinBox = \ - {int: QSpinBox, float: QDoubleSpinBox}[self.config_field.default]() + spin_box: QDoubleSpinBox = {int: QSpinBox, float: QDoubleSpinBox}[ + type(self.config_field.default)]() spin_box.setMinimumWidth(90) spin_box.setObjectName(self.config_field.name) From 70d7a0d3492054f7e6790e898b7e8caf3b6fecef Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 092/125] Main pie classes documentation and codestyle fixes --- .../controllers/view_controllers.py | 14 +++-- shortcut_composer/templates/pie_menu.py | 53 +++++++++---------- .../templates/pie_menu_utils/label_widget.py | 29 +++++++--- .../templates/pie_menu_utils/pie_config.py | 21 +++++--- .../templates/pie_menu_utils/pie_manager.py | 5 +- .../templates/pie_menu_utils/pie_widget.py | 39 ++++++-------- .../settings_gui/enum_pie_settings.py | 4 +- .../pie_menu_utils/widget_utils/edit_mode.py | 2 +- .../widget_utils/label_holder.py | 3 +- .../widget_utils/round_button.py | 2 +- .../templates/raw_instructions.py | 8 +-- 11 files changed, 102 insertions(+), 78 deletions(-) diff --git a/shortcut_composer/core_components/controllers/view_controllers.py b/shortcut_composer/core_components/controllers/view_controllers.py index 7ba824d2..7223291e 100644 --- a/shortcut_composer/core_components/controllers/view_controllers.py +++ b/shortcut_composer/core_components/controllers/view_controllers.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later +from typing import Optional from PyQt5.QtGui import QPixmap, QImage from api_krita import Krita from api_krita.enums import BlendingMode @@ -34,9 +35,13 @@ def set_value(self, value: str) -> None: """Set a preset of passed name.""" self.view.brush_preset = value - def get_label(self, value: str) -> QPixmap: - image: QImage = Krita.get_presets()[value].image() - return QPixmap.fromImage(image) + def get_label(self, value: str) -> Optional[QPixmap]: + try: + image: QImage = Krita.get_presets()[value].image() + except KeyError: + return None + else: + return QPixmap.fromImage(image) class BrushSizeController(ViewBasedController, Controller[int]): @@ -82,7 +87,7 @@ def set_value(self, value: BlendingMode) -> None: def get_label(self, value: BlendingMode) -> Text: return Text(value.name[:3], Colorizer.blending_mode(value)) - + def get_pretty_name(self, value: float) -> str: return f"{round(value)}px" @@ -111,6 +116,7 @@ def get_label(self, value: int) -> Text: def get_pretty_name(self, value: float) -> str: return f"{value}%" + class FlowController(ViewBasedController, Controller[int]): """ Gives access to `brush flow` in %. diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index b6692687..a7db55c6 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -63,16 +63,6 @@ class PieMenu(RawInstructions, Generic[T]): ) ``` """ - """ - Class is responsible for: - - Handling the key press/release interface - - Reading widget configuration and storing it in PieStyle - passed - to objects that can be displayed - - Creating the PieWidget - and PieManager which displays it - - Starting and stopping the PieManager on key press and release - - Creating Labels - paintable representations of handled values - - Setting a value on key release when the deadzone was reached - """ def __init__( self, *, @@ -118,47 +108,54 @@ def __init__( self.settings_button = RoundButton( icon=Krita.get_icon("properties"), + icon_scale=1.1, parent=self.pie_widget, radius_callback=lambda: self._style.setting_button_radius, - icon_scale=1.1, style=self._style, config=self._config) self.settings_button.clicked.connect(lambda: self._edit_mode.set(True)) self.accept_button = RoundButton( icon=Krita.get_icon("dialog-ok"), + icon_scale=1.5, parent=self.pie_widget, radius_callback=lambda: self._style.accept_button_radius, - icon_scale=1.5, style=self._style, config=self._config) self.accept_button.clicked.connect(lambda: self._edit_mode.set(False)) self.accept_button.hide() - def _reset(self): - values = self._config.values() - self._reset_labels(self._labels, values) - self._config.ORDER.write(values) - + def _move_buttons(self): + """Move accept button to center and setting button to bottom-right.""" self.accept_button.move_center(self.pie_widget.center) self.settings_button.move(QPoint( self.pie_widget.width()-self.settings_button.width(), self.pie_widget.height()-self.settings_button.height())) def on_key_press(self) -> None: - """Show widget under mouse and start manager which repaints it.""" + """Reload labels, start GUI manager and run instructions.""" self._controller.refresh() - self._reset() + + values = self._config.values() + self._reset_labels(self._labels, values) + self._config.ORDER.write(values) + self._move_buttons() + self.pie_manager.start() super().on_key_press() def on_every_key_release(self) -> None: - """Stop the widget. Set selected value if deadzone was reached.""" + """ + Handle the key release event. + + Ignore if in edit mode. Otherwise, stop the manager and set the + selected value if deadzone was reached. + """ super().on_every_key_release() + if self._edit_mode.get(): return self.pie_manager.stop() - self.pie_widget.hide() if label := self.pie_widget.active: self._controller.set_value(label.value) @@ -167,19 +164,19 @@ def _reset_labels( label_list: List[Label[T]], values: List[T] ) -> None: - """Wrap values into paintable label objects with position info.""" + """Replace list values with newly created labels if changed.""" if [label.value for label in label_list] == values: return label_list.clear() for value in values: - try: - label = self._controller.get_label(value) - except KeyError: - continue - label_list.append(Label(value=value, display_value=label)) + label = self._controller.get_label(value) + if label is not None: + label_list.append(Label(value=value, display_value=label)) - def _get_all_values(self, values: List[T]) -> List[T]: + @staticmethod + def _get_all_values(values: List[T]) -> List[T]: + """Return list of available enum values. HACK""" if not values: return [] diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget.py index 77ed823c..c99c32de 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget.py @@ -33,26 +33,35 @@ def __init__( self._hovered = False @property - def draggable(self): + def draggable(self) -> bool: + """Return whether the label accepts dragging.""" return self._draggable @draggable.setter - def draggable(self, value: bool): + def draggable(self, value: bool) -> None: + """Make the widget accept dragging or not.""" self._draggable = value if value: return self.setCursor(Qt.ArrowCursor) self.setCursor(Qt.CrossCursor) - def move_to_label(self) -> None: - """Move the widget by providing a new center point.""" - self.move_center(self.label.center) + @property + def enabled(self): + """Return whether the label interacts with mouse hover and drag.""" + return self._enabled - def set_enabled(self, value: bool): + @enabled.setter + def enabled(self, value: bool) -> None: + """Make the widget interact with mouse or not.""" self._enabled = value if not value: - self._draggable = False + self.draggable = False self.repaint() + def move_to_label(self) -> None: + """Move the widget according to current center of label it holds.""" + self.move_center(self.label.center) + def mousePressEvent(self, e: QMouseEvent) -> None: """Initiate a drag loop for this Widget, so Widgets can be swapped.""" if e.buttons() != Qt.LeftButton or not self._draggable: @@ -68,18 +77,21 @@ def mousePressEvent(self, e: QMouseEvent) -> None: drag.exec_(Qt.MoveAction) def enterEvent(self, e: QEvent) -> None: + """Notice that mouse moved over the widget.""" self._hovered = True self.repaint() return super().enterEvent(e) def leaveEvent(self, e: QEvent) -> None: + """Notice that mouse moved out of the widget.""" self._hovered = False self.repaint() return super().leaveEvent(e) @property def _border_color(self): - if not self._enabled: + """Return border color which differs when enabled or hovered.""" + if not self.enabled: return self._style.active_color_dark if self._hovered and self.draggable: return self._style.active_color @@ -87,6 +99,7 @@ def _border_color(self): @property def icon_radius(self): + """Return icon radius based flag passed on initialization.""" if self._is_unscaled: return self._style.unscaled_icon_radius return self._style.icon_radius diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 67a81c58..9db6e889 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later +from abc import ABC, abstractmethod from typing import List, Generic, TypeVar, Optional from PyQt5.QtGui import QColor from config_system import Field, FieldGroup @@ -9,18 +10,25 @@ T = TypeVar("T") -class PieConfig(FieldGroup, Generic[T]): +class PieConfig(FieldGroup, Generic[T], ABC): + ALLOW_REMOVE: bool + name: str - allow_remove: bool + background_color: Optional[QColor] + active_color: QColor + ORDER: Field[List[T]] PIE_RADIUS_SCALE: Field[float] ICON_RADIUS_SCALE: Field[float] - background_color: Optional[QColor] - active_color: QColor + + @abstractmethod def values(self) -> List[T]: ... class PresetPieConfig(PieConfig): + + ALLOW_REMOVE = False + def __init__( self, name: str, @@ -31,7 +39,6 @@ def __init__( active_color: QColor, ) -> None: super().__init__(name) - self.allow_remove = False self.PIE_RADIUS_SCALE = self.field("Pie scale", pie_radius_scale) self.ICON_RADIUS_SCALE = self.field("Icon scale", icon_radius_scale) @@ -51,6 +58,9 @@ def values(self) -> List[str]: class EnumPieConfig(PieConfig, Generic[T]): + + ALLOW_REMOVE = True + def __init__( self, name: str, @@ -61,7 +71,6 @@ def __init__( active_color: QColor, ) -> None: super().__init__(name) - self.allow_remove = True self.PIE_RADIUS_SCALE = self.field("Pie scale", pie_radius_scale) self.ICON_RADIUS_SCALE = self.field("Icon scale", icon_radius_scale) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index 17e0efc3..564df1fc 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -35,6 +35,7 @@ def start(self) -> None: self._pie_widget.move_center(QCursor().pos()) self._pie_widget.show() + # Qt bug workaround. Settings does not move right when hidden. self._pie_settings.show() self._pie_settings.move_to_pie_side() self._pie_settings.hide() @@ -42,11 +43,13 @@ def start(self) -> None: self._circle = CirclePoints(self._pie_widget.center_global, 0) self._timer.start() - def stop(self) -> None: + def stop(self, hide: bool = True) -> None: """Hide the widget and stop the mouse tracking loop.""" self._timer.stop() for label in self._pie_widget.label_holder: label.activation_progress.reset() + if hide: + self._pie_widget.hide() def _handle_cursor(self) -> None: """Calculate zone of the cursor and mark which child is active.""" diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index c0f24a93..e61067a0 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -33,18 +33,10 @@ class PieWidget(AnimatedWidget, BaseWidget, Generic[T]): - hide() - hides the widget - repaint() - updates widget display after its data was changed - Contains children widgets that are draggable. When one of the - children is dragged, the widget enters the edit mode. That can be - used by whoever controls this widget to handle it differently. - - Stores the values in three forms: - - Labels: contain bare data - - LabelWidgets: widget children displaying a single Label - - LabelHolder: container of all LabelWidgets that operate on angles - - Makes changes to LabelHolder when one of children is dragged. - When the widget is hidden while in the edit mode, changes made to - the LabelHolder are saved in the related configuration. + In LabelHolder it stores children widgets that are draggable when + pie menu is in edit mode. + + Dragging them, makes changes to LabelHolder. """ def __init__( @@ -77,14 +69,17 @@ def __init__( self._last_widget = None self.label_holder = LabelHolder( - self._labels, - self._style, - self.config, - self) + labels=self._labels, + style=self._style, + config=self.config, + owner=self) self._circle_points: CirclePoints + + self.set_draggable(False) self._reset() def _reset(self): + """Set widget geometry according to style and refresh CirclePoints.""" widget_diameter = self._style.widget_radius*2 self.setGeometry(0, 0, widget_diameter, widget_diameter) self._circle_points = CirclePoints( @@ -102,8 +97,10 @@ def paintEvent(self, event: QPaintEvent) -> None: PiePainter(painter, self._labels, self._style, self.is_edit_mode) def dragEnterEvent(self, e: QDragEnterEvent) -> None: - """Start edit mode when one of the draggable children gets dragged.""" - e.accept() + """Allow dragging the widgets while in edit mode.""" + if self.is_edit_mode: + return e.accept() + e.ignore() def dragMoveEvent(self, e: QDragMoveEvent) -> None: """Swap children during drag when mouse is moved to another zone.""" @@ -131,14 +128,12 @@ def dragMoveEvent(self, e: QDragMoveEvent) -> None: self.repaint() def dragLeaveEvent(self, e: QDragLeaveEvent) -> None: + """Remove the label when its widget is dragged out.""" if self._last_widget is not None: self.label_holder.remove(self._last_widget.label) return super().dragLeaveEvent(e) - def show(self): - self.set_draggable(False) - return super().show() - def set_draggable(self, draggable: bool): + """Change draggable state of all children.""" for widget in self.label_holder.widget_holder: widget.draggable = draggable diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index 28d7838f..75105c9c 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -52,10 +52,10 @@ def __init__( def refresh(self): for widget in self._action_values._children_list: if widget.label in self._used_values: - widget.set_enabled(False) + widget.enabled = False widget.draggable = False else: - widget.set_enabled(True) + widget.enabled = True widget.draggable = True def show(self): diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py index 511729ce..2066dfef 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py @@ -29,7 +29,7 @@ def set(self, mode_to_set: bool) -> None: self._edit_mode = mode_to_set def set_edit_mode_true(self): - self._obj.pie_manager.stop() + self._obj.pie_manager.stop(hide=False) self._obj.pie_widget.set_draggable(True) self._obj.pie_widget.is_edit_mode = True self._obj.pie_widget.repaint() diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py index be94fa3c..9270dd23 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py @@ -38,7 +38,7 @@ def append(self, label: Label): def remove(self, label: Label): if (label in self._labels and len(self._labels) > 1 - and self._config.allow_remove): + and self._config.ALLOW_REMOVE): self._labels.remove(label) self._reset() @@ -77,6 +77,7 @@ def _reset(self, notify: bool = True) -> None: child.label.angle = angle child.label.center = point child.move_to_label() + child.draggable = True # FIXME: set False after config change self.widget_holder.add(child) if notify: diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py index cd659536..e61837be 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py @@ -15,8 +15,8 @@ class RoundButton(QPushButton, BaseWidget): def __init__( self, icon: QIcon, - radius_callback: Callable[[], int], icon_scale: float, + radius_callback: Callable[[], int], style: PieStyle, config: PieConfig, parent: Optional[QWidget] = None, diff --git a/shortcut_composer/templates/raw_instructions.py b/shortcut_composer/templates/raw_instructions.py index 36a32bb0..86b222a6 100644 --- a/shortcut_composer/templates/raw_instructions.py +++ b/shortcut_composer/templates/raw_instructions.py @@ -51,19 +51,19 @@ def __init__( self._instructions = InstructionHolder(instructions) def on_key_press(self) -> None: - """Called on each press of key specified in settings.""" + """Run instructions meant for key press event.""" self._instructions.on_key_press() def on_short_key_release(self) -> None: - """Called when related key was released shortly after press.""" + """Run instructions meant for key release event.""" self._instructions.on_short_key_release() def on_long_key_release(self) -> None: - """Called when related key was released after a long time.""" + """Run instructions meant for key release event after long time.""" self._instructions.on_long_key_release() def on_every_key_release(self) -> None: - """Called on each release of related key, after short/long callback.""" + """Run instructions meant for key release event after short time.""" self._instructions.on_every_key_release() From 3b79e3491e6749ed136b4d0a8bc3e9f91a20684d Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 093/125] bugfix: Make pie children not draggable after change in config --- shortcut_composer/templates/pie_menu_utils/pie_manager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index 564df1fc..a1159346 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -43,6 +43,10 @@ def start(self) -> None: self._circle = CirclePoints(self._pie_widget.center_global, 0) self._timer.start() + # Make sure the pie widget is not draggable. It could have been + # broken by pie settings reloading the widgets. + self._pie_widget.set_draggable(False) + def stop(self, hide: bool = True) -> None: """Hide the widget and stop the mouse tracking loop.""" self._timer.stop() From 35da5e60bff0e5f06d21037ad3736ec20b89f8ce Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 094/125] Specific pie settings for numeric values --- .../templates/pie_menu_utils/dispatchers.py | 16 +++++-- .../templates/pie_menu_utils/pie_config.py | 3 +- .../pie_menu_utils/settings_gui/__init__.py | 8 +++- .../settings_gui/enum_pie_settings.py | 7 ++- .../settings_gui/number_pie_config.py | 44 +++++++++++++++++++ .../settings_gui/pie_settings.py | 2 + .../settings_gui/preset_pie_settings.py | 1 + 7 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 shortcut_composer/templates/pie_menu_utils/settings_gui/number_pie_config.py diff --git a/shortcut_composer/templates/pie_menu_utils/dispatchers.py b/shortcut_composer/templates/pie_menu_utils/dispatchers.py index a18ab466..14808de2 100644 --- a/shortcut_composer/templates/pie_menu_utils/dispatchers.py +++ b/shortcut_composer/templates/pie_menu_utils/dispatchers.py @@ -1,11 +1,17 @@ from typing import List, TypeVar, Optional +from enum import Enum from PyQt5.QtGui import QColor from data_components import Tag -from .pie_config import PieConfig, PresetPieConfig, EnumPieConfig -from .settings_gui import PieSettings, PresetPieSettings, EnumPieSettings +from .pie_config import PieConfig, PresetPieConfig, NonPresetPieConfig +from .settings_gui import ( + PieSettings, + PresetPieSettings, + EnumPieSettings, + NumberPieSettings, +) from .label import Label from .pie_style import PieStyle @@ -25,7 +31,7 @@ def create_local_config( background_color, active_color] if isinstance(values, Tag): return PresetPieConfig(*args) - return EnumPieConfig(*args) + return NonPresetPieConfig(*args) def create_pie_settings_window( @@ -38,6 +44,8 @@ def create_pie_settings_window( args = [values, used_values, style, pie_config, parent] if isinstance(pie_config, PresetPieConfig): return PresetPieSettings(*args) - elif isinstance(pie_config, EnumPieConfig): + elif isinstance(pie_config, NonPresetPieConfig): + if not values or isinstance(values[0], Enum): + return NumberPieSettings(*args) return EnumPieSettings(*args) raise ValueError(f"Unknown pie config {pie_config}") diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 9db6e889..1f249704 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -11,6 +11,7 @@ class PieConfig(FieldGroup, Generic[T], ABC): + ALLOW_REMOVE: bool name: str @@ -57,7 +58,7 @@ def values(self) -> List[str]: return preset_order + missing -class EnumPieConfig(PieConfig, Generic[T]): +class NonPresetPieConfig(PieConfig, Generic[T]): ALLOW_REMOVE = True diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py index 661dea4f..9d5470ab 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py @@ -1,5 +1,11 @@ from .pie_settings import PieSettings from .enum_pie_settings import EnumPieSettings from .preset_pie_settings import PresetPieSettings +from .number_pie_config import NumberPieSettings -__all__ = ["PieSettings", "EnumPieSettings", "PresetPieSettings"] +__all__ = [ + "PieSettings", + "EnumPieSettings", + "PresetPieSettings", + "NumberPieSettings" +] diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index 75105c9c..deed353d 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -5,7 +5,7 @@ from config_system.ui import ConfigFormWidget, ConfigSpinBox from ..label import Label from ..pie_style import PieStyle -from ..pie_config import EnumPieConfig +from ..pie_config import NonPresetPieConfig from .pie_settings import PieSettings from .scroll_area import ScrollArea @@ -16,17 +16,16 @@ def __init__( values: List[Label], used_values: List[Label], style: PieStyle, - pie_config: EnumPieConfig, + pie_config: NonPresetPieConfig, parent=None ) -> None: super().__init__( values, + used_values, style, pie_config, parent) - self._used_values = used_values - tab_holder = QTabWidget() self._action_values = ScrollArea(values, self._style, 3) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/number_pie_config.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/number_pie_config.py new file mode 100644 index 00000000..aa513786 --- /dev/null +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/number_pie_config.py @@ -0,0 +1,44 @@ +from typing import List + +from PyQt5.QtWidgets import QVBoxLayout +from config_system.ui import ConfigFormWidget, ConfigSpinBox +from ..label import Label +from ..pie_style import PieStyle +from ..pie_config import PresetPieConfig +from .pie_settings import PieSettings + + +class NumberPieSettings(PieSettings): + def __init__( + self, + values: List[Label], + used_values: List[Label], + style: PieStyle, + pie_config: PresetPieConfig, + parent=None + ) -> None: + super().__init__( + values, + used_values, + style, + pie_config, + parent) + + self._local_settings = ConfigFormWidget([ + ConfigSpinBox( + pie_config.PIE_RADIUS_SCALE, self, "Pie scale", 0.05, 4), + ConfigSpinBox( + pie_config.ICON_RADIUS_SCALE, self, "Icon scale", 0.05, 4), + ]) + + layout = QVBoxLayout(self) + layout.addWidget(self._local_settings) + self.setLayout(layout) + + def show(self): + self._local_settings.refresh() + super().show() + + def hide(self) -> None: + self._local_settings.apply() + super().hide() diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py index 8a938e81..bf2da5d6 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py @@ -15,6 +15,7 @@ class PieSettings(AnimatedWidget, BaseWidget): def __init__( self, values: List[Label], + used_values: List[Label], style: PieStyle, pie_config: PieConfig, parent=None @@ -28,6 +29,7 @@ def __init__( self.setCursor(Qt.ArrowCursor) self._values = values + self._used_values = used_values self._style = style self._pie_config = pie_config self._pie_config.register_callback(self._reset) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py index d3d3673f..8ea82d23 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py @@ -23,6 +23,7 @@ def __init__( ) -> None: super().__init__( values, + used_values, style, pie_config, parent) From 421af951a1fac90cba18f0cb8d467c18975b5822 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 095/125] Documentation and codestyle changes --- .../api_krita/pyqt/custom_widgets.py | 1 + .../templates/pie_menu_utils/dispatchers.py | 8 +-- .../templates/pie_menu_utils/pie_config.py | 21 ++++++-- .../templates/pie_menu_utils/pie_manager.py | 1 - .../templates/pie_menu_utils/pie_style.py | 51 ++++++++++++------- .../settings_gui/enum_pie_settings.py | 24 ++++----- .../settings_gui/number_pie_config.py | 24 ++++----- .../settings_gui/pie_settings.py | 26 ++++++---- .../settings_gui/preset_pie_settings.py | 25 ++++----- .../pie_menu_utils/widget_utils/edit_mode.py | 11 +++- .../widget_utils/label_holder.py | 33 +++++++++--- .../widget_utils/round_button.py | 8 ++- .../widget_utils/widget_holder.py | 21 +++----- 13 files changed, 152 insertions(+), 102 deletions(-) diff --git a/shortcut_composer/api_krita/pyqt/custom_widgets.py b/shortcut_composer/api_krita/pyqt/custom_widgets.py index 53f58c20..d53c3281 100644 --- a/shortcut_composer/api_krita/pyqt/custom_widgets.py +++ b/shortcut_composer/api_krita/pyqt/custom_widgets.py @@ -29,6 +29,7 @@ def setGeometry(self, ax: int, ay: int, aw: int, ah: int): super().setGeometry(ax, ay, aw, ah) self.move_center(center) + class AnimatedWidget(QWidget): """Adds the fade-in animation when the widget is shown (60 FPS).""" diff --git a/shortcut_composer/templates/pie_menu_utils/dispatchers.py b/shortcut_composer/templates/pie_menu_utils/dispatchers.py index 14808de2..53eae772 100644 --- a/shortcut_composer/templates/pie_menu_utils/dispatchers.py +++ b/shortcut_composer/templates/pie_menu_utils/dispatchers.py @@ -26,11 +26,12 @@ def create_local_config( background_color: Optional[QColor], active_color: QColor, ) -> PieConfig[T]: + """Create and return the right local config based on values type.""" config_name = f"ShortcutComposer: {name}" args = [config_name, values, pie_radius_scale, icon_radius_scale, background_color, active_color] if isinstance(values, Tag): - return PresetPieConfig(*args) + return PresetPieConfig(*args) # type: ignore return NonPresetPieConfig(*args) @@ -41,11 +42,12 @@ def create_pie_settings_window( pie_config: PieConfig, parent=None ) -> PieSettings: - args = [values, used_values, style, pie_config, parent] + """Create and return the right settings based on config and value type.""" + args = [pie_config, style, parent] if isinstance(pie_config, PresetPieConfig): return PresetPieSettings(*args) elif isinstance(pie_config, NonPresetPieConfig): if not values or isinstance(values[0], Enum): return NumberPieSettings(*args) - return EnumPieSettings(*args) + return EnumPieSettings(values, used_values, *args) raise ValueError(f"Unknown pie config {pie_config}") diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 1f249704..b3e25e65 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -11,22 +11,34 @@ class PieConfig(FieldGroup, Generic[T], ABC): + """Abstract FieldGroup representing config of PieMenu.""" ALLOW_REMOVE: bool + """Is it allowed to remove elements in runtime. """ name: str + """Name of the group in .kritarc.""" background_color: Optional[QColor] active_color: QColor ORDER: Field[List[T]] + """Value order stored in .kritarc.""" PIE_RADIUS_SCALE: Field[float] ICON_RADIUS_SCALE: Field[float] @abstractmethod - def values(self) -> List[T]: ... + def values(self) -> List[T]: + """Return values to display as icons on the pie.""" + ... + +class PresetPieConfig(PieConfig[str]): + """ + FieldGroup representing config of PieMenu of presets. -class PresetPieConfig(PieConfig): + Values are calculated according to presets belonging to handled tag + and the custom order saved by the user in .kritarc. + """ ALLOW_REMOVE = False @@ -50,6 +62,7 @@ def __init__( self.active_color = active_color def values(self) -> List[str]: + """Return all presets from the tag. Respect order from .kritarc.""" saved_order = self.ORDER.read() tag_values = Tag(self.TAG_NAME.read()) @@ -58,7 +71,8 @@ def values(self) -> List[str]: return preset_order + missing -class NonPresetPieConfig(PieConfig, Generic[T]): +class NonPresetPieConfig(PieConfig[T], Generic[T]): + """FieldGroup representing config of PieMenu of non-preset values.""" ALLOW_REMOVE = True @@ -81,4 +95,5 @@ def __init__( self.active_color = active_color def values(self) -> List[T]: + """Return values to display as icons as defined be the user.""" return self.ORDER.read() diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index a1159346..ba01a0b2 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -19,7 +19,6 @@ class PieManager: - Displays the widget between start() and stop() calls. - Starts a thread loop which checks for changes of active label. - - Asks the widget to repaint when after changing active label. """ def __init__(self, pie_widget: PieWidget, pie_settings: PieSettings): diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index 75c83d0e..9f9c6978 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -3,29 +3,32 @@ import math import platform +from typing import TYPE_CHECKING from copy import copy from PyQt5.QtGui import QColor from api_krita import Krita from composer_utils import Config -from .pie_config import PieConfig + +if TYPE_CHECKING: + from .pie_config import PieConfig class PieStyle: """ Holds and calculates configuration of displayed elements. - All style elements are calculated based on passed base colors and - scale multipliers. + Style elements are calculated based on passed local config and + imported global config. - Using adapt_to_item_amount() allows to modify the style to make it - fit the given amount of labels. + They are also affected by length of passed items list which size can + change over time. """ def __init__( self, - pie_config: PieConfig, + pie_config: 'PieConfig', items: list, ) -> None: self._items = items @@ -33,15 +36,18 @@ def __init__( self._pie_config = pie_config @property - def _pie_radius_scale(self): + def _pie_radius_scale(self) -> float: + """Local scale of pie selected by user.""" return self._pie_config.PIE_RADIUS_SCALE.read() @property - def _icon_radius_scale(self): + def _icon_radius_scale(self) -> float: + """Local scale of pie child selected by user.""" return self._pie_config.ICON_RADIUS_SCALE.read() @property def pie_radius(self) -> int: + """Radius in pixels at which icon centers are located.""" return round( 165 * self._base_size * self._pie_radius_scale @@ -49,27 +55,26 @@ def pie_radius(self) -> int: @property def _base_icon_radius(self) -> int: + """Radius of icons in pixels. Not affected by items amount.""" return round( 50 * self._base_size * self._icon_radius_scale * Config.PIE_ICON_GLOBAL_SCALE.read()) @property - def _unscaled_base_icon_radius(self) -> int: + def unscaled_icon_radius(self) -> int: + """Radius of icons in pixels. Ignores local scale and items amount.""" return round( 50 * self._base_size * Config.PIE_ICON_GLOBAL_SCALE.read()) @property def _max_icon_radius(self) -> int: + """Max icon radius in pixels according to items amount.""" if not self._items: return 1 return round(self.pie_radius * math.pi / len(self._items)) - @property - def unscaled_icon_radius(self) -> int: - return min(self._unscaled_base_icon_radius, self._max_icon_radius) - @property def icon_radius(self) -> int: """Icons radius depend on settings, but they have to fit in the pie.""" @@ -85,42 +90,50 @@ def deadzone_radius(self) -> float: * Config.PIE_DEADZONE_GLOBAL_SCALE.read()) @property - def widget_radius(self): + def widget_radius(self) -> int: + """Radius of the entire widget, including base and the icons.""" return self.pie_radius + self._base_icon_radius @property def border_thickness(self): - return round(self.pie_radius*0.02) + """Thickness of border around icons.""" + return round(self.unscaled_icon_radius*0.06) @property def area_thickness(self): - return round(self.pie_radius/self._pie_radius_scale*0.4) + """Thickness of the base area of pie menu.""" + return round(self.pie_radius*0.4) @property def inner_edge_radius(self): + """Radius at which the base area starts.""" return self.pie_radius - self.area_thickness @property def no_border_radius(self): + """Radius at which pie decoration border starts.""" return self.pie_radius - self.border_thickness//2 @property def setting_button_radius(self) -> int: + """Radius of the button which activates edit mode.""" return round(30 * self._base_size) @property def accept_button_radius(self) -> int: + """Radius of the button which applies the changes from edit mode.""" default_radius = self.setting_button_radius radius = self.deadzone_radius return int(radius) if radius != float("inf") else default_radius @property def active_color(self): + """Color of active element.""" return self._pie_config.active_color @property def background_color(self) -> QColor: - """Default background color depends on the app theme lightness.""" + """Color of base area. Depends on the app theme lightness""" if self._pie_config.background_color is not None: return self._pie_config.background_color if Krita.is_light_theme_active: @@ -129,6 +142,7 @@ def background_color(self) -> QColor: @property def active_color_dark(self): + """Color variation of active element.""" return QColor( round(self.active_color.red()*0.8), round(self.active_color.green()*0.8), @@ -136,12 +150,14 @@ def active_color_dark(self): @property def icon_color(self): + """Color of icon background.""" color = copy(self.background_color) color.setAlpha(255) return color @property def border_color(self): + """Color of icon borders.""" return QColor( min(self.icon_color.red()+15, 255), min(self.icon_color.green()+15, 255), @@ -150,6 +166,7 @@ def border_color(self): @property def font_multiplier(self): + """Multiplier to apply to the font depending on the used OS.""" return self.SYSTEM_FONT_SIZE[platform.system()] SYSTEM_FONT_SIZE = { diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index deed353d..9d33f086 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -1,6 +1,6 @@ -from typing import List +from typing import List, Optional -from PyQt5.QtWidgets import QVBoxLayout, QTabWidget +from PyQt5.QtWidgets import QVBoxLayout, QTabWidget, QWidget from config_system.ui import ConfigFormWidget, ConfigSpinBox from ..label import Label @@ -15,16 +15,13 @@ def __init__( self, values: List[Label], used_values: List[Label], + config: NonPresetPieConfig, style: PieStyle, - pie_config: NonPresetPieConfig, - parent=None + parent: Optional[QWidget] = None, ) -> None: - super().__init__( - values, - used_values, - style, - pie_config, - parent) + super().__init__(config, style, parent) + + self._used_values = used_values tab_holder = QTabWidget() @@ -34,10 +31,9 @@ def __init__( tab_holder.addTab(self._action_values, "Action values") self._local_settings = ConfigFormWidget([ + ConfigSpinBox(config.PIE_RADIUS_SCALE, self, "Pie scale", 0.05, 4), ConfigSpinBox( - pie_config.PIE_RADIUS_SCALE, self, "Pie scale", 0.05, 4), - ConfigSpinBox( - pie_config.ICON_RADIUS_SCALE, self, "Icon scale", 0.05, 4), + config.ICON_RADIUS_SCALE, self, "Icon scale", 0.05, 4), ]) tab_holder.addTab(self._local_settings, "Local settings") @@ -45,7 +41,7 @@ def __init__( layout.addWidget(tab_holder) self.setLayout(layout) - self._pie_config.ORDER.register_callback(self.refresh) + self._config.ORDER.register_callback(self.refresh) self.refresh() def refresh(self): diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/number_pie_config.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/number_pie_config.py index aa513786..42e21f30 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/number_pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/number_pie_config.py @@ -1,34 +1,28 @@ -from typing import List +from typing import Optional +from PyQt5.QtWidgets import QWidget from PyQt5.QtWidgets import QVBoxLayout + from config_system.ui import ConfigFormWidget, ConfigSpinBox -from ..label import Label +from ..pie_config import NonPresetPieConfig from ..pie_style import PieStyle -from ..pie_config import PresetPieConfig from .pie_settings import PieSettings class NumberPieSettings(PieSettings): def __init__( self, - values: List[Label], - used_values: List[Label], + config: NonPresetPieConfig, style: PieStyle, - pie_config: PresetPieConfig, - parent=None + parent: Optional[QWidget] = None, ) -> None: - super().__init__( - values, - used_values, - style, - pie_config, - parent) + super().__init__(config, style, parent) self._local_settings = ConfigFormWidget([ ConfigSpinBox( - pie_config.PIE_RADIUS_SCALE, self, "Pie scale", 0.05, 4), + config.PIE_RADIUS_SCALE, self, "Pie scale", 0.05, 4), ConfigSpinBox( - pie_config.ICON_RADIUS_SCALE, self, "Icon scale", 0.05, 4), + config.ICON_RADIUS_SCALE, self, "Icon scale", 0.05, 4), ]) layout = QVBoxLayout(self) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py index bf2da5d6..11d8549f 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py @@ -1,24 +1,27 @@ -from typing import List +from typing import Optional from PyQt5.QtCore import QPoint, Qt from PyQt5.QtGui import QCursor +from PyQt5.QtWidgets import QWidget from api_krita.pyqt import AnimatedWidget, BaseWidget from composer_utils import Config - -from ..label import Label from ..pie_style import PieStyle from ..pie_config import PieConfig class PieSettings(AnimatedWidget, BaseWidget): + """ + Abstract widget that allows to change values in passed config. + + List of values + """ + def __init__( self, - values: List[Label], - used_values: List[Label], + config: PieConfig, style: PieStyle, - pie_config: PieConfig, - parent=None + parent: Optional[QWidget] = None, ) -> None: AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) self.setAcceptDrops(True) @@ -28,17 +31,18 @@ def __init__( Qt.FramelessWindowHint)) self.setCursor(Qt.ArrowCursor) - self._values = values - self._used_values = used_values self._style = style - self._pie_config = pie_config - self._pie_config.register_callback(self._reset) + self._config = config + self._config.register_callback(self._reset) self._reset() def move_to_pie_side(self): + """Move the widget on the right side of the pie.""" offset = self.width()//2 + self._style.widget_radius * 1.05 point = QPoint(round(offset), 0) + # Assume the pie center should be at the cursor self.move_center(QCursor().pos() + point) # type: ignore def _reset(self): + """React to change in pie size.""" self.setMinimumHeight(self._style.widget_radius*2) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py index 8ea82d23..b1da7870 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py @@ -1,12 +1,13 @@ -from typing import List +from typing import List, Optional from PyQt5.QtWidgets import QVBoxLayout +from PyQt5.QtWidgets import QWidget + from api_krita.wrappers import Database from config_system.ui import ( ConfigFormWidget, ConfigComboBox, ConfigSpinBox) -from ..label import Label from ..pie_style import PieStyle from ..pie_config import PresetPieConfig from .pie_settings import PieSettings @@ -15,28 +16,20 @@ class PresetPieSettings(PieSettings): def __init__( self, - values: List[Label], - used_values: List[Label], + config: PresetPieConfig, style: PieStyle, - pie_config: PresetPieConfig, - parent=None + parent: Optional[QWidget] = None, ) -> None: - super().__init__( - values, - used_values, - style, - pie_config, - parent) + super().__init__(config, style, parent) self._tags: List[str] = [] self._refresh_tags() self._local_settings = ConfigFormWidget([ - ConfigComboBox(pie_config.TAG_NAME, self, "Tag name", self._tags), - ConfigSpinBox( - pie_config.PIE_RADIUS_SCALE, self, "Pie scale", 0.05, 4), + ConfigComboBox(config.TAG_NAME, self, "Tag name", self._tags), + ConfigSpinBox(config.PIE_RADIUS_SCALE, self, "Pie scale", 0.05, 4), ConfigSpinBox( - pie_config.ICON_RADIUS_SCALE, self, "Icon scale", 0.05, 4), + config.ICON_RADIUS_SCALE, self, "Icon scale", 0.05, 4), ]) layout = QVBoxLayout(self) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py index 2066dfef..0cf9b312 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py @@ -5,6 +5,13 @@ class EditMode: + """ + Handles the edit mode of the PieMenu action. + + Changing its state to between False and True performs actions on + PieMenu widgets and components. + """ + def __init__(self, obj: 'PieMenu') -> None: self._edit_mode = False self._obj = obj @@ -25,10 +32,10 @@ def set(self, mode_to_set: bool) -> None: self.set_edit_mode_true() else: self.set_edit_mode_false() - self._edit_mode = mode_to_set def set_edit_mode_true(self): + """Set the edit mode on.""" self._obj.pie_manager.stop(hide=False) self._obj.pie_widget.set_draggable(True) self._obj.pie_widget.is_edit_mode = True @@ -38,6 +45,7 @@ def set_edit_mode_true(self): self._obj.settings_button.hide() def set_edit_mode_false(self): + """Set the edit mode off.""" self._obj.pie_widget.hide() self._obj.pie_widget.set_draggable(False) self._obj.pie_widget.is_edit_mode = False @@ -46,6 +54,7 @@ def set_edit_mode_false(self): self._obj.settings_button.show() def swap_mode(self): + """Change the edit mode to the other one.""" self.set(not self._edit_mode) def _write_settings(self) -> None: diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py index 9270dd23..46a6dbfd 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py @@ -15,6 +15,14 @@ class LabelHolder: + """ + Represents the pie icons as a positional label container. + + Creates and controls the publically available WidgetHolder with + actual pie widgets. Is responsible for making sure that WidgetHolder + state always reflect the internal state of this container. + """ + def __init__( self, labels: List[Label], @@ -25,17 +33,21 @@ def __init__( self._labels = labels self._style = style self._config = config - self._config.register_callback(partial(self._reset, False)) + # Refresh itself when config changed, but do not notify change + # in config as holder was not their cause + self._config.register_callback(partial(self._reset, notify=False)) self._owner = owner - self.widget_holder: WidgetHolder = WidgetHolder() - self._reset(False) + self.widget_holder = WidgetHolder() + self._reset(notify=False) def append(self, label: Label): + """Append the new label to the holder.""" self._labels.append(label) self._reset() def remove(self, label: Label): + """Remove the label from the holder.""" if (label in self._labels and len(self._labels) > 1 and self._config.ALLOW_REMOVE): @@ -43,6 +55,7 @@ def remove(self, label: Label): self._reset() def swap(self, _a: Label, _b: Label): + """Swap positions of two labels from the holder.""" _a.swap_locations(_b) idx_a, idx_b = self._labels.index(_a), self._labels.index(_b) @@ -52,12 +65,18 @@ def swap(self, _a: Label, _b: Label): self._reset() def __iter__(self): + """Iterate over all labels in the holder.""" return iter(self._labels) - def __bool__(self): - return bool(self._labels) - def _reset(self, notify: bool = True) -> None: + """ + Ensure the icon widgets properly represet this container. + + If notify flag is set to True, saves the new order to config. + + HACK: Small changes in container should not result in complete + widget recreation. + """ for child in self.widget_holder: child.setParent(None) # type: ignore self.widget_holder.clear() @@ -77,7 +96,7 @@ def _reset(self, notify: bool = True) -> None: child.label.angle = angle child.label.center = point child.move_to_label() - child.draggable = True # FIXME: set False after config change + child.draggable = True self.widget_holder.add(child) if notify: diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py index e61837be..208bea33 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py @@ -10,7 +10,12 @@ class RoundButton(QPushButton, BaseWidget): - """Round button with a tick icon which uses provided PieStyle.""" + """ + Round button with a tick icon which uses provided PieStyle. + + `Radius callback` defines how the button radius is determined. Each + change in passed `config` results in resetting the button size. + """ def __init__( self, @@ -43,6 +48,7 @@ def __init__( self.show() def _reset(self): + """Reset the size and repaint the button.""" radius = self._radius_callback() self.setGeometry(0, 0, radius*2, radius*2) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py index 8c728115..7e48c65e 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py @@ -7,12 +7,7 @@ class WidgetHolder: - """ - Holds LabelWidgets and in relation to their angles on PieWidget. - - Holds which of them is active (One of them None), but does not - handle this attribute by itself - this is done by PieManager. - """ + """Holds LabelWidgets in relation to their angles on PieWidget.""" def __init__(self): self._widgets: Dict[int, LabelWidget] = {} @@ -21,16 +16,10 @@ def add(self, widget: LabelWidget) -> None: """Add a new LabelWidget to the holder.""" self._widgets[widget.label.angle] = widget - def angles(self) -> Iterator[int]: - """Iterate over all angles at which LabelWidgets are.""" - return iter(self._widgets.keys()) - def on_angle(self, angle: float) -> LabelWidget: """Return LabelWidget which is the closest to given `angle`.""" - def angle_difference(label_angle: float) -> float: """Return the smallest difference between two angles.""" - nonlocal angle raw_difference = label_angle - angle return abs((raw_difference + 180) % 360 - 180) @@ -38,21 +27,27 @@ def angle_difference(label_angle: float) -> float: return self._widgets[closest] def on_label(self, label: Label): + """Return widget wrapping the label of the same value as given.""" for widget in self._widgets.values(): if widget.label == label: return widget raise ValueError(f"{label} not in holder.") def angle(self, widget: LabelWidget) -> int: - """Return at which angle angle is the given LabelWidget.""" + """Return at which angle is the given LabelWidget.""" for angle, held_widget in self._widgets.items(): if widget == held_widget: return angle raise ValueError(f"{widget} not in holder.") def clear(self): + """Remove all widgets from the holder.""" self._widgets = {} + def angles(self) -> Iterator[int]: + """Iterate over all angles at which LabelWidgets are.""" + return iter(self._widgets.keys()) + def __iter__(self) -> Iterator[LabelWidget]: """Iterate over all held LabelWidgets.""" for angle in sorted(self.angles()): From c2a63b4175b3c69febcdca6540dbbba8968a83ca Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 096/125] Improved adding labels to pie without messing order --- .../templates/pie_menu_utils/pie_widget.py | 21 +++++++++++++++---- .../widget_utils/label_holder.py | 9 ++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index e61067a0..3a0fdbda 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -17,6 +17,7 @@ from .label_widget import LabelWidget from .pie_config import PieConfig from .widget_utils import ( + WidgetHolder, CirclePoints, LabelHolder, PiePainter) @@ -108,21 +109,29 @@ def dragMoveEvent(self, e: QDragMoveEvent) -> None: source_widget = e.source() pos = e.pos() distance = self._circle_points.distance(pos) + if not isinstance(source_widget, LabelWidget): + # Drag incoming from outside the PieWidget ecosystem return self._last_widget = source_widget if distance > self._style.widget_radius: + # Dragged out of the PieWidget return self.label_holder.remove(source_widget.label) if distance < self._style.deadzone_radius: + # Dragged over the deadzone return + angle = self._circle_points.angle_from_point(pos) + _b = self._widget_holder.on_angle(angle) + if source_widget.label not in self.label_holder or not self._labels: - return self.label_holder.append(source_widget.label) + # Dragged with unknown label + index = self.label_holder.index(_b.label) + return self.label_holder.insert(index, source_widget.label) - _a = self.label_holder.widget_holder.on_label(source_widget.label) - angle = self._circle_points.angle_from_point(pos) - _b = self.label_holder.widget_holder.on_angle(angle) + # Dragged existing label to a new location + _a = self._widget_holder.on_label(source_widget.label) if _a != _b: self.label_holder.swap(_a.label, _b.label) self.repaint() @@ -137,3 +146,7 @@ def set_draggable(self, draggable: bool): """Change draggable state of all children.""" for widget in self.label_holder.widget_holder: widget.draggable = draggable + + @property + def _widget_holder(self) -> WidgetHolder: + return self.label_holder.widget_holder diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py index 46a6dbfd..791fc51c 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py @@ -46,6 +46,11 @@ def append(self, label: Label): self._labels.append(label) self._reset() + def insert(self, index: int, label: Label): + """Insert the new label to the holder at given index.""" + self._labels.insert(index, label) + self._reset() + def remove(self, label: Label): """Remove the label from the holder.""" if (label in self._labels @@ -54,6 +59,10 @@ def remove(self, label: Label): self._labels.remove(label) self._reset() + def index(self, label: Label): + """Return the index at which the label is stored.""" + return self._labels.index(label) + def swap(self, _a: Label, _b: Label): """Swap positions of two labels from the holder.""" _a.swap_locations(_b) From a82665f1e9c3fa38aa4bc7e6dda667185e81cd6f Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:14 +0200 Subject: [PATCH 097/125] Separated RoundButton and PieButton --- shortcut_composer/api_krita/pyqt/__init__.py | 2 + .../pyqt}/round_button.py | 57 +++++++++---------- shortcut_composer/templates/pie_menu.py | 6 +- .../pie_menu_utils/widget_utils/__init__.py | 4 +- .../pie_menu_utils/widget_utils/pie_button.py | 35 ++++++++++++ 5 files changed, 70 insertions(+), 34 deletions(-) rename shortcut_composer/{templates/pie_menu_utils/widget_utils => api_krita/pyqt}/round_button.py (53%) create mode 100644 shortcut_composer/templates/pie_menu_utils/widget_utils/pie_button.py diff --git a/shortcut_composer/api_krita/pyqt/__init__.py b/shortcut_composer/api_krita/pyqt/__init__.py index 812be5ac..a94b7364 100644 --- a/shortcut_composer/api_krita/pyqt/__init__.py +++ b/shortcut_composer/api_krita/pyqt/__init__.py @@ -5,6 +5,7 @@ from .custom_widgets import AnimatedWidget, BaseWidget from .pixmap_transform import PixmapTransform +from .round_button import RoundButton from .colorizer import Colorizer from .painter import Painter from .timer import Timer @@ -13,6 +14,7 @@ __all__ = [ "PixmapTransform", "AnimatedWidget", + "RoundButton", "BaseWidget", "Colorizer", "Painter", diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py b/shortcut_composer/api_krita/pyqt/round_button.py similarity index 53% rename from shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py rename to shortcut_composer/api_krita/pyqt/round_button.py index 208bea33..b3b3fdff 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/round_button.py +++ b/shortcut_composer/api_krita/pyqt/round_button.py @@ -1,39 +1,30 @@ -from typing import Optional, Callable +from typing import Optional from PyQt5.QtWidgets import QWidget, QPushButton from PyQt5.QtGui import QColor, QIcon from PyQt5.QtCore import Qt -from ..pie_style import PieStyle -from ..pie_config import PieConfig -from api_krita.pyqt import BaseWidget +from .custom_widgets import BaseWidget class RoundButton(QPushButton, BaseWidget): - """ - Round button with a tick icon which uses provided PieStyle. - - `Radius callback` defines how the button radius is determined. Each - change in passed `config` results in resetting the button size. - """ + """Round button with custom icon.""" def __init__( - self, - icon: QIcon, - icon_scale: float, - radius_callback: Callable[[], int], - style: PieStyle, - config: PieConfig, + self, *, + icon: QIcon = QIcon(), + icon_scale: float = 1, + initial_radius: int, + background_color: QColor, + active_color: QColor, parent: Optional[QWidget] = None, - ): + ) -> None: QPushButton.__init__(self, icon, "", parent) self.setCursor(Qt.ArrowCursor) - self._radius_callback = radius_callback self._icon_scale = icon_scale - self._style = style - self._config = config - self._config.register_callback(self._reset) + self._background_color = background_color + self._active_color = active_color if parent is None: self.setWindowFlags(( @@ -44,28 +35,36 @@ def __init__( self.setAttribute(Qt.WA_TranslucentBackground) self.setStyleSheet("background: transparent;") - self._reset() + self.resize(initial_radius) self.show() - def _reset(self): - """Reset the size and repaint the button.""" - radius = self._radius_callback() + def resize(self, radius: int): + """Change the size and repaint the button.""" self.setGeometry(0, 0, radius*2, radius*2) self.setStyleSheet(f""" QPushButton [ - border: {self._style.border_thickness}px - {self._color_to_str(self._style.border_color)}; + border: {round(radius*0.06)}px + {self._color_to_str(self._border_color)}; border-radius: {radius}px; border-style: outset; - background: {self._color_to_str(self._style.background_color)}; + background: {self._color_to_str(self._background_color)}; qproperty-iconSize:{round(radius*self._icon_scale)}px; ] QPushButton:hover [ - background: {self._color_to_str(self._style.active_color)}; + background: {self._color_to_str(self._active_color)}; ] """.replace('[', '{').replace(']', '}')) @staticmethod def _color_to_str(color: QColor) -> str: return f'''rgba( {color.red()}, {color.green()}, {color.blue()}, {color.alpha()})''' + + @property + def _border_color(self): + """Color of button border.""" + return QColor( + min(self._background_color.red()+15, 255), + min(self._background_color.green()+15, 255), + min(self._background_color.blue()+15, 255), + 255) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index a7db55c6..084e96e2 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -16,7 +16,7 @@ PieWidget, PieStyle, Label) -from .pie_menu_utils.widget_utils import EditMode, RoundButton +from .pie_menu_utils.widget_utils import EditMode, PieButton from .raw_instructions import RawInstructions T = TypeVar('T') @@ -106,7 +106,7 @@ def __init__( pie_widget=self.pie_widget, pie_settings=self.pie_settings) - self.settings_button = RoundButton( + self.settings_button = PieButton( icon=Krita.get_icon("properties"), icon_scale=1.1, parent=self.pie_widget, @@ -114,7 +114,7 @@ def __init__( style=self._style, config=self._config) self.settings_button.clicked.connect(lambda: self._edit_mode.set(True)) - self.accept_button = RoundButton( + self.accept_button = PieButton( icon=Krita.get_icon("dialog-ok"), icon_scale=1.5, parent=self.pie_widget, diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py index 3478747e..fcff489b 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py @@ -5,16 +5,16 @@ from .circle_points import CirclePoints from .widget_holder import WidgetHolder -from .round_button import RoundButton from .label_holder import LabelHolder from .pie_painter import PiePainter +from .pie_button import PieButton from .edit_mode import EditMode __all__ = [ "CirclePoints", "WidgetHolder", - "RoundButton", "LabelHolder", "PiePainter", + "PieButton", "EditMode", ] diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_button.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_button.py new file mode 100644 index 00000000..1bf86501 --- /dev/null +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_button.py @@ -0,0 +1,35 @@ +from typing import Optional, Callable + +from PyQt5.QtWidgets import QWidget +from PyQt5.QtGui import QIcon + +from api_krita.pyqt import RoundButton +from ..pie_style import PieStyle +from ..pie_config import PieConfig + + +class PieButton(RoundButton): + """ + Round button with custom icon, which uses provided PieStyle. + + `radius_callback` defines how the button radius is determined. Each + change in passed `config` results in resetting the button size. + """ + + def __init__( + self, + icon: QIcon, + icon_scale: float, + radius_callback: Callable[[], int], + style: PieStyle, + config: PieConfig, + parent: Optional[QWidget] = None, + ): + super().__init__( + icon=icon, + icon_scale=icon_scale, + initial_radius=radius_callback(), + background_color=style.background_color, + active_color=style.active_color, + parent=parent) + config.register_callback(lambda: self.resize(radius_callback())) From 33b89d3d00a3a2c4f4f73acef0bf4b8526136fa9 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:15 +0200 Subject: [PATCH 098/125] Settings window for MultipleAssignment --- .../composer_utils/tabs/__init__.py | 7 -- .../composer_utils/tabs/action_values_tab.py | 69 ------------------- .../templates/multiple_assignment.py | 11 ++- .../multiple_assignment_utils}/__init__.py | 9 ++- .../action_values.py | 30 ++++---- .../action_values_window.py | 53 ++++++++++++++ .../settings_handler.py | 49 +++++++++++++ .../multiple_assignment_utils}/value_list.py | 0 8 files changed, 138 insertions(+), 90 deletions(-) delete mode 100644 shortcut_composer/composer_utils/tabs/__init__.py delete mode 100644 shortcut_composer/composer_utils/tabs/action_values_tab.py rename shortcut_composer/{composer_utils/utils => templates/multiple_assignment_utils}/__init__.py (54%) rename shortcut_composer/{composer_utils/utils => templates/multiple_assignment_utils}/action_values.py (80%) create mode 100644 shortcut_composer/templates/multiple_assignment_utils/action_values_window.py create mode 100644 shortcut_composer/templates/multiple_assignment_utils/settings_handler.py rename shortcut_composer/{composer_utils/utils => templates/multiple_assignment_utils}/value_list.py (100%) diff --git a/shortcut_composer/composer_utils/tabs/__init__.py b/shortcut_composer/composer_utils/tabs/__init__.py deleted file mode 100644 index 4eaff5c7..00000000 --- a/shortcut_composer/composer_utils/tabs/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus -# SPDX-License-Identifier: GPL-3.0-or-later - -from .action_values_tab import ActionValuesTab - - -__all__ = ["ActionValuesTab"] diff --git a/shortcut_composer/composer_utils/tabs/action_values_tab.py b/shortcut_composer/composer_utils/tabs/action_values_tab.py deleted file mode 100644 index 794ea8a5..00000000 --- a/shortcut_composer/composer_utils/tabs/action_values_tab.py +++ /dev/null @@ -1,69 +0,0 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus -# SPDX-License-Identifier: GPL-3.0-or-later - -from PyQt5.QtWidgets import ( - QVBoxLayout, - QComboBox, - QWidget, -) - -from api_krita.enums import BlendingMode, Tool, TransformMode -from ..utils import ActionValues -from global_config import Config - - -class ActionValuesTab(QWidget): - """Tab in which user can change values used in actions and their order.""" - - def __init__(self) -> None: - super().__init__() - layout = QVBoxLayout() - self.combo_widget_picker = QComboBox() - self.widgets = { - "Blending modes": ActionValues( - allowed_values=set(BlendingMode._member_names_), - config=Config.BLENDING_MODES_VALUES - ), - "Create layer with blending": ActionValues( - allowed_values=set(BlendingMode._member_names_), - config=Config.CREATE_BLENDING_LAYER_VALUES - ), - "Selection tools": ActionValues( - allowed_values=set(Tool._member_names_), - config=Config.SELECTION_TOOLS_VALUES - ), - "Misc tools": ActionValues( - allowed_values=set(Tool._member_names_), - config=Config.MISC_TOOLS_VALUES - ), - "Transform modes": ActionValues( - allowed_values=set(TransformMode._member_names_), - config=Config.TRANSFORM_MODES_VALUES - ), - } - self.combo_widget_picker.addItems(self.widgets.keys()) - self.combo_widget_picker.currentTextChanged.connect( - self._change_widget) - - layout.addWidget(self.combo_widget_picker) - for widget in self.widgets.values(): - widget.hide() - layout.addWidget(widget) - self.widgets["Blending modes"].show() - self.setLayout(layout) - - def _change_widget(self): - """Show a selectable list for a different action.""" - for widget in self.widgets.values(): - widget.hide() - self.widgets[self.combo_widget_picker.currentText()].show() - - def apply(self) -> None: - """Save values and their order for each selectable list.""" - for widget in self.widgets.values(): - widget.apply() - - def refresh(self) -> None: - """Refresh each selectable list by loading values from settings.""" - for widget in self.widgets.values(): - widget.refresh() diff --git a/shortcut_composer/templates/multiple_assignment.py b/shortcut_composer/templates/multiple_assignment.py index 4c9b136f..d1ee9602 100644 --- a/shortcut_composer/templates/multiple_assignment.py +++ b/shortcut_composer/templates/multiple_assignment.py @@ -6,6 +6,7 @@ from core_components import Controller, Instruction from .raw_instructions import RawInstructions +from .multiple_assignment_utils import SettingsHandler T = TypeVar('T') @@ -70,9 +71,17 @@ def __init__( super().__init__(name, instructions, short_vs_long_press_time) self._controller = controller - self._values_to_cycle = values self._default_value = self._read_default_value(default_value) + self.handler = SettingsHandler(name, values, instructions) + self._values_to_cycle = self.handler.values_field.read() + + def reset() -> None: + self._values_to_cycle = self.handler.values_field.read() + self._reset_iterator() + + self.handler.values_field.register_callback(reset) + self._last_value: Optional[T] = None self._iterator: Iterator[T] diff --git a/shortcut_composer/composer_utils/utils/__init__.py b/shortcut_composer/templates/multiple_assignment_utils/__init__.py similarity index 54% rename from shortcut_composer/composer_utils/utils/__init__.py rename to shortcut_composer/templates/multiple_assignment_utils/__init__.py index 2164e89c..8ffc0607 100644 --- a/shortcut_composer/composer_utils/utils/__init__.py +++ b/shortcut_composer/templates/multiple_assignment_utils/__init__.py @@ -3,7 +3,14 @@ """Widgets to display on the settings dialog.""" +from .action_values_window import ActionValuesWindow +from .settings_handler import SettingsHandler from .action_values import ActionValues from .value_list import ValueList -__all__ = ["ActionValues", "ValueList"] +__all__ = [ + "ActionValuesWindow", + "SettingsHandler", + "ActionValues", + "ValueList" +] diff --git a/shortcut_composer/composer_utils/utils/action_values.py b/shortcut_composer/templates/multiple_assignment_utils/action_values.py similarity index 80% rename from shortcut_composer/composer_utils/utils/action_values.py rename to shortcut_composer/templates/multiple_assignment_utils/action_values.py index 42106cec..39f13fc9 100644 --- a/shortcut_composer/composer_utils/utils/action_values.py +++ b/shortcut_composer/templates/multiple_assignment_utils/action_values.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import Set, List +from typing import List, Type from enum import Enum from PyQt5.QtCore import Qt @@ -14,15 +14,16 @@ ) from api_krita import Krita -from composer_utils import Config +from config_system import Field from .value_list import ValueList class ActionValues(QWidget): - def __init__(self, allowed_values: Set[str], config: Config) -> None: + def __init__(self, enum_type: Type[Enum], config: Field[List[Enum]]): super().__init__() + layout = QHBoxLayout() - self.allowed_values = allowed_values + self.enum_type = enum_type self.config = config self.available_list = ValueList(movable=False, parent=self) @@ -62,8 +63,7 @@ def add(self): for value in self.available_list.selected: self.current_list.insert( position=self.current_list.current_row, - value=value, - ) + value=value) self.available_list.remove(value=value,) def remove(self): @@ -74,17 +74,23 @@ def remove(self): self.available_list.addItems(sorted(new_available)) def apply(self): - texts = [] + to_write: List[Enum] = [] for row in range(self.current_list.count()): - texts.append(self.current_list.item(row).text()) - self.config.write(texts) + text = self.current_list.item(row).text() + to_write.append(self.enum_type[text]) + + self.config.write(to_write) def refresh(self): self.current_list.clear() - current_list: List[Enum] = self.config.read() - text_list = [item.value for item in current_list] + current_list = self.config.read() + text_list = [item.name for item in current_list] self.current_list.addItems(text_list) self.available_list.clear() - allowed_items = sorted(self.allowed_values - set(text_list)) + allowed_items = sorted(set(self.allowed_values) - set(text_list)) self.available_list.addItems(allowed_items) + + @property + def allowed_values(self): + return self.enum_type._member_names_ diff --git a/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py b/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py new file mode 100644 index 00000000..df49745b --- /dev/null +++ b/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import Type +from enum import Enum + +from PyQt5.QtWidgets import QVBoxLayout, QWidget +from PyQt5.QtCore import Qt + +from config_system import Field +from composer_utils.layouts import ButtonsLayout +from .action_values import ActionValues + + +class ActionValuesWindow(QWidget): + """Tab in which user can change values used in actions and their order.""" + + def __init__(self, enum_type: Type[Enum], config: Field) -> None: + super().__init__() + self.setWindowFlags(self.windowFlags() | Qt.Tool) # type: ignore + layout = QVBoxLayout() + + self._config = config + self.widget = ActionValues(enum_type, config) + layout.addWidget(self.widget) + + layout.addLayout(ButtonsLayout( + ok_callback=self.ok, + apply_callback=self.apply, + reset_callback=self.reset, + cancel_callback=self.hide)) + + self.setLayout(layout) + + def show(self) -> None: + self.refresh() + return super().show() + + def ok(self) -> None: + """Hide the dialog after applying the changes""" + self.apply() + self.hide() + + def reset(self) -> None: + """Reset all config values to defaults in krita and elements.""" + self._config.reset_default() + self.refresh() + + def apply(self) -> None: + self.widget.apply() + + def refresh(self) -> None: + self.widget.refresh() diff --git a/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py b/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py new file mode 100644 index 00000000..7a6612c1 --- /dev/null +++ b/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import List +from enum import Enum + +from PyQt5.QtGui import QColor + +from api_krita import Krita +from api_krita.pyqt import RoundButton +from core_components import Instruction +from config_system import Field +from .action_values_window import ActionValuesWindow + + +class SettingsHandler: + def __init__( + self, + name: str, + values: list, + instructions: List[Instruction], + ) -> None: + self.values_field = Field( + config_group=f"ShortcutComposer: {name}", + name="Values", + default=values) + + to_cycle = self.values_field.read() + if not to_cycle or not isinstance(to_cycle[0], Enum): + return + + self._settings = ActionValuesWindow( + type(to_cycle[0]), + self.values_field) + + self._settings_button = RoundButton( + icon=Krita.get_icon("properties"), + icon_scale=1.1, + initial_radius=25, + background_color=QColor(75, 75, 75, 255), + active_color=QColor(100, 150, 230, 255)) + self._settings_button.clicked.connect(lambda: self._settings.show()) + self._settings_button.move(0, 0) + self._settings_button.hide() + + inst = Instruction() + inst.on_key_press = lambda: self._settings_button.show() + inst.on_every_key_release = lambda: self._settings_button.hide() + instructions.append(inst) diff --git a/shortcut_composer/composer_utils/utils/value_list.py b/shortcut_composer/templates/multiple_assignment_utils/value_list.py similarity index 100% rename from shortcut_composer/composer_utils/utils/value_list.py rename to shortcut_composer/templates/multiple_assignment_utils/value_list.py From d78978ea9914f51e931d05fbfa289a62bf977850 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:15 +0200 Subject: [PATCH 099/125] Prevent SettingsHandler to show button when settings visible --- .../settings_handler.py | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py b/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py index 7a6612c1..84d4ed8c 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py +++ b/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later +from dataclasses import dataclass from typing import List from enum import Enum @@ -20,18 +21,16 @@ def __init__( values: list, instructions: List[Instruction], ) -> None: - self.values_field = Field( + self.values = Field( config_group=f"ShortcutComposer: {name}", name="Values", default=values) - to_cycle = self.values_field.read() + to_cycle = self.values.read() if not to_cycle or not isinstance(to_cycle[0], Enum): return - self._settings = ActionValuesWindow( - type(to_cycle[0]), - self.values_field) + self._settings = ActionValuesWindow(type(to_cycle[0]), self.values) self._settings_button = RoundButton( icon=Krita.get_icon("properties"), @@ -43,7 +42,18 @@ def __init__( self._settings_button.move(0, 0) self._settings_button.hide() - inst = Instruction() - inst.on_key_press = lambda: self._settings_button.show() - inst.on_every_key_release = lambda: self._settings_button.hide() - instructions.append(inst) + instructions.append(HandlerInstruction( + self._settings, self._settings_button)) + + +@dataclass +class HandlerInstruction(Instruction): + settings: ActionValuesWindow + button: RoundButton + + def on_key_press(self) -> None: + if not self.settings.isVisible(): + self.button.show() + + def on_every_key_release(self) -> None: + self.button.hide() From fab503df55ca4a8f6a903bfadf80c9615e4caea8 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:15 +0200 Subject: [PATCH 100/125] Documentation and codestyle changes --- shortcut_composer/templates/cursor_tracker.py | 12 ++++------- .../templates/multiple_assignment.py | 8 ++++---- .../templates/pie_menu_utils/dispatchers.py | 4 ++-- .../templates/pie_menu_utils/pie_widget.py | 7 ++++--- .../pie_menu_utils/settings_gui/__init__.py | 4 ++-- .../settings_gui/enum_pie_settings.py | 11 ++++++++++ ...er_pie_config.py => numeric_pie_config.py} | 9 ++++++--- .../settings_gui/pie_settings.py | 2 +- .../settings_gui/preset_pie_settings.py | 10 +++++++--- .../settings_gui/scroll_area.py | 5 +---- .../pie_menu_utils/widget_utils/pie_button.py | 3 ++- .../widget_utils/pie_painter.py | 20 ++++++++----------- 12 files changed, 52 insertions(+), 43 deletions(-) rename shortcut_composer/templates/pie_menu_utils/settings_gui/{number_pie_config.py => numeric_pie_config.py} (73%) diff --git a/shortcut_composer/templates/cursor_tracker.py b/shortcut_composer/templates/cursor_tracker.py index 68640376..58126c1c 100644 --- a/shortcut_composer/templates/cursor_tracker.py +++ b/shortcut_composer/templates/cursor_tracker.py @@ -80,8 +80,7 @@ def __new__( instructions=instructions, slider_handler=SliderHandler( slider=horizontal_slider, - is_horizontal=True, - ) + is_horizontal=True) ) if not horizontal_slider and vertical_slider: return SingleAxisTracker( @@ -89,8 +88,7 @@ def __new__( instructions=instructions, slider_handler=SliderHandler( slider=vertical_slider, - is_horizontal=False, - ) + is_horizontal=False) ) if horizontal_slider and vertical_slider: return DoubleAxisTracker( @@ -98,11 +96,9 @@ def __new__( instructions=instructions, horizontal_handler=SliderHandler( slider=horizontal_slider, - is_horizontal=True, - ), + is_horizontal=True), vertical_handler=SliderHandler( slider=vertical_slider, - is_horizontal=False, - ) + is_horizontal=False) ) raise ValueError("At least one slider needed.") diff --git a/shortcut_composer/templates/multiple_assignment.py b/shortcut_composer/templates/multiple_assignment.py index d1ee9602..e3764c44 100644 --- a/shortcut_composer/templates/multiple_assignment.py +++ b/shortcut_composer/templates/multiple_assignment.py @@ -73,14 +73,14 @@ def __init__( self._controller = controller self._default_value = self._read_default_value(default_value) - self.handler = SettingsHandler(name, values, instructions) - self._values_to_cycle = self.handler.values_field.read() + self._settings = SettingsHandler(name, values, instructions) + self._values_to_cycle = self._settings.values.read() def reset() -> None: - self._values_to_cycle = self.handler.values_field.read() + self._values_to_cycle = self._settings.values.read() self._reset_iterator() - self.handler.values_field.register_callback(reset) + self._settings.values.register_callback(reset) self._last_value: Optional[T] = None self._iterator: Iterator[T] diff --git a/shortcut_composer/templates/pie_menu_utils/dispatchers.py b/shortcut_composer/templates/pie_menu_utils/dispatchers.py index 53eae772..be34a992 100644 --- a/shortcut_composer/templates/pie_menu_utils/dispatchers.py +++ b/shortcut_composer/templates/pie_menu_utils/dispatchers.py @@ -10,7 +10,7 @@ PieSettings, PresetPieSettings, EnumPieSettings, - NumberPieSettings, + NumericPieSettings, ) from .label import Label from .pie_style import PieStyle @@ -48,6 +48,6 @@ def create_pie_settings_window( return PresetPieSettings(*args) elif isinstance(pie_config, NonPresetPieConfig): if not values or isinstance(values[0], Enum): - return NumberPieSettings(*args) + return NumericPieSettings(*args) return EnumPieSettings(values, used_values, *args) raise ValueError(f"Unknown pie config {pie_config}") diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index 3a0fdbda..cb4df9c6 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -123,15 +123,15 @@ def dragMoveEvent(self, e: QDragMoveEvent) -> None: return angle = self._circle_points.angle_from_point(pos) - _b = self._widget_holder.on_angle(angle) + _a = self._widget_holder.on_angle(angle) if source_widget.label not in self.label_holder or not self._labels: # Dragged with unknown label - index = self.label_holder.index(_b.label) + index = self.label_holder.index(_a.label) return self.label_holder.insert(index, source_widget.label) # Dragged existing label to a new location - _a = self._widget_holder.on_label(source_widget.label) + _b = self._widget_holder.on_label(source_widget.label) if _a != _b: self.label_holder.swap(_a.label, _b.label) self.repaint() @@ -149,4 +149,5 @@ def set_draggable(self, draggable: bool): @property def _widget_holder(self) -> WidgetHolder: + """Return the holder with child widgets.""" return self.label_holder.widget_holder diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py index 9d5470ab..49bf49ac 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py @@ -1,11 +1,11 @@ from .pie_settings import PieSettings from .enum_pie_settings import EnumPieSettings from .preset_pie_settings import PresetPieSettings -from .number_pie_config import NumberPieSettings +from .numeric_pie_config import NumericPieSettings __all__ = [ "PieSettings", "EnumPieSettings", "PresetPieSettings", - "NumberPieSettings" + "NumericPieSettings" ] diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index 9d33f086..f7008935 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -11,6 +11,14 @@ class EnumPieSettings(PieSettings): + """ + Pie setting window for pie values being enums. + + Consists of two tabs: + - usual form with field values to set + - scrollable area with all available enum values to drag into pie + """ + def __init__( self, values: List[Label], @@ -45,6 +53,7 @@ def __init__( self.refresh() def refresh(self): + """Make all values currently used in pie undraggable and disabled.""" for widget in self._action_values._children_list: if widget.label in self._used_values: widget.enabled = False @@ -54,9 +63,11 @@ def refresh(self): widget.draggable = True def show(self): + """Show the window after its settings are refreshed.""" self._local_settings.refresh() super().show() def hide(self) -> None: + """Hide the window after its settings are saved to .kritarc.""" self._local_settings.apply() super().hide() diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/number_pie_config.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_config.py similarity index 73% rename from shortcut_composer/templates/pie_menu_utils/settings_gui/number_pie_config.py rename to shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_config.py index 42e21f30..50fe5a0d 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/number_pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_config.py @@ -9,7 +9,9 @@ from .pie_settings import PieSettings -class NumberPieSettings(PieSettings): +class NumericPieSettings(PieSettings): + """Pie setting window for pie values being numeric (float or int).""" + def __init__( self, config: NonPresetPieConfig, @@ -19,8 +21,7 @@ def __init__( super().__init__(config, style, parent) self._local_settings = ConfigFormWidget([ - ConfigSpinBox( - config.PIE_RADIUS_SCALE, self, "Pie scale", 0.05, 4), + ConfigSpinBox(config.PIE_RADIUS_SCALE, self, "Pie scale", 0.05, 4), ConfigSpinBox( config.ICON_RADIUS_SCALE, self, "Icon scale", 0.05, 4), ]) @@ -30,9 +31,11 @@ def __init__( self.setLayout(layout) def show(self): + """Show the window after its settings are refreshed.""" self._local_settings.refresh() super().show() def hide(self) -> None: + """Hide the window after its settings are saved to .kritarc.""" self._local_settings.apply() super().hide() diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py index 11d8549f..700566df 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py @@ -14,7 +14,7 @@ class PieSettings(AnimatedWidget, BaseWidget): """ Abstract widget that allows to change values in passed config. - List of values + Meant to be displayed next to pie menu, having the same heigth. """ def __init__( diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py index b1da7870..0ae97184 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py @@ -14,6 +14,8 @@ class PresetPieSettings(PieSettings): + """Pie setting window for pie values being brush presets.""" + def __init__( self, config: PresetPieConfig, @@ -37,16 +39,18 @@ def __init__( self.setLayout(layout) def show(self): + """Show the window after its settings are refreshed.""" self._refresh_tags() self._local_settings.refresh() super().show() def hide(self) -> None: + """Hide the window after its settings are saved to .kritarc.""" self._local_settings.apply() super().hide() def _refresh_tags(self): - with Database() as database: - tags = sorted(database.get_brush_tags(), key=str.lower) + """Replace list of available tags with those red from database.""" self._tags.clear() - self._tags.extend(tags) + with Database() as database: + self._tags.extend(sorted(database.get_brush_tags(), key=str.lower)) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py index 4b601bd1..6c18da1c 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py @@ -1,10 +1,7 @@ from typing import List from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import ( - QWidget, - QScrollArea, - QGridLayout) +from PyQt5.QtWidgets import QWidget, QScrollArea, QGridLayout from ..label import Label from ..label_widget import LabelWidget diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_button.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_button.py index 1bf86501..583a7efb 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_button.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_button.py @@ -24,7 +24,7 @@ def __init__( style: PieStyle, config: PieConfig, parent: Optional[QWidget] = None, - ): + ) -> None: super().__init__( icon=icon, icon_scale=icon_scale, @@ -32,4 +32,5 @@ def __init__( background_color=style.background_color, active_color=style.active_color, parent=parent) + config.register_callback(lambda: self.resize(radius_callback())) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_painter.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_painter.py index 3d33a791..57163477 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_painter.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_painter.py @@ -42,14 +42,13 @@ def _paint_deadzone_indicator(self) -> None: center=self._center, outer_radius=self.style.deadzone_radius, color=QColor(128, 255, 128, 120), - thickness=1, - ) + thickness=1) + self.painter.paint_wheel( center=self._center, outer_radius=self.style.deadzone_radius-1, color=QColor(255, 128, 128, 120), - thickness=1, - ) + thickness=1) def _paint_base_wheel(self) -> None: """Paint a base circle.""" @@ -58,14 +57,13 @@ def _paint_base_wheel(self) -> None: self.painter.paint_wheel( center=self._center, outer_radius=self.style.no_border_radius, - color=QColor(128, 128, 128, 1), - ) + color=QColor(128, 128, 128, 1)) + self.painter.paint_wheel( center=self._center, outer_radius=self.style.no_border_radius, color=self.style.background_color, - thickness=self.style.area_thickness, - ) + thickness=self.style.area_thickness) def _paint_base_border(self) -> None: """Paint a border on the inner edge of base circle.""" @@ -73,8 +71,7 @@ def _paint_base_border(self) -> None: center=self._center, outer_radius=self.style.inner_edge_radius, color=self.style.border_color, - thickness=self.style.border_thickness, - ) + thickness=self.style.border_thickness) def _paint_active_pie(self) -> None: """Paint a pie behind a label which is active or during animation.""" @@ -92,8 +89,7 @@ def _paint_active_pie(self) -> None: angle=label.angle, span=360//len(self.labels), color=self._pick_pie_color(label), - thickness=self.style.area_thickness + thickness_addition, - ) + thickness=self.style.area_thickness + thickness_addition) def _pick_pie_color(self, label: Label) -> QColor: """Pick color of pie based on widget mode and animation progress.""" From e0e98c034c490e04108aa5eab6455c0573eae858 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:15 +0200 Subject: [PATCH 101/125] Hide the MA settings button after being clicked --- .../templates/multiple_assignment_utils/settings_handler.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py b/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py index 84d4ed8c..640093f2 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py +++ b/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py @@ -38,13 +38,17 @@ def __init__( initial_radius=25, background_color=QColor(75, 75, 75, 255), active_color=QColor(100, 150, 230, 255)) - self._settings_button.clicked.connect(lambda: self._settings.show()) + self._settings_button.clicked.connect(self._on_button_click) self._settings_button.move(0, 0) self._settings_button.hide() instructions.append(HandlerInstruction( self._settings, self._settings_button)) + def _on_button_click(self): + self._settings.show() + self._settings_button.hide() + @dataclass class HandlerInstruction(Instruction): From 58e3e6aa3cb9b4ec9db92bb953273b474f000c25 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:15 +0200 Subject: [PATCH 102/125] TO_REVERT: remove scrollArea methods unless unused and possibly not working --- .../pie_menu_utils/settings_gui/scroll_area.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py index 6c18da1c..47767cc8 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py @@ -78,21 +78,6 @@ def append(self, widget: QWidget): self.addWidget(widget, *self._new_position(), 2, 2) self._refresh() - def pop(self, index: int): - widget = self.widgets.pop(index) - self.removeWidget(widget) - self._refresh() - return widget - - def remove(self, widget: QWidget): - for index, held_widget in enumerate(self.widgets): - if held_widget == widget: - return self.pop(index) - - def insert(self, index: int, widget: QWidget): - self.widgets.insert(index, widget) - self._refresh() - def _refresh(self): for i, widget in enumerate(self.widgets): self.addWidget(widget, *self._get_position(i), 2, 2) From 29d4584f65d8d0f3cb6705bec279bb53aea223b6 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:15 +0200 Subject: [PATCH 103/125] Changed ScrollArea names --- .../settings_gui/scroll_area.py | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py index 47767cc8..5fbf2c8d 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, NamedTuple from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QWidget, QScrollArea, QGridLayout @@ -49,27 +49,35 @@ def _create_children(self) -> List[LabelWidget]: return children +class GridPosition(NamedTuple): + gridrow: int + gridcol: int + + class ScrollAreaLayout(QGridLayout): - def __init__(self, cols: int, owner: QWidget): + def __init__(self, max_columns: int, owner: QWidget): super().__init__() self.widgets: List[QWidget] = [] - self._max_rows = cols - self._uniques = 2*cols - 1 + self._max_columns = max_columns + self._items_in_group = 2*max_columns - 1 self._owner = owner - def __len__(self): + def __len__(self) -> int: return len(self.widgets) - def _get_position(self, index: int): - row, col = divmod(index, self._uniques) - if col < self._max_rows: - return (row*4, col*2) - return (row*4+2, (col-self._max_rows)*2+1) + def _get_position(self, index: int) -> GridPosition: + group, item = divmod(index, self._items_in_group) + + if item < self._max_columns: + return GridPosition(gridrow=group*4, gridcol=item*2) + + col = item-self._max_columns + return GridPosition(gridrow=group*4+2, gridcol=col*2+1) - def _new_position(self): + def _new_position(self) -> GridPosition: return self._get_position(len(self.widgets)) - def append(self, widget: QWidget): + def append(self, widget: QWidget) -> None: if widget in self.widgets: return widget.setParent(self._owner) From b962c5b00ff7e51e05aa25f3aabcaf9e20feb34a Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:15 +0200 Subject: [PATCH 104/125] Make MA buttons appear after short time --- .../multiple_assignment_utils/settings_handler.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py b/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py index 640093f2..656d9ec7 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py +++ b/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py @@ -8,7 +8,7 @@ from PyQt5.QtGui import QColor from api_krita import Krita -from api_krita.pyqt import RoundButton +from api_krita.pyqt import RoundButton, Timer from core_components import Instruction from config_system import Field from .action_values_window import ActionValuesWindow @@ -52,12 +52,21 @@ def _on_button_click(self): @dataclass class HandlerInstruction(Instruction): + settings: ActionValuesWindow button: RoundButton + def __post_init__(self): + self.timer = Timer(self.timer_callback, 500) + def on_key_press(self) -> None: + self.timer.start() + + def timer_callback(self): if not self.settings.isVisible(): self.button.show() + self.timer.stop() def on_every_key_release(self) -> None: self.button.hide() + self.timer.stop() From 4abf5ff691a246c4370b5037d3f02770c08e9929 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:15 +0200 Subject: [PATCH 105/125] Update SPDX --- shortcut_composer/__init__.py | 2 +- shortcut_composer/actions.py | 2 +- shortcut_composer/api_krita/__init__.py | 2 +- shortcut_composer/api_krita/actions/__init__.py | 2 +- .../api_krita/actions/transform_actions.py | 2 +- shortcut_composer/api_krita/core_api.py | 2 +- shortcut_composer/api_krita/enums/__init__.py | 2 +- shortcut_composer/api_krita/enums/blending_mode.py | 4 ++-- shortcut_composer/api_krita/enums/node_types.py | 10 ++++++---- shortcut_composer/api_krita/enums/toggle.py | 4 ++-- shortcut_composer/api_krita/enums/tool.py | 2 +- shortcut_composer/api_krita/enums/transform_mode.py | 2 +- shortcut_composer/api_krita/pyqt/__init__.py | 2 +- shortcut_composer/api_krita/pyqt/colorizer.py | 2 +- shortcut_composer/api_krita/pyqt/custom_widgets.py | 3 +++ shortcut_composer/api_krita/pyqt/painter.py | 2 +- shortcut_composer/api_krita/pyqt/pixmap_transform.py | 2 +- shortcut_composer/api_krita/pyqt/round_button.py | 3 +++ shortcut_composer/api_krita/pyqt/text.py | 2 +- shortcut_composer/api_krita/pyqt/timer.py | 3 +++ shortcut_composer/api_krita/wrappers/__init__.py | 2 +- shortcut_composer/api_krita/wrappers/canvas.py | 2 +- shortcut_composer/api_krita/wrappers/cursor.py | 2 +- shortcut_composer/api_krita/wrappers/database.py | 2 +- shortcut_composer/api_krita/wrappers/document.py | 4 ++-- shortcut_composer/api_krita/wrappers/node.py | 6 +++--- .../api_krita/wrappers/tool_descriptor.py | 2 +- shortcut_composer/api_krita/wrappers/view.py | 4 +++- shortcut_composer/composer_utils/__init__.py | 2 +- shortcut_composer/composer_utils/compatibility_fix.py | 3 +++ shortcut_composer/composer_utils/global_config.py | 2 +- shortcut_composer/composer_utils/layouts/__init__.py | 2 +- .../composer_utils/layouts/buttons_layout.py | 2 +- shortcut_composer/composer_utils/settings_dialog.py | 2 +- shortcut_composer/config_system/__init__.py | 2 +- shortcut_composer/config_system/api_krita.py | 3 ++- shortcut_composer/config_system/field.py | 2 +- shortcut_composer/config_system/field_base.py | 2 +- .../config_system/field_implementations.py | 4 +++- shortcut_composer/config_system/parsers.py | 3 ++- shortcut_composer/config_system/ui/__init__.py | 2 +- .../config_system/ui/config_based_widget.py | 2 +- .../config_system/ui/config_form_widget.py | 2 +- shortcut_composer/config_system/ui/widgets.py | 2 +- shortcut_composer/core_components/__init__.py | 2 +- shortcut_composer/core_components/controller_base.py | 2 +- .../core_components/controllers/__init__.py | 2 +- .../core_components/controllers/canvas_controllers.py | 2 +- .../core_components/controllers/core_controllers.py | 2 +- .../controllers/document_controllers.py | 5 ++--- .../core_components/controllers/node_controllers.py | 9 +++++---- .../core_components/controllers/view_controllers.py | 2 +- shortcut_composer/core_components/instruction_base.py | 2 +- .../core_components/instructions/__init__.py | 2 +- .../core_components/instructions/layer_hide.py | 2 +- .../core_components/instructions/set_brush_strategy.py | 2 +- .../core_components/instructions/togglers.py | 2 +- shortcut_composer/core_components/instructions/undo.py | 2 +- shortcut_composer/data_components/__init__.py | 2 +- .../data_components/current_layer_stack.py | 2 +- shortcut_composer/data_components/pick_strategy.py | 2 +- shortcut_composer/data_components/range.py | 2 +- shortcut_composer/data_components/slider.py | 2 +- shortcut_composer/data_components/tag.py | 3 +++ shortcut_composer/input_adapter/__init__.py | 2 +- shortcut_composer/input_adapter/action_manager.py | 2 +- shortcut_composer/input_adapter/api_krita.py | 3 ++- .../input_adapter/complex_action_interface.py | 3 ++- shortcut_composer/input_adapter/event_filter.py | 2 +- shortcut_composer/input_adapter/shortcut_adapter.py | 2 +- shortcut_composer/shortcut_composer.py | 2 +- shortcut_composer/templates/__init__.py | 2 +- shortcut_composer/templates/cursor_tracker.py | 2 +- .../templates/mouse_tracker_utils/__init__.py | 2 +- .../templates/mouse_tracker_utils/axis_trackers.py | 2 +- .../templates/mouse_tracker_utils/mouse_interpreter.py | 2 +- .../templates/mouse_tracker_utils/new_types.py | 2 +- .../templates/mouse_tracker_utils/slider_handler.py | 2 +- .../templates/mouse_tracker_utils/slider_values.py | 2 +- shortcut_composer/templates/multiple_assignment.py | 2 +- .../templates/multiple_assignment_utils/__init__.py | 2 +- .../multiple_assignment_utils/action_values.py | 2 +- .../multiple_assignment_utils/action_values_window.py | 2 +- .../multiple_assignment_utils/settings_handler.py | 2 +- .../templates/multiple_assignment_utils/value_list.py | 2 +- shortcut_composer/templates/pie_menu.py | 2 +- shortcut_composer/templates/pie_menu_utils/__init__.py | 2 +- .../templates/pie_menu_utils/dispatchers.py | 2 ++ shortcut_composer/templates/pie_menu_utils/label.py | 2 +- .../templates/pie_menu_utils/label_widget.py | 2 +- .../pie_menu_utils/label_widget_utils/__init__.py | 2 +- .../label_widget_utils/create_label_widget.py | 2 +- .../label_widget_utils/icon_label_widget.py | 2 +- .../label_widget_utils/image_label_widget.py | 2 +- .../label_widget_utils/text_label_widget.py | 2 +- .../templates/pie_menu_utils/pie_config.py | 2 +- .../templates/pie_menu_utils/pie_manager.py | 2 +- .../templates/pie_menu_utils/pie_style.py | 2 +- .../templates/pie_menu_utils/pie_widget.py | 2 +- .../templates/pie_menu_utils/settings_gui/__init__.py | 3 +++ .../pie_menu_utils/settings_gui/enum_pie_settings.py | 3 +++ .../pie_menu_utils/settings_gui/numeric_pie_config.py | 3 +++ .../pie_menu_utils/settings_gui/pie_settings.py | 3 +++ .../pie_menu_utils/settings_gui/preset_pie_settings.py | 3 +++ .../pie_menu_utils/settings_gui/scroll_area.py | 3 +++ .../templates/pie_menu_utils/widget_utils/__init__.py | 2 +- .../pie_menu_utils/widget_utils/circle_points.py | 2 +- .../templates/pie_menu_utils/widget_utils/edit_mode.py | 3 +++ .../pie_menu_utils/widget_utils/label_holder.py | 2 +- .../pie_menu_utils/widget_utils/pie_button.py | 3 +++ .../pie_menu_utils/widget_utils/pie_painter.py | 2 +- .../pie_menu_utils/widget_utils/widget_holder.py | 2 +- shortcut_composer/templates/raw_instructions.py | 2 +- shortcut_composer/templates/temporary_key.py | 2 +- 114 files changed, 164 insertions(+), 113 deletions(-) diff --git a/shortcut_composer/__init__.py b/shortcut_composer/__init__.py index e77ec860..fb94de0c 100755 --- a/shortcut_composer/__init__.py +++ b/shortcut_composer/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """ diff --git a/shortcut_composer/actions.py b/shortcut_composer/actions.py index 03205a69..574c62e7 100644 --- a/shortcut_composer/actions.py +++ b/shortcut_composer/actions.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """ diff --git a/shortcut_composer/api_krita/__init__.py b/shortcut_composer/api_krita/__init__.py index 327de44f..4a513e89 100644 --- a/shortcut_composer/api_krita/__init__.py +++ b/shortcut_composer/api_krita/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """ diff --git a/shortcut_composer/api_krita/actions/__init__.py b/shortcut_composer/api_krita/actions/__init__.py index 349c2560..96512cb7 100644 --- a/shortcut_composer/api_krita/actions/__init__.py +++ b/shortcut_composer/api_krita/actions/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from .transform_actions import TransformModeActions, TransformModeFinder diff --git a/shortcut_composer/api_krita/actions/transform_actions.py b/shortcut_composer/api_krita/actions/transform_actions.py index 947673a4..af527c95 100644 --- a/shortcut_composer/api_krita/actions/transform_actions.py +++ b/shortcut_composer/api_krita/actions/transform_actions.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import Dict, Optional diff --git a/shortcut_composer/api_krita/core_api.py b/shortcut_composer/api_krita/core_api.py index e4b8fac6..5cb8dae0 100644 --- a/shortcut_composer/api_krita/core_api.py +++ b/shortcut_composer/api_krita/core_api.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from krita import Krita as Api, Extension, qApp diff --git a/shortcut_composer/api_krita/enums/__init__.py b/shortcut_composer/api_krita/enums/__init__.py index 901335d8..113d3df3 100644 --- a/shortcut_composer/api_krita/enums/__init__.py +++ b/shortcut_composer/api_krita/enums/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """Enumerated values used in krita api wrappers.""" diff --git a/shortcut_composer/api_krita/enums/blending_mode.py b/shortcut_composer/api_krita/enums/blending_mode.py index 52443993..7c0cdf31 100644 --- a/shortcut_composer/api_krita/enums/blending_mode.py +++ b/shortcut_composer/api_krita/enums/blending_mode.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from enum import Enum @@ -143,4 +143,4 @@ class BlendingMode(Enum): @property def pretty_name(self): - return self.value \ No newline at end of file + return self.value diff --git a/shortcut_composer/api_krita/enums/node_types.py b/shortcut_composer/api_krita/enums/node_types.py index e2f6fd94..a716c90b 100644 --- a/shortcut_composer/api_krita/enums/node_types.py +++ b/shortcut_composer/api_krita/enums/node_types.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from krita import Krita as Api @@ -6,13 +6,14 @@ from PyQt5.QtGui import QIcon + class NodeType(Enum): """ Contains all known node types in krita. Example usage: `NodeType.PAINT_LAYER` """ - + PAINT_LAYER = "paintlayer" GROUP_LAYER = "grouplayer" FILE_LAYER = "filelayer" @@ -31,7 +32,8 @@ def icon(self) -> QIcon: """Return the icon of this node type.""" icon_name = _ICON_NAME_MAP.get(self, "edit-delete") return Api.instance().icon(icon_name) - + + _ICON_NAME_MAP = { NodeType.PAINT_LAYER: "paintLayer", NodeType.GROUP_LAYER: "groupLayer", @@ -46,4 +48,4 @@ def icon(self) -> QIcon: NodeType.SELECTION_MASK: "selectionMask", NodeType.COLORIZE_MASK: "colorizeMask" } -"""Maps node types to names of their icons.""" \ No newline at end of file +"""Maps node types to names of their icons.""" diff --git a/shortcut_composer/api_krita/enums/toggle.py b/shortcut_composer/api_krita/enums/toggle.py index a550b289..22e3975c 100644 --- a/shortcut_composer/api_krita/enums/toggle.py +++ b/shortcut_composer/api_krita/enums/toggle.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from krita import Krita as Api @@ -31,7 +31,7 @@ class Toggle(Enum): def pretty_name(self) -> str: action: QWidgetAction = Api.instance().action(self.value) return action.text() - + @property def state(self) -> bool: """Return state of checkable krita action called `action_name`.""" diff --git a/shortcut_composer/api_krita/enums/tool.py b/shortcut_composer/api_krita/enums/tool.py index 1ceabb52..3060ad67 100644 --- a/shortcut_composer/api_krita/enums/tool.py +++ b/shortcut_composer/api_krita/enums/tool.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from krita import Krita as Api diff --git a/shortcut_composer/api_krita/enums/transform_mode.py b/shortcut_composer/api_krita/enums/transform_mode.py index c2dd7312..1689bd4c 100644 --- a/shortcut_composer/api_krita/enums/transform_mode.py +++ b/shortcut_composer/api_krita/enums/transform_mode.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from krita import Krita as Api diff --git a/shortcut_composer/api_krita/pyqt/__init__.py b/shortcut_composer/api_krita/pyqt/__init__.py index a94b7364..a340f019 100644 --- a/shortcut_composer/api_krita/pyqt/__init__.py +++ b/shortcut_composer/api_krita/pyqt/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """Wrappers and utilities based on PyQt5 objects.""" diff --git a/shortcut_composer/api_krita/pyqt/colorizer.py b/shortcut_composer/api_krita/pyqt/colorizer.py index 62de22dd..95a4270f 100644 --- a/shortcut_composer/api_krita/pyqt/colorizer.py +++ b/shortcut_composer/api_krita/pyqt/colorizer.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from collections import defaultdict diff --git a/shortcut_composer/api_krita/pyqt/custom_widgets.py b/shortcut_composer/api_krita/pyqt/custom_widgets.py index d53c3281..de9b085f 100644 --- a/shortcut_composer/api_krita/pyqt/custom_widgets.py +++ b/shortcut_composer/api_krita/pyqt/custom_widgets.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + from PyQt5.QtWidgets import QWidget from PyQt5.QtCore import QPoint diff --git a/shortcut_composer/api_krita/pyqt/painter.py b/shortcut_composer/api_krita/pyqt/painter.py index d5420812..bbc5da21 100644 --- a/shortcut_composer/api_krita/pyqt/painter.py +++ b/shortcut_composer/api_krita/pyqt/painter.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later import math diff --git a/shortcut_composer/api_krita/pyqt/pixmap_transform.py b/shortcut_composer/api_krita/pyqt/pixmap_transform.py index 5f9c687f..264448b8 100644 --- a/shortcut_composer/api_krita/pyqt/pixmap_transform.py +++ b/shortcut_composer/api_krita/pyqt/pixmap_transform.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from PyQt5.QtCore import Qt diff --git a/shortcut_composer/api_krita/pyqt/round_button.py b/shortcut_composer/api_krita/pyqt/round_button.py index b3b3fdff..bb94e759 100644 --- a/shortcut_composer/api_krita/pyqt/round_button.py +++ b/shortcut_composer/api_krita/pyqt/round_button.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + from typing import Optional from PyQt5.QtWidgets import QWidget, QPushButton diff --git a/shortcut_composer/api_krita/pyqt/text.py b/shortcut_composer/api_krita/pyqt/text.py index a4d28e0f..347261c2 100644 --- a/shortcut_composer/api_krita/pyqt/text.py +++ b/shortcut_composer/api_krita/pyqt/text.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from dataclasses import dataclass diff --git a/shortcut_composer/api_krita/pyqt/timer.py b/shortcut_composer/api_krita/pyqt/timer.py index fdaffea4..1191e805 100644 --- a/shortcut_composer/api_krita/pyqt/timer.py +++ b/shortcut_composer/api_krita/pyqt/timer.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + from typing import Callable from PyQt5.QtCore import QTimer diff --git a/shortcut_composer/api_krita/wrappers/__init__.py b/shortcut_composer/api_krita/wrappers/__init__.py index ef640ecf..f19927cc 100644 --- a/shortcut_composer/api_krita/wrappers/__init__.py +++ b/shortcut_composer/api_krita/wrappers/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """ diff --git a/shortcut_composer/api_krita/wrappers/canvas.py b/shortcut_composer/api_krita/wrappers/canvas.py index bd54e562..7fa2ccac 100644 --- a/shortcut_composer/api_krita/wrappers/canvas.py +++ b/shortcut_composer/api_krita/wrappers/canvas.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from dataclasses import dataclass diff --git a/shortcut_composer/api_krita/wrappers/cursor.py b/shortcut_composer/api_krita/wrappers/cursor.py index c1e004a1..1df187eb 100644 --- a/shortcut_composer/api_krita/wrappers/cursor.py +++ b/shortcut_composer/api_krita/wrappers/cursor.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from dataclasses import dataclass diff --git a/shortcut_composer/api_krita/wrappers/database.py b/shortcut_composer/api_krita/wrappers/database.py index 80ca6ae0..9f064eb2 100644 --- a/shortcut_composer/api_krita/wrappers/database.py +++ b/shortcut_composer/api_krita/wrappers/database.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from krita import Krita as Api diff --git a/shortcut_composer/api_krita/wrappers/document.py b/shortcut_composer/api_krita/wrappers/document.py index 7617b98d..b8441681 100644 --- a/shortcut_composer/api_krita/wrappers/document.py +++ b/shortcut_composer/api_krita/wrappers/document.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from dataclasses import dataclass @@ -40,7 +40,7 @@ def active_node(self, node: Node) -> None: def create_node(self, name: str, node_type: NodeType) -> Node: """ Create a Node. - + IMPORTANT: Created node must be then added to node tree to be usable from Krita. For example with add_child_node() method of Node Class. diff --git a/shortcut_composer/api_krita/wrappers/node.py b/shortcut_composer/api_krita/wrappers/node.py index 4b17113c..6f7dfb96 100644 --- a/shortcut_composer/api_krita/wrappers/node.py +++ b/shortcut_composer/api_krita/wrappers/node.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from dataclasses import dataclass @@ -38,11 +38,11 @@ class Node(): def add_child_node(self, child: 'Node', above: 'Node') -> bool: """ Add the given node in the list of children. - + Parameters: child - the node to be added above - the node above which this node will be placed - + Returns false if adding the node failed. """ return self.node.addChildNode(child.node, above.node) diff --git a/shortcut_composer/api_krita/wrappers/tool_descriptor.py b/shortcut_composer/api_krita/wrappers/tool_descriptor.py index baae7c91..b28db866 100644 --- a/shortcut_composer/api_krita/wrappers/tool_descriptor.py +++ b/shortcut_composer/api_krita/wrappers/tool_descriptor.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from krita import Krita as Api diff --git a/shortcut_composer/api_krita/wrappers/view.py b/shortcut_composer/api_krita/wrappers/view.py index 915fd2ef..1f92498b 100644 --- a/shortcut_composer/api_krita/wrappers/view.py +++ b/shortcut_composer/api_krita/wrappers/view.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from krita import Krita as Api @@ -11,11 +11,13 @@ class _KritaPreset(Protocol): """Krita `Resource` object API.""" + def name(self) -> str: ... class KritaView(Protocol): """Krita `View` object API.""" + def currentBrushPreset(self) -> _KritaPreset: ... def currentBlendingMode(self) -> str: ... def paintingOpacity(self) -> float: ... diff --git a/shortcut_composer/composer_utils/__init__.py b/shortcut_composer/composer_utils/__init__.py index f1eafe2d..67ffa24a 100644 --- a/shortcut_composer/composer_utils/__init__.py +++ b/shortcut_composer/composer_utils/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """Utilities specific for this plugin. Not directly reusable elsewhere.""" diff --git a/shortcut_composer/composer_utils/compatibility_fix.py b/shortcut_composer/composer_utils/compatibility_fix.py index 660ca268..67495a43 100644 --- a/shortcut_composer/composer_utils/compatibility_fix.py +++ b/shortcut_composer/composer_utils/compatibility_fix.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + from api_krita import Krita diff --git a/shortcut_composer/composer_utils/global_config.py b/shortcut_composer/composer_utils/global_config.py index e2d699ff..b169b8d0 100644 --- a/shortcut_composer/composer_utils/global_config.py +++ b/shortcut_composer/composer_utils/global_config.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from config_system import FieldGroup diff --git a/shortcut_composer/composer_utils/layouts/__init__.py b/shortcut_composer/composer_utils/layouts/__init__.py index a7cbac1e..dc3315a8 100644 --- a/shortcut_composer/composer_utils/layouts/__init__.py +++ b/shortcut_composer/composer_utils/layouts/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """Layouts to put inside the settings dialog.""" diff --git a/shortcut_composer/composer_utils/layouts/buttons_layout.py b/shortcut_composer/composer_utils/layouts/buttons_layout.py index fa148141..b85c6e8d 100644 --- a/shortcut_composer/composer_utils/layouts/buttons_layout.py +++ b/shortcut_composer/composer_utils/layouts/buttons_layout.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import Callable diff --git a/shortcut_composer/composer_utils/settings_dialog.py b/shortcut_composer/composer_utils/settings_dialog.py index 22c096cc..643d3ff4 100644 --- a/shortcut_composer/composer_utils/settings_dialog.py +++ b/shortcut_composer/composer_utils/settings_dialog.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import List diff --git a/shortcut_composer/config_system/__init__.py b/shortcut_composer/config_system/__init__.py index e615b936..6601d3af 100644 --- a/shortcut_composer/config_system/__init__.py +++ b/shortcut_composer/config_system/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from .field import Field, FieldGroup diff --git a/shortcut_composer/config_system/api_krita.py b/shortcut_composer/config_system/api_krita.py index 3862ead8..ba13dc93 100644 --- a/shortcut_composer/config_system/api_krita.py +++ b/shortcut_composer/config_system/api_krita.py @@ -1,5 +1,6 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later + """Required part of api_krita package, so that no dependency is needed.""" from krita import Krita as Api diff --git a/shortcut_composer/config_system/field.py b/shortcut_composer/config_system/field.py index 249a2b3c..f5dce1af 100644 --- a/shortcut_composer/config_system/field.py +++ b/shortcut_composer/config_system/field.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import TypeVar, Generic, Optional, Callable, List diff --git a/shortcut_composer/config_system/field_base.py b/shortcut_composer/config_system/field_base.py index 436037cf..51b29cad 100644 --- a/shortcut_composer/config_system/field_base.py +++ b/shortcut_composer/config_system/field_base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import TypeVar, Generic, Callable, List diff --git a/shortcut_composer/config_system/field_implementations.py b/shortcut_composer/config_system/field_implementations.py index af70f068..4e07ac78 100644 --- a/shortcut_composer/config_system/field_implementations.py +++ b/shortcut_composer/config_system/field_implementations.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import ( @@ -16,6 +16,7 @@ class NonListField(FieldBase, Generic[T]): """Config field containing a basic, non-list value.""" + def __init__( self, config_group: str, @@ -40,6 +41,7 @@ def _to_string(self, value: T) -> str: class ListField(FieldBase, Generic[T]): """Config field containing a list value.""" + def __init__( self, config_group: str, diff --git a/shortcut_composer/config_system/parsers.py b/shortcut_composer/config_system/parsers.py index ae5dd5c7..e160f75e 100644 --- a/shortcut_composer/config_system/parsers.py +++ b/shortcut_composer/config_system/parsers.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import Generic, TypeVar, Type, Protocol @@ -52,6 +52,7 @@ def parse_from(self, value: bool) -> str: class EnumParser(Parser[EnumT]): """Parses from string to enum and vice-versa.""" + def __init__(self, type: Type[EnumT]) -> None: self.type = type diff --git a/shortcut_composer/config_system/ui/__init__.py b/shortcut_composer/config_system/ui/__init__.py index 1e34d1c9..1b87ed01 100644 --- a/shortcut_composer/config_system/ui/__init__.py +++ b/shortcut_composer/config_system/ui/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from .config_based_widget import ConfigBasedWidget diff --git a/shortcut_composer/config_system/ui/config_based_widget.py b/shortcut_composer/config_system/ui/config_based_widget.py index 39db48d5..18b1f4cb 100644 --- a/shortcut_composer/config_system/ui/config_based_widget.py +++ b/shortcut_composer/config_system/ui/config_based_widget.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from abc import ABC, abstractmethod diff --git a/shortcut_composer/config_system/ui/config_form_widget.py b/shortcut_composer/config_system/ui/config_form_widget.py index e9a02338..396a9581 100644 --- a/shortcut_composer/config_system/ui/config_form_widget.py +++ b/shortcut_composer/config_system/ui/config_form_widget.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import List, Union diff --git a/shortcut_composer/config_system/ui/widgets.py b/shortcut_composer/config_system/ui/widgets.py index b4d4a628..dc536d19 100644 --- a/shortcut_composer/config_system/ui/widgets.py +++ b/shortcut_composer/config_system/ui/widgets.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import Any, List, Final, Optional, TypeVar, Generic, Protocol diff --git a/shortcut_composer/core_components/__init__.py b/shortcut_composer/core_components/__init__.py index 53879ed3..b7662a7f 100644 --- a/shortcut_composer/core_components/__init__.py +++ b/shortcut_composer/core_components/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """ diff --git a/shortcut_composer/core_components/controller_base.py b/shortcut_composer/core_components/controller_base.py index f2f67cd8..b206f1c0 100644 --- a/shortcut_composer/core_components/controller_base.py +++ b/shortcut_composer/core_components/controller_base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import Optional, Union, Generic, TypeVar diff --git a/shortcut_composer/core_components/controllers/__init__.py b/shortcut_composer/core_components/controllers/__init__.py index 86b01026..4736ebf7 100644 --- a/shortcut_composer/core_components/controllers/__init__.py +++ b/shortcut_composer/core_components/controllers/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """ diff --git a/shortcut_composer/core_components/controllers/canvas_controllers.py b/shortcut_composer/core_components/controllers/canvas_controllers.py index dbe37381..8b34c980 100644 --- a/shortcut_composer/core_components/controllers/canvas_controllers.py +++ b/shortcut_composer/core_components/controllers/canvas_controllers.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from api_krita import Krita diff --git a/shortcut_composer/core_components/controllers/core_controllers.py b/shortcut_composer/core_components/controllers/core_controllers.py index feb68afb..a5d97602 100644 --- a/shortcut_composer/core_components/controllers/core_controllers.py +++ b/shortcut_composer/core_components/controllers/core_controllers.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import Optional diff --git a/shortcut_composer/core_components/controllers/document_controllers.py b/shortcut_composer/core_components/controllers/document_controllers.py index 4c3b9a71..6c57c9f2 100644 --- a/shortcut_composer/core_components/controllers/document_controllers.py +++ b/shortcut_composer/core_components/controllers/document_controllers.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from api_krita import Krita @@ -31,7 +31,7 @@ def get_value(self) -> Node: def set_value(self, value: Node) -> None: """Set passed node as current.""" self.document.active_node = value - + def get_pretty_name(self, value: Node) -> str: return value.name @@ -54,4 +54,3 @@ def set_value(self, value: int) -> None: def get_label(self, value: int) -> Text: return Text(self.get_pretty_name(value)) - diff --git a/shortcut_composer/core_components/controllers/node_controllers.py b/shortcut_composer/core_components/controllers/node_controllers.py index 711f5fc3..f467de7d 100644 --- a/shortcut_composer/core_components/controllers/node_controllers.py +++ b/shortcut_composer/core_components/controllers/node_controllers.py @@ -1,7 +1,6 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import TypeVar from api_krita import Krita from api_krita.enums import BlendingMode, NodeType from api_krita.pyqt import Text, Colorizer @@ -44,7 +43,8 @@ def get_pretty_name(self, value: float) -> str: return f"{value}%" -class LayerBlendingModeController(NodeBasedController, Controller[BlendingMode]): +class LayerBlendingModeController(NodeBasedController, + Controller[BlendingMode]): """ Gives access to active layers' `blending mode`. @@ -92,7 +92,8 @@ def set_value(self, visibility: bool) -> None: self.active_document.refresh() -class CreateLayerWithBlendingController(NodeBasedController, Controller[BlendingMode]): +class CreateLayerWithBlendingController(NodeBasedController, + Controller[BlendingMode]): """Creates Paint Layer with set Blending Mode.""" default_value = BlendingMode.NORMAL diff --git a/shortcut_composer/core_components/controllers/view_controllers.py b/shortcut_composer/core_components/controllers/view_controllers.py index 7223291e..a2aa73a4 100644 --- a/shortcut_composer/core_components/controllers/view_controllers.py +++ b/shortcut_composer/core_components/controllers/view_controllers.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import Optional diff --git a/shortcut_composer/core_components/instruction_base.py b/shortcut_composer/core_components/instruction_base.py index e0cc5b18..28e412d0 100644 --- a/shortcut_composer/core_components/instruction_base.py +++ b/shortcut_composer/core_components/instruction_base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import List diff --git a/shortcut_composer/core_components/instructions/__init__.py b/shortcut_composer/core_components/instructions/__init__.py index d2eca8c5..ff84e377 100644 --- a/shortcut_composer/core_components/instructions/__init__.py +++ b/shortcut_composer/core_components/instructions/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """ diff --git a/shortcut_composer/core_components/instructions/layer_hide.py b/shortcut_composer/core_components/instructions/layer_hide.py index 15b91f9e..423dae18 100644 --- a/shortcut_composer/core_components/instructions/layer_hide.py +++ b/shortcut_composer/core_components/instructions/layer_hide.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from api_krita import Krita diff --git a/shortcut_composer/core_components/instructions/set_brush_strategy.py b/shortcut_composer/core_components/instructions/set_brush_strategy.py index dddbaadf..94884f79 100644 --- a/shortcut_composer/core_components/instructions/set_brush_strategy.py +++ b/shortcut_composer/core_components/instructions/set_brush_strategy.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from api_krita import Krita diff --git a/shortcut_composer/core_components/instructions/togglers.py b/shortcut_composer/core_components/instructions/togglers.py index af5ae185..ed11211a 100644 --- a/shortcut_composer/core_components/instructions/togglers.py +++ b/shortcut_composer/core_components/instructions/togglers.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from dataclasses import dataclass diff --git a/shortcut_composer/core_components/instructions/undo.py b/shortcut_composer/core_components/instructions/undo.py index 355ee339..8afc6a33 100644 --- a/shortcut_composer/core_components/instructions/undo.py +++ b/shortcut_composer/core_components/instructions/undo.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from api_krita import Krita diff --git a/shortcut_composer/data_components/__init__.py b/shortcut_composer/data_components/__init__.py index 08f211f4..db2012d5 100644 --- a/shortcut_composer/data_components/__init__.py +++ b/shortcut_composer/data_components/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """ diff --git a/shortcut_composer/data_components/current_layer_stack.py b/shortcut_composer/data_components/current_layer_stack.py index 30548e3a..1db0eeaf 100644 --- a/shortcut_composer/data_components/current_layer_stack.py +++ b/shortcut_composer/data_components/current_layer_stack.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import List diff --git a/shortcut_composer/data_components/pick_strategy.py b/shortcut_composer/data_components/pick_strategy.py index a5996cb3..39ac5b3a 100644 --- a/shortcut_composer/data_components/pick_strategy.py +++ b/shortcut_composer/data_components/pick_strategy.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from enum import Enum diff --git a/shortcut_composer/data_components/range.py b/shortcut_composer/data_components/range.py index b3ba93b2..87a97387 100644 --- a/shortcut_composer/data_components/range.py +++ b/shortcut_composer/data_components/range.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from dataclasses import dataclass diff --git a/shortcut_composer/data_components/slider.py b/shortcut_composer/data_components/slider.py index 47467ea8..fbe7f5a7 100644 --- a/shortcut_composer/data_components/slider.py +++ b/shortcut_composer/data_components/slider.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import List, Union, Optional, Generic, TypeVar diff --git a/shortcut_composer/data_components/tag.py b/shortcut_composer/data_components/tag.py index 5e177b3b..28c4c8a9 100644 --- a/shortcut_composer/data_components/tag.py +++ b/shortcut_composer/data_components/tag.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + from typing import List from api_krita.wrappers import Database diff --git a/shortcut_composer/input_adapter/__init__.py b/shortcut_composer/input_adapter/__init__.py index ab128ff4..00224b9c 100644 --- a/shortcut_composer/input_adapter/__init__.py +++ b/shortcut_composer/input_adapter/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """ diff --git a/shortcut_composer/input_adapter/action_manager.py b/shortcut_composer/input_adapter/action_manager.py index d77cc824..0888e51b 100644 --- a/shortcut_composer/input_adapter/action_manager.py +++ b/shortcut_composer/input_adapter/action_manager.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """ diff --git a/shortcut_composer/input_adapter/api_krita.py b/shortcut_composer/input_adapter/api_krita.py index 379eef78..de188e2d 100644 --- a/shortcut_composer/input_adapter/api_krita.py +++ b/shortcut_composer/input_adapter/api_krita.py @@ -1,5 +1,6 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later + """Required part of api_krita package, so that no dependency is needed.""" from krita import Krita as Api diff --git a/shortcut_composer/input_adapter/complex_action_interface.py b/shortcut_composer/input_adapter/complex_action_interface.py index 192180b4..3b6f9452 100644 --- a/shortcut_composer/input_adapter/complex_action_interface.py +++ b/shortcut_composer/input_adapter/complex_action_interface.py @@ -1,5 +1,6 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later + from typing import Protocol diff --git a/shortcut_composer/input_adapter/event_filter.py b/shortcut_composer/input_adapter/event_filter.py index 6879f19f..d597752e 100644 --- a/shortcut_composer/input_adapter/event_filter.py +++ b/shortcut_composer/input_adapter/event_filter.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import Callable, List, Literal diff --git a/shortcut_composer/input_adapter/shortcut_adapter.py b/shortcut_composer/input_adapter/shortcut_adapter.py index d05906bf..5dcc174d 100644 --- a/shortcut_composer/input_adapter/shortcut_adapter.py +++ b/shortcut_composer/input_adapter/shortcut_adapter.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from time import time diff --git a/shortcut_composer/shortcut_composer.py b/shortcut_composer/shortcut_composer.py index d8ec44ff..1f80aa93 100755 --- a/shortcut_composer/shortcut_composer.py +++ b/shortcut_composer/shortcut_composer.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later __version__ = "1.2.0 prealpha" diff --git a/shortcut_composer/templates/__init__.py b/shortcut_composer/templates/__init__.py index fdcdf5b9..fcde9c77 100644 --- a/shortcut_composer/templates/__init__.py +++ b/shortcut_composer/templates/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """ diff --git a/shortcut_composer/templates/cursor_tracker.py b/shortcut_composer/templates/cursor_tracker.py index 58126c1c..1093ac1e 100644 --- a/shortcut_composer/templates/cursor_tracker.py +++ b/shortcut_composer/templates/cursor_tracker.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import List, Optional, Generic, TypeVar diff --git a/shortcut_composer/templates/mouse_tracker_utils/__init__.py b/shortcut_composer/templates/mouse_tracker_utils/__init__.py index 5c832382..c6275236 100644 --- a/shortcut_composer/templates/mouse_tracker_utils/__init__.py +++ b/shortcut_composer/templates/mouse_tracker_utils/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """Implementation of CursorTracker.""" diff --git a/shortcut_composer/templates/mouse_tracker_utils/axis_trackers.py b/shortcut_composer/templates/mouse_tracker_utils/axis_trackers.py index f529fde4..fb6a0f5d 100644 --- a/shortcut_composer/templates/mouse_tracker_utils/axis_trackers.py +++ b/shortcut_composer/templates/mouse_tracker_utils/axis_trackers.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import List, Optional diff --git a/shortcut_composer/templates/mouse_tracker_utils/mouse_interpreter.py b/shortcut_composer/templates/mouse_tracker_utils/mouse_interpreter.py index d9870263..b4583a13 100644 --- a/shortcut_composer/templates/mouse_tracker_utils/mouse_interpreter.py +++ b/shortcut_composer/templates/mouse_tracker_utils/mouse_interpreter.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from dataclasses import dataclass diff --git a/shortcut_composer/templates/mouse_tracker_utils/new_types.py b/shortcut_composer/templates/mouse_tracker_utils/new_types.py index 728f567a..7e1ea183 100644 --- a/shortcut_composer/templates/mouse_tracker_utils/new_types.py +++ b/shortcut_composer/templates/mouse_tracker_utils/new_types.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import NewType diff --git a/shortcut_composer/templates/mouse_tracker_utils/slider_handler.py b/shortcut_composer/templates/mouse_tracker_utils/slider_handler.py index 2b86cb85..5f701a08 100644 --- a/shortcut_composer/templates/mouse_tracker_utils/slider_handler.py +++ b/shortcut_composer/templates/mouse_tracker_utils/slider_handler.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import Callable, Iterable, Generic, TypeVar diff --git a/shortcut_composer/templates/mouse_tracker_utils/slider_values.py b/shortcut_composer/templates/mouse_tracker_utils/slider_values.py index 3d7fe67d..cbd5565f 100644 --- a/shortcut_composer/templates/mouse_tracker_utils/slider_values.py +++ b/shortcut_composer/templates/mouse_tracker_utils/slider_values.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import Any, List, Generic, TypeVar diff --git a/shortcut_composer/templates/multiple_assignment.py b/shortcut_composer/templates/multiple_assignment.py index e3764c44..74f48b8a 100644 --- a/shortcut_composer/templates/multiple_assignment.py +++ b/shortcut_composer/templates/multiple_assignment.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import List, Iterator, TypeVar, Generic, Optional diff --git a/shortcut_composer/templates/multiple_assignment_utils/__init__.py b/shortcut_composer/templates/multiple_assignment_utils/__init__.py index 8ffc0607..f4fecd97 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/__init__.py +++ b/shortcut_composer/templates/multiple_assignment_utils/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """Widgets to display on the settings dialog.""" diff --git a/shortcut_composer/templates/multiple_assignment_utils/action_values.py b/shortcut_composer/templates/multiple_assignment_utils/action_values.py index 39f13fc9..e0f45c35 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/action_values.py +++ b/shortcut_composer/templates/multiple_assignment_utils/action_values.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import List, Type diff --git a/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py b/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py index df49745b..8685f3d9 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py +++ b/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import Type diff --git a/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py b/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py index 656d9ec7..5beac6b2 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py +++ b/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from dataclasses import dataclass diff --git a/shortcut_composer/templates/multiple_assignment_utils/value_list.py b/shortcut_composer/templates/multiple_assignment_utils/value_list.py index 7af1c700..232220c1 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/value_list.py +++ b/shortcut_composer/templates/multiple_assignment_utils/value_list.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import Optional, List diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 084e96e2..32fb1417 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import List, TypeVar, Generic, Optional diff --git a/shortcut_composer/templates/pie_menu_utils/__init__.py b/shortcut_composer/templates/pie_menu_utils/__init__.py index 0e214fea..bf6d8ab4 100644 --- a/shortcut_composer/templates/pie_menu_utils/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """Implementation of PieMenu main elements.""" diff --git a/shortcut_composer/templates/pie_menu_utils/dispatchers.py b/shortcut_composer/templates/pie_menu_utils/dispatchers.py index be34a992..d0203aa3 100644 --- a/shortcut_composer/templates/pie_menu_utils/dispatchers.py +++ b/shortcut_composer/templates/pie_menu_utils/dispatchers.py @@ -1,3 +1,5 @@ +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later from typing import List, TypeVar, Optional from enum import Enum diff --git a/shortcut_composer/templates/pie_menu_utils/label.py b/shortcut_composer/templates/pie_menu_utils/label.py index da48b703..ccff3230 100644 --- a/shortcut_composer/templates/pie_menu_utils/label.py +++ b/shortcut_composer/templates/pie_menu_utils/label.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from api_krita.pyqt import Text diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget.py index c99c32de..435707fb 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from PyQt5.QtCore import Qt, QMimeData, QEvent diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/__init__.py b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/__init__.py index 68760a4e..6d3e69fd 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """Implementation of different LabelWidget types.""" diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/create_label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/create_label_widget.py index a1004a6c..7fee159b 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/create_label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/create_label_widget.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import Type diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/icon_label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/icon_label_widget.py index a44a88cd..cd7fd3ea 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/icon_label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/icon_label_widget.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from PyQt5.QtGui import QPixmap, QIcon diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/image_label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/image_label_widget.py index b6aeeda2..7ac52b93 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/image_label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/image_label_widget.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from PyQt5.QtGui import ( diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/text_label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/text_label_widget.py index 613eac69..01431c14 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/text_label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/text_label_widget.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from PyQt5.QtCore import Qt diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index b3e25e65..2b5347c3 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from abc import ABC, abstractmethod diff --git a/shortcut_composer/templates/pie_menu_utils/pie_manager.py b/shortcut_composer/templates/pie_menu_utils/pie_manager.py index ba01a0b2..9c02d3a9 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_manager.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_manager.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import Optional diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index 9f9c6978..ad854052 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later import math diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index cb4df9c6..b5ea0bac 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import TypeVar, Optional, Generic, List diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py index 49bf49ac..0de26d85 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + from .pie_settings import PieSettings from .enum_pie_settings import EnumPieSettings from .preset_pie_settings import PresetPieSettings diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index f7008935..d098ae2b 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + from typing import List, Optional from PyQt5.QtWidgets import QVBoxLayout, QTabWidget, QWidget diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_config.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_config.py index 50fe5a0d..ae637c85 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_config.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + from typing import Optional from PyQt5.QtWidgets import QWidget diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py index 700566df..f3ee77fd 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + from typing import Optional from PyQt5.QtCore import QPoint, Qt diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py index 0ae97184..b261da3e 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + from typing import List, Optional from PyQt5.QtWidgets import QVBoxLayout diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py index 5fbf2c8d..f32c84a4 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + from typing import List, NamedTuple from PyQt5.QtCore import Qt diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py index fcff489b..bfd3d700 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later """Additional classes used by pie menu components.""" diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/circle_points.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/circle_points.py index 7c8739fe..3272870d 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/circle_points.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/circle_points.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later import math diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py index 0cf9b312..56c05195 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/edit_mode.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + from typing import TYPE_CHECKING if TYPE_CHECKING: diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py index 791fc51c..c70cf7c9 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import List diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_button.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_button.py index 583a7efb..e41af8a4 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_button.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_button.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + from typing import Optional, Callable from PyQt5.QtWidgets import QWidget diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_painter.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_painter.py index 57163477..5c3447af 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_painter.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_painter.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import List diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py index 7e48c65e..11a5601f 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/widget_holder.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import Dict, Iterator diff --git a/shortcut_composer/templates/raw_instructions.py b/shortcut_composer/templates/raw_instructions.py index 86b222a6..3a9db5bf 100644 --- a/shortcut_composer/templates/raw_instructions.py +++ b/shortcut_composer/templates/raw_instructions.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import List, Optional diff --git a/shortcut_composer/templates/temporary_key.py b/shortcut_composer/templates/temporary_key.py index 97271b8d..f5e9d641 100644 --- a/shortcut_composer/templates/temporary_key.py +++ b/shortcut_composer/templates/temporary_key.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: © 2022 Wojciech Trybus +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later from typing import List, TypeVar, Generic, Optional From 1b3e691fb9b27f16390b50c91db8a7ad5a619d3f Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:15 +0200 Subject: [PATCH 106/125] Add label telling which pie setting label was hovered --- .../templates/pie_menu_utils/label_widget.py | 19 +++++++++ .../settings_gui/scroll_area.py | 41 +++++++++++++++---- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget.py index 435707fb..5a0ebc18 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later +from typing import Protocol + from PyQt5.QtCore import Qt, QMimeData, QEvent from PyQt5.QtWidgets import QWidget from PyQt5.QtGui import QDrag, QPixmap, QMouseEvent @@ -10,6 +12,13 @@ from .label import Label +class WidgetInstructions(Protocol): + """Additional logic to do on entering and leaving a widget.""" + + def on_enter(self, label: Label) -> None: ... + def on_leave(self, label: Label) -> None: ... + + class LabelWidget(BaseWidget): """Displays a `label` inside of `widget` using given `style`.""" @@ -32,6 +41,12 @@ def __init__( self._enabled = True self._hovered = False + self._instructions: list[WidgetInstructions] = [] + + def add_instruction(self, instruction: WidgetInstructions): + """Add additional logic to do on entering and leaving widget.""" + self._instructions.append(instruction) + @property def draggable(self) -> bool: """Return whether the label accepts dragging.""" @@ -79,12 +94,16 @@ def mousePressEvent(self, e: QMouseEvent) -> None: def enterEvent(self, e: QEvent) -> None: """Notice that mouse moved over the widget.""" self._hovered = True + for instruction in self._instructions: + instruction.on_enter(self.label) self.repaint() return super().enterEvent(e) def leaveEvent(self, e: QEvent) -> None: """Notice that mouse moved out of the widget.""" self._hovered = False + for instruction in self._instructions: + instruction.on_leave(self.label) self.repaint() return super().leaveEvent(e) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py index f32c84a4..93423404 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py @@ -4,7 +4,12 @@ from typing import List, NamedTuple from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QWidget, QScrollArea, QGridLayout +from PyQt5.QtWidgets import ( + QWidget, + QScrollArea, + QLabel, + QGridLayout, + QVBoxLayout) from ..label import Label from ..label_widget import LabelWidget @@ -12,7 +17,22 @@ from ..pie_style import PieStyle -class ScrollArea(QScrollArea): +class ChildInstruction: + """Logic of displaying widget text in passed QLabel.""" + + def __init__(self, display_label: QLabel) -> None: + self._display_label = display_label + + def on_enter(self, label: Label) -> None: + """Set text of label which was entered with mouse.""" + self._display_label.setText(str(label.value)) + + def on_leave(self, label: Label) -> None: + """Reset text after mouse leaves the widget.""" + self._display_label.setText("") + + +class ScrollArea(QWidget): def __init__( self, values: List[Label], @@ -21,19 +41,25 @@ def __init__( parent=None ) -> None: super().__init__(parent) - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) - self.setWidgetResizable(True) + self._area = QScrollArea() + self._area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self._area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) + self._area.setWidgetResizable(True) self._style = style self.labels = values self._scroll_area_layout = ScrollAreaLayout(columns, self) + self._active_label_display = QLabel(self) self._children_list = self._create_children() + layout = QVBoxLayout() scroll_widget = QWidget() scroll_widget.setLayout(self._scroll_area_layout) - self.setWidget(scroll_widget) + self._area.setWidget(scroll_widget) + layout.addWidget(self._area) + layout.addWidget(self._active_label_display) + self.setLayout(layout) def _create_children(self) -> List[LabelWidget]: """Create LabelWidgets that represent the labels.""" @@ -47,8 +73,9 @@ def _create_children(self) -> List[LabelWidget]: is_unscaled=True) child.setFixedSize(child.icon_radius*2, child.icon_radius*2) child.draggable = True + child.add_instruction(ChildInstruction(self._active_label_display)) children.append(child) - self._scroll_area_layout.append(children[-1]) + self._scroll_area_layout.append(child) return children From 72e9a74ba3d32d0f02bb41e47d96c73266ce0cb1 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:15 +0200 Subject: [PATCH 107/125] Fix pretty names and use them in display label --- shortcut_composer/api_krita/enums/blending_mode.py | 2 +- shortcut_composer/api_krita/enums/node_types.py | 4 ++++ shortcut_composer/api_krita/enums/toggle.py | 6 ++---- shortcut_composer/api_krita/enums/tool.py | 6 ++---- shortcut_composer/api_krita/enums/transform_mode.py | 11 +---------- .../core_components/controllers/view_controllers.py | 4 ++-- shortcut_composer/templates/pie_menu.py | 5 ++++- shortcut_composer/templates/pie_menu_utils/label.py | 1 + .../pie_menu_utils/settings_gui/scroll_area.py | 2 +- 9 files changed, 18 insertions(+), 23 deletions(-) diff --git a/shortcut_composer/api_krita/enums/blending_mode.py b/shortcut_composer/api_krita/enums/blending_mode.py index 7c0cdf31..686dff87 100644 --- a/shortcut_composer/api_krita/enums/blending_mode.py +++ b/shortcut_composer/api_krita/enums/blending_mode.py @@ -143,4 +143,4 @@ class BlendingMode(Enum): @property def pretty_name(self): - return self.value + return self.name diff --git a/shortcut_composer/api_krita/enums/node_types.py b/shortcut_composer/api_krita/enums/node_types.py index a716c90b..92652559 100644 --- a/shortcut_composer/api_krita/enums/node_types.py +++ b/shortcut_composer/api_krita/enums/node_types.py @@ -33,6 +33,10 @@ def icon(self) -> QIcon: icon_name = _ICON_NAME_MAP.get(self, "edit-delete") return Api.instance().icon(icon_name) + @property + def pretty_name(self): + return self.name + _ICON_NAME_MAP = { NodeType.PAINT_LAYER: "paintLayer", diff --git a/shortcut_composer/api_krita/enums/toggle.py b/shortcut_composer/api_krita/enums/toggle.py index 22e3975c..ae08e32b 100644 --- a/shortcut_composer/api_krita/enums/toggle.py +++ b/shortcut_composer/api_krita/enums/toggle.py @@ -3,7 +3,6 @@ from krita import Krita as Api from enum import Enum -from PyQt5.QtWidgets import QWidgetAction class Toggle(Enum): @@ -28,9 +27,8 @@ class Toggle(Enum): SNAP_TO_GRID = "view_snap_to_grid" @property - def pretty_name(self) -> str: - action: QWidgetAction = Api.instance().action(self.value) - return action.text() + def pretty_name(self): + return self.name @property def state(self) -> bool: diff --git a/shortcut_composer/api_krita/enums/tool.py b/shortcut_composer/api_krita/enums/tool.py index 3060ad67..8f6149c5 100644 --- a/shortcut_composer/api_krita/enums/tool.py +++ b/shortcut_composer/api_krita/enums/tool.py @@ -5,7 +5,6 @@ from enum import Enum from PyQt5.QtGui import QIcon -from PyQt5.QtWidgets import QWidgetAction class Tool(Enum): @@ -69,9 +68,8 @@ def icon(self) -> QIcon: return Api.instance().icon(icon_name) @property - def pretty_name(self) -> str: - action: QWidgetAction = Api.instance().action(self.value) - return action.text() + def pretty_name(self): + return self.name _PAINTABLE = { diff --git a/shortcut_composer/api_krita/enums/transform_mode.py b/shortcut_composer/api_krita/enums/transform_mode.py index 1689bd4c..7132e3ce 100644 --- a/shortcut_composer/api_krita/enums/transform_mode.py +++ b/shortcut_composer/api_krita/enums/transform_mode.py @@ -40,18 +40,9 @@ def icon(self) -> QIcon: @property def pretty_name(self) -> str: - return _PRETTY_NAME_MAP[self] + return self.name -_PRETTY_NAME_MAP = { - TransformMode.FREE: "Transform mode: free", - TransformMode.PERSPECTIVE: "Transform mode: perspective", - TransformMode.WARP: "Transform mode: warp", - TransformMode.CAGE: "Transform mode: cage", - TransformMode.LIQUIFY: "Transform mode: liquify", - TransformMode.MESH: "Transform mode: mesh", -} - _ICON_NAME_MAP = { TransformMode.FREE: "krita_tool_transform", TransformMode.PERSPECTIVE: "transform_icons_perspective", diff --git a/shortcut_composer/core_components/controllers/view_controllers.py b/shortcut_composer/core_components/controllers/view_controllers.py index a2aa73a4..c03b32cb 100644 --- a/shortcut_composer/core_components/controllers/view_controllers.py +++ b/shortcut_composer/core_components/controllers/view_controllers.py @@ -88,8 +88,8 @@ def set_value(self, value: BlendingMode) -> None: def get_label(self, value: BlendingMode) -> Text: return Text(value.name[:3], Colorizer.blending_mode(value)) - def get_pretty_name(self, value: float) -> str: - return f"{round(value)}px" + def get_pretty_name(self, value: BlendingMode) -> str: + return value.pretty_name class OpacityController(ViewBasedController, Controller[int]): diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 32fb1417..f3ecb901 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -172,7 +172,10 @@ def _reset_labels( for value in values: label = self._controller.get_label(value) if label is not None: - label_list.append(Label(value=value, display_value=label)) + label_list.append(Label( + value=value, + display_value=label, + pretty_name=self._controller.get_pretty_name(value))) @staticmethod def _get_all_values(values: List[T]) -> List[T]: diff --git a/shortcut_composer/templates/pie_menu_utils/label.py b/shortcut_composer/templates/pie_menu_utils/label.py index ccff3230..5dbe85c4 100644 --- a/shortcut_composer/templates/pie_menu_utils/label.py +++ b/shortcut_composer/templates/pie_menu_utils/label.py @@ -31,6 +31,7 @@ class Label(Generic[T]): center: QPoint = QPoint(0, 0) angle: int = 0 display_value: Union[QPixmap, QIcon, Text, None] = None + pretty_name: str = "" def __post_init__(self) -> None: self.activation_progress = AnimationProgress(speed_scale=1, steep=1) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py index 93423404..1218bb6a 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py @@ -25,7 +25,7 @@ def __init__(self, display_label: QLabel) -> None: def on_enter(self, label: Label) -> None: """Set text of label which was entered with mouse.""" - self._display_label.setText(str(label.value)) + self._display_label.setText(str(label.pretty_name)) def on_leave(self, label: Label) -> None: """Reset text after mouse leaves the widget.""" From ee73d674a914e10e779ae234805e40629354a9a1 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:15 +0200 Subject: [PATCH 108/125] Spawn setting button under mouse, settings title name --- .../templates/multiple_assignment_utils/settings_handler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py b/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py index 5beac6b2..755fe907 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py +++ b/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py @@ -5,7 +5,7 @@ from typing import List from enum import Enum -from PyQt5.QtGui import QColor +from PyQt5.QtGui import QColor, QCursor from api_krita import Krita from api_krita.pyqt import RoundButton, Timer @@ -31,6 +31,7 @@ def __init__( return self._settings = ActionValuesWindow(type(to_cycle[0]), self.values) + self._settings.setWindowTitle(f"Configure: {name}") self._settings_button = RoundButton( icon=Krita.get_icon("properties"), @@ -64,6 +65,7 @@ def on_key_press(self) -> None: def timer_callback(self): if not self.settings.isVisible(): + self.button.move_center(QCursor().pos()) self.button.show() self.timer.stop() From edeba75d5254c5c25701cb1416904596589ad198 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:15 +0200 Subject: [PATCH 109/125] Bugfix: brush size action definition got corrupted --- shortcut_composer/actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shortcut_composer/actions.py b/shortcut_composer/actions.py index 574c62e7..9ce87b39 100644 --- a/shortcut_composer/actions.py +++ b/shortcut_composer/actions.py @@ -147,7 +147,7 @@ def create_actions() -> List[templates.RawInstructions]: return [ templates.CursorTracker( name="Scroll brush size or opacity", horizontal_slider=Slider( - controller=controllers.FlowController(), + controller=controllers.BrushSizeController(), values=[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 20, 25, 30, 35, 40, 50, 60, 70, 80, From f3c305e0c1b13bb2a67ceab4a9c177d1da35f7d6 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:15 +0200 Subject: [PATCH 110/125] Bugfix: labels losing positions after fetching presets failed --- shortcut_composer/templates/pie_menu.py | 15 ++++++++------- .../pie_menu_utils/widget_utils/label_holder.py | 14 +++++++------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index f3ecb901..899359c0 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -86,6 +86,7 @@ def __init__( background_color=background_color, active_color=active_color) + self._last_values: List[T] = [] self._labels: List[Label] = [] self._reset_labels(self._labels, self._config.values()) self._all_labels: List[Label] = [] @@ -135,9 +136,12 @@ def on_key_press(self) -> None: """Reload labels, start GUI manager and run instructions.""" self._controller.refresh() - values = self._config.values() - self._reset_labels(self._labels, values) - self._config.ORDER.write(values) + new_values = self._config.values() + if self._last_values != new_values: + self._reset_labels(self._labels, new_values) + self._last_values = new_values + self.pie_widget.label_holder.reset() # HACK: should be automatic + self._move_buttons() self.pie_manager.start() @@ -164,10 +168,7 @@ def _reset_labels( label_list: List[Label[T]], values: List[T] ) -> None: - """Replace list values with newly created labels if changed.""" - if [label.value for label in label_list] == values: - return - + """Replace list values with newly created labels.""" label_list.clear() for value in values: label = self._controller.get_label(value) diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py index c70cf7c9..293238c3 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/label_holder.py @@ -35,21 +35,21 @@ def __init__( self._config = config # Refresh itself when config changed, but do not notify change # in config as holder was not their cause - self._config.register_callback(partial(self._reset, notify=False)) + self._config.register_callback(partial(self.reset, notify=False)) self._owner = owner self.widget_holder = WidgetHolder() - self._reset(notify=False) + self.reset(notify=False) def append(self, label: Label): """Append the new label to the holder.""" self._labels.append(label) - self._reset() + self.reset() def insert(self, index: int, label: Label): """Insert the new label to the holder at given index.""" self._labels.insert(index, label) - self._reset() + self.reset() def remove(self, label: Label): """Remove the label from the holder.""" @@ -57,7 +57,7 @@ def remove(self, label: Label): and len(self._labels) > 1 and self._config.ALLOW_REMOVE): self._labels.remove(label) - self._reset() + self.reset() def index(self, label: Label): """Return the index at which the label is stored.""" @@ -71,13 +71,13 @@ def swap(self, _a: Label, _b: Label): self._labels[idx_b] = _a self._labels[idx_a] = _b - self._reset() + self.reset() def __iter__(self): """Iterate over all labels in the holder.""" return iter(self._labels) - def _reset(self, notify: bool = True) -> None: + def reset(self, notify: bool = True) -> None: """ Ensure the icon widgets properly represet this container. From bdc30e0b81b708c6d39dad8e67406ebb0ed0b410 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 17:33:15 +0200 Subject: [PATCH 111/125] Docstrings and small codestyle fixes --- .../api_krita/actions/transform_actions.py | 1 + shortcut_composer/composer_utils/__init__.py | 3 +- .../{layouts => }/buttons_layout.py | 0 .../composer_utils/compatibility_fix.py | 1 + .../composer_utils/layouts/__init__.py | 8 -- .../composer_utils/settings_dialog.py | 2 +- shortcut_composer/config_system/__init__.py | 13 ++- shortcut_composer/config_system/field.py | 58 +++-------- shortcut_composer/config_system/field_base.py | 1 + .../config_system/field_group.py | 55 +++++++++++ .../config_system/ui/__init__.py | 15 +++ shortcut_composer/config_system/ui/widgets.py | 2 + .../core_components/controller_base.py | 1 + .../controllers/canvas_controllers.py | 14 ++- .../controllers/core_controllers.py | 7 ++ .../controllers/document_controllers.py | 4 + .../controllers/node_controllers.py | 6 ++ .../controllers/view_controllers.py | 13 +++ .../core_components/instructions/undo.py | 1 + shortcut_composer/data_components/tag.py | 7 +- .../templates/multiple_assignment.py | 14 ++- .../action_values.py | 36 +++++-- .../action_values_window.py | 31 +++--- .../settings_handler.py | 65 ++++++------ .../multiple_assignment_utils/value_list.py | 6 +- .../templates/pie_menu_utils/label.py | 1 + .../templates/pie_menu_utils/label_widget.py | 16 +-- .../label_widget_utils/create_label_widget.py | 2 +- .../templates/pie_menu_utils/pie_widget.py | 11 ++- .../settings_gui/enum_pie_settings.py | 2 +- .../settings_gui/pie_settings.py | 2 +- .../settings_gui/scroll_area.py | 98 ++++++++++++++----- .../templates/raw_instructions.py | 5 +- 33 files changed, 336 insertions(+), 165 deletions(-) rename shortcut_composer/composer_utils/{layouts => }/buttons_layout.py (100%) delete mode 100644 shortcut_composer/composer_utils/layouts/__init__.py create mode 100644 shortcut_composer/config_system/field_group.py diff --git a/shortcut_composer/api_krita/actions/transform_actions.py b/shortcut_composer/api_krita/actions/transform_actions.py index af527c95..9e0b5b0f 100644 --- a/shortcut_composer/api_krita/actions/transform_actions.py +++ b/shortcut_composer/api_krita/actions/transform_actions.py @@ -14,6 +14,7 @@ from ..enums import Tool, TransformMode from ..core_api import KritaInstance + Krita = KritaInstance() diff --git a/shortcut_composer/composer_utils/__init__.py b/shortcut_composer/composer_utils/__init__.py index 67ffa24a..ea41dc79 100644 --- a/shortcut_composer/composer_utils/__init__.py +++ b/shortcut_composer/composer_utils/__init__.py @@ -4,6 +4,7 @@ """Utilities specific for this plugin. Not directly reusable elsewhere.""" from .settings_dialog import SettingsDialog +from .buttons_layout import ButtonsLayout from .global_config import Config -__all__ = ["SettingsDialog", "Config"] +__all__ = ["SettingsDialog", "ButtonsLayout", "Config"] diff --git a/shortcut_composer/composer_utils/layouts/buttons_layout.py b/shortcut_composer/composer_utils/buttons_layout.py similarity index 100% rename from shortcut_composer/composer_utils/layouts/buttons_layout.py rename to shortcut_composer/composer_utils/buttons_layout.py diff --git a/shortcut_composer/composer_utils/compatibility_fix.py b/shortcut_composer/composer_utils/compatibility_fix.py index 67495a43..ef22ddf2 100644 --- a/shortcut_composer/composer_utils/compatibility_fix.py +++ b/shortcut_composer/composer_utils/compatibility_fix.py @@ -5,6 +5,7 @@ def fix_config(): + """Rewrites config values from their position in 1.1.1 to 1.2.0.""" def fix(group: str, old_name: str, new_name: str): if Krita.read_setting(group, new_name, "not given") != "not given": return diff --git a/shortcut_composer/composer_utils/layouts/__init__.py b/shortcut_composer/composer_utils/layouts/__init__.py deleted file mode 100644 index dc3315a8..00000000 --- a/shortcut_composer/composer_utils/layouts/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus -# SPDX-License-Identifier: GPL-3.0-or-later - -"""Layouts to put inside the settings dialog.""" - -from .buttons_layout import ButtonsLayout - -__all__ = ["ButtonsLayout"] diff --git a/shortcut_composer/composer_utils/settings_dialog.py b/shortcut_composer/composer_utils/settings_dialog.py index 643d3ff4..0d1ddd56 100644 --- a/shortcut_composer/composer_utils/settings_dialog.py +++ b/shortcut_composer/composer_utils/settings_dialog.py @@ -11,7 +11,7 @@ from api_krita.wrappers import Database from config_system.ui import ConfigFormWidget, ConfigSpinBox from .global_config import Config -from .layouts import ButtonsLayout +from .buttons_layout import ButtonsLayout class SettingsDialog(QDialog): diff --git a/shortcut_composer/config_system/__init__.py b/shortcut_composer/config_system/__init__.py index 6601d3af..aa3542bc 100644 --- a/shortcut_composer/config_system/__init__.py +++ b/shortcut_composer/config_system/__init__.py @@ -1,6 +1,17 @@ # SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from .field import Field, FieldGroup +""" +System granting easier API to control krita configuration in .kritarc. + +Consists of two classes: "Field" and "FieldGroup". +Read the documentation of those classes for more info. + +Holds a subpackage with ui elements dependent on the introducad +configuration concept. +""" + +from .field import Field +from .field_group import FieldGroup __all__ = ["Field", "FieldGroup"] diff --git a/shortcut_composer/config_system/field.py b/shortcut_composer/config_system/field.py index f5dce1af..cc0fb18d 100644 --- a/shortcut_composer/config_system/field.py +++ b/shortcut_composer/config_system/field.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import TypeVar, Generic, Optional, Callable, List +from typing import TypeVar, Generic, Optional, Callable T = TypeVar('T') @@ -13,9 +13,20 @@ class Field(Generic[T]): Fields are type aware, and allow to read and write from krita config automatically parsing to the value type. - Type is determined based on default value passed on initialization. + Type of default value passed on initlization is remembered, and used + to parse values both on read and write. Supported types are: + -`int`, + -`float`, + -`str`, + -`bool`, + -`custom Enums` + and homogeneous lists of every type above. + For empty, homogeneous lists, `parser_type` must be used to determine type of list elements. + + Callbacks can be registered to the field, to run a method each time + the value changes. Repeated saves of the same value are filtered. """ def __new__( @@ -30,7 +41,7 @@ def __new__( cls.original = super().__new__ if isinstance(default, list): return ListField(config_group, name, default, parser_type) - return NonListField(config_group, name, default, parser_type) + return NonListField(config_group, name, default) config_group: str """Configuration section in .kritarc toml file.""" @@ -52,44 +63,3 @@ def register_callback(self, callback: Callable[[], None]) -> None: def reset_default(self) -> None: """Write a default value to .kritarc file.""" ... - - -class FieldGroup: - """ - Representation of section in .kritarc toml file. - - All fields in the group must be created using `field()` method. - """ - - def __init__(self, name: str) -> None: - self.name = name - self._fields: List[Field] = [] - self._callbacks: List[Callable[[], None]] = [] - - def field( - self, - name: str, - default: T, - passed_type: Optional[type] = None - ) -> Field[T]: - """Create and return a new field in the group.""" - field = Field(self.name, name, default, passed_type) - self._fields.append(field) - for callback in self._callbacks: - field.register_callback(callback) - return field - - def reset_default(self): - """Reset values of all fields stored in this group.""" - for field in self._fields: - field.reset_default() - - def register_callback(self, callback: Callable[[], None]): - """Register a callback on every past and future field in group.""" - self._callbacks.append(callback) - for field in self._fields: - field.register_callback(callback) - - def __iter__(self): - """Iterate over all fields in the group.""" - return iter(self._fields) diff --git a/shortcut_composer/config_system/field_base.py b/shortcut_composer/config_system/field_base.py index 51b29cad..77dd4bbd 100644 --- a/shortcut_composer/config_system/field_base.py +++ b/shortcut_composer/config_system/field_base.py @@ -16,6 +16,7 @@ class FieldBase(ABC, Field, Generic[T]): """Implementation base of List, and NonList field.""" + def __new__(cls, *args, **kwargs) -> 'FieldBase[T]': obj = object.__new__(cls) obj.__init__(*args, **kwargs) diff --git a/shortcut_composer/config_system/field_group.py b/shortcut_composer/config_system/field_group.py new file mode 100644 index 00000000..42dbb25c --- /dev/null +++ b/shortcut_composer/config_system/field_group.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import TypeVar, Optional, Callable, List +from .field import Field + +T = TypeVar('T') + + +class FieldGroup: + """ + Representation of section in .kritarc toml file. + + All fields in the group should be created using `field()` method. + It simplifies the field creation by auto-completing the group name. + + FieldGroup holds and aggregates fields created with it. + + Allows to reset all the fields at once, as well as register a + callback to all existing fields, as well as those that will be + added to the group in the future. + """ + + def __init__(self, name: str) -> None: + self.name = name + self._fields: List[Field] = [] + self._callbacks: List[Callable[[], None]] = [] + + def field( + self, + name: str, + default: T, + passed_type: Optional[type] = None + ) -> Field[T]: + """Create and return a new field in the group.""" + field = Field(self.name, name, default, passed_type) + self._fields.append(field) + for callback in self._callbacks: + field.register_callback(callback) + return field + + def reset_default(self): + """Reset values of all fields stored in this group.""" + for field in self._fields: + field.reset_default() + + def register_callback(self, callback: Callable[[], None]): + """Register a callback on every past and future field in group.""" + self._callbacks.append(callback) + for field in self._fields: + field.register_callback(callback) + + def __iter__(self): + """Iterate over all fields in the group.""" + return iter(self._fields) diff --git a/shortcut_composer/config_system/ui/__init__.py b/shortcut_composer/config_system/ui/__init__.py index 1b87ed01..43b7961e 100644 --- a/shortcut_composer/config_system/ui/__init__.py +++ b/shortcut_composer/config_system/ui/__init__.py @@ -1,6 +1,21 @@ # SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later +""" +UI elements based on the config concept introduced in parent package. + +Grants QWidgets paired with configuration fields. +All widgets have unified interface for reading and setting their values. + +They also allow to directly fill them with values from .kritarc, save +their current values to .kritarc, and reset them to default values of +fields they hold. + +ConfigFormWidget is a class that aggregates multiple input widgets, +display them in a form, and allow to perform actions with all of them at +once using their unified interface. +""" + from .config_based_widget import ConfigBasedWidget from .config_form_widget import ConfigFormWidget from .widgets import ConfigComboBox, ConfigSpinBox diff --git a/shortcut_composer/config_system/ui/widgets.py b/shortcut_composer/config_system/ui/widgets.py index dc536d19..021aef0c 100644 --- a/shortcut_composer/config_system/ui/widgets.py +++ b/shortcut_composer/config_system/ui/widgets.py @@ -11,6 +11,8 @@ class SpinBox(Protocol, Generic[F]): + """Representation of both Qt spinboxes as one generic class.""" + def value(self) -> F: ... def setValue(self, val: F) -> None: ... diff --git a/shortcut_composer/core_components/controller_base.py b/shortcut_composer/core_components/controller_base.py index b206f1c0..ace7f9de 100644 --- a/shortcut_composer/core_components/controller_base.py +++ b/shortcut_composer/core_components/controller_base.py @@ -30,4 +30,5 @@ def get_label(self, value: T) -> Union[Text, QPixmap, QIcon, None]: ... def get_pretty_name(self, value: T) -> str: + """Get value name that can be displayed to the user in GUI.""" return str(value) diff --git a/shortcut_composer/core_components/controllers/canvas_controllers.py b/shortcut_composer/core_components/controllers/canvas_controllers.py index 8b34c980..0ed025f6 100644 --- a/shortcut_composer/core_components/controllers/canvas_controllers.py +++ b/shortcut_composer/core_components/controllers/canvas_controllers.py @@ -16,25 +16,29 @@ def refresh(self): class CanvasZoomController(CanvasBasedController, Controller[float]): """ - Gives access to `zoom`. + Gives access to `zoom` in %. - Operates on `float` - - Defaults to `1.0` + - Defaults to `100` """ default_value: float = 100.0 def get_value(self) -> float: + """Get current zoom level in %""" return self.canvas.zoom def set_value(self, value: float) -> None: + """Set current zoom level in %""" self.canvas.zoom = value def get_label(self, value: float) -> Text: + """Return Text with formatted canvas zoom.""" return Text(self.get_pretty_name(value)) def get_pretty_name(self, value: float) -> str: - return f"{round(value/100, 2)}" + """Format the canvas zoom like: `100%`.""" + return f"{round(value)}%" class CanvasRotationController(CanvasBasedController, Controller[float]): @@ -49,13 +53,17 @@ class CanvasRotationController(CanvasBasedController, Controller[float]): default_value: float = 0.0 def get_value(self) -> float: + """Get canvas rotation in degrees.""" return self.canvas.rotation def set_value(self, value: float) -> None: + """Set rotation in degrees.""" self.canvas.rotation = value def get_label(self, value: float) -> Text: + """Return Text with formatted canvas rotation.""" return Text(self.get_pretty_name(value)) def get_pretty_name(self, value: float) -> str: + """Format the canvas rotation like: `30°`.""" return f"{round(value)}°" diff --git a/shortcut_composer/core_components/controllers/core_controllers.py b/shortcut_composer/core_components/controllers/core_controllers.py index a5d97602..a7daba25 100644 --- a/shortcut_composer/core_components/controllers/core_controllers.py +++ b/shortcut_composer/core_components/controllers/core_controllers.py @@ -33,9 +33,11 @@ def set_value(value: Tool) -> None: Krita.active_tool = value def get_label(self, value: Tool) -> QIcon: + """Forward the tools' icon.""" return value.icon def get_pretty_name(self, value: Tool) -> str: + """Forward enums' pretty name.""" return value.pretty_name @@ -65,9 +67,11 @@ def set_value(value: Optional[TransformMode]) -> None: value.activate() def get_label(self, value: Tool) -> QIcon: + """Forward the transform mode icon.""" return value.icon def get_pretty_name(self, value: Tool) -> str: + """Forward enums' pretty name.""" return value.pretty_name @@ -85,12 +89,15 @@ class ToggleController(Controller[bool]): default_value = False def get_value(self) -> bool: + """Return whether the toggle action is on.""" return self.toggle.state def set_value(self, value: bool) -> None: + """Set the toggle action on or off using a bool.""" self.toggle.state = value def get_pretty_name(self, value: Tool) -> str: + """Forward enums' pretty name.""" return value.pretty_name diff --git a/shortcut_composer/core_components/controllers/document_controllers.py b/shortcut_composer/core_components/controllers/document_controllers.py index 6c57c9f2..9653be6f 100644 --- a/shortcut_composer/core_components/controllers/document_controllers.py +++ b/shortcut_composer/core_components/controllers/document_controllers.py @@ -33,6 +33,7 @@ def set_value(self, value: Node) -> None: self.document.active_node = value def get_pretty_name(self, value: Node) -> str: + """Forward enums' pretty name.""" return value.name @@ -47,10 +48,13 @@ class TimeController(DocumentBasedController, Controller[int]): default_value = 0 def get_value(self) -> int: + """Get current frame on animation timeline.""" return self.document.current_time def set_value(self, value: int) -> None: + """Set passed frame of animation timeline as active.""" self.document.current_time = value def get_label(self, value: int) -> Text: + """Return Text with frame id as string.""" return Text(self.get_pretty_name(value)) diff --git a/shortcut_composer/core_components/controllers/node_controllers.py b/shortcut_composer/core_components/controllers/node_controllers.py index f467de7d..d72db10a 100644 --- a/shortcut_composer/core_components/controllers/node_controllers.py +++ b/shortcut_composer/core_components/controllers/node_controllers.py @@ -37,9 +37,11 @@ def set_value(self, opacity: int) -> None: self.active_document.refresh() def get_label(self, value: int) -> Text: + """Return Text with formatted layer opacity.""" return Text(self.get_pretty_name(value), Colorizer.percentage(value)) def get_pretty_name(self, value: float) -> str: + """Format the layer opacity like: `100%`""" return f"{value}%" @@ -65,9 +67,11 @@ def set_value(self, blending_mode: BlendingMode) -> None: self.active_document.refresh() def get_label(self, value: BlendingMode) -> Text: + """Return Label of 3 first letters of mode name in correct color.""" return Text(value.name[:3], Colorizer.blending_mode(value)) def get_pretty_name(self, value: BlendingMode) -> str: + """Forward enums' pretty name.""" return value.pretty_name @@ -112,7 +116,9 @@ def set_value(self, blending_mode: BlendingMode) -> None: parent.add_child_node(layer, self.active_node) def get_label(self, value: BlendingMode) -> Text: + """Return Label of 3 first letters of mode name in correct color.""" return Text("+" + value.name[:3], Colorizer.blending_mode(value)) def get_pretty_name(self, value: BlendingMode) -> str: + """Forward enums' pretty name.""" return value.pretty_name diff --git a/shortcut_composer/core_components/controllers/view_controllers.py b/shortcut_composer/core_components/controllers/view_controllers.py index c03b32cb..7ba9656c 100644 --- a/shortcut_composer/core_components/controllers/view_controllers.py +++ b/shortcut_composer/core_components/controllers/view_controllers.py @@ -36,6 +36,7 @@ def set_value(self, value: str) -> None: self.view.brush_preset = value def get_label(self, value: str) -> Optional[QPixmap]: + """Return the preset icon or None, when there preset name unknown.""" try: image: QImage = Krita.get_presets()[value].image() except KeyError: @@ -55,15 +56,19 @@ class BrushSizeController(ViewBasedController, Controller[int]): default_value: float = 100 def get_value(self) -> float: + """Get current brush size.""" return self.view.brush_size def set_value(self, value: float) -> None: + """Set current brush size.""" self.view.brush_size = value def get_label(self, value: float) -> Text: + """Return Text with formatted brush size.""" return Text(self.get_pretty_name(value)) def get_pretty_name(self, value: float) -> str: + """Format the brush size like: `100px`""" return f"{round(value)}px" @@ -86,9 +91,11 @@ def set_value(self, value: BlendingMode) -> None: self.view.blending_mode = value def get_label(self, value: BlendingMode) -> Text: + """Return Label of 3 first letters of mode name in correct color.""" return Text(value.name[:3], Colorizer.blending_mode(value)) def get_pretty_name(self, value: BlendingMode) -> str: + """Forward enums' pretty name.""" return value.pretty_name @@ -111,9 +118,11 @@ def set_value(self, value: int) -> None: self.view.opacity = value def get_label(self, value: int) -> Text: + """Return Text with formatted brush opacity.""" return Text(self.get_pretty_name(value), Colorizer.percentage(value)) def get_pretty_name(self, value: float) -> str: + """Format the opacity like: `100%`""" return f"{value}%" @@ -128,13 +137,17 @@ class FlowController(ViewBasedController, Controller[int]): default_value: int = 100 def get_value(self) -> int: + """Get current brush flow.""" return self.view.flow def set_value(self, value: int) -> None: + """Set passed brush flow.""" self.view.flow = value def get_label(self, value: int) -> Text: + """Return Text with formatted brush flow.""" return Text(self.get_pretty_name(value), Colorizer.percentage(value)) def get_pretty_name(self, value: float) -> str: + """Format the flow like: `100%`""" return f"{value}%" diff --git a/shortcut_composer/core_components/instructions/undo.py b/shortcut_composer/core_components/instructions/undo.py index 8afc6a33..7b6cf615 100644 --- a/shortcut_composer/core_components/instructions/undo.py +++ b/shortcut_composer/core_components/instructions/undo.py @@ -16,4 +16,5 @@ class UndoOnPress(Instruction): """ def on_key_press(self) -> None: + """Trigger the undo action.""" Krita.trigger_action("edit_undo") diff --git a/shortcut_composer/data_components/tag.py b/shortcut_composer/data_components/tag.py index 28c4c8a9..bc67a1a5 100644 --- a/shortcut_composer/data_components/tag.py +++ b/shortcut_composer/data_components/tag.py @@ -6,10 +6,13 @@ class Tag(List[str]): + """List representing names of presets in a tag of given name.""" + def __init__(self, tag_name: str): self.tag_name = tag_name - self.extend(self._read_brushes()) + self.extend(self._read_presets()) - def _read_brushes(self): + def _read_presets(self) -> List[str]: + """Read the brush presets from the database using tag name.""" with Database() as database: return database.get_preset_names_from_tag(self.tag_name) diff --git a/shortcut_composer/templates/multiple_assignment.py b/shortcut_composer/templates/multiple_assignment.py index 74f48b8a..fa93db81 100644 --- a/shortcut_composer/templates/multiple_assignment.py +++ b/shortcut_composer/templates/multiple_assignment.py @@ -5,6 +5,7 @@ from itertools import cycle from core_components import Controller, Instruction +from config_system import Field from .raw_instructions import RawInstructions from .multiple_assignment_utils import SettingsHandler @@ -73,14 +74,19 @@ def __init__( self._controller = controller self._default_value = self._read_default_value(default_value) - self._settings = SettingsHandler(name, values, instructions) - self._values_to_cycle = self._settings.values.read() + self.config = Field( + config_group=f"ShortcutComposer: {name}", + name="Values", + default=values) + + self._settings = SettingsHandler(name, self.config, instructions) + self._values_to_cycle = self.config.read() def reset() -> None: - self._values_to_cycle = self._settings.values.read() + self._values_to_cycle = self.config.read() self._reset_iterator() - self._settings.values.register_callback(reset) + self.config.register_callback(reset) self._last_value: Optional[T] = None self._iterator: Iterator[T] diff --git a/shortcut_composer/templates/multiple_assignment_utils/action_values.py b/shortcut_composer/templates/multiple_assignment_utils/action_values.py index e0f45c35..f5d842d1 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/action_values.py +++ b/shortcut_composer/templates/multiple_assignment_utils/action_values.py @@ -19,6 +19,18 @@ class ActionValues(QWidget): + """ + Widget for selecting values and their order available as Enum. + + Consists of two lists next to each other. Values from the left + represent all unused Enum values. They can be moved to the list of + selected values on the right. + + Elements are moved between lists using two buttons. Widget supports + moving multiple values at once. Drag&drop allows to change order in + the list of selected values. + """ + def __init__(self, enum_type: Type[Enum], config: Field[List[Enum]]): super().__init__() @@ -45,13 +57,14 @@ def __init__(self, enum_type: Type[Enum], config: Field[List[Enum]]): control_layout.addWidget(remove_button) control_layout.addStretch() - layout.addLayout(self._labeled_list(self.available_list, "Available:")) + layout.addLayout(self._add_label(self.available_list, "Available:")) layout.addLayout(control_layout) - layout.addLayout(self._labeled_list(self.current_list, "Selected:")) + layout.addLayout(self._add_label(self.current_list, "Selected:")) self.setLayout(layout) - def _labeled_list(self, value_list: ValueList, text: str) -> QVBoxLayout: + def _add_label(self, value_list: ValueList, text: str) -> QVBoxLayout: + """Adds a label on top of the list area.""" layout = QVBoxLayout() label = QLabel(text) label.setAlignment(Qt.AlignCenter) @@ -59,21 +72,24 @@ def _labeled_list(self, value_list: ValueList, text: str) -> QVBoxLayout: layout.addWidget(value_list) return layout - def add(self): + def add(self) -> None: + """Move mouse-selected values from the left list to the right.""" for value in self.available_list.selected: self.current_list.insert( position=self.current_list.current_row, value=value) self.available_list.remove(value=value,) - def remove(self): + def remove(self) -> None: + """Move mouse-selected values from the right list to the left.""" selected = self.current_list.selected self.current_list.remove_selected() new_available = set(self.available_list.get_all()) | set(selected) self.available_list.clear() self.available_list.addItems(sorted(new_available)) - def apply(self): + def apply(self) -> None: + """Save the right list into .kritarc.""" to_write: List[Enum] = [] for row in range(self.current_list.count()): text = self.current_list.item(row).text() @@ -81,16 +97,18 @@ def apply(self): self.config.write(to_write) - def refresh(self): + def refresh(self) -> None: + """Refresh right list with .kritarc values and left one accordingly.""" self.current_list.clear() current_list = self.config.read() text_list = [item.name for item in current_list] self.current_list.addItems(text_list) self.available_list.clear() - allowed_items = sorted(set(self.allowed_values) - set(text_list)) + allowed_items = sorted(set(self._allowed_values) - set(text_list)) self.available_list.addItems(allowed_items) @property - def allowed_values(self): + def _allowed_values(self) -> List[str]: + """Return list of all available values using the enum type.""" return self.enum_type._member_names_ diff --git a/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py b/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py index 8685f3d9..45815673 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py +++ b/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py @@ -1,21 +1,21 @@ # SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import Type +from typing import List, Type from enum import Enum from PyQt5.QtWidgets import QVBoxLayout, QWidget from PyQt5.QtCore import Qt from config_system import Field -from composer_utils.layouts import ButtonsLayout +from composer_utils import ButtonsLayout from .action_values import ActionValues class ActionValuesWindow(QWidget): - """Tab in which user can change values used in actions and their order.""" + """Tab in which user can change action enums and their order.""" - def __init__(self, enum_type: Type[Enum], config: Field) -> None: + def __init__(self, enum_type: Type[Enum], config: Field[List[Enum]]): super().__init__() self.setWindowFlags(self.windowFlags() | Qt.Tool) # type: ignore layout = QVBoxLayout() @@ -25,29 +25,32 @@ def __init__(self, enum_type: Type[Enum], config: Field) -> None: layout.addWidget(self.widget) layout.addLayout(ButtonsLayout( - ok_callback=self.ok, - apply_callback=self.apply, - reset_callback=self.reset, + ok_callback=self._ok, + apply_callback=self._apply, + reset_callback=self._reset, cancel_callback=self.hide)) self.setLayout(layout) def show(self) -> None: - self.refresh() + """Refresh the widget before showing it.""" + self._refresh() return super().show() - def ok(self) -> None: + def _ok(self) -> None: """Hide the dialog after applying the changes""" - self.apply() + self._apply() self.hide() - def reset(self) -> None: + def _reset(self) -> None: """Reset all config values to defaults in krita and elements.""" self._config.reset_default() - self.refresh() + self._refresh() - def apply(self) -> None: + def _apply(self) -> None: + """Apply changes in held widget.""" self.widget.apply() - def refresh(self) -> None: + def _refresh(self) -> None: + """Refresh the held widget.""" self.widget.refresh() diff --git a/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py b/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py index 755fe907..09f24ee1 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py +++ b/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from dataclasses import dataclass from typing import List from enum import Enum @@ -15,60 +14,68 @@ class SettingsHandler: + """ + Manages showing the MA settings window and button activating it. + + Creates settings window and button, and installs a instruction in + multiple assignment action to show the button when the action key is + pressed for a short time. + + Once clicked, the button shows the window in which the MA + configuration can be modified. + """ + def __init__( self, name: str, - values: list, + config: Field[list], instructions: List[Instruction], ) -> None: - self.values = Field( - config_group=f"ShortcutComposer: {name}", - name="Values", - default=values) - - to_cycle = self.values.read() + to_cycle = config.read() if not to_cycle or not isinstance(to_cycle[0], Enum): return - self._settings = ActionValuesWindow(type(to_cycle[0]), self.values) + self._settings = ActionValuesWindow(type(to_cycle[0]), config) self._settings.setWindowTitle(f"Configure: {name}") - self._settings_button = RoundButton( + self._button = RoundButton( icon=Krita.get_icon("properties"), icon_scale=1.1, initial_radius=25, background_color=QColor(75, 75, 75, 255), active_color=QColor(100, 150, 230, 255)) - self._settings_button.clicked.connect(self._on_button_click) - self._settings_button.move(0, 0) - self._settings_button.hide() + self._button.clicked.connect(self._on_button_click) + self._button.move(0, 0) + self._button.hide() - instructions.append(HandlerInstruction( - self._settings, self._settings_button)) + instructions.append(HandlerInstruction(self._settings, self._button)) def _on_button_click(self): + """Show the settings and hide the button after it was clicked.""" self._settings.show() - self._settings_button.hide() + self._button.hide() -@dataclass class HandlerInstruction(Instruction): + """Instruction installed on the MA action which activates the button.""" - settings: ActionValuesWindow - button: RoundButton - - def __post_init__(self): - self.timer = Timer(self.timer_callback, 500) + def __init__(self, settings: ActionValuesWindow, button: RoundButton): + self._settings = settings + self._button = button + self._timer = Timer(self.timer_callback, 500) def on_key_press(self) -> None: - self.timer.start() + """Start a timer which soon will run a callback once.""" + self._timer.start() def timer_callback(self): - if not self.settings.isVisible(): - self.button.move_center(QCursor().pos()) - self.button.show() - self.timer.stop() + """Show a button under the cursor.""" + if not self._settings.isVisible(): + self._button.move_center(QCursor().pos()) + self._button.show() + self._timer.stop() def on_every_key_release(self) -> None: - self.button.hide() - self.timer.stop() + """Hide the button when visible, or cancel the timer if not.""" + self._button.hide() + self._timer.stop() diff --git a/shortcut_composer/templates/multiple_assignment_utils/value_list.py b/shortcut_composer/templates/multiple_assignment_utils/value_list.py index 232220c1..65008846 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/value_list.py +++ b/shortcut_composer/templates/multiple_assignment_utils/value_list.py @@ -4,11 +4,7 @@ from typing import Optional, List from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import ( - QAbstractItemView, - QListWidget, - QWidget, -) +from PyQt5.QtWidgets import QAbstractItemView, QListWidget, QWidget class ValueList(QListWidget): diff --git a/shortcut_composer/templates/pie_menu_utils/label.py b/shortcut_composer/templates/pie_menu_utils/label.py index 5dbe85c4..7c5ba31f 100644 --- a/shortcut_composer/templates/pie_menu_utils/label.py +++ b/shortcut_composer/templates/pie_menu_utils/label.py @@ -42,6 +42,7 @@ def swap_locations(self, other: 'Label[T]') -> None: self.center, other.center = other.center, self.center def __eq__(self, other: T) -> bool: + """Consider two labels with the same value equal.""" if not isinstance(other, Label): return False return self.value == other.value diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget.py index 5a0ebc18..4f4ae00c 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget.py @@ -15,8 +15,11 @@ class WidgetInstructions(Protocol): """Additional logic to do on entering and leaving a widget.""" - def on_enter(self, label: Label) -> None: ... - def on_leave(self, label: Label) -> None: ... + def on_enter(self, label: Label) -> None: + """Logic to perform when mouse starts hovering over widget.""" + + def on_leave(self, label: Label) -> None: + """Logic to perform when mouse stops hovering over widget.""" class LabelWidget(BaseWidget): @@ -35,8 +38,7 @@ def __init__( self._style = style self._is_unscaled = is_unscaled - self._draggable = True - self.draggable = self._draggable + self.draggable = self._draggable = True self._enabled = True self._hovered = False @@ -49,7 +51,7 @@ def add_instruction(self, instruction: WidgetInstructions): @property def draggable(self) -> bool: - """Return whether the label accepts dragging.""" + """Return whether the label can be dragged.""" return self._draggable @draggable.setter @@ -92,20 +94,20 @@ def mousePressEvent(self, e: QMouseEvent) -> None: drag.exec_(Qt.MoveAction) def enterEvent(self, e: QEvent) -> None: + super().enterEvent(e) """Notice that mouse moved over the widget.""" self._hovered = True for instruction in self._instructions: instruction.on_enter(self.label) self.repaint() - return super().enterEvent(e) def leaveEvent(self, e: QEvent) -> None: """Notice that mouse moved out of the widget.""" + super().leaveEvent(e) self._hovered = False for instruction in self._instructions: instruction.on_leave(self.label) self.repaint() - return super().leaveEvent(e) @property def _border_color(self): diff --git a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/create_label_widget.py b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/create_label_widget.py index 7fee159b..9fbc5117 100644 --- a/shortcut_composer/templates/pie_menu_utils/label_widget_utils/create_label_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/label_widget_utils/create_label_widget.py @@ -23,7 +23,7 @@ def create_label_widget( style: PieStyle, parent: QWidget, is_unscaled: bool = False, -) -> 'LabelWidget': +) -> LabelWidget: """Return LabelWidget which can display this label.""" if label.display_value is None: raise ValueError(f"Label {label} is not valid") diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index b5ea0bac..fe19e89e 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -34,10 +34,13 @@ class PieWidget(AnimatedWidget, BaseWidget, Generic[T]): - hide() - hides the widget - repaint() - updates widget display after its data was changed - In LabelHolder it stores children widgets that are draggable when - pie menu is in edit mode. + It uses LabelHolder to store children widgets representing the + values user can pick. When the pie enters the edit mode, its + children become draggable. - Dragging them, makes changes to LabelHolder. + By dragging children, user can change their order or remove them + by moving them out of the widget. New children can be added by + dragging them from other widgets. """ def __init__( @@ -104,7 +107,7 @@ def dragEnterEvent(self, e: QDragEnterEvent) -> None: e.ignore() def dragMoveEvent(self, e: QDragMoveEvent) -> None: - """Swap children during drag when mouse is moved to another zone.""" + """Handle all children actions - order change, add and remove.""" e.accept() source_widget = e.source() pos = e.pos() diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index d098ae2b..25d6bcfb 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -57,7 +57,7 @@ def __init__( def refresh(self): """Make all values currently used in pie undraggable and disabled.""" - for widget in self._action_values._children_list: + for widget in self._action_values.children_list: if widget.label in self._used_values: widget.enabled = False widget.draggable = False diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py index f3ee77fd..0adc6579 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py @@ -43,7 +43,7 @@ def move_to_pie_side(self): """Move the widget on the right side of the pie.""" offset = self.width()//2 + self._style.widget_radius * 1.05 point = QPoint(round(offset), 0) - # Assume the pie center should be at the cursor + # HACK Assume the pie center should be at the cursor self.move_center(QCursor().pos() + point) # type: ignore def _reset(self): diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py index 1218bb6a..5807dfa1 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/scroll_area.py @@ -33,39 +33,50 @@ def on_leave(self, label: Label) -> None: class ScrollArea(QWidget): + """ + Widget containing a scrollable list of PieWidgets. + + Widgets are created based on the passed labels and then made + publically available in `children_list` attribute, so that the owner + of the class can change their state (draggable, enabled). + + ScrollArea comes with embedded QLabel showing the name of the + children widget over which mouse was hovered. + """ + def __init__( self, - values: List[Label], + labels: List[Label], style: PieStyle, columns: int, parent=None ) -> None: super().__init__(parent) - self._area = QScrollArea() - self._area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self._area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) - self._area.setWidgetResizable(True) - self._style = style - self.labels = values - - self._scroll_area_layout = ScrollAreaLayout(columns, self) - self._active_label_display = QLabel(self) - self._children_list = self._create_children() + self._labels = labels - layout = QVBoxLayout() + self._scroll_area_layout = OffsetGridLayout(columns, self) scroll_widget = QWidget() scroll_widget.setLayout(self._scroll_area_layout) - self._area.setWidget(scroll_widget) - layout.addWidget(self._area) + area = QScrollArea() + area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) + area.setWidgetResizable(True) + area.setWidget(scroll_widget) + + layout = QVBoxLayout() + layout.addWidget(area) + self._active_label_display = QLabel(self) layout.addWidget(self._active_label_display) self.setLayout(layout) + self.children_list = self._create_children() + def _create_children(self) -> List[LabelWidget]: """Create LabelWidgets that represent the labels.""" children: List[LabelWidget] = [] - for label in self.labels: + for label in self._labels: child = create_label_widget( label=label, style=self._style, @@ -75,7 +86,8 @@ def _create_children(self) -> List[LabelWidget]: child.draggable = True child.add_instruction(ChildInstruction(self._active_label_display)) children.append(child) - self._scroll_area_layout.append(child) + + self._scroll_area_layout.extend(children) return children @@ -84,18 +96,37 @@ class GridPosition(NamedTuple): gridcol: int -class ScrollAreaLayout(QGridLayout): +class OffsetGridLayout(QGridLayout): + """ + Layout displaying widgets, as the grid in which even rows have offset. + + Even rows have one item less than uneven rows, and are moved half + the widget width to make them overlap with each other. + + The layout acts like list of widgets it's responsibility is to + automatically refresh, when changes are being made to it. + + Implemented using QGridLayout in which every widget uses 2x2 fields. + + max_columns -- Amount of widgets in uneven rows. + When set to 4, rows will cycle: (4, 3, 4, 3, 4...) + group -- Two consecutive rows of widgets. + When max_columns is 4 will consist of 7 (4+3) widgets + """ + def __init__(self, max_columns: int, owner: QWidget): super().__init__() - self.widgets: List[QWidget] = [] + self._widgets: List[QWidget] = [] self._max_columns = max_columns self._items_in_group = 2*max_columns - 1 self._owner = owner def __len__(self) -> int: - return len(self.widgets) + """Amount of held LabelWidgets.""" + return len(self._widgets) def _get_position(self, index: int) -> GridPosition: + """Return a GridPosition (row, col) of it's widget.""" group, item = divmod(index, self._items_in_group) if item < self._max_columns: @@ -104,18 +135,31 @@ def _get_position(self, index: int) -> GridPosition: col = item-self._max_columns return GridPosition(gridrow=group*4+2, gridcol=col*2+1) - def _new_position(self) -> GridPosition: - return self._get_position(len(self.widgets)) - - def append(self, widget: QWidget) -> None: - if widget in self.widgets: + def _internal_insert(self, index: int, widget: LabelWidget) -> None: + """Insert widget at given index if not stored already.""" + if widget in self._widgets: return widget.setParent(self._owner) widget.show() - self.widgets.append(widget) - self.addWidget(widget, *self._new_position(), 2, 2) + self._widgets.insert(index, widget) + + def insert(self, index: int, widget: LabelWidget) -> None: + """Insert the widget at given index and refresh the layout.""" + self._internal_insert(index, widget) + self._refresh() + + def append(self, widget: LabelWidget) -> None: + """Append the widget at the end and refresh the layout.""" + self._internal_insert(len(self), widget) + self._refresh() + + def extend(self, widgets: List[LabelWidget]) -> None: + """Extend layout with the given widgets and refresh the layout.""" + for widget in widgets: + self._internal_insert(len(self), widget) self._refresh() def _refresh(self): - for i, widget in enumerate(self.widgets): + """Refresh the layout by adding all the internal widgets to it.""" + for i, widget in enumerate(self._widgets): self.addWidget(widget, *self._get_position(i), 2, 2) diff --git a/shortcut_composer/templates/raw_instructions.py b/shortcut_composer/templates/raw_instructions.py index 3a9db5bf..5444ed25 100644 --- a/shortcut_composer/templates/raw_instructions.py +++ b/shortcut_composer/templates/raw_instructions.py @@ -10,10 +10,9 @@ class RawInstructions(ComplexActionInterface): """ - Temporarily toggle plugin instructions. + ShortcutComposer action base. - Action starts all the instructions on key press, and ends them on - release. + Handles passed instructions, fulfilling the ComplexActionInterface. ### Arguments: From e7828ee4988f9a7acb1f1d7ab533ed05ef141109 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Fri, 7 Apr 2023 22:11:13 +0200 Subject: [PATCH 112/125] Fix Win10 specific issues --- .../multiple_assignment_utils/action_values_window.py | 6 ++---- .../templates/pie_menu_utils/settings_gui/__init__.py | 2 +- .../{numeric_pie_config.py => numeric_pie_settings.py} | 0 .../templates/pie_menu_utils/settings_gui/pie_settings.py | 1 + .../templates/pie_menu_utils/widget_utils/pie_painter.py | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) rename shortcut_composer/templates/pie_menu_utils/settings_gui/{numeric_pie_config.py => numeric_pie_settings.py} (100%) diff --git a/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py b/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py index 45815673..af6779c4 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py +++ b/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py @@ -4,20 +4,18 @@ from typing import List, Type from enum import Enum -from PyQt5.QtWidgets import QVBoxLayout, QWidget -from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QVBoxLayout, QDialog from config_system import Field from composer_utils import ButtonsLayout from .action_values import ActionValues -class ActionValuesWindow(QWidget): +class ActionValuesWindow(QDialog): """Tab in which user can change action enums and their order.""" def __init__(self, enum_type: Type[Enum], config: Field[List[Enum]]): super().__init__() - self.setWindowFlags(self.windowFlags() | Qt.Tool) # type: ignore layout = QVBoxLayout() self._config = config diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py index 0de26d85..d6677014 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/__init__.py @@ -4,7 +4,7 @@ from .pie_settings import PieSettings from .enum_pie_settings import EnumPieSettings from .preset_pie_settings import PresetPieSettings -from .numeric_pie_config import NumericPieSettings +from .numeric_pie_settings import NumericPieSettings __all__ = [ "PieSettings", diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_config.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_settings.py similarity index 100% rename from shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_config.py rename to shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_settings.py diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py index 0adc6579..79eb1a13 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/pie_settings.py @@ -27,6 +27,7 @@ def __init__( parent: Optional[QWidget] = None, ) -> None: AnimatedWidget.__init__(self, parent, Config.PIE_ANIMATION_TIME.read()) + self.setMaximumHeight(round(style.widget_radius*3)) self.setAcceptDrops(True) self.setWindowFlags(( self.windowFlags() | # type: ignore diff --git a/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_painter.py b/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_painter.py index 5c3447af..fe7be481 100644 --- a/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_painter.py +++ b/shortcut_composer/templates/pie_menu_utils/widget_utils/pie_painter.py @@ -56,7 +56,7 @@ def _paint_base_wheel(self) -> None: # of the widget, so a low opacity circle allows to trick it. self.painter.paint_wheel( center=self._center, - outer_radius=self.style.no_border_radius, + outer_radius=self.style.widget_radius, color=QColor(128, 128, 128, 1)) self.painter.paint_wheel( From 3c5271bdb4121673bedae9417a40c99ee076c49a Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Sat, 8 Apr 2023 14:38:45 +0200 Subject: [PATCH 113/125] Removed changelog from repository --- CHANGELOG.md | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 1428eb13..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,33 +0,0 @@ -# Change Log - -## [1.1.1] - 2023-02-04 -### Fixed -- Fix a crash when any PieMenu is empty. -- Support multiple krita windows. -- Fix transform modes ocasionally stopping to work. -- Handle invalid configuration. -- Add missing icon for transform tool. - -## [1.1.0] - 2023-01-20 -### Added -- Edit mode for PieMenus - click or drag the value icon to enter it. While in edit mode, the values can be dragged across the PieMenu to change their order. -- Action values tab in Configure Shortcut Composer for adding and removing values in PieMenus and MultipleAssignment actions. -- PieValues backtrounds are now animated. -- New PieMenu to create a layer with chosen blending mode. - -### Fixed -- Allow scrolling through masks while using layey mouse trackers -- Make sure that presets in PieMenu are displayed only once. -- Allow using "Enclose and fill" tool -- Fix icon of "Colorize Mask" and "Color Sampler" tools -- Fix scaling issues of Text labels in PieMenu - -## [1.0.1] - 2023-01-09 -Fixes following issues: -- Fix displaying inactive presets and tags -- Detect and support light krita theme, switch between them when changed -- PieWidget as popup - pies no longer recognized by OS as windows -- Allow different parameters for each configuration value -- Use default font to fix MacOS helvetica issue -- Allow using actions when active layer is locked -- Work around krita bug: zoom being dependent on document dpi From 1746dd80e333498119f2946a8433f167e9425662 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Sat, 8 Apr 2023 15:47:21 +0200 Subject: [PATCH 114/125] Move MA button to top left corner of painting area --- shortcut_composer/api_krita/core_api.py | 9 ++++++++- .../multiple_assignment_utils/settings_handler.py | 7 ++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/shortcut_composer/api_krita/core_api.py b/shortcut_composer/api_krita/core_api.py index 5cb8dae0..424d70f8 100644 --- a/shortcut_composer/api_krita/core_api.py +++ b/shortcut_composer/api_krita/core_api.py @@ -4,7 +4,11 @@ from krita import Krita as Api, Extension, qApp from typing import Callable, Protocol, Any, Optional -from PyQt5.QtWidgets import QMainWindow, QDesktopWidget, QWidgetAction +from PyQt5.QtWidgets import ( + QMainWindow, + QDesktopWidget, + QWidgetAction, + QMdiArea) from PyQt5.QtGui import QKeySequence, QColor, QIcon from PyQt5.QtCore import QTimer @@ -61,6 +65,9 @@ def get_active_qwindow(self) -> QMainWindow: """Return qt window of krita. Don't use on plugin init phase.""" return self.instance.activeWindow().qwindow() + def get_active_mdi_area(self) -> QMdiArea: + return self.get_active_qwindow().findChild(QMdiArea) # type: ignore + def get_icon(self, icon_name: str) -> QIcon: return self.instance.icon(icon_name) diff --git a/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py b/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py index 09f24ee1..67112815 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py +++ b/shortcut_composer/templates/multiple_assignment_utils/settings_handler.py @@ -4,7 +4,7 @@ from typing import List from enum import Enum -from PyQt5.QtGui import QColor, QCursor +from PyQt5.QtGui import QColor from api_krita import Krita from api_krita.pyqt import RoundButton, Timer @@ -69,9 +69,10 @@ def on_key_press(self) -> None: self._timer.start() def timer_callback(self): - """Show a button under the cursor.""" + """Show a button in top left corner of painting area.""" if not self._settings.isVisible(): - self._button.move_center(QCursor().pos()) + mdiArea = Krita.get_active_mdi_area() + self._button.move(mdiArea.mapToGlobal(mdiArea.pos())) self._button.show() self._timer.stop() From 869404be35976ad7746f571aebc0f38fbb759350 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Sat, 8 Apr 2023 15:48:19 +0200 Subject: [PATCH 115/125] Make QDialogs stay on top --- shortcut_composer/composer_utils/settings_dialog.py | 6 +++--- .../multiple_assignment_utils/action_values_window.py | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/shortcut_composer/composer_utils/settings_dialog.py b/shortcut_composer/composer_utils/settings_dialog.py index 0d1ddd56..e0169bd0 100644 --- a/shortcut_composer/composer_utils/settings_dialog.py +++ b/shortcut_composer/composer_utils/settings_dialog.py @@ -1,10 +1,8 @@ # SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from typing import List - from PyQt5.QtWidgets import QVBoxLayout, QDialog -from PyQt5.QtCore import QSize +from PyQt5.QtCore import Qt, QSize from PyQt5.QtGui import QCursor from api_krita import Krita @@ -19,6 +17,8 @@ class SettingsDialog(QDialog): def __init__(self) -> None: super().__init__() + self.setWindowFlags( + self.windowFlags() | Qt.WindowStaysOnTopHint) # type: ignore self.setMinimumSize(QSize(300, 200)) self.setWindowTitle("Configure Shortcut Composer") diff --git a/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py b/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py index af6779c4..cac8fa0c 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py +++ b/shortcut_composer/templates/multiple_assignment_utils/action_values_window.py @@ -5,6 +5,7 @@ from enum import Enum from PyQt5.QtWidgets import QVBoxLayout, QDialog +from PyQt5.QtCore import Qt from config_system import Field from composer_utils import ButtonsLayout @@ -16,6 +17,8 @@ class ActionValuesWindow(QDialog): def __init__(self, enum_type: Type[Enum], config: Field[List[Enum]]): super().__init__() + self.setWindowFlags( + self.windowFlags() | Qt.WindowStaysOnTopHint) # type: ignore layout = QVBoxLayout() self._config = config From d37e142528ff54e9a8b735bd05e8ca4297418ef5 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Sat, 8 Apr 2023 15:49:01 +0200 Subject: [PATCH 116/125] Display plugin info in Configure SC --- shortcut_composer/INFO.py | 6 ++++++ shortcut_composer/composer_utils/settings_dialog.py | 5 ++++- shortcut_composer/shortcut_composer.py | 3 --- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 shortcut_composer/INFO.py diff --git a/shortcut_composer/INFO.py b/shortcut_composer/INFO.py new file mode 100644 index 00000000..7b9fbbff --- /dev/null +++ b/shortcut_composer/INFO.py @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus +# SPDX-License-Identifier: GPL-3.0-or-later + +__version__ = "1.2.0 prealpha" +__author__ = "Wojciech Trybus" +__license__ = "GPL-3.0-or-later" diff --git a/shortcut_composer/composer_utils/settings_dialog.py b/shortcut_composer/composer_utils/settings_dialog.py index e0169bd0..9fb8cbb1 100644 --- a/shortcut_composer/composer_utils/settings_dialog.py +++ b/shortcut_composer/composer_utils/settings_dialog.py @@ -5,8 +5,8 @@ from PyQt5.QtCore import Qt, QSize from PyQt5.QtGui import QCursor +from INFO import __version__, __author__, __license__ from api_krita import Krita -from api_krita.wrappers import Database from config_system.ui import ConfigFormWidget, ConfigSpinBox from .global_config import Config from .buttons_layout import ButtonsLayout @@ -41,6 +41,9 @@ def __init__(self) -> None: ConfigSpinBox( Config.PIE_DEADZONE_GLOBAL_SCALE, self, None, 0.05, 4), ConfigSpinBox(Config.PIE_ANIMATION_TIME, self, None, 0.01, 1), + f"Shortcut Composer v{__version__}\n" + f"Maintainer: {__author__}\n" + f"License: {__license__}", ]) full_layout = QVBoxLayout(self) diff --git a/shortcut_composer/shortcut_composer.py b/shortcut_composer/shortcut_composer.py index 1f80aa93..8e77884c 100755 --- a/shortcut_composer/shortcut_composer.py +++ b/shortcut_composer/shortcut_composer.py @@ -1,9 +1,6 @@ # SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -__version__ = "1.2.0 prealpha" -__author__ = "Wojciech Trybus" - from typing import List from dataclasses import dataclass from PyQt5.QtWidgets import QWidgetAction From 12877b36a797c4416465bf91314a42ea3fef2a0e Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Sat, 8 Apr 2023 15:49:17 +0200 Subject: [PATCH 117/125] Remove unused code in Configure SC --- shortcut_composer/composer_utils/settings_dialog.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/shortcut_composer/composer_utils/settings_dialog.py b/shortcut_composer/composer_utils/settings_dialog.py index 9fb8cbb1..6ec5e36e 100644 --- a/shortcut_composer/composer_utils/settings_dialog.py +++ b/shortcut_composer/composer_utils/settings_dialog.py @@ -23,9 +23,6 @@ def __init__(self) -> None: self.setMinimumSize(QSize(300, 200)) self.setWindowTitle("Configure Shortcut Composer") - self._tags: List[str] = [] - self._refresh_tags() - self._general_tab = ConfigFormWidget([ "Common settings", ConfigSpinBox( @@ -78,12 +75,5 @@ def reset(self) -> None: self.refresh() Krita.trigger_action("Reload Shortcut Composer") - def _refresh_tags(self): - with Database() as database: - tags = sorted(database.get_brush_tags(), key=str.lower) - self._tags.clear() - self._tags.extend(tags) - def refresh(self): - self._refresh_tags() self._general_tab.refresh() From d1ebd18c527414fe5d9c23d1e43eb70e30fdb6b9 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Sat, 8 Apr 2023 18:01:08 +0200 Subject: [PATCH 118/125] Rename passed_type to parser_type for name unification --- shortcut_composer/config_system/field_group.py | 4 ++-- shortcut_composer/templates/pie_menu_utils/pie_config.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shortcut_composer/config_system/field_group.py b/shortcut_composer/config_system/field_group.py index 42dbb25c..0a9868c3 100644 --- a/shortcut_composer/config_system/field_group.py +++ b/shortcut_composer/config_system/field_group.py @@ -30,10 +30,10 @@ def field( self, name: str, default: T, - passed_type: Optional[type] = None + parser_type: Optional[type] = None ) -> Field[T]: """Create and return a new field in the group.""" - field = Field(self.name, name, default, passed_type) + field = Field(self.name, name, default, parser_type) self._fields.append(field) for callback in self._callbacks: field.register_callback(callback) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 2b5347c3..17b6b814 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -56,7 +56,7 @@ def __init__( self.PIE_RADIUS_SCALE = self.field("Pie scale", pie_radius_scale) self.ICON_RADIUS_SCALE = self.field("Icon scale", icon_radius_scale) self.TAG_NAME = self.field("Tag", values.tag_name) - self.ORDER = self.field("Values", [], passed_type=str) + self.ORDER = self.field("Values", [], parser_type=str) self.background_color = background_color self.active_color = active_color From a2c525b1d027defe87ec587786a00cb7d276d462 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Sat, 8 Apr 2023 18:01:52 +0200 Subject: [PATCH 119/125] Raise exception when trying to write value of wrong type in config --- shortcut_composer/config_system/field_base.py | 3 +++ shortcut_composer/config_system/field_implementations.py | 6 ++++++ shortcut_composer/config_system/parsers.py | 6 +++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/shortcut_composer/config_system/field_base.py b/shortcut_composer/config_system/field_base.py index 77dd4bbd..bcf08d5a 100644 --- a/shortcut_composer/config_system/field_base.py +++ b/shortcut_composer/config_system/field_base.py @@ -39,6 +39,9 @@ def register_callback(self, callback: Callable[[], None]): def write(self, value: T): """Write value to file and run callbacks if it was not redundant.""" + if not isinstance(value, type(self.default)): + raise TypeError(f"{value} not of type {type(self.default)}") + if self._is_write_redundant(value): return diff --git a/shortcut_composer/config_system/field_implementations.py b/shortcut_composer/config_system/field_implementations.py index 4e07ac78..78afc719 100644 --- a/shortcut_composer/config_system/field_implementations.py +++ b/shortcut_composer/config_system/field_implementations.py @@ -52,6 +52,12 @@ def __init__( super().__init__(config_group, name, default) self._parser: Parser[T] = self._get_parser(self._get_type(parser_type)) + def write(self, value: List[T]): + for element in value: + if not isinstance(element, self._parser.type): + raise ValueError(f"{value} not of type {type(self.default)}") + return super().write(value) + def _get_type(self, passed_type: Optional[type]) -> type: """ Determine parser type based on default value or passed type. diff --git a/shortcut_composer/config_system/parsers.py b/shortcut_composer/config_system/parsers.py index e160f75e..65ac1850 100644 --- a/shortcut_composer/config_system/parsers.py +++ b/shortcut_composer/config_system/parsers.py @@ -12,6 +12,8 @@ class Parser(Generic[T], Protocol): """Parses from string to specific type and vice-versa.""" + type: type + def parse_to(self, value: str) -> T: """Parse from string to specific type.""" ... @@ -39,6 +41,8 @@ def parse_from(self, value: Basic) -> str: class BoolParser(Parser[bool]): """Parses from string to bool and vice-versa.""" + type = bool + def parse_to(self, value: str) -> bool: """Parses from string to bool.""" if value not in ("true", "false"): @@ -58,7 +62,7 @@ def __init__(self, type: Type[EnumT]) -> None: def parse_to(self, value: str) -> EnumT: """Parse from string to enum.""" - return self.type[value] + return self.type[value] # type: ignore def parse_from(self, value: EnumT) -> str: """Parse from enum to string.""" From 637b927cc3da08ce10cca712a1bc1f142af038c4 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Sat, 8 Apr 2023 18:28:53 +0200 Subject: [PATCH 120/125] Rewrite readme for 1.2.0. Docstring fixes --- README.md | 238 +++++++++++++----- shortcut_composer/api_krita/core_api.py | 4 +- shortcut_composer/config_system/__init__.py | 2 +- shortcut_composer/config_system/api_krita.py | 4 +- shortcut_composer/config_system/field.py | 37 +-- shortcut_composer/config_system/field_base.py | 4 +- .../config_system/field_group.py | 7 +- .../config_system/field_implementations.py | 4 +- .../config_system/ui/__init__.py | 4 +- .../config_system/ui/config_based_widget.py | 2 +- .../input_adapter/action_manager.py | 9 +- .../input_adapter/complex_action_interface.py | 4 +- .../action_values.py | 4 +- .../templates/pie_menu_utils/pie_config.py | 8 +- .../settings_gui/enum_pie_settings.py | 2 +- .../settings_gui/numeric_pie_settings.py | 2 +- .../settings_gui/preset_pie_settings.py | 2 +- 17 files changed, 227 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index f763852d..be038f1a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ **Extension** for painting application **Krita**, which allows to create custom, complex **keyboard shortcuts**. - The plugin adds new shortcuts of the following types: - `Pie menu` - while key is pressed, displays a pie menu, which allows to pick values by hovering a mouse. - `Cursor tracker` - while key is pressed, tracks a cursor, switching values according to cursor offset. @@ -12,16 +11,28 @@ The plugin adds new shortcuts of the following types: [![PIE MENUS - introducing Shortcut Composer](http://img.youtube.com/vi/hrjBycVYFZM/0.jpg)](https://www.youtube.com/watch?v=hrjBycVYFZM "PIE MENUS - introducing Shortcut Composer") -## What's new in v1.1.1 +## What's new in `v1.2.0` +### Added +- Adding and removing PieMenu icons with drag and drop. +- Class-oriented configuration system with automatic value parsing. Can be reused in other plugins. +- PieMenus are now able to handle their own local configuration with Pie settings (Replaces section in `Configure Shortcut Composer`). +- Change PieIcon border's color when hovered in EditMode (Replaces gray indicator) +- Allow changes to PieMenu and PieIcon size for each pie separately. +- Separate button to enter PieMenu edit mode. Replaces clicking on PieIcon which was easy to do unintentionally. +- Preset PieMenus tag now can be chosen directly from the Pie settings. (Replaces section in `Configure Shortcut Composer`) +- Preset PieMenus now reload automatically when the tag content changes. (Replaces `Reload Shortcut Composer` action) +- PieMenus now automatically change between dark/light theme when the krita theme changes between these two theme families. +- `Cycle selection tools` action now is configured by local settings activated with a button that appears on long press (Replaces section in `Configure Shortcut Composer`) + ### Fixed -- Fix a crash when any PieMenu is empty. -- Support multiple krita windows. -- Fix transform modes ocasionally stopping to work. -- Handle invalid configuration. -- Add missing icon for transform tool. +- Support tags with quote and double-quote signs. +- Make `input_adapter` package independent from the rest of the plugin to improve re-usability. +- Fix crash when picking a deleted preset with PieMenu. + +Check out historic [changelogs](https://github.com/wojtryb/Shortcut-Composer/wiki/Change-log). ## Requirements -Shortcut Composer **v1.1.1** Requires krita **5.1.0** or later. +Shortcut Composer **v1.2.0** Requires krita **5.1.0** or later. OS support state: - [x] Windows 10/11 @@ -29,7 +40,7 @@ OS support state: - [ ] MacOS (Known bug of canvas losing focus after using PieMenu) - [ ] Android (Does not support python plugins yet) -## Installation: +## How to install or update the plugin: 1. on [github project page](https://github.com/wojtryb/Shortcut-Composer), click the green button code and pick the download zip option. Do not extract it. 2. in krita's topbar, open **Tools > Scripts > Import Python Plugin From File** and pick the downloaded .zip file 3. restart krita. @@ -39,20 +50,27 @@ OS support state: While Shortcut-Composer is highly configurable and extendable, the add-on comes with pre-made, plug-and-play actions. ### (`Pie menus`): -Pie menu is a widget displayed on the canvas while a key is pressed. It will disappear as soon, as the key is released. Moving cursor in a direction of a icon, activates its value on key release. **The action does not recognise mouse clicks, and only requires hovering**. Pie menu does nothing if the cursor is not moved out of the deadzone. +Pie menu is a widget displayed on the canvas while a key is pressed. It will disappear as soon, as the key is released. Moving cursor in a direction of an icon, activates its value on key release. **The action only requires hovering**. Pie menu does nothing if the cursor is not moved out of the deadzone. -Dragging a value enters `Edit mode` in which the keyboard button no longer needs to be pressed. In this mode values can be dragged accross the widget to change their order. When done, press the tick button to apply the changes. +Clicking the settings button in bottom-right corner switches into `Edit mode` which allows to modify pie. At this point the keyboard button no longer needs to be pressed. In this mode values one can: +- drag icons to change their order +- drag icons out of the ring to remove them +- drag icons from the settings window to add them + +Settings window visible in `Edit mode` also allows to change the local settings of the pie. When done, press the tick button to apply the changes. - ### Pick brush presets (red, green, blue) - Three color coded pie menus that let you pick a **brush preset** from related **tag** with brush presets. Used tags can be changed in **Tools > Scripts > Configure Shortcut Composer**. Default tag mapping is as follows: + Three color coded pie menus that let you pick a **brush preset** from related **tag** with brush presets. + + Used tag can be changed in pie settings by entering `Edit mode`. Presets in the pie depend on the tag, so they cannot be removed or added with dragging, but it is possible to change their order. When presets are added or remove from the tag, pie should update automatically. + + Default tag mapping is as follows: - red: "★ My Favorites" - green: "RGBA" - blue: "Erasers" - Presets in edited tags do not reload by themselves. Use **Tools > Scripts > Reload Shortcut Composer** or press apply/ok button in plugin configuration dialog. - - ### Pick misc tools - Pie menu for picking **active tools**. Includes tools that are used rather sporadically, and may not be worth a dedicated keyboard shortcut each: + Pie menu for picking **active tools**. It is recommended to change the default values being: - crop tool, - reference tool, - gradient tool, @@ -60,7 +78,7 @@ Dragging a value enters `Edit mode` in which the keyboard button no longer needs - assistant tool - ### Pick painting blending modes - Pie menu for picking **painting blending modes**. Consists of most commonly used ones: + Pie menu for picking **painting blending modes**. It is recommended to change the default values being: - normal - overlay, - color, @@ -71,7 +89,7 @@ Dragging a value enters `Edit mode` in which the keyboard button no longer needs - lighten - ### Create painting layer with blending mode - Pie menu for creating a new layer with picked **blending mode**. Consists of most commonly used ones: + Pie menu for creating a new layer with picked **blending mode**. It is recommended to change the default values being: - normal - erase - overlay, @@ -135,11 +153,17 @@ Multiple assignment is an action which cycles between multiple values of single Performing a long press, goes back to the `freehand brush tool`. Tools can be used while the key is pressed. + Default values can be modified in `Edit mode`. To enter it, long press the button, and click on the button which appears in top-left corner of painting area. + + Values are added by selecting the list value(s) on the left and pressing the green **add** button. Analogically, selecting values on the right, and pressing the **remove** button, removes the values from action. Dragging values on the right, allow to change their order. + - ### Cycle painting opacity Pressing a key repeatedly cycles brush predefined opacity values: `100%`, `70%`, `50%`, `30%` Performing a long press, goes back to the `100%` opacity. Modified opacity can be used while the key is pressed. + Currently does not allow to configure predefined values without editing code. + ### (`Temporary keys`): - ### Temporary move tool Pressing a key temporarily activates the `move tool` which goes back to the `freehand brush tool` after the key release. Short key presses allow to permanently toggle between those two tools. @@ -155,10 +179,6 @@ Multiple assignment is an action which cycles between multiple values of single ### Tweaking the global parameters Shortcut-Composer comes with a settings dialog available from krita topbar: **Tools > Scripts > Configure Shortcut Composer**. The dialog allows to change the following aspects of actions: -- Preset pie-menus mapping - - `Tag (red)` - tag to be used in red preset pie-menu - - `Tag (green)` - tag to be used in green preset pie-menu - - `Tag (blue)` - tag to be used in blue preset pie-menu - Common settings - `Short vs long press time` - Time in seconds distinguishing short key presses from long ones. - `FPS limit` - Maximum rate of Mouse Tracker and Pie Menu refresh. @@ -171,16 +191,6 @@ Shortcut-Composer comes with a settings dialog available from krita topbar: **To - `Pie deadzone global scale` - Global scale factor for the deadzone area of every pie menu. - `Pie animation time` - Time (in seconds) for fade-in animation when showing the pie menu. -### Changing the values in Pie menus and Multiple assignments -**Configure Shortcut Composer** has a separate tab called `Action values` in which you can add new values to some of the actions, as well as remove those that are not needed. - -Action to modify can be selected using the combobox on the top. - -Values are added by selecting the list value(s) on the left and pressing the green **add** button. Analogically, selecting values on the right, and pressing the **remove** button, removes the values from action. - -Dragging values on the right, allow to change their order. This can also be done using the `Edit mode` directly from the PieMenu (does not apply to MultipleAssignments) - - ### Modifying actions and creating custom ones While the settings dialog allows to tweak the values common for plugin actions, it does not allow to modify the behaviour of the actions or create new ones. @@ -199,12 +209,143 @@ To achieve that it is required to modify actions implementation: ### Known limitations - Pressing a modifier while the usual key is pressed, will result in conflict. For instance, pressing ctrl while using temporary eraser assigned to x will result in unwanted ctrl+x operation which cuts current layer. +- It is possible to activate multiple pie menus at the same time. +- Keyboard shortcuts assigned to actions can conflict with Canvas Input (General limitation of Krita). ## For krita plugin programmers +Some parts of plugin code solve general problems, which can apply outside of Shortcut Composer. Those solutions were placed in separate packages that can be copy-pasted into any other plugin and reused there. + +They depend only on original Krita API and PyQt5 with which krita is shipped. + +### Custom keyboard shortcut interface +Package `input_adapter` consists of `ActionManager` and `ComplexActionInterface` which together allow to recognise more keyboard events than usual krita action does. + +While usual actions can only recognise key press, implementing `ComplexActionInterface` lets you override methods performed on: +- key press +- short key release +- long key release +- every key release + +Each action needs to have public `name: str` attribute which is the same, as the one used in .action file, as well as `short_vs_long_press_time: float` which determines how many seconds need to elapse to consider that a key press was long. + +Use `ActionManager` instance to bind objects of those custom actions to krita during `CreateActions` phase: + +```python +""" +Print whether action key was released before of after +0.2 seconds from being pressed. +""" +from krita import Krita +from input_adapter import ActionManager, ComplexActionInterface + + +class CustomAction(ComplexActionInterface): + def __init__(self, name: str, press_time: float = 0.2): + self.name = name + self.short_vs_long_press_time = press_time + + def on_key_press(self): print("key was pressed") + def on_short_key_release(self): print("key released before than 0.2s") + def on_long_key_release(self): print("key released later than after 0.2s") + def on_every_key_release(self): pass + + +class MyExtension(Extension): + def setup(self) -> None: pass + def createActions(self, window) -> None: + action = CustomAction(name="Custom action name") + self.manager = ActionManager(window) + self.manager.bind_action(action) + +Krita.instance().addExtension(MyExtension(Krita.instance())) +``` + +### Config system +Package `config_system` consists of `Field` and `FieldGroup` which grant object-oriented API to control kritarc configuration file easier, than with API of krita. + +--- + +`Field` represents a single value in kritarc file. Once initialized with its group name, name and default value, it allows to: +- write a given value to kritarc. +- read current value from kritarc, parsing it to correct python type. +- reset the value to default. +- register a callback run on each value change. + +Type of default value passed on initlization is remembered, and used to parse values both on read and write. Supported types are: +- `int`, `list[int]`, +- `float`, `list[float]`, +- `str`, `list[str]`, +- `bool`, `list[bool]`, +- `Enum`, `list[Enum]` + +For empty, homogeneous lists, `parser_type` argument must be used to determine type of list elements. Default values are not saved when until the field does not exist in kritarc. Repeated saves of the same value are filtered, so that callbacks are not called when the same value is written multiple times one after the other. + +--- + +`FieldGroup` represents a section of fields in kritarc file. It simplifies the field creation by auto-completing the group name. + +FieldGroup holds and aggregates fields created with it. It allows to reset all the fields at once, and register a callback to all its fields: both existing and future ones. + +--- + +Example usage: +```python +from enum import Enum +from config_system import FieldGroup + + +class EnumMock(Enum): + MODE_A = 0 + MODE_B = 1 + +# Create a config group +group = FieldGroup("MyGroup") +# Register a callback on all three fields +group.register_callback(lambda: print("any field changed")) + +# Create three fields inside a group - for string and two enum lists +str_field = group.field(name="my_str", default="Sketch") +enums_field_1 = group.field("my_enums_1", [], parser_type=EnumMock) +enums_field_2 = group.field("my_enums_2", [EnumMock.MODE_A]) + +# Register a different callback on each field +str_field.register_callback(lambda: print("string changed")) +enums_field_1.register_callback(lambda: print("enum 1 changed")) +enums_field_2.register_callback(lambda: print("enum 2 changed")) + +# Change the value from default "Sketch" to "Digital" +str_field.write("Digital") +# Change the value from empty list to one with two values +enums_field_1.write([EnumMock.MODE_A, EnumMock.MODE_B]) +# Repeat the default value. Will be filtered +enums_field_2.write([EnumMock.MODE_A]) + +# The program will not break, as red values are the same as written ones +assert str_field.read() == "Digital" +assert enums_field_1.read() == [EnumMock.MODE_A, EnumMock.MODE_B] +assert enums_field_2.read() == [EnumMock.MODE_A] +``` + +The code above produces "MyGroup" section in kritarc file. my_enums_2 is missing, as the default value was not changed: +```toml +[MyGroup] +my_str=Digital +my_enums_1=MODE_A\tMODE_B +``` + +Registered callbacks outputs on the terminal: +``` +any field changed +string changed +any field changed +enum 1 changed +``` + +Calling `group.reset_defaults()` would change both values back to their defaults, and produce the same output on the terminal, as resetting changes the fields. + ### Alternative API -The extension consists of elements that can be reused in other krita plugins under GPL-3.0-or-later license. Package `api_krita` wrappes krita api offering PEP8 compatibility, typings, and docstring documentation. Most of objects attributes are now available as settables properties. +Package `api_krita` wraps krita api offering PEP8 compatibility, typings, and docstring documentation. Most of objects attributes are now available as settables properties. -Copy `api_krita` to the extension directory, to access syntax such as: ```python from .api_krita import Krita from .api_krita.enums import BlendingMode, Tool, Toggle @@ -237,34 +378,3 @@ Toggle.PRESERVE_ALPHA.switch_state() # change state of preserve alpha ``` Only functionalities that were needed during this plugin development are wrapped, so some of them are not yet available. The syntax can also change over time. - -### Custom keyboard shortcut interface -Package `input_adapter` consists of `ActionManager` and `ComplexAction` which grants extended interface for creating keyboard shortcuts. - -While usual actions can only recognise key press, subclassing `ComplexAction` lets you override methods performed on: -- key press -- short key release -- long key release -- every key release - -Then use `ActionManager` instance to bind objects of those custom actions to krita during CreateActions phase: - -```python -from api_krita import Extension -from input_adapter import ActionManager, ComplexAction - - -class CustomAction(ComplexAction): - def on_key_press(self): ... - def on_short_key_release(self): ... - def on_long_key_release(self): ... - def on_every_key_release(self): ... - - -class ExtensionName(Extension): - ... - def createActions(self, window) -> None: - action = CustomAction(name="Custom action name") - self.manager = ActionManager(window) - self.manager.bind_action(action) -``` diff --git a/shortcut_composer/api_krita/core_api.py b/shortcut_composer/api_krita/core_api.py index 424d70f8..dbea2368 100644 --- a/shortcut_composer/api_krita/core_api.py +++ b/shortcut_composer/api_krita/core_api.py @@ -78,7 +78,7 @@ def read_setting( default: str = "Not stored" ) -> Optional[str]: """ - Read a setting from .kritarc file. + Read a setting from kritarc file. - Return string red from file if present - Return default if it was given @@ -88,7 +88,7 @@ def read_setting( return None if red_value == "Not stored" else red_value def write_setting(self, group: str, name: str, value: Any) -> None: - """Write setting to .kritarc file. Value type will be lost.""" + """Write setting to kritarc file. Value type will be lost.""" self.instance.writeSetting(group, name, str(value)) def create_action( diff --git a/shortcut_composer/config_system/__init__.py b/shortcut_composer/config_system/__init__.py index aa3542bc..e9a392df 100644 --- a/shortcut_composer/config_system/__init__.py +++ b/shortcut_composer/config_system/__init__.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later """ -System granting easier API to control krita configuration in .kritarc. +System granting easier API to control krita configuration in kritarc. Consists of two classes: "Field" and "FieldGroup". Read the documentation of those classes for more info. diff --git a/shortcut_composer/config_system/api_krita.py b/shortcut_composer/config_system/api_krita.py index ba13dc93..f16bc553 100644 --- a/shortcut_composer/config_system/api_krita.py +++ b/shortcut_composer/config_system/api_krita.py @@ -20,7 +20,7 @@ def read_setting( default: str = "Not stored" ) -> Optional[str]: """ - Read a setting from .kritarc file. + Read a setting from kritarc file. - Return string red from file if present - Return default if it was given @@ -30,7 +30,7 @@ def read_setting( return None if red_value == "Not stored" else red_value def write_setting(self, group: str, name: str, value: Any) -> None: - """Write setting to .kritarc file. Value type will be lost.""" + """Write setting to kritarc file. Value type will be lost.""" self.instance.writeSetting(group, name, str(value)) diff --git a/shortcut_composer/config_system/field.py b/shortcut_composer/config_system/field.py index cc0fb18d..7b2ff8f5 100644 --- a/shortcut_composer/config_system/field.py +++ b/shortcut_composer/config_system/field.py @@ -8,25 +8,30 @@ class Field(Generic[T]): """ - Representation of a single value in .kritarc file. + Representation of a single value in kritarc file. - Fields are type aware, and allow to read and write from krita config - automatically parsing to the value type. + Once initialized with its group name, name, and default value, it + allows to: + - write a given value to kritarc. + - read current value from kritarc, parsing it to correct python type. + - reset the value to default. + - register a callback run on each value change. Type of default value passed on initlization is remembered, and used to parse values both on read and write. Supported types are: - -`int`, - -`float`, - -`str`, - -`bool`, - -`custom Enums` - and homogeneous lists of every type above. + - `int`, `list[int]`, + - `float`, `list[float]`, + - `str`, `list[str]`, + - `bool`, `list[bool]`, + - `Enum`, `list[Enum]` - For empty, homogeneous lists, `parser_type` must be used to + For empty, homogeneous lists, `parser_type` argument must be used to determine type of list elements. - Callbacks can be registered to the field, to run a method each time - the value changes. Repeated saves of the same value are filtered. + Default values are not saved when until the field does not exist in + kritarc. Repeated saves of the same value are filtered, so that + callbacks are not called when the same value is written multiple + times one after the other. """ def __new__( @@ -44,22 +49,22 @@ def __new__( return NonListField(config_group, name, default) config_group: str - """Configuration section in .kritarc toml file.""" + """Configuration section in kritarc toml file.""" name: str """Field name in entire config group.""" default: T """Default value used when the field is not present in file.""" def write(self, value: T) -> None: - """Write a value to .kritarc file.""" + """Write a value to kritarc file.""" def read(self) -> T: - """Return value from .kritarc parsed to field type.""" + """Return value from kritarc parsed to field type.""" ... def register_callback(self, callback: Callable[[], None]) -> None: """Register a method which will be called when field value changes.""" def reset_default(self) -> None: - """Write a default value to .kritarc file.""" + """Write a default value to kritarc file.""" ... diff --git a/shortcut_composer/config_system/field_base.py b/shortcut_composer/config_system/field_base.py index bcf08d5a..3ba0c97e 100644 --- a/shortcut_composer/config_system/field_base.py +++ b/shortcut_composer/config_system/field_base.py @@ -54,7 +54,7 @@ def write(self, value: T): @abstractmethod def read(self) -> T: - """Return value from .kritarc parsed to field type.""" + """Return value from kritarc parsed to field type.""" ... @abstractmethod @@ -76,7 +76,7 @@ def _is_write_redundant(self, value: T) -> bool: return raw is None and value == self.default def reset_default(self) -> None: - """Write a default value to .kritarc file.""" + """Write a default value to kritarc file.""" self.write(self.default) @staticmethod diff --git a/shortcut_composer/config_system/field_group.py b/shortcut_composer/config_system/field_group.py index 0a9868c3..6dbf72ec 100644 --- a/shortcut_composer/config_system/field_group.py +++ b/shortcut_composer/config_system/field_group.py @@ -9,16 +9,15 @@ class FieldGroup: """ - Representation of section in .kritarc toml file. + Representation of section in fields in kritarc file. All fields in the group should be created using `field()` method. It simplifies the field creation by auto-completing the group name. FieldGroup holds and aggregates fields created with it. - Allows to reset all the fields at once, as well as register a - callback to all existing fields, as well as those that will be - added to the group in the future. + Allows to reset all the fields at once, and register a callback to + all its fields: both existing and future ones. """ def __init__(self, name: str) -> None: diff --git a/shortcut_composer/config_system/field_implementations.py b/shortcut_composer/config_system/field_implementations.py index 78afc719..c8cd98e1 100644 --- a/shortcut_composer/config_system/field_implementations.py +++ b/shortcut_composer/config_system/field_implementations.py @@ -28,7 +28,7 @@ def __init__( self._parser: Parser[T] = self._get_parser(type(self.default)) def read(self) -> T: - """Return value from .kritarc parsed to field type.""" + """Return value from kritarc parsed to field type.""" raw = Krita.read_setting(self.config_group, self.name) if raw is None: return self.default @@ -73,7 +73,7 @@ def _get_type(self, passed_type: Optional[type]) -> type: def read(self) -> List[T]: """ - Return value from .kritarc parsed to field type. + Return value from kritarc parsed to field type. Each list element requires parsing. """ diff --git a/shortcut_composer/config_system/ui/__init__.py b/shortcut_composer/config_system/ui/__init__.py index 43b7961e..957f79c9 100644 --- a/shortcut_composer/config_system/ui/__init__.py +++ b/shortcut_composer/config_system/ui/__init__.py @@ -7,8 +7,8 @@ Grants QWidgets paired with configuration fields. All widgets have unified interface for reading and setting their values. -They also allow to directly fill them with values from .kritarc, save -their current values to .kritarc, and reset them to default values of +They also allow to directly fill them with values from kritarc, save +their current values to kritarc, and reset them to default values of fields they hold. ConfigFormWidget is a class that aggregates multiple input widgets, diff --git a/shortcut_composer/config_system/ui/config_based_widget.py b/shortcut_composer/config_system/ui/config_based_widget.py index 18b1f4cb..f17e5a95 100644 --- a/shortcut_composer/config_system/ui/config_based_widget.py +++ b/shortcut_composer/config_system/ui/config_based_widget.py @@ -48,7 +48,7 @@ def reset(self) -> None: self.set(self.config_field.read()) def save(self) -> None: - """Save the current value of the widget to .kritarc.""" + """Save the current value of the widget to kritarc.""" self.config_field.write(self.read()) def _init_pretty_name(self, pretty_name: Optional[str]) -> str: diff --git a/shortcut_composer/input_adapter/action_manager.py b/shortcut_composer/input_adapter/action_manager.py index 0888e51b..98a5c97a 100644 --- a/shortcut_composer/input_adapter/action_manager.py +++ b/shortcut_composer/input_adapter/action_manager.py @@ -22,7 +22,7 @@ class ActionContainer: """ Holds action elements together. - - `ComplexAction` is the action implementation. + - `ComplexActionInterface` is the action implementation. - `QWidgetAction` krita representation, which ComplexAction implements. - `ShortcutAdapter` manages running elements of ComplexAction interface at right time. @@ -43,10 +43,13 @@ def replace_action(self, new_action: ComplexActionInterface) -> None: class ActionManager: """ - Creates and stores `ActionContainers` from `ComplexActions`. + Creates and stores `ActionContainers` from `ComplexActionInterfaces`. + + Ensures, that methods of the action interface will be called at + appropriate keyboard input events. `QWidgetAction` and `ShortcutAdapter` are created and stored in - container along with passed `ComplexAction` by using the + container along with passed `ComplexActionInterfaces` by using the bind_action() method. """ diff --git a/shortcut_composer/input_adapter/complex_action_interface.py b/shortcut_composer/input_adapter/complex_action_interface.py index 3b6f9452..6c9ac1ea 100644 --- a/shortcut_composer/input_adapter/complex_action_interface.py +++ b/shortcut_composer/input_adapter/complex_action_interface.py @@ -6,10 +6,10 @@ class ComplexActionInterface(Protocol): """ - Interface of the main plugin action. + Interface of action with extended keyboard events recognition. - `name` -- unique name of action. Must match the definition in - shortcut_composer.action file + the .action file - `short_vs_long_press_time` -- time [s] that specifies if key press is short or long. """ diff --git a/shortcut_composer/templates/multiple_assignment_utils/action_values.py b/shortcut_composer/templates/multiple_assignment_utils/action_values.py index f5d842d1..e19760fc 100644 --- a/shortcut_composer/templates/multiple_assignment_utils/action_values.py +++ b/shortcut_composer/templates/multiple_assignment_utils/action_values.py @@ -89,7 +89,7 @@ def remove(self) -> None: self.available_list.addItems(sorted(new_available)) def apply(self) -> None: - """Save the right list into .kritarc.""" + """Save the right list into kritarc.""" to_write: List[Enum] = [] for row in range(self.current_list.count()): text = self.current_list.item(row).text() @@ -98,7 +98,7 @@ def apply(self) -> None: self.config.write(to_write) def refresh(self) -> None: - """Refresh right list with .kritarc values and left one accordingly.""" + """Refresh right list with kritarc values and left one accordingly.""" self.current_list.clear() current_list = self.config.read() text_list = [item.name for item in current_list] diff --git a/shortcut_composer/templates/pie_menu_utils/pie_config.py b/shortcut_composer/templates/pie_menu_utils/pie_config.py index 17b6b814..cea2bcb5 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_config.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_config.py @@ -17,12 +17,12 @@ class PieConfig(FieldGroup, Generic[T], ABC): """Is it allowed to remove elements in runtime. """ name: str - """Name of the group in .kritarc.""" + """Name of the group in kritarc.""" background_color: Optional[QColor] active_color: QColor ORDER: Field[List[T]] - """Value order stored in .kritarc.""" + """Value order stored in kritarc.""" PIE_RADIUS_SCALE: Field[float] ICON_RADIUS_SCALE: Field[float] @@ -37,7 +37,7 @@ class PresetPieConfig(PieConfig[str]): FieldGroup representing config of PieMenu of presets. Values are calculated according to presets belonging to handled tag - and the custom order saved by the user in .kritarc. + and the custom order saved by the user in kritarc. """ ALLOW_REMOVE = False @@ -62,7 +62,7 @@ def __init__( self.active_color = active_color def values(self) -> List[str]: - """Return all presets from the tag. Respect order from .kritarc.""" + """Return all presets from the tag. Respect order from kritarc.""" saved_order = self.ORDER.read() tag_values = Tag(self.TAG_NAME.read()) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index 25d6bcfb..fb14871a 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -71,6 +71,6 @@ def show(self): super().show() def hide(self) -> None: - """Hide the window after its settings are saved to .kritarc.""" + """Hide the window after its settings are saved to kritarc.""" self._local_settings.apply() super().hide() diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_settings.py index ae637c85..3b4dd150 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_settings.py @@ -39,6 +39,6 @@ def show(self): super().show() def hide(self) -> None: - """Hide the window after its settings are saved to .kritarc.""" + """Hide the window after its settings are saved to kritarc.""" self._local_settings.apply() super().hide() diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py index b261da3e..c2b077a5 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py @@ -48,7 +48,7 @@ def show(self): super().show() def hide(self) -> None: - """Hide the window after its settings are saved to .kritarc.""" + """Hide the window after its settings are saved to kritarc.""" self._local_settings.apply() super().hide() From 07ab4f2e8f994a90559b4a64eb80d1a1971e1188 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Mon, 10 Apr 2023 16:42:18 +0200 Subject: [PATCH 121/125] Improve pretty names of enums --- .../api_krita/enums/blending_mode.py | 5 +- .../api_krita/enums/node_types.py | 3 +- shortcut_composer/api_krita/enums/toggle.py | 3 +- shortcut_composer/api_krita/enums/tool.py | 53 ++++++++++--------- .../api_krita/enums/transform_mode.py | 3 +- 5 files changed, 37 insertions(+), 30 deletions(-) diff --git a/shortcut_composer/api_krita/enums/blending_mode.py b/shortcut_composer/api_krita/enums/blending_mode.py index 686dff87..190c3571 100644 --- a/shortcut_composer/api_krita/enums/blending_mode.py +++ b/shortcut_composer/api_krita/enums/blending_mode.py @@ -143,4 +143,7 @@ class BlendingMode(Enum): @property def pretty_name(self): - return self.name + """Format blending mode name like: `Darker Color`.""" + parts = self.name.split("_") + parts = [f"{part[0]}{part[1:].lower()}" for part in parts] + return " ".join(parts) diff --git a/shortcut_composer/api_krita/enums/node_types.py b/shortcut_composer/api_krita/enums/node_types.py index 92652559..7ee0359e 100644 --- a/shortcut_composer/api_krita/enums/node_types.py +++ b/shortcut_composer/api_krita/enums/node_types.py @@ -35,7 +35,8 @@ def icon(self) -> QIcon: @property def pretty_name(self): - return self.name + """Format node type name like: `Paint layer`.""" + return f"{self.name[0]}{self.name[1:].lower().replace('_', ' ')}" _ICON_NAME_MAP = { diff --git a/shortcut_composer/api_krita/enums/toggle.py b/shortcut_composer/api_krita/enums/toggle.py index ae08e32b..1e59ee63 100644 --- a/shortcut_composer/api_krita/enums/toggle.py +++ b/shortcut_composer/api_krita/enums/toggle.py @@ -28,7 +28,8 @@ class Toggle(Enum): @property def pretty_name(self): - return self.name + """Format toggle name like: `Preserve alpha`.""" + return f"{self.name[0]}{self.name[1:].lower().replace('_', ' ')}" @property def state(self) -> bool: diff --git a/shortcut_composer/api_krita/enums/tool.py b/shortcut_composer/api_krita/enums/tool.py index 8f6149c5..760e09b1 100644 --- a/shortcut_composer/api_krita/enums/tool.py +++ b/shortcut_composer/api_krita/enums/tool.py @@ -16,42 +16,42 @@ class Tool(Enum): Example usage: `Tool.FREEHAND_BRUSH` """ + SHAPE_SELECT = "InteractionTool" + TEXT = "SvgTextTool" + EDIT_SHAPES = "PathTool" + CALLIGRAPHY = "KarbonCalligraphyTool" FREEHAND_BRUSH = "KritaShape/KisToolBrush" - FREEHAND_SELECTION = "KisToolSelectOutline" - GRADIENT = "KritaFill/KisToolGradient" LINE = "KritaShape/KisToolLine" + RECTANGLE = "KritaShape/KisToolRectangle" + ELLIPSE = "KritaShape/KisToolEllipse" + POLYGON = "KisToolPolygon" + POLYLINE = "KisToolPolyline" + BEZIER_PATH = "KisToolPath" + FREEHAND_PATH = "KisToolPencil" + DYNAMIC_BRUSH = "KritaShape/KisToolDyna" + MULTI_BRUSH = "KritaShape/KisToolMultiBrush" TRANSFORM = "KisToolTransform" MOVE = "KritaTransform/KisToolMove" - RECTANGULAR_SELECTION = "KisToolSelectRectangular" - CONTIGUOUS_SELECTION = "KisToolSelectContiguous" - REFERENCE = "ToolReferenceImages" CROP = "KisToolCrop" - BEZIER_PATH = "KisToolPath" - FREEHAND_PATH = "KisToolPencil" - POLYLINE = "KisToolPolyline" - SHAPE_SELECT = "InteractionTool" - ASSISTANTS = "KisAssistantTool" + GRADIENT = "KritaFill/KisToolGradient" COLOR_SAMPLER = "KritaSelected/KisToolColorSampler" - POLYGON = "KisToolPolygon" - MEASUREMENT = "KritaShape/KisToolMeasure" - TEXT = "SvgTextTool" - ELLIPSE = "KritaShape/KisToolEllipse" + COLORIZE_MASK = "KritaShape/KisToolLazyBrush" + SMART_PATCH = "KritaShape/KisToolSmartPatch" FILL = "KritaFill/KisToolFill" ENCLOSE_AND_FILL = "KisToolEncloseAndFill" - BEZIER_SELECTION = "KisToolSelectPath" - DYNAMIC_BRUSH = "KritaShape/KisToolDyna" - RECTANGLE = "KritaShape/KisToolRectangle" - PAN = "PanTool" - MULTI_BRUSH = "KritaShape/KisToolMultiBrush" - EDIT_SHAPES = "PathTool" + ASSISTANTS = "KisAssistantTool" + MEASUREMENT = "KritaShape/KisToolMeasure" + REFERENCE = "ToolReferenceImages" + RECTANGULAR_SELECTION = "KisToolSelectRectangular" ELIPTICAL_SELECTION = "KisToolSelectElliptical" - SMART_PATCH = "KritaShape/KisToolSmartPatch" - COLORIZE_MASK = "KritaShape/KisToolLazyBrush" + POLYGONAL_SELECTION = "KisToolSelectPolygonal" + FREEHAND_SELECTION = "KisToolSelectOutline" + CONTIGUOUS_SELECTION = "KisToolSelectContiguous" SIMILAR_COLOR_SELECTION = "KisToolSelectSimilar" - ZOOM = "ZoomTool" + BEZIER_SELECTION = "KisToolSelectPath" MAGNETIC_SELECTION = "KisToolSelectMagnetic" - CALLIGRAPHY = "KarbonCalligraphyTool" - POLYGONAL_SELECTION = "KisToolSelectPolygonal" + ZOOM = "ZoomTool" + PAN = "PanTool" def activate(self): Api.instance().action(self.value).trigger() @@ -69,7 +69,8 @@ def icon(self) -> QIcon: @property def pretty_name(self): - return self.name + """Format tool name like: `Shape select tool`.""" + return f"{self.name[0]}{self.name[1:].lower().replace('_', ' ')} tool" _PAINTABLE = { diff --git a/shortcut_composer/api_krita/enums/transform_mode.py b/shortcut_composer/api_krita/enums/transform_mode.py index 7132e3ce..27d745af 100644 --- a/shortcut_composer/api_krita/enums/transform_mode.py +++ b/shortcut_composer/api_krita/enums/transform_mode.py @@ -40,7 +40,8 @@ def icon(self) -> QIcon: @property def pretty_name(self) -> str: - return self.name + """Format mode name like: `Liquify`.""" + return f"{self.name[0]}{self.name[1:].lower()}" _ICON_NAME_MAP = { From 631f70c2046ee838b9d6ea20c9e3dc7d67007353 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Mon, 10 Apr 2023 20:43:07 +0200 Subject: [PATCH 122/125] Fix crash when widget from other scrollarea dragged into pie --- .../templates/pie_menu_utils/pie_widget.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index fe19e89e..458e1a69 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -117,12 +117,16 @@ def dragMoveEvent(self, e: QDragMoveEvent) -> None: # Drag incoming from outside the PieWidget ecosystem return + if self.type and not isinstance(source_widget.label.value, self.type): + # Label type does not match the type of pie menu + return + self._last_widget = source_widget if distance > self._style.widget_radius: # Dragged out of the PieWidget return self.label_holder.remove(source_widget.label) if distance < self._style.deadzone_radius: - # Dragged over the deadzone + # Do nothing in deadzone return angle = self._circle_points.angle_from_point(pos) @@ -154,3 +158,10 @@ def set_draggable(self, draggable: bool): def _widget_holder(self) -> WidgetHolder: """Return the holder with child widgets.""" return self.label_holder.widget_holder + + @property + def type(self) -> Optional[type]: + """Return type of values stored in labels. None if no labels.""" + if not self._labels: + return None + return type(self._labels[0].value) From 956845bc1b38a6229f73ac56dbbed7add47dc94f Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Mon, 10 Apr 2023 20:43:45 +0200 Subject: [PATCH 123/125] Do not restart pie when button pressed while in edit mode --- shortcut_composer/templates/pie_menu.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index 899359c0..f50b9e08 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -134,6 +134,9 @@ def _move_buttons(self): def on_key_press(self) -> None: """Reload labels, start GUI manager and run instructions.""" + if self.pie_widget.isVisible(): + return + self._controller.refresh() new_values = self._config.values() From 2a5e32eb51de7e86a0305e393830b89c69c51dfe Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Mon, 10 Apr 2023 20:44:18 +0200 Subject: [PATCH 124/125] Rename QLabel "Icon scale" to "Icon max scale" --- .../templates/pie_menu_utils/settings_gui/enum_pie_settings.py | 2 +- .../pie_menu_utils/settings_gui/numeric_pie_settings.py | 2 +- .../pie_menu_utils/settings_gui/preset_pie_settings.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py index fb14871a..99b317db 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/enum_pie_settings.py @@ -44,7 +44,7 @@ def __init__( self._local_settings = ConfigFormWidget([ ConfigSpinBox(config.PIE_RADIUS_SCALE, self, "Pie scale", 0.05, 4), ConfigSpinBox( - config.ICON_RADIUS_SCALE, self, "Icon scale", 0.05, 4), + config.ICON_RADIUS_SCALE, self, "Icon max scale", 0.05, 4), ]) tab_holder.addTab(self._local_settings, "Local settings") diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_settings.py index 3b4dd150..d70b90bb 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/numeric_pie_settings.py @@ -26,7 +26,7 @@ def __init__( self._local_settings = ConfigFormWidget([ ConfigSpinBox(config.PIE_RADIUS_SCALE, self, "Pie scale", 0.05, 4), ConfigSpinBox( - config.ICON_RADIUS_SCALE, self, "Icon scale", 0.05, 4), + config.ICON_RADIUS_SCALE, self, "Icon max scale", 0.05, 4), ]) layout = QVBoxLayout(self) diff --git a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py index c2b077a5..4b8a6bc9 100644 --- a/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py +++ b/shortcut_composer/templates/pie_menu_utils/settings_gui/preset_pie_settings.py @@ -34,7 +34,7 @@ def __init__( ConfigComboBox(config.TAG_NAME, self, "Tag name", self._tags), ConfigSpinBox(config.PIE_RADIUS_SCALE, self, "Pie scale", 0.05, 4), ConfigSpinBox( - config.ICON_RADIUS_SCALE, self, "Icon scale", 0.05, 4), + config.ICON_RADIUS_SCALE, self, "Icon max scale", 0.05, 4), ]) layout = QVBoxLayout(self) From 7cc4e6aef5c8ecda949f252676c1493dc62d91f7 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Tue, 11 Apr 2023 17:05:00 +0200 Subject: [PATCH 125/125] Prepared manuals for release --- README.md | 19 +- shortcut_composer/INFO.py | 2 +- shortcut_composer/manual.html | 866 ++++++++++++++++++++-------------- 3 files changed, 523 insertions(+), 364 deletions(-) diff --git a/README.md b/README.md index be038f1a..6ce46b70 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,11 @@ The plugin adds new shortcuts of the following types: - `Multiple assignment` - repeatedly pressing a key, cycles between multiple values of krita property. - `Temporary key` - temporarily activates a krita property with long press or toggles it on/off with short press. -[![PIE MENUS - introducing Shortcut Composer](http://img.youtube.com/vi/hrjBycVYFZM/0.jpg)](https://www.youtube.com/watch?v=hrjBycVYFZM "PIE MENUS - introducing Shortcut Composer") +## What's new in **1.2.0** + +[![PIE MENUS - introducing Shortcut Composer](http://img.youtube.com/vi/Tkf2-U0OyG4/0.jpg)](https://www.youtube.com/watch?v=Tkf2-U0OyG4 "PIE MENUS - introducing Shortcut Composer") + -## What's new in `v1.2.0` ### Added - Adding and removing PieMenu icons with drag and drop. - Class-oriented configuration system with automatic value parsing. Can be reused in other plugins. @@ -31,12 +33,17 @@ The plugin adds new shortcuts of the following types: Check out historic [changelogs](https://github.com/wojtryb/Shortcut-Composer/wiki/Change-log). +## Plugin release video: + +[![PIE MENUS - introducing Shortcut Composer](http://img.youtube.com/vi/hrjBycVYFZM/0.jpg)](https://www.youtube.com/watch?v=hrjBycVYFZM "PIE MENUS - introducing Shortcut Composer") + ## Requirements -Shortcut Composer **v1.2.0** Requires krita **5.1.0** or later. +- Version of krita on plugin release: **5.1.5** +- Required version of krita: **5.1.0** OS support state: -- [x] Windows 10/11 -- [x] Linux +- [x] Windows (10, 11) +- [x] Linux (Ubuntu 20.04, 22.04) - [ ] MacOS (Known bug of canvas losing focus after using PieMenu) - [ ] Android (Does not support python plugins yet) @@ -327,7 +334,7 @@ assert enums_field_2.read() == [EnumMock.MODE_A] ``` The code above produces "MyGroup" section in kritarc file. my_enums_2 is missing, as the default value was not changed: -```toml +``` [MyGroup] my_str=Digital my_enums_1=MODE_A\tMODE_B diff --git a/shortcut_composer/INFO.py b/shortcut_composer/INFO.py index 7b9fbbff..d324cbd8 100644 --- a/shortcut_composer/INFO.py +++ b/shortcut_composer/INFO.py @@ -1,6 +1,6 @@ # SPDX-FileCopyrightText: © 2022-2023 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -__version__ = "1.2.0 prealpha" +__version__ = "1.2.0" __author__ = "Wojciech Trybus" __license__ = "GPL-3.0-or-later" diff --git a/shortcut_composer/manual.html b/shortcut_composer/manual.html index d0537645..8a0e180d 100644 --- a/shortcut_composer/manual.html +++ b/shortcut_composer/manual.html @@ -4,333 +4,518 @@ - Shortcut Composer + Shortcut Composer -

Shortcut composer

-

Extension for painting application Krita, which allows to create custom, complex - keyboard shortcuts.

-

The plugin adds new shortcuts of the following types:

-
    -
  • Pie menu - while key is pressed, displays a pie menu, which allows to pick values by hovering a - mouse.
  • -
  • Cursor tracker - while key is pressed, tracks a cursor, switching values according to cursor - offset. -
  • -
  • Canvas preview - Temporarily changes canvas elements while the key is pressed.
  • -
  • Multiple assignment - repeatedly pressing a key, cycles between multiple values of krita property. -
  • -
  • Temporary key - temporarily activates a krita property with long press or toggles it on/off with - short press.
  • -
-

What's new in v1.1.1

-

Fixed

-
    -
  • Fix a crash when any PieMenu is empty.
  • -
  • Support multiple krita windows.
  • -
  • Fix transform modes ocasionally stopping to work.
  • -
  • Handle invalid configuration.
  • -
  • Add missing icon for transform tool.
  • -
-

Requirements

-

Shortcut Composer v1.1.1 Requires krita 5.1.0 or later.

-

OS support state:

-
    -
  • [x] Windows 10/11
  • -
  • [x] Linux
  • -
  • [ ] MacOS (Known bug of canvas losing focus after using PieMenu)
  • -
  • [ ] Android (Does not support python plugins yet)
  • -
-

Installation:

-
    -
  1. on github project page, click the green button - code and pick the download zip option. Do not extract it.
  2. -
  3. in krita's topbar, open Tools > Scripts > Import Python Plugin From File and pick the - downloaded .zip file
  4. -
  5. restart krita.
  6. -
  7. set custom shortcuts in Settings > Configure Krita > Keyboard Shortcuts under - Scripts > Shortcut Composer: Complex Actions section. By intention, there are no default - bindings.
  8. -
-

Pre-made actions

-

While Shortcut-Composer is highly configurable and extendable, the add-on comes with pre-made, plug-and-play - actions. -

-

(Pie menus):

-

Pie menu is a widget displayed on the canvas while a key is pressed. It will disappear as soon, as the key is - released. Moving cursor in a direction of a icon, activates its value on key release. The action does not - recognise mouse clicks, and only requires hovering. Pie menu does nothing if the cursor is not moved out - of - the deadzone.

-

Dragging a value enters Edit mode in which the keyboard button no longer needs to be pressed. In this - mode values can be dragged accross the widget to change their order. When done, press the tick button to apply the - changes.

-
    -
  • -

    Pick brush presets (red, green, blue)

    -

    Three color coded pie menus that let you pick a brush preset from related tag - with brush presets. Used tags can be changed in Tools > Scripts > Configure Shortcut - Composer. Default tag mapping is as follows:

    -
      -
    • red: "★ My Favorites"
    • -
    • green: "RGBA"
    • -
    • blue: "Erasers"
    • -
    -

    Presets in edited tags do not reload by themselves. Use Tools > Scripts > Reload Shortcut - Composer or press apply/ok button in plugin configuration dialog.

    -
  • -
  • -

    Pick misc tools

    -

    Pie menu for picking active tools. Includes tools that are used rather sporadically, and may - not - be worth a dedicated keyboard shortcut each:

    -
      -
    • crop tool,
    • -
    • reference tool,
    • -
    • gradient tool,
    • -
    • multi_brush tool,
    • -
    • assistant tool
    • -
    -
  • -
  • -

    Pick painting blending modes

    -

    Pie menu for picking painting blending modes. Consists of most commonly used ones:

    -
      -
    • normal
    • -
    • overlay,
    • -
    • color,
    • -
    • multiply,
    • -
    • add,
    • -
    • screen,
    • -
    • darken,
    • -
    • lighten
    • -
    -
  • -
  • -

    Create painting layer with blending mode

    -

    Pie menu for creating a new layer with picked blending mode. Consists of most commonly used - ones:

    -
      -
    • normal
    • -
    • erase
    • -
    • overlay,
    • -
    • color,
    • -
    • multiply,
    • -
    • add,
    • -
    • screen,
    • -
    • darken,
    • -
    • lighten
    • -
    -
  • -
-

(Cursor trackers):

-

Cursor tracker is an action for switching values using cursor movement, while the keyboard key is pressed. It - changes - a single krita property according to the cursor movement along horizontal or vertical axis. The action does - not recognise mouse clicks, and only requires hovering

-
    -
  • -

    Scroll isolated layers

    -

    Scrolls the layers by sliding the cursor vertically. Can be used for picking the active layer and analizing the - layer stack. While the key is pressed, isolates the active layer to give better response of which layer is - active. -

    -
      -
    • Key press: isloate active layer
    • -
    • Horizontal: -
    • -
    • Vertical: scroll all layers
    • -
    -
  • -
  • -

    Scroll timeline or animated layers

    -

    Variation on "Scroll isolated layers" for animators. Scrolling is restricted only to layers pinned to - the animation timeline. Horizontal mouse movement changes the current frame.

    -
      -
    • Key press: isloate active layer
    • -
    • Horizontal: scroll animation frames
    • -
    • Vertical: scroll layers pinned to timeline
    • -
    -
  • -
  • -

    Scroll undo stack

    -

    Extends the krita undo action ctrl+z. While the key is pressed, horizontal mouse movement - controls the undo stack by performing undo and redo actions. Usual undo with short key press is still possible. -

    -
      -
    • Key press: undo last operation
    • -
    • Horizontal: scroll left to undo, or right to redo
    • -
    • Vertical: -
    • -
    -
  • -
  • -

    Scroll brush size or opacity

    -

    Allows to control both brush size or opacity with a single key. Opacity changes - contiguously with vertical mouse movement, while brush size snaps to custom values.

    -
      -
    • Key press: -
    • -
    • Horizontal: scroll brush size (descrete)
    • -
    • Vertical: scroll painting opacity (contiguous)
    • -
    -
  • -
  • -

    Scroll canvas zoom or rotation

    -

    Allows to control both canvas zoom or canvas rotation with a single key. Does not - block - the ability to paint.

    -
      -
    • Key press: -
    • -
    • Horizontal: canvas rotation (contiguous)
    • -
    • Vertical: canvas zoom (contiguous)
    • -
    -
  • -
-

(Canvas previews):

-

Canvas preview is an action which changes canvas elements when the key is pressed, and changes them back to their - original state on key release.

-
    -
  • -

    Preview current layer visibility

    -

    Changes active layer visibility on key press and release. Allows to quickly check layer's content.

    -
  • -
  • -

    Preview projection below

    -

    Hides all visible layers above the active one on key press, and reverses this change on key release. Allows to - check what is the position of current layer in a stack. It is possible to paint while action is active.

    -
  • -
-

(Multiple assignments):

-

Multiple assignment is an action which cycles between multiple values of single krita property. Each key press - activates next list element. Performing a long press breaks the cycle and sets a default value, which does not have - to - belong the the list.

-
    -
  • -

    Cycle selection tools

    -

    Pressing a key repeatedly cycles most commonly used selection tools:

    -
      -
    • freehand selection tool,
    • -
    • rectangular selection tool,
    • -
    • contiguous selection tool
    • -
    -

    Performing a long press, goes back to the freehand brush tool. Tools can be used while the key is - pressed.

    -
  • -
  • -

    Cycle painting opacity

    -

    Pressing a key repeatedly cycles brush predefined opacity values: 100%, 70%, - 50%, 30%

    -

    Performing a long press, goes back to the 100% opacity. Modified opacity can be used while the key - is pressed.

    -
  • -
-

(Temporary keys):

-
    -
  • -

    Temporary move tool

    -

    Pressing a key temporarily activates the move tool which goes back to the freehand brush - tool after the key release. Short key presses allow to permanently toggle between those two tools.

    -
  • -
  • -

    Temporary eraser

    -

    Pressing a key temporarily activates the eraser mode which gets turned off after the key is - released. Short key presses allow to permanently toggle between those two states.

    -
  • -
  • -

    Temporary preserve alpha

    -

    Pressing a key temporarily activates the preserve alpha mode which gets turned off after the key - is - released. Short key presses allow to permanently toggle between those two states.

    -
  • -
-

Modifying default plugin behaviour

-

Tweaking the global parameters

-

Shortcut-Composer comes with a settings dialog available from krita topbar: Tools > Scripts > - Configure - Shortcut Composer. The dialog allows to change the following aspects of actions:

-
    -
  • Preset pie-menus mapping
      -
    • Tag (red) - tag to be used in red preset pie-menu
    • -
    • Tag (green) - tag to be used in green preset pie-menu
    • -
    • Tag (blue) - tag to be used in blue preset pie-menu
    • -
    -
  • -
  • Common settings
      -
    • Short vs long press time - Time in seconds distinguishing short key presses from long ones. + +

      Shortcut composer

      +

      Extension for painting application Krita, which allows to create custom, + complex + keyboard shortcuts. +

      +

      The plugin adds new shortcuts of the following types:

      +
        +
      • Pie menu - while key is pressed, displays a pie menu, which allows to pick values by hovering a + mouse.
      • +
      • Cursor tracker - while key is pressed, tracks a cursor, switching values according to cursor + offset.
      • +
      • Canvas preview - Temporarily changes canvas elements while the key is pressed.
      • +
      • Multiple assignment - repeatedly pressing a key, cycles between multiple values of krita + property. +
      • +
      • Temporary key - temporarily activates a krita property with long press or toggles it on/off + with + short press.
      • +
      +

      What's new in 1.2.0

      +

      Added

      +
        +
      • Adding and removing PieMenu icons with drag and drop.
      • +
      • Class-oriented configuration system with automatic value parsing. Can be reused in other plugins.
      • +
      • PieMenus are now able to handle their own local configuration with Pie settings (Replaces section in + Configure Shortcut Composer). +
      • +
      • Change PieIcon border's color when hovered in EditMode (Replaces gray indicator)
      • +
      • Allow changes to PieMenu and PieIcon size for each pie separately.
      • +
      • Separate button to enter PieMenu edit mode. Replaces clicking on PieIcon which was easy to do + unintentionally. +
      • +
      • Preset PieMenus tag now can be chosen directly from the Pie settings. (Replaces section in + Configure Shortcut Composer) +
      • +
      • Preset PieMenus now reload automatically when the tag content changes. (Replaces + Reload Shortcut Composer action) +
      • +
      • PieMenus now automatically change between dark/light theme when the krita theme changes between these two + theme + families.
      • +
      • Cycle selection tools action now is configured by local settings activated with a button that + appears on long press (Replaces section in Configure Shortcut Composer)
      • +
      +

      Fixed

      +
        +
      • Support tags with quote and double-quote signs.
      • +
      • Make input_adapter package independent from the rest of the plugin to improve re-usability. +
      • +
      • Fix crash when picking a deleted preset with PieMenu.
      • +
      +

      Check out historic changelogs.

      +

      Requirements

      +
        +
      • Version of krita on plugin release: 5.1.5
      • +
      • Required version of krita: 5.1.0
      • +
      +

      OS support state:

      +
        +
      • [x] Windows (10, 11)
      • +
      • [x] Linux (Ubuntu 20.04, 22.04)
      • +
      • [ ] MacOS (Known bug of canvas losing focus after using PieMenu)
      • +
      • [ ] Android (Does not support python plugins yet)
      • +
      +

      How to install or update the plugin:

      +
        +
      1. on github project page, click the green button + code and pick the download zip option. Do not extract it. +
      2. +
      3. in krita's topbar, open Tools > Scripts > Import Python Plugin From File and pick + the + downloaded .zip file
      4. +
      5. restart krita.
      6. +
      7. set custom shortcuts in Settings > Configure Krita > Keyboard Shortcuts under + Scripts > Shortcut Composer: Complex Actions section. By intention, there are no default + bindings. +
      8. +
      +

      Pre-made actions

      +

      While Shortcut-Composer is highly configurable and extendable, the add-on comes with pre-made, plug-and-play + actions. +

      +

      (Pie menus):

      +

      Pie menu is a widget displayed on the canvas while a key is pressed. It will disappear as soon, as the key is + released. Moving cursor in a direction of an icon, activates its value on key release. The action only + requires hovering. Pie menu does nothing if the cursor is not moved out of the deadzone.

      +

      Clicking the settings button in bottom-right corner switches into Edit mode which allows to modify + pie. + At this point the keyboard button no longer needs to be pressed. In this mode values one can:

      +
        +
      • drag icons to change their order
      • +
      • drag icons out of the ring to remove them
      • +
      • drag icons from the settings window to add them
      • +
      +

      Settings window visible in Edit mode also allows to change the local settings of the pie. When done, + press the tick button to apply the changes.

      +
        +
      • +

        Pick brush presets (red, green, blue)

        +

        Three color coded pie menus that let you pick a brush preset from related + tag with brush presets. +

        +

        Used tag can be changed in pie settings by entering Edit mode. Presets in the pie depend on + the + tag, so they cannot be removed or added with dragging, but it is possible to change their order. When + presets are added or remove from the tag, pie should update automatically.

        +

        Default tag mapping is as follows:

        +
          +
        • red: "★ My Favorites"
        • +
        • green: "RGBA"
        • +
        • blue: "Erasers"
        • +
        +
      • +
      • +

        Pick misc tools

        +

        Pie menu for picking active tools. It is recommended to change the default values being: +

        +
          +
        • crop tool,
        • +
        • reference tool,
        • +
        • gradient tool,
        • +
        • multi_brush tool,
        • +
        • assistant tool
        • +
        +
      • +
      • +

        Pick painting blending modes

        +

        Pie menu for picking painting blending modes. It is recommended to change the default + values + being:

        +
          +
        • normal
        • +
        • overlay,
        • +
        • color,
        • +
        • multiply,
        • +
        • add,
        • +
        • screen,
        • +
        • darken,
        • +
        • lighten
        • +
        +
      • +
      • +

        Create painting layer with blending mode

        +

        Pie menu for creating a new layer with picked blending mode. It is recommended to change + the + default values being:

        +
          +
        • normal
        • +
        • erase
        • +
        • overlay,
        • +
        • color,
        • +
        • multiply,
        • +
        • add,
        • +
        • screen,
        • +
        • darken,
        • +
        • lighten
        • +
        +
      • +
      +

      (Cursor trackers):

      +

      Cursor tracker is an action for switching values using cursor movement, while the keyboard key is pressed. It + changes + a single krita property according to the cursor movement along horizontal or vertical axis. The action + does + not recognise mouse clicks, and only requires hovering

      +
        +
      • +

        Scroll isolated layers

        +

        Scrolls the layers by sliding the cursor vertically. Can be used for picking the active layer and + analizing + the layer stack. While the key is pressed, isolates the active layer to give better response of which + layer + is active.

        +
          +
        • Key press: isloate active layer
        • +
        • Horizontal: -
        • +
        • Vertical: scroll all layers
        • +
        +
      • +
      • +

        Scroll timeline or animated layers

        +

        Variation on "Scroll isolated layers" for animators. Scrolling is restricted only to layers + pinned + to the animation timeline. Horizontal mouse movement changes the current frame.

        +
          +
        • Key press: isloate active layer
        • +
        • Horizontal: scroll animation frames
        • +
        • Vertical: scroll layers pinned to timeline
        • +
        +
      • +
      • +

        Scroll undo stack

        +

        Extends the krita undo action ctrl+z. While the key is pressed, horizontal mouse + movement controls the undo stack by performing undo and redo actions. Usual undo with short key press is + still possible.

        +
          +
        • Key press: undo last operation
        • +
        • Horizontal: scroll left to undo, or right to redo
        • +
        • Vertical: -
        • +
        +
      • +
      • +

        Scroll brush size or opacity

        +

        Allows to control both brush size or opacity with a single key. Opacity changes + contiguously with vertical mouse movement, while brush size snaps to custom values.

        +
          +
        • Key press: -
        • +
        • Horizontal: scroll brush size (descrete)
        • +
        • Vertical: scroll painting opacity (contiguous)
        • +
        +
      • +
      • +

        Scroll canvas zoom or rotation

        +

        Allows to control both canvas zoom or canvas rotation with a single key. Does + not + block the ability to paint.

        +
          +
        • Key press: -
        • +
        • Horizontal: canvas rotation (contiguous)
        • +
        • Vertical: canvas zoom (contiguous)
        • +
        +
      • +
      +

      (Canvas previews):

      +

      Canvas preview is an action which changes canvas elements when the key is pressed, and changes them back to their + original state on key release.

      +
        +
      • +

        Preview current layer visibility

        +

        Changes active layer visibility on key press and release. Allows to quickly check layer's content. +

        +
      • +
      • +

        Preview projection below

        +

        Hides all visible layers above the active one on key press, and reverses this change on key release. + Allows + to check what is the position of current layer in a stack. It is possible to paint while action is + active. +

        +
      • +
      +

      (Multiple assignments):

      +

      Multiple assignment is an action which cycles between multiple values of single krita property. Each key press + activates next list element. Performing a long press breaks the cycle and sets a default value, which does not + have + to belong the the list.

      +
        +
      • +

        Cycle selection tools

        +

        Pressing a key repeatedly cycles most commonly used selection tools:

        +
          +
        • freehand selection tool,
        • +
        • rectangular selection tool,
        • +
        • contiguous selection tool
        • +
        +

        Performing a long press, goes back to the freehand brush tool. Tools can be used while the + key + is pressed.

        +

        Default values can be modified in Edit mode. To enter it, long press the button, and click + on + the button which appears in top-left corner of painting area.

        +

        Values are added by selecting the list value(s) on the left and pressing the green add + button. Analogically, selecting values on the right, and pressing the remove button, + removes the values from action. Dragging values on the right, allow to change their order.

        +
      • +
      • +

        Cycle painting opacity

        +

        Pressing a key repeatedly cycles brush predefined opacity values: 100%, 70%, + 50%, 30% +

        +

        Performing a long press, goes back to the 100% opacity. Modified opacity can be used while + the + key is pressed.

        +

        Currently does not allow to configure predefined values without editing code.

        +
      • +
      +

      (Temporary keys):

      +
        +
      • +

        Temporary move tool

        +

        Pressing a key temporarily activates the move tool which goes back to the + freehand brush tool after the key release. Short key presses allow to permanently toggle + between those two tools. +

        +
      • +
      • +

        Temporary eraser

        +

        Pressing a key temporarily activates the eraser mode which gets turned off after the key is + released. Short key presses allow to permanently toggle between those two states.

        +
      • +
      • +

        Temporary preserve alpha

        +

        Pressing a key temporarily activates the preserve alpha mode which gets turned off after the + key + is released. Short key presses allow to permanently toggle between those two states.

      • -
      • FPS limit - Maximum rate of Mouse Tracker and Pie Menu refresh.
      • -
      -
    • -
    • Cursor trackers
        -
      • Tracker sensitivity scale - Sensitivity multiplier of all Mouse Trackers.
      • -
      • Tracker deadzone - Amount of pixels a mouse needs to moved for Mouse Trackers to start work. +
      +

      Modifying default plugin behaviour

      +

      Tweaking the global parameters

      +

      Shortcut-Composer comes with a settings dialog available from krita topbar: Tools > Scripts > + Configure + Shortcut Composer. The dialog allows to change the following aspects of actions:

      +
        +
      • Common settings
          +
        • Short vs long press time - Time in seconds distinguishing short key presses from long + ones. +
        • +
        • FPS limit - Maximum rate of Mouse Tracker and Pie Menu refresh.
        • +
      • -
      -
    • -
    • Pie menus display
        -
      • Pie global scale - Global scale factor for base of every pie menu.
      • -
      • Pie icon global scale - Global scale factor for icons of every pie menu.
      • -
      • Pie deadzone global scale - Global scale factor for the deadzone area of every pie menu.
      • -
      • Pie animation time - Time (in seconds) for fade-in animation when showing the pie menu.
      • -
      -
    • -
    -

    Changing the values in Pie menus and Multiple - assignments

    -

    Configure Shortcut Composer has a separate tab called Action values in which you can - add new values to some of the actions, as well as remove those that are not needed.

    -

    Action to modify can be selected using the combobox on the top.

    -

    Values are added by selecting the list value(s) on the left and pressing the green add button. - Analogically, selecting values on the right, and pressing the remove button, removes the values - from - action.

    -

    Dragging values on the right, allow to change their order. This can also be done using the Edit mode - directly from the PieMenu (does not apply to MultipleAssignments)

    -

    Modifying actions and creating custom ones

    -

    While the settings dialog allows to tweak the values common for plugin actions, it does not allow to modify the - behaviour of the actions or create new ones.

    -

    To achieve that it is required to modify actions implementation:

    -
      -
    • in krita's topbar, open Settings > Manage Resources > Open Resource Folder
    • -
    • navigate to ./pykrita/shortcut_composer/ directory.
    • -
    • action definitions are located in actions.action file.
    • -
    • -

      actions implementation is located in actions.py file.

      -
    • -
    • -

      Define an action in actions.action file by duplicating one of the existing definitions and using - an - unique name for it.

      -
    • -
    • Implement an action in actions.py file. Once again, duplicate one of the existing implementations. - It - is best to pick the one that feels closest to desired action. Fill its arguments, making sure the name is exactly - the same as defined earlier.
    • -
    -

    Worth noting

    -
      -
    • Key bindings with modifiers like ctrl or shift are supported. When assigned to a key - combination, the key is considered released when the main key in sequence (non-modifier) is released.
    • -
    • Multiple shortcuts from this plugin can be used at the same time, unless bindings make it technically - impossible. - For example holding both keys for Temporary eraser and Cycle painting opacity result in - an - eraser with 70% opacity.
    • -
    -

    Known limitations

    -
      -
    • Pressing a modifier while the usual key is pressed, will result in conflict. For instance, pressing - ctrl while using temporary eraser assigned to x will result in unwanted - ctrl+x operation which cuts current layer.
    • -
    -

    For krita plugin programmers

    -

    Alternative API

    -

    The extension consists of elements that can be reused in other krita plugins under GPL-3.0-or-later license. - Package - api_krita wrappes krita api offering PEP8 compatibility, typings, and docstring documentation. Most of - objects attributes are now available as settables properties.

    -

    Copy api_krita to the extension directory, to access syntax such as:

    -
    from .api_krita import Krita
    +        
  • Cursor trackers
      +
    • Tracker sensitivity scale - Sensitivity multiplier of all Mouse Trackers.
    • +
    • Tracker deadzone - Amount of pixels a mouse needs to moved for Mouse Trackers to start + work.
    • +
    +
  • +
  • Pie menus display
      +
    • Pie global scale - Global scale factor for base of every pie menu.
    • +
    • Pie icon global scale - Global scale factor for icons of every pie menu.
    • +
    • Pie deadzone global scale - Global scale factor for the deadzone area of every pie + menu. +
    • +
    • Pie animation time - Time (in seconds) for fade-in animation when showing the pie menu. +
    • +
    +
  • +
+

Modifying actions and creating custom ones

+

While the settings dialog allows to tweak the values common for plugin actions, it does not allow to modify the + behaviour of the actions or create new ones.

+

To achieve that it is required to modify actions implementation:

+
    +
  • in krita's topbar, open Settings > Manage Resources > Open Resource Folder
  • +
  • navigate to ./pykrita/shortcut_composer/ directory.
  • +
  • action definitions are located in actions.action file.
  • +
  • +

    actions implementation is located in actions.py file.

    +
  • +
  • +

    Define an action in actions.action file by duplicating one of the existing definitions and + using + an unique name for it.

    +
  • +
  • Implement an action in actions.py file. Once again, duplicate one of the existing + implementations. + It is best to pick the one that feels closest to desired action. Fill its arguments, making sure the name is + exactly the same as defined earlier.
  • +
+

Worth noting

+
    +
  • Key bindings with modifiers like ctrl or shift are supported. When assigned to a key + combination, the key is considered released when the main key in sequence (non-modifier) is released.
  • +
  • Multiple shortcuts from this plugin can be used at the same time, unless bindings make it technically + impossible. For example holding both keys for Temporary eraser and + Cycle painting opacity result in an eraser with 70% opacity. +
  • +
+

Known limitations

+
    +
  • Pressing a modifier while the usual key is pressed, will result in conflict. For instance, pressing + ctrl while using temporary eraser assigned to x will result in unwanted + ctrl+x operation which cuts current layer. +
  • +
  • It is possible to activate multiple pie menus at the same time.
  • +
  • Keyboard shortcuts assigned to actions can conflict with Canvas Input (General limitation of Krita).
  • +
+

For krita plugin programmers

+

Some parts of plugin code solve general problems, which can apply outside of Shortcut Composer. Those solutions + were + placed in separate packages that can be copy-pasted into any other plugin and reused there.

+

They depend only on original Krita API and PyQt5 with which krita is shipped.

+

Custom keyboard shortcut interface

+

Package input_adapter consists of ActionManager and ComplexActionInterface + which together allow to recognise more keyboard events than usual krita action does.

+

While usual actions can only recognise key press, implementing ComplexActionInterface lets you + override + methods performed on:

+
    +
  • key press
  • +
  • short key release
  • +
  • long key release
  • +
  • every key release
  • +
+

Each action needs to have public name: str attribute which is the same, as the one used in .action + file, + as well as short_vs_long_press_time: float which determines how many seconds need to elapse to + consider + that a key press was long.

+

Use ActionManager instance to bind objects of those custom actions to krita during + CreateActions phase: +

+
"""
+Print whether action key was released before of after
+0.2 seconds from being pressed.
+"""
+from krita import Krita
+from input_adapter import ActionManager, ComplexActionInterface
+
+
+class CustomAction(ComplexActionInterface):
+    def __init__(self, name: str, press_time: float = 0.2):
+      self.name = name
+      self.short_vs_long_press_time = press_time
+
+    def on_key_press(self): print("key was pressed")
+    def on_short_key_release(self): print("key released before than 0.2s")
+    def on_long_key_release(self): print("key released later than after 0.2s")
+    def on_every_key_release(self): pass
+
+
+class MyExtension(Extension):
+    def setup(self) -> None: pass
+    def createActions(self, window) -> None:
+        action = CustomAction(name="Custom action name")
+        self.manager = ActionManager(window)
+        self.manager.bind_action(action)
+
+Krita.instance().addExtension(MyExtension(Krita.instance()))
+
+

Config system

+

Package config_system consists of Field and FieldGroup which grant + object-oriented API to control kritarc configuration file easier, than with API of krita.

+
+

Field represents a single value in kritarc file. Once initialized with its group name, name and + default + value, it allows to:

+
    +
  • write a given value to kritarc.
  • +
  • read current value from kritarc, parsing it to correct python type.
  • +
  • reset the value to default.
  • +
  • register a callback run on each value change.
  • +
+

Type of default value passed on initlization is remembered, and used to parse values both on read and write. + Supported types are:

+
    +
  • int, list[int],
  • +
  • float, list[float],
  • +
  • str, list[str],
  • +
  • bool, list[bool],
  • +
  • Enum, list[Enum]
  • +
+

For empty, homogeneous lists, parser_type argument must be used to determine type of list elements. + Default values are not saved when until the field does not exist in kritarc. Repeated saves of the same value + are + filtered, so that callbacks are not called when the same value is written multiple times one after the other. +

+
+

FieldGroup represents a section of fields in kritarc file. It simplifies the field creation by + auto-completing the group name.

+

FieldGroup holds and aggregates fields created with it. It allows to reset all the fields at once, and register a + callback to all its fields: both existing and future ones.

+
+

Example usage:

+
from enum import Enum
+from config_system import FieldGroup
+
+
+class EnumMock(Enum):
+    MODE_A = 0
+    MODE_B = 1
+
+# Create a config group
+group = FieldGroup("MyGroup")
+# Register a callback on all three fields
+group.register_callback(lambda: print("any field changed"))
+
+# Create three fields inside a group - for string and two enum lists
+str_field = group.field(name="my_str", default="Sketch")
+enums_field_1 = group.field("my_enums_1", [], parser_type=EnumMock)
+enums_field_2 = group.field("my_enums_2", [EnumMock.MODE_A])
+
+# Register a different callback on each field
+str_field.register_callback(lambda: print("string changed"))
+enums_field_1.register_callback(lambda: print("enum 1 changed"))
+enums_field_2.register_callback(lambda: print("enum 2 changed"))
+
+# Change the value from default "Sketch" to "Digital"
+str_field.write("Digital")
+# Change the value from empty list to one with two values
+enums_field_1.write([EnumMock.MODE_A, EnumMock.MODE_B])
+# Repeat the default value. Will be filtered
+enums_field_2.write([EnumMock.MODE_A])
+
+# The program will not break, as red values are the same as written ones
+assert str_field.read() == "Digital"
+assert enums_field_1.read() == [EnumMock.MODE_A, EnumMock.MODE_B]
+assert enums_field_2.read() == [EnumMock.MODE_A]
+
+

The code above produces "MyGroup" section in kritarc file. my_enums_2 is missing, as the default value + was + not changed:

+
[MyGroup]
+my_str=Digital
+my_enums_1=MODE_A\tMODE_B
+
+

Registered callbacks outputs on the terminal:

+
any field changed
+string changed
+any field changed
+enum 1 changed
+
+

Calling group.reset_defaults() would change both values back to their defaults, and produce the same + output on the terminal, as resetting changes the fields.

+

Alternative API

+

Package api_krita wraps krita api offering PEP8 compatibility, typings, and docstring documentation. + Most of objects attributes are now available as settables properties.

+
from .api_krita import Krita
 from .api_krita.enums import BlendingMode, Tool, Toggle
 
 # active tool operations
@@ -359,41 +544,8 @@ 

Alternative API

Toggle.SOFT_PROOFING.state = False # turn off soft proofing Toggle.PRESERVE_ALPHA.switch_state() # change state of preserve alpha
-

Only functionalities that were needed during this plugin development are wrapped, so some of them are not yet - available. The syntax can also change over time.

-

Custom keyboard shortcut interface

-

Package input_adapter consists of ActionManager and ComplexAction which - grants - extended interface for creating keyboard shortcuts.

-

While usual actions can only recognise key press, subclassing ComplexAction lets you override methods - performed on:

-
    -
  • key press
  • -
  • short key release
  • -
  • long key release
  • -
  • every key release
  • -
-

Then use ActionManager instance to bind objects of those custom actions to krita during CreateActions - phase:

-
from api_krita import Extension
-  from input_adapter import ActionManager, ComplexAction
-  
-  
-  class CustomAction(ComplexAction):
-      def on_key_press(self): ...
-      def on_short_key_release(self): ...
-      def on_long_key_release(self): ...
-      def on_every_key_release(self): ...
-  
-  
-  class ExtensionName(Extension):
-      ...
-      def createActions(self, window) -> None:
-          action = CustomAction(name="Custom action name")
-          self.manager = ActionManager(window)
-          self.manager.bind_action(action)
-  
- +

Only functionalities that were needed during this plugin development are wrapped, so some of them are not yet + available. The syntax can also change over time.