353 lines
11 KiB
Python
353 lines
11 KiB
Python
from datetime import datetime
|
|
|
|
from fabric.widgets.box import Box
|
|
from fabric.widgets.button import Button
|
|
from fabric.widgets.image import Image
|
|
from fabric.widgets.label import Label
|
|
from fabric.widgets.scrolledwindow import ScrolledWindow
|
|
from fabric.widgets.wayland import WaylandWindow as Window
|
|
from gi.repository import Gdk
|
|
|
|
from sims.modules.calendar import CalendarService
|
|
from sims.modules.vinyl import VinylButton
|
|
from sims.services.notification_history import NotificationHistoryService
|
|
from sims.widgets.notification_history_entry import NotificationHistoryEntryWidget
|
|
|
|
|
|
class ControlCenter(Window):
|
|
def __init__(
|
|
self,
|
|
history: NotificationHistoryService,
|
|
calendar_service: CalendarService | None = None,
|
|
vinyl_button: VinylButton | None = None,
|
|
monitor: int = 0,
|
|
width: int = 380,
|
|
):
|
|
super().__init__(
|
|
name="control-center",
|
|
anchor="top right bottom",
|
|
monitor=monitor,
|
|
margin="0",
|
|
exclusivity="none",
|
|
keyboard_mode="on-demand",
|
|
visible=False,
|
|
)
|
|
self._history = history
|
|
self._calendar_service = calendar_service
|
|
self._vinyl_button = vinyl_button
|
|
self._width = width
|
|
self._visibility_listeners: list = []
|
|
|
|
close_button = Button(
|
|
name="control-center-close",
|
|
image=Image(icon_name="window-close-symbolic", icon_size=16),
|
|
on_clicked=lambda *_: self.hide(),
|
|
)
|
|
header = Box(
|
|
name="control-center-header",
|
|
orientation="h",
|
|
spacing=8,
|
|
)
|
|
header.pack_start(
|
|
Label(name="control-center-title", label="Control Center", h_align="start"),
|
|
True,
|
|
True,
|
|
0,
|
|
)
|
|
header.pack_end(close_button, False, False, 0)
|
|
|
|
sections: list = []
|
|
|
|
if vinyl_button is not None:
|
|
sections.append(self._build_settings_section())
|
|
|
|
if calendar_service is not None:
|
|
sections.append(self._build_calendar_section())
|
|
|
|
sections.append(self._build_notifications_section())
|
|
|
|
sections_box = Box(
|
|
name="control-center-sections",
|
|
orientation="v",
|
|
spacing=12,
|
|
children=sections,
|
|
h_expand=True,
|
|
)
|
|
|
|
scroll = ScrolledWindow(
|
|
name="control-center-scroll",
|
|
h_scrollbar_policy="never",
|
|
v_scrollbar_policy="automatic",
|
|
child=sections_box,
|
|
h_expand=True,
|
|
v_expand=True,
|
|
)
|
|
|
|
body = Box(
|
|
name="control-center-body",
|
|
orientation="v",
|
|
spacing=8,
|
|
children=[header, scroll],
|
|
h_expand=True,
|
|
v_expand=True,
|
|
)
|
|
body.set_size_request(self._width, -1)
|
|
self.add(body)
|
|
self.connect("key-press-event", self._on_key_press)
|
|
|
|
self._history.connect("changed", lambda *_: self._refresh_notifications())
|
|
if self._calendar_service is not None:
|
|
self._calendar_service.connect(
|
|
"events-changed",
|
|
lambda _service, events: self._refresh_calendar(events),
|
|
)
|
|
|
|
self._refresh_notifications()
|
|
if self._calendar_service is not None:
|
|
self._refresh_calendar(self._calendar_service.get_cached_events())
|
|
|
|
def _build_section(self, name: str, title: str | None) -> Box:
|
|
section = Box(
|
|
name=name,
|
|
orientation="v",
|
|
spacing=6,
|
|
h_expand=True,
|
|
)
|
|
if title is not None:
|
|
section.add(
|
|
Label(
|
|
name="control-center-section-title",
|
|
label=title,
|
|
h_align="start",
|
|
)
|
|
)
|
|
return section
|
|
|
|
def _build_settings_section(self) -> Box:
|
|
section = self._build_section("control-center-settings", "Settings")
|
|
|
|
row = Box(
|
|
name="control-center-settings-row",
|
|
orientation="h",
|
|
spacing=8,
|
|
)
|
|
row.pack_start(
|
|
Label(
|
|
name="control-center-settings-label",
|
|
label="Vinyl Passthrough",
|
|
h_align="start",
|
|
),
|
|
True,
|
|
True,
|
|
0,
|
|
)
|
|
row.pack_end(self._vinyl_button, False, False, 0)
|
|
section.add(row)
|
|
return section
|
|
|
|
def _build_calendar_section(self) -> Box:
|
|
section = self._build_section("control-center-calendar", "Calendar")
|
|
self._calendar_events_box = Box(
|
|
name="control-center-events",
|
|
orientation="v",
|
|
spacing=4,
|
|
h_expand=True,
|
|
)
|
|
section.add(self._calendar_events_box)
|
|
return section
|
|
|
|
def _build_notifications_section(self) -> Box:
|
|
section = self._build_section("control-center-notifications", None)
|
|
|
|
clear_button = Button(
|
|
name="control-center-notifications-clear",
|
|
label="Clear all",
|
|
on_clicked=lambda *_: self._history.clear(),
|
|
)
|
|
header = Box(
|
|
name="control-center-notifications-header",
|
|
orientation="h",
|
|
spacing=8,
|
|
)
|
|
header.pack_start(
|
|
Label(
|
|
name="control-center-section-title",
|
|
label="Notifications",
|
|
h_align="start",
|
|
),
|
|
True,
|
|
True,
|
|
0,
|
|
)
|
|
header.pack_end(clear_button, False, False, 0)
|
|
|
|
self._notifications_empty = Label(
|
|
name="control-center-notifications-empty",
|
|
label="No notifications",
|
|
h_align="start",
|
|
)
|
|
self._notifications_list = Box(
|
|
name="control-center-notifications-list",
|
|
orientation="v",
|
|
spacing=6,
|
|
h_expand=True,
|
|
)
|
|
|
|
section.add(header)
|
|
section.add(self._notifications_list)
|
|
return section
|
|
|
|
def add_visibility_listener(self, callback) -> None:
|
|
self._visibility_listeners.append(callback)
|
|
|
|
def _notify_visibility(self, visible: bool) -> None:
|
|
for callback in self._visibility_listeners:
|
|
callback(visible)
|
|
|
|
def toggle(self) -> None:
|
|
if self.get_visible():
|
|
self.hide()
|
|
else:
|
|
self.show()
|
|
|
|
def show(self) -> None: # type: ignore[override]
|
|
self._history.mark_all_seen()
|
|
super().show()
|
|
self.show_all()
|
|
self._notify_visibility(True)
|
|
|
|
def hide(self) -> None: # type: ignore[override]
|
|
super().hide()
|
|
self._notify_visibility(False)
|
|
|
|
def _on_key_press(self, _widget, event):
|
|
if event.keyval == Gdk.KEY_Escape:
|
|
self.hide()
|
|
return True
|
|
return False
|
|
|
|
def _refresh_notifications(self) -> None:
|
|
for child in self._notifications_list.get_children():
|
|
self._notifications_list.remove(child)
|
|
if child is not self._notifications_empty:
|
|
child.destroy()
|
|
entries = self._history.entries
|
|
if not entries:
|
|
self._notifications_list.add(self._notifications_empty)
|
|
self._notifications_empty.show_all()
|
|
return
|
|
for entry in entries:
|
|
self._notifications_list.add(
|
|
NotificationHistoryEntryWidget(
|
|
entry, on_dismiss=self._history.remove
|
|
)
|
|
)
|
|
self._notifications_list.show_all()
|
|
|
|
def _refresh_calendar(self, events) -> None:
|
|
for child in self._calendar_events_box.get_children():
|
|
self._calendar_events_box.remove(child)
|
|
child.destroy()
|
|
|
|
if not events:
|
|
self._calendar_events_box.add(
|
|
Label(
|
|
name="control-center-no-events",
|
|
label="No events today",
|
|
h_align="start",
|
|
)
|
|
)
|
|
self._calendar_events_box.show_all()
|
|
return
|
|
|
|
now = datetime.now()
|
|
current_time = now.strftime("%H:%M")
|
|
current_time_added = False
|
|
|
|
for event in events:
|
|
title = event.get("title", "No title")
|
|
start_raw = event.get("start", "")
|
|
end_raw = event.get("end", "")
|
|
start_time = start_raw.split()[1] if start_raw else ""
|
|
end_time = end_raw.split()[1] if end_raw else ""
|
|
location = event.get("location", "")
|
|
|
|
if not current_time_added and start_time and start_time > current_time:
|
|
self._calendar_events_box.add(self._build_now_indicator(current_time))
|
|
current_time_added = True
|
|
|
|
if start_time and end_time:
|
|
time_str = f"{start_time} - {end_time}"
|
|
elif start_time:
|
|
time_str = start_time
|
|
else:
|
|
time_str = "All day"
|
|
|
|
event_box = Box(
|
|
name="event-item",
|
|
orientation="h",
|
|
spacing=12,
|
|
style_classes=["event-item"],
|
|
)
|
|
event_box.add(
|
|
Label(
|
|
time_str,
|
|
name="event-time",
|
|
style_classes=["event-time"],
|
|
style="min-width: 90px;",
|
|
)
|
|
)
|
|
content_box = Box(
|
|
name="event-content",
|
|
orientation="v",
|
|
spacing=2,
|
|
)
|
|
content_box.add(
|
|
Label(
|
|
title,
|
|
name="event-title",
|
|
style_classes=["event-title"],
|
|
h_align="start",
|
|
)
|
|
)
|
|
if location:
|
|
content_box.add(
|
|
Label(
|
|
f"📍 {location}",
|
|
name="event-location",
|
|
style_classes=["event-location"],
|
|
h_align="start",
|
|
)
|
|
)
|
|
event_box.add(content_box)
|
|
self._calendar_events_box.add(event_box)
|
|
|
|
if not current_time_added:
|
|
self._calendar_events_box.add(self._build_now_indicator(current_time))
|
|
|
|
self._calendar_events_box.show_all()
|
|
|
|
def _build_now_indicator(self, current_time: str) -> Box:
|
|
indicator = Box(
|
|
name="current-time-indicator",
|
|
orientation="h",
|
|
spacing=8,
|
|
style_classes=["current-time-indicator"],
|
|
)
|
|
indicator.add(
|
|
Label(
|
|
current_time,
|
|
name="current-time-label",
|
|
style_classes=["current-time-label"],
|
|
style="min-width: 90px; font-weight: bold;",
|
|
)
|
|
)
|
|
indicator.add(
|
|
Label(
|
|
"━━━ NOW",
|
|
name="current-time-line",
|
|
style_classes=["current-time-line"],
|
|
)
|
|
)
|
|
return indicator
|