gtkcellrenderercombo.c 16.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* GtkCellRendererCombo
 * Copyright (C) 2004 Lorenzo Gil Sanchez
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16
17
 */

18
#include "config.h"
19
20
21
22
23
24
25
26
27
#include <string.h>

#include "gtkintl.h"
#include "gtkbin.h"
#include "gtkentry.h"
#include "gtkcelllayout.h"
#include "gtkcellrenderercombo.h"
#include "gtkcellrenderertext.h"
#include "gtkcombobox.h"
28
#include "gtkmarshalers.h"
29
#include "gtkprivate.h"
30

31
32
33
34
35
36
37
38

/**
 * SECTION:gtkcellrenderercombo
 * @Short_description: Renders a combobox in a cell
 * @Title: GtkCellRendererCombo
 *
 * #GtkCellRendererCombo renders text in a cell like #GtkCellRendererText from
 * which it is derived. But while #GtkCellRendererText offers a simple entry to
39
 * edit the text, #GtkCellRendererCombo offers a #GtkComboBox
40
41
42
43
44
 * widget to edit the text. The values to display in the combo box are taken from
 * the tree model specified in the #GtkCellRendererCombo:model property.
 *
 * The combo cell renderer takes care of adding a text cell renderer to the combo
 * box and sets it to display the column specified by its
Matthias Clasen's avatar
Matthias Clasen committed
45
 * #GtkCellRendererCombo:text-column property. Further properties of the combo box
46
47
48
49
50
51
 * can be set in a handler for the #GtkCellRenderer::editing-started signal.
 *
 * The #GtkCellRendererCombo cell renderer was added in GTK+ 2.6.
 */


52
struct _GtkCellRendererComboPrivate
53
{
54
55
  GtkTreeModel *model;

56
  GtkWidget *combo;
57
58
59
60
61

  gboolean has_entry;

  gint text_column;

62
  gulong focus_out_id;
63
64
65
};


66
67
static void gtk_cell_renderer_combo_class_init (GtkCellRendererComboClass *klass);
static void gtk_cell_renderer_combo_init       (GtkCellRendererCombo      *self);
68
static void gtk_cell_renderer_combo_finalize     (GObject      *object);
69
70
71
72
73
74
75
76
77
78
79
static void gtk_cell_renderer_combo_get_property (GObject      *object,
						  guint         prop_id,
						  GValue       *value,
						  GParamSpec   *pspec);

static void gtk_cell_renderer_combo_set_property (GObject      *object,
						  guint         prop_id,
						  const GValue *value,
						  GParamSpec   *pspec);

static GtkCellEditable *gtk_cell_renderer_combo_start_editing (GtkCellRenderer     *cell,
80
81
82
83
84
85
                                                               GdkEvent            *event,
                                                               GtkWidget           *widget,
                                                               const gchar         *path,
                                                               const GdkRectangle  *background_area,
                                                               const GdkRectangle  *cell_area,
                                                               GtkCellRendererState flags);
86
87
88
89
90
91
92
93

enum {
  PROP_0,
  PROP_MODEL,
  PROP_TEXT_COLUMN,
  PROP_HAS_ENTRY
};

94
95
96
97
98
99
100
enum {
  CHANGED,
  LAST_SIGNAL
};

static guint cell_renderer_combo_signals[LAST_SIGNAL] = { 0, };

101
102
#define GTK_CELL_RENDERER_COMBO_PATH "gtk-cell-renderer-combo-path"

103
G_DEFINE_TYPE_WITH_PRIVATE (GtkCellRendererCombo, gtk_cell_renderer_combo, GTK_TYPE_CELL_RENDERER_TEXT)
104
105
106
107
108
109
110

static void
gtk_cell_renderer_combo_class_init (GtkCellRendererComboClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass);

111
  object_class->finalize = gtk_cell_renderer_combo_finalize;
112
113
114
115
116
117
118
119
  object_class->get_property = gtk_cell_renderer_combo_get_property;
  object_class->set_property = gtk_cell_renderer_combo_set_property;

  cell_class->start_editing = gtk_cell_renderer_combo_start_editing;

  /**
   * GtkCellRendererCombo:model:
   *
Matthias Clasen's avatar
Matthias Clasen committed
120
121
   * Holds a tree model containing the possible values for the combo box. 
   * Use the text_column property to specify the column holding the values.
122
123
124
125
126
127
128
129
130
   * 
   * Since: 2.6
   */
  g_object_class_install_property (object_class,
				   PROP_MODEL,
				   g_param_spec_object ("model",
							P_("Model"),
							P_("The model containing the possible values for the combo box"),
							GTK_TYPE_TREE_MODEL,
131
							GTK_PARAM_READWRITE));
132
133

  /**
Matthias Clasen's avatar
Matthias Clasen committed
134
   * GtkCellRendererCombo:text-column:
135
   *
Matthias Clasen's avatar
Matthias Clasen committed
136
137
138
   * Specifies the model column which holds the possible values for the 
   * combo box. 
   *
Matthias Clasen's avatar
Matthias Clasen committed
139
   * Note that this refers to the model specified in the model property, 
140
   * not the model backing the tree view to which 
Matthias Clasen's avatar
Matthias Clasen committed
141
   * this cell renderer is attached.
142
   * 
Matthias Clasen's avatar
Matthias Clasen committed
143
144
   * #GtkCellRendererCombo automatically adds a text cell renderer for 
   * this column to its combo box.
Matthias Clasen's avatar
Matthias Clasen committed
145
   *
146
147
148
149
   * Since: 2.6
   */
  g_object_class_install_property (object_class,
                                   PROP_TEXT_COLUMN,
150
                                   g_param_spec_int ("text-column",
151
152
153
154
155
                                                     P_("Text Column"),
                                                     P_("A column in the data source model to get the strings from"),
                                                     -1,
                                                     G_MAXINT,
                                                     -1,
156
                                                     GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
157
158

  /** 
Matthias Clasen's avatar
Matthias Clasen committed
159
   * GtkCellRendererCombo:has-entry:
160
   *
Matthias Clasen's avatar
Matthias Clasen committed
161
162
   * If %TRUE, the cell renderer will include an entry and allow to enter 
   * values other than the ones in the popup list. 
163
164
165
166
167
   *
   * Since: 2.6
   */
  g_object_class_install_property (object_class,
                                   PROP_HAS_ENTRY,
168
                                   g_param_spec_boolean ("has-entry",
169
							 P_("Has Entry"),
Matthias Clasen's avatar
Matthias Clasen committed
170
							 P_("If FALSE, don't allow to enter strings other than the chosen ones"),
171
							 TRUE,
172
							 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
173

174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206

  /**
   * GtkCellRendererCombo::changed:
   * @combo: the object on which the signal is emitted
   * @path_string: a string of the path identifying the edited cell
   *               (relative to the tree view model)
   * @new_iter: the new iter selected in the combo box
   *            (relative to the combo box model)
   *
   * This signal is emitted each time after the user selected an item in
   * the combo box, either by using the mouse or the arrow keys.  Contrary
   * to GtkComboBox, GtkCellRendererCombo::changed is not emitted for
   * changes made to a selected item in the entry.  The argument @new_iter
   * corresponds to the newly selected item in the combo box and it is relative
   * to the GtkTreeModel set via the model property on GtkCellRendererCombo.
   *
   * Note that as soon as you change the model displayed in the tree view,
   * the tree view will immediately cease the editing operating.  This
   * means that you most probably want to refrain from changing the model
   * until the combo cell renderer emits the edited or editing_canceled signal.
   *
   * Since: 2.14
   */
  cell_renderer_combo_signals[CHANGED] =
    g_signal_new (I_("changed"),
		  G_TYPE_FROM_CLASS (object_class),
		  G_SIGNAL_RUN_LAST,
		  0,
		  NULL, NULL,
		  _gtk_marshal_VOID__STRING_BOXED,
		  G_TYPE_NONE, 2,
		  G_TYPE_STRING,
		  GTK_TYPE_TREE_ITER);
207
208
209
  g_signal_set_va_marshaller (cell_renderer_combo_signals[CHANGED],
                              G_TYPE_FROM_CLASS (object_class),
                              _gtk_marshal_VOID__STRING_BOXEDv);
210
211
212
213
214
}

static void
gtk_cell_renderer_combo_init (GtkCellRendererCombo *self)
{
215
  GtkCellRendererComboPrivate *priv;
216

217
  self->priv = gtk_cell_renderer_combo_get_instance_private (self);
218
219
220
221
222
223
  priv = self->priv;

  priv->model = NULL;
  priv->text_column = -1;
  priv->has_entry = TRUE;
  priv->focus_out_id = 0;
224
225
226
227
228
}

/**
 * gtk_cell_renderer_combo_new: 
 * 
Matthias Clasen's avatar
Matthias Clasen committed
229
 * Creates a new #GtkCellRendererCombo. 
230
231
232
 * Adjust how text is drawn using object properties. 
 * Object properties can be set globally (with g_object_set()). 
 * Also, with #GtkTreeViewColumn, you can bind a property to a value 
William Jon McCann's avatar
William Jon McCann committed
233
 * in a #GtkTreeModel. For example, you can bind the “text” property 
234
235
236
237
238
239
240
241
242
243
244
245
246
 * on the cell renderer to a string value in the model, thus rendering 
 * a different string in each row of the #GtkTreeView.
 * 
 * Returns: the new cell renderer
 *
 * Since: 2.6
 */
GtkCellRenderer *
gtk_cell_renderer_combo_new (void)
{
  return g_object_new (GTK_TYPE_CELL_RENDERER_COMBO, NULL); 
}

247
248
249
250
static void
gtk_cell_renderer_combo_finalize (GObject *object)
{
  GtkCellRendererCombo *cell = GTK_CELL_RENDERER_COMBO (object);
251
  GtkCellRendererComboPrivate *priv = cell->priv;
252
  
253
  if (priv->model)
254
    {
255
256
      g_object_unref (priv->model);
      priv->model = NULL;
257
258
259
260
261
    }
  
  G_OBJECT_CLASS (gtk_cell_renderer_combo_parent_class)->finalize (object);
}

262
263
264
265
266
267
static void
gtk_cell_renderer_combo_get_property (GObject    *object,
				      guint       prop_id,
				      GValue     *value,
				      GParamSpec *pspec)
{
268
  GtkCellRendererCombo *cell = GTK_CELL_RENDERER_COMBO (object);
269
  GtkCellRendererComboPrivate *priv = cell->priv;
270
271
272
273

  switch (prop_id)
    {
    case PROP_MODEL:
274
      g_value_set_object (value, priv->model);
275
276
      break; 
    case PROP_TEXT_COLUMN:
277
      g_value_set_int (value, priv->text_column);
278
279
      break;
    case PROP_HAS_ENTRY:
280
      g_value_set_boolean (value, priv->has_entry);
281
282
283
      break;
   default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
284
      break;
285
286
287
288
289
290
291
292
293
    }
}

static void
gtk_cell_renderer_combo_set_property (GObject      *object,
				      guint         prop_id,
				      const GValue *value,
				      GParamSpec   *pspec)
{
294
  GtkCellRendererCombo *cell = GTK_CELL_RENDERER_COMBO (object);
295
  GtkCellRendererComboPrivate *priv = cell->priv;
296
297
298
299

  switch (prop_id)
    {
    case PROP_MODEL:
300
      {
301
302
303
304
305
        if (priv->model)
          g_object_unref (priv->model);
        priv->model = GTK_TREE_MODEL (g_value_get_object (value));
        if (priv->model)
          g_object_ref (priv->model);
306
        break;
307
      }
308
    case PROP_TEXT_COLUMN:
309
310
311
312
313
      if (priv->text_column != g_value_get_int (value))
        {
          priv->text_column = g_value_get_int (value);
          g_object_notify_by_pspec (object, pspec);
        }
314
315
      break;
    case PROP_HAS_ENTRY:
316
317
318
319
320
      if (priv->has_entry != g_value_get_boolean (value))
        {
          priv->has_entry = g_value_get_boolean (value);
          g_object_notify_by_pspec (object, pspec);
        }
321
322
323
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
324
      break;
325
326
327
    }
}

328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
static void
gtk_cell_renderer_combo_changed (GtkComboBox *combo,
				 gpointer     data)
{
  GtkTreeIter iter;
  GtkCellRendererCombo *cell;

  cell = GTK_CELL_RENDERER_COMBO (data);

  if (gtk_combo_box_get_active_iter (combo, &iter))
    {
      const char *path;

      path = g_object_get_data (G_OBJECT (combo), GTK_CELL_RENDERER_COMBO_PATH);
      g_signal_emit (cell, cell_renderer_combo_signals[CHANGED], 0,
		     path, &iter);
    }
}

347
348
349
350
351
352
353
354
355
356
static void
gtk_cell_renderer_combo_editing_done (GtkCellEditable *combo,
				      gpointer         data)
{
  const gchar *path;
  gchar *new_text = NULL;
  GtkTreeModel *model;
  GtkTreeIter iter;
  GtkCellRendererCombo *cell;
  GtkEntry *entry;
357
  gboolean canceled;
358
  GtkCellRendererComboPrivate *priv;
359
360

  cell = GTK_CELL_RENDERER_COMBO (data);
361
  priv = cell->priv;
362

363
  if (priv->focus_out_id > 0)
364
    {
365
366
      g_signal_handler_disconnect (combo, priv->focus_out_id);
      priv->focus_out_id = 0;
367
    }
368
369
370
371

  g_object_get (combo,
                "editing-canceled", &canceled,
                NULL);
372
373
  gtk_cell_renderer_stop_editing (GTK_CELL_RENDERER (data), canceled);
  if (canceled)
374
375
376
377
    {
      priv->combo = NULL;
      return;
    }
378

379
  if (gtk_combo_box_get_has_entry (GTK_COMBO_BOX (combo)))
380
381
382
383
384
385
386
    {
      entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (combo)));
      new_text = g_strdup (gtk_entry_get_text (entry));
    }
  else 
    {
      model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
387
388
389

      if (model
          && gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
390
        gtk_tree_model_get (model, &iter, priv->text_column, &new_text, -1);
391
392
393
394
395
    }

  path = g_object_get_data (G_OBJECT (combo), GTK_CELL_RENDERER_COMBO_PATH);
  g_signal_emit_by_name (cell, "edited", path, new_text);

396
397
  priv->combo = NULL;

398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
  g_free (new_text);
}

static gboolean
gtk_cell_renderer_combo_focus_out_event (GtkWidget *widget,
					 GdkEvent  *event,
					 gpointer   data)
{
  
  gtk_cell_renderer_combo_editing_done (GTK_CELL_EDITABLE (widget), data);

  return FALSE;
}

typedef struct 
{
  GtkCellRendererCombo *cell;
  gboolean found;
  GtkTreeIter iter;
} SearchData;

static gboolean 
find_text (GtkTreeModel *model, 
	   GtkTreePath  *path, 
	   GtkTreeIter  *iter, 
	   gpointer      data)
{
425
  GtkCellRendererComboPrivate *priv;
426
  SearchData *search_data = (SearchData *)data;
427
  gchar *text, *cell_text;
428
429

  priv = search_data->cell->priv;
430
  
431
  gtk_tree_model_get (model, iter, priv->text_column, &text, -1);
432
433
434
435
  g_object_get (GTK_CELL_RENDERER_TEXT (search_data->cell),
                "text", &cell_text,
                NULL);
  if (text && cell_text && g_strcmp0 (text, cell_text) == 0)
436
437
438
439
    {
      search_data->iter = *iter;
      search_data->found = TRUE;
    }
440

441
  g_free (cell_text);
442
  g_free (text);
443
444
445
446
447
448
  
  return search_data->found;
}

static GtkCellEditable *
gtk_cell_renderer_combo_start_editing (GtkCellRenderer     *cell,
449
450
451
452
453
454
                                       GdkEvent            *event,
                                       GtkWidget           *widget,
                                       const gchar         *path,
                                       const GdkRectangle  *background_area,
                                       const GdkRectangle  *cell_area,
                                       GtkCellRendererState flags)
455
456
457
458
459
{
  GtkCellRendererCombo *cell_combo;
  GtkCellRendererText *cell_text;
  GtkWidget *combo;
  SearchData search_data;
460
  GtkCellRendererComboPrivate *priv;
461
462
  gboolean editable;
  gchar *text;
463
464

  cell_text = GTK_CELL_RENDERER_TEXT (cell);
465
466
  g_object_get (cell_text, "editable", &editable, NULL);
  if (editable == FALSE)
467
468
469
    return NULL;

  cell_combo = GTK_CELL_RENDERER_COMBO (cell);
470
  priv = cell_combo->priv;
471

472
473
  if (priv->text_column < 0)
    return NULL;
474

475
  if (priv->has_entry)
476
    {
477
      combo = g_object_new (GTK_TYPE_COMBO_BOX, "has-entry", TRUE, NULL);
478

479
480
      if (priv->model)
        gtk_combo_box_set_model (GTK_COMBO_BOX (combo), priv->model);
481
      gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (combo),
482
                                           priv->text_column);
483

484
485
      g_object_get (cell_text, "text", &text, NULL);
      if (text)
Javier Jardón's avatar
Javier Jardón committed
486
	gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (combo))),
487
488
			    text);
      g_free (text);
489
490
491
492
    }
  else
    {
      cell = gtk_cell_renderer_text_new ();
493
494

      combo = gtk_combo_box_new ();
495
496
      if (priv->model)
        gtk_combo_box_set_model (GTK_COMBO_BOX (combo), priv->model);
497

498
499
      gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
      gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), 
500
				      cell, "text", priv->text_column,
501
502
503
				      NULL);

      /* determine the current value */
504
      if (priv->model)
505
506
507
        {
          search_data.cell = cell_combo;
          search_data.found = FALSE;
508
          gtk_tree_model_foreach (priv->model, find_text, &search_data);
509
510
511
512
          if (search_data.found)
            gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo),
                                           &(search_data.iter));
        }
513
514
    }

515
  g_object_set (combo, "has-frame", FALSE, NULL);
516
  g_object_set_data_full (G_OBJECT (combo),
Matthias Clasen's avatar
Matthias Clasen committed
517
			  I_(GTK_CELL_RENDERER_COMBO_PATH),
518
519
520
521
			  g_strdup (path), g_free);

  gtk_widget_show (combo);

522
  g_signal_connect (GTK_CELL_EDITABLE (combo), "editing-done",
523
524
		    G_CALLBACK (gtk_cell_renderer_combo_editing_done),
		    cell_combo);
525
526
527
  g_signal_connect (GTK_CELL_EDITABLE (combo), "changed",
		    G_CALLBACK (gtk_cell_renderer_combo_changed),
		    cell_combo);
528
529
530
  priv->focus_out_id = g_signal_connect (combo, "focus-out-event",
                                         G_CALLBACK (gtk_cell_renderer_combo_focus_out_event),
                                         cell_combo);
531

532
533
  priv->combo = combo;

534
535
  return GTK_CELL_EDITABLE (combo);
}