diff options
Diffstat (limited to 'src/interface-gtk/gtkflowbox.c')
-rw-r--r-- | src/interface-gtk/gtkflowbox.c | 4795 |
1 files changed, 0 insertions, 4795 deletions
diff --git a/src/interface-gtk/gtkflowbox.c b/src/interface-gtk/gtkflowbox.c deleted file mode 100644 index 1a5c2e9..0000000 --- a/src/interface-gtk/gtkflowbox.c +++ /dev/null @@ -1,4795 +0,0 @@ -/* - * 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. - */ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <gtk/gtk.h> - -#include "gtkflowbox.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 -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; - gint focus_pad; - - 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_style_context_get_style (context, - "focus-padding", &focus_pad, - NULL); - gtk_render_focus (context, cr, border.left + focus_pad, border.top + focus_pad, - allocation.width - 2 * focus_pad - border.left - border.right, - allocation.height - 2 * focus_pad - 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; - int focus_width, focus_pad; - - 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); - gtk_style_context_get_style (context, - "focus-line-width", &focus_width, - "focus-padding", &focus_pad, - NULL); - - full_border->left = padding.left + border.left + focus_width + focus_pad; - full_border->right = padding.right + border.right + focus_width + focus_pad; - full_border->top = padding.top + border.top + focus_width + focus_pad; - full_border->bottom = padding.bottom + border.bottom + focus_width + focus_pad; -} - -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); - - if (minimum_height) - *minimum_height = full_border.top + child_min + full_border.bottom; - if (natural_height) - *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); - - if (minimum_width) - *minimum_width = full_border.left + child_min + full_border.right; - if (natural_width) - *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); - - if (minimum_width) - *minimum_width = full_border.left + child_min + full_border.right; - if (natural_width) - *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); - - if (minimum_height) - *minimum_height = full_border.top + child_min + full_border.bottom; - if (natural_height) - *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 ("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_ORIENTATION, - PROP_HOMOGENEOUS, - PROP_HALIGN_POLICY, - PROP_VALIGN_POLICY, - PROP_COLUMN_SPACING, - PROP_ROW_SPACING, - PROP_MIN_CHILDREN_PER_LINE, - PROP_MAX_CHILDREN_PER_LINE, - PROP_SELECTION_MODE, - PROP_ACTIVATE_ON_SINGLE_CLICK -}; - -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; - - gboolean track_motion; - gboolean rubberband_select; - GtkFlowBoxChild *rubberband_first; - GtkFlowBoxChild *rubberband_last; - gint button_down_x; - gint button_down_y; - gboolean rubberband_modify; - gboolean rubberband_extend; - GdkDevice *rubberband_device; - - GtkScrollType autoscroll_mode; - guint autoscroll_id; -}; - -#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)); -} - -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; - - child_allocation.x = 0; - child_allocation.y = 0; - child_allocation.width = 0; - child_allocation.height = 0; - - 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); - - child_allocation.x = 0; - child_allocation.y = 0; - child_allocation.width = allocation->width; - - 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->x + allocation->width - (child_allocation.x - 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; - - GTK_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, &min_height, NULL); - 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; - - GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, NULL); - 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; - 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, NULL); - - 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; - 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, NULL); - - 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); - gtk_style_context_get_border_color (context, state, &border_color); - 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) - { - gint x, y; - GtkFlowBoxChild *child; - - gdk_window_get_device_position (gtk_widget_get_window (widget), - priv->rubberband_device, - &x, &y, NULL); - - 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), - (GtkTickCallback)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 = 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 gboolean -gtk_flow_box_motion_notify_event (GtkWidget *widget, - GdkEventMotion *event) -{ - GtkFlowBox *box = GTK_FLOW_BOX (widget); - GtkFlowBoxPrivate *priv = BOX_PRIV (box); - 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); - - if (priv->track_motion) - { - if (!priv->rubberband_select && - (event->x - priv->button_down_x) * (event->x - priv->button_down_x) + - (event->y - priv->button_down_y) * (event->y - priv->button_down_y) > RUBBERBAND_START_DISTANCE * RUBBERBAND_START_DISTANCE) - { - priv->rubberband_select = TRUE; - priv->rubberband_first = gtk_flow_box_find_child_at_pos (box, priv->button_down_x, priv->button_down_y); - - /* Grab focus here, so Escape-to-stop-rubberband works */ - gtk_flow_box_update_cursor (box, priv->rubberband_first); - } - - if (priv->rubberband_select) - { - if (priv->rubberband_first == NULL) - priv->rubberband_first = child; - if (child != NULL) - priv->rubberband_last = child; - - update_autoscroll_mode (box, event->x, event->y); - } - } - - return FALSE; -} - -static gboolean -gtk_flow_box_button_press_event (GtkWidget *widget, - GdkEventButton *event) -{ - GtkFlowBox *box = GTK_FLOW_BOX (widget); - GtkFlowBoxPrivate *priv = BOX_PRIV (box); - GtkFlowBoxChild *child; - - if (event->button == GDK_BUTTON_PRIMARY) - { - child = gtk_flow_box_find_child_at_pos (box, event->x, event->y); - if (child != NULL) - { - priv->active_child = child; - priv->active_child_active = TRUE; - gtk_widget_queue_draw (GTK_WIDGET (box)); - if (event->type == GDK_2BUTTON_PRESS && - !priv->activate_on_single_click) - { - g_signal_emit (box, signals[CHILD_ACTIVATED], 0, child); - return TRUE; - } - } - - if (priv->selection_mode == GTK_SELECTION_MULTIPLE) - { - priv->track_motion = TRUE; - priv->rubberband_select = FALSE; - priv->rubberband_first = NULL; - priv->rubberband_last = NULL; - priv->button_down_x = event->x; - priv->button_down_y = event->y; - priv->rubberband_device = gdk_event_get_device ((GdkEvent*)event); - get_current_selection_modifiers (widget, &priv->rubberband_modify, &priv->rubberband_extend); - } - } - - return FALSE; -} - -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; - priv->rubberband_device = NULL; - - remove_autoscroll (box); - - gtk_widget_queue_draw (GTK_WIDGET (box)); -} - -static gboolean -gtk_flow_box_button_release_event (GtkWidget *widget, - GdkEventButton *event) -{ - GtkFlowBox *box = GTK_FLOW_BOX (widget); - GtkFlowBoxPrivate *priv = BOX_PRIV (box); - - if (event->button == GDK_BUTTON_PRIMARY) - { - 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 - { - gboolean modify; - gboolean extend; - GdkDevice *device; - - get_current_selection_modifiers (widget, &modify, &extend); - - /* With touch, we default to modifying the selection. - * You can still clear the selection and start over - * by holding Ctrl. - */ - device = gdk_event_get_source_device ((GdkEvent *)event); - if (gdk_device_get_source (device) == 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)); - } - - priv->track_motion = FALSE; - if (priv->rubberband_select) - { - 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); - - } - - return FALSE; -} - -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); -} - -static void -gtk_flow_box_grab_notify (GtkWidget *widget, - gboolean was_grabbed) -{ - GtkFlowBox *box = GTK_FLOW_BOX (widget); - GtkFlowBoxPrivate *priv = BOX_PRIV (box); - - if (!was_grabbed) - { - if (priv->rubberband_select) - gtk_flow_box_stop_rubberband (box); - } -} - -/* 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); - gtk_style_context_set_background (gtk_widget_get_style_context (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 (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) -{ -} - -/* 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: - priv->orientation = g_value_get_enum (value); - /* Re-box the children in the new orientation */ - gtk_widget_queue_resize (GTK_WIDGET (box)); - 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_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->button_press_event = gtk_flow_box_button_press_event; - widget_class->button_release_event = gtk_flow_box_button_release_event; - widget_class->key_press_event = gtk_flow_box_key_press_event; - widget_class->grab_notify = gtk_flow_box_grab_notify; - 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. - */ - g_object_class_install_property (object_class, - PROP_SELECTION_MODE, - g_param_spec_enum ("selection-mode", - "Selection mode", - "The selection mode", - GTK_TYPE_SELECTION_MODE, - GTK_SELECTION_SINGLE, - G_PARAM_READWRITE)); - - /** - * GtkFlowBox:activate-on-single-click: - * - * Determines whether children can be activated with a single - * click, or require a double-click. - */ - g_object_class_install_property (object_class, - PROP_ACTIVATE_ON_SINGLE_CLICK, - g_param_spec_boolean ("activate-on-single-click", - "Activate on Single Click", - "Activate row on a single click", - TRUE, - G_PARAM_READWRITE)); - - /** - * GtkFlowBox:homogeneous: - * - * Determines whether all children should be allocated the - * same size. - */ - g_object_class_install_property (object_class, - PROP_HOMOGENEOUS, - g_param_spec_boolean ("homogeneous", - "Homogeneous", - "Whether the children should all be the same size", - FALSE, - G_PARAM_READWRITE)); - - /** - * 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. - */ - g_object_class_install_property (object_class, - PROP_MIN_CHILDREN_PER_LINE, - g_param_spec_uint ("min-children-per-line", - "Minimum Children Per Line", - "The minimum number of children to allocate " - "consecutively in the given orientation.", - 0, - G_MAXUINT, - 0, - G_PARAM_READWRITE)); - - /** - * GtkFlowBox:max-children-per-line: - * - * The maximum amount of children to request space for consecutively - * in the given orientation. - */ - g_object_class_install_property (object_class, - PROP_MAX_CHILDREN_PER_LINE, - g_param_spec_uint ("max-children-per-line", - "Maximum Children Per Line", - "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)); - - /** - * GtkFlowBox:row-spacing: - * - * The amount of vertical space between two children. - */ - g_object_class_install_property (object_class, - PROP_ROW_SPACING, - g_param_spec_uint ("row-spacing", - "Vertical spacing", - "The amount of vertical space between two children", - 0, - G_MAXUINT, - 0, - G_PARAM_READWRITE)); - - /** - * GtkFlowBox:column-spacing: - * - * The amount of horizontal space between two children. - */ - g_object_class_install_property (object_class, - PROP_COLUMN_SPACING, - g_param_spec_uint ("column-spacing", - "Horizontal spacing", - "The amount of horizontal space between two children", - 0, - G_MAXUINT, - 0, - G_PARAM_READWRITE)); - - /** - * 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, - NULL, - 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); -} - -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; - - priv->children = g_sequence_new (NULL); -} - - /* 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); -} - -/** - * 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); - } - - 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); -} - -/* 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 (G_OBJECT (box), "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 (G_OBJECT (box), "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 (G_OBJECT (box), "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 (G_OBJECT (box), "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 (G_OBJECT (box), "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 (G_OBJECT (box), "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 (G_OBJECT (box), "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. - * - * 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_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. - * - * 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_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); -} - -/** - * 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; - - 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); - gtk_widget_queue_resize (GTK_WIDGET (box)); - } -} - -/* vim:set foldmethod=marker expandtab: */ |