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 | |
39 | struct _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 | |
62 | enum { |
63 | PROP_0, |
64 | PROP_MODEL, |
65 | PROP_CELL_AREA, |
66 | |
67 | NUM_PROPERTIES |
68 | }; |
69 | |
70 | enum { |
71 | , |
72 | NUM_SIGNALS |
73 | }; |
74 | |
75 | static guint signals[NUM_SIGNALS]; |
76 | |
77 | static void gtk_tree_popover_cell_layout_init (GtkCellLayoutIface *iface); |
78 | static void gtk_tree_popover_set_area (GtkTreePopover *popover, |
79 | GtkCellArea *area); |
80 | static void rebuild_menu (GtkTreePopover *popover); |
81 | static void context_size_changed_cb (GtkCellAreaContext *context, |
82 | GParamSpec *pspec, |
83 | GtkWidget *popover); |
84 | static GtkWidget * gtk_tree_popover_create_item (GtkTreePopover *popover, |
85 | GtkTreePath *path, |
86 | GtkTreeIter *iter, |
87 | gboolean ); |
88 | static GtkWidget * gtk_tree_popover_get_path_item (GtkTreePopover *popover, |
89 | GtkTreePath *search); |
90 | static void gtk_tree_popover_set_active_item (GtkTreePopover *popover, |
91 | GtkWidget *item); |
92 | |
93 | G_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 | |
97 | static void |
98 | gtk_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 | |
116 | static void |
117 | gtk_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 | |
135 | static void |
136 | gtk_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 | |
146 | static void |
147 | gtk_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 | |
170 | static void |
171 | gtk_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 | |
194 | static void |
195 | gtk_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 | |
231 | static GtkWidget * |
232 | gtk_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 | |
241 | static void |
242 | (GtkTreePopover *popover, |
243 | GtkWidget *, |
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 | |
250 | static GtkWidget * |
251 | (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 | |
258 | void |
259 | (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 | |
266 | static void |
267 | gtk_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 | |
287 | static GtkCellArea * |
288 | gtk_tree_popover_cell_layout_get_area (GtkCellLayout *layout) |
289 | { |
290 | return GTK_TREE_POPOVER (ptr: layout)->area; |
291 | } |
292 | |
293 | static void |
294 | gtk_tree_popover_cell_layout_init (GtkCellLayoutIface *iface) |
295 | { |
296 | iface->get_area = gtk_tree_popover_cell_layout_get_area; |
297 | } |
298 | |
299 | static void |
300 | insert_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 | |
318 | static GtkWidget * |
319 | (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 | |
352 | static void |
353 | row_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 | |
388 | static void |
389 | row_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 | |
404 | static void |
405 | row_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 | |
441 | static void |
442 | row_reordered_cb (GtkTreeModel *model, |
443 | GtkTreePath *path, |
444 | GtkTreeIter *iter, |
445 | int *new_order, |
446 | GtkTreePopover *popover) |
447 | { |
448 | rebuild_menu (popover); |
449 | } |
450 | |
451 | static void |
452 | context_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 | |
463 | static gboolean |
464 | area_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 | |
483 | static GtkWidget * |
484 | gtk_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 | |
535 | static void |
536 | area_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 = 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 | |
564 | static void |
565 | gtk_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 | |
585 | static void |
586 | activate_item (GtkWidget *item, |
587 | GtkTreePopover *popover) |
588 | { |
589 | GtkCellView *view; |
590 | GtkTreePath *path; |
591 | char *path_str; |
592 | gboolean = FALSE; |
593 | gboolean = 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 | |
631 | static void |
632 | item_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 | |
642 | static void |
643 | enter_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 | |
654 | static void |
655 | enter_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 | |
663 | static gboolean |
664 | activate_shortcut (GtkWidget *widget, |
665 | GVariant *args, |
666 | gpointer user_data) |
667 | { |
668 | activate_item (item: widget, popover: user_data); |
669 | return TRUE; |
670 | } |
671 | |
672 | static GtkWidget * |
673 | gtk_tree_popover_create_item (GtkTreePopover *popover, |
674 | GtkTreePath *path, |
675 | GtkTreeIter *iter, |
676 | gboolean ) |
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 = 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 | |
763 | static void |
764 | populate (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 | |
789 | static void |
790 | gtk_tree_popover_populate (GtkTreePopover *popover) |
791 | { |
792 | populate (popover, NULL); |
793 | } |
794 | |
795 | static void |
796 | (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 | |
809 | void |
810 | gtk_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 | |
849 | void |
850 | gtk_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 | |
865 | static void |
866 | gtk_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 | |
887 | void |
888 | gtk_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 | |