evolution-3.6.4/widgets/misc/e-action-combo-box.c

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 }