From 88b2fd89ff0bc4be81acd8f6d5751dba96e295f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20=27sil2100=27=20Zemczak?= Date: Tue, 20 Aug 2013 10:53:01 +0200 Subject: Revert rev 352 as it is causing a SIGTRAP on start --- src/Makefile.am | 71 ++- src/app-section.c | 434 +++++++---------- src/dbus-data.h | 17 +- src/gactionmuxer.c | 8 - src/gactionmuxer.h | 3 - src/gmenuutils.c | 2 +- src/ido-detail-label.c | 401 ++++++++++++++++ src/ido-detail-label.h | 59 +++ src/ido-menu-item.c | 439 +++++++++++++++++ src/ido-menu-item.h | 54 +++ src/im-app-menu-item.c | 354 ++++++++++++++ src/im-app-menu-item.h | 54 +++ src/im-application-list.c | 1139 --------------------------------------------- src/im-application-list.h | 62 --- src/im-desktop-menu.c | 294 ------------ src/im-desktop-menu.h | 35 -- src/im-menu.c | 191 -------- src/im-menu.h | 64 --- src/im-phone-menu.c | 317 ------------- src/im-phone-menu.h | 68 --- src/im-source-menu-item.c | 407 ++++++++++++++++ src/im-source-menu-item.h | 54 +++ src/indicator-messages.c | 382 +++++++++++++++ src/messages-service.c | 617 +++++++++++++++++++++--- src/messages-service.xml | 28 ++ 25 files changed, 3000 insertions(+), 2554 deletions(-) create mode 100644 src/ido-detail-label.c create mode 100644 src/ido-detail-label.h create mode 100644 src/ido-menu-item.c create mode 100644 src/ido-menu-item.h create mode 100644 src/im-app-menu-item.c create mode 100644 src/im-app-menu-item.h delete mode 100644 src/im-application-list.c delete mode 100644 src/im-application-list.h delete mode 100644 src/im-desktop-menu.c delete mode 100644 src/im-desktop-menu.h delete mode 100644 src/im-menu.c delete mode 100644 src/im-menu.h delete mode 100644 src/im-phone-menu.c delete mode 100644 src/im-phone-menu.h create mode 100644 src/im-source-menu-item.c create mode 100644 src/im-source-menu-item.h create mode 100644 src/indicator-messages.c create mode 100644 src/messages-service.xml (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index e03406a..1df80e5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,10 +1,53 @@ +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 \ @@ -13,20 +56,11 @@ indicator_messages_service_SOURCES = \ gsettingsstrv.c \ gsettingsstrv.h \ gmenuutils.c \ - 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 + gmenuutils.h indicator_messages_service_CFLAGS = \ $(APPLET_CFLAGS) \ $(COVERAGE_CFLAGS) \ - -I$(top_builddir)/common \ -Wall \ -Wl,-Bsymbolic-functions \ -Wl,-z,defs \ @@ -35,11 +69,26 @@ 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 aad994d..1106c62 100644 --- a/src/app-section.c +++ b/src/app-section.c @@ -34,7 +34,6 @@ with this program. If not, see . #include "dbus-data.h" #include "gmenuutils.h" #include "gactionmuxer.h" -#include "indicator-messages-application.h" struct _AppSectionPrivate { @@ -43,14 +42,11 @@ struct _AppSectionPrivate IndicatorDesktopShortcuts * ids; - GCancellable *app_proxy_cancellable; - IndicatorMessagesApplication *app_proxy; - GMenu *menu; - GMenu *source_menu; + GMenuModel *source_menu; GSimpleActionGroup *static_shortcuts; - GSimpleActionGroup *source_actions; + GActionGroup *source_actions; GActionMuxer *muxer; gboolean draws_attention; @@ -94,6 +90,19 @@ 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, @@ -161,7 +170,6 @@ static void app_section_init (AppSection *self) { AppSectionPrivate *priv; - GMenuItem *item; self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, APP_SECTION_TYPE, @@ -171,19 +179,10 @@ 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; @@ -250,30 +249,32 @@ 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); @@ -313,12 +314,14 @@ nick_activate_cb (GSimpleAction *action, g_return_if_fail(priv->ids != NULL); - if (!indicator_desktop_shortcuts_nick_exec_with_context(priv->ids, nick, NULL)) { + GAppLaunchContext *context = get_launch_context (g_variant_get_uint32 (param)); + + if (!indicator_desktop_shortcuts_nick_exec_with_context(priv->ids, nick, context)) { g_warning("Unable to execute nick '%s' for desktop file '%s'", nick, g_desktop_app_info_get_filename (priv->appinfo)); } - return; + g_object_unref (context); } static void @@ -411,9 +414,7 @@ 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))); - if (iconstr != NULL) { - g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr); - } + g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr); g_free (iconstr); g_menu_append_item (priv->menu, item); @@ -444,11 +445,6 @@ 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); @@ -587,242 +583,49 @@ app_section_get_draws_attention (AppSection *self) void app_section_clear_draws_attention (AppSection *self) { - 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; + AppSectionPrivate * priv = self->priv; + gchar **action_names; 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))); -} -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); + if (priv->source_actions == NULL) return; - } - g_menu_clear (priv->source_menu); - g_simple_action_group_clear (priv->source_actions); - priv->draws_attention = FALSE; + action_names = g_action_group_list_actions (priv->source_actions); - g_variant_iter_init (&iter, sources); - while (g_variant_iter_next (&iter, "(&s&s&sux&sb)", &id, &label, &iconstr, - &count, &time, &string, &draws_attention)) - { + for (it = action_names; *it; it++) { GVariant *state; - GSimpleAction *action; - GMenuItem *item; - 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)); + state = g_action_group_get_action_state (priv->source_actions, *it); + if (!state) + continue; - 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); + /* clear draws-attention while preserving other state */ + if (action_draws_attention (state)) { + guint32 count; + gint64 time; + const gchar *str; + GVariant *new_state; - priv->draws_attention = priv->draws_attention || draws_attention; - - 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_get (state, "(ux&sb)", &count, &time, &str, NULL); -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)); + new_state = g_variant_new ("(uxsb)", count, time, str, FALSE); + g_action_group_change_action_state (priv->source_actions, *it, new_state); + } - if (draws_attention && !priv->draws_attention) { - priv->draws_attention = TRUE; - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); + g_variant_unref (state); } - 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); + g_strfreev (action_names); } static void -source_removed (IndicatorMessagesApplication *app, - const gchar *id, - gpointer user_data) +application_vanished (GDBusConnection *bus, + const gchar *name, + gpointer user_data) { AppSection *self = user_data; - 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); + app_section_unset_object_path (self); } /* @@ -843,20 +646,27 @@ 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->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_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 = FALSE; + 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->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->name_watch_id = g_bus_watch_name_on_connection (bus, bus_name, 0, NULL, application_vanished, @@ -884,19 +694,26 @@ 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; } - g_simple_action_group_clear (priv->source_actions); - g_menu_clear (priv->source_menu); + 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); + } priv->draws_attention = FALSE; g_clear_pointer (&priv->chat_status, g_free); @@ -910,6 +727,85 @@ 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 59bd305..64747a9 100644 --- a/src/dbus-data.h +++ b/src/dbus-data.h @@ -1,24 +1,9 @@ -/* - * 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 . - */ #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" +#define INDICATOR_MESSAGES_DBUS_OBJECT "/com/canonical/indicator/messages/menu" #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 0f3cda4..2b1d11a 100644 --- a/src/gactionmuxer.c +++ b/src/gactionmuxer.c @@ -483,11 +483,3 @@ 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 caf9ec7..5c5e839 100644 --- a/src/gactionmuxer.h +++ b/src/gactionmuxer.h @@ -40,8 +40,5 @@ 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 cfd751e..f63615b 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, "icon", "s", icon_name); + g_menu_item_set_attribute (item, "x-canonical-icon", "s", icon_name); g_menu_append_item (menu, item); diff --git a/src/ido-detail-label.c b/src/ido-detail-label.c new file mode 100644 index 0000000..8b7ef90 --- /dev/null +++ b/src/ido-detail-label.c @@ -0,0 +1,401 @@ +/* + * 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 . + * + * Authors: + * Lars Uebernickel + */ + +#include "ido-detail-label.h" + +#include + +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 new file mode 100644 index 0000000..1995fee --- /dev/null +++ b/src/ido-detail-label.h @@ -0,0 +1,59 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Lars Uebernickel + */ + +#ifndef __IDO_DETAIL_LABEL_H__ +#define __IDO_DETAIL_LABEL_H__ + +#include + +#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 new file mode 100644 index 0000000..f702828 --- /dev/null +++ b/src/ido-menu-item.c @@ -0,0 +1,439 @@ +/* + * 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 . + * + * Authors: + * Lars Uebernickel + */ + +#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 new file mode 100644 index 0000000..0521928 --- /dev/null +++ b/src/ido-menu-item.h @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Lars Uebernickel + */ + +#ifndef __IDO_MENU_ITEM_H__ +#define __IDO_MENU_ITEM_H__ + +#include + +#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 new file mode 100644 index 0000000..03b11c2 --- /dev/null +++ b/src/im-app-menu-item.c @@ -0,0 +1,354 @@ +/* + * 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 . + * + * Authors: + * Lars Uebernickel + */ + +#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 new file mode 100644 index 0000000..519de8d --- /dev/null +++ b/src/im-app-menu-item.h @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Lars Uebernickel + */ + +#ifndef __IM_APP_MENU_ITEM_H__ +#define __IM_APP_MENU_ITEM_H__ + +#include + +#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 deleted file mode 100644 index bba7972..0000000 --- a/src/im-application-list.c +++ /dev/null @@ -1,1139 +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 . - * - * Authors: - * Lars Uebernickel - */ - -#include "im-application-list.h" - -#include "indicator-messages-application.h" -#include "gactionmuxer.h" - -#include -#include - -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 *actions; - GSimpleActionGroup *source_actions; - GSimpleActionGroup *message_actions; - GActionMuxer *message_sub_actions; - GCancellable *cancellable; - gboolean draws_attention; -} 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_slice_free (Application, app); -} - -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 *icon_name; - GVariant *state; - GActionGroup *main_actions; - - if (g_hash_table_find (list->applications, application_draws_attention, NULL)) - icon_name = "indicator-messages-new"; - else - icon_name = "indicator-messages"; - - main_actions = g_action_muxer_get_group (list->muxer, NULL); - state = g_variant_new ("(sssb)", "", icon_name, "Messages", TRUE); - g_action_group_change_action_state (main_actions, "messages", state); -} - -/* 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[] = { - { "messages", NULL, NULL, "('', 'indicator-messages', 'Messages', true)", NULL }, - { "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 (); - 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)); - -} - -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; - - g_desktop_app_info_launch_action (app->info, 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; - - 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); - - 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; - - 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)); - - { - const gchar *const *app_actions; - - for (app_actions = g_desktop_app_info_list_actions (app->info); *app_actions; app_actions++) - { - GSimpleAction *action; - - action = g_simple_action_new (*app_actions, 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; - - g_variant_get (source, "(&s&s&sux&sb)", - &id, &label, &iconstr, &count, &time, &string, &draws_attention); - - 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); - - 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); - - 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])); - - return; -} - diff --git a/src/im-application-list.h b/src/im-application-list.h deleted file mode 100644 index f1de220..0000000 --- a/src/im-application-list.h +++ /dev/null @@ -1,62 +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 . - * - * Authors: - * Lars Uebernickel - */ - -#ifndef __IM_APPLICATION_LIST_H__ -#define __IM_APPLICATION_LIST_H__ - -#include -#include - -#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 deleted file mode 100644 index 1040d62..0000000 --- a/src/im-desktop-menu.c +++ /dev/null @@ -1,294 +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 . - * - * Authors: - * Lars Uebernickel - */ - -#include "im-desktop-menu.h" -#include - -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 */ -#if 0 - { - const gchar *const *actions; - - for (actions = g_desktop_app_info_list_actions (app_info); *actions; actions++) - { - gchar *label; - - label = g_desktop_app_info_get_action_name (app_info, *actions); - g_menu_append (app_section, label, *actions); - - g_free (label); - } - } -#endif - - 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), -1, 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, action); - 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 deleted file mode 100644 index 9469ea6..0000000 --- a/src/im-desktop-menu.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 . - * - * Authors: - * Lars Uebernickel - */ - -#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 deleted file mode 100644 index ac23a29..0000000 --- a/src/im-menu.c +++ /dev/null @@ -1,191 +0,0 @@ -/* - * 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 . - * - * Authors: - * Lars Uebernickel - */ - -#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, - gint position, - const gchar *namespace, - GMenuModel *section) -{ - 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); - - /* count from the back if position is < 0 */ - if (position < 0) - position = g_menu_model_get_n_items (G_MENU_MODEL (priv->menu)) + position; - - item = g_menu_item_new_section (NULL, section); - - 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 deleted file mode 100644 index d3775ad..0000000 --- a/src/im-menu.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 . - * - * Authors: - * Lars Uebernickel - */ - -#ifndef __IM_MENU_H__ -#define __IM_MENU_H__ - -#include -#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, - gint position, - const gchar *namespace, - GMenuModel *section); - -#endif diff --git a/src/im-phone-menu.c b/src/im-phone-menu.c deleted file mode 100644 index 7381097..0000000 --- a/src/im-phone-menu.c +++ /dev/null @@ -1,317 +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 . - * - * Authors: - * Lars Uebernickel - */ - -#include "im-phone-menu.h" - -#include - -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 deleted file mode 100644 index 9742f61..0000000 --- a/src/im-phone-menu.h +++ /dev/null @@ -1,68 +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 . - * - * Authors: - * Lars Uebernickel - */ - -#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 new file mode 100644 index 0000000..2577c30 --- /dev/null +++ b/src/im-source-menu-item.c @@ -0,0 +1,407 @@ +/* + * 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 . + * + * Authors: + * Lars Uebernickel + */ + +#include "im-source-menu-item.h" + +#include +#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 new file mode 100644 index 0000000..c359b94 --- /dev/null +++ b/src/im-source-menu-item.h @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Lars Uebernickel + */ + +#ifndef __IM_SOURCE_MENU_ITEM_H__ +#define __IM_SOURCE_MENU_ITEM_H__ + +#include + +#define IM_TYPE_SOURCE_MENU_ITEM (im_source_menu_item_get_type ()) +#define IM_SOURCE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_SOURCE_MENU_ITEM, ImSourceMenuItem)) +#define IM_SOURCE_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_SOURCE_MENU_ITEM, ImSourceMenuItemClass)) +#define IS_IM_SOURCE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_SOURCE_MENU_ITEM)) +#define IS_IM_SOURCE_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_SOURCE_MENU_ITEM)) +#define IM_SOURCE_MENU_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_SOURCE_MENU_ITEM, ImSourceMenuItemClass)) + +typedef struct _ImSourceMenuItem ImSourceMenuItem; +typedef struct _ImSourceMenuItemClass ImSourceMenuItemClass; +typedef struct _ImSourceMenuItemPrivate ImSourceMenuItemPrivate; + +struct _ImSourceMenuItemClass +{ + GtkMenuItemClass parent_class; +}; + +struct _ImSourceMenuItem +{ + GtkMenuItem parent; + ImSourceMenuItemPrivate *priv; +}; + +GType im_source_menu_item_get_type (void); + +void im_source_menu_item_set_menu_item (ImSourceMenuItem *item, + GMenuItem *menuitem); +void im_source_menu_item_set_action_group (ImSourceMenuItem *self, + GActionGroup *action_group); + +#endif diff --git a/src/indicator-messages.c b/src/indicator-messages.c new file mode 100644 index 0000000..5c5df31 --- /dev/null +++ b/src/indicator-messages.c @@ -0,0 +1,382 @@ +/* +An indicator to show information that is in messaging applications +that the user is using. + +Copyright 2012 Canonical Ltd. + +Authors: + Ted Gould + Lars Uebernickel + +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 . +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 71fa09b..25a19b9 100644 --- a/src/messages-service.c +++ b/src/messages-service.c @@ -23,25 +23,419 @@ with this program. If not, see . #include #include +#include +#include #include +#include #include +#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 ImApplicationList *applications; +static GHashTable *applications; static IndicatorMessagesService *messages_service; -static GHashTable *menus; +static GSimpleActionGroup *actions; +static GActionMuxer *action_muxer; +static GMenu *toplevel_menu; +static GMenu *menu; +static GMenuModel *chat_section; static GSettings *settings; +static gboolean draws_attention; +static const gchar *global_status[6]; /* max 5: available, away, busy, invisible, offline */ + +static gchar * +indicator_messages_get_icon_name () +{ + GString *name; + GIcon *icon; + gchar *iconstr; + + name = g_string_new ("indicator-messages"); + + if (global_status[0] != NULL) + { + if (global_status[1] != NULL) + g_string_append (name, "-mixed"); + else + g_string_append_printf (name, "-%s", global_status[0]); + } + + if (draws_attention) + g_string_append (name, "-new"); + + icon = g_themed_icon_new (name->str); + g_themed_icon_append_name (G_THEMED_ICON (icon), + draws_attention ? "indicator-messages-new" + : "indicator-messages"); + + iconstr = g_icon_to_string (icon); + + g_object_unref (icon); + g_string_free (name, TRUE); + + return iconstr; +} + +static void +indicator_messages_update_icon () +{ + GSimpleAction *messages; + gchar *icon; + + messages = G_SIMPLE_ACTION (g_simple_action_group_lookup (actions, "messages")); + g_return_if_fail (messages != NULL); + + icon = indicator_messages_get_icon_name (); + g_simple_action_set_state (messages, g_variant_new_string (icon)); + + g_free (icon); +} + +static gchar * +g_app_info_get_simple_id (GAppInfo *appinfo) +{ + 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, @@ -50,15 +444,18 @@ register_application (IndicatorMessagesService *service, const gchar *menu_path, gpointer user_data) { + AppSection *section; GDBusConnection *bus; const gchar *sender; - im_application_list_add (applications, desktop_id); + section = add_application (desktop_id); + if (!section) + return; bus = g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (service)); sender = g_dbus_method_invocation_get_sender (invocation); - im_application_list_set_remote (applications, desktop_id, bus, sender, menu_path); + app_section_set_object_path (section, bus, sender, menu_path); g_settings_strv_append_unique (settings, "applications", desktop_id); indicator_messages_service_complete_register_application (service, invocation); @@ -70,12 +467,35 @@ unregister_application (IndicatorMessagesService *service, const gchar *desktop_id, gpointer user_data) { - im_application_list_remove (applications, desktop_id); + remove_application (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, @@ -84,7 +504,8 @@ set_status (IndicatorMessagesService *service, gpointer user_data) { GDesktopAppInfo *appinfo; - const gchar *id; + gchar *id; + AppSection *section; g_return_if_fail (g_str_equal (status_str, "available") || g_str_equal (status_str, "away")|| @@ -98,53 +519,113 @@ set_status (IndicatorMessagesService *service, return; } - id = g_app_info_get_id (G_APP_INFO (appinfo)); - - im_application_list_set_status(applications, id, status_str); + id = g_app_info_get_simple_id (G_APP_INFO (appinfo)); + section = g_hash_table_lookup (applications, id); + if (section != NULL) + app_section_set_status (section, status_str); indicator_messages_service_complete_set_status (service, invocation); + g_free (id); g_object_unref (appinfo); } -/* 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) +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) { - indicator_messages_service_emit_status_changed(messages_service, status); - return; + 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); } static void -on_bus_acquired (GDBusConnection *bus, - const gchar *name, - gpointer user_data) +got_bus (GObject *object, + GAsyncResult * res, + gpointer user_data) { + GDBusConnection *bus; GError *error = NULL; - GHashTableIter it; - const gchar *profile; - ImMenu *menu; + + 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; + } g_dbus_connection_export_action_group (bus, INDICATOR_MESSAGES_DBUS_OBJECT, - im_application_list_get_action_group (applications), - &error); + G_ACTION_GROUP (action_muxer), &error); if (error) { g_warning ("unable to export action group 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_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_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (messages_service), @@ -159,29 +640,19 @@ on_bus_acquired (GDBusConnection *bus, 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); -} - int main (int argc, char ** argv) { - GMainLoop * mainloop = NULL; - GBusNameOwnerFlags flags; - - /* Glib init */ -#if G_ENCODE_VERSION(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34 - g_type_init(); -#endif + GMainLoop * mainloop; + IndicatorService * service; + gdk_init(&argc, &argv); mainloop = g_main_loop_new (NULL, FALSE); + /* 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); + /* Setting up i18n and gettext. Apparently, we need all of these. */ setlocale (LC_ALL, ""); @@ -191,46 +662,40 @@ main (int argc, char ** argv) /* Bring up the service DBus interface */ messages_service = indicator_messages_service_skeleton_new (); - flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT; - if (argc >= 2 && g_str_equal (argv[1], "--replace")) - flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE; + g_bus_get (G_BUS_TYPE_SESSION, NULL, got_bus, NULL); + + actions = create_action_group (); - g_bus_own_name (G_BUS_TYPE_SESSION, "com.canonical.indicator.messages", flags, - on_bus_acquired, NULL, on_name_lost, mainloop, NULL); + action_muxer = g_action_muxer_new (); + g_action_muxer_insert (action_muxer, NULL, G_ACTION_GROUP (actions)); 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); - applications = im_application_list_new (); - g_signal_connect (applications, "status-set", - G_CALLBACK (status_set_by_user), NULL); + menu = g_menu_new (); + chat_section = create_status_section (); + g_menu_append (menu, _("Clear"), "clear"); - settings = g_settings_new ("com.canonical.indicator.messages"); - { - gchar **app_ids; - gchar **id; + toplevel_menu = g_menu_new (); - app_ids = g_settings_get_strv (settings, "applications"); - for (id = app_ids; *id; id++) - im_application_list_add (applications, *id); + settings = g_settings_new ("com.canonical.indicator.messages"); - 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_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_object_unref (applications); + g_hash_table_unref (applications); return 0; } diff --git a/src/messages-service.xml b/src/messages-service.xml new file mode 100644 index 0000000..3c3c779 --- /dev/null +++ b/src/messages-service.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3