diff --git a/docs/reference/gtk/Makefile.am b/docs/reference/gtk/Makefile.am index 63cd349c72..ce6d6e2533 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 9194c4722d..3b975641be 100644 --- a/docs/reference/gtk/gtk-docs.sgml +++ b/docs/reference/gtk/gtk-docs.sgml @@ -239,6 +239,15 @@ + + Shortcuts Overview + + + + + + + Miscellaneous diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt index 60d3eb8449..f4482e1378 100644 --- a/docs/reference/gtk/gtk3-sections.txt +++ b/docs/reference/gtk/gtk3-sections.txt @@ -8422,3 +8422,74 @@ GTK_IS_GL_AREA_CLASS gtk_gl_area_get_type + +
+gtkshortcutswindow +GtkShortcutsWindow + +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 + +gtk_shortcuts_window_get_type +
+ +
+gtkshortcutssection +GtkShortcutsSection + +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 + +gtk_shortcuts_section_get_type +
+ +
+gtkshortcutsgroup +GtkShortcutsGroup + +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 + +gtk_shortcuts_group_get_type +
+ +
+gtkshortcutsshortcut +GtkShortcutsShortcut + +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 + + +gtk_shortcuts_shortcut_get_type +
+ +
+gtkshortcutsgesture +GtkShortcutsGesture + +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 + +gtk_shortcuts_gesture_get_type +
diff --git a/docs/reference/gtk/gtk3.types.in b/docs/reference/gtk/gtk3.types.in index 4b206223e8..93e42dcc12 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 0000000000..639a1d3c37 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 0000000000..9ab2d5a71f 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 0000000000..5d566ff8ba 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 83b3cd2ffd..7d2ebdf2df 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 3cbd13fcd5..ae556d4847 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -183,6 +183,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include diff --git a/gtk/gtkshortcutlabel.c b/gtk/gtkshortcutlabel.c new file mode 100644 index 0000000000..7140142133 --- /dev/null +++ b/gtk/gtkshortcutlabel.c @@ -0,0 +1,314 @@ +/* gtkshortcutlabel.c + * + * Copyright (C) 2015 Christian Hergert + * + * 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 . + */ + +#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 0000000000..4f5096c449 --- /dev/null +++ b/gtk/gtkshortcutlabelprivate.h @@ -0,0 +1,47 @@ +/* gtkshortcutlabelprivate.h + * + * Copyright (C) 2015 Christian Hergert + * + * 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 . + */ + +#ifndef __GTK_SHORTCUT_LABEL_H__ +#define __GTK_SHORTCUT_LABEL_H__ + +#include + +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 0000000000..0889da66c8 --- /dev/null +++ b/gtk/gtkshortcutsgesture.c @@ -0,0 +1,311 @@ + /* gtkshortcutsgesture.c + * + * Copyright (C) 2015 Christian Hergert + * + * 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 . + */ + +#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 0000000000..0b60b39840 --- /dev/null +++ b/gtk/gtkshortcutsgesture.h @@ -0,0 +1,42 @@ +/* gtkshortcutsgestureprivate.h + * + * Copyright (C) 2015 Christian Hergert + * + * 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 . + */ + +#ifndef __GTK_SHORTCUTS_GESTURE_H__ +#define __GTK_SHORTCUTS_GESTURE_H__ + +#include + +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 0000000000..1ac3fc5276 --- /dev/null +++ b/gtk/gtkshortcutsgroup.c @@ -0,0 +1,306 @@ +/* gtkshortcutsgroup.c + * + * Copyright (C) 2015 Christian Hergert + * + * 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 . + */ + +#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 0000000000..790d60a855 --- /dev/null +++ b/gtk/gtkshortcutsgroup.h @@ -0,0 +1,42 @@ +/* gtkshortcutsgroupprivate.h + * + * Copyright (C) 2015 Christian Hergert + * + * 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 . + */ + +#ifndef __GTK_SHORTCUTS_GROUP_H__ +#define __GTK_SHORTCUTS_GROUP_H__ + +#include + +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 0000000000..9941e131cb --- /dev/null +++ b/gtk/gtkshortcutssection.c @@ -0,0 +1,716 @@ +/* gtkshortcutssection.c + * + * Copyright (C) 2015 Christian Hergert + * + * 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 . + */ + +#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 0000000000..9d8b5cc432 --- /dev/null +++ b/gtk/gtkshortcutssection.h @@ -0,0 +1,43 @@ +/* gtkshortcutssection.h + * + * Copyright (C) 2015 Christian Hergert + * + * 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 . + */ + +#ifndef __GTK_SHORTCUTS_SECTION_H__ +#define __GTK_SHORTCUTS_SECTION_H__ + +#include +#include + +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 0000000000..c14eb0a455 --- /dev/null +++ b/gtk/gtkshortcutsshortcut.c @@ -0,0 +1,264 @@ +/* gtkshortcutsshortcut.c + * + * Copyright (C) 2015 Christian Hergert + * + * 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 . + */ + +#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: ? 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 0000000000..57d76ad60e --- /dev/null +++ b/gtk/gtkshortcutsshortcut.h @@ -0,0 +1,43 @@ +/* gtkshortcutsshortcutprivate.h + * + * Copyright (C) 2015 Christian Hergert + * + * 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 . + */ + +#ifndef GTK_SHORTCUTS_SHORTCUT_H +#define GTK_SHORTCUTS_SHORTCUT_H + +#include + +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 0000000000..5e274ec1b6 --- /dev/null +++ b/gtk/gtkshortcutswindow.c @@ -0,0 +1,882 @@ +/* gtkshortcutswindow.c + * + * Copyright (C) 2015 Christian Hergert + * + * 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 . + */ + +#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 0000000000..ca2ded8317 --- /dev/null +++ b/gtk/gtkshortcutswindow.h @@ -0,0 +1,57 @@ +/* gtkshortcutswindow.h + * + * Copyright (C) 2015 Christian Hergert + * + * 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 . + */ + +#ifndef __GTK_SHORTCUTS_WINDOW_H__ +#define __GTK_SHORTCUTS_WINDOW_H__ + +#include + +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 */