No issues found
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* e-action-combo-box.c
3 *
4 * Copyright (C) 2008 Novell, Inc.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of version 2 of the GNU Lesser General Public
8 * License as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include "e-action-combo-box.h"
26
27 #include <glib/gi18n.h>
28
29 #define E_ACTION_COMBO_BOX_GET_PRIVATE(obj) \
30 (G_TYPE_INSTANCE_GET_PRIVATE \
31 ((obj), E_TYPE_ACTION_COMBO_BOX, EActionComboBoxPrivate))
32
33 enum {
34 COLUMN_ACTION,
35 COLUMN_SORT
36 };
37
38 enum {
39 PROP_0,
40 PROP_ACTION
41 };
42
43 struct _EActionComboBoxPrivate {
44 GtkRadioAction *action;
45 GtkActionGroup *action_group;
46 GHashTable *index;
47 guint changed_handler_id; /* action::changed */
48 guint group_sensitive_handler_id; /* action-group::sensitive */
49 guint group_visible_handler_id; /* action-group::visible */
50 gboolean group_has_icons : 1;
51 };
52
53 G_DEFINE_TYPE (
54 EActionComboBox,
55 e_action_combo_box,
56 GTK_TYPE_COMBO_BOX)
57
58 static void
59 action_combo_box_action_changed_cb (GtkRadioAction *action,
60 GtkRadioAction *current,
61 EActionComboBox *combo_box)
62 {
63 GtkTreeRowReference *reference;
64 GtkTreeModel *model;
65 GtkTreePath *path;
66 GtkTreeIter iter;
67 gboolean valid;
68
69 reference = g_hash_table_lookup (
70 combo_box->priv->index, GINT_TO_POINTER (
71 gtk_radio_action_get_current_value (current)));
72 g_return_if_fail (reference != NULL);
73
74 model = gtk_tree_row_reference_get_model (reference);
75 path = gtk_tree_row_reference_get_path (reference);
76 valid = gtk_tree_model_get_iter (model, &iter, path);
77 gtk_tree_path_free (path);
78 g_return_if_fail (valid);
79
80 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box), &iter);
81 }
82
83 static void
84 action_combo_box_action_group_notify_cb (GtkActionGroup *action_group,
85 GParamSpec *pspec,
86 EActionComboBox *combo_box)
87 {
88 g_object_set (
89 combo_box, "sensitive",
90 gtk_action_group_get_sensitive (action_group), "visible",
91 gtk_action_group_get_visible (action_group), NULL);
92 }
93
94 static void
95 action_combo_box_render_pixbuf (GtkCellLayout *layout,
96 GtkCellRenderer *renderer,
97 GtkTreeModel *model,
98 GtkTreeIter *iter,
99 EActionComboBox *combo_box)
100 {
101 GtkRadioAction *action;
102 gchar *icon_name;
103 gchar *stock_id;
104 gboolean sensitive;
105 gboolean visible;
106 gint width;
107
108 gtk_tree_model_get (model, iter, COLUMN_ACTION, &action, -1);
109
110 /* Do any of the actions have an icon? */
111 if (!combo_box->priv->group_has_icons)
112 return;
113
114 /* A NULL action means the row is a separator. */
115 if (action == NULL)
116 return;
117
118 g_object_get (
119 G_OBJECT (action),
120 "icon-name", &icon_name,
121 "sensitive", &sensitive,
122 "stock-id", &stock_id,
123 "visible", &visible,
124 NULL);
125
126 /* Keep the pixbuf renderer a fixed size for proper alignment. */
127 gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, NULL);
128
129 /* We can't set both "icon-name" and "stock-id" because setting
130 * one unsets the other. So pick the one that has a non-NULL
131 * value. If both are non-NULL, "stock-id" wins. */
132
133 if (stock_id != NULL)
134 g_object_set (
135 G_OBJECT (renderer),
136 "sensitive", sensitive,
137 "icon-name", NULL,
138 "stock-id", stock_id,
139 "stock-size", GTK_ICON_SIZE_MENU,
140 "visible", visible,
141 "width", width,
142 NULL);
143 else
144 g_object_set (
145 G_OBJECT (renderer),
146 "sensitive", sensitive,
147 "icon-name", icon_name,
148 "stock-id", NULL,
149 "stock-size", GTK_ICON_SIZE_MENU,
150 "visible", visible,
151 "width", width,
152 NULL);
153
154 g_free (icon_name);
155 g_free (stock_id);
156 }
157
158 static void
159 action_combo_box_render_text (GtkCellLayout *layout,
160 GtkCellRenderer *renderer,
161 GtkTreeModel *model,
162 GtkTreeIter *iter,
163 EActionComboBox *combo_box)
164 {
165 GtkRadioAction *action;
166 gchar **strv;
167 gchar *label;
168 gboolean sensitive;
169 gboolean visible;
170 gint xpad;
171
172 gtk_tree_model_get (model, iter, COLUMN_ACTION, &action, -1);
173
174 /* A NULL action means the row is a separator. */
175 if (action == NULL)
176 return;
177
178 g_object_get (
179 G_OBJECT (action),
180 "label", &label,
181 "sensitive", &sensitive,
182 "visible", &visible,
183 NULL);
184
185 /* Strip out underscores. */
186 strv = g_strsplit (label, "_", -1);
187 g_free (label);
188 label = g_strjoinv (NULL, strv);
189 g_strfreev (strv);
190
191 xpad = combo_box->priv->group_has_icons ? 3 : 0;
192
193 g_object_set (
194 G_OBJECT (renderer),
195 "sensitive", sensitive,
196 "text", label,
197 "visible", visible,
198 "xpad", xpad,
199 NULL);
200
201 g_free (label);
202 }
203
204 static gboolean
205 action_combo_box_is_row_separator (GtkTreeModel *model,
206 GtkTreeIter *iter)
207 {
208 GtkAction *action;
209 gboolean separator;
210
211 /* NULL actions are rendered as separators. */
212 gtk_tree_model_get (model, iter, COLUMN_ACTION, &action, -1);
213 separator = (action == NULL);
214 if (action != NULL)
215 g_object_unref (action);
216
217 return separator;
218 }
219
220 static void
221 action_combo_box_update_model (EActionComboBox *combo_box)
222 {
223 GtkListStore *list_store;
224 GSList *list;
225
226 g_hash_table_remove_all (combo_box->priv->index);
227
228 if (combo_box->priv->action == NULL) {
229 gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), NULL);
230 return;
231 }
232
233 /* We store values in the sort column as floats so that we can
234 * insert separators in between consecutive integer values and
235 * still maintain the proper ordering. */
236 list_store = gtk_list_store_new (
237 2, GTK_TYPE_RADIO_ACTION, G_TYPE_FLOAT);
238
239 list = gtk_radio_action_get_group (combo_box->priv->action);
240 combo_box->priv->group_has_icons = FALSE;
241
242 while (list != NULL) {
243 GtkTreeRowReference *reference;
244 GtkRadioAction *action = list->data;
245 GtkTreePath *path;
246 GtkTreeIter iter;
247 gchar *icon_name;
248 gchar *stock_id;
249 gint value;
250
251 g_object_get (
252 action, "icon-name", &icon_name,
253 "stock-id", &stock_id, NULL);
254 combo_box->priv->group_has_icons |=
255 (icon_name != NULL || stock_id != NULL);
256 g_free (icon_name);
257 g_free (stock_id);
258
259 gtk_list_store_append (list_store, &iter);
260 g_object_get (action, "value", &value, NULL);
261 gtk_list_store_set (
262 list_store, &iter, COLUMN_ACTION,
263 list->data, COLUMN_SORT, (gfloat) value, -1);
264
265 path = gtk_tree_model_get_path (
266 GTK_TREE_MODEL (list_store), &iter);
267 reference = gtk_tree_row_reference_new (
268 GTK_TREE_MODEL (list_store), path);
269 g_hash_table_insert (
270 combo_box->priv->index,
271 GINT_TO_POINTER (value), reference);
272 gtk_tree_path_free (path);
273
274 list = g_slist_next (list);
275 }
276
277 gtk_tree_sortable_set_sort_column_id (
278 GTK_TREE_SORTABLE (list_store),
279 COLUMN_SORT, GTK_SORT_ASCENDING);
280 gtk_combo_box_set_model (
281 GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (list_store));
282
283 action_combo_box_action_changed_cb (
284 combo_box->priv->action,
285 combo_box->priv->action,
286 combo_box);
287 }
288
289 static void
290 action_combo_box_set_property (GObject *object,
291 guint property_id,
292 const GValue *value,
293 GParamSpec *pspec)
294 {
295 switch (property_id) {
296 case PROP_ACTION:
297 e_action_combo_box_set_action (
298 E_ACTION_COMBO_BOX (object),
299 g_value_get_object (value));
300 return;
301 }
302
303 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
304 }
305
306 static void
307 action_combo_box_get_property (GObject *object,
308 guint property_id,
309 GValue *value,
310 GParamSpec *pspec)
311 {
312 switch (property_id) {
313 case PROP_ACTION:
314 g_value_set_object (
315 value, e_action_combo_box_get_action (
316 E_ACTION_COMBO_BOX (object)));
317 return;
318 }
319
320 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
321 }
322
323 static void
324 action_combo_box_dispose (GObject *object)
325 {
326 EActionComboBoxPrivate *priv = E_ACTION_COMBO_BOX_GET_PRIVATE (object);
327
328 if (priv->action != NULL) {
329 g_object_unref (priv->action);
330 priv->action = NULL;
331 }
332
333 if (priv->action_group != NULL) {
334 g_object_unref (priv->action_group);
335 priv->action_group = NULL;
336 }
337
338 g_hash_table_remove_all (priv->index);
339
340 /* Chain up to parent's dispose() method. */
341 G_OBJECT_CLASS (e_action_combo_box_parent_class)->dispose (object);
342 }
343
344 static void
345 action_combo_box_finalize (GObject *object)
346 {
347 EActionComboBoxPrivate *priv = E_ACTION_COMBO_BOX_GET_PRIVATE (object);
348
349 g_hash_table_destroy (priv->index);
350
351 /* Chain up to parent's finalize() method. */
352 G_OBJECT_CLASS (e_action_combo_box_parent_class)->finalize (object);
353 }
354
355 static void
356 action_combo_box_constructed (GObject *object)
357 {
358 GtkComboBox *combo_box;
359 GtkCellRenderer *renderer;
360
361 combo_box = GTK_COMBO_BOX (object);
362
363 /* This needs to happen after constructor properties are set
364 * so that GtkCellLayout.get_area() returns something valid. */
365
366 renderer = gtk_cell_renderer_pixbuf_new ();
367 gtk_cell_layout_pack_start (
368 GTK_CELL_LAYOUT (combo_box), renderer, FALSE);
369 gtk_cell_layout_set_cell_data_func (
370 GTK_CELL_LAYOUT (combo_box), renderer,
371 (GtkCellLayoutDataFunc) action_combo_box_render_pixbuf,
372 combo_box, NULL);
373
374 renderer = gtk_cell_renderer_text_new ();
375 gtk_cell_layout_pack_start (
376 GTK_CELL_LAYOUT (combo_box), renderer, TRUE);
377 gtk_cell_layout_set_cell_data_func (
378 GTK_CELL_LAYOUT (combo_box), renderer,
379 (GtkCellLayoutDataFunc) action_combo_box_render_text,
380 combo_box, NULL);
381
382 gtk_combo_box_set_row_separator_func (
383 combo_box, (GtkTreeViewRowSeparatorFunc)
384 action_combo_box_is_row_separator, NULL, NULL);
385 }
386
387 static void
388 action_combo_box_changed (GtkComboBox *combo_box)
389 {
390 GtkRadioAction *action;
391 GtkTreeModel *model;
392 GtkTreeIter iter;
393 gint value;
394
395 /* This method is virtual, so no need to chain up. */
396
397 if (!gtk_combo_box_get_active_iter (combo_box, &iter))
398 return;
399
400 model = gtk_combo_box_get_model (combo_box);
401 gtk_tree_model_get (model, &iter, COLUMN_ACTION, &action, -1);
402 g_object_get (action, "value", &value, NULL);
403 gtk_radio_action_set_current_value (action, value);
404 }
405
406 static void
407 e_action_combo_box_class_init (EActionComboBoxClass *class)
408 {
409 GObjectClass *object_class;
410 GtkComboBoxClass *combo_box_class;
411
412 g_type_class_add_private (class, sizeof (EActionComboBoxPrivate));
413
414 object_class = G_OBJECT_CLASS (class);
415 object_class->set_property = action_combo_box_set_property;
416 object_class->get_property = action_combo_box_get_property;
417 object_class->dispose = action_combo_box_dispose;
418 object_class->finalize = action_combo_box_finalize;
419 object_class->constructed = action_combo_box_constructed;
420
421 combo_box_class = GTK_COMBO_BOX_CLASS (class);
422 combo_box_class->changed = action_combo_box_changed;
423
424 g_object_class_install_property (
425 object_class,
426 PROP_ACTION,
427 g_param_spec_object (
428 "action",
429 "Action",
430 "A GtkRadioAction",
431 GTK_TYPE_RADIO_ACTION,
432 G_PARAM_READWRITE));
433 }
434
435 static void
436 e_action_combo_box_init (EActionComboBox *combo_box)
437 {
438 combo_box->priv = E_ACTION_COMBO_BOX_GET_PRIVATE (combo_box);
439
440 combo_box->priv->index = g_hash_table_new_full (
441 g_direct_hash, g_direct_equal,
442 (GDestroyNotify) NULL,
443 (GDestroyNotify) gtk_tree_row_reference_free);
444 }
445
446 GtkWidget *
447 e_action_combo_box_new (void)
448 {
449 return e_action_combo_box_new_with_action (NULL);
450 }
451
452 GtkWidget *
453 e_action_combo_box_new_with_action (GtkRadioAction *action)
454 {
455 return g_object_new (E_TYPE_ACTION_COMBO_BOX, "action", action, NULL);
456 }
457
458 GtkRadioAction *
459 e_action_combo_box_get_action (EActionComboBox *combo_box)
460 {
461 g_return_val_if_fail (E_ACTION_IS_COMBO_BOX (combo_box), NULL);
462
463 return combo_box->priv->action;
464 }
465
466 void
467 e_action_combo_box_set_action (EActionComboBox *combo_box,
468 GtkRadioAction *action)
469 {
470 g_return_if_fail (E_ACTION_IS_COMBO_BOX (combo_box));
471
472 if (action != NULL)
473 g_return_if_fail (GTK_IS_RADIO_ACTION (action));
474
475 if (combo_box->priv->action != NULL) {
476 g_signal_handler_disconnect (
477 combo_box->priv->action,
478 combo_box->priv->changed_handler_id);
479 g_object_unref (combo_box->priv->action);
480 }
481
482 if (combo_box->priv->action_group != NULL) {
483 g_signal_handler_disconnect (
484 combo_box->priv->action_group,
485 combo_box->priv->group_sensitive_handler_id);
486 g_signal_handler_disconnect (
487 combo_box->priv->action_group,
488 combo_box->priv->group_visible_handler_id);
489 g_object_unref (combo_box->priv->action_group);
490 combo_box->priv->action_group = NULL;
491 }
492
493 if (action != NULL) {
494 /* This also adds a reference to the combo_box->priv->action_group */
495 g_object_get (
496 g_object_ref (action), "action-group",
497 &combo_box->priv->action_group, NULL);
498 }
499
500 combo_box->priv->action = action;
501 action_combo_box_update_model (combo_box);
502
503 if (combo_box->priv->action != NULL)
504 combo_box->priv->changed_handler_id = g_signal_connect (
505 combo_box->priv->action, "changed",
506 G_CALLBACK (action_combo_box_action_changed_cb),
507 combo_box);
508
509 if (combo_box->priv->action_group != NULL) {
510 combo_box->priv->group_sensitive_handler_id =
511 g_signal_connect (
512 combo_box->priv->action_group,
513 "notify::sensitive", G_CALLBACK (
514 action_combo_box_action_group_notify_cb),
515 combo_box);
516 combo_box->priv->group_visible_handler_id =
517 g_signal_connect (
518 combo_box->priv->action_group,
519 "notify::visible", G_CALLBACK (
520 action_combo_box_action_group_notify_cb),
521 combo_box);
522 }
523
524 g_object_notify (G_OBJECT (combo_box), "action");
525 }
526
527 gint
528 e_action_combo_box_get_current_value (EActionComboBox *combo_box)
529 {
530 g_return_val_if_fail (E_ACTION_IS_COMBO_BOX (combo_box), 0);
531 g_return_val_if_fail (combo_box->priv->action != NULL, 0);
532
533 return gtk_radio_action_get_current_value (combo_box->priv->action);
534 }
535
536 void
537 e_action_combo_box_set_current_value (EActionComboBox *combo_box,
538 gint current_value)
539 {
540 g_return_if_fail (E_ACTION_IS_COMBO_BOX (combo_box));
541 g_return_if_fail (combo_box->priv->action != NULL);
542
543 gtk_radio_action_set_current_value (
544 combo_box->priv->action, current_value);
545 }
546
547 void
548 e_action_combo_box_add_separator_before (EActionComboBox *combo_box,
549 gint action_value)
550 {
551 GtkTreeModel *model;
552 GtkTreeIter iter;
553
554 g_return_if_fail (E_ACTION_IS_COMBO_BOX (combo_box));
555
556 /* NULL actions are rendered as separators. */
557 model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
558 gtk_list_store_append (GTK_LIST_STORE (model), &iter);
559 gtk_list_store_set (
560 GTK_LIST_STORE (model), &iter, COLUMN_ACTION,
561 NULL, COLUMN_SORT, (gfloat) action_value - 0.5, -1);
562 }
563
564 void
565 e_action_combo_box_add_separator_after (EActionComboBox *combo_box,
566 gint action_value)
567 {
568 GtkTreeModel *model;
569 GtkTreeIter iter;
570
571 g_return_if_fail (E_ACTION_IS_COMBO_BOX (combo_box));
572
573 /* NULL actions are rendered as separators. */
574 model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
575 gtk_list_store_append (GTK_LIST_STORE (model), &iter);
576 gtk_list_store_set (
577 GTK_LIST_STORE (model), &iter, COLUMN_ACTION,
578 NULL, COLUMN_SORT, (gfloat) action_value + 0.5, -1);
579 }