diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/Makefile.in | 13 | ||||
-rw-r--r-- | src/app-section.c | 193 | ||||
-rw-r--r-- | src/app-section.h | 5 | ||||
-rw-r--r-- | src/ido-detail-label.c | 396 | ||||
-rw-r--r-- | src/ido-detail-label.h | 59 | ||||
-rw-r--r-- | src/ido-menu-item.c | 34 | ||||
-rw-r--r-- | src/im-source-menu-item.c | 83 | ||||
-rw-r--r-- | src/indicator-messages-service.c | 29 | ||||
-rw-r--r-- | src/indicator-messages-service.h | 5 | ||||
-rw-r--r-- | src/indicator-messages.c | 135 | ||||
-rw-r--r-- | src/messages-service.c | 289 | ||||
-rw-r--r-- | src/messages-service.xml | 1 |
13 files changed, 1002 insertions, 242 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 4d86730..1df80e5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -21,6 +21,8 @@ libmessaging_la_SOURCES = \ im-app-menu-item.h \ im-source-menu-item.c \ im-source-menu-item.h \ + ido-detail-label.c \ + ido-detail-label.h \ indicator-messages-service.c \ indicator-messages-service.h dbus-data.h diff --git a/src/Makefile.in b/src/Makefile.in index 9fe1c45..2fd86c6 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.11.5 from Makefile.am. +# Makefile.in generated by automake 1.11.6 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, @@ -100,6 +100,7 @@ am_libmessaging_la_OBJECTS = libmessaging_la-indicator-messages.lo \ libmessaging_la-ido-menu-item.lo \ libmessaging_la-im-app-menu-item.lo \ libmessaging_la-im-source-menu-item.lo \ + libmessaging_la-ido-detail-label.lo \ libmessaging_la-indicator-messages-service.lo libmessaging_la_OBJECTS = $(am_libmessaging_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) @@ -366,6 +367,8 @@ libmessaging_la_SOURCES = \ im-app-menu-item.h \ im-source-menu-item.c \ im-source-menu-item.h \ + ido-detail-label.c \ + ido-detail-label.h \ indicator-messages-service.c \ indicator-messages-service.h @@ -549,6 +552,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/indicator_messages_service-gsettingsstrv.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/indicator_messages_service-indicator-messages-service.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/indicator_messages_service-messages-service.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmessaging_la-ido-detail-label.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmessaging_la-ido-menu-item.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmessaging_la-im-app-menu-item.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmessaging_la-im-source-menu-item.Plo@am__quote@ @@ -604,6 +608,13 @@ libmessaging_la-im-source-menu-item.lo: im-source-menu-item.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libmessaging_la_CFLAGS) $(CFLAGS) -c -o libmessaging_la-im-source-menu-item.lo `test -f 'im-source-menu-item.c' || echo '$(srcdir)/'`im-source-menu-item.c +libmessaging_la-ido-detail-label.lo: ido-detail-label.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libmessaging_la_CFLAGS) $(CFLAGS) -MT libmessaging_la-ido-detail-label.lo -MD -MP -MF $(DEPDIR)/libmessaging_la-ido-detail-label.Tpo -c -o libmessaging_la-ido-detail-label.lo `test -f 'ido-detail-label.c' || echo '$(srcdir)/'`ido-detail-label.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmessaging_la-ido-detail-label.Tpo $(DEPDIR)/libmessaging_la-ido-detail-label.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ido-detail-label.c' object='libmessaging_la-ido-detail-label.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libmessaging_la_CFLAGS) $(CFLAGS) -c -o libmessaging_la-ido-detail-label.lo `test -f 'ido-detail-label.c' || echo '$(srcdir)/'`ido-detail-label.c + libmessaging_la-indicator-messages-service.lo: indicator-messages-service.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libmessaging_la_CFLAGS) $(CFLAGS) -MT libmessaging_la-indicator-messages-service.lo -MD -MP -MF $(DEPDIR)/libmessaging_la-indicator-messages-service.Tpo -c -o libmessaging_la-indicator-messages-service.lo `test -f 'indicator-messages-service.c' || echo '$(srcdir)/'`indicator-messages-service.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmessaging_la-indicator-messages-service.Tpo $(DEPDIR)/libmessaging_la-indicator-messages-service.Plo diff --git a/src/app-section.c b/src/app-section.c index bed1302..6aac52a 100644 --- a/src/app-section.c +++ b/src/app-section.c @@ -37,7 +37,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. struct _AppSectionPrivate { GDesktopAppInfo * appinfo; - guint unreadcount; + GFileMonitor *desktop_file_monitor; IndicatorDesktopShortcuts * ids; @@ -50,6 +50,7 @@ struct _AppSectionPrivate gboolean draws_attention; gboolean uses_chat_status; + gchar *chat_status; guint name_watch_id; }; @@ -60,10 +61,12 @@ enum { PROP_ACTIONS, PROP_DRAWS_ATTENTION, PROP_USES_CHAT_STATUS, + PROP_CHAT_STATUS, NUM_PROPERTIES }; static GParamSpec *properties[NUM_PROPERTIES]; +static guint destroy_signal; /* Prototypes */ static void app_section_class_init (AppSectionClass *klass); @@ -77,6 +80,7 @@ static void app_section_set_property (GObject *object, const GValue *value, GParamSpec *pspec); static void app_section_dispose (GObject *object); +static void app_section_finalize (GObject *object); static void activate_cb (GSimpleAction *action, GVariant *param, gpointer userdata); @@ -98,6 +102,11 @@ static void action_removed (GActionGroup *group, const gchar *action_name, gpointer user_data); static gboolean action_draws_attention (GVariant *state); +static void desktop_file_changed_cb (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + gpointer user_data); /* GObject Boilerplate */ G_DEFINE_TYPE (AppSection, app_section, G_TYPE_OBJECT); @@ -112,6 +121,7 @@ app_section_class_init (AppSectionClass *klass) object_class->get_property = app_section_get_property; object_class->set_property = app_section_set_property; object_class->dispose = app_section_dispose; + object_class->finalize = app_section_finalize; properties[PROP_APPINFO] = g_param_spec_object ("app-info", "AppInfo", @@ -137,7 +147,22 @@ app_section_class_init (AppSectionClass *klass) FALSE, G_PARAM_READABLE); + properties[PROP_CHAT_STATUS] = g_param_spec_string ("chat-status", + "Chat status", + "Current chat status of the application", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); + + destroy_signal = g_signal_new ("destroy", + APP_SECTION_TYPE, + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); } static void @@ -151,7 +176,6 @@ app_section_init (AppSection *self) priv = self->priv; priv->appinfo = NULL; - priv->unreadcount = 0; priv->menu = g_menu_new (); priv->static_shortcuts = g_simple_action_group_new (); @@ -186,6 +210,10 @@ app_section_get_property (GObject *object, g_value_set_boolean (value, app_section_get_uses_chat_status (self)); break; + case PROP_CHAT_STATUS: + g_value_set_string (value, app_section_get_status (self)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -205,16 +233,26 @@ app_section_set_property (GObject *object, app_section_set_app_info (self, g_value_get_object (value)); break; + case PROP_CHAT_STATUS: + app_section_set_status (self, g_value_get_string (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } + static void app_section_dispose (GObject *object) { AppSection * self = APP_SECTION(object); AppSectionPrivate * priv = self->priv; + if (priv->desktop_file_monitor) { + g_signal_handlers_disconnect_by_func (priv->desktop_file_monitor, desktop_file_changed_cb, self); + g_clear_object (&priv->desktop_file_monitor); + } + g_clear_object (&priv->menu); g_clear_object (&priv->static_shortcuts); @@ -242,6 +280,16 @@ app_section_dispose (GObject *object) G_OBJECT_CLASS (app_section_parent_class)->dispose (object); } +static void +app_section_finalize (GObject *object) +{ + AppSection * self = APP_SECTION(object); + + g_free (self->priv->chat_status); + + G_OBJECT_CLASS (app_section_parent_class)->dispose (object); +} + /* Respond to one of the shortcuts getting clicked on. */ static void nick_activate_cb (GSimpleAction *action, @@ -289,6 +337,9 @@ keyfile_loaded (GObject *source_object, G_KEY_FILE_DESKTOP_GROUP, "X-MessagingMenu-UsesChatSection", &error); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USES_CHAT_STATUS]); + if (error) { if (error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND) { g_warning ("could not read X-MessagingMenu-UsesChatSection: %s", @@ -298,34 +349,48 @@ keyfile_loaded (GObject *source_object, goto out; } - if (self->priv->uses_chat_status) - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USES_CHAT_STATUS]); - out: g_key_file_free (keyfile); g_free (contents); } static void -app_section_set_app_info (AppSection *self, - GDesktopAppInfo *appinfo) +g_menu_clear (GMenu *menu) +{ + gint n_items = g_menu_model_get_n_items (G_MENU_MODEL (menu)); + + while (n_items--) + g_menu_remove (menu, 0); +} + +static void +g_simple_action_group_clear (GSimpleActionGroup *group) +{ + gchar **actions; + gchar **it; + + actions = g_action_group_list_actions (G_ACTION_GROUP (group)); + for (it = actions; *it; it++) + g_simple_action_group_remove (group, *it); + + g_strfreev (actions); +} + +static void +app_section_update_menu (AppSection *self) { AppSectionPrivate *priv = self->priv; GSimpleAction *launch; GFile *keyfile; GMenuItem *item; gchar *iconstr; + gboolean is_running; - g_return_if_fail (priv->appinfo == NULL); - - if (appinfo == NULL) { - g_warning ("appinfo must not be NULL"); - return; - } - - priv->appinfo = g_object_ref (appinfo); + g_menu_clear (priv->menu); + g_simple_action_group_clear (priv->static_shortcuts); - launch = g_simple_action_new_stateful ("launch", NULL, g_variant_new_boolean (FALSE)); + is_running = priv->name_watch_id > 0; + launch = g_simple_action_new_stateful ("launch", NULL, g_variant_new_boolean (is_running)); g_signal_connect (launch, "activate", G_CALLBACK (activate_cb), self); g_signal_connect (launch, "change-state", G_CALLBACK (launch_action_change_state), self); g_simple_action_group_insert (priv->static_shortcuts, G_ACTION (launch)); @@ -366,14 +431,65 @@ app_section_set_app_info (AppSection *self, keyfile = g_file_new_for_path (g_desktop_app_info_get_filename (priv->appinfo)); g_file_load_contents_async (keyfile, NULL, keyfile_loaded, self); - g_object_unref (keyfile); - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_APPINFO]); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACTIONS]); + g_object_unref (keyfile); g_object_unref (launch); } +static void +desktop_file_changed_cb (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + gpointer user_data) +{ + AppSection *self = user_data; + + if (event == G_FILE_MONITOR_EVENT_CHANGED) { + app_section_update_menu (self); + } + else if (event == G_FILE_MONITOR_EVENT_DELETED || + event == G_FILE_MONITOR_EVENT_UNMOUNTED) { + g_signal_emit (self, destroy_signal, 0); + } +} + +static void +app_section_set_app_info (AppSection *self, + GDesktopAppInfo *appinfo) +{ + AppSectionPrivate *priv = self->priv; + GFile *desktop_file; + GError *error = NULL; + + g_return_if_fail (priv->appinfo == NULL); + g_return_if_fail (priv->desktop_file_monitor == NULL); + + if (appinfo == NULL) { + g_warning ("appinfo must not be NULL"); + return; + } + + priv->appinfo = g_object_ref (appinfo); + + desktop_file = g_file_new_for_path (g_desktop_app_info_get_filename (appinfo)); + priv->desktop_file_monitor = g_file_monitor (desktop_file, G_FILE_MONITOR_SEND_MOVED, NULL, &error); + if (priv->desktop_file_monitor == NULL) { + g_warning ("unable to watch desktop file: %s", error->message); + g_error_free (error); + } + g_signal_connect (priv->desktop_file_monitor, "changed", + G_CALLBACK (desktop_file_changed_cb), self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_APPINFO]); + + app_section_update_menu (self); + + g_object_unref (desktop_file); +} + AppSection * app_section_new (GDesktopAppInfo *appinfo) { @@ -407,25 +523,6 @@ launch_action_change_state (GSimpleAction *action, g_simple_action_set_state (action, value); } -guint -app_section_get_count (AppSection * self) -{ - AppSectionPrivate * priv = self->priv; - - return priv->unreadcount; -} - -const gchar * -app_section_get_name (AppSection * self) -{ - AppSectionPrivate * priv = self->priv; - - if (priv->appinfo) { - return g_app_info_get_name(G_APP_INFO(priv->appinfo)); - } - return NULL; -} - const gchar * app_section_get_desktop (AppSection * self) { @@ -600,10 +697,12 @@ app_section_unset_object_path (AppSection *self) } priv->draws_attention = FALSE; + g_clear_pointer (&priv->chat_status, g_free); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACTIONS]); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USES_CHAT_STATUS]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHAT_STATUS]); g_action_group_change_action_state (G_ACTION_GROUP (priv->static_shortcuts), "launch", g_variant_new_boolean (FALSE)); @@ -695,3 +794,23 @@ app_section_get_uses_chat_status (AppSection *self) return priv->uses_chat_status; } + +const gchar * +app_section_get_status (AppSection *self) +{ + AppSectionPrivate * priv = self->priv; + + return priv->chat_status; +} + +void +app_section_set_status (AppSection *self, + const gchar *status) +{ + AppSectionPrivate * priv = self->priv; + + g_free (priv->chat_status); + priv->chat_status = g_strdup (status); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHAT_STATUS]); +} diff --git a/src/app-section.h b/src/app-section.h index 711fdc9..7c39e8e 100644 --- a/src/app-section.h +++ b/src/app-section.h @@ -51,8 +51,6 @@ struct _AppSection { GType app_section_get_type (void); AppSection * app_section_new (GDesktopAppInfo *appinfo); -guint app_section_get_count (AppSection * appitem); -const gchar * app_section_get_name (AppSection * appitem); const gchar * app_section_get_desktop (AppSection * appitem); GActionGroup * app_section_get_actions (AppSection *self); GMenuModel * app_section_get_menu (AppSection *appitem); @@ -65,6 +63,9 @@ void app_section_set_object_path (AppSection *self, const gchar *object_path); void app_section_unset_object_path (AppSection *self); gboolean app_section_get_uses_chat_status (AppSection *self); +const gchar * app_section_get_status (AppSection *self); +void app_section_set_status (AppSection *self, + const gchar *status); G_END_DECLS diff --git a/src/ido-detail-label.c b/src/ido-detail-label.c new file mode 100644 index 0000000..780a2dd --- /dev/null +++ b/src/ido-detail-label.c @@ -0,0 +1,396 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#include "ido-detail-label.h" + +#include <math.h> + +G_DEFINE_TYPE (IdoDetailLabel, ido_detail_label, GTK_TYPE_WIDGET) + +struct _IdoDetailLabelPrivate +{ + gchar *text; + PangoLayout *layout; + gboolean draw_lozenge; +}; + +enum +{ + PROP_0, + PROP_TEXT, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +static void +ido_detail_label_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + IdoDetailLabel *self = IDO_DETAIL_LABEL (object); + + switch (property_id) + { + case PROP_TEXT: + g_value_set_string (value, self->priv->text); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ido_detail_label_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + IdoDetailLabel *self = IDO_DETAIL_LABEL (object); + + switch (property_id) + { + case PROP_TEXT: + ido_detail_label_set_text (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + + +static void +ido_detail_label_finalize (GObject *object) +{ + IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (object)->priv; + + g_free (priv->text); + + G_OBJECT_CLASS (ido_detail_label_parent_class)->finalize (object); +} + +static void +ido_detail_label_dispose (GObject *object) +{ + IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (object)->priv; + + g_clear_object (&priv->layout); + + G_OBJECT_CLASS (ido_detail_label_parent_class)->dispose (object); +} + +static void +ido_detail_label_ensure_layout (IdoDetailLabel *label) +{ + IdoDetailLabelPrivate *priv = label->priv; + + if (priv->layout == NULL) + { + priv->layout = gtk_widget_create_pango_layout (GTK_WIDGET (label), priv->text); + pango_layout_set_alignment (priv->layout, PANGO_ALIGN_CENTER); + pango_layout_set_ellipsize (priv->layout, PANGO_ELLIPSIZE_END); + pango_layout_set_height (priv->layout, -1); + + // TODO update layout on "style-updated" and "direction-changed" + } +} + +static void +cairo_lozenge (cairo_t *cr, + double x, + double y, + double w, + double h, + double radius) +{ + double x1 = x + w - radius; + double x2 = x + radius; + double y1 = y + radius; + double y2 = y + h - radius; + + cairo_move_to (cr, x + radius, y); + cairo_arc (cr, x1, y1, radius, G_PI * 1.5, G_PI * 2); + cairo_arc (cr, x1, y2, radius, 0, G_PI * 0.5); + cairo_arc (cr, x2, y2, radius, G_PI * 0.5, G_PI); + cairo_arc (cr, x2, y1, radius, G_PI, G_PI * 1.5); +} + +static PangoFontMetrics * +gtk_widget_get_font_metrics (GtkWidget *widget, + PangoContext *context) +{ + const PangoFontDescription *font; + + font = gtk_style_context_get_font (gtk_widget_get_style_context (widget), + gtk_widget_get_state_flags (widget)); + + return pango_context_get_metrics (context, + font, + pango_context_get_language (context)); +} + +static gint +ido_detail_label_get_minimum_text_width (IdoDetailLabel *label) +{ + IdoDetailLabelPrivate *priv = label->priv; + PangoContext *context; + PangoFontMetrics *metrics; + gint char_width; + gint w; + + context = pango_layout_get_context (priv->layout); + metrics = gtk_widget_get_font_metrics (GTK_WIDGET (label), context); + char_width = pango_font_metrics_get_approximate_digit_width (metrics); + + w = 2 * char_width / PANGO_SCALE; + pango_font_metrics_unref (metrics); + return w; +} + +static gboolean +ido_detail_label_draw (GtkWidget *widget, + cairo_t *cr) +{ + IdoDetailLabel *label = IDO_DETAIL_LABEL (widget); + IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (widget)->priv; + PangoRectangle extents; + GtkAllocation allocation; + double x, w, h, radius; + GdkRGBA color; + + if (!priv->text || !*priv->text) + return TRUE; + + gtk_widget_get_allocation (widget, &allocation); + + ido_detail_label_ensure_layout (IDO_DETAIL_LABEL (widget)); + + pango_layout_get_extents (priv->layout, NULL, &extents); + pango_extents_to_pixels (&extents, NULL); + + h = MIN (allocation.height, extents.height); + radius = floor (h / 2.0); + w = MAX (ido_detail_label_get_minimum_text_width (label), extents.width) + 2.0 * radius; + x = allocation.width - w; + + pango_layout_set_width (priv->layout, (allocation.width - 2 * radius) * PANGO_SCALE); + pango_layout_get_extents (priv->layout, NULL, &extents); + pango_extents_to_pixels (&extents, NULL); + + gtk_style_context_get_color (gtk_widget_get_style_context (widget), + gtk_widget_get_state_flags (widget), + &color); + gdk_cairo_set_source_rgba (cr, &color); + + cairo_set_line_width (cr, 1.0); + cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD); + + if (priv->draw_lozenge) + cairo_lozenge (cr, x, 0.0, w, h, radius); + + cairo_move_to (cr, x + radius, (allocation.height - extents.height) / 2.0); + pango_cairo_layout_path (cr, priv->layout); + cairo_fill (cr); + + return TRUE; +} + +static void +ido_detail_label_get_preferred_width (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (widget)->priv; + PangoRectangle extents; + double radius; + + ido_detail_label_ensure_layout (IDO_DETAIL_LABEL (widget)); + + pango_layout_get_extents (priv->layout, NULL, &extents); + pango_extents_to_pixels (&extents, NULL); + + radius = floor (extents.height / 2.0); + + *minimum = ido_detail_label_get_minimum_text_width (IDO_DETAIL_LABEL (widget)) + 2.0 * radius; + *natural = MAX (*minimum, extents.width + 2.0 * radius); +} + +static void +ido_detail_label_get_preferred_height (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (widget)->priv; + PangoContext *context; + PangoFontMetrics *metrics; + PangoRectangle extents; + + ido_detail_label_ensure_layout (IDO_DETAIL_LABEL (widget)); + + pango_layout_get_extents (priv->layout, NULL, &extents); + pango_extents_to_pixels (&extents, NULL); + context = pango_layout_get_context (priv->layout); + metrics = gtk_widget_get_font_metrics (widget, context); + + *minimum = *natural = (pango_font_metrics_get_ascent (metrics) + + pango_font_metrics_get_descent (metrics)) / PANGO_SCALE; + + pango_font_metrics_unref (metrics); +} + +static void +ido_detail_label_class_init (IdoDetailLabelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = ido_detail_label_get_property; + object_class->set_property = ido_detail_label_set_property; + object_class->finalize = ido_detail_label_finalize; + object_class->dispose = ido_detail_label_dispose; + + widget_class->draw = ido_detail_label_draw; + widget_class->get_preferred_width = ido_detail_label_get_preferred_width; + widget_class->get_preferred_height = ido_detail_label_get_preferred_height; + + g_type_class_add_private (klass, sizeof (IdoDetailLabelPrivate)); + + properties[PROP_TEXT] = g_param_spec_string ("text", + "Text", + "The text of the label", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); +} + +static void +ido_detail_label_init (IdoDetailLabel *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + IDO_TYPE_DETAIL_LABEL, + IdoDetailLabelPrivate); + + gtk_widget_set_has_window (GTK_WIDGET (self), FALSE); +} + +GtkWidget * +ido_detail_label_new (const gchar *label) +{ + return g_object_new (IDO_TYPE_DETAIL_LABEL, + "text", label, + NULL); +} + +const gchar * +ido_detail_label_get_text (IdoDetailLabel *label) +{ + g_return_val_if_fail (IDO_IS_DETAIL_LABEL (label), NULL); + return label->priv->text; +} + +/* collapse_whitespace: + * @str: the source string + * + * Collapses all occurences of consecutive whitespace charactes in @str + * into a single space. + * + * Returns: (transfer full): a newly-allocated string + */ +static gchar * +collapse_whitespace (const gchar *str) +{ + GString *result; + gboolean in_space = FALSE; + + if (str == NULL) + return NULL; + + result = g_string_new (""); + + while (*str) + { + gunichar c = g_utf8_get_char_validated (str, -1); + + if (c < 0) + break; + + if (!g_unichar_isspace (c)) + { + g_string_append_unichar (result, c); + in_space = FALSE; + } + else if (!in_space) + { + g_string_append_c (result, ' '); + in_space = TRUE; + } + + str = g_utf8_next_char (str); + } + + return g_string_free (result, FALSE); +} + +static void +ido_detail_label_set_text_impl (IdoDetailLabel *label, + const gchar *text, + gboolean draw_lozenge) +{ + IdoDetailLabelPrivate * priv = label->priv; + + g_clear_object (&priv->layout); + g_free (priv->text); + + priv->text = g_strdup (text); + priv->draw_lozenge = draw_lozenge; + + g_object_notify_by_pspec (G_OBJECT (label), properties[PROP_TEXT]); + gtk_widget_queue_resize (GTK_WIDGET (label)); +} + +void +ido_detail_label_set_text (IdoDetailLabel *label, + const gchar *text) +{ + gchar *str; + + g_return_if_fail (IDO_IS_DETAIL_LABEL (label)); + + str = collapse_whitespace (text); + ido_detail_label_set_text_impl (label, str, FALSE); + g_free (str); +} + +void +ido_detail_label_set_count (IdoDetailLabel *label, + gint count) +{ + gchar *text; + + g_return_if_fail (IDO_IS_DETAIL_LABEL (label)); + + text = g_strdup_printf ("%d", count); + ido_detail_label_set_text_impl (label, text, TRUE); + g_free (text); +} diff --git a/src/ido-detail-label.h b/src/ido-detail-label.h new file mode 100644 index 0000000..1995fee --- /dev/null +++ b/src/ido-detail-label.h @@ -0,0 +1,59 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#ifndef __IDO_DETAIL_LABEL_H__ +#define __IDO_DETAIL_LABEL_H__ + +#include <gtk/gtk.h> + +#define IDO_TYPE_DETAIL_LABEL (ido_detail_label_get_type()) +#define IDO_DETAIL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_TYPE_DETAIL_LABEL, IdoDetailLabel)) +#define IDO_DETAIL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IDO_TYPE_DETAIL_LABEL, IdoDetailLabelClass)) +#define IDO_IS_DETAIL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_TYPE_DETAIL_LABEL)) +#define IDO_IS_DETAIL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IDO_TYPE_DETAIL_LABEL)) +#define IDO_DETAIL_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IDO_TYPE_DETAIL_LABEL, IdoDetailLabelClass)) + +typedef struct _IdoDetailLabel IdoDetailLabel; +typedef struct _IdoDetailLabelClass IdoDetailLabelClass; +typedef struct _IdoDetailLabelPrivate IdoDetailLabelPrivate; + +struct _IdoDetailLabel +{ + GtkWidget parent; + IdoDetailLabelPrivate *priv; +}; + +struct _IdoDetailLabelClass +{ + GtkWidgetClass parent_class; +}; + +GType ido_detail_label_get_type (void) G_GNUC_CONST; + +GtkWidget * ido_detail_label_new (const gchar *str); + +const gchar * ido_detail_label_get_text (IdoDetailLabel *label); + +void ido_detail_label_set_text (IdoDetailLabel *label, + const gchar *text); + +void ido_detail_label_set_count (IdoDetailLabel *label, + gint count); + +#endif diff --git a/src/ido-menu-item.c b/src/ido-menu-item.c index 6b19d2a..32044ff 100644 --- a/src/ido-menu-item.c +++ b/src/ido-menu-item.c @@ -99,8 +99,37 @@ ido_menu_item_set_state (IdoMenuItem *self, if (priv->target) { ido_menu_item_set_has_indicator (self, TRUE); + ido_menu_item_set_active (self, FALSE); gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (self), TRUE); - ido_menu_item_set_active (self, g_variant_equal (priv->target, state)); + gtk_check_menu_item_set_inconsistent (GTK_CHECK_MENU_ITEM (self), FALSE); + + if (g_variant_is_of_type (state, G_VARIANT_TYPE_STRING)) + { + ido_menu_item_set_active (self, g_variant_equal (priv->target, state)); + } + else if (g_variant_is_of_type (state, G_VARIANT_TYPE ("as")) && + g_variant_is_of_type (priv->target, G_VARIANT_TYPE_STRING)) + { + const gchar *target_str; + const gchar **state_strs; + const gchar **it; + + target_str = g_variant_get_string (priv->target, NULL); + state_strs = g_variant_get_strv (state, NULL); + + it = state_strs; + while (*it != NULL && !g_str_equal (*it, target_str)) + it++; + + if (*it != NULL) + { + ido_menu_item_set_active (self, TRUE); + gtk_check_menu_item_set_inconsistent (GTK_CHECK_MENU_ITEM (self), + g_strv_length ((gchar **)state_strs) > 1); + } + + g_free (state_strs); + } } else if (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN)) { @@ -258,7 +287,8 @@ ido_menu_item_activate (GtkMenuItem *item) if (!priv->in_set_active && priv->action && priv->action_group) g_action_group_activate_action (priv->action_group, priv->action, priv->target); - GTK_MENU_ITEM_CLASS (ido_menu_item_parent_class)->activate (item); + if (priv->in_set_active) + GTK_MENU_ITEM_CLASS (ido_menu_item_parent_class)->activate (item); } static void diff --git a/src/im-source-menu-item.c b/src/im-source-menu-item.c index 5aebb68..775fa91 100644 --- a/src/im-source-menu-item.c +++ b/src/im-source-menu-item.c @@ -20,6 +20,7 @@ #include "im-source-menu-item.h" #include <libintl.h> +#include "ido-detail-label.h" struct _ImSourceMenuItemPrivate { @@ -59,11 +60,13 @@ im_source_menu_item_constructed (GObject *object) gtk_widget_set_margin_left (priv->icon, icon_width + 6); priv->label = g_object_ref (gtk_label_new ("")); + gtk_label_set_max_width_chars (GTK_LABEL (priv->label), 40); + gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_END); + gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5); - priv->detail = g_object_ref (gtk_label_new ("")); + priv->detail = g_object_ref (ido_detail_label_new ("")); gtk_widget_set_halign (priv->detail, GTK_ALIGN_END); gtk_widget_set_hexpand (priv->detail, TRUE); - gtk_misc_set_alignment (GTK_MISC (priv->label), 1.0, 0.5); gtk_style_context_add_class (gtk_widget_get_style_context (priv->detail), "accelerator"); grid = gtk_grid_new (); @@ -77,49 +80,6 @@ im_source_menu_item_constructed (GObject *object) G_OBJECT_CLASS (im_source_menu_item_parent_class)->constructed (object); } -/* collapse_whitespace: - * @str: the source string - * - * Collapses all occurences of consecutive whitespace charactes in @str - * into a single space. - * - * Returns: (transfer full): a newly-allocated string - */ -static gchar * -collapse_whitespace (const gchar *str) -{ - GString *result; - gboolean in_space = FALSE; - - if (str == NULL) - return NULL; - - result = g_string_new (""); - - while (*str) - { - gunichar c = g_utf8_get_char_validated (str, -1); - - if (c < 0) - break; - - if (!g_unichar_isspace (c)) - { - g_string_append_unichar (result, c); - in_space = FALSE; - } - else if (!in_space) - { - g_string_append_c (result, ' '); - in_space = TRUE; - } - - str = g_utf8_next_char (str); - } - - return g_string_free (result, FALSE); -} - static gchar * im_source_menu_item_time_span_string (gint64 timestamp) { @@ -146,16 +106,28 @@ im_source_menu_item_time_span_string (gint64 timestamp) return str; } +static void +im_source_menu_item_set_detail_time (ImSourceMenuItem *self, + gint64 time) +{ + ImSourceMenuItemPrivate *priv = self->priv; + gchar *str; + + priv->time = time; + + str = im_source_menu_item_time_span_string (priv->time); + ido_detail_label_set_text (IDO_DETAIL_LABEL (priv->detail), str); + + g_free (str); +} + static gboolean im_source_menu_item_update_time (gpointer data) { ImSourceMenuItem *self = data; - gchar *str; - str = im_source_menu_item_time_span_string (self->priv->time); - gtk_label_set_text (GTK_LABEL (self->priv->detail), str); + im_source_menu_item_set_detail_time (self, self->priv->time); - g_free (str); return TRUE; } @@ -167,7 +139,6 @@ im_source_menu_item_set_state (ImSourceMenuItem *self, guint32 count; gint64 time; const gchar *str; - gchar *detail; if (priv->timer_id != 0) { @@ -180,21 +151,15 @@ im_source_menu_item_set_state (ImSourceMenuItem *self, g_variant_get (state, "(ux&sb)", &count, &time, &str, NULL); if (count != 0) - detail = g_strdup_printf ("%d", count); + ido_detail_label_set_count (IDO_DETAIL_LABEL (priv->detail), count); else if (time != 0) { - priv->time = time; - detail = im_source_menu_item_time_span_string (time); + im_source_menu_item_set_detail_time (self, time); priv->timer_id = g_timeout_add_seconds (59, im_source_menu_item_update_time, self); } else if (str != NULL && *str) - detail = collapse_whitespace (str); - else - detail = NULL; - - gtk_label_set_text (GTK_LABEL (priv->detail), detail ? detail : ""); + ido_detail_label_set_text (IDO_DETAIL_LABEL (priv->detail), str); - g_free (detail); return TRUE; } diff --git a/src/indicator-messages-service.c b/src/indicator-messages-service.c index a42a534..7f2ab87 100644 --- a/src/indicator-messages-service.c +++ b/src/indicator-messages-service.c @@ -1,5 +1,5 @@ /* - * Generated by gdbus-codegen 2.33.10. DO NOT EDIT. + * Generated by gdbus-codegen 2.33.12. DO NOT EDIT. * * The license of this code is the same as for the source it was derived from. */ @@ -235,6 +235,17 @@ static const _ExtendedGDBusMethodInfo _indicator_messages_service_method_info_un FALSE }; +static const _ExtendedGDBusArgInfo _indicator_messages_service_method_info_set_status_IN_ARG_desktop_id = +{ + { + -1, + (gchar *) "desktop_id", + (gchar *) "s", + NULL + }, + FALSE +}; + static const _ExtendedGDBusArgInfo _indicator_messages_service_method_info_set_status_IN_ARG_status = { { @@ -248,6 +259,7 @@ static const _ExtendedGDBusArgInfo _indicator_messages_service_method_info_set_s static const _ExtendedGDBusArgInfo * const _indicator_messages_service_method_info_set_status_IN_ARG_pointers[] = { + &_indicator_messages_service_method_info_set_status_IN_ARG_desktop_id, &_indicator_messages_service_method_info_set_status_IN_ARG_status, NULL }; @@ -424,6 +436,7 @@ indicator_messages_service_default_init (IndicatorMessagesServiceIface *iface) * IndicatorMessagesService::handle-set-status: * @object: A #IndicatorMessagesService. * @invocation: A #GDBusMethodInvocation. + * @arg_desktop_id: Argument passed by remote caller. * @arg_status: Argument passed by remote caller. * * Signal emitted when a remote caller is invoking the <link linkend="gdbus-method-com-canonical-indicator-messages-service.SetStatus">SetStatus()</link> D-Bus method. @@ -440,8 +453,8 @@ indicator_messages_service_default_init (IndicatorMessagesServiceIface *iface) NULL, g_cclosure_marshal_generic, G_TYPE_BOOLEAN, - 2, - G_TYPE_DBUS_METHOD_INVOCATION, G_TYPE_STRING); + 3, + G_TYPE_DBUS_METHOD_INVOCATION, G_TYPE_STRING, G_TYPE_STRING); /* GObject signals for received D-Bus signals: */ /** @@ -688,6 +701,7 @@ _out: /** * indicator_messages_service_call_set_status: * @proxy: A #IndicatorMessagesServiceProxy. + * @arg_desktop_id: Argument to pass with the method invocation. * @arg_status: Argument to pass with the method invocation. * @cancellable: (allow-none): A #GCancellable or %NULL. * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. @@ -702,6 +716,7 @@ _out: void indicator_messages_service_call_set_status ( IndicatorMessagesService *proxy, + const gchar *arg_desktop_id, const gchar *arg_status, GCancellable *cancellable, GAsyncReadyCallback callback, @@ -709,7 +724,8 @@ indicator_messages_service_call_set_status ( { g_dbus_proxy_call (G_DBUS_PROXY (proxy), "SetStatus", - g_variant_new ("(s)", + g_variant_new ("(ss)", + arg_desktop_id, arg_status), G_DBUS_CALL_FLAGS_NONE, -1, @@ -748,6 +764,7 @@ _out: /** * indicator_messages_service_call_set_status_sync: * @proxy: A #IndicatorMessagesServiceProxy. + * @arg_desktop_id: Argument to pass with the method invocation. * @arg_status: Argument to pass with the method invocation. * @cancellable: (allow-none): A #GCancellable or %NULL. * @error: Return location for error or %NULL. @@ -761,6 +778,7 @@ _out: gboolean indicator_messages_service_call_set_status_sync ( IndicatorMessagesService *proxy, + const gchar *arg_desktop_id, const gchar *arg_status, GCancellable *cancellable, GError **error) @@ -768,7 +786,8 @@ indicator_messages_service_call_set_status_sync ( GVariant *_ret; _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), "SetStatus", - g_variant_new ("(s)", + g_variant_new ("(ss)", + arg_desktop_id, arg_status), G_DBUS_CALL_FLAGS_NONE, -1, diff --git a/src/indicator-messages-service.h b/src/indicator-messages-service.h index 2adaaf3..2401d0e 100644 --- a/src/indicator-messages-service.h +++ b/src/indicator-messages-service.h @@ -1,5 +1,5 @@ /* - * Generated by gdbus-codegen 2.33.10. DO NOT EDIT. + * Generated by gdbus-codegen 2.33.12. DO NOT EDIT. * * The license of this code is the same as for the source it was derived from. */ @@ -38,6 +38,7 @@ struct _IndicatorMessagesServiceIface gboolean (*handle_set_status) ( IndicatorMessagesService *object, GDBusMethodInvocation *invocation, + const gchar *arg_desktop_id, const gchar *arg_status); gboolean (*handle_unregister_application) ( @@ -120,6 +121,7 @@ gboolean indicator_messages_service_call_unregister_application_sync ( void indicator_messages_service_call_set_status ( IndicatorMessagesService *proxy, + const gchar *arg_desktop_id, const gchar *arg_status, GCancellable *cancellable, GAsyncReadyCallback callback, @@ -132,6 +134,7 @@ gboolean indicator_messages_service_call_set_status_finish ( gboolean indicator_messages_service_call_set_status_sync ( IndicatorMessagesService *proxy, + const gchar *arg_desktop_id, const gchar *arg_status, GCancellable *cancellable, GError **error); diff --git a/src/indicator-messages.c b/src/indicator-messages.c index 942e46f..5c5df31 100644 --- a/src/indicator-messages.c +++ b/src/indicator-messages.c @@ -58,7 +58,6 @@ struct _IndicatorMessages { IndicatorObject parent; IndicatorServiceManager * service; GActionGroup *actions; - GMenu *menu_wrapper; GMenuModel *menu; GtkWidget *image; GtkWidget *gtkmenu; @@ -83,17 +82,19 @@ static GtkImage * get_image (IndicatorObject * io); static GtkMenu * get_menu (IndicatorObject * io); static const gchar * get_accessible_desc (IndicatorObject * io); static const gchar * get_name_hint (IndicatorObject * io); -static void update_root_item (IndicatorMessages * self); -static void update_menu (IndicatorMessages *self); static void menu_items_changed (GMenuModel *menu, gint position, gint removed, gint added, gpointer user_data); +static void messages_action_added (GActionGroup *action_group, + gchar *action_name, + gpointer user_data); static void messages_state_changed (GActionGroup *action_group, gchar *action_name, GVariant *value, gpointer user_data); +static void indicator_messages_add_toplevel_menu (IndicatorMessages *self); G_DEFINE_TYPE (IndicatorMessages, indicator_messages, INDICATOR_OBJECT_TYPE); @@ -122,8 +123,7 @@ indicator_messages_init (IndicatorMessages *self) g_signal_connect (self->service, "connection-change", G_CALLBACK (service_connection_changed), self); - self->menu_wrapper = g_menu_new (); - self->gtkmenu = gtk_menu_new_from_model (G_MENU_MODEL (self->menu_wrapper)); + self->gtkmenu = gtk_menu_new (); g_object_ref_sink (self->gtkmenu); self->image = g_object_ref_sink (gtk_image_new ()); @@ -143,7 +143,6 @@ indicator_messages_dispose (GObject *object) g_return_if_fail(self != NULL); g_clear_object (&self->service); - g_clear_object (&self->menu_wrapper); g_clear_object (&self->actions); g_clear_object (&self->menu); g_clear_object (&self->gtkmenu); @@ -178,6 +177,7 @@ static void service_connection_changed (IndicatorServiceManager *sm, GError *error = NULL; if (self->actions != NULL) { + g_signal_handlers_disconnect_by_func (self->actions, messages_action_added, self); g_signal_handlers_disconnect_by_func (self->actions, messages_state_changed, self); g_clear_object (&self->actions); } @@ -185,8 +185,7 @@ static void service_connection_changed (IndicatorServiceManager *sm, g_signal_handlers_disconnect_by_func (self->menu, menu_items_changed, self); g_clear_object (&self->menu); } - if (g_menu_model_get_n_items (G_MENU_MODEL (self->menu_wrapper)) == 1) - g_menu_remove (self->menu_wrapper, 0); + gtk_menu_shell_bind_model (GTK_MENU_SHELL (self->gtkmenu), NULL, NULL, FALSE); if (connected == FALSE) return; @@ -204,6 +203,8 @@ static void service_connection_changed (IndicatorServiceManager *sm, gtk_widget_insert_action_group (self->gtkmenu, get_name_hint (INDICATOR_OBJECT (self)), self->actions); + g_signal_connect (self->actions, "action-added::messages", + G_CALLBACK (messages_action_added), self); g_signal_connect (self->actions, "action-state-changed::messages", G_CALLBACK (messages_state_changed), self); @@ -212,8 +213,10 @@ static void service_connection_changed (IndicatorServiceManager *sm, INDICATOR_MESSAGES_DBUS_OBJECT)); g_signal_connect (self->menu, "items-changed", G_CALLBACK (menu_items_changed), self); - update_root_item (self); - update_menu (self); + if (g_menu_model_get_n_items (self->menu) == 1) + indicator_messages_add_toplevel_menu (self); + else + indicator_object_set_visible (INDICATOR_OBJECT (self), FALSE); g_object_unref (bus); } @@ -261,24 +264,19 @@ indicator_messages_accessible_desc_updated (IndicatorMessages *self) g_list_free (entries); } -static void -update_root_item (IndicatorMessages * self) +static GIcon * +g_menu_model_get_item_attribute_icon (GMenuModel *menu, + gint index, + const gchar *attribute) { gchar *iconstr; + GIcon *icon = NULL; - if (g_menu_model_get_n_items (self->menu) == 0) - return; - - if (g_menu_model_get_item_attribute (self->menu, 0, "x-canonical-icon", "s", &iconstr)) { - GIcon *icon; + if (g_menu_model_get_item_attribute (menu, index, attribute, "s", &iconstr)) { GError *error; icon = g_icon_new_for_string (iconstr, &error); - if (icon) { - gtk_image_set_from_gicon (GTK_IMAGE (self->image), icon, GTK_ICON_SIZE_MENU); - g_object_unref (icon); - } - else { + if (icon == NULL) { g_warning ("unable to load icon: %s", error->message); g_error_free (error); } @@ -286,37 +284,39 @@ update_root_item (IndicatorMessages * self) g_free (iconstr); } - g_free (self->accessible_desc); - self->accessible_desc = NULL; - - g_menu_model_get_item_attribute (self->menu, 0, "x-canonical-accessible-description", - "s", &self->accessible_desc); - indicator_messages_accessible_desc_updated (self); + return icon; } static void -update_menu (IndicatorMessages *self) +indicator_messages_add_toplevel_menu (IndicatorMessages *self) { + GIcon *icon; GMenuModel *popup; - GMenuItem *item; - if (self->menu == NULL || g_menu_model_get_n_items (self->menu) == 0) - return; + indicator_object_set_visible (INDICATOR_OBJECT (self), TRUE); - popup = g_menu_model_get_item_link (self->menu, 0, G_MENU_LINK_SUBMENU); - if (popup == NULL) - return; + icon = g_menu_model_get_item_attribute_icon (self->menu, 0, "x-canonical-icon"); + if (icon) { + gtk_image_set_from_gicon (GTK_IMAGE (self->image), icon, GTK_ICON_SIZE_LARGE_TOOLBAR); + g_object_unref (icon); + } - if (g_menu_model_get_n_items (G_MENU_MODEL (self->menu_wrapper)) == 1) - g_menu_remove (self->menu_wrapper, 0); + g_free (self->accessible_desc); + self->accessible_desc = NULL; + if (g_menu_model_get_item_attribute (self->menu, 0, "x-canonical-accessible-description", + "s", &self->accessible_desc)) { + indicator_messages_accessible_desc_updated (self); + } - item = g_menu_item_new_section (NULL, popup); - g_menu_item_set_attribute (item, "action-namespace", - "s", get_name_hint (INDICATOR_OBJECT (self))); - g_menu_append_item (self->menu_wrapper, item); + popup = g_menu_model_get_item_link (self->menu, 0, G_MENU_LINK_SUBMENU); + if (popup) { + gtk_menu_shell_bind_model (GTK_MENU_SHELL (self->gtkmenu), + popup, + get_name_hint (INDICATOR_OBJECT (self)), + TRUE); - g_object_unref (item); - g_object_unref (popup); + g_object_unref (popup); + } } static void @@ -328,13 +328,49 @@ menu_items_changed (GMenuModel *menu, { IndicatorMessages *self = user_data; - if (position == 0) { - update_root_item (self); - update_menu (self); + g_return_if_fail (position == 0); + + if (added == 1) + indicator_messages_add_toplevel_menu (self); + else if (removed == 1) + indicator_object_set_visible (INDICATOR_OBJECT (self), FALSE); +} + +static void +indicator_messages_update_icon (IndicatorMessages *self, + GVariant *state) +{ + GIcon *icon; + GError *error = NULL; + + g_return_if_fail (g_variant_is_of_type (state, G_VARIANT_TYPE_STRING)); + + icon = g_icon_new_for_string (g_variant_get_string (state, NULL), &error); + if (icon == NULL) { + g_warning ("unable to load icon: %s", error->message); + g_error_free (error); + } + else { + gtk_image_set_from_gicon (GTK_IMAGE (self->image), icon, GTK_ICON_SIZE_LARGE_TOOLBAR); + g_object_unref (icon); } } static void +messages_action_added (GActionGroup *action_group, + gchar *action_name, + gpointer user_data) +{ + IndicatorMessages *self = user_data; + GVariant *state; + + state = g_action_group_get_action_state (action_group, "messages"); + indicator_messages_update_icon (self, state); + + g_variant_unref (state); +} + +static void messages_state_changed (GActionGroup *action_group, gchar *action_name, GVariant *value, @@ -342,10 +378,5 @@ messages_state_changed (GActionGroup *action_group, { IndicatorMessages *self = user_data; - g_return_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)); - - if (g_variant_get_boolean (value)) - gtk_image_set_from_icon_name (GTK_IMAGE (self->image), "indicator-messages-new", GTK_ICON_SIZE_MENU); - else - gtk_image_set_from_icon_name (GTK_IMAGE (self->image), "indicator-messages", GTK_ICON_SIZE_MENU); + indicator_messages_update_icon (self, value); } diff --git a/src/messages-service.c b/src/messages-service.c index 15c5123..b36a0a2 100644 --- a/src/messages-service.c +++ b/src/messages-service.c @@ -35,16 +35,67 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include "gmenuutils.h" #include "indicator-messages-service.h" +#define NUM_STATUSES 5 + static GHashTable *applications; -IndicatorMessagesService *messages_service; +static IndicatorMessagesService *messages_service; static GSimpleActionGroup *actions; static GActionMuxer *action_muxer; static GMenu *toplevel_menu; static GMenu *menu; static GMenuModel *chat_section; static GSettings *settings; +static gboolean draws_attention; +static const gchar *global_status[6]; /* max 5: available, away, busy, invisible, offline */ + +static gchar * +indicator_messages_get_icon_name () +{ + GString *name; + GIcon *icon; + gchar *iconstr; + + name = g_string_new ("indicator-messages"); + + if (global_status[0] != NULL) + { + if (global_status[1] != NULL) + g_string_append (name, "-mixed"); + else + g_string_append_printf (name, "-%s", global_status[0]); + } + + if (draws_attention) + g_string_append (name, "-new"); + + icon = g_themed_icon_new (name->str); + g_themed_icon_append_name (G_THEMED_ICON (icon), + draws_attention ? "indicator-messages-new" + : "indicator-messages"); + + iconstr = g_icon_to_string (icon); + + g_object_unref (icon); + g_string_free (name, TRUE); + + return iconstr; +} + +static void +indicator_messages_update_icon () +{ + GSimpleAction *messages; + gchar *icon; + messages = G_SIMPLE_ACTION (g_simple_action_group_lookup (actions, "messages")); + g_return_if_fail (messages != NULL); + + icon = indicator_messages_get_icon_name (); + g_simple_action_set_state (messages, g_variant_new_string (icon)); + + g_free (icon); +} static gchar * g_app_info_get_simple_id (GAppInfo *appinfo) @@ -92,18 +143,16 @@ draws_attention_changed (GObject *object, GParamSpec *pspec, gpointer user_data) { - GSimpleAction *messages; GSimpleAction *clear; - gboolean attention; - messages = G_SIMPLE_ACTION (g_simple_action_group_lookup (actions, "messages")); clear = G_SIMPLE_ACTION (g_simple_action_group_lookup (actions, "clear")); - g_return_if_fail (messages != NULL && clear != NULL); + g_return_if_fail (clear != NULL); + + draws_attention = g_hash_table_find (applications, app_section_draws_attention, NULL) != NULL; - attention = g_hash_table_find (applications, app_section_draws_attention, NULL) != NULL; + g_simple_action_set_enabled (clear, draws_attention); - g_simple_action_set_state (messages, g_variant_new_boolean (attention)); - g_simple_action_set_enabled (clear, attention); + indicator_messages_update_icon (); } static gboolean @@ -116,9 +165,7 @@ app_section_uses_chat (gpointer key, } static void -uses_chat_status_changed (GObject *object, - GParamSpec *pspec, - gpointer user_data) +update_chat_section () { gboolean show_chat; GMenuModel *first_section; @@ -135,7 +182,99 @@ uses_chat_status_changed (GObject *object, g_menu_insert_section (menu, 0, NULL, chat_section); } - g_object_unref (first_section); + if (first_section != NULL) + g_object_unref (first_section); + + indicator_messages_update_icon (); +} + +static void +uses_chat_status_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + update_chat_section (); +} + +static gboolean +strv_contains (const gchar **strv, + const gchar *needle) +{ + const gchar **it; + + it = strv; + while (*it != NULL && !g_str_equal (*it, needle)) + it++; + + return *it != NULL; +} + +static void +update_chat_status () +{ + GHashTableIter iter; + AppSection *section; + int pos; + GAction *status; + + for (pos = 0; pos < G_N_ELEMENTS (global_status); pos++) + global_status[pos] = NULL; + + pos = 0; + g_hash_table_iter_init (&iter, applications); + while (g_hash_table_iter_next (&iter, NULL, (gpointer) §ion) && + pos < G_N_ELEMENTS (global_status)) + { + const gchar *status_str = NULL; + + status_str = app_section_get_status (section); + if (status_str != NULL && !strv_contains (global_status, status_str)) + global_status[pos++] = status_str; + } + + if (pos == 0) + global_status[0] = "offline"; + + status = g_simple_action_group_lookup (actions, "status"); + g_return_if_fail (status != NULL); + + g_simple_action_set_state (G_SIMPLE_ACTION (status), g_variant_new_strv (global_status, -1)); + + indicator_messages_update_icon (); +} + +static void +chat_status_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + update_chat_status (); +} + +static void +remove_section (AppSection *section, + const gchar *id) +{ + int pos = g_menu_find_section (menu, app_section_get_menu (section)); + if (pos >= 0) + g_menu_remove (menu, pos); + g_action_muxer_remove (action_muxer, id); + + g_signal_handlers_disconnect_by_func (section, actions_changed, NULL); + g_signal_handlers_disconnect_by_func (section, draws_attention_changed, NULL); + g_signal_handlers_disconnect_by_func (section, uses_chat_status_changed, NULL); + g_signal_handlers_disconnect_by_func (section, chat_status_changed, NULL); + g_signal_handlers_disconnect_by_func (section, remove_section, NULL); + + g_hash_table_remove (applications, id); + + if (g_hash_table_size (applications) == 0 && + g_menu_model_get_n_items (G_MENU_MODEL (toplevel_menu)) == 1) { + g_menu_remove (toplevel_menu, 0); + } + + update_chat_status (); + update_chat_section (); } static AppSection * @@ -167,6 +306,13 @@ add_application (const gchar *desktop_id) G_CALLBACK (draws_attention_changed), NULL); g_signal_connect (section, "notify::uses-chat-status", G_CALLBACK (uses_chat_status_changed), NULL); + g_signal_connect (section, "notify::chat-status", + G_CALLBACK (chat_status_changed), NULL); + g_signal_connect_data (section, "destroy", + G_CALLBACK (remove_section), + g_strdup (id), + (GClosureNotify) g_free, + 0); /* TODO insert it at the right position (alphabetically by application name) */ menuitem = g_menu_item_new_section (NULL, app_section_get_menu (section)); @@ -175,6 +321,17 @@ add_application (const gchar *desktop_id) g_object_unref (menuitem); } + if (g_menu_model_get_n_items (G_MENU_MODEL (toplevel_menu)) == 0) { + GMenuItem *header; + + header = g_menu_item_new (NULL, "messages"); + g_menu_item_set_submenu (header, G_MENU_MODEL (menu)); + g_menu_item_set_attribute (header, "x-canonical-accessible-description", "s", _("Messages")); + g_menu_append_item (toplevel_menu, header); + + g_object_unref (header); + } + g_free (id); g_object_unref (appinfo); return section; @@ -197,20 +354,12 @@ remove_application (const char *desktop_id) section = g_hash_table_lookup (applications, id); if (section) { - int pos = g_menu_find_section (menu, app_section_get_menu (section)); - if (pos >= 0) - g_menu_remove (menu, pos); - g_action_muxer_remove (action_muxer, id); - - g_signal_handlers_disconnect_by_func (section, actions_changed, NULL); - g_signal_handlers_disconnect_by_func (section, draws_attention_changed, NULL); - g_signal_handlers_disconnect_by_func (section, uses_chat_status_changed, NULL); + remove_section (section, id); } else { g_warning ("could not remove '%s', it's not registered", desktop_id); } - g_hash_table_remove (applications, id); g_free (id); g_object_unref (appinfo); } @@ -276,48 +425,15 @@ clear_action_activate (GSimpleAction *simple, } static void -radio_item_activate (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - g_action_change_state (G_ACTION (action), parameter); -} - -static gboolean -g_action_state_equal (GAction *action, - GVariant *value) -{ - GVariant *state; - gboolean eq; - - state = g_action_get_state (action); - g_return_val_if_fail (state != NULL, FALSE); - - eq = g_variant_equal (state, value); - - g_variant_unref (state); - return eq; -} - -static void -change_status_action (GSimpleAction *action, - GVariant *value, - gpointer user_data) +status_action_activate (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { const gchar *status; - g_variant_get (value, "&s", &status); + status = g_variant_get_string (parameter, NULL); - g_return_if_fail (g_str_equal (status, "available") || - g_str_equal (status, "away")|| - g_str_equal (status, "busy") || - g_str_equal (status, "invisible") || - g_str_equal (status, "offline")); - - if (!g_action_state_equal (G_ACTION (action), value)) { - g_simple_action_set_state (action, value); - indicator_messages_service_emit_status_changed (messages_service, status); - } + indicator_messages_service_emit_status_changed (messages_service, status); } static void @@ -359,17 +475,35 @@ unregister_application (IndicatorMessagesService *service, static void set_status (IndicatorMessagesService *service, GDBusMethodInvocation *invocation, + const gchar *desktop_id, const gchar *status_str, gpointer user_data) { - GAction *status; + GDesktopAppInfo *appinfo; + gchar *id; + AppSection *section; - status = g_simple_action_group_lookup (actions, "status"); - g_return_if_fail (status != NULL); + g_return_if_fail (g_str_equal (status_str, "available") || + g_str_equal (status_str, "away")|| + g_str_equal (status_str, "busy") || + g_str_equal (status_str, "invisible") || + g_str_equal (status_str, "offline")); - g_action_change_state (status, g_variant_new_string (status_str)); + appinfo = g_desktop_app_info_new (desktop_id); + if (!appinfo) { + g_warning ("could not set status for '%s', there's no desktop file with that id", desktop_id); + return; + } + + id = g_app_info_get_simple_id (G_APP_INFO (appinfo)); + section = g_hash_table_lookup (applications, id); + if (section != NULL) + app_section_set_status (section, status_str); indicator_messages_service_complete_set_status (service, invocation); + + g_free (id); + g_object_unref (appinfo); } static GSimpleActionGroup * @@ -379,17 +513,19 @@ create_action_group (void) GSimpleAction *messages; GSimpleAction *clear; GSimpleAction *status; + const gchar *default_status[] = { "offline", NULL }; + gchar *icon; actions = g_simple_action_group_new (); - /* state of the messages action mirrors "draws-attention" */ - messages = g_simple_action_new_stateful ("messages", G_VARIANT_TYPE ("b"), - g_variant_new_boolean (FALSE)); + /* state of the messages action is its icon name */ + icon = indicator_messages_get_icon_name (); + messages = g_simple_action_new_stateful ("messages", G_VARIANT_TYPE ("s"), + g_variant_new_string (icon)); status = g_simple_action_new_stateful ("status", G_VARIANT_TYPE ("s"), - g_variant_new ("s", "offline")); - g_signal_connect (status, "activate", G_CALLBACK (radio_item_activate), NULL); - g_signal_connect (status, "change-state", G_CALLBACK (change_status_action), NULL); + g_variant_new_strv (default_status, -1)); + g_signal_connect (status, "activate", G_CALLBACK (status_action_activate), NULL); clear = g_simple_action_new ("clear", NULL); g_simple_action_set_enabled (clear, FALSE); @@ -399,6 +535,7 @@ create_action_group (void) g_simple_action_group_insert (actions, G_ACTION (status)); g_simple_action_group_insert (actions, G_ACTION (clear)); + g_free (icon); return actions; } @@ -484,9 +621,6 @@ main (int argc, char ** argv) { GMainLoop * mainloop = NULL; IndicatorService * service = NULL; - GMenuItem *header; - GIcon *icon; - gchar *iconstr; /* Glib init */ g_type_init(); @@ -524,16 +658,7 @@ main (int argc, char ** argv) chat_section = create_status_section (); g_menu_append (menu, _("Clear"), "clear"); - icon = g_themed_icon_new ("indicator-messages"); - iconstr = g_icon_to_string (icon); - toplevel_menu = g_menu_new (); - header = g_menu_item_new (NULL, "messages"); - g_menu_item_set_submenu (header, G_MENU_MODEL (menu)); - g_menu_item_set_attribute (header, "x-canonical-icon", "s", iconstr); - g_menu_item_set_attribute (header, "x-canonical-accessible-description", "s", _("Messages")); - g_menu_append_item (toplevel_menu, header); - g_object_unref (header); settings = g_settings_new ("com.canonical.indicator.messages"); @@ -544,8 +669,6 @@ main (int argc, char ** argv) g_main_loop_run(mainloop); /* Clean up */ - g_free (iconstr); - g_object_unref (icon); g_object_unref (messages_service); g_object_unref (chat_section); g_object_unref (settings); diff --git a/src/messages-service.xml b/src/messages-service.xml index edd47c7..00ae154 100644 --- a/src/messages-service.xml +++ b/src/messages-service.xml @@ -12,6 +12,7 @@ </method> <method name="SetStatus"> + <arg type="s" name="desktop_id" direction="in" /> <arg type="s" name="status" direction="in" /> </method> |