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