aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Uebernickel <lars.uebernickel@canonical.com>2013-08-13 18:50:26 +0200
committerLars Uebernickel <lars.uebernickel@canonical.com>2013-08-13 18:50:26 +0200
commitbe7301a144e10765a5bcc610b38e0a9e7df4d5cc (patch)
tree0f87d2d226ba4ddaf22f292578d33a302308d596
parent8b1bba629a99cb8d4afd4d114db66f637acf222c (diff)
downloadayatana-ido-be7301a144e10765a5bcc610b38e0a9e7df4d5cc.tar.gz
ayatana-ido-be7301a144e10765a5bcc610b38e0a9e7df4d5cc.tar.bz2
ayatana-ido-be7301a144e10765a5bcc610b38e0a9e7df4d5cc.zip
Add IdoSourceMenuItem
A menu item showing messaging menu sources.
-rw-r--r--src/Makefile.am8
-rw-r--r--src/idoactionhelper.c20
-rw-r--r--src/idoactionhelper.h3
-rw-r--r--src/idodetaillabel.c401
-rw-r--r--src/idodetaillabel.h59
-rw-r--r--src/idomenuitemfactory.c4
-rw-r--r--src/idosourcemenuitem.c264
-rw-r--r--src/idosourcemenuitem.h36
8 files changed, 793 insertions, 2 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 177e859..a545094 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -28,7 +28,9 @@ sources_h = \
idoactionhelper.h \
idomediaplayermenuitem.h \
idoplaybackmenuitem.h \
- idoapplicationmenuitem.h
+ idoapplicationmenuitem.h \
+ idodetaillabel.h \
+ idosourcemenuitem.h
EXTRA_DIST = \
ido.list \
@@ -88,7 +90,9 @@ libido_0_1_la_SOURCES = \
idobasicmenuitem.c \
idotimestampmenuitem.c \
idolocationmenuitem.c \
- idoapplicationmenuitem.c
+ idoapplicationmenuitem.c \
+ idodetaillabel.c \
+ idosourcemenuitem.c
libido3_0_1_la_SOURCES = $(libido_0_1_la_SOURCES)
diff --git a/src/idoactionhelper.c b/src/idoactionhelper.c
index f0e300b..8ce5691 100644
--- a/src/idoactionhelper.c
+++ b/src/idoactionhelper.c
@@ -403,6 +403,26 @@ ido_action_helper_activate (IdoActionHelper *helper)
}
/**
+ * ido_action_helper_activate_with_parameter:
+ * @helper: an #IdoActionHelper
+ * @parameter: a #GVariant containing the parameter
+ *
+ * Activates the action that is associated with this helper passing
+ * @parameter instead the "target" associated with the menu item this
+ * helper is bound to.
+ */
+void
+ido_action_helper_activate_with_parameter (IdoActionHelper *helper,
+ GVariant *parameter)
+{
+ g_return_if_fail (IDO_IS_ACTION_HELPER (helper));
+ g_return_if_fail (parameter != NULL);
+
+ if (helper->actions && helper->action_name)
+ g_action_group_activate_action (helper->actions, helper->action_name, parameter);
+}
+
+/**
* ido_action_helper_change_action_state:
* @helper: an #IdoActionHelper
* @state: the proposed new state of the action
diff --git a/src/idoactionhelper.h b/src/idoactionhelper.h
index 27dafb7..81977fb 100644
--- a/src/idoactionhelper.h
+++ b/src/idoactionhelper.h
@@ -41,6 +41,9 @@ GVariant * ido_action_helper_get_action_target (IdoActionHelper *helper
void ido_action_helper_activate (IdoActionHelper *helper);
+void ido_action_helper_activate_with_parameter (IdoActionHelper *helper,
+ GVariant *parameter);
+
void ido_action_helper_change_action_state (IdoActionHelper *helper,
GVariant *state);
diff --git a/src/idodetaillabel.c b/src/idodetaillabel.c
new file mode 100644
index 0000000..b36b222
--- /dev/null
+++ b/src/idodetaillabel.c
@@ -0,0 +1,401 @@
+/*
+ * Copyright 2012 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 "idodetaillabel.h"
+
+#include <math.h>
+
+G_DEFINE_TYPE (IdoDetailLabel, ido_detail_label, GTK_TYPE_WIDGET)
+
+struct _IdoDetailLabelPrivate
+{
+ gchar *text;
+ PangoLayout *layout;
+ gboolean draw_lozenge;
+};
+
+enum
+{
+ PROP_0,
+ PROP_TEXT,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES];
+
+static void
+ido_detail_label_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdoDetailLabel *self = IDO_DETAIL_LABEL (object);
+
+ switch (property_id)
+ {
+ case PROP_TEXT:
+ g_value_set_string (value, self->priv->text);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+ido_detail_label_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdoDetailLabel *self = IDO_DETAIL_LABEL (object);
+
+ switch (property_id)
+ {
+ case PROP_TEXT:
+ ido_detail_label_set_text (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+
+static void
+ido_detail_label_finalize (GObject *object)
+{
+ IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (object)->priv;
+
+ g_free (priv->text);
+
+ G_OBJECT_CLASS (ido_detail_label_parent_class)->finalize (object);
+}
+
+static void
+ido_detail_label_dispose (GObject *object)
+{
+ IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (object)->priv;
+
+ g_clear_object (&priv->layout);
+
+ G_OBJECT_CLASS (ido_detail_label_parent_class)->dispose (object);
+}
+
+static void
+ido_detail_label_ensure_layout (IdoDetailLabel *label)
+{
+ IdoDetailLabelPrivate *priv = label->priv;
+
+ if (priv->layout == NULL)
+ {
+ priv->layout = gtk_widget_create_pango_layout (GTK_WIDGET (label), priv->text);
+ pango_layout_set_alignment (priv->layout, PANGO_ALIGN_CENTER);
+ pango_layout_set_ellipsize (priv->layout, PANGO_ELLIPSIZE_END);
+ pango_layout_set_height (priv->layout, -1);
+
+ // TODO update layout on "style-updated" and "direction-changed"
+ }
+}
+
+static void
+cairo_lozenge (cairo_t *cr,
+ double x,
+ double y,
+ double w,
+ double h,
+ double radius)
+{
+ double x1 = x + w - radius;
+ double x2 = x + radius;
+ double y1 = y + radius;
+ double y2 = y + h - radius;
+
+ cairo_move_to (cr, x + radius, y);
+ cairo_arc (cr, x1, y1, radius, G_PI * 1.5, G_PI * 2);
+ cairo_arc (cr, x1, y2, radius, 0, G_PI * 0.5);
+ cairo_arc (cr, x2, y2, radius, G_PI * 0.5, G_PI);
+ cairo_arc (cr, x2, y1, radius, G_PI, G_PI * 1.5);
+}
+
+static PangoFontMetrics *
+gtk_widget_get_font_metrics (GtkWidget *widget,
+ PangoContext *context)
+{
+ PangoFontDescription *font;
+ PangoFontMetrics *metrics;
+
+ gtk_style_context_get (gtk_widget_get_style_context (widget),
+ gtk_widget_get_state_flags (widget),
+ "font", &font, NULL);
+
+ metrics = pango_context_get_metrics (context,
+ font,
+ pango_context_get_language (context));
+
+ pango_font_description_free (font);
+ return metrics;
+}
+
+static gint
+ido_detail_label_get_minimum_text_width (IdoDetailLabel *label)
+{
+ IdoDetailLabelPrivate *priv = label->priv;
+ PangoContext *context;
+ PangoFontMetrics *metrics;
+ gint char_width;
+ gint w;
+
+ context = pango_layout_get_context (priv->layout);
+ metrics = gtk_widget_get_font_metrics (GTK_WIDGET (label), context);
+ char_width = pango_font_metrics_get_approximate_digit_width (metrics);
+
+ w = 2 * char_width / PANGO_SCALE;
+ pango_font_metrics_unref (metrics);
+ return w;
+}
+
+static gboolean
+ido_detail_label_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ IdoDetailLabel *label = IDO_DETAIL_LABEL (widget);
+ IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (widget)->priv;
+ PangoRectangle extents;
+ GtkAllocation allocation;
+ double x, w, h, radius;
+ GdkRGBA color;
+
+ if (!priv->text || !*priv->text)
+ return TRUE;
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ ido_detail_label_ensure_layout (IDO_DETAIL_LABEL (widget));
+
+ pango_layout_get_extents (priv->layout, NULL, &extents);
+ pango_extents_to_pixels (&extents, NULL);
+
+ h = MIN (allocation.height, extents.height);
+ radius = floor (h / 2.0);
+ w = MAX (ido_detail_label_get_minimum_text_width (label), extents.width) + 2.0 * radius;
+ x = allocation.width - w;
+
+ pango_layout_set_width (priv->layout, (allocation.width - 2 * radius) * PANGO_SCALE);
+ pango_layout_get_extents (priv->layout, NULL, &extents);
+ pango_extents_to_pixels (&extents, NULL);
+
+ gtk_style_context_get_color (gtk_widget_get_style_context (widget),
+ gtk_widget_get_state_flags (widget),
+ &color);
+ gdk_cairo_set_source_rgba (cr, &color);
+
+ cairo_set_line_width (cr, 1.0);
+ cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
+
+ if (priv->draw_lozenge)
+ cairo_lozenge (cr, x, 0.0, w, h, radius);
+
+ cairo_move_to (cr, x + radius, (allocation.height - extents.height) / 2.0);
+ pango_cairo_layout_path (cr, priv->layout);
+ cairo_fill (cr);
+
+ return TRUE;
+}
+
+static void
+ido_detail_label_get_preferred_width (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (widget)->priv;
+ PangoRectangle extents;
+ double radius;
+
+ ido_detail_label_ensure_layout (IDO_DETAIL_LABEL (widget));
+
+ pango_layout_get_extents (priv->layout, NULL, &extents);
+ pango_extents_to_pixels (&extents, NULL);
+
+ radius = floor (extents.height / 2.0);
+
+ *minimum = ido_detail_label_get_minimum_text_width (IDO_DETAIL_LABEL (widget)) + 2.0 * radius;
+ *natural = MAX (*minimum, extents.width + 2.0 * radius);
+}
+
+static void
+ido_detail_label_get_preferred_height (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (widget)->priv;
+ PangoContext *context;
+ PangoFontMetrics *metrics;
+ PangoRectangle extents;
+
+ ido_detail_label_ensure_layout (IDO_DETAIL_LABEL (widget));
+
+ pango_layout_get_extents (priv->layout, NULL, &extents);
+ pango_extents_to_pixels (&extents, NULL);
+ context = pango_layout_get_context (priv->layout);
+ metrics = gtk_widget_get_font_metrics (widget, context);
+
+ *minimum = *natural = (pango_font_metrics_get_ascent (metrics) +
+ pango_font_metrics_get_descent (metrics)) / PANGO_SCALE;
+
+ pango_font_metrics_unref (metrics);
+}
+
+static void
+ido_detail_label_class_init (IdoDetailLabelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = ido_detail_label_get_property;
+ object_class->set_property = ido_detail_label_set_property;
+ object_class->finalize = ido_detail_label_finalize;
+ object_class->dispose = ido_detail_label_dispose;
+
+ widget_class->draw = ido_detail_label_draw;
+ widget_class->get_preferred_width = ido_detail_label_get_preferred_width;
+ widget_class->get_preferred_height = ido_detail_label_get_preferred_height;
+
+ g_type_class_add_private (klass, sizeof (IdoDetailLabelPrivate));
+
+ properties[PROP_TEXT] = g_param_spec_string ("text",
+ "Text",
+ "The text of the label",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
+}
+
+static void
+ido_detail_label_init (IdoDetailLabel *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ IDO_TYPE_DETAIL_LABEL,
+ IdoDetailLabelPrivate);
+
+ gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+}
+
+GtkWidget *
+ido_detail_label_new (const gchar *label)
+{
+ return g_object_new (IDO_TYPE_DETAIL_LABEL,
+ "text", label,
+ NULL);
+}
+
+const gchar *
+ido_detail_label_get_text (IdoDetailLabel *label)
+{
+ g_return_val_if_fail (IDO_IS_DETAIL_LABEL (label), NULL);
+ return label->priv->text;
+}
+
+/* collapse_whitespace:
+ * @str: the source string
+ *
+ * Collapses all occurences of consecutive whitespace charactes in @str
+ * into a single space.
+ *
+ * Returns: (transfer full): a newly-allocated string
+ */
+static gchar *
+collapse_whitespace (const gchar *str)
+{
+ GString *result;
+ gboolean in_space = FALSE;
+
+ if (str == NULL)
+ return NULL;
+
+ result = g_string_new ("");
+
+ while (*str)
+ {
+ gunichar c = g_utf8_get_char_validated (str, -1);
+
+ if (c == (gunichar) -1)
+ break;
+
+ if (!g_unichar_isspace (c))
+ {
+ g_string_append_unichar (result, c);
+ in_space = FALSE;
+ }
+ else if (!in_space)
+ {
+ g_string_append_c (result, ' ');
+ in_space = TRUE;
+ }
+
+ str = g_utf8_next_char (str);
+ }
+
+ return g_string_free (result, FALSE);
+}
+
+static void
+ido_detail_label_set_text_impl (IdoDetailLabel *label,
+ const gchar *text,
+ gboolean draw_lozenge)
+{
+ IdoDetailLabelPrivate * priv = label->priv;
+
+ g_clear_object (&priv->layout);
+ g_free (priv->text);
+
+ priv->text = g_strdup (text);
+ priv->draw_lozenge = draw_lozenge;
+
+ g_object_notify_by_pspec (G_OBJECT (label), properties[PROP_TEXT]);
+ gtk_widget_queue_resize (GTK_WIDGET (label));
+}
+
+void
+ido_detail_label_set_text (IdoDetailLabel *label,
+ const gchar *text)
+{
+ gchar *str;
+
+ g_return_if_fail (IDO_IS_DETAIL_LABEL (label));
+
+ str = collapse_whitespace (text);
+ ido_detail_label_set_text_impl (label, str, FALSE);
+ g_free (str);
+}
+
+void
+ido_detail_label_set_count (IdoDetailLabel *label,
+ gint count)
+{
+ gchar *text;
+
+ g_return_if_fail (IDO_IS_DETAIL_LABEL (label));
+
+ text = g_strdup_printf ("%d", count);
+ ido_detail_label_set_text_impl (label, text, TRUE);
+ g_free (text);
+}
diff --git a/src/idodetaillabel.h b/src/idodetaillabel.h
new file mode 100644
index 0000000..1995fee
--- /dev/null
+++ b/src/idodetaillabel.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 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_DETAIL_LABEL_H__
+#define __IDO_DETAIL_LABEL_H__
+
+#include <gtk/gtk.h>
+
+#define IDO_TYPE_DETAIL_LABEL (ido_detail_label_get_type())
+#define IDO_DETAIL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_TYPE_DETAIL_LABEL, IdoDetailLabel))
+#define IDO_DETAIL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IDO_TYPE_DETAIL_LABEL, IdoDetailLabelClass))
+#define IDO_IS_DETAIL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_TYPE_DETAIL_LABEL))
+#define IDO_IS_DETAIL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IDO_TYPE_DETAIL_LABEL))
+#define IDO_DETAIL_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IDO_TYPE_DETAIL_LABEL, IdoDetailLabelClass))
+
+typedef struct _IdoDetailLabel IdoDetailLabel;
+typedef struct _IdoDetailLabelClass IdoDetailLabelClass;
+typedef struct _IdoDetailLabelPrivate IdoDetailLabelPrivate;
+
+struct _IdoDetailLabel
+{
+ GtkWidget parent;
+ IdoDetailLabelPrivate *priv;
+};
+
+struct _IdoDetailLabelClass
+{
+ GtkWidgetClass parent_class;
+};
+
+GType ido_detail_label_get_type (void) G_GNUC_CONST;
+
+GtkWidget * ido_detail_label_new (const gchar *str);
+
+const gchar * ido_detail_label_get_text (IdoDetailLabel *label);
+
+void ido_detail_label_set_text (IdoDetailLabel *label,
+ const gchar *text);
+
+void ido_detail_label_set_count (IdoDetailLabel *label,
+ gint count);
+
+#endif
diff --git a/src/idomenuitemfactory.c b/src/idomenuitemfactory.c
index 8802a7d..376134e 100644
--- a/src/idomenuitemfactory.c
+++ b/src/idomenuitemfactory.c
@@ -30,6 +30,7 @@
#include "idomediaplayermenuitem.h"
#include "idoplaybackmenuitem.h"
#include "idoapplicationmenuitem.h"
+#include "idosourcemenuitem.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))
@@ -87,6 +88,9 @@ ido_menu_item_factory_create_menu_item (UbuntuMenuItemFactory *factory,
else if (g_str_equal (type, "com.canonical.application"))
item = ido_application_menu_item_new_from_model (menuitem, actions);
+ else if (g_str_equal (type, "com.canonical.indicator.messages.source"))
+ item = ido_source_menu_item_new_from_menu_model (menuitem, actions);
+
return item;
}
diff --git a/src/idosourcemenuitem.c b/src/idosourcemenuitem.c
new file mode 100644
index 0000000..6bc2ffa
--- /dev/null
+++ b/src/idosourcemenuitem.c
@@ -0,0 +1,264 @@
+/*
+ * 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 "idosourcemenuitem.h"
+
+#include <libintl.h>
+#include "idodetaillabel.h"
+#include "idoactionhelper.h"
+
+typedef GtkMenuItemClass IdoSourceMenuItemClass;
+
+struct _IdoSourceMenuItem
+{
+ GtkMenuItem parent;
+
+ GtkWidget *icon;
+ GtkWidget *label;
+ GtkWidget *detail;
+
+ gint64 time;
+ guint timer_id;
+};
+
+G_DEFINE_TYPE (IdoSourceMenuItem, ido_source_menu_item, GTK_TYPE_MENU_ITEM);
+
+static void
+ido_source_menu_item_constructed (GObject *object)
+{
+ IdoSourceMenuItem *item = IDO_SOURCE_MENU_ITEM (object);
+ GtkWidget *grid;
+ gint icon_width;
+
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &icon_width, NULL);
+
+ item->icon = g_object_ref (gtk_image_new ());
+ gtk_widget_set_margin_left (item->icon, icon_width);
+ gtk_widget_set_margin_right (item->icon, 6);
+
+ item->label = g_object_ref (gtk_label_new (""));
+ gtk_label_set_max_width_chars (GTK_LABEL (item->label), 40);
+ gtk_label_set_ellipsize (GTK_LABEL (item->label), PANGO_ELLIPSIZE_END);
+ gtk_misc_set_alignment (GTK_MISC (item->label), 0.0, 0.5);
+
+ item->detail = g_object_ref (ido_detail_label_new (""));
+ gtk_widget_set_halign (item->detail, GTK_ALIGN_END);
+ gtk_widget_set_hexpand (item->detail, TRUE);
+ gtk_style_context_add_class (gtk_widget_get_style_context (item->detail), "accelerator");
+
+ grid = gtk_grid_new ();
+ gtk_grid_attach (GTK_GRID (grid), item->icon, 0, 0, 1, 1);
+ gtk_grid_attach (GTK_GRID (grid), item->label, 1, 0, 1, 1);
+ gtk_grid_attach (GTK_GRID (grid), item->detail, 2, 0, 1, 1);
+
+ gtk_container_add (GTK_CONTAINER (object), grid);
+ gtk_widget_show_all (grid);
+
+ G_OBJECT_CLASS (ido_source_menu_item_parent_class)->constructed (object);
+}
+
+static gchar *
+ido_source_menu_item_time_span_string (gint64 timestamp)
+{
+ gchar *str;
+ gint64 span;
+ gint hours;
+ gint minutes;
+
+ span = MAX (g_get_real_time () - timestamp, 0) / G_USEC_PER_SEC;
+ hours = span / 3600;
+ minutes = (span / 60) % 60;
+
+ if (hours == 0)
+ {
+ /* TRANSLATORS: number of minutes that have passed */
+ str = g_strdup_printf (ngettext ("%d min", "%d min", minutes), minutes);
+ }
+ else
+ {
+ /* TRANSLATORS: number of hours that have passed */
+ str = g_strdup_printf (ngettext ("%d h", "%d h", hours), hours);
+ }
+
+ return str;
+}
+
+static void
+ido_source_menu_item_set_detail_time (IdoSourceMenuItem *self,
+ gint64 time)
+{
+ gchar *str;
+
+ self->time = time;
+
+ str = ido_source_menu_item_time_span_string (self->time);
+ ido_detail_label_set_text (IDO_DETAIL_LABEL (self->detail), str);
+
+ g_free (str);
+}
+
+static gboolean
+ido_source_menu_item_update_time (gpointer data)
+{
+ IdoSourceMenuItem *self = data;
+
+ ido_source_menu_item_set_detail_time (self, self->time);
+
+ return TRUE;
+}
+
+static void
+ido_source_menu_item_dispose (GObject *object)
+{
+ IdoSourceMenuItem *self = IDO_SOURCE_MENU_ITEM (object);
+
+ if (self->timer_id != 0)
+ {
+ g_source_remove (self->timer_id);
+ self->timer_id = 0;
+ }
+
+ g_clear_object (&self->icon);
+ g_clear_object (&self->label);
+ g_clear_object (&self->detail);
+
+ G_OBJECT_CLASS (ido_source_menu_item_parent_class)->dispose (object);
+}
+
+static void
+ido_source_menu_item_class_init (IdoSourceMenuItemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = ido_source_menu_item_constructed;
+ object_class->dispose = ido_source_menu_item_dispose;
+}
+
+static void
+ido_source_menu_item_init (IdoSourceMenuItem *self)
+{
+}
+
+static void
+ido_source_menu_item_set_label (IdoSourceMenuItem *item,
+ const gchar *label)
+{
+ gtk_label_set_label (GTK_LABEL (item->label), label ? label : "");
+}
+
+static void
+ido_source_menu_item_set_icon (IdoSourceMenuItem *item,
+ GIcon *icon)
+{
+ if (icon)
+ gtk_image_set_from_gicon (GTK_IMAGE (item->icon), icon, GTK_ICON_SIZE_MENU);
+ else
+ gtk_image_clear (GTK_IMAGE (item->icon));
+}
+
+
+static void
+ido_source_menu_item_activate (GtkMenuItem *item,
+ gpointer user_data)
+{
+ IdoActionHelper *helper = user_data;
+
+ /* The parameter signifies whether this source was activated (TRUE) or
+ * dismissed (FALSE). Since there's no UI to dismiss a gtkmenuitem,
+ * this always passes TRUE. */
+ ido_action_helper_activate_with_parameter (helper, g_variant_new_boolean (TRUE));
+}
+
+static void
+ido_source_menu_item_state_changed (IdoActionHelper *helper,
+ GVariant *state,
+ gpointer user_data)
+{
+ IdoSourceMenuItem *item = user_data;
+ guint32 count;
+ gint64 time;
+ const gchar *str;
+
+ if (item->timer_id != 0)
+ {
+ g_source_remove (item->timer_id);
+ item->timer_id = 0;
+ }
+
+ g_return_val_if_fail (g_variant_is_of_type (state, G_VARIANT_TYPE ("(uxsb)")), FALSE);
+
+ g_variant_get (state, "(ux&sb)", &count, &time, &str, NULL);
+
+ if (count != 0)
+ ido_detail_label_set_count (IDO_DETAIL_LABEL (item->detail), count);
+ else if (time != 0)
+ {
+ ido_source_menu_item_set_detail_time (item, time);
+ item->timer_id = g_timeout_add_seconds (59, ido_source_menu_item_update_time, item);
+ }
+ else if (str != NULL && *str)
+ ido_detail_label_set_text (IDO_DETAIL_LABEL (item->detail), str);
+}
+
+GtkMenuItem *
+ido_source_menu_item_new_from_menu_model (GMenuItem *menuitem,
+ GActionGroup *actions)
+{
+ GtkMenuItem *item;
+ GVariant *serialized_icon;
+ GIcon *icon = NULL;
+ gchar *label;
+ gchar *action = NULL;
+
+ item = g_object_new (IDO_TYPE_SOURCE_MENU_ITEM, NULL);
+
+ if (g_menu_item_get_attribute (menuitem, "label", "s", &label))
+ {
+ ido_source_menu_item_set_label (IDO_SOURCE_MENU_ITEM (item), label);
+ g_free (label);
+ }
+
+ serialized_icon = g_menu_item_get_attribute_value (menuitem, "icon", NULL);
+ if (serialized_icon)
+ {
+ icon = g_icon_deserialize (serialized_icon);
+ g_variant_unref (serialized_icon);
+ }
+ ido_source_menu_item_set_icon (IDO_SOURCE_MENU_ITEM (item), icon);
+
+ if (g_menu_item_get_attribute (menuitem, "action", "s", &action))
+ {
+ IdoActionHelper *helper;
+
+ helper = ido_action_helper_new (GTK_WIDGET (item), actions, action, NULL);
+ g_signal_connect (helper, "action-state-changed",
+ G_CALLBACK (ido_source_menu_item_state_changed), item);
+ g_signal_connect_object (item, "activate",
+ G_CALLBACK (ido_source_menu_item_activate), helper,
+ 0);
+ g_signal_connect_swapped (item, "destroy", G_CALLBACK (g_object_unref), helper);
+
+ g_free (action);
+ }
+
+ if (icon)
+ g_object_unref (icon);
+
+ return item;
+}
diff --git a/src/idosourcemenuitem.h b/src/idosourcemenuitem.h
new file mode 100644
index 0000000..0a12572
--- /dev/null
+++ b/src/idosourcemenuitem.h
@@ -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>
+ */
+
+#ifndef __IDO_SOURCE_MENU_ITEM_H__
+#define __IDO_SOURCE_MENU_ITEM_H__
+
+#include <gtk/gtk.h>
+
+#define IDO_TYPE_SOURCE_MENU_ITEM (ido_source_menu_item_get_type ())
+#define IDO_SOURCE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_TYPE_SOURCE_MENU_ITEM, IdoSourceMenuItem))
+#define IDO_IS_SOURCE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_TYPE_SOURCE_MENU_ITEM))
+
+typedef struct _IdoSourceMenuItem IdoSourceMenuItem;
+
+GType ido_source_menu_item_get_type (void);
+
+GtkMenuItem * ido_source_menu_item_new_from_menu_model (GMenuItem *menuitem,
+ GActionGroup *actions);
+
+#endif