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