import tkinter as tk from tkinter import ttk, filedialog, messagebox import os from codebridge.core.comparer import CodeComparer from codebridge.core.sync_manager import SyncManager from codebridge.gui.diff_viewer import DiffViewer from codebridge.gui.commit_dialog import CommitDialog class MainWindow: """ Main GUI class for CodeBridge application. """ def __init__(self, root: tk.Tk): self.root = root self.root.title("CodeBridge - Codebase Synchronizer") self.root.geometry("900x600") self.comparer = None self.sync_manager = SyncManager() self._setup_ui() def _setup_ui(self) -> None: """ Initializes the user interface layout. """ # Path selection frame path_frame = ttk.LabelFrame(self.root, text="Path Selection", padding=10) path_frame.pack(fill="x", padx=10, pady=5) self.src_path_var = tk.StringVar() self.dst_path_var = tk.StringVar() self._create_path_row(path_frame, "Source (New):", self.src_path_var, 0) self._create_path_row(path_frame, "Destination (Old):", self.dst_path_var, 1) # Action buttons btn_frame = ttk.Frame(self.root, padding=5) btn_frame.pack(fill="x", padx=10) compare_btn = ttk.Button(btn_frame, text="Compare Folders", command=self._run_comparison) compare_btn.pack(side="left", padx=5) export_btn = ttk.Button(btn_frame, text="Export Changes (ZIP)", command=self._export_changes) export_btn.pack(side="left", padx=5) import_btn = ttk.Button(btn_frame, text="Import Package (ZIP)", command=self._import_package) import_btn.pack(side="left", padx=5) # Results treeview self._setup_treeview() def _create_path_row(self, parent: ttk.Frame, label: str, var: tk.StringVar, row: int) -> None: """ Creates a row with a label, entry and browse button for paths. """ ttk.Label(parent, text=label).grid(row=row, column=0, sticky="w", padx=5) ttk.Entry(parent, textvariable=var, width=80).grid(row=row, column=1, padx=5, pady=2) ttk.Button(parent, text="Browse", command=lambda: self._browse_folder(var)).grid(row=row, column=2, padx=5) def _browse_folder(self, var: tk.StringVar) -> None: """ Opens a directory selection dialog. """ directory = filedialog.askdirectory() if directory: var.set(directory) def _setup_treeview(self) -> None: """ Configures the treeview to display file differences. """ tree_frame = ttk.Frame(self.root, padding=10) tree_frame.pack(fill="both", expand=True) columns = ("status", "path") self.tree = ttk.Treeview(tree_frame, columns=columns, show="headings") self.tree.heading("status", text="Status") self.tree.heading("path", text="File Path") self.tree.column("status", width=100, stretch=False) self.tree.tag_configure("added", foreground="green") self.tree.tag_configure("modified", foreground="orange") self.tree.tag_configure("deleted", foreground="red") self.tree.bind("", self._on_item_double_click) scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview) self.tree.configure(yscroll=scrollbar.set) self.tree.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") def _run_comparison(self) -> None: """ Executes comparison and populates the treeview. """ src = self.src_path_var.get() dst = self.dst_path_var.get() if not src or not dst: messagebox.showwarning("Warning", "Please select both source and destination folders.") return self.comparer = CodeComparer(src, dst) added, modified, deleted = self.comparer.compare() for item in self.tree.get_children(): self.tree.delete(item) for f in added: self.tree.insert("", "end", values=("Added", f), tags=("added",)) for f in modified: self.tree.insert("", "end", values=("Modified", f), tags=("modified",)) for f in deleted: self.tree.insert("", "end", values=("Deleted", f), tags=("deleted",)) def _on_item_double_click(self, event) -> None: """ Opens the DiffViewer when a file is double-clicked. """ selection = self.tree.selection() if not selection: return item_id = selection[0] item = self.tree.item(item_id) status, relative_path = item["values"] if status == "Deleted": messagebox.showinfo("Info", "File was deleted. Nothing to compare.") return source_full_path = os.path.join(self.src_path_var.get(), relative_path) dest_full_path = os.path.join(self.dst_path_var.get(), relative_path) DiffViewer(self.root, source_full_path, dest_full_path, relative_path) def _export_changes(self) -> None: """ Prompts for a commit message and exports changes to a ZIP. """ if not self.comparer: messagebox.showwarning("Warning", "Please run comparison first.") return dialog = CommitDialog(self.root) commit_msg = dialog.get_message() if not commit_msg: messagebox.showwarning("Cancelled", "Export cancelled: Commit message is required.") return output_zip = filedialog.asksaveasfilename(defaultextension=".zip", filetypes=[("ZIP files", "*.zip")]) if output_zip: changes = { "added": self.comparer.added, "modified": self.comparer.modified, "deleted": self.comparer.deleted } self.sync_manager.create_export_package(self.src_path_var.get(), changes, output_zip, commit_msg) messagebox.showinfo("Success", f"Package created: {output_zip}") def _import_package(self) -> None: """ Selects a ZIP package and applies it to the destination folder. """ dst = self.dst_path_var.get() if not dst: messagebox.showwarning("Warning", "Please select a destination folder first.") return package_path = filedialog.askopenfilename(filetypes=[("ZIP files", "*.zip")]) if not package_path: return confirm = messagebox.askyesno("Confirm Import", "This will overwrite/delete files in the destination. Continue?") if confirm: commit_msg = self.sync_manager.apply_package(package_path, dst) messagebox.showinfo("Import Complete", f"Package applied successfully.\n\nCommit Message:\n{commit_msg}") self._run_comparison()