diff --git a/ChangeLog b/ChangeLog index 2e9da6e8a2..384e05adc3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2007-04-26 Matthias Clasen + + * demo/gtk-demo/combobox.c: Add a simple validation demo. + * gtk/gtkcomboboxentry.c: Make it possible to add arbitrary + children to a GtkComboBoxEntry. (#426401, Paul Pogonyshev) + 2007-04-26 Tor Lillqvist * gtk/gtkinputdialog.c (gtk_input_dialog_fill_axes) diff --git a/demos/gtk-demo/combobox.c b/demos/gtk-demo/combobox.c index 99d9681584..7b47032bfc 100644 --- a/demos/gtk-demo/combobox.c +++ b/demos/gtk-demo/combobox.c @@ -246,11 +246,87 @@ fill_combo_entry (GtkWidget *entry) gtk_combo_box_append_text (GTK_COMBO_BOX (entry), "Three"); } + +/* A simple validating entry */ + +#define TYPE_MASK_ENTRY (mask_entry_get_type ()) +#define MASK_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_MASK_ENTRY, MaskEntry)) +#define MASK_ENTRY_CLASS(vtable) (G_TYPE_CHECK_CLASS_CAST ((vtable), TYPE_MASK_ENTRY, MaskEntryClass)) +#define IS_MASK_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_MASK_ENTRY)) +#define IS_MASK_ENTRY_CLASS(vtable) (G_TYPE_CHECK_CLASS_TYPE ((vtable), TYPE_MASK_ENTRY)) +#define MASK_ENTRY_GET_CLASS(inst) (G_TYPE_INSTANCE_GET_CLASS ((inst), TYPE_MASK_ENTRY, MaskEntryClass)) + + +typedef struct _MaskEntry MaskEntry; +struct _MaskEntry +{ + GtkEntry entry; + gchar *mask; +}; + +typedef struct _MaskEntryClass MaskEntryClass; +struct _MaskEntryClass +{ + GtkEntryClass parent_class; +}; + + +static void mask_entry_editable_init (GtkEditableClass *iface); + +G_DEFINE_TYPE_WITH_CODE (MaskEntry, mask_entry, GTK_TYPE_ENTRY, + G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, + mask_entry_editable_init)); + + +static void +mask_entry_set_background (MaskEntry *entry) +{ + static const GdkColor error_color = { 0, 65535, 60000, 60000 }; + + if (entry->mask) + { + if (!g_regex_match_simple (entry->mask, gtk_entry_get_text (GTK_ENTRY (entry)), 0, 0)) + { + gtk_widget_modify_base (GTK_WIDGET (entry), GTK_STATE_NORMAL, &error_color); + return; + } + } + + gtk_widget_modify_base (GTK_WIDGET (entry), GTK_STATE_NORMAL, NULL); +} + + +static void +mask_entry_changed (GtkEditable *editable) +{ + mask_entry_set_background (MASK_ENTRY (editable)); +} + + +static void +mask_entry_init (MaskEntry *entry) +{ + entry->mask = NULL; +} + + +static void +mask_entry_class_init (MaskEntryClass *klass) +{ } + + +static void +mask_entry_editable_init (GtkEditableClass *iface) +{ + iface->changed = mask_entry_changed; +} + + GtkWidget * do_combobox (GtkWidget *do_widget) { static GtkWidget *window = NULL; - GtkWidget *vbox, *frame, *box, *combo; + GtkWidget *vbox, *frame, *box, *combo, *entry; GtkTreeModel *model; GtkCellRenderer *renderer; GtkTreePath *path; @@ -343,7 +419,7 @@ do_combobox (GtkWidget *do_widget) gtk_tree_path_free (path); gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter); - /* A GtkComboBoxEntry + /* A GtkComboBoxEntry with validation. */ frame = gtk_frame_new ("Editable"); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); @@ -356,6 +432,12 @@ do_combobox (GtkWidget *do_widget) fill_combo_entry (combo); gtk_container_add (GTK_CONTAINER (box), combo); + entry = g_object_new (TYPE_MASK_ENTRY, NULL); + MASK_ENTRY (entry)->mask = "^([0-9]*|One|Two|2\302\275|Three)$"; + + gtk_container_remove (GTK_CONTAINER (combo), GTK_BIN (combo)->child); + gtk_container_add (GTK_CONTAINER (combo), entry); + } if (!GTK_WIDGET_VISIBLE (window)) diff --git a/docs/reference/ChangeLog b/docs/reference/ChangeLog index 13d6107d4e..f4bbd1553d 100644 --- a/docs/reference/ChangeLog +++ b/docs/reference/ChangeLog @@ -1,3 +1,8 @@ +2007-04-26 Matthias Clasen + + * gtk/tmpl/gtkcomboboxentry.sgml: Mention that the entry + can be replaced. + 2007-04-25 Matthias Clasen * gtk/tmpl/gtkdialog.sgml: Fix a wrong cross reference. diff --git a/docs/reference/gtk/tmpl/gtkcomboboxentry.sgml b/docs/reference/gtk/tmpl/gtkcomboboxentry.sgml index 46bba4ff0b..a5d3646494 100644 --- a/docs/reference/gtk/tmpl/gtkcomboboxentry.sgml +++ b/docs/reference/gtk/tmpl/gtkcomboboxentry.sgml @@ -15,25 +15,34 @@ allow modifying it. In contrast to a #GtkComboBox, the underlying model of a #GtkComboBoxEntry must always have a text column (see gtk_combo_box_entry_set_text_column()), -and the entry will show the content of the text column in the selected row. To -get the text from the entry, use gtk_combo_box_get_active_text(). +and the entry will show the content of the text column in the selected row. +To get the text from the entry, use gtk_combo_box_get_active_text(). -The changed signal will be emitted while typing into a GtkComboBoxEntry, + +The changed signal will be emitted while typing into a GtkComboBoxEntry, as well as when selecting an item from the GtkComboBoxEntry's list. Use gtk_combo_box_get_active() or gtk_combo_box_get_active_iter() to discover whether an item was actually selected from the list. -Connect to the activate signal of the GtkEntry (use gtk_bin_get_child()) to -detect when the user actually finishes entering text. + +Connect to the activate signal of the GtkEntry (use gtk_bin_get_child()) +to detect when the user actually finishes entering text. + -The convenience API to construct simple text-only #GtkComboBoxes can -also be used with #GtkComboBoxEntrys which have been constructed +The convenience API to construct simple text-only #GtkComboBoxes +can also be used with #GtkComboBoxEntrys which have been constructed with gtk_combo_box_entry_new_text(). + +If you have special needs that go beyond a simple entry (e.g. input validation), +it is possible to replace the child entry by a different widget using +gtk_container_remove() and gtk_container_add(). + + #GtkComboBox diff --git a/gtk/gtkcomboboxentry.c b/gtk/gtkcomboboxentry.c index e4fc737dca..f8b698a849 100644 --- a/gtk/gtkcomboboxentry.c +++ b/gtk/gtkcomboboxentry.c @@ -32,8 +32,6 @@ struct _GtkComboBoxEntryPrivate { - GtkWidget *entry; - GtkCellRenderer *text_renderer; gint text_column; }; @@ -46,6 +44,10 @@ static void gtk_combo_box_entry_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); +static void gtk_combo_box_entry_add (GtkContainer *container, + GtkWidget *child); +static void gtk_combo_box_entry_remove (GtkContainer *container, + GtkWidget *child); static gchar *gtk_combo_box_entry_get_active_text (GtkComboBox *combo_box); static void gtk_combo_box_entry_active_changed (GtkComboBox *combo_box, @@ -72,6 +74,7 @@ gtk_combo_box_entry_class_init (GtkComboBoxEntryClass *klass) { GObjectClass *object_class; GtkWidgetClass *widget_class; + GtkContainerClass *container_class; GtkComboBoxClass *combo_class; object_class = (GObjectClass *)klass; @@ -82,6 +85,10 @@ gtk_combo_box_entry_class_init (GtkComboBoxEntryClass *klass) widget_class->mnemonic_activate = gtk_combo_box_entry_mnemonic_activate; widget_class->grab_focus = gtk_combo_box_entry_grab_focus; + container_class = (GtkContainerClass *)klass; + container_class->add = gtk_combo_box_entry_add; + container_class->remove = gtk_combo_box_entry_remove; + combo_class = (GtkComboBoxClass *)klass; combo_class->get_active_text = gtk_combo_box_entry_get_active_text; @@ -102,15 +109,14 @@ gtk_combo_box_entry_class_init (GtkComboBoxEntryClass *klass) static void gtk_combo_box_entry_init (GtkComboBoxEntry *entry_box) { + GtkWidget *entry; + entry_box->priv = GTK_COMBO_BOX_ENTRY_GET_PRIVATE (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); + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_container_add (GTK_CONTAINER (entry_box), entry); entry_box->priv->text_renderer = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (entry_box), @@ -118,12 +124,8 @@ gtk_combo_box_entry_init (GtkComboBoxEntry *entry_box) gtk_combo_box_set_active (GTK_COMBO_BOX (entry_box), -1); - g_signal_connect (entry_box->priv->entry, "changed", - G_CALLBACK (gtk_combo_box_entry_contents_changed), - 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); } @@ -167,6 +169,47 @@ gtk_combo_box_entry_get_property (GObject *object, } } +static void +gtk_combo_box_entry_add (GtkContainer *container, + GtkWidget *child) +{ + GtkComboBoxEntry *entry_box = GTK_COMBO_BOX_ENTRY (container); + + if (!GTK_IS_ENTRY (child)) + { + g_warning ("Attempting to add a widget with type %s to a GtkComboBoxEntry " + "(need an instance of GtkEntry or of a subclass)", + G_OBJECT_TYPE_NAME (child)); + return; + } + + GTK_CONTAINER_CLASS (gtk_combo_box_entry_parent_class)->add (container, child); + + /* this flag is a hack to tell the entry to fill its allocation. + */ + GTK_ENTRY (child)->is_cell_renderer = TRUE; + + g_signal_connect (child, "changed", + G_CALLBACK (gtk_combo_box_entry_contents_changed), + entry_box); + has_frame_changed (entry_box, NULL, NULL); +} + +static void +gtk_combo_box_entry_remove (GtkContainer *container, + GtkWidget *child) +{ + if (child && child == GTK_BIN (container)->child) + { + g_signal_handlers_disconnect_by_func (child, + gtk_combo_box_entry_contents_changed, + container); + GTK_ENTRY (child)->is_cell_renderer = FALSE; + } + + GTK_CONTAINER_CLASS (gtk_combo_box_entry_parent_class)->remove (container, child); +} + static void gtk_combo_box_entry_active_changed (GtkComboBox *combo_box, gpointer user_data) @@ -178,21 +221,26 @@ gtk_combo_box_entry_active_changed (GtkComboBox *combo_box, if (gtk_combo_box_get_active_iter (combo_box, &iter)) { - g_signal_handlers_block_by_func (entry_box->priv->entry, - gtk_combo_box_entry_contents_changed, - combo_box); + GtkEntry *entry = GTK_ENTRY (GTK_BIN (combo_box)->child); - model = gtk_combo_box_get_model (combo_box); + if (entry) + { + g_signal_handlers_block_by_func (entry, + gtk_combo_box_entry_contents_changed, + combo_box); - gtk_tree_model_get (model, &iter, - entry_box->priv->text_column, &str, - -1); - gtk_entry_set_text (GTK_ENTRY (entry_box->priv->entry), str); - g_free (str); + model = gtk_combo_box_get_model (combo_box); - g_signal_handlers_unblock_by_func (entry_box->priv->entry, - gtk_combo_box_entry_contents_changed, - combo_box); + gtk_tree_model_get (model, &iter, + entry_box->priv->text_column, &str, + -1); + gtk_entry_set_text (entry, str); + g_free (str); + + g_signal_handlers_unblock_by_func (entry, + gtk_combo_box_entry_contents_changed, + combo_box); + } } } @@ -201,11 +249,14 @@ has_frame_changed (GtkComboBoxEntry *entry_box, GParamSpec *pspec, gpointer data) { - gboolean has_frame; + if (GTK_BIN (entry_box)->child) + { + gboolean has_frame; - g_object_get (entry_box, "has-frame", &has_frame, NULL); + g_object_get (entry_box, "has-frame", &has_frame, NULL); - gtk_entry_set_has_frame (GTK_ENTRY (entry_box->priv->entry), has_frame); + gtk_entry_set_has_frame (GTK_ENTRY (GTK_BIN (entry_box)->child), has_frame); + } } static void @@ -323,9 +374,10 @@ static gboolean gtk_combo_box_entry_mnemonic_activate (GtkWidget *widget, gboolean group_cycling) { - GtkComboBoxEntry *entry_box = GTK_COMBO_BOX_ENTRY (widget); + GtkBin *entry_box = GTK_BIN (widget); - gtk_widget_grab_focus (entry_box->priv->entry); + if (entry_box->child) + gtk_widget_grab_focus (entry_box->child); return TRUE; } @@ -333,9 +385,10 @@ gtk_combo_box_entry_mnemonic_activate (GtkWidget *widget, static void gtk_combo_box_entry_grab_focus (GtkWidget *widget) { - GtkComboBoxEntry *entry_box = GTK_COMBO_BOX_ENTRY (widget); + GtkBin *entry_box = GTK_BIN (widget); - gtk_widget_grab_focus (entry_box->priv->entry); + if (entry_box->child) + gtk_widget_grab_focus (entry_box->child); } @@ -372,10 +425,10 @@ gtk_combo_box_entry_new_text (void) static gchar * gtk_combo_box_entry_get_active_text (GtkComboBox *combo_box) { - GtkComboBoxEntry *combo = GTK_COMBO_BOX_ENTRY (combo_box); + GtkBin *combo = GTK_BIN (combo_box); - if (combo->priv->entry) - return g_strdup (gtk_entry_get_text (GTK_ENTRY (combo->priv->entry))); + if (combo->child) + return g_strdup (gtk_entry_get_text (GTK_ENTRY (combo->child))); return NULL; }