# 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()