File size: 6,069 Bytes
74f57c5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#!/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())