1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 2013 Red Hat, Inc. |
3 | * |
4 | * Authors: |
5 | * - Bastien Nocera <bnocera@redhat.com> |
6 | * |
7 | * This library is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Lesser General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2 of the License, or (at your option) any later version. |
11 | * |
12 | * This library is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Lesser General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Lesser General Public |
18 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
19 | */ |
20 | |
21 | /* |
22 | * Modified by the GTK+ Team and others 2013. See the AUTHORS |
23 | * file for a list of people on the GTK+ Team. See the ChangeLog |
24 | * files for a list of changes. These files are distributed with |
25 | * GTK+ at ftp://ftp.gtk.org/pub/gtk/. |
26 | */ |
27 | |
28 | #include "config.h" |
29 | |
30 | #include "gtksearchbar.h" |
31 | |
32 | #include "gtkbinlayout.h" |
33 | #include "gtkbuildable.h" |
34 | #include "gtkbutton.h" |
35 | #include "gtkcenterbox.h" |
36 | #include "gtkentryprivate.h" |
37 | #include "gtkeventcontrollerkey.h" |
38 | #include "gtkintl.h" |
39 | #include "gtkprivate.h" |
40 | #include "gtkrevealer.h" |
41 | #include "gtksearchentryprivate.h" |
42 | #include "gtksnapshot.h" |
43 | #include "gtkwidgetprivate.h" |
44 | |
45 | /** |
46 | * GtkSearchBar: |
47 | * |
48 | * `GtkSearchBar` is a container made to have a search entry. |
49 | * |
50 | * ![An example GtkSearchBar](search-bar.png) |
51 | * |
52 | * It can also contain additional widgets, such as drop-down menus, |
53 | * or buttons. The search bar would appear when a search is started |
54 | * through typing on the keyboard, or the application’s search mode |
55 | * is toggled on. |
56 | * |
57 | * For keyboard presses to start a search, the search bar must be told |
58 | * of a widget to capture key events from through |
59 | * [method@Gtk.SearchBar.set_key_capture_widget]. This widget will |
60 | * typically be the top-level window, or a parent container of the |
61 | * search bar. Common shortcuts such as Ctrl+F should be handled as an |
62 | * application action, or through the menu items. |
63 | * |
64 | * You will also need to tell the search bar about which entry you |
65 | * are using as your search entry using [method@Gtk.SearchBar.connect_entry]. |
66 | * |
67 | * ## Creating a search bar |
68 | * |
69 | * The following example shows you how to create a more complex search |
70 | * entry. |
71 | * |
72 | * [A simple example](https://gitlab.gnome.org/GNOME/gtk/tree/main/examples/search-bar.c) |
73 | * |
74 | * # CSS nodes |
75 | * |
76 | * ``` |
77 | * searchbar |
78 | * ╰── revealer |
79 | * ╰── box |
80 | * ├── [child] |
81 | * ╰── [button.close] |
82 | * ``` |
83 | * |
84 | * `GtkSearchBar` has a main CSS node with name searchbar. It has a child |
85 | * node with name revealer that contains a node with name box. The box node |
86 | * contains both the CSS node of the child widget as well as an optional button |
87 | * node which gets the .close style class applied. |
88 | * |
89 | * # Accessibility |
90 | * |
91 | * `GtkSearchBar` uses the %GTK_ACCESSIBLE_ROLE_SEARCH role. |
92 | */ |
93 | |
94 | typedef struct _GtkSearchBarClass GtkSearchBarClass; |
95 | |
96 | struct _GtkSearchBar |
97 | { |
98 | GtkWidget parent; |
99 | |
100 | GtkWidget *child; |
101 | GtkWidget *revealer; |
102 | GtkWidget *box_center; |
103 | GtkWidget *close_button; |
104 | |
105 | GtkWidget *entry; |
106 | gboolean reveal_child; |
107 | |
108 | GtkWidget *capture_widget; |
109 | GtkEventController *capture_widget_controller; |
110 | }; |
111 | |
112 | struct _GtkSearchBarClass |
113 | { |
114 | GtkWidgetClass parent_class; |
115 | }; |
116 | |
117 | static void gtk_search_bar_buildable_iface_init (GtkBuildableIface *iface); |
118 | |
119 | G_DEFINE_TYPE_WITH_CODE (GtkSearchBar, gtk_search_bar, GTK_TYPE_WIDGET, |
120 | G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, |
121 | gtk_search_bar_buildable_iface_init)) |
122 | |
123 | enum { |
124 | PROP_0, |
125 | PROP_SEARCH_MODE_ENABLED, |
126 | PROP_SHOW_CLOSE_BUTTON, |
127 | PROP_CHILD, |
128 | PROP_KEY_CAPTURE_WIDGET, |
129 | LAST_PROPERTY |
130 | }; |
131 | |
132 | static GParamSpec *widget_props[LAST_PROPERTY] = { NULL, }; |
133 | |
134 | static GtkBuildableIface *parent_buildable_iface; |
135 | |
136 | static void |
137 | gtk_search_bar_buildable_add_child (GtkBuildable *buildable, |
138 | GtkBuilder *builder, |
139 | GObject *child, |
140 | const char *type) |
141 | { |
142 | if (GTK_IS_WIDGET (child)) |
143 | gtk_search_bar_set_child (GTK_SEARCH_BAR (buildable), GTK_WIDGET (child)); |
144 | else |
145 | parent_buildable_iface->add_child (buildable, builder, child, type); |
146 | } |
147 | |
148 | static void |
149 | gtk_search_bar_buildable_iface_init (GtkBuildableIface *iface) |
150 | { |
151 | parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface); |
152 | |
153 | iface->add_child = gtk_search_bar_buildable_add_child; |
154 | } |
155 | |
156 | static void |
157 | stop_search_cb (GtkWidget *entry, |
158 | GtkSearchBar *bar) |
159 | { |
160 | gtk_revealer_set_reveal_child (GTK_REVEALER (bar->revealer), FALSE); |
161 | } |
162 | |
163 | static void |
164 | reveal_child_changed_cb (GObject *object, |
165 | GParamSpec *pspec, |
166 | GtkSearchBar *bar) |
167 | { |
168 | gboolean reveal_child; |
169 | |
170 | g_object_get (object, first_property_name: "reveal-child" , &reveal_child, NULL); |
171 | |
172 | if (reveal_child == bar->reveal_child) |
173 | return; |
174 | |
175 | bar->reveal_child = reveal_child; |
176 | |
177 | if (bar->entry) |
178 | { |
179 | if (reveal_child && GTK_IS_ENTRY (bar->entry)) |
180 | gtk_entry_grab_focus_without_selecting (GTK_ENTRY (bar->entry)); |
181 | else if (reveal_child && GTK_IS_SEARCH_ENTRY (bar->entry)) |
182 | gtk_widget_grab_focus (widget: bar->entry); |
183 | else |
184 | gtk_editable_set_text (GTK_EDITABLE (bar->entry), text: "" ); |
185 | } |
186 | |
187 | g_object_notify_by_pspec (G_OBJECT (bar), pspec: widget_props[PROP_SEARCH_MODE_ENABLED]); |
188 | } |
189 | |
190 | static void |
191 | close_button_clicked_cb (GtkWidget *button, |
192 | GtkSearchBar *bar) |
193 | { |
194 | gtk_revealer_set_reveal_child (GTK_REVEALER (bar->revealer), FALSE); |
195 | } |
196 | |
197 | static void |
198 | gtk_search_bar_set_property (GObject *object, |
199 | guint prop_id, |
200 | const GValue *value, |
201 | GParamSpec *pspec) |
202 | { |
203 | GtkSearchBar *bar = GTK_SEARCH_BAR (object); |
204 | |
205 | switch (prop_id) |
206 | { |
207 | case PROP_SEARCH_MODE_ENABLED: |
208 | gtk_search_bar_set_search_mode (bar, search_mode: g_value_get_boolean (value)); |
209 | break; |
210 | case PROP_SHOW_CLOSE_BUTTON: |
211 | gtk_search_bar_set_show_close_button (bar, visible: g_value_get_boolean (value)); |
212 | break; |
213 | case PROP_CHILD: |
214 | gtk_search_bar_set_child (bar, child: g_value_get_object (value)); |
215 | break; |
216 | case PROP_KEY_CAPTURE_WIDGET: |
217 | gtk_search_bar_set_key_capture_widget (bar, widget: g_value_get_object (value)); |
218 | break; |
219 | default: |
220 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
221 | break; |
222 | } |
223 | } |
224 | |
225 | static void |
226 | gtk_search_bar_get_property (GObject *object, |
227 | guint prop_id, |
228 | GValue *value, |
229 | GParamSpec *pspec) |
230 | { |
231 | GtkSearchBar *bar = GTK_SEARCH_BAR (object); |
232 | |
233 | switch (prop_id) |
234 | { |
235 | case PROP_SEARCH_MODE_ENABLED: |
236 | g_value_set_boolean (value, v_boolean: gtk_search_bar_get_search_mode (bar)); |
237 | break; |
238 | case PROP_SHOW_CLOSE_BUTTON: |
239 | g_value_set_boolean (value, v_boolean: gtk_search_bar_get_show_close_button (bar)); |
240 | break; |
241 | case PROP_CHILD: |
242 | g_value_set_object (value, v_object: gtk_search_bar_get_child (bar)); |
243 | break; |
244 | case PROP_KEY_CAPTURE_WIDGET: |
245 | g_value_set_object (value, v_object: gtk_search_bar_get_key_capture_widget (bar)); |
246 | break; |
247 | default: |
248 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
249 | break; |
250 | } |
251 | } |
252 | |
253 | static void gtk_search_bar_set_entry (GtkSearchBar *bar, |
254 | GtkEditable *editable); |
255 | |
256 | static void |
257 | gtk_search_bar_dispose (GObject *object) |
258 | { |
259 | GtkSearchBar *bar = GTK_SEARCH_BAR (object); |
260 | |
261 | gtk_search_bar_set_key_capture_widget (bar, NULL); |
262 | gtk_search_bar_set_entry (bar, NULL); |
263 | |
264 | g_clear_pointer (&bar->revealer, gtk_widget_unparent); |
265 | |
266 | bar->child = NULL; |
267 | bar->box_center = NULL; |
268 | bar->close_button = NULL; |
269 | |
270 | G_OBJECT_CLASS (gtk_search_bar_parent_class)->dispose (object); |
271 | } |
272 | |
273 | static void |
274 | gtk_search_bar_compute_expand (GtkWidget *widget, |
275 | gboolean *hexpand, |
276 | gboolean *vexpand) |
277 | { |
278 | GtkSearchBar *bar = GTK_SEARCH_BAR (widget); |
279 | |
280 | if (bar->child) |
281 | { |
282 | *hexpand = gtk_widget_compute_expand (widget: bar->child, orientation: GTK_ORIENTATION_HORIZONTAL); |
283 | *vexpand = gtk_widget_compute_expand (widget: bar->child, orientation: GTK_ORIENTATION_VERTICAL); |
284 | } |
285 | else |
286 | { |
287 | *hexpand = FALSE; |
288 | *vexpand = FALSE; |
289 | } |
290 | } |
291 | |
292 | static void |
293 | gtk_search_bar_class_init (GtkSearchBarClass *klass) |
294 | { |
295 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
296 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
297 | |
298 | object_class->dispose = gtk_search_bar_dispose; |
299 | object_class->set_property = gtk_search_bar_set_property; |
300 | object_class->get_property = gtk_search_bar_get_property; |
301 | |
302 | widget_class->compute_expand = gtk_search_bar_compute_expand; |
303 | widget_class->focus = gtk_widget_focus_child; |
304 | |
305 | /** |
306 | * GtkSearchBar:search-mode-enabled: (attributes org.gtk.Property.get=gtk_search_bar_get_search_mode org.gtk.Property.set=gtk_search_bar_set_search_mode) |
307 | * |
308 | * Whether the search mode is on and the search bar shown. |
309 | */ |
310 | widget_props[PROP_SEARCH_MODE_ENABLED] = g_param_spec_boolean (name: "search-mode-enabled" , |
311 | P_("Search Mode Enabled" ), |
312 | P_("Whether the search mode is on and the search bar shown" ), |
313 | FALSE, |
314 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
315 | |
316 | /** |
317 | * GtkSearchBar:show-close-button: (attributes org.gtk.Property.get=gtk_search_bar_get_show_close_button org.gtk.Property.set=gtk_search_bar_set_show_close_button) |
318 | * |
319 | * Whether to show the close button in the search bar. |
320 | */ |
321 | widget_props[PROP_SHOW_CLOSE_BUTTON] = g_param_spec_boolean (name: "show-close-button" , |
322 | P_("Show Close Button" ), |
323 | P_("Whether to show the close button in the toolbar" ), |
324 | FALSE, |
325 | GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY); |
326 | |
327 | /** |
328 | * GtkSearchBar:child: (attributes org.gtk.Property.get=gtk_search_bar_get_child org.gtk.Property.set=gtk_search_bar_set_child) |
329 | * |
330 | * The child widget. |
331 | */ |
332 | widget_props[PROP_CHILD] = g_param_spec_object (name: "child" , |
333 | P_("Child" ), |
334 | P_("The child widget" ), |
335 | GTK_TYPE_WIDGET, |
336 | GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY); |
337 | |
338 | /** |
339 | * GtkSearchBar:key-capture-widget: (attributes org.gtk.Property.get=gtk_search_bar_get_key_capture_widget org.gtk.Property.set=gtk_search_bar_set_key_capture_widget) |
340 | * |
341 | * The key capture widget. |
342 | */ |
343 | widget_props[PROP_KEY_CAPTURE_WIDGET] |
344 | = g_param_spec_object (name: "key-capture-widget" , |
345 | P_("Key Capture Widget" ), |
346 | P_("Key Capture Widget" ), |
347 | GTK_TYPE_WIDGET, |
348 | GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY); |
349 | |
350 | g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROPERTY, pspecs: widget_props); |
351 | |
352 | gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); |
353 | gtk_widget_class_set_css_name (widget_class, I_("searchbar" )); |
354 | gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_SEARCH); |
355 | } |
356 | |
357 | static void |
358 | gtk_search_bar_init (GtkSearchBar *bar) |
359 | { |
360 | bar->revealer = gtk_revealer_new (); |
361 | gtk_revealer_set_reveal_child (GTK_REVEALER (bar->revealer), FALSE); |
362 | gtk_widget_set_hexpand (widget: bar->revealer, TRUE); |
363 | gtk_widget_set_parent (widget: bar->revealer, GTK_WIDGET (bar)); |
364 | |
365 | bar->box_center = gtk_center_box_new (); |
366 | gtk_widget_set_hexpand (widget: bar->box_center, TRUE); |
367 | |
368 | bar->close_button = gtk_button_new_from_icon_name (icon_name: "window-close-symbolic" ); |
369 | gtk_widget_set_valign (widget: bar->close_button, align: GTK_ALIGN_CENTER); |
370 | gtk_widget_add_css_class (widget: bar->close_button, css_class: "close" ); |
371 | gtk_center_box_set_end_widget (GTK_CENTER_BOX (bar->box_center), child: bar->close_button); |
372 | gtk_widget_hide (widget: bar->close_button); |
373 | |
374 | gtk_revealer_set_child (GTK_REVEALER (bar->revealer), child: bar->box_center); |
375 | |
376 | g_signal_connect (bar->revealer, "notify::reveal-child" , |
377 | G_CALLBACK (reveal_child_changed_cb), bar); |
378 | |
379 | g_signal_connect (bar->close_button, "clicked" , |
380 | G_CALLBACK (close_button_clicked_cb), bar); |
381 | } |
382 | |
383 | /** |
384 | * gtk_search_bar_new: |
385 | * |
386 | * Creates a `GtkSearchBar`. |
387 | * |
388 | * You will need to tell it about which widget is going to be your text |
389 | * entry using [method@Gtk.SearchBar.connect_entry]. |
390 | * |
391 | * Returns: a new `GtkSearchBar` |
392 | */ |
393 | GtkWidget * |
394 | gtk_search_bar_new (void) |
395 | { |
396 | return g_object_new (GTK_TYPE_SEARCH_BAR, NULL); |
397 | } |
398 | |
399 | static void |
400 | gtk_search_bar_set_entry (GtkSearchBar *bar, |
401 | GtkEditable *entry) |
402 | { |
403 | if (bar->entry != NULL) |
404 | { |
405 | if (GTK_IS_SEARCH_ENTRY (bar->entry)) |
406 | { |
407 | gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (bar->entry), NULL); |
408 | g_signal_handlers_disconnect_by_func (bar->entry, stop_search_cb, bar); |
409 | } |
410 | g_object_remove_weak_pointer (G_OBJECT (bar->entry), weak_pointer_location: (gpointer *) &bar->entry); |
411 | } |
412 | |
413 | bar->entry = GTK_WIDGET (entry); |
414 | |
415 | if (bar->entry != NULL) |
416 | { |
417 | g_object_add_weak_pointer (G_OBJECT (bar->entry), weak_pointer_location: (gpointer *) &bar->entry); |
418 | if (GTK_IS_SEARCH_ENTRY (bar->entry)) |
419 | { |
420 | g_signal_connect (bar->entry, "stop-search" , |
421 | G_CALLBACK (stop_search_cb), bar); |
422 | gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (bar->entry), |
423 | GTK_WIDGET (bar)); |
424 | } |
425 | |
426 | } |
427 | } |
428 | |
429 | /** |
430 | * gtk_search_bar_connect_entry: |
431 | * @bar: a `GtkSearchBar` |
432 | * @entry: a `GtkEditable` |
433 | * |
434 | * Connects the `GtkEditable widget passed as the one to be used in |
435 | * this search bar. |
436 | * |
437 | * The entry should be a descendant of the search bar. Calling this |
438 | * function manually is only required if the entry isn’t the direct |
439 | * child of the search bar (as in our main example). |
440 | */ |
441 | void |
442 | gtk_search_bar_connect_entry (GtkSearchBar *bar, |
443 | GtkEditable *entry) |
444 | { |
445 | g_return_if_fail (GTK_IS_SEARCH_BAR (bar)); |
446 | g_return_if_fail (entry == NULL || GTK_IS_EDITABLE (entry)); |
447 | |
448 | gtk_search_bar_set_entry (bar, entry); |
449 | } |
450 | |
451 | /** |
452 | * gtk_search_bar_get_search_mode: (attributes org.gtk.Method.get_property=search-mode-enabled) |
453 | * @bar: a `GtkSearchBar` |
454 | * |
455 | * Returns whether the search mode is on or off. |
456 | * |
457 | * Returns: whether search mode is toggled on |
458 | */ |
459 | gboolean |
460 | gtk_search_bar_get_search_mode (GtkSearchBar *bar) |
461 | { |
462 | g_return_val_if_fail (GTK_IS_SEARCH_BAR (bar), FALSE); |
463 | |
464 | return bar->reveal_child; |
465 | } |
466 | |
467 | /** |
468 | * gtk_search_bar_set_search_mode: (attributes org.gtk.Method.set_property=search-mode-enabled) |
469 | * @bar: a `GtkSearchBar` |
470 | * @search_mode: the new state of the search mode |
471 | * |
472 | * Switches the search mode on or off. |
473 | */ |
474 | void |
475 | gtk_search_bar_set_search_mode (GtkSearchBar *bar, |
476 | gboolean search_mode) |
477 | { |
478 | g_return_if_fail (GTK_IS_SEARCH_BAR (bar)); |
479 | |
480 | gtk_revealer_set_reveal_child (GTK_REVEALER (bar->revealer), reveal_child: search_mode); |
481 | } |
482 | |
483 | /** |
484 | * gtk_search_bar_get_show_close_button: (attributes org.gtk.Method.get_property=show-close-button) |
485 | * @bar: a `GtkSearchBar` |
486 | * |
487 | * Returns whether the close button is shown. |
488 | * |
489 | * Returns: whether the close button is shown |
490 | */ |
491 | gboolean |
492 | gtk_search_bar_get_show_close_button (GtkSearchBar *bar) |
493 | { |
494 | g_return_val_if_fail (GTK_IS_SEARCH_BAR (bar), FALSE); |
495 | |
496 | return gtk_widget_get_visible (widget: bar->close_button); |
497 | } |
498 | |
499 | /** |
500 | * gtk_search_bar_set_show_close_button: (attributes org.gtk.Method.set_property=show-close-button) |
501 | * @bar: a `GtkSearchBar` |
502 | * @visible: whether the close button will be shown or not |
503 | * |
504 | * Shows or hides the close button. |
505 | * |
506 | * Applications that already have a “search” toggle button should not |
507 | * show a close button in their search bar, as it duplicates the role |
508 | * of the toggle button. |
509 | */ |
510 | void |
511 | gtk_search_bar_set_show_close_button (GtkSearchBar *bar, |
512 | gboolean visible) |
513 | { |
514 | g_return_if_fail (GTK_IS_SEARCH_BAR (bar)); |
515 | |
516 | visible = visible != FALSE; |
517 | |
518 | if (gtk_widget_get_visible (widget: bar->close_button) != visible) |
519 | { |
520 | gtk_widget_set_visible (widget: bar->close_button, visible); |
521 | g_object_notify_by_pspec (G_OBJECT (bar), pspec: widget_props[PROP_SHOW_CLOSE_BUTTON]); |
522 | } |
523 | } |
524 | |
525 | static void |
526 | changed_cb (gboolean *changed) |
527 | { |
528 | *changed = TRUE; |
529 | } |
530 | |
531 | static gboolean |
532 | capture_widget_key_handled (GtkEventControllerKey *controller, |
533 | guint keyval, |
534 | guint keycode, |
535 | GdkModifierType state, |
536 | GtkSearchBar *bar) |
537 | { |
538 | gboolean handled; |
539 | |
540 | if (!gtk_widget_get_mapped (GTK_WIDGET (bar))) |
541 | return GDK_EVENT_PROPAGATE; |
542 | |
543 | if (bar->reveal_child) |
544 | return GDK_EVENT_PROPAGATE; |
545 | |
546 | if (bar->entry == NULL) |
547 | { |
548 | g_warning ("The search bar does not have an entry connected to it. Call gtk_search_bar_connect_entry() to connect one." ); |
549 | return GDK_EVENT_PROPAGATE; |
550 | } |
551 | |
552 | if (GTK_IS_SEARCH_ENTRY (bar->entry)) |
553 | { |
554 | /* The search entry was told to listen to events from the search bar, so |
555 | * just forward the event to self, so the search entry has an opportunity |
556 | * to intercept those. |
557 | */ |
558 | handled = gtk_event_controller_key_forward (controller, GTK_WIDGET (bar)); |
559 | } |
560 | else |
561 | { |
562 | gboolean preedit_changed, buffer_changed; |
563 | guint preedit_change_id, buffer_change_id; |
564 | gboolean res; |
565 | |
566 | if (gtk_search_entry_is_keynav (keyval, state) || |
567 | keyval == GDK_KEY_space || |
568 | keyval == GDK_KEY_Menu) |
569 | return GDK_EVENT_PROPAGATE; |
570 | |
571 | if (keyval == GDK_KEY_Escape) |
572 | { |
573 | if (gtk_revealer_get_reveal_child (GTK_REVEALER (bar->revealer))) |
574 | { |
575 | stop_search_cb (entry: bar->entry, bar); |
576 | return GDK_EVENT_STOP; |
577 | } |
578 | |
579 | return GDK_EVENT_PROPAGATE; |
580 | } |
581 | |
582 | handled = GDK_EVENT_PROPAGATE; |
583 | preedit_changed = buffer_changed = FALSE; |
584 | preedit_change_id = g_signal_connect_swapped (bar->entry, "preedit-changed" , |
585 | G_CALLBACK (changed_cb), &preedit_changed); |
586 | buffer_change_id = g_signal_connect_swapped (bar->entry, "changed" , |
587 | G_CALLBACK (changed_cb), &buffer_changed); |
588 | |
589 | res = gtk_event_controller_key_forward (controller, widget: bar->entry); |
590 | |
591 | g_signal_handler_disconnect (instance: bar->entry, handler_id: preedit_change_id); |
592 | g_signal_handler_disconnect (instance: bar->entry, handler_id: buffer_change_id); |
593 | |
594 | if ((res && buffer_changed) || preedit_changed) |
595 | handled = GDK_EVENT_STOP; |
596 | } |
597 | |
598 | if (handled == GDK_EVENT_STOP) |
599 | gtk_revealer_set_reveal_child (GTK_REVEALER (bar->revealer), TRUE); |
600 | |
601 | return handled; |
602 | } |
603 | |
604 | /** |
605 | * gtk_search_bar_set_key_capture_widget: (attributes org.gtk.Method.set_property=key-capture-widget) |
606 | * @bar: a `GtkSearchBar` |
607 | * @widget: (nullable) (transfer none): a `GtkWidget` |
608 | * |
609 | * Sets @widget as the widget that @bar will capture key events |
610 | * from. |
611 | * |
612 | * If key events are handled by the search bar, the bar will |
613 | * be shown, and the entry populated with the entered text. |
614 | * |
615 | * Note that despite the name of this function, the events |
616 | * are only 'captured' in the bubble phase, which means that |
617 | * editable child widgets of @widget will receive text input |
618 | * before it gets captured. If that is not desired, you can |
619 | * capture and forward the events yourself with |
620 | * [method@Gtk.EventControllerKey.forward]. |
621 | */ |
622 | void |
623 | gtk_search_bar_set_key_capture_widget (GtkSearchBar *bar, |
624 | GtkWidget *widget) |
625 | { |
626 | g_return_if_fail (GTK_IS_SEARCH_BAR (bar)); |
627 | g_return_if_fail (!widget || GTK_IS_WIDGET (widget)); |
628 | |
629 | if (bar->capture_widget == widget) |
630 | return; |
631 | |
632 | if (bar->capture_widget) |
633 | { |
634 | gtk_widget_remove_controller (widget: bar->capture_widget, |
635 | controller: bar->capture_widget_controller); |
636 | g_object_remove_weak_pointer (G_OBJECT (bar->capture_widget), |
637 | weak_pointer_location: (gpointer *) &bar->capture_widget); |
638 | } |
639 | |
640 | bar->capture_widget = widget; |
641 | |
642 | if (widget) |
643 | { |
644 | g_object_add_weak_pointer (G_OBJECT (bar->capture_widget), |
645 | weak_pointer_location: (gpointer *) &bar->capture_widget); |
646 | |
647 | bar->capture_widget_controller = gtk_event_controller_key_new (); |
648 | gtk_event_controller_set_propagation_phase (controller: bar->capture_widget_controller, |
649 | phase: GTK_PHASE_BUBBLE); |
650 | g_signal_connect (bar->capture_widget_controller, "key-pressed" , |
651 | G_CALLBACK (capture_widget_key_handled), bar); |
652 | g_signal_connect (bar->capture_widget_controller, "key-released" , |
653 | G_CALLBACK (capture_widget_key_handled), bar); |
654 | gtk_widget_add_controller (widget, controller: bar->capture_widget_controller); |
655 | } |
656 | |
657 | g_object_notify_by_pspec (G_OBJECT (bar), pspec: widget_props[PROP_KEY_CAPTURE_WIDGET]); |
658 | } |
659 | |
660 | /** |
661 | * gtk_search_bar_get_key_capture_widget: (attributes org.gtk.Method.get_property=key-capture-widget) |
662 | * @bar: a `GtkSearchBar` |
663 | * |
664 | * Gets the widget that @bar is capturing key events from. |
665 | * |
666 | * Returns: (nullable) (transfer none): The key capture widget. |
667 | **/ |
668 | GtkWidget * |
669 | gtk_search_bar_get_key_capture_widget (GtkSearchBar *bar) |
670 | { |
671 | g_return_val_if_fail (GTK_IS_SEARCH_BAR (bar), NULL); |
672 | |
673 | return bar->capture_widget; |
674 | } |
675 | |
676 | /** |
677 | * gtk_search_bar_set_child: (attributes org.gtk.Method.set_property=child) |
678 | * @bar: a `GtkSearchBar` |
679 | * @child: (nullable): the child widget |
680 | * |
681 | * Sets the child widget of @bar. |
682 | */ |
683 | void |
684 | gtk_search_bar_set_child (GtkSearchBar *bar, |
685 | GtkWidget *child) |
686 | { |
687 | if (bar->child) |
688 | { |
689 | if (GTK_IS_EDITABLE (bar->child)) |
690 | gtk_search_bar_connect_entry (bar, NULL); |
691 | |
692 | gtk_center_box_set_center_widget (GTK_CENTER_BOX (bar->box_center), NULL); |
693 | } |
694 | |
695 | bar->child = child; |
696 | |
697 | if (bar->child) |
698 | { |
699 | gtk_center_box_set_center_widget (GTK_CENTER_BOX (bar->box_center), child); |
700 | |
701 | if (GTK_IS_EDITABLE (child)) |
702 | gtk_search_bar_connect_entry (bar, GTK_EDITABLE (child)); |
703 | } |
704 | |
705 | g_object_notify_by_pspec (G_OBJECT (bar), pspec: widget_props[PROP_CHILD]); |
706 | } |
707 | |
708 | /** |
709 | * gtk_search_bar_get_child: (attributes org.gtk.Method.get_property=child) |
710 | * @bar: a `GtkSearchBar` |
711 | * |
712 | * Gets the child widget of @bar. |
713 | * |
714 | * Returns: (nullable) (transfer none): the child widget of @bar |
715 | */ |
716 | GtkWidget * |
717 | gtk_search_bar_get_child (GtkSearchBar *bar) |
718 | { |
719 | return bar->child; |
720 | } |
721 | |