[gtk+] Add GtkShortcutsWindow
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+] Add GtkShortcutsWindow
- Date: Wed, 21 Oct 2015 19:48:50 +0000 (UTC)
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 < and > 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]