]> Pileus Git - ~andy/gtk/blob - gtk/gtkcellrenderercombo.c
initialize path so we don't potentially use an undefined pointer.
[~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, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include <config.h>
21 #include <string.h>
22
23 #include "gtkalias.h"
24 #include "gtkintl.h"
25 #include "gtkbin.h"
26 #include "gtkentry.h"
27 #include "gtkcelllayout.h"
28 #include "gtkcellrenderercombo.h"
29 #include "gtkcellrenderertext.h"
30 #include "gtkcombobox.h"
31 #include "gtkcomboboxentry.h"
32
33 static void gtk_cell_renderer_combo_class_init (GtkCellRendererComboClass *klass);
34 static void gtk_cell_renderer_combo_init       (GtkCellRendererCombo      *self);
35 static void gtk_cell_renderer_combo_get_property (GObject      *object,
36                                                   guint         prop_id,
37                                                   GValue       *value,
38                                                   GParamSpec   *pspec);
39
40 static void gtk_cell_renderer_combo_set_property (GObject      *object,
41                                                   guint         prop_id,
42                                                   const GValue *value,
43                                                   GParamSpec   *pspec);
44
45 static GtkCellEditable *gtk_cell_renderer_combo_start_editing (GtkCellRenderer     *cell,
46                                                                GdkEvent            *event,
47                                                                GtkWidget           *widget,
48                                                                const gchar         *path,
49                                                                GdkRectangle        *background_area,
50                                                                GdkRectangle        *cell_area,
51                                                                GtkCellRendererState flags);
52
53 enum {
54   PROP_0,
55   PROP_MODEL,
56   PROP_TEXT_COLUMN,
57   PROP_HAS_ENTRY
58 };
59
60 static GObjectClass *parent_class = NULL;
61
62 #define GTK_CELL_RENDERER_COMBO_PATH "gtk-cell-renderer-combo-path"
63
64 GType
65 gtk_cell_renderer_combo_get_type (void)
66 {
67   static GType gtk_cell_renderer_combo_type = 0;
68
69   if (!gtk_cell_renderer_combo_type)
70     {
71       static const GTypeInfo gtk_cell_renderer_combo_info = 
72         {
73           sizeof (GtkCellRendererComboClass),
74           (GBaseInitFunc)     NULL,
75           (GBaseFinalizeFunc) NULL,
76           (GClassInitFunc)    gtk_cell_renderer_combo_class_init,
77           NULL,
78           NULL,
79           sizeof (GtkCellRendererCombo),
80           0,
81           (GInstanceInitFunc) gtk_cell_renderer_combo_init
82         };
83       gtk_cell_renderer_combo_type =
84         g_type_register_static (GTK_TYPE_CELL_RENDERER_TEXT,
85                                 "GtkCellRendererCombo",
86                                 &gtk_cell_renderer_combo_info,
87                                 0);
88     }
89   return gtk_cell_renderer_combo_type;
90 }
91
92 static void
93 gtk_cell_renderer_combo_class_init (GtkCellRendererComboClass *klass)
94 {
95   GObjectClass *object_class = G_OBJECT_CLASS (klass);
96   GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass);
97
98   parent_class = g_type_class_peek_parent (klass);
99
100   object_class->get_property = gtk_cell_renderer_combo_get_property;
101   object_class->set_property = gtk_cell_renderer_combo_set_property;
102
103   cell_class->start_editing = gtk_cell_renderer_combo_start_editing;
104
105   /**
106    * GtkCellRendererCombo:model:
107    *
108    * The :model property holds a tree model containing the possible
109    * values for the combo box. Use the :text_column property to specify
110    * the column holding the values.
111    * 
112    * Since: 2.6
113    */
114   g_object_class_install_property (object_class,
115                                    PROP_MODEL,
116                                    g_param_spec_object ("model",
117                                                         P_("Model"),
118                                                         P_("The model containing the possible values for the combo box"),
119                                                         GTK_TYPE_TREE_MODEL,
120                                                         G_PARAM_READWRITE));
121
122   /**
123    * GtkCellRendererCombo:text_column:
124    *
125    * The :text_column property specifies the model column which
126    * holds the possible values for the combo box. Note that this
127    * refers to the model specified in the :model property, 
128    * <emphasis>not</emphasis> the model backing the tree view to 
129    * which this cell renderer is attached.
130    * 
131    * Since: 2.6
132    */
133   g_object_class_install_property (object_class,
134                                    PROP_TEXT_COLUMN,
135                                    g_param_spec_int ("text_column",
136                                                      P_("Text Column"),
137                                                      P_("A column in the data source model to get the strings from"),
138                                                      -1,
139                                                      G_MAXINT,
140                                                      -1,
141                                                      G_PARAM_READWRITE));
142
143   /** 
144    * GtkCellRendererCombo:has_entry:
145    *
146    * If the :has_entry property is %TRUe, the cell renderer will 
147    * include an entry and allow to enter values other than the ones 
148    * in the popup list. 
149    *
150    * Since: 2.6
151    */
152   g_object_class_install_property (object_class,
153                                    PROP_HAS_ENTRY,
154                                    g_param_spec_boolean ("has_entry",
155                                                          P_("Has Entry"),
156                                                          P_("If %FALSE, don't allow to enter strings other than the chosen ones"),
157                                                          TRUE,
158                                                          G_PARAM_READWRITE));
159
160 }
161
162 static void
163 gtk_cell_renderer_combo_init (GtkCellRendererCombo *self)
164 {
165   self->model = NULL;
166   self->text_column = -1;
167   self->focus_out_id = 0;
168 }
169
170 /**
171  * gtk_cell_renderer_combo_new: 
172  * 
173  * Creates a new #GtkCellRendererCombo 
174  * Adjust how text is drawn using object properties. 
175  * Object properties can be set globally (with g_object_set()). 
176  * Also, with #GtkTreeViewColumn, you can bind a property to a value 
177  * in a #GtkTreeModel. For example, you can bind the "text" property 
178  * on the cell renderer to a string value in the model, thus rendering 
179  * a different string in each row of the #GtkTreeView.
180  * 
181  * Returns: the new cell renderer
182  *
183  * Since: 2.6
184  */
185 GtkCellRenderer *
186 gtk_cell_renderer_combo_new (void)
187 {
188   return g_object_new (GTK_TYPE_CELL_RENDERER_COMBO, NULL); 
189 }
190
191 static void
192 gtk_cell_renderer_combo_get_property (GObject    *object,
193                                       guint       prop_id,
194                                       GValue     *value,
195                                       GParamSpec *pspec)
196 {
197   GtkCellRendererCombo *cell;
198  
199   g_return_if_fail (GTK_IS_CELL_RENDERER_COMBO (object));
200
201   cell = GTK_CELL_RENDERER_COMBO (object);
202
203   switch (prop_id)
204     {
205     case PROP_MODEL:
206       g_value_set_object (value, cell->model);
207       break; 
208     case PROP_TEXT_COLUMN:
209       g_value_set_int (value, cell->text_column);
210       break;
211     case PROP_HAS_ENTRY:
212       g_value_set_boolean (value, cell->has_entry);
213       break;
214    default:
215       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
216     }
217 }
218
219 static void
220 gtk_cell_renderer_combo_set_property (GObject      *object,
221                                       guint         prop_id,
222                                       const GValue *value,
223                                       GParamSpec   *pspec)
224 {
225   GtkCellRendererCombo *cell;
226   
227   g_return_if_fail (GTK_IS_CELL_RENDERER_COMBO (object));
228
229   cell = GTK_CELL_RENDERER_COMBO (object);
230
231   switch (prop_id)
232     {
233     case PROP_MODEL:
234       cell->model = g_value_get_object (value);
235       break;
236     case PROP_TEXT_COLUMN:
237       cell->text_column = g_value_get_int (value);
238       break;
239     case PROP_HAS_ENTRY:
240       cell->has_entry = g_value_get_boolean (value);
241       break;
242     default:
243       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
244     }
245 }
246
247 static void
248 gtk_cell_renderer_combo_editing_done (GtkCellEditable *combo,
249                                       gpointer         data)
250 {
251   const gchar *path;
252   gchar *new_text = NULL;
253   GtkTreeModel *model;
254   GtkTreeIter iter;
255   GtkCellRendererCombo *cell;
256   GtkEntry *entry;
257
258   cell = GTK_CELL_RENDERER_COMBO (data);
259
260   if (cell->focus_out_id > 0)
261     {
262       g_signal_handler_disconnect (combo, cell->focus_out_id);
263       cell->focus_out_id = 0;
264     }
265
266   if (_gtk_combo_box_editing_canceled (GTK_COMBO_BOX (combo)))
267     {
268       gtk_cell_renderer_editing_canceled (GTK_CELL_RENDERER (data));
269       return;
270     }
271
272   if (GTK_IS_COMBO_BOX_ENTRY (combo))
273     {
274       entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (combo)));
275       new_text = g_strdup (gtk_entry_get_text (entry));
276     }
277   else 
278     {
279       model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
280       if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
281         gtk_tree_model_get (model, &iter, cell->text_column, &new_text, -1);
282     }
283
284   path = g_object_get_data (G_OBJECT (combo), GTK_CELL_RENDERER_COMBO_PATH);
285   g_signal_emit_by_name (cell, "edited", path, new_text);
286
287   g_free (new_text);
288 }
289
290 static gboolean
291 gtk_cell_renderer_combo_focus_out_event (GtkWidget *widget,
292                                          GdkEvent  *event,
293                                          gpointer   data)
294 {
295   
296   gtk_cell_renderer_combo_editing_done (GTK_CELL_EDITABLE (widget), data);
297
298   return FALSE;
299 }
300
301 typedef struct 
302 {
303   GtkCellRendererCombo *cell;
304   gboolean found;
305   GtkTreeIter iter;
306 } SearchData;
307
308 static gboolean 
309 find_text (GtkTreeModel *model, 
310            GtkTreePath  *path, 
311            GtkTreeIter  *iter, 
312            gpointer      data)
313 {
314   SearchData *search_data = (SearchData *)data;
315   gchar *text;
316   
317   gtk_tree_model_get (model, iter, search_data->cell->text_column, &text, -1);
318   if (text && GTK_CELL_RENDERER_TEXT (search_data->cell)->text &&
319       strcmp (text, GTK_CELL_RENDERER_TEXT (search_data->cell)->text) == 0)
320     {
321       search_data->iter = *iter;
322       search_data->found = TRUE;
323     }
324   
325   return search_data->found;
326 }
327
328 static GtkCellEditable *
329 gtk_cell_renderer_combo_start_editing (GtkCellRenderer     *cell,
330                                        GdkEvent            *event,
331                                        GtkWidget           *widget,
332                                        const gchar         *path,
333                                        GdkRectangle        *background_area,
334                                        GdkRectangle        *cell_area,
335                                        GtkCellRendererState flags)
336 {
337   GtkCellRendererCombo *cell_combo;
338   GtkCellRendererText *cell_text;
339   GtkWidget *combo;
340   SearchData search_data;
341
342   cell_text = GTK_CELL_RENDERER_TEXT (cell);
343   if (cell_text->editable == FALSE)
344     return NULL;
345
346   cell_combo = GTK_CELL_RENDERER_COMBO (cell);
347   if (cell_combo->model == NULL || cell_combo->text_column < 0)
348     return NULL;
349
350   if (cell_combo->has_entry) 
351     {
352       combo = gtk_combo_box_entry_new_with_model (cell_combo->model, cell_combo->text_column);
353
354       if (cell_text->text)
355         gtk_entry_set_text (GTK_ENTRY (GTK_BIN (combo)->child), 
356                             cell_text->text);
357     }
358   else
359     {
360       cell = gtk_cell_renderer_text_new ();
361       combo = gtk_combo_box_new_with_model (cell_combo->model);
362       gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
363       gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), 
364                                       cell, "text", cell_combo->text_column, 
365                                       NULL);
366
367       /* determine the current value */
368       search_data.cell = cell_combo;
369       search_data.found = FALSE;
370       gtk_tree_model_foreach (cell_combo->model, find_text, &search_data);
371       if (search_data.found)
372         gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo),
373                                        &(search_data.iter));
374     }
375
376   g_object_set (combo, "has_frame", FALSE, NULL);
377   g_object_set_data_full (G_OBJECT (combo),
378                           GTK_CELL_RENDERER_COMBO_PATH,
379                           g_strdup (path), g_free);
380
381   gtk_widget_show (combo);
382
383   g_signal_connect (GTK_CELL_EDITABLE (combo), "editing_done",
384                     G_CALLBACK (gtk_cell_renderer_combo_editing_done),
385                     cell_combo);
386   cell_combo->focus_out_id = 
387     g_signal_connect (combo, "focus_out_event",
388                       G_CALLBACK (gtk_cell_renderer_combo_focus_out_event),
389                       cell_combo);
390
391   return GTK_CELL_EDITABLE (combo);
392 }