Initial commit
Dieser Commit ist enthalten in:
319
gui/gitea_toolbar.py
Normale Datei
319
gui/gitea_toolbar.py
Normale Datei
@ -0,0 +1,319 @@
|
||||
"""
|
||||
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"
|
||||
)
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren