diff options
-rw-r--r-- | configure.ac | 10 | ||||
-rw-r--r-- | debian/changelog | 6 | ||||
-rw-r--r-- | debian/control | 4 | ||||
-rw-r--r-- | debian/libido3-0.1-0.symbols | 14 | ||||
-rw-r--r-- | example/menus.c | 45 | ||||
-rw-r--r-- | src/Makefile.am | 10 | ||||
-rw-r--r-- | src/idoactionhelper.c | 403 | ||||
-rw-r--r-- | src/idoactionhelper.h | 44 | ||||
-rw-r--r-- | src/idomenuitemfactory.c | 68 | ||||
-rw-r--r-- | src/idousermenuitem.c | 457 | ||||
-rw-r--r-- | src/idousermenuitem.h | 69 | ||||
-rw-r--r-- | src/libido.c | 36 | ||||
-rw-r--r-- | src/libido.h | 2 |
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__ */ |