From a2037fd08436d98377bf8b85e02cc1febecac51d Mon Sep 17 00:00:00 2001 From: Faraphel Date: Sun, 31 Jul 2022 19:09:49 +0200 Subject: [PATCH] implemented settings preview, tweaked some visual and made the preview window code a bit simpler --- Pack/MKWFaraphel/mod_config.json | 2 +- source/gui/mod_settings.py | 29 ++++++-------- source/gui/preview/__init__.py | 38 +++++++++++++++++++ source/gui/preview/track_formatting.py | 30 +++++---------- source/mkw/ModConfig.py | 34 ++++++++++++++--- .../mkw/ModSettings/TypeSettings/Choices.py | 15 +++++--- source/mkw/ModSettings/TypeSettings/String.py | 24 ++++++++---- .../mkw/ModSettings/TypeSettings/__init__.py | 17 ++++++++- source/mkw/ModSettings/__init__.py | 16 -------- 9 files changed, 132 insertions(+), 73 deletions(-) diff --git a/Pack/MKWFaraphel/mod_config.json b/Pack/MKWFaraphel/mod_config.json index e3a4bec..97dfadd 100644 --- a/Pack/MKWFaraphel/mod_config.json +++ b/Pack/MKWFaraphel/mod_config.json @@ -4,7 +4,7 @@ "nickname": "MKWF", "variant": "60", - "settings": { + "specific_settings": { "mode": { "type": "choices", "choices": [ diff --git a/source/gui/mod_settings.py b/source/gui/mod_settings.py index 52e80bd..c868ebe 100644 --- a/source/gui/mod_settings.py +++ b/source/gui/mod_settings.py @@ -30,22 +30,14 @@ class FrameGlobalSettings(ttk.Frame): super().__init__(master) master.add(self, text=_("GLOBAL_MOD_SETTINGS")) - self.checkbutton_override_random_new = ttk.Checkbutton(self, text=_("OVERRIDE_RANDOM_NEW")) - self.frame_override_random_new = ttk.LabelFrame(self, labelwidget=self.checkbutton_override_random_new) - self.frame_override_random_new.grid(row=1, column=1, sticky="NEWS") + self.columnconfigure(1, weight=1) - variable = tkinter.StringVar(self) - variable.trace_add("write", lambda *_: setattr(self, "value", variable.get())) - self.entry_override_random_new_cup = ttk.Entry(self.frame_override_random_new, width=70, textvariable=variable) - self.entry_override_random_new_cup.grid(row=1, column=1, sticky="NEWS") + for index, (settings_name, settings_data) in enumerate(self.master.master.mod_config.global_settings.items()): + checkbox = ttk.Checkbutton(self, text=settings_name) + frame = ttk.LabelFrame(self, labelwidget=checkbox) + frame.grid(row=index, column=1, sticky="NEWS") - self.button_preview_override_random_new = ttk.Button(self.frame_override_random_new, text="...", width=3) - self.button_preview_override_random_new.configure(command=lambda: track_formatting.Window.ask_for_template( - mod_config=self.master.master.mod_config, - variable=variable, - template=self.entry_override_random_new_cup.get(), - )) - self.button_preview_override_random_new.grid(row=1, column=2, sticky="NEWS") + settings_data.tkinter_show(frame, checkbox) class FrameSpecificSettings(ttk.Frame): @@ -53,8 +45,11 @@ class FrameSpecificSettings(ttk.Frame): super().__init__(master) master.add(self, text=_("SPECIFIC_MOD_SETTINGS")) - for index, (settings_name, settings_data) in enumerate(self.master.master.mod_config.settings.items()): - frame = ttk.LabelFrame(self, text=settings_name) + self.columnconfigure(1, weight=1) + + for index, (settings_name, settings_data) in enumerate(self.master.master.mod_config.specific_settings.items()): + checkbox = ttk.Checkbutton(self, text=settings_name) + frame = ttk.LabelFrame(self, labelwidget=checkbox) frame.grid(row=index, column=1, sticky="NEWS") - settings_data.tkinter_show(frame) + settings_data.tkinter_show(frame, checkbox) diff --git a/source/gui/preview/__init__.py b/source/gui/preview/__init__.py index e69de29..33ad0d9 100644 --- a/source/gui/preview/__init__.py +++ b/source/gui/preview/__init__.py @@ -0,0 +1,38 @@ +import tkinter +from abc import abstractmethod, ABC +from typing import Type + + +ModConfig: any + + +class InvalidPreviewWindowName(Exception): + def __init__(self, name: str): + super().__init__(f"Error : Type of preview window '{name}' not found.") + + +class AbstractPreviewWindow(tkinter.Toplevel, ABC): + """ + Represent a window that allow a preview of the result of a value that can be in a settings entry + """ + + name: str + + @abstractmethod + def __init__(self, mod_config: "ModConfig", template_variable: tkinter.StringVar = None): + super().__init__() + ... + + +def get_preview_window_class(name: str) -> Type[AbstractPreviewWindow]: + """ + Return the windows class object from its name + :param name: name of the window class + :return: the window class object + """ + for window_class in filter(lambda cls: cls.name == name, AbstractPreviewWindow.__subclasses__()): + return window_class + raise InvalidPreviewWindowName(name) + + +from source.gui.preview import track_formatting, track_selecting diff --git a/source/gui/preview/track_formatting.py b/source/gui/preview/track_formatting.py index f402b35..44adabf 100644 --- a/source/gui/preview/track_formatting.py +++ b/source/gui/preview/track_formatting.py @@ -3,23 +3,27 @@ from tkinter import ttk import re from source.mkw.MKWColor import MKWColor +from source.gui.preview import AbstractPreviewWindow + ModConfig: any -class Window(tkinter.Toplevel): - def __init__(self, mod_config: "ModConfig", template: str = ""): - super().__init__() +class Window(AbstractPreviewWindow): + + name = "track_formatting" + + def __init__(self, mod_config: "ModConfig", template_variable: tkinter.StringVar = None): + super().__init__(mod_config, template_variable) self.grid_rowconfigure(2, weight=1) self.grid_columnconfigure(1, weight=1) self.grab_set() + if template_variable is None: template_variable = tkinter.StringVar() self.mod_config = mod_config - self.stringvar_template_input = tkinter.StringVar(self.master) - self.entry_template_input = ttk.Entry(self, width=100, textvariable=self.stringvar_template_input) + self.entry_template_input = ttk.Entry(self, width=100, textvariable=template_variable) self.entry_template_input.grid(row=1, column=1, columnspan=2, sticky="NEWS") - self.entry_template_input.insert(tkinter.END, template) self.entry_template_input.bind("", self.preview) self.text_track_preview = tkinter.Text( @@ -36,20 +40,6 @@ class Window(tkinter.Toplevel): self.text_track_preview.tag_configure(color.bmg, foreground=color.color_code) self.text_track_preview.tag_configure("error", background="red", foreground="white") - @classmethod - def ask_for_template(cls, variable=None, *args, **kwargs) -> str: - """ - prompt the user for a template. Return the final template typed by the user - :entry: entry widget wwhere the final template can be inserted - :return: final template entered by the user - """ - window = cls(*args, **kwargs) - window.wait_window() - - result = window.stringvar_template_input.get() - if variable is not None: variable.set(result) - return result - def preview(self, event: tkinter.Event = None): """ Preview all the tracks name with the track format diff --git a/source/mkw/ModConfig.py b/source/mkw/ModConfig.py index 21328bf..104d3f8 100644 --- a/source/mkw/ModConfig.py +++ b/source/mkw/ModConfig.py @@ -22,14 +22,35 @@ CT_ICON_SIZE: int = 128 Thread: any -# representation of the configuration of a mod +global_settings = { + "override_random_new": { + "type": "string", + "preview": "track_selecting" + }, + "remove_track_if": { + "type": "string", + "preview": "track_selecting" + }, + "sort_track_by": { + "type": "choices", + "choices": [ + "test1", + "test2", + "test3" + ] + } +} class ModConfig: + """ + Representation of a mod + """ + __slots__ = ("name", "path", "nickname", "variant", "tags_prefix", "tags_suffix", "default_track", "_tracks", "version", "original_track_prefix", "swap_original_order", "keep_original_track", "enable_random_cup", "tags_cups", "track_file_template", - "multiplayer_disable_if", "track_new_if", "macros", "messages", "settings") + "multiplayer_disable_if", "track_new_if", "macros", "messages", "global_settings", "specific_settings") def __init__(self, path: Path | str, name: str, nickname: str = None, version: str = None, variant: str = None, tags_prefix: dict[Tag, str] = None, tags_suffix: dict[Tag, str] = None, @@ -38,12 +59,14 @@ class ModConfig: swap_original_order: bool = None, keep_original_track: bool = None, enable_random_cup: bool = None, track_file_template: str = None, multiplayer_disable_if: str = None, macros: dict[str, str] = None, track_new_if: str = None, messages: dict[str, dict[str, str]] = None, - settings: dict[str, dict[str, str]] = None): + specific_settings: dict[str, dict[str, str]] = None): self.path = Path(path) self.macros: dict = macros if macros is not None else {} self.messages: dict = messages if messages is not None else {} - self.settings: dict = ModSettings(settings if settings is not None else {}) + + self.global_settings: dict = ModSettings(global_settings) + self.specific_settings: dict = ModSettings(specific_settings if specific_settings is not None else {}) self.name: str = name self.nickname: str = nickname if nickname is not None else name @@ -82,7 +105,8 @@ class ModConfig: kwargs = { attr: config_dict.get(attr) for attr in cls.__slots__ - if attr not in ["name", "default_track", "_tracks", "tracks", "path", "macros", "messages"] + if attr not in ["name", "default_track", "_tracks", "tracks", "path", "macros", "messages", + "global_settings"] # these keys are treated after or are reserved } diff --git a/source/mkw/ModSettings/TypeSettings/Choices.py b/source/mkw/ModSettings/TypeSettings/Choices.py index 247073d..3be543c 100644 --- a/source/mkw/ModSettings/TypeSettings/Choices.py +++ b/source/mkw/ModSettings/TypeSettings/Choices.py @@ -11,13 +11,16 @@ class Choices(AbstractTypeSettings): type = "choices" - def __init__(self, choices: list[str], value: str = None): + def __init__(self, choices: list[str], value: str = None, enabled: bool = False): self.value = value if value is not None else choices[0] + self.enabled = enabled self.choices = choices - def tkinter_show(self, master) -> None: - variable = tkinter.StringVar(master, value=self.value) - variable.trace_add("write", lambda *_: setattr(self, "value", variable.get())) + def tkinter_show(self, master: ttk.LabelFrame, checkbox) -> None: + super().tkinter_show(master, checkbox) - combobox = ttk.Combobox(master, width=100, values=self.choices, textvariable=variable) - combobox.grid(row=1, column=1) + value_variable = tkinter.StringVar(master, value=self.value) + value_variable.trace_add("write", lambda *_: setattr(self, "value", value_variable.get())) + + combobox = ttk.Combobox(master, values=self.choices, textvariable=value_variable) + combobox.grid(row=1, column=1, sticky="NEWS") diff --git a/source/mkw/ModSettings/TypeSettings/String.py b/source/mkw/ModSettings/TypeSettings/String.py index 0fc0c70..da65948 100644 --- a/source/mkw/ModSettings/TypeSettings/String.py +++ b/source/mkw/ModSettings/TypeSettings/String.py @@ -2,6 +2,7 @@ import tkinter from tkinter import ttk from source.mkw.ModSettings.TypeSettings import AbstractTypeSettings +from source.gui.preview import get_preview_window_class class String(AbstractTypeSettings): @@ -12,16 +13,25 @@ class String(AbstractTypeSettings): type = "string" - def __init__(self, value=None, preview: str = None): + def __init__(self, value: str = None, preview: str = None, enabled: bool = False): self.value: str = value if value is not None else "" + self.enabled = enabled self.preview: str | None = preview - def tkinter_show(self, master) -> None: - variable = tkinter.StringVar(master, value=self.value) - variable.trace_add("write", lambda *_: setattr(self, "value", variable.get())) + def tkinter_show(self, master: ttk.LabelFrame, checkbox) -> None: + super().tkinter_show(master, checkbox) - entry = ttk.Entry(master, width=100, textvariable=variable) + value_variable = tkinter.StringVar(master, value=self.value) + value_variable.trace_add("write", lambda *_: setattr(self, "value", value_variable.get())) + + entry = ttk.Entry(master, textvariable=value_variable) entry.grid(row=1, column=1, sticky="NEWS") - button = ttk.Button(master, text="...", width=3) - button.grid(row=1, column=2, sticky="NEWS") + if self.preview is not None: + button = ttk.Button( + master, text="...", width=3, + command=lambda: get_preview_window_class( + self.preview + )(master.master.master.master.mod_config, value_variable) + ) + button.grid(row=1, column=2, sticky="NEWS") diff --git a/source/mkw/ModSettings/TypeSettings/__init__.py b/source/mkw/ModSettings/TypeSettings/__init__.py index 7f8f57e..37fc06c 100644 --- a/source/mkw/ModSettings/TypeSettings/__init__.py +++ b/source/mkw/ModSettings/TypeSettings/__init__.py @@ -1,15 +1,30 @@ +import tkinter +from tkinter import ttk from abc import ABC, abstractmethod class AbstractTypeSettings(ABC): type: str # type name of the settings value: str # value for the settings + enabled: bool # is the settings enabled @abstractmethod - def tkinter_show(self, master) -> None: + def __init__(self, value: str = None, preview: str = None, enabled: bool = False): + ... + + @abstractmethod + def tkinter_show(self, master: ttk.LabelFrame, checkbox) -> None: """ Show the option inside a tkinter widget + :master: master widget + :checkbox: checkbox inside the labelframe allowing to enable or disable the setting """ + master.grid_rowconfigure(1, weight=1) + master.grid_columnconfigure(1, weight=1) + + enabled_variable = tkinter.BooleanVar(master, value=self.enabled) + enabled_variable.trace_add("write", lambda *_: setattr(self, "enabled", enabled_variable.get())) + checkbox.configure(variable=enabled_variable) ... diff --git a/source/mkw/ModSettings/__init__.py b/source/mkw/ModSettings/__init__.py index e82ce81..269089c 100644 --- a/source/mkw/ModSettings/__init__.py +++ b/source/mkw/ModSettings/__init__.py @@ -24,19 +24,3 @@ class ModSettings: else: raise InvalidSettingsType(settings_name) return settings - - - -example_settings = { - "mode": { - "type": "choices", - "choices": [ - "normal", - "debug" - ] - }, - "highlight_if": { - "type": "str", - "preview": "track_formatting" - } -}