seven-mods/seven_mods_gui.py

289 lines
13 KiB
Python
Raw Permalink Normal View History

2023-01-22 18:46:23 -05:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# seven_mods_gui.py
#
# Copyright 2023 Patrick Marsee <patrickm@patrick-b550mc>
#
# 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)
2023-01-22 18:46:23 -05:00
# 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,
2023-01-22 18:46:23 -05:00
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,
2023-01-22 18:46:23 -05:00
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,
2023-01-22 18:46:23 -05:00
text = "Load",
command = self.command_load_button)
self.profile_load_btn.grid(column = 2, row = 0)
self.profile_save_btn = ttk.Button(profile_frame,
2023-01-22 18:46:23 -05:00
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
2023-01-22 18:46:23 -05:00
self.mod_list = ttk.Treeview(self,
columns = ("name", "version", "author", "serverside", "enabled"),
2023-01-22 18:46:23 -05:00
height = 20,
selectmode = "browse")
self.mod_list.heading("name", text = "Name")
self.mod_list.heading("version", text = "Version")
self.mod_list.column("version", width = 100)
2023-01-22 18:46:23 -05:00
self.mod_list.heading("author", text = "Author")
self.mod_list.heading("serverside", text = "Serverside")
self.mod_list.column("serverside", width = 100)
2023-01-22 18:46:23 -05:00
self.mod_list.heading("enabled", text = "Enabled")
self.mod_list.column("enabled", width = 100)
2023-01-22 18:46:23 -05:00
self.mod_list.tag_configure("DISABLED", foreground = "red")
self.mod_list.tag_configure("ENABLED", foreground = "green")
self.mod_list.tag_bind("DISABLED", '<Double-1>', lambda e: self.command_enable(self.mod_list.selection()[0]))
self.mod_list.tag_bind("ENABLED", '<Double-1>', 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)
2023-01-22 18:46:23 -05:00
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)
2023-01-22 18:46:23 -05:00
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)
2023-01-22 18:46:23 -05:00
t = Toplevel(self)
t.attributes("-topmost", 1)
2023-01-22 18:46:23 -05:00
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)
2023-01-22 18:46:23 -05:00
ttk.Label(paths_frame, text = "Mod folder").grid(column = 0, row = 0)
2023-01-22 18:46:23 -05:00
mod_path_var = StringVar()
mod_path_var.set(MODS_DIR)
mod_path_entry = ttk.Entry(paths_frame, textvariable = mod_path_var)
2023-01-22 18:46:23 -05:00
mod_path_entry.grid(column = 1, row = 0, sticky = (E, W))
mod_path_button = ttk.Button(paths_frame,
2023-01-22 18:46:23 -05:00
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)
2023-01-22 18:46:23 -05:00
seven_path_var = StringVar()
seven_path_var.set(SEVEN_DIR)
seven_path_entry = ttk.Entry(paths_frame, textvariable = seven_path_var)
2023-01-22 18:46:23 -05:00
seven_path_entry.grid(column = 1, row = 1, sticky = (E, W))
seven_path_button = ttk.Button(paths_frame,
2023-01-22 18:46:23 -05:00
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)
2023-01-22 18:46:23 -05:00
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)
2023-01-22 18:46:23 -05:00
seven_mods.save_config(self.cfg)
self.refresh_mod_list()
def get_mod_data(self, mod: str) -> tuple:
mod_path = os.path.join(self.cfg.mods_dir, mod)
mod_info_path = os.path.join(mod_path, "ModInfo.xml")
2023-01-22 18:46:23 -05:00
try:
tree = et.parse(mod_info_path)
except:
return "", "", ""
root = tree.getroot()
name = ""
author = ""
version = ""
serverside = str(seven_mods.mod_is_server_only(mod_path))
2023-01-22 18:46:23 -05:00
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, serverside
2023-01-22 18:46:23 -05:00
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
2023-01-22 18:46:23 -05:00
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()
2023-01-22 18:46:23 -05:00
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}")
2023-01-22 18:46:23 -05:00
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))