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 "gtkshortcutswindow.h"
22 #include "gtkscrolledwindow.h"
23 #include "gtkshortcutssection.h"
24 #include "gtkshortcutsgroup.h"
25 #include "gtkshortcutsshortcutprivate.h"
26 #include "gtksearchbar.h"
27 #include "gtksearchentry.h"
28 #include "gtkwidgetprivate.h"
29 #include "gtkprivate.h"
30 #include "gtkintl.h"
31
32 /**
33 * SECTION:gtkshortcutswindow
34 * @Title: GtkShortcutsWindow
35 * @Short_description: Toplevel which shows help for shortcuts
36 *
37 * A GtkShortcutsWindow shows brief information about the keyboard shortcuts
38 * and gestures of an application. The shortcuts can be grouped, and you can
39 * have multiple sections in this window, corresponding to the major modes of
40 * your application.
41 *
42 * Additionally, the shortcuts can be filtered by the current view, to avoid
43 * showing information that is not relevant in the current application context.
44 *
45 * The recommended way to construct a GtkShortcutsWindow is with GtkBuilder,
46 * by populating a #GtkShortcutsWindow with one or more #GtkShortcutsSection
47 * objects, which contain #GtkShortcutsGroups that in turn contain objects of
48 * class #GtkShortcutsShortcut.
49 *
50 * # A simple example:
51 *
52 * ![](gedit-shortcuts.png)
53 *
54 * This example has as single section. As you can see, the shortcut groups
55 * are arranged in columns, and spread across several pages if there are too
56 * many to find on a single page.
57 *
58 * The .ui file for this example can be found [here](https://git.gnome.org/browse/gtk+/tree/demos/gtk-demo/shortcuts-gedit.ui).
59 *
60 * # An example with multiple views:
61 *
62 * ![](clocks-shortcuts.png)
63 *
64 * This example shows a #GtkShortcutsWindow that has been configured to show only
65 * the shortcuts relevant to the "stopwatch" view.
66 *
67 * The .ui file for this example can be found [here](https://git.gnome.org/browse/gtk+/tree/demos/gtk-demo/shortcuts-clocks.ui).
68 *
69 * # An example with multiple sections:
70 *
71 * ![](builder-shortcuts.png)
72 *
73 * This example shows a #GtkShortcutsWindow with two sections, "Editor Shortcuts"
74 * and "Terminal Shortcuts".
75 *
76 * The .ui file for this example can be found [here](https://git.gnome.org/browse/gtk+/tree/demos/gtk-demo/shortcuts-builder.ui).
77 */
78
79 typedef struct
80 {
81 GHashTable *keywords;
82 gchar *initial_section;
83 gchar *last_section_name;
84 gchar *view_name;
85 GtkSizeGroup *search_text_group;
86 GtkSizeGroup *search_image_group;
87 GHashTable *search_items_hash;
88
89 GtkStack *stack;
90 GtkStack *title_stack;
91 GtkMenuButton *menu_button;
92 GtkLabel *menu_label;
93 GtkSearchBar *search_bar;
94 GtkSearchEntry *search_entry;
95 GtkHeaderBar *header_bar;
96 GtkWidget *main_box;
97 GtkPopover *popover;
98 GtkListBox *list_box;
99 GtkBox *search_gestures;
100 GtkBox *search_shortcuts;
101
102 GtkWindow *window;
103 gulong keys_changed_id;
104 } GtkShortcutsWindowPrivate;
105
106 typedef struct
107 {
108 GtkShortcutsWindow *self;
109 GtkBuilder *builder;
110 GQueue *stack;
111 gchar *property_name;
112 guint translatable : 1;
113 } ViewsParserData;
114
115
116 G_DEFINE_TYPE_WITH_PRIVATE (GtkShortcutsWindow, gtk_shortcuts_window, GTK_TYPE_WINDOW)
117
118
119 enum {
120 CLOSE,
121 SEARCH,
122 LAST_SIGNAL
123 };
124
125 enum {
126 PROP_0,
127 PROP_SECTION_NAME,
128 PROP_VIEW_NAME,
129 LAST_PROP
130 };
131
132 static GParamSpec *properties[LAST_PROP];
133 static guint signals[LAST_SIGNAL];
134
135
136 static gint
number_of_children(GtkContainer * container)137 number_of_children (GtkContainer *container)
138 {
139 GList *children;
140 gint n;
141
142 children = gtk_container_get_children (container);
143 n = g_list_length (children);
144 g_list_free (children);
145
146 return n;
147 }
148
149 static void
update_title_stack(GtkShortcutsWindow * self)150 update_title_stack (GtkShortcutsWindow *self)
151 {
152 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
153 GtkWidget *visible_child;
154
155 visible_child = gtk_stack_get_visible_child (priv->stack);
156
157 if (GTK_IS_SHORTCUTS_SECTION (visible_child))
158 {
159 if (number_of_children (GTK_CONTAINER (priv->stack)) > 3)
160 {
161 gchar *title;
162
163 gtk_stack_set_visible_child_name (priv->title_stack, "sections");
164 g_object_get (visible_child, "title", &title, NULL);
165 gtk_label_set_label (priv->menu_label, title);
166 g_free (title);
167 }
168 else
169 {
170 gtk_stack_set_visible_child_name (priv->title_stack, "title");
171 }
172 }
173 else if (visible_child != NULL)
174 {
175 gtk_stack_set_visible_child_name (priv->title_stack, "search");
176 }
177 }
178
179 static void
gtk_shortcuts_window_add_search_item(GtkWidget * child,gpointer data)180 gtk_shortcuts_window_add_search_item (GtkWidget *child, gpointer data)
181 {
182 GtkShortcutsWindow *self = data;
183 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
184 GtkWidget *item;
185 gchar *accelerator = NULL;
186 gchar *title = NULL;
187 gchar *hash_key = NULL;
188 GIcon *icon = NULL;
189 gboolean icon_set = FALSE;
190 gboolean subtitle_set = FALSE;
191 GtkTextDirection direction;
192 GtkShortcutType shortcut_type;
193 gchar *action_name = NULL;
194 gchar *subtitle;
195 gchar *str;
196 gchar *keywords;
197
198 if (GTK_IS_SHORTCUTS_SHORTCUT (child))
199 {
200 GEnumClass *class;
201 GEnumValue *value;
202
203 g_object_get (child,
204 "accelerator", &accelerator,
205 "title", &title,
206 "direction", &direction,
207 "icon-set", &icon_set,
208 "subtitle-set", &subtitle_set,
209 "shortcut-type", &shortcut_type,
210 "action-name", &action_name,
211 NULL);
212
213 class = G_ENUM_CLASS (g_type_class_ref (GTK_TYPE_SHORTCUT_TYPE));
214 value = g_enum_get_value (class, shortcut_type);
215
216 hash_key = g_strdup_printf ("%s-%s-%s", title, value->value_nick, accelerator);
217
218 g_type_class_unref (class);
219
220 if (g_hash_table_contains (priv->search_items_hash, hash_key))
221 {
222 g_free (hash_key);
223 g_free (title);
224 g_free (accelerator);
225 return;
226 }
227
228 g_hash_table_insert (priv->search_items_hash, hash_key, GINT_TO_POINTER (1));
229
230 item = g_object_new (GTK_TYPE_SHORTCUTS_SHORTCUT,
231 "accelerator", accelerator,
232 "title", title,
233 "direction", direction,
234 "shortcut-type", shortcut_type,
235 "accel-size-group", priv->search_image_group,
236 "title-size-group", priv->search_text_group,
237 "action-name", action_name,
238 NULL);
239 if (icon_set)
240 {
241 g_object_get (child, "icon", &icon, NULL);
242 g_object_set (item, "icon", icon, NULL);
243 g_clear_object (&icon);
244 }
245 if (subtitle_set)
246 {
247 g_object_get (child, "subtitle", &subtitle, NULL);
248 g_object_set (item, "subtitle", subtitle, NULL);
249 g_free (subtitle);
250 }
251 str = g_strdup_printf ("%s %s", accelerator, title);
252 keywords = g_utf8_strdown (str, -1);
253
254 g_hash_table_insert (priv->keywords, item, keywords);
255 if (shortcut_type == GTK_SHORTCUT_ACCELERATOR)
256 gtk_container_add (GTK_CONTAINER (priv->search_shortcuts), item);
257 else
258 gtk_container_add (GTK_CONTAINER (priv->search_gestures), item);
259
260 g_free (title);
261 g_free (accelerator);
262 g_free (str);
263 g_free (action_name);
264 }
265 else if (GTK_IS_CONTAINER (child))
266 {
267 gtk_container_foreach (GTK_CONTAINER (child), gtk_shortcuts_window_add_search_item, self);
268 }
269 }
270
271 static void
section_notify_cb(GObject * section,GParamSpec * pspec,gpointer data)272 section_notify_cb (GObject *section,
273 GParamSpec *pspec,
274 gpointer data)
275 {
276 GtkShortcutsWindow *self = data;
277 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
278
279 if (strcmp (pspec->name, "section-name") == 0)
280 {
281 gchar *name;
282
283 g_object_get (section, "section-name", &name, NULL);
284 gtk_container_child_set (GTK_CONTAINER (priv->stack), GTK_WIDGET (section), "name", name, NULL);
285 g_free (name);
286 }
287 else if (strcmp (pspec->name, "title") == 0)
288 {
289 gchar *title;
290 GtkWidget *label;
291
292 label = g_object_get_data (section, "gtk-shortcuts-title");
293 g_object_get (section, "title", &title, NULL);
294 gtk_label_set_label (GTK_LABEL (label), title);
295 g_free (title);
296 }
297 }
298
299 static void
gtk_shortcuts_window_add_section(GtkShortcutsWindow * self,GtkShortcutsSection * section)300 gtk_shortcuts_window_add_section (GtkShortcutsWindow *self,
301 GtkShortcutsSection *section)
302 {
303 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
304 GtkListBoxRow *row;
305 gchar *title;
306 gchar *name;
307 const gchar *visible_section;
308 GtkWidget *label;
309
310 gtk_container_foreach (GTK_CONTAINER (section), gtk_shortcuts_window_add_search_item, self);
311
312 g_object_get (section,
313 "section-name", &name,
314 "title", &title,
315 NULL);
316
317 g_signal_connect (section, "notify", G_CALLBACK (section_notify_cb), self);
318
319 if (name == NULL)
320 name = g_strdup ("shortcuts");
321
322 gtk_stack_add_titled (priv->stack, GTK_WIDGET (section), name, title);
323
324 visible_section = gtk_stack_get_visible_child_name (priv->stack);
325 if (strcmp (visible_section, "internal-search") == 0 ||
326 (priv->initial_section && strcmp (priv->initial_section, visible_section) == 0))
327 gtk_stack_set_visible_child (priv->stack, GTK_WIDGET (section));
328
329 row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
330 "visible", TRUE,
331 NULL);
332 g_object_set_data (G_OBJECT (row), "gtk-shortcuts-section", section);
333 label = g_object_new (GTK_TYPE_LABEL,
334 "margin", 6,
335 "label", title,
336 "xalign", 0.5f,
337 "visible", TRUE,
338 NULL);
339 g_object_set_data (G_OBJECT (section), "gtk-shortcuts-title", label);
340 gtk_container_add (GTK_CONTAINER (row), GTK_WIDGET (label));
341 gtk_container_add (GTK_CONTAINER (priv->list_box), GTK_WIDGET (row));
342
343 update_title_stack (self);
344
345 g_free (name);
346 g_free (title);
347 }
348
349 static void
gtk_shortcuts_window_add(GtkContainer * container,GtkWidget * widget)350 gtk_shortcuts_window_add (GtkContainer *container,
351 GtkWidget *widget)
352 {
353 GtkShortcutsWindow *self = (GtkShortcutsWindow *)container;
354
355 if (GTK_IS_SHORTCUTS_SECTION (widget))
356 gtk_shortcuts_window_add_section (self, GTK_SHORTCUTS_SECTION (widget));
357 else
358 g_warning ("Can't add children of type %s to %s",
359 G_OBJECT_TYPE_NAME (widget),
360 G_OBJECT_TYPE_NAME (container));
361 }
362
363 static void
gtk_shortcuts_window_remove(GtkContainer * container,GtkWidget * widget)364 gtk_shortcuts_window_remove (GtkContainer *container,
365 GtkWidget *widget)
366 {
367 GtkShortcutsWindow *self = (GtkShortcutsWindow *)container;
368 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
369
370 g_signal_handlers_disconnect_by_func (widget, section_notify_cb, self);
371
372 if (widget == (GtkWidget *)priv->header_bar ||
373 widget == (GtkWidget *)priv->main_box)
374 GTK_CONTAINER_CLASS (gtk_shortcuts_window_parent_class)->remove (container, widget);
375 else
376 gtk_container_remove (GTK_CONTAINER (priv->stack), widget);
377 }
378
379 static void
gtk_shortcuts_window_forall(GtkContainer * container,gboolean include_internal,GtkCallback callback,gpointer callback_data)380 gtk_shortcuts_window_forall (GtkContainer *container,
381 gboolean include_internal,
382 GtkCallback callback,
383 gpointer callback_data)
384 {
385 GtkShortcutsWindow *self = (GtkShortcutsWindow *)container;
386 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
387
388 if (include_internal)
389 {
390 GTK_CONTAINER_CLASS (gtk_shortcuts_window_parent_class)->forall (container, include_internal, callback, callback_data);
391 }
392 else
393 {
394 if (priv->stack)
395 {
396 GList *children, *l;
397 GtkWidget *search;
398 GtkWidget *empty;
399
400 search = gtk_stack_get_child_by_name (GTK_STACK (priv->stack), "internal-search");
401 empty = gtk_stack_get_child_by_name (GTK_STACK (priv->stack), "no-search-results");
402 children = gtk_container_get_children (GTK_CONTAINER (priv->stack));
403 for (l = children; l; l = l->next)
404 {
405 GtkWidget *child = l->data;
406
407 if (include_internal ||
408 (child != search && child != empty))
409 callback (child, callback_data);
410 }
411 g_list_free (children);
412 }
413 }
414 }
415
416 static void
gtk_shortcuts_window_set_view_name(GtkShortcutsWindow * self,const gchar * view_name)417 gtk_shortcuts_window_set_view_name (GtkShortcutsWindow *self,
418 const gchar *view_name)
419 {
420 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
421 GList *sections, *l;
422
423 g_free (priv->view_name);
424 priv->view_name = g_strdup (view_name);
425
426 sections = gtk_container_get_children (GTK_CONTAINER (priv->stack));
427 for (l = sections; l; l = l->next)
428 {
429 GtkShortcutsSection *section = l->data;
430
431 if (GTK_IS_SHORTCUTS_SECTION (section))
432 g_object_set (section, "view-name", priv->view_name, NULL);
433 }
434 g_list_free (sections);
435 }
436
437 static void
gtk_shortcuts_window_set_section_name(GtkShortcutsWindow * self,const gchar * section_name)438 gtk_shortcuts_window_set_section_name (GtkShortcutsWindow *self,
439 const gchar *section_name)
440 {
441 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
442 GtkWidget *section = NULL;
443
444 g_free (priv->initial_section);
445 priv->initial_section = g_strdup (section_name);
446
447 if (section_name)
448 section = gtk_stack_get_child_by_name (priv->stack, section_name);
449 if (section)
450 gtk_stack_set_visible_child (priv->stack, section);
451 }
452
453 static void
update_accels_cb(GtkWidget * widget,gpointer data)454 update_accels_cb (GtkWidget *widget,
455 gpointer data)
456 {
457 GtkShortcutsWindow *self = data;
458 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
459
460 if (GTK_IS_SHORTCUTS_SHORTCUT (widget))
461 gtk_shortcuts_shortcut_update_accel (GTK_SHORTCUTS_SHORTCUT (widget), priv->window);
462 else if (GTK_IS_CONTAINER (widget))
463 gtk_container_foreach (GTK_CONTAINER (widget), update_accels_cb, self);
464 }
465
466 static void
update_accels_for_actions(GtkShortcutsWindow * self)467 update_accels_for_actions (GtkShortcutsWindow *self)
468 {
469 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
470
471 if (priv->window)
472 gtk_container_forall (GTK_CONTAINER (self), update_accels_cb, self);
473 }
474
475 static void
keys_changed_handler(GtkWindow * window,GtkShortcutsWindow * self)476 keys_changed_handler (GtkWindow *window,
477 GtkShortcutsWindow *self)
478 {
479 update_accels_for_actions (self);
480 }
481
482 void
gtk_shortcuts_window_set_window(GtkShortcutsWindow * self,GtkWindow * window)483 gtk_shortcuts_window_set_window (GtkShortcutsWindow *self,
484 GtkWindow *window)
485 {
486 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
487
488 if (priv->keys_changed_id)
489 {
490 g_signal_handler_disconnect (priv->window, priv->keys_changed_id);
491 priv->keys_changed_id = 0;
492 }
493
494 priv->window = window;
495
496 if (priv->window)
497 priv->keys_changed_id = g_signal_connect (window, "keys-changed",
498 G_CALLBACK (keys_changed_handler),
499 self);
500
501 update_accels_for_actions (self);
502 }
503
504 static void
gtk_shortcuts_window__list_box__row_activated(GtkShortcutsWindow * self,GtkListBoxRow * row,GtkListBox * list_box)505 gtk_shortcuts_window__list_box__row_activated (GtkShortcutsWindow *self,
506 GtkListBoxRow *row,
507 GtkListBox *list_box)
508 {
509 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
510 GtkWidget *section;
511
512 section = g_object_get_data (G_OBJECT (row), "gtk-shortcuts-section");
513 gtk_stack_set_visible_child (priv->stack, section);
514 gtk_popover_popdown (priv->popover);
515 }
516
517 static gboolean
hidden_by_direction(GtkWidget * widget)518 hidden_by_direction (GtkWidget *widget)
519 {
520 if (GTK_IS_SHORTCUTS_SHORTCUT (widget))
521 {
522 GtkTextDirection dir;
523
524 g_object_get (widget, "direction", &dir, NULL);
525 if (dir != GTK_TEXT_DIR_NONE &&
526 dir != gtk_widget_get_direction (widget))
527 return TRUE;
528 }
529
530 return FALSE;
531 }
532
533 static void
gtk_shortcuts_window__entry__changed(GtkShortcutsWindow * self,GtkSearchEntry * search_entry)534 gtk_shortcuts_window__entry__changed (GtkShortcutsWindow *self,
535 GtkSearchEntry *search_entry)
536 {
537 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
538 gchar *downcase = NULL;
539 GHashTableIter iter;
540 const gchar *text;
541 const gchar *last_section_name;
542 gpointer key;
543 gpointer value;
544 gboolean has_result;
545
546 text = gtk_entry_get_text (GTK_ENTRY (search_entry));
547
548 if (!text || !*text)
549 {
550 if (priv->last_section_name != NULL)
551 {
552 gtk_stack_set_visible_child_name (priv->stack, priv->last_section_name);
553 return;
554
555 }
556 }
557
558 last_section_name = gtk_stack_get_visible_child_name (priv->stack);
559
560 if (g_strcmp0 (last_section_name, "internal-search") != 0 &&
561 g_strcmp0 (last_section_name, "no-search-results") != 0)
562 {
563 g_free (priv->last_section_name);
564 priv->last_section_name = g_strdup (last_section_name);
565 }
566
567 downcase = g_utf8_strdown (text, -1);
568
569 g_hash_table_iter_init (&iter, priv->keywords);
570
571 has_result = FALSE;
572 while (g_hash_table_iter_next (&iter, &key, &value))
573 {
574 GtkWidget *widget = key;
575 const gchar *keywords = value;
576 gboolean match;
577
578 if (hidden_by_direction (widget))
579 match = FALSE;
580 else
581 match = strstr (keywords, downcase) != NULL;
582
583 gtk_widget_set_visible (widget, match);
584 has_result |= match;
585 }
586
587 g_free (downcase);
588
589 if (has_result)
590 gtk_stack_set_visible_child_name (priv->stack, "internal-search");
591 else
592 gtk_stack_set_visible_child_name (priv->stack, "no-search-results");
593 }
594
595 static void
gtk_shortcuts_window__search_mode__changed(GtkShortcutsWindow * self)596 gtk_shortcuts_window__search_mode__changed (GtkShortcutsWindow *self)
597 {
598 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
599
600 if (!gtk_search_bar_get_search_mode (priv->search_bar))
601 {
602 if (priv->last_section_name != NULL)
603 gtk_stack_set_visible_child_name (priv->stack, priv->last_section_name);
604 }
605 }
606
607 static void
gtk_shortcuts_window_close(GtkShortcutsWindow * self)608 gtk_shortcuts_window_close (GtkShortcutsWindow *self)
609 {
610 gtk_window_close (GTK_WINDOW (self));
611 }
612
613 static void
gtk_shortcuts_window_search(GtkShortcutsWindow * self)614 gtk_shortcuts_window_search (GtkShortcutsWindow *self)
615 {
616 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
617
618 gtk_search_bar_set_search_mode (priv->search_bar, TRUE);
619 }
620
621 static void
gtk_shortcuts_window_constructed(GObject * object)622 gtk_shortcuts_window_constructed (GObject *object)
623 {
624 GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
625 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
626
627 G_OBJECT_CLASS (gtk_shortcuts_window_parent_class)->constructed (object);
628
629 if (priv->initial_section != NULL)
630 gtk_stack_set_visible_child_name (priv->stack, priv->initial_section);
631 }
632
633 static void
gtk_shortcuts_window_finalize(GObject * object)634 gtk_shortcuts_window_finalize (GObject *object)
635 {
636 GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
637 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
638
639 g_clear_pointer (&priv->keywords, g_hash_table_unref);
640 g_clear_pointer (&priv->initial_section, g_free);
641 g_clear_pointer (&priv->view_name, g_free);
642 g_clear_pointer (&priv->last_section_name, g_free);
643 g_clear_pointer (&priv->search_items_hash, g_hash_table_unref);
644
645 g_clear_object (&priv->search_image_group);
646 g_clear_object (&priv->search_text_group);
647
648 G_OBJECT_CLASS (gtk_shortcuts_window_parent_class)->finalize (object);
649 }
650
651 static void
gtk_shortcuts_window_dispose(GObject * object)652 gtk_shortcuts_window_dispose (GObject *object)
653 {
654 GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
655 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
656
657 g_signal_handlers_disconnect_by_func (priv->stack, G_CALLBACK (update_title_stack), self);
658
659 gtk_shortcuts_window_set_window (self, NULL);
660
661 if (priv->header_bar)
662 {
663 gtk_widget_destroy (GTK_WIDGET (priv->header_bar));
664 priv->header_bar = NULL;
665 priv->popover = NULL;
666 }
667
668 G_OBJECT_CLASS (gtk_shortcuts_window_parent_class)->dispose (object);
669
670 #if 0
671 if (priv->main_box)
672 {
673 gtk_widget_destroy (GTK_WIDGET (priv->main_box));
674 priv->main_box = NULL;
675 }
676 #endif
677 }
678
679 static void
gtk_shortcuts_window_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)680 gtk_shortcuts_window_get_property (GObject *object,
681 guint prop_id,
682 GValue *value,
683 GParamSpec *pspec)
684 {
685 GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
686 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
687
688 switch (prop_id)
689 {
690 case PROP_SECTION_NAME:
691 {
692 GtkWidget *child = gtk_stack_get_visible_child (priv->stack);
693
694 if (child != NULL)
695 {
696 gchar *name = NULL;
697
698 gtk_container_child_get (GTK_CONTAINER (priv->stack), child,
699 "name", &name,
700 NULL);
701 g_value_take_string (value, name);
702 }
703 }
704 break;
705
706 case PROP_VIEW_NAME:
707 g_value_set_string (value, priv->view_name);
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_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)716 gtk_shortcuts_window_set_property (GObject *object,
717 guint prop_id,
718 const GValue *value,
719 GParamSpec *pspec)
720 {
721 GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
722
723 switch (prop_id)
724 {
725 case PROP_SECTION_NAME:
726 gtk_shortcuts_window_set_section_name (self, g_value_get_string (value));
727 break;
728
729 case PROP_VIEW_NAME:
730 gtk_shortcuts_window_set_view_name (self, g_value_get_string (value));
731 break;
732
733 default:
734 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
735 }
736 }
737
738 static void
gtk_shortcuts_window_unmap(GtkWidget * widget)739 gtk_shortcuts_window_unmap (GtkWidget *widget)
740 {
741 GtkShortcutsWindow *self = (GtkShortcutsWindow *)widget;
742 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
743
744 gtk_search_bar_set_search_mode (priv->search_bar, FALSE);
745
746 GTK_WIDGET_CLASS (gtk_shortcuts_window_parent_class)->unmap (widget);
747 }
748
749 static GType
gtk_shortcuts_window_child_type(GtkContainer * container)750 gtk_shortcuts_window_child_type (GtkContainer *container)
751 {
752 return GTK_TYPE_SHORTCUTS_SECTION;
753 }
754
755 static void
gtk_shortcuts_window_class_init(GtkShortcutsWindowClass * klass)756 gtk_shortcuts_window_class_init (GtkShortcutsWindowClass *klass)
757 {
758 GObjectClass *object_class = G_OBJECT_CLASS (klass);
759 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
760 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
761 GtkBindingSet *binding_set = gtk_binding_set_by_class (klass);
762
763 object_class->constructed = gtk_shortcuts_window_constructed;
764 object_class->finalize = gtk_shortcuts_window_finalize;
765 object_class->get_property = gtk_shortcuts_window_get_property;
766 object_class->set_property = gtk_shortcuts_window_set_property;
767 object_class->dispose = gtk_shortcuts_window_dispose;
768
769 widget_class->unmap = gtk_shortcuts_window_unmap;
770 container_class->add = gtk_shortcuts_window_add;
771 container_class->remove = gtk_shortcuts_window_remove;
772 container_class->child_type = gtk_shortcuts_window_child_type;
773 container_class->forall = gtk_shortcuts_window_forall;
774
775 klass->close = gtk_shortcuts_window_close;
776 klass->search = gtk_shortcuts_window_search;
777
778 /**
779 * GtkShortcutsWindow:section-name:
780 *
781 * The name of the section to show.
782 *
783 * This should be the section-name of one of the #GtkShortcutsSection
784 * objects that are in this shortcuts window.
785 */
786 properties[PROP_SECTION_NAME] =
787 g_param_spec_string ("section-name", P_("Section Name"), P_("Section Name"),
788 "internal-search",
789 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
790
791 /**
792 * GtkShortcutsWindow:view-name:
793 *
794 * The view name by which to filter the contents.
795 *
796 * This should correspond to the #GtkShortcutsGroup:view property of some of
797 * the #GtkShortcutsGroup objects that are inside this shortcuts window.
798 *
799 * Set this to %NULL to show all groups.
800 */
801 properties[PROP_VIEW_NAME] =
802 g_param_spec_string ("view-name", P_("View Name"), P_("View Name"),
803 NULL,
804 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
805
806 g_object_class_install_properties (object_class, LAST_PROP, properties);
807
808 /**
809 * GtkShortcutsWindow::close:
810 *
811 * The ::close signal is a
812 * [keybinding signal][GtkBindingSignal]
813 * which gets emitted when the user uses a keybinding to close
814 * the window.
815 *
816 * The default binding for this signal is the Escape key.
817 */
818 signals[CLOSE] = g_signal_new (I_("close"),
819 G_TYPE_FROM_CLASS (klass),
820 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
821 G_STRUCT_OFFSET (GtkShortcutsWindowClass, close),
822 NULL, NULL, NULL,
823 G_TYPE_NONE,
824 0);
825
826 /**
827 * GtkShortcutsWindow::search:
828 *
829 * The ::search signal is a
830 * [keybinding signal][GtkBindingSignal]
831 * which gets emitted when the user uses a keybinding to start a search.
832 *
833 * The default binding for this signal is Control-F.
834 */
835 signals[SEARCH] = g_signal_new (I_("search"),
836 G_TYPE_FROM_CLASS (klass),
837 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
838 G_STRUCT_OFFSET (GtkShortcutsWindowClass, search),
839 NULL, NULL, NULL,
840 G_TYPE_NONE,
841 0);
842
843 gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "close", 0);
844 gtk_binding_entry_add_signal (binding_set, GDK_KEY_f, GDK_CONTROL_MASK, "search", 0);
845
846 g_type_ensure (GTK_TYPE_SHORTCUTS_GROUP);
847 g_type_ensure (GTK_TYPE_SHORTCUTS_SHORTCUT);
848 }
849
850 static gboolean
window_key_press_event_cb(GtkWidget * window,GdkEvent * event,gpointer data)851 window_key_press_event_cb (GtkWidget *window,
852 GdkEvent *event,
853 gpointer data)
854 {
855 GtkShortcutsWindow *self = GTK_SHORTCUTS_WINDOW (window);
856 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
857
858 return gtk_search_bar_handle_event (priv->search_bar, event);
859 }
860
861 static void
gtk_shortcuts_window_init(GtkShortcutsWindow * self)862 gtk_shortcuts_window_init (GtkShortcutsWindow *self)
863 {
864 GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
865 GtkToggleButton *search_button;
866 GtkBox *menu_box;
867 GtkBox *box;
868 GtkArrow *arrow;
869 GtkWidget *scroller;
870 GtkWidget *label;
871 GtkWidget *empty;
872 PangoAttrList *attributes;
873
874 gtk_window_set_resizable (GTK_WINDOW (self), FALSE);
875 gtk_window_set_type_hint (GTK_WINDOW (self), GDK_WINDOW_TYPE_HINT_DIALOG);
876
877 g_signal_connect (self, "key-press-event",
878 G_CALLBACK (window_key_press_event_cb), NULL);
879
880 priv->keywords = g_hash_table_new_full (NULL, NULL, NULL, g_free);
881 priv->search_items_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
882
883 priv->search_text_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
884 priv->search_image_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
885
886 priv->header_bar = g_object_new (GTK_TYPE_HEADER_BAR,
887 "show-close-button", TRUE,
888 "visible", TRUE,
889 NULL);
890 gtk_window_set_titlebar (GTK_WINDOW (self), GTK_WIDGET (priv->header_bar));
891
892 search_button = g_object_new (GTK_TYPE_TOGGLE_BUTTON,
893 "child", g_object_new (GTK_TYPE_IMAGE,
894 "visible", TRUE,
895 "icon-name", "edit-find-symbolic",
896 NULL),
897 "visible", TRUE,
898 NULL);
899 gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (search_button)), "image-button");
900 gtk_container_add (GTK_CONTAINER (priv->header_bar), GTK_WIDGET (search_button));
901
902 priv->main_box = g_object_new (GTK_TYPE_BOX,
903 "orientation", GTK_ORIENTATION_VERTICAL,
904 "visible", TRUE,
905 NULL);
906 GTK_CONTAINER_CLASS (gtk_shortcuts_window_parent_class)->add (GTK_CONTAINER (self), GTK_WIDGET (priv->main_box));
907
908 priv->search_bar = g_object_new (GTK_TYPE_SEARCH_BAR,
909 "visible", TRUE,
910 NULL);
911 g_object_bind_property (priv->search_bar, "search-mode-enabled",
912 search_button, "active",
913 G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
914 gtk_container_add (GTK_CONTAINER (priv->main_box), GTK_WIDGET (priv->search_bar));
915
916 priv->stack = g_object_new (GTK_TYPE_STACK,
917 "expand", TRUE,
918 "homogeneous", TRUE,
919 "transition-type", GTK_STACK_TRANSITION_TYPE_CROSSFADE,
920 "visible", TRUE,
921 NULL);
922 gtk_container_add (GTK_CONTAINER (priv->main_box), GTK_WIDGET (priv->stack));
923
924 priv->title_stack = g_object_new (GTK_TYPE_STACK,
925 "visible", TRUE,
926 NULL);
927 gtk_header_bar_set_custom_title (priv->header_bar, GTK_WIDGET (priv->title_stack));
928
929 label = gtk_label_new (_("Shortcuts"));
930 gtk_widget_show (label);
931 gtk_style_context_add_class (gtk_widget_get_style_context (label), GTK_STYLE_CLASS_TITLE);
932 gtk_stack_add_named (priv->title_stack, label, "title");
933
934 label = gtk_label_new (_("Search Results"));
935 gtk_widget_show (label);
936 gtk_style_context_add_class (gtk_widget_get_style_context (label), GTK_STYLE_CLASS_TITLE);
937 gtk_stack_add_named (priv->title_stack, label, "search");
938
939 priv->menu_button = g_object_new (GTK_TYPE_MENU_BUTTON,
940 "focus-on-click", FALSE,
941 "visible", TRUE,
942 "relief", GTK_RELIEF_NONE,
943 NULL);
944 gtk_stack_add_named (priv->title_stack, GTK_WIDGET (priv->menu_button), "sections");
945
946 menu_box = g_object_new (GTK_TYPE_BOX,
947 "orientation", GTK_ORIENTATION_HORIZONTAL,
948 "spacing", 6,
949 "visible", TRUE,
950 NULL);
951 gtk_container_add (GTK_CONTAINER (priv->menu_button), GTK_WIDGET (menu_box));
952
953 priv->menu_label = g_object_new (GTK_TYPE_LABEL,
954 "visible", TRUE,
955 NULL);
956 gtk_container_add (GTK_CONTAINER (menu_box), GTK_WIDGET (priv->menu_label));
957
958 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
959 arrow = g_object_new (GTK_TYPE_ARROW,
960 "arrow-type", GTK_ARROW_DOWN,
961 "visible", TRUE,
962 NULL);
963 gtk_container_add (GTK_CONTAINER (menu_box), GTK_WIDGET (arrow));
964 G_GNUC_END_IGNORE_DEPRECATIONS;
965
966 priv->popover = g_object_new (GTK_TYPE_POPOVER,
967 "border-width", 6,
968 "relative-to", priv->menu_button,
969 "position", GTK_POS_BOTTOM,
970 NULL);
971 gtk_menu_button_set_popover (priv->menu_button, GTK_WIDGET (priv->popover));
972
973 priv->list_box = g_object_new (GTK_TYPE_LIST_BOX,
974 "selection-mode", GTK_SELECTION_NONE,
975 "visible", TRUE,
976 NULL);
977 g_signal_connect_object (priv->list_box,
978 "row-activated",
979 G_CALLBACK (gtk_shortcuts_window__list_box__row_activated),
980 self,
981 G_CONNECT_SWAPPED);
982 gtk_container_add (GTK_CONTAINER (priv->popover), GTK_WIDGET (priv->list_box));
983
984 priv->search_entry = GTK_SEARCH_ENTRY (gtk_search_entry_new ());
985 gtk_widget_show (GTK_WIDGET (priv->search_entry));
986 gtk_container_add (GTK_CONTAINER (priv->search_bar), GTK_WIDGET (priv->search_entry));
987 g_object_set (priv->search_entry,
988 "placeholder-text", _("Search Shortcuts"),
989 "width-chars", 40,
990 NULL);
991 g_signal_connect_object (priv->search_entry,
992 "search-changed",
993 G_CALLBACK (gtk_shortcuts_window__entry__changed),
994 self,
995 G_CONNECT_SWAPPED);
996 g_signal_connect_object (priv->search_bar,
997 "notify::search-mode-enabled",
998 G_CALLBACK (gtk_shortcuts_window__search_mode__changed),
999 self,
1000 G_CONNECT_SWAPPED);
1001
1002 scroller = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
1003 "visible", TRUE,
1004 NULL);
1005 box = g_object_new (GTK_TYPE_BOX,
1006 "border-width", 24,
1007 "halign", GTK_ALIGN_CENTER,
1008 "spacing", 24,
1009 "orientation", GTK_ORIENTATION_VERTICAL,
1010 "visible", TRUE,
1011 NULL);
1012 gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (box));
1013 gtk_stack_add_named (priv->stack, scroller, "internal-search");
1014
1015 priv->search_shortcuts = g_object_new (GTK_TYPE_BOX,
1016 "halign", GTK_ALIGN_CENTER,
1017 "spacing", 6,
1018 "orientation", GTK_ORIENTATION_VERTICAL,
1019 "visible", TRUE,
1020 NULL);
1021 gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (priv->search_shortcuts));
1022
1023 priv->search_gestures = g_object_new (GTK_TYPE_BOX,
1024 "halign", GTK_ALIGN_CENTER,
1025 "spacing", 6,
1026 "orientation", GTK_ORIENTATION_VERTICAL,
1027 "visible", TRUE,
1028 NULL);
1029 gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (priv->search_gestures));
1030
1031 empty = g_object_new (GTK_TYPE_GRID,
1032 "visible", TRUE,
1033 "row-spacing", 12,
1034 "margin", 12,
1035 "hexpand", TRUE,
1036 "vexpand", TRUE,
1037 "halign", GTK_ALIGN_CENTER,
1038 "valign", GTK_ALIGN_CENTER,
1039 NULL);
1040 gtk_style_context_add_class (gtk_widget_get_style_context (empty), GTK_STYLE_CLASS_DIM_LABEL);
1041 gtk_grid_attach (GTK_GRID (empty),
1042 g_object_new (GTK_TYPE_IMAGE,
1043 "visible", TRUE,
1044 "icon-name", "edit-find-symbolic",
1045 "pixel-size", 72,
1046 NULL),
1047 0, 0, 1, 1);
1048 attributes = pango_attr_list_new ();
1049 pango_attr_list_insert (attributes, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
1050 pango_attr_list_insert (attributes, pango_attr_scale_new (1.44));
1051 label = g_object_new (GTK_TYPE_LABEL,
1052 "visible", TRUE,
1053 "label", _("No Results Found"),
1054 "attributes", attributes,
1055 NULL);
1056 pango_attr_list_unref (attributes);
1057 gtk_grid_attach (GTK_GRID (empty), label, 0, 1, 1, 1);
1058 label = g_object_new (GTK_TYPE_LABEL,
1059 "visible", TRUE,
1060 "label", _("Try a different search"),
1061 NULL);
1062 gtk_grid_attach (GTK_GRID (empty), label, 0, 2, 1, 1);
1063
1064 gtk_stack_add_named (priv->stack, empty, "no-search-results");
1065
1066 g_signal_connect_object (priv->stack, "notify::visible-child",
1067 G_CALLBACK (update_title_stack), self, G_CONNECT_SWAPPED);
1068
1069 }
1070