From 299d3334db4b438c63f6606691ccea5c18bfa47d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corentin=20No=C3=ABl?= Date: Tue, 3 Nov 2020 00:38:50 +0100 Subject: [PATCH] Use a GLib.ListStore to handle the notifications. --- src/Indicator.vala | 128 ++++++++++++++++++++--------- src/Services/Notification.vala | 4 + src/Services/Session.vala | 19 ++--- src/Widgets/AppEntry.vala | 43 ++-------- src/Widgets/NotificationEntry.vala | 12 +-- src/Widgets/NotificationsList.vala | 118 +++++++++----------------- 6 files changed, 151 insertions(+), 173 deletions(-) diff --git a/src/Indicator.vala b/src/Indicator.vala index 616c7c57..27450c13 100644 --- a/src/Indicator.vala +++ b/src/Indicator.vala @@ -23,26 +23,18 @@ public class Notifications.Indicator : Wingpanel.Indicator { private Gtk.Spinner? dynamic_icon = null; private Gtk.Grid? main_box = null; - private Gtk.ModelButton clear_all_btn; - private Wingpanel.Widgets.Switch not_disturb_switch; - - private NotificationsList nlist; + private Notifications.Session session; + private GLib.ListStore notifications; + private GLib.HashTable app_datetime; private Gee.HashMap app_settings_cache; - - public static GLib.Settings notify_settings; + private GLib.Settings notify_settings; public Indicator () { - Object (code_name: Wingpanel.Indicator.MESSAGES); - - visible = true; + Object (code_name: Wingpanel.Indicator.MESSAGES, visible: true); } construct { - app_settings_cache = new Gee.HashMap (); - } - - static construct { if (GLib.SettingsSchemaSource.get_default ().lookup ("io.elementary.notifications", true) != null) { debug ("Using io.elementary.notifications server"); notify_settings = new GLib.Settings ("io.elementary.notifications"); @@ -50,6 +42,10 @@ public class Notifications.Indicator : Wingpanel.Indicator { debug ("Using notifications in gala"); notify_settings = new GLib.Settings ("org.pantheon.desktop.gala.notifications"); } + + app_datetime = new GLib.HashTable (str_hash, str_equal); + app_settings_cache = new Gee.HashMap (); + notifications = new GLib.ListStore (typeof (Notifications.Notification)); } public override Gtk.Widget get_display_widget () { @@ -57,12 +53,17 @@ public class Notifications.Indicator : Wingpanel.Indicator { dynamic_icon = new Gtk.Spinner (); dynamic_icon.active = true; - nlist = new NotificationsList (); + session = new Notifications.Session (); + var previous_session = session.get_session_notifications (); + foreach (unowned Notification notification in previous_session) { + unowned GLib.DateTime? time = app_datetime[notification.desktop_id]; + if (time == null || time.compare (notification.timestamp) <= 0) { + app_datetime[notification.desktop_id] = notification.timestamp; + } + } - var previous_session = Session.get_instance ().get_session_notifications (); - previous_session.foreach ((notification) => { - nlist.add_entry (notification); - }); + notifications.splice (0, 0, (Object[]) previous_session); + notifications.sort ((GLib.CompareDataFunc) sort_notification); var provider = new Gtk.CssProvider (); provider.load_from_resource ("io/elementary/wingpanel/notifications/indicator.css"); @@ -88,8 +89,9 @@ public class Notifications.Indicator : Wingpanel.Indicator { set_display_icon_name (); }); - nlist.add.connect (set_display_icon_name); - nlist.remove.connect (set_display_icon_name); + notifications.items_changed.connect ((position, removed, added) => { + set_display_icon_name (); + }); set_display_icon_name (); } @@ -99,16 +101,18 @@ public class Notifications.Indicator : Wingpanel.Indicator { public override Gtk.Widget? get_widget () { if (main_box == null) { + var nlist = new NotificationsList (notifications); + var scrolled = new Gtk.ScrolledWindow (null, null); scrolled.hscrollbar_policy = Gtk.PolicyType.NEVER; scrolled.max_content_height = 500; scrolled.propagate_natural_height = true; scrolled.add (nlist); - not_disturb_switch = new Wingpanel.Widgets.Switch (_("Do Not Disturb")); + var not_disturb_switch = new Wingpanel.Widgets.Switch (_("Do Not Disturb")); not_disturb_switch.get_style_context ().add_class (Granite.STYLE_CLASS_H4_LABEL); - clear_all_btn = new Gtk.ModelButton (); + var clear_all_btn = new Gtk.ModelButton (); clear_all_btn.text = _("Clear All Notifications"); var settings_btn = new Gtk.ModelButton (); @@ -127,13 +131,40 @@ public class Notifications.Indicator : Wingpanel.Indicator { notify_settings.bind ("do-not-disturb", not_disturb_switch, "active", GLib.SettingsBindFlags.DEFAULT); + nlist.clear_app.connect ((app_id) => { + nlist.get_children ().foreach ((child) => { + if (child is NotificationEntry) { + unowned NotificationEntry entry = (NotificationEntry) child; + if (entry.notification.desktop_id == app_id) { + entry.dismiss (); + } + } + }); + }); + + nlist.remove_notification.connect ((notification) => { + uint position; + if (notifications.find (notification, out position)) { + session.remove_notification (notification); + notifications.remove (position); + } else { + warning ("Notification not found!"); + } + }); + nlist.close_popover.connect (() => close ()); - nlist.add.connect (update_clear_all_sensitivity); - nlist.remove.connect (update_clear_all_sensitivity); clear_all_btn.clicked.connect (() => { - nlist.clear_all (); - Session.get_instance ().clear (); + session.clear (); + nlist.get_children ().foreach ((child) => { + if (child is NotificationEntry) { + ((NotificationEntry) child).dismiss (); + } + }); + }); + + notifications.items_changed.connect ((position, removed, added) => { + clear_all_btn.sensitive = notifications.get_n_items () > 0; }); settings_btn.clicked.connect (show_settings); @@ -143,7 +174,6 @@ public class Notifications.Indicator : Wingpanel.Indicator { } public override void opened () { - update_clear_all_sensitivity (); } public override void closed () { @@ -167,32 +197,50 @@ public class Notifications.Indicator : Wingpanel.Indicator { } if (app_settings == null || app_settings.get_boolean (REMEMBER_KEY)) { - nlist.add_entry (notification); - } + unowned GLib.DateTime? time = app_datetime[notification.desktop_id]; + if (time == null || time.compare (notification.timestamp) <= 0) { + app_datetime[notification.desktop_id] = notification.timestamp; + } - set_display_icon_name (); + notifications.insert_sorted (notification, (GLib.CompareDataFunc) sort_notification); + session.add_notification (notification); + } } - private void update_clear_all_sensitivity () { - clear_all_btn.sensitive = nlist.app_entries.size > 0; + [CCode (instance_pos = -1)] + private int sort_notification (Notification a, Notification b) { + if (a.desktop_id == b.desktop_id) { + return Notification.compare (a, b); + } else { + unowned GLib.DateTime? time_a = app_datetime[a.desktop_id]; + unowned GLib.DateTime? time_b = app_datetime[b.desktop_id]; + if (time_a != null && time_b != null) { + return time_a.compare (time_b); + } else if (time_a != null) { + return -1; + } else if (time_b != null) { + return 1; + } else { + return 0; + } + } } private void on_notification_closed (uint32 id) { - foreach (var app_entry in nlist.app_entries.values) { - foreach (var item in app_entry.app_notifications) { - if (item.notification.id == id) { - item.notification.close (); - return; - } + for (int i = 0; i < notifications.get_n_items (); i++) { + var notification = (Notification) notifications.get_item (i); + if (notification.id == id) { + notification.close (); + return; } } } private void set_display_icon_name () { - var dynamic_icon_style_context = dynamic_icon.get_style_context (); + unowned Gtk.StyleContext dynamic_icon_style_context = dynamic_icon.get_style_context (); if (notify_settings.get_boolean ("do-not-disturb")) { dynamic_icon_style_context.add_class ("disabled"); - } else if (nlist != null && nlist.app_entries.size > 0) { + } else if (notifications.get_n_items () > 0) { dynamic_icon_style_context.remove_class ("disabled"); dynamic_icon_style_context.add_class ("new"); } else { diff --git a/src/Services/Notification.vala b/src/Services/Notification.vala index 4fbe8e41..ef2f398e 100644 --- a/src/Services/Notification.vala +++ b/src/Services/Notification.vala @@ -143,6 +143,10 @@ public class Notifications.Notification : Object { return false; } + public static int compare (Notification self, Notification other) { + return self.timestamp.compare (other.timestamp); + } + private string get_string (Variant tuple, int column) { var child = tuple.get_child_value (column); return child.dup_string (); diff --git a/src/Services/Session.vala b/src/Services/Session.vala index 27e396bd..99c5e634 100644 --- a/src/Services/Session.vala +++ b/src/Services/Session.vala @@ -23,7 +23,6 @@ */ public class Notifications.Session : GLib.Object { private const string SESSION_FILE_NAME = ".notifications.session"; - private static Session? instance = null; private File session_file = null; @@ -39,15 +38,7 @@ public class Notifications.Session : GLib.Object { private KeyFile key; - public static Session get_instance () { - if (instance == null) { - instance = new Session (); - } - - return instance; - } - - private Session () { + public Session () { session_file = File.new_for_path (Path.build_filename (Environment.get_user_cache_dir (), SESSION_FILE_NAME)); if (!session_file.query_exists ()) { create_session_file (); @@ -57,8 +48,8 @@ public class Notifications.Session : GLib.Object { key.set_list_separator (';'); } - public List get_session_notifications () { - var list = new List (); + public Notification[] get_session_notifications () { + Notification[] list = {}; try { key.load_from_file (session_file.get_path (), KeyFileFlags.NONE); foreach (unowned string group in key.get_groups ()) { @@ -72,7 +63,7 @@ public class Notifications.Session : GLib.Object { key.get_int64 (group, UNIX_TIME_KEY), key.get_uint64 (group, REPLACES_ID_KEY), key.get_string (group, SENDER_KEY)); - list.append (notification); + list += notification; } } catch (KeyFileError e) { warning (e.message); @@ -102,7 +93,7 @@ public class Notifications.Session : GLib.Object { try { key.remove_group (notification.id.to_string ()); } catch (KeyFileError e) { - warning (e.message); + return; } write_contents (); diff --git a/src/Widgets/AppEntry.vala b/src/Widgets/AppEntry.vala index b059db0e..8322c0d0 100644 --- a/src/Widgets/AppEntry.vala +++ b/src/Widgets/AppEntry.vala @@ -15,30 +15,23 @@ * along with this program. If not, see . */ -public class Notifications.AppEntry : Gtk.ListBoxRow { +public class Notifications.AppEntry : Gtk.Grid { public signal void clear (); - public NotificationEntry entry { get; construct; } public string app_id { get; private set; } - public AppInfo? app_info = null; - public List app_notifications; + public AppInfo? app_info { get; construct; } - public AppEntry (NotificationEntry entry) { - Object (entry: entry); + public AppEntry (AppInfo? app_info) { + Object (app_info: app_info); } construct { margin = 12; margin_bottom = 3; margin_top = 6; + column_spacing = 6; - app_notifications = new List (); - add_notification_entry (entry); - - var notification = entry.notification; - app_info = notification.app_info; - - string name; + unowned string name; if (app_info != null) { app_id = app_info.get_id (); name = app_info.get_name (); @@ -57,31 +50,11 @@ public class Notifications.AppEntry : Gtk.ListBoxRow { }; clear_btn_entry.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); - var grid = new Gtk.Grid (); - grid.column_spacing = 6; - grid.add (label); - grid.add (clear_btn_entry); - - add (grid); - show_all (); + add (label); + add (clear_btn_entry); clear_btn_entry.clicked.connect (() => { clear (); }); } - - public void add_notification_entry (NotificationEntry entry) { - app_notifications.prepend (entry); - entry.clear.connect (remove_notification_entry); - } - - public async void remove_notification_entry (NotificationEntry entry) { - app_notifications.remove (entry); - entry.dismiss (); - - Session.get_instance ().remove_notification (entry.notification); - if (app_notifications.length () == 0) { - clear (); - } - } } diff --git a/src/Widgets/NotificationEntry.vala b/src/Widgets/NotificationEntry.vala index 08ed2474..b5dbb6f8 100644 --- a/src/Widgets/NotificationEntry.vala +++ b/src/Widgets/NotificationEntry.vala @@ -16,7 +16,7 @@ */ public class Notifications.NotificationEntry : Gtk.ListBoxRow { - public signal void clear (); + public signal void removed (); public bool active = true; public Notification notification { get; construct; } @@ -179,7 +179,7 @@ public class Notifications.NotificationEntry : Gtk.ListBoxRow { show_all (); delete_button.clicked.connect (() => { - clear (); + dismiss (); }); eventbox.enter_notify_event.connect ((event) => { @@ -198,17 +198,17 @@ public class Notifications.NotificationEntry : Gtk.ListBoxRow { return active; }); - notification.closed.connect (() => clear ()); + notification.closed.connect (() => dismiss ()); deck.notify["visible-child"].connect (() => { if (deck.transition_running == false && deck.visible_child != overlay) { - clear (); + dismiss (); } }); deck.notify["transition-running"].connect (() => { if (deck.transition_running == false && deck.visible_child != overlay) { - clear (); + dismiss (); } }); } @@ -218,7 +218,7 @@ public class Notifications.NotificationEntry : Gtk.ListBoxRow { revealer.notify["child-revealed"].connect (() => { if (!revealer.child_revealed) { - destroy (); + removed (); } }); revealer.reveal_child = false; diff --git a/src/Widgets/NotificationsList.vala b/src/Widgets/NotificationsList.vala index 1408fc01..12d86881 100644 --- a/src/Widgets/NotificationsList.vala +++ b/src/Widgets/NotificationsList.vala @@ -17,14 +17,13 @@ public class Notifications.NotificationsList : Gtk.ListBox { public signal void close_popover (); + public signal void clear_app (string app_id); + public signal void remove_notification (Notification notification); - public Gee.HashMap app_entries { get; private set; } - - private HashTable table; + private GLib.GenericArray apps; construct { - app_entries = new Gee.HashMap (); - table = new HashTable (str_hash, str_equal); + apps = new GLib.GenericArray (); var placeholder = new Gtk.Label (_("No Notifications")); placeholder.margin_top = placeholder.margin_bottom = 24; @@ -38,95 +37,58 @@ public class Notifications.NotificationsList : Gtk.ListBox { activate_on_single_click = true; selection_mode = Gtk.SelectionMode.NONE; set_placeholder (placeholder); + set_header_func (header_func); show_all (); row_activated.connect (on_row_activated); } - public void add_entry (Notification notification) { - if (notification.app_info != null && notification.app_info.get_id () != null) { - AppEntry? app_entry = null; - - if (app_entries[notification.desktop_id] != null) { - app_entry = app_entries[notification.desktop_id]; - } - - var entry = new NotificationEntry (notification); - if (app_entry == null) { - app_entry = new AppEntry (entry); - app_entries[notification.desktop_id] = app_entry; - - prepend (app_entry); - insert (entry, 1); - table.insert (app_entry.app_id, 0); - } else { - resort_app_entry (app_entry); - app_entry.add_notification_entry (entry); - - int insert_pos = table.get (app_entry.app_id); - insert (entry, insert_pos + 1); - } - - app_entry.clear.connect (clear_app_entry); - - show_all (); - - Session.get_instance ().add_notification (notification); - } + public NotificationsList (GLib.ListModel list_model) { + bind_model (list_model, create_notification); } - public void clear_all () { - app_entries.values.foreach ((app_entry) => { - clear_app_entry (app_entry); - return true; + private Gtk.Widget create_notification (GLib.Object object) { + unowned Notification notification = (Notifications.Notification) object; + var notification_entry = new NotificationEntry (notification); + notification_entry.removed.connect (() => { + remove_notification (notification); }); - close_popover (); + notification_entry.show_all (); + return notification_entry; } - private void resort_app_entry (AppEntry app_entry) { - if (get_row_at_index (0) != app_entry) { - remove (app_entry); - prepend (app_entry); - app_entry.app_notifications.foreach ((notification_entry) => { - remove (notification_entry); - insert (notification_entry, 1); - }); + private void header_func (Gtk.ListBoxRow row, Gtk.ListBoxRow? before) { + unowned NotificationEntry row_entry = (NotificationEntry) row; + unowned NotificationEntry? before_entry = (NotificationEntry) before; + unowned string row_app_id = row_entry.notification.desktop_id; + if (before == null || row_app_id != before_entry.notification.desktop_id) { + for (uint i = 0; i < apps.length; i++) { + unowned AppEntry app = apps.get (i); + if (app.app_id == row_app_id) { + row.set_header (app); + return; + } + } + + var app_entry = new AppEntry (row_entry.notification.app_info); + app_entry.show_all (); + app_entry.clear.connect (() => { remove_app (app_entry); }); + row.set_header (app_entry); + } else { + row.set_header (null); } } - private void clear_app_entry (AppEntry app_entry) { - app_entries.unset (app_entry.app_id); - - app_entry.app_notifications.foreach ((notification_entry) => { - app_entry.remove_notification_entry.begin (notification_entry); - }); - - app_entry.destroy (); - - if (app_entries.size == 0) { - Session.get_instance ().clear (); - } + private void remove_app (AppEntry entry) { + clear_app (entry.app_id); + apps.remove_fast (entry); } private void on_row_activated (Gtk.ListBoxRow row) { - bool close = true; - - if (row is AppEntry) { - var app_entry = (AppEntry)row; - app_entry.clear (); - - } else if (row is NotificationEntry) { - unowned NotificationEntry notification_entry = (NotificationEntry) row; - notification_entry.notification.run_default_action (); - notification_entry.clear (); - - } else { - close = false; - } - - if (close) { - close_popover (); - } + unowned NotificationEntry notification_entry = (NotificationEntry) row; + notification_entry.notification.run_default_action (); + notification_entry.dismiss (); + close_popover (); } }