aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am71
-rw-r--r--src/idoactionhelper.c428
-rw-r--r--src/idoactionhelper.h47
-rw-r--r--src/idoappointmentmenuitem.c483
-rw-r--r--src/idoappointmentmenuitem.h79
-rw-r--r--src/idocalendarmenuitem.c255
-rw-r--r--src/idocalendarmenuitem.h7
-rw-r--r--src/idoentrymenuitem.c22
-rw-r--r--src/idolocationmenuitem.c477
-rw-r--r--src/idolocationmenuitem.h73
-rw-r--r--src/idomediaplayermenuitem.c376
-rw-r--r--src/idomediaplayermenuitem.h42
-rw-r--r--src/idomenuitemfactory.c92
-rw-r--r--src/idomessagedialog.c21
-rw-r--r--src/idomessagedialog.h2
-rw-r--r--src/idoplaybackmenuitem.c1680
-rw-r--r--src/idoplaybackmenuitem.h37
-rw-r--r--src/idorange.c2
-rw-r--r--src/idoscalemenuitem.c217
-rw-r--r--src/idoscalemenuitem.h7
-rw-r--r--src/idoswitchmenuitem.c19
-rw-r--r--src/idotimeline.c95
-rw-r--r--src/idousermenuitem.c468
-rw-r--r--src/idousermenuitem.h70
-rw-r--r--src/libido.c36
-rw-r--r--src/libido.h2
26 files changed, 5057 insertions, 51 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 017874f..2791964 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,3 +1,5 @@
+CLEANFILES =
+
VER=3
lib_LTLIBRARIES = libido3-0.1.la
@@ -15,8 +17,14 @@ sources_h = \
idorange.h \
idoscalemenuitem.h \
idoswitchmenuitem.h \
+ idousermenuitem.h \
+ idoappointmentmenuitem.h \
+ idolocationmenuitem.h \
idotimeline.h \
- libido.h
+ libido.h \
+ idoactionhelper.h \
+ idomediaplayermenuitem.h \
+ idoplaybackmenuitem.h
EXTRA_DIST = \
ido.list \
@@ -57,6 +65,7 @@ AM_CFLAGS = \
$(COVERAGE_CFLAGS)
libido_0_1_la_SOURCES = \
+ libido.c \
idotypebuiltins.c \
idocalendarmenuitem.c \
idoentrymenuitem.c \
@@ -64,7 +73,14 @@ libido_0_1_la_SOURCES = \
idorange.c \
idoscalemenuitem.c \
idoswitchmenuitem.c \
- idotimeline.c
+ idotimeline.c \
+ idomenuitemfactory.c \
+ idoactionhelper.c \
+ idousermenuitem.c \
+ idomediaplayermenuitem.c \
+ idoplaybackmenuitem.c \
+ idoappointmentmenuitem.c \
+ idolocationmenuitem.c
libido3_0_1_la_SOURCES = $(libido_0_1_la_SOURCES)
@@ -94,3 +110,54 @@ DISTCLEANFILES = \
idotypebuiltins.h \
idotypebuiltins.c
+-include $(INTROSPECTION_MAKEFILE)
+INTROSPECTION_GIRS =
+INTROSPECTION_SCANNER_ARGS = \
+ --symbol-prefix=ido \
+ --warn-all \
+ --identifier-prefix=Ido
+
+if HAVE_INTROSPECTION
+
+Ido3-0.1.gir: libido3-0.1.la
+Ido3_0_1_gir_INCLUDES = Gtk-3.0
+Ido3_0_1_gir_CFLAGS =
+Ido3_0_1_gir_LIBS = libido3-0.1.la
+Ido3_0_1_gir_FILES = \
+ idocalendarmenuitem.h \
+ idoentrymenuitem.h \
+ idomessagedialog.h \
+ idorange.h \
+ idoscalemenuitem.h \
+ idoswitchmenuitem.h \
+ idotimeline.h \
+ $(libido_0_1_la_SOURCES)
+Ido3_0_1_gir_NAMESPACE = Ido3
+Ido3_0_1_gir_VERSION = 0.1
+Ido3_0_1_gir_SCANNER_FLAGS = $(INTROSPECTION_SCANNER_ARGS)
+
+INTROSPECTION_GIRS += Ido3-0.1.gir
+
+girdir = $(datadir)/gir-1.0
+gir_DATA = $(INTROSPECTION_GIRS)
+
+typelibdir = $(libdir)/girepository-1.0
+typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib)
+
+CLEANFILES += $(gir_DATA) $(typelib_DATA)
+
+endif
+
+if HAVE_INTROSPECTION
+
+vapidir = $(datadir)/vala/vapi
+vapi_DATA = Ido3-0.1.vapi
+
+Ido3-0.1.vapi: Ido3-0.1.gir
+ $(VALA_API_GEN) --library=Ido3-0.1 \
+ --pkg gtk+-3.0 \
+ $<
+
+CLEANFILES += $(vapi_DATA)
+
+endif
diff --git a/src/idoactionhelper.c b/src/idoactionhelper.c
new file mode 100644
index 0000000..f0e300b
--- /dev/null
+++ b/src/idoactionhelper.c
@@ -0,0 +1,428 @@
+/*
+* 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);
+}
+
+/**
+ * ido_action_helper_change_action_state:
+ * @helper: an #IdoActionHelper
+ * @state: the proposed new state of the action
+ *
+ * Requests changing the state of the action that is associated with
+ * @helper to @state.
+ *
+ * If @state is floating, it is consumed.
+ */
+void
+ido_action_helper_change_action_state (IdoActionHelper *helper,
+ GVariant *state)
+{
+ g_return_if_fail (IDO_IS_ACTION_HELPER (helper));
+ g_return_if_fail (state != NULL);
+
+ g_variant_ref_sink (state);
+
+ if (helper->actions && helper->action_name)
+ g_action_group_change_action_state (helper->actions, helper->action_name, state);
+
+ g_variant_unref (state);
+}
diff --git a/src/idoactionhelper.h b/src/idoactionhelper.h
new file mode 100644
index 0000000..27dafb7
--- /dev/null
+++ b/src/idoactionhelper.h
@@ -0,0 +1,47 @@
+/*
+* 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);
+
+void ido_action_helper_change_action_state (IdoActionHelper *helper,
+ GVariant *state);
+
+#endif
diff --git a/src/idoappointmentmenuitem.c b/src/idoappointmentmenuitem.c
new file mode 100644
index 0000000..2ac518a
--- /dev/null
+++ b/src/idoappointmentmenuitem.c
@@ -0,0 +1,483 @@
+/**
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ * Ted Gould <ted@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 <string.h> /* strstr() */
+
+#include <gtk/gtk.h>
+
+#include "idoactionhelper.h"
+#include "idoappointmentmenuitem.h"
+
+enum
+{
+ PROP_0,
+ PROP_COLOR,
+ PROP_SUMMARY,
+ PROP_TIME,
+ PROP_FORMAT,
+ PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+struct _IdoAppointmentMenuItemPrivate
+{
+ char * summary;
+ char * format;
+ char * color_string;
+ GDateTime * date_time;
+
+ GtkWidget * color_image;
+ GtkWidget * summary_label;
+ GtkWidget * timestamp_label;
+};
+
+typedef IdoAppointmentMenuItemPrivate priv_t;
+
+G_DEFINE_TYPE (IdoAppointmentMenuItem,
+ ido_appointment_menu_item,
+ GTK_TYPE_MENU_ITEM);
+
+/***
+**** GObject Virtual Functions
+***/
+
+static void
+my_get_property (GObject * o,
+ guint property_id,
+ GValue * v,
+ GParamSpec * pspec)
+{
+ IdoAppointmentMenuItem * self = IDO_APPOINTMENT_MENU_ITEM (o);
+ priv_t * p = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_COLOR:
+ g_value_set_string (v, p->color_string);
+ break;
+
+ case PROP_SUMMARY:
+ g_value_set_string (v, p->summary);
+ break;
+
+ case PROP_FORMAT:
+ g_value_set_string (v, p->format);
+ break;
+
+ case PROP_TIME:
+ g_value_set_uint64 (v, g_date_time_to_unix (p->date_time));
+ 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 * v,
+ GParamSpec * pspec)
+{
+ IdoAppointmentMenuItem * self = IDO_APPOINTMENT_MENU_ITEM (o);
+
+ switch (property_id)
+ {
+ case PROP_COLOR:
+ ido_appointment_menu_item_set_color (self, g_value_get_string (v));
+ break;
+
+ case PROP_SUMMARY:
+ ido_appointment_menu_item_set_summary (self, g_value_get_string (v));
+ break;
+
+ case PROP_FORMAT:
+ ido_appointment_menu_item_set_format (self, g_value_get_string (v));
+ break;
+
+ case PROP_TIME:
+ ido_appointment_menu_item_set_time (self, g_value_get_int64 (v));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec);
+ break;
+ }
+}
+
+static void
+my_dispose (GObject * object)
+{
+ IdoAppointmentMenuItem * self = IDO_APPOINTMENT_MENU_ITEM (object);
+ priv_t * p = self->priv;
+
+ g_clear_pointer (&p->date_time, g_date_time_unref);
+
+ G_OBJECT_CLASS (ido_appointment_menu_item_parent_class)->dispose (object);
+}
+
+static void
+my_finalize (GObject * object)
+{
+ IdoAppointmentMenuItem * self = IDO_APPOINTMENT_MENU_ITEM (object);
+ priv_t * p = self->priv;
+
+ g_free (p->color_string);
+ g_free (p->summary);
+ g_free (p->format);
+
+ G_OBJECT_CLASS (ido_appointment_menu_item_parent_class)->finalize (object);
+}
+
+/***
+**** Instantiation
+***/
+
+static void
+ido_appointment_menu_item_class_init (IdoAppointmentMenuItemClass *klass)
+{
+ GParamFlags prop_flags;
+ GObjectClass * gobject_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (IdoAppointmentMenuItemPrivate));
+
+ 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_COLOR] = g_param_spec_string (
+ "color",
+ "Color",
+ "Color coding for the appointment's type",
+ "White",
+ prop_flags);
+
+ properties[PROP_SUMMARY] = g_param_spec_string (
+ "summary",
+ "Summary",
+ "Brief description of the appointment",
+ "",
+ prop_flags);
+
+ properties[PROP_TIME] = g_param_spec_int64 (
+ "time",
+ "Time",
+ "unix time_t specifying when the appointment begins",
+ 0, G_MAXINT64, 0,
+ prop_flags);
+
+ properties[PROP_FORMAT] = g_param_spec_string (
+ "format",
+ "strftime format",
+ "strftime-style format string for the timestamp",
+ "%F %T",
+ prop_flags);
+
+ g_object_class_install_properties (gobject_class, PROP_LAST, properties);
+}
+
+static void
+ido_appointment_menu_item_init (IdoAppointmentMenuItem *self)
+{
+ priv_t * p;
+ GtkBox * box;
+ GtkWidget * w;
+
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ IDO_APPOINTMENT_MENU_ITEM_TYPE,
+ IdoAppointmentMenuItemPrivate);
+
+ p = self->priv;
+
+ p->color_image = gtk_image_new ();
+ p->summary_label = gtk_label_new (NULL);
+ p->timestamp_label = gtk_label_new (NULL);
+ w = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
+
+ gtk_misc_set_alignment (GTK_MISC(p->timestamp_label), 1.0, 0.5);
+ box = GTK_BOX (w);
+ gtk_box_pack_start (box, p->color_image, FALSE, FALSE, 2);
+ gtk_box_pack_start (box, p->summary_label, FALSE, FALSE, 2);
+ gtk_box_pack_end (box, p->timestamp_label, FALSE, FALSE, 5);
+
+ gtk_widget_show_all (w);
+ gtk_container_add (GTK_CONTAINER (self), w);
+}
+
+/***
+****
+***/
+
+/* creates a menu-sized pixbuf filled with specified color */
+static GdkPixbuf *
+create_color_icon_pixbuf (const char * color_spec)
+{
+ static int width = -1;
+ static int height = -1;
+ GdkPixbuf * pixbuf = NULL;
+
+ if (width == -1)
+ {
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height);
+ width = CLAMP (width, 10, 30);
+ height = CLAMP (height, 10, 30);
+ }
+
+ if (color_spec && *color_spec)
+ {
+ cairo_surface_t * surface;
+ cairo_t * cr;
+ GdkRGBA rgba;
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+ cr = cairo_create (surface);
+
+ if (gdk_rgba_parse (&rgba, color_spec))
+ gdk_cairo_set_source_rgba (cr, &rgba);
+
+ cairo_paint (cr);
+ cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
+ cairo_set_line_width (cr, 1);
+ cairo_rectangle (cr, 0.5, 0.5, width-1, height-1);
+ cairo_stroke (cr);
+
+ pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, width, height);
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+ }
+
+ return pixbuf;
+}
+
+static void
+update_timestamp_label (IdoAppointmentMenuItem * self)
+{
+ char * str;
+ priv_t * p = self->priv;
+
+ if (p->date_time && p->format)
+ str = g_date_time_format (p->date_time, p->format);
+ else
+ str = NULL;
+
+ gtk_label_set_text (GTK_LABEL(p->timestamp_label), str);
+ g_free (str);
+}
+
+/***
+**** Public API
+***/
+
+/* create a new IdoAppointmentMenuItem */
+GtkWidget *
+ido_appointment_menu_item_new (void)
+{
+ return GTK_WIDGET (g_object_new (IDO_APPOINTMENT_MENU_ITEM_TYPE, NULL));
+}
+
+/**
+ * ido_appointment_menu_item_set_color:
+ * @color: parseable color string
+ *
+ * When this is set, the menuitem will include an icon with this color.
+ *
+ * These colors can be set in the end user's calendar app as a quick visual cue
+ * to show what kind of appointment this is.
+ */
+void
+ido_appointment_menu_item_set_color (IdoAppointmentMenuItem * self,
+ const char * color_string)
+{
+ priv_t * p;
+ GdkPixbuf * pixbuf;
+
+ g_return_if_fail (IDO_IS_APPOINTMENT_MENU_ITEM (self));
+ p = self->priv;
+
+ g_free (p->color_string);
+ p->color_string = g_strdup (color_string);
+ pixbuf = create_color_icon_pixbuf (p->color_string);
+ gtk_image_set_from_pixbuf (GTK_IMAGE(p->color_image), pixbuf);
+ g_object_unref (G_OBJECT(pixbuf));
+}
+
+/**
+ * ido_appointment_menu_item_set_summary:
+ * @summary: short string describing the appointment.
+ *
+ * Set the menuitem's primary label with a short description of the appointment
+ */
+void
+ido_appointment_menu_item_set_summary (IdoAppointmentMenuItem * self,
+ const char * summary)
+{
+ priv_t * p;
+
+ g_return_if_fail (IDO_IS_APPOINTMENT_MENU_ITEM (self));
+ p = self->priv;
+
+ g_free (p->summary);
+ p->summary = g_strdup (summary);
+ gtk_label_set_text (GTK_LABEL(p->summary_label), p->summary);
+}
+
+/**
+ * ido_appointment_menu_item_set_time:
+ * @time: the time to be rendered in the appointment's timestamp label.
+ *
+ * Set the time that will be displayed in the menuitem's
+ * right-justified timestamp label
+ */
+void
+ido_appointment_menu_item_set_time (IdoAppointmentMenuItem * self,
+ time_t time)
+{
+ priv_t * p;
+
+ g_return_if_fail (IDO_IS_APPOINTMENT_MENU_ITEM (self));
+ p = self->priv;
+
+ g_clear_pointer (&p->date_time, g_date_time_unref);
+ p->date_time = g_date_time_new_from_unix_local (time);
+ update_timestamp_label (self);
+}
+
+/**
+ * ido_appointment_menu_item_set_format:
+ * @format: the format string used when showing the appointment's time
+ *
+ * Set the format string for rendering the appointment's time
+ * in its right-justified secondary label.
+ *
+ * See strfrtime(3) for more information on the format string.
+ */
+void
+ido_appointment_menu_item_set_format (IdoAppointmentMenuItem * self,
+ const char * strftime_fmt)
+{
+ priv_t * p;
+
+ g_return_if_fail (IDO_IS_APPOINTMENT_MENU_ITEM (self));
+ p = self->priv;
+
+ g_free (p->format);
+ p->format = g_strdup (strftime_fmt);
+ update_timestamp_label (self);
+}
+
+/**
+ * ido_appointment_menu_item_new_from_model:
+ * @menu_item: the corresponding menuitem
+ * @actions: action group to tell when this GtkMenuItem is activated
+ *
+ * Creates a new IdoAppointmentMenuItem with properties initialized from
+ * the menuitem's attributes.
+ *
+ * If the menuitem's 'action' attribute is set, trigger that action
+ * in @actions when this IdoAppointmentMenuItem is activated.
+ */
+GtkMenuItem *
+ido_appointment_menu_item_new_from_model (GMenuItem * menu_item,
+ GActionGroup * actions)
+{
+ guint i;
+ guint n;
+ gint64 i64;
+ gchar * str;
+ IdoAppointmentMenuItem * ido_appointment;
+ GParameter parameters[8];
+
+ /* create the ido_appointment */
+
+ n = 0;
+
+ if (g_menu_item_get_attribute (menu_item, "label", "s", &str))
+ {
+ GParameter p = { "summary", G_VALUE_INIT };
+ g_value_init (&p.value, G_TYPE_STRING);
+ g_value_take_string (&p.value, str);
+ parameters[n++] = p;
+ }
+
+ if (g_menu_item_get_attribute (menu_item, "x-canonical-color", "s", &str))
+ {
+ GParameter p = { "color", G_VALUE_INIT };
+ g_value_init (&p.value, G_TYPE_STRING);
+ g_value_take_string (&p.value, str);
+ parameters[n++] = p;
+ }
+
+ if (g_menu_item_get_attribute (menu_item, "x-canonical-time-format", "s", &str))
+ {
+ GParameter p = { "format", G_VALUE_INIT };
+ g_value_init (&p.value, G_TYPE_STRING);
+ g_value_take_string (&p.value, str);
+ parameters[n++] = p;
+ }
+
+ if (g_menu_item_get_attribute (menu_item, "x-canonical-time", "x", &i64))
+ {
+ GParameter p = { "time", G_VALUE_INIT };
+ g_value_init (&p.value, G_TYPE_INT64);
+ g_value_set_int64 (&p.value, i64);
+ parameters[n++] = p;
+ }
+
+ g_assert (n <= G_N_ELEMENTS (parameters));
+ ido_appointment = g_object_newv (IDO_APPOINTMENT_MENU_ITEM_TYPE, n, parameters);
+
+ for (i=0; i<n; i++)
+ g_value_unset (&parameters[i].value);
+
+
+ /* add an ActionHelper */
+
+ if (g_menu_item_get_attribute (menu_item, "action", "s", &str))
+ {
+ GVariant * target;
+ IdoActionHelper * helper;
+
+ target = g_menu_item_get_attribute_value (menu_item, "target",
+ G_VARIANT_TYPE_ANY);
+ helper = ido_action_helper_new (GTK_WIDGET(ido_appointment), actions,
+ str, target);
+ g_signal_connect_swapped (ido_appointment, "activate",
+ G_CALLBACK (ido_action_helper_activate), helper);
+ g_signal_connect_swapped (ido_appointment, "destroy",
+ G_CALLBACK (g_object_unref), helper);
+
+ g_clear_pointer (&target, g_variant_unref);
+ g_free (str);
+ }
+
+ return GTK_MENU_ITEM (ido_appointment);
+}
diff --git a/src/idoappointmentmenuitem.h b/src/idoappointmentmenuitem.h
new file mode 100644
index 0000000..3a8c853
--- /dev/null
+++ b/src/idoappointmentmenuitem.h
@@ -0,0 +1,79 @@
+/**
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Authors:
+ * 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/>.
+ */
+
+#ifndef __IDO_APPOINTMENT_MENU_ITEM_H__
+#define __IDO_APPOINTMENT_MENU_ITEM_H__
+
+#include <time.h> /* time_t */
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDO_APPOINTMENT_MENU_ITEM_TYPE (ido_appointment_menu_item_get_type ())
+#define IDO_APPOINTMENT_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_APPOINTMENT_MENU_ITEM_TYPE, IdoAppointmentMenuItem))
+#define IDO_IS_APPOINTMENT_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_APPOINTMENT_MENU_ITEM_TYPE))
+
+typedef struct _IdoAppointmentMenuItem IdoAppointmentMenuItem;
+typedef struct _IdoAppointmentMenuItemClass IdoAppointmentMenuItemClass;
+typedef struct _IdoAppointmentMenuItemPrivate IdoAppointmentMenuItemPrivate;
+
+struct _IdoAppointmentMenuItemClass
+{
+ GtkMenuItemClass parent_class;
+};
+
+/**
+ * A menuitem that indicates a appointment.
+ *
+ * It contains a color-coded icon to indicate the appointment type,
+ * a primary label showing the appointment summary,
+ * and a right-justified secondary label telling when the appointment begins.
+ */
+struct _IdoAppointmentMenuItem
+{
+ /*< private >*/
+ GtkMenuItem parent;
+ IdoAppointmentMenuItemPrivate * priv;
+};
+
+
+GType ido_appointment_menu_item_get_type (void) G_GNUC_CONST;
+
+GtkWidget * ido_appointment_menu_item_new (void);
+
+GtkMenuItem * ido_appointment_menu_item_new_from_model (GMenuItem * menuitem,
+ GActionGroup * actions);
+
+void ido_appointment_menu_item_set_summary (IdoAppointmentMenuItem * menuitem,
+ const char * summary);
+
+void ido_appointment_menu_item_set_time (IdoAppointmentMenuItem * menuitem,
+ time_t time);
+
+void ido_appointment_menu_item_set_color (IdoAppointmentMenuItem * menuitem,
+ const char * color_str);
+
+void ido_appointment_menu_item_set_format (IdoAppointmentMenuItem * menuitem,
+ const char * strftime_fmt);
+
+
+G_END_DECLS
+
+#endif
diff --git a/src/idocalendarmenuitem.c b/src/idocalendarmenuitem.c
index baae342..94f4f61 100644
--- a/src/idocalendarmenuitem.c
+++ b/src/idocalendarmenuitem.c
@@ -24,6 +24,7 @@
*/
#include <gdk/gdkkeysyms.h>
+#include "idoactionhelper.h"
#include "idocalendarmenuitem.h"
#include "config.h"
@@ -324,21 +325,44 @@ calendar_day_selected_double_click_cb (GtkWidget *widget,
g_signal_emit_by_name (item, "day-selected-double-click", NULL);
}
-/* Public API */
+/**
+ * ido_calendar_menu_item_new:
+ *
+ * Creates a new #IdoCalendarMenuItem
+ *
+ * Return Value: a new #IdoCalendarMenuItem.
+ **/
GtkWidget *
ido_calendar_menu_item_new (void)
{
return g_object_new (IDO_TYPE_CALENDAR_MENU_ITEM, NULL);
}
+/**
+ * ido_calendar_menu_item_get_calendar:
+ * @menuitem: A #IdoCalendarMenuItem
+ *
+ * Returns the calendar associated with this menu item.
+ *
+ * Return Value: (transfer none): The #GtkCalendar used in this item.
+ */
GtkWidget *
-ido_calendar_menu_item_get_calendar (IdoCalendarMenuItem *item)
+ido_calendar_menu_item_get_calendar (IdoCalendarMenuItem *menuitem)
{
- g_return_val_if_fail (IDO_IS_CALENDAR_MENU_ITEM (item), NULL);
+ g_return_val_if_fail (IDO_IS_CALENDAR_MENU_ITEM (menuitem), NULL);
- return item->priv->calendar;
+ return menuitem->priv->calendar;
}
+/**
+ * ido_calendar_menu_item_mark_day:
+ * @menuitem: A #IdoCalendarMenuItem
+ * @day: the day number to unmark between 1 and 31.
+ *
+ * Places a visual marker on a particular day.
+ *
+ * Return Value: #TRUE
+ */
gboolean
ido_calendar_menu_item_mark_day (IdoCalendarMenuItem *menuitem, guint day)
{
@@ -348,6 +372,15 @@ ido_calendar_menu_item_mark_day (IdoCalendarMenuItem *menuitem, guint day)
return TRUE;
}
+/**
+ * ido_calendar_menu_item_unmark_day:
+ * @menuitem: A #IdoCalendarMenuItem
+ * @day: the day number to unmark between 1 and 31.
+ *
+ * Removes the visual marker from a particular day.
+ *
+ * Return Value: #TRUE
+ */
gboolean
ido_calendar_menu_item_unmark_day (IdoCalendarMenuItem *menuitem, guint day)
{
@@ -357,6 +390,12 @@ ido_calendar_menu_item_unmark_day (IdoCalendarMenuItem *menuitem, guint day)
return TRUE;
}
+/**
+ * ido_calendar_menu_item_clear_marks:
+ * @menuitem: A #IdoCalendarMenuItem
+ *
+ * Remove all visual markers.
+ */
void
ido_calendar_menu_item_clear_marks (IdoCalendarMenuItem *menuitem)
{
@@ -365,6 +404,13 @@ ido_calendar_menu_item_clear_marks (IdoCalendarMenuItem *menuitem)
gtk_calendar_clear_marks(GTK_CALENDAR (menuitem->priv->calendar));
}
+/**
+ * ido_calendar_menu_item_set_display_options:
+ * @menuitem: A #IdoCalendarMenuItem
+ * @flags: the display options to set
+ *
+ * Set the display options for the calendar.
+ */
void
ido_calendar_menu_item_set_display_options (IdoCalendarMenuItem *menuitem, GtkCalendarDisplayOptions flags)
{
@@ -373,6 +419,14 @@ ido_calendar_menu_item_set_display_options (IdoCalendarMenuItem *menuitem, GtkCa
gtk_calendar_set_display_options (GTK_CALENDAR (menuitem->priv->calendar), flags);
}
+/**
+ * ido_calendar_menu_item_get_display_options:
+ * @menuitem: A #IdoCalendarMenuItem
+ *
+ * Get the display options for the calendar.
+ *
+ * Return Value: the display options in use
+ */
GtkCalendarDisplayOptions
ido_calendar_menu_item_get_display_options (IdoCalendarMenuItem *menuitem)
{
@@ -381,6 +435,15 @@ ido_calendar_menu_item_get_display_options (IdoCalendarMenuItem *menuitem)
return gtk_calendar_get_display_options (GTK_CALENDAR (menuitem->priv->calendar));
}
+/**
+ * ido_calendar_menu_item_get_date:
+ * @menuitem: A #IdoCalendarMenuItem
+ * @year: (out) (allow-none): location to store the year as a decimal number (e.g. 2011), or #NULL.
+ * @month: (out) (allow-none): location to store the month number (between 0 and 11), or #NULL.
+ * @day: (out) (allow-none): location to store the day number (between 1 and 31), or #NULL.
+ *
+ * Gets the selected date.
+ */
void
ido_calendar_menu_item_get_date (IdoCalendarMenuItem *menuitem,
guint *year,
@@ -391,17 +454,195 @@ ido_calendar_menu_item_get_date (IdoCalendarMenuItem *menuitem,
gtk_calendar_get_date (GTK_CALENDAR (menuitem->priv->calendar), year, month, day);
}
+/**
+ * ido_calendar_menu_item_set_date:
+ * @menuitem: A #IdoCalendarMenuItem
+ * @year: the year to show (e.g. 2011).
+ * @month: a month number (between 0 and 11).
+ * @day: The day number (between 1 and 31).
+ *
+ * Set the date shown on the calendar.
+ *
+ * Return Value: #TRUE
+ */
gboolean
ido_calendar_menu_item_set_date (IdoCalendarMenuItem *menuitem,
guint year,
guint month,
guint day)
{
- g_return_val_if_fail(IDO_IS_CALENDAR_MENU_ITEM(menuitem), FALSE);
- gtk_calendar_select_month (GTK_CALENDAR (menuitem->priv->calendar), month, year);
- gtk_calendar_select_day (GTK_CALENDAR (menuitem->priv->calendar), day);
+ guint old_y, old_m, old_d;
+
+ g_return_val_if_fail (IDO_IS_CALENDAR_MENU_ITEM(menuitem), FALSE);
+
+ ido_calendar_menu_item_get_date (menuitem, &old_y, &old_m, &old_d);
+
+ if ((old_y != year) || (old_m != month))
+ gtk_calendar_select_month (GTK_CALENDAR (menuitem->priv->calendar), month, year);
+
+ if (old_d != day)
+ gtk_calendar_select_day (GTK_CALENDAR (menuitem->priv->calendar), day);
+
return TRUE;
}
+/***
+****
+****
+****
+***/
+
+static void
+activate_current_day (IdoCalendarMenuItem * ido_calendar,
+ const char * action_name_key)
+{
+ GObject * o;
+ const char * action_name;
+ GActionGroup * action_group;
+
+ o = G_OBJECT (ido_calendar);
+ action_name = g_object_get_data (o, action_name_key);
+ action_group = g_object_get_data (o, "ido-action-group");
+
+ if (action_group && action_name)
+ {
+ guint y, m, d;
+ GDateTime * date_time;
+ GVariant * target;
+
+ ido_calendar_menu_item_get_date (ido_calendar, &y, &m, &d);
+ m++; /* adjust month from GtkCalendar (0 based) to GDateTime (1 based) */
+ date_time = g_date_time_new_local (y, m, d, 9, 0, 0);
+ target = g_variant_new_int64 (g_date_time_to_unix (date_time));
+
+ g_action_group_activate_action (action_group, action_name, target);
+
+ g_date_time_unref (date_time);
+ }
+}
+
+static void
+on_day_selected (IdoCalendarMenuItem * ido_calendar)
+{
+ activate_current_day (ido_calendar, "ido-selection-action-name");
+}
+
+static void
+on_day_double_clicked (IdoCalendarMenuItem * ido_calendar)
+{
+ activate_current_day (ido_calendar, "ido-activation-action-name");
+}
+
+static void
+on_action_state_changed (IdoActionHelper * helper,
+ GVariant * state,
+ gpointer unused G_GNUC_UNUSED)
+{
+ GVariant * v;
+ const char * key;
+ IdoCalendarMenuItem * ido_calendar;
+ ido_calendar = IDO_CALENDAR_MENU_ITEM (ido_action_helper_get_widget (helper));
+
+ g_return_if_fail (ido_calendar != NULL);
+ g_return_if_fail (g_variant_is_of_type (state, G_VARIANT_TYPE_DICTIONARY));
+
+ /* an int64 representing a time_t indicating which year and month should
+ be visible in the calendar and which day should be given the cursor. */
+ key = "calendar-day";
+ if ((v = g_variant_lookup_value (state, key, G_VARIANT_TYPE_INT64)))
+ {
+ int y, m, d;
+ time_t t;
+ GDateTime * date_time;
+
+ t = g_variant_get_int64 (v);
+ date_time = g_date_time_new_from_unix_local (t);
+ g_date_time_get_ymd (date_time, &y, &m, &d);
+ m--; /* adjust month from GDateTime (1 based) to GtkCalendar (0 based) */
+ ido_calendar_menu_item_set_date (ido_calendar, y, m, d);
+
+ g_date_time_unref (date_time);
+ g_variant_unref (v);
+ }
+ /* a boolean value of whether or not to show the week numbers */
+ key = "show-week-numbers";
+ if ((v = g_variant_lookup_value (state, key, G_VARIANT_TYPE_BOOLEAN)))
+ {
+ const GtkCalendarDisplayOptions old_flags = ido_calendar_menu_item_get_display_options (ido_calendar);
+ GtkCalendarDisplayOptions new_flags = old_flags;
+
+ if (g_variant_get_boolean (v))
+ new_flags |= GTK_CALENDAR_SHOW_WEEK_NUMBERS;
+ else
+ new_flags &= ~GTK_CALENDAR_SHOW_WEEK_NUMBERS;
+
+ if (new_flags != old_flags)
+ ido_calendar_menu_item_set_display_options (ido_calendar, new_flags);
+
+ g_variant_unref (v);
+ }
+
+ /* an array of int32 day-of-months denoting days that have appointments */
+ key = "appointment-days";
+ ido_calendar_menu_item_clear_marks (ido_calendar);
+ if ((v = g_variant_lookup_value (state, key, G_VARIANT_TYPE("ai"))))
+ {
+ gint32 day;
+ GVariantIter iter;
+
+ g_variant_iter_init (&iter, v);
+ while (g_variant_iter_next (&iter, "i", &day))
+ ido_calendar_menu_item_mark_day (ido_calendar, day);
+
+ g_variant_unref (v);
+ }
+}
+
+GtkMenuItem *
+ido_calendar_menu_item_new_from_model (GMenuItem * menu_item,
+ GActionGroup * actions)
+{
+ GObject * o;
+ GtkWidget * calendar;
+ IdoCalendarMenuItem * ido_calendar;
+ gchar * selection_action_name;
+ gchar * activation_action_name;
+
+ /* get the select & activate action names */
+ g_menu_item_get_attribute (menu_item, "action", "s", &selection_action_name);
+ g_menu_item_get_attribute (menu_item, "activation-action", "s", &activation_action_name);
+
+ /* remember the action group & action names so that we can poke them
+ when user selects and double-clicks */
+ ido_calendar = IDO_CALENDAR_MENU_ITEM (ido_calendar_menu_item_new ());
+ o = G_OBJECT (ido_calendar);
+ g_object_set_data_full (o, "ido-action-group", g_object_ref(actions), g_object_unref);
+ g_object_set_data_full (o, "ido-selection-action-name", selection_action_name, g_free);
+ g_object_set_data_full (o, "ido-activation-action-name", activation_action_name, g_free);
+ calendar = ido_calendar_menu_item_get_calendar (ido_calendar);
+ g_signal_connect_swapped (calendar, "day-selected",
+ G_CALLBACK(on_day_selected), ido_calendar);
+ g_signal_connect_swapped (calendar, "day-selected-double-click",
+ G_CALLBACK(on_day_double_clicked), ido_calendar);
+
+ /* Use an IdoActionHelper for state updates.
+ Since we have two separate actions for selection & activation,
+ we'll do the activation & targets logic here in ido-calendar */
+ if (selection_action_name != NULL)
+ {
+ IdoActionHelper * helper;
+
+ helper = ido_action_helper_new (GTK_WIDGET(ido_calendar),
+ actions,
+ selection_action_name,
+ NULL);
+ g_signal_connect (helper, "action-state-changed",
+ G_CALLBACK (on_action_state_changed), NULL);
+ g_signal_connect_swapped (ido_calendar, "destroy",
+ G_CALLBACK (g_object_unref), helper);
+ }
+
+ return GTK_MENU_ITEM (ido_calendar);
+}
diff --git a/src/idocalendarmenuitem.h b/src/idocalendarmenuitem.h
index c4833fb..5cd913e 100644
--- a/src/idocalendarmenuitem.h
+++ b/src/idocalendarmenuitem.h
@@ -69,7 +69,12 @@ void ido_calendar_menu_item_get_date (IdoCalendarMenuItem *menu
gboolean ido_calendar_menu_item_set_date (IdoCalendarMenuItem *menuitem,
guint year,
guint month,
- guint day);
+ guint day);
+
+GtkMenuItem * ido_calendar_menu_item_new_from_model (GMenuItem * menuitem,
+ GActionGroup * actions);
+
+
G_END_DECLS
#endif /* __IDO_CALENDAR_MENU_ITEM_H__ */
diff --git a/src/idoentrymenuitem.c b/src/idoentrymenuitem.c
index 5390d0b..5b5a3fb 100644
--- a/src/idoentrymenuitem.c
+++ b/src/idoentrymenuitem.c
@@ -260,17 +260,31 @@ entry_move_focus_cb (GtkWidget *widget,
GTK_DIR_TAB_FORWARD);
}
-/* Public API */
+/**
+ * ido_entry_menu_item_new:
+ *
+ * Creates a new #IdoEntryMenuItem.
+ *
+ * Return Value: the newly created #IdoEntryMenuItem.
+ */
GtkWidget *
ido_entry_menu_item_new (void)
{
return g_object_new (IDO_TYPE_ENTRY_MENU_ITEM, NULL);
}
+/**
+ * ido_entry_menu_item_get_entry:
+ * @menuitem: The #IdoEntryMenuItem.
+ *
+ * Get the #GtkEntry used in this menu item.
+ *
+ * Return Value: (transfer none): The #GtkEntry inside this menu item.
+ */
GtkWidget *
-ido_entry_menu_item_get_entry (IdoEntryMenuItem *item)
+ido_entry_menu_item_get_entry (IdoEntryMenuItem *menuitem)
{
- g_return_val_if_fail (IDO_IS_ENTRY_MENU_ITEM (item), NULL);
+ g_return_val_if_fail (IDO_IS_ENTRY_MENU_ITEM (menuitem), NULL);
- return item->priv->entry;
+ return menuitem->priv->entry;
}
diff --git a/src/idolocationmenuitem.c b/src/idolocationmenuitem.c
new file mode 100644
index 0000000..347c9e8
--- /dev/null
+++ b/src/idolocationmenuitem.c
@@ -0,0 +1,477 @@
+/**
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Authors:
+ * 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 <string.h> /* strstr() */
+
+#include <gtk/gtk.h>
+
+#include "idoactionhelper.h"
+#include "idolocationmenuitem.h"
+
+enum
+{
+ PROP_0,
+ PROP_NAME,
+ PROP_TIMEZONE,
+ PROP_FORMAT,
+ PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+struct _IdoLocationMenuItemPrivate
+{
+ char * name;
+ char * timezone;
+ char * format;
+
+ guint timestamp_timer;
+
+ GtkWidget * name_label;
+ GtkWidget * timestamp_label;
+};
+
+typedef IdoLocationMenuItemPrivate priv_t;
+
+G_DEFINE_TYPE (IdoLocationMenuItem, ido_location_menu_item, GTK_TYPE_MENU_ITEM);
+
+/***
+**** Timestamp Label
+***/
+
+static void
+update_timestamp_label (IdoLocationMenuItem * self)
+{
+ priv_t * p = self->priv;
+
+ if (p->format && *p->format)
+ {
+ GTimeZone * tz;
+ GDateTime * now;
+ char * str;
+
+ tz = g_time_zone_new (p->timezone);
+ if (tz == NULL)
+ tz = g_time_zone_new_local ();
+ now = g_date_time_new_now (tz);
+ str = g_date_time_format (now, p->format);
+
+ gtk_label_set_text (GTK_LABEL(p->timestamp_label), str);
+
+ g_free (str);
+ g_date_time_unref (now);
+ g_time_zone_unref (tz);
+ }
+ else
+ {
+ gtk_label_set_text (GTK_LABEL(p->timestamp_label), "");
+ }
+}
+
+static void
+stop_timestamp_timer (IdoLocationMenuItem * self)
+{
+ priv_t * p = self->priv;
+
+ if (p->timestamp_timer != 0)
+ {
+ g_source_remove (p->timestamp_timer);
+ p->timestamp_timer = 0;
+ }
+}
+
+static void start_timestamp_timer (IdoLocationMenuItem * self);
+
+static gboolean
+on_timestamp_timer (gpointer gself)
+{
+ IdoLocationMenuItem * self = IDO_LOCATION_MENU_ITEM (gself);
+
+ update_timestamp_label (self);
+
+ start_timestamp_timer (self);
+ return G_SOURCE_REMOVE;
+}
+
+static guint
+calculate_seconds_until_next_minute (void)
+{
+ guint seconds;
+ GTimeSpan diff;
+ GDateTime * now;
+ GDateTime * next;
+ GDateTime * start_of_next;
+
+ now = g_date_time_new_now_local ();
+ next = g_date_time_add_minutes (now, 1);
+ start_of_next = g_date_time_new_local (g_date_time_get_year (next),
+ g_date_time_get_month (next),
+ g_date_time_get_day_of_month (next),
+ g_date_time_get_hour (next),
+ g_date_time_get_minute (next),
+ 1);
+
+ diff = g_date_time_difference (start_of_next, now);
+ seconds = (diff + (G_TIME_SPAN_SECOND - 1)) / G_TIME_SPAN_SECOND;
+
+ /* cleanup */
+ g_date_time_unref (start_of_next);
+ g_date_time_unref (next);
+ g_date_time_unref (now);
+
+ return seconds;
+}
+
+static void
+start_timestamp_timer (IdoLocationMenuItem * self)
+{
+ int interval_sec;
+ gboolean timestamp_shows_seconds;
+ priv_t * p = self->priv;
+ const char * const fmt = p->format;
+
+ stop_timestamp_timer (self);
+
+ timestamp_shows_seconds = fmt && (strstr(fmt,"%s") || strstr(fmt,"%S") ||
+ strstr(fmt,"%T") || strstr(fmt,"%X") ||
+ strstr(fmt,"%c"));
+
+ if (timestamp_shows_seconds)
+ interval_sec = 1;
+ else
+ interval_sec = calculate_seconds_until_next_minute();
+
+ p->timestamp_timer = g_timeout_add_seconds (interval_sec,
+ on_timestamp_timer,
+ self);
+}
+
+/***
+**** GObject Virtual Functions
+***/
+
+static void
+my_get_property (GObject * o,
+ guint property_id,
+ GValue * value,
+ GParamSpec * pspec)
+{
+ IdoLocationMenuItem * self = IDO_LOCATION_MENU_ITEM (o);
+ priv_t * p = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_NAME:
+ g_value_set_string (value, p->name);
+ break;
+
+ case PROP_TIMEZONE:
+ g_value_set_string (value, p->timezone);
+ break;
+
+ case PROP_FORMAT:
+ g_value_set_string (value, p->format);
+ 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)
+{
+ IdoLocationMenuItem * self = IDO_LOCATION_MENU_ITEM (o);
+
+ switch (property_id)
+ {
+ case PROP_NAME:
+ ido_location_menu_item_set_name (self, g_value_get_string (value));
+ break;
+
+ case PROP_TIMEZONE:
+ ido_location_menu_item_set_timezone (self, g_value_get_string (value));
+ break;
+
+ case PROP_FORMAT:
+ ido_location_menu_item_set_format (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec);
+ break;
+ }
+}
+
+static void
+my_dispose (GObject * object)
+{
+ stop_timestamp_timer (IDO_LOCATION_MENU_ITEM (object));
+
+ G_OBJECT_CLASS (ido_location_menu_item_parent_class)->dispose (object);
+}
+
+static void
+my_finalize (GObject * object)
+{
+ IdoLocationMenuItem * self = IDO_LOCATION_MENU_ITEM (object);
+ priv_t * p = self->priv;
+
+ g_free (p->format);
+ g_free (p->name);
+ g_free (p->timezone);
+
+ G_OBJECT_CLASS (ido_location_menu_item_parent_class)->finalize (object);
+}
+
+/***
+**** Instantiation
+***/
+
+static void
+ido_location_menu_item_class_init (IdoLocationMenuItemClass *klass)
+{
+ GParamFlags prop_flags;
+ GObjectClass * gobject_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (IdoLocationMenuItemPrivate));
+
+ 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_NAME] = g_param_spec_string (
+ "name",
+ "The location's name",
+ "The name to display; eg, 'Oklahoma City'",
+ "Location",
+ prop_flags);
+
+ properties[PROP_TIMEZONE] = g_param_spec_string (
+ "timezone",
+ "timezone identifier",
+ "string used to identify a timezone; eg, 'America/Chicago'",
+ NULL,
+ prop_flags);
+
+ properties[PROP_FORMAT] = g_param_spec_string (
+ "format",
+ "strftime format",
+ "strftime-style format string for the timestamp",
+ "%T",
+ prop_flags);
+
+ g_object_class_install_properties (gobject_class, PROP_LAST, properties);
+}
+
+static void
+ido_location_menu_item_init (IdoLocationMenuItem *self)
+{
+ priv_t * p;
+ GtkBox * box;
+ GtkWidget * w;
+
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ IDO_LOCATION_MENU_ITEM_TYPE,
+ IdoLocationMenuItemPrivate);
+
+ p = self->priv;
+
+ p->name_label = gtk_label_new (NULL);
+ p->timestamp_label = gtk_label_new (NULL);
+
+ w = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
+ gtk_misc_set_alignment (GTK_MISC(p->timestamp_label), 1.0, 0.5);
+ box = GTK_BOX (w);
+ gtk_box_pack_start (box, p->name_label, FALSE, FALSE, 3);
+ gtk_box_pack_end (box, p->timestamp_label, FALSE, FALSE, 5);
+
+ gtk_widget_show_all (w);
+ gtk_container_add (GTK_CONTAINER (self), w);
+}
+
+
+/***
+**** Public API
+***/
+
+/* create a new IdoLocationMenuItemType */
+GtkWidget *
+ido_location_menu_item_new (void)
+{
+ return GTK_WIDGET (g_object_new (IDO_LOCATION_MENU_ITEM_TYPE, NULL));
+}
+
+/**
+ * ido_location_menu_item_set_name:
+ * @name: human-readable name, such as a city (eg: "Oklahoma City")
+ *
+ * Sets this location's name, for display in the menuitem's primary label.
+ */
+void
+ido_location_menu_item_set_name (IdoLocationMenuItem * self,
+ const char * name)
+{
+ priv_t * p;
+
+ g_return_if_fail (IDO_IS_LOCATION_MENU_ITEM (self));
+ p = self->priv;
+
+ g_free (p->name);
+ p->name = g_strdup (name);
+ gtk_label_set_text (GTK_LABEL(p->name_label), p->name);
+}
+
+/**
+ * ido_location_menu_item_set_timezone:
+ * @timezone: timezone identifier (eg: "America/Chicago")
+ *
+ * Set this location's timezone. This will be used to show the location's
+ * current time in menuitem's right-justified secondary label.
+ */
+void
+ido_location_menu_item_set_timezone (IdoLocationMenuItem * self,
+ const char * timezone)
+{
+ priv_t * p;
+
+ g_return_if_fail (IDO_IS_LOCATION_MENU_ITEM (self));
+ p = self->priv;
+
+ g_free (p->timezone);
+ p->timezone = g_strdup (timezone);
+ update_timestamp_label (self);
+}
+
+/**
+ * ido_location_menu_item_set_format:
+ * @format: the format string used when showing the location's time
+ *
+ * Set the format string for rendering the location's time
+ * in its right-justified secondary label.
+ *
+ * See strfrtime(3) for more information on the format string.
+ */
+void
+ido_location_menu_item_set_format (IdoLocationMenuItem * self,
+ const char * format)
+{
+ priv_t * p;
+
+ g_return_if_fail (IDO_IS_LOCATION_MENU_ITEM (self));
+ p = self->priv;
+
+ g_free (p->format);
+ p->format = g_strdup (format);
+ update_timestamp_label (self);
+ start_timestamp_timer (self);
+}
+
+/**
+ * ido_location_menu_item_new_from_model:
+ * @menu_item: the corresponding menuitem
+ * @actions: action group to tell when this GtkMenuItem is activated
+ *
+ * Creates a new IdoLocationMenuItem with properties initialized from
+ * the menuitem's attributes.
+ *
+ * If the menuitem's 'action' attribute is set, trigger that action
+ * in @actions when this IdoLocationMenuItem is activated.
+ */
+GtkMenuItem *
+ido_location_menu_item_new_from_model (GMenuItem * menu_item,
+ GActionGroup * actions)
+{
+ guint i;
+ guint n;
+ gchar * str;
+ IdoLocationMenuItem * ido_location;
+ GParameter parameters[4];
+
+ /* create the ido_location */
+
+ n = 0;
+
+ if (g_menu_item_get_attribute (menu_item, "label", "s", &str))
+ {
+ GParameter p = { "name", G_VALUE_INIT };
+ g_value_init (&p.value, G_TYPE_STRING);
+ g_value_take_string (&p.value, str);
+ parameters[n++] = p;
+ }
+
+ if (g_menu_item_get_attribute (menu_item, "x-canonical-timezone", "s", &str))
+ {
+ GParameter p = { "timezone", G_VALUE_INIT };
+ g_value_init (&p.value, G_TYPE_STRING);
+ g_value_take_string (&p.value, str);
+ parameters[n++] = p;
+ }
+
+ if (g_menu_item_get_attribute (menu_item, "x-canonical-time-format", "s", &str))
+ {
+ GParameter p = { "format", G_VALUE_INIT };
+ g_value_init (&p.value, G_TYPE_STRING);
+ g_value_take_string (&p.value, str);
+ parameters[n++] = p;
+ }
+
+ g_assert (n <= G_N_ELEMENTS (parameters));
+ ido_location = g_object_newv (IDO_LOCATION_MENU_ITEM_TYPE, n, parameters);
+
+ for (i=0; i<n; i++)
+ g_value_unset (&parameters[i].value);
+
+
+ /* give it an ActionHelper */
+
+ if (g_menu_item_get_attribute (menu_item, "action", "s", &str))
+ {
+ GVariant * target;
+ IdoActionHelper * helper;
+
+ target = g_menu_item_get_attribute_value (menu_item, "target",
+ G_VARIANT_TYPE_ANY);
+ helper = ido_action_helper_new (GTK_WIDGET(ido_location), actions,
+ str, target);
+ g_signal_connect_swapped (ido_location, "activate",
+ G_CALLBACK (ido_action_helper_activate), helper);
+ g_signal_connect_swapped (ido_location, "destroy",
+ G_CALLBACK (g_object_unref), helper);
+
+ if (target)
+ g_variant_unref (target);
+ g_free (str);
+ }
+
+ return GTK_MENU_ITEM (ido_location);
+}
diff --git a/src/idolocationmenuitem.h b/src/idolocationmenuitem.h
new file mode 100644
index 0000000..08a97af
--- /dev/null
+++ b/src/idolocationmenuitem.h
@@ -0,0 +1,73 @@
+/**
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Authors:
+ * 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/>.
+ */
+
+#ifndef __IDO_LOCATION_MENU_ITEM_H__
+#define __IDO_LOCATION_MENU_ITEM_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDO_LOCATION_MENU_ITEM_TYPE (ido_location_menu_item_get_type ())
+#define IDO_LOCATION_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_LOCATION_MENU_ITEM_TYPE, IdoLocationMenuItem))
+#define IDO_IS_LOCATION_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_LOCATION_MENU_ITEM_TYPE))
+
+typedef struct _IdoLocationMenuItem IdoLocationMenuItem;
+typedef struct _IdoLocationMenuItemClass IdoLocationMenuItemClass;
+typedef struct _IdoLocationMenuItemPrivate IdoLocationMenuItemPrivate;
+
+struct _IdoLocationMenuItemClass
+{
+ GtkMenuItemClass parent_class;
+};
+
+/**
+ * A menuitem that indicates a location.
+ *
+ * It contains a primary label giving the location's name and a
+ * right-aligned secondary label showing the location's current time
+ */
+struct _IdoLocationMenuItem
+{
+ /*< private >*/
+ GtkMenuItem parent;
+ IdoLocationMenuItemPrivate * priv;
+};
+
+
+GType ido_location_menu_item_get_type (void) G_GNUC_CONST;
+
+GtkWidget * ido_location_menu_item_new (void);
+
+GtkMenuItem * ido_location_menu_item_new_from_model (GMenuItem * menuitem,
+ GActionGroup * actions);
+
+void ido_location_menu_item_set_name (IdoLocationMenuItem * menuitem,
+ const char * name);
+
+void ido_location_menu_item_set_timezone (IdoLocationMenuItem * menuitem,
+ const char * timezone);
+
+void ido_location_menu_item_set_format (IdoLocationMenuItem * menuitem,
+ const char * strftime_fmt);
+
+
+G_END_DECLS
+
+#endif
diff --git a/src/idomediaplayermenuitem.c b/src/idomediaplayermenuitem.c
new file mode 100644
index 0000000..7e6e9d3
--- /dev/null
+++ b/src/idomediaplayermenuitem.c
@@ -0,0 +1,376 @@
+/*
+ * 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:
+ * Conor Curran <conor.curran@canonical.com>
+ * Mirco Müller <mirco.mueller@canonical.com>
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#include "config.h"
+
+#include "idomediaplayermenuitem.h"
+#include "idoactionhelper.h"
+
+#define ALBUM_ART_SIZE 60
+
+typedef GtkMenuItemClass IdoMediaPlayerMenuItemClass;
+
+struct _IdoMediaPlayerMenuItem
+{
+ GtkMenuItem parent;
+
+ GCancellable *cancellable;
+ GtkWidget* player_label;
+ GtkWidget* player_icon;
+ GtkWidget* metadata_widget;
+ GtkWidget* album_art;
+ GtkWidget* artist_label;
+ GtkWidget* piece_label;
+ GtkWidget* container_label;
+
+ gboolean running;
+};
+
+G_DEFINE_TYPE (IdoMediaPlayerMenuItem, ido_media_player_menu_item, GTK_TYPE_MENU_ITEM);
+
+static void
+ido_media_player_menu_item_dispose (GObject *object)
+{
+ IdoMediaPlayerMenuItem *self = IDO_MEDIA_PLAYER_MENU_ITEM (object);
+
+ if (self->cancellable)
+ {
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+ }
+
+ G_OBJECT_CLASS (ido_media_player_menu_item_parent_class)->dispose (object);
+}
+
+static gboolean
+ido_media_player_menu_item_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ IdoMediaPlayerMenuItem *self = IDO_MEDIA_PLAYER_MENU_ITEM (widget);
+
+ GTK_WIDGET_CLASS (ido_media_player_menu_item_parent_class)->draw (widget, cr);
+
+ /* draw a triangle next to the application name if the app is running */
+ if (self->running)
+ {
+ const int arrow_width = 5;
+ const int half_arrow_height = 4;
+
+ GdkRGBA color;
+ GtkAllocation allocation;
+ GtkAllocation label_allocation;
+ int x;
+ int y;
+
+ gtk_style_context_get_color (gtk_widget_get_style_context (widget),
+ gtk_widget_get_state (widget),
+ &color);
+
+ gtk_widget_get_allocation (widget, &allocation);
+ gtk_widget_get_allocation (self->player_label, &label_allocation);
+ x = allocation.x;
+ y = label_allocation.y - allocation.y + label_allocation.height / 2;
+
+ cairo_move_to (cr, x, y - half_arrow_height);
+ cairo_line_to (cr, x, y + half_arrow_height);
+ cairo_line_to (cr, x + arrow_width, y);
+ cairo_close_path (cr);
+
+ gdk_cairo_set_source_rgba (cr, &color);
+ cairo_fill (cr);
+ }
+
+ return FALSE;
+}
+
+static void
+ido_media_player_menu_item_get_preferred_width (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ *minimum = *natural = 200;
+}
+
+static void
+ido_media_player_menu_item_class_init (IdoMediaPlayerMenuItemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = ido_media_player_menu_item_dispose;
+
+ widget_class->get_preferred_width = ido_media_player_menu_item_get_preferred_width;
+ widget_class->draw = ido_media_player_menu_item_draw;
+}
+
+static void
+ido_media_player_menu_item_init (IdoMediaPlayerMenuItem *self)
+{
+ GtkWidget *grid;
+
+ self->cancellable = g_cancellable_new ();
+
+ self->player_icon = gtk_image_new();
+ gtk_widget_set_margin_right (self->player_icon, 6);
+ gtk_widget_set_halign (self->player_icon, GTK_ALIGN_START);
+
+ self->player_label = gtk_label_new (NULL);
+ gtk_widget_set_halign (self->player_label, GTK_ALIGN_START);
+ gtk_widget_set_hexpand (self->player_label, TRUE);
+
+ self->album_art = gtk_image_new();
+ gtk_widget_set_size_request (self->album_art, ALBUM_ART_SIZE, ALBUM_ART_SIZE);
+ gtk_widget_set_margin_right (self->album_art, 8);
+
+ self->artist_label = gtk_label_new (NULL);
+ gtk_widget_set_halign (self->artist_label, GTK_ALIGN_START);
+ gtk_label_set_ellipsize (GTK_LABEL (self->artist_label), PANGO_ELLIPSIZE_MIDDLE);
+
+ self->piece_label = gtk_label_new (NULL);
+ gtk_widget_set_halign (self->piece_label, GTK_ALIGN_START);
+ gtk_label_set_ellipsize (GTK_LABEL (self->piece_label), PANGO_ELLIPSIZE_MIDDLE);
+
+ self->container_label = gtk_label_new (NULL);
+ gtk_widget_set_halign (self->container_label, GTK_ALIGN_START);
+ gtk_widget_set_valign (self->container_label, GTK_ALIGN_START);
+ gtk_widget_set_vexpand (self->container_label, TRUE);
+ gtk_label_set_ellipsize (GTK_LABEL (self->container_label), PANGO_ELLIPSIZE_MIDDLE);
+
+ self->metadata_widget = gtk_grid_new ();
+ gtk_grid_attach (GTK_GRID (self->metadata_widget), self->album_art, 0, 0, 1, 4);
+ gtk_grid_attach (GTK_GRID (self->metadata_widget), self->piece_label, 1, 0, 1, 1);
+ gtk_grid_attach (GTK_GRID (self->metadata_widget), self->artist_label, 1, 1, 1, 1);
+ gtk_grid_attach (GTK_GRID (self->metadata_widget), self->container_label, 1, 2, 1, 1);
+
+ grid = gtk_grid_new ();
+ gtk_grid_set_row_spacing (GTK_GRID (grid), 8);
+ gtk_grid_attach (GTK_GRID (grid), self->player_icon, 0, 0, 1, 1);
+ gtk_grid_attach (GTK_GRID (grid), self->player_label, 1, 0, 1, 1);
+ gtk_grid_attach (GTK_GRID (grid), self->metadata_widget, 0, 1, 2, 1);
+
+ gtk_container_add (GTK_CONTAINER (self), grid);
+ gtk_widget_show_all (grid);
+
+ /* hide metadata by defalut (player is not running) */
+ gtk_widget_hide (self->metadata_widget);
+}
+
+static void
+ido_media_player_menu_item_set_player_name (IdoMediaPlayerMenuItem *self,
+ const gchar *name)
+{
+ g_return_if_fail (IDO_IS_MEDIA_PLAYER_MENU_ITEM (self));
+
+ gtk_label_set_label (GTK_LABEL (self->player_label), name);
+}
+
+static void
+ido_media_player_menu_item_set_player_icon (IdoMediaPlayerMenuItem *self,
+ GIcon *icon)
+{
+ g_return_if_fail (IDO_IS_MEDIA_PLAYER_MENU_ITEM (self));
+
+ gtk_image_set_from_gicon (GTK_IMAGE (self->player_icon), icon, GTK_ICON_SIZE_MENU);
+}
+
+static void
+ido_media_player_menu_item_set_is_running (IdoMediaPlayerMenuItem *self,
+ gboolean running)
+{
+ g_return_if_fail (IDO_IS_MEDIA_PLAYER_MENU_ITEM (self));
+
+ if (self->running != running)
+ {
+ self->running = running;
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+ }
+}
+
+static void
+album_art_received (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdoMediaPlayerMenuItem *self = user_data;
+ GdkPixbuf *pixbuf;
+ GError *error = NULL;
+
+ pixbuf = gdk_pixbuf_new_from_stream_finish (result, &error);
+ if (pixbuf == NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("unable to fetch album art: %s", error->message);
+
+ g_error_free (error);
+ return;
+ }
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (self->album_art), pixbuf);
+ g_object_unref (pixbuf);
+}
+
+static void
+album_art_file_opened (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdoMediaPlayerMenuItem *self = user_data;
+ GFileInputStream *input;
+ GError *error = NULL;
+
+ input = g_file_read_finish (G_FILE (object), result, &error);
+ if (input == NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("unable to fetch album art: %s", error->message);
+
+ g_error_free (error);
+ return;
+ }
+
+ gdk_pixbuf_new_from_stream_at_scale_async (G_INPUT_STREAM (input),
+ ALBUM_ART_SIZE, ALBUM_ART_SIZE, TRUE,
+ self->cancellable,
+ album_art_received, self);
+
+ g_object_unref (input);
+}
+
+static void
+ido_media_player_menu_item_set_album_art (IdoMediaPlayerMenuItem *self,
+ const gchar *url)
+{
+ GFile *file;
+
+ g_return_if_fail (IDO_IS_MEDIA_PLAYER_MENU_ITEM (self));
+
+ gtk_image_clear (GTK_IMAGE (self->album_art));
+
+ if (url == NULL)
+ return;
+
+ file = g_file_new_for_uri (url);
+ g_file_read_async (file, G_PRIORITY_DEFAULT, self->cancellable, album_art_file_opened, self);
+
+ g_object_unref (file);
+}
+
+static void
+ido_media_player_menu_item_set_metadata (IdoMediaPlayerMenuItem *self,
+ const gchar *title,
+ const gchar *artist,
+ const gchar *album,
+ const gchar *art_url)
+{
+ g_return_if_fail (IDO_IS_MEDIA_PLAYER_MENU_ITEM (self));
+
+ /* hide if there's no metadata */
+ if (title == NULL || *title == '\0')
+ {
+ gtk_label_set_label (GTK_LABEL (self->piece_label), NULL);
+ gtk_label_set_label (GTK_LABEL (self->artist_label), NULL);
+ gtk_label_set_label (GTK_LABEL (self->container_label), NULL);
+ ido_media_player_menu_item_set_album_art (self, NULL);
+ gtk_widget_hide (self->metadata_widget);
+ }
+ else
+ {
+ gtk_label_set_label (GTK_LABEL (self->piece_label), title);
+ gtk_label_set_label (GTK_LABEL (self->artist_label), artist);
+ gtk_label_set_label (GTK_LABEL (self->container_label), album);
+ ido_media_player_menu_item_set_album_art (self, art_url);
+ gtk_widget_show (self->metadata_widget);
+ }
+}
+
+static void
+ido_media_player_menu_item_state_changed (IdoActionHelper *helper,
+ GVariant *state,
+ gpointer user_data)
+{
+ IdoMediaPlayerMenuItem *widget;
+ gboolean running = FALSE;
+ const gchar *title = NULL;
+ const gchar *artist = NULL;
+ const gchar *album = NULL;
+ const gchar *art_url = NULL;
+
+ g_variant_lookup (state, "running", "b", &running);
+ g_variant_lookup (state, "title", "&s", &title);
+ g_variant_lookup (state, "artist", "&s", &artist);
+ g_variant_lookup (state, "album", "&s", &album);
+ g_variant_lookup (state, "art-url", "&s", &art_url);
+
+ widget = IDO_MEDIA_PLAYER_MENU_ITEM (ido_action_helper_get_widget (helper));
+ ido_media_player_menu_item_set_is_running (widget, running);
+ ido_media_player_menu_item_set_metadata (widget, title, artist, album, art_url);
+}
+
+GtkMenuItem *
+ido_media_player_menu_item_new_from_model (GMenuItem *menuitem,
+ GActionGroup *actions)
+{
+ GtkMenuItem *widget;
+ gchar *label;
+ gchar *action;
+ GVariant *v;
+
+ widget = g_object_new (IDO_TYPE_MEDIA_PLAYER_MENU_ITEM, NULL);
+
+ if (g_menu_item_get_attribute (menuitem, "label", "s", &label))
+ {
+ ido_media_player_menu_item_set_player_name (IDO_MEDIA_PLAYER_MENU_ITEM (widget), label);
+ g_free (label);
+ }
+
+ if ((v = g_menu_item_get_attribute_value (menuitem, "icon", NULL)))
+ {
+ GIcon *icon;
+
+ icon = g_icon_deserialize (v);
+ if (icon)
+ {
+ ido_media_player_menu_item_set_player_icon (IDO_MEDIA_PLAYER_MENU_ITEM (widget), icon);
+ g_object_unref (icon);
+ }
+
+ g_variant_unref (v);
+ }
+
+ if (g_menu_item_get_attribute (menuitem, "action", "s", &action))
+ {
+ IdoActionHelper *helper;
+
+ helper = ido_action_helper_new (GTK_WIDGET (widget), actions, action, NULL);
+ g_signal_connect (helper, "action-state-changed",
+ G_CALLBACK (ido_media_player_menu_item_state_changed), NULL);
+
+ g_signal_connect_object (widget, "activate",
+ G_CALLBACK (ido_action_helper_activate),
+ helper, G_CONNECT_SWAPPED);
+
+ g_signal_connect_swapped (widget, "destroy", G_CALLBACK (g_object_unref), helper);
+
+ g_free (action);
+ }
+
+ return widget;
+}
diff --git a/src/idomediaplayermenuitem.h b/src/idomediaplayermenuitem.h
new file mode 100644
index 0000000..e4a7e8e
--- /dev/null
+++ b/src/idomediaplayermenuitem.h
@@ -0,0 +1,42 @@
+/*
+ * 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:
+ * Conor Curran <conor.curran@canonical.com>
+ * Mirco Müller <mirco.mueller@canonical.com>
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#ifndef __IDO_MEDIA_PLAYER_MENU_ITEM_H__
+#define __IDO_MEDIA_PLAYER_MENU_ITEM_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDO_TYPE_MEDIA_PLAYER_MENU_ITEM (ido_media_player_menu_item_get_type ())
+#define IDO_MEDIA_PLAYER_MENU_ITEM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), IDO_TYPE_MEDIA_PLAYER_MENU_ITEM, IdoMediaPlayerMenuItem))
+#define IDO_IS_MEDIA_PLAYER_MENU_ITEM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), IDO_TYPE_MEDIA_PLAYER_MENU_ITEM))
+
+typedef struct _IdoMediaPlayerMenuItem IdoMediaPlayerMenuItem;
+
+GType ido_media_player_menu_item_get_type (void);
+
+GtkMenuItem * ido_media_player_menu_item_new_from_model (GMenuItem *menuitem,
+ GActionGroup *actions);
+
+G_END_DECLS
+
+#endif
diff --git a/src/idomenuitemfactory.c b/src/idomenuitemfactory.c
new file mode 100644
index 0000000..650c95f
--- /dev/null
+++ b/src/idomenuitemfactory.c
@@ -0,0 +1,92 @@
+/*
+ * 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 "idoappointmentmenuitem.h"
+#include "idocalendarmenuitem.h"
+#include "idolocationmenuitem.h"
+#include "idoscalemenuitem.h"
+#include "idousermenuitem.h"
+#include "idomediaplayermenuitem.h"
+#include "idoplaybackmenuitem.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);
+
+ else if (g_str_equal (type, "com.canonical.indicator.calendar"))
+ item = ido_calendar_menu_item_new_from_model (menuitem, actions);
+
+ else if (g_str_equal (type, "com.canonical.indicator.location"))
+ item = ido_location_menu_item_new_from_model (menuitem, actions);
+
+ else if (g_str_equal (type, "com.canonical.indicator.appointment"))
+ item = ido_appointment_menu_item_new_from_model (menuitem, actions);
+
+ else if (g_str_equal (type, "com.canonical.unity.slider"))
+ item = ido_scale_menu_item_new_from_model (menuitem, actions);
+
+ else if (g_str_equal (type, "com.canonical.unity.media-player"))
+ item = ido_media_player_menu_item_new_from_model (menuitem, actions);
+
+ else if (g_str_equal (type, "com.canonical.unity.playback-item"))
+ item = ido_playback_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/idomessagedialog.c b/src/idomessagedialog.c
index 41ff2e7..f2c2e93 100644
--- a/src/idomessagedialog.c
+++ b/src/idomessagedialog.c
@@ -258,7 +258,7 @@ ido_message_dialog_init (IdoMessageDialog *dialog)
* dialog it will expand to provide the secondary message
* and the action buttons.
*
- * Return value: a new #IdoMessageDialog
+ * Return Value: a new #IdoMessageDialog
**/
GtkWidget*
ido_message_dialog_new (GtkWindow *parent,
@@ -305,6 +305,25 @@ ido_message_dialog_new (GtkWindow *parent,
return widget;
}
+/**
+ * ido_message_dialog_new_with_markup:
+ * @parent: transient parent, or %NULL for none
+ * @flags: flags
+ * @type: type of message
+ * @buttons: a set of buttons to use
+ * @message_format: printf()-style format string, or %NULL
+ * @Varargs: arguments for @message_format. They will be escaped to allow valid XML.
+ *
+ * Creates a new message dialog, which is based upon
+ * GtkMessageDialog so it shares API and functionality
+ * with it. IdoMessageDialog differs in that it has two
+ * states. The initial state hides the action buttons
+ * and the secondary message. When a user clicks on the
+ * dialog it will expand to provide the secondary message
+ * and the action buttons.
+ *
+ * Return Value: a new #IdoMessageDialog
+ **/
GtkWidget*
ido_message_dialog_new_with_markup (GtkWindow *parent,
GtkDialogFlags flags,
diff --git a/src/idomessagedialog.h b/src/idomessagedialog.h
index e11cd59..4313fb3 100644
--- a/src/idomessagedialog.h
+++ b/src/idomessagedialog.h
@@ -29,8 +29,6 @@
#ifndef __IDO_MESSAGE_DIALOG_H__
#define __IDO_MESSAGE_DIALOG_H__
-G_BEGIN_DECLS
-
#include <gtk/gtk.h>
#define IDO_TYPE_MESSAGE_DIALOG (ido_message_dialog_get_type ())
diff --git a/src/idoplaybackmenuitem.c b/src/idoplaybackmenuitem.c
new file mode 100644
index 0000000..662ceaf
--- /dev/null
+++ b/src/idoplaybackmenuitem.c
@@ -0,0 +1,1680 @@
+/*
+ * 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:
+ * Conor Curran <conor.curran@canonical.com>
+ * Mirco Müller <mirco.mueller@canonical.com>
+ * Andrea Cimitan <andrea.cimitan@canonical.com>
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#include "config.h"
+
+#include "idoplaybackmenuitem.h"
+
+#include <gdk/gdkkeysyms.h>
+#include <math.h>
+
+typedef enum
+{
+ STATE_PAUSED,
+ STATE_PLAYING,
+ STATE_LAUNCHING
+} State;
+
+typedef enum
+{
+ BUTTON_NONE,
+ BUTTON_PREVIOUS,
+ BUTTON_PLAYPAUSE,
+ BUTTON_NEXT
+} Button;
+
+typedef GtkMenuItemClass IdoPlaybackMenuItemClass;
+
+struct _IdoPlaybackMenuItem
+{
+ GtkMenuItem parent;
+
+ State current_state;
+ Button cur_pushed_button;
+ Button cur_hover_button;
+ gboolean has_focus;
+ gboolean keyboard_activated; /* TRUE if the current button was activated with a key */
+
+ GActionGroup *action_group;
+ gchar *play_action;
+ gchar *next_action;
+ gchar *prev_action;
+};
+
+G_DEFINE_TYPE (IdoPlaybackMenuItem, ido_playback_menu_item, GTK_TYPE_MENU_ITEM);
+
+static gboolean ido_playback_menu_item_draw (GtkWidget* button, cairo_t *cr);
+
+static void
+ido_playback_menu_item_dispose (GObject *object)
+{
+ IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (object);
+
+ if (item->action_group)
+ {
+ g_signal_handlers_disconnect_by_data (item->action_group, item);
+ g_clear_object (&item->action_group);
+ }
+
+ G_OBJECT_CLASS (ido_playback_menu_item_parent_class)->dispose (object);
+}
+
+static void
+ido_playback_menu_item_finalize (GObject *object)
+{
+ IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (object);
+
+ g_free (item->play_action);
+ g_free (item->next_action);
+ g_free (item->prev_action);
+
+ G_OBJECT_CLASS (ido_playback_menu_item_parent_class)->finalize (object);
+}
+
+static Button
+ido_playback_menu_item_get_button_at_pos (gint x,
+ gint y)
+{
+ /* 57 101 143 187
+ * 5 +------+
+ * 12 +-----+ +-----+
+ * |prev play next|
+ * 40 +-----+ +-----+
+ * 47 +------+
+ */
+
+ if (x > 57 && x < 102 && y > 12 && y < 40)
+ return BUTTON_PREVIOUS;
+
+ if (x > 101 && x < 143 && y > 5 && y < 47)
+ return BUTTON_PLAYPAUSE;
+
+ if (x > 142 && x < 187 && y > 12 && y < 40)
+ return BUTTON_NEXT;
+
+ return BUTTON_NONE;
+}
+
+static gboolean
+ido_playback_menu_item_parent_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ IdoPlaybackMenuItem *self = user_data;
+
+ /* only listen to events when the playback menu item is selected */
+ if (!self->has_focus)
+ return FALSE;
+
+ switch (event->keyval)
+ {
+ case GDK_KEY_Left:
+ self->cur_pushed_button = BUTTON_PREVIOUS;
+ if (self->action_group && self->prev_action)
+ g_action_group_activate_action (self->action_group, self->prev_action, NULL);
+ break;
+
+ case GDK_KEY_Right:
+ self->cur_pushed_button = BUTTON_NEXT;
+ if (self->action_group && self->next_action)
+ g_action_group_activate_action (self->action_group, self->next_action, NULL);
+ break;
+
+ case GDK_KEY_space:
+ self->cur_pushed_button = BUTTON_PLAYPAUSE;
+ if (self->action_group && self->play_action)
+ g_action_group_activate_action (self->action_group, self->play_action, NULL);
+ break;
+
+ default:
+ self->cur_pushed_button = BUTTON_NONE;
+ }
+
+ if (self->cur_pushed_button != BUTTON_NONE)
+ {
+ self->keyboard_activated = TRUE;
+ gtk_widget_queue_draw (widget);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+ido_playback_menu_item_parent_key_release_event (GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ IdoPlaybackMenuItem *self = user_data;
+
+ switch (event->keyval)
+ {
+ case GDK_KEY_Left:
+ case GDK_KEY_Right:
+ case GDK_KEY_space:
+ self->cur_pushed_button = BUTTON_NONE;
+ self->keyboard_activated = FALSE;
+ gtk_widget_queue_draw (widget);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+ido_playback_menu_item_parent_set (GtkWidget *widget,
+ GtkWidget *old_parent)
+{
+ GtkWidget *parent;
+
+ /* Menus don't pass key events to their children. This works around
+ * that by listening to key events on the parent widget. */
+
+ if (old_parent)
+ {
+ g_signal_handlers_disconnect_by_func (old_parent, ido_playback_menu_item_parent_key_press_event, widget);
+ g_signal_handlers_disconnect_by_func (old_parent, ido_playback_menu_item_parent_key_release_event, widget);
+ }
+
+ parent = gtk_widget_get_parent (widget);
+ if (parent)
+ {
+ g_signal_connect (parent, "key-press-event",
+ G_CALLBACK (ido_playback_menu_item_parent_key_press_event), widget);
+ g_signal_connect (parent, "key-release-event",
+ G_CALLBACK (ido_playback_menu_item_parent_key_release_event), widget);
+ }
+}
+
+static void
+ido_playback_menu_item_select (GtkMenuItem *item)
+{
+ IdoPlaybackMenuItem *self = IDO_PLAYBACK_MENU_ITEM (item);
+
+ self->has_focus = TRUE;
+
+ GTK_MENU_ITEM_CLASS (ido_playback_menu_item_parent_class)->select (item);
+}
+
+static void
+ido_playback_menu_item_deselect (GtkMenuItem *item)
+{
+ IdoPlaybackMenuItem *self = IDO_PLAYBACK_MENU_ITEM (item);
+
+ self->has_focus = FALSE;
+
+ GTK_MENU_ITEM_CLASS (ido_playback_menu_item_parent_class)->deselect (item);
+}
+
+static gboolean
+ido_playback_menu_item_button_press_event (GtkWidget *menuitem,
+ GdkEventButton *event)
+{
+ IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (menuitem);
+
+ item->cur_pushed_button = ido_playback_menu_item_get_button_at_pos (event->x, event->y);
+ gtk_widget_queue_draw (menuitem);
+
+ return TRUE;
+}
+
+static gboolean
+ido_playback_menu_item_button_release_event (GtkWidget *menuitem,
+ GdkEventButton *event)
+{
+ IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (menuitem);
+ Button button;
+
+ button = ido_playback_menu_item_get_button_at_pos (event->x, event->y);
+ if (button != item->cur_pushed_button)
+ button = BUTTON_NONE;
+
+ switch (button)
+ {
+ case BUTTON_NONE:
+ break;
+
+ case BUTTON_PREVIOUS:
+ if (item->action_group && item->prev_action)
+ g_action_group_activate_action (item->action_group, item->prev_action, NULL);
+ break;
+
+ case BUTTON_NEXT:
+ if (item->action_group && item->next_action)
+ g_action_group_activate_action (item->action_group, item->next_action, NULL);
+ break;
+
+ case BUTTON_PLAYPAUSE:
+ if (item->action_group && item->play_action)
+ g_action_group_activate_action (item->action_group, item->play_action, NULL);
+ break;
+ }
+
+ item->cur_pushed_button = BUTTON_NONE;
+ gtk_widget_queue_draw (menuitem);
+
+ return TRUE;
+}
+
+static gboolean
+ido_playback_menu_item_motion_notify_event (GtkWidget *menuitem,
+ GdkEventMotion *event)
+{
+ IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (menuitem);
+
+ item->cur_hover_button = ido_playback_menu_item_get_button_at_pos (event->x, event->y);
+ gtk_widget_queue_draw (menuitem);
+
+ return TRUE;
+}
+
+static gboolean
+ido_playback_menu_item_leave_notify_event (GtkWidget *menuitem,
+ GdkEventCrossing *event)
+{
+ IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (menuitem);
+
+ item->cur_pushed_button = BUTTON_NONE;
+ item->cur_hover_button = BUTTON_NONE;
+ gtk_widget_queue_draw (GTK_WIDGET(menuitem));
+
+ return TRUE;
+}
+
+static void
+ido_playback_menu_item_class_init (IdoPlaybackMenuItemClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkMenuItemClass *menuitem_class = GTK_MENU_ITEM_CLASS (klass);
+
+ gobject_class->dispose = ido_playback_menu_item_dispose;
+ gobject_class->finalize = ido_playback_menu_item_finalize;
+
+ widget_class->button_press_event = ido_playback_menu_item_button_press_event;
+ widget_class->button_release_event = ido_playback_menu_item_button_release_event;
+ widget_class->motion_notify_event = ido_playback_menu_item_motion_notify_event;
+ widget_class->leave_notify_event = ido_playback_menu_item_leave_notify_event;
+ widget_class->parent_set = ido_playback_menu_item_parent_set;
+ widget_class->draw = ido_playback_menu_item_draw;
+
+ menuitem_class->select = ido_playback_menu_item_select;
+ menuitem_class->deselect = ido_playback_menu_item_deselect;
+}
+
+static void
+ido_playback_menu_item_init (IdoPlaybackMenuItem *self)
+{
+ gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self)), GTK_STYLE_CLASS_SPINNER);
+
+ gtk_widget_set_size_request (GTK_WIDGET (self), 200, 43);
+}
+
+static void
+ido_playback_menu_item_set_state_from_string (IdoPlaybackMenuItem *self,
+ const gchar *state)
+{
+ g_return_if_fail (state != NULL);
+
+ if (g_str_equal (state, "Playing"))
+ self->current_state = STATE_PLAYING;
+ else if (g_str_equal (state, "Launching"))
+ self->current_state = STATE_LAUNCHING;
+ else /* "Paused" and fallback */
+ self->current_state = STATE_PAUSED;
+
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+ido_playback_menu_item_action_added (GActionGroup *action_group,
+ const gchar *action_name,
+ gpointer user_data)
+{
+ IdoPlaybackMenuItem *self = user_data;
+
+ if (self->play_action && g_str_equal (action_name, self->play_action))
+ {
+ GVariant *state;
+
+ state = g_action_group_get_action_state (action_group, self->play_action);
+ if (g_variant_is_of_type (state, G_VARIANT_TYPE_STRING))
+ ido_playback_menu_item_set_state_from_string (self, g_variant_get_string (state, NULL));
+
+ g_variant_unref (state);
+ }
+}
+
+static void
+ido_playback_menu_item_action_removed (GActionGroup *action_group,
+ const gchar *action_name,
+ gpointer user_data)
+{
+ IdoPlaybackMenuItem *self = user_data;
+
+ if (self->play_action && g_str_equal (action_name, self->play_action))
+ {
+ self->current_state = STATE_PAUSED;
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+ }
+}
+
+static void
+ido_playback_menu_item_action_state_changed (GActionGroup *action_group,
+ const gchar *action_name,
+ GVariant *value,
+ gpointer user_data)
+{
+ IdoPlaybackMenuItem *self = user_data;
+
+ g_return_if_fail (action_name != NULL);
+
+ if (self->play_action && g_str_equal (action_name, self->play_action))
+ {
+ if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+ ido_playback_menu_item_set_state_from_string (self, g_variant_get_string (value, NULL));
+ }
+}
+
+GtkMenuItem *
+ido_playback_menu_item_new_from_model (GMenuItem *item,
+ GActionGroup *actions)
+{
+ IdoPlaybackMenuItem *widget;
+
+ widget = g_object_new (IDO_TYPE_PLAYBACK_MENU_ITEM, NULL);
+
+ widget->action_group = g_object_ref (actions);
+ g_signal_connect (actions, "action-state-changed", G_CALLBACK (ido_playback_menu_item_action_state_changed), widget);
+ g_signal_connect (actions, "action-added", G_CALLBACK (ido_playback_menu_item_action_added), widget);
+ g_signal_connect (actions, "action-removed", G_CALLBACK (ido_playback_menu_item_action_removed), widget);
+
+ g_menu_item_get_attribute (item, "x-canonical-play-action", "s", &widget->play_action);
+ g_menu_item_get_attribute (item, "x-canonical-next-action", "s", &widget->next_action);
+ g_menu_item_get_attribute (item, "x-canonical-previous-action", "s", &widget->prev_action);
+
+ if (widget->play_action && g_action_group_has_action (actions, widget->play_action))
+ ido_playback_menu_item_action_added (actions, widget->play_action, widget);
+
+ return GTK_MENU_ITEM (widget);
+}
+
+
+/*
+ * Drawing
+ */
+
+#define RECT_WIDTH 130.0f
+#define Y 7.0f
+#define X 70.0f
+#define INNER_RADIUS 12.5
+#define MIDDLE_RADIUS 13.0f
+#define OUTER_RADIUS 14.5f
+#define CIRCLE_RADIUS 21.0f
+#define PREV_WIDTH 25.0f
+#define PREV_HEIGHT 17.0f
+#define NEXT_WIDTH 25.0f //PREV_WIDTH
+#define NEXT_HEIGHT 17.0f //PREV_HEIGHT
+#define TRI_WIDTH 11.0f
+#define TRI_HEIGHT 13.0f
+#define TRI_OFFSET 6.0f
+#define PREV_X 68.0f
+#define PREV_Y 13.0f
+#define NEXT_X 146.0f
+#define NEXT_Y 13.0f //prev_y
+#define PAUSE_WIDTH 21.0f
+#define PAUSE_HEIGHT 27.0f
+#define BAR_WIDTH 4.5f
+#define BAR_HEIGHT 24.0f
+#define BAR_OFFSET 10.0f
+#define PAUSE_X 111.0f
+#define PAUSE_Y 7.0f
+#define PLAY_WIDTH 28.0f
+#define PLAY_HEIGHT 29.0f
+#define PLAY_PADDING 5.0f
+#define INNER_START_SHADE 0.98
+#define INNER_END_SHADE 0.98
+#define MIDDLE_START_SHADE 1.0
+#define MIDDLE_END_SHADE 1.0
+#define OUTER_START_SHADE 0.75
+#define OUTER_END_SHADE 1.3
+#define SHADOW_BUTTON_SHADE 0.8
+#define OUTER_PLAY_START_SHADE 0.7
+#define OUTER_PLAY_END_SHADE 1.38
+#define BUTTON_START_SHADE 1.1
+#define BUTTON_END_SHADE 0.9
+#define BUTTON_SHADOW_SHADE 0.8
+#define INNER_COMPRESSED_START_SHADE 1.0
+#define INNER_COMPRESSED_END_SHADE 1.0
+
+typedef struct
+{
+ double r;
+ double g;
+ double b;
+} CairoColorRGB;
+
+static void
+draw_gradient (cairo_t* cr,
+ double x,
+ double y,
+ double w,
+ double r,
+ double* rgba_start,
+ double* rgba_end)
+{
+ cairo_pattern_t* pattern = NULL;
+
+ cairo_move_to (cr, x, y);
+ cairo_line_to (cr, x + w - 2.0f * r, y);
+ cairo_arc (cr,
+ x + w - 2.0f * r,
+ y + r,
+ r,
+ -90.0f * G_PI / 180.0f,
+ 90.0f * G_PI / 180.0f);
+ cairo_line_to (cr, x, y + 2.0f * r);
+ cairo_arc (cr,
+ x,
+ y + r,
+ r,
+ 90.0f * G_PI / 180.0f,
+ 270.0f * G_PI / 180.0f);
+ cairo_close_path (cr);
+
+ pattern = cairo_pattern_create_linear (x, y, x, y + 2.0f * r);
+ cairo_pattern_add_color_stop_rgba (pattern,
+ 0.0f,
+ rgba_start[0],
+ rgba_start[1],
+ rgba_start[2],
+ rgba_start[3]);
+ cairo_pattern_add_color_stop_rgba (pattern,
+ 1.0f,
+ rgba_end[0],
+ rgba_end[1],
+ rgba_end[2],
+ rgba_end[3]);
+ cairo_set_source (cr, pattern);
+ cairo_fill (cr);
+ cairo_pattern_destroy (pattern);
+}
+
+static void
+draw_circle (cairo_t* cr,
+ double x,
+ double y,
+ double r,
+ double* rgba_start,
+ double* rgba_end)
+{
+ cairo_pattern_t* pattern = NULL;
+
+ cairo_move_to (cr, x, y);
+ cairo_arc (cr,
+ x + r,
+ y + r,
+ r,
+ 0.0f * G_PI / 180.0f,
+ 360.0f * G_PI / 180.0f);
+
+ pattern = cairo_pattern_create_linear (x, y, x, y + 2.0f * r);
+ cairo_pattern_add_color_stop_rgba (pattern,
+ 0.0f,
+ rgba_start[0],
+ rgba_start[1],
+ rgba_start[2],
+ rgba_start[3]);
+ cairo_pattern_add_color_stop_rgba (pattern,
+ 1.0f,
+ rgba_end[0],
+ rgba_end[1],
+ rgba_end[2],
+ rgba_end[3]);
+ cairo_set_source (cr, pattern);
+ cairo_fill (cr);
+ cairo_pattern_destroy (pattern);
+}
+
+static void
+_setup (cairo_t** cr,
+ cairo_surface_t** surf,
+ gint width,
+ gint height)
+{
+ if (!cr || !surf)
+ return;
+
+ *surf = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+ *cr = cairo_create (*surf);
+ cairo_scale (*cr, 1.0f, 1.0f);
+ cairo_set_operator (*cr, CAIRO_OPERATOR_CLEAR);
+ cairo_paint (*cr);
+ cairo_set_operator (*cr, CAIRO_OPERATOR_OVER);
+}
+
+static void
+_mask_prev (cairo_t* cr,
+ double x,
+ double y,
+ double tri_width,
+ double tri_height,
+ double tri_offset)
+{
+ if (!cr)
+ return;
+
+ cairo_move_to (cr, x, y + tri_height / 2.0f);
+ cairo_line_to (cr, x + tri_width, y);
+ cairo_line_to (cr, x + tri_width, y + tri_height);
+ x += tri_offset;
+ cairo_move_to (cr, x, y + tri_height / 2.0f);
+ cairo_line_to (cr, x + tri_width, y);
+ cairo_line_to (cr, x + tri_width, y + tri_height);
+ x -= tri_offset;
+ cairo_rectangle (cr, x, y, 2.5f, tri_height);
+ cairo_close_path (cr);
+}
+
+static void
+_mask_next (cairo_t* cr,
+ double x,
+ double y,
+ double tri_width,
+ double tri_height,
+ double tri_offset)
+{
+ if (!cr)
+ return;
+
+ cairo_move_to (cr, x, y);
+ cairo_line_to (cr, x + tri_width, y + tri_height / 2.0f);
+ cairo_line_to (cr, x, y + tri_height);
+ x += tri_offset;
+ cairo_move_to (cr, x, y);
+ cairo_line_to (cr, x + tri_width, y + tri_height / 2.0f);
+ cairo_line_to (cr, x, y + tri_height);
+ x -= tri_offset;
+ x += 2.0f * tri_width - tri_offset - 1.0f;
+ cairo_rectangle (cr, x, y, 2.5f, tri_height);
+
+ cairo_close_path (cr);
+}
+
+static void
+_mask_pause (cairo_t* cr,
+ double x,
+ double y,
+ double bar_width,
+ double bar_height,
+ double bar_offset)
+{
+ if (!cr)
+ return;
+
+ cairo_set_line_width (cr, bar_width);
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
+
+ x += bar_width;
+ y += bar_width;
+ cairo_move_to (cr, x, y);
+ cairo_line_to (cr, x, y + bar_height);
+ cairo_move_to (cr, x + bar_offset, y);
+ cairo_line_to (cr, x + bar_offset, y + bar_height);
+
+}
+
+static void
+_mask_play (cairo_t* cr,
+ double x,
+ double y,
+ double tri_width,
+ double tri_height)
+{
+ if (!cr)
+ return;
+
+ cairo_move_to (cr, x, y);
+ cairo_line_to (cr, x + tri_width, y + tri_height / 2.0f);
+ cairo_line_to (cr, x, y + tri_height);
+ cairo_close_path (cr);
+
+}
+
+static void
+_fill (cairo_t* cr,
+ double x_start,
+ double y_start,
+ double x_end,
+ double y_end,
+ double* rgba_start,
+ double* rgba_end,
+ gboolean stroke)
+{
+ cairo_pattern_t* pattern = NULL;
+
+ if (!cr || !rgba_start || !rgba_end)
+ return;
+
+ pattern = cairo_pattern_create_linear (x_start, y_start, x_end, y_end);
+ cairo_pattern_add_color_stop_rgba (pattern,
+ 0.0f,
+ rgba_start[0],
+ rgba_start[1],
+ rgba_start[2],
+ rgba_start[3]);
+ cairo_pattern_add_color_stop_rgba (pattern,
+ 1.0f,
+ rgba_end[0],
+ rgba_end[1],
+ rgba_end[2],
+ rgba_end[3]);
+ cairo_set_source (cr, pattern);
+ if (stroke)
+ cairo_stroke (cr);
+ else
+ cairo_fill (cr);
+ cairo_pattern_destroy (pattern);
+}
+
+static void
+_finalize (cairo_t* cr,
+ cairo_t** cr_surf,
+ cairo_surface_t** surf,
+ double x,
+ double y)
+{
+ if (!cr || !cr_surf || !surf)
+ return;
+
+ cairo_set_source_surface (cr, *surf, x, y);
+ cairo_paint (cr);
+ cairo_surface_destroy (*surf);
+ cairo_destroy (*cr_surf);
+}
+
+static void
+_finalize_repaint (cairo_t* cr,
+ cairo_t** cr_surf,
+ cairo_surface_t** surf,
+ double x,
+ double y,
+ int repaints)
+{
+ if (!cr || !cr_surf || !surf)
+ return;
+
+ while (repaints > 0)
+ {
+ cairo_set_source_surface (cr, *surf, x, y);
+ cairo_paint (cr);
+ repaints--;
+ }
+
+ cairo_surface_destroy (*surf);
+ cairo_destroy (*cr_surf);
+}
+
+static void
+_color_rgb_to_hls (gdouble *r,
+ gdouble *g,
+ gdouble *b)
+{
+ gdouble min;
+ gdouble max;
+ gdouble red;
+ gdouble green;
+ gdouble blue;
+ gdouble h = 0;
+ gdouble l;
+ gdouble s;
+ gdouble delta;
+
+ red = *r;
+ green = *g;
+ blue = *b;
+
+ if (red > green)
+ {
+ if (red > blue)
+ max = red;
+ else
+ max = blue;
+
+ if (green < blue)
+ min = green;
+ else
+ min = blue;
+ }
+ else
+ {
+ if (green > blue)
+ max = green;
+ else
+ max = blue;
+
+ if (red < blue)
+ min = red;
+ else
+ min = blue;
+ }
+ l = (max+min)/2;
+ if (fabs (max-min) < 0.0001)
+ {
+ h = 0;
+ s = 0;
+ }
+ else
+ {
+ if (l <= 0.5)
+ s = (max-min)/(max+min);
+ else
+ s = (max-min)/(2-max-min);
+
+ delta = (max -min) != 0 ? (max -min) : 1;
+
+ if(delta == 0)
+ delta = 1;
+ if (red == max)
+ h = (green-blue)/delta;
+ else if (green == max)
+ h = 2+(blue-red)/delta;
+ else if (blue == max)
+ h = 4+(red-green)/delta;
+
+ h *= 60;
+ if (h < 0.0)
+ h += 360;
+ }
+
+ *r = h;
+ *g = l;
+ *b = s;
+}
+
+static void
+_color_hls_to_rgb (gdouble *h,
+ gdouble *l,
+ gdouble *s)
+{
+ gdouble hue;
+ gdouble lightness;
+ gdouble saturation;
+ gdouble m1, m2;
+ gdouble r, g, b;
+
+ lightness = *l;
+ saturation = *s;
+
+ if (lightness <= 0.5)
+ m2 = lightness*(1+saturation);
+ else
+ m2 = lightness+saturation-lightness*saturation;
+
+ m1 = 2*lightness-m2;
+
+ if (saturation == 0)
+ {
+ *h = lightness;
+ *l = lightness;
+ *s = lightness;
+ }
+ else
+ {
+ hue = *h+120;
+ while (hue > 360)
+ hue -= 360;
+ while (hue < 0)
+ hue += 360;
+
+ if (hue < 60)
+ r = m1+(m2-m1)*hue/60;
+ else if (hue < 180)
+ r = m2;
+ else if (hue < 240)
+ r = m1+(m2-m1)*(240-hue)/60;
+ else
+ r = m1;
+
+ hue = *h;
+ while (hue > 360)
+ hue -= 360;
+ while (hue < 0)
+ hue += 360;
+
+ if (hue < 60)
+ g = m1+(m2-m1)*hue/60;
+ else if (hue < 180)
+ g = m2;
+ else if (hue < 240)
+ g = m1+(m2-m1)*(240-hue)/60;
+ else
+ g = m1;
+
+ hue = *h-120;
+ while (hue > 360)
+ hue -= 360;
+ while (hue < 0)
+ hue += 360;
+
+ if (hue < 60)
+ b = m1+(m2-m1)*hue/60;
+ else if (hue < 180)
+ b = m2;
+ else if (hue < 240)
+ b = m1+(m2-m1)*(240-hue)/60;
+ else
+ b = m1;
+
+ *h = r;
+ *l = g;
+ *s = b;
+ }
+}
+
+static void
+_color_shade (const CairoColorRGB *a, float k, CairoColorRGB *b)
+{
+ double red;
+ double green;
+ double blue;
+
+ red = a->r;
+ green = a->g;
+ blue = a->b;
+
+ if (k == 1.0)
+ {
+ b->r = red;
+ b->g = green;
+ b->b = blue;
+ return;
+ }
+
+ _color_rgb_to_hls (&red, &green, &blue);
+
+ green *= k;
+ if (green > 1.0)
+ green = 1.0;
+ else if (green < 0.0)
+ green = 0.0;
+
+ blue *= k;
+ if (blue > 1.0)
+ blue = 1.0;
+ else if (blue < 0.0)
+ blue = 0.0;
+
+ _color_hls_to_rgb (&red, &green, &blue);
+
+ b->r = red;
+ b->g = green;
+ b->b = blue;
+}
+
+static inline void
+_blurinner (guchar* pixel,
+ gint* zR,
+ gint* zG,
+ gint* zB,
+ gint* zA,
+ gint alpha,
+ gint aprec,
+ gint zprec)
+{
+ gint R;
+ gint G;
+ gint B;
+ guchar A;
+
+ R = *pixel;
+ G = *(pixel + 1);
+ B = *(pixel + 2);
+ A = *(pixel + 3);
+
+ *zR += (alpha * ((R << zprec) - *zR)) >> aprec;
+ *zG += (alpha * ((G << zprec) - *zG)) >> aprec;
+ *zB += (alpha * ((B << zprec) - *zB)) >> aprec;
+ *zA += (alpha * ((A << zprec) - *zA)) >> aprec;
+
+ *pixel = *zR >> zprec;
+ *(pixel + 1) = *zG >> zprec;
+ *(pixel + 2) = *zB >> zprec;
+ *(pixel + 3) = *zA >> zprec;
+}
+
+static inline void
+_blurrow (guchar* pixels,
+ gint width,
+ gint height,
+ gint channels,
+ gint line,
+ gint alpha,
+ gint aprec,
+ gint zprec)
+{
+ gint zR;
+ gint zG;
+ gint zB;
+ gint zA;
+ gint index;
+ guchar* scanline;
+
+ scanline = &(pixels[line * width * channels]);
+
+ zR = *scanline << zprec;
+ zG = *(scanline + 1) << zprec;
+ zB = *(scanline + 2) << zprec;
+ zA = *(scanline + 3) << zprec;
+
+ for (index = 0; index < width; index ++)
+ _blurinner (&scanline[index * channels],
+ &zR,
+ &zG,
+ &zB,
+ &zA,
+ alpha,
+ aprec,
+ zprec);
+
+ for (index = width - 2; index >= 0; index--)
+ _blurinner (&scanline[index * channels],
+ &zR,
+ &zG,
+ &zB,
+ &zA,
+ alpha,
+ aprec,
+ zprec);
+}
+
+static inline void
+_blurcol (guchar* pixels,
+ gint width,
+ gint height,
+ gint channels,
+ gint x,
+ gint alpha,
+ gint aprec,
+ gint zprec)
+{
+ gint zR;
+ gint zG;
+ gint zB;
+ gint zA;
+ gint index;
+ guchar* ptr;
+
+ ptr = pixels;
+
+ ptr += x * channels;
+
+ zR = *((guchar*) ptr ) << zprec;
+ zG = *((guchar*) ptr + 1) << zprec;
+ zB = *((guchar*) ptr + 2) << zprec;
+ zA = *((guchar*) ptr + 3) << zprec;
+
+ for (index = width; index < (height - 1) * width; index += width)
+ _blurinner ((guchar*) &ptr[index * channels],
+ &zR,
+ &zG,
+ &zB,
+ &zA,
+ alpha,
+ aprec,
+ zprec);
+
+ for (index = (height - 2) * width; index >= 0; index -= width)
+ _blurinner ((guchar*) &ptr[index * channels],
+ &zR,
+ &zG,
+ &zB,
+ &zA,
+ alpha,
+ aprec,
+ zprec);
+}
+
+static void
+_expblur (guchar* pixels,
+ gint width,
+ gint height,
+ gint channels,
+ gint radius,
+ gint aprec,
+ gint zprec)
+{
+ gint alpha;
+ gint row = 0;
+ gint col = 0;
+
+ if (radius < 1)
+ return;
+
+ // calculate the alpha such that 90% of
+ // the kernel is within the radius.
+ // (Kernel extends to infinity)
+ alpha = (gint) ((1 << aprec) * (1.0f - expf (-2.3f / (radius + 1.f))));
+
+ for (; row < height; row++)
+ _blurrow (pixels,
+ width,
+ height,
+ channels,
+ row,
+ alpha,
+ aprec,
+ zprec);
+
+ for(; col < width; col++)
+ _blurcol (pixels,
+ width,
+ height,
+ channels,
+ col,
+ alpha,
+ aprec,
+ zprec);
+
+ return;
+}
+
+static void
+_surface_blur (cairo_surface_t* surface,
+ guint radius)
+{
+ guchar* pixels;
+ guint width;
+ guint height;
+ cairo_format_t format;
+
+ // before we mess with the surface execute any pending drawing
+ cairo_surface_flush (surface);
+
+ pixels = cairo_image_surface_get_data (surface);
+ width = cairo_image_surface_get_width (surface);
+ height = cairo_image_surface_get_height (surface);
+ format = cairo_image_surface_get_format (surface);
+
+ switch (format)
+ {
+ case CAIRO_FORMAT_ARGB32:
+ _expblur (pixels, width, height, 4, radius, 16, 7);
+ break;
+
+ case CAIRO_FORMAT_RGB24:
+ _expblur (pixels, width, height, 3, radius, 16, 7);
+ break;
+
+ case CAIRO_FORMAT_A8:
+ _expblur (pixels, width, height, 1, radius, 16, 7);
+ break;
+
+ default :
+ // do nothing
+ break;
+ }
+
+ // inform cairo we altered the surfaces contents
+ cairo_surface_mark_dirty (surface);
+}
+
+static gboolean
+ido_playback_menu_item_draw (GtkWidget* button, cairo_t *cr)
+{
+ IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (button);
+
+ g_return_val_if_fail(IDO_IS_PLAYBACK_MENU_ITEM (button), FALSE);
+ g_return_val_if_fail(cr != NULL, FALSE);
+
+ cairo_surface_t* surf = NULL;
+ cairo_t* cr_surf = NULL;
+
+ GtkStyle *style;
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (button),
+ GTK_STYLE_CLASS_MENU);
+ CairoColorRGB bg_color, fg_color, bg_selected, bg_prelight;
+ CairoColorRGB color_middle[2], color_middle_prelight[2], color_outer[2], color_outer_prelight[2],
+ color_play_outer[2], color_play_outer_prelight[2],
+ color_button[4], color_button_shadow, color_inner[2], color_inner_compressed[2];
+
+ /* Use the menu's style instead of that of the menuitem ('button' is a
+ * menuitem that is packed in a menu directly). The menuitem's style
+ * can't be used due to a change in light-themes (lp #1130183).
+ * Menuitems now have a transparent background, which confuses
+ * GtkStyle.
+ *
+ * This is a workaround until this code gets refactored to use
+ * GtkStyleContext.
+ */
+ style = gtk_widget_get_style (gtk_widget_get_parent (button));
+
+ bg_color.r = style->bg[0].red/65535.0;
+ bg_color.g = style->bg[0].green/65535.0;
+ bg_color.b = style->bg[0].blue/65535.0;
+
+ bg_prelight.r = style->bg[GTK_STATE_PRELIGHT].red/65535.0;
+ bg_prelight.g = style->bg[GTK_STATE_PRELIGHT].green/65535.0;
+ bg_prelight.b = style->bg[GTK_STATE_PRELIGHT].blue/65535.0;
+
+ bg_selected.r = style->bg[GTK_STATE_SELECTED].red/65535.0;
+ bg_selected.g = style->bg[GTK_STATE_SELECTED].green/65535.0;
+ bg_selected.b = style->bg[GTK_STATE_SELECTED].blue/65535.0;
+
+ fg_color.r = style->fg[0].red/65535.0;
+ fg_color.g = style->fg[0].green/65535.0;
+ fg_color.b = style->fg[0].blue/65535.0;
+
+ _color_shade (&bg_color, MIDDLE_START_SHADE, &color_middle[0]);
+ _color_shade (&bg_color, MIDDLE_END_SHADE, &color_middle[1]);
+ _color_shade (&bg_prelight, MIDDLE_START_SHADE, &color_middle_prelight[0]);
+ _color_shade (&bg_prelight, MIDDLE_END_SHADE, &color_middle_prelight[1]);
+ _color_shade (&bg_color, OUTER_START_SHADE, &color_outer[0]);
+ _color_shade (&bg_color, OUTER_END_SHADE, &color_outer[1]);
+ _color_shade (&bg_prelight, OUTER_START_SHADE, &color_outer_prelight[0]);
+ _color_shade (&bg_prelight, OUTER_END_SHADE, &color_outer_prelight[1]);
+ _color_shade (&bg_color, OUTER_PLAY_START_SHADE, &color_play_outer[0]);
+ _color_shade (&bg_color, OUTER_PLAY_END_SHADE, &color_play_outer[1]);
+ _color_shade (&bg_prelight, OUTER_PLAY_START_SHADE, &color_play_outer_prelight[0]);
+ _color_shade (&bg_prelight, OUTER_PLAY_END_SHADE, &color_play_outer_prelight[1]);
+ _color_shade (&bg_color, INNER_START_SHADE, &color_inner[0]);
+ _color_shade (&bg_color, INNER_END_SHADE, &color_inner[1]);
+ _color_shade (&fg_color, BUTTON_START_SHADE, &color_button[0]);
+ _color_shade (&fg_color, BUTTON_END_SHADE, &color_button[1]);
+ _color_shade (&bg_color, BUTTON_SHADOW_SHADE, &color_button[2]);
+ _color_shade (&bg_color, SHADOW_BUTTON_SHADE, &color_button_shadow);
+ _color_shade (&bg_selected, 1.0, &color_button[3]);
+ _color_shade (&bg_color, INNER_COMPRESSED_START_SHADE, &color_inner_compressed[0]);
+ _color_shade (&bg_color, INNER_COMPRESSED_END_SHADE, &color_inner_compressed[1]);
+
+ double MIDDLE_END[] = {color_middle[0].r, color_middle[0].g, color_middle[0].b, 1.0f};
+ double MIDDLE_START[] = {color_middle[1].r, color_middle[1].g, color_middle[1].b, 1.0f};
+ double MIDDLE_END_PRELIGHT[] = {color_middle_prelight[0].r, color_middle_prelight[0].g, color_middle_prelight[0].b, 1.0f};
+ double MIDDLE_START_PRELIGHT[] = {color_middle_prelight[1].r, color_middle_prelight[1].g, color_middle_prelight[1].b, 1.0f};
+ double OUTER_END[] = {color_outer[0].r, color_outer[0].g, color_outer[0].b, 1.0f};
+ double OUTER_START[] = {color_outer[1].r, color_outer[1].g, color_outer[1].b, 1.0f};
+ double OUTER_END_PRELIGHT[] = {color_outer_prelight[0].r, color_outer_prelight[0].g, color_outer_prelight[0].b, 1.0f};
+ double OUTER_START_PRELIGHT[] = {color_outer_prelight[1].r, color_outer_prelight[1].g, color_outer_prelight[1].b, 1.0f};
+ double SHADOW_BUTTON[] = {color_button_shadow.r, color_button_shadow.g, color_button_shadow.b, 0.3f};
+ double OUTER_PLAY_END[] = {color_play_outer[0].r, color_play_outer[0].g, color_play_outer[0].b, 1.0f};
+ double OUTER_PLAY_START[] = {color_play_outer[1].r, color_play_outer[1].g, color_play_outer[1].b, 1.0f};
+ double OUTER_PLAY_END_PRELIGHT[] = {color_play_outer_prelight[0].r, color_play_outer_prelight[0].g, color_play_outer_prelight[0].b, 1.0f};
+ double OUTER_PLAY_START_PRELIGHT[] = {color_play_outer_prelight[1].r, color_play_outer_prelight[1].g, color_play_outer_prelight[1].b, 1.0f};
+ double BUTTON_END[] = {color_button[0].r, color_button[0].g, color_button[0].b, 1.0f};
+ double BUTTON_START[] = {color_button[1].r, color_button[1].g, color_button[1].b, 1.0f};
+ double BUTTON_SHADOW[] = {color_button[2].r, color_button[2].g, color_button[2].b, 0.75f};
+ double BUTTON_SHADOW_FOCUS[] = {color_button[3].r, color_button[3].g, color_button[3].b, 1.0f};
+ double INNER_COMPRESSED_END[] = {color_inner_compressed[1].r, color_inner_compressed[1].g, color_inner_compressed[1].b, 1.0f};
+ double INNER_COMPRESSED_START[] = {color_inner_compressed[0].r, color_inner_compressed[0].g, color_inner_compressed[0].b, 1.0f};
+
+
+ draw_gradient (cr,
+ X,
+ Y,
+ RECT_WIDTH,
+ OUTER_RADIUS,
+ OUTER_START,
+ OUTER_END);
+
+ draw_gradient (cr,
+ X,
+ Y + 1,
+ RECT_WIDTH - 2,
+ MIDDLE_RADIUS,
+ MIDDLE_START,
+ MIDDLE_END);
+
+ draw_gradient (cr,
+ X,
+ Y + 2,
+ RECT_WIDTH - 4,
+ MIDDLE_RADIUS,
+ MIDDLE_START,
+ MIDDLE_END);
+
+
+ if(item->cur_pushed_button == BUTTON_PREVIOUS)
+ {
+ draw_gradient (cr,
+ X,
+ Y,
+ RECT_WIDTH/2,
+ OUTER_RADIUS,
+ OUTER_END,
+ OUTER_START);
+
+ draw_gradient (cr,
+ X,
+ Y + 1,
+ RECT_WIDTH/2,
+ MIDDLE_RADIUS,
+ INNER_COMPRESSED_START,
+ INNER_COMPRESSED_END);
+
+ draw_gradient (cr,
+ X,
+ Y + 2,
+ RECT_WIDTH/2,
+ MIDDLE_RADIUS,
+ INNER_COMPRESSED_START,
+ INNER_COMPRESSED_END);
+ }
+ else if(item->cur_pushed_button == BUTTON_NEXT)
+ {
+ draw_gradient (cr,
+ RECT_WIDTH / 2 + X,
+ Y,
+ RECT_WIDTH/2,
+ OUTER_RADIUS,
+ OUTER_END,
+ OUTER_START);
+
+ draw_gradient (cr,
+ RECT_WIDTH / 2 + X,
+ Y + 1,
+ (RECT_WIDTH - 4.5)/2,
+ MIDDLE_RADIUS,
+ INNER_COMPRESSED_START,
+ INNER_COMPRESSED_END);
+
+ draw_gradient (cr,
+ RECT_WIDTH / 2 + X,
+ Y + 2,
+ (RECT_WIDTH - 7)/2,
+ MIDDLE_RADIUS,
+ INNER_COMPRESSED_START,
+ INNER_COMPRESSED_END);
+ }
+ else if (item->cur_hover_button == BUTTON_PREVIOUS)
+ {
+ draw_gradient (cr,
+ X,
+ Y,
+ RECT_WIDTH/2,
+ OUTER_RADIUS,
+ OUTER_START_PRELIGHT,
+ OUTER_END_PRELIGHT);
+
+ draw_gradient (cr,
+ X,
+ Y + 1,
+ RECT_WIDTH/2,
+ MIDDLE_RADIUS,
+ MIDDLE_START_PRELIGHT,
+ MIDDLE_END_PRELIGHT);
+
+ draw_gradient (cr,
+ X,
+ Y + 2,
+ RECT_WIDTH/2,
+ MIDDLE_RADIUS,
+ MIDDLE_START_PRELIGHT,
+ MIDDLE_END_PRELIGHT);
+ }
+ else if (item->cur_hover_button == BUTTON_NEXT)
+ {
+ draw_gradient (cr,
+ RECT_WIDTH / 2 + X,
+ Y,
+ RECT_WIDTH/2,
+ OUTER_RADIUS,
+ OUTER_START_PRELIGHT,
+ OUTER_END_PRELIGHT);
+
+ draw_gradient (cr,
+ RECT_WIDTH / 2 + X,
+ Y + 1,
+ (RECT_WIDTH - 4.5)/2,
+ MIDDLE_RADIUS,
+ MIDDLE_START_PRELIGHT,
+ MIDDLE_END_PRELIGHT);
+
+ draw_gradient (cr,
+ RECT_WIDTH / 2 + X,
+ Y + 2,
+ (RECT_WIDTH - 7)/2,
+ MIDDLE_RADIUS,
+ MIDDLE_START_PRELIGHT,
+ MIDDLE_END_PRELIGHT);
+ }
+
+ // play/pause shadow
+ if(item->cur_pushed_button != BUTTON_PLAYPAUSE)
+ {
+ cairo_save (cr);
+ cairo_rectangle (cr, X, Y, RECT_WIDTH, MIDDLE_RADIUS*2);
+ cairo_clip (cr);
+
+ draw_circle (cr,
+ X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f - 1.0f,
+ Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) - 1.0f,
+ CIRCLE_RADIUS + 1.0f,
+ SHADOW_BUTTON,
+ SHADOW_BUTTON);
+
+ cairo_restore (cr);
+ }
+
+ // play/pause button
+ if(item->cur_pushed_button == BUTTON_PLAYPAUSE)
+ {
+ draw_circle (cr,
+ X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f,
+ Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) ,
+ CIRCLE_RADIUS,
+ OUTER_PLAY_END,
+ OUTER_PLAY_START);
+
+ draw_circle (cr,
+ X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f + 1.25f,
+ Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) + 1.25f,
+ CIRCLE_RADIUS - 1.25,
+ INNER_COMPRESSED_START,
+ INNER_COMPRESSED_END);
+ }
+ else if (item->cur_hover_button == BUTTON_PLAYPAUSE)
+ {
+ /* this subtle offset is to fix alpha borders, should be removed once this draw routine will be refactored */
+ draw_circle (cr,
+ X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f + 0.1,
+ Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) + 0.1,
+ CIRCLE_RADIUS - 0.1,
+ OUTER_PLAY_START_PRELIGHT,
+ OUTER_PLAY_END_PRELIGHT);
+
+ draw_circle (cr,
+ X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f + 1.25f,
+ Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) + 1.25f,
+ CIRCLE_RADIUS - 1.25,
+ MIDDLE_START_PRELIGHT,
+ MIDDLE_END_PRELIGHT);
+ }
+ else
+ {
+ draw_circle (cr,
+ X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f,
+ Y - ((CIRCLE_RADIUS - OUTER_RADIUS)),
+ CIRCLE_RADIUS,
+ OUTER_PLAY_START,
+ OUTER_PLAY_END);
+
+ draw_circle (cr,
+ X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f + 1.25f,
+ Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) + 1.25f,
+ CIRCLE_RADIUS - 1.25,
+ MIDDLE_START,
+ MIDDLE_END);
+ }
+
+ // draw previous-button drop-shadow
+ if (item->cur_pushed_button == BUTTON_PREVIOUS && item->keyboard_activated )
+ {
+ _setup (&cr_surf, &surf, PREV_WIDTH+6, PREV_HEIGHT+6);
+ _mask_prev (cr_surf,
+ (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (PREV_HEIGHT - TRI_HEIGHT) / 2.0f,
+ TRI_WIDTH,
+ TRI_HEIGHT,
+ TRI_OFFSET);
+ _fill (cr_surf,
+ (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (PREV_HEIGHT - TRI_HEIGHT) / 2.0f,
+ (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (double) TRI_HEIGHT,
+ BUTTON_SHADOW_FOCUS,
+ BUTTON_SHADOW_FOCUS,
+ FALSE);
+ _surface_blur (surf, 3);
+ _finalize_repaint (cr, &cr_surf, &surf, PREV_X, PREV_Y + 0.5f, 3);
+ }
+ else
+ {
+ _setup (&cr_surf, &surf, PREV_WIDTH, PREV_HEIGHT);
+ _mask_prev (cr_surf,
+ (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (PREV_HEIGHT - TRI_HEIGHT) / 2.0f,
+ TRI_WIDTH,
+ TRI_HEIGHT,
+ TRI_OFFSET);
+ _fill (cr_surf,
+ (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (PREV_HEIGHT - TRI_HEIGHT) / 2.0f,
+ (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (double) TRI_HEIGHT,
+ BUTTON_SHADOW,
+ BUTTON_SHADOW,
+ FALSE);
+ _surface_blur (surf, 1);
+ _finalize (cr, &cr_surf, &surf, PREV_X, PREV_Y + 1.0f);
+ }
+
+ // draw previous-button
+ _setup (&cr_surf, &surf, PREV_WIDTH, PREV_HEIGHT);
+ _mask_prev (cr_surf,
+ (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (PREV_HEIGHT - TRI_HEIGHT) / 2.0f,
+ TRI_WIDTH,
+ TRI_HEIGHT,
+ TRI_OFFSET);
+ _fill (cr_surf,
+ (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (PREV_HEIGHT - TRI_HEIGHT) / 2.0f,
+ (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (double) TRI_HEIGHT,
+ BUTTON_START,
+ BUTTON_END,
+ FALSE);
+ _finalize (cr, &cr_surf, &surf, PREV_X, PREV_Y);
+
+ // draw next-button drop-shadow
+ if (item->cur_pushed_button == BUTTON_NEXT && item->keyboard_activated)
+ {
+ _setup (&cr_surf, &surf, NEXT_WIDTH+6, NEXT_HEIGHT+6);
+ _mask_next (cr_surf,
+ (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f,
+ TRI_WIDTH,
+ TRI_HEIGHT,
+ TRI_OFFSET);
+ _fill (cr_surf,
+ (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f,
+ (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (double) TRI_HEIGHT,
+ BUTTON_SHADOW_FOCUS,
+ BUTTON_SHADOW_FOCUS,
+ FALSE);
+ _surface_blur (surf, 3);
+ _finalize_repaint (cr, &cr_surf, &surf, NEXT_X, NEXT_Y + 0.5f, 3);
+ }
+ else
+ {
+ _setup (&cr_surf, &surf, NEXT_WIDTH, NEXT_HEIGHT);
+ _mask_next (cr_surf,
+ (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f,
+ TRI_WIDTH,
+ TRI_HEIGHT,
+ TRI_OFFSET);
+ _fill (cr_surf,
+ (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f,
+ (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (double) TRI_HEIGHT,
+ BUTTON_SHADOW,
+ BUTTON_SHADOW,
+ FALSE);
+ _surface_blur (surf, 1);
+ _finalize (cr, &cr_surf, &surf, NEXT_X, NEXT_Y + 1.0f);
+ }
+
+ // draw next-button
+ _setup (&cr_surf, &surf, NEXT_WIDTH, NEXT_HEIGHT);
+ _mask_next (cr_surf,
+ (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f,
+ TRI_WIDTH,
+ TRI_HEIGHT,
+ TRI_OFFSET);
+ _fill (cr_surf,
+ (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f,
+ (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f,
+ (double) TRI_HEIGHT,
+ BUTTON_START,
+ BUTTON_END,
+ FALSE);
+ _finalize (cr, &cr_surf, &surf, NEXT_X, NEXT_Y);
+
+ // draw pause-button drop-shadow
+ if (item->current_state == STATE_PLAYING)
+ {
+ if (item->has_focus &&
+ (item->cur_pushed_button == BUTTON_NONE || item->cur_pushed_button == BUTTON_PLAYPAUSE))
+ {
+ _setup (&cr_surf, &surf, PAUSE_WIDTH+6, PAUSE_HEIGHT+6);
+ _mask_pause (cr_surf,
+ (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f,
+ (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f,
+ BAR_WIDTH,
+ BAR_HEIGHT - 2.0f * BAR_WIDTH,
+ BAR_OFFSET);
+ _fill (cr_surf,
+ (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f,
+ (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f,
+ (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f,
+ (double) BAR_HEIGHT,
+ BUTTON_SHADOW_FOCUS,
+ BUTTON_SHADOW_FOCUS,
+ TRUE);
+ _surface_blur (surf, 3);
+ _finalize_repaint (cr, &cr_surf, &surf, PAUSE_X, PAUSE_Y + 0.5f, 3);
+ }
+ else
+ {
+ _setup (&cr_surf, &surf, PAUSE_WIDTH, PAUSE_HEIGHT);
+ _mask_pause (cr_surf,
+ (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f,
+ (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f,
+ BAR_WIDTH,
+ BAR_HEIGHT - 2.0f * BAR_WIDTH,
+ BAR_OFFSET);
+ _fill (cr_surf,
+ (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f,
+ (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f,
+ (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f,
+ (double) BAR_HEIGHT,
+ BUTTON_SHADOW,
+ BUTTON_SHADOW,
+ TRUE);
+ _surface_blur (surf, 1);
+ _finalize (cr, &cr_surf, &surf, PAUSE_X, PAUSE_Y + 1.0f);
+ }
+
+ // draw pause-button
+ _setup (&cr_surf, &surf, PAUSE_WIDTH, PAUSE_HEIGHT);
+ _mask_pause (cr_surf,
+ (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f,
+ (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f,
+ BAR_WIDTH,
+ BAR_HEIGHT - 2.0f * BAR_WIDTH,
+ BAR_OFFSET);
+ _fill (cr_surf,
+ (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f,
+ (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f,
+ (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f,
+ (double) BAR_HEIGHT,
+ BUTTON_START,
+ BUTTON_END,
+ TRUE);
+ _finalize (cr, &cr_surf, &surf, PAUSE_X, PAUSE_Y);
+ }
+ else if (item->current_state == STATE_PAUSED)
+ {
+ if (item->has_focus &&
+ (item->cur_pushed_button == BUTTON_NONE || item->cur_pushed_button == BUTTON_PLAYPAUSE))
+ {
+ _setup (&cr_surf, &surf, PLAY_WIDTH+6, PLAY_HEIGHT+6);
+ _mask_play (cr_surf,
+ PLAY_PADDING,
+ PLAY_PADDING,
+ PLAY_WIDTH - (2*PLAY_PADDING),
+ PLAY_HEIGHT - (2*PLAY_PADDING));
+ _fill (cr_surf,
+ PLAY_PADDING,
+ PLAY_PADDING,
+ PLAY_WIDTH - (2*PLAY_PADDING),
+ PLAY_HEIGHT - (2*PLAY_PADDING),
+ BUTTON_SHADOW_FOCUS,
+ BUTTON_SHADOW_FOCUS,
+ FALSE);
+ _surface_blur (surf, 3);
+ _finalize_repaint (cr, &cr_surf, &surf, PAUSE_X-0.5f, PAUSE_Y + 0.5f, 3);
+ }
+ else
+ {
+ _setup (&cr_surf, &surf, PLAY_WIDTH, PLAY_HEIGHT);
+ _mask_play (cr_surf,
+ PLAY_PADDING,
+ PLAY_PADDING,
+ PLAY_WIDTH - (2*PLAY_PADDING),
+ PLAY_HEIGHT - (2*PLAY_PADDING));
+ _fill (cr_surf,
+ PLAY_PADDING,
+ PLAY_PADDING,
+ PLAY_WIDTH - (2*PLAY_PADDING),
+ PLAY_HEIGHT - (2*PLAY_PADDING),
+ BUTTON_SHADOW,
+ BUTTON_SHADOW,
+ FALSE);
+ _surface_blur (surf, 1);
+ _finalize (cr, &cr_surf, &surf, PAUSE_X-0.75f, PAUSE_Y + 1.0f);
+ }
+
+ // draw play-button
+ _setup (&cr_surf, &surf, PLAY_WIDTH, PLAY_HEIGHT);
+ cairo_set_line_width (cr, 10.5);
+ cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
+ cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
+ _mask_play (cr_surf,
+ PLAY_PADDING,
+ PLAY_PADDING,
+ PLAY_WIDTH - (2*PLAY_PADDING),
+ PLAY_HEIGHT - (2*PLAY_PADDING));
+ _fill (cr_surf,
+ PLAY_PADDING,
+ PLAY_PADDING,
+ PLAY_WIDTH - (2*PLAY_PADDING),
+ PLAY_HEIGHT - (2*PLAY_PADDING),
+ BUTTON_START,
+ BUTTON_END,
+ FALSE);
+ _finalize (cr, &cr_surf, &surf, PAUSE_X-0.5f, PAUSE_Y);
+ }
+ else if (item->current_state == STATE_LAUNCHING)
+ {
+ // the spinner is not aligned, why? because the play button has odd width/height numbers
+ gtk_render_activity (gtk_widget_get_style_context (button), cr, 106, 6, 30, 30);
+ }
+ return FALSE;
+}
diff --git a/src/idoplaybackmenuitem.h b/src/idoplaybackmenuitem.h
new file mode 100644
index 0000000..8b6d45a
--- /dev/null
+++ b/src/idoplaybackmenuitem.h
@@ -0,0 +1,37 @@
+/*
+ * 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:
+ * Conor Curran <conor.curran@canonical.com>
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#ifndef __IDO_PLAYBACK_MENU_ITEM_H__
+#define __IDO_PLAYBACK_MENU_ITEM_H__
+
+#include <gtk/gtk.h>
+
+#define IDO_TYPE_PLAYBACK_MENU_ITEM (ido_playback_menu_item_get_type ())
+#define IDO_PLAYBACK_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_TYPE_PLAYBACK_MENU_ITEM, IdoPlaybackMenuItem))
+#define IDO_IS_PLAYBACK_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_TYPE_PLAYBACK_MENU_ITEM))
+
+typedef struct _IdoPlaybackMenuItem IdoPlaybackMenuItem;
+
+GType ido_playback_menu_item_get_type (void);
+
+GtkMenuItem * ido_playback_menu_item_new_from_model (GMenuItem *item,
+ GActionGroup *actions);
+
+#endif
diff --git a/src/idorange.c b/src/idorange.c
index 3e88fd9..acdfa5d 100644
--- a/src/idorange.c
+++ b/src/idorange.c
@@ -172,6 +172,8 @@ ido_range_init (IdoRange *range)
* @style: The range style
*
* Creates a new #IdoRange widget.
+ *
+ * Return Value: A new #IdoRange
**/
GtkWidget *
ido_range_new (GObject *adj,
diff --git a/src/idoscalemenuitem.c b/src/idoscalemenuitem.c
index 986d9a7..7aaf9d6 100644
--- a/src/idoscalemenuitem.c
+++ b/src/idoscalemenuitem.c
@@ -30,6 +30,7 @@
#include "idorange.h"
#include "idoscalemenuitem.h"
#include "idotypebuiltins.h"
+#include "idoactionhelper.h"
static void ido_scale_menu_item_set_property (GObject *object,
guint prop_id,
@@ -309,6 +310,12 @@ ido_scale_menu_item_class_init (IdoScaleMenuItemClass *item_class)
FALSE,
G_PARAM_READWRITE));
+ /**
+ * IdoScaleMenuItem::slider-grabbed:
+ * @menuitem: The #IdoScaleMenuItem emitting the signal.
+ *
+ * The ::slider-grabbed signal is emitted when the pointer selects the slider.
+ */
signals[SLIDER_GRABBED] = g_signal_new ("slider-grabbed",
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_FIRST,
@@ -317,6 +324,12 @@ ido_scale_menu_item_class_init (IdoScaleMenuItemClass *item_class)
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
+ /**
+ * IdoScaleMenuItem::slider-released:
+ * @menuitem: The #IdoScaleMenuItem emitting the signal.
+ *
+ * The ::slider-released signal is emitted when the pointer releases the slider.
+ */
signals[SLIDER_RELEASED] = g_signal_new ("slider-released",
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_FIRST,
@@ -325,6 +338,12 @@ ido_scale_menu_item_class_init (IdoScaleMenuItemClass *item_class)
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
+ /**
+ * IdoScaleMenuItem::primary-clicked:
+ * @menuitem: The #IdoScaleMenuItem emitting the signal.
+ *
+ * The ::primary-clicked signal is emitted when the pointer clicks the primary label.
+ */
signals[PRIMARY_CLICKED] = g_signal_new ("primary-clicked",
G_TYPE_FROM_CLASS (item_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
@@ -334,6 +353,12 @@ ido_scale_menu_item_class_init (IdoScaleMenuItemClass *item_class)
G_TYPE_NONE, /* return type */
0 /* n_params */);
+ /**
+ * IdoScaleMenuItem::secondary-clicked:
+ * @menuitem: The #IdoScaleMenuItem emitting the signal.
+ *
+ * The ::secondary-clicked signal is emitted when the pointer clicks the secondary label.
+ */
signals[SECONDARY_CLICKED] = g_signal_new ("secondary-clicked",
G_TYPE_FROM_CLASS (item_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
@@ -365,18 +390,18 @@ update_packing (IdoScaleMenuItem *self, IdoScaleMenuItemStyle style)
{
case IDO_SCALE_MENU_ITEM_STYLE_IMAGE:
gtk_box_pack_start (box, priv->primary_image, FALSE, FALSE, 0);
- gtk_box_pack_start (box, priv->scale, FALSE, FALSE, 0);
+ gtk_box_pack_start (box, priv->scale, TRUE, TRUE, 0);
gtk_box_pack_start (box, priv->secondary_image, FALSE, FALSE, 0);
break;
case IDO_SCALE_MENU_ITEM_STYLE_LABEL:
gtk_box_pack_start (box, priv->primary_label, FALSE, FALSE, 0);
- gtk_box_pack_start (box, priv->scale, FALSE, FALSE, 0);
+ gtk_box_pack_start (box, priv->scale, TRUE, TRUE, 0);
gtk_box_pack_start (box, priv->secondary_label, FALSE, FALSE, 0);
break;
default:
- gtk_box_pack_start (box, priv->scale, FALSE, FALSE, 0);
+ gtk_box_pack_start (box, priv->scale, TRUE, TRUE, 0);
break;
}
@@ -625,9 +650,10 @@ ido_scale_menu_item_secondary_image_notify (GtkImage *image,
* @label: the text of the new menu item.
* @size: The size style of the range.
* @adjustment: A #GtkAdjustment describing the slider value.
- * @returns: a new #IdoScaleMenuItem.
*
* Creates a new #IdoScaleMenuItem with an empty label.
+ *
+ * Return Value: a new #IdoScaleMenuItem.
**/
GtkWidget*
ido_scale_menu_item_new (const gchar *label,
@@ -647,9 +673,10 @@ ido_scale_menu_item_new (const gchar *label,
* @min: The minimum value of the slider.
* @max: The maximum value of the slider.
* @step: The step increment of the slider.
- * @returns: a new #IdoScaleMenuItem.
*
* Creates a new #IdoScaleMenuItem containing a label.
+ *
+ * Return Value: a new #IdoScaleMenuItem.
**/
GtkWidget*
ido_scale_menu_item_new_with_range (const gchar *label,
@@ -671,9 +698,10 @@ ido_scale_menu_item_new_with_range (const gchar *label,
/**
* ido_scale_menu_item_get_scale:
* @menuitem: The #IdoScaleMenuItem
- * @returns: A pointer to the scale widget.
*
* Retrieves the scale widget.
+ *
+ * Return Value: (transfer none): The #IdoRange in this item
**/
GtkWidget*
ido_scale_menu_item_get_scale (IdoScaleMenuItem *menuitem)
@@ -690,10 +718,11 @@ ido_scale_menu_item_get_scale (IdoScaleMenuItem *menuitem)
/**
* ido_scale_menu_item_get_style:
* @menuitem: The #IdoScaleMenuItem
- * @returns: A #IdoScaleMenuItemStyle enum describing the style.
*
* Retrieves the type of widgets being used for the primary and
* secondary widget slots. This could be images, labels, or nothing.
+ *
+ * Return Value: A #IdoScaleMenuItemStyle enum describing the style.
**/
IdoScaleMenuItemStyle
ido_scale_menu_item_get_style (IdoScaleMenuItem *menuitem)
@@ -707,6 +736,14 @@ ido_scale_menu_item_get_style (IdoScaleMenuItem *menuitem)
return priv->style;
}
+/**
+ * ido_scale_menu_item_set_style:
+ * @menuitem: The #IdoScaleMenuItem
+ * @style: Set the style use for the primary and secondary widget slots.
+ *
+ * Sets the type of widgets being used for the primary and
+ * secondary widget slots. This could be images, labels, or nothing.
+ **/
void
ido_scale_menu_item_set_style (IdoScaleMenuItem *menuitem,
IdoScaleMenuItemStyle style)
@@ -725,11 +762,12 @@ ido_scale_menu_item_set_style (IdoScaleMenuItem *menuitem,
/**
* ido_scale_menu_item_get_primary_image:
* @menuitem: The #IdoScaleMenuItem
- * @returns: A #GtkWidget pointer for the primary image.
*
* Retrieves a pointer to the image widget used in the primary slot.
* Whether this is visible depends upon the return value from
* ido_scale_menu_item_get_style().
+ *
+ * Return Value: (transfer none): A #GtkWidget pointer for the primary image.
**/
GtkWidget *
ido_scale_menu_item_get_primary_image (IdoScaleMenuItem *menuitem)
@@ -746,11 +784,12 @@ ido_scale_menu_item_get_primary_image (IdoScaleMenuItem *menuitem)
/**
* ido_scale_menu_item_get_secondary_image:
* @menuitem: The #IdoScaleMenuItem
- * @returns: A #GtkWidget pointer for the secondary image.
*
* Retrieves a pointer to the image widget used in the secondary slot.
* Whether this is visible depends upon the return value from
* ido_scale_menu_item_get_style().
+ *
+ * Return Value: (transfer none): A #GtkWidget pointer for the secondary image.
**/
GtkWidget *
ido_scale_menu_item_get_secondary_image (IdoScaleMenuItem *menuitem)
@@ -765,13 +804,45 @@ ido_scale_menu_item_get_secondary_image (IdoScaleMenuItem *menuitem)
}
/**
+ * ido_scale_menu_item_set_icons:
+ * @item: a #IdoScaleMenuItem
+ * @primary-icon: (allow-none): the primary icon, or %NULL
+ * @secondary-icon: (allow-non): the secondary icon, %NULL
+ *
+ * Sets the icons of @item to @primary_icon and @secondary_icon.
+ * Pass %NULL for either of them to unset the icon.
+ */
+static void
+ido_scale_menu_item_set_icons (IdoScaleMenuItem *item,
+ GIcon *primary_icon,
+ GIcon *secondary_icon)
+{
+ GtkWidget *primary;
+ GtkWidget *secondary;
+
+ primary = ido_scale_menu_item_get_primary_image (item);
+ secondary = ido_scale_menu_item_get_secondary_image (item);
+
+ if (primary_icon)
+ gtk_image_set_from_gicon (GTK_IMAGE (primary), primary_icon, GTK_ICON_SIZE_MENU);
+ else
+ gtk_image_clear (GTK_IMAGE (primary));
+
+ if (secondary_icon)
+ gtk_image_set_from_gicon (GTK_IMAGE (secondary), secondary_icon, GTK_ICON_SIZE_MENU);
+ else
+ gtk_image_clear (GTK_IMAGE (secondary));
+}
+
+/**
* ido_scale_menu_item_get_primary_label:
* @menuitem: The #IdoScaleMenuItem
- * @returns: A const gchar* string of the label text.
*
* Retrieves a string of the text for the primary label widget.
* Whether this is visible depends upon the return value from
* ido_scale_menu_item_get_style().
+ *
+ * Return Value: The label text.
**/
const gchar*
ido_scale_menu_item_get_primary_label (IdoScaleMenuItem *menuitem)
@@ -786,13 +857,14 @@ ido_scale_menu_item_get_primary_label (IdoScaleMenuItem *menuitem)
}
/**
- * ido_scale_menu_item_get_primary_label:
+ * ido_scale_menu_item_get_secondary_label:
* @menuitem: The #IdoScaleMenuItem
- * @returns: A const gchar* string of the label text.
*
- * Retrieves a string of the text for the primary label widget.
+ * Retrieves a string of the text for the secondary label widget.
* Whether this is visible depends upon the return value from
* ido_scale_menu_item_get_style().
+ *
+ * Return Value: The label text.
**/
const gchar*
ido_scale_menu_item_get_secondary_label (IdoScaleMenuItem *menuitem)
@@ -809,7 +881,7 @@ ido_scale_menu_item_get_secondary_label (IdoScaleMenuItem *menuitem)
/**
* ido_scale_menu_item_set_primary_label:
* @menuitem: The #IdoScaleMenuItem
- * @label: A string containing the label text
+ * @label: The label text
*
* Sets the text for the label widget in the primary slot. This
* widget will only be visibile if the return value of
@@ -832,11 +904,11 @@ ido_scale_menu_item_set_primary_label (IdoScaleMenuItem *menuitem,
}
/**
- * ido_scale_menu_item_set_primary_label:
+ * ido_scale_menu_item_set_secondary_label:
* @menuitem: The #IdoScaleMenuItem
- * @label: A string containing the label text
+ * @label: The label text
*
- * Sets the text for the label widget in the primary slot. This
+ * Sets the text for the label widget in the secondary slot. This
* widget will only be visibile if the return value of
* ido_scale_menu_item_get_style() is set to %IDO_SCALE_MENU_ITEM_STYLE_LABEL.
**/
@@ -859,16 +931,16 @@ ido_scale_menu_item_set_secondary_label (IdoScaleMenuItem *menuitem,
/**
* ido_scale_menu_item_primary_clicked:
* @menuitem: the #IdoScaleMenuItem
- *
+ *
* Emits the "primary-clicked" signal.
*
* The default handler for this signal lowers the scale's
* adjustment to its lower bound.
*/
void
-ido_scale_menu_item_primary_clicked (IdoScaleMenuItem * item)
+ido_scale_menu_item_primary_clicked (IdoScaleMenuItem * menuitem)
{
- g_signal_emit (item, signals[PRIMARY_CLICKED], 0);
+ g_signal_emit (menuitem, signals[PRIMARY_CLICKED], 0);
}
static void
default_primary_clicked_handler (IdoScaleMenuItem * item)
@@ -880,18 +952,18 @@ default_primary_clicked_handler (IdoScaleMenuItem * item)
}
/**
- * ido_scale_menu_item_primary_clicked:
+ * ido_scale_menu_item_secondary_clicked:
* @menuitem: the #IdoScaleMenuItem
- *
- * Emits the "primary-clicked" signal.
+ *
+ * Emits the "secondary-clicked" signal.
*
* The default handler for this signal raises the scale's
* adjustment to its upper bound.
*/
void
-ido_scale_menu_item_secondary_clicked (IdoScaleMenuItem * item)
+ido_scale_menu_item_secondary_clicked (IdoScaleMenuItem * menuitem)
{
- g_signal_emit (item, signals[SECONDARY_CLICKED], 0);
+ g_signal_emit (menuitem, signals[SECONDARY_CLICKED], 0);
}
static void
default_secondary_clicked_handler (IdoScaleMenuItem * item)
@@ -902,4 +974,99 @@ default_secondary_clicked_handler (IdoScaleMenuItem * item)
gtk_adjustment_set_value (adj, gtk_adjustment_get_upper (adj));
}
+/**
+ * ido_scale_menu_item_state_changed:
+ *
+ * Updates a IdoScaleMenuItem from @state. State must be a double which
+ * contains the current value of the slider.
+ */
+static void
+ido_scale_menu_item_state_changed (IdoActionHelper *helper,
+ GVariant *state,
+ gpointer user_data)
+{
+ GtkWidget *scale;
+
+ scale = ido_scale_menu_item_get_scale (IDO_SCALE_MENU_ITEM (ido_action_helper_get_widget (helper)));
+ gtk_range_set_value (GTK_RANGE (scale), g_variant_get_double (state));
+}
+
+static void
+ido_scale_menu_item_value_changed (GtkScale *scale,
+ gpointer user_data)
+{
+ IdoActionHelper *helper = user_data;
+ gdouble value;
+
+ value = gtk_range_get_value (GTK_RANGE (scale));
+ ido_action_helper_change_action_state (helper, g_variant_new_double (value));
+}
+
+static GIcon *
+menu_item_get_icon (GMenuItem *menuitem,
+ const gchar *attribute)
+{
+ GVariant *value;
+
+ value = g_menu_item_get_attribute_value (menuitem, attribute, NULL);
+
+ return value ? g_icon_deserialize (value) : NULL;
+}
+
+/**
+ * ido_scale_menu_item_new_from_model:
+ *
+ * Creates a new #IdoScaleMenuItem. If @menuitem contains an action, it
+ * will be bound to that action in @actions.
+ *
+ * Returns: (transfer full): a new #IdoScaleMenuItem
+ */
+GtkMenuItem *
+ido_scale_menu_item_new_from_model (GMenuItem *menuitem,
+ GActionGroup *actions)
+{
+ GtkWidget *item;
+ gchar *action;
+ gdouble min = 0.0;
+ gdouble max = 100.0;
+ gdouble step = 1.0;
+ GIcon *min_icon;
+ GIcon *max_icon;
+
+ g_menu_item_get_attribute (menuitem, "min-value", "d", &min);
+ g_menu_item_get_attribute (menuitem, "max-value", "d", &max);
+ g_menu_item_get_attribute (menuitem, "step", "d", &step);
+
+ item = ido_scale_menu_item_new_with_range ("Volume", IDO_RANGE_STYLE_DEFAULT, 0.0, min, max, step);
+ ido_scale_menu_item_set_style (IDO_SCALE_MENU_ITEM (item), IDO_SCALE_MENU_ITEM_STYLE_IMAGE);
+
+ if (g_menu_item_get_attribute (menuitem, "action", "s", &action))
+ {
+ IdoActionHelper *helper;
+ GtkWidget *scale;
+
+ helper = ido_action_helper_new (item, actions, action, NULL);
+ g_signal_connect (helper, "action-state-changed",
+ G_CALLBACK (ido_scale_menu_item_state_changed), NULL);
+
+ scale = ido_scale_menu_item_get_scale (IDO_SCALE_MENU_ITEM (item));
+ g_signal_connect (scale, "value-changed", G_CALLBACK (ido_scale_menu_item_value_changed), helper);
+
+ g_signal_connect_swapped (item, "destroy", G_CALLBACK (g_object_unref), helper);
+
+ g_free (action);
+ }
+
+ min_icon = menu_item_get_icon (menuitem, "min-icon");
+ max_icon = menu_item_get_icon (menuitem, "max-icon");
+ ido_scale_menu_item_set_icons (IDO_SCALE_MENU_ITEM (item), min_icon, max_icon);
+
+ if (min_icon)
+ g_object_unref (min_icon);
+ if (max_icon)
+ g_object_unref (max_icon);
+
+ return GTK_MENU_ITEM (item);
+}
+
#define __IDO_SCALE_MENU_ITEM_C__
diff --git a/src/idoscalemenuitem.h b/src/idoscalemenuitem.h
index 81a3474..2c32a49 100644
--- a/src/idoscalemenuitem.h
+++ b/src/idoscalemenuitem.h
@@ -61,8 +61,8 @@ struct _IdoScaleMenuItemClass
GtkMenuItemClass parent_class;
/* signal default handlers */
- void (*primary_clicked)(IdoScaleMenuItem * self);
- void (*secondary_clicked)(IdoScaleMenuItem * self);
+ void (*primary_clicked)(IdoScaleMenuItem * menuitem);
+ void (*secondary_clicked)(IdoScaleMenuItem * menuitem);
};
@@ -92,6 +92,9 @@ void ido_scale_menu_item_set_secondary_label (IdoScaleMenuItem
void ido_scale_menu_item_primary_clicked (IdoScaleMenuItem *menuitem);
void ido_scale_menu_item_secondary_clicked (IdoScaleMenuItem *menuitem);
+GtkMenuItem * ido_scale_menu_item_new_from_model (GMenuItem *menuitem,
+ GActionGroup *actions);
+
G_END_DECLS
#endif /* __IDO_SCALE_MENU_ITEM_H__ */
diff --git a/src/idoswitchmenuitem.c b/src/idoswitchmenuitem.c
index 3831336..10ff1f3 100644
--- a/src/idoswitchmenuitem.c
+++ b/src/idoswitchmenuitem.c
@@ -101,16 +101,27 @@ ido_switch_menu_button_release_event (GtkWidget * widget, GdkEventButton * event
return TRUE; /* stop the event so that it doesn't trigger popdown() */
}
-/***
-**** Public API
-***/
-
+/**
+ * ido_switch_menu_item_new:
+ *
+ * Creates a new #IdoSwitchMenuItem
+ *
+ * Return Value: a new #IdoSwitchMenuItem.
+ **/
GtkWidget *
ido_switch_menu_item_new (void)
{
return g_object_new (IDO_TYPE_SWITCH_MENU_ITEM, NULL);
}
+/**
+ * ido_switch_menu_item_get_content_area:
+ * @item: The #IdoSwitchMenuItem.
+ *
+ * Get the #GtkContainer to add additional widgets into.
+ *
+ * Return Value: (transfer none): The #GtkContainer to add additional widgets into.
+ **/
GtkContainer *
ido_switch_menu_item_get_content_area (IdoSwitchMenuItem * item)
{
diff --git a/src/idotimeline.c b/src/idotimeline.c
index c6275ae..8eea4b5 100644
--- a/src/idotimeline.c
+++ b/src/idotimeline.c
@@ -132,6 +132,12 @@ ido_timeline_class_init (IdoTimelineClass *klass)
GDK_TYPE_SCREEN,
G_PARAM_READWRITE));
+ /**
+ * IdoTimeline::started:
+ * @timeline: The #IdoTimeline emitting the signal.
+ *
+ * The ::started signal is emitted when the timeline starts.
+ */
signals[STARTED] =
g_signal_new ("started",
G_TYPE_FROM_CLASS (object_class),
@@ -141,6 +147,12 @@ ido_timeline_class_init (IdoTimelineClass *klass)
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
+ /**
+ * IdoTimeline::paused:
+ * @timeline: The #IdoTimeline emitting the signal.
+ *
+ * The ::paused signal is emitted when the timeline pauses.
+ */
signals[PAUSED] =
g_signal_new ("paused",
G_TYPE_FROM_CLASS (object_class),
@@ -150,6 +162,12 @@ ido_timeline_class_init (IdoTimelineClass *klass)
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
+ /**
+ * IdoTimeline::finished:
+ * @timeline: The #IdoTimeline emitting the signal.
+ *
+ * The ::paused signal is emitted when the timeline finishes.
+ */
signals[FINISHED] =
g_signal_new ("finished",
G_TYPE_FROM_CLASS (object_class),
@@ -159,6 +177,13 @@ ido_timeline_class_init (IdoTimelineClass *klass)
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
+ /**
+ * IdoTimeline::frame:
+ * @timeline: The #IdoTimeline emitting the signal.
+ * @progress: The progress position for this frame from 0.0 (start) to 1.0 (end).
+ *
+ * The ::frame signal is emitted when a frame should be drawn.
+ */
signals[FRAME] =
g_signal_new ("frame",
G_TYPE_FROM_CLASS (object_class),
@@ -342,6 +367,15 @@ ido_timeline_new (guint duration)
NULL);
}
+/**
+ * ido_timeline_new_for_screen:
+ * @duration: duration in milliseconds for the timeline
+ * @screen: Screen to start on.
+ *
+ * Creates a new #IdoTimeline with the specified number of frames on the given screen.
+ *
+ * Return Value: the newly created #IdoTimeline
+ **/
IdoTimeline *
ido_timeline_new_for_screen (guint duration,
GdkScreen *screen)
@@ -375,8 +409,8 @@ ido_timeline_start (IdoTimeline *timeline)
else
priv->timer = g_timer_new ();
- /* sanity check */
- g_assert (priv->fps > 0);
+ /* sanity check; CID: 12651 */
+ priv->fps = priv->fps > 0 ? priv->fps : DEFAULT_FPS;
if (priv->screen)
{
@@ -515,7 +549,8 @@ ido_timeline_set_fps (IdoTimeline *timeline,
priv = IDO_TIMELINE_GET_PRIV (timeline);
- priv->fps = fps;
+ /* Coverity CID: 12650/12651: guard against division by 0. */
+ priv->fps = fps > 0 ? fps : priv->fps;
if (ido_timeline_is_running (timeline))
{
@@ -573,6 +608,13 @@ ido_timeline_set_loop (IdoTimeline *timeline,
}
}
+/**
+ * ido_timeline_set_duration:
+ * @timeline: A #IdoTimeline
+ * @duration: Duration in milliseconds.
+ *
+ * Set the animation duration.
+ */
void
ido_timeline_set_duration (IdoTimeline *timeline,
guint duration)
@@ -590,6 +632,14 @@ ido_timeline_set_duration (IdoTimeline *timeline,
}
}
+/**
+ * ido_timeline_get_duration:
+ * @timeline: A #IdoTimeline
+ *
+ * Set the animation duration.
+ *
+ * Return Value: Duration in milliseconds.
+ */
guint
ido_timeline_get_duration (IdoTimeline *timeline)
{
@@ -645,6 +695,13 @@ ido_timeline_get_direction (IdoTimeline *timeline)
return priv->direction;
}
+/**
+ * ido_timeline_set_screen:
+ * @timeline: A #IdoTimeline
+ * @screen: A #GdkScreen to use
+ *
+ * Set the screen the timeline is running on.
+ */
void
ido_timeline_set_screen (IdoTimeline *timeline,
GdkScreen *screen)
@@ -664,6 +721,14 @@ ido_timeline_set_screen (IdoTimeline *timeline,
g_object_notify (G_OBJECT (timeline), "screen");
}
+/**
+ * ido_timeline_get_screen:
+ * @timeline: A #IdoTimeline
+ *
+ * Get the screen this timeline is running on.
+ *
+ * Return Value: (transfer none): The #GdkScreen this timeline is running on.
+ */
GdkScreen *
ido_timeline_get_screen (IdoTimeline *timeline)
{
@@ -675,6 +740,14 @@ ido_timeline_get_screen (IdoTimeline *timeline)
return priv->screen;
}
+/**
+ * ido_timeline_get_progress:
+ * @timeline: A #IdoTimeline
+ *
+ * Get the progress on the timeline.
+ *
+ * Return Value: The progress from 0.0 (start) to 1.0 (end)
+ */
gdouble
ido_timeline_get_progress (IdoTimeline *timeline)
{
@@ -686,6 +759,13 @@ ido_timeline_get_progress (IdoTimeline *timeline)
return priv->progress;
}
+/**
+ * ido_timeline_set_progress:
+ * @timeline: A #IdoTimeline
+ * @progress: The progress from 0.0 (start) to 1.0 (end)
+ *
+ * Set the progress on the timeline.
+ */
void
ido_timeline_set_progress (IdoTimeline *timeline, gdouble progress)
{
@@ -707,6 +787,15 @@ ido_timeline_set_progress (IdoTimeline *timeline, gdouble progress)
ido_timeline_start (timeline);
}
+/**
+ * ido_timeline_calculate_progress:
+ * @linear_progress: The progress from 0.0 (start) to 1.0 (end)
+ * @progress_type: The progress transform to apply
+ *
+ * Transform a linear progress position using the given transform.
+ *
+ * Return Value: the progress position using the provided transform.
+ */
gdouble
ido_timeline_calculate_progress (gdouble linear_progress,
IdoTimelineProgressType progress_type)
diff --git a/src/idousermenuitem.c b/src/idousermenuitem.c
new file mode 100644
index 0000000..655ae21
--- /dev/null
+++ b/src/idousermenuitem.c
@@ -0,0 +1,468 @@
+/*
+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,
+ 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;
+ GIcon * icon;
+};
+
+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:
+ g_value_set_object (value, self->priv->icon);
+ 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:
+ ido_user_menu_item_set_icon (self, g_value_get_object (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)
+{
+ IdoUserMenuItem * self = IDO_USER_MENU_ITEM (object);
+
+ g_clear_object (&self->priv->icon);
+
+ 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_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] = g_param_spec_object ("icon",
+ "Icon",
+ "The user's GIcon",
+ G_TYPE_OBJECT,
+ 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, GIcon * icon)
+{
+ IdoUserMenuItemPrivate * p = self->priv;
+ GtkImage * image = GTK_IMAGE (p->user_image);
+
+ g_clear_object (&p->icon);
+
+ if (icon != NULL)
+ g_object_ref (icon);
+ else
+ icon = g_themed_icon_new_with_default_fallbacks (FALLBACK_ICON_NAME);
+
+ gtk_image_set_from_gicon (image, icon, GTK_ICON_SIZE_MENU);
+}
+
+void
+ido_user_menu_item_set_icon_from_file (IdoUserMenuItem * self, const char * filename)
+{
+ GFile * file = g_file_new_for_path (filename);
+ GIcon * icon = g_file_icon_new (file);
+
+ ido_user_menu_item_set_icon (self, icon);
+
+ g_clear_object (&icon);
+ g_clear_object (&file);
+}
+
+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)
+{
+ guint i;
+ guint n;
+ IdoUserMenuItem * ido_user;
+ gchar * str;
+ gchar * action;
+ GVariant * v;
+ GParameter parameters[4];
+
+ /* create the ido_user */
+
+ n = 0;
+
+ if (g_menu_item_get_attribute (menuitem, "label", "s", &str))
+ {
+ GParameter p = { "label", G_VALUE_INIT };
+ g_value_init (&p.value, G_TYPE_STRING);
+ g_value_take_string (&p.value, str);
+ parameters[n++] = p;
+ }
+
+ if ((v = g_menu_item_get_attribute_value (menuitem, G_MENU_ATTRIBUTE_ICON, NULL)))
+ {
+ GParameter p = { "icon", G_VALUE_INIT };
+ GIcon * icon = g_icon_deserialize (v);
+ g_value_init (&p.value, G_TYPE_OBJECT);
+ g_value_take_object (&p.value, icon);
+ g_variant_unref (v);
+ parameters[n++] = p;
+ }
+
+ g_assert (n <= G_N_ELEMENTS (parameters));
+ ido_user = g_object_newv (IDO_USER_MENU_ITEM_TYPE, n, parameters);
+
+ for (i=0; i<n; i++)
+ g_value_unset (&parameters[i].value);
+
+ /* gie it an ActionHelper */
+
+ 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 (ido_user), actions, action, target);
+ g_signal_connect (helper, "action-state-changed",
+ G_CALLBACK (user_menu_item_state_changed), NULL);
+
+ g_signal_connect_object (ido_user, "activate",
+ G_CALLBACK (ido_action_helper_activate),
+ helper, G_CONNECT_SWAPPED);
+ g_signal_connect_swapped (ido_user, "destroy", G_CALLBACK (g_object_unref), helper);
+
+ if (target)
+ g_variant_unref (target);
+ g_free (action);
+ }
+
+ return GTK_MENU_ITEM (ido_user);
+}
+
diff --git a/src/idousermenuitem.h b/src/idousermenuitem.h
new file mode 100644
index 0000000..89e9a12
--- /dev/null
+++ b/src/idousermenuitem.h
@@ -0,0 +1,70 @@
+/*
+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, GIcon * icon);
+void ido_user_menu_item_set_icon_from_file (IdoUserMenuItem * self, const char * filename);
+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__ */