gtk2/gtk/gtklistview.c
Benjamin Otte a5949960bc listbase: Compute rubberband region on-demand
Instead of storing the active items as we go, compute the affected items
whenever the rubberband changes and in particular when the rubberband
ends.
That way, the rubberband is guaranteed to select a rectangle even
after scrolling very far.

This is achieved by having a get_items_in_rect() vfunc that selects all
the items in the rubberbanded rectangle and returns them as a bitset.
2020-06-26 07:13:32 +02:00

1178 lines
33 KiB
C

/*
* Copyright © 2018 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtklistview.h"
#include "gtkbitset.h"
#include "gtkintl.h"
#include "gtklistbaseprivate.h"
#include "gtklistitemmanagerprivate.h"
#include "gtkmain.h"
#include "gtkprivate.h"
#include "gtkrbtreeprivate.h"
#include "gtkstylecontext.h"
#include "gtkwidgetprivate.h"
/* Maximum number of list items created by the listview.
* For debugging, you can set this to G_MAXUINT to ensure
* there's always a list item for every row.
*/
#define GTK_LIST_VIEW_MAX_LIST_ITEMS 200
/* Extra items to keep above + below every tracker */
#define GTK_LIST_VIEW_EXTRA_ITEMS 2
/**
* SECTION:gtklistview
* @title: GtkListView
* @short_description: A widget for displaying lists
* @see_also: #GListModel, #GtkColumnView, #GtkGridView
*
* GtkListView is a widget to present a view into a large dynamic list of items.
*
* GtkListView uses its factory to generate one row widget for each visible item
* and shows them in a linear display, either vertically or horizontally. The
* #GtkListView:show-separators property offers a simple way to display separators
* between the rows.
*
* GtkListView allows the user to select items according to the selection
* characteristics of the model. If the provided model is not a #GtkSelectionModel,
* GtkListView will wrap it in a #GtkSingleSelection. For models that allow
* multiple selected items, it is possible to turn on _rubberband selection_,
* using #GtkListView:enable-rubberband.
*
* If you need multiple columns with headers, see #GtkColumnView.
*
* To learn more about the list widget framework, see the [overview](#ListWidget).
*
* An example of using GtkListView:
* |[
* static void
* setup_listitem_cb (GtkListItemFactory *factory,
* GtkListItem *list_item)
* {
* GtkWidget *image;
*
* image = gtk_image_new ();
* gtk_image_set_icon_size (GTK_IMAGE (image), GTK_ICON_SIZE_LARGE);
* gtk_list_item_set_child (list_item, image);
* }
*
* static void
* bind_listitem_cb (GtkListItemFactory *factory,
* GtkListItem *list_item)
* {
* GtkWidget *image;
* GAppInfo *app_info;
*
* image = gtk_list_item_get_child (list_item);
* app_info = gtk_list_item_get_item (list_item);
* gtk_image_set_from_gicon (GTK_IMAGE (image), g_app_info_get_icon (app_info));
* }
*
* static void
* activate_cb (GtkListView *list,
* guint position,
* gpointer unused)
* {
* GAppInfo *app_info;
*
* app_info = g_list_model_get_item (gtk_list_view_get_model (list), position);
* g_app_info_launch (app_info, NULL, NULL, NULL);
* g_object_unref (app_info);
* }
*
* ...
*
* factory = gtk_signal_list_item_factory_new ();
* g_signal_connect (factory, "setup", G_CALLBACK (setup_listitem_cb), NULL);
* g_signal_connect (factory, "bind", G_CALLBACK (bind_listitem_cb), NULL);
*
* list = gtk_list_view_new_with_factory (factory);
*
* g_signal_connect (list, "activate", G_CALLBACK (activate_cb), NULL);
*
* model = create_application_list ();
* gtk_list_view_set_model (GTK_LIST_VIEW (list), model);
* g_object_unref (model);
*
* gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), list);
* ]|
*
* # CSS nodes
*
* |[<!-- language="plain" -->
* listview[.separators]
* ├── row
* │
* ├── row
* │
* ┊
* ╰── [rubberband]
* ]|
*
* GtkListView uses a single CSS node named listview. It may carry the
* .separators style class, when #GtkListView:show-separators property
* is set. Each child widget uses a single CSS node named row. For
* rubberband selection, a node with name rubberband is used.
*/
typedef struct _ListRow ListRow;
typedef struct _ListRowAugment ListRowAugment;
struct _GtkListView
{
GtkListBase parent_instance;
GtkListItemManager *item_manager;
gboolean show_separators;
int list_width;
};
struct _GtkListViewClass
{
GtkListBaseClass parent_class;
};
struct _ListRow
{
GtkListItemManagerItem parent;
guint height; /* per row */
};
struct _ListRowAugment
{
GtkListItemManagerItemAugment parent;
guint height; /* total */
};
enum
{
PROP_0,
PROP_FACTORY,
PROP_MODEL,
PROP_SHOW_SEPARATORS,
PROP_SINGLE_CLICK_ACTIVATE,
PROP_ENABLE_RUBBERBAND,
N_PROPS
};
enum {
ACTIVATE,
LAST_SIGNAL
};
G_DEFINE_TYPE (GtkListView, gtk_list_view, GTK_TYPE_LIST_BASE)
static GParamSpec *properties[N_PROPS] = { NULL, };
static guint signals[LAST_SIGNAL] = { 0 };
static void G_GNUC_UNUSED
dump (GtkListView *self)
{
ListRow *row;
guint n_widgets, n_list_rows;
n_widgets = 0;
n_list_rows = 0;
//g_print ("ANCHOR: %u - %u\n", self->anchor_start, self->anchor_end);
for (row = gtk_list_item_manager_get_first (self->item_manager);
row;
row = gtk_rb_tree_node_get_next (row))
{
if (row->parent.widget)
n_widgets++;
n_list_rows++;
g_print (" %4u%s (%upx)\n", row->parent.n_items, row->parent.widget ? " (widget)" : "", row->height);
}
g_print (" => %u widgets in %u list rows\n", n_widgets, n_list_rows);
}
static void
list_row_augment (GtkRbTree *tree,
gpointer node_augment,
gpointer node,
gpointer left,
gpointer right)
{
ListRow *row = node;
ListRowAugment *aug = node_augment;
gtk_list_item_manager_augment_node (tree, node_augment, node, left, right);
aug->height = row->height * row->parent.n_items;
if (left)
{
ListRowAugment *left_aug = gtk_rb_tree_get_augment (tree, left);
aug->height += left_aug->height;
}
if (right)
{
ListRowAugment *right_aug = gtk_rb_tree_get_augment (tree, right);
aug->height += right_aug->height;
}
}
static ListRow *
gtk_list_view_get_row_at_y (GtkListView *self,
int y,
int *offset)
{
ListRow *row, *tmp;
row = gtk_list_item_manager_get_root (self->item_manager);
while (row)
{
tmp = gtk_rb_tree_node_get_left (row);
if (tmp)
{
ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, tmp);
if (y < aug->height)
{
row = tmp;
continue;
}
y -= aug->height;
}
if (y < row->height * row->parent.n_items)
break;
y -= row->height * row->parent.n_items;
row = gtk_rb_tree_node_get_right (row);
}
if (offset)
*offset = row ? y : 0;
return row;
}
static int
list_row_get_y (GtkListView *self,
ListRow *row)
{
ListRow *parent, *left;
int y;
left = gtk_rb_tree_node_get_left (row);
if (left)
{
ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, left);
y = aug->height;
}
else
y = 0;
for (parent = gtk_rb_tree_node_get_parent (row);
parent != NULL;
parent = gtk_rb_tree_node_get_parent (row))
{
left = gtk_rb_tree_node_get_left (parent);
if (left != row)
{
if (left)
{
ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, left);
y += aug->height;
}
y += parent->height * parent->parent.n_items;
}
row = parent;
}
return y ;
}
static int
gtk_list_view_get_list_height (GtkListView *self)
{
ListRow *row;
ListRowAugment *aug;
row = gtk_list_item_manager_get_root (self->item_manager);
if (row == NULL)
return 0;
aug = gtk_list_item_manager_get_item_augment (self->item_manager, row);
return aug->height;
}
static gboolean
gtk_list_view_get_allocation_along (GtkListBase *base,
guint pos,
int *offset,
int *size)
{
GtkListView *self = GTK_LIST_VIEW (base);
ListRow *row;
guint skip;
int y;
row = gtk_list_item_manager_get_nth (self->item_manager, pos, &skip);
if (row == NULL)
{
if (offset)
*offset = 0;
if (size)
*size = 0;
return FALSE;
}
y = list_row_get_y (self, row);
y += skip * row->height;
if (offset)
*offset = y;
if (size)
*size = row->height;
return TRUE;
}
static gboolean
gtk_list_view_get_allocation_across (GtkListBase *base,
guint pos,
int *offset,
int *size)
{
GtkListView *self = GTK_LIST_VIEW (base);
if (offset)
*offset = 0;
if (size)
*size = self->list_width;
return TRUE;
}
static GtkBitset *
gtk_list_view_get_items_in_rect (GtkListBase *base,
const cairo_rectangle_int_t *rect)
{
GtkListView *self = GTK_LIST_VIEW (base);
guint first, last, n_items;
GtkBitset *result;
ListRow *row;
result = gtk_bitset_new_empty ();
n_items = gtk_list_base_get_n_items (base);
if (n_items == 0)
return result;
row = gtk_list_view_get_row_at_y (self, rect->y, NULL);
if (row)
first = gtk_list_item_manager_get_item_position (self->item_manager, row);
else
first = rect->y < 0 ? 0 : n_items - 1;
row = gtk_list_view_get_row_at_y (self, rect->y + rect->height, NULL);
if (row)
last = gtk_list_item_manager_get_item_position (self->item_manager, row);
else
last = rect->y < 0 ? 0 : n_items - 1;
gtk_bitset_add_range_closed (result, first, last);
return result;
}
static guint
gtk_list_view_move_focus_along (GtkListBase *base,
guint pos,
int steps)
{
if (steps < 0)
return pos - MIN (pos, -steps);
else
{
pos += MIN (gtk_list_base_get_n_items (base) - pos - 1, steps);
}
return pos;
}
static gboolean
gtk_list_view_get_position_from_allocation (GtkListBase *base,
int across,
int along,
guint *pos,
cairo_rectangle_int_t *area)
{
GtkListView *self = GTK_LIST_VIEW (base);
ListRow *row;
int remaining;
if (across >= self->list_width)
return FALSE;
row = gtk_list_view_get_row_at_y (self, along, &remaining);
if (row == NULL)
return FALSE;
*pos = gtk_list_item_manager_get_item_position (self->item_manager, row);
g_assert (remaining < row->height * row->parent.n_items);
*pos += remaining / row->height;
if (area)
{
area->x = 0;
area->width = self->list_width;
area->y = along - remaining % row->height;
area->height = row->height;
}
return TRUE;
}
static guint
gtk_list_view_move_focus_across (GtkListBase *base,
guint pos,
int steps)
{
return pos;
}
static int
compare_ints (gconstpointer first,
gconstpointer second)
{
return *(int *) first - *(int *) second;
}
static guint
gtk_list_view_get_unknown_row_height (GtkListView *self,
GArray *heights)
{
g_return_val_if_fail (heights->len > 0, 0);
/* return the median and hope rows are generally uniform with few outliers */
g_array_sort (heights, compare_ints);
return g_array_index (heights, int, heights->len / 2);
}
static void
gtk_list_view_measure_across (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural)
{
GtkListView *self = GTK_LIST_VIEW (widget);
ListRow *row;
int min, nat, child_min, child_nat;
/* XXX: Figure out how to split a given height into per-row heights.
* Good luck! */
for_size = -1;
min = 0;
nat = 0;
for (row = gtk_list_item_manager_get_first (self->item_manager);
row != NULL;
row = gtk_rb_tree_node_get_next (row))
{
/* ignore unavailable rows */
if (row->parent.widget == NULL)
continue;
gtk_widget_measure (row->parent.widget,
orientation, for_size,
&child_min, &child_nat, NULL, NULL);
min = MAX (min, child_min);
nat = MAX (nat, child_nat);
}
*minimum = min;
*natural = nat;
}
static void
gtk_list_view_measure_list (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural)
{
GtkListView *self = GTK_LIST_VIEW (widget);
ListRow *row;
int min, nat, child_min, child_nat;
GArray *min_heights, *nat_heights;
guint n_unknown;
min_heights = g_array_new (FALSE, FALSE, sizeof (int));
nat_heights = g_array_new (FALSE, FALSE, sizeof (int));
n_unknown = 0;
min = 0;
nat = 0;
for (row = gtk_list_item_manager_get_first (self->item_manager);
row != NULL;
row = gtk_rb_tree_node_get_next (row))
{
if (row->parent.widget)
{
gtk_widget_measure (row->parent.widget,
orientation, for_size,
&child_min, &child_nat, NULL, NULL);
g_array_append_val (min_heights, child_min);
g_array_append_val (nat_heights, child_nat);
min += child_min;
nat += child_nat;
}
else
{
n_unknown += row->parent.n_items;
}
}
if (n_unknown)
{
min += n_unknown * gtk_list_view_get_unknown_row_height (self, min_heights);
nat += n_unknown * gtk_list_view_get_unknown_row_height (self, nat_heights);
}
g_array_free (min_heights, TRUE);
g_array_free (nat_heights, TRUE);
*minimum = min;
*natural = nat;
}
static void
gtk_list_view_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkListView *self = GTK_LIST_VIEW (widget);
if (orientation == gtk_list_base_get_orientation (GTK_LIST_BASE (self)))
gtk_list_view_measure_list (widget, orientation, for_size, minimum, natural);
else
gtk_list_view_measure_across (widget, orientation, for_size, minimum, natural);
}
static void
gtk_list_view_size_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkListView *self = GTK_LIST_VIEW (widget);
ListRow *row;
GArray *heights;
int min, nat, row_height;
int x, y;
GtkOrientation orientation, opposite_orientation;
GtkScrollablePolicy scroll_policy;
orientation = gtk_list_base_get_orientation (GTK_LIST_BASE (self));
opposite_orientation = OPPOSITE_ORIENTATION (orientation);
scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), orientation);
/* step 0: exit early if list is empty */
if (gtk_list_item_manager_get_root (self->item_manager) == NULL)
return;
/* step 1: determine width of the list */
gtk_widget_measure (widget, opposite_orientation,
-1,
&min, &nat, NULL, NULL);
self->list_width = orientation == GTK_ORIENTATION_VERTICAL ? width : height;
if (scroll_policy == GTK_SCROLL_MINIMUM)
self->list_width = MAX (min, self->list_width);
else
self->list_width = MAX (nat, self->list_width);
/* step 2: determine height of known list items */
heights = g_array_new (FALSE, FALSE, sizeof (int));
for (row = gtk_list_item_manager_get_first (self->item_manager);
row != NULL;
row = gtk_rb_tree_node_get_next (row))
{
if (row->parent.widget == NULL)
continue;
gtk_widget_measure (row->parent.widget, orientation,
self->list_width,
&min, &nat, NULL, NULL);
if (scroll_policy == GTK_SCROLL_MINIMUM)
row_height = min;
else
row_height = nat;
if (row->height != row_height)
{
row->height = row_height;
gtk_rb_tree_node_mark_dirty (row);
}
g_array_append_val (heights, row_height);
}
/* step 3: determine height of unknown items */
row_height = gtk_list_view_get_unknown_row_height (self, heights);
g_array_free (heights, TRUE);
for (row = gtk_list_item_manager_get_first (self->item_manager);
row != NULL;
row = gtk_rb_tree_node_get_next (row))
{
if (row->parent.widget)
continue;
if (row->height != row_height)
{
row->height = row_height;
gtk_rb_tree_node_mark_dirty (row);
}
}
/* step 3: update the adjustments */
gtk_list_base_update_adjustments (GTK_LIST_BASE (self),
self->list_width,
gtk_list_view_get_list_height (self),
gtk_widget_get_size (widget, opposite_orientation),
gtk_widget_get_size (widget, orientation),
&x, &y);
x = -x;
y = -y;
/* step 4: actually allocate the widgets */
for (row = gtk_list_item_manager_get_first (self->item_manager);
row != NULL;
row = gtk_rb_tree_node_get_next (row))
{
if (row->parent.widget)
{
gtk_list_base_size_allocate_child (GTK_LIST_BASE (self),
row->parent.widget,
x,
y,
self->list_width,
row->height);
}
y += row->height * row->parent.n_items;
}
gtk_list_base_allocate_rubberband (GTK_LIST_BASE (self));
}
static void
gtk_list_view_dispose (GObject *object)
{
GtkListView *self = GTK_LIST_VIEW (object);
self->item_manager = NULL;
G_OBJECT_CLASS (gtk_list_view_parent_class)->dispose (object);
}
static void
gtk_list_view_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkListView *self = GTK_LIST_VIEW (object);
switch (property_id)
{
case PROP_FACTORY:
g_value_set_object (value, gtk_list_item_manager_get_factory (self->item_manager));
break;
case PROP_MODEL:
g_value_set_object (value, gtk_list_base_get_model (GTK_LIST_BASE (self)));
break;
case PROP_SHOW_SEPARATORS:
g_value_set_boolean (value, self->show_separators);
break;
case PROP_SINGLE_CLICK_ACTIVATE:
g_value_set_boolean (value, gtk_list_item_manager_get_single_click_activate (self->item_manager));
break;
case PROP_ENABLE_RUBBERBAND:
g_value_set_boolean (value, gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_list_view_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkListView *self = GTK_LIST_VIEW (object);
switch (property_id)
{
case PROP_FACTORY:
gtk_list_view_set_factory (self, g_value_get_object (value));
break;
case PROP_MODEL:
gtk_list_view_set_model (self, g_value_get_object (value));
break;
case PROP_SHOW_SEPARATORS:
gtk_list_view_set_show_separators (self, g_value_get_boolean (value));
break;
case PROP_SINGLE_CLICK_ACTIVATE:
gtk_list_view_set_single_click_activate (self, g_value_get_boolean (value));
break;
case PROP_ENABLE_RUBBERBAND:
gtk_list_view_set_enable_rubberband (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_list_view_activate_item (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
GtkListView *self = GTK_LIST_VIEW (widget);
guint pos;
if (!g_variant_check_format_string (parameter, "u", FALSE))
return;
g_variant_get (parameter, "u", &pos);
if (pos >= gtk_list_base_get_n_items (GTK_LIST_BASE (self)))
return;
g_signal_emit (widget, signals[ACTIVATE], 0, pos);
}
static void
gtk_list_view_class_init (GtkListViewClass *klass)
{
GtkListBaseClass *list_base_class = GTK_LIST_BASE_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
list_base_class->list_item_name = "row";
list_base_class->list_item_size = sizeof (ListRow);
list_base_class->list_item_augment_size = sizeof (ListRowAugment);
list_base_class->list_item_augment_func = list_row_augment;
list_base_class->get_allocation_along = gtk_list_view_get_allocation_along;
list_base_class->get_allocation_across = gtk_list_view_get_allocation_across;
list_base_class->get_items_in_rect = gtk_list_view_get_items_in_rect;
list_base_class->get_position_from_allocation = gtk_list_view_get_position_from_allocation;
list_base_class->move_focus_along = gtk_list_view_move_focus_along;
list_base_class->move_focus_across = gtk_list_view_move_focus_across;
widget_class->measure = gtk_list_view_measure;
widget_class->size_allocate = gtk_list_view_size_allocate;
gobject_class->dispose = gtk_list_view_dispose;
gobject_class->get_property = gtk_list_view_get_property;
gobject_class->set_property = gtk_list_view_set_property;
/**
* GtkListView:factory:
*
* Factory for populating list items
*/
properties[PROP_FACTORY] =
g_param_spec_object ("factory",
P_("Factory"),
P_("Factory for populating list items"),
GTK_TYPE_LIST_ITEM_FACTORY,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListView:model:
*
* Model for the items displayed
*/
properties[PROP_MODEL] =
g_param_spec_object ("model",
P_("Model"),
P_("Model for the items displayed"),
G_TYPE_LIST_MODEL,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListView:show-separators:
*
* Show separators between rows
*/
properties[PROP_SHOW_SEPARATORS] =
g_param_spec_boolean ("show-separators",
P_("Show separators"),
P_("Show separators between rows"),
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkListView:single-click-activate:
*
* Activate rows on single click and select them on hover
*/
properties[PROP_SINGLE_CLICK_ACTIVATE] =
g_param_spec_boolean ("single-click-activate",
P_("Single click activate"),
P_("Activate rows on single click"),
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkListView:enable-rubberband:
*
* Allow rubberband selection
*/
properties[PROP_ENABLE_RUBBERBAND] =
g_param_spec_boolean ("enable-rubberband",
P_("Enable rubberband selection"),
P_("Allow selecting items by dragging with the mouse"),
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
/**
* GtkListView::activate:
* @self: The #GtkListView
* @position: position of item to activate
*
* The ::activate signal is emitted when a row has been activated by the user,
* usually via activating the GtkListView|list.activate-item action.
*
* This allows for a convenient way to handle activation in a listview.
* See gtk_list_item_set_activatable() for details on how to use this signal.
*/
signals[ACTIVATE] =
g_signal_new (I_("activate"),
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__UINT,
G_TYPE_NONE, 1,
G_TYPE_UINT);
g_signal_set_va_marshaller (signals[ACTIVATE],
G_TYPE_FROM_CLASS (gobject_class),
g_cclosure_marshal_VOID__UINTv);
/**
* GtkListView|list.activate-item:
* @position: position of item to activate
*
* Activates the item given in @position by emitting the GtkListView::activate
* signal.
*/
gtk_widget_class_install_action (widget_class,
"list.activate-item",
"u",
gtk_list_view_activate_item);
gtk_widget_class_set_css_name (widget_class, I_("listview"));
}
static void
gtk_list_view_init (GtkListView *self)
{
self->item_manager = gtk_list_base_get_manager (GTK_LIST_BASE (self));
gtk_list_base_set_anchor_max_widgets (GTK_LIST_BASE (self),
GTK_LIST_VIEW_MAX_LIST_ITEMS,
GTK_LIST_VIEW_EXTRA_ITEMS);
}
/**
* gtk_list_view_new:
*
* Creates a new empty #GtkListView.
*
* You most likely want to call gtk_list_view_set_factory() to
* set up a way to map its items to widgets and gtk_list_view_set_model()
* to set a model to provide items next.
*
* Returns: a new #GtkListView
**/
GtkWidget *
gtk_list_view_new (void)
{
return g_object_new (GTK_TYPE_LIST_VIEW, NULL);
}
/**
* gtk_list_view_new_with_factory:
* @factory: (transfer full): The factory to populate items with
*
* Creates a new #GtkListView that uses the given @factory for
* mapping items to widgets.
*
* You most likely want to call gtk_list_view_set_model() to set
* a model next.
*
* The function takes ownership of the
* argument, so you can write code like
* ```
* list_view = gtk_list_view_new_with_factory (
* gtk_builder_list_item_factory_newfrom_resource ("/resource.ui"));
* ```
*
* Returns: a new #GtkListView using the given @factory
**/
GtkWidget *
gtk_list_view_new_with_factory (GtkListItemFactory *factory)
{
GtkWidget *result;
g_return_val_if_fail (GTK_IS_LIST_ITEM_FACTORY (factory), NULL);
result = g_object_new (GTK_TYPE_LIST_VIEW,
"factory", factory,
NULL);
g_object_unref (factory);
return result;
}
/**
* gtk_list_view_get_model:
* @self: a #GtkListView
*
* Gets the model that's currently used to read the items displayed.
*
* Returns: (nullable) (transfer none): The model in use
**/
GListModel *
gtk_list_view_get_model (GtkListView *self)
{
g_return_val_if_fail (GTK_IS_LIST_VIEW (self), NULL);
return gtk_list_base_get_model (GTK_LIST_BASE (self));
}
/**
* gtk_list_view_set_model:
* @self: a #GtkListView
* @model: (allow-none) (transfer none): the model to use or %NULL for none
*
* Sets the #GListModel to use.
*
* If the @model is a #GtkSelectionModel, it is used for managing the selection.
* Otherwise, @self creates a #GtkSingleSelection for the selection.
**/
void
gtk_list_view_set_model (GtkListView *self,
GListModel *model)
{
g_return_if_fail (GTK_IS_LIST_VIEW (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
if (!gtk_list_base_set_model (GTK_LIST_BASE (self), model))
return;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}
/**
* gtk_list_view_get_factory:
* @self: a #GtkListView
*
* Gets the factory that's currently used to populate list items.
*
* Returns: (nullable) (transfer none): The factory in use
**/
GtkListItemFactory *
gtk_list_view_get_factory (GtkListView *self)
{
g_return_val_if_fail (GTK_IS_LIST_VIEW (self), NULL);
return gtk_list_item_manager_get_factory (self->item_manager);
}
/**
* gtk_list_view_set_factory:
* @self: a #GtkListView
* @factory: (allow-none) (transfer none): the factory to use or %NULL for none
*
* Sets the #GtkListItemFactory to use for populating list items.
**/
void
gtk_list_view_set_factory (GtkListView *self,
GtkListItemFactory *factory)
{
g_return_if_fail (GTK_IS_LIST_VIEW (self));
g_return_if_fail (factory == NULL || GTK_LIST_ITEM_FACTORY (factory));
if (factory == gtk_list_item_manager_get_factory (self->item_manager))
return;
gtk_list_item_manager_set_factory (self->item_manager, factory);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
}
/**
* gtk_list_view_set_show_separators:
* @self: a #GtkListView
* @show_separators: %TRUE to show separators
*
* Sets whether the list box should show separators
* between rows.
*/
void
gtk_list_view_set_show_separators (GtkListView *self,
gboolean show_separators)
{
g_return_if_fail (GTK_IS_LIST_VIEW (self));
if (self->show_separators == show_separators)
return;
self->show_separators = show_separators;
if (show_separators)
gtk_widget_add_css_class (GTK_WIDGET (self), "separators");
else
gtk_widget_remove_css_class (GTK_WIDGET (self), "separators");
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_SEPARATORS]);
}
/**
* gtk_list_view_get_show_separators:
* @self: a #GtkListView
*
* Returns whether the list box should show separators
* between rows.
*
* Returns: %TRUE if the list box shows separators
*/
gboolean
gtk_list_view_get_show_separators (GtkListView *self)
{
g_return_val_if_fail (GTK_IS_LIST_VIEW (self), FALSE);
return self->show_separators;
}
/**
* gtk_list_view_set_single_click_activate:
* @self: a #GtkListView
* @single_click_activate: %TRUE to activate items on single click
*
* Sets whether rows should be activated on single click and
* selected on hover.
*/
void
gtk_list_view_set_single_click_activate (GtkListView *self,
gboolean single_click_activate)
{
g_return_if_fail (GTK_IS_LIST_VIEW (self));
if (single_click_activate == gtk_list_item_manager_get_single_click_activate (self->item_manager))
return;
gtk_list_item_manager_set_single_click_activate (self->item_manager, single_click_activate);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SINGLE_CLICK_ACTIVATE]);
}
/**
* gtk_list_view_get_single_click_activate:
* @self: a #GtkListView
*
* Returns whether rows will be activated on single click and
* selected on hover.
*
* Returns: %TRUE if rows are activated on single click
*/
gboolean
gtk_list_view_get_single_click_activate (GtkListView *self)
{
g_return_val_if_fail (GTK_IS_LIST_VIEW (self), FALSE);
return gtk_list_item_manager_get_single_click_activate (self->item_manager);
}
/**
* gtk_list_view_set_enable_rubberband:
* @self: a #GtkListView
* @enable_rubberband: %TRUE to enable rubberband selection
*
* Sets whether selections can be changed by dragging with the mouse.
*/
void
gtk_list_view_set_enable_rubberband (GtkListView *self,
gboolean enable_rubberband)
{
g_return_if_fail (GTK_IS_LIST_VIEW (self));
if (enable_rubberband == gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self)))
return;
gtk_list_base_set_enable_rubberband (GTK_LIST_BASE (self), enable_rubberband);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLE_RUBBERBAND]);
}
/**
* gtk_list_view_get_enable_rubberband:
* @self: a #GtkListView
*
* Returns whether rows can be selected by dragging with the mouse.
*
* Returns: %TRUE if rubberband selection is enabled
*/
gboolean
gtk_list_view_get_enable_rubberband (GtkListView *self)
{
g_return_val_if_fail (GTK_IS_LIST_VIEW (self), FALSE);
return gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self));
}