792 lines
27 KiB
Python
792 lines
27 KiB
Python
import json
|
|
import os
|
|
from datetime import datetime
|
|
|
|
from fabric.widgets.box import Box
|
|
from fabric.widgets.button import Button
|
|
from fabric.widgets.centerbox import CenterBox
|
|
from fabric.widgets.eventbox import EventBox
|
|
from fabric.widgets.label import Label
|
|
from fabric.widgets.revealer import Revealer
|
|
from fabric.widgets.scrolledwindow import ScrolledWindow
|
|
from gi.repository import Gdk, GLib, Gtk
|
|
|
|
import config.data as data
|
|
import utils.icons as icons
|
|
from modules.notification_popup import NotificationBox, get_limited_apps_history, get_history_ignored_apps
|
|
from utils.custom_image import CustomImage
|
|
from utils.notification_utils import (
|
|
CONFIG_FILE,
|
|
PERSISTENT_DIR,
|
|
PERSISTENT_HISTORY_FILE,
|
|
cleanup_orphan_cached_images,
|
|
compute_time_label,
|
|
create_historical_notification_from_data,
|
|
get_date_header,
|
|
get_shared_notification_history,
|
|
load_scaled_pixbuf,
|
|
save_persistent_history,
|
|
schedule_midnight_update,
|
|
)
|
|
from utils.wayland import WaylandWindow as Window
|
|
|
|
|
|
class NotificationHistoryPopup(Window):
|
|
"""Popup window that shows notification history with hover-based reveal"""
|
|
|
|
def __init__(self, dock_component=None, **kwargs):
|
|
# Get dock position for proper anchoring
|
|
dock_position = data.DOCK_POSITION
|
|
popup_anchor = self._get_popup_anchor(dock_position)
|
|
margin = self._get_popup_margin(dock_position)
|
|
|
|
super().__init__(
|
|
name="notification-history-popup",
|
|
layer="top",
|
|
anchor=popup_anchor,
|
|
margin=margin,
|
|
exclusive=False,
|
|
keyboard_mode="on-demand",
|
|
visible=False,
|
|
all_visible=False,
|
|
**kwargs,
|
|
)
|
|
|
|
self.dock_component = dock_component
|
|
|
|
# Get transition type based on dock position
|
|
transition_type = self._get_revealer_transition(dock_position)
|
|
|
|
# Create notification history content
|
|
self.notification_history = NotificationHistory()
|
|
|
|
# Create scrolled window for the history
|
|
self.scrolled = ScrolledWindow(
|
|
name="notifications-scrolled",
|
|
child=self.notification_history,
|
|
h_scrollbar_policy="never",
|
|
v_scrollbar_policy="automatic",
|
|
min_content_size=(400, 450),
|
|
max_content_size=(450, 500),
|
|
h_expand=True,
|
|
v_expand=True,
|
|
propagate_width=True,
|
|
propagate_height=True,
|
|
)
|
|
|
|
# Create main content box
|
|
self.content_box = Box(
|
|
name="notification-history-popup-content",
|
|
orientation="v",
|
|
spacing=8,
|
|
h_expand=True,
|
|
v_expand=True,
|
|
children=[self.scrolled],
|
|
style_classes=["notification-history-popup"],
|
|
)
|
|
|
|
# Create revealer for slide transition
|
|
self.revealer = Revealer(
|
|
transition_type=transition_type,
|
|
transition_duration=300,
|
|
child=self.content_box,
|
|
reveal_child=False,
|
|
)
|
|
|
|
self.add(self.revealer)
|
|
|
|
# Connect to escape key to close
|
|
self.connect("key-press-event", self.on_key_press)
|
|
self.connect("button-press-event", self.on_button_press)
|
|
self.connect("enter-notify-event", self.on_enter_notify)
|
|
self.connect("leave-notify-event", self.on_leave_notify)
|
|
self.set_can_focus(True)
|
|
|
|
def _get_popup_anchor(self, dock_position):
|
|
"""Get popup anchor based on dock position"""
|
|
anchor_map = {
|
|
"Top": "top",
|
|
"Bottom": "bottom right",
|
|
"Left": "left",
|
|
"Right": "right",
|
|
}
|
|
return anchor_map.get(dock_position, "bottom")
|
|
|
|
def _get_popup_margin(self, dock_position):
|
|
"""Get popup margin based on dock position"""
|
|
margin_map = {
|
|
"Top": "60px 10px 10px 10px",
|
|
"Bottom": "10px 650px 60px 10px",
|
|
"Left": "10px 10px 10px 60px",
|
|
"Right": "10px 60px 10px 10px",
|
|
}
|
|
return margin_map.get(dock_position, "10px 10px 60px 10px")
|
|
|
|
def _get_revealer_transition(self, dock_position):
|
|
"""Get revealer transition type based on dock position"""
|
|
transition_map = {
|
|
"Top": "slide-down",
|
|
"Bottom": "slide-up",
|
|
"Left": "slide-right",
|
|
"Right": "slide-left",
|
|
}
|
|
return transition_map.get(dock_position, "slide-up")
|
|
|
|
def on_key_press(self, widget, event):
|
|
"""Handle key press events"""
|
|
if event.keyval == Gdk.KEY_Escape:
|
|
self.hide_popup()
|
|
return True
|
|
elif event.state & Gdk.ModifierType.CONTROL_MASK and event.keyval == Gdk.KEY_d:
|
|
current_dnd = self.notification_history.do_not_disturb_enabled
|
|
self.notification_history.header_switch.set_active(not current_dnd)
|
|
return True
|
|
elif event.state & Gdk.ModifierType.CONTROL_MASK and event.keyval == Gdk.KEY_a:
|
|
self.notification_history.clear_history()
|
|
return True
|
|
return False
|
|
|
|
def on_button_press(self, widget, event):
|
|
"""Handle button press events"""
|
|
return False
|
|
|
|
def on_enter_notify(self, widget, event):
|
|
"""Handle mouse entering popup"""
|
|
# Cancel any pending hide from dock component
|
|
if self.dock_component and hasattr(self.dock_component, '_hover_timeout'):
|
|
if self.dock_component._hover_timeout:
|
|
GLib.source_remove(self.dock_component._hover_timeout)
|
|
self.dock_component._hover_timeout = None
|
|
return False
|
|
|
|
def on_leave_notify(self, widget, event):
|
|
"""Handle mouse leaving popup"""
|
|
# Hide popup when mouse leaves
|
|
self.hide_popup()
|
|
return False
|
|
|
|
def show_popup(self):
|
|
"""Show the notification history popup"""
|
|
self.set_visible(True)
|
|
self.show_all()
|
|
# Use GLib.idle_add to ensure the window is shown before revealing
|
|
GLib.idle_add(self._reveal_popup)
|
|
|
|
def _reveal_popup(self):
|
|
"""Reveal the popup after window is shown"""
|
|
self.revealer.set_reveal_child(True)
|
|
self.grab_focus()
|
|
return False # Don't repeat
|
|
|
|
def hide_popup(self):
|
|
"""Hide the notification history popup"""
|
|
self.revealer.set_reveal_child(False)
|
|
# Hide window after transition completes
|
|
GLib.timeout_add(350, lambda: self.set_visible(False))
|
|
|
|
|
|
class NotificationIndicator(EventBox):
|
|
def __init__(self, **kwargs):
|
|
self.notification_icon = Label(
|
|
name="notification-icon", markup=icons.notifications
|
|
)
|
|
|
|
# Create button for the icon
|
|
self.icon_button = Button(
|
|
name="button-bar-notifications",
|
|
child=self.notification_icon,
|
|
)
|
|
|
|
super().__init__(
|
|
name="notification-indicator",
|
|
child=self.icon_button,
|
|
**kwargs
|
|
)
|
|
|
|
# Set up event box for proper event handling
|
|
self.set_visible_window(False)
|
|
self.set_above_child(True)
|
|
|
|
# Connect hover events
|
|
self.connect("enter-notify-event", self.on_enter_notify)
|
|
self.connect("leave-notify-event", self.on_leave_notify)
|
|
self.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK)
|
|
|
|
self.popup = None
|
|
self._hover_timeout = None
|
|
|
|
self._connect_to_dnd_state()
|
|
|
|
# Connect to destroy signal for cleanup
|
|
self.connect("destroy", self.on_destroy)
|
|
|
|
def on_destroy(self, widget):
|
|
"""Clean up popup when component is destroyed"""
|
|
try:
|
|
# Remove hover timeout
|
|
if hasattr(self, "_hover_timeout") and self._hover_timeout:
|
|
GLib.source_remove(self._hover_timeout)
|
|
self._hover_timeout = None
|
|
|
|
# Clean up popup
|
|
if hasattr(self, 'popup') and self.popup:
|
|
try:
|
|
self.popup.destroy()
|
|
self.popup = None
|
|
except Exception:
|
|
pass
|
|
|
|
except Exception:
|
|
pass
|
|
|
|
def on_enter_notify(self, widget, event):
|
|
"""Handle mouse entering the notification component"""
|
|
print("Notification hover detected!") # Debug print
|
|
# Cancel any pending hover timeout
|
|
if self._hover_timeout:
|
|
GLib.source_remove(self._hover_timeout)
|
|
self._hover_timeout = None
|
|
|
|
# Show popup immediately on hover
|
|
if not self.popup or not self.popup.get_visible():
|
|
if not self.popup:
|
|
self.popup = NotificationHistoryPopup(dock_component=self)
|
|
self.popup.show_popup()
|
|
return False
|
|
|
|
def on_leave_notify(self, widget, event):
|
|
"""Handle mouse leaving the notification component"""
|
|
# Hide popup with a small delay to prevent flickering
|
|
self._hover_timeout = GLib.timeout_add(100, self._hide_popup_delayed)
|
|
return False
|
|
|
|
def _hide_popup_delayed(self):
|
|
"""Hide popup after delay"""
|
|
if self.popup and self.popup.get_visible():
|
|
self.popup.hide_popup()
|
|
self._hover_timeout = None
|
|
return False # Don't repeat timeout
|
|
|
|
def _connect_to_dnd_state(self):
|
|
try:
|
|
self.notification_history = get_shared_notification_history()
|
|
|
|
self.notification_history.connect(
|
|
"dnd-state-changed", self._on_dnd_state_changed
|
|
)
|
|
|
|
self._update_icon_for_dnd_state(
|
|
self.notification_history.do_not_disturb_enabled
|
|
)
|
|
except Exception as e:
|
|
print(f"Could not connect to DND state: {e}")
|
|
|
|
def _on_dnd_state_changed(self, notification_history, dnd_enabled):
|
|
self._update_icon_for_dnd_state(dnd_enabled)
|
|
|
|
def _update_icon_for_dnd_state(self, dnd_enabled):
|
|
if dnd_enabled:
|
|
self.notification_icon.set_markup(icons.notifications_off)
|
|
else:
|
|
self.notification_icon.set_markup(icons.notifications)
|
|
|
|
|
|
class NotificationHistory(Box):
|
|
def __init__(self, **kwargs):
|
|
super().__init__(name="notification-history", orientation="v", **kwargs)
|
|
|
|
self.containers = []
|
|
self.header_label = Label(
|
|
name="nhh",
|
|
label="Notifications",
|
|
h_align="start",
|
|
h_expand=True,
|
|
)
|
|
self.header_switch = Gtk.Switch(name="dnd-switch")
|
|
self.header_switch.set_vexpand(False)
|
|
self.header_switch.set_valign(Gtk.Align.CENTER)
|
|
self.header_switch.set_active(False)
|
|
self.header_clean = Button(
|
|
name="nhh-button",
|
|
child=Label(name="nhh-button-label", markup=icons.trash),
|
|
on_clicked=self.clear_history,
|
|
)
|
|
self.do_not_disturb_enabled = False
|
|
self.header_switch.connect("notify::active", self.on_do_not_disturb_changed)
|
|
self.dnd_label = Label(name="dnd-label", markup=icons.notifications_off)
|
|
|
|
self.history_header = CenterBox(
|
|
name="notification-history-header",
|
|
spacing=8,
|
|
start_children=[self.header_switch, self.dnd_label],
|
|
center_children=[self.header_label],
|
|
end_children=[self.header_clean],
|
|
)
|
|
self.notifications_list = Box(
|
|
name="notifications-list",
|
|
orientation="v",
|
|
spacing=4,
|
|
h_expand=True,
|
|
v_expand=True,
|
|
h_align="fill",
|
|
v_align="fill",
|
|
)
|
|
self.no_notifications_label = Label(
|
|
name="no-notif",
|
|
markup=icons.notifications_clear,
|
|
v_align="fill",
|
|
h_align="fill",
|
|
v_expand=True,
|
|
h_expand=True,
|
|
justification="center",
|
|
)
|
|
self.no_notifications_box = Box(
|
|
name="no-notifications-box",
|
|
v_align="fill",
|
|
h_align="fill",
|
|
v_expand=True,
|
|
h_expand=True,
|
|
children=[self.no_notifications_label],
|
|
)
|
|
self.scrolled_window = ScrolledWindow(
|
|
name="notification-history-scrolled-window",
|
|
orientation="v",
|
|
h_expand=True,
|
|
v_expand=True,
|
|
h_align="fill",
|
|
v_align="fill",
|
|
propagate_width=False,
|
|
propagate_height=False,
|
|
)
|
|
self.scrolled_window_viewport_box = Box(
|
|
orientation="v",
|
|
children=[self.notifications_list, self.no_notifications_box],
|
|
)
|
|
self.scrolled_window.add_with_viewport(self.scrolled_window_viewport_box)
|
|
self.persistent_notifications = []
|
|
self.add(self.history_header)
|
|
self.add(self.scrolled_window)
|
|
self._load_persistent_history()
|
|
cleanup_orphan_cached_images(self.persistent_notifications)
|
|
self.schedule_midnight_update()
|
|
|
|
self._connect_to_shared_history()
|
|
|
|
self._load_and_sync_dnd_state()
|
|
|
|
def _connect_to_shared_history(self):
|
|
try:
|
|
shared_history = get_shared_notification_history()
|
|
shared_history.connect(
|
|
"notification-added", self._on_shared_notification_added
|
|
)
|
|
except Exception as e:
|
|
print(f"Could not connect to shared notification history: {e}")
|
|
|
|
def _on_shared_notification_added(self, shared_history):
|
|
GLib.idle_add(self._refresh_history)
|
|
|
|
def _refresh_history(self):
|
|
try:
|
|
for child in self.notifications_list.get_children()[:]:
|
|
self.notifications_list.remove(child)
|
|
if hasattr(child, "destroy"):
|
|
child.destroy()
|
|
|
|
self.containers.clear()
|
|
|
|
if os.path.exists(PERSISTENT_HISTORY_FILE):
|
|
with open(PERSISTENT_HISTORY_FILE, "r") as f:
|
|
self.persistent_notifications = json.load(f)
|
|
|
|
for note in reversed(self.persistent_notifications[-50:]):
|
|
self._add_historical_notification(note)
|
|
|
|
self.update_no_notifications_label_visibility()
|
|
except Exception as e:
|
|
print(f"Error refreshing notification history: {e}")
|
|
|
|
def _load_and_sync_dnd_state(self):
|
|
try:
|
|
dnd_enabled = False
|
|
if os.path.exists(CONFIG_FILE):
|
|
with open(CONFIG_FILE, "r") as f:
|
|
config_data = json.load(f)
|
|
dnd_enabled = config_data.get("dnd_enabled", False)
|
|
|
|
shared_history = get_shared_notification_history()
|
|
shared_history.do_not_disturb_enabled = dnd_enabled
|
|
|
|
self.header_switch.set_active(dnd_enabled)
|
|
self.do_not_disturb_enabled = dnd_enabled
|
|
|
|
except Exception as e:
|
|
print(f"Could not sync DND state: {e}")
|
|
|
|
def schedule_midnight_update(self):
|
|
schedule_midnight_update(self.on_midnight)
|
|
|
|
def on_midnight(self):
|
|
self.rebuild_with_separators()
|
|
self.schedule_midnight_update()
|
|
return GLib.SOURCE_REMOVE
|
|
|
|
def create_date_separator(self, date_header):
|
|
return Box(
|
|
name="notif-date-sep",
|
|
children=[
|
|
Label(
|
|
name="notif-date-sep-label",
|
|
label=date_header,
|
|
h_align="center",
|
|
h_expand=True,
|
|
)
|
|
],
|
|
)
|
|
|
|
def rebuild_with_separators(self):
|
|
GLib.idle_add(self._do_rebuild_with_separators)
|
|
|
|
def _do_rebuild_with_separators(self):
|
|
children = list(self.notifications_list.get_children())
|
|
for child in children:
|
|
self.notifications_list.remove(child)
|
|
|
|
current_date_header = None
|
|
last_date_header = None
|
|
for container in sorted(
|
|
self.containers, key=lambda x: x.arrival_time, reverse=True
|
|
):
|
|
arrival_time = container.arrival_time
|
|
date_header = get_date_header(arrival_time)
|
|
if date_header != current_date_header:
|
|
sep = self.create_date_separator(date_header)
|
|
self.notifications_list.add(sep)
|
|
current_date_header = date_header
|
|
last_date_header = date_header
|
|
self.notifications_list.add(container)
|
|
|
|
if not self.containers and last_date_header:
|
|
for child in list(self.notifications_list.get_children()):
|
|
if child.get_name() == "notif-date-sep":
|
|
self.notifications_list.remove(child)
|
|
|
|
self.notifications_list.show_all()
|
|
self.update_no_notifications_label_visibility()
|
|
|
|
def on_do_not_disturb_changed(self, switch, pspec):
|
|
self.do_not_disturb_enabled = switch.get_active()
|
|
|
|
try:
|
|
shared_history = get_shared_notification_history()
|
|
shared_history.set_do_not_disturb_enabled(self.do_not_disturb_enabled)
|
|
except Exception as e:
|
|
print(f"Could not update shared DND state: {e}")
|
|
|
|
def clear_history(self, *args):
|
|
for child in self.notifications_list.get_children()[:]:
|
|
container = child
|
|
notif_box = (
|
|
container.notification_box
|
|
if hasattr(container, "notification_box")
|
|
else None
|
|
)
|
|
if notif_box:
|
|
notif_box.destroy(from_history_delete=True)
|
|
self.notifications_list.remove(child)
|
|
child.destroy()
|
|
|
|
if os.path.exists(PERSISTENT_HISTORY_FILE):
|
|
try:
|
|
os.remove(PERSISTENT_HISTORY_FILE)
|
|
|
|
except Exception as e:
|
|
print(f"Error deleting persistent history file: {e}")
|
|
|
|
self.persistent_notifications = []
|
|
self.containers = []
|
|
self.rebuild_with_separators()
|
|
|
|
def _load_persistent_history(self):
|
|
if not os.path.exists(PERSISTENT_DIR):
|
|
os.makedirs(PERSISTENT_DIR, exist_ok=True)
|
|
|
|
if os.path.exists(PERSISTENT_HISTORY_FILE):
|
|
try:
|
|
with open(PERSISTENT_HISTORY_FILE, "r") as f:
|
|
self.persistent_notifications = json.load(f)
|
|
|
|
for note in reversed(self.persistent_notifications[-50:]):
|
|
self._add_historical_notification(note)
|
|
|
|
except Exception as e:
|
|
print(f"Error loading persistent history: {e}")
|
|
self.persistent_notifications = []
|
|
else:
|
|
self.persistent_notifications = []
|
|
|
|
GLib.idle_add(self.update_no_notifications_label_visibility)
|
|
|
|
def _save_persistent_history(self):
|
|
save_persistent_history(self.persistent_notifications)
|
|
|
|
def update_no_notifications_label_visibility(self):
|
|
has_notifications = bool(self.containers)
|
|
self.no_notifications_box.set_visible(not has_notifications)
|
|
self.notifications_list.set_visible(has_notifications)
|
|
|
|
def _add_historical_notification(self, note):
|
|
hist_notif = create_historical_notification_from_data(note)
|
|
|
|
hist_box = NotificationBox(hist_notif, timeout_ms=0)
|
|
hist_box.uuid = hist_notif.id
|
|
hist_box.cached_image_path = hist_notif.cached_image_path
|
|
hist_box.set_is_history(True)
|
|
for child in hist_box.get_children():
|
|
if child.get_name() == "notification-action-buttons":
|
|
hist_box.remove(child)
|
|
container = Box(
|
|
name="notification-container",
|
|
orientation="v",
|
|
h_align="fill",
|
|
h_expand=True,
|
|
)
|
|
container.notification_box = hist_box
|
|
try:
|
|
arrival = datetime.fromisoformat(hist_notif.timestamp)
|
|
except Exception:
|
|
arrival = datetime.now()
|
|
container.arrival_time = arrival
|
|
|
|
self.hist_time_label = Label(
|
|
name="notification-timestamp",
|
|
markup=compute_time_label(container.arrival_time),
|
|
h_align="start",
|
|
ellipsization="end",
|
|
)
|
|
self.hist_notif_image_box = Box(
|
|
name="notification-image",
|
|
orientation="v",
|
|
children=[
|
|
CustomImage(pixbuf=load_scaled_pixbuf(hist_box, 48, 48)),
|
|
Box(v_expand=True),
|
|
],
|
|
)
|
|
self.hist_notif_summary_label = Label(
|
|
name="notification-summary",
|
|
markup=hist_notif.summary,
|
|
h_align="start",
|
|
ellipsization="end",
|
|
)
|
|
|
|
self.hist_notif_app_name_label = Label(
|
|
name="notification-app-name",
|
|
markup=f"{hist_notif.app_name}",
|
|
h_align="start",
|
|
ellipsization="end",
|
|
)
|
|
|
|
self.hist_notif_body_label = (
|
|
Label(
|
|
name="notification-body",
|
|
markup=hist_notif.body,
|
|
h_align="start",
|
|
ellipsization="end",
|
|
line_wrap="word-char",
|
|
)
|
|
if hist_notif.body
|
|
else Box()
|
|
)
|
|
self.hist_notif_body_label.set_single_line_mode(
|
|
True
|
|
) if hist_notif.body else None
|
|
|
|
self.hist_notif_summary_box = Box(
|
|
name="notification-summary-box",
|
|
orientation="h",
|
|
children=[
|
|
self.hist_notif_summary_label,
|
|
Box(
|
|
name="notif-sep",
|
|
h_expand=False,
|
|
v_expand=False,
|
|
h_align="center",
|
|
v_align="center",
|
|
),
|
|
self.hist_notif_app_name_label,
|
|
Box(
|
|
name="notif-sep",
|
|
h_expand=False,
|
|
v_expand=False,
|
|
h_align="center",
|
|
v_align="center",
|
|
),
|
|
self.hist_time_label,
|
|
],
|
|
)
|
|
self.hist_notif_text_box = Box(
|
|
name="notification-text",
|
|
orientation="v",
|
|
v_align="center",
|
|
h_expand=True,
|
|
children=[
|
|
self.hist_notif_summary_box,
|
|
self.hist_notif_body_label,
|
|
],
|
|
)
|
|
self.hist_notif_close_button = Button(
|
|
name="notif-close-button",
|
|
child=Label(name="notif-close-label", markup=icons.cancel),
|
|
on_clicked=lambda *_: self.delete_historical_notification(
|
|
hist_notif.id, container
|
|
),
|
|
)
|
|
self.hist_notif_close_button_box = Box(
|
|
orientation="v",
|
|
children=[
|
|
self.hist_notif_close_button,
|
|
Box(v_expand=True),
|
|
],
|
|
)
|
|
content_box = Box(
|
|
name="notification-box-hist",
|
|
spacing=8,
|
|
children=[
|
|
self.hist_notif_image_box,
|
|
self.hist_notif_text_box,
|
|
self.hist_notif_close_button_box,
|
|
],
|
|
)
|
|
container.add(content_box)
|
|
self.containers.insert(0, container)
|
|
self.rebuild_with_separators()
|
|
self.update_no_notifications_label_visibility()
|
|
|
|
def delete_historical_notification(self, note_id, container):
|
|
if hasattr(container, "notification_box"):
|
|
notif_box = container.notification_box
|
|
notif_box.destroy(from_history_delete=True)
|
|
|
|
target_note_id_str = str(note_id)
|
|
|
|
new_persistent_notifications = []
|
|
removed_from_list = False
|
|
for note_in_list in self.persistent_notifications:
|
|
current_note_id_str = str(note_in_list.get("id"))
|
|
if current_note_id_str == target_note_id_str:
|
|
removed_from_list = True
|
|
continue
|
|
new_persistent_notifications.append(note_in_list)
|
|
|
|
if removed_from_list:
|
|
self.persistent_notifications = new_persistent_notifications
|
|
|
|
self._save_persistent_history()
|
|
container.destroy()
|
|
self.containers = [c for c in self.containers if c != container]
|
|
self.rebuild_with_separators()
|
|
|
|
|
|
class NotificationHistoryWindow(Window):
|
|
def __init__(self):
|
|
self.notification_history = NotificationHistory()
|
|
|
|
scrolled = ScrolledWindow(
|
|
name="notifications-scrolled",
|
|
child=self.notification_history,
|
|
h_scrollbar_policy="never",
|
|
v_scrollbar_policy="automatic",
|
|
min_content_size=(400, 450),
|
|
max_content_size=(450, 500),
|
|
h_expand=True,
|
|
v_expand=True,
|
|
propagate_width=False,
|
|
propagate_height=False,
|
|
)
|
|
|
|
main_box = Box(
|
|
name="notifications-popup-main",
|
|
orientation="v",
|
|
spacing=8,
|
|
h_expand=True,
|
|
v_expand=True,
|
|
children=[scrolled],
|
|
)
|
|
|
|
dock_position = data.DOCK_POSITION
|
|
popup_anchor = self._get_popup_anchor(dock_position)
|
|
|
|
margin = self._get_popup_margin(dock_position)
|
|
|
|
super().__init__(
|
|
anchor=popup_anchor,
|
|
margin=margin,
|
|
child=main_box,
|
|
layer="top",
|
|
exclusive=False,
|
|
keyboard_mode="on-demand",
|
|
visible=True,
|
|
all_visible=True,
|
|
)
|
|
|
|
self.connect("button-press-event", self.on_button_press)
|
|
self.connect("key-press-event", self.on_key_press)
|
|
|
|
self.set_can_focus(True)
|
|
self.grab_focus()
|
|
|
|
self.set_tooltip_text(
|
|
"Keyboard shortcuts:\n• Escape: Close popup\n• Ctrl+D: Toggle DND\n• Ctrl+A: Clear all notifications"
|
|
)
|
|
|
|
def _get_popup_anchor(self, dock_position):
|
|
anchor_map = {
|
|
"Top": "top", # Dock at top -> popup at top right
|
|
"Bottom": "bottom", # Dock at bottom -> popup at bottom right
|
|
"Left": "left", # Dock at left -> popup at top left
|
|
"Right": "right", # Dock at right -> popup at top right
|
|
}
|
|
return anchor_map.get(dock_position, "bottom")
|
|
|
|
def _get_popup_margin(self, dock_position):
|
|
margin_map = {
|
|
"Top": "60px 10px 10px 10px",
|
|
"Bottom": "10px 10px 60px 10px",
|
|
"Left": "10px 10px 10px 60px",
|
|
"Right": "10px 60px 10px 10px",
|
|
}
|
|
return margin_map.get(dock_position, "10px 10px 10px 10px")
|
|
|
|
def refresh_popup(self):
|
|
pass
|
|
|
|
def on_key_press(self, _widget, event):
|
|
if event.keyval == Gdk.KEY_Escape:
|
|
self.set_visible(False)
|
|
return True
|
|
|
|
elif event.state & Gdk.ModifierType.CONTROL_MASK and event.keyval == Gdk.KEY_d:
|
|
current_dnd = self.notification_history.do_not_disturb_enabled
|
|
self.notification_history.header_switch.set_active(not current_dnd)
|
|
return True
|
|
|
|
elif event.state & Gdk.ModifierType.CONTROL_MASK and event.keyval == Gdk.KEY_a:
|
|
self.notification_history.clear_history()
|
|
return True
|
|
|
|
return False
|
|
|
|
def on_button_press(self, _widget, _event):
|
|
return False
|
|
|
|
|
|
class Notifications(Box):
|
|
def __init__(self, **kwargs):
|
|
super().__init__(
|
|
orientation="h" if not data.VERTICAL else "v",
|
|
spacing=4,
|
|
children=[NotificationIndicator()],
|
|
**kwargs,
|
|
)
|
|
self.show_all()
|