1/* GtkCellRendererCombo
2 * Copyright (C) 2004 Lorenzo Gil Sanchez
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19#include <string.h>
20
21#include "gtkintl.h"
22#include "gtkentry.h"
23#include "gtkcelllayout.h"
24#include "gtkcellrenderercombo.h"
25#include "gtkcellrenderertext.h"
26#include "gtkcombobox.h"
27#include "gtkmarshalers.h"
28#include "gtkprivate.h"
29
30
31/**
32 * GtkCellRendererCombo:
33 *
34 * Renders a combobox in a cell
35 *
36 * `GtkCellRendererCombo` renders text in a cell like `GtkCellRendererText` from
37 * which it is derived. But while `GtkCellRendererText` offers a simple entry to
38 * edit the text, `GtkCellRendererCombo` offers a `GtkComboBox`
39 * widget to edit the text. The values to display in the combo box are taken from
40 * the tree model specified in the `GtkCellRendererCombo`:model property.
41 *
42 * The combo cell renderer takes care of adding a text cell renderer to the combo
43 * box and sets it to display the column specified by its
44 * `GtkCellRendererCombo`:text-column property. Further properties of the combo box
45 * can be set in a handler for the `GtkCellRenderer::editing-started` signal.
46 */
47
48typedef struct _GtkCellRendererComboPrivate GtkCellRendererComboPrivate;
49typedef struct _GtkCellRendererComboClass GtkCellRendererComboClass;
50
51struct _GtkCellRendererCombo
52{
53 GtkCellRendererText parent;
54};
55
56struct _GtkCellRendererComboClass
57{
58 GtkCellRendererTextClass parent;
59};
60
61
62struct _GtkCellRendererComboPrivate
63{
64 GtkTreeModel *model;
65
66 GtkWidget *combo;
67
68 gboolean has_entry;
69
70 int text_column;
71
72 gulong focus_out_id;
73};
74
75
76static void gtk_cell_renderer_combo_finalize (GObject *object);
77static void gtk_cell_renderer_combo_get_property (GObject *object,
78 guint prop_id,
79 GValue *value,
80 GParamSpec *pspec);
81
82static void gtk_cell_renderer_combo_set_property (GObject *object,
83 guint prop_id,
84 const GValue *value,
85 GParamSpec *pspec);
86
87static GtkCellEditable *gtk_cell_renderer_combo_start_editing (GtkCellRenderer *cell,
88 GdkEvent *event,
89 GtkWidget *widget,
90 const char *path,
91 const GdkRectangle *background_area,
92 const GdkRectangle *cell_area,
93 GtkCellRendererState flags);
94
95enum {
96 PROP_0,
97 PROP_MODEL,
98 PROP_TEXT_COLUMN,
99 PROP_HAS_ENTRY
100};
101
102enum {
103 CHANGED,
104 LAST_SIGNAL
105};
106
107static guint cell_renderer_combo_signals[LAST_SIGNAL] = { 0, };
108
109#define GTK_CELL_RENDERER_COMBO_PATH "gtk-cell-renderer-combo-path"
110
111G_DEFINE_TYPE_WITH_PRIVATE (GtkCellRendererCombo, gtk_cell_renderer_combo, GTK_TYPE_CELL_RENDERER_TEXT)
112
113static void
114gtk_cell_renderer_combo_class_init (GtkCellRendererComboClass *klass)
115{
116 GObjectClass *object_class = G_OBJECT_CLASS (klass);
117 GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass);
118
119 object_class->finalize = gtk_cell_renderer_combo_finalize;
120 object_class->get_property = gtk_cell_renderer_combo_get_property;
121 object_class->set_property = gtk_cell_renderer_combo_set_property;
122
123 cell_class->start_editing = gtk_cell_renderer_combo_start_editing;
124
125 /**
126 * GtkCellRendererCombo:model:
127 *
128 * Holds a tree model containing the possible values for the combo box.
129 * Use the text_column property to specify the column holding the values.
130 */
131 g_object_class_install_property (oclass: object_class,
132 property_id: PROP_MODEL,
133 pspec: g_param_spec_object (name: "model",
134 P_("Model"),
135 P_("The model containing the possible values for the combo box"),
136 GTK_TYPE_TREE_MODEL,
137 GTK_PARAM_READWRITE));
138
139 /**
140 * GtkCellRendererCombo:text-column:
141 *
142 * Specifies the model column which holds the possible values for the
143 * combo box.
144 *
145 * Note that this refers to the model specified in the model property,
146 * not the model backing the tree view to which
147 * this cell renderer is attached.
148 *
149 * `GtkCellRendererCombo` automatically adds a text cell renderer for
150 * this column to its combo box.
151 */
152 g_object_class_install_property (oclass: object_class,
153 property_id: PROP_TEXT_COLUMN,
154 pspec: g_param_spec_int (name: "text-column",
155 P_("Text Column"),
156 P_("A column in the data source model to get the strings from"),
157 minimum: -1,
158 G_MAXINT,
159 default_value: -1,
160 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
161
162 /**
163 * GtkCellRendererCombo:has-entry:
164 *
165 * If %TRUE, the cell renderer will include an entry and allow to enter
166 * values other than the ones in the popup list.
167 */
168 g_object_class_install_property (oclass: object_class,
169 property_id: PROP_HAS_ENTRY,
170 pspec: g_param_spec_boolean (name: "has-entry",
171 P_("Has Entry"),
172 P_("If FALSE, don’t allow to enter strings other than the chosen ones"),
173 TRUE,
174 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
175
176
177 /**
178 * GtkCellRendererCombo::changed:
179 * @combo: the object on which the signal is emitted
180 * @path_string: a string of the path identifying the edited cell
181 * (relative to the tree view model)
182 * @new_iter: the new iter selected in the combo box
183 * (relative to the combo box model)
184 *
185 * This signal is emitted each time after the user selected an item in
186 * the combo box, either by using the mouse or the arrow keys. Contrary
187 * to GtkComboBox, GtkCellRendererCombo::changed is not emitted for
188 * changes made to a selected item in the entry. The argument @new_iter
189 * corresponds to the newly selected item in the combo box and it is relative
190 * to the GtkTreeModel set via the model property on GtkCellRendererCombo.
191 *
192 * Note that as soon as you change the model displayed in the tree view,
193 * the tree view will immediately cease the editing operating. This
194 * means that you most probably want to refrain from changing the model
195 * until the combo cell renderer emits the edited or editing_canceled signal.
196 */
197 cell_renderer_combo_signals[CHANGED] =
198 g_signal_new (I_("changed"),
199 G_TYPE_FROM_CLASS (object_class),
200 signal_flags: G_SIGNAL_RUN_LAST,
201 class_offset: 0,
202 NULL, NULL,
203 c_marshaller: _gtk_marshal_VOID__STRING_BOXED,
204 G_TYPE_NONE, n_params: 2,
205 G_TYPE_STRING,
206 GTK_TYPE_TREE_ITER);
207 g_signal_set_va_marshaller (signal_id: cell_renderer_combo_signals[CHANGED],
208 G_TYPE_FROM_CLASS (object_class),
209 va_marshaller: _gtk_marshal_VOID__STRING_BOXEDv);
210}
211
212static void
213gtk_cell_renderer_combo_init (GtkCellRendererCombo *self)
214{
215 GtkCellRendererComboPrivate *priv = gtk_cell_renderer_combo_get_instance_private (self);
216
217 priv->model = NULL;
218 priv->text_column = -1;
219 priv->has_entry = TRUE;
220 priv->focus_out_id = 0;
221}
222
223/**
224 * gtk_cell_renderer_combo_new:
225 *
226 * Creates a new `GtkCellRendererCombo`.
227 * Adjust how text is drawn using object properties.
228 * Object properties can be set globally (with g_object_set()).
229 * Also, with `GtkTreeViewColumn`, you can bind a property to a value
230 * in a `GtkTreeModel`. For example, you can bind the “text” property
231 * on the cell renderer to a string value in the model, thus rendering
232 * a different string in each row of the `GtkTreeView`.
233 *
234 * Returns: the new cell renderer
235 */
236GtkCellRenderer *
237gtk_cell_renderer_combo_new (void)
238{
239 return g_object_new (GTK_TYPE_CELL_RENDERER_COMBO, NULL);
240}
241
242static void
243gtk_cell_renderer_combo_finalize (GObject *object)
244{
245 GtkCellRendererCombo *cell = GTK_CELL_RENDERER_COMBO (object);
246 GtkCellRendererComboPrivate *priv = gtk_cell_renderer_combo_get_instance_private (self: cell);
247
248 if (priv->model)
249 {
250 g_object_unref (object: priv->model);
251 priv->model = NULL;
252 }
253
254 G_OBJECT_CLASS (gtk_cell_renderer_combo_parent_class)->finalize (object);
255}
256
257static void
258gtk_cell_renderer_combo_get_property (GObject *object,
259 guint prop_id,
260 GValue *value,
261 GParamSpec *pspec)
262{
263 GtkCellRendererCombo *cell = GTK_CELL_RENDERER_COMBO (object);
264 GtkCellRendererComboPrivate *priv = gtk_cell_renderer_combo_get_instance_private (self: cell);
265
266 switch (prop_id)
267 {
268 case PROP_MODEL:
269 g_value_set_object (value, v_object: priv->model);
270 break;
271 case PROP_TEXT_COLUMN:
272 g_value_set_int (value, v_int: priv->text_column);
273 break;
274 case PROP_HAS_ENTRY:
275 g_value_set_boolean (value, v_boolean: priv->has_entry);
276 break;
277 default:
278 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
279 break;
280 }
281}
282
283static void
284gtk_cell_renderer_combo_set_property (GObject *object,
285 guint prop_id,
286 const GValue *value,
287 GParamSpec *pspec)
288{
289 GtkCellRendererCombo *cell = GTK_CELL_RENDERER_COMBO (object);
290 GtkCellRendererComboPrivate *priv = gtk_cell_renderer_combo_get_instance_private (self: cell);
291
292 switch (prop_id)
293 {
294 case PROP_MODEL:
295 {
296 if (priv->model)
297 g_object_unref (object: priv->model);
298 priv->model = GTK_TREE_MODEL (g_value_get_object (value));
299 if (priv->model)
300 g_object_ref (priv->model);
301 break;
302 }
303 case PROP_TEXT_COLUMN:
304 if (priv->text_column != g_value_get_int (value))
305 {
306 priv->text_column = g_value_get_int (value);
307 g_object_notify_by_pspec (object, pspec);
308 }
309 break;
310 case PROP_HAS_ENTRY:
311 if (priv->has_entry != g_value_get_boolean (value))
312 {
313 priv->has_entry = g_value_get_boolean (value);
314 g_object_notify_by_pspec (object, pspec);
315 }
316 break;
317 default:
318 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
319 break;
320 }
321}
322
323static void
324gtk_cell_renderer_combo_changed (GtkComboBox *combo,
325 gpointer data)
326{
327 GtkTreeIter iter;
328 GtkCellRendererCombo *cell;
329
330 cell = GTK_CELL_RENDERER_COMBO (data);
331
332 if (gtk_combo_box_get_active_iter (combo_box: combo, iter: &iter))
333 {
334 const char *path;
335
336 path = g_object_get_data (G_OBJECT (combo), GTK_CELL_RENDERER_COMBO_PATH);
337 g_signal_emit (instance: cell, signal_id: cell_renderer_combo_signals[CHANGED], detail: 0,
338 path, &iter);
339 }
340}
341
342static void
343gtk_cell_renderer_combo_editing_done (GtkCellEditable *combo,
344 gpointer data)
345{
346 GtkCellRendererCombo *cell = GTK_CELL_RENDERER_COMBO (data);
347 GtkCellRendererComboPrivate *priv = gtk_cell_renderer_combo_get_instance_private (self: cell);
348 const char *path;
349 char *new_text = NULL;
350 GtkTreeModel *model;
351 GtkTreeIter iter;
352 GtkEntry *entry;
353 gboolean canceled;
354
355 if (priv->focus_out_id > 0)
356 {
357 g_signal_handler_disconnect (instance: combo, handler_id: priv->focus_out_id);
358 priv->focus_out_id = 0;
359 }
360
361 g_object_get (object: combo,
362 first_property_name: "editing-canceled", &canceled,
363 NULL);
364 gtk_cell_renderer_stop_editing (GTK_CELL_RENDERER (data), canceled);
365 if (canceled)
366 {
367 priv->combo = NULL;
368 return;
369 }
370
371 if (gtk_combo_box_get_has_entry (GTK_COMBO_BOX (combo)))
372 {
373 entry = GTK_ENTRY (gtk_combo_box_get_child (GTK_COMBO_BOX (combo)));
374 new_text = g_strdup (str: gtk_editable_get_text (GTK_EDITABLE (entry)));
375 }
376 else
377 {
378 model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
379
380 if (model
381 && gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), iter: &iter))
382 gtk_tree_model_get (tree_model: model, iter: &iter, priv->text_column, &new_text, -1);
383 }
384
385 path = g_object_get_data (G_OBJECT (combo), GTK_CELL_RENDERER_COMBO_PATH);
386 g_signal_emit_by_name (instance: cell, detailed_signal: "edited", path, new_text);
387
388 priv->combo = NULL;
389
390 g_free (mem: new_text);
391}
392
393static void
394gtk_cell_renderer_combo_focus_change (GtkWidget *widget,
395 GParamSpec *pspec,
396 gpointer data)
397{
398 if (!gtk_widget_has_focus (widget))
399 gtk_cell_renderer_combo_editing_done (GTK_CELL_EDITABLE (widget), data);
400}
401
402typedef struct
403{
404 GtkCellRendererCombo *cell;
405 gboolean found;
406 GtkTreeIter iter;
407} SearchData;
408
409static gboolean
410find_text (GtkTreeModel *model,
411 GtkTreePath *path,
412 GtkTreeIter *iter,
413 gpointer data)
414{
415 SearchData *search_data = (SearchData *)data;
416 GtkCellRendererComboPrivate *priv = gtk_cell_renderer_combo_get_instance_private (self: search_data->cell);
417 char *text, *cell_text;
418
419 gtk_tree_model_get (tree_model: model, iter, priv->text_column, &text, -1);
420 g_object_get (GTK_CELL_RENDERER_TEXT (search_data->cell),
421 first_property_name: "text", &cell_text,
422 NULL);
423 if (text && cell_text && g_strcmp0 (str1: text, str2: cell_text) == 0)
424 {
425 search_data->iter = *iter;
426 search_data->found = TRUE;
427 }
428
429 g_free (mem: cell_text);
430 g_free (mem: text);
431
432 return search_data->found;
433}
434
435static GtkCellEditable *
436gtk_cell_renderer_combo_start_editing (GtkCellRenderer *cell,
437 GdkEvent *event,
438 GtkWidget *widget,
439 const char *path,
440 const GdkRectangle *background_area,
441 const GdkRectangle *cell_area,
442 GtkCellRendererState flags)
443{
444 GtkCellRendererCombo *cell_combo = GTK_CELL_RENDERER_COMBO (cell);
445 GtkCellRendererComboPrivate *priv = gtk_cell_renderer_combo_get_instance_private (self: cell_combo);
446 GtkWidget *combo;
447 SearchData search_data;
448 gboolean editable;
449 char *text;
450
451 g_object_get (object: cell, first_property_name: "editable", &editable, NULL);
452 if (editable == FALSE)
453 return NULL;
454
455 if (priv->text_column < 0)
456 return NULL;
457
458 if (priv->has_entry)
459 {
460 combo = g_object_new (GTK_TYPE_COMBO_BOX, first_property_name: "has-entry", TRUE, NULL);
461
462 if (priv->model)
463 gtk_combo_box_set_model (GTK_COMBO_BOX (combo), model: priv->model);
464 gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (combo),
465 text_column: priv->text_column);
466
467 g_object_get (object: cell, first_property_name: "text", &text, NULL);
468 if (text)
469 gtk_editable_set_text (GTK_EDITABLE (gtk_combo_box_get_child (GTK_COMBO_BOX (combo))), text);
470 g_free (mem: text);
471 }
472 else
473 {
474 cell = gtk_cell_renderer_text_new ();
475
476 combo = gtk_combo_box_new ();
477 if (priv->model)
478 gtk_combo_box_set_model (GTK_COMBO_BOX (combo), model: priv->model);
479
480 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
481 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo),
482 cell, "text", priv->text_column,
483 NULL);
484
485 /* determine the current value */
486 if (priv->model)
487 {
488 search_data.cell = cell_combo;
489 search_data.found = FALSE;
490 gtk_tree_model_foreach (model: priv->model, func: find_text, user_data: &search_data);
491 if (search_data.found)
492 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo),
493 iter: &(search_data.iter));
494 }
495 }
496
497 g_object_set (object: combo, first_property_name: "has-frame", FALSE, NULL);
498 g_object_set_data_full (G_OBJECT (combo),
499 I_(GTK_CELL_RENDERER_COMBO_PATH),
500 data: g_strdup (str: path), destroy: g_free);
501
502 gtk_widget_show (widget: combo);
503
504 g_signal_connect (GTK_CELL_EDITABLE (combo), "editing-done",
505 G_CALLBACK (gtk_cell_renderer_combo_editing_done),
506 cell_combo);
507 g_signal_connect (GTK_CELL_EDITABLE (combo), "changed",
508 G_CALLBACK (gtk_cell_renderer_combo_changed),
509 cell_combo);
510 priv->focus_out_id = g_signal_connect (combo, "notify::has-focus",
511 G_CALLBACK (gtk_cell_renderer_combo_focus_change),
512 cell_combo);
513
514 priv->combo = combo;
515
516 return GTK_CELL_EDITABLE (combo);
517}
518

source code of gtk/gtk/gtkcellrenderercombo.c