Options class have been rewritten to make saving and getting more explicit

This commit is contained in:
Faraphel 2022-08-21 11:30:25 +02:00
parent e88fb57143
commit c511f7bb86
6 changed files with 116 additions and 74 deletions

View file

@ -1,13 +1,13 @@
from source.gui import install
from source.option import Option
from source.option import Options
from source.translation import load_language
# this allows every variable to be accessible from other files, useful for the plugins
self = __import__(__name__)
options = Option.from_file("./option.json")
translater = load_language(options["language"])
options = Options.from_file("./option.json")
translater = load_language(options.language.get())
self.window = install.Window(options)
self.window.run()

View file

@ -161,12 +161,15 @@ class Menu(tkinter.Menu):
master.add_cascade(label=_("LANGUAGE_SELECTION"), menu=self)
self.lang_variable = tkinter.StringVar(value=self.root.options.language.get())
for file in Path("./assets/language/").iterdir():
lang_json = json.loads(file.read_text(encoding="utf8"))
self.add_radiobutton(
label=lang_json["name"],
value=file.stem,
variable=self.root.options.language
variable=self.lang_variable,
command=(lambda value: (lambda: self.root.options.language.set(value)))(file.stem),
)
# Advanced menu
@ -187,11 +190,14 @@ class Menu(tkinter.Menu):
master.add_cascade(label=_("THREADS_USAGE"), menu=self)
self.variable = tkinter.IntVar(value=self.root.options.threads.get())
for i in [1, 2, 4, 8, 12, 16]:
self.add_radiobutton(
label=_("USE", f" {i} ", "THREADS"),
value=i,
variable=self.root.options.threads,
variable=self.variable,
command=(lambda amount: (lambda: self.root.options.threads.set(amount)))(i),
)
# Help menu
@ -310,8 +316,9 @@ class DestinationGame(ttk.LabelFrame):
self.entry = ttk.Entry(self)
self.entry.grid(row=1, column=1, sticky="nsew")
self.output_type = ttk.Combobox(self, width=5, values=[extension.name for extension in Extension],
textvariable=self.root.options.extension)
self.output_type = ttk.Combobox(self, width=5, values=[extension.name for extension in Extension])
self.output_type.set(self.root.options.extension.get())
self.output_type.bind("<<ComboboxSelected>>", lambda _: self.root.options.extension.set(self.output_type.get()))
self.output_type.grid(row=1, column=2, sticky="nsew")
self.button = ttk.Button(self, text="...", width=2, command=self.select)
@ -420,7 +427,7 @@ class ButtonInstall(ttk.Button):
message: str = translate_external(
mod_config,
self.root.options.language.value,
self.root.options.language.get(),
mod_config.messages.get("installation_completed", {}).get("text", {})
)

View file

@ -66,7 +66,7 @@ class FrameSettings(ttk.Frame):
for index, (settings_name, settings_data) in enumerate(settings.items()):
text = translate_external(
self.master.master.mod_config,
self.root.options["language"],
self.root.options.language.get(),
settings_data.text,
)
@ -84,7 +84,7 @@ class FrameSettings(ttk.Frame):
# add at the end a message from the mod creator where he can put some additional note about the settings.
if text := translate_external(
self.master.master.mod_config,
self.root.options["language"],
self.root.options.language.get(),
self.master.master.mod_config.messages.get("settings_description", {}).get("text", {})
):
self.label_description = ttk.Label(self, text="\n"+text, foreground="gray")

View file

@ -20,7 +20,6 @@ class Window(tkinter.Toplevel):
self.grab_set() # the others window will be disabled, keeping only this one activated
self.disabled_text: str = _("<", "DISABLED", ">")
self.root.options["mystuff_pack_selected"] = self.root.options["mystuff_pack_selected"]
self.frame_profile = ttk.Frame(self)
self.frame_profile.grid(row=1, column=1, sticky="NEWS")
@ -80,33 +79,38 @@ class Window(tkinter.Toplevel):
"""
Refresh all the profile
"""
mystuff_packs = self.root.options.mystuff_packs.get()
selected_mystuff_pack = self.root.options.mystuff_pack_selected.get()
combobox_values = [self.disabled_text, *self.root.options["mystuff_packs"]]
combobox_values = [self.disabled_text, *self.root.options.mystuff_packs.get()]
self.combobox_profile.configure(values=combobox_values)
self.combobox_profile.current(combobox_values.index(
self.root.options["mystuff_pack_selected"]
if self.root.options["mystuff_pack_selected"] in self.root.options["mystuff_packs"] else
self.disabled_text
selected_mystuff_pack if selected_mystuff_pack in mystuff_packs else self.disabled_text
))
def select_profile(self, event: tkinter.Event = None, profile_name: str = None) -> None:
"""
Select another profile
"""
mystuff_packs = self.root.options.mystuff_packs.get()
profile_name = self.combobox_profile.get() if profile_name is None else profile_name
if not profile_name in self.root.options["mystuff_packs"]: profile_name = self.disabled_text
if not profile_name in mystuff_packs: profile_name = self.disabled_text
self.combobox_profile.set(profile_name)
self.root.options["mystuff_pack_selected"] = profile_name
self.root.options.mystuff_pack_selected.set(profile_name)
self.listbox_mystuff_paths.delete(0, tkinter.END)
is_disabled: bool = (profile_name == self.disabled_text)
state = tkinter.DISABLED if is_disabled else tkinter.NORMAL
self.button_delete_profile.configure(state=state)
for children in self.frame_mystuff_paths_action.children.values():
children.configure(state=tkinter.DISABLED if is_disabled else tkinter.NORMAL)
children.configure(state=state)
if is_disabled: return
profile_data = self.root.options["mystuff_packs"][profile_name]
profile_data = mystuff_packs[profile_name]
for path in profile_data["paths"]:
self.listbox_mystuff_paths.insert(tkinter.END, path)
@ -115,9 +119,10 @@ class Window(tkinter.Toplevel):
"""
Save the new profile
"""
mystuff_packs = self.root.options.mystuff_packs.get()
profile_name: str = self.combobox_profile.get()
if profile_name in self.root.options["mystuff_packs"]:
if profile_name in mystuff_packs:
messagebox.showerror(_("ERROR"), _("MYSTUFF_PROFILE_ALREADY_EXIST"))
return
@ -126,7 +131,8 @@ class Window(tkinter.Toplevel):
messagebox.showerror(_("ERROR"), _("MYSTUFF_PROFILE_FORBIDDEN_NAME"))
return
self.root.options["mystuff_packs"][profile_name] = {"paths": []}
mystuff_packs[profile_name] = {"paths": []}
self.root.options.mystuff_packs.set(mystuff_packs)
self.refresh_profiles()
self.select_profile(profile_name=profile_name)
@ -134,8 +140,10 @@ class Window(tkinter.Toplevel):
"""
Delete the currently selected profile
"""
mystuff_packs = self.root.options.mystuff_packs.get()
mystuff_packs.pop(self.root.options.mystuff_pack_selected.get())
self.root.options.mystuff_packs.set(mystuff_packs)
self.root.options["mystuff_packs"].pop(self.root.options["mystuff_pack_selected"])
self.refresh_profiles()
self.select_profile()
@ -147,9 +155,9 @@ class Window(tkinter.Toplevel):
if (mystuff_path := filedialog.askdirectory(title=_("SELECT_MYSTUFF"), mustexist=True)) is None: return
mystuff_path = Path(mystuff_path)
self.root.options["mystuff_packs"][self.root.options["mystuff_pack_selected"]]["paths"].append(
str(mystuff_path.resolve())
)
mystuff_packs = self.root.options.mystuff_packs.get()
mystuff_packs[self.root.options.mystuff_pack_selected.get()]["paths"].append(str(mystuff_path.resolve()))
self.root.options.mystuff_packs.set(mystuff_packs)
self.select_profile()
@ -161,7 +169,9 @@ class Window(tkinter.Toplevel):
selections = self.listbox_mystuff_paths.curselection()
if not selections: return
mystuff_packs = self.root.options.mystuff_packs.get()
for selection in selections:
self.root.options["mystuff_packs"][self.root.options["mystuff_pack_selected"]]["paths"].pop(selection)
mystuff_packs[self.root.options.mystuff_pack_selected.get()]["paths"].pop(selection)
self.root.options.mystuff_packs.set(mystuff_packs)
self.select_profile()

View file

@ -5,7 +5,7 @@ from source.mkw.ExtractedGame import ExtractedGame
from source.mkw.ModConfig import ModConfig
from source.mkw.collection.Extension import Extension
from source.mkw.collection.Region import Region
from source.option import Option
from source.option import Options
from source.progress import Progress
from source.wt.wit import WITPath
from source.translation import translate as _
@ -94,7 +94,7 @@ class Game:
return extracted_game
def install_mod(self, dest: Path, mod_config: ModConfig, options: "Option", output_type: Extension
def install_mod(self, dest: Path, mod_config: ModConfig, options: "Options", output_type: Extension
) -> Generator[Progress, None, None]:
"""
Patch the game with the mod
@ -125,7 +125,8 @@ class Game:
# install mystuff
yield Progress(title=_("MYSTUFF"), set_part=2)
mystuff_data = options["mystuff_packs"].get(options["mystuff_pack_selected"])
mystuff_packs = options.mystuff_packs.get()
mystuff_data = mystuff_packs.get(options.mystuff_pack_selected.get())
if mystuff_data is not None: yield from extracted_game.install_multiple_mystuff(mystuff_data["paths"])
# prepare the cache
@ -136,7 +137,7 @@ class Game:
cache_autoadd_directory,
cache_cttracks_directory,
cache_ogtracks_directory,
options["threads"],
options.threads.get(),
)
yield from extracted_game.prepare_dol()
yield from extracted_game.prepare_special_file(mod_config)

View file

@ -4,29 +4,62 @@ from pathlib import Path
from source import restart_program
class OptionLoadingError(Exception):
def __init__(self):
super().__init__(f"An error occured while loading options. Try deleting the option.json file.")
class Option:
"""
Class representing a single option. It mimic a TkinterVar to make binding easier
"""
__slots__ = ("options", "_value", "reboot_on_change")
def __init__(self, options: "Options", value: any, reboot_on_change: bool = False):
self.options = options
self._value = value
self.reboot_on_change = reboot_on_change
def get(self) -> any:
"""
:return: the value of the option
"""
return self._value
def set(self, value, ignore_reboot: bool = False) -> None:
"""
Set the value of the option and save the settings.
:param value: the new value of the option
:param ignore_reboot: should the installer ignore the reboot if the settings need it ?
"""
self._value = value
self.options.save()
if self.reboot_on_change and not ignore_reboot: restart_program()
class Options:
"""
Class representing a group of Options
"""
__slots__ = ("_path", "_options")
reboot_on_change: list[str] = [
"language",
]
def __init__(self, path, **options):
self._path: Path = Path(path)
default_options: dict[str, any] = {
"language": "en",
"threads": 8,
"mystuff_pack_selected": None,
"mystuff_packs": {},
"extension": "WBFS",
}
def __init__(self, **options):
self._path: Path | None = None
self._options: dict[str, any] = self.default_options.copy()
self._options: dict[str, Option] = {
"language": Option(self, value="en", reboot_on_change=True),
"threads": Option(self, value=8),
"mystuff_pack_selected": Option(self, value=None),
"mystuff_packs": Option(self, value={}),
"extension": Option(self, value="WBFS"),
}
for option_name, option_value in options.items():
self._options[option_name] = option_value
self._options[option_name].set(option_value, ignore_reboot=True)
def __getitem__(self, key: str) -> any:
def __getattr__(self, key: str) -> any:
"""
get an options value from its key
:param key: the option name
@ -34,49 +67,40 @@ class Option:
"""
return self._options[key]
def __setitem__(self, key: str, value: any) -> None:
"""
change the value of an options for a key, if the options have been loaded from a file, save it inside
if the option is in the reboot_on_change list, reboot the program
:param key: the name of the option to edit
:param value: the value of the option
:return:
"""
self._options[key] = value
if self._path: self.save()
if key in self.reboot_on_change: restart_program()
def save(self, option_file: Path | str = None) -> None:
def save(self) -> None:
"""
save the options to the file
:return: None
"""
if option_file is None: option_file = self._path
option_file = Path(option_file)
with self._path.open("w") as file:
json.dump(self.to_dict(), file, indent=4, ensure_ascii=False)
with option_file.open("w") as file:
json.dump(self._options, file, indent=4, ensure_ascii=False)
def to_dict(self) -> dict[str, any]:
"""
Return the dictionary form of the options
:return:
"""
return {key: option.get() for key, option in self._options.items()}
@classmethod
def from_dict(cls, option_dict: dict) -> "Option":
def from_dict(cls, path: Path, option_dict: dict) -> "Options":
"""
Create a Option from a dict if the parameters are in the default_options
:param path: path to the option file
:param option_dict: dict containing the configuration
:return: Option
"""
return cls(**option_dict)
return cls(path, **option_dict)
@classmethod
def from_file(cls, option_file: str | Path) -> "Option":
def from_file(cls, option_file: str | Path) -> "Options":
"""
Loads the option from a file. If the option file does not exist, only load default configuration
:param option_file: the option file
:return: Option
"""
option_file = Path(option_file)
try: data = json.loads(option_file.read_text(encoding="utf8")) if option_file.exists() else {}
except Exception as exc: raise OptionLoadingError() from exc
return cls.from_dict(option_file, data)
if not option_file.exists(): obj = cls()
else: obj = cls.from_dict(json.loads(option_file.read_text(encoding="utf8")))
obj._path = option_file
return obj