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.gui import install
|
||||||
from source.option import Option
|
from source.option import Options
|
||||||
from source.translation import load_language
|
from source.translation import load_language
|
||||||
|
|
||||||
|
|
||||||
# this allows every variable to be accessible from other files, useful for the plugins
|
# this allows every variable to be accessible from other files, useful for the plugins
|
||||||
self = __import__(__name__)
|
self = __import__(__name__)
|
||||||
|
|
||||||
options = Option.from_file("./option.json")
|
options = Options.from_file("./option.json")
|
||||||
translater = load_language(options["language"])
|
translater = load_language(options.language.get())
|
||||||
|
|
||||||
self.window = install.Window(options)
|
self.window = install.Window(options)
|
||||||
self.window.run()
|
self.window.run()
|
||||||
|
|
|
@ -161,12 +161,15 @@ class Menu(tkinter.Menu):
|
||||||
|
|
||||||
master.add_cascade(label=_("LANGUAGE_SELECTION"), menu=self)
|
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():
|
for file in Path("./assets/language/").iterdir():
|
||||||
lang_json = json.loads(file.read_text(encoding="utf8"))
|
lang_json = json.loads(file.read_text(encoding="utf8"))
|
||||||
self.add_radiobutton(
|
self.add_radiobutton(
|
||||||
label=lang_json["name"],
|
label=lang_json["name"],
|
||||||
value=file.stem,
|
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
|
# Advanced menu
|
||||||
|
@ -187,11 +190,14 @@ class Menu(tkinter.Menu):
|
||||||
|
|
||||||
master.add_cascade(label=_("THREADS_USAGE"), menu=self)
|
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]:
|
for i in [1, 2, 4, 8, 12, 16]:
|
||||||
self.add_radiobutton(
|
self.add_radiobutton(
|
||||||
label=_("USE", f" {i} ", "THREADS"),
|
label=_("USE", f" {i} ", "THREADS"),
|
||||||
value=i,
|
value=i,
|
||||||
variable=self.root.options.threads,
|
variable=self.variable,
|
||||||
|
command=(lambda amount: (lambda: self.root.options.threads.set(amount)))(i),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Help menu
|
# Help menu
|
||||||
|
@ -310,8 +316,9 @@ class DestinationGame(ttk.LabelFrame):
|
||||||
self.entry = ttk.Entry(self)
|
self.entry = ttk.Entry(self)
|
||||||
self.entry.grid(row=1, column=1, sticky="nsew")
|
self.entry.grid(row=1, column=1, sticky="nsew")
|
||||||
|
|
||||||
self.output_type = ttk.Combobox(self, width=5, values=[extension.name for extension in Extension],
|
self.output_type = ttk.Combobox(self, width=5, values=[extension.name for extension in Extension])
|
||||||
textvariable=self.root.options.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.output_type.grid(row=1, column=2, sticky="nsew")
|
||||||
|
|
||||||
self.button = ttk.Button(self, text="...", width=2, command=self.select)
|
self.button = ttk.Button(self, text="...", width=2, command=self.select)
|
||||||
|
@ -420,7 +427,7 @@ class ButtonInstall(ttk.Button):
|
||||||
|
|
||||||
message: str = translate_external(
|
message: str = translate_external(
|
||||||
mod_config,
|
mod_config,
|
||||||
self.root.options.language.value,
|
self.root.options.language.get(),
|
||||||
mod_config.messages.get("installation_completed", {}).get("text", {})
|
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()):
|
for index, (settings_name, settings_data) in enumerate(settings.items()):
|
||||||
text = translate_external(
|
text = translate_external(
|
||||||
self.master.master.mod_config,
|
self.master.master.mod_config,
|
||||||
self.root.options["language"],
|
self.root.options.language.get(),
|
||||||
settings_data.text,
|
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.
|
# add at the end a message from the mod creator where he can put some additional note about the settings.
|
||||||
if text := translate_external(
|
if text := translate_external(
|
||||||
self.master.master.mod_config,
|
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.master.master.mod_config.messages.get("settings_description", {}).get("text", {})
|
||||||
):
|
):
|
||||||
self.label_description = ttk.Label(self, text="\n"+text, foreground="gray")
|
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.grab_set() # the others window will be disabled, keeping only this one activated
|
||||||
|
|
||||||
self.disabled_text: str = _("<", "DISABLED", ">")
|
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 = ttk.Frame(self)
|
||||||
self.frame_profile.grid(row=1, column=1, sticky="NEWS")
|
self.frame_profile.grid(row=1, column=1, sticky="NEWS")
|
||||||
|
@ -80,33 +79,38 @@ class Window(tkinter.Toplevel):
|
||||||
"""
|
"""
|
||||||
Refresh all the profile
|
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.configure(values=combobox_values)
|
||||||
self.combobox_profile.current(combobox_values.index(
|
self.combobox_profile.current(combobox_values.index(
|
||||||
self.root.options["mystuff_pack_selected"]
|
selected_mystuff_pack if selected_mystuff_pack in mystuff_packs else self.disabled_text
|
||||||
if self.root.options["mystuff_pack_selected"] in self.root.options["mystuff_packs"] else
|
|
||||||
self.disabled_text
|
|
||||||
))
|
))
|
||||||
|
|
||||||
def select_profile(self, event: tkinter.Event = None, profile_name: str = None) -> None:
|
def select_profile(self, event: tkinter.Event = None, profile_name: str = None) -> None:
|
||||||
"""
|
"""
|
||||||
Select another profile
|
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
|
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.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)
|
self.listbox_mystuff_paths.delete(0, tkinter.END)
|
||||||
|
|
||||||
is_disabled: bool = (profile_name == self.disabled_text)
|
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():
|
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
|
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"]:
|
for path in profile_data["paths"]:
|
||||||
self.listbox_mystuff_paths.insert(tkinter.END, path)
|
self.listbox_mystuff_paths.insert(tkinter.END, path)
|
||||||
|
@ -115,9 +119,10 @@ class Window(tkinter.Toplevel):
|
||||||
"""
|
"""
|
||||||
Save the new profile
|
Save the new profile
|
||||||
"""
|
"""
|
||||||
|
mystuff_packs = self.root.options.mystuff_packs.get()
|
||||||
|
|
||||||
profile_name: str = self.combobox_profile.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"))
|
messagebox.showerror(_("ERROR"), _("MYSTUFF_PROFILE_ALREADY_EXIST"))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -126,7 +131,8 @@ class Window(tkinter.Toplevel):
|
||||||
messagebox.showerror(_("ERROR"), _("MYSTUFF_PROFILE_FORBIDDEN_NAME"))
|
messagebox.showerror(_("ERROR"), _("MYSTUFF_PROFILE_FORBIDDEN_NAME"))
|
||||||
return
|
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.refresh_profiles()
|
||||||
self.select_profile(profile_name=profile_name)
|
self.select_profile(profile_name=profile_name)
|
||||||
|
|
||||||
|
@ -134,8 +140,10 @@ class Window(tkinter.Toplevel):
|
||||||
"""
|
"""
|
||||||
Delete the currently selected profile
|
Delete the currently selected profile
|
||||||
"""
|
"""
|
||||||
|
mystuff_packs = self.root.options.mystuff_packs.get()
|
||||||
self.root.options["mystuff_packs"].pop(self.root.options["mystuff_pack_selected"])
|
mystuff_packs.pop(self.root.options.mystuff_pack_selected.get())
|
||||||
|
self.root.options.mystuff_packs.set(mystuff_packs)
|
||||||
|
|
||||||
self.refresh_profiles()
|
self.refresh_profiles()
|
||||||
self.select_profile()
|
self.select_profile()
|
||||||
|
|
||||||
|
@ -147,9 +155,9 @@ class Window(tkinter.Toplevel):
|
||||||
if (mystuff_path := filedialog.askdirectory(title=_("SELECT_MYSTUFF"), mustexist=True)) is None: return
|
if (mystuff_path := filedialog.askdirectory(title=_("SELECT_MYSTUFF"), mustexist=True)) is None: return
|
||||||
mystuff_path = Path(mystuff_path)
|
mystuff_path = Path(mystuff_path)
|
||||||
|
|
||||||
self.root.options["mystuff_packs"][self.root.options["mystuff_pack_selected"]]["paths"].append(
|
mystuff_packs = self.root.options.mystuff_packs.get()
|
||||||
str(mystuff_path.resolve())
|
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()
|
self.select_profile()
|
||||||
|
|
||||||
|
@ -161,7 +169,9 @@ class Window(tkinter.Toplevel):
|
||||||
selections = self.listbox_mystuff_paths.curselection()
|
selections = self.listbox_mystuff_paths.curselection()
|
||||||
if not selections: return
|
if not selections: return
|
||||||
|
|
||||||
|
mystuff_packs = self.root.options.mystuff_packs.get()
|
||||||
for selection in selections:
|
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()
|
self.select_profile()
|
||||||
|
|
|
@ -5,7 +5,7 @@ from source.mkw.ExtractedGame import ExtractedGame
|
||||||
from source.mkw.ModConfig import ModConfig
|
from source.mkw.ModConfig import ModConfig
|
||||||
from source.mkw.collection.Extension import Extension
|
from source.mkw.collection.Extension import Extension
|
||||||
from source.mkw.collection.Region import Region
|
from source.mkw.collection.Region import Region
|
||||||
from source.option import Option
|
from source.option import Options
|
||||||
from source.progress import Progress
|
from source.progress import Progress
|
||||||
from source.wt.wit import WITPath
|
from source.wt.wit import WITPath
|
||||||
from source.translation import translate as _
|
from source.translation import translate as _
|
||||||
|
@ -94,7 +94,7 @@ class Game:
|
||||||
|
|
||||||
return extracted_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]:
|
) -> Generator[Progress, None, None]:
|
||||||
"""
|
"""
|
||||||
Patch the game with the mod
|
Patch the game with the mod
|
||||||
|
@ -125,7 +125,8 @@ class Game:
|
||||||
|
|
||||||
# install mystuff
|
# install mystuff
|
||||||
yield Progress(title=_("MYSTUFF"), set_part=2)
|
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"])
|
if mystuff_data is not None: yield from extracted_game.install_multiple_mystuff(mystuff_data["paths"])
|
||||||
|
|
||||||
# prepare the cache
|
# prepare the cache
|
||||||
|
@ -136,7 +137,7 @@ class Game:
|
||||||
cache_autoadd_directory,
|
cache_autoadd_directory,
|
||||||
cache_cttracks_directory,
|
cache_cttracks_directory,
|
||||||
cache_ogtracks_directory,
|
cache_ogtracks_directory,
|
||||||
options["threads"],
|
options.threads.get(),
|
||||||
)
|
)
|
||||||
yield from extracted_game.prepare_dol()
|
yield from extracted_game.prepare_dol()
|
||||||
yield from extracted_game.prepare_special_file(mod_config)
|
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
|
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 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")
|
__slots__ = ("_path", "_options")
|
||||||
|
|
||||||
reboot_on_change: list[str] = [
|
def __init__(self, path, **options):
|
||||||
"language",
|
self._path: Path = Path(path)
|
||||||
]
|
|
||||||
|
|
||||||
default_options: dict[str, any] = {
|
self._options: dict[str, Option] = {
|
||||||
"language": "en",
|
"language": Option(self, value="en", reboot_on_change=True),
|
||||||
"threads": 8,
|
"threads": Option(self, value=8),
|
||||||
"mystuff_pack_selected": None,
|
"mystuff_pack_selected": Option(self, value=None),
|
||||||
"mystuff_packs": {},
|
"mystuff_packs": Option(self, value={}),
|
||||||
"extension": "WBFS",
|
"extension": Option(self, value="WBFS"),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, **options):
|
|
||||||
self._path: Path | None = None
|
|
||||||
self._options: dict[str, any] = self.default_options.copy()
|
|
||||||
|
|
||||||
for option_name, option_value in options.items():
|
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
|
get an options value from its key
|
||||||
:param key: the option name
|
:param key: the option name
|
||||||
|
@ -34,49 +67,40 @@ class Option:
|
||||||
"""
|
"""
|
||||||
return self._options[key]
|
return self._options[key]
|
||||||
|
|
||||||
def __setitem__(self, key: str, value: any) -> None:
|
def save(self) -> 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:
|
|
||||||
"""
|
"""
|
||||||
save the options to the file
|
save the options to the file
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
if option_file is None: option_file = self._path
|
with self._path.open("w") as file:
|
||||||
option_file = Path(option_file)
|
json.dump(self.to_dict(), file, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
with option_file.open("w") as file:
|
def to_dict(self) -> dict[str, any]:
|
||||||
json.dump(self._options, file, indent=4, ensure_ascii=False)
|
"""
|
||||||
|
Return the dictionary form of the options
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return {key: option.get() for key, option in self._options.items()}
|
||||||
|
|
||||||
@classmethod
|
@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
|
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
|
:param option_dict: dict containing the configuration
|
||||||
:return: Option
|
:return: Option
|
||||||
"""
|
"""
|
||||||
return cls(**option_dict)
|
return cls(path, **option_dict)
|
||||||
|
|
||||||
@classmethod
|
@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
|
Loads the option from a file. If the option file does not exist, only load default configuration
|
||||||
:param option_file: the option file
|
:param option_file: the option file
|
||||||
:return: Option
|
:return: Option
|
||||||
"""
|
"""
|
||||||
option_file = Path(option_file)
|
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