Quelle gtkflowbox.c
Sprache: C
/*
* 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
* License along with this library ; if not , see < http : //www.gnu.org/licenses/>.
*
* Authors :
* Tristan Van Berkom < tristanvb @ openismus . com >
* Matthias Clasen < mclasen @ redhat . com >
* William Jon McCann < jmccann @ redhat . com >
*/
/* Preamble {{{1 */
/**
* GtkFlowBox :
*
* Puts child widgets in a reflowing grid .
*
* < picture >
* < source srcset = " flow - box - dark . png " media = " ( prefers - color - scheme : dark ) " >
* < img alt = " An example GtkFlowBox " src = " flow - box . png " >
* < / picture >
*
* 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 .
*
* 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 .
*
* 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 [ method @ Gtk . FlowBox . insert ] , and a
* ` GtkFlowBoxChild ` widget will automatically be inserted between the box
* and the widget .
*
* Also see [ class @ Gtk . ListBox ] .
*
* # Shortcuts and Gestures
*
* The following signals have default keybindings :
*
* - [ signal @ Gtk . FlowBox : : move - cursor ]
* - [ signal @ Gtk . FlowBox : : select - all ]
* - [ signal @ Gtk . FlowBox : : toggle - cursor - child ]
* - [ signal @ Gtk . FlowBox : : unselect - all ]
*
* # CSS nodes
*
* ` ` `
* flowbox
* ├ ─ ─ flowboxchild
* │ ╰ ─ ─ < child >
* ├ ─ ─ flowboxchild
* │ ╰ ─ ─ < child >
* ┊
* ╰ ─ ─ [ rubberband ]
* ` ` `
*
* ` GtkFlowBox ` uses a single CSS node with name flowbox . ` GtkFlowBoxChild `
* uses a single CSS node with name flowboxchild . For rubberband selection ,
* a subnode with name rubberband is used .
*
* # Accessibility
*
* ` GtkFlowBox ` uses the [ enum @ Gtk . AccessibleRole . grid ] role , and ` GtkFlowBoxChild `
* uses the [ enum @ Gtk . AccessibleRole . grid_cell ] role .
*/
/**
* GtkFlowBoxChild :
*
* The kind of widget that can be added to a ` GtkFlowBox ` .
*
* [ class @ Gtk . FlowBox ] will automatically wrap its children in a ` GtkFlowBoxChild `
* when necessary .
*/
#include <config.h>
#include "gtkflowboxprivate.h"
#include "gtkaccessible.h"
#include "gtkadjustment.h"
#include "gtkbinlayout.h"
#include "gtkbuildable.h"
#include "gtkbuilderprivate.h"
#include "gtkcsscolorvalueprivate.h"
#include "gtkeventcontrollerkey.h"
#include "gtkgestureclick.h"
#include "gtkgesturedrag.h"
#include "gtkmain.h"
#include "gtkmarshalers.h"
#include "gtkorientable.h"
#include "gtkprivate.h"
#include "gtksizerequest.h"
#include "gtksnapshot.h"
#include "gtkrenderbackgroundprivate.h"
#include "gtktypebuiltins.h"
#include "gtkviewport.h"
#include "gtkwidgetprivate.h"
/* 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 int gtk_flow_box_sort (GtkFlowBoxChild *a,
GtkFlowBoxChild *b,
GtkFlowBox *box);
static void gtk_flow_box_bound_model_changed (GListModel *list,
guint position,
guint removed,
guint added,
gpointer user_data);
static void gtk_flow_box_set_accept_unpaired_release (GtkFlowBox *box,
gboolean accept);
static void gtk_flow_box_check_model_compat (GtkFlowBox *box);
static void
path_from_horizontal_line_rects (cairo_t *cr,
GdkRectangle *lines,
int n_lines)
{
int start_line, end_line;
GdkRectangle *r;
int 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 ];
int 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,
int n_lines)
{
int start_line, end_line;
GdkRectangle *r;
int 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 ];
int 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 };
enum {
PROP_CHILD = 1 ,
N_PROPS
};
static GParamSpec *obj_props[N_PROPS] = { NULL, };
typedef struct _GtkFlowBoxChildPrivate GtkFlowBoxChildPrivate;
struct _GtkFlowBoxChildPrivate
{
GtkWidget *child;
GSequenceIter *iter;
gboolean selected;
};
#define CHILD_PRIV(child) ((GtkFlowBoxChildPrivate*)gtk_flow_box_child_get_instance_private ((GtkFlowBoxChild*)(child)))
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))
/* 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);
gtk_flow_box_update_selection (box, child, FALSE , FALSE );
}
/* GtkWidget implementation {{{2 */
static GtkBuildableIface *parent_child_buildable_iface;
static void
gtk_flow_box_child_buildable_add_child (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const char *type)
{
if (GTK_IS_WIDGET (child))
{
gtk_buildable_child_deprecation_warning (buildable, builder, NULL, "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;
}
static gboolean
gtk_flow_box_child_focus (GtkWidget *widget,
GtkDirectionType direction)
{
GtkFlowBoxChild *self = GTK_FLOW_BOX_CHILD (widget);
GtkFlowBoxChildPrivate *priv = CHILD_PRIV (self);
GtkWidget *child = priv->child;
gboolean had_focus = FALSE ;
/* Without "focusable" flag try to pass the focus to the child immediately */
if (!gtk_widget_get_focusable (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 ;
}
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 ;
}
else if (gtk_widget_get_focus_child (widget) != NULL)
{
/* 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 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 ;
}
}
static void
gtk_flow_box_child_root (GtkWidget *widget)
{
GtkFlowBoxChild *child = GTK_FLOW_BOX_CHILD (widget);
GTK_WIDGET_CLASS (gtk_flow_box_child_parent_class)->root (widget);
gtk_accessible_update_state (GTK_ACCESSIBLE (child),
GTK_ACCESSIBLE_STATE_SELECTED, CHILD_PRIV (child)->selected,
-1 );
}
/* 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 );
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;
widget_class->root = gtk_flow_box_child_root;
widget_class->compute_expand = gtk_flow_box_child_compute_expand;
widget_class->focus = gtk_flow_box_child_focus;
class ->activate = gtk_flow_box_child_activate;
/**
* GtkFlowBoxChild : child :
*
* The child widget .
*/
obj_props[PROP_CHILD] = g_param_spec_object ("child" , NULL, NULL,
GTK_TYPE_WIDGET,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, N_PROPS, obj_props);
/**
* GtkFlowBoxChild : : activate :
* @ child : The child on which the signal is emitted
*
* Emitted when the user activates a child widget in a ` GtkFlowBox ` .
*
* This can happen either by clicking or double - clicking ,
* or via a keybinding .
*
* This is a [ keybinding signal ] ( class . SignalAction . html ) ,
* but it can be used by applications for their own purposes .
*
* The default bindings are < kbd > Space < / kbd > and < kbd > Enter < / kbd > .
*/
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,
NULL,
G_TYPE_NONE, 0 );
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
gtk_widget_class_set_css_name (widget_class, I_("flowboxchild" ));
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GRID_CELL);
gtk_widget_class_set_activate_signal (widget_class, child_signals[CHILD_ACTIVATE]);
}
static void
gtk_flow_box_child_init (GtkFlowBoxChild *child)
{
gtk_widget_set_focusable (GTK_WIDGET (child), TRUE );
}
/* Public API {{{2 */
/**
* gtk_flow_box_child_new :
*
* Creates a new ` GtkFlowBoxChild ` .
*
* This should only 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);
}
/**
* gtk_flow_box_child_set_child :
* @ self : a ` GtkFlowBoxChild `
* @ child : ( nullable ) : 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_return_if_fail (GTK_IS_FLOW_BOX_CHILD (self));
g_return_if_fail (child == NULL || priv->child == child || gtk_widget_get_parent (child) == NULL);
if (priv->child == child)
return ;
g_clear_pointer (&priv->child, gtk_widget_unparent);
priv->child = child;
if (child)
gtk_widget_set_parent (child, GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_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;
}
/**
* 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
*/
int
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)
{
g_return_val_if_fail (GTK_IS_FLOW_BOX_CHILD (child), FALSE );
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 .
*
* This generally means that if you don ’ t fully control the data
* model , you have to duplicate the data that affects the sorting
* and filtering functions into the widgets themselves .
*
* Another alternative is to call [ method @ Gtk . FlowBox . 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,
PROP_ACTIVATE_ON_SINGLE_CLICK,
PROP_ACCEPT_UNPAIRED_RELEASE,
/* GtkOrientable */
PROP_ORIENTATION,
LAST_PROP
};
static GParamSpec *props[LAST_PROP] = { NULL, };
typedef struct _GtkFlowBoxClass GtkFlowBoxClass;
struct _GtkFlowBox
{
GtkWidget container;
};
struct _GtkFlowBoxClass
{
GtkWidgetClass 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,
int count,
gboolean extend,
gboolean modify);
void (*select_all) (GtkFlowBox *box);
void (*unselect_all) (GtkFlowBox *box);
};
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;
gboolean accept_unpaired_release;
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;
GtkGesture *drag_gesture;
GtkFlowBoxChild *rubberband_first;
GtkFlowBoxChild *rubberband_last;
GtkCssNode *rubberband_node;
gboolean rubberband_select;
gboolean rubberband_modify;
gboolean rubberband_extend;
GtkScrollType autoscroll_mode;
guint autoscroll_id;
GListModel *bound_model;
GtkFlowBoxCreateWidgetFunc create_widget_func;
gpointer create_widget_func_data;
GDestroyNotify create_widget_func_data_destroy;
gboolean disable_move_cursor;
};
#define BOX_PRIV(box) ((GtkFlowBoxPrivate*)gtk_flow_box_get_instance_private ((GtkFlowBox*)(box)))
static void gtk_flow_box_buildable_iface_init (GtkBuildableIface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkFlowBox, gtk_flow_box, GTK_TYPE_WIDGET,
G_ADD_PRIVATE (GtkFlowBox)
G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
gtk_flow_box_buildable_iface_init))
/* Internal API, utilities {{{2 */
#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 int
get_visible_children (GtkFlowBox *box)
{
GSequenceIter *iter;
int 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);
}
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));
}
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));
}
}
/* Sel ection 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);
gtk_accessible_update_state (GTK_ACCESSIBLE (child),
GTK_ACCESSIBLE_STATE_SELECTED, selected,
-1 );
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
gtk_flow_box_unselect_child_internal (GtkFlowBox *box,
GtkFlowBoxChild *child)
{
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));
}
static void
gtk_flow_box_select_child_internal (GtkFlowBox *box,
GtkFlowBoxChild *child)
{
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,
GtkFlowBoxChild *child2,
gboolean modify)
{
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))
{
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 );
}
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
gtk_flow_box_select_all_between (box, priv->selected_child, child, FALSE );
}
else
{
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;
}
}
}
g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0 );
}
static void
gtk_flow_box_select_and_activate (GtkFlowBox *box,
GtkFlowBoxChild *child)
{
if (child != NULL)
{
gtk_flow_box_select_child_internal (box, child);
gtk_flow_box_update_cursor (box, child);
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))
return NULL;
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);
if (g_sequence_iter_is_end (iter))
return NULL;
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;
int 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;
int 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))
return NULL;
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,
int *min_size,
int *nat_size)
{
GSequenceIter *iter;
int max_min_size = 0 ;
int 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;
int child_min, child_nat;
child = g_sequence_get (iter);
if (!child_is_visible (child))
continue ;
gtk_widget_measure (child, orientation, -1 ,
&child_min, &child_nat,
NULL, NULL);
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,
int item_size,
int *min_item_size,
int *nat_item_size)
{
GSequenceIter *iter;
int max_min_size = 0 ;
int 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;
int child_min, child_nat;
child = g_sequence_get (iter);
if (!child_is_visible (child))
continue ;
gtk_widget_measure (child, 1 - orientation, item_size,
&child_min, &child_nat,
NULL, NULL);
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,
int line_length,
GtkRequestedSize *item_sizes,
int extra_pixels,
int *min_item_size,
int *nat_item_size)
{
GSequenceIter *iter;
int max_min_size = 0 ;
int max_nat_size = 0 ;
int i;
i = 0 ;
for (iter = cursor;
!g_sequence_iter_is_end (iter) && i < line_length;
iter = g_sequence_iter_next (iter))
{
GtkWidget *child;
int 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--;
}
gtk_widget_measure (child, 1 - orientation, this_item_size,
&child_min, &child_nat,
NULL, NULL);
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 int
gather_aligned_item_requests (GtkFlowBox *box,
GtkOrientation orientation,
int line_length,
int item_spacing,
int n_children,
GtkRequestedSize *item_sizes)
{
GSequenceIter *iter;
int i;
int 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;
int child_min, child_nat;
int position;
child = g_sequence_get (iter);
if (!child_is_visible (child))
continue ;
gtk_widget_measure (child, orientation, -1 ,
&child_min, &child_nat,
NULL, NULL);
/* 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,
int avail_size,
int item_spacing,
int *line_length, /* in-out */
int items_per_line,
int n_children)
{
GtkRequestedSize *sizes, *try_sizes;
int 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;
int extra_pixels;
} AllocatedLine;
static int
get_offset_pixels (GtkAlign align,
int pixels)
{
int 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 ;
case GTK_ALIGN_BASELINE_FILL:
case GTK_ALIGN_BASELINE_CENTER:
default :
g_assert_not_reached ();
break ;
}
return offset;
}
static void
gtk_flow_box_size_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkFlowBox *box = GTK_FLOW_BOX (widget);
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
GtkAllocation child_allocation;
int 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;
int min_item_size, nat_item_size;
int line_length;
int item_size = 0 ;
int line_size = 0 , min_fixed_line_size = 0 , nat_fixed_line_size = 0 ;
int line_offset, item_offset, n_children, n_lines, line_count;
int extra_pixels = 0 , extra_per_item = 0 , extra_extra = 0 ;
int extra_line_pixels = 0 , extra_per_line = 0 , extra_line_extra = 0 ;
int i, this_line_size;
GSequenceIter *iter;
min_items = MAX (1 , priv->min_children_per_line);
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
{
avail_size = width;
avail_other_size = height;
item_spacing = priv->column_spacing; line_spacing = priv->row_spacing;
}
else /* GTK_ORIENTATION_VERTICAL */
{
avail_size = height;
avail_other_size = width;
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 ;
/* Deal with ALIGNED/HOMOGENEOUS modes first, start with
* initial guesses at item / line sizes
*/
get_max_item_size (box, priv->orientation, &min_item_size, &nat_item_size);
if (nat_item_size <= 0 )
{
child_allocation.x = 0 ;
child_allocation.y = 0 ;
child_allocation.width = 0 ;
child_allocation.height = 0 ;
for (iter = g_sequence_get_begin_iter (priv->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))
continue ;
gtk_widget_size_allocate (child, &child_allocation, -1 );
}
return ;
}
/* By default flow at the natural item width */
line_length = avail_size / (nat_item_size + item_spacing);
/* After the above approximation, check if we can't 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
* minimum item flow length
*/
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
* of all lines
*/
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 in case of GTK_ALIGN_START lines */
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;
}
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 */
item_offset = get_offset_pixels (item_align, extra_pixels);
line_offset = get_offset_pixels (line_align, extra_line_pixels);
/* 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;
int position;
int this_item_size;
int last_line_n_items = n_children % line_length;
if (last_line_n_items == 0 )
last_line_n_items = line_length;
int last_line_extra_items = line_length - last_line_n_items;
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++;
}
}
item_offset = 0 ;
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 )
{
if (priv->homogeneous)
{
item_offset += item_size * last_line_extra_items;
item_offset += item_spacing * last_line_extra_items;
}
else
{
int j;
for (j = 0 ; j < last_line_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 )
position += last_line_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)
{
child_allocation.x = item_offset;
child_allocation.y = line_offset;
child_allocation.width = this_item_size;
child_allocation.height = this_line_size;
}
else /* GTK_ORIENTATION_VERTICAL */
{
child_allocation.x = line_offset;
child_allocation.y = item_offset;
child_allocation.width = this_line_size;
child_allocation.height = this_item_size;
}
if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
child_allocation.x = width - child_allocation.x - child_allocation.width;
gtk_widget_size_allocate (child, &child_allocation, -1 );
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);
GtkWidget *visible_child = NULL;
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))
{
GtkWidget *child;
child = g_sequence_get (iter);
if (!child_is_visible (child))
continue ;
if (!visible_child)
visible_child = child;
else
/* Multiple visible children */
return (BOX_PRIV (box)->orientation == GTK_ORIENTATION_HORIZONTAL) ?
GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH : GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
}
if (visible_child)
return gtk_widget_get_request_mode (visible_child);
return GTK_SIZE_REQUEST_CONSTANT_SIZE;
}
/* 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,
int line_length,
int *min_size,
int *nat_size)
{
GSequenceIter *iter;
int max_min_size = 0 ;
int max_nat_size = 0 ;
int 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;
int child_min, child_nat;
child = g_sequence_get (iter);
if (!child_is_visible (child))
continue ;
gtk_widget_measure (child, orientation, -1 ,
&child_min, &child_nat,
NULL, NULL);
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
gtk_flow_box_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkFlowBox *box = GTK_FLOW_BOX (widget);
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
if (for_size < 0 )
{
int min_item_width, nat_item_width;
int min_items, nat_items;
int min_width, nat_width;
min_items = MAX (1 , priv->min_children_per_line);
nat_items = MAX (min_items, priv->max_children_per_line);
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
{
min_width = nat_width = 0 ;
if (!priv->homogeneous)
{
/* When not homogeneous; horizontally oriented boxes
* need enough width for the widest row
*/
int 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 boxes
* 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;
nat_width += nat_item_width * nat_items;
nat_width += (nat_items -1 ) * priv->column_spacing;
}
}
else /* GTK_ORIENTATION_VERTICAL */
{
/* Return the width for the minimum height */
int min_height;
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);
}
*minimum = min_width;
*natural = nat_width;
}
else
{
int min_item_height, nat_item_height;
int min_items;
int min_width, nat_width;
int avail_size, n_children;
min_items = MAX (1 , priv->min_children_per_line);
min_width = 0 ;
nat_width = 0 ;
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
{
/* Binary search :( */
int min, max;
int min_height, nat_height;
gtk_flow_box_measure (widget,
GTK_ORIENTATION_HORIZONTAL,
-1 ,
&min_width, &nat_width,
NULL, NULL);
min = min_width;
max = G_MAXINT;
while (min < max)
{
int test;
if (max != G_MAXINT)
test = (min + max) / 2 ;
else if (min == min_width)
test = min;
else
test = min * 2 ;
gtk_flow_box_measure (widget, GTK_ORIENTATION_VERTICAL,
test, &min_height, &nat_height,
NULL, NULL);
if (min_height > for_size)
min = test + 1 ;
else
max = test;
}
/* TODO: calculate natural size properly */
min_width = min;
nat_width = MAX (min, nat_width);
}
else /* GTK_ORIENTATION_VERTICAL */
{
int min_height;
int line_length;
int item_size, extra_pixels;
int dummy;
n_children = get_visible_children (box);
if (n_children <= 0 )
goto out_width;
/* Make sure its no smaller than the minimum */
gtk_flow_box_measure (widget,
GTK_ORIENTATION_VERTICAL,
-1 ,
&min_height, &dummy,
NULL, NULL);
avail_size = MAX (for_size, min_height);
if (avail_size <= 0 )
goto out_width;
get_max_item_size (box, GTK_ORIENTATION_VERTICAL, &min_item_height, &nat_item_height);
if (nat_item_height <= 0 )
goto out_width;
/* By default flow at the natural item width */
line_length = avail_size / (nat_item_height + priv->row_spacing);
/* After the above approximation, check if we can't fit one more on the line */
if (line_length * priv->row_spacing + (line_length + 1 ) * nat_item_height <= avail_size)
line_length++;
/* 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);
/* Now we need the real item allocation size */
item_size = (avail_size - (line_length - 1 ) * priv->row_spacing) / line_length;
/* 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;
if (priv->homogeneous)
{
int min_item_width, nat_item_width;
int 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
{
int 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);
}
}
out_width:
*minimum = min_width;
*natural = nat_width;
}
}
else
{
if (for_size < 0 )
{
int min_item_height, nat_item_height;
int min_items, nat_items;
int min_height, nat_height;
min_items = MAX (1 , priv->min_children_per_line);
nat_items = MAX (min_items, priv->max_children_per_line);
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
{
/* Return the height for the natural width */
int nat_width, dummy;
gtk_flow_box_measure (widget,
GTK_ORIENTATION_HORIZONTAL,
-1 ,
&dummy, &nat_width,
NULL, NULL);
gtk_flow_box_measure (widget,
GTK_ORIENTATION_VERTICAL,
nat_width,
&min_height, &nat_height,
NULL, NULL);
}
else /* GTK_ORIENTATION_VERTICAL */
{
min_height = nat_height = 0 ;
if (! priv->homogeneous)
{
/* 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;
}
else
{
int 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;
}
}
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);
min_height += min_item_height * min_items;
min_height += (min_items -1 ) * priv->row_spacing;
nat_height += nat_item_height * nat_items;
nat_height += (nat_items -1 ) * priv->row_spacing;
}
}
*minimum = min_height;
*natural = nat_height;
}
else
{
int min_item_width, nat_item_width;
int min_items;
int min_height, nat_height;
int avail_size, n_children;
min_items = MAX (1 , priv->min_children_per_line);
min_height = 0 ;
nat_height = 0 ;
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
{
int min_width;
int line_length;
int item_size, extra_pixels;
int dummy;
n_children = get_visible_children (box);
if (n_children <= 0 )
goto out_height;
/* Make sure its no smaller than the minimum */
gtk_flow_box_measure (widget,
GTK_ORIENTATION_HORIZONTAL,
-1 ,
&min_width, &dummy,
NULL, NULL);
avail_size = MAX (for_size, min_width);
if (avail_size <= 0 )
goto out_height;
get_max_item_size (box, GTK_ORIENTATION_HORIZONTAL, &min_item_width, &nat_item_width);
if (nat_item_width <= 0 )
goto out_height;
/* By default flow at the natural item width */
line_length = avail_size / (nat_item_width + priv->column_spacing);
/* After the above approximation, check if we can't fit one more on the line */
if (line_length * priv->column_spacing + (line_length + 1 ) * nat_item_width <= avail_size)
line_length++;
/* 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);
/* Now we need the real item allocation size */
item_size = (avail_size - (line_length - 1 ) * priv->column_spacing) / line_length;
/* 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;
if (priv->homogeneous)
{
int min_item_height, nat_item_height;
int 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
{
int 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);)
{
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;
}
}
g_free (item_sizes);
}
}
else /* GTK_ORIENTATION_VERTICAL */
{
/* Return the minimum height */
gtk_flow_box_measure (widget,
GTK_ORIENTATION_VERTICAL,
-1 ,
&min_height, &nat_height,
NULL, NULL);
}
out_height:
*minimum = min_height;
*natural = nat_height;
}
}
}
/* Drawing {{{3 */
static void
gtk_flow_box_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
GtkFlowBox *box = GTK_FLOW_BOX (widget);
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
int x, y, width, height;
GtkCssBoxes boxes;
GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->snapshot (widget, snapshot);
x = 0 ;
y = 0 ;
width = gtk_widget_get_width (widget);
height = gtk_widget_get_height (widget);
if (priv->rubberband_first && priv->rubberband_last)
{
GtkCssStyle *style;
GSequenceIter *iter, *iter1, *iter2;
GdkRectangle line_rect = { 0 , }, rect;
GArray *lines;
gboolean vertical;
cairo_t *cr;
vertical = priv->orientation == GTK_ORIENTATION_VERTICAL;
cr = gtk_snapshot_append_cairo (snapshot,
&GRAPHENE_RECT_INIT (x, y, width, height));
style = gtk_css_node_get_style (priv->rubberband_node);
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;
}
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);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
gtk_widget_get_allocation (GTK_WIDGET (child), &rect);
G_GNUC_END_IGNORE_DEPRECATIONS
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;
const GdkRGBA *border_color;
int border_width;
GtkSnapshot *bg_snapshot;
GskRenderNode *node;
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);
bg_snapshot = gtk_snapshot_new ();
gtk_css_boxes_init_border_box (&boxes, style, x, y, width, height);
gtk_css_style_snapshot_background (&boxes, bg_snapshot);
node = gtk_snapshot_free_to_node (bg_snapshot);
if (node)
{
gsk_render_node_draw (node, cr);
gsk_render_node_unref (node);
}
cairo_restore (cr);
cairo_append_path (cr, path);
cairo_path_destroy (path);
border_color = gtk_css_color_value_get_rgba (style->used->border_top_color);
border_width = round (gtk_css_number_value_get (style->border->border_left_width, 100 ));
cairo_set_line_width (cr, border_width);
gdk_cairo_set_source_rgba (cr, border_color);
cairo_stroke (cr);
}
g_array_free (lines, TRUE );
cairo_destroy (cr);
}
}
/* 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;
double factor;
double increment;
double 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 ;
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:
default :
g_assert_not_reached ();
break ;
}
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)
{
GdkEventSequence *sequence;
double x, y;
GtkFlowBoxChild *child;
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (priv->drag_gesture));
gtk_gesture_get_point (priv->drag_gesture, sequence, &x, &y);
child = gtk_flow_box_get_child_at_pos (box, x, y);
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),
autoscroll_cb,
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);
rect->width = gtk_widget_get_width (parent);
rect->height = gtk_widget_get_height (parent);
return TRUE ;
}
return FALSE ;
}
static void
update_autoscroll_mode (GtkFlowBox *box,
int x,
int y)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
GtkScrollType mode = GTK_SCROLL_NONE;
GdkRectangle rect;
int 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 */
static void
gtk_flow_box_drag_gesture_update (GtkGestureDrag *gesture,
double offset_x,
double offset_y,
GtkFlowBox *box)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
double start_x, start_y;
GtkFlowBoxChild *child;
GtkCssNode *widget_node;
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 ;
priv->rubberband_first = gtk_flow_box_get_child_at_pos (box, start_x, start_y);
widget_node = gtk_widget_get_css_node (GTK_WIDGET (box));
priv->rubberband_node = gtk_css_node_new ();
gtk_css_node_set_name (priv->rubberband_node, g_quark_from_static_string ("rubberband" ));
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);
/* Grab focus here, so Escape-to-stop-rubberband works */
if (priv->rubberband_first)
gtk_flow_box_update_cursor (box, priv->rubberband_first);
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
}
if (priv->rubberband_select)
{
child = gtk_flow_box_get_child_at_pos (box, start_x + offset_x,
start_y + offset_y);
if (priv->rubberband_first == NULL)
{
priv->rubberband_first = child;
if (priv->rubberband_first)
gtk_flow_box_update_cursor (box, priv->rubberband_first);
}
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
gtk_flow_box_click_gesture_pressed (GtkGestureClick *gesture,
guint n_press,
double x,
double y,
GtkFlowBox *box)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
GtkFlowBoxChild *child;
child = gtk_flow_box_get_child_at_pos (box, x, y);
if (child == NULL)
return ;
/* 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)
{
gtk_gesture_set_state (GTK_GESTURE (gesture),
GTK_EVENT_SEQUENCE_CLAIMED);
g_signal_emit (box, signals[CHILD_ACTIVATED], 0 , child);
}
}
static void
gtk_flow_box_click_unpaired_release (GtkGestureClick *gesture,
double x,
double y,
guint button,
GdkEventSequence *sequence,
GtkFlowBox *box)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
GtkFlowBoxChild *child;
if (!priv->activate_on_single_click || !priv->accept_unpaired_release)
return ;
child = gtk_flow_box_get_child_at_pos (box, x, y);
if (child)
gtk_flow_box_select_and_activate (box, child);
}
static void
gtk_flow_box_click_gesture_released (GtkGestureClick *gesture,
guint n_press,
double x,
double y,
GtkFlowBox *box)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
if (priv->active_child != NULL &&
priv->active_child == gtk_flow_box_get_child_at_pos (box, x, y))
{
gtk_gesture_set_state (GTK_GESTURE (gesture),
GTK_EVENT_SEQUENCE_CLAIMED);
if (priv->activate_on_single_click)
gtk_flow_box_select_and_activate (box, priv->active_child);
else
{
GdkEventSequence *sequence;
GdkInputSource source;
GdkEvent *event;
GdkModifierType state;
gboolean modify;
gboolean extend;
state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture));
modify = (state & GDK_CONTROL_MASK) != 0 ;
extend = (state & GDK_SHIFT_MASK) != 0 ;
/* 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_device (event));
if (source == GDK_SOURCE_TOUCHSCREEN)
modify = !modify;
gtk_flow_box_update_selection (box, priv->active_child, modify, extend);
}
}
}
static void
gtk_flow_box_click_gesture_stopped (GtkGestureClick *gesture,
GtkFlowBox *box)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
priv->active_child = NULL;
gtk_widget_queue_draw (GTK_WIDGET (box));
}
static void
gtk_flow_box_drag_gesture_begin (GtkGestureDrag *gesture,
double start_x,
double start_y,
GtkWidget *widget)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (widget);
GdkModifierType state;
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;
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 ;
}
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;
gtk_css_node_set_parent (priv->rubberband_node, NULL);
priv->rubberband_node = NULL;
remove_autoscroll (box);
gtk_widget_queue_draw (GTK_WIDGET (box));
}
static void
gtk_flow_box_drag_gesture_end (GtkGestureDrag *gesture,
double offset_x,
double offset_y,
GtkFlowBox *box)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
GdkEventSequence *sequence;
if (!priv->rubberband_select)
return ;
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
if (gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
{
if (!priv->rubberband_extend && !priv->rubberband_modify)
gtk_flow_box_unselect_all_internal (box);
if (priv->rubberband_first && priv->rubberband_last)
gtk_flow_box_select_all_between (box, priv->rubberband_first, priv->rubberband_last, priv->rubberband_modify);
gtk_flow_box_stop_rubberband (box);
g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0 );
}
else
gtk_flow_box_stop_rubberband (box);
gtk_widget_queue_draw (GTK_WIDGET (box));
}
static gboolean
gtk_flow_box_key_controller_key_pressed (GtkEventControllerKey *controller,
guint keyval,
guint keycode,
GdkModifierType state,
GtkWidget *widget)
{
GtkFlowBox *box = GTK_FLOW_BOX (widget);
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
if (priv->rubberband_select && keyval == GDK_KEY_Escape)
{
gtk_flow_box_stop_rubberband (box);
return TRUE ;
}
return FALSE ;
}
/* 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);
}
/**
* gtk_flow_box_remove :
* @ box : a ` GtkFlowBox `
* @ widget : the child widget to remove
*
* Removes a child from @ box .
*/
void
gtk_flow_box_remove (GtkFlowBox *box,
GtkWidget *widget)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
gboolean was_visible;
gboolean was_selected;
GtkFlowBoxChild *child;
g_return_if_fail (GTK_IS_FLOW_BOX (box));
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (gtk_widget_get_parent (widget) == GTK_WIDGET (box) ||
gtk_widget_get_parent (gtk_widget_get_parent (widget)) == GTK_WIDGET (box));
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))
{
g_warning ("Tried to remove non-child %p" , widget);
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;
g_sequence_remove (CHILD_PRIV (child)->iter);
gtk_widget_unparent (GTK_WIDGET (child));
if (was_visible && gtk_widget_get_visible (GTK_WIDGET (box)))
gtk_widget_queue_resize (GTK_WIDGET (box));
if (was_selected && !gtk_widget_in_destruction (GTK_WIDGET (box)))
g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0 );
}
/**
* gtk_flow_box_remove_all :
* @ box : a ` GtkFlowBox `
*
* Removes all children from @ box .
*
* This function does nothing if @ box is backed by a model .
*
* Since : 4 . 12
*/
void
gtk_flow_box_remove_all (GtkFlowBox *box)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
GtkWidget *widget = GTK_WIDGET (box);
GtkWidget *child;
g_return_if_fail (GTK_IS_FLOW_BOX (box));
if (priv->bound_model)
return ;
while ((child = gtk_widget_get_first_child (widget)) != NULL)
gtk_flow_box_remove (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;
focus_child = gtk_widget_get_focus_child (widget);
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);
if (iter != NULL)
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);
if (iter != NULL)
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
gtk_flow_box_add_move_binding (GtkWidgetClass *widget_class,
guint keyval,
GdkModifierType modmask,
GtkMovementStep step,
int count)
{
gtk_widget_class_add_binding_signal (widget_class,
keyval, modmask,
"move-cursor" ,
"(iibb)" , step, count, FALSE , FALSE );
gtk_widget_class_add_binding_signal (widget_class,
keyval, modmask | GDK_SHIFT_MASK,
"move-cursor" ,
"(iibb)" , step, count, TRUE , FALSE );
gtk_widget_class_add_binding_signal (widget_class,
keyval, modmask | GDK_CONTROL_MASK,
"move-cursor" ,
"(iibb)" , step, count, FALSE , TRUE );
gtk_widget_class_add_binding_signal (widget_class,
keyval, modmask | GDK_SHIFT_MASK | GDK_CONTROL_MASK,
"move-cursor" ,
"(iibb)" , step, count, TRUE , TRUE );
}
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)
gtk_flow_box_unselect_child_internal (box, priv->cursor_child);
else
gtk_flow_box_select_and_activate (box, priv->cursor_child);
}
void
gtk_flow_box_disable_move_cursor (GtkFlowBox *box)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
priv->disable_move_cursor = TRUE ;
}
static gboolean
gtk_flow_box_move_cursor (GtkFlowBox *box,
GtkMovementStep step,
int count,
gboolean extend,
gboolean modify)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
GtkFlowBoxChild *child;
GtkFlowBoxChild *prev;
GtkFlowBoxChild *next;
GtkAllocation allocation;
int page_size;
GSequenceIter *iter;
int start;
GtkAdjustment *adjustment;
gboolean vertical;
if (priv->disable_move_cursor)
return FALSE ;
vertical = priv->orientation == GTK_ORIENTATION_VERTICAL;
if (vertical)
{
switch ((guint) step)
{
case GTK_MOVEMENT_VISUAL_POSITIONS:
step = GTK_MOVEMENT_DISPLAY_LINES;
break ;
case GTK_MOVEMENT_DISPLAY_LINES:
step = GTK_MOVEMENT_VISUAL_POSITIONS;
break ;
default :
break ;
}
}
child = NULL;
switch ((guint) step)
{
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);
if (iter != NULL)
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 ;
}
if (iter != NULL)
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;
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
gtk_widget_get_allocation (GTK_WIDGET (child), &allocation);
G_GNUC_END_IGNORE_DEPRECATIONS
start = vertical ? allocation.x : allocation.y;
if (count < 0 )
{
int 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 )
{
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
gtk_widget_get_allocation (GTK_WIDGET (prev), &allocation);
G_GNUC_END_IGNORE_DEPRECATIONS
if ((vertical ? allocation.x : allocation.y) < start - page_size)
break ;
}
child = prev;
i++;
}
}
else
{
int i = 0 ;
/* Down */
while (!g_sequence_iter_is_end (iter))
{
iter = gtk_flow_box_get_next_focusable (box, iter);
if (iter == NULL || g_sequence_iter_is_end (iter))
break ;
next = g_sequence_get (iter);
if (i % priv->cur_children_per_line == 0 )
{
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
gtk_widget_get_allocation (GTK_WIDGET (next), &allocation);
G_GNUC_END_IGNORE_DEPRECATIONS
if ((vertical ? allocation.x : allocation.y) > start + page_size)
break ;
}
child = next;
i++;
}
}
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
gtk_widget_get_allocation (GTK_WIDGET (child), &allocation);
G_GNUC_END_IGNORE_DEPRECATIONS
}
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))
{
return FALSE ;
}
return TRUE ;
}
/* If the child has its "focusable" property set to FALSE then it will
* not grab the focus . We must pass the focus to its child directly .
*/
if (!gtk_widget_get_focusable (GTK_WIDGET (child)))
{
GtkWidget *subchild;
subchild = gtk_flow_box_child_get_child (GTK_FLOW_BOX_CHILD (child));
if (subchild)
{
GtkDirectionType direction = count < 0 ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD;
gtk_widget_child_focus (subchild, direction);
}
}
gtk_flow_box_update_cursor (box, child);
if (!modify)
gtk_flow_box_update_selection (box, child, FALSE , extend);
return TRUE ;
}
/* Selection {{{2 */
static void
gtk_flow_box_selected_children_changed (GtkFlowBox *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 ;
case PROP_ACCEPT_UNPAIRED_RELEASE:
g_value_set_boolean (value, priv->accept_unpaired_release);
break ;
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:
{
GtkOrientation orientation = g_value_get_enum (value);
if (priv->orientation != orientation)
{
priv->orientation = orientation;
gtk_widget_update_orientation (GTK_WIDGET (box), priv->orientation);
/* Re-box the children in the new orientation */
gtk_widget_queue_resize (GTK_WIDGET (box));
g_object_notify_by_pspec (object, pspec);
}
}
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 ;
case PROP_ACCEPT_UNPAIRED_RELEASE:
gtk_flow_box_set_accept_unpaired_release (box, g_value_get_boolean (value));
break ;
default :
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break ;
}
}
static void
gtk_flow_box_dispose (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);
if (priv->children)
{
GSequenceIter *iter;
GtkWidget *child;
for (iter = g_sequence_get_begin_iter (priv->children);
!g_sequence_iter_is_end (iter);
iter = g_sequence_iter_next (iter))
{
child = g_sequence_get (iter);
gtk_widget_unparent (child);
}
g_clear_pointer (&priv->children, g_sequence_free);
}
g_clear_object (&priv->hadjustment);
g_clear_object (&priv->vadjustment);
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);
}
G_OBJECT_CLASS (gtk_flow_box_parent_class)->dispose (obj);
}
static void
gtk_flow_box_compute_expand (GtkWidget *widget,
gboolean *hexpand_p,
gboolean *vexpand_p)
{
GtkWidget *w;
gboolean hexpand = FALSE ;
gboolean vexpand = FALSE ;
for (w = gtk_widget_get_first_child (widget);
w != NULL;
w = gtk_widget_get_next_sibling (w))
{
hexpand = hexpand || gtk_widget_compute_expand (w, GTK_ORIENTATION_HORIZONTAL);
vexpand = vexpand || gtk_widget_compute_expand (w, GTK_ORIENTATION_VERTICAL);
}
*hexpand_p = hexpand;
*vexpand_p = vexpand;
}
static void
gtk_flow_box_class_init (GtkFlowBoxClass *class )
{
GObjectClass *object_class = G_OBJECT_CLASS (class );
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class );
object_class->dispose = gtk_flow_box_dispose;
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;
widget_class->snapshot = gtk_flow_box_snapshot;
widget_class->get_request_mode = gtk_flow_box_get_request_mode;
widget_class->compute_expand = gtk_flow_box_compute_expand;
widget_class->measure = gtk_flow_box_measure;
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;
props[PROP_ORIENTATION] = g_param_spec_override ("orientation" ,
g_object_interface_find_property (g_type_default_interface_ref (GTK_TYPE_ORIENTABLE), "orientation" ));
/**
* GtkFlowBox : selection - mode :
*
* The selection mode used by the flow box .
*/
props[PROP_SELECTION_MODE] =
g_param_spec_enum ("selection-mode" , NULL, NULL,
GTK_TYPE_SELECTION_MODE,
GTK_SELECTION_SINGLE,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkFlowBox : activate - on - single - click :
*
* Determines whether children can be activated with a single
* click , or require a double - click .
*/
props[PROP_ACTIVATE_ON_SINGLE_CLICK] =
g_param_spec_boolean ("activate-on-single-click" , NULL, NULL,
TRUE ,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkFlowBox : accept - unpaired - release :
*
* Whether to accept unpaired release events .
*/
props[PROP_ACCEPT_UNPAIRED_RELEASE] =
g_param_spec_boolean ("accept-unpaired-release" , NULL, NULL,
FALSE ,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkFlowBox : homogeneous :
*
* Determines whether all children should be allocated the
* same size .
*/
props[PROP_HOMOGENEOUS] =
g_param_spec_boolean ("homogeneous" , NULL, NULL,
FALSE ,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkFlowBox : min - children - per - line :
*
* The minimum number of children to allocate consecutively
* in the given orientation .
*
* Setting the minimum children per line ensures
* that a reasonably small height will be requested
* for the overall minimum width of the box .
*/
props[PROP_MIN_CHILDREN_PER_LINE] =
g_param_spec_uint ("min-children-per-line" , NULL, NULL,
0 , G_MAXUINT, 0 ,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkFlowBox : max - children - per - line :
*
* The maximum amount of children to request space for consecutively
* in the given orientation .
*/
props[PROP_MAX_CHILDREN_PER_LINE] =
g_param_spec_uint ("max-children-per-line" , NULL, NULL,
1 , G_MAXUINT, DEFAULT_MAX_CHILDREN_PER_LINE,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkFlowBox : row - spacing :
*
* The amount of vertical space between two children .
*/
props[PROP_ROW_SPACING] =
g_param_spec_uint ("row-spacing" , NULL, NULL,
0 , G_MAXUINT, 0 ,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkFlowBox : column - spacing :
*
* The amount of horizontal space between two children .
*/
props[PROP_COLUMN_SPACING] =
g_param_spec_uint ("column-spacing" , NULL, NULL,
0 , G_MAXUINT, 0 ,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, LAST_PROP, props);
/**
* GtkFlowBox : : child - activated :
* @ box : the ` GtkFlowBox ` on which the signal is emitted
* @ child : the child that is activated
*
* Emitted when a child has been activated by the user .
*/
signals[CHILD_ACTIVATED] = g_signal_new (I_("child-activated" ),
GTK_TYPE_FLOW_BOX,
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkFlowBoxClass, child_activated),
NULL, NULL,
NULL,
G_TYPE_NONE, 1 ,
GTK_TYPE_FLOW_BOX_CHILD);
/**
* GtkFlowBox : : selected - children - changed :
* @ box : the ` GtkFlowBox ` on which the signal is emitted
*
* Emitted when the set of selected children changes .
*
* Use [ method @ Gtk . FlowBox . selected_foreach ] or
* [ method @ Gtk . FlowBox . get_selected_children ] to obtain the
* selected children .
*/
signals[SELECTED_CHILDREN_CHANGED] = g_signal_new (I_("selected-children-changed" ),
GTK_TYPE_FLOW_BOX,
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GtkFlowBoxClass, selected_children_changed),
NULL, NULL,
NULL,
G_TYPE_NONE, 0 );
/**
* GtkFlowBox : : activate - cursor - child :
* @ box : the ` GtkFlowBox ` on which the signal is emitted
*
* Emitted when the user activates the @ box .
*
* This is a [ keybinding signal ] ( class . SignalAction . html ) .
*/
signals[ACTIVATE_CURSOR_CHILD] = g_signal_new (I_("activate-cursor-child" ),
GTK_TYPE_FLOW_BOX,
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkFlowBoxClass, activate_cursor_child),
NULL, NULL,
NULL,
G_TYPE_NONE, 0 );
/**
* GtkFlowBox : : toggle - cursor - child :
* @ box : the ` GtkFlowBox ` on which the signal is emitted
*
* Emitted to toggle the selection of the child that has the focus .
*
* This is a [ keybinding signal ] ( class . SignalAction . html ) .
*
* The default binding for this signal is < kbd > Ctrl < / kbd > - < kbd > Space < / kbd > .
*/
signals[TOGGLE_CURSOR_CHILD] = g_signal_new (I_("toggle-cursor-child" ),
GTK_TYPE_FLOW_BOX,
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkFlowBoxClass, toggle_cursor_child),
NULL, NULL,
NULL,
G_TYPE_NONE, 0 );
/**
* GtkFlowBox : : move - cursor :
* @ box : the ` GtkFlowBox ` on which the signal is emitted
* @ step : the granularity of the move , as a ` GtkMovementStep `
* @ count : the number of @ step units to move
* @ extend : whether to extend the selection
* @ modify : whether to modify the selection
*
* Emitted when the user initiates a cursor movement .
*
* This is a [ keybinding signal ] ( class . SignalAction . html ) .
* 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 modifier does not .
* There are too many key combinations to list them all here .
*
* - < kbd > ← < / kbd > , < kbd > → < / kbd > , < kbd > ↑ < / kbd > , < kbd > ↓ < / kbd >
* move by individual children
* - < kbd > Home < / kbd > , < kbd > End < / kbd > move to the ends of the box
* - < kbd > PgUp < / kbd > , < kbd > PgDn < / kbd > move vertically by pages
*
* Returns : % TRUE to stop other handlers from being invoked for the event .
* % FALSE to propagate the event further .
*/
signals[MOVE_CURSOR] = g_signal_new (I_("move-cursor" ),
GTK_TYPE_FLOW_BOX,
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkFlowBoxClass, move_cursor),
NULL, NULL,
_gtk_marshal_BOOLEAN__ENUM_INT_BOOLEAN_BOOLEAN,
G_TYPE_BOOLEAN, 4 ,
GTK_TYPE_MOVEMENT_STEP, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
g_signal_set_va_marshaller (signals[MOVE_CURSOR],
G_TYPE_FROM_CLASS (class ),
_gtk_marshal_BOOLEAN__ENUM_INT_BOOLEAN_BOOLEANv);
/**
* GtkFlowBox : : select - all :
* @ box : the ` GtkFlowBox ` on which the signal is emitted
*
* Emitted to select all children of the box ,
* if the selection mode permits it .
*
* This is a [ keybinding signal ] ( class . SignalAction . html ) .
*
* The default bindings for this signal is < kbd > Ctrl < / kbd > - < kbd > a < / kbd > .
*/
signals[SELECT_ALL] = g_signal_new (I_("select-all" ),
GTK_TYPE_FLOW_BOX,
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkFlowBoxClass, select_all),
NULL, NULL,
NULL,
G_TYPE_NONE, 0 );
/**
* GtkFlowBox : : unselect - all :
* @ box : the ` GtkFlowBox ` on which the signal is emitted
*
* Emitted to unselect all children of the box ,
* if the selection mode permits it .
*
* This is a [ keybinding signal ] ( class . SignalAction . html ) .
*
* The default bindings for this signal is < kbd > Ctrl < / kbd > - < kbd > Shift < / kbd > - < kbd > a < / kbd > .
*/
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,
NULL,
G_TYPE_NONE, 0 );
gtk_widget_class_set_activate_signal (widget_class, signals[ACTIVATE_CURSOR_CHILD]);
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Home, 0 ,
GTK_MOVEMENT_BUFFER_ENDS, -1 );
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_End, 0 ,
GTK_MOVEMENT_BUFFER_ENDS, 1 );
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Up, 0 ,
GTK_MOVEMENT_DISPLAY_LINES, -1 );
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Down, 0 ,
GTK_MOVEMENT_DISPLAY_LINES, 1 );
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Page_Up, 0 ,
GTK_MOVEMENT_PAGES, -1 );
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Page_Down, 0 ,
GTK_MOVEMENT_PAGES, 1 );
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Right, 0 ,
GTK_MOVEMENT_VISUAL_POSITIONS, 1 );
gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Left, 0 ,
GTK_MOVEMENT_VISUAL_POSITIONS, -1 );
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_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);
gtk_widget_class_set_css_name (widget_class, I_("flowbox" ));
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GRID);
}
static void
gtk_flow_box_init (GtkFlowBox *box)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
GtkEventController *controller;
GtkGesture *gesture;
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 ;
gtk_widget_update_orientation (GTK_WIDGET (box), priv->orientation);
priv->children = g_sequence_new (NULL);
gesture = gtk_gesture_click_new ();
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture),
FALSE );
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture),
GDK_BUTTON_PRIMARY);
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
GTK_PHASE_BUBBLE);
g_signal_connect (gesture, "pressed" ,
G_CALLBACK (gtk_flow_box_click_gesture_pressed), box);
g_signal_connect (gesture, "released" ,
G_CALLBACK (gtk_flow_box_click_gesture_released), box);
g_signal_connect (gesture, "stopped" ,
G_CALLBACK (gtk_flow_box_click_gesture_stopped), box);
g_signal_connect (gesture, "unpaired-release" ,
G_CALLBACK (gtk_flow_box_click_unpaired_release), box);
gtk_widget_add_controller (GTK_WIDGET (box), GTK_EVENT_CONTROLLER (gesture));
priv->drag_gesture = gtk_gesture_drag_new ();
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);
gtk_widget_add_controller (GTK_WIDGET (box), GTK_EVENT_CONTROLLER (priv->drag_gesture));
controller = gtk_event_controller_key_new ();
g_signal_connect (controller, "key-pressed" ,
G_CALLBACK (gtk_flow_box_key_controller_key_pressed), box);
gtk_widget_add_controller (GTK_WIDGET (box), controller);
}
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);
int i;
while (removed--)
{
GtkFlowBoxChild *child;
child = gtk_flow_box_get_child_at_index (box, position);
gtk_flow_box_remove (box, 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);
/* 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);
gtk_widget_set_visible (widget, TRUE );
gtk_flow_box_insert (box, widget, position + i);
g_object_unref (widget);
g_object_unref (item);
}
}
/* Buildable implementation {{{3 */
static GtkBuildableIface *parent_buildable_iface;
static void
gtk_flow_box_buildable_add_child (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const char *type)
{
if (GTK_IS_WIDGET (child))
gtk_flow_box_insert (GTK_FLOW_BOX (buildable), GTK_WIDGET (child), -1 );
else
parent_buildable_iface->add_child (buildable, builder, child, type);
}
static void
gtk_flow_box_buildable_iface_init (GtkBuildableIface *iface)
{
parent_buildable_iface = g_type_interface_peek_parent (iface);
iface->add_child = gtk_flow_box_buildable_add_child;
}
/* Public API {{{2 */
/**
* gtk_flow_box_new :
*
* Creates a ` GtkFlowBox ` .
*
* Returns : a new ` GtkFlowBox `
*/
GtkWidget *
gtk_flow_box_new (void )
{
return (GtkWidget *)g_object_new (GTK_TYPE_FLOW_BOX, NULL);
}
static void
gtk_flow_box_insert_widget (GtkFlowBox *box,
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);
else
sibling = NULL;
gtk_widget_insert_after (child, GTK_WIDGET (box), sibling);
}
/**
* gtk_flow_box_prepend :
* @ self : a ` GtkFlowBox
* @ child : the ` GtkWidget ` to add
*
* Adds @ child to the start of @ self .
*
* If a sort function is set , the widget will
* actually be inserted at the calculated position .
*
* See also : [ method @ Gtk . FlowBox . insert ] .
*
* Since : 4 . 6
*/
void
gtk_flow_box_prepend (GtkFlowBox *self,
GtkWidget *child)
{
g_return_if_fail (GTK_IS_FLOW_BOX (self));
g_return_if_fail (GTK_IS_WIDGET (child));
gtk_flow_box_insert (self, child, 0 );
}
/**
* gtk_flow_box_append :
* @ self : a ` GtkFlowBox
* @ child : the ` GtkWidget ` to add
*
* Adds @ child to the end of @ self .
*
* If a sort function is set , the widget will
* actually be inserted at the calculated position .
*
* See also : [ method @ Gtk . FlowBox . insert ] .
*
* Since : 4 . 6
*/
void
gtk_flow_box_append (GtkFlowBox *self,
GtkWidget *child)
{
g_return_if_fail (GTK_IS_FLOW_BOX (self));
g_return_if_fail (GTK_IS_WIDGET (child));
gtk_flow_box_insert (self, child, -1 );
}
/**
* 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 .
*
* 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,
int 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 ());
gtk_flow_box_child_set_child (child, widget);
}
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);
}
CHILD_PRIV (child)->iter = iter;
gtk_flow_box_insert_widget (box, GTK_WIDGET (child), iter);
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 .
*
* 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 .
*/
GtkFlowBoxChild *
gtk_flow_box_get_child_at_index (GtkFlowBox *box,
int 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);
if (!g_sequence_iter_is_end (iter))
return g_sequence_get (iter);
return NULL;
}
/**
* gtk_flow_box_get_child_at_pos :
* @ box : a ` GtkFlowBox `
* @ x : the x coordinate of the child
* @ y : the y coordinate of the child
*
* Gets the child in the ( @ x , @ y ) position .
*
* Both @ x and @ y are assumed to be relative to the origin of @ box .
*
* 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,
int x,
int y)
{
GtkWidget *child = gtk_widget_pick (GTK_WIDGET (box), x, y, GTK_PICK_DEFAULT);
if (!child)
return NULL;
return (GtkFlowBoxChild *)gtk_widget_get_ancestor (child, GTK_TYPE_FLOW_BOX_CHILD);
}
/**
* 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
*
* Hooks up an adjustment to focus handling in @ box .
*
* The adjustment is also used for autoscrolling during
* rubberband selection . See [ method @ Gtk . ScrolledWindow . get_hadjustment ]
* for a typical way of obtaining the adjustment , and
* [ method @ Gtk . FlowBox . 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
*
* Hooks up an adjustment to focus handling in @ box .
*
* The adjustment is also used for autoscrolling during
* rubberband selection . See [ method @ Gtk . ScrolledWindow . get_vadjustment ]
* for a typical way of obtaining the adjustment , and
* [ method @ Gtk . FlowBox . 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;
}
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" );
}
/**
* gtk_flow_box_bind_model :
* @ box : a ` GtkFlowBox `
* @ model : ( nullable ) : the ` GListModel ` to be bound to @ box
* @ create_widget_func : ( scope notified ) ( closure user_data ) ( destroy user_data_free_func ) : a function
* that creates widgets for items
* @ user_data : user data passed to @ create_widget_func
* @ 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
* [ method @ Gtk . FlowBox . insert ] ) while @ box is bound to a model .
*
* 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 .
*/
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);
GtkWidget *child;
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);
}
while ((child = gtk_widget_get_first_child (GTK_WIDGET (box))))
gtk_flow_box_remove (box, child);
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;
gtk_flow_box_check_model_compat (box);
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);
}
/* Setters and getters {{{2 */
/**
* gtk_flow_box_get_homogeneous :
* @ box : a ` GtkFlowBox `
*
* Returns whether the box is homogeneous .
*
* Returns : % TRUE if the box is homogeneous .
*/
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 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;
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_HOMOGENEOUS]);
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 .
*/
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));
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_ROW_SPACING]);
}
}
/**
* 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 .
*/
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));
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_COLUMN_SPACING]);
}
}
/**
* 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
* in @ box ’ s orientation before flowing .
*/
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));
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_MIN_CHILDREN_PER_LINE]);
}
}
/**
* 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
* allocate space for in @ box ’ s orientation .
*
* 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));
g_return_if_fail (n_children > 0 );
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));
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_MAX_CHILDREN_PER_LINE]);
}
}
/**
* 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;
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_ACTIVATE_ON_SINGLE_CLICK]);
}
}
/**
* 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;
}
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]);
}
/* Selection handling {{{2 */
/**
* 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));
gtk_flow_box_select_child_internal (box, child);
}
/**
* 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));
gtk_flow_box_unselect_child_internal (box, child);
}
/**
* 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 )
{
gtk_flow_box_select_all_between (box, NULL, NULL, FALSE );
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 ( ) .
*
* It will be called on every selected child of the @ box .
*/
/**
* 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 .
*/
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;
gtk_accessible_update_property (GTK_ACCESSIBLE (box),
GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE, mode == GTK_SELECTION_MULTIPLE,
-1 );
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_SELECTION_MODE]);
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
*
* A function that will be called whenever a child changes
* 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 `
* @ filter_func : ( nullable ) ( scope notified ) ( closure user_data ) ( destroy destroy ) : callback
* that lets you filter which children to show
* @ user_data : user data passed to @ filter_func
* @ 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 .
*
* The @ filter_func will be called for each child after the call , and
* it will continue to be called each time a child changes ( via
* [ method @ Gtk . FlowBoxChild . changed ] ) or when
* [ method @ Gtk . FlowBox . invalidate_filter ] is called .
*
* Note that using a filter function is incompatible with using a model
* ( see [ method @ Gtk . FlowBox . bind_model ] ) .
*/
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;
gtk_flow_box_check_model_compat (box);
gtk_flow_box_apply_filter_all (box);
}
/**
* 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 to 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)
gtk_flow_box_apply_filter_all (box);
}
/* 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
* they are equal , and > 0 otherwise
*/
/**
* gtk_flow_box_set_sort_func :
* @ box : a ` GtkFlowBox `
* @ sort_func : ( nullable ) ( scope notified ) ( closure user_data ) ( destroy destroy ) : the sort function
* @ user_data : user data passed to @ sort_func
* @ 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
* [ method @ Gtk . FlowBoxChild . changed ] ) and when
* [ method @ Gtk . FlowBox . invalidate_sort ] is called .
*
* Note that using a sort function is incompatible with using a model
* ( see [ method @ Gtk . FlowBox . bind_model ] ) .
*/
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;
gtk_flow_box_check_model_compat (box);
gtk_flow_box_invalidate_sort (box);
}
static int
gtk_flow_box_sort (GtkFlowBoxChild *a,
GtkFlowBoxChild *b,
GtkFlowBox *box)
{
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
return priv->sort_func (a, b, priv->sort_data);
}
static void
gtk_flow_box_reorder_foreach (gpointer data,
gpointer user_data)
{
GtkWidget **previous = user_data;
GtkWidget *row = data;
if (*previous)
gtk_widget_insert_after (row, _gtk_widget_get_parent (row), *previous);
*previous = row;
}
/**
* 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;
GtkWidget *previous = NULL;
g_return_if_fail (GTK_IS_FLOW_BOX (box));
priv = BOX_PRIV (box);
if (priv->sort_func != NULL)
{
g_sequence_sort (priv->children, (GCompareDataFunc)gtk_flow_box_sort, box);
g_sequence_foreach (priv->children, gtk_flow_box_reorder_foreach, &previous);
gtk_widget_queue_resize (GTK_WIDGET (box));
}
}
/* vim:set foldmethod=marker: */
Messung V0.5 in Prozent C=98 H=95 G=96
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.116Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-07-02)
¤
*Eine klare Vorstellung vom Zielzustand
2026-08-25
Neuigkeiten
Aktuelles
Motto des Tages
letze Version des Elbe Quellennavigators
Jenseits des Üblichen ....
Besucher
Statistik
#Sources=141584
#Domains=752002