#!/usr/bin/env python # -*- coding: utf-8 -*- # # seven_mods_gui.py # # Copyright 2023 Patrick Marsee # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # # from tkinter import * from tkinter import ttk from tkinter import messagebox from tkinter import filedialog import seven_mods import os.path import xml.etree.ElementTree as et # I don't need anything powerful for this class AppFrame(ttk.Frame): def __init__(self, root): super(AppFrame, self).__init__(root) self.grid(column = 0, row = 0) self.cfg = None try: self.cfg = seven_mods.load_config() except: # no config or invalid config print("Should be making a new window") self.prompt_configuration_window() self.profiles = seven_mods.ModProfiles() self.profiles.load_mod_profiles() self.initialize_widgets() self.refresh_mod_list() def initialize_widgets(self): self.columnconfigure(0, weight = 1) self.rowconfigure(1, weight = 1) # Profile management profile_frame = ttk.Frame(self) profile_frame.grid(column = 0, row = 0, columnspan = 2, sticky = (N, S, E, W)) profile_frame.columnconfigure(1, weight = 1) self.configure_button = ttk.Button(profile_frame, text = "Configure", command = self.prompt_configuration_window) self.configure_button.grid(column = 0, row = 0) self.profile_var = StringVar() self.profile_box = ttk.Combobox(profile_frame, textvariable = self.profile_var) self.refresh_profile_list() self.profile_box.grid(column = 1, row = 0, sticky = (E, W), padx = 5) self.profile_load_btn = ttk.Button(profile_frame, text = "Load", command = self.command_load_button) self.profile_load_btn.grid(column = 2, row = 0) self.profile_save_btn = ttk.Button(profile_frame, text = "Save", command = self.command_save_button) self.profile_save_btn.grid(column = 3, row = 0) self.profile_delete_btn = ttk.Button(profile_frame, text = "Delete", command = self.command_delete_button) self.profile_delete_btn.grid(column = 4, row = 0) # Mod list self.mod_list = ttk.Treeview(self, columns = ("name", "version", "author", "enabled"), height = 20, selectmode = "browse") self.mod_list.heading("name", text = "Name") self.mod_list.heading("version", text = "Version") self.mod_list.heading("author", text = "Author") self.mod_list.heading("enabled", text = "Enabled") self.mod_list.tag_configure("DISABLED", foreground = "red") self.mod_list.tag_configure("ENABLED", foreground = "green") self.mod_list.tag_bind("DISABLED", '', lambda e: self.command_enable(self.mod_list.selection()[0])) self.mod_list.tag_bind("ENABLED", '', lambda e: self.command_disable(self.mod_list.selection()[0])) self.mod_list.grid(column = 0, row = 1, sticky = (N, E, S, W)) scroll = ttk.Scrollbar(self, orient = VERTICAL, command = self.mod_list.yview) scroll.grid(column = 1, row = 1, sticky = (N, E, S, W)) self.mod_list.configure(yscrollcommand=scroll.set) def prompt_configuration_window(self): SEVEN_DIR = "" MODS_DIR = "" warn_load_var = BooleanVar(value = True) warn_save_var = BooleanVar(value = True) warn_delete_var = BooleanVar(value = True) if self.cfg == None: SEVEN_DIR = seven_mods.get_seven_days_install_path() MODS_DIR = seven_mods.MODS_DIR else: SEVEN_DIR = self.cfg.seven_dir MODS_DIR = self.cfg.mods_dir warn_load_var.set(self.cfg.warn_load) warn_save_var.set(self.cfg.warn_save) warn_delete_var.set(self.cfg.warn_delete) t = Toplevel(self) t.attributes("-topmost", 1) t.title("Configuration") t.columnconfigure(0, weight = 1) t.rowconfigure(0, weight = 1) t.rowconfigure(1, weight = 1) paths_frame = ttk.Labelframe(t, text = "Paths") paths_frame.grid(column = 0, row = 0, sticky = (N, S, E, W)) paths_frame.columnconfigure(1, weight = 1) ttk.Label(paths_frame, text = "Mod folder").grid(column = 0, row = 0) mod_path_var = StringVar() mod_path_var.set(MODS_DIR) mod_path_entry = ttk.Entry(paths_frame, textvariable = mod_path_var) mod_path_entry.grid(column = 1, row = 0, sticky = (E, W)) mod_path_button = ttk.Button(paths_frame, text = "Browse...", command = lambda: mod_path_var.set(filedialog.askdirectory(initialdir = MODS_DIR))) mod_path_button.grid(column = 2, row = 0, sticky = (E, W)) ttk.Label(paths_frame, text = "7D2D installation folder").grid(column = 0, row = 1) seven_path_var = StringVar() seven_path_var.set(SEVEN_DIR) seven_path_entry = ttk.Entry(paths_frame, textvariable = seven_path_var) seven_path_entry.grid(column = 1, row = 1, sticky = (E, W)) seven_path_button = ttk.Button(paths_frame, text = "Browse...", command = lambda: seven_path_var.set(filedialog.askdirectory(initialdir = SEVEN_DIR))) seven_path_button.grid(column = 2, row = 1, sticky = (E, W)) warnings_frame = ttk.Labelframe(t, text = "Warnings") warnings_frame.grid(column = 0, row = 1, sticky = (N, S, E, W)) warnings_frame.columnconfigure(1, weight = 1) ttk.Checkbutton(warnings_frame, text = "Warn on loading profile", variable = warn_load_var).grid(column = 0, row = 0, sticky = (E, W)) ttk.Checkbutton(warnings_frame, text = "Warn on saving profile", variable = warn_save_var).grid(column = 0, row = 1, sticky = (E, W)) ttk.Checkbutton(warnings_frame, text = "Warn on deleting profile", variable = warn_delete_var).grid(column = 0, row = 2, sticky = (E, W)) save_button = ttk.Button(t, text = "Apply", command = lambda: self.save_config(mod_path_var.get(), seven_path_var.get(), warn_load_var.get(), warn_save_var.get(), warn_delete_var.get())) save_button.grid(column = 0, row = 2) def save_config(self, mods_dir: str, seven_dir: str, warn_load: bool, warn_save: bool, warn_delete: bool): self.cfg = seven_mods.Config(mods_dir = mods_dir, seven_dir = seven_dir, warn_load = warn_load, warn_save = warn_save, warn_delete = warn_delete) seven_mods.save_config(self.cfg) self.refresh_mod_list() def get_mod_data(self, mod: str) -> tuple: mod_info_path = os.path.join(self.cfg.mods_dir, mod, "ModInfo.xml") try: tree = et.parse(mod_info_path) except: return "", "", "" root = tree.getroot() name = "" author = "" version = "" for node in root[0]: if node.tag == "Name": name = node.attrib["value"] elif node.tag == "Author": author = node.attrib["value"] elif node.tag == "Version": version = node.attrib["value"] return name, version, author def refresh_mod_list(self): if self.cfg == None: return available_mods = seven_mods.get_available_mods(self.cfg) loaded_mods = seven_mods.get_loaded_mods(self.cfg) # remove the current list items for item in self.mod_list.get_children(""): self.mod_list.delete(item) # add the new ones for mod in available_mods: enabled_text = "DISABLED" if mod in loaded_mods: enabled_text = "ENABLED" values = *seven_mods.get_mod_data(mod, self.cfg), enabled_text self.mod_list.insert("", "end", mod, text = mod, values = values, tags = (enabled_text)) def refresh_profile_list(self): prof_list = [p for p in self.profiles.profiles] self.profile_box["values"] = prof_list def command_enable(self, mod): try: seven_mods.enable_mod(self.cfg, mod) self.mod_list.item(mod, tags = ("ENABLED")) self.mod_list.set(mod, "enabled", "ENABLED") except seven_mods.SevenModsError as e: messagebox.showerror(message = str(e)) def command_disable(self, mod): try: seven_mods.disable_mod(self.cfg, mod) self.mod_list.item(mod, tags = ("DISABLED")) self.mod_list.set(mod, "enabled", "DISABLED") except seven_mods.SevenModsError as e: messagebox.showerror(message = str(e)) def command_load_button(self): prof = self.profile_var.get() if prof in self.profiles.profiles: try: if not self.cfg.warn_load or messagebox.askokcancel(message = f'Are you sure you want to load profile"{prof}"?'): seven_mods.command_disable(["", "", "*"], self.cfg, self.profiles) seven_mods.command_enable(["", ""] + self.profiles.profiles[prof], self.cfg, self.profiles) self.refresh_mod_list() except seven_mods.SevenModsError as e: messagebox.showerror(message = str(e)) else: messagebox.showerror(message = "Must load an existing profile.") def command_save_button(self): prof = self.profile_var.get() try: if len(prof) == 0 or prof.isspace(): raise seven_mods.SevenModsError("Profile must have a name.") if not self.cfg.warn_save or prof not in self.profiles.profiles or messagebox.askokcancel(message = f'Are you sure you want to overwrite profile"{prof}"?'): self.profiles.profiles[prof] = seven_mods.get_loaded_mods(self.cfg) self.profiles.save_mod_profiles() self.refresh_profile_list() except seven_mods.SevenModsError as e: messagebox.showerror(message = str(e)) def command_delete_button(self): prof = self.profile_var.get() try: if prof in self.profiles.profiles: if not self.cfg.warn_delete or messagebox.askokcancel(message = f'Are you sure you want to delete profile"{prof}"?'): del self.profiles.profiles[prof] self.profiles.save_mod_profiles() else: raise seven_mods.SevenModsError(f"Cannot delete non-existant profile: {prof}") except seven_mods.SevenModsError as e: messagebox.showerror(message = str(e)) def main(args): root = Tk() root.title("Seven Mods") root.columnconfigure(0, weight=1) root.rowconfigure(0, weight=1) AppFrame(root) root.mainloop() return 0 if __name__ == '__main__': import sys sys.exit(main(sys.argv))