shared-gui / gui_base.py
smlflg's picture
Initial public upload from Projekte/shared-gui
74f57c5 verified
Raw History Blame Contribute Delete
6.07 kB
#!/usr/bin/env python3
"""Shared GUI Base Module for GTK3 Apps.
Provides BaseApp class with:
- Window setup (title, size, icon, HeaderBar, StatusBar)
- Catppuccin theme via ClaudeCodePanel/theme.py
- COSMIC dark/light auto-detection + watcher
- Refresh timer management
- Extendable CSS
Usage:
from gui_base import BaseApp
class MyApp(BaseApp):
def __init__(self):
super().__init__("My App", 800, 600)
# Add widgets to self.content_box
label = Gtk.Label(label="Hello")
self.content_box.pack_start(label, True, True, 0)
"""
import sys
from pathlib import Path
import gi
gi.require_version("Gdk", "3.0")
gi.require_version("Gtk", "3.0")
from gi.repository import Gdk, GLib, Gtk
# Import theme module from ClaudeCodePanel
sys.path.insert(0, str(Path.home() / "Projekte" / "ClaudeCodePanel"))
from theme import build_css, get_palette, setup_theme_watcher
# ---------------------------------------------------------------------------
# Additional CSS for shared components
# ---------------------------------------------------------------------------
def _base_css(p: dict[str, str]) -> str:
"""Generate additional shared CSS classes."""
return f"""
.base-card {{
border-radius: 8px;
background: {p["card"]};
border: 1px solid {p["border"]};
padding: 10px;
margin: 4px 0;
}}
.base-card:hover {{
background: {p["border"]};
}}
.base-toolbar {{
background: {p["mantle"]};
border-radius: 8px;
padding: 6px 10px;
}}
.base-badge {{
border-radius: 12px;
padding: 2px 8px;
font-size: 10px;
font-weight: bold;
}}
.base-badge-ok {{
background: {p["green_hover_bg"]};
color: {p["green"]};
}}
.base-badge-warn {{
background: {p["yellow_hover_bg"]};
color: {p["yellow"]};
}}
.base-badge-error {{
background: rgba(243,139,168,0.2);
color: {p["red"]};
}}
.base-accent-btn {{
padding: 6px 16px;
border-radius: 6px;
background: {p["accent"]};
color: {p["resume_text"]};
font-weight: bold;
border: none;
}}
.base-accent-btn:hover {{
background: {p["lavender"]};
}}
"""
class BaseApp(Gtk.Window):
"""Base class for standalone GTK3 apps with Catppuccin theming."""
def __init__(
self,
title: str,
width: int = 800,
height: int = 600,
icon_name: str | None = None,
):
super().__init__(title=title)
self.set_default_size(width, height)
if icon_name:
self.set_icon_name(icon_name)
self.connect("destroy", Gtk.main_quit)
self._refresh_sources: list[int] = []
# --- Theme setup ---
self._css_provider = Gtk.CssProvider()
self._apply_full_css()
Gtk.StyleContext.add_provider_for_screen(
Gdk.Screen.get_default(),
self._css_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
)
setup_theme_watcher(self._css_provider)
# --- Layout: Header + Content + StatusBar ---
root_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.add(root_box)
# HeaderBar
self._header = Gtk.HeaderBar()
self._header.set_show_close_button(True)
self._header.set_title(title)
self.set_titlebar(self._header)
# Content area (subclasses add widgets here)
self.content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.content_box.set_margin_start(12)
self.content_box.set_margin_end(12)
self.content_box.set_margin_top(12)
self.content_box.set_margin_bottom(6)
root_box.pack_start(self.content_box, True, True, 0)
# StatusBar
self._status_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self._status_box.get_style_context().add_class("status-bar")
self._status_label = Gtk.Label(label="Ready")
self._status_label.set_xalign(0)
self._status_box.pack_start(self._status_label, True, True, 0)
root_box.pack_end(self._status_box, False, False, 0)
# --- Public API ---
def set_subtitle(self, subtitle: str) -> None:
"""Set the HeaderBar subtitle."""
self._header.set_subtitle(subtitle)
def add_header_widget(self, widget: Gtk.Widget, end: bool = True) -> None:
"""Add a widget to the HeaderBar (end=True for right side)."""
if end:
self._header.pack_end(widget)
else:
self._header.pack_start(widget)
def set_status(self, text: str, css_class: str = "") -> None:
"""Update the status bar text and optional CSS class."""
self._status_label.set_text(text)
ctx = self._status_label.get_style_context()
for cls in ("status-saved", "status-error"):
ctx.remove_class(cls)
if css_class:
ctx.add_class(css_class)
def start_refresh(self, interval_seconds: int, callback) -> int:
"""Start a periodic refresh timer. Returns the source ID."""
source_id = GLib.timeout_add_seconds(interval_seconds, callback)
self._refresh_sources.append(source_id)
return source_id
def stop_all_refresh(self) -> None:
"""Stop all refresh timers."""
for source_id in self._refresh_sources:
GLib.source_remove(source_id)
self._refresh_sources.clear()
def add_css(self, css_string: str) -> None:
"""Add extra CSS rules. Call after __init__."""
palette = get_palette()
full_css = build_css(palette).decode() + _base_css(palette) + css_string
self._css_provider.load_from_data(full_css.encode())
def run(self) -> None:
"""Show all widgets and start the GTK main loop."""
self.show_all()
Gtk.main()
# --- Internal ---
def _apply_full_css(self) -> None:
"""Load theme CSS + base CSS into the provider."""
palette = get_palette()
full_css = build_css(palette).decode() + _base_css(palette)
self._css_provider.load_from_data(full_css.encode())