diff options
author | Matthias Clasen <mclasen@redhat.com> | 2020-06-24 22:26:24 -0400 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2020-06-25 08:24:05 -0400 |
commit | 0b8092a2ba9558879ae1b5adb68a86399a6869ef (patch) | |
tree | 46f5e52c6628d4d2e06f57c47a2a0bea8f008020 | |
parent | ab3b1d9d2a9321d50c56eb3dc32fc8dbfd52cad4 (diff) | |
download | gtk+-port-entry-completion.tar.gz |
entrycompletion: port to list modelsport-entry-completion
Replace tree models and cell renderers with list models
and factories, in the entry completion api. The new apis
are
gtk_entry_completion_set_model
gtk_entry_completion_set_expression
gtk_entry_completion_set_factory
Port all internal uses of GtkEntryCompletion.
testentrycompletion has an example of using a custom model,
factory and expression.
-rw-r--r-- | demos/gtk-demo/entry_completion.c | 23 | ||||
-rw-r--r-- | demos/widget-factory/widget-factory.ui | 18 | ||||
-rw-r--r-- | docs/reference/gtk/gtk4-sections.txt | 12 | ||||
-rw-r--r-- | gtk/gtkentry.c | 6 | ||||
-rw-r--r-- | gtk/gtkentrycompletion.c | 1068 | ||||
-rw-r--r-- | gtk/gtkentrycompletion.h | 58 | ||||
-rw-r--r-- | gtk/gtkentryprivate.h | 51 | ||||
-rw-r--r-- | gtk/gtkplacesview.c | 14 | ||||
-rw-r--r-- | gtk/ui/gtkplacesview.ui | 7 | ||||
-rw-r--r-- | tests/testentrycompletion.c | 421 | ||||
-rw-r--r-- | tests/testgtk.c | 14 | ||||
-rw-r--r-- | testsuite/gtk/cellarea.c | 51 |
12 files changed, 581 insertions, 1162 deletions
diff --git a/demos/gtk-demo/entry_completion.c b/demos/gtk-demo/entry_completion.c index 40d64bd8b6..3d48ae6cd9 100644 --- a/demos/gtk-demo/entry_completion.c +++ b/demos/gtk-demo/entry_completion.c @@ -8,8 +8,8 @@ #include <glib/gi18n.h> #include <gtk/gtk.h> -/* Creates a tree model containing the completions */ -static GtkTreeModel * +/* Creates a list model containing the completions */ +static GListModel * create_completion_model (void) { const char *strings[] = { @@ -42,20 +42,8 @@ create_completion_model (void) "aæz", NULL }; - int i; - GtkListStore *store; - GtkTreeIter iter; - store = gtk_list_store_new (1, G_TYPE_STRING); - - for (i = 0; strings[i]; i++) - { - /* Append one word */ - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, strings[i], -1); - } - - return GTK_TREE_MODEL (store); + return G_LIST_MODEL (gtk_string_list_new (strings)); } @@ -67,7 +55,7 @@ do_entry_completion (GtkWidget *do_widget) GtkWidget *label; GtkWidget *entry; GtkEntryCompletion *completion; - GtkTreeModel *completion_model; + GListModel *completion_model; if (!window) { @@ -105,9 +93,6 @@ do_entry_completion (GtkWidget *do_widget) gtk_entry_completion_set_model (completion, completion_model); g_object_unref (completion_model); - /* Use model column 0 as the text column */ - gtk_entry_completion_set_text_column (completion, 0); - gtk_entry_completion_set_inline_completion (completion, TRUE); gtk_entry_completion_set_inline_selection (completion, TRUE); } diff --git a/demos/widget-factory/widget-factory.ui b/demos/widget-factory/widget-factory.ui index 5414ce82be..1699c5b18f 100644 --- a/demos/widget-factory/widget-factory.ui +++ b/demos/widget-factory/widget-factory.ui @@ -200,18 +200,18 @@ </row> </data> </object> + <object class="GtkStringList" id="name_list"> + <items> + <item>Andrea</item> + <item>Otto</item> + <item>Orville</item> + <item>Benjamin</item> + </items> + </object> <object class="GtkEntryCompletion" id="name_completion"> - <property name="model">liststore1</property> - <property name="text-column">2</property> + <property name="model">name_list</property> <property name="inline-completion">1</property> - <property name="popup-single-match">0</property> <property name="inline-selection">1</property> - <child> - <object class="GtkCellRendererText"/> - <attributes> - <attribute name="text">2</attribute> - </attributes> - </child> </object> <object class="GtkListStore" id="lrmodel"> <columns> diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 39e04bfb81..03285e5a65 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -1162,24 +1162,20 @@ gtk_entry_buffer_get_type <FILE>gtkentrycompletion</FILE> <TITLE>GtkEntryCompletion</TITLE> GtkEntryCompletion -GtkEntryCompletionMatchFunc gtk_entry_completion_new -gtk_entry_completion_new_with_area gtk_entry_completion_get_entry gtk_entry_completion_set_model gtk_entry_completion_get_model -gtk_entry_completion_set_match_func +gtk_entry_completion_set_expression +gtk_entry_completion_get_expression +gtk_entry_completion_set_factory +gtk_entry_completion_get_factory gtk_entry_completion_set_minimum_key_length gtk_entry_completion_get_minimum_key_length gtk_entry_completion_compute_prefix gtk_entry_completion_complete gtk_entry_completion_get_completion_prefix gtk_entry_completion_insert_prefix -gtk_entry_completion_insert_action_text -gtk_entry_completion_insert_action_markup -gtk_entry_completion_delete_action -gtk_entry_completion_set_text_column -gtk_entry_completion_get_text_column gtk_entry_completion_set_inline_completion gtk_entry_completion_get_inline_completion gtk_entry_completion_set_inline_selection diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c index 6ab195c7c6..c20cef4418 100644 --- a/gtk/gtkentry.c +++ b/gtk/gtkentry.c @@ -1752,7 +1752,7 @@ gtk_entry_size_allocate (GtkWidget *widget, completion = gtk_entry_get_completion (entry); if (completion) - _gtk_entry_completion_resize_popup (completion); + gtk_entry_completion_resize_popup (completion); } if (priv->emoji_chooser) @@ -3183,7 +3183,7 @@ gtk_entry_set_completion (GtkEntry *entry, if (old) { - _gtk_entry_completion_disconnect (old); + gtk_entry_completion_disconnect (old); g_object_unref (old); } @@ -3196,7 +3196,7 @@ gtk_entry_set_completion (GtkEntry *entry, /* hook into the entry */ g_object_ref (completion); - _gtk_entry_completion_connect (completion, entry); + gtk_entry_completion_connect (completion, entry); g_object_set_qdata (G_OBJECT (entry), quark_entry_completion, completion); diff --git a/gtk/gtkentrycompletion.c b/gtk/gtkentrycompletion.c index 09a43778b2..4eab9cdfe1 100644 --- a/gtk/gtkentrycompletion.c +++ b/gtk/gtkentrycompletion.c @@ -29,9 +29,8 @@ * in the entry, #GtkEntryCompletion checks which rows in the model match * the current content of the entry, and displays a list of matches. * By default, the matching is done by comparing the entry text - * case-insensitively against the text column of the model (see - * gtk_entry_completion_set_text_column()), but this can be overridden - * with a custom match function (see gtk_entry_completion_set_match_func()). + * case-insensitively against the text obtained from the model using + * an expression set with gtk_entry_completion_set_expression(). * * When the user selects a completion, the content of the entry is * updated. By default, the content of the entry is replaced by the @@ -41,19 +40,6 @@ * the signal handler to suppress the default behaviour. * * To add completion functionality to an entry, use gtk_entry_set_completion(). - * - * GtkEntryCompletion uses a #GtkTreeModelFilter model to represent the - * subset of the entire model that is currently matching. While the - * GtkEntryCompletion signals #GtkEntryCompletion::match-selected and - * #GtkEntryCompletion::cursor-on-match take the original model and an - * iter pointing to that model as arguments, other callbacks and signals - * (such as #GtkCellLayoutDataFuncs or #GtkCellArea::apply-attributes) - * will generally take the filter model as argument. As long as you are - * only calling gtk_tree_model_get(), this will make no difference to - * you. If for some reason, you need the original model, use - * gtk_tree_model_filter_get_model(). Don’t forget to use - * gtk_tree_model_filter_convert_iter_to_child_iter() to obtain a - * matching iter. */ #include "config.h" @@ -62,14 +48,8 @@ #include "gtkentryprivate.h" #include "gtktextprivate.h" -#include "gtkcelllayout.h" -#include "gtkcellareabox.h" #include "gtkintl.h" -#include "gtkcellrenderertext.h" -#include "gtkframe.h" -#include "gtktreeselection.h" -#include "gtktreeview.h" #include "gtkscrolledwindow.h" #include "gtksizerequest.h" #include "gtkbox.h" @@ -86,6 +66,16 @@ #include "gtkwidgetprivate.h" #include "gtknative.h" #include "gtkstylecontext.h" +#include "gtkstringfilter.h" +#include "gtkbuildable.h" +#include "gtkfilterlistmodel.h" +#include "gtklistview.h" +#include "gtkstringlist.h" +#include "gtksignallistitemfactory.h" +#include "gtklistitem.h" +#include "gtklabel.h" +#include "gtksingleselection.h" +#include "gtkselectionmodel.h" #include <string.h> @@ -107,21 +97,18 @@ enum { PROP_0, PROP_MODEL, + PROP_EXPRESSION, + PROP_FACTORY, PROP_MINIMUM_KEY_LENGTH, - PROP_TEXT_COLUMN, PROP_INLINE_COMPLETION, PROP_POPUP_COMPLETION, PROP_POPUP_SET_WIDTH, PROP_POPUP_SINGLE_MATCH, PROP_INLINE_SELECTION, - PROP_CELL_AREA, NUM_PROPERTIES }; -static void gtk_entry_completion_cell_layout_init (GtkCellLayoutIface *iface); -static GtkCellArea* gtk_entry_completion_get_area (GtkCellLayout *cell_layout); - static void gtk_entry_completion_constructed (GObject *object); static void gtk_entry_completion_set_property (GObject *object, guint prop_id, @@ -134,29 +121,17 @@ static void gtk_entry_completion_get_property (GObject *object, static void gtk_entry_completion_finalize (GObject *object); static void gtk_entry_completion_dispose (GObject *object); -static gboolean gtk_entry_completion_visible_func (GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data); -static void gtk_entry_completion_list_activated (GtkTreeView *treeview, - GtkTreePath *path, - GtkTreeViewColumn *column, - gpointer user_data); -static void gtk_entry_completion_selection_changed (GtkTreeSelection *selection, - gpointer data); - +static void gtk_entry_completion_list_activated (GtkListView *listview, + guint position, + gpointer user_data); static gboolean gtk_entry_completion_match_selected (GtkEntryCompletion *completion, - GtkTreeModel *model, - GtkTreeIter *iter); + guint position); static gboolean gtk_entry_completion_real_insert_prefix (GtkEntryCompletion *completion, - const gchar *prefix); + const char *prefix); static gboolean gtk_entry_completion_cursor_on_match (GtkEntryCompletion *completion, - GtkTreeModel *model, - GtkTreeIter *iter); + guint position); static gboolean gtk_entry_completion_insert_completion (GtkEntryCompletion *completion, - GtkTreeModel *model, - GtkTreeIter *iter); -static void gtk_entry_completion_insert_completion_text (GtkEntryCompletion *completion, - const gchar *text); + guint position); static void connect_completion_signals (GtkEntryCompletion *completion); static void disconnect_completion_signals (GtkEntryCompletion *completion); @@ -166,11 +141,12 @@ static GParamSpec *entry_completion_props[NUM_PROPERTIES] = { NULL, }; static guint entry_completion_signals[LAST_SIGNAL] = { 0 }; /* GtkBuildable */ -static void gtk_entry_completion_buildable_init (GtkBuildableIface *iface); +static void +gtk_entry_completion_buildable_init (GtkBuildableIface *iface) +{ +} G_DEFINE_TYPE_WITH_CODE (GtkEntryCompletion, gtk_entry_completion, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT, - gtk_entry_completion_cell_layout_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_entry_completion_buildable_init)) @@ -222,16 +198,11 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) /** * GtkEntryCompletion::match-selected: * @widget: the object which received the signal - * @model: the #GtkTreeModel containing the matches - * @iter: a #GtkTreeIter positioned at the selected match + * @selected: the position of the selected item * * Gets emitted when a match from the list is selected. * The default behaviour is to replace the contents of the - * entry with the contents of the text column in the row - * pointed to by @iter. - * - * Note that @model is the model that was passed to - * gtk_entry_completion_set_model(). + * entry with the contents of the selected item. * * Returns: %TRUE if the signal has been handled */ @@ -241,25 +212,20 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkEntryCompletionClass, match_selected), _gtk_boolean_handled_accumulator, NULL, - _gtk_marshal_BOOLEAN__OBJECT_BOXED, - G_TYPE_BOOLEAN, 2, - GTK_TYPE_TREE_MODEL, - GTK_TYPE_TREE_ITER); + _gtk_marshal_BOOLEAN__UINT, + G_TYPE_BOOLEAN, 1, + G_TYPE_UINT); /** * GtkEntryCompletion::cursor-on-match: * @widget: the object which received the signal - * @model: the #GtkTreeModel containing the matches - * @iter: a #GtkTreeIter positioned at the selected match + * @selected: the position of the selected item * * Gets emitted when a match from the cursor is on a match * of the list. The default behaviour is to replace the contents * of the entry with the contents of the text column in the row * pointed to by @iter. * - * Note that @model is the model that was passed to - * gtk_entry_completion_set_model(). - * * Returns: %TRUE if the signal has been handled */ entry_completion_signals[CURSOR_ON_MATCH] = @@ -268,10 +234,9 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkEntryCompletionClass, cursor_on_match), _gtk_boolean_handled_accumulator, NULL, - _gtk_marshal_BOOLEAN__OBJECT_BOXED, - G_TYPE_BOOLEAN, 2, - GTK_TYPE_TREE_MODEL, - GTK_TYPE_TREE_ITER); + _gtk_marshal_BOOLEAN__UINT, + G_TYPE_BOOLEAN, 1, + G_TYPE_UINT); /** * GtkEntryCompletion::no-matches: @@ -291,13 +256,43 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) NULL, G_TYPE_NONE, 0); + /** + * GtkEntryCompletion:model: + * + * Model for the displayed list items. + */ entry_completion_props[PROP_MODEL] = g_param_spec_object ("model", P_("Completion Model"), P_("The model to find matches in"), - GTK_TYPE_TREE_MODEL, + G_TYPE_LIST_MODEL, GTK_PARAM_READWRITE); + /** + * GtkEntryCompletion:factory: + * + * Factory for populating list items. + */ + entry_completion_props[PROP_FACTORY] = + g_param_spec_object ("factory", + P_("Factory"), + P_("The factory for populating list items"), + GTK_TYPE_LIST_ITEM_FACTORY, + GTK_PARAM_READWRITE); + + /** + * GtkEntryCompletion:expression: (type GtkExpression) + * + * An expression to evaluate to obtain strings to match against the text + * of the entry. If #GtkEntryCompletion:factory is not set, the expression + * is also used to bind strings to labels produced by a default factory. + */ + entry_completion_props[PROP_EXPRESSION] = + gtk_param_spec_expression ("expression", + P_("Expression to use"), + P_("Expression to evaluate to get strings"), + GTK_PARAM_READWRITE); + entry_completion_props[PROP_MINIMUM_KEY_LENGTH] = g_param_spec_int ("minimum-key-length", P_("Minimum Key Length"), @@ -306,25 +301,12 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); /** - * GtkEntryCompletion:text-column: - * - * The column of the model containing the strings. - * Note that the strings must be UTF-8. - */ - entry_completion_props[PROP_TEXT_COLUMN] = - g_param_spec_int ("text-column", - P_("Text column"), - P_("The column of the model containing the strings."), - -1, G_MAXINT, -1, - GTK_PARAM_READWRITE); - - /** * GtkEntryCompletion:inline-completion: * * Determines whether the common prefix of the possible completions * should be inserted automatically in the entry. Note that this - * requires text-column to be set, even if you are using a custom - * match function. + * requires #GtkEntryCompletion:expression to be set, even if you + * are using a custom match function. **/ entry_completion_props[PROP_INLINE_COMPLETION] = g_param_spec_boolean ("inline-completion", @@ -387,56 +369,58 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) FALSE, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - /** - * GtkEntryCompletion:cell-area: - * - * The #GtkCellArea used to layout cell renderers in the treeview column. - * - * If no area is specified when creating the entry completion with - * gtk_entry_completion_new_with_area() a horizontally oriented - * #GtkCellAreaBox will be used. - */ - entry_completion_props[PROP_CELL_AREA] = - g_param_spec_object ("cell-area", - P_("Cell Area"), - P_("The GtkCellArea used to layout cells"), - GTK_TYPE_CELL_AREA, - GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); - g_object_class_install_properties (object_class, NUM_PROPERTIES, entry_completion_props); } - static void -gtk_entry_completion_buildable_custom_tag_end (GtkBuildable *buildable, - GtkBuilder *builder, - GObject *child, - const gchar *tagname, - gpointer data) +setup_item (GtkSignalListItemFactory *factory, + GtkListItem *item) { - /* Just ignore the boolean return from here */ - _gtk_cell_layout_buildable_custom_tag_end (buildable, builder, child, tagname, data); -} + GtkWidget *label; -static void -gtk_entry_completion_buildable_init (GtkBuildableIface *iface) -{ - iface->add_child = _gtk_cell_layout_buildable_add_child; - iface->custom_tag_start = _gtk_cell_layout_buildable_custom_tag_start; - iface->custom_tag_end = gtk_entry_completion_buildable_custom_tag_end; + label = gtk_label_new (""); + gtk_label_set_xalign (GTK_LABEL (label), 0); + gtk_list_item_set_child (item, label); } static void -gtk_entry_completion_cell_layout_init (GtkCellLayoutIface *iface) +bind_item (GtkSignalListItemFactory *factory, + GtkListItem *item, + GtkEntryCompletion *completion) { - iface->get_area = gtk_entry_completion_get_area; + GtkWidget *label; + gpointer obj; + GValue value = G_VALUE_INIT; + + label = gtk_list_item_get_child (item); + obj = gtk_list_item_get_item (item); + + if (completion->expression && + gtk_expression_evaluate (completion->expression, obj, &value)) + { + gtk_label_set_label (GTK_LABEL (label), g_value_get_string (&value)); + g_value_unset (&value); + } + else if (GTK_IS_STRING_OBJECT (obj)) + { + const char *string; + + string = gtk_string_object_get_string (GTK_STRING_OBJECT (obj)); + gtk_label_set_label (GTK_LABEL (label), string); + } + else + { + g_critical ("Either GtkEntryCompletion:factory or GtkEntryCompletion:expression must be set"); + } } static void gtk_entry_completion_init (GtkEntryCompletion *completion) { + GListModel *model; + GtkFilter *filter; + completion->minimum_key_length = 1; - completion->text_column = -1; completion->has_completion = FALSE; completion->inline_completion = FALSE; completion->popup_completion = TRUE; @@ -444,7 +428,21 @@ gtk_entry_completion_init (GtkEntryCompletion *completion) completion->popup_single_match = TRUE; completion->inline_selection = FALSE; - completion->filter_model = NULL; + completion->expression = gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string"); + + filter = gtk_string_filter_new (); + gtk_string_filter_set_expression (GTK_STRING_FILTER (filter), completion->expression); + gtk_string_filter_set_match_mode (GTK_STRING_FILTER (filter), GTK_STRING_FILTER_MATCH_MODE_PREFIX); + gtk_string_filter_set_ignore_case (GTK_STRING_FILTER (filter), TRUE); + + model = G_LIST_MODEL (gtk_string_list_new ((const char *[]){ NULL })); + completion->filter_model = G_LIST_MODEL (gtk_filter_list_model_new (model, filter)); + g_object_unref (model); + g_object_unref (filter); + + completion->factory = gtk_signal_list_item_factory_new (); + g_signal_connect (completion->factory, "setup", G_CALLBACK (setup_item), NULL); + g_signal_connect (completion->factory, "bind", G_CALLBACK (bind_item), completion); } static gboolean @@ -462,49 +460,32 @@ propagate_to_entry (GtkEventControllerKey *key, static void gtk_entry_completion_constructed (GObject *object) { - GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object); - GtkTreeSelection *sel; - GtkWidget *popup_frame; - GtkEventController *controller; + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object); + GtkEventController *controller; + GListModel *selection; G_OBJECT_CLASS (gtk_entry_completion_parent_class)->constructed (object); - if (!completion->cell_area) - { - completion->cell_area = gtk_cell_area_box_new (); - g_object_ref_sink (completion->cell_area); - } + completion->list_view = gtk_list_view_new (); + gtk_list_view_set_single_click_activate (GTK_LIST_VIEW (completion->list_view), TRUE); + selection = G_LIST_MODEL (gtk_single_selection_new (completion->filter_model)); + gtk_single_selection_set_autoselect (GTK_SINGLE_SELECTION (selection), FALSE); + gtk_single_selection_set_can_unselect (GTK_SINGLE_SELECTION (selection), TRUE); + gtk_list_view_set_model (GTK_LIST_VIEW (completion->list_view), selection); + g_object_unref (selection); - /* completions */ - completion->tree_view = gtk_tree_view_new (); - g_signal_connect (completion->tree_view, "row-activated", - G_CALLBACK (gtk_entry_completion_list_activated), - completion); + gtk_list_view_set_factory (GTK_LIST_VIEW (completion->list_view), completion->factory); - gtk_tree_view_set_enable_search (GTK_TREE_VIEW (completion->tree_view), FALSE); - gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (completion->tree_view), FALSE); - gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (completion->tree_view), TRUE); - gtk_tree_view_set_activate_on_single_click (GTK_TREE_VIEW (completion->tree_view), TRUE); - - sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->tree_view)); - gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE); - gtk_tree_selection_unselect_all (sel); - g_signal_connect (sel, "changed", - G_CALLBACK (gtk_entry_completion_selection_changed), + g_signal_connect (completion->list_view, "activate", + G_CALLBACK (gtk_entry_completion_list_activated), completion); - completion->first_sel_changed = TRUE; - - completion->column = gtk_tree_view_column_new_with_area (completion->cell_area); - gtk_tree_view_append_column (GTK_TREE_VIEW (completion->tree_view), completion->column); completion->scrolled_window = gtk_scrolled_window_new (); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (completion->scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); - - /* a nasty hack to get the completions treeview to size nicely */ - gtk_widget_set_size_request (gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (completion->scrolled_window)), - -1, 0); + gtk_scrolled_window_set_max_content_height (GTK_SCROLLED_WINDOW (completion->scrolled_window), 400); + gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (completion->scrolled_window), TRUE); /* pack it all */ completion->popup_window = gtk_popover_new (); @@ -522,21 +503,18 @@ gtk_entry_completion_constructed (GObject *object) controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); g_signal_connect_swapped (controller, "released", - G_CALLBACK (_gtk_entry_completion_popdown), + G_CALLBACK (gtk_entry_completion_popdown), completion); gtk_widget_add_controller (completion->popup_window, controller); - popup_frame = gtk_frame_new (NULL); - gtk_popover_set_child (GTK_POPOVER (completion->popup_window), popup_frame); - gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (completion->scrolled_window), - completion->tree_view); + completion->list_view); gtk_widget_set_hexpand (completion->scrolled_window, TRUE); gtk_widget_set_vexpand (completion->scrolled_window, TRUE); - gtk_frame_set_child (GTK_FRAME (popup_frame), completion->scrolled_window); + gtk_popover_set_child (GTK_POPOVER (completion->popup_window), + completion->scrolled_window); } - static void gtk_entry_completion_set_property (GObject *object, guint prop_id, @@ -544,7 +522,6 @@ gtk_entry_completion_set_property (GObject *object, GParamSpec *pspec) { GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object); - GtkCellArea *area; switch (prop_id) { @@ -553,54 +530,44 @@ gtk_entry_completion_set_property (GObject *object, g_value_get_object (value)); break; + case PROP_FACTORY: + gtk_entry_completion_set_factory (completion, + g_value_get_object (value)); + break; + + case PROP_EXPRESSION: + gtk_entry_completion_set_expression (completion, + gtk_value_get_expression (value)); + break; + case PROP_MINIMUM_KEY_LENGTH: gtk_entry_completion_set_minimum_key_length (completion, g_value_get_int (value)); break; - case PROP_TEXT_COLUMN: - completion->text_column = g_value_get_int (value); - break; - case PROP_INLINE_COMPLETION: - gtk_entry_completion_set_inline_completion (completion, - g_value_get_boolean (value)); + gtk_entry_completion_set_inline_completion (completion, + g_value_get_boolean (value)); break; case PROP_POPUP_COMPLETION: - gtk_entry_completion_set_popup_completion (completion, - g_value_get_boolean (value)); + gtk_entry_completion_set_popup_completion (completion, + g_value_get_boolean (value)); break; case PROP_POPUP_SET_WIDTH: - gtk_entry_completion_set_popup_set_width (completion, - g_value_get_boolean (value)); + gtk_entry_completion_set_popup_set_width (completion, + g_value_get_boolean (value)); break; case PROP_POPUP_SINGLE_MATCH: - gtk_entry_completion_set_popup_single_match (completion, - g_value_get_boolean (value)); + gtk_entry_completion_set_popup_single_match (completion, + g_value_get_boolean (value)); break; case PROP_INLINE_SELECTION: - gtk_entry_completion_set_inline_selection (completion, - g_value_get_boolean (value)); - break; - - case PROP_CELL_AREA: - /* Construct-only, can only be assigned once */ - area = g_value_get_object (value); - if (area) - { - if (completion->cell_area != NULL) - { - g_warning ("cell-area has already been set, ignoring construct property"); - g_object_ref_sink (area); - g_object_unref (area); - } - else - completion->cell_area = g_object_ref_sink (area); - } + gtk_entry_completion_set_inline_selection (completion, + g_value_get_boolean (value)); break; default: @@ -620,16 +587,19 @@ gtk_entry_completion_get_property (GObject *object, switch (prop_id) { case PROP_MODEL: - g_value_set_object (value, - gtk_entry_completion_get_model (completion)); + g_value_set_object (value, gtk_entry_completion_get_model (completion)); break; - case PROP_MINIMUM_KEY_LENGTH: - g_value_set_int (value, gtk_entry_completion_get_minimum_key_length (completion)); + case PROP_FACTORY: + g_value_set_object (value, gtk_entry_completion_get_factory (completion)); break; - case PROP_TEXT_COLUMN: - g_value_set_int (value, gtk_entry_completion_get_text_column (completion)); + case PROP_EXPRESSION: + gtk_value_set_expression (value, gtk_entry_completion_get_expression (completion)); + break; + + case PROP_MINIMUM_KEY_LENGTH: + g_value_set_int (value, gtk_entry_completion_get_minimum_key_length (completion)); break; case PROP_INLINE_COMPLETION: @@ -652,10 +622,6 @@ gtk_entry_completion_get_property (GObject *object, g_value_set_boolean (value, gtk_entry_completion_get_inline_selection (completion)); break; - case PROP_CELL_AREA: - g_value_set_object (value, completion->cell_area); - break; - default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -670,9 +636,6 @@ gtk_entry_completion_finalize (GObject *object) g_free (completion->case_normalized_key); g_free (completion->completion_prefix); - if (completion->match_notify) - (* completion->match_notify) (completion->match_data); - G_OBJECT_CLASS (gtk_entry_completion_parent_class)->finalize (object); } @@ -684,135 +647,30 @@ gtk_entry_completion_dispose (GObject *object) if (completion->entry) gtk_entry_set_completion (GTK_ENTRY (completion->entry), NULL); - g_clear_object (&completion->cell_area); + g_clear_object (&completion->filter_model); + g_clear_object (&completion->expression); + g_clear_object (&completion->factory); G_OBJECT_CLASS (gtk_entry_completion_parent_class)->dispose (object); } -/* implement cell layout interface (only need to return the underlying cell area) */ -static GtkCellArea* -gtk_entry_completion_get_area (GtkCellLayout *cell_layout) -{ - GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (cell_layout); - - if (G_UNLIKELY (!completion->cell_area)) - { - completion->cell_area = gtk_cell_area_box_new (); - g_object_ref_sink (completion->cell_area); - } - - return completion->cell_area; -} - /* all those callbacks */ -static gboolean -gtk_entry_completion_default_completion_func (GtkEntryCompletion *completion, - const gchar *key, - GtkTreeIter *iter, - gpointer user_data) -{ - gchar *item = NULL; - gchar *normalized_string; - gchar *case_normalized_string; - - gboolean ret = FALSE; - - GtkTreeModel *model; - - model = gtk_tree_model_filter_get_model (completion->filter_model); - - g_return_val_if_fail (gtk_tree_model_get_column_type (model, completion->text_column) == G_TYPE_STRING, - FALSE); - - gtk_tree_model_get (model, iter, - completion->text_column, &item, - -1); - - if (item != NULL) - { - normalized_string = g_utf8_normalize (item, -1, G_NORMALIZE_ALL); - - if (normalized_string != NULL) - { - case_normalized_string = g_utf8_casefold (normalized_string, -1); - - if (!strncmp (key, case_normalized_string, strlen (key))) - ret = TRUE; - - g_free (case_normalized_string); - } - g_free (normalized_string); - } - g_free (item); - - return ret; -} - -static gboolean -gtk_entry_completion_visible_func (GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - gboolean ret = FALSE; - - GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (data); - - if (!completion->case_normalized_key) - return ret; - - if (completion->match_func) - ret = (* completion->match_func) (completion, - completion->case_normalized_key, - iter, - completion->match_data); - else if (completion->text_column >= 0) - ret = gtk_entry_completion_default_completion_func (completion, - completion->case_normalized_key, - iter, - NULL); - - return ret; -} static void -gtk_entry_completion_list_activated (GtkTreeView *treeview, - GtkTreePath *path, - GtkTreeViewColumn *column, - gpointer user_data) +gtk_entry_completion_list_activated (GtkListView *listview, + guint position, + gpointer user_data) { GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); - GtkTreeIter iter; gboolean entry_set; - GtkTreeModel *model; - GtkTreeIter child_iter; GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (completion->entry)); - gtk_tree_model_get_iter (GTK_TREE_MODEL (completion->filter_model), &iter, path); - gtk_tree_model_filter_convert_iter_to_child_iter (completion->filter_model, - &child_iter, - &iter); - model = gtk_tree_model_filter_get_model (completion->filter_model); - g_signal_handler_block (text, completion->changed_id); g_signal_emit (completion, entry_completion_signals[MATCH_SELECTED], - 0, model, &child_iter, &entry_set); + 0, position, &entry_set); g_signal_handler_unblock (text, completion->changed_id); - _gtk_entry_completion_popdown (completion); -} - -static void -gtk_entry_completion_selection_changed (GtkTreeSelection *selection, - gpointer data) -{ - GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (data); - - if (completion->first_sel_changed) - { - completion->first_sel_changed = FALSE; - if (gtk_widget_is_focus (completion->tree_view)) - gtk_tree_selection_unselect_all (selection); - } + gtk_entry_completion_popdown (completion); } /* public API */ @@ -835,26 +693,6 @@ gtk_entry_completion_new (void) } /** - * gtk_entry_completion_new_with_area: - * @area: the #GtkCellArea used to layout cells - * - * Creates a new #GtkEntryCompletion object using the - * specified @area to layout cells in the underlying - * #GtkTreeViewColumn for the drop-down menu. - * - * Returns: A newly created #GtkEntryCompletion object - */ -GtkEntryCompletion * -gtk_entry_completion_new_with_area (GtkCellArea *area) -{ - GtkEntryCompletion *completion; - - completion = g_object_new (GTK_TYPE_ENTRY_COMPLETION, "cell-area", area, NULL); - - return completion; -} - -/** * gtk_entry_completion_get_entry: * @completion: a #GtkEntryCompletion * @@ -873,7 +711,7 @@ gtk_entry_completion_get_entry (GtkEntryCompletion *completion) /** * gtk_entry_completion_set_model: * @completion: a #GtkEntryCompletion - * @model: (allow-none): the #GtkTreeModel + * @model: (allow-none): the #GListModel * * Sets the model for a #GtkEntryCompletion. If @completion already has * a model set, it will remove it before setting the new model. @@ -881,36 +719,23 @@ gtk_entry_completion_get_entry (GtkEntryCompletion *completion) */ void gtk_entry_completion_set_model (GtkEntryCompletion *completion, - GtkTreeModel *model) + GListModel *model) { g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); - g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model)); + g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model)); + + gtk_filter_list_model_set_model (GTK_FILTER_LIST_MODEL (completion->filter_model), model); if (!model) { - gtk_tree_view_set_model (GTK_TREE_VIEW (completion->tree_view), - NULL); - _gtk_entry_completion_popdown (completion); - completion->filter_model = NULL; + gtk_entry_completion_popdown (completion); return; } - /* code will unref the old filter model (if any) */ - completion->filter_model = - GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (model, NULL)); - gtk_tree_model_filter_set_visible_func (completion->filter_model, - gtk_entry_completion_visible_func, - completion, - NULL); - - gtk_tree_view_set_model (GTK_TREE_VIEW (completion->tree_view), - GTK_TREE_MODEL (completion->filter_model)); - g_object_unref (completion->filter_model); - g_object_notify_by_pspec (G_OBJECT (completion), entry_completion_props[PROP_MODEL]); if (gtk_widget_get_visible (completion->popup_window)) - _gtk_entry_completion_resize_popup (completion); + gtk_entry_completion_resize_popup (completion); } /** @@ -920,45 +745,103 @@ gtk_entry_completion_set_model (GtkEntryCompletion *completion, * Returns the model the #GtkEntryCompletion is using as data source. * Returns %NULL if the model is unset. * - * Returns: (nullable) (transfer none): A #GtkTreeModel, or %NULL if none + * Returns: (nullable) (transfer none): A #GListModel, or %NULL if none * is currently being used */ -GtkTreeModel * +GListModel * gtk_entry_completion_get_model (GtkEntryCompletion *completion) { g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), NULL); - if (!completion->filter_model) - return NULL; + return gtk_filter_list_model_get_model (GTK_FILTER_LIST_MODEL (completion->filter_model)); +} + +/** + * gtk_entry_completion_set_expression: + * @completion: a #GtkEntryCompletion + * @expression: (nullable): a #GtkExpression, or %NULL + * + * Sets the expression that gets evaluated to obtain strings from items + * when finding completions. The expression must have a value type of + * #GTK_TYPE_STRING. + */ +void +gtk_entry_completion_set_expression (GtkEntryCompletion *completion, + GtkExpression *expression) +{ + GtkFilter *filter; + + g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); + g_return_if_fail (expression == NULL || GTK_IS_EXPRESSION (expression)); + + if (completion->expression) + gtk_expression_unref (completion->expression); + + completion->expression = expression; + + if (completion->expression) + gtk_expression_ref (completion->expression); + + filter = gtk_filter_list_model_get_filter (GTK_FILTER_LIST_MODEL (completion->filter_model)); + gtk_string_filter_set_expression (GTK_STRING_FILTER (filter), completion->expression); - return gtk_tree_model_filter_get_model (completion->filter_model); + g_object_notify_by_pspec (G_OBJECT (completion), entry_completion_props[PROP_EXPRESSION]); } /** - * gtk_entry_completion_set_match_func: + * gtk_entry_completion_get_expression: * @completion: a #GtkEntryCompletion - * @func: the #GtkEntryCompletionMatchFunc to use - * @func_data: user data for @func - * @func_notify: destroy notify for @func_data. * - * Sets the match function for @completion to be @func. The match function - * is used to determine if a row should or should not be in the completion - * list. + * Gets the expression set with gtk_entry_completion_set_expression(). + * + * Returns: (nullable) (transfer none): a #GtkExpression or %NULL */ +GtkExpression * +gtk_entry_completion_get_expression (GtkEntryCompletion *completion) +{ + g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), NULL); + + return completion->expression; +} + +/** + * gtk_entry_completion_set_factory: + * @completion: a #GtkEntryCompletion + * @factory: (allow-none) (transfer none): the factory to use or %NULL for none + * + * Sets the #GtkListItemFactory to use for populating list items. + **/ void -gtk_entry_completion_set_match_func (GtkEntryCompletion *completion, - GtkEntryCompletionMatchFunc func, - gpointer func_data, - GDestroyNotify func_notify) +gtk_entry_completion_set_factory (GtkEntryCompletion *completion, + GtkListItemFactory *factory) { g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); + g_return_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory)); + + if (!g_set_object (&completion->factory, factory)) + return; - if (completion->match_notify) - (* completion->match_notify) (completion->match_data); + if (completion->list_view) + gtk_list_view_set_factory (GTK_LIST_VIEW (completion->list_view), factory); - completion->match_func = func; - completion->match_data = func_data; - completion->match_notify = func_notify; + g_object_notify_by_pspec (G_OBJECT (completion), entry_completion_props[PROP_FACTORY]); +} + +/** + * gtk_entry_completion_set_factory: + * @completion: a #GtkEntryCompletion + * @self: a #GtkDropDown + * + * Gets the factory that's currently used to populate list items in the popup. + * + * Returns: (nullable) (transfer none): The factory in use + **/ +GtkListItemFactory * +gtk_entry_completion_get_factory (GtkEntryCompletion *completion) +{ + g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), NULL); + + return completion->factory; } /** @@ -1014,148 +897,56 @@ gtk_entry_completion_get_minimum_key_length (GtkEntryCompletion *completion) void gtk_entry_completion_complete (GtkEntryCompletion *completion) { - gchar *tmp; - GtkTreeIter iter; + GtkFilter *filter; + const char *text; g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); g_return_if_fail (GTK_IS_ENTRY (completion->entry)); - if (!completion->filter_model) + filter = gtk_filter_list_model_get_filter (GTK_FILTER_LIST_MODEL (completion->filter_model)); + if (!filter) return; - g_free (completion->case_normalized_key); - - tmp = g_utf8_normalize (gtk_editable_get_text (GTK_EDITABLE (completion->entry)), - -1, G_NORMALIZE_ALL); - completion->case_normalized_key = g_utf8_casefold (tmp, -1); - g_free (tmp); + text = gtk_editable_get_text (GTK_EDITABLE (completion->entry)); + gtk_string_filter_set_search (GTK_STRING_FILTER (filter), text); - gtk_tree_model_filter_refilter (completion->filter_model); - - if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (completion->filter_model), &iter)) + if (g_list_model_get_n_items (completion->filter_model) == 0) g_signal_emit (completion, entry_completion_signals[NO_MATCHES], 0); if (gtk_widget_get_visible (completion->popup_window)) - _gtk_entry_completion_resize_popup (completion); -} - -/** - * gtk_entry_completion_set_text_column: - * @completion: a #GtkEntryCompletion - * @column: the column in the model of @completion to get strings from - * - * Convenience function for setting up the most used case of this code: a - * completion list with just strings. This function will set up @completion - * to have a list displaying all (and just) strings in the completion list, - * and to get those strings from @column in the model of @completion. - * - * This functions creates and adds a #GtkCellRendererText for the selected - * column. If you need to set the text column, but don't want the cell - * renderer, use g_object_set() to set the #GtkEntryCompletion:text-column - * property directly. - */ -void -gtk_entry_completion_set_text_column (GtkEntryCompletion *completion, - gint column) -{ - GtkCellRenderer *cell; - - g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); - g_return_if_fail (column >= 0); - - if (completion->text_column == column) - return; - - completion->text_column = column; - - cell = gtk_cell_renderer_text_new (); - gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), - cell, TRUE); - gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion), - cell, - "text", column); - - g_object_notify_by_pspec (G_OBJECT (completion), entry_completion_props[PROP_TEXT_COLUMN]); -} - -/** - * gtk_entry_completion_get_text_column: - * @completion: a #GtkEntryCompletion - * - * Returns the column in the model of @completion to get strings from. - * - * Returns: the column containing the strings - */ -gint -gtk_entry_completion_get_text_column (GtkEntryCompletion *completion) -{ - g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), -1); - - return completion->text_column; + gtk_entry_completion_resize_popup (completion); } /* private */ /* some nasty size requisition */ void -_gtk_entry_completion_resize_popup (GtkEntryCompletion *completion) +gtk_entry_completion_resize_popup (GtkEntryCompletion *completion) { GtkAllocation allocation; - gint matches, items, height; - GdkSurface *surface; GtkRequisition entry_req; - GtkRequisition tree_req; - GtkTreePath *path; - gint width; + GtkRequisition list_req; + int width; - surface = gtk_native_get_surface (gtk_widget_get_native (completion->entry)); - - if (!surface) - return; - - if (!completion->filter_model) + if (!gtk_native_get_surface (gtk_widget_get_native (completion->entry))) return; gtk_widget_get_surface_allocation (completion->entry, &allocation); - gtk_widget_get_preferred_size (completion->entry, - &entry_req, NULL); - - matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->filter_model), NULL); + gtk_widget_get_preferred_size (completion->entry, &entry_req, NULL); /* Call get preferred size on the on the tree view to force it to validate its * cells before calling into the cell size functions. */ - gtk_widget_get_preferred_size (completion->tree_view, - &tree_req, NULL); - gtk_tree_view_column_cell_get_size (completion->column, - NULL, NULL, NULL, &height); - - gtk_widget_realize (completion->tree_view); - - items = MIN (matches, 10); - - if (items <= 0) - gtk_widget_hide (completion->scrolled_window); - else - gtk_widget_show (completion->scrolled_window); + gtk_widget_get_preferred_size (completion->list_view, &list_req, NULL); + gtk_widget_realize (completion->list_view); if (completion->popup_set_width) width = allocation.width; else width = -1; - gtk_tree_view_columns_autosize (GTK_TREE_VIEW (completion->tree_view)); gtk_scrolled_window_set_min_content_width (GTK_SCROLLED_WINDOW (completion->scrolled_window), width); gtk_widget_set_size_request (completion->popup_window, width, -1); - gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (completion->scrolled_window), items * height); - - if (matches > 0) - { - path = gtk_tree_path_new_from_indices (0, -1); - gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (completion->tree_view), path, - NULL, FALSE, 0.0, 0.0); - gtk_tree_path_free (path); - } gtk_native_check_resize (GTK_NATIVE (completion->popup_window)); } @@ -1178,13 +969,13 @@ gtk_entry_completion_popup (GtkEntryCompletion *completion) gtk_widget_realize (completion->popup_window); - _gtk_entry_completion_resize_popup (completion); + gtk_entry_completion_resize_popup (completion); gtk_popover_popup (GTK_POPOVER (completion->popup_window)); } void -_gtk_entry_completion_popdown (GtkEntryCompletion *completion) +gtk_entry_completion_popdown (GtkEntryCompletion *completion) { if (!gtk_widget_get_mapped (completion->popup_window)) return; @@ -1193,30 +984,48 @@ _gtk_entry_completion_popdown (GtkEntryCompletion *completion) } static gboolean +gtk_entry_completion_get_text (GtkEntryCompletion *completion, + guint position, + GValue *value) +{ + gpointer item; + + item = g_list_model_get_item (completion->filter_model, position); + + if (completion->expression == NULL || + !gtk_expression_evaluate (completion->expression, item, value)) + { + g_object_unref (item); + return FALSE; + } + + g_object_unref (item); + return TRUE; +} + +static gboolean gtk_entry_completion_match_selected (GtkEntryCompletion *completion, - GtkTreeModel *model, - GtkTreeIter *iter) + guint position) { - gchar *str = NULL; + GValue value = G_VALUE_INIT; - gtk_tree_model_get (model, iter, completion->text_column, &str, -1); - gtk_editable_set_text (GTK_EDITABLE (completion->entry), str ? str : ""); + if (!gtk_entry_completion_get_text (completion, position, &value)) + return FALSE; - /* move cursor to the end */ + gtk_editable_set_text (GTK_EDITABLE (completion->entry), + g_value_get_string (&value)); gtk_editable_set_position (GTK_EDITABLE (completion->entry), -1); - g_free (str); + g_value_unset (&value); return TRUE; } static gboolean gtk_entry_completion_cursor_on_match (GtkEntryCompletion *completion, - GtkTreeModel *model, - GtkTreeIter *iter) + guint position) { - gtk_entry_completion_insert_completion (completion, model, iter); - + gtk_entry_completion_insert_completion (completion, position); return TRUE; } @@ -1228,7 +1037,7 @@ gtk_entry_completion_cursor_on_match (GtkEntryCompletion *completion, * Computes the common prefix that is shared by all rows in @completion * that start with @key. If no row matches @key, %NULL will be returned. * Note that a text column must have been set for this function to work, - * see gtk_entry_completion_set_text_column() for details. + * see gtk_entry_completion_set_text_column() for details. * * Returns: (nullable) (transfer full): The common prefix all rows starting with * @key or %NULL if no row matches @key. @@ -1237,23 +1046,30 @@ gchar * gtk_entry_completion_compute_prefix (GtkEntryCompletion *completion, const char *key) { - GtkTreeIter iter; - gchar *prefix = NULL; - gboolean valid; + char *prefix = NULL; + guint i, n; - if (completion->text_column < 0) - return NULL; - - valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (completion->filter_model), - &iter); - - while (valid) + n = g_list_model_get_n_items (completion->filter_model); + for (i = 0; i < n; i++) { - gchar *text; + gpointer item = g_list_model_get_item (completion->filter_model, i); + GValue value = G_VALUE_INIT; + const char *text; - gtk_tree_model_get (GTK_TREE_MODEL (completion->filter_model), - &iter, completion->text_column, &text, - -1); + if (completion->expression && + gtk_expression_evaluate (completion->expression, item, &value)) + { + text = g_value_get_string (&value); + } + else if (GTK_IS_STRING_OBJECT (item)) + { + text = gtk_string_object_get_string (GTK_STRING_OBJECT (item)); + } + else + { + g_object_unref (item); + continue; + } if (text && g_str_has_prefix (text, key)) { @@ -1261,8 +1077,8 @@ gtk_entry_completion_compute_prefix (GtkEntryCompletion *completion, prefix = g_strdup (text); else { - gchar *p = prefix; - gchar *q = text; + char *p = prefix; + char *q = (char *)text; while (*p && *p == *q) { @@ -1288,9 +1104,8 @@ gtk_entry_completion_compute_prefix (GtkEntryCompletion *completion, } } - g_free (text); - valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (completion->filter_model), - &iter); + g_value_unset (&value); + g_object_unref (item); } return prefix; @@ -1299,13 +1114,13 @@ gtk_entry_completion_compute_prefix (GtkEntryCompletion *completion, static gboolean gtk_entry_completion_real_insert_prefix (GtkEntryCompletion *completion, - const gchar *prefix) + const char *prefix) { if (prefix) { gint key_len; gint prefix_len; - const gchar *key; + const char *key; prefix_len = g_utf8_strlen (prefix, -1); @@ -1345,21 +1160,25 @@ gtk_entry_completion_get_completion_prefix (GtkEntryCompletion *completion) return completion->completion_prefix; } -static void -gtk_entry_completion_insert_completion_text (GtkEntryCompletion *completion, - const gchar *new_text) +static gboolean +gtk_entry_completion_insert_completion (GtkEntryCompletion *completion, + guint position) { - gint len; + GValue value = G_VALUE_INIT; + int len; GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (completion->entry)); GtkEntryBuffer *buffer = gtk_text_get_buffer (text); + if (!gtk_entry_completion_get_text (completion, position, &value)) + return FALSE; + if (completion->changed_id > 0) g_signal_handler_block (text, completion->changed_id); if (completion->insert_text_id > 0) g_signal_handler_block (buffer, completion->insert_text_id); - gtk_editable_set_text (GTK_EDITABLE (completion->entry), new_text); + gtk_editable_set_text (GTK_EDITABLE (completion->entry), g_value_get_string (&value)); len = g_utf8_strlen (completion->completion_prefix, -1); gtk_editable_select_region (GTK_EDITABLE (completion->entry), len, -1); @@ -1369,25 +1188,8 @@ gtk_entry_completion_insert_completion_text (GtkEntryCompletion *completion, if (completion->insert_text_id > 0) g_signal_handler_unblock (buffer, completion->insert_text_id); -} - -static gboolean -gtk_entry_completion_insert_completion (GtkEntryCompletion *completion, - GtkTreeModel *model, - GtkTreeIter *iter) -{ - gchar *str = NULL; - - if (completion->text_column < 0) - return FALSE; - gtk_tree_model_get (model, iter, - completion->text_column, &str, - -1); - - gtk_entry_completion_insert_completion_text (completion, str); - - g_free (str); + g_value_unset (&value); return TRUE; } @@ -1437,14 +1239,12 @@ gtk_entry_completion_set_inline_completion (GtkEntryCompletion *completion, { g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); - inline_completion = inline_completion != FALSE; + if (completion->inline_completion == inline_completion) + return; - if (completion->inline_completion != inline_completion) - { - completion->inline_completion = inline_completion; + completion->inline_completion = inline_completion; - g_object_notify_by_pspec (G_OBJECT (completion), entry_completion_props[PROP_INLINE_COMPLETION]); - } + g_object_notify_by_pspec (G_OBJECT (completion), entry_completion_props[PROP_INLINE_COMPLETION]); } /** @@ -1477,14 +1277,12 @@ gtk_entry_completion_set_popup_completion (GtkEntryCompletion *completion, { g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); - popup_completion = popup_completion != FALSE; + if (completion->popup_completion == popup_completion) + return; - if (completion->popup_completion != popup_completion) - { - completion->popup_completion = popup_completion; + completion->popup_completion = popup_completion; - g_object_notify_by_pspec (G_OBJECT (completion), entry_completion_props[PROP_POPUP_COMPLETION]); - } + g_object_notify_by_pspec (G_OBJECT (completion), entry_completion_props[PROP_POPUP_COMPLETION]); } @@ -1563,14 +1361,12 @@ gtk_entry_completion_set_popup_single_match (GtkEntryCompletion *completion, { g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); - popup_single_match = popup_single_match != FALSE; + if (completion->popup_single_match == popup_single_match) + return; - if (completion->popup_single_match != popup_single_match) - { - completion->popup_single_match = popup_single_match; + completion->popup_single_match = popup_single_match; - g_object_notify_by_pspec (G_OBJECT (completion), entry_completion_props[PROP_POPUP_SINGLE_MATCH]); - } + g_object_notify_by_pspec (G_OBJECT (completion), entry_completion_props[PROP_POPUP_SINGLE_MATCH]); } /** @@ -1605,14 +1401,12 @@ gtk_entry_completion_set_inline_selection (GtkEntryCompletion *completion, { g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); - inline_selection = inline_selection != FALSE; + if (completion->inline_selection == inline_selection) + return; - if (completion->inline_selection != inline_selection) - { - completion->inline_selection = inline_selection; + completion->inline_selection = inline_selection; - g_object_notify_by_pspec (G_OBJECT (completion), entry_completion_props[PROP_INLINE_SELECTION]); - } + g_object_notify_by_pspec (G_OBJECT (completion), entry_completion_props[PROP_INLINE_SELECTION]); } /** @@ -1636,33 +1430,35 @@ static gint gtk_entry_completion_timeout (gpointer data) { GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (data); + const char *text; completion->completion_timeout = 0; + text = gtk_editable_get_text (GTK_EDITABLE (completion->entry)); if (completion->filter_model && - g_utf8_strlen (gtk_editable_get_text (GTK_EDITABLE (completion->entry)), -1) - >= completion->minimum_key_length) + g_utf8_strlen (text, -1) >= completion->minimum_key_length) { - gint matches; + int matches; gboolean popup_single; gtk_entry_completion_complete (completion); - matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->filter_model), NULL); - gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->tree_view))); + matches = g_list_model_get_n_items (completion->filter_model); + gtk_selection_model_unselect_all (GTK_SELECTION_MODEL (gtk_list_view_get_model (GTK_LIST_VIEW (completion->list_view)))); g_object_get (completion, "popup-single-match", &popup_single, NULL); - if (matches > (popup_single ? 0: 1)) + if ((completion->popup_single_match && matches > 0) || matches > 1) { if (gtk_widget_get_visible (completion->popup_window)) - _gtk_entry_completion_resize_popup (completion); + gtk_entry_completion_resize_popup (completion); else gtk_entry_completion_popup (completion); } else - _gtk_entry_completion_popdown (completion); + gtk_entry_completion_popdown (completion); } else if (gtk_widget_get_visible (completion->popup_window)) - _gtk_entry_completion_popdown (completion); + gtk_entry_completion_popdown (completion); + return G_SOURCE_REMOVE; } @@ -1691,10 +1487,9 @@ gtk_entry_completion_key_pressed (GtkEventControllerKey *controller, GdkModifierType state, gpointer user_data) { - gint matches; GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); GtkWidget *widget = completion->entry; - GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (widget)); + int matches; if (!completion->popup_completion) return FALSE; @@ -1714,12 +1509,10 @@ gtk_entry_completion_key_pressed (GtkEventControllerKey *controller, if (!gtk_widget_get_mapped (completion->popup_window)) return FALSE; - matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->filter_model), NULL); + matches = g_list_model_get_n_items (completion->filter_model); if (keyval_is_cursor_move (keyval)) { - GtkTreePath *path = NULL; - if (keyval == GDK_KEY_Up || keyval == GDK_KEY_KP_Up) { if (completion->current_selected < 0) @@ -1777,7 +1570,7 @@ gtk_entry_completion_key_pressed (GtkEventControllerKey *controller, if (completion->current_selected < 0) { - gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->tree_view))); + gtk_selection_model_unselect_all (GTK_SELECTION_MODEL (gtk_list_view_get_model (GTK_LIST_VIEW (completion->list_view)))); if (completion->inline_selection && completion->completion_prefix) @@ -1789,34 +1582,40 @@ gtk_entry_completion_key_pressed (GtkEventControllerKey *controller, } else if (completion->current_selected < matches) { - path = gtk_tree_path_new_from_indices (completion->current_selected, -1); - gtk_tree_view_set_cursor (GTK_TREE_VIEW (completion->tree_view), - path, NULL, FALSE); + gtk_selection_model_select_item (GTK_SELECTION_MODEL (gtk_list_view_get_model (GTK_LIST_VIEW (completion->list_view))), + completion->current_selected, + TRUE); if (completion->inline_selection) { + gpointer obj = gtk_single_selection_get_selected_item (GTK_SINGLE_SELECTION (gtk_list_view_get_model (GTK_LIST_VIEW (completion->list_view)))); - GtkTreeIter iter; - GtkTreeIter child_iter; - GtkTreeModel *model = NULL; - GtkTreeSelection *sel; gboolean entry_set; - sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->tree_view)); - if (!gtk_tree_selection_get_selected (sel, &model, &iter)) + if (obj == NULL) return FALSE; - gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_iter, &iter); - model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model)); if (completion->completion_prefix == NULL) completion->completion_prefix = g_strdup (gtk_editable_get_text (GTK_EDITABLE (completion->entry))); - g_signal_emit_by_name (completion, "cursor-on-match", model, - &child_iter, &entry_set); + g_signal_emit_by_name (completion, "cursor-on-match", + completion->current_selected, &entry_set); } } + else if (completion->current_selected - matches >= 0) + { + gtk_selection_model_select_item (GTK_SELECTION_MODEL (gtk_list_view_get_model (GTK_LIST_VIEW (completion->list_view))), + completion->current_selected - matches, + TRUE); - gtk_tree_path_free (path); + if (completion->inline_selection && + completion->completion_prefix) + { + gtk_editable_set_text (GTK_EDITABLE (completion->entry), + completion->completion_prefix); + gtk_editable_set_position (GTK_EDITABLE (widget), -1); + } + } return TRUE; } @@ -1829,7 +1628,7 @@ gtk_entry_completion_key_pressed (GtkEventControllerKey *controller, gboolean retval = TRUE; gtk_entry_reset_im_context (GTK_ENTRY (widget)); - _gtk_entry_completion_popdown (completion); + gtk_entry_completion_popdown (completion); if (completion->current_selected < 0) { @@ -1853,8 +1652,9 @@ gtk_entry_completion_key_pressed (GtkEventControllerKey *controller, keyval == GDK_KEY_KP_Right || keyval == GDK_KEY_Escape) gtk_editable_set_position (GTK_EDITABLE (widget), -1); - /* Let the default keybindings run for Left, i.e. either move to the - * * previous character or select word if a modifier is used */ + /* Let the default keybindings run for Left, i.e. either move + * to the previous character or select word if a modifier is used + */ else retval = FALSE; } @@ -1870,7 +1670,7 @@ keypress_completion_out: keyval == GDK_KEY_ISO_Left_Tab) { gtk_entry_reset_im_context (GTK_ENTRY (widget)); - _gtk_entry_completion_popdown (completion); + gtk_entry_completion_popdown (completion); g_clear_pointer (&completion->completion_prefix, g_free); @@ -1880,43 +1680,31 @@ keypress_completion_out: keyval == GDK_KEY_KP_Enter || keyval == GDK_KEY_Return) { - GtkTreeIter iter; - GtkTreeModel *model = NULL; - GtkTreeModel *child_model; - GtkTreeIter child_iter; - GtkTreeSelection *sel; gboolean retval = TRUE; gtk_entry_reset_im_context (GTK_ENTRY (widget)); - _gtk_entry_completion_popdown (completion); + gtk_entry_completion_popdown (completion); if (completion->current_selected < matches) { - gboolean entry_set; + gpointer obj = gtk_single_selection_get_selected_item (GTK_SINGLE_SELECTION (gtk_list_view_get_model (GTK_LIST_VIEW (completion->list_view)))); + GValue value = G_VALUE_INIT; - sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->tree_view)); - if (gtk_tree_selection_get_selected (sel, &model, &iter)) + if (completion->expression && + gtk_expression_evaluate (completion->expression, obj, &value)) { - gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_iter, &iter); - child_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model)); + GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (completion->entry)); + gboolean entry_set; + g_signal_handler_block (text, completion->changed_id); g_signal_emit_by_name (completion, "match-selected", - child_model, &child_iter, &entry_set); + completion->current_selected, &entry_set); g_signal_handler_unblock (text, completion->changed_id); if (!entry_set) { - gchar *str = NULL; - - gtk_tree_model_get (model, &iter, - completion->text_column, &str, - -1); - - gtk_editable_set_text (GTK_EDITABLE (widget), str); - - /* move the cursor to the end */ + gtk_editable_set_text (GTK_EDITABLE (widget), g_value_get_string (&value)); gtk_editable_set_position (GTK_EDITABLE (widget), -1); - g_free (str); } } else @@ -1938,6 +1726,7 @@ gtk_entry_completion_changed (GtkWidget *widget, gpointer user_data) { GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); + const char *text; if (!completion->popup_completion) return; @@ -1949,30 +1738,31 @@ gtk_entry_completion_changed (GtkWidget *widget, completion->completion_timeout = 0; } - if (!gtk_editable_get_text (GTK_EDITABLE (widget))) + text = gtk_editable_get_text (GTK_EDITABLE (widget)); + if (!text) return; /* no need to normalize for this test */ if (completion->minimum_key_length > 0 && - strcmp ("", gtk_editable_get_text (GTK_EDITABLE (widget))) == 0) + strcmp ("", text) == 0) { if (gtk_widget_get_visible (completion->popup_window)) - _gtk_entry_completion_popdown (completion); + gtk_entry_completion_popdown (completion); return; } - completion->completion_timeout = - g_timeout_add (COMPLETION_TIMEOUT, - gtk_entry_completion_timeout, - completion); - g_source_set_name_by_id (completion->completion_timeout, "[gtk] gtk_entry_completion_timeout"); + completion->completion_timeout = g_timeout_add (COMPLETION_TIMEOUT, + gtk_entry_completion_timeout, + completion); + g_source_set_name_by_id (completion->completion_timeout, + "[gtk] gtk_entry_completion_timeout"); } static gboolean check_completion_callback (GtkEntryCompletion *completion) { completion->check_completion_idle = NULL; - + gtk_entry_completion_complete (completion); gtk_entry_completion_insert_prefix (completion); @@ -2117,7 +1907,7 @@ disconnect_completion_signals (GtkEntryCompletion *completion) } void -_gtk_entry_completion_disconnect (GtkEntryCompletion *completion) +gtk_entry_completion_disconnect (GtkEntryCompletion *completion) { if (completion->completion_timeout) { @@ -2131,26 +1921,20 @@ _gtk_entry_completion_disconnect (GtkEntryCompletion *completion) } if (gtk_widget_get_mapped (completion->popup_window)) - _gtk_entry_completion_popdown (completion); + gtk_entry_completion_popdown (completion); disconnect_completion_signals (completion); - - unset_accessible_relation (completion->popup_window, - completion->entry); + unset_accessible_relation (completion->popup_window, completion->entry); gtk_widget_unparent (completion->popup_window); - completion->entry = NULL; } void -_gtk_entry_completion_connect (GtkEntryCompletion *completion, - GtkEntry *entry) +gtk_entry_completion_connect (GtkEntryCompletion *completion, + GtkEntry *entry) { completion->entry = GTK_WIDGET (entry); - - set_accessible_relation (completion->popup_window, - completion->entry); + set_accessible_relation (completion->popup_window, completion->entry); gtk_widget_set_parent (completion->popup_window, GTK_WIDGET (entry)); - connect_completion_signals (completion); } diff --git a/gtk/gtkentrycompletion.h b/gtk/gtkentrycompletion.h index 6fd623edd9..2809ad2dd7 100644 --- a/gtk/gtkentrycompletion.h +++ b/gtk/gtkentrycompletion.h @@ -23,11 +23,8 @@ #endif #include <gdk/gdk.h> -#include <gtk/gtktreemodel.h> -#include <gtk/gtkliststore.h> -#include <gtk/gtkcellarea.h> -#include <gtk/gtktreeviewcolumn.h> -#include <gtk/gtktreemodelfilter.h> +#include <gtk/gtklistitemfactory.h> +#include <gtk/gtkexpression.h> G_BEGIN_DECLS @@ -37,50 +34,31 @@ G_BEGIN_DECLS typedef struct _GtkEntryCompletion GtkEntryCompletion; -/** - * GtkEntryCompletionMatchFunc: - * @completion: the #GtkEntryCompletion - * @key: the string to match, normalized and case-folded - * @iter: a #GtkTreeIter indicating the row to match - * @user_data: user data given to gtk_entry_completion_set_match_func() - * - * A function which decides whether the row indicated by @iter matches - * a given @key, and should be displayed as a possible completion for @key. - * Note that @key is normalized and case-folded (see g_utf8_normalize() - * and g_utf8_casefold()). If this is not appropriate, match functions - * have access to the unmodified key via - * `gtk_editable_get_text (GTK_EDITABLE (gtk_entry_completion_get_entry ()))`. - * - * Returns: %TRUE if @iter should be displayed as a possible completion - * for @key - */ -typedef gboolean (* GtkEntryCompletionMatchFunc) (GtkEntryCompletion *completion, - const gchar *key, - GtkTreeIter *iter, - gpointer user_data); - - GDK_AVAILABLE_IN_ALL GType gtk_entry_completion_get_type (void) G_GNUC_CONST; GDK_AVAILABLE_IN_ALL GtkEntryCompletion *gtk_entry_completion_new (void); GDK_AVAILABLE_IN_ALL -GtkEntryCompletion *gtk_entry_completion_new_with_area (GtkCellArea *area); - -GDK_AVAILABLE_IN_ALL GtkWidget *gtk_entry_completion_get_entry (GtkEntryCompletion *completion); GDK_AVAILABLE_IN_ALL void gtk_entry_completion_set_model (GtkEntryCompletion *completion, - GtkTreeModel *model); + GListModel *model); GDK_AVAILABLE_IN_ALL -GtkTreeModel *gtk_entry_completion_get_model (GtkEntryCompletion *completion); +GListModel * gtk_entry_completion_get_model (GtkEntryCompletion *completion); GDK_AVAILABLE_IN_ALL -void gtk_entry_completion_set_match_func (GtkEntryCompletion *completion, - GtkEntryCompletionMatchFunc func, - gpointer func_data, - GDestroyNotify func_notify); +void gtk_entry_completion_set_expression (GtkEntryCompletion *completion, + GtkExpression *expression); +GDK_AVAILABLE_IN_ALL +GtkExpression * gtk_entry_completion_get_expression (GtkEntryCompletion *completion); + +GDK_AVAILABLE_IN_ALL +void gtk_entry_completion_set_factory (GtkEntryCompletion *completion, + GtkListItemFactory *factory); +GDK_AVAILABLE_IN_ALL +GtkListItemFactory *gtk_entry_completion_get_factory (GtkEntryCompletion *completion); + GDK_AVAILABLE_IN_ALL void gtk_entry_completion_set_minimum_key_length (GtkEntryCompletion *completion, gint length); @@ -122,12 +100,6 @@ gboolean gtk_entry_completion_get_popup_single_match (GtkEntryComplet GDK_AVAILABLE_IN_ALL const gchar *gtk_entry_completion_get_completion_prefix (GtkEntryCompletion *completion); -/* convenience */ -GDK_AVAILABLE_IN_ALL -void gtk_entry_completion_set_text_column (GtkEntryCompletion *completion, - gint column); -GDK_AVAILABLE_IN_ALL -gint gtk_entry_completion_get_text_column (GtkEntryCompletion *completion); G_END_DECLS diff --git a/gtk/gtkentryprivate.h b/gtk/gtkentryprivate.h index a04b63e288..0008c1c38a 100644 --- a/gtk/gtkentryprivate.h +++ b/gtk/gtkentryprivate.h @@ -38,43 +38,34 @@ struct _GtkEntryCompletion GtkWidget *entry; - GtkWidget *tree_view; - GtkTreeViewColumn *column; - GtkTreeModelFilter *filter_model; - GtkCellArea *cell_area; - - GtkEntryCompletionMatchFunc match_func; - gpointer match_data; - GDestroyNotify match_notify; + GListModel *filter_model; + GtkExpression *expression; + GtkListItemFactory *factory; gint minimum_key_length; - gint text_column; - - gchar *case_normalized_key; + char *case_normalized_key; GtkEventController *entry_key_controller; GtkEventController *entry_focus_controller; - /* only used by GtkEntry when attached: */ GtkWidget *popup_window; GtkWidget *scrolled_window; + GtkWidget *list_view; gulong completion_timeout; gulong changed_id; gulong insert_text_id; - gint current_selected; + int current_selected; - guint first_sel_changed : 1; - guint has_completion : 1; - guint inline_completion : 1; - guint popup_completion : 1; - guint popup_set_width : 1; + guint has_completion : 1; + guint inline_completion : 1; + guint popup_completion : 1; + guint popup_set_width : 1; guint popup_single_match : 1; guint inline_selection : 1; - guint has_grab : 1; - gchar *completion_prefix; + char *completion_prefix; GSource *check_completion_idle; }; @@ -84,23 +75,19 @@ struct _GtkEntryCompletionClass GObjectClass parent_class; gboolean (* match_selected) (GtkEntryCompletion *completion, - GtkTreeModel *model, - GtkTreeIter *iter); - void (* action_activated) (GtkEntryCompletion *completion, - gint index_); + guint position); gboolean (* insert_prefix) (GtkEntryCompletion *completion, - const gchar *prefix); + const char *prefix); gboolean (* cursor_on_match) (GtkEntryCompletion *completion, - GtkTreeModel *model, - GtkTreeIter *iter); + guint position); void (* no_matches) (GtkEntryCompletion *completion); }; -void _gtk_entry_completion_resize_popup (GtkEntryCompletion *completion); -void _gtk_entry_completion_popdown (GtkEntryCompletion *completion); -void _gtk_entry_completion_connect (GtkEntryCompletion *completion, - GtkEntry *entry); -void _gtk_entry_completion_disconnect (GtkEntryCompletion *completion); +void gtk_entry_completion_resize_popup (GtkEntryCompletion *completion); +void gtk_entry_completion_popdown (GtkEntryCompletion *completion); +void gtk_entry_completion_connect (GtkEntryCompletion *completion, + GtkEntry *entry); +void gtk_entry_completion_disconnect (GtkEntryCompletion *completion); GtkIMContext * _gtk_entry_get_im_context (GtkEntry *entry); GtkEventController * gtk_entry_get_key_controller (GtkEntry *entry); diff --git a/gtk/gtkplacesview.c b/gtk/gtkplacesview.c index eb2b9baf7d..1af94d0e11 100644 --- a/gtk/gtkplacesview.c +++ b/gtk/gtkplacesview.c @@ -30,6 +30,7 @@ #include "gtktypebuiltins.h" #include "gtkeventcontrollerkey.h" #include "gtkpopovermenu.h" +#include "gtkstringlist.h" /* * SECTION:gtkplacesview @@ -98,7 +99,7 @@ struct _GtkPlacesView GtkSizeGroup *space_size_group; GtkEntryCompletion *address_entry_completion; - GtkListStore *completion_store; + GListModel *completion_store; GCancellable *networks_fetching_cancellable; @@ -551,12 +552,12 @@ populate_servers (GtkPlacesView *view) while ((child = gtk_widget_get_first_child (GTK_WIDGET (view->recent_servers_listbox)))) gtk_list_box_remove (GTK_LIST_BOX (view->listbox), child); - gtk_list_store_clear (view->completion_store); + while (g_list_model_get_n_items (G_LIST_MODEL (view->completion_store)) > 0) + gtk_string_list_remove (GTK_STRING_LIST (view->completion_store), 0); for (i = 0; i < num_uris; i++) { RemoveServerData *data; - GtkTreeIter iter; GtkWidget *row; GtkWidget *grid; GtkWidget *button; @@ -568,12 +569,7 @@ populate_servers (GtkPlacesView *view) dup_uri = g_strdup (uris[i]); /* add to the completion list */ - gtk_list_store_append (view->completion_store, &iter); - gtk_list_store_set (view->completion_store, - &iter, - 0, name, - 1, uris[i], - -1); + gtk_string_list_append (GTK_STRING_LIST (view->completion_store), uris[i]); /* add to the recent servers listbox */ row = gtk_list_box_row_new (); diff --git a/gtk/ui/gtkplacesview.ui b/gtk/ui/gtkplacesview.ui index 4668ae758b..a2a1afac65 100644 --- a/gtk/ui/gtkplacesview.ui +++ b/gtk/ui/gtkplacesview.ui @@ -1,15 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> <interface domain="gtk40"> <requires lib="gtk+" version="3.16"/> - <object class="GtkListStore" id="completion_store"> - <columns> - <column type="gchararray"/> - <column type="gchararray"/> - </columns> + <object class="GtkStringList" id="completion_store"> </object> <object class="GtkEntryCompletion" id="address_entry_completion"> <property name="model">completion_store</property> - <property name="text-column">1</property> <property name="inline-completion">1</property> <property name="popup-completion">0</property> </object> diff --git a/tests/testentrycompletion.c b/tests/testentrycompletion.c index 68985d7383..6588c8ce5d 100644 --- a/tests/testentrycompletion.c +++ b/tests/testentrycompletion.c @@ -21,272 +21,80 @@ #include <string.h> #include <gtk/gtk.h> -/* Don't copy this bad example; inline RGB data is always a better - * idea than inline XPMs. - */ -static const char *book_closed_xpm[] = { -"16 16 6 1", -" c None s None", -". c black", -"X c red", -"o c yellow", -"O c #808080", -"# c white", -" ", -" .. ", -" ..XX. ", -" ..XXXXX. ", -" ..XXXXXXXX. ", -".ooXXXXXXXXX. ", -"..ooXXXXXXXXX. ", -".X.ooXXXXXXXXX. ", -".XX.ooXXXXXX.. ", -" .XX.ooXXX..#O ", -" .XX.oo..##OO. ", -" .XX..##OO.. ", -" .X.#OO.. ", -" ..O.. ", -" .. ", -" " -}; - static GtkWidget *window = NULL; -/* Creates a tree model containing the completions */ -static GtkTreeModel * +/* Creates a list model containing the completions */ +static GListModel * create_simple_completion_model (void) { - GtkListStore *store; - GtkTreeIter iter; - - store = gtk_list_store_new (1, G_TYPE_STRING); - - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "GNOME", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "gnominious", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "Gnomonic projection", -1); - - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "total", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "totally", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "toto", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "tottery", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "totterer", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "Totten trust", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "totipotent", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "totipotency", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "totemism", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "totem pole", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "Totara", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "totalizer", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "totalizator", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "totalitarianism", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "total parenteral nutrition", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "total hysterectomy", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "total eclipse", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "Totipresence", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "Totipalmi", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "zombie", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "a\303\246x", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "a\303\246y", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "a\303\246z", -1); - - return GTK_TREE_MODEL (store); -} - -/* Creates a tree model containing the completions */ -static GtkTreeModel * -create_completion_model (void) -{ - GtkListStore *store; - GtkTreeIter iter; - GdkPixbuf *pixbuf; - - pixbuf = gdk_pixbuf_new_from_xpm_data ((const char **)book_closed_xpm); - - store = gtk_list_store_new (2, GDK_TYPE_PIXBUF, G_TYPE_STRING); - - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, pixbuf, 1, "ambient", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, pixbuf, 1, "ambidextrously", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, pixbuf, 1, "ambidexter", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, pixbuf, 1, "ambiguity", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, pixbuf, 1, "American Party", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, pixbuf, 1, "American mountain ash", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, pixbuf, 1, "amelioration", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, pixbuf, 1, "Amelia Earhart", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, pixbuf, 1, "Totten trust", -1); - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, pixbuf, 1, "Laminated arch", -1); - - return GTK_TREE_MODEL (store); + const char *strings[] = { + "GNOME", + "gnominious", + "Gnomonic projection", + "total", + "totally", + "toto", + "tottery", + "totterer", + "Totten trust", + "totipotent", + "totipotency", + "totemism", + "totem pole", + "Totara", + "totalizer", + "totalizator", + "totalitarianism", + "total parenteral nutrition", + "total hysterectomy", + "total eclipse", + "Totipresence", + "Totipalmi", + "zombie", + "a\303\246x", + "a\303\246y", + "a\303\246z", + NULL + }; + + return G_LIST_MODEL (gtk_string_list_new (strings)); } -static gboolean -match_func (GtkEntryCompletion *completion, - const gchar *key, - GtkTreeIter *iter, - gpointer user_data) +static char * +get_file_name (gpointer item) { - gchar *item = NULL; - GtkTreeModel *model; - - gboolean ret = FALSE; - - model = gtk_entry_completion_get_model (completion); - - gtk_tree_model_get (model, iter, 1, &item, -1); - - if (item != NULL) - { - g_print ("compare %s %s\n", key, item); - if (strncmp (key, item, strlen (key)) == 0) - ret = TRUE; - - g_free (item); - } - - return ret; + return g_strdup (g_file_info_get_display_name (G_FILE_INFO (item))); } -static gint timer_count = 0; - -static const char *dynamic_completions[] = { - "GNOME", - "gnominious", - "Gnomonic projection", - "total", - "totally", - "toto", - "tottery", - "totterer", - "Totten trust", - "totipotent", - "totipotency", - "totemism", - "totem pole", - "Totara", - "totalizer", - "totalizator", - "totalitarianism", - "total parenteral nutrition", - "total hysterectomy", - "total eclipse", - "Totipresence", - "Totipalmi", - "zombie" -}; - -static gint -animation_timer (GtkEntryCompletion *completion) -{ - GtkTreeIter iter; - gint n_completions = G_N_ELEMENTS (dynamic_completions); - gint n; - static GtkListStore *old_store = NULL; - GtkListStore *store = GTK_LIST_STORE (gtk_entry_completion_get_model (completion)); - - if (timer_count % 10 == 0) - { - if (!old_store) - { - g_print ("removing model!\n"); - - old_store = g_object_ref (store); - gtk_entry_completion_set_model (completion, NULL); - } - else - { - g_print ("readding model!\n"); - - gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (old_store)); - g_object_unref (old_store); - old_store = NULL; - } - - timer_count ++; - return TRUE; - } - - if (!old_store) - { - if ((timer_count / n_completions) % 2 == 0) - { - n = timer_count % n_completions; - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, dynamic_completions[n], -1); - - } - else - { - if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) - gtk_list_store_remove (store, &iter); - } - } - - timer_count++; - return TRUE; -} - -static gboolean -match_selected_cb (GtkEntryCompletion *completion, - GtkTreeModel *model, - GtkTreeIter *iter) +static void +setup_item (GtkSignalListItemFactory *factory, + GtkListItem *item) { - gchar *str; - GtkWidget *entry; - - entry = gtk_entry_completion_get_entry (completion); - gtk_tree_model_get (GTK_TREE_MODEL (model), iter, 1, &str, -1); - gtk_editable_set_text (GTK_EDITABLE (entry), str); - gtk_editable_set_position (GTK_EDITABLE (entry), -1); - g_free (str); + GtkWidget *box; + GtkWidget *icon; + GtkWidget *label; - return TRUE; + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); + icon = gtk_image_new (); + label = gtk_label_new (""); + gtk_label_set_xalign (GTK_LABEL (label), 0); + gtk_box_append (GTK_BOX (box), icon); + gtk_box_append (GTK_BOX (box), label); + gtk_list_item_set_child (item, box); } static void -quit_cb (GtkWidget *widget, - gpointer data) +bind_item (GtkSignalListItemFactory *factory, + GtkListItem *item) { - gboolean *done = data; - - *done = TRUE; + GFileInfo *info = G_FILE_INFO (gtk_list_item_get_item (item)); + GtkWidget *box = gtk_list_item_get_child (item); + GtkWidget *icon = gtk_widget_get_first_child (box); + GtkWidget *label = gtk_widget_get_last_child (box); - g_main_context_wakeup (NULL); + gtk_image_set_from_gicon (GTK_IMAGE (icon), g_file_info_get_icon (info)); + gtk_label_set_label (GTK_LABEL (label), g_file_info_get_display_name (info)); } int @@ -296,124 +104,79 @@ main (int argc, char *argv[]) GtkWidget *label; GtkWidget *entry; GtkEntryCompletion *completion; - GtkTreeModel *completion_model; - GtkCellRenderer *cell; - gboolean done = FALSE; + GListModel *model; + char *cwd; + GFile *file; + GtkExpression *expression; + GtkListItemFactory *factory; gtk_init (); window = gtk_window_new (); - g_signal_connect (window, "destroy", G_CALLBACK (quit_cb), &done); - + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); gtk_widget_set_margin_start (vbox, 5); gtk_widget_set_margin_end (vbox, 5); gtk_widget_set_margin_top (vbox, 5); gtk_widget_set_margin_bottom (vbox, 5); gtk_window_set_child (GTK_WINDOW (window), vbox); - + label = gtk_label_new (NULL); gtk_label_set_markup (GTK_LABEL (label), "Completion demo, try writing <b>total</b> or <b>gnome</b> for example."); gtk_box_append (GTK_BOX (vbox), label); - /* Create our first entry */ entry = gtk_entry_new (); - - /* Create the completion object */ + completion = gtk_entry_completion_new (); gtk_entry_completion_set_inline_completion (completion, TRUE); - - /* Assign the completion to the entry */ - gtk_entry_set_completion (GTK_ENTRY (entry), completion); - g_object_unref (completion); - - gtk_box_append (GTK_BOX (vbox), entry); - - /* Create a tree model and use it as the completion model */ - completion_model = create_simple_completion_model (); - gtk_entry_completion_set_model (completion, completion_model); - g_object_unref (completion_model); - - /* Use model column 0 as the text column */ - gtk_entry_completion_set_text_column (completion, 0); + model = create_simple_completion_model (); + gtk_entry_completion_set_model (completion, model); + g_object_unref (model); - /* Create our second entry */ - entry = gtk_entry_new (); - - /* Create the completion object */ - completion = gtk_entry_completion_new (); - - /* Assign the completion to the entry */ gtk_entry_set_completion (GTK_ENTRY (entry), completion); g_object_unref (completion); - + gtk_box_append (GTK_BOX (vbox), entry); - /* Create a tree model and use it as the completion model */ - completion_model = create_completion_model (); - gtk_entry_completion_set_model (completion, completion_model); - gtk_entry_completion_set_minimum_key_length (completion, 2); - g_object_unref (completion_model); - - /* Use model column 1 as the text column */ - cell = gtk_cell_renderer_pixbuf_new (); - gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), cell, FALSE); - gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (completion), cell, - "pixbuf", 0, NULL); - - cell = gtk_cell_renderer_text_new (); - gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), cell, FALSE); - gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (completion), cell, - "text", 1, NULL); - - gtk_entry_completion_set_match_func (completion, match_func, NULL, NULL); - g_signal_connect (completion, "match-selected", - G_CALLBACK (match_selected_cb), NULL); - - /* Create our third entry */ entry = gtk_entry_new (); - /* Create the completion object */ completion = gtk_entry_completion_new (); - - /* Assign the completion to the entry */ gtk_entry_set_completion (GTK_ENTRY (entry), completion); - g_object_unref (completion); - - gtk_box_append (GTK_BOX (vbox), entry); - - /* Create a tree model and use it as the completion model */ - completion_model = GTK_TREE_MODEL (gtk_list_store_new (1, G_TYPE_STRING)); - - gtk_entry_completion_set_model (completion, completion_model); - g_object_unref (completion_model); - /* Use model column 0 as the text column */ - gtk_entry_completion_set_text_column (completion, 0); + gtk_box_append (GTK_BOX (vbox), entry); - /* Fill the completion dynamically */ - g_timeout_add (1000, (GSourceFunc) animation_timer, completion); + cwd = g_get_current_dir (); + file = g_file_new_for_path (cwd); + model = G_LIST_MODEL (gtk_directory_list_new ("standard::display-name,standard::content-type,standard::icon,standard::size", file)); + gtk_entry_completion_set_model (completion, model); + g_object_unref (model); + g_object_unref (file); + g_free (cwd); - /* Fourth entry */ - gtk_box_append (GTK_BOX (vbox), gtk_label_new ("Model-less entry completion")); + expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL, + 0, NULL, + (GCallback)get_file_name, + NULL, NULL); + gtk_entry_completion_set_expression (completion, expression); - entry = gtk_entry_new (); + factory = gtk_signal_list_item_factory_new (); + g_signal_connect (factory, "setup", G_CALLBACK (setup_item), NULL); + g_signal_connect (factory, "bind", G_CALLBACK (bind_item), NULL); + gtk_entry_completion_set_factory (completion, factory); + g_object_unref (factory); - /* Create the completion object */ - completion = gtk_entry_completion_new (); + gtk_expression_unref (expression); - /* Assign the completion to the entry */ - gtk_entry_set_completion (GTK_ENTRY (entry), completion); g_object_unref (completion); - gtk_box_append (GTK_BOX (vbox), entry); + gtk_window_present (GTK_WINDOW (window)); - gtk_widget_show (window); - - while (!done) + while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0) g_main_context_iteration (NULL, TRUE); + gtk_window_destroy (GTK_WINDOW (window)); + return 0; } diff --git a/tests/testgtk.c b/tests/testgtk.c index 28bd0db358..7ff1e0c36f 100644 --- a/tests/testgtk.c +++ b/tests/testgtk.c @@ -2850,17 +2850,10 @@ static const gchar *cursor_names[] = { NULL }; -static GtkTreeModel * +static GListModel * cursor_model (void) { - GtkListStore *store; - gint i; - store = gtk_list_store_new (1, G_TYPE_STRING); - - for (i = 0; i < G_N_ELEMENTS (cursor_names); i++) - gtk_list_store_insert_with_values (store, NULL, -1, 0, cursor_names[i], -1); - - return (GtkTreeModel *)store; + return G_LIST_MODEL (gtk_string_list_new (cursor_names)); } static void @@ -2961,7 +2954,7 @@ create_cursors (GtkWidget *widget) GtkWidget *entry; GtkWidget *size; GtkEntryCompletion *completion; - GtkTreeModel *model; + GListModel *model; gboolean cursor_demo = FALSE; GtkGesture *gesture; @@ -3038,7 +3031,6 @@ create_cursors (GtkWidget *widget) completion = gtk_entry_completion_new (); model = cursor_model (); gtk_entry_completion_set_model (completion, model); - gtk_entry_completion_set_text_column (completion, 0); gtk_entry_set_completion (GTK_ENTRY (entry), completion); g_object_unref (model); gtk_widget_set_hexpand (entry, TRUE); diff --git a/testsuite/gtk/cellarea.c b/testsuite/gtk/cellarea.c index 1319ad5fb1..bd53702ddf 100644 --- a/testsuite/gtk/cellarea.c +++ b/testsuite/gtk/cellarea.c @@ -255,53 +255,6 @@ test_column_object_new (void) g_object_unref (col); } -/* test that we have a cell area after new() */ -static void -test_completion_new (void) -{ - GtkEntryCompletion *c; - GtkCellArea *area; - - c = gtk_entry_completion_new (); - - area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (c)); - g_assert (GTK_IS_CELL_AREA_BOX (area)); - - g_object_ref_sink (c); - g_object_unref (c); -} - -/* test that new_with_area() keeps the provided area */ -static void -test_completion_new_with_area (void) -{ - GtkEntryCompletion *c; - GtkCellArea *area; - - area = gtk_cell_area_box_new (); - c = gtk_entry_completion_new_with_area (area); - g_assert (gtk_cell_layout_get_area (GTK_CELL_LAYOUT (c)) == area); - - g_object_ref_sink (c); - g_object_unref (c); -} - -/* test that g_object_new keeps the provided area */ -static void -test_completion_object_new (void) -{ - GtkEntryCompletion *c; - GtkCellArea *area; - - area = gtk_cell_area_box_new (); - gtk_orientable_set_orientation (GTK_ORIENTABLE (area), GTK_ORIENTATION_HORIZONTAL); - c = g_object_new (GTK_TYPE_ENTRY_COMPLETION, "cell-area", area, NULL); - g_assert (gtk_cell_layout_get_area (GTK_CELL_LAYOUT (c)) == area); - - g_object_ref_sink (c); - g_object_unref (c); -} - int main (int argc, char *argv[]) { @@ -324,9 +277,5 @@ main (int argc, char *argv[]) g_test_add_func ("/tests/column-new-with-area", test_column_new_with_area); g_test_add_func ("/tests/column-object-new", test_column_object_new); - g_test_add_func ("/tests/completion-new", test_completion_new); - g_test_add_func ("/tests/completion-new-with-area", test_completion_new_with_area); - g_test_add_func ("/tests/completion-object-new", test_completion_object_new); - return g_test_run(); } |