aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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__ */