SXXXXXXX_ProjectInitializer/projectinitializer/gui/app_window.py
2025-05-08 10:47:04 +02:00

148 lines
6.5 KiB
Python

# ProjectInitializerTool/project_initializer/gui/app_window.py
import tkinter as tk
from tkinter import filedialog, messagebox
from pathlib import Path
from projectinitializer.config import settings # type: ignore
from projectinitializer.core import project_creator # type: ignore
# --- Import Version Info FOR THE WRAPPER ITSELF ---
try:
# Use absolute import based on package name
from projectinitializer import _version as wrapper_version
WRAPPER_APP_VERSION_STRING = f"{wrapper_version.__version__} ({wrapper_version.GIT_BRANCH}/{wrapper_version.GIT_COMMIT_HASH[:7]})"
WRAPPER_BUILD_INFO = f"Wrapper Built: {wrapper_version.BUILD_TIMESTAMP}"
except ImportError:
# This might happen if you run the wrapper directly from source
# without generating its _version.py first (if you use that approach for the wrapper itself)
WRAPPER_APP_VERSION_STRING = "(Dev Wrapper)"
WRAPPER_BUILD_INFO = "Wrapper build time unknown"
# --- End Import Version Info ---
# --- Constants for Version Generation ---
DEFAULT_VERSION = "0.0.0+unknown"
DEFAULT_COMMIT = "Unknown"
DEFAULT_BRANCH = "Unknown"
# --- End Constants ---
class AppWindow:
def __init__(self, master: tk.Tk):
self.master = master
master.title(f"Project Initializer Tool - {WRAPPER_APP_VERSION_STRING}")
# master.geometry("500x200") # Optional: set a default size
self.app_config = settings.load_app_configuration()
self.last_root_dir = self.app_config.get("last_root_directory", str(Path.home()))
# --- Widgets ---
# Root Directory Frame
root_dir_frame = tk.Frame(master, padx=5, pady=5)
root_dir_frame.pack(fill=tk.X, expand=False)
tk.Label(root_dir_frame, text="Root Directory:").pack(side=tk.LEFT, padx=(0, 5))
self.root_dir_entry = tk.Entry(root_dir_frame)
self.root_dir_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
self.root_dir_entry.insert(0, self.last_root_dir)
self.browse_button = tk.Button(root_dir_frame, text="Browse...", command=self._browse_root_directory)
self.browse_button.pack(side=tk.LEFT, padx=(5, 0))
# Project Name Frame
project_name_frame = tk.Frame(master, padx=5, pady=5)
project_name_frame.pack(fill=tk.X, expand=False)
tk.Label(project_name_frame, text="Project Name:").pack(side=tk.LEFT, padx=(0, 5))
self.project_name_entry = tk.Entry(project_name_frame)
self.project_name_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
# Ensure project name entry also expands if window is resized
project_name_frame.grid_columnconfigure(1, weight=1)
# Proceed Button Frame (for centering or specific layout)
button_frame = tk.Frame(master, pady=10)
button_frame.pack()
self.proceed_button = tk.Button(button_frame, text="Create Project", command=self._proceed_action, width=20)
self.proceed_button.pack()
# Set focus to project name entry initially
self.project_name_entry.focus_set()
# Configure resizing behavior for the main window content
# Let the entry fields expand with the window width
# Not strictly necessary with pack, but good practice for grid or more complex layouts
# master.grid_columnconfigure(0, weight=1) # if using grid for frames
def _browse_root_directory(self) -> None:
"""Opens a dialog to select the root directory."""
initial_dir = self.root_dir_entry.get() or str(Path.home())
if not Path(initial_dir).is_dir(): # check if path is valid, otherwise default to home
initial_dir = str(Path.home())
directory = filedialog.askdirectory(
initialdir=initial_dir,
title="Select Root Directory"
)
if directory: # If a directory is selected (not cancelled)
self.root_dir_entry.delete(0, tk.END)
self.root_dir_entry.insert(0, directory)
def _validate_inputs(self) -> bool:
"""Validates user inputs from the GUI."""
self.root_dir = self.root_dir_entry.get().strip()
self.project_name = self.project_name_entry.get().strip()
if not self.root_dir:
messagebox.showerror("Input Error", "Root directory cannot be empty.")
self.root_dir_entry.focus_set()
return False
if not Path(self.root_dir).is_dir():
messagebox.showerror("Input Error", "The selected root directory is not a valid directory.")
self.root_dir_entry.focus_set()
return False
if not self.project_name:
messagebox.showerror("Input Error", "Project name cannot be empty.")
self.project_name_entry.focus_set()
return False
# Basic check for project name characters.
# More complex validation (like isidentifier for the sanitized version)
# is handled in the core, but a simple GUI check can be useful.
# For now, we rely on the core's validation.
return True
def _proceed_action(self) -> None:
"""Handles the 'Create Project' button click."""
if not self._validate_inputs():
return
# Save current root_dir for next time if it's valid and different
if Path(self.root_dir).is_dir() and self.root_dir != self.last_root_dir:
self.app_config["last_root_directory"] = self.root_dir
settings.save_app_configuration(self.app_config)
self.last_root_dir = self.root_dir # Update internal state
self.proceed_button.config(state=tk.DISABLED)
self.master.update_idletasks() # Ensure button state updates immediately
try:
# Call the core logic
success_message = project_creator.create_project(self.root_dir, self.project_name)
messagebox.showinfo("Success", success_message)
# Optionally clear fields or perform other actions on success
self.project_name_entry.delete(0, tk.END) # Clear project name for the next one
self.project_name_entry.focus_set()
except project_creator.ProjectCreationError as e:
messagebox.showerror("Project Creation Failed", str(e))
except Exception as e: # Catch any other unexpected errors
messagebox.showerror("Unexpected Error", f"An unexpected error occurred: {str(e)}")
finally:
# Re-enable button
self.proceed_button.config(state=tk.NORMAL)
def launch_gui_application() -> None:
"""Initializes and runs the Tkinter GUI application."""
root = tk.Tk()
app = AppWindow(root)
root.mainloop()