gtk2/gtk/gtkshortcutswindow.c
Sophie Herold a546ae32d7 Remove all nicks and blurbs from param specs
Those property features don't seem to be in use anywhere.
They are redundant since the docs cover the same information
and more. They also created unnecessary translation work.

Closes #4904
2022-05-11 18:16:29 +02:00

984 lines
31 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 "gtktogglebutton.h"
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
/**
* GtkShortcutsWindow:
*
* A `GtkShortcutsWindow` shows 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
* [class@Gtk.Builder], 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/main/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/main/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/main/demos/gtk-demo/shortcuts-builder.ui).
*/
struct _GtkShortcutsWindow
{
GtkWindow parent_instance;
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;
};
typedef struct
{
GtkWindowClass parent_class;
void (*close) (GtkShortcutsWindow *self);
void (*search) (GtkShortcutsWindow *self);
} GtkShortcutsWindowClass;
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_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)
{
GtkWidget *visible_child;
visible_child = gtk_stack_get_visible_child (self->stack);
if (GTK_IS_SHORTCUTS_SECTION (visible_child))
{
if (more_than_three_children (GTK_WIDGET (self->stack)))
{
char *title;
gtk_stack_set_visible_child_name (self->title_stack, "sections");
g_object_get (visible_child, "title", &title, NULL);
gtk_menu_button_set_label (self->menu_button, title);
g_free (title);
}
else
{
gtk_stack_set_visible_child_name (self->title_stack, "title");
}
}
else if (visible_child != NULL)
{
gtk_stack_set_visible_child_name (self->title_stack, "search");
}
}
static void
gtk_shortcuts_window_add_search_item (GtkWidget *child, gpointer data)
{
GtkShortcutsWindow *self = data;
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 (self->search_items_hash, hash_key))
{
g_free (hash_key);
g_free (title);
g_free (accelerator);
return;
}
g_hash_table_insert (self->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", self->search_image_group,
"title-size-group", self->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 (self->keywords, item, keywords);
if (shortcut_type == GTK_SHORTCUT_ACCELERATOR)
gtk_box_append (GTK_BOX (self->search_shortcuts), item);
else
gtk_box_append (GTK_BOX (self->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;
if (strcmp (pspec->name, "section-name") == 0)
{
char *name;
g_object_get (section, "section-name", &name, NULL);
g_object_set (gtk_stack_get_page (self->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)
{
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 (self->stack, GTK_WIDGET (section), name, title);
visible_section = gtk_stack_get_visible_child_name (self->stack);
if (strcmp (visible_section, "internal-search") == 0 ||
(self->initial_section && strcmp (self->initial_section, visible_section) == 0))
gtk_stack_set_visible_child (self->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 (self->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)
{
GtkWidget *section;
g_free (self->view_name);
self->view_name = g_strdup (view_name);
for (section = gtk_widget_get_first_child (GTK_WIDGET (self->stack));
section != NULL;
section = gtk_widget_get_next_sibling (section))
{
if (GTK_IS_SHORTCUTS_SECTION (section))
g_object_set (section, "view-name", self->view_name, NULL);
}
}
static void
gtk_shortcuts_window_set_section_name (GtkShortcutsWindow *self,
const char *section_name)
{
GtkWidget *section = NULL;
g_free (self->initial_section);
self->initial_section = g_strdup (section_name);
if (section_name)
section = gtk_stack_get_child_by_name (self->stack, section_name);
if (section)
gtk_stack_set_visible_child (self->stack, section);
}
static void
update_accels_cb (GtkWidget *widget,
gpointer data)
{
GtkShortcutsWindow *self = data;
if (GTK_IS_SHORTCUTS_SHORTCUT (widget))
gtk_shortcuts_shortcut_update_accel (GTK_SHORTCUTS_SHORTCUT (widget), self->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)
{
if (self->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)
{
if (self->keys_changed_id)
{
g_signal_handler_disconnect (self->window, self->keys_changed_id);
self->keys_changed_id = 0;
}
self->window = window;
if (self->window)
self->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)
{
GtkWidget *section;
section = g_object_get_data (G_OBJECT (row), "gtk-shortcuts-section");
gtk_stack_set_visible_child (self->stack, section);
gtk_popover_popdown (self->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)
{
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 (self->last_section_name != NULL)
{
gtk_stack_set_visible_child_name (self->stack, self->last_section_name);
return;
}
}
last_section_name = gtk_stack_get_visible_child_name (self->stack);
if (g_strcmp0 (last_section_name, "internal-search") != 0 &&
g_strcmp0 (last_section_name, "no-search-results") != 0)
{
g_free (self->last_section_name);
self->last_section_name = g_strdup (last_section_name);
}
downcase = g_utf8_strdown (text, -1);
g_hash_table_iter_init (&iter, self->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 (self->stack, "internal-search");
else
gtk_stack_set_visible_child_name (self->stack, "no-search-results");
}
static void
gtk_shortcuts_window__search_mode__changed (GtkShortcutsWindow *self)
{
if (!gtk_search_bar_get_search_mode (self->search_bar))
{
if (self->last_section_name != NULL)
gtk_stack_set_visible_child_name (self->stack, self->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)
{
gtk_search_bar_set_search_mode (self->search_bar, TRUE);
}
static void
gtk_shortcuts_window_constructed (GObject *object)
{
GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
G_OBJECT_CLASS (gtk_shortcuts_window_parent_class)->constructed (object);
if (self->initial_section != NULL)
gtk_stack_set_visible_child_name (self->stack, self->initial_section);
}
static void
gtk_shortcuts_window_finalize (GObject *object)
{
GtkShortcutsWindow *self = (GtkShortcutsWindow *)object;
g_clear_pointer (&self->keywords, g_hash_table_unref);
g_clear_pointer (&self->initial_section, g_free);
g_clear_pointer (&self->view_name, g_free);
g_clear_pointer (&self->last_section_name, g_free);
g_clear_pointer (&self->search_items_hash, g_hash_table_unref);
g_clear_object (&self->search_image_group);
g_clear_object (&self->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;
if (self->stack)
g_signal_handlers_disconnect_by_func (self->stack, G_CALLBACK (update_title_stack), self);
gtk_shortcuts_window_set_window (self, NULL);
self->stack = NULL;
self->search_bar = NULL;
self->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;
switch (prop_id)
{
case PROP_SECTION_NAME:
{
GtkWidget *child = gtk_stack_get_visible_child (self->stack);
if (child != NULL)
{
char *name = NULL;
g_object_get (gtk_stack_get_page (self->stack, child),
"name", &name,
NULL);
g_value_take_string (value, name);
}
}
break;
case PROP_VIEW_NAME:
g_value_set_string (value, self->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;
gtk_search_bar_set_search_mode (self->search_bar, FALSE);
gtk_editable_set_text (GTK_EDITABLE (self->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", NULL, NULL,
"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 [property@Gtk.ShortcutsGroup:view]
* property of some of the [class@Gtk.ShortcutsGroup] 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", NULL, NULL,
NULL,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, LAST_PROP, properties);
/**
* GtkShortcutsWindow::close:
*
* Emitted when the user uses a keybinding to close the window.
*
* This is a [keybinding signal](class.SignalAction.html).
*
* 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:
*
* Emitted when the user uses a keybinding to start a search.
*
* This is a [keybinding signal](class.SignalAction.html).
*
* 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)
{
GtkWidget *search_button;
GtkBox *box;
GtkWidget *scroller;
GtkWidget *label;
GtkWidget *empty;
PangoAttrList *attributes;
gtk_window_set_resizable (GTK_WINDOW (self), FALSE);
self->keywords = g_hash_table_new_full (NULL, NULL, NULL, g_free);
self->search_items_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
self->search_text_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
self->search_image_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
self->header_bar = GTK_HEADER_BAR (gtk_header_bar_new ());
gtk_window_set_titlebar (GTK_WINDOW (self), GTK_WIDGET (self->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 (self->header_bar), search_button);
self->main_box = g_object_new (GTK_TYPE_BOX,
"orientation", GTK_ORIENTATION_VERTICAL,
NULL);
gtk_window_set_child (GTK_WINDOW (self), self->main_box);
self->search_bar = g_object_new (GTK_TYPE_SEARCH_BAR, NULL);
g_object_bind_property (self->search_bar, "search-mode-enabled",
search_button, "active",
G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
gtk_box_append (GTK_BOX (self->main_box), GTK_WIDGET (self->search_bar));
gtk_search_bar_set_key_capture_widget (GTK_SEARCH_BAR (self->search_bar),
GTK_WIDGET (self));
self->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 (self->main_box), GTK_WIDGET (self->stack));
self->title_stack = g_object_new (GTK_TYPE_STACK,
NULL);
gtk_header_bar_set_title_widget (self->header_bar, GTK_WIDGET (self->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 (self->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 (self->title_stack, label, "search");
self->menu_button = g_object_new (GTK_TYPE_MENU_BUTTON,
"focus-on-click", FALSE,
NULL);
gtk_widget_add_css_class (GTK_WIDGET (self->menu_button), "flat");
gtk_stack_add_named (self->title_stack, GTK_WIDGET (self->menu_button), "sections");
self->popover = g_object_new (GTK_TYPE_POPOVER,
"position", GTK_POS_BOTTOM,
NULL);
gtk_menu_button_set_popover (self->menu_button, GTK_WIDGET (self->popover));
self->list_box = g_object_new (GTK_TYPE_LIST_BOX,
"selection-mode", GTK_SELECTION_NONE,
NULL);
g_signal_connect_object (self->list_box,
"row-activated",
G_CALLBACK (gtk_shortcuts_window__list_box__row_activated),
self,
G_CONNECT_SWAPPED);
gtk_popover_set_child (GTK_POPOVER (self->popover), GTK_WIDGET (self->list_box));
self->search_entry = GTK_SEARCH_ENTRY (gtk_search_entry_new ());
gtk_search_bar_set_child (GTK_SEARCH_BAR (self->search_bar), GTK_WIDGET (self->search_entry));
g_object_set (self->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 (self->search_entry,
"search-changed",
G_CALLBACK (gtk_shortcuts_window__entry__changed),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (self->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 (self->stack, scroller, "internal-search");
self->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 (self->search_shortcuts));
self->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 (self->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 (self->stack, empty, "no-search-results");
g_signal_connect_object (self->stack, "notify::visible-child",
G_CALLBACK (update_title_stack), self, G_CONNECT_SWAPPED);
}