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.
771 lines
24 KiB
C
771 lines
24 KiB
C
/* gtkshortcutssection.c
|
|
*
|
|
* Copyright (C) 2015 Christian Hergert <christian@hergert.me>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gtkshortcutssection.h"
|
|
|
|
#include "gtkbox.h"
|
|
#include "gtkbuildable.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 "gtkprivate.h"
|
|
#include "gtkmarshalers.h"
|
|
#include "gtkgesturepan.h"
|
|
#include "gtkwidgetprivate.h"
|
|
#include "gtkcenterbox.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;
|
|
|
|
char *name;
|
|
char *title;
|
|
char *view_name;
|
|
guint max_height;
|
|
|
|
GtkStack *stack;
|
|
GtkStackSwitcher *switcher;
|
|
GtkWidget *show_all;
|
|
GtkWidget *footer;
|
|
GList *groups;
|
|
|
|
gboolean has_filtered_group;
|
|
};
|
|
|
|
struct _GtkShortcutsSectionClass
|
|
{
|
|
GtkBoxClass parent_class;
|
|
|
|
gboolean (* change_current_page) (GtkShortcutsSection *self,
|
|
int offset);
|
|
|
|
};
|
|
|
|
static void gtk_shortcuts_section_buildable_iface_init (GtkBuildableIface *iface);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GtkShortcutsSection, gtk_shortcuts_section, GTK_TYPE_BOX,
|
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
|
|
gtk_shortcuts_section_buildable_iface_init))
|
|
|
|
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 char *view_name);
|
|
static void gtk_shortcuts_section_set_max_height (GtkShortcutsSection *self,
|
|
guint max_height);
|
|
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 gboolean gtk_shortcuts_section_change_current_page (GtkShortcutsSection *self,
|
|
int offset);
|
|
|
|
static void gtk_shortcuts_section_pan_gesture_pan (GtkGesturePan *gesture,
|
|
GtkPanDirection direction,
|
|
double offset,
|
|
GtkShortcutsSection *self);
|
|
|
|
static GtkBuildableIface *parent_buildable_iface;
|
|
|
|
static void
|
|
gtk_shortcuts_section_buildable_add_child (GtkBuildable *buildable,
|
|
GtkBuilder *builder,
|
|
GObject *child,
|
|
const char *type)
|
|
{
|
|
if (GTK_IS_SHORTCUTS_GROUP (child))
|
|
gtk_shortcuts_section_add_group (GTK_SHORTCUTS_SECTION (buildable), GTK_SHORTCUTS_GROUP (child));
|
|
else
|
|
parent_buildable_iface->add_child (buildable, builder, child, type);
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_section_buildable_iface_init (GtkBuildableIface *iface)
|
|
{
|
|
parent_buildable_iface = g_type_interface_peek_parent (iface);
|
|
|
|
iface->add_child = gtk_shortcuts_section_buildable_add_child;
|
|
}
|
|
|
|
static void
|
|
map_child (GtkWidget *child)
|
|
{
|
|
if (_gtk_widget_get_visible (child) &&
|
|
_gtk_widget_get_child_visible (child) &&
|
|
!_gtk_widget_get_mapped (child))
|
|
gtk_widget_map (child);
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_section_map (GtkWidget *widget)
|
|
{
|
|
GtkShortcutsSection *self = GTK_SHORTCUTS_SECTION (widget);
|
|
|
|
GTK_WIDGET_CLASS (gtk_shortcuts_section_parent_class)->map (widget);
|
|
|
|
map_child (GTK_WIDGET (self->stack));
|
|
map_child (GTK_WIDGET (self->footer));
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_section_unmap (GtkWidget *widget)
|
|
{
|
|
GtkShortcutsSection *self = GTK_SHORTCUTS_SECTION (widget);
|
|
|
|
GTK_WIDGET_CLASS (gtk_shortcuts_section_parent_class)->unmap (widget);
|
|
|
|
gtk_widget_unmap (GTK_WIDGET (self->footer));
|
|
gtk_widget_unmap (GTK_WIDGET (self->stack));
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_section_dispose (GObject *object)
|
|
{
|
|
GtkShortcutsSection *self = GTK_SHORTCUTS_SECTION (object);
|
|
|
|
g_clear_pointer ((GtkWidget **)&self->stack, gtk_widget_unparent);
|
|
g_clear_pointer (&self->footer, gtk_widget_unparent);
|
|
|
|
g_list_free (self->groups);
|
|
self->groups = NULL;
|
|
|
|
G_OBJECT_CLASS (gtk_shortcuts_section_parent_class)->dispose (object);
|
|
}
|
|
|
|
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_pointer (&self->view_name, g_free);
|
|
|
|
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:
|
|
gtk_shortcuts_section_set_max_height (self, g_value_get_uint (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_section_class_init (GtkShortcutsSectionClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
|
|
object_class->finalize = gtk_shortcuts_section_finalize;
|
|
object_class->dispose = gtk_shortcuts_section_dispose;
|
|
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;
|
|
widget_class->unmap = gtk_shortcuts_section_unmap;
|
|
|
|
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 | G_PARAM_EXPLICIT_NOTIFY));
|
|
|
|
/**
|
|
* 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
|
|
* 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_PARAM_EXPLICIT_NOTIFY));
|
|
|
|
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);
|
|
|
|
gtk_widget_class_add_binding_signal (widget_class,
|
|
GDK_KEY_Page_Up, 0,
|
|
"change-current-page",
|
|
"(i)", -1);
|
|
gtk_widget_class_add_binding_signal (widget_class,
|
|
GDK_KEY_Page_Down, 0,
|
|
"change-current-page",
|
|
"(i)", 1);
|
|
gtk_widget_class_add_binding_signal (widget_class,
|
|
GDK_KEY_Page_Up, GDK_CONTROL_MASK,
|
|
"change-current-page",
|
|
"(i)", -1);
|
|
gtk_widget_class_add_binding_signal (widget_class,
|
|
GDK_KEY_Page_Down, GDK_CONTROL_MASK,
|
|
"change-current-page",
|
|
"(i)", 1);
|
|
|
|
gtk_widget_class_set_css_name (widget_class, I_("shortcuts-section"));
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_section_init (GtkShortcutsSection *self)
|
|
{
|
|
GtkGesture *gesture;
|
|
|
|
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);
|
|
|
|
self->stack = g_object_new (GTK_TYPE_STACK,
|
|
"hhomogeneous", TRUE,
|
|
"vhomogeneous", TRUE,
|
|
"transition-type", GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT,
|
|
"vexpand", TRUE,
|
|
"visible", TRUE,
|
|
NULL);
|
|
gtk_box_append (GTK_BOX (self), GTK_WIDGET (self->stack));
|
|
|
|
self->switcher = g_object_new (GTK_TYPE_STACK_SWITCHER,
|
|
"halign", GTK_ALIGN_CENTER,
|
|
"stack", self->stack,
|
|
"visible", FALSE,
|
|
NULL);
|
|
|
|
gtk_widget_remove_css_class (GTK_WIDGET (self->switcher), "linked");
|
|
|
|
self->show_all = gtk_button_new_with_mnemonic (_("_Show All"));
|
|
gtk_widget_hide (self->show_all);
|
|
g_signal_connect_swapped (self->show_all, "clicked",
|
|
G_CALLBACK (gtk_shortcuts_section_show_all), self);
|
|
|
|
self->footer = gtk_center_box_new ();
|
|
gtk_box_append (GTK_BOX (self), GTK_WIDGET (self->footer));
|
|
|
|
gtk_widget_set_hexpand (GTK_WIDGET (self->switcher), TRUE);
|
|
gtk_widget_set_halign (GTK_WIDGET (self->switcher), GTK_ALIGN_CENTER);
|
|
gtk_center_box_set_center_widget (GTK_CENTER_BOX (self->footer), GTK_WIDGET (self->switcher));
|
|
gtk_center_box_set_end_widget (GTK_CENTER_BOX (self->footer), self->show_all);
|
|
gtk_widget_set_halign (self->show_all, GTK_ALIGN_END);
|
|
|
|
gesture = gtk_gesture_pan_new (GTK_ORIENTATION_HORIZONTAL);
|
|
g_signal_connect (gesture, "pan",
|
|
G_CALLBACK (gtk_shortcuts_section_pan_gesture_pan), self);
|
|
gtk_widget_add_controller (GTK_WIDGET (self->stack), GTK_EVENT_CONTROLLER (gesture));
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_section_set_view_name (GtkShortcutsSection *self,
|
|
const char *view_name)
|
|
{
|
|
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_set_max_height (GtkShortcutsSection *self,
|
|
guint max_height)
|
|
{
|
|
if (self->max_height == max_height)
|
|
return;
|
|
|
|
self->max_height = max_height;
|
|
|
|
gtk_shortcuts_section_reflow_groups (self);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_HEIGHT]);
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_section_add_group (GtkShortcutsSection *self,
|
|
GtkShortcutsGroup *group)
|
|
{
|
|
GtkWidget *page, *column;
|
|
|
|
page = gtk_widget_get_last_child (GTK_WIDGET (self->stack));
|
|
if (page == NULL)
|
|
{
|
|
page = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 22);
|
|
gtk_stack_add_named (self->stack, page, "1");
|
|
}
|
|
|
|
column = gtk_widget_get_last_child (page);
|
|
if (column == NULL)
|
|
{
|
|
column = gtk_box_new (GTK_ORIENTATION_VERTICAL, 22);
|
|
gtk_box_append (GTK_BOX (page), column);
|
|
}
|
|
|
|
gtk_box_append (GTK_BOX (column), GTK_WIDGET (group));
|
|
self->groups = g_list_append (self->groups, group);
|
|
|
|
gtk_shortcuts_section_reflow_groups (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))
|
|
{
|
|
char *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
|
|
{
|
|
for (child = gtk_widget_get_first_child (GTK_WIDGET (child));
|
|
child != NULL;
|
|
child = gtk_widget_get_next_sibling (child))
|
|
update_group_visibility (child, self);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_section_filter_groups (GtkShortcutsSection *self)
|
|
{
|
|
GtkWidget *child;
|
|
|
|
self->has_filtered_group = FALSE;
|
|
|
|
for (child = gtk_widget_get_first_child (GTK_WIDGET (self));
|
|
child != NULL;
|
|
child = gtk_widget_get_next_sibling (child))
|
|
update_group_visibility (child, 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_reflow_groups (GtkShortcutsSection *self)
|
|
{
|
|
GList *pages, *p;
|
|
GtkWidget *page;
|
|
GList *groups, *g;
|
|
guint n_rows;
|
|
guint n_columns;
|
|
guint n_pages;
|
|
GtkWidget *current_page, *current_column;
|
|
|
|
/* collect all groups from the current pages */
|
|
groups = NULL;
|
|
for (page = gtk_widget_get_first_child (GTK_WIDGET (self->stack));
|
|
page != NULL;
|
|
page = gtk_widget_get_next_sibling (page))
|
|
{
|
|
GtkWidget *column;
|
|
|
|
for (column = gtk_widget_get_last_child (page);
|
|
column != NULL;
|
|
column = gtk_widget_get_prev_sibling (column))
|
|
{
|
|
GtkWidget *group;
|
|
|
|
for (group = gtk_widget_get_last_child (column);
|
|
group != NULL;
|
|
group = gtk_widget_get_prev_sibling (group))
|
|
{
|
|
groups = g_list_prepend (groups, group);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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 (current_column == NULL || n_rows + height > self->max_height)
|
|
{
|
|
GtkWidget *column_box;
|
|
GtkSizeGroup *size_group;
|
|
|
|
column_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 22);
|
|
|
|
size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
|
|
g_object_set_data_full (G_OBJECT (column_box), "accel-size-group", size_group, g_object_unref);
|
|
size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
|
|
g_object_set_data_full (G_OBJECT (column_box), "title-size-group", size_group, g_object_unref);
|
|
|
|
if (n_columns % 2 == 0)
|
|
{
|
|
page = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 22);
|
|
|
|
pages = g_list_append (pages, page);
|
|
current_page = page;
|
|
}
|
|
|
|
gtk_box_append (GTK_BOX (current_page), column_box);
|
|
current_column = column_box;
|
|
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_box_remove (GTK_BOX (gtk_widget_get_parent (GTK_WIDGET (group))), GTK_WIDGET (group));
|
|
gtk_box_append (GTK_BOX (current_column), GTK_WIDGET (group));
|
|
g_object_unref (group);
|
|
}
|
|
|
|
/* balance the last page */
|
|
if (n_columns % 2 == 1)
|
|
{
|
|
GtkWidget *column_box;
|
|
GtkSizeGroup *size_group;
|
|
GList *content;
|
|
GtkWidget *child;
|
|
guint n;
|
|
|
|
column_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 22);
|
|
|
|
size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
|
|
g_object_set_data_full (G_OBJECT (column_box), "accel-size-group", size_group, g_object_unref);
|
|
size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
|
|
g_object_set_data_full (G_OBJECT (column_box), "title-size-group", size_group, g_object_unref);
|
|
|
|
gtk_box_append (GTK_BOX (current_page), column_box);
|
|
|
|
content = NULL;
|
|
for (child = gtk_widget_get_last_child (current_column);
|
|
child != NULL;
|
|
child = gtk_widget_get_prev_sibling (child))
|
|
content = g_list_prepend (content, child);
|
|
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;
|
|
}
|
|
|
|
g_assert (g);
|
|
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_box), "accel-size-group"),
|
|
"title-size-group", g_object_get_data (G_OBJECT (column_box), "title-size-group"),
|
|
NULL);
|
|
|
|
g_object_ref (group);
|
|
gtk_box_remove (GTK_BOX (current_column), GTK_WIDGET (group));
|
|
gtk_box_append (GTK_BOX (column_box), GTK_WIDGET (group));
|
|
g_object_unref (group);
|
|
}
|
|
|
|
g_list_free (content);
|
|
}
|
|
|
|
/* replace the current pages with the new pages */
|
|
while ((page = gtk_widget_get_first_child (GTK_WIDGET (self->stack))))
|
|
gtk_stack_remove (self->stack, page);
|
|
|
|
for (p = pages, n_pages = 0; p; p = p->next, n_pages++)
|
|
{
|
|
char *title;
|
|
|
|
page = p->data;
|
|
title = g_strdup_printf ("_%u", n_pages + 1);
|
|
gtk_stack_add_titled (self->stack, page, title, title);
|
|
g_free (title);
|
|
}
|
|
|
|
/* fix up stack switcher */
|
|
{
|
|
GtkWidget *w;
|
|
|
|
gtk_widget_add_css_class (GTK_WIDGET (self->switcher), "circular");
|
|
|
|
for (w = gtk_widget_get_first_child (GTK_WIDGET (self->switcher));
|
|
w != NULL;
|
|
w = gtk_widget_get_next_sibling (w))
|
|
{
|
|
GtkWidget *label;
|
|
|
|
gtk_widget_add_css_class (w, "circular");
|
|
|
|
label = gtk_button_get_child (GTK_BUTTON (w));
|
|
gtk_label_set_use_underline (GTK_LABEL (label), TRUE);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
static gboolean
|
|
gtk_shortcuts_section_change_current_page (GtkShortcutsSection *self,
|
|
int offset)
|
|
{
|
|
GtkWidget *child;
|
|
|
|
child = gtk_stack_get_visible_child (self->stack);
|
|
|
|
if (offset == 1)
|
|
child = gtk_widget_get_next_sibling (child);
|
|
else if (offset == -1)
|
|
child = gtk_widget_get_prev_sibling (child);
|
|
else
|
|
g_assert_not_reached ();
|
|
|
|
if (child)
|
|
gtk_stack_set_visible_child (self->stack, child);
|
|
else
|
|
gtk_widget_error_bell (GTK_WIDGET (self));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gtk_shortcuts_section_pan_gesture_pan (GtkGesturePan *gesture,
|
|
GtkPanDirection direction,
|
|
double 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);
|
|
}
|