module filtering subclasses have been simplified to made the code easier to read with less directory inside other useless directory and comment have been added

This commit is contained in:
Faraphel 2022-07-31 23:05:54 +02:00
parent 547e9108da
commit 59106d8ed9
30 changed files with 223 additions and 223 deletions

View file

@ -1,7 +1,6 @@
import tkinter import tkinter
from tkinter import ttk from tkinter import ttk
from source.translation import translate as _ from source.translation import translate as _
from source.gui.preview import track_formatting
ModConfig: any ModConfig: any

View file

@ -23,16 +23,16 @@ class AbstractPreviewWindow(tkinter.Toplevel, ABC):
super().__init__() super().__init__()
... ...
@classmethod
def get_preview_window_class(name: str) -> Type[AbstractPreviewWindow]: def get(cls, name: str) -> Type["AbstractPreviewWindow"]:
""" """
Return the windows class object from its name Return the windows class object from its name
:param name: name of the window class :param name: name of the window class
:return: the window class object :return: the window class object
""" """
for window_class in filter(lambda cls: cls.name == name, AbstractPreviewWindow.__subclasses__()): for subclass in filter(lambda subclass: subclass.name == name, cls.__subclasses__()):
return window_class return subclass
raise InvalidPreviewWindowName(name) raise InvalidPreviewWindowName(name)
from source.gui.preview import track_formatting, track_selecting from source.gui.preview import track_formatting, track_selecting

View file

@ -8,7 +8,7 @@ from source import threaded
from source.mkw import Tag from source.mkw import Tag
from source.mkw.Cup import Cup from source.mkw.Cup import Cup
from source.mkw.MKWColor import bmg_color_text from source.mkw.MKWColor import bmg_color_text
from source.mkw.ModSettings import ModSettings from source.mkw.ModSettings import AbstractModSettings
from source.mkw.Track import Track from source.mkw.Track import Track
import json import json
@ -65,8 +65,10 @@ class ModConfig:
self.macros: dict = macros if macros is not None else {} self.macros: dict = macros if macros is not None else {}
self.messages: dict = messages if messages is not None else {} self.messages: dict = messages if messages is not None else {}
self.global_settings: dict = ModSettings(global_settings) self.global_settings: dict = AbstractModSettings.get(global_settings)
self.specific_settings: dict = ModSettings(specific_settings if specific_settings is not None else {}) self.specific_settings: dict = AbstractModSettings.get(
specific_settings if specific_settings is not None else {}
)
self.name: str = name self.name: str = name
self.nickname: str = nickname if nickname is not None else name self.nickname: str = nickname if nickname is not None else name

View file

@ -1,9 +1,10 @@
import tkinter import tkinter
from tkinter import ttk from tkinter import ttk
from source.mkw.ModSettings.TypeSettings import AbstractTypeSettings
from source.mkw.ModSettings import AbstractModSettings
class Choices(AbstractTypeSettings): class Choices(AbstractModSettings):
""" """
This setting type allow you to input a string text. 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. You can optionally add a "preview" to allow the user to use a window to select the value.

View file

@ -1,11 +1,11 @@
import tkinter import tkinter
from tkinter import ttk from tkinter import ttk
from source.mkw.ModSettings.TypeSettings import AbstractTypeSettings from source.mkw.ModSettings import AbstractModSettings
from source.gui.preview import get_preview_window_class from source.gui.preview import AbstractPreviewWindow
class String(AbstractTypeSettings): class String(AbstractModSettings):
""" """
This setting type allow you to input a string text. 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. You can optionally add a "preview" to allow the user to use a window to select the value.
@ -30,7 +30,7 @@ class String(AbstractTypeSettings):
if self.preview is not None: if self.preview is not None:
button = ttk.Button( button = ttk.Button(
master, text="...", width=3, master, text="...", width=3,
command=lambda: get_preview_window_class( command=lambda: AbstractPreviewWindow.get(
self.preview self.preview
)(master.master.master.master.mod_config, value_variable) )(master.master.master.master.mod_config, value_variable)
) )

View file

@ -1,31 +0,0 @@
import tkinter
from tkinter import ttk
from abc import ABC, abstractmethod
class AbstractTypeSettings(ABC):
type: str # type name of the settings
value: str # value for the settings
enabled: bool # is the settings enabled
@abstractmethod
def __init__(self, value: str = None, preview: str = None, enabled: bool = False):
...
@abstractmethod
def tkinter_show(self, master: ttk.LabelFrame, checkbox) -> None:
"""
Show the option inside a tkinter widget
:master: master widget
:checkbox: checkbox inside the labelframe allowing to enable or disable the setting
"""
master.grid_rowconfigure(1, weight=1)
master.grid_columnconfigure(1, weight=1)
enabled_variable = tkinter.BooleanVar(master, value=self.enabled)
enabled_variable.trace_add("write", lambda *_: setattr(self, "enabled", enabled_variable.get()))
checkbox.configure(variable=enabled_variable)
...
from source.mkw.ModSettings.TypeSettings import String, Choices

View file

@ -1,4 +1,6 @@
from source.mkw.ModSettings.TypeSettings import AbstractTypeSettings import tkinter
from tkinter import ttk
from abc import ABC, abstractmethod
class InvalidSettingsType(Exception): class InvalidSettingsType(Exception):
@ -6,21 +8,51 @@ class InvalidSettingsType(Exception):
super().__init__(f"Error : Type of mod settings '{settings_type}' not found.") super().__init__(f"Error : Type of mod settings '{settings_type}' not found.")
class ModSettings: class AbstractModSettings(ABC):
def __new__(cls, settings_dict: dict) -> dict[str, AbstractTypeSettings]: """
Base class for every different type of ModSettings
"""
type: str # type name of the settings
value: str # value for the settings
enabled: bool # is the settings enabled
@abstractmethod
def __init__(self, value: str = None, preview: str = None, enabled: bool = False):
...
@abstractmethod
def tkinter_show(self, master: ttk.LabelFrame, checkbox) -> None:
"""
Show the option inside a tkinter widget
:master: master widget
:checkbox: checkbox inside the labelframe allowing to enable or disable the setting
"""
master.grid_rowconfigure(1, weight=1)
master.grid_columnconfigure(1, weight=1)
enabled_variable = tkinter.BooleanVar(master, value=self.enabled)
enabled_variable.trace_add("write", lambda *_: setattr(self, "enabled", enabled_variable.get()))
checkbox.configure(variable=enabled_variable)
...
@classmethod
def get(cls, settings_dict: dict) -> dict[str, "AbstractModSettings"]:
""" """
Load all the settings in mod_settings_dict Load all the settings in mod_settings_dict
:param settings_dict: dictionnary containing all the settings defined for the mod :param settings_dict: dictionary containing all the settings defined for the mod
""" """
settings: dict[str, AbstractTypeSettings] = {} settings: dict[str, AbstractModSettings] = {}
for settings_name, settings_data in settings_dict.items(): for settings_name, settings_data in settings_dict.items():
for subclass in filter( for subclass in filter(lambda subclass: subclass.type == settings_data["type"], cls.__subclasses__()):
lambda subclass: subclass.type == settings_data["type"], AbstractTypeSettings.__subclasses__()
):
settings_data.pop("type") settings_data.pop("type")
settings[settings_name] = subclass(**settings_data) settings[settings_name] = subclass(**settings_data)
break break
else: raise InvalidSettingsType(settings_name) else: raise InvalidSettingsType(settings_name)
return settings return settings
# these import load the different ModSettings, and so get_mod_settings will be able to fetch them with __subclasses__
from source.mkw.ModSettings import Choices, String

View file

@ -2,7 +2,7 @@ from io import BytesIO
from typing import Generator, IO from typing import Generator, IO
from source.mkw.Patch import * from source.mkw.Patch import *
from source.mkw.Patch.PatchOperation import PatchOperation from source.mkw.Patch.PatchOperation import AbstractPatchOperation
from source.mkw.Patch.PatchObject import PatchObject from source.mkw.Patch.PatchObject import PatchObject
from source.wt.szs import SZSPath from source.wt.szs import SZSPath
@ -55,7 +55,7 @@ class PatchFile(PatchObject):
for operation_name, operation in self.configuration.get("operation", {}).items(): for operation_name, operation in self.configuration.get("operation", {}).items():
# process every operation and get the new patch_path (if the name is changed) # process every operation and get the new patch_path (if the name is changed)
# and the new content of the patch # and the new content of the patch
patch_name, patch_content = PatchOperation(operation_name)(**operation).patch( patch_name, patch_content = AbstractPatchOperation.get(operation_name)(**operation).patch(
self.patch, patch_name, patch_content self.patch, patch_name, patch_content
) )

View file

@ -1,14 +1,14 @@
from io import BytesIO from io import BytesIO
from typing import IO from typing import IO
from source.mkw.Patch.PatchOperation.Operation import * from source.mkw.Patch.PatchOperation import AbstractPatchOperation
from source.wt import bmg from source.wt import bmg
Patch: any Patch: any
class BmgDecoder(AbstractOperation): class BmgDecoder(AbstractPatchOperation):
""" """
decode a bmg file to a txt file decode a bmg file to a txt file
""" """

View file

@ -1,13 +1,13 @@
from io import BytesIO from io import BytesIO
from typing import IO from typing import IO
from source.mkw.Patch.PatchOperation.Operation import * from source.mkw.Patch.PatchOperation import AbstractPatchOperation
from source.wt import bmg from source.wt import bmg
Patch: any Patch: any
class BmgEncoder(AbstractOperation): class BmgEncoder(AbstractPatchOperation):
""" """
encode a bmg file to a txt file encode a bmg file to a txt file
""" """

View file

@ -1,6 +1,7 @@
from source.mkw.Patch.PatchOperation.Operation.BmgTxtEditor.Layer import * from source.mkw.Patch.PatchOperation.BmgTxtEditor import AbstractLayer
from source.wt import ctc from source.wt import ctc
Patch: any Patch: any

View file

@ -1,9 +1,10 @@
import re import re
from source.mkw.Patch.PatchOperation.Operation.BmgTxtEditor.Layer import * from source.mkw.Patch.PatchOperation.BmgTxtEditor import AbstractLayer
from source.mkw.Track import Track from source.mkw.Track import Track
from source.wt import bmg from source.wt import bmg
Patch: any Patch: any

View file

@ -1,4 +1,4 @@
from source.mkw.Patch.PatchOperation.Operation.BmgTxtEditor.Layer import * from source.mkw.Patch.PatchOperation.BmgTxtEditor import AbstractLayer
Patch: any Patch: any

View file

@ -1,6 +1,7 @@
from source.mkw.Patch.PatchOperation.Operation.BmgTxtEditor.Layer import * from source.mkw.Patch.PatchOperation.BmgTxtEditor import AbstractLayer
from source.wt import bmg from source.wt import bmg
Patch: any Patch: any

View file

@ -1,6 +1,6 @@
import re import re
from source.mkw.Patch.PatchOperation.Operation.BmgTxtEditor.Layer import * from source.mkw.Patch.PatchOperation.BmgTxtEditor import AbstractLayer
Patch: any Patch: any

View file

@ -0,0 +1,62 @@
from io import BytesIO
from typing import IO
from abc import ABC, abstractmethod
from source.mkw.Patch import *
from source.mkw.Patch.PatchOperation import AbstractPatchOperation
Layer: any
Patch: any
class AbstractLayer(ABC):
"""
Represent a Layer for a bmgtxt patch
"""
@abstractmethod
def patch_bmg(self, patch: "Patch", decoded_content: str) -> str:
"""
Patch a bmg with the actual layer. Return the new bmg content.
"""
@classmethod
def get(cls, layer: dict) -> "Layer":
"""
return the correct type of layer corresponding to the layer mode
:param layer: the layer to load
"""
for subclass in filter(lambda subclass: subclass.mode == layer["mode"], cls.__subclasses__()):
layer.pop("mode")
return subclass(**layer)
raise InvalidBmgLayerMode(layer["mode"])
class BmgTxtEditor(AbstractPatchOperation):
"""
edit a decoded bmg
"""
type = "bmgtxt-edit"
def __init__(self, layers: list[dict]):
"""
:param layers: layers
"""
self.layers = layers
def patch(self, patch: "Patch", file_name: str, file_content: IO) -> (str, IO):
decoded_content: str = file_content.read().decode("utf-8")
for layer in self.layers:
decoded_content = AbstractLayer.get(layer).patch_bmg(patch, decoded_content)
patch_content: IO = BytesIO(decoded_content.encode("utf-8"))
return file_name, patch_content
# Load the subclasses so that get_layer can filter them.
from source.mkw.Patch.PatchOperation.BmgTxtEditor import (
CTFileLayer, FormatOriginalTrackLayer, IDLayer, PatchLayer, RegexLayer
)

View file

@ -1,14 +1,14 @@
from io import BytesIO from io import BytesIO
from typing import IO from typing import IO
from source.mkw.Patch.PatchOperation.Operation import * from source.mkw.Patch.PatchOperation import AbstractPatchOperation
from source.wt import img from source.wt import img
Patch: any Patch: any
class ImageDecoder(AbstractOperation): class ImageDecoder(AbstractPatchOperation):
""" """
decode a game image to a image file decode a game image to a image file
""" """

View file

@ -1,6 +1,6 @@
from PIL import ImageDraw, Image from PIL import ImageDraw, Image
from source.mkw.Patch.PatchOperation.Operation.ImageEditor.Layer import * from source.mkw.Patch.PatchOperation.ImageEditor import AbstractLayer
Patch: any Patch: any
@ -24,4 +24,4 @@ class ColorLayer(AbstractLayer):
draw = ImageDraw.Draw(image) draw = ImageDraw.Draw(image)
draw.rectangle(self.get_bbox(image), fill=self.color) draw.rectangle(self.get_bbox(image), fill=self.color)
return image return image

View file

@ -1,7 +1,7 @@
from PIL import Image from PIL import Image
from source.mkw.Patch import * from source.mkw.Patch import *
from source.mkw.Patch.PatchOperation.Operation.ImageEditor.Layer import * from source.mkw.Patch.PatchOperation.ImageEditor import AbstractLayer
class ImageLayer(AbstractLayer): class ImageLayer(AbstractLayer):
@ -36,3 +36,4 @@ class ImageLayer(AbstractLayer):
) )
return image return image

View file

@ -1,7 +1,7 @@
from PIL import ImageFont, ImageDraw, Image from PIL import ImageFont, ImageDraw, Image
from source.mkw.Patch import * from source.mkw.Patch import *
from source.mkw.Patch.PatchOperation.Operation.ImageEditor.Layer import * from source.mkw.Patch.PatchOperation.ImageEditor import AbstractLayer
class TextLayer(AbstractLayer): class TextLayer(AbstractLayer):

View file

@ -1,8 +1,13 @@
from io import BytesIO
from typing import IO
from abc import abstractmethod, ABC from abc import abstractmethod, ABC
from PIL import Image from PIL import Image
from source.mkw.Patch import *
from source.mkw.Patch.PatchOperation import AbstractPatchOperation
Layer: any
Patch: any Patch: any
@ -58,6 +63,42 @@ class AbstractLayer(ABC):
Patch an image with the actual layer. Return the new image. Patch an image with the actual layer. Return the new image.
""" """
@classmethod
def get(cls, layer: dict) -> "Layer":
"""
return the correct type of layer corresponding to the layer mode
:param layer: the layer to load
"""
for subclass in filter(lambda subclass: subclass.type == layer["type"], cls.__subclasses__()):
layer.pop("type")
return subclass(**layer)
raise InvalidImageLayerType(layer["type"])
from source.mkw.Patch.PatchOperation.Operation.ImageEditor.Layer import ColorLayer, ImageLayer, TextLayer
__all__ = ["AbstractLayer", "ColorLayer", "ImageLayer", "TextLayer"] class ImageGenerator(AbstractPatchOperation):
"""
generate a new image based on a file and apply a generator on it
"""
type = "img-edit"
def __init__(self, layers: list[dict]):
self.layers: list["Layer"] = [AbstractLayer.get(layer) for layer in layers]
def patch(self, patch: "Patch", file_name: str, file_content: IO) -> (str, IO):
image = Image.open(file_content).convert("RGBA")
for layer in self.layers:
image = layer.patch_image(patch, image)
patch_content = BytesIO()
image.save(patch_content, format="PNG")
patch_content.seek(0)
return file_name, patch_content
# Load the class so that __subclasses__ can find them
from source.mkw.Patch.PatchOperation.ImageEditor import (
ColorLayer, ImageLayer, TextLayer
)

View file

@ -1,14 +1,14 @@
from io import BytesIO from io import BytesIO
from typing import IO from typing import IO
from source.mkw.Patch.PatchOperation.Operation import * from source.mkw.Patch.PatchOperation import AbstractPatchOperation
from source.wt import img from source.wt import img
Patch: any Patch: any
class ImageEncoder(AbstractOperation): class ImageEncoder(AbstractPatchOperation):
""" """
encode an image to a game image file encode an image to a game image file
""" """

View file

@ -1,17 +0,0 @@
from abc import ABC, abstractmethod
Patch: any
class AbstractLayer(ABC):
@abstractmethod
def patch_bmg(self, patch: "Patch", decoded_content: str) -> str:
"""
Patch a bmg with the actual layer. Return the new bmg content.
"""
from source.mkw.Patch.PatchOperation.Operation.BmgTxtEditor.Layer import IDLayer, RegexLayer, CTFileLayer, PatchLayer, \
FormatOriginalTrackLayer
__all__ = ["AbstractLayer", "IDLayer", "RegexLayer", "CTFileLayer", "PatchLayer", "FormatOriginalTrackLayer"]

View file

@ -1,45 +0,0 @@
from io import BytesIO
from typing import IO
from source.mkw.Patch import *
from source.mkw.Patch.PatchOperation.Operation import *
from source.mkw.Patch.PatchOperation.Operation.BmgTxtEditor.Layer import *
class BmgTxtEditor(AbstractOperation):
"""
edit a decoded bmg
"""
type = "bmgtxt-edit"
def __init__(self, layers: list[dict]):
"""
:param layers: layers
"""
self.layers = layers
def patch(self, patch: "Patch", file_name: str, file_content: IO) -> (str, IO):
decoded_content: str = file_content.read().decode("utf-8")
for layer in self.layers:
decoded_content = self.Layer(layer).patch_bmg(patch, decoded_content)
patch_content: IO = BytesIO(decoded_content.encode("utf-8"))
return file_name, patch_content
class Layer:
"""
represent a layer for a bmg-edit
"""
def __new__(cls, layer: dict) -> "Layer":
"""
return the correct type of layer corresponding to the layer mode
:param layer: the layer to load
"""
for subclass in filter(lambda subclass: subclass.mode == layer["mode"],
AbstractLayer.__subclasses__()):
layer.pop("mode")
return subclass(**layer)
raise InvalidBmgLayerMode(layer["mode"])

View file

@ -1,47 +0,0 @@
from io import BytesIO
from typing import IO
from PIL import Image
from source.mkw.Patch import *
from source.mkw.Patch.PatchOperation.Operation import *
from source.mkw.Patch.PatchOperation.Operation.ImageEditor.Layer import *
class ImageGenerator(AbstractOperation):
"""
generate a new image based on a file and apply a generator on it
"""
type = "img-edit"
def __init__(self, layers: list[dict]):
self.layers: list["Layer"] = [self.Layer(layer) for layer in layers]
def patch(self, patch: "Patch", file_name: str, file_content: IO) -> (str, IO):
image = Image.open(file_content).convert("RGBA")
for layer in self.layers:
image = layer.patch_image(patch, image)
patch_content = BytesIO()
image.save(patch_content, format="PNG")
patch_content.seek(0)
return file_name, patch_content
class Layer:
"""
represent a layer for an image generator
"""
def __new__(cls, layer: dict) -> "Layer":
"""
return the correct type of layer corresponding to the layer mode
:param layer: the layer to load
"""
for subclass in filter(lambda subclass: subclass.type == layer["type"],
AbstractLayer.__subclasses__()):
layer.pop("type")
return subclass(**layer)
raise InvalidImageLayerType(layer["type"])

View file

@ -1,22 +0,0 @@
from abc import ABC, abstractmethod
from typing import IO
Patch: any
Layer: any
class AbstractOperation(ABC):
mode: str # name of the operation
@abstractmethod
def patch(self, patch: "Patch", file_name: str, file_content: IO) -> (str, IO):
"""
patch a file and return the new file_path (if changed) and the new content of the file
"""
from source.mkw.Patch.PatchOperation.Operation import ImageDecoder, ImageEncoder, Rename, Special, StrEditor, \
BmgTxtEditor, ImageEditor, BmgEncoder, BmgDecoder
__all__ = ["AbstractOperation", "ImageDecoder", "ImageEncoder", "Rename",
"Special", "StrEditor", "BmgTxtEditor", "ImageEditor", "BmgEncoder", "BmgDecoder"]

View file

@ -1,12 +1,12 @@
from typing import IO from typing import IO
from source.mkw.Patch.PatchOperation.Operation import * from source.mkw.Patch.PatchOperation import AbstractPatchOperation
Patch: any Patch: any
class Rename(AbstractOperation): class Rename(AbstractPatchOperation):
""" """
Rename the output file Rename the output file
""" """

View file

@ -1,12 +1,12 @@
from typing import IO from typing import IO
from source.mkw.Patch.PatchOperation.Operation import * from source.mkw.Patch.PatchOperation import AbstractPatchOperation
Patch: any Patch: any
class Special(AbstractOperation): class Special(AbstractPatchOperation):
""" """
use a file defined as special in the patch to replace the current file content use a file defined as special in the patch to replace the current file content
""" """

View file

@ -1,12 +1,12 @@
from io import BytesIO from io import BytesIO
from typing import IO from typing import IO
from source.mkw.Patch.PatchOperation.Operation import * from source.mkw.Patch.PatchOperation import AbstractPatchOperation
from source.mkw.Patch import * from source.mkw.Patch import *
from source.wt import wstrt from source.wt import wstrt
class StrEditor(AbstractOperation): class StrEditor(AbstractPatchOperation):
""" """
patch the main.dol file patch the main.dol file
""" """

View file

@ -1,18 +1,39 @@
from source.mkw.Patch import * from source.mkw.Patch import *
from source.mkw.Patch.PatchOperation.Operation import * from abc import ABC, abstractmethod
from typing import IO, Type
Patch: any
Layer: any
class PatchOperation: class AbstractPatchOperation(ABC):
""" """
Represent an operation that can be applied onto a patch to modify it before installing An AbstractPatchOperation representing any PatchOperation that can be used on one of the game files
""" """
def __new__(cls, name) -> "Operation": mode: str # name of the operation
@abstractmethod
def patch(self, patch: "Patch", file_name: str, file_content: IO) -> (str, IO):
"""
patch a file and return the new file_path (if changed) and the new content of the file
"""
@classmethod
def get(cls, name: str) -> Type["AbstractPatchOperation"]:
""" """
Return an operation from its name Return an operation from its name
:name: name of the operation
:return: an Operation from its name :return: an Operation from its name
""" """
for subclass in filter(lambda subclass: subclass.type == name, AbstractOperation.__subclasses__()): for subclass in filter(lambda subclass: subclass.type == name, cls.__subclasses__()):
return subclass return subclass
raise InvalidPatchOperation(name) raise InvalidPatchOperation(name)
# load all the subclass of AbstractPatchOperation to that __subclasses__ can filter them
from source.mkw.Patch.PatchOperation import (
ImageDecoder, ImageEncoder, Rename, Special, StrEditor,
BmgTxtEditor, ImageEditor, BmgEncoder, BmgDecoder
)