mirror of
https://github.com/Faraphel/Atlas-Install.git
synced 2025-07-02 02:38:30 +02:00
Options class have been rewritten to make saving and getting more explicit
This commit is contained in:
parent
e88fb57143
commit
c511f7bb86
6 changed files with 116 additions and 74 deletions
6
main.pyw
6
main.pyw
|
@ -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()
|
||||
|
|
|
@ -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", {})
|
||||
)
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
||||
self.root.options["mystuff_packs"].pop(self.root.options["mystuff_pack_selected"])
|
||||
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.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()
|
||||
|
|
|
@ -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)
|
||||
|
|
106
source/option.py
106
source/option.py
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue