/* * 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 . * * Authors: * Lars Uebernickel */ #include "im-source-menu-item.h" #include #include "ido-detail-label.h" struct _ImSourceMenuItemPrivate { GActionGroup *action_group; gchar *action; GtkWidget *icon; GtkWidget *label; GtkWidget *detail; gint64 time; guint timer_id; }; enum { PROP_0, PROP_MENU_ITEM, PROP_ACTION_GROUP, NUM_PROPERTIES }; static GParamSpec *properties[NUM_PROPERTIES]; G_DEFINE_TYPE (ImSourceMenuItem, im_source_menu_item, GTK_TYPE_MENU_ITEM); static void im_source_menu_item_constructed (GObject *object) { ImSourceMenuItemPrivate *priv = IM_SOURCE_MENU_ITEM (object)->priv; GtkWidget *grid; gint icon_width; gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &icon_width, NULL); priv->icon = g_object_ref (gtk_image_new ()); gtk_widget_set_margin_left (priv->icon, icon_width + 6); priv->label = g_object_ref (gtk_label_new ("")); gtk_label_set_max_width_chars (GTK_LABEL (priv->label), 40); gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_END); gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5); priv->detail = g_object_ref (ido_detail_label_new ("")); gtk_widget_set_halign (priv->detail, GTK_ALIGN_END); gtk_widget_set_hexpand (priv->detail, TRUE); gtk_style_context_add_class (gtk_widget_get_style_context (priv->detail), "accelerator"); grid = gtk_grid_new (); gtk_grid_attach (GTK_GRID (grid), priv->icon, 0, 0, 1, 1); gtk_grid_attach (GTK_GRID (grid), priv->label, 1, 0, 1, 1); gtk_grid_attach (GTK_GRID (grid), priv->detail, 2, 0, 1, 1); gtk_container_add (GTK_CONTAINER (object), grid); gtk_widget_show_all (grid); G_OBJECT_CLASS (im_source_menu_item_parent_class)->constructed (object); } static gchar * im_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 m", "%d m", 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 im_source_menu_item_set_detail_time (ImSourceMenuItem *self, gint64 time) { ImSourceMenuItemPrivate *priv = self->priv; gchar *str; priv->time = time; str = im_source_menu_item_time_span_string (priv->time); ido_detail_label_set_text (IDO_DETAIL_LABEL (priv->detail), str); g_free (str); } static gboolean im_source_menu_item_update_time (gpointer data) { ImSourceMenuItem *self = data; im_source_menu_item_set_detail_time (self, self->priv->time); return TRUE; } static gboolean im_source_menu_item_set_state (ImSourceMenuItem *self, GVariant *state) { ImSourceMenuItemPrivate *priv = self->priv; guint32 count; gint64 time; const gchar *str; if (priv->timer_id != 0) { g_source_remove (priv->timer_id); priv->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 (priv->detail), count); else if (time != 0) { im_source_menu_item_set_detail_time (self, time); priv->timer_id = g_timeout_add_seconds (59, im_source_menu_item_update_time, self); } else if (str != NULL && *str) ido_detail_label_set_text (IDO_DETAIL_LABEL (priv->detail), str); return TRUE; } static void im_source_menu_item_set_action_name (ImSourceMenuItem *self, const gchar *action_name) { ImSourceMenuItemPrivate *priv = self->priv; gboolean enabled = FALSE; GVariant *state; if (priv->action != NULL) g_free (priv->action); priv->action = g_strdup (action_name); if (priv->action_group != NULL && priv->action != NULL && g_action_group_query_action (priv->action_group, priv->action, &enabled, NULL, NULL, NULL, &state)) { if (!state || !im_source_menu_item_set_state (self, state)) enabled = FALSE; if (state) g_variant_unref (state); } gtk_widget_set_sensitive (GTK_WIDGET (self), enabled); } static void im_source_menu_item_action_added (GActionGroup *action_group, gchar *action_name, gpointer user_data) { ImSourceMenuItem *self = user_data; if (g_strcmp0 (self->priv->action, action_name) == 0) im_source_menu_item_set_action_name (self, action_name); } static void im_source_menu_item_action_removed (GActionGroup *action_group, gchar *action_name, gpointer user_data) { ImSourceMenuItem *self = user_data; if (g_strcmp0 (self->priv->action, action_name) == 0) { gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); } } static void im_source_menu_item_action_enabled_changed (GActionGroup *action_group, gchar *action_name, gboolean enabled, gpointer user_data) { ImSourceMenuItem *self = user_data; if (g_strcmp0 (self->priv->action, action_name) == 0) gtk_widget_set_sensitive (GTK_WIDGET (self), enabled); } static void im_source_menu_item_action_state_changed (GActionGroup *action_group, gchar *action_name, GVariant *value, gpointer user_data) { ImSourceMenuItem *self = user_data; if (g_strcmp0 (self->priv->action, action_name) == 0) im_source_menu_item_set_state (self, value); } static void im_source_menu_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { ImSourceMenuItem *self = IM_SOURCE_MENU_ITEM (object); switch (property_id) { case PROP_MENU_ITEM: im_source_menu_item_set_menu_item (self, G_MENU_ITEM (g_value_get_object (value))); break; case PROP_ACTION_GROUP: im_source_menu_item_set_action_group (self, G_ACTION_GROUP (g_value_get_object (value))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void im_source_menu_item_dispose (GObject *object) { ImSourceMenuItem *self = IM_SOURCE_MENU_ITEM (object); if (self->priv->timer_id != 0) { g_source_remove (self->priv->timer_id); self->priv->timer_id = 0; } if (self->priv->action_group) im_source_menu_item_set_action_group (self, NULL); g_clear_object (&self->priv->icon); g_clear_object (&self->priv->label); g_clear_object (&self->priv->detail); G_OBJECT_CLASS (im_source_menu_item_parent_class)->dispose (object); } static void im_source_menu_item_finalize (GObject *object) { ImSourceMenuItemPrivate *priv = IM_SOURCE_MENU_ITEM (object)->priv; g_free (priv->action); G_OBJECT_CLASS (im_source_menu_item_parent_class)->finalize (object); } static void im_source_menu_item_activate (GtkMenuItem *item) { ImSourceMenuItemPrivate *priv = IM_SOURCE_MENU_ITEM (item)->priv; if (priv->action && priv->action_group) g_action_group_activate_action (priv->action_group, priv->action, NULL); } static void im_source_menu_item_class_init (ImSourceMenuItemClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkMenuItemClass *menu_item_class = GTK_MENU_ITEM_CLASS (klass); g_type_class_add_private (klass, sizeof (ImSourceMenuItemPrivate)); object_class->constructed = im_source_menu_item_constructed; object_class->set_property = im_source_menu_set_property; object_class->dispose = im_source_menu_item_dispose; object_class->finalize = im_source_menu_item_finalize; menu_item_class->activate = im_source_menu_item_activate; properties[PROP_MENU_ITEM] = g_param_spec_object ("menu-item", "Menu item", "The model GMenuItem for this menu item", G_TYPE_MENU_ITEM, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); properties[PROP_ACTION_GROUP] = g_param_spec_object ("action-group", "Action group", "The action group associated with this menu item", G_TYPE_ACTION_GROUP, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); } static void im_source_menu_item_init (ImSourceMenuItem *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, IM_TYPE_SOURCE_MENU_ITEM, ImSourceMenuItemPrivate); } void im_source_menu_item_set_menu_item (ImSourceMenuItem *self, GMenuItem *menuitem) { gchar *iconstr = NULL; GIcon *icon = NULL; gchar *label; gchar *action = NULL; if (g_menu_item_get_attribute (menuitem, "x-canonical-icon", "s", &iconstr)) { GError *error; icon = g_icon_new_for_string (iconstr, &error); if (icon == NULL) { g_warning ("unable to set icon: %s", error->message); g_error_free (error); } g_free (iconstr); } gtk_image_set_from_gicon (GTK_IMAGE (self->priv->icon), icon, GTK_ICON_SIZE_MENU); g_menu_item_get_attribute (menuitem, "label", "s", &label); gtk_label_set_label (GTK_LABEL (self->priv->label), label ? label : ""); g_menu_item_get_attribute (menuitem, "action", "s", &action); im_source_menu_item_set_action_name (self, action); if (icon) g_object_unref (icon); g_free (label); g_free (action); } void im_source_menu_item_set_action_group (ImSourceMenuItem *self, GActionGroup *action_group) { ImSourceMenuItemPrivate *priv = self->priv; if (priv->action_group != NULL) { g_signal_handlers_disconnect_by_func (priv->action_group, im_source_menu_item_action_added, self); g_signal_handlers_disconnect_by_func (priv->action_group, im_source_menu_item_action_removed, self); g_signal_handlers_disconnect_by_func (priv->action_group, im_source_menu_item_action_enabled_changed, self); g_signal_handlers_disconnect_by_func (priv->action_group, im_source_menu_item_action_state_changed, self); g_clear_object (&priv->action_group); } if (action_group != NULL) { priv->action_group = g_object_ref (action_group); g_signal_connect (priv->action_group, "action-added", G_CALLBACK (im_source_menu_item_action_added), self); g_signal_connect (priv->action_group, "action-removed", G_CALLBACK (im_source_menu_item_action_removed), self); g_signal_connect (priv->action_group, "action-enabled-changed", G_CALLBACK (im_source_menu_item_action_enabled_changed), self); g_signal_connect (priv->action_group, "action-state-changed", G_CALLBACK (im_source_menu_item_action_state_changed), self); } }