diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 71 | ||||
-rw-r--r-- | src/idoactionhelper.c | 428 | ||||
-rw-r--r-- | src/idoactionhelper.h | 47 | ||||
-rw-r--r-- | src/idoappointmentmenuitem.c | 483 | ||||
-rw-r--r-- | src/idoappointmentmenuitem.h | 79 | ||||
-rw-r--r-- | src/idocalendarmenuitem.c | 255 | ||||
-rw-r--r-- | src/idocalendarmenuitem.h | 7 | ||||
-rw-r--r-- | src/idoentrymenuitem.c | 22 | ||||
-rw-r--r-- | src/idolocationmenuitem.c | 477 | ||||
-rw-r--r-- | src/idolocationmenuitem.h | 73 | ||||
-rw-r--r-- | src/idomediaplayermenuitem.c | 376 | ||||
-rw-r--r-- | src/idomediaplayermenuitem.h | 42 | ||||
-rw-r--r-- | src/idomenuitemfactory.c | 92 | ||||
-rw-r--r-- | src/idomessagedialog.c | 21 | ||||
-rw-r--r-- | src/idomessagedialog.h | 2 | ||||
-rw-r--r-- | src/idoplaybackmenuitem.c | 1680 | ||||
-rw-r--r-- | src/idoplaybackmenuitem.h | 37 | ||||
-rw-r--r-- | src/idorange.c | 2 | ||||
-rw-r--r-- | src/idoscalemenuitem.c | 217 | ||||
-rw-r--r-- | src/idoscalemenuitem.h | 7 | ||||
-rw-r--r-- | src/idoswitchmenuitem.c | 19 | ||||
-rw-r--r-- | src/idotimeline.c | 95 | ||||
-rw-r--r-- | src/idousermenuitem.c | 468 | ||||
-rw-r--r-- | src/idousermenuitem.h | 70 | ||||
-rw-r--r-- | src/libido.c | 36 | ||||
-rw-r--r-- | src/libido.h | 2 |
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 (¶meters[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 (¶meters[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 (¶meters[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__ */ |