1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* e-action-combo-box.c
4 * Copyright (C) 2008 Novell, Inc.
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
23 #include "e-action-combo-box.h"
24 #include "e-misc-utils.h"
26 #include <glib/gi18n.h>
28 #define E_ACTION_COMBO_BOX_GET_PRIVATE(obj) \
29 (G_TYPE_INSTANCE_GET_PRIVATE \
30 ((obj), E_TYPE_ACTION_COMBO_BOX, EActionComboBoxPrivate))
42 struct _EActionComboBoxPrivate
{
43 GtkRadioAction
*action
;
44 GtkActionGroup
*action_group
;
46 guint changed_handler_id
; /* action::changed */
47 guint group_sensitive_handler_id
; /* action-group::sensitive */
48 guint group_visible_handler_id
; /* action-group::visible */
49 gboolean group_has_icons
: 1;
58 action_combo_box_action_changed_cb (GtkRadioAction
*action
,
59 GtkRadioAction
*current
,
60 EActionComboBox
*combo_box
)
62 GtkTreeRowReference
*reference
;
68 reference
= g_hash_table_lookup (
69 combo_box
->priv
->index
, GINT_TO_POINTER (
70 gtk_radio_action_get_current_value (current
)));
71 g_return_if_fail (reference
!= NULL
);
73 model
= gtk_tree_row_reference_get_model (reference
);
74 path
= gtk_tree_row_reference_get_path (reference
);
75 valid
= gtk_tree_model_get_iter (model
, &iter
, path
);
76 gtk_tree_path_free (path
);
77 g_return_if_fail (valid
);
79 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box
), &iter
);
83 action_combo_box_action_group_notify_cb (GtkActionGroup
*action_group
,
85 EActionComboBox
*combo_box
)
88 combo_box
, "sensitive",
89 gtk_action_group_get_sensitive (action_group
), "visible",
90 gtk_action_group_get_visible (action_group
), NULL
);
94 action_combo_box_render_pixbuf (GtkCellLayout
*layout
,
95 GtkCellRenderer
*renderer
,
98 EActionComboBox
*combo_box
)
100 GtkRadioAction
*action
;
107 gtk_tree_model_get (model
, iter
, COLUMN_ACTION
, &action
, -1);
109 /* A NULL action means the row is a separator. */
115 "icon-name", &icon_name
,
116 "sensitive", &sensitive
,
117 "stock-id", &stock_id
,
121 /* If some action has an icon */
122 if (combo_box
->priv
->group_has_icons
)
123 /* Keep the pixbuf renderer a fixed size for proper alignment. */
124 gtk_icon_size_lookup (GTK_ICON_SIZE_MENU
, &width
, NULL
);
128 /* We can't set both "icon-name" and "stock-id" because setting
129 * one unsets the other. So pick the one that has a non-NULL
130 * value. If both are non-NULL, "stock-id" wins. */
132 if (stock_id
!= NULL
)
135 "sensitive", sensitive
,
137 "stock-id", stock_id
,
138 "stock-size", GTK_ICON_SIZE_MENU
,
145 "sensitive", sensitive
,
146 "icon-name", icon_name
,
148 "stock-size", GTK_ICON_SIZE_MENU
,
153 g_object_unref (action
);
159 action_combo_box_render_text (GtkCellLayout
*layout
,
160 GtkCellRenderer
*renderer
,
163 EActionComboBox
*combo_box
)
165 GtkRadioAction
*action
;
172 gtk_tree_model_get (model
, iter
, COLUMN_ACTION
, &action
, -1);
174 /* A NULL action means the row is a separator. */
181 "sensitive", &sensitive
,
185 /* Strip out underscores. */
186 strv
= g_strsplit (label
, "_", -1);
188 label
= g_strjoinv (NULL
, strv
);
191 xpad
= combo_box
->priv
->group_has_icons
? 3 : 0;
195 "sensitive", sensitive
,
201 g_object_unref (action
);
206 action_combo_box_is_row_separator (GtkTreeModel
*model
,
212 /* NULL actions are rendered as separators. */
213 gtk_tree_model_get (model
, iter
, COLUMN_ACTION
, &action
, -1);
214 separator
= (action
== NULL
);
216 g_object_unref (action
);
222 action_combo_box_update_model (EActionComboBox
*combo_box
)
224 GtkListStore
*list_store
;
227 g_hash_table_remove_all (combo_box
->priv
->index
);
229 if (combo_box
->priv
->action
== NULL
) {
230 gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box
), NULL
);
234 /* We store values in the sort column as floats so that we can
235 * insert separators in between consecutive integer values and
236 * still maintain the proper ordering. */
237 list_store
= gtk_list_store_new (
238 2, GTK_TYPE_RADIO_ACTION
, G_TYPE_FLOAT
);
240 list
= gtk_radio_action_get_group (combo_box
->priv
->action
);
241 combo_box
->priv
->group_has_icons
= FALSE
;
243 while (list
!= NULL
) {
244 GtkTreeRowReference
*reference
;
245 GtkRadioAction
*action
= list
->data
;
248 gchar
*icon_name
= NULL
;
249 gchar
*stock_id
= NULL
;
250 gboolean visible
= FALSE
;
253 g_object_get (action
,
254 "icon-name", &icon_name
,
255 "stock-id", &stock_id
,
263 list
= g_slist_next (list
);
267 combo_box
->priv
->group_has_icons
|=
268 (icon_name
!= NULL
|| stock_id
!= NULL
);
272 gtk_list_store_append (list_store
, &iter
);
273 g_object_get (action
, "value", &value
, NULL
);
275 list_store
, &iter
, COLUMN_ACTION
,
276 list
->data
, COLUMN_SORT
, (gfloat
) value
, -1);
278 path
= gtk_tree_model_get_path (
279 GTK_TREE_MODEL (list_store
), &iter
);
280 reference
= gtk_tree_row_reference_new (
281 GTK_TREE_MODEL (list_store
), path
);
282 g_hash_table_insert (
283 combo_box
->priv
->index
,
284 GINT_TO_POINTER (value
), reference
);
285 gtk_tree_path_free (path
);
287 list
= g_slist_next (list
);
290 gtk_tree_sortable_set_sort_column_id (
291 GTK_TREE_SORTABLE (list_store
),
292 COLUMN_SORT
, GTK_SORT_ASCENDING
);
293 gtk_combo_box_set_model (
294 GTK_COMBO_BOX (combo_box
), GTK_TREE_MODEL (list_store
));
295 g_object_unref (list_store
);
297 action_combo_box_action_changed_cb (
298 combo_box
->priv
->action
,
299 combo_box
->priv
->action
,
304 action_combo_box_set_property (GObject
*object
,
309 switch (property_id
) {
311 e_action_combo_box_set_action (
312 E_ACTION_COMBO_BOX (object
),
313 g_value_get_object (value
));
317 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
321 action_combo_box_get_property (GObject
*object
,
326 switch (property_id
) {
329 value
, e_action_combo_box_get_action (
330 E_ACTION_COMBO_BOX (object
)));
334 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
338 action_combo_box_dispose (GObject
*object
)
340 EActionComboBoxPrivate
*priv
= E_ACTION_COMBO_BOX_GET_PRIVATE (object
);
342 if (priv
->action
!= NULL
) {
343 g_object_unref (priv
->action
);
347 if (priv
->action_group
!= NULL
) {
348 g_object_unref (priv
->action_group
);
349 priv
->action_group
= NULL
;
352 g_hash_table_remove_all (priv
->index
);
354 /* Chain up to parent's dispose() method. */
355 G_OBJECT_CLASS (e_action_combo_box_parent_class
)->dispose (object
);
359 action_combo_box_finalize (GObject
*object
)
361 EActionComboBoxPrivate
*priv
= E_ACTION_COMBO_BOX_GET_PRIVATE (object
);
363 g_hash_table_destroy (priv
->index
);
365 /* Chain up to parent's finalize() method. */
366 G_OBJECT_CLASS (e_action_combo_box_parent_class
)->finalize (object
);
370 action_combo_box_constructed (GObject
*object
)
372 GtkComboBox
*combo_box
;
373 GtkCellRenderer
*renderer
;
375 combo_box
= GTK_COMBO_BOX (object
);
377 /* Chain up to parent's constructed() method. */
378 G_OBJECT_CLASS (e_action_combo_box_parent_class
)->constructed (object
);
380 /* This needs to happen after constructor properties are set
381 * so that GtkCellLayout.get_area() returns something valid. */
383 renderer
= gtk_cell_renderer_pixbuf_new ();
384 gtk_cell_layout_pack_start (
385 GTK_CELL_LAYOUT (combo_box
), renderer
, FALSE
);
386 gtk_cell_layout_set_cell_data_func (
387 GTK_CELL_LAYOUT (combo_box
), renderer
,
388 (GtkCellLayoutDataFunc
) action_combo_box_render_pixbuf
,
391 renderer
= gtk_cell_renderer_text_new ();
392 gtk_cell_layout_pack_start (
393 GTK_CELL_LAYOUT (combo_box
), renderer
, TRUE
);
394 gtk_cell_layout_set_cell_data_func (
395 GTK_CELL_LAYOUT (combo_box
), renderer
,
396 (GtkCellLayoutDataFunc
) action_combo_box_render_text
,
399 gtk_combo_box_set_row_separator_func (
400 combo_box
, (GtkTreeViewRowSeparatorFunc
)
401 action_combo_box_is_row_separator
, NULL
, NULL
);
405 action_combo_box_changed (GtkComboBox
*combo_box
)
407 GtkRadioAction
*action
;
412 /* This method is virtual, so no need to chain up. */
414 if (!gtk_combo_box_get_active_iter (combo_box
, &iter
))
417 model
= gtk_combo_box_get_model (combo_box
);
418 gtk_tree_model_get (model
, &iter
, COLUMN_ACTION
, &action
, -1);
419 g_object_get (action
, "value", &value
, NULL
);
420 gtk_radio_action_set_current_value (action
, value
);
421 g_object_unref (action
);
425 e_action_combo_box_class_init (EActionComboBoxClass
*class)
427 GObjectClass
*object_class
;
428 GtkComboBoxClass
*combo_box_class
;
430 g_type_class_add_private (class, sizeof (EActionComboBoxPrivate
));
432 object_class
= G_OBJECT_CLASS (class);
433 object_class
->set_property
= action_combo_box_set_property
;
434 object_class
->get_property
= action_combo_box_get_property
;
435 object_class
->dispose
= action_combo_box_dispose
;
436 object_class
->finalize
= action_combo_box_finalize
;
437 object_class
->constructed
= action_combo_box_constructed
;
439 combo_box_class
= GTK_COMBO_BOX_CLASS (class);
440 combo_box_class
->changed
= action_combo_box_changed
;
442 g_object_class_install_property (
445 g_param_spec_object (
449 GTK_TYPE_RADIO_ACTION
,
454 e_action_combo_box_init (EActionComboBox
*combo_box
)
456 combo_box
->priv
= E_ACTION_COMBO_BOX_GET_PRIVATE (combo_box
);
458 combo_box
->priv
->index
= g_hash_table_new_full (
459 g_direct_hash
, g_direct_equal
,
460 (GDestroyNotify
) NULL
,
461 (GDestroyNotify
) gtk_tree_row_reference_free
);
465 e_action_combo_box_new (void)
467 return e_action_combo_box_new_with_action (NULL
);
471 e_action_combo_box_new_with_action (GtkRadioAction
*action
)
473 return g_object_new (E_TYPE_ACTION_COMBO_BOX
, "action", action
, NULL
);
477 e_action_combo_box_get_action (EActionComboBox
*combo_box
)
479 g_return_val_if_fail (E_IS_ACTION_COMBO_BOX (combo_box
), NULL
);
481 return combo_box
->priv
->action
;
485 e_action_combo_box_set_action (EActionComboBox
*combo_box
,
486 GtkRadioAction
*action
)
488 g_return_if_fail (E_IS_ACTION_COMBO_BOX (combo_box
));
491 g_return_if_fail (GTK_IS_RADIO_ACTION (action
));
493 if (combo_box
->priv
->action
!= NULL
) {
494 g_signal_handler_disconnect (
495 combo_box
->priv
->action
,
496 combo_box
->priv
->changed_handler_id
);
497 g_object_unref (combo_box
->priv
->action
);
500 if (combo_box
->priv
->action_group
!= NULL
) {
501 g_signal_handler_disconnect (
502 combo_box
->priv
->action_group
,
503 combo_box
->priv
->group_sensitive_handler_id
);
504 g_signal_handler_disconnect (
505 combo_box
->priv
->action_group
,
506 combo_box
->priv
->group_visible_handler_id
);
507 g_object_unref (combo_box
->priv
->action_group
);
508 combo_box
->priv
->action_group
= NULL
;
511 if (action
!= NULL
) {
512 /* This also adds a reference to the combo_box->priv->action_group */
514 g_object_ref (action
), "action-group",
515 &combo_box
->priv
->action_group
, NULL
);
518 combo_box
->priv
->action
= action
;
519 action_combo_box_update_model (combo_box
);
521 if (combo_box
->priv
->action
!= NULL
)
522 combo_box
->priv
->changed_handler_id
= g_signal_connect (
523 combo_box
->priv
->action
, "changed",
524 G_CALLBACK (action_combo_box_action_changed_cb
),
527 if (combo_box
->priv
->action_group
!= NULL
) {
528 combo_box
->priv
->group_sensitive_handler_id
=
529 e_signal_connect_notify (
530 combo_box
->priv
->action_group
,
531 "notify::sensitive", G_CALLBACK (
532 action_combo_box_action_group_notify_cb
),
534 combo_box
->priv
->group_visible_handler_id
=
535 e_signal_connect_notify (
536 combo_box
->priv
->action_group
,
537 "notify::visible", G_CALLBACK (
538 action_combo_box_action_group_notify_cb
),
542 g_object_notify (G_OBJECT (combo_box
), "action");
546 e_action_combo_box_get_current_value (EActionComboBox
*combo_box
)
548 g_return_val_if_fail (E_IS_ACTION_COMBO_BOX (combo_box
), 0);
549 g_return_val_if_fail (combo_box
->priv
->action
!= NULL
, 0);
551 return gtk_radio_action_get_current_value (combo_box
->priv
->action
);
555 e_action_combo_box_set_current_value (EActionComboBox
*combo_box
,
558 g_return_if_fail (E_IS_ACTION_COMBO_BOX (combo_box
));
559 g_return_if_fail (combo_box
->priv
->action
!= NULL
);
561 gtk_radio_action_set_current_value (
562 combo_box
->priv
->action
, current_value
);
566 e_action_combo_box_add_separator_before (EActionComboBox
*combo_box
,
572 g_return_if_fail (E_IS_ACTION_COMBO_BOX (combo_box
));
574 /* NULL actions are rendered as separators. */
575 model
= gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box
));
576 gtk_list_store_append (GTK_LIST_STORE (model
), &iter
);
578 GTK_LIST_STORE (model
), &iter
, COLUMN_ACTION
,
579 NULL
, COLUMN_SORT
, (gfloat
) action_value
- 0.5, -1);
583 e_action_combo_box_add_separator_after (EActionComboBox
*combo_box
,
589 g_return_if_fail (E_IS_ACTION_COMBO_BOX (combo_box
));
591 /* NULL actions are rendered as separators. */
592 model
= gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box
));
593 gtk_list_store_append (GTK_LIST_STORE (model
), &iter
);
595 GTK_LIST_STORE (model
), &iter
, COLUMN_ACTION
,
596 NULL
, COLUMN_SORT
, (gfloat
) action_value
+ 0.5, -1);
600 e_action_combo_box_update_model (EActionComboBox
*combo_box
)
602 g_return_if_fail (E_IS_ACTION_COMBO_BOX (combo_box
));
604 action_combo_box_update_model (combo_box
);