/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* e-action-combo-box.c * * Copyright (C) 2008 Novell, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License 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 warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #include "e-action-combo-box.h" #include #define E_ACTION_COMBO_BOX_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_ACTION_COMBO_BOX, EActionComboBoxPrivate)) enum { COLUMN_ACTION, COLUMN_SORT }; enum { PROP_0, PROP_ACTION }; struct _EActionComboBoxPrivate { GtkRadioAction *action; GtkActionGroup *action_group; GHashTable *index; guint changed_handler_id; /* action::changed */ guint group_sensitive_handler_id; /* action-group::sensitive */ guint group_visible_handler_id; /* action-group::visible */ gboolean group_has_icons : 1; }; G_DEFINE_TYPE ( EActionComboBox, e_action_combo_box, GTK_TYPE_COMBO_BOX) static void action_combo_box_action_changed_cb (GtkRadioAction *action, GtkRadioAction *current, EActionComboBox *combo_box) { GtkTreeRowReference *reference; GtkTreeModel *model; GtkTreePath *path; GtkTreeIter iter; gboolean valid; reference = g_hash_table_lookup ( combo_box->priv->index, GINT_TO_POINTER ( gtk_radio_action_get_current_value (current))); g_return_if_fail (reference != NULL); model = gtk_tree_row_reference_get_model (reference); path = gtk_tree_row_reference_get_path (reference); valid = gtk_tree_model_get_iter (model, &iter, path); gtk_tree_path_free (path); g_return_if_fail (valid); gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box), &iter); } static void action_combo_box_action_group_notify_cb (GtkActionGroup *action_group, GParamSpec *pspec, EActionComboBox *combo_box) { g_object_set ( combo_box, "sensitive", gtk_action_group_get_sensitive (action_group), "visible", gtk_action_group_get_visible (action_group), NULL); } static void action_combo_box_render_pixbuf (GtkCellLayout *layout, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, EActionComboBox *combo_box) { GtkRadioAction *action; gchar *icon_name; gchar *stock_id; gboolean sensitive; gboolean visible; gint width; /* Do any of the actions have an icon? */ if (!combo_box->priv->group_has_icons) return; gtk_tree_model_get (model, iter, COLUMN_ACTION, &action, -1); /* A NULL action means the row is a separator. */ if (action == NULL) return; g_object_get ( G_OBJECT (action), "icon-name", &icon_name, "sensitive", &sensitive, "stock-id", &stock_id, "visible", &visible, NULL); /* Keep the pixbuf renderer a fixed size for proper alignment. */ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, NULL); /* We can't set both "icon-name" and "stock-id" because setting * one unsets the other. So pick the one that has a non-NULL * value. If both are non-NULL, "stock-id" wins. */ if (stock_id != NULL) g_object_set ( G_OBJECT (renderer), "sensitive", sensitive, "icon-name", NULL, "stock-id", stock_id, "stock-size", GTK_ICON_SIZE_MENU, "visible", visible, "width", width, NULL); else g_object_set ( G_OBJECT (renderer), "sensitive", sensitive, "icon-name", icon_name, "stock-id", NULL, "stock-size", GTK_ICON_SIZE_MENU, "visible", visible, "width", width, NULL); g_object_unref (action); g_free (icon_name); g_free (stock_id); } static void action_combo_box_render_text (GtkCellLayout *layout, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, EActionComboBox *combo_box) { GtkRadioAction *action; gchar **strv; gchar *label; gboolean sensitive; gboolean visible; gint xpad; gtk_tree_model_get (model, iter, COLUMN_ACTION, &action, -1); /* A NULL action means the row is a separator. */ if (action == NULL) return; g_object_get ( G_OBJECT (action), "label", &label, "sensitive", &sensitive, "visible", &visible, NULL); /* Strip out underscores. */ strv = g_strsplit (label, "_", -1); g_free (label); label = g_strjoinv (NULL, strv); g_strfreev (strv); xpad = combo_box->priv->group_has_icons ? 3 : 0; g_object_set ( G_OBJECT (renderer), "sensitive", sensitive, "text", label, "visible", visible, "xpad", xpad, NULL); g_object_unref (action); g_free (label); } static gboolean action_combo_box_is_row_separator (GtkTreeModel *model, GtkTreeIter *iter) { GtkAction *action; gboolean separator; /* NULL actions are rendered as separators. */ gtk_tree_model_get (model, iter, COLUMN_ACTION, &action, -1); separator = (action == NULL); if (action != NULL) g_object_unref (action); return separator; } static void action_combo_box_update_model (EActionComboBox *combo_box) { GtkListStore *list_store; GSList *list; g_hash_table_remove_all (combo_box->priv->index); if (combo_box->priv->action == NULL) { gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), NULL); return; } /* We store values in the sort column as floats so that we can * insert separators in between consecutive integer values and * still maintain the proper ordering. */ list_store = gtk_list_store_new ( 2, GTK_TYPE_RADIO_ACTION, G_TYPE_FLOAT); list = gtk_radio_action_get_group (combo_box->priv->action); combo_box->priv->group_has_icons = FALSE; while (list != NULL) { GtkTreeRowReference *reference; GtkRadioAction *action = list->data; GtkTreePath *path; GtkTreeIter iter; gchar *icon_name; gchar *stock_id; gint value; g_object_get ( action, "icon-name", &icon_name, "stock-id", &stock_id, NULL); combo_box->priv->group_has_icons |= (icon_name != NULL || stock_id != NULL); g_free (icon_name); g_free (stock_id); gtk_list_store_append (list_store, &iter); g_object_get (action, "value", &value, NULL); gtk_list_store_set ( list_store, &iter, COLUMN_ACTION, list->data, COLUMN_SORT, (gfloat) value, -1); path = gtk_tree_model_get_path ( GTK_TREE_MODEL (list_store), &iter); reference = gtk_tree_row_reference_new ( GTK_TREE_MODEL (list_store), path); g_hash_table_insert ( combo_box->priv->index, GINT_TO_POINTER (value), reference); gtk_tree_path_free (path); list = g_slist_next (list); } gtk_tree_sortable_set_sort_column_id ( GTK_TREE_SORTABLE (list_store), COLUMN_SORT, GTK_SORT_ASCENDING); gtk_combo_box_set_model ( GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (list_store)); g_object_unref (list_store); action_combo_box_action_changed_cb ( combo_box->priv->action, combo_box->priv->action, combo_box); } static void action_combo_box_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_ACTION: e_action_combo_box_set_action ( E_ACTION_COMBO_BOX (object), g_value_get_object (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void action_combo_box_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_ACTION: g_value_set_object ( value, e_action_combo_box_get_action ( E_ACTION_COMBO_BOX (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void action_combo_box_dispose (GObject *object) { EActionComboBoxPrivate *priv = E_ACTION_COMBO_BOX_GET_PRIVATE (object); if (priv->action != NULL) { g_object_unref (priv->action); priv->action = NULL; } if (priv->action_group != NULL) { g_object_unref (priv->action_group); priv->action_group = NULL; } g_hash_table_remove_all (priv->index); /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_action_combo_box_parent_class)->dispose (object); } static void action_combo_box_finalize (GObject *object) { EActionComboBoxPrivate *priv = E_ACTION_COMBO_BOX_GET_PRIVATE (object); g_hash_table_destroy (priv->index); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_action_combo_box_parent_class)->finalize (object); } static void action_combo_box_constructed (GObject *object) { GtkComboBox *combo_box; GtkCellRenderer *renderer; combo_box = GTK_COMBO_BOX (object); /* This needs to happen after constructor properties are set * so that GtkCellLayout.get_area() returns something valid. */ renderer = gtk_cell_renderer_pixbuf_new (); gtk_cell_layout_pack_start ( GTK_CELL_LAYOUT (combo_box), renderer, FALSE); gtk_cell_layout_set_cell_data_func ( GTK_CELL_LAYOUT (combo_box), renderer, (GtkCellLayoutDataFunc) action_combo_box_render_pixbuf, combo_box, NULL); renderer = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start ( GTK_CELL_LAYOUT (combo_box), renderer, TRUE); gtk_cell_layout_set_cell_data_func ( GTK_CELL_LAYOUT (combo_box), renderer, (GtkCellLayoutDataFunc) action_combo_box_render_text, combo_box, NULL); gtk_combo_box_set_row_separator_func ( combo_box, (GtkTreeViewRowSeparatorFunc) action_combo_box_is_row_separator, NULL, NULL); } static void action_combo_box_changed (GtkComboBox *combo_box) { GtkRadioAction *action; GtkTreeModel *model; GtkTreeIter iter; gint value; /* This method is virtual, so no need to chain up. */ if (!gtk_combo_box_get_active_iter (combo_box, &iter)) return; model = gtk_combo_box_get_model (combo_box); gtk_tree_model_get (model, &iter, COLUMN_ACTION, &action, -1); g_object_get (action, "value", &value, NULL); gtk_radio_action_set_current_value (action, value); g_object_unref (action); } static void e_action_combo_box_class_init (EActionComboBoxClass *class) { GObjectClass *object_class; GtkComboBoxClass *combo_box_class; g_type_class_add_private (class, sizeof (EActionComboBoxPrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = action_combo_box_set_property; object_class->get_property = action_combo_box_get_property; object_class->dispose = action_combo_box_dispose; object_class->finalize = action_combo_box_finalize; object_class->constructed = action_combo_box_constructed; combo_box_class = GTK_COMBO_BOX_CLASS (class); combo_box_class->changed = action_combo_box_changed; g_object_class_install_property ( object_class, PROP_ACTION, g_param_spec_object ( "action", "Action", "A GtkRadioAction", GTK_TYPE_RADIO_ACTION, G_PARAM_READWRITE)); } static void e_action_combo_box_init (EActionComboBox *combo_box) { combo_box->priv = E_ACTION_COMBO_BOX_GET_PRIVATE (combo_box); combo_box->priv->index = g_hash_table_new_full ( g_direct_hash, g_direct_equal, (GDestroyNotify) NULL, (GDestroyNotify) gtk_tree_row_reference_free); } GtkWidget * e_action_combo_box_new (void) { return e_action_combo_box_new_with_action (NULL); } GtkWidget * e_action_combo_box_new_with_action (GtkRadioAction *action) { return g_object_new (E_TYPE_ACTION_COMBO_BOX, "action", action, NULL); } GtkRadioAction * e_action_combo_box_get_action (EActionComboBox *combo_box) { g_return_val_if_fail (E_IS_ACTION_COMBO_BOX (combo_box), NULL); return combo_box->priv->action; } void e_action_combo_box_set_action (EActionComboBox *combo_box, GtkRadioAction *action) { g_return_if_fail (E_IS_ACTION_COMBO_BOX (combo_box)); if (action != NULL) g_return_if_fail (GTK_IS_RADIO_ACTION (action)); if (combo_box->priv->action != NULL) { g_signal_handler_disconnect ( combo_box->priv->action, combo_box->priv->changed_handler_id); g_object_unref (combo_box->priv->action); } if (combo_box->priv->action_group != NULL) { g_signal_handler_disconnect ( combo_box->priv->action_group, combo_box->priv->group_sensitive_handler_id); g_signal_handler_disconnect ( combo_box->priv->action_group, combo_box->priv->group_visible_handler_id); g_object_unref (combo_box->priv->action_group); combo_box->priv->action_group = NULL; } if (action != NULL) { /* This also adds a reference to the combo_box->priv->action_group */ g_object_get ( g_object_ref (action), "action-group", &combo_box->priv->action_group, NULL); } combo_box->priv->action = action; action_combo_box_update_model (combo_box); if (combo_box->priv->action != NULL) combo_box->priv->changed_handler_id = g_signal_connect ( combo_box->priv->action, "changed", G_CALLBACK (action_combo_box_action_changed_cb), combo_box); if (combo_box->priv->action_group != NULL) { combo_box->priv->group_sensitive_handler_id = g_signal_connect ( combo_box->priv->action_group, "notify::sensitive", G_CALLBACK ( action_combo_box_action_group_notify_cb), combo_box); combo_box->priv->group_visible_handler_id = g_signal_connect ( combo_box->priv->action_group, "notify::visible", G_CALLBACK ( action_combo_box_action_group_notify_cb), combo_box); } g_object_notify (G_OBJECT (combo_box), "action"); } gint e_action_combo_box_get_current_value (EActionComboBox *combo_box) { g_return_val_if_fail (E_IS_ACTION_COMBO_BOX (combo_box), 0); g_return_val_if_fail (combo_box->priv->action != NULL, 0); return gtk_radio_action_get_current_value (combo_box->priv->action); } void e_action_combo_box_set_current_value (EActionComboBox *combo_box, gint current_value) { g_return_if_fail (E_IS_ACTION_COMBO_BOX (combo_box)); g_return_if_fail (combo_box->priv->action != NULL); gtk_radio_action_set_current_value ( combo_box->priv->action, current_value); } void e_action_combo_box_add_separator_before (EActionComboBox *combo_box, gint action_value) { GtkTreeModel *model; GtkTreeIter iter; g_return_if_fail (E_IS_ACTION_COMBO_BOX (combo_box)); /* NULL actions are rendered as separators. */ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)); gtk_list_store_append (GTK_LIST_STORE (model), &iter); gtk_list_store_set ( GTK_LIST_STORE (model), &iter, COLUMN_ACTION, NULL, COLUMN_SORT, (gfloat) action_value - 0.5, -1); } void e_action_combo_box_add_separator_after (EActionComboBox *combo_box, gint action_value) { GtkTreeModel *model; GtkTreeIter iter; g_return_if_fail (E_IS_ACTION_COMBO_BOX (combo_box)); /* NULL actions are rendered as separators. */ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)); gtk_list_store_append (GTK_LIST_STORE (model), &iter); gtk_list_store_set ( GTK_LIST_STORE (model), &iter, COLUMN_ACTION, NULL, COLUMN_SORT, (gfloat) action_value + 0.5, -1); }