diff --git a/ChangeLog b/ChangeLog index b15ca79470..452243d8f2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,28 @@ +2004-07-16 Matthias Clasen + + Add a combo box cell renderer (#139347, Lorenzo Gil Sanchez) + + * gtk/gtkcellrenderercombo.[hc]: New Files. + + * gtk/gtk.h: + * gtk/Makefile.am (gtk_public_h_sources, gtk_c_sources): + Add the new files. + + * gtk/gtkcombobox.[hc]: Implement GtkCellEditable, add a + :has-frame property for suppressing the frame around + the child and redo the size allocation logic to take + focus width into account. + + * gtk/gtkcomboboxentry.c (gtk_combo_box_entry_init): + Acknowledge the GtkComboBox:has-frame property and make + the entry fill its allocation vertically. + + * gtk/gtkmarshalers.list: Add BOOLEAN:STRING. + + * gtk/gtktreeview.c (gtk_tree_view_remove_widget): Queue + a draw here to keep non-widget windows from leaving + shadows behind. + 2004-07-16 Matthias Clasen * gtk/gtkcombobox.c (gtk_combo_box_forall): Don't iterate diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index b15ca79470..452243d8f2 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,28 @@ +2004-07-16 Matthias Clasen + + Add a combo box cell renderer (#139347, Lorenzo Gil Sanchez) + + * gtk/gtkcellrenderercombo.[hc]: New Files. + + * gtk/gtk.h: + * gtk/Makefile.am (gtk_public_h_sources, gtk_c_sources): + Add the new files. + + * gtk/gtkcombobox.[hc]: Implement GtkCellEditable, add a + :has-frame property for suppressing the frame around + the child and redo the size allocation logic to take + focus width into account. + + * gtk/gtkcomboboxentry.c (gtk_combo_box_entry_init): + Acknowledge the GtkComboBox:has-frame property and make + the entry fill its allocation vertically. + + * gtk/gtkmarshalers.list: Add BOOLEAN:STRING. + + * gtk/gtktreeview.c (gtk_tree_view_remove_widget): Queue + a draw here to keep non-widget windows from leaving + shadows behind. + 2004-07-16 Matthias Clasen * gtk/gtkcombobox.c (gtk_combo_box_forall): Don't iterate diff --git a/ChangeLog.pre-2-6 b/ChangeLog.pre-2-6 index b15ca79470..452243d8f2 100644 --- a/ChangeLog.pre-2-6 +++ b/ChangeLog.pre-2-6 @@ -1,3 +1,28 @@ +2004-07-16 Matthias Clasen + + Add a combo box cell renderer (#139347, Lorenzo Gil Sanchez) + + * gtk/gtkcellrenderercombo.[hc]: New Files. + + * gtk/gtk.h: + * gtk/Makefile.am (gtk_public_h_sources, gtk_c_sources): + Add the new files. + + * gtk/gtkcombobox.[hc]: Implement GtkCellEditable, add a + :has-frame property for suppressing the frame around + the child and redo the size allocation logic to take + focus width into account. + + * gtk/gtkcomboboxentry.c (gtk_combo_box_entry_init): + Acknowledge the GtkComboBox:has-frame property and make + the entry fill its allocation vertically. + + * gtk/gtkmarshalers.list: Add BOOLEAN:STRING. + + * gtk/gtktreeview.c (gtk_tree_view_remove_widget): Queue + a draw here to keep non-widget windows from leaving + shadows behind. + 2004-07-16 Matthias Clasen * gtk/gtkcombobox.c (gtk_combo_box_forall): Don't iterate diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8 index b15ca79470..452243d8f2 100644 --- a/ChangeLog.pre-2-8 +++ b/ChangeLog.pre-2-8 @@ -1,3 +1,28 @@ +2004-07-16 Matthias Clasen + + Add a combo box cell renderer (#139347, Lorenzo Gil Sanchez) + + * gtk/gtkcellrenderercombo.[hc]: New Files. + + * gtk/gtk.h: + * gtk/Makefile.am (gtk_public_h_sources, gtk_c_sources): + Add the new files. + + * gtk/gtkcombobox.[hc]: Implement GtkCellEditable, add a + :has-frame property for suppressing the frame around + the child and redo the size allocation logic to take + focus width into account. + + * gtk/gtkcomboboxentry.c (gtk_combo_box_entry_init): + Acknowledge the GtkComboBox:has-frame property and make + the entry fill its allocation vertically. + + * gtk/gtkmarshalers.list: Add BOOLEAN:STRING. + + * gtk/gtktreeview.c (gtk_tree_view_remove_widget): Queue + a draw here to keep non-widget windows from leaving + shadows behind. + 2004-07-16 Matthias Clasen * gtk/gtkcombobox.c (gtk_combo_box_forall): Don't iterate diff --git a/gtk/Makefile.am b/gtk/Makefile.am index b515866175..cd490b0b0a 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -118,6 +118,7 @@ gtk_public_h_sources = \ gtkcelleditable.h \ gtkcelllayout.h \ gtkcellrenderer.h \ + gtkcellrenderercombo.h \ gtkcellrendererpixbuf.h \ gtkcellrendererprogress.h \ gtkcellrenderertext.h \ @@ -324,6 +325,7 @@ gtk_c_sources = \ gtkcelleditable.c \ gtkcelllayout.c \ gtkcellrenderer.c \ + gtkcellrenderercombo.c \ gtkcellrendererpixbuf.c \ gtkcellrendererprogress.c \ gtkcellrendererseptext.c\ diff --git a/gtk/gtk.h b/gtk/gtk.h index f9494dba79..03e30a37b7 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkcellrenderercombo.c b/gtk/gtkcellrenderercombo.c new file mode 100644 index 0000000000..e03a4873cc --- /dev/null +++ b/gtk/gtkcellrenderercombo.c @@ -0,0 +1,390 @@ +/* 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 + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include + +#include "gtkintl.h" +#include "gtkbin.h" +#include "gtkentry.h" +#include "gtkcelllayout.h" +#include "gtkcellrenderercombo.h" +#include "gtkcellrenderertext.h" +#include "gtkcombobox.h" +#include "gtkcomboboxentry.h" + +static void gtk_cell_renderer_combo_class_init (GtkCellRendererComboClass *klass); +static void gtk_cell_renderer_combo_init (GtkCellRendererCombo *self); +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, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags); + +enum { + PROP_0, + PROP_MODEL, + PROP_TEXT_COLUMN, + PROP_HAS_ENTRY +}; + +static GObjectClass *parent_class = NULL; + +#define GTK_CELL_RENDERER_COMBO_PATH "gtk-cell-renderer-combo-path" + +GType +gtk_cell_renderer_combo_get_type (void) +{ + static GType gtk_cell_renderer_combo_type = 0; + + if (!gtk_cell_renderer_combo_type) + { + static const GTypeInfo gtk_cell_renderer_combo_info = + { + sizeof (GtkCellRendererComboClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gtk_cell_renderer_combo_class_init, + NULL, + NULL, + sizeof (GtkCellRendererCombo), + 0, + (GInstanceInitFunc) gtk_cell_renderer_combo_init + }; + gtk_cell_renderer_combo_type = + g_type_register_static (GTK_TYPE_CELL_RENDERER_TEXT, + "GtkCellRendererCombo", + >k_cell_renderer_combo_info, + 0); + } + return gtk_cell_renderer_combo_type; +} + +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); + + parent_class = g_type_class_peek_parent (klass); + + 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: + * + * The :model property holds a tree model containing the possible + * values for the combo box. Use the :text_column property to specify + * the column holding the values. + * + * 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, + G_PARAM_READWRITE)); + + /** + * GtkCellRendererCombo:text_column: + * + * The :text_column property specifies the model column which + * holds the possible values for the combo box. Note that this + * refers to the model specified in the :model property, + * not the model backing the tree view to + * which this cell renderer is attached. + * + * Since: 2.6 + */ + g_object_class_install_property (object_class, + PROP_TEXT_COLUMN, + g_param_spec_int ("text_column", + P_("Text Column"), + P_("A column in the data source model to get the strings from"), + -1, + G_MAXINT, + -1, + G_PARAM_READWRITE)); + + /** + * GtkCellRendererCombo:has_entry: + * + * If the :has_entry property is %TRUe, the cell renderer will + * include an entry and allow to enter values other than the ones + * in the popup list. + * + * Since: 2.6 + */ + g_object_class_install_property (object_class, + PROP_HAS_ENTRY, + g_param_spec_boolean ("has_entry", + P_("Has Entry"), + P_("If %FALSE, don't allow to enter strings other than the chosen ones"), + TRUE, + G_PARAM_READWRITE)); + +} + +static void +gtk_cell_renderer_combo_init (GtkCellRendererCombo *self) +{ + self->model = NULL; + self->text_column = -1; + self->focus_out_id = 0; +} + +/** + * gtk_cell_renderer_combo_new: + * + * Creates a new #GtkCellRendererCombo + * 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 + * in a #GtkTreeModel. For example, you can bind the "text" property + * 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); +} + +static void +gtk_cell_renderer_combo_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkCellRendererCombo *cell; + + g_return_if_fail (GTK_IS_CELL_RENDERER_COMBO (object)); + + cell = GTK_CELL_RENDERER_COMBO (object); + + switch (prop_id) + { + case PROP_MODEL: + g_value_set_object (value, cell->model); + break; + case PROP_TEXT_COLUMN: + g_value_set_int (value, cell->text_column); + break; + case PROP_HAS_ENTRY: + g_value_set_boolean (value, cell->has_entry); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtk_cell_renderer_combo_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkCellRendererCombo *cell; + + g_return_if_fail (GTK_IS_CELL_RENDERER_COMBO (object)); + + cell = GTK_CELL_RENDERER_COMBO (object); + + switch (prop_id) + { + case PROP_MODEL: + cell->model = g_value_get_object (value); + break; + case PROP_TEXT_COLUMN: + cell->text_column = g_value_get_int (value); + break; + case PROP_HAS_ENTRY: + cell->has_entry = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +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; + + cell = GTK_CELL_RENDERER_COMBO (data); + + if (cell->focus_out_id > 0) + { + g_signal_handler_disconnect (combo, cell->focus_out_id); + cell->focus_out_id = 0; + } + + if (_gtk_combo_box_editing_canceled (GTK_COMBO_BOX (combo))) + { + gtk_cell_renderer_editing_canceled (GTK_CELL_RENDERER (data)); + return; + } + + if (GTK_IS_COMBO_BOX_ENTRY (combo)) + { + 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)); + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) + gtk_tree_model_get (model, &iter, cell->text_column, &new_text, -1); + } + + path = g_object_get_data (G_OBJECT (combo), GTK_CELL_RENDERER_COMBO_PATH); + g_signal_emit_by_name (cell, "edited", path, new_text); + + 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) +{ + SearchData *search_data = (SearchData *)data; + gchar *text; + + gtk_tree_model_get (model, iter, search_data->cell->text_column, &text, -1); + if (text && GTK_CELL_RENDERER_TEXT (search_data->cell)->text && + strcmp (text, GTK_CELL_RENDERER_TEXT (search_data->cell)->text) == 0) + { + search_data->iter = *iter; + search_data->found = TRUE; + } + + return search_data->found; +} + +static GtkCellEditable * +gtk_cell_renderer_combo_start_editing (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + GtkCellRendererCombo *cell_combo; + GtkCellRendererText *cell_text; + GtkWidget *combo; + SearchData search_data; + + cell_text = GTK_CELL_RENDERER_TEXT (cell); + if (cell_text->editable == FALSE) + return NULL; + + cell_combo = GTK_CELL_RENDERER_COMBO (cell); + if (cell_combo->model == NULL || cell_combo->text_column < 0) + return NULL; + + if (cell_combo->has_entry) + { + combo = gtk_combo_box_entry_new_with_model (cell_combo->model, cell_combo->text_column); + + if (cell_text->text) + gtk_entry_set_text (GTK_ENTRY (GTK_BIN (combo)->child), + cell_text->text); + } + else + { + cell = gtk_cell_renderer_text_new (); + combo = gtk_combo_box_new_with_model (cell_combo->model); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), + cell, "text", cell_combo->text_column, + NULL); + + /* determine the current value */ + search_data.cell = cell_combo; + search_data.found = FALSE; + gtk_tree_model_foreach (cell_combo->model, find_text, &search_data); + if (search_data.found) + gtk_combo_box_set_active_iter (combo, &(search_data.iter)); + } + + g_object_set (combo, "has_frame", FALSE, NULL); + g_object_set_data_full (G_OBJECT (combo), + GTK_CELL_RENDERER_COMBO_PATH, + g_strdup (path), g_free); + + gtk_widget_show (combo); + + g_signal_connect (GTK_CELL_EDITABLE (combo), "editing_done", + G_CALLBACK (gtk_cell_renderer_combo_editing_done), + cell_combo); + cell_combo->focus_out_id = + g_signal_connect (combo, "focus_out_event", + G_CALLBACK (gtk_cell_renderer_combo_focus_out_event), + cell_combo); + + return GTK_CELL_EDITABLE (combo); +} diff --git a/gtk/gtkcellrenderercombo.h b/gtk/gtkcellrenderercombo.h new file mode 100644 index 0000000000..8168543962 --- /dev/null +++ b/gtk/gtkcellrenderercombo.h @@ -0,0 +1,61 @@ +/* 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 + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef GTK_CELL_RENDERER_COMBO_H +#define GTK_CELL_RENDERER_COMBO_H + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_CELL_RENDERER_COMBO (gtk_cell_renderer_combo_get_type ()) +#define GTK_CELL_RENDERER_COMBO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CELL_RENDERER_COMBO, GtkCellRendererCombo)) +#define GTK_CELL_RENDERER_COMBO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_CELL_RENDERER_COMBO, GtkCellRendererComboClass)) +#define GTK_IS_CELL_RENDERER_COMBO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_CELL_RENDERER_COMBO)) +#define GTK_IS_CELL_RENDERER_COMBO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_CELL_RENDERER_COMBO)) +#define GTK_CELL_RENDERER_COMBO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_CELL_RENDERER_COMBO, GtkCellRendererTextClass)) + +typedef struct _GtkCellRendererCombo GtkCellRendererCombo; +typedef struct _GtkCellRendererComboClass GtkCellRendererComboClass; + +struct _GtkCellRendererCombo +{ + GtkCellRendererText parent; + + GtkTreeModel *model; + gint text_column; + gboolean has_entry; + + /*< private >*/ + guint focus_out_id; +}; + +struct _GtkCellRendererComboClass +{ + GtkCellRendererTextClass parent; +}; + +GType gtk_cell_renderer_combo_get_type (void); +GtkCellRenderer *gtk_cell_renderer_combo_new (void); + +G_END_DECLS + +#endif diff --git a/gtk/gtkcombobox.c b/gtk/gtkcombobox.c index a00b54a0bc..8471f74f3d 100644 --- a/gtk/gtkcombobox.c +++ b/gtk/gtkcombobox.c @@ -99,6 +99,7 @@ struct _GtkComboBoxPrivate guint deleted_id; guint reordered_id; guint changed_id; + guint popup_idle_id; gint width; GSList *cells; @@ -106,6 +107,9 @@ struct _GtkComboBoxPrivate guint popup_in_progress : 1; guint destroying : 1; guint add_tearoffs : 1; + guint has_frame : 1; + guint is_cell_renderer : 1; + guint editing_canceled : 1; }; /* While debugging this evil code, I have learned that @@ -175,7 +179,8 @@ enum { PROP_COLUMN_SPAN_COLUMN, PROP_ROW_SEPARATOR_COLUMN, PROP_ACTIVE, - PROP_ADD_TEAROFFS + PROP_ADD_TEAROFFS, + PROP_HAS_FRAME }; static GtkBinClass *parent_class = NULL; @@ -187,6 +192,7 @@ static guint combo_box_signals[LAST_SIGNAL] = {0,}; /* common */ static void gtk_combo_box_class_init (GtkComboBoxClass *klass); static void gtk_combo_box_cell_layout_init (GtkCellLayoutIface *iface); +static void gtk_combo_box_cell_editable_init (GtkCellEditableIface *iface); static void gtk_combo_box_init (GtkComboBox *combo_box); static void gtk_combo_box_finalize (GObject *object); static void gtk_combo_box_destroy (GtkObject *object); @@ -261,6 +267,8 @@ static gboolean gtk_combo_box_key_press (GtkWidget *widget, GdkEventKey *event, gpointer data); +static void gtk_combo_box_check_appearance (GtkComboBox *combo_box); + /* listening to the model */ static void gtk_combo_box_model_row_inserted (GtkTreeModel *model, GtkTreePath *path, @@ -375,6 +383,11 @@ static gboolean gtk_combo_box_mnemonic_activate (GtkWidget *widg static void cell_view_sync_cells (GtkComboBox *combo_box, GtkCellView *cell_view); +/* GtkCellEditable method implementations */ +static void gtk_combo_box_start_editing (GtkCellEditable *cell_editable, + GdkEvent *event); + + GType gtk_combo_box_get_type (void) { @@ -402,6 +415,13 @@ gtk_combo_box_get_type (void) NULL }; + static const GInterfaceInfo cell_editable_info = + { + (GInterfaceInitFunc) gtk_combo_box_cell_editable_init, + NULL, + NULL + }; + combo_box_type = g_type_register_static (GTK_TYPE_BIN, "GtkComboBox", &combo_box_info, @@ -410,6 +430,13 @@ gtk_combo_box_get_type (void) g_type_add_interface_static (combo_box_type, GTK_TYPE_CELL_LAYOUT, &cell_layout_info); + + + g_type_add_interface_static (combo_box_type, + GTK_TYPE_CELL_EDITABLE, + &cell_editable_info); + + } return combo_box_type; @@ -534,14 +561,30 @@ gtk_combo_box_class_init (GtkComboBoxClass *klass) PROP_ADD_TEAROFFS, g_param_spec_boolean ("add-tearoffs", P_("Add tearoffs to menus"), - P_("Whether combobox dropdowns should have a tearoff menu item"), + P_("Whether dropdowns should have a tearoff menu item"), FALSE, G_PARAM_READWRITE)); + /** + * GtkComboBox:has-frame: + * + * The :has-frame property controls whether a frame + * is drawn around the entry. + * + * Since: 2.6 + */ + g_object_class_install_property (object_class, + PROP_HAS_FRAME, + g_param_spec_boolean ("has-frame", + P_("Has Frame"), + P_("Whether the combo box draws a frame around the child"), + TRUE, + G_PARAM_READWRITE)); + gtk_widget_class_install_style_property (widget_class, g_param_spec_boolean ("appears-as-list", P_("Appears as list"), - P_("Whether combobox dropdowns should look like lists rather than menus"), + P_("Whether dropdowns should look like lists rather than menus"), FALSE, G_PARAM_READABLE)); @@ -560,6 +603,12 @@ gtk_combo_box_cell_layout_init (GtkCellLayoutIface *iface) iface->reorder = gtk_combo_box_cell_layout_reorder; } +static void +gtk_combo_box_cell_editable_init (GtkCellEditableIface *iface) +{ + iface->start_editing = gtk_combo_box_start_editing; +} + static void gtk_combo_box_init (GtkComboBox *combo_box) { @@ -577,6 +626,11 @@ gtk_combo_box_init (GtkComboBox *combo_box) combo_box->priv->col_column = -1; combo_box->priv->row_column = -1; combo_box->priv->separator_column = -1; + + combo_box->priv->add_tearoffs = FALSE; + combo_box->priv->has_frame = TRUE; + combo_box->priv->is_cell_renderer = FALSE; + combo_box->priv->editing_canceled = FALSE; } static void @@ -617,6 +671,10 @@ gtk_combo_box_set_property (GObject *object, gtk_combo_box_set_add_tearoffs (combo_box, g_value_get_boolean (value)); break; + case PROP_HAS_FRAME: + combo_box->priv->has_frame = g_value_get_boolean (value); + break; + default: break; } @@ -660,6 +718,10 @@ gtk_combo_box_get_property (GObject *object, g_value_set_boolean (value, gtk_combo_box_get_add_tearoffs (combo_box)); break; + case PROP_HAS_FRAME: + g_value_set_boolean (value, combo_box->priv->has_frame); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -781,6 +843,7 @@ gtk_combo_box_add (GtkContainer *container, { gtk_widget_unparent (combo_box->priv->cell_view_frame); combo_box->priv->cell_view_frame = NULL; + combo_box->priv->box = NULL; } } } @@ -1138,7 +1201,7 @@ gtk_combo_box_list_position (GtkComboBox *combo_box, gdk_window_get_origin (sample->window, x, y); - if (combo_box->priv->cell_view_frame) + if (combo_box->priv->cell_view_frame && combo_box->priv->has_frame) { *x -= GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width + GTK_WIDGET (combo_box->priv->cell_view_frame)->style->xthickness; @@ -1501,6 +1564,7 @@ gtk_combo_box_size_request (GtkWidget *widget, GtkRequisition *requisition) { gint width, height; + gint focus_width, focus_pad; GtkRequisition bin_req; GtkComboBox *combo_box = GTK_COMBO_BOX (widget); @@ -1510,6 +1574,11 @@ gtk_combo_box_size_request (GtkWidget *widget, gtk_combo_box_remeasure (combo_box); bin_req.width = MAX (bin_req.width, combo_box->priv->width); + gtk_widget_style_get (GTK_WIDGET (widget), + "focus-line-width", &focus_width, + "focus-padding", &focus_pad, + NULL); + if (!combo_box->priv->tree_view) { /* menu mode */ @@ -1520,7 +1589,7 @@ gtk_combo_box_size_request (GtkWidget *widget, gint border_width, xthickness, ythickness; gtk_widget_size_request (combo_box->priv->button, &button_req); - border_width = GTK_CONTAINER (combo_box->priv->button)->border_width; + border_width = GTK_CONTAINER (combo_box)->border_width; xthickness = combo_box->priv->button->style->xthickness; ythickness = combo_box->priv->button->style->ythickness; @@ -1534,8 +1603,8 @@ gtk_combo_box_size_request (GtkWidget *widget, width = bin_req.width + sep_req.width + arrow_req.width; - height += border_width + 1 + ythickness * 2 + 4; - width += border_width + 1 + xthickness * 2 + 4; + height += 2*(border_width + ythickness + focus_width + focus_pad); + width += 2*(border_width + xthickness + focus_width + focus_pad); requisition->width = width; requisition->height = height; @@ -1561,12 +1630,15 @@ gtk_combo_box_size_request (GtkWidget *widget, if (combo_box->priv->cell_view_frame) { gtk_widget_size_request (combo_box->priv->cell_view_frame, &frame_req); - requisition->width += 2 * - (GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width + - GTK_WIDGET (combo_box->priv->cell_view_frame)->style->xthickness); - requisition->height += 2 * - (GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width + - GTK_WIDGET (combo_box->priv->cell_view_frame)->style->ythickness); + if (combo_box->priv->has_frame) + { + requisition->width += 2 * + (GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width + + GTK_WIDGET (combo_box->priv->cell_view_frame)->style->xthickness); + requisition->height += 2 * + (GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width + + GTK_WIDGET (combo_box->priv->cell_view_frame)->style->ythickness); + } } /* the button */ @@ -1582,12 +1654,18 @@ gtk_combo_box_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkComboBox *combo_box = GTK_COMBO_BOX (widget); + gint focus_width, focus_pad; GtkAllocation child; GtkRequisition req; gboolean is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL; widget->allocation = *allocation; + gtk_widget_style_get (GTK_WIDGET (widget), + "focus-line-width", &focus_width, + "focus-padding", &focus_pad, + NULL); + if (!combo_box->priv->tree_view) { if (combo_box->priv->cell_view) @@ -1603,15 +1681,23 @@ gtk_combo_box_size_allocate (GtkWidget *widget, xthickness = combo_box->priv->button->style->xthickness; ythickness = combo_box->priv->button->style->ythickness; - child.x = allocation->x + border_width + 1 + xthickness + 2; - child.y = allocation->y + border_width + 1 + ythickness + 2; + child.x = allocation->x; + child.y = allocation->y; + width = allocation->width; + child.height = allocation->height; + + if (!combo_box->priv->is_cell_renderer) + { + child.x += border_width + xthickness + focus_width + focus_pad; + child.y += border_width + ythickness + focus_width + focus_pad; + width -= 2 * (child.x - allocation->x); + child.height -= 2 * (child.y - allocation->y); + } - width = allocation->width - (border_width + 1 + xthickness * 2 + 4); /* handle the children */ gtk_widget_size_request (combo_box->priv->arrow, &req); child.width = req.width; - child.height = allocation->height - 2 * (child.y - allocation->y); if (!is_rtl) child.x += width - req.width; gtk_widget_size_allocate (combo_box->priv->arrow, &child); @@ -1627,12 +1713,14 @@ gtk_combo_box_size_allocate (GtkWidget *widget, { child.x += req.width; child.width = allocation->x + allocation->width - - (border_width + 1 + xthickness + 2) - child.x; + - (border_width + xthickness + focus_width + focus_pad) + - child.x; } else { child.width = child.x; - child.x = allocation->x + border_width + 1 + xthickness + 2; + child.x = allocation->x + + border_width + xthickness + focus_width + focus_pad; child.width -= child.x; } @@ -1688,20 +1776,23 @@ gtk_combo_box_size_allocate (GtkWidget *widget, gtk_widget_size_allocate (combo_box->priv->cell_view_frame, &child); /* the sample */ - child.x += - GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width + - GTK_WIDGET (combo_box->priv->cell_view_frame)->style->xthickness; - child.y += - GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width + - GTK_WIDGET (combo_box->priv->cell_view_frame)->style->ythickness; - child.width -= 2 * ( - GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width + - GTK_WIDGET (combo_box->priv->cell_view_frame)->style->xthickness); - child.height -= 2 * ( - GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width + - GTK_WIDGET (combo_box->priv->cell_view_frame)->style->ythickness); + if (combo_box->priv->has_frame) + { + child.x += + GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width + + GTK_WIDGET (combo_box->priv->cell_view_frame)->style->xthickness; + child.y += + GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width + + GTK_WIDGET (combo_box->priv->cell_view_frame)->style->ythickness; + child.width -= 2 * ( + GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width + + GTK_WIDGET (combo_box->priv->cell_view_frame)->style->xthickness); + child.height -= 2 * ( + GTK_CONTAINER (combo_box->priv->cell_view_frame)->border_width + + GTK_WIDGET (combo_box->priv->cell_view_frame)->style->ythickness); + } } - + gtk_widget_size_allocate (GTK_BIN (combo_box)->child, &child); } } @@ -1768,7 +1859,7 @@ gtk_combo_box_expose_event (GtkWidget *widget, if (!combo_box->priv->tree_view) { gtk_container_propagate_expose (GTK_CONTAINER (widget), - combo_box->priv->button, event); + combo_box->priv->button, event); } else { @@ -1869,7 +1960,8 @@ gtk_combo_box_menu_setup (GtkComboBox *combo_box, combo_box->priv->button = gtk_toggle_button_new (); g_signal_connect (combo_box->priv->button, "toggled", G_CALLBACK (gtk_combo_box_button_toggled), combo_box); - g_signal_connect_after (combo_box->priv->button, "key_press_event", + g_signal_connect_after (combo_box->priv->button, + "key_press_event", G_CALLBACK (gtk_combo_box_key_press), combo_box); gtk_widget_set_parent (combo_box->priv->button, GTK_BIN (combo_box)->child->parent); @@ -2226,6 +2318,8 @@ gtk_combo_box_menu_item_activate (GtkWidget *item, index = g_list_index (children, item); gtk_combo_box_set_active (combo_box, index); + + combo_box->priv->editing_canceled = FALSE; } static void @@ -2448,12 +2542,6 @@ gtk_combo_box_list_setup (GtkComboBox *combo_box) if (combo_box->priv->cell_view) { - combo_box->priv->cell_view_frame = gtk_frame_new (NULL); - gtk_widget_set_parent (combo_box->priv->cell_view_frame, - GTK_BIN (combo_box)->child->parent); - gtk_frame_set_shadow_type (GTK_FRAME (combo_box->priv->cell_view_frame), - GTK_SHADOW_IN); - gtk_cell_view_set_background_color (GTK_CELL_VIEW (combo_box->priv->cell_view), >K_WIDGET (combo_box)->style->base[GTK_WIDGET_STATE (combo_box)]); @@ -2461,9 +2549,23 @@ gtk_combo_box_list_setup (GtkComboBox *combo_box) gtk_event_box_set_visible_window (GTK_EVENT_BOX (combo_box->priv->box), FALSE); + if (combo_box->priv->has_frame) + { + combo_box->priv->cell_view_frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (combo_box->priv->cell_view_frame), + GTK_SHADOW_IN); + } + else + { + combo_box->priv->cell_view_frame = gtk_event_box_new (); + gtk_event_box_set_visible_window (GTK_EVENT_BOX (combo_box->priv->cell_view_frame), + FALSE); + } + + gtk_widget_set_parent (combo_box->priv->cell_view_frame, + GTK_BIN (combo_box)->child->parent); gtk_container_add (GTK_CONTAINER (combo_box->priv->cell_view_frame), combo_box->priv->box); - gtk_widget_show_all (combo_box->priv->cell_view_frame); g_signal_connect (combo_box->priv->box, "button_press_event", @@ -3879,10 +3981,19 @@ gtk_combo_box_destroy (GtkObject *object) { GtkComboBox *combo_box = GTK_COMBO_BOX (object); + if (combo_box->priv->popup_idle_id > 0) + { + g_source_remove (combo_box->priv->popup_idle_id); + combo_box->priv->popup_idle_id = 0; + } + gtk_combo_box_popdown (combo_box); + combo_box->priv->destroying = 1; + GTK_OBJECT_CLASS (parent_class)->destroy (object); combo_box->priv->cell_view = NULL; + combo_box->priv->destroying = 0; } @@ -3929,7 +4040,120 @@ gtk_combo_box_finalize (GObject *object) G_OBJECT_CLASS (parent_class)->finalize (object); } +static gboolean +gtk_cell_editable_key_press (GtkWidget *widget, + GdkEventKey *event, + gpointer data) +{ + GtkComboBox *combo_box = GTK_COMBO_BOX (data); + if (event->keyval == GDK_Escape) + { + combo_box->priv->editing_canceled = TRUE; + + gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (combo_box)); + gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (combo_box)); + + return TRUE; + } + else if (event->keyval == GDK_Return) + { + gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (combo_box)); + if (GTK_IS_CELL_EDITABLE (combo_box)) + gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (combo_box)); + + return TRUE; + } + else if (event->keyval == GDK_space) + { + /* ignore */ + return TRUE; + } + + return FALSE; +} + +static gboolean +popdown_idle (gpointer data) +{ + GtkComboBox *combo_box = GTK_COMBO_BOX (data); + + gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (combo_box)); + gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (combo_box)); + + g_object_unref (combo_box); + + return FALSE; +} + +static void +popdown_handler (GtkWidget *widget, + gpointer data) +{ + g_idle_add (popdown_idle, g_object_ref (data)); +} + +static gboolean +popup_idle (gpointer data) +{ + GtkComboBox *combo_box = GTK_COMBO_BOX (data); + + if (GTK_IS_MENU (combo_box->priv->popup_widget) && + combo_box->priv->cell_view) + g_signal_connect_object (combo_box->priv->popup_widget, + "unmap", G_CALLBACK (popdown_handler), + combo_box, 0); + + /* we unset this if a menu item is activated */ + combo_box->priv->editing_canceled = TRUE; + gtk_combo_box_popup (combo_box); + + return FALSE; +} + +static void +gtk_combo_box_start_editing (GtkCellEditable *cell_editable, + GdkEvent *event) +{ + GtkComboBox *combo_box = GTK_COMBO_BOX (cell_editable); + + combo_box->priv->is_cell_renderer = TRUE; + + if (combo_box->priv->cell_view) + { + g_signal_connect (combo_box->priv->button, "key_press_event", + G_CALLBACK (gtk_cell_editable_key_press), + cell_editable); + + gtk_widget_grab_focus (combo_box->priv->button); + } + else + { + g_signal_connect (GTK_BIN (combo_box)->child, "key_press_event", + G_CALLBACK (gtk_cell_editable_key_press), + cell_editable); + + gtk_widget_grab_focus (GTK_WIDGET (GTK_BIN (combo_box)->child)); + GTK_WIDGET_UNSET_FLAGS (combo_box->priv->button, GTK_CAN_FOCUS); + } + + /* we do the immediate popup only for the optionmenu-like + * appearance + */ + if (combo_box->priv->is_cell_renderer && + combo_box->priv->cell_view && !combo_box->priv->tree_view) + combo_box->priv->popup_idle_id = g_idle_add (popup_idle, combo_box); +} + + +/** + * gtk_combo_box_get_add_tearoffs: + * @combo_box: a #GtkComboBox + * + * Gets the current value of the :add-tearoffs property. + * + * Return value: the current value of the :add-tearoffs property. + **/ gboolean gtk_combo_box_get_add_tearoffs (GtkComboBox *combo_box) { @@ -3938,6 +4162,16 @@ gtk_combo_box_get_add_tearoffs (GtkComboBox *combo_box) return combo_box->priv->add_tearoffs; } +/** + * gtk_combo_box_set_add_tearoffs: + * @combo_box: a #GtkComboBox + * @add_tearoffs: %TRUE to add tearoff menu items + * + * Sets whether the popup menu should have a tearoff + * menu item. + * + * Since: 2.6 + **/ void gtk_combo_box_set_add_tearoffs (GtkComboBox *combo_box, gboolean add_tearoffs) @@ -3982,7 +4216,7 @@ gtk_combo_box_set_row_separator_column (GtkComboBox *combo_box, { combo_box->priv->separator_column = column; - gtk_widget_queue_draw (combo_box); + gtk_widget_queue_draw (GTK_WIDGET (combo_box)); g_object_notify (G_OBJECT (combo_box), "row_separator_column"); } @@ -4005,3 +4239,11 @@ gtk_combo_box_get_row_separator_column (GtkComboBox *combo_box) return combo_box->priv->separator_column; } + +gboolean +_gtk_combo_box_editing_canceled (GtkComboBox *combo_box) +{ + g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), TRUE); + + return combo_box->priv->editing_canceled; +} diff --git a/gtk/gtkcombobox.h b/gtk/gtkcombobox.h index 4d6b3e460f..71e82fbba9 100644 --- a/gtk/gtkcombobox.h +++ b/gtk/gtkcombobox.h @@ -115,6 +115,9 @@ gchar *gtk_combo_box_get_active_text (GtkComboBox *combo_box); void gtk_combo_box_popup (GtkComboBox *combo_box); void gtk_combo_box_popdown (GtkComboBox *combo_box); +/* private */ +gboolean _gtk_combo_box_editing_canceled (GtkComboBox *combo_box); + G_END_DECLS #endif /* __GTK_COMBO_BOX_H__ */ diff --git a/gtk/gtkcomboboxentry.c b/gtk/gtkcomboboxentry.c index 72f2e7d948..18927bdbde 100644 --- a/gtk/gtkcomboboxentry.c +++ b/gtk/gtkcomboboxentry.c @@ -54,6 +54,9 @@ static void gtk_combo_box_entry_contents_changed (GtkEntry *entry, gpointer user_data); static gboolean gtk_combo_box_entry_mnemonic_activate (GtkWidget *entry, gboolean group_cycling); +static void has_frame_changed (GtkComboBoxEntry *entry_box, + GParamSpec *pspec, + gpointer data); enum { @@ -125,6 +128,9 @@ gtk_combo_box_entry_init (GtkComboBoxEntry *entry_box) entry_box->priv->text_column = -1; entry_box->priv->entry = gtk_entry_new (); + /* this flag is a hack to tell the entry to fill its allocation. + */ + GTK_ENTRY (entry_box->priv->entry)->is_cell_renderer = TRUE; gtk_container_add (GTK_CONTAINER (entry_box), entry_box->priv->entry); gtk_widget_show (entry_box->priv->entry); @@ -139,6 +145,8 @@ gtk_combo_box_entry_init (GtkComboBoxEntry *entry_box) entry_box); g_signal_connect (entry_box, "changed", G_CALLBACK (gtk_combo_box_entry_active_changed), NULL); + has_frame_changed (entry_box, NULL, NULL); + g_signal_connect (entry_box, "notify::has-frame", G_CALLBACK (has_frame_changed), NULL); } static void @@ -217,6 +225,18 @@ gtk_combo_box_entry_active_changed (GtkComboBox *combo_box, combo_box); } +static void +has_frame_changed (GtkComboBoxEntry *entry_box, + GParamSpec *pspec, + gpointer data) +{ + gboolean has_frame; + + g_object_get (entry_box, "has_frame", &has_frame, NULL); + + gtk_entry_set_has_frame (GTK_ENTRY (entry_box->priv->entry), has_frame); +} + static void gtk_combo_box_entry_contents_changed (GtkEntry *entry, gpointer user_data) diff --git a/gtk/gtkmarshalers.list b/gtk/gtkmarshalers.list index fd65235793..eef9ecc66e 100644 --- a/gtk/gtkmarshalers.list +++ b/gtk/gtkmarshalers.list @@ -38,6 +38,7 @@ BOOLEAN:VOID BOOLEAN:BOOLEAN BOOLEAN:NONE BOOLEAN:BOOLEAN,BOOLEAN,BOOLEAN +BOOLEAN:STRING ENUM:ENUM INT:POINTER NONE:BOOLEAN diff --git a/gtk/gtktreeview.c b/gtk/gtktreeview.c index 212384f82c..89ba3a603e 100644 --- a/gtk/gtktreeview.c +++ b/gtk/gtktreeview.c @@ -6784,7 +6784,7 @@ gtk_tree_view_put (GtkTreeView *tree_view, gint height) { GtkTreeViewChild *child; - + g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); g_return_if_fail (GTK_IS_WIDGET (child_widget)); @@ -6800,7 +6800,7 @@ gtk_tree_view_put (GtkTreeView *tree_view, if (GTK_WIDGET_REALIZED (tree_view)) gtk_widget_set_parent_window (child->widget, tree_view->priv->bin_window); - + gtk_widget_set_parent (child_widget, GTK_WIDGET (tree_view)); } @@ -12355,7 +12355,9 @@ gtk_tree_view_remove_widget (GtkCellEditable *cell_editable, gtk_widget_grab_focus (GTK_WIDGET (tree_view)); gtk_container_remove (GTK_CONTAINER (tree_view), - GTK_WIDGET (cell_editable)); + GTK_WIDGET (cell_editable)); + /* FIXME should only redraw a single node */ + gtk_widget_queue_draw (GTK_WIDGET (tree_view)); } static gboolean