Files
ClaudeProjectManager-main/gui/gitea_toolbar.py
Claude Project Manager ec92da8a64 Initial commit
2025-07-07 22:11:38 +02:00

319 Zeilen
12 KiB
Python

"""
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("<Enter>", on_enter)
widget.bind("<Leave>", on_leave)
widget.bind("<Button-1>", 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"
)