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