From 55483b87132daf92ed65f531f648a7fec17bd733 Mon Sep 17 00:00:00 2001 From: Jason Conti Date: Sat, 8 Jun 2019 17:38:00 -0400 Subject: * Add a settings UI --- src/Makefile.am | 14 ++ src/indicator-notifications-settings.c | 375 +++++++++++++++++++++++++++++++++ src/indicator-notifications.c | 68 ++++-- src/settings.h | 14 ++ 4 files changed, 452 insertions(+), 19 deletions(-) create mode 100644 src/indicator-notifications-settings.c create mode 100644 src/settings.h (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 5cbce14..e89ade2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,10 +7,12 @@ libayatana_notifications_la_SOURCES = \ urlregex.h \ notification-menuitem.c \ notification-menuitem.h \ + settings.h \ indicator-notifications.c \ notification.c \ notification.h libayatana_notifications_la_CFLAGS = \ + -DSETTINGS_PATH=\""$(libexecdir)/$(PACKAGE)/indicator-notifications-settings"\" \ $(INDICATOR_CFLAGS) \ -Wall \ -DG_LOG_DOMAIN=\"ayatana-indicator-notifications\" @@ -20,3 +22,15 @@ libayatana_notifications_la_LDFLAGS = \ -module \ -avoid-version +pkglibexec_PROGRAMS = indicator-notifications-settings + +indicator_notifications_settings_SOURCES = \ + settings.h \ + indicator-notifications-settings.c + +indicator_notifications_settings_CFLAGS = \ + $(SETTINGS_CFLAGS) \ + -Wall + +indicator_notifications_settings_LDADD = \ + $(SETTINGS_LIBS) diff --git a/src/indicator-notifications-settings.c b/src/indicator-notifications-settings.c new file mode 100644 index 0000000..439413e --- /dev/null +++ b/src/indicator-notifications-settings.c @@ -0,0 +1,375 @@ +/* + * indicator-notifications-settings.c - UI for indicator settings + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "settings.h" + +#define SCHEMA_KEY "schema-key" + +#define COLUMN_APPNAME 0 + +typedef struct +{ + GtkApplication parent_instance; + + GSettings *settings; + + GtkWidget *blacklist_treeview; + GtkWidget *blacklist_entry; + + /* GtkTreeModel foreach variables */ + gboolean result; + gchar *text; + GPtrArray *array; +} IndicatorNotificationsSettings; + +typedef GtkApplicationClass IndicatorNotificationsSettingsClass; + +G_DEFINE_TYPE(IndicatorNotificationsSettings, indicator_notifications_settings, GTK_TYPE_APPLICATION) + +/* Class Functions */ +static void indicator_notifications_settings_class_init(IndicatorNotificationsSettingsClass *klass); +static void indicator_notifications_settings_init(IndicatorNotificationsSettings *self); +static void indicator_notifications_settings_dispose(GObject *object); + +/* GtkApplication Signals */ +static void indicator_notifications_settings_activate(GApplication *app); + +/* Utility Functions */ +static void load_blacklist(IndicatorNotificationsSettings *self); +static void save_blacklist(IndicatorNotificationsSettings *self); +static gboolean foreach_check_duplicates(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer user_data); +static gboolean foreach_build_array(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer user_data); + +/* Callbacks */ +static void blacklist_add_clicked_cb(GtkButton *button, gpointer user_data); +static void blacklist_remove_clicked_cb(GtkButton *button, gpointer user_data); +static void button_toggled_cb(GtkToggleButton *button, gpointer user_data); +static void max_items_changed_cb(GtkSpinButton *button, gpointer user_data); + +static void +load_blacklist(IndicatorNotificationsSettings *self) +{ + GtkListStore *list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(self->blacklist_treeview))); + GtkTreeIter iter; + gchar **items; + + gtk_list_store_clear(list); + + items = g_settings_get_strv(self->settings, NOTIFICATIONS_KEY_BLACKLIST); + + for (int i = 0; items[i] != NULL; i++) { + gtk_list_store_append(list, &iter); + gtk_list_store_set(list, &iter, COLUMN_APPNAME, items[i], -1); + } + + g_strfreev(items); +} + +static void +save_blacklist(IndicatorNotificationsSettings *self) +{ + GtkListStore *list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(self->blacklist_treeview))); + gchar **items; + + /* build an array of the blacklist items */ + self->array = g_ptr_array_new(); + gtk_tree_model_foreach(GTK_TREE_MODEL(list), foreach_build_array, self); + g_ptr_array_add(self->array, NULL); + items = (gchar **) g_ptr_array_free(self->array, FALSE); + self->array = NULL; + + g_settings_set_strv(self->settings, NOTIFICATIONS_KEY_BLACKLIST, (const gchar **) items); + + g_strfreev(items); +} + +static gboolean +foreach_check_duplicates(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) +{ + IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) user_data; + gchar *appname; + gboolean result = FALSE; + + gtk_tree_model_get(model, iter, COLUMN_APPNAME, &appname, -1); + + if (g_strcmp0(appname, self->text) == 0) { + result = TRUE; + self->result = TRUE; + } + + g_free(appname); + + return result; +} + +static gboolean +foreach_build_array(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) +{ + IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) user_data; + gchar *appname; + + gtk_tree_model_get(model, iter, COLUMN_APPNAME, &appname, -1); + + g_ptr_array_add(self->array, appname); + + return FALSE; +} + +static void +blacklist_add_clicked_cb(GtkButton *button, gpointer user_data) +{ + IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) user_data; + GtkListStore *list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(self->blacklist_treeview))); + GtkTreeIter iter; + + /* strip off the leading and trailing whitespace in case of user error */ + self->text = g_strdup(gtk_entry_get_text(GTK_ENTRY(self->blacklist_entry))); + g_strstrip(self->text); + + if (strlen(self->text) > 0) { + /* check for duplicates first */ + self->result = FALSE; + gtk_tree_model_foreach(GTK_TREE_MODEL(list), foreach_check_duplicates, self); + + if (self->result == FALSE) { + gtk_list_store_append(list, &iter); + gtk_list_store_set(list, &iter, COLUMN_APPNAME, self->text, -1); + save_blacklist(self); + } + } + + /* clear the entry */ + gtk_entry_set_text(GTK_ENTRY(self->blacklist_entry), ""); + + /* cleanup text */ + g_free(self->text); + self->text = ""; +} + +static void +blacklist_remove_clicked_cb(GtkButton *button, gpointer user_data) +{ + IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) user_data; + GtkListStore *list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(self->blacklist_treeview))); + GtkTreeIter iter; + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self->blacklist_treeview)); + + if (gtk_tree_selection_get_selected(selection, NULL, &iter) == TRUE) { + gtk_list_store_remove(list, &iter); + + save_blacklist(self); + } +} + +static void +button_toggled_cb(GtkToggleButton *button, gpointer user_data) +{ + GSettings *settings = G_SETTINGS(user_data); + char *schema_key = (char *) g_object_get_data(G_OBJECT(button), SCHEMA_KEY); + + g_settings_set_boolean(settings, schema_key, gtk_toggle_button_get_active(button)); +} + +static void +max_items_changed_cb(GtkSpinButton *button, gpointer user_data) +{ + GSettings *settings = G_SETTINGS(user_data); + + int value = gtk_spin_button_get_value_as_int(button); + g_settings_set_int(settings, NOTIFICATIONS_KEY_MAX_ITEMS, value); +} + +static void +indicator_notifications_settings_activate(GApplication *app) +{ + GtkWidget *window; + GtkWidget *frame; + GtkWidget *vbox; + GtkWidget *button_1; + GtkWidget *button_2; + GtkWidget *spin; + GtkWidget *spin_label; + GtkWidget *blacklist_label; + GtkListStore *blacklist_list; + GtkWidget *blacklist_scroll; + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + GtkWidget *hbox; + GtkWidget *button_3; + GtkWidget *button_4; + + IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) app; + + /* Check for a pre-existing window */ + GtkWindow *old_window = gtk_application_get_active_window(GTK_APPLICATION(app)); + if (old_window != NULL) { + gtk_window_present_with_time(old_window, GDK_CURRENT_TIME); + return; + } + + /* GSettings */ + self->settings = g_settings_new(NOTIFICATIONS_SCHEMA); + + /* Main Window */ + window = gtk_application_window_new(GTK_APPLICATION(app)); + gtk_window_set_title(GTK_WINDOW(window), _("Indicator Notifications Settings")); + gtk_window_set_default_size(GTK_WINDOW(window), 400, 400); + gtk_container_set_border_width(GTK_CONTAINER(window), 10); + gtk_widget_show(window); + + /* Window Frame */ + frame = gtk_frame_new(_("Indicator Notifications Settings")); + gtk_container_add(GTK_CONTAINER(window), frame); + gtk_widget_show(frame); + + /* Main Vertical Box */ + vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 10); + gtk_container_add(GTK_CONTAINER(frame), vbox); + gtk_widget_show(vbox); + + /* clear-on-middle-click */ + button_1 = gtk_check_button_new_with_label(_("Clear notifications on middle click")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button_1), + g_settings_get_boolean(self->settings, NOTIFICATIONS_KEY_CLEAR_MC)); + g_object_set_data(G_OBJECT(button_1), SCHEMA_KEY, NOTIFICATIONS_KEY_CLEAR_MC); + g_signal_connect(button_1, "toggled", G_CALLBACK(button_toggled_cb), self->settings); + gtk_box_pack_start(GTK_BOX(vbox), button_1, FALSE, FALSE, 4); + gtk_widget_show(button_1); + + /* hide-indicator */ + button_2 = gtk_check_button_new_with_label(_("Hide indicator")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button_2), + g_settings_get_boolean(self->settings, NOTIFICATIONS_KEY_HIDE_INDICATOR)); + g_object_set_data(G_OBJECT(button_2), SCHEMA_KEY, NOTIFICATIONS_KEY_HIDE_INDICATOR); + g_signal_connect(button_2, "toggled", G_CALLBACK(button_toggled_cb), self->settings); + gtk_box_pack_start(GTK_BOX(vbox), button_2, FALSE, FALSE, 4); + gtk_widget_show(button_2); + + /* max-items */ + /* FIXME: indicator does not change max items until restart... */ + spin_label = gtk_label_new(_("Maximum number of visible notifications")); + gtk_box_pack_start(GTK_BOX(vbox), spin_label, FALSE, FALSE, 4); + gtk_widget_show(spin_label); + + spin = gtk_spin_button_new_with_range(1, 10, 1); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), g_settings_get_int(self->settings, NOTIFICATIONS_KEY_MAX_ITEMS)); + g_signal_connect(spin, "value-changed", G_CALLBACK(max_items_changed_cb), self->settings); + gtk_box_pack_start(GTK_BOX(vbox), spin, FALSE, FALSE, 4); + gtk_widget_show(spin); + + /* blacklist */ + blacklist_label = gtk_label_new(_("Discard notifications by application name")); + gtk_box_pack_start(GTK_BOX(vbox), blacklist_label, FALSE, FALSE, 4); + gtk_widget_show(blacklist_label); + + blacklist_scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_box_pack_start(GTK_BOX(vbox), blacklist_scroll, TRUE, TRUE, 4); + gtk_widget_show(blacklist_scroll); + + blacklist_list = gtk_list_store_new(1, G_TYPE_STRING); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes("appname", renderer, "text", COLUMN_APPNAME, NULL); + + self->blacklist_treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(blacklist_list)); + g_object_unref(blacklist_list); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(self->blacklist_treeview), FALSE); + gtk_tree_view_append_column(GTK_TREE_VIEW(self->blacklist_treeview), column); + load_blacklist(self); + gtk_container_add(GTK_CONTAINER(blacklist_scroll), self->blacklist_treeview); + gtk_widget_show(self->blacklist_treeview); + + hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show(hbox); + + button_3 = gtk_button_new_with_label(_("Remove")); + g_signal_connect(button_3, "clicked", G_CALLBACK(blacklist_remove_clicked_cb), self); + gtk_box_pack_start(GTK_BOX(hbox), button_3, FALSE, FALSE, 2); + gtk_widget_show(button_3); + + button_4 = gtk_button_new_with_label(_("Add")); + g_signal_connect(button_4, "clicked", G_CALLBACK(blacklist_add_clicked_cb), self); + gtk_box_pack_start(GTK_BOX(hbox), button_4, FALSE, FALSE, 2); + gtk_widget_show(button_4); + + self->blacklist_entry = gtk_entry_new(); + gtk_box_pack_start(GTK_BOX(hbox), self->blacklist_entry, TRUE, TRUE, 0); + gtk_widget_show(self->blacklist_entry); +} + +static void +indicator_notifications_settings_init (IndicatorNotificationsSettings *self) +{ +} + +static void +indicator_notifications_settings_class_init (IndicatorNotificationsSettingsClass *class) +{ + GApplicationClass *application_class = G_APPLICATION_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + application_class->activate = indicator_notifications_settings_activate; + + object_class->dispose = indicator_notifications_settings_dispose; +} + +static void +indicator_notifications_settings_dispose(GObject *object) +{ + IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) object; + + if(self->settings != NULL) { + g_object_unref(G_OBJECT(self->settings)); + self->settings = NULL; + } + + G_OBJECT_CLASS(indicator_notifications_settings_parent_class)->dispose(object); +} + +IndicatorNotificationsSettings * +indicator_notifications_settings_new (void) +{ + IndicatorNotificationsSettings *self; + + g_set_application_name(_("Indicator Notifications Settings")); + + self = g_object_new(indicator_notifications_settings_get_type(), + "application-id", NOTIFICATIONS_SCHEMA ".settings", + "flags", G_APPLICATION_FLAGS_NONE, + NULL); + + return self; +} + +int +main(int argc, char **argv) +{ + IndicatorNotificationsSettings *self; + int status; + + setlocale(LC_ALL, ""); + bindtextdomain( GETTEXT_PACKAGE, LOCALEDIR ); + textdomain( GETTEXT_PACKAGE ); + + self = indicator_notifications_settings_new(); + + status = g_application_run(G_APPLICATION(self), argc, argv); + + g_object_unref(self); + + return status; +} diff --git a/src/indicator-notifications.c b/src/indicator-notifications.c index 0655445..1c4e491 100644 --- a/src/indicator-notifications.c +++ b/src/indicator-notifications.c @@ -21,11 +21,6 @@ with this program. If not, see . #include "config.h" #endif -#include -#include -#include -#include - /* GStuff */ #include #include @@ -74,6 +69,7 @@ struct _IndicatorNotificationsPrivate { GtkMenu *menu; GtkWidget *clear_item; GtkWidget *clear_item_label; + GtkWidget *settings_item; gchar *accessible_desc; @@ -87,11 +83,7 @@ struct _IndicatorNotificationsPrivate { #define INDICATOR_NOTIFICATIONS_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), INDICATOR_NOTIFICATIONS_TYPE, IndicatorNotificationsPrivate)) -#define NOTIFICATIONS_SCHEMA "org.ayatana.indicator.notifications" -#define NOTIFICATIONS_KEY_BLACKLIST "blacklist" -#define NOTIFICATIONS_KEY_CLEAR_MC "clear-on-middle-click" -#define NOTIFICATIONS_KEY_HIDE_INDICATOR "hide-indicator" -#define NOTIFICATIONS_KEY_MAX_ITEMS "max-items" +#include "settings.h" #define INDICATOR_ICON_SIZE 22 #define INDICATOR_ICON_READ "ayatana-indicator-notification-read" @@ -115,13 +107,13 @@ static void indicator_notifications_middle_click(IndicatorObject *io, gpointer user_data); /* Utility Functions */ -static void clear_menuitems(IndicatorNotifications *self); -static void insert_menuitem(IndicatorNotifications *self, GtkWidget *item); -static void remove_menuitem(IndicatorNotifications *self, GtkWidget *item); -static void set_unread(IndicatorNotifications *self, gboolean unread); -static void update_blacklist(IndicatorNotifications *self); -static void update_clear_item_markup(IndicatorNotifications *self); -static void update_indicator_visibility(IndicatorNotifications *self); +static void clear_menuitems(IndicatorNotifications *self); +static void insert_menuitem(IndicatorNotifications *self, GtkWidget *item); +static void remove_menuitem(IndicatorNotifications *self, GtkWidget *item); +static void set_unread(IndicatorNotifications *self, gboolean unread); +static void update_blacklist(IndicatorNotifications *self); +static void update_clear_item_markup(IndicatorNotifications *self); +static void update_indicator_visibility(IndicatorNotifications *self); /* Callbacks */ static void clear_item_activated_cb(GtkMenuItem *menuitem, gpointer user_data); @@ -129,12 +121,13 @@ static void menu_visible_notify_cb(GtkWidget *menu, GParamSpec *pspec, gpointer static void message_received_cb(DBusSpy *spy, Notification *note, gpointer user_data); static void notification_clicked_cb(NotificationMenuItem *menuitem, guint button, gpointer user_data); static void setting_changed_cb(GSettings *settings, gchar *key, gpointer user_data); +static void settings_item_activated_cb(GtkMenuItem *menuitem, gpointer user_data); /* Indicator Module Config */ INDICATOR_SET_VERSION INDICATOR_SET_TYPE(INDICATOR_NOTIFICATIONS_TYPE) -G_DEFINE_TYPE (IndicatorNotifications, indicator_notifications, INDICATOR_OBJECT_TYPE); +G_DEFINE_TYPE(IndicatorNotifications, indicator_notifications, INDICATOR_OBJECT_TYPE); static void indicator_notifications_class_init(IndicatorNotificationsClass *klass) @@ -179,9 +172,17 @@ indicator_notifications_init(IndicatorNotifications *self) self->priv->menu = GTK_MENU(gtk_menu_new()); g_signal_connect(self->priv->menu, "notify::visible", G_CALLBACK(menu_visible_notify_cb), self); + /* Create the settings menuitem */ + self->priv->settings_item = gtk_menu_item_new_with_label(_("Settings...")); + g_signal_connect(self->priv->settings_item, "activate", G_CALLBACK(settings_item_activated_cb), NULL); + gtk_widget_show(self->priv->settings_item); + + gtk_menu_shell_prepend(GTK_MENU_SHELL(self->priv->menu), self->priv->settings_item); + /* Create the clear menuitem */ self->priv->clear_item_label = gtk_label_new(NULL); - gtk_misc_set_alignment(GTK_MISC(self->priv->clear_item_label), 0, 0); + gtk_label_set_xalign(GTK_LABEL(self->priv->clear_item_label), 0); + gtk_label_set_yalign(GTK_LABEL(self->priv->clear_item_label), 0); gtk_label_set_use_markup(GTK_LABEL(self->priv->clear_item_label), TRUE); update_clear_item_markup(self); gtk_widget_show(self->priv->clear_item_label); @@ -521,6 +522,35 @@ clear_item_activated_cb(GtkMenuItem *menuitem, gpointer user_data) clear_menuitems(self); } +/** + * settings_item_activated_cb: + * @menuitem: the settings menuitem + * @user_data: the indicator object + * + * Called when the settings menuitem is activated. + **/ +static void +settings_item_activated_cb(GtkMenuItem *menuitem, gpointer user_data) +{ + g_return_if_fail(GTK_IS_MENU_ITEM(menuitem)); + + GError *error = NULL; + + gchar *argv[] = { SETTINGS_PATH, NULL }; + + GPid pid; + + g_spawn_async(NULL, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, &pid, &error); + + if (error != NULL) { + g_message("%s", error->message); + g_error_free(error); + } + else { + g_spawn_close_pid(pid); + } +} + /** * setting_changed_cb: * @settings: the GSettings object diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..87e341f --- /dev/null +++ b/src/settings.h @@ -0,0 +1,14 @@ +/* + * settings.h - Definitions of settings keys and schema. + */ + +#ifndef __SETTINGS_H__ +#define __SETTINGS_H__ + +#define NOTIFICATIONS_SCHEMA "org.ayatana.indicator.notifications" +#define NOTIFICATIONS_KEY_BLACKLIST "blacklist" +#define NOTIFICATIONS_KEY_CLEAR_MC "clear-on-middle-click" +#define NOTIFICATIONS_KEY_HIDE_INDICATOR "hide-indicator" +#define NOTIFICATIONS_KEY_MAX_ITEMS "max-items" + +#endif -- cgit v1.2.3