gtkcellrenderercombo.c 16.1 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
   *
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 
141
   * this cell renderer is attached.
142
   * 
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
   *
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 210 211
}

static void
gtk_cell_renderer_combo_init (GtkCellRendererCombo *self)
{
212
  GtkCellRendererComboPrivate *priv;
213

214
  self->priv = gtk_cell_renderer_combo_get_instance_private (self);
215 216 217 218 219 220
  priv = self->priv;

  priv->model = NULL;
  priv->text_column = -1;
  priv->has_entry = TRUE;
  priv->focus_out_id = 0;
221 222 223 224 225
}

/**
 * gtk_cell_renderer_combo_new: 
 * 
Matthias Clasen's avatar
Matthias Clasen committed
226
 * Creates a new #GtkCellRendererCombo. 
227 228 229
 * 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 
230
 * in a #GtkTreeModel. For example, you can bind the “text” property 
231 232 233 234 235 236 237 238 239 240 241 242 243
 * 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); 
}

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

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

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

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

  switch (prop_id)
    {
    case PROP_MODEL:
297
      {
298 299 300 301 302
        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);
303
        break;
304
      }
305
    case PROP_TEXT_COLUMN:
306 307 308 309 310
      if (priv->text_column != g_value_get_int (value))
        {
          priv->text_column = g_value_get_int (value);
          g_object_notify_by_pspec (object, pspec);
        }
311 312
      break;
    case PROP_HAS_ENTRY:
313 314 315 316 317
      if (priv->has_entry != g_value_get_boolean (value))
        {
          priv->has_entry = g_value_get_boolean (value);
          g_object_notify_by_pspec (object, pspec);
        }
318 319 320
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
321
      break;
322 323 324
    }
}

325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
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);
    }
}

344 345 346 347 348 349 350 351 352 353
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;
354
  gboolean canceled;
355
  GtkCellRendererComboPrivate *priv;
356 357

  cell = GTK_CELL_RENDERER_COMBO (data);
358
  priv = cell->priv;
359

360
  if (priv->focus_out_id > 0)
361
    {
362 363
      g_signal_handler_disconnect (combo, priv->focus_out_id);
      priv->focus_out_id = 0;
364
    }
365 366 367 368

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

376
  if (gtk_combo_box_get_has_entry (GTK_COMBO_BOX (combo)))
377 378 379 380 381 382 383
    {
      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));
384 385 386

      if (model
          && gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
387
        gtk_tree_model_get (model, &iter, priv->text_column, &new_text, -1);
388 389 390 391 392
    }

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

393 394
  priv->combo = NULL;

395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
  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)
{
422
  GtkCellRendererComboPrivate *priv;
423
  SearchData *search_data = (SearchData *)data;
424
  gchar *text, *cell_text;
425 426

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

438
  g_free (cell_text);
439
  g_free (text);
440 441 442 443 444 445
  
  return search_data->found;
}

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

  cell_text = GTK_CELL_RENDERER_TEXT (cell);
462 463
  g_object_get (cell_text, "editable", &editable, NULL);
  if (editable == FALSE)
464 465 466
    return NULL;

  cell_combo = GTK_CELL_RENDERER_COMBO (cell);
467
  priv = cell_combo->priv;
468

469 470
  if (priv->text_column < 0)
    return NULL;
471

472
  if (priv->has_entry)
473
    {
474
      combo = g_object_new (GTK_TYPE_COMBO_BOX, "has-entry", TRUE, NULL);
475

476 477
      if (priv->model)
        gtk_combo_box_set_model (GTK_COMBO_BOX (combo), priv->model);
478
      gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (combo),
479
                                           priv->text_column);
480

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

      combo = gtk_combo_box_new ();
492 493
      if (priv->model)
        gtk_combo_box_set_model (GTK_COMBO_BOX (combo), priv->model);
494

495 496
      gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
      gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), 
497
				      cell, "text", priv->text_column,
498 499 500
				      NULL);

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

512
  g_object_set (combo, "has-frame", FALSE, NULL);
513
  g_object_set_data_full (G_OBJECT (combo),
514
			  I_(GTK_CELL_RENDERER_COMBO_PATH),
515 516 517 518
			  g_strdup (path), g_free);

  gtk_widget_show (combo);

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

529 530
  priv->combo = combo;

531 532
  return GTK_CELL_EDITABLE (combo);
}