forked from AuroraMiddleware/gtk
ae7cefd97d
We document the supported style classes by name, not by macro name, and these macros don't really add any value. Drop them for GTK 4.
1008 lines
33 KiB
C
1008 lines
33 KiB
C
/* gtkshortcutswindow.c
|
|
*
|
|
* Copyright (C) 2015 Christian Hergert <christian@hergert.me>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gtkshortcutswindowprivate.h"
|
|
|
|
#include "gtkbox.h"
|
|
#include "gtkbuildable.h"
|
|
#include "gtkgrid.h"
|
|
#include "gtkheaderbar.h"
|
|
#include "gtkintl.h"
|
|
#include "gtklabel.h"
|
|
#include "gtklistbox.h"
|
|
#include "gtkmain.h"
|
|
#include "gtkmenubutton.h"
|
|
#include "gtkpopover.h"
|
|
#include "gtkprivate.h"
|
|
#include "gtkscrolledwindow.h"
|
|
#include "gtksearchbar.h"
|
|
#include "gtksearchentry.h"
|
|
#include "gtkshortcutssection.h"
|
|
#include "gtkshortcutsgroup.h"
|
|
#include "gtkshortcutsshortcutprivate.h"
|
|
#include "gtksizegroup.h"
|
|
#include "gtkstack.h"
|
|
#include "gtkstylecontext.h"
|
|
#include "gtktogglebutton.h"
|
|
#include "gtktypebuiltins.h"
|
|
#include "gtkwidgetprivate.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.
|
|
*
|
|
* # 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://gitlab.gnome.org/GNOME/gtk/tree/master/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://gitlab.gnome.org/GNOME/gtk/tree/master/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://gitlab.gnome.org/GNOME/gtk/tree/master/demos/gtk-demo/shortcuts-builder.ui).
|
|
*/
|
|
|
|
struct _GtkShortcutsWindowClass
|
|
{
|
|
GtkWindowClass parent_class;
|
|
|
|
void (*close) (GtkShortcutsWindow *self);
|
|
void (*search) (GtkShortcutsWindow *self);
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
GHashTable *keywords;
|
|
char *initial_section;
|
|
char *last_section_name;
|
|
char *view_name;
|
|
GtkSizeGroup *search_text_group;
|
|
GtkSizeGroup *search_image_group;
|
|
GHashTable *search_items_hash;
|
|
|
|
GtkStack *stack;
|
|
GtkStack *title_stack;
|
|
GtkMenuButton *menu_button;
|
|
GtkSearchBar *search_bar;
|
|
GtkSearchEntry *search_entry;
|
|
GtkHeaderBar *header_bar;
|
|
GtkWidget *main_box;
|
|
GtkPopover *popover;
|
|
GtkListBox *list_box;
|
|
GtkBox *search_gestures;
|
|
GtkBox *search_shortcuts;
|
|
|
|
GtkWindow *window;
|
|
gulong keys_changed_id;
|
|
} GtkShortcutsWindowPrivate;
|
|
|
|
typedef struct
|
|
{
|
|
GtkShortcutsWindow *self;
|
|
GtkBuilder *builder;
|
|
GQueue *stack;
|
|
char *property_name;
|
|
guint translatable : 1;
|
|
} ViewsParserData;
|
|
|
|
static void gtk_shortcuts_window_buildable_iface_init (GtkBuildableIface *iface);
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GtkShortcutsWindow, gtk_shortcuts_window, GTK_TYPE_WINDOW,
|
|
G_ADD_PRIVATE (GtkShortcutsWindow)
|
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
|
|
gtk_shortcuts_window_buildable_iface_init))
|
|
|
|
|
|
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 gboolean
|
|
more_than_three_children (GtkWidget *widget)
|
|
{
|
|
GtkWidget *child;
|
|
int i;
|
|
|
|
for (child = gtk_widget_get_first_child (widget), i = 0;
|
|
child != NULL;
|
|
child = gtk_widget_get_next_sibling (child), i++)
|
|
{
|
|
if (i == 3)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
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 (more_than_three_children (GTK_WIDGET (priv->stack)))
|
|
{
|
|
char *title;
|
|
|
|
gtk_stack_set_visible_child_name (priv->title_stack, "sections");
|
|
g_object_get (visible_child, "title", &title, NULL);
|
|
gtk_menu_button_set_label (priv->menu_button, 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;
|
|
char *accelerator = NULL;
|
|
char *title = NULL;
|
|
char *hash_key = NULL;
|
|
GIcon *icon = NULL;
|
|
gboolean icon_set = FALSE;
|
|
gboolean subtitle_set = FALSE;
|
|
GtkTextDirection direction;
|
|
GtkShortcutType shortcut_type;
|
|
char *action_name = NULL;
|
|
char *subtitle;
|
|
char *str;
|
|
char *keywords;
|
|
|
|
if (GTK_IS_SHORTCUTS_SHORTCUT (child))
|
|
{
|
|
GEnumClass *class;
|
|
GEnumValue *value;
|
|
|
|
g_object_get (child,
|
|
"accelerator", &accelerator,
|
|
"title", &title,
|
|
"direction", &direction,
|
|
"icon-set", &icon_set,
|
|
"subtitle-set", &subtitle_set,
|
|
"shortcut-type", &shortcut_type,
|
|
"action-name", &action_name,
|
|
NULL);
|
|
|
|
class = G_ENUM_CLASS (g_type_class_ref (GTK_TYPE_SHORTCUT_TYPE));
|
|
value = g_enum_get_value (class, shortcut_type);
|
|
|
|
hash_key = g_strdup_printf ("%s-%s-%s", title, value->value_nick, accelerator);
|
|
|
|
g_type_class_unref (class);
|
|
|
|
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,
|
|
"accelerator", accelerator,
|
|
"title", title,
|
|
"direction", direction,
|
|
"shortcut-type", shortcut_type,
|
|
"accel-size-group", priv->search_image_group,
|
|
"title-size-group", priv->search_text_group,
|
|
"action-name", action_name,
|
|
NULL);
|
|
if (icon_set)
|
|
{
|
|
g_object_get (child, "icon", &icon, NULL);
|
|
g_object_set (item, "icon", icon, NULL);
|
|
g_clear_object (&icon);
|
|
}
|
|
if (subtitle_set)
|
|
{
|
|
g_object_get (child, "subtitle", &subtitle, NULL);
|
|
g_object_set (item, "subtitle", subtitle, NULL);
|
|
g_free (subtitle);
|
|
}
|
|
str = g_strdup_printf ("%s %s", accelerator, title);
|
|
keywords = g_utf8_strdown (str, -1);
|
|
|
|
g_hash_table_insert (priv->keywords, item, keywords);
|
|
if (shortcut_type == GTK_SHORTCUT_ACCELERATOR)
|
|
gtk_box_append (GTK_BOX (priv->search_shortcuts), item);
|
|
else
|
|
gtk_box_append (GTK_BOX (priv->search_gestures), item);
|
|
|
|
g_free (title);
|
|
g_free (accelerator);
|
|
g_free (str);
|
|
g_free (action_name);
|
|
}
|
|
else
|
|
{
|
|
GtkWidget *widget;
|
|
|
|
for (widget = gtk_widget_get_first_child (child);
|
|
widget != NULL;
|
|
widget = gtk_widget_get_next_sibling (widget))
|
|
gtk_shortcuts_window_add_search_item (widget, self);
|
|
}
|
|
}
|
|
|
|
static void
|
|
section_notify_cb (GObject *section,
|
|
GParamSpec *pspec,
|
|
gpointer data)
|
|
{
|
|
GtkShortcutsWindow *self = data;
|
|
GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
|
|
|
|
if (strcmp (pspec->name, "section-name") == 0)
|
|
{
|
|
char *name;
|
|
|
|
g_object_get (section, "section-name", &name, NULL);
|
|
g_object_set (gtk_stack_get_page (priv->stack, GTK_WIDGET (section)), "name", name, NULL);
|
|
g_free (name);
|
|
}
|
|
else if (strcmp (pspec->name, "title") == 0)
|
|
{
|
|
char *title;
|
|
GtkWidget *label;
|
|
|
|
label = g_object_get_data (section, "gtk-shortcuts-title");
|
|
g_object_get (section, "title", &title, NULL);
|
|
gtk_label_set_label (GTK_LABEL (label), title);
|
|
g_free (title);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_window_add_section (GtkShortcutsWindow *self,
|
|
GtkShortcutsSection *section)
|
|
{
|
|
GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
|
|
GtkListBoxRow *row;
|
|
char *title;
|
|
char *name;
|
|
const char *visible_section;
|
|
GtkWidget *label;
|
|
GtkWidget *child;
|
|
|
|
for (child = gtk_widget_get_first_child (GTK_WIDGET (section));
|
|
child != NULL;
|
|
child = gtk_widget_get_next_sibling (child))
|
|
gtk_shortcuts_window_add_search_item (child, self);
|
|
|
|
g_object_get (section,
|
|
"section-name", &name,
|
|
"title", &title,
|
|
NULL);
|
|
|
|
g_signal_connect (section, "notify", G_CALLBACK (section_notify_cb), self);
|
|
|
|
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,
|
|
NULL);
|
|
g_object_set_data (G_OBJECT (row), "gtk-shortcuts-section", section);
|
|
label = g_object_new (GTK_TYPE_LABEL,
|
|
"margin-start", 6,
|
|
"margin-end", 6,
|
|
"margin-top", 6,
|
|
"margin-bottom", 6,
|
|
"label", title,
|
|
"xalign", 0.5f,
|
|
NULL);
|
|
g_object_set_data (G_OBJECT (section), "gtk-shortcuts-title", label);
|
|
gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), GTK_WIDGET (label));
|
|
gtk_list_box_insert (GTK_LIST_BOX (priv->list_box), GTK_WIDGET (row), -1);
|
|
|
|
update_title_stack (self);
|
|
|
|
g_free (name);
|
|
g_free (title);
|
|
}
|
|
|
|
static GtkBuildableIface *parent_buildable_iface;
|
|
|
|
static void
|
|
gtk_shortcuts_window_buildable_add_child (GtkBuildable *buildable,
|
|
GtkBuilder *builder,
|
|
GObject *child,
|
|
const char *type)
|
|
{
|
|
if (GTK_IS_SHORTCUTS_SECTION (child))
|
|
gtk_shortcuts_window_add_section (GTK_SHORTCUTS_WINDOW (buildable),
|
|
GTK_SHORTCUTS_SECTION (child));
|
|
else
|
|
parent_buildable_iface->add_child (buildable, builder, child, type);
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_window_buildable_iface_init (GtkBuildableIface *iface)
|
|
{
|
|
parent_buildable_iface = g_type_interface_peek_parent (iface);
|
|
|
|
iface->add_child = gtk_shortcuts_window_buildable_add_child;
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_window_set_view_name (GtkShortcutsWindow *self,
|
|
const char *view_name)
|
|
{
|
|
GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
|
|
GtkWidget *section;
|
|
|
|
g_free (priv->view_name);
|
|
priv->view_name = g_strdup (view_name);
|
|
|
|
for (section = gtk_widget_get_first_child (GTK_WIDGET (priv->stack));
|
|
section != NULL;
|
|
section = gtk_widget_get_next_sibling (section))
|
|
{
|
|
if (GTK_IS_SHORTCUTS_SECTION (section))
|
|
g_object_set (section, "view-name", priv->view_name, NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_window_set_section_name (GtkShortcutsWindow *self,
|
|
const char *section_name)
|
|
{
|
|
GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
|
|
GtkWidget *section = NULL;
|
|
|
|
g_free (priv->initial_section);
|
|
priv->initial_section = g_strdup (section_name);
|
|
|
|
if (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
|
|
update_accels_cb (GtkWidget *widget,
|
|
gpointer data)
|
|
{
|
|
GtkShortcutsWindow *self = data;
|
|
GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
|
|
|
|
if (GTK_IS_SHORTCUTS_SHORTCUT (widget))
|
|
gtk_shortcuts_shortcut_update_accel (GTK_SHORTCUTS_SHORTCUT (widget), priv->window);
|
|
else
|
|
{
|
|
GtkWidget *child;
|
|
|
|
for (child = gtk_widget_get_first_child (GTK_WIDGET (widget));
|
|
child != NULL;
|
|
child = gtk_widget_get_next_sibling (child ))
|
|
update_accels_cb (child, self);
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_accels_for_actions (GtkShortcutsWindow *self)
|
|
{
|
|
GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
|
|
|
|
if (priv->window)
|
|
{
|
|
GtkWidget *child;
|
|
|
|
for (child = gtk_widget_get_first_child (GTK_WIDGET (self));
|
|
child != NULL;
|
|
child = gtk_widget_get_next_sibling (child))
|
|
update_accels_cb (child, self);
|
|
}
|
|
}
|
|
|
|
static void
|
|
keys_changed_handler (GtkWindow *window,
|
|
GtkShortcutsWindow *self)
|
|
{
|
|
update_accels_for_actions (self);
|
|
}
|
|
|
|
void
|
|
gtk_shortcuts_window_set_window (GtkShortcutsWindow *self,
|
|
GtkWindow *window)
|
|
{
|
|
GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
|
|
|
|
if (priv->keys_changed_id)
|
|
{
|
|
g_signal_handler_disconnect (priv->window, priv->keys_changed_id);
|
|
priv->keys_changed_id = 0;
|
|
}
|
|
|
|
priv->window = window;
|
|
|
|
if (priv->window)
|
|
priv->keys_changed_id = g_signal_connect (window, "keys-changed",
|
|
G_CALLBACK (keys_changed_handler),
|
|
self);
|
|
|
|
update_accels_for_actions (self);
|
|
}
|
|
|
|
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);
|
|
GtkWidget *section;
|
|
|
|
section = g_object_get_data (G_OBJECT (row), "gtk-shortcuts-section");
|
|
gtk_stack_set_visible_child (priv->stack, section);
|
|
gtk_popover_popdown (priv->popover);
|
|
}
|
|
|
|
static gboolean
|
|
hidden_by_direction (GtkWidget *widget)
|
|
{
|
|
if (GTK_IS_SHORTCUTS_SHORTCUT (widget))
|
|
{
|
|
GtkTextDirection dir;
|
|
|
|
g_object_get (widget, "direction", &dir, NULL);
|
|
if (dir != GTK_TEXT_DIR_NONE &&
|
|
dir != gtk_widget_get_direction (widget))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_window__entry__changed (GtkShortcutsWindow *self,
|
|
GtkSearchEntry *search_entry)
|
|
{
|
|
GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
|
|
char *downcase = NULL;
|
|
GHashTableIter iter;
|
|
const char *text;
|
|
const char *last_section_name;
|
|
gpointer key;
|
|
gpointer value;
|
|
gboolean has_result;
|
|
|
|
text = gtk_editable_get_text (GTK_EDITABLE (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 char *keywords = value;
|
|
gboolean match;
|
|
|
|
if (hidden_by_direction (widget))
|
|
match = FALSE;
|
|
else
|
|
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_dispose (GObject *object)
|
|
{
|
|
GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
|
|
GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
|
|
|
|
if (priv->stack)
|
|
g_signal_handlers_disconnect_by_func (priv->stack, G_CALLBACK (update_title_stack), self);
|
|
|
|
gtk_shortcuts_window_set_window (self, NULL);
|
|
|
|
priv->stack = NULL;
|
|
priv->search_bar = NULL;
|
|
priv->main_box = NULL;
|
|
|
|
G_OBJECT_CLASS (gtk_shortcuts_window_parent_class)->dispose (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)
|
|
{
|
|
char *name = NULL;
|
|
|
|
g_object_get (gtk_stack_get_page (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_editable_set_text (GTK_EDITABLE (priv->search_entry), "");
|
|
|
|
GTK_WIDGET_CLASS (gtk_shortcuts_window_parent_class)->unmap (widget);
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_window_class_init (GtkShortcutsWindowClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_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;
|
|
object_class->dispose = gtk_shortcuts_window_dispose;
|
|
|
|
widget_class->unmap = gtk_shortcuts_window_unmap;
|
|
|
|
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"),
|
|
"internal-search",
|
|
(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_widget_class_add_binding_signal (widget_class,
|
|
GDK_KEY_Escape, 0,
|
|
"close",
|
|
NULL);
|
|
gtk_widget_class_add_binding_signal (widget_class,
|
|
GDK_KEY_f, GDK_CONTROL_MASK,
|
|
"search",
|
|
NULL);
|
|
|
|
g_type_ensure (GTK_TYPE_SHORTCUTS_GROUP);
|
|
g_type_ensure (GTK_TYPE_SHORTCUTS_SHORTCUT);
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_window_init (GtkShortcutsWindow *self)
|
|
{
|
|
GtkShortcutsWindowPrivate *priv = gtk_shortcuts_window_get_instance_private (self);
|
|
GtkWidget *search_button;
|
|
GtkBox *box;
|
|
GtkWidget *scroller;
|
|
GtkWidget *label;
|
|
GtkWidget *empty;
|
|
PangoAttrList *attributes;
|
|
|
|
gtk_window_set_resizable (GTK_WINDOW (self), FALSE);
|
|
|
|
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 = GTK_HEADER_BAR (gtk_header_bar_new ());
|
|
gtk_window_set_titlebar (GTK_WINDOW (self), GTK_WIDGET (priv->header_bar));
|
|
|
|
search_button = g_object_new (GTK_TYPE_TOGGLE_BUTTON,
|
|
"icon-name", "edit-find-symbolic",
|
|
NULL);
|
|
gtk_header_bar_pack_start (GTK_HEADER_BAR (priv->header_bar), search_button);
|
|
|
|
priv->main_box = g_object_new (GTK_TYPE_BOX,
|
|
"orientation", GTK_ORIENTATION_VERTICAL,
|
|
NULL);
|
|
gtk_window_set_child (GTK_WINDOW (self), priv->main_box);
|
|
|
|
priv->search_bar = g_object_new (GTK_TYPE_SEARCH_BAR, NULL);
|
|
g_object_bind_property (priv->search_bar, "search-mode-enabled",
|
|
search_button, "active",
|
|
G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
|
|
gtk_box_append (GTK_BOX (priv->main_box), GTK_WIDGET (priv->search_bar));
|
|
gtk_search_bar_set_key_capture_widget (GTK_SEARCH_BAR (priv->search_bar),
|
|
GTK_WIDGET (self));
|
|
|
|
priv->stack = g_object_new (GTK_TYPE_STACK,
|
|
"hexpand", TRUE,
|
|
"vexpand", TRUE,
|
|
"hhomogeneous", TRUE,
|
|
"vhomogeneous", TRUE,
|
|
"transition-type", GTK_STACK_TRANSITION_TYPE_CROSSFADE,
|
|
NULL);
|
|
gtk_box_append (GTK_BOX (priv->main_box), GTK_WIDGET (priv->stack));
|
|
|
|
priv->title_stack = g_object_new (GTK_TYPE_STACK,
|
|
NULL);
|
|
gtk_header_bar_set_title_widget (priv->header_bar, GTK_WIDGET (priv->title_stack));
|
|
|
|
/* Translators: This is the window title for the shortcuts window in normal mode */
|
|
label = gtk_label_new (_("Shortcuts"));
|
|
gtk_widget_add_css_class (label, "title");
|
|
gtk_stack_add_named (priv->title_stack, label, "title");
|
|
|
|
/* Translators: This is the window title for the shortcuts window in search mode */
|
|
label = gtk_label_new (_("Search Results"));
|
|
gtk_widget_add_css_class (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,
|
|
NULL);
|
|
gtk_widget_add_css_class (GTK_WIDGET (priv->menu_button), "flat");
|
|
gtk_stack_add_named (priv->title_stack, GTK_WIDGET (priv->menu_button), "sections");
|
|
|
|
priv->popover = g_object_new (GTK_TYPE_POPOVER,
|
|
"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,
|
|
NULL);
|
|
g_signal_connect_object (priv->list_box,
|
|
"row-activated",
|
|
G_CALLBACK (gtk_shortcuts_window__list_box__row_activated),
|
|
self,
|
|
G_CONNECT_SWAPPED);
|
|
gtk_popover_set_child (GTK_POPOVER (priv->popover), GTK_WIDGET (priv->list_box));
|
|
|
|
priv->search_entry = GTK_SEARCH_ENTRY (gtk_search_entry_new ());
|
|
gtk_search_bar_set_child (GTK_SEARCH_BAR (priv->search_bar), GTK_WIDGET (priv->search_entry));
|
|
|
|
g_object_set (priv->search_entry,
|
|
/* Translators: This is placeholder text for the search entry in the shortcuts window */
|
|
"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);
|
|
|
|
scroller = gtk_scrolled_window_new ();
|
|
box = g_object_new (GTK_TYPE_BOX,
|
|
"halign", GTK_ALIGN_CENTER,
|
|
"orientation", GTK_ORIENTATION_VERTICAL,
|
|
NULL);
|
|
gtk_widget_add_css_class (GTK_WIDGET (box), "shortcuts-search-results");
|
|
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (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,
|
|
NULL);
|
|
gtk_box_append (GTK_BOX (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,
|
|
NULL);
|
|
gtk_box_append (GTK_BOX (box), GTK_WIDGET (priv->search_gestures));
|
|
|
|
empty = g_object_new (GTK_TYPE_GRID,
|
|
"row-spacing", 12,
|
|
"margin-start", 12,
|
|
"margin-end", 12,
|
|
"margin-top", 12,
|
|
"margin-bottom", 12,
|
|
"hexpand", TRUE,
|
|
"vexpand", TRUE,
|
|
"halign", GTK_ALIGN_CENTER,
|
|
"valign", GTK_ALIGN_CENTER,
|
|
NULL);
|
|
gtk_widget_add_css_class (empty, "dim-label");
|
|
gtk_grid_attach (GTK_GRID (empty),
|
|
g_object_new (GTK_TYPE_IMAGE,
|
|
"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,
|
|
"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,
|
|
"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");
|
|
|
|
g_signal_connect_object (priv->stack, "notify::visible-child",
|
|
G_CALLBACK (update_title_stack), self, G_CONNECT_SWAPPED);
|
|
|
|
}
|