1/*
2 * Copyright (C) 2012 Alexander Larsson <alexl@redhat.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19
20#include "gtklistbox.h"
21
22#include "gtkaccessible.h"
23#include "gtkactionhelperprivate.h"
24#include "gtkadjustmentprivate.h"
25#include "gtkbinlayout.h"
26#include "gtkbuildable.h"
27#include "gtkgestureclick.h"
28#include "gtkintl.h"
29#include "gtkmain.h"
30#include "gtkmarshalers.h"
31#include "gtkprivate.h"
32#include "gtkscrollable.h"
33#include "gtktypebuiltins.h"
34#include "gtkwidgetprivate.h"
35
36#include <float.h>
37#include <math.h>
38#include <string.h>
39
40/**
41 * GtkListBox:
42 *
43 * `GtkListBox` is a vertical list.
44 *
45 * A `GtkListBox` only contains `GtkListBoxRow` children. These rows can
46 * by dynamically sorted and filtered, and headers can be added dynamically
47 * depending on the row content. It also allows keyboard and mouse navigation
48 * and selection like a typical list.
49 *
50 * Using `GtkListBox` is often an alternative to `GtkTreeView`, especially
51 * when the list contents has a more complicated layout than what is allowed
52 * by a `GtkCellRenderer`, or when the contents is interactive (i.e. has a
53 * button in it).
54 *
55 * Although a `GtkListBox` must have only `GtkListBoxRow` children, you can
56 * add any kind of widget to it via [method@Gtk.ListBox.prepend],
57 * [method@Gtk.ListBox.append] and [method@Gtk.ListBox.insert] and a
58 * `GtkListBoxRow` widget will automatically be inserted between the list
59 * and the widget.
60 *
61 * `GtkListBoxRows` can be marked as activatable or selectable. If a row is
62 * activatable, [signal@Gtk.ListBox::row-activated] will be emitted for it when
63 * the user tries to activate it. If it is selectable, the row will be marked
64 * as selected when the user tries to select it.
65 *
66 * # GtkListBox as GtkBuildable
67 *
68 * The `GtkListBox` implementation of the `GtkBuildable` interface supports
69 * setting a child as the placeholder by specifying “placeholder” as the “type”
70 * attribute of a <child> element. See [method@Gtk.ListBox.set_placeholder]
71 * for info.
72 *
73 * # CSS nodes
74 *
75 * |[<!-- language="plain" -->
76 * list[.separators][.rich-list][.navigation-sidebar]
77 * ╰── row[.activatable]
78 * ]|
79 *
80 * `GtkListBox` uses a single CSS node named list. It may carry the .separators
81 * style class, when the [property@Gtk.ListBox:show-separators] property is set.
82 * Each `GtkListBoxRow` uses a single CSS node named row. The row nodes get the
83 * .activatable style class added when appropriate.
84 *
85 * The main list node may also carry style classes to select
86 * the style of [list presentation](section-list-widget.html#list-styles):
87 * .rich-list, .navigation-sidebar or .data-table.
88 *
89 * # Accessibility
90 *
91 * `GtkListBox` uses the %GTK_ACCESSIBLE_ROLE_LIST role and `GtkListBoxRow` uses
92 * the %GTK_ACCESSIBLE_ROLE_LIST_ITEM role.
93 */
94
95/**
96 * GtkListBoxRow:
97 *
98 * `GtkListBoxRow` is the kind of widget that can be added to a `GtkListBox`.
99 */
100
101typedef struct _GtkListBoxClass GtkListBoxClass;
102
103struct _GtkListBox
104{
105 GtkWidget parent_instance;
106
107 GSequence *children;
108 GHashTable *header_hash;
109
110 GtkWidget *placeholder;
111
112 GtkListBoxSortFunc sort_func;
113 gpointer sort_func_target;
114 GDestroyNotify sort_func_target_destroy_notify;
115
116 GtkListBoxFilterFunc filter_func;
117 gpointer filter_func_target;
118 GDestroyNotify filter_func_target_destroy_notify;
119
120 GtkListBoxUpdateHeaderFunc update_header_func;
121 gpointer update_header_func_target;
122 GDestroyNotify update_header_func_target_destroy_notify;
123
124 GtkListBoxRow *selected_row;
125 GtkListBoxRow *cursor_row;
126
127 GtkListBoxRow *active_row;
128
129 GtkSelectionMode selection_mode;
130
131 gulong adjustment_changed_id;
132 GtkWidget *scrollable_parent;
133 GtkAdjustment *adjustment;
134 gboolean activate_single_click;
135 gboolean accept_unpaired_release;
136 gboolean show_separators;
137
138 /* DnD */
139 GtkListBoxRow *drag_highlighted_row;
140
141 int n_visible_rows;
142
143 GListModel *bound_model;
144 GtkListBoxCreateWidgetFunc create_widget_func;
145 gpointer create_widget_func_data;
146 GDestroyNotify create_widget_func_data_destroy;
147};
148
149struct _GtkListBoxClass
150{
151 GtkWidgetClass parent_class;
152
153 void (*row_selected) (GtkListBox *box,
154 GtkListBoxRow *row);
155 void (*row_activated) (GtkListBox *box,
156 GtkListBoxRow *row);
157 void (*activate_cursor_row) (GtkListBox *box);
158 void (*toggle_cursor_row) (GtkListBox *box);
159 void (*move_cursor) (GtkListBox *box,
160 GtkMovementStep step,
161 int count,
162 gboolean extend,
163 gboolean modify);
164 void (*selected_rows_changed) (GtkListBox *box);
165 void (*select_all) (GtkListBox *box);
166 void (*unselect_all) (GtkListBox *box);
167};
168
169typedef struct
170{
171 GtkWidget *child;
172 GSequenceIter *iter;
173 GtkWidget *header;
174 GtkActionHelper *action_helper;
175 int y;
176 int height;
177 guint visible :1;
178 guint selected :1;
179 guint activatable :1;
180 guint selectable :1;
181} GtkListBoxRowPrivate;
182
183enum {
184 ROW_SELECTED,
185 ROW_ACTIVATED,
186 ACTIVATE_CURSOR_ROW,
187 TOGGLE_CURSOR_ROW,
188 MOVE_CURSOR,
189 SELECTED_ROWS_CHANGED,
190 SELECT_ALL,
191 UNSELECT_ALL,
192 LAST_SIGNAL
193};
194
195enum {
196 ROW__ACTIVATE,
197 ROW__LAST_SIGNAL
198};
199
200enum {
201 PROP_0,
202 PROP_SELECTION_MODE,
203 PROP_ACTIVATE_ON_SINGLE_CLICK,
204 PROP_ACCEPT_UNPAIRED_RELEASE,
205 PROP_SHOW_SEPARATORS,
206 LAST_PROPERTY
207};
208
209enum {
210 ROW_PROP_0,
211 ROW_PROP_ACTIVATABLE,
212 ROW_PROP_SELECTABLE,
213 ROW_PROP_CHILD,
214
215 /* actionable properties */
216 ROW_PROP_ACTION_NAME,
217 ROW_PROP_ACTION_TARGET,
218
219 LAST_ROW_PROPERTY = ROW_PROP_ACTION_NAME
220};
221
222#define ROW_PRIV(row) ((GtkListBoxRowPrivate*)gtk_list_box_row_get_instance_private ((GtkListBoxRow*)(row)))
223
224static GtkBuildableIface *parent_buildable_iface;
225
226static void gtk_list_box_buildable_interface_init (GtkBuildableIface *iface);
227
228static void gtk_list_box_row_buildable_iface_init (GtkBuildableIface *iface);
229static void gtk_list_box_row_actionable_iface_init (GtkActionableInterface *iface);
230
231G_DEFINE_TYPE_WITH_CODE (GtkListBox, gtk_list_box, GTK_TYPE_WIDGET,
232 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
233 gtk_list_box_buildable_interface_init))
234G_DEFINE_TYPE_WITH_CODE (GtkListBoxRow, gtk_list_box_row, GTK_TYPE_WIDGET,
235 G_ADD_PRIVATE (GtkListBoxRow)
236 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
237 gtk_list_box_row_buildable_iface_init )
238 G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIONABLE, gtk_list_box_row_actionable_iface_init))
239
240static void gtk_list_box_apply_filter_all (GtkListBox *box);
241static void gtk_list_box_update_header (GtkListBox *box,
242 GSequenceIter *iter);
243static GSequenceIter * gtk_list_box_get_next_visible (GtkListBox *box,
244 GSequenceIter *iter);
245static void gtk_list_box_apply_filter (GtkListBox *box,
246 GtkListBoxRow *row);
247static void gtk_list_box_add_move_binding (GtkWidgetClass *widget_class,
248 guint keyval,
249 GdkModifierType modmask,
250 GtkMovementStep step,
251 int count);
252static void gtk_list_box_update_cursor (GtkListBox *box,
253 GtkListBoxRow *row,
254 gboolean grab_focus);
255static void gtk_list_box_show (GtkWidget *widget);
256static gboolean gtk_list_box_focus (GtkWidget *widget,
257 GtkDirectionType direction);
258static GSequenceIter* gtk_list_box_get_previous_visible (GtkListBox *box,
259 GSequenceIter *iter);
260static GtkListBoxRow *gtk_list_box_get_first_focusable (GtkListBox *box);
261static GtkListBoxRow *gtk_list_box_get_last_focusable (GtkListBox *box);
262static void gtk_list_box_compute_expand (GtkWidget *widget,
263 gboolean *hexpand,
264 gboolean *vexpand);
265static GtkSizeRequestMode gtk_list_box_get_request_mode (GtkWidget *widget);
266static void gtk_list_box_size_allocate (GtkWidget *widget,
267 int width,
268 int height,
269 int baseline);
270static void gtk_list_box_activate_cursor_row (GtkListBox *box);
271static void gtk_list_box_toggle_cursor_row (GtkListBox *box);
272static void gtk_list_box_move_cursor (GtkListBox *box,
273 GtkMovementStep step,
274 int count,
275 gboolean extend,
276 gboolean modify);
277static void gtk_list_box_parent_cb (GObject *object,
278 GParamSpec *pspec,
279 gpointer user_data);
280static void gtk_list_box_select_row_internal (GtkListBox *box,
281 GtkListBoxRow *row);
282static void gtk_list_box_unselect_row_internal (GtkListBox *box,
283 GtkListBoxRow *row);
284static void gtk_list_box_select_all_between (GtkListBox *box,
285 GtkListBoxRow *row1,
286 GtkListBoxRow *row2,
287 gboolean modify);
288static gboolean gtk_list_box_unselect_all_internal (GtkListBox *box);
289static void gtk_list_box_selected_rows_changed (GtkListBox *box);
290static void gtk_list_box_set_accept_unpaired_release (GtkListBox *box,
291 gboolean accept);
292
293static void gtk_list_box_click_gesture_pressed (GtkGestureClick *gesture,
294 guint n_press,
295 double x,
296 double y,
297 GtkListBox *box);
298static void gtk_list_box_click_gesture_released (GtkGestureClick *gesture,
299 guint n_press,
300 double x,
301 double y,
302 GtkListBox *box);
303static void gtk_list_box_click_unpaired_release (GtkGestureClick *gesture,
304 double x,
305 double y,
306 guint button,
307 GdkEventSequence *sequence,
308 GtkListBox *box);
309static void gtk_list_box_click_gesture_stopped (GtkGestureClick *gesture,
310 GtkListBox *box);
311
312static void gtk_list_box_update_row_styles (GtkListBox *box);
313static void gtk_list_box_update_row_style (GtkListBox *box,
314 GtkListBoxRow *row);
315
316static void gtk_list_box_bound_model_changed (GListModel *list,
317 guint position,
318 guint removed,
319 guint added,
320 gpointer user_data);
321
322static void gtk_list_box_check_model_compat (GtkListBox *box);
323
324static void gtk_list_box_measure (GtkWidget *widget,
325 GtkOrientation orientation,
326 int for_size,
327 int *minimum,
328 int *natural,
329 int *minimum_baseline,
330 int *natural_baseline);
331
332
333
334static GParamSpec *properties[LAST_PROPERTY] = { NULL, };
335static guint signals[LAST_SIGNAL] = { 0 };
336static GParamSpec *row_properties[LAST_ROW_PROPERTY] = { NULL, };
337static guint row_signals[ROW__LAST_SIGNAL] = { 0 };
338
339
340static GtkBuildableIface *parent_row_buildable_iface;
341
342static void
343gtk_list_box_row_buildable_add_child (GtkBuildable *buildable,
344 GtkBuilder *builder,
345 GObject *child,
346 const char *type)
347{
348 if (GTK_IS_WIDGET (child))
349 gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (buildable), GTK_WIDGET (child));
350 else
351 parent_row_buildable_iface->add_child (buildable, builder, child, type);
352}
353
354static void
355gtk_list_box_row_buildable_iface_init (GtkBuildableIface *iface)
356{
357 parent_row_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
358
359 iface->add_child = gtk_list_box_row_buildable_add_child;
360}
361
362/**
363 * gtk_list_box_new:
364 *
365 * Creates a new `GtkListBox` container.
366 *
367 * Returns: a new `GtkListBox`
368 */
369GtkWidget *
370gtk_list_box_new (void)
371{
372 return g_object_new (GTK_TYPE_LIST_BOX, NULL);
373}
374
375static void
376gtk_list_box_get_property (GObject *obj,
377 guint property_id,
378 GValue *value,
379 GParamSpec *pspec)
380{
381 GtkListBox *box = GTK_LIST_BOX (obj);
382
383 switch (property_id)
384 {
385 case PROP_SELECTION_MODE:
386 g_value_set_enum (value, v_enum: box->selection_mode);
387 break;
388 case PROP_ACTIVATE_ON_SINGLE_CLICK:
389 g_value_set_boolean (value, v_boolean: box->activate_single_click);
390 break;
391 case PROP_ACCEPT_UNPAIRED_RELEASE:
392 g_value_set_boolean (value, v_boolean: box->accept_unpaired_release);
393 break;
394 case PROP_SHOW_SEPARATORS:
395 g_value_set_boolean (value, v_boolean: box->show_separators);
396 break;
397 default:
398 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
399 break;
400 }
401}
402
403static void
404gtk_list_box_set_property (GObject *obj,
405 guint property_id,
406 const GValue *value,
407 GParamSpec *pspec)
408{
409 GtkListBox *box = GTK_LIST_BOX (obj);
410
411 switch (property_id)
412 {
413 case PROP_SELECTION_MODE:
414 gtk_list_box_set_selection_mode (box, mode: g_value_get_enum (value));
415 break;
416 case PROP_ACTIVATE_ON_SINGLE_CLICK:
417 gtk_list_box_set_activate_on_single_click (box, single: g_value_get_boolean (value));
418 break;
419 case PROP_ACCEPT_UNPAIRED_RELEASE:
420 gtk_list_box_set_accept_unpaired_release (box, accept: g_value_get_boolean (value));
421 break;
422 case PROP_SHOW_SEPARATORS:
423 gtk_list_box_set_show_separators (box, show_separators: g_value_get_boolean (value));
424 break;
425 default:
426 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
427 break;
428 }
429}
430
431static void
432gtk_list_box_dispose (GObject *object)
433{
434 GtkWidget *child;
435
436 while ((child = gtk_widget_get_first_child (GTK_WIDGET (object))))
437 gtk_list_box_remove (GTK_LIST_BOX (object), child);
438
439 G_OBJECT_CLASS (gtk_list_box_parent_class)->dispose (object);
440}
441
442static void
443gtk_list_box_finalize (GObject *obj)
444{
445 GtkListBox *box = GTK_LIST_BOX (obj);
446
447 if (box->sort_func_target_destroy_notify != NULL)
448 box->sort_func_target_destroy_notify (box->sort_func_target);
449 if (box->filter_func_target_destroy_notify != NULL)
450 box->filter_func_target_destroy_notify (box->filter_func_target);
451 if (box->update_header_func_target_destroy_notify != NULL)
452 box->update_header_func_target_destroy_notify (box->update_header_func_target);
453
454 g_clear_object (&box->adjustment);
455 g_clear_object (&box->drag_highlighted_row);
456
457 g_sequence_free (seq: box->children);
458 g_hash_table_unref (hash_table: box->header_hash);
459
460 if (box->bound_model)
461 {
462 if (box->create_widget_func_data_destroy)
463 box->create_widget_func_data_destroy (box->create_widget_func_data);
464
465 g_signal_handlers_disconnect_by_func (box->bound_model, gtk_list_box_bound_model_changed, obj);
466 g_clear_object (&box->bound_model);
467 }
468
469 G_OBJECT_CLASS (gtk_list_box_parent_class)->finalize (obj);
470}
471
472static void
473gtk_list_box_class_init (GtkListBoxClass *klass)
474{
475 GObjectClass *object_class = G_OBJECT_CLASS (klass);
476 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
477
478 object_class->get_property = gtk_list_box_get_property;
479 object_class->set_property = gtk_list_box_set_property;
480 object_class->dispose = gtk_list_box_dispose;
481 object_class->finalize = gtk_list_box_finalize;
482
483 widget_class->show = gtk_list_box_show;
484 widget_class->focus = gtk_list_box_focus;
485 widget_class->grab_focus = gtk_widget_grab_focus_self;
486 widget_class->compute_expand = gtk_list_box_compute_expand;
487 widget_class->get_request_mode = gtk_list_box_get_request_mode;
488 widget_class->measure = gtk_list_box_measure;
489 widget_class->size_allocate = gtk_list_box_size_allocate;
490 klass->activate_cursor_row = gtk_list_box_activate_cursor_row;
491 klass->toggle_cursor_row = gtk_list_box_toggle_cursor_row;
492 klass->move_cursor = gtk_list_box_move_cursor;
493 klass->select_all = gtk_list_box_select_all;
494 klass->unselect_all = gtk_list_box_unselect_all;
495 klass->selected_rows_changed = gtk_list_box_selected_rows_changed;
496
497 /**
498 * GtkListBox:selection-mode: (attributes org.gtk.Property.get=gtk_list_box_get_selection_mode org.gtk.Property.set=gtk_list_box_set_selection_mode)
499 *
500 * The selection mode used by the list box.
501 */
502 properties[PROP_SELECTION_MODE] =
503 g_param_spec_enum (name: "selection-mode",
504 P_("Selection mode"),
505 P_("The selection mode"),
506 enum_type: GTK_TYPE_SELECTION_MODE,
507 default_value: GTK_SELECTION_SINGLE,
508 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
509
510 /**
511 * GtkListBox:activate-on-single-click: (attributes org.gtk.Property.get=gtk_list_box_get_activate_on_single_click org.gtk.Property.set=gtk_list_box_set_activate_on_single_click)
512 *
513 * Determines whether children can be activated with a single
514 * click, or require a double-click.
515 */
516 properties[PROP_ACTIVATE_ON_SINGLE_CLICK] =
517 g_param_spec_boolean (name: "activate-on-single-click",
518 P_("Activate on Single Click"),
519 P_("Activate row on a single click"),
520 TRUE,
521 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
522
523 /**
524 * GtkListBox:accept-unpaired-release:
525 *
526 * Whether to accept unpaired release events.
527 */
528 properties[PROP_ACCEPT_UNPAIRED_RELEASE] =
529 g_param_spec_boolean (name: "accept-unpaired-release",
530 P_("Accept unpaired release"),
531 P_("Accept unpaired release"),
532 FALSE,
533 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
534
535
536 /**
537 * GtkListBox:show-separators: (attributes org.gtk.Property.get=gtk_list_box_get_show_separators org.gtk.Property.set=gtk_list_box_set_show_separators)
538 *
539 * Whether to show separators between rows.
540 */
541 properties[PROP_SHOW_SEPARATORS] =
542 g_param_spec_boolean (name: "show-separators",
543 P_("Show separators"),
544 P_("Show separators between rows"),
545 FALSE,
546 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
547
548 g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROPERTY, pspecs: properties);
549
550 /**
551 * GtkListBox::row-selected:
552 * @box: the `GtkListBox`
553 * @row: (nullable): the selected row
554 *
555 * Emitted when a new row is selected, or (with a %NULL @row)
556 * when the selection is cleared.
557 *
558 * When the @box is using %GTK_SELECTION_MULTIPLE, this signal will not
559 * give you the full picture of selection changes, and you should use
560 * the [signal@Gtk.ListBox::selected-rows-changed] signal instead.
561 */
562 signals[ROW_SELECTED] =
563 g_signal_new (I_("row-selected"),
564 GTK_TYPE_LIST_BOX,
565 signal_flags: G_SIGNAL_RUN_LAST,
566 G_STRUCT_OFFSET (GtkListBoxClass, row_selected),
567 NULL, NULL,
568 NULL,
569 G_TYPE_NONE, n_params: 1,
570 GTK_TYPE_LIST_BOX_ROW);
571
572 /**
573 * GtkListBox::selected-rows-changed:
574 * @box: the `GtkListBox` on which the signal is emitted
575 *
576 * Emitted when the set of selected rows changes.
577 */
578 signals[SELECTED_ROWS_CHANGED] = g_signal_new (I_("selected-rows-changed"),
579 GTK_TYPE_LIST_BOX,
580 signal_flags: G_SIGNAL_RUN_FIRST,
581 G_STRUCT_OFFSET (GtkListBoxClass, selected_rows_changed),
582 NULL, NULL,
583 NULL,
584 G_TYPE_NONE, n_params: 0);
585
586 /**
587 * GtkListBox::select-all:
588 * @box: the `GtkListBox` on which the signal is emitted
589 *
590 * Emitted to select all children of the box, if the selection
591 * mode permits it.
592 *
593 * This is a [keybinding signal](class.SignalAction.html).
594 *
595 * The default binding for this signal is <kbd>Ctrl</kbd>-<kbd>a</kbd>.
596 */
597 signals[SELECT_ALL] = g_signal_new (I_("select-all"),
598 GTK_TYPE_LIST_BOX,
599 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
600 G_STRUCT_OFFSET (GtkListBoxClass, select_all),
601 NULL, NULL,
602 NULL,
603 G_TYPE_NONE, n_params: 0);
604
605 /**
606 * GtkListBox::unselect-all:
607 * @box: the `GtkListBox` on which the signal is emitted
608 *
609 * Emitted to unselect all children of the box, if the selection
610 * mode permits it.
611 *
612 * This is a [keybinding signal](class.SignalAction.html).
613 *
614 * The default binding for this signal is
615 * <kbd>Ctrl</kbd>-<kbd>Shift</kbd>-<kbd>a</kbd>.
616 */
617 signals[UNSELECT_ALL] = g_signal_new (I_("unselect-all"),
618 GTK_TYPE_LIST_BOX,
619 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
620 G_STRUCT_OFFSET (GtkListBoxClass, unselect_all),
621 NULL, NULL,
622 NULL,
623 G_TYPE_NONE, n_params: 0);
624
625 /**
626 * GtkListBox::row-activated:
627 * @box: the `GtkListBox`
628 * @row: the activated row
629 *
630 * Emitted when a row has been activated by the user.
631 */
632 signals[ROW_ACTIVATED] =
633 g_signal_new (I_("row-activated"),
634 GTK_TYPE_LIST_BOX,
635 signal_flags: G_SIGNAL_RUN_LAST,
636 G_STRUCT_OFFSET (GtkListBoxClass, row_activated),
637 NULL, NULL,
638 NULL,
639 G_TYPE_NONE, n_params: 1,
640 GTK_TYPE_LIST_BOX_ROW);
641 signals[ACTIVATE_CURSOR_ROW] =
642 g_signal_new (I_("activate-cursor-row"),
643 GTK_TYPE_LIST_BOX,
644 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
645 G_STRUCT_OFFSET (GtkListBoxClass, activate_cursor_row),
646 NULL, NULL,
647 NULL,
648 G_TYPE_NONE, n_params: 0);
649 signals[TOGGLE_CURSOR_ROW] =
650 g_signal_new (I_("toggle-cursor-row"),
651 GTK_TYPE_LIST_BOX,
652 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
653 G_STRUCT_OFFSET (GtkListBoxClass, toggle_cursor_row),
654 NULL, NULL,
655 NULL,
656 G_TYPE_NONE, n_params: 0);
657 signals[MOVE_CURSOR] =
658 g_signal_new (I_("move-cursor"),
659 GTK_TYPE_LIST_BOX,
660 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
661 G_STRUCT_OFFSET (GtkListBoxClass, move_cursor),
662 NULL, NULL,
663 c_marshaller: _gtk_marshal_VOID__ENUM_INT_BOOLEAN_BOOLEAN,
664 G_TYPE_NONE, n_params: 4,
665 GTK_TYPE_MOVEMENT_STEP, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
666 g_signal_set_va_marshaller (signal_id: signals[MOVE_CURSOR],
667 G_TYPE_FROM_CLASS (klass),
668 va_marshaller: _gtk_marshal_VOID__ENUM_INT_BOOLEAN_BOOLEANv);
669
670 gtk_widget_class_set_activate_signal (widget_class, signal_id: signals[ACTIVATE_CURSOR_ROW]);
671
672 gtk_list_box_add_move_binding (widget_class, GDK_KEY_Home, modmask: 0,
673 step: GTK_MOVEMENT_BUFFER_ENDS, count: -1);
674 gtk_list_box_add_move_binding (widget_class, GDK_KEY_KP_Home, modmask: 0,
675 step: GTK_MOVEMENT_BUFFER_ENDS, count: -1);
676 gtk_list_box_add_move_binding (widget_class, GDK_KEY_End, modmask: 0,
677 step: GTK_MOVEMENT_BUFFER_ENDS, count: 1);
678 gtk_list_box_add_move_binding (widget_class, GDK_KEY_KP_End, modmask: 0,
679 step: GTK_MOVEMENT_BUFFER_ENDS, count: 1);
680 gtk_list_box_add_move_binding (widget_class, GDK_KEY_Up, modmask: 0,
681 step: GTK_MOVEMENT_DISPLAY_LINES, count: -1);
682 gtk_list_box_add_move_binding (widget_class, GDK_KEY_KP_Up, modmask: 0,
683 step: GTK_MOVEMENT_DISPLAY_LINES, count: -1);
684 gtk_list_box_add_move_binding (widget_class, GDK_KEY_Down, modmask: 0,
685 step: GTK_MOVEMENT_DISPLAY_LINES, count: 1);
686 gtk_list_box_add_move_binding (widget_class, GDK_KEY_KP_Down, modmask: 0,
687 step: GTK_MOVEMENT_DISPLAY_LINES, count: 1);
688 gtk_list_box_add_move_binding (widget_class, GDK_KEY_Page_Up, modmask: 0,
689 step: GTK_MOVEMENT_PAGES, count: -1);
690 gtk_list_box_add_move_binding (widget_class, GDK_KEY_KP_Page_Up, modmask: 0,
691 step: GTK_MOVEMENT_PAGES, count: -1);
692 gtk_list_box_add_move_binding (widget_class, GDK_KEY_Page_Down, modmask: 0,
693 step: GTK_MOVEMENT_PAGES, count: 1);
694 gtk_list_box_add_move_binding (widget_class, GDK_KEY_KP_Page_Down, modmask: 0,
695 step: GTK_MOVEMENT_PAGES, count: 1);
696
697 gtk_widget_class_add_binding_signal (widget_class,
698 GDK_KEY_space, mods: GDK_CONTROL_MASK,
699 signal: "toggle-cursor-row",
700 NULL);
701 gtk_widget_class_add_binding_signal (widget_class,
702 GDK_KEY_KP_Space, mods: GDK_CONTROL_MASK,
703 signal: "toggle-cursor-row",
704 NULL);
705
706 gtk_widget_class_add_binding_signal (widget_class,
707 GDK_KEY_a, mods: GDK_CONTROL_MASK,
708 signal: "select-all",
709 NULL);
710 gtk_widget_class_add_binding_signal (widget_class,
711 GDK_KEY_a, mods: GDK_CONTROL_MASK | GDK_SHIFT_MASK,
712 signal: "unselect-all",
713 NULL);
714
715 gtk_widget_class_set_css_name (widget_class, I_("list"));
716 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_LIST);
717}
718
719static void
720gtk_list_box_init (GtkListBox *box)
721{
722 GtkWidget *widget = GTK_WIDGET (box);
723 GtkGesture *gesture;
724
725 gtk_widget_set_focusable (GTK_WIDGET (box), TRUE);
726
727 box->selection_mode = GTK_SELECTION_SINGLE;
728 box->activate_single_click = TRUE;
729
730 box->children = g_sequence_new (NULL);
731 box->header_hash = g_hash_table_new_full (hash_func: g_direct_hash, key_equal_func: g_direct_equal, NULL, NULL);
732
733 gesture = gtk_gesture_click_new ();
734 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
735 phase: GTK_PHASE_BUBBLE);
736 gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture),
737 FALSE);
738 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture),
739 GDK_BUTTON_PRIMARY);
740 g_signal_connect (gesture, "pressed",
741 G_CALLBACK (gtk_list_box_click_gesture_pressed), box);
742 g_signal_connect (gesture, "released",
743 G_CALLBACK (gtk_list_box_click_gesture_released), box);
744 g_signal_connect (gesture, "stopped",
745 G_CALLBACK (gtk_list_box_click_gesture_stopped), box);
746 g_signal_connect (gesture, "unpaired-release",
747 G_CALLBACK (gtk_list_box_click_unpaired_release), box);
748 gtk_widget_add_controller (widget, GTK_EVENT_CONTROLLER (gesture));
749
750 g_signal_connect (box, "notify::parent", G_CALLBACK (gtk_list_box_parent_cb), NULL);
751}
752
753/**
754 * gtk_list_box_get_selected_row:
755 * @box: a `GtkListBox`
756 *
757 * Gets the selected row, or %NULL if no rows are selected.
758 *
759 * Note that the box may allow multiple selection, in which
760 * case you should use [method@Gtk.ListBox.selected_foreach] to
761 * find all selected rows.
762 *
763 * Returns: (transfer none) (nullable): the selected row
764 */
765GtkListBoxRow *
766gtk_list_box_get_selected_row (GtkListBox *box)
767{
768 g_return_val_if_fail (GTK_IS_LIST_BOX (box), NULL);
769
770 return box->selected_row;
771}
772
773/**
774 * gtk_list_box_get_row_at_index:
775 * @box: a `GtkListBox`
776 * @index_: the index of the row
777 *
778 * Gets the n-th child in the list (not counting headers).
779 *
780 * If @index_ is negative or larger than the number of items in the
781 * list, %NULL is returned.
782 *
783 * Returns: (transfer none) (nullable): the child `GtkWidget`
784 */
785GtkListBoxRow *
786gtk_list_box_get_row_at_index (GtkListBox *box,
787 int index_)
788{
789 GSequenceIter *iter;
790
791 g_return_val_if_fail (GTK_IS_LIST_BOX (box), NULL);
792
793 iter = g_sequence_get_iter_at_pos (seq: box->children, pos: index_);
794 if (!g_sequence_iter_is_end (iter))
795 return g_sequence_get (iter);
796
797 return NULL;
798}
799
800static int
801row_y_cmp_func (gconstpointer a,
802 gconstpointer b,
803 gpointer user_data)
804{
805 int y = GPOINTER_TO_INT (b);
806 GtkListBoxRowPrivate *row_priv = ROW_PRIV (a);
807
808
809 if (y < row_priv->y)
810 return 1;
811 else if (y >= row_priv->y + row_priv->height)
812 return -1;
813
814 return 0;
815}
816
817/**
818 * gtk_list_box_get_row_at_y:
819 * @box: a `GtkListBox`
820 * @y: position
821 *
822 * Gets the row at the @y position.
823 *
824 * Returns: (transfer none) (nullable): the row
825 */
826GtkListBoxRow *
827gtk_list_box_get_row_at_y (GtkListBox *box,
828 int y)
829{
830 GSequenceIter *iter;
831
832 g_return_val_if_fail (GTK_IS_LIST_BOX (box), NULL);
833
834 iter = g_sequence_lookup (seq: box->children,
835 GINT_TO_POINTER (y),
836 cmp_func: row_y_cmp_func,
837 NULL);
838
839 if (iter)
840 return GTK_LIST_BOX_ROW (g_sequence_get (iter));
841
842 return NULL;
843}
844
845/**
846 * gtk_list_box_select_row:
847 * @box: a `GtkListBox`
848 * @row: (nullable): The row to select
849 *
850 * Make @row the currently selected row.
851 */
852void
853gtk_list_box_select_row (GtkListBox *box,
854 GtkListBoxRow *row)
855{
856 gboolean dirty = FALSE;
857
858 g_return_if_fail (GTK_IS_LIST_BOX (box));
859 g_return_if_fail (row == NULL || GTK_IS_LIST_BOX_ROW (row));
860
861 if (row)
862 gtk_list_box_select_row_internal (box, row);
863 else
864 dirty = gtk_list_box_unselect_all_internal (box);
865
866 if (dirty)
867 {
868 g_signal_emit (instance: box, signal_id: signals[ROW_SELECTED], detail: 0, NULL);
869 g_signal_emit (instance: box, signal_id: signals[SELECTED_ROWS_CHANGED], detail: 0);
870 }
871}
872
873/**
874 * gtk_list_box_unselect_row:
875 * @box: a `GtkListBox`
876 * @row: the row to unselected
877 *
878 * Unselects a single row of @box, if the selection mode allows it.
879 */
880void
881gtk_list_box_unselect_row (GtkListBox *box,
882 GtkListBoxRow *row)
883{
884 g_return_if_fail (GTK_IS_LIST_BOX (box));
885 g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
886
887 gtk_list_box_unselect_row_internal (box, row);
888}
889
890/**
891 * gtk_list_box_select_all:
892 * @box: a `GtkListBox`
893 *
894 * Select all children of @box, if the selection mode allows it.
895 */
896void
897gtk_list_box_select_all (GtkListBox *box)
898{
899 g_return_if_fail (GTK_IS_LIST_BOX (box));
900
901 if (box->selection_mode != GTK_SELECTION_MULTIPLE)
902 return;
903
904 if (g_sequence_get_length (seq: box->children) > 0)
905 {
906 gtk_list_box_select_all_between (box, NULL, NULL, FALSE);
907 g_signal_emit (instance: box, signal_id: signals[SELECTED_ROWS_CHANGED], detail: 0);
908 }
909}
910
911/**
912 * gtk_list_box_unselect_all:
913 * @box: a `GtkListBox`
914 *
915 * Unselect all children of @box, if the selection mode allows it.
916 */
917void
918gtk_list_box_unselect_all (GtkListBox *box)
919{
920 gboolean dirty = FALSE;
921
922 g_return_if_fail (GTK_IS_LIST_BOX (box));
923
924 if (box->selection_mode == GTK_SELECTION_BROWSE)
925 return;
926
927 dirty = gtk_list_box_unselect_all_internal (box);
928
929 if (dirty)
930 {
931 g_signal_emit (instance: box, signal_id: signals[ROW_SELECTED], detail: 0, NULL);
932 g_signal_emit (instance: box, signal_id: signals[SELECTED_ROWS_CHANGED], detail: 0);
933 }
934}
935
936static void
937gtk_list_box_selected_rows_changed (GtkListBox *box)
938{
939}
940
941/**
942 * GtkListBoxForeachFunc:
943 * @box: a `GtkListBox`
944 * @row: a `GtkListBoxRow`
945 * @user_data: (closure): user data
946 *
947 * A function used by gtk_list_box_selected_foreach().
948 *
949 * It will be called on every selected child of the @box.
950 */
951
952/**
953 * gtk_list_box_selected_foreach:
954 * @box: a `GtkListBox`
955 * @func: (scope call): the function to call for each selected child
956 * @data: user data to pass to the function
957 *
958 * Calls a function for each selected child.
959 *
960 * Note that the selection cannot be modified from within this function.
961 */
962void
963gtk_list_box_selected_foreach (GtkListBox *box,
964 GtkListBoxForeachFunc func,
965 gpointer data)
966{
967 GtkListBoxRow *row;
968 GSequenceIter *iter;
969
970 g_return_if_fail (GTK_IS_LIST_BOX (box));
971
972 for (iter = g_sequence_get_begin_iter (seq: box->children);
973 !g_sequence_iter_is_end (iter);
974 iter = g_sequence_iter_next (iter))
975 {
976 row = g_sequence_get (iter);
977 if (gtk_list_box_row_is_selected (row))
978 (*func) (box, row, data);
979 }
980}
981
982/**
983 * gtk_list_box_get_selected_rows:
984 * @box: a `GtkListBox`
985 *
986 * Creates a list of all selected children.
987 *
988 * Returns: (element-type GtkListBoxRow) (transfer container):
989 * A `GList` containing the `GtkWidget` for each selected child.
990 * Free with g_list_free() when done.
991 */
992GList *
993gtk_list_box_get_selected_rows (GtkListBox *box)
994{
995 GtkListBoxRow *row;
996 GSequenceIter *iter;
997 GList *selected = NULL;
998
999 g_return_val_if_fail (GTK_IS_LIST_BOX (box), NULL);
1000
1001 for (iter = g_sequence_get_begin_iter (seq: box->children);
1002 !g_sequence_iter_is_end (iter);
1003 iter = g_sequence_iter_next (iter))
1004 {
1005 row = g_sequence_get (iter);
1006 if (gtk_list_box_row_is_selected (row))
1007 selected = g_list_prepend (list: selected, data: row);
1008 }
1009
1010 return g_list_reverse (list: selected);
1011}
1012
1013/**
1014 * gtk_list_box_set_placeholder:
1015 * @box: a `GtkListBox`
1016 * @placeholder: (nullable): a `GtkWidget`
1017 *
1018 * Sets the placeholder widget that is shown in the list when
1019 * it doesn't display any visible children.
1020 */
1021void
1022gtk_list_box_set_placeholder (GtkListBox *box,
1023 GtkWidget *placeholder)
1024{
1025 g_return_if_fail (GTK_IS_LIST_BOX (box));
1026
1027 if (box->placeholder)
1028 {
1029 gtk_widget_unparent (widget: box->placeholder);
1030 gtk_widget_queue_resize (GTK_WIDGET (box));
1031 }
1032
1033 box->placeholder = placeholder;
1034
1035 if (placeholder)
1036 {
1037 gtk_widget_set_parent (widget: placeholder, GTK_WIDGET (box));
1038 gtk_widget_set_child_visible (widget: placeholder,
1039 child_visible: box->n_visible_rows == 0);
1040 }
1041}
1042
1043
1044/**
1045 * gtk_list_box_set_adjustment:
1046 * @box: a `GtkListBox`
1047 * @adjustment: (nullable): the adjustment
1048 *
1049 * Sets the adjustment (if any) that the widget uses to
1050 * for vertical scrolling.
1051 *
1052 * For instance, this is used to get the page size for
1053 * PageUp/Down key handling.
1054 *
1055 * In the normal case when the @box is packed inside
1056 * a `GtkScrolledWindow` the adjustment from that will
1057 * be picked up automatically, so there is no need
1058 * to manually do that.
1059 */
1060void
1061gtk_list_box_set_adjustment (GtkListBox *box,
1062 GtkAdjustment *adjustment)
1063{
1064 g_return_if_fail (GTK_IS_LIST_BOX (box));
1065 g_return_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment));
1066
1067 if (adjustment)
1068 g_object_ref_sink (adjustment);
1069 if (box->adjustment)
1070 g_object_unref (object: box->adjustment);
1071 box->adjustment = adjustment;
1072}
1073
1074/**
1075 * gtk_list_box_get_adjustment:
1076 * @box: a `GtkListBox`
1077 *
1078 * Gets the adjustment (if any) that the widget uses to
1079 * for vertical scrolling.
1080 *
1081 * Returns: (transfer none) (nullable): the adjustment
1082 */
1083GtkAdjustment *
1084gtk_list_box_get_adjustment (GtkListBox *box)
1085{
1086 g_return_val_if_fail (GTK_IS_LIST_BOX (box), NULL);
1087
1088 return box->adjustment;
1089}
1090
1091static void
1092adjustment_changed (GObject *object,
1093 GParamSpec *pspec,
1094 gpointer data)
1095{
1096 GtkAdjustment *adjustment;
1097
1098 adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (object));
1099 gtk_list_box_set_adjustment (GTK_LIST_BOX (data), adjustment);
1100}
1101
1102static void
1103gtk_list_box_parent_cb (GObject *object,
1104 GParamSpec *pspec,
1105 gpointer user_data)
1106{
1107 GtkListBox *box = GTK_LIST_BOX (object);
1108 GtkWidget *parent;
1109
1110 parent = gtk_widget_get_parent (GTK_WIDGET (object));
1111
1112 if (box->adjustment_changed_id != 0 &&
1113 box->scrollable_parent != NULL)
1114 {
1115 g_signal_handler_disconnect (instance: box->scrollable_parent,
1116 handler_id: box->adjustment_changed_id);
1117 }
1118
1119 if (parent && GTK_IS_SCROLLABLE (parent))
1120 {
1121 adjustment_changed (G_OBJECT (parent), NULL, data: object);
1122 box->scrollable_parent = parent;
1123 box->adjustment_changed_id = g_signal_connect (parent, "notify::vadjustment",
1124 G_CALLBACK (adjustment_changed), object);
1125 }
1126 else
1127 {
1128 gtk_list_box_set_adjustment (GTK_LIST_BOX (object), NULL);
1129 box->adjustment_changed_id = 0;
1130 box->scrollable_parent = NULL;
1131 }
1132}
1133
1134/**
1135 * gtk_list_box_set_selection_mode: (attributes org.gtk.Method.set_property=selection-mode)
1136 * @box: a `GtkListBox`
1137 * @mode: The `GtkSelectionMode`
1138 *
1139 * Sets how selection works in the listbox.
1140 */
1141void
1142gtk_list_box_set_selection_mode (GtkListBox *box,
1143 GtkSelectionMode mode)
1144{
1145 gboolean dirty = FALSE;
1146
1147 g_return_if_fail (GTK_IS_LIST_BOX (box));
1148
1149 if (box->selection_mode == mode)
1150 return;
1151
1152 if (mode == GTK_SELECTION_NONE ||
1153 box->selection_mode == GTK_SELECTION_MULTIPLE)
1154 dirty = gtk_list_box_unselect_all_internal (box);
1155
1156 box->selection_mode = mode;
1157
1158 gtk_list_box_update_row_styles (box);
1159
1160 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: box),
1161 first_property: GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE, mode == GTK_SELECTION_MULTIPLE,
1162 -1);
1163
1164 g_object_notify_by_pspec (G_OBJECT (box), pspec: properties[PROP_SELECTION_MODE]);
1165
1166 if (dirty)
1167 {
1168 g_signal_emit (instance: box, signal_id: signals[ROW_SELECTED], detail: 0, NULL);
1169 g_signal_emit (instance: box, signal_id: signals[SELECTED_ROWS_CHANGED], detail: 0);
1170 }
1171}
1172
1173/**
1174 * gtk_list_box_get_selection_mode: (attributes org.gtk.Method.get_property=selection-mode)
1175 * @box: a `GtkListBox`
1176 *
1177 * Gets the selection mode of the listbox.
1178 *
1179 * Returns: a `GtkSelectionMode`
1180 */
1181GtkSelectionMode
1182gtk_list_box_get_selection_mode (GtkListBox *box)
1183{
1184 g_return_val_if_fail (GTK_IS_LIST_BOX (box), GTK_SELECTION_NONE);
1185
1186 return box->selection_mode;
1187}
1188
1189/**
1190 * gtk_list_box_set_filter_func:
1191 * @box: a `GtkListBox`
1192 * @filter_func: (nullable): callback that lets you filter which rows to show
1193 * @user_data: (closure): user data passed to @filter_func
1194 * @destroy: destroy notifier for @user_data
1195 *
1196 * By setting a filter function on the @box one can decide dynamically which
1197 * of the rows to show.
1198 *
1199 * For instance, to implement a search function on a list that
1200 * filters the original list to only show the matching rows.
1201 *
1202 * The @filter_func will be called for each row after the call, and
1203 * it will continue to be called each time a row changes (via
1204 * [method@Gtk.ListBoxRow.changed]) or when [method@Gtk.ListBox.invalidate_filter]
1205 * is called.
1206 *
1207 * Note that using a filter function is incompatible with using a model
1208 * (see [method@Gtk.ListBox.bind_model]).
1209 */
1210void
1211gtk_list_box_set_filter_func (GtkListBox *box,
1212 GtkListBoxFilterFunc filter_func,
1213 gpointer user_data,
1214 GDestroyNotify destroy)
1215{
1216 g_return_if_fail (GTK_IS_LIST_BOX (box));
1217
1218 if (box->filter_func_target_destroy_notify != NULL)
1219 box->filter_func_target_destroy_notify (box->filter_func_target);
1220
1221 box->filter_func = filter_func;
1222 box->filter_func_target = user_data;
1223 box->filter_func_target_destroy_notify = destroy;
1224
1225 gtk_list_box_check_model_compat (box);
1226
1227 gtk_list_box_invalidate_filter (box);
1228}
1229
1230/**
1231 * gtk_list_box_set_header_func:
1232 * @box: a `GtkListBox`
1233 * @update_header: (nullable): callback that lets you add row headers
1234 * @user_data: (closure): user data passed to @update_header
1235 * @destroy: destroy notifier for @user_data
1236 *
1237 * Sets a header function.
1238 *
1239 * By setting a header function on the @box one can dynamically add headers
1240 * in front of rows, depending on the contents of the row and its position
1241 * in the list.
1242 *
1243 * For instance, one could use it to add headers in front of the first item
1244 * of a new kind, in a list sorted by the kind.
1245 *
1246 * The @update_header can look at the current header widget using
1247 * [method@Gtk.ListBoxRow.get_header] and either update the state of the widget
1248 * as needed, or set a new one using [method@Gtk.ListBoxRow.set_header]. If no
1249 * header is needed, set the header to %NULL.
1250 *
1251 * Note that you may get many calls @update_header to this for a particular
1252 * row when e.g. changing things that don’t affect the header. In this case
1253 * it is important for performance to not blindly replace an existing header
1254 * with an identical one.
1255 *
1256 * The @update_header function will be called for each row after the call,
1257 * and it will continue to be called each time a row changes (via
1258 * [method@Gtk.ListBoxRow.changed]) and when the row before changes (either
1259 * by [method@Gtk.ListBoxRow.changed] on the previous row, or when the previous
1260 * row becomes a different row). It is also called for all rows when
1261 * [method@Gtk.ListBox.invalidate_headers] is called.
1262 */
1263void
1264gtk_list_box_set_header_func (GtkListBox *box,
1265 GtkListBoxUpdateHeaderFunc update_header,
1266 gpointer user_data,
1267 GDestroyNotify destroy)
1268{
1269 g_return_if_fail (GTK_IS_LIST_BOX (box));
1270
1271 if (box->update_header_func_target_destroy_notify != NULL)
1272 box->update_header_func_target_destroy_notify (box->update_header_func_target);
1273
1274 box->update_header_func = update_header;
1275 box->update_header_func_target = user_data;
1276 box->update_header_func_target_destroy_notify = destroy;
1277 gtk_list_box_invalidate_headers (box);
1278}
1279
1280/**
1281 * gtk_list_box_invalidate_filter:
1282 * @box: a `GtkListBox`
1283 *
1284 * Update the filtering for all rows.
1285 *
1286 * Call this when result
1287 * of the filter function on the @box is changed due
1288 * to an external factor. For instance, this would be used
1289 * if the filter function just looked for a specific search
1290 * string and the entry with the search string has changed.
1291 */
1292void
1293gtk_list_box_invalidate_filter (GtkListBox *box)
1294{
1295 g_return_if_fail (GTK_IS_LIST_BOX (box));
1296
1297 gtk_list_box_apply_filter_all (box);
1298 gtk_list_box_invalidate_headers (box);
1299 gtk_widget_queue_resize (GTK_WIDGET (box));
1300}
1301
1302static int
1303do_sort (GtkListBoxRow *a,
1304 GtkListBoxRow *b,
1305 GtkListBox *box)
1306{
1307 return box->sort_func (a, b, box->sort_func_target);
1308}
1309
1310static void
1311gtk_list_box_reorder_foreach (gpointer data,
1312 gpointer user_data)
1313{
1314 GtkWidget **previous = user_data;
1315 GtkWidget *row = data;
1316
1317 if (*previous)
1318 gtk_widget_insert_after (widget: row, parent: _gtk_widget_get_parent (widget: row), previous_sibling: *previous);
1319
1320 *previous = row;
1321}
1322
1323/**
1324 * gtk_list_box_invalidate_sort:
1325 * @box: a `GtkListBox`
1326 *
1327 * Update the sorting for all rows.
1328 *
1329 * Call this when result
1330 * of the sort function on the @box is changed due
1331 * to an external factor.
1332 */
1333void
1334gtk_list_box_invalidate_sort (GtkListBox *box)
1335{
1336 GtkWidget *previous = NULL;
1337
1338 g_return_if_fail (GTK_IS_LIST_BOX (box));
1339
1340 if (box->sort_func == NULL)
1341 return;
1342
1343 g_sequence_sort (seq: box->children, cmp_func: (GCompareDataFunc)do_sort, cmp_data: box);
1344 g_sequence_foreach (seq: box->children, func: gtk_list_box_reorder_foreach, user_data: &previous);
1345
1346 gtk_list_box_invalidate_headers (box);
1347 gtk_widget_queue_resize (GTK_WIDGET (box));
1348}
1349
1350static void
1351gtk_list_box_do_reseparate (GtkListBox *box)
1352{
1353 GSequenceIter *iter;
1354
1355 for (iter = g_sequence_get_begin_iter (seq: box->children);
1356 !g_sequence_iter_is_end (iter);
1357 iter = g_sequence_iter_next (iter))
1358 gtk_list_box_update_header (box, iter);
1359
1360 gtk_widget_queue_resize (GTK_WIDGET (box));
1361}
1362
1363
1364/**
1365 * gtk_list_box_invalidate_headers:
1366 * @box: a `GtkListBox`
1367 *
1368 * Update the separators for all rows.
1369 *
1370 * Call this when result
1371 * of the header function on the @box is changed due
1372 * to an external factor.
1373 */
1374void
1375gtk_list_box_invalidate_headers (GtkListBox *box)
1376{
1377 g_return_if_fail (GTK_IS_LIST_BOX (box));
1378
1379 if (!gtk_widget_get_visible (GTK_WIDGET (box)))
1380 return;
1381
1382 gtk_list_box_do_reseparate (box);
1383}
1384
1385/**
1386 * gtk_list_box_set_sort_func:
1387 * @box: a `GtkListBox`
1388 * @sort_func: (nullable): the sort function
1389 * @user_data: (closure): user data passed to @sort_func
1390 * @destroy: destroy notifier for @user_data
1391 *
1392 * Sets a sort function.
1393 *
1394 * By setting a sort function on the @box one can dynamically reorder
1395 * the rows of the list, based on the contents of the rows.
1396 *
1397 * The @sort_func will be called for each row after the call, and will
1398 * continue to be called each time a row changes (via
1399 * [method@Gtk.ListBoxRow.changed]) and when [method@Gtk.ListBox.invalidate_sort]
1400 * is called.
1401 *
1402 * Note that using a sort function is incompatible with using a model
1403 * (see [method@Gtk.ListBox.bind_model]).
1404 */
1405void
1406gtk_list_box_set_sort_func (GtkListBox *box,
1407 GtkListBoxSortFunc sort_func,
1408 gpointer user_data,
1409 GDestroyNotify destroy)
1410{
1411 g_return_if_fail (GTK_IS_LIST_BOX (box));
1412
1413 if (box->sort_func_target_destroy_notify != NULL)
1414 box->sort_func_target_destroy_notify (box->sort_func_target);
1415
1416 box->sort_func = sort_func;
1417 box->sort_func_target = user_data;
1418 box->sort_func_target_destroy_notify = destroy;
1419
1420 gtk_list_box_check_model_compat (box);
1421
1422 gtk_list_box_invalidate_sort (box);
1423}
1424
1425static void
1426gtk_list_box_got_row_changed (GtkListBox *box,
1427 GtkListBoxRow *row)
1428{
1429 GtkListBoxRowPrivate *row_priv = ROW_PRIV (row);
1430 GSequenceIter *prev_next, *next;
1431
1432 g_return_if_fail (GTK_IS_LIST_BOX (box));
1433 g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
1434
1435 prev_next = gtk_list_box_get_next_visible (box, iter: row_priv->iter);
1436 if (box->sort_func != NULL)
1437 {
1438 g_sequence_sort_changed (iter: row_priv->iter,
1439 cmp_func: (GCompareDataFunc)do_sort,
1440 cmp_data: box);
1441 gtk_widget_queue_resize (GTK_WIDGET (box));
1442 }
1443 gtk_list_box_apply_filter (box, row);
1444 if (gtk_widget_get_visible (GTK_WIDGET (box)))
1445 {
1446 next = gtk_list_box_get_next_visible (box, iter: row_priv->iter);
1447 gtk_list_box_update_header (box, iter: row_priv->iter);
1448 gtk_list_box_update_header (box, iter: next);
1449 gtk_list_box_update_header (box, iter: prev_next);
1450 }
1451}
1452
1453/**
1454 * gtk_list_box_set_activate_on_single_click: (attributes org.gtk.Method.set_property=activate-on-single-click)
1455 * @box: a `GtkListBox`
1456 * @single: a boolean
1457 *
1458 * If @single is %TRUE, rows will be activated when you click on them,
1459 * otherwise you need to double-click.
1460 */
1461void
1462gtk_list_box_set_activate_on_single_click (GtkListBox *box,
1463 gboolean single)
1464{
1465 g_return_if_fail (GTK_IS_LIST_BOX (box));
1466
1467 single = single != FALSE;
1468
1469 if (box->activate_single_click == single)
1470 return;
1471
1472 box->activate_single_click = single;
1473
1474 g_object_notify_by_pspec (G_OBJECT (box), pspec: properties[PROP_ACTIVATE_ON_SINGLE_CLICK]);
1475}
1476
1477/**
1478 * gtk_list_box_get_activate_on_single_click: (attributes org.gtk.Metthod.get_property=activate-on-single-click)
1479 * @box: a `GtkListBox`
1480 *
1481 * Returns whether rows activate on single clicks.
1482 *
1483 * Returns: %TRUE if rows are activated on single click, %FALSE otherwise
1484 */
1485gboolean
1486gtk_list_box_get_activate_on_single_click (GtkListBox *box)
1487{
1488 g_return_val_if_fail (GTK_IS_LIST_BOX (box), FALSE);
1489
1490 return box->activate_single_click;
1491}
1492
1493void
1494gtk_list_box_set_accept_unpaired_release (GtkListBox *box,
1495 gboolean accept)
1496{
1497 if (box->accept_unpaired_release == accept)
1498 return;
1499
1500 box->accept_unpaired_release = accept;
1501
1502 g_object_notify_by_pspec (G_OBJECT (box), pspec: properties[PROP_ACCEPT_UNPAIRED_RELEASE]);
1503}
1504
1505static void
1506gtk_list_box_add_move_binding (GtkWidgetClass *widget_class,
1507 guint keyval,
1508 GdkModifierType modmask,
1509 GtkMovementStep step,
1510 int count)
1511{
1512 gtk_widget_class_add_binding_signal (widget_class,
1513 keyval, mods: modmask,
1514 signal: "move-cursor",
1515 format_string: "(iibb)", step, count, FALSE, FALSE);
1516 gtk_widget_class_add_binding_signal (widget_class,
1517 keyval, mods: modmask | GDK_SHIFT_MASK,
1518 signal: "move-cursor",
1519 format_string: "(iibb)", step, count, TRUE, FALSE);
1520 gtk_widget_class_add_binding_signal (widget_class,
1521 keyval, mods: modmask | GDK_CONTROL_MASK,
1522 signal: "move-cursor",
1523 format_string: "(iibb)", step, count, FALSE, TRUE);
1524 gtk_widget_class_add_binding_signal (widget_class,
1525 keyval, mods: modmask | GDK_SHIFT_MASK | GDK_CONTROL_MASK,
1526 signal: "move-cursor",
1527 format_string: "(iibb)", step, count, TRUE, TRUE);
1528}
1529
1530static void
1531ensure_row_visible (GtkListBox *box,
1532 GtkListBoxRow *row)
1533{
1534 GtkWidget *header;
1535 int y, height;
1536 graphene_rect_t rect;
1537
1538 if (!box->adjustment)
1539 return;
1540
1541 if (!gtk_widget_compute_bounds (GTK_WIDGET (row), GTK_WIDGET (box), out_bounds: &rect))
1542 return;
1543
1544 y = rect.origin.y;
1545 height = rect.size.height;
1546
1547 /* If the row has a header, we want to ensure that it is visible as well. */
1548 header = ROW_PRIV (row)->header;
1549 if (GTK_IS_WIDGET (header) && gtk_widget_is_drawable (widget: header))
1550 {
1551 if (gtk_widget_compute_bounds (widget: header, GTK_WIDGET (box), out_bounds: &rect))
1552 {
1553 y = rect.origin.y;
1554 height += rect.size.height;
1555 }
1556 }
1557
1558 gtk_adjustment_clamp_page (adjustment: box->adjustment, lower: y, upper: y + height);
1559}
1560
1561static void
1562gtk_list_box_update_cursor (GtkListBox *box,
1563 GtkListBoxRow *row,
1564 gboolean grab_focus)
1565{
1566 box->cursor_row = row;
1567 ensure_row_visible (box, row);
1568 if (grab_focus)
1569 {
1570 GtkWidget *focus;
1571
1572 focus = gtk_root_get_focus (self: gtk_widget_get_root (GTK_WIDGET (box)));
1573 if (!focus || !gtk_widget_is_ancestor (widget: focus, GTK_WIDGET (row)))
1574 gtk_widget_grab_focus (GTK_WIDGET (row));
1575 }
1576 gtk_widget_queue_draw (GTK_WIDGET (row));
1577}
1578
1579static GtkListBox *
1580gtk_list_box_row_get_box (GtkListBoxRow *row)
1581{
1582 GtkWidget *parent;
1583
1584 parent = gtk_widget_get_parent (GTK_WIDGET (row));
1585 if (parent && GTK_IS_LIST_BOX (parent))
1586 return GTK_LIST_BOX (parent);
1587
1588 return NULL;
1589}
1590
1591static gboolean
1592row_is_visible (GtkListBoxRow *row)
1593{
1594 return ROW_PRIV (row)->visible;
1595}
1596
1597static gboolean
1598gtk_list_box_row_set_selected (GtkListBoxRow *row,
1599 gboolean selected)
1600{
1601 if (!ROW_PRIV (row)->selectable)
1602 return FALSE;
1603
1604 if (ROW_PRIV (row)->selected != selected)
1605 {
1606 ROW_PRIV (row)->selected = selected;
1607 if (selected)
1608 gtk_widget_set_state_flags (GTK_WIDGET (row),
1609 flags: GTK_STATE_FLAG_SELECTED, FALSE);
1610 else
1611 gtk_widget_unset_state_flags (GTK_WIDGET (row),
1612 flags: GTK_STATE_FLAG_SELECTED);
1613
1614 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: row),
1615 first_state: GTK_ACCESSIBLE_STATE_SELECTED, selected,
1616 -1);
1617
1618 return TRUE;
1619 }
1620
1621 return FALSE;
1622}
1623
1624static gboolean
1625gtk_list_box_unselect_all_internal (GtkListBox *box)
1626{
1627 GtkListBoxRow *row;
1628 GSequenceIter *iter;
1629 gboolean dirty = FALSE;
1630
1631 if (box->selection_mode == GTK_SELECTION_NONE)
1632 return FALSE;
1633
1634 for (iter = g_sequence_get_begin_iter (seq: box->children);
1635 !g_sequence_iter_is_end (iter);
1636 iter = g_sequence_iter_next (iter))
1637 {
1638 row = g_sequence_get (iter);
1639 dirty |= gtk_list_box_row_set_selected (row, FALSE);
1640 }
1641
1642 box->selected_row = NULL;
1643
1644 return dirty;
1645}
1646
1647static void
1648gtk_list_box_unselect_row_internal (GtkListBox *box,
1649 GtkListBoxRow *row)
1650{
1651 if (!ROW_PRIV (row)->selected)
1652 return;
1653
1654 if (box->selection_mode == GTK_SELECTION_NONE)
1655 return;
1656 else if (box->selection_mode != GTK_SELECTION_MULTIPLE)
1657 gtk_list_box_unselect_all_internal (box);
1658 else
1659 gtk_list_box_row_set_selected (row, FALSE);
1660
1661 g_signal_emit (instance: box, signal_id: signals[ROW_SELECTED], detail: 0, NULL);
1662 g_signal_emit (instance: box, signal_id: signals[SELECTED_ROWS_CHANGED], detail: 0);
1663}
1664
1665static void
1666gtk_list_box_select_row_internal (GtkListBox *box,
1667 GtkListBoxRow *row)
1668{
1669 if (!ROW_PRIV (row)->selectable)
1670 return;
1671
1672 if (ROW_PRIV (row)->selected)
1673 return;
1674
1675 if (box->selection_mode == GTK_SELECTION_NONE)
1676 return;
1677
1678 if (box->selection_mode != GTK_SELECTION_MULTIPLE)
1679 gtk_list_box_unselect_all_internal (box);
1680
1681 gtk_list_box_row_set_selected (row, TRUE);
1682 box->selected_row = row;
1683
1684 g_signal_emit (instance: box, signal_id: signals[ROW_SELECTED], detail: 0, row);
1685 g_signal_emit (instance: box, signal_id: signals[SELECTED_ROWS_CHANGED], detail: 0);
1686}
1687
1688static void
1689gtk_list_box_select_all_between (GtkListBox *box,
1690 GtkListBoxRow *row1,
1691 GtkListBoxRow *row2,
1692 gboolean modify)
1693{
1694 GSequenceIter *iter, *iter1, *iter2;
1695
1696 if (row1)
1697 iter1 = ROW_PRIV (row1)->iter;
1698 else
1699 iter1 = g_sequence_get_begin_iter (seq: box->children);
1700
1701 if (row2)
1702 iter2 = ROW_PRIV (row2)->iter;
1703 else
1704 iter2 = g_sequence_get_end_iter (seq: box->children);
1705
1706 if (g_sequence_iter_compare (a: iter2, b: iter1) < 0)
1707 {
1708 iter = iter1;
1709 iter1 = iter2;
1710 iter2 = iter;
1711 }
1712
1713 for (iter = iter1;
1714 !g_sequence_iter_is_end (iter);
1715 iter = g_sequence_iter_next (iter))
1716 {
1717 GtkListBoxRow *row;
1718
1719 row = GTK_LIST_BOX_ROW (g_sequence_get (iter));
1720 if (row_is_visible (row))
1721 {
1722 if (modify)
1723 gtk_list_box_row_set_selected (row, selected: !ROW_PRIV (row)->selected);
1724 else
1725 gtk_list_box_row_set_selected (row, TRUE);
1726 }
1727
1728 if (g_sequence_iter_compare (a: iter, b: iter2) == 0)
1729 break;
1730 }
1731}
1732
1733#define gtk_list_box_update_selection(b,r,m,e) \
1734 gtk_list_box_update_selection_full((b), (r), (m), (e), TRUE)
1735static void
1736gtk_list_box_update_selection_full (GtkListBox *box,
1737 GtkListBoxRow *row,
1738 gboolean modify,
1739 gboolean extend,
1740 gboolean grab_cursor)
1741{
1742 gtk_list_box_update_cursor (box, row, grab_focus: grab_cursor);
1743
1744 if (box->selection_mode == GTK_SELECTION_NONE)
1745 return;
1746
1747 if (!ROW_PRIV (row)->selectable)
1748 return;
1749
1750 if (box->selection_mode == GTK_SELECTION_BROWSE)
1751 {
1752 gtk_list_box_unselect_all_internal (box);
1753 gtk_list_box_row_set_selected (row, TRUE);
1754 box->selected_row = row;
1755 g_signal_emit (instance: box, signal_id: signals[ROW_SELECTED], detail: 0, row);
1756 }
1757 else if (box->selection_mode == GTK_SELECTION_SINGLE)
1758 {
1759 gboolean was_selected;
1760
1761 was_selected = ROW_PRIV (row)->selected;
1762 gtk_list_box_unselect_all_internal (box);
1763 gtk_list_box_row_set_selected (row, selected: modify ? !was_selected : TRUE);
1764 box->selected_row = ROW_PRIV (row)->selected ? row : NULL;
1765 g_signal_emit (instance: box, signal_id: signals[ROW_SELECTED], detail: 0, box->selected_row);
1766 }
1767 else /* GTK_SELECTION_MULTIPLE */
1768 {
1769 if (extend)
1770 {
1771 GtkListBoxRow *selected_row;
1772
1773 selected_row = box->selected_row;
1774
1775 gtk_list_box_unselect_all_internal (box);
1776
1777 if (selected_row == NULL)
1778 {
1779 gtk_list_box_row_set_selected (row, TRUE);
1780 box->selected_row = row;
1781 g_signal_emit (instance: box, signal_id: signals[ROW_SELECTED], detail: 0, row);
1782 }
1783 else
1784 {
1785 gtk_list_box_select_all_between (box, row1: selected_row, row2: row, FALSE);
1786 box->selected_row = selected_row;
1787 }
1788 }
1789 else
1790 {
1791 if (modify)
1792 {
1793 gtk_list_box_row_set_selected (row, selected: !ROW_PRIV (row)->selected);
1794 g_signal_emit (instance: box, signal_id: signals[ROW_SELECTED], detail: 0, ROW_PRIV (row)->selected ? row
1795 : NULL);
1796 }
1797 else
1798 {
1799 gtk_list_box_unselect_all_internal (box);
1800 gtk_list_box_row_set_selected (row, selected: !ROW_PRIV (row)->selected);
1801 box->selected_row = row;
1802 g_signal_emit (instance: box, signal_id: signals[ROW_SELECTED], detail: 0, row);
1803 }
1804 }
1805 }
1806
1807 g_signal_emit (instance: box, signal_id: signals[SELECTED_ROWS_CHANGED], detail: 0);
1808}
1809
1810static void
1811gtk_list_box_activate (GtkListBox *box,
1812 GtkListBoxRow *row)
1813{
1814 if (!gtk_list_box_row_get_activatable (row))
1815 return;
1816
1817 if (ROW_PRIV (row)->action_helper)
1818 gtk_action_helper_activate (ROW_PRIV (row)->action_helper);
1819 else
1820 g_signal_emit (instance: box, signal_id: signals[ROW_ACTIVATED], detail: 0, row);
1821}
1822
1823#define gtk_list_box_select_and_activate(b,r) \
1824 gtk_list_box_select_and_activate_full ((b), (r), TRUE)
1825static void
1826gtk_list_box_select_and_activate_full (GtkListBox *box,
1827 GtkListBoxRow *row,
1828 gboolean grab_focus)
1829{
1830 if (row != NULL)
1831 {
1832 gtk_list_box_select_row_internal (box, row);
1833 gtk_list_box_update_cursor (box, row, grab_focus);
1834 gtk_list_box_activate (box, row);
1835 }
1836}
1837
1838static void
1839gtk_list_box_click_gesture_pressed (GtkGestureClick *gesture,
1840 guint n_press,
1841 double x,
1842 double y,
1843 GtkListBox *box)
1844{
1845 GtkListBoxRow *row;
1846
1847 box->active_row = NULL;
1848 row = gtk_list_box_get_row_at_y (box, y);
1849
1850 if (row != NULL && gtk_widget_is_sensitive (GTK_WIDGET (row)))
1851 {
1852 box->active_row = row;
1853
1854 if (n_press == 2 && !box->activate_single_click)
1855 gtk_list_box_activate (box, row);
1856 }
1857}
1858
1859static void
1860gtk_list_box_click_unpaired_release (GtkGestureClick *gesture,
1861 double x,
1862 double y,
1863 guint button,
1864 GdkEventSequence *sequence,
1865 GtkListBox *box)
1866{
1867 GtkListBoxRow *row;
1868
1869 if (!box->activate_single_click || !box->accept_unpaired_release)
1870 return;
1871
1872 row = gtk_list_box_get_row_at_y (box, y);
1873
1874 if (row)
1875 gtk_list_box_select_and_activate (box, row);
1876}
1877
1878static void
1879gtk_list_box_click_gesture_released (GtkGestureClick *gesture,
1880 guint n_press,
1881 double x,
1882 double y,
1883 GtkListBox *box)
1884{
1885 /* Take a ref to protect against reentrancy
1886 * (the activation may destroy the widget)
1887 */
1888 g_object_ref (box);
1889
1890 if (box->active_row != NULL &&
1891 box->active_row == gtk_list_box_get_row_at_y (box, y))
1892 {
1893 gboolean focus_on_click = gtk_widget_get_focus_on_click (GTK_WIDGET (box->active_row));
1894
1895 if (n_press == 1 && box->activate_single_click)
1896 gtk_list_box_select_and_activate_full (box, row: box->active_row, grab_focus: focus_on_click);
1897 else
1898 {
1899 GdkEventSequence *sequence;
1900 GdkInputSource source;
1901 GdkEvent *event;
1902 GdkModifierType state;
1903 gboolean extend;
1904 gboolean modify;
1905
1906 /* With touch, we default to modifying the selection.
1907 * You can still clear the selection and start over
1908 * by holding Ctrl.
1909 */
1910 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
1911 event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
1912 state = gdk_event_get_modifier_state (event);
1913 extend = (state & GDK_SHIFT_MASK) != 0;
1914 modify = (state & GDK_CONTROL_MASK) != 0;
1915 source = gdk_device_get_source (device: gdk_event_get_device (event));
1916
1917 if (source == GDK_SOURCE_TOUCHSCREEN)
1918 modify = !modify;
1919
1920 gtk_list_box_update_selection_full (box, row: box->active_row, modify, extend, grab_cursor: focus_on_click);
1921 }
1922 }
1923
1924 if (box->active_row)
1925 {
1926 box->active_row = NULL;
1927 }
1928
1929 g_object_unref (object: box);
1930}
1931
1932static void
1933gtk_list_box_click_gesture_stopped (GtkGestureClick *gesture,
1934 GtkListBox *box)
1935{
1936 if (box->active_row)
1937 {
1938 box->active_row = NULL;
1939 gtk_widget_queue_draw (GTK_WIDGET (box));
1940 }
1941}
1942
1943static void
1944gtk_list_box_show (GtkWidget *widget)
1945{
1946 gtk_list_box_do_reseparate (GTK_LIST_BOX (widget));
1947
1948 GTK_WIDGET_CLASS (gtk_list_box_parent_class)->show (widget);
1949}
1950
1951static gboolean
1952gtk_list_box_focus (GtkWidget *widget,
1953 GtkDirectionType direction)
1954{
1955 GtkListBox *box = GTK_LIST_BOX (widget);
1956 GtkWidget *focus_child;
1957 GtkListBoxRow *next_focus_row;
1958 GtkWidget *row;
1959 GtkWidget *header;
1960
1961 focus_child = gtk_widget_get_focus_child (widget);
1962
1963 next_focus_row = NULL;
1964 if (focus_child != NULL)
1965 {
1966 GSequenceIter *i;
1967
1968 if (gtk_widget_child_focus (widget: focus_child, direction))
1969 return TRUE;
1970
1971 if (direction == GTK_DIR_UP || direction == GTK_DIR_TAB_BACKWARD)
1972 {
1973 if (GTK_IS_LIST_BOX_ROW (focus_child))
1974 {
1975 header = ROW_PRIV (GTK_LIST_BOX_ROW (focus_child))->header;
1976 if (header && gtk_widget_child_focus (widget: header, direction))
1977 return TRUE;
1978 }
1979
1980 if (GTK_IS_LIST_BOX_ROW (focus_child))
1981 row = focus_child;
1982 else
1983 row = g_hash_table_lookup (hash_table: box->header_hash, key: focus_child);
1984
1985 if (GTK_IS_LIST_BOX_ROW (row))
1986 i = gtk_list_box_get_previous_visible (box, ROW_PRIV (GTK_LIST_BOX_ROW (row))->iter);
1987 else
1988 i = NULL;
1989
1990 while (i != NULL)
1991 {
1992 if (gtk_widget_get_sensitive (widget: g_sequence_get (iter: i)))
1993 {
1994 next_focus_row = g_sequence_get (iter: i);
1995 break;
1996 }
1997
1998 i = gtk_list_box_get_previous_visible (box, iter: i);
1999 }
2000 }
2001 else if (direction == GTK_DIR_DOWN || direction == GTK_DIR_TAB_FORWARD)
2002 {
2003 if (GTK_IS_LIST_BOX_ROW (focus_child))
2004 i = gtk_list_box_get_next_visible (box, ROW_PRIV (GTK_LIST_BOX_ROW (focus_child))->iter);
2005 else
2006 {
2007 row = g_hash_table_lookup (hash_table: box->header_hash, key: focus_child);
2008 if (GTK_IS_LIST_BOX_ROW (row))
2009 i = ROW_PRIV (GTK_LIST_BOX_ROW (row))->iter;
2010 else
2011 i = NULL;
2012 }
2013
2014 while (!g_sequence_iter_is_end (iter: i))
2015 {
2016 if (gtk_widget_get_sensitive (widget: g_sequence_get (iter: i)))
2017 {
2018 next_focus_row = g_sequence_get (iter: i);
2019 break;
2020 }
2021
2022 i = gtk_list_box_get_next_visible (box, iter: i);
2023 }
2024 }
2025 }
2026 else
2027 {
2028 /* No current focus row */
2029 switch (direction)
2030 {
2031 case GTK_DIR_UP:
2032 case GTK_DIR_TAB_BACKWARD:
2033 next_focus_row = box->selected_row;
2034 if (next_focus_row == NULL)
2035 next_focus_row = gtk_list_box_get_last_focusable (box);
2036 break;
2037 case GTK_DIR_DOWN:
2038 case GTK_DIR_TAB_FORWARD:
2039 case GTK_DIR_LEFT:
2040 case GTK_DIR_RIGHT:
2041 default:
2042 next_focus_row = box->selected_row;
2043 if (next_focus_row == NULL)
2044 next_focus_row = gtk_list_box_get_first_focusable (box);
2045 break;
2046 }
2047 }
2048
2049 if (next_focus_row == NULL)
2050 {
2051 if (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN)
2052 {
2053 if (gtk_widget_keynav_failed (GTK_WIDGET (box), direction))
2054 return TRUE;
2055 }
2056
2057 return FALSE;
2058 }
2059
2060 if (direction == GTK_DIR_DOWN || direction == GTK_DIR_TAB_FORWARD)
2061 {
2062 header = ROW_PRIV (next_focus_row)->header;
2063 if (header && gtk_widget_child_focus (widget: header, direction))
2064 return TRUE;
2065 }
2066
2067 if (gtk_widget_child_focus (GTK_WIDGET (next_focus_row), direction))
2068 return TRUE;
2069
2070 return FALSE;
2071}
2072
2073static void
2074list_box_add_visible_rows (GtkListBox *box,
2075 int n)
2076{
2077 int was_zero;
2078
2079 was_zero = box->n_visible_rows == 0;
2080 box->n_visible_rows += n;
2081
2082 if (box->placeholder &&
2083 (was_zero || box->n_visible_rows == 0))
2084 gtk_widget_set_child_visible (GTK_WIDGET (box->placeholder),
2085 child_visible: box->n_visible_rows == 0);
2086}
2087
2088/* Children are visible if they are shown by the app (visible)
2089 * and not filtered out (child_visible) by the listbox
2090 */
2091static void
2092update_row_is_visible (GtkListBox *box,
2093 GtkListBoxRow *row)
2094{
2095 GtkListBoxRowPrivate *row_priv = ROW_PRIV (row);
2096 gboolean was_visible;
2097
2098 was_visible = row_priv->visible;
2099
2100 row_priv->visible =
2101 gtk_widget_get_visible (GTK_WIDGET (row)) &&
2102 gtk_widget_get_child_visible (GTK_WIDGET (row));
2103
2104 if (was_visible && !row_priv->visible)
2105 list_box_add_visible_rows (box, n: -1);
2106 if (!was_visible && row_priv->visible)
2107 list_box_add_visible_rows (box, n: 1);
2108}
2109
2110static void
2111gtk_list_box_apply_filter (GtkListBox *box,
2112 GtkListBoxRow *row)
2113{
2114 gboolean do_show;
2115
2116 do_show = TRUE;
2117 if (box->filter_func != NULL)
2118 do_show = box->filter_func (row, box->filter_func_target);
2119
2120 gtk_widget_set_child_visible (GTK_WIDGET (row), child_visible: do_show);
2121
2122 update_row_is_visible (box, row);
2123}
2124
2125static void
2126gtk_list_box_apply_filter_all (GtkListBox *box)
2127{
2128 GtkListBoxRow *row;
2129 GSequenceIter *iter;
2130
2131 for (iter = g_sequence_get_begin_iter (seq: box->children);
2132 !g_sequence_iter_is_end (iter);
2133 iter = g_sequence_iter_next (iter))
2134 {
2135 row = g_sequence_get (iter);
2136 gtk_list_box_apply_filter (box, row);
2137 }
2138}
2139
2140static GtkListBoxRow *
2141gtk_list_box_get_first_focusable (GtkListBox *box)
2142{
2143 GtkListBoxRow *row;
2144 GSequenceIter *iter;
2145
2146 for (iter = g_sequence_get_begin_iter (seq: box->children);
2147 !g_sequence_iter_is_end (iter);
2148 iter = g_sequence_iter_next (iter))
2149 {
2150 row = g_sequence_get (iter);
2151 if (row_is_visible (row) && gtk_widget_is_sensitive (GTK_WIDGET (row)))
2152 return row;
2153 }
2154
2155 return NULL;
2156}
2157
2158static GtkListBoxRow *
2159gtk_list_box_get_last_focusable (GtkListBox *box)
2160{
2161 GtkListBoxRow *row;
2162 GSequenceIter *iter;
2163
2164 iter = g_sequence_get_end_iter (seq: box->children);
2165 while (!g_sequence_iter_is_begin (iter))
2166 {
2167 iter = g_sequence_iter_prev (iter);
2168 row = g_sequence_get (iter);
2169 if (row_is_visible (row) && gtk_widget_is_sensitive (GTK_WIDGET (row)))
2170 return row;
2171 }
2172
2173 return NULL;
2174}
2175
2176static GSequenceIter *
2177gtk_list_box_get_previous_visible (GtkListBox *box,
2178 GSequenceIter *iter)
2179{
2180 GtkListBoxRow *row;
2181
2182 if (g_sequence_iter_is_begin (iter))
2183 return NULL;
2184
2185 do
2186 {
2187 iter = g_sequence_iter_prev (iter);
2188 row = g_sequence_get (iter);
2189 if (row_is_visible (row))
2190 return iter;
2191 }
2192 while (!g_sequence_iter_is_begin (iter));
2193
2194 return NULL;
2195}
2196
2197static GSequenceIter *
2198gtk_list_box_get_next_visible (GtkListBox *box,
2199 GSequenceIter *iter)
2200{
2201 GtkListBoxRow *row;
2202
2203 if (g_sequence_iter_is_end (iter))
2204 return iter;
2205
2206 do
2207 {
2208 iter = g_sequence_iter_next (iter);
2209 if (!g_sequence_iter_is_end (iter))
2210 {
2211 row = g_sequence_get (iter);
2212 if (row_is_visible (row))
2213 return iter;
2214 }
2215 }
2216 while (!g_sequence_iter_is_end (iter));
2217
2218 return iter;
2219}
2220
2221static GSequenceIter *
2222gtk_list_box_get_last_visible (GtkListBox *box,
2223 GSequenceIter *iter)
2224{
2225 GSequenceIter *next = NULL;
2226
2227 if (g_sequence_iter_is_end (iter))
2228 return NULL;
2229
2230 do
2231 {
2232 next = gtk_list_box_get_next_visible (box, iter);
2233
2234 if (!g_sequence_iter_is_end (iter: next))
2235 iter = next;
2236 }
2237 while (!g_sequence_iter_is_end (iter: next));
2238
2239 return iter;
2240}
2241
2242static void
2243gtk_list_box_update_header (GtkListBox *box,
2244 GSequenceIter *iter)
2245{
2246 GtkListBoxRow *row;
2247 GSequenceIter *before_iter;
2248 GtkListBoxRow *before_row;
2249 GtkWidget *old_header, *new_header;
2250
2251 if (iter == NULL || g_sequence_iter_is_end (iter))
2252 return;
2253
2254 row = g_sequence_get (iter);
2255 g_object_ref (row);
2256
2257 before_iter = gtk_list_box_get_previous_visible (box, iter);
2258 before_row = NULL;
2259 if (before_iter != NULL)
2260 {
2261 before_row = g_sequence_get (iter: before_iter);
2262 if (before_row)
2263 g_object_ref (before_row);
2264 }
2265
2266 if (box->update_header_func != NULL &&
2267 row_is_visible (row))
2268 {
2269 old_header = ROW_PRIV (row)->header;
2270 if (old_header)
2271 g_object_ref (old_header);
2272 box->update_header_func (row,
2273 before_row,
2274 box->update_header_func_target);
2275 new_header = ROW_PRIV (row)->header;
2276 if (old_header != new_header)
2277 {
2278 if (old_header != NULL &&
2279 g_hash_table_lookup (hash_table: box->header_hash, key: old_header) == row)
2280 {
2281 /* Only unparent the @old_header if it hasn’t been re-used as the
2282 * header for a different row. */
2283 gtk_widget_unparent (widget: old_header);
2284 g_hash_table_remove (hash_table: box->header_hash, key: old_header);
2285 }
2286 if (new_header != NULL)
2287 {
2288 g_hash_table_insert (hash_table: box->header_hash, key: new_header, value: row);
2289 gtk_widget_unparent (widget: new_header);
2290 gtk_widget_set_parent (widget: new_header, GTK_WIDGET (box));
2291 gtk_widget_show (widget: new_header);
2292 }
2293 gtk_widget_queue_resize (GTK_WIDGET (box));
2294 }
2295 if (old_header)
2296 g_object_unref (object: old_header);
2297 }
2298 else
2299 {
2300 if (ROW_PRIV (row)->header != NULL)
2301 {
2302 g_hash_table_remove (hash_table: box->header_hash, ROW_PRIV (row)->header);
2303 gtk_widget_unparent (ROW_PRIV (row)->header);
2304 gtk_list_box_row_set_header (row, NULL);
2305 gtk_widget_queue_resize (GTK_WIDGET (box));
2306 }
2307 }
2308 if (before_row)
2309 g_object_unref (object: before_row);
2310 g_object_unref (object: row);
2311}
2312
2313static void
2314gtk_list_box_row_visibility_changed (GtkListBox *box,
2315 GtkListBoxRow *row)
2316{
2317 update_row_is_visible (box, row);
2318
2319 if (gtk_widget_get_visible (GTK_WIDGET (box)))
2320 {
2321 gtk_list_box_update_header (box, ROW_PRIV (row)->iter);
2322 gtk_list_box_update_header (box,
2323 iter: gtk_list_box_get_next_visible (box, ROW_PRIV (row)->iter));
2324 }
2325}
2326
2327/**
2328 * gtk_list_box_remove:
2329 * @box: a `GtkListBox`
2330 * @child: the child to remove
2331 *
2332 * Removes a child from @box.
2333 */
2334void
2335gtk_list_box_remove (GtkListBox *box,
2336 GtkWidget *child)
2337{
2338 GtkWidget *widget;
2339 gboolean was_visible;
2340 gboolean was_selected;
2341 GtkListBoxRow *row;
2342 GSequenceIter *iter;
2343 GSequenceIter *next;
2344
2345 g_return_if_fail (GTK_IS_LIST_BOX (box));
2346 g_return_if_fail (GTK_IS_WIDGET (child));
2347
2348 widget = GTK_WIDGET (box);
2349 was_visible = gtk_widget_get_visible (widget: child);
2350
2351 if (child == box->placeholder)
2352 {
2353 gtk_widget_unparent (widget: child);
2354 box->placeholder = NULL;
2355 if (was_visible && gtk_widget_get_visible (widget))
2356 gtk_widget_queue_resize (widget);
2357
2358 return;
2359 }
2360
2361 if (!GTK_IS_LIST_BOX_ROW (child))
2362 {
2363 row = g_hash_table_lookup (hash_table: box->header_hash, key: child);
2364 if (row != NULL)
2365 {
2366 g_hash_table_remove (hash_table: box->header_hash, key: child);
2367 g_clear_object (&ROW_PRIV (row)->header);
2368 gtk_widget_unparent (widget: child);
2369 if (was_visible && gtk_widget_get_visible (widget))
2370 gtk_widget_queue_resize (widget);
2371 }
2372 else
2373 {
2374 g_warning ("Tried to remove non-child %p", child);
2375 }
2376 return;
2377 }
2378
2379 row = GTK_LIST_BOX_ROW (child);
2380 iter = ROW_PRIV (row)->iter;
2381 if (g_sequence_iter_get_sequence (iter) != box->children)
2382 {
2383 g_warning ("Tried to remove non-child %p", child);
2384 return;
2385 }
2386
2387 was_selected = ROW_PRIV (row)->selected;
2388
2389 if (ROW_PRIV (row)->visible)
2390 list_box_add_visible_rows (box, n: -1);
2391
2392 if (ROW_PRIV (row)->header != NULL)
2393 {
2394 g_hash_table_remove (hash_table: box->header_hash, ROW_PRIV (row)->header);
2395 gtk_widget_unparent (ROW_PRIV (row)->header);
2396 g_clear_object (&ROW_PRIV (row)->header);
2397 }
2398
2399 if (row == box->selected_row)
2400 box->selected_row = NULL;
2401 if (row == box->cursor_row)
2402 box->cursor_row = NULL;
2403 if (row == box->active_row)
2404 box->active_row = NULL;
2405
2406 if (row == box->drag_highlighted_row)
2407 gtk_list_box_drag_unhighlight_row (box);
2408
2409 next = gtk_list_box_get_next_visible (box, iter);
2410 gtk_widget_unparent (widget: child);
2411 g_sequence_remove (iter);
2412
2413 /* After unparenting, those values are garbage */
2414 iter = NULL;
2415 row = NULL;
2416 child = NULL;
2417
2418 if (gtk_widget_get_visible (widget))
2419 gtk_list_box_update_header (box, iter: next);
2420
2421 if (was_visible && gtk_widget_get_visible (GTK_WIDGET (box)))
2422 gtk_widget_queue_resize (widget);
2423
2424 if (was_selected && !gtk_widget_in_destruction (widget))
2425 {
2426 g_signal_emit (instance: box, signal_id: signals[ROW_SELECTED], detail: 0, NULL);
2427 g_signal_emit (instance: box, signal_id: signals[SELECTED_ROWS_CHANGED], detail: 0);
2428 }
2429}
2430
2431static void
2432gtk_list_box_compute_expand (GtkWidget *widget,
2433 gboolean *hexpand_p,
2434 gboolean *vexpand_p)
2435{
2436 GtkWidget *w;
2437 gboolean hexpand = FALSE;
2438 gboolean vexpand = FALSE;
2439
2440 for (w = gtk_widget_get_first_child (widget);
2441 w != NULL;
2442 w = gtk_widget_get_next_sibling (widget: w))
2443 {
2444 hexpand = hexpand || gtk_widget_compute_expand (widget: w, orientation: GTK_ORIENTATION_HORIZONTAL);
2445 vexpand = vexpand || gtk_widget_compute_expand (widget: w, orientation: GTK_ORIENTATION_VERTICAL);
2446 }
2447
2448 *hexpand_p = hexpand;
2449 *vexpand_p = vexpand;
2450
2451 /* We don't expand vertically beyond the minimum size */
2452 if (*vexpand_p)
2453 *vexpand_p = FALSE;
2454}
2455
2456static GtkSizeRequestMode
2457gtk_list_box_get_request_mode (GtkWidget *widget)
2458{
2459 return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
2460}
2461
2462static void
2463gtk_list_box_measure (GtkWidget *widget,
2464 GtkOrientation orientation,
2465 int for_size,
2466 int *minimum,
2467 int *natural,
2468 int *minimum_baseline,
2469 int *natural_baseline)
2470{
2471 GtkListBox *box = GTK_LIST_BOX (widget);
2472 GSequenceIter *iter;
2473
2474 if (orientation == GTK_ORIENTATION_HORIZONTAL)
2475 {
2476 *minimum = 0;
2477 *natural = 0;
2478
2479 if (box->placeholder && gtk_widget_get_child_visible (widget: box->placeholder))
2480 gtk_widget_measure (widget: box->placeholder, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
2481 minimum, natural,
2482 NULL, NULL);
2483
2484 for (iter = g_sequence_get_begin_iter (seq: box->children);
2485 !g_sequence_iter_is_end (iter);
2486 iter = g_sequence_iter_next (iter))
2487 {
2488 GtkListBoxRow *row;
2489 int row_min;
2490 int row_nat;
2491
2492 row = g_sequence_get (iter);
2493
2494 /* We *do* take visible but filtered rows into account here so that
2495 * the list width doesn't change during filtering
2496 */
2497 if (!gtk_widget_get_visible (GTK_WIDGET (row)))
2498 continue;
2499
2500 gtk_widget_measure (GTK_WIDGET (row), orientation, for_size: -1,
2501 minimum: &row_min, natural: &row_nat,
2502 NULL, NULL);
2503
2504 *minimum = MAX (*minimum, row_min);
2505 *natural = MAX (*natural, row_nat);
2506
2507 if (ROW_PRIV (row)->header != NULL)
2508 {
2509 gtk_widget_measure (ROW_PRIV (row)->header, orientation, for_size: -1,
2510 minimum: &row_min, natural: &row_nat,
2511 NULL, NULL);
2512 *minimum = MAX (*minimum, row_min);
2513 *natural = MAX (*natural, row_nat);
2514 }
2515 }
2516 }
2517 else
2518 {
2519 if (for_size < 0)
2520 {
2521 int f;
2522 gtk_list_box_measure (widget, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
2523 minimum: &f, natural: &for_size, NULL, NULL);
2524 }
2525
2526 *minimum = 0;
2527
2528 if (box->placeholder && gtk_widget_get_child_visible (widget: box->placeholder))
2529 gtk_widget_measure (widget: box->placeholder, orientation, for_size,
2530 minimum, NULL,
2531 NULL, NULL);
2532
2533 for (iter = g_sequence_get_begin_iter (seq: box->children);
2534 !g_sequence_iter_is_end (iter);
2535 iter = g_sequence_iter_next (iter))
2536 {
2537 GtkListBoxRow *row;
2538 int row_min = 0;
2539
2540 row = g_sequence_get (iter);
2541 if (!row_is_visible (row))
2542 continue;
2543
2544 if (ROW_PRIV (row)->header != NULL)
2545 {
2546 gtk_widget_measure (ROW_PRIV (row)->header, orientation, for_size,
2547 minimum: &row_min, NULL,
2548 NULL, NULL);
2549 *minimum += row_min;
2550 }
2551 gtk_widget_measure (GTK_WIDGET (row), orientation, for_size,
2552 minimum: &row_min, NULL,
2553 NULL, NULL);
2554 *minimum += row_min;
2555 }
2556
2557 /* We always allocate the minimum height, since handling expanding rows
2558 * is way too costly, and unlikely to be used, as lists are generally put
2559 * inside a scrolling window anyway.
2560 */
2561 *natural = *minimum;
2562 }
2563}
2564
2565static void
2566gtk_list_box_size_allocate (GtkWidget *widget,
2567 int width,
2568 int height,
2569 int baseline)
2570{
2571 GtkListBox *box = GTK_LIST_BOX (widget);
2572 GtkAllocation child_allocation;
2573 GtkAllocation header_allocation;
2574 GtkListBoxRow *row;
2575 GSequenceIter *iter;
2576 int child_min;
2577
2578
2579 child_allocation.x = 0;
2580 child_allocation.y = 0;
2581 child_allocation.width = width;
2582 child_allocation.height = 0;
2583
2584 header_allocation.x = 0;
2585 header_allocation.y = 0;
2586 header_allocation.width = width;
2587 header_allocation.height = 0;
2588
2589 if (box->placeholder && gtk_widget_get_child_visible (widget: box->placeholder))
2590 {
2591 gtk_widget_measure (widget: box->placeholder, orientation: GTK_ORIENTATION_VERTICAL,
2592 for_size: width,
2593 minimum: &child_min, NULL, NULL, NULL);
2594 header_allocation.height = height;
2595 header_allocation.y = child_allocation.y;
2596 gtk_widget_size_allocate (widget: box->placeholder, allocation: &header_allocation, baseline: -1);
2597 child_allocation.y += child_min;
2598 }
2599
2600 for (iter = g_sequence_get_begin_iter (seq: box->children);
2601 !g_sequence_iter_is_end (iter);
2602 iter = g_sequence_iter_next (iter))
2603 {
2604 row = g_sequence_get (iter);
2605 if (!row_is_visible (row))
2606 {
2607 ROW_PRIV (row)->y = child_allocation.y;
2608 ROW_PRIV (row)->height = 0;
2609 continue;
2610 }
2611
2612 if (ROW_PRIV (row)->header != NULL)
2613 {
2614 gtk_widget_measure (ROW_PRIV (row)->header, orientation: GTK_ORIENTATION_VERTICAL,
2615 for_size: width,
2616 minimum: &child_min, NULL, NULL, NULL);
2617 header_allocation.height = child_min;
2618 header_allocation.y = child_allocation.y;
2619 gtk_widget_size_allocate (ROW_PRIV (row)->header,
2620 allocation: &header_allocation,
2621 baseline: -1);
2622 child_allocation.y += child_min;
2623 }
2624
2625 ROW_PRIV (row)->y = child_allocation.y;
2626
2627 gtk_widget_measure (GTK_WIDGET (row), orientation: GTK_ORIENTATION_VERTICAL,
2628 for_size: child_allocation.width,
2629 minimum: &child_min, NULL, NULL, NULL);
2630 child_allocation.height = child_min;
2631
2632 ROW_PRIV (row)->height = child_allocation.height;
2633 gtk_widget_size_allocate (GTK_WIDGET (row), allocation: &child_allocation, baseline: -1);
2634 child_allocation.y += child_min;
2635 }
2636}
2637
2638/**
2639 * gtk_list_box_prepend:
2640 * @box: a `GtkListBox`
2641 * @child: the `GtkWidget` to add
2642 *
2643 * Prepend a widget to the list.
2644 *
2645 * If a sort function is set, the widget will
2646 * actually be inserted at the calculated position.
2647 */
2648void
2649gtk_list_box_prepend (GtkListBox *box,
2650 GtkWidget *child)
2651{
2652 gtk_list_box_insert (box, child, position: 0);
2653}
2654
2655/**
2656 * gtk_list_box_append:
2657 * @box: a `GtkListBox`
2658 * @child: the `GtkWidget` to add
2659 *
2660 * Append a widget to the list.
2661 *
2662 * If a sort function is set, the widget will
2663 * actually be inserted at the calculated position.
2664 */
2665void
2666gtk_list_box_append (GtkListBox *box,
2667 GtkWidget *child)
2668{
2669 gtk_list_box_insert (box, child, position: -1);
2670}
2671
2672/**
2673 * gtk_list_box_insert:
2674 * @box: a `GtkListBox`
2675 * @child: the `GtkWidget` to add
2676 * @position: the position to insert @child in
2677 *
2678 * Insert the @child into the @box at @position.
2679 *
2680 * If a sort function is
2681 * set, the widget will actually be inserted at the calculated position.
2682 *
2683 * If @position is -1, or larger than the total number of items in the
2684 * @box, then the @child will be appended to the end.
2685 */
2686void
2687gtk_list_box_insert (GtkListBox *box,
2688 GtkWidget *child,
2689 int position)
2690{
2691 GtkListBoxRow *row;
2692 GSequenceIter *prev = NULL;
2693 GSequenceIter *iter = NULL;
2694
2695 g_return_if_fail (GTK_IS_LIST_BOX (box));
2696 g_return_if_fail (GTK_IS_WIDGET (child));
2697
2698 if (GTK_IS_LIST_BOX_ROW (child))
2699 row = GTK_LIST_BOX_ROW (child);
2700 else
2701 {
2702 row = GTK_LIST_BOX_ROW (gtk_list_box_row_new ());
2703 gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), child);
2704 }
2705
2706 if (box->sort_func != NULL)
2707 iter = g_sequence_insert_sorted (seq: box->children, data: row,
2708 cmp_func: (GCompareDataFunc)do_sort, cmp_data: box);
2709 else if (position == 0)
2710 iter = g_sequence_prepend (seq: box->children, data: row);
2711 else if (position == -1)
2712 iter = g_sequence_append (seq: box->children, data: row);
2713 else
2714 {
2715 GSequenceIter *current_iter;
2716
2717 current_iter = g_sequence_get_iter_at_pos (seq: box->children, pos: position);
2718 iter = g_sequence_insert_before (iter: current_iter, data: row);
2719 }
2720
2721 ROW_PRIV (row)->iter = iter;
2722 prev = g_sequence_iter_prev (iter);
2723 gtk_widget_insert_after (GTK_WIDGET (row), GTK_WIDGET (box),
2724 previous_sibling: prev != iter ? g_sequence_get (iter: prev) : NULL);
2725
2726 gtk_widget_set_child_visible (GTK_WIDGET (row), TRUE);
2727 ROW_PRIV (row)->visible = gtk_widget_get_visible (GTK_WIDGET (row));
2728 if (ROW_PRIV (row)->visible)
2729 list_box_add_visible_rows (box, n: 1);
2730 gtk_list_box_apply_filter (box, row);
2731 gtk_list_box_update_row_style (box, row);
2732 if (gtk_widget_get_visible (GTK_WIDGET (box)))
2733 {
2734 gtk_list_box_update_header (box, ROW_PRIV (row)->iter);
2735 gtk_list_box_update_header (box,
2736 iter: gtk_list_box_get_next_visible (box, ROW_PRIV (row)->iter));
2737 }
2738}
2739
2740/**
2741 * gtk_list_box_drag_unhighlight_row:
2742 * @box: a `GtkListBox`
2743 *
2744 * If a row has previously been highlighted via gtk_list_box_drag_highlight_row(),
2745 * it will have the highlight removed.
2746 */
2747void
2748gtk_list_box_drag_unhighlight_row (GtkListBox *box)
2749{
2750 g_return_if_fail (GTK_IS_LIST_BOX (box));
2751
2752 if (box->drag_highlighted_row == NULL)
2753 return;
2754
2755 gtk_widget_unset_state_flags (GTK_WIDGET (box->drag_highlighted_row), flags: GTK_STATE_FLAG_DROP_ACTIVE);
2756 g_clear_object (&box->drag_highlighted_row);
2757}
2758
2759/**
2760 * gtk_list_box_drag_highlight_row:
2761 * @box: a `GtkListBox`
2762 * @row: a `GtkListBoxRow`
2763 *
2764 * Add a drag highlight to a row.
2765 *
2766 * This is a helper function for implementing DnD onto a `GtkListBox`.
2767 * The passed in @row will be highlighted by setting the
2768 * %GTK_STATE_FLAG_DROP_ACTIVE state and any previously highlighted
2769 * row will be unhighlighted.
2770 *
2771 * The row will also be unhighlighted when the widget gets
2772 * a drag leave event.
2773 */
2774void
2775gtk_list_box_drag_highlight_row (GtkListBox *box,
2776 GtkListBoxRow *row)
2777{
2778 g_return_if_fail (GTK_IS_LIST_BOX (box));
2779 g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
2780
2781 if (box->drag_highlighted_row == row)
2782 return;
2783
2784 gtk_list_box_drag_unhighlight_row (box);
2785 gtk_widget_set_state_flags (GTK_WIDGET (row), flags: GTK_STATE_FLAG_DROP_ACTIVE, FALSE);
2786 box->drag_highlighted_row = g_object_ref (row);
2787}
2788
2789static void
2790gtk_list_box_activate_cursor_row (GtkListBox *box)
2791{
2792 gtk_list_box_select_and_activate (box, box->cursor_row);
2793}
2794
2795static void
2796gtk_list_box_toggle_cursor_row (GtkListBox *box)
2797{
2798 if (box->cursor_row == NULL)
2799 return;
2800
2801 if ((box->selection_mode == GTK_SELECTION_SINGLE ||
2802 box->selection_mode == GTK_SELECTION_MULTIPLE) &&
2803 ROW_PRIV (box->cursor_row)->selected)
2804 gtk_list_box_unselect_row_internal (box, row: box->cursor_row);
2805 else
2806 gtk_list_box_select_and_activate (box, box->cursor_row);
2807}
2808
2809static void
2810gtk_list_box_move_cursor (GtkListBox *box,
2811 GtkMovementStep step,
2812 int count,
2813 gboolean extend,
2814 gboolean modify)
2815{
2816 GtkListBoxRow *row;
2817 int page_size;
2818 GSequenceIter *iter;
2819 int start_y;
2820 int end_y;
2821 int height;
2822
2823 row = NULL;
2824 switch ((guint) step)
2825 {
2826 case GTK_MOVEMENT_BUFFER_ENDS:
2827 if (count < 0)
2828 row = gtk_list_box_get_first_focusable (box);
2829 else
2830 row = gtk_list_box_get_last_focusable (box);
2831 break;
2832 case GTK_MOVEMENT_DISPLAY_LINES:
2833 if (box->cursor_row != NULL)
2834 {
2835 int i = count;
2836
2837 iter = ROW_PRIV (box->cursor_row)->iter;
2838
2839 while (i < 0 && iter != NULL)
2840 {
2841 iter = gtk_list_box_get_previous_visible (box, iter);
2842 i = i + 1;
2843 }
2844 while (i > 0 && iter != NULL)
2845 {
2846 iter = gtk_list_box_get_next_visible (box, iter);
2847 i = i - 1;
2848 }
2849
2850 if (iter != NULL && !g_sequence_iter_is_end (iter))
2851 row = g_sequence_get (iter);
2852 }
2853 break;
2854 case GTK_MOVEMENT_PAGES:
2855 page_size = 100;
2856 if (box->adjustment != NULL)
2857 page_size = gtk_adjustment_get_page_increment (adjustment: box->adjustment);
2858
2859 if (box->cursor_row != NULL)
2860 {
2861 start_y = ROW_PRIV (box->cursor_row)->y;
2862 height = gtk_widget_get_height (GTK_WIDGET (box));
2863 end_y = CLAMP (start_y + page_size * count, 0, height - 1);
2864 row = gtk_list_box_get_row_at_y (box, y: end_y);
2865
2866 if (!row)
2867 {
2868 GSequenceIter *cursor_iter;
2869 GSequenceIter *next_iter;
2870
2871 if (count > 0)
2872 {
2873 cursor_iter = ROW_PRIV (box->cursor_row)->iter;
2874 next_iter = gtk_list_box_get_last_visible (box, iter: cursor_iter);
2875
2876 if (next_iter)
2877 {
2878 row = g_sequence_get (iter: next_iter);
2879 end_y = ROW_PRIV (row)->y;
2880 }
2881 }
2882 else
2883 {
2884 row = gtk_list_box_get_row_at_index (box, index_: 0);
2885 end_y = ROW_PRIV (row)->y;
2886 }
2887 }
2888 else if (row == box->cursor_row)
2889 {
2890 iter = ROW_PRIV (row)->iter;
2891
2892 /* Move at least one row. This is important when the cursor_row's height is
2893 * greater than page_size */
2894 if (count < 0)
2895 iter = g_sequence_iter_prev (iter);
2896 else
2897 iter = g_sequence_iter_next (iter);
2898
2899 if (!g_sequence_iter_is_begin (iter) && !g_sequence_iter_is_end (iter))
2900 {
2901 row = g_sequence_get (iter);
2902 end_y = ROW_PRIV (row)->y;
2903 }
2904 }
2905
2906 if (end_y != start_y && box->adjustment != NULL)
2907 gtk_adjustment_animate_to_value (adjustment: box->adjustment, value: end_y);
2908 }
2909 break;
2910 default:
2911 return;
2912 }
2913
2914 if (row == NULL || row == box->cursor_row)
2915 {
2916 GtkDirectionType direction = count < 0 ? GTK_DIR_UP : GTK_DIR_DOWN;
2917
2918 if (!gtk_widget_keynav_failed (GTK_WIDGET (box), direction))
2919 {
2920 GtkWidget *toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (box)));
2921
2922 if (toplevel)
2923 gtk_widget_child_focus (widget: toplevel,
2924 direction: direction == GTK_DIR_UP ?
2925 GTK_DIR_TAB_BACKWARD :
2926 GTK_DIR_TAB_FORWARD);
2927
2928 }
2929
2930 return;
2931 }
2932
2933 gtk_list_box_update_cursor (box, row, TRUE);
2934 if (!modify)
2935 gtk_list_box_update_selection (box, row, FALSE, extend);
2936}
2937
2938
2939/**
2940 * gtk_list_box_row_new:
2941 *
2942 * Creates a new `GtkListBoxRow`.
2943 *
2944 * Returns: a new `GtkListBoxRow`
2945 */
2946GtkWidget *
2947gtk_list_box_row_new (void)
2948{
2949 return g_object_new (GTK_TYPE_LIST_BOX_ROW, NULL);
2950}
2951
2952/**
2953 * gtk_list_box_row_set_child: (attributes org.gtk.Method.set_property=child)
2954 * @row: a `GtkListBoxRow`
2955 * @child: (nullable): the child widget
2956 *
2957 * Sets the child widget of @self.
2958 */
2959void
2960gtk_list_box_row_set_child (GtkListBoxRow *row,
2961 GtkWidget *child)
2962{
2963 GtkListBoxRowPrivate *priv = ROW_PRIV (row);
2964
2965 g_clear_pointer (&priv->child, gtk_widget_unparent);
2966
2967 priv->child = child;
2968 if (child)
2969 gtk_widget_set_parent (widget: child, GTK_WIDGET (row));
2970
2971 g_object_notify_by_pspec (G_OBJECT (row), pspec: row_properties[ROW_PROP_CHILD]);
2972}
2973
2974/**
2975 * gtk_list_box_row_get_child: (attributes org.gtk.Method.get_property=child)
2976 * @row: a `GtkListBoxRow`
2977 *
2978 * Gets the child widget of @row.
2979 *
2980 * Returns: (nullable) (transfer none): the child widget of @row
2981 */
2982GtkWidget *
2983gtk_list_box_row_get_child (GtkListBoxRow *row)
2984{
2985 return ROW_PRIV (row)->child;
2986}
2987
2988static void
2989gtk_list_box_row_set_focus (GtkListBoxRow *row)
2990{
2991 GtkListBox *box = gtk_list_box_row_get_box (row);
2992
2993 if (!box)
2994 return;
2995
2996 gtk_list_box_update_selection (box, row, FALSE, FALSE);
2997}
2998
2999static gboolean
3000gtk_list_box_row_focus (GtkWidget *widget,
3001 GtkDirectionType direction)
3002{
3003 GtkListBoxRow *row = GTK_LIST_BOX_ROW (widget);
3004 gboolean had_focus = FALSE;
3005 GtkWidget *child = ROW_PRIV (row)->child;
3006 GtkWidget *focus_child = gtk_widget_get_focus_child (widget);
3007
3008 g_object_get (object: widget, first_property_name: "has-focus", &had_focus, NULL);
3009
3010 /* If a child has focus, always try to navigate within that first. */
3011 if (focus_child != NULL)
3012 {
3013 if (gtk_widget_child_focus (widget: focus_child, direction))
3014 return TRUE;
3015 }
3016
3017 /* Otherwise, decide based on the direction. */
3018 if (direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD)
3019 {
3020 /* If a child was focused and focus couldn't be moved within that (see
3021 * above), let focus leave. */
3022 if (focus_child != NULL)
3023 return FALSE;
3024
3025 /* If the row is not focused, try to focus it. */
3026 if (!had_focus && gtk_widget_grab_focus (widget))
3027 {
3028 gtk_list_box_row_set_focus (row);
3029 return TRUE;
3030 }
3031
3032 /* Finally, try to move focus into the child. */
3033 if (child != NULL && gtk_widget_child_focus (widget: child, direction))
3034 return TRUE;
3035
3036 return FALSE;
3037 }
3038 else if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD)
3039 {
3040 /* If the row itself is focused, let focus leave it. */
3041 if (had_focus)
3042 return FALSE;
3043
3044 /* Otherwise, let focus enter the child widget, if possible. */
3045 if (child != NULL && gtk_widget_child_focus (widget: child, direction))
3046 return TRUE;
3047
3048 /* If that didn't work, try to focus the row itself. */
3049 if (gtk_widget_grab_focus (widget))
3050 {
3051 gtk_list_box_row_set_focus (row);
3052 return TRUE;
3053 }
3054
3055 return FALSE;
3056 }
3057
3058 return FALSE;
3059}
3060
3061static void
3062gtk_list_box_row_activate (GtkListBoxRow *row)
3063{
3064 GtkListBox *box;
3065
3066 box = gtk_list_box_row_get_box (row);
3067 if (box)
3068 gtk_list_box_select_and_activate (box, row);
3069}
3070
3071static void
3072gtk_list_box_row_show (GtkWidget *widget)
3073{
3074 GtkListBoxRow *row = GTK_LIST_BOX_ROW (widget);
3075 GtkListBox *box;
3076
3077 GTK_WIDGET_CLASS (gtk_list_box_row_parent_class)->show (widget);
3078
3079 box = gtk_list_box_row_get_box (row);
3080 if (box)
3081 gtk_list_box_row_visibility_changed (box, row);
3082}
3083
3084static void
3085gtk_list_box_row_hide (GtkWidget *widget)
3086{
3087 GtkListBoxRow *row = GTK_LIST_BOX_ROW (widget);
3088 GtkListBox *box;
3089
3090 GTK_WIDGET_CLASS (gtk_list_box_row_parent_class)->hide (widget);
3091
3092 box = gtk_list_box_row_get_box (row);
3093 if (box)
3094 gtk_list_box_row_visibility_changed (box, row);
3095}
3096
3097static void
3098gtk_list_box_row_root (GtkWidget *widget)
3099{
3100 GtkListBoxRow *row = GTK_LIST_BOX_ROW (widget);
3101
3102 GTK_WIDGET_CLASS (gtk_list_box_row_parent_class)->root (widget);
3103
3104 if (ROW_PRIV (row)->selectable)
3105 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: row),
3106 first_state: GTK_ACCESSIBLE_STATE_SELECTED, ROW_PRIV (row)->selected,
3107 -1);
3108}
3109
3110/**
3111 * gtk_list_box_row_changed:
3112 * @row: a `GtkListBoxRow`
3113 *
3114 * Marks @row as changed, causing any state that depends on this
3115 * to be updated.
3116 *
3117 * This affects sorting, filtering and headers.
3118 *
3119 * Note that calls to this method must be in sync with the data
3120 * used for the row functions. For instance, if the list is
3121 * mirroring some external data set, and *two* rows changed in the
3122 * external data set then when you call gtk_list_box_row_changed()
3123 * on the first row the sort function must only read the new data
3124 * for the first of the two changed rows, otherwise the resorting
3125 * of the rows will be wrong.
3126 *
3127 * This generally means that if you don’t fully control the data
3128 * model you have to duplicate the data that affects the listbox
3129 * row functions into the row widgets themselves. Another alternative
3130 * is to call [method@Gtk.ListBox.invalidate_sort] on any model change,
3131 * but that is more expensive.
3132 */
3133void
3134gtk_list_box_row_changed (GtkListBoxRow *row)
3135{
3136 GtkListBox *box;
3137
3138 g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
3139
3140 box = gtk_list_box_row_get_box (row);
3141 if (box)
3142 gtk_list_box_got_row_changed (box, row);
3143}
3144
3145/**
3146 * gtk_list_box_row_get_header:
3147 * @row: a `GtkListBoxRow`
3148 *
3149 * Returns the current header of the @row.
3150 *
3151 * This can be used
3152 * in a [callback@Gtk.ListBoxUpdateHeaderFunc] to see if
3153 * there is a header set already, and if so to update
3154 * the state of it.
3155 *
3156 * Returns: (transfer none) (nullable): the current header
3157 */
3158GtkWidget *
3159gtk_list_box_row_get_header (GtkListBoxRow *row)
3160{
3161 g_return_val_if_fail (GTK_IS_LIST_BOX_ROW (row), NULL);
3162
3163 return ROW_PRIV (row)->header;
3164}
3165
3166/**
3167 * gtk_list_box_row_set_header:
3168 * @row: a `GtkListBoxRow`
3169 * @header: (nullable): the header
3170 *
3171 * Sets the current header of the @row.
3172 *
3173 * This is only allowed to be called
3174 * from a [callback@Gtk.ListBoxUpdateHeaderFunc].
3175 * It will replace any existing header in the row,
3176 * and be shown in front of the row in the listbox.
3177 */
3178void
3179gtk_list_box_row_set_header (GtkListBoxRow *row,
3180 GtkWidget *header)
3181{
3182 GtkListBoxRowPrivate *priv = ROW_PRIV (row);
3183
3184 g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
3185 g_return_if_fail (header == NULL || GTK_IS_WIDGET (header));
3186
3187 if (priv->header)
3188 g_object_unref (object: priv->header);
3189
3190 priv->header = header;
3191
3192 if (header)
3193 g_object_ref_sink (header);
3194}
3195
3196/**
3197 * gtk_list_box_row_get_index:
3198 * @row: a `GtkListBoxRow`
3199 *
3200 * Gets the current index of the @row in its `GtkListBox` container.
3201 *
3202 * Returns: the index of the @row, or -1 if the @row is not in a listbox
3203 */
3204int
3205gtk_list_box_row_get_index (GtkListBoxRow *row)
3206{
3207 GtkListBoxRowPrivate *priv = ROW_PRIV (row);
3208
3209 g_return_val_if_fail (GTK_IS_LIST_BOX_ROW (row), -1);
3210
3211 if (priv->iter != NULL)
3212 return g_sequence_iter_get_position (iter: priv->iter);
3213
3214 return -1;
3215}
3216
3217/**
3218 * gtk_list_box_row_is_selected:
3219 * @row: a `GtkListBoxRow`
3220 *
3221 * Returns whether the child is currently selected in its
3222 * `GtkListBox` container.
3223 *
3224 * Returns: %TRUE if @row is selected
3225 */
3226gboolean
3227gtk_list_box_row_is_selected (GtkListBoxRow *row)
3228{
3229 g_return_val_if_fail (GTK_IS_LIST_BOX_ROW (row), FALSE);
3230
3231 return ROW_PRIV (row)->selected;
3232}
3233
3234static void
3235gtk_list_box_update_row_style (GtkListBox *box,
3236 GtkListBoxRow *row)
3237{
3238 gboolean can_select;
3239
3240 if (box && box->selection_mode != GTK_SELECTION_NONE)
3241 can_select = TRUE;
3242 else
3243 can_select = FALSE;
3244
3245 if (ROW_PRIV (row)->activatable ||
3246 (ROW_PRIV (row)->selectable && can_select))
3247 gtk_widget_add_css_class (GTK_WIDGET (row), css_class: "activatable");
3248 else
3249 gtk_widget_remove_css_class (GTK_WIDGET (row), css_class: "activatable");
3250}
3251
3252static void
3253gtk_list_box_update_row_styles (GtkListBox *box)
3254{
3255 GSequenceIter *iter;
3256 GtkListBoxRow *row;
3257
3258 for (iter = g_sequence_get_begin_iter (seq: box->children);
3259 !g_sequence_iter_is_end (iter);
3260 iter = g_sequence_iter_next (iter))
3261 {
3262 row = g_sequence_get (iter);
3263 gtk_list_box_update_row_style (box, row);
3264 }
3265}
3266
3267/**
3268 * gtk_list_box_row_set_activatable: (attributes org.gtk.Method.set_property=activatable)
3269 * @row: a `GtkListBoxRow`
3270 * @activatable: %TRUE to mark the row as activatable
3271 *
3272 * Set whether the row is activatable.
3273 */
3274void
3275gtk_list_box_row_set_activatable (GtkListBoxRow *row,
3276 gboolean activatable)
3277{
3278 g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
3279
3280 activatable = activatable != FALSE;
3281
3282 if (ROW_PRIV (row)->activatable != activatable)
3283 {
3284 ROW_PRIV (row)->activatable = activatable;
3285
3286 gtk_list_box_update_row_style (box: gtk_list_box_row_get_box (row), row);
3287 g_object_notify_by_pspec (G_OBJECT (row), pspec: row_properties[ROW_PROP_ACTIVATABLE]);
3288 }
3289}
3290
3291/**
3292 * gtk_list_box_row_get_activatable: (attributes org.gtk.Method.get_property=activatable)
3293 * @row: a `GtkListBoxRow`
3294 *
3295 * Gets whether the row is activatable.
3296 *
3297 * Returns: %TRUE if the row is activatable
3298 */
3299gboolean
3300gtk_list_box_row_get_activatable (GtkListBoxRow *row)
3301{
3302 g_return_val_if_fail (GTK_IS_LIST_BOX_ROW (row), TRUE);
3303
3304 return ROW_PRIV (row)->activatable;
3305}
3306
3307/**
3308 * gtk_list_box_row_set_selectable: (attributes org.gtk.Method.set_property=selectable)
3309 * @row: a `GtkListBoxRow`
3310 * @selectable: %TRUE to mark the row as selectable
3311 *
3312 * Set whether the row can be selected.
3313 */
3314void
3315gtk_list_box_row_set_selectable (GtkListBoxRow *row,
3316 gboolean selectable)
3317{
3318 g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
3319
3320 selectable = selectable != FALSE;
3321
3322 if (ROW_PRIV (row)->selectable != selectable)
3323 {
3324 if (!selectable)
3325 gtk_list_box_row_set_selected (row, FALSE);
3326
3327 ROW_PRIV (row)->selectable = selectable;
3328
3329 if (selectable)
3330 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: row),
3331 first_state: GTK_ACCESSIBLE_STATE_SELECTED, FALSE,
3332 -1);
3333 else
3334 gtk_accessible_reset_state (self: GTK_ACCESSIBLE (ptr: row),
3335 state: GTK_ACCESSIBLE_STATE_SELECTED);
3336
3337 gtk_list_box_update_row_style (box: gtk_list_box_row_get_box (row), row);
3338
3339 g_object_notify_by_pspec (G_OBJECT (row), pspec: row_properties[ROW_PROP_SELECTABLE]);
3340 }
3341}
3342
3343/**
3344 * gtk_list_box_row_get_selectable: (attributes org.gtk.Method.get_property=selectable)
3345 * @row: a `GtkListBoxRow`
3346 *
3347 * Gets whether the row can be selected.
3348 *
3349 * Returns: %TRUE if the row is selectable
3350 */
3351gboolean
3352gtk_list_box_row_get_selectable (GtkListBoxRow *row)
3353{
3354 g_return_val_if_fail (GTK_IS_LIST_BOX_ROW (row), TRUE);
3355
3356 return ROW_PRIV (row)->selectable;
3357}
3358
3359static void
3360gtk_list_box_row_set_action_name (GtkActionable *actionable,
3361 const char *action_name)
3362{
3363 GtkListBoxRow *row = GTK_LIST_BOX_ROW (actionable);
3364 GtkListBoxRowPrivate *priv = ROW_PRIV (row);
3365
3366 if (!priv->action_helper)
3367 priv->action_helper = gtk_action_helper_new (widget: actionable);
3368
3369 gtk_action_helper_set_action_name (helper: priv->action_helper, action_name);
3370}
3371
3372static void
3373gtk_list_box_row_set_action_target_value (GtkActionable *actionable,
3374 GVariant *action_target)
3375{
3376 GtkListBoxRow *row = GTK_LIST_BOX_ROW (actionable);
3377 GtkListBoxRowPrivate *priv = ROW_PRIV (row);
3378
3379 if (!priv->action_helper)
3380 priv->action_helper = gtk_action_helper_new (widget: actionable);
3381
3382 gtk_action_helper_set_action_target_value (helper: priv->action_helper, action_target);
3383}
3384
3385static void
3386gtk_list_box_row_get_property (GObject *obj,
3387 guint property_id,
3388 GValue *value,
3389 GParamSpec *pspec)
3390{
3391 GtkListBoxRow *row = GTK_LIST_BOX_ROW (obj);
3392
3393 switch (property_id)
3394 {
3395 case ROW_PROP_ACTIVATABLE:
3396 g_value_set_boolean (value, v_boolean: gtk_list_box_row_get_activatable (row));
3397 break;
3398 case ROW_PROP_SELECTABLE:
3399 g_value_set_boolean (value, v_boolean: gtk_list_box_row_get_selectable (row));
3400 break;
3401 case ROW_PROP_ACTION_NAME:
3402 g_value_set_string (value, v_string: gtk_action_helper_get_action_name (ROW_PRIV (row)->action_helper));
3403 break;
3404 case ROW_PROP_ACTION_TARGET:
3405 g_value_set_variant (value, variant: gtk_action_helper_get_action_target_value (ROW_PRIV (row)->action_helper));
3406 break;
3407 case ROW_PROP_CHILD:
3408 g_value_set_object (value, v_object: gtk_list_box_row_get_child (row));
3409 break;
3410 default:
3411 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
3412 break;
3413 }
3414}
3415
3416static void
3417gtk_list_box_row_set_property (GObject *obj,
3418 guint property_id,
3419 const GValue *value,
3420 GParamSpec *pspec)
3421{
3422 GtkListBoxRow *row = GTK_LIST_BOX_ROW (obj);
3423
3424 switch (property_id)
3425 {
3426 case ROW_PROP_ACTIVATABLE:
3427 gtk_list_box_row_set_activatable (row, activatable: g_value_get_boolean (value));
3428 break;
3429 case ROW_PROP_SELECTABLE:
3430 gtk_list_box_row_set_selectable (row, selectable: g_value_get_boolean (value));
3431 break;
3432 case ROW_PROP_ACTION_NAME:
3433 gtk_list_box_row_set_action_name (GTK_ACTIONABLE (row), action_name: g_value_get_string (value));
3434 break;
3435 case ROW_PROP_ACTION_TARGET:
3436 gtk_list_box_row_set_action_target_value (GTK_ACTIONABLE (row), action_target: g_value_get_variant (value));
3437 break;
3438 case ROW_PROP_CHILD:
3439 gtk_list_box_row_set_child (row, child: g_value_get_object (value));
3440 break;
3441 default:
3442 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
3443 break;
3444 }
3445}
3446
3447static const char *
3448gtk_list_box_row_get_action_name (GtkActionable *actionable)
3449{
3450 GtkListBoxRow *row = GTK_LIST_BOX_ROW (actionable);
3451
3452 return gtk_action_helper_get_action_name (ROW_PRIV (row)->action_helper);
3453}
3454
3455static GVariant *
3456gtk_list_box_row_get_action_target_value (GtkActionable *actionable)
3457{
3458 GtkListBoxRow *row = GTK_LIST_BOX_ROW (actionable);
3459
3460 return gtk_action_helper_get_action_target_value (ROW_PRIV (row)->action_helper);
3461}
3462
3463static void
3464gtk_list_box_row_actionable_iface_init (GtkActionableInterface *iface)
3465{
3466 iface->get_action_name = gtk_list_box_row_get_action_name;
3467 iface->set_action_name = gtk_list_box_row_set_action_name;
3468 iface->get_action_target_value = gtk_list_box_row_get_action_target_value;
3469 iface->set_action_target_value = gtk_list_box_row_set_action_target_value;
3470}
3471
3472static void
3473gtk_list_box_row_finalize (GObject *obj)
3474{
3475 g_clear_object (&ROW_PRIV (GTK_LIST_BOX_ROW (obj))->header);
3476
3477 G_OBJECT_CLASS (gtk_list_box_row_parent_class)->finalize (obj);
3478}
3479
3480static void
3481gtk_list_box_row_dispose (GObject *object)
3482{
3483 GtkListBoxRow *row = GTK_LIST_BOX_ROW (object);
3484 GtkListBoxRowPrivate *priv = ROW_PRIV (row);
3485
3486 g_clear_object (&priv->action_helper);
3487 g_clear_pointer (&priv->child, gtk_widget_unparent);
3488
3489 G_OBJECT_CLASS (gtk_list_box_row_parent_class)->dispose (object);
3490}
3491
3492static gboolean
3493gtk_list_box_row_grab_focus (GtkWidget *widget)
3494{
3495 GtkListBoxRow *row = GTK_LIST_BOX_ROW (widget);
3496 GtkListBox *box = gtk_list_box_row_get_box (row);
3497
3498 g_return_val_if_fail (box != NULL, FALSE);
3499
3500 if (gtk_widget_grab_focus_self (widget))
3501 {
3502 if (box->cursor_row != row)
3503 gtk_list_box_update_cursor (box, row, FALSE);
3504
3505 return TRUE;
3506 }
3507
3508 return FALSE;
3509}
3510
3511static void
3512gtk_list_box_row_class_init (GtkListBoxRowClass *klass)
3513{
3514 GObjectClass *object_class = G_OBJECT_CLASS (klass);
3515 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
3516
3517 object_class->get_property = gtk_list_box_row_get_property;
3518 object_class->set_property = gtk_list_box_row_set_property;
3519 object_class->finalize = gtk_list_box_row_finalize;
3520 object_class->dispose = gtk_list_box_row_dispose;
3521
3522 widget_class->root = gtk_list_box_row_root;
3523 widget_class->show = gtk_list_box_row_show;
3524 widget_class->hide = gtk_list_box_row_hide;
3525 widget_class->focus = gtk_list_box_row_focus;
3526 widget_class->grab_focus = gtk_list_box_row_grab_focus;
3527
3528 klass->activate = gtk_list_box_row_activate;
3529
3530 /**
3531 * GtkListBoxRow::activate:
3532 *
3533 * This is a keybinding signal, which will cause this row to be activated.
3534 *
3535 * If you want to be notified when the user activates a row (by key or not),
3536 * use the [signal@Gtk.ListBox::row-activated] signal on the row’s parent
3537 * `GtkListBox`.
3538 */
3539 row_signals[ROW__ACTIVATE] =
3540 g_signal_new (I_("activate"),
3541 G_OBJECT_CLASS_TYPE (object_class),
3542 signal_flags: G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
3543 G_STRUCT_OFFSET (GtkListBoxRowClass, activate),
3544 NULL, NULL,
3545 NULL,
3546 G_TYPE_NONE, n_params: 0);
3547
3548 gtk_widget_class_set_activate_signal (widget_class, signal_id: row_signals[ROW__ACTIVATE]);
3549
3550 /**
3551 * GtkListBoxRow:activatable: (attributes org.gtk.Property.get=gtk_list_box_row_get_activatable org.gtk.Property.set=gtk_list_box_row_set_activatable)
3552 *
3553 * Determines whether the ::row-activated
3554 * signal will be emitted for this row.
3555 */
3556 row_properties[ROW_PROP_ACTIVATABLE] =
3557 g_param_spec_boolean (name: "activatable",
3558 P_("Activatable"),
3559 P_("Whether this row can be activated"),
3560 TRUE,
3561 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3562
3563 /**
3564 * GtkListBoxRow:selectable: (attributes org.gtk.Property.get=gtk_list_box_row_get_selectable org.gtk.Property.set=gtk_list_box_row_set_selectable)
3565 *
3566 * Determines whether this row can be selected.
3567 */
3568 row_properties[ROW_PROP_SELECTABLE] =
3569 g_param_spec_boolean (name: "selectable",
3570 P_("Selectable"),
3571 P_("Whether this row can be selected"),
3572 TRUE,
3573 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3574
3575 /**
3576 * GtkListBoxRow:child: (attributes org.gtk.Proeprty.get=gtk_list_box_row_get_child org.gtk.Property.set=gtk_list_box_row_set_child)
3577 *
3578 * The child widget.
3579 */
3580 row_properties[ROW_PROP_CHILD] =
3581 g_param_spec_object (name: "child",
3582 P_("Child"),
3583 P_("The child widget"),
3584 GTK_TYPE_WIDGET,
3585 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3586
3587 g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_ROW_PROPERTY, pspecs: row_properties);
3588
3589 g_object_class_override_property (oclass: object_class, property_id: ROW_PROP_ACTION_NAME, name: "action-name");
3590 g_object_class_override_property (oclass: object_class, property_id: ROW_PROP_ACTION_TARGET, name: "action-target");
3591
3592 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
3593 gtk_widget_class_set_css_name (widget_class, I_("row"));
3594 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_LIST_ITEM);
3595}
3596
3597static void
3598gtk_list_box_row_init (GtkListBoxRow *row)
3599{
3600 ROW_PRIV (row)->activatable = TRUE;
3601 ROW_PRIV (row)->selectable = TRUE;
3602
3603 gtk_widget_set_focusable (GTK_WIDGET (row), TRUE);
3604 gtk_widget_add_css_class (GTK_WIDGET (row), css_class: "activatable");
3605}
3606
3607static void
3608gtk_list_box_buildable_add_child (GtkBuildable *buildable,
3609 GtkBuilder *builder,
3610 GObject *child,
3611 const char *type)
3612{
3613 if (type && strcmp (s1: type, s2: "placeholder") == 0)
3614 gtk_list_box_set_placeholder (GTK_LIST_BOX (buildable), GTK_WIDGET (child));
3615 else if (GTK_IS_WIDGET (child))
3616 gtk_list_box_insert (GTK_LIST_BOX (buildable), GTK_WIDGET (child), position: -1);
3617 else
3618 parent_buildable_iface->add_child (buildable, builder, child, type);
3619}
3620
3621static void
3622gtk_list_box_buildable_interface_init (GtkBuildableIface *iface)
3623{
3624 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
3625
3626 iface->add_child = gtk_list_box_buildable_add_child;
3627}
3628
3629static void
3630gtk_list_box_bound_model_changed (GListModel *list,
3631 guint position,
3632 guint removed,
3633 guint added,
3634 gpointer user_data)
3635{
3636 GtkListBox *box = user_data;
3637 guint i;
3638
3639 while (removed--)
3640 {
3641 GtkListBoxRow *row;
3642
3643 row = gtk_list_box_get_row_at_index (box, index_: position);
3644 gtk_list_box_remove (box, GTK_WIDGET (row));
3645 }
3646
3647 for (i = 0; i < added; i++)
3648 {
3649 GObject *item;
3650 GtkWidget *widget;
3651
3652 item = g_list_model_get_item (list, position: position + i);
3653 widget = box->create_widget_func (item, box->create_widget_func_data);
3654
3655 /* We allow the create_widget_func to either return a full
3656 * reference or a floating reference. If we got the floating
3657 * reference, then turn it into a full reference now. That means
3658 * that gtk_list_box_insert() will take another full reference.
3659 * Finally, we'll release this full reference below, leaving only
3660 * the one held by the box.
3661 */
3662 if (g_object_is_floating (object: widget))
3663 g_object_ref_sink (widget);
3664
3665 gtk_widget_show (widget);
3666 gtk_list_box_insert (box, child: widget, position: position + i);
3667
3668 g_object_unref (object: widget);
3669 g_object_unref (object: item);
3670 }
3671}
3672
3673static void
3674gtk_list_box_check_model_compat (GtkListBox *box)
3675{
3676 if (box->bound_model &&
3677 (box->sort_func || box->filter_func))
3678 g_warning ("GtkListBox with a model will ignore sort and filter functions");
3679}
3680
3681/**
3682 * gtk_list_box_bind_model:
3683 * @box: a `GtkListBox`
3684 * @model: (nullable): the `GListModel` to be bound to @box
3685 * @create_widget_func: (nullable): a function that creates widgets for items
3686 * or %NULL in case you also passed %NULL as @model
3687 * @user_data: (closure): user data passed to @create_widget_func
3688 * @user_data_free_func: function for freeing @user_data
3689 *
3690 * Binds @model to @box.
3691 *
3692 * If @box was already bound to a model, that previous binding is
3693 * destroyed.
3694 *
3695 * The contents of @box are cleared and then filled with widgets that
3696 * represent items from @model. @box is updated whenever @model changes.
3697 * If @model is %NULL, @box is left empty.
3698 *
3699 * It is undefined to add or remove widgets directly (for example, with
3700 * [method@Gtk.ListBox.insert]) while @box is bound to a model.
3701 *
3702 * Note that using a model is incompatible with the filtering and sorting
3703 * functionality in `GtkListBox`. When using a model, filtering and sorting
3704 * should be implemented by the model.
3705 */
3706void
3707gtk_list_box_bind_model (GtkListBox *box,
3708 GListModel *model,
3709 GtkListBoxCreateWidgetFunc create_widget_func,
3710 gpointer user_data,
3711 GDestroyNotify user_data_free_func)
3712{
3713 GSequenceIter *iter;
3714
3715 g_return_if_fail (GTK_IS_LIST_BOX (box));
3716 g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
3717 g_return_if_fail (model == NULL || create_widget_func != NULL);
3718
3719 if (box->bound_model)
3720 {
3721 if (box->create_widget_func_data_destroy)
3722 box->create_widget_func_data_destroy (box->create_widget_func_data);
3723
3724 g_signal_handlers_disconnect_by_func (box->bound_model, gtk_list_box_bound_model_changed, box);
3725 g_clear_object (&box->bound_model);
3726 }
3727
3728 iter = g_sequence_get_begin_iter (seq: box->children);
3729 while (!g_sequence_iter_is_end (iter))
3730 {
3731 GtkWidget *row = g_sequence_get (iter);
3732 iter = g_sequence_iter_next (iter);
3733 gtk_list_box_remove (box, child: row);
3734 }
3735
3736
3737 if (model == NULL)
3738 return;
3739
3740 box->bound_model = g_object_ref (model);
3741 box->create_widget_func = create_widget_func;
3742 box->create_widget_func_data = user_data;
3743 box->create_widget_func_data_destroy = user_data_free_func;
3744
3745 gtk_list_box_check_model_compat (box);
3746
3747 g_signal_connect (box->bound_model, "items-changed", G_CALLBACK (gtk_list_box_bound_model_changed), box);
3748 gtk_list_box_bound_model_changed (list: model, position: 0, removed: 0, added: g_list_model_get_n_items (list: model), user_data: box);
3749}
3750
3751/**
3752 * gtk_list_box_set_show_separators: (attributes org.gtk.Method.set_property=show-separators)
3753 * @box: a `GtkListBox`
3754 * @show_separators: %TRUE to show separators
3755 *
3756 * Sets whether the list box should show separators
3757 * between rows.
3758 */
3759void
3760gtk_list_box_set_show_separators (GtkListBox *box,
3761 gboolean show_separators)
3762{
3763 g_return_if_fail (GTK_IS_LIST_BOX (box));
3764
3765 if (box->show_separators == show_separators)
3766 return;
3767
3768 box->show_separators = show_separators;
3769
3770 if (show_separators)
3771 gtk_widget_add_css_class (GTK_WIDGET (box), css_class: "separators");
3772 else
3773 gtk_widget_remove_css_class (GTK_WIDGET (box), css_class: "separators");
3774
3775 g_object_notify_by_pspec (G_OBJECT (box), pspec: properties[PROP_SHOW_SEPARATORS]);
3776}
3777
3778/**
3779 * gtk_list_box_get_show_separators: (attributes org.gtk.Method.get_property=show-separators)
3780 * @box: a `GtkListBox`
3781 *
3782 * Returns whether the list box should show separators
3783 * between rows.
3784 *
3785 * Returns: %TRUE if the list box shows separators
3786 */
3787gboolean
3788gtk_list_box_get_show_separators (GtkListBox *box)
3789{
3790 g_return_val_if_fail (GTK_IS_LIST_BOX (box), FALSE);
3791
3792 return box->show_separators;
3793}
3794

source code of gtk/gtk/gtklistbox.c