diff options
author | Pete Woods <pete.woods@canonical.com> | 2013-08-21 16:04:53 +0000 |
---|---|---|
committer | Tarmac <Unknown> | 2013-08-21 16:04:53 +0000 |
commit | 933deb6d32c36e8bf6db23d5673982cab788337a (patch) | |
tree | be808298ec80ca3dc6d73309ac9e1e7024ee7950 /src | |
parent | 94fef3bf0ca765544ef93f4a6cd38684b3ccf02e (diff) | |
parent | 046f655a8859e2e3eaa6015fc2ecd226c8b46b1f (diff) | |
download | ayatana-indicator-messages-933deb6d32c36e8bf6db23d5673982cab788337a.tar.gz ayatana-indicator-messages-933deb6d32c36e8bf6db23d5673982cab788337a.tar.bz2 ayatana-indicator-messages-933deb6d32c36e8bf6db23d5673982cab788337a.zip |
Re-merge the consolidate branch, but with additional bugfix.
Approved by Sebastien Bacher, PS Jenkins bot.
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 73 | ||||
-rw-r--r-- | src/app-section.c | 434 | ||||
-rw-r--r-- | src/dbus-data.h | 17 | ||||
-rw-r--r-- | src/gactionmuxer.c | 8 | ||||
-rw-r--r-- | src/gactionmuxer.h | 3 | ||||
-rw-r--r-- | src/gmenuutils.c | 2 | ||||
-rw-r--r-- | src/ido-detail-label.c | 401 | ||||
-rw-r--r-- | src/ido-detail-label.h | 59 | ||||
-rw-r--r-- | src/ido-menu-item.c | 439 | ||||
-rw-r--r-- | src/ido-menu-item.h | 54 | ||||
-rw-r--r-- | src/im-app-menu-item.c | 354 | ||||
-rw-r--r-- | src/im-app-menu-item.h | 54 | ||||
-rw-r--r-- | src/im-application-list.c | 1237 | ||||
-rw-r--r-- | src/im-application-list.h | 62 | ||||
-rw-r--r-- | src/im-desktop-menu.c | 303 | ||||
-rw-r--r-- | src/im-desktop-menu.h | 35 | ||||
-rw-r--r-- | src/im-menu.c | 198 | ||||
-rw-r--r-- | src/im-menu.h | 64 | ||||
-rw-r--r-- | src/im-phone-menu.c | 318 | ||||
-rw-r--r-- | src/im-phone-menu.h | 68 | ||||
-rw-r--r-- | src/im-source-menu-item.c | 407 | ||||
-rw-r--r-- | src/im-source-menu-item.h | 54 | ||||
-rw-r--r-- | src/indicator-desktop-shortcuts.c | 680 | ||||
-rw-r--r-- | src/indicator-desktop-shortcuts.h | 80 | ||||
-rw-r--r-- | src/indicator-messages.c | 382 | ||||
-rw-r--r-- | src/messages-service.c | 630 | ||||
-rw-r--r-- | src/messages-service.xml | 28 |
27 files changed, 3444 insertions, 3000 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 1df80e5..9b1cc9a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,53 +1,10 @@ -BUILT_SOURCES = EXTRA_DIST = -CLEANFILES = -DISTCLEANFILES = libexec_PROGRAMS = indicator-messages-service - -###################################### -# Building the messages indicator -###################################### - -messaginglibdir = $(INDICATORDIR) -messaginglib_LTLIBRARIES = libmessaging.la -libmessaging_la_SOURCES = \ - indicator-messages.c \ - ido-menu-item.c \ - ido-menu-item.h \ - im-app-menu-item.c \ - 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 -libmessaging_la_CFLAGS = \ - $(APPLET_CFLAGS) \ - $(COVERAGE_CFLAGS) \ - -Wall \ - -Wl,-Bsymbolic-functions \ - -Wl,-z,defs \ - -Wl,--as-needed \ - -Werror \ - -DG_LOG_DOMAIN=\"Indicator-Messages\" -libmessaging_la_LIBADD = $(APPLET_LIBS) -lm -libmessaging_la_LDFLAGS = \ - $(COVERAGE_LDFLAGS) \ - -module -avoid-version - -###################################### -# Building the messages service -###################################### - indicator_messages_service_SOURCES = \ messages-service.c \ - indicator-messages-service.c \ - indicator-messages-service.h \ app-section.c \ app-section.h \ dbus-data.h \ @@ -56,11 +13,22 @@ indicator_messages_service_SOURCES = \ gsettingsstrv.c \ gsettingsstrv.h \ gmenuutils.c \ - gmenuutils.h + gmenuutils.h \ + im-menu.c \ + im-menu.h \ + im-phone-menu.c \ + im-phone-menu.h \ + im-desktop-menu.c \ + im-desktop-menu.h \ + im-application-list.c \ + im-application-list.h \ + indicator-desktop-shortcuts.c \ + indicator-desktop-shortcuts.h indicator_messages_service_CFLAGS = \ $(APPLET_CFLAGS) \ $(COVERAGE_CFLAGS) \ + -I$(top_builddir)/common \ -Wall \ -Wl,-Bsymbolic-functions \ -Wl,-z,defs \ @@ -69,26 +37,11 @@ indicator_messages_service_CFLAGS = \ -DG_LOG_DOMAIN=\"Indicator-Messages\" indicator_messages_service_LDADD = \ + $(top_builddir)/common/libmessaging-common.la \ $(APPLET_LIBS) indicator_messages_service_LDFLAGS = \ $(COVERAGE_LDFLAGS) -indicator-messages-service.c: $(top_srcdir)/src/messages-service.xml - $(AM_V_GEN) gdbus-codegen \ - --interface-prefix com.canonical.indicator.messages. \ - --generate-c-code indicator-messages-service \ - --c-namespace IndicatorMessages \ - $^ -indicator-messages-service.h: indicator-messages-service.c - -BUILT_SOURCES += \ - indicator-messages-service.c \ - indicator-messages-service.h - EXTRA_DIST += \ messages-service.xml - -CLEANFILES += \ - $(BUILT_SOURCES) - diff --git a/src/app-section.c b/src/app-section.c index 1106c62..aad994d 100644 --- a/src/app-section.c +++ b/src/app-section.c @@ -34,6 +34,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include "dbus-data.h" #include "gmenuutils.h" #include "gactionmuxer.h" +#include "indicator-messages-application.h" struct _AppSectionPrivate { @@ -42,11 +43,14 @@ struct _AppSectionPrivate IndicatorDesktopShortcuts * ids; + GCancellable *app_proxy_cancellable; + IndicatorMessagesApplication *app_proxy; + GMenu *menu; - GMenuModel *source_menu; + GMenu *source_menu; GSimpleActionGroup *static_shortcuts; - GActionGroup *source_actions; + GSimpleActionGroup *source_actions; GActionMuxer *muxer; gboolean draws_attention; @@ -90,19 +94,6 @@ static void launch_action_change_state (GSimpleAction *action, gpointer user_data); static void app_section_set_app_info (AppSection *self, GDesktopAppInfo *appinfo); -static gboolean any_action_draws_attention (GActionGroup *group, - const gchar *ignored_action); -static void action_added (GActionGroup *group, - const gchar *action_name, - gpointer user_data); -static void action_state_changed (GActionGroup *group, - const gchar *action_name, - GVariant *value, - gpointer user_data); -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, @@ -170,6 +161,7 @@ static void app_section_init (AppSection *self) { AppSectionPrivate *priv; + GMenuItem *item; self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, APP_SECTION_TYPE, @@ -179,10 +171,19 @@ app_section_init (AppSection *self) priv->appinfo = NULL; priv->menu = g_menu_new (); + + priv->source_menu = g_menu_new (); + item = g_menu_item_new_section (NULL, G_MENU_MODEL (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->static_shortcuts = g_simple_action_group_new (); + priv->source_actions = g_simple_action_group_new (); priv->muxer = g_action_muxer_new (); g_action_muxer_insert (priv->muxer, NULL, G_ACTION_GROUP (priv->static_shortcuts)); + g_action_muxer_insert (priv->muxer, "source", G_ACTION_GROUP (priv->source_actions)); priv->draws_attention = FALSE; @@ -249,32 +250,30 @@ app_section_dispose (GObject *object) AppSection * self = APP_SECTION(object); AppSectionPrivate * priv = self->priv; + if (priv->app_proxy_cancellable) { + g_cancellable_cancel (priv->app_proxy_cancellable); + g_clear_object (&priv->app_proxy_cancellable); + } + 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->app_proxy); + g_clear_object (&priv->menu); + g_clear_object (&priv->source_menu); g_clear_object (&priv->static_shortcuts); + g_clear_object (&priv->source_actions); if (priv->name_watch_id) { g_bus_unwatch_name (priv->name_watch_id); priv->name_watch_id = 0; } - 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->source_actions); - } - g_clear_object (&priv->muxer); - g_clear_object (&priv->source_menu); g_clear_object (&priv->ids); g_clear_object (&priv->appinfo); @@ -314,14 +313,12 @@ nick_activate_cb (GSimpleAction *action, g_return_if_fail(priv->ids != NULL); - GAppLaunchContext *context = get_launch_context (g_variant_get_uint32 (param)); - - if (!indicator_desktop_shortcuts_nick_exec_with_context(priv->ids, nick, context)) { + if (!indicator_desktop_shortcuts_nick_exec_with_context(priv->ids, nick, NULL)) { g_warning("Unable to execute nick '%s' for desktop file '%s'", nick, g_desktop_app_info_get_filename (priv->appinfo)); } - g_object_unref (context); + return; } static void @@ -414,7 +411,9 @@ app_section_update_menu (AppSection *self) 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"); iconstr = g_icon_to_string (g_app_info_get_icon (G_APP_INFO (priv->appinfo))); - g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr); + if (iconstr != NULL) { + g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr); + } g_free (iconstr); g_menu_append_item (priv->menu, item); @@ -445,6 +444,11 @@ app_section_update_menu (AppSection *self) g_free(name); } + item = g_menu_item_new_section (NULL, G_MENU_MODEL (priv->source_menu)); + g_menu_item_set_attribute (item, "action-namespace", "s", "source"); + g_menu_append_item (priv->menu, item); + g_object_unref (item); + keyfile = g_file_new_for_path (g_desktop_app_info_get_filename (priv->appinfo)); g_file_load_contents_async (keyfile, NULL, keyfile_loaded, self); @@ -583,49 +587,242 @@ app_section_get_draws_attention (AppSection *self) void app_section_clear_draws_attention (AppSection *self) { - AppSectionPrivate * priv = self->priv; - gchar **action_names; + self->priv->draws_attention = FALSE; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); +} + +static void +application_vanished (GDBusConnection *bus, + const gchar *name, + gpointer user_data) +{ + AppSection *self = user_data; + + app_section_unset_object_path (self); +} + +static void +update_draws_attention (AppSection *self) +{ + AppSectionPrivate *priv = self->priv; + gchar **actions; gchar **it; + gboolean draws_attention = FALSE; + + actions = g_action_group_list_actions (G_ACTION_GROUP (priv->source_actions)); + + for (it = actions; *it; it++) { + GVariant *state; + + state = g_action_group_get_action_state (G_ACTION_GROUP (priv->source_actions), *it); + if (state) { + gboolean b; + g_variant_get (state, "(uxsb)", NULL, NULL, NULL, &b); + draws_attention = b || draws_attention; + g_variant_unref (state); + } + + if (draws_attention) + break; + } + + if (draws_attention != priv->draws_attention) { + priv->draws_attention = draws_attention; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); + } + + g_strfreev (actions); +} + +static void +remove_source (AppSection *self, + const gchar *id) +{ + AppSectionPrivate *priv = self->priv; + guint n_items; + guint i; + + n_items = g_menu_model_get_n_items (G_MENU_MODEL (priv->source_menu)); + for (i = 0; i < n_items; i++) { + gchar *action; + gboolean found = FALSE; + + if (g_menu_model_get_item_attribute (G_MENU_MODEL (priv->source_menu), i, + G_MENU_ATTRIBUTE_ACTION, "s", &action)) { + found = g_str_equal (action, id); + g_free (action); + } + + if (found) { + g_menu_remove (priv->source_menu, i); + break; + } + } + + g_simple_action_group_remove (priv->source_actions, id); + update_draws_attention (self); +} + +static void +source_action_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + AppSection *self = APP_SECTION (user_data); + AppSectionPrivate *priv = APP_SECTION (user_data)->priv; + + g_return_if_fail (priv->app_proxy != NULL); + + indicator_messages_application_call_activate_source (priv->app_proxy, + g_action_get_name (G_ACTION (action)), + priv->app_proxy_cancellable, + NULL, NULL); + + remove_source (self, g_action_get_name (G_ACTION (action))); +} - if (priv->source_actions == NULL) +static void +sources_listed (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + AppSection *self = user_data; + AppSectionPrivate *priv = self->priv; + GVariant *sources = NULL; + GError *error = NULL; + GVariantIter iter; + const gchar *id; + const gchar *label; + const gchar *iconstr; + guint32 count; + gint64 time; + const gchar *string; + gboolean draws_attention; + + if (!indicator_messages_application_call_list_sources_finish (INDICATOR_MESSAGES_APPLICATION (source_object), + &sources, result, &error)) + { + g_warning ("could not fetch the list of sources: %s", error->message); + g_error_free (error); return; + } - action_names = g_action_group_list_actions (priv->source_actions); + g_menu_clear (priv->source_menu); + g_simple_action_group_clear (priv->source_actions); + priv->draws_attention = FALSE; - for (it = action_names; *it; it++) { + g_variant_iter_init (&iter, sources); + while (g_variant_iter_next (&iter, "(&s&s&sux&sb)", &id, &label, &iconstr, + &count, &time, &string, &draws_attention)) + { GVariant *state; + GSimpleAction *action; + GMenuItem *item; - state = g_action_group_get_action_state (priv->source_actions, *it); - if (!state) - continue; + state = g_variant_new ("(uxsb)", count, time, string, draws_attention); + action = g_simple_action_new_stateful (id, NULL, state); + g_signal_connect (action, "activate", G_CALLBACK (source_action_activated), self); + g_simple_action_group_insert (priv->source_actions, G_ACTION (action)); - /* clear draws-attention while preserving other state */ - if (action_draws_attention (state)) { - guint32 count; - gint64 time; - const gchar *str; - GVariant *new_state; + item = g_menu_item_new (label, id); + g_menu_item_set_attribute (item, "x-canonical-type", "s", "ImSourceMenuItem"); + g_menu_append_item (priv->source_menu, item); - g_variant_get (state, "(ux&sb)", &count, &time, &str, NULL); + priv->draws_attention = priv->draws_attention || draws_attention; - new_state = g_variant_new ("(uxsb)", count, time, str, FALSE); - g_action_group_change_action_state (priv->source_actions, *it, new_state); - } + g_object_unref (item); + g_object_unref (action); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); + + g_variant_unref (sources); +} - g_variant_unref (state); +static void +source_added (IndicatorMessagesApplication *app, + const gchar *id, + const gchar *label, + const gchar *iconstr, + guint count, + gint64 time, + const gchar *string, + gboolean draws_attention, + gpointer user_data) +{ + AppSection *self = user_data; + AppSectionPrivate *priv = self->priv; + GVariant *state; + GSimpleAction *action; + + /* TODO put label and icon into the action as well */ + + state = g_variant_new ("(uxsb)", count, time, string, draws_attention); + action = g_simple_action_new_stateful (id, NULL, state); + + g_simple_action_group_insert (priv->source_actions, G_ACTION (action)); + + if (draws_attention && !priv->draws_attention) { + priv->draws_attention = TRUE; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); } - g_strfreev (action_names); + g_object_unref (action); +} +static void +source_changed (IndicatorMessagesApplication *app, + const gchar *id, + const gchar *label, + const gchar *iconstr, + guint count, + gint64 time, + const gchar *string, + gboolean draws_attention, + gpointer user_data) +{ + AppSection *self = user_data; + AppSectionPrivate *priv = self->priv; + GVariant *state; + + /* TODO put label and icon into the action as well */ + + state = g_variant_new ("(uxsb)", count, time, string, draws_attention); + g_action_group_change_action_state (G_ACTION_GROUP (priv->source_actions), id, state); + + update_draws_attention (self); } static void -application_vanished (GDBusConnection *bus, - const gchar *name, - gpointer user_data) +source_removed (IndicatorMessagesApplication *app, + const gchar *id, + gpointer user_data) { AppSection *self = user_data; - app_section_unset_object_path (self); + remove_source (self, id); +} + +static void +app_proxy_created (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + AppSectionPrivate *priv = APP_SECTION (user_data)->priv; + GError *error = NULL; + + priv->app_proxy = indicator_messages_application_proxy_new_finish (result, &error); + if (!priv->app_proxy) { + g_warning ("could not create application proxy: %s", error->message); + g_error_free (error); + return; + } + + indicator_messages_application_call_list_sources (priv->app_proxy, priv->app_proxy_cancellable, + sources_listed, user_data); + + g_signal_connect (priv->app_proxy, "source-added", G_CALLBACK (source_added), user_data); + g_signal_connect (priv->app_proxy, "source-changed", G_CALLBACK (source_changed), user_data); + g_signal_connect (priv->app_proxy, "source-removed", G_CALLBACK (source_removed), user_data); } /* @@ -646,27 +843,20 @@ app_section_set_object_path (AppSection *self, const gchar *object_path) { AppSectionPrivate *priv = self->priv; - GMenuItem *item; g_object_freeze_notify (G_OBJECT (self)); app_section_unset_object_path (self); - 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->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->app_proxy_cancellable = g_cancellable_new (); + indicator_messages_application_proxy_new (bus, + G_DBUS_PROXY_FLAGS_NONE, + bus_name, + object_path, + priv->app_proxy_cancellable, + app_proxy_created, + self); - priv->source_menu = G_MENU_MODEL (g_dbus_menu_model_get (bus, bus_name, object_path)); - - 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->draws_attention = FALSE; priv->name_watch_id = g_bus_watch_name_on_connection (bus, bus_name, 0, NULL, application_vanished, @@ -694,26 +884,19 @@ app_section_unset_object_path (AppSection *self) { AppSectionPrivate *priv = self->priv; + if (priv->app_proxy_cancellable) { + g_cancellable_cancel (priv->app_proxy_cancellable); + g_clear_object (&priv->app_proxy_cancellable); + } + g_clear_object (&priv->app_proxy); + if (priv->name_watch_id) { g_bus_unwatch_name (priv->name_watch_id); priv->name_watch_id = 0; } - 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->source_actions); - } - - 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->source_menu); - } + g_simple_action_group_clear (priv->source_actions); + g_menu_clear (priv->source_menu); priv->draws_attention = FALSE; g_clear_pointer (&priv->chat_status, g_free); @@ -727,85 +910,6 @@ app_section_unset_object_path (AppSection *self) "launch", g_variant_new_boolean (FALSE)); } -static gboolean -action_draws_attention (GVariant *state) -{ - gboolean attention; - - if (state && g_variant_is_of_type (state, G_VARIANT_TYPE ("(uxsb)"))) - g_variant_get_child (state, 3, "b", &attention); - else - attention = FALSE; - - return attention; -} - -static gboolean -any_action_draws_attention (GActionGroup *group, - const gchar *ignored_action) -{ - gchar **actions; - gchar **it; - gboolean attention = FALSE; - - actions = g_action_group_list_actions (group); - - for (it = actions; *it && !attention; it++) { - GVariant *state; - - if (ignored_action && g_str_equal (ignored_action, *it)) - continue; - - state = g_action_group_get_action_state (group, *it); - if (state) { - attention = action_draws_attention (state); - g_variant_unref (state); - } - } - - g_strfreev (actions); - return attention; -} - -static void -action_added (GActionGroup *group, - const gchar *action_name, - gpointer user_data) -{ - AppSection *self = user_data; - GVariant *state; - - state = g_action_group_get_action_state (group, action_name); - if (state) { - self->priv->draws_attention |= action_draws_attention (state); - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); - g_variant_unref (state); - } -} - -static void -action_state_changed (GActionGroup *group, - const gchar *action_name, - GVariant *value, - gpointer user_data) -{ - AppSection *self = user_data; - - self->priv->draws_attention = any_action_draws_attention (group, NULL); - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); -} - -static void -action_removed (GActionGroup *group, - const gchar *action_name, - gpointer user_data) -{ - AppSection *self = user_data; - - self->priv->draws_attention = any_action_draws_attention (group, action_name); - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); -} - gboolean app_section_get_uses_chat_status (AppSection *self) { diff --git a/src/dbus-data.h b/src/dbus-data.h index 64747a9..59bd305 100644 --- a/src/dbus-data.h +++ b/src/dbus-data.h @@ -1,9 +1,24 @@ +/* + * Copyright 2012-2013 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/>. + */ #ifndef __DBUS_DATA_H__ #define __DBUS_DATA_H__ 1 #define INDICATOR_MESSAGES_DBUS_NAME "com.canonical.indicator.messages" -#define INDICATOR_MESSAGES_DBUS_OBJECT "/com/canonical/indicator/messages/menu" +#define INDICATOR_MESSAGES_DBUS_OBJECT "/com/canonical/indicator/messages" #define INDICATOR_MESSAGES_DBUS_SERVICE_OBJECT "/com/canonical/indicator/messages/service" #define INDICATOR_MESSAGES_DBUS_SERVICE_INTERFACE "com.canonical.indicator.messages.service" diff --git a/src/gactionmuxer.c b/src/gactionmuxer.c index 2b1d11a..0f3cda4 100644 --- a/src/gactionmuxer.c +++ b/src/gactionmuxer.c @@ -483,3 +483,11 @@ g_action_muxer_remove (GActionMuxer *muxer, g_clear_object (&muxer->global_actions); } +GActionGroup * +g_action_muxer_get_group (GActionMuxer *muxer, + const gchar *prefix) +{ + g_return_val_if_fail (G_IS_ACTION_MUXER (muxer), NULL); + + return prefix ? g_hash_table_lookup (muxer->groups, prefix) : muxer->global_actions; +} diff --git a/src/gactionmuxer.h b/src/gactionmuxer.h index 5c5e839..caf9ec7 100644 --- a/src/gactionmuxer.h +++ b/src/gactionmuxer.h @@ -40,5 +40,8 @@ void g_action_muxer_insert (GActionMuxer *muxer, void g_action_muxer_remove (GActionMuxer *muxer, const gchar *prefix); +GActionGroup * g_action_muxer_get_group (GActionMuxer *muxer, + const gchar *prefix); + #endif diff --git a/src/gmenuutils.c b/src/gmenuutils.c index f63615b..cfd751e 100644 --- a/src/gmenuutils.c +++ b/src/gmenuutils.c @@ -79,7 +79,7 @@ g_menu_append_with_icon_name (GMenu *menu, GMenuItem *item; item = g_menu_item_new (label, detailed_action); - g_menu_item_set_attribute (item, "x-canonical-icon", "s", icon_name); + g_menu_item_set_attribute (item, "icon", "s", icon_name); g_menu_append_item (menu, item); diff --git a/src/ido-detail-label.c b/src/ido-detail-label.c deleted file mode 100644 index 8b7ef90..0000000 --- a/src/ido-detail-label.c +++ /dev/null @@ -1,401 +0,0 @@ -/* - * 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) -{ - PangoFontDescription *font; - PangoFontMetrics *metrics; - - gtk_style_context_get (gtk_widget_get_style_context (widget), - gtk_widget_get_state_flags (widget), - "font", &font, NULL); - - metrics = pango_context_get_metrics (context, - font, - pango_context_get_language (context)); - - pango_font_description_free (font); - return metrics; -} - -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 deleted file mode 100644 index 1995fee..0000000 --- a/src/ido-detail-label.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 deleted file mode 100644 index f702828..0000000 --- a/src/ido-menu-item.c +++ /dev/null @@ -1,439 +0,0 @@ -/* - * 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-menu-item.h" - -struct _IdoMenuItemPrivate -{ - GActionGroup *action_group; - gchar *action; - GVariant *target; - - GtkWidget *icon; - GtkWidget *label; - - gboolean has_indicator; - gboolean in_set_active; -}; - -enum -{ - PROP_0, - PROP_MENU_ITEM, - PROP_ACTION_GROUP, - NUM_PROPERTIES -}; - -static GParamSpec *properties[NUM_PROPERTIES]; - -G_DEFINE_TYPE (IdoMenuItem, ido_menu_item, GTK_TYPE_CHECK_MENU_ITEM); - -static void -ido_menu_item_constructed (GObject *object) -{ - IdoMenuItemPrivate *priv = IDO_MENU_ITEM (object)->priv; - GtkWidget *grid; - - priv->icon = g_object_ref (gtk_image_new ()); - gtk_widget_set_margin_right (priv->icon, 6); - - priv->label = g_object_ref (gtk_label_new ("")); - - grid = gtk_grid_new (); - gtk_grid_attach (GTK_GRID (grid), priv->icon, 0, 0, 1, 1); - gtk_grid_attach (GTK_GRID (grid), priv->label, 1, 0, 1, 1); - - gtk_container_add (GTK_CONTAINER (object), grid); - gtk_widget_show_all (grid); - - G_OBJECT_CLASS (ido_menu_item_parent_class)->constructed (object); -} - -static void -ido_menu_item_set_active (IdoMenuItem *self, - gboolean active) -{ - /* HACK gtk_check_menu_item_set_active calls gtk_menu_item_activate. - * Make sure our activate handler doesn't toggle the action as a - * result of calling this function. */ - - self->priv->in_set_active = TRUE; - gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (self), active); - self->priv->in_set_active = FALSE; -} - -static void -ido_menu_item_set_has_indicator (IdoMenuItem *self, - gboolean has_indicator) -{ - if (has_indicator == self->priv->has_indicator) - return; - - self->priv->has_indicator = has_indicator; - - gtk_widget_queue_resize (GTK_WIDGET (self)); -} - -static void -ido_menu_item_set_state (IdoMenuItem *self, - GVariant *state) -{ - IdoMenuItemPrivate *priv = self->priv; - - 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); - 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)) - { - ido_menu_item_set_has_indicator (self, TRUE); - gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (self), FALSE); - ido_menu_item_set_active (self, g_variant_get_boolean (state)); - } - else - { - ido_menu_item_set_has_indicator (self, FALSE); - } -} - -static void -ido_menu_item_set_action_name (IdoMenuItem *self, - const gchar *action_name) -{ - IdoMenuItemPrivate *priv = self->priv; - gboolean enabled = FALSE; - GVariant *state; - const GVariantType *param_type; - - 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, ¶m_type, NULL, NULL, &state)) - { - gtk_widget_set_sensitive (GTK_WIDGET (self), enabled); - - if (state) - { - ido_menu_item_set_state (self, state); - g_variant_unref (state); - } - } - else - { - ido_menu_item_set_active (self, FALSE); - gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); - ido_menu_item_set_has_indicator (self, FALSE); - } -} - -static void -ido_menu_item_action_added (GActionGroup *action_group, - gchar *action_name, - gpointer user_data) -{ - IdoMenuItem *self = user_data; - - if (g_strcmp0 (self->priv->action, action_name) == 0) - ido_menu_item_set_action_name (self, action_name); -} - -static void -ido_menu_item_action_removed (GActionGroup *action_group, - gchar *action_name, - gpointer user_data) -{ - IdoMenuItem *self = user_data; - - if (g_strcmp0 (self->priv->action, action_name) == 0) - { - gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); - } -} - -static void -ido_menu_item_action_enabled_changed (GActionGroup *action_group, - gchar *action_name, - gboolean enabled, - gpointer user_data) -{ - IdoMenuItem *self = user_data; - - if (g_strcmp0 (self->priv->action, action_name) == 0) - gtk_widget_set_sensitive (GTK_WIDGET (self), enabled); -} - -static void -ido_menu_item_action_state_changed (GActionGroup *action_group, - gchar *action_name, - GVariant *value, - gpointer user_data) -{ - IdoMenuItem *self = user_data; - - if (g_strcmp0 (self->priv->action, action_name) == 0) - ido_menu_item_set_state (self, value); -} - -static void -ido_menu_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) -{ - IdoMenuItem *self = IDO_MENU_ITEM (object); - - switch (property_id) - { - case PROP_MENU_ITEM: - ido_menu_item_set_menu_item (self, G_MENU_ITEM (g_value_get_object (value))); - break; - - case PROP_ACTION_GROUP: - ido_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 -ido_menu_item_dispose (GObject *object) -{ - IdoMenuItem *self = IDO_MENU_ITEM (object); - - if (self->priv->action_group) - ido_menu_item_set_action_group (self, NULL); - - g_clear_object (&self->priv->icon); - g_clear_object (&self->priv->label); - - if (self->priv->target) - { - g_variant_unref (self->priv->target); - self->priv->target = NULL; - } - - G_OBJECT_CLASS (ido_menu_item_parent_class)->dispose (object); -} - -static void -ido_menu_item_finalize (GObject *object) -{ - IdoMenuItemPrivate *priv = IDO_MENU_ITEM (object)->priv; - - g_free (priv->action); - - G_OBJECT_CLASS (ido_menu_item_parent_class)->finalize (object); -} - -static void -ido_menu_item_activate (GtkMenuItem *item) -{ - IdoMenuItemPrivate *priv = IDO_MENU_ITEM (item)->priv; - GVariant *parameter; - - /* see ido_menu_item_set_active */ - if (!priv->in_set_active && priv->action && priv->action_group) - { - guint32 event_time = gtk_get_current_event_time (); - - if (priv->target) - { - parameter = priv->target; - } - else - { - parameter = g_variant_new_uint32 (event_time); - } - - g_action_group_activate_action (priv->action_group, priv->action, parameter); - } - - if (priv->in_set_active) - GTK_MENU_ITEM_CLASS (ido_menu_item_parent_class)->activate (item); -} - -static void -ido_menu_item_draw_indicator (GtkCheckMenuItem *item, - cairo_t *cr) -{ - IdoMenuItem *self = IDO_MENU_ITEM (item); - - if (self->priv->has_indicator) - GTK_CHECK_MENU_ITEM_CLASS (ido_menu_item_parent_class) - ->draw_indicator (item, cr); -} - -static void -ido_menu_item_class_init (IdoMenuItemClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkMenuItemClass *menu_item_class = GTK_MENU_ITEM_CLASS (klass); - GtkCheckMenuItemClass *check_class = GTK_CHECK_MENU_ITEM_CLASS (klass); - - g_type_class_add_private (klass, sizeof (IdoMenuItemPrivate)); - - object_class->constructed = ido_menu_item_constructed; - object_class->set_property = ido_menu_set_property; - object_class->dispose = ido_menu_item_dispose; - object_class->finalize = ido_menu_item_finalize; - - menu_item_class->activate = ido_menu_item_activate; - - check_class->draw_indicator = ido_menu_item_draw_indicator; - - 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 -ido_menu_item_init (IdoMenuItem *self) -{ - self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, - IDO_TYPE_MENU_ITEM, - IdoMenuItemPrivate); -} - -void -ido_menu_item_set_menu_item (IdoMenuItem *self, - GMenuItem *menuitem) -{ - gchar *iconstr = NULL; - GIcon *icon = NULL; - gchar *label = NULL; - gchar *action = NULL; - - if (g_menu_item_get_attribute (menuitem, "x-canonical-icon", "s", &iconstr)) - { - GError *error; - - /* only indent the label if icon is set to "" */ - if (iconstr[0] == '\0') - { - gint width; - - gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, NULL); - gtk_widget_set_size_request (self->priv->icon, width, -1); - } - else - { - icon = g_icon_new_for_string (iconstr, &error); - if (icon == NULL) - { - g_warning ("unable to set icon: %s", error->message); - g_error_free (error); - } - } - g_free (iconstr); - } - gtk_image_set_from_gicon (GTK_IMAGE (self->priv->icon), icon, GTK_ICON_SIZE_MENU); - - g_menu_item_get_attribute (menuitem, "label", "s", &label); - gtk_label_set_label (GTK_LABEL (self->priv->label), label ? label : ""); - - self->priv->target = g_menu_item_get_attribute_value (menuitem, "target", NULL); - - g_menu_item_get_attribute (menuitem, "action", "s", &action); - ido_menu_item_set_action_name (self, action); - - if (icon) - g_object_unref (icon); - g_free (label); - g_free (action); -} - -void -ido_menu_item_set_action_group (IdoMenuItem *self, - GActionGroup *action_group) -{ - IdoMenuItemPrivate *priv = self->priv; - - if (priv->action_group != NULL) - { - g_signal_handlers_disconnect_by_func (priv->action_group, ido_menu_item_action_added, self); - g_signal_handlers_disconnect_by_func (priv->action_group, ido_menu_item_action_removed, self); - g_signal_handlers_disconnect_by_func (priv->action_group, ido_menu_item_action_enabled_changed, self); - g_signal_handlers_disconnect_by_func (priv->action_group, ido_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 (ido_menu_item_action_added), self); - g_signal_connect (priv->action_group, "action-removed", - G_CALLBACK (ido_menu_item_action_removed), self); - g_signal_connect (priv->action_group, "action-enabled-changed", - G_CALLBACK (ido_menu_item_action_enabled_changed), self); - g_signal_connect (priv->action_group, "action-state-changed", - G_CALLBACK (ido_menu_item_action_state_changed), self); - } -} diff --git a/src/ido-menu-item.h b/src/ido-menu-item.h deleted file mode 100644 index 0521928..0000000 --- a/src/ido-menu-item.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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_MENU_ITEM_H__ -#define __IDO_MENU_ITEM_H__ - -#include <gtk/gtk.h> - -#define IDO_TYPE_MENU_ITEM (ido_menu_item_get_type ()) -#define IDO_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_TYPE_MENU_ITEM, IdoMenuItem)) -#define IDO_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IDO_TYPE_MENU_ITEM, IdoMenuItemClass)) -#define IS_IDO_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_TYPE_MENU_ITEM)) -#define IS_IDO_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IDO_TYPE_MENU_ITEM)) -#define IDO_MENU_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IDO_TYPE_MENU_ITEM, IdoMenuItemClass)) - -typedef struct _IdoMenuItem IdoMenuItem; -typedef struct _IdoMenuItemClass IdoMenuItemClass; -typedef struct _IdoMenuItemPrivate IdoMenuItemPrivate; - -struct _IdoMenuItemClass -{ - GtkCheckMenuItemClass parent_class; -}; - -struct _IdoMenuItem -{ - GtkCheckMenuItem parent; - IdoMenuItemPrivate *priv; -}; - -GType ido_menu_item_get_type (void); - -void ido_menu_item_set_menu_item (IdoMenuItem *item, - GMenuItem *menuitem); -void ido_menu_item_set_action_group (IdoMenuItem *self, - GActionGroup *action_group); - -#endif diff --git a/src/im-app-menu-item.c b/src/im-app-menu-item.c deleted file mode 100644 index 03b11c2..0000000 --- a/src/im-app-menu-item.c +++ /dev/null @@ -1,354 +0,0 @@ -/* - * 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; - - GtkWidget *icon; - GtkWidget *label; -}; - -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_constructed (GObject *object) -{ - ImAppMenuItemPrivate *priv = IM_APP_MENU_ITEM (object)->priv; - GtkWidget *grid; - - priv->icon = g_object_ref (gtk_image_new ()); - gtk_widget_set_margin_right (priv->icon, 6); - - priv->label = g_object_ref (gtk_label_new ("")); - - grid = gtk_grid_new (); - gtk_grid_attach (GTK_GRID (grid), priv->icon, 0, 0, 1, 1); - gtk_grid_attach (GTK_GRID (grid), priv->label, 1, 0, 1, 1); - - gtk_container_add (GTK_CONTAINER (object), grid); - gtk_widget_show_all (grid); - - G_OBJECT_CLASS (im_app_menu_item_parent_class)->constructed (object); -} - -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_clear_object (&self->priv->icon); - g_clear_object (&self->priv->label); - - 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) - { - guint32 event_time = gtk_get_current_event_time (); - g_action_group_activate_action (priv->action_group, priv->action, g_variant_new_uint32 (event_time)); - } -} - -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->constructed = im_app_menu_item_constructed; - 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 *iconstr = NULL; - GIcon *icon = NULL; - gchar *label; - gchar *action = NULL; - - if (g_menu_item_get_attribute (menuitem, "x-canonical-icon", "s", &iconstr)) - { - GError *error; - - icon = g_icon_new_for_string (iconstr, &error); - if (icon == NULL) - { - g_warning ("unable to set icon: %s", error->message); - g_error_free (error); - } - g_free (iconstr); - } - gtk_image_set_from_gicon (GTK_IMAGE (self->priv->icon), icon, GTK_ICON_SIZE_MENU); - - 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_app_menu_item_set_action_name (self, action); - - if (icon) - g_object_unref (icon); - 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 deleted file mode 100644 index 519de8d..0000000 --- a/src/im-app-menu-item.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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-application-list.c b/src/im-application-list.c new file mode 100644 index 0000000..b599b85 --- /dev/null +++ b/src/im-application-list.c @@ -0,0 +1,1237 @@ +/* + * 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-application-list.h" + +#include "indicator-messages-application.h" +#include "gactionmuxer.h" +#include "indicator-desktop-shortcuts.h" + +#include <gio/gdesktopappinfo.h> +#include <string.h> + +#include "glib/gi18n.h" + +typedef GObjectClass ImApplicationListClass; + +struct _ImApplicationList +{ + GObject parent; + + GHashTable *applications; + GActionMuxer *muxer; + + GSimpleActionGroup * globalactions; + GSimpleAction * statusaction; + + GHashTable *app_status; +}; + +G_DEFINE_TYPE (ImApplicationList, im_application_list, G_TYPE_OBJECT); +G_DEFINE_QUARK (draws_attention, message_action_draws_attention); + +enum +{ + SOURCE_ADDED, + SOURCE_CHANGED, + SOURCE_REMOVED, + MESSAGE_ADDED, + MESSAGE_REMOVED, + APP_ADDED, + APP_STOPPED, + REMOVE_ALL, + STATUS_SET, + N_SIGNALS +}; + +static guint signals[N_SIGNALS]; + +typedef struct +{ + ImApplicationList *list; + GDesktopAppInfo *info; + gchar *id; + IndicatorMessagesApplication *proxy; + GActionMuxer *muxer; + GSimpleActionGroup *source_actions; + GSimpleActionGroup *message_actions; + GActionMuxer *message_sub_actions; + GCancellable *cancellable; + gboolean draws_attention; + IndicatorDesktopShortcuts * shortcuts; +} Application; + + +/* Prototypes */ +static void status_activated (GSimpleAction * action, + GVariant * param, + gpointer user_data); + +static void +application_free (gpointer data) +{ + Application *app = data; + + if (!app) + return; + + g_object_unref (app->info); + g_free (app->id); + + if (app->cancellable) + { + g_cancellable_cancel (app->cancellable); + g_clear_object (&app->cancellable); + } + + if (app->proxy) + g_object_unref (app->proxy); + + if (app->muxer) + { + g_object_unref (app->muxer); + g_object_unref (app->source_actions); + g_object_unref (app->message_actions); + g_object_unref (app->message_sub_actions); + } + + g_clear_object (&app->shortcuts); + + g_slice_free (Application, app); +} + +/* Check to see if we have actions by getting the full list of + names and see if there is one. Not exactly efficient :-/ */ +static gboolean +_g_action_group_has_actions (GActionGroup * ag) +{ + gchar ** list = NULL; + gboolean retval = FALSE; + + list = g_action_group_list_actions(ag); + retval = (list[0] != NULL); + g_strfreev(list); + + return retval; +} + +/* Check to see if either of our action groups has any actions, if + so return TRUE so we get chosen! */ +static gboolean +application_has_items (gpointer key, + gpointer value, + gpointer user_data) +{ + Application *app = value; + + return _g_action_group_has_actions(G_ACTION_GROUP(app->source_actions)) || + _g_action_group_has_actions(G_ACTION_GROUP(app->message_actions)); +} + +static gboolean +application_draws_attention (gpointer key, + gpointer value, + gpointer user_data) +{ + Application *app = value; + + return app->draws_attention; +} + +static void +im_application_list_update_draws_attention (ImApplicationList *list) +{ + const gchar *base_icon_name; + const gchar *accessible_name; + const gchar *icon_name; + GIcon * icon; + GVariantBuilder builder; + GVariant *state; + + /* Figure out what type of icon we should be drawing */ + if (g_hash_table_find (list->applications, application_draws_attention, NULL)) { + base_icon_name = "indicator-messages-new-%s"; + accessible_name = _("New Messages"); + } else { + base_icon_name = "indicator-messages-%s"; + accessible_name = _("Messages"); + } + + /* Include the IM state in the icon */ + state = g_action_group_get_action_state(G_ACTION_GROUP(list->globalactions), "status"); + icon_name = g_strdup_printf(base_icon_name, g_variant_get_string(state, NULL)); + g_variant_unref(state); + + /* Build up the dictionary of values for the state */ + g_variant_builder_init(&builder, G_VARIANT_TYPE_DICTIONARY); + + /* icon */ + g_variant_builder_open(&builder, G_VARIANT_TYPE_DICT_ENTRY); + g_variant_builder_add_value(&builder, g_variant_new_string("icon")); + icon = g_themed_icon_new_with_default_fallbacks(icon_name); + g_variant_builder_add_value(&builder, g_variant_new_variant(g_icon_serialize(icon))); + g_object_unref(icon); + g_variant_builder_close(&builder); + + /* accessible description */ + g_variant_builder_open(&builder, G_VARIANT_TYPE_DICT_ENTRY); + g_variant_builder_add_value(&builder, g_variant_new_string("accessible-desc")); + g_variant_builder_add_value(&builder, g_variant_new_variant(g_variant_new_string(accessible_name))); + g_variant_builder_close(&builder); + + /* visibility */ + g_variant_builder_open(&builder, G_VARIANT_TYPE_DICT_ENTRY); + g_variant_builder_add_value(&builder, g_variant_new_string("visible")); + g_variant_builder_add_value(&builder, g_variant_new_variant(g_variant_new_boolean(TRUE))); + g_variant_builder_close(&builder); + + /* Set the state */ + g_action_group_change_action_state (G_ACTION_GROUP(list->globalactions), "messages", g_variant_builder_end(&builder)); + + GAction * remove_action = g_simple_action_group_lookup(list->globalactions, "remove-all"); + if (g_hash_table_find (list->applications, application_has_items, NULL)) { + g_debug("Enabling remove-all"); + g_simple_action_set_enabled(G_SIMPLE_ACTION(remove_action), TRUE); + } else { + g_debug("Disabling remove-all"); + g_simple_action_set_enabled(G_SIMPLE_ACTION(remove_action), FALSE); + } +} + +/* Check a source action to see if it draws */ +static gboolean +app_source_action_check_draw (Application * app, const gchar * action_name) +{ + gboolean retval = FALSE; + GVariant * state; + GVariant * draw; + + state = g_action_group_get_action_state (G_ACTION_GROUP(app->source_actions), action_name); + + /* uxsb */ + draw = g_variant_get_child_value(state, 3); + retval = g_variant_get_boolean(draw); + + g_variant_unref(draw); + g_variant_unref(state); + + return retval; +} + +/* Check a message action to see if it draws */ +static gboolean +app_message_action_check_draw (Application * app, const gchar * action_name) +{ + GAction * action = NULL; + action = g_simple_action_group_lookup (app->message_actions, action_name); + return GPOINTER_TO_INT(g_object_get_qdata(G_OBJECT(action), message_action_draws_attention_quark())); +} + +/* Regenerate the draw attention flag based on the sources and messages + that we have in the action groups */ +static void +app_check_draw_attention (Application * app) +{ + gchar **source_actions = NULL; + gchar **message_actions = NULL; + gchar **it; + + source_actions = g_action_group_list_actions (G_ACTION_GROUP (app->source_actions)); + for (it = source_actions; *it && !app->draws_attention; it++) + app->draws_attention = app_source_action_check_draw (app, *it); + + message_actions = g_action_group_list_actions (G_ACTION_GROUP (app->message_actions)); + for (it = message_actions; *it; it++) + app->draws_attention = app_message_action_check_draw (app, *it); + + g_strfreev (source_actions); + g_strfreev (message_actions); + + return; +} + +/* Remove a source from an application, signal up and update the status + of the draws attention flag. */ +static void +im_application_list_source_removed (Application *app, + const gchar *id) +{ + g_simple_action_group_remove (app->source_actions, id); + + g_signal_emit (app->list, signals[SOURCE_REMOVED], 0, app->id, id); + + if (app->draws_attention) + { + app->draws_attention = FALSE; + app_check_draw_attention(app); + } + + im_application_list_update_draws_attention (app->list); +} + +static void +im_application_list_source_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + Application *app = user_data; + const gchar *source_id; + + source_id = g_action_get_name (G_ACTION (action)); + + if (g_variant_get_boolean (parameter)) + { + indicator_messages_application_call_activate_source (app->proxy, + source_id, + app->cancellable, + NULL, NULL); + } + else + { + const gchar *sources[] = { source_id, NULL }; + const gchar *messages[] = { NULL }; + indicator_messages_application_call_dismiss (app->proxy, sources, messages, + app->cancellable, NULL, NULL); + } + + im_application_list_source_removed (app, source_id); +} + +static void +im_application_list_message_removed (Application *app, + const gchar *id) +{ + g_simple_action_group_remove (app->message_actions, id); + g_action_muxer_remove (app->message_sub_actions, id); + + im_application_list_update_draws_attention (app->list); + + g_signal_emit (app->list, signals[MESSAGE_REMOVED], 0, app->id, id); +} + +static void +im_application_list_message_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + Application *app = user_data; + const gchar *message_id; + + message_id = g_action_get_name (G_ACTION (action)); + + if (g_variant_get_boolean (parameter)) + { + indicator_messages_application_call_activate_message (app->proxy, + message_id, + "", + g_variant_new_array (G_VARIANT_TYPE_VARIANT, NULL, 0), + app->cancellable, + NULL, NULL); + } + else + { + const gchar *sources[] = { NULL }; + const gchar *messages[] = { message_id, NULL }; + indicator_messages_application_call_dismiss (app->proxy, sources, messages, + app->cancellable, NULL, NULL); + } + + im_application_list_message_removed (app, message_id); +} + +static void +im_application_list_sub_message_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + Application *app = user_data; + const gchar *message_id; + const gchar *action_id; + GVariantBuilder builder; + + message_id = g_object_get_data (G_OBJECT (action), "message"); + action_id = g_action_get_name (G_ACTION (action)); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("av")); + if (parameter) + g_variant_builder_add (&builder, "v", parameter); + + indicator_messages_application_call_activate_message (app->proxy, + message_id, + action_id, + g_variant_builder_end (&builder), + app->cancellable, + NULL, NULL); + + im_application_list_message_removed (app, message_id); +} + +static void +im_application_list_remove_all (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + ImApplicationList *list = user_data; + GHashTableIter iter; + Application *app; + + g_signal_emit (list, signals[REMOVE_ALL], 0); + + g_hash_table_iter_init (&iter, list->applications); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &app)) + { + gchar **source_actions; + gchar **message_actions; + gchar **it; + + app->draws_attention = FALSE; + + source_actions = g_action_group_list_actions (G_ACTION_GROUP (app->source_actions)); + for (it = source_actions; *it; it++) + im_application_list_source_removed (app, *it); + + message_actions = g_action_group_list_actions (G_ACTION_GROUP (app->message_actions)); + for (it = message_actions; *it; it++) + im_application_list_message_removed (app, *it); + + if (app->proxy != NULL) /* If it is remote, we tell the app we've cleared */ + { + indicator_messages_application_call_dismiss (app->proxy, + (const gchar * const *) source_actions, + (const gchar * const *) message_actions, + app->cancellable, NULL, NULL); + } + + g_strfreev (source_actions); + g_strfreev (message_actions); + } + + im_application_list_update_draws_attention (list); +} + +static void +im_application_list_dispose (GObject *object) +{ + ImApplicationList *list = IM_APPLICATION_LIST (object); + + g_clear_object (&list->statusaction); + g_clear_object (&list->globalactions); + g_clear_pointer (&list->app_status, g_hash_table_unref); + + g_clear_pointer (&list->applications, g_hash_table_unref); + g_clear_object (&list->muxer); + + G_OBJECT_CLASS (im_application_list_parent_class)->dispose (object); +} + +static void +im_application_list_finalize (GObject *object) +{ + G_OBJECT_CLASS (im_application_list_parent_class)->finalize (object); +} + +static void +im_application_list_class_init (ImApplicationListClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = im_application_list_dispose; + object_class->finalize = im_application_list_finalize; + + signals[SOURCE_ADDED] = g_signal_new ("source-added", + IM_TYPE_APPLICATION_LIST, + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 4, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING); + + signals[SOURCE_CHANGED] = g_signal_new ("source-changed", + IM_TYPE_APPLICATION_LIST, + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 4, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING); + + signals[SOURCE_REMOVED] = g_signal_new ("source-removed", + IM_TYPE_APPLICATION_LIST, + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_STRING); + + signals[MESSAGE_ADDED] = g_signal_new ("message-added", + IM_TYPE_APPLICATION_LIST, + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 10, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_VARIANT, + G_TYPE_INT64, + G_TYPE_BOOLEAN); + + signals[MESSAGE_REMOVED] = g_signal_new ("message-removed", + IM_TYPE_APPLICATION_LIST, + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_STRING); + + signals[APP_ADDED] = g_signal_new ("app-added", + IM_TYPE_APPLICATION_LIST, + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_DESKTOP_APP_INFO); + + signals[APP_STOPPED] = g_signal_new ("app-stopped", + IM_TYPE_APPLICATION_LIST, + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + + signals[REMOVE_ALL] = g_signal_new ("remove-all", + IM_TYPE_APPLICATION_LIST, + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + signals[STATUS_SET] = g_signal_new ("status-set", + IM_TYPE_APPLICATION_LIST, + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 1, + G_TYPE_STRING); +} + +static void +im_application_list_init (ImApplicationList *list) +{ + const GActionEntry action_entries[] = { + { "remove-all", im_application_list_remove_all } + }; + + list->applications = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, application_free); + list->app_status = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + list->globalactions = g_simple_action_group_new (); + { + GSimpleAction * messages = g_simple_action_new_stateful("messages", G_VARIANT_TYPE("a{sv}"), g_variant_new_array(G_VARIANT_TYPE("{sv}"), NULL, 0)); + g_simple_action_group_insert(list->globalactions, G_ACTION(messages)); + } + g_simple_action_group_add_entries (list->globalactions, action_entries, G_N_ELEMENTS (action_entries), list); + + list->statusaction = g_simple_action_new_stateful("status", G_VARIANT_TYPE_STRING, g_variant_new_string("offline")); + g_signal_connect(list->statusaction, "activate", G_CALLBACK(status_activated), list); + g_simple_action_group_insert(list->globalactions, G_ACTION(list->statusaction)); + + list->muxer = g_action_muxer_new (); + g_action_muxer_insert (list->muxer, NULL, G_ACTION_GROUP (list->globalactions)); + + im_application_list_update_draws_attention (list); +} + +ImApplicationList * +im_application_list_new (void) +{ + return g_object_new (IM_TYPE_APPLICATION_LIST, NULL); +} + +static gchar * +im_application_list_canonical_id (const gchar *id) +{ + gchar *str; + gchar *p; + int len; + + len = strlen (id); + if (g_str_has_suffix (id, ".desktop")) + len -= 8; + + str = g_strndup (id, len); + + for (p = str; *p; p++) + { + if (*p == '.') + *p = '_'; + } + + return str; +} + +static Application * +im_application_list_lookup (ImApplicationList *list, + const gchar *desktop_id) +{ + gchar *id; + Application *app; + + id = im_application_list_canonical_id (desktop_id); + app = g_hash_table_lookup (list->applications, id); + + g_free (id); + return app; +} + +void +im_application_list_activate_launch (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + Application *app = user_data; + GError *error = NULL; + + if (!g_app_info_launch (G_APP_INFO (app->info), NULL, NULL, &error)) + { + g_warning ("unable to launch application: %s", error->message); + g_error_free (error); + } +} + +void +im_application_list_activate_app_action (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + Application *app = user_data; + + indicator_desktop_shortcuts_nick_exec_with_context (app->shortcuts, g_action_get_name (G_ACTION (action)), NULL); +} + +void +im_application_list_add (ImApplicationList *list, + const gchar *desktop_id) +{ + GDesktopAppInfo *info; + Application *app; + const gchar *id; + GSimpleActionGroup *actions; + GSimpleAction *launch_action; + IndicatorDesktopShortcuts * shortcuts = NULL; + + g_return_if_fail (IM_IS_APPLICATION_LIST (list)); + g_return_if_fail (desktop_id != NULL); + + if (im_application_list_lookup (list, desktop_id)) + return; + + info = g_desktop_app_info_new (desktop_id); + if (!info) + { + g_warning ("an application with id '%s' is not installed", desktop_id); + return; + } + + id = g_app_info_get_id (G_APP_INFO (info)); + g_return_if_fail (id != NULL); + + { + const char * filename = g_desktop_app_info_get_filename(info); + if (filename != NULL) + shortcuts = indicator_desktop_shortcuts_new(filename, "Messaging Menu"); + } + + app = g_slice_new0 (Application); + app->info = info; + app->id = im_application_list_canonical_id (id); + app->list = list; + app->muxer = g_action_muxer_new (); + app->source_actions = g_simple_action_group_new (); + app->message_actions = g_simple_action_group_new (); + app->message_sub_actions = g_action_muxer_new (); + app->draws_attention = FALSE; + app->shortcuts = shortcuts; + + actions = g_simple_action_group_new (); + + launch_action = g_simple_action_new_stateful ("launch", NULL, g_variant_new_boolean (FALSE)); + g_signal_connect (launch_action, "activate", G_CALLBACK (im_application_list_activate_launch), app); + g_action_map_add_action (G_ACTION_MAP (actions), G_ACTION (launch_action)); + + if (app->shortcuts != NULL) { + const gchar ** nicks; + + for (nicks = indicator_desktop_shortcuts_get_nicks (app->shortcuts); *nicks; nicks++) + { + GSimpleAction *action; + + action = g_simple_action_new (*nicks, NULL); + g_signal_connect (action, "activate", G_CALLBACK (im_application_list_activate_app_action), app); + g_action_map_add_action (G_ACTION_MAP (actions), G_ACTION (action)); + + g_object_unref (action); + } + } + + g_action_muxer_insert (app->muxer, NULL, G_ACTION_GROUP (actions)); + g_action_muxer_insert (app->muxer, "src", G_ACTION_GROUP (app->source_actions)); + g_action_muxer_insert (app->muxer, "msg", G_ACTION_GROUP (app->message_actions)); + g_action_muxer_insert (app->muxer, "msg-actions", G_ACTION_GROUP (app->message_sub_actions)); + + g_hash_table_insert (list->applications, (gpointer) app->id, app); + g_action_muxer_insert (list->muxer, app->id, G_ACTION_GROUP (app->muxer)); + + g_signal_emit (app->list, signals[APP_ADDED], 0, app->id, app->info); + + g_object_unref (launch_action); + g_object_unref (actions); +} + +void +im_application_list_remove (ImApplicationList *list, + const gchar *id) +{ + Application *app; + + g_return_if_fail (IM_IS_APPLICATION_LIST (list)); + + app = im_application_list_lookup (list, id); + if (app) + { + if (app->proxy || app->cancellable) + g_signal_emit (app->list, signals[APP_STOPPED], 0, app->id); + + g_hash_table_remove (list->applications, id); + g_action_muxer_remove (list->muxer, id); + + im_application_list_update_draws_attention (list); + } +} + +static void +im_application_list_source_added (Application *app, + guint position, + GVariant *source) +{ + const gchar *id; + const gchar *label; + const gchar *iconstr; + guint32 count; + gint64 time; + const gchar *string; + gboolean draws_attention; + GVariant *state; + GSimpleAction *action; + + g_variant_get (source, "(&s&s&sux&sb)", + &id, &label, &iconstr, &count, &time, &string, &draws_attention); + + state = g_variant_new ("(uxsb)", count, time, string, draws_attention); + action = g_simple_action_new_stateful (id, G_VARIANT_TYPE_BOOLEAN, state); + g_signal_connect (action, "activate", G_CALLBACK (im_application_list_source_activated), app); + + g_simple_action_group_insert (app->source_actions, G_ACTION (action)); + + g_signal_emit (app->list, signals[SOURCE_ADDED], 0, app->id, id, label, iconstr); + + if (draws_attention) + app->draws_attention = TRUE; + + im_application_list_update_draws_attention (app->list); + + g_object_unref (action); +} + +static void +im_application_list_source_changed (Application *app, + GVariant *source) +{ + const gchar *id; + const gchar *label; + const gchar *iconstr; + guint32 count; + gint64 time; + const gchar *string; + gboolean draws_attention; + gboolean old_draw; + + g_variant_get (source, "(&s&s&sux&sb)", + &id, &label, &iconstr, &count, &time, &string, &draws_attention); + + old_draw = app_source_action_check_draw(app, id); + + g_action_group_change_action_state (G_ACTION_GROUP (app->source_actions), id, + g_variant_new ("(uxsb)", count, time, string, draws_attention)); + + g_signal_emit (app->list, signals[SOURCE_CHANGED], 0, app->id, id, label, iconstr); + + if (!old_draw && draws_attention) + app->draws_attention = TRUE; + + im_application_list_update_draws_attention (app->list); +} + +static void +im_application_list_sources_listed (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + Application *app = user_data; + GVariant *sources; + GError *error = NULL; + + if (indicator_messages_application_call_list_sources_finish (app->proxy, &sources, result, &error)) + { + GVariantIter iter; + GVariant *source; + guint i = 0; + + g_variant_iter_init (&iter, sources); + while ((source = g_variant_iter_next_value (&iter))) + { + im_application_list_source_added (app, i++, source); + g_variant_unref (source); + } + + g_variant_unref (sources); + } + else + { + g_warning ("could not fetch the list of sources: %s", error->message); + g_error_free (error); + } +} + +static gchar * +get_symbolic_app_icon_string (GIcon *icon) +{ + const gchar * const *names; + gchar *symbolic_name; + GIcon *symbolic_icon; + gchar *str; + + if (!G_IS_THEMED_ICON (icon)) + return NULL; + + names = g_themed_icon_get_names (G_THEMED_ICON (icon)); + if (!names || !names[0]) + return NULL; + + symbolic_icon = g_themed_icon_new_from_names ((gchar **) names, -1); + + symbolic_name = g_strconcat (names[0], "-symbolic", NULL); + g_themed_icon_prepend_name (G_THEMED_ICON (symbolic_icon), symbolic_name); + + str = g_icon_to_string (symbolic_icon); + + g_free (symbolic_name); + g_object_unref (symbolic_icon); + return str; +} + +static void +im_application_list_message_added (Application *app, + GVariant *message) +{ + const gchar *id; + const gchar *iconstr; + const gchar *title; + const gchar *subtitle; + const gchar *body; + gint64 time; + GVariantIter *action_iter; + gboolean draws_attention; + GSimpleAction *action; + GIcon *app_icon; + gchar *app_iconstr = NULL; + GVariant *actions = NULL; + + g_variant_get (message, "(&s&s&s&s&sxaa{sv}b)", + &id, &iconstr, &title, &subtitle, &body, &time, &action_iter, &draws_attention); + + app_icon = g_app_info_get_icon (G_APP_INFO (app->info)); + if (app_icon) + app_iconstr = get_symbolic_app_icon_string (app_icon); + + action = g_simple_action_new (id, G_VARIANT_TYPE_BOOLEAN); + g_object_set_qdata(G_OBJECT(action), message_action_draws_attention_quark(), GINT_TO_POINTER(draws_attention)); + g_signal_connect (action, "activate", G_CALLBACK (im_application_list_message_activated), app); + g_simple_action_group_insert (app->message_actions, G_ACTION (action)); + + { + GVariant *entry; + GSimpleActionGroup *action_group; + GVariantBuilder actions_builder; + + g_variant_builder_init (&actions_builder, G_VARIANT_TYPE ("aa{sv}")); + action_group = g_simple_action_group_new (); + + while ((entry = g_variant_iter_next_value (action_iter))) + { + const gchar *name; + GSimpleAction *action; + GVariant *label; + const gchar *type = NULL; + GVariant *hint; + GVariantBuilder dict_builder; + gchar *prefixed_name; + + if (!g_variant_lookup (entry, "name", "&s", &name)) + { + g_warning ("action dictionary for message '%s' is missing 'name' key", id); + continue; + } + + label = g_variant_lookup_value (entry, "label", G_VARIANT_TYPE_STRING); + g_variant_lookup (entry, "parameter-type", "&g", &type); + hint = g_variant_lookup_value (entry, "parameter-hint", NULL); + + action = g_simple_action_new (name, type ? G_VARIANT_TYPE (type) : NULL); + g_object_set_data_full (G_OBJECT (action), "message", g_strdup (id), g_free); + g_signal_connect (action, "activate", G_CALLBACK (im_application_list_sub_message_activated), app); + g_simple_action_group_insert (action_group, G_ACTION (action)); + + g_variant_builder_init (&dict_builder, G_VARIANT_TYPE ("a{sv}")); + + prefixed_name = g_strjoin (".", app->id, "msg-actions", id, name, NULL); + g_variant_builder_add (&dict_builder, "{sv}", "name", g_variant_new_string (prefixed_name)); + + if (label) + { + g_variant_builder_add (&dict_builder, "{sv}", "label", label); + g_variant_unref (label); + } + + if (type) + g_variant_builder_add (&dict_builder, "{sv}", "parameter-type", g_variant_new_string (type)); + + if (hint) + { + g_variant_builder_add (&dict_builder, "{sv}", "parameter-hint", hint); + g_variant_unref (hint); + } + + g_variant_builder_add (&actions_builder, "a{sv}", &dict_builder); + + g_object_unref (action); + g_variant_unref (entry); + g_free (prefixed_name); + } + + g_action_muxer_insert (app->message_sub_actions, id, G_ACTION_GROUP (action_group)); + actions = g_variant_builder_end (&actions_builder); + + g_object_unref (action_group); + } + + if (draws_attention) + app->draws_attention = TRUE; + + im_application_list_update_draws_attention (app->list); + + g_signal_emit (app->list, signals[MESSAGE_ADDED], 0, + app->id, app_iconstr, id, iconstr, title, + subtitle, body, actions, time, draws_attention); + + g_variant_iter_free (action_iter); + g_free (app_iconstr); + g_object_unref (action); +} + +static void +im_application_list_messages_listed (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + Application *app = user_data; + GVariant *messages; + GError *error = NULL; + + if (indicator_messages_application_call_list_messages_finish (app->proxy, &messages, result, &error)) + { + GVariantIter iter; + GVariant *message; + + g_variant_iter_init (&iter, messages); + while ((message = g_variant_iter_next_value (&iter))) + { + im_application_list_message_added (app, message); + g_variant_unref (message); + } + + g_variant_unref (messages); + } + else + { + g_warning ("could not fetch the list of messages: %s", error->message); + g_error_free (error); + } +} + +static void +im_application_list_unset_remote (Application *app) +{ + gboolean was_running; + + was_running = app->proxy || app->cancellable; + + if (app->cancellable) + { + g_cancellable_cancel (app->cancellable); + g_clear_object (&app->cancellable); + } + g_clear_object (&app->proxy); + + /* clear actions by creating a new action group and overriding it in + * the muxer */ + g_object_unref (app->source_actions); + g_object_unref (app->message_actions); + g_object_unref (app->message_sub_actions); + app->source_actions = g_simple_action_group_new (); + app->message_actions = g_simple_action_group_new (); + app->message_sub_actions = g_action_muxer_new (); + g_action_muxer_insert (app->muxer, "src", G_ACTION_GROUP (app->source_actions)); + g_action_muxer_insert (app->muxer, "msg", G_ACTION_GROUP (app->message_actions)); + g_action_muxer_insert (app->muxer, "msg-actions", G_ACTION_GROUP (app->message_sub_actions)); + + im_application_list_update_draws_attention (app->list); + g_action_group_change_action_state (G_ACTION_GROUP (app->muxer), "launch", g_variant_new_boolean (FALSE)); + + if (was_running) + g_signal_emit (app->list, signals[APP_STOPPED], 0, app->id); +} + +static void +im_application_list_app_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + Application *app = user_data; + + im_application_list_unset_remote (app); +} + +static void +im_application_list_proxy_created (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + Application *app = user_data; + GError *error = NULL; + + app->proxy = indicator_messages_application_proxy_new_finish (result, &error); + if (!app->proxy) + { + if (error->code != G_IO_ERROR_CANCELLED) + g_warning ("could not create application proxy: %s", error->message); + g_error_free (error); + return; + } + + indicator_messages_application_call_list_sources (app->proxy, app->cancellable, + im_application_list_sources_listed, app); + indicator_messages_application_call_list_messages (app->proxy, app->cancellable, + im_application_list_messages_listed, app); + + g_signal_connect_swapped (app->proxy, "source-added", G_CALLBACK (im_application_list_source_added), app); + g_signal_connect_swapped (app->proxy, "source-changed", G_CALLBACK (im_application_list_source_changed), app); + g_signal_connect_swapped (app->proxy, "source-removed", G_CALLBACK (im_application_list_source_removed), app); + g_signal_connect_swapped (app->proxy, "message-added", G_CALLBACK (im_application_list_message_added), app); + g_signal_connect_swapped (app->proxy, "message-removed", G_CALLBACK (im_application_list_message_removed), app); + + g_action_group_change_action_state (G_ACTION_GROUP (app->muxer), "launch", g_variant_new_boolean (TRUE)); + + g_bus_watch_name_on_connection (g_dbus_proxy_get_connection (G_DBUS_PROXY (app->proxy)), + g_dbus_proxy_get_name (G_DBUS_PROXY (app->proxy)), + G_BUS_NAME_WATCHER_FLAGS_NONE, + NULL, im_application_list_app_vanished, + app, NULL); +} + +void +im_application_list_set_remote (ImApplicationList *list, + const gchar *id, + GDBusConnection *connection, + const gchar *unique_bus_name, + const gchar *object_path) +{ + Application *app; + + g_return_if_fail (IM_IS_APPLICATION_LIST (list)); + + app = im_application_list_lookup (list, id); + if (!app) + { + g_warning ("'%s' is not a registered application", id); + return; + } + + if (app->cancellable) + { + gchar *name_owner = NULL; + + if (app->proxy) + name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (app->proxy)); + g_warning ("replacing '%s' at %s with %s", id, name_owner, unique_bus_name); + + im_application_list_unset_remote (app); + + g_free (name_owner); + } + + app->cancellable = g_cancellable_new (); + indicator_messages_application_proxy_new (connection, G_DBUS_PROXY_FLAGS_NONE, + unique_bus_name, object_path, app->cancellable, + im_application_list_proxy_created, app); +} + +GActionGroup * +im_application_list_get_action_group (ImApplicationList *list) +{ + g_return_val_if_fail (IM_IS_APPLICATION_LIST (list), NULL); + + return G_ACTION_GROUP (list->muxer); +} + +GList * +im_application_list_get_applications (ImApplicationList *list) +{ + g_return_val_if_fail (IM_IS_APPLICATION_LIST (list), NULL); + + return g_hash_table_get_keys (list->applications); +} + +GDesktopAppInfo * +im_application_list_get_application (ImApplicationList *list, + const gchar *id) +{ + Application *app; + + g_return_val_if_fail (IM_IS_APPLICATION_LIST (list), NULL); + + app = g_hash_table_lookup (list->applications, id); + return app ? app->info : NULL; +} + +static void +status_activated (GSimpleAction * action, GVariant * param, gpointer user_data) +{ + g_return_if_fail (IM_IS_APPLICATION_LIST(user_data)); + ImApplicationList * list = IM_APPLICATION_LIST(user_data); + const gchar * status = g_variant_get_string(param, NULL); + + g_simple_action_set_state(action, param); + + GList * appshash = g_hash_table_get_keys(list->app_status); + GList * appsfree = g_list_copy_deep(appshash, (GCopyFunc)g_strdup, NULL); + GList * app; + + for (app = appsfree; app != NULL; app = g_list_next(app)) { + g_hash_table_insert(list->app_status, app->data, g_strdup(status)); + } + + g_list_free(appshash); + g_list_free(appsfree); + + g_signal_emit (list, signals[STATUS_SET], 0, status); + + im_application_list_update_draws_attention(list); + + return; +} + +#define STATUS_ID_OFFLINE (G_N_ELEMENTS(status_ids) - 1) +static const gchar *status_ids[] = { "available", "away", "busy", "invisible", "offline" }; + +static guint +status2val (const gchar * string) +{ + if (string == NULL) return STATUS_ID_OFFLINE; + + guint i; + for (i = 0; i < G_N_ELEMENTS(status_ids); i++) { + if (g_strcmp0(status_ids[i], string) == 0) { + break; + } + } + + if (i > STATUS_ID_OFFLINE) + i = STATUS_ID_OFFLINE; + + return i; +} + +void +im_application_list_set_status (ImApplicationList * list, const gchar * id, const gchar *status) +{ + g_return_if_fail (IM_IS_APPLICATION_LIST (list)); + + g_hash_table_insert(list->app_status, im_application_list_canonical_id(id), g_strdup(status)); + + guint final_status = STATUS_ID_OFFLINE; + + GList * statuses = g_hash_table_get_values(list->app_status); + GList * statusentry; + + for (statusentry = statuses; statusentry != NULL; statusentry = g_list_next(statusentry)) { + guint statusval = status2val((gchar *)statusentry->data); + final_status = MIN(final_status, statusval); + } + + g_list_free(statuses); + + g_simple_action_set_state(list->statusaction, g_variant_new_string(status_ids[final_status])); + + im_application_list_update_draws_attention(list); + + return; +} + diff --git a/src/im-application-list.h b/src/im-application-list.h new file mode 100644 index 0000000..f1de220 --- /dev/null +++ b/src/im-application-list.h @@ -0,0 +1,62 @@ +/* + * 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_APPLICATION_LIST_H__ +#define __IM_APPLICATION_LIST_H__ + +#include <gio/gio.h> +#include <gio/gdesktopappinfo.h> + +#define IM_TYPE_APPLICATION_LIST (im_application_list_get_type ()) +#define IM_APPLICATION_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_APPLICATION_LIST, ImApplicationList)) +#define IM_APPLICATION_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_APPLICATION_LIST, ImApplicationListClass)) +#define IM_IS_APPLICATION_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_APPLICATION_LIST)) +#define IM_IS_APPLICATION_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_APPLICATION_LIST)) +#define IM_APPLICATION_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_APPLICATION_LIST, ImApplicationListClass)) + +typedef struct _ImApplicationList ImApplicationList; + +GType im_application_list_get_type (void); + +ImApplicationList * im_application_list_new (void); + +void im_application_list_add (ImApplicationList *list, + const gchar *desktop_id); + +void im_application_list_remove (ImApplicationList *list, + const gchar *id); + +void im_application_list_set_remote (ImApplicationList *list, + const gchar *id, + GDBusConnection *connection, + const gchar *unique_bus_name, + const gchar *object_path); + +GActionGroup * im_application_list_get_action_group (ImApplicationList *list); + +GList * im_application_list_get_applications (ImApplicationList *list); + +GDesktopAppInfo * im_application_list_get_application (ImApplicationList *list, + const gchar *id); + +void im_application_list_set_status (ImApplicationList *list, + const gchar *id, + const gchar *status); + +#endif diff --git a/src/im-desktop-menu.c b/src/im-desktop-menu.c new file mode 100644 index 0000000..834a8c9 --- /dev/null +++ b/src/im-desktop-menu.c @@ -0,0 +1,303 @@ +/* + * 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-desktop-menu.h" +#include "indicator-desktop-shortcuts.h" +#include <glib/gi18n.h> + +typedef ImMenuClass ImDesktopMenuClass; + +struct _ImDesktopMenu +{ + ImMenu parent; + + GHashTable *source_sections; +}; + +G_DEFINE_TYPE (ImDesktopMenu, im_desktop_menu, IM_TYPE_MENU); + +static void +im_desktop_menu_app_added (ImApplicationList *applist, + const gchar *app_id, + GDesktopAppInfo *app_info, + gpointer user_data) +{ + ImDesktopMenu *menu = user_data; + GMenu *section; + GMenu *app_section; + GMenu *source_section; + gchar *namespace; + + app_section = g_menu_new (); + + /* application launcher */ + { + GMenuItem *item; + GVariant *icon; + + item = g_menu_item_new (g_app_info_get_name (G_APP_INFO (app_info)), "launch"); + g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.application"); + + icon = g_icon_serialize (g_app_info_get_icon (G_APP_INFO (app_info))); + if (icon) + { + g_menu_item_set_attribute_value (item, "icon", icon); + g_variant_unref (icon); + } + + g_menu_append_item (app_section, item); + + g_object_unref (item); + } + + /* application actions */ + { + const gchar * filename = NULL; + IndicatorDesktopShortcuts * shortcuts = NULL; + const gchar ** nicks = {NULL}; + + filename = g_desktop_app_info_get_filename(app_info); + if (filename != NULL) + shortcuts = indicator_desktop_shortcuts_new(filename, "Messaging Menu"); + + if (shortcuts != NULL) + for (nicks = indicator_desktop_shortcuts_get_nicks(shortcuts); *nicks; nicks++) + { + gchar *label; + + label = indicator_desktop_shortcuts_nick_get_name (shortcuts, *nicks); + g_menu_append (app_section, label, *nicks); + + g_free (label); + } + + g_clear_object(&shortcuts); + } + + source_section = g_menu_new (); + + section = g_menu_new (); + g_menu_append_section (section, NULL, G_MENU_MODEL (app_section)); + g_menu_append_section (section, NULL, G_MENU_MODEL (source_section)); + + namespace = g_strconcat ("indicator.", app_id, NULL); + im_menu_insert_section (IM_MENU (menu), g_app_info_get_name(G_APP_INFO(app_info)), namespace, G_MENU_MODEL (section)); + g_hash_table_insert (menu->source_sections, g_strdup (app_id), source_section); + + g_free (namespace); + g_object_unref (section); + g_object_unref (app_section); +} + +static void +im_desktop_menu_source_added (ImApplicationList *applist, + const gchar *app_id, + const gchar *source_id, + const gchar *label, + const gchar *icon, + gpointer user_data) +{ + ImDesktopMenu *menu = user_data; + GMenu *source_section; + GMenuItem *item; + gchar *action; + + source_section = g_hash_table_lookup (menu->source_sections, app_id); + g_return_if_fail (source_section != NULL); + + action = g_strconcat ("src.", source_id, NULL); + item = g_menu_item_new (label, NULL); + g_menu_item_set_action_and_target_value(item, action, NULL); + g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.messages.source"); + if (icon && *icon) + g_menu_item_set_attribute (item, "icon", "s", icon); + + g_menu_append_item (source_section, item); + + g_free (action); + g_object_unref (item); +} + +static void +im_desktop_menu_source_removed (ImApplicationList *applist, + const gchar *app_id, + const gchar *source_id, + gpointer user_data) +{ + ImDesktopMenu *menu = user_data; + GMenu *source_section; + gint n_items; + gchar *action; + gint i; + + source_section = g_hash_table_lookup (menu->source_sections, app_id); + g_return_if_fail (source_section != NULL); + + n_items = g_menu_model_get_n_items (G_MENU_MODEL (source_section)); + action = g_strconcat ("src.", source_id, NULL); + + for (i = 0; i < n_items; i++) + { + gchar *item_action; + + if (g_menu_model_get_item_attribute (G_MENU_MODEL (source_section), i, "action", "s", &item_action)) + { + if (g_str_equal (action, item_action)) + g_menu_remove (source_section, i); + + g_free (item_action); + } + } + + g_free (action); +} + +static void +im_desktop_menu_remove_all (ImApplicationList *applist, + gpointer user_data) +{ + ImDesktopMenu *menu = user_data; + GHashTableIter it; + GMenu *section; + + g_hash_table_iter_init (&it, menu->source_sections); + while (g_hash_table_iter_next (&it, NULL, (gpointer *) §ion)) + { + while (g_menu_model_get_n_items (G_MENU_MODEL (section)) > 0) + g_menu_remove (section, 0); + } +} + +static GMenu * +create_status_section (void) +{ + GMenu *menu; + GMenuItem *item; + struct status_item { + gchar *label; + gchar *action; + gchar *icon_name; + } status_items[] = { + { _("Available"), "indicator.status::available", "user-available" }, + { _("Away"), "indicator.status::away", "user-away" }, + { _("Busy"), "indicator.status::busy", "user-busy" }, + { _("Invisible"), "indicator.status::invisible", "user-invisible" }, + { _("Offline"), "indicator.status::offline", "user-offline" } + }; + int i; + + menu = g_menu_new (); + + item = g_menu_item_new (NULL, NULL); + + for (i = 0; i < G_N_ELEMENTS (status_items); i++) { + g_menu_item_set_label (item, status_items[i].label); + g_menu_item_set_detailed_action (item, status_items[i].action); + g_menu_item_set_attribute (item, "icon", "s", status_items[i].icon_name); + g_menu_append_item (menu, item); + } + + g_object_unref (item); + return menu; +} + +static void +im_desktop_menu_constructed (GObject *object) +{ + ImDesktopMenu *menu = IM_DESKTOP_MENU (object); + ImApplicationList *applist; + + { + GMenu *status_section; + + status_section = create_status_section(); + im_menu_append_section (IM_MENU (menu), G_MENU_MODEL (status_section)); + + g_object_unref (status_section); + } + + { + GMenu *clear_section; + + clear_section = g_menu_new (); + g_menu_append (clear_section, _("Clear"), "indicator.remove-all"); + im_menu_append_section (IM_MENU (menu), G_MENU_MODEL (clear_section)); + + g_object_unref (clear_section); + } + + applist = im_menu_get_application_list (IM_MENU (menu)); + + { + GList *apps; + GList *it; + + apps = im_application_list_get_applications (applist); + for (it = apps; it != NULL; it = it->next) + { + const gchar *id = it->data; + im_desktop_menu_app_added (applist, id, im_application_list_get_application (applist, id), menu); + } + + g_list_free (apps); + } + + + g_signal_connect (applist, "app-added", G_CALLBACK (im_desktop_menu_app_added), menu); + g_signal_connect (applist, "source-added", G_CALLBACK (im_desktop_menu_source_added), menu); + g_signal_connect (applist, "source-removed", G_CALLBACK (im_desktop_menu_source_removed), menu); + g_signal_connect (applist, "remove-all", G_CALLBACK (im_desktop_menu_remove_all), menu); + + G_OBJECT_CLASS (im_desktop_menu_parent_class)->constructed (object); +} + +static void +im_desktop_menu_finalize (GObject *object) +{ + ImDesktopMenu *menu = IM_DESKTOP_MENU (object); + + g_hash_table_unref (menu->source_sections); + + G_OBJECT_CLASS (im_desktop_menu_parent_class)->finalize (object); +} + +static void +im_desktop_menu_class_init (ImDesktopMenuClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = im_desktop_menu_constructed; + object_class->finalize = im_desktop_menu_finalize; +} + +static void +im_desktop_menu_init (ImDesktopMenu *menu) +{ + menu->source_sections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); +} + +ImDesktopMenu * +im_desktop_menu_new (ImApplicationList *applist) +{ + g_return_val_if_fail (IM_IS_APPLICATION_LIST (applist), NULL); + + return g_object_new (IM_TYPE_DESKTOP_MENU, + "application-list", applist, + NULL); +} diff --git a/src/im-desktop-menu.h b/src/im-desktop-menu.h new file mode 100644 index 0000000..9469ea6 --- /dev/null +++ b/src/im-desktop-menu.h @@ -0,0 +1,35 @@ +/* + * Copyright 2013 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_DESKTOP_MENU_H__ +#define __IM_DESKTOP_MENU_H__ + +#include "im-menu.h" + +#define IM_TYPE_DESKTOP_MENU (im_desktop_menu_get_type ()) +#define IM_DESKTOP_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_DESKTOP_MENU, ImDesktopMenu)) +#define IM_IS_DESKTOP_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_DESKTOP_MENU)) + +typedef struct _ImDesktopMenu ImDesktopMenu; + +GType im_desktop_menu_get_type (void); + +ImDesktopMenu * im_desktop_menu_new (ImApplicationList *applist); + +#endif diff --git a/src/im-menu.c b/src/im-menu.c new file mode 100644 index 0000000..1aaffe3 --- /dev/null +++ b/src/im-menu.c @@ -0,0 +1,198 @@ +/* + * Copyright 2013 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-menu.h" + +struct _ImMenuPrivate +{ + GMenu *toplevel_menu; + GMenu *menu; + ImApplicationList *applist; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (ImMenu, im_menu, G_TYPE_OBJECT) + +enum +{ + PROP_0, + PROP_APPLICATION_LIST, + NUM_PROPERTIES +}; + +static void +im_menu_finalize (GObject *object) +{ + ImMenuPrivate *priv = im_menu_get_instance_private (IM_MENU (object)); + + g_object_unref (priv->toplevel_menu); + g_object_unref (priv->menu); + g_object_unref (priv->applist); + + G_OBJECT_CLASS (im_menu_parent_class)->finalize (object); +} + +static void +im_menu_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + ImMenuPrivate *priv = im_menu_get_instance_private (IM_MENU (object)); + + switch (property_id) + { + case PROP_APPLICATION_LIST: + g_value_set_object (value, priv->applist); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +im_menu_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + ImMenuPrivate *priv = im_menu_get_instance_private (IM_MENU (object)); + + switch (property_id) + { + case PROP_APPLICATION_LIST: /* construct only */ + priv->applist = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +im_menu_class_init (ImMenuClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = im_menu_finalize; + object_class->get_property = im_menu_get_property; + object_class->set_property = im_menu_set_property; + + g_object_class_install_property (object_class, PROP_APPLICATION_LIST, + g_param_spec_object ("application-list", "", "", + IM_TYPE_APPLICATION_LIST, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); +} + +static void +im_menu_init (ImMenu *menu) +{ + ImMenuPrivate *priv = im_menu_get_instance_private (menu); + GMenuItem *root; + + priv->toplevel_menu = g_menu_new (); + priv->menu = g_menu_new (); + + root = g_menu_item_new (NULL, "indicator.messages"); + g_menu_item_set_attribute (root, "x-canonical-type", "s", "com.canonical.indicator.root"); + g_menu_item_set_attribute (root, "action-namespace", "s", "indicator"); + g_menu_item_set_submenu (root, G_MENU_MODEL (priv->menu)); + g_menu_append_item (priv->toplevel_menu, root); + + g_object_unref (root); +} + +ImApplicationList * +im_menu_get_application_list (ImMenu *menu) +{ + ImMenuPrivate *priv; + + g_return_val_if_fail (IM_IS_MENU (menu), FALSE); + + priv = im_menu_get_instance_private (menu); + return priv->applist; +} + +gboolean +im_menu_export (ImMenu *menu, + GDBusConnection *connection, + const gchar *object_path, + GError **error) +{ + ImMenuPrivate *priv; + + g_return_val_if_fail (IM_IS_MENU (menu), FALSE); + + priv = im_menu_get_instance_private (menu); + return g_dbus_connection_export_menu_model (connection, + object_path, + G_MENU_MODEL (priv->toplevel_menu), + error) > 0; +} + +void +im_menu_append_section (ImMenu *menu, + GMenuModel *section) +{ + ImMenuPrivate *priv; + + g_return_if_fail (IM_IS_MENU (menu)); + g_return_if_fail (G_IS_MENU_MODEL (section)); + + priv = im_menu_get_instance_private (menu); + + g_menu_append_section (priv->menu, NULL, section); +} + +void +im_menu_insert_section (ImMenu *menu, + const gchar *sort_string, + const gchar *namespace, + GMenuModel *section) +{ + int position; + ImMenuPrivate *priv; + GMenuItem *item; + + g_return_if_fail (IM_IS_MENU (menu)); + g_return_if_fail (G_IS_MENU_MODEL (section)); + + priv = im_menu_get_instance_private (menu); + + for (position = 1; position < g_menu_model_get_n_items(G_MENU_MODEL (priv->menu)) - 1; position++) + { + gchar * item_sort = NULL; + if (g_menu_model_get_item_attribute(G_MENU_MODEL(priv->menu), position, "x-messaging-menu-sort-string", "s", &item_sort)) + if (g_utf8_collate(sort_string, item_sort) < 0) + break; + g_free(item_sort); + } + + item = g_menu_item_new_section (NULL, section); + g_menu_item_set_attribute (item, "x-messaging-menu-sort-string", "s", sort_string); + + if (namespace) + g_menu_item_set_attribute (item, "action-namespace", "s", namespace); + + g_menu_insert_item (priv->menu, position, item); + + g_object_unref (item); +} diff --git a/src/im-menu.h b/src/im-menu.h new file mode 100644 index 0000000..7c15eb7 --- /dev/null +++ b/src/im-menu.h @@ -0,0 +1,64 @@ +/* + * Copyright 2013 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_MENU_H__ +#define __IM_MENU_H__ + +#include <gio/gio.h> +#include "im-application-list.h" + +#define IM_TYPE_MENU (im_menu_get_type ()) +#define IM_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_MENU, ImMenu)) +#define IM_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_MENU, ImMenuClass)) +#define IM_IS_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_MENU)) +#define IM_IS_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_MENU)) +#define IM_MENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_MENU, ImMenuClass)) + +typedef struct _ImMenuClass ImMenuClass; +typedef struct _ImMenu ImMenu; +typedef struct _ImMenuPrivate ImMenuPrivate; + +struct _ImMenuClass +{ + GObjectClass parent_class; +}; + +struct _ImMenu +{ + GObject parent_instance; +}; + +GType im_menu_get_type (void) G_GNUC_CONST; + +ImApplicationList * im_menu_get_application_list (ImMenu *menu); + +gboolean im_menu_export (ImMenu *menu, + GDBusConnection *connection, + const gchar *object_path, + GError **error); + +void im_menu_append_section (ImMenu *menu, + GMenuModel *section); + +void im_menu_insert_section (ImMenu *menu, + const gchar *sort_string, + const gchar *namespace, + GMenuModel *section); + +#endif diff --git a/src/im-phone-menu.c b/src/im-phone-menu.c new file mode 100644 index 0000000..0ea6f76 --- /dev/null +++ b/src/im-phone-menu.c @@ -0,0 +1,318 @@ +/* + * 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-phone-menu.h" + +#include <string.h> +#include <glib/gi18n.h> + +typedef ImMenuClass ImPhoneMenuClass; + +struct _ImPhoneMenu +{ + ImMenu parent; + + GMenu *message_section; + GMenu *source_section; +}; + +G_DEFINE_TYPE (ImPhoneMenu, im_phone_menu, IM_TYPE_MENU); + +typedef void (*ImMenuForeachFunc) (GMenuModel *menu, gint pos); + +static void +im_phone_menu_foreach_item_with_action (GMenuModel *menu, + const gchar *action, + ImMenuForeachFunc func) +{ + gint n_items; + gint i; + + n_items = g_menu_model_get_n_items (menu); + for (i = 0; i < n_items; i++) + { + gchar *item_action; + + g_menu_model_get_item_attribute (menu, i, G_MENU_ATTRIBUTE_ACTION, "s", &item_action); + + if (g_str_equal (action, item_action)) + func (menu, i); + + g_free (item_action); + } +} + +static void +im_phone_menu_constructed (GObject *object) +{ + ImPhoneMenu *menu = IM_PHONE_MENU (object); + ImApplicationList *applist; + + im_menu_append_section (IM_MENU (menu), G_MENU_MODEL (menu->message_section)); + im_menu_append_section (IM_MENU (menu), G_MENU_MODEL (menu->source_section)); + + { + GMenu *clear_section; + GMenuItem *item; + + clear_section = g_menu_new (); + + item = g_menu_item_new (_("Clear All"), "remove-all"); + g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.button"); + g_menu_append_item (clear_section, item); + + im_menu_append_section (IM_MENU (menu), G_MENU_MODEL (clear_section)); + + g_object_unref (item); + g_object_unref (clear_section); + } + + applist = im_menu_get_application_list (IM_MENU (menu)); + + g_signal_connect_swapped (applist, "message-added", G_CALLBACK (im_phone_menu_add_message), menu); + g_signal_connect_swapped (applist, "message-removed", G_CALLBACK (im_phone_menu_remove_message), menu); + g_signal_connect_swapped (applist, "app-stopped", G_CALLBACK (im_phone_menu_remove_application), menu); + g_signal_connect_swapped (applist, "remove-all", G_CALLBACK (im_phone_menu_remove_all), menu); + + G_OBJECT_CLASS (im_phone_menu_parent_class)->constructed (object); +} + +static void +im_phone_menu_dispose (GObject *object) +{ + ImPhoneMenu *menu = IM_PHONE_MENU (object); + + g_clear_object (&menu->message_section); + g_clear_object (&menu->source_section); + + G_OBJECT_CLASS (im_phone_menu_parent_class)->dispose (object); +} + +static void +im_phone_menu_finalize (GObject *object) +{ + G_OBJECT_CLASS (im_phone_menu_parent_class)->finalize (object); +} + +static void +im_phone_menu_class_init (ImPhoneMenuClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = im_phone_menu_constructed; + object_class->dispose = im_phone_menu_dispose; + object_class->finalize = im_phone_menu_finalize; +} + +static void +im_phone_menu_init (ImPhoneMenu *menu) +{ + menu->message_section = g_menu_new (); + menu->source_section = g_menu_new (); +} + +ImPhoneMenu * +im_phone_menu_new (ImApplicationList *applist) +{ + g_return_val_if_fail (IM_IS_APPLICATION_LIST (applist), NULL); + + return g_object_new (IM_TYPE_PHONE_MENU, + "application-list", applist, + NULL); +} + +static gint64 +im_phone_menu_get_message_time (GMenuModel *model, + gint i) +{ + gint64 time; + + g_menu_model_get_item_attribute (model, i, "x-canonical-time", "x", &time); + + return time; +} + +void +im_phone_menu_add_message (ImPhoneMenu *menu, + const gchar *app_id, + const gchar *app_icon, + const gchar *id, + const gchar *iconstr, + const gchar *title, + const gchar *subtitle, + const gchar *body, + GVariant *actions, + gint64 time) +{ + GMenuItem *item; + gchar *action_name; + gint n_messages; + gint pos; + + g_return_if_fail (IM_IS_PHONE_MENU (menu)); + g_return_if_fail (app_id); + + action_name = g_strconcat (app_id, ".msg.", id, NULL); + + item = g_menu_item_new (title, action_name); + + g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.messages.messageitem"); + g_menu_item_set_attribute (item, "x-canonical-message-id", "s", id); + g_menu_item_set_attribute (item, "x-canonical-subtitle", "s", subtitle); + g_menu_item_set_attribute (item, "x-canonical-text", "s", body); + g_menu_item_set_attribute (item, "x-canonical-time", "x", time); + + if (iconstr) + g_menu_item_set_attribute (item, "icon", "s", iconstr); + + if (app_icon) + g_menu_item_set_attribute (item, "x-canonical-app-icon", "s", app_icon); + + if (actions) + g_menu_item_set_attribute (item, "x-canonical-message-actions", "v", actions); + + n_messages = g_menu_model_get_n_items (G_MENU_MODEL (menu->message_section)); + pos = 0; + while (pos < n_messages && + time < im_phone_menu_get_message_time (G_MENU_MODEL (menu->message_section), pos)) + pos++; + + g_menu_insert_item (menu->message_section, pos, item); + + g_free (action_name); + g_object_unref (item); +} + +void +im_phone_menu_remove_message (ImPhoneMenu *menu, + const gchar *app_id, + const gchar *id) +{ + gchar *action_name; + + g_return_if_fail (IM_IS_PHONE_MENU (menu)); + g_return_if_fail (app_id != NULL); + + action_name = g_strconcat (app_id, ".msg.", id, NULL); + im_phone_menu_foreach_item_with_action (G_MENU_MODEL (menu->message_section), + action_name, + (ImMenuForeachFunc) g_menu_remove); + + g_free (action_name); +} + +void +im_phone_menu_add_source (ImPhoneMenu *menu, + const gchar *app_id, + const gchar *id, + const gchar *label, + const gchar *iconstr) +{ + GMenuItem *item; + gchar *action_name; + + g_return_if_fail (IM_IS_PHONE_MENU (menu)); + g_return_if_fail (app_id != NULL); + + action_name = g_strconcat (app_id, ".src.", id, NULL); + + item = g_menu_item_new (label, action_name); + g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.messages.sourceitem"); + + if (iconstr) + g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr); + + g_menu_prepend_item (menu->source_section, item); + + g_free (action_name); + g_object_unref (item); +} + +void +im_phone_menu_remove_source (ImPhoneMenu *menu, + const gchar *app_id, + const gchar *id) +{ + gchar *action_name; + + g_return_if_fail (IM_IS_PHONE_MENU (menu)); + g_return_if_fail (app_id != NULL); + + action_name = g_strconcat (app_id, ".src.", id, NULL); + im_phone_menu_foreach_item_with_action (G_MENU_MODEL (menu->source_section), + action_name, + (ImMenuForeachFunc) g_menu_remove); + + g_free (action_name); +} + +static void +im_phone_menu_remove_all_for_app (GMenu *menu, + const gchar *app_id) +{ + gchar *prefix; + gint n_items; + gint i = 0; + + prefix = g_strconcat (app_id, ".", NULL); + + n_items = g_menu_model_get_n_items (G_MENU_MODEL (menu)); + while (i < n_items) + { + gchar *action; + + g_menu_model_get_item_attribute (G_MENU_MODEL (menu), i, G_MENU_ATTRIBUTE_ACTION, "s", &action); + if (g_str_has_prefix (action, prefix)) + { + g_menu_remove (menu, i); + n_items--; + } + else + { + i++; + } + + g_free (action); + } + + g_free (prefix); +} + +void +im_phone_menu_remove_application (ImPhoneMenu *menu, + const gchar *app_id) +{ + g_return_if_fail (IM_IS_PHONE_MENU (menu)); + g_return_if_fail (app_id != NULL); + + im_phone_menu_remove_all_for_app (menu->source_section, app_id); + im_phone_menu_remove_all_for_app (menu->message_section, app_id); +} + +void +im_phone_menu_remove_all (ImPhoneMenu *menu) +{ + g_return_if_fail (IM_IS_PHONE_MENU (menu)); + + while (g_menu_model_get_n_items (G_MENU_MODEL (menu->message_section))) + g_menu_remove (menu->message_section, 0); + + while (g_menu_model_get_n_items (G_MENU_MODEL (menu->source_section))) + g_menu_remove (menu->source_section, 0); +} diff --git a/src/im-phone-menu.h b/src/im-phone-menu.h new file mode 100644 index 0000000..9742f61 --- /dev/null +++ b/src/im-phone-menu.h @@ -0,0 +1,68 @@ +/* + * 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_PHONE_MENU_H__ +#define __IM_PHONE_MENU_H__ + +#include "im-menu.h" + +#define IM_TYPE_PHONE_MENU (im_phone_menu_get_type ()) +#define IM_PHONE_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_PHONE_MENU, ImPhoneMenu)) +#define IM_PHONE_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_PHONE_MENU, ImPhoneMenuClass)) +#define IM_IS_PHONE_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_PHONE_MENU)) +#define IM_IS_PHONE_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_PHONE_MENU)) +#define IM_PHONE_MENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_PHONE_MENU, ImPhoneMenuClass)) + +typedef struct _ImPhoneMenu ImPhoneMenu; + +GType im_phone_menu_get_type (void); + +ImPhoneMenu * im_phone_menu_new (ImApplicationList *applist); + +void im_phone_menu_add_message (ImPhoneMenu *menu, + const gchar *app_id, + const gchar *app_icon, + const gchar *id, + const gchar *iconstr, + const gchar *title, + const gchar *subtitle, + const gchar *body, + GVariant *actions, + gint64 time); + +void im_phone_menu_remove_message (ImPhoneMenu *menu, + const gchar *app_id, + const gchar *id); + +void im_phone_menu_add_source (ImPhoneMenu *menu, + const gchar *app_id, + const gchar *id, + const gchar *label, + const gchar *iconstr); + +void im_phone_menu_remove_source (ImPhoneMenu *menu, + const gchar *app_id, + const gchar *id); + +void im_phone_menu_remove_application (ImPhoneMenu *menu, + const gchar *app_id); + +void im_phone_menu_remove_all (ImPhoneMenu *menu); + +#endif diff --git a/src/im-source-menu-item.c b/src/im-source-menu-item.c deleted file mode 100644 index 2577c30..0000000 --- a/src/im-source-menu-item.c +++ /dev/null @@ -1,407 +0,0 @@ -/* - * 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" - -#include <libintl.h> -#include "ido-detail-label.h" - -struct _ImSourceMenuItemPrivate -{ - GActionGroup *action_group; - gchar *action; - - GtkWidget *icon; - GtkWidget *label; - GtkWidget *detail; - - gint64 time; - guint timer_id; -}; - -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->icon = g_object_ref (gtk_image_new ()); - 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 (ido_detail_label_new ("")); - gtk_widget_set_halign (priv->detail, GTK_ALIGN_END); - gtk_widget_set_hexpand (priv->detail, TRUE); - gtk_style_context_add_class (gtk_widget_get_style_context (priv->detail), "accelerator"); - - grid = gtk_grid_new (); - gtk_grid_attach (GTK_GRID (grid), priv->icon, 0, 0, 1, 1); - gtk_grid_attach (GTK_GRID (grid), priv->label, 1, 0, 1, 1); - gtk_grid_attach (GTK_GRID (grid), priv->detail, 2, 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 gchar * -im_source_menu_item_time_span_string (gint64 timestamp) -{ - gchar *str; - gint64 span; - gint hours; - gint minutes; - - span = MAX (g_get_real_time () - timestamp, 0) / G_USEC_PER_SEC; - hours = span / 3600; - minutes = (span / 60) % 60; - - if (hours == 0) - { - /* TRANSLATORS: number of minutes that have passed */ - str = g_strdup_printf (ngettext ("%d min", "%d min", minutes), minutes); - } - else - { - /* TRANSLATORS: number of hours that have passed */ - str = g_strdup_printf (ngettext ("%d h", "%d h", hours), hours); - } - - 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; - - im_source_menu_item_set_detail_time (self, self->priv->time); - - return TRUE; -} - -static gboolean -im_source_menu_item_set_state (ImSourceMenuItem *self, - GVariant *state) -{ - ImSourceMenuItemPrivate *priv = self->priv; - guint32 count; - gint64 time; - const gchar *str; - - if (priv->timer_id != 0) - { - g_source_remove (priv->timer_id); - priv->timer_id = 0; - } - - g_return_val_if_fail (g_variant_is_of_type (state, G_VARIANT_TYPE ("(uxsb)")), FALSE); - - g_variant_get (state, "(ux&sb)", &count, &time, &str, NULL); - - if (count != 0) - ido_detail_label_set_count (IDO_DETAIL_LABEL (priv->detail), count); - else if (time != 0) - { - 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) - ido_detail_label_set_text (IDO_DETAIL_LABEL (priv->detail), str); - - return TRUE; -} - -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 || !im_source_menu_item_set_state (self, state)) - 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) - im_source_menu_item_set_state (self, value); -} - -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->timer_id != 0) - { - g_source_remove (self->priv->timer_id); - self->priv->timer_id = 0; - } - - if (self->priv->action_group) - im_source_menu_item_set_action_group (self, NULL); - - g_clear_object (&self->priv->icon); - g_clear_object (&self->priv->label); - g_clear_object (&self->priv->detail); - - 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); -} - -void -im_source_menu_item_set_menu_item (ImSourceMenuItem *self, - GMenuItem *menuitem) -{ - gchar *iconstr = NULL; - GIcon *icon = NULL; - gchar *label; - gchar *action = NULL; - - if (g_menu_item_get_attribute (menuitem, "x-canonical-icon", "s", &iconstr)) - { - GError *error; - icon = g_icon_new_for_string (iconstr, &error); - if (icon == NULL) - { - g_warning ("unable to set icon: %s", error->message); - g_error_free (error); - } - g_free (iconstr); - } - gtk_image_set_from_gicon (GTK_IMAGE (self->priv->icon), icon, GTK_ICON_SIZE_MENU); - - 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); - - if (icon) - g_object_unref (icon); - 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 deleted file mode 100644 index c359b94..0000000 --- a/src/im-source-menu-item.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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-desktop-shortcuts.c b/src/indicator-desktop-shortcuts.c new file mode 100644 index 0000000..7b43630 --- /dev/null +++ b/src/indicator-desktop-shortcuts.c @@ -0,0 +1,680 @@ +/* +A small file to parse through the actions that are available +in the desktop file and making those easily usable. + +Copyright 2010 Canonical Ltd. + +Authors: + Ted Gould <ted@canonical.com> + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +version 3.0 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License version 3.0 for more details. + +You should have received a copy of the GNU General Public +License along with this library. If not, see +<http://www.gnu.org/licenses/>. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gio/gdesktopappinfo.h> +#include "indicator-desktop-shortcuts.h" + +#define ACTIONS_KEY "Actions" +#define ACTION_GROUP_PREFIX "Desktop Action" + +#define OLD_GROUP_SUFFIX "Shortcut Group" +#define OLD_SHORTCUTS_KEY "X-Ayatana-Desktop-Shortcuts" +#define OLD_ENVIRON_KEY "TargetEnvironment" + +#define PROP_DESKTOP_FILE_S "desktop-file" +#define PROP_IDENTITY_S "identity" + +typedef enum _actions_t actions_t; +enum _actions_t { + ACTIONS_NONE, + ACTIONS_XAYATANA, + ACTIONS_DESKTOP_SPEC +}; + +typedef struct _IndicatorDesktopShortcutsPrivate IndicatorDesktopShortcutsPrivate; +struct _IndicatorDesktopShortcutsPrivate { + actions_t actions; + GKeyFile * keyfile; + gchar * identity; + GArray * nicks; + gchar * domain; +}; + +enum { + PROP_0, + PROP_DESKTOP_FILE, + PROP_IDENTITY +}; + +#define INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), INDICATOR_TYPE_DESKTOP_SHORTCUTS, IndicatorDesktopShortcutsPrivate)) + +static void indicator_desktop_shortcuts_class_init (IndicatorDesktopShortcutsClass *klass); +static void indicator_desktop_shortcuts_init (IndicatorDesktopShortcuts *self); +static void indicator_desktop_shortcuts_dispose (GObject *object); +static void indicator_desktop_shortcuts_finalize (GObject *object); +static void set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); +static void get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); +static void parse_keyfile (IndicatorDesktopShortcuts * ids); +static gboolean should_show (GKeyFile * keyfile, const gchar * group, const gchar * identity, gboolean should_have_target); + +G_DEFINE_TYPE (IndicatorDesktopShortcuts, indicator_desktop_shortcuts, G_TYPE_OBJECT); + +/* Build up the class */ +static void +indicator_desktop_shortcuts_class_init (IndicatorDesktopShortcutsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (IndicatorDesktopShortcutsPrivate)); + + object_class->dispose = indicator_desktop_shortcuts_dispose; + object_class->finalize = indicator_desktop_shortcuts_finalize; + + /* Property funcs */ + object_class->set_property = set_property; + object_class->get_property = get_property; + + g_object_class_install_property(object_class, PROP_DESKTOP_FILE, + g_param_spec_string(PROP_DESKTOP_FILE_S, + "The path of the desktop file to read", + "A path to a desktop file that we'll look for shortcuts in.", + NULL, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property(object_class, PROP_IDENTITY, + g_param_spec_string(PROP_IDENTITY_S, + "The string that represents the identity that we're acting as.", + "Used to process ShowIn and NotShownIn fields of the desktop shortcust to get the proper list.", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); + + return; +} + +/* Initialize instance data */ +static void +indicator_desktop_shortcuts_init (IndicatorDesktopShortcuts *self) +{ + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(self); + + priv->keyfile = NULL; + priv->identity = NULL; + priv->domain = NULL; + priv->nicks = g_array_new(TRUE, TRUE, sizeof(gchar *)); + priv->actions = ACTIONS_NONE; + + return; +} + +/* Clear object references */ +static void +indicator_desktop_shortcuts_dispose (GObject *object) +{ + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(object); + + if (priv->keyfile) { + g_key_file_free(priv->keyfile); + priv->keyfile = NULL; + } + + G_OBJECT_CLASS (indicator_desktop_shortcuts_parent_class)->dispose (object); + return; +} + +/* Free all memory */ +static void +indicator_desktop_shortcuts_finalize (GObject *object) +{ + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(object); + + if (priv->identity != NULL) { + g_free(priv->identity); + priv->identity = NULL; + } + + if (priv->domain != NULL) { + g_free(priv->domain); + priv->domain = NULL; + } + + if (priv->nicks != NULL) { + gint i; + for (i = 0; i < priv->nicks->len; i++) { + gchar * nick = g_array_index(priv->nicks, gchar *, i); + g_free(nick); + } + g_array_free(priv->nicks, TRUE); + priv->nicks = NULL; + } + + G_OBJECT_CLASS (indicator_desktop_shortcuts_parent_class)->finalize (object); + return; +} + +/* Sets one of the two properties we have, only at construction though */ +static void +set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) +{ + g_return_if_fail(INDICATOR_IS_DESKTOP_SHORTCUTS(object)); + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(object); + + switch(prop_id) { + case PROP_DESKTOP_FILE: { + if (priv->keyfile != NULL) { + g_key_file_free(priv->keyfile); + priv->keyfile = NULL; + priv->actions = ACTIONS_NONE; + } + + GError * error = NULL; + GKeyFile * keyfile = g_key_file_new(); + g_key_file_load_from_file(keyfile, g_value_get_string(value), G_KEY_FILE_NONE, &error); + + if (error != NULL) { + g_warning("Unable to load keyfile from file '%s': %s", g_value_get_string(value), error->message); + g_error_free(error); + g_key_file_free(keyfile); + break; + } + + /* Always prefer the desktop spec if we can get it */ + if (priv->actions == ACTIONS_NONE && g_key_file_has_key(keyfile, G_KEY_FILE_DESKTOP_GROUP, ACTIONS_KEY, NULL)) { + priv->actions = ACTIONS_DESKTOP_SPEC; + } + + /* But fallback if we can't */ + if (priv->actions == ACTIONS_NONE && g_key_file_has_key(keyfile, G_KEY_FILE_DESKTOP_GROUP, OLD_SHORTCUTS_KEY, NULL)) { + priv->actions = ACTIONS_XAYATANA; + g_warning("Desktop file '%s' is using a deprecated format for its actions that will be dropped soon.", g_value_get_string(value)); + } + + if (priv->actions == ACTIONS_NONE) { + g_key_file_free(keyfile); + break; + } + + priv->keyfile = keyfile; + parse_keyfile(INDICATOR_DESKTOP_SHORTCUTS(object)); + break; + } + case PROP_IDENTITY: + if (priv->identity != NULL) { + g_warning("Identity already set to '%s' and trying to set it to '%s'.", priv->identity, g_value_get_string(value)); + return; + } + priv->identity = g_value_dup_string(value); + parse_keyfile(INDICATOR_DESKTOP_SHORTCUTS(object)); + break; + /* *********************** */ + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + return; +} + +/* Gets either the desktop file our the identity. */ +static void +get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) +{ + g_return_if_fail(INDICATOR_IS_DESKTOP_SHORTCUTS(object)); + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(object); + + switch(prop_id) { + case PROP_IDENTITY: + g_value_set_string(value, priv->identity); + break; + /* *********************** */ + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + return; +} + +/* Checks to see if we can, and if we can it goes through + and parses the keyfile entries. */ +static void +parse_keyfile (IndicatorDesktopShortcuts * ids) +{ + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(ids); + + if (priv->keyfile == NULL) { + return; + } + + if (priv->identity == NULL) { + return; + } + + /* Remove a previous translation domain if we had one + from a previously parsed file. */ + if (priv->domain != NULL) { + g_free(priv->domain); + priv->domain = NULL; + } + + /* Check to see if there is a custom translation domain that + we should take into account. */ + if (priv->domain == NULL && + g_key_file_has_key(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, "X-GNOME-Gettext-Domain", NULL)) { + priv->domain = g_key_file_get_string(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, "X-GNOME-Gettext-Domain", NULL); + } + + if (priv->domain == NULL && + g_key_file_has_key(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, "X-Ubuntu-Gettext-Domain", NULL)) { + priv->domain = g_key_file_get_string(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, "X-Ubuntu-Gettext-Domain", NULL); + } + + /* We need to figure out what we're looking for and what we want to + look for in the rest of the file */ + const gchar * list_name = NULL; + const gchar * group_format = NULL; + gboolean should_have_target = FALSE; + + switch (priv->actions) { + case ACTIONS_NONE: + /* None, let's just get outta here */ + return; + case ACTIONS_XAYATANA: + list_name = OLD_SHORTCUTS_KEY; + group_format = "%s " OLD_GROUP_SUFFIX; + should_have_target = TRUE; + break; + case ACTIONS_DESKTOP_SPEC: + list_name = ACTIONS_KEY; + group_format = ACTION_GROUP_PREFIX " %s"; + should_have_target = FALSE; + break; + default: + g_assert_not_reached(); + return; + } + + /* Okay, we've got everything we need. Let's get it on! */ + gint i; + gsize num_nicks = 0; + gchar ** nicks = g_key_file_get_string_list(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, list_name, &num_nicks, NULL); + + /* If there is an error from get_string_list num_nicks should still + be zero, so this loop will drop out. */ + for (i = 0; i < num_nicks; i++) { + /* g_debug("Looking at group nick %s", nicks[i]); */ + gchar * groupname = g_strdup_printf(group_format, nicks[i]); + if (!g_key_file_has_group(priv->keyfile, groupname)) { + g_warning("Unable to find group '%s'", groupname); + g_free(groupname); + continue; + } + + if (!should_show(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, priv->identity, FALSE)) { + g_free(groupname); + continue; + } + + if (!should_show(priv->keyfile, groupname, priv->identity, should_have_target)) { + g_free(groupname); + continue; + } + + gchar * nickalloc = g_strdup(nicks[i]); + g_array_append_val(priv->nicks, nickalloc); + g_free(groupname); + } + + if (nicks != NULL) { + g_strfreev(nicks); + } + + return; +} + +/* Checks the ONLY_SHOW_IN and NOT_SHOW_IN keys for a group to + see if we should be showing ourselves. */ +static gboolean +should_show (GKeyFile * keyfile, const gchar * group, const gchar * identity, gboolean should_have_target) +{ + if (should_have_target && g_key_file_has_key(keyfile, group, OLD_ENVIRON_KEY, NULL)) { + /* If we've got this key, we're going to return here and not + process the deprecated keys. */ + gint j; + gsize num_env = 0; + gchar ** envs = g_key_file_get_string_list(keyfile, group, OLD_ENVIRON_KEY, &num_env, NULL); + + for (j = 0; j < num_env; j++) { + if (g_strcmp0(envs[j], identity) == 0) { + break; + } + } + + if (envs != NULL) { + g_strfreev(envs); + } + + if (j == num_env) { + return FALSE; + } + return TRUE; + } + + /* If there is a list of OnlyShowIn entries we need to check + to see if we're in that list. If not, we drop this nick */ + if (g_key_file_has_key(keyfile, group, G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN, NULL)) { + gint j; + gsize num_only = 0; + gchar ** onlies = g_key_file_get_string_list(keyfile, group, G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN, &num_only, NULL); + + for (j = 0; j < num_only; j++) { + if (g_strcmp0(onlies[j], identity) == 0) { + break; + } + } + + if (onlies != NULL) { + g_strfreev(onlies); + } + + if (j == num_only) { + return FALSE; + } + } + + /* If there is a NotShowIn entry we need to make sure that we're + not in that list. If we are, we need to drop out. */ + if (g_key_file_has_key(keyfile, group, G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN, NULL)) { + gint j; + gsize num_not = 0; + gchar ** nots = g_key_file_get_string_list(keyfile, group, G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN, &num_not, NULL); + + for (j = 0; j < num_not; j++) { + if (g_strcmp0(nots[j], identity) == 0) { + break; + } + } + + if (nots != NULL) { + g_strfreev(nots); + } + + if (j != num_not) { + return FALSE; + } + } + + return TRUE; +} + +/* Looks through the nicks to see if this one is in the list, + and thus valid to use. */ +static gboolean +is_valid_nick (gchar ** list, const gchar * nick) +{ + if (*list == NULL) + return FALSE; + /* g_debug("Checking Nick: %s", list[0]); */ + if (g_strcmp0(list[0], nick) == 0) + return TRUE; + return is_valid_nick(&list[1], nick); +} + +/* API */ + +/** + indicator_desktop_shortcuts_new: + @file: The desktop file that would be opened to + find the actions. + @identity: This is a string that represents the identity + that should be used in searching those actions. It + relates to the ShowIn and NotShownIn properties. + + This function creates the basic object. It involves opening + the file and parsing it. It could potentially block on IO. At + the end of the day you'll have a fully functional object. + + Return value: A new #IndicatorDesktopShortcuts object. +*/ +IndicatorDesktopShortcuts * +indicator_desktop_shortcuts_new (const gchar * file, const gchar * identity) +{ + GObject * obj = g_object_new(INDICATOR_TYPE_DESKTOP_SHORTCUTS, + PROP_DESKTOP_FILE_S, file, + PROP_IDENTITY_S, identity, + NULL); + return INDICATOR_DESKTOP_SHORTCUTS(obj); +} + +/** + indicator_desktop_shortcuts_get_nicks: + @ids: The #IndicatorDesktopShortcuts object to look in + + Give you the list of commands that are available for this desktop + file given the identity that was passed in at creation. This will + filter out the various items in the desktop file. These nicks can + then be used as keys for working with the desktop file. + + Return value: A #NULL terminated list of strings. This memory + is managed by the @ids object. +*/ +const gchar ** +indicator_desktop_shortcuts_get_nicks (IndicatorDesktopShortcuts * ids) +{ + g_return_val_if_fail(INDICATOR_IS_DESKTOP_SHORTCUTS(ids), NULL); + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(ids); + return (const gchar **)priv->nicks->data; +} + +/** + indicator_desktop_shortcuts_nick_get_name: + @ids: The #IndicatorDesktopShortcuts object to look in + @nick: Which command that we're referencing. + + This function looks in a desktop file for a nick to find the + user visible name for that shortcut. The @nick parameter + should be gotten from #indicator_desktop_shortcuts_get_nicks + though it's not required that the exact memory location + be the same. + + Return value: A user visible string for the shortcut or + #NULL on error. +*/ +gchar * +indicator_desktop_shortcuts_nick_get_name (IndicatorDesktopShortcuts * ids, const gchar * nick) +{ + g_return_val_if_fail(INDICATOR_IS_DESKTOP_SHORTCUTS(ids), NULL); + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(ids); + + g_return_val_if_fail(priv->actions != ACTIONS_NONE, NULL); + g_return_val_if_fail(priv->keyfile != NULL, NULL); + g_return_val_if_fail(is_valid_nick((gchar **)priv->nicks->data, nick), NULL); + + const gchar * group_format = NULL; + + switch (priv->actions) { + case ACTIONS_XAYATANA: + group_format = "%s " OLD_GROUP_SUFFIX; + break; + case ACTIONS_DESKTOP_SPEC: + group_format = ACTION_GROUP_PREFIX " %s"; + break; + default: + g_assert_not_reached(); + return NULL; + } + + gchar * groupheader = g_strdup_printf(group_format, nick); + if (!g_key_file_has_group(priv->keyfile, groupheader)) { + g_warning("The group for nick '%s' doesn't exist anymore.", nick); + g_free(groupheader); + return NULL; + } + + if (!g_key_file_has_key(priv->keyfile, groupheader, G_KEY_FILE_DESKTOP_KEY_NAME, NULL)) { + g_warning("No name available for nick '%s'", nick); + g_free(groupheader); + return NULL; + } + + gchar * name = NULL; + gchar * keyvalue = g_key_file_get_string(priv->keyfile, + groupheader, + G_KEY_FILE_DESKTOP_KEY_NAME, + NULL); + gchar * localeval = g_key_file_get_locale_string(priv->keyfile, + groupheader, + G_KEY_FILE_DESKTOP_KEY_NAME, + NULL, + NULL); + g_free(groupheader); + + if (priv->domain != NULL && g_strcmp0(keyvalue, localeval) == 0) { + name = g_strdup(g_dgettext(priv->domain, keyvalue)); + g_free(localeval); + } else { + name = localeval; + } + + g_free(keyvalue); + + return name; +} + +/** + indicator_desktop_shortcuts_nick_exec_with_context: + @ids: The #IndicatorDesktopShortcuts object to look in + @nick: Which command that we're referencing. + @launch_context: The #GAppLaunchContext to use for launching the shortcut + + Here we take a @nick and try and execute the action that is + associated with it. The @nick parameter should be gotten + from #indicator_desktop_shortcuts_get_nicks though it's not + required that the exact memory location be the same. + + Return value: #TRUE on success or #FALSE on error. +*/ +gboolean +indicator_desktop_shortcuts_nick_exec_with_context (IndicatorDesktopShortcuts * ids, const gchar * nick, GAppLaunchContext * launch_context) +{ + GError * error = NULL; + + g_return_val_if_fail(INDICATOR_IS_DESKTOP_SHORTCUTS(ids), FALSE); + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(ids); + + g_return_val_if_fail(priv->actions != ACTIONS_NONE, FALSE); + g_return_val_if_fail(priv->keyfile != NULL, FALSE); + g_return_val_if_fail(is_valid_nick((gchar **)priv->nicks->data, nick), FALSE); + + const gchar * group_format = NULL; + + switch (priv->actions) { + case ACTIONS_XAYATANA: + group_format = "%s " OLD_GROUP_SUFFIX; + break; + case ACTIONS_DESKTOP_SPEC: + group_format = ACTION_GROUP_PREFIX " %s"; + break; + default: + g_assert_not_reached(); + return FALSE; + } + + gchar * groupheader = g_strdup_printf(group_format, nick); + if (!g_key_file_has_group(priv->keyfile, groupheader)) { + g_warning("The group for nick '%s' doesn't exist anymore.", nick); + g_free(groupheader); + return FALSE; + } + + if (!g_key_file_has_key(priv->keyfile, groupheader, G_KEY_FILE_DESKTOP_KEY_NAME, NULL)) { + g_warning("No name available for nick '%s'", nick); + g_free(groupheader); + return FALSE; + } + + if (!g_key_file_has_key(priv->keyfile, groupheader, G_KEY_FILE_DESKTOP_KEY_EXEC, NULL)) { + g_warning("No exec available for nick '%s'", nick); + g_free(groupheader); + return FALSE; + } + + /* Grab the name and the exec entries out of our current group */ + gchar * name = g_key_file_get_locale_string(priv->keyfile, + groupheader, + G_KEY_FILE_DESKTOP_KEY_NAME, + NULL, + NULL); + + gchar * exec = g_key_file_get_locale_string(priv->keyfile, + groupheader, + G_KEY_FILE_DESKTOP_KEY_EXEC, + NULL, + NULL); + + g_free(groupheader); + + GAppInfoCreateFlags flags = G_APP_INFO_CREATE_NONE; + + if (launch_context) { + flags |= G_APP_INFO_CREATE_SUPPORTS_STARTUP_NOTIFICATION; + } + + GAppInfo * appinfo = g_app_info_create_from_commandline(exec, name, flags, &error); + g_free(name); g_free(exec); + + if (error != NULL) { + g_warning("Unable to build Command line App info: %s", error->message); + g_error_free(error); + return FALSE; + } + + if (appinfo == NULL) { + g_warning("Unable to build Command line App info (unknown)"); + return FALSE; + } + + gboolean launched = g_app_info_launch(appinfo, NULL, launch_context, &error); + + if (error != NULL) { + g_warning("Unable to launch file from nick '%s': %s", nick, error->message); + g_clear_error(&error); + } + + g_object_unref(appinfo); + + return launched; +} + +/** + indicator_desktop_shortcuts_nick_exec: + @ids: The #IndicatorDesktopShortcuts object to look in + @nick: Which command that we're referencing. + + Here we take a @nick and try and execute the action that is + associated with it. The @nick parameter should be gotten + from #indicator_desktop_shortcuts_get_nicks though it's not + required that the exact memory location be the same. + This function is deprecated and shouldn't be used in newly + written code. + + Return value: #TRUE on success or #FALSE on error. +*/ +gboolean +indicator_desktop_shortcuts_nick_exec (IndicatorDesktopShortcuts * ids, const gchar * nick) +{ + return indicator_desktop_shortcuts_nick_exec_with_context (ids, nick, NULL); +} diff --git a/src/indicator-desktop-shortcuts.h b/src/indicator-desktop-shortcuts.h new file mode 100644 index 0000000..fb997ea --- /dev/null +++ b/src/indicator-desktop-shortcuts.h @@ -0,0 +1,80 @@ +/* +A small file to parse through the actions that are available +in the desktop file and making those easily usable. + +Copyright 2010 Canonical Ltd. + +Authors: + Ted Gould <ted@canonical.com> + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +version 3.0 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License version 3.0 for more details. + +You should have received a copy of the GNU General Public +License along with this library. If not, see +<http://www.gnu.org/licenses/>. +*/ + +#ifndef __INDICATOR_DESKTOP_SHORTCUTS_H__ +#define __INDICATOR_DESKTOP_SHORTCUTS_H__ + +#include <gio/gio.h> +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define INDICATOR_TYPE_DESKTOP_SHORTCUTS (indicator_desktop_shortcuts_get_type ()) +#define INDICATOR_DESKTOP_SHORTCUTS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INDICATOR_TYPE_DESKTOP_SHORTCUTS, IndicatorDesktopShortcuts)) +#define INDICATOR_DESKTOP_SHORTCUTS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INDICATOR_TYPE_DESKTOP_SHORTCUTS, IndicatorDesktopShortcutsClass)) +#define INDICATOR_IS_DESKTOP_SHORTCUTS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INDICATOR_TYPE_DESKTOP_SHORTCUTS)) +#define INDICATOR_IS_DESKTOP_SHORTCUTS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INDICATOR_TYPE_DESKTOP_SHORTCUTS)) +#define INDICATOR_DESKTOP_SHORTCUTS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), INDICATOR_TYPE_DESKTOP_SHORTCUTS, IndicatorDesktopShortcutsClass)) + +typedef struct _IndicatorDesktopShortcuts IndicatorDesktopShortcuts; +typedef struct _IndicatorDesktopShortcutsClass IndicatorDesktopShortcutsClass; + +/** + IndicatorDesktopShortcutsClass: + @parent_class: Space for #GObjectClass + + The vtable for our precious #IndicatorDesktopShortcutsClass. +*/ +struct _IndicatorDesktopShortcutsClass { + GObjectClass parent_class; +}; + +/** + IndicatorDesktopShortcuts: + @parent: The parent data from #GObject + + The public data for an instance of the class + #IndicatorDesktopShortcuts. +*/ +struct _IndicatorDesktopShortcuts { + GObject parent; +}; + +GType indicator_desktop_shortcuts_get_type (void); +IndicatorDesktopShortcuts * indicator_desktop_shortcuts_new (const gchar * file, + const gchar * identity); +const gchar ** indicator_desktop_shortcuts_get_nicks (IndicatorDesktopShortcuts * ids); +gchar * indicator_desktop_shortcuts_nick_get_name (IndicatorDesktopShortcuts * ids, + const gchar * nick); +gboolean indicator_desktop_shortcuts_nick_exec_with_context (IndicatorDesktopShortcuts * ids, + const gchar * nick, + GAppLaunchContext * launch_context); + +GLIB_DEPRECATED_FOR(indicator_desktop_shortcuts_nick_exec_with_context) +gboolean indicator_desktop_shortcuts_nick_exec (IndicatorDesktopShortcuts * ids, + const gchar * nick); + +G_END_DECLS + +#endif diff --git a/src/indicator-messages.c b/src/indicator-messages.c deleted file mode 100644 index 5c5df31..0000000 --- a/src/indicator-messages.c +++ /dev/null @@ -1,382 +0,0 @@ -/* -An indicator to show information that is in messaging applications -that the user is using. - -Copyright 2012 Canonical Ltd. - -Authors: - Ted Gould <ted@canonical.com> - Lars Uebernickel <lars.uebernickel@canonical.com> - -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/>. -*/ - -#include "config.h" - -#include <string.h> -#include <math.h> -#include <glib.h> -#include <glib-object.h> -#include <glib/gi18n-lib.h> -#include <gtk/gtk.h> - -#include <libindicator/indicator.h> -#include <libindicator/indicator-object.h> -#include <libindicator/indicator-service-manager.h> - -#include "dbus-data.h" - -#include "ido-menu-item.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)) -#define IS_INDICATOR_MESSAGES(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INDICATOR_MESSAGES_TYPE)) -#define IS_INDICATOR_MESSAGES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INDICATOR_MESSAGES_TYPE)) -#define INDICATOR_MESSAGES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), INDICATOR_MESSAGES_TYPE, IndicatorMessagesClass)) - -typedef struct _IndicatorMessages IndicatorMessages; -typedef struct _IndicatorMessagesClass IndicatorMessagesClass; - -struct _IndicatorMessagesClass { - IndicatorObjectClass parent_class; -}; - -struct _IndicatorMessages { - IndicatorObject parent; - IndicatorServiceManager * service; - GActionGroup *actions; - GMenuModel *menu; - GtkWidget *image; - GtkWidget *gtkmenu; - gchar *accessible_desc; -}; - -GType indicator_messages_get_type (void); - -/* Indicator Module Config */ -INDICATOR_SET_VERSION -INDICATOR_SET_TYPE(INDICATOR_MESSAGES_TYPE) - -/* Prototypes */ -static void indicator_messages_class_init (IndicatorMessagesClass *klass); -static void indicator_messages_init (IndicatorMessages *self); -static void indicator_messages_dispose (GObject *object); -static void indicator_messages_finalize (GObject *object); -static void service_connection_changed (IndicatorServiceManager *sm, - gboolean connected, - gpointer user_data); -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 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); - -/* Initialize the one-timers */ -static void -indicator_messages_class_init (IndicatorMessagesClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->dispose = indicator_messages_dispose; - object_class->finalize = indicator_messages_finalize; - - IndicatorObjectClass * io_class = INDICATOR_OBJECT_CLASS(klass); - - io_class->get_image = get_image; - io_class->get_menu = get_menu; - io_class->get_accessible_desc = get_accessible_desc; - io_class->get_name_hint = get_name_hint; -} - -/* Build up our per-instance variables */ -static void -indicator_messages_init (IndicatorMessages *self) -{ - self->service = indicator_service_manager_new_version(INDICATOR_MESSAGES_DBUS_NAME, 1); - g_signal_connect (self->service, "connection-change", - G_CALLBACK (service_connection_changed), self); - - self->gtkmenu = gtk_menu_new (); - g_object_ref_sink (self->gtkmenu); - - self->image = g_object_ref_sink (gtk_image_new ()); - - /* make sure custom menu item types are registered (so that - * gtk_model_new_from_menu can pick them up */ - ido_menu_item_get_type (); - im_app_menu_item_get_type (); - im_source_menu_item_get_type (); -} - -/* Unref stuff */ -static void -indicator_messages_dispose (GObject *object) -{ - IndicatorMessages * self = INDICATOR_MESSAGES(object); - g_return_if_fail(self != NULL); - - g_clear_object (&self->service); - g_clear_object (&self->actions); - g_clear_object (&self->menu); - g_clear_object (&self->gtkmenu); - g_clear_object (&self->image); - - G_OBJECT_CLASS (indicator_messages_parent_class)->dispose (object); - return; -} - -/* Destory all memory users */ -static void -indicator_messages_finalize (GObject *object) -{ - IndicatorMessages *self = INDICATOR_MESSAGES (object); - - g_free (self->accessible_desc); - - G_OBJECT_CLASS (indicator_messages_parent_class)->finalize (object); - return; -} - - - -/* Functions */ - -static void service_connection_changed (IndicatorServiceManager *sm, - gboolean connected, - gpointer user_data) -{ - IndicatorMessages *self = user_data; - GDBusConnection *bus; - 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); - } - if (self->menu != NULL) { - g_signal_handlers_disconnect_by_func (self->menu, menu_items_changed, self); - g_clear_object (&self->menu); - } - gtk_menu_shell_bind_model (GTK_MENU_SHELL (self->gtkmenu), NULL, NULL, FALSE); - - if (connected == FALSE) - return; - - bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); - if (!bus) { - g_warning ("error connecting to the session bus: %s", error->message); - g_error_free (error); - return; - } - - self->actions = G_ACTION_GROUP (g_dbus_action_group_get (bus, - INDICATOR_MESSAGES_DBUS_NAME, - INDICATOR_MESSAGES_DBUS_OBJECT)); - 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); - - self->menu = G_MENU_MODEL (g_dbus_menu_model_get (bus, - INDICATOR_MESSAGES_DBUS_NAME, - INDICATOR_MESSAGES_DBUS_OBJECT)); - g_signal_connect (self->menu, "items-changed", G_CALLBACK (menu_items_changed), 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); -} - -static GtkImage * -get_image (IndicatorObject * io) -{ - IndicatorMessages *self = INDICATOR_MESSAGES (io); - - gtk_widget_show (self->image); - return GTK_IMAGE (self->image); -} - -static GtkMenu * -get_menu (IndicatorObject * io) -{ - IndicatorMessages *self = INDICATOR_MESSAGES (io); - - return GTK_MENU (self->gtkmenu); -} - -static const gchar * -get_accessible_desc (IndicatorObject * io) -{ - IndicatorMessages *self = INDICATOR_MESSAGES (io); - return self->accessible_desc; -} - -static const gchar * -get_name_hint (IndicatorObject *io) -{ - return PACKAGE; -} - -static void -indicator_messages_accessible_desc_updated (IndicatorMessages *self) -{ - GList *entries; - - entries = indicator_object_get_entries (INDICATOR_OBJECT (self)); - g_return_if_fail (entries != NULL); - - g_signal_emit_by_name (self, INDICATOR_OBJECT_SIGNAL_ACCESSIBLE_DESC_UPDATE, entries->data); - - g_list_free (entries); -} - -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_item_attribute (menu, index, attribute, "s", &iconstr)) { - GError *error; - - icon = g_icon_new_for_string (iconstr, &error); - if (icon == NULL) { - g_warning ("unable to load icon: %s", error->message); - g_error_free (error); - } - - g_free (iconstr); - } - - return icon; -} - -static void -indicator_messages_add_toplevel_menu (IndicatorMessages *self) -{ - GIcon *icon; - GMenuModel *popup; - - indicator_object_set_visible (INDICATOR_OBJECT (self), TRUE); - - 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); - } - - 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); - } - - 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 (popup); - } -} - -static void -menu_items_changed (GMenuModel *menu, - gint position, - gint removed, - gint added, - gpointer user_data) -{ - IndicatorMessages *self = user_data; - - 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, - gpointer user_data) -{ - IndicatorMessages *self = user_data; - - indicator_messages_update_icon (self, value); -} diff --git a/src/messages-service.c b/src/messages-service.c index 25a19b9..0eec2ad 100644 --- a/src/messages-service.c +++ b/src/messages-service.c @@ -23,419 +23,26 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include <config.h> #include <locale.h> -#include <libindicator/indicator-service.h> -#include <gdk/gdk.h> #include <gio/gio.h> -#include <gio/gdesktopappinfo.h> #include <glib/gi18n.h> +#include <glib-unix.h> -#include "app-section.h" #include "dbus-data.h" -#include "gactionmuxer.h" #include "gsettingsstrv.h" #include "gmenuutils.h" #include "indicator-messages-service.h" +#include "indicator-messages-application.h" +#include "im-phone-menu.h" +#include "im-desktop-menu.h" +#include "im-application-list.h" #define NUM_STATUSES 5 -static GHashTable *applications; +static ImApplicationList *applications; static IndicatorMessagesService *messages_service; -static GSimpleActionGroup *actions; -static GActionMuxer *action_muxer; -static GMenu *toplevel_menu; -static GMenu *menu; -static GMenuModel *chat_section; +static GHashTable *menus; 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) -{ - const gchar *id; - - id = g_app_info_get_id (appinfo); - if (!id) - return NULL; - - if (g_str_has_suffix (id, ".desktop")) - return g_strndup (id, strlen (id) - 8); - else - return g_strdup (id); -} - -static void -actions_changed (GObject *object, - GParamSpec *pspec, - gpointer user_data) -{ - AppSection *section = APP_SECTION (object); - gchar *id; - GActionGroup *actions; - - id = g_app_info_get_simple_id (app_section_get_app_info (section)); - actions = app_section_get_actions (section); - - g_action_muxer_insert (action_muxer, id, actions); - g_free (id); -} - - -static gboolean -app_section_draws_attention (gpointer key, - gpointer value, - gpointer user_data) -{ - AppSection *section = value; - return app_section_get_draws_attention (section); -} - -static void -draws_attention_changed (GObject *object, - GParamSpec *pspec, - gpointer user_data) -{ - GSimpleAction *clear; - - clear = G_SIMPLE_ACTION (g_simple_action_group_lookup (actions, "clear")); - g_return_if_fail (clear != NULL); - - draws_attention = g_hash_table_find (applications, app_section_draws_attention, NULL) != NULL; - - g_simple_action_set_enabled (clear, draws_attention); - - indicator_messages_update_icon (); -} - -static gboolean -app_section_uses_chat (gpointer key, - gpointer value, - gpointer user_data) -{ - AppSection *section = value; - return app_section_get_uses_chat_status (section); -} - -static void -update_chat_section () -{ - gboolean show_chat; - GMenuModel *first_section; - - show_chat = g_hash_table_find (applications, app_section_uses_chat, NULL) != NULL; - - first_section = g_menu_model_get_item_link (G_MENU_MODEL (menu), 0, G_MENU_LINK_SECTION); - if (first_section == chat_section) { - if (!show_chat) - g_menu_remove (menu, 0); - } - else { - if (show_chat) - g_menu_insert_section (menu, 0, NULL, chat_section); - } - - if (first_section != NULL) - g_object_unref (first_section); - - indicator_messages_update_icon (); -} - -static void -uses_chat_status_changed (GObject *object, - GParamSpec *pspec, - gpointer user_data) -{ - update_chat_section (); -} - -static gboolean -strv_contains (const gchar **strv, - const gchar *needle) -{ - const gchar **it; - - it = strv; - while (*it != NULL && !g_str_equal (*it, needle)) - it++; - - return *it != NULL; -} - -static void -update_chat_status () -{ - GHashTableIter iter; - AppSection *section; - int pos; - GAction *status; - - for (pos = 0; pos < G_N_ELEMENTS (global_status); pos++) - global_status[pos] = NULL; - - pos = 0; - g_hash_table_iter_init (&iter, applications); - while (g_hash_table_iter_next (&iter, NULL, (gpointer) §ion) && - pos < G_N_ELEMENTS (global_status)) - { - const gchar *status_str = NULL; - - status_str = app_section_get_status (section); - if (status_str != NULL && !strv_contains (global_status, status_str)) - global_status[pos++] = status_str; - } - - if (pos == 0) - global_status[0] = "offline"; - - status = g_simple_action_group_lookup (actions, "status"); - g_return_if_fail (status != NULL); - - g_simple_action_set_state (G_SIMPLE_ACTION (status), g_variant_new_strv (global_status, -1)); - - indicator_messages_update_icon (); -} - -static void -chat_status_changed (GObject *object, - GParamSpec *pspec, - gpointer user_data) -{ - update_chat_status (); -} - -static void -remove_section (AppSection *section, - const gchar *id) -{ - int pos = g_menu_find_section (menu, app_section_get_menu (section)); - if (pos >= 0) - g_menu_remove (menu, pos); - g_action_muxer_remove (action_muxer, id); - - g_signal_handlers_disconnect_by_func (section, actions_changed, NULL); - g_signal_handlers_disconnect_by_func (section, draws_attention_changed, NULL); - g_signal_handlers_disconnect_by_func (section, uses_chat_status_changed, NULL); - g_signal_handlers_disconnect_by_func (section, chat_status_changed, NULL); - g_signal_handlers_disconnect_by_func (section, remove_section, NULL); - - g_hash_table_remove (applications, id); - - if (g_hash_table_size (applications) == 0 && - g_menu_model_get_n_items (G_MENU_MODEL (toplevel_menu)) == 1) { - g_menu_remove (toplevel_menu, 0); - } - - update_chat_status (); - update_chat_section (); -} - -static AppSection * -add_application (const gchar *desktop_id) -{ - GDesktopAppInfo *appinfo; - gchar *id; - AppSection *section; - - appinfo = g_desktop_app_info_new (desktop_id); - if (!appinfo) { - g_warning ("could not add '%s', there's no desktop file with that id", desktop_id); - return NULL; - } - - id = g_app_info_get_simple_id (G_APP_INFO (appinfo)); - section = g_hash_table_lookup (applications, id); - - if (!section) { - GMenuItem *menuitem; - - section = app_section_new(appinfo); - g_hash_table_insert (applications, g_strdup (id), section); - - g_action_muxer_insert (action_muxer, id, app_section_get_actions (section)); - g_signal_connect (section, "notify::actions", - G_CALLBACK (actions_changed), NULL); - g_signal_connect (section, "notify::draws-attention", - 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)); - g_menu_item_set_attribute (menuitem, "action-namespace", "s", id); - g_menu_insert_item (menu, g_menu_model_get_n_items (G_MENU_MODEL (menu)) -1, menuitem); - 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; -} - -static void -remove_application (const char *desktop_id) -{ - GDesktopAppInfo *appinfo; - gchar *id; - AppSection *section; - - appinfo = g_desktop_app_info_new (desktop_id); - if (!appinfo) { - g_warning ("could not remove '%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) { - remove_section (section, id); - } - else { - g_warning ("could not remove '%s', it's not registered", desktop_id); - } - - g_free (id); - g_object_unref (appinfo); -} - -/* This function turns a specific desktop id into a menu - item and registers it appropriately with everyone */ -static gboolean -build_launcher (gpointer data) -{ - gchar *desktop_id = data; - - add_application (desktop_id); - - g_free (desktop_id); - return FALSE; -} - -/* This function goes through all the launchers that we're - supposed to be grabbing and decides to show turn them - into menu items or not. It doens't do the work, but it - makes the decision. */ -static gboolean -build_launchers (gpointer data) -{ - gchar **applications = g_settings_get_strv (settings, "applications"); - gchar **app; - - g_return_val_if_fail (applications != NULL, FALSE); - - for (app = applications; *app; app++) - { - g_idle_add(build_launcher, g_strdup (*app)); - } - - g_strfreev (applications); - return FALSE; -} - -static void -service_shutdown (IndicatorService * service, gpointer user_data) -{ - GMainLoop *mainloop = user_data; - - g_warning("Shutting down service!"); - g_main_loop_quit(mainloop); -} - -static void -app_section_remove_attention (gpointer key, - gpointer value, - gpointer user_data) -{ - AppSection *section = value; - app_section_clear_draws_attention (section); -} - -static void -clear_action_activate (GSimpleAction *simple, - GVariant *param, - gpointer user_data) -{ - g_hash_table_foreach (applications, app_section_remove_attention, NULL); -} - -static void -status_action_activate (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - const gchar *status; - - status = g_variant_get_string (parameter, NULL); - - indicator_messages_service_emit_status_changed (messages_service, status); -} static void register_application (IndicatorMessagesService *service, @@ -444,18 +51,15 @@ register_application (IndicatorMessagesService *service, const gchar *menu_path, gpointer user_data) { - AppSection *section; GDBusConnection *bus; const gchar *sender; - section = add_application (desktop_id); - if (!section) - return; + im_application_list_add (applications, desktop_id); bus = g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (service)); sender = g_dbus_method_invocation_get_sender (invocation); - app_section_set_object_path (section, bus, sender, menu_path); + im_application_list_set_remote (applications, desktop_id, bus, sender, menu_path); g_settings_strv_append_unique (settings, "applications", desktop_id); indicator_messages_service_complete_register_application (service, invocation); @@ -467,36 +71,13 @@ unregister_application (IndicatorMessagesService *service, const gchar *desktop_id, gpointer user_data) { - remove_application (desktop_id); + im_application_list_remove (applications, desktop_id); g_settings_strv_remove (settings, "applications", desktop_id); indicator_messages_service_complete_unregister_application (service, invocation); } static void -application_stopped_running (IndicatorMessagesService *service, - GDBusMethodInvocation *invocation, - const gchar *desktop_id, - gpointer user_data) -{ - GDesktopAppInfo *appinfo; - gchar *id; - AppSection *section; - - indicator_messages_service_complete_application_stopped_running (service, invocation); - - if (!(appinfo = g_desktop_app_info_new (desktop_id))) - return; - - id = g_app_info_get_simple_id (G_APP_INFO (appinfo)); - section = g_hash_table_lookup (applications, id); - app_section_unset_object_path (section); - - g_free (id); - g_object_unref (appinfo); -} - -static void set_status (IndicatorMessagesService *service, GDBusMethodInvocation *invocation, const gchar *desktop_id, @@ -504,8 +85,7 @@ set_status (IndicatorMessagesService *service, gpointer user_data) { GDesktopAppInfo *appinfo; - gchar *id; - AppSection *section; + const gchar *id; g_return_if_fail (g_str_equal (status_str, "available") || g_str_equal (status_str, "away")|| @@ -519,113 +99,53 @@ set_status (IndicatorMessagesService *service, 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); + id = g_app_info_get_id (G_APP_INFO (appinfo)); + + im_application_list_set_status(applications, id, status_str); indicator_messages_service_complete_set_status (service, invocation); - g_free (id); g_object_unref (appinfo); } -static GSimpleActionGroup * -create_action_group (void) -{ - GSimpleActionGroup *actions; - 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 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_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); - g_signal_connect (clear, "activate", G_CALLBACK (clear_action_activate), NULL); - - g_simple_action_group_insert (actions, G_ACTION (messages)); - g_simple_action_group_insert (actions, G_ACTION (status)); - g_simple_action_group_insert (actions, G_ACTION (clear)); - - g_free (icon); - return actions; -} - -static GMenuModel * -create_status_section (void) +/* The status has been set by the user, let's tell the world! */ +static void +status_set_by_user (ImApplicationList * list, const gchar * status, gpointer user_data) { - GMenu *menu; - GMenuItem *item; - struct status_item { - gchar *label; - gchar *action; - gchar *icon_name; - } status_items[] = { - { _("Available"), "status::available", "user-available" }, - { _("Away"), "status::away", "user-away" }, - { _("Busy"), "status::busy", "user-busy" }, - { _("Invisible"), "status::invisible", "user-invisible" }, - { _("Offline"), "status::offline", "user-offline" } - }; - int i; - - menu = g_menu_new (); - - item = g_menu_item_new (NULL, NULL); - g_menu_item_set_attribute (item, "x-canonical-type", "s", "IdoMenuItem"); - - for (i = 0; i < G_N_ELEMENTS (status_items); i++) { - g_menu_item_set_label (item, status_items[i].label); - g_menu_item_set_detailed_action (item, status_items[i].action); - g_menu_item_set_attribute (item, "x-canonical-icon", "s", status_items[i].icon_name); - g_menu_append_item (menu, item); - } - - g_object_unref (item); - return G_MENU_MODEL (menu); + indicator_messages_service_emit_status_changed(messages_service, status); + return; } static void -got_bus (GObject *object, - GAsyncResult * res, - gpointer user_data) +on_bus_acquired (GDBusConnection *bus, + const gchar *name, + gpointer user_data) { - GDBusConnection *bus; GError *error = NULL; - - bus = g_bus_get_finish (res, &error); - if (!bus) { - g_warning ("unable to connect to the session bus: %s", error->message); - g_error_free (error); - return; - } + GHashTableIter it; + const gchar *profile; + ImMenu *menu; g_dbus_connection_export_action_group (bus, INDICATOR_MESSAGES_DBUS_OBJECT, - G_ACTION_GROUP (action_muxer), &error); + im_application_list_get_action_group (applications), + &error); if (error) { g_warning ("unable to export action group on dbus: %s", error->message); g_error_free (error); return; } - g_dbus_connection_export_menu_model (bus, INDICATOR_MESSAGES_DBUS_OBJECT, - G_MENU_MODEL (toplevel_menu), &error); - if (error) { - g_warning ("unable to export menu on dbus: %s", error->message); - g_error_free (error); - return; + g_hash_table_iter_init (&it, menus); + while (g_hash_table_iter_next (&it, (gpointer *) &profile, (gpointer *) &menu)) { + gchar *object_path; + + object_path = g_strconcat (INDICATOR_MESSAGES_DBUS_OBJECT, "/", profile, NULL); + if (!im_menu_export (menu, bus, object_path, &error)) { + g_warning ("unable to export menu for profile '%s': %s", profile, error->message); + g_clear_error (&error); + } + + g_free (object_path); } g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (messages_service), @@ -640,18 +160,38 @@ got_bus (GObject *object, g_object_unref (bus); } +static void +on_name_lost (GDBusConnection *bus, + const gchar *name, + gpointer user_data) +{ + GMainLoop *mainloop = user_data; + + g_main_loop_quit (mainloop); +} + +static gboolean +sig_term_handler (gpointer user_data) +{ + GMainLoop *mainloop = user_data; + + g_main_loop_quit (mainloop); + + return FALSE; +} + int main (int argc, char ** argv) { - GMainLoop * mainloop; - IndicatorService * service; + GMainLoop * mainloop = NULL; + GBusNameOwnerFlags flags; - gdk_init(&argc, &argv); - mainloop = g_main_loop_new (NULL, FALSE); + /* Glib init */ +#if G_ENCODE_VERSION(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34 + g_type_init(); +#endif - /* Create the Indicator Service interface */ - service = indicator_service_new_version(INDICATOR_MESSAGES_DBUS_NAME, 1); - g_signal_connect(service, INDICATOR_SERVICE_SIGNAL_SHUTDOWN, G_CALLBACK(service_shutdown), mainloop); + mainloop = g_main_loop_new (NULL, FALSE); /* Setting up i18n and gettext. Apparently, we need all of these. */ @@ -662,40 +202,48 @@ main (int argc, char ** argv) /* Bring up the service DBus interface */ messages_service = indicator_messages_service_skeleton_new (); - g_bus_get (G_BUS_TYPE_SESSION, NULL, got_bus, NULL); + flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT; + if (argc >= 2 && g_str_equal (argv[1], "--replace")) + flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE; - actions = create_action_group (); - - action_muxer = g_action_muxer_new (); - g_action_muxer_insert (action_muxer, NULL, G_ACTION_GROUP (actions)); + g_bus_own_name (G_BUS_TYPE_SESSION, "com.canonical.indicator.messages", flags, + on_bus_acquired, NULL, on_name_lost, mainloop, NULL); g_signal_connect (messages_service, "handle-register-application", G_CALLBACK (register_application), NULL); g_signal_connect (messages_service, "handle-unregister-application", G_CALLBACK (unregister_application), NULL); - g_signal_connect (messages_service, "handle-application-stopped-running", - G_CALLBACK (application_stopped_running), NULL); g_signal_connect (messages_service, "handle-set-status", G_CALLBACK (set_status), NULL); - menu = g_menu_new (); - chat_section = create_status_section (); - g_menu_append (menu, _("Clear"), "clear"); - - toplevel_menu = g_menu_new (); + applications = im_application_list_new (); + g_signal_connect (applications, "status-set", + G_CALLBACK (status_set_by_user), NULL); settings = g_settings_new ("com.canonical.indicator.messages"); + { + gchar **app_ids; + gchar **id; + + app_ids = g_settings_get_strv (settings, "applications"); + for (id = app_ids; *id; id++) + im_application_list_add (applications, *id); + + g_strfreev (app_ids); + } - applications = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + menus = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref); + g_hash_table_insert (menus, "phone", im_phone_menu_new (applications)); + g_hash_table_insert (menus, "desktop", im_desktop_menu_new (applications)); - g_idle_add(build_launchers, NULL); + g_unix_signal_add(SIGTERM, sig_term_handler, mainloop); g_main_loop_run(mainloop); /* Clean up */ + g_hash_table_unref (menus); g_object_unref (messages_service); - g_object_unref (chat_section); g_object_unref (settings); - g_hash_table_unref (applications); + g_object_unref (applications); return 0; } diff --git a/src/messages-service.xml b/src/messages-service.xml deleted file mode 100644 index 3c3c779..0000000 --- a/src/messages-service.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<node name="/"> - <interface name="com.canonical.indicator.messages.service"> - - <method name="RegisterApplication"> - <arg type="s" name="desktop_id" direction="in" /> - <arg type="o" name="menu_path" direction="in" /> - </method> - - <method name="UnregisterApplication"> - <arg type="s" name="desktop_id" direction="in" /> - </method> - - <method name="ApplicationStoppedRunning"> - <arg type="s" name="desktop_id" direction="in" /> - </method> - - <method name="SetStatus"> - <arg type="s" name="desktop_id" direction="in" /> - <arg type="s" name="status" direction="in" /> - </method> - - <signal name="StatusChanged"> - <arg type="s" name="status" direction="in" /> - </signal> - - </interface> -</node> |