gui.py: one button per direction, "SAB file to SAX file" and "SAX file to SAB file". The button decides which extension the chooser offers and which way the conversion goes. The log names the chosen file and its folder before the work starts, then the output file and folder when it finishes, each value on its own line. Output lands next to the input under the timestamped name, so there is no save dialog and nothing is overwritten. Sedona home sits at the top with a Change... button that validates manifests/ and rewrites system.properties. run.py: no arguments opens the window, any argument goes to the command line converter. pythonw run.py for a shortcut with no console. config: NAME/VERSION are now "SAB - SAX Sedona Files Converter 0.0.0.002", carried by the window title, every dialog title and the CLI banner. Adds choose_sab_input() - the existing choose_sab_file() is the save dialog. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
167 lines
6.3 KiB
Python
167 lines
6.3 KiB
Python
"""A window with two buttons, one per direction.
|
|
|
|
python gui.py
|
|
|
|
Pick a direction, pick a file; the log shows what was chosen before the
|
|
conversion runs and where the result landed afterwards. Output goes next to the
|
|
input under a timestamped name, so nothing is ever overwritten.
|
|
"""
|
|
import os, sys, traceback
|
|
import tkinter as tk
|
|
from tkinter import ttk, messagebox
|
|
|
|
import config, sab, sax
|
|
|
|
|
|
class App:
|
|
def __init__(self, root):
|
|
self.root = root
|
|
root.title("%s %s" % (config.NAME, config.VERSION))
|
|
root.minsize(620, 340)
|
|
|
|
frame = ttk.Frame(root, padding=12)
|
|
frame.pack(fill="both", expand=True)
|
|
frame.columnconfigure(0, weight=1)
|
|
frame.rowconfigure(3, weight=1)
|
|
|
|
# sedona home
|
|
home_box = ttk.LabelFrame(frame, text="Sedona home", padding=(8, 4))
|
|
home_box.grid(row=0, column=0, sticky="ew")
|
|
home_box.columnconfigure(0, weight=1)
|
|
self.home_var = tk.StringVar(value="(not set)")
|
|
ttk.Label(home_box, textvariable=self.home_var).grid(row=0, column=0, sticky="w")
|
|
ttk.Button(home_box, text="Change...", command=self.change_home).grid(row=0, column=1, padx=(8, 0))
|
|
|
|
# the two directions
|
|
buttons = ttk.Frame(frame, padding=(0, 12))
|
|
buttons.grid(row=1, column=0, sticky="ew")
|
|
buttons.columnconfigure(0, weight=1)
|
|
buttons.columnconfigure(1, weight=1)
|
|
self.b_sab2sax = ttk.Button(buttons, text="SAB file to SAX file", padding=10,
|
|
command=lambda: self.run("sab"))
|
|
self.b_sab2sax.grid(row=0, column=0, sticky="ew", padx=(0, 6))
|
|
self.b_sax2sab = ttk.Button(buttons, text="SAX file to SAB file", padding=10,
|
|
command=lambda: self.run("sax"))
|
|
self.b_sax2sab.grid(row=0, column=1, sticky="ew", padx=(6, 0))
|
|
|
|
self.status = tk.StringVar(value="Choose a direction.")
|
|
ttk.Label(frame, textvariable=self.status).grid(row=2, column=0, sticky="w")
|
|
|
|
# log
|
|
log_box = ttk.Frame(frame)
|
|
log_box.grid(row=3, column=0, sticky="nsew", pady=(8, 0))
|
|
log_box.columnconfigure(0, weight=1)
|
|
log_box.rowconfigure(0, weight=1)
|
|
self.log_text = tk.Text(log_box, height=10, wrap="word", state="disabled")
|
|
self.log_text.grid(row=0, column=0, sticky="nsew")
|
|
bar = ttk.Scrollbar(log_box, orient="vertical", command=self.log_text.yview)
|
|
bar.grid(row=0, column=1, sticky="ns")
|
|
self.log_text.configure(yscrollcommand=bar.set)
|
|
|
|
self.home = None
|
|
root.after(50, self.first_home)
|
|
|
|
# ---------- helpers ----------
|
|
def log(self, line=""):
|
|
self.log_text.configure(state="normal")
|
|
self.log_text.insert("end", line + "\n")
|
|
self.log_text.see("end")
|
|
self.log_text.configure(state="disabled")
|
|
self.root.update_idletasks() # so it is on screen before the work starts
|
|
|
|
def set_home(self, path):
|
|
self.home = path
|
|
self.home_var.set(path or "(not set)")
|
|
|
|
def first_home(self):
|
|
"""Resolve sedona home at startup; the folder chooser opens if it isn't set."""
|
|
try:
|
|
self.set_home(config.get_sedona_home())
|
|
self.log("sedona home: %s" % self.home)
|
|
except SystemExit as e:
|
|
self.log("no sedona home: %s" % e)
|
|
messagebox.showwarning(config.title("Sedona home"),
|
|
"No sedona home selected.\n\n"
|
|
"Set it with Change... before converting.")
|
|
|
|
def change_home(self):
|
|
picked = config.ask_directory("Select Sedona home (the folder containing manifests/)",
|
|
self.home)
|
|
if not picked:
|
|
return
|
|
picked = os.path.normpath(picked)
|
|
if not config.is_sedona_home(picked):
|
|
messagebox.showerror(config.title("Sedona home"),
|
|
"%s has no manifests/ subfolder." % picked)
|
|
return
|
|
config.write_prop(config.KEY, picked)
|
|
self.set_home(picked)
|
|
self.log("sedona home: %s (saved to %s)" % (picked, config.PROPS))
|
|
|
|
# ---------- conversion ----------
|
|
def run(self, direction):
|
|
"""direction: 'sab' means sab -> sax, 'sax' means sax -> sab."""
|
|
if not config.is_sedona_home(self.home):
|
|
messagebox.showerror(config.title("Sedona home"),
|
|
"Set a valid sedona home first (Change...).")
|
|
return
|
|
|
|
src = config.choose_sab_input() if direction == "sab" else config.choose_sax_file()
|
|
if not src:
|
|
self.status.set("Cancelled.")
|
|
return
|
|
src = os.path.abspath(src)
|
|
|
|
# show what was chosen, before doing anything with it
|
|
self.log()
|
|
self.log("%s -> %s" % (direction, "sax" if direction == "sab" else "sab"))
|
|
self.log(" INPUT FILE:")
|
|
self.log(" %s" % os.path.basename(src))
|
|
self.log(" INPUT PATH:")
|
|
self.log(" %s" % os.path.dirname(src))
|
|
self.status.set("Converting %s ..." % os.path.basename(src))
|
|
|
|
if direction == "sab":
|
|
out = os.path.join(os.path.dirname(src), sax.default_sax_name(src))
|
|
work = lambda: sax.decode(src, self.home, out)
|
|
else:
|
|
out = os.path.join(os.path.dirname(src), sab.default_sab_name(src))
|
|
work = lambda: sab.encode(src, self.home, out)
|
|
|
|
self.busy(True)
|
|
try:
|
|
n = work()
|
|
except Exception as e:
|
|
self.busy(False)
|
|
self.log(" FAILED: %s" % e)
|
|
self.status.set("Failed.")
|
|
traceback.print_exc()
|
|
messagebox.showerror(config.title("Conversion failed"), str(e))
|
|
return
|
|
self.busy(False)
|
|
|
|
self.log(" OUTPUT FILE:")
|
|
self.log(" %s" % os.path.basename(out))
|
|
self.log(" OUTPUT PATH:")
|
|
self.log(" %s" % os.path.dirname(out))
|
|
self.log(" wrote %d bytes" % n)
|
|
self.status.set("Done: %s" % os.path.basename(out))
|
|
|
|
def busy(self, on):
|
|
state = "disabled" if on else "normal"
|
|
self.b_sab2sax.configure(state=state)
|
|
self.b_sax2sab.configure(state=state)
|
|
self.root.configure(cursor="watch" if on else "")
|
|
self.root.update_idletasks()
|
|
|
|
|
|
def main():
|
|
root = tk.Tk()
|
|
App(root)
|
|
root.mainloop()
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|