forked from AuroraMiddleware/gtk
dd85acbaf1
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
2851 lines
91 KiB
C
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];
|
|
}
|