diff --git a/Pack/MKWFaraphel/mod_config.json b/Pack/MKWFaraphel/mod_config.json index 02c4112..e3a4bec 100644 --- a/Pack/MKWFaraphel/mod_config.json +++ b/Pack/MKWFaraphel/mod_config.json @@ -6,15 +6,15 @@ "settings": { "mode": { - "type": "str", + "type": "choices", "choices": [ "normal", "debug" ] }, "highlight_if": { - "type": "str", - "safe_eval_preview": "track" + "type": "string", + "preview": "track_formatting" } }, "track_new_if": "'Retro' not in getattr(track, 'tags', []) and getattr(track, 'warning', 0) == 0", diff --git a/source/gui/install.py b/source/gui/install.py index c646da5..dec88aa 100644 --- a/source/gui/install.py +++ b/source/gui/install.py @@ -9,7 +9,7 @@ from tkinter import messagebox import webbrowser from typing import Generator -from source.gui import better_gui_error, mystuff, mod_configuration +from source.gui import better_gui_error, mystuff, mod_settings from source.mkw.Game import Game from source.mkw.ModConfig import ModConfig from source.option import Option @@ -515,7 +515,7 @@ class SelectPack(ttk.Frame): super().__init__(master) self.combobox = ttk.Combobox(self) - self.combobox.grid(row=1, column=1) + self.combobox.grid(row=1, column=1, sticky="NEWS") self.button_settings = ttk.Button(self, text="...", width=2, command=self.open_mod_configuration) self.button_settings.grid(row=1, column=2, sticky="NEWS") @@ -529,7 +529,7 @@ class SelectPack(ttk.Frame): self.combobox.bind("<>", lambda _: self.select()) def open_mod_configuration(self) -> None: - mod_configuration.Window(self.mod_config) + mod_settings.Window(self.mod_config) def refresh_packs(self) -> None: """ diff --git a/source/gui/mod_configuration.py b/source/gui/mod_configuration.py deleted file mode 100644 index 74e697d..0000000 --- a/source/gui/mod_configuration.py +++ /dev/null @@ -1,42 +0,0 @@ -import tkinter -from tkinter import ttk -from source.translation import translate as _ -from source.gui.preview import track_formatting - - -ModConfig: any - - -class Window(tkinter.Toplevel): - def __init__(self, mod_config: "ModConfig"): - super().__init__() - - self.rowconfigure(1, weight=1) - self.columnconfigure(1, weight=1) - - self.mod_config = mod_config - - self.panel_window = ttk.Notebook(self) - self.panel_window.grid(row=1, column=1, sticky="NEWS") - - self.frame_global_settings = FrameGlobalSettings(self.panel_window) - self.frame_specific_settings = FrameSpecificSettings(self.panel_window) - - -class FrameGlobalSettings(ttk.Frame): - def __init__(self, master: ttk.Notebook): - super().__init__(master) - master.add(self, text=_("GLOBAL_MOD_SETTINGS")) - - # TODO: overwrite new tracks entry - button = ttk.Button(self, text="test search", command=self.open_test_button) - button.grid(row=1, column=1) - - def open_test_button(self): - track_formatting.Window(self.master.master.mod_config) - - -class FrameSpecificSettings(ttk.Frame): - def __init__(self, master: ttk.Notebook): - super().__init__(master) - master.add(self, text=_("SPECIFIC_MOD_SETTINGS")) diff --git a/source/gui/mod_settings.py b/source/gui/mod_settings.py new file mode 100644 index 0000000..52e80bd --- /dev/null +++ b/source/gui/mod_settings.py @@ -0,0 +1,60 @@ +import tkinter +from tkinter import ttk +from source.translation import translate as _ +from source.gui.preview import track_formatting + + +ModConfig: any + + +class Window(tkinter.Toplevel): + def __init__(self, mod_config: "ModConfig"): + super().__init__() + self.resizable(False, False) + self.grab_set() + + self.rowconfigure(1, weight=1) + self.columnconfigure(1, weight=1) + + self.mod_config = mod_config + + self.panel_window = ttk.Notebook(self) + self.panel_window.grid(row=1, column=1, sticky="NEWS") + + self.frame_global_settings = FrameGlobalSettings(self.panel_window) + self.frame_specific_settings = FrameSpecificSettings(self.panel_window) + + +class FrameGlobalSettings(ttk.Frame): + def __init__(self, master: ttk.Notebook): + 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") + + 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") + + 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") + + +class FrameSpecificSettings(ttk.Frame): + def __init__(self, master: ttk.Notebook): + 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) + frame.grid(row=index, column=1, sticky="NEWS") + + settings_data.tkinter_show(frame) diff --git a/source/gui/preview/__init__.py b/source/gui/preview/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/gui/preview/track_formatting.py b/source/gui/preview/track_formatting.py index b2054d3..922904e 100644 --- a/source/gui/preview/track_formatting.py +++ b/source/gui/preview/track_formatting.py @@ -8,16 +8,19 @@ ModConfig: any class Window(tkinter.Toplevel): - def __init__(self, mod_config: "ModConfig"): + def __init__(self, mod_config: "ModConfig", template: str = ""): super().__init__() self.grid_rowconfigure(2, weight=1) self.grid_columnconfigure(1, weight=1) + self.grab_set() self.mod_config = mod_config - self.entry_format_input = ttk.Entry(self, width=100) - self.entry_format_input.grid(row=1, column=1, columnspan=2, sticky="NEWS") - self.entry_format_input.bind("", self.preview) + 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.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(self, background="black", foreground=MKWColor("off").color_code) self.text_track_preview.grid(row=2, column=1, sticky="NEWS") @@ -31,6 +34,20 @@ 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 @@ -42,7 +59,7 @@ class Window(tkinter.Toplevel): for track in self.mod_config.get_tracks(): try: track_repr = track.repr_format( - self.mod_config, self.entry_format_input.get() + self.mod_config, self.entry_template_input.get() ) offset: int = 0 # the color tag is removed at every sub, so keep track of the offset @@ -77,3 +94,4 @@ class Window(tkinter.Toplevel): formatted_exc = str(exc).replace('\n', ' ') self.text_track_preview.insert(tkinter.END, f"< Error: {formatted_exc} >\n") self.text_track_preview.tag_add("error", "end-1c-1l", "end-1c") + diff --git a/source/gui/preview/track_selecting.py b/source/gui/preview/track_selecting.py new file mode 100644 index 0000000..e69de29 diff --git a/source/mkw/ModConfig.py b/source/mkw/ModConfig.py index af223b8..21328bf 100644 --- a/source/mkw/ModConfig.py +++ b/source/mkw/ModConfig.py @@ -8,6 +8,7 @@ from source import threaded from source.mkw import Tag from source.mkw.Cup import Cup from source.mkw.MKWColor import bmg_color_text +from source.mkw.ModSettings import ModSettings from source.mkw.Track import Track import json @@ -42,7 +43,7 @@ class ModConfig: 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 = settings if settings is not None else {} + self.settings: dict = ModSettings(settings if settings is not None else {}) self.name: str = name self.nickname: str = nickname if nickname is not None else name diff --git a/source/mkw/ModSettings/TypeSettings/Choices.py b/source/mkw/ModSettings/TypeSettings/Choices.py new file mode 100644 index 0000000..247073d --- /dev/null +++ b/source/mkw/ModSettings/TypeSettings/Choices.py @@ -0,0 +1,23 @@ +import tkinter +from tkinter import ttk +from source.mkw.ModSettings.TypeSettings import AbstractTypeSettings + + +class Choices(AbstractTypeSettings): + """ + This setting type allow you to input a string text. + You can optionally add a "preview" to allow the user to use a window to select the value. + """ + + type = "choices" + + def __init__(self, choices: list[str], value: str = None): + self.value = value if value is not None else choices[0] + 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())) + + combobox = ttk.Combobox(master, width=100, values=self.choices, textvariable=variable) + combobox.grid(row=1, column=1) diff --git a/source/mkw/ModSettings/TypeSettings/String.py b/source/mkw/ModSettings/TypeSettings/String.py new file mode 100644 index 0000000..0fc0c70 --- /dev/null +++ b/source/mkw/ModSettings/TypeSettings/String.py @@ -0,0 +1,27 @@ +import tkinter +from tkinter import ttk + +from source.mkw.ModSettings.TypeSettings import AbstractTypeSettings + + +class String(AbstractTypeSettings): + """ + This setting type allow you to input a string text. + You can optionally add a "preview" to allow the user to use a window to select the value. + """ + + type = "string" + + def __init__(self, value=None, preview: str = None): + self.value: str = value if value is not None else "" + 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())) + + entry = ttk.Entry(master, width=100, textvariable=variable) + entry.grid(row=1, column=1, sticky="NEWS") + + button = ttk.Button(master, text="...", width=3) + button.grid(row=1, column=2, sticky="NEWS") diff --git a/source/mkw/ModSettings/TypeSettings/__init__.py b/source/mkw/ModSettings/TypeSettings/__init__.py new file mode 100644 index 0000000..7f8f57e --- /dev/null +++ b/source/mkw/ModSettings/TypeSettings/__init__.py @@ -0,0 +1,16 @@ +from abc import ABC, abstractmethod + + +class AbstractTypeSettings(ABC): + type: str # type name of the settings + value: str # value for the settings + + @abstractmethod + def tkinter_show(self, master) -> None: + """ + Show the option inside a tkinter widget + """ + ... + + +from source.mkw.ModSettings.TypeSettings import String, Choices diff --git a/source/mkw/ModSettings/__init__.py b/source/mkw/ModSettings/__init__.py new file mode 100644 index 0000000..e82ce81 --- /dev/null +++ b/source/mkw/ModSettings/__init__.py @@ -0,0 +1,42 @@ +from source.mkw.ModSettings.TypeSettings import AbstractTypeSettings + + +class InvalidSettingsType(Exception): + def __init__(self, settings_type: str): + super().__init__(f"Error : Type of mod settings '{settings_type}' not found.") + + +class ModSettings: + def __new__(cls, settings_dict: dict) -> dict[str, AbstractTypeSettings]: + """ + Load all the settings in mod_settings_dict + :param settings_dict: dictionnary containing all the settings defined for the mod + """ + settings: dict[str, AbstractTypeSettings] = {} + + for settings_name, settings_data in settings_dict.items(): + for subclass in filter( + lambda subclass: subclass.type == settings_data["type"], AbstractTypeSettings.__subclasses__() + ): + settings_data.pop("type") + settings[settings_name] = subclass(**settings_data) + break + else: raise InvalidSettingsType(settings_name) + + return settings + + + +example_settings = { + "mode": { + "type": "choices", + "choices": [ + "normal", + "debug" + ] + }, + "highlight_if": { + "type": "str", + "preview": "track_formatting" + } +} diff --git a/source/mkw/Patch/PatchOperation/Operation/Special.py b/source/mkw/Patch/PatchOperation/Operation/Special.py index 32d68a1..c9eeae5 100644 --- a/source/mkw/Patch/PatchOperation/Operation/Special.py +++ b/source/mkw/Patch/PatchOperation/Operation/Special.py @@ -8,7 +8,7 @@ Patch: any class Special(AbstractOperation): """ - use a file defined as special in the patch to replate the current file content + use a file defined as special in the patch to replace the current file content """ type = "special"