diff --git a/assets/language/en.json b/assets/language/en.json index f97aa8f..0173745 100644 --- a/assets/language/en.json +++ b/assets/language/en.json @@ -1,6 +1,10 @@ { "name": "English", "translation": { - "INSTALLER_TITLE": "MKWF-Install" + "INSTALLER_TITLE": "MKWF-Install", + "LANGUAGE_SELECTION": "Language", + "TRACK_CONFIGURATION": "Track Configuration", + "ADVANCED_CONFIGURATION": "Advanced", + "HELP": "Help" } } \ No newline at end of file diff --git a/assets/language/fr.json b/assets/language/fr.json index f60b8c6..0cc76ef 100644 --- a/assets/language/fr.json +++ b/assets/language/fr.json @@ -1,6 +1,10 @@ { "name": "Français", "translation": { - + "INSTALLER_TITLE": "MKWF-Install", + "LANGUAGE_SELECTION": "Langue", + "TRACK_CONFIGURATION": "Configuration des courses", + "ADVANCED_CONFIGURATION": "Avancée", + "HELP": "Aide" } } \ No newline at end of file diff --git a/main.pyw b/main.pyw index 3c69467..b37acc9 100644 --- a/main.pyw +++ b/main.pyw @@ -1,3 +1,8 @@ from source.gui import install +import sys -install.Window().mainloop() +# this allows every variable to be accessible from other files, useful for the plugins +self = sys.modules[__name__] + +self.window = install.Window() +self.window.run() diff --git a/plugins/_test.py b/plugins/_test.py index d479385..101845f 100644 --- a/plugins/_test.py +++ b/plugins/_test.py @@ -1,10 +1,22 @@ from source import event import tkinter +import sys -@event.on("source.gui.install.Window.__init__") -def test_button(master): - tkinter.Button(master, text="test des plugins", command=lambda: print("test")).grid( +@event.on("source.gui.install.Window.run_after") +def test_button(): + """ + Test function for the plugins + :return: + """ + + # get the main window from the main module + window = sys.modules["__main__"].window + + # get the install button from the main window + window.button_install.config(text="installation plugins") + + # add a custom button on the main window + tkinter.Button(window, text="test des plugins", command=lambda: print("test")).grid( row=10, column=1, sticky="nsew" - ) - print("I have been called") + ) \ No newline at end of file diff --git a/setup.py b/setup.py index dcf8460..18cb888 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from cx_Freeze import setup, Executable import sys -import json +import source include_files = [ "./LICENSE", @@ -25,7 +25,7 @@ options = { setup( options=options, name='MKWF-Install', - version="0.12", + version=".".join(source.__version__), url='https://github.com/Faraphel/MKWF-Install', license='Apache-2.0', author='Faraphel', diff --git a/source/__init__.py b/source/__init__.py index e69de29..30d928d 100644 --- a/source/__init__.py +++ b/source/__init__.py @@ -0,0 +1,12 @@ +__version__ = (0, 12, 0) +__author__ = 'Faraphel' + + +discord_url = "https://discord.gg/HEYW5v8ZCd" +github_wiki_url = "https://github.com/Faraphel/MKWF-Install/wiki/help" + +Ko = 1_000 +Mo = 1_000 * Ko +Go = 1_000 * Mo + +minimum_space_available = 15*Go diff --git a/source/event.py b/source/event.py index 3860eea..b7a5bfb 100644 --- a/source/event.py +++ b/source/event.py @@ -2,11 +2,11 @@ from types import FunctionType from pathlib import Path import sys +# using this self variable allow us to keep the events in the whole module self = sys.modules[__name__] self.events = {} -# register the function to the event def on(event_name: str): """ Register the function to be called when the event is called. @@ -23,7 +23,6 @@ def on(event_name: str): return decorator -# register all the events at the end of the function def register(func: FunctionType): """ Register the function as an event. @@ -39,7 +38,6 @@ def register(func: FunctionType): return wrapper -# call all the events of the event_name def call_event(event_name: str, *args, **kwargs) -> None: """ Call all the events associated with the event_name. @@ -47,9 +45,13 @@ def call_event(event_name: str, *args, **kwargs) -> None: :return: """ for func in self.events.get(event_name, []): - func(*args, **kwargs) + func() -# execute all scripts in the ./plugins/ directory that don't start with an underscore -for file in Path("./plugins/").rglob("[!_]*.py"): - exec(file.read_text(encoding="utf8"), globals()) +def initialise_plugins() -> None: + """ + Execute all the scripts in the ./plugins/ directory that don't start with an underscore. + :return: + """ + for file in Path("./plugins/").rglob("[!_]*.py"): + exec(file.read_text(encoding="utf8"), globals()) diff --git a/source/gui/install.py b/source/gui/install.py index 1d76186..a66d240 100644 --- a/source/gui/install.py +++ b/source/gui/install.py @@ -1,16 +1,20 @@ +import shutil import tkinter from pathlib import Path import json from tkinter import ttk - +from tkinter import filedialog +from tkinter import messagebox +import webbrowser from source.translation import translate as _ from source import event +from source import * +import os # Main window for the installer class Window(tkinter.Tk): - @event.register def __init__(self): super().__init__() @@ -38,6 +42,22 @@ class Window(tkinter.Tk): self.progress_bar = ProgressBar(self) self.progress_bar.grid(row=5, column=1, sticky="nsew") + def run(self) -> None: + """ + Run the installer + """ + event.initialise_plugins() + self.after(0, self.run_after) + self.mainloop() + + @event.register + def run_after(self) -> None: + """ + Run after the installer has been initialised, can be used to add plugins + :return: + """ + return None + # Menu bar class Menu(tkinter.Menu): @@ -54,7 +74,7 @@ class Menu(tkinter.Menu): def __init__(self, master: tkinter.Menu): super().__init__(master, tearoff=False) - master.add_cascade(label="Language", menu=self) + master.add_cascade(label=_("LANGUAGE_SELECTION"), menu=self) for file in Path("./assets/language/").iterdir(): self.add_command(label=json.loads(file.read_text(encoding="utf8"))["name"]) @@ -64,7 +84,7 @@ class Menu(tkinter.Menu): def __init__(self, master: tkinter.Menu): super().__init__(master, tearoff=False) - master.add_cascade(label="Track Configuration", menu=self) + master.add_cascade(label=_("TRACK_CONFIGURATION"), menu=self) self.add_command(label="Change configuration") # Advanced menu @@ -72,7 +92,7 @@ class Menu(tkinter.Menu): def __init__(self, master: tkinter.Menu): super().__init__(master, tearoff=False) - master.add_cascade(label="Advanced", menu=self) + master.add_cascade(label=_("ADVANCED_CONFIGURATION"), menu=self) self.add_command(label="Debug mode") # Help menu @@ -81,8 +101,8 @@ class Menu(tkinter.Menu): super().__init__(master, tearoff=False) master.add_cascade(label="Help", menu=self) - self.add_command(label="Discord") - self.add_command(label="Github Wiki") + self.add_command(label="Discord", command=lambda: webbrowser.open(discord_url)) + self.add_command(label="Github Wiki", command=lambda: webbrowser.open(github_wiki_url)) # Select game frame @@ -93,7 +113,35 @@ class SourceGame(ttk.LabelFrame): self.entry = ttk.Entry(self, width=50) self.entry.grid(row=1, column=1, sticky="nsew") - ttk.Button(self, text="...", width=2).grid(row=1, column=2, sticky="nsew") + self.button = ttk.Button(self, text="...", width=2, command=self.select) + self.button.grid(row=1, column=2, sticky="nsew") + + def select(self) -> None: + """ + Select the source game + :return: + """ + path = Path(tkinter.filedialog.askopenfilename( + title=_("SELECT_SOURCE_GAME"), + filetypes=[(_("WII GAMES"), "*.iso *.wbfs *.dol")], + )) + # if the user didn't select any file, return None + if not path.exists(): + messagebox.showerror(_("ERROR"), _("ERROR_INVALID_SOURCE_GAME")) + return + + self.set_path(path) + + def set_path(self, path: Path) -> None: + """ + Set the source game path + :param path: + :return: + """ + self.entry.delete(0, tkinter.END) + self.entry.insert(0, str(path.absolute())) + + self.master.destination_game.set_path(path.parent / "MKWF.iso") # Select game destination frame @@ -104,7 +152,29 @@ class DestinationGame(ttk.LabelFrame): self.entry = ttk.Entry(self, width=50) self.entry.grid(row=1, column=1, sticky="nsew") - ttk.Button(self, text="...", width=2).grid(row=1, column=2, sticky="nsew") + self.button = ttk.Button(self, text="...", width=2, command=self.select) + self.button.grid(row=1, column=2, sticky="nsew") + + def select(self) -> None: + """ + Select the source game + :return: + """ + path = Path(tkinter.filedialog.asksaveasfilename( + title=_("SELECT_DESTINATION_GAME"), + filetypes=[(_("WII GAMES"), "*.iso *.wbfs *.dol")], + )) + + path.parent.mkdir(mode=0o777, parents=True, exist_ok=True) + + self.set_path(path) + + def set_path(self, path: Path): + if not os.access(path.parent, os.W_OK): + messagebox.showwarning(_("WARNING"), _("WARNING_DESTINATION_GAME_NOT_WRITABLE")) + + self.entry.delete(0, tkinter.END) + self.entry.insert(0, str(path.absolute())) # Install button @@ -113,18 +183,23 @@ class ButtonInstall(ttk.Button): super().__init__(master, text="Install", command=self.install) def install(self): - ... + # get space remaining on the C: drive + if shutil.disk_usage(".").free < minimum_space_available: + if not messagebox.askokcancel(_("WARNING"), _("WARNING_NOT_ENOUGH_SPACE_CONTINUE")): return # Progress bar -class ProgressBar(ttk.Frame): +class ProgressBar(ttk.LabelFrame): def __init__(self, master: tkinter.Tk): super().__init__(master) - self.progress_bar = ttk.Progressbar(self) + # make the element fill the whole frame + self.columnconfigure(1, weight=1) + + self.progress_bar = ttk.Progressbar(self, orient="horizontal") self.progress_bar.grid(row=1, column=1, sticky="nsew") - self.description = tkinter.Label(self, text="test") + self.description = ttk.Label(self, text="no process running", anchor="center", font=("TkDefaultFont", 10)) self.description.grid(row=2, column=1, sticky="nsew") diff --git a/source/translation.py b/source/translation.py index 1fa54f2..4e24f9f 100644 --- a/source/translation.py +++ b/source/translation.py @@ -5,5 +5,10 @@ from pathlib import Path language_data = json.loads(Path("./assets/language/en.json").read_text(encoding="utf8")) -def translate(*text): +def translate(*text) -> str: + """ + Translate a text to the loaded language. + :param text: list of text to translate + :return: translated text + """ return "".join([language_data["translation"].get(word, word) for word in text])