diff options
-rw-r--r-- | src/indicator-notifications.c | 442 |
1 files changed, 230 insertions, 212 deletions
diff --git a/src/indicator-notifications.c b/src/indicator-notifications.c index 5a2b749..9863e50 100644 --- a/src/indicator-notifications.c +++ b/src/indicator-notifications.c @@ -93,25 +93,27 @@ struct _IndicatorNotificationsPrivate { GType indicator_notifications_get_type(void); +/* Indicator Class Functions */ static void indicator_notifications_class_init(IndicatorNotificationsClass *klass); static void indicator_notifications_init(IndicatorNotifications *self); static void indicator_notifications_dispose(GObject *object); static void indicator_notifications_finalize(GObject *object); -static GtkImage *get_image(IndicatorObject *io); -static GtkMenu *get_menu(IndicatorObject *io); -static const gchar *get_accessible_desc(IndicatorObject *io); -static GdkPixbuf *load_icon(const gchar *name, gint size); -static void menu_visible_notify_cb(GtkWidget *menu, GParamSpec *pspec, gpointer user_data); -static void insert_menuitem(IndicatorNotifications *self, GtkWidget *item); -static void update_clear_item_markup(IndicatorNotifications *self); +/* Indicator Standard Methods */ +static GtkImage *get_image(IndicatorObject *io); +static GtkMenu *get_menu(IndicatorObject *io); +static const gchar *get_accessible_desc(IndicatorObject *io); -static void calculate_size_cb(GtkWidget *item, gpointer user_data); -static void resize_menu(GtkWidget *menu); +/* Utility Functions */ +static void insert_menuitem(IndicatorNotifications *self, GtkWidget *item); +static GdkPixbuf *load_icon(const gchar *name, gint size); +static GtkWidget *new_notification_menuitem(Notification *note); +static void update_clear_item_markup(IndicatorNotifications *self); +/* Callbacks */ +static void menu_visible_notify_cb(GtkWidget *menu, GParamSpec *pspec, gpointer user_data); static void message_received_cb(DBusSpy *spy, Notification *note, gpointer user_data); -static GtkWidget *new_notification_menuitem(Notification *note); -static void style_changed(GtkWidget *widget, GtkStyle *oldstyle, gpointer user_data); +static void style_changed_cb(GtkWidget *widget, GtkStyle *oldstyle, gpointer user_data); /* Indicator Module Config */ INDICATOR_SET_VERSION @@ -139,125 +141,6 @@ indicator_notifications_class_init(IndicatorNotificationsClass *klass) } static void -menu_visible_notify_cb(GtkWidget *menu, G_GNUC_UNUSED GParamSpec *pspec, gpointer user_data) -{ - IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(user_data); - - gboolean visible; - g_object_get(G_OBJECT(menu), "visible", &visible, NULL); - if(!visible) { - if(self->priv->pixbuf_read != NULL) { - self->priv->have_unread = FALSE; - gtk_image_set_from_pixbuf(self->priv->image, self->priv->pixbuf_read); - } - } -} - -/* In GTK3 labels can now automatically wrap to fit the size of their parent, - * however, it seems to take several tries for GtkMenu to resize properly. - * - * As a workaround, calculate the preferred size for each of the menu items and - * set the menu's size request appropriately. - */ -static void -calculate_size_cb(GtkWidget *item, gpointer user_data) -{ - GtkAllocation *alloc = (GtkAllocation *)user_data; - gint width; - gint height; - - gtk_widget_get_preferred_width(item, NULL, &width); - gtk_widget_get_preferred_height_for_width(item, width, NULL, &height); - - if(alloc->width < width) - alloc->width = width; - - alloc->height += height; -} - -static void -resize_menu(GtkWidget *menu) -{ - GtkAllocation alloc; - GtkAllocation child_alloc; - - gtk_widget_get_allocation(menu, &alloc); - - child_alloc.x = 0; - child_alloc.y = 0; - child_alloc.width = 1; - child_alloc.height = 1; - - gtk_container_foreach(GTK_CONTAINER(menu), calculate_size_cb, &child_alloc); - gtk_widget_set_size_request(menu, child_alloc.width, child_alloc.height); - g_debug("RESIZE_MENU: W: %d H: %d -> W: %d H: %d", alloc.width, alloc.height, - child_alloc.width, child_alloc.height); -} - -static void -update_clear_item_markup(IndicatorNotifications *self) -{ - guint visible_length = g_list_length(self->priv->visible_items); - guint hidden_length = g_list_length(self->priv->hidden_items); - guint total_length = visible_length + hidden_length; - - gchar *markup = g_strdup_printf(ngettext( - "Clear <small>(%d Notification)</small>", - "Clear <small>(%d Notifications)</small>", - total_length), - total_length); - - gtk_label_set_markup(GTK_LABEL(self->priv->clear_item_label), markup); - g_free(markup); -} - -static void -insert_menuitem(IndicatorNotifications *self, GtkWidget *item) -{ - GList *last_item; - GtkWidget *last_widget; - - /* List holds a ref to the menuitem */ - self->priv->visible_items = g_list_prepend(self->priv->visible_items, g_object_ref(item)); - gtk_menu_shell_prepend(GTK_MENU_SHELL(self->priv->menu), item); - - /* Move items that overflow to the hidden list */ - while(g_list_length(self->priv->visible_items) > INDICATOR_MAX_ITEMS) { - last_item = g_list_last(self->priv->visible_items); - last_widget = GTK_WIDGET(last_item->data); - /* Steal the ref from the visible list */ - self->priv->visible_items = g_list_delete_link(self->priv->visible_items, last_item); - self->priv->hidden_items = g_list_prepend(self->priv->hidden_items, last_widget); - gtk_container_remove(GTK_CONTAINER(self->priv->menu), last_widget); - last_item = NULL; - last_widget = NULL; - } - - update_clear_item_markup(self); -} - -static void -message_received_cb(DBusSpy *spy, Notification *note, gpointer user_data) -{ - IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(user_data); - - /* Discard volume notifications */ - if(notification_is_volume(note) || notification_is_empty(note)) - return; - - GtkWidget *item = new_notification_menuitem(note); - g_object_unref(note); - - insert_menuitem(self, item); - - if(self->priv->pixbuf_unread != NULL) { - self->priv->have_unread = TRUE; - gtk_image_set_from_pixbuf(self->priv->image, self->priv->pixbuf_unread); - } - //resize_menu(GTK_WIDGET(self->priv->menu)); -} - -static void indicator_notifications_init(IndicatorNotifications *self) { self->priv = INDICATOR_NOTIFICATIONS_GET_PRIVATE(self); @@ -349,6 +232,139 @@ indicator_notifications_finalize(GObject *object) return; } +static GtkImage * +get_image(IndicatorObject *io) +{ + IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(io); + + if(self->priv->image == NULL) { + self->priv->image = GTK_IMAGE(gtk_image_new()); + + self->priv->pixbuf_read = load_icon(INDICATOR_ICON_READ, INDICATOR_ICON_SIZE); + + if(self->priv->pixbuf_read == NULL) { + g_error("Failed to load read icon"); + return NULL; + } + + self->priv->pixbuf_unread = load_icon(INDICATOR_ICON_UNREAD, INDICATOR_ICON_SIZE); + + if(self->priv->pixbuf_unread == NULL) { + g_error("Failed to load unread icon"); + return NULL; + } + + gtk_image_set_from_pixbuf(self->priv->image, self->priv->pixbuf_read); + + g_signal_connect(G_OBJECT(self->priv->image), "style-set", G_CALLBACK(style_changed_cb), self); + + gtk_widget_show(GTK_WIDGET(self->priv->image)); + } + + return self->priv->image; +} + +static GtkMenu * +get_menu(IndicatorObject *io) +{ + IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(io); + + return GTK_MENU(self->priv->menu); +} + +static const gchar * +get_accessible_desc(IndicatorObject *io) +{ + IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(io); + + return self->priv->accessible_desc; +} + +/** + * insert_menuitem: + * @self: the indicator + * @item: the menuitem to insert + * + * Inserts a menuitem into the indicator's menu and updates the visible and + * hidden lists. + **/ +static void +insert_menuitem(IndicatorNotifications *self, GtkWidget *item) +{ + GList *last_item; + GtkWidget *last_widget; + + /* List holds a ref to the menuitem */ + self->priv->visible_items = g_list_prepend(self->priv->visible_items, g_object_ref(item)); + gtk_menu_shell_prepend(GTK_MENU_SHELL(self->priv->menu), item); + + /* Move items that overflow to the hidden list */ + while(g_list_length(self->priv->visible_items) > INDICATOR_MAX_ITEMS) { + last_item = g_list_last(self->priv->visible_items); + last_widget = GTK_WIDGET(last_item->data); + /* Steal the ref from the visible list */ + self->priv->visible_items = g_list_delete_link(self->priv->visible_items, last_item); + self->priv->hidden_items = g_list_prepend(self->priv->hidden_items, last_widget); + gtk_container_remove(GTK_CONTAINER(self->priv->menu), last_widget); + last_item = NULL; + last_widget = NULL; + } + + update_clear_item_markup(self); +} + +/** + * load_icon: + * @name: the icon name + * @size: the icon size + * + * Tries to load the icon first from the default icon theme, then from an + * absolute path. + * + * Returns: a new gdk pixbuf + **/ +static GdkPixbuf * +load_icon(const gchar *name, gint size) +{ + GError *error = NULL; + GdkPixbuf *pixbuf = NULL; + + /* First try to load the icon from the icon theme */ + GtkIconTheme *theme = gtk_icon_theme_get_default(); + + if(gtk_icon_theme_has_icon(theme, name)) { + pixbuf = gtk_icon_theme_load_icon(theme, name, size, GTK_ICON_LOOKUP_FORCE_SVG, &error); + + if(error != NULL) { + g_warning("Failed to load icon '%s' from icon theme: %s", name, error->message); + } + else { + return pixbuf; + } + } + + /* Otherwise load from the icon installation path */ + gchar *path = g_strdup_printf(ICONS_DIR "/hicolor/scalable/status/%s.svg", name); + pixbuf = gdk_pixbuf_new_from_file_at_scale(path, size, size, FALSE, &error); + + if(error != NULL) { + g_warning("Failed to load icon at '%s': %s", path, error->message); + pixbuf = NULL; + } + + g_free(path); + + return pixbuf; +} + +/** + * new_notification_menuitem: + * @note: the notification object + * + * Constructs a new notification menuitem from the given notification object. + * + * Returns: a new notification menuitem + **/ static GtkWidget * new_notification_menuitem(Notification *note) { @@ -392,8 +408,91 @@ new_notification_menuitem(Notification *note) return item; } +/** + * update_clear_item_markup: + * @self: the indicator object + * + * Updates the clear menuitem's label markup based on the number of + * notifications available. + **/ +static void +update_clear_item_markup(IndicatorNotifications *self) +{ + guint visible_length = g_list_length(self->priv->visible_items); + guint hidden_length = g_list_length(self->priv->hidden_items); + guint total_length = visible_length + hidden_length; + + gchar *markup = g_strdup_printf(ngettext( + "Clear <small>(%d Notification)</small>", + "Clear <small>(%d Notifications)</small>", + total_length), + total_length); + + gtk_label_set_markup(GTK_LABEL(self->priv->clear_item_label), markup); + g_free(markup); +} + +/** + * menu_visible_notify_cb: + * @menu: the menu + * @pspec: unused + * @user_data: the indicator object + * + * Called when the indicator's menu is shown or hidden. + **/ +static void +menu_visible_notify_cb(GtkWidget *menu, G_GNUC_UNUSED GParamSpec *pspec, gpointer user_data) +{ + IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(user_data); + + gboolean visible; + g_object_get(G_OBJECT(menu), "visible", &visible, NULL); + if(!visible) { + if(self->priv->pixbuf_read != NULL) { + self->priv->have_unread = FALSE; + gtk_image_set_from_pixbuf(self->priv->image, self->priv->pixbuf_read); + } + } +} + +/** + * message_received_cb: + * @spy: the dbus notification monitor + * @note: the notification received + * @user_data: the indicator object + * + * Called when a notification arrives on dbus. + **/ +static void +message_received_cb(DBusSpy *spy, Notification *note, gpointer user_data) +{ + IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(user_data); + + /* Discard volume notifications */ + if(notification_is_volume(note) || notification_is_empty(note)) + return; + + GtkWidget *item = new_notification_menuitem(note); + g_object_unref(note); + + insert_menuitem(self, item); + + if(self->priv->pixbuf_unread != NULL) { + self->priv->have_unread = TRUE; + gtk_image_set_from_pixbuf(self->priv->image, self->priv->pixbuf_unread); + } +} + +/** + * style_changed_cb: + * @widget: the indicator image + * @oldstyle: unused + * @user_data: the indicator object + * + * Called when the theme changes, and reloads the indicator icons. + **/ static void -style_changed(GtkWidget *widget, GtkStyle *oldstyle, gpointer user_data) +style_changed_cb(GtkWidget *widget, GtkStyle *oldstyle, gpointer user_data) { IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(user_data); GdkPixbuf *pixbuf_read = NULL, *pixbuf_unread = NULL; @@ -426,84 +525,3 @@ style_changed(GtkWidget *widget, GtkStyle *oldstyle, gpointer user_data) } } -static GdkPixbuf * -load_icon(const gchar *name, gint size) -{ - GError *error = NULL; - GdkPixbuf *pixbuf = NULL; - - /* First try to load the icon from the icon theme */ - GtkIconTheme *theme = gtk_icon_theme_get_default(); - - if(gtk_icon_theme_has_icon(theme, name)) { - pixbuf = gtk_icon_theme_load_icon(theme, name, size, GTK_ICON_LOOKUP_FORCE_SVG, &error); - - if(error != NULL) { - g_warning("Failed to load icon '%s' from icon theme: %s", name, error->message); - } - else { - return pixbuf; - } - } - - /* Otherwise load from the icon installation path */ - gchar *path = g_strdup_printf(ICONS_DIR "/hicolor/scalable/status/%s.svg", name); - pixbuf = gdk_pixbuf_new_from_file_at_scale(path, size, size, FALSE, &error); - - if(error != NULL) { - g_warning("Failed to load icon at '%s': %s", path, error->message); - pixbuf = NULL; - } - - g_free(path); - - return pixbuf; -} - -static GtkImage * -get_image(IndicatorObject *io) -{ - IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(io); - - if(self->priv->image == NULL) { - self->priv->image = GTK_IMAGE(gtk_image_new()); - - self->priv->pixbuf_read = load_icon(INDICATOR_ICON_READ, INDICATOR_ICON_SIZE); - - if(self->priv->pixbuf_read == NULL) { - g_error("Failed to load read icon"); - return NULL; - } - - self->priv->pixbuf_unread = load_icon(INDICATOR_ICON_UNREAD, INDICATOR_ICON_SIZE); - - if(self->priv->pixbuf_unread == NULL) { - g_error("Failed to load unread icon"); - return NULL; - } - - gtk_image_set_from_pixbuf(self->priv->image, self->priv->pixbuf_read); - - g_signal_connect(G_OBJECT(self->priv->image), "style-set", G_CALLBACK(style_changed), self); - - gtk_widget_show(GTK_WIDGET(self->priv->image)); - } - - return self->priv->image; -} - -static GtkMenu * -get_menu(IndicatorObject *io) -{ - IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(io); - - return GTK_MENU(self->priv->menu); -} - -static const gchar * -get_accessible_desc(IndicatorObject *io) -{ - IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(io); - - return self->priv->accessible_desc; -} |