""" Gitea Toolbar for context-sensitive Git operations """ import customtkinter as ctk from tkinter import messagebox import threading from pathlib import Path from typing import Optional, Callable, Dict from gui.styles import COLORS, FONTS class GiteaToolbar(ctk.CTkFrame): def __init__(self, parent, **kwargs): super().__init__(parent, fg_color=COLORS['bg_secondary'], height=60, **kwargs) self.pack_propagate(False) # Callbacks self.callbacks: Dict[str, Optional[Callable]] = { 'commit': None, 'push': None, 'pull': None, 'fetch': None, 'status': None, 'branch': None, 'link': None, 'clone': None, 'create_project': None } # Current context self.current_context = None # 'local_project', 'gitea_repo', or None self.current_item = None # Project or Repo object # Animation self.is_visible = False self.target_height = 60 self.current_height = 0 # Create buttons container self.button_container = ctk.CTkFrame(self, fg_color="transparent") self.button_container.pack(fill="both", expand=True, padx=10, pady=8) # Status label self.status_label = ctk.CTkLabel( self.button_container, text="", font=FONTS['small'], text_color=COLORS['text_secondary'] ) self.status_label.pack(side="left", padx=(0, 20)) # Button groups self.button_groups = { 'local_project': [], 'gitea_repo': [] } self.setup_buttons() def setup_buttons(self): """Setup all buttons for different contexts""" # Buttons for local project context local_buttons = [ ("📊 Status", "status", "Git-Status anzeigen\nZeigt uncommittete Änderungen"), ("💾 Commit", "commit", "Änderungen speichern\nLokale Änderungen committen"), ("📤 Push", "push", "Zu Gitea hochladen\nCommits zum Server pushen"), ("📥 Pull", "pull", "Änderungen abrufen\nNeueste Änderungen vom Server"), ("🔗 Verknüpfen", "link", "Mit Gitea verknüpfen\nLokales Projekt mit Repository verbinden"), ("🌿 Branch", "branch", "Branch-Verwaltung\nBranches anzeigen und wechseln") ] # Buttons for Gitea repo context gitea_buttons = [ ("📥 Clone", "clone", "Repository klonen\nLokale Kopie erstellen und Projekt anlegen"), ("🔄 Fetch", "fetch", "Updates prüfen\nNeueste Änderungen abrufen"), ("📊 Info", "status", "Repository-Info\nDetails zum Repository anzeigen") ] # Create button frames self.local_frame = ctk.CTkFrame(self.button_container, fg_color="transparent") self.gitea_frame = ctk.CTkFrame(self.button_container, fg_color="transparent") # Create local project buttons for text, callback_name, tooltip in local_buttons: btn = self.create_button(self.local_frame, text, callback_name, tooltip) self.button_groups['local_project'].append(btn) # Create Gitea repo buttons for text, callback_name, tooltip in gitea_buttons: btn = self.create_button(self.gitea_frame, text, callback_name, tooltip) self.button_groups['gitea_repo'].append(btn) def create_button(self, parent, text, callback_name, tooltip): """Create a button with tooltip""" btn = ctk.CTkButton( parent, text=text, command=lambda: self.execute_callback(callback_name), width=120, height=36, fg_color=COLORS['accent_secondary'], hover_color=COLORS['accent_hover'], text_color="#FFFFFF", font=FONTS['body'] ) btn.pack(side="left", padx=4) # Create tooltip self.create_tooltip(btn, tooltip) return btn def create_tooltip(self, widget, text): """Create a tooltip for a widget""" def on_enter(event): # Destroy any existing tooltip first if hasattr(widget, 'tooltip') and widget.tooltip: try: widget.tooltip.destroy() except: pass tooltip = ctk.CTkToplevel(widget) tooltip.wm_overrideredirect(True) tooltip.wm_geometry(f"+{event.x_root + 10}+{event.y_root + 10}") label = ctk.CTkLabel( tooltip, text=text, font=FONTS['small'], fg_color=COLORS['bg_secondary'], text_color=COLORS['text_primary'], corner_radius=6, padx=10, pady=5 ) label.pack() widget.tooltip = tooltip def on_leave(event): if hasattr(widget, 'tooltip') and widget.tooltip: try: widget.tooltip.destroy() widget.tooltip = None except: pass # Also destroy tooltip when button is clicked def on_click(event): if hasattr(widget, 'tooltip') and widget.tooltip: try: widget.tooltip.destroy() widget.tooltip = None except: pass widget.bind("", on_enter) widget.bind("", on_leave) widget.bind("", on_click, add=True) def set_callback(self, name: str, callback: Callable): """Set a callback for a button""" if name in self.callbacks: self.callbacks[name] = callback def execute_callback(self, name: str): """Execute a callback if set""" if name in self.callbacks and self.callbacks[name]: # Run in thread to avoid blocking UI threading.Thread( target=self.callbacks[name], args=(self.current_item,), daemon=True ).start() def show(self): """Show the toolbar""" if not self.is_visible: self.animate_show() def show_for_context(self, context: str, item=None, status_text: str = ""): """Show toolbar for specific context""" if context not in ['local_project', 'gitea_repo']: self.hide() return self.current_context = context self.current_item = item # Update status self.status_label.configure(text=status_text) # Hide all frames self.local_frame.pack_forget() self.gitea_frame.pack_forget() # Show relevant frame if context == 'local_project': self.local_frame.pack(side="left", padx=10) elif context == 'gitea_repo': self.gitea_frame.pack(side="left", padx=10) # Animate show if not self.is_visible: self.animate_show() def hide(self): """Hide the toolbar""" if self.is_visible: self.animate_hide() def animate_show(self): """Animate toolbar appearing""" print("animate_show called") self.is_visible = True # Pack the toolbar try: # Simple approach: just pack after any header that exists parent_children = self.master.winfo_children() print(f"Parent has {len(parent_children)} children") # Find the best position (after header, before content) packed = False for i, child in enumerate(parent_children): # Look for a frame that might be the header if isinstance(child, ctk.CTkFrame): # Check if it's likely the header (has small height or contains title) try: if child.winfo_height() < 100 or any( isinstance(w, ctk.CTkLabel) and "Claude Project Manager" in str(w.cget("text")) for w in child.winfo_children() ): print(f"Packing toolbar after header at position {i}") self.pack(fill="x", after=child, pady=(0, 10)) packed = True break except: pass if not packed: print("Packing toolbar at default position") self.pack(fill="x", pady=(0, 10)) except Exception as e: print(f"Error showing toolbar: {e}") import traceback traceback.print_exc() self.pack(fill="x", pady=(0, 10)) # Skip animation for now - just show full height self.configure(height=self.target_height) print(f"Toolbar configured with height {self.target_height}") def animate_hide(self): """Animate toolbar disappearing""" self.is_visible = False # Destroy all tooltips before hiding for group in self.button_groups.values(): for btn in group: if hasattr(btn, 'tooltip') and btn.tooltip: try: btn.tooltip.destroy() btn.tooltip = None except: pass # Skip animation for now - just hide immediately self.pack_forget() def _animate_height(self, start, end, duration=200, callback=None): """Animate height change""" steps = 10 step_duration = duration // steps step_size = (end - start) / steps def animate_step(current_step): if current_step <= steps: new_height = start + (step_size * current_step) self.configure(height=new_height) self.after(step_duration, lambda: animate_step(current_step + 1)) elif callback: callback() animate_step(0) def set_selected_repo(self, repo): """Set the selected repository and show appropriate buttons""" self.show_for_context('gitea_repo', repo) def update_button_states(self, states: Dict[str, bool]): """Update button enabled/disabled states""" for context_buttons in self.button_groups.values(): for btn in context_buttons: # Extract callback name from button text btn_text = btn.cget("text") for callback_name in self.callbacks: if callback_name in states: # Simple matching - could be improved btn.configure(state="normal" if states[callback_name] else "disabled") def refresh_colors(self): """Refresh the toolbar's colors""" from gui.styles import COLORS # Update frame color self.configure(fg_color=COLORS['bg_secondary']) # Update status label self.status_label.configure(text_color=COLORS['text_secondary']) # Update all buttons for context_buttons in self.button_groups.values(): for btn in context_buttons: btn.configure( fg_color=COLORS['accent_primary'], hover_color=COLORS['accent_hover'], text_color="#FFFFFF" )