forked from AuroraMiddleware/gtk
7a676cdc14
If the position of the children is always relative to the box then we should not take the allocation of the box into account when flipping the children for RTL text direction. This patch also removes unused assignments to child_allocation. https://bugzilla.gnome.org/show_bug.cgi?id=754559
5026 lines
154 KiB
C
5026 lines
154 KiB
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 */
|
||
|
||
/**
|
||
* SECTION:gtkflowbox
|
||
* @Short_Description: A container that allows reflowing its children
|
||
* @Title: GtkFlowBox
|
||
*
|
||
* A GtkFlowBox positions child widgets in sequence according to its
|
||
* orientation.
|
||
*
|
||
* For instance, with the horizontal orientation, the widgets will be
|
||
* arranged from left to right, starting a new row under the previous
|
||
* row when necessary. Reducing the width in this case will require more
|
||
* rows, so a larger height will be requested.
|
||
*
|
||
* Likewise, with the vertical orientation, the widgets will be arranged
|
||
* from top to bottom, starting a new column to the right when necessary.
|
||
* Reducing the height will require more columns, so a larger width will
|
||
* be requested.
|
||
*
|
||
* The children of a GtkFlowBox can be dynamically sorted and filtered.
|
||
*
|
||
* Although a GtkFlowBox must have only #GtkFlowBoxChild children,
|
||
* you can add any kind of widget to it via gtk_container_add(), and
|
||
* a GtkFlowBoxChild widget will automatically be inserted between
|
||
* the box and the widget.
|
||
*
|
||
* Also see #GtkListBox.
|
||
*
|
||
* GtkFlowBox was added in GTK+ 3.12.
|
||
*/
|
||
|
||
#include <config.h>
|
||
|
||
#include "gtkflowbox.h"
|
||
#include "gtkmarshalers.h"
|
||
#include "gtkprivate.h"
|
||
#include "gtkorientableprivate.h"
|
||
#include "gtkintl.h"
|
||
#include "gtkcssnodeprivate.h"
|
||
#include "gtkwidgetprivate.h"
|
||
|
||
#include "a11y/gtkflowboxaccessibleprivate.h"
|
||
#include "a11y/gtkflowboxchildaccessible.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 gint 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_check_model_compat (GtkFlowBox *box);
|
||
|
||
static void
|
||
get_current_selection_modifiers (GtkWidget *widget,
|
||
gboolean *modify,
|
||
gboolean *extend)
|
||
{
|
||
GdkModifierType state = 0;
|
||
GdkModifierType mask;
|
||
|
||
*modify = FALSE;
|
||
*extend = FALSE;
|
||
|
||
if (gtk_get_current_event_state (&state))
|
||
{
|
||
mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_MODIFY_SELECTION);
|
||
if ((state & mask) == mask)
|
||
*modify = TRUE;
|
||
mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_EXTEND_SELECTION);
|
||
if ((state & mask) == mask)
|
||
*extend = TRUE;
|
||
}
|
||
}
|
||
|
||
static void
|
||
path_from_horizontal_line_rects (cairo_t *cr,
|
||
GdkRectangle *lines,
|
||
gint n_lines)
|
||
{
|
||
gint start_line, end_line;
|
||
GdkRectangle *r;
|
||
gint i;
|
||
|
||
/* Join rows vertically by extending to the middle */
|
||
for (i = 0; i < n_lines - 1; i++)
|
||
{
|
||
GdkRectangle *r1 = &lines[i];
|
||
GdkRectangle *r2 = &lines[i+1];
|
||
gint gap, old;
|
||
|
||
gap = r2->y - (r1->y + r1->height);
|
||
r1->height += gap / 2;
|
||
old = r2->y;
|
||
r2->y = r1->y + r1->height;
|
||
r2->height += old - r2->y;
|
||
}
|
||
|
||
cairo_new_path (cr);
|
||
start_line = 0;
|
||
|
||
do
|
||
{
|
||
for (i = start_line; i < n_lines; i++)
|
||
{
|
||
r = &lines[i];
|
||
if (i == start_line)
|
||
cairo_move_to (cr, r->x + r->width, r->y);
|
||
else
|
||
cairo_line_to (cr, r->x + r->width, r->y);
|
||
cairo_line_to (cr, r->x + r->width, r->y + r->height);
|
||
|
||
if (i < n_lines - 1 &&
|
||
(r->x + r->width < lines[i+1].x ||
|
||
r->x > lines[i+1].x + lines[i+1].width))
|
||
{
|
||
i++;
|
||
break;
|
||
}
|
||
}
|
||
end_line = i;
|
||
for (i = end_line - 1; i >= start_line; i--)
|
||
{
|
||
r = &lines[i];
|
||
cairo_line_to (cr, r->x, r->y + r->height);
|
||
cairo_line_to (cr, r->x, r->y);
|
||
}
|
||
cairo_close_path (cr);
|
||
start_line = end_line;
|
||
}
|
||
while (end_line < n_lines);
|
||
}
|
||
|
||
static void
|
||
path_from_vertical_line_rects (cairo_t *cr,
|
||
GdkRectangle *lines,
|
||
gint n_lines)
|
||
{
|
||
gint start_line, end_line;
|
||
GdkRectangle *r;
|
||
gint i;
|
||
|
||
/* Join rows horizontally by extending to the middle */
|
||
for (i = 0; i < n_lines - 1; i++)
|
||
{
|
||
GdkRectangle *r1 = &lines[i];
|
||
GdkRectangle *r2 = &lines[i+1];
|
||
gint gap, old;
|
||
|
||
gap = r2->x - (r1->x + r1->width);
|
||
r1->width += gap / 2;
|
||
old = r2->x;
|
||
r2->x = r1->x + r1->width;
|
||
r2->width += old - r2->x;
|
||
}
|
||
|
||
cairo_new_path (cr);
|
||
start_line = 0;
|
||
|
||
do
|
||
{
|
||
for (i = start_line; i < n_lines; i++)
|
||
{
|
||
r = &lines[i];
|
||
if (i == start_line)
|
||
cairo_move_to (cr, r->x, r->y + r->height);
|
||
else
|
||
cairo_line_to (cr, r->x, r->y + r->height);
|
||
cairo_line_to (cr, r->x + r->width, r->y + r->height);
|
||
|
||
if (i < n_lines - 1 &&
|
||
(r->y + r->height < lines[i+1].y ||
|
||
r->y > lines[i+1].y + lines[i+1].height))
|
||
{
|
||
i++;
|
||
break;
|
||
}
|
||
}
|
||
end_line = i;
|
||
for (i = end_line - 1; i >= start_line; i--)
|
||
{
|
||
r = &lines[i];
|
||
cairo_line_to (cr, r->x + r->width, r->y);
|
||
cairo_line_to (cr, r->x, r->y);
|
||
}
|
||
cairo_close_path (cr);
|
||
start_line = end_line;
|
||
}
|
||
while (end_line < n_lines);
|
||
}
|
||
|
||
/* GtkFlowBoxChild {{{1 */
|
||
|
||
/* GObject boilerplate {{{2 */
|
||
|
||
enum {
|
||
CHILD_ACTIVATE,
|
||
CHILD_LAST_SIGNAL
|
||
};
|
||
|
||
static guint child_signals[CHILD_LAST_SIGNAL] = { 0 };
|
||
|
||
typedef struct _GtkFlowBoxChildPrivate GtkFlowBoxChildPrivate;
|
||
struct _GtkFlowBoxChildPrivate
|
||
{
|
||
GSequenceIter *iter;
|
||
gboolean selected;
|
||
};
|
||
|
||
#define CHILD_PRIV(child) ((GtkFlowBoxChildPrivate*)gtk_flow_box_child_get_instance_private ((GtkFlowBoxChild*)(child)))
|
||
|
||
G_DEFINE_TYPE_WITH_PRIVATE (GtkFlowBoxChild, gtk_flow_box_child, GTK_TYPE_BIN)
|
||
|
||
/* 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);
|
||
gboolean modify;
|
||
gboolean extend;
|
||
|
||
get_current_selection_modifiers (GTK_WIDGET (box), &modify, &extend);
|
||
|
||
if (modify)
|
||
gtk_flow_box_update_cursor (box, child);
|
||
else
|
||
gtk_flow_box_update_selection (box, child, FALSE, FALSE);
|
||
}
|
||
|
||
/* GtkWidget implementation {{{2 */
|
||
|
||
static gboolean
|
||
gtk_flow_box_child_focus (GtkWidget *widget,
|
||
GtkDirectionType direction)
|
||
{
|
||
gboolean had_focus = FALSE;
|
||
GtkWidget *child;
|
||
|
||
child = gtk_bin_get_child (GTK_BIN (widget));
|
||
|
||
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_container_get_focus_child (GTK_CONTAINER (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);
|
||
}
|
||
|
||
static gboolean
|
||
gtk_flow_box_child_draw (GtkWidget *widget,
|
||
cairo_t *cr)
|
||
{
|
||
GtkAllocation allocation = {0};
|
||
GtkStyleContext* context;
|
||
GtkStateFlags state;
|
||
GtkBorder border;
|
||
|
||
gtk_widget_get_allocation (widget, &allocation);
|
||
context = gtk_widget_get_style_context (widget);
|
||
state = gtk_widget_get_state_flags (widget);
|
||
|
||
gtk_render_background (context, cr, 0, 0, allocation.width, allocation.height);
|
||
gtk_render_frame (context, cr, 0, 0, allocation.width, allocation.height);
|
||
|
||
if (gtk_widget_has_visible_focus (widget))
|
||
{
|
||
gtk_style_context_get_border (context, state, &border);
|
||
gtk_render_focus (context, cr, border.left, border.top,
|
||
allocation.width - border.left - border.right,
|
||
allocation.height - border.top - border.bottom);
|
||
}
|
||
|
||
GTK_WIDGET_CLASS (gtk_flow_box_child_parent_class)->draw (widget, cr);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Size allocation {{{3 */
|
||
|
||
static void
|
||
gtk_flow_box_child_get_full_border (GtkFlowBoxChild *child,
|
||
GtkBorder *full_border)
|
||
{
|
||
GtkWidget *widget = GTK_WIDGET (child);
|
||
GtkStyleContext *context;
|
||
GtkStateFlags state;
|
||
GtkBorder padding, border;
|
||
|
||
context = gtk_widget_get_style_context (widget);
|
||
state = gtk_style_context_get_state (context);
|
||
|
||
gtk_style_context_get_padding (context, state, &padding);
|
||
gtk_style_context_get_border (context, state, &border);
|
||
|
||
full_border->left = padding.left + border.left;
|
||
full_border->right = padding.right + border.right;
|
||
full_border->top = padding.top + border.top;
|
||
full_border->bottom = padding.bottom + border.bottom;
|
||
}
|
||
|
||
static GtkSizeRequestMode
|
||
gtk_flow_box_child_get_request_mode (GtkWidget *widget)
|
||
{
|
||
GtkFlowBox *box;
|
||
|
||
box = gtk_flow_box_child_get_box (GTK_FLOW_BOX_CHILD (widget));
|
||
if (box)
|
||
return gtk_widget_get_request_mode (GTK_WIDGET (box));
|
||
else
|
||
return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
|
||
}
|
||
|
||
static void gtk_flow_box_child_get_preferred_width_for_height (GtkWidget *widget,
|
||
gint height,
|
||
gint *minimum_width,
|
||
gint *natural_width);
|
||
static void gtk_flow_box_child_get_preferred_height (GtkWidget *widget,
|
||
gint *minimum_height,
|
||
gint *natural_height);
|
||
|
||
static void
|
||
gtk_flow_box_child_get_preferred_height_for_width (GtkWidget *widget,
|
||
gint width,
|
||
gint *minimum_height,
|
||
gint *natural_height)
|
||
{
|
||
if (gtk_flow_box_child_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
|
||
{
|
||
GtkWidget *child;
|
||
gint child_min = 0, child_natural = 0;
|
||
GtkBorder full_border = { 0, };
|
||
|
||
gtk_flow_box_child_get_full_border (GTK_FLOW_BOX_CHILD (widget), &full_border);
|
||
child = gtk_bin_get_child (GTK_BIN (widget));
|
||
if (child && gtk_widget_get_visible (child))
|
||
gtk_widget_get_preferred_height_for_width (child, width - full_border.left - full_border.right,
|
||
&child_min, &child_natural);
|
||
|
||
*minimum_height = full_border.top + child_min + full_border.bottom;
|
||
*natural_height = full_border.top + child_natural + full_border.bottom;
|
||
}
|
||
else
|
||
{
|
||
gtk_flow_box_child_get_preferred_height (widget, minimum_height, natural_height);
|
||
}
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_child_get_preferred_width (GtkWidget *widget,
|
||
gint *minimum_width,
|
||
gint *natural_width)
|
||
{
|
||
if (gtk_flow_box_child_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
|
||
{
|
||
GtkWidget *child;
|
||
gint child_min = 0, child_natural = 0;
|
||
GtkBorder full_border = { 0, };
|
||
|
||
gtk_flow_box_child_get_full_border (GTK_FLOW_BOX_CHILD (widget), &full_border);
|
||
child = gtk_bin_get_child (GTK_BIN (widget));
|
||
if (child && gtk_widget_get_visible (child))
|
||
gtk_widget_get_preferred_width (child, &child_min, &child_natural);
|
||
|
||
*minimum_width = full_border.left + child_min + full_border.right;
|
||
*natural_width = full_border.left + child_natural + full_border.right;
|
||
}
|
||
else
|
||
{
|
||
gint natural_height;
|
||
|
||
gtk_flow_box_child_get_preferred_height (widget, NULL, &natural_height);
|
||
gtk_flow_box_child_get_preferred_width_for_height (widget, natural_height,
|
||
minimum_width, natural_width);
|
||
}
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_child_get_preferred_width_for_height (GtkWidget *widget,
|
||
gint height,
|
||
gint *minimum_width,
|
||
gint *natural_width)
|
||
{
|
||
if (gtk_flow_box_child_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
|
||
{
|
||
gtk_flow_box_child_get_preferred_width (widget, minimum_width, natural_width);
|
||
}
|
||
else
|
||
{
|
||
GtkWidget *child;
|
||
gint child_min = 0, child_natural = 0;
|
||
GtkBorder full_border = { 0, };
|
||
|
||
gtk_flow_box_child_get_full_border (GTK_FLOW_BOX_CHILD (widget), &full_border);
|
||
child = gtk_bin_get_child (GTK_BIN (widget));
|
||
if (child && gtk_widget_get_visible (child))
|
||
gtk_widget_get_preferred_width_for_height (child, height - full_border.top - full_border.bottom,
|
||
&child_min, &child_natural);
|
||
|
||
*minimum_width = full_border.left + child_min + full_border.right;
|
||
*natural_width = full_border.left + child_natural + full_border.right;
|
||
}
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_child_get_preferred_height (GtkWidget *widget,
|
||
gint *minimum_height,
|
||
gint *natural_height)
|
||
{
|
||
if (gtk_flow_box_child_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
|
||
{
|
||
gint natural_width;
|
||
|
||
gtk_flow_box_child_get_preferred_width (widget, NULL, &natural_width);
|
||
gtk_flow_box_child_get_preferred_height_for_width (widget, natural_width,
|
||
minimum_height, natural_height);
|
||
}
|
||
else
|
||
{
|
||
GtkWidget *child;
|
||
gint child_min = 0, child_natural = 0;
|
||
GtkBorder full_border = { 0, };
|
||
|
||
gtk_flow_box_child_get_full_border (GTK_FLOW_BOX_CHILD (widget), &full_border);
|
||
child = gtk_bin_get_child (GTK_BIN (widget));
|
||
if (child && gtk_widget_get_visible (child))
|
||
gtk_widget_get_preferred_height (child, &child_min, &child_natural);
|
||
|
||
*minimum_height = full_border.top + child_min + full_border.bottom;
|
||
*natural_height = full_border.top + child_natural + full_border.bottom;
|
||
}
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_child_size_allocate (GtkWidget *widget,
|
||
GtkAllocation *allocation)
|
||
{
|
||
GtkWidget *child;
|
||
|
||
gtk_widget_set_allocation (widget, allocation);
|
||
|
||
child = gtk_bin_get_child (GTK_BIN (widget));
|
||
if (child && gtk_widget_get_visible (child))
|
||
{
|
||
GtkAllocation child_allocation;
|
||
GtkBorder border = { 0, };
|
||
|
||
gtk_flow_box_child_get_full_border (GTK_FLOW_BOX_CHILD (widget), &border);
|
||
|
||
child_allocation.x = allocation->x + border.left;
|
||
child_allocation.y = allocation->y + border.top;
|
||
child_allocation.width = allocation->width - border.left - border.right;
|
||
child_allocation.height = allocation->height - border.top - border.bottom;
|
||
|
||
child_allocation.width = MAX (1, child_allocation.width);
|
||
child_allocation.height = MAX (1, child_allocation.height);
|
||
|
||
gtk_widget_size_allocate (child, &child_allocation);
|
||
}
|
||
}
|
||
|
||
/* 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);
|
||
|
||
widget_class->draw = gtk_flow_box_child_draw;
|
||
widget_class->get_request_mode = gtk_flow_box_child_get_request_mode;
|
||
widget_class->get_preferred_height = gtk_flow_box_child_get_preferred_height;
|
||
widget_class->get_preferred_height_for_width = gtk_flow_box_child_get_preferred_height_for_width;
|
||
widget_class->get_preferred_width = gtk_flow_box_child_get_preferred_width;
|
||
widget_class->get_preferred_width_for_height = gtk_flow_box_child_get_preferred_width_for_height;
|
||
widget_class->size_allocate = gtk_flow_box_child_size_allocate;
|
||
widget_class->focus = gtk_flow_box_child_focus;
|
||
|
||
class->activate = gtk_flow_box_child_activate;
|
||
|
||
/**
|
||
* GtkFlowBoxChild::activate:
|
||
* @child: The child on which the signal is emitted
|
||
*
|
||
* The ::activate signal is emitted when the user activates
|
||
* a child widget in a #GtkFlowBox, either by clicking or
|
||
* double-clicking, or by using the Space or Enter key.
|
||
*
|
||
* While this signal is used as a
|
||
* [keybinding signal][GtkBindingSignal],
|
||
* it can be used by applications for their own purposes.
|
||
*/
|
||
child_signals[CHILD_ACTIVATE] =
|
||
g_signal_new (I_("activate"),
|
||
G_OBJECT_CLASS_TYPE (object_class),
|
||
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||
G_STRUCT_OFFSET (GtkFlowBoxChildClass, activate),
|
||
NULL, NULL,
|
||
g_cclosure_marshal_VOID__VOID,
|
||
G_TYPE_NONE, 0);
|
||
widget_class->activate_signal = child_signals[CHILD_ACTIVATE];
|
||
|
||
gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_LIST_ITEM);
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_child_init (GtkFlowBoxChild *child)
|
||
{
|
||
GtkStyleContext *context;
|
||
|
||
gtk_widget_set_can_focus (GTK_WIDGET (child), TRUE);
|
||
gtk_widget_set_redraw_on_allocate (GTK_WIDGET (child), TRUE);
|
||
|
||
context = gtk_widget_get_style_context (GTK_WIDGET (child));
|
||
gtk_style_context_add_class (context, "grid-child");
|
||
}
|
||
|
||
/* Public API {{{2 */
|
||
|
||
/**
|
||
* gtk_flow_box_child_new:
|
||
*
|
||
* Creates a new #GtkFlowBoxChild, to be used as a child
|
||
* of a #GtkFlowBox.
|
||
*
|
||
* Returns: a new #GtkFlowBoxChild
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
GtkWidget *
|
||
gtk_flow_box_child_new (void)
|
||
{
|
||
return g_object_new (GTK_TYPE_FLOW_BOX_CHILD, NULL);
|
||
}
|
||
|
||
/**
|
||
* 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.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
gint
|
||
gtk_flow_box_child_get_index (GtkFlowBoxChild *child)
|
||
{
|
||
GtkFlowBoxChildPrivate *priv;
|
||
|
||
g_return_val_if_fail (GTK_IS_FLOW_BOX_CHILD (child), -1);
|
||
|
||
priv = CHILD_PRIV (child);
|
||
|
||
if (priv->iter != NULL)
|
||
return g_sequence_iter_get_position (priv->iter);
|
||
|
||
return -1;
|
||
}
|
||
|
||
/**
|
||
* gtk_flow_box_child_is_selected:
|
||
* @child: a #GtkFlowBoxChild
|
||
*
|
||
* Returns whether the @child is currently selected in its
|
||
* #GtkFlowBox container.
|
||
*
|
||
* Returns: %TRUE if @child is selected
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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 gtk_flow_box_invalidate_sort() on any
|
||
* model change, but that is more expensive.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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,
|
||
|
||
/* orientable */
|
||
PROP_ORIENTATION,
|
||
LAST_PROP = PROP_ORIENTATION
|
||
};
|
||
|
||
static GParamSpec *props[LAST_PROP] = { NULL, };
|
||
|
||
typedef struct _GtkFlowBoxPrivate GtkFlowBoxPrivate;
|
||
struct _GtkFlowBoxPrivate {
|
||
GtkOrientation orientation;
|
||
gboolean homogeneous;
|
||
|
||
guint row_spacing;
|
||
guint column_spacing;
|
||
|
||
GtkFlowBoxChild *prelight_child;
|
||
GtkFlowBoxChild *cursor_child;
|
||
GtkFlowBoxChild *selected_child;
|
||
|
||
gboolean active_child_active;
|
||
GtkFlowBoxChild *active_child;
|
||
|
||
GtkSelectionMode selection_mode;
|
||
|
||
GtkAdjustment *hadjustment;
|
||
GtkAdjustment *vadjustment;
|
||
gboolean activate_on_single_click;
|
||
|
||
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 *multipress_gesture;
|
||
GtkGesture *drag_gesture;
|
||
|
||
gboolean rubberband_select;
|
||
GtkFlowBoxChild *rubberband_first;
|
||
GtkFlowBoxChild *rubberband_last;
|
||
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;
|
||
};
|
||
|
||
#define BOX_PRIV(box) ((GtkFlowBoxPrivate*)gtk_flow_box_get_instance_private ((GtkFlowBox*)(box)))
|
||
|
||
G_DEFINE_TYPE_WITH_CODE (GtkFlowBox, gtk_flow_box, GTK_TYPE_CONTAINER,
|
||
G_ADD_PRIVATE (GtkFlowBox)
|
||
G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
|
||
|
||
/* 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 gint
|
||
get_visible_children (GtkFlowBox *box)
|
||
{
|
||
GSequenceIter *iter;
|
||
gint i = 0;
|
||
|
||
for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
||
!g_sequence_iter_is_end (iter);
|
||
iter = g_sequence_iter_next (iter))
|
||
{
|
||
GtkWidget *child;
|
||
|
||
child = g_sequence_get (iter);
|
||
if (child_is_visible (child))
|
||
i++;
|
||
}
|
||
|
||
return i;
|
||
}
|
||
|
||
static GtkFlowBoxChild *
|
||
gtk_flow_box_find_child_at_pos (GtkFlowBox *box,
|
||
gint x,
|
||
gint y)
|
||
{
|
||
GtkWidget *child;
|
||
GSequenceIter *iter;
|
||
GtkAllocation allocation;
|
||
|
||
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_is_visible (child))
|
||
continue;
|
||
gtk_widget_get_allocation (child, &allocation);
|
||
if (x >= allocation.x && x < (allocation.x + allocation.width) &&
|
||
y >= allocation.y && y < (allocation.y + allocation.height))
|
||
return GTK_FLOW_BOX_CHILD (child);
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_update_prelight (GtkFlowBox *box,
|
||
GtkFlowBoxChild *child)
|
||
{
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
|
||
if (child != priv->prelight_child)
|
||
{
|
||
priv->prelight_child = child;
|
||
gtk_widget_queue_draw (GTK_WIDGET (box));
|
||
}
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_update_active (GtkFlowBox *box,
|
||
GtkFlowBoxChild *child)
|
||
{
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
gboolean val;
|
||
|
||
val = priv->active_child == child;
|
||
if (priv->active_child != NULL &&
|
||
val != priv->active_child_active)
|
||
{
|
||
priv->active_child_active = val;
|
||
gtk_widget_queue_draw (GTK_WIDGET (box));
|
||
}
|
||
}
|
||
|
||
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));
|
||
}
|
||
}
|
||
|
||
/* Selection utilities {{{3 */
|
||
|
||
static gboolean
|
||
gtk_flow_box_child_set_selected (GtkFlowBoxChild *child,
|
||
gboolean selected)
|
||
{
|
||
if (CHILD_PRIV (child)->selected != selected)
|
||
{
|
||
CHILD_PRIV (child)->selected = selected;
|
||
if (selected)
|
||
gtk_widget_set_state_flags (GTK_WIDGET (child),
|
||
GTK_STATE_FLAG_SELECTED, FALSE);
|
||
else
|
||
gtk_widget_unset_state_flags (GTK_WIDGET (child),
|
||
GTK_STATE_FLAG_SELECTED);
|
||
|
||
gtk_widget_queue_draw (GTK_WIDGET (child));
|
||
|
||
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));
|
||
gtk_widget_queue_draw (GTK_WIDGET (child));
|
||
_gtk_flow_box_accessible_update_cursor (GTK_WIDGET (box), 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;
|
||
gint i;
|
||
|
||
while (TRUE)
|
||
{
|
||
i = 0;
|
||
while (i < BOX_PRIV (box)->cur_children_per_line)
|
||
{
|
||
if (g_sequence_iter_is_begin (iter))
|
||
return NULL;
|
||
iter = g_sequence_iter_prev (iter);
|
||
child = g_sequence_get (iter);
|
||
if (child_is_visible (GTK_WIDGET (child)))
|
||
i++;
|
||
}
|
||
if (child && gtk_widget_get_sensitive (GTK_WIDGET (child)))
|
||
return iter;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static GSequenceIter *
|
||
gtk_flow_box_get_below_focusable (GtkFlowBox *box,
|
||
GSequenceIter *iter)
|
||
{
|
||
GtkFlowBoxChild *child = NULL;
|
||
gint i;
|
||
|
||
while (TRUE)
|
||
{
|
||
i = 0;
|
||
while (i < BOX_PRIV (box)->cur_children_per_line)
|
||
{
|
||
iter = g_sequence_iter_next (iter);
|
||
if (g_sequence_iter_is_end (iter))
|
||
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,
|
||
gint *min_size,
|
||
gint *nat_size)
|
||
{
|
||
GSequenceIter *iter;
|
||
gint max_min_size = 0;
|
||
gint max_nat_size = 0;
|
||
|
||
for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
||
!g_sequence_iter_is_end (iter);
|
||
iter = g_sequence_iter_next (iter))
|
||
{
|
||
GtkWidget *child;
|
||
gint child_min, child_nat;
|
||
|
||
child = g_sequence_get (iter);
|
||
|
||
if (!child_is_visible (child))
|
||
continue;
|
||
|
||
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
||
gtk_widget_get_preferred_width (child, &child_min, &child_nat);
|
||
else
|
||
gtk_widget_get_preferred_height (child, &child_min, &child_nat);
|
||
|
||
max_min_size = MAX (max_min_size, child_min);
|
||
max_nat_size = MAX (max_nat_size, child_nat);
|
||
}
|
||
|
||
if (min_size)
|
||
*min_size = max_min_size;
|
||
|
||
if (nat_size)
|
||
*nat_size = max_nat_size;
|
||
}
|
||
|
||
|
||
/* Gets the largest minimum/natural size for a given size (used to get
|
||
* the largest item heights for a fixed item width and the opposite)
|
||
*/
|
||
static void
|
||
get_largest_size_for_opposing_orientation (GtkFlowBox *box,
|
||
GtkOrientation orientation,
|
||
gint item_size,
|
||
gint *min_item_size,
|
||
gint *nat_item_size)
|
||
{
|
||
GSequenceIter *iter;
|
||
gint max_min_size = 0;
|
||
gint max_nat_size = 0;
|
||
|
||
for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
||
!g_sequence_iter_is_end (iter);
|
||
iter = g_sequence_iter_next (iter))
|
||
{
|
||
GtkWidget *child;
|
||
gint child_min, child_nat;
|
||
|
||
child = g_sequence_get (iter);
|
||
|
||
if (!child_is_visible (child))
|
||
continue;
|
||
|
||
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
||
gtk_widget_get_preferred_height_for_width (child,
|
||
item_size,
|
||
&child_min, &child_nat);
|
||
else
|
||
gtk_widget_get_preferred_width_for_height (child,
|
||
item_size,
|
||
&child_min, &child_nat);
|
||
|
||
max_min_size = MAX (max_min_size, child_min);
|
||
max_nat_size = MAX (max_nat_size, child_nat);
|
||
}
|
||
|
||
if (min_item_size)
|
||
*min_item_size = max_min_size;
|
||
|
||
if (nat_item_size)
|
||
*nat_item_size = max_nat_size;
|
||
}
|
||
|
||
/* Gets the largest minimum/natural size on a single line for a given size
|
||
* (used to get the largest line heights for a fixed item width and the opposite
|
||
* while iterating over a list of children, note the new index is returned)
|
||
*/
|
||
static GSequenceIter *
|
||
get_largest_size_for_line_in_opposing_orientation (GtkFlowBox *box,
|
||
GtkOrientation orientation,
|
||
GSequenceIter *cursor,
|
||
gint line_length,
|
||
GtkRequestedSize *item_sizes,
|
||
gint extra_pixels,
|
||
gint *min_item_size,
|
||
gint *nat_item_size)
|
||
{
|
||
GSequenceIter *iter;
|
||
gint max_min_size = 0;
|
||
gint max_nat_size = 0;
|
||
gint i;
|
||
|
||
i = 0;
|
||
for (iter = cursor;
|
||
!g_sequence_iter_is_end (iter) && i < line_length;
|
||
iter = g_sequence_iter_next (iter))
|
||
{
|
||
GtkWidget *child;
|
||
gint child_min, child_nat, this_item_size;
|
||
|
||
child = g_sequence_get (iter);
|
||
|
||
if (!child_is_visible (child))
|
||
continue;
|
||
|
||
/* Distribute the extra pixels to the first children in the line
|
||
* (could be fancier and spread them out more evenly) */
|
||
this_item_size = item_sizes[i].minimum_size;
|
||
if (extra_pixels > 0 && ORIENTATION_ALIGN (box) == GTK_ALIGN_FILL)
|
||
{
|
||
this_item_size++;
|
||
extra_pixels--;
|
||
}
|
||
|
||
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
||
gtk_widget_get_preferred_height_for_width (child,
|
||
this_item_size,
|
||
&child_min, &child_nat);
|
||
else
|
||
gtk_widget_get_preferred_width_for_height (child,
|
||
this_item_size,
|
||
&child_min, &child_nat);
|
||
|
||
max_min_size = MAX (max_min_size, child_min);
|
||
max_nat_size = MAX (max_nat_size, child_nat);
|
||
|
||
i++;
|
||
}
|
||
|
||
if (min_item_size)
|
||
*min_item_size = max_min_size;
|
||
|
||
if (nat_item_size)
|
||
*nat_item_size = max_nat_size;
|
||
|
||
/* Return next item in the list */
|
||
return iter;
|
||
}
|
||
|
||
/* fit_aligned_item_requests() helper */
|
||
static gint
|
||
gather_aligned_item_requests (GtkFlowBox *box,
|
||
GtkOrientation orientation,
|
||
gint line_length,
|
||
gint item_spacing,
|
||
gint n_children,
|
||
GtkRequestedSize *item_sizes)
|
||
{
|
||
GSequenceIter *iter;
|
||
gint i;
|
||
gint extra_items, natural_line_size = 0;
|
||
|
||
extra_items = n_children % line_length;
|
||
|
||
i = 0;
|
||
for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
||
!g_sequence_iter_is_end (iter);
|
||
iter = g_sequence_iter_next (iter))
|
||
{
|
||
GtkWidget *child;
|
||
GtkAlign item_align;
|
||
gint child_min, child_nat;
|
||
gint position;
|
||
|
||
child = g_sequence_get (iter);
|
||
|
||
if (!child_is_visible (child))
|
||
continue;
|
||
|
||
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
||
gtk_widget_get_preferred_width (child,
|
||
&child_min, &child_nat);
|
||
else
|
||
gtk_widget_get_preferred_height (child,
|
||
&child_min, &child_nat);
|
||
|
||
/* Get the index and push it over for the last line when spreading to the end */
|
||
position = i % line_length;
|
||
|
||
item_align = ORIENTATION_ALIGN (box);
|
||
if (item_align == GTK_ALIGN_END && i >= n_children - extra_items)
|
||
position += line_length - extra_items;
|
||
|
||
/* Round up the size of every column/row */
|
||
item_sizes[position].minimum_size = MAX (item_sizes[position].minimum_size, child_min);
|
||
item_sizes[position].natural_size = MAX (item_sizes[position].natural_size, child_nat);
|
||
|
||
i++;
|
||
}
|
||
|
||
for (i = 0; i < line_length; i++)
|
||
natural_line_size += item_sizes[i].natural_size;
|
||
|
||
natural_line_size += (line_length - 1) * item_spacing;
|
||
|
||
return natural_line_size;
|
||
}
|
||
|
||
static GtkRequestedSize *
|
||
fit_aligned_item_requests (GtkFlowBox *box,
|
||
GtkOrientation orientation,
|
||
gint avail_size,
|
||
gint item_spacing,
|
||
gint *line_length, /* in-out */
|
||
gint items_per_line,
|
||
gint n_children)
|
||
{
|
||
GtkRequestedSize *sizes, *try_sizes;
|
||
gint try_line_size, try_length;
|
||
|
||
sizes = g_new0 (GtkRequestedSize, *line_length);
|
||
|
||
/* get the sizes for the initial guess */
|
||
try_line_size = gather_aligned_item_requests (box,
|
||
orientation,
|
||
*line_length,
|
||
item_spacing,
|
||
n_children,
|
||
sizes);
|
||
|
||
/* Try columnizing the whole thing and adding an item to the end of
|
||
* the line; try to fit as many columns into the available size as
|
||
* possible
|
||
*/
|
||
for (try_length = *line_length + 1; try_line_size < avail_size; try_length++)
|
||
{
|
||
try_sizes = g_new0 (GtkRequestedSize, try_length);
|
||
try_line_size = gather_aligned_item_requests (box,
|
||
orientation,
|
||
try_length,
|
||
item_spacing,
|
||
n_children,
|
||
try_sizes);
|
||
|
||
if (try_line_size <= avail_size &&
|
||
items_per_line >= try_length)
|
||
{
|
||
*line_length = try_length;
|
||
|
||
g_free (sizes);
|
||
sizes = try_sizes;
|
||
}
|
||
else
|
||
{
|
||
/* oops, this one failed; stick to the last size that fit and then return */
|
||
g_free (try_sizes);
|
||
break;
|
||
}
|
||
}
|
||
|
||
return sizes;
|
||
}
|
||
|
||
typedef struct {
|
||
GArray *requested;
|
||
gint extra_pixels;
|
||
} AllocatedLine;
|
||
|
||
static gint
|
||
get_offset_pixels (GtkAlign align,
|
||
gint pixels)
|
||
{
|
||
gint offset;
|
||
|
||
switch (align) {
|
||
case GTK_ALIGN_START:
|
||
case GTK_ALIGN_FILL:
|
||
offset = 0;
|
||
break;
|
||
case GTK_ALIGN_CENTER:
|
||
offset = pixels / 2;
|
||
break;
|
||
case GTK_ALIGN_END:
|
||
offset = pixels;
|
||
break;
|
||
default:
|
||
g_assert_not_reached ();
|
||
break;
|
||
}
|
||
|
||
return offset;
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_size_allocate (GtkWidget *widget,
|
||
GtkAllocation *allocation)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
GtkAllocation child_allocation;
|
||
gint avail_size, avail_other_size, min_items, item_spacing, line_spacing;
|
||
GtkAlign item_align;
|
||
GtkAlign line_align;
|
||
GdkWindow *window;
|
||
GtkRequestedSize *line_sizes = NULL;
|
||
GtkRequestedSize *item_sizes = NULL;
|
||
gint min_item_size, nat_item_size;
|
||
gint line_length;
|
||
gint item_size = 0;
|
||
gint line_size = 0, min_fixed_line_size = 0, nat_fixed_line_size = 0;
|
||
gint line_offset, item_offset, n_children, n_lines, line_count;
|
||
gint extra_pixels = 0, extra_per_item = 0, extra_extra = 0;
|
||
gint extra_line_pixels = 0, extra_per_line = 0, extra_line_extra = 0;
|
||
gint i, this_line_size;
|
||
GSequenceIter *iter;
|
||
|
||
gtk_widget_set_allocation (widget, allocation);
|
||
window = gtk_widget_get_window (widget);
|
||
if (window != NULL)
|
||
gdk_window_move_resize (window,
|
||
allocation->x, allocation->y,
|
||
allocation->width, allocation->height);
|
||
|
||
min_items = MAX (1, priv->min_children_per_line);
|
||
|
||
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
|
||
{
|
||
avail_size = allocation->width;
|
||
avail_other_size = allocation->height;
|
||
item_spacing = priv->column_spacing; line_spacing = priv->row_spacing;
|
||
}
|
||
else /* GTK_ORIENTATION_VERTICAL */
|
||
{
|
||
avail_size = allocation->height;
|
||
avail_other_size = allocation->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)
|
||
return;
|
||
|
||
/* By default flow at the natural item width */
|
||
line_length = avail_size / (nat_item_size + item_spacing);
|
||
|
||
/* After the above aproximation, check if we cant fit one more on the line */
|
||
if (line_length * item_spacing + (line_length + 1) * nat_item_size <= avail_size)
|
||
line_length++;
|
||
|
||
/* Its possible we were allocated just less than the natural width of the
|
||
* 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 incase 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;
|
||
}
|
||
|
||
/*
|
||
* Prepare item/line initial offsets and jump into the
|
||
* real allocation loop.
|
||
*/
|
||
line_offset = item_offset = 0;
|
||
|
||
/* 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;
|
||
gint position;
|
||
gint this_item_size;
|
||
|
||
child = g_sequence_get (iter);
|
||
|
||
if (!child_is_visible (child))
|
||
continue;
|
||
|
||
/* Get item position */
|
||
position = i % line_length;
|
||
|
||
/* adjust the line_offset/count at the beginning of each new line */
|
||
if (i > 0 && position == 0)
|
||
{
|
||
/* Push the line_offset */
|
||
line_offset += this_line_size + line_spacing;
|
||
|
||
line_count++;
|
||
|
||
/* Get the new line size */
|
||
if (priv->homogeneous)
|
||
this_line_size = line_size;
|
||
else
|
||
{
|
||
this_line_size = line_sizes[line_count].minimum_size;
|
||
|
||
if (line_align == GTK_ALIGN_FILL)
|
||
{
|
||
this_line_size += extra_per_line;
|
||
|
||
if (line_count < extra_line_extra)
|
||
this_line_size++;
|
||
}
|
||
}
|
||
|
||
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)
|
||
{
|
||
gint extra_items = n_children % line_length;
|
||
|
||
if (priv->homogeneous)
|
||
{
|
||
item_offset += item_size * (line_length - extra_items);
|
||
item_offset += item_spacing * (line_length - extra_items);
|
||
}
|
||
else
|
||
{
|
||
gint j;
|
||
|
||
for (j = 0; j < (line_length - extra_items); j++)
|
||
{
|
||
item_offset += item_sizes[j].minimum_size;
|
||
item_offset += item_spacing;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Push the index along for the last line when spreading to the end */
|
||
if (item_align == GTK_ALIGN_END && line_count == n_lines -1)
|
||
{
|
||
gint extra_items = n_children % line_length;
|
||
|
||
position += line_length - extra_items;
|
||
}
|
||
|
||
if (priv->homogeneous)
|
||
this_item_size = item_size;
|
||
else
|
||
this_item_size = item_sizes[position].minimum_size;
|
||
|
||
if (item_align == GTK_ALIGN_FILL)
|
||
{
|
||
this_item_size += extra_per_item;
|
||
|
||
if (position < extra_extra)
|
||
this_item_size++;
|
||
}
|
||
|
||
/* Do the actual allocation */
|
||
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
|
||
{
|
||
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 = allocation->width - child_allocation.x - child_allocation.width;
|
||
gtk_widget_size_allocate (child, &child_allocation);
|
||
|
||
item_offset += this_item_size;
|
||
item_offset += item_spacing;
|
||
|
||
i++;
|
||
}
|
||
|
||
g_free (item_sizes);
|
||
g_free (line_sizes);
|
||
}
|
||
|
||
static GtkSizeRequestMode
|
||
gtk_flow_box_get_request_mode (GtkWidget *widget)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
||
|
||
return (BOX_PRIV (box)->orientation == GTK_ORIENTATION_HORIZONTAL) ?
|
||
GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH : GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
|
||
}
|
||
|
||
/* Gets the largest minimum and natural length of
|
||
* 'line_length' consecutive items when aligned into rows/columns */
|
||
static void
|
||
get_largest_aligned_line_length (GtkFlowBox *box,
|
||
GtkOrientation orientation,
|
||
gint line_length,
|
||
gint *min_size,
|
||
gint *nat_size)
|
||
{
|
||
GSequenceIter *iter;
|
||
gint max_min_size = 0;
|
||
gint max_nat_size = 0;
|
||
gint spacing, i;
|
||
GtkRequestedSize *aligned_item_sizes;
|
||
|
||
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
||
spacing = BOX_PRIV (box)->column_spacing;
|
||
else
|
||
spacing = BOX_PRIV (box)->row_spacing;
|
||
|
||
aligned_item_sizes = g_new0 (GtkRequestedSize, line_length);
|
||
|
||
/* Get the largest sizes of each index in the line.
|
||
*/
|
||
i = 0;
|
||
for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
||
!g_sequence_iter_is_end (iter);
|
||
iter = g_sequence_iter_next (iter))
|
||
{
|
||
GtkWidget *child;
|
||
gint child_min, child_nat;
|
||
|
||
child = g_sequence_get (iter);
|
||
if (!child_is_visible (child))
|
||
continue;
|
||
|
||
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
||
gtk_widget_get_preferred_width (child,
|
||
&child_min, &child_nat);
|
||
else /* GTK_ORIENTATION_VERTICAL */
|
||
gtk_widget_get_preferred_height (child,
|
||
&child_min, &child_nat);
|
||
|
||
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_get_preferred_width (GtkWidget *widget,
|
||
gint *minimum_size,
|
||
gint *natural_size)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
gint min_item_width, nat_item_width;
|
||
gint min_items, nat_items;
|
||
gint 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 */
|
||
if (min_items == 1)
|
||
{
|
||
get_max_item_size (box,
|
||
GTK_ORIENTATION_HORIZONTAL,
|
||
&min_item_width,
|
||
&nat_item_width);
|
||
|
||
min_width += min_item_width;
|
||
nat_width += nat_item_width;
|
||
}
|
||
else
|
||
{
|
||
gint min_line_length, nat_line_length;
|
||
|
||
get_largest_aligned_line_length (box,
|
||
GTK_ORIENTATION_HORIZONTAL,
|
||
min_items,
|
||
&min_line_length,
|
||
&nat_line_length);
|
||
|
||
if (nat_items > min_items)
|
||
get_largest_aligned_line_length (box,
|
||
GTK_ORIENTATION_HORIZONTAL,
|
||
nat_items,
|
||
NULL,
|
||
&nat_line_length);
|
||
|
||
min_width += min_line_length;
|
||
nat_width += nat_line_length;
|
||
}
|
||
}
|
||
else /* In homogeneous mode; horizontally oriented boxs
|
||
* give the same width to all children */
|
||
{
|
||
get_max_item_size (box, GTK_ORIENTATION_HORIZONTAL,
|
||
&min_item_width, &nat_item_width);
|
||
|
||
min_width += min_item_width * min_items;
|
||
min_width += (min_items -1) * priv->column_spacing;
|
||
|
||
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 */
|
||
gint min_height, nat_height;
|
||
|
||
GTK_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, &min_height, &nat_height);
|
||
GTK_WIDGET_GET_CLASS (widget)->get_preferred_width_for_height (widget,
|
||
min_height,
|
||
&min_width,
|
||
&nat_width);
|
||
|
||
}
|
||
|
||
if (minimum_size)
|
||
*minimum_size = min_width;
|
||
|
||
if (natural_size)
|
||
*natural_size = nat_width;
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_get_preferred_height (GtkWidget *widget,
|
||
gint *minimum_size,
|
||
gint *natural_size)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
gint min_item_height, nat_item_height;
|
||
gint min_items, nat_items;
|
||
gint 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 minimum width */
|
||
gint min_width, nat_width;
|
||
|
||
GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, &nat_width);
|
||
GTK_WIDGET_GET_CLASS (widget)->get_preferred_height_for_width (widget,
|
||
min_width,
|
||
&min_height,
|
||
&nat_height);
|
||
}
|
||
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
|
||
{
|
||
gint min_line_length, nat_line_length;
|
||
|
||
get_largest_aligned_line_length (box,
|
||
GTK_ORIENTATION_VERTICAL,
|
||
min_items,
|
||
&min_line_length,
|
||
&nat_line_length);
|
||
|
||
if (nat_items > min_items)
|
||
get_largest_aligned_line_length (box,
|
||
GTK_ORIENTATION_VERTICAL,
|
||
nat_items,
|
||
NULL,
|
||
&nat_line_length);
|
||
|
||
min_height += min_line_length;
|
||
nat_height += nat_line_length;
|
||
}
|
||
|
||
}
|
||
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;
|
||
}
|
||
}
|
||
|
||
if (minimum_size)
|
||
*minimum_size = min_height;
|
||
|
||
if (natural_size)
|
||
*natural_size = nat_height;
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_get_preferred_height_for_width (GtkWidget *widget,
|
||
gint width,
|
||
gint *minimum_height,
|
||
gint *natural_height)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
gint min_item_width, nat_item_width;
|
||
gint min_items;
|
||
gint min_height, nat_height;
|
||
gint 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)
|
||
{
|
||
gint min_width, nat_width;
|
||
gint line_length;
|
||
gint item_size, extra_pixels;
|
||
|
||
n_children = get_visible_children (box);
|
||
if (n_children <= 0)
|
||
goto out;
|
||
|
||
/* Make sure its no smaller than the minimum */
|
||
GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, &nat_width);
|
||
|
||
avail_size = MAX (width, min_width);
|
||
if (avail_size <= 0)
|
||
goto out;
|
||
|
||
get_max_item_size (box, GTK_ORIENTATION_HORIZONTAL, &min_item_width, &nat_item_width);
|
||
if (nat_item_width <= 0)
|
||
goto out;
|
||
|
||
/* By default flow at the natural item width */
|
||
line_length = avail_size / (nat_item_width + priv->column_spacing);
|
||
|
||
/* After the above aproximation, check if we cant fit one more on the line */
|
||
if (line_length * priv->column_spacing + (line_length + 1) * nat_item_width <= avail_size)
|
||
line_length++;
|
||
|
||
/* 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)
|
||
{
|
||
gint min_item_height, nat_item_height;
|
||
gint lines;
|
||
|
||
/* Here we just use the largest height-for-width and
|
||
* add up the size accordingly
|
||
*/
|
||
get_largest_size_for_opposing_orientation (box,
|
||
GTK_ORIENTATION_HORIZONTAL,
|
||
item_size,
|
||
&min_item_height,
|
||
&nat_item_height);
|
||
|
||
/* Round up how many lines we need to allocate for */
|
||
lines = n_children / line_length;
|
||
if ((n_children % line_length) > 0)
|
||
lines++;
|
||
|
||
min_height = min_item_height * lines;
|
||
nat_height = nat_item_height * lines;
|
||
|
||
min_height += (lines - 1) * priv->row_spacing;
|
||
nat_height += (lines - 1) * priv->row_spacing;
|
||
}
|
||
else
|
||
{
|
||
gint min_line_height, nat_line_height, i;
|
||
gboolean first_line = TRUE;
|
||
GtkRequestedSize *item_sizes;
|
||
GSequenceIter *iter;
|
||
|
||
/* First get the size each set of items take to span the line
|
||
* when aligning the items above and below after flowping.
|
||
*/
|
||
item_sizes = fit_aligned_item_requests (box,
|
||
priv->orientation,
|
||
avail_size,
|
||
priv->column_spacing,
|
||
&line_length,
|
||
priv->max_children_per_line,
|
||
n_children);
|
||
|
||
/* Get the available remaining size */
|
||
avail_size -= (line_length - 1) * priv->column_spacing;
|
||
for (i = 0; i < line_length; i++)
|
||
avail_size -= item_sizes[i].minimum_size;
|
||
|
||
if (avail_size > 0)
|
||
extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes);
|
||
|
||
for (iter = g_sequence_get_begin_iter (priv->children);
|
||
!g_sequence_iter_is_end (iter);)
|
||
{
|
||
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_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, &min_height, &nat_height);
|
||
}
|
||
|
||
out:
|
||
|
||
if (minimum_height)
|
||
*minimum_height = min_height;
|
||
|
||
if (natural_height)
|
||
*natural_height = nat_height;
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_get_preferred_width_for_height (GtkWidget *widget,
|
||
gint height,
|
||
gint *minimum_width,
|
||
gint *natural_width)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
gint min_item_height, nat_item_height;
|
||
gint min_items;
|
||
gint min_width, nat_width;
|
||
gint 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)
|
||
{
|
||
/* Return the minimum width */
|
||
GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, &nat_width);
|
||
}
|
||
else /* GTK_ORIENTATION_VERTICAL */
|
||
{
|
||
gint min_height, nat_height;
|
||
gint line_length;
|
||
gint item_size, extra_pixels;
|
||
|
||
n_children = get_visible_children (box);
|
||
if (n_children <= 0)
|
||
goto out;
|
||
|
||
/* Make sure its no smaller than the minimum */
|
||
GTK_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, &min_height, &nat_height);
|
||
|
||
avail_size = MAX (height, min_height);
|
||
if (avail_size <= 0)
|
||
goto out;
|
||
|
||
get_max_item_size (box, GTK_ORIENTATION_VERTICAL, &min_item_height, &nat_item_height);
|
||
|
||
/* By default flow at the natural item width */
|
||
line_length = avail_size / (nat_item_height + priv->row_spacing);
|
||
|
||
/* After the above aproximation, check if we cant fit one more on the line */
|
||
if (line_length * priv->row_spacing + (line_length + 1) * nat_item_height <= avail_size)
|
||
line_length++;
|
||
|
||
/* 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)
|
||
{
|
||
gint min_item_width, nat_item_width;
|
||
gint lines;
|
||
|
||
/* Here we just use the largest height-for-width and
|
||
* add up the size accordingly
|
||
*/
|
||
get_largest_size_for_opposing_orientation (box,
|
||
GTK_ORIENTATION_VERTICAL,
|
||
item_size,
|
||
&min_item_width,
|
||
&nat_item_width);
|
||
|
||
/* Round up how many lines we need to allocate for */
|
||
n_children = get_visible_children (box);
|
||
lines = n_children / line_length;
|
||
if ((n_children % line_length) > 0)
|
||
lines++;
|
||
|
||
min_width = min_item_width * lines;
|
||
nat_width = nat_item_width * lines;
|
||
|
||
min_width += (lines - 1) * priv->column_spacing;
|
||
nat_width += (lines - 1) * priv->column_spacing;
|
||
}
|
||
else
|
||
{
|
||
gint min_line_width, nat_line_width, i;
|
||
gboolean first_line = TRUE;
|
||
GtkRequestedSize *item_sizes;
|
||
GSequenceIter *iter;
|
||
|
||
/* First get the size each set of items take to span the line
|
||
* when aligning the items above and below after flowping.
|
||
*/
|
||
item_sizes = fit_aligned_item_requests (box,
|
||
priv->orientation,
|
||
avail_size,
|
||
priv->row_spacing,
|
||
&line_length,
|
||
priv->max_children_per_line,
|
||
n_children);
|
||
|
||
/* Get the available remaining size */
|
||
avail_size -= (line_length - 1) * priv->column_spacing;
|
||
for (i = 0; i < line_length; i++)
|
||
avail_size -= item_sizes[i].minimum_size;
|
||
|
||
if (avail_size > 0)
|
||
extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes);
|
||
|
||
for (iter = g_sequence_get_begin_iter (priv->children);
|
||
!g_sequence_iter_is_end (iter);)
|
||
{
|
||
iter = get_largest_size_for_line_in_opposing_orientation (box,
|
||
GTK_ORIENTATION_VERTICAL,
|
||
iter,
|
||
line_length,
|
||
item_sizes,
|
||
extra_pixels,
|
||
&min_line_width,
|
||
&nat_line_width);
|
||
|
||
/* Its possible the last line only had invisible widgets */
|
||
if (nat_line_width > 0)
|
||
{
|
||
if (first_line)
|
||
first_line = FALSE;
|
||
else
|
||
{
|
||
min_width += priv->column_spacing;
|
||
nat_width += priv->column_spacing;
|
||
}
|
||
|
||
min_width += min_line_width;
|
||
nat_width += nat_line_width;
|
||
}
|
||
}
|
||
g_free (item_sizes);
|
||
}
|
||
}
|
||
|
||
out:
|
||
if (minimum_width)
|
||
*minimum_width = min_width;
|
||
|
||
if (natural_width)
|
||
*natural_width = nat_width;
|
||
}
|
||
|
||
/* Drawing {{{3 */
|
||
|
||
static gboolean
|
||
gtk_flow_box_draw (GtkWidget *widget,
|
||
cairo_t *cr)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
GtkAllocation allocation = { 0, };
|
||
GtkStyleContext* context;
|
||
|
||
gtk_widget_get_allocation (GTK_WIDGET (box), &allocation);
|
||
context = gtk_widget_get_style_context (GTK_WIDGET (box));
|
||
gtk_render_background (context, cr, 0, 0, allocation.width, allocation.height);
|
||
|
||
GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->draw (widget, cr);
|
||
|
||
if (priv->rubberband_first && priv->rubberband_last)
|
||
{
|
||
GSequenceIter *iter, *iter1, *iter2;
|
||
GdkRectangle line_rect, rect;
|
||
GArray *lines;
|
||
gboolean vertical;
|
||
|
||
vertical = priv->orientation == GTK_ORIENTATION_VERTICAL;
|
||
|
||
cairo_save (cr);
|
||
|
||
context = gtk_widget_get_style_context (widget);
|
||
gtk_style_context_save (context);
|
||
gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND);
|
||
|
||
iter1 = CHILD_PRIV (priv->rubberband_first)->iter;
|
||
iter2 = CHILD_PRIV (priv->rubberband_last)->iter;
|
||
|
||
if (g_sequence_iter_compare (iter2, iter1) < 0)
|
||
{
|
||
iter = iter1;
|
||
iter1 = iter2;
|
||
iter2 = iter;
|
||
}
|
||
|
||
line_rect.width = 0;
|
||
lines = g_array_new (FALSE, FALSE, sizeof (GdkRectangle));
|
||
|
||
for (iter = iter1;
|
||
!g_sequence_iter_is_end (iter);
|
||
iter = g_sequence_iter_next (iter))
|
||
{
|
||
GtkWidget *child;
|
||
|
||
child = g_sequence_get (iter);
|
||
gtk_widget_get_allocation (GTK_WIDGET (child), &rect);
|
||
if (line_rect.width == 0)
|
||
line_rect = rect;
|
||
else
|
||
{
|
||
if ((vertical && rect.x == line_rect.x) ||
|
||
(!vertical && rect.y == line_rect.y))
|
||
gdk_rectangle_union (&rect, &line_rect, &line_rect);
|
||
else
|
||
{
|
||
g_array_append_val (lines, line_rect);
|
||
line_rect = rect;
|
||
}
|
||
}
|
||
|
||
if (g_sequence_iter_compare (iter, iter2) == 0)
|
||
break;
|
||
}
|
||
|
||
if (line_rect.width != 0)
|
||
g_array_append_val (lines, line_rect);
|
||
|
||
if (lines->len > 0)
|
||
{
|
||
GtkStateFlags state;
|
||
cairo_path_t *path;
|
||
GtkBorder border;
|
||
GdkRGBA border_color;
|
||
|
||
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);
|
||
gtk_widget_get_allocation (widget, &allocation);
|
||
gtk_render_background (context, cr,
|
||
0, 0,
|
||
allocation.width, allocation.height);
|
||
cairo_restore (cr);
|
||
|
||
cairo_append_path (cr, path);
|
||
cairo_path_destroy (path);
|
||
|
||
state = gtk_widget_get_state_flags (widget);
|
||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||
gtk_style_context_get_border_color (context, state, &border_color);
|
||
G_GNUC_END_IGNORE_DEPRECATIONS
|
||
gtk_style_context_get_border (context, state, &border);
|
||
|
||
cairo_set_line_width (cr, border.left);
|
||
gdk_cairo_set_source_rgba (cr, &border_color);
|
||
cairo_stroke (cr);
|
||
}
|
||
g_array_free (lines, TRUE);
|
||
|
||
gtk_style_context_restore (context);
|
||
cairo_restore (cr);
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Autoscrolling {{{3 */
|
||
|
||
static void
|
||
remove_autoscroll (GtkFlowBox *box)
|
||
{
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
|
||
if (priv->autoscroll_id)
|
||
{
|
||
gtk_widget_remove_tick_callback (GTK_WIDGET (box), priv->autoscroll_id);
|
||
priv->autoscroll_id = 0;
|
||
}
|
||
|
||
priv->autoscroll_mode = GTK_SCROLL_NONE;
|
||
}
|
||
|
||
static gboolean
|
||
autoscroll_cb (GtkWidget *widget,
|
||
GdkFrameClock *frame_clock,
|
||
gpointer data)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (data);
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
GtkAdjustment *adjustment;
|
||
gdouble factor;
|
||
gdouble increment;
|
||
gdouble value;
|
||
|
||
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
|
||
adjustment = priv->vadjustment;
|
||
else
|
||
adjustment = priv->hadjustment;
|
||
|
||
switch (priv->autoscroll_mode)
|
||
{
|
||
case GTK_SCROLL_STEP_FORWARD:
|
||
factor = AUTOSCROLL_FACTOR;
|
||
break;
|
||
case GTK_SCROLL_STEP_BACKWARD:
|
||
factor = - AUTOSCROLL_FACTOR;
|
||
break;
|
||
case GTK_SCROLL_PAGE_FORWARD:
|
||
factor = AUTOSCROLL_FACTOR_FAST;
|
||
break;
|
||
case GTK_SCROLL_PAGE_BACKWARD:
|
||
factor = - AUTOSCROLL_FACTOR_FAST;
|
||
break;
|
||
default:
|
||
g_assert_not_reached ();
|
||
}
|
||
|
||
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;
|
||
gdouble 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_find_child_at_pos (box, x, y);
|
||
|
||
gtk_flow_box_update_prelight (box, child);
|
||
gtk_flow_box_update_active (box, child);
|
||
|
||
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;
|
||
GdkWindow *view;
|
||
|
||
parent = gtk_widget_get_parent (GTK_WIDGET (box));
|
||
if (GTK_IS_VIEWPORT (parent))
|
||
{
|
||
view = gtk_viewport_get_view_window (GTK_VIEWPORT (parent));
|
||
rect->x = rect->y = 0;
|
||
|
||
rect->x = gtk_adjustment_get_value (priv->hadjustment);
|
||
rect->y = gtk_adjustment_get_value (priv->vadjustment);
|
||
rect->width = gdk_window_get_width (view);
|
||
rect->height = gdk_window_get_height (view);
|
||
return TRUE;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static void
|
||
update_autoscroll_mode (GtkFlowBox *box,
|
||
gint x,
|
||
gint y)
|
||
{
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
GtkScrollType mode = GTK_SCROLL_NONE;
|
||
GdkRectangle rect;
|
||
gint size, pos;
|
||
|
||
if (priv->rubberband_select && get_view_rect (box, &rect))
|
||
{
|
||
if (priv->orientation == GTK_ORIENTATION_VERTICAL)
|
||
{
|
||
size = rect.width;
|
||
pos = x - rect.x;
|
||
}
|
||
else
|
||
{
|
||
size = rect.height;
|
||
pos = y - rect.y;
|
||
}
|
||
|
||
if (pos < 0 - AUTOSCROLL_FAST_DISTANCE)
|
||
mode = GTK_SCROLL_PAGE_BACKWARD;
|
||
else if (pos > size + AUTOSCROLL_FAST_DISTANCE)
|
||
mode = GTK_SCROLL_PAGE_FORWARD;
|
||
else if (pos < 0)
|
||
mode = GTK_SCROLL_STEP_BACKWARD;
|
||
else if (pos > size)
|
||
mode = GTK_SCROLL_STEP_FORWARD;
|
||
}
|
||
|
||
if (mode != priv->autoscroll_mode)
|
||
{
|
||
remove_autoscroll (box);
|
||
priv->autoscroll_mode = mode;
|
||
add_autoscroll (box);
|
||
}
|
||
}
|
||
|
||
/* Event handling {{{3 */
|
||
|
||
static gboolean
|
||
gtk_flow_box_enter_notify_event (GtkWidget *widget,
|
||
GdkEventCrossing *event)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
||
GtkFlowBoxChild *child;
|
||
|
||
if (event->window != gtk_widget_get_window (GTK_WIDGET (box)))
|
||
return FALSE;
|
||
|
||
child = gtk_flow_box_find_child_at_pos (box, event->x, event->y);
|
||
gtk_flow_box_update_prelight (box, child);
|
||
gtk_flow_box_update_active (box, child);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static gboolean
|
||
gtk_flow_box_leave_notify_event (GtkWidget *widget,
|
||
GdkEventCrossing *event)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
||
GtkFlowBoxChild *child = NULL;
|
||
|
||
if (event->window != gtk_widget_get_window (GTK_WIDGET (box)))
|
||
return FALSE;
|
||
|
||
if (event->detail != GDK_NOTIFY_INFERIOR)
|
||
child = NULL;
|
||
else
|
||
child = gtk_flow_box_find_child_at_pos (box, event->x, event->y);
|
||
|
||
gtk_flow_box_update_prelight (box, child);
|
||
gtk_flow_box_update_active (box, child);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_drag_gesture_update (GtkGestureDrag *gesture,
|
||
gdouble offset_x,
|
||
gdouble offset_y,
|
||
GtkFlowBox *box)
|
||
{
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
gdouble start_x, start_y;
|
||
GtkFlowBoxChild *child;
|
||
|
||
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_find_child_at_pos (box, start_x, start_y);
|
||
|
||
/* Grab focus here, so Escape-to-stop-rubberband works */
|
||
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_find_child_at_pos (box, start_x + offset_x,
|
||
start_y + offset_y);
|
||
|
||
if (priv->rubberband_first == NULL)
|
||
priv->rubberband_first = child;
|
||
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 gboolean
|
||
gtk_flow_box_motion_notify_event (GtkWidget *widget,
|
||
GdkEventMotion *event)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
||
GtkFlowBoxChild *child;
|
||
GdkWindow *window;
|
||
GdkWindow *event_window;
|
||
gint relative_x;
|
||
gint relative_y;
|
||
gdouble parent_x;
|
||
gdouble parent_y;
|
||
|
||
window = gtk_widget_get_window (GTK_WIDGET (box));
|
||
event_window = event->window;
|
||
relative_x = event->x;
|
||
relative_y = event->y;
|
||
|
||
while ((event_window != NULL) && (event_window != window))
|
||
{
|
||
gdk_window_coords_to_parent (event_window,
|
||
relative_x, relative_y,
|
||
&parent_x, &parent_y);
|
||
relative_x = parent_x;
|
||
relative_y = parent_y;
|
||
event_window = gdk_window_get_effective_parent (event_window);
|
||
}
|
||
|
||
child = gtk_flow_box_find_child_at_pos (box, relative_x, relative_y);
|
||
gtk_flow_box_update_prelight (box, child);
|
||
gtk_flow_box_update_active (box, child);
|
||
|
||
return GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->motion_notify_event (widget, event);
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_multipress_gesture_pressed (GtkGestureMultiPress *gesture,
|
||
guint n_press,
|
||
gdouble x,
|
||
gdouble y,
|
||
GtkFlowBox *box)
|
||
{
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
GtkFlowBoxChild *child;
|
||
|
||
child = gtk_flow_box_find_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);
|
||
|
||
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
|
||
priv->active_child = child;
|
||
priv->active_child_active = TRUE;
|
||
gtk_widget_queue_draw (GTK_WIDGET (box));
|
||
|
||
if (n_press == 2 && !priv->activate_on_single_click)
|
||
g_signal_emit (box, signals[CHILD_ACTIVATED], 0, child);
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_multipress_gesture_released (GtkGestureMultiPress *gesture,
|
||
guint n_press,
|
||
gdouble x,
|
||
gdouble y,
|
||
GtkFlowBox *box)
|
||
{
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
|
||
if (priv->active_child != NULL && priv->active_child_active)
|
||
{
|
||
if (priv->activate_on_single_click)
|
||
gtk_flow_box_select_and_activate (box, priv->active_child);
|
||
else
|
||
{
|
||
GdkEventSequence *sequence;
|
||
GdkInputSource source;
|
||
const GdkEvent *event;
|
||
gboolean modify;
|
||
gboolean extend;
|
||
|
||
get_current_selection_modifiers (GTK_WIDGET (box), &modify, &extend);
|
||
|
||
/* With touch, we default to modifying the selection.
|
||
* You can still clear the selection and start over
|
||
* by holding Ctrl.
|
||
*/
|
||
|
||
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
|
||
event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
|
||
source = gdk_device_get_source (gdk_event_get_source_device (event));
|
||
|
||
if (source == GDK_SOURCE_TOUCHSCREEN)
|
||
modify = !modify;
|
||
|
||
gtk_flow_box_update_selection (box, priv->active_child, modify, extend);
|
||
}
|
||
}
|
||
|
||
priv->active_child = NULL;
|
||
priv->active_child_active = FALSE;
|
||
gtk_widget_queue_draw (GTK_WIDGET (box));
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_drag_gesture_begin (GtkGestureDrag *gesture,
|
||
gdouble start_x,
|
||
gdouble start_y,
|
||
GtkWidget *widget)
|
||
{
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (widget);
|
||
|
||
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;
|
||
get_current_selection_modifiers (widget, &priv->rubberband_modify, &priv->rubberband_extend);
|
||
}
|
||
|
||
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;
|
||
|
||
remove_autoscroll (box);
|
||
|
||
gtk_widget_queue_draw (GTK_WIDGET (box));
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_drag_gesture_end (GtkGestureDrag *gesture,
|
||
gdouble offset_x,
|
||
gdouble 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);
|
||
|
||
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_press_event (GtkWidget *widget,
|
||
GdkEventKey *event)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
|
||
if (priv->rubberband_select)
|
||
{
|
||
if (event->keyval == GDK_KEY_Escape)
|
||
{
|
||
gtk_flow_box_stop_rubberband (box);
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
return GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->key_press_event (widget, event);
|
||
}
|
||
|
||
/* Realize and map {{{3 */
|
||
|
||
static void
|
||
gtk_flow_box_realize (GtkWidget *widget)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
||
GtkAllocation allocation;
|
||
GdkWindowAttr attributes = {0};
|
||
GdkWindow *window;
|
||
|
||
gtk_widget_get_allocation (GTK_WIDGET (box), &allocation);
|
||
gtk_widget_set_realized (GTK_WIDGET (box), TRUE);
|
||
|
||
attributes.x = allocation.x;
|
||
attributes.y = allocation.y;
|
||
attributes.width = allocation.width;
|
||
attributes.height = allocation.height;
|
||
attributes.window_type = GDK_WINDOW_CHILD;
|
||
attributes.event_mask = gtk_widget_get_events (GTK_WIDGET (box))
|
||
| GDK_ENTER_NOTIFY_MASK
|
||
| GDK_LEAVE_NOTIFY_MASK
|
||
| GDK_POINTER_MOTION_MASK
|
||
| GDK_EXPOSURE_MASK
|
||
| GDK_KEY_PRESS_MASK
|
||
| GDK_BUTTON_PRESS_MASK
|
||
| GDK_BUTTON_RELEASE_MASK;
|
||
attributes.wclass = GDK_INPUT_OUTPUT;
|
||
|
||
window = gdk_window_new (gtk_widget_get_parent_window (GTK_WIDGET (box)),
|
||
&attributes, GDK_WA_X | GDK_WA_Y);
|
||
gtk_widget_register_window (GTK_WIDGET (box), window);
|
||
gtk_widget_set_window (GTK_WIDGET (box), window);
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_unmap (GtkWidget *widget)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
||
|
||
remove_autoscroll (box);
|
||
|
||
GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->unmap (widget);
|
||
}
|
||
|
||
/* GtkContainer implementation {{{2 */
|
||
|
||
static void
|
||
gtk_flow_box_add (GtkContainer *container,
|
||
GtkWidget *child)
|
||
{
|
||
gtk_flow_box_insert (GTK_FLOW_BOX (container), child, -1);
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_remove (GtkContainer *container,
|
||
GtkWidget *widget)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (container);
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
gboolean was_visible;
|
||
gboolean was_selected;
|
||
GtkFlowBoxChild *child;
|
||
|
||
if (GTK_IS_FLOW_BOX_CHILD (widget))
|
||
child = GTK_FLOW_BOX_CHILD (widget);
|
||
else
|
||
{
|
||
child = (GtkFlowBoxChild*)gtk_widget_get_parent (widget);
|
||
if (!GTK_IS_FLOW_BOX_CHILD (child))
|
||
{
|
||
g_warning ("Tried to remove non-child %p\n", widget);
|
||
return;
|
||
}
|
||
}
|
||
|
||
was_visible = child_is_visible (GTK_WIDGET (child));
|
||
was_selected = CHILD_PRIV (child)->selected;
|
||
|
||
if (child == priv->prelight_child)
|
||
priv->prelight_child = NULL;
|
||
if (child == priv->active_child)
|
||
priv->active_child = NULL;
|
||
if (child == priv->selected_child)
|
||
priv->selected_child = NULL;
|
||
|
||
gtk_widget_unparent (GTK_WIDGET (child));
|
||
g_sequence_remove (CHILD_PRIV (child)->iter);
|
||
|
||
if (was_visible && gtk_widget_get_visible (GTK_WIDGET (box)))
|
||
gtk_widget_queue_resize (GTK_WIDGET (box));
|
||
|
||
if (was_selected)
|
||
g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_forall (GtkContainer *container,
|
||
gboolean include_internals,
|
||
GtkCallback callback,
|
||
gpointer callback_target)
|
||
{
|
||
GSequenceIter *iter;
|
||
GtkWidget *child;
|
||
|
||
iter = g_sequence_get_begin_iter (BOX_PRIV (container)->children);
|
||
while (!g_sequence_iter_is_end (iter))
|
||
{
|
||
child = g_sequence_get (iter);
|
||
iter = g_sequence_iter_next (iter);
|
||
callback (child, callback_target);
|
||
}
|
||
}
|
||
|
||
static GType
|
||
gtk_flow_box_child_type (GtkContainer *container)
|
||
{
|
||
return GTK_TYPE_FLOW_BOX_CHILD;
|
||
}
|
||
|
||
/* Keynav {{{2 */
|
||
|
||
static gboolean
|
||
gtk_flow_box_focus (GtkWidget *widget,
|
||
GtkDirectionType direction)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (widget);
|
||
GtkWidget *focus_child;
|
||
GSequenceIter *iter;
|
||
GtkFlowBoxChild *next_focus_child;
|
||
|
||
focus_child = gtk_container_get_focus_child (GTK_CONTAINER (box));
|
||
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 (GtkBindingSet *binding_set,
|
||
guint keyval,
|
||
GdkModifierType modmask,
|
||
GtkMovementStep step,
|
||
gint count)
|
||
{
|
||
GdkDisplay *display;
|
||
GdkModifierType extend_mod_mask = GDK_SHIFT_MASK;
|
||
GdkModifierType modify_mod_mask = GDK_CONTROL_MASK;
|
||
|
||
display = gdk_display_get_default ();
|
||
if (display)
|
||
{
|
||
extend_mod_mask = gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display),
|
||
GDK_MODIFIER_INTENT_EXTEND_SELECTION);
|
||
modify_mod_mask = gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display),
|
||
GDK_MODIFIER_INTENT_MODIFY_SELECTION);
|
||
}
|
||
|
||
gtk_binding_entry_add_signal (binding_set, keyval, modmask,
|
||
"move-cursor", 2,
|
||
GTK_TYPE_MOVEMENT_STEP, step,
|
||
G_TYPE_INT, count,
|
||
NULL);
|
||
gtk_binding_entry_add_signal (binding_set, keyval, modmask | extend_mod_mask,
|
||
"move-cursor", 2,
|
||
GTK_TYPE_MOVEMENT_STEP, step,
|
||
G_TYPE_INT, count,
|
||
NULL);
|
||
gtk_binding_entry_add_signal (binding_set, keyval, modmask | modify_mod_mask,
|
||
"move-cursor", 2,
|
||
GTK_TYPE_MOVEMENT_STEP, step,
|
||
G_TYPE_INT, count,
|
||
NULL);
|
||
gtk_binding_entry_add_signal (binding_set, keyval, modmask | extend_mod_mask | modify_mod_mask,
|
||
"move-cursor", 2,
|
||
GTK_TYPE_MOVEMENT_STEP, step,
|
||
G_TYPE_INT, count,
|
||
NULL);
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_move_cursor (GtkFlowBox *box,
|
||
GtkMovementStep step,
|
||
gint count)
|
||
{
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
gboolean modify;
|
||
gboolean extend;
|
||
GtkFlowBoxChild *child;
|
||
GtkFlowBoxChild *prev;
|
||
GtkFlowBoxChild *next;
|
||
GtkAllocation allocation;
|
||
gint page_size;
|
||
GSequenceIter *iter;
|
||
gint start;
|
||
GtkAdjustment *adjustment;
|
||
gboolean vertical;
|
||
|
||
vertical = priv->orientation == GTK_ORIENTATION_VERTICAL;
|
||
|
||
if (vertical)
|
||
{
|
||
switch (step)
|
||
{
|
||
case GTK_MOVEMENT_VISUAL_POSITIONS:
|
||
step = GTK_MOVEMENT_DISPLAY_LINES;
|
||
break;
|
||
case GTK_MOVEMENT_DISPLAY_LINES:
|
||
step = GTK_MOVEMENT_VISUAL_POSITIONS;
|
||
break;
|
||
default: ;
|
||
}
|
||
}
|
||
|
||
child = NULL;
|
||
switch (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;
|
||
gtk_widget_get_allocation (GTK_WIDGET (child), &allocation);
|
||
start = vertical ? allocation.x : allocation.y;
|
||
|
||
if (count < 0)
|
||
{
|
||
gint i = 0;
|
||
|
||
/* Up */
|
||
while (iter != NULL)
|
||
{
|
||
iter = gtk_flow_box_get_previous_focusable (box, iter);
|
||
if (iter == NULL)
|
||
break;
|
||
|
||
prev = g_sequence_get (iter);
|
||
|
||
/* go up an even number of rows */
|
||
if (i % priv->cur_children_per_line == 0)
|
||
{
|
||
gtk_widget_get_allocation (GTK_WIDGET (prev), &allocation);
|
||
if ((vertical ? allocation.x : allocation.y) < start - page_size)
|
||
break;
|
||
}
|
||
|
||
child = prev;
|
||
i++;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
gint i = 0;
|
||
|
||
/* Down */
|
||
while (!g_sequence_iter_is_end (iter))
|
||
{
|
||
iter = gtk_flow_box_get_next_focusable (box, iter);
|
||
if (iter == NULL || g_sequence_iter_is_end (iter))
|
||
break;
|
||
|
||
next = g_sequence_get (iter);
|
||
|
||
if (i % priv->cur_children_per_line == 0)
|
||
{
|
||
gtk_widget_get_allocation (GTK_WIDGET (next), &allocation);
|
||
if ((vertical ? allocation.x : allocation.y) > start + page_size)
|
||
break;
|
||
}
|
||
|
||
child = next;
|
||
i++;
|
||
}
|
||
}
|
||
gtk_widget_get_allocation (GTK_WIDGET (child), &allocation);
|
||
}
|
||
break;
|
||
|
||
default:
|
||
g_assert_not_reached ();
|
||
}
|
||
|
||
if (child == NULL || child == priv->cursor_child)
|
||
{
|
||
GtkDirectionType direction = count < 0 ? GTK_DIR_UP : GTK_DIR_DOWN;
|
||
|
||
if (!gtk_widget_keynav_failed (GTK_WIDGET (box), direction))
|
||
{
|
||
GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (box));
|
||
|
||
if (toplevel)
|
||
gtk_widget_child_focus (toplevel,
|
||
direction == GTK_DIR_UP ?
|
||
GTK_DIR_TAB_BACKWARD :
|
||
GTK_DIR_TAB_FORWARD);
|
||
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
get_current_selection_modifiers (GTK_WIDGET (box), &modify, &extend);
|
||
|
||
gtk_flow_box_update_cursor (box, child);
|
||
if (!modify)
|
||
gtk_flow_box_update_selection (box, child, FALSE, extend);
|
||
}
|
||
|
||
/* Selection {{{2 */
|
||
|
||
static void
|
||
gtk_flow_box_selected_children_changed (GtkFlowBox *box)
|
||
{
|
||
_gtk_flow_box_accessible_selection_changed (GTK_WIDGET (box));
|
||
}
|
||
|
||
/* GObject implementation {{{2 */
|
||
|
||
static void
|
||
gtk_flow_box_get_property (GObject *object,
|
||
guint prop_id,
|
||
GValue *value,
|
||
GParamSpec *pspec)
|
||
{
|
||
GtkFlowBox *box = GTK_FLOW_BOX (object);
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
|
||
switch (prop_id)
|
||
{
|
||
case PROP_ORIENTATION:
|
||
g_value_set_enum (value, priv->orientation);
|
||
break;
|
||
case PROP_HOMOGENEOUS:
|
||
g_value_set_boolean (value, priv->homogeneous);
|
||
break;
|
||
case PROP_COLUMN_SPACING:
|
||
g_value_set_uint (value, priv->column_spacing);
|
||
break;
|
||
case PROP_ROW_SPACING:
|
||
g_value_set_uint (value, priv->row_spacing);
|
||
break;
|
||
case PROP_MIN_CHILDREN_PER_LINE:
|
||
g_value_set_uint (value, priv->min_children_per_line);
|
||
break;
|
||
case PROP_MAX_CHILDREN_PER_LINE:
|
||
g_value_set_uint (value, priv->max_children_per_line);
|
||
break;
|
||
case PROP_SELECTION_MODE:
|
||
g_value_set_enum (value, priv->selection_mode);
|
||
break;
|
||
case PROP_ACTIVATE_ON_SINGLE_CLICK:
|
||
g_value_set_boolean (value, priv->activate_on_single_click);
|
||
break;
|
||
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:
|
||
if (priv->orientation != g_value_get_enum (value))
|
||
{
|
||
priv->orientation = g_value_get_enum (value);
|
||
_gtk_orientable_set_style_classes (GTK_ORIENTABLE (box));
|
||
/* 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;
|
||
default:
|
||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_finalize (GObject *obj)
|
||
{
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (obj);
|
||
|
||
if (priv->filter_destroy != NULL)
|
||
priv->filter_destroy (priv->filter_data);
|
||
if (priv->sort_destroy != NULL)
|
||
priv->sort_destroy (priv->sort_data);
|
||
|
||
g_sequence_free (priv->children);
|
||
g_clear_object (&priv->hadjustment);
|
||
g_clear_object (&priv->vadjustment);
|
||
|
||
g_object_unref (priv->drag_gesture);
|
||
g_object_unref (priv->multipress_gesture);
|
||
|
||
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)->finalize (obj);
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_class_init (GtkFlowBoxClass *class)
|
||
{
|
||
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
|
||
GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
|
||
GtkBindingSet *binding_set;
|
||
|
||
object_class->finalize = gtk_flow_box_finalize;
|
||
object_class->get_property = gtk_flow_box_get_property;
|
||
object_class->set_property = gtk_flow_box_set_property;
|
||
|
||
widget_class->enter_notify_event = gtk_flow_box_enter_notify_event;
|
||
widget_class->leave_notify_event = gtk_flow_box_leave_notify_event;
|
||
widget_class->motion_notify_event = gtk_flow_box_motion_notify_event;
|
||
widget_class->size_allocate = gtk_flow_box_size_allocate;
|
||
widget_class->realize = gtk_flow_box_realize;
|
||
widget_class->unmap = gtk_flow_box_unmap;
|
||
widget_class->focus = gtk_flow_box_focus;
|
||
widget_class->draw = gtk_flow_box_draw;
|
||
widget_class->key_press_event = gtk_flow_box_key_press_event;
|
||
widget_class->get_request_mode = gtk_flow_box_get_request_mode;
|
||
widget_class->get_preferred_width = gtk_flow_box_get_preferred_width;
|
||
widget_class->get_preferred_height = gtk_flow_box_get_preferred_height;
|
||
widget_class->get_preferred_height_for_width = gtk_flow_box_get_preferred_height_for_width;
|
||
widget_class->get_preferred_width_for_height = gtk_flow_box_get_preferred_width_for_height;
|
||
|
||
container_class->add = gtk_flow_box_add;
|
||
container_class->remove = gtk_flow_box_remove;
|
||
container_class->forall = gtk_flow_box_forall;
|
||
container_class->child_type = gtk_flow_box_child_type;
|
||
gtk_container_class_handle_border_width (container_class);
|
||
|
||
class->activate_cursor_child = gtk_flow_box_activate_cursor_child;
|
||
class->toggle_cursor_child = gtk_flow_box_toggle_cursor_child;
|
||
class->move_cursor = gtk_flow_box_move_cursor;
|
||
class->select_all = gtk_flow_box_select_all;
|
||
class->unselect_all = gtk_flow_box_unselect_all;
|
||
class->selected_children_changed = gtk_flow_box_selected_children_changed;
|
||
|
||
g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");
|
||
|
||
/**
|
||
* GtkFlowBox:selection-mode:
|
||
*
|
||
* The selection mode used by the flow box.
|
||
*/
|
||
props[PROP_SELECTION_MODE] =
|
||
g_param_spec_enum ("selection-mode",
|
||
P_("Selection mode"),
|
||
P_("The selection mode"),
|
||
GTK_TYPE_SELECTION_MODE,
|
||
GTK_SELECTION_SINGLE,
|
||
G_PARAM_READWRITE | 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",
|
||
P_("Activate on Single Click"),
|
||
P_("Activate row on a single click"),
|
||
TRUE,
|
||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
||
|
||
/**
|
||
* GtkFlowBox:homogeneous:
|
||
*
|
||
* Determines whether all children should be allocated the
|
||
* same size.
|
||
*/
|
||
props[PROP_HOMOGENEOUS] =
|
||
g_param_spec_boolean ("homogeneous",
|
||
P_("Homogeneous"),
|
||
P_("Whether the children should all be the same size"),
|
||
FALSE,
|
||
G_PARAM_READWRITE | 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",
|
||
P_("Minimum Children Per Line"),
|
||
P_("The minimum number of children to allocate "
|
||
"consecutively in the given orientation."),
|
||
0, G_MAXUINT, 0,
|
||
G_PARAM_READWRITE | 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",
|
||
P_("Maximum Children Per Line"),
|
||
P_("The maximum amount of children to request space for "
|
||
"consecutively in the given orientation."),
|
||
0, G_MAXUINT, DEFAULT_MAX_CHILDREN_PER_LINE,
|
||
G_PARAM_READWRITE | 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",
|
||
P_("Vertical spacing"),
|
||
P_("The amount of vertical space between two children"),
|
||
0, G_MAXUINT, 0,
|
||
G_PARAM_READWRITE | 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",
|
||
P_("Horizontal spacing"),
|
||
P_("The amount of horizontal space between two children"),
|
||
0, G_MAXUINT, 0,
|
||
G_PARAM_READWRITE | 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
|
||
*
|
||
* The ::child-activated signal is emitted when a child has been
|
||
* activated by the user.
|
||
*/
|
||
signals[CHILD_ACTIVATED] = g_signal_new ("child-activated",
|
||
GTK_TYPE_FLOW_BOX,
|
||
G_SIGNAL_RUN_LAST,
|
||
G_STRUCT_OFFSET (GtkFlowBoxClass, child_activated),
|
||
NULL, NULL,
|
||
g_cclosure_marshal_VOID__OBJECT,
|
||
G_TYPE_NONE, 1,
|
||
GTK_TYPE_FLOW_BOX_CHILD);
|
||
|
||
/**
|
||
* GtkFlowBox::selected-children-changed:
|
||
* @box: the #GtkFlowBox on wich the signal is emitted
|
||
*
|
||
* The ::selected-children-changed signal is emitted when the
|
||
* set of selected children changes.
|
||
*
|
||
* Use gtk_flow_box_selected_foreach() or
|
||
* gtk_flow_box_get_selected_children() to obtain the
|
||
* selected children.
|
||
*/
|
||
signals[SELECTED_CHILDREN_CHANGED] = g_signal_new ("selected-children-changed",
|
||
GTK_TYPE_FLOW_BOX,
|
||
G_SIGNAL_RUN_FIRST,
|
||
G_STRUCT_OFFSET (GtkFlowBoxClass, selected_children_changed),
|
||
NULL, NULL,
|
||
g_cclosure_marshal_VOID__VOID,
|
||
G_TYPE_NONE, 0);
|
||
|
||
/**
|
||
* GtkFlowBox::activate-cursor-child:
|
||
* @box: the #GtkFlowBox on which the signal is emitted
|
||
*
|
||
* The ::activate-cursor-child signal is a
|
||
* [keybinding signal][GtkBindingSignal]
|
||
* which gets emitted when the user activates the @box.
|
||
*/
|
||
signals[ACTIVATE_CURSOR_CHILD] = g_signal_new ("activate-cursor-child",
|
||
GTK_TYPE_FLOW_BOX,
|
||
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
||
G_STRUCT_OFFSET (GtkFlowBoxClass, activate_cursor_child),
|
||
NULL, NULL,
|
||
g_cclosure_marshal_VOID__VOID,
|
||
G_TYPE_NONE, 0);
|
||
|
||
/**
|
||
* GtkFlowBox::toggle-cursor-child:
|
||
* @box: the #GtkFlowBox on which the signal is emitted
|
||
*
|
||
* The ::toggle-cursor-child signal is a
|
||
* [keybinding signal][GtkBindingSignal]
|
||
* which toggles the selection of the child that has the focus.
|
||
*
|
||
* The default binding for this signal is Ctrl-Space.
|
||
*/
|
||
signals[TOGGLE_CURSOR_CHILD] = g_signal_new ("toggle-cursor-child",
|
||
GTK_TYPE_FLOW_BOX,
|
||
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
||
G_STRUCT_OFFSET (GtkFlowBoxClass, toggle_cursor_child),
|
||
NULL, NULL,
|
||
g_cclosure_marshal_VOID__VOID,
|
||
G_TYPE_NONE, 0);
|
||
|
||
/**
|
||
* GtkFlowBox::move-cursor:
|
||
* @box: the #GtkFlowBox on which the signal is emitted
|
||
* @step: the granularity fo the move, as a #GtkMovementStep
|
||
* @count: the number of @step units to move
|
||
*
|
||
* The ::move-cursor signal is a
|
||
* [keybinding signal][GtkBindingSignal]
|
||
* which gets emitted when the user initiates a cursor movement.
|
||
* If the cursor is not visible in @text_view, this signal causes
|
||
* the viewport to be moved instead.
|
||
*
|
||
* Applications should not connect to it, but may emit it with
|
||
* g_signal_emit_by_name() if they need to control the cursor
|
||
* programmatically.
|
||
*
|
||
* The default bindings for this signal come in two variants,
|
||
* the variant with the Shift modifier extends the selection,
|
||
* the variant without the Shift modifer does not.
|
||
* There are too many key combinations to list them all here.
|
||
* - Arrow keys move by individual children
|
||
* - Home/End keys move to the ends of the box
|
||
* - PageUp/PageDown keys move vertically by pages
|
||
*/
|
||
signals[MOVE_CURSOR] = g_signal_new ("move-cursor",
|
||
GTK_TYPE_FLOW_BOX,
|
||
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
||
G_STRUCT_OFFSET (GtkFlowBoxClass, move_cursor),
|
||
NULL, NULL,
|
||
_gtk_marshal_VOID__ENUM_INT,
|
||
G_TYPE_NONE, 2,
|
||
GTK_TYPE_MOVEMENT_STEP, G_TYPE_INT);
|
||
/**
|
||
* GtkFlowBox::select-all:
|
||
* @box: the #GtkFlowBox on which the signal is emitted
|
||
*
|
||
* The ::select-all signal is a
|
||
* [keybinding signal][GtkBindingSignal]
|
||
* which gets emitted to select all children of the box, if
|
||
* the selection mode permits it.
|
||
*
|
||
* The default bindings for this signal is Ctrl-a.
|
||
*/
|
||
signals[SELECT_ALL] = g_signal_new ("select-all",
|
||
GTK_TYPE_FLOW_BOX,
|
||
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
||
G_STRUCT_OFFSET (GtkFlowBoxClass, select_all),
|
||
NULL, NULL,
|
||
g_cclosure_marshal_VOID__VOID,
|
||
G_TYPE_NONE, 0);
|
||
|
||
/**
|
||
* GtkFlowBox::unselect-all:
|
||
* @box: the #GtkFlowBox on which the signal is emitted
|
||
*
|
||
* The ::unselect-all signal is a
|
||
* [keybinding signal][GtkBindingSignal]
|
||
* which gets emitted to unselect all children of the box, if
|
||
* the selection mode permits it.
|
||
*
|
||
* The default bindings for this signal is Ctrl-Shift-a.
|
||
*/
|
||
signals[UNSELECT_ALL] = g_signal_new ("unselect-all",
|
||
GTK_TYPE_FLOW_BOX,
|
||
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
||
G_STRUCT_OFFSET (GtkFlowBoxClass, unselect_all),
|
||
NULL, NULL,
|
||
g_cclosure_marshal_VOID__VOID,
|
||
G_TYPE_NONE, 0);
|
||
|
||
widget_class->activate_signal = signals[ACTIVATE_CURSOR_CHILD];
|
||
|
||
binding_set = gtk_binding_set_by_class (class);
|
||
gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Home, 0,
|
||
GTK_MOVEMENT_BUFFER_ENDS, -1);
|
||
gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Home, 0,
|
||
GTK_MOVEMENT_BUFFER_ENDS, -1);
|
||
gtk_flow_box_add_move_binding (binding_set, GDK_KEY_End, 0,
|
||
GTK_MOVEMENT_BUFFER_ENDS, 1);
|
||
gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_End, 0,
|
||
GTK_MOVEMENT_BUFFER_ENDS, 1);
|
||
gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Up, 0,
|
||
GTK_MOVEMENT_DISPLAY_LINES, -1);
|
||
gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Up, 0,
|
||
GTK_MOVEMENT_DISPLAY_LINES, -1);
|
||
gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Down, 0,
|
||
GTK_MOVEMENT_DISPLAY_LINES, 1);
|
||
gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Down, 0,
|
||
GTK_MOVEMENT_DISPLAY_LINES, 1);
|
||
gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Page_Up, 0,
|
||
GTK_MOVEMENT_PAGES, -1);
|
||
gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Page_Up, 0,
|
||
GTK_MOVEMENT_PAGES, -1);
|
||
gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Page_Down, 0,
|
||
GTK_MOVEMENT_PAGES, 1);
|
||
gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Page_Down, 0,
|
||
GTK_MOVEMENT_PAGES, 1);
|
||
|
||
gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Right, 0,
|
||
GTK_MOVEMENT_VISUAL_POSITIONS, 1);
|
||
gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Right, 0,
|
||
GTK_MOVEMENT_VISUAL_POSITIONS, 1);
|
||
gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Left, 0,
|
||
GTK_MOVEMENT_VISUAL_POSITIONS, -1);
|
||
gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Left, 0,
|
||
GTK_MOVEMENT_VISUAL_POSITIONS, -1);
|
||
|
||
gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, GDK_CONTROL_MASK,
|
||
"toggle-cursor-child", 0, NULL);
|
||
gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Space, GDK_CONTROL_MASK,
|
||
"toggle-cursor-child", 0, NULL);
|
||
|
||
gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_CONTROL_MASK,
|
||
"select-all", 0);
|
||
gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
|
||
"unselect-all", 0);
|
||
|
||
gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_FLOW_BOX_ACCESSIBLE);
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_init (GtkFlowBox *box)
|
||
{
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
|
||
gtk_widget_set_has_window (GTK_WIDGET (box), TRUE);
|
||
gtk_widget_set_redraw_on_allocate (GTK_WIDGET (box), TRUE);
|
||
|
||
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_orientable_set_style_classes (GTK_ORIENTABLE (box));
|
||
|
||
priv->children = g_sequence_new (NULL);
|
||
|
||
priv->multipress_gesture = gtk_gesture_multi_press_new (GTK_WIDGET (box));
|
||
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->multipress_gesture),
|
||
FALSE);
|
||
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->multipress_gesture),
|
||
GDK_BUTTON_PRIMARY);
|
||
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->multipress_gesture),
|
||
GTK_PHASE_BUBBLE);
|
||
g_signal_connect (priv->multipress_gesture, "pressed",
|
||
G_CALLBACK (gtk_flow_box_multipress_gesture_pressed), box);
|
||
g_signal_connect (priv->multipress_gesture, "released",
|
||
G_CALLBACK (gtk_flow_box_multipress_gesture_released), box);
|
||
|
||
priv->drag_gesture = gtk_gesture_drag_new (GTK_WIDGET (box));
|
||
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);
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_bound_model_changed (GListModel *list,
|
||
guint position,
|
||
guint removed,
|
||
guint added,
|
||
gpointer user_data)
|
||
{
|
||
GtkFlowBox *box = user_data;
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
gint i;
|
||
|
||
while (removed--)
|
||
{
|
||
GtkFlowBoxChild *child;
|
||
|
||
child = gtk_flow_box_get_child_at_index (box, position);
|
||
gtk_widget_destroy (GTK_WIDGET (child));
|
||
}
|
||
|
||
for (i = 0; i < added; i++)
|
||
{
|
||
GObject *item;
|
||
GtkWidget *widget;
|
||
|
||
item = g_list_model_get_item (list, position + i);
|
||
widget = priv->create_widget_func (item, priv->create_widget_func_data);
|
||
|
||
/* 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_show (widget);
|
||
gtk_flow_box_insert (box, widget, position + i);
|
||
|
||
g_object_unref (widget);
|
||
g_object_unref (item);
|
||
}
|
||
}
|
||
|
||
/* Public API {{{2 */
|
||
|
||
/**
|
||
* gtk_flow_box_new:
|
||
*
|
||
* Creates a GtkFlowBox.
|
||
*
|
||
* Returns: a new #GtkFlowBox container
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
GtkWidget *
|
||
gtk_flow_box_new (void)
|
||
{
|
||
return (GtkWidget *)g_object_new (GTK_TYPE_FLOW_BOX, NULL);
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_insert_css_node (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);
|
||
gtk_css_node_insert_after (gtk_widget_get_css_node (GTK_WIDGET (box)),
|
||
gtk_widget_get_css_node (child),
|
||
gtk_widget_get_css_node (sibling));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* gtk_flow_box_insert:
|
||
* @box: a #GtkFlowBox
|
||
* @widget: the #GtkWidget to add
|
||
* @position: the position to insert @child in
|
||
*
|
||
* Inserts the @widget into @box at @position.
|
||
*
|
||
* If a sort function is set, the widget will actually be inserted
|
||
* at the calculated position and this function has the same effect
|
||
* as gtk_container_add().
|
||
*
|
||
* If @position is -1, or larger than the total number of children
|
||
* in the @box, then the @widget will be appended to the end.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
void
|
||
gtk_flow_box_insert (GtkFlowBox *box,
|
||
GtkWidget *widget,
|
||
gint position)
|
||
{
|
||
GtkFlowBoxPrivate *priv;
|
||
GtkFlowBoxChild *child;
|
||
GSequenceIter *iter;
|
||
|
||
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
||
g_return_if_fail (GTK_IS_WIDGET (widget));
|
||
|
||
priv = BOX_PRIV (box);
|
||
|
||
if (GTK_IS_FLOW_BOX_CHILD (widget))
|
||
child = GTK_FLOW_BOX_CHILD (widget);
|
||
else
|
||
{
|
||
child = GTK_FLOW_BOX_CHILD (gtk_flow_box_child_new ());
|
||
gtk_widget_show (GTK_WIDGET (child));
|
||
gtk_container_add (GTK_CONTAINER (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);
|
||
}
|
||
|
||
gtk_flow_box_insert_css_node (box, GTK_WIDGET (child), iter);
|
||
|
||
CHILD_PRIV (child)->iter = iter;
|
||
gtk_widget_set_parent (GTK_WIDGET (child), GTK_WIDGET (box));
|
||
gtk_flow_box_apply_filter (box, child);
|
||
}
|
||
|
||
/**
|
||
* gtk_flow_box_get_child_at_index:
|
||
* @box: a #GtkFlowBox
|
||
* @idx: the position of the child
|
||
*
|
||
* Gets the nth child in the @box.
|
||
*
|
||
* Returns: (transfer none): the child widget, which will
|
||
* always be a #GtkFlowBoxChild
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
GtkFlowBoxChild *
|
||
gtk_flow_box_get_child_at_index (GtkFlowBox *box,
|
||
gint idx)
|
||
{
|
||
GSequenceIter *iter;
|
||
|
||
g_return_val_if_fail (GTK_IS_FLOW_BOX (box), NULL);
|
||
|
||
iter = g_sequence_get_iter_at_pos (BOX_PRIV (box)->children, idx);
|
||
if (iter)
|
||
return g_sequence_get (iter);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
/**
|
||
* 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 gtk_scrolled_window_get_hadjustment()
|
||
* for a typical way of obtaining the adjustment, and
|
||
* gtk_flow_box_set_vadjustment()for setting the vertical
|
||
* adjustment.
|
||
*
|
||
* The adjustments have to be in pixel units and in the same
|
||
* coordinate system as the allocation for immediate children
|
||
* of the box.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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_container_set_focus_hadjustment (GTK_CONTAINER (box), 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 gtk_scrolled_window_get_vadjustment()
|
||
* for a typical way of obtaining the adjustment, and
|
||
* gtk_flow_box_set_hadjustment()for setting the horizontal
|
||
* adjustment.
|
||
*
|
||
* The adjustments have to be in pixel units and in the same
|
||
* coordinate system as the allocation for immediate children
|
||
* of the box.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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;
|
||
gtk_container_set_focus_vadjustment (GTK_CONTAINER (box), 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: (allow-none): the #GListModel to be bound to @box
|
||
* @create_widget_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
|
||
* gtk_flow_box_insert() or gtk_container_add()) 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.
|
||
*
|
||
* Since: 3.18
|
||
*/
|
||
void
|
||
gtk_flow_box_bind_model (GtkFlowBox *box,
|
||
GListModel *model,
|
||
GtkFlowBoxCreateWidgetFunc create_widget_func,
|
||
gpointer user_data,
|
||
GDestroyNotify user_data_free_func)
|
||
{
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
|
||
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
||
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
|
||
g_return_if_fail (model == NULL || create_widget_func != NULL);
|
||
|
||
if (priv->bound_model)
|
||
{
|
||
if (priv->create_widget_func_data_destroy)
|
||
priv->create_widget_func_data_destroy (priv->create_widget_func_data);
|
||
|
||
g_signal_handlers_disconnect_by_func (priv->bound_model, gtk_flow_box_bound_model_changed, box);
|
||
g_clear_object (&priv->bound_model);
|
||
}
|
||
|
||
gtk_flow_box_forall (GTK_CONTAINER (box), FALSE, (GtkCallback) gtk_widget_destroy, NULL);
|
||
|
||
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 (all children are the
|
||
* same size). See gtk_box_set_homogeneous().
|
||
*
|
||
* Returns: %TRUE if the box is homogeneous.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
gboolean
|
||
gtk_flow_box_get_homogeneous (GtkFlowBox *box)
|
||
{
|
||
g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
|
||
|
||
return BOX_PRIV (box)->homogeneous;
|
||
}
|
||
|
||
/**
|
||
* gtk_flow_box_set_homogeneous:
|
||
* @box: a #GtkFlowBox
|
||
* @homogeneous: %TRUE to create equal allotments,
|
||
* %FALSE for variable allotments
|
||
*
|
||
* Sets the #GtkFlowBox:homogeneous property of @box, controlling
|
||
* whether or not all children of @box are given equal space
|
||
* in the box.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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.
|
||
* See the #GtkFlowBox:row-spacing property.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
guint
|
||
gtk_flow_box_get_row_spacing (GtkFlowBox *box)
|
||
{
|
||
g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
|
||
|
||
return BOX_PRIV (box)->row_spacing;
|
||
}
|
||
|
||
/**
|
||
* gtk_flow_box_set_column_spacing:
|
||
* @box: a #GtkFlowBox
|
||
* @spacing: the spacing to use
|
||
*
|
||
* Sets the horizontal space to add between children.
|
||
* See the #GtkFlowBox:column-spacing property.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
void
|
||
gtk_flow_box_set_max_children_per_line (GtkFlowBox *box,
|
||
guint n_children)
|
||
{
|
||
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
||
|
||
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
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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;
|
||
}
|
||
|
||
/* 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.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
|
||
/**
|
||
* 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.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
void
|
||
gtk_flow_box_selected_foreach (GtkFlowBox *box,
|
||
GtkFlowBoxForeachFunc func,
|
||
gpointer data)
|
||
{
|
||
GtkFlowBoxChild *child;
|
||
GSequenceIter *iter;
|
||
|
||
g_return_if_fail (GTK_IS_FLOW_BOX (box));
|
||
|
||
for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
|
||
!g_sequence_iter_is_end (iter);
|
||
iter = g_sequence_iter_next (iter))
|
||
{
|
||
child = g_sequence_get (iter);
|
||
if (CHILD_PRIV (child)->selected)
|
||
(*func) (box, child, data);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* gtk_flow_box_set_selection_mode:
|
||
* @box: a #GtkFlowBox
|
||
* @mode: the new selection mode
|
||
*
|
||
* Sets how selection works in @box.
|
||
* See #GtkSelectionMode for details.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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;
|
||
|
||
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
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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 whenrever 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
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
|
||
/**
|
||
* gtk_flow_box_set_filter_func:
|
||
* @box: a #GtkFlowBox
|
||
* @filter_func: (closure user_data) (allow-none): 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
|
||
* gtk_flow_box_child_changed()) or when gtk_flow_box_invalidate_filter()
|
||
* is called.
|
||
*
|
||
* Note that using a filter function is incompatible with using a model
|
||
* (see gtk_flow_box_bind_model()).
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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 ot an external
|
||
* factor. For instance, this would be used if the
|
||
* filter function just looked for a specific search
|
||
* term, and the entry with the string has changed.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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
|
||
* the are equal, and > 0 otherwise
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
|
||
/**
|
||
* gtk_flow_box_set_sort_func:
|
||
* @box: a #GtkFlowBox
|
||
* @sort_func: (closure user_data) (allow-none): 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
|
||
* gtk_flow_box_child_changed()) and when gtk_flow_box_invalidate_sort()
|
||
* is called.
|
||
*
|
||
* + * Note that using a sort function is incompatible with using a model
|
||
* + * (see gtk_list_box_bind_model()).
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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 gint
|
||
gtk_flow_box_sort (GtkFlowBoxChild *a,
|
||
GtkFlowBoxChild *b,
|
||
GtkFlowBox *box)
|
||
{
|
||
GtkFlowBoxPrivate *priv = BOX_PRIV (box);
|
||
|
||
return priv->sort_func (a, b, priv->sort_data);
|
||
}
|
||
|
||
static void
|
||
gtk_flow_box_css_node_foreach (gpointer data,
|
||
gpointer user_data)
|
||
{
|
||
GtkWidget **previous = user_data;
|
||
GtkWidget *row = data;
|
||
GtkCssNode *row_node;
|
||
GtkCssNode *prev_node;
|
||
|
||
if (*previous)
|
||
{
|
||
prev_node = gtk_widget_get_css_node (*previous);
|
||
row_node = gtk_widget_get_css_node (row);
|
||
gtk_css_node_insert_after (gtk_css_node_get_parent (row_node),
|
||
row_node,
|
||
prev_node);
|
||
}
|
||
|
||
*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.
|
||
*
|
||
* Since: 3.12
|
||
*/
|
||
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_css_node_foreach, &previous);
|
||
gtk_widget_queue_resize (GTK_WIDGET (box));
|
||
}
|
||
}
|
||
|
||
/* vim:set foldmethod=marker expandtab: */
|