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"
23 #include <gtk/gtkcombobox.h>
24 #include <gtk/gtktreemodel.h>
25 #include <gtk/gtkcelllayout.h>
26 #include <gtk/gtkcellrenderertext.h>
27 #include <gtk/gtkcellrenderertoggle.h>
28 #include <gtk/gtkcellrenderer.h>
30 typedef struct _CgCellRendererFlagsPrivate CgCellRendererFlagsPrivate
;
31 struct _CgCellRendererFlagsPrivate
37 GHashTable
*edit_status
;
41 #define CG_CELL_RENDERER_FLAGS_PRIVATE(o) \
42 (G_TYPE_INSTANCE_GET_PRIVATE( \
44 CG_TYPE_CELL_RENDERER_FLAGS, \
45 CgCellRendererFlagsPrivate \
56 #define CG_CELL_RENDERER_FLAGS_PATH "cg-cell-renderer-flags-path"
58 static GtkCellRendererTextClass
*parent_class
= NULL
;
61 cg_cell_renderer_flags_init (CgCellRendererFlags
*cell_renderer_flags
)
63 CgCellRendererFlagsPrivate
*priv
;
64 priv
= CG_CELL_RENDERER_FLAGS_PRIVATE (cell_renderer_flags
);
67 priv
->text_column
= -1;
68 priv
->abbr_column
= -1;
70 priv
->edit_status
= NULL
;
71 priv
->focus_out_id
= 0;
75 cg_cell_renderer_flags_finalize (GObject
*object
)
77 CgCellRendererFlags
*cell_renderer_flags
;
78 CgCellRendererFlagsPrivate
*priv
;
80 cell_renderer_flags
= CG_CELL_RENDERER_FLAGS (object
);
81 priv
= CG_CELL_RENDERER_FLAGS_PRIVATE (cell_renderer_flags
);
83 if (priv
->edit_status
!= NULL
)
85 g_hash_table_destroy (priv
->edit_status
);
86 priv
->edit_status
= NULL
;
89 if (priv
->model
!= NULL
)
91 g_object_unref (G_OBJECT(priv
->model
));
95 G_OBJECT_CLASS (parent_class
)-> finalize(object
);
99 cg_cell_renderer_flags_set_property (GObject
*object
,
104 CgCellRendererFlags
*renderer
;
105 CgCellRendererFlagsPrivate
*priv
;
107 g_return_if_fail (CG_IS_CELL_RENDERER_FLAGS (object
));
109 renderer
= CG_CELL_RENDERER_FLAGS (object
);
110 priv
= CG_CELL_RENDERER_FLAGS_PRIVATE (renderer
);
115 if(priv
->model
!= NULL
) g_object_unref (G_OBJECT (priv
->model
));
116 priv
->model
= GTK_TREE_MODEL (g_value_dup_object (value
));
118 case PROP_TEXT_COLUMN
:
119 priv
->text_column
= g_value_get_int (value
);
121 case PROP_ABBR_COLUMN
:
122 priv
->abbr_column
= g_value_get_int (value
);
125 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
131 cg_cell_renderer_flags_get_property (GObject
*object
,
136 CgCellRendererFlags
*renderer
;
137 CgCellRendererFlagsPrivate
*priv
;
139 g_return_if_fail (CG_IS_CELL_RENDERER_FLAGS (object
));
141 renderer
= CG_CELL_RENDERER_FLAGS (object
);
142 priv
= CG_CELL_RENDERER_FLAGS_PRIVATE (renderer
);
147 g_value_set_object (value
, G_OBJECT (priv
->model
));
149 case PROP_TEXT_COLUMN
:
150 g_value_set_int (value
, priv
->text_column
);
152 case PROP_ABBR_COLUMN
:
153 g_value_set_int (value
, priv
->abbr_column
);
156 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
162 cg_cell_renderer_flags_editing_done (GtkCellEditable
*editable
,
163 G_GNUC_UNUSED gpointer data
)
165 CgCellRendererFlags
*cell_flags
;
166 CgCellRendererFlagsPrivate
*priv
;
175 cell_flags
= CG_CELL_RENDERER_FLAGS (data
);
176 priv
= CG_CELL_RENDERER_FLAGS_PRIVATE (cell_flags
);
178 g_assert (priv
->edit_status
!= NULL
);
180 if (priv
->focus_out_id
> 0)
182 g_signal_handler_disconnect (G_OBJECT (editable
), priv
->focus_out_id
);
183 priv
->focus_out_id
= 0;
186 canceled
= cg_combo_flags_editing_canceled (CG_COMBO_FLAGS (editable
));
187 gtk_cell_renderer_stop_editing (GTK_CELL_RENDERER(cell_flags
), canceled
);
189 if (canceled
== FALSE
)
191 str
= g_string_sized_new (128);
193 /* We do not just call g_hash_table_foreach to get the flags
194 * in the correct order. */
195 for (result
= gtk_tree_model_get_iter_first (priv
->model
, &iter
);
197 result
= gtk_tree_model_iter_next (priv
->model
, &iter
))
199 gtk_tree_model_get (priv
->model
, &iter
,
200 priv
->abbr_column
, &abbr
, -1);
202 if (g_hash_table_lookup (priv
->edit_status
, abbr
) != NULL
)
204 if (str
->len
> 0) g_string_append_c (str
, '|');
205 g_string_append (str
, abbr
);
211 path
= g_object_get_data (G_OBJECT (editable
),
212 CG_CELL_RENDERER_FLAGS_PATH
);
214 g_signal_emit_by_name (G_OBJECT (cell_flags
), "edited",
217 g_string_free (str
, TRUE
);
220 g_hash_table_destroy (priv
->edit_status
);
221 priv
->edit_status
= NULL
;
225 cg_cell_renderer_flags_selected (CgComboFlags
*combo
,
227 CgComboFlagsSelectionType type
,
230 CgCellRendererFlags
*cell_flags
;
231 CgCellRendererFlagsPrivate
*priv
;
236 cell_flags
= CG_CELL_RENDERER_FLAGS (user_data
);
237 priv
= CG_CELL_RENDERER_FLAGS_PRIVATE (cell_flags
);
239 gtk_tree_model_get (priv
->model
, iter
, priv
->text_column
, &name
,
240 priv
->abbr_column
, &abbr
, -1);
242 g_assert (priv
->edit_status
!= NULL
);
243 result
= g_hash_table_lookup (priv
->edit_status
, abbr
);
245 /* abbr needs not to be freed if it gets inserted into the hash table
246 * because the hash table then takes ownership of it. */
249 case CG_COMBO_FLAGS_SELECTION_NONE
:
252 case CG_COMBO_FLAGS_SELECTION_SELECT
:
253 if (GPOINTER_TO_INT(result
) != 1)
254 g_hash_table_insert (priv
->edit_status
, abbr
, GINT_TO_POINTER (1));
259 case CG_COMBO_FLAGS_SELECTION_UNSELECT
:
260 if (GPOINTER_TO_INT (result
) == 1)
261 g_hash_table_remove(priv
->edit_status
, abbr
);
265 case CG_COMBO_FLAGS_SELECTION_TOGGLE
:
266 if (GPOINTER_TO_INT (result
) == 1)
268 g_hash_table_remove (priv
->edit_status
, abbr
);
273 g_hash_table_insert (priv
->edit_status
, abbr
, GINT_TO_POINTER (1));
278 g_assert_not_reached ();
282 /* This is done to get GTK+ to re-render this row with the changed flag
283 * status that is set via the cell data func, but GTK+ does not call it
284 * again because it does not know that the hash table changed. There are
285 * probably better means to achieve this, but I am not aware of those. */
286 gtk_list_store_set (GTK_LIST_STORE (priv
->model
), iter
,
287 priv
->text_column
, name
, -1);
293 cg_cell_renderer_flags_focus_out_event (GtkWidget
*widget
,
294 G_GNUC_UNUSED GdkEvent
*event
,
297 cg_cell_renderer_flags_editing_done (GTK_CELL_EDITABLE (widget
), data
);
302 cg_cell_renderer_flags_set_data_func (G_GNUC_UNUSED GtkCellLayout
*cell_layout
,
303 GtkCellRenderer
*cell
,
308 CgCellRendererFlags
*cell_flags
;
309 CgCellRendererFlagsPrivate
*priv
;
312 cell_flags
= CG_CELL_RENDERER_FLAGS (data
);
313 priv
= CG_CELL_RENDERER_FLAGS_PRIVATE (cell_flags
);
315 if(priv
->edit_status
!= NULL
)
317 gtk_tree_model_get (model
, iter
, priv
->abbr_column
, &abbr
, -1);
319 if (g_hash_table_lookup (priv
->edit_status
, abbr
) != NULL
)
320 g_object_set (G_OBJECT (cell
), "active", TRUE
, NULL
);
322 g_object_set (G_OBJECT (cell
), "active", FALSE
, NULL
);
328 static GtkCellEditable
*
329 cg_cell_renderer_flags_start_editing (GtkCellRenderer
*cell
,
330 G_GNUC_UNUSED GdkEvent
*event
,
331 G_GNUC_UNUSED GtkWidget
*widget
,
333 G_GNUC_UNUSED GdkRectangle
335 G_GNUC_UNUSED GdkRectangle
*cell_area
,
336 G_GNUC_UNUSED GtkCellRendererState flags
)
338 CgCellRendererFlags
*cell_flags
;
339 CgCellRendererFlagsPrivate
*priv
;
340 GtkCellRendererText
*cell_text
;
345 GtkCellRenderer
*cell_combo_set
;
346 GtkCellRenderer
*cell_combo_text
;
348 cell_flags
= CG_CELL_RENDERER_FLAGS (cell
);
349 priv
= CG_CELL_RENDERER_FLAGS_PRIVATE (cell_flags
);
351 cell_text
= GTK_CELL_RENDERER_TEXT (cell
);
352 if (cell_text
->editable
== FALSE
) return NULL
;
354 if (priv
->model
== NULL
|| priv
->text_column
< 0 || priv
->abbr_column
< 0)
357 cell_combo_set
= gtk_cell_renderer_toggle_new ();
358 cell_combo_text
= gtk_cell_renderer_text_new ();
360 combo
= cg_combo_flags_new_with_model (priv
->model
);
362 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo
),
363 cell_combo_set
, FALSE
);
364 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo
),
365 cell_combo_text
, TRUE
);
367 gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo
),
368 cell_combo_text
, "text", priv
->text_column
);
370 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo
),
372 cg_cell_renderer_flags_set_data_func
,
375 g_object_set (G_OBJECT (cell_combo_set
), "activatable", FALSE
, NULL
);
377 /* Create hash table with current status. We could also operate
378 * directly on a string here, but a hash table is probably more
380 g_assert (priv
->edit_status
== NULL
);
381 priv
->edit_status
= g_hash_table_new_full (g_str_hash
, g_str_equal
,
382 (GDestroyNotify
) g_free
, NULL
);
384 pos
= cell_text
->text
;
385 prev
= cell_text
->text
;
387 while (prev
!= NULL
&& *prev
!= '\0')
389 while (*pos
!= '|' && *pos
!= '\0') ++ pos
;
391 g_hash_table_insert (priv
->edit_status
, g_strndup(prev
, pos
- prev
),
394 if(*pos
!= '\0') ++ pos
;
398 g_object_set_data_full (G_OBJECT (combo
), CG_CELL_RENDERER_FLAGS_PATH
,
399 g_strdup (path
), g_free
);
401 gtk_widget_show (combo
);
403 g_signal_connect (G_OBJECT (combo
), "editing-done",
404 G_CALLBACK (cg_cell_renderer_flags_editing_done
),
407 g_signal_connect (G_OBJECT (combo
), "selected",
408 G_CALLBACK (cg_cell_renderer_flags_selected
),
412 g_signal_connect (G_OBJECT (combo
), "focus_out_event",
413 G_CALLBACK (cg_cell_renderer_flags_focus_out_event
),
416 return GTK_CELL_EDITABLE (combo
);
420 cg_cell_renderer_flags_class_init (CgCellRendererFlagsClass
*klass
)
422 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
423 GtkCellRendererClass
*cell_class
= GTK_CELL_RENDERER_CLASS (klass
);
424 parent_class
= g_type_class_peek_parent (klass
);
426 g_type_class_add_private (klass
, sizeof (CgCellRendererFlagsPrivate
));
428 object_class
->finalize
= cg_cell_renderer_flags_finalize
;
429 object_class
->set_property
= cg_cell_renderer_flags_set_property
;
430 object_class
->get_property
= cg_cell_renderer_flags_get_property
;
432 cell_class
->start_editing
= cg_cell_renderer_flags_start_editing
;
434 g_object_class_install_property (object_class
,
436 g_param_spec_object ("model",
438 "Model holding the available flags",
442 g_object_class_install_property (object_class
,
444 g_param_spec_int ("text-column",
446 "Column in the model holding the text for a flag",
452 g_object_class_install_property (object_class
,
454 g_param_spec_int ("abbrevation-column",
455 "Abbrevation column",
456 "Column in the model holding the abbrevation for a flag",
464 cg_cell_renderer_flags_get_type (void)
466 static GType our_type
= 0;
470 static const GTypeInfo our_info
=
472 sizeof (CgCellRendererFlagsClass
),
473 (GBaseInitFunc
) NULL
,
474 (GBaseFinalizeFunc
) NULL
,
475 (GClassInitFunc
) cg_cell_renderer_flags_class_init
,
478 sizeof (CgCellRendererFlags
),
480 (GInstanceInitFunc
) cg_cell_renderer_flags_init
,
484 our_type
= g_type_register_static (GTK_TYPE_CELL_RENDERER_TEXT
,
485 "CgCellRendererFlags",
493 cg_cell_renderer_flags_new (void)
497 object
= g_object_new (CG_TYPE_CELL_RENDERER_FLAGS
, NULL
);
499 return GTK_CELL_RENDERER (object
);