1/* gtkshortcutswindow.c
2 *
3 * Copyright (C) 2015 Christian Hergert <christian@hergert.me>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "config.h"
20
21#include "gtkshortcutswindowprivate.h"
22
23#include "gtkbox.h"
24#include "gtkbuildable.h"
25#include "gtkgrid.h"
26#include "gtkheaderbar.h"
27#include "gtkintl.h"
28#include "gtklabel.h"
29#include "gtklistbox.h"
30#include "gtkmain.h"
31#include "gtkmenubutton.h"
32#include "gtkpopover.h"
33#include "gtkprivate.h"
34#include "gtkscrolledwindow.h"
35#include "gtksearchbar.h"
36#include "gtksearchentry.h"
37#include "gtkshortcutssection.h"
38#include "gtkshortcutsgroup.h"
39#include "gtkshortcutsshortcutprivate.h"
40#include "gtksizegroup.h"
41#include "gtkstack.h"
42#include "gtktogglebutton.h"
43#include "gtktypebuiltins.h"
44#include "gtkwidgetprivate.h"
45
46/**
47 * GtkShortcutsWindow:
48 *
49 * A `GtkShortcutsWindow` shows information about the keyboard shortcuts
50 * and gestures of an application.
51 *
52 * The shortcuts can be grouped, and you can have multiple sections in this
53 * window, corresponding to the major modes of your application.
54 *
55 * Additionally, the shortcuts can be filtered by the current view, to avoid
56 * showing information that is not relevant in the current application context.
57 *
58 * The recommended way to construct a `GtkShortcutsWindow` is with
59 * [class@Gtk.Builder], by populating a `GtkShortcutsWindow` with one or
60 * more `GtkShortcutsSection` objects, which contain `GtkShortcutsGroups`
61 * that in turn contain objects of class `GtkShortcutsShortcut`.
62 *
63 * # A simple example:
64 *
65 * ![](gedit-shortcuts.png)
66 *
67 * This example has as single section. As you can see, the shortcut groups
68 * are arranged in columns, and spread across several pages if there are too
69 * many to find on a single page.
70 *
71 * The .ui file for this example can be found [here](https://gitlab.gnome.org/GNOME/gtk/tree/main/demos/gtk-demo/shortcuts-gedit.ui).
72 *
73 * # An example with multiple views:
74 *
75 * ![](clocks-shortcuts.png)
76 *
77 * This example shows a `GtkShortcutsWindow` that has been configured to show only
78 * the shortcuts relevant to the "stopwatch" view.
79 *
80 * The .ui file for this example can be found [here](https://gitlab.gnome.org/GNOME/gtk/tree/main/demos/gtk-demo/shortcuts-clocks.ui).
81 *
82 * # An example with multiple sections:
83 *
84 * ![](builder-shortcuts.png)
85 *
86 * This example shows a `GtkShortcutsWindow` with two sections, "Editor Shortcuts"
87 * and "Terminal Shortcuts".
88 *
89 * The .ui file for this example can be found [here](https://gitlab.gnome.org/GNOME/gtk/tree/main/demos/gtk-demo/shortcuts-builder.ui).
90 */
91
92struct _GtkShortcutsWindow
93{
94 GtkWindow parent_instance;
95
96 GHashTable *keywords;
97 char *initial_section;
98 char *last_section_name;
99 char *view_name;
100 GtkSizeGroup *search_text_group;
101 GtkSizeGroup *search_image_group;
102 GHashTable *search_items_hash;
103
104 GtkStack *stack;
105 GtkStack *title_stack;
106 GtkMenuButton *menu_button;
107 GtkSearchBar *search_bar;
108 GtkSearchEntry *search_entry;
109 GtkHeaderBar *header_bar;
110 GtkWidget *main_box;
111 GtkPopover *popover;
112 GtkListBox *list_box;
113 GtkBox *search_gestures;
114 GtkBox *search_shortcuts;
115
116 GtkWindow *window;
117 gulong keys_changed_id;
118};
119
120typedef struct
121{
122 GtkWindowClass parent_class;
123
124 void (*close) (GtkShortcutsWindow *self);
125 void (*search) (GtkShortcutsWindow *self);
126} GtkShortcutsWindowClass;
127
128typedef struct
129{
130 GtkShortcutsWindow *self;
131 GtkBuilder *builder;
132 GQueue *stack;
133 char *property_name;
134 guint translatable : 1;
135} ViewsParserData;
136
137static void gtk_shortcuts_window_buildable_iface_init (GtkBuildableIface *iface);
138
139
140G_DEFINE_TYPE_WITH_CODE (GtkShortcutsWindow, gtk_shortcuts_window, GTK_TYPE_WINDOW,
141 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
142 gtk_shortcuts_window_buildable_iface_init))
143
144
145enum {
146 CLOSE,
147 SEARCH,
148 LAST_SIGNAL
149};
150
151enum {
152 PROP_0,
153 PROP_SECTION_NAME,
154 PROP_VIEW_NAME,
155 LAST_PROP
156};
157
158static GParamSpec *properties[LAST_PROP];
159static guint signals[LAST_SIGNAL];
160
161
162static gboolean
163more_than_three_children (GtkWidget *widget)
164{
165 GtkWidget *child;
166 int i;
167
168 for (child = gtk_widget_get_first_child (widget), i = 0;
169 child != NULL;
170 child = gtk_widget_get_next_sibling (widget: child), i++)
171 {
172 if (i == 3)
173 return TRUE;
174 }
175
176 return FALSE;
177}
178
179static void
180update_title_stack (GtkShortcutsWindow *self)
181{
182 GtkWidget *visible_child;
183
184 visible_child = gtk_stack_get_visible_child (stack: self->stack);
185
186 if (GTK_IS_SHORTCUTS_SECTION (visible_child))
187 {
188 if (more_than_three_children (GTK_WIDGET (self->stack)))
189 {
190 char *title;
191
192 gtk_stack_set_visible_child_name (stack: self->title_stack, name: "sections");
193 g_object_get (object: visible_child, first_property_name: "title", &title, NULL);
194 gtk_menu_button_set_label (menu_button: self->menu_button, label: title);
195 g_free (mem: title);
196 }
197 else
198 {
199 gtk_stack_set_visible_child_name (stack: self->title_stack, name: "title");
200 }
201 }
202 else if (visible_child != NULL)
203 {
204 gtk_stack_set_visible_child_name (stack: self->title_stack, name: "search");
205 }
206}
207
208static void
209gtk_shortcuts_window_add_search_item (GtkWidget *child, gpointer data)
210{
211 GtkShortcutsWindow *self = data;
212 GtkWidget *item;
213 char *accelerator = NULL;
214 char *title = NULL;
215 char *hash_key = NULL;
216 GIcon *icon = NULL;
217 gboolean icon_set = FALSE;
218 gboolean subtitle_set = FALSE;
219 GtkTextDirection direction;
220 GtkShortcutType shortcut_type;
221 char *action_name = NULL;
222 char *subtitle;
223 char *str;
224 char *keywords;
225
226 if (GTK_IS_SHORTCUTS_SHORTCUT (child))
227 {
228 GEnumClass *class;
229 GEnumValue *value;
230
231 g_object_get (object: child,
232 first_property_name: "accelerator", &accelerator,
233 "title", &title,
234 "direction", &direction,
235 "icon-set", &icon_set,
236 "subtitle-set", &subtitle_set,
237 "shortcut-type", &shortcut_type,
238 "action-name", &action_name,
239 NULL);
240
241 class = G_ENUM_CLASS (g_type_class_ref (GTK_TYPE_SHORTCUT_TYPE));
242 value = g_enum_get_value (enum_class: class, value: shortcut_type);
243
244 hash_key = g_strdup_printf (format: "%s-%s-%s", title, value->value_nick, accelerator);
245
246 g_type_class_unref (g_class: class);
247
248 if (g_hash_table_contains (hash_table: self->search_items_hash, key: hash_key))
249 {
250 g_free (mem: hash_key);
251 g_free (mem: title);
252 g_free (mem: accelerator);
253 return;
254 }
255
256 g_hash_table_insert (hash_table: self->search_items_hash, key: hash_key, GINT_TO_POINTER (1));
257
258 item = g_object_new (GTK_TYPE_SHORTCUTS_SHORTCUT,
259 first_property_name: "accelerator", accelerator,
260 "title", title,
261 "direction", direction,
262 "shortcut-type", shortcut_type,
263 "accel-size-group", self->search_image_group,
264 "title-size-group", self->search_text_group,
265 "action-name", action_name,
266 NULL);
267 if (icon_set)
268 {
269 g_object_get (object: child, first_property_name: "icon", &icon, NULL);
270 g_object_set (object: item, first_property_name: "icon", icon, NULL);
271 g_clear_object (&icon);
272 }
273 if (subtitle_set)
274 {
275 g_object_get (object: child, first_property_name: "subtitle", &subtitle, NULL);
276 g_object_set (object: item, first_property_name: "subtitle", subtitle, NULL);
277 g_free (mem: subtitle);
278 }
279 str = g_strdup_printf (format: "%s %s", accelerator, title);
280 keywords = g_utf8_strdown (str, len: -1);
281
282 g_hash_table_insert (hash_table: self->keywords, key: item, value: keywords);
283 if (shortcut_type == GTK_SHORTCUT_ACCELERATOR)
284 gtk_box_append (GTK_BOX (self->search_shortcuts), child: item);
285 else
286 gtk_box_append (GTK_BOX (self->search_gestures), child: item);
287
288 g_free (mem: title);
289 g_free (mem: accelerator);
290 g_free (mem: str);
291 g_free (mem: action_name);
292 }
293 else
294 {
295 GtkWidget *widget;
296
297 for (widget = gtk_widget_get_first_child (widget: child);
298 widget != NULL;
299 widget = gtk_widget_get_next_sibling (widget))
300 gtk_shortcuts_window_add_search_item (child: widget, data: self);
301 }
302}
303
304static void
305section_notify_cb (GObject *section,
306 GParamSpec *pspec,
307 gpointer data)
308{
309 GtkShortcutsWindow *self = data;
310
311 if (strcmp (s1: pspec->name, s2: "section-name") == 0)
312 {
313 char *name;
314
315 g_object_get (object: section, first_property_name: "section-name", &name, NULL);
316 g_object_set (object: gtk_stack_get_page (stack: self->stack, GTK_WIDGET (section)), first_property_name: "name", name, NULL);
317 g_free (mem: name);
318 }
319 else if (strcmp (s1: pspec->name, s2: "title") == 0)
320 {
321 char *title;
322 GtkWidget *label;
323
324 label = g_object_get_data (object: section, key: "gtk-shortcuts-title");
325 g_object_get (object: section, first_property_name: "title", &title, NULL);
326 gtk_label_set_label (GTK_LABEL (label), str: title);
327 g_free (mem: title);
328 }
329}
330
331static void
332gtk_shortcuts_window_add_section (GtkShortcutsWindow *self,
333 GtkShortcutsSection *section)
334{
335 GtkListBoxRow *row;
336 char *title;
337 char *name;
338 const char *visible_section;
339 GtkWidget *label;
340 GtkWidget *child;
341
342 for (child = gtk_widget_get_first_child (GTK_WIDGET (section));
343 child != NULL;
344 child = gtk_widget_get_next_sibling (widget: child))
345 gtk_shortcuts_window_add_search_item (child, data: self);
346
347 g_object_get (object: section,
348 first_property_name: "section-name", &name,
349 "title", &title,
350 NULL);
351
352 g_signal_connect (section, "notify", G_CALLBACK (section_notify_cb), self);
353
354 if (name == NULL)
355 name = g_strdup (str: "shortcuts");
356
357 gtk_stack_add_titled (stack: self->stack, GTK_WIDGET (section), name, title);
358
359 visible_section = gtk_stack_get_visible_child_name (stack: self->stack);
360 if (strcmp (s1: visible_section, s2: "internal-search") == 0 ||
361 (self->initial_section && strcmp (s1: self->initial_section, s2: visible_section) == 0))
362 gtk_stack_set_visible_child (stack: self->stack, GTK_WIDGET (section));
363
364 row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
365 NULL);
366 g_object_set_data (G_OBJECT (row), key: "gtk-shortcuts-section", data: section);
367 label = g_object_new (GTK_TYPE_LABEL,
368 first_property_name: "margin-start", 6,
369 "margin-end", 6,
370 "margin-top", 6,
371 "margin-bottom", 6,
372 "label", title,
373 "xalign", 0.5f,
374 NULL);
375 g_object_set_data (G_OBJECT (section), key: "gtk-shortcuts-title", data: label);
376 gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), GTK_WIDGET (label));
377 gtk_list_box_insert (GTK_LIST_BOX (self->list_box), GTK_WIDGET (row), position: -1);
378
379 update_title_stack (self);
380
381 g_free (mem: name);
382 g_free (mem: title);
383}
384
385static GtkBuildableIface *parent_buildable_iface;
386
387static void
388gtk_shortcuts_window_buildable_add_child (GtkBuildable *buildable,
389 GtkBuilder *builder,
390 GObject *child,
391 const char *type)
392{
393 if (GTK_IS_SHORTCUTS_SECTION (child))
394 gtk_shortcuts_window_add_section (GTK_SHORTCUTS_WINDOW (buildable),
395 GTK_SHORTCUTS_SECTION (child));
396 else
397 parent_buildable_iface->add_child (buildable, builder, child, type);
398}
399
400static void
401gtk_shortcuts_window_buildable_iface_init (GtkBuildableIface *iface)
402{
403 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
404
405 iface->add_child = gtk_shortcuts_window_buildable_add_child;
406}
407
408static void
409gtk_shortcuts_window_set_view_name (GtkShortcutsWindow *self,
410 const char *view_name)
411{
412 GtkWidget *section;
413
414 g_free (mem: self->view_name);
415 self->view_name = g_strdup (str: view_name);
416
417 for (section = gtk_widget_get_first_child (GTK_WIDGET (self->stack));
418 section != NULL;
419 section = gtk_widget_get_next_sibling (widget: section))
420 {
421 if (GTK_IS_SHORTCUTS_SECTION (section))
422 g_object_set (object: section, first_property_name: "view-name", self->view_name, NULL);
423 }
424}
425
426static void
427gtk_shortcuts_window_set_section_name (GtkShortcutsWindow *self,
428 const char *section_name)
429{
430 GtkWidget *section = NULL;
431
432 g_free (mem: self->initial_section);
433 self->initial_section = g_strdup (str: section_name);
434
435 if (section_name)
436 section = gtk_stack_get_child_by_name (stack: self->stack, name: section_name);
437 if (section)
438 gtk_stack_set_visible_child (stack: self->stack, child: section);
439}
440
441static void
442update_accels_cb (GtkWidget *widget,
443 gpointer data)
444{
445 GtkShortcutsWindow *self = data;
446
447 if (GTK_IS_SHORTCUTS_SHORTCUT (widget))
448 gtk_shortcuts_shortcut_update_accel (GTK_SHORTCUTS_SHORTCUT (widget), window: self->window);
449 else
450 {
451 GtkWidget *child;
452
453 for (child = gtk_widget_get_first_child (GTK_WIDGET (widget));
454 child != NULL;
455 child = gtk_widget_get_next_sibling (widget: child ))
456 update_accels_cb (widget: child, data: self);
457 }
458}
459
460static void
461update_accels_for_actions (GtkShortcutsWindow *self)
462{
463 if (self->window)
464 {
465 GtkWidget *child;
466
467 for (child = gtk_widget_get_first_child (GTK_WIDGET (self));
468 child != NULL;
469 child = gtk_widget_get_next_sibling (widget: child))
470 update_accels_cb (widget: child, data: self);
471 }
472}
473
474static void
475keys_changed_handler (GtkWindow *window,
476 GtkShortcutsWindow *self)
477{
478 update_accels_for_actions (self);
479}
480
481void
482gtk_shortcuts_window_set_window (GtkShortcutsWindow *self,
483 GtkWindow *window)
484{
485 if (self->keys_changed_id)
486 {
487 g_signal_handler_disconnect (instance: self->window, handler_id: self->keys_changed_id);
488 self->keys_changed_id = 0;
489 }
490
491 self->window = window;
492
493 if (self->window)
494 self->keys_changed_id = g_signal_connect (window, "keys-changed",
495 G_CALLBACK (keys_changed_handler),
496 self);
497
498 update_accels_for_actions (self);
499}
500
501static void
502gtk_shortcuts_window__list_box__row_activated (GtkShortcutsWindow *self,
503 GtkListBoxRow *row,
504 GtkListBox *list_box)
505{
506 GtkWidget *section;
507
508 section = g_object_get_data (G_OBJECT (row), key: "gtk-shortcuts-section");
509 gtk_stack_set_visible_child (stack: self->stack, child: section);
510 gtk_popover_popdown (popover: self->popover);
511}
512
513static gboolean
514hidden_by_direction (GtkWidget *widget)
515{
516 if (GTK_IS_SHORTCUTS_SHORTCUT (widget))
517 {
518 GtkTextDirection dir;
519
520 g_object_get (object: widget, first_property_name: "direction", &dir, NULL);
521 if (dir != GTK_TEXT_DIR_NONE &&
522 dir != gtk_widget_get_direction (widget))
523 return TRUE;
524 }
525
526 return FALSE;
527}
528
529static void
530gtk_shortcuts_window__entry__changed (GtkShortcutsWindow *self,
531 GtkSearchEntry *search_entry)
532{
533 char *downcase = NULL;
534 GHashTableIter iter;
535 const char *text;
536 const char *last_section_name;
537 gpointer key;
538 gpointer value;
539 gboolean has_result;
540
541 text = gtk_editable_get_text (GTK_EDITABLE (search_entry));
542
543 if (!text || !*text)
544 {
545 if (self->last_section_name != NULL)
546 {
547 gtk_stack_set_visible_child_name (stack: self->stack, name: self->last_section_name);
548 return;
549
550 }
551 }
552
553 last_section_name = gtk_stack_get_visible_child_name (stack: self->stack);
554
555 if (g_strcmp0 (str1: last_section_name, str2: "internal-search") != 0 &&
556 g_strcmp0 (str1: last_section_name, str2: "no-search-results") != 0)
557 {
558 g_free (mem: self->last_section_name);
559 self->last_section_name = g_strdup (str: last_section_name);
560 }
561
562 downcase = g_utf8_strdown (str: text, len: -1);
563
564 g_hash_table_iter_init (iter: &iter, hash_table: self->keywords);
565
566 has_result = FALSE;
567 while (g_hash_table_iter_next (iter: &iter, key: &key, value: &value))
568 {
569 GtkWidget *widget = key;
570 const char *keywords = value;
571 gboolean match;
572
573 if (hidden_by_direction (widget))
574 match = FALSE;
575 else
576 match = strstr (haystack: keywords, needle: downcase) != NULL;
577
578 gtk_widget_set_visible (widget, visible: match);
579 has_result |= match;
580 }
581
582 g_free (mem: downcase);
583
584 if (has_result)
585 gtk_stack_set_visible_child_name (stack: self->stack, name: "internal-search");
586 else
587 gtk_stack_set_visible_child_name (stack: self->stack, name: "no-search-results");
588}
589
590static void
591gtk_shortcuts_window__search_mode__changed (GtkShortcutsWindow *self)
592{
593 if (!gtk_search_bar_get_search_mode (bar: self->search_bar))
594 {
595 if (self->last_section_name != NULL)
596 gtk_stack_set_visible_child_name (stack: self->stack, name: self->last_section_name);
597 }
598}
599
600static void
601gtk_shortcuts_window_close (GtkShortcutsWindow *self)
602{
603 gtk_window_close (GTK_WINDOW (self));
604}
605
606static void
607gtk_shortcuts_window_search (GtkShortcutsWindow *self)
608{
609 gtk_search_bar_set_search_mode (bar: self->search_bar, TRUE);
610}
611
612static void
613gtk_shortcuts_window_constructed (GObject *object)
614{
615 GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
616
617 G_OBJECT_CLASS (gtk_shortcuts_window_parent_class)->constructed (object);
618
619 if (self->initial_section != NULL)
620 gtk_stack_set_visible_child_name (stack: self->stack, name: self->initial_section);
621}
622
623static void
624gtk_shortcuts_window_finalize (GObject *object)
625{
626 GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
627
628 g_clear_pointer (&self->keywords, g_hash_table_unref);
629 g_clear_pointer (&self->initial_section, g_free);
630 g_clear_pointer (&self->view_name, g_free);
631 g_clear_pointer (&self->last_section_name, g_free);
632 g_clear_pointer (&self->search_items_hash, g_hash_table_unref);
633
634 g_clear_object (&self->search_image_group);
635 g_clear_object (&self->search_text_group);
636
637 G_OBJECT_CLASS (gtk_shortcuts_window_parent_class)->finalize (object);
638}
639
640static void
641gtk_shortcuts_window_dispose (GObject *object)
642{
643 GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
644
645 if (self->stack)
646 g_signal_handlers_disconnect_by_func (self->stack, G_CALLBACK (update_title_stack), self);
647
648 gtk_shortcuts_window_set_window (self, NULL);
649
650 self->stack = NULL;
651 self->search_bar = NULL;
652 self->main_box = NULL;
653
654 G_OBJECT_CLASS (gtk_shortcuts_window_parent_class)->dispose (object);
655}
656
657static void
658gtk_shortcuts_window_get_property (GObject *object,
659 guint prop_id,
660 GValue *value,
661 GParamSpec *pspec)
662{
663 GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
664
665 switch (prop_id)
666 {
667 case PROP_SECTION_NAME:
668 {
669 GtkWidget *child = gtk_stack_get_visible_child (stack: self->stack);
670
671 if (child != NULL)
672 {
673 char *name = NULL;
674
675 g_object_get (object: gtk_stack_get_page (stack: self->stack, child),
676 first_property_name: "name", &name,
677 NULL);
678 g_value_take_string (value, v_string: name);
679 }
680 }
681 break;
682
683 case PROP_VIEW_NAME:
684 g_value_set_string (value, v_string: self->view_name);
685 break;
686
687 default:
688 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
689 }
690}
691
692static void
693gtk_shortcuts_window_set_property (GObject *object,
694 guint prop_id,
695 const GValue *value,
696 GParamSpec *pspec)
697{
698 GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
699
700 switch (prop_id)
701 {
702 case PROP_SECTION_NAME:
703 gtk_shortcuts_window_set_section_name (self, section_name: g_value_get_string (value));
704 break;
705
706 case PROP_VIEW_NAME:
707 gtk_shortcuts_window_set_view_name (self, view_name: g_value_get_string (value));
708 break;
709
710 default:
711 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
712 }
713}
714
715static void
716gtk_shortcuts_window_unmap (GtkWidget *widget)
717{
718 GtkShortcutsWindow *self = (GtkShortcutsWindow *)widget;
719
720 gtk_search_bar_set_search_mode (bar: self->search_bar, FALSE);
721 gtk_editable_set_text (GTK_EDITABLE (self->search_entry), text: "");
722
723 GTK_WIDGET_CLASS (gtk_shortcuts_window_parent_class)->unmap (widget);
724}
725
726static void
727gtk_shortcuts_window_class_init (GtkShortcutsWindowClass *klass)
728{
729 GObjectClass *object_class = G_OBJECT_CLASS (klass);
730 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
731
732 object_class->constructed = gtk_shortcuts_window_constructed;
733 object_class->finalize = gtk_shortcuts_window_finalize;
734 object_class->get_property = gtk_shortcuts_window_get_property;
735 object_class->set_property = gtk_shortcuts_window_set_property;
736 object_class->dispose = gtk_shortcuts_window_dispose;
737
738 widget_class->unmap = gtk_shortcuts_window_unmap;
739
740 klass->close = gtk_shortcuts_window_close;
741 klass->search = gtk_shortcuts_window_search;
742
743 /**
744 * GtkShortcutsWindow:section-name:
745 *
746 * The name of the section to show.
747 *
748 * This should be the section-name of one of the `GtkShortcutsSection`
749 * objects that are in this shortcuts window.
750 */
751 properties[PROP_SECTION_NAME] =
752 g_param_spec_string (name: "section-name", P_("Section Name"), P_("Section Name"),
753 default_value: "internal-search",
754 flags: (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
755
756 /**
757 * GtkShortcutsWindow:view-name:
758 *
759 * The view name by which to filter the contents.
760 *
761 * This should correspond to the [property@Gtk.ShortcutsGroup:view]
762 * property of some of the [class@Gtk.ShortcutsGroup] objects that
763 * are inside this shortcuts window.
764 *
765 * Set this to %NULL to show all groups.
766 */
767 properties[PROP_VIEW_NAME] =
768 g_param_spec_string (name: "view-name", P_("View Name"), P_("View Name"),
769 NULL,
770 flags: (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
771
772 g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROP, pspecs: properties);
773
774 /**
775 * GtkShortcutsWindow::close:
776 *
777 * Emitted when the user uses a keybinding to close the window.
778 *
779 * This is a [keybinding signal](class.SignalAction.html).
780 *
781 * The default binding for this signal is the Escape key.
782 */
783 signals[CLOSE] = g_signal_new (I_("close"),
784 G_TYPE_FROM_CLASS (klass),
785 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
786 G_STRUCT_OFFSET (GtkShortcutsWindowClass, close),
787 NULL, NULL, NULL,
788 G_TYPE_NONE,
789 n_params: 0);
790
791 /**
792 * GtkShortcutsWindow::search:
793 *
794 * Emitted when the user uses a keybinding to start a search.
795 *
796 * This is a [keybinding signal](class.SignalAction.html).
797 *
798 * The default binding for this signal is Control-F.
799 */
800 signals[SEARCH] = g_signal_new (I_("search"),
801 G_TYPE_FROM_CLASS (klass),
802 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
803 G_STRUCT_OFFSET (GtkShortcutsWindowClass, search),
804 NULL, NULL, NULL,
805 G_TYPE_NONE,
806 n_params: 0);
807
808 gtk_widget_class_add_binding_signal (widget_class,
809 GDK_KEY_Escape, mods: 0,
810 signal: "close",
811 NULL);
812 gtk_widget_class_add_binding_signal (widget_class,
813 GDK_KEY_f, mods: GDK_CONTROL_MASK,
814 signal: "search",
815 NULL);
816
817 g_type_ensure (GTK_TYPE_SHORTCUTS_GROUP);
818 g_type_ensure (GTK_TYPE_SHORTCUTS_SHORTCUT);
819}
820
821static void
822gtk_shortcuts_window_init (GtkShortcutsWindow *self)
823{
824 GtkWidget *search_button;
825 GtkBox *box;
826 GtkWidget *scroller;
827 GtkWidget *label;
828 GtkWidget *empty;
829 PangoAttrList *attributes;
830
831 gtk_window_set_resizable (GTK_WINDOW (self), FALSE);
832
833 self->keywords = g_hash_table_new_full (NULL, NULL, NULL, value_destroy_func: g_free);
834 self->search_items_hash = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal, key_destroy_func: g_free, NULL);
835
836 self->search_text_group = gtk_size_group_new (mode: GTK_SIZE_GROUP_HORIZONTAL);
837 self->search_image_group = gtk_size_group_new (mode: GTK_SIZE_GROUP_HORIZONTAL);
838
839 self->header_bar = GTK_HEADER_BAR (gtk_header_bar_new ());
840 gtk_window_set_titlebar (GTK_WINDOW (self), GTK_WIDGET (self->header_bar));
841
842 search_button = g_object_new (GTK_TYPE_TOGGLE_BUTTON,
843 first_property_name: "icon-name", "edit-find-symbolic",
844 NULL);
845 gtk_header_bar_pack_start (GTK_HEADER_BAR (self->header_bar), child: search_button);
846
847 self->main_box = g_object_new (GTK_TYPE_BOX,
848 first_property_name: "orientation", GTK_ORIENTATION_VERTICAL,
849 NULL);
850 gtk_window_set_child (GTK_WINDOW (self), child: self->main_box);
851
852 self->search_bar = g_object_new (GTK_TYPE_SEARCH_BAR, NULL);
853 g_object_bind_property (source: self->search_bar, source_property: "search-mode-enabled",
854 target: search_button, target_property: "active",
855 flags: G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
856 gtk_box_append (GTK_BOX (self->main_box), GTK_WIDGET (self->search_bar));
857 gtk_search_bar_set_key_capture_widget (GTK_SEARCH_BAR (self->search_bar),
858 GTK_WIDGET (self));
859
860 self->stack = g_object_new (GTK_TYPE_STACK,
861 first_property_name: "hexpand", TRUE,
862 "vexpand", TRUE,
863 "hhomogeneous", TRUE,
864 "vhomogeneous", TRUE,
865 "transition-type", GTK_STACK_TRANSITION_TYPE_CROSSFADE,
866 NULL);
867 gtk_box_append (GTK_BOX (self->main_box), GTK_WIDGET (self->stack));
868
869 self->title_stack = g_object_new (GTK_TYPE_STACK,
870 NULL);
871 gtk_header_bar_set_title_widget (bar: self->header_bar, GTK_WIDGET (self->title_stack));
872
873 /* Translators: This is the window title for the shortcuts window in normal mode */
874 label = gtk_label_new (_("Shortcuts"));
875 gtk_widget_add_css_class (widget: label, css_class: "title");
876 gtk_stack_add_named (stack: self->title_stack, child: label, name: "title");
877
878 /* Translators: This is the window title for the shortcuts window in search mode */
879 label = gtk_label_new (_("Search Results"));
880 gtk_widget_add_css_class (widget: label, css_class: "title");
881 gtk_stack_add_named (stack: self->title_stack, child: label, name: "search");
882
883 self->menu_button = g_object_new (GTK_TYPE_MENU_BUTTON,
884 first_property_name: "focus-on-click", FALSE,
885 NULL);
886 gtk_widget_add_css_class (GTK_WIDGET (self->menu_button), css_class: "flat");
887 gtk_stack_add_named (stack: self->title_stack, GTK_WIDGET (self->menu_button), name: "sections");
888
889 self->popover = g_object_new (GTK_TYPE_POPOVER,
890 first_property_name: "position", GTK_POS_BOTTOM,
891 NULL);
892 gtk_menu_button_set_popover (menu_button: self->menu_button, GTK_WIDGET (self->popover));
893
894 self->list_box = g_object_new (GTK_TYPE_LIST_BOX,
895 first_property_name: "selection-mode", GTK_SELECTION_NONE,
896 NULL);
897 g_signal_connect_object (instance: self->list_box,
898 detailed_signal: "row-activated",
899 G_CALLBACK (gtk_shortcuts_window__list_box__row_activated),
900 gobject: self,
901 connect_flags: G_CONNECT_SWAPPED);
902 gtk_popover_set_child (GTK_POPOVER (self->popover), GTK_WIDGET (self->list_box));
903
904 self->search_entry = GTK_SEARCH_ENTRY (gtk_search_entry_new ());
905 gtk_search_bar_set_child (GTK_SEARCH_BAR (self->search_bar), GTK_WIDGET (self->search_entry));
906
907 g_object_set (object: self->search_entry,
908 /* Translators: This is placeholder text for the search entry in the shortcuts window */
909 first_property_name: "placeholder-text", _("Search Shortcuts"),
910 "width-chars", 40,
911 NULL);
912 g_signal_connect_object (instance: self->search_entry,
913 detailed_signal: "search-changed",
914 G_CALLBACK (gtk_shortcuts_window__entry__changed),
915 gobject: self,
916 connect_flags: G_CONNECT_SWAPPED);
917 g_signal_connect_object (instance: self->search_bar,
918 detailed_signal: "notify::search-mode-enabled",
919 G_CALLBACK (gtk_shortcuts_window__search_mode__changed),
920 gobject: self,
921 connect_flags: G_CONNECT_SWAPPED);
922
923 scroller = gtk_scrolled_window_new ();
924 box = g_object_new (GTK_TYPE_BOX,
925 first_property_name: "halign", GTK_ALIGN_CENTER,
926 "orientation", GTK_ORIENTATION_VERTICAL,
927 NULL);
928 gtk_widget_add_css_class (GTK_WIDGET (box), css_class: "shortcuts-search-results");
929 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scroller), GTK_WIDGET (box));
930 gtk_stack_add_named (stack: self->stack, child: scroller, name: "internal-search");
931
932 self->search_shortcuts = g_object_new (GTK_TYPE_BOX,
933 first_property_name: "halign", GTK_ALIGN_CENTER,
934 "spacing", 6,
935 "orientation", GTK_ORIENTATION_VERTICAL,
936 NULL);
937 gtk_box_append (GTK_BOX (box), GTK_WIDGET (self->search_shortcuts));
938
939 self->search_gestures = g_object_new (GTK_TYPE_BOX,
940 first_property_name: "halign", GTK_ALIGN_CENTER,
941 "spacing", 6,
942 "orientation", GTK_ORIENTATION_VERTICAL,
943 NULL);
944 gtk_box_append (GTK_BOX (box), GTK_WIDGET (self->search_gestures));
945
946 empty = g_object_new (GTK_TYPE_GRID,
947 first_property_name: "row-spacing", 12,
948 "margin-start", 12,
949 "margin-end", 12,
950 "margin-top", 12,
951 "margin-bottom", 12,
952 "hexpand", TRUE,
953 "vexpand", TRUE,
954 "halign", GTK_ALIGN_CENTER,
955 "valign", GTK_ALIGN_CENTER,
956 NULL);
957 gtk_widget_add_css_class (widget: empty, css_class: "dim-label");
958 gtk_grid_attach (GTK_GRID (empty),
959 child: g_object_new (GTK_TYPE_IMAGE,
960 first_property_name: "icon-name", "edit-find-symbolic",
961 "pixel-size", 72,
962 NULL),
963 column: 0, row: 0, width: 1, height: 1);
964 attributes = pango_attr_list_new ();
965 pango_attr_list_insert (list: attributes, attr: pango_attr_weight_new (weight: PANGO_WEIGHT_BOLD));
966 pango_attr_list_insert (list: attributes, attr: pango_attr_scale_new (scale_factor: 1.44));
967 label = g_object_new (GTK_TYPE_LABEL,
968 first_property_name: "label", _("No Results Found"),
969 "attributes", attributes,
970 NULL);
971 pango_attr_list_unref (list: attributes);
972 gtk_grid_attach (GTK_GRID (empty), child: label, column: 0, row: 1, width: 1, height: 1);
973 label = g_object_new (GTK_TYPE_LABEL,
974 first_property_name: "label", _("Try a different search"),
975 NULL);
976 gtk_grid_attach (GTK_GRID (empty), child: label, column: 0, row: 2, width: 1, height: 1);
977
978 gtk_stack_add_named (stack: self->stack, child: empty, name: "no-search-results");
979
980 g_signal_connect_object (instance: self->stack, detailed_signal: "notify::visible-child",
981 G_CALLBACK (update_title_stack), gobject: self, connect_flags: G_CONNECT_SWAPPED);
982
983}
984

source code of gtk/gtk/gtkshortcutswindow.c