1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /* cell-renderer-flags.c
3 * Copyright (C) 2006 Armin Burgmeier
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
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
13 * GNU Library General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #include "cell-renderer-flags.h"
21 #include "combo-flags.h"
25 typedef struct _CgCellRendererFlagsPrivate CgCellRendererFlagsPrivate
;
26 struct _CgCellRendererFlagsPrivate
32 GHashTable
*edit_status
;
36 #define CG_CELL_RENDERER_FLAGS_PRIVATE(o) \
37 (G_TYPE_INSTANCE_GET_PRIVATE( \
39 CG_TYPE_CELL_RENDERER_FLAGS, \
40 CgCellRendererFlagsPrivate \
51 #define CG_CELL_RENDERER_FLAGS_PATH "cg-cell-renderer-flags-path"
53 static GtkCellRendererTextClass
*parent_class
= NULL
;
56 cg_cell_renderer_flags_init (CgCellRendererFlags
*cell_renderer_flags
)
58 CgCellRendererFlagsPrivate
*priv
;
59 priv
= CG_CELL_RENDERER_FLAGS_PRIVATE (cell_renderer_flags
);
62 priv
->text_column
= -1;
63 priv
->abbr_column
= -1;
65 priv
->edit_status
= NULL
;
66 priv
->focus_out_id
= 0;
70 cg_cell_renderer_flags_finalize (GObject
*object
)
72 CgCellRendererFlags
*cell_renderer_flags
;
73 CgCellRendererFlagsPrivate
*priv
;
75 cell_renderer_flags
= CG_CELL_RENDERER_FLAGS (object
);
76 priv
= CG_CELL_RENDERER_FLAGS_PRIVATE (cell_renderer_flags
);
78 if (priv
->edit_status
!= NULL
)
80 g_hash_table_destroy (priv
->edit_status
);
81 priv
->edit_status
= NULL
;
84 if (priv
->model
!= NULL
)
86 g_object_unref (G_OBJECT(priv
->model
));
90 G_OBJECT_CLASS (parent_class
)-> finalize(object
);
94 cg_cell_renderer_flags_set_property (GObject
*object
,
99 CgCellRendererFlags
*renderer
;
100 CgCellRendererFlagsPrivate
*priv
;
102 g_return_if_fail (CG_IS_CELL_RENDERER_FLAGS (object
));
104 renderer
= CG_CELL_RENDERER_FLAGS (object
);
105 priv
= CG_CELL_RENDERER_FLAGS_PRIVATE (renderer
);
110 if(priv
->model
!= NULL
) g_object_unref (G_OBJECT (priv
->model
));
111 priv
->model
= GTK_TREE_MODEL (g_value_dup_object (value
));
113 case PROP_TEXT_COLUMN
:
114 priv
->text_column
= g_value_get_int (value
);
116 case PROP_ABBR_COLUMN
:
117 priv
->abbr_column
= g_value_get_int (value
);
120 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
126 cg_cell_renderer_flags_get_property (GObject
*object
,
131 CgCellRendererFlags
*renderer
;
132 CgCellRendererFlagsPrivate
*priv
;
134 g_return_if_fail (CG_IS_CELL_RENDERER_FLAGS (object
));
136 renderer
= CG_CELL_RENDERER_FLAGS (object
);
137 priv
= CG_CELL_RENDERER_FLAGS_PRIVATE (renderer
);
142 g_value_set_object (value
, G_OBJECT (priv
->model
));
144 case PROP_TEXT_COLUMN
:
145 g_value_set_int (value
, priv
->text_column
);
147 case PROP_ABBR_COLUMN
:
148 g_value_set_int (value
, priv
->abbr_column
);
151 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
157 cg_cell_renderer_flags_editing_done (GtkCellEditable
*editable
,
158 G_GNUC_UNUSED gpointer data
)
160 CgCellRendererFlags
*cell_flags
;
161 CgCellRendererFlagsPrivate
*priv
;
170 cell_flags
= CG_CELL_RENDERER_FLAGS (data
);
171 priv
= CG_CELL_RENDERER_FLAGS_PRIVATE (cell_flags
);
173 g_assert (priv
->edit_status
!= NULL
);
175 if (priv
->focus_out_id
> 0)
177 g_signal_handler_disconnect (G_OBJECT (editable
), priv
->focus_out_id
);
178 priv
->focus_out_id
= 0;
181 canceled
= cg_combo_flags_editing_canceled (CG_COMBO_FLAGS (editable
));
182 gtk_cell_renderer_stop_editing (GTK_CELL_RENDERER(cell_flags
), canceled
);
184 if (canceled
== FALSE
)
186 str
= g_string_sized_new (128);
188 /* We do not just call g_hash_table_foreach to get the flags
189 * in the correct order. */
190 for (result
= gtk_tree_model_get_iter_first (priv
->model
, &iter
);
192 result
= gtk_tree_model_iter_next (priv
->model
, &iter
))
194 gtk_tree_model_get (priv
->model
, &iter
,
195 priv
->abbr_column
, &abbr
, -1);
197 if (g_hash_table_lookup (priv
->edit_status
, abbr
) != NULL
)
199 if (str
->len
> 0) g_string_append_c (str
, '|');
200 g_string_append (str
, abbr
);
206 path
= g_object_get_data (G_OBJECT (editable
),
207 CG_CELL_RENDERER_FLAGS_PATH
);
209 g_signal_emit_by_name (G_OBJECT (cell_flags
), "edited",
212 g_string_free (str
, TRUE
);
215 g_hash_table_destroy (priv
->edit_status
);
216 priv
->edit_status
= NULL
;
220 cg_cell_renderer_flags_selected (CgComboFlags
*combo
,
222 CgComboFlagsSelectionType type
,
225 CgCellRendererFlags
*cell_flags
;
226 CgCellRendererFlagsPrivate
*priv
;
231 cell_flags
= CG_CELL_RENDERER_FLAGS (user_data
);
232 priv
= CG_CELL_RENDERER_FLAGS_PRIVATE (cell_flags
);
234 gtk_tree_model_get (priv
->model
, iter
, priv
->text_column
, &name
,
235 priv
->abbr_column
, &abbr
, -1);
237 g_assert (priv
->edit_status
!= NULL
);
238 result
= g_hash_table_lookup (priv
->edit_status
, abbr
);
240 /* abbr needs not to be freed if it gets inserted into the hash table
241 * because the hash table then takes ownership of it. */
244 case CG_COMBO_FLAGS_SELECTION_NONE
:
247 case CG_COMBO_FLAGS_SELECTION_SELECT
:
248 if (GPOINTER_TO_INT(result
) != 1)
249 g_hash_table_insert (priv
->edit_status
, abbr
, GINT_TO_POINTER (1));
254 case CG_COMBO_FLAGS_SELECTION_UNSELECT
:
255 if (GPOINTER_TO_INT (result
) == 1)
256 g_hash_table_remove(priv
->edit_status
, abbr
);
260 case CG_COMBO_FLAGS_SELECTION_TOGGLE
:
261 if (GPOINTER_TO_INT (result
) == 1)
263 g_hash_table_remove (priv
->edit_status
, abbr
);
268 g_hash_table_insert (priv
->edit_status
, abbr
, GINT_TO_POINTER (1));
273 g_assert_not_reached ();
277 /* This is done to get GTK+ to re-render this row with the changed flag
278 * status that is set via the cell data func, but GTK+ does not call it
279 * again because it does not know that the hash table changed. There are
280 * probably better means to achieve this, but I am not aware of those. */
281 gtk_list_store_set (GTK_LIST_STORE (priv
->model
), iter
,
282 priv
->text_column
, name
, -1);
288 cg_cell_renderer_flags_focus_out_event (GtkWidget
*widget
,
289 G_GNUC_UNUSED GdkEvent
*event
,
292 cg_cell_renderer_flags_editing_done (GTK_CELL_EDITABLE (widget
), data
);
297 cg_cell_renderer_flags_set_data_func (G_GNUC_UNUSED GtkCellLayout
*cell_layout
,
298 GtkCellRenderer
*cell
,
303 CgCellRendererFlags
*cell_flags
;
304 CgCellRendererFlagsPrivate
*priv
;
307 cell_flags
= CG_CELL_RENDERER_FLAGS (data
);
308 priv
= CG_CELL_RENDERER_FLAGS_PRIVATE (cell_flags
);
310 if(priv
->edit_status
!= NULL
)
312 gtk_tree_model_get (model
, iter
, priv
->abbr_column
, &abbr
, -1);
314 if (g_hash_table_lookup (priv
->edit_status
, abbr
) != NULL
)
315 g_object_set (G_OBJECT (cell
), "active", TRUE
, NULL
);
317 g_object_set (G_OBJECT (cell
), "active", FALSE
, NULL
);
323 static GtkCellEditable
*
324 cg_cell_renderer_flags_start_editing (GtkCellRenderer
*cell
,
325 G_GNUC_UNUSED GdkEvent
*event
,
326 G_GNUC_UNUSED GtkWidget
*widget
,
328 G_GNUC_UNUSED GdkRectangle
330 G_GNUC_UNUSED GdkRectangle
*cell_area
,
331 G_GNUC_UNUSED GtkCellRendererState flags
)
333 CgCellRendererFlags
*cell_flags
;
334 CgCellRendererFlagsPrivate
*priv
;
335 GtkCellRendererText
*cell_text
;
342 GtkCellRenderer
*cell_combo_set
;
343 GtkCellRenderer
*cell_combo_text
;
345 cell_flags
= CG_CELL_RENDERER_FLAGS (cell
);
346 priv
= CG_CELL_RENDERER_FLAGS_PRIVATE (cell_flags
);
348 cell_text
= GTK_CELL_RENDERER_TEXT (cell
);
350 g_object_get (cell_text
,
351 "editable", &editable
,
355 if (editable
== FALSE
) return NULL
;
357 if (priv
->model
== NULL
|| priv
->text_column
< 0 || priv
->abbr_column
< 0)
360 cell_combo_set
= gtk_cell_renderer_toggle_new ();
361 cell_combo_text
= gtk_cell_renderer_text_new ();
363 combo
= cg_combo_flags_new_with_model (priv
->model
);
365 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo
),
366 cell_combo_set
, FALSE
);
367 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo
),
368 cell_combo_text
, TRUE
);
370 gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo
),
371 cell_combo_text
, "text", priv
->text_column
);
373 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo
),
375 cg_cell_renderer_flags_set_data_func
,
378 g_object_set (G_OBJECT (cell_combo_set
), "activatable", FALSE
, NULL
);
380 /* Create hash table with current status. We could also operate
381 * directly on a string here, but a hash table is probably more
383 g_assert (priv
->edit_status
== NULL
);
384 priv
->edit_status
= g_hash_table_new_full (g_str_hash
, g_str_equal
,
385 (GDestroyNotify
) g_free
, NULL
);
390 while (prev
!= NULL
&& *prev
!= '\0')
392 while (*pos
!= '|' && *pos
!= '\0') ++ pos
;
394 g_hash_table_insert (priv
->edit_status
, g_strndup(prev
, pos
- prev
),
397 if(*pos
!= '\0') ++ pos
;
402 g_object_set_data_full (G_OBJECT (combo
), CG_CELL_RENDERER_FLAGS_PATH
,
403 g_strdup (path
), g_free
);
405 gtk_widget_show (combo
);
407 g_signal_connect (G_OBJECT (combo
), "editing-done",
408 G_CALLBACK (cg_cell_renderer_flags_editing_done
),
411 g_signal_connect (G_OBJECT (combo
), "selected",
412 G_CALLBACK (cg_cell_renderer_flags_selected
),
416 g_signal_connect (G_OBJECT (combo
), "focus_out_event",
417 G_CALLBACK (cg_cell_renderer_flags_focus_out_event
),
420 return GTK_CELL_EDITABLE (combo
);
424 cg_cell_renderer_flags_class_init (CgCellRendererFlagsClass
*klass
)
426 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
427 GtkCellRendererClass
*cell_class
= GTK_CELL_RENDERER_CLASS (klass
);
428 parent_class
= g_type_class_peek_parent (klass
);
430 g_type_class_add_private (klass
, sizeof (CgCellRendererFlagsPrivate
));
432 object_class
->finalize
= cg_cell_renderer_flags_finalize
;
433 object_class
->set_property
= cg_cell_renderer_flags_set_property
;
434 object_class
->get_property
= cg_cell_renderer_flags_get_property
;
436 cell_class
->start_editing
= cg_cell_renderer_flags_start_editing
;
438 g_object_class_install_property (object_class
,
440 g_param_spec_object ("model",
442 "Model holding the available flags",
446 g_object_class_install_property (object_class
,
448 g_param_spec_int ("text-column",
450 "Column in the model holding the text for a flag",
456 g_object_class_install_property (object_class
,
458 g_param_spec_int ("abbrevation-column",
459 "Abbrevation column",
460 "Column in the model holding the abbrevation for a flag",
468 cg_cell_renderer_flags_get_type (void)
470 static GType our_type
= 0;
474 static const GTypeInfo our_info
=
476 sizeof (CgCellRendererFlagsClass
),
477 (GBaseInitFunc
) NULL
,
478 (GBaseFinalizeFunc
) NULL
,
479 (GClassInitFunc
) cg_cell_renderer_flags_class_init
,
482 sizeof (CgCellRendererFlags
),
484 (GInstanceInitFunc
) cg_cell_renderer_flags_init
,
488 our_type
= g_type_register_static (GTK_TYPE_CELL_RENDERER_TEXT
,
489 "CgCellRendererFlags",
497 cg_cell_renderer_flags_new (void)
501 object
= g_object_new (CG_TYPE_CELL_RENDERER_FLAGS
, NULL
);
503 return GTK_CELL_RENDERER (object
);