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