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/master/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/master/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/master/demos/gtk-demo/shortcuts-builder.ui).
90  */
91 
92 struct _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 
120 typedef struct
121 {
122   GtkWindowClass parent_class;
123 
124   void (*close)  (GtkShortcutsWindow *self);
125   void (*search) (GtkShortcutsWindow *self);
126 } GtkShortcutsWindowClass;
127 
128 typedef struct
129 {
130   GtkShortcutsWindow *self;
131   GtkBuilder        *builder;
132   GQueue            *stack;
133   char              *property_name;
134   guint              translatable : 1;
135 } ViewsParserData;
136 
137 static void gtk_shortcuts_window_buildable_iface_init (GtkBuildableIface *iface);
138 
139 
140 G_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 
145 enum {
146   CLOSE,
147   SEARCH,
148   LAST_SIGNAL
149 };
150 
151 enum {
152   PROP_0,
153   PROP_SECTION_NAME,
154   PROP_VIEW_NAME,
155   LAST_PROP
156 };
157 
158 static GParamSpec *properties[LAST_PROP];
159 static guint signals[LAST_SIGNAL];
160 
161 
162 static gboolean
more_than_three_children(GtkWidget * widget)163 more_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 (child), i++)
171     {
172       if (i == 3)
173         return TRUE;
174     }
175 
176   return FALSE;
177 }
178 
179 static void
update_title_stack(GtkShortcutsWindow * self)180 update_title_stack (GtkShortcutsWindow *self)
181 {
182   GtkWidget *visible_child;
183 
184   visible_child = gtk_stack_get_visible_child (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 (self->title_stack, "sections");
193           g_object_get (visible_child, "title", &title, NULL);
194           gtk_menu_button_set_label (self->menu_button, title);
195           g_free (title);
196         }
197       else
198         {
199           gtk_stack_set_visible_child_name (self->title_stack, "title");
200         }
201     }
202   else if (visible_child != NULL)
203     {
204       gtk_stack_set_visible_child_name (self->title_stack, "search");
205     }
206 }
207 
208 static void
gtk_shortcuts_window_add_search_item(GtkWidget * child,gpointer data)209 gtk_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 (child,
232                     "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 (class, shortcut_type);
243 
244       hash_key = g_strdup_printf ("%s-%s-%s", title, value->value_nick, accelerator);
245 
246       g_type_class_unref (class);
247 
248       if (g_hash_table_contains (self->search_items_hash, hash_key))
249         {
250           g_free (hash_key);
251           g_free (title);
252           g_free (accelerator);
253           return;
254         }
255 
256       g_hash_table_insert (self->search_items_hash, hash_key, GINT_TO_POINTER (1));
257 
258       item = g_object_new (GTK_TYPE_SHORTCUTS_SHORTCUT,
259                            "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 (child, "icon", &icon, NULL);
270           g_object_set (item, "icon", icon, NULL);
271           g_clear_object (&icon);
272         }
273       if (subtitle_set)
274         {
275           g_object_get (child, "subtitle", &subtitle, NULL);
276           g_object_set (item, "subtitle", subtitle, NULL);
277           g_free (subtitle);
278         }
279       str = g_strdup_printf ("%s %s", accelerator, title);
280       keywords = g_utf8_strdown (str, -1);
281 
282       g_hash_table_insert (self->keywords, item, keywords);
283       if (shortcut_type == GTK_SHORTCUT_ACCELERATOR)
284         gtk_box_append (GTK_BOX (self->search_shortcuts), item);
285       else
286         gtk_box_append (GTK_BOX (self->search_gestures), item);
287 
288       g_free (title);
289       g_free (accelerator);
290       g_free (str);
291       g_free (action_name);
292     }
293   else
294     {
295       GtkWidget *widget;
296 
297       for (widget = gtk_widget_get_first_child (child);
298            widget != NULL;
299            widget = gtk_widget_get_next_sibling (widget))
300         gtk_shortcuts_window_add_search_item (widget, self);
301     }
302 }
303 
304 static void
section_notify_cb(GObject * section,GParamSpec * pspec,gpointer data)305 section_notify_cb (GObject    *section,
306                    GParamSpec *pspec,
307                    gpointer    data)
308 {
309   GtkShortcutsWindow *self = data;
310 
311   if (strcmp (pspec->name, "section-name") == 0)
312     {
313       char *name;
314 
315       g_object_get (section, "section-name", &name, NULL);
316       g_object_set (gtk_stack_get_page (self->stack, GTK_WIDGET (section)), "name", name, NULL);
317       g_free (name);
318     }
319   else if (strcmp (pspec->name, "title") == 0)
320     {
321       char *title;
322       GtkWidget *label;
323 
324       label = g_object_get_data (section, "gtk-shortcuts-title");
325       g_object_get (section, "title", &title, NULL);
326       gtk_label_set_label (GTK_LABEL (label), title);
327       g_free (title);
328     }
329 }
330 
331 static void
gtk_shortcuts_window_add_section(GtkShortcutsWindow * self,GtkShortcutsSection * section)332 gtk_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 (child))
345     gtk_shortcuts_window_add_search_item (child, self);
346 
347   g_object_get (section,
348                 "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 ("shortcuts");
356 
357   gtk_stack_add_titled (self->stack, GTK_WIDGET (section), name, title);
358 
359   visible_section = gtk_stack_get_visible_child_name (self->stack);
360   if (strcmp (visible_section, "internal-search") == 0 ||
361       (self->initial_section && strcmp (self->initial_section, visible_section) == 0))
362     gtk_stack_set_visible_child (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), "gtk-shortcuts-section", section);
367   label = g_object_new (GTK_TYPE_LABEL,
368                         "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), "gtk-shortcuts-title", 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), -1);
378 
379   update_title_stack (self);
380 
381   g_free (name);
382   g_free (title);
383 }
384 
385 static GtkBuildableIface *parent_buildable_iface;
386 
387 static void
gtk_shortcuts_window_buildable_add_child(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const char * type)388 gtk_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 
400 static void
gtk_shortcuts_window_buildable_iface_init(GtkBuildableIface * iface)401 gtk_shortcuts_window_buildable_iface_init (GtkBuildableIface *iface)
402 {
403   parent_buildable_iface = g_type_interface_peek_parent (iface);
404 
405   iface->add_child = gtk_shortcuts_window_buildable_add_child;
406 }
407 
408 static void
gtk_shortcuts_window_set_view_name(GtkShortcutsWindow * self,const char * view_name)409 gtk_shortcuts_window_set_view_name (GtkShortcutsWindow *self,
410                                     const char         *view_name)
411 {
412   GtkWidget *section;
413 
414   g_free (self->view_name);
415   self->view_name = g_strdup (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 (section))
420     {
421       if (GTK_IS_SHORTCUTS_SECTION (section))
422         g_object_set (section, "view-name", self->view_name, NULL);
423     }
424 }
425 
426 static void
gtk_shortcuts_window_set_section_name(GtkShortcutsWindow * self,const char * section_name)427 gtk_shortcuts_window_set_section_name (GtkShortcutsWindow *self,
428                                        const char         *section_name)
429 {
430   GtkWidget *section = NULL;
431 
432   g_free (self->initial_section);
433   self->initial_section = g_strdup (section_name);
434 
435   if (section_name)
436     section = gtk_stack_get_child_by_name (self->stack, section_name);
437   if (section)
438     gtk_stack_set_visible_child (self->stack, section);
439 }
440 
441 static void
update_accels_cb(GtkWidget * widget,gpointer data)442 update_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), 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 (child ))
456         update_accels_cb (child, self);
457     }
458 }
459 
460 static void
update_accels_for_actions(GtkShortcutsWindow * self)461 update_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 (child))
470         update_accels_cb (child, self);
471     }
472 }
473 
474 static void
keys_changed_handler(GtkWindow * window,GtkShortcutsWindow * self)475 keys_changed_handler (GtkWindow          *window,
476                       GtkShortcutsWindow *self)
477 {
478   update_accels_for_actions (self);
479 }
480 
481 void
gtk_shortcuts_window_set_window(GtkShortcutsWindow * self,GtkWindow * window)482 gtk_shortcuts_window_set_window (GtkShortcutsWindow *self,
483                                  GtkWindow          *window)
484 {
485   if (self->keys_changed_id)
486     {
487       g_signal_handler_disconnect (self->window, 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 
501 static void
gtk_shortcuts_window__list_box__row_activated(GtkShortcutsWindow * self,GtkListBoxRow * row,GtkListBox * list_box)502 gtk_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), "gtk-shortcuts-section");
509   gtk_stack_set_visible_child (self->stack, section);
510   gtk_popover_popdown (self->popover);
511 }
512 
513 static gboolean
hidden_by_direction(GtkWidget * widget)514 hidden_by_direction (GtkWidget *widget)
515 {
516   if (GTK_IS_SHORTCUTS_SHORTCUT (widget))
517     {
518       GtkTextDirection dir;
519 
520       g_object_get (widget, "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 
529 static void
gtk_shortcuts_window__entry__changed(GtkShortcutsWindow * self,GtkSearchEntry * search_entry)530 gtk_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 (self->stack, self->last_section_name);
548           return;
549 
550         }
551     }
552 
553   last_section_name = gtk_stack_get_visible_child_name (self->stack);
554 
555   if (g_strcmp0 (last_section_name, "internal-search") != 0 &&
556       g_strcmp0 (last_section_name, "no-search-results") != 0)
557     {
558       g_free (self->last_section_name);
559       self->last_section_name = g_strdup (last_section_name);
560     }
561 
562   downcase = g_utf8_strdown (text, -1);
563 
564   g_hash_table_iter_init (&iter, self->keywords);
565 
566   has_result = FALSE;
567   while (g_hash_table_iter_next (&iter, &key, &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 (keywords, downcase) != NULL;
577 
578       gtk_widget_set_visible (widget, match);
579       has_result |= match;
580     }
581 
582   g_free (downcase);
583 
584   if (has_result)
585     gtk_stack_set_visible_child_name (self->stack, "internal-search");
586   else
587     gtk_stack_set_visible_child_name (self->stack, "no-search-results");
588 }
589 
590 static void
gtk_shortcuts_window__search_mode__changed(GtkShortcutsWindow * self)591 gtk_shortcuts_window__search_mode__changed (GtkShortcutsWindow *self)
592 {
593   if (!gtk_search_bar_get_search_mode (self->search_bar))
594     {
595       if (self->last_section_name != NULL)
596         gtk_stack_set_visible_child_name (self->stack, self->last_section_name);
597     }
598 }
599 
600 static void
gtk_shortcuts_window_close(GtkShortcutsWindow * self)601 gtk_shortcuts_window_close (GtkShortcutsWindow *self)
602 {
603   gtk_window_close (GTK_WINDOW (self));
604 }
605 
606 static void
gtk_shortcuts_window_search(GtkShortcutsWindow * self)607 gtk_shortcuts_window_search (GtkShortcutsWindow *self)
608 {
609   gtk_search_bar_set_search_mode (self->search_bar, TRUE);
610 }
611 
612 static void
gtk_shortcuts_window_constructed(GObject * object)613 gtk_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 (self->stack, self->initial_section);
621 }
622 
623 static void
gtk_shortcuts_window_finalize(GObject * object)624 gtk_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 
640 static void
gtk_shortcuts_window_dispose(GObject * object)641 gtk_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 
657 static void
gtk_shortcuts_window_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)658 gtk_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 (self->stack);
670 
671         if (child != NULL)
672           {
673             char *name = NULL;
674 
675             g_object_get (gtk_stack_get_page (self->stack, child),
676                                      "name", &name,
677                                      NULL);
678             g_value_take_string (value, name);
679           }
680       }
681       break;
682 
683     case PROP_VIEW_NAME:
684       g_value_set_string (value, self->view_name);
685       break;
686 
687     default:
688       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
689     }
690 }
691 
692 static void
gtk_shortcuts_window_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)693 gtk_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, g_value_get_string (value));
704       break;
705 
706     case PROP_VIEW_NAME:
707       gtk_shortcuts_window_set_view_name (self, g_value_get_string (value));
708       break;
709 
710     default:
711       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
712     }
713 }
714 
715 static void
gtk_shortcuts_window_unmap(GtkWidget * widget)716 gtk_shortcuts_window_unmap (GtkWidget *widget)
717 {
718   GtkShortcutsWindow *self = (GtkShortcutsWindow *)widget;
719 
720   gtk_search_bar_set_search_mode (self->search_bar, FALSE);
721   gtk_editable_set_text (GTK_EDITABLE (self->search_entry), "");
722 
723   GTK_WIDGET_CLASS (gtk_shortcuts_window_parent_class)->unmap (widget);
724 }
725 
726 static void
gtk_shortcuts_window_class_init(GtkShortcutsWindowClass * klass)727 gtk_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 ("section-name", P_("Section Name"), P_("Section Name"),
753                          "internal-search",
754                          (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 ("view-name", P_("View Name"), P_("View Name"),
769                          NULL,
770                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
771 
772   g_object_class_install_properties (object_class, LAST_PROP, 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                                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
786                                  G_STRUCT_OFFSET (GtkShortcutsWindowClass, close),
787                                  NULL, NULL, NULL,
788                                  G_TYPE_NONE,
789                                  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                                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
803                                  G_STRUCT_OFFSET (GtkShortcutsWindowClass, search),
804                                  NULL, NULL, NULL,
805                                  G_TYPE_NONE,
806                                  0);
807 
808   gtk_widget_class_add_binding_signal (widget_class,
809                                        GDK_KEY_Escape, 0,
810                                        "close",
811                                        NULL);
812   gtk_widget_class_add_binding_signal (widget_class,
813                                        GDK_KEY_f, GDK_CONTROL_MASK,
814                                        "search",
815                                        NULL);
816 
817   g_type_ensure (GTK_TYPE_SHORTCUTS_GROUP);
818   g_type_ensure (GTK_TYPE_SHORTCUTS_SHORTCUT);
819 }
820 
821 static void
gtk_shortcuts_window_init(GtkShortcutsWindow * self)822 gtk_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, g_free);
834   self->search_items_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
835 
836   self->search_text_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
837   self->search_image_group = gtk_size_group_new (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                                 "icon-name", "edit-find-symbolic",
844                                 NULL);
845   gtk_header_bar_pack_start (GTK_HEADER_BAR (self->header_bar), search_button);
846 
847   self->main_box = g_object_new (GTK_TYPE_BOX,
848                            "orientation", GTK_ORIENTATION_VERTICAL,
849                            NULL);
850   gtk_window_set_child (GTK_WINDOW (self), self->main_box);
851 
852   self->search_bar = g_object_new (GTK_TYPE_SEARCH_BAR, NULL);
853   g_object_bind_property (self->search_bar, "search-mode-enabled",
854                           search_button, "active",
855                           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                               "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 (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 (label, "title");
876   gtk_stack_add_named (self->title_stack, label, "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 (label, "title");
881   gtk_stack_add_named (self->title_stack, label, "search");
882 
883   self->menu_button = g_object_new (GTK_TYPE_MENU_BUTTON,
884                                     "focus-on-click", FALSE,
885                                     NULL);
886   gtk_widget_add_css_class (GTK_WIDGET (self->menu_button), "flat");
887   gtk_stack_add_named (self->title_stack, GTK_WIDGET (self->menu_button), "sections");
888 
889   self->popover = g_object_new (GTK_TYPE_POPOVER,
890                                 "position", GTK_POS_BOTTOM,
891                                 NULL);
892   gtk_menu_button_set_popover (self->menu_button, GTK_WIDGET (self->popover));
893 
894   self->list_box = g_object_new (GTK_TYPE_LIST_BOX,
895                                  "selection-mode", GTK_SELECTION_NONE,
896                                  NULL);
897   g_signal_connect_object (self->list_box,
898                            "row-activated",
899                            G_CALLBACK (gtk_shortcuts_window__list_box__row_activated),
900                            self,
901                            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 (self->search_entry,
908                 /* Translators: This is placeholder text for the search entry in the shortcuts window */
909                 "placeholder-text", _("Search Shortcuts"),
910                 "width-chars", 40,
911                 NULL);
912   g_signal_connect_object (self->search_entry,
913                            "search-changed",
914                            G_CALLBACK (gtk_shortcuts_window__entry__changed),
915                            self,
916                            G_CONNECT_SWAPPED);
917   g_signal_connect_object (self->search_bar,
918                            "notify::search-mode-enabled",
919                            G_CALLBACK (gtk_shortcuts_window__search_mode__changed),
920                            self,
921                            G_CONNECT_SWAPPED);
922 
923   scroller = gtk_scrolled_window_new ();
924   box = g_object_new (GTK_TYPE_BOX,
925                       "halign", GTK_ALIGN_CENTER,
926                       "orientation", GTK_ORIENTATION_VERTICAL,
927                       NULL);
928   gtk_widget_add_css_class (GTK_WIDGET (box), "shortcuts-search-results");
929   gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scroller), GTK_WIDGET (box));
930   gtk_stack_add_named (self->stack, scroller, "internal-search");
931 
932   self->search_shortcuts = g_object_new (GTK_TYPE_BOX,
933                                          "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                                         "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                         "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 (empty, "dim-label");
958   gtk_grid_attach (GTK_GRID (empty),
959                    g_object_new (GTK_TYPE_IMAGE,
960                                  "icon-name", "edit-find-symbolic",
961                                  "pixel-size", 72,
962                                  NULL),
963                    0, 0, 1, 1);
964   attributes = pango_attr_list_new ();
965   pango_attr_list_insert (attributes, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
966   pango_attr_list_insert (attributes, pango_attr_scale_new (1.44));
967   label = g_object_new (GTK_TYPE_LABEL,
968                         "label", _("No Results Found"),
969                         "attributes", attributes,
970                         NULL);
971   pango_attr_list_unref (attributes);
972   gtk_grid_attach (GTK_GRID (empty), label, 0, 1, 1, 1);
973   label = g_object_new (GTK_TYPE_LABEL,
974                         "label", _("Try a different search"),
975                         NULL);
976   gtk_grid_attach (GTK_GRID (empty), label, 0, 2, 1, 1);
977 
978   gtk_stack_add_named (self->stack, empty, "no-search-results");
979 
980   g_signal_connect_object (self->stack, "notify::visible-child",
981                            G_CALLBACK (update_title_stack), self, G_CONNECT_SWAPPED);
982 
983 }
984