aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCharles Kerr <charles.kerr@canonical.com>2012-08-21 00:49:13 -0500
committerCharles Kerr <charles.kerr@canonical.com>2012-08-21 00:49:13 -0500
commit993109acc17163354749a7a3ba4c78ca657f5b14 (patch)
treeafdc3a1810b5c167ec2a6018f1d41f00544c4856 /src
parentd34fb7000e9b508b05c653b47bb60f35989c34f9 (diff)
parent91eb0b1a17c5bc9d0087fe807f373d1b8a8eb87b (diff)
downloadayatana-indicator-messages-993109acc17163354749a7a3ba4c78ca657f5b14.tar.gz
ayatana-indicator-messages-993109acc17163354749a7a3ba4c78ca657f5b14.tar.bz2
ayatana-indicator-messages-993109acc17163354749a7a3ba4c78ca657f5b14.zip
sync with lp:~larsu/indicator-messages/towards-q-redesign
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am4
-rw-r--r--src/app-section.c102
-rw-r--r--src/im-app-menu-item.c305
-rw-r--r--src/im-app-menu-item.h54
-rw-r--r--src/im-source-menu-item.c278
-rw-r--r--src/im-source-menu-item.h54
-rw-r--r--src/indicator-messages.c8
-rw-r--r--src/messages-service.c2
8 files changed, 767 insertions, 40 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 1bc5b59..403a289 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -15,6 +15,10 @@ messaginglibdir = $(INDICATORDIR)
messaginglib_LTLIBRARIES = libmessaging.la
libmessaging_la_SOURCES = \
indicator-messages.c \
+ im-app-menu-item.c \
+ im-app-menu-item.h \
+ im-source-menu-item.c \
+ im-source-menu-item.h \
indicator-messages-service.c \
indicator-messages-service.h
dbus-data.h
diff --git a/src/app-section.c b/src/app-section.c
index 0b6a4b1..1602ac6 100644
--- a/src/app-section.c
+++ b/src/app-section.c
@@ -32,6 +32,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include "app-section.h"
#include "dbus-data.h"
#include "gmenuutils.h"
+#include "gactionmuxer.h"
struct _AppSectionPrivate
{
@@ -41,10 +42,11 @@ struct _AppSectionPrivate
IndicatorDesktopShortcuts * ids;
GMenu *menu;
- GSimpleActionGroup *static_shortcuts;
+ GMenuModel *source_menu;
- GMenuModel *remote_menu;
- GActionGroup *actions;
+ GSimpleActionGroup *static_shortcuts;
+ GActionGroup *source_actions;
+ GActionMuxer *muxer;
gboolean draws_attention;
gboolean uses_chat_status;
@@ -78,6 +80,9 @@ static void app_section_dispose (GObject *object);
static void activate_cb (GSimpleAction *action,
GVariant *param,
gpointer userdata);
+static void launch_action_change_state (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data);
static void app_section_set_app_info (AppSection *self,
GDesktopAppInfo *appinfo);
static gboolean any_action_draws_attention (GActionGroup *group,
@@ -151,6 +156,9 @@ app_section_init (AppSection *self)
priv->menu = g_menu_new ();
priv->static_shortcuts = g_simple_action_group_new ();
+ priv->muxer = g_action_muxer_new ();
+ g_action_muxer_insert (priv->muxer, NULL, G_ACTION_GROUP (priv->static_shortcuts));
+
priv->draws_attention = FALSE;
return;
@@ -215,16 +223,17 @@ app_section_dispose (GObject *object)
priv->name_watch_id = 0;
}
- if (priv->actions) {
- g_object_disconnect (priv->actions,
+ if (priv->source_actions) {
+ g_action_muxer_remove (priv->muxer, "source");
+ g_object_disconnect (priv->source_actions,
"any_signal::action-added", action_added, self,
"any_signal::action-state-changed", action_state_changed, self,
"any_signal::action-removed", action_removed, self,
NULL);
- g_clear_object (&priv->actions);
+ g_clear_object (&priv->source_actions);
}
- g_clear_object (&priv->remote_menu);
+ g_clear_object (&priv->source_menu);
g_clear_object (&priv->ids);
g_clear_object (&priv->appinfo);
@@ -302,6 +311,8 @@ app_section_set_app_info (AppSection *self,
AppSectionPrivate *priv = self->priv;
GSimpleAction *launch;
GFile *keyfile;
+ GMenuItem *item;
+ gchar *iconname;
g_return_if_fail (priv->appinfo == NULL);
@@ -312,14 +323,19 @@ app_section_set_app_info (AppSection *self,
priv->appinfo = g_object_ref (appinfo);
- launch = g_simple_action_new ("launch", NULL);
+ launch = g_simple_action_new_stateful ("launch", NULL, g_variant_new_boolean (FALSE));
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));
- g_menu_append_with_icon (priv->menu,
- g_app_info_get_name (G_APP_INFO (priv->appinfo)),
- g_app_info_get_icon (G_APP_INFO (priv->appinfo)),
- "launch");
+ item = g_menu_item_new (g_app_info_get_name (G_APP_INFO (priv->appinfo)), "launch");
+ g_menu_item_set_attribute (item, "x-canonical-type", "s", "ImAppMenuItem");
+ iconname = g_icon_to_string (g_app_info_get_icon (G_APP_INFO (priv->appinfo)));
+ g_menu_item_set_attribute (item, INDICATOR_MENU_ATTRIBUTE_ICON_NAME, "s", iconname);
+ g_free (iconname);
+
+ g_menu_append_item (priv->menu, item);
+ g_object_unref (item);
/* Start to build static shortcuts */
priv->ids = indicator_desktop_shortcuts_new(g_desktop_app_info_get_filename (priv->appinfo), "Messaging Menu");
@@ -373,10 +389,17 @@ activate_cb (GSimpleAction *action,
}
}
+static void
+launch_action_change_state (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ g_simple_action_set_state (action, value);
+}
+
guint
app_section_get_count (AppSection * self)
{
- g_return_val_if_fail(IS_APP_SECTION(self), 0);
AppSectionPrivate * priv = self->priv;
return priv->unreadcount;
@@ -385,7 +408,6 @@ app_section_get_count (AppSection * self)
const gchar *
app_section_get_name (AppSection * self)
{
- g_return_val_if_fail(IS_APP_SECTION(self), NULL);
AppSectionPrivate * priv = self->priv;
if (priv->appinfo) {
@@ -397,7 +419,6 @@ app_section_get_name (AppSection * self)
const gchar *
app_section_get_desktop (AppSection * self)
{
- g_return_val_if_fail(IS_APP_SECTION(self), NULL);
AppSectionPrivate * priv = self->priv;
if (priv->appinfo)
return g_desktop_app_info_get_filename (priv->appinfo);
@@ -408,15 +429,13 @@ app_section_get_desktop (AppSection * self)
GActionGroup *
app_section_get_actions (AppSection *self)
{
- g_return_val_if_fail(IS_APP_SECTION(self), NULL);
AppSectionPrivate * priv = self->priv;
- return priv->actions ? priv->actions : G_ACTION_GROUP (priv->static_shortcuts);
+ return G_ACTION_GROUP (priv->muxer);
}
GMenuModel *
app_section_get_menu (AppSection *self)
{
- g_return_val_if_fail(IS_APP_SECTION(self), NULL);
AppSectionPrivate * priv = self->priv;
return G_MENU_MODEL (priv->menu);
}
@@ -424,7 +443,6 @@ app_section_get_menu (AppSection *self)
GAppInfo *
app_section_get_app_info (AppSection *self)
{
- g_return_val_if_fail(IS_APP_SECTION(self), NULL);
AppSectionPrivate * priv = self->priv;
return G_APP_INFO (priv->appinfo);
}
@@ -432,7 +450,6 @@ app_section_get_app_info (AppSection *self)
gboolean
app_section_get_draws_attention (AppSection *self)
{
- g_return_val_if_fail(IS_APP_SECTION(self), FALSE);
AppSectionPrivate * priv = self->priv;
return priv->draws_attention;
}
@@ -440,20 +457,19 @@ app_section_get_draws_attention (AppSection *self)
void
app_section_clear_draws_attention (AppSection *self)
{
- g_return_if_fail (IS_APP_SECTION(self));
AppSectionPrivate * priv = self->priv;
gchar **action_names;
gchar **it;
- if (priv->actions == NULL)
+ if (priv->source_actions == NULL)
return;
- action_names = g_action_group_list_actions (priv->actions);
+ action_names = g_action_group_list_actions (priv->source_actions);
for (it = action_names; *it; it++) {
GVariant *state;
- state = g_action_group_get_action_state (priv->actions, *it);
+ state = g_action_group_get_action_state (priv->source_actions, *it);
if (!state)
continue;
@@ -467,7 +483,7 @@ app_section_clear_draws_attention (AppSection *self)
g_variant_get (state, "(ux&sb)", &count, &time, &str, NULL);
new_state = g_variant_new ("(uxsb)", count, time, str, FALSE);
- g_action_group_change_action_state (priv->actions, *it, new_state);
+ g_action_group_change_action_state (priv->source_actions, *it, new_state);
}
g_variant_unref (state);
@@ -503,24 +519,28 @@ app_section_set_object_path (AppSection *self,
const gchar *bus_name,
const gchar *object_path)
{
- g_return_if_fail (IS_APP_SECTION(self));
AppSectionPrivate *priv = self->priv;
+ GMenuItem *item;
g_object_freeze_notify (G_OBJECT (self));
app_section_unset_object_path (self);
- priv->actions = G_ACTION_GROUP (g_dbus_action_group_get (bus, bus_name, object_path));
+ priv->source_actions = G_ACTION_GROUP (g_dbus_action_group_get (bus, bus_name, object_path));
+ g_action_muxer_insert (priv->muxer, "source", priv->source_actions);
- priv->draws_attention = any_action_draws_attention (priv->actions, NULL);
- g_object_connect (priv->actions,
+ priv->draws_attention = any_action_draws_attention (priv->source_actions, NULL);
+ g_object_connect (priv->source_actions,
"signal::action-added", action_added, self,
"signal::action-state-changed", action_state_changed, self,
"signal::action-removed", action_removed, self,
NULL);
- priv->remote_menu = G_MENU_MODEL (g_dbus_menu_model_get (bus, bus_name, object_path));
+ priv->source_menu = G_MENU_MODEL (g_dbus_menu_model_get (bus, bus_name, object_path));
- g_menu_append_section (priv->menu, NULL, priv->remote_menu);
+ item = g_menu_item_new_section (NULL, priv->source_menu);
+ g_menu_item_set_attribute (item, "action-namespace", "s", "source");
+ g_menu_append_item (priv->menu, item);
+ g_object_unref (item);
priv->name_watch_id = g_bus_watch_name_on_connection (bus, bus_name, 0,
NULL, application_vanished,
@@ -530,6 +550,9 @@ app_section_set_object_path (AppSection *self,
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_thaw_notify (G_OBJECT (self));
+
+ g_action_group_change_action_state (G_ACTION_GROUP (priv->static_shortcuts),
+ "launch", g_variant_new_boolean (TRUE));
}
/*
@@ -543,7 +566,6 @@ app_section_set_object_path (AppSection *self,
void
app_section_unset_object_path (AppSection *self)
{
- g_return_if_fail (IS_APP_SECTION(self));
AppSectionPrivate *priv = self->priv;
if (priv->name_watch_id) {
@@ -551,20 +573,20 @@ app_section_unset_object_path (AppSection *self)
priv->name_watch_id = 0;
}
- if (priv->actions) {
- g_object_disconnect (priv->actions,
+ if (priv->source_actions) {
+ g_object_disconnect (priv->source_actions,
"any_signal::action-added", action_added, self,
"any_signal::action-state-changed", action_state_changed, self,
"any_signal::action-removed", action_removed, self,
NULL);
- g_clear_object (&priv->actions);
+ g_clear_object (&priv->source_actions);
}
- if (priv->remote_menu) {
+ if (priv->source_menu) {
/* the last menu item points is linked to the app's menumodel */
gint n_items = g_menu_model_get_n_items (G_MENU_MODEL (priv->menu));
g_menu_remove (priv->menu, n_items -1);
- g_clear_object (&priv->remote_menu);
+ g_clear_object (&priv->source_menu);
}
priv->draws_attention = FALSE;
@@ -572,6 +594,9 @@ app_section_unset_object_path (AppSection *self)
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_action_group_change_action_state (G_ACTION_GROUP (priv->static_shortcuts),
+ "launch", g_variant_new_boolean (FALSE));
}
static gboolean
@@ -663,9 +688,8 @@ action_removed (GActionGroup *group,
gboolean
app_section_get_uses_chat_status (AppSection *self)
{
- g_return_val_if_fail (IS_APP_SECTION(self), FALSE);
AppSectionPrivate * priv = self->priv;
/* chat status is only useful when the app is running */
- return priv->uses_chat_status && priv->actions;
+ return priv->uses_chat_status && priv->source_actions;
}
diff --git a/src/im-app-menu-item.c b/src/im-app-menu-item.c
new file mode 100644
index 0000000..f4430be
--- /dev/null
+++ b/src/im-app-menu-item.c
@@ -0,0 +1,305 @@
+/*
+ * 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 "im-app-menu-item.h"
+
+struct _ImAppMenuItemPrivate
+{
+ GActionGroup *action_group;
+ gchar *action;
+ gboolean is_running;
+};
+
+enum
+{
+ PROP_0,
+ PROP_MENU_ITEM,
+ PROP_ACTION_GROUP,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES];
+
+G_DEFINE_TYPE (ImAppMenuItem, im_app_menu_item, GTK_TYPE_MENU_ITEM);
+
+static void
+im_app_menu_item_set_action_name (ImAppMenuItem *self,
+ const gchar *action_name)
+{
+ ImAppMenuItemPrivate *priv = self->priv;
+ gboolean enabled = FALSE;
+ GVariant *state;
+
+ if (priv->action != NULL)
+ g_free (priv->action);
+
+ priv->action = g_strdup (action_name);
+
+ priv->is_running = FALSE;
+
+ if (priv->action_group != NULL && priv->action != NULL &&
+ g_action_group_query_action (priv->action_group, priv->action,
+ &enabled, NULL, NULL, NULL, &state))
+ {
+ if (state && g_variant_is_of_type (state, G_VARIANT_TYPE ("b")))
+ priv->is_running = g_variant_get_boolean (state);
+ else
+ enabled = FALSE;
+
+ if (state)
+ g_variant_unref (state);
+ }
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self), enabled);
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+im_app_menu_item_action_added (GActionGroup *action_group,
+ gchar *action_name,
+ gpointer user_data)
+{
+ ImAppMenuItem *self = user_data;
+
+ if (g_strcmp0 (self->priv->action, action_name) == 0)
+ im_app_menu_item_set_action_name (self, action_name);
+}
+
+static void
+im_app_menu_item_action_removed (GActionGroup *action_group,
+ gchar *action_name,
+ gpointer user_data)
+{
+ ImAppMenuItem *self = user_data;
+
+ if (g_strcmp0 (self->priv->action, action_name) == 0)
+ {
+ gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
+ self->priv->is_running = FALSE;
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+ }
+}
+
+static void
+im_app_menu_item_action_enabled_changed (GActionGroup *action_group,
+ gchar *action_name,
+ gboolean enabled,
+ gpointer user_data)
+{
+ ImAppMenuItem *self = user_data;
+
+ if (g_strcmp0 (self->priv->action, action_name) == 0)
+ gtk_widget_set_sensitive (GTK_WIDGET (self), enabled);
+}
+
+static void
+im_app_menu_item_action_state_changed (GActionGroup *action_group,
+ gchar *action_name,
+ GVariant *value,
+ gpointer user_data)
+{
+ ImAppMenuItem *self = user_data;
+
+ if (g_strcmp0 (self->priv->action, action_name) == 0)
+ {
+ g_return_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE ("b")));
+
+ self->priv->is_running = g_variant_get_boolean (value);
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+ }
+}
+
+static void
+im_app_menu_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ImAppMenuItem *self = IM_APP_MENU_ITEM (object);
+
+ switch (property_id)
+ {
+ case PROP_MENU_ITEM:
+ im_app_menu_item_set_menu_item (self, G_MENU_ITEM (g_value_get_object (value)));
+ break;
+
+ case PROP_ACTION_GROUP:
+ im_app_menu_item_set_action_group (self, G_ACTION_GROUP (g_value_get_object (value)));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+im_app_menu_item_dispose (GObject *object)
+{
+ ImAppMenuItem *self = IM_APP_MENU_ITEM (object);
+
+ if (self->priv->action_group)
+ im_app_menu_item_set_action_group (self, NULL);
+
+ G_OBJECT_CLASS (im_app_menu_item_parent_class)->dispose (object);
+}
+
+static void
+im_app_menu_item_finalize (GObject *object)
+{
+ ImAppMenuItemPrivate *priv = IM_APP_MENU_ITEM (object)->priv;
+
+ g_free (priv->action);
+
+ G_OBJECT_CLASS (im_app_menu_item_parent_class)->finalize (object);
+}
+
+static gboolean
+im_app_menu_item_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ ImAppMenuItemPrivate *priv = IM_APP_MENU_ITEM (widget)->priv;
+
+ GTK_WIDGET_CLASS (im_app_menu_item_parent_class)->draw (widget, cr);
+
+ if (priv->is_running)
+ {
+ const int arrow_width = 5;
+ const double half_arrow_height = 4.5;
+ GtkAllocation alloc;
+ GdkRGBA color;
+ double center;
+
+ gtk_widget_get_allocation (widget, &alloc);
+
+ 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);
+
+ center = alloc.height / 2 + 0.5;
+
+ cairo_move_to (cr, 0, center - half_arrow_height);
+ cairo_line_to (cr, 0, center + half_arrow_height);
+ cairo_line_to (cr, arrow_width, center);
+ cairo_close_path (cr);
+
+ cairo_fill (cr);
+ }
+
+ return FALSE;
+}
+
+static void
+im_app_menu_item_activate (GtkMenuItem *item)
+{
+ ImAppMenuItemPrivate *priv = IM_APP_MENU_ITEM (item)->priv;
+
+ if (priv->action && priv->action_group)
+ g_action_group_activate_action (priv->action_group, priv->action, NULL);
+}
+
+static void
+im_app_menu_item_class_init (ImAppMenuItemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkMenuItemClass *menu_item_class = GTK_MENU_ITEM_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (ImAppMenuItemPrivate));
+
+ object_class->set_property = im_app_menu_set_property;
+ object_class->dispose = im_app_menu_item_dispose;
+ object_class->finalize = im_app_menu_item_finalize;
+
+ widget_class->draw = im_app_menu_item_draw;
+
+ menu_item_class->activate = im_app_menu_item_activate;
+
+ properties[PROP_MENU_ITEM] = g_param_spec_object ("menu-item",
+ "Menu item",
+ "The model GMenuItem for this menu item",
+ G_TYPE_MENU_ITEM,
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_ACTION_GROUP] = g_param_spec_object ("action-group",
+ "Action group",
+ "The action group associated with this menu item",
+ G_TYPE_ACTION_GROUP,
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
+}
+
+static void
+im_app_menu_item_init (ImAppMenuItem *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ IM_TYPE_APP_MENU_ITEM,
+ ImAppMenuItemPrivate);
+}
+
+void
+im_app_menu_item_set_menu_item (ImAppMenuItem *self,
+ GMenuItem *menuitem)
+{
+ gchar *label;
+ gchar *action = NULL;
+
+ g_menu_item_get_attribute (menuitem, "label", "s", &label);
+ gtk_menu_item_set_label (GTK_MENU_ITEM (self), label ? label : "");
+
+ g_menu_item_get_attribute (menuitem, "action", "s", &action);
+ im_app_menu_item_set_action_name (self, action);
+
+ g_free (label);
+ g_free (action);
+}
+
+void
+im_app_menu_item_set_action_group (ImAppMenuItem *self,
+ GActionGroup *action_group)
+{
+ ImAppMenuItemPrivate *priv = self->priv;
+
+ if (priv->action_group != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (priv->action_group, im_app_menu_item_action_added, self);
+ g_signal_handlers_disconnect_by_func (priv->action_group, im_app_menu_item_action_removed, self);
+ g_signal_handlers_disconnect_by_func (priv->action_group, im_app_menu_item_action_enabled_changed, self);
+ g_signal_handlers_disconnect_by_func (priv->action_group, im_app_menu_item_action_state_changed, self);
+
+ g_clear_object (&priv->action_group);
+ }
+
+ if (action_group != NULL)
+ {
+ priv->action_group = g_object_ref (action_group);
+
+ g_signal_connect (priv->action_group, "action-added",
+ G_CALLBACK (im_app_menu_item_action_added), self);
+ g_signal_connect (priv->action_group, "action-removed",
+ G_CALLBACK (im_app_menu_item_action_removed), self);
+ g_signal_connect (priv->action_group, "action-enabled-changed",
+ G_CALLBACK (im_app_menu_item_action_enabled_changed), self);
+ g_signal_connect (priv->action_group, "action-state-changed",
+ G_CALLBACK (im_app_menu_item_action_state_changed), self);
+ }
+}
diff --git a/src/im-app-menu-item.h b/src/im-app-menu-item.h
new file mode 100644
index 0000000..519de8d
--- /dev/null
+++ b/src/im-app-menu-item.h
@@ -0,0 +1,54 @@
+/*
+ * 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 __IM_APP_MENU_ITEM_H__
+#define __IM_APP_MENU_ITEM_H__
+
+#include <gtk/gtk.h>
+
+#define IM_TYPE_APP_MENU_ITEM (im_app_menu_item_get_type ())
+#define IM_APP_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_APP_MENU_ITEM, ImAppMenuItem))
+#define IM_APP_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_APP_MENU_ITEM, ImAppMenuItemClass))
+#define IS_IM_APP_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_APP_MENU_ITEM))
+#define IS_IM_APP_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_APP_MENU_ITEM))
+#define IM_APP_MENU_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_APP_MENU_ITEM, ImAppMenuItemClass))
+
+typedef struct _ImAppMenuItem ImAppMenuItem;
+typedef struct _ImAppMenuItemClass ImAppMenuItemClass;
+typedef struct _ImAppMenuItemPrivate ImAppMenuItemPrivate;
+
+struct _ImAppMenuItemClass
+{
+ GtkMenuItemClass parent_class;
+};
+
+struct _ImAppMenuItem
+{
+ GtkMenuItem parent;
+ ImAppMenuItemPrivate *priv;
+};
+
+GType im_app_menu_item_get_type (void);
+
+void im_app_menu_item_set_menu_item (ImAppMenuItem *item,
+ GMenuItem *menuitem);
+void im_app_menu_item_set_action_group (ImAppMenuItem *self,
+ GActionGroup *action_group);
+
+#endif
diff --git a/src/im-source-menu-item.c b/src/im-source-menu-item.c
new file mode 100644
index 0000000..82d553f
--- /dev/null
+++ b/src/im-source-menu-item.c
@@ -0,0 +1,278 @@
+/*
+ * 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 "im-source-menu-item.h"
+
+struct _ImSourceMenuItemPrivate
+{
+ GActionGroup *action_group;
+ gchar *action;
+
+ GtkWidget *label;
+};
+
+enum
+{
+ PROP_0,
+ PROP_MENU_ITEM,
+ PROP_ACTION_GROUP,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES];
+
+G_DEFINE_TYPE (ImSourceMenuItem, im_source_menu_item, GTK_TYPE_MENU_ITEM);
+
+static void
+im_source_menu_item_constructed (GObject *object)
+{
+ ImSourceMenuItemPrivate *priv = IM_SOURCE_MENU_ITEM (object)->priv;
+ GtkWidget *grid;
+ gint icon_width;
+
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &icon_width, NULL);
+
+ priv->label = g_object_ref (gtk_label_new (""));
+ gtk_widget_set_margin_left (priv->label, icon_width + 2);
+
+ grid = gtk_grid_new ();
+ gtk_grid_attach (GTK_GRID (grid), priv->label, 0, 0, 1, 1);
+
+ gtk_container_add (GTK_CONTAINER (object), grid);
+ gtk_widget_show_all (grid);
+
+ G_OBJECT_CLASS (im_source_menu_item_parent_class)->constructed (object);
+}
+
+static void
+im_source_menu_item_set_action_name (ImSourceMenuItem *self,
+ const gchar *action_name)
+{
+ ImSourceMenuItemPrivate *priv = self->priv;
+ gboolean enabled = FALSE;
+ GVariant *state;
+
+ if (priv->action != NULL)
+ g_free (priv->action);
+
+ priv->action = g_strdup (action_name);
+
+ if (priv->action_group != NULL && priv->action != NULL &&
+ g_action_group_query_action (priv->action_group, priv->action,
+ &enabled, NULL, NULL, NULL, &state))
+ {
+ if (!state || !g_variant_is_of_type (state, G_VARIANT_TYPE ("(uxsb)")))
+ enabled = FALSE;
+
+ if (state)
+ g_variant_unref (state);
+ }
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self), enabled);
+}
+
+static void
+im_source_menu_item_action_added (GActionGroup *action_group,
+ gchar *action_name,
+ gpointer user_data)
+{
+ ImSourceMenuItem *self = user_data;
+
+ if (g_strcmp0 (self->priv->action, action_name) == 0)
+ im_source_menu_item_set_action_name (self, action_name);
+}
+
+static void
+im_source_menu_item_action_removed (GActionGroup *action_group,
+ gchar *action_name,
+ gpointer user_data)
+{
+ ImSourceMenuItem *self = user_data;
+
+ if (g_strcmp0 (self->priv->action, action_name) == 0)
+ {
+ gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
+ }
+}
+
+static void
+im_source_menu_item_action_enabled_changed (GActionGroup *action_group,
+ gchar *action_name,
+ gboolean enabled,
+ gpointer user_data)
+{
+ ImSourceMenuItem *self = user_data;
+
+ if (g_strcmp0 (self->priv->action, action_name) == 0)
+ gtk_widget_set_sensitive (GTK_WIDGET (self), enabled);
+}
+
+static void
+im_source_menu_item_action_state_changed (GActionGroup *action_group,
+ gchar *action_name,
+ GVariant *value,
+ gpointer user_data)
+{
+ ImSourceMenuItem *self = user_data;
+
+ if (g_strcmp0 (self->priv->action, action_name) == 0)
+ g_return_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE ("(uxsb)")));
+}
+
+static void
+im_source_menu_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ImSourceMenuItem *self = IM_SOURCE_MENU_ITEM (object);
+
+ switch (property_id)
+ {
+ case PROP_MENU_ITEM:
+ im_source_menu_item_set_menu_item (self, G_MENU_ITEM (g_value_get_object (value)));
+ break;
+
+ case PROP_ACTION_GROUP:
+ im_source_menu_item_set_action_group (self, G_ACTION_GROUP (g_value_get_object (value)));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+im_source_menu_item_dispose (GObject *object)
+{
+ ImSourceMenuItem *self = IM_SOURCE_MENU_ITEM (object);
+
+ if (self->priv->action_group)
+ im_source_menu_item_set_action_group (self, NULL);
+
+ G_OBJECT_CLASS (im_source_menu_item_parent_class)->dispose (object);
+}
+
+static void
+im_source_menu_item_finalize (GObject *object)
+{
+ ImSourceMenuItemPrivate *priv = IM_SOURCE_MENU_ITEM (object)->priv;
+
+ g_free (priv->action);
+
+ G_OBJECT_CLASS (im_source_menu_item_parent_class)->finalize (object);
+}
+
+static void
+im_source_menu_item_activate (GtkMenuItem *item)
+{
+ ImSourceMenuItemPrivate *priv = IM_SOURCE_MENU_ITEM (item)->priv;
+
+ if (priv->action && priv->action_group)
+ g_action_group_activate_action (priv->action_group, priv->action, NULL);
+}
+
+static void
+im_source_menu_item_class_init (ImSourceMenuItemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkMenuItemClass *menu_item_class = GTK_MENU_ITEM_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (ImSourceMenuItemPrivate));
+
+ object_class->constructed = im_source_menu_item_constructed;
+ object_class->set_property = im_source_menu_set_property;
+ object_class->dispose = im_source_menu_item_dispose;
+ object_class->finalize = im_source_menu_item_finalize;
+
+ menu_item_class->activate = im_source_menu_item_activate;
+
+ properties[PROP_MENU_ITEM] = g_param_spec_object ("menu-item",
+ "Menu item",
+ "The model GMenuItem for this menu item",
+ G_TYPE_MENU_ITEM,
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_ACTION_GROUP] = g_param_spec_object ("action-group",
+ "Action group",
+ "The action group associated with this menu item",
+ G_TYPE_ACTION_GROUP,
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
+}
+
+static void
+im_source_menu_item_init (ImSourceMenuItem *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ IM_TYPE_SOURCE_MENU_ITEM,
+ ImSourceMenuItemPrivate);
+ g_message (G_STRFUNC);
+}
+
+void
+im_source_menu_item_set_menu_item (ImSourceMenuItem *self,
+ GMenuItem *menuitem)
+{
+ gchar *label;
+ gchar *action = NULL;
+
+ g_menu_item_get_attribute (menuitem, "label", "s", &label);
+ gtk_label_set_label (GTK_LABEL (self->priv->label), label ? label : "");
+
+ g_menu_item_get_attribute (menuitem, "action", "s", &action);
+ im_source_menu_item_set_action_name (self, action);
+
+ g_free (label);
+ g_free (action);
+}
+
+void
+im_source_menu_item_set_action_group (ImSourceMenuItem *self,
+ GActionGroup *action_group)
+{
+ ImSourceMenuItemPrivate *priv = self->priv;
+
+ if (priv->action_group != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (priv->action_group, im_source_menu_item_action_added, self);
+ g_signal_handlers_disconnect_by_func (priv->action_group, im_source_menu_item_action_removed, self);
+ g_signal_handlers_disconnect_by_func (priv->action_group, im_source_menu_item_action_enabled_changed, self);
+ g_signal_handlers_disconnect_by_func (priv->action_group, im_source_menu_item_action_state_changed, self);
+
+ g_clear_object (&priv->action_group);
+ }
+
+ if (action_group != NULL)
+ {
+ priv->action_group = g_object_ref (action_group);
+
+ g_signal_connect (priv->action_group, "action-added",
+ G_CALLBACK (im_source_menu_item_action_added), self);
+ g_signal_connect (priv->action_group, "action-removed",
+ G_CALLBACK (im_source_menu_item_action_removed), self);
+ g_signal_connect (priv->action_group, "action-enabled-changed",
+ G_CALLBACK (im_source_menu_item_action_enabled_changed), self);
+ g_signal_connect (priv->action_group, "action-state-changed",
+ G_CALLBACK (im_source_menu_item_action_state_changed), self);
+ }
+}
diff --git a/src/im-source-menu-item.h b/src/im-source-menu-item.h
new file mode 100644
index 0000000..c359b94
--- /dev/null
+++ b/src/im-source-menu-item.h
@@ -0,0 +1,54 @@
+/*
+ * 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 __IM_SOURCE_MENU_ITEM_H__
+#define __IM_SOURCE_MENU_ITEM_H__
+
+#include <gtk/gtk.h>
+
+#define IM_TYPE_SOURCE_MENU_ITEM (im_source_menu_item_get_type ())
+#define IM_SOURCE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_SOURCE_MENU_ITEM, ImSourceMenuItem))
+#define IM_SOURCE_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_SOURCE_MENU_ITEM, ImSourceMenuItemClass))
+#define IS_IM_SOURCE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_SOURCE_MENU_ITEM))
+#define IS_IM_SOURCE_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_SOURCE_MENU_ITEM))
+#define IM_SOURCE_MENU_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_SOURCE_MENU_ITEM, ImSourceMenuItemClass))
+
+typedef struct _ImSourceMenuItem ImSourceMenuItem;
+typedef struct _ImSourceMenuItemClass ImSourceMenuItemClass;
+typedef struct _ImSourceMenuItemPrivate ImSourceMenuItemPrivate;
+
+struct _ImSourceMenuItemClass
+{
+ GtkMenuItemClass parent_class;
+};
+
+struct _ImSourceMenuItem
+{
+ GtkMenuItem parent;
+ ImSourceMenuItemPrivate *priv;
+};
+
+GType im_source_menu_item_get_type (void);
+
+void im_source_menu_item_set_menu_item (ImSourceMenuItem *item,
+ GMenuItem *menuitem);
+void im_source_menu_item_set_action_group (ImSourceMenuItem *self,
+ GActionGroup *action_group);
+
+#endif
diff --git a/src/indicator-messages.c b/src/indicator-messages.c
index 162fd4d..d898ad6 100644
--- a/src/indicator-messages.c
+++ b/src/indicator-messages.c
@@ -36,6 +36,9 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include "dbus-data.h"
+#include "im-app-menu-item.h"
+#include "im-source-menu-item.h"
+
#define INDICATOR_MESSAGES_TYPE (indicator_messages_get_type ())
#define INDICATOR_MESSAGES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INDICATOR_MESSAGES_TYPE, IndicatorMessages))
#define INDICATOR_MESSAGES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INDICATOR_MESSAGES_TYPE, IndicatorMessagesClass))
@@ -140,6 +143,11 @@ indicator_messages_init (IndicatorMessages *self)
update_menu (self);
g_object_unref (bus);
+
+ /* make sure custom menu item types are registered (so that
+ * gtk_model_new_from_menu can pick them up */
+ im_app_menu_item_get_type ();
+ im_source_menu_item_get_type ();
}
/* Unref stuff */
diff --git a/src/messages-service.c b/src/messages-service.c
index ccd2eba..aade829 100644
--- a/src/messages-service.c
+++ b/src/messages-service.c
@@ -168,7 +168,7 @@ add_application (const gchar *desktop_id)
/* TODO insert it at the right position (alphabetically by application name) */
menuitem = g_menu_item_new_section (NULL, app_section_get_menu (section));
g_menu_item_set_attribute (menuitem, "action-namespace", "s", id);
- g_menu_insert_item (menu, 2, menuitem);
+ g_menu_insert_item (menu, g_menu_model_get_n_items (G_MENU_MODEL (menu)) -1, menuitem);
g_object_unref (menuitem);
}