SXXXXXXX_CodeBridge/codebridge/gui/main_window.py
2025-12-23 09:31:38 +01:00

156 lines
6.7 KiB
Python

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("<Double-1>", 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()