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