Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e72cc79c6a | |||
| 667c0babcb |
@@ -139,6 +139,32 @@ class NotificationPresenter:
|
|||||||
self._notifications = []
|
self._notifications = []
|
||||||
self._current_index = -1
|
self._current_index = -1
|
||||||
|
|
||||||
|
def remove_entry(self, entry: Optional[NotificationEntry]) -> bool:
|
||||||
|
"""Removes entry from the notification history."""
|
||||||
|
|
||||||
|
if entry is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
index = self._notifications.index(entry)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
del self._notifications[index]
|
||||||
|
if not self._notifications:
|
||||||
|
self._current_index = -1
|
||||||
|
return True
|
||||||
|
|
||||||
|
if self._current_index == -1:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if index < self._current_index:
|
||||||
|
self._current_index -= 1
|
||||||
|
elif index == self._current_index and self._current_index >= len(self._notifications):
|
||||||
|
self._current_index = -1
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def refresh_live_notifications(self) -> bool:
|
def refresh_live_notifications(self) -> bool:
|
||||||
"""Refreshes live mako state without announcing new notifications."""
|
"""Refreshes live mako state without announcing new notifications."""
|
||||||
|
|
||||||
@@ -207,6 +233,8 @@ class NotificationPresenter:
|
|||||||
|
|
||||||
result = self._mako_monitor.dismiss_notification(entry.notification_id)
|
result = self._mako_monitor.dismiss_notification(entry.notification_id)
|
||||||
if result:
|
if result:
|
||||||
|
entry.live = False
|
||||||
|
self.remove_entry(entry)
|
||||||
script.presentMessage(messages.NOTIFICATION_DISMISSED)
|
script.presentMessage(messages.NOTIFICATION_DISMISSED)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -421,8 +449,7 @@ class NotificationListGUI:
|
|||||||
"""The dialog containing the notifications list."""
|
"""The dialog containing the notifications list."""
|
||||||
|
|
||||||
RESPONSE_COPY = 1
|
RESPONSE_COPY = 1
|
||||||
RESPONSE_ACTIONS = 2
|
RESPONSE_DISMISS = 2
|
||||||
RESPONSE_DISMISS = 3
|
|
||||||
|
|
||||||
def __init__(self, presenter, script, title, column_headers, entries):
|
def __init__(self, presenter, script, title, column_headers, entries):
|
||||||
self._presenter = presenter
|
self._presenter = presenter
|
||||||
@@ -431,17 +458,14 @@ class NotificationListGUI:
|
|||||||
self._tree = None
|
self._tree = None
|
||||||
self._selection = None
|
self._selection = None
|
||||||
self._dismiss_button = None
|
self._dismiss_button = None
|
||||||
self._actions_button = None
|
self._actions_box = None
|
||||||
|
self._actions_status_label = None
|
||||||
self._gui = self._create_dialog(title, column_headers, entries)
|
self._gui = self._create_dialog(title, column_headers, entries)
|
||||||
|
|
||||||
def _create_dialog(self, title, column_headers, entries):
|
def _create_dialog(self, title, column_headers, entries):
|
||||||
dialog = Gtk.Dialog(title, None, Gtk.DialogFlags.MODAL)
|
dialog = Gtk.Dialog(title, None, Gtk.DialogFlags.MODAL)
|
||||||
dialog.set_default_size(600, 400)
|
dialog.set_default_size(600, 400)
|
||||||
dialog.add_button(Gtk.STOCK_COPY, self.RESPONSE_COPY)
|
dialog.add_button(Gtk.STOCK_COPY, self.RESPONSE_COPY)
|
||||||
self._actions_button = dialog.add_button(
|
|
||||||
guilabels.NOTIFICATIONS_ACTIONS_BUTTON,
|
|
||||||
self.RESPONSE_ACTIONS,
|
|
||||||
)
|
|
||||||
self._dismiss_button = dialog.add_button(
|
self._dismiss_button = dialog.add_button(
|
||||||
guilabels.NOTIFICATIONS_DISMISS_BUTTON,
|
guilabels.NOTIFICATIONS_DISMISS_BUTTON,
|
||||||
self.RESPONSE_DISMISS,
|
self.RESPONSE_DISMISS,
|
||||||
@@ -451,10 +475,13 @@ class NotificationListGUI:
|
|||||||
dialog.set_default_response(Gtk.ResponseType.CLOSE)
|
dialog.set_default_response(Gtk.ResponseType.CLOSE)
|
||||||
|
|
||||||
grid = Gtk.Grid()
|
grid = Gtk.Grid()
|
||||||
|
grid.set_row_spacing(12)
|
||||||
content_area = dialog.get_content_area()
|
content_area = dialog.get_content_area()
|
||||||
content_area.add(grid)
|
content_area.add(grid)
|
||||||
|
|
||||||
scrolled_window = Gtk.ScrolledWindow()
|
scrolled_window = Gtk.ScrolledWindow()
|
||||||
|
scrolled_window.set_hexpand(True)
|
||||||
|
scrolled_window.set_vexpand(True)
|
||||||
grid.add(scrolled_window)
|
grid.add(scrolled_window)
|
||||||
|
|
||||||
tree = Gtk.TreeView()
|
tree = Gtk.TreeView()
|
||||||
@@ -492,6 +519,29 @@ class NotificationListGUI:
|
|||||||
selection.select_path(0)
|
selection.select_path(0)
|
||||||
self._selection = selection
|
self._selection = selection
|
||||||
self._tree = tree
|
self._tree = tree
|
||||||
|
|
||||||
|
actions_frame = Gtk.Frame(label=guilabels.NOTIFICATIONS_ACTIONS_TITLE)
|
||||||
|
actions_frame.set_label_align(0.0, 0.5)
|
||||||
|
actions_accessible = actions_frame.get_accessible()
|
||||||
|
if actions_accessible:
|
||||||
|
actions_accessible.set_name(guilabels.NOTIFICATIONS_ACTIONS_TITLE)
|
||||||
|
grid.attach_next_to(actions_frame, scrolled_window, Gtk.PositionType.BOTTOM, 1, 1)
|
||||||
|
|
||||||
|
actions_content = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||||
|
actions_content.set_margin_top(6)
|
||||||
|
actions_content.set_margin_bottom(6)
|
||||||
|
actions_content.set_margin_start(6)
|
||||||
|
actions_content.set_margin_end(6)
|
||||||
|
actions_frame.add(actions_content)
|
||||||
|
|
||||||
|
self._actions_status_label = Gtk.Label(xalign=0)
|
||||||
|
self._actions_status_label.set_line_wrap(True)
|
||||||
|
self._actions_status_label.set_no_show_all(True)
|
||||||
|
actions_content.pack_start(self._actions_status_label, False, False, 0)
|
||||||
|
|
||||||
|
self._actions_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||||
|
actions_content.pack_start(self._actions_box, False, False, 0)
|
||||||
|
|
||||||
dialog.connect("response", self.on_response)
|
dialog.connect("response", self.on_response)
|
||||||
dialog.connect("destroy", self._on_destroy)
|
dialog.connect("destroy", self._on_destroy)
|
||||||
self._update_action_buttons()
|
self._update_action_buttons()
|
||||||
@@ -508,10 +558,6 @@ class NotificationListGUI:
|
|||||||
self._copy_selected_notification()
|
self._copy_selected_notification()
|
||||||
return
|
return
|
||||||
|
|
||||||
if response == self.RESPONSE_ACTIONS:
|
|
||||||
self._show_selected_actions()
|
|
||||||
return
|
|
||||||
|
|
||||||
if response == self.RESPONSE_DISMISS:
|
if response == self.RESPONSE_DISMISS:
|
||||||
self._dismiss_selected_notification()
|
self._dismiss_selected_notification()
|
||||||
return
|
return
|
||||||
@@ -560,12 +606,70 @@ class NotificationListGUI:
|
|||||||
def _update_action_buttons(self) -> None:
|
def _update_action_buttons(self) -> None:
|
||||||
entry = self._get_selected_entry()
|
entry = self._get_selected_entry()
|
||||||
can_control = self._presenter.can_control_entry(entry)
|
can_control = self._presenter.can_control_entry(entry)
|
||||||
has_actions = bool(self._presenter.get_actions_for_entry(entry))
|
actions = self._presenter.get_actions_for_entry(entry)
|
||||||
|
|
||||||
if self._dismiss_button is not None:
|
if self._dismiss_button is not None:
|
||||||
self._dismiss_button.set_sensitive(can_control)
|
self._dismiss_button.set_sensitive(can_control)
|
||||||
if self._actions_button is not None:
|
|
||||||
self._actions_button.set_sensitive(can_control and has_actions)
|
self._clear_inline_action_buttons()
|
||||||
|
|
||||||
|
if not can_control:
|
||||||
|
self._set_inline_action_status(messages.NOTIFICATION_UNAVAILABLE)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not actions:
|
||||||
|
self._set_inline_action_status(messages.NOTIFICATION_NO_ACTIONS)
|
||||||
|
return
|
||||||
|
|
||||||
|
self._set_inline_action_status("")
|
||||||
|
for action_key, label_text in actions.items():
|
||||||
|
self._add_inline_action_button(entry, action_key, label_text)
|
||||||
|
|
||||||
|
def _clear_inline_action_buttons(self) -> None:
|
||||||
|
if self._actions_box is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
for child in self._actions_box.get_children():
|
||||||
|
self._actions_box.remove(child)
|
||||||
|
|
||||||
|
def _set_inline_action_status(self, text: str) -> None:
|
||||||
|
if self._actions_status_label is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._actions_status_label.set_text(text)
|
||||||
|
self._actions_status_label.set_visible(bool(text))
|
||||||
|
|
||||||
|
def _add_inline_action_button(
|
||||||
|
self,
|
||||||
|
entry: Optional[NotificationEntry],
|
||||||
|
action_key: str,
|
||||||
|
label_text: str,
|
||||||
|
) -> None:
|
||||||
|
if self._actions_box is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
button = Gtk.Button(label=label_text or action_key)
|
||||||
|
button.connect("clicked", self._on_inline_action_clicked, entry, action_key)
|
||||||
|
self._actions_box.pack_start(button, False, False, 0)
|
||||||
|
self._actions_box.show_all()
|
||||||
|
|
||||||
|
def _on_inline_action_clicked(
|
||||||
|
self,
|
||||||
|
button: Gtk.Button,
|
||||||
|
entry: Optional[NotificationEntry],
|
||||||
|
action_key: str,
|
||||||
|
) -> None:
|
||||||
|
del button
|
||||||
|
self._presenter.refresh_live_notifications()
|
||||||
|
if not self._presenter.can_control_entry(entry):
|
||||||
|
self._script.presentMessage(messages.NOTIFICATION_UNAVAILABLE)
|
||||||
|
self._update_action_buttons()
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._presenter.invoke_action_for_entry(self._script, entry, action_key):
|
||||||
|
self._script.presentMessage(messages.NOTIFICATION_UNAVAILABLE)
|
||||||
|
|
||||||
|
self._update_action_buttons()
|
||||||
|
|
||||||
def _copy_selected_notification(self):
|
def _copy_selected_notification(self):
|
||||||
entry = self._get_selected_entry()
|
entry = self._get_selected_entry()
|
||||||
@@ -593,154 +697,50 @@ class NotificationListGUI:
|
|||||||
|
|
||||||
if not self._presenter.dismiss_entry(self._script, entry):
|
if not self._presenter.dismiss_entry(self._script, entry):
|
||||||
self._script.presentMessage(messages.NOTIFICATION_UNAVAILABLE)
|
self._script.presentMessage(messages.NOTIFICATION_UNAVAILABLE)
|
||||||
|
else:
|
||||||
|
self._remove_selected_row()
|
||||||
|
|
||||||
self._update_action_buttons()
|
self._update_action_buttons()
|
||||||
|
|
||||||
def _show_selected_actions(self) -> None:
|
def _remove_selected_row(self) -> None:
|
||||||
self._presenter.refresh_live_notifications()
|
if self._selection is None or self._model is None:
|
||||||
entry = self._get_selected_entry()
|
|
||||||
actions = self._presenter.get_actions_for_entry(entry)
|
|
||||||
if not self._presenter.can_control_entry(entry):
|
|
||||||
self._script.presentMessage(messages.NOTIFICATION_UNAVAILABLE)
|
|
||||||
self._update_action_buttons()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if not actions:
|
model, paths = self._selection.get_selected_rows()
|
||||||
self._script.presentMessage(messages.NOTIFICATION_NO_ACTIONS)
|
if not paths:
|
||||||
self._update_action_buttons()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
dialog = NotificationActionsGUI(
|
row_iter = model.get_iter(paths[0])
|
||||||
self._gui,
|
if row_iter is None:
|
||||||
self._script,
|
|
||||||
self._presenter,
|
|
||||||
entry,
|
|
||||||
actions,
|
|
||||||
)
|
|
||||||
dialog.show_gui()
|
|
||||||
|
|
||||||
|
|
||||||
class NotificationActionsGUI:
|
|
||||||
"""Dialog listing live mako actions for a notification."""
|
|
||||||
|
|
||||||
RESPONSE_INVOKE = 1
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
parent: Gtk.Dialog,
|
|
||||||
script: Any,
|
|
||||||
presenter: NotificationPresenter,
|
|
||||||
entry: NotificationEntry,
|
|
||||||
actions: Dict[str, str],
|
|
||||||
) -> None:
|
|
||||||
self._parent = parent
|
|
||||||
self._script = script
|
|
||||||
self._presenter = presenter
|
|
||||||
self._entry = entry
|
|
||||||
self._actions = dict(actions)
|
|
||||||
self._list_box = None
|
|
||||||
self._invoke_button = None
|
|
||||||
self._dialog = self._create_dialog()
|
|
||||||
|
|
||||||
def _create_dialog(self):
|
|
||||||
dialog = Gtk.Dialog(
|
|
||||||
guilabels.NOTIFICATIONS_ACTIONS_TITLE,
|
|
||||||
self._parent,
|
|
||||||
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
|
|
||||||
)
|
|
||||||
dialog.set_default_size(420, 260)
|
|
||||||
self._invoke_button = dialog.add_button(
|
|
||||||
guilabels.NOTIFICATIONS_INVOKE_ACTION_BUTTON,
|
|
||||||
self.RESPONSE_INVOKE,
|
|
||||||
)
|
|
||||||
dialog.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
|
|
||||||
|
|
||||||
content_area = dialog.get_content_area()
|
|
||||||
scrolled_window = Gtk.ScrolledWindow()
|
|
||||||
scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
|
||||||
content_area.add(scrolled_window)
|
|
||||||
|
|
||||||
self._list_box = Gtk.ListBox()
|
|
||||||
self._list_box.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
|
||||||
self._list_box.connect("row-activated", self._on_row_activated)
|
|
||||||
self._list_box.connect("selected-rows-changed", self._on_selection_changed)
|
|
||||||
list_accessible = self._list_box.get_accessible()
|
|
||||||
if list_accessible:
|
|
||||||
list_accessible.set_name(guilabels.NOTIFICATIONS_ACTIONS_TITLE)
|
|
||||||
scrolled_window.add(self._list_box)
|
|
||||||
|
|
||||||
for action_key, label_text in self._actions.items():
|
|
||||||
row = Gtk.ListBoxRow()
|
|
||||||
row._action_key = action_key # type: ignore[attr-defined]
|
|
||||||
label = Gtk.Label(label=label_text or action_key, xalign=0)
|
|
||||||
label.set_margin_start(10)
|
|
||||||
label.set_margin_end(10)
|
|
||||||
label.set_margin_top(6)
|
|
||||||
label.set_margin_bottom(6)
|
|
||||||
row.add(label)
|
|
||||||
self._list_box.add(row)
|
|
||||||
|
|
||||||
first_row = self._list_box.get_row_at_index(0)
|
|
||||||
if first_row is not None:
|
|
||||||
self._list_box.select_row(first_row)
|
|
||||||
|
|
||||||
dialog.connect("response", self._on_response)
|
|
||||||
self._update_invoke_button()
|
|
||||||
return dialog
|
|
||||||
|
|
||||||
def show_gui(self) -> None:
|
|
||||||
self._dialog.show_all()
|
|
||||||
time_stamp = Gtk.get_current_event_time()
|
|
||||||
if not time_stamp or time_stamp > 0xFFFFFFFF:
|
|
||||||
time_stamp = Gdk.CURRENT_TIME
|
|
||||||
self._dialog.present_with_time(int(time_stamp))
|
|
||||||
|
|
||||||
def _on_response(self, dialog, response) -> None:
|
|
||||||
if response == Gtk.ResponseType.CLOSE:
|
|
||||||
self._dialog.destroy()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if response == self.RESPONSE_INVOKE:
|
model.remove(row_iter)
|
||||||
self._invoke_selected_action()
|
if self._model.iter_n_children(None) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
def _on_row_activated(self, list_box, row) -> None:
|
row_index = self._path_to_index(paths[0])
|
||||||
self._invoke_selected_action()
|
if row_index is None:
|
||||||
|
self._selection.select_path(0)
|
||||||
|
return
|
||||||
|
|
||||||
def _on_selection_changed(self, list_box) -> None:
|
next_index = min(row_index, self._model.iter_n_children(None) - 1)
|
||||||
self._update_invoke_button()
|
self._selection.select_path(next_index)
|
||||||
|
|
||||||
def _get_selected_action_key(self) -> Optional[str]:
|
def _path_to_index(self, path: Any) -> Optional[int]:
|
||||||
if self._list_box is None:
|
if isinstance(path, int):
|
||||||
|
return path
|
||||||
|
|
||||||
|
get_indices = getattr(path, "get_indices", None)
|
||||||
|
if callable(get_indices):
|
||||||
|
indices = get_indices()
|
||||||
|
if indices:
|
||||||
|
return indices[0]
|
||||||
|
|
||||||
|
try:
|
||||||
|
return path[0]
|
||||||
|
except (TypeError, IndexError, KeyError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
row = self._list_box.get_selected_row()
|
|
||||||
if row is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return getattr(row, "_action_key", None)
|
|
||||||
|
|
||||||
def _update_invoke_button(self) -> None:
|
|
||||||
if self._invoke_button is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._invoke_button.set_sensitive(self._get_selected_action_key() is not None)
|
|
||||||
|
|
||||||
def _invoke_selected_action(self) -> None:
|
|
||||||
self._presenter.refresh_live_notifications()
|
|
||||||
action_key = self._get_selected_action_key()
|
|
||||||
if not action_key:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not self._presenter.can_control_entry(self._entry):
|
|
||||||
self._script.presentMessage(messages.NOTIFICATION_UNAVAILABLE)
|
|
||||||
self._dialog.destroy()
|
|
||||||
return
|
|
||||||
|
|
||||||
if not self._presenter.invoke_action_for_entry(self._script, self._entry, action_key):
|
|
||||||
self._script.presentMessage(messages.NOTIFICATION_UNAVAILABLE)
|
|
||||||
|
|
||||||
self._dialog.destroy()
|
|
||||||
|
|
||||||
|
|
||||||
_presenter = None
|
_presenter = None
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from unittest import mock
|
|||||||
|
|
||||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||||
|
|
||||||
|
from cthulhu import notification_presenter
|
||||||
from cthulhu import messages
|
from cthulhu import messages
|
||||||
from cthulhu.notification_presenter import NotificationPresenter
|
from cthulhu.notification_presenter import NotificationPresenter
|
||||||
|
|
||||||
@@ -30,6 +31,91 @@ class _FakeMonitor:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeButton:
|
||||||
|
def __init__(self, label=None):
|
||||||
|
self.label = label
|
||||||
|
self.sensitive = True
|
||||||
|
self._signals = {}
|
||||||
|
|
||||||
|
def connect(self, signal_name, callback, *args):
|
||||||
|
self._signals[signal_name] = (callback, args)
|
||||||
|
|
||||||
|
def set_sensitive(self, value):
|
||||||
|
self.sensitive = value
|
||||||
|
|
||||||
|
def set_margin_top(self, _value):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def click(self):
|
||||||
|
callback, args = self._signals["clicked"]
|
||||||
|
callback(self, *args)
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeBox:
|
||||||
|
def __init__(self):
|
||||||
|
self.children = []
|
||||||
|
|
||||||
|
def get_children(self):
|
||||||
|
return list(self.children)
|
||||||
|
|
||||||
|
def remove(self, child):
|
||||||
|
self.children.remove(child)
|
||||||
|
|
||||||
|
def pack_start(self, child, expand, fill, padding):
|
||||||
|
del expand, fill, padding
|
||||||
|
self.children.append(child)
|
||||||
|
|
||||||
|
def show_all(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeLabel:
|
||||||
|
def __init__(self):
|
||||||
|
self.text = ""
|
||||||
|
self.visible = False
|
||||||
|
|
||||||
|
def set_text(self, value):
|
||||||
|
self.text = value
|
||||||
|
|
||||||
|
def set_visible(self, value):
|
||||||
|
self.visible = value
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeListStore:
|
||||||
|
def __init__(self, rows=None):
|
||||||
|
self.rows = list(rows or [])
|
||||||
|
|
||||||
|
def iter_n_children(self, _parent):
|
||||||
|
return len(self.rows)
|
||||||
|
|
||||||
|
def get_iter(self, path):
|
||||||
|
index = path if isinstance(path, int) else path[0]
|
||||||
|
if 0 <= index < len(self.rows):
|
||||||
|
return index
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_value(self, row_iter, column):
|
||||||
|
return self.rows[row_iter][column]
|
||||||
|
|
||||||
|
def remove(self, row_iter):
|
||||||
|
del self.rows[row_iter]
|
||||||
|
return row_iter < len(self.rows)
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeSelection:
|
||||||
|
def __init__(self, model=None, selected_path=0):
|
||||||
|
self.model = model
|
||||||
|
self.selected_path = selected_path
|
||||||
|
|
||||||
|
def get_selected_rows(self):
|
||||||
|
if self.selected_path is None:
|
||||||
|
return self.model, []
|
||||||
|
return self.model, [self.selected_path]
|
||||||
|
|
||||||
|
def select_path(self, path):
|
||||||
|
self.selected_path = path
|
||||||
|
|
||||||
|
|
||||||
class NotificationPresenterMakoTests(unittest.TestCase):
|
class NotificationPresenterMakoTests(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.presenter = NotificationPresenter()
|
self.presenter = NotificationPresenter()
|
||||||
@@ -112,13 +198,212 @@ class NotificationPresenterMakoTests(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertTrue(self.presenter.dismiss_entry(script, entry))
|
self.assertTrue(self.presenter.dismiss_entry(script, entry))
|
||||||
self.assertEqual(self.monitor.dismissed, [6])
|
self.assertEqual(self.monitor.dismissed, [6])
|
||||||
|
self.assertEqual(self.presenter._notifications, [])
|
||||||
script.presentMessage.assert_called_with(messages.NOTIFICATION_DISMISSED)
|
script.presentMessage.assert_called_with(messages.NOTIFICATION_DISMISSED)
|
||||||
|
|
||||||
script.reset_mock()
|
script.reset_mock()
|
||||||
|
entry = self.presenter.save_notification(
|
||||||
|
"Notification current",
|
||||||
|
source="mako",
|
||||||
|
source_generation=2,
|
||||||
|
notification_id=6,
|
||||||
|
live=True,
|
||||||
|
actions={"default": "View"},
|
||||||
|
)
|
||||||
self.assertTrue(self.presenter.invoke_action_for_entry(script, entry, "default"))
|
self.assertTrue(self.presenter.invoke_action_for_entry(script, entry, "default"))
|
||||||
self.assertEqual(self.monitor.invoked, [(6, "default")])
|
self.assertEqual(self.monitor.invoked, [(6, "default")])
|
||||||
script.presentMessage.assert_called_with(messages.NOTIFICATION_ACTION_INVOKED)
|
script.presentMessage.assert_called_with(messages.NOTIFICATION_ACTION_INVOKED)
|
||||||
|
|
||||||
|
def test_notification_list_dismiss_removes_selected_entry_from_history_and_model(self):
|
||||||
|
entry = self.presenter.save_notification(
|
||||||
|
"Notification current",
|
||||||
|
source="mako",
|
||||||
|
source_generation=2,
|
||||||
|
notification_id=6,
|
||||||
|
live=True,
|
||||||
|
actions={"default": "View"},
|
||||||
|
)
|
||||||
|
remaining = self.presenter.save_notification(
|
||||||
|
"Notification stale",
|
||||||
|
source="mako",
|
||||||
|
source_generation=1,
|
||||||
|
notification_id=7,
|
||||||
|
live=False,
|
||||||
|
actions={},
|
||||||
|
)
|
||||||
|
|
||||||
|
gui = notification_presenter.NotificationListGUI.__new__(
|
||||||
|
notification_presenter.NotificationListGUI
|
||||||
|
)
|
||||||
|
gui._script = mock.Mock()
|
||||||
|
gui._presenter = self.presenter
|
||||||
|
gui._dismiss_button = _FakeButton()
|
||||||
|
gui._actions_box = _FakeBox()
|
||||||
|
gui._actions_status_label = _FakeLabel()
|
||||||
|
gui._model = _FakeListStore(
|
||||||
|
[
|
||||||
|
[entry.message, "now", entry],
|
||||||
|
[remaining.message, "later", remaining],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
gui._selection = _FakeSelection(model=gui._model, selected_path=0)
|
||||||
|
gui._update_action_buttons = mock.Mock()
|
||||||
|
|
||||||
|
gui._dismiss_selected_notification()
|
||||||
|
|
||||||
|
self.assertEqual(self.monitor.dismissed, [6])
|
||||||
|
self.assertEqual(self.presenter._notifications, [remaining])
|
||||||
|
self.assertEqual(gui._model.rows, [[remaining.message, "later", remaining]])
|
||||||
|
gui._script.presentMessage.assert_called_with(messages.NOTIFICATION_DISMISSED)
|
||||||
|
|
||||||
|
def test_notification_list_dismiss_reselects_remaining_row_after_removing_last_selection(self):
|
||||||
|
remaining = self.presenter.save_notification(
|
||||||
|
"Notification stale",
|
||||||
|
source="mako",
|
||||||
|
source_generation=1,
|
||||||
|
notification_id=7,
|
||||||
|
live=False,
|
||||||
|
actions={},
|
||||||
|
)
|
||||||
|
entry = self.presenter.save_notification(
|
||||||
|
"Notification current",
|
||||||
|
source="mako",
|
||||||
|
source_generation=2,
|
||||||
|
notification_id=6,
|
||||||
|
live=True,
|
||||||
|
actions={"default": "View"},
|
||||||
|
)
|
||||||
|
|
||||||
|
gui = notification_presenter.NotificationListGUI.__new__(
|
||||||
|
notification_presenter.NotificationListGUI
|
||||||
|
)
|
||||||
|
gui._script = mock.Mock()
|
||||||
|
gui._presenter = self.presenter
|
||||||
|
gui._dismiss_button = _FakeButton()
|
||||||
|
gui._actions_box = _FakeBox()
|
||||||
|
gui._actions_status_label = _FakeLabel()
|
||||||
|
gui._model = _FakeListStore(
|
||||||
|
[
|
||||||
|
[remaining.message, "later", remaining],
|
||||||
|
[entry.message, "now", entry],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
gui._selection = _FakeSelection(model=gui._model, selected_path=1)
|
||||||
|
gui._update_action_buttons = mock.Mock()
|
||||||
|
|
||||||
|
gui._dismiss_selected_notification()
|
||||||
|
|
||||||
|
self.assertEqual(gui._model.rows, [[remaining.message, "later", remaining]])
|
||||||
|
self.assertEqual(gui._selection.selected_path, 0)
|
||||||
|
|
||||||
|
def test_notification_list_builds_inline_action_buttons_in_reported_order(self):
|
||||||
|
entry = self.presenter.save_notification(
|
||||||
|
"Notification current",
|
||||||
|
source="mako",
|
||||||
|
source_generation=2,
|
||||||
|
notification_id=6,
|
||||||
|
live=True,
|
||||||
|
actions={"default": "View", "snooze": "Snooze"},
|
||||||
|
)
|
||||||
|
|
||||||
|
gui = notification_presenter.NotificationListGUI.__new__(
|
||||||
|
notification_presenter.NotificationListGUI
|
||||||
|
)
|
||||||
|
gui._script = mock.Mock()
|
||||||
|
gui._presenter = mock.Mock()
|
||||||
|
gui._presenter.can_control_entry.return_value = True
|
||||||
|
gui._presenter.get_actions_for_entry.return_value = {
|
||||||
|
"default": "View",
|
||||||
|
"snooze": "Snooze",
|
||||||
|
}
|
||||||
|
gui._presenter.invoke_action_for_entry.return_value = True
|
||||||
|
gui._dismiss_button = _FakeButton()
|
||||||
|
gui._actions_box = _FakeBox()
|
||||||
|
gui._actions_status_label = _FakeLabel()
|
||||||
|
gui._get_selected_entry = mock.Mock(return_value=entry)
|
||||||
|
|
||||||
|
with mock.patch.object(notification_presenter.Gtk, "Button", _FakeButton):
|
||||||
|
gui._update_action_buttons()
|
||||||
|
self.assertTrue(gui._dismiss_button.sensitive)
|
||||||
|
self.assertEqual(
|
||||||
|
[button.label for button in gui._actions_box.children],
|
||||||
|
["View", "Snooze"],
|
||||||
|
)
|
||||||
|
gui._actions_box.children[1].click()
|
||||||
|
|
||||||
|
gui._presenter.invoke_action_for_entry.assert_called_once_with(
|
||||||
|
gui._script,
|
||||||
|
entry,
|
||||||
|
"snooze",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_notification_list_clears_inline_actions_when_notification_is_unavailable(self):
|
||||||
|
entry = self.presenter.save_notification(
|
||||||
|
"Notification stale",
|
||||||
|
source="mako",
|
||||||
|
source_generation=1,
|
||||||
|
notification_id=7,
|
||||||
|
live=True,
|
||||||
|
actions={"default": "Open"},
|
||||||
|
)
|
||||||
|
|
||||||
|
gui = notification_presenter.NotificationListGUI.__new__(
|
||||||
|
notification_presenter.NotificationListGUI
|
||||||
|
)
|
||||||
|
gui._script = mock.Mock()
|
||||||
|
gui._presenter = mock.Mock()
|
||||||
|
gui._presenter.can_control_entry.return_value = False
|
||||||
|
gui._presenter.get_actions_for_entry.return_value = {}
|
||||||
|
gui._dismiss_button = _FakeButton()
|
||||||
|
gui._actions_box = _FakeBox()
|
||||||
|
gui._actions_box.children.append(_FakeButton("Old"))
|
||||||
|
gui._actions_status_label = _FakeLabel()
|
||||||
|
gui._get_selected_entry = mock.Mock(return_value=entry)
|
||||||
|
|
||||||
|
with mock.patch.object(notification_presenter.Gtk, "Button", _FakeButton):
|
||||||
|
gui._update_action_buttons()
|
||||||
|
|
||||||
|
self.assertFalse(gui._dismiss_button.sensitive)
|
||||||
|
self.assertEqual(gui._actions_box.children, [])
|
||||||
|
self.assertEqual(
|
||||||
|
gui._actions_status_label.text,
|
||||||
|
messages.NOTIFICATION_UNAVAILABLE,
|
||||||
|
)
|
||||||
|
self.assertTrue(gui._actions_status_label.visible)
|
||||||
|
|
||||||
|
def test_notification_list_shows_no_actions_state_for_live_notification_without_actions(self):
|
||||||
|
entry = self.presenter.save_notification(
|
||||||
|
"Notification current",
|
||||||
|
source="mako",
|
||||||
|
source_generation=2,
|
||||||
|
notification_id=8,
|
||||||
|
live=True,
|
||||||
|
actions={},
|
||||||
|
)
|
||||||
|
|
||||||
|
gui = notification_presenter.NotificationListGUI.__new__(
|
||||||
|
notification_presenter.NotificationListGUI
|
||||||
|
)
|
||||||
|
gui._script = mock.Mock()
|
||||||
|
gui._presenter = mock.Mock()
|
||||||
|
gui._presenter.can_control_entry.return_value = True
|
||||||
|
gui._presenter.get_actions_for_entry.return_value = {}
|
||||||
|
gui._dismiss_button = _FakeButton()
|
||||||
|
gui._actions_box = _FakeBox()
|
||||||
|
gui._actions_status_label = _FakeLabel()
|
||||||
|
gui._get_selected_entry = mock.Mock(return_value=entry)
|
||||||
|
|
||||||
|
with mock.patch.object(notification_presenter.Gtk, "Button", _FakeButton):
|
||||||
|
gui._update_action_buttons()
|
||||||
|
|
||||||
|
self.assertTrue(gui._dismiss_button.sensitive)
|
||||||
|
self.assertEqual(gui._actions_box.children, [])
|
||||||
|
self.assertEqual(
|
||||||
|
gui._actions_status_label.text,
|
||||||
|
messages.NOTIFICATION_NO_ACTIONS,
|
||||||
|
)
|
||||||
|
self.assertTrue(gui._actions_status_label.visible)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user