2013-09-29 17:43:27 +00:00
|
|
|
|
/*
|
|
|
|
|
* Copyright (C) 2007-2010 Openismus GmbH
|
|
|
|
|
* Copyright (C) 2013 Red Hat, Inc.
|
|
|
|
|
*
|
|
|
|
|
* 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
|
2014-01-12 12:56:49 +00:00
|
|
|
|
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
2013-09-29 17:43:27 +00:00
|
|
|
|
*
|
|
|
|
|
* Authors:
|
|
|
|
|
* Tristan Van Berkom <tristanvb@openismus.com>
|
|
|
|
|
* Matthias Clasen <mclasen@redhat.com>
|
|
|
|
|
* William Jon McCann <jmccann@redhat.com>
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* Preamble {{{1 */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* SECTION:gtkflowbox
|
|
|
|
|
* @Short_Description: A container that allows reflowing its children
|
|
|
|
|
* @Title: GtkFlowBox
|
|
|
|
|
*
|
2013-10-01 00:57:43 +00:00
|
|
|
|
* A GtkFlowBox positions child widgets in sequence according to its
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* orientation.
|
|
|
|
|
*
|
|
|
|
|
* For instance, with the horizontal orientation, the widgets will be
|
|
|
|
|
* arranged from left to right, starting a new row under the previous
|
|
|
|
|
* row when necessary. Reducing the width in this case will require more
|
|
|
|
|
* rows, so a larger height will be requested.
|
|
|
|
|
*
|
|
|
|
|
* Likewise, with the vertical orientation, the widgets will be arranged
|
|
|
|
|
* from top to bottom, starting a new column to the right when necessary.
|
|
|
|
|
* Reducing the height will require more columns, so a larger width will
|
|
|
|
|
* be requested.
|
|
|
|
|
*
|
2017-09-12 21:48:58 +00:00
|
|
|
|
* The size request of a GtkFlowBox alone may not be what you expect; if you
|
|
|
|
|
* need to be able to shrink it along both axes and dynamically reflow its
|
|
|
|
|
* children, you may have to wrap it in a #GtkScrolledWindow to enable that.
|
|
|
|
|
*
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* The children of a GtkFlowBox can be dynamically sorted and filtered.
|
|
|
|
|
*
|
|
|
|
|
* Although a GtkFlowBox must have only #GtkFlowBoxChild children,
|
|
|
|
|
* you can add any kind of widget to it via gtk_container_add(), and
|
|
|
|
|
* a GtkFlowBoxChild widget will automatically be inserted between
|
|
|
|
|
* the box and the widget.
|
|
|
|
|
*
|
|
|
|
|
* Also see #GtkListBox.
|
|
|
|
|
*
|
|
|
|
|
* GtkFlowBox was added in GTK+ 3.12.
|
2015-11-05 20:23:44 +00:00
|
|
|
|
*
|
|
|
|
|
* # CSS nodes
|
|
|
|
|
*
|
2015-11-10 12:39:40 +00:00
|
|
|
|
* |[<!-- language="plain" -->
|
|
|
|
|
* flowbox
|
|
|
|
|
* ├── flowboxchild
|
2015-12-16 15:55:52 +00:00
|
|
|
|
* │ ╰── <child>
|
2015-11-10 12:39:40 +00:00
|
|
|
|
* ├── flowboxchild
|
2015-12-16 15:55:52 +00:00
|
|
|
|
* │ ╰── <child>
|
2015-11-10 12:39:40 +00:00
|
|
|
|
* ┊
|
|
|
|
|
* ╰── [rubberband]
|
|
|
|
|
* ]|
|
|
|
|
|
*
|
2015-11-05 20:23:44 +00:00
|
|
|
|
* GtkFlowBox uses a single CSS node with name flowbox. GtkFlowBoxChild
|
|
|
|
|
* uses a single CSS node with name flowboxchild.
|
2015-11-10 05:50:42 +00:00
|
|
|
|
* For rubberband selection, a subnode with name rubberband is used.
|
2013-09-29 17:43:27 +00:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
|
|
#include "gtkflowbox.h"
|
2020-04-14 19:03:57 +00:00
|
|
|
|
#include "gtkflowboxprivate.h"
|
2018-02-08 23:47:58 +00:00
|
|
|
|
|
|
|
|
|
#include "gtkadjustment.h"
|
2020-05-03 00:53:40 +00:00
|
|
|
|
#include "gtkbinlayout.h"
|
|
|
|
|
#include "gtkbuildable.h"
|
2020-01-27 23:27:36 +00:00
|
|
|
|
#include "gtkcsscolorvalueprivate.h"
|
2018-02-08 23:47:58 +00:00
|
|
|
|
#include "gtkcssnodeprivate.h"
|
|
|
|
|
#include "gtkgesturedrag.h"
|
2019-05-29 17:10:46 +00:00
|
|
|
|
#include "gtkgestureclick.h"
|
2018-02-08 23:47:58 +00:00
|
|
|
|
#include "gtkintl.h"
|
|
|
|
|
#include "gtkmain.h"
|
2013-09-29 17:43:27 +00:00
|
|
|
|
#include "gtkmarshalers.h"
|
|
|
|
|
#include "gtkprivate.h"
|
2014-07-19 03:00:54 +00:00
|
|
|
|
#include "gtkorientableprivate.h"
|
2018-02-08 23:47:58 +00:00
|
|
|
|
#include "gtkrender.h"
|
|
|
|
|
#include "gtksizerequest.h"
|
|
|
|
|
#include "gtksnapshot.h"
|
2015-11-10 05:50:42 +00:00
|
|
|
|
#include "gtkstylecontextprivate.h"
|
2018-02-08 23:47:58 +00:00
|
|
|
|
#include "gtktypebuiltins.h"
|
|
|
|
|
#include "gtkviewport.h"
|
|
|
|
|
#include "gtkwidgetprivate.h"
|
2018-03-05 14:00:06 +00:00
|
|
|
|
#include "gtkeventcontrollerkey.h"
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
#include "a11y/gtkflowboxaccessibleprivate.h"
|
|
|
|
|
#include "a11y/gtkflowboxchildaccessible.h"
|
2020-05-03 00:53:40 +00:00
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
/* Forward declarations and utilities {{{1 */
|
|
|
|
|
|
|
|
|
|
static void gtk_flow_box_update_cursor (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxChild *child);
|
|
|
|
|
static void gtk_flow_box_select_and_activate (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxChild *child);
|
|
|
|
|
static void gtk_flow_box_update_selection (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxChild *child,
|
|
|
|
|
gboolean modify,
|
|
|
|
|
gboolean extend);
|
|
|
|
|
static void gtk_flow_box_apply_filter (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxChild *child);
|
|
|
|
|
static void gtk_flow_box_apply_sort (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxChild *child);
|
|
|
|
|
static gint gtk_flow_box_sort (GtkFlowBoxChild *a,
|
|
|
|
|
GtkFlowBoxChild *b,
|
|
|
|
|
GtkFlowBox *box);
|
|
|
|
|
|
2015-08-16 22:05:33 +00:00
|
|
|
|
static void gtk_flow_box_bound_model_changed (GListModel *list,
|
|
|
|
|
guint position,
|
|
|
|
|
guint removed,
|
|
|
|
|
guint added,
|
|
|
|
|
gpointer user_data);
|
|
|
|
|
|
2017-12-31 04:23:16 +00:00
|
|
|
|
static void gtk_flow_box_set_accept_unpaired_release (GtkFlowBox *box,
|
|
|
|
|
gboolean accept);
|
|
|
|
|
|
2015-08-25 17:28:49 +00:00
|
|
|
|
static void gtk_flow_box_check_model_compat (GtkFlowBox *box);
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
static void
|
|
|
|
|
path_from_horizontal_line_rects (cairo_t *cr,
|
|
|
|
|
GdkRectangle *lines,
|
|
|
|
|
gint n_lines)
|
|
|
|
|
{
|
|
|
|
|
gint start_line, end_line;
|
|
|
|
|
GdkRectangle *r;
|
|
|
|
|
gint i;
|
|
|
|
|
|
|
|
|
|
/* Join rows vertically by extending to the middle */
|
|
|
|
|
for (i = 0; i < n_lines - 1; i++)
|
|
|
|
|
{
|
|
|
|
|
GdkRectangle *r1 = &lines[i];
|
|
|
|
|
GdkRectangle *r2 = &lines[i+1];
|
|
|
|
|
gint gap, old;
|
|
|
|
|
|
|
|
|
|
gap = r2->y - (r1->y + r1->height);
|
|
|
|
|
r1->height += gap / 2;
|
|
|
|
|
old = r2->y;
|
|
|
|
|
r2->y = r1->y + r1->height;
|
|
|
|
|
r2->height += old - r2->y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cairo_new_path (cr);
|
|
|
|
|
start_line = 0;
|
|
|
|
|
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
for (i = start_line; i < n_lines; i++)
|
|
|
|
|
{
|
|
|
|
|
r = &lines[i];
|
|
|
|
|
if (i == start_line)
|
|
|
|
|
cairo_move_to (cr, r->x + r->width, r->y);
|
|
|
|
|
else
|
|
|
|
|
cairo_line_to (cr, r->x + r->width, r->y);
|
|
|
|
|
cairo_line_to (cr, r->x + r->width, r->y + r->height);
|
|
|
|
|
|
|
|
|
|
if (i < n_lines - 1 &&
|
|
|
|
|
(r->x + r->width < lines[i+1].x ||
|
|
|
|
|
r->x > lines[i+1].x + lines[i+1].width))
|
|
|
|
|
{
|
|
|
|
|
i++;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
end_line = i;
|
|
|
|
|
for (i = end_line - 1; i >= start_line; i--)
|
|
|
|
|
{
|
|
|
|
|
r = &lines[i];
|
|
|
|
|
cairo_line_to (cr, r->x, r->y + r->height);
|
|
|
|
|
cairo_line_to (cr, r->x, r->y);
|
|
|
|
|
}
|
|
|
|
|
cairo_close_path (cr);
|
|
|
|
|
start_line = end_line;
|
|
|
|
|
}
|
|
|
|
|
while (end_line < n_lines);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
path_from_vertical_line_rects (cairo_t *cr,
|
|
|
|
|
GdkRectangle *lines,
|
|
|
|
|
gint n_lines)
|
|
|
|
|
{
|
|
|
|
|
gint start_line, end_line;
|
|
|
|
|
GdkRectangle *r;
|
|
|
|
|
gint i;
|
|
|
|
|
|
|
|
|
|
/* Join rows horizontally by extending to the middle */
|
|
|
|
|
for (i = 0; i < n_lines - 1; i++)
|
|
|
|
|
{
|
|
|
|
|
GdkRectangle *r1 = &lines[i];
|
|
|
|
|
GdkRectangle *r2 = &lines[i+1];
|
|
|
|
|
gint gap, old;
|
|
|
|
|
|
|
|
|
|
gap = r2->x - (r1->x + r1->width);
|
|
|
|
|
r1->width += gap / 2;
|
|
|
|
|
old = r2->x;
|
|
|
|
|
r2->x = r1->x + r1->width;
|
|
|
|
|
r2->width += old - r2->x;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cairo_new_path (cr);
|
|
|
|
|
start_line = 0;
|
|
|
|
|
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
for (i = start_line; i < n_lines; i++)
|
|
|
|
|
{
|
|
|
|
|
r = &lines[i];
|
|
|
|
|
if (i == start_line)
|
|
|
|
|
cairo_move_to (cr, r->x, r->y + r->height);
|
|
|
|
|
else
|
|
|
|
|
cairo_line_to (cr, r->x, r->y + r->height);
|
|
|
|
|
cairo_line_to (cr, r->x + r->width, r->y + r->height);
|
|
|
|
|
|
|
|
|
|
if (i < n_lines - 1 &&
|
|
|
|
|
(r->y + r->height < lines[i+1].y ||
|
|
|
|
|
r->y > lines[i+1].y + lines[i+1].height))
|
|
|
|
|
{
|
|
|
|
|
i++;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
end_line = i;
|
|
|
|
|
for (i = end_line - 1; i >= start_line; i--)
|
|
|
|
|
{
|
|
|
|
|
r = &lines[i];
|
|
|
|
|
cairo_line_to (cr, r->x + r->width, r->y);
|
|
|
|
|
cairo_line_to (cr, r->x, r->y);
|
|
|
|
|
}
|
|
|
|
|
cairo_close_path (cr);
|
|
|
|
|
start_line = end_line;
|
|
|
|
|
}
|
|
|
|
|
while (end_line < n_lines);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* GtkFlowBoxChild {{{1 */
|
|
|
|
|
|
|
|
|
|
/* GObject boilerplate {{{2 */
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
|
CHILD_ACTIVATE,
|
|
|
|
|
CHILD_LAST_SIGNAL
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static guint child_signals[CHILD_LAST_SIGNAL] = { 0 };
|
|
|
|
|
|
2020-05-03 00:53:40 +00:00
|
|
|
|
enum {
|
|
|
|
|
PROP_CHILD = 1
|
|
|
|
|
};
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
typedef struct _GtkFlowBoxChildPrivate GtkFlowBoxChildPrivate;
|
|
|
|
|
struct _GtkFlowBoxChildPrivate
|
|
|
|
|
{
|
2020-05-03 00:53:40 +00:00
|
|
|
|
GtkWidget *child;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
gboolean selected;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define CHILD_PRIV(child) ((GtkFlowBoxChildPrivate*)gtk_flow_box_child_get_instance_private ((GtkFlowBoxChild*)(child)))
|
|
|
|
|
|
2020-05-03 00:53:40 +00:00
|
|
|
|
static void gtk_flow_box_child_buildable_iface_init (GtkBuildableIface *iface);
|
|
|
|
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GtkFlowBoxChild, gtk_flow_box_child, GTK_TYPE_WIDGET,
|
|
|
|
|
G_ADD_PRIVATE (GtkFlowBoxChild)
|
|
|
|
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
|
|
|
|
|
gtk_flow_box_child_buildable_iface_init))
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
/* Internal API {{{2 */
|
|
|
|
|
|
|
|
|
|
static GtkFlowBox *
|
|
|
|
|
gtk_flow_box_child_get_box (GtkFlowBoxChild *child)
|
|
|
|
|
{
|
|
|
|
|
GtkWidget *parent;
|
|
|
|
|
|
|
|
|
|
parent = gtk_widget_get_parent (GTK_WIDGET (child));
|
|
|
|
|
if (parent && GTK_IS_FLOW_BOX (parent))
|
|
|
|
|
return GTK_FLOW_BOX (parent);
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_child_set_focus (GtkFlowBoxChild *child)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBox *box = gtk_flow_box_child_get_box (child);
|
|
|
|
|
|
2020-04-11 18:52:43 +00:00
|
|
|
|
gtk_flow_box_update_selection (box, child, FALSE, FALSE);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* GtkWidget implementation {{{2 */
|
|
|
|
|
|
2020-05-03 00:53:40 +00:00
|
|
|
|
static GtkBuildableIface *parent_child_buildable_iface;
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_child_buildable_add_child (GtkBuildable *buildable,
|
|
|
|
|
GtkBuilder *builder,
|
|
|
|
|
GObject *child,
|
|
|
|
|
const gchar *type)
|
|
|
|
|
{
|
|
|
|
|
if (GTK_IS_WIDGET (child))
|
|
|
|
|
gtk_flow_box_child_set_child (GTK_FLOW_BOX_CHILD (buildable), GTK_WIDGET (child));
|
|
|
|
|
else
|
|
|
|
|
parent_child_buildable_iface->add_child (buildable, builder, child, type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_child_buildable_iface_init (GtkBuildableIface *iface)
|
|
|
|
|
{
|
|
|
|
|
parent_child_buildable_iface = g_type_interface_peek_parent (iface);
|
|
|
|
|
|
|
|
|
|
iface->add_child = gtk_flow_box_child_buildable_add_child;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
static gboolean
|
|
|
|
|
gtk_flow_box_child_focus (GtkWidget *widget,
|
|
|
|
|
GtkDirectionType direction)
|
|
|
|
|
{
|
2020-05-03 00:53:40 +00:00
|
|
|
|
GtkFlowBoxChild *self = GTK_FLOW_BOX_CHILD (widget);
|
|
|
|
|
GtkFlowBoxChildPrivate *priv = CHILD_PRIV (self);
|
|
|
|
|
GtkWidget *child = priv->child;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
gboolean had_focus = FALSE;
|
|
|
|
|
|
2015-08-23 00:32:21 +00:00
|
|
|
|
/* Without "can-focus" flag try to pass the focus to the child immediately */
|
|
|
|
|
if (!gtk_widget_get_can_focus (widget))
|
|
|
|
|
{
|
|
|
|
|
if (child)
|
|
|
|
|
{
|
|
|
|
|
if (gtk_widget_child_focus (child, direction))
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBox *box;
|
|
|
|
|
box = gtk_flow_box_child_get_box (GTK_FLOW_BOX_CHILD (widget));
|
|
|
|
|
if (box)
|
|
|
|
|
gtk_flow_box_update_cursor (box, GTK_FLOW_BOX_CHILD (widget));
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
g_object_get (widget, "has-focus", &had_focus, NULL);
|
|
|
|
|
if (had_focus)
|
|
|
|
|
{
|
|
|
|
|
/* If on row, going right, enter into possible container */
|
|
|
|
|
if (child &&
|
|
|
|
|
(direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD))
|
|
|
|
|
{
|
|
|
|
|
if (gtk_widget_child_focus (GTK_WIDGET (child), direction))
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
2017-02-08 08:00:29 +00:00
|
|
|
|
else if (gtk_widget_get_focus_child (widget) != NULL)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
|
|
|
|
/* Child has focus, always navigate inside it first */
|
|
|
|
|
if (gtk_widget_child_focus (child, direction))
|
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
|
|
/* If exiting child container to the left, select child */
|
|
|
|
|
if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD)
|
|
|
|
|
{
|
|
|
|
|
gtk_flow_box_child_set_focus (GTK_FLOW_BOX_CHILD (widget));
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* If coming from the left, enter into possible container */
|
|
|
|
|
if (child &&
|
|
|
|
|
(direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD))
|
|
|
|
|
{
|
|
|
|
|
if (gtk_widget_child_focus (child, direction))
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gtk_flow_box_child_set_focus (GTK_FLOW_BOX_CHILD (widget));
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_child_activate (GtkFlowBoxChild *child)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBox *box;
|
|
|
|
|
|
|
|
|
|
box = gtk_flow_box_child_get_box (child);
|
|
|
|
|
if (box)
|
|
|
|
|
gtk_flow_box_select_and_activate (box, child);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Size allocation {{{3 */
|
|
|
|
|
|
|
|
|
|
static GtkSizeRequestMode
|
|
|
|
|
gtk_flow_box_child_get_request_mode (GtkWidget *widget)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBox *box;
|
|
|
|
|
|
|
|
|
|
box = gtk_flow_box_child_get_box (GTK_FLOW_BOX_CHILD (widget));
|
|
|
|
|
if (box)
|
|
|
|
|
return gtk_widget_get_request_mode (GTK_WIDGET (box));
|
|
|
|
|
else
|
|
|
|
|
return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-03 00:53:40 +00:00
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_child_dispose (GObject *object)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxChild *self = GTK_FLOW_BOX_CHILD (object);
|
|
|
|
|
GtkFlowBoxChildPrivate *priv = CHILD_PRIV (self);
|
|
|
|
|
|
|
|
|
|
g_clear_pointer (&priv->child, gtk_widget_unparent);
|
|
|
|
|
|
|
|
|
|
G_OBJECT_CLASS (gtk_flow_box_child_parent_class)->dispose (object);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_child_get_property (GObject *object,
|
|
|
|
|
guint prop_id,
|
|
|
|
|
GValue *value,
|
|
|
|
|
GParamSpec *pspec)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxChild *self = GTK_FLOW_BOX_CHILD (object);
|
|
|
|
|
|
|
|
|
|
switch (prop_id)
|
|
|
|
|
{
|
|
|
|
|
case PROP_CHILD:
|
|
|
|
|
g_value_set_object (value, gtk_flow_box_child_get_child (self));
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_child_set_property (GObject *object,
|
|
|
|
|
guint prop_id,
|
|
|
|
|
const GValue *value,
|
|
|
|
|
GParamSpec *pspec)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxChild *self = GTK_FLOW_BOX_CHILD (object);
|
|
|
|
|
|
|
|
|
|
switch (prop_id)
|
|
|
|
|
{
|
|
|
|
|
case PROP_CHILD:
|
|
|
|
|
gtk_flow_box_child_set_child (self, g_value_get_object (value));
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_child_compute_expand (GtkWidget *widget,
|
|
|
|
|
gboolean *hexpand,
|
|
|
|
|
gboolean *vexpand)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxChild *self = GTK_FLOW_BOX_CHILD (widget);
|
|
|
|
|
GtkFlowBoxChildPrivate *priv = CHILD_PRIV (self);
|
|
|
|
|
|
|
|
|
|
if (priv->child)
|
|
|
|
|
{
|
|
|
|
|
*hexpand = gtk_widget_compute_expand (priv->child, GTK_ORIENTATION_HORIZONTAL);
|
|
|
|
|
*vexpand = gtk_widget_compute_expand (priv->child, GTK_ORIENTATION_VERTICAL);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
*hexpand = FALSE;
|
|
|
|
|
*vexpand = FALSE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
/* GObject implementation {{{2 */
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_child_class_init (GtkFlowBoxChildClass *class)
|
|
|
|
|
{
|
|
|
|
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
|
|
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
|
|
|
|
|
|
2020-05-03 00:53:40 +00:00
|
|
|
|
object_class->dispose = gtk_flow_box_child_dispose;
|
|
|
|
|
object_class->get_property = gtk_flow_box_child_get_property;
|
|
|
|
|
object_class->set_property = gtk_flow_box_child_set_property;
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
widget_class->get_request_mode = gtk_flow_box_child_get_request_mode;
|
2020-05-03 00:53:40 +00:00
|
|
|
|
widget_class->compute_expand = gtk_flow_box_child_compute_expand;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
widget_class->focus = gtk_flow_box_child_focus;
|
2020-05-03 00:53:40 +00:00
|
|
|
|
widget_class->grab_focus = gtk_widget_grab_focus_none;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
class->activate = gtk_flow_box_child_activate;
|
|
|
|
|
|
2020-05-03 00:53:40 +00:00
|
|
|
|
g_object_class_install_property (object_class,
|
|
|
|
|
PROP_CHILD,
|
|
|
|
|
g_param_spec_object ("child",
|
|
|
|
|
P_("Child"),
|
|
|
|
|
P_("The child widget"),
|
|
|
|
|
GTK_TYPE_WIDGET,
|
|
|
|
|
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBoxChild::activate:
|
|
|
|
|
* @child: The child on which the signal is emitted
|
|
|
|
|
*
|
|
|
|
|
* The ::activate signal is emitted when the user activates
|
|
|
|
|
* a child widget in a #GtkFlowBox, either by clicking or
|
|
|
|
|
* double-clicking, or by using the Space or Enter key.
|
|
|
|
|
*
|
|
|
|
|
* While this signal is used as a
|
2014-02-07 20:37:02 +00:00
|
|
|
|
* [keybinding signal][GtkBindingSignal],
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* it can be used by applications for their own purposes.
|
|
|
|
|
*/
|
|
|
|
|
child_signals[CHILD_ACTIVATE] =
|
|
|
|
|
g_signal_new (I_("activate"),
|
|
|
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
|
|
|
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
|
|
|
|
G_STRUCT_OFFSET (GtkFlowBoxChildClass, activate),
|
|
|
|
|
NULL, NULL,
|
2019-05-29 20:05:19 +00:00
|
|
|
|
NULL,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
G_TYPE_NONE, 0);
|
|
|
|
|
widget_class->activate_signal = child_signals[CHILD_ACTIVATE];
|
|
|
|
|
|
2020-05-03 00:53:40 +00:00
|
|
|
|
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_LIST_ITEM);
|
2017-11-18 03:49:57 +00:00
|
|
|
|
gtk_widget_class_set_css_name (widget_class, I_("flowboxchild"));
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_child_init (GtkFlowBoxChild *child)
|
|
|
|
|
{
|
|
|
|
|
}
|
2020-05-03 00:53:40 +00:00
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
/* Public API {{{2 */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_child_new:
|
|
|
|
|
*
|
|
|
|
|
* Creates a new #GtkFlowBoxChild, to be used as a child
|
|
|
|
|
* of a #GtkFlowBox.
|
|
|
|
|
*
|
|
|
|
|
* Returns: a new #GtkFlowBoxChild
|
|
|
|
|
*/
|
|
|
|
|
GtkWidget *
|
|
|
|
|
gtk_flow_box_child_new (void)
|
|
|
|
|
{
|
|
|
|
|
return g_object_new (GTK_TYPE_FLOW_BOX_CHILD, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-03 00:53:40 +00:00
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_child_set_child:
|
|
|
|
|
* @self: a #GtkFlowBoxChild
|
|
|
|
|
* @child: (allow-none): the child widget
|
|
|
|
|
*
|
|
|
|
|
* Sets the child widget of @self.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_child_set_child (GtkFlowBoxChild *self,
|
|
|
|
|
GtkWidget *child)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxChildPrivate *priv = CHILD_PRIV (self);
|
|
|
|
|
|
|
|
|
|
g_clear_pointer (&priv->child, gtk_widget_unparent);
|
|
|
|
|
|
|
|
|
|
priv->child = child;
|
|
|
|
|
if (child)
|
|
|
|
|
gtk_widget_set_parent (child, GTK_WIDGET (self));
|
|
|
|
|
g_object_notify (G_OBJECT (self), "child");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_child_get_child:
|
|
|
|
|
* @self: a #GtkFlowBoxChild
|
|
|
|
|
*
|
|
|
|
|
* Gets the child widget of @self.
|
|
|
|
|
*
|
|
|
|
|
* Returns: (nullable) (transfer none): the child widget of @self
|
|
|
|
|
*/
|
|
|
|
|
GtkWidget *
|
|
|
|
|
gtk_flow_box_child_get_child (GtkFlowBoxChild *self)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxChildPrivate *priv = CHILD_PRIV (self);
|
|
|
|
|
|
|
|
|
|
return priv->child;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_child_get_index:
|
|
|
|
|
* @child: a #GtkFlowBoxChild
|
|
|
|
|
*
|
|
|
|
|
* Gets the current index of the @child in its #GtkFlowBox container.
|
|
|
|
|
*
|
|
|
|
|
* Returns: the index of the @child, or -1 if the @child is not
|
|
|
|
|
* in a flow box.
|
|
|
|
|
*/
|
|
|
|
|
gint
|
|
|
|
|
gtk_flow_box_child_get_index (GtkFlowBoxChild *child)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxChildPrivate *priv;
|
|
|
|
|
|
|
|
|
|
g_return_val_if_fail (GTK_IS_FLOW_BOX_CHILD (child), -1);
|
|
|
|
|
|
|
|
|
|
priv = CHILD_PRIV (child);
|
|
|
|
|
|
|
|
|
|
if (priv->iter != NULL)
|
|
|
|
|
return g_sequence_iter_get_position (priv->iter);
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_child_is_selected:
|
|
|
|
|
* @child: a #GtkFlowBoxChild
|
|
|
|
|
*
|
|
|
|
|
* Returns whether the @child is currently selected in its
|
|
|
|
|
* #GtkFlowBox container.
|
|
|
|
|
*
|
|
|
|
|
* Returns: %TRUE if @child is selected
|
|
|
|
|
*/
|
|
|
|
|
gboolean
|
|
|
|
|
gtk_flow_box_child_is_selected (GtkFlowBoxChild *child)
|
|
|
|
|
{
|
2013-10-07 04:00:30 +00:00
|
|
|
|
g_return_val_if_fail (GTK_IS_FLOW_BOX_CHILD (child), FALSE);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
return CHILD_PRIV (child)->selected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_child_changed:
|
|
|
|
|
* @child: a #GtkFlowBoxChild
|
|
|
|
|
*
|
|
|
|
|
* Marks @child as changed, causing any state that depends on this
|
|
|
|
|
* to be updated. This affects sorting and filtering.
|
|
|
|
|
*
|
|
|
|
|
* Note that calls to this method must be in sync with the data
|
|
|
|
|
* used for the sorting and filtering functions. For instance, if
|
|
|
|
|
* the list is mirroring some external data set, and *two* children
|
|
|
|
|
* changed in the external data set when you call
|
|
|
|
|
* gtk_flow_box_child_changed() on the first child, the sort function
|
|
|
|
|
* must only read the new data for the first of the two changed
|
|
|
|
|
* children, otherwise the resorting of the children will be wrong.
|
|
|
|
|
*
|
2014-02-07 18:32:47 +00:00
|
|
|
|
* This generally means that if you don’t fully control the data
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* model, you have to duplicate the data that affects the sorting
|
|
|
|
|
* and filtering functions into the widgets themselves. Another
|
|
|
|
|
* alternative is to call gtk_flow_box_invalidate_sort() on any
|
|
|
|
|
* model change, but that is more expensive.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_child_changed (GtkFlowBoxChild *child)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBox *box;
|
|
|
|
|
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX_CHILD (child));
|
|
|
|
|
|
|
|
|
|
box = gtk_flow_box_child_get_box (child);
|
|
|
|
|
|
|
|
|
|
if (box == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
gtk_flow_box_apply_sort (box, child);
|
|
|
|
|
gtk_flow_box_apply_filter (box, child);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* GtkFlowBox {{{1 */
|
|
|
|
|
|
|
|
|
|
/* Constants {{{2 */
|
|
|
|
|
|
|
|
|
|
#define DEFAULT_MAX_CHILDREN_PER_LINE 7
|
|
|
|
|
#define RUBBERBAND_START_DISTANCE 32
|
|
|
|
|
#define AUTOSCROLL_FAST_DISTANCE 32
|
|
|
|
|
#define AUTOSCROLL_FACTOR 20
|
|
|
|
|
#define AUTOSCROLL_FACTOR_FAST 10
|
|
|
|
|
|
|
|
|
|
/* GObject boilerplate {{{2 */
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
|
CHILD_ACTIVATED,
|
|
|
|
|
SELECTED_CHILDREN_CHANGED,
|
|
|
|
|
ACTIVATE_CURSOR_CHILD,
|
|
|
|
|
TOGGLE_CURSOR_CHILD,
|
|
|
|
|
MOVE_CURSOR,
|
|
|
|
|
SELECT_ALL,
|
|
|
|
|
UNSELECT_ALL,
|
|
|
|
|
LAST_SIGNAL
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
|
PROP_0,
|
|
|
|
|
PROP_HOMOGENEOUS,
|
|
|
|
|
PROP_COLUMN_SPACING,
|
|
|
|
|
PROP_ROW_SPACING,
|
|
|
|
|
PROP_MIN_CHILDREN_PER_LINE,
|
|
|
|
|
PROP_MAX_CHILDREN_PER_LINE,
|
|
|
|
|
PROP_SELECTION_MODE,
|
2014-06-07 13:51:48 +00:00
|
|
|
|
PROP_ACTIVATE_ON_SINGLE_CLICK,
|
2017-12-31 04:23:16 +00:00
|
|
|
|
PROP_ACCEPT_UNPAIRED_RELEASE,
|
2014-06-07 13:51:48 +00:00
|
|
|
|
|
|
|
|
|
/* orientable */
|
2014-06-07 04:39:00 +00:00
|
|
|
|
PROP_ORIENTATION,
|
|
|
|
|
LAST_PROP = PROP_ORIENTATION
|
2013-09-29 17:43:27 +00:00
|
|
|
|
};
|
|
|
|
|
|
2014-06-07 04:39:00 +00:00
|
|
|
|
static GParamSpec *props[LAST_PROP] = { NULL, };
|
|
|
|
|
|
2019-05-29 00:01:03 +00:00
|
|
|
|
typedef struct _GtkFlowBoxClass GtkFlowBoxClass;
|
|
|
|
|
|
|
|
|
|
struct _GtkFlowBox
|
|
|
|
|
{
|
|
|
|
|
GtkContainer container;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct _GtkFlowBoxClass
|
|
|
|
|
{
|
|
|
|
|
GtkContainerClass parent_class;
|
|
|
|
|
|
|
|
|
|
void (*child_activated) (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxChild *child);
|
|
|
|
|
void (*selected_children_changed) (GtkFlowBox *box);
|
|
|
|
|
void (*activate_cursor_child) (GtkFlowBox *box);
|
|
|
|
|
void (*toggle_cursor_child) (GtkFlowBox *box);
|
|
|
|
|
gboolean (*move_cursor) (GtkFlowBox *box,
|
|
|
|
|
GtkMovementStep step,
|
2020-04-11 18:52:43 +00:00
|
|
|
|
gint count,
|
|
|
|
|
gboolean extend,
|
|
|
|
|
gboolean modify);
|
2019-05-29 00:01:03 +00:00
|
|
|
|
void (*select_all) (GtkFlowBox *box);
|
|
|
|
|
void (*unselect_all) (GtkFlowBox *box);
|
|
|
|
|
};
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
typedef struct _GtkFlowBoxPrivate GtkFlowBoxPrivate;
|
|
|
|
|
struct _GtkFlowBoxPrivate {
|
|
|
|
|
GtkOrientation orientation;
|
|
|
|
|
gboolean homogeneous;
|
|
|
|
|
|
|
|
|
|
guint row_spacing;
|
|
|
|
|
guint column_spacing;
|
|
|
|
|
|
|
|
|
|
GtkFlowBoxChild *cursor_child;
|
|
|
|
|
GtkFlowBoxChild *selected_child;
|
|
|
|
|
|
|
|
|
|
GtkFlowBoxChild *active_child;
|
|
|
|
|
|
|
|
|
|
GtkSelectionMode selection_mode;
|
|
|
|
|
|
|
|
|
|
GtkAdjustment *hadjustment;
|
|
|
|
|
GtkAdjustment *vadjustment;
|
|
|
|
|
gboolean activate_on_single_click;
|
2017-12-31 04:23:16 +00:00
|
|
|
|
gboolean accept_unpaired_release;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
guint16 min_children_per_line;
|
|
|
|
|
guint16 max_children_per_line;
|
|
|
|
|
guint16 cur_children_per_line;
|
|
|
|
|
|
|
|
|
|
GSequence *children;
|
|
|
|
|
|
|
|
|
|
GtkFlowBoxFilterFunc filter_func;
|
|
|
|
|
gpointer filter_data;
|
|
|
|
|
GDestroyNotify filter_destroy;
|
|
|
|
|
|
|
|
|
|
GtkFlowBoxSortFunc sort_func;
|
|
|
|
|
gpointer sort_data;
|
|
|
|
|
GDestroyNotify sort_destroy;
|
|
|
|
|
|
2014-06-05 13:45:21 +00:00
|
|
|
|
GtkGesture *drag_gesture;
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GtkFlowBoxChild *rubberband_first;
|
|
|
|
|
GtkFlowBoxChild *rubberband_last;
|
2015-11-10 05:50:42 +00:00
|
|
|
|
GtkCssNode *rubberband_node;
|
|
|
|
|
gboolean rubberband_select;
|
2013-10-07 03:15:27 +00:00
|
|
|
|
gboolean rubberband_modify;
|
|
|
|
|
gboolean rubberband_extend;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
GtkScrollType autoscroll_mode;
|
|
|
|
|
guint autoscroll_id;
|
2015-08-16 22:05:33 +00:00
|
|
|
|
|
|
|
|
|
GListModel *bound_model;
|
|
|
|
|
GtkFlowBoxCreateWidgetFunc create_widget_func;
|
|
|
|
|
gpointer create_widget_func_data;
|
|
|
|
|
GDestroyNotify create_widget_func_data_destroy;
|
2020-04-14 19:03:57 +00:00
|
|
|
|
|
|
|
|
|
gboolean disable_move_cursor;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define BOX_PRIV(box) ((GtkFlowBoxPrivate*)gtk_flow_box_get_instance_private ((GtkFlowBox*)(box)))
|
|
|
|
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GtkFlowBox, gtk_flow_box, GTK_TYPE_CONTAINER,
|
|
|
|
|
G_ADD_PRIVATE (GtkFlowBox)
|
|
|
|
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
|
|
|
|
|
|
2015-08-16 22:05:33 +00:00
|
|
|
|
/* Internal API, utilities {{{2 */
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
#define ORIENTATION_ALIGN(box) \
|
|
|
|
|
(BOX_PRIV(box)->orientation == GTK_ORIENTATION_HORIZONTAL \
|
|
|
|
|
? gtk_widget_get_halign (GTK_WIDGET (box)) \
|
|
|
|
|
: gtk_widget_get_valign (GTK_WIDGET (box)))
|
|
|
|
|
|
|
|
|
|
#define OPPOSING_ORIENTATION_ALIGN(box) \
|
|
|
|
|
(BOX_PRIV(box)->orientation == GTK_ORIENTATION_HORIZONTAL \
|
|
|
|
|
? gtk_widget_get_valign (GTK_WIDGET (box)) \
|
|
|
|
|
: gtk_widget_get_halign (GTK_WIDGET (box)))
|
|
|
|
|
|
|
|
|
|
/* Children are visible if they are shown by the app (visible)
|
|
|
|
|
* and not filtered out (child_visible) by the box
|
|
|
|
|
*/
|
|
|
|
|
static inline gboolean
|
|
|
|
|
child_is_visible (GtkWidget *child)
|
|
|
|
|
{
|
|
|
|
|
return gtk_widget_get_visible (child) &&
|
|
|
|
|
gtk_widget_get_child_visible (child);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gint
|
|
|
|
|
get_visible_children (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
gint i = 0;
|
|
|
|
|
|
|
|
|
|
for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
|
|
|
|
!g_sequence_iter_is_end (iter);
|
|
|
|
|
iter = g_sequence_iter_next (iter))
|
|
|
|
|
{
|
|
|
|
|
GtkWidget *child;
|
|
|
|
|
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
if (child_is_visible (child))
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_apply_filter (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxChild *child)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
gboolean do_show;
|
|
|
|
|
|
|
|
|
|
do_show = TRUE;
|
|
|
|
|
if (priv->filter_func != NULL)
|
|
|
|
|
do_show = priv->filter_func (child, priv->filter_data);
|
|
|
|
|
|
|
|
|
|
gtk_widget_set_child_visible (GTK_WIDGET (child), do_show);
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-01 01:57:41 +00:00
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_apply_filter_all (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
|
|
|
|
|
for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
|
|
|
|
!g_sequence_iter_is_end (iter);
|
|
|
|
|
iter = g_sequence_iter_next (iter))
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxChild *child;
|
|
|
|
|
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
gtk_flow_box_apply_filter (box, child);
|
|
|
|
|
}
|
|
|
|
|
gtk_widget_queue_resize (GTK_WIDGET (box));
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_apply_sort (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxChild *child)
|
|
|
|
|
{
|
|
|
|
|
if (BOX_PRIV (box)->sort_func != NULL)
|
|
|
|
|
{
|
|
|
|
|
g_sequence_sort_changed (CHILD_PRIV (child)->iter,
|
|
|
|
|
(GCompareDataFunc)gtk_flow_box_sort, box);
|
|
|
|
|
gtk_widget_queue_resize (GTK_WIDGET (box));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Selection utilities {{{3 */
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
gtk_flow_box_child_set_selected (GtkFlowBoxChild *child,
|
|
|
|
|
gboolean selected)
|
|
|
|
|
{
|
|
|
|
|
if (CHILD_PRIV (child)->selected != selected)
|
|
|
|
|
{
|
|
|
|
|
CHILD_PRIV (child)->selected = selected;
|
|
|
|
|
if (selected)
|
|
|
|
|
gtk_widget_set_state_flags (GTK_WIDGET (child),
|
|
|
|
|
GTK_STATE_FLAG_SELECTED, FALSE);
|
|
|
|
|
else
|
|
|
|
|
gtk_widget_unset_state_flags (GTK_WIDGET (child),
|
|
|
|
|
GTK_STATE_FLAG_SELECTED);
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
gtk_flow_box_unselect_all_internal (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxChild *child;
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
gboolean dirty = FALSE;
|
|
|
|
|
|
|
|
|
|
if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_NONE)
|
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
|
|
for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
|
|
|
|
!g_sequence_iter_is_end (iter);
|
|
|
|
|
iter = g_sequence_iter_next (iter))
|
|
|
|
|
{
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
dirty |= gtk_flow_box_child_set_selected (child, FALSE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dirty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2013-10-07 03:15:27 +00:00
|
|
|
|
gtk_flow_box_unselect_child_internal (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxChild *child)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
|
|
|
|
if (!CHILD_PRIV (child)->selected)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_NONE)
|
|
|
|
|
return;
|
|
|
|
|
else if (BOX_PRIV (box)->selection_mode != GTK_SELECTION_MULTIPLE)
|
|
|
|
|
gtk_flow_box_unselect_all_internal (box);
|
|
|
|
|
else
|
|
|
|
|
gtk_flow_box_child_set_selected (child, FALSE);
|
|
|
|
|
|
|
|
|
|
g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_update_cursor (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxChild *child)
|
|
|
|
|
{
|
|
|
|
|
BOX_PRIV (box)->cursor_child = child;
|
|
|
|
|
gtk_widget_grab_focus (GTK_WIDGET (child));
|
|
|
|
|
_gtk_flow_box_accessible_update_cursor (GTK_WIDGET (box), GTK_WIDGET (child));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2013-10-07 03:15:27 +00:00
|
|
|
|
gtk_flow_box_select_child_internal (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxChild *child)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
|
|
|
|
if (CHILD_PRIV (child)->selected)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_NONE)
|
|
|
|
|
return;
|
|
|
|
|
if (BOX_PRIV (box)->selection_mode != GTK_SELECTION_MULTIPLE)
|
|
|
|
|
gtk_flow_box_unselect_all_internal (box);
|
|
|
|
|
|
|
|
|
|
gtk_flow_box_child_set_selected (child, TRUE);
|
|
|
|
|
BOX_PRIV (box)->selected_child = child;
|
|
|
|
|
|
|
|
|
|
g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_select_all_between (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxChild *child1,
|
2013-10-07 03:15:27 +00:00
|
|
|
|
GtkFlowBoxChild *child2,
|
|
|
|
|
gboolean modify)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
|
|
|
|
GSequenceIter *iter, *iter1, *iter2;
|
|
|
|
|
|
|
|
|
|
if (child1)
|
|
|
|
|
iter1 = CHILD_PRIV (child1)->iter;
|
|
|
|
|
else
|
|
|
|
|
iter1 = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
|
|
|
|
|
|
|
|
|
if (child2)
|
|
|
|
|
iter2 = CHILD_PRIV (child2)->iter;
|
|
|
|
|
else
|
|
|
|
|
iter2 = g_sequence_get_end_iter (BOX_PRIV (box)->children);
|
|
|
|
|
|
|
|
|
|
if (g_sequence_iter_compare (iter2, iter1) < 0)
|
|
|
|
|
{
|
|
|
|
|
iter = iter1;
|
|
|
|
|
iter1 = iter2;
|
|
|
|
|
iter2 = iter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (iter = iter1;
|
|
|
|
|
!g_sequence_iter_is_end (iter);
|
|
|
|
|
iter = g_sequence_iter_next (iter))
|
|
|
|
|
{
|
|
|
|
|
GtkWidget *child;
|
|
|
|
|
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
if (child_is_visible (child))
|
2013-10-07 03:15:27 +00:00
|
|
|
|
{
|
|
|
|
|
if (modify)
|
|
|
|
|
gtk_flow_box_child_set_selected (GTK_FLOW_BOX_CHILD (child), !CHILD_PRIV (child)->selected);
|
|
|
|
|
else
|
|
|
|
|
gtk_flow_box_child_set_selected (GTK_FLOW_BOX_CHILD (child), TRUE);
|
|
|
|
|
}
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
if (g_sequence_iter_compare (iter, iter2) == 0)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_update_selection (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxChild *child,
|
|
|
|
|
gboolean modify,
|
|
|
|
|
gboolean extend)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
gtk_flow_box_update_cursor (box, child);
|
|
|
|
|
|
|
|
|
|
if (priv->selection_mode == GTK_SELECTION_NONE)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (priv->selection_mode == GTK_SELECTION_BROWSE)
|
|
|
|
|
{
|
|
|
|
|
gtk_flow_box_unselect_all_internal (box);
|
|
|
|
|
gtk_flow_box_child_set_selected (child, TRUE);
|
|
|
|
|
priv->selected_child = child;
|
|
|
|
|
}
|
|
|
|
|
else if (priv->selection_mode == GTK_SELECTION_SINGLE)
|
|
|
|
|
{
|
|
|
|
|
gboolean was_selected;
|
|
|
|
|
|
|
|
|
|
was_selected = CHILD_PRIV (child)->selected;
|
|
|
|
|
gtk_flow_box_unselect_all_internal (box);
|
|
|
|
|
gtk_flow_box_child_set_selected (child, modify ? !was_selected : TRUE);
|
|
|
|
|
priv->selected_child = CHILD_PRIV (child)->selected ? child : NULL;
|
|
|
|
|
}
|
|
|
|
|
else /* GTK_SELECTION_MULTIPLE */
|
|
|
|
|
{
|
|
|
|
|
if (extend)
|
|
|
|
|
{
|
|
|
|
|
gtk_flow_box_unselect_all_internal (box);
|
|
|
|
|
if (priv->selected_child == NULL)
|
|
|
|
|
{
|
|
|
|
|
gtk_flow_box_child_set_selected (child, TRUE);
|
|
|
|
|
priv->selected_child = child;
|
|
|
|
|
}
|
|
|
|
|
else
|
2013-10-07 03:15:27 +00:00
|
|
|
|
gtk_flow_box_select_all_between (box, priv->selected_child, child, FALSE);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2013-10-07 03:15:27 +00:00
|
|
|
|
if (modify)
|
|
|
|
|
{
|
|
|
|
|
gtk_flow_box_child_set_selected (child, !CHILD_PRIV (child)->selected);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
gtk_flow_box_unselect_all_internal (box);
|
|
|
|
|
gtk_flow_box_child_set_selected (child, !CHILD_PRIV (child)->selected);
|
|
|
|
|
priv->selected_child = child;
|
|
|
|
|
}
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_select_and_activate (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxChild *child)
|
|
|
|
|
{
|
|
|
|
|
if (child != NULL)
|
|
|
|
|
{
|
2013-10-07 03:15:27 +00:00
|
|
|
|
gtk_flow_box_select_child_internal (box, child);
|
|
|
|
|
gtk_flow_box_update_cursor (box, child);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
g_signal_emit (box, signals[CHILD_ACTIVATED], 0, child);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Focus utilities {{{3 */
|
|
|
|
|
|
|
|
|
|
static GSequenceIter *
|
|
|
|
|
gtk_flow_box_get_previous_focusable (GtkFlowBox *box,
|
|
|
|
|
GSequenceIter *iter)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxChild *child;
|
|
|
|
|
|
|
|
|
|
while (!g_sequence_iter_is_begin (iter))
|
|
|
|
|
{
|
|
|
|
|
iter = g_sequence_iter_prev (iter);
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
if (child_is_visible (GTK_WIDGET (child)) &&
|
|
|
|
|
gtk_widget_is_sensitive (GTK_WIDGET (child)))
|
|
|
|
|
return iter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GSequenceIter *
|
|
|
|
|
gtk_flow_box_get_next_focusable (GtkFlowBox *box,
|
|
|
|
|
GSequenceIter *iter)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxChild *child;
|
|
|
|
|
|
|
|
|
|
while (TRUE)
|
|
|
|
|
{
|
|
|
|
|
iter = g_sequence_iter_next (iter);
|
|
|
|
|
if (g_sequence_iter_is_end (iter))
|
2013-10-21 14:32:49 +00:00
|
|
|
|
return NULL;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
if (child_is_visible (GTK_WIDGET (child)) &&
|
|
|
|
|
gtk_widget_is_sensitive (GTK_WIDGET (child)))
|
|
|
|
|
return iter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GSequenceIter *
|
|
|
|
|
gtk_flow_box_get_first_focusable (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
GtkFlowBoxChild *child;
|
|
|
|
|
|
|
|
|
|
iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
2013-10-21 14:21:11 +00:00
|
|
|
|
if (g_sequence_iter_is_end (iter))
|
|
|
|
|
return NULL;
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
if (child_is_visible (GTK_WIDGET (child)) &&
|
|
|
|
|
gtk_widget_is_sensitive (GTK_WIDGET (child)))
|
|
|
|
|
return iter;
|
|
|
|
|
|
|
|
|
|
return gtk_flow_box_get_next_focusable (box, iter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GSequenceIter *
|
|
|
|
|
gtk_flow_box_get_last_focusable (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
|
|
|
|
|
iter = g_sequence_get_end_iter (BOX_PRIV (box)->children);
|
|
|
|
|
return gtk_flow_box_get_previous_focusable (box, iter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static GSequenceIter *
|
|
|
|
|
gtk_flow_box_get_above_focusable (GtkFlowBox *box,
|
|
|
|
|
GSequenceIter *iter)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxChild *child = NULL;
|
|
|
|
|
gint i;
|
|
|
|
|
|
|
|
|
|
while (TRUE)
|
|
|
|
|
{
|
|
|
|
|
i = 0;
|
|
|
|
|
while (i < BOX_PRIV (box)->cur_children_per_line)
|
|
|
|
|
{
|
|
|
|
|
if (g_sequence_iter_is_begin (iter))
|
|
|
|
|
return NULL;
|
|
|
|
|
iter = g_sequence_iter_prev (iter);
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
if (child_is_visible (GTK_WIDGET (child)))
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
if (child && gtk_widget_get_sensitive (GTK_WIDGET (child)))
|
|
|
|
|
return iter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GSequenceIter *
|
|
|
|
|
gtk_flow_box_get_below_focusable (GtkFlowBox *box,
|
|
|
|
|
GSequenceIter *iter)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxChild *child = NULL;
|
|
|
|
|
gint i;
|
|
|
|
|
|
|
|
|
|
while (TRUE)
|
|
|
|
|
{
|
|
|
|
|
i = 0;
|
|
|
|
|
while (i < BOX_PRIV (box)->cur_children_per_line)
|
|
|
|
|
{
|
|
|
|
|
iter = g_sequence_iter_next (iter);
|
|
|
|
|
if (g_sequence_iter_is_end (iter))
|
2013-10-21 14:32:49 +00:00
|
|
|
|
return NULL;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
if (child_is_visible (GTK_WIDGET (child)))
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
if (child && gtk_widget_get_sensitive (GTK_WIDGET (child)))
|
|
|
|
|
return iter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* GtkWidget implementation {{{2 */
|
|
|
|
|
|
|
|
|
|
/* Size allocation {{{3 */
|
|
|
|
|
|
|
|
|
|
/* Used in columned modes where all items share at least their
|
|
|
|
|
* equal widths or heights
|
|
|
|
|
*/
|
|
|
|
|
static void
|
|
|
|
|
get_max_item_size (GtkFlowBox *box,
|
|
|
|
|
GtkOrientation orientation,
|
|
|
|
|
gint *min_size,
|
|
|
|
|
gint *nat_size)
|
|
|
|
|
{
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
gint max_min_size = 0;
|
|
|
|
|
gint max_nat_size = 0;
|
|
|
|
|
|
|
|
|
|
for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
|
|
|
|
!g_sequence_iter_is_end (iter);
|
|
|
|
|
iter = g_sequence_iter_next (iter))
|
|
|
|
|
{
|
|
|
|
|
GtkWidget *child;
|
|
|
|
|
gint child_min, child_nat;
|
|
|
|
|
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
|
|
|
|
|
if (!child_is_visible (child))
|
|
|
|
|
continue;
|
|
|
|
|
|
2017-01-22 14:01:40 +00:00
|
|
|
|
gtk_widget_measure (child, orientation, -1,
|
|
|
|
|
&child_min, &child_nat,
|
|
|
|
|
NULL, NULL);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
max_min_size = MAX (max_min_size, child_min);
|
|
|
|
|
max_nat_size = MAX (max_nat_size, child_nat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (min_size)
|
|
|
|
|
*min_size = max_min_size;
|
|
|
|
|
|
|
|
|
|
if (nat_size)
|
|
|
|
|
*nat_size = max_nat_size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Gets the largest minimum/natural size for a given size (used to get
|
|
|
|
|
* the largest item heights for a fixed item width and the opposite)
|
|
|
|
|
*/
|
|
|
|
|
static void
|
|
|
|
|
get_largest_size_for_opposing_orientation (GtkFlowBox *box,
|
|
|
|
|
GtkOrientation orientation,
|
|
|
|
|
gint item_size,
|
|
|
|
|
gint *min_item_size,
|
|
|
|
|
gint *nat_item_size)
|
|
|
|
|
{
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
gint max_min_size = 0;
|
|
|
|
|
gint max_nat_size = 0;
|
|
|
|
|
|
|
|
|
|
for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
|
|
|
|
!g_sequence_iter_is_end (iter);
|
|
|
|
|
iter = g_sequence_iter_next (iter))
|
|
|
|
|
{
|
|
|
|
|
GtkWidget *child;
|
|
|
|
|
gint child_min, child_nat;
|
|
|
|
|
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
|
|
|
|
|
if (!child_is_visible (child))
|
|
|
|
|
continue;
|
|
|
|
|
|
2017-01-22 14:01:40 +00:00
|
|
|
|
gtk_widget_measure (child, 1 - orientation, item_size,
|
|
|
|
|
&child_min, &child_nat,
|
|
|
|
|
NULL, NULL);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
max_min_size = MAX (max_min_size, child_min);
|
|
|
|
|
max_nat_size = MAX (max_nat_size, child_nat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (min_item_size)
|
|
|
|
|
*min_item_size = max_min_size;
|
|
|
|
|
|
|
|
|
|
if (nat_item_size)
|
|
|
|
|
*nat_item_size = max_nat_size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Gets the largest minimum/natural size on a single line for a given size
|
|
|
|
|
* (used to get the largest line heights for a fixed item width and the opposite
|
|
|
|
|
* while iterating over a list of children, note the new index is returned)
|
|
|
|
|
*/
|
|
|
|
|
static GSequenceIter *
|
|
|
|
|
get_largest_size_for_line_in_opposing_orientation (GtkFlowBox *box,
|
|
|
|
|
GtkOrientation orientation,
|
|
|
|
|
GSequenceIter *cursor,
|
|
|
|
|
gint line_length,
|
|
|
|
|
GtkRequestedSize *item_sizes,
|
|
|
|
|
gint extra_pixels,
|
|
|
|
|
gint *min_item_size,
|
|
|
|
|
gint *nat_item_size)
|
|
|
|
|
{
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
gint max_min_size = 0;
|
|
|
|
|
gint max_nat_size = 0;
|
|
|
|
|
gint i;
|
|
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
|
for (iter = cursor;
|
|
|
|
|
!g_sequence_iter_is_end (iter) && i < line_length;
|
|
|
|
|
iter = g_sequence_iter_next (iter))
|
|
|
|
|
{
|
|
|
|
|
GtkWidget *child;
|
|
|
|
|
gint child_min, child_nat, this_item_size;
|
|
|
|
|
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
|
|
|
|
|
if (!child_is_visible (child))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
/* Distribute the extra pixels to the first children in the line
|
|
|
|
|
* (could be fancier and spread them out more evenly) */
|
|
|
|
|
this_item_size = item_sizes[i].minimum_size;
|
|
|
|
|
if (extra_pixels > 0 && ORIENTATION_ALIGN (box) == GTK_ALIGN_FILL)
|
|
|
|
|
{
|
|
|
|
|
this_item_size++;
|
|
|
|
|
extra_pixels--;
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-22 14:01:40 +00:00
|
|
|
|
gtk_widget_measure (child, 1 - orientation, this_item_size,
|
|
|
|
|
&child_min, &child_nat,
|
|
|
|
|
NULL, NULL);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
max_min_size = MAX (max_min_size, child_min);
|
|
|
|
|
max_nat_size = MAX (max_nat_size, child_nat);
|
|
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (min_item_size)
|
|
|
|
|
*min_item_size = max_min_size;
|
|
|
|
|
|
|
|
|
|
if (nat_item_size)
|
|
|
|
|
*nat_item_size = max_nat_size;
|
|
|
|
|
|
|
|
|
|
/* Return next item in the list */
|
|
|
|
|
return iter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* fit_aligned_item_requests() helper */
|
|
|
|
|
static gint
|
|
|
|
|
gather_aligned_item_requests (GtkFlowBox *box,
|
|
|
|
|
GtkOrientation orientation,
|
|
|
|
|
gint line_length,
|
|
|
|
|
gint item_spacing,
|
|
|
|
|
gint n_children,
|
|
|
|
|
GtkRequestedSize *item_sizes)
|
|
|
|
|
{
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
gint i;
|
|
|
|
|
gint extra_items, natural_line_size = 0;
|
|
|
|
|
|
|
|
|
|
extra_items = n_children % line_length;
|
|
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
|
for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
|
|
|
|
!g_sequence_iter_is_end (iter);
|
|
|
|
|
iter = g_sequence_iter_next (iter))
|
|
|
|
|
{
|
|
|
|
|
GtkWidget *child;
|
|
|
|
|
GtkAlign item_align;
|
|
|
|
|
gint child_min, child_nat;
|
|
|
|
|
gint position;
|
|
|
|
|
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
|
|
|
|
|
if (!child_is_visible (child))
|
|
|
|
|
continue;
|
|
|
|
|
|
2017-01-22 14:01:40 +00:00
|
|
|
|
gtk_widget_measure (child, orientation, -1,
|
|
|
|
|
&child_min, &child_nat,
|
|
|
|
|
NULL, NULL);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
/* Get the index and push it over for the last line when spreading to the end */
|
|
|
|
|
position = i % line_length;
|
|
|
|
|
|
|
|
|
|
item_align = ORIENTATION_ALIGN (box);
|
|
|
|
|
if (item_align == GTK_ALIGN_END && i >= n_children - extra_items)
|
|
|
|
|
position += line_length - extra_items;
|
|
|
|
|
|
|
|
|
|
/* Round up the size of every column/row */
|
|
|
|
|
item_sizes[position].minimum_size = MAX (item_sizes[position].minimum_size, child_min);
|
|
|
|
|
item_sizes[position].natural_size = MAX (item_sizes[position].natural_size, child_nat);
|
|
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < line_length; i++)
|
|
|
|
|
natural_line_size += item_sizes[i].natural_size;
|
|
|
|
|
|
|
|
|
|
natural_line_size += (line_length - 1) * item_spacing;
|
|
|
|
|
|
|
|
|
|
return natural_line_size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GtkRequestedSize *
|
|
|
|
|
fit_aligned_item_requests (GtkFlowBox *box,
|
|
|
|
|
GtkOrientation orientation,
|
|
|
|
|
gint avail_size,
|
|
|
|
|
gint item_spacing,
|
|
|
|
|
gint *line_length, /* in-out */
|
|
|
|
|
gint items_per_line,
|
|
|
|
|
gint n_children)
|
|
|
|
|
{
|
|
|
|
|
GtkRequestedSize *sizes, *try_sizes;
|
|
|
|
|
gint try_line_size, try_length;
|
|
|
|
|
|
|
|
|
|
sizes = g_new0 (GtkRequestedSize, *line_length);
|
|
|
|
|
|
|
|
|
|
/* get the sizes for the initial guess */
|
|
|
|
|
try_line_size = gather_aligned_item_requests (box,
|
|
|
|
|
orientation,
|
|
|
|
|
*line_length,
|
|
|
|
|
item_spacing,
|
|
|
|
|
n_children,
|
|
|
|
|
sizes);
|
|
|
|
|
|
|
|
|
|
/* Try columnizing the whole thing and adding an item to the end of
|
|
|
|
|
* the line; try to fit as many columns into the available size as
|
|
|
|
|
* possible
|
|
|
|
|
*/
|
|
|
|
|
for (try_length = *line_length + 1; try_line_size < avail_size; try_length++)
|
|
|
|
|
{
|
|
|
|
|
try_sizes = g_new0 (GtkRequestedSize, try_length);
|
|
|
|
|
try_line_size = gather_aligned_item_requests (box,
|
|
|
|
|
orientation,
|
|
|
|
|
try_length,
|
|
|
|
|
item_spacing,
|
|
|
|
|
n_children,
|
|
|
|
|
try_sizes);
|
|
|
|
|
|
|
|
|
|
if (try_line_size <= avail_size &&
|
|
|
|
|
items_per_line >= try_length)
|
|
|
|
|
{
|
|
|
|
|
*line_length = try_length;
|
|
|
|
|
|
|
|
|
|
g_free (sizes);
|
|
|
|
|
sizes = try_sizes;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* oops, this one failed; stick to the last size that fit and then return */
|
|
|
|
|
g_free (try_sizes);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sizes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
GArray *requested;
|
|
|
|
|
gint extra_pixels;
|
|
|
|
|
} AllocatedLine;
|
|
|
|
|
|
|
|
|
|
static gint
|
|
|
|
|
get_offset_pixels (GtkAlign align,
|
|
|
|
|
gint pixels)
|
|
|
|
|
{
|
|
|
|
|
gint offset;
|
|
|
|
|
|
|
|
|
|
switch (align) {
|
|
|
|
|
case GTK_ALIGN_START:
|
|
|
|
|
case GTK_ALIGN_FILL:
|
|
|
|
|
offset = 0;
|
|
|
|
|
break;
|
|
|
|
|
case GTK_ALIGN_CENTER:
|
|
|
|
|
offset = pixels / 2;
|
|
|
|
|
break;
|
|
|
|
|
case GTK_ALIGN_END:
|
|
|
|
|
offset = pixels;
|
|
|
|
|
break;
|
2017-10-06 19:19:42 +00:00
|
|
|
|
case GTK_ALIGN_BASELINE:
|
2013-09-29 17:43:27 +00:00
|
|
|
|
default:
|
|
|
|
|
g_assert_not_reached ();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return offset;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2018-08-16 04:53:03 +00:00
|
|
|
|
gtk_flow_box_size_allocate (GtkWidget *widget,
|
|
|
|
|
int width,
|
|
|
|
|
int height,
|
|
|
|
|
int baseline)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
|
|
|
|
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
GtkAllocation child_allocation;
|
|
|
|
|
gint avail_size, avail_other_size, min_items, item_spacing, line_spacing;
|
|
|
|
|
GtkAlign item_align;
|
|
|
|
|
GtkAlign line_align;
|
|
|
|
|
GtkRequestedSize *line_sizes = NULL;
|
|
|
|
|
GtkRequestedSize *item_sizes = NULL;
|
|
|
|
|
gint min_item_size, nat_item_size;
|
|
|
|
|
gint line_length;
|
|
|
|
|
gint item_size = 0;
|
|
|
|
|
gint line_size = 0, min_fixed_line_size = 0, nat_fixed_line_size = 0;
|
|
|
|
|
gint line_offset, item_offset, n_children, n_lines, line_count;
|
|
|
|
|
gint extra_pixels = 0, extra_per_item = 0, extra_extra = 0;
|
|
|
|
|
gint extra_line_pixels = 0, extra_per_line = 0, extra_line_extra = 0;
|
|
|
|
|
gint i, this_line_size;
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
|
|
|
|
|
min_items = MAX (1, priv->min_children_per_line);
|
|
|
|
|
|
|
|
|
|
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
|
|
|
{
|
2018-08-16 04:53:03 +00:00
|
|
|
|
avail_size = width;
|
|
|
|
|
avail_other_size = height;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
item_spacing = priv->column_spacing; line_spacing = priv->row_spacing;
|
|
|
|
|
}
|
|
|
|
|
else /* GTK_ORIENTATION_VERTICAL */
|
|
|
|
|
{
|
2018-08-16 04:53:03 +00:00
|
|
|
|
avail_size = height;
|
|
|
|
|
avail_other_size = width;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
item_spacing = priv->row_spacing;
|
|
|
|
|
line_spacing = priv->column_spacing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
item_align = ORIENTATION_ALIGN (box);
|
|
|
|
|
line_align = OPPOSING_ORIENTATION_ALIGN (box);
|
|
|
|
|
|
|
|
|
|
/* Get how many lines we'll be needing to flow */
|
|
|
|
|
n_children = get_visible_children (box);
|
|
|
|
|
if (n_children <= 0)
|
|
|
|
|
return;
|
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
/* Deal with ALIGNED/HOMOGENEOUS modes first, start with
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* initial guesses at item/line sizes
|
|
|
|
|
*/
|
|
|
|
|
get_max_item_size (box, priv->orientation, &min_item_size, &nat_item_size);
|
|
|
|
|
if (nat_item_size <= 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
/* By default flow at the natural item width */
|
|
|
|
|
line_length = avail_size / (nat_item_size + item_spacing);
|
|
|
|
|
|
|
|
|
|
/* After the above aproximation, check if we cant fit one more on the line */
|
|
|
|
|
if (line_length * item_spacing + (line_length + 1) * nat_item_size <= avail_size)
|
|
|
|
|
line_length++;
|
|
|
|
|
|
|
|
|
|
/* Its possible we were allocated just less than the natural width of the
|
2015-12-13 00:41:40 +00:00
|
|
|
|
* minimum item flow length
|
|
|
|
|
*/
|
2013-09-29 17:43:27 +00:00
|
|
|
|
line_length = MAX (min_items, line_length);
|
|
|
|
|
line_length = MIN (line_length, priv->max_children_per_line);
|
|
|
|
|
|
|
|
|
|
/* Here we just use the largest height-for-width and use that for the height
|
2015-12-13 00:41:40 +00:00
|
|
|
|
* of all lines
|
|
|
|
|
*/
|
2013-09-29 17:43:27 +00:00
|
|
|
|
if (priv->homogeneous)
|
|
|
|
|
{
|
|
|
|
|
n_lines = n_children / line_length;
|
|
|
|
|
if ((n_children % line_length) > 0)
|
|
|
|
|
n_lines++;
|
|
|
|
|
|
|
|
|
|
n_lines = MAX (n_lines, 1);
|
|
|
|
|
|
|
|
|
|
/* Now we need the real item allocation size */
|
|
|
|
|
item_size = (avail_size - (line_length - 1) * item_spacing) / line_length;
|
|
|
|
|
|
|
|
|
|
/* Cut out the expand space if we're not distributing any */
|
|
|
|
|
if (item_align != GTK_ALIGN_FILL)
|
|
|
|
|
item_size = MIN (item_size, nat_item_size);
|
|
|
|
|
|
|
|
|
|
get_largest_size_for_opposing_orientation (box,
|
|
|
|
|
priv->orientation,
|
|
|
|
|
item_size,
|
|
|
|
|
&min_fixed_line_size,
|
|
|
|
|
&nat_fixed_line_size);
|
|
|
|
|
|
|
|
|
|
/* resolve a fixed 'line_size' */
|
|
|
|
|
line_size = (avail_other_size - (n_lines - 1) * line_spacing) / n_lines;
|
|
|
|
|
|
|
|
|
|
if (line_align != GTK_ALIGN_FILL)
|
|
|
|
|
line_size = MIN (line_size, nat_fixed_line_size);
|
|
|
|
|
|
|
|
|
|
/* Get the real extra pixels incase of GTK_ALIGN_START lines */
|
2015-12-13 00:41:40 +00:00
|
|
|
|
extra_pixels = avail_size - (line_length - 1) * item_spacing - item_size * line_length;
|
|
|
|
|
extra_line_pixels = avail_other_size - (n_lines - 1) * line_spacing - line_size * n_lines;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
gboolean first_line = TRUE;
|
|
|
|
|
|
|
|
|
|
/* Find the amount of columns that can fit aligned into the available space
|
|
|
|
|
* and collect their requests.
|
|
|
|
|
*/
|
|
|
|
|
item_sizes = fit_aligned_item_requests (box,
|
|
|
|
|
priv->orientation,
|
|
|
|
|
avail_size,
|
|
|
|
|
item_spacing,
|
|
|
|
|
&line_length,
|
|
|
|
|
priv->max_children_per_line,
|
|
|
|
|
n_children);
|
|
|
|
|
|
|
|
|
|
/* Calculate the number of lines after determining the final line_length */
|
|
|
|
|
n_lines = n_children / line_length;
|
|
|
|
|
if ((n_children % line_length) > 0)
|
|
|
|
|
n_lines++;
|
|
|
|
|
|
|
|
|
|
n_lines = MAX (n_lines, 1);
|
|
|
|
|
line_sizes = g_new0 (GtkRequestedSize, n_lines);
|
|
|
|
|
|
|
|
|
|
/* Get the available remaining size */
|
|
|
|
|
avail_size -= (line_length - 1) * item_spacing;
|
|
|
|
|
for (i = 0; i < line_length; i++)
|
|
|
|
|
avail_size -= item_sizes[i].minimum_size;
|
|
|
|
|
|
|
|
|
|
/* Perform a natural allocation on the columnized items and get the remaining pixels */
|
|
|
|
|
if (avail_size > 0)
|
|
|
|
|
extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes);
|
|
|
|
|
|
|
|
|
|
/* Now that we have the size of each column of items find the size of each individual
|
|
|
|
|
* line based on the aligned item sizes.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
for (i = 0, iter = g_sequence_get_begin_iter (priv->children);
|
|
|
|
|
!g_sequence_iter_is_end (iter) && i < n_lines;
|
|
|
|
|
i++)
|
|
|
|
|
{
|
|
|
|
|
iter = get_largest_size_for_line_in_opposing_orientation (box,
|
|
|
|
|
priv->orientation,
|
|
|
|
|
iter,
|
|
|
|
|
line_length,
|
|
|
|
|
item_sizes,
|
|
|
|
|
extra_pixels,
|
|
|
|
|
&line_sizes[i].minimum_size,
|
|
|
|
|
&line_sizes[i].natural_size);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Its possible a line is made of completely invisible children */
|
|
|
|
|
if (line_sizes[i].natural_size > 0)
|
|
|
|
|
{
|
|
|
|
|
if (first_line)
|
|
|
|
|
first_line = FALSE;
|
|
|
|
|
else
|
|
|
|
|
avail_other_size -= line_spacing;
|
|
|
|
|
|
|
|
|
|
avail_other_size -= line_sizes[i].minimum_size;
|
|
|
|
|
|
|
|
|
|
line_sizes[i].data = GINT_TO_POINTER (i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Distribute space among lines naturally */
|
|
|
|
|
if (avail_other_size > 0)
|
|
|
|
|
extra_line_pixels = gtk_distribute_natural_allocation (avail_other_size, n_lines, line_sizes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Initial sizes of items/lines guessed at this point,
|
|
|
|
|
* go on to distribute expand space if needed.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
priv->cur_children_per_line = line_length;
|
|
|
|
|
|
|
|
|
|
/* FIXME: This portion needs to consider which columns
|
|
|
|
|
* and rows asked for expand space and distribute those
|
|
|
|
|
* accordingly for the case of ALIGNED allocation.
|
|
|
|
|
*
|
|
|
|
|
* If at least one child in a column/row asked for expand;
|
|
|
|
|
* we should make that row/column expand entirely.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* Calculate expand space per item */
|
|
|
|
|
if (item_align == GTK_ALIGN_FILL)
|
|
|
|
|
{
|
|
|
|
|
extra_per_item = extra_pixels / line_length;
|
|
|
|
|
extra_extra = extra_pixels % line_length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Calculate expand space per line */
|
|
|
|
|
if (line_align == GTK_ALIGN_FILL)
|
|
|
|
|
{
|
|
|
|
|
extra_per_line = extra_line_pixels / n_lines;
|
|
|
|
|
extra_line_extra = extra_line_pixels % n_lines;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* prepend extra space to item_offset/line_offset for SPREAD_END */
|
2017-11-05 13:46:01 +00:00
|
|
|
|
item_offset = get_offset_pixels (item_align, extra_pixels);
|
|
|
|
|
line_offset = get_offset_pixels (line_align, extra_line_pixels);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
/* Get the allocation size for the first line */
|
|
|
|
|
if (priv->homogeneous)
|
|
|
|
|
this_line_size = line_size;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
this_line_size = line_sizes[0].minimum_size;
|
|
|
|
|
|
|
|
|
|
if (line_align == GTK_ALIGN_FILL)
|
|
|
|
|
{
|
|
|
|
|
this_line_size += extra_per_line;
|
|
|
|
|
|
|
|
|
|
if (extra_line_extra > 0)
|
|
|
|
|
this_line_size++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
|
line_count = 0;
|
|
|
|
|
for (iter = g_sequence_get_begin_iter (priv->children);
|
|
|
|
|
!g_sequence_iter_is_end (iter);
|
|
|
|
|
iter = g_sequence_iter_next (iter))
|
|
|
|
|
{
|
|
|
|
|
GtkWidget *child;
|
|
|
|
|
gint position;
|
|
|
|
|
gint this_item_size;
|
|
|
|
|
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
|
|
|
|
|
if (!child_is_visible (child))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
/* Get item position */
|
|
|
|
|
position = i % line_length;
|
|
|
|
|
|
|
|
|
|
/* adjust the line_offset/count at the beginning of each new line */
|
|
|
|
|
if (i > 0 && position == 0)
|
|
|
|
|
{
|
|
|
|
|
/* Push the line_offset */
|
|
|
|
|
line_offset += this_line_size + line_spacing;
|
|
|
|
|
|
|
|
|
|
line_count++;
|
|
|
|
|
|
|
|
|
|
/* Get the new line size */
|
|
|
|
|
if (priv->homogeneous)
|
|
|
|
|
this_line_size = line_size;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
this_line_size = line_sizes[line_count].minimum_size;
|
|
|
|
|
|
|
|
|
|
if (line_align == GTK_ALIGN_FILL)
|
|
|
|
|
{
|
|
|
|
|
this_line_size += extra_per_line;
|
|
|
|
|
|
|
|
|
|
if (line_count < extra_line_extra)
|
|
|
|
|
this_line_size++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-05 13:46:01 +00:00
|
|
|
|
item_offset = 0;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
if (item_align == GTK_ALIGN_CENTER)
|
|
|
|
|
{
|
|
|
|
|
item_offset += get_offset_pixels (item_align, extra_pixels);
|
|
|
|
|
}
|
|
|
|
|
else if (item_align == GTK_ALIGN_END)
|
|
|
|
|
{
|
|
|
|
|
item_offset += get_offset_pixels (item_align, extra_pixels);
|
|
|
|
|
|
|
|
|
|
/* If we're on the last line, prepend the space for
|
|
|
|
|
* any leading items */
|
|
|
|
|
if (line_count == n_lines -1)
|
|
|
|
|
{
|
|
|
|
|
gint extra_items = n_children % line_length;
|
|
|
|
|
|
|
|
|
|
if (priv->homogeneous)
|
|
|
|
|
{
|
|
|
|
|
item_offset += item_size * (line_length - extra_items);
|
|
|
|
|
item_offset += item_spacing * (line_length - extra_items);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
gint j;
|
|
|
|
|
|
|
|
|
|
for (j = 0; j < (line_length - extra_items); j++)
|
|
|
|
|
{
|
|
|
|
|
item_offset += item_sizes[j].minimum_size;
|
|
|
|
|
item_offset += item_spacing;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Push the index along for the last line when spreading to the end */
|
|
|
|
|
if (item_align == GTK_ALIGN_END && line_count == n_lines -1)
|
|
|
|
|
{
|
|
|
|
|
gint extra_items = n_children % line_length;
|
|
|
|
|
|
|
|
|
|
position += line_length - extra_items;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (priv->homogeneous)
|
|
|
|
|
this_item_size = item_size;
|
|
|
|
|
else
|
|
|
|
|
this_item_size = item_sizes[position].minimum_size;
|
|
|
|
|
|
|
|
|
|
if (item_align == GTK_ALIGN_FILL)
|
|
|
|
|
{
|
|
|
|
|
this_item_size += extra_per_item;
|
|
|
|
|
|
|
|
|
|
if (position < extra_extra)
|
|
|
|
|
this_item_size++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Do the actual allocation */
|
|
|
|
|
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
|
|
|
{
|
2017-11-05 13:46:01 +00:00
|
|
|
|
child_allocation.x = item_offset;
|
|
|
|
|
child_allocation.y = line_offset;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
child_allocation.width = this_item_size;
|
|
|
|
|
child_allocation.height = this_line_size;
|
|
|
|
|
}
|
|
|
|
|
else /* GTK_ORIENTATION_VERTICAL */
|
|
|
|
|
{
|
2017-11-05 13:46:01 +00:00
|
|
|
|
child_allocation.x = line_offset;
|
|
|
|
|
child_allocation.y = item_offset;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
child_allocation.width = this_line_size;
|
|
|
|
|
child_allocation.height = this_item_size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
|
2018-08-16 04:53:03 +00:00
|
|
|
|
child_allocation.x = width - child_allocation.x - child_allocation.width;
|
2017-06-25 16:51:23 +00:00
|
|
|
|
|
2018-03-31 19:02:28 +00:00
|
|
|
|
gtk_widget_size_allocate (child, &child_allocation, -1);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
item_offset += this_item_size;
|
|
|
|
|
item_offset += item_spacing;
|
|
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_free (item_sizes);
|
|
|
|
|
g_free (line_sizes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GtkSizeRequestMode
|
|
|
|
|
gtk_flow_box_get_request_mode (GtkWidget *widget)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
|
|
|
|
|
|
|
|
|
return (BOX_PRIV (box)->orientation == GTK_ORIENTATION_HORIZONTAL) ?
|
|
|
|
|
GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH : GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Gets the largest minimum and natural length of
|
|
|
|
|
* 'line_length' consecutive items when aligned into rows/columns */
|
|
|
|
|
static void
|
|
|
|
|
get_largest_aligned_line_length (GtkFlowBox *box,
|
|
|
|
|
GtkOrientation orientation,
|
|
|
|
|
gint line_length,
|
|
|
|
|
gint *min_size,
|
|
|
|
|
gint *nat_size)
|
|
|
|
|
{
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
gint max_min_size = 0;
|
|
|
|
|
gint max_nat_size = 0;
|
|
|
|
|
gint spacing, i;
|
|
|
|
|
GtkRequestedSize *aligned_item_sizes;
|
|
|
|
|
|
|
|
|
|
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
|
|
|
spacing = BOX_PRIV (box)->column_spacing;
|
|
|
|
|
else
|
|
|
|
|
spacing = BOX_PRIV (box)->row_spacing;
|
|
|
|
|
|
|
|
|
|
aligned_item_sizes = g_new0 (GtkRequestedSize, line_length);
|
|
|
|
|
|
|
|
|
|
/* Get the largest sizes of each index in the line.
|
|
|
|
|
*/
|
|
|
|
|
i = 0;
|
|
|
|
|
for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
|
|
|
|
!g_sequence_iter_is_end (iter);
|
|
|
|
|
iter = g_sequence_iter_next (iter))
|
|
|
|
|
{
|
|
|
|
|
GtkWidget *child;
|
|
|
|
|
gint child_min, child_nat;
|
|
|
|
|
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
if (!child_is_visible (child))
|
|
|
|
|
continue;
|
|
|
|
|
|
2017-01-22 14:01:40 +00:00
|
|
|
|
gtk_widget_measure (child, orientation, -1,
|
|
|
|
|
&child_min, &child_nat,
|
|
|
|
|
NULL, NULL);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
aligned_item_sizes[i % line_length].minimum_size =
|
|
|
|
|
MAX (aligned_item_sizes[i % line_length].minimum_size, child_min);
|
|
|
|
|
|
|
|
|
|
aligned_item_sizes[i % line_length].natural_size =
|
|
|
|
|
MAX (aligned_item_sizes[i % line_length].natural_size, child_nat);
|
|
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Add up the largest indexes */
|
|
|
|
|
for (i = 0; i < line_length; i++)
|
|
|
|
|
{
|
|
|
|
|
max_min_size += aligned_item_sizes[i].minimum_size;
|
|
|
|
|
max_nat_size += aligned_item_sizes[i].natural_size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_free (aligned_item_sizes);
|
|
|
|
|
|
|
|
|
|
max_min_size += (line_length - 1) * spacing;
|
|
|
|
|
max_nat_size += (line_length - 1) * spacing;
|
|
|
|
|
|
|
|
|
|
if (min_size)
|
|
|
|
|
*min_size = max_min_size;
|
|
|
|
|
|
|
|
|
|
if (nat_size)
|
|
|
|
|
*nat_size = max_nat_size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2017-05-05 07:27:06 +00:00
|
|
|
|
gtk_flow_box_measure (GtkWidget *widget,
|
2016-10-22 14:06:14 +00:00
|
|
|
|
GtkOrientation orientation,
|
|
|
|
|
int for_size,
|
|
|
|
|
int *minimum,
|
|
|
|
|
int *natural,
|
|
|
|
|
int *minimum_baseline,
|
|
|
|
|
int *natural_baseline)
|
2015-12-13 00:41:40 +00:00
|
|
|
|
{
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
2015-12-13 00:41:40 +00:00
|
|
|
|
if (for_size < 0)
|
|
|
|
|
{
|
|
|
|
|
gint min_item_width, nat_item_width;
|
|
|
|
|
gint min_items, nat_items;
|
|
|
|
|
gint min_width, nat_width;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
min_items = MAX (1, priv->min_children_per_line);
|
|
|
|
|
nat_items = MAX (min_items, priv->max_children_per_line);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
2015-12-13 00:41:40 +00:00
|
|
|
|
min_width = nat_width = 0;
|
|
|
|
|
|
|
|
|
|
if (!priv->homogeneous)
|
|
|
|
|
{
|
|
|
|
|
/* When not homogeneous; horizontally oriented boxes
|
|
|
|
|
* need enough width for the widest row
|
|
|
|
|
*/
|
|
|
|
|
if (min_items == 1)
|
|
|
|
|
{
|
|
|
|
|
get_max_item_size (box,
|
|
|
|
|
GTK_ORIENTATION_HORIZONTAL,
|
|
|
|
|
&min_item_width,
|
|
|
|
|
&nat_item_width);
|
|
|
|
|
|
|
|
|
|
min_width += min_item_width;
|
|
|
|
|
nat_width += nat_item_width;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
gint min_line_length, nat_line_length;
|
|
|
|
|
|
|
|
|
|
get_largest_aligned_line_length (box,
|
|
|
|
|
GTK_ORIENTATION_HORIZONTAL,
|
|
|
|
|
min_items,
|
|
|
|
|
&min_line_length,
|
|
|
|
|
&nat_line_length);
|
|
|
|
|
|
|
|
|
|
if (nat_items > min_items)
|
|
|
|
|
get_largest_aligned_line_length (box,
|
|
|
|
|
GTK_ORIENTATION_HORIZONTAL,
|
|
|
|
|
nat_items,
|
|
|
|
|
NULL,
|
|
|
|
|
&nat_line_length);
|
|
|
|
|
|
|
|
|
|
min_width += min_line_length;
|
|
|
|
|
nat_width += nat_line_length;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else /* In homogeneous mode; horizontally oriented boxs
|
|
|
|
|
* give the same width to all children */
|
|
|
|
|
{
|
|
|
|
|
get_max_item_size (box, GTK_ORIENTATION_HORIZONTAL,
|
|
|
|
|
&min_item_width, &nat_item_width);
|
|
|
|
|
|
|
|
|
|
min_width += min_item_width * min_items;
|
|
|
|
|
min_width += (min_items -1) * priv->column_spacing;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
nat_width += nat_item_width * nat_items;
|
|
|
|
|
nat_width += (nat_items -1) * priv->column_spacing;
|
|
|
|
|
}
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
2015-12-13 00:41:40 +00:00
|
|
|
|
else /* GTK_ORIENTATION_VERTICAL */
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
2015-12-13 00:41:40 +00:00
|
|
|
|
/* Return the width for the minimum height */
|
|
|
|
|
gint min_height;
|
2017-05-05 07:27:06 +00:00
|
|
|
|
int dummy;
|
|
|
|
|
|
|
|
|
|
gtk_flow_box_measure (widget,
|
|
|
|
|
GTK_ORIENTATION_VERTICAL,
|
|
|
|
|
-1,
|
|
|
|
|
&min_height, &dummy,
|
|
|
|
|
NULL, NULL);
|
|
|
|
|
gtk_flow_box_measure (widget,
|
|
|
|
|
GTK_ORIENTATION_HORIZONTAL,
|
|
|
|
|
min_height,
|
|
|
|
|
&min_width, &nat_width,
|
|
|
|
|
NULL, NULL);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
*minimum = min_width;
|
|
|
|
|
*natural = nat_width;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-12-13 00:41:40 +00:00
|
|
|
|
gint min_item_height, nat_item_height;
|
|
|
|
|
gint min_items;
|
|
|
|
|
gint min_width, nat_width;
|
|
|
|
|
gint avail_size, n_children;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
min_items = MAX (1, priv->min_children_per_line);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
min_width = 0;
|
|
|
|
|
nat_width = 0;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
|
|
|
{
|
|
|
|
|
/* Return the minimum width */
|
2017-05-05 07:27:06 +00:00
|
|
|
|
gtk_flow_box_measure (widget,
|
|
|
|
|
GTK_ORIENTATION_HORIZONTAL,
|
|
|
|
|
-1,
|
|
|
|
|
&min_width, &nat_width,
|
|
|
|
|
NULL, NULL);
|
2015-12-13 00:41:40 +00:00
|
|
|
|
}
|
|
|
|
|
else /* GTK_ORIENTATION_VERTICAL */
|
|
|
|
|
{
|
|
|
|
|
gint min_height;
|
|
|
|
|
gint line_length;
|
|
|
|
|
gint item_size, extra_pixels;
|
2017-05-05 07:27:06 +00:00
|
|
|
|
int dummy;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
n_children = get_visible_children (box);
|
|
|
|
|
if (n_children <= 0)
|
|
|
|
|
goto out_width;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
/* Make sure its no smaller than the minimum */
|
2017-05-05 07:27:06 +00:00
|
|
|
|
gtk_flow_box_measure (widget,
|
|
|
|
|
GTK_ORIENTATION_VERTICAL,
|
|
|
|
|
-1,
|
|
|
|
|
&min_height, &dummy,
|
|
|
|
|
NULL, NULL);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
avail_size = MAX (for_size, min_height);
|
|
|
|
|
if (avail_size <= 0)
|
|
|
|
|
goto out_width;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
get_max_item_size (box, GTK_ORIENTATION_VERTICAL, &min_item_height, &nat_item_height);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
/* By default flow at the natural item width */
|
|
|
|
|
line_length = avail_size / (nat_item_height + priv->row_spacing);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
/* After the above aproximation, check if we cant fit one more on the line */
|
|
|
|
|
if (line_length * priv->row_spacing + (line_length + 1) * nat_item_height <= avail_size)
|
|
|
|
|
line_length++;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
/* Its possible we were allocated just less than the natural width of the
|
|
|
|
|
* minimum item flow length
|
|
|
|
|
*/
|
|
|
|
|
line_length = MAX (min_items, line_length);
|
|
|
|
|
line_length = MIN (line_length, priv->max_children_per_line);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
/* Now we need the real item allocation size */
|
|
|
|
|
item_size = (avail_size - (line_length - 1) * priv->row_spacing) / line_length;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
/* Cut out the expand space if we're not distributing any */
|
|
|
|
|
if (gtk_widget_get_valign (widget) != GTK_ALIGN_FILL)
|
|
|
|
|
{
|
|
|
|
|
item_size = MIN (item_size, nat_item_height);
|
|
|
|
|
extra_pixels = 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
/* Collect the extra pixels for expand children */
|
|
|
|
|
extra_pixels = (avail_size - (line_length - 1) * priv->row_spacing) % line_length;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
if (priv->homogeneous)
|
|
|
|
|
{
|
|
|
|
|
gint min_item_width, nat_item_width;
|
|
|
|
|
gint lines;
|
|
|
|
|
|
|
|
|
|
/* Here we just use the largest height-for-width and
|
|
|
|
|
* add up the size accordingly
|
|
|
|
|
*/
|
|
|
|
|
get_largest_size_for_opposing_orientation (box,
|
|
|
|
|
GTK_ORIENTATION_VERTICAL,
|
|
|
|
|
item_size,
|
|
|
|
|
&min_item_width,
|
|
|
|
|
&nat_item_width);
|
|
|
|
|
|
|
|
|
|
/* Round up how many lines we need to allocate for */
|
|
|
|
|
n_children = get_visible_children (box);
|
|
|
|
|
lines = n_children / line_length;
|
|
|
|
|
if ((n_children % line_length) > 0)
|
|
|
|
|
lines++;
|
|
|
|
|
|
|
|
|
|
min_width = min_item_width * lines;
|
|
|
|
|
nat_width = nat_item_width * lines;
|
|
|
|
|
|
|
|
|
|
min_width += (lines - 1) * priv->column_spacing;
|
|
|
|
|
nat_width += (lines - 1) * priv->column_spacing;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
gint min_line_width, nat_line_width, i;
|
|
|
|
|
gboolean first_line = TRUE;
|
|
|
|
|
GtkRequestedSize *item_sizes;
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
|
|
|
|
|
/* First get the size each set of items take to span the line
|
|
|
|
|
* when aligning the items above and below after flowping.
|
|
|
|
|
*/
|
|
|
|
|
item_sizes = fit_aligned_item_requests (box,
|
|
|
|
|
priv->orientation,
|
|
|
|
|
avail_size,
|
|
|
|
|
priv->row_spacing,
|
|
|
|
|
&line_length,
|
|
|
|
|
priv->max_children_per_line,
|
|
|
|
|
n_children);
|
|
|
|
|
|
|
|
|
|
/* Get the available remaining size */
|
|
|
|
|
avail_size -= (line_length - 1) * priv->column_spacing;
|
|
|
|
|
for (i = 0; i < line_length; i++)
|
|
|
|
|
avail_size -= item_sizes[i].minimum_size;
|
|
|
|
|
|
|
|
|
|
if (avail_size > 0)
|
|
|
|
|
extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes);
|
|
|
|
|
|
|
|
|
|
for (iter = g_sequence_get_begin_iter (priv->children);
|
|
|
|
|
!g_sequence_iter_is_end (iter);)
|
|
|
|
|
{
|
|
|
|
|
iter = get_largest_size_for_line_in_opposing_orientation (box,
|
|
|
|
|
GTK_ORIENTATION_VERTICAL,
|
|
|
|
|
iter,
|
|
|
|
|
line_length,
|
|
|
|
|
item_sizes,
|
|
|
|
|
extra_pixels,
|
|
|
|
|
&min_line_width,
|
|
|
|
|
&nat_line_width);
|
|
|
|
|
|
|
|
|
|
/* Its possible the last line only had invisible widgets */
|
|
|
|
|
if (nat_line_width > 0)
|
|
|
|
|
{
|
|
|
|
|
if (first_line)
|
|
|
|
|
first_line = FALSE;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
min_width += priv->column_spacing;
|
|
|
|
|
nat_width += priv->column_spacing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
min_width += min_line_width;
|
|
|
|
|
nat_width += nat_line_width;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
g_free (item_sizes);
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
out_width:
|
|
|
|
|
*minimum = min_width;
|
|
|
|
|
*natural = nat_width;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
2015-12-13 00:41:40 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (for_size < 0)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
|
|
|
|
gint min_item_height, nat_item_height;
|
2015-12-13 00:41:40 +00:00
|
|
|
|
gint min_items, nat_items;
|
|
|
|
|
gint min_height, nat_height;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
min_items = MAX (1, priv->min_children_per_line);
|
|
|
|
|
nat_items = MAX (min_items, priv->max_children_per_line);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
|
|
|
{
|
|
|
|
|
/* Return the height for the minimum width */
|
|
|
|
|
gint min_width;
|
2017-05-05 07:27:06 +00:00
|
|
|
|
int dummy;
|
|
|
|
|
|
|
|
|
|
gtk_flow_box_measure (widget,
|
|
|
|
|
GTK_ORIENTATION_HORIZONTAL,
|
|
|
|
|
-1,
|
|
|
|
|
&min_width, &dummy,
|
|
|
|
|
NULL, NULL);
|
|
|
|
|
gtk_flow_box_measure (widget,
|
|
|
|
|
GTK_ORIENTATION_VERTICAL,
|
|
|
|
|
min_width,
|
|
|
|
|
&min_height, &nat_height,
|
|
|
|
|
NULL, NULL);
|
2015-12-13 00:41:40 +00:00
|
|
|
|
}
|
|
|
|
|
else /* GTK_ORIENTATION_VERTICAL */
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
2015-12-13 00:41:40 +00:00
|
|
|
|
min_height = nat_height = 0;
|
|
|
|
|
|
|
|
|
|
if (! priv->homogeneous)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
2015-12-13 00:41:40 +00:00
|
|
|
|
/* When not homogeneous; vertically oriented boxes
|
|
|
|
|
* need enough height for the tallest column
|
|
|
|
|
*/
|
|
|
|
|
if (min_items == 1)
|
|
|
|
|
{
|
|
|
|
|
get_max_item_size (box, GTK_ORIENTATION_VERTICAL,
|
|
|
|
|
&min_item_height, &nat_item_height);
|
|
|
|
|
|
|
|
|
|
min_height += min_item_height;
|
|
|
|
|
nat_height += nat_item_height;
|
|
|
|
|
}
|
2013-09-29 17:43:27 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
2015-12-13 00:41:40 +00:00
|
|
|
|
gint min_line_length, nat_line_length;
|
|
|
|
|
|
|
|
|
|
get_largest_aligned_line_length (box,
|
|
|
|
|
GTK_ORIENTATION_VERTICAL,
|
|
|
|
|
min_items,
|
|
|
|
|
&min_line_length,
|
|
|
|
|
&nat_line_length);
|
|
|
|
|
|
|
|
|
|
if (nat_items > min_items)
|
|
|
|
|
get_largest_aligned_line_length (box,
|
|
|
|
|
GTK_ORIENTATION_VERTICAL,
|
|
|
|
|
nat_items,
|
|
|
|
|
NULL,
|
|
|
|
|
&nat_line_length);
|
|
|
|
|
|
|
|
|
|
min_height += min_line_length;
|
|
|
|
|
nat_height += nat_line_length;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2015-12-13 00:41:40 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* In homogeneous mode; vertically oriented boxes
|
|
|
|
|
* give the same height to all children
|
|
|
|
|
*/
|
|
|
|
|
get_max_item_size (box,
|
|
|
|
|
GTK_ORIENTATION_VERTICAL,
|
|
|
|
|
&min_item_height,
|
|
|
|
|
&nat_item_height);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
min_height += min_item_height * min_items;
|
|
|
|
|
min_height += (min_items -1) * priv->row_spacing;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
nat_height += nat_item_height * nat_items;
|
|
|
|
|
nat_height += (nat_items -1) * priv->row_spacing;
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
*minimum = min_height;
|
|
|
|
|
*natural = nat_height;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
gint min_item_width, nat_item_width;
|
|
|
|
|
gint min_items;
|
|
|
|
|
gint min_height, nat_height;
|
|
|
|
|
gint avail_size, n_children;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
min_items = MAX (1, priv->min_children_per_line);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
min_height = 0;
|
|
|
|
|
nat_height = 0;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
|
|
|
{
|
|
|
|
|
gint min_width;
|
|
|
|
|
gint line_length;
|
|
|
|
|
gint item_size, extra_pixels;
|
2017-05-05 07:27:06 +00:00
|
|
|
|
int dummy;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
n_children = get_visible_children (box);
|
|
|
|
|
if (n_children <= 0)
|
|
|
|
|
goto out_height;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
/* Make sure its no smaller than the minimum */
|
2017-05-05 07:27:06 +00:00
|
|
|
|
gtk_flow_box_measure (widget,
|
|
|
|
|
GTK_ORIENTATION_HORIZONTAL,
|
|
|
|
|
-1,
|
|
|
|
|
&min_width, &dummy,
|
|
|
|
|
NULL, NULL);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
avail_size = MAX (for_size, min_width);
|
|
|
|
|
if (avail_size <= 0)
|
|
|
|
|
goto out_height;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
get_max_item_size (box, GTK_ORIENTATION_HORIZONTAL, &min_item_width, &nat_item_width);
|
|
|
|
|
if (nat_item_width <= 0)
|
|
|
|
|
goto out_height;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
/* By default flow at the natural item width */
|
|
|
|
|
line_length = avail_size / (nat_item_width + priv->column_spacing);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
/* After the above aproximation, check if we cant fit one more on the line */
|
|
|
|
|
if (line_length * priv->column_spacing + (line_length + 1) * nat_item_width <= avail_size)
|
|
|
|
|
line_length++;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
/* Its possible we were allocated just less than the natural width of the
|
|
|
|
|
* minimum item flow length
|
|
|
|
|
*/
|
|
|
|
|
line_length = MAX (min_items, line_length);
|
|
|
|
|
line_length = MIN (line_length, priv->max_children_per_line);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
/* Now we need the real item allocation size */
|
|
|
|
|
item_size = (avail_size - (line_length - 1) * priv->column_spacing) / line_length;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
/* Cut out the expand space if we're not distributing any */
|
|
|
|
|
if (gtk_widget_get_halign (widget) != GTK_ALIGN_FILL)
|
|
|
|
|
{
|
|
|
|
|
item_size = MIN (item_size, nat_item_width);
|
|
|
|
|
extra_pixels = 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
/* Collect the extra pixels for expand children */
|
|
|
|
|
extra_pixels = (avail_size - (line_length - 1) * priv->column_spacing) % line_length;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
if (priv->homogeneous)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
2015-12-13 00:41:40 +00:00
|
|
|
|
gint min_item_height, nat_item_height;
|
|
|
|
|
gint lines;
|
|
|
|
|
|
|
|
|
|
/* Here we just use the largest height-for-width and
|
|
|
|
|
* add up the size accordingly
|
|
|
|
|
*/
|
|
|
|
|
get_largest_size_for_opposing_orientation (box,
|
|
|
|
|
GTK_ORIENTATION_HORIZONTAL,
|
|
|
|
|
item_size,
|
|
|
|
|
&min_item_height,
|
|
|
|
|
&nat_item_height);
|
|
|
|
|
|
|
|
|
|
/* Round up how many lines we need to allocate for */
|
|
|
|
|
lines = n_children / line_length;
|
|
|
|
|
if ((n_children % line_length) > 0)
|
|
|
|
|
lines++;
|
|
|
|
|
|
|
|
|
|
min_height = min_item_height * lines;
|
|
|
|
|
nat_height = nat_item_height * lines;
|
|
|
|
|
|
|
|
|
|
min_height += (lines - 1) * priv->row_spacing;
|
|
|
|
|
nat_height += (lines - 1) * priv->row_spacing;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
gint min_line_height, nat_line_height, i;
|
|
|
|
|
gboolean first_line = TRUE;
|
|
|
|
|
GtkRequestedSize *item_sizes;
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
|
|
|
|
|
/* First get the size each set of items take to span the line
|
|
|
|
|
* when aligning the items above and below after flowping.
|
|
|
|
|
*/
|
|
|
|
|
item_sizes = fit_aligned_item_requests (box,
|
|
|
|
|
priv->orientation,
|
|
|
|
|
avail_size,
|
|
|
|
|
priv->column_spacing,
|
|
|
|
|
&line_length,
|
|
|
|
|
priv->max_children_per_line,
|
|
|
|
|
n_children);
|
|
|
|
|
|
|
|
|
|
/* Get the available remaining size */
|
|
|
|
|
avail_size -= (line_length - 1) * priv->column_spacing;
|
|
|
|
|
for (i = 0; i < line_length; i++)
|
|
|
|
|
avail_size -= item_sizes[i].minimum_size;
|
|
|
|
|
|
|
|
|
|
if (avail_size > 0)
|
|
|
|
|
extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes);
|
|
|
|
|
|
|
|
|
|
for (iter = g_sequence_get_begin_iter (priv->children);
|
|
|
|
|
!g_sequence_iter_is_end (iter);)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
2015-12-13 00:41:40 +00:00
|
|
|
|
iter = get_largest_size_for_line_in_opposing_orientation (box,
|
|
|
|
|
GTK_ORIENTATION_HORIZONTAL,
|
|
|
|
|
iter,
|
|
|
|
|
line_length,
|
|
|
|
|
item_sizes,
|
|
|
|
|
extra_pixels,
|
|
|
|
|
&min_line_height,
|
|
|
|
|
&nat_line_height);
|
|
|
|
|
/* Its possible the line only had invisible widgets */
|
|
|
|
|
if (nat_line_height > 0)
|
|
|
|
|
{
|
|
|
|
|
if (first_line)
|
|
|
|
|
first_line = FALSE;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
min_height += priv->row_spacing;
|
|
|
|
|
nat_height += priv->row_spacing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
min_height += min_line_height;
|
|
|
|
|
nat_height += nat_line_height;
|
|
|
|
|
}
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-13 00:41:40 +00:00
|
|
|
|
g_free (item_sizes);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2015-12-13 00:41:40 +00:00
|
|
|
|
else /* GTK_ORIENTATION_VERTICAL */
|
|
|
|
|
{
|
|
|
|
|
/* Return the minimum height */
|
2017-05-05 07:27:06 +00:00
|
|
|
|
gtk_flow_box_measure (widget,
|
|
|
|
|
GTK_ORIENTATION_VERTICAL,
|
|
|
|
|
-1,
|
|
|
|
|
&min_height, &nat_height,
|
|
|
|
|
NULL, NULL);
|
2015-12-13 00:41:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out_height:
|
|
|
|
|
*minimum = min_height;
|
|
|
|
|
*natural = nat_height;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Drawing {{{3 */
|
|
|
|
|
|
2016-11-15 15:11:19 +00:00
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_snapshot (GtkWidget *widget,
|
|
|
|
|
GtkSnapshot *snapshot)
|
2015-12-13 00:41:40 +00:00
|
|
|
|
{
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
2017-05-03 08:29:02 +00:00
|
|
|
|
int x, y, width, height;
|
2017-05-04 16:32:45 +00:00
|
|
|
|
|
2017-05-05 07:27:06 +00:00
|
|
|
|
GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->snapshot (widget, snapshot);
|
2016-08-18 06:39:08 +00:00
|
|
|
|
|
2017-06-18 13:45:42 +00:00
|
|
|
|
x = 0;
|
|
|
|
|
y = 0;
|
2017-12-06 06:56:12 +00:00
|
|
|
|
width = gtk_widget_get_width (widget);
|
|
|
|
|
height = gtk_widget_get_height (widget);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
if (priv->rubberband_first && priv->rubberband_last)
|
|
|
|
|
{
|
2015-12-13 00:41:40 +00:00
|
|
|
|
GtkStyleContext *context;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GSequenceIter *iter, *iter1, *iter2;
|
|
|
|
|
GdkRectangle line_rect, rect;
|
|
|
|
|
GArray *lines;
|
|
|
|
|
gboolean vertical;
|
2016-11-15 15:11:19 +00:00
|
|
|
|
cairo_t *cr;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2016-08-18 06:39:08 +00:00
|
|
|
|
vertical = priv->orientation == GTK_ORIENTATION_VERTICAL;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2017-01-13 03:46:09 +00:00
|
|
|
|
cr = gtk_snapshot_append_cairo (snapshot,
|
2018-04-24 01:17:23 +00:00
|
|
|
|
&GRAPHENE_RECT_INIT (x, y, width, height));
|
2016-11-15 15:11:19 +00:00
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
context = gtk_widget_get_style_context (widget);
|
2015-11-10 05:50:42 +00:00
|
|
|
|
gtk_style_context_save_to_node (context, priv->rubberband_node);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
iter1 = CHILD_PRIV (priv->rubberband_first)->iter;
|
|
|
|
|
iter2 = CHILD_PRIV (priv->rubberband_last)->iter;
|
|
|
|
|
|
|
|
|
|
if (g_sequence_iter_compare (iter2, iter1) < 0)
|
|
|
|
|
{
|
|
|
|
|
iter = iter1;
|
|
|
|
|
iter1 = iter2;
|
|
|
|
|
iter2 = iter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
line_rect.width = 0;
|
|
|
|
|
lines = g_array_new (FALSE, FALSE, sizeof (GdkRectangle));
|
|
|
|
|
|
|
|
|
|
for (iter = iter1;
|
|
|
|
|
!g_sequence_iter_is_end (iter);
|
|
|
|
|
iter = g_sequence_iter_next (iter))
|
|
|
|
|
{
|
|
|
|
|
GtkWidget *child;
|
|
|
|
|
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (child), &rect);
|
|
|
|
|
if (line_rect.width == 0)
|
|
|
|
|
line_rect = rect;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if ((vertical && rect.x == line_rect.x) ||
|
|
|
|
|
(!vertical && rect.y == line_rect.y))
|
|
|
|
|
gdk_rectangle_union (&rect, &line_rect, &line_rect);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
g_array_append_val (lines, line_rect);
|
|
|
|
|
line_rect = rect;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (g_sequence_iter_compare (iter, iter2) == 0)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (line_rect.width != 0)
|
|
|
|
|
g_array_append_val (lines, line_rect);
|
|
|
|
|
|
|
|
|
|
if (lines->len > 0)
|
|
|
|
|
{
|
|
|
|
|
cairo_path_t *path;
|
|
|
|
|
GtkBorder border;
|
2020-01-27 23:27:36 +00:00
|
|
|
|
const GdkRGBA *border_color;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
if (vertical)
|
|
|
|
|
path_from_vertical_line_rects (cr, (GdkRectangle *)lines->data, lines->len);
|
|
|
|
|
else
|
|
|
|
|
path_from_horizontal_line_rects (cr, (GdkRectangle *)lines->data, lines->len);
|
|
|
|
|
|
|
|
|
|
/* For some reason we need to copy and reapply the path,
|
|
|
|
|
* or it gets eaten by gtk_render_background()
|
|
|
|
|
*/
|
|
|
|
|
path = cairo_copy_path (cr);
|
|
|
|
|
|
|
|
|
|
cairo_save (cr);
|
|
|
|
|
cairo_clip (cr);
|
2016-11-15 15:11:19 +00:00
|
|
|
|
gtk_render_background (context, cr, x, y, width, height);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
cairo_restore (cr);
|
|
|
|
|
|
|
|
|
|
cairo_append_path (cr, path);
|
|
|
|
|
cairo_path_destroy (path);
|
|
|
|
|
|
2020-01-27 23:27:36 +00:00
|
|
|
|
border_color = gtk_css_color_value_get_rgba (_gtk_style_context_peek_property (context, GTK_CSS_PROPERTY_BORDER_TOP_COLOR));
|
2016-10-08 03:07:23 +00:00
|
|
|
|
gtk_style_context_get_border (context, &border);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
cairo_set_line_width (cr, border.left);
|
2019-04-01 00:43:19 +00:00
|
|
|
|
gdk_cairo_set_source_rgba (cr, border_color);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
cairo_stroke (cr);
|
|
|
|
|
}
|
|
|
|
|
g_array_free (lines, TRUE);
|
|
|
|
|
|
|
|
|
|
gtk_style_context_restore (context);
|
2016-08-18 06:39:08 +00:00
|
|
|
|
cairo_destroy (cr);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Autoscrolling {{{3 */
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
remove_autoscroll (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
if (priv->autoscroll_id)
|
|
|
|
|
{
|
|
|
|
|
gtk_widget_remove_tick_callback (GTK_WIDGET (box), priv->autoscroll_id);
|
|
|
|
|
priv->autoscroll_id = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
priv->autoscroll_mode = GTK_SCROLL_NONE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
autoscroll_cb (GtkWidget *widget,
|
|
|
|
|
GdkFrameClock *frame_clock,
|
|
|
|
|
gpointer data)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBox *box = GTK_FLOW_BOX (data);
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
GtkAdjustment *adjustment;
|
|
|
|
|
gdouble factor;
|
|
|
|
|
gdouble increment;
|
|
|
|
|
gdouble value;
|
|
|
|
|
|
|
|
|
|
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
|
|
|
adjustment = priv->vadjustment;
|
|
|
|
|
else
|
|
|
|
|
adjustment = priv->hadjustment;
|
|
|
|
|
|
|
|
|
|
switch (priv->autoscroll_mode)
|
|
|
|
|
{
|
|
|
|
|
case GTK_SCROLL_STEP_FORWARD:
|
|
|
|
|
factor = AUTOSCROLL_FACTOR;
|
|
|
|
|
break;
|
|
|
|
|
case GTK_SCROLL_STEP_BACKWARD:
|
|
|
|
|
factor = - AUTOSCROLL_FACTOR;
|
|
|
|
|
break;
|
|
|
|
|
case GTK_SCROLL_PAGE_FORWARD:
|
|
|
|
|
factor = AUTOSCROLL_FACTOR_FAST;
|
|
|
|
|
break;
|
|
|
|
|
case GTK_SCROLL_PAGE_BACKWARD:
|
|
|
|
|
factor = - AUTOSCROLL_FACTOR_FAST;
|
|
|
|
|
break;
|
2017-10-06 19:19:42 +00:00
|
|
|
|
case GTK_SCROLL_NONE:
|
|
|
|
|
case GTK_SCROLL_JUMP:
|
|
|
|
|
case GTK_SCROLL_STEP_UP:
|
|
|
|
|
case GTK_SCROLL_STEP_DOWN:
|
|
|
|
|
case GTK_SCROLL_STEP_LEFT:
|
|
|
|
|
case GTK_SCROLL_STEP_RIGHT:
|
|
|
|
|
case GTK_SCROLL_PAGE_UP:
|
|
|
|
|
case GTK_SCROLL_PAGE_DOWN:
|
|
|
|
|
case GTK_SCROLL_PAGE_LEFT:
|
|
|
|
|
case GTK_SCROLL_PAGE_RIGHT:
|
|
|
|
|
case GTK_SCROLL_START:
|
|
|
|
|
case GTK_SCROLL_END:
|
2013-09-29 17:43:27 +00:00
|
|
|
|
default:
|
|
|
|
|
g_assert_not_reached ();
|
2017-10-06 19:19:42 +00:00
|
|
|
|
break;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
increment = gtk_adjustment_get_step_increment (adjustment) / factor;
|
|
|
|
|
|
|
|
|
|
value = gtk_adjustment_get_value (adjustment);
|
|
|
|
|
value += increment;
|
|
|
|
|
gtk_adjustment_set_value (adjustment, value);
|
|
|
|
|
|
|
|
|
|
if (priv->rubberband_select)
|
|
|
|
|
{
|
2014-06-05 13:45:21 +00:00
|
|
|
|
GdkEventSequence *sequence;
|
|
|
|
|
gdouble x, y;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GtkFlowBoxChild *child;
|
|
|
|
|
|
2014-06-05 13:45:21 +00:00
|
|
|
|
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (priv->drag_gesture));
|
|
|
|
|
gtk_gesture_get_point (priv->drag_gesture, sequence, &x, &y);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2016-12-16 22:40:21 +00:00
|
|
|
|
child = gtk_flow_box_get_child_at_pos (box, x, y);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
if (child != NULL)
|
|
|
|
|
priv->rubberband_last = child;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
add_autoscroll (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
if (priv->autoscroll_id != 0 ||
|
|
|
|
|
priv->autoscroll_mode == GTK_SCROLL_NONE)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
priv->autoscroll_id = gtk_widget_add_tick_callback (GTK_WIDGET (box),
|
2014-04-16 13:36:22 +00:00
|
|
|
|
autoscroll_cb,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
box,
|
|
|
|
|
NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
get_view_rect (GtkFlowBox *box,
|
|
|
|
|
GdkRectangle *rect)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
GtkWidget *parent;
|
|
|
|
|
|
|
|
|
|
parent = gtk_widget_get_parent (GTK_WIDGET (box));
|
|
|
|
|
if (GTK_IS_VIEWPORT (parent))
|
|
|
|
|
{
|
|
|
|
|
rect->x = gtk_adjustment_get_value (priv->hadjustment);
|
|
|
|
|
rect->y = gtk_adjustment_get_value (priv->vadjustment);
|
2017-11-10 14:33:24 +00:00
|
|
|
|
rect->width = gtk_widget_get_width (parent);
|
|
|
|
|
rect->height = gtk_widget_get_height (parent);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
update_autoscroll_mode (GtkFlowBox *box,
|
|
|
|
|
gint x,
|
|
|
|
|
gint y)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
GtkScrollType mode = GTK_SCROLL_NONE;
|
|
|
|
|
GdkRectangle rect;
|
|
|
|
|
gint size, pos;
|
|
|
|
|
|
|
|
|
|
if (priv->rubberband_select && get_view_rect (box, &rect))
|
|
|
|
|
{
|
|
|
|
|
if (priv->orientation == GTK_ORIENTATION_VERTICAL)
|
|
|
|
|
{
|
|
|
|
|
size = rect.width;
|
|
|
|
|
pos = x - rect.x;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
size = rect.height;
|
|
|
|
|
pos = y - rect.y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pos < 0 - AUTOSCROLL_FAST_DISTANCE)
|
|
|
|
|
mode = GTK_SCROLL_PAGE_BACKWARD;
|
|
|
|
|
else if (pos > size + AUTOSCROLL_FAST_DISTANCE)
|
|
|
|
|
mode = GTK_SCROLL_PAGE_FORWARD;
|
|
|
|
|
else if (pos < 0)
|
|
|
|
|
mode = GTK_SCROLL_STEP_BACKWARD;
|
|
|
|
|
else if (pos > size)
|
|
|
|
|
mode = GTK_SCROLL_STEP_FORWARD;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mode != priv->autoscroll_mode)
|
|
|
|
|
{
|
|
|
|
|
remove_autoscroll (box);
|
|
|
|
|
priv->autoscroll_mode = mode;
|
|
|
|
|
add_autoscroll (box);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Event handling {{{3 */
|
|
|
|
|
|
2014-06-05 13:45:21 +00:00
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_drag_gesture_update (GtkGestureDrag *gesture,
|
|
|
|
|
gdouble offset_x,
|
|
|
|
|
gdouble offset_y,
|
|
|
|
|
GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
gdouble start_x, start_y;
|
|
|
|
|
GtkFlowBoxChild *child;
|
2015-11-10 05:50:42 +00:00
|
|
|
|
GtkCssNode *widget_node;
|
2014-06-05 13:45:21 +00:00
|
|
|
|
|
|
|
|
|
gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
|
|
|
|
|
|
|
|
|
|
if (!priv->rubberband_select &&
|
|
|
|
|
(offset_x * offset_x) + (offset_y * offset_y) > RUBBERBAND_START_DISTANCE * RUBBERBAND_START_DISTANCE)
|
|
|
|
|
{
|
|
|
|
|
priv->rubberband_select = TRUE;
|
2016-12-16 22:40:21 +00:00
|
|
|
|
priv->rubberband_first = gtk_flow_box_get_child_at_pos (box, start_x, start_y);
|
2015-11-10 05:50:42 +00:00
|
|
|
|
|
|
|
|
|
widget_node = gtk_widget_get_css_node (GTK_WIDGET (box));
|
|
|
|
|
priv->rubberband_node = gtk_css_node_new ();
|
2020-01-23 23:43:26 +00:00
|
|
|
|
gtk_css_node_set_name (priv->rubberband_node, g_quark_from_static_string ("rubberband"));
|
2015-11-10 05:50:42 +00:00
|
|
|
|
gtk_css_node_set_parent (priv->rubberband_node, widget_node);
|
|
|
|
|
gtk_css_node_set_state (priv->rubberband_node, gtk_css_node_get_state (widget_node));
|
|
|
|
|
g_object_unref (priv->rubberband_node);
|
2014-06-05 13:45:21 +00:00
|
|
|
|
|
|
|
|
|
/* Grab focus here, so Escape-to-stop-rubberband works */
|
2017-04-06 00:10:09 +00:00
|
|
|
|
if (priv->rubberband_first)
|
|
|
|
|
gtk_flow_box_update_cursor (box, priv->rubberband_first);
|
2014-06-05 13:45:21 +00:00
|
|
|
|
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (priv->rubberband_select)
|
|
|
|
|
{
|
2016-12-16 22:40:21 +00:00
|
|
|
|
child = gtk_flow_box_get_child_at_pos (box, start_x + offset_x,
|
2014-06-05 13:45:21 +00:00
|
|
|
|
start_y + offset_y);
|
|
|
|
|
|
|
|
|
|
if (priv->rubberband_first == NULL)
|
2017-04-06 00:10:09 +00:00
|
|
|
|
{
|
|
|
|
|
priv->rubberband_first = child;
|
|
|
|
|
if (priv->rubberband_first)
|
|
|
|
|
gtk_flow_box_update_cursor (box, priv->rubberband_first);
|
|
|
|
|
}
|
2014-06-05 13:45:21 +00:00
|
|
|
|
if (child != NULL)
|
|
|
|
|
priv->rubberband_last = child;
|
|
|
|
|
|
|
|
|
|
update_autoscroll_mode (box, start_x + offset_x, start_y + offset_y);
|
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (box));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2019-05-29 17:10:46 +00:00
|
|
|
|
gtk_flow_box_click_gesture_pressed (GtkGestureClick *gesture,
|
|
|
|
|
guint n_press,
|
|
|
|
|
gdouble x,
|
|
|
|
|
gdouble y,
|
|
|
|
|
GtkFlowBox *box)
|
2014-06-05 13:45:21 +00:00
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
GtkFlowBoxChild *child;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2016-12-16 22:40:21 +00:00
|
|
|
|
child = gtk_flow_box_get_child_at_pos (box, x, y);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2014-06-05 13:45:21 +00:00
|
|
|
|
if (child == NULL)
|
|
|
|
|
return;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2014-06-05 13:45:21 +00:00
|
|
|
|
/* The drag gesture is only triggered by first press */
|
|
|
|
|
if (n_press != 1)
|
|
|
|
|
gtk_gesture_set_state (priv->drag_gesture, GTK_EVENT_SEQUENCE_DENIED);
|
|
|
|
|
|
|
|
|
|
priv->active_child = child;
|
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (box));
|
|
|
|
|
|
|
|
|
|
if (n_press == 2 && !priv->activate_on_single_click)
|
2019-01-02 18:10:49 +00:00
|
|
|
|
{
|
|
|
|
|
gtk_gesture_set_state (GTK_GESTURE (gesture),
|
|
|
|
|
GTK_EVENT_SEQUENCE_CLAIMED);
|
|
|
|
|
g_signal_emit (box, signals[CHILD_ACTIVATED], 0, child);
|
|
|
|
|
}
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-21 21:35:31 +00:00
|
|
|
|
static void
|
2019-05-29 17:10:46 +00:00
|
|
|
|
gtk_flow_box_click_unpaired_release (GtkGestureClick *gesture,
|
|
|
|
|
gdouble x,
|
|
|
|
|
gdouble y,
|
|
|
|
|
guint button,
|
|
|
|
|
GdkEventSequence *sequence,
|
|
|
|
|
GtkFlowBox *box)
|
2017-11-21 21:35:31 +00:00
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
GtkFlowBoxChild *child;
|
|
|
|
|
|
2017-12-31 04:23:16 +00:00
|
|
|
|
if (!priv->activate_on_single_click || !priv->accept_unpaired_release)
|
2017-11-21 21:35:31 +00:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
child = gtk_flow_box_get_child_at_pos (box, x, y);
|
|
|
|
|
|
|
|
|
|
if (child)
|
|
|
|
|
gtk_flow_box_select_and_activate (box, child);
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-05 13:45:21 +00:00
|
|
|
|
static void
|
2019-05-29 17:10:46 +00:00
|
|
|
|
gtk_flow_box_click_gesture_released (GtkGestureClick *gesture,
|
|
|
|
|
guint n_press,
|
|
|
|
|
gdouble x,
|
|
|
|
|
gdouble y,
|
|
|
|
|
GtkFlowBox *box)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
|
2017-09-15 14:16:24 +00:00
|
|
|
|
if (priv->active_child != NULL &&
|
|
|
|
|
priv->active_child == gtk_flow_box_get_child_at_pos (box, x, y))
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
2019-01-02 18:10:49 +00:00
|
|
|
|
gtk_gesture_set_state (GTK_GESTURE (gesture),
|
|
|
|
|
GTK_EVENT_SEQUENCE_CLAIMED);
|
|
|
|
|
|
2014-06-05 13:45:21 +00:00
|
|
|
|
if (priv->activate_on_single_click)
|
|
|
|
|
gtk_flow_box_select_and_activate (box, priv->active_child);
|
|
|
|
|
else
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
2014-06-05 13:45:21 +00:00
|
|
|
|
GdkEventSequence *sequence;
|
|
|
|
|
GdkInputSource source;
|
2020-02-16 16:09:02 +00:00
|
|
|
|
GdkEvent *event;
|
2020-04-11 18:52:43 +00:00
|
|
|
|
GdkModifierType state;
|
2014-06-05 13:45:21 +00:00
|
|
|
|
gboolean modify;
|
|
|
|
|
gboolean extend;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2020-04-11 18:52:43 +00:00
|
|
|
|
state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture));
|
|
|
|
|
modify = (state & GDK_CONTROL_MASK) != 0;
|
|
|
|
|
extend = (state & GDK_SHIFT_MASK) != 0;
|
2014-06-05 13:45:21 +00:00
|
|
|
|
|
|
|
|
|
/* With touch, we default to modifying the selection.
|
|
|
|
|
* You can still clear the selection and start over
|
|
|
|
|
* by holding Ctrl.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
|
|
|
|
|
event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
|
|
|
|
|
source = gdk_device_get_source (gdk_event_get_source_device (event));
|
|
|
|
|
|
|
|
|
|
if (source == GDK_SOURCE_TOUCHSCREEN)
|
|
|
|
|
modify = !modify;
|
|
|
|
|
|
|
|
|
|
gtk_flow_box_update_selection (box, priv->active_child, modify, extend);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-20 11:55:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2019-05-29 17:10:46 +00:00
|
|
|
|
gtk_flow_box_click_gesture_stopped (GtkGestureClick *gesture,
|
|
|
|
|
GtkFlowBox *box)
|
2016-12-20 11:55:41 +00:00
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2014-06-05 13:45:21 +00:00
|
|
|
|
priv->active_child = NULL;
|
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (box));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_drag_gesture_begin (GtkGestureDrag *gesture,
|
|
|
|
|
gdouble start_x,
|
|
|
|
|
gdouble start_y,
|
|
|
|
|
GtkWidget *widget)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (widget);
|
2020-04-11 18:52:43 +00:00
|
|
|
|
GdkModifierType state;
|
2014-06-05 13:45:21 +00:00
|
|
|
|
|
|
|
|
|
if (priv->selection_mode != GTK_SELECTION_MULTIPLE)
|
|
|
|
|
{
|
|
|
|
|
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
priv->rubberband_select = FALSE;
|
|
|
|
|
priv->rubberband_first = NULL;
|
|
|
|
|
priv->rubberband_last = NULL;
|
2020-04-11 18:52:43 +00:00
|
|
|
|
|
|
|
|
|
state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture));
|
|
|
|
|
priv->rubberband_modify = (state & GDK_CONTROL_MASK) != 0;
|
|
|
|
|
priv->rubberband_extend = (state & GDK_SHIFT_MASK) != 0;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-10-07 03:15:27 +00:00
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_stop_rubberband (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
priv->rubberband_select = FALSE;
|
|
|
|
|
priv->rubberband_first = NULL;
|
|
|
|
|
priv->rubberband_last = NULL;
|
|
|
|
|
|
2015-11-10 05:50:42 +00:00
|
|
|
|
gtk_css_node_set_parent (priv->rubberband_node, NULL);
|
|
|
|
|
priv->rubberband_node = NULL;
|
|
|
|
|
|
2013-10-07 03:15:27 +00:00
|
|
|
|
remove_autoscroll (box);
|
|
|
|
|
|
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (box));
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-05 13:45:21 +00:00
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_drag_gesture_end (GtkGestureDrag *gesture,
|
|
|
|
|
gdouble offset_x,
|
|
|
|
|
gdouble offset_y,
|
|
|
|
|
GtkFlowBox *box)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
2014-06-05 13:45:21 +00:00
|
|
|
|
GdkEventSequence *sequence;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2014-06-05 13:45:21 +00:00
|
|
|
|
if (!priv->rubberband_select)
|
|
|
|
|
return;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2014-06-05 13:45:21 +00:00
|
|
|
|
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2014-06-05 13:45:21 +00:00
|
|
|
|
if (gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
2013-10-07 03:15:27 +00:00
|
|
|
|
if (!priv->rubberband_extend && !priv->rubberband_modify)
|
|
|
|
|
gtk_flow_box_unselect_all_internal (box);
|
|
|
|
|
|
2017-03-30 19:29:57 +00:00
|
|
|
|
if (priv->rubberband_first && priv->rubberband_last)
|
|
|
|
|
gtk_flow_box_select_all_between (box, priv->rubberband_first, priv->rubberband_last, priv->rubberband_modify);
|
|
|
|
|
|
2013-10-07 03:15:27 +00:00
|
|
|
|
gtk_flow_box_stop_rubberband (box);
|
|
|
|
|
|
|
|
|
|
g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
2014-06-05 13:45:21 +00:00
|
|
|
|
else
|
|
|
|
|
gtk_flow_box_stop_rubberband (box);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2014-06-05 13:45:21 +00:00
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (box));
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-10-07 03:15:27 +00:00
|
|
|
|
static gboolean
|
2018-03-05 14:00:06 +00:00
|
|
|
|
gtk_flow_box_key_controller_key_pressed (GtkEventControllerKey *controller,
|
|
|
|
|
guint keyval,
|
|
|
|
|
guint keycode,
|
|
|
|
|
GdkModifierType state,
|
|
|
|
|
GtkWidget *widget)
|
2013-10-07 03:15:27 +00:00
|
|
|
|
{
|
|
|
|
|
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
|
2018-03-05 14:00:06 +00:00
|
|
|
|
if (priv->rubberband_select && keyval == GDK_KEY_Escape)
|
2013-10-07 03:15:27 +00:00
|
|
|
|
{
|
2018-03-05 14:00:06 +00:00
|
|
|
|
gtk_flow_box_stop_rubberband (box);
|
|
|
|
|
return TRUE;
|
2013-10-07 03:15:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-05 14:00:06 +00:00
|
|
|
|
return FALSE;
|
2013-10-07 03:15:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
/* Realize and map {{{3 */
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_unmap (GtkWidget *widget)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
|
|
|
|
|
|
|
|
|
remove_autoscroll (box);
|
|
|
|
|
|
|
|
|
|
GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->unmap (widget);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* GtkContainer implementation {{{2 */
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_add (GtkContainer *container,
|
|
|
|
|
GtkWidget *child)
|
|
|
|
|
{
|
|
|
|
|
gtk_flow_box_insert (GTK_FLOW_BOX (container), child, -1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_remove (GtkContainer *container,
|
|
|
|
|
GtkWidget *widget)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBox *box = GTK_FLOW_BOX (container);
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
gboolean was_visible;
|
|
|
|
|
gboolean was_selected;
|
|
|
|
|
GtkFlowBoxChild *child;
|
|
|
|
|
|
|
|
|
|
if (GTK_IS_FLOW_BOX_CHILD (widget))
|
|
|
|
|
child = GTK_FLOW_BOX_CHILD (widget);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
child = (GtkFlowBoxChild*)gtk_widget_get_parent (widget);
|
|
|
|
|
if (!GTK_IS_FLOW_BOX_CHILD (child))
|
|
|
|
|
{
|
2016-02-28 16:06:25 +00:00
|
|
|
|
g_warning ("Tried to remove non-child %p", widget);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
was_visible = child_is_visible (GTK_WIDGET (child));
|
|
|
|
|
was_selected = CHILD_PRIV (child)->selected;
|
|
|
|
|
|
|
|
|
|
if (child == priv->active_child)
|
|
|
|
|
priv->active_child = NULL;
|
|
|
|
|
if (child == priv->selected_child)
|
|
|
|
|
priv->selected_child = NULL;
|
|
|
|
|
|
|
|
|
|
gtk_widget_unparent (GTK_WIDGET (child));
|
|
|
|
|
g_sequence_remove (CHILD_PRIV (child)->iter);
|
|
|
|
|
|
|
|
|
|
if (was_visible && gtk_widget_get_visible (GTK_WIDGET (box)))
|
|
|
|
|
gtk_widget_queue_resize (GTK_WIDGET (box));
|
|
|
|
|
|
2016-12-12 20:12:51 +00:00
|
|
|
|
if (was_selected && !gtk_widget_in_destruction (GTK_WIDGET (box)))
|
2013-09-29 17:43:27 +00:00
|
|
|
|
g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_forall (GtkContainer *container,
|
|
|
|
|
GtkCallback callback,
|
|
|
|
|
gpointer callback_target)
|
|
|
|
|
{
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
GtkWidget *child;
|
|
|
|
|
|
|
|
|
|
iter = g_sequence_get_begin_iter (BOX_PRIV (container)->children);
|
|
|
|
|
while (!g_sequence_iter_is_end (iter))
|
|
|
|
|
{
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
iter = g_sequence_iter_next (iter);
|
|
|
|
|
callback (child, callback_target);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GType
|
|
|
|
|
gtk_flow_box_child_type (GtkContainer *container)
|
|
|
|
|
{
|
|
|
|
|
return GTK_TYPE_FLOW_BOX_CHILD;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Keynav {{{2 */
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
gtk_flow_box_focus (GtkWidget *widget,
|
|
|
|
|
GtkDirectionType direction)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
|
|
|
|
GtkWidget *focus_child;
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
GtkFlowBoxChild *next_focus_child;
|
|
|
|
|
|
2017-02-08 08:00:29 +00:00
|
|
|
|
focus_child = gtk_widget_get_focus_child (widget);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
next_focus_child = NULL;
|
|
|
|
|
|
|
|
|
|
if (focus_child != NULL)
|
|
|
|
|
{
|
|
|
|
|
if (gtk_widget_child_focus (focus_child, direction))
|
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
|
|
iter = CHILD_PRIV (focus_child)->iter;
|
|
|
|
|
|
|
|
|
|
if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD)
|
|
|
|
|
iter = gtk_flow_box_get_previous_focusable (box, iter);
|
|
|
|
|
else if (direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD)
|
|
|
|
|
iter = gtk_flow_box_get_next_focusable (box, iter);
|
|
|
|
|
else if (direction == GTK_DIR_UP)
|
|
|
|
|
iter = gtk_flow_box_get_above_focusable (box, iter);
|
|
|
|
|
else if (direction == GTK_DIR_DOWN)
|
|
|
|
|
iter = gtk_flow_box_get_below_focusable (box, iter);
|
|
|
|
|
|
2013-10-21 14:32:49 +00:00
|
|
|
|
if (iter != NULL)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
next_focus_child = g_sequence_get (iter);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (BOX_PRIV (box)->selected_child)
|
|
|
|
|
next_focus_child = BOX_PRIV (box)->selected_child;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (direction == GTK_DIR_UP || direction == GTK_DIR_TAB_BACKWARD)
|
|
|
|
|
iter = gtk_flow_box_get_last_focusable (box);
|
|
|
|
|
else
|
|
|
|
|
iter = gtk_flow_box_get_first_focusable (box);
|
|
|
|
|
|
2013-10-21 14:32:49 +00:00
|
|
|
|
if (iter != NULL)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
next_focus_child = g_sequence_get (iter);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (next_focus_child == NULL)
|
|
|
|
|
{
|
|
|
|
|
if (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN ||
|
|
|
|
|
direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT)
|
|
|
|
|
{
|
|
|
|
|
if (gtk_widget_keynav_failed (GTK_WIDGET (box), direction))
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (gtk_widget_child_focus (GTK_WIDGET (next_focus_child), direction))
|
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (GtkWidgetClass *widget_class,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
guint keyval,
|
|
|
|
|
GdkModifierType modmask,
|
|
|
|
|
GtkMovementStep step,
|
|
|
|
|
gint count)
|
|
|
|
|
{
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_widget_class_add_binding_signal (widget_class,
|
|
|
|
|
keyval, modmask,
|
|
|
|
|
"move-cursor",
|
2020-04-11 18:52:43 +00:00
|
|
|
|
"(iibb)", step, count, FALSE, FALSE);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_widget_class_add_binding_signal (widget_class,
|
2020-04-11 18:52:43 +00:00
|
|
|
|
keyval, modmask | GDK_SHIFT_MASK,
|
2018-08-12 01:45:09 +00:00
|
|
|
|
"move-cursor",
|
2020-04-11 18:52:43 +00:00
|
|
|
|
"(iibb)", step, count, TRUE, FALSE);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_widget_class_add_binding_signal (widget_class,
|
2020-04-11 18:52:43 +00:00
|
|
|
|
keyval, modmask | GDK_CONTROL_MASK,
|
2018-08-12 01:45:09 +00:00
|
|
|
|
"move-cursor",
|
2020-04-11 18:52:43 +00:00
|
|
|
|
"(iibb)", step, count, FALSE, TRUE);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_widget_class_add_binding_signal (widget_class,
|
2020-04-11 18:52:43 +00:00
|
|
|
|
keyval, modmask | GDK_SHIFT_MASK | GDK_CONTROL_MASK,
|
2018-08-12 01:45:09 +00:00
|
|
|
|
"move-cursor",
|
2020-04-11 18:52:43 +00:00
|
|
|
|
"(iibb)", step, count, TRUE, TRUE);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_activate_cursor_child (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
gtk_flow_box_select_and_activate (box, BOX_PRIV (box)->cursor_child);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_toggle_cursor_child (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
if (priv->cursor_child == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if ((priv->selection_mode == GTK_SELECTION_SINGLE ||
|
|
|
|
|
priv->selection_mode == GTK_SELECTION_MULTIPLE) &&
|
|
|
|
|
CHILD_PRIV (priv->cursor_child)->selected)
|
2013-10-07 03:15:27 +00:00
|
|
|
|
gtk_flow_box_unselect_child_internal (box, priv->cursor_child);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
else
|
|
|
|
|
gtk_flow_box_select_and_activate (box, priv->cursor_child);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-14 19:03:57 +00:00
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_disable_move_cursor (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
priv->disable_move_cursor = TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-23 00:32:21 +00:00
|
|
|
|
static gboolean
|
2013-09-29 17:43:27 +00:00
|
|
|
|
gtk_flow_box_move_cursor (GtkFlowBox *box,
|
|
|
|
|
GtkMovementStep step,
|
2020-04-11 18:52:43 +00:00
|
|
|
|
gint count,
|
|
|
|
|
gboolean extend,
|
|
|
|
|
gboolean modify)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
GtkFlowBoxChild *child;
|
|
|
|
|
GtkFlowBoxChild *prev;
|
|
|
|
|
GtkFlowBoxChild *next;
|
|
|
|
|
GtkAllocation allocation;
|
|
|
|
|
gint page_size;
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
gint start;
|
|
|
|
|
GtkAdjustment *adjustment;
|
|
|
|
|
gboolean vertical;
|
|
|
|
|
|
2020-04-14 19:03:57 +00:00
|
|
|
|
if (priv->disable_move_cursor)
|
|
|
|
|
return FALSE;
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
vertical = priv->orientation == GTK_ORIENTATION_VERTICAL;
|
|
|
|
|
|
|
|
|
|
if (vertical)
|
|
|
|
|
{
|
2017-10-06 19:19:42 +00:00
|
|
|
|
switch ((guint) step)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
|
|
|
|
case GTK_MOVEMENT_VISUAL_POSITIONS:
|
|
|
|
|
step = GTK_MOVEMENT_DISPLAY_LINES;
|
|
|
|
|
break;
|
|
|
|
|
case GTK_MOVEMENT_DISPLAY_LINES:
|
|
|
|
|
step = GTK_MOVEMENT_VISUAL_POSITIONS;
|
|
|
|
|
break;
|
2017-10-06 19:19:42 +00:00
|
|
|
|
default:
|
|
|
|
|
break;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
child = NULL;
|
2017-10-06 19:19:42 +00:00
|
|
|
|
switch ((guint) step)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
{
|
|
|
|
|
case GTK_MOVEMENT_VISUAL_POSITIONS:
|
|
|
|
|
if (priv->cursor_child != NULL)
|
|
|
|
|
{
|
|
|
|
|
iter = CHILD_PRIV (priv->cursor_child)->iter;
|
|
|
|
|
if (gtk_widget_get_direction (GTK_WIDGET (box)) == GTK_TEXT_DIR_RTL)
|
|
|
|
|
count = - count;
|
|
|
|
|
|
|
|
|
|
while (count < 0 && iter != NULL)
|
|
|
|
|
{
|
|
|
|
|
iter = gtk_flow_box_get_previous_focusable (box, iter);
|
|
|
|
|
count = count + 1;
|
|
|
|
|
}
|
|
|
|
|
while (count > 0 && iter != NULL)
|
|
|
|
|
{
|
|
|
|
|
iter = gtk_flow_box_get_next_focusable (box, iter);
|
|
|
|
|
count = count - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (iter != NULL && !g_sequence_iter_is_end (iter))
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GTK_MOVEMENT_BUFFER_ENDS:
|
|
|
|
|
if (count < 0)
|
|
|
|
|
iter = gtk_flow_box_get_first_focusable (box);
|
|
|
|
|
else
|
|
|
|
|
iter = gtk_flow_box_get_last_focusable (box);
|
2013-10-21 14:32:49 +00:00
|
|
|
|
if (iter != NULL)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GTK_MOVEMENT_DISPLAY_LINES:
|
|
|
|
|
if (priv->cursor_child != NULL)
|
|
|
|
|
{
|
|
|
|
|
iter = CHILD_PRIV (priv->cursor_child)->iter;
|
|
|
|
|
|
|
|
|
|
while (count < 0 && iter != NULL)
|
|
|
|
|
{
|
|
|
|
|
iter = gtk_flow_box_get_above_focusable (box, iter);
|
|
|
|
|
count = count + 1;
|
|
|
|
|
}
|
|
|
|
|
while (count > 0 && iter != NULL)
|
|
|
|
|
{
|
|
|
|
|
iter = gtk_flow_box_get_below_focusable (box, iter);
|
|
|
|
|
count = count - 1;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-21 14:32:49 +00:00
|
|
|
|
if (iter != NULL)
|
2013-09-29 17:43:27 +00:00
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GTK_MOVEMENT_PAGES:
|
|
|
|
|
page_size = 100;
|
|
|
|
|
adjustment = vertical ? priv->hadjustment : priv->vadjustment;
|
|
|
|
|
if (adjustment)
|
|
|
|
|
page_size = gtk_adjustment_get_page_increment (adjustment);
|
|
|
|
|
|
|
|
|
|
if (priv->cursor_child != NULL)
|
|
|
|
|
{
|
|
|
|
|
child = priv->cursor_child;
|
|
|
|
|
iter = CHILD_PRIV (child)->iter;
|
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (child), &allocation);
|
|
|
|
|
start = vertical ? allocation.x : allocation.y;
|
|
|
|
|
|
|
|
|
|
if (count < 0)
|
|
|
|
|
{
|
|
|
|
|
gint i = 0;
|
|
|
|
|
|
|
|
|
|
/* Up */
|
|
|
|
|
while (iter != NULL)
|
|
|
|
|
{
|
|
|
|
|
iter = gtk_flow_box_get_previous_focusable (box, iter);
|
|
|
|
|
if (iter == NULL)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
prev = g_sequence_get (iter);
|
|
|
|
|
|
|
|
|
|
/* go up an even number of rows */
|
|
|
|
|
if (i % priv->cur_children_per_line == 0)
|
|
|
|
|
{
|
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (prev), &allocation);
|
|
|
|
|
if ((vertical ? allocation.x : allocation.y) < start - page_size)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
child = prev;
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
gint i = 0;
|
|
|
|
|
|
|
|
|
|
/* Down */
|
|
|
|
|
while (!g_sequence_iter_is_end (iter))
|
|
|
|
|
{
|
|
|
|
|
iter = gtk_flow_box_get_next_focusable (box, iter);
|
2015-08-26 23:57:15 +00:00
|
|
|
|
if (iter == NULL || g_sequence_iter_is_end (iter))
|
2013-09-29 17:43:27 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
next = g_sequence_get (iter);
|
|
|
|
|
|
|
|
|
|
if (i % priv->cur_children_per_line == 0)
|
|
|
|
|
{
|
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (next), &allocation);
|
|
|
|
|
if ((vertical ? allocation.x : allocation.y) > start + page_size)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
child = next;
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (child), &allocation);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
g_assert_not_reached ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (child == NULL || child == priv->cursor_child)
|
|
|
|
|
{
|
|
|
|
|
GtkDirectionType direction = count < 0 ? GTK_DIR_UP : GTK_DIR_DOWN;
|
|
|
|
|
|
|
|
|
|
if (!gtk_widget_keynav_failed (GTK_WIDGET (box), direction))
|
|
|
|
|
{
|
2015-08-23 00:32:21 +00:00
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-08-23 00:32:21 +00:00
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2015-08-23 00:32:21 +00:00
|
|
|
|
/* If the child has its "can-focus" property set to FALSE then it will
|
|
|
|
|
* not grab the focus. We must pass the focus to its child directly.
|
|
|
|
|
*/
|
|
|
|
|
if (!gtk_widget_get_can_focus (GTK_WIDGET (child)))
|
|
|
|
|
{
|
|
|
|
|
GtkWidget *subchild;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2020-05-03 00:53:40 +00:00
|
|
|
|
subchild = gtk_flow_box_child_get_child (GTK_FLOW_BOX_CHILD (child));
|
2015-08-23 00:32:21 +00:00
|
|
|
|
if (subchild)
|
|
|
|
|
{
|
|
|
|
|
GtkDirectionType direction = count < 0 ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD;
|
|
|
|
|
gtk_widget_child_focus (subchild, direction);
|
|
|
|
|
}
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gtk_flow_box_update_cursor (box, child);
|
2013-10-07 03:15:27 +00:00
|
|
|
|
if (!modify)
|
|
|
|
|
gtk_flow_box_update_selection (box, child, FALSE, extend);
|
2015-08-23 00:32:21 +00:00
|
|
|
|
return TRUE;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Selection {{{2 */
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_selected_children_changed (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
_gtk_flow_box_accessible_selection_changed (GTK_WIDGET (box));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* GObject implementation {{{2 */
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_get_property (GObject *object,
|
|
|
|
|
guint prop_id,
|
|
|
|
|
GValue *value,
|
|
|
|
|
GParamSpec *pspec)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBox *box = GTK_FLOW_BOX (object);
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
switch (prop_id)
|
|
|
|
|
{
|
|
|
|
|
case PROP_ORIENTATION:
|
|
|
|
|
g_value_set_enum (value, priv->orientation);
|
|
|
|
|
break;
|
|
|
|
|
case PROP_HOMOGENEOUS:
|
|
|
|
|
g_value_set_boolean (value, priv->homogeneous);
|
|
|
|
|
break;
|
|
|
|
|
case PROP_COLUMN_SPACING:
|
|
|
|
|
g_value_set_uint (value, priv->column_spacing);
|
|
|
|
|
break;
|
|
|
|
|
case PROP_ROW_SPACING:
|
|
|
|
|
g_value_set_uint (value, priv->row_spacing);
|
|
|
|
|
break;
|
|
|
|
|
case PROP_MIN_CHILDREN_PER_LINE:
|
|
|
|
|
g_value_set_uint (value, priv->min_children_per_line);
|
|
|
|
|
break;
|
|
|
|
|
case PROP_MAX_CHILDREN_PER_LINE:
|
|
|
|
|
g_value_set_uint (value, priv->max_children_per_line);
|
|
|
|
|
break;
|
|
|
|
|
case PROP_SELECTION_MODE:
|
|
|
|
|
g_value_set_enum (value, priv->selection_mode);
|
|
|
|
|
break;
|
|
|
|
|
case PROP_ACTIVATE_ON_SINGLE_CLICK:
|
|
|
|
|
g_value_set_boolean (value, priv->activate_on_single_click);
|
|
|
|
|
break;
|
2017-12-31 04:23:16 +00:00
|
|
|
|
case PROP_ACCEPT_UNPAIRED_RELEASE:
|
|
|
|
|
g_value_set_boolean (value, priv->accept_unpaired_release);
|
|
|
|
|
break;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
default:
|
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_set_property (GObject *object,
|
|
|
|
|
guint prop_id,
|
|
|
|
|
const GValue *value,
|
|
|
|
|
GParamSpec *pspec)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBox *box = GTK_FLOW_BOX (object);
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
switch (prop_id)
|
|
|
|
|
{
|
|
|
|
|
case PROP_ORIENTATION:
|
2014-06-08 15:57:27 +00:00
|
|
|
|
if (priv->orientation != g_value_get_enum (value))
|
|
|
|
|
{
|
|
|
|
|
priv->orientation = g_value_get_enum (value);
|
2014-07-18 23:38:16 +00:00
|
|
|
|
_gtk_orientable_set_style_classes (GTK_ORIENTABLE (box));
|
2014-06-08 15:57:27 +00:00
|
|
|
|
/* Re-box the children in the new orientation */
|
|
|
|
|
gtk_widget_queue_resize (GTK_WIDGET (box));
|
|
|
|
|
g_object_notify_by_pspec (object, pspec);
|
|
|
|
|
}
|
2013-09-29 17:43:27 +00:00
|
|
|
|
break;
|
|
|
|
|
case PROP_HOMOGENEOUS:
|
|
|
|
|
gtk_flow_box_set_homogeneous (box, g_value_get_boolean (value));
|
|
|
|
|
break;
|
|
|
|
|
case PROP_COLUMN_SPACING:
|
|
|
|
|
gtk_flow_box_set_column_spacing (box, g_value_get_uint (value));
|
|
|
|
|
break;
|
|
|
|
|
case PROP_ROW_SPACING:
|
|
|
|
|
gtk_flow_box_set_row_spacing (box, g_value_get_uint (value));
|
|
|
|
|
break;
|
|
|
|
|
case PROP_MIN_CHILDREN_PER_LINE:
|
|
|
|
|
gtk_flow_box_set_min_children_per_line (box, g_value_get_uint (value));
|
|
|
|
|
break;
|
|
|
|
|
case PROP_MAX_CHILDREN_PER_LINE:
|
|
|
|
|
gtk_flow_box_set_max_children_per_line (box, g_value_get_uint (value));
|
|
|
|
|
break;
|
|
|
|
|
case PROP_SELECTION_MODE:
|
|
|
|
|
gtk_flow_box_set_selection_mode (box, g_value_get_enum (value));
|
|
|
|
|
break;
|
|
|
|
|
case PROP_ACTIVATE_ON_SINGLE_CLICK:
|
|
|
|
|
gtk_flow_box_set_activate_on_single_click (box, g_value_get_boolean (value));
|
|
|
|
|
break;
|
2017-12-31 04:23:16 +00:00
|
|
|
|
case PROP_ACCEPT_UNPAIRED_RELEASE:
|
|
|
|
|
gtk_flow_box_set_accept_unpaired_release (box, g_value_get_boolean (value));
|
|
|
|
|
break;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
default:
|
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_finalize (GObject *obj)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (obj);
|
|
|
|
|
|
|
|
|
|
if (priv->filter_destroy != NULL)
|
|
|
|
|
priv->filter_destroy (priv->filter_data);
|
|
|
|
|
if (priv->sort_destroy != NULL)
|
|
|
|
|
priv->sort_destroy (priv->sort_data);
|
|
|
|
|
|
|
|
|
|
g_sequence_free (priv->children);
|
|
|
|
|
g_clear_object (&priv->hadjustment);
|
|
|
|
|
g_clear_object (&priv->vadjustment);
|
|
|
|
|
|
2015-08-16 22:05:33 +00:00
|
|
|
|
if (priv->bound_model)
|
|
|
|
|
{
|
|
|
|
|
if (priv->create_widget_func_data_destroy)
|
|
|
|
|
priv->create_widget_func_data_destroy (priv->create_widget_func_data);
|
|
|
|
|
|
|
|
|
|
g_signal_handlers_disconnect_by_func (priv->bound_model, gtk_flow_box_bound_model_changed, obj);
|
|
|
|
|
g_clear_object (&priv->bound_model);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
G_OBJECT_CLASS (gtk_flow_box_parent_class)->finalize (obj);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_class_init (GtkFlowBoxClass *class)
|
|
|
|
|
{
|
|
|
|
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
|
|
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
|
|
|
|
|
GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
|
|
|
|
|
|
|
|
|
|
object_class->finalize = gtk_flow_box_finalize;
|
|
|
|
|
object_class->get_property = gtk_flow_box_get_property;
|
|
|
|
|
object_class->set_property = gtk_flow_box_set_property;
|
|
|
|
|
|
|
|
|
|
widget_class->size_allocate = gtk_flow_box_size_allocate;
|
|
|
|
|
widget_class->unmap = gtk_flow_box_unmap;
|
|
|
|
|
widget_class->focus = gtk_flow_box_focus;
|
2016-11-15 15:11:19 +00:00
|
|
|
|
widget_class->snapshot = gtk_flow_box_snapshot;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
widget_class->get_request_mode = gtk_flow_box_get_request_mode;
|
2017-05-05 07:27:06 +00:00
|
|
|
|
widget_class->measure = gtk_flow_box_measure;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
container_class->add = gtk_flow_box_add;
|
|
|
|
|
container_class->remove = gtk_flow_box_remove;
|
|
|
|
|
container_class->forall = gtk_flow_box_forall;
|
|
|
|
|
container_class->child_type = gtk_flow_box_child_type;
|
|
|
|
|
|
|
|
|
|
class->activate_cursor_child = gtk_flow_box_activate_cursor_child;
|
|
|
|
|
class->toggle_cursor_child = gtk_flow_box_toggle_cursor_child;
|
|
|
|
|
class->move_cursor = gtk_flow_box_move_cursor;
|
|
|
|
|
class->select_all = gtk_flow_box_select_all;
|
|
|
|
|
class->unselect_all = gtk_flow_box_unselect_all;
|
|
|
|
|
class->selected_children_changed = gtk_flow_box_selected_children_changed;
|
|
|
|
|
|
|
|
|
|
g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBox:selection-mode:
|
|
|
|
|
*
|
|
|
|
|
* The selection mode used by the flow box.
|
|
|
|
|
*/
|
2014-06-07 04:39:00 +00:00
|
|
|
|
props[PROP_SELECTION_MODE] =
|
|
|
|
|
g_param_spec_enum ("selection-mode",
|
|
|
|
|
P_("Selection mode"),
|
|
|
|
|
P_("The selection mode"),
|
|
|
|
|
GTK_TYPE_SELECTION_MODE,
|
|
|
|
|
GTK_SELECTION_SINGLE,
|
2017-11-18 13:18:11 +00:00
|
|
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBox:activate-on-single-click:
|
|
|
|
|
*
|
|
|
|
|
* Determines whether children can be activated with a single
|
|
|
|
|
* click, or require a double-click.
|
|
|
|
|
*/
|
2014-06-07 04:39:00 +00:00
|
|
|
|
props[PROP_ACTIVATE_ON_SINGLE_CLICK] =
|
|
|
|
|
g_param_spec_boolean ("activate-on-single-click",
|
|
|
|
|
P_("Activate on Single Click"),
|
|
|
|
|
P_("Activate row on a single click"),
|
|
|
|
|
TRUE,
|
2017-11-18 13:18:11 +00:00
|
|
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
2017-12-31 04:23:16 +00:00
|
|
|
|
props[PROP_ACCEPT_UNPAIRED_RELEASE] =
|
|
|
|
|
g_param_spec_boolean ("accept-unpaired-release",
|
|
|
|
|
P_("Accept unpaired release"),
|
|
|
|
|
P_("Accept an unpaired release event"),
|
|
|
|
|
FALSE,
|
|
|
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBox:homogeneous:
|
|
|
|
|
*
|
|
|
|
|
* Determines whether all children should be allocated the
|
|
|
|
|
* same size.
|
|
|
|
|
*/
|
2014-06-07 04:39:00 +00:00
|
|
|
|
props[PROP_HOMOGENEOUS] =
|
|
|
|
|
g_param_spec_boolean ("homogeneous",
|
|
|
|
|
P_("Homogeneous"),
|
|
|
|
|
P_("Whether the children should all be the same size"),
|
|
|
|
|
FALSE,
|
2017-11-18 13:18:11 +00:00
|
|
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBox:min-children-per-line:
|
|
|
|
|
*
|
|
|
|
|
* The minimum number of children to allocate consecutively
|
|
|
|
|
* in the given orientation.
|
|
|
|
|
*
|
2014-02-02 06:22:14 +00:00
|
|
|
|
* Setting the minimum children per line ensures
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* that a reasonably small height will be requested
|
2014-02-02 06:22:14 +00:00
|
|
|
|
* for the overall minimum width of the box.
|
2013-09-29 17:43:27 +00:00
|
|
|
|
*/
|
2014-06-07 04:39:00 +00:00
|
|
|
|
props[PROP_MIN_CHILDREN_PER_LINE] =
|
|
|
|
|
g_param_spec_uint ("min-children-per-line",
|
|
|
|
|
P_("Minimum Children Per Line"),
|
|
|
|
|
P_("The minimum number of children to allocate "
|
|
|
|
|
"consecutively in the given orientation."),
|
|
|
|
|
0, G_MAXUINT, 0,
|
2017-11-18 13:18:11 +00:00
|
|
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBox:max-children-per-line:
|
|
|
|
|
*
|
|
|
|
|
* The maximum amount of children to request space for consecutively
|
|
|
|
|
* in the given orientation.
|
|
|
|
|
*/
|
2014-06-07 04:39:00 +00:00
|
|
|
|
props[PROP_MAX_CHILDREN_PER_LINE] =
|
|
|
|
|
g_param_spec_uint ("max-children-per-line",
|
|
|
|
|
P_("Maximum Children Per Line"),
|
|
|
|
|
P_("The maximum amount of children to request space for "
|
|
|
|
|
"consecutively in the given orientation."),
|
2016-11-12 21:57:31 +00:00
|
|
|
|
1, G_MAXUINT, DEFAULT_MAX_CHILDREN_PER_LINE,
|
2017-11-18 13:18:11 +00:00
|
|
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBox:row-spacing:
|
|
|
|
|
*
|
|
|
|
|
* The amount of vertical space between two children.
|
|
|
|
|
*/
|
2014-06-07 04:39:00 +00:00
|
|
|
|
props[PROP_ROW_SPACING] =
|
|
|
|
|
g_param_spec_uint ("row-spacing",
|
|
|
|
|
P_("Vertical spacing"),
|
|
|
|
|
P_("The amount of vertical space between two children"),
|
|
|
|
|
0, G_MAXUINT, 0,
|
2017-11-18 13:18:11 +00:00
|
|
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBox:column-spacing:
|
|
|
|
|
*
|
|
|
|
|
* The amount of horizontal space between two children.
|
|
|
|
|
*/
|
2014-06-07 04:39:00 +00:00
|
|
|
|
props[PROP_COLUMN_SPACING] =
|
|
|
|
|
g_param_spec_uint ("column-spacing",
|
|
|
|
|
P_("Horizontal spacing"),
|
|
|
|
|
P_("The amount of horizontal space between two children"),
|
|
|
|
|
0, G_MAXUINT, 0,
|
2017-11-18 13:18:11 +00:00
|
|
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
2014-06-07 04:39:00 +00:00
|
|
|
|
|
|
|
|
|
g_object_class_install_properties (object_class, LAST_PROP, props);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBox::child-activated:
|
|
|
|
|
* @box: the #GtkFlowBox on which the signal is emitted
|
|
|
|
|
* @child: the child that is activated
|
|
|
|
|
*
|
|
|
|
|
* The ::child-activated signal is emitted when a child has been
|
|
|
|
|
* activated by the user.
|
|
|
|
|
*/
|
2015-09-12 13:13:00 +00:00
|
|
|
|
signals[CHILD_ACTIVATED] = g_signal_new (I_("child-activated"),
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_TYPE_FLOW_BOX,
|
|
|
|
|
G_SIGNAL_RUN_LAST,
|
|
|
|
|
G_STRUCT_OFFSET (GtkFlowBoxClass, child_activated),
|
|
|
|
|
NULL, NULL,
|
2019-05-31 03:56:50 +00:00
|
|
|
|
NULL,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
G_TYPE_NONE, 1,
|
2014-02-05 21:10:05 +00:00
|
|
|
|
GTK_TYPE_FLOW_BOX_CHILD);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBox::selected-children-changed:
|
|
|
|
|
* @box: the #GtkFlowBox on wich the signal is emitted
|
|
|
|
|
*
|
|
|
|
|
* The ::selected-children-changed signal is emitted when the
|
|
|
|
|
* set of selected children changes.
|
|
|
|
|
*
|
|
|
|
|
* Use gtk_flow_box_selected_foreach() or
|
|
|
|
|
* gtk_flow_box_get_selected_children() to obtain the
|
|
|
|
|
* selected children.
|
|
|
|
|
*/
|
2015-09-12 13:13:00 +00:00
|
|
|
|
signals[SELECTED_CHILDREN_CHANGED] = g_signal_new (I_("selected-children-changed"),
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_TYPE_FLOW_BOX,
|
|
|
|
|
G_SIGNAL_RUN_FIRST,
|
|
|
|
|
G_STRUCT_OFFSET (GtkFlowBoxClass, selected_children_changed),
|
|
|
|
|
NULL, NULL,
|
2019-05-29 20:05:19 +00:00
|
|
|
|
NULL,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
G_TYPE_NONE, 0);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBox::activate-cursor-child:
|
|
|
|
|
* @box: the #GtkFlowBox on which the signal is emitted
|
|
|
|
|
*
|
|
|
|
|
* The ::activate-cursor-child signal is a
|
2014-02-07 20:37:02 +00:00
|
|
|
|
* [keybinding signal][GtkBindingSignal]
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* which gets emitted when the user activates the @box.
|
|
|
|
|
*/
|
2015-09-12 13:13:00 +00:00
|
|
|
|
signals[ACTIVATE_CURSOR_CHILD] = g_signal_new (I_("activate-cursor-child"),
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_TYPE_FLOW_BOX,
|
|
|
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
|
|
|
G_STRUCT_OFFSET (GtkFlowBoxClass, activate_cursor_child),
|
|
|
|
|
NULL, NULL,
|
2019-05-29 20:05:19 +00:00
|
|
|
|
NULL,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
G_TYPE_NONE, 0);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBox::toggle-cursor-child:
|
|
|
|
|
* @box: the #GtkFlowBox on which the signal is emitted
|
|
|
|
|
*
|
|
|
|
|
* The ::toggle-cursor-child signal is a
|
2014-02-07 20:37:02 +00:00
|
|
|
|
* [keybinding signal][GtkBindingSignal]
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* which toggles the selection of the child that has the focus.
|
|
|
|
|
*
|
|
|
|
|
* The default binding for this signal is Ctrl-Space.
|
|
|
|
|
*/
|
2015-09-12 13:13:00 +00:00
|
|
|
|
signals[TOGGLE_CURSOR_CHILD] = g_signal_new (I_("toggle-cursor-child"),
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_TYPE_FLOW_BOX,
|
|
|
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
|
|
|
G_STRUCT_OFFSET (GtkFlowBoxClass, toggle_cursor_child),
|
|
|
|
|
NULL, NULL,
|
2019-05-29 20:05:19 +00:00
|
|
|
|
NULL,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
G_TYPE_NONE, 0);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBox::move-cursor:
|
|
|
|
|
* @box: the #GtkFlowBox on which the signal is emitted
|
|
|
|
|
* @step: the granularity fo the move, as a #GtkMovementStep
|
|
|
|
|
* @count: the number of @step units to move
|
2020-04-11 18:52:43 +00:00
|
|
|
|
* @extend: whether to extend the selection
|
|
|
|
|
* @modify: whether to modify the selection
|
2013-09-29 17:43:27 +00:00
|
|
|
|
*
|
|
|
|
|
* The ::move-cursor signal is a
|
2014-02-07 20:37:02 +00:00
|
|
|
|
* [keybinding signal][GtkBindingSignal]
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* which gets emitted when the user initiates a cursor movement.
|
|
|
|
|
*
|
|
|
|
|
* Applications should not connect to it, but may emit it with
|
|
|
|
|
* g_signal_emit_by_name() if they need to control the cursor
|
|
|
|
|
* programmatically.
|
|
|
|
|
*
|
|
|
|
|
* The default bindings for this signal come in two variants,
|
|
|
|
|
* the variant with the Shift modifier extends the selection,
|
|
|
|
|
* the variant without the Shift modifer does not.
|
|
|
|
|
* There are too many key combinations to list them all here.
|
2014-02-02 06:07:39 +00:00
|
|
|
|
* - Arrow keys move by individual children
|
|
|
|
|
* - Home/End keys move to the ends of the box
|
|
|
|
|
* - PageUp/PageDown keys move vertically by pages
|
2015-08-23 00:32:21 +00:00
|
|
|
|
*
|
|
|
|
|
* Returns: %TRUE to stop other handlers from being invoked for the event.
|
|
|
|
|
* %FALSE to propagate the event further.
|
2013-09-29 17:43:27 +00:00
|
|
|
|
*/
|
2015-09-12 13:13:00 +00:00
|
|
|
|
signals[MOVE_CURSOR] = g_signal_new (I_("move-cursor"),
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_TYPE_FLOW_BOX,
|
|
|
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
|
|
|
G_STRUCT_OFFSET (GtkFlowBoxClass, move_cursor),
|
|
|
|
|
NULL, NULL,
|
2020-04-11 18:52:43 +00:00
|
|
|
|
_gtk_marshal_BOOLEAN__ENUM_INT_BOOLEAN_BOOLEAN,
|
|
|
|
|
G_TYPE_BOOLEAN, 4,
|
|
|
|
|
GTK_TYPE_MOVEMENT_STEP, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
|
2019-06-06 22:27:54 +00:00
|
|
|
|
g_signal_set_va_marshaller (signals[MOVE_CURSOR],
|
|
|
|
|
G_TYPE_FROM_CLASS (class),
|
2020-04-11 18:52:43 +00:00
|
|
|
|
_gtk_marshal_BOOLEAN__ENUM_INT_BOOLEAN_BOOLEANv);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBox::select-all:
|
|
|
|
|
* @box: the #GtkFlowBox on which the signal is emitted
|
|
|
|
|
*
|
|
|
|
|
* The ::select-all signal is a
|
2014-02-07 20:37:02 +00:00
|
|
|
|
* [keybinding signal][GtkBindingSignal]
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* which gets emitted to select all children of the box, if
|
|
|
|
|
* the selection mode permits it.
|
|
|
|
|
*
|
|
|
|
|
* The default bindings for this signal is Ctrl-a.
|
|
|
|
|
*/
|
2015-09-12 13:13:00 +00:00
|
|
|
|
signals[SELECT_ALL] = g_signal_new (I_("select-all"),
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_TYPE_FLOW_BOX,
|
|
|
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
|
|
|
G_STRUCT_OFFSET (GtkFlowBoxClass, select_all),
|
|
|
|
|
NULL, NULL,
|
2019-05-29 20:05:19 +00:00
|
|
|
|
NULL,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
G_TYPE_NONE, 0);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBox::unselect-all:
|
|
|
|
|
* @box: the #GtkFlowBox on which the signal is emitted
|
|
|
|
|
*
|
|
|
|
|
* The ::unselect-all signal is a
|
2014-02-07 20:37:02 +00:00
|
|
|
|
* [keybinding signal][GtkBindingSignal]
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* which gets emitted to unselect all children of the box, if
|
|
|
|
|
* the selection mode permits it.
|
|
|
|
|
*
|
|
|
|
|
* The default bindings for this signal is Ctrl-Shift-a.
|
|
|
|
|
*/
|
2015-09-12 13:13:00 +00:00
|
|
|
|
signals[UNSELECT_ALL] = g_signal_new (I_("unselect-all"),
|
|
|
|
|
GTK_TYPE_FLOW_BOX,
|
|
|
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
|
|
|
G_STRUCT_OFFSET (GtkFlowBoxClass, unselect_all),
|
|
|
|
|
NULL, NULL,
|
2019-05-29 20:05:19 +00:00
|
|
|
|
NULL,
|
2015-09-12 13:13:00 +00:00
|
|
|
|
G_TYPE_NONE, 0);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
widget_class->activate_signal = signals[ACTIVATE_CURSOR_CHILD];
|
|
|
|
|
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Home, 0,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_MOVEMENT_BUFFER_ENDS, -1);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_KP_Home, 0,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_MOVEMENT_BUFFER_ENDS, -1);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_End, 0,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_MOVEMENT_BUFFER_ENDS, 1);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_KP_End, 0,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_MOVEMENT_BUFFER_ENDS, 1);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Up, 0,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_MOVEMENT_DISPLAY_LINES, -1);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_KP_Up, 0,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_MOVEMENT_DISPLAY_LINES, -1);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Down, 0,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_MOVEMENT_DISPLAY_LINES, 1);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_KP_Down, 0,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_MOVEMENT_DISPLAY_LINES, 1);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Page_Up, 0,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_MOVEMENT_PAGES, -1);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_KP_Page_Up, 0,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_MOVEMENT_PAGES, -1);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Page_Down, 0,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_MOVEMENT_PAGES, 1);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_KP_Page_Down, 0,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_MOVEMENT_PAGES, 1);
|
|
|
|
|
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Right, 0,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_MOVEMENT_VISUAL_POSITIONS, 1);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_KP_Right, 0,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_MOVEMENT_VISUAL_POSITIONS, 1);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Left, 0,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_MOVEMENT_VISUAL_POSITIONS, -1);
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_KP_Left, 0,
|
2013-09-29 17:43:27 +00:00
|
|
|
|
GTK_MOVEMENT_VISUAL_POSITIONS, -1);
|
|
|
|
|
|
2018-08-12 01:45:09 +00:00
|
|
|
|
gtk_widget_class_add_binding_signal (widget_class,
|
|
|
|
|
GDK_KEY_space, GDK_CONTROL_MASK,
|
|
|
|
|
"toggle-cursor-child",
|
|
|
|
|
NULL);
|
|
|
|
|
gtk_widget_class_add_binding_signal (widget_class,
|
|
|
|
|
GDK_KEY_KP_Space, GDK_CONTROL_MASK,
|
|
|
|
|
"toggle-cursor-child",
|
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
|
|
gtk_widget_class_add_binding_signal (widget_class,
|
|
|
|
|
GDK_KEY_a, GDK_CONTROL_MASK,
|
|
|
|
|
"select-all",
|
|
|
|
|
NULL);
|
|
|
|
|
gtk_widget_class_add_binding_signal (widget_class,
|
|
|
|
|
GDK_KEY_a, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
|
|
|
|
|
"unselect-all",
|
|
|
|
|
NULL);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_FLOW_BOX_ACCESSIBLE);
|
2017-11-18 03:49:57 +00:00
|
|
|
|
gtk_widget_class_set_css_name (widget_class, I_("flowbox"));
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_init (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
2018-04-20 17:58:06 +00:00
|
|
|
|
GtkEventController *controller;
|
2018-03-09 05:14:59 +00:00
|
|
|
|
GtkGesture *gesture;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
priv->orientation = GTK_ORIENTATION_HORIZONTAL;
|
|
|
|
|
priv->selection_mode = GTK_SELECTION_SINGLE;
|
|
|
|
|
priv->max_children_per_line = DEFAULT_MAX_CHILDREN_PER_LINE;
|
|
|
|
|
priv->column_spacing = 0;
|
|
|
|
|
priv->row_spacing = 0;
|
|
|
|
|
priv->activate_on_single_click = TRUE;
|
|
|
|
|
|
2014-07-18 23:38:16 +00:00
|
|
|
|
_gtk_orientable_set_style_classes (GTK_ORIENTABLE (box));
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
priv->children = g_sequence_new (NULL);
|
2014-06-05 13:45:21 +00:00
|
|
|
|
|
2019-05-29 17:10:46 +00:00
|
|
|
|
gesture = gtk_gesture_click_new ();
|
2018-03-09 05:14:59 +00:00
|
|
|
|
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture),
|
2014-06-05 13:45:21 +00:00
|
|
|
|
FALSE);
|
2018-03-09 05:14:59 +00:00
|
|
|
|
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture),
|
2014-06-05 13:45:21 +00:00
|
|
|
|
GDK_BUTTON_PRIMARY);
|
2018-03-09 05:14:59 +00:00
|
|
|
|
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
|
2014-06-05 13:45:21 +00:00
|
|
|
|
GTK_PHASE_BUBBLE);
|
2018-03-09 05:14:59 +00:00
|
|
|
|
g_signal_connect (gesture, "pressed",
|
2019-05-29 17:10:46 +00:00
|
|
|
|
G_CALLBACK (gtk_flow_box_click_gesture_pressed), box);
|
2018-03-09 05:14:59 +00:00
|
|
|
|
g_signal_connect (gesture, "released",
|
2019-05-29 17:10:46 +00:00
|
|
|
|
G_CALLBACK (gtk_flow_box_click_gesture_released), box);
|
2018-03-09 05:14:59 +00:00
|
|
|
|
g_signal_connect (gesture, "stopped",
|
2019-05-29 17:10:46 +00:00
|
|
|
|
G_CALLBACK (gtk_flow_box_click_gesture_stopped), box);
|
2018-03-09 05:14:59 +00:00
|
|
|
|
g_signal_connect (gesture, "unpaired-release",
|
2019-05-29 17:10:46 +00:00
|
|
|
|
G_CALLBACK (gtk_flow_box_click_unpaired_release), box);
|
2018-03-09 05:14:59 +00:00
|
|
|
|
gtk_widget_add_controller (GTK_WIDGET (box), GTK_EVENT_CONTROLLER (gesture));
|
2014-06-05 13:45:21 +00:00
|
|
|
|
|
2018-03-10 17:05:57 +00:00
|
|
|
|
priv->drag_gesture = gtk_gesture_drag_new ();
|
2014-06-05 13:45:21 +00:00
|
|
|
|
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->drag_gesture),
|
|
|
|
|
FALSE);
|
|
|
|
|
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->drag_gesture),
|
|
|
|
|
GDK_BUTTON_PRIMARY);
|
|
|
|
|
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->drag_gesture),
|
|
|
|
|
GTK_PHASE_CAPTURE);
|
|
|
|
|
g_signal_connect (priv->drag_gesture, "drag-begin",
|
|
|
|
|
G_CALLBACK (gtk_flow_box_drag_gesture_begin), box);
|
|
|
|
|
g_signal_connect (priv->drag_gesture, "drag-update",
|
|
|
|
|
G_CALLBACK (gtk_flow_box_drag_gesture_update), box);
|
|
|
|
|
g_signal_connect (priv->drag_gesture, "drag-end",
|
|
|
|
|
G_CALLBACK (gtk_flow_box_drag_gesture_end), box);
|
2018-03-10 17:05:57 +00:00
|
|
|
|
gtk_widget_add_controller (GTK_WIDGET (box), GTK_EVENT_CONTROLLER (priv->drag_gesture));
|
2018-03-05 14:00:06 +00:00
|
|
|
|
|
2018-04-20 17:58:06 +00:00
|
|
|
|
controller = gtk_event_controller_key_new ();
|
|
|
|
|
g_signal_connect (controller, "key-pressed",
|
2018-03-05 14:00:06 +00:00
|
|
|
|
G_CALLBACK (gtk_flow_box_key_controller_key_pressed), box);
|
2018-04-20 17:58:06 +00:00
|
|
|
|
gtk_widget_add_controller (GTK_WIDGET (box), controller);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
2015-08-16 22:05:33 +00:00
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_bound_model_changed (GListModel *list,
|
|
|
|
|
guint position,
|
|
|
|
|
guint removed,
|
|
|
|
|
guint added,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBox *box = user_data;
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
gint i;
|
|
|
|
|
|
|
|
|
|
while (removed--)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxChild *child;
|
|
|
|
|
|
|
|
|
|
child = gtk_flow_box_get_child_at_index (box, position);
|
|
|
|
|
gtk_widget_destroy (GTK_WIDGET (child));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < added; i++)
|
|
|
|
|
{
|
|
|
|
|
GObject *item;
|
|
|
|
|
GtkWidget *widget;
|
|
|
|
|
|
|
|
|
|
item = g_list_model_get_item (list, position + i);
|
|
|
|
|
widget = priv->create_widget_func (item, priv->create_widget_func_data);
|
2015-08-17 12:32:35 +00:00
|
|
|
|
|
|
|
|
|
/* We need to sink the floating reference here, so that we can accept
|
|
|
|
|
* both instances created with a floating reference (e.g. C functions
|
|
|
|
|
* that just return the result of g_object_new()) and without (e.g.
|
|
|
|
|
* from language bindings which will automatically sink the floating
|
|
|
|
|
* reference).
|
|
|
|
|
*
|
|
|
|
|
* See the similar code in gtklistbox.c:gtk_list_box_bound_model_changed.
|
|
|
|
|
*/
|
|
|
|
|
if (g_object_is_floating (widget))
|
|
|
|
|
g_object_ref_sink (widget);
|
|
|
|
|
|
2015-08-16 22:05:33 +00:00
|
|
|
|
gtk_widget_show (widget);
|
|
|
|
|
gtk_flow_box_insert (box, widget, position + i);
|
|
|
|
|
|
2015-08-17 12:32:35 +00:00
|
|
|
|
g_object_unref (widget);
|
2015-08-16 22:05:33 +00:00
|
|
|
|
g_object_unref (item);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
/* Public API {{{2 */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_new:
|
|
|
|
|
*
|
|
|
|
|
* Creates a GtkFlowBox.
|
|
|
|
|
*
|
|
|
|
|
* Returns: a new #GtkFlowBox container
|
|
|
|
|
*/
|
|
|
|
|
GtkWidget *
|
|
|
|
|
gtk_flow_box_new (void)
|
|
|
|
|
{
|
|
|
|
|
return (GtkWidget *)g_object_new (GTK_TYPE_FLOW_BOX, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-30 03:37:42 +00:00
|
|
|
|
static void
|
2015-05-30 03:59:55 +00:00
|
|
|
|
gtk_flow_box_insert_css_node (GtkFlowBox *box,
|
2015-05-30 03:37:42 +00:00
|
|
|
|
GtkWidget *child,
|
|
|
|
|
GSequenceIter *iter)
|
|
|
|
|
{
|
|
|
|
|
GSequenceIter *prev_iter;
|
|
|
|
|
GtkWidget *sibling;
|
|
|
|
|
|
|
|
|
|
prev_iter = g_sequence_iter_prev (iter);
|
|
|
|
|
|
|
|
|
|
if (prev_iter != iter)
|
|
|
|
|
{
|
|
|
|
|
sibling = g_sequence_get (prev_iter);
|
2015-07-12 19:39:49 +00:00
|
|
|
|
gtk_css_node_insert_after (gtk_widget_get_css_node (GTK_WIDGET (box)),
|
|
|
|
|
gtk_widget_get_css_node (child),
|
|
|
|
|
gtk_widget_get_css_node (sibling));
|
2015-05-30 03:37:42 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_insert:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @widget: the #GtkWidget to add
|
|
|
|
|
* @position: the position to insert @child in
|
|
|
|
|
*
|
|
|
|
|
* Inserts the @widget into @box at @position.
|
|
|
|
|
*
|
|
|
|
|
* If a sort function is set, the widget will actually be inserted
|
|
|
|
|
* at the calculated position and this function has the same effect
|
|
|
|
|
* as gtk_container_add().
|
|
|
|
|
*
|
|
|
|
|
* If @position is -1, or larger than the total number of children
|
|
|
|
|
* in the @box, then the @widget will be appended to the end.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_insert (GtkFlowBox *box,
|
|
|
|
|
GtkWidget *widget,
|
|
|
|
|
gint position)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv;
|
|
|
|
|
GtkFlowBoxChild *child;
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
|
|
|
|
|
|
|
|
priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
if (GTK_IS_FLOW_BOX_CHILD (widget))
|
|
|
|
|
child = GTK_FLOW_BOX_CHILD (widget);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
child = GTK_FLOW_BOX_CHILD (gtk_flow_box_child_new ());
|
2020-05-03 00:53:40 +00:00
|
|
|
|
gtk_flow_box_child_set_child (child, widget);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (priv->sort_func != NULL)
|
|
|
|
|
iter = g_sequence_insert_sorted (priv->children, child,
|
|
|
|
|
(GCompareDataFunc)gtk_flow_box_sort, box);
|
|
|
|
|
else if (position == 0)
|
|
|
|
|
iter = g_sequence_prepend (priv->children, child);
|
|
|
|
|
else if (position == -1)
|
|
|
|
|
iter = g_sequence_append (priv->children, child);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
GSequenceIter *pos;
|
|
|
|
|
pos = g_sequence_get_iter_at_pos (priv->children, position);
|
|
|
|
|
iter = g_sequence_insert_before (pos, child);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-30 03:37:42 +00:00
|
|
|
|
gtk_flow_box_insert_css_node (box, GTK_WIDGET (child), iter);
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
CHILD_PRIV (child)->iter = iter;
|
|
|
|
|
gtk_widget_set_parent (GTK_WIDGET (child), GTK_WIDGET (box));
|
|
|
|
|
gtk_flow_box_apply_filter (box, child);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_get_child_at_index:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @idx: the position of the child
|
|
|
|
|
*
|
|
|
|
|
* Gets the nth child in the @box.
|
|
|
|
|
*
|
2015-12-10 15:54:59 +00:00
|
|
|
|
* Returns: (transfer none) (nullable): the child widget, which will
|
|
|
|
|
* always be a #GtkFlowBoxChild or %NULL in case no child widget
|
|
|
|
|
* with the given index exists.
|
2013-09-29 17:43:27 +00:00
|
|
|
|
*/
|
|
|
|
|
GtkFlowBoxChild *
|
|
|
|
|
gtk_flow_box_get_child_at_index (GtkFlowBox *box,
|
|
|
|
|
gint idx)
|
|
|
|
|
{
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
|
|
|
|
|
g_return_val_if_fail (GTK_IS_FLOW_BOX (box), NULL);
|
|
|
|
|
|
|
|
|
|
iter = g_sequence_get_iter_at_pos (BOX_PRIV (box)->children, idx);
|
2016-12-02 15:38:05 +00:00
|
|
|
|
if (!g_sequence_iter_is_end (iter))
|
2013-09-29 17:43:27 +00:00
|
|
|
|
return g_sequence_get (iter);
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-16 23:30:39 +00:00
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_get_child_at_pos:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @x: the x coordinate of the child
|
|
|
|
|
* @y: the y coordinate of the child
|
|
|
|
|
*
|
2017-05-10 10:11:58 +00:00
|
|
|
|
* Gets the child in the (@x, @y) position. Both @x and @y are
|
2019-02-14 06:11:34 +00:00
|
|
|
|
* assumed to be relative to the origin of @box.
|
2016-12-16 23:30:39 +00:00
|
|
|
|
*
|
|
|
|
|
* Returns: (transfer none) (nullable): the child widget, which will
|
|
|
|
|
* always be a #GtkFlowBoxChild or %NULL in case no child widget
|
|
|
|
|
* exists for the given x and y coordinates.
|
|
|
|
|
*/
|
|
|
|
|
GtkFlowBoxChild *
|
|
|
|
|
gtk_flow_box_get_child_at_pos (GtkFlowBox *box,
|
|
|
|
|
gint x,
|
|
|
|
|
gint y)
|
|
|
|
|
{
|
2019-04-07 21:36:40 +00:00
|
|
|
|
GtkWidget *child = gtk_widget_pick (GTK_WIDGET (box), x, y, GTK_PICK_DEFAULT);
|
2017-05-10 10:11:58 +00:00
|
|
|
|
|
2019-02-14 06:11:34 +00:00
|
|
|
|
if (!child)
|
|
|
|
|
return NULL;
|
2016-12-16 23:30:39 +00:00
|
|
|
|
|
2019-02-14 06:11:34 +00:00
|
|
|
|
return (GtkFlowBoxChild *)gtk_widget_get_ancestor (child, GTK_TYPE_FLOW_BOX_CHILD);
|
2016-12-16 23:30:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_set_hadjustment:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @adjustment: an adjustment which should be adjusted
|
|
|
|
|
* when the focus is moved among the descendents of @container
|
|
|
|
|
*
|
2013-10-07 03:15:27 +00:00
|
|
|
|
* Hooks up an adjustment to focus handling in @box.
|
|
|
|
|
* The adjustment is also used for autoscrolling during
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* rubberband selection. See gtk_scrolled_window_get_hadjustment()
|
|
|
|
|
* for a typical way of obtaining the adjustment, and
|
|
|
|
|
* gtk_flow_box_set_vadjustment()for setting the vertical
|
|
|
|
|
* adjustment.
|
|
|
|
|
*
|
|
|
|
|
* The adjustments have to be in pixel units and in the same
|
|
|
|
|
* coordinate system as the allocation for immediate children
|
|
|
|
|
* of the box.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_set_hadjustment (GtkFlowBox *box,
|
|
|
|
|
GtkAdjustment *adjustment)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv;
|
|
|
|
|
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
|
|
|
|
|
|
|
|
|
|
priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
g_object_ref (adjustment);
|
|
|
|
|
if (priv->hadjustment)
|
|
|
|
|
g_object_unref (priv->hadjustment);
|
|
|
|
|
priv->hadjustment = adjustment;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_set_vadjustment:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @adjustment: an adjustment which should be adjusted
|
|
|
|
|
* when the focus is moved among the descendents of @container
|
|
|
|
|
*
|
2013-10-07 03:15:27 +00:00
|
|
|
|
* Hooks up an adjustment to focus handling in @box.
|
|
|
|
|
* The adjustment is also used for autoscrolling during
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* rubberband selection. See gtk_scrolled_window_get_vadjustment()
|
|
|
|
|
* for a typical way of obtaining the adjustment, and
|
|
|
|
|
* gtk_flow_box_set_hadjustment()for setting the horizontal
|
|
|
|
|
* adjustment.
|
|
|
|
|
*
|
|
|
|
|
* The adjustments have to be in pixel units and in the same
|
|
|
|
|
* coordinate system as the allocation for immediate children
|
|
|
|
|
* of the box.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_set_vadjustment (GtkFlowBox *box,
|
|
|
|
|
GtkAdjustment *adjustment)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv;
|
|
|
|
|
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
|
|
|
|
|
|
|
|
|
|
priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
g_object_ref (adjustment);
|
|
|
|
|
if (priv->vadjustment)
|
|
|
|
|
g_object_unref (priv->vadjustment);
|
|
|
|
|
priv->vadjustment = adjustment;
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-25 17:28:49 +00:00
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_check_model_compat (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
if (priv->bound_model &&
|
|
|
|
|
(priv->sort_func || priv->filter_func))
|
|
|
|
|
g_warning ("GtkFlowBox with a model will ignore sort and filter functions");
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-16 22:05:33 +00:00
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_bind_model:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @model: (allow-none): the #GListModel to be bound to @box
|
|
|
|
|
* @create_widget_func: a function that creates widgets for items
|
2020-01-13 01:26:50 +00:00
|
|
|
|
* @user_data: (closure): user data passed to @create_widget_func
|
2015-08-16 22:05:33 +00:00
|
|
|
|
* @user_data_free_func: function for freeing @user_data
|
|
|
|
|
*
|
|
|
|
|
* Binds @model to @box.
|
|
|
|
|
*
|
|
|
|
|
* If @box was already bound to a model, that previous binding is
|
|
|
|
|
* destroyed.
|
|
|
|
|
*
|
|
|
|
|
* The contents of @box are cleared and then filled with widgets that
|
|
|
|
|
* represent items from @model. @box is updated whenever @model changes.
|
|
|
|
|
* If @model is %NULL, @box is left empty.
|
|
|
|
|
*
|
|
|
|
|
* It is undefined to add or remove widgets directly (for example, with
|
|
|
|
|
* gtk_flow_box_insert() or gtk_container_add()) while @box is bound to a
|
|
|
|
|
* model.
|
|
|
|
|
*
|
2015-08-25 17:58:40 +00:00
|
|
|
|
* Note that using a model is incompatible with the filtering and sorting
|
|
|
|
|
* functionality in GtkFlowBox. When using a model, filtering and sorting
|
|
|
|
|
* should be implemented by the model.
|
2015-08-16 22:05:33 +00:00
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_bind_model (GtkFlowBox *box,
|
|
|
|
|
GListModel *model,
|
|
|
|
|
GtkFlowBoxCreateWidgetFunc create_widget_func,
|
|
|
|
|
gpointer user_data,
|
|
|
|
|
GDestroyNotify user_data_free_func)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
|
|
|
|
|
g_return_if_fail (model == NULL || create_widget_func != NULL);
|
|
|
|
|
|
|
|
|
|
if (priv->bound_model)
|
|
|
|
|
{
|
|
|
|
|
if (priv->create_widget_func_data_destroy)
|
|
|
|
|
priv->create_widget_func_data_destroy (priv->create_widget_func_data);
|
|
|
|
|
|
|
|
|
|
g_signal_handlers_disconnect_by_func (priv->bound_model, gtk_flow_box_bound_model_changed, box);
|
|
|
|
|
g_clear_object (&priv->bound_model);
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-22 20:16:27 +00:00
|
|
|
|
gtk_flow_box_forall (GTK_CONTAINER (box), (GtkCallback) gtk_widget_destroy, NULL);
|
2015-08-16 22:05:33 +00:00
|
|
|
|
|
|
|
|
|
if (model == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
priv->bound_model = g_object_ref (model);
|
|
|
|
|
priv->create_widget_func = create_widget_func;
|
|
|
|
|
priv->create_widget_func_data = user_data;
|
|
|
|
|
priv->create_widget_func_data_destroy = user_data_free_func;
|
|
|
|
|
|
2015-08-25 17:28:49 +00:00
|
|
|
|
gtk_flow_box_check_model_compat (box);
|
|
|
|
|
|
2015-08-16 22:05:33 +00:00
|
|
|
|
g_signal_connect (priv->bound_model, "items-changed", G_CALLBACK (gtk_flow_box_bound_model_changed), box);
|
|
|
|
|
gtk_flow_box_bound_model_changed (model, 0, 0, g_list_model_get_n_items (model), box);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
/* Setters and getters {{{2 */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_get_homogeneous:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
*
|
|
|
|
|
* Returns whether the box is homogeneous (all children are the
|
|
|
|
|
* same size). See gtk_box_set_homogeneous().
|
|
|
|
|
*
|
2014-02-19 23:49:43 +00:00
|
|
|
|
* Returns: %TRUE if the box is homogeneous.
|
2013-09-29 17:43:27 +00:00
|
|
|
|
*/
|
|
|
|
|
gboolean
|
|
|
|
|
gtk_flow_box_get_homogeneous (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
|
|
|
|
|
|
|
|
|
|
return BOX_PRIV (box)->homogeneous;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_set_homogeneous:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @homogeneous: %TRUE to create equal allotments,
|
|
|
|
|
* %FALSE for variable allotments
|
|
|
|
|
*
|
|
|
|
|
* Sets the #GtkFlowBox:homogeneous property of @box, controlling
|
|
|
|
|
* whether or not all children of @box are given equal space
|
|
|
|
|
* in the box.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_set_homogeneous (GtkFlowBox *box,
|
|
|
|
|
gboolean homogeneous)
|
|
|
|
|
{
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
|
|
|
|
|
homogeneous = homogeneous != FALSE;
|
|
|
|
|
|
|
|
|
|
if (BOX_PRIV (box)->homogeneous != homogeneous)
|
|
|
|
|
{
|
|
|
|
|
BOX_PRIV (box)->homogeneous = homogeneous;
|
|
|
|
|
|
2014-06-07 04:39:00 +00:00
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_HOMOGENEOUS]);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
gtk_widget_queue_resize (GTK_WIDGET (box));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_set_row_spacing:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @spacing: the spacing to use
|
|
|
|
|
*
|
|
|
|
|
* Sets the vertical space to add between children.
|
|
|
|
|
* See the #GtkFlowBox:row-spacing property.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_set_row_spacing (GtkFlowBox *box,
|
|
|
|
|
guint spacing)
|
|
|
|
|
{
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
|
|
|
|
|
if (BOX_PRIV (box)->row_spacing != spacing)
|
|
|
|
|
{
|
|
|
|
|
BOX_PRIV (box)->row_spacing = spacing;
|
|
|
|
|
|
|
|
|
|
gtk_widget_queue_resize (GTK_WIDGET (box));
|
2014-06-07 04:39:00 +00:00
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_ROW_SPACING]);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_get_row_spacing:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
*
|
|
|
|
|
* Gets the vertical spacing.
|
|
|
|
|
*
|
|
|
|
|
* Returns: the vertical spacing
|
|
|
|
|
*/
|
|
|
|
|
guint
|
|
|
|
|
gtk_flow_box_get_row_spacing (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
|
|
|
|
|
|
|
|
|
|
return BOX_PRIV (box)->row_spacing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_set_column_spacing:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @spacing: the spacing to use
|
|
|
|
|
*
|
|
|
|
|
* Sets the horizontal space to add between children.
|
|
|
|
|
* See the #GtkFlowBox:column-spacing property.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_set_column_spacing (GtkFlowBox *box,
|
|
|
|
|
guint spacing)
|
|
|
|
|
{
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
|
|
|
|
|
if (BOX_PRIV (box)->column_spacing != spacing)
|
|
|
|
|
{
|
|
|
|
|
BOX_PRIV (box)->column_spacing = spacing;
|
|
|
|
|
|
|
|
|
|
gtk_widget_queue_resize (GTK_WIDGET (box));
|
2014-06-07 04:39:00 +00:00
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_COLUMN_SPACING]);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_get_column_spacing:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
*
|
|
|
|
|
* Gets the horizontal spacing.
|
|
|
|
|
*
|
|
|
|
|
* Returns: the horizontal spacing
|
|
|
|
|
*/
|
|
|
|
|
guint
|
|
|
|
|
gtk_flow_box_get_column_spacing (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
|
|
|
|
|
|
|
|
|
|
return BOX_PRIV (box)->column_spacing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_set_min_children_per_line:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @n_children: the minimum number of children per line
|
|
|
|
|
*
|
|
|
|
|
* Sets the minimum number of children to line up
|
2014-02-07 18:01:26 +00:00
|
|
|
|
* in @box’s orientation before flowing.
|
2013-09-29 17:43:27 +00:00
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_set_min_children_per_line (GtkFlowBox *box,
|
|
|
|
|
guint n_children)
|
|
|
|
|
{
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
|
|
|
|
|
if (BOX_PRIV (box)->min_children_per_line != n_children)
|
|
|
|
|
{
|
|
|
|
|
BOX_PRIV (box)->min_children_per_line = n_children;
|
|
|
|
|
|
|
|
|
|
gtk_widget_queue_resize (GTK_WIDGET (box));
|
2014-06-07 04:39:00 +00:00
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_MIN_CHILDREN_PER_LINE]);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_get_min_children_per_line:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
*
|
|
|
|
|
* Gets the minimum number of children per line.
|
|
|
|
|
*
|
|
|
|
|
* Returns: the minimum number of children per line
|
|
|
|
|
*/
|
|
|
|
|
guint
|
|
|
|
|
gtk_flow_box_get_min_children_per_line (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
|
|
|
|
|
|
|
|
|
|
return BOX_PRIV (box)->min_children_per_line;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_set_max_children_per_line:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @n_children: the maximum number of children per line
|
|
|
|
|
*
|
|
|
|
|
* Sets the maximum number of children to request and
|
2014-02-07 18:01:26 +00:00
|
|
|
|
* allocate space for in @box’s orientation.
|
2013-09-29 17:43:27 +00:00
|
|
|
|
*
|
|
|
|
|
* Setting the maximum number of children per line
|
|
|
|
|
* limits the overall natural size request to be no more
|
|
|
|
|
* than @n_children children long in the given orientation.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_set_max_children_per_line (GtkFlowBox *box,
|
|
|
|
|
guint n_children)
|
|
|
|
|
{
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
2016-11-12 21:57:31 +00:00
|
|
|
|
g_return_if_fail (n_children > 0);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
if (BOX_PRIV (box)->max_children_per_line != n_children)
|
|
|
|
|
{
|
|
|
|
|
BOX_PRIV (box)->max_children_per_line = n_children;
|
|
|
|
|
|
|
|
|
|
gtk_widget_queue_resize (GTK_WIDGET (box));
|
2014-06-07 04:39:00 +00:00
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_MAX_CHILDREN_PER_LINE]);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_get_max_children_per_line:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
*
|
|
|
|
|
* Gets the maximum number of children per line.
|
|
|
|
|
*
|
|
|
|
|
* Returns: the maximum number of children per line
|
|
|
|
|
*/
|
|
|
|
|
guint
|
|
|
|
|
gtk_flow_box_get_max_children_per_line (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
|
|
|
|
|
|
|
|
|
|
return BOX_PRIV (box)->max_children_per_line;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_set_activate_on_single_click:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @single: %TRUE to emit child-activated on a single click
|
|
|
|
|
*
|
|
|
|
|
* If @single is %TRUE, children will be activated when you click
|
|
|
|
|
* on them, otherwise you need to double-click.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_set_activate_on_single_click (GtkFlowBox *box,
|
|
|
|
|
gboolean single)
|
|
|
|
|
{
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
|
|
|
|
|
single = single != FALSE;
|
|
|
|
|
|
|
|
|
|
if (BOX_PRIV (box)->activate_on_single_click != single)
|
|
|
|
|
{
|
|
|
|
|
BOX_PRIV (box)->activate_on_single_click = single;
|
2014-06-07 04:39:00 +00:00
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_ACTIVATE_ON_SINGLE_CLICK]);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_get_activate_on_single_click:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
*
|
|
|
|
|
* Returns whether children activate on single clicks.
|
|
|
|
|
*
|
|
|
|
|
* Returns: %TRUE if children are activated on single click,
|
|
|
|
|
* %FALSE otherwise
|
|
|
|
|
*/
|
|
|
|
|
gboolean
|
|
|
|
|
gtk_flow_box_get_activate_on_single_click (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
|
|
|
|
|
|
|
|
|
|
return BOX_PRIV (box)->activate_on_single_click;
|
|
|
|
|
}
|
2017-12-31 04:23:16 +00:00
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_set_accept_unpaired_release (GtkFlowBox *box,
|
|
|
|
|
gboolean accept)
|
|
|
|
|
{
|
|
|
|
|
if (BOX_PRIV (box)->accept_unpaired_release == accept)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
BOX_PRIV (box)->accept_unpaired_release = accept;
|
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_ACCEPT_UNPAIRED_RELEASE]);
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-07 03:15:27 +00:00
|
|
|
|
/* Selection handling {{{2 */
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_get_selected_children:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
*
|
|
|
|
|
* Creates a list of all selected children.
|
|
|
|
|
*
|
|
|
|
|
* Returns: (element-type GtkFlowBoxChild) (transfer container):
|
|
|
|
|
* A #GList containing the #GtkWidget for each selected child.
|
|
|
|
|
* Free with g_list_free() when done.
|
|
|
|
|
*/
|
|
|
|
|
GList *
|
|
|
|
|
gtk_flow_box_get_selected_children (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxChild *child;
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
GList *selected = NULL;
|
|
|
|
|
|
|
|
|
|
g_return_val_if_fail (GTK_IS_FLOW_BOX (box), NULL);
|
|
|
|
|
|
|
|
|
|
for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
|
|
|
|
!g_sequence_iter_is_end (iter);
|
|
|
|
|
iter = g_sequence_iter_next (iter))
|
|
|
|
|
{
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
if (CHILD_PRIV (child)->selected)
|
|
|
|
|
selected = g_list_prepend (selected, child);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return g_list_reverse (selected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_select_child:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @child: a child of @box
|
|
|
|
|
*
|
|
|
|
|
* Selects a single child of @box, if the selection
|
|
|
|
|
* mode allows it.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_select_child (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxChild *child)
|
|
|
|
|
{
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX_CHILD (child));
|
|
|
|
|
|
2013-10-07 03:15:27 +00:00
|
|
|
|
gtk_flow_box_select_child_internal (box, child);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_unselect_child:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @child: a child of @box
|
|
|
|
|
*
|
|
|
|
|
* Unselects a single child of @box, if the selection
|
|
|
|
|
* mode allows it.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_unselect_child (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxChild *child)
|
|
|
|
|
{
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX_CHILD (child));
|
|
|
|
|
|
2013-10-07 03:15:27 +00:00
|
|
|
|
gtk_flow_box_unselect_child_internal (box, child);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_select_all:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
*
|
|
|
|
|
* Select all children of @box, if the selection
|
|
|
|
|
* mode allows it.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_select_all (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
|
|
|
|
|
if (BOX_PRIV (box)->selection_mode != GTK_SELECTION_MULTIPLE)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (g_sequence_get_length (BOX_PRIV (box)->children) > 0)
|
|
|
|
|
{
|
2013-10-07 03:15:27 +00:00
|
|
|
|
gtk_flow_box_select_all_between (box, NULL, NULL, FALSE);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_unselect_all:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
*
|
|
|
|
|
* Unselect all children of @box, if the selection
|
|
|
|
|
* mode allows it.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_unselect_all (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
gboolean dirty = FALSE;
|
|
|
|
|
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
|
|
|
|
|
if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_BROWSE)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
dirty = gtk_flow_box_unselect_all_internal (box);
|
|
|
|
|
|
|
|
|
|
if (dirty)
|
|
|
|
|
g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBoxForeachFunc:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @child: a #GtkFlowBoxChild
|
|
|
|
|
* @user_data: (closure): user data
|
|
|
|
|
*
|
|
|
|
|
* A function used by gtk_flow_box_selected_foreach().
|
2014-03-28 23:19:37 +00:00
|
|
|
|
* It will be called on every selected child of the @box.
|
2013-09-29 17:43:27 +00:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_selected_foreach:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @func: (scope call): the function to call for each selected child
|
|
|
|
|
* @data: user data to pass to the function
|
|
|
|
|
*
|
|
|
|
|
* Calls a function for each selected child.
|
|
|
|
|
*
|
|
|
|
|
* Note that the selection cannot be modified from within
|
|
|
|
|
* this function.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_selected_foreach (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxForeachFunc func,
|
|
|
|
|
gpointer data)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxChild *child;
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
|
|
|
|
|
for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
|
|
|
|
!g_sequence_iter_is_end (iter);
|
|
|
|
|
iter = g_sequence_iter_next (iter))
|
|
|
|
|
{
|
|
|
|
|
child = g_sequence_get (iter);
|
|
|
|
|
if (CHILD_PRIV (child)->selected)
|
|
|
|
|
(*func) (box, child, data);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_set_selection_mode:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
* @mode: the new selection mode
|
|
|
|
|
*
|
|
|
|
|
* Sets how selection works in @box.
|
|
|
|
|
* See #GtkSelectionMode for details.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_set_selection_mode (GtkFlowBox *box,
|
|
|
|
|
GtkSelectionMode mode)
|
|
|
|
|
{
|
|
|
|
|
gboolean dirty = FALSE;
|
|
|
|
|
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
|
|
|
|
|
if (mode == BOX_PRIV (box)->selection_mode)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (mode == GTK_SELECTION_NONE ||
|
|
|
|
|
BOX_PRIV (box)->selection_mode == GTK_SELECTION_MULTIPLE)
|
|
|
|
|
{
|
|
|
|
|
dirty = gtk_flow_box_unselect_all_internal (box);
|
|
|
|
|
BOX_PRIV (box)->selected_child = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BOX_PRIV (box)->selection_mode = mode;
|
|
|
|
|
|
2014-06-07 04:39:00 +00:00
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_SELECTION_MODE]);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
if (dirty)
|
|
|
|
|
g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_get_selection_mode:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
*
|
|
|
|
|
* Gets the selection mode of @box.
|
|
|
|
|
*
|
|
|
|
|
* Returns: the #GtkSelectionMode
|
|
|
|
|
*/
|
|
|
|
|
GtkSelectionMode
|
|
|
|
|
gtk_flow_box_get_selection_mode (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (GTK_IS_FLOW_BOX (box), GTK_SELECTION_SINGLE);
|
|
|
|
|
|
|
|
|
|
return BOX_PRIV (box)->selection_mode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Filtering {{{2 */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBoxFilterFunc:
|
|
|
|
|
* @child: a #GtkFlowBoxChild that may be filtered
|
|
|
|
|
* @user_data: (closure): user data
|
|
|
|
|
*
|
2020-04-01 13:04:46 +00:00
|
|
|
|
* A function that will be called whenever a child changes
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* or is added. It lets you control if the child should be
|
|
|
|
|
* visible or not.
|
|
|
|
|
*
|
|
|
|
|
* Returns: %TRUE if the row should be visible, %FALSE otherwise
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_set_filter_func:
|
|
|
|
|
* @box: a #GtkFlowBox
|
2020-01-13 01:11:15 +00:00
|
|
|
|
* @filter_func: (allow-none): callback that
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* lets you filter which children to show
|
2020-01-13 01:11:15 +00:00
|
|
|
|
* @user_data: (closure): user data passed to @filter_func
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* @destroy: destroy notifier for @user_data
|
|
|
|
|
*
|
|
|
|
|
* By setting a filter function on the @box one can decide dynamically
|
|
|
|
|
* which of the children to show. For instance, to implement a search
|
|
|
|
|
* function that only shows the children matching the search terms.
|
|
|
|
|
*
|
2014-02-10 11:44:52 +00:00
|
|
|
|
* The @filter_func will be called for each child after the call, and
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* it will continue to be called each time a child changes (via
|
|
|
|
|
* gtk_flow_box_child_changed()) or when gtk_flow_box_invalidate_filter()
|
|
|
|
|
* is called.
|
|
|
|
|
*
|
2015-08-25 17:58:40 +00:00
|
|
|
|
* Note that using a filter function is incompatible with using a model
|
|
|
|
|
* (see gtk_flow_box_bind_model()).
|
2013-09-29 17:43:27 +00:00
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_set_filter_func (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxFilterFunc filter_func,
|
|
|
|
|
gpointer user_data,
|
|
|
|
|
GDestroyNotify destroy)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv;
|
|
|
|
|
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
|
|
|
|
|
priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
if (priv->filter_destroy != NULL)
|
|
|
|
|
priv->filter_destroy (priv->filter_data);
|
|
|
|
|
|
|
|
|
|
priv->filter_func = filter_func;
|
|
|
|
|
priv->filter_data = user_data;
|
|
|
|
|
priv->filter_destroy = destroy;
|
|
|
|
|
|
2015-08-25 17:28:49 +00:00
|
|
|
|
gtk_flow_box_check_model_compat (box);
|
|
|
|
|
|
2013-10-01 01:57:41 +00:00
|
|
|
|
gtk_flow_box_apply_filter_all (box);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_invalidate_filter:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
*
|
|
|
|
|
* Updates the filtering for all children.
|
|
|
|
|
*
|
|
|
|
|
* Call this function when the result of the filter
|
|
|
|
|
* function on the @box is changed due ot an external
|
|
|
|
|
* factor. For instance, this would be used if the
|
|
|
|
|
* filter function just looked for a specific search
|
|
|
|
|
* term, and the entry with the string has changed.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_invalidate_filter (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
|
|
|
|
|
if (BOX_PRIV (box)->filter_func != NULL)
|
2013-10-01 01:57:41 +00:00
|
|
|
|
gtk_flow_box_apply_filter_all (box);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Sorting {{{2 */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GtkFlowBoxSortFunc:
|
|
|
|
|
* @child1: the first child
|
|
|
|
|
* @child2: the second child
|
|
|
|
|
* @user_data: (closure): user data
|
|
|
|
|
*
|
|
|
|
|
* A function to compare two children to determine which
|
|
|
|
|
* should come first.
|
|
|
|
|
*
|
|
|
|
|
* Returns: < 0 if @child1 should be before @child2, 0 if
|
|
|
|
|
* the are equal, and > 0 otherwise
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_set_sort_func:
|
|
|
|
|
* @box: a #GtkFlowBox
|
2020-01-13 01:11:15 +00:00
|
|
|
|
* @sort_func: (allow-none): the sort function
|
|
|
|
|
* @user_data: (closure): user data passed to @sort_func
|
2013-09-29 17:43:27 +00:00
|
|
|
|
* @destroy: destroy notifier for @user_data
|
|
|
|
|
*
|
|
|
|
|
* By setting a sort function on the @box, one can dynamically
|
|
|
|
|
* reorder the children of the box, based on the contents of
|
|
|
|
|
* the children.
|
|
|
|
|
*
|
|
|
|
|
* The @sort_func will be called for each child after the call,
|
|
|
|
|
* and will continue to be called each time a child changes (via
|
|
|
|
|
* gtk_flow_box_child_changed()) and when gtk_flow_box_invalidate_sort()
|
|
|
|
|
* is called.
|
|
|
|
|
*
|
2015-12-09 16:15:04 +00:00
|
|
|
|
* Note that using a sort function is incompatible with using a model
|
|
|
|
|
* (see gtk_flow_box_bind_model()).
|
2013-09-29 17:43:27 +00:00
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_set_sort_func (GtkFlowBox *box,
|
|
|
|
|
GtkFlowBoxSortFunc sort_func,
|
|
|
|
|
gpointer user_data,
|
|
|
|
|
GDestroyNotify destroy)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv;
|
|
|
|
|
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
|
|
|
|
|
priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
if (priv->sort_destroy != NULL)
|
|
|
|
|
priv->sort_destroy (priv->sort_data);
|
|
|
|
|
|
|
|
|
|
priv->sort_func = sort_func;
|
|
|
|
|
priv->sort_data = user_data;
|
|
|
|
|
priv->sort_destroy = destroy;
|
|
|
|
|
|
2015-08-25 17:28:49 +00:00
|
|
|
|
gtk_flow_box_check_model_compat (box);
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
gtk_flow_box_invalidate_sort (box);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gint
|
|
|
|
|
gtk_flow_box_sort (GtkFlowBoxChild *a,
|
|
|
|
|
GtkFlowBoxChild *b,
|
|
|
|
|
GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
return priv->sort_func (a, b, priv->sort_data);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-30 03:37:42 +00:00
|
|
|
|
static void
|
|
|
|
|
gtk_flow_box_css_node_foreach (gpointer data,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GtkWidget **previous = user_data;
|
|
|
|
|
GtkWidget *row = data;
|
|
|
|
|
GtkCssNode *row_node;
|
|
|
|
|
GtkCssNode *prev_node;
|
|
|
|
|
|
|
|
|
|
if (*previous)
|
|
|
|
|
{
|
|
|
|
|
prev_node = gtk_widget_get_css_node (*previous);
|
|
|
|
|
row_node = gtk_widget_get_css_node (row);
|
2015-07-12 19:39:49 +00:00
|
|
|
|
gtk_css_node_insert_after (gtk_css_node_get_parent (row_node),
|
|
|
|
|
row_node,
|
|
|
|
|
prev_node);
|
2015-05-30 03:37:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*previous = row;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-29 17:43:27 +00:00
|
|
|
|
/**
|
|
|
|
|
* gtk_flow_box_invalidate_sort:
|
|
|
|
|
* @box: a #GtkFlowBox
|
|
|
|
|
*
|
|
|
|
|
* Updates the sorting for all children.
|
|
|
|
|
*
|
|
|
|
|
* Call this when the result of the sort function on
|
|
|
|
|
* @box is changed due to an external factor.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
gtk_flow_box_invalidate_sort (GtkFlowBox *box)
|
|
|
|
|
{
|
|
|
|
|
GtkFlowBoxPrivate *priv;
|
2015-05-30 03:37:42 +00:00
|
|
|
|
GtkWidget *previous = NULL;
|
2013-09-29 17:43:27 +00:00
|
|
|
|
|
|
|
|
|
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
|
|
|
|
|
|
|
|
|
priv = BOX_PRIV (box);
|
|
|
|
|
|
|
|
|
|
if (priv->sort_func != NULL)
|
|
|
|
|
{
|
2015-05-30 03:37:42 +00:00
|
|
|
|
g_sequence_sort (priv->children, (GCompareDataFunc)gtk_flow_box_sort, box);
|
|
|
|
|
g_sequence_foreach (priv->children, gtk_flow_box_css_node_foreach, &previous);
|
2013-09-29 17:43:27 +00:00
|
|
|
|
gtk_widget_queue_resize (GTK_WIDGET (box));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-07 03:15:27 +00:00
|
|
|
|
/* vim:set foldmethod=marker expandtab: */
|