319 Zeilen
12 KiB
Python
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"
|
|
) |