aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am2
-rw-r--r--src/Makefile.in13
-rw-r--r--src/app-section.c193
-rw-r--r--src/app-section.h5
-rw-r--r--src/ido-detail-label.c396
-rw-r--r--src/ido-detail-label.h59
-rw-r--r--src/ido-menu-item.c34
-rw-r--r--src/im-source-menu-item.c83
-rw-r--r--src/indicator-messages-service.c29
-rw-r--r--src/indicator-messages-service.h5
-rw-r--r--src/indicator-messages.c135
-rw-r--r--src/messages-service.c289
-rw-r--r--src/messages-service.xml1
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) &section) &&
+ 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>