aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharles Kerr <charles.kerr@canonical.com>2013-05-31 19:51:15 +0000
committerTarmac <Unknown>2013-05-31 19:51:15 +0000
commite5d21874d8c6a0d0adfab5e5294b3d0d02445f11 (patch)
tree87d34939e9de15291122684b5b81105fc111fb14
parent27f3661cfb2266ee87a552738e5c03774dc9ae68 (diff)
parent18f2ca9e20c5ea1ac895f7892ae9d652153333ad (diff)
downloadayatana-ido-e5d21874d8c6a0d0adfab5e5294b3d0d02445f11.tar.gz
ayatana-ido-e5d21874d8c6a0d0adfab5e5294b3d0d02445f11.tar.bz2
ayatana-ido-e5d21874d8c6a0d0adfab5e5294b3d0d02445f11.zip
Put a framework in place to move custom menu items from the indicator packages into ido. It depends on a vendor-patch to gtk (see bug #1183505), because gtk+ does not yet have an API for inserting arbitrary menu items into menus created with gtk_menu_new_from_model().
IdoMenuItemFactory implements the GtkMenuItemFactory interface, which is used by (the patched) gtk+ to create menu items that have an "x-canonical-type" attribute. IdoActionHelper contains some common functionality that all menu items need (watching the action group for action additions and removals, as well es enabled and state changes). It can be used to glue a menu item widget to an action in IdoMenuItemFactory. This changeset also adds the first widget: IdoUserMenuItem (merged from lp:~charlesk/ido/add-idousermenuitem). Please follow this example when adding additional widgets. Approved by PS Jenkins bot, Ted Gould.
-rw-r--r--configure.ac10
-rw-r--r--debian/changelog6
-rw-r--r--debian/control4
-rw-r--r--debian/libido3-0.1-0.symbols14
-rw-r--r--example/menus.c45
-rw-r--r--src/Makefile.am10
-rw-r--r--src/idoactionhelper.c403
-rw-r--r--src/idoactionhelper.h44
-rw-r--r--src/idomenuitemfactory.c68
-rw-r--r--src/idousermenuitem.c457
-rw-r--r--src/idousermenuitem.h69
-rw-r--r--src/libido.c36
-rw-r--r--src/libido.h2
13 files changed, 1158 insertions, 10 deletions
diff --git a/configure.ac b/configure.ac
index 2219ff0..706919f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,9 +1,9 @@
#
# shamelessly stolen from clutter-gtk
#
-m4_define([ido_major_version], [12])
+m4_define([ido_major_version], [13])
m4_define([ido_minor_version], [10])
-m4_define([ido_micro_version], [2])
+m4_define([ido_micro_version], [0])
m4_define([ido_api_version],
[ido_major_version.ido_minor_version])
@@ -79,11 +79,11 @@ AC_FUNC_MMAP
AC_CHECK_FUNCS([memset munmap strcasecmp strdup])
AC_CHECK_LIBM
-GLIB_REQUIRED_VERSION=2.32.0
-GTK_REQUIRED_VERSION=3.4.0
+GIO_REQUIRED_VERSION=2.37.0
+GTK_REQUIRED_VERSION=3.8.2
PKG_CHECK_MODULES(GTK,[gtk+-3.0 >= $GTK_REQUIRED_VERSION
- glib-2.0 >= $GLIB_REQUIRED_VERSION])
+ gio-2.0 >= $GIO_REQUIRED_VERSION])
AC_SUBST(GTK_CFLAGS)
AC_SUBST(GTK_LIBS)
diff --git a/debian/changelog b/debian/changelog
index 217d98b..d5204f4 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+ido (13.10.0-0ubuntu1) UNRELEASED; urgency=low
+
+ * New upstream release
+
+ -- Lars Uebernickel <lars.uebernickel@ubuntu.com> Mon, 27 May 2013 10:56:11 -0400
+
ido (12.10.3daily13.03.01-0ubuntu1) raring; urgency=low
[ Mathieu Trudel-Lapierre ]
diff --git a/debian/control b/debian/control
index c706502..f9f385b 100644
--- a/debian/control
+++ b/debian/control
@@ -10,7 +10,7 @@ Build-Depends: debhelper (>= 9),
libx11-dev,
libgtest-dev,
libglib2.0-dev (>=2.14.0),
- libgtk-3-dev (>= 3.0),
+ libgtk-3-dev (>= 3.8.2-0ubuntu2),
gtk-doc-tools,
gobject-introspection,
libgirepository1.0-dev,
@@ -45,7 +45,7 @@ Depends: ${shlibs:Depends},
libido3-0.1-0 (= ${binary:Version}),
pkg-config,
libglib2.0-dev (>=2.14.0),
- libgtk-3-dev (>= 3.0),
+ libgtk-3-dev (>= 3.8.2-0ubuntu2),
Description: Shared library providing extra gtk menu items for display in
system indicators
.
diff --git a/debian/libido3-0.1-0.symbols b/debian/libido3-0.1-0.symbols
index 2ddb354..342ee31 100644
--- a/debian/libido3-0.1-0.symbols
+++ b/debian/libido3-0.1-0.symbols
@@ -58,3 +58,17 @@ libido3-0.1.so.0 libido3-0.1-0 #MINVER#
ido_timeline_set_progress@Base 0.1.10
ido_timeline_set_screen@Base 0.1.8
ido_timeline_start@Base 0.1.8
+ ido_action_helper_activate@Base 0replaceme
+ ido_action_helper_get_action_target@Base 0replaceme
+ ido_action_helper_get_type@Base 0replaceme
+ ido_action_helper_get_widget@Base 0replaceme
+ ido_action_helper_new@Base 0replaceme
+ ido_user_menu_item_get_type@Base 0replaceme
+ ido_user_menu_item_new@Base 0replaceme
+ ido_user_menu_item_set_current_user@Base 0replaceme
+ ido_user_menu_item_set_icon@Base 0replaceme
+ ido_user_menu_item_set_label@Base 0replaceme
+ ido_user_menu_item_set_logged_in@Base 0replaceme
+ ido_user_menu_item_new_from_model@Base 0replaceme
+ ido_init@Base 0replaceme
+ ido_menu_item_factory_get_type@Base 0replaceme
diff --git a/example/menus.c b/example/menus.c
index 5687b8e..1675f45 100644
--- a/example/menus.c
+++ b/example/menus.c
@@ -4,6 +4,7 @@
#include "idocalendarmenuitem.h"
#include "idoentrymenuitem.h"
#include "idoswitchmenuitem.h"
+#include "idousermenuitem.h"
#include "config.h"
static void
@@ -92,10 +93,52 @@ main (int argc, char *argv[])
image = ido_scale_menu_item_get_secondary_image (IDO_SCALE_MENU_ITEM (menuitem));
gtk_image_set_from_stock (GTK_IMAGE (image), GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
-
g_signal_connect (menuitem, "slider-grabbed", G_CALLBACK (slider_grabbed), NULL);
g_signal_connect (menuitem, "slider-released", G_CALLBACK (slider_released), NULL);
+ /**
+ *** Users
+ **/
+
+ menuitem = gtk_separator_menu_item_new ();
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
+
+ menuitem = ido_user_menu_item_new ();
+ g_object_set (menuitem,
+ "label", "Guest",
+ "icon-filename", NULL,
+ "is-logged-in", FALSE,
+ "is-current-user", FALSE,
+ NULL);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
+
+ menuitem = ido_user_menu_item_new ();
+ g_object_set (menuitem,
+ "label", "Bobby Fischer",
+ "icon-filename", "/usr/share/pixmaps/faces/chess.jpg",
+ "is-logged-in", FALSE,
+ "is-current-user", FALSE,
+ NULL);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
+
+ menuitem = ido_user_menu_item_new ();
+ g_object_set (menuitem,
+ "label", "Linus Torvalds",
+ "icon-filename", "/usr/share/pixmaps/faces/penguin.jpg",
+ "is-logged-in", TRUE,
+ "is-current-user", FALSE,
+ NULL);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
+
+ menuitem = ido_user_menu_item_new ();
+ g_object_set (menuitem, "label", "Mark Shuttleworth",
+ "icon-filename", "/usr/share/pixmaps/faces/astronaut.jpg",
+ "is-logged-in", TRUE,
+ "is-current-user", TRUE,
+ NULL);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
+
+
/* Add the menubar */
gtk_menu_shell_append (GTK_MENU_SHELL (menubar), root);
diff --git a/src/Makefile.am b/src/Makefile.am
index e14efbf..0a4dbab 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -17,8 +17,10 @@ sources_h = \
idorange.h \
idoscalemenuitem.h \
idoswitchmenuitem.h \
+ idousermenuitem.h \
idotimeline.h \
- libido.h
+ libido.h \
+ idoactionhelper.h
EXTRA_DIST = \
ido.list \
@@ -59,6 +61,7 @@ AM_CFLAGS = \
$(COVERAGE_CFLAGS)
libido_0_1_la_SOURCES = \
+ libido.c \
idotypebuiltins.c \
idocalendarmenuitem.c \
idoentrymenuitem.c \
@@ -66,7 +69,10 @@ libido_0_1_la_SOURCES = \
idorange.c \
idoscalemenuitem.c \
idoswitchmenuitem.c \
- idotimeline.c
+ idotimeline.c \
+ idomenuitemfactory.c \
+ idoactionhelper.c \
+ idousermenuitem.c
libido3_0_1_la_SOURCES = $(libido_0_1_la_SOURCES)
diff --git a/src/idoactionhelper.c b/src/idoactionhelper.c
new file mode 100644
index 0000000..fdd9479
--- /dev/null
+++ b/src/idoactionhelper.c
@@ -0,0 +1,403 @@
+/*
+* 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 "idoactionhelper.h"
+
+typedef GObjectClass IdoActionHelperClass;
+
+struct _IdoActionHelper
+{
+ GObject parent;
+
+ GtkWidget *widget;
+ GActionGroup *actions;
+ gchar *action_name;
+ GVariant *action_target;
+};
+
+G_DEFINE_TYPE (IdoActionHelper, ido_action_helper, G_TYPE_OBJECT)
+
+enum
+{
+ PROP_0,
+ PROP_WIDGET,
+ PROP_ACTION_GROUP,
+ PROP_ACTION_NAME,
+ PROP_ACTION_TARGET,
+ NUM_PROPERTIES
+};
+
+enum
+{
+ ACTION_STATE_CHANGED,
+ NUM_SIGNALS
+};
+
+static GParamSpec *properties[NUM_PROPERTIES];
+static guint signals[NUM_SIGNALS];
+
+static void
+ido_action_helper_action_added (GActionGroup *actions,
+ const gchar *action_name,
+ gpointer user_data)
+{
+ IdoActionHelper *helper = user_data;
+ gboolean enabled;
+ GVariant *state;
+
+ if (!g_str_equal (action_name, helper->action_name))
+ return;
+
+ if (g_action_group_query_action (actions, action_name,
+ &enabled, NULL, NULL, NULL, &state))
+ {
+ gtk_widget_set_sensitive (helper->widget, enabled);
+
+ if (state)
+ {
+ g_signal_emit (helper, signals[ACTION_STATE_CHANGED], 0, state);
+ g_variant_unref (state);
+ }
+ }
+ else
+ {
+ gtk_widget_set_sensitive (helper->widget, FALSE);
+ }
+}
+
+static void
+ido_action_helper_action_removed (GActionGroup *action_group,
+ gchar *action_name,
+ gpointer user_data)
+{
+ IdoActionHelper *helper = user_data;
+
+ if (g_str_equal (action_name, helper->action_name))
+ gtk_widget_set_sensitive (helper->widget, FALSE);
+}
+
+static void
+ido_action_helper_action_enabled_changed (GActionGroup *action_group,
+ gchar *action_name,
+ gboolean enabled,
+ gpointer user_data)
+{
+ IdoActionHelper *helper = user_data;
+
+ if (g_str_equal (action_name, helper->action_name))
+ gtk_widget_set_sensitive (helper->widget, enabled);
+}
+
+static void
+ido_action_helper_action_state_changed (GActionGroup *action_group,
+ gchar *action_name,
+ GVariant *value,
+ gpointer user_data)
+{
+ IdoActionHelper *helper = user_data;
+
+ if (g_str_equal (action_name, helper->action_name))
+ g_signal_emit (helper, signals[ACTION_STATE_CHANGED], 0, value);
+}
+
+static gboolean
+call_action_added (gpointer user_data)
+{
+ IdoActionHelper *helper = user_data;
+
+ ido_action_helper_action_added (helper->actions, helper->action_name, helper);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+ido_action_helper_constructed (GObject *object)
+{
+ IdoActionHelper *helper = IDO_ACTION_HELPER (object);
+
+ g_signal_connect (helper->actions, "action-added",
+ G_CALLBACK (ido_action_helper_action_added), helper);
+ g_signal_connect (helper->actions, "action-removed",
+ G_CALLBACK (ido_action_helper_action_removed), helper);
+ g_signal_connect (helper->actions, "action-enabled-changed",
+ G_CALLBACK (ido_action_helper_action_enabled_changed), helper);
+ g_signal_connect (helper->actions, "action-state-changed",
+ G_CALLBACK (ido_action_helper_action_state_changed), helper);
+
+ if (g_action_group_has_action (helper->actions, helper->action_name))
+ {
+ /* call action_added in an idle, so that we don't fire the
+ * state-changed signal during construction (nobody could have
+ * connected by then).
+ */
+ g_idle_add (call_action_added, helper);
+ }
+
+ G_OBJECT_CLASS (ido_action_helper_parent_class)->constructed (object);
+}
+
+static void
+ido_action_helper_get_property (GObject *object,
+ guint id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdoActionHelper *helper = IDO_ACTION_HELPER (object);
+
+ switch (id)
+ {
+ case PROP_WIDGET:
+ g_value_set_object (value, helper->widget);
+ break;
+
+ case PROP_ACTION_GROUP:
+ g_value_set_object (value, helper->actions);
+ break;
+
+ case PROP_ACTION_NAME:
+ g_value_set_string (value, helper->action_name);
+ break;
+
+ case PROP_ACTION_TARGET:
+ g_value_set_variant (value, helper->action_target);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec);
+ }
+}
+
+static void
+ido_action_helper_set_property (GObject *object,
+ guint id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdoActionHelper *helper = IDO_ACTION_HELPER (object);
+
+ switch (id)
+ {
+ case PROP_WIDGET: /* construct-only */
+ helper->widget = g_value_dup_object (value);
+ break;
+
+ case PROP_ACTION_GROUP: /* construct-only */
+ helper->actions = g_value_dup_object (value);
+ break;
+
+ case PROP_ACTION_NAME: /* construct-only */
+ helper->action_name = g_value_dup_string (value);
+ break;
+
+ case PROP_ACTION_TARGET: /* construct-only */
+ helper->action_target = g_value_dup_variant (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec);
+ }
+}
+
+static void
+ido_action_helper_finalize (GObject *object)
+{
+ IdoActionHelper *helper = IDO_ACTION_HELPER (object);
+
+ g_object_unref (helper->widget);
+
+ g_signal_handlers_disconnect_by_data (helper->actions, helper);
+ g_object_unref (helper->actions);
+
+ g_free (helper->action_name);
+
+ if (helper->action_target)
+ g_variant_unref (helper->action_target);
+
+ G_OBJECT_CLASS (ido_action_helper_parent_class)->finalize (object);
+}
+
+static void
+ido_action_helper_class_init (IdoActionHelperClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->constructed = ido_action_helper_constructed;
+ object_class->get_property = ido_action_helper_get_property;
+ object_class->set_property = ido_action_helper_set_property;
+ object_class->finalize = ido_action_helper_finalize;
+
+ /**
+ * IdoActionHelper::action-state-changed:
+ * @helper: the #IdoActionHelper watching the action
+ * @state: the new state of the action
+ *
+ * Emitted when the widget must be updated from the action's state,
+ * which happens every time the action appears in the group and when
+ * the action changes its state.
+ */
+ signals[ACTION_STATE_CHANGED] = g_signal_new ("action-state-changed",
+ IDO_TYPE_ACTION_HELPER,
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VARIANT,
+ G_TYPE_NONE, 1, G_TYPE_VARIANT);
+
+ /**
+ * IdoActionHelper:widget:
+ *
+ * The widget that is associated with this action helper. The action
+ * helper updates the widget's "sensitive" property to reflect whether
+ * the action #IdoActionHelper:action-name exists in
+ * #IdoActionHelper:action-group.
+ */
+ properties[PROP_WIDGET] = g_param_spec_object ("widget", "", "",
+ GTK_TYPE_WIDGET,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ /**
+ * IdoActionHelper:action-group:
+ *
+ * The action group that eventually contains the action that
+ * #IdoActionHelper:widget should be bound to.
+ */
+ properties[PROP_ACTION_GROUP] = g_param_spec_object ("action-group", "", "",
+ G_TYPE_ACTION_GROUP,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ /**
+ * IdoActionHelper:action-name:
+ *
+ * The name of the action in #IdoActionHelper:action-group that
+ * should be bound to #IdoActionHelper:widget
+ */
+ properties[PROP_ACTION_NAME] = g_param_spec_string ("action-name", "", "", NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ /**
+ * IdoActionHelper:action-target:
+ *
+ * The target of #IdoActionHelper:widget. ido_action_helper_activate()
+ * passes the target as parameter when activating the action.
+ *
+ * The handler of #IdoActionHelper:action-state-changed is responsible
+ * for comparing this target with the action's state and updating the
+ * #IdoActionHelper:widget appropriately.
+ */
+ properties[PROP_ACTION_TARGET] = g_param_spec_variant ("action-target", "", "",
+ G_VARIANT_TYPE_ANY, NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
+}
+
+static void
+ido_action_helper_init (IdoActionHelper *helper)
+{
+}
+
+/**
+ * ido_action_helper_new:
+ * @widget: a #GtkWidget
+ * @action_group: a #GActionGroup
+ * @action_name: the name of an action in @action_group
+ * @target: the target of the action
+ *
+ * Creates a new #IdoActionHelper. This helper ties @widget to an action
+ * (and a target), and performs some common tasks:
+ *
+ * @widget will be set to insensitive whenever @action_group does not
+ * contain an action with the name @action_name, or the action with that
+ * name is disabled.
+ *
+ * Also, the helper emits the "action-state-changed" signal whenever the
+ * widget must be updated from the action's state. This includes once
+ * when the action was added, and every time the action changes its
+ * state.
+ *
+ * Returns: (transfer full): a new #IdoActionHelper
+ */
+IdoActionHelper *
+ido_action_helper_new (GtkWidget *widget,
+ GActionGroup *action_group,
+ const gchar *action_name,
+ GVariant *target)
+{
+ g_return_val_if_fail (widget != NULL, NULL);
+ g_return_val_if_fail (action_group != NULL, NULL);
+ g_return_val_if_fail (action_name != NULL, NULL);
+
+ return g_object_new (IDO_TYPE_ACTION_HELPER,
+ "widget", widget,
+ "action-group", action_group,
+ "action-name", action_name,
+ "action-target", target,
+ NULL);
+}
+
+/**
+ * ido_action_helper_get_widget:
+ * @helper: an #IdoActionHelper
+ *
+ * Returns: (transfer none): the #GtkWidget associated with @helper
+ */
+GtkWidget *
+ido_action_helper_get_widget (IdoActionHelper *helper)
+{
+ g_return_val_if_fail (IDO_IS_ACTION_HELPER (helper), NULL);
+
+ return helper->widget;
+}
+
+/**
+ * ido_action_helper_get_action_target:
+ * @helper: an #IdoActionHelper
+ *
+ * Returns: (transfer none): the action target that was set in
+ * ido_action_helper_new() as a #GVariant
+ */
+GVariant *
+ido_action_helper_get_action_target (IdoActionHelper *helper)
+{
+ g_return_val_if_fail (IDO_IS_ACTION_HELPER (helper), NULL);
+
+ return helper->action_target;
+}
+
+/**
+ * ido_action_helper_activate:
+ * @helper: an #IdoActionHelper
+ *
+ * Activates the action that is associated with this helper.
+ */
+void
+ido_action_helper_activate (IdoActionHelper *helper)
+{
+ g_return_if_fail (IDO_IS_ACTION_HELPER (helper));
+
+ if (helper->actions && helper->action_name)
+ g_action_group_activate_action (helper->actions, helper->action_name, helper->action_target);
+}
diff --git a/src/idoactionhelper.h b/src/idoactionhelper.h
new file mode 100644
index 0000000..22cdcae
--- /dev/null
+++ b/src/idoactionhelper.h
@@ -0,0 +1,44 @@
+/*
+* 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 __IDO_ACTION_HELPER_H__
+#define __IDO_ACTION_HELPER_H__
+
+#include <gtk/gtk.h>
+
+#define IDO_TYPE_ACTION_HELPER (ido_action_helper_get_type ())
+#define IDO_ACTION_HELPER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), IDO_TYPE_ACTION_HELPER, IdoActionHelper))
+#define IDO_IS_ACTION_HELPER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), IDO_TYPE_ACTION_HELPER))
+
+typedef struct _IdoActionHelper IdoActionHelper;
+
+GType ido_menu_item_get_type (void);
+
+IdoActionHelper * ido_action_helper_new (GtkWidget *widget,
+ GActionGroup *action_group,
+ const gchar *action_name,
+ GVariant *target);
+
+GtkWidget * ido_action_helper_get_widget (IdoActionHelper *helper);
+
+GVariant * ido_action_helper_get_action_target (IdoActionHelper *helper);
+
+void ido_action_helper_activate (IdoActionHelper *helper);
+
+#endif
diff --git a/src/idomenuitemfactory.c b/src/idomenuitemfactory.c
new file mode 100644
index 0000000..59f3630
--- /dev/null
+++ b/src/idomenuitemfactory.c
@@ -0,0 +1,68 @@
+/*
+ * 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 <gtk/gtk.h>
+#include <gtk/ubuntu-private.h>
+
+#include "idousermenuitem.h"
+
+#define IDO_TYPE_MENU_ITEM_FACTORY (ido_menu_item_factory_get_type ())
+#define IDO_MENU_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), IDO_TYPE_MENU_ITEM_FACTORY, IdoMenuItemFactory))
+#define IDO_IS_MENU_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), IDO_TYPE_MENU_ITEM_FACTORY))
+
+typedef GObject IdoMenuItemFactory;
+typedef GObjectClass IdoMenuItemFactoryClass;
+
+GType ido_menu_item_factory_get_type (void);
+static void ido_menu_item_factory_interface_init (UbuntuMenuItemFactoryInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (IdoMenuItemFactory, ido_menu_item_factory, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (UBUNTU_TYPE_MENU_ITEM_FACTORY, ido_menu_item_factory_interface_init)
+ g_io_extension_point_implement (UBUNTU_MENU_ITEM_FACTORY_EXTENSION_POINT_NAME,
+ g_define_type_id, "ido", 0);)
+
+static GtkMenuItem *
+ido_menu_item_factory_create_menu_item (UbuntuMenuItemFactory *factory,
+ const gchar *type,
+ GMenuItem *menuitem,
+ GActionGroup *actions)
+{
+ GtkMenuItem *item = NULL;
+
+ if (g_str_equal (type, "indicator.user-menu-item"))
+ item = ido_user_menu_item_new_from_model (menuitem, actions);
+
+ return item;
+}
+
+static void
+ido_menu_item_factory_class_init (IdoMenuItemFactoryClass *class)
+{
+}
+
+static void
+ido_menu_item_factory_interface_init (UbuntuMenuItemFactoryInterface *iface)
+{
+ iface->create_menu_item = ido_menu_item_factory_create_menu_item;
+}
+
+static void
+ido_menu_item_factory_init (IdoMenuItemFactory *factory)
+{
+}
diff --git a/src/idousermenuitem.c b/src/idousermenuitem.c
new file mode 100644
index 0000000..4c6e81d
--- /dev/null
+++ b/src/idousermenuitem.c
@@ -0,0 +1,457 @@
+/*
+Copyright 2011 Canonical Ltd.
+
+Authors:
+ Conor Curran <conor.curran@canonical.com>
+ Mirco Müller <mirco.mueller@canonical.com>
+ Charles Kerr <charles.kerr@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/>.
+*/
+
+#ifdef HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+#include <gtk/gtk.h>
+
+#include "idousermenuitem.h"
+#include "idoactionhelper.h"
+
+#define FALLBACK_ICON_NAME "avatar-default"
+
+enum
+{
+ PROP_0,
+ PROP_LABEL,
+ PROP_ICON_FILENAME,
+ PROP_IS_LOGGED_IN,
+ PROP_IS_CURRENT_USER,
+ PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+struct _IdoUserMenuItemPrivate
+{
+ GtkWidget* user_image;
+ GtkWidget* user_name;
+ GtkWidget* container;
+ GtkWidget* tick_icon;
+ gboolean is_logged_in;
+ gboolean is_current_user;
+ gchar * label;
+ gchar * icon_filename;
+};
+
+G_DEFINE_TYPE (IdoUserMenuItem, ido_user_menu_item, GTK_TYPE_MENU_ITEM);
+
+/* Prototypes */
+static gboolean ido_user_menu_item_primitive_draw_cb_gtk_3 (GtkWidget * image,
+ cairo_t * cr,
+ gpointer gself);
+
+
+static void
+my_get_property (GObject * o,
+ guint property_id,
+ GValue * value,
+ GParamSpec * pspec)
+{
+ IdoUserMenuItem * self = IDO_USER_MENU_ITEM (o);
+
+ switch (property_id)
+ {
+ case PROP_LABEL:
+ g_value_set_string (value, self->priv->label);
+ break;
+
+ case PROP_ICON_FILENAME:
+ g_value_set_string (value, self->priv->icon_filename);
+ break;
+
+ case PROP_IS_LOGGED_IN:
+ g_value_set_boolean (value, self->priv->is_logged_in);
+ break;
+
+ case PROP_IS_CURRENT_USER:
+ g_value_set_boolean (value, self->priv->is_current_user);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec);
+ break;
+ }
+}
+
+static void
+my_set_property (GObject * o,
+ guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ IdoUserMenuItem * self = IDO_USER_MENU_ITEM (o);
+
+ switch (property_id)
+ {
+ case PROP_LABEL:
+ ido_user_menu_item_set_label (self, g_value_get_string (value));
+ break;
+
+ case PROP_ICON_FILENAME:
+ ido_user_menu_item_set_icon (self, g_value_get_string (value));
+ break;
+
+ case PROP_IS_LOGGED_IN:
+ ido_user_menu_item_set_logged_in (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_IS_CURRENT_USER:
+ self->priv->is_current_user = g_value_get_boolean (value);
+ gtk_widget_queue_draw (GTK_WIDGET(self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec);
+ break;
+ }
+}
+
+static void
+my_dispose (GObject *object)
+{
+ G_OBJECT_CLASS (ido_user_menu_item_parent_class)->dispose (object);
+}
+
+static void
+my_finalize (GObject *object)
+{
+ IdoUserMenuItem * self = IDO_USER_MENU_ITEM (object);
+
+ g_free (self->priv->label);
+ g_free (self->priv->icon_filename);
+
+ G_OBJECT_CLASS (ido_user_menu_item_parent_class)->finalize (object);
+}
+
+static void
+ido_user_menu_item_class_init (IdoUserMenuItemClass *klass)
+{
+ GParamFlags prop_flags;
+ GObjectClass * gobject_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (IdoUserMenuItemPrivate));
+
+ gobject_class->get_property = my_get_property;
+ gobject_class->set_property = my_set_property;
+ gobject_class->dispose = my_dispose;
+ gobject_class->finalize = my_finalize;
+
+ prop_flags = G_PARAM_CONSTRUCT
+ | G_PARAM_READWRITE
+ | G_PARAM_STATIC_STRINGS;
+
+ properties[PROP_LABEL] = g_param_spec_string ("label",
+ "The user's name",
+ "The name to display",
+ "J. Random User",
+ prop_flags);
+
+ properties[PROP_ICON_FILENAME] = g_param_spec_string ("icon-filename",
+ "The icon's filename",
+ "The icon to display",
+ NULL,
+ prop_flags);
+
+ properties[PROP_IS_LOGGED_IN] = g_param_spec_boolean ("is-logged-in",
+ "is logged in",
+ "is user logged in?",
+ FALSE,
+ prop_flags);
+
+ properties[PROP_IS_CURRENT_USER] = g_param_spec_boolean ("is-current-user",
+ "is current user",
+ "is user current?",
+ FALSE,
+ prop_flags);
+
+ g_object_class_install_properties (gobject_class, PROP_LAST, properties);
+}
+
+static void
+ido_user_menu_item_init (IdoUserMenuItem *self)
+{
+ IdoUserMenuItemPrivate * priv;
+
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ IDO_USER_MENU_ITEM_TYPE,
+ IdoUserMenuItemPrivate);
+
+ priv = self->priv;
+
+ // Create the UI elements.
+ priv->user_image = gtk_image_new ();
+ gtk_misc_set_alignment(GTK_MISC(priv->user_image), 0.0, 0.0);
+
+ priv->user_name = gtk_label_new (NULL);
+
+ priv->container = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
+
+ priv->tick_icon = gtk_image_new_from_icon_name ("account-logged-in",
+ GTK_ICON_SIZE_MENU);
+ gtk_misc_set_alignment(GTK_MISC(priv->tick_icon), 1.0, 0.5);
+
+ // Pack it together
+ gtk_box_pack_start (GTK_BOX (priv->container),
+ priv->user_image,
+ FALSE,
+ FALSE,
+ 0);
+ gtk_box_pack_start (GTK_BOX (priv->container),
+ priv->user_name,
+ FALSE,
+ FALSE,
+ 3);
+ gtk_box_pack_end (GTK_BOX(priv->container),
+ priv->tick_icon,
+ FALSE,
+ FALSE, 5);
+
+ gtk_widget_show_all (priv->container);
+ gtk_container_add (GTK_CONTAINER (self), priv->container);
+ gtk_widget_show_all (priv->tick_icon);
+ gtk_widget_set_no_show_all (priv->tick_icon, TRUE);
+ gtk_widget_hide (priv->tick_icon);
+
+
+ // Fetch the drawing context.
+ g_signal_connect_after (GTK_WIDGET(self), "draw",
+ G_CALLBACK(ido_user_menu_item_primitive_draw_cb_gtk_3),
+ GTK_WIDGET(self));
+}
+
+
+/*****************************************************************/
+
+static gboolean
+ido_user_menu_item_primitive_draw_cb_gtk_3 (GtkWidget * widget,
+ cairo_t * cr,
+ gpointer user_data)
+{
+ IdoUserMenuItemPrivate * priv;
+
+ g_return_val_if_fail(IS_IDO_USER_MENU_ITEM(user_data), FALSE);
+
+ priv = IDO_USER_MENU_ITEM(user_data)->priv;
+
+ /* Draw dot only when user is the current user. */
+ if (priv->is_current_user)
+ {
+ GtkStyleContext * style_context;
+ GtkStateFlags state_flags;
+ GdkRGBA color;
+ gdouble x, y;
+
+ /* get the foreground color */
+ style_context = gtk_widget_get_style_context (widget);
+ state_flags = gtk_widget_get_state_flags (widget);
+ gtk_style_context_get_color (style_context, state_flags, &color);
+
+ GtkAllocation allocation;
+ gtk_widget_get_allocation (widget, &allocation);
+ x = allocation.x + 13;
+ y = allocation.height / 2;
+
+ cairo_arc (cr, x, y, 3.0, 0.0, 2 * G_PI);
+
+ gdk_cairo_set_source_rgba (cr, &color);
+
+ cairo_fill (cr);
+ }
+
+ return FALSE;
+}
+
+/***
+**** PUBLIC API
+***/
+
+void
+ido_user_menu_item_set_icon (IdoUserMenuItem * self, const char * icon_filename)
+{
+ gboolean updated = FALSE;
+ IdoUserMenuItemPrivate * p = self->priv;
+ GtkImage * image = GTK_IMAGE (p->user_image);
+
+ /* make a private copy of the icon name */
+ g_free (p->icon_filename);
+ self->priv->icon_filename = g_strdup (icon_filename);
+
+ /* now try to use it */
+ if (icon_filename && *icon_filename)
+ {
+ int width = 18; /* arbitrary default values */
+ int height = 18;
+ GError * err = NULL;
+ GdkPixbuf * pixbuf = NULL;
+
+ /* load the image */
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height);
+ pixbuf = gdk_pixbuf_new_from_file_at_size (icon_filename,
+ width, height, &err);
+ if (err == NULL)
+ {
+ gtk_image_set_from_pixbuf (image, pixbuf);
+ g_object_unref (pixbuf);
+ updated = TRUE;
+ }
+ else
+ {
+ g_warning ("Couldn't load the image \"%s\": %s",
+ icon_filename, err->message);
+ g_clear_error (&err);
+ }
+ }
+
+ /* as a fallback, use the default user icon */
+ if (!updated)
+ {
+ gtk_image_set_from_icon_name (image,
+ FALLBACK_ICON_NAME,
+ GTK_ICON_SIZE_MENU);
+ }
+}
+
+void
+ido_user_menu_item_set_logged_in (IdoUserMenuItem * self, gboolean is_logged_in)
+{
+ gtk_widget_set_visible (self->priv->tick_icon, is_logged_in);
+}
+
+void
+ido_user_menu_item_set_current_user (IdoUserMenuItem * self, gboolean is_current_user)
+{
+ self->priv->is_current_user = is_current_user;
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+void
+ido_user_menu_item_set_label (IdoUserMenuItem * self, const char * label)
+{
+ gtk_label_set_label (GTK_LABEL(self->priv->user_name), label);
+}
+
+GtkWidget*
+ido_user_menu_item_new (void)
+{
+ return GTK_WIDGET (g_object_new (IDO_USER_MENU_ITEM_TYPE, NULL));
+}
+
+/**
+ * user_menu_item_state_changed:
+ *
+ * Updates an IdoUserMenuItem from @state. The state contains a
+ * dictionary with keys 'active-user' (for the user that the current
+ * session belongs too) and 'logged-in-users' (a list of all currently
+ * logged in users).
+ */
+static void
+user_menu_item_state_changed (IdoActionHelper *helper,
+ GVariant *state,
+ gpointer user_data)
+{
+ IdoUserMenuItem *item;
+ GVariant *target;
+ GVariant *v;
+
+ item = IDO_USER_MENU_ITEM (ido_action_helper_get_widget (helper));
+
+ ido_user_menu_item_set_current_user (item, FALSE);
+ ido_user_menu_item_set_logged_in (item, FALSE);
+
+ target = ido_action_helper_get_action_target (helper);
+ g_return_if_fail (g_variant_is_of_type (target, G_VARIANT_TYPE_STRING));
+
+ if ((v = g_variant_lookup_value (state, "active-user", G_VARIANT_TYPE_STRING)))
+ {
+ if (g_variant_equal (v, target))
+ ido_user_menu_item_set_current_user (item, TRUE);
+
+ g_variant_unref (v);
+ }
+
+ if ((v = g_variant_lookup_value (state, "logged-in-users", G_VARIANT_TYPE_STRING_ARRAY)))
+ {
+ GVariantIter it;
+ GVariant *user;
+
+ g_variant_iter_init (&it, v);
+ while ((user = g_variant_iter_next_value (&it)))
+ {
+ if (g_variant_equal (user, target))
+ ido_user_menu_item_set_logged_in (item, TRUE);
+ g_variant_unref (user);
+ }
+
+ g_variant_unref (v);
+ }
+}
+
+/**
+ * ido_user_menu_item_new_from_model:
+ *
+ * Creates an #IdoUserMenuItem. If @menuitem contains an action, the
+ * widget is bound to that action in @actions.
+ *
+ * Returns: (transfer full): a new #IdoUserMenuItem
+ */
+GtkMenuItem *
+ido_user_menu_item_new_from_model (GMenuItem *menuitem,
+ GActionGroup *actions)
+{
+ IdoUserMenuItem *item;
+ gchar *label;
+ gchar *action;
+
+ item = IDO_USER_MENU_ITEM (ido_user_menu_item_new ());
+
+ if (g_menu_item_get_attribute (menuitem, "label", "s", &label))
+ {
+ ido_user_menu_item_set_label (item, label);
+ g_free (label);
+ }
+
+ if (g_menu_item_get_attribute (menuitem, "action", "s", &action))
+ {
+ IdoActionHelper *helper;
+ GVariant *target;
+
+ target = g_menu_item_get_attribute_value (menuitem, "target", G_VARIANT_TYPE_ANY);
+
+ helper = ido_action_helper_new (GTK_WIDGET (item), actions, action, target);
+ g_signal_connect (helper, "action-state-changed",
+ G_CALLBACK (user_menu_item_state_changed), NULL);
+
+ g_signal_connect_object (item, "activate",
+ G_CALLBACK (ido_action_helper_activate),
+ helper, G_CONNECT_SWAPPED);
+ g_signal_connect_swapped (item, "destroy", G_CALLBACK (g_object_unref), helper);
+
+ if (target)
+ g_variant_unref (target);
+ g_free (action);
+ }
+
+ return GTK_MENU_ITEM (item);
+}
+
diff --git a/src/idousermenuitem.h b/src/idousermenuitem.h
new file mode 100644
index 0000000..d51f6c7
--- /dev/null
+++ b/src/idousermenuitem.h
@@ -0,0 +1,69 @@
+/*
+Copyright 2011 Canonical Ltd.
+
+Authors:
+ Conor Curran <conor.curran@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/>.
+*/
+#ifndef __IDO_USER_MENU_ITEM_H__
+#define __IDO_USER_MENU_ITEM_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDO_USER_MENU_ITEM_TYPE (ido_user_menu_item_get_type ())
+#define IDO_USER_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_USER_MENU_ITEM_TYPE, IdoUserMenuItem))
+#define IDO_USER_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IDO_USER_MENU_ITEM_TYPE, IdoUserMenuItemClass))
+#define IS_IDO_USER_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_USER_MENU_ITEM_TYPE))
+#define IS_IDO_USER_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IDO_USER_MENU_ITEM_TYPE))
+#define IDO_USER_MENU_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IDO_USER_MENU_ITEM_TYPE, IdoUserMenuItemClass))
+
+typedef struct _IdoUserMenuItem IdoUserMenuItem;
+typedef struct _IdoUserMenuItemClass IdoUserMenuItemClass;
+typedef struct _IdoUserMenuItemPrivate IdoUserMenuItemPrivate;
+
+/* property keys */
+#define IDO_USER_MENU_ITEM_PROP_LABEL "label"
+#define IDO_USER_MENU_ITEM_PROP_ICON_FILENAME "icon-filename"
+#define IDO_USER_MENU_ITEM_PROP_IS_LOGGED_IN "is-logged-in"
+#define IDO_USER_MENU_ITEM_PROP_IS_CURRENT_USER "is-current-user"
+
+struct _IdoUserMenuItemClass
+{
+ GtkMenuItemClass parent_class;
+};
+
+struct _IdoUserMenuItem
+{
+ /*< private >*/
+ GtkMenuItem parent;
+ IdoUserMenuItemPrivate * priv;
+};
+
+GType ido_user_menu_item_get_type (void) G_GNUC_CONST;
+
+GtkWidget* ido_user_menu_item_new(void);
+
+void ido_user_menu_item_set_icon (IdoUserMenuItem * self, const char * icon_name);
+void ido_user_menu_item_set_logged_in (IdoUserMenuItem * self, gboolean is_logged_in);
+void ido_user_menu_item_set_current_user (IdoUserMenuItem * self, gboolean is_current_user);
+void ido_user_menu_item_set_label (IdoUserMenuItem * self, const char * label);
+
+GtkMenuItem * ido_user_menu_item_new_from_model (GMenuItem *menuitem,
+ GActionGroup *actions);
+
+G_END_DECLS
+
+#endif
diff --git a/src/libido.c b/src/libido.c
new file mode 100644
index 0000000..0c90213
--- /dev/null
+++ b/src/libido.c
@@ -0,0 +1,36 @@
+/*
+ * 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 <gio/gio.h>
+
+/**
+ * ido_init:
+ *
+ * Initializes ido. It has to be called after gtk_init(), but before any
+ * other calls into ido are made.
+ */
+void
+ido_init (void)
+{
+ GType ido_menu_item_factory_get_type (void);
+
+ /* make sure this extension point is registered so that gtk calls it
+ * when finding custom menu items */
+ g_type_ensure (ido_menu_item_factory_get_type ());
+}
diff --git a/src/libido.h b/src/libido.h
index 4c4f68b..43a8168 100644
--- a/src/libido.h
+++ b/src/libido.h
@@ -31,4 +31,6 @@
#include <libido/idoentrymenuitem.h>
#include <libido/idomessagedialog.h>
+void ido_init (void);
+
#endif /* __IDO__ */