1 | /* |
2 | * Copyright © 2018 Benjamin Otte |
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.1 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 | * Authors: Benjamin Otte <otte@gnome.org> |
18 | */ |
19 | |
20 | #include "config.h" |
21 | |
22 | #include "gtklistitemwidgetprivate.h" |
23 | |
24 | #include "gtkbinlayout.h" |
25 | #include "gtkeventcontrollerfocus.h" |
26 | #include "gtkeventcontrollermotion.h" |
27 | #include "gtkgestureclick.h" |
28 | #include "gtkintl.h" |
29 | #include "gtklistitemfactoryprivate.h" |
30 | #include "gtklistitemprivate.h" |
31 | #include "gtklistbaseprivate.h" |
32 | #include "gtkmain.h" |
33 | #include "gtkselectionmodel.h" |
34 | #include "gtkwidget.h" |
35 | #include "gtkwidgetprivate.h" |
36 | |
37 | typedef struct _GtkListItemWidgetPrivate GtkListItemWidgetPrivate; |
38 | struct _GtkListItemWidgetPrivate |
39 | { |
40 | GtkListItemFactory *factory; |
41 | GtkListItem *list_item; |
42 | |
43 | GObject *item; |
44 | guint position; |
45 | gboolean selected; |
46 | gboolean single_click_activate; |
47 | }; |
48 | |
49 | enum { |
50 | PROP_0, |
51 | PROP_FACTORY, |
52 | PROP_SINGLE_CLICK_ACTIVATE, |
53 | |
54 | N_PROPS |
55 | }; |
56 | |
57 | enum |
58 | { |
59 | ACTIVATE_SIGNAL, |
60 | LAST_SIGNAL |
61 | }; |
62 | |
63 | G_DEFINE_TYPE_WITH_PRIVATE (GtkListItemWidget, gtk_list_item_widget, GTK_TYPE_WIDGET) |
64 | |
65 | static GParamSpec *properties[N_PROPS] = { NULL, }; |
66 | static guint signals[LAST_SIGNAL] = { 0 }; |
67 | |
68 | static void |
69 | gtk_list_item_widget_activate_signal (GtkListItemWidget *self) |
70 | { |
71 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
72 | |
73 | if (priv->list_item && !priv->list_item->activatable) |
74 | return; |
75 | |
76 | gtk_widget_activate_action (GTK_WIDGET (self), |
77 | name: "list.activate-item" , |
78 | format_string: "u" , |
79 | priv->position); |
80 | } |
81 | |
82 | static gboolean |
83 | gtk_list_item_widget_focus (GtkWidget *widget, |
84 | GtkDirectionType direction) |
85 | { |
86 | GtkWidget *child, *focus_child; |
87 | |
88 | /* The idea of this function is the following: |
89 | * 1. If any child can take focus, do not ever attempt |
90 | * to take focus. |
91 | * 2. Otherwise, if this item is selectable or activatable, |
92 | * allow focusing this widget. |
93 | * |
94 | * This makes sure every item in a list is focusable for |
95 | * activation and selection handling, but no useless widgets |
96 | * get focused and moving focus is as fast as possible. |
97 | */ |
98 | |
99 | focus_child = gtk_widget_get_focus_child (widget); |
100 | if (focus_child && gtk_widget_child_focus (widget: focus_child, direction)) |
101 | return TRUE; |
102 | |
103 | for (child = focus_child ? gtk_widget_get_next_sibling (widget: focus_child) |
104 | : gtk_widget_get_first_child (widget); |
105 | child; |
106 | child = gtk_widget_get_next_sibling (widget: child)) |
107 | { |
108 | if (gtk_widget_child_focus (widget: child, direction)) |
109 | return TRUE; |
110 | } |
111 | |
112 | if (focus_child) |
113 | return FALSE; |
114 | |
115 | if (gtk_widget_is_focus (widget)) |
116 | return FALSE; |
117 | |
118 | return gtk_widget_grab_focus (widget); |
119 | } |
120 | |
121 | static gboolean |
122 | gtk_list_item_widget_grab_focus (GtkWidget *widget) |
123 | { |
124 | GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (widget); |
125 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
126 | GtkWidget *child; |
127 | |
128 | for (child = gtk_widget_get_first_child (widget); |
129 | child; |
130 | child = gtk_widget_get_next_sibling (widget: child)) |
131 | { |
132 | if (gtk_widget_grab_focus (widget: child)) |
133 | return TRUE; |
134 | } |
135 | |
136 | if (priv->list_item == NULL || |
137 | !priv->list_item->selectable) |
138 | return FALSE; |
139 | |
140 | return GTK_WIDGET_CLASS (gtk_list_item_widget_parent_class)->grab_focus (widget); |
141 | } |
142 | |
143 | static void |
144 | gtk_list_item_widget_root (GtkWidget *widget) |
145 | { |
146 | GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (widget); |
147 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
148 | |
149 | GTK_WIDGET_CLASS (gtk_list_item_widget_parent_class)->root (widget); |
150 | |
151 | if (priv->factory) |
152 | gtk_list_item_factory_setup (self: priv->factory, widget: self); |
153 | } |
154 | |
155 | static void |
156 | gtk_list_item_widget_unroot (GtkWidget *widget) |
157 | { |
158 | GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (widget); |
159 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
160 | |
161 | GTK_WIDGET_CLASS (gtk_list_item_widget_parent_class)->unroot (widget); |
162 | |
163 | if (priv->list_item) |
164 | gtk_list_item_factory_teardown (self: priv->factory, widget: self); |
165 | } |
166 | |
167 | static void |
168 | gtk_list_item_widget_set_property (GObject *object, |
169 | guint property_id, |
170 | const GValue *value, |
171 | GParamSpec *pspec) |
172 | { |
173 | GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (object); |
174 | |
175 | switch (property_id) |
176 | { |
177 | case PROP_FACTORY: |
178 | gtk_list_item_widget_set_factory (self, factory: g_value_get_object (value)); |
179 | break; |
180 | |
181 | case PROP_SINGLE_CLICK_ACTIVATE: |
182 | gtk_list_item_widget_set_single_click_activate (self, single_click_activate: g_value_get_boolean (value)); |
183 | break; |
184 | |
185 | default: |
186 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
187 | break; |
188 | } |
189 | } |
190 | |
191 | static void |
192 | gtk_list_item_widget_dispose (GObject *object) |
193 | { |
194 | GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (object); |
195 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
196 | |
197 | g_assert (priv->list_item == NULL); |
198 | |
199 | g_clear_object (&priv->item); |
200 | g_clear_object (&priv->factory); |
201 | |
202 | G_OBJECT_CLASS (gtk_list_item_widget_parent_class)->dispose (object); |
203 | } |
204 | |
205 | static void |
206 | gtk_list_item_widget_select_action (GtkWidget *widget, |
207 | const char *action_name, |
208 | GVariant *parameter) |
209 | { |
210 | GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (widget); |
211 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
212 | gboolean modify, extend; |
213 | |
214 | if (priv->list_item && !priv->list_item->selectable) |
215 | return; |
216 | |
217 | g_variant_get (value: parameter, format_string: "(bb)" , &modify, &extend); |
218 | |
219 | gtk_widget_activate_action (GTK_WIDGET (self), |
220 | name: "list.select-item" , |
221 | format_string: "(ubb)" , |
222 | priv->position, modify, extend); |
223 | } |
224 | |
225 | static void |
226 | gtk_list_item_widget_class_init (GtkListItemWidgetClass *klass) |
227 | { |
228 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
229 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
230 | |
231 | klass->activate_signal = gtk_list_item_widget_activate_signal; |
232 | |
233 | widget_class->focus = gtk_list_item_widget_focus; |
234 | widget_class->grab_focus = gtk_list_item_widget_grab_focus; |
235 | widget_class->root = gtk_list_item_widget_root; |
236 | widget_class->unroot = gtk_list_item_widget_unroot; |
237 | |
238 | gobject_class->set_property = gtk_list_item_widget_set_property; |
239 | gobject_class->dispose = gtk_list_item_widget_dispose; |
240 | |
241 | properties[PROP_FACTORY] = |
242 | g_param_spec_object (name: "factory" , |
243 | nick: "Factory" , |
244 | blurb: "Factory managing this list item" , |
245 | GTK_TYPE_LIST_ITEM_FACTORY, |
246 | flags: G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); |
247 | |
248 | properties[PROP_SINGLE_CLICK_ACTIVATE] = |
249 | g_param_spec_boolean (name: "single-click-activate" , |
250 | nick: "Single click activate" , |
251 | blurb: "Activate on single click" , |
252 | FALSE, |
253 | flags: G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); |
254 | |
255 | g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPS, pspecs: properties); |
256 | |
257 | signals[ACTIVATE_SIGNAL] = |
258 | g_signal_new (I_("activate-keybinding" ), |
259 | G_OBJECT_CLASS_TYPE (gobject_class), |
260 | signal_flags: G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, |
261 | G_STRUCT_OFFSET (GtkListItemWidgetClass, activate_signal), |
262 | NULL, NULL, |
263 | NULL, |
264 | G_TYPE_NONE, n_params: 0); |
265 | |
266 | gtk_widget_class_set_activate_signal (widget_class, signal_id: signals[ACTIVATE_SIGNAL]); |
267 | |
268 | /** |
269 | * GtkListItem|listitem.select: |
270 | * @modify: %TRUE to toggle the existing selection, %FALSE to select |
271 | * @extend: %TRUE to extend the selection |
272 | * |
273 | * Changes selection if the item is selectable. |
274 | * If the item is not selectable, nothing happens. |
275 | * |
276 | * This function will emit the list.select-item action and the resulting |
277 | * behavior, in particular the interpretation of @modify and @extend |
278 | * depends on the view containing this listitem. See for example |
279 | * GtkListView|list.select-item or GtkGridView|list.select-item. |
280 | */ |
281 | gtk_widget_class_install_action (widget_class, |
282 | action_name: "listitem.select" , |
283 | parameter_type: "(bb)" , |
284 | activate: gtk_list_item_widget_select_action); |
285 | |
286 | gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_Return, mods: 0, |
287 | signal: "activate-keybinding" , format_string: 0); |
288 | gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_ISO_Enter, mods: 0, |
289 | signal: "activate-keybinding" , format_string: 0); |
290 | gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_KP_Enter, mods: 0, |
291 | signal: "activate-keybinding" , format_string: 0); |
292 | |
293 | /* note that some of these may get overwritten by child widgets, |
294 | * such as GtkTreeExpander */ |
295 | gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, mods: 0, |
296 | action_name: "listitem.select" , format_string: "(bb)" , TRUE, FALSE); |
297 | gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, mods: GDK_CONTROL_MASK, |
298 | action_name: "listitem.select" , format_string: "(bb)" , TRUE, FALSE); |
299 | gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, mods: GDK_SHIFT_MASK, |
300 | action_name: "listitem.select" , format_string: "(bb)" , TRUE, FALSE); |
301 | gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, mods: GDK_CONTROL_MASK | GDK_SHIFT_MASK, |
302 | action_name: "listitem.select" , format_string: "(bb)" , TRUE, FALSE); |
303 | gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, mods: 0, |
304 | action_name: "listitem.select" , format_string: "(bb)" , TRUE, FALSE); |
305 | gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, mods: GDK_CONTROL_MASK, |
306 | action_name: "listitem.select" , format_string: "(bb)" , TRUE, FALSE); |
307 | gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, mods: GDK_SHIFT_MASK, |
308 | action_name: "listitem.select" , format_string: "(bb)" , TRUE, FALSE); |
309 | gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, mods: GDK_CONTROL_MASK | GDK_SHIFT_MASK, |
310 | action_name: "listitem.select" , format_string: "(bb)" , TRUE, FALSE); |
311 | |
312 | /* This gets overwritten by gtk_list_item_widget_new() but better safe than sorry */ |
313 | gtk_widget_class_set_css_name (widget_class, I_("row" )); |
314 | gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); |
315 | } |
316 | |
317 | static void |
318 | gtk_list_item_widget_click_gesture_pressed (GtkGestureClick *gesture, |
319 | int n_press, |
320 | double x, |
321 | double y, |
322 | GtkListItemWidget *self) |
323 | { |
324 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
325 | GtkWidget *widget = GTK_WIDGET (self); |
326 | |
327 | if (priv->list_item && !priv->list_item->selectable && !priv->list_item->activatable) |
328 | { |
329 | gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_DENIED); |
330 | return; |
331 | } |
332 | |
333 | if (!priv->list_item || priv->list_item->activatable) |
334 | { |
335 | if (n_press == 2 && !priv->single_click_activate) |
336 | { |
337 | gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED); |
338 | gtk_widget_activate_action (GTK_WIDGET (self), |
339 | name: "list.activate-item" , |
340 | format_string: "u" , |
341 | priv->position); |
342 | } |
343 | } |
344 | |
345 | gtk_widget_set_state_flags (widget, flags: GTK_STATE_FLAG_ACTIVE, FALSE); |
346 | |
347 | if (gtk_widget_get_focus_on_click (widget)) |
348 | gtk_widget_grab_focus (widget); |
349 | } |
350 | |
351 | static void |
352 | gtk_list_item_widget_click_gesture_released (GtkGestureClick *gesture, |
353 | int n_press, |
354 | double x, |
355 | double y, |
356 | GtkListItemWidget *self) |
357 | { |
358 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
359 | |
360 | if (!priv->list_item || priv->list_item->activatable) |
361 | { |
362 | if (n_press == 1 && priv->single_click_activate) |
363 | { |
364 | gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED); |
365 | gtk_widget_activate_action (GTK_WIDGET (self), |
366 | name: "list.activate-item" , |
367 | format_string: "u" , |
368 | priv->position); |
369 | return; |
370 | } |
371 | } |
372 | |
373 | if (!priv->list_item || priv->list_item->selectable) |
374 | { |
375 | GdkModifierType state; |
376 | GdkEvent *event; |
377 | gboolean extend, modify; |
378 | |
379 | event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), |
380 | sequence: gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture))); |
381 | state = gdk_event_get_modifier_state (event); |
382 | extend = (state & GDK_SHIFT_MASK) != 0; |
383 | modify = (state & GDK_CONTROL_MASK) != 0; |
384 | |
385 | gtk_widget_activate_action (GTK_WIDGET (self), |
386 | name: "list.select-item" , |
387 | format_string: "(ubb)" , |
388 | priv->position, modify, extend); |
389 | } |
390 | |
391 | gtk_widget_unset_state_flags (GTK_WIDGET (self), flags: GTK_STATE_FLAG_ACTIVE); |
392 | } |
393 | |
394 | static void |
395 | gtk_list_item_widget_enter_cb (GtkEventControllerFocus *controller, |
396 | GtkListItemWidget *self) |
397 | { |
398 | GtkWidget *widget = GTK_WIDGET (self); |
399 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
400 | |
401 | gtk_widget_activate_action (widget, |
402 | name: "list.scroll-to-item" , |
403 | format_string: "u" , |
404 | priv->position); |
405 | } |
406 | |
407 | static void |
408 | gtk_list_item_widget_hover_cb (GtkEventControllerMotion *controller, |
409 | double x, |
410 | double y, |
411 | GtkListItemWidget *self) |
412 | { |
413 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
414 | |
415 | if (!priv->single_click_activate) |
416 | return; |
417 | |
418 | if (!priv->list_item || priv->list_item->selectable) |
419 | { |
420 | gtk_widget_activate_action (GTK_WIDGET (self), |
421 | name: "list.select-item" , |
422 | format_string: "(ubb)" , |
423 | priv->position, FALSE, FALSE); |
424 | } |
425 | } |
426 | |
427 | static void |
428 | gtk_list_item_widget_click_gesture_canceled (GtkGestureClick *gesture, |
429 | GdkEventSequence *sequence, |
430 | GtkListItemWidget *self) |
431 | { |
432 | gtk_widget_unset_state_flags (GTK_WIDGET (self), flags: GTK_STATE_FLAG_ACTIVE); |
433 | } |
434 | |
435 | static void |
436 | gtk_list_item_widget_init (GtkListItemWidget *self) |
437 | { |
438 | GtkEventController *controller; |
439 | GtkGesture *gesture; |
440 | |
441 | gtk_widget_set_focusable (GTK_WIDGET (self), TRUE); |
442 | |
443 | gesture = gtk_gesture_click_new (); |
444 | gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), |
445 | phase: GTK_PHASE_BUBBLE); |
446 | gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), |
447 | FALSE); |
448 | gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), |
449 | GDK_BUTTON_PRIMARY); |
450 | g_signal_connect (gesture, "pressed" , |
451 | G_CALLBACK (gtk_list_item_widget_click_gesture_pressed), self); |
452 | g_signal_connect (gesture, "released" , |
453 | G_CALLBACK (gtk_list_item_widget_click_gesture_released), self); |
454 | g_signal_connect (gesture, "cancel" , |
455 | G_CALLBACK (gtk_list_item_widget_click_gesture_canceled), self); |
456 | gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture)); |
457 | |
458 | controller = gtk_event_controller_focus_new (); |
459 | g_signal_connect (controller, "enter" , G_CALLBACK (gtk_list_item_widget_enter_cb), self); |
460 | gtk_widget_add_controller (GTK_WIDGET (self), controller); |
461 | |
462 | controller = gtk_event_controller_motion_new (); |
463 | g_signal_connect (controller, "enter" , G_CALLBACK (gtk_list_item_widget_hover_cb), self); |
464 | gtk_widget_add_controller (GTK_WIDGET (self), controller); |
465 | } |
466 | |
467 | GtkWidget * |
468 | gtk_list_item_widget_new (GtkListItemFactory *factory, |
469 | const char *css_name, |
470 | GtkAccessibleRole role) |
471 | { |
472 | g_return_val_if_fail (css_name != NULL, NULL); |
473 | |
474 | return g_object_new (GTK_TYPE_LIST_ITEM_WIDGET, |
475 | first_property_name: "css-name" , css_name, |
476 | "accessible-role" , role, |
477 | "factory" , factory, |
478 | NULL); |
479 | } |
480 | |
481 | void |
482 | gtk_list_item_widget_update (GtkListItemWidget *self, |
483 | guint position, |
484 | gpointer item, |
485 | gboolean selected) |
486 | { |
487 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
488 | gboolean was_selected; |
489 | |
490 | was_selected = priv->selected; |
491 | |
492 | if (priv->list_item) |
493 | gtk_list_item_factory_update (self: priv->factory, widget: self, position, item, selected); |
494 | else |
495 | gtk_list_item_widget_default_update (self, NULL, position, item, selected); |
496 | |
497 | /* don't look at selected variable, it's not reentrancy safe */ |
498 | if (was_selected != priv->selected) |
499 | { |
500 | if (priv->selected) |
501 | gtk_widget_set_state_flags (GTK_WIDGET (self), flags: GTK_STATE_FLAG_SELECTED, FALSE); |
502 | else |
503 | gtk_widget_unset_state_flags (GTK_WIDGET (self), flags: GTK_STATE_FLAG_SELECTED); |
504 | |
505 | gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: self), |
506 | first_state: GTK_ACCESSIBLE_STATE_SELECTED, priv->selected, |
507 | -1); |
508 | } |
509 | } |
510 | |
511 | void |
512 | gtk_list_item_widget_default_setup (GtkListItemWidget *self, |
513 | GtkListItem *list_item) |
514 | { |
515 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
516 | |
517 | priv->list_item = list_item; |
518 | list_item->owner = self; |
519 | |
520 | if (list_item->child) |
521 | gtk_list_item_widget_add_child (self, child: list_item->child); |
522 | |
523 | gtk_list_item_widget_set_activatable (self, activatable: list_item->activatable); |
524 | |
525 | if (priv->item) |
526 | g_object_notify (G_OBJECT (list_item), property_name: "item" ); |
527 | if (priv->position != GTK_INVALID_LIST_POSITION) |
528 | g_object_notify (G_OBJECT (list_item), property_name: "position" ); |
529 | if (priv->selected) |
530 | g_object_notify (G_OBJECT (list_item), property_name: "selected" ); |
531 | } |
532 | |
533 | void |
534 | gtk_list_item_widget_default_teardown (GtkListItemWidget *self, |
535 | GtkListItem *list_item) |
536 | { |
537 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
538 | |
539 | g_assert (priv->list_item == list_item); |
540 | |
541 | priv->list_item = NULL; |
542 | list_item->owner = NULL; |
543 | |
544 | if (list_item->child) |
545 | gtk_list_item_widget_remove_child (self, child: list_item->child); |
546 | |
547 | gtk_list_item_widget_set_activatable (self, FALSE); |
548 | |
549 | if (priv->item) |
550 | g_object_notify (G_OBJECT (list_item), property_name: "item" ); |
551 | if (priv->position != GTK_INVALID_LIST_POSITION) |
552 | g_object_notify (G_OBJECT (list_item), property_name: "position" ); |
553 | if (priv->selected) |
554 | g_object_notify (G_OBJECT (list_item), property_name: "selected" ); |
555 | } |
556 | |
557 | void |
558 | gtk_list_item_widget_default_update (GtkListItemWidget *self, |
559 | GtkListItem *list_item, |
560 | guint position, |
561 | gpointer item, |
562 | gboolean selected) |
563 | { |
564 | /* Track notify manually instead of freeze/thaw_notify for performance reasons. */ |
565 | gboolean notify_item = FALSE, notify_position = FALSE, notify_selected = FALSE; |
566 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
567 | |
568 | /* FIXME: It's kinda evil to notify external objects from here... */ |
569 | |
570 | if (g_set_object (&priv->item, item)) |
571 | notify_item = TRUE; |
572 | |
573 | if (priv->position != position) |
574 | { |
575 | priv->position = position; |
576 | notify_position = TRUE; |
577 | } |
578 | |
579 | if (priv->selected != selected) |
580 | { |
581 | priv->selected = selected; |
582 | notify_selected = TRUE; |
583 | } |
584 | |
585 | if (list_item) |
586 | gtk_list_item_do_notify (list_item, notify_item, notify_position, notify_selected); |
587 | } |
588 | |
589 | void |
590 | gtk_list_item_widget_set_factory (GtkListItemWidget *self, |
591 | GtkListItemFactory *factory) |
592 | { |
593 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
594 | |
595 | if (priv->factory == factory) |
596 | return; |
597 | |
598 | if (priv->factory) |
599 | { |
600 | if (priv->list_item) |
601 | gtk_list_item_factory_teardown (self: factory, widget: self); |
602 | g_clear_object (&priv->factory); |
603 | } |
604 | |
605 | if (factory) |
606 | { |
607 | priv->factory = g_object_ref (factory); |
608 | |
609 | if (gtk_widget_get_root (GTK_WIDGET (self))) |
610 | gtk_list_item_factory_setup (self: factory, widget: self); |
611 | } |
612 | |
613 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_FACTORY]); |
614 | } |
615 | |
616 | void |
617 | gtk_list_item_widget_set_single_click_activate (GtkListItemWidget *self, |
618 | gboolean single_click_activate) |
619 | { |
620 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
621 | |
622 | if (priv->single_click_activate == single_click_activate) |
623 | return; |
624 | |
625 | priv->single_click_activate = single_click_activate; |
626 | |
627 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SINGLE_CLICK_ACTIVATE]); |
628 | } |
629 | |
630 | void |
631 | gtk_list_item_widget_set_activatable (GtkListItemWidget *self, |
632 | gboolean activatable) |
633 | { |
634 | if (activatable) |
635 | gtk_widget_add_css_class (GTK_WIDGET (self), css_class: "activatable" ); |
636 | else |
637 | gtk_widget_remove_css_class (GTK_WIDGET (self), css_class: "activatable" ); |
638 | } |
639 | |
640 | void |
641 | gtk_list_item_widget_add_child (GtkListItemWidget *self, |
642 | GtkWidget *child) |
643 | { |
644 | gtk_widget_set_parent (widget: child, GTK_WIDGET (self)); |
645 | } |
646 | |
647 | void |
648 | gtk_list_item_widget_reorder_child (GtkListItemWidget *self, |
649 | GtkWidget *child, |
650 | guint position) |
651 | { |
652 | GtkWidget *widget = GTK_WIDGET (self); |
653 | GtkWidget *sibling = NULL; |
654 | |
655 | if (position > 0) |
656 | { |
657 | GtkWidget *c; |
658 | guint i; |
659 | |
660 | for (c = gtk_widget_get_first_child (widget), i = 0; |
661 | c; |
662 | c = gtk_widget_get_next_sibling (widget: c), i++) |
663 | { |
664 | if (i + 1 == position) |
665 | { |
666 | sibling = c; |
667 | break; |
668 | } |
669 | } |
670 | } |
671 | |
672 | if (child != sibling) |
673 | gtk_widget_insert_after (widget: child, parent: widget, previous_sibling: sibling); |
674 | } |
675 | |
676 | void |
677 | gtk_list_item_widget_remove_child (GtkListItemWidget *self, |
678 | GtkWidget *child) |
679 | { |
680 | gtk_widget_unparent (widget: child); |
681 | } |
682 | |
683 | GtkListItem * |
684 | gtk_list_item_widget_get_list_item (GtkListItemWidget *self) |
685 | { |
686 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
687 | |
688 | return priv->list_item; |
689 | } |
690 | |
691 | guint |
692 | gtk_list_item_widget_get_position (GtkListItemWidget *self) |
693 | { |
694 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
695 | |
696 | return priv->position; |
697 | } |
698 | |
699 | gpointer |
700 | gtk_list_item_widget_get_item (GtkListItemWidget *self) |
701 | { |
702 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
703 | |
704 | return priv->item; |
705 | } |
706 | |
707 | gboolean |
708 | gtk_list_item_widget_get_selected (GtkListItemWidget *self) |
709 | { |
710 | GtkListItemWidgetPrivate *priv = gtk_list_item_widget_get_instance_private (self); |
711 | |
712 | return priv->selected; |
713 | } |
714 | |
715 | |