Added GUI version.
This commit is contained in:
parent
61843399b0
commit
a0fb05c319
3 changed files with 255 additions and 23 deletions
30
README.md
30
README.md
|
@ -18,7 +18,8 @@ to enable and disable them, respectively.
|
||||||
|
|
||||||
Upon first use, you will be prompted for the locations of your mods folder and
|
Upon first use, you will be prompted for the locations of your mods folder and
|
||||||
7 Days to Die installation. After configuring these, they are stored in
|
7 Days to Die installation. After configuring these, they are stored in
|
||||||
`seven-mods.cfg`.
|
`seven-mods.cfg`. The default location for the mods folder is the current
|
||||||
|
working directory. The default location for the 7 Days to Die installation directory is autodetected if installed from steam, or blank otherwise.
|
||||||
|
|
||||||
seven-days also allows the user to create separate mod profiles. If, for
|
seven-days also allows the user to create separate mod profiles. If, for
|
||||||
instance, you frequent a multiplayer server with a certain set of mods, but
|
instance, you frequent a multiplayer server with a certain set of mods, but
|
||||||
|
@ -29,7 +30,7 @@ were enabled when the profile was last saved.
|
||||||
|
|
||||||
## CLI Version
|
## CLI Version
|
||||||
|
|
||||||
The main source file for the CLI version is `seven-mods.py`. General usage is:
|
The main source file for the CLI version is `seven_mods.py`. General usage is:
|
||||||
|
|
||||||
`python seven-mods.py <command> <args...>`
|
`python seven-mods.py <command> <args...>`
|
||||||
|
|
||||||
|
@ -64,12 +65,27 @@ come in the future.
|
||||||
|
|
||||||
## GUI Version
|
## GUI Version
|
||||||
|
|
||||||
A GUI version that uses tkinter is planned, but not yet ready. It'll be great
|
The main source file for the GUI version is `seven_mods_gui.py`. It must be in
|
||||||
though!
|
the same directory as `seven_mods.py` in order to work.
|
||||||
|
|
||||||
Trust me.
|
Upon first startup, it will prompt to configure the locations of your mods and
|
||||||
|
installation directories. Once finished, click "save" on the prompt and close
|
||||||
|
the window.
|
||||||
|
|
||||||
I'm a doctor.\*
|
Note: Sometimes the configuration prompt will appear behind the main window, so
|
||||||
|
if you don't see it at first, that's probably where it ended up.
|
||||||
|
|
||||||
\* I'm not an actual doctor.
|
The configuration prompt can be reopened at any time by clicking the "Configure"
|
||||||
|
button in the upper left corner of the main window. The combobox at the top is
|
||||||
|
for profiles. A profile can be loaded by selecting one from the combobox's
|
||||||
|
dropdown menu, then clicking the "Load" button. A profile can be saved by typing
|
||||||
|
its name into the combobox, then clicking "Save". A profile can be overwritten
|
||||||
|
the same way, or by selecting it from the dropdown menu and clicking "Save".
|
||||||
|
|
||||||
|
The main portion of the display is a scrollable listing of all available mods.
|
||||||
|
Similarly to the CLI version's `list` command, enabled mods are shown in green,
|
||||||
|
and disabled mods are shown in red. Clicking on a mod's list entry will toggle
|
||||||
|
it.
|
||||||
|
|
||||||
|
Note: If you click twice too fast, tkinter will think you're double-clicking,
|
||||||
|
and won't change its state twice.
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# seven-mods.py
|
# seven_mods.py
|
||||||
#
|
#
|
||||||
# Copyright 2023 Patrick Marsee <patrickm@patrick-b550mc>
|
# Copyright 2023 Patrick Marsee <patrickm@patrick-b550mc>
|
||||||
#
|
#
|
||||||
|
@ -208,20 +208,16 @@ def save_config(cfg: Config) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def load_config():
|
def load_config():
|
||||||
try:
|
lines = []
|
||||||
lines = []
|
with open(CONFIG_FILE, 'r') as config_file:
|
||||||
with open(CONFIG_FILE, 'r') as config_file:
|
lines = config_file.readlines()
|
||||||
lines = config_file.readlines()
|
cfg_dict = {}
|
||||||
cfg_dict = {}
|
for line in lines:
|
||||||
for line in lines:
|
if not "=" in line:
|
||||||
if not "=" in line:
|
continue
|
||||||
continue
|
var, val = line.split("=")
|
||||||
var, val = line.split("=")
|
cfg_dict[var.strip()] = val.strip()
|
||||||
cfg_dict[var.strip()] = val.strip()
|
return Config(**cfg_dict)
|
||||||
return Config(**cfg_dict)
|
|
||||||
except:
|
|
||||||
# no config or invalid config
|
|
||||||
return prompt_configuration_cli()
|
|
||||||
|
|
||||||
def get_loaded_mods(cfg: Config) -> list:
|
def get_loaded_mods(cfg: Config) -> list:
|
||||||
internal_mods_path = get_internal_mods_path(cfg)
|
internal_mods_path = get_internal_mods_path(cfg)
|
||||||
|
@ -350,7 +346,12 @@ def parse_input(args: list, cfg: Config, profiles: ModProfiles):
|
||||||
print(command)
|
print(command)
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
cfg = load_config()
|
cfg = None
|
||||||
|
try:
|
||||||
|
cfg = load_config()
|
||||||
|
except:
|
||||||
|
# no config or invalid config
|
||||||
|
cfg = prompt_configuration_cli()
|
||||||
profiles = ModProfiles()
|
profiles = ModProfiles()
|
||||||
profiles.load_mod_profiles()
|
profiles.load_mod_profiles()
|
||||||
try:
|
try:
|
215
seven_mods_gui.py
Executable file
215
seven_mods_gui.py
Executable file
|
@ -0,0 +1,215 @@
|
||||||
|
#!/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(1, weight = 1)
|
||||||
|
|
||||||
|
self.configure_button = ttk.Button(self,
|
||||||
|
text = "Configure",
|
||||||
|
command = self.prompt_configuration_window)
|
||||||
|
self.configure_button.grid(column = 0, row = 0)
|
||||||
|
|
||||||
|
self.profile_var = StringVar()
|
||||||
|
self.profile_box = ttk.Combobox(self,
|
||||||
|
textvariable = self.profile_var)
|
||||||
|
self.refresh_profile_list()
|
||||||
|
self.profile_box.grid(column = 1, row = 0, sticky = (E, W))
|
||||||
|
self.profile_load_btn = ttk.Button(self,
|
||||||
|
text = "Load",
|
||||||
|
command = self.command_load_button)
|
||||||
|
self.profile_load_btn.grid(column = 2, row = 0)
|
||||||
|
self.profile_save_btn = ttk.Button(self,
|
||||||
|
text = "Save",
|
||||||
|
command = self.command_save_button)
|
||||||
|
self.profile_save_btn.grid(column = 3, row = 0)
|
||||||
|
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", '<<TreeviewSelect>>', lambda e: self.command_enable(self.mod_list.selection()[0]))
|
||||||
|
self.mod_list.tag_bind("ENABLED", '<<TreeviewSelect>>', lambda e: self.command_disable(self.mod_list.selection()[0]))
|
||||||
|
self.mod_list.grid(column = 0, row = 1, columnspan = 4, sticky = (N, E, S, W))
|
||||||
|
|
||||||
|
def prompt_configuration_window(self):
|
||||||
|
SEVEN_DIR = ""
|
||||||
|
MODS_DIR = ""
|
||||||
|
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
|
||||||
|
t = Toplevel(self)
|
||||||
|
t.title("Configuration")
|
||||||
|
t.columnconfigure(1, weight = 1)
|
||||||
|
t.geometry("800x90")
|
||||||
|
|
||||||
|
ttk.Label(t, text = "Mod folder").grid(column = 0, row = 0)
|
||||||
|
mod_path_var = StringVar()
|
||||||
|
mod_path_var.set(MODS_DIR)
|
||||||
|
mod_path_entry = ttk.Entry(t, textvariable = mod_path_var)
|
||||||
|
mod_path_entry.grid(column = 1, row = 0, sticky = (E, W))
|
||||||
|
mod_path_button = ttk.Button(t,
|
||||||
|
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(t, text = "7D2D installation folder").grid(column = 0, row = 1)
|
||||||
|
seven_path_var = StringVar()
|
||||||
|
seven_path_var.set(SEVEN_DIR)
|
||||||
|
seven_path_entry = ttk.Entry(t, textvariable = seven_path_var)
|
||||||
|
seven_path_entry.grid(column = 1, row = 1, sticky = (E, W))
|
||||||
|
seven_path_button = ttk.Button(t,
|
||||||
|
text = "Browse...",
|
||||||
|
command = lambda: seven_path_var.set(filedialog.askdirectory(initialdir = SEVEN_DIR)))
|
||||||
|
seven_path_button.grid(column = 2, row = 1, sticky = (E, W))
|
||||||
|
|
||||||
|
save_button = ttk.Button(t, text = "Save", command = lambda: self.save_config(mod_path_var.get(), seven_path_var.get()))
|
||||||
|
save_button.grid(column = 2, row = 2)
|
||||||
|
|
||||||
|
def save_config(self, mods_dir: str, seven_dir: str):
|
||||||
|
self.cfg = seven_mods.Config(mods_dir = mods_dir, seven_dir = seven_dir)
|
||||||
|
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 = *self.get_mod_data(mod), 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:
|
||||||
|
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:
|
||||||
|
self.profiles.profiles[prof] = seven_mods.get_loaded_mods(cfg)
|
||||||
|
self.profiles.save_mod_profiles()
|
||||||
|
self.refresh_profile_list()
|
||||||
|
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))
|
Loading…
Add table
Add a link
Reference in a new issue