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