diff options
author | Charles Kerr <charles.kerr@canonical.com> | 2012-08-21 00:49:13 -0500 |
---|---|---|
committer | Charles Kerr <charles.kerr@canonical.com> | 2012-08-21 00:49:13 -0500 |
commit | 993109acc17163354749a7a3ba4c78ca657f5b14 (patch) | |
tree | afdc3a1810b5c167ec2a6018f1d41f00544c4856 /src | |
parent | d34fb7000e9b508b05c653b47bb60f35989c34f9 (diff) | |
parent | 91eb0b1a17c5bc9d0087fe807f373d1b8a8eb87b (diff) | |
download | ayatana-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.am | 4 | ||||
-rw-r--r-- | src/app-section.c | 102 | ||||
-rw-r--r-- | src/im-app-menu-item.c | 305 | ||||
-rw-r--r-- | src/im-app-menu-item.h | 54 | ||||
-rw-r--r-- | src/im-source-menu-item.c | 278 | ||||
-rw-r--r-- | src/im-source-menu-item.h | 54 | ||||
-rw-r--r-- | src/indicator-messages.c | 8 | ||||
-rw-r--r-- | src/messages-service.c | 2 |
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); } |