1/*
2 * Copyright © 2019 Red Hat, Inc.
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 licence, 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 * Author: Matthias Clasen
18 */
19
20#include "config.h"
21
22#include "gtktreepopoverprivate.h"
23
24#include "gtktreemodel.h"
25#include "gtkcellarea.h"
26#include "gtkcelllayout.h"
27#include "gtkcellview.h"
28#include "gtkintl.h"
29#include "gtkprivate.h"
30#include "gtkgizmoprivate.h"
31#include "gtkwidgetprivate.h"
32#include "gtkbuiltiniconprivate.h"
33#include "gtkscrolledwindow.h"
34#include "gtkviewport.h"
35
36// TODO
37// positioning + sizing
38
39struct _GtkTreePopover
40{
41 GtkPopover parent_instance;
42
43 GtkTreeModel *model;
44
45 GtkCellArea *area;
46 GtkCellAreaContext *context;
47
48 gulong size_changed_id;
49 gulong row_inserted_id;
50 gulong row_deleted_id;
51 gulong row_changed_id;
52 gulong row_reordered_id;
53 gulong apply_attributes_id;
54
55 GtkTreeViewRowSeparatorFunc row_separator_func;
56 gpointer row_separator_data;
57 GDestroyNotify row_separator_destroy;
58
59 GtkWidget *active_item;
60};
61
62enum {
63 PROP_0,
64 PROP_MODEL,
65 PROP_CELL_AREA,
66
67 NUM_PROPERTIES
68};
69
70enum {
71 MENU_ACTIVATE,
72 NUM_SIGNALS
73};
74
75static guint signals[NUM_SIGNALS];
76
77static void gtk_tree_popover_cell_layout_init (GtkCellLayoutIface *iface);
78static void gtk_tree_popover_set_area (GtkTreePopover *popover,
79 GtkCellArea *area);
80static void rebuild_menu (GtkTreePopover *popover);
81static void context_size_changed_cb (GtkCellAreaContext *context,
82 GParamSpec *pspec,
83 GtkWidget *popover);
84static GtkWidget * gtk_tree_popover_create_item (GtkTreePopover *popover,
85 GtkTreePath *path,
86 GtkTreeIter *iter,
87 gboolean header_item);
88static GtkWidget * gtk_tree_popover_get_path_item (GtkTreePopover *popover,
89 GtkTreePath *search);
90static void gtk_tree_popover_set_active_item (GtkTreePopover *popover,
91 GtkWidget *item);
92
93G_DEFINE_TYPE_WITH_CODE (GtkTreePopover, gtk_tree_popover, GTK_TYPE_POPOVER,
94 G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
95 gtk_tree_popover_cell_layout_init));
96
97static void
98gtk_tree_popover_constructed (GObject *object)
99{
100 GtkTreePopover *popover = GTK_TREE_POPOVER (ptr: object);
101
102 G_OBJECT_CLASS (gtk_tree_popover_parent_class)->constructed (object);
103
104 if (!popover->area)
105 {
106 GtkCellArea *area = gtk_cell_area_box_new ();
107 gtk_tree_popover_set_area (popover, area);
108 }
109
110 popover->context = gtk_cell_area_create_context (area: popover->area);
111
112 popover->size_changed_id = g_signal_connect (popover->context, "notify",
113 G_CALLBACK (context_size_changed_cb), popover);
114}
115
116static void
117gtk_tree_popover_dispose (GObject *object)
118{
119 GtkTreePopover *popover = GTK_TREE_POPOVER (ptr: object);
120
121 gtk_tree_popover_set_model (popover, NULL);
122 gtk_tree_popover_set_area (popover, NULL);
123
124 if (popover->context)
125 {
126 g_signal_handler_disconnect (instance: popover->context, handler_id: popover->size_changed_id);
127 popover->size_changed_id = 0;
128
129 g_clear_object (&popover->context);
130 }
131
132 G_OBJECT_CLASS (gtk_tree_popover_parent_class)->dispose (object);
133}
134
135static void
136gtk_tree_popover_finalize (GObject *object)
137{
138 GtkTreePopover *popover = GTK_TREE_POPOVER (ptr: object);
139
140 if (popover->row_separator_destroy)
141 popover->row_separator_destroy (popover->row_separator_data);
142
143 G_OBJECT_CLASS (gtk_tree_popover_parent_class)->finalize (object);
144}
145
146static void
147gtk_tree_popover_set_property (GObject *object,
148 guint prop_id,
149 const GValue *value,
150 GParamSpec *pspec)
151{
152 GtkTreePopover *popover = GTK_TREE_POPOVER (ptr: object);
153
154 switch (prop_id)
155 {
156 case PROP_MODEL:
157 gtk_tree_popover_set_model (popover, model: g_value_get_object (value));
158 break;
159
160 case PROP_CELL_AREA:
161 gtk_tree_popover_set_area (popover, area: g_value_get_object (value));
162 break;
163
164 default:
165 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
166 break;
167 }
168}
169
170static void
171gtk_tree_popover_get_property (GObject *object,
172 guint prop_id,
173 GValue *value,
174 GParamSpec *pspec)
175{
176 GtkTreePopover *popover = GTK_TREE_POPOVER (ptr: object);
177
178 switch (prop_id)
179 {
180 case PROP_MODEL:
181 g_value_set_object (value, v_object: popover->model);
182 break;
183
184 case PROP_CELL_AREA:
185 g_value_set_object (value, v_object: popover->area);
186 break;
187
188 default:
189 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
190 break;
191 }
192}
193
194static void
195gtk_tree_popover_class_init (GtkTreePopoverClass *class)
196{
197 GObjectClass *object_class = G_OBJECT_CLASS (class);
198
199 object_class->constructed = gtk_tree_popover_constructed;
200 object_class->dispose = gtk_tree_popover_dispose;
201 object_class->finalize = gtk_tree_popover_finalize;
202 object_class->set_property = gtk_tree_popover_set_property;
203 object_class->get_property = gtk_tree_popover_get_property;
204
205 g_object_class_install_property (oclass: object_class,
206 property_id: PROP_MODEL,
207 pspec: g_param_spec_object (name: "model",
208 P_("model"),
209 P_("The model for the popover"),
210 GTK_TYPE_TREE_MODEL,
211 GTK_PARAM_READWRITE));
212
213 g_object_class_install_property (oclass: object_class,
214 property_id: PROP_CELL_AREA,
215 pspec: g_param_spec_object (name: "cell-area",
216 P_("Cell Area"),
217 P_("The GtkCellArea used to layout cells"),
218 GTK_TYPE_CELL_AREA,
219 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
220
221 signals[MENU_ACTIVATE] =
222 g_signal_new (I_("menu-activate"),
223 G_OBJECT_CLASS_TYPE (object_class),
224 signal_flags: G_SIGNAL_RUN_FIRST,
225 class_offset: 0,
226 NULL, NULL,
227 NULL,
228 G_TYPE_NONE, n_params: 1, G_TYPE_STRING);
229}
230
231static GtkWidget *
232gtk_tree_popover_get_stack (GtkTreePopover *popover)
233{
234 GtkWidget *sw = gtk_popover_get_child (GTK_POPOVER (popover));
235 GtkWidget *vp = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (sw));
236 GtkWidget *stack = gtk_viewport_get_child (GTK_VIEWPORT (vp));
237
238 return stack;
239}
240
241static void
242gtk_tree_popover_add_submenu (GtkTreePopover *popover,
243 GtkWidget *submenu,
244 const char *name)
245{
246 GtkWidget *stack = gtk_tree_popover_get_stack (popover);
247 gtk_stack_add_named (GTK_STACK (stack), child: submenu, name);
248}
249
250static GtkWidget *
251gtk_tree_popover_get_submenu (GtkTreePopover *popover,
252 const char *name)
253{
254 GtkWidget *stack = gtk_tree_popover_get_stack (popover);
255 return gtk_stack_get_child_by_name (GTK_STACK (stack), name);
256}
257
258void
259gtk_tree_popover_open_submenu (GtkTreePopover *popover,
260 const char *name)
261{
262 GtkWidget *stack = gtk_tree_popover_get_stack (popover);
263 gtk_stack_set_visible_child_name (GTK_STACK (stack), name);
264}
265
266static void
267gtk_tree_popover_init (GtkTreePopover *popover)
268{
269 GtkWidget *sw;
270 GtkWidget *stack;
271
272 sw = gtk_scrolled_window_new ();
273 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), hscrollbar_policy: GTK_POLICY_NEVER, vscrollbar_policy: GTK_POLICY_AUTOMATIC);
274 gtk_scrolled_window_set_propagate_natural_width (GTK_SCROLLED_WINDOW (sw), TRUE);
275 gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (sw), TRUE);
276 gtk_popover_set_child (GTK_POPOVER (popover), child: sw);
277
278 stack = gtk_stack_new ();
279 gtk_stack_set_vhomogeneous (GTK_STACK (stack), FALSE);
280 gtk_stack_set_transition_type (GTK_STACK (stack), transition: GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
281 gtk_stack_set_interpolate_size (GTK_STACK (stack), TRUE);
282 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child: stack);
283
284 gtk_widget_add_css_class (GTK_WIDGET (popover), css_class: "menu");
285}
286
287static GtkCellArea *
288gtk_tree_popover_cell_layout_get_area (GtkCellLayout *layout)
289{
290 return GTK_TREE_POPOVER (ptr: layout)->area;
291}
292
293static void
294gtk_tree_popover_cell_layout_init (GtkCellLayoutIface *iface)
295{
296 iface->get_area = gtk_tree_popover_cell_layout_get_area;
297}
298
299static void
300insert_at_position (GtkBox *box,
301 GtkWidget *child,
302 int position)
303{
304 GtkWidget *sibling = NULL;
305
306 if (position > 0)
307 {
308 int i;
309
310 sibling = gtk_widget_get_first_child (GTK_WIDGET (box));
311 for (i = 1; i < position; i++)
312 sibling = gtk_widget_get_next_sibling (widget: sibling);
313 }
314
315 gtk_box_insert_child_after (box, child, sibling);
316}
317
318static GtkWidget *
319ensure_submenu (GtkTreePopover *popover,
320 GtkTreePath *path)
321{
322 GtkWidget *box;
323 char *name;
324
325 if (path)
326 name = gtk_tree_path_to_string (path);
327 else
328 name = NULL;
329
330 box = gtk_tree_popover_get_submenu (popover, name: name ? name : "main");
331 if (!box)
332 {
333 box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0);
334 gtk_tree_popover_add_submenu (popover, submenu: box, name: name ? name : "main");
335 if (path)
336 {
337 GtkTreeIter iter;
338 GtkWidget *item;
339 gtk_tree_model_get_iter (tree_model: popover->model, iter: &iter, path);
340 item = gtk_tree_popover_create_item (popover, path, iter: &iter, TRUE);
341 gtk_box_append (GTK_BOX (box), child: item);
342 gtk_box_append (GTK_BOX (box), child: gtk_separator_new (orientation: GTK_ORIENTATION_HORIZONTAL));
343 }
344
345 }
346
347 g_free (mem: name);
348
349 return box;
350}
351
352static void
353row_inserted_cb (GtkTreeModel *model,
354 GtkTreePath *path,
355 GtkTreeIter *iter,
356 GtkTreePopover *popover)
357{
358 int *indices, depth, index;
359 GtkWidget *item;
360 GtkWidget *box;
361
362 indices = gtk_tree_path_get_indices (path);
363 depth = gtk_tree_path_get_depth (path);
364 index = indices[depth - 1];
365
366 item = gtk_tree_popover_create_item (popover, path, iter, FALSE);
367 if (depth == 1)
368 {
369 box = ensure_submenu (popover, NULL);
370 insert_at_position (GTK_BOX (box), child: item, position: index);
371 }
372 else
373 {
374 GtkTreePath *ppath;
375
376 ppath = gtk_tree_path_copy (path);
377 gtk_tree_path_up (path: ppath);
378
379 box = ensure_submenu (popover, path: ppath);
380 insert_at_position (GTK_BOX (box), child: item, position: index + 2);
381
382 gtk_tree_path_free (path: ppath);
383 }
384
385 gtk_cell_area_context_reset (context: popover->context);
386}
387
388static void
389row_deleted_cb (GtkTreeModel *model,
390 GtkTreePath *path,
391 GtkTreePopover *popover)
392{
393 GtkWidget *item;
394
395 item = gtk_tree_popover_get_path_item (popover, search: path);
396
397 if (item)
398 {
399 gtk_widget_unparent (widget: item);
400 gtk_cell_area_context_reset (context: popover->context);
401 }
402}
403
404static void
405row_changed_cb (GtkTreeModel *model,
406 GtkTreePath *path,
407 GtkTreeIter *iter,
408 GtkTreePopover *popover)
409{
410 gboolean is_separator = FALSE;
411 GtkWidget *item;
412 int *indices, depth, index;
413
414 item = gtk_tree_popover_get_path_item (popover, search: path);
415
416 if (!item)
417 return;
418
419 indices = gtk_tree_path_get_indices (path);
420 depth = gtk_tree_path_get_depth (path);
421 index = indices[depth - 1];
422
423 if (popover->row_separator_func)
424 is_separator = popover->row_separator_func (model, iter, popover->row_separator_data);
425
426 if (is_separator != GTK_IS_SEPARATOR (item))
427 {
428 GtkWidget *box = gtk_widget_get_parent (widget: item);
429
430 gtk_box_remove (GTK_BOX (box), child: item);
431
432 item = gtk_tree_popover_create_item (popover, path, iter, FALSE);
433
434 if (depth == 1)
435 insert_at_position (GTK_BOX (box), child: item, position: index);
436 else
437 insert_at_position (GTK_BOX (box), child: item, position: index + 2);
438 }
439}
440
441static void
442row_reordered_cb (GtkTreeModel *model,
443 GtkTreePath *path,
444 GtkTreeIter *iter,
445 int *new_order,
446 GtkTreePopover *popover)
447{
448 rebuild_menu (popover);
449}
450
451static void
452context_size_changed_cb (GtkCellAreaContext *context,
453 GParamSpec *pspec,
454 GtkWidget *popover)
455{
456 if (!strcmp (s1: pspec->name, s2: "minimum-width") ||
457 !strcmp (s1: pspec->name, s2: "natural-width") ||
458 !strcmp (s1: pspec->name, s2: "minimum-height") ||
459 !strcmp (s1: pspec->name, s2: "natural-height"))
460 gtk_widget_queue_resize (widget: popover);
461}
462
463static gboolean
464area_is_sensitive (GtkCellArea *area)
465{
466 GList *cells, *list;
467 gboolean sensitive = FALSE;
468
469 cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area));
470
471 for (list = cells; list; list = list->next)
472 {
473 g_object_get (object: list->data, first_property_name: "sensitive", &sensitive, NULL);
474
475 if (sensitive)
476 break;
477 }
478 g_list_free (list: cells);
479
480 return sensitive;
481}
482
483static GtkWidget *
484gtk_tree_popover_get_path_item (GtkTreePopover *popover,
485 GtkTreePath *search)
486{
487 GtkWidget *stack = gtk_tree_popover_get_stack (popover);
488 GtkWidget *item = NULL;
489 GtkWidget *stackchild;
490 GtkWidget *child;
491
492 for (stackchild = gtk_widget_get_first_child (widget: stack);
493 stackchild != NULL;
494 stackchild = gtk_widget_get_next_sibling (widget: stackchild))
495 {
496 for (child = gtk_widget_get_first_child (widget: stackchild);
497 !item && child;
498 child = gtk_widget_get_next_sibling (widget: child))
499 {
500 GtkTreePath *path = NULL;
501
502 if (GTK_IS_SEPARATOR (child))
503 {
504 GtkTreeRowReference *row = g_object_get_data (G_OBJECT (child), key: "gtk-tree-path");
505
506 if (row)
507 {
508 path = gtk_tree_row_reference_get_path (reference: row);
509 if (!path)
510 item = child;
511 }
512 }
513 else
514 {
515 GtkWidget *view = GTK_WIDGET (g_object_get_data (G_OBJECT (child), "view"));
516
517 path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
518
519 if (!path)
520 item = child;
521 }
522
523 if (path)
524 {
525 if (gtk_tree_path_compare (a: search, b: path) == 0)
526 item = child;
527 gtk_tree_path_free (path);
528 }
529 }
530 }
531
532 return item;
533}
534
535static void
536area_apply_attributes_cb (GtkCellArea *area,
537 GtkTreeModel *tree_model,
538 GtkTreeIter *iter,
539 gboolean is_expander,
540 gboolean is_expanded,
541 GtkTreePopover *popover)
542{
543 GtkTreePath*path;
544 GtkWidget *item;
545 gboolean sensitive;
546 GtkTreeIter dummy;
547 gboolean has_submenu = FALSE;
548
549 if (gtk_tree_model_iter_children (tree_model: popover->model, iter: &dummy, parent: iter))
550 has_submenu = TRUE;
551
552 path = gtk_tree_model_get_path (tree_model, iter);
553 item = gtk_tree_popover_get_path_item (popover, search: path);
554
555 if (item)
556 {
557 sensitive = area_is_sensitive (area: popover->area);
558 gtk_widget_set_sensitive (widget: item, sensitive: sensitive || has_submenu);
559 }
560
561 gtk_tree_path_free (path);
562}
563
564static void
565gtk_tree_popover_set_area (GtkTreePopover *popover,
566 GtkCellArea *area)
567{
568 if (popover->area)
569 {
570 g_signal_handler_disconnect (instance: popover->area, handler_id: popover->apply_attributes_id);
571 popover->apply_attributes_id = 0;
572 g_clear_object (&popover->area);
573 }
574
575 popover->area = area;
576
577 if (popover->area)
578 {
579 g_object_ref_sink (popover->area);
580 popover->apply_attributes_id = g_signal_connect (popover->area, "apply-attributes",
581 G_CALLBACK (area_apply_attributes_cb), popover);
582 }
583}
584
585static void
586activate_item (GtkWidget *item,
587 GtkTreePopover *popover)
588{
589 GtkCellView *view;
590 GtkTreePath *path;
591 char *path_str;
592 gboolean is_header = FALSE;
593 gboolean has_submenu = FALSE;
594
595 is_header = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "is-header"));
596
597 view = GTK_CELL_VIEW (g_object_get_data (G_OBJECT (item), "view"));
598
599 path = gtk_cell_view_get_displayed_row (cell_view: view);
600
601 if (is_header)
602 {
603 gtk_tree_path_up (path);
604 }
605 else
606 {
607 GtkTreeIter iter;
608 GtkTreeIter dummy;
609
610 gtk_tree_model_get_iter (tree_model: popover->model, iter: &iter, path);
611 if (gtk_tree_model_iter_children (tree_model: popover->model, iter: &dummy, parent: &iter))
612 has_submenu = TRUE;
613 }
614
615 path_str = gtk_tree_path_to_string (path);
616
617 if (is_header || has_submenu)
618 {
619 gtk_tree_popover_open_submenu (popover, name: path_str ? path_str : "main");
620 }
621 else
622 {
623 g_signal_emit (instance: popover, signal_id: signals[MENU_ACTIVATE], detail: 0, path_str);
624 gtk_popover_popdown (GTK_POPOVER (popover));
625 }
626
627 g_free (mem: path_str);
628 gtk_tree_path_free (path);
629}
630
631static void
632item_activated_cb (GtkGesture *gesture,
633 guint n_press,
634 double x,
635 double y,
636 GtkTreePopover *popover)
637{
638 GtkWidget *item = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
639 activate_item (item, popover);
640}
641
642static void
643enter_cb (GtkEventController *controller,
644 double x,
645 double y,
646 GtkTreePopover *popover)
647{
648 GtkWidget *item;
649 item = gtk_event_controller_get_widget (controller);
650
651 gtk_tree_popover_set_active_item (popover, item);
652}
653
654static void
655enter_focus_cb (GtkEventController *controller,
656 GtkTreePopover *popover)
657{
658 GtkWidget *item = gtk_event_controller_get_widget (controller);
659
660 gtk_tree_popover_set_active_item (popover, item);
661}
662
663static gboolean
664activate_shortcut (GtkWidget *widget,
665 GVariant *args,
666 gpointer user_data)
667{
668 activate_item (item: widget, popover: user_data);
669 return TRUE;
670}
671
672static GtkWidget *
673gtk_tree_popover_create_item (GtkTreePopover *popover,
674 GtkTreePath *path,
675 GtkTreeIter *iter,
676 gboolean header_item)
677{
678 GtkWidget *item, *view;
679 gboolean is_separator = FALSE;
680
681 if (popover->row_separator_func)
682 is_separator = popover->row_separator_func (popover->model, iter, popover->row_separator_data);
683
684 if (is_separator)
685 {
686 item = gtk_separator_new (orientation: GTK_ORIENTATION_HORIZONTAL);
687 g_object_set_data_full (G_OBJECT (item), key: "gtk-tree-path",
688 data: gtk_tree_row_reference_new (model: popover->model, path),
689 destroy: (GDestroyNotify)gtk_tree_row_reference_free);
690 }
691 else
692 {
693 GtkEventController *controller;
694 GtkTreeIter dummy;
695 gboolean has_submenu = FALSE;
696 GtkWidget *indicator;
697
698 if (!header_item &&
699 gtk_tree_model_iter_children (tree_model: popover->model, iter: &dummy, parent: iter))
700 has_submenu = TRUE;
701
702 view = gtk_cell_view_new_with_context (area: popover->area, context: popover->context);
703 gtk_cell_view_set_model (GTK_CELL_VIEW (view), model: popover->model);
704 gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (view), path);
705 gtk_widget_set_hexpand (widget: view, TRUE);
706
707 item = gtk_gizmo_new (css_name: "modelbutton", NULL, NULL, NULL, NULL,
708 focus_func: (GtkGizmoFocusFunc)gtk_widget_focus_self,
709 grab_focus_func: (GtkGizmoGrabFocusFunc)gtk_widget_grab_focus_self);
710 gtk_widget_set_layout_manager (widget: item, layout_manager: gtk_box_layout_new (orientation: GTK_ORIENTATION_HORIZONTAL));
711 gtk_widget_set_focusable (widget: item, TRUE);
712 gtk_widget_add_css_class (widget: item, css_class: "flat");
713
714 if (header_item)
715 {
716 indicator = gtk_builtin_icon_new (css_name: "arrow");
717 gtk_widget_add_css_class (widget: indicator, css_class: "left");
718 gtk_widget_set_parent (widget: indicator, parent: item);
719 }
720
721 gtk_widget_set_parent (widget: view, parent: item);
722
723 indicator = gtk_builtin_icon_new (css_name: has_submenu ? "arrow" : "none");
724 gtk_widget_add_css_class (widget: indicator, css_class: "right");
725 gtk_widget_set_parent (widget: indicator, parent: item);
726
727 controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
728 g_signal_connect (controller, "pressed", G_CALLBACK (item_activated_cb), popover);
729 gtk_widget_add_controller (widget: item, GTK_EVENT_CONTROLLER (controller));
730
731 controller = gtk_event_controller_motion_new ();
732 g_signal_connect (controller, "enter", G_CALLBACK (enter_cb), popover);
733 gtk_widget_add_controller (widget: item, controller);
734
735 controller = gtk_event_controller_focus_new ();
736 g_signal_connect (controller, "enter", G_CALLBACK (enter_focus_cb), popover);
737 gtk_widget_add_controller (widget: item, controller);
738
739 {
740 const guint activate_keyvals[] = { GDK_KEY_space, GDK_KEY_KP_Space,
741 GDK_KEY_Return, GDK_KEY_ISO_Enter,
742 GDK_KEY_KP_Enter };
743 GtkShortcutTrigger *trigger;
744 GtkShortcut *shortcut;
745
746 trigger = g_object_ref (gtk_never_trigger_get ());
747 for (int i = 0; i < G_N_ELEMENTS (activate_keyvals); i++)
748 trigger = gtk_alternative_trigger_new (first: gtk_keyval_trigger_new (keyval: activate_keyvals[i], modifiers: 0), second: trigger);
749
750 shortcut = gtk_shortcut_new (trigger, action: gtk_callback_action_new (callback: activate_shortcut, data: popover, NULL));
751 controller = gtk_shortcut_controller_new ();
752 gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut);
753 gtk_widget_add_controller (widget: item, controller);
754 }
755
756 g_object_set_data (G_OBJECT (item), key: "is-header", GINT_TO_POINTER (header_item));
757 g_object_set_data (G_OBJECT (item), key: "view", data: view);
758 }
759
760 return item;
761}
762
763static void
764populate (GtkTreePopover *popover,
765 GtkTreeIter *parent)
766{
767 GtkTreeIter iter;
768 gboolean valid = FALSE;
769
770 if (!popover->model)
771 return;
772
773 valid = gtk_tree_model_iter_children (tree_model: popover->model, iter: &iter, parent);
774
775 while (valid)
776 {
777 GtkTreePath *path;
778
779 path = gtk_tree_model_get_path (tree_model: popover->model, iter: &iter);
780 row_inserted_cb (model: popover->model, path, iter: &iter, popover);
781
782 populate (popover, parent: &iter);
783
784 valid = gtk_tree_model_iter_next (tree_model: popover->model, iter: &iter);
785 gtk_tree_path_free (path);
786 }
787}
788
789static void
790gtk_tree_popover_populate (GtkTreePopover *popover)
791{
792 populate (popover, NULL);
793}
794
795static void
796rebuild_menu (GtkTreePopover *popover)
797{
798 GtkWidget *stack;
799 GtkWidget *child;
800
801 stack = gtk_tree_popover_get_stack (popover);
802 while ((child = gtk_widget_get_first_child (widget: stack)))
803 gtk_stack_remove (GTK_STACK (stack), child);
804
805 if (popover->model)
806 gtk_tree_popover_populate (popover);
807}
808
809void
810gtk_tree_popover_set_model (GtkTreePopover *popover,
811 GtkTreeModel *model)
812{
813 if (popover->model == model)
814 return;
815
816 if (popover->model)
817 {
818 g_signal_handler_disconnect (instance: popover->model, handler_id: popover->row_inserted_id);
819 g_signal_handler_disconnect (instance: popover->model, handler_id: popover->row_deleted_id);
820 g_signal_handler_disconnect (instance: popover->model, handler_id: popover->row_changed_id);
821 g_signal_handler_disconnect (instance: popover->model, handler_id: popover->row_reordered_id);
822 popover->row_inserted_id = 0;
823 popover->row_deleted_id = 0;
824 popover->row_changed_id = 0;
825 popover->row_reordered_id = 0;
826
827 g_object_unref (object: popover->model);
828 }
829
830 popover->model = model;
831
832 if (popover->model)
833 {
834 g_object_ref (popover->model);
835
836 popover->row_inserted_id = g_signal_connect (popover->model, "row-inserted",
837 G_CALLBACK (row_inserted_cb), popover);
838 popover->row_deleted_id = g_signal_connect (popover->model, "row-deleted",
839 G_CALLBACK (row_deleted_cb), popover);
840 popover->row_changed_id = g_signal_connect (popover->model, "row-changed",
841 G_CALLBACK (row_changed_cb), popover);
842 popover->row_reordered_id = g_signal_connect (popover->model, "rows-reordered",
843 G_CALLBACK (row_reordered_cb), popover);
844 }
845
846 rebuild_menu (popover);
847}
848
849void
850gtk_tree_popover_set_row_separator_func (GtkTreePopover *popover,
851 GtkTreeViewRowSeparatorFunc func,
852 gpointer data,
853 GDestroyNotify destroy)
854{
855 if (popover->row_separator_destroy)
856 popover->row_separator_destroy (popover->row_separator_data);
857
858 popover->row_separator_func = func;
859 popover->row_separator_data = data;
860 popover->row_separator_destroy = destroy;
861
862 rebuild_menu (popover);
863}
864
865static void
866gtk_tree_popover_set_active_item (GtkTreePopover *popover,
867 GtkWidget *item)
868{
869 if (popover->active_item == item)
870 return;
871
872 if (popover->active_item)
873 {
874 gtk_widget_unset_state_flags (widget: popover->active_item, flags: GTK_STATE_FLAG_SELECTED);
875 g_object_remove_weak_pointer (G_OBJECT (popover->active_item), weak_pointer_location: (gpointer *)&popover->active_item);
876 }
877
878 popover->active_item = item;
879
880 if (popover->active_item)
881 {
882 g_object_add_weak_pointer (G_OBJECT (popover->active_item), weak_pointer_location: (gpointer *)&popover->active_item);
883 gtk_widget_set_state_flags (widget: popover->active_item, flags: GTK_STATE_FLAG_SELECTED, FALSE);
884 }
885}
886
887void
888gtk_tree_popover_set_active (GtkTreePopover *popover,
889 int item)
890{
891 GtkWidget *box;
892 GtkWidget *child;
893 int pos;
894
895 if (item == -1)
896 {
897 gtk_tree_popover_set_active_item (popover, NULL);
898 return;
899 }
900
901 box = gtk_tree_popover_get_submenu (popover, name: "main");
902 if (!box)
903 return;
904
905 for (child = gtk_widget_get_first_child (widget: box), pos = 0;
906 child;
907 child = gtk_widget_get_next_sibling (widget: child), pos++)
908 {
909 if (pos == item)
910 {
911 gtk_tree_popover_set_active_item (popover, item: child);
912 break;
913 }
914 }
915}
916
917

source code of gtk/gtk/gtktreepopover.c