gtk2/gtk/gtklistbox.c
Florian Müllner dd85acbaf1 listbox: Fix keynav_failed() parameter in move_cursor()
The 'direction' parameter to gtk_widget_keynav_failed() is based on
gtk_list_box_move_cursor()'s 'count' parameter. However if the passed
in movement is GTK_MOVEMENT_DISPLAY_LINES, 'count' is modified by
the keynav handling and will always be 0. To avoid messing up the
'direction' parameter, use a local variable for keynav handling and
leave 'count' untouched.

https://bugzilla.gnome.org/show_bug.cgi?id=709687
2013-10-11 13:11:59 +02:00

2851 lines
91 KiB
C

/*
* Copyright (C) 2012 Alexander Larsson <alexl@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtklistbox.h"
#include "gtkwidget.h"
#include "gtkmarshalers.h"
#include "gtkprivate.h"
#include "gtkintl.h"
#include <float.h>
#include <math.h>
#include <string.h>
#include "a11y/gtklistboxaccessibleprivate.h"
#include "a11y/gtklistboxrowaccessible.h"
/**
* SECTION:gtklistbox
* @Short_description: A list container
* @Title: GtkListBox
* @See_also: #GtkScrolledWindow
*
* A GtkListBox is a vertical container that contains GtkListBoxRow
* children. These rows can by dynamically sorted and filtered, and
* headers can be added dynamically depending on the row content.
* It also allows keyboard and mouse navigation and selection like
* a typical list.
*
* Using GtkListBox is often an alternative to #GtkTreeView, especially
* when the list contents has a more complicated layout than what is allowed
* by a #GtkCellRenderer, or when the contents is interactive (i.e. has a
* button in it).
*
* Although a #GtkListBox must have only #GtkListBoxRow children you can
* add any kind of widget to it via gtk_container_add(), and a #GtkListBoxRow
* widget will automatically be inserted between the list and the widget.
*
* The GtkListBox widget was added in GTK+ 3.10.
*/
typedef struct
{
GSequence *children;
GHashTable *header_hash;
GtkWidget *placeholder;
GtkListBoxSortFunc sort_func;
gpointer sort_func_target;
GDestroyNotify sort_func_target_destroy_notify;
GtkListBoxFilterFunc filter_func;
gpointer filter_func_target;
GDestroyNotify filter_func_target_destroy_notify;
GtkListBoxUpdateHeaderFunc update_header_func;
gpointer update_header_func_target;
GDestroyNotify update_header_func_target_destroy_notify;
GtkListBoxRow *selected_row;
GtkListBoxRow *prelight_row;
GtkListBoxRow *cursor_row;
gboolean active_row_active;
GtkListBoxRow *active_row;
GtkSelectionMode selection_mode;
GtkAdjustment *adjustment;
gboolean activate_single_click;
/* DnD */
GtkListBoxRow *drag_highlighted_row;
int n_visible_rows;
gboolean in_widget;
} GtkListBoxPrivate;
typedef struct
{
GSequenceIter *iter;
GtkWidget *header;
gint y;
gint height;
gboolean visible;
} GtkListBoxRowPrivate;
enum {
ROW_SELECTED,
ROW_ACTIVATED,
ACTIVATE_CURSOR_ROW,
TOGGLE_CURSOR_ROW,
MOVE_CURSOR,
LAST_SIGNAL
};
enum {
ROW__ACTIVATE,
ROW__LAST_SIGNAL
};
enum {
PROP_0,
PROP_SELECTION_MODE,
PROP_ACTIVATE_ON_SINGLE_CLICK,
LAST_PROPERTY
};
G_DEFINE_TYPE_WITH_PRIVATE (GtkListBox, gtk_list_box, GTK_TYPE_CONTAINER)
G_DEFINE_TYPE_WITH_PRIVATE (GtkListBoxRow, gtk_list_box_row, GTK_TYPE_BIN)
static void gtk_list_box_update_selected (GtkListBox *list_box,
GtkListBoxRow *row);
static void gtk_list_box_apply_filter_all (GtkListBox *list_box);
static void gtk_list_box_update_header (GtkListBox *list_box,
GSequenceIter *iter);
static GSequenceIter * gtk_list_box_get_next_visible (GtkListBox *list_box,
GSequenceIter *_iter);
static void gtk_list_box_apply_filter (GtkListBox *list_box,
GtkListBoxRow *row);
static void gtk_list_box_add_move_binding (GtkBindingSet *binding_set,
guint keyval,
GdkModifierType modmask,
GtkMovementStep step,
gint count);
static void gtk_list_box_update_cursor (GtkListBox *list_box,
GtkListBoxRow *row);
static void gtk_list_box_select_and_activate (GtkListBox *list_box,
GtkListBoxRow *row);
static void gtk_list_box_update_prelight (GtkListBox *list_box,
GtkListBoxRow *row);
static void gtk_list_box_update_active (GtkListBox *list_box,
GtkListBoxRow *row);
static gboolean gtk_list_box_enter_notify_event (GtkWidget *widget,
GdkEventCrossing *event);
static gboolean gtk_list_box_leave_notify_event (GtkWidget *widget,
GdkEventCrossing *event);
static gboolean gtk_list_box_motion_notify_event (GtkWidget *widget,
GdkEventMotion *event);
static gboolean gtk_list_box_button_press_event (GtkWidget *widget,
GdkEventButton *event);
static gboolean gtk_list_box_button_release_event (GtkWidget *widget,
GdkEventButton *event);
static void gtk_list_box_show (GtkWidget *widget);
static gboolean gtk_list_box_focus (GtkWidget *widget,
GtkDirectionType direction);
static GSequenceIter* gtk_list_box_get_previous_visible (GtkListBox *list_box,
GSequenceIter *_iter);
static GtkListBoxRow *gtk_list_box_get_first_focusable (GtkListBox *list_box);
static GtkListBoxRow *gtk_list_box_get_last_focusable (GtkListBox *list_box);
static gboolean gtk_list_box_draw (GtkWidget *widget,
cairo_t *cr);
static void gtk_list_box_realize (GtkWidget *widget);
static void gtk_list_box_add (GtkContainer *container,
GtkWidget *widget);
static void gtk_list_box_remove (GtkContainer *container,
GtkWidget *widget);
static void gtk_list_box_forall_internal (GtkContainer *container,
gboolean include_internals,
GtkCallback callback,
gpointer callback_target);
static void gtk_list_box_compute_expand_internal (GtkWidget *widget,
gboolean *hexpand,
gboolean *vexpand);
static GType gtk_list_box_child_type (GtkContainer *container);
static GtkSizeRequestMode gtk_list_box_get_request_mode (GtkWidget *widget);
static void gtk_list_box_size_allocate (GtkWidget *widget,
GtkAllocation *allocation);
static void gtk_list_box_drag_leave (GtkWidget *widget,
GdkDragContext *context,
guint time_);
static void gtk_list_box_activate_cursor_row (GtkListBox *list_box);
static void gtk_list_box_toggle_cursor_row (GtkListBox *list_box);
static void gtk_list_box_move_cursor (GtkListBox *list_box,
GtkMovementStep step,
gint count);
static void gtk_list_box_finalize (GObject *obj);
static void gtk_list_box_parent_set (GtkWidget *widget,
GtkWidget *prev_parent);
static void gtk_list_box_get_preferred_height (GtkWidget *widget,
gint *minimum_height,
gint *natural_height);
static void gtk_list_box_get_preferred_height_for_width (GtkWidget *widget,
gint width,
gint *minimum_height,
gint *natural_height);
static void gtk_list_box_get_preferred_width (GtkWidget *widget,
gint *minimum_width,
gint *natural_width);
static void gtk_list_box_get_preferred_width_for_height (GtkWidget *widget,
gint height,
gint *minimum_width,
gint *natural_width);
static GParamSpec *properties[LAST_PROPERTY] = { NULL, };
static guint signals[LAST_SIGNAL] = { 0 };
static guint row_signals[ROW__LAST_SIGNAL] = { 0 };
/**
* gtk_list_box_new:
*
* Creates a new #GtkListBox container.
*
* Returns: a new #GtkListBox
*
* Since: 3.10
*/
GtkWidget *
gtk_list_box_new (void)
{
return g_object_new (GTK_TYPE_LIST_BOX, NULL);
}
static void
gtk_list_box_init (GtkListBox *list_box)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
GtkStyleContext *context;
gtk_widget_set_has_window (GTK_WIDGET (list_box), TRUE);
gtk_widget_set_redraw_on_allocate (GTK_WIDGET (list_box), TRUE);
priv->selection_mode = GTK_SELECTION_SINGLE;
priv->activate_single_click = TRUE;
priv->children = g_sequence_new (NULL);
priv->header_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
context = gtk_widget_get_style_context (GTK_WIDGET (list_box));
gtk_style_context_add_class (context, GTK_STYLE_CLASS_LIST);
}
static void
gtk_list_box_get_property (GObject *obj,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkListBox *list_box = GTK_LIST_BOX (obj);
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
switch (property_id)
{
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_single_click);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
break;
}
}
static void
gtk_list_box_set_property (GObject *obj,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkListBox *list_box = GTK_LIST_BOX (obj);
switch (property_id)
{
case PROP_SELECTION_MODE:
gtk_list_box_set_selection_mode (list_box, g_value_get_enum (value));
break;
case PROP_ACTIVATE_ON_SINGLE_CLICK:
gtk_list_box_set_activate_on_single_click (list_box, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
break;
}
}
static void
gtk_list_box_finalize (GObject *obj)
{
GtkListBox *list_box = GTK_LIST_BOX (obj);
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
if (priv->sort_func_target_destroy_notify != NULL)
priv->sort_func_target_destroy_notify (priv->sort_func_target);
if (priv->filter_func_target_destroy_notify != NULL)
priv->filter_func_target_destroy_notify (priv->filter_func_target);
if (priv->update_header_func_target_destroy_notify != NULL)
priv->update_header_func_target_destroy_notify (priv->update_header_func_target);
g_clear_object (&priv->adjustment);
g_clear_object (&priv->drag_highlighted_row);
g_sequence_free (priv->children);
g_hash_table_unref (priv->header_hash);
G_OBJECT_CLASS (gtk_list_box_parent_class)->finalize (obj);
}
static void
gtk_list_box_class_init (GtkListBoxClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
GtkBindingSet *binding_set;
gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_LIST_BOX_ACCESSIBLE);
object_class->get_property = gtk_list_box_get_property;
object_class->set_property = gtk_list_box_set_property;
object_class->finalize = gtk_list_box_finalize;
widget_class->enter_notify_event = gtk_list_box_enter_notify_event;
widget_class->leave_notify_event = gtk_list_box_leave_notify_event;
widget_class->motion_notify_event = gtk_list_box_motion_notify_event;
widget_class->button_press_event = gtk_list_box_button_press_event;
widget_class->button_release_event = gtk_list_box_button_release_event;
widget_class->show = gtk_list_box_show;
widget_class->focus = gtk_list_box_focus;
widget_class->draw = gtk_list_box_draw;
widget_class->realize = gtk_list_box_realize;
widget_class->compute_expand = gtk_list_box_compute_expand_internal;
widget_class->get_request_mode = gtk_list_box_get_request_mode;
widget_class->get_preferred_height = gtk_list_box_get_preferred_height;
widget_class->get_preferred_height_for_width = gtk_list_box_get_preferred_height_for_width;
widget_class->get_preferred_width = gtk_list_box_get_preferred_width;
widget_class->get_preferred_width_for_height = gtk_list_box_get_preferred_width_for_height;
widget_class->size_allocate = gtk_list_box_size_allocate;
widget_class->drag_leave = gtk_list_box_drag_leave;
widget_class->parent_set = gtk_list_box_parent_set;
container_class->add = gtk_list_box_add;
container_class->remove = gtk_list_box_remove;
container_class->forall = gtk_list_box_forall_internal;
container_class->child_type = gtk_list_box_child_type;
klass->activate_cursor_row = gtk_list_box_activate_cursor_row;
klass->toggle_cursor_row = gtk_list_box_toggle_cursor_row;
klass->move_cursor = gtk_list_box_move_cursor;
properties[PROP_SELECTION_MODE] =
g_param_spec_enum ("selection-mode",
P_("Selection mode"),
P_("The selection mode"),
GTK_TYPE_SELECTION_MODE,
GTK_SELECTION_SINGLE,
G_PARAM_READWRITE);
properties[PROP_ACTIVATE_ON_SINGLE_CLICK] =
g_param_spec_boolean ("activate-on-single-click",
P_("Activate on Single Click"),
P_("Activate row on a single click"),
TRUE,
G_PARAM_READWRITE);
g_object_class_install_properties (object_class, LAST_PROPERTY, properties);
/**
* GtkListBox::row-selected:
* @list_box: the #GtkListBox
* @row: the selected row
*
* The ::row-selected signal is emitted when a new row is selected, or
* (with a %NULL @row) when the selection is cleared.
*
* Since: 3.10
*/
signals[ROW_SELECTED] =
g_signal_new ("row-selected",
GTK_TYPE_LIST_BOX,
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkListBoxClass, row_selected),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
GTK_TYPE_LIST_BOX_ROW);
/**
* GtkListBox::row-activated:
* @list_box: the #GtkListBox
* @row: the activated row
*
* The ::row-activated signal is emitted when a row has been activated by
* the user.
*
* Since: 3.10
*/
signals[ROW_ACTIVATED] =
g_signal_new ("row-activated",
GTK_TYPE_LIST_BOX,
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkListBoxClass, row_activated),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
GTK_TYPE_LIST_BOX_ROW);
signals[ACTIVATE_CURSOR_ROW] =
g_signal_new ("activate-cursor-row",
GTK_TYPE_LIST_BOX,
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkListBoxClass, activate_cursor_row),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[TOGGLE_CURSOR_ROW] =
g_signal_new ("toggle-cursor-row",
GTK_TYPE_LIST_BOX,
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkListBoxClass, toggle_cursor_row),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[MOVE_CURSOR] =
g_signal_new ("move-cursor",
GTK_TYPE_LIST_BOX,
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkListBoxClass, move_cursor),
NULL, NULL,
_gtk_marshal_VOID__ENUM_INT,
G_TYPE_NONE, 2,
GTK_TYPE_MOVEMENT_STEP, G_TYPE_INT);
widget_class->activate_signal = signals[ACTIVATE_CURSOR_ROW];
binding_set = gtk_binding_set_by_class (klass);
gtk_list_box_add_move_binding (binding_set, GDK_KEY_Home, 0,
GTK_MOVEMENT_BUFFER_ENDS, -1);
gtk_list_box_add_move_binding (binding_set, GDK_KEY_KP_Home, 0,
GTK_MOVEMENT_BUFFER_ENDS, -1);
gtk_list_box_add_move_binding (binding_set, GDK_KEY_End, 0,
GTK_MOVEMENT_BUFFER_ENDS, 1);
gtk_list_box_add_move_binding (binding_set, GDK_KEY_KP_End, 0,
GTK_MOVEMENT_BUFFER_ENDS, 1);
gtk_list_box_add_move_binding (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK,
GTK_MOVEMENT_DISPLAY_LINES, -1);
gtk_list_box_add_move_binding (binding_set, GDK_KEY_KP_Up, GDK_CONTROL_MASK,
GTK_MOVEMENT_DISPLAY_LINES, -1);
gtk_list_box_add_move_binding (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK,
GTK_MOVEMENT_DISPLAY_LINES, 1);
gtk_list_box_add_move_binding (binding_set, GDK_KEY_KP_Down, GDK_CONTROL_MASK,
GTK_MOVEMENT_DISPLAY_LINES, 1);
gtk_list_box_add_move_binding (binding_set, GDK_KEY_Page_Up, 0,
GTK_MOVEMENT_PAGES, -1);
gtk_list_box_add_move_binding (binding_set, GDK_KEY_KP_Page_Up, 0,
GTK_MOVEMENT_PAGES, -1);
gtk_list_box_add_move_binding (binding_set, GDK_KEY_Page_Down, 0,
GTK_MOVEMENT_PAGES, 1);
gtk_list_box_add_move_binding (binding_set, GDK_KEY_KP_Page_Down, 0,
GTK_MOVEMENT_PAGES, 1);
gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, GDK_CONTROL_MASK,
"toggle-cursor-row", 0, NULL);
}
/**
* gtk_list_box_get_selected_row:
* @list_box: a #GtkListBox
*
* Gets the selected row.
*
* Return value: (transfer none): the selected #GtkWidget
*
* Since: 3.10
*/
GtkListBoxRow *
gtk_list_box_get_selected_row (GtkListBox *list_box)
{
GtkListBoxPrivate *priv;
g_return_val_if_fail (GTK_IS_LIST_BOX (list_box), NULL);
priv = gtk_list_box_get_instance_private (list_box);
if (priv->selected_row != NULL)
return priv->selected_row;
return NULL;
}
/**
* gtk_list_box_get_row_at_index:
* @list_box: a #GtkListBox
* @index_: the index of the row
*
* Gets the n:th child in the list (not counting headers).
*
* Return value: (transfer none): the child #GtkWidget
*
* Since: 3.10
*/
GtkListBoxRow *
gtk_list_box_get_row_at_index (GtkListBox *list_box,
gint index_)
{
GtkListBoxPrivate *priv;
GSequenceIter *iter;
g_return_val_if_fail (GTK_IS_LIST_BOX (list_box), NULL);
priv = gtk_list_box_get_instance_private (list_box);
iter = g_sequence_get_iter_at_pos (priv->children, index_);
if (iter)
return g_sequence_get (iter);
return NULL;
}
/**
* gtk_list_box_get_row_at_y:
* @list_box: a #GtkListBox
* @y: position
*
* Gets the row at the @y position.
*
* Return value: (transfer none): the row
*
* Since: 3.10
*/
GtkListBoxRow *
gtk_list_box_get_row_at_y (GtkListBox *list_box,
gint y)
{
GtkListBoxPrivate *priv;
GtkListBoxRow *row, *found_row;
GtkListBoxRowPrivate *row_priv;
GSequenceIter *iter;
g_return_val_if_fail (GTK_IS_LIST_BOX (list_box), NULL);
priv = gtk_list_box_get_instance_private (list_box);
/* TODO: This should use g_sequence_search */
found_row = NULL;
for (iter = g_sequence_get_begin_iter (priv->children);
!g_sequence_iter_is_end (iter);
iter = g_sequence_iter_next (iter))
{
row = (GtkListBoxRow*) g_sequence_get (iter);
row_priv = gtk_list_box_row_get_instance_private (row);
if (y >= row_priv->y && y < (row_priv->y + row_priv->height))
{
found_row = row;
break;
}
}
return found_row;
}
/**
* gtk_list_box_select_row:
* @list_box: a #GtkListBox
* @row: (allow-none): The row to select or %NULL
*
* Make @row the currently selected row.
*
* Since: 3.10
*/
void
gtk_list_box_select_row (GtkListBox *list_box,
GtkListBoxRow *row)
{
g_return_if_fail (GTK_IS_LIST_BOX (list_box));
gtk_list_box_update_selected (list_box, row);
}
/**
* gtk_list_box_set_placeholder:
* @list_box: a #GtkListBox
* @placeholder: (allow-none): a #GtkWidget or %NULL
*
* Sets the placeholder widget that is shown in the list when
* it doesn't display any visible children.
*
* Since: 3.10
*/
void
gtk_list_box_set_placeholder (GtkListBox *list_box,
GtkWidget *placeholder)
{
GtkListBoxPrivate *priv;
g_return_if_fail (GTK_IS_LIST_BOX (list_box));
priv = gtk_list_box_get_instance_private (list_box);
if (priv->placeholder)
{
gtk_widget_unparent (priv->placeholder);
gtk_widget_queue_resize (GTK_WIDGET (list_box));
}
priv->placeholder = placeholder;
if (placeholder)
{
gtk_widget_set_parent (GTK_WIDGET (placeholder), GTK_WIDGET (list_box));
gtk_widget_set_child_visible (GTK_WIDGET (placeholder),
priv->n_visible_rows == 0);
}
}
/**
* gtk_list_box_set_adjustment:
* @list_box: a #GtkListBox
* @adjustment: (allow-none): the adjustment, or %NULL
*
* Sets the adjustment (if any) that the widget uses to
* for vertical scrolling. For instance, this is used
* to get the page size for PageUp/Down key handling.
*
* In the normal case when the @list_box is packed inside
* a #GtkScrolledWindow the adjustment from that will
* be picked up automatically, so there is no need
* to manually do that.
*
* Since: 3.10
*/
void
gtk_list_box_set_adjustment (GtkListBox *list_box,
GtkAdjustment *adjustment)
{
GtkListBoxPrivate *priv;
g_return_if_fail (GTK_IS_LIST_BOX (list_box));
priv = gtk_list_box_get_instance_private (list_box);
g_object_ref_sink (adjustment);
if (priv->adjustment)
g_object_unref (priv->adjustment);
priv->adjustment = adjustment;
}
/**
* gtk_list_box_get_adjustment:
* @list_box: a #GtkListBox
*
* Gets the adjustment (if any) that the widget uses to
* for vertical scrolling.
*
* Return value: (transfer none): the adjustment
*
* Since: 3.10
*/
GtkAdjustment *
gtk_list_box_get_adjustment (GtkListBox *list_box)
{
GtkListBoxPrivate *priv;
g_return_val_if_fail (GTK_IS_LIST_BOX (list_box), NULL);
priv = gtk_list_box_get_instance_private (list_box);
return priv->adjustment;
}
static void
gtk_list_box_parent_set (GtkWidget *widget,
GtkWidget *prev_parent)
{
GtkWidget *parent;
GtkAdjustment *adjustment;
parent = gtk_widget_get_parent (widget);
if (parent && GTK_IS_SCROLLABLE (parent))
{
adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (parent));
gtk_list_box_set_adjustment (GTK_LIST_BOX (widget), adjustment);
}
}
/**
* gtk_list_box_set_selection_mode:
* @list_box: a #GtkListBox
* @mode: The #GtkSelectionMode
*
* Sets how selection works in the listbox.
* See #GtkSelectionMode for details.
*
* Note: #GtkListBox does not support @GTK_SELECTION_MULTIPLE.
*
* Since: 3.10
*/
void
gtk_list_box_set_selection_mode (GtkListBox *list_box,
GtkSelectionMode mode)
{
GtkListBoxPrivate *priv;
g_return_if_fail (GTK_IS_LIST_BOX (list_box));
priv = gtk_list_box_get_instance_private (list_box);
if (mode == GTK_SELECTION_MULTIPLE)
{
g_warning ("Multiple selections not supported");
return;
}
if (priv->selection_mode == mode)
return;
priv->selection_mode = mode;
if (mode == GTK_SELECTION_NONE)
gtk_list_box_update_selected (list_box, NULL);
g_object_notify_by_pspec (G_OBJECT (list_box), properties[PROP_SELECTION_MODE]);
}
/**
* gtk_list_box_get_selection_mode:
* @list_box: a #GtkListBox
*
* Gets the selection mode of the listbox.
*
* Returns: a #GtkSelectionMode
*
* Since: 3.10
*/
GtkSelectionMode
gtk_list_box_get_selection_mode (GtkListBox *list_box)
{
GtkListBoxPrivate *priv;
g_return_val_if_fail (GTK_IS_LIST_BOX (list_box), GTK_SELECTION_NONE);
priv = gtk_list_box_get_instance_private (list_box);
return priv->selection_mode;
}
/**
* gtk_list_box_set_filter_func:
* @list_box: a #GtkListBox
* @filter_func: (closure user_data) (allow-none): callback that lets you filter which rows to show
* @user_data: user data passed to @filter_func
* @destroy: destroy notifier for @user_data
*
* By setting a filter function on the @list_box one can decide dynamically which
* of the rows to show. For instance, to implement a search function on a list that
* filters the original list to only show the matching rows.
*
* The @filter_func will be called for each row after the call, and it will
* continue to be called each time a row changes (via gtk_list_box_row_changed()) or
* when gtk_list_box_invalidate_filter() is called.
*
* Since: 3.10
*/
void
gtk_list_box_set_filter_func (GtkListBox *list_box,
GtkListBoxFilterFunc filter_func,
gpointer user_data,
GDestroyNotify destroy)
{
GtkListBoxPrivate *priv;
g_return_if_fail (GTK_IS_LIST_BOX (list_box));
priv = gtk_list_box_get_instance_private (list_box);
if (priv->filter_func_target_destroy_notify != NULL)
priv->filter_func_target_destroy_notify (priv->filter_func_target);
priv->filter_func = filter_func;
priv->filter_func_target = user_data;
priv->filter_func_target_destroy_notify = destroy;
gtk_list_box_invalidate_filter (list_box);
}
/**
* gtk_list_box_set_header_func:
* @list_box: a #GtkListBox
* @update_header: (closure user_data) (allow-none): callback that lets you add row headers
* @user_data: user data passed to @update_header
* @destroy: destroy notifier for @user_data
*
* By setting a header function on the @list_box one can dynamically add headers
* in front of rows, depending on the contents of the row and its position in the list.
* For instance, one could use it to add headers in front of the first item of a
* new kind, in a list sorted by the kind.
*
* The @update_header can look at the current header widget using gtk_list_box_row_get_header()
* and either update the state of the widget as needed, or set a new one using
* gtk_list_box_row_set_header(). If no header is needed, set the header to %NULL.
*
* Note that you may get many calls @update_header to this for a particular row when e.g.
* changing things that don't affect the header. In this case it is important for performance
* to not blindly replace an exisiting header widh an identical one.
*
* The @update_header function will be called for each row after the call, and it will
* continue to be called each time a row changes (via gtk_list_box_row_changed()) and when
* the row before changes (either by gtk_list_box_row_changed() on the previous row, or when
* the previous row becomes a different row). It is also called for all rows when
* gtk_list_box_invalidate_headers() is called.
*
* Since: 3.10
*/
void
gtk_list_box_set_header_func (GtkListBox *list_box,
GtkListBoxUpdateHeaderFunc update_header,
gpointer user_data,
GDestroyNotify destroy)
{
GtkListBoxPrivate *priv;
g_return_if_fail (GTK_IS_LIST_BOX (list_box));
priv = gtk_list_box_get_instance_private (list_box);
if (priv->update_header_func_target_destroy_notify != NULL)
priv->update_header_func_target_destroy_notify (priv->update_header_func_target);
priv->update_header_func = update_header;
priv->update_header_func_target = user_data;
priv->update_header_func_target_destroy_notify = destroy;
gtk_list_box_invalidate_headers (list_box);
}
/**
* gtk_list_box_invalidate_filter:
* @list_box: a #GtkListBox
*
* Update the filtering for all rows. Call this when result
* of the filter function on the @list_box is changed due
* to an external factor. For instance, this would be used
* if the filter function just looked for a specific search
* string and the entry with the search string has changed.
*
* Since: 3.10
*/
void
gtk_list_box_invalidate_filter (GtkListBox *list_box)
{
g_return_if_fail (GTK_IS_LIST_BOX (list_box));
gtk_list_box_apply_filter_all (list_box);
gtk_list_box_invalidate_headers (list_box);
gtk_widget_queue_resize (GTK_WIDGET (list_box));
}
static gint
do_sort (GtkListBoxRow *a,
GtkListBoxRow *b,
GtkListBox *list_box)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
return priv->sort_func (a, b, priv->sort_func_target);
}
/**
* gtk_list_box_invalidate_sort:
* @list_box: a #GtkListBox
*
* Update the sorting for all rows. Call this when result
* of the sort function on the @list_box is changed due
* to an external factor.
*
* Since: 3.10
*/
void
gtk_list_box_invalidate_sort (GtkListBox *list_box)
{
GtkListBoxPrivate *priv;
g_return_if_fail (GTK_IS_LIST_BOX (list_box));
priv = gtk_list_box_get_instance_private (list_box);
g_sequence_sort (priv->children, (GCompareDataFunc)do_sort, list_box);
gtk_list_box_invalidate_headers (list_box);
gtk_widget_queue_resize (GTK_WIDGET (list_box));
}
static void
gtk_list_box_do_reseparate (GtkListBox *list_box)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
GSequenceIter *iter;
for (iter = g_sequence_get_begin_iter (priv->children);
!g_sequence_iter_is_end (iter);
iter = g_sequence_iter_next (iter))
gtk_list_box_update_header (list_box, iter);
gtk_widget_queue_resize (GTK_WIDGET (list_box));
}
/**
* gtk_list_box_invalidate_headers:
* @list_box: a #GtkListBox
*
* Update the separators for all rows. Call this when result
* of the header function on the @list_box is changed due
* to an external factor.
*
* Since: 3.10
*/
void
gtk_list_box_invalidate_headers (GtkListBox *list_box)
{
g_return_if_fail (GTK_IS_LIST_BOX (list_box));
if (!gtk_widget_get_visible (GTK_WIDGET (list_box)))
return;
gtk_list_box_do_reseparate (list_box);
}
/**
* gtk_list_box_set_sort_func:
* @list_box: a #GtkListBox
* @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 @list_box one can dynamically reorder the rows
* of the list, based on the contents of the rows.
*
* The @sort_func will be called for each row after the call, and will continue to
* be called each time a row changes (via gtk_list_box_row_changed()) and when
* gtk_list_box_invalidate_sort() is called.
*
* Since: 3.10
*/
void
gtk_list_box_set_sort_func (GtkListBox *list_box,
GtkListBoxSortFunc sort_func,
gpointer user_data,
GDestroyNotify destroy)
{
GtkListBoxPrivate *priv;
g_return_if_fail (GTK_IS_LIST_BOX (list_box));
priv = gtk_list_box_get_instance_private (list_box);
if (priv->sort_func_target_destroy_notify != NULL)
priv->sort_func_target_destroy_notify (priv->sort_func_target);
priv->sort_func = sort_func;
priv->sort_func_target = user_data;
priv->sort_func_target_destroy_notify = destroy;
gtk_list_box_invalidate_sort (list_box);
}
static void
gtk_list_box_got_row_changed (GtkListBox *list_box,
GtkListBoxRow *row)
{
GtkListBoxPrivate *priv;
GtkListBoxRowPrivate *row_priv;
GSequenceIter *prev_next, *next;
g_return_if_fail (GTK_IS_LIST_BOX (list_box));
g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
priv = gtk_list_box_get_instance_private (list_box);
row_priv = gtk_list_box_row_get_instance_private (row);
prev_next = gtk_list_box_get_next_visible (list_box, row_priv->iter);
if (priv->sort_func != NULL)
{
g_sequence_sort_changed (row_priv->iter,
(GCompareDataFunc)do_sort,
list_box);
gtk_widget_queue_resize (GTK_WIDGET (list_box));
}
gtk_list_box_apply_filter (list_box, row);
if (gtk_widget_get_visible (GTK_WIDGET (list_box)))
{
next = gtk_list_box_get_next_visible (list_box, row_priv->iter);
gtk_list_box_update_header (list_box, row_priv->iter);
gtk_list_box_update_header (list_box, next);
gtk_list_box_update_header (list_box, prev_next);
}
}
/**
* gtk_list_box_set_activate_on_single_click:
* @list_box: a #GtkListBox
* @single: a boolean
*
* If @single is %TRUE, rows will be activated when you click on them,
* otherwise you need to double-click.
*
* Since: 3.10
*/
void
gtk_list_box_set_activate_on_single_click (GtkListBox *list_box,
gboolean single)
{
GtkListBoxPrivate *priv;
g_return_if_fail (GTK_IS_LIST_BOX (list_box));
priv = gtk_list_box_get_instance_private (list_box);
single = single != FALSE;
if (priv->activate_single_click == single)
return;
priv->activate_single_click = single;
g_object_notify_by_pspec (G_OBJECT (list_box), properties[PROP_ACTIVATE_ON_SINGLE_CLICK]);
}
/**
* gtk_list_box_get_activate_on_single_click:
* @list_box: a #GtkListBox
*
* Returns whether rows activate on single clicks.
*
* Returns: %TRUE if rows are activated on single click, %FALSE otherwise
*
* Since: 3.10
*/
gboolean
gtk_list_box_get_activate_on_single_click (GtkListBox *list_box)
{
GtkListBoxPrivate *priv;
g_return_val_if_fail (GTK_IS_LIST_BOX (list_box), FALSE);
priv = gtk_list_box_get_instance_private (list_box);
return priv->activate_single_click;
}
static void
gtk_list_box_add_move_binding (GtkBindingSet *binding_set,
guint keyval,
GdkModifierType modmask,
GtkMovementStep step,
gint count)
{
gtk_binding_entry_add_signal (binding_set, keyval, modmask,
"move-cursor", (guint) 2, GTK_TYPE_MOVEMENT_STEP, step, G_TYPE_INT, count, NULL);
if ((modmask & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
return;
gtk_binding_entry_add_signal (binding_set, keyval, GDK_CONTROL_MASK,
"move-cursor", (guint) 2, GTK_TYPE_MOVEMENT_STEP, step, G_TYPE_INT, count, NULL);
}
static void
gtk_list_box_update_cursor (GtkListBox *list_box,
GtkListBoxRow *row)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
priv->cursor_row = row;
gtk_widget_grab_focus (GTK_WIDGET (row));
gtk_widget_queue_draw (GTK_WIDGET (row));
_gtk_list_box_accessible_update_cursor (list_box, row);
}
static void
gtk_list_box_update_selected (GtkListBox *list_box,
GtkListBoxRow *row)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
if (row != priv->selected_row &&
(row == NULL || priv->selection_mode != GTK_SELECTION_NONE))
{
if (priv->selected_row)
gtk_widget_unset_state_flags (GTK_WIDGET (priv->selected_row),
GTK_STATE_FLAG_SELECTED);
priv->selected_row = row;
if (priv->selected_row)
gtk_widget_set_state_flags (GTK_WIDGET (priv->selected_row),
GTK_STATE_FLAG_SELECTED,
FALSE);
g_signal_emit (list_box, signals[ROW_SELECTED], 0,
priv->selected_row);
gtk_widget_queue_draw (GTK_WIDGET (list_box));
}
_gtk_list_box_accessible_selection_changed (list_box);
if (row != NULL)
gtk_list_box_update_cursor (list_box, row);
}
static void
gtk_list_box_select_and_activate (GtkListBox *list_box,
GtkListBoxRow *row)
{
gtk_list_box_update_selected (list_box, row);
if (row != NULL)
g_signal_emit (list_box, signals[ROW_ACTIVATED], 0, row);
}
static void
gtk_list_box_update_prelight (GtkListBox *list_box,
GtkListBoxRow *row)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
if (row != priv->prelight_row)
{
if (priv->prelight_row)
gtk_widget_unset_state_flags (GTK_WIDGET (priv->prelight_row),
GTK_STATE_FLAG_PRELIGHT);
if (row != NULL && gtk_widget_is_sensitive (GTK_WIDGET (row)))
{
priv->prelight_row = row;
gtk_widget_set_state_flags (GTK_WIDGET (priv->prelight_row),
GTK_STATE_FLAG_PRELIGHT,
FALSE);
}
else
{
priv->prelight_row = NULL;
}
gtk_widget_queue_draw (GTK_WIDGET (list_box));
}
}
static void
gtk_list_box_update_active (GtkListBox *list_box,
GtkListBoxRow *row)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
gboolean val;
val = priv->active_row == row;
if (priv->active_row != NULL &&
val != priv->active_row_active)
{
priv->active_row_active = val;
if (priv->active_row_active)
gtk_widget_set_state_flags (GTK_WIDGET (priv->active_row),
GTK_STATE_FLAG_ACTIVE,
FALSE);
else
gtk_widget_unset_state_flags (GTK_WIDGET (priv->active_row),
GTK_STATE_FLAG_ACTIVE);
gtk_widget_queue_draw (GTK_WIDGET (list_box));
}
}
static gboolean
gtk_list_box_enter_notify_event (GtkWidget *widget,
GdkEventCrossing *event)
{
GtkListBox *list_box = GTK_LIST_BOX (widget);
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
GtkListBoxRow *row;
if (event->window != gtk_widget_get_window (GTK_WIDGET (list_box)))
return FALSE;
priv->in_widget = TRUE;
row = gtk_list_box_get_row_at_y (list_box, event->y);
gtk_list_box_update_prelight (list_box, row);
gtk_list_box_update_active (list_box, row);
return FALSE;
}
static gboolean
gtk_list_box_leave_notify_event (GtkWidget *widget,
GdkEventCrossing *event)
{
GtkListBox *list_box = GTK_LIST_BOX (widget);
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
GtkListBoxRow *row = NULL;
if (event->window != gtk_widget_get_window (GTK_WIDGET (list_box)))
return FALSE;
if (event->detail != GDK_NOTIFY_INFERIOR)
{
priv->in_widget = FALSE;
row = NULL;
}
else
row = gtk_list_box_get_row_at_y (list_box, event->y);
gtk_list_box_update_prelight (list_box, row);
gtk_list_box_update_active (list_box, row);
return FALSE;
}
static gboolean
gtk_list_box_motion_notify_event (GtkWidget *widget,
GdkEventMotion *event)
{
GtkListBox *list_box = GTK_LIST_BOX (widget);
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
GtkListBoxRow *row;
GdkWindow *window, *event_window;
gint relative_y;
gdouble parent_y;
if (!priv->in_widget)
return FALSE;
window = gtk_widget_get_window (GTK_WIDGET (list_box));
event_window = event->window;
relative_y = event->y;
while ((event_window != NULL) && (event_window != window))
{
gdk_window_coords_to_parent (event_window, 0, relative_y, NULL, &parent_y);
relative_y = parent_y;
event_window = gdk_window_get_effective_parent (event_window);
}
row = gtk_list_box_get_row_at_y (list_box, relative_y);
gtk_list_box_update_prelight (list_box, row);
gtk_list_box_update_active (list_box, row);
return FALSE;
}
static gboolean
gtk_list_box_button_press_event (GtkWidget *widget,
GdkEventButton *event)
{
GtkListBox *list_box = GTK_LIST_BOX (widget);
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
gboolean ctrl_pressed;
GdkWindow *window;
double x, y;
if (event->button == GDK_BUTTON_PRIMARY)
{
GtkListBoxRow *row;
ctrl_pressed = (event->state & GDK_CONTROL_MASK) != 0;
priv->active_row = NULL;
window = event->window;
x = event->x;
y = event->y;
while (window && window != gtk_widget_get_window (widget))
{
gdk_window_coords_to_parent (window, x, y, &x, &y);
window = gdk_window_get_effective_parent (window);
}
row = gtk_list_box_get_row_at_y (list_box, y);
if (row != NULL && gtk_widget_is_sensitive (GTK_WIDGET (row)))
{
if (event->type == GDK_2BUTTON_PRESS)
{
if (!priv->activate_single_click)
g_signal_emit (list_box, signals[ROW_ACTIVATED], 0,
row);
}
else if (ctrl_pressed)
{
if (priv->selection_mode == GTK_SELECTION_SINGLE &&
priv->selected_row == row)
gtk_list_box_update_selected (list_box, NULL);
else
gtk_list_box_update_selected (list_box, row);
}
else
{
priv->active_row = row;
priv->active_row_active = TRUE;
gtk_widget_set_state_flags (GTK_WIDGET (priv->active_row),
GTK_STATE_FLAG_ACTIVE,
FALSE);
gtk_widget_queue_draw (GTK_WIDGET (list_box));
}
}
/* TODO:
Should mark as active while down,
and handle grab breaks */
}
return FALSE;
}
static gboolean
gtk_list_box_button_release_event (GtkWidget *widget,
GdkEventButton *event)
{
GtkListBox *list_box = GTK_LIST_BOX (widget);
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
/* Take a ref to protect against reentrancy (i.e. the activation may destroy the widget) */
g_object_ref (list_box);
if (event->button == GDK_BUTTON_PRIMARY)
{
if (priv->active_row != NULL &&
priv->active_row_active)
{
gtk_widget_unset_state_flags (GTK_WIDGET (priv->active_row),
GTK_STATE_FLAG_ACTIVE);
if (priv->activate_single_click)
gtk_list_box_select_and_activate (list_box, priv->active_row);
else
gtk_list_box_update_selected (list_box, priv->active_row);
}
priv->active_row = NULL;
priv->active_row_active = FALSE;
gtk_widget_queue_draw (GTK_WIDGET (list_box));
}
g_object_unref (list_box);
return FALSE;
}
static void
gtk_list_box_show (GtkWidget *widget)
{
GtkListBox *list_box = GTK_LIST_BOX (widget);
gtk_list_box_do_reseparate (list_box);
GTK_WIDGET_CLASS (gtk_list_box_parent_class)->show (widget);
}
#define ROW_PRIV(_row) ((GtkListBoxRowPrivate *)gtk_list_box_row_get_instance_private (_row))
static gboolean
gtk_list_box_focus (GtkWidget *widget,
GtkDirectionType direction)
{
GtkListBox *list_box = GTK_LIST_BOX (widget);
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
GtkWidget *focus_child;
GtkListBoxRow *next_focus_row;
focus_child = gtk_container_get_focus_child ((GtkContainer*) list_box);
next_focus_row = NULL;
if (focus_child != NULL)
{
GSequenceIter *i;
if (gtk_widget_child_focus (focus_child, direction))
return TRUE;
if (direction == GTK_DIR_UP || direction == GTK_DIR_TAB_BACKWARD)
{
i = gtk_list_box_get_previous_visible (list_box, ROW_PRIV (GTK_LIST_BOX_ROW (focus_child))->iter);
while (i != NULL)
{
if (gtk_widget_get_sensitive (g_sequence_get (i)))
{
next_focus_row = g_sequence_get (i);
break;
}
i = gtk_list_box_get_previous_visible (list_box, i);
}
}
else if (direction == GTK_DIR_DOWN || direction == GTK_DIR_TAB_FORWARD)
{
i = gtk_list_box_get_next_visible (list_box, ROW_PRIV (GTK_LIST_BOX_ROW (focus_child))->iter);
while (!g_sequence_iter_is_end (i))
{
if (gtk_widget_get_sensitive (g_sequence_get (i)))
{
next_focus_row = g_sequence_get (i);
break;
}
i = gtk_list_box_get_next_visible (list_box, i);
}
}
}
else
{
/* No current focus row */
switch (direction)
{
case GTK_DIR_UP:
case GTK_DIR_TAB_BACKWARD:
next_focus_row = priv->selected_row;
if (next_focus_row == NULL)
next_focus_row = gtk_list_box_get_last_focusable (list_box);
break;
default:
next_focus_row = priv->selected_row;
if (next_focus_row == NULL)
next_focus_row =
gtk_list_box_get_first_focusable (list_box);
break;
}
}
if (next_focus_row == NULL)
{
if (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN)
{
if (gtk_widget_keynav_failed (GTK_WIDGET (list_box), direction))
return TRUE;
}
return FALSE;
}
if (gtk_widget_child_focus (GTK_WIDGET (next_focus_row), direction))
return TRUE;
return TRUE;
}
static gboolean
gtk_list_box_draw (GtkWidget *widget,
cairo_t *cr)
{
GtkAllocation allocation;
GtkStyleContext *context;
gtk_widget_get_allocation (widget, &allocation);
context = gtk_widget_get_style_context (widget);
gtk_render_background (context, cr, 0, 0, allocation.width, allocation.height);
GTK_WIDGET_CLASS (gtk_list_box_parent_class)->draw (widget, cr);
return TRUE;
}
static void
gtk_list_box_realize (GtkWidget *widget)
{
GtkListBox *list_box = GTK_LIST_BOX (widget);
GtkAllocation allocation;
GdkWindowAttr attributes = { 0, };
GdkWindow *window;
gtk_widget_get_allocation (GTK_WIDGET (list_box), &allocation);
gtk_widget_set_realized (GTK_WIDGET (list_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 (list_box)) |
GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_POINTER_MOTION_MASK |
GDK_EXPOSURE_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 (list_box)),
&attributes, GDK_WA_X | GDK_WA_Y);
gtk_style_context_set_background (gtk_widget_get_style_context (GTK_WIDGET (list_box)), window);
gdk_window_set_user_data (window, (GObject*) list_box);
gtk_widget_set_window (GTK_WIDGET (list_box), window); /* Passes ownership */
}
static void
list_box_add_visible_rows (GtkListBox *list_box,
gint n)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
int was_zero;
was_zero = priv->n_visible_rows == 0;
priv->n_visible_rows += n;
if (priv->placeholder &&
(was_zero || priv->n_visible_rows == 0))
gtk_widget_set_child_visible (GTK_WIDGET (priv->placeholder),
priv->n_visible_rows == 0);
}
/* Children are visible if they are shown by the app (visible)
* and not filtered out (child_visible) by the listbox
*/
static void
update_row_is_visible (GtkListBox *list_box,
GtkListBoxRow *row)
{
GtkListBoxRowPrivate *row_priv = ROW_PRIV (row);
gboolean was_visible;
was_visible = row_priv->visible;
row_priv->visible =
gtk_widget_get_visible (GTK_WIDGET (row)) &&
gtk_widget_get_child_visible (GTK_WIDGET (row));
if (was_visible && !row_priv->visible)
list_box_add_visible_rows (list_box, -1);
if (!was_visible && row_priv->visible)
list_box_add_visible_rows (list_box, 1);
}
static gboolean
row_is_visible (GtkListBoxRow *row)
{
return ROW_PRIV (row)->visible;
}
static void
gtk_list_box_apply_filter (GtkListBox *list_box,
GtkListBoxRow *row)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
gboolean do_show;
do_show = TRUE;
if (priv->filter_func != NULL)
do_show = priv->filter_func (row, priv->filter_func_target);
gtk_widget_set_child_visible (GTK_WIDGET (row), do_show);
update_row_is_visible (list_box, row);
}
static void
gtk_list_box_apply_filter_all (GtkListBox *list_box)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
GtkListBoxRow *row;
GSequenceIter *iter;
for (iter = g_sequence_get_begin_iter (priv->children);
!g_sequence_iter_is_end (iter);
iter = g_sequence_iter_next (iter))
{
row = g_sequence_get (iter);
gtk_list_box_apply_filter (list_box, row);
}
}
static GtkListBoxRow *
gtk_list_box_get_first_focusable (GtkListBox *list_box)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
GtkListBoxRow *row;
GSequenceIter *iter;
for (iter = g_sequence_get_begin_iter (priv->children);
!g_sequence_iter_is_end (iter);
iter = g_sequence_iter_next (iter))
{
row = g_sequence_get (iter);
if (row_is_visible (row) && gtk_widget_is_sensitive (GTK_WIDGET (row)))
return row;
}
return NULL;
}
static GtkListBoxRow *
gtk_list_box_get_last_focusable (GtkListBox *list_box)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
GtkListBoxRow *row;
GSequenceIter *iter;
iter = g_sequence_get_end_iter (priv->children);
while (!g_sequence_iter_is_begin (iter))
{
iter = g_sequence_iter_prev (iter);
row = g_sequence_get (iter);
if (row_is_visible (row) && gtk_widget_is_sensitive (GTK_WIDGET (row)))
return row;
}
return NULL;
}
static GSequenceIter *
gtk_list_box_get_previous_visible (GtkListBox *list_box,
GSequenceIter *iter)
{
GtkListBoxRow *row;
if (g_sequence_iter_is_begin (iter))
return NULL;
do
{
iter = g_sequence_iter_prev (iter);
row = g_sequence_get (iter);
if (row_is_visible (row))
return iter;
}
while (!g_sequence_iter_is_begin (iter));
return NULL;
}
static GSequenceIter *
gtk_list_box_get_next_visible (GtkListBox *list_box,
GSequenceIter *iter)
{
GtkListBoxRow *row;
if (g_sequence_iter_is_end (iter))
return iter;
do
{
iter = g_sequence_iter_next (iter);
if (!g_sequence_iter_is_end (iter))
{
row = g_sequence_get (iter);
if (row_is_visible (row))
return iter;
}
}
while (!g_sequence_iter_is_end (iter));
return iter;
}
static void
gtk_list_box_update_header (GtkListBox *list_box,
GSequenceIter *iter)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
GtkListBoxRow *row;
GSequenceIter *before_iter;
GtkListBoxRow *before_row;
GtkWidget *old_header;
if (iter == NULL || g_sequence_iter_is_end (iter))
return;
row = g_sequence_get (iter);
before_iter = gtk_list_box_get_previous_visible (list_box, iter);
if (row)
g_object_ref (row);
before_row = NULL;
if (before_iter != NULL)
{
before_row = g_sequence_get (before_iter);
if (before_row)
g_object_ref (before_row);
}
if (priv->update_header_func != NULL &&
row_is_visible (row))
{
old_header = ROW_PRIV (row)->header;
if (old_header)
g_object_ref (old_header);
priv->update_header_func (row,
before_row,
priv->update_header_func_target);
if (old_header != ROW_PRIV (row)->header)
{
if (old_header != NULL)
{
gtk_widget_unparent (old_header);
g_hash_table_remove (priv->header_hash, old_header);
}
if (ROW_PRIV (row)->header != NULL)
{
g_hash_table_insert (priv->header_hash, ROW_PRIV (row)->header, row);
gtk_widget_set_parent (ROW_PRIV (row)->header, GTK_WIDGET (list_box));
gtk_widget_show (ROW_PRIV (row)->header);
}
gtk_widget_queue_resize (GTK_WIDGET (list_box));
}
if (old_header)
g_object_unref (old_header);
}
else
{
if (ROW_PRIV (row)->header != NULL)
{
g_hash_table_remove (priv->header_hash, ROW_PRIV (row)->header);
gtk_widget_unparent (ROW_PRIV (row)->header);
gtk_list_box_row_set_header (row, NULL);
gtk_widget_queue_resize (GTK_WIDGET (list_box));
}
}
if (before_row)
g_object_unref (before_row);
if (row)
g_object_unref (row);
}
static void
gtk_list_box_row_visibility_changed (GtkListBox *list_box,
GtkListBoxRow *row)
{
update_row_is_visible (list_box, row);
if (gtk_widget_get_visible (GTK_WIDGET (list_box)))
{
gtk_list_box_update_header (list_box, ROW_PRIV (row)->iter);
gtk_list_box_update_header (list_box,
gtk_list_box_get_next_visible (list_box, ROW_PRIV (row)->iter));
}
}
static void
gtk_list_box_add (GtkContainer *container,
GtkWidget *child)
{
gtk_list_box_insert (GTK_LIST_BOX (container), child, -1);
}
static void
gtk_list_box_remove (GtkContainer *container,
GtkWidget *child)
{
GtkListBox *list_box = GTK_LIST_BOX (container);
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
gboolean was_visible;
GtkListBoxRow *row;
GSequenceIter *next;
was_visible = gtk_widget_get_visible (child);
if (!GTK_IS_LIST_BOX_ROW (child))
{
row = g_hash_table_lookup (priv->header_hash, child);
if (row != NULL)
{
g_hash_table_remove (priv->header_hash, child);
g_clear_object (&ROW_PRIV (row)->header);
gtk_widget_unparent (child);
if (was_visible && gtk_widget_get_visible (GTK_WIDGET (list_box)))
gtk_widget_queue_resize (GTK_WIDGET (list_box));
}
else
{
g_warning ("Tried to remove non-child %p\n", child);
}
return;
}
row = GTK_LIST_BOX_ROW (child);
if (g_sequence_iter_get_sequence (ROW_PRIV (row)->iter) != priv->children)
{
g_warning ("Tried to remove non-child %p\n", child);
return;
}
if (ROW_PRIV (row)->visible)
list_box_add_visible_rows (list_box, -1);
if (ROW_PRIV (row)->header != NULL)
{
g_hash_table_remove (priv->header_hash, ROW_PRIV (row)->header);
gtk_widget_unparent (ROW_PRIV (row)->header);
g_clear_object (&ROW_PRIV (row)->header);
}
if (row == priv->selected_row)
gtk_list_box_update_selected (list_box, NULL);
if (row == priv->prelight_row) {
gtk_widget_unset_state_flags (GTK_WIDGET (priv->prelight_row),
GTK_STATE_FLAG_PRELIGHT);
priv->prelight_row = NULL;
}
if (row == priv->cursor_row)
priv->cursor_row = NULL;
if (row == priv->active_row) {
if (priv->active_row_active)
gtk_widget_unset_state_flags (GTK_WIDGET (priv->active_row),
GTK_STATE_FLAG_ACTIVE);
priv->active_row = NULL;
}
if (row == priv->drag_highlighted_row)
gtk_list_box_drag_unhighlight_row (list_box);
next = gtk_list_box_get_next_visible (list_box, ROW_PRIV (row)->iter);
gtk_widget_unparent (child);
g_sequence_remove (ROW_PRIV (row)->iter);
if (gtk_widget_get_visible (GTK_WIDGET (list_box)))
gtk_list_box_update_header (list_box, next);
if (was_visible && gtk_widget_get_visible (GTK_WIDGET (list_box)))
gtk_widget_queue_resize (GTK_WIDGET (list_box));
}
static void
gtk_list_box_forall_internal (GtkContainer *container,
gboolean include_internals,
GtkCallback callback,
gpointer callback_target)
{
GtkListBox *list_box = GTK_LIST_BOX (container);
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
GSequenceIter *iter;
GtkListBoxRow *row;
if (priv->placeholder != NULL && include_internals)
callback (priv->placeholder, callback_target);
iter = g_sequence_get_begin_iter (priv->children);
while (!g_sequence_iter_is_end (iter))
{
row = g_sequence_get (iter);
iter = g_sequence_iter_next (iter);
if (ROW_PRIV (row)->header != NULL && include_internals)
callback (ROW_PRIV (row)->header, callback_target);
callback (GTK_WIDGET (row), callback_target);
}
}
static void
gtk_list_box_compute_expand_internal (GtkWidget *widget,
gboolean *hexpand,
gboolean *vexpand)
{
GTK_WIDGET_CLASS (gtk_list_box_parent_class)->compute_expand (widget,
hexpand, vexpand);
/* We don't expand vertically beyound the minimum size */
if (vexpand)
*vexpand = FALSE;
}
static GType
gtk_list_box_child_type (GtkContainer *container)
{
/* We really support any type but we wrap it in a row. But that is
* more like a C helper function, in an abstract sense we only support
* row children, so that is what tools accessing this should use.
*/
return GTK_TYPE_LIST_BOX_ROW;
}
static GtkSizeRequestMode
gtk_list_box_get_request_mode (GtkWidget *widget)
{
return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
}
static void
gtk_list_box_get_preferred_height (GtkWidget *widget,
gint *minimum_height,
gint *natural_height)
{
gint natural_width;
gtk_list_box_get_preferred_width (widget, NULL, &natural_width);
gtk_list_box_get_preferred_height_for_width (widget, natural_width,
minimum_height, natural_height);
}
static void
gtk_list_box_get_preferred_height_for_width (GtkWidget *widget,
gint width,
gint *minimum_height_out,
gint *natural_height_out)
{
GtkListBox *list_box = GTK_LIST_BOX (widget);
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
GSequenceIter *iter;
gint minimum_height;
gint natural_height;
minimum_height = 0;
if (priv->placeholder != NULL && gtk_widget_get_child_visible (priv->placeholder))
gtk_widget_get_preferred_height_for_width (priv->placeholder, width,
&minimum_height, NULL);
for (iter = g_sequence_get_begin_iter (priv->children);
!g_sequence_iter_is_end (iter);
iter = g_sequence_iter_next (iter))
{
GtkListBoxRow *row;
gint row_min = 0;
row = g_sequence_get (iter);
if (!row_is_visible (row))
continue;
if (ROW_PRIV (row)->header != NULL)
{
gtk_widget_get_preferred_height_for_width (ROW_PRIV (row)->header, width, &row_min, NULL);
minimum_height += row_min;
}
gtk_widget_get_preferred_height_for_width (GTK_WIDGET (row), width,
&row_min, NULL);
minimum_height += row_min;
}
/* We always allocate the minimum height, since handling expanding rows is way too costly,
* and unlikely to be used, as lists are generally put inside a scrolling window anyway.
*/
natural_height = minimum_height;
if (minimum_height_out)
*minimum_height_out = minimum_height;
if (natural_height_out)
*natural_height_out = natural_height;
}
static void
gtk_list_box_get_preferred_width (GtkWidget *widget,
gint *minimum_width_out,
gint *natural_width_out)
{
GtkListBox *list_box = GTK_LIST_BOX (widget);
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
gint minimum_width;
gint natural_width;
GSequenceIter *iter;
GtkListBoxRow *row;
gint row_min;
gint row_nat;
minimum_width = 0;
natural_width = 0;
if (priv->placeholder != NULL && gtk_widget_get_child_visible (priv->placeholder))
gtk_widget_get_preferred_width (priv->placeholder,
&minimum_width, &natural_width);
for (iter = g_sequence_get_begin_iter (priv->children);
!g_sequence_iter_is_end (iter);
iter = g_sequence_iter_next (iter))
{
row = g_sequence_get (iter);
/* We *do* take visible but filtered rows into account here so that
* the list width doesn't change during filtering
*/
if (!gtk_widget_get_visible (GTK_WIDGET (row)))
continue;
gtk_widget_get_preferred_width (GTK_WIDGET (row), &row_min, &row_nat);
minimum_width = MAX (minimum_width, row_min);
natural_width = MAX (natural_width, row_nat);
if (ROW_PRIV (row)->header != NULL)
{
gtk_widget_get_preferred_width (ROW_PRIV (row)->header, &row_min, &row_nat);
minimum_width = MAX (minimum_width, row_min);
natural_width = MAX (natural_width, row_nat);
}
}
if (minimum_width_out)
*minimum_width_out = minimum_width;
if (natural_width_out)
*natural_width_out = natural_width;
}
static void
gtk_list_box_get_preferred_width_for_height (GtkWidget *widget,
gint height,
gint *minimum_width,
gint *natural_width)
{
gtk_list_box_get_preferred_width (widget, minimum_width, natural_width);
}
static void
gtk_list_box_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkListBox *list_box = GTK_LIST_BOX (widget);
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
GtkAllocation child_allocation;
GtkAllocation header_allocation;
GtkListBoxRow *row;
GdkWindow *window;
GSequenceIter *iter;
int child_min;
child_allocation.x = 0;
child_allocation.y = 0;
child_allocation.width = 0;
child_allocation.height = 0;
header_allocation.x = 0;
header_allocation.y = 0;
header_allocation.width = 0;
header_allocation.height = 0;
gtk_widget_set_allocation (GTK_WIDGET (list_box), allocation);
window = gtk_widget_get_window (GTK_WIDGET (list_box));
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;
header_allocation.x = 0;
header_allocation.width = allocation->width;
if (priv->placeholder != NULL && gtk_widget_get_child_visible (priv->placeholder))
{
gtk_widget_get_preferred_height_for_width (priv->placeholder,
allocation->width, &child_min, NULL);
header_allocation.height = child_min;
header_allocation.y = child_allocation.y;
gtk_widget_size_allocate (priv->placeholder,
&header_allocation);
child_allocation.y += child_min;
}
for (iter = g_sequence_get_begin_iter (priv->children);
!g_sequence_iter_is_end (iter);
iter = g_sequence_iter_next (iter))
{
row = g_sequence_get (iter);
if (!row_is_visible (row))
{
ROW_PRIV (row)->y = child_allocation.y;
ROW_PRIV (row)->height = 0;
continue;
}
if (ROW_PRIV (row)->header != NULL)
{
gtk_widget_get_preferred_height_for_width (ROW_PRIV (row)->header,
allocation->width, &child_min, NULL);
header_allocation.height = child_min;
header_allocation.y = child_allocation.y;
gtk_widget_size_allocate (ROW_PRIV (row)->header, &header_allocation);
child_allocation.y += child_min;
}
ROW_PRIV (row)->y = child_allocation.y;
gtk_widget_get_preferred_height_for_width (GTK_WIDGET (row),
child_allocation.width, &child_min, NULL);
child_allocation.height = child_min;
ROW_PRIV (row)->height = child_allocation.height;
gtk_widget_size_allocate (GTK_WIDGET (row), &child_allocation);
child_allocation.y += child_min;
}
}
/**
* gtk_list_box_prepend:
* @list_box: a #GtkListBox.
* @child: the #GtkWidget to add
*
* Prepend a widget to the list. If a sort function is set, the widget will
* actually be inserted at the calculated position and this function has the
* same effect of gtk_container_add().
*
* Since: 3.10
*/
void
gtk_list_box_prepend (GtkListBox *list_box,
GtkWidget *child)
{
gtk_list_box_insert (list_box, child, 0);
}
/**
* gtk_list_box_insert:
* @list_box: a #GtkListBox.
* @child: the #GtkWidget to add
* @position: the position to insert @child in
*
* Insert the @child into the @list_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 of gtk_container_add().
*
* If @position is -1, or larger than the total number of items in the
* @list_box, then the @child will be appended to the end.
*
* Since: 3.10
*/
void
gtk_list_box_insert (GtkListBox *list_box,
GtkWidget *child,
gint position)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
GtkListBoxRow *row;
GSequenceIter *iter = NULL;
g_return_if_fail (GTK_IS_LIST_BOX (list_box));
g_return_if_fail (GTK_IS_WIDGET (child));
if (GTK_IS_LIST_BOX_ROW (child))
row = GTK_LIST_BOX_ROW (child);
else
{
row = GTK_LIST_BOX_ROW (gtk_list_box_row_new ());
gtk_widget_show (GTK_WIDGET (row));
gtk_container_add (GTK_CONTAINER (row), child);
}
if (priv->sort_func != NULL)
iter = g_sequence_insert_sorted (priv->children, row,
(GCompareDataFunc)do_sort, list_box);
else if (position == 0)
iter = g_sequence_prepend (priv->children, row);
else if (position == -1)
iter = g_sequence_append (priv->children, row);
else
{
GSequenceIter *current_iter;
current_iter = g_sequence_get_iter_at_pos (priv->children, position);
iter = g_sequence_insert_before (current_iter, row);
}
ROW_PRIV (row)->iter = iter;
gtk_widget_set_parent (GTK_WIDGET (row), GTK_WIDGET (list_box));
gtk_widget_set_child_visible (GTK_WIDGET (row), TRUE);
ROW_PRIV (row)->visible = gtk_widget_get_visible (GTK_WIDGET (row));
if (ROW_PRIV (row)->visible)
list_box_add_visible_rows (list_box, 1);
gtk_list_box_apply_filter (list_box, row);
if (gtk_widget_get_visible (GTK_WIDGET (list_box)))
{
gtk_list_box_update_header (list_box, ROW_PRIV (row)->iter);
gtk_list_box_update_header (list_box,
gtk_list_box_get_next_visible (list_box, ROW_PRIV (row)->iter));
}
}
/**
* gtk_list_box_drag_unhighlight_row:
* @list_box: An #GtkListBox.
*
* If a row has previously been highlighted via gtk_list_box_drag_highlight_row()
* it will have the highlight removed.
*
* Since: 3.10
*/
void
gtk_list_box_drag_unhighlight_row (GtkListBox *list_box)
{
GtkListBoxPrivate *priv;
g_return_if_fail (GTK_IS_LIST_BOX (list_box));
priv = gtk_list_box_get_instance_private (list_box);
if (priv->drag_highlighted_row == NULL)
return;
gtk_drag_unhighlight (GTK_WIDGET (priv->drag_highlighted_row));
g_clear_object (&priv->drag_highlighted_row);
}
/**
* gtk_list_box_drag_highlight_row:
* @list_box: An #GtkListBox.
* @row: a #GtkListBoxRow
*
* This is a helper function for implementing DnD onto a #GtkListBox.
* The passed in @row will be highlighted via gtk_drag_highlight(),
* and any previously highlighted row will be unhighlighted.
*
* The row will also be unhighlighted when the widget gets
* a drag leave event.
*
* Since: 3.10
*/
void
gtk_list_box_drag_highlight_row (GtkListBox *list_box,
GtkListBoxRow *row)
{
GtkListBoxPrivate *priv;
g_return_if_fail (GTK_IS_LIST_BOX (list_box));
g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
priv = gtk_list_box_get_instance_private (list_box);
if (priv->drag_highlighted_row == row)
return;
gtk_list_box_drag_unhighlight_row (list_box);
gtk_drag_highlight (GTK_WIDGET (row));
priv->drag_highlighted_row = g_object_ref (row);
}
static void
gtk_list_box_drag_leave (GtkWidget *widget,
GdkDragContext *context,
guint time_)
{
GtkListBox *list_box = GTK_LIST_BOX (widget);
gtk_list_box_drag_unhighlight_row (list_box);
}
static void
gtk_list_box_activate_cursor_row (GtkListBox *list_box)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
gtk_list_box_select_and_activate (list_box, priv->cursor_row);
}
static void
gtk_list_box_toggle_cursor_row (GtkListBox *list_box)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
if (priv->cursor_row == NULL)
return;
if (priv->selection_mode == GTK_SELECTION_SINGLE &&
priv->selected_row == priv->cursor_row)
gtk_list_box_update_selected (list_box, NULL);
else
gtk_list_box_select_and_activate (list_box, priv->cursor_row);
}
static void
gtk_list_box_move_cursor (GtkListBox *list_box,
GtkMovementStep step,
gint count)
{
GtkListBoxPrivate *priv = gtk_list_box_get_instance_private (list_box);
GdkModifierType state;
gboolean modify_selection_pressed;
GtkListBoxRow *row;
GdkModifierType modify_mod_mask;
GtkListBoxRow *prev;
GtkListBoxRow *next;
gint page_size;
GSequenceIter *iter;
gint start_y;
gint end_y;
modify_selection_pressed = FALSE;
if (gtk_get_current_event_state (&state))
{
modify_mod_mask = gtk_widget_get_modifier_mask (GTK_WIDGET (list_box),
GDK_MODIFIER_INTENT_MODIFY_SELECTION);
if ((state & modify_mod_mask) == modify_mod_mask)
modify_selection_pressed = TRUE;
}
row = NULL;
switch (step)
{
case GTK_MOVEMENT_BUFFER_ENDS:
if (count < 0)
row = gtk_list_box_get_first_focusable (list_box);
else
row = gtk_list_box_get_last_focusable (list_box);
break;
case GTK_MOVEMENT_DISPLAY_LINES:
if (priv->cursor_row != NULL)
{
int i = count;
iter = ROW_PRIV (priv->cursor_row)->iter;
while (i < 0 && iter != NULL)
{
iter = gtk_list_box_get_previous_visible (list_box, iter);
i = i + 1;
}
while (i > 0 && iter != NULL)
{
iter = gtk_list_box_get_next_visible (list_box, iter);
i = i - 1;
}
if (iter != NULL && !g_sequence_iter_is_end (iter))
row = g_sequence_get (iter);
}
break;
case GTK_MOVEMENT_PAGES:
page_size = 100;
if (priv->adjustment != NULL)
page_size = gtk_adjustment_get_page_increment (priv->adjustment);
if (priv->cursor_row != NULL)
{
start_y = ROW_PRIV (priv->cursor_row)->y;
end_y = start_y;
iter = ROW_PRIV (priv->cursor_row)->iter;
row = priv->cursor_row;
if (count < 0)
{
/* Up */
while (iter != NULL && !g_sequence_iter_is_begin (iter))
{
iter = gtk_list_box_get_previous_visible (list_box, iter);
if (iter == NULL)
break;
prev = g_sequence_get (iter);
if (ROW_PRIV (prev)->y < start_y - page_size)
break;
row = prev;
}
}
else
{
/* Down */
while (iter != NULL && !g_sequence_iter_is_end (iter))
{
iter = gtk_list_box_get_next_visible (list_box, iter);
if (g_sequence_iter_is_end (iter))
break;
next = g_sequence_get (iter);
if (ROW_PRIV (next)->y > start_y + page_size)
break;
row = next;
}
}
end_y = ROW_PRIV (row)->y;
if (end_y != start_y && priv->adjustment != NULL)
gtk_adjustment_set_value (priv->adjustment,
gtk_adjustment_get_value (priv->adjustment) +
end_y - start_y);
}
break;
default:
return;
}
if (row == NULL || row == priv->cursor_row)
{
GtkDirectionType direction = count < 0 ? GTK_DIR_UP : GTK_DIR_DOWN;
if (!gtk_widget_keynav_failed (GTK_WIDGET (list_box), direction))
{
GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (list_box));
if (toplevel)
gtk_widget_child_focus (toplevel,
direction == GTK_DIR_UP ?
GTK_DIR_TAB_BACKWARD :
GTK_DIR_TAB_FORWARD);
}
return;
}
gtk_list_box_update_cursor (list_box, row);
if (!modify_selection_pressed)
gtk_list_box_update_selected (list_box, row);
}
/**
* gtk_list_box_row_new:
*
* Creates a new #GtkListBoxRow, to be used as a child of a #GtkListBox.
*
* Returns: a new #GtkListBoxRow
*
* Since: 3.10
*/
GtkWidget *
gtk_list_box_row_new (void)
{
return g_object_new (GTK_TYPE_LIST_BOX_ROW, NULL);
}
static void
gtk_list_box_row_init (GtkListBoxRow *row)
{
GtkStyleContext *context;
gtk_widget_set_can_focus (GTK_WIDGET (row), TRUE);
gtk_widget_set_redraw_on_allocate (GTK_WIDGET (row), TRUE);
context = gtk_widget_get_style_context (GTK_WIDGET (row));
gtk_style_context_add_class (context, GTK_STYLE_CLASS_LIST_ROW);
}
static GtkListBox *
gtk_list_box_row_get_box (GtkListBoxRow *row)
{
GtkWidget *parent;
parent = gtk_widget_get_parent (GTK_WIDGET (row));
if (parent && GTK_IS_LIST_BOX (parent))
return GTK_LIST_BOX (parent);
return NULL;
}
static void
gtk_list_box_row_set_focus (GtkListBoxRow *row)
{
GtkListBox *list_box = gtk_list_box_row_get_box (row);
GdkModifierType state = 0;
gboolean modify_selection_pressed;
if (!list_box)
return;
modify_selection_pressed = FALSE;
if (gtk_get_current_event_state (&state))
{
GdkModifierType modify_mod_mask;
modify_mod_mask =
gtk_widget_get_modifier_mask (GTK_WIDGET (list_box),
GDK_MODIFIER_INTENT_MODIFY_SELECTION);
if ((state & modify_mod_mask) == modify_mod_mask)
modify_selection_pressed = TRUE;
}
if (modify_selection_pressed)
gtk_list_box_update_cursor (list_box, row);
else
gtk_list_box_update_selected (list_box, row);
}
static gboolean
gtk_list_box_row_focus (GtkWidget *widget,
GtkDirectionType direction)
{
GtkListBoxRow *row = GTK_LIST_BOX_ROW (widget);
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 (row)) != 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 row */
if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD)
{
gtk_list_box_row_set_focus (row);
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_list_box_row_set_focus (row);
return TRUE;
}
}
static void
gtk_list_box_row_activate (GtkListBoxRow *row)
{
GtkListBox *list_box;
list_box = gtk_list_box_row_get_box (row);
if (list_box)
gtk_list_box_select_and_activate (list_box, row);
}
static void
gtk_list_box_row_show (GtkWidget *widget)
{
GtkListBoxRow *row = GTK_LIST_BOX_ROW (widget);
GtkListBox *list_box;
GTK_WIDGET_CLASS (gtk_list_box_row_parent_class)->show (widget);
list_box = gtk_list_box_row_get_box (row);
if (list_box)
gtk_list_box_row_visibility_changed (list_box, row);
}
static void
gtk_list_box_row_hide (GtkWidget *widget)
{
GtkListBoxRow *row = GTK_LIST_BOX_ROW (widget);
GtkListBox *list_box;
GTK_WIDGET_CLASS (gtk_list_box_row_parent_class)->hide (widget);
list_box = gtk_list_box_row_get_box (row);
if (list_box)
gtk_list_box_row_visibility_changed (list_box, row);
}
static gboolean
gtk_list_box_row_draw (GtkWidget *widget,
cairo_t *cr)
{
GtkListBoxRow *row = GTK_LIST_BOX_ROW (widget);
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, (gdouble) 0, (gdouble) 0, (gdouble) allocation.width, (gdouble) allocation.height);
gtk_render_frame (context, cr, (gdouble) 0, (gdouble) 0, (gdouble) allocation.width, (gdouble) allocation.height);
if (gtk_widget_has_visible_focus (GTK_WIDGET (row)))
{
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_list_box_row_parent_class)->draw (widget, cr);
return TRUE;
}
static void
gtk_list_box_row_get_full_border (GtkListBoxRow *row,
GtkBorder *full_border)
{
GtkWidget *widget = GTK_WIDGET (row);
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 void gtk_list_box_row_get_preferred_height_for_width (GtkWidget *widget,
gint width,
gint *minimum_height_out,
gint *natural_height_out);
static void gtk_list_box_row_get_preferred_width (GtkWidget *widget,
gint *minimum_width_out,
gint *natural_width_out);
static void
gtk_list_box_row_get_preferred_height (GtkWidget *widget,
gint *minimum_height,
gint *natural_height)
{
gint natural_width;
gtk_list_box_row_get_preferred_width (widget, NULL, &natural_width);
gtk_list_box_row_get_preferred_height_for_width (widget, natural_width,
minimum_height, natural_height);
}
static void
gtk_list_box_row_get_preferred_height_for_width (GtkWidget *widget,
gint width,
gint *minimum_height_out,
gint *natural_height_out)
{
GtkListBoxRow *row = GTK_LIST_BOX_ROW (widget);
GtkWidget *child;
gint child_min = 0, child_natural = 0;
GtkBorder full_border;
gtk_list_box_row_get_full_border (row, &full_border);
child = gtk_bin_get_child (GTK_BIN (row));
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_out)
*minimum_height_out = full_border.top + child_min + full_border.bottom;
if (natural_height_out)
*natural_height_out = full_border.top + child_natural + full_border.bottom;
}
static void
gtk_list_box_row_get_preferred_width (GtkWidget *widget,
gint *minimum_width_out,
gint *natural_width_out)
{
GtkListBoxRow *row = GTK_LIST_BOX_ROW (widget);
GtkWidget *child;
gint child_min = 0, child_natural = 0;
GtkBorder full_border;
gtk_list_box_row_get_full_border (row, &full_border);
child = gtk_bin_get_child (GTK_BIN (row));
if (child && gtk_widget_get_visible (child))
gtk_widget_get_preferred_width (child,
&child_min, &child_natural);
if (minimum_width_out)
*minimum_width_out = full_border.left + child_min + full_border.right;
if (natural_width_out)
*natural_width_out = full_border.left + child_natural + full_border.bottom;
}
static void
gtk_list_box_row_get_preferred_width_for_height (GtkWidget *widget,
gint height,
gint *minimum_width,
gint *natural_width)
{
gtk_list_box_row_get_preferred_width (widget, minimum_width, natural_width);
}
static void
gtk_list_box_row_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkListBoxRow *row = GTK_LIST_BOX_ROW (widget);
GtkWidget *child;
gtk_widget_set_allocation (widget, allocation);
child = gtk_bin_get_child (GTK_BIN (row));
if (child && gtk_widget_get_visible (child))
{
GtkAllocation child_allocation;
GtkBorder border;
gtk_list_box_row_get_full_border (row, &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);
}
}
/**
* gtk_list_box_row_changed:
* @row: a #GtkListBoxRow
*
* Marks @row as changed, causing any state that depends on this
* to be updated. This affects sorting, filtering and headers.
*
* Note that calls to this method must be in sync with the data
* used for the row functions. For instance, if the list is
* mirroring some external data set, and *two* rows changed in the
* external data set then when you call gtk_list_box_row_changed()
* on the first row the sort function must only read the new data
* for the first of the two changed rows, otherwise the resorting
* of the rows 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 listbox
* row functions into the row widgets themselves. Another alternative
* is to call gtk_list_box_invalidate_sort() on any model change,
* but that is more expensive.
*
* Since: 3.10
*/
void
gtk_list_box_row_changed (GtkListBoxRow *row)
{
GtkListBox *list_box;
g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
list_box = gtk_list_box_row_get_box (row);
if (list_box)
gtk_list_box_got_row_changed (GTK_LIST_BOX (list_box), row);
}
/**
* gtk_list_box_row_get_header:
* @row: a #GtkListBoxRow
*
* Returns the current header of the @row. This can be used
* in a #GtkListBoxUpdateHeaderFunc to see if there is a header
* set already, and if so to update the state of it.
*
* Return value: (transfer none): the current header, or %NULL if none
*
* Since: 3.10
*/
GtkWidget *
gtk_list_box_row_get_header (GtkListBoxRow *row)
{
g_return_val_if_fail (GTK_IS_LIST_BOX_ROW (row), NULL);
return ROW_PRIV (row)->header;
}
/**
* gtk_list_box_row_set_header:
* @row: a #GtkListBoxRow
* @header: (allow-none): the header, or %NULL
*
* Sets the current header of the @row. This is only allowed to be called
* from a #GtkListBoxUpdateHeaderFunc. It will replace any existing
* header in the row, and be shown in front of the row in the listbox.
*
* Since: 3.10
*/
void
gtk_list_box_row_set_header (GtkListBoxRow *row,
GtkWidget *header)
{
GtkListBoxRowPrivate *priv;
g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
g_return_if_fail (header == NULL || GTK_IS_WIDGET (header));
priv = ROW_PRIV (row);
if (priv->header)
g_object_unref (priv->header);
priv->header = header;
if (header)
g_object_ref_sink (header);
}
/**
* gtk_list_box_row_get_index:
* @row: a #GtkListBoxRow
*
* Gets the current index of the @row in its #GtkListBox container.
*
* Returns: the index of the @row, or -1 if the @row is not in a listbox
*
* Since: 3.10
*/
gint
gtk_list_box_row_get_index (GtkListBoxRow *row)
{
GtkListBoxRowPrivate *priv;
g_return_val_if_fail (GTK_IS_LIST_BOX_ROW (row), -1);
priv = ROW_PRIV (row);
if (priv->iter != NULL)
return g_sequence_iter_get_position (priv->iter);
return -1;
}
static void
gtk_list_box_row_finalize (GObject *obj)
{
g_clear_object (&ROW_PRIV (GTK_LIST_BOX_ROW (obj))->header);
G_OBJECT_CLASS (gtk_list_box_row_parent_class)->finalize (obj);
}
static void
gtk_list_box_row_class_init (GtkListBoxRowClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_LIST_BOX_ROW_ACCESSIBLE);
object_class->finalize = gtk_list_box_row_finalize;
widget_class->show = gtk_list_box_row_show;
widget_class->hide = gtk_list_box_row_hide;
widget_class->draw = gtk_list_box_row_draw;
widget_class->get_preferred_height = gtk_list_box_row_get_preferred_height;
widget_class->get_preferred_height_for_width = gtk_list_box_row_get_preferred_height_for_width;
widget_class->get_preferred_width = gtk_list_box_row_get_preferred_width;
widget_class->get_preferred_width_for_height = gtk_list_box_row_get_preferred_width_for_height;
widget_class->size_allocate = gtk_list_box_row_size_allocate;
widget_class->focus = gtk_list_box_row_focus;
klass->activate = gtk_list_box_row_activate;
row_signals[ROW__ACTIVATE] =
g_signal_new (I_("activate"),
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkListBoxRowClass, activate),
NULL, NULL,
_gtk_marshal_VOID__VOID,
G_TYPE_NONE, 0);
widget_class->activate_signal = row_signals[ROW__ACTIVATE];
}