aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPete Woods <pete.woods@canonical.com>2013-08-20 11:38:10 +0100
committerPete Woods <pete.woods@canonical.com>2013-08-20 11:38:10 +0100
commit79c89af3ac3c0c04f947c98b360a3ccaccbfc5dc (patch)
treecfc0e002b44a53eba419ad3f80b72201dde58bba /src
parent94fef3bf0ca765544ef93f4a6cd38684b3ccf02e (diff)
downloadayatana-indicator-messages-79c89af3ac3c0c04f947c98b360a3ccaccbfc5dc.tar.gz
ayatana-indicator-messages-79c89af3ac3c0c04f947c98b360a3ccaccbfc5dc.tar.bz2
ayatana-indicator-messages-79c89af3ac3c0c04f947c98b360a3ccaccbfc5dc.zip
Re-do merge
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am71
-rw-r--r--src/app-section.c434
-rw-r--r--src/dbus-data.h17
-rw-r--r--src/gactionmuxer.c8
-rw-r--r--src/gactionmuxer.h3
-rw-r--r--src/gmenuutils.c2
-rw-r--r--src/ido-detail-label.c401
-rw-r--r--src/ido-detail-label.h59
-rw-r--r--src/ido-menu-item.c439
-rw-r--r--src/ido-menu-item.h54
-rw-r--r--src/im-app-menu-item.c354
-rw-r--r--src/im-app-menu-item.h54
-rw-r--r--src/im-application-list.c1139
-rw-r--r--src/im-application-list.h62
-rw-r--r--src/im-desktop-menu.c294
-rw-r--r--src/im-desktop-menu.h35
-rw-r--r--src/im-menu.c191
-rw-r--r--src/im-menu.h64
-rw-r--r--src/im-phone-menu.c317
-rw-r--r--src/im-phone-menu.h68
-rw-r--r--src/im-source-menu-item.c407
-rw-r--r--src/im-source-menu-item.h54
-rw-r--r--src/indicator-messages.c382
-rw-r--r--src/messages-service.c617
-rw-r--r--src/messages-service.xml28
25 files changed, 2554 insertions, 3000 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 1df80e5..e03406a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,53 +1,10 @@
-BUILT_SOURCES =
EXTRA_DIST =
-CLEANFILES =
-DISTCLEANFILES =
libexec_PROGRAMS = indicator-messages-service
-
-######################################
-# Building the messages indicator
-######################################
-
-messaginglibdir = $(INDICATORDIR)
-messaginglib_LTLIBRARIES = libmessaging.la
-libmessaging_la_SOURCES = \
- indicator-messages.c \
- ido-menu-item.c \
- ido-menu-item.h \
- im-app-menu-item.c \
- im-app-menu-item.h \
- im-source-menu-item.c \
- im-source-menu-item.h \
- ido-detail-label.c \
- ido-detail-label.h \
- indicator-messages-service.c \
- indicator-messages-service.h
- dbus-data.h
-libmessaging_la_CFLAGS = \
- $(APPLET_CFLAGS) \
- $(COVERAGE_CFLAGS) \
- -Wall \
- -Wl,-Bsymbolic-functions \
- -Wl,-z,defs \
- -Wl,--as-needed \
- -Werror \
- -DG_LOG_DOMAIN=\"Indicator-Messages\"
-libmessaging_la_LIBADD = $(APPLET_LIBS) -lm
-libmessaging_la_LDFLAGS = \
- $(COVERAGE_LDFLAGS) \
- -module -avoid-version
-
-######################################
-# Building the messages service
-######################################
-
indicator_messages_service_SOURCES = \
messages-service.c \
- indicator-messages-service.c \
- indicator-messages-service.h \
app-section.c \
app-section.h \
dbus-data.h \
@@ -56,11 +13,20 @@ indicator_messages_service_SOURCES = \
gsettingsstrv.c \
gsettingsstrv.h \
gmenuutils.c \
- gmenuutils.h
+ gmenuutils.h \
+ im-menu.c \
+ im-menu.h \
+ im-phone-menu.c \
+ im-phone-menu.h \
+ im-desktop-menu.c \
+ im-desktop-menu.h \
+ im-application-list.c \
+ im-application-list.h
indicator_messages_service_CFLAGS = \
$(APPLET_CFLAGS) \
$(COVERAGE_CFLAGS) \
+ -I$(top_builddir)/common \
-Wall \
-Wl,-Bsymbolic-functions \
-Wl,-z,defs \
@@ -69,26 +35,11 @@ indicator_messages_service_CFLAGS = \
-DG_LOG_DOMAIN=\"Indicator-Messages\"
indicator_messages_service_LDADD = \
+ $(top_builddir)/common/libmessaging-common.la \
$(APPLET_LIBS)
indicator_messages_service_LDFLAGS = \
$(COVERAGE_LDFLAGS)
-indicator-messages-service.c: $(top_srcdir)/src/messages-service.xml
- $(AM_V_GEN) gdbus-codegen \
- --interface-prefix com.canonical.indicator.messages. \
- --generate-c-code indicator-messages-service \
- --c-namespace IndicatorMessages \
- $^
-indicator-messages-service.h: indicator-messages-service.c
-
-BUILT_SOURCES += \
- indicator-messages-service.c \
- indicator-messages-service.h
-
EXTRA_DIST += \
messages-service.xml
-
-CLEANFILES += \
- $(BUILT_SOURCES)
-
diff --git a/src/app-section.c b/src/app-section.c
index 1106c62..aad994d 100644
--- a/src/app-section.c
+++ b/src/app-section.c
@@ -34,6 +34,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include "dbus-data.h"
#include "gmenuutils.h"
#include "gactionmuxer.h"
+#include "indicator-messages-application.h"
struct _AppSectionPrivate
{
@@ -42,11 +43,14 @@ struct _AppSectionPrivate
IndicatorDesktopShortcuts * ids;
+ GCancellable *app_proxy_cancellable;
+ IndicatorMessagesApplication *app_proxy;
+
GMenu *menu;
- GMenuModel *source_menu;
+ GMenu *source_menu;
GSimpleActionGroup *static_shortcuts;
- GActionGroup *source_actions;
+ GSimpleActionGroup *source_actions;
GActionMuxer *muxer;
gboolean draws_attention;
@@ -90,19 +94,6 @@ static void launch_action_change_state (GSimpleAction *action,
gpointer user_data);
static void app_section_set_app_info (AppSection *self,
GDesktopAppInfo *appinfo);
-static gboolean any_action_draws_attention (GActionGroup *group,
- const gchar *ignored_action);
-static void action_added (GActionGroup *group,
- const gchar *action_name,
- gpointer user_data);
-static void action_state_changed (GActionGroup *group,
- const gchar *action_name,
- GVariant *value,
- gpointer user_data);
-static void action_removed (GActionGroup *group,
- const gchar *action_name,
- gpointer user_data);
-static gboolean action_draws_attention (GVariant *state);
static void desktop_file_changed_cb (GFileMonitor *monitor,
GFile *file,
GFile *other_file,
@@ -170,6 +161,7 @@ static void
app_section_init (AppSection *self)
{
AppSectionPrivate *priv;
+ GMenuItem *item;
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
APP_SECTION_TYPE,
@@ -179,10 +171,19 @@ app_section_init (AppSection *self)
priv->appinfo = NULL;
priv->menu = g_menu_new ();
+
+ priv->source_menu = g_menu_new ();
+ item = g_menu_item_new_section (NULL, G_MENU_MODEL (priv->source_menu));
+ g_menu_item_set_attribute (item, "action-namespace", "s", "source");
+ g_menu_append_item (priv->menu, item);
+ g_object_unref (item);
+
priv->static_shortcuts = g_simple_action_group_new ();
+ priv->source_actions = g_simple_action_group_new ();
priv->muxer = g_action_muxer_new ();
g_action_muxer_insert (priv->muxer, NULL, G_ACTION_GROUP (priv->static_shortcuts));
+ g_action_muxer_insert (priv->muxer, "source", G_ACTION_GROUP (priv->source_actions));
priv->draws_attention = FALSE;
@@ -249,32 +250,30 @@ app_section_dispose (GObject *object)
AppSection * self = APP_SECTION(object);
AppSectionPrivate * priv = self->priv;
+ if (priv->app_proxy_cancellable) {
+ g_cancellable_cancel (priv->app_proxy_cancellable);
+ g_clear_object (&priv->app_proxy_cancellable);
+ }
+
if (priv->desktop_file_monitor) {
g_signal_handlers_disconnect_by_func (priv->desktop_file_monitor, desktop_file_changed_cb, self);
g_clear_object (&priv->desktop_file_monitor);
}
+ g_clear_object (&priv->app_proxy);
+
g_clear_object (&priv->menu);
+ g_clear_object (&priv->source_menu);
g_clear_object (&priv->static_shortcuts);
+ g_clear_object (&priv->source_actions);
if (priv->name_watch_id) {
g_bus_unwatch_name (priv->name_watch_id);
priv->name_watch_id = 0;
}
- if (priv->source_actions) {
- g_action_muxer_remove (priv->muxer, "source");
- g_object_disconnect (priv->source_actions,
- "any_signal::action-added", action_added, self,
- "any_signal::action-state-changed", action_state_changed, self,
- "any_signal::action-removed", action_removed, self,
- NULL);
- g_clear_object (&priv->source_actions);
- }
-
g_clear_object (&priv->muxer);
- g_clear_object (&priv->source_menu);
g_clear_object (&priv->ids);
g_clear_object (&priv->appinfo);
@@ -314,14 +313,12 @@ nick_activate_cb (GSimpleAction *action,
g_return_if_fail(priv->ids != NULL);
- GAppLaunchContext *context = get_launch_context (g_variant_get_uint32 (param));
-
- if (!indicator_desktop_shortcuts_nick_exec_with_context(priv->ids, nick, context)) {
+ if (!indicator_desktop_shortcuts_nick_exec_with_context(priv->ids, nick, NULL)) {
g_warning("Unable to execute nick '%s' for desktop file '%s'",
nick, g_desktop_app_info_get_filename (priv->appinfo));
}
- g_object_unref (context);
+ return;
}
static void
@@ -414,7 +411,9 @@ app_section_update_menu (AppSection *self)
item = g_menu_item_new (g_app_info_get_name (G_APP_INFO (priv->appinfo)), "launch");
g_menu_item_set_attribute (item, "x-canonical-type", "s", "ImAppMenuItem");
iconstr = g_icon_to_string (g_app_info_get_icon (G_APP_INFO (priv->appinfo)));
- g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr);
+ if (iconstr != NULL) {
+ g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr);
+ }
g_free (iconstr);
g_menu_append_item (priv->menu, item);
@@ -445,6 +444,11 @@ app_section_update_menu (AppSection *self)
g_free(name);
}
+ item = g_menu_item_new_section (NULL, G_MENU_MODEL (priv->source_menu));
+ g_menu_item_set_attribute (item, "action-namespace", "s", "source");
+ g_menu_append_item (priv->menu, item);
+ g_object_unref (item);
+
keyfile = g_file_new_for_path (g_desktop_app_info_get_filename (priv->appinfo));
g_file_load_contents_async (keyfile, NULL, keyfile_loaded, self);
@@ -583,49 +587,242 @@ app_section_get_draws_attention (AppSection *self)
void
app_section_clear_draws_attention (AppSection *self)
{
- AppSectionPrivate * priv = self->priv;
- gchar **action_names;
+ self->priv->draws_attention = FALSE;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]);
+}
+
+static void
+application_vanished (GDBusConnection *bus,
+ const gchar *name,
+ gpointer user_data)
+{
+ AppSection *self = user_data;
+
+ app_section_unset_object_path (self);
+}
+
+static void
+update_draws_attention (AppSection *self)
+{
+ AppSectionPrivate *priv = self->priv;
+ gchar **actions;
gchar **it;
+ gboolean draws_attention = FALSE;
+
+ actions = g_action_group_list_actions (G_ACTION_GROUP (priv->source_actions));
+
+ for (it = actions; *it; it++) {
+ GVariant *state;
+
+ state = g_action_group_get_action_state (G_ACTION_GROUP (priv->source_actions), *it);
+ if (state) {
+ gboolean b;
+ g_variant_get (state, "(uxsb)", NULL, NULL, NULL, &b);
+ draws_attention = b || draws_attention;
+ g_variant_unref (state);
+ }
+
+ if (draws_attention)
+ break;
+ }
+
+ if (draws_attention != priv->draws_attention) {
+ priv->draws_attention = draws_attention;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]);
+ }
+
+ g_strfreev (actions);
+}
+
+static void
+remove_source (AppSection *self,
+ const gchar *id)
+{
+ AppSectionPrivate *priv = self->priv;
+ guint n_items;
+ guint i;
+
+ n_items = g_menu_model_get_n_items (G_MENU_MODEL (priv->source_menu));
+ for (i = 0; i < n_items; i++) {
+ gchar *action;
+ gboolean found = FALSE;
+
+ if (g_menu_model_get_item_attribute (G_MENU_MODEL (priv->source_menu), i,
+ G_MENU_ATTRIBUTE_ACTION, "s", &action)) {
+ found = g_str_equal (action, id);
+ g_free (action);
+ }
+
+ if (found) {
+ g_menu_remove (priv->source_menu, i);
+ break;
+ }
+ }
+
+ g_simple_action_group_remove (priv->source_actions, id);
+ update_draws_attention (self);
+}
+
+static void
+source_action_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ AppSection *self = APP_SECTION (user_data);
+ AppSectionPrivate *priv = APP_SECTION (user_data)->priv;
+
+ g_return_if_fail (priv->app_proxy != NULL);
+
+ indicator_messages_application_call_activate_source (priv->app_proxy,
+ g_action_get_name (G_ACTION (action)),
+ priv->app_proxy_cancellable,
+ NULL, NULL);
+
+ remove_source (self, g_action_get_name (G_ACTION (action)));
+}
- if (priv->source_actions == NULL)
+static void
+sources_listed (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ AppSection *self = user_data;
+ AppSectionPrivate *priv = self->priv;
+ GVariant *sources = NULL;
+ GError *error = NULL;
+ GVariantIter iter;
+ const gchar *id;
+ const gchar *label;
+ const gchar *iconstr;
+ guint32 count;
+ gint64 time;
+ const gchar *string;
+ gboolean draws_attention;
+
+ if (!indicator_messages_application_call_list_sources_finish (INDICATOR_MESSAGES_APPLICATION (source_object),
+ &sources, result, &error))
+ {
+ g_warning ("could not fetch the list of sources: %s", error->message);
+ g_error_free (error);
return;
+ }
- action_names = g_action_group_list_actions (priv->source_actions);
+ g_menu_clear (priv->source_menu);
+ g_simple_action_group_clear (priv->source_actions);
+ priv->draws_attention = FALSE;
- for (it = action_names; *it; it++) {
+ g_variant_iter_init (&iter, sources);
+ while (g_variant_iter_next (&iter, "(&s&s&sux&sb)", &id, &label, &iconstr,
+ &count, &time, &string, &draws_attention))
+ {
GVariant *state;
+ GSimpleAction *action;
+ GMenuItem *item;
- state = g_action_group_get_action_state (priv->source_actions, *it);
- if (!state)
- continue;
+ state = g_variant_new ("(uxsb)", count, time, string, draws_attention);
+ action = g_simple_action_new_stateful (id, NULL, state);
+ g_signal_connect (action, "activate", G_CALLBACK (source_action_activated), self);
+ g_simple_action_group_insert (priv->source_actions, G_ACTION (action));
- /* clear draws-attention while preserving other state */
- if (action_draws_attention (state)) {
- guint32 count;
- gint64 time;
- const gchar *str;
- GVariant *new_state;
+ item = g_menu_item_new (label, id);
+ g_menu_item_set_attribute (item, "x-canonical-type", "s", "ImSourceMenuItem");
+ g_menu_append_item (priv->source_menu, item);
- g_variant_get (state, "(ux&sb)", &count, &time, &str, NULL);
+ priv->draws_attention = priv->draws_attention || draws_attention;
- new_state = g_variant_new ("(uxsb)", count, time, str, FALSE);
- g_action_group_change_action_state (priv->source_actions, *it, new_state);
- }
+ g_object_unref (item);
+ g_object_unref (action);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]);
+
+ g_variant_unref (sources);
+}
- g_variant_unref (state);
+static void
+source_added (IndicatorMessagesApplication *app,
+ const gchar *id,
+ const gchar *label,
+ const gchar *iconstr,
+ guint count,
+ gint64 time,
+ const gchar *string,
+ gboolean draws_attention,
+ gpointer user_data)
+{
+ AppSection *self = user_data;
+ AppSectionPrivate *priv = self->priv;
+ GVariant *state;
+ GSimpleAction *action;
+
+ /* TODO put label and icon into the action as well */
+
+ state = g_variant_new ("(uxsb)", count, time, string, draws_attention);
+ action = g_simple_action_new_stateful (id, NULL, state);
+
+ g_simple_action_group_insert (priv->source_actions, G_ACTION (action));
+
+ if (draws_attention && !priv->draws_attention) {
+ priv->draws_attention = TRUE;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]);
}
- g_strfreev (action_names);
+ g_object_unref (action);
+}
+static void
+source_changed (IndicatorMessagesApplication *app,
+ const gchar *id,
+ const gchar *label,
+ const gchar *iconstr,
+ guint count,
+ gint64 time,
+ const gchar *string,
+ gboolean draws_attention,
+ gpointer user_data)
+{
+ AppSection *self = user_data;
+ AppSectionPrivate *priv = self->priv;
+ GVariant *state;
+
+ /* TODO put label and icon into the action as well */
+
+ state = g_variant_new ("(uxsb)", count, time, string, draws_attention);
+ g_action_group_change_action_state (G_ACTION_GROUP (priv->source_actions), id, state);
+
+ update_draws_attention (self);
}
static void
-application_vanished (GDBusConnection *bus,
- const gchar *name,
- gpointer user_data)
+source_removed (IndicatorMessagesApplication *app,
+ const gchar *id,
+ gpointer user_data)
{
AppSection *self = user_data;
- app_section_unset_object_path (self);
+ remove_source (self, id);
+}
+
+static void
+app_proxy_created (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ AppSectionPrivate *priv = APP_SECTION (user_data)->priv;
+ GError *error = NULL;
+
+ priv->app_proxy = indicator_messages_application_proxy_new_finish (result, &error);
+ if (!priv->app_proxy) {
+ g_warning ("could not create application proxy: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ indicator_messages_application_call_list_sources (priv->app_proxy, priv->app_proxy_cancellable,
+ sources_listed, user_data);
+
+ g_signal_connect (priv->app_proxy, "source-added", G_CALLBACK (source_added), user_data);
+ g_signal_connect (priv->app_proxy, "source-changed", G_CALLBACK (source_changed), user_data);
+ g_signal_connect (priv->app_proxy, "source-removed", G_CALLBACK (source_removed), user_data);
}
/*
@@ -646,27 +843,20 @@ app_section_set_object_path (AppSection *self,
const gchar *object_path)
{
AppSectionPrivate *priv = self->priv;
- GMenuItem *item;
g_object_freeze_notify (G_OBJECT (self));
app_section_unset_object_path (self);
- priv->source_actions = G_ACTION_GROUP (g_dbus_action_group_get (bus, bus_name, object_path));
- g_action_muxer_insert (priv->muxer, "source", priv->source_actions);
-
- priv->draws_attention = any_action_draws_attention (priv->source_actions, NULL);
- g_object_connect (priv->source_actions,
- "signal::action-added", action_added, self,
- "signal::action-state-changed", action_state_changed, self,
- "signal::action-removed", action_removed, self,
- NULL);
+ priv->app_proxy_cancellable = g_cancellable_new ();
+ indicator_messages_application_proxy_new (bus,
+ G_DBUS_PROXY_FLAGS_NONE,
+ bus_name,
+ object_path,
+ priv->app_proxy_cancellable,
+ app_proxy_created,
+ self);
- priv->source_menu = G_MENU_MODEL (g_dbus_menu_model_get (bus, bus_name, object_path));
-
- item = g_menu_item_new_section (NULL, priv->source_menu);
- g_menu_item_set_attribute (item, "action-namespace", "s", "source");
- g_menu_append_item (priv->menu, item);
- g_object_unref (item);
+ priv->draws_attention = FALSE;
priv->name_watch_id = g_bus_watch_name_on_connection (bus, bus_name, 0,
NULL, application_vanished,
@@ -694,26 +884,19 @@ app_section_unset_object_path (AppSection *self)
{
AppSectionPrivate *priv = self->priv;
+ if (priv->app_proxy_cancellable) {
+ g_cancellable_cancel (priv->app_proxy_cancellable);
+ g_clear_object (&priv->app_proxy_cancellable);
+ }
+ g_clear_object (&priv->app_proxy);
+
if (priv->name_watch_id) {
g_bus_unwatch_name (priv->name_watch_id);
priv->name_watch_id = 0;
}
- if (priv->source_actions) {
- g_object_disconnect (priv->source_actions,
- "any_signal::action-added", action_added, self,
- "any_signal::action-state-changed", action_state_changed, self,
- "any_signal::action-removed", action_removed, self,
- NULL);
- g_clear_object (&priv->source_actions);
- }
-
- if (priv->source_menu) {
- /* the last menu item points is linked to the app's menumodel */
- gint n_items = g_menu_model_get_n_items (G_MENU_MODEL (priv->menu));
- g_menu_remove (priv->menu, n_items -1);
- g_clear_object (&priv->source_menu);
- }
+ g_simple_action_group_clear (priv->source_actions);
+ g_menu_clear (priv->source_menu);
priv->draws_attention = FALSE;
g_clear_pointer (&priv->chat_status, g_free);
@@ -727,85 +910,6 @@ app_section_unset_object_path (AppSection *self)
"launch", g_variant_new_boolean (FALSE));
}
-static gboolean
-action_draws_attention (GVariant *state)
-{
- gboolean attention;
-
- if (state && g_variant_is_of_type (state, G_VARIANT_TYPE ("(uxsb)")))
- g_variant_get_child (state, 3, "b", &attention);
- else
- attention = FALSE;
-
- return attention;
-}
-
-static gboolean
-any_action_draws_attention (GActionGroup *group,
- const gchar *ignored_action)
-{
- gchar **actions;
- gchar **it;
- gboolean attention = FALSE;
-
- actions = g_action_group_list_actions (group);
-
- for (it = actions; *it && !attention; it++) {
- GVariant *state;
-
- if (ignored_action && g_str_equal (ignored_action, *it))
- continue;
-
- state = g_action_group_get_action_state (group, *it);
- if (state) {
- attention = action_draws_attention (state);
- g_variant_unref (state);
- }
- }
-
- g_strfreev (actions);
- return attention;
-}
-
-static void
-action_added (GActionGroup *group,
- const gchar *action_name,
- gpointer user_data)
-{
- AppSection *self = user_data;
- GVariant *state;
-
- state = g_action_group_get_action_state (group, action_name);
- if (state) {
- self->priv->draws_attention |= action_draws_attention (state);
- g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]);
- g_variant_unref (state);
- }
-}
-
-static void
-action_state_changed (GActionGroup *group,
- const gchar *action_name,
- GVariant *value,
- gpointer user_data)
-{
- AppSection *self = user_data;
-
- self->priv->draws_attention = any_action_draws_attention (group, NULL);
- g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]);
-}
-
-static void
-action_removed (GActionGroup *group,
- const gchar *action_name,
- gpointer user_data)
-{
- AppSection *self = user_data;
-
- self->priv->draws_attention = any_action_draws_attention (group, action_name);
- g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]);
-}
-
gboolean
app_section_get_uses_chat_status (AppSection *self)
{
diff --git a/src/dbus-data.h b/src/dbus-data.h
index 64747a9..59bd305 100644
--- a/src/dbus-data.h
+++ b/src/dbus-data.h
@@ -1,9 +1,24 @@
+/*
+ * Copyright 2012-2013 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
#ifndef __DBUS_DATA_H__
#define __DBUS_DATA_H__ 1
#define INDICATOR_MESSAGES_DBUS_NAME "com.canonical.indicator.messages"
-#define INDICATOR_MESSAGES_DBUS_OBJECT "/com/canonical/indicator/messages/menu"
+#define INDICATOR_MESSAGES_DBUS_OBJECT "/com/canonical/indicator/messages"
#define INDICATOR_MESSAGES_DBUS_SERVICE_OBJECT "/com/canonical/indicator/messages/service"
#define INDICATOR_MESSAGES_DBUS_SERVICE_INTERFACE "com.canonical.indicator.messages.service"
diff --git a/src/gactionmuxer.c b/src/gactionmuxer.c
index 2b1d11a..0f3cda4 100644
--- a/src/gactionmuxer.c
+++ b/src/gactionmuxer.c
@@ -483,3 +483,11 @@ g_action_muxer_remove (GActionMuxer *muxer,
g_clear_object (&muxer->global_actions);
}
+GActionGroup *
+g_action_muxer_get_group (GActionMuxer *muxer,
+ const gchar *prefix)
+{
+ g_return_val_if_fail (G_IS_ACTION_MUXER (muxer), NULL);
+
+ return prefix ? g_hash_table_lookup (muxer->groups, prefix) : muxer->global_actions;
+}
diff --git a/src/gactionmuxer.h b/src/gactionmuxer.h
index 5c5e839..caf9ec7 100644
--- a/src/gactionmuxer.h
+++ b/src/gactionmuxer.h
@@ -40,5 +40,8 @@ void g_action_muxer_insert (GActionMuxer *muxer,
void g_action_muxer_remove (GActionMuxer *muxer,
const gchar *prefix);
+GActionGroup * g_action_muxer_get_group (GActionMuxer *muxer,
+ const gchar *prefix);
+
#endif
diff --git a/src/gmenuutils.c b/src/gmenuutils.c
index f63615b..cfd751e 100644
--- a/src/gmenuutils.c
+++ b/src/gmenuutils.c
@@ -79,7 +79,7 @@ g_menu_append_with_icon_name (GMenu *menu,
GMenuItem *item;
item = g_menu_item_new (label, detailed_action);
- g_menu_item_set_attribute (item, "x-canonical-icon", "s", icon_name);
+ g_menu_item_set_attribute (item, "icon", "s", icon_name);
g_menu_append_item (menu, item);
diff --git a/src/ido-detail-label.c b/src/ido-detail-label.c
deleted file mode 100644
index 8b7ef90..0000000
--- a/src/ido-detail-label.c
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
- * Copyright 2012 Canonical Ltd.
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 3, as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranties of
- * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
- * PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Lars Uebernickel <lars.uebernickel@canonical.com>
- */
-
-#include "ido-detail-label.h"
-
-#include <math.h>
-
-G_DEFINE_TYPE (IdoDetailLabel, ido_detail_label, GTK_TYPE_WIDGET)
-
-struct _IdoDetailLabelPrivate
-{
- gchar *text;
- PangoLayout *layout;
- gboolean draw_lozenge;
-};
-
-enum
-{
- PROP_0,
- PROP_TEXT,
- NUM_PROPERTIES
-};
-
-static GParamSpec *properties[NUM_PROPERTIES];
-
-static void
-ido_detail_label_get_property (GObject *object,
- guint property_id,
- GValue *value,
- GParamSpec *pspec)
-{
- IdoDetailLabel *self = IDO_DETAIL_LABEL (object);
-
- switch (property_id)
- {
- case PROP_TEXT:
- g_value_set_string (value, self->priv->text);
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- }
-}
-
-static void
-ido_detail_label_set_property (GObject *object,
- guint property_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- IdoDetailLabel *self = IDO_DETAIL_LABEL (object);
-
- switch (property_id)
- {
- case PROP_TEXT:
- ido_detail_label_set_text (self, g_value_get_string (value));
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- }
-}
-
-
-static void
-ido_detail_label_finalize (GObject *object)
-{
- IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (object)->priv;
-
- g_free (priv->text);
-
- G_OBJECT_CLASS (ido_detail_label_parent_class)->finalize (object);
-}
-
-static void
-ido_detail_label_dispose (GObject *object)
-{
- IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (object)->priv;
-
- g_clear_object (&priv->layout);
-
- G_OBJECT_CLASS (ido_detail_label_parent_class)->dispose (object);
-}
-
-static void
-ido_detail_label_ensure_layout (IdoDetailLabel *label)
-{
- IdoDetailLabelPrivate *priv = label->priv;
-
- if (priv->layout == NULL)
- {
- priv->layout = gtk_widget_create_pango_layout (GTK_WIDGET (label), priv->text);
- pango_layout_set_alignment (priv->layout, PANGO_ALIGN_CENTER);
- pango_layout_set_ellipsize (priv->layout, PANGO_ELLIPSIZE_END);
- pango_layout_set_height (priv->layout, -1);
-
- // TODO update layout on "style-updated" and "direction-changed"
- }
-}
-
-static void
-cairo_lozenge (cairo_t *cr,
- double x,
- double y,
- double w,
- double h,
- double radius)
-{
- double x1 = x + w - radius;
- double x2 = x + radius;
- double y1 = y + radius;
- double y2 = y + h - radius;
-
- cairo_move_to (cr, x + radius, y);
- cairo_arc (cr, x1, y1, radius, G_PI * 1.5, G_PI * 2);
- cairo_arc (cr, x1, y2, radius, 0, G_PI * 0.5);
- cairo_arc (cr, x2, y2, radius, G_PI * 0.5, G_PI);
- cairo_arc (cr, x2, y1, radius, G_PI, G_PI * 1.5);
-}
-
-static PangoFontMetrics *
-gtk_widget_get_font_metrics (GtkWidget *widget,
- PangoContext *context)
-{
- PangoFontDescription *font;
- PangoFontMetrics *metrics;
-
- gtk_style_context_get (gtk_widget_get_style_context (widget),
- gtk_widget_get_state_flags (widget),
- "font", &font, NULL);
-
- metrics = pango_context_get_metrics (context,
- font,
- pango_context_get_language (context));
-
- pango_font_description_free (font);
- return metrics;
-}
-
-static gint
-ido_detail_label_get_minimum_text_width (IdoDetailLabel *label)
-{
- IdoDetailLabelPrivate *priv = label->priv;
- PangoContext *context;
- PangoFontMetrics *metrics;
- gint char_width;
- gint w;
-
- context = pango_layout_get_context (priv->layout);
- metrics = gtk_widget_get_font_metrics (GTK_WIDGET (label), context);
- char_width = pango_font_metrics_get_approximate_digit_width (metrics);
-
- w = 2 * char_width / PANGO_SCALE;
- pango_font_metrics_unref (metrics);
- return w;
-}
-
-static gboolean
-ido_detail_label_draw (GtkWidget *widget,
- cairo_t *cr)
-{
- IdoDetailLabel *label = IDO_DETAIL_LABEL (widget);
- IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (widget)->priv;
- PangoRectangle extents;
- GtkAllocation allocation;
- double x, w, h, radius;
- GdkRGBA color;
-
- if (!priv->text || !*priv->text)
- return TRUE;
-
- gtk_widget_get_allocation (widget, &allocation);
-
- ido_detail_label_ensure_layout (IDO_DETAIL_LABEL (widget));
-
- pango_layout_get_extents (priv->layout, NULL, &extents);
- pango_extents_to_pixels (&extents, NULL);
-
- h = MIN (allocation.height, extents.height);
- radius = floor (h / 2.0);
- w = MAX (ido_detail_label_get_minimum_text_width (label), extents.width) + 2.0 * radius;
- x = allocation.width - w;
-
- pango_layout_set_width (priv->layout, (allocation.width - 2 * radius) * PANGO_SCALE);
- pango_layout_get_extents (priv->layout, NULL, &extents);
- pango_extents_to_pixels (&extents, NULL);
-
- gtk_style_context_get_color (gtk_widget_get_style_context (widget),
- gtk_widget_get_state_flags (widget),
- &color);
- gdk_cairo_set_source_rgba (cr, &color);
-
- cairo_set_line_width (cr, 1.0);
- cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
-
- if (priv->draw_lozenge)
- cairo_lozenge (cr, x, 0.0, w, h, radius);
-
- cairo_move_to (cr, x + radius, (allocation.height - extents.height) / 2.0);
- pango_cairo_layout_path (cr, priv->layout);
- cairo_fill (cr);
-
- return TRUE;
-}
-
-static void
-ido_detail_label_get_preferred_width (GtkWidget *widget,
- gint *minimum,
- gint *natural)
-{
- IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (widget)->priv;
- PangoRectangle extents;
- double radius;
-
- ido_detail_label_ensure_layout (IDO_DETAIL_LABEL (widget));
-
- pango_layout_get_extents (priv->layout, NULL, &extents);
- pango_extents_to_pixels (&extents, NULL);
-
- radius = floor (extents.height / 2.0);
-
- *minimum = ido_detail_label_get_minimum_text_width (IDO_DETAIL_LABEL (widget)) + 2.0 * radius;
- *natural = MAX (*minimum, extents.width + 2.0 * radius);
-}
-
-static void
-ido_detail_label_get_preferred_height (GtkWidget *widget,
- gint *minimum,
- gint *natural)
-{
- IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (widget)->priv;
- PangoContext *context;
- PangoFontMetrics *metrics;
- PangoRectangle extents;
-
- ido_detail_label_ensure_layout (IDO_DETAIL_LABEL (widget));
-
- pango_layout_get_extents (priv->layout, NULL, &extents);
- pango_extents_to_pixels (&extents, NULL);
- context = pango_layout_get_context (priv->layout);
- metrics = gtk_widget_get_font_metrics (widget, context);
-
- *minimum = *natural = (pango_font_metrics_get_ascent (metrics) +
- pango_font_metrics_get_descent (metrics)) / PANGO_SCALE;
-
- pango_font_metrics_unref (metrics);
-}
-
-static void
-ido_detail_label_class_init (IdoDetailLabelClass *klass)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
- GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
-
- object_class->get_property = ido_detail_label_get_property;
- object_class->set_property = ido_detail_label_set_property;
- object_class->finalize = ido_detail_label_finalize;
- object_class->dispose = ido_detail_label_dispose;
-
- widget_class->draw = ido_detail_label_draw;
- widget_class->get_preferred_width = ido_detail_label_get_preferred_width;
- widget_class->get_preferred_height = ido_detail_label_get_preferred_height;
-
- g_type_class_add_private (klass, sizeof (IdoDetailLabelPrivate));
-
- properties[PROP_TEXT] = g_param_spec_string ("text",
- "Text",
- "The text of the label",
- NULL,
- G_PARAM_READWRITE |
- G_PARAM_STATIC_STRINGS);
-
- g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
-}
-
-static void
-ido_detail_label_init (IdoDetailLabel *self)
-{
- self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
- IDO_TYPE_DETAIL_LABEL,
- IdoDetailLabelPrivate);
-
- gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
-}
-
-GtkWidget *
-ido_detail_label_new (const gchar *label)
-{
- return g_object_new (IDO_TYPE_DETAIL_LABEL,
- "text", label,
- NULL);
-}
-
-const gchar *
-ido_detail_label_get_text (IdoDetailLabel *label)
-{
- g_return_val_if_fail (IDO_IS_DETAIL_LABEL (label), NULL);
- return label->priv->text;
-}
-
-/* collapse_whitespace:
- * @str: the source string
- *
- * Collapses all occurences of consecutive whitespace charactes in @str
- * into a single space.
- *
- * Returns: (transfer full): a newly-allocated string
- */
-static gchar *
-collapse_whitespace (const gchar *str)
-{
- GString *result;
- gboolean in_space = FALSE;
-
- if (str == NULL)
- return NULL;
-
- result = g_string_new ("");
-
- while (*str)
- {
- gunichar c = g_utf8_get_char_validated (str, -1);
-
- if (c < 0)
- break;
-
- if (!g_unichar_isspace (c))
- {
- g_string_append_unichar (result, c);
- in_space = FALSE;
- }
- else if (!in_space)
- {
- g_string_append_c (result, ' ');
- in_space = TRUE;
- }
-
- str = g_utf8_next_char (str);
- }
-
- return g_string_free (result, FALSE);
-}
-
-static void
-ido_detail_label_set_text_impl (IdoDetailLabel *label,
- const gchar *text,
- gboolean draw_lozenge)
-{
- IdoDetailLabelPrivate * priv = label->priv;
-
- g_clear_object (&priv->layout);
- g_free (priv->text);
-
- priv->text = g_strdup (text);
- priv->draw_lozenge = draw_lozenge;
-
- g_object_notify_by_pspec (G_OBJECT (label), properties[PROP_TEXT]);
- gtk_widget_queue_resize (GTK_WIDGET (label));
-}
-
-void
-ido_detail_label_set_text (IdoDetailLabel *label,
- const gchar *text)
-{
- gchar *str;
-
- g_return_if_fail (IDO_IS_DETAIL_LABEL (label));
-
- str = collapse_whitespace (text);
- ido_detail_label_set_text_impl (label, str, FALSE);
- g_free (str);
-}
-
-void
-ido_detail_label_set_count (IdoDetailLabel *label,
- gint count)
-{
- gchar *text;
-
- g_return_if_fail (IDO_IS_DETAIL_LABEL (label));
-
- text = g_strdup_printf ("%d", count);
- ido_detail_label_set_text_impl (label, text, TRUE);
- g_free (text);
-}
diff --git a/src/ido-detail-label.h b/src/ido-detail-label.h
deleted file mode 100644
index 1995fee..0000000
--- a/src/ido-detail-label.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2012 Canonical Ltd.
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 3, as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranties of
- * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
- * PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Lars Uebernickel <lars.uebernickel@canonical.com>
- */
-
-#ifndef __IDO_DETAIL_LABEL_H__
-#define __IDO_DETAIL_LABEL_H__
-
-#include <gtk/gtk.h>
-
-#define IDO_TYPE_DETAIL_LABEL (ido_detail_label_get_type())
-#define IDO_DETAIL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_TYPE_DETAIL_LABEL, IdoDetailLabel))
-#define IDO_DETAIL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IDO_TYPE_DETAIL_LABEL, IdoDetailLabelClass))
-#define IDO_IS_DETAIL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_TYPE_DETAIL_LABEL))
-#define IDO_IS_DETAIL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IDO_TYPE_DETAIL_LABEL))
-#define IDO_DETAIL_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IDO_TYPE_DETAIL_LABEL, IdoDetailLabelClass))
-
-typedef struct _IdoDetailLabel IdoDetailLabel;
-typedef struct _IdoDetailLabelClass IdoDetailLabelClass;
-typedef struct _IdoDetailLabelPrivate IdoDetailLabelPrivate;
-
-struct _IdoDetailLabel
-{
- GtkWidget parent;
- IdoDetailLabelPrivate *priv;
-};
-
-struct _IdoDetailLabelClass
-{
- GtkWidgetClass parent_class;
-};
-
-GType ido_detail_label_get_type (void) G_GNUC_CONST;
-
-GtkWidget * ido_detail_label_new (const gchar *str);
-
-const gchar * ido_detail_label_get_text (IdoDetailLabel *label);
-
-void ido_detail_label_set_text (IdoDetailLabel *label,
- const gchar *text);
-
-void ido_detail_label_set_count (IdoDetailLabel *label,
- gint count);
-
-#endif
diff --git a/src/ido-menu-item.c b/src/ido-menu-item.c
deleted file mode 100644
index f702828..0000000
--- a/src/ido-menu-item.c
+++ /dev/null
@@ -1,439 +0,0 @@
-/*
- * Copyright 2012 Canonical Ltd.
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 3, as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranties of
- * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
- * PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Lars Uebernickel <lars.uebernickel@canonical.com>
- */
-
-#include "ido-menu-item.h"
-
-struct _IdoMenuItemPrivate
-{
- GActionGroup *action_group;
- gchar *action;
- GVariant *target;
-
- GtkWidget *icon;
- GtkWidget *label;
-
- gboolean has_indicator;
- gboolean in_set_active;
-};
-
-enum
-{
- PROP_0,
- PROP_MENU_ITEM,
- PROP_ACTION_GROUP,
- NUM_PROPERTIES
-};
-
-static GParamSpec *properties[NUM_PROPERTIES];
-
-G_DEFINE_TYPE (IdoMenuItem, ido_menu_item, GTK_TYPE_CHECK_MENU_ITEM);
-
-static void
-ido_menu_item_constructed (GObject *object)
-{
- IdoMenuItemPrivate *priv = IDO_MENU_ITEM (object)->priv;
- GtkWidget *grid;
-
- priv->icon = g_object_ref (gtk_image_new ());
- gtk_widget_set_margin_right (priv->icon, 6);
-
- priv->label = g_object_ref (gtk_label_new (""));
-
- grid = gtk_grid_new ();
- gtk_grid_attach (GTK_GRID (grid), priv->icon, 0, 0, 1, 1);
- gtk_grid_attach (GTK_GRID (grid), priv->label, 1, 0, 1, 1);
-
- gtk_container_add (GTK_CONTAINER (object), grid);
- gtk_widget_show_all (grid);
-
- G_OBJECT_CLASS (ido_menu_item_parent_class)->constructed (object);
-}
-
-static void
-ido_menu_item_set_active (IdoMenuItem *self,
- gboolean active)
-{
- /* HACK gtk_check_menu_item_set_active calls gtk_menu_item_activate.
- * Make sure our activate handler doesn't toggle the action as a
- * result of calling this function. */
-
- self->priv->in_set_active = TRUE;
- gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (self), active);
- self->priv->in_set_active = FALSE;
-}
-
-static void
-ido_menu_item_set_has_indicator (IdoMenuItem *self,
- gboolean has_indicator)
-{
- if (has_indicator == self->priv->has_indicator)
- return;
-
- self->priv->has_indicator = has_indicator;
-
- gtk_widget_queue_resize (GTK_WIDGET (self));
-}
-
-static void
-ido_menu_item_set_state (IdoMenuItem *self,
- GVariant *state)
-{
- IdoMenuItemPrivate *priv = self->priv;
-
- if (priv->target)
- {
- ido_menu_item_set_has_indicator (self, TRUE);
- ido_menu_item_set_active (self, FALSE);
- gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (self), TRUE);
- gtk_check_menu_item_set_inconsistent (GTK_CHECK_MENU_ITEM (self), FALSE);
-
- if (g_variant_is_of_type (state, G_VARIANT_TYPE_STRING))
- {
- ido_menu_item_set_active (self, g_variant_equal (priv->target, state));
- }
- else if (g_variant_is_of_type (state, G_VARIANT_TYPE ("as")) &&
- g_variant_is_of_type (priv->target, G_VARIANT_TYPE_STRING))
- {
- const gchar *target_str;
- const gchar **state_strs;
- const gchar **it;
-
- target_str = g_variant_get_string (priv->target, NULL);
- state_strs = g_variant_get_strv (state, NULL);
-
- it = state_strs;
- while (*it != NULL && !g_str_equal (*it, target_str))
- it++;
-
- if (*it != NULL)
- {
- ido_menu_item_set_active (self, TRUE);
- gtk_check_menu_item_set_inconsistent (GTK_CHECK_MENU_ITEM (self),
- g_strv_length ((gchar **)state_strs) > 1);
- }
-
- g_free (state_strs);
- }
- }
- else if (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
- {
- ido_menu_item_set_has_indicator (self, TRUE);
- gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (self), FALSE);
- ido_menu_item_set_active (self, g_variant_get_boolean (state));
- }
- else
- {
- ido_menu_item_set_has_indicator (self, FALSE);
- }
-}
-
-static void
-ido_menu_item_set_action_name (IdoMenuItem *self,
- const gchar *action_name)
-{
- IdoMenuItemPrivate *priv = self->priv;
- gboolean enabled = FALSE;
- GVariant *state;
- const GVariantType *param_type;
-
- if (priv->action != NULL)
- g_free (priv->action);
-
- priv->action = g_strdup (action_name);
-
- if (priv->action_group != NULL && priv->action != NULL &&
- g_action_group_query_action (priv->action_group, priv->action,
- &enabled, &param_type, NULL, NULL, &state))
- {
- gtk_widget_set_sensitive (GTK_WIDGET (self), enabled);
-
- if (state)
- {
- ido_menu_item_set_state (self, state);
- g_variant_unref (state);
- }
- }
- else
- {
- ido_menu_item_set_active (self, FALSE);
- gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
- ido_menu_item_set_has_indicator (self, FALSE);
- }
-}
-
-static void
-ido_menu_item_action_added (GActionGroup *action_group,
- gchar *action_name,
- gpointer user_data)
-{
- IdoMenuItem *self = user_data;
-
- if (g_strcmp0 (self->priv->action, action_name) == 0)
- ido_menu_item_set_action_name (self, action_name);
-}
-
-static void
-ido_menu_item_action_removed (GActionGroup *action_group,
- gchar *action_name,
- gpointer user_data)
-{
- IdoMenuItem *self = user_data;
-
- if (g_strcmp0 (self->priv->action, action_name) == 0)
- {
- gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
- }
-}
-
-static void
-ido_menu_item_action_enabled_changed (GActionGroup *action_group,
- gchar *action_name,
- gboolean enabled,
- gpointer user_data)
-{
- IdoMenuItem *self = user_data;
-
- if (g_strcmp0 (self->priv->action, action_name) == 0)
- gtk_widget_set_sensitive (GTK_WIDGET (self), enabled);
-}
-
-static void
-ido_menu_item_action_state_changed (GActionGroup *action_group,
- gchar *action_name,
- GVariant *value,
- gpointer user_data)
-{
- IdoMenuItem *self = user_data;
-
- if (g_strcmp0 (self->priv->action, action_name) == 0)
- ido_menu_item_set_state (self, value);
-}
-
-static void
-ido_menu_set_property (GObject *object,
- guint property_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- IdoMenuItem *self = IDO_MENU_ITEM (object);
-
- switch (property_id)
- {
- case PROP_MENU_ITEM:
- ido_menu_item_set_menu_item (self, G_MENU_ITEM (g_value_get_object (value)));
- break;
-
- case PROP_ACTION_GROUP:
- ido_menu_item_set_action_group (self, G_ACTION_GROUP (g_value_get_object (value)));
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- }
-}
-
-static void
-ido_menu_item_dispose (GObject *object)
-{
- IdoMenuItem *self = IDO_MENU_ITEM (object);
-
- if (self->priv->action_group)
- ido_menu_item_set_action_group (self, NULL);
-
- g_clear_object (&self->priv->icon);
- g_clear_object (&self->priv->label);
-
- if (self->priv->target)
- {
- g_variant_unref (self->priv->target);
- self->priv->target = NULL;
- }
-
- G_OBJECT_CLASS (ido_menu_item_parent_class)->dispose (object);
-}
-
-static void
-ido_menu_item_finalize (GObject *object)
-{
- IdoMenuItemPrivate *priv = IDO_MENU_ITEM (object)->priv;
-
- g_free (priv->action);
-
- G_OBJECT_CLASS (ido_menu_item_parent_class)->finalize (object);
-}
-
-static void
-ido_menu_item_activate (GtkMenuItem *item)
-{
- IdoMenuItemPrivate *priv = IDO_MENU_ITEM (item)->priv;
- GVariant *parameter;
-
- /* see ido_menu_item_set_active */
- if (!priv->in_set_active && priv->action && priv->action_group)
- {
- guint32 event_time = gtk_get_current_event_time ();
-
- if (priv->target)
- {
- parameter = priv->target;
- }
- else
- {
- parameter = g_variant_new_uint32 (event_time);
- }
-
- g_action_group_activate_action (priv->action_group, priv->action, parameter);
- }
-
- if (priv->in_set_active)
- GTK_MENU_ITEM_CLASS (ido_menu_item_parent_class)->activate (item);
-}
-
-static void
-ido_menu_item_draw_indicator (GtkCheckMenuItem *item,
- cairo_t *cr)
-{
- IdoMenuItem *self = IDO_MENU_ITEM (item);
-
- if (self->priv->has_indicator)
- GTK_CHECK_MENU_ITEM_CLASS (ido_menu_item_parent_class)
- ->draw_indicator (item, cr);
-}
-
-static void
-ido_menu_item_class_init (IdoMenuItemClass *klass)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
- GtkMenuItemClass *menu_item_class = GTK_MENU_ITEM_CLASS (klass);
- GtkCheckMenuItemClass *check_class = GTK_CHECK_MENU_ITEM_CLASS (klass);
-
- g_type_class_add_private (klass, sizeof (IdoMenuItemPrivate));
-
- object_class->constructed = ido_menu_item_constructed;
- object_class->set_property = ido_menu_set_property;
- object_class->dispose = ido_menu_item_dispose;
- object_class->finalize = ido_menu_item_finalize;
-
- menu_item_class->activate = ido_menu_item_activate;
-
- check_class->draw_indicator = ido_menu_item_draw_indicator;
-
- properties[PROP_MENU_ITEM] = g_param_spec_object ("menu-item",
- "Menu item",
- "The model GMenuItem for this menu item",
- G_TYPE_MENU_ITEM,
- G_PARAM_WRITABLE |
- G_PARAM_STATIC_STRINGS);
-
- properties[PROP_ACTION_GROUP] = g_param_spec_object ("action-group",
- "Action group",
- "The action group associated with this menu item",
- G_TYPE_ACTION_GROUP,
- G_PARAM_WRITABLE |
- G_PARAM_STATIC_STRINGS);
-
- g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
-}
-
-static void
-ido_menu_item_init (IdoMenuItem *self)
-{
- self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
- IDO_TYPE_MENU_ITEM,
- IdoMenuItemPrivate);
-}
-
-void
-ido_menu_item_set_menu_item (IdoMenuItem *self,
- GMenuItem *menuitem)
-{
- gchar *iconstr = NULL;
- GIcon *icon = NULL;
- gchar *label = NULL;
- gchar *action = NULL;
-
- if (g_menu_item_get_attribute (menuitem, "x-canonical-icon", "s", &iconstr))
- {
- GError *error;
-
- /* only indent the label if icon is set to "" */
- if (iconstr[0] == '\0')
- {
- gint width;
-
- gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, NULL);
- gtk_widget_set_size_request (self->priv->icon, width, -1);
- }
- else
- {
- icon = g_icon_new_for_string (iconstr, &error);
- if (icon == NULL)
- {
- g_warning ("unable to set icon: %s", error->message);
- g_error_free (error);
- }
- }
- g_free (iconstr);
- }
- gtk_image_set_from_gicon (GTK_IMAGE (self->priv->icon), icon, GTK_ICON_SIZE_MENU);
-
- g_menu_item_get_attribute (menuitem, "label", "s", &label);
- gtk_label_set_label (GTK_LABEL (self->priv->label), label ? label : "");
-
- self->priv->target = g_menu_item_get_attribute_value (menuitem, "target", NULL);
-
- g_menu_item_get_attribute (menuitem, "action", "s", &action);
- ido_menu_item_set_action_name (self, action);
-
- if (icon)
- g_object_unref (icon);
- g_free (label);
- g_free (action);
-}
-
-void
-ido_menu_item_set_action_group (IdoMenuItem *self,
- GActionGroup *action_group)
-{
- IdoMenuItemPrivate *priv = self->priv;
-
- if (priv->action_group != NULL)
- {
- g_signal_handlers_disconnect_by_func (priv->action_group, ido_menu_item_action_added, self);
- g_signal_handlers_disconnect_by_func (priv->action_group, ido_menu_item_action_removed, self);
- g_signal_handlers_disconnect_by_func (priv->action_group, ido_menu_item_action_enabled_changed, self);
- g_signal_handlers_disconnect_by_func (priv->action_group, ido_menu_item_action_state_changed, self);
-
- g_clear_object (&priv->action_group);
- }
-
- if (action_group != NULL)
- {
- priv->action_group = g_object_ref (action_group);
-
- g_signal_connect (priv->action_group, "action-added",
- G_CALLBACK (ido_menu_item_action_added), self);
- g_signal_connect (priv->action_group, "action-removed",
- G_CALLBACK (ido_menu_item_action_removed), self);
- g_signal_connect (priv->action_group, "action-enabled-changed",
- G_CALLBACK (ido_menu_item_action_enabled_changed), self);
- g_signal_connect (priv->action_group, "action-state-changed",
- G_CALLBACK (ido_menu_item_action_state_changed), self);
- }
-}
diff --git a/src/ido-menu-item.h b/src/ido-menu-item.h
deleted file mode 100644
index 0521928..0000000
--- a/src/ido-menu-item.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2012 Canonical Ltd.
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 3, as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranties of
- * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
- * PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Lars Uebernickel <lars.uebernickel@canonical.com>
- */
-
-#ifndef __IDO_MENU_ITEM_H__
-#define __IDO_MENU_ITEM_H__
-
-#include <gtk/gtk.h>
-
-#define IDO_TYPE_MENU_ITEM (ido_menu_item_get_type ())
-#define IDO_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_TYPE_MENU_ITEM, IdoMenuItem))
-#define IDO_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IDO_TYPE_MENU_ITEM, IdoMenuItemClass))
-#define IS_IDO_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_TYPE_MENU_ITEM))
-#define IS_IDO_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IDO_TYPE_MENU_ITEM))
-#define IDO_MENU_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IDO_TYPE_MENU_ITEM, IdoMenuItemClass))
-
-typedef struct _IdoMenuItem IdoMenuItem;
-typedef struct _IdoMenuItemClass IdoMenuItemClass;
-typedef struct _IdoMenuItemPrivate IdoMenuItemPrivate;
-
-struct _IdoMenuItemClass
-{
- GtkCheckMenuItemClass parent_class;
-};
-
-struct _IdoMenuItem
-{
- GtkCheckMenuItem parent;
- IdoMenuItemPrivate *priv;
-};
-
-GType ido_menu_item_get_type (void);
-
-void ido_menu_item_set_menu_item (IdoMenuItem *item,
- GMenuItem *menuitem);
-void ido_menu_item_set_action_group (IdoMenuItem *self,
- GActionGroup *action_group);
-
-#endif
diff --git a/src/im-app-menu-item.c b/src/im-app-menu-item.c
deleted file mode 100644
index 03b11c2..0000000
--- a/src/im-app-menu-item.c
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * Copyright 2012 Canonical Ltd.
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 3, as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranties of
- * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
- * PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Lars Uebernickel <lars.uebernickel@canonical.com>
- */
-
-#include "im-app-menu-item.h"
-
-struct _ImAppMenuItemPrivate
-{
- GActionGroup *action_group;
- gchar *action;
- gboolean is_running;
-
- GtkWidget *icon;
- GtkWidget *label;
-};
-
-enum
-{
- PROP_0,
- PROP_MENU_ITEM,
- PROP_ACTION_GROUP,
- NUM_PROPERTIES
-};
-
-static GParamSpec *properties[NUM_PROPERTIES];
-
-G_DEFINE_TYPE (ImAppMenuItem, im_app_menu_item, GTK_TYPE_MENU_ITEM);
-
-static void
-im_app_menu_item_constructed (GObject *object)
-{
- ImAppMenuItemPrivate *priv = IM_APP_MENU_ITEM (object)->priv;
- GtkWidget *grid;
-
- priv->icon = g_object_ref (gtk_image_new ());
- gtk_widget_set_margin_right (priv->icon, 6);
-
- priv->label = g_object_ref (gtk_label_new (""));
-
- grid = gtk_grid_new ();
- gtk_grid_attach (GTK_GRID (grid), priv->icon, 0, 0, 1, 1);
- gtk_grid_attach (GTK_GRID (grid), priv->label, 1, 0, 1, 1);
-
- gtk_container_add (GTK_CONTAINER (object), grid);
- gtk_widget_show_all (grid);
-
- G_OBJECT_CLASS (im_app_menu_item_parent_class)->constructed (object);
-}
-
-static void
-im_app_menu_item_set_action_name (ImAppMenuItem *self,
- const gchar *action_name)
-{
- ImAppMenuItemPrivate *priv = self->priv;
- gboolean enabled = FALSE;
- GVariant *state;
-
- if (priv->action != NULL)
- g_free (priv->action);
-
- priv->action = g_strdup (action_name);
-
- priv->is_running = FALSE;
-
- if (priv->action_group != NULL && priv->action != NULL &&
- g_action_group_query_action (priv->action_group, priv->action,
- &enabled, NULL, NULL, NULL, &state))
- {
- if (state && g_variant_is_of_type (state, G_VARIANT_TYPE ("b")))
- priv->is_running = g_variant_get_boolean (state);
- else
- enabled = FALSE;
-
- if (state)
- g_variant_unref (state);
- }
-
- gtk_widget_set_sensitive (GTK_WIDGET (self), enabled);
- gtk_widget_queue_draw (GTK_WIDGET (self));
-}
-
-static void
-im_app_menu_item_action_added (GActionGroup *action_group,
- gchar *action_name,
- gpointer user_data)
-{
- ImAppMenuItem *self = user_data;
-
- if (g_strcmp0 (self->priv->action, action_name) == 0)
- im_app_menu_item_set_action_name (self, action_name);
-}
-
-static void
-im_app_menu_item_action_removed (GActionGroup *action_group,
- gchar *action_name,
- gpointer user_data)
-{
- ImAppMenuItem *self = user_data;
-
- if (g_strcmp0 (self->priv->action, action_name) == 0)
- {
- gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
- self->priv->is_running = FALSE;
- gtk_widget_queue_draw (GTK_WIDGET (self));
- }
-}
-
-static void
-im_app_menu_item_action_enabled_changed (GActionGroup *action_group,
- gchar *action_name,
- gboolean enabled,
- gpointer user_data)
-{
- ImAppMenuItem *self = user_data;
-
- if (g_strcmp0 (self->priv->action, action_name) == 0)
- gtk_widget_set_sensitive (GTK_WIDGET (self), enabled);
-}
-
-static void
-im_app_menu_item_action_state_changed (GActionGroup *action_group,
- gchar *action_name,
- GVariant *value,
- gpointer user_data)
-{
- ImAppMenuItem *self = user_data;
-
- if (g_strcmp0 (self->priv->action, action_name) == 0)
- {
- g_return_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE ("b")));
-
- self->priv->is_running = g_variant_get_boolean (value);
- gtk_widget_queue_draw (GTK_WIDGET (self));
- }
-}
-
-static void
-im_app_menu_set_property (GObject *object,
- guint property_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- ImAppMenuItem *self = IM_APP_MENU_ITEM (object);
-
- switch (property_id)
- {
- case PROP_MENU_ITEM:
- im_app_menu_item_set_menu_item (self, G_MENU_ITEM (g_value_get_object (value)));
- break;
-
- case PROP_ACTION_GROUP:
- im_app_menu_item_set_action_group (self, G_ACTION_GROUP (g_value_get_object (value)));
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- }
-}
-
-static void
-im_app_menu_item_dispose (GObject *object)
-{
- ImAppMenuItem *self = IM_APP_MENU_ITEM (object);
-
- if (self->priv->action_group)
- im_app_menu_item_set_action_group (self, NULL);
-
- g_clear_object (&self->priv->icon);
- g_clear_object (&self->priv->label);
-
- G_OBJECT_CLASS (im_app_menu_item_parent_class)->dispose (object);
-}
-
-static void
-im_app_menu_item_finalize (GObject *object)
-{
- ImAppMenuItemPrivate *priv = IM_APP_MENU_ITEM (object)->priv;
-
- g_free (priv->action);
-
- G_OBJECT_CLASS (im_app_menu_item_parent_class)->finalize (object);
-}
-
-static gboolean
-im_app_menu_item_draw (GtkWidget *widget,
- cairo_t *cr)
-{
- ImAppMenuItemPrivate *priv = IM_APP_MENU_ITEM (widget)->priv;
-
- GTK_WIDGET_CLASS (im_app_menu_item_parent_class)->draw (widget, cr);
-
- if (priv->is_running)
- {
- const int arrow_width = 5;
- const double half_arrow_height = 4.5;
- GtkAllocation alloc;
- GdkRGBA color;
- double center;
-
- gtk_widget_get_allocation (widget, &alloc);
-
- gtk_style_context_get_color (gtk_widget_get_style_context (widget),
- gtk_widget_get_state_flags (widget),
- &color);
- gdk_cairo_set_source_rgba (cr, &color);
-
- center = alloc.height / 2 + 0.5;
-
- cairo_move_to (cr, 0, center - half_arrow_height);
- cairo_line_to (cr, 0, center + half_arrow_height);
- cairo_line_to (cr, arrow_width, center);
- cairo_close_path (cr);
-
- cairo_fill (cr);
- }
-
- return FALSE;
-}
-
-static void
-im_app_menu_item_activate (GtkMenuItem *item)
-{
- ImAppMenuItemPrivate *priv = IM_APP_MENU_ITEM (item)->priv;
-
- if (priv->action && priv->action_group)
- {
- guint32 event_time = gtk_get_current_event_time ();
- g_action_group_activate_action (priv->action_group, priv->action, g_variant_new_uint32 (event_time));
- }
-}
-
-static void
-im_app_menu_item_class_init (ImAppMenuItemClass *klass)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
- GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
- GtkMenuItemClass *menu_item_class = GTK_MENU_ITEM_CLASS (klass);
-
- g_type_class_add_private (klass, sizeof (ImAppMenuItemPrivate));
-
- object_class->constructed = im_app_menu_item_constructed;
- object_class->set_property = im_app_menu_set_property;
- object_class->dispose = im_app_menu_item_dispose;
- object_class->finalize = im_app_menu_item_finalize;
-
- widget_class->draw = im_app_menu_item_draw;
-
- menu_item_class->activate = im_app_menu_item_activate;
-
- properties[PROP_MENU_ITEM] = g_param_spec_object ("menu-item",
- "Menu item",
- "The model GMenuItem for this menu item",
- G_TYPE_MENU_ITEM,
- G_PARAM_WRITABLE |
- G_PARAM_STATIC_STRINGS);
-
- properties[PROP_ACTION_GROUP] = g_param_spec_object ("action-group",
- "Action group",
- "The action group associated with this menu item",
- G_TYPE_ACTION_GROUP,
- G_PARAM_WRITABLE |
- G_PARAM_STATIC_STRINGS);
-
- g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
-}
-
-static void
-im_app_menu_item_init (ImAppMenuItem *self)
-{
- self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
- IM_TYPE_APP_MENU_ITEM,
- ImAppMenuItemPrivate);
-}
-
-void
-im_app_menu_item_set_menu_item (ImAppMenuItem *self,
- GMenuItem *menuitem)
-{
- gchar *iconstr = NULL;
- GIcon *icon = NULL;
- gchar *label;
- gchar *action = NULL;
-
- if (g_menu_item_get_attribute (menuitem, "x-canonical-icon", "s", &iconstr))
- {
- GError *error;
-
- icon = g_icon_new_for_string (iconstr, &error);
- if (icon == NULL)
- {
- g_warning ("unable to set icon: %s", error->message);
- g_error_free (error);
- }
- g_free (iconstr);
- }
- gtk_image_set_from_gicon (GTK_IMAGE (self->priv->icon), icon, GTK_ICON_SIZE_MENU);
-
- g_menu_item_get_attribute (menuitem, "label", "s", &label);
- gtk_label_set_label (GTK_LABEL (self->priv->label), label ? label : "");
-
- g_menu_item_get_attribute (menuitem, "action", "s", &action);
- im_app_menu_item_set_action_name (self, action);
-
- if (icon)
- g_object_unref (icon);
- g_free (label);
- g_free (action);
-}
-
-void
-im_app_menu_item_set_action_group (ImAppMenuItem *self,
- GActionGroup *action_group)
-{
- ImAppMenuItemPrivate *priv = self->priv;
-
- if (priv->action_group != NULL)
- {
- g_signal_handlers_disconnect_by_func (priv->action_group, im_app_menu_item_action_added, self);
- g_signal_handlers_disconnect_by_func (priv->action_group, im_app_menu_item_action_removed, self);
- g_signal_handlers_disconnect_by_func (priv->action_group, im_app_menu_item_action_enabled_changed, self);
- g_signal_handlers_disconnect_by_func (priv->action_group, im_app_menu_item_action_state_changed, self);
-
- g_clear_object (&priv->action_group);
- }
-
- if (action_group != NULL)
- {
- priv->action_group = g_object_ref (action_group);
-
- g_signal_connect (priv->action_group, "action-added",
- G_CALLBACK (im_app_menu_item_action_added), self);
- g_signal_connect (priv->action_group, "action-removed",
- G_CALLBACK (im_app_menu_item_action_removed), self);
- g_signal_connect (priv->action_group, "action-enabled-changed",
- G_CALLBACK (im_app_menu_item_action_enabled_changed), self);
- g_signal_connect (priv->action_group, "action-state-changed",
- G_CALLBACK (im_app_menu_item_action_state_changed), self);
- }
-}
diff --git a/src/im-app-menu-item.h b/src/im-app-menu-item.h
deleted file mode 100644
index 519de8d..0000000
--- a/src/im-app-menu-item.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2012 Canonical Ltd.
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 3, as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranties of
- * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
- * PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Lars Uebernickel <lars.uebernickel@canonical.com>
- */
-
-#ifndef __IM_APP_MENU_ITEM_H__
-#define __IM_APP_MENU_ITEM_H__
-
-#include <gtk/gtk.h>
-
-#define IM_TYPE_APP_MENU_ITEM (im_app_menu_item_get_type ())
-#define IM_APP_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_APP_MENU_ITEM, ImAppMenuItem))
-#define IM_APP_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_APP_MENU_ITEM, ImAppMenuItemClass))
-#define IS_IM_APP_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_APP_MENU_ITEM))
-#define IS_IM_APP_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_APP_MENU_ITEM))
-#define IM_APP_MENU_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_APP_MENU_ITEM, ImAppMenuItemClass))
-
-typedef struct _ImAppMenuItem ImAppMenuItem;
-typedef struct _ImAppMenuItemClass ImAppMenuItemClass;
-typedef struct _ImAppMenuItemPrivate ImAppMenuItemPrivate;
-
-struct _ImAppMenuItemClass
-{
- GtkMenuItemClass parent_class;
-};
-
-struct _ImAppMenuItem
-{
- GtkMenuItem parent;
- ImAppMenuItemPrivate *priv;
-};
-
-GType im_app_menu_item_get_type (void);
-
-void im_app_menu_item_set_menu_item (ImAppMenuItem *item,
- GMenuItem *menuitem);
-void im_app_menu_item_set_action_group (ImAppMenuItem *self,
- GActionGroup *action_group);
-
-#endif
diff --git a/src/im-application-list.c b/src/im-application-list.c
new file mode 100644
index 0000000..bba7972
--- /dev/null
+++ b/src/im-application-list.c
@@ -0,0 +1,1139 @@
+/*
+ * Copyright 2012 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#include "im-application-list.h"
+
+#include "indicator-messages-application.h"
+#include "gactionmuxer.h"
+
+#include <gio/gdesktopappinfo.h>
+#include <string.h>
+
+typedef GObjectClass ImApplicationListClass;
+
+struct _ImApplicationList
+{
+ GObject parent;
+
+ GHashTable *applications;
+ GActionMuxer *muxer;
+
+ GSimpleActionGroup * globalactions;
+ GSimpleAction * statusaction;
+
+ GHashTable *app_status;
+};
+
+G_DEFINE_TYPE (ImApplicationList, im_application_list, G_TYPE_OBJECT);
+G_DEFINE_QUARK (draws_attention, message_action_draws_attention);
+
+enum
+{
+ SOURCE_ADDED,
+ SOURCE_CHANGED,
+ SOURCE_REMOVED,
+ MESSAGE_ADDED,
+ MESSAGE_REMOVED,
+ APP_ADDED,
+ APP_STOPPED,
+ REMOVE_ALL,
+ STATUS_SET,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+typedef struct
+{
+ ImApplicationList *list;
+ GDesktopAppInfo *info;
+ gchar *id;
+ IndicatorMessagesApplication *proxy;
+ GActionMuxer *muxer;
+ GSimpleActionGroup *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
new file mode 100644
index 0000000..f1de220
--- /dev/null
+++ b/src/im-application-list.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#ifndef __IM_APPLICATION_LIST_H__
+#define __IM_APPLICATION_LIST_H__
+
+#include <gio/gio.h>
+#include <gio/gdesktopappinfo.h>
+
+#define IM_TYPE_APPLICATION_LIST (im_application_list_get_type ())
+#define IM_APPLICATION_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_APPLICATION_LIST, ImApplicationList))
+#define IM_APPLICATION_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_APPLICATION_LIST, ImApplicationListClass))
+#define IM_IS_APPLICATION_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_APPLICATION_LIST))
+#define IM_IS_APPLICATION_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_APPLICATION_LIST))
+#define IM_APPLICATION_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_APPLICATION_LIST, ImApplicationListClass))
+
+typedef struct _ImApplicationList ImApplicationList;
+
+GType im_application_list_get_type (void);
+
+ImApplicationList * im_application_list_new (void);
+
+void im_application_list_add (ImApplicationList *list,
+ const gchar *desktop_id);
+
+void im_application_list_remove (ImApplicationList *list,
+ const gchar *id);
+
+void im_application_list_set_remote (ImApplicationList *list,
+ const gchar *id,
+ GDBusConnection *connection,
+ const gchar *unique_bus_name,
+ const gchar *object_path);
+
+GActionGroup * im_application_list_get_action_group (ImApplicationList *list);
+
+GList * im_application_list_get_applications (ImApplicationList *list);
+
+GDesktopAppInfo * im_application_list_get_application (ImApplicationList *list,
+ const gchar *id);
+
+void im_application_list_set_status (ImApplicationList *list,
+ const gchar *id,
+ const gchar *status);
+
+#endif
diff --git a/src/im-desktop-menu.c b/src/im-desktop-menu.c
new file mode 100644
index 0000000..1040d62
--- /dev/null
+++ b/src/im-desktop-menu.c
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2012 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#include "im-desktop-menu.h"
+#include <glib/gi18n.h>
+
+typedef ImMenuClass ImDesktopMenuClass;
+
+struct _ImDesktopMenu
+{
+ ImMenu parent;
+
+ GHashTable *source_sections;
+};
+
+G_DEFINE_TYPE (ImDesktopMenu, im_desktop_menu, IM_TYPE_MENU);
+
+static void
+im_desktop_menu_app_added (ImApplicationList *applist,
+ const gchar *app_id,
+ GDesktopAppInfo *app_info,
+ gpointer user_data)
+{
+ ImDesktopMenu *menu = user_data;
+ GMenu *section;
+ GMenu *app_section;
+ GMenu *source_section;
+ gchar *namespace;
+
+ app_section = g_menu_new ();
+
+ /* application launcher */
+ {
+ GMenuItem *item;
+ GVariant *icon;
+
+ item = g_menu_item_new (g_app_info_get_name (G_APP_INFO (app_info)), "launch");
+ g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.application");
+
+ icon = g_icon_serialize (g_app_info_get_icon (G_APP_INFO (app_info)));
+ if (icon)
+ {
+ g_menu_item_set_attribute_value (item, "icon", icon);
+ g_variant_unref (icon);
+ }
+
+ g_menu_append_item (app_section, item);
+
+ g_object_unref (item);
+ }
+
+ /* application actions */
+#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 *) &section))
+ {
+ while (g_menu_model_get_n_items (G_MENU_MODEL (section)) > 0)
+ g_menu_remove (section, 0);
+ }
+}
+
+static GMenu *
+create_status_section (void)
+{
+ GMenu *menu;
+ GMenuItem *item;
+ struct status_item {
+ gchar *label;
+ gchar *action;
+ gchar *icon_name;
+ } status_items[] = {
+ { _("Available"), "indicator.status::available", "user-available" },
+ { _("Away"), "indicator.status::away", "user-away" },
+ { _("Busy"), "indicator.status::busy", "user-busy" },
+ { _("Invisible"), "indicator.status::invisible", "user-invisible" },
+ { _("Offline"), "indicator.status::offline", "user-offline" }
+ };
+ int i;
+
+ menu = g_menu_new ();
+
+ item = g_menu_item_new (NULL, NULL);
+
+ for (i = 0; i < G_N_ELEMENTS (status_items); i++) {
+ g_menu_item_set_label (item, status_items[i].label);
+ g_menu_item_set_detailed_action (item, status_items[i].action);
+ g_menu_item_set_attribute (item, "icon", "s", status_items[i].icon_name);
+ g_menu_append_item (menu, item);
+ }
+
+ g_object_unref (item);
+ return menu;
+}
+
+static void
+im_desktop_menu_constructed (GObject *object)
+{
+ ImDesktopMenu *menu = IM_DESKTOP_MENU (object);
+ ImApplicationList *applist;
+
+ {
+ GMenu *status_section;
+
+ status_section = create_status_section();
+ im_menu_append_section (IM_MENU (menu), G_MENU_MODEL (status_section));
+
+ g_object_unref (status_section);
+ }
+
+ {
+ GMenu *clear_section;
+
+ clear_section = g_menu_new ();
+ g_menu_append (clear_section, "Clear", "indicator.remove-all");
+ im_menu_append_section (IM_MENU (menu), G_MENU_MODEL (clear_section));
+
+ g_object_unref (clear_section);
+ }
+
+ applist = im_menu_get_application_list (IM_MENU (menu));
+
+ {
+ GList *apps;
+ GList *it;
+
+ apps = im_application_list_get_applications (applist);
+ for (it = apps; it != NULL; it = it->next)
+ {
+ const gchar *id = it->data;
+ im_desktop_menu_app_added (applist, id, im_application_list_get_application (applist, id), menu);
+ }
+
+ g_list_free (apps);
+ }
+
+
+ g_signal_connect (applist, "app-added", G_CALLBACK (im_desktop_menu_app_added), menu);
+ g_signal_connect (applist, "source-added", G_CALLBACK (im_desktop_menu_source_added), menu);
+ g_signal_connect (applist, "source-removed", G_CALLBACK (im_desktop_menu_source_removed), menu);
+ g_signal_connect (applist, "remove-all", G_CALLBACK (im_desktop_menu_remove_all), menu);
+
+ G_OBJECT_CLASS (im_desktop_menu_parent_class)->constructed (object);
+}
+
+static void
+im_desktop_menu_finalize (GObject *object)
+{
+ ImDesktopMenu *menu = IM_DESKTOP_MENU (object);
+
+ g_hash_table_unref (menu->source_sections);
+
+ G_OBJECT_CLASS (im_desktop_menu_parent_class)->finalize (object);
+}
+
+static void
+im_desktop_menu_class_init (ImDesktopMenuClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = im_desktop_menu_constructed;
+ object_class->finalize = im_desktop_menu_finalize;
+}
+
+static void
+im_desktop_menu_init (ImDesktopMenu *menu)
+{
+ menu->source_sections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+}
+
+ImDesktopMenu *
+im_desktop_menu_new (ImApplicationList *applist)
+{
+ g_return_val_if_fail (IM_IS_APPLICATION_LIST (applist), NULL);
+
+ return g_object_new (IM_TYPE_DESKTOP_MENU,
+ "application-list", applist,
+ NULL);
+}
diff --git a/src/im-desktop-menu.h b/src/im-desktop-menu.h
new file mode 100644
index 0000000..9469ea6
--- /dev/null
+++ b/src/im-desktop-menu.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#ifndef __IM_DESKTOP_MENU_H__
+#define __IM_DESKTOP_MENU_H__
+
+#include "im-menu.h"
+
+#define IM_TYPE_DESKTOP_MENU (im_desktop_menu_get_type ())
+#define IM_DESKTOP_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_DESKTOP_MENU, ImDesktopMenu))
+#define IM_IS_DESKTOP_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_DESKTOP_MENU))
+
+typedef struct _ImDesktopMenu ImDesktopMenu;
+
+GType im_desktop_menu_get_type (void);
+
+ImDesktopMenu * im_desktop_menu_new (ImApplicationList *applist);
+
+#endif
diff --git a/src/im-menu.c b/src/im-menu.c
new file mode 100644
index 0000000..ac23a29
--- /dev/null
+++ b/src/im-menu.c
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#include "im-menu.h"
+
+struct _ImMenuPrivate
+{
+ GMenu *toplevel_menu;
+ GMenu *menu;
+ ImApplicationList *applist;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ImMenu, im_menu, G_TYPE_OBJECT)
+
+enum
+{
+ PROP_0,
+ PROP_APPLICATION_LIST,
+ NUM_PROPERTIES
+};
+
+static void
+im_menu_finalize (GObject *object)
+{
+ ImMenuPrivate *priv = im_menu_get_instance_private (IM_MENU (object));
+
+ g_object_unref (priv->toplevel_menu);
+ g_object_unref (priv->menu);
+ g_object_unref (priv->applist);
+
+ G_OBJECT_CLASS (im_menu_parent_class)->finalize (object);
+}
+
+static void
+im_menu_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ImMenuPrivate *priv = im_menu_get_instance_private (IM_MENU (object));
+
+ switch (property_id)
+ {
+ case PROP_APPLICATION_LIST:
+ g_value_set_object (value, priv->applist);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+im_menu_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ImMenuPrivate *priv = im_menu_get_instance_private (IM_MENU (object));
+
+ switch (property_id)
+ {
+ case PROP_APPLICATION_LIST: /* construct only */
+ priv->applist = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+im_menu_class_init (ImMenuClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = im_menu_finalize;
+ object_class->get_property = im_menu_get_property;
+ object_class->set_property = im_menu_set_property;
+
+ g_object_class_install_property (object_class, PROP_APPLICATION_LIST,
+ g_param_spec_object ("application-list", "", "",
+ IM_TYPE_APPLICATION_LIST,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+im_menu_init (ImMenu *menu)
+{
+ ImMenuPrivate *priv = im_menu_get_instance_private (menu);
+ GMenuItem *root;
+
+ priv->toplevel_menu = g_menu_new ();
+ priv->menu = g_menu_new ();
+
+ root = g_menu_item_new (NULL, "indicator.messages");
+ g_menu_item_set_attribute (root, "x-canonical-type", "s", "com.canonical.indicator.root");
+ g_menu_item_set_attribute (root, "action-namespace", "s", "indicator");
+ g_menu_item_set_submenu (root, G_MENU_MODEL (priv->menu));
+ g_menu_append_item (priv->toplevel_menu, root);
+
+ g_object_unref (root);
+}
+
+ImApplicationList *
+im_menu_get_application_list (ImMenu *menu)
+{
+ ImMenuPrivate *priv;
+
+ g_return_val_if_fail (IM_IS_MENU (menu), FALSE);
+
+ priv = im_menu_get_instance_private (menu);
+ return priv->applist;
+}
+
+gboolean
+im_menu_export (ImMenu *menu,
+ GDBusConnection *connection,
+ const gchar *object_path,
+ GError **error)
+{
+ ImMenuPrivate *priv;
+
+ g_return_val_if_fail (IM_IS_MENU (menu), FALSE);
+
+ priv = im_menu_get_instance_private (menu);
+ return g_dbus_connection_export_menu_model (connection,
+ object_path,
+ G_MENU_MODEL (priv->toplevel_menu),
+ error) > 0;
+}
+
+void
+im_menu_append_section (ImMenu *menu,
+ GMenuModel *section)
+{
+ ImMenuPrivate *priv;
+
+ g_return_if_fail (IM_IS_MENU (menu));
+ g_return_if_fail (G_IS_MENU_MODEL (section));
+
+ priv = im_menu_get_instance_private (menu);
+
+ g_menu_append_section (priv->menu, NULL, section);
+}
+
+void
+im_menu_insert_section (ImMenu *menu,
+ 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
new file mode 100644
index 0000000..d3775ad
--- /dev/null
+++ b/src/im-menu.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#ifndef __IM_MENU_H__
+#define __IM_MENU_H__
+
+#include <gio/gio.h>
+#include "im-application-list.h"
+
+#define IM_TYPE_MENU (im_menu_get_type ())
+#define IM_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_MENU, ImMenu))
+#define IM_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_MENU, ImMenuClass))
+#define IM_IS_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_MENU))
+#define IM_IS_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_MENU))
+#define IM_MENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_MENU, ImMenuClass))
+
+typedef struct _ImMenuClass ImMenuClass;
+typedef struct _ImMenu ImMenu;
+typedef struct _ImMenuPrivate ImMenuPrivate;
+
+struct _ImMenuClass
+{
+ GObjectClass parent_class;
+};
+
+struct _ImMenu
+{
+ GObject parent_instance;
+};
+
+GType im_menu_get_type (void) G_GNUC_CONST;
+
+ImApplicationList * im_menu_get_application_list (ImMenu *menu);
+
+gboolean im_menu_export (ImMenu *menu,
+ GDBusConnection *connection,
+ const gchar *object_path,
+ GError **error);
+
+void im_menu_append_section (ImMenu *menu,
+ GMenuModel *section);
+
+void im_menu_insert_section (ImMenu *menu,
+ gint position,
+ const gchar *namespace,
+ GMenuModel *section);
+
+#endif
diff --git a/src/im-phone-menu.c b/src/im-phone-menu.c
new file mode 100644
index 0000000..7381097
--- /dev/null
+++ b/src/im-phone-menu.c
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2012 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#include "im-phone-menu.h"
+
+#include <string.h>
+
+typedef ImMenuClass ImPhoneMenuClass;
+
+struct _ImPhoneMenu
+{
+ ImMenu parent;
+
+ GMenu *message_section;
+ GMenu *source_section;
+};
+
+G_DEFINE_TYPE (ImPhoneMenu, im_phone_menu, IM_TYPE_MENU);
+
+typedef void (*ImMenuForeachFunc) (GMenuModel *menu, gint pos);
+
+static void
+im_phone_menu_foreach_item_with_action (GMenuModel *menu,
+ const gchar *action,
+ ImMenuForeachFunc func)
+{
+ gint n_items;
+ gint i;
+
+ n_items = g_menu_model_get_n_items (menu);
+ for (i = 0; i < n_items; i++)
+ {
+ gchar *item_action;
+
+ g_menu_model_get_item_attribute (menu, i, G_MENU_ATTRIBUTE_ACTION, "s", &item_action);
+
+ if (g_str_equal (action, item_action))
+ func (menu, i);
+
+ g_free (item_action);
+ }
+}
+
+static void
+im_phone_menu_constructed (GObject *object)
+{
+ ImPhoneMenu *menu = IM_PHONE_MENU (object);
+ ImApplicationList *applist;
+
+ im_menu_append_section (IM_MENU (menu), G_MENU_MODEL (menu->message_section));
+ im_menu_append_section (IM_MENU (menu), G_MENU_MODEL (menu->source_section));
+
+ {
+ GMenu *clear_section;
+ GMenuItem *item;
+
+ clear_section = g_menu_new ();
+
+ item = g_menu_item_new ("Clear All", "remove-all");
+ g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.button");
+ g_menu_append_item (clear_section, item);
+
+ im_menu_append_section (IM_MENU (menu), G_MENU_MODEL (clear_section));
+
+ g_object_unref (item);
+ g_object_unref (clear_section);
+ }
+
+ applist = im_menu_get_application_list (IM_MENU (menu));
+
+ g_signal_connect_swapped (applist, "message-added", G_CALLBACK (im_phone_menu_add_message), menu);
+ g_signal_connect_swapped (applist, "message-removed", G_CALLBACK (im_phone_menu_remove_message), menu);
+ g_signal_connect_swapped (applist, "app-stopped", G_CALLBACK (im_phone_menu_remove_application), menu);
+ g_signal_connect_swapped (applist, "remove-all", G_CALLBACK (im_phone_menu_remove_all), menu);
+
+ G_OBJECT_CLASS (im_phone_menu_parent_class)->constructed (object);
+}
+
+static void
+im_phone_menu_dispose (GObject *object)
+{
+ ImPhoneMenu *menu = IM_PHONE_MENU (object);
+
+ g_clear_object (&menu->message_section);
+ g_clear_object (&menu->source_section);
+
+ G_OBJECT_CLASS (im_phone_menu_parent_class)->dispose (object);
+}
+
+static void
+im_phone_menu_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (im_phone_menu_parent_class)->finalize (object);
+}
+
+static void
+im_phone_menu_class_init (ImPhoneMenuClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = im_phone_menu_constructed;
+ object_class->dispose = im_phone_menu_dispose;
+ object_class->finalize = im_phone_menu_finalize;
+}
+
+static void
+im_phone_menu_init (ImPhoneMenu *menu)
+{
+ menu->message_section = g_menu_new ();
+ menu->source_section = g_menu_new ();
+}
+
+ImPhoneMenu *
+im_phone_menu_new (ImApplicationList *applist)
+{
+ g_return_val_if_fail (IM_IS_APPLICATION_LIST (applist), NULL);
+
+ return g_object_new (IM_TYPE_PHONE_MENU,
+ "application-list", applist,
+ NULL);
+}
+
+static gint64
+im_phone_menu_get_message_time (GMenuModel *model,
+ gint i)
+{
+ gint64 time;
+
+ g_menu_model_get_item_attribute (model, i, "x-canonical-time", "x", &time);
+
+ return time;
+}
+
+void
+im_phone_menu_add_message (ImPhoneMenu *menu,
+ const gchar *app_id,
+ const gchar *app_icon,
+ const gchar *id,
+ const gchar *iconstr,
+ const gchar *title,
+ const gchar *subtitle,
+ const gchar *body,
+ GVariant *actions,
+ gint64 time)
+{
+ GMenuItem *item;
+ gchar *action_name;
+ gint n_messages;
+ gint pos;
+
+ g_return_if_fail (IM_IS_PHONE_MENU (menu));
+ g_return_if_fail (app_id);
+
+ action_name = g_strconcat (app_id, ".msg.", id, NULL);
+
+ item = g_menu_item_new (title, action_name);
+
+ g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.messages.messageitem");
+ g_menu_item_set_attribute (item, "x-canonical-message-id", "s", id);
+ g_menu_item_set_attribute (item, "x-canonical-subtitle", "s", subtitle);
+ g_menu_item_set_attribute (item, "x-canonical-text", "s", body);
+ g_menu_item_set_attribute (item, "x-canonical-time", "x", time);
+
+ if (iconstr)
+ g_menu_item_set_attribute (item, "icon", "s", iconstr);
+
+ if (app_icon)
+ g_menu_item_set_attribute (item, "x-canonical-app-icon", "s", app_icon);
+
+ if (actions)
+ g_menu_item_set_attribute (item, "x-canonical-message-actions", "v", actions);
+
+ n_messages = g_menu_model_get_n_items (G_MENU_MODEL (menu->message_section));
+ pos = 0;
+ while (pos < n_messages &&
+ time < im_phone_menu_get_message_time (G_MENU_MODEL (menu->message_section), pos))
+ pos++;
+
+ g_menu_insert_item (menu->message_section, pos, item);
+
+ g_free (action_name);
+ g_object_unref (item);
+}
+
+void
+im_phone_menu_remove_message (ImPhoneMenu *menu,
+ const gchar *app_id,
+ const gchar *id)
+{
+ gchar *action_name;
+
+ g_return_if_fail (IM_IS_PHONE_MENU (menu));
+ g_return_if_fail (app_id != NULL);
+
+ action_name = g_strconcat (app_id, ".msg.", id, NULL);
+ im_phone_menu_foreach_item_with_action (G_MENU_MODEL (menu->message_section),
+ action_name,
+ (ImMenuForeachFunc) g_menu_remove);
+
+ g_free (action_name);
+}
+
+void
+im_phone_menu_add_source (ImPhoneMenu *menu,
+ const gchar *app_id,
+ const gchar *id,
+ const gchar *label,
+ const gchar *iconstr)
+{
+ GMenuItem *item;
+ gchar *action_name;
+
+ g_return_if_fail (IM_IS_PHONE_MENU (menu));
+ g_return_if_fail (app_id != NULL);
+
+ action_name = g_strconcat (app_id, ".src.", id, NULL);
+
+ item = g_menu_item_new (label, action_name);
+ g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.messages.sourceitem");
+
+ if (iconstr)
+ g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr);
+
+ g_menu_prepend_item (menu->source_section, item);
+
+ g_free (action_name);
+ g_object_unref (item);
+}
+
+void
+im_phone_menu_remove_source (ImPhoneMenu *menu,
+ const gchar *app_id,
+ const gchar *id)
+{
+ gchar *action_name;
+
+ g_return_if_fail (IM_IS_PHONE_MENU (menu));
+ g_return_if_fail (app_id != NULL);
+
+ action_name = g_strconcat (app_id, ".src.", id, NULL);
+ im_phone_menu_foreach_item_with_action (G_MENU_MODEL (menu->source_section),
+ action_name,
+ (ImMenuForeachFunc) g_menu_remove);
+
+ g_free (action_name);
+}
+
+static void
+im_phone_menu_remove_all_for_app (GMenu *menu,
+ const gchar *app_id)
+{
+ gchar *prefix;
+ gint n_items;
+ gint i = 0;
+
+ prefix = g_strconcat (app_id, ".", NULL);
+
+ n_items = g_menu_model_get_n_items (G_MENU_MODEL (menu));
+ while (i < n_items)
+ {
+ gchar *action;
+
+ g_menu_model_get_item_attribute (G_MENU_MODEL (menu), i, G_MENU_ATTRIBUTE_ACTION, "s", &action);
+ if (g_str_has_prefix (action, prefix))
+ {
+ g_menu_remove (menu, i);
+ n_items--;
+ }
+ else
+ {
+ i++;
+ }
+
+ g_free (action);
+ }
+
+ g_free (prefix);
+}
+
+void
+im_phone_menu_remove_application (ImPhoneMenu *menu,
+ const gchar *app_id)
+{
+ g_return_if_fail (IM_IS_PHONE_MENU (menu));
+ g_return_if_fail (app_id != NULL);
+
+ im_phone_menu_remove_all_for_app (menu->source_section, app_id);
+ im_phone_menu_remove_all_for_app (menu->message_section, app_id);
+}
+
+void
+im_phone_menu_remove_all (ImPhoneMenu *menu)
+{
+ g_return_if_fail (IM_IS_PHONE_MENU (menu));
+
+ while (g_menu_model_get_n_items (G_MENU_MODEL (menu->message_section)))
+ g_menu_remove (menu->message_section, 0);
+
+ while (g_menu_model_get_n_items (G_MENU_MODEL (menu->source_section)))
+ g_menu_remove (menu->source_section, 0);
+}
diff --git a/src/im-phone-menu.h b/src/im-phone-menu.h
new file mode 100644
index 0000000..9742f61
--- /dev/null
+++ b/src/im-phone-menu.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#ifndef __IM_PHONE_MENU_H__
+#define __IM_PHONE_MENU_H__
+
+#include "im-menu.h"
+
+#define IM_TYPE_PHONE_MENU (im_phone_menu_get_type ())
+#define IM_PHONE_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_PHONE_MENU, ImPhoneMenu))
+#define IM_PHONE_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_PHONE_MENU, ImPhoneMenuClass))
+#define IM_IS_PHONE_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_PHONE_MENU))
+#define IM_IS_PHONE_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_PHONE_MENU))
+#define IM_PHONE_MENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_PHONE_MENU, ImPhoneMenuClass))
+
+typedef struct _ImPhoneMenu ImPhoneMenu;
+
+GType im_phone_menu_get_type (void);
+
+ImPhoneMenu * im_phone_menu_new (ImApplicationList *applist);
+
+void im_phone_menu_add_message (ImPhoneMenu *menu,
+ const gchar *app_id,
+ const gchar *app_icon,
+ const gchar *id,
+ const gchar *iconstr,
+ const gchar *title,
+ const gchar *subtitle,
+ const gchar *body,
+ GVariant *actions,
+ gint64 time);
+
+void im_phone_menu_remove_message (ImPhoneMenu *menu,
+ const gchar *app_id,
+ const gchar *id);
+
+void im_phone_menu_add_source (ImPhoneMenu *menu,
+ const gchar *app_id,
+ const gchar *id,
+ const gchar *label,
+ const gchar *iconstr);
+
+void im_phone_menu_remove_source (ImPhoneMenu *menu,
+ const gchar *app_id,
+ const gchar *id);
+
+void im_phone_menu_remove_application (ImPhoneMenu *menu,
+ const gchar *app_id);
+
+void im_phone_menu_remove_all (ImPhoneMenu *menu);
+
+#endif
diff --git a/src/im-source-menu-item.c b/src/im-source-menu-item.c
deleted file mode 100644
index 2577c30..0000000
--- a/src/im-source-menu-item.c
+++ /dev/null
@@ -1,407 +0,0 @@
-/*
- * Copyright 2012 Canonical Ltd.
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 3, as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranties of
- * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
- * PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Lars Uebernickel <lars.uebernickel@canonical.com>
- */
-
-#include "im-source-menu-item.h"
-
-#include <libintl.h>
-#include "ido-detail-label.h"
-
-struct _ImSourceMenuItemPrivate
-{
- GActionGroup *action_group;
- gchar *action;
-
- GtkWidget *icon;
- GtkWidget *label;
- GtkWidget *detail;
-
- gint64 time;
- guint timer_id;
-};
-
-enum
-{
- PROP_0,
- PROP_MENU_ITEM,
- PROP_ACTION_GROUP,
- NUM_PROPERTIES
-};
-
-static GParamSpec *properties[NUM_PROPERTIES];
-
-G_DEFINE_TYPE (ImSourceMenuItem, im_source_menu_item, GTK_TYPE_MENU_ITEM);
-
-static void
-im_source_menu_item_constructed (GObject *object)
-{
- ImSourceMenuItemPrivate *priv = IM_SOURCE_MENU_ITEM (object)->priv;
- GtkWidget *grid;
- gint icon_width;
-
- gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &icon_width, NULL);
-
- priv->icon = g_object_ref (gtk_image_new ());
- gtk_widget_set_margin_left (priv->icon, icon_width + 6);
-
- priv->label = g_object_ref (gtk_label_new (""));
- gtk_label_set_max_width_chars (GTK_LABEL (priv->label), 40);
- gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_END);
- gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5);
-
- priv->detail = g_object_ref (ido_detail_label_new (""));
- gtk_widget_set_halign (priv->detail, GTK_ALIGN_END);
- gtk_widget_set_hexpand (priv->detail, TRUE);
- gtk_style_context_add_class (gtk_widget_get_style_context (priv->detail), "accelerator");
-
- grid = gtk_grid_new ();
- gtk_grid_attach (GTK_GRID (grid), priv->icon, 0, 0, 1, 1);
- gtk_grid_attach (GTK_GRID (grid), priv->label, 1, 0, 1, 1);
- gtk_grid_attach (GTK_GRID (grid), priv->detail, 2, 0, 1, 1);
-
- gtk_container_add (GTK_CONTAINER (object), grid);
- gtk_widget_show_all (grid);
-
- G_OBJECT_CLASS (im_source_menu_item_parent_class)->constructed (object);
-}
-
-static gchar *
-im_source_menu_item_time_span_string (gint64 timestamp)
-{
- gchar *str;
- gint64 span;
- gint hours;
- gint minutes;
-
- span = MAX (g_get_real_time () - timestamp, 0) / G_USEC_PER_SEC;
- hours = span / 3600;
- minutes = (span / 60) % 60;
-
- if (hours == 0)
- {
- /* TRANSLATORS: number of minutes that have passed */
- str = g_strdup_printf (ngettext ("%d min", "%d min", minutes), minutes);
- }
- else
- {
- /* TRANSLATORS: number of hours that have passed */
- str = g_strdup_printf (ngettext ("%d h", "%d h", hours), hours);
- }
-
- return str;
-}
-
-static void
-im_source_menu_item_set_detail_time (ImSourceMenuItem *self,
- gint64 time)
-{
- ImSourceMenuItemPrivate *priv = self->priv;
- gchar *str;
-
- priv->time = time;
-
- str = im_source_menu_item_time_span_string (priv->time);
- ido_detail_label_set_text (IDO_DETAIL_LABEL (priv->detail), str);
-
- g_free (str);
-}
-
-static gboolean
-im_source_menu_item_update_time (gpointer data)
-{
- ImSourceMenuItem *self = data;
-
- im_source_menu_item_set_detail_time (self, self->priv->time);
-
- return TRUE;
-}
-
-static gboolean
-im_source_menu_item_set_state (ImSourceMenuItem *self,
- GVariant *state)
-{
- ImSourceMenuItemPrivate *priv = self->priv;
- guint32 count;
- gint64 time;
- const gchar *str;
-
- if (priv->timer_id != 0)
- {
- g_source_remove (priv->timer_id);
- priv->timer_id = 0;
- }
-
- g_return_val_if_fail (g_variant_is_of_type (state, G_VARIANT_TYPE ("(uxsb)")), FALSE);
-
- g_variant_get (state, "(ux&sb)", &count, &time, &str, NULL);
-
- if (count != 0)
- ido_detail_label_set_count (IDO_DETAIL_LABEL (priv->detail), count);
- else if (time != 0)
- {
- im_source_menu_item_set_detail_time (self, time);
- priv->timer_id = g_timeout_add_seconds (59, im_source_menu_item_update_time, self);
- }
- else if (str != NULL && *str)
- ido_detail_label_set_text (IDO_DETAIL_LABEL (priv->detail), str);
-
- return TRUE;
-}
-
-static void
-im_source_menu_item_set_action_name (ImSourceMenuItem *self,
- const gchar *action_name)
-{
- ImSourceMenuItemPrivate *priv = self->priv;
- gboolean enabled = FALSE;
- GVariant *state;
-
- if (priv->action != NULL)
- g_free (priv->action);
-
- priv->action = g_strdup (action_name);
-
- if (priv->action_group != NULL && priv->action != NULL &&
- g_action_group_query_action (priv->action_group, priv->action,
- &enabled, NULL, NULL, NULL, &state))
- {
- if (!state || !im_source_menu_item_set_state (self, state))
- enabled = FALSE;
-
- if (state)
- g_variant_unref (state);
- }
-
- gtk_widget_set_sensitive (GTK_WIDGET (self), enabled);
-}
-
-static void
-im_source_menu_item_action_added (GActionGroup *action_group,
- gchar *action_name,
- gpointer user_data)
-{
- ImSourceMenuItem *self = user_data;
-
- if (g_strcmp0 (self->priv->action, action_name) == 0)
- im_source_menu_item_set_action_name (self, action_name);
-}
-
-static void
-im_source_menu_item_action_removed (GActionGroup *action_group,
- gchar *action_name,
- gpointer user_data)
-{
- ImSourceMenuItem *self = user_data;
-
- if (g_strcmp0 (self->priv->action, action_name) == 0)
- {
- gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
- }
-}
-
-static void
-im_source_menu_item_action_enabled_changed (GActionGroup *action_group,
- gchar *action_name,
- gboolean enabled,
- gpointer user_data)
-{
- ImSourceMenuItem *self = user_data;
-
- if (g_strcmp0 (self->priv->action, action_name) == 0)
- gtk_widget_set_sensitive (GTK_WIDGET (self), enabled);
-}
-
-static void
-im_source_menu_item_action_state_changed (GActionGroup *action_group,
- gchar *action_name,
- GVariant *value,
- gpointer user_data)
-{
- ImSourceMenuItem *self = user_data;
-
- if (g_strcmp0 (self->priv->action, action_name) == 0)
- im_source_menu_item_set_state (self, value);
-}
-
-static void
-im_source_menu_set_property (GObject *object,
- guint property_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- ImSourceMenuItem *self = IM_SOURCE_MENU_ITEM (object);
-
- switch (property_id)
- {
- case PROP_MENU_ITEM:
- im_source_menu_item_set_menu_item (self, G_MENU_ITEM (g_value_get_object (value)));
- break;
-
- case PROP_ACTION_GROUP:
- im_source_menu_item_set_action_group (self, G_ACTION_GROUP (g_value_get_object (value)));
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- }
-}
-
-static void
-im_source_menu_item_dispose (GObject *object)
-{
- ImSourceMenuItem *self = IM_SOURCE_MENU_ITEM (object);
-
- if (self->priv->timer_id != 0)
- {
- g_source_remove (self->priv->timer_id);
- self->priv->timer_id = 0;
- }
-
- if (self->priv->action_group)
- im_source_menu_item_set_action_group (self, NULL);
-
- g_clear_object (&self->priv->icon);
- g_clear_object (&self->priv->label);
- g_clear_object (&self->priv->detail);
-
- G_OBJECT_CLASS (im_source_menu_item_parent_class)->dispose (object);
-}
-
-static void
-im_source_menu_item_finalize (GObject *object)
-{
- ImSourceMenuItemPrivate *priv = IM_SOURCE_MENU_ITEM (object)->priv;
-
- g_free (priv->action);
-
- G_OBJECT_CLASS (im_source_menu_item_parent_class)->finalize (object);
-}
-
-static void
-im_source_menu_item_activate (GtkMenuItem *item)
-{
- ImSourceMenuItemPrivate *priv = IM_SOURCE_MENU_ITEM (item)->priv;
-
- if (priv->action && priv->action_group)
- g_action_group_activate_action (priv->action_group, priv->action, NULL);
-}
-
-static void
-im_source_menu_item_class_init (ImSourceMenuItemClass *klass)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
- GtkMenuItemClass *menu_item_class = GTK_MENU_ITEM_CLASS (klass);
-
- g_type_class_add_private (klass, sizeof (ImSourceMenuItemPrivate));
-
- object_class->constructed = im_source_menu_item_constructed;
- object_class->set_property = im_source_menu_set_property;
- object_class->dispose = im_source_menu_item_dispose;
- object_class->finalize = im_source_menu_item_finalize;
-
- menu_item_class->activate = im_source_menu_item_activate;
-
- properties[PROP_MENU_ITEM] = g_param_spec_object ("menu-item",
- "Menu item",
- "The model GMenuItem for this menu item",
- G_TYPE_MENU_ITEM,
- G_PARAM_WRITABLE |
- G_PARAM_STATIC_STRINGS);
-
- properties[PROP_ACTION_GROUP] = g_param_spec_object ("action-group",
- "Action group",
- "The action group associated with this menu item",
- G_TYPE_ACTION_GROUP,
- G_PARAM_WRITABLE |
- G_PARAM_STATIC_STRINGS);
-
- g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
-}
-
-static void
-im_source_menu_item_init (ImSourceMenuItem *self)
-{
- self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
- IM_TYPE_SOURCE_MENU_ITEM,
- ImSourceMenuItemPrivate);
-}
-
-void
-im_source_menu_item_set_menu_item (ImSourceMenuItem *self,
- GMenuItem *menuitem)
-{
- gchar *iconstr = NULL;
- GIcon *icon = NULL;
- gchar *label;
- gchar *action = NULL;
-
- if (g_menu_item_get_attribute (menuitem, "x-canonical-icon", "s", &iconstr))
- {
- GError *error;
- icon = g_icon_new_for_string (iconstr, &error);
- if (icon == NULL)
- {
- g_warning ("unable to set icon: %s", error->message);
- g_error_free (error);
- }
- g_free (iconstr);
- }
- gtk_image_set_from_gicon (GTK_IMAGE (self->priv->icon), icon, GTK_ICON_SIZE_MENU);
-
- g_menu_item_get_attribute (menuitem, "label", "s", &label);
- gtk_label_set_label (GTK_LABEL (self->priv->label), label ? label : "");
-
- g_menu_item_get_attribute (menuitem, "action", "s", &action);
- im_source_menu_item_set_action_name (self, action);
-
- if (icon)
- g_object_unref (icon);
- g_free (label);
- g_free (action);
-}
-
-void
-im_source_menu_item_set_action_group (ImSourceMenuItem *self,
- GActionGroup *action_group)
-{
- ImSourceMenuItemPrivate *priv = self->priv;
-
- if (priv->action_group != NULL)
- {
- g_signal_handlers_disconnect_by_func (priv->action_group, im_source_menu_item_action_added, self);
- g_signal_handlers_disconnect_by_func (priv->action_group, im_source_menu_item_action_removed, self);
- g_signal_handlers_disconnect_by_func (priv->action_group, im_source_menu_item_action_enabled_changed, self);
- g_signal_handlers_disconnect_by_func (priv->action_group, im_source_menu_item_action_state_changed, self);
-
- g_clear_object (&priv->action_group);
- }
-
- if (action_group != NULL)
- {
- priv->action_group = g_object_ref (action_group);
-
- g_signal_connect (priv->action_group, "action-added",
- G_CALLBACK (im_source_menu_item_action_added), self);
- g_signal_connect (priv->action_group, "action-removed",
- G_CALLBACK (im_source_menu_item_action_removed), self);
- g_signal_connect (priv->action_group, "action-enabled-changed",
- G_CALLBACK (im_source_menu_item_action_enabled_changed), self);
- g_signal_connect (priv->action_group, "action-state-changed",
- G_CALLBACK (im_source_menu_item_action_state_changed), self);
- }
-}
diff --git a/src/im-source-menu-item.h b/src/im-source-menu-item.h
deleted file mode 100644
index c359b94..0000000
--- a/src/im-source-menu-item.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2012 Canonical Ltd.
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 3, as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranties of
- * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
- * PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * Authors:
- * Lars Uebernickel <lars.uebernickel@canonical.com>
- */
-
-#ifndef __IM_SOURCE_MENU_ITEM_H__
-#define __IM_SOURCE_MENU_ITEM_H__
-
-#include <gtk/gtk.h>
-
-#define IM_TYPE_SOURCE_MENU_ITEM (im_source_menu_item_get_type ())
-#define IM_SOURCE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_SOURCE_MENU_ITEM, ImSourceMenuItem))
-#define IM_SOURCE_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_SOURCE_MENU_ITEM, ImSourceMenuItemClass))
-#define IS_IM_SOURCE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_SOURCE_MENU_ITEM))
-#define IS_IM_SOURCE_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_SOURCE_MENU_ITEM))
-#define IM_SOURCE_MENU_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_SOURCE_MENU_ITEM, ImSourceMenuItemClass))
-
-typedef struct _ImSourceMenuItem ImSourceMenuItem;
-typedef struct _ImSourceMenuItemClass ImSourceMenuItemClass;
-typedef struct _ImSourceMenuItemPrivate ImSourceMenuItemPrivate;
-
-struct _ImSourceMenuItemClass
-{
- GtkMenuItemClass parent_class;
-};
-
-struct _ImSourceMenuItem
-{
- GtkMenuItem parent;
- ImSourceMenuItemPrivate *priv;
-};
-
-GType im_source_menu_item_get_type (void);
-
-void im_source_menu_item_set_menu_item (ImSourceMenuItem *item,
- GMenuItem *menuitem);
-void im_source_menu_item_set_action_group (ImSourceMenuItem *self,
- GActionGroup *action_group);
-
-#endif
diff --git a/src/indicator-messages.c b/src/indicator-messages.c
deleted file mode 100644
index 5c5df31..0000000
--- a/src/indicator-messages.c
+++ /dev/null
@@ -1,382 +0,0 @@
-/*
-An indicator to show information that is in messaging applications
-that the user is using.
-
-Copyright 2012 Canonical Ltd.
-
-Authors:
- Ted Gould <ted@canonical.com>
- Lars Uebernickel <lars.uebernickel@canonical.com>
-
-This program is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License version 3, as published
-by the Free Software Foundation.
-
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranties of
-MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-PURPOSE. See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License along
-with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "config.h"
-
-#include <string.h>
-#include <math.h>
-#include <glib.h>
-#include <glib-object.h>
-#include <glib/gi18n-lib.h>
-#include <gtk/gtk.h>
-
-#include <libindicator/indicator.h>
-#include <libindicator/indicator-object.h>
-#include <libindicator/indicator-service-manager.h>
-
-#include "dbus-data.h"
-
-#include "ido-menu-item.h"
-#include "im-app-menu-item.h"
-#include "im-source-menu-item.h"
-
-#define INDICATOR_MESSAGES_TYPE (indicator_messages_get_type ())
-#define INDICATOR_MESSAGES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INDICATOR_MESSAGES_TYPE, IndicatorMessages))
-#define INDICATOR_MESSAGES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INDICATOR_MESSAGES_TYPE, IndicatorMessagesClass))
-#define IS_INDICATOR_MESSAGES(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INDICATOR_MESSAGES_TYPE))
-#define IS_INDICATOR_MESSAGES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INDICATOR_MESSAGES_TYPE))
-#define INDICATOR_MESSAGES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), INDICATOR_MESSAGES_TYPE, IndicatorMessagesClass))
-
-typedef struct _IndicatorMessages IndicatorMessages;
-typedef struct _IndicatorMessagesClass IndicatorMessagesClass;
-
-struct _IndicatorMessagesClass {
- IndicatorObjectClass parent_class;
-};
-
-struct _IndicatorMessages {
- IndicatorObject parent;
- IndicatorServiceManager * service;
- GActionGroup *actions;
- GMenuModel *menu;
- GtkWidget *image;
- GtkWidget *gtkmenu;
- gchar *accessible_desc;
-};
-
-GType indicator_messages_get_type (void);
-
-/* Indicator Module Config */
-INDICATOR_SET_VERSION
-INDICATOR_SET_TYPE(INDICATOR_MESSAGES_TYPE)
-
-/* Prototypes */
-static void indicator_messages_class_init (IndicatorMessagesClass *klass);
-static void indicator_messages_init (IndicatorMessages *self);
-static void indicator_messages_dispose (GObject *object);
-static void indicator_messages_finalize (GObject *object);
-static void service_connection_changed (IndicatorServiceManager *sm,
- gboolean connected,
- gpointer user_data);
-static GtkImage * get_image (IndicatorObject * io);
-static GtkMenu * get_menu (IndicatorObject * io);
-static const gchar * get_accessible_desc (IndicatorObject * io);
-static const gchar * get_name_hint (IndicatorObject * io);
-static void menu_items_changed (GMenuModel *menu,
- gint position,
- gint removed,
- gint added,
- gpointer user_data);
-static void messages_action_added (GActionGroup *action_group,
- gchar *action_name,
- gpointer user_data);
-static void messages_state_changed (GActionGroup *action_group,
- gchar *action_name,
- GVariant *value,
- gpointer user_data);
-static void indicator_messages_add_toplevel_menu (IndicatorMessages *self);
-
-G_DEFINE_TYPE (IndicatorMessages, indicator_messages, INDICATOR_OBJECT_TYPE);
-
-/* Initialize the one-timers */
-static void
-indicator_messages_class_init (IndicatorMessagesClass *klass)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
- object_class->dispose = indicator_messages_dispose;
- object_class->finalize = indicator_messages_finalize;
-
- IndicatorObjectClass * io_class = INDICATOR_OBJECT_CLASS(klass);
-
- io_class->get_image = get_image;
- io_class->get_menu = get_menu;
- io_class->get_accessible_desc = get_accessible_desc;
- io_class->get_name_hint = get_name_hint;
-}
-
-/* Build up our per-instance variables */
-static void
-indicator_messages_init (IndicatorMessages *self)
-{
- self->service = indicator_service_manager_new_version(INDICATOR_MESSAGES_DBUS_NAME, 1);
- g_signal_connect (self->service, "connection-change",
- G_CALLBACK (service_connection_changed), self);
-
- self->gtkmenu = gtk_menu_new ();
- g_object_ref_sink (self->gtkmenu);
-
- self->image = g_object_ref_sink (gtk_image_new ());
-
- /* make sure custom menu item types are registered (so that
- * gtk_model_new_from_menu can pick them up */
- ido_menu_item_get_type ();
- im_app_menu_item_get_type ();
- im_source_menu_item_get_type ();
-}
-
-/* Unref stuff */
-static void
-indicator_messages_dispose (GObject *object)
-{
- IndicatorMessages * self = INDICATOR_MESSAGES(object);
- g_return_if_fail(self != NULL);
-
- g_clear_object (&self->service);
- g_clear_object (&self->actions);
- g_clear_object (&self->menu);
- g_clear_object (&self->gtkmenu);
- g_clear_object (&self->image);
-
- G_OBJECT_CLASS (indicator_messages_parent_class)->dispose (object);
- return;
-}
-
-/* Destory all memory users */
-static void
-indicator_messages_finalize (GObject *object)
-{
- IndicatorMessages *self = INDICATOR_MESSAGES (object);
-
- g_free (self->accessible_desc);
-
- G_OBJECT_CLASS (indicator_messages_parent_class)->finalize (object);
- return;
-}
-
-
-
-/* Functions */
-
-static void service_connection_changed (IndicatorServiceManager *sm,
- gboolean connected,
- gpointer user_data)
-{
- IndicatorMessages *self = user_data;
- GDBusConnection *bus;
- GError *error = NULL;
-
- if (self->actions != NULL) {
- g_signal_handlers_disconnect_by_func (self->actions, messages_action_added, self);
- g_signal_handlers_disconnect_by_func (self->actions, messages_state_changed, self);
- g_clear_object (&self->actions);
- }
- if (self->menu != NULL) {
- g_signal_handlers_disconnect_by_func (self->menu, menu_items_changed, self);
- g_clear_object (&self->menu);
- }
- gtk_menu_shell_bind_model (GTK_MENU_SHELL (self->gtkmenu), NULL, NULL, FALSE);
-
- if (connected == FALSE)
- return;
-
- bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
- if (!bus) {
- g_warning ("error connecting to the session bus: %s", error->message);
- g_error_free (error);
- return;
- }
-
- self->actions = G_ACTION_GROUP (g_dbus_action_group_get (bus,
- INDICATOR_MESSAGES_DBUS_NAME,
- INDICATOR_MESSAGES_DBUS_OBJECT));
- gtk_widget_insert_action_group (self->gtkmenu,
- get_name_hint (INDICATOR_OBJECT (self)),
- self->actions);
- g_signal_connect (self->actions, "action-added::messages",
- G_CALLBACK (messages_action_added), self);
- g_signal_connect (self->actions, "action-state-changed::messages",
- G_CALLBACK (messages_state_changed), self);
-
- self->menu = G_MENU_MODEL (g_dbus_menu_model_get (bus,
- INDICATOR_MESSAGES_DBUS_NAME,
- INDICATOR_MESSAGES_DBUS_OBJECT));
- g_signal_connect (self->menu, "items-changed", G_CALLBACK (menu_items_changed), self);
-
- if (g_menu_model_get_n_items (self->menu) == 1)
- indicator_messages_add_toplevel_menu (self);
- else
- indicator_object_set_visible (INDICATOR_OBJECT (self), FALSE);
-
- g_object_unref (bus);
-}
-
-static GtkImage *
-get_image (IndicatorObject * io)
-{
- IndicatorMessages *self = INDICATOR_MESSAGES (io);
-
- gtk_widget_show (self->image);
- return GTK_IMAGE (self->image);
-}
-
-static GtkMenu *
-get_menu (IndicatorObject * io)
-{
- IndicatorMessages *self = INDICATOR_MESSAGES (io);
-
- return GTK_MENU (self->gtkmenu);
-}
-
-static const gchar *
-get_accessible_desc (IndicatorObject * io)
-{
- IndicatorMessages *self = INDICATOR_MESSAGES (io);
- return self->accessible_desc;
-}
-
-static const gchar *
-get_name_hint (IndicatorObject *io)
-{
- return PACKAGE;
-}
-
-static void
-indicator_messages_accessible_desc_updated (IndicatorMessages *self)
-{
- GList *entries;
-
- entries = indicator_object_get_entries (INDICATOR_OBJECT (self));
- g_return_if_fail (entries != NULL);
-
- g_signal_emit_by_name (self, INDICATOR_OBJECT_SIGNAL_ACCESSIBLE_DESC_UPDATE, entries->data);
-
- g_list_free (entries);
-}
-
-static GIcon *
-g_menu_model_get_item_attribute_icon (GMenuModel *menu,
- gint index,
- const gchar *attribute)
-{
- gchar *iconstr;
- GIcon *icon = NULL;
-
- if (g_menu_model_get_item_attribute (menu, index, attribute, "s", &iconstr)) {
- GError *error;
-
- icon = g_icon_new_for_string (iconstr, &error);
- if (icon == NULL) {
- g_warning ("unable to load icon: %s", error->message);
- g_error_free (error);
- }
-
- g_free (iconstr);
- }
-
- return icon;
-}
-
-static void
-indicator_messages_add_toplevel_menu (IndicatorMessages *self)
-{
- GIcon *icon;
- GMenuModel *popup;
-
- indicator_object_set_visible (INDICATOR_OBJECT (self), TRUE);
-
- icon = g_menu_model_get_item_attribute_icon (self->menu, 0, "x-canonical-icon");
- if (icon) {
- gtk_image_set_from_gicon (GTK_IMAGE (self->image), icon, GTK_ICON_SIZE_LARGE_TOOLBAR);
- g_object_unref (icon);
- }
-
- g_free (self->accessible_desc);
- self->accessible_desc = NULL;
- if (g_menu_model_get_item_attribute (self->menu, 0, "x-canonical-accessible-description",
- "s", &self->accessible_desc)) {
- indicator_messages_accessible_desc_updated (self);
- }
-
- popup = g_menu_model_get_item_link (self->menu, 0, G_MENU_LINK_SUBMENU);
- if (popup) {
- gtk_menu_shell_bind_model (GTK_MENU_SHELL (self->gtkmenu),
- popup,
- get_name_hint (INDICATOR_OBJECT (self)),
- TRUE);
-
- g_object_unref (popup);
- }
-}
-
-static void
-menu_items_changed (GMenuModel *menu,
- gint position,
- gint removed,
- gint added,
- gpointer user_data)
-{
- IndicatorMessages *self = user_data;
-
- g_return_if_fail (position == 0);
-
- if (added == 1)
- indicator_messages_add_toplevel_menu (self);
- else if (removed == 1)
- indicator_object_set_visible (INDICATOR_OBJECT (self), FALSE);
-}
-
-static void
-indicator_messages_update_icon (IndicatorMessages *self,
- GVariant *state)
-{
- GIcon *icon;
- GError *error = NULL;
-
- g_return_if_fail (g_variant_is_of_type (state, G_VARIANT_TYPE_STRING));
-
- icon = g_icon_new_for_string (g_variant_get_string (state, NULL), &error);
- if (icon == NULL) {
- g_warning ("unable to load icon: %s", error->message);
- g_error_free (error);
- }
- else {
- gtk_image_set_from_gicon (GTK_IMAGE (self->image), icon, GTK_ICON_SIZE_LARGE_TOOLBAR);
- g_object_unref (icon);
- }
-}
-
-static void
-messages_action_added (GActionGroup *action_group,
- gchar *action_name,
- gpointer user_data)
-{
- IndicatorMessages *self = user_data;
- GVariant *state;
-
- state = g_action_group_get_action_state (action_group, "messages");
- indicator_messages_update_icon (self, state);
-
- g_variant_unref (state);
-}
-
-static void
-messages_state_changed (GActionGroup *action_group,
- gchar *action_name,
- GVariant *value,
- gpointer user_data)
-{
- IndicatorMessages *self = user_data;
-
- indicator_messages_update_icon (self, value);
-}
diff --git a/src/messages-service.c b/src/messages-service.c
index 25a19b9..71fa09b 100644
--- a/src/messages-service.c
+++ b/src/messages-service.c
@@ -23,419 +23,25 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include <config.h>
#include <locale.h>
-#include <libindicator/indicator-service.h>
-#include <gdk/gdk.h>
#include <gio/gio.h>
-#include <gio/gdesktopappinfo.h>
#include <glib/gi18n.h>
-#include "app-section.h"
#include "dbus-data.h"
-#include "gactionmuxer.h"
#include "gsettingsstrv.h"
#include "gmenuutils.h"
#include "indicator-messages-service.h"
+#include "indicator-messages-application.h"
+#include "im-phone-menu.h"
+#include "im-desktop-menu.h"
+#include "im-application-list.h"
#define NUM_STATUSES 5
-static GHashTable *applications;
+static ImApplicationList *applications;
static IndicatorMessagesService *messages_service;
-static GSimpleActionGroup *actions;
-static GActionMuxer *action_muxer;
-static GMenu *toplevel_menu;
-static GMenu *menu;
-static GMenuModel *chat_section;
+static GHashTable *menus;
static GSettings *settings;
-static gboolean draws_attention;
-static const gchar *global_status[6]; /* max 5: available, away, busy, invisible, offline */
-
-static gchar *
-indicator_messages_get_icon_name ()
-{
- GString *name;
- GIcon *icon;
- gchar *iconstr;
-
- name = g_string_new ("indicator-messages");
-
- if (global_status[0] != NULL)
- {
- if (global_status[1] != NULL)
- g_string_append (name, "-mixed");
- else
- g_string_append_printf (name, "-%s", global_status[0]);
- }
-
- if (draws_attention)
- g_string_append (name, "-new");
-
- icon = g_themed_icon_new (name->str);
- g_themed_icon_append_name (G_THEMED_ICON (icon),
- draws_attention ? "indicator-messages-new"
- : "indicator-messages");
-
- iconstr = g_icon_to_string (icon);
-
- g_object_unref (icon);
- g_string_free (name, TRUE);
-
- return iconstr;
-}
-
-static void
-indicator_messages_update_icon ()
-{
- GSimpleAction *messages;
- gchar *icon;
-
- messages = G_SIMPLE_ACTION (g_simple_action_group_lookup (actions, "messages"));
- g_return_if_fail (messages != NULL);
-
- icon = indicator_messages_get_icon_name ();
- g_simple_action_set_state (messages, g_variant_new_string (icon));
-
- g_free (icon);
-}
-
-static gchar *
-g_app_info_get_simple_id (GAppInfo *appinfo)
-{
- const gchar *id;
-
- id = g_app_info_get_id (appinfo);
- if (!id)
- return NULL;
-
- if (g_str_has_suffix (id, ".desktop"))
- return g_strndup (id, strlen (id) - 8);
- else
- return g_strdup (id);
-}
-
-static void
-actions_changed (GObject *object,
- GParamSpec *pspec,
- gpointer user_data)
-{
- AppSection *section = APP_SECTION (object);
- gchar *id;
- GActionGroup *actions;
-
- id = g_app_info_get_simple_id (app_section_get_app_info (section));
- actions = app_section_get_actions (section);
-
- g_action_muxer_insert (action_muxer, id, actions);
- g_free (id);
-}
-
-
-static gboolean
-app_section_draws_attention (gpointer key,
- gpointer value,
- gpointer user_data)
-{
- AppSection *section = value;
- return app_section_get_draws_attention (section);
-}
-
-static void
-draws_attention_changed (GObject *object,
- GParamSpec *pspec,
- gpointer user_data)
-{
- GSimpleAction *clear;
-
- clear = G_SIMPLE_ACTION (g_simple_action_group_lookup (actions, "clear"));
- g_return_if_fail (clear != NULL);
-
- draws_attention = g_hash_table_find (applications, app_section_draws_attention, NULL) != NULL;
-
- g_simple_action_set_enabled (clear, draws_attention);
-
- indicator_messages_update_icon ();
-}
-
-static gboolean
-app_section_uses_chat (gpointer key,
- gpointer value,
- gpointer user_data)
-{
- AppSection *section = value;
- return app_section_get_uses_chat_status (section);
-}
-
-static void
-update_chat_section ()
-{
- gboolean show_chat;
- GMenuModel *first_section;
-
- show_chat = g_hash_table_find (applications, app_section_uses_chat, NULL) != NULL;
-
- first_section = g_menu_model_get_item_link (G_MENU_MODEL (menu), 0, G_MENU_LINK_SECTION);
- if (first_section == chat_section) {
- if (!show_chat)
- g_menu_remove (menu, 0);
- }
- else {
- if (show_chat)
- g_menu_insert_section (menu, 0, NULL, chat_section);
- }
-
- if (first_section != NULL)
- g_object_unref (first_section);
-
- indicator_messages_update_icon ();
-}
-
-static void
-uses_chat_status_changed (GObject *object,
- GParamSpec *pspec,
- gpointer user_data)
-{
- update_chat_section ();
-}
-
-static gboolean
-strv_contains (const gchar **strv,
- const gchar *needle)
-{
- const gchar **it;
-
- it = strv;
- while (*it != NULL && !g_str_equal (*it, needle))
- it++;
-
- return *it != NULL;
-}
-
-static void
-update_chat_status ()
-{
- GHashTableIter iter;
- AppSection *section;
- int pos;
- GAction *status;
-
- for (pos = 0; pos < G_N_ELEMENTS (global_status); pos++)
- global_status[pos] = NULL;
-
- pos = 0;
- g_hash_table_iter_init (&iter, applications);
- while (g_hash_table_iter_next (&iter, NULL, (gpointer) &section) &&
- pos < G_N_ELEMENTS (global_status))
- {
- const gchar *status_str = NULL;
-
- status_str = app_section_get_status (section);
- if (status_str != NULL && !strv_contains (global_status, status_str))
- global_status[pos++] = status_str;
- }
-
- if (pos == 0)
- global_status[0] = "offline";
-
- status = g_simple_action_group_lookup (actions, "status");
- g_return_if_fail (status != NULL);
-
- g_simple_action_set_state (G_SIMPLE_ACTION (status), g_variant_new_strv (global_status, -1));
-
- indicator_messages_update_icon ();
-}
-
-static void
-chat_status_changed (GObject *object,
- GParamSpec *pspec,
- gpointer user_data)
-{
- update_chat_status ();
-}
-
-static void
-remove_section (AppSection *section,
- const gchar *id)
-{
- int pos = g_menu_find_section (menu, app_section_get_menu (section));
- if (pos >= 0)
- g_menu_remove (menu, pos);
- g_action_muxer_remove (action_muxer, id);
-
- g_signal_handlers_disconnect_by_func (section, actions_changed, NULL);
- g_signal_handlers_disconnect_by_func (section, draws_attention_changed, NULL);
- g_signal_handlers_disconnect_by_func (section, uses_chat_status_changed, NULL);
- g_signal_handlers_disconnect_by_func (section, chat_status_changed, NULL);
- g_signal_handlers_disconnect_by_func (section, remove_section, NULL);
-
- g_hash_table_remove (applications, id);
-
- if (g_hash_table_size (applications) == 0 &&
- g_menu_model_get_n_items (G_MENU_MODEL (toplevel_menu)) == 1) {
- g_menu_remove (toplevel_menu, 0);
- }
-
- update_chat_status ();
- update_chat_section ();
-}
-
-static AppSection *
-add_application (const gchar *desktop_id)
-{
- GDesktopAppInfo *appinfo;
- gchar *id;
- AppSection *section;
-
- appinfo = g_desktop_app_info_new (desktop_id);
- if (!appinfo) {
- g_warning ("could not add '%s', there's no desktop file with that id", desktop_id);
- return NULL;
- }
-
- id = g_app_info_get_simple_id (G_APP_INFO (appinfo));
- section = g_hash_table_lookup (applications, id);
-
- if (!section) {
- GMenuItem *menuitem;
-
- section = app_section_new(appinfo);
- g_hash_table_insert (applications, g_strdup (id), section);
-
- g_action_muxer_insert (action_muxer, id, app_section_get_actions (section));
- g_signal_connect (section, "notify::actions",
- G_CALLBACK (actions_changed), NULL);
- g_signal_connect (section, "notify::draws-attention",
- G_CALLBACK (draws_attention_changed), NULL);
- g_signal_connect (section, "notify::uses-chat-status",
- G_CALLBACK (uses_chat_status_changed), NULL);
- g_signal_connect (section, "notify::chat-status",
- G_CALLBACK (chat_status_changed), NULL);
- g_signal_connect_data (section, "destroy",
- G_CALLBACK (remove_section),
- g_strdup (id),
- (GClosureNotify) g_free,
- 0);
-
- /* TODO insert it at the right position (alphabetically by application name) */
- menuitem = g_menu_item_new_section (NULL, app_section_get_menu (section));
- g_menu_item_set_attribute (menuitem, "action-namespace", "s", id);
- g_menu_insert_item (menu, g_menu_model_get_n_items (G_MENU_MODEL (menu)) -1, menuitem);
- g_object_unref (menuitem);
- }
-
- if (g_menu_model_get_n_items (G_MENU_MODEL (toplevel_menu)) == 0) {
- GMenuItem *header;
-
- header = g_menu_item_new (NULL, "messages");
- g_menu_item_set_submenu (header, G_MENU_MODEL (menu));
- g_menu_item_set_attribute (header, "x-canonical-accessible-description", "s", _("Messages"));
- g_menu_append_item (toplevel_menu, header);
-
- g_object_unref (header);
- }
-
- g_free (id);
- g_object_unref (appinfo);
- return section;
-}
-
-static void
-remove_application (const char *desktop_id)
-{
- GDesktopAppInfo *appinfo;
- gchar *id;
- AppSection *section;
-
- appinfo = g_desktop_app_info_new (desktop_id);
- if (!appinfo) {
- g_warning ("could not remove '%s', there's no desktop file with that id", desktop_id);
- return;
- }
-
- id = g_app_info_get_simple_id (G_APP_INFO (appinfo));
-
- section = g_hash_table_lookup (applications, id);
- if (section) {
- remove_section (section, id);
- }
- else {
- g_warning ("could not remove '%s', it's not registered", desktop_id);
- }
-
- g_free (id);
- g_object_unref (appinfo);
-}
-
-/* This function turns a specific desktop id into a menu
- item and registers it appropriately with everyone */
-static gboolean
-build_launcher (gpointer data)
-{
- gchar *desktop_id = data;
-
- add_application (desktop_id);
-
- g_free (desktop_id);
- return FALSE;
-}
-
-/* This function goes through all the launchers that we're
- supposed to be grabbing and decides to show turn them
- into menu items or not. It doens't do the work, but it
- makes the decision. */
-static gboolean
-build_launchers (gpointer data)
-{
- gchar **applications = g_settings_get_strv (settings, "applications");
- gchar **app;
-
- g_return_val_if_fail (applications != NULL, FALSE);
-
- for (app = applications; *app; app++)
- {
- g_idle_add(build_launcher, g_strdup (*app));
- }
-
- g_strfreev (applications);
- return FALSE;
-}
-
-static void
-service_shutdown (IndicatorService * service, gpointer user_data)
-{
- GMainLoop *mainloop = user_data;
-
- g_warning("Shutting down service!");
- g_main_loop_quit(mainloop);
-}
-
-static void
-app_section_remove_attention (gpointer key,
- gpointer value,
- gpointer user_data)
-{
- AppSection *section = value;
- app_section_clear_draws_attention (section);
-}
-
-static void
-clear_action_activate (GSimpleAction *simple,
- GVariant *param,
- gpointer user_data)
-{
- g_hash_table_foreach (applications, app_section_remove_attention, NULL);
-}
-
-static void
-status_action_activate (GSimpleAction *action,
- GVariant *parameter,
- gpointer user_data)
-{
- const gchar *status;
-
- status = g_variant_get_string (parameter, NULL);
-
- indicator_messages_service_emit_status_changed (messages_service, status);
-}
static void
register_application (IndicatorMessagesService *service,
@@ -444,18 +50,15 @@ register_application (IndicatorMessagesService *service,
const gchar *menu_path,
gpointer user_data)
{
- AppSection *section;
GDBusConnection *bus;
const gchar *sender;
- section = add_application (desktop_id);
- if (!section)
- return;
+ im_application_list_add (applications, desktop_id);
bus = g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (service));
sender = g_dbus_method_invocation_get_sender (invocation);
- app_section_set_object_path (section, bus, sender, menu_path);
+ im_application_list_set_remote (applications, desktop_id, bus, sender, menu_path);
g_settings_strv_append_unique (settings, "applications", desktop_id);
indicator_messages_service_complete_register_application (service, invocation);
@@ -467,36 +70,13 @@ unregister_application (IndicatorMessagesService *service,
const gchar *desktop_id,
gpointer user_data)
{
- remove_application (desktop_id);
+ im_application_list_remove (applications, desktop_id);
g_settings_strv_remove (settings, "applications", desktop_id);
indicator_messages_service_complete_unregister_application (service, invocation);
}
static void
-application_stopped_running (IndicatorMessagesService *service,
- GDBusMethodInvocation *invocation,
- const gchar *desktop_id,
- gpointer user_data)
-{
- GDesktopAppInfo *appinfo;
- gchar *id;
- AppSection *section;
-
- indicator_messages_service_complete_application_stopped_running (service, invocation);
-
- if (!(appinfo = g_desktop_app_info_new (desktop_id)))
- return;
-
- id = g_app_info_get_simple_id (G_APP_INFO (appinfo));
- section = g_hash_table_lookup (applications, id);
- app_section_unset_object_path (section);
-
- g_free (id);
- g_object_unref (appinfo);
-}
-
-static void
set_status (IndicatorMessagesService *service,
GDBusMethodInvocation *invocation,
const gchar *desktop_id,
@@ -504,8 +84,7 @@ set_status (IndicatorMessagesService *service,
gpointer user_data)
{
GDesktopAppInfo *appinfo;
- gchar *id;
- AppSection *section;
+ const gchar *id;
g_return_if_fail (g_str_equal (status_str, "available") ||
g_str_equal (status_str, "away")||
@@ -519,113 +98,53 @@ set_status (IndicatorMessagesService *service,
return;
}
- id = g_app_info_get_simple_id (G_APP_INFO (appinfo));
- section = g_hash_table_lookup (applications, id);
- if (section != NULL)
- app_section_set_status (section, status_str);
+ id = g_app_info_get_id (G_APP_INFO (appinfo));
+
+ im_application_list_set_status(applications, id, status_str);
indicator_messages_service_complete_set_status (service, invocation);
- g_free (id);
g_object_unref (appinfo);
}
-static GSimpleActionGroup *
-create_action_group (void)
-{
- GSimpleActionGroup *actions;
- GSimpleAction *messages;
- GSimpleAction *clear;
- GSimpleAction *status;
- const gchar *default_status[] = { "offline", NULL };
- gchar *icon;
-
- actions = g_simple_action_group_new ();
-
- /* state of the messages action is its icon name */
- icon = indicator_messages_get_icon_name ();
- messages = g_simple_action_new_stateful ("messages", G_VARIANT_TYPE ("s"),
- g_variant_new_string (icon));
-
- status = g_simple_action_new_stateful ("status", G_VARIANT_TYPE ("s"),
- g_variant_new_strv (default_status, -1));
- g_signal_connect (status, "activate", G_CALLBACK (status_action_activate), NULL);
-
- clear = g_simple_action_new ("clear", NULL);
- g_simple_action_set_enabled (clear, FALSE);
- g_signal_connect (clear, "activate", G_CALLBACK (clear_action_activate), NULL);
-
- g_simple_action_group_insert (actions, G_ACTION (messages));
- g_simple_action_group_insert (actions, G_ACTION (status));
- g_simple_action_group_insert (actions, G_ACTION (clear));
-
- g_free (icon);
- return actions;
-}
-
-static GMenuModel *
-create_status_section (void)
+/* The status has been set by the user, let's tell the world! */
+static void
+status_set_by_user (ImApplicationList * list, const gchar * status, gpointer user_data)
{
- GMenu *menu;
- GMenuItem *item;
- struct status_item {
- gchar *label;
- gchar *action;
- gchar *icon_name;
- } status_items[] = {
- { _("Available"), "status::available", "user-available" },
- { _("Away"), "status::away", "user-away" },
- { _("Busy"), "status::busy", "user-busy" },
- { _("Invisible"), "status::invisible", "user-invisible" },
- { _("Offline"), "status::offline", "user-offline" }
- };
- int i;
-
- menu = g_menu_new ();
-
- item = g_menu_item_new (NULL, NULL);
- g_menu_item_set_attribute (item, "x-canonical-type", "s", "IdoMenuItem");
-
- for (i = 0; i < G_N_ELEMENTS (status_items); i++) {
- g_menu_item_set_label (item, status_items[i].label);
- g_menu_item_set_detailed_action (item, status_items[i].action);
- g_menu_item_set_attribute (item, "x-canonical-icon", "s", status_items[i].icon_name);
- g_menu_append_item (menu, item);
- }
-
- g_object_unref (item);
- return G_MENU_MODEL (menu);
+ indicator_messages_service_emit_status_changed(messages_service, status);
+ return;
}
static void
-got_bus (GObject *object,
- GAsyncResult * res,
- gpointer user_data)
+on_bus_acquired (GDBusConnection *bus,
+ const gchar *name,
+ gpointer user_data)
{
- GDBusConnection *bus;
GError *error = NULL;
-
- bus = g_bus_get_finish (res, &error);
- if (!bus) {
- g_warning ("unable to connect to the session bus: %s", error->message);
- g_error_free (error);
- return;
- }
+ GHashTableIter it;
+ const gchar *profile;
+ ImMenu *menu;
g_dbus_connection_export_action_group (bus, INDICATOR_MESSAGES_DBUS_OBJECT,
- G_ACTION_GROUP (action_muxer), &error);
+ im_application_list_get_action_group (applications),
+ &error);
if (error) {
g_warning ("unable to export action group on dbus: %s", error->message);
g_error_free (error);
return;
}
- g_dbus_connection_export_menu_model (bus, INDICATOR_MESSAGES_DBUS_OBJECT,
- G_MENU_MODEL (toplevel_menu), &error);
- if (error) {
- g_warning ("unable to export menu on dbus: %s", error->message);
- g_error_free (error);
- return;
+ g_hash_table_iter_init (&it, menus);
+ while (g_hash_table_iter_next (&it, (gpointer *) &profile, (gpointer *) &menu)) {
+ gchar *object_path;
+
+ object_path = g_strconcat (INDICATOR_MESSAGES_DBUS_OBJECT, "/", profile, NULL);
+ if (!im_menu_export (menu, bus, object_path, &error)) {
+ g_warning ("unable to export menu for profile '%s': %s", profile, error->message);
+ g_clear_error (&error);
+ }
+
+ g_free (object_path);
}
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (messages_service),
@@ -640,18 +159,28 @@ got_bus (GObject *object,
g_object_unref (bus);
}
+static void
+on_name_lost (GDBusConnection *bus,
+ const gchar *name,
+ gpointer user_data)
+{
+ GMainLoop *mainloop = user_data;
+
+ g_main_loop_quit (mainloop);
+}
+
int
main (int argc, char ** argv)
{
- GMainLoop * mainloop;
- IndicatorService * service;
+ GMainLoop * mainloop = NULL;
+ GBusNameOwnerFlags flags;
- gdk_init(&argc, &argv);
- mainloop = g_main_loop_new (NULL, FALSE);
+ /* Glib init */
+#if G_ENCODE_VERSION(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34
+ g_type_init();
+#endif
- /* Create the Indicator Service interface */
- service = indicator_service_new_version(INDICATOR_MESSAGES_DBUS_NAME, 1);
- g_signal_connect(service, INDICATOR_SERVICE_SIGNAL_SHUTDOWN, G_CALLBACK(service_shutdown), mainloop);
+ mainloop = g_main_loop_new (NULL, FALSE);
/* Setting up i18n and gettext. Apparently, we need
all of these. */
@@ -662,40 +191,46 @@ main (int argc, char ** argv)
/* Bring up the service DBus interface */
messages_service = indicator_messages_service_skeleton_new ();
- g_bus_get (G_BUS_TYPE_SESSION, NULL, got_bus, NULL);
-
- actions = create_action_group ();
+ flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT;
+ if (argc >= 2 && g_str_equal (argv[1], "--replace"))
+ flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE;
- action_muxer = g_action_muxer_new ();
- g_action_muxer_insert (action_muxer, NULL, G_ACTION_GROUP (actions));
+ g_bus_own_name (G_BUS_TYPE_SESSION, "com.canonical.indicator.messages", flags,
+ on_bus_acquired, NULL, on_name_lost, mainloop, NULL);
g_signal_connect (messages_service, "handle-register-application",
G_CALLBACK (register_application), NULL);
g_signal_connect (messages_service, "handle-unregister-application",
G_CALLBACK (unregister_application), NULL);
- g_signal_connect (messages_service, "handle-application-stopped-running",
- G_CALLBACK (application_stopped_running), NULL);
g_signal_connect (messages_service, "handle-set-status",
G_CALLBACK (set_status), NULL);
- menu = g_menu_new ();
- chat_section = create_status_section ();
- g_menu_append (menu, _("Clear"), "clear");
-
- toplevel_menu = g_menu_new ();
+ applications = im_application_list_new ();
+ g_signal_connect (applications, "status-set",
+ G_CALLBACK (status_set_by_user), NULL);
settings = g_settings_new ("com.canonical.indicator.messages");
+ {
+ gchar **app_ids;
+ gchar **id;
+
+ app_ids = g_settings_get_strv (settings, "applications");
+ for (id = app_ids; *id; id++)
+ im_application_list_add (applications, *id);
- applications = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+ g_strfreev (app_ids);
+ }
- g_idle_add(build_launchers, NULL);
+ 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_main_loop_run(mainloop);
/* Clean up */
+ g_hash_table_unref (menus);
g_object_unref (messages_service);
- g_object_unref (chat_section);
g_object_unref (settings);
- g_hash_table_unref (applications);
+ g_object_unref (applications);
return 0;
}
diff --git a/src/messages-service.xml b/src/messages-service.xml
deleted file mode 100644
index 3c3c779..0000000
--- a/src/messages-service.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<node name="/">
- <interface name="com.canonical.indicator.messages.service">
-
- <method name="RegisterApplication">
- <arg type="s" name="desktop_id" direction="in" />
- <arg type="o" name="menu_path" direction="in" />
- </method>
-
- <method name="UnregisterApplication">
- <arg type="s" name="desktop_id" direction="in" />
- </method>
-
- <method name="ApplicationStoppedRunning">
- <arg type="s" name="desktop_id" direction="in" />
- </method>
-
- <method name="SetStatus">
- <arg type="s" name="desktop_id" direction="in" />
- <arg type="s" name="status" direction="in" />
- </method>
-
- <signal name="StatusChanged">
- <arg type="s" name="status" direction="in" />
- </signal>
-
- </interface>
-</node>