[gtk+] Add GtkShortcutsWindow



commit 1dfbae1aa41bb2b3b95fa841e0c1707fab52ca8b
Author: Matthias Clasen <mclasen redhat com>
Date:   Sun Oct 11 16:39:18 2015 -0400

    Add GtkShortcutsWindow
    
    This is a toplevel window that is tailored towards showing
    help for shortcuts in an application. The implementation closely
    follows this design: https://wiki.gnome.org/Design/OS/HelpOverlay
    
    This implementation is inspired by earlier work in gnome-builder,
    thanks to Christian Hergert.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=756428

 docs/reference/gtk/Makefile.am                  |    5 +-
 docs/reference/gtk/gtk-docs.sgml                |    9 +
 docs/reference/gtk/gtk3-sections.txt            |   71 ++
 docs/reference/gtk/gtk3.types.in                |    7 +-
 docs/reference/gtk/images/builder-shortcuts.png |  Bin 0 -> 74526 bytes
 docs/reference/gtk/images/clocks-shortcuts.png  |  Bin 0 -> 26017 bytes
 docs/reference/gtk/images/gedit-shortcuts.png   |  Bin 0 -> 62962 bytes
 gtk/Makefile.am                                 |   14 +
 gtk/gtk.h                                       |    5 +
 gtk/gtkshortcutlabel.c                          |  314 ++++++++
 gtk/gtkshortcutlabelprivate.h                   |   47 ++
 gtk/gtkshortcutsgesture.c                       |  311 ++++++++
 gtk/gtkshortcutsgesture.h                       |   42 ++
 gtk/gtkshortcutsgroup.c                         |  306 ++++++++
 gtk/gtkshortcutsgroup.h                         |   42 ++
 gtk/gtkshortcutssection.c                       |  716 ++++++++++++++++++
 gtk/gtkshortcutssection.h                       |   43 ++
 gtk/gtkshortcutsshortcut.c                      |  264 +++++++
 gtk/gtkshortcutsshortcut.h                      |   43 ++
 gtk/gtkshortcutswindow.c                        |  882 +++++++++++++++++++++++
 gtk/gtkshortcutswindow.h                        |   57 ++
 21 files changed, 3176 insertions(+), 2 deletions(-)
---
diff --git a/docs/reference/gtk/Makefile.am b/docs/reference/gtk/Makefile.am
index 63cd349..ce6d6e2 100644
--- a/docs/reference/gtk/Makefile.am
+++ b/docs/reference/gtk/Makefile.am
@@ -470,7 +470,10 @@ HTML_IMAGES = \
        $(srcdir)/images/getting-started-app10.png                      \
        $(srcdir)/images/exampleapp.png                                 \
        $(srcdir)/images/flow-box.png                                   \
-       $(srcdir)/images/inspector.png
+       $(srcdir)/images/inspector.png                                  \
+       $(srcdir)/images/gedit-shortcuts.png                            \
+       $(srcdir)/images/clocks-shortcuts.png                           \
+       $(srcdir)/images/builder-shortcuts.png
 
 if ENABLE_DOC_CROSS_REFERENCES
 # Extra options to supply to gtkdoc-fixref
diff --git a/docs/reference/gtk/gtk-docs.sgml b/docs/reference/gtk/gtk-docs.sgml
index 9194c47..3b97564 100644
--- a/docs/reference/gtk/gtk-docs.sgml
+++ b/docs/reference/gtk/gtk-docs.sgml
@@ -239,6 +239,15 @@
       <xi:include href="xml/gtkpagesetupunixdialog.xml" />
     </chapter>
 
+    <chapter id="ShortcutsOverview">
+      <title>Shortcuts Overview</title>
+      <xi:include href="xml/gtkshortcutswindow.xml" />
+      <xi:include href="xml/gtkshortcutssection.xml" />
+      <xi:include href="xml/gtkshortcutsgroup.xml" />
+      <xi:include href="xml/gtkshortcutsshortcut.xml" />
+      <xi:include href="xml/gtkshortcutsgesture.xml" />
+    </chapter>
+
     <chapter id="MiscObjects">
       <title>Miscellaneous</title>
       <xi:include href="xml/gtkadjustment.xml" />
diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt
index 60d3eb8..f4482e1 100644
--- a/docs/reference/gtk/gtk3-sections.txt
+++ b/docs/reference/gtk/gtk3-sections.txt
@@ -8422,3 +8422,74 @@ GTK_IS_GL_AREA_CLASS
 <SUBSECTION Private>
 gtk_gl_area_get_type
 </SECTION>
+
+<SECTION>
+<FILE>gtkshortcutswindow</FILE>
+GtkShortcutsWindow
+<SUBSECTION Standard>
+GTK_TYPE_SHORTCUTS_WINDOW
+GTK_SHORTCUTS_WINDOW
+GTK_IS_SHORTCUTS_WINDOW
+GTK_SHORTCUTS_WINDOW_CLASS
+GTK_IS_SHORTCUTS_WINDOW_CLASS
+GTK_GET_SHORTCUTS_WINDOW_CLASS
+<SUBSECTION Private>
+gtk_shortcuts_window_get_type
+</SECTION>
+
+<SECTION>
+<FILE>gtkshortcutssection</FILE>
+GtkShortcutsSection
+<SUBSECTION Standard>
+GTK_TYPE_SHORTCUTS_SECTION
+GTK_SHORTCUTS_SECTION
+GTK_IS_SHORTCUTS_SECTION
+GTK_SHORTCUTS_SECTION_CLASS
+GTK_IS_SHORTCUTS_SECTION_CLASS
+GTK_GET_SHORTCUTS_SECTION_CLASS
+<SUBSECTION Private>
+gtk_shortcuts_section_get_type
+</SECTION>
+
+<SECTION>
+<FILE>gtkshortcutsgroup</FILE>
+GtkShortcutsGroup
+<SUBSECTION Standard>
+GTK_TYPE_SHORTCUTS_GROUP
+GTK_SHORTCUTS_GROUP
+GTK_IS_SHORTCUTS_GROUP
+GTK_SHORTCUTS_GROUP_CLASS
+GTK_IS_SHORTCUTS_GROUP_CLASS
+GTK_GET_SHORTCUTS_GROUP_CLASS
+<SUBSECTION Private>
+gtk_shortcuts_group_get_type
+</SECTION>
+
+<SECTION>
+<FILE>gtkshortcutsshortcut</FILE>
+GtkShortcutsShortcut
+<SUBSECTION Standard>
+GTK_TYPE_SHORTCUTS_SHORTCUT
+GTK_SHORTCUTS_SHORTCUT
+GTK_IS_SHORTCUTS_SHORTCUT
+GTK_SHORTCUTS_SHORTCUT_CLASS
+GTK_IS_SHORTCUTS_SHORTCUT_CLASS
+GTK_GET_SHORTCUTS_SHORTCUT_CLASS
+<SUBSECTION Private>
+<SUBSECTION Private>
+gtk_shortcuts_shortcut_get_type
+</SECTION>
+
+<SECTION>
+<FILE>gtkshortcutsgesture</FILE>
+GtkShortcutsGesture
+<SUBSECTION Standard>
+GTK_TYPE_SHORTCUTS_GESTURE
+GTK_SHORTCUTS_GESTURE
+GTK_IS_SHORTCUTS_GESTURE
+GTK_SHORTCUTS_GESTURE_CLASS
+GTK_IS_SHORTCUTS_GESTURE_CLASS
+GTK_GET_SHORTCUTS_GESTURE_CLASS
+<SUBSECTION Private>
+gtk_shortcuts_gesture_get_type
+</SECTION>
diff --git a/docs/reference/gtk/gtk3.types.in b/docs/reference/gtk/gtk3.types.in
index 4b20622..93e42dc 100644
--- a/docs/reference/gtk/gtk3.types.in
+++ b/docs/reference/gtk/gtk3.types.in
@@ -173,12 +173,17 @@ gtk_separator_get_type
 gtk_separator_menu_item_get_type
 gtk_separator_tool_item_get_type
 gtk_settings_get_type
-gtk_stack_sidebar_get_type
+gtk_shortcuts_window_get_type
+gtk_shortcuts_section_get_type
+gtk_shortcuts_group_get_type
+gtk_shortcuts_shortcut_get_type
+gtk_shortcuts_gesture_get_type
 gtk_size_group_get_type
 @ENABLE_ON_X11 gtk_socket_get_type
 gtk_spin_button_get_type
 gtk_spinner_get_type
 gtk_stack_get_type
+gtk_stack_sidebar_get_type
 gtk_stack_switcher_get_type
 gtk_statusbar_get_type
 gtk_status_icon_get_type
diff --git a/docs/reference/gtk/images/builder-shortcuts.png b/docs/reference/gtk/images/builder-shortcuts.png
new file mode 100644
index 0000000..639a1d3
Binary files /dev/null and b/docs/reference/gtk/images/builder-shortcuts.png differ
diff --git a/docs/reference/gtk/images/clocks-shortcuts.png b/docs/reference/gtk/images/clocks-shortcuts.png
new file mode 100644
index 0000000..9ab2d5a
Binary files /dev/null and b/docs/reference/gtk/images/clocks-shortcuts.png differ
diff --git a/docs/reference/gtk/images/gedit-shortcuts.png b/docs/reference/gtk/images/gedit-shortcuts.png
new file mode 100644
index 0000000..5d566ff
Binary files /dev/null and b/docs/reference/gtk/images/gedit-shortcuts.png differ
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 83b3cd2..7d2ebdf 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -269,6 +269,11 @@ gtk_public_h_sources =             \
        gtkseparatormenuitem.h  \
        gtkseparatortoolitem.h  \
        gtksettings.h           \
+       gtkshortcutsgesture.h   \
+       gtkshortcutsgroup.h     \
+       gtkshortcutssection.h   \
+       gtkshortcutsshortcut.h  \
+       gtkshortcutswindow.h    \
        gtkshow.h               \
        gtkstacksidebar.h       \
        gtksizegroup.h          \
@@ -505,6 +510,9 @@ gtk_private_h_sources =             \
        gtkselectionprivate.h   \
        gtksidebarrowprivate.h  \
        gtksettingsprivate.h    \
+       gtkshortcutsgestureprivate.h    \
+       gtkshortcutlabelprivate.h       \
+       gtkshortcutsshortcutprivate.h   \
        gtksizegroup-private.h  \
        gtksizerequestcacheprivate.h    \
        gtksocketprivate.h      \
@@ -806,6 +814,12 @@ gtk_base_c_sources =               \
        gtkseparatormenuitem.c  \
        gtkseparatortoolitem.c  \
        gtksettings.c           \
+       gtkshortcutsgesture.c   \
+       gtkshortcutsgroup.c     \
+       gtkshortcutlabel.c      \
+       gtkshortcutsshortcut.c  \
+       gtkshortcutssection.c   \
+       gtkshortcutswindow.c    \
        gtksidebarrow.c         \
        gtksizegroup.c          \
        gtksizerequest.c        \
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 3cbd13f..ae556d4 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -183,6 +183,11 @@
 #include <gtk/gtkseparatormenuitem.h>
 #include <gtk/gtkseparatortoolitem.h>
 #include <gtk/gtksettings.h>
+#include <gtk/gtkshortcutsgesture.h>
+#include <gtk/gtkshortcutsgroup.h>
+#include <gtk/gtkshortcutssection.h>
+#include <gtk/gtkshortcutsshortcut.h>
+#include <gtk/gtkshortcutswindow.h>
 #include <gtk/gtkshow.h>
 #include <gtk/gtkstacksidebar.h>
 #include <gtk/gtksizegroup.h>
diff --git a/gtk/gtkshortcutlabel.c b/gtk/gtkshortcutlabel.c
new file mode 100644
index 0000000..7140142
--- /dev/null
+++ b/gtk/gtkshortcutlabel.c
@@ -0,0 +1,314 @@
+/* gtkshortcutlabel.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkshortcutlabelprivate.h"
+#include "gtklabel.h"
+#include "gtkframe.h"
+#include "gtkstylecontext.h"
+#include "gtkprivate.h"
+#include "gtkintl.h"
+
+struct _GtkShortcutLabel
+{
+  GtkBox  parent_instance;
+  gchar  *accelerator;
+};
+
+struct _GtkShortcutLabelClass
+{
+  GtkBoxClass parent_class;
+};
+
+G_DEFINE_TYPE (GtkShortcutLabel, gtk_shortcut_label, GTK_TYPE_BOX)
+
+enum {
+  PROP_0,
+  PROP_ACCELERATOR,
+  LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP];
+
+static gchar **
+get_labels (guint key, GdkModifierType modifier, guint *n_mods)
+{
+  const gchar *labels[16];
+  gchar key_label[6];
+  gchar *tmp;
+  gunichar ch;
+  gint i = 0;
+
+  if (modifier & GDK_SHIFT_MASK)
+    labels[i++] = C_("keyboard label", "Shift");
+  if (modifier & GDK_CONTROL_MASK)
+    labels[i++] = C_("keyboard label", "Ctrl");
+  if (modifier & GDK_MOD1_MASK)
+    labels[i++] = C_("keyboard label", "Alt");
+  if (modifier & GDK_MOD2_MASK)
+    labels[i++] = "Mod2";
+  if (modifier & GDK_MOD3_MASK)
+    labels[i++] = "Mod3";
+  if (modifier & GDK_MOD4_MASK)
+    labels[i++] = "Mod4";
+  if (modifier & GDK_MOD5_MASK)
+    labels[i++] = "Mod5";
+  if (modifier & GDK_SUPER_MASK)
+    labels[i++] = C_("keyboard label", "Super");
+  if (modifier & GDK_HYPER_MASK)
+    labels[i++] = C_("keyboard label", "Hyper");
+  if (modifier & GDK_META_MASK)
+    labels[i++] = C_("keyboard label", "Meta");
+
+  *n_mods = i;
+
+  ch = gdk_keyval_to_unicode (key);
+  if (ch && ch < 0x80 && g_unichar_isgraph (ch))
+    {
+      switch (ch)
+        {
+        case '\\':
+          labels[i++] = C_("keyboard label", "Backslash");
+          break;
+        default:
+          memset (key_label, 0, 6);
+          g_unichar_to_utf8 (g_unichar_toupper (ch), key_label);
+          labels[i++] = key_label;
+          break;
+        }
+    }
+  else
+    {
+      switch (key)
+        {
+        case GDK_KEY_Left:
+          labels[i++] = "\xe2\x86\x90";
+          break;
+        case GDK_KEY_Up:
+          labels[i++] = "\xe2\x86\x91";
+          break;
+        case GDK_KEY_Right:
+          labels[i++] = "\xe2\x86\x92";
+          break;
+        case GDK_KEY_Down:
+          labels[i++] = "\xe2\x86\x93";
+          break;
+        case GDK_KEY_space:
+          labels[i++] = "\xe2\x90\xa3";
+          break;
+        case GDK_KEY_Return:
+          labels[i++] = "\xe2\x8f\x8e";
+          break;
+        case GDK_KEY_Page_Up:
+          labels[i++] = C_("keyboard label", "Page_Up");
+          break;
+        case GDK_KEY_Page_Down:
+          labels[i++] = C_("keyboard label", "Page_Down");
+          break;
+        default:
+          tmp = gdk_keyval_name (gdk_keyval_to_lower (key));
+          if (tmp != NULL)
+            {
+              if (tmp[0] != 0 && tmp[1] == 0)
+                {
+                  key_label[0] = g_ascii_toupper (tmp[0]);
+                  key_label[1] = '\0';
+                  labels[i++] = key_label;
+                }
+              else
+                {
+                  labels[i++] = g_dpgettext2 (GETTEXT_PACKAGE, "keyboard label", tmp);
+                }
+            }
+        }
+    }
+
+  labels[i] = NULL;
+
+  return g_strdupv ((gchar **)labels);
+}
+
+static GtkWidget *
+dim_label (const gchar *text)
+{
+  GtkWidget *label;
+
+  label = gtk_label_new (text);
+  gtk_widget_show (label);
+  gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label");
+
+  return label;
+}
+
+static void
+gtk_shortcut_label_rebuild (GtkShortcutLabel *self)
+{
+  gchar **accels = NULL;
+  gchar **keys = NULL;
+  GdkModifierType modifier = 0;
+  guint key = 0;
+  guint i, k;
+  guint n_mods;
+
+  gtk_container_foreach (GTK_CONTAINER (self), (GtkCallback)gtk_widget_destroy, NULL);
+
+  if (self->accelerator == NULL)
+    return;
+
+  accels = g_strsplit (self->accelerator, " ", 0);
+  for (k = 0; accels[k]; k++)
+    {
+      gtk_accelerator_parse (accels[k], &key, &modifier);
+      if ((key == 0) && (modifier == 0))
+        {
+          g_warning ("Failed to parse accelerator '%s'", self->accelerator);
+          goto out;
+        }
+
+      if (k > 0)
+        gtk_container_add (GTK_CONTAINER (self), dim_label ("/"));
+
+      keys = get_labels (key, modifier, &n_mods);
+      for (i = 0; keys[i]; i++)
+        {
+          GtkWidget *frame;
+          GtkWidget *disp;
+
+          if (i > 0)
+            gtk_container_add (GTK_CONTAINER (self), dim_label ("+"));
+
+          frame = gtk_frame_new (NULL);
+          gtk_widget_show (frame);
+          gtk_container_add (GTK_CONTAINER (self), frame);
+
+          if (i < n_mods)
+            gtk_widget_set_size_request (frame, 50, -1);
+
+          disp = gtk_label_new (keys[i]);
+          gtk_widget_show (disp);
+          gtk_container_add (GTK_CONTAINER (frame), disp);
+        }
+       g_strfreev (keys);
+    }
+
+out:
+  g_strfreev (accels);
+}
+
+static void
+gtk_shortcut_label_finalize (GObject *object)
+{
+  GtkShortcutLabel *self = (GtkShortcutLabel *)object;
+
+  g_free (self->accelerator);
+
+  G_OBJECT_CLASS (gtk_shortcut_label_parent_class)->finalize (object);
+}
+
+static void
+gtk_shortcut_label_get_property (GObject    *object,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  GtkShortcutLabel *self = GTK_SHORTCUT_LABEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_ACCELERATOR:
+      g_value_set_string (value, gtk_shortcut_label_get_accelerator (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_shortcut_label_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  GtkShortcutLabel *self = GTK_SHORTCUT_LABEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_ACCELERATOR:
+      gtk_shortcut_label_set_accelerator (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_shortcut_label_class_init (GtkShortcutLabelClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtk_shortcut_label_finalize;
+  object_class->get_property = gtk_shortcut_label_get_property;
+  object_class->set_property = gtk_shortcut_label_set_property;
+
+  properties[PROP_ACCELERATOR] =
+    g_param_spec_string ("accelerator", P_("Accelerator"), P_("Accelerator"),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+gtk_shortcut_label_init (GtkShortcutLabel *self)
+{
+  gtk_box_set_spacing (GTK_BOX (self), 6);
+}
+
+GtkWidget *
+gtk_shortcut_label_new (const gchar *accelerator)
+{
+  return g_object_new (GTK_TYPE_SHORTCUT_LABEL,
+                       "accelerator", accelerator,
+                       NULL);
+}
+
+const gchar *
+gtk_shortcut_label_get_accelerator (GtkShortcutLabel *self)
+{
+  g_return_val_if_fail (GTK_IS_SHORTCUT_LABEL (self), NULL);
+
+  return self->accelerator;
+}
+
+void
+gtk_shortcut_label_set_accelerator (GtkShortcutLabel *self,
+                                    const gchar      *accelerator)
+{
+  g_return_if_fail (GTK_IS_SHORTCUT_LABEL (self));
+
+  if (g_strcmp0 (accelerator, self->accelerator) != 0)
+    {
+      g_free (self->accelerator);
+      self->accelerator = g_strdup (accelerator);
+      gtk_shortcut_label_rebuild (self);
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACCELERATOR]);
+    }
+}
diff --git a/gtk/gtkshortcutlabelprivate.h b/gtk/gtkshortcutlabelprivate.h
new file mode 100644
index 0000000..4f5096c
--- /dev/null
+++ b/gtk/gtkshortcutlabelprivate.h
@@ -0,0 +1,47 @@
+/* gtkshortcutlabelprivate.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public License as
+ *  published by the Free Software Foundation; either version 2 of the
+ *  License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_SHORTCUT_LABEL_H__
+#define __GTK_SHORTCUT_LABEL_H__
+
+#include <gtk/gtkbox.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SHORTCUT_LABEL (gtk_shortcut_label_get_type())
+#define GTK_SHORTCUT_LABEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SHORTCUT_LABEL, 
GtkShortcutLabel))
+#define GTK_SHORTCUT_LABEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SHORTCUT_LABEL, 
GtkShortcutLabelClass))
+#define GTK_IS_SHORTCUT_LABEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SHORTCUT_LABEL))
+#define GTK_IS_SHORTCUT_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SHORTCUT_LABEL))
+#define GTK_SHORTCUT_LABEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SHORTCUT_LABEL, 
GtkShortcutLabelClass))
+
+
+typedef struct _GtkShortcutLabel      GtkShortcutLabel;
+typedef struct _GtkShortcutLabelClass GtkShortcutLabelClass;
+
+
+GType        gtk_shortcut_label_get_type        (void) G_GNUC_CONST;
+
+GtkWidget   *gtk_shortcut_label_new             (const gchar      *accelerator);
+const gchar *gtk_shortcut_label_get_accelerator (GtkShortcutLabel *self);
+void         gtk_shortcut_label_set_accelerator (GtkShortcutLabel *self,
+                                                 const gchar      *accelerator);
+
+G_END_DECLS
+
+#endif /* __GTK_SHORTCUT_LABEL_H__ */
diff --git a/gtk/gtkshortcutsgesture.c b/gtk/gtkshortcutsgesture.c
new file mode 100644
index 0000000..0889da6
--- /dev/null
+++ b/gtk/gtkshortcutsgesture.c
@@ -0,0 +1,311 @@
+ /* gtkshortcutsgesture.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public License as
+ *  published by the Free Software Foundation; either version 2 of the
+ *  License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkshortcutsgesture.h"
+#include "gtkimage.h"
+#include "gtklabel.h"
+#include "gtksizegroup.h"
+#include "gtkorientable.h"
+#include "gtkstylecontext.h"
+#include "gtkprivate.h"
+#include "gtkintl.h"
+
+/**
+ * SECTION:gtkshortcutsgesture
+ * @Title: GtkShortcutsGesture
+ * @Short_description: Represents a gesture in a GtkShortcutsWindow
+ *
+ * A GtkShortcutsGesture represents a single gesture with an image
+ * an a short text.
+ *
+ * This widget is only meant to be used with #GtkShortcutsWindow.
+ */
+struct _GtkShortcutsGesture
+{
+  GtkBox    parent_instance;
+
+  GtkImage *image;
+  GtkLabel *title;
+  GtkLabel *subtitle;
+  GtkBox   *title_box;
+
+  GtkSizeGroup *title_size_group;
+  GtkSizeGroup *icon_size_group;
+};
+
+struct _GtkShortcutsGestureClass
+{
+  GtkBoxClass parent_class;
+};
+
+G_DEFINE_TYPE (GtkShortcutsGesture, gtk_shortcuts_gesture, GTK_TYPE_BOX)
+
+enum {
+  PROP_0,
+  PROP_ICON,
+  PROP_TITLE,
+  PROP_SUBTITLE,
+  PROP_ICON_SIZE_GROUP,
+  PROP_TITLE_SIZE_GROUP,
+  LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP];
+
+static void
+gtk_shortcuts_gesture_set_title_size_group (GtkShortcutsGesture *self,
+                                            GtkSizeGroup        *group)
+{
+  if (self->title_size_group)
+    gtk_size_group_remove_widget (self->title_size_group, GTK_WIDGET (self->title_box));
+  if (group)
+    gtk_size_group_add_widget (group, GTK_WIDGET (self->title_box));
+
+  g_set_object (&self->title_size_group, group);
+}
+
+static void
+gtk_shortcuts_gesture_set_icon_size_group (GtkShortcutsGesture *self,
+                                           GtkSizeGroup        *group)
+{
+  if (self->icon_size_group)
+    gtk_size_group_remove_widget (self->icon_size_group, GTK_WIDGET (self->image));
+  if (group)
+    gtk_size_group_add_widget (group, GTK_WIDGET (self->image));
+
+  g_set_object (&self->icon_size_group, group);
+}
+
+static void
+gtk_shortcuts_gesture_set_icon (GtkShortcutsGesture *self,
+                                GIcon               *gicon)
+{
+  gtk_image_set_from_gicon (self->image, gicon, GTK_ICON_SIZE_DIALOG);
+}
+
+static void
+gtk_shortcuts_gesture_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  GtkShortcutsGesture *self = GTK_SHORTCUTS_GESTURE (object);
+
+  switch (prop_id)
+    {
+    case PROP_ICON:
+      {
+        GIcon *icon;
+
+        gtk_image_get_gicon (self->image, &icon, NULL);
+        g_value_set_object (value, icon);
+      }
+      break;
+
+    case PROP_TITLE:
+      g_value_set_string (value, gtk_label_get_label (self->title));
+      break;
+
+    case PROP_SUBTITLE:
+      g_value_set_string (value, gtk_label_get_label (self->subtitle));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_shortcuts_gesture_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  GtkShortcutsGesture *self = GTK_SHORTCUTS_GESTURE (object);
+
+  switch (prop_id)
+    {
+    case PROP_ICON:
+      gtk_shortcuts_gesture_set_icon (self, g_value_get_object (value));
+      break;
+
+    case PROP_TITLE:
+      gtk_label_set_label (self->title, g_value_get_string (value));
+      break;
+
+    case PROP_SUBTITLE:
+      gtk_label_set_label (self->subtitle, g_value_get_string (value));
+      break;
+
+    case PROP_TITLE_SIZE_GROUP:
+      gtk_shortcuts_gesture_set_title_size_group (self, GTK_SIZE_GROUP (g_value_get_object (value)));
+      break;
+
+    case PROP_ICON_SIZE_GROUP:
+      gtk_shortcuts_gesture_set_icon_size_group (self, GTK_SIZE_GROUP (g_value_get_object (value)));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_shortcuts_gesture_finalize (GObject *object)
+{
+  GtkShortcutsGesture *self = GTK_SHORTCUTS_GESTURE (object);
+
+  g_clear_object (&self->title_size_group);
+  g_clear_object (&self->icon_size_group);
+
+  G_OBJECT_CLASS (gtk_shortcuts_gesture_parent_class)->finalize (object);
+}
+
+static void
+gtk_shortcuts_gesture_add (GtkContainer *container,
+                           GtkWidget    *widget)
+{
+  g_warning ("Can't add children to %s", G_OBJECT_TYPE_NAME (container));
+}
+
+static GType
+gtk_shortcuts_gesture_child_type (GtkContainer *container)
+{
+  return G_TYPE_NONE;
+}
+
+static void
+gtk_shortcuts_gesture_class_init (GtkShortcutsGestureClass *klass)
+{
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtk_shortcuts_gesture_finalize;
+  object_class->get_property = gtk_shortcuts_gesture_get_property;
+  object_class->set_property = gtk_shortcuts_gesture_set_property;
+
+  container_class->add = gtk_shortcuts_gesture_add;
+  container_class->child_type = gtk_shortcuts_gesture_child_type;
+
+  /**
+   * GtkShortcutsGesture:icon:
+   *
+   * The icon used to represent the gesture.
+   */
+  properties[PROP_ICON] =
+    g_param_spec_object ("icon",
+                         P_("Icon"),
+                         P_("Icon"),
+                         G_TYPE_ICON,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GtkShortcutsGesture:title:
+   *
+   * The title for the gesture.
+   *
+   * This should be a short, one-line text that describes the action
+   * associated with the gesture.
+   */
+  properties[PROP_TITLE] =
+    g_param_spec_string ("title",
+                         P_("Title"),
+                         P_("Title"),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GtkShortcutsGesture:subtitle:
+   *
+   * The subtitle for the gesture.
+   *
+   * This should be a short, one-line text that describes the gesture
+   * itself, e.g. "Two-finger swipe".
+   */
+  properties[PROP_SUBTITLE] =
+    g_param_spec_string ("subtitle",
+                         P_("Subtitle"),
+                         P_("Subtitle"),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GtkShortcutsGesture:title-size-group:
+   *
+   * The size group for the textual portion of this gesture.
+   *
+   * This is used internally by GTK+, and must not be modified by applications.
+   */
+  properties[PROP_TITLE_SIZE_GROUP] =
+    g_param_spec_object ("title-size-group",
+                         P_("Title Size Group"),
+                         P_("Title Size Group"),
+                         GTK_TYPE_SIZE_GROUP,
+                         (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GtkShortcutsShortcut:icon-size-group:
+   *
+   * The size group for the image portion of this gesture.
+   *
+   * This is used internally by GTK+, and must not be modified by applications.
+   */
+  properties[PROP_ICON_SIZE_GROUP] =
+    g_param_spec_object ("icon-size-group",
+                         P_("Icon Size Group"),
+                         P_("Icon Size Group"),
+                         GTK_TYPE_SIZE_GROUP,
+                         (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+gtk_shortcuts_gesture_init (GtkShortcutsGesture *self)
+{
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_HORIZONTAL);
+  gtk_box_set_spacing (GTK_BOX (self), 12);
+
+  self->image = g_object_new (GTK_TYPE_IMAGE,
+                              "visible", TRUE,
+                              NULL);
+  GTK_CONTAINER_CLASS (gtk_shortcuts_gesture_parent_class)->add (GTK_CONTAINER (self), GTK_WIDGET 
(self->image));
+
+  self->title_box = g_object_new (GTK_TYPE_BOX,
+                                  "hexpand", TRUE,
+                                  "orientation", GTK_ORIENTATION_VERTICAL,
+                                  "visible", TRUE,
+                                  NULL);
+  GTK_CONTAINER_CLASS (gtk_shortcuts_gesture_parent_class)->add (GTK_CONTAINER (self), GTK_WIDGET 
(self->title_box));
+
+  self->title = g_object_new (GTK_TYPE_LABEL,
+                              "visible", TRUE,
+                              "xalign", 0.0f,
+                              NULL);
+  gtk_container_add (GTK_CONTAINER (self->title_box), GTK_WIDGET (self->title));
+
+  self->subtitle = g_object_new (GTK_TYPE_LABEL,
+                                 "visible", TRUE,
+                                 "xalign", 0.0f,
+                                 NULL);
+  gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self->subtitle)),
+                               "dim-label");
+  gtk_container_add (GTK_CONTAINER (self->title_box), GTK_WIDGET (self->subtitle));
+}
diff --git a/gtk/gtkshortcutsgesture.h b/gtk/gtkshortcutsgesture.h
new file mode 100644
index 0000000..0b60b39
--- /dev/null
+++ b/gtk/gtkshortcutsgesture.h
@@ -0,0 +1,42 @@
+/* gtkshortcutsgestureprivate.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public License as
+ *  published by the Free Software Foundation; either version 2 of the
+ *  License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_SHORTCUTS_GESTURE_H__
+#define __GTK_SHORTCUTS_GESTURE_H__
+
+#include <gtk/gtkbox.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SHORTCUTS_GESTURE (gtk_shortcuts_gesture_get_type())
+#define GTK_SHORTCUTS_GESTURE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GTK_TYPE_SHORTCUTS_GESTURE, GtkShortcutsGesture))
+#define GTK_SHORTCUTS_GESTURE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SHORTCUTS_GESTURE, 
GtkShortcutsGestureClass))
+#define GTK_IS_SHORTCUTS_GESTURE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GTK_TYPE_SHORTCUTS_GESTURE))
+#define GTK_IS_SHORTCUTS_GESTURE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SHORTCUTS_GESTURE))
+#define GTK_SHORTCUTS_GESTURE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SHORTCUTS_GESTURE, 
GtkShortcutsGestureClass))
+
+
+typedef struct _GtkShortcutsGesture      GtkShortcutsGesture;
+typedef struct _GtkShortcutsGestureClass GtkShortcutsGestureClass;
+
+GDK_AVAILABLE_IN_3_20
+GType        gtk_shortcuts_gesture_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __GTK_SHORTCUTS_GESTURE_H__ */
diff --git a/gtk/gtkshortcutsgroup.c b/gtk/gtkshortcutsgroup.c
new file mode 100644
index 0000000..1ac3fc5
--- /dev/null
+++ b/gtk/gtkshortcutsgroup.c
@@ -0,0 +1,306 @@
+/* gtkshortcutsgroup.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public License as
+ *  published by the Free Software Foundation; either version 2 of the
+ *  License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkshortcutsgroup.h"
+
+#include "gtkshortcutsshortcut.h"
+#include "gtkshortcutsgesture.h"
+#include "gtklabel.h"
+#include "gtkorientable.h"
+#include "gtksizegroup.h"
+#include "gtkprivate.h"
+#include "gtkintl.h"
+
+/**
+ * SECTION:gtkshortcutsgroup
+ * @Title: GtkShortcutsGroup
+ * @Short_description: Represents a group of shortcuts in a GtkShortcutsWindow
+ *
+ * A GtkShortcutsGroup represents a group of related keyboard shortcuts
+ * or gestures. The group has a title. It may optionally be associated with
+ * a view of the application, which can be used to show only relevant shortcuts
+ * depending on the application context.
+ *
+ * This widget is only meant to be used with #GtkShortcutsWindow.
+ */
+
+struct _GtkShortcutsGroup
+{
+  GtkBox    parent_instance;
+
+  GtkLabel *title;
+  gchar    *view;
+  guint     height;
+};
+
+struct _GtkShortcutsGroupClass
+{
+  GtkBoxClass parent_class;
+};
+
+G_DEFINE_TYPE (GtkShortcutsGroup, gtk_shortcuts_group, GTK_TYPE_BOX)
+
+enum {
+  PROP_0,
+  PROP_TITLE,
+  PROP_VIEW,
+  PROP_ACCEL_SIZE_GROUP,
+  PROP_TITLE_SIZE_GROUP,
+  PROP_HEIGHT,
+  LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP];
+
+static void
+gtk_shortcuts_group_set_accel_size_group (GtkShortcutsGroup *group,
+                                          GtkSizeGroup      *size_group)
+{
+  GList *children, *l;
+
+  children = gtk_container_get_children (GTK_CONTAINER (group));
+  for (l = children; l; l = l->next)
+    {
+      if (GTK_IS_SHORTCUTS_SHORTCUT (l->data))
+        g_object_set (l->data, "accel-size-group", size_group, NULL);
+      else if (GTK_IS_SHORTCUTS_GESTURE (l->data))
+        g_object_set (l->data, "icon-size-group", size_group, NULL);
+    }
+  g_list_free (children);
+}
+
+static void
+gtk_shortcuts_group_set_title_size_group (GtkShortcutsGroup *group,
+                                          GtkSizeGroup      *size_group)
+{
+  GList *children, *l;
+
+  children = gtk_container_get_children (GTK_CONTAINER (group));
+  for (l = children; l; l = l->next)
+    {
+      if (GTK_IS_SHORTCUTS_SHORTCUT (l->data))
+        g_object_set (l->data, "title-size-group", size_group, NULL);
+      else if (GTK_IS_SHORTCUTS_GESTURE (l->data))
+        g_object_set (l->data, "title-size-group", size_group, NULL);
+    }
+  g_list_free (children);
+}
+
+static guint
+gtk_shortcuts_group_get_height (GtkShortcutsGroup *group)
+{
+  GList *children, *l;
+  guint height;
+
+  height = 1;
+
+  children = gtk_container_get_children (GTK_CONTAINER (group));
+  for (l = children; l; l = l->next)
+    {
+      if (GTK_IS_SHORTCUTS_SHORTCUT (l->data))
+        height += 1;
+      else if (GTK_IS_SHORTCUTS_GESTURE (l->data))
+        height += 2;
+    }
+  g_list_free (children);
+
+  return height;
+}
+
+static void
+gtk_shortcuts_group_add (GtkContainer *container,
+                         GtkWidget    *widget)
+{
+  if (GTK_IS_SHORTCUTS_SHORTCUT (widget) ||
+      GTK_IS_SHORTCUTS_GESTURE (widget))
+    GTK_CONTAINER_CLASS (gtk_shortcuts_group_parent_class)->add (container, widget);
+  else
+    g_warning ("Can't add children of type %s to %s",
+               G_OBJECT_TYPE_NAME (widget),
+               G_OBJECT_TYPE_NAME (container));
+}
+
+static void
+gtk_shortcuts_group_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  GtkShortcutsGroup *self = GTK_SHORTCUTS_GROUP (object);
+
+  switch (prop_id)
+    {
+    case PROP_TITLE:
+      g_value_set_string (value, gtk_label_get_label (self->title));
+      break;
+
+    case PROP_VIEW:
+      g_value_set_string (value, self->view);
+      break;
+
+    case PROP_HEIGHT:
+      g_value_set_uint (value, gtk_shortcuts_group_get_height (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_shortcuts_group_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  GtkShortcutsGroup *self = GTK_SHORTCUTS_GROUP (object);
+
+  switch (prop_id)
+    {
+    case PROP_TITLE:
+      gtk_label_set_label (self->title, g_value_get_string (value));
+      break;
+
+    case PROP_VIEW:
+      g_free (self->view);
+      self->view = g_value_dup_string (value);
+      break;
+
+    case PROP_ACCEL_SIZE_GROUP:
+      gtk_shortcuts_group_set_accel_size_group (self, GTK_SIZE_GROUP (g_value_get_object (value)));
+      break;
+
+    case PROP_TITLE_SIZE_GROUP:
+      gtk_shortcuts_group_set_title_size_group (self, GTK_SIZE_GROUP (g_value_get_object (value)));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_shortcuts_group_finalize (GObject *object)
+{
+  GtkShortcutsGroup *self = GTK_SHORTCUTS_GROUP (object);
+
+  g_free (self->view);
+
+  G_OBJECT_CLASS (gtk_shortcuts_group_parent_class)->finalize (object);
+}
+
+static void
+gtk_shortcuts_group_class_init (GtkShortcutsGroupClass *klass)
+{
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtk_shortcuts_group_finalize;
+  object_class->get_property = gtk_shortcuts_group_get_property;
+  object_class->set_property = gtk_shortcuts_group_set_property;
+
+  container_class->add = gtk_shortcuts_group_add;
+
+  /**
+   * GtkShortcutsGroup:title:
+   *
+   * The title for this group of shortcuts.
+   */
+  properties[PROP_TITLE] =
+    g_param_spec_string ("title", P_("Title"), P_("Title"),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GtkShortcutsGroup:view:
+   *
+   * An optional view that the shortcuts in this group are relevant for.
+   * The group will be hidden if the #GtkShortcutsWindow:view-name property
+   * does not match the view of this group.
+   *
+   * Set this to %NULL to make the group always visible.
+   */
+  properties[PROP_VIEW] =
+    g_param_spec_string ("view", P_("View"), P_("View"),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GtkShortcutsGroup:accel-size-group:
+   *
+   * The size group for the accelerator portion of shortcuts in this group.
+   *
+   * This is used internally by GTK+, and must not be modified by applications.
+   */
+  properties[PROP_ACCEL_SIZE_GROUP] =
+    g_param_spec_object ("accel-size-group",
+                         P_("Accelerator Size Group"),
+                         P_("Accelerator Size Group"),
+                         GTK_TYPE_SIZE_GROUP,
+                         (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GtkShortcutsGroup:title-size-group:
+   *
+   * The size group for the textual portion of shortcuts in this group.
+   *
+   * This is used internally by GTK+, and must not be modified by applications.
+   */
+  properties[PROP_TITLE_SIZE_GROUP] =
+    g_param_spec_object ("title-size-group",
+                         P_("Title Size Group"),
+                         P_("Title Size Group"),
+                         GTK_TYPE_SIZE_GROUP,
+                         (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GtkShortcutsGroup:height:
+   *
+   * A rough measure for the number of lines in this group.
+   *
+   * This is used internally by GTK+, and is not useful for applications.
+   */
+  properties[PROP_HEIGHT] =
+    g_param_spec_uint ("height", P_("Height"), P_("Height"),
+                       0, G_MAXUINT, 0,
+                       (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+gtk_shortcuts_group_init (GtkShortcutsGroup *self)
+{
+  PangoAttrList *attrs;
+
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
+  gtk_box_set_spacing (GTK_BOX (self), 10);
+
+  attrs = pango_attr_list_new ();
+  pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
+  self->title = g_object_new (GTK_TYPE_LABEL,
+                              "attributes", attrs,
+                              "visible", TRUE,
+                              "xalign", 0.0f,
+                              NULL);
+  pango_attr_list_unref (attrs);
+
+  GTK_CONTAINER_CLASS (gtk_shortcuts_group_parent_class)->add (GTK_CONTAINER (self), GTK_WIDGET 
(self->title));
+}
diff --git a/gtk/gtkshortcutsgroup.h b/gtk/gtkshortcutsgroup.h
new file mode 100644
index 0000000..790d60a
--- /dev/null
+++ b/gtk/gtkshortcutsgroup.h
@@ -0,0 +1,42 @@
+/* gtkshortcutsgroupprivate.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public License as
+ *  published by the Free Software Foundation; either version 2 of the
+ *  License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_SHORTCUTS_GROUP_H__
+#define __GTK_SHORTCUTS_GROUP_H__
+
+#include <gtk/gtkbox.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SHORTCUTS_GROUP            (gtk_shortcuts_group_get_type ())
+#define GTK_SHORTCUTS_GROUP(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SHORTCUTS_GROUP, 
GtkShortcutsGroup))
+#define GTK_SHORTCUTS_GROUP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SHORTCUTS_GROUP, 
GtkShortcutsGroupClass))
+#define GTK_IS_SHORTCUTS_GROUP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SHORTCUTS_GROUP))
+#define GTK_IS_SHORTCUTS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SHORTCUTS_GROUP))
+#define GTK_SHORTCUTS_GROUP_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SHORTCUTS_GROUP, 
GtkShortcutsGroupClass))
+
+
+typedef struct _GtkShortcutsGroup         GtkShortcutsGroup;
+typedef struct _GtkShortcutsGroupClass    GtkShortcutsGroupClass;
+
+GDK_AVAILABLE_IN_3_20
+GType gtk_shortcuts_group_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __GTK_SHORTCUTS_GROUP_H__ */
diff --git a/gtk/gtkshortcutssection.c b/gtk/gtkshortcutssection.c
new file mode 100644
index 0000000..9941e13
--- /dev/null
+++ b/gtk/gtkshortcutssection.c
@@ -0,0 +1,716 @@
+/* gtkshortcutssection.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public License as
+ *  published by the Free Software Foundation; either version 2 of the
+ *  License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkshortcutssection.h"
+
+#include "gtkshortcutsgroup.h"
+#include "gtkbutton.h"
+#include "gtklabel.h"
+#include "gtkstack.h"
+#include "gtkstackswitcher.h"
+#include "gtkstylecontext.h"
+#include "gtkorientable.h"
+#include "gtksizegroup.h"
+#include "gtkwidget.h"
+#include "gtkbindings.h"
+#include "gtkprivate.h"
+#include "gtkmarshalers.h"
+#include "gtkgesturepan.h"
+#include "gtkintl.h"
+
+/**
+ * SECTION:gtkshortcutssection
+ * @Title: GtkShortcutsSection
+ * @Short_description: Represents an application mode in a GtkShortcutsWindow
+ *
+ * A GtkShortcutsSection collects all the keyboard shortcuts and gestures
+ * for a major application mode. If your application needs multiple sections,
+ * you should give each section a unique #GtkShortcutsSection:section-name and
+ * a #GtkShortcutsSection:title that can be shown in the section selector of
+ * the GtkShortcutsWindow.
+ *
+ * The #GtkShortcutsSection:max-height property can be used to influence how
+ * the groups in the section are distributed over pages and columns.
+ *
+ * This widget is only meant to be used with #GtkShortcutsWindow.
+ */
+
+struct _GtkShortcutsSection
+{
+  GtkBox            parent_instance;
+
+  gchar            *name;
+  gchar            *title;
+  gchar            *view_name;
+  guint             max_height;
+
+  GtkStack         *stack;
+  GtkStackSwitcher *switcher;
+  GtkWidget        *show_all;
+
+  gboolean          has_filtered_group;
+  gboolean          need_reflow;
+
+  GtkGesture       *pan_gesture;
+};
+
+struct _GtkShortcutsSectionClass
+{
+  GtkBoxClass parent_class;
+
+  gboolean (* change_current_page) (GtkShortcutsSection *self,
+                                    gint                 offset);
+
+};
+
+G_DEFINE_TYPE (GtkShortcutsSection, gtk_shortcuts_section, GTK_TYPE_BOX)
+
+enum {
+  PROP_0,
+  PROP_TITLE,
+  PROP_SECTION_NAME,
+  PROP_VIEW_NAME,
+  PROP_MAX_HEIGHT,
+  LAST_PROP
+};
+
+enum {
+  CHANGE_CURRENT_PAGE,
+  LAST_SIGNAL
+};
+
+static GParamSpec *properties[LAST_PROP];
+static guint signals[LAST_SIGNAL];
+
+static void gtk_shortcuts_section_set_view_name    (GtkShortcutsSection *self,
+                                                    const gchar         *view_name);
+static void gtk_shortcuts_section_add_group        (GtkShortcutsSection *self,
+                                                    GtkShortcutsGroup   *group);
+
+static void gtk_shortcuts_section_show_all         (GtkShortcutsSection *self);
+static void gtk_shortcuts_section_filter_groups    (GtkShortcutsSection *self);
+static void gtk_shortcuts_section_reflow_groups    (GtkShortcutsSection *self);
+static void gtk_shortcuts_section_maybe_reflow     (GtkShortcutsSection *self);
+
+static gboolean gtk_shortcuts_section_change_current_page (GtkShortcutsSection *self,
+                                                           gint                 offset);
+
+static void gtk_shortcuts_section_pan_gesture_pan (GtkGesturePan       *gesture,
+                                                   GtkPanDirection      direction,
+                                                   gdouble              offset,
+                                                   GtkShortcutsSection *self);
+
+static void
+gtk_shortcuts_section_map (GtkWidget *widget)
+{
+  GtkShortcutsSection *self = GTK_SHORTCUTS_SECTION (widget);
+
+  if (self->need_reflow)
+    gtk_shortcuts_section_reflow_groups (self);
+
+  GTK_WIDGET_CLASS (gtk_shortcuts_section_parent_class)->map (widget);
+}
+
+static void
+gtk_shortcuts_section_add (GtkContainer *container,
+                           GtkWidget    *child)
+{
+  GtkShortcutsSection *self = GTK_SHORTCUTS_SECTION (container);
+
+  if (GTK_IS_SHORTCUTS_GROUP (child))
+    gtk_shortcuts_section_add_group (self, GTK_SHORTCUTS_GROUP (child));
+  else
+    g_warning ("Can't add children of type %s to %s",
+               G_OBJECT_TYPE_NAME (child),
+               G_OBJECT_TYPE_NAME (container));
+}
+
+static void
+gtk_shortcuts_section_finalize (GObject *object)
+{
+  GtkShortcutsSection *self = (GtkShortcutsSection *)object;
+
+  g_clear_pointer (&self->name, g_free);
+  g_clear_pointer (&self->title, g_free);
+  g_clear_object (&self->pan_gesture);
+
+  G_OBJECT_CLASS (gtk_shortcuts_section_parent_class)->finalize (object);
+}
+
+static void
+gtk_shortcuts_section_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  GtkShortcutsSection *self = (GtkShortcutsSection *)object;
+
+  switch (prop_id)
+    {
+    case PROP_SECTION_NAME:
+      g_value_set_string (value, self->name);
+      break;
+
+    case PROP_VIEW_NAME:
+      g_value_set_string (value, self->view_name);
+      break;
+
+    case PROP_TITLE:
+      g_value_set_string (value, self->title);
+      break;
+
+    case PROP_MAX_HEIGHT:
+      g_value_set_uint (value, self->max_height);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_shortcuts_section_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  GtkShortcutsSection *self = (GtkShortcutsSection *)object;
+
+  switch (prop_id)
+    {
+    case PROP_SECTION_NAME:
+      g_free (self->name);
+      self->name = g_value_dup_string (value);
+      break;
+
+    case PROP_VIEW_NAME:
+      gtk_shortcuts_section_set_view_name (self, g_value_get_string (value));
+      break;
+
+    case PROP_TITLE:
+      g_free (self->title);
+      self->title = g_value_dup_string (value);
+      break;
+
+    case PROP_MAX_HEIGHT:
+      self->max_height = g_value_get_uint (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static GType
+gtk_shortcuts_section_child_type (GtkContainer *container)
+{
+  return GTK_TYPE_SHORTCUTS_GROUP;
+}
+
+static void
+gtk_shortcuts_section_class_init (GtkShortcutsSectionClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+  GtkBindingSet *binding_set;
+
+  object_class->finalize = gtk_shortcuts_section_finalize;
+  object_class->get_property = gtk_shortcuts_section_get_property;
+  object_class->set_property = gtk_shortcuts_section_set_property;
+
+  widget_class->map = gtk_shortcuts_section_map;
+
+  container_class->add = gtk_shortcuts_section_add;
+  container_class->child_type = gtk_shortcuts_section_child_type;
+
+  klass->change_current_page = gtk_shortcuts_section_change_current_page;
+
+  /**
+   * GtkShortcutsSection:section-name:
+   *
+   * A unique name to identify this section among the sections
+   * added to the GtkShortcutsWindow. Setting the #GtkShortcutsWindow:section-name
+   * property to this string will make this section shown in the
+   * GtkShortcutsWindow.
+   */
+  properties[PROP_SECTION_NAME] =
+    g_param_spec_string ("section-name", P_("Section Name"), P_("Section Name"),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GtkShortcutsSection:view-name:
+   *
+   * A view name to filter the groups in this section by.
+   * See #GtkShortcutsGroup:view.
+   *
+   * Applications are expected to use the #GtkShortcutsWindow:view-name
+   * property for this purpose.
+   */
+  properties[PROP_VIEW_NAME] =
+    g_param_spec_string ("view-name", P_("View Name"), P_("View Name"),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GtkShortcutsSection:title:
+   *
+   * The string to show in the section selector of the GtkShortcutsWindow
+   * for this section. If there is only one section, you don't need to
+   * set a title, since the section selector will not be shown in this case.
+   */
+  properties[PROP_TITLE] =
+    g_param_spec_string ("title", P_("Title"), P_("Title"),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GtkShortcutsSection:max-height:
+   *
+   * The maximum number of lines to allow per column. This property can
+   * be used to influence how the groups in this section are distributed
+   * across pages and columns. The default value of 15 should work in
+   * for most cases.
+   */
+  properties[PROP_MAX_HEIGHT] =
+    g_param_spec_uint ("max-height", P_("Maximum Height"), P_("Maximum Height"),
+                       0, G_MAXUINT, 15,
+                       (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+  signals[CHANGE_CURRENT_PAGE] =
+    g_signal_new (I_("change-current-page"),
+                  G_TYPE_FROM_CLASS (object_class),
+                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                  G_STRUCT_OFFSET (GtkShortcutsSectionClass, change_current_page),
+                  NULL, NULL,
+                  _gtk_marshal_BOOLEAN__INT,
+                  G_TYPE_BOOLEAN, 1,
+                  G_TYPE_INT);
+
+  binding_set = gtk_binding_set_by_class (klass);
+  gtk_binding_entry_add_signal (binding_set,
+                                GDK_KEY_Page_Up, 0,
+                                "change-current-page", 1,
+                                G_TYPE_INT, -1);
+  gtk_binding_entry_add_signal (binding_set,
+                                GDK_KEY_Page_Down, 0,
+                                "change-current-page", 1,
+                                G_TYPE_INT, 1);
+  gtk_binding_entry_add_signal (binding_set,
+                                GDK_KEY_Page_Up, GDK_CONTROL_MASK,
+                                "change-current-page", 1,
+                                G_TYPE_INT, -1);
+  gtk_binding_entry_add_signal (binding_set,
+                                GDK_KEY_Page_Down, GDK_CONTROL_MASK,
+                                "change-current-page", 1,
+                                G_TYPE_INT, 1);
+}
+
+static void
+gtk_shortcuts_section_init (GtkShortcutsSection *self)
+{
+  GtkWidget *box;
+
+  self->max_height = 15;
+
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
+  gtk_box_set_homogeneous (GTK_BOX (self), FALSE);
+  gtk_box_set_spacing (GTK_BOX (self), 22);
+  gtk_container_set_border_width (GTK_CONTAINER (self), 24);
+
+  self->stack = g_object_new (GTK_TYPE_STACK,
+                              "homogeneous", TRUE,
+                              "transition-type", GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT,
+                              "vexpand", TRUE,
+                              "visible", TRUE,
+                              NULL);
+  GTK_CONTAINER_CLASS (gtk_shortcuts_section_parent_class)->add (GTK_CONTAINER (self), GTK_WIDGET 
(self->stack));
+
+  self->switcher = g_object_new (GTK_TYPE_STACK_SWITCHER,
+                                 "halign", GTK_ALIGN_CENTER,
+                                 "stack", self->stack,
+                                 "spacing", 12,
+                                 "no-show-all", TRUE,
+                                 NULL);
+  gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self->switcher)), "round");
+  gtk_style_context_remove_class (gtk_widget_get_style_context (GTK_WIDGET (self->switcher)), "linked");
+
+  self->show_all = gtk_button_new_with_mnemonic (_("_Show All"));
+  gtk_widget_set_no_show_all (self->show_all, TRUE);
+  g_signal_connect_swapped (self->show_all, "clicked",
+                            G_CALLBACK (gtk_shortcuts_section_show_all), self);
+
+  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 20);
+  GTK_CONTAINER_CLASS (gtk_shortcuts_section_parent_class)->add (GTK_CONTAINER (self), box);
+
+  gtk_box_set_center_widget (GTK_BOX (box), GTK_WIDGET (self->switcher));
+  gtk_box_pack_end (GTK_BOX (box), self->show_all, TRUE, TRUE, 0);
+  gtk_widget_set_halign (self->show_all, GTK_ALIGN_END);
+
+  self->pan_gesture = gtk_gesture_pan_new (GTK_WIDGET (self->stack), GTK_ORIENTATION_HORIZONTAL);
+  g_signal_connect (self->pan_gesture, "pan",
+                    G_CALLBACK (gtk_shortcuts_section_pan_gesture_pan), self);
+}
+
+static void
+gtk_shortcuts_section_set_view_name (GtkShortcutsSection *self,
+                                     const gchar         *view_name)
+{
+  g_return_if_fail (GTK_IS_SHORTCUTS_SECTION (self));
+
+  if (g_strcmp0 (self->view_name, view_name) == 0)
+    return;
+
+  g_free (self->view_name);
+  self->view_name = g_strdup (view_name);
+
+  gtk_shortcuts_section_filter_groups (self);
+  gtk_shortcuts_section_reflow_groups (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VIEW_NAME]);
+}
+
+static void
+gtk_shortcuts_section_add_group (GtkShortcutsSection *self,
+                                 GtkShortcutsGroup   *group)
+{
+  GList *children;
+  GtkWidget *page, *column;
+
+  g_return_if_fail (GTK_IS_SHORTCUTS_SECTION (self));
+  g_return_if_fail (GTK_IS_SHORTCUTS_GROUP (group));
+
+  children = gtk_container_get_children (GTK_CONTAINER (self->stack));
+  if (children)
+    page = children->data;
+  else
+    {
+      page = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 22);
+      gtk_stack_add_named (self->stack, page, "1");
+    }
+  g_list_free (children);
+  children = gtk_container_get_children (GTK_CONTAINER (page));
+  if (children)
+    column = children->data;
+  else
+    {
+      column = gtk_box_new (GTK_ORIENTATION_VERTICAL, 22);
+      gtk_container_add (GTK_CONTAINER (page), column);
+    }
+  g_list_free (children);
+
+  gtk_container_add (GTK_CONTAINER (column), GTK_WIDGET (group));
+
+  gtk_shortcuts_section_maybe_reflow (self);
+}
+
+static void
+gtk_shortcuts_section_show_all (GtkShortcutsSection *self)
+{
+  gtk_shortcuts_section_set_view_name (self, NULL);
+}
+
+static void
+update_group_visibility (GtkWidget *child, gpointer data)
+{
+  GtkShortcutsSection *self = data;
+
+  if (GTK_IS_SHORTCUTS_GROUP (child))
+    {
+      gchar *view;
+      gboolean match;
+
+      g_object_get (child, "view", &view, NULL);
+      match = view == NULL ||
+              self->view_name == NULL ||
+              strcmp (view, self->view_name) == 0;
+
+      gtk_widget_set_visible (child, match);
+      self->has_filtered_group |= !match;
+
+      g_free (view);
+    }
+  else if (GTK_IS_CONTAINER (child))
+    {
+      gtk_container_foreach (GTK_CONTAINER (child), update_group_visibility, data);
+    }
+}
+
+static void
+gtk_shortcuts_section_filter_groups (GtkShortcutsSection *self)
+{
+  self->has_filtered_group = FALSE;
+
+  gtk_container_foreach (GTK_CONTAINER (self), update_group_visibility, self);
+
+  gtk_widget_set_visible (GTK_WIDGET (self->show_all), self->has_filtered_group);
+  gtk_widget_set_visible (gtk_widget_get_parent (GTK_WIDGET (self->show_all)),
+                          gtk_widget_get_visible (GTK_WIDGET (self->show_all)) ||
+                          gtk_widget_get_visible (GTK_WIDGET (self->switcher)));
+}
+
+static void
+gtk_shortcuts_section_maybe_reflow (GtkShortcutsSection *self)
+{
+  if (gtk_widget_get_mapped (GTK_WIDGET (self)))
+    gtk_shortcuts_section_reflow_groups (self);
+  else
+    self->need_reflow = TRUE;
+}
+
+static void
+adjust_page_buttons (GtkWidget *widget,
+                     gpointer   data)
+{
+  GtkWidget *label;
+
+  /*
+   * TODO: This is a hack to get the GtkStackSwitcher radio
+   *       buttons to look how we want. However, it's very
+   *       much font size specific.
+   */
+  gtk_widget_set_size_request (widget, 34, 34);
+
+  label = gtk_bin_get_child (GTK_BIN (widget));
+  gtk_label_set_use_underline (GTK_LABEL (label), TRUE);
+}
+
+static void
+gtk_shortcuts_section_reflow_groups (GtkShortcutsSection *self)
+{
+  GList *pages, *p;
+  GList *columns, *c;
+  GList *groups, *g;
+  GList *children;
+  guint n_rows;
+  guint n_columns;
+  guint n_pages;
+  GtkWidget *current_page, *current_column;
+
+  /* collect all groups from the current pages */
+  groups = NULL;
+  pages = gtk_container_get_children (GTK_CONTAINER (self->stack));
+  for (p = pages; p; p = p->next)
+    {
+      columns = gtk_container_get_children (GTK_CONTAINER (p->data));
+      for (c = columns; c; c = c->next)
+        {
+          children = gtk_container_get_children (GTK_CONTAINER (c->data));
+          groups = g_list_concat (groups, children);
+        }
+      g_list_free (columns);
+    }
+  g_list_free (pages);
+
+  /* create new pages */
+  current_page = NULL;
+  current_column = NULL;
+  pages = NULL;
+  n_rows = 0;
+  n_columns = 0;
+  n_pages = 0;
+  for (g = groups; g; g = g->next)
+    {
+      GtkShortcutsGroup *group = g->data;
+      guint height;
+      gboolean visible;
+
+      g_object_get (group,
+                    "visible", &visible,
+                    "height", &height,
+                    NULL);
+      if (!visible)
+        height = 0;
+
+      if (n_rows == 0 || n_rows + height > self->max_height)
+        {
+          GtkWidget *column;
+
+          column = gtk_box_new (GTK_ORIENTATION_VERTICAL, 22);
+          gtk_widget_show (column);
+
+          g_object_set_data_full (G_OBJECT (column), "accel-size-group", gtk_size_group_new 
(GTK_SIZE_GROUP_HORIZONTAL), g_object_unref);
+          g_object_set_data_full (G_OBJECT (column), "title-size-group", gtk_size_group_new 
(GTK_SIZE_GROUP_HORIZONTAL), g_object_unref);
+
+          if (n_columns % 2 == 0)
+            {
+              GtkWidget *page;
+
+              page = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 22);
+              gtk_widget_show (page);
+
+              pages = g_list_append (pages, page);
+              current_page = page;
+            }
+
+          gtk_container_add (GTK_CONTAINER (current_page), column);
+          current_column = column;
+          n_columns += 1;
+          n_rows = 0;
+        }
+
+      n_rows += height;
+
+      g_object_set (group,
+                    "accel-size-group", g_object_get_data (G_OBJECT (current_column), "accel-size-group"),
+                    "title-size-group", g_object_get_data (G_OBJECT (current_column), "title-size-group"),
+                    NULL);
+
+      g_object_ref (group);
+      gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (GTK_WIDGET (group))), GTK_WIDGET (group));
+      gtk_container_add (GTK_CONTAINER (current_column), GTK_WIDGET (group));
+      g_object_unref (group);
+    }
+
+  /* balance the last page */
+  if (n_columns % 2 == 1)
+    {
+      GtkWidget *column;
+      GList *content;
+      guint n;
+
+      column = gtk_box_new (GTK_ORIENTATION_VERTICAL, 22);
+      gtk_widget_show (column);
+
+      g_object_set_data_full (G_OBJECT (column), "accel-size-group", gtk_size_group_new 
(GTK_SIZE_GROUP_HORIZONTAL), g_object_unref);
+      g_object_set_data_full (G_OBJECT (column), "title-size-group", gtk_size_group_new 
(GTK_SIZE_GROUP_HORIZONTAL), g_object_unref);
+
+      gtk_container_add (GTK_CONTAINER (current_page), column);
+
+      content = gtk_container_get_children (GTK_CONTAINER (current_column));
+      n = 0;
+
+      for (g = g_list_last (content); g; g = g->prev)
+        {
+          GtkShortcutsGroup *group = g->data;
+          guint height;
+          gboolean visible;
+
+          g_object_get (group,
+                        "visible", &visible,
+                        "height", &height,
+                        NULL);
+          if (!visible)
+            height = 0;
+
+          if (n_rows - height == 0)
+            break;
+          if (ABS (n_rows - n) < ABS ((n_rows - height) - (n + height)))
+            break;
+
+          n_rows -= height;
+          n += height;
+        }
+
+      for (g = g->next; g; g = g->next)
+        {
+          GtkShortcutsGroup *group = g->data;
+
+          g_object_set (group,
+                        "accel-size-group", g_object_get_data (G_OBJECT (column), "accel-size-group"),
+                        "title-size-group", g_object_get_data (G_OBJECT (column), "title-size-group"),
+                        NULL);
+
+          g_object_ref (group);
+          gtk_container_remove (GTK_CONTAINER (current_column), GTK_WIDGET (group));
+          gtk_container_add (GTK_CONTAINER (column), GTK_WIDGET (group));
+          g_object_unref (group);
+        }
+
+      g_list_free (content);
+    }
+
+  /* replace the current pages with the new pages */
+  children = gtk_container_get_children (GTK_CONTAINER (self->stack));
+  g_list_free_full (children, (GDestroyNotify)gtk_widget_destroy);
+
+  for (p = pages, n_pages = 0; p; p = p->next, n_pages++)
+    {
+      GtkWidget *page = p->data;
+      gchar *title;
+
+      title = g_strdup_printf ("_%u", n_pages + 1);
+      gtk_stack_add_titled (self->stack, page, title, title);
+      g_free (title);
+    }
+
+  /* fix up stack switcher */
+  gtk_container_foreach (GTK_CONTAINER (self->switcher), adjust_page_buttons, NULL);
+  gtk_widget_set_visible (GTK_WIDGET (self->switcher), (n_pages > 1));
+  gtk_widget_set_visible (gtk_widget_get_parent (GTK_WIDGET (self->switcher)),
+                          gtk_widget_get_visible (GTK_WIDGET (self->show_all)) ||
+                          gtk_widget_get_visible (GTK_WIDGET (self->switcher)));
+
+  /* clean up */
+  g_list_free (groups);
+  g_list_free (pages);
+
+  self->need_reflow = FALSE;
+}
+
+static gboolean
+gtk_shortcuts_section_change_current_page (GtkShortcutsSection *self,
+                                           gint                 offset)
+{
+  GtkWidget *child;
+  GList *children, *l;
+
+  child = gtk_stack_get_visible_child (self->stack);
+  children = gtk_container_get_children (GTK_CONTAINER (self->stack));
+  l = g_list_find (children, child);
+
+  if (offset == 1)
+    l = l->next;
+  else if (offset == -1)
+    l = l->prev;
+  else
+    g_assert_not_reached ();
+
+  if (l)
+    gtk_stack_set_visible_child (self->stack, GTK_WIDGET (l->data));
+  else
+    gtk_widget_error_bell (GTK_WIDGET (self));
+
+  g_list_free (children);
+
+  return TRUE;
+}
+
+static void
+gtk_shortcuts_section_pan_gesture_pan (GtkGesturePan       *gesture,
+                                       GtkPanDirection      direction,
+                                       gdouble              offset,
+                                       GtkShortcutsSection *self)
+{
+  if (offset < 50)
+    return;
+
+  if (direction == GTK_PAN_DIRECTION_LEFT)
+    gtk_shortcuts_section_change_current_page (self, 1);
+  else if (direction == GTK_PAN_DIRECTION_RIGHT)
+    gtk_shortcuts_section_change_current_page (self, -1);
+  else
+    g_assert_not_reached ();
+
+  gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+}
diff --git a/gtk/gtkshortcutssection.h b/gtk/gtkshortcutssection.h
new file mode 100644
index 0000000..9d8b5cc
--- /dev/null
+++ b/gtk/gtkshortcutssection.h
@@ -0,0 +1,43 @@
+/* gtkshortcutssection.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public License as
+ *  published by the Free Software Foundation; either version 2 of the
+ *  License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_SHORTCUTS_SECTION_H__
+#define __GTK_SHORTCUTS_SECTION_H__
+
+#include <gtk/gtkbox.h>
+#include <gtk/gtkshortcutsgroup.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SHORTCUTS_SECTION (gtk_shortcuts_section_get_type ())
+#define GTK_SHORTCUTS_SECTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GTK_TYPE_SHORTCUTS_SECTION, GtkShortcutsSection))
+#define GTK_SHORTCUTS_SECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SHORTCUTS_SECTION, 
GtkShortcutsSectionClass))
+#define GTK_IS_SHORTCUTS_SECTION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GTK_TYPE_SHORTCUTS_SECTION))
+#define GTK_IS_SHORTCUTS_SECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SHORTCUTS_SECTION))
+#define GTK_SHORTCUTS_SECTION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SHORTCUTS_SECTION, 
GtkShortcutsSectionClass))
+
+
+typedef struct _GtkShortcutsSection      GtkShortcutsSection;
+typedef struct _GtkShortcutsSectionClass GtkShortcutsSectionClass;
+
+GDK_AVAILABLE_IN_3_20
+GType        gtk_shortcuts_section_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __GTK_SHORTCUTS_SECTION_H__ */
diff --git a/gtk/gtkshortcutsshortcut.c b/gtk/gtkshortcutsshortcut.c
new file mode 100644
index 0000000..c14eb0a
--- /dev/null
+++ b/gtk/gtkshortcutsshortcut.c
@@ -0,0 +1,264 @@
+/* gtkshortcutsshortcut.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public License as
+ *  published by the Free Software Foundation; either version 2 of the
+ *  License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkshortcutsshortcut.h"
+
+#include "gtkshortcutlabelprivate.h"
+#include "gtkprivate.h"
+#include "gtkintl.h"
+
+/**
+ * SECTION:gtkshortcutsshortcut
+ * @Title: GtkShortcutsShortcut
+ * @Short_description: Represents a keyboard shortcut in a GtkShortcutsWindow
+ *
+ * A GtkShortcutsShortcut represents a single keyboard shortcut with
+ * a short text. This widget is only meant to be used with
+ * #GtkShortcutsWindow.
+ */
+
+struct _GtkShortcutsShortcut
+{
+  GtkBox            parent_instance;
+
+  GtkShortcutLabel *accelerator;
+  GtkLabel         *title;
+
+  GtkSizeGroup *accel_size_group;
+  GtkSizeGroup *title_size_group;
+};
+
+struct _GtkShortcutsShortcutClass
+{
+  GtkBoxClass parent_class;
+};
+
+G_DEFINE_TYPE (GtkShortcutsShortcut, gtk_shortcuts_shortcut, GTK_TYPE_BOX)
+
+enum {
+  PROP_0,
+  PROP_ACCELERATOR,
+  PROP_TITLE,
+  PROP_ACCEL_SIZE_GROUP,
+  PROP_TITLE_SIZE_GROUP,
+  LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP];
+
+static void
+gtk_shortcuts_shortcut_set_accel_size_group (GtkShortcutsShortcut *self,
+                                             GtkSizeGroup         *group)
+{
+  if (self->accel_size_group)
+    gtk_size_group_remove_widget (self->accel_size_group, GTK_WIDGET (self->accelerator));
+  if (group)
+    gtk_size_group_add_widget (group, GTK_WIDGET (self->accelerator));
+
+  g_set_object (&self->accel_size_group, group);
+}
+
+static void
+gtk_shortcuts_shortcut_set_title_size_group (GtkShortcutsShortcut *self,
+                                             GtkSizeGroup         *group)
+{
+  if (self->title_size_group)
+    gtk_size_group_remove_widget (self->title_size_group, GTK_WIDGET (self->title));
+  if (group)
+    gtk_size_group_add_widget (group, GTK_WIDGET (self->title));
+
+  g_set_object (&self->title_size_group, group);
+}
+
+static void
+gtk_shortcuts_shortcut_get_property (GObject    *object,
+                                     guint       prop_id,
+                                     GValue     *value,
+                                     GParamSpec *pspec)
+{
+  GtkShortcutsShortcut *self = GTK_SHORTCUTS_SHORTCUT (object);
+
+  switch (prop_id)
+    {
+    case PROP_TITLE:
+      g_value_set_string (value, gtk_label_get_label (self->title));
+      break;
+
+    case PROP_ACCELERATOR:
+      g_value_set_string (value, gtk_shortcut_label_get_accelerator (self->accelerator));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_shortcuts_shortcut_set_property (GObject      *object,
+                                     guint         prop_id,
+                                     const GValue *value,
+                                     GParamSpec   *pspec)
+{
+  GtkShortcutsShortcut *self = GTK_SHORTCUTS_SHORTCUT (object);
+
+  switch (prop_id)
+    {
+    case PROP_ACCELERATOR:
+      gtk_shortcut_label_set_accelerator (self->accelerator, g_value_get_string (value));
+      break;
+
+    case PROP_ACCEL_SIZE_GROUP:
+      gtk_shortcuts_shortcut_set_accel_size_group (self, GTK_SIZE_GROUP (g_value_get_object (value)));
+      break;
+
+    case PROP_TITLE:
+      gtk_label_set_label (self->title, g_value_get_string (value));
+      break;
+
+    case PROP_TITLE_SIZE_GROUP:
+      gtk_shortcuts_shortcut_set_title_size_group (self, GTK_SIZE_GROUP (g_value_get_object (value)));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_shortcuts_shortcut_finalize (GObject *object)
+{
+  GtkShortcutsShortcut *self = GTK_SHORTCUTS_SHORTCUT (object);
+
+  g_clear_object (&self->accel_size_group);
+  g_clear_object (&self->title_size_group);
+
+  G_OBJECT_CLASS (gtk_shortcuts_shortcut_parent_class)->finalize (object);
+}
+
+static void
+gtk_shortcuts_shortcut_add (GtkContainer *container,
+                            GtkWidget    *widget)
+{
+  g_warning ("Can't add children to %s", G_OBJECT_TYPE_NAME (container));
+}
+
+static GType
+gtk_shortcuts_shortcut_child_type (GtkContainer *container)
+{
+  return G_TYPE_NONE;
+}
+
+static void
+gtk_shortcuts_shortcut_class_init (GtkShortcutsShortcutClass *klass)
+{
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtk_shortcuts_shortcut_finalize;
+  object_class->get_property = gtk_shortcuts_shortcut_get_property;
+  object_class->set_property = gtk_shortcuts_shortcut_set_property;
+
+  container_class->add = gtk_shortcuts_shortcut_add;
+  container_class->child_type = gtk_shortcuts_shortcut_child_type;
+
+  /**
+   * GtkShortcutsShortcut:accelerator:
+   *
+   * The accelerator(s) represented by this object, in the syntax
+   * understood by gtk_accelerator_parse(). Multiple accelerators
+   * can be specified by separating them with a space, but keep in
+   * mind that the available width is limited.
+   *
+   * Here is an example: <ctrl>? F1
+   *
+   * Note that < and > need to escaped as &lt; and &gt; when used
+   * in .ui files.
+   */
+  properties[PROP_ACCELERATOR] =
+    g_param_spec_string ("accelerator",
+                         P_("Accelerator"),
+                         P_("Accelerator"),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GtkShortcutsShortcut:title:
+   *
+   * The textual description for the accelerators represented by
+   * this object. This should be a short string that can fit in
+   * a single line.
+   */
+  properties[PROP_TITLE] =
+    g_param_spec_string ("title",
+                         P_("Title"),
+                         P_("Title"),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GtkShortcutsShortcut:accel-size-group:
+   *
+   * The size group for the accelerator portion of this shortcut.
+   *
+   * This is used internally by GTK+, and must not be modified by applications.
+   */
+  properties[PROP_ACCEL_SIZE_GROUP] =
+    g_param_spec_object ("accel-size-group",
+                         P_("Accelerator Size Group"),
+                         P_("Accelerator Size Group"),
+                         GTK_TYPE_SIZE_GROUP,
+                         (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GtkShortcutsShortcut:title-size-group:
+   *
+   * The size group for the textual portion of this shortcut.
+   *
+   * This is used internally by GTK+, and must not be modified by applications.
+   */
+  properties[PROP_TITLE_SIZE_GROUP] =
+    g_param_spec_object ("title-size-group",
+                         P_("Title Size Group"),
+                         P_("Title Size Group"),
+                         GTK_TYPE_SIZE_GROUP,
+                         (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+gtk_shortcuts_shortcut_init (GtkShortcutsShortcut *self)
+{
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_HORIZONTAL);
+  gtk_box_set_spacing (GTK_BOX (self), 12);
+
+  self->accelerator = g_object_new (GTK_TYPE_SHORTCUT_LABEL,
+                                    "visible", TRUE,
+                                    NULL);
+  GTK_CONTAINER_CLASS (gtk_shortcuts_shortcut_parent_class)->add (GTK_CONTAINER (self), GTK_WIDGET 
(self->accelerator));
+
+  self->title = g_object_new (GTK_TYPE_LABEL,
+                              "hexpand", TRUE,
+                              "visible", TRUE,
+                              "xalign", 0.0f,
+                              NULL);
+  GTK_CONTAINER_CLASS (gtk_shortcuts_shortcut_parent_class)->add (GTK_CONTAINER (self), GTK_WIDGET 
(self->title));
+}
diff --git a/gtk/gtkshortcutsshortcut.h b/gtk/gtkshortcutsshortcut.h
new file mode 100644
index 0000000..57d76ad
--- /dev/null
+++ b/gtk/gtkshortcutsshortcut.h
@@ -0,0 +1,43 @@
+/* gtkshortcutsshortcutprivate.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public License as
+ *  published by the Free Software Foundation; either version 2 of the
+ *  License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTK_SHORTCUTS_SHORTCUT_H
+#define GTK_SHORTCUTS_SHORTCUT_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SHORTCUTS_SHORTCUT (gtk_shortcuts_shortcut_get_type())
+#define GTK_SHORTCUTS_SHORTCUT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GTK_TYPE_SHORTCUTS_SHORTCUT, GtkShortcutsShortcut))
+#define GTK_SHORTCUTS_SHORTCUT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), 
GTK_TYPE_SHORTCUTS_SHORTCUT, GtkShortcutsShortcutClass))
+#define GTK_IS_SHORTCUTS_SHORTCUT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GTK_TYPE_SHORTCUTS_SHORTCUT))
+#define GTK_IS_SHORTCUTS_SHORTCUT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GTK_TYPE_SHORTCUTS_SHORTCUT))
+#define GTK_SHORTCUTS_SHORTCUT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), 
GTK_TYPE_SHORTCUTS_SHORTCUT, GtkShortcutsShortcutClass))
+
+
+typedef struct _GtkShortcutsShortcut      GtkShortcutsShortcut;
+typedef struct _GtkShortcutsShortcutClass GtkShortcutsShortcutClass;
+
+
+GDK_AVAILABLE_IN_3_20
+GType        gtk_shortcuts_shortcut_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GTK_SHORTCUTS_SHORTCUT_H */
diff --git a/gtk/gtkshortcutswindow.c b/gtk/gtkshortcutswindow.c
new file mode 100644
index 0000000..5e274ec
--- /dev/null
+++ b/gtk/gtkshortcutswindow.c
@@ -0,0 +1,882 @@
+/* gtkshortcutswindow.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public License as
+ *  published by the Free Software Foundation; either version 2 of the
+ *  License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkshortcutswindow.h"
+#include "gtkscrolledwindow.h"
+#include "gtkshortcutssection.h"
+#include "gtkshortcutsgroup.h"
+#include "gtkshortcutsgesture.h"
+#include "gtkshortcutsshortcut.h"
+#include "gtksearchbar.h"
+#include "gtksearchentry.h"
+#include "gtkprivate.h"
+#include "gtkintl.h"
+
+/**
+ * SECTION:gtkshortcutswindow
+ * @Title: GtkShortcutsWindow
+ * @Short_description: Toplevel which shows help for shortcuts
+ *
+ * A GtkShortcutsWindow shows brief information about the keyboard shortcuts
+ * and gestures of an application. The shortcuts can be grouped, and you can
+ * have multiple sections in this window, corresponding to the major modes of
+ * your application.
+ *
+ * Additionally, the shortcuts can be filtered by the current view, to avoid
+ * showing information that is not relevant in the current application context.
+ *
+ * The recommended way to construct a GtkShortcutsWindow is with GtkBuilder,
+ * by populating a #GtkShortcutsWindow with one or more #GtkShortcutsSection
+ * objects, which contain #GtkShortcutsGroups that in turn contain objects of
+ * class #GtkShortcutsShortcut or #GtkShortcutsGesture.
+ *
+ * # A simple example:
+ *
+ * ![](gedit-shortcuts.png)
+ *
+ * This example has as single section. As you can see, the shortcut groups
+ * are arranged in columns, and spread across several pages if there are too
+ * many to find on a single page.
+ *
+ * The .ui file for this example can be found 
[here](https://git.gnome.org/browse/gtk+/tree/demos/gtk-demo/shortcuts-gedit.ui).
+ *
+ * # An example with multiple views:
+ *
+ * ![](clocks-shortcuts.png)
+ *
+ * This example shows a #GtkShortcutsWindow that has been configured to show only
+ * the shortcuts relevant to the "stopwatch" view.
+ *
+ * The .ui file for this example can be found 
[here](https://git.gnome.org/browse/gtk+/tree/demos/gtk-demo/shortcuts-clocks.ui).
+ *
+ * # An example with multiple sections:
+ *
+ * ![](builder-shortcuts.png)
+ *
+ * This example shows a #GtkShortcutsWindow with two sections, "Editor Shortcuts"
+ * and "Terminal Shortcuts".
+ *
+ * The .ui file for this example can be found 
[here](https://git.gnome.org/browse/gtk+/tree/demos/gtk-demo/shortcuts-clocks.ui).
+ */
+
+typedef struct
+{
+  GHashTable     *keywords;
+  gchar          *initial_section;
+  gchar          *last_section_name;
+  gchar          *view_name;
+  GtkSizeGroup   *search_text_group;
+  GtkSizeGroup   *search_image_group;
+  GHashTable     *search_items_hash;
+
+  GtkStack       *stack;
+  GtkStack       *title_stack;
+  GtkMenuButton  *menu_button;
+  GtkLabel       *menu_label;
+  GtkSearchBar   *search_bar;
+  GtkSearchEntry *search_entry;
+  GtkHeaderBar   *header_bar;
+  GtkPopover     *popover;
+  GtkListBox     *list_box;
+  GtkBox         *search_gestures;
+  GtkBox         *search_shortcuts;
+} GtkShortcutsWindowPrivate;
+
+typedef struct
+{
+  GtkShortcutsWindow *self;
+  GtkBuilder        *builder;
+  GQueue            *stack;
+  gchar             *property_name;
+  guint              translatable : 1;
+} ViewsParserData;
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtkShortcutsWindow, gtk_shortcuts_window, GTK_TYPE_WINDOW)
+
+
+enum {
+  CLOSE,
+  SEARCH,
+  LAST_SIGNAL
+};
+
+enum {
+  PROP_0,
+  PROP_SECTION_NAME,
+  PROP_VIEW_NAME,
+  LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP];
+static guint signals[LAST_SIGNAL];
+
+
+static gint
+number_of_children (GtkContainer *container)
+{
+  GList *children;
+  gint n;
+
+  children = gtk_container_get_children (container);
+  n = g_list_length (children);
+  g_list_free (children);
+
+  return n;
+}
+
+static void
+update_title_stack (GtkShortcutsWindow *self)
+{
+  GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
+  GtkWidget *visible_child;
+
+  visible_child = gtk_stack_get_visible_child (priv->stack);
+
+  if (GTK_IS_SHORTCUTS_SECTION (visible_child))
+    {
+      if (number_of_children (GTK_CONTAINER (priv->stack)) > 3)
+        {
+          gchar *title;
+
+          gtk_stack_set_visible_child_name (priv->title_stack, "sections");
+          g_object_get (visible_child, "title", &title, NULL);
+          gtk_label_set_label (priv->menu_label, title);
+          g_free (title);
+        }
+      else
+        {
+          gtk_stack_set_visible_child_name (priv->title_stack, "title");
+        }
+    }
+  else if (visible_child != NULL)
+    {
+      gtk_stack_set_visible_child_name (priv->title_stack, "search");
+    }
+}
+
+static void
+gtk_shortcuts_window_add_search_item (GtkWidget *child, gpointer data)
+{
+  GtkShortcutsWindow *self = data;
+  GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
+  GtkWidget *item;
+  gchar *subtitle = NULL;
+  gchar *accelerator = NULL;
+  gchar *title = NULL;
+  gchar *hash_key = NULL;
+  GIcon *icon = NULL;
+  gchar *str;
+  gchar *keywords;
+
+  if (GTK_IS_SHORTCUTS_SHORTCUT (child))
+    {
+      g_object_get (child,
+                    "accelerator", &accelerator,
+                    "title", &title,
+                    NULL);
+
+      hash_key = g_strdup_printf ("%s-%s", title, accelerator);
+      if (g_hash_table_contains (priv->search_items_hash, hash_key))
+        {
+          g_free (hash_key);
+          g_free (title);
+          g_free (accelerator);
+          return;
+        }
+
+      g_hash_table_insert (priv->search_items_hash, hash_key, GINT_TO_POINTER (1));
+
+      item = g_object_new (GTK_TYPE_SHORTCUTS_SHORTCUT,
+                           "visible", TRUE,
+                           "accelerator", accelerator,
+                           "title", title,
+                           "accel-size-group", priv->search_image_group,
+                           "title-size-group", priv->search_text_group,
+                           NULL);
+
+      str = g_strdup_printf ("%s %s", accelerator, title);
+      keywords = g_utf8_strdown (str, -1);
+
+      g_hash_table_insert (priv->keywords, item, keywords);
+      gtk_container_add (GTK_CONTAINER (priv->search_shortcuts), item);
+
+      g_free (title);
+      g_free (accelerator);
+      g_free (str);
+    }
+  else if (GTK_IS_SHORTCUTS_GESTURE (child))
+    {
+      g_object_get (child,
+                    "title", &title,
+                    "subtitle", &subtitle,
+                    "icon", &icon,
+                    NULL);
+
+      hash_key = g_strdup_printf ("%s-%s", title, subtitle);
+      if (g_hash_table_contains (priv->search_items_hash, hash_key))
+        {
+          g_free (subtitle);
+          g_free (title);
+          g_free (hash_key);
+          g_clear_object (&icon);
+          return;
+        }
+
+      g_hash_table_insert (priv->search_items_hash, hash_key, GINT_TO_POINTER (1));
+
+      item = g_object_new (GTK_TYPE_SHORTCUTS_GESTURE,
+                           "visible", TRUE,
+                           "title", title,
+                           "subtitle", subtitle,
+                           "icon", icon,
+                           "icon-size-group", priv->search_image_group,
+                           "title-size-group", priv->search_text_group,
+                           NULL);
+
+      str = g_strdup_printf ("%s %s", title, subtitle);
+      keywords = g_utf8_strdown (str, -1);
+
+      g_hash_table_insert (priv->keywords, item, keywords);
+      gtk_container_add (GTK_CONTAINER (priv->search_gestures), item);
+
+      g_free (subtitle);
+      g_free (title);
+      g_clear_object (&icon);
+      g_free (str);
+    }
+  else if (GTK_IS_CONTAINER (child))
+    {
+      gtk_container_foreach (GTK_CONTAINER (child), gtk_shortcuts_window_add_search_item, self);
+    }
+}
+
+static void
+gtk_shortcuts_window_add_section (GtkShortcutsWindow  *self,
+                                  GtkShortcutsSection *section)
+{
+  GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
+  GtkListBoxRow *row;
+  gchar *title;
+  gchar *name;
+  const gchar *visible_section;
+  GtkWidget *label;
+
+  gtk_container_foreach (GTK_CONTAINER (section), gtk_shortcuts_window_add_search_item, self);
+
+  g_object_get (section,
+                "section-name", &name,
+                "title", &title,
+                NULL);
+
+  if (name == NULL)
+    name = g_strdup ("shortcuts");
+
+  gtk_stack_add_titled (priv->stack, GTK_WIDGET (section), name, title);
+
+  visible_section = gtk_stack_get_visible_child_name (priv->stack);
+  if (strcmp (visible_section, "internal-search") == 0 ||
+      (priv->initial_section && strcmp (priv->initial_section, visible_section) == 0))
+    gtk_stack_set_visible_child (priv->stack, GTK_WIDGET (section));
+
+  row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
+                      "visible", TRUE,
+                      NULL);
+  g_object_set_data_full (G_OBJECT (row), "GTK_SHORTCUTS_SECTION_NAME", g_strdup (name), g_free);
+  label = g_object_new (GTK_TYPE_LABEL,
+                        "margin", 6,
+                        "label", title,
+                        "xalign", 0.5f,
+                        "visible", TRUE,
+                        NULL);
+  gtk_container_add (GTK_CONTAINER (row), GTK_WIDGET (label));
+  gtk_container_add (GTK_CONTAINER (priv->list_box), GTK_WIDGET (row));
+
+  update_title_stack (self);
+
+  g_free (name);
+  g_free (title);
+}
+
+static void
+gtk_shortcuts_window_add (GtkContainer *container,
+                          GtkWidget    *widget)
+{
+  GtkShortcutsWindow *self = (GtkShortcutsWindow *)container;
+
+  if (GTK_IS_SHORTCUTS_SECTION (widget))
+    gtk_shortcuts_window_add_section (self, GTK_SHORTCUTS_SECTION (widget));
+  else
+    g_warning ("Can't add children of type %s to %s",
+               G_OBJECT_TYPE_NAME (widget),
+               G_OBJECT_TYPE_NAME (container));
+}
+
+static void
+gtk_shortcuts_window_set_view_name (GtkShortcutsWindow *self,
+                                    const gchar        *view_name)
+{
+  GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
+  GList *sections, *l;
+
+  g_free (priv->view_name);
+  priv->view_name = g_strdup (view_name);
+
+  sections = gtk_container_get_children (GTK_CONTAINER (priv->stack));
+  for (l = sections; l; l = l->next)
+    {
+      GtkShortcutsSection *section = l->data;
+
+      if (GTK_IS_SHORTCUTS_SECTION (section))
+        g_object_set (section, "view-name", priv->view_name, NULL);
+    }
+  g_list_free (sections);
+}
+
+static void
+gtk_shortcuts_window_set_section_name (GtkShortcutsWindow *self,
+                                       const gchar        *section_name)
+{
+  GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
+  GtkWidget *section;
+
+  g_free (priv->initial_section);
+  priv->initial_section = g_strdup (section_name);
+
+  section = gtk_stack_get_child_by_name (priv->stack, section_name);
+  if (section)
+    gtk_stack_set_visible_child (priv->stack, section);
+}
+
+static void
+gtk_shortcuts_window__list_box__row_activated (GtkShortcutsWindow *self,
+                                               GtkListBoxRow      *row,
+                                               GtkListBox         *list_box)
+{
+  GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
+  const gchar *name;
+
+  name = g_object_get_data (G_OBJECT (row), "GTK_SHORTCUTS_SECTION_NAME");
+  gtk_stack_set_visible_child_name (priv->stack, name);
+  gtk_widget_hide (GTK_WIDGET (priv->popover));
+}
+
+static void
+gtk_shortcuts_window__entry__changed (GtkShortcutsWindow *self,
+                                     GtkSearchEntry      *search_entry)
+{
+  GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
+  gchar *downcase = NULL;
+  GHashTableIter iter;
+  const gchar *text;
+  const gchar *last_section_name;
+  gpointer key;
+  gpointer value;
+  gboolean has_result;
+
+  text = gtk_entry_get_text (GTK_ENTRY (search_entry));
+
+  if (!text || !*text)
+    {
+      if (priv->last_section_name != NULL)
+        {
+          gtk_stack_set_visible_child_name (priv->stack, priv->last_section_name);
+          return;
+        }
+    }
+
+  last_section_name = gtk_stack_get_visible_child_name (priv->stack);
+
+  if (g_strcmp0 (last_section_name, "internal-search") != 0 &&
+      g_strcmp0 (last_section_name, "no-search-results") != 0)
+    {
+      g_free (priv->last_section_name);
+      priv->last_section_name = g_strdup (last_section_name);
+    }
+
+  downcase = g_utf8_strdown (text, -1);
+
+  g_hash_table_iter_init (&iter, priv->keywords);
+
+  has_result = FALSE;
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      GtkWidget *widget = key;
+      const gchar *keywords = value;
+      gboolean match;
+
+      match = strstr (keywords, downcase) != NULL;
+      gtk_widget_set_visible (widget, match);
+      has_result |= match;
+    }
+
+  g_free (downcase);
+
+  if (has_result)
+    gtk_stack_set_visible_child_name (priv->stack, "internal-search");
+  else
+    gtk_stack_set_visible_child_name (priv->stack, "no-search-results");
+}
+
+static void
+gtk_shortcuts_window__search_mode__changed (GtkShortcutsWindow *self)
+{
+  GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
+
+  if (!gtk_search_bar_get_search_mode (priv->search_bar))
+    {
+      if (priv->last_section_name != NULL)
+        gtk_stack_set_visible_child_name (priv->stack, priv->last_section_name);
+    }
+}
+
+static void
+gtk_shortcuts_window_close (GtkShortcutsWindow *self)
+{
+  gtk_window_close (GTK_WINDOW (self));
+}
+
+static void
+gtk_shortcuts_window_search (GtkShortcutsWindow *self)
+{
+  GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
+
+  gtk_search_bar_set_search_mode (priv->search_bar, TRUE);
+}
+
+static void
+gtk_shortcuts_window_constructed (GObject *object)
+{
+  GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
+  GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
+
+  G_OBJECT_CLASS (gtk_shortcuts_window_parent_class)->constructed (object);
+
+  if (priv->initial_section != NULL)
+    gtk_stack_set_visible_child_name (priv->stack, priv->initial_section);
+}
+
+static void
+gtk_shortcuts_window_finalize (GObject *object)
+{
+  GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
+  GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
+
+  g_clear_pointer (&priv->keywords, g_hash_table_unref);
+  g_clear_pointer (&priv->initial_section, g_free);
+  g_clear_pointer (&priv->view_name, g_free);
+  g_clear_pointer (&priv->last_section_name, g_free);
+  g_clear_pointer (&priv->search_items_hash, g_hash_table_unref);
+
+  g_clear_object (&priv->search_image_group);
+  g_clear_object (&priv->search_text_group);
+
+  G_OBJECT_CLASS (gtk_shortcuts_window_parent_class)->finalize (object);
+}
+
+static void
+gtk_shortcuts_window_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
+  GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_SECTION_NAME:
+      {
+        GtkWidget *child = gtk_stack_get_visible_child (priv->stack);
+
+        if (child != NULL)
+          {
+            gchar *name = NULL;
+
+            gtk_container_child_get (GTK_CONTAINER (priv->stack), child,
+                                     "name", &name,
+                                     NULL);
+            g_value_take_string (value, name);
+          }
+      }
+      break;
+
+    case PROP_VIEW_NAME:
+      g_value_set_string (value, priv->view_name);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_shortcuts_window_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
+
+  switch (prop_id)
+    {
+    case PROP_SECTION_NAME:
+      gtk_shortcuts_window_set_section_name (self, g_value_get_string (value));
+      break;
+
+    case PROP_VIEW_NAME:
+      gtk_shortcuts_window_set_view_name (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_shortcuts_window_unmap (GtkWidget *widget)
+{
+  GtkShortcutsWindow *self = (GtkShortcutsWindow *)widget;
+  GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
+
+  gtk_search_bar_set_search_mode (priv->search_bar, FALSE);
+
+  GTK_WIDGET_CLASS (gtk_shortcuts_window_parent_class)->unmap (widget);
+}
+
+static GType
+gtk_shortcuts_window_child_type (GtkContainer *container)
+{
+  return GTK_TYPE_SHORTCUTS_SECTION;
+}
+
+static void
+gtk_shortcuts_window_class_init (GtkShortcutsWindowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+  GtkBindingSet *binding_set = gtk_binding_set_by_class (klass);
+
+  object_class->constructed = gtk_shortcuts_window_constructed;
+  object_class->finalize = gtk_shortcuts_window_finalize;
+  object_class->get_property = gtk_shortcuts_window_get_property;
+  object_class->set_property = gtk_shortcuts_window_set_property;
+
+  widget_class->unmap = gtk_shortcuts_window_unmap;
+  container_class->add = gtk_shortcuts_window_add;
+  container_class->child_type = gtk_shortcuts_window_child_type;
+
+  klass->close = gtk_shortcuts_window_close;
+  klass->search = gtk_shortcuts_window_search;
+
+  /**
+   * GtkShortcutsWindow:section-name:
+   *
+   * The name of the section to show.
+   *
+   * This should be the section-name of one of the #GtkShortcutsSection
+   * objects that are in this shortcuts window.
+   */
+  properties[PROP_SECTION_NAME] =
+    g_param_spec_string ("section-name", P_("Section Name"), P_("Section Name"),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GtkShortcutsWindow:view-name:
+   *
+   * The view name by which to filter the contents.
+   *
+   * This should correspond to the #GtkShortcutsGroup:view property of some of
+   * the #GtkShortcutsGroup objects that are inside this shortcuts window.
+   *
+   * Set this to %NULL to show all groups.
+   */
+  properties[PROP_VIEW_NAME] =
+    g_param_spec_string ("view-name", P_("View Name"), P_("View Name"),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+  /**
+   * GtkShortcutsWindow::close:
+   *
+   * The ::close signal is a
+   * [keybinding signal][GtkBindingSignal]
+   * which gets emitted when the user uses a keybinding to close
+   * the window.
+   *
+   * The default binding for this signal is the Escape key.
+   */
+  signals[CLOSE] = g_signal_new (I_("close"),
+                                 G_TYPE_FROM_CLASS (klass),
+                                 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                                 G_STRUCT_OFFSET (GtkShortcutsWindowClass, close),
+                                 NULL, NULL, NULL,
+                                 G_TYPE_NONE,
+                                 0);
+
+  /**
+   * GtkShortcutsWindow::search:
+   *
+   * The ::search signal is a
+   * [keybinding signal][GtkBindingSignal]
+   * which gets emitted when the user uses a keybinding to start a search.
+   *
+   * The default binding for this signal is Control-F.
+   */
+  signals[SEARCH] = g_signal_new (I_("search"),
+                                 G_TYPE_FROM_CLASS (klass),
+                                 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                                 G_STRUCT_OFFSET (GtkShortcutsWindowClass, search),
+                                 NULL, NULL, NULL,
+                                 G_TYPE_NONE,
+                                 0);
+
+  gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "close", 0);
+  gtk_binding_entry_add_signal (binding_set, GDK_KEY_f, GDK_CONTROL_MASK, "search", 0);
+
+  g_type_ensure (GTK_TYPE_SHORTCUTS_GROUP);
+  g_type_ensure (GTK_TYPE_SHORTCUTS_GROUP);
+  g_type_ensure (GTK_TYPE_SHORTCUTS_GESTURE);
+  g_type_ensure (GTK_TYPE_SHORTCUTS_SHORTCUT);
+}
+
+static gboolean
+window_key_press_event_cb (GtkWidget *window,
+                           GdkEvent  *event,
+                           gpointer   data)
+{
+  GtkShortcutsWindow *self = GTK_SHORTCUTS_WINDOW (window);
+  GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
+
+  return gtk_search_bar_handle_event (priv->search_bar, event);
+}
+
+static void
+gtk_shortcuts_window_init (GtkShortcutsWindow *self)
+{
+  GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
+  GtkToggleButton *search_button;
+  GtkBox *main_box;
+  GtkBox *menu_box;
+  GtkBox *box;
+  GtkArrow *arrow;
+  GtkWidget *scroller;
+  GtkWidget *label;
+  GtkWidget *empty;
+  PangoAttrList *attributes;
+
+  gtk_window_set_resizable (GTK_WINDOW (self), FALSE);
+  gtk_window_set_type_hint (GTK_WINDOW (self), GDK_WINDOW_TYPE_HINT_DIALOG);
+
+  g_signal_connect (self, "key-press-event",
+                    G_CALLBACK (window_key_press_event_cb), NULL);
+
+  priv->keywords = g_hash_table_new_full (NULL, NULL, NULL, g_free);
+  priv->search_items_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+  priv->search_text_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+  priv->search_image_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+  priv->header_bar = g_object_new (GTK_TYPE_HEADER_BAR,
+                                   "show-close-button", TRUE,
+                                   "visible", TRUE,
+                                   NULL);
+  gtk_window_set_titlebar (GTK_WINDOW (self), GTK_WIDGET (priv->header_bar));
+
+  search_button = g_object_new (GTK_TYPE_TOGGLE_BUTTON,
+                                "child", g_object_new (GTK_TYPE_IMAGE,
+                                                       "visible", TRUE,
+                                                       "icon-name", "edit-find-symbolic",
+                                                       NULL),
+                                "visible", TRUE,
+                                NULL);
+  gtk_container_add (GTK_CONTAINER (priv->header_bar), GTK_WIDGET (search_button));
+
+  main_box = g_object_new (GTK_TYPE_BOX,
+                           "orientation", GTK_ORIENTATION_VERTICAL,
+                           "visible", TRUE,
+                           NULL);
+  GTK_CONTAINER_CLASS (gtk_shortcuts_window_parent_class)->add (GTK_CONTAINER (self), GTK_WIDGET (main_box));
+
+  priv->search_bar = g_object_new (GTK_TYPE_SEARCH_BAR,
+                                   "visible", TRUE,
+                                   NULL);
+  g_object_bind_property (priv->search_bar, "search-mode-enabled",
+                          search_button, "active",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+  gtk_container_add (GTK_CONTAINER (main_box), GTK_WIDGET (priv->search_bar));
+
+  priv->stack = g_object_new (GTK_TYPE_STACK,
+                              "expand", TRUE,
+                              "homogeneous", TRUE,
+                              "transition-type", GTK_STACK_TRANSITION_TYPE_CROSSFADE,
+                              "visible", TRUE,
+                              NULL);
+  gtk_container_add (GTK_CONTAINER (main_box), GTK_WIDGET (priv->stack));
+
+  priv->title_stack = g_object_new (GTK_TYPE_STACK,
+                                    "visible", TRUE,
+                                    NULL);
+  gtk_header_bar_set_custom_title (priv->header_bar, GTK_WIDGET (priv->title_stack));
+
+  label = gtk_label_new (_("Shortcuts"));
+  gtk_widget_show (label);
+  gtk_style_context_add_class (gtk_widget_get_style_context (label), "title");
+  gtk_stack_add_named (priv->title_stack, label, "title");
+
+  label = gtk_label_new (_("Search Results"));
+  gtk_widget_show (label);
+  gtk_style_context_add_class (gtk_widget_get_style_context (label), "title");
+  gtk_stack_add_named (priv->title_stack, label, "search");
+
+  priv->menu_button = g_object_new (GTK_TYPE_MENU_BUTTON,
+                                    "focus-on-click", FALSE,
+                                    "visible", TRUE,
+                                    NULL);
+  gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (priv->menu_button)), "flat");
+  gtk_stack_add_named (priv->title_stack, GTK_WIDGET (priv->menu_button), "sections");
+
+  menu_box = g_object_new (GTK_TYPE_BOX,
+                           "orientation", GTK_ORIENTATION_HORIZONTAL,
+                           "spacing", 6,
+                           "visible", TRUE,
+                           NULL);
+  gtk_container_add (GTK_CONTAINER (priv->menu_button), GTK_WIDGET (menu_box));
+
+  priv->menu_label = g_object_new (GTK_TYPE_LABEL,
+                                   "visible", TRUE,
+                                   NULL);
+  gtk_container_add (GTK_CONTAINER (menu_box), GTK_WIDGET (priv->menu_label));
+
+  G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+  arrow = g_object_new (GTK_TYPE_ARROW,
+                        "arrow-type", GTK_ARROW_DOWN,
+                        "visible", TRUE,
+                        NULL);
+  gtk_container_add (GTK_CONTAINER (menu_box), GTK_WIDGET (arrow));
+  G_GNUC_END_IGNORE_DEPRECATIONS;
+
+  priv->popover = g_object_new (GTK_TYPE_POPOVER,
+                                "border-width", 6,
+                                "relative-to", priv->menu_button,
+                                "position", GTK_POS_BOTTOM,
+                                NULL);
+  gtk_menu_button_set_popover (priv->menu_button, GTK_WIDGET (priv->popover));
+
+  priv->list_box = g_object_new (GTK_TYPE_LIST_BOX,
+                                 "selection-mode", GTK_SELECTION_NONE,
+                                 "visible", TRUE,
+                                 NULL);
+  g_signal_connect_object (priv->list_box,
+                           "row-activated",
+                           G_CALLBACK (gtk_shortcuts_window__list_box__row_activated),
+                           self,
+                           G_CONNECT_SWAPPED);
+  gtk_container_add (GTK_CONTAINER (priv->popover), GTK_WIDGET (priv->list_box));
+
+  priv->search_entry = GTK_SEARCH_ENTRY (gtk_search_entry_new ());
+  gtk_widget_show (GTK_WIDGET (priv->search_entry));
+  gtk_container_add (GTK_CONTAINER (priv->search_bar), GTK_WIDGET (priv->search_entry));
+  g_object_set (priv->search_entry,
+                "placeholder-text", _("Search Shortcuts"),
+                "width-chars", 40,
+                NULL);
+  g_signal_connect_object (priv->search_entry,
+                           "search-changed",
+                           G_CALLBACK (gtk_shortcuts_window__entry__changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (priv->search_bar,
+                           "notify::search-mode-enabled",
+                           G_CALLBACK (gtk_shortcuts_window__search_mode__changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (priv->stack, "notify::visible-child",
+                           G_CALLBACK (update_title_stack), self, G_CONNECT_SWAPPED);
+
+  scroller = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
+                           "visible", TRUE,
+                           NULL);
+  box = g_object_new (GTK_TYPE_BOX,
+                      "border-width", 24,
+                      "halign", GTK_ALIGN_CENTER,
+                      "spacing", 24,
+                      "orientation", GTK_ORIENTATION_VERTICAL,
+                      "visible", TRUE,
+                      NULL);
+  gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (box));
+  gtk_stack_add_named (priv->stack, scroller, "internal-search");
+
+  priv->search_shortcuts = g_object_new (GTK_TYPE_BOX,
+                                         "halign", GTK_ALIGN_CENTER,
+                                         "spacing", 6,
+                                         "orientation", GTK_ORIENTATION_VERTICAL,
+                                         "visible", TRUE,
+                                         NULL);
+  gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (priv->search_shortcuts));
+
+  priv->search_gestures = g_object_new (GTK_TYPE_BOX,
+                                        "halign", GTK_ALIGN_CENTER,
+                                        "spacing", 6,
+                                        "orientation", GTK_ORIENTATION_VERTICAL,
+                                        "visible", TRUE,
+                                        NULL);
+  gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (priv->search_gestures));
+
+  empty = g_object_new (GTK_TYPE_GRID,
+                        "visible", TRUE,
+                        "row-spacing", 12,
+                        "margin", 12,
+                        "hexpand", TRUE,
+                        "vexpand", TRUE,
+                        "halign", GTK_ALIGN_CENTER,
+                        "valign", GTK_ALIGN_CENTER,
+                        NULL);
+  gtk_style_context_add_class (gtk_widget_get_style_context (empty), "dim-label");
+  gtk_grid_attach (GTK_GRID (empty),
+                   g_object_new (GTK_TYPE_IMAGE,
+                                 "visible", TRUE,
+                                 "icon-name", "edit-find-symbolic",
+                                 "pixel-size", 72,
+                                 NULL),
+                   0, 0, 1, 1);
+  attributes = pango_attr_list_new ();
+  pango_attr_list_insert (attributes, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
+  pango_attr_list_insert (attributes, pango_attr_scale_new (1.44));
+  label = g_object_new (GTK_TYPE_LABEL,
+                        "visible", TRUE,
+                        "label", _("No Results Found"),
+                        "attributes", attributes,
+                        NULL);
+  pango_attr_list_unref (attributes);
+  gtk_grid_attach (GTK_GRID (empty), label, 0, 1, 1, 1);
+  label = g_object_new (GTK_TYPE_LABEL,
+                        "visible", TRUE,
+                        "label", _("Try a different search"),
+                        NULL);
+  gtk_grid_attach (GTK_GRID (empty), label, 0, 2, 1, 1);
+
+  gtk_stack_add_named (priv->stack, empty, "no-search-results");
+}
diff --git a/gtk/gtkshortcutswindow.h b/gtk/gtkshortcutswindow.h
new file mode 100644
index 0000000..ca2ded8
--- /dev/null
+++ b/gtk/gtkshortcutswindow.h
@@ -0,0 +1,57 @@
+/* gtkshortcutswindow.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public License as
+ *  published by the Free Software Foundation; either version 2 of the
+ *  License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public
+ *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_SHORTCUTS_WINDOW_H__
+#define __GTK_SHORTCUTS_WINDOW_H__
+
+#include <gtk/gtkwindow.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SHORTCUTS_WINDOW            (gtk_shortcuts_window_get_type ())
+#define GTK_SHORTCUTS_WINDOW(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SHORTCUTS_WINDOW, 
GtkShortcutsWindow))
+#define GTK_SHORTCUTS_WINDOW_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SHORTCUTS_WINDOW, 
GtkShortcutsWindowClass))
+#define GTK_IS_SHORTCUTS_WINDOW(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SHORTCUTS_WINDOW))
+#define GTK_IS_SHORTCUTS_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SHORTCUTS_WINDOW))
+#define GTK_SHORTCUTS_WINDOW_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SHORTCUTS_WINDOW, 
GtkShortcutsWindowClass))
+
+
+typedef struct _GtkShortcutsWindow         GtkShortcutsWindow;
+typedef struct _GtkShortcutsWindowClass    GtkShortcutsWindowClass;
+
+
+struct _GtkShortcutsWindow
+{
+  GtkWindow window;
+};
+
+struct _GtkShortcutsWindowClass
+{
+  GtkWindowClass parent_class;
+
+  void (*close)  (GtkShortcutsWindow *self);
+  void (*search) (GtkShortcutsWindow *self);
+};
+
+GDK_AVAILABLE_IN_3_20
+GType gtk_shortcuts_window_get_type (void) G_GNUC_CONST;
+
+
+G_END_DECLS
+
+#endif /* GTK_SHORTCUTS_WINDOW _H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]