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