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