1/* gtkentrycompletion.c
2 * Copyright (C) 2003 Kristian Rietveld <kris@gtk.org>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18/**
19 * GtkEntryCompletion:
20 *
21 * `GtkEntryCompletion` is an auxiliary object to provide completion functionality
22 * for `GtkEntry`.
23 *
24 * It implements the [iface@Gtk.CellLayout] interface, to allow the user
25 * to add extra cells to the `GtkTreeView` with completion matches.
26 *
27 * “Completion functionality” means that when the user modifies the text
28 * in the entry, `GtkEntryCompletion` checks which rows in the model match
29 * the current content of the entry, and displays a list of matches.
30 * By default, the matching is done by comparing the entry text
31 * case-insensitively against the text column of the model (see
32 * [method@Gtk.EntryCompletion.set_text_column]), but this can be overridden
33 * with a custom match function (see [method@Gtk.EntryCompletion.set_match_func]).
34 *
35 * When the user selects a completion, the content of the entry is
36 * updated. By default, the content of the entry is replaced by the
37 * text column of the model, but this can be overridden by connecting
38 * to the [signal@Gtk.EntryCompletion::match-selected] signal and updating the
39 * entry in the signal handler. Note that you should return %TRUE from
40 * the signal handler to suppress the default behaviour.
41 *
42 * To add completion functionality to an entry, use
43 * [method@Gtk.Entry.set_completion].
44 *
45 * `GtkEntryCompletion` uses a [class@Gtk.TreeModelFilter] model to
46 * represent the subset of the entire model that is currently matching.
47 * While the `GtkEntryCompletion` signals
48 * [signal@Gtk.EntryCompletion::match-selected] and
49 * [signal@Gtk.EntryCompletion::cursor-on-match] take the original model
50 * and an iter pointing to that model as arguments, other callbacks and
51 * signals (such as `GtkCellLayoutDataFunc` or
52 * [signal@Gtk.CellArea::apply-attributes)]
53 * will generally take the filter model as argument. As long as you are
54 * only calling [method@Gtk.TreeModel.get], this will make no difference to
55 * you. If for some reason, you need the original model, use
56 * [method@Gtk.TreeModelFilter.get_model]. Don’t forget to use
57 * [method@Gtk.TreeModelFilter.convert_iter_to_child_iter] to obtain a
58 * matching iter.
59 */
60
61#include "config.h"
62
63#include "gtkentrycompletion.h"
64
65#include "gtkentryprivate.h"
66#include "gtktextprivate.h"
67#include "gtkcelllayout.h"
68#include "gtkcellareabox.h"
69
70#include "gtkintl.h"
71#include "gtkcellrenderertext.h"
72#include "gtktreeselection.h"
73#include "gtktreeview.h"
74#include "gtkscrolledwindow.h"
75#include "gtksizerequest.h"
76#include "gtkbox.h"
77#include "gtkpopover.h"
78#include "gtkentry.h"
79#include "gtkmain.h"
80#include "gtkmarshalers.h"
81#include "gtkeventcontrollerfocus.h"
82#include "gtkeventcontrollerkey.h"
83#include "gtkgestureclick.h"
84
85#include "gtkprivate.h"
86#include "gtkwindowprivate.h"
87#include "gtkwidgetprivate.h"
88#include "gtknative.h"
89
90#include <string.h>
91
92#define PAGE_STEP 14
93#define COMPLETION_TIMEOUT 100
94
95/* signals */
96enum
97{
98 INSERT_PREFIX,
99 MATCH_SELECTED,
100 CURSOR_ON_MATCH,
101 NO_MATCHES,
102 LAST_SIGNAL
103};
104
105/* properties */
106enum
107{
108 PROP_0,
109 PROP_MODEL,
110 PROP_MINIMUM_KEY_LENGTH,
111 PROP_TEXT_COLUMN,
112 PROP_INLINE_COMPLETION,
113 PROP_POPUP_COMPLETION,
114 PROP_POPUP_SET_WIDTH,
115 PROP_POPUP_SINGLE_MATCH,
116 PROP_INLINE_SELECTION,
117 PROP_CELL_AREA,
118 NUM_PROPERTIES
119};
120
121
122static void gtk_entry_completion_cell_layout_init (GtkCellLayoutIface *iface);
123static GtkCellArea* gtk_entry_completion_get_area (GtkCellLayout *cell_layout);
124
125static void gtk_entry_completion_constructed (GObject *object);
126static void gtk_entry_completion_set_property (GObject *object,
127 guint prop_id,
128 const GValue *value,
129 GParamSpec *pspec);
130static void gtk_entry_completion_get_property (GObject *object,
131 guint prop_id,
132 GValue *value,
133 GParamSpec *pspec);
134static void gtk_entry_completion_finalize (GObject *object);
135static void gtk_entry_completion_dispose (GObject *object);
136
137static gboolean gtk_entry_completion_visible_func (GtkTreeModel *model,
138 GtkTreeIter *iter,
139 gpointer data);
140static void gtk_entry_completion_list_activated (GtkTreeView *treeview,
141 GtkTreePath *path,
142 GtkTreeViewColumn *column,
143 gpointer user_data);
144static void gtk_entry_completion_selection_changed (GtkTreeSelection *selection,
145 gpointer data);
146
147static gboolean gtk_entry_completion_match_selected (GtkEntryCompletion *completion,
148 GtkTreeModel *model,
149 GtkTreeIter *iter);
150static gboolean gtk_entry_completion_real_insert_prefix (GtkEntryCompletion *completion,
151 const char *prefix);
152static gboolean gtk_entry_completion_cursor_on_match (GtkEntryCompletion *completion,
153 GtkTreeModel *model,
154 GtkTreeIter *iter);
155static gboolean gtk_entry_completion_insert_completion (GtkEntryCompletion *completion,
156 GtkTreeModel *model,
157 GtkTreeIter *iter);
158static void gtk_entry_completion_insert_completion_text (GtkEntryCompletion *completion,
159 const char *text);
160static void connect_completion_signals (GtkEntryCompletion *completion);
161static void disconnect_completion_signals (GtkEntryCompletion *completion);
162
163
164static GParamSpec *entry_completion_props[NUM_PROPERTIES] = { NULL, };
165
166static guint entry_completion_signals[LAST_SIGNAL] = { 0 };
167
168/* GtkBuildable */
169static void gtk_entry_completion_buildable_init (GtkBuildableIface *iface);
170
171G_DEFINE_TYPE_WITH_CODE (GtkEntryCompletion, gtk_entry_completion, G_TYPE_OBJECT,
172 G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
173 gtk_entry_completion_cell_layout_init)
174 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
175 gtk_entry_completion_buildable_init))
176
177
178static void
179gtk_entry_completion_class_init (GtkEntryCompletionClass *klass)
180{
181 GObjectClass *object_class;
182
183 object_class = (GObjectClass *)klass;
184
185 object_class->constructed = gtk_entry_completion_constructed;
186 object_class->set_property = gtk_entry_completion_set_property;
187 object_class->get_property = gtk_entry_completion_get_property;
188 object_class->dispose = gtk_entry_completion_dispose;
189 object_class->finalize = gtk_entry_completion_finalize;
190
191 klass->match_selected = gtk_entry_completion_match_selected;
192 klass->insert_prefix = gtk_entry_completion_real_insert_prefix;
193 klass->cursor_on_match = gtk_entry_completion_cursor_on_match;
194 klass->no_matches = NULL;
195
196 /**
197 * GtkEntryCompletion::insert-prefix:
198 * @widget: the object which received the signal
199 * @prefix: the common prefix of all possible completions
200 *
201 * Emitted when the inline autocompletion is triggered.
202 *
203 * The default behaviour is to make the entry display the
204 * whole prefix and select the newly inserted part.
205 *
206 * Applications may connect to this signal in order to insert only a
207 * smaller part of the @prefix into the entry - e.g. the entry used in
208 * the `GtkFileChooser` inserts only the part of the prefix up to the
209 * next '/'.
210 *
211 * Returns: %TRUE if the signal has been handled
212 */
213 entry_completion_signals[INSERT_PREFIX] =
214 g_signal_new (I_("insert-prefix"),
215 G_TYPE_FROM_CLASS (klass),
216 signal_flags: G_SIGNAL_RUN_LAST,
217 G_STRUCT_OFFSET (GtkEntryCompletionClass, insert_prefix),
218 accumulator: _gtk_boolean_handled_accumulator, NULL,
219 c_marshaller: _gtk_marshal_BOOLEAN__STRING,
220 G_TYPE_BOOLEAN, n_params: 1,
221 G_TYPE_STRING);
222
223 /**
224 * GtkEntryCompletion::match-selected:
225 * @widget: the object which received the signal
226 * @model: the `GtkTreeModel` containing the matches
227 * @iter: a `GtkTreeIter` positioned at the selected match
228 *
229 * Emitted when a match from the list is selected.
230 *
231 * The default behaviour is to replace the contents of the
232 * entry with the contents of the text column in the row
233 * pointed to by @iter.
234 *
235 * Note that @model is the model that was passed to
236 * [method@Gtk.EntryCompletion.set_model].
237 *
238 * Returns: %TRUE if the signal has been handled
239 */
240 entry_completion_signals[MATCH_SELECTED] =
241 g_signal_new (I_("match-selected"),
242 G_TYPE_FROM_CLASS (klass),
243 signal_flags: G_SIGNAL_RUN_LAST,
244 G_STRUCT_OFFSET (GtkEntryCompletionClass, match_selected),
245 accumulator: _gtk_boolean_handled_accumulator, NULL,
246 c_marshaller: _gtk_marshal_BOOLEAN__OBJECT_BOXED,
247 G_TYPE_BOOLEAN, n_params: 2,
248 GTK_TYPE_TREE_MODEL,
249 GTK_TYPE_TREE_ITER);
250
251 /**
252 * GtkEntryCompletion::cursor-on-match:
253 * @widget: the object which received the signal
254 * @model: the `GtkTreeModel` containing the matches
255 * @iter: a `GtkTreeIter` positioned at the selected match
256 *
257 * Emitted when a match from the cursor is on a match of the list.
258 *
259 * The default behaviour is to replace the contents
260 * of the entry with the contents of the text column in the row
261 * pointed to by @iter.
262 *
263 * Note that @model is the model that was passed to
264 * [method@Gtk.EntryCompletion.set_model].
265 *
266 * Returns: %TRUE if the signal has been handled
267 */
268 entry_completion_signals[CURSOR_ON_MATCH] =
269 g_signal_new (I_("cursor-on-match"),
270 G_TYPE_FROM_CLASS (klass),
271 signal_flags: G_SIGNAL_RUN_LAST,
272 G_STRUCT_OFFSET (GtkEntryCompletionClass, cursor_on_match),
273 accumulator: _gtk_boolean_handled_accumulator, NULL,
274 c_marshaller: _gtk_marshal_BOOLEAN__OBJECT_BOXED,
275 G_TYPE_BOOLEAN, n_params: 2,
276 GTK_TYPE_TREE_MODEL,
277 GTK_TYPE_TREE_ITER);
278
279 /**
280 * GtkEntryCompletion::no-matches:
281 * @widget: the object which received the signal
282 *
283 * Emitted when the filter model has zero
284 * number of rows in completion_complete method.
285 *
286 * In other words when `GtkEntryCompletion` is out of suggestions.
287 */
288 entry_completion_signals[NO_MATCHES] =
289 g_signal_new (I_("no-matches"),
290 G_TYPE_FROM_CLASS (klass),
291 signal_flags: G_SIGNAL_RUN_LAST,
292 G_STRUCT_OFFSET (GtkEntryCompletionClass, no_matches),
293 NULL, NULL,
294 NULL,
295 G_TYPE_NONE, n_params: 0);
296
297 entry_completion_props[PROP_MODEL] =
298 g_param_spec_object (name: "model",
299 P_("Completion Model"),
300 P_("The model to find matches in"),
301 GTK_TYPE_TREE_MODEL,
302 GTK_PARAM_READWRITE);
303
304 entry_completion_props[PROP_MINIMUM_KEY_LENGTH] =
305 g_param_spec_int (name: "minimum-key-length",
306 P_("Minimum Key Length"),
307 P_("Minimum length of the search key in order to look up matches"),
308 minimum: 0, G_MAXINT, default_value: 1,
309 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
310
311 /**
312 * GtkEntryCompletion:text-column: (attributes org.gtk.Property.get=gtk_entry_completion_get_text_column org.gtk.Property.set=gtk_entry_completion_set_text_column)
313 *
314 * The column of the model containing the strings.
315 *
316 * Note that the strings must be UTF-8.
317 */
318 entry_completion_props[PROP_TEXT_COLUMN] =
319 g_param_spec_int (name: "text-column",
320 P_("Text column"),
321 P_("The column of the model containing the strings."),
322 minimum: -1, G_MAXINT, default_value: -1,
323 GTK_PARAM_READWRITE);
324
325 /**
326 * GtkEntryCompletion:inline-completion: (attributes org.gtk.Property.get=gtk_entry_completion_get_inline_completion org.gtk.Property.set=gtk_entry_completion_set_inline_completion)
327 *
328 * Determines whether the common prefix of the possible completions
329 * should be inserted automatically in the entry.
330 *
331 * Note that this requires text-column to be set, even if you are
332 * using a custom match function.
333 */
334 entry_completion_props[PROP_INLINE_COMPLETION] =
335 g_param_spec_boolean (name: "inline-completion",
336 P_("Inline completion"),
337 P_("Whether the common prefix should be inserted automatically"),
338 FALSE,
339 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
340
341 /**
342 * GtkEntryCompletion:popup-completion: (attributes org.gtk.Property.get=gtk_entry_completion_get_popup_completion org.gtk.Property.set=gtk_entry_completion_set_popup_completion)
343 *
344 * Determines whether the possible completions should be
345 * shown in a popup window.
346 */
347 entry_completion_props[PROP_POPUP_COMPLETION] =
348 g_param_spec_boolean (name: "popup-completion",
349 P_("Popup completion"),
350 P_("Whether the completions should be shown in a popup window"),
351 TRUE,
352 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
353
354 /**
355 * GtkEntryCompletion:popup-set-width: (attributes org.gtk.Property.get=gtk_entry_completion_get_popup_set_width org.gtk.Property.set=gtk_entry_completion_set_popup_set_width)
356 *
357 * Determines whether the completions popup window will be
358 * resized to the width of the entry.
359 */
360 entry_completion_props[PROP_POPUP_SET_WIDTH] =
361 g_param_spec_boolean (name: "popup-set-width",
362 P_("Popup set width"),
363 P_("If TRUE, the popup window will have the same size as the entry"),
364 TRUE,
365 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
366
367 /**
368 * GtkEntryCompletion:popup-single-match: (attributes org.gtk.Property.get=gtk_entry_completion_get_popup_single_match org.gtk.Property.set=gtk_entry_completion_set_popup_single_match)
369 *
370 * Determines whether the completions popup window will shown
371 * for a single possible completion.
372 *
373 * You probably want to set this to %FALSE if you are using
374 * [property@Gtk.EntryCompletion:inline-completion].
375 */
376 entry_completion_props[PROP_POPUP_SINGLE_MATCH] =
377 g_param_spec_boolean (name: "popup-single-match",
378 P_("Popup single match"),
379 P_("If TRUE, the popup window will appear for a single match."),
380 TRUE,
381 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
382
383 /**
384 * GtkEntryCompletion:inline-selection: (attributes org.gtk.Property.get=gtk_entry_completion_get_inline_selection org.gtk.Property.set=gtk_entry_completion_set_inline_selection)
385 *
386 * Determines whether the possible completions on the popup
387 * will appear in the entry as you navigate through them.
388 */
389 entry_completion_props[PROP_INLINE_SELECTION] =
390 g_param_spec_boolean (name: "inline-selection",
391 P_("Inline selection"),
392 P_("Your description here"),
393 FALSE,
394 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
395
396 /**
397 * GtkEntryCompletion:cell-area:
398 *
399 * The `GtkCellArea` used to layout cell renderers in the treeview column.
400 *
401 * If no area is specified when creating the entry completion with
402 * [ctor@Gtk.EntryCompletion.new_with_area], a horizontally oriented
403 * [class@Gtk.CellAreaBox] will be used.
404 */
405 entry_completion_props[PROP_CELL_AREA] =
406 g_param_spec_object (name: "cell-area",
407 P_("Cell Area"),
408 P_("The GtkCellArea used to layout cells"),
409 GTK_TYPE_CELL_AREA,
410 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
411
412 g_object_class_install_properties (oclass: object_class, n_pspecs: NUM_PROPERTIES, pspecs: entry_completion_props);
413}
414
415
416static void
417gtk_entry_completion_buildable_custom_tag_end (GtkBuildable *buildable,
418 GtkBuilder *builder,
419 GObject *child,
420 const char *tagname,
421 gpointer data)
422{
423 /* Just ignore the boolean return from here */
424 _gtk_cell_layout_buildable_custom_tag_end (buildable, builder, child, tagname, data);
425}
426
427static void
428gtk_entry_completion_buildable_init (GtkBuildableIface *iface)
429{
430 iface->add_child = _gtk_cell_layout_buildable_add_child;
431 iface->custom_tag_start = _gtk_cell_layout_buildable_custom_tag_start;
432 iface->custom_tag_end = gtk_entry_completion_buildable_custom_tag_end;
433}
434
435static void
436gtk_entry_completion_cell_layout_init (GtkCellLayoutIface *iface)
437{
438 iface->get_area = gtk_entry_completion_get_area;
439}
440
441static void
442gtk_entry_completion_init (GtkEntryCompletion *completion)
443{
444 completion->minimum_key_length = 1;
445 completion->text_column = -1;
446 completion->has_completion = FALSE;
447 completion->inline_completion = FALSE;
448 completion->popup_completion = TRUE;
449 completion->popup_set_width = TRUE;
450 completion->popup_single_match = TRUE;
451 completion->inline_selection = FALSE;
452
453 completion->filter_model = NULL;
454}
455
456static gboolean
457propagate_to_entry (GtkEventControllerKey *key,
458 guint keyval,
459 guint keycode,
460 GdkModifierType modifiers,
461 GtkEntryCompletion *completion)
462{
463 GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (completion->entry));
464
465 return gtk_event_controller_key_forward (controller: key, GTK_WIDGET (text));
466}
467
468static void
469gtk_entry_completion_constructed (GObject *object)
470{
471 GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object);
472 GtkTreeSelection *sel;
473 GtkEventController *controller;
474
475 G_OBJECT_CLASS (gtk_entry_completion_parent_class)->constructed (object);
476
477 if (!completion->cell_area)
478 {
479 completion->cell_area = gtk_cell_area_box_new ();
480 g_object_ref_sink (completion->cell_area);
481 }
482
483 /* completions */
484 completion->tree_view = gtk_tree_view_new ();
485 g_signal_connect (completion->tree_view, "row-activated",
486 G_CALLBACK (gtk_entry_completion_list_activated),
487 completion);
488
489 gtk_tree_view_set_enable_search (GTK_TREE_VIEW (completion->tree_view), FALSE);
490 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (completion->tree_view), FALSE);
491 gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (completion->tree_view), TRUE);
492 gtk_tree_view_set_activate_on_single_click (GTK_TREE_VIEW (completion->tree_view), TRUE);
493
494 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->tree_view));
495 gtk_tree_selection_set_mode (selection: sel, type: GTK_SELECTION_SINGLE);
496 gtk_tree_selection_unselect_all (selection: sel);
497 g_signal_connect (sel, "changed",
498 G_CALLBACK (gtk_entry_completion_selection_changed),
499 completion);
500 completion->first_sel_changed = TRUE;
501
502 completion->column = gtk_tree_view_column_new_with_area (area: completion->cell_area);
503 gtk_tree_view_append_column (GTK_TREE_VIEW (completion->tree_view), column: completion->column);
504
505 completion->scrolled_window = gtk_scrolled_window_new ();
506 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (completion->scrolled_window),
507 hscrollbar_policy: GTK_POLICY_NEVER,
508 vscrollbar_policy: GTK_POLICY_AUTOMATIC);
509
510 /* a nasty hack to get the completions treeview to size nicely */
511 gtk_widget_set_size_request (widget: gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (completion->scrolled_window)),
512 width: -1, height: 0);
513
514 /* pack it all */
515 completion->popup_window = gtk_popover_new ();
516 gtk_popover_set_position (GTK_POPOVER (completion->popup_window), position: GTK_POS_BOTTOM);
517 gtk_popover_set_autohide (GTK_POPOVER (completion->popup_window), FALSE);
518 gtk_popover_set_has_arrow (GTK_POPOVER (completion->popup_window), FALSE);
519 gtk_widget_add_css_class (widget: completion->popup_window, css_class: "entry-completion");
520
521 controller = gtk_event_controller_key_new ();
522 g_signal_connect (controller, "key-pressed",
523 G_CALLBACK (propagate_to_entry), completion);
524 g_signal_connect (controller, "key-released",
525 G_CALLBACK (propagate_to_entry), completion);
526 gtk_widget_add_controller (widget: completion->popup_window, controller);
527
528 controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
529 g_signal_connect_swapped (controller, "released",
530 G_CALLBACK (_gtk_entry_completion_popdown),
531 completion);
532 gtk_widget_add_controller (widget: completion->popup_window, controller);
533
534 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (completion->scrolled_window),
535 child: completion->tree_view);
536 gtk_widget_set_hexpand (widget: completion->scrolled_window, TRUE);
537 gtk_widget_set_vexpand (widget: completion->scrolled_window, TRUE);
538 gtk_popover_set_child (GTK_POPOVER (completion->popup_window), child: completion->scrolled_window);
539}
540
541
542static void
543gtk_entry_completion_set_property (GObject *object,
544 guint prop_id,
545 const GValue *value,
546 GParamSpec *pspec)
547{
548 GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object);
549 GtkCellArea *area;
550
551 switch (prop_id)
552 {
553 case PROP_MODEL:
554 gtk_entry_completion_set_model (completion,
555 model: g_value_get_object (value));
556 break;
557
558 case PROP_MINIMUM_KEY_LENGTH:
559 gtk_entry_completion_set_minimum_key_length (completion,
560 length: g_value_get_int (value));
561 break;
562
563 case PROP_TEXT_COLUMN:
564 completion->text_column = g_value_get_int (value);
565 break;
566
567 case PROP_INLINE_COMPLETION:
568 gtk_entry_completion_set_inline_completion (completion,
569 inline_completion: g_value_get_boolean (value));
570 break;
571
572 case PROP_POPUP_COMPLETION:
573 gtk_entry_completion_set_popup_completion (completion,
574 popup_completion: g_value_get_boolean (value));
575 break;
576
577 case PROP_POPUP_SET_WIDTH:
578 gtk_entry_completion_set_popup_set_width (completion,
579 popup_set_width: g_value_get_boolean (value));
580 break;
581
582 case PROP_POPUP_SINGLE_MATCH:
583 gtk_entry_completion_set_popup_single_match (completion,
584 popup_single_match: g_value_get_boolean (value));
585 break;
586
587 case PROP_INLINE_SELECTION:
588 gtk_entry_completion_set_inline_selection (completion,
589 inline_selection: g_value_get_boolean (value));
590 break;
591
592 case PROP_CELL_AREA:
593 /* Construct-only, can only be assigned once */
594 area = g_value_get_object (value);
595 if (area)
596 {
597 if (completion->cell_area != NULL)
598 {
599 g_warning ("cell-area has already been set, ignoring construct property");
600 g_object_ref_sink (area);
601 g_object_unref (object: area);
602 }
603 else
604 completion->cell_area = g_object_ref_sink (area);
605 }
606 break;
607
608 default:
609 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
610 break;
611 }
612}
613
614static void
615gtk_entry_completion_get_property (GObject *object,
616 guint prop_id,
617 GValue *value,
618 GParamSpec *pspec)
619{
620 GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object);
621
622 switch (prop_id)
623 {
624 case PROP_MODEL:
625 g_value_set_object (value,
626 v_object: gtk_entry_completion_get_model (completion));
627 break;
628
629 case PROP_MINIMUM_KEY_LENGTH:
630 g_value_set_int (value, v_int: gtk_entry_completion_get_minimum_key_length (completion));
631 break;
632
633 case PROP_TEXT_COLUMN:
634 g_value_set_int (value, v_int: gtk_entry_completion_get_text_column (completion));
635 break;
636
637 case PROP_INLINE_COMPLETION:
638 g_value_set_boolean (value, v_boolean: gtk_entry_completion_get_inline_completion (completion));
639 break;
640
641 case PROP_POPUP_COMPLETION:
642 g_value_set_boolean (value, v_boolean: gtk_entry_completion_get_popup_completion (completion));
643 break;
644
645 case PROP_POPUP_SET_WIDTH:
646 g_value_set_boolean (value, v_boolean: gtk_entry_completion_get_popup_set_width (completion));
647 break;
648
649 case PROP_POPUP_SINGLE_MATCH:
650 g_value_set_boolean (value, v_boolean: gtk_entry_completion_get_popup_single_match (completion));
651 break;
652
653 case PROP_INLINE_SELECTION:
654 g_value_set_boolean (value, v_boolean: gtk_entry_completion_get_inline_selection (completion));
655 break;
656
657 case PROP_CELL_AREA:
658 g_value_set_object (value, v_object: completion->cell_area);
659 break;
660
661 default:
662 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
663 break;
664 }
665}
666
667static void
668gtk_entry_completion_finalize (GObject *object)
669{
670 GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object);
671
672 g_free (mem: completion->case_normalized_key);
673 g_free (mem: completion->completion_prefix);
674
675 if (completion->match_notify)
676 (* completion->match_notify) (completion->match_data);
677
678 G_OBJECT_CLASS (gtk_entry_completion_parent_class)->finalize (object);
679}
680
681static void
682gtk_entry_completion_dispose (GObject *object)
683{
684 GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object);
685
686 if (completion->entry)
687 gtk_entry_set_completion (GTK_ENTRY (completion->entry), NULL);
688
689 g_clear_object (&completion->cell_area);
690
691 G_OBJECT_CLASS (gtk_entry_completion_parent_class)->dispose (object);
692}
693
694/* implement cell layout interface (only need to return the underlying cell area) */
695static GtkCellArea*
696gtk_entry_completion_get_area (GtkCellLayout *cell_layout)
697{
698 GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (cell_layout);
699
700 if (G_UNLIKELY (!completion->cell_area))
701 {
702 completion->cell_area = gtk_cell_area_box_new ();
703 g_object_ref_sink (completion->cell_area);
704 }
705
706 return completion->cell_area;
707}
708
709/* all those callbacks */
710static gboolean
711gtk_entry_completion_default_completion_func (GtkEntryCompletion *completion,
712 const char *key,
713 GtkTreeIter *iter,
714 gpointer user_data)
715{
716 char *item = NULL;
717 char *normalized_string;
718 char *case_normalized_string;
719
720 gboolean ret = FALSE;
721
722 GtkTreeModel *model;
723
724 model = gtk_tree_model_filter_get_model (filter: completion->filter_model);
725
726 g_return_val_if_fail (gtk_tree_model_get_column_type (model, completion->text_column) == G_TYPE_STRING,
727 FALSE);
728
729 gtk_tree_model_get (tree_model: model, iter,
730 completion->text_column, &item,
731 -1);
732
733 if (item != NULL)
734 {
735 normalized_string = g_utf8_normalize (str: item, len: -1, mode: G_NORMALIZE_ALL);
736
737 if (normalized_string != NULL)
738 {
739 case_normalized_string = g_utf8_casefold (str: normalized_string, len: -1);
740
741 if (!strncmp (s1: key, s2: case_normalized_string, n: strlen (s: key)))
742 ret = TRUE;
743
744 g_free (mem: case_normalized_string);
745 }
746 g_free (mem: normalized_string);
747 }
748 g_free (mem: item);
749
750 return ret;
751}
752
753static gboolean
754gtk_entry_completion_visible_func (GtkTreeModel *model,
755 GtkTreeIter *iter,
756 gpointer data)
757{
758 gboolean ret = FALSE;
759
760 GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (data);
761
762 if (!completion->case_normalized_key)
763 return ret;
764
765 if (completion->match_func)
766 ret = (* completion->match_func) (completion,
767 completion->case_normalized_key,
768 iter,
769 completion->match_data);
770 else if (completion->text_column >= 0)
771 ret = gtk_entry_completion_default_completion_func (completion,
772 key: completion->case_normalized_key,
773 iter,
774 NULL);
775
776 return ret;
777}
778
779static void
780gtk_entry_completion_list_activated (GtkTreeView *treeview,
781 GtkTreePath *path,
782 GtkTreeViewColumn *column,
783 gpointer user_data)
784{
785 GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data);
786 GtkTreeIter iter;
787 gboolean entry_set;
788 GtkTreeModel *model;
789 GtkTreeIter child_iter;
790 GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (completion->entry));
791
792 gtk_tree_model_get_iter (GTK_TREE_MODEL (completion->filter_model), iter: &iter, path);
793 gtk_tree_model_filter_convert_iter_to_child_iter (filter: completion->filter_model,
794 child_iter: &child_iter,
795 filter_iter: &iter);
796 model = gtk_tree_model_filter_get_model (filter: completion->filter_model);
797
798 g_signal_handler_block (instance: text, handler_id: completion->changed_id);
799 g_signal_emit (instance: completion, signal_id: entry_completion_signals[MATCH_SELECTED],
800 detail: 0, model, &child_iter, &entry_set);
801 g_signal_handler_unblock (instance: text, handler_id: completion->changed_id);
802
803 _gtk_entry_completion_popdown (completion);
804}
805
806static void
807gtk_entry_completion_selection_changed (GtkTreeSelection *selection,
808 gpointer data)
809{
810 GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (data);
811
812 if (completion->first_sel_changed)
813 {
814 completion->first_sel_changed = FALSE;
815 if (gtk_widget_is_focus (widget: completion->tree_view))
816 gtk_tree_selection_unselect_all (selection);
817 }
818}
819
820/* public API */
821
822/**
823 * gtk_entry_completion_new:
824 *
825 * Creates a new `GtkEntryCompletion` object.
826 *
827 * Returns: A newly created `GtkEntryCompletion` object
828 */
829GtkEntryCompletion *
830gtk_entry_completion_new (void)
831{
832 GtkEntryCompletion *completion;
833
834 completion = g_object_new (GTK_TYPE_ENTRY_COMPLETION, NULL);
835
836 return completion;
837}
838
839/**
840 * gtk_entry_completion_new_with_area:
841 * @area: the `GtkCellArea` used to layout cells
842 *
843 * Creates a new `GtkEntryCompletion` object using the
844 * specified @area.
845 *
846 * The `GtkCellArea` is used to layout cells in the underlying
847 * `GtkTreeViewColumn` for the drop-down menu.
848 *
849 * Returns: A newly created `GtkEntryCompletion` object
850 */
851GtkEntryCompletion *
852gtk_entry_completion_new_with_area (GtkCellArea *area)
853{
854 GtkEntryCompletion *completion;
855
856 completion = g_object_new (GTK_TYPE_ENTRY_COMPLETION, first_property_name: "cell-area", area, NULL);
857
858 return completion;
859}
860
861/**
862 * gtk_entry_completion_get_entry:
863 * @completion: a `GtkEntryCompletion`
864 *
865 * Gets the entry @completion has been attached to.
866 *
867 * Returns: (transfer none): The entry @completion has been attached to
868 */
869GtkWidget *
870gtk_entry_completion_get_entry (GtkEntryCompletion *completion)
871{
872 g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), NULL);
873
874 return completion->entry;
875}
876
877/**
878 * gtk_entry_completion_set_model: (attributes org.gtk.Method.set_property=model)
879 * @completion: a `GtkEntryCompletion`
880 * @model: (nullable): the `GtkTreeModel`
881 *
882 * Sets the model for a `GtkEntryCompletion`.
883 *
884 * If @completion already has a model set, it will remove it
885 * before setting the new model. If model is %NULL, then it
886 * will unset the model.
887 */
888void
889gtk_entry_completion_set_model (GtkEntryCompletion *completion,
890 GtkTreeModel *model)
891{
892 g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion));
893 g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
894
895 if (!model)
896 {
897 gtk_tree_view_set_model (GTK_TREE_VIEW (completion->tree_view),
898 NULL);
899 _gtk_entry_completion_popdown (completion);
900 completion->filter_model = NULL;
901 return;
902 }
903
904 /* code will unref the old filter model (if any) */
905 completion->filter_model =
906 GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (model, NULL));
907 gtk_tree_model_filter_set_visible_func (filter: completion->filter_model,
908 func: gtk_entry_completion_visible_func,
909 data: completion,
910 NULL);
911
912 gtk_tree_view_set_model (GTK_TREE_VIEW (completion->tree_view),
913 GTK_TREE_MODEL (completion->filter_model));
914 g_object_unref (object: completion->filter_model);
915
916 g_object_notify_by_pspec (G_OBJECT (completion), pspec: entry_completion_props[PROP_MODEL]);
917
918 if (gtk_widget_get_visible (widget: completion->popup_window))
919 _gtk_entry_completion_resize_popup (completion);
920}
921
922/**
923 * gtk_entry_completion_get_model: (attributes org.gtk.Method.get_property=model)
924 * @completion: a `GtkEntryCompletion`
925 *
926 * Returns the model the `GtkEntryCompletion` is using as data source.
927 *
928 * Returns %NULL if the model is unset.
929 *
930 * Returns: (nullable) (transfer none): A `GtkTreeModel`
931 */
932GtkTreeModel *
933gtk_entry_completion_get_model (GtkEntryCompletion *completion)
934{
935 g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), NULL);
936
937 if (!completion->filter_model)
938 return NULL;
939
940 return gtk_tree_model_filter_get_model (filter: completion->filter_model);
941}
942
943/**
944 * gtk_entry_completion_set_match_func:
945 * @completion: a `GtkEntryCompletion`
946 * @func: the `GtkEntryCompletion`MatchFunc to use
947 * @func_data: user data for @func
948 * @func_notify: destroy notify for @func_data.
949 *
950 * Sets the match function for @completion to be @func.
951 *
952 * The match function is used to determine if a row should or
953 * should not be in the completion list.
954 */
955void
956gtk_entry_completion_set_match_func (GtkEntryCompletion *completion,
957 GtkEntryCompletionMatchFunc func,
958 gpointer func_data,
959 GDestroyNotify func_notify)
960{
961 g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion));
962
963 if (completion->match_notify)
964 (* completion->match_notify) (completion->match_data);
965
966 completion->match_func = func;
967 completion->match_data = func_data;
968 completion->match_notify = func_notify;
969}
970
971/**
972 * gtk_entry_completion_set_minimum_key_length:
973 * @completion: a `GtkEntryCompletion`
974 * @length: the minimum length of the key in order to start completing
975 *
976 * Requires the length of the search key for @completion to be at least
977 * @length.
978 *
979 * This is useful for long lists, where completing using a small
980 * key takes a lot of time and will come up with meaningless results anyway
981 * (ie, a too large dataset).
982 */
983void
984gtk_entry_completion_set_minimum_key_length (GtkEntryCompletion *completion,
985 int length)
986{
987 g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion));
988 g_return_if_fail (length >= 0);
989
990 if (completion->minimum_key_length != length)
991 {
992 completion->minimum_key_length = length;
993
994 g_object_notify_by_pspec (G_OBJECT (completion),
995 pspec: entry_completion_props[PROP_MINIMUM_KEY_LENGTH]);
996 }
997}
998
999/**
1000 * gtk_entry_completion_get_minimum_key_length:
1001 * @completion: a `GtkEntryCompletion`
1002 *
1003 * Returns the minimum key length as set for @completion.
1004 *
1005 * Returns: The currently used minimum key length
1006 */
1007int
1008gtk_entry_completion_get_minimum_key_length (GtkEntryCompletion *completion)
1009{
1010 g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), 0);
1011
1012 return completion->minimum_key_length;
1013}
1014
1015/**
1016 * gtk_entry_completion_complete:
1017 * @completion: a `GtkEntryCompletion`
1018 *
1019 * Requests a completion operation, or in other words a refiltering of the
1020 * current list with completions, using the current key.
1021 *
1022 * The completion list view will be updated accordingly.
1023 */
1024void
1025gtk_entry_completion_complete (GtkEntryCompletion *completion)
1026{
1027 char *tmp;
1028 GtkTreeIter iter;
1029
1030 g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion));
1031 g_return_if_fail (GTK_IS_ENTRY (completion->entry));
1032
1033 if (!completion->filter_model)
1034 return;
1035
1036 g_free (mem: completion->case_normalized_key);
1037
1038 tmp = g_utf8_normalize (str: gtk_editable_get_text (GTK_EDITABLE (completion->entry)),
1039 len: -1, mode: G_NORMALIZE_ALL);
1040 completion->case_normalized_key = g_utf8_casefold (str: tmp, len: -1);
1041 g_free (mem: tmp);
1042
1043 gtk_tree_model_filter_refilter (filter: completion->filter_model);
1044
1045 if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (completion->filter_model), iter: &iter))
1046 g_signal_emit (instance: completion, signal_id: entry_completion_signals[NO_MATCHES], detail: 0);
1047
1048 if (gtk_widget_get_visible (widget: completion->popup_window))
1049 _gtk_entry_completion_resize_popup (completion);
1050}
1051
1052/**
1053 * gtk_entry_completion_set_text_column: (attributes org.gtk.Method.set_property=text-column)
1054 * @completion: a `GtkEntryCompletion`
1055 * @column: the column in the model of @completion to get strings from
1056 *
1057 * Convenience function for setting up the most used case of this code: a
1058 * completion list with just strings.
1059 *
1060 * This function will set up @completion
1061 * to have a list displaying all (and just) strings in the completion list,
1062 * and to get those strings from @column in the model of @completion.
1063 *
1064 * This functions creates and adds a `GtkCellRendererText` for the selected
1065 * column. If you need to set the text column, but don't want the cell
1066 * renderer, use g_object_set() to set the
1067 * [property@Gtk.EntryCompletion:text-column] property directly.
1068 */
1069void
1070gtk_entry_completion_set_text_column (GtkEntryCompletion *completion,
1071 int column)
1072{
1073 GtkCellRenderer *cell;
1074
1075 g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion));
1076 g_return_if_fail (column >= 0);
1077
1078 if (completion->text_column == column)
1079 return;
1080
1081 completion->text_column = column;
1082
1083 cell = gtk_cell_renderer_text_new ();
1084 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion),
1085 cell, TRUE);
1086 gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion),
1087 cell,
1088 attribute: "text", column);
1089
1090 g_object_notify_by_pspec (G_OBJECT (completion), pspec: entry_completion_props[PROP_TEXT_COLUMN]);
1091}
1092
1093/**
1094 * gtk_entry_completion_get_text_column: (attributes org.gtk.Method.get_property=text-column)
1095 * @completion: a `GtkEntryCompletion`
1096 *
1097 * Returns the column in the model of @completion to get strings from.
1098 *
1099 * Returns: the column containing the strings
1100 */
1101int
1102gtk_entry_completion_get_text_column (GtkEntryCompletion *completion)
1103{
1104 g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), -1);
1105
1106 return completion->text_column;
1107}
1108
1109/* private */
1110
1111/* some nasty size requisition */
1112void
1113_gtk_entry_completion_resize_popup (GtkEntryCompletion *completion)
1114{
1115 GtkAllocation allocation;
1116 int matches, items, height;
1117 GdkSurface *surface;
1118 GtkRequisition entry_req;
1119 GtkRequisition tree_req;
1120 int width;
1121
1122 surface = gtk_native_get_surface (self: gtk_widget_get_native (widget: completion->entry));
1123
1124 if (!surface)
1125 return;
1126
1127 if (!completion->filter_model)
1128 return;
1129
1130 gtk_widget_get_surface_allocation (widget: completion->entry, allocation: &allocation);
1131 gtk_widget_get_preferred_size (widget: completion->entry,
1132 minimum_size: &entry_req, NULL);
1133
1134 matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->filter_model), NULL);
1135
1136 /* Call get preferred size on the on the tree view to force it to validate its
1137 * cells before calling into the cell size functions.
1138 */
1139 gtk_widget_get_preferred_size (widget: completion->tree_view,
1140 minimum_size: &tree_req, NULL);
1141 gtk_tree_view_column_cell_get_size (tree_column: completion->column,
1142 NULL, NULL, NULL, height: &height);
1143
1144 gtk_widget_realize (widget: completion->tree_view);
1145
1146 items = MIN (matches, 10);
1147
1148 if (items <= 0)
1149 gtk_widget_hide (widget: completion->scrolled_window);
1150 else
1151 gtk_widget_show (widget: completion->scrolled_window);
1152
1153 if (completion->popup_set_width)
1154 width = allocation.width;
1155 else
1156 width = -1;
1157
1158 gtk_tree_view_columns_autosize (GTK_TREE_VIEW (completion->tree_view));
1159 gtk_scrolled_window_set_min_content_width (GTK_SCROLLED_WINDOW (completion->scrolled_window), width);
1160 gtk_widget_set_size_request (widget: completion->popup_window, width, height: -1);
1161 gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (completion->scrolled_window), height: items * height);
1162
1163 gtk_popover_present (GTK_POPOVER (completion->popup_window));
1164}
1165
1166static void
1167gtk_entry_completion_popup (GtkEntryCompletion *completion)
1168{
1169 GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (completion->entry));
1170
1171 if (gtk_widget_get_mapped (widget: completion->popup_window))
1172 return;
1173
1174 if (!gtk_widget_get_mapped (GTK_WIDGET (text)))
1175 return;
1176
1177 if (!gtk_widget_has_focus (GTK_WIDGET (text)))
1178 return;
1179
1180 /* default on no match */
1181 completion->current_selected = -1;
1182
1183 gtk_widget_realize (widget: completion->popup_window);
1184
1185 _gtk_entry_completion_resize_popup (completion);
1186
1187 if (completion->filter_model)
1188 {
1189 GtkTreePath *path;
1190
1191 path = gtk_tree_path_new_from_indices (first_index: 0, -1);
1192 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (completion->tree_view), path,
1193 NULL, FALSE, row_align: 0.0, col_align: 0.0);
1194 gtk_tree_path_free (path);
1195 }
1196
1197 gtk_popover_popup (GTK_POPOVER (completion->popup_window));
1198}
1199
1200void
1201_gtk_entry_completion_popdown (GtkEntryCompletion *completion)
1202{
1203 if (!gtk_widget_get_mapped (widget: completion->popup_window))
1204 return;
1205
1206 gtk_popover_popdown (GTK_POPOVER (completion->popup_window));
1207}
1208
1209static gboolean
1210gtk_entry_completion_match_selected (GtkEntryCompletion *completion,
1211 GtkTreeModel *model,
1212 GtkTreeIter *iter)
1213{
1214 g_assert (completion->entry != NULL);
1215
1216 char *str = NULL;
1217
1218 gtk_tree_model_get (tree_model: model, iter, completion->text_column, &str, -1);
1219 gtk_editable_set_text (GTK_EDITABLE (completion->entry), text: str ? str : "");
1220
1221 /* move cursor to the end */
1222 gtk_editable_set_position (GTK_EDITABLE (completion->entry), position: -1);
1223
1224 g_free (mem: str);
1225
1226 return TRUE;
1227}
1228
1229static gboolean
1230gtk_entry_completion_cursor_on_match (GtkEntryCompletion *completion,
1231 GtkTreeModel *model,
1232 GtkTreeIter *iter)
1233{
1234 g_assert (completion->entry != NULL);
1235
1236 gtk_entry_completion_insert_completion (completion, model, iter);
1237
1238 return TRUE;
1239}
1240
1241/**
1242 * gtk_entry_completion_compute_prefix:
1243 * @completion: the entry completion
1244 * @key: The text to complete for
1245 *
1246 * Computes the common prefix that is shared by all rows in @completion
1247 * that start with @key.
1248 *
1249 * If no row matches @key, %NULL will be returned.
1250 * Note that a text column must have been set for this function to work,
1251 * see [method@Gtk.EntryCompletion.set_text_column] for details.
1252 *
1253 * Returns: (nullable) (transfer full): The common prefix all rows
1254 * starting with @key
1255 */
1256char *
1257gtk_entry_completion_compute_prefix (GtkEntryCompletion *completion,
1258 const char *key)
1259{
1260 GtkTreeIter iter;
1261 char *prefix = NULL;
1262 gboolean valid;
1263
1264 if (completion->text_column < 0)
1265 return NULL;
1266
1267 valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (completion->filter_model),
1268 iter: &iter);
1269
1270 while (valid)
1271 {
1272 char *text;
1273
1274 gtk_tree_model_get (GTK_TREE_MODEL (completion->filter_model),
1275 iter: &iter, completion->text_column, &text,
1276 -1);
1277
1278 if (text && g_str_has_prefix (str: text, prefix: key))
1279 {
1280 if (!prefix)
1281 prefix = g_strdup (str: text);
1282 else
1283 {
1284 char *p = prefix;
1285 char *q = text;
1286
1287 while (*p && *p == *q)
1288 {
1289 p++;
1290 q++;
1291 }
1292
1293 *p = '\0';
1294
1295 if (p > prefix)
1296 {
1297 /* strip a partial multibyte character */
1298 q = g_utf8_find_prev_char (str: prefix, p);
1299 switch (g_utf8_get_char_validated (p: q, max_len: p - q))
1300 {
1301 case (gunichar)-2:
1302 case (gunichar)-1:
1303 *q = 0;
1304 break;
1305 default: ;
1306 }
1307 }
1308 }
1309 }
1310
1311 g_free (mem: text);
1312 valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (completion->filter_model),
1313 iter: &iter);
1314 }
1315
1316 return prefix;
1317}
1318
1319
1320static gboolean
1321gtk_entry_completion_real_insert_prefix (GtkEntryCompletion *completion,
1322 const char *prefix)
1323{
1324 g_assert (completion->entry != NULL);
1325
1326 if (prefix)
1327 {
1328 int key_len;
1329 int prefix_len;
1330 const char *key;
1331
1332 prefix_len = g_utf8_strlen (p: prefix, max: -1);
1333
1334 key = gtk_editable_get_text (GTK_EDITABLE (completion->entry));
1335 key_len = g_utf8_strlen (p: key, max: -1);
1336
1337 if (prefix_len > key_len)
1338 {
1339 int pos = prefix_len;
1340
1341 gtk_editable_insert_text (GTK_EDITABLE (completion->entry),
1342 text: prefix + strlen (s: key), length: -1, position: &pos);
1343 gtk_editable_select_region (GTK_EDITABLE (completion->entry),
1344 start_pos: key_len, end_pos: prefix_len);
1345
1346 completion->has_completion = TRUE;
1347 }
1348 }
1349
1350 return TRUE;
1351}
1352
1353/**
1354 * gtk_entry_completion_get_completion_prefix:
1355 * @completion: a `GtkEntryCompletion`
1356 *
1357 * Get the original text entered by the user that triggered
1358 * the completion or %NULL if there’s no completion ongoing.
1359 *
1360 * Returns: (nullable): the prefix for the current completion
1361 */
1362const char *
1363gtk_entry_completion_get_completion_prefix (GtkEntryCompletion *completion)
1364{
1365 g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), NULL);
1366
1367 return completion->completion_prefix;
1368}
1369
1370static void
1371gtk_entry_completion_insert_completion_text (GtkEntryCompletion *completion,
1372 const char *new_text)
1373{
1374 int len;
1375 GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (completion->entry));
1376 GtkEntryBuffer *buffer = gtk_text_get_buffer (self: text);
1377
1378 if (completion->changed_id > 0)
1379 g_signal_handler_block (instance: text, handler_id: completion->changed_id);
1380
1381 if (completion->insert_text_id > 0)
1382 g_signal_handler_block (instance: buffer, handler_id: completion->insert_text_id);
1383
1384 gtk_editable_set_text (GTK_EDITABLE (completion->entry), text: new_text);
1385
1386 len = g_utf8_strlen (p: completion->completion_prefix, max: -1);
1387 gtk_editable_select_region (GTK_EDITABLE (completion->entry), start_pos: len, end_pos: -1);
1388
1389 if (completion->changed_id > 0)
1390 g_signal_handler_unblock (instance: text, handler_id: completion->changed_id);
1391
1392 if (completion->insert_text_id > 0)
1393 g_signal_handler_unblock (instance: buffer, handler_id: completion->insert_text_id);
1394}
1395
1396static gboolean
1397gtk_entry_completion_insert_completion (GtkEntryCompletion *completion,
1398 GtkTreeModel *model,
1399 GtkTreeIter *iter)
1400{
1401 char *str = NULL;
1402
1403 if (completion->text_column < 0)
1404 return FALSE;
1405
1406 gtk_tree_model_get (tree_model: model, iter,
1407 completion->text_column, &str,
1408 -1);
1409
1410 gtk_entry_completion_insert_completion_text (completion, new_text: str);
1411
1412 g_free (mem: str);
1413
1414 return TRUE;
1415}
1416
1417/**
1418 * gtk_entry_completion_insert_prefix:
1419 * @completion: a `GtkEntryCompletion`
1420 *
1421 * Requests a prefix insertion.
1422 */
1423void
1424gtk_entry_completion_insert_prefix (GtkEntryCompletion *completion)
1425{
1426 g_return_if_fail (completion->entry != NULL);
1427
1428 gboolean done;
1429 char *prefix;
1430 GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (completion->entry));
1431 GtkEntryBuffer *buffer = gtk_text_get_buffer (self: text);
1432
1433 if (completion->insert_text_id > 0)
1434 g_signal_handler_block (instance: buffer, handler_id: completion->insert_text_id);
1435
1436 prefix = gtk_entry_completion_compute_prefix (completion,
1437 key: gtk_editable_get_text (GTK_EDITABLE (completion->entry)));
1438
1439 if (prefix)
1440 {
1441 g_signal_emit (instance: completion, signal_id: entry_completion_signals[INSERT_PREFIX],
1442 detail: 0, prefix, &done);
1443 g_free (mem: prefix);
1444 }
1445
1446 if (completion->insert_text_id > 0)
1447 g_signal_handler_unblock (instance: buffer, handler_id: completion->insert_text_id);
1448}
1449
1450/**
1451 * gtk_entry_completion_set_inline_completion: (attributes org.gtk.Method.set_property=inline-completion)
1452 * @completion: a `GtkEntryCompletion`
1453 * @inline_completion: %TRUE to do inline completion
1454 *
1455 * Sets whether the common prefix of the possible completions should
1456 * be automatically inserted in the entry.
1457 */
1458void
1459gtk_entry_completion_set_inline_completion (GtkEntryCompletion *completion,
1460 gboolean inline_completion)
1461{
1462 g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion));
1463
1464 inline_completion = inline_completion != FALSE;
1465
1466 if (completion->inline_completion != inline_completion)
1467 {
1468 completion->inline_completion = inline_completion;
1469
1470 g_object_notify_by_pspec (G_OBJECT (completion), pspec: entry_completion_props[PROP_INLINE_COMPLETION]);
1471 }
1472}
1473
1474/**
1475 * gtk_entry_completion_get_inline_completion: (attributes org.gtk.Method.get_property=inline-completion)
1476 * @completion: a `GtkEntryCompletion`
1477 *
1478 * Returns whether the common prefix of the possible completions should
1479 * be automatically inserted in the entry.
1480 *
1481 * Returns: %TRUE if inline completion is turned on
1482 */
1483gboolean
1484gtk_entry_completion_get_inline_completion (GtkEntryCompletion *completion)
1485{
1486 g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), FALSE);
1487
1488 return completion->inline_completion;
1489}
1490
1491/**
1492 * gtk_entry_completion_set_popup_completion: (attributes org.gtk.Method.set_property=popup-completion)
1493 * @completion: a `GtkEntryCompletion`
1494 * @popup_completion: %TRUE to do popup completion
1495 *
1496 * Sets whether the completions should be presented in a popup window.
1497 */
1498void
1499gtk_entry_completion_set_popup_completion (GtkEntryCompletion *completion,
1500 gboolean popup_completion)
1501{
1502 g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion));
1503
1504 popup_completion = popup_completion != FALSE;
1505
1506 if (completion->popup_completion != popup_completion)
1507 {
1508 completion->popup_completion = popup_completion;
1509
1510 g_object_notify_by_pspec (G_OBJECT (completion), pspec: entry_completion_props[PROP_POPUP_COMPLETION]);
1511 }
1512}
1513
1514
1515/**
1516 * gtk_entry_completion_get_popup_completion: (attributes org.gtk.Method.get_property=popup-completion)
1517 * @completion: a `GtkEntryCompletion`
1518 *
1519 * Returns whether the completions should be presented in a popup window.
1520 *
1521 * Returns: %TRUE if popup completion is turned on
1522 */
1523gboolean
1524gtk_entry_completion_get_popup_completion (GtkEntryCompletion *completion)
1525{
1526 g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), TRUE);
1527
1528 return completion->popup_completion;
1529}
1530
1531/**
1532 * gtk_entry_completion_set_popup_set_width: (attributes org.gtk.Method.set_property=popup-set-width)
1533 * @completion: a `GtkEntryCompletion`
1534 * @popup_set_width: %TRUE to make the width of the popup the same as the entry
1535 *
1536 * Sets whether the completion popup window will be resized to be the same
1537 * width as the entry.
1538 */
1539void
1540gtk_entry_completion_set_popup_set_width (GtkEntryCompletion *completion,
1541 gboolean popup_set_width)
1542{
1543 g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion));
1544
1545 popup_set_width = popup_set_width != FALSE;
1546
1547 if (completion->popup_set_width != popup_set_width)
1548 {
1549 completion->popup_set_width = popup_set_width;
1550
1551 g_object_notify_by_pspec (G_OBJECT (completion), pspec: entry_completion_props[PROP_POPUP_SET_WIDTH]);
1552 }
1553}
1554
1555/**
1556 * gtk_entry_completion_get_popup_set_width: (attributes org.gtk.Method.get_property=popup-set-width)
1557 * @completion: a `GtkEntryCompletion`
1558 *
1559 * Returns whether the completion popup window will be resized to the
1560 * width of the entry.
1561 *
1562 * Returns: %TRUE if the popup window will be resized to the width of
1563 * the entry
1564 */
1565gboolean
1566gtk_entry_completion_get_popup_set_width (GtkEntryCompletion *completion)
1567{
1568 g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), TRUE);
1569
1570 return completion->popup_set_width;
1571}
1572
1573
1574/**
1575 * gtk_entry_completion_set_popup_single_match: (attributes org.gtk.Method.set_property=popup-single-match)
1576 * @completion: a `GtkEntryCompletion`
1577 * @popup_single_match: %TRUE if the popup should appear even for a single match
1578 *
1579 * Sets whether the completion popup window will appear even if there is
1580 * only a single match.
1581 *
1582 * You may want to set this to %FALSE if you
1583 * are using [property@Gtk.EntryCompletion:inline-completion].
1584 */
1585void
1586gtk_entry_completion_set_popup_single_match (GtkEntryCompletion *completion,
1587 gboolean popup_single_match)
1588{
1589 g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion));
1590
1591 popup_single_match = popup_single_match != FALSE;
1592
1593 if (completion->popup_single_match != popup_single_match)
1594 {
1595 completion->popup_single_match = popup_single_match;
1596
1597 g_object_notify_by_pspec (G_OBJECT (completion), pspec: entry_completion_props[PROP_POPUP_SINGLE_MATCH]);
1598 }
1599}
1600
1601/**
1602 * gtk_entry_completion_get_popup_single_match: (attributes org.gtk.Method.get_property=popup-single-match)
1603 * @completion: a `GtkEntryCompletion`
1604 *
1605 * Returns whether the completion popup window will appear even if there is
1606 * only a single match.
1607 *
1608 * Returns: %TRUE if the popup window will appear regardless of the
1609 * number of matches
1610 */
1611gboolean
1612gtk_entry_completion_get_popup_single_match (GtkEntryCompletion *completion)
1613{
1614 g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), TRUE);
1615
1616 return completion->popup_single_match;
1617}
1618
1619/**
1620 * gtk_entry_completion_set_inline_selection: (attributes org.gtk.Method.set_property=inline-selection)
1621 * @completion: a `GtkEntryCompletion`
1622 * @inline_selection: %TRUE to do inline selection
1623 *
1624 * Sets whether it is possible to cycle through the possible completions
1625 * inside the entry.
1626 */
1627void
1628gtk_entry_completion_set_inline_selection (GtkEntryCompletion *completion,
1629 gboolean inline_selection)
1630{
1631 g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion));
1632
1633 inline_selection = inline_selection != FALSE;
1634
1635 if (completion->inline_selection != inline_selection)
1636 {
1637 completion->inline_selection = inline_selection;
1638
1639 g_object_notify_by_pspec (G_OBJECT (completion), pspec: entry_completion_props[PROP_INLINE_SELECTION]);
1640 }
1641}
1642
1643/**
1644 * gtk_entry_completion_get_inline_selection: (attributes org.gtk.Method.get_property=inline-selection)
1645 * @completion: a `GtkEntryCompletion`
1646 *
1647 * Returns %TRUE if inline-selection mode is turned on.
1648 *
1649 * Returns: %TRUE if inline-selection mode is on
1650 */
1651gboolean
1652gtk_entry_completion_get_inline_selection (GtkEntryCompletion *completion)
1653{
1654 g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), FALSE);
1655
1656 return completion->inline_selection;
1657}
1658
1659
1660static int
1661gtk_entry_completion_timeout (gpointer data)
1662{
1663 GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (data);
1664
1665 completion->completion_timeout = 0;
1666
1667 if (completion->filter_model &&
1668 g_utf8_strlen (p: gtk_editable_get_text (GTK_EDITABLE (completion->entry)), max: -1)
1669 >= completion->minimum_key_length)
1670 {
1671 int matches;
1672 gboolean popup_single;
1673
1674 gtk_entry_completion_complete (completion);
1675 matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->filter_model), NULL);
1676 gtk_tree_selection_unselect_all (selection: gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->tree_view)));
1677
1678 g_object_get (object: completion, first_property_name: "popup-single-match", &popup_single, NULL);
1679 if (matches > (popup_single ? 0: 1))
1680 {
1681 if (gtk_widget_get_visible (widget: completion->popup_window))
1682 _gtk_entry_completion_resize_popup (completion);
1683 else
1684 gtk_entry_completion_popup (completion);
1685 }
1686 else
1687 _gtk_entry_completion_popdown (completion);
1688 }
1689 else if (gtk_widget_get_visible (widget: completion->popup_window))
1690 _gtk_entry_completion_popdown (completion);
1691 return G_SOURCE_REMOVE;
1692}
1693
1694static inline gboolean
1695keyval_is_cursor_move (guint keyval)
1696{
1697 if (keyval == GDK_KEY_Up || keyval == GDK_KEY_KP_Up)
1698 return TRUE;
1699
1700 if (keyval == GDK_KEY_Down || keyval == GDK_KEY_KP_Down)
1701 return TRUE;
1702
1703 if (keyval == GDK_KEY_Page_Up)
1704 return TRUE;
1705
1706 if (keyval == GDK_KEY_Page_Down)
1707 return TRUE;
1708
1709 return FALSE;
1710}
1711
1712static gboolean
1713gtk_entry_completion_key_pressed (GtkEventControllerKey *controller,
1714 guint keyval,
1715 guint keycode,
1716 GdkModifierType state,
1717 gpointer user_data)
1718{
1719 int matches;
1720 GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data);
1721 GtkWidget *widget = completion->entry;
1722 GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (widget));
1723
1724 if (!completion->popup_completion)
1725 return FALSE;
1726
1727 if (keyval == GDK_KEY_Return ||
1728 keyval == GDK_KEY_KP_Enter ||
1729 keyval == GDK_KEY_ISO_Enter ||
1730 keyval == GDK_KEY_Escape)
1731 {
1732 if (completion->completion_timeout)
1733 {
1734 g_source_remove (tag: completion->completion_timeout);
1735 completion->completion_timeout = 0;
1736 }
1737 }
1738
1739 if (!gtk_widget_get_mapped (widget: completion->popup_window))
1740 return FALSE;
1741
1742 matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->filter_model), NULL);
1743
1744 if (keyval_is_cursor_move (keyval))
1745 {
1746 GtkTreePath *path = NULL;
1747
1748 if (keyval == GDK_KEY_Up || keyval == GDK_KEY_KP_Up)
1749 {
1750 if (completion->current_selected < 0)
1751 completion->current_selected = matches - 1;
1752 else
1753 completion->current_selected--;
1754 }
1755 else if (keyval == GDK_KEY_Down || keyval == GDK_KEY_KP_Down)
1756 {
1757 if (completion->current_selected < matches - 1)
1758 completion->current_selected++;
1759 else
1760 completion->current_selected = -1;
1761 }
1762 else if (keyval == GDK_KEY_Page_Up)
1763 {
1764 if (completion->current_selected < 0)
1765 completion->current_selected = matches - 1;
1766 else if (completion->current_selected == 0)
1767 completion->current_selected = -1;
1768 else if (completion->current_selected < matches)
1769 {
1770 completion->current_selected -= PAGE_STEP;
1771 if (completion->current_selected < 0)
1772 completion->current_selected = 0;
1773 }
1774 else
1775 {
1776 completion->current_selected -= PAGE_STEP;
1777 if (completion->current_selected < matches - 1)
1778 completion->current_selected = matches - 1;
1779 }
1780 }
1781 else if (keyval == GDK_KEY_Page_Down)
1782 {
1783 if (completion->current_selected < 0)
1784 completion->current_selected = 0;
1785 else if (completion->current_selected < matches - 1)
1786 {
1787 completion->current_selected += PAGE_STEP;
1788 if (completion->current_selected > matches - 1)
1789 completion->current_selected = matches - 1;
1790 }
1791 else if (completion->current_selected == matches - 1)
1792 {
1793 completion->current_selected = -1;
1794 }
1795 else
1796 {
1797 completion->current_selected += PAGE_STEP;
1798 if (completion->current_selected > matches - 1)
1799 completion->current_selected = matches - 1;
1800 }
1801 }
1802
1803 if (completion->current_selected < 0)
1804 {
1805 gtk_tree_selection_unselect_all (selection: gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->tree_view)));
1806
1807 if (completion->inline_selection &&
1808 completion->completion_prefix)
1809 {
1810 gtk_editable_set_text (GTK_EDITABLE (completion->entry),
1811 text: completion->completion_prefix);
1812 gtk_editable_set_position (GTK_EDITABLE (widget), position: -1);
1813 }
1814 }
1815 else if (completion->current_selected < matches)
1816 {
1817 path = gtk_tree_path_new_from_indices (first_index: completion->current_selected, -1);
1818 gtk_tree_view_set_cursor (GTK_TREE_VIEW (completion->tree_view),
1819 path, NULL, FALSE);
1820
1821 if (completion->inline_selection)
1822 {
1823
1824 GtkTreeIter iter;
1825 GtkTreeIter child_iter;
1826 GtkTreeModel *model = NULL;
1827 GtkTreeSelection *sel;
1828 gboolean entry_set;
1829
1830 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->tree_view));
1831 if (!gtk_tree_selection_get_selected (selection: sel, model: &model, iter: &iter))
1832 return FALSE;
1833 gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), child_iter: &child_iter, filter_iter: &iter);
1834 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model));
1835
1836 if (completion->completion_prefix == NULL)
1837 completion->completion_prefix = g_strdup (str: gtk_editable_get_text (GTK_EDITABLE (completion->entry)));
1838
1839 g_signal_emit_by_name (instance: completion, detailed_signal: "cursor-on-match", model,
1840 &child_iter, &entry_set);
1841 }
1842 }
1843
1844 gtk_tree_path_free (path);
1845
1846 return TRUE;
1847 }
1848 else if (keyval == GDK_KEY_Escape ||
1849 keyval == GDK_KEY_Left ||
1850 keyval == GDK_KEY_KP_Left ||
1851 keyval == GDK_KEY_Right ||
1852 keyval == GDK_KEY_KP_Right)
1853 {
1854 gboolean retval = TRUE;
1855
1856 gtk_entry_reset_im_context (GTK_ENTRY (widget));
1857 _gtk_entry_completion_popdown (completion);
1858
1859 if (completion->current_selected < 0)
1860 {
1861 retval = FALSE;
1862 goto keypress_completion_out;
1863 }
1864 else if (completion->inline_selection)
1865 {
1866 /* Escape rejects the tentative completion */
1867 if (keyval == GDK_KEY_Escape)
1868 {
1869 if (completion->completion_prefix)
1870 gtk_editable_set_text (GTK_EDITABLE (completion->entry),
1871 text: completion->completion_prefix);
1872 else
1873 gtk_editable_set_text (GTK_EDITABLE (completion->entry), text: "");
1874 }
1875
1876 /* Move the cursor to the end for Right/Esc */
1877 if (keyval == GDK_KEY_Right ||
1878 keyval == GDK_KEY_KP_Right ||
1879 keyval == GDK_KEY_Escape)
1880 gtk_editable_set_position (GTK_EDITABLE (widget), position: -1);
1881 /* Let the default keybindings run for Left, i.e. either move to the
1882 * * previous character or select word if a modifier is used */
1883 else
1884 retval = FALSE;
1885 }
1886
1887keypress_completion_out:
1888 if (completion->inline_selection)
1889 g_clear_pointer (&completion->completion_prefix, g_free);
1890
1891 return retval;
1892 }
1893 else if (keyval == GDK_KEY_Tab ||
1894 keyval == GDK_KEY_KP_Tab ||
1895 keyval == GDK_KEY_ISO_Left_Tab)
1896 {
1897 gtk_entry_reset_im_context (GTK_ENTRY (widget));
1898 _gtk_entry_completion_popdown (completion);
1899
1900 g_clear_pointer (&completion->completion_prefix, g_free);
1901
1902 return FALSE;
1903 }
1904 else if (keyval == GDK_KEY_ISO_Enter ||
1905 keyval == GDK_KEY_KP_Enter ||
1906 keyval == GDK_KEY_Return)
1907 {
1908 GtkTreeIter iter;
1909 GtkTreeModel *model = NULL;
1910 GtkTreeModel *child_model;
1911 GtkTreeIter child_iter;
1912 GtkTreeSelection *sel;
1913 gboolean retval = TRUE;
1914
1915 gtk_entry_reset_im_context (GTK_ENTRY (widget));
1916 _gtk_entry_completion_popdown (completion);
1917
1918 if (completion->current_selected < matches)
1919 {
1920 gboolean entry_set;
1921
1922 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->tree_view));
1923 if (gtk_tree_selection_get_selected (selection: sel, model: &model, iter: &iter))
1924 {
1925 gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), child_iter: &child_iter, filter_iter: &iter);
1926 child_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model));
1927 g_signal_handler_block (instance: text, handler_id: completion->changed_id);
1928 g_signal_emit_by_name (instance: completion, detailed_signal: "match-selected",
1929 child_model, &child_iter, &entry_set);
1930 g_signal_handler_unblock (instance: text, handler_id: completion->changed_id);
1931
1932 if (!entry_set)
1933 {
1934 char *str = NULL;
1935
1936 gtk_tree_model_get (tree_model: model, iter: &iter,
1937 completion->text_column, &str,
1938 -1);
1939
1940 gtk_editable_set_text (GTK_EDITABLE (widget), text: str);
1941
1942 /* move the cursor to the end */
1943 gtk_editable_set_position (GTK_EDITABLE (widget), position: -1);
1944 g_free (mem: str);
1945 }
1946 }
1947 else
1948 retval = FALSE;
1949 }
1950
1951 g_clear_pointer (&completion->completion_prefix, g_free);
1952
1953 return retval;
1954 }
1955
1956 g_clear_pointer (&completion->completion_prefix, g_free);
1957
1958 return FALSE;
1959}
1960
1961static void
1962gtk_entry_completion_changed (GtkWidget *widget,
1963 gpointer user_data)
1964{
1965 GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data);
1966
1967 if (!completion->popup_completion)
1968 return;
1969
1970 /* (re)install completion timeout */
1971 if (completion->completion_timeout)
1972 {
1973 g_source_remove (tag: completion->completion_timeout);
1974 completion->completion_timeout = 0;
1975 }
1976
1977 if (!gtk_editable_get_text (GTK_EDITABLE (widget)))
1978 return;
1979
1980 /* no need to normalize for this test */
1981 if (completion->minimum_key_length > 0 &&
1982 strcmp (s1: "", s2: gtk_editable_get_text (GTK_EDITABLE (widget))) == 0)
1983 {
1984 if (gtk_widget_get_visible (widget: completion->popup_window))
1985 _gtk_entry_completion_popdown (completion);
1986 return;
1987 }
1988
1989 completion->completion_timeout =
1990 g_timeout_add (COMPLETION_TIMEOUT,
1991 function: gtk_entry_completion_timeout,
1992 data: completion);
1993 gdk_source_set_static_name_by_id (tag: completion->completion_timeout, name: "[gtk] gtk_entry_completion_timeout");
1994}
1995
1996static gboolean
1997check_completion_callback (GtkEntryCompletion *completion)
1998{
1999 completion->check_completion_idle = NULL;
2000
2001 gtk_entry_completion_complete (completion);
2002 gtk_entry_completion_insert_prefix (completion);
2003
2004 return FALSE;
2005}
2006
2007static void
2008clear_completion_callback (GObject *text,
2009 GParamSpec *pspec,
2010 GtkEntryCompletion *completion)
2011{
2012 if (!completion->inline_completion)
2013 return;
2014
2015 if (pspec->name == I_("cursor-position") ||
2016 pspec->name == I_("selection-bound"))
2017 completion->has_completion = FALSE;
2018}
2019
2020static gboolean
2021accept_completion_callback (GtkEntryCompletion *completion)
2022{
2023 if (!completion->inline_completion)
2024 return FALSE;
2025
2026 if (completion->has_completion)
2027 gtk_editable_set_position (GTK_EDITABLE (completion->entry),
2028 position: gtk_entry_buffer_get_length (buffer: gtk_entry_get_buffer (GTK_ENTRY (completion->entry))));
2029
2030 return FALSE;
2031}
2032
2033static void
2034text_focus_out (GtkEntryCompletion *completion)
2035{
2036 if (!gtk_widget_get_mapped (widget: completion->popup_window))
2037 accept_completion_callback (completion);
2038}
2039
2040static void
2041completion_inserted_text_callback (GtkEntryBuffer *buffer,
2042 guint position,
2043 const char *text,
2044 guint length,
2045 GtkEntryCompletion *completion)
2046{
2047 if (!completion->inline_completion)
2048 return;
2049
2050 /* idle to update the selection based on the file list */
2051 if (completion->check_completion_idle == NULL)
2052 {
2053 completion->check_completion_idle = g_idle_source_new ();
2054 g_source_set_priority (source: completion->check_completion_idle, G_PRIORITY_HIGH);
2055 g_source_set_closure (source: completion->check_completion_idle,
2056 closure: g_cclosure_new_object (G_CALLBACK (check_completion_callback),
2057 G_OBJECT (completion)));
2058 g_source_attach (source: completion->check_completion_idle, NULL);
2059 g_source_set_static_name (completion->check_completion_idle, "[gtk] check_completion_callback");
2060 }
2061}
2062
2063static void
2064connect_completion_signals (GtkEntryCompletion *completion)
2065{
2066 GtkEventController *controller;
2067 GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (completion->entry));
2068 GtkEntryBuffer *buffer = gtk_text_get_buffer (self: text);
2069
2070 controller = completion->entry_key_controller = gtk_event_controller_key_new ();
2071 gtk_event_controller_set_name (controller, name: "gtk-entry-completion");
2072 g_signal_connect (controller, "key-pressed",
2073 G_CALLBACK (gtk_entry_completion_key_pressed), completion);
2074 gtk_widget_add_controller (GTK_WIDGET (text), controller);
2075 controller = completion->entry_focus_controller = gtk_event_controller_focus_new ();
2076 gtk_event_controller_set_name (controller, name: "gtk-entry-completion");
2077 g_signal_connect_swapped (controller, "leave", G_CALLBACK (text_focus_out), completion);
2078 gtk_widget_add_controller (GTK_WIDGET (text), controller);
2079
2080 completion->changed_id =
2081 g_signal_connect (text, "changed", G_CALLBACK (gtk_entry_completion_changed), completion);
2082
2083 completion->insert_text_id =
2084 g_signal_connect (buffer, "inserted-text", G_CALLBACK (completion_inserted_text_callback), completion);
2085 g_signal_connect (text, "notify", G_CALLBACK (clear_completion_callback), completion);
2086 g_signal_connect_swapped (text, "activate", G_CALLBACK (accept_completion_callback), completion);
2087}
2088
2089static void
2090disconnect_completion_signals (GtkEntryCompletion *completion)
2091{
2092 GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (completion->entry));
2093 GtkEntryBuffer *buffer = gtk_text_get_buffer (self: text);
2094
2095 gtk_widget_remove_controller (GTK_WIDGET (text), controller: completion->entry_key_controller);
2096 gtk_widget_remove_controller (GTK_WIDGET (text), controller: completion->entry_focus_controller);
2097
2098 if (completion->changed_id > 0 &&
2099 g_signal_handler_is_connected (instance: text, handler_id: completion->changed_id))
2100 {
2101 g_signal_handler_disconnect (instance: text, handler_id: completion->changed_id);
2102 completion->changed_id = 0;
2103 }
2104 if (completion->insert_text_id > 0 &&
2105 g_signal_handler_is_connected (instance: buffer, handler_id: completion->insert_text_id))
2106 {
2107 g_signal_handler_disconnect (instance: buffer, handler_id: completion->insert_text_id);
2108 completion->insert_text_id = 0;
2109 }
2110 g_signal_handlers_disconnect_by_func (text, G_CALLBACK (clear_completion_callback), completion);
2111 g_signal_handlers_disconnect_by_func (text, G_CALLBACK (accept_completion_callback), completion);
2112}
2113
2114void
2115_gtk_entry_completion_disconnect (GtkEntryCompletion *completion)
2116{
2117 if (completion->completion_timeout)
2118 {
2119 g_source_remove (tag: completion->completion_timeout);
2120 completion->completion_timeout = 0;
2121 }
2122 if (completion->check_completion_idle)
2123 {
2124 g_source_destroy (source: completion->check_completion_idle);
2125 completion->check_completion_idle = NULL;
2126 }
2127
2128 if (gtk_widget_get_mapped (widget: completion->popup_window))
2129 _gtk_entry_completion_popdown (completion);
2130
2131 disconnect_completion_signals (completion);
2132
2133 gtk_widget_unparent (widget: completion->popup_window);
2134
2135 completion->entry = NULL;
2136}
2137
2138void
2139_gtk_entry_completion_connect (GtkEntryCompletion *completion,
2140 GtkEntry *entry)
2141{
2142 completion->entry = GTK_WIDGET (entry);
2143
2144 gtk_widget_set_parent (widget: completion->popup_window, GTK_WIDGET (entry));
2145
2146 connect_completion_signals (completion);
2147}
2148

source code of gtk/gtk/gtkentrycompletion.c