1/* GTK - The GIMP Toolkit
2 * Copyright (C) 2019 Red Hat, Inc.
3 *
4 * Authors:
5 * - Matthias Clasen <mclasen@redhat.com>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21/**
22 * GtkPopover:
23 *
24 * `GtkPopover` is a bubble-like context popup.
25 *
26 * ![An example GtkPopover](popover.png)
27 *
28 * It is primarily meant to provide context-dependent information
29 * or options. Popovers are attached to a parent widget. By default,
30 * they point to the whole widget area, although this behavior can be
31 * changed with [method@Gtk.Popover.set_pointing_to].
32 *
33 * The position of a popover relative to the widget it is attached to
34 * can also be changed with [method@Gtk.Popover.set_position]
35 *
36 * By default, `GtkPopover` performs a grab, in order to ensure input
37 * events get redirected to it while it is shown, and also so the popover
38 * is dismissed in the expected situations (clicks outside the popover,
39 * or the Escape key being pressed). If no such modal behavior is desired
40 * on a popover, [method@Gtk.Popover.set_autohide] may be called on it to
41 * tweak its behavior.
42 *
43 * ## GtkPopover as menu replacement
44 *
45 * `GtkPopover` is often used to replace menus. The best was to do this
46 * is to use the [class@Gtk.PopoverMenu] subclass which supports being
47 * populated from a `GMenuModel` with [ctor@Gtk.PopoverMenu.new_from_model].
48 *
49 * ```xml
50 * <section>
51 * <attribute name="display-hint">horizontal-buttons</attribute>
52 * <item>
53 * <attribute name="label">Cut</attribute>
54 * <attribute name="action">app.cut</attribute>
55 * <attribute name="verb-icon">edit-cut-symbolic</attribute>
56 * </item>
57 * <item>
58 * <attribute name="label">Copy</attribute>
59 * <attribute name="action">app.copy</attribute>
60 * <attribute name="verb-icon">edit-copy-symbolic</attribute>
61 * </item>
62 * <item>
63 * <attribute name="label">Paste</attribute>
64 * <attribute name="action">app.paste</attribute>
65 * <attribute name="verb-icon">edit-paste-symbolic</attribute>
66 * </item>
67 * </section>
68 * ```
69 *
70 * # CSS nodes
71 *
72 * ```
73 * popover[.menu]
74 * ├── arrow
75 * ╰── contents.background
76 * ╰── <child>
77 * ```
78 *
79 * The contents child node always gets the .background style class
80 * and the popover itself gets the .menu style class if the popover
81 * is menu-like (i.e. `GtkPopoverMenu`).
82 *
83 * Particular uses of `GtkPopover`, such as touch selection popups or
84 * magnifiers in `GtkEntry` or `GtkTextView` get style classes like
85 * .touch-selection or .magnifier to differentiate from plain popovers.
86 *
87 * When styling a popover directly, the popover node should usually
88 * not have any background. The visible part of the popover can have
89 * a shadow. To specify it in CSS, set the box-shadow of the contents node.
90 *
91 * Note that, in order to accomplish appropriate arrow visuals, `GtkPopover`
92 * uses custom drawing for the arrow node. This makes it possible for the
93 * arrow to change its shape dynamically, but it also limits the possibilities
94 * of styling it using CSS. In particular, the arrow gets drawn over the
95 * content node's border and shadow, so they look like one shape, which
96 * means that the border width of the content node and the arrow node should
97 * be the same. The arrow also does not support any border shape other than
98 * solid, no border-radius, only one border width (border-bottom-width is
99 * used) and no box-shadow.
100 */
101
102#include "config.h"
103
104#include "gtkpopoverprivate.h"
105#include "gtkpopovermenuprivate.h"
106#include "gtknative.h"
107#include "gtkwidgetprivate.h"
108#include "gtkeventcontrollerkey.h"
109#include "gtkeventcontrollerfocus.h"
110#include "gtkbinlayout.h"
111#include "gtkenums.h"
112#include "gtktypebuiltins.h"
113#include "gtkpopovercontentprivate.h"
114#include "gtkintl.h"
115#include "gtkprivate.h"
116#include "gtkmain.h"
117#include "gtkstack.h"
118#include "gtkmenusectionboxprivate.h"
119#include "gdk/gdkeventsprivate.h"
120#include "gtkpointerfocusprivate.h"
121#include "gtkcsscolorvalueprivate.h"
122#include "gtksnapshot.h"
123#include "gtkshortcutmanager.h"
124#include "gtkbuildable.h"
125#include "gtktooltipprivate.h"
126#include "gtkcssboxesimplprivate.h"
127#include "gtknativeprivate.h"
128
129#include "gtkrender.h"
130#include "gtkstylecontextprivate.h"
131#include "gtkroundedboxprivate.h"
132#include "gsk/gskroundedrectprivate.h"
133#include "gtkcssshadowvalueprivate.h"
134
135#include "gdk/gdksurfaceprivate.h"
136
137#define MNEMONICS_DELAY 300 /* ms */
138
139#define TAIL_GAP_WIDTH 24
140#define TAIL_HEIGHT 12
141
142#define POS_IS_VERTICAL(p) ((p) == GTK_POS_TOP || (p) == GTK_POS_BOTTOM)
143
144typedef struct {
145 GdkSurface *surface;
146 GskRenderer *renderer;
147 GtkWidget *default_widget;
148
149 GdkRectangle pointing_to;
150 gboolean has_pointing_to;
151 guint surface_transform_changed_cb;
152 GtkPositionType position;
153 gboolean autohide;
154 gboolean has_arrow;
155 gboolean mnemonics_visible;
156 gboolean disable_auto_mnemonics;
157 gboolean cascade_popdown;
158
159 int x_offset;
160 int y_offset;
161
162 guint mnemonics_display_timeout_id;
163
164 GtkWidget *child;
165 GtkWidget *contents_widget;
166 GtkCssNode *arrow_node;
167 GskRenderNode *arrow_render_node;
168
169 GdkPopupLayout *layout;
170 GdkRectangle final_rect;
171 GtkPositionType final_position;
172} GtkPopoverPrivate;
173
174enum {
175 CLOSED,
176 ACTIVATE_DEFAULT,
177 LAST_SIGNAL
178};
179
180static guint signals[LAST_SIGNAL] = { 0 };
181
182enum {
183 PROP_POINTING_TO = 1,
184 PROP_POSITION,
185 PROP_AUTOHIDE,
186 PROP_DEFAULT_WIDGET,
187 PROP_HAS_ARROW,
188 PROP_MNEMONICS_VISIBLE,
189 PROP_CHILD,
190 PROP_CASCADE_POPDOWN,
191 NUM_PROPERTIES
192};
193
194static GParamSpec *properties[NUM_PROPERTIES] = { NULL };
195
196static void gtk_popover_buildable_init (GtkBuildableIface *iface);
197
198static void gtk_popover_shortcut_manager_interface_init (GtkShortcutManagerInterface *iface);
199static void gtk_popover_native_interface_init (GtkNativeInterface *iface);
200
201G_DEFINE_TYPE_WITH_CODE (GtkPopover, gtk_popover, GTK_TYPE_WIDGET,
202 G_ADD_PRIVATE (GtkPopover)
203 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
204 gtk_popover_buildable_init)
205 G_IMPLEMENT_INTERFACE (GTK_TYPE_SHORTCUT_MANAGER,
206 gtk_popover_shortcut_manager_interface_init)
207 G_IMPLEMENT_INTERFACE (GTK_TYPE_NATIVE,
208 gtk_popover_native_interface_init))
209
210
211static GdkSurface *
212gtk_popover_native_get_surface (GtkNative *native)
213{
214 GtkPopover *popover = GTK_POPOVER (native);
215 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
216
217 return priv->surface;
218}
219
220static GskRenderer *
221gtk_popover_native_get_renderer (GtkNative *native)
222{
223 GtkPopover *popover = GTK_POPOVER (native);
224 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
225
226 return priv->renderer;
227}
228
229static void
230gtk_popover_native_get_surface_transform (GtkNative *native,
231 double *x,
232 double *y)
233{
234 GtkCssBoxes css_boxes;
235 const graphene_rect_t *margin_rect;
236
237 gtk_css_boxes_init (boxes: &css_boxes, GTK_WIDGET (native));
238 margin_rect = gtk_css_boxes_get_margin_rect (boxes: &css_boxes);
239
240 *x = - margin_rect->origin.x;
241 *y = - margin_rect->origin.y;
242}
243
244static gboolean
245is_gravity_facing_north (GdkGravity gravity)
246{
247 switch (gravity)
248 {
249 case GDK_GRAVITY_NORTH_EAST:
250 case GDK_GRAVITY_NORTH:
251 case GDK_GRAVITY_NORTH_WEST:
252 case GDK_GRAVITY_STATIC:
253 return TRUE;
254 case GDK_GRAVITY_SOUTH_WEST:
255 case GDK_GRAVITY_WEST:
256 case GDK_GRAVITY_SOUTH_EAST:
257 case GDK_GRAVITY_EAST:
258 case GDK_GRAVITY_CENTER:
259 case GDK_GRAVITY_SOUTH:
260 return FALSE;
261 default:
262 g_assert_not_reached ();
263 }
264}
265
266static gboolean
267is_gravity_facing_south (GdkGravity gravity)
268{
269 switch (gravity)
270 {
271 case GDK_GRAVITY_SOUTH_WEST:
272 case GDK_GRAVITY_SOUTH_EAST:
273 case GDK_GRAVITY_SOUTH:
274 return TRUE;
275 case GDK_GRAVITY_NORTH_EAST:
276 case GDK_GRAVITY_NORTH:
277 case GDK_GRAVITY_NORTH_WEST:
278 case GDK_GRAVITY_STATIC:
279 case GDK_GRAVITY_WEST:
280 case GDK_GRAVITY_EAST:
281 case GDK_GRAVITY_CENTER:
282 return FALSE;
283 default:
284 g_assert_not_reached ();
285 }
286}
287
288static gboolean
289is_gravity_facing_west (GdkGravity gravity)
290{
291 switch (gravity)
292 {
293 case GDK_GRAVITY_NORTH_WEST:
294 case GDK_GRAVITY_STATIC:
295 case GDK_GRAVITY_SOUTH_WEST:
296 case GDK_GRAVITY_WEST:
297 return TRUE;
298 case GDK_GRAVITY_NORTH_EAST:
299 case GDK_GRAVITY_SOUTH_EAST:
300 case GDK_GRAVITY_EAST:
301 case GDK_GRAVITY_NORTH:
302 case GDK_GRAVITY_CENTER:
303 case GDK_GRAVITY_SOUTH:
304 return FALSE;
305 default:
306 g_assert_not_reached ();
307 }
308}
309
310static gboolean
311is_gravity_facing_east (GdkGravity gravity)
312{
313 switch (gravity)
314 {
315 case GDK_GRAVITY_NORTH_EAST:
316 case GDK_GRAVITY_SOUTH_EAST:
317 case GDK_GRAVITY_EAST:
318 return TRUE;
319 case GDK_GRAVITY_NORTH_WEST:
320 case GDK_GRAVITY_STATIC:
321 case GDK_GRAVITY_SOUTH_WEST:
322 case GDK_GRAVITY_WEST:
323 case GDK_GRAVITY_NORTH:
324 case GDK_GRAVITY_CENTER:
325 case GDK_GRAVITY_SOUTH:
326 return FALSE;
327 default:
328 g_assert_not_reached ();
329 }
330}
331
332static gboolean
333did_flip_horizontally (GdkGravity original_gravity,
334 GdkGravity final_gravity)
335{
336 g_return_val_if_fail (original_gravity, FALSE);
337 g_return_val_if_fail (final_gravity, FALSE);
338
339 if (is_gravity_facing_east (gravity: original_gravity) &&
340 is_gravity_facing_west (gravity: final_gravity))
341 return TRUE;
342
343 if (is_gravity_facing_west (gravity: original_gravity) &&
344 is_gravity_facing_east (gravity: final_gravity))
345 return TRUE;
346
347 return FALSE;
348}
349
350static gboolean
351did_flip_vertically (GdkGravity original_gravity,
352 GdkGravity final_gravity)
353{
354 g_return_val_if_fail (original_gravity, FALSE);
355 g_return_val_if_fail (final_gravity, FALSE);
356
357 if (is_gravity_facing_north (gravity: original_gravity) &&
358 is_gravity_facing_south (gravity: final_gravity))
359 return TRUE;
360
361 if (is_gravity_facing_south (gravity: original_gravity) &&
362 is_gravity_facing_north (gravity: final_gravity))
363 return TRUE;
364
365 return FALSE;
366}
367
368static void
369update_popover_layout (GtkPopover *popover,
370 GdkPopupLayout *layout,
371 int width,
372 int height)
373{
374 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
375 GdkRectangle final_rect;
376 gboolean flipped_x;
377 gboolean flipped_y;
378 GdkPopup *popup = GDK_POPUP (ptr: priv->surface);
379 GtkPositionType position;
380
381 g_clear_pointer (&priv->layout, gdk_popup_layout_unref);
382 priv->layout = layout;
383
384 final_rect = (GdkRectangle) {
385 .x = gdk_popup_get_position_x (popup: GDK_POPUP (ptr: priv->surface)),
386 .y = gdk_popup_get_position_y (popup: GDK_POPUP (ptr: priv->surface)),
387 .width = gdk_surface_get_width (surface: priv->surface),
388 .height = gdk_surface_get_height (surface: priv->surface),
389 };
390
391 flipped_x =
392 did_flip_horizontally (original_gravity: gdk_popup_layout_get_rect_anchor (layout),
393 final_gravity: gdk_popup_get_rect_anchor (popup)) &&
394 did_flip_horizontally (original_gravity: gdk_popup_layout_get_surface_anchor (layout),
395 final_gravity: gdk_popup_get_surface_anchor (popup));
396 flipped_y =
397 did_flip_vertically (original_gravity: gdk_popup_layout_get_rect_anchor (layout),
398 final_gravity: gdk_popup_get_rect_anchor (popup)) &&
399 did_flip_vertically (original_gravity: gdk_popup_layout_get_surface_anchor (layout),
400 final_gravity: gdk_popup_get_surface_anchor (popup));
401
402 priv->final_rect = final_rect;
403
404 position = priv->final_position;
405
406 switch (priv->position)
407 {
408 case GTK_POS_LEFT:
409 priv->final_position = flipped_x ? GTK_POS_RIGHT : GTK_POS_LEFT;
410 break;
411 case GTK_POS_RIGHT:
412 priv->final_position = flipped_x ? GTK_POS_LEFT : GTK_POS_RIGHT;
413 break;
414 case GTK_POS_TOP:
415 priv->final_position = flipped_y ? GTK_POS_BOTTOM : GTK_POS_TOP;
416 break;
417 case GTK_POS_BOTTOM:
418 priv->final_position = flipped_y ? GTK_POS_TOP : GTK_POS_BOTTOM;
419 break;
420 default:
421 g_assert_not_reached ();
422 break;
423 }
424
425 if (priv->final_position != position ||
426 priv->final_rect.width != width ||
427 priv->final_rect.height != height)
428 {
429 gtk_widget_queue_allocate (GTK_WIDGET (popover));
430 g_clear_pointer (&priv->arrow_render_node, gsk_render_node_unref);
431 }
432
433 gtk_widget_queue_draw (GTK_WIDGET (popover));
434}
435
436static GdkPopupLayout *
437create_popup_layout (GtkPopover *popover)
438{
439 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
440 GdkRectangle rect;
441 GdkGravity parent_anchor;
442 GdkGravity surface_anchor;
443 GdkAnchorHints anchor_hints;
444 GdkPopupLayout *layout;
445 GtkWidget *parent;
446 GtkCssStyle *style;
447 GtkBorder shadow_width;
448
449 parent = gtk_widget_get_parent (GTK_WIDGET (popover));
450 gtk_widget_get_surface_allocation (widget: parent, allocation: &rect);
451 if (priv->has_pointing_to)
452 {
453 rect.x += priv->pointing_to.x;
454 rect.y += priv->pointing_to.y;
455 rect.width = priv->pointing_to.width;
456 rect.height = priv->pointing_to.height;
457 }
458
459 style = gtk_css_node_get_style (cssnode: gtk_widget_get_css_node (GTK_WIDGET (priv->contents_widget)));
460 gtk_css_shadow_value_get_extents (shadow: style->background->box_shadow, border: &shadow_width);
461
462 switch (priv->position)
463 {
464 case GTK_POS_LEFT:
465 switch (gtk_widget_get_valign (GTK_WIDGET (popover)))
466 {
467 case GTK_ALIGN_START:
468 parent_anchor = GDK_GRAVITY_NORTH_WEST;
469 surface_anchor = GDK_GRAVITY_NORTH_EAST;
470 break;
471
472 case GTK_ALIGN_END:
473 parent_anchor = GDK_GRAVITY_SOUTH_WEST;
474 surface_anchor = GDK_GRAVITY_SOUTH_EAST;
475 break;
476
477 case GTK_ALIGN_FILL:
478 case GTK_ALIGN_CENTER:
479 case GTK_ALIGN_BASELINE:
480 default:
481 parent_anchor = GDK_GRAVITY_WEST;
482 surface_anchor = GDK_GRAVITY_EAST;
483 break;
484 }
485 anchor_hints = GDK_ANCHOR_FLIP_X | GDK_ANCHOR_SLIDE_Y;
486 break;
487
488 case GTK_POS_RIGHT:
489 switch (gtk_widget_get_valign (GTK_WIDGET (popover)))
490 {
491 case GTK_ALIGN_START:
492 parent_anchor = GDK_GRAVITY_NORTH_EAST;
493 surface_anchor = GDK_GRAVITY_NORTH_WEST;
494 break;
495
496 case GTK_ALIGN_END:
497 parent_anchor = GDK_GRAVITY_SOUTH_EAST;
498 surface_anchor = GDK_GRAVITY_SOUTH_WEST;
499 break;
500
501 case GTK_ALIGN_FILL:
502 case GTK_ALIGN_CENTER:
503 case GTK_ALIGN_BASELINE:
504 default:
505 parent_anchor = GDK_GRAVITY_EAST;
506 surface_anchor = GDK_GRAVITY_WEST;
507 break;
508 }
509 anchor_hints = GDK_ANCHOR_FLIP_X | GDK_ANCHOR_SLIDE_Y;
510 break;
511
512 case GTK_POS_TOP:
513 switch (gtk_widget_get_halign (GTK_WIDGET (popover)))
514 {
515 case GTK_ALIGN_START:
516 parent_anchor = GDK_GRAVITY_NORTH_WEST;
517 surface_anchor = GDK_GRAVITY_SOUTH_WEST;
518 break;
519
520 case GTK_ALIGN_END:
521 parent_anchor = GDK_GRAVITY_NORTH_EAST;
522 surface_anchor = GDK_GRAVITY_SOUTH_EAST;
523 break;
524
525 case GTK_ALIGN_FILL:
526 case GTK_ALIGN_CENTER:
527 case GTK_ALIGN_BASELINE:
528 default:
529 parent_anchor = GDK_GRAVITY_NORTH;
530 surface_anchor = GDK_GRAVITY_SOUTH;
531 break;
532 }
533 anchor_hints = GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_SLIDE_X;
534 break;
535
536 case GTK_POS_BOTTOM:
537 switch (gtk_widget_get_halign (GTK_WIDGET (popover)))
538 {
539 case GTK_ALIGN_START:
540 parent_anchor = GDK_GRAVITY_SOUTH_WEST;
541 surface_anchor = GDK_GRAVITY_NORTH_WEST;
542 break;
543
544 case GTK_ALIGN_END:
545 parent_anchor = GDK_GRAVITY_SOUTH_EAST;
546 surface_anchor = GDK_GRAVITY_NORTH_EAST;
547 break;
548
549 case GTK_ALIGN_FILL:
550 case GTK_ALIGN_CENTER:
551 case GTK_ALIGN_BASELINE:
552 default:
553 parent_anchor = GDK_GRAVITY_SOUTH;
554 surface_anchor = GDK_GRAVITY_NORTH;
555 break;
556 }
557 anchor_hints = GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_SLIDE_X;
558 break;
559
560 default:
561 g_assert_not_reached ();
562 }
563
564 anchor_hints |= GDK_ANCHOR_RESIZE;
565
566 layout = gdk_popup_layout_new (anchor_rect: &rect, rect_anchor: parent_anchor, surface_anchor);
567 gdk_popup_layout_set_anchor_hints (layout, anchor_hints);
568 gdk_popup_layout_set_shadow_width (layout,
569 left: shadow_width.left,
570 right: shadow_width.right,
571 top: shadow_width.top,
572 bottom: shadow_width.bottom);
573
574 if (priv->x_offset || priv->y_offset)
575 gdk_popup_layout_set_offset (layout, dx: priv->x_offset, dy: priv->y_offset);
576
577 return layout;
578}
579
580static gboolean
581present_popup (GtkPopover *popover)
582{
583 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
584 GtkRequisition nat;
585 GdkPopupLayout *layout;
586
587 layout = create_popup_layout (popover);
588 gtk_widget_get_preferred_size (GTK_WIDGET (popover), NULL, natural_size: &nat);
589
590 if (gdk_popup_present (popup: GDK_POPUP (ptr: priv->surface), width: nat.width, height: nat.height, layout))
591 {
592 update_popover_layout (popover, layout, width: nat.width, height: nat.height);
593 return TRUE;
594 }
595
596 return FALSE;
597}
598
599/**
600 * gtk_popover_present:
601 * @popover: a `GtkPopover`
602 *
603 * Presents the popover to the user.
604 */
605void
606gtk_popover_present (GtkPopover *popover)
607{
608 GtkWidget *widget = GTK_WIDGET (popover);
609
610 if (!_gtk_widget_get_alloc_needed (widget))
611 gtk_widget_ensure_allocate (widget);
612 else if (gtk_widget_get_visible (widget))
613 present_popup (popover);
614}
615
616static void
617maybe_request_motion_event (GtkPopover *popover)
618{
619 GtkWidget *widget = GTK_WIDGET (popover);
620 GtkRoot *root = gtk_widget_get_root (widget);
621 GdkSeat *seat;
622 GdkDevice *device;
623 GtkWidget *focus;
624 GdkSurface *focus_surface;
625
626 seat = gdk_display_get_default_seat (display: gtk_widget_get_display (widget));
627 if (!seat)
628 return;
629
630
631 device = gdk_seat_get_pointer (seat);
632 focus = gtk_window_lookup_pointer_focus_widget (GTK_WINDOW (root),
633 device, NULL);
634 if (!focus)
635 return;
636
637 if (!gtk_widget_is_ancestor (widget: focus, GTK_WIDGET (popover)))
638 return;
639
640 focus_surface = gtk_native_get_surface (self: gtk_widget_get_native (widget: focus));
641 gdk_surface_request_motion (surface: focus_surface);
642}
643
644static void
645gtk_popover_native_layout (GtkNative *native,
646 int width,
647 int height)
648{
649 GtkPopover *popover = GTK_POPOVER (native);
650 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
651 GtkWidget *widget = GTK_WIDGET (popover);
652
653 update_popover_layout (popover, layout: gdk_popup_layout_ref (layout: priv->layout), width, height);
654
655 if (gtk_widget_needs_allocate (widget))
656 {
657 gtk_widget_allocate (widget, width, height, baseline: -1, NULL);
658
659 /* This fake motion event is needed for getting up to date pointer focus
660 * and coordinates when the pointer didn't move but the layout changed
661 * within the popover.
662 */
663 maybe_request_motion_event (popover);
664 }
665 else
666 {
667 gtk_widget_ensure_allocate (widget);
668 }
669}
670
671static gboolean
672gtk_popover_has_mnemonic_modifier_pressed (GtkPopover *popover)
673{
674 GList *seats, *s;
675 gboolean retval = FALSE;
676
677 seats = gdk_display_list_seats (display: gtk_widget_get_display (GTK_WIDGET (popover)));
678
679 for (s = seats; s; s = s->next)
680 {
681 GdkDevice *dev = gdk_seat_get_keyboard (seat: s->data);
682 GdkModifierType mask;
683
684 mask = gdk_device_get_modifier_state (device: dev);
685 if ((mask & gtk_accelerator_get_default_mod_mask ()) == GDK_ALT_MASK)
686 {
687 retval = TRUE;
688 break;
689 }
690 }
691
692 g_list_free (list: seats);
693
694 return retval;
695}
696
697static gboolean
698schedule_mnemonics_visible_cb (gpointer data)
699{
700 GtkPopover *popover = data;
701 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
702
703 priv->mnemonics_display_timeout_id = 0;
704
705 gtk_popover_set_mnemonics_visible (popover, TRUE);
706
707 return G_SOURCE_REMOVE;
708}
709
710static void
711gtk_popover_schedule_mnemonics_visible (GtkPopover *popover)
712{
713 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
714
715 if (priv->mnemonics_display_timeout_id)
716 return;
717
718 priv->mnemonics_display_timeout_id =
719 g_timeout_add (MNEMONICS_DELAY, function: schedule_mnemonics_visible_cb, data: popover);
720 gdk_source_set_static_name_by_id (tag: priv->mnemonics_display_timeout_id, name: "[gtk] popover_schedule_mnemonics_visible_cb");
721}
722
723static void
724gtk_popover_focus_in (GtkWidget *widget)
725{
726 GtkPopover *popover = GTK_POPOVER (widget);
727 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
728
729 if (priv->disable_auto_mnemonics)
730 return;
731
732 if (gtk_widget_get_visible (widget))
733 {
734 if (gtk_popover_has_mnemonic_modifier_pressed (popover))
735 gtk_popover_schedule_mnemonics_visible (popover);
736 }
737}
738
739static void
740gtk_popover_focus_out (GtkWidget *widget)
741{
742 GtkPopover *popover = GTK_POPOVER (widget);
743 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
744
745 if (priv->disable_auto_mnemonics)
746 return;
747
748 gtk_popover_set_mnemonics_visible (popover, FALSE);
749}
750
751static void
752update_mnemonics_visible (GtkPopover *popover,
753 guint keyval,
754 GdkModifierType state,
755 gboolean visible)
756{
757 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
758
759 if (priv->disable_auto_mnemonics)
760 return;
761
762 if ((keyval == GDK_KEY_Alt_L || keyval == GDK_KEY_Alt_R) &&
763 ((state & (gtk_accelerator_get_default_mod_mask ()) & ~(GDK_ALT_MASK)) == 0))
764 {
765 if (visible)
766 gtk_popover_schedule_mnemonics_visible (popover);
767 else
768 gtk_popover_set_mnemonics_visible (popover, FALSE);
769 }
770}
771
772static gboolean
773gtk_popover_key_pressed (GtkWidget *widget,
774 guint keyval,
775 guint keycode,
776 GdkModifierType state)
777{
778 GtkPopover *popover = GTK_POPOVER (widget);
779 GtkWindow *root;
780
781 if (keyval == GDK_KEY_Escape)
782 {
783 gtk_popover_popdown (popover);
784 return TRUE;
785 }
786
787 root = GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (popover)));
788 _gtk_window_update_focus_visible (window: root, keyval, state, TRUE);
789 update_mnemonics_visible (popover, keyval, state, TRUE);
790
791 return FALSE;
792}
793
794static gboolean
795gtk_popover_key_released (GtkWidget *widget,
796 guint keyval,
797 guint keycode,
798 GdkModifierType state)
799{
800 GtkPopover *popover = GTK_POPOVER (widget);
801 GtkWindow *root;
802
803 root = GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (popover)));
804 _gtk_window_update_focus_visible (window: root, keyval, state, FALSE);
805 update_mnemonics_visible (popover, keyval, state, FALSE);
806
807 return FALSE;
808}
809
810static void
811surface_mapped_changed (GtkWidget *widget)
812{
813 GtkPopover *popover = GTK_POPOVER (widget);
814 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
815
816 gtk_widget_set_visible (widget, visible: gdk_surface_get_mapped (surface: priv->surface));
817}
818
819static gboolean
820surface_render (GdkSurface *surface,
821 cairo_region_t *region,
822 GtkWidget *widget)
823{
824 gtk_widget_render (widget, surface, region);
825 return TRUE;
826}
827
828static gboolean
829surface_event (GdkSurface *surface,
830 GdkEvent *event,
831 GtkWidget *widget)
832{
833 gtk_main_do_event (event);
834 return TRUE;
835}
836
837static void
838gtk_popover_activate_default (GtkPopover *popover)
839{
840 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
841 GtkWidget *focus_widget;
842
843 focus_widget = gtk_window_get_focus (GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (popover))));
844 if (!gtk_widget_is_ancestor (widget: focus_widget, GTK_WIDGET (popover)))
845 focus_widget = NULL;
846
847 if (priv->default_widget && gtk_widget_is_sensitive (widget: priv->default_widget) &&
848 (!focus_widget || !gtk_widget_get_receives_default (widget: focus_widget)
849))
850 gtk_widget_activate (widget: priv->default_widget);
851 else if (focus_widget && gtk_widget_is_sensitive (widget: focus_widget))
852 gtk_widget_activate (widget: focus_widget);
853}
854
855static void
856activate_default_cb (GSimpleAction *action,
857 GVariant *parameter,
858 gpointer data)
859{
860 gtk_popover_activate_default (GTK_POPOVER (data));
861}
862
863static void
864add_actions (GtkPopover *popover)
865{
866 GActionEntry entries[] = {
867 { "activate", activate_default_cb, NULL, NULL, NULL },
868 };
869
870 GActionGroup *actions;
871
872 actions = G_ACTION_GROUP (g_simple_action_group_new ());
873 g_action_map_add_action_entries (G_ACTION_MAP (actions),
874 entries, G_N_ELEMENTS (entries),
875 user_data: popover);
876 gtk_widget_insert_action_group (GTK_WIDGET (popover), name: "default", group: actions);
877 g_object_unref (object: actions);
878}
879
880static void
881node_style_changed_cb (GtkCssNode *node,
882 GtkCssStyleChange *change,
883 GtkWidget *widget)
884{
885 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (GTK_POPOVER (widget));
886 g_clear_pointer (&priv->arrow_render_node, gsk_render_node_unref);
887
888 if (gtk_css_style_change_affects (change, affects: GTK_CSS_AFFECTS_SIZE))
889 gtk_widget_queue_resize (widget);
890 else
891 gtk_widget_queue_draw (widget);
892}
893
894static void
895gtk_popover_init (GtkPopover *popover)
896{
897 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
898 GtkWidget *widget = GTK_WIDGET (popover);
899 GtkEventController *controller;
900
901 priv->position = GTK_POS_BOTTOM;
902 priv->final_position = GTK_POS_BOTTOM;
903 priv->autohide = TRUE;
904 priv->has_arrow = TRUE;
905 priv->cascade_popdown = FALSE;
906
907 controller = gtk_event_controller_key_new ();
908 g_signal_connect_swapped (controller, "key-pressed", G_CALLBACK (gtk_popover_key_pressed), popover);
909 g_signal_connect_swapped (controller, "key-released", G_CALLBACK (gtk_popover_key_released), popover);
910 gtk_widget_add_controller (GTK_WIDGET (popover), controller);
911
912 controller = gtk_event_controller_focus_new ();
913 g_signal_connect_swapped (controller, "enter", G_CALLBACK (gtk_popover_focus_in), popover);
914 g_signal_connect_swapped (controller, "leave", G_CALLBACK (gtk_popover_focus_out), popover);
915 gtk_widget_add_controller (widget, controller);
916
917 priv->arrow_node = gtk_css_node_new ();
918 gtk_css_node_set_name (cssnode: priv->arrow_node, name: g_quark_from_static_string (string: "arrow"));
919 gtk_css_node_set_parent (cssnode: priv->arrow_node, parent: gtk_widget_get_css_node (widget));
920 gtk_css_node_set_state (cssnode: priv->arrow_node,
921 state_flags: gtk_css_node_get_state (cssnode: gtk_widget_get_css_node (widget)));
922 g_signal_connect_object (instance: priv->arrow_node, detailed_signal: "style-changed",
923 G_CALLBACK (node_style_changed_cb), gobject: popover, connect_flags: 0);
924 g_object_unref (object: priv->arrow_node);
925
926 priv->contents_widget = gtk_popover_content_new ();
927
928 gtk_widget_set_layout_manager (widget: priv->contents_widget, layout_manager: gtk_bin_layout_new ());
929 gtk_widget_set_parent (widget: priv->contents_widget, GTK_WIDGET (popover));
930 gtk_widget_set_overflow (widget: priv->contents_widget, overflow: GTK_OVERFLOW_HIDDEN);
931
932 gtk_widget_add_css_class (widget, css_class: "background");
933
934 add_actions (popover);
935}
936
937static void
938gtk_popover_realize (GtkWidget *widget)
939{
940 GtkPopover *popover = GTK_POPOVER (widget);
941 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
942 GdkSurface *parent_surface;
943 GtkWidget *parent;
944
945 parent = gtk_widget_get_parent (widget);
946 parent_surface = gtk_native_get_surface (self: gtk_widget_get_native (widget: parent));
947 priv->surface = gdk_surface_new_popup (parent: parent_surface, autohide: priv->autohide);
948
949 gdk_surface_set_widget (self: priv->surface, widget);
950
951 g_signal_connect_swapped (priv->surface, "notify::mapped", G_CALLBACK (surface_mapped_changed), widget);
952 g_signal_connect (priv->surface, "render", G_CALLBACK (surface_render), widget);
953 g_signal_connect (priv->surface, "event", G_CALLBACK (surface_event), widget);
954
955 GTK_WIDGET_CLASS (gtk_popover_parent_class)->realize (widget);
956
957 priv->renderer = gsk_renderer_new_for_surface (surface: priv->surface);
958
959 gtk_native_realize (self: GTK_NATIVE (ptr: popover));
960
961 gtk_native_update_opaque_region (native: GTK_NATIVE (ptr: popover), contents: priv->contents_widget, TRUE, TRUE, resize_handle_size: 0);
962}
963
964static void
965gtk_popover_unrealize (GtkWidget *widget)
966{
967 GtkPopover *popover = GTK_POPOVER (widget);
968 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
969
970 gtk_native_unrealize (self: GTK_NATIVE (ptr: popover));
971
972 GTK_WIDGET_CLASS (gtk_popover_parent_class)->unrealize (widget);
973
974 gsk_renderer_unrealize (renderer: priv->renderer);
975 g_clear_object (&priv->renderer);
976
977 g_signal_handlers_disconnect_by_func (priv->surface, surface_mapped_changed, widget);
978 g_signal_handlers_disconnect_by_func (priv->surface, surface_render, widget);
979 g_signal_handlers_disconnect_by_func (priv->surface, surface_event, widget);
980 gdk_surface_set_widget (self: priv->surface, NULL);
981 gdk_surface_destroy (surface: priv->surface);
982 g_clear_object (&priv->surface);
983}
984
985static gboolean
986gtk_popover_focus (GtkWidget *widget,
987 GtkDirectionType direction)
988{
989 if (!gtk_widget_get_visible (widget))
990 return FALSE;
991
992 /* This code initially comes from gtkpopovermenu.c */
993 if (gtk_widget_get_first_child (widget) == NULL)
994 {
995 /* Empty popover, so nothing to Tab through. */
996 return FALSE;
997 }
998 else
999 {
1000 /* Move focus normally, but when nothing can be focused in this direction then we cycle around. */
1001 if (gtk_widget_focus_move (widget, direction))
1002 return TRUE;
1003
1004 if (gtk_popover_get_autohide (GTK_POPOVER (widget)))
1005 {
1006 GtkWidget *p = gtk_root_get_focus (self: gtk_widget_get_root (widget));
1007
1008 /* In the case where the popover doesn't have any focusable child (like
1009 * the GtkTreePopover for combo boxes) then the focus will end up out of
1010 * the popover, hence creating an infinite loop below. To avoid this, just
1011 * say we had focus and stop here.
1012 */
1013 if (!gtk_widget_is_ancestor (widget: p, ancestor: widget) && p != widget)
1014 return TRUE;
1015
1016 /* Cycle around with (Shift+)Tab */
1017 if (direction == GTK_DIR_TAB_FORWARD || direction == GTK_DIR_TAB_BACKWARD)
1018 {
1019 for (;
1020 p != widget;
1021 p = gtk_widget_get_parent (widget: p))
1022 {
1023 /* Unfocus everything in the popover. */
1024 gtk_widget_set_focus_child (widget: p, NULL);
1025 }
1026 }
1027 /* Focus again from scratch */
1028 gtk_widget_focus_move (widget, direction);
1029 return TRUE;
1030 }
1031 else
1032 {
1033 return FALSE;
1034 }
1035 }
1036}
1037
1038static void
1039gtk_popover_show (GtkWidget *widget)
1040{
1041 GtkPopover *popover = GTK_POPOVER (widget);
1042 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1043
1044 _gtk_widget_set_visible_flag (widget, TRUE);
1045 gtk_widget_realize (widget);
1046 if (!present_popup (popover))
1047 return;
1048
1049 gtk_widget_map (widget);
1050
1051 if (priv->autohide)
1052 {
1053 if (!gtk_widget_get_focus_child (widget))
1054 gtk_widget_child_focus (widget, direction: GTK_DIR_TAB_FORWARD);
1055
1056 gtk_grab_add (widget);
1057 }
1058}
1059
1060static void
1061gtk_popover_hide (GtkWidget *widget)
1062{
1063 GtkPopover *popover = GTK_POPOVER (widget);
1064 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1065
1066 if (priv->autohide)
1067 gtk_grab_remove (widget);
1068
1069 gtk_popover_set_mnemonics_visible (GTK_POPOVER (widget), FALSE);
1070 _gtk_widget_set_visible_flag (widget, FALSE);
1071 gtk_widget_unmap (widget);
1072 g_signal_emit (instance: widget, signal_id: signals[CLOSED], detail: 0);
1073}
1074
1075static void
1076unset_surface_transform_changed_cb (gpointer data)
1077{
1078 GtkPopover *popover = data;
1079 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1080
1081 priv->surface_transform_changed_cb = 0;
1082}
1083
1084static gboolean
1085surface_transform_changed_cb (GtkWidget *widget,
1086 const graphene_matrix_t *transform,
1087 gpointer user_data)
1088{
1089 GtkPopover *popover = user_data;
1090 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1091
1092 if (priv->surface && gdk_surface_get_mapped (surface: priv->surface))
1093 present_popup (popover);
1094
1095 return G_SOURCE_CONTINUE;
1096}
1097
1098static void
1099gtk_popover_map (GtkWidget *widget)
1100{
1101 GtkPopover *popover = GTK_POPOVER (widget);
1102 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1103 GtkWidget *parent;
1104
1105 present_popup (popover);
1106
1107 parent = gtk_widget_get_parent (widget);
1108 priv->surface_transform_changed_cb =
1109 gtk_widget_add_surface_transform_changed_callback (widget: parent,
1110 callback: surface_transform_changed_cb,
1111 user_data: popover,
1112 notify: unset_surface_transform_changed_cb);
1113
1114 GTK_WIDGET_CLASS (gtk_popover_parent_class)->map (widget);
1115}
1116
1117static void
1118gtk_popover_unmap (GtkWidget *widget)
1119{
1120 GtkPopover *popover = GTK_POPOVER (widget);
1121 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1122 GtkWidget *parent;
1123
1124 parent = gtk_widget_get_parent (widget);
1125 gtk_widget_remove_surface_transform_changed_callback (widget: parent,
1126 id: priv->surface_transform_changed_cb);
1127 priv->surface_transform_changed_cb = 0;
1128
1129 GTK_WIDGET_CLASS (gtk_popover_parent_class)->unmap (widget);
1130 gdk_surface_hide (surface: priv->surface);
1131}
1132
1133static void
1134gtk_popover_dispose (GObject *object)
1135{
1136 GtkPopover *popover = GTK_POPOVER (object);
1137 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1138
1139 g_clear_object (&priv->default_widget);
1140 g_clear_pointer (&priv->contents_widget, gtk_widget_unparent);
1141 g_clear_pointer (&priv->arrow_render_node, gsk_render_node_unref);
1142
1143 G_OBJECT_CLASS (gtk_popover_parent_class)->dispose (object);
1144}
1145
1146static void
1147gtk_popover_finalize (GObject *object)
1148{
1149 GtkPopover *popover = GTK_POPOVER (object);
1150 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1151
1152 g_clear_pointer (&priv->layout, gdk_popup_layout_unref);
1153
1154 if (priv->mnemonics_display_timeout_id)
1155 {
1156 g_source_remove (tag: priv->mnemonics_display_timeout_id);
1157 priv->mnemonics_display_timeout_id = 0;
1158 }
1159
1160 G_OBJECT_CLASS (gtk_popover_parent_class)->finalize (object);
1161}
1162
1163static void
1164gtk_popover_get_gap_coords (GtkPopover *popover,
1165 int *initial_x_out,
1166 int *initial_y_out,
1167 int *tip_x_out,
1168 int *tip_y_out,
1169 int *final_x_out,
1170 int *final_y_out)
1171{
1172 GtkWidget *widget = GTK_WIDGET (popover);
1173 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1174 GdkRectangle rect = { 0 };
1175 int base, tip, tip_pos;
1176 int initial_x, initial_y;
1177 int tip_x, tip_y;
1178 int final_x, final_y;
1179 GtkPositionType pos;
1180 int border_top, border_right, border_bottom;
1181 int border_radius;
1182 int popover_width, popover_height;
1183 GtkCssStyle *style;
1184 GtkWidget *parent;
1185 GtkBorder shadow_width;
1186
1187 popover_width = gtk_widget_get_allocated_width (widget);
1188 popover_height = gtk_widget_get_allocated_height (widget);
1189 parent = gtk_widget_get_parent (widget);
1190
1191 gtk_widget_get_surface_allocation (widget: parent, allocation: &rect);
1192 if (priv->has_pointing_to)
1193 {
1194 rect.x += priv->pointing_to.x;
1195 rect.y += priv->pointing_to.y;
1196 rect.width = priv->pointing_to.width;
1197 rect.height = priv->pointing_to.height;
1198 }
1199
1200 rect.x -= priv->final_rect.x;
1201 rect.y -= priv->final_rect.y;
1202
1203 pos = priv->final_position;
1204
1205 style = gtk_css_node_get_style (cssnode: gtk_widget_get_css_node (widget: priv->contents_widget));
1206 border_radius = _gtk_css_number_value_get (number: style->border->border_top_left_radius, one_hundred_percent: 100);
1207 border_top = _gtk_css_number_value_get (number: style->border->border_top_width, one_hundred_percent: 100);
1208 border_right = _gtk_css_number_value_get (number: style->border->border_right_width, one_hundred_percent: 100);
1209 border_bottom = _gtk_css_number_value_get (number: style->border->border_bottom_width, one_hundred_percent: 100);
1210
1211 gtk_css_shadow_value_get_extents (shadow: style->background->box_shadow, border: &shadow_width);
1212
1213 if (pos == GTK_POS_BOTTOM)
1214 {
1215 tip = shadow_width.top;
1216 base = tip + TAIL_HEIGHT + border_top;
1217 }
1218 else if (pos == GTK_POS_RIGHT)
1219 {
1220 tip = shadow_width.left;
1221 base = tip + TAIL_HEIGHT + border_top;
1222 }
1223 else if (pos == GTK_POS_TOP)
1224 {
1225 tip = popover_height - shadow_width.bottom;
1226 base = tip - border_bottom - TAIL_HEIGHT;
1227 }
1228 else if (pos == GTK_POS_LEFT)
1229 {
1230 tip = popover_width - shadow_width.right;
1231 base = tip - border_right - TAIL_HEIGHT;
1232 }
1233 else
1234 g_assert_not_reached ();
1235
1236 if (POS_IS_VERTICAL (pos))
1237 {
1238 tip_pos = rect.x + (rect.width / 2);
1239 initial_x = CLAMP (tip_pos - TAIL_GAP_WIDTH / 2,
1240 border_radius,
1241 popover_width - TAIL_GAP_WIDTH - border_radius);
1242 initial_y = base;
1243
1244 tip_x = CLAMP (tip_pos, 0, popover_width);
1245 tip_y = tip;
1246
1247 final_x = CLAMP (tip_pos + TAIL_GAP_WIDTH / 2,
1248 border_radius + TAIL_GAP_WIDTH,
1249 popover_width - border_radius);
1250 final_y = base;
1251 }
1252 else
1253 {
1254 tip_pos = rect.y + (rect.height / 2);
1255
1256 initial_x = base;
1257 initial_y = CLAMP (tip_pos - TAIL_GAP_WIDTH / 2,
1258 border_radius,
1259 popover_height - TAIL_GAP_WIDTH - border_radius);
1260
1261 tip_x = tip;
1262 tip_y = CLAMP (tip_pos, 0, popover_height);
1263
1264 final_x = base;
1265 final_y = CLAMP (tip_pos + TAIL_GAP_WIDTH / 2,
1266 border_radius + TAIL_GAP_WIDTH,
1267 popover_height - border_radius);
1268 }
1269
1270 *initial_x_out = initial_x;
1271 *initial_y_out = initial_y;
1272
1273 *tip_x_out = tip_x;
1274 *tip_y_out = tip_y;
1275
1276 *final_x_out = final_x;
1277 *final_y_out = final_y;
1278}
1279
1280static void
1281get_border (GtkCssNode *node,
1282 GtkBorder *border)
1283{
1284 GtkCssStyle *style;
1285
1286 style = gtk_css_node_get_style (cssnode: node);
1287
1288 border->top = _gtk_css_number_value_get (number: style->border->border_top_width, one_hundred_percent: 100);
1289 border->right = _gtk_css_number_value_get (number: style->border->border_right_width, one_hundred_percent: 100);
1290 border->bottom = _gtk_css_number_value_get (number: style->border->border_bottom_width, one_hundred_percent: 100);
1291 border->left = _gtk_css_number_value_get (number: style->border->border_left_width, one_hundred_percent: 100);
1292}
1293
1294static void
1295gtk_popover_apply_tail_path (GtkPopover *popover,
1296 cairo_t *cr)
1297{
1298 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1299 int initial_x, initial_y;
1300 int tip_x, tip_y;
1301 int final_x, final_y;
1302 GtkBorder border;
1303 GtkWidget *parent;
1304
1305 parent = gtk_widget_get_parent (GTK_WIDGET (popover));
1306
1307 if (!parent)
1308 return;
1309
1310 get_border (node: priv->arrow_node, border: &border);
1311
1312 cairo_set_line_width (cr, width: 1);
1313 gtk_popover_get_gap_coords (popover,
1314 initial_x_out: &initial_x, initial_y_out: &initial_y,
1315 tip_x_out: &tip_x, tip_y_out: &tip_y,
1316 final_x_out: &final_x, final_y_out: &final_y);
1317
1318 cairo_move_to (cr, x: initial_x, y: initial_y);
1319 cairo_line_to (cr, x: tip_x, y: tip_y);
1320 cairo_line_to (cr, x: final_x, y: final_y);
1321}
1322
1323static void
1324gtk_popover_update_shape (GtkPopover *popover)
1325{
1326 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1327
1328 if (priv->has_arrow)
1329 {
1330 GtkCssBoxes content_css_boxes;
1331 const GskRoundedRect *box;
1332 cairo_surface_t *cairo_surface;
1333 cairo_region_t *region;
1334 cairo_t *cr;
1335 double x, y;
1336 double native_x, native_y;
1337
1338 gtk_native_get_surface_transform (self: GTK_NATIVE (ptr: popover), x: &native_x, y: &native_y);
1339 gtk_css_boxes_init (boxes: &content_css_boxes, widget: priv->contents_widget);
1340
1341 cairo_surface =
1342 gdk_surface_create_similar_surface (surface: priv->surface,
1343 content: CAIRO_CONTENT_COLOR_ALPHA,
1344 width: gdk_surface_get_width (surface: priv->surface),
1345 height: gdk_surface_get_height (surface: priv->surface));
1346
1347 cr = cairo_create (target: cairo_surface);
1348
1349 cairo_translate (cr, tx: native_x, ty: native_y);
1350
1351 cairo_set_source_rgba (cr, red: 0, green: 0, blue: 0, alpha: 1);
1352 gtk_popover_apply_tail_path (popover, cr);
1353 cairo_close_path (cr);
1354 cairo_fill (cr);
1355
1356 box = gtk_css_boxes_get_border_box (boxes: &content_css_boxes);
1357 gtk_widget_translate_coordinates (src_widget: priv->contents_widget, GTK_WIDGET (popover),
1358 src_x: 0, src_y: 0,
1359 dest_x: &x, dest_y: &y);
1360 cairo_translate (cr, tx: x, ty: y);
1361 gsk_rounded_rect_path (self: box, cr);
1362 cairo_fill (cr);
1363 cairo_destroy (cr);
1364
1365 region = gdk_cairo_region_create_from_surface (surface: cairo_surface);
1366 cairo_surface_destroy (surface: cairo_surface);
1367
1368 gdk_surface_set_input_region (surface: priv->surface, region);
1369 cairo_region_destroy (region);
1370 }
1371 else
1372 {
1373 GtkCssNode *content_css_node;
1374 GtkCssStyle *style;
1375 GtkBorder shadow_width;
1376 cairo_rectangle_int_t input_rect;
1377 cairo_region_t *region;
1378
1379 content_css_node =
1380 gtk_widget_get_css_node (GTK_WIDGET (priv->contents_widget));
1381 style = gtk_css_node_get_style (cssnode: content_css_node);
1382 gtk_css_shadow_value_get_extents (shadow: style->background->box_shadow, border: &shadow_width);
1383
1384 input_rect.x = shadow_width.left;
1385 input_rect.y = shadow_width.top;
1386 input_rect.width =
1387 gdk_surface_get_width (surface: priv->surface) -
1388 (shadow_width.left + shadow_width.right);
1389 input_rect.height =
1390 gdk_surface_get_height (surface: priv->surface) -
1391 (shadow_width.top + shadow_width.bottom);
1392
1393 region = cairo_region_create_rectangle (rectangle: &input_rect);
1394 gdk_surface_set_input_region (surface: priv->surface, region);
1395 cairo_region_destroy (region);
1396 }
1397
1398 if (_gtk_widget_get_realized (GTK_WIDGET (popover)))
1399 gtk_native_update_opaque_region (native: GTK_NATIVE (ptr: popover), contents: priv->contents_widget, TRUE, TRUE, resize_handle_size: 0);
1400}
1401
1402static int
1403get_border_radius (GtkWidget *widget)
1404{
1405 GtkCssStyle *style;
1406
1407 style = gtk_css_node_get_style (cssnode: gtk_widget_get_css_node (widget));
1408 return round (x: _gtk_css_number_value_get (number: style->border->border_top_left_radius, one_hundred_percent: 100));
1409}
1410
1411static int
1412get_minimal_size (GtkPopover *popover,
1413 GtkOrientation orientation)
1414{
1415 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1416 GtkPositionType pos;
1417 int minimal_size;
1418 int tail_gap_width = priv->has_arrow ? TAIL_GAP_WIDTH : 0;
1419 int min_width, min_height;
1420
1421 minimal_size = 2 * get_border_radius (GTK_WIDGET (priv->contents_widget));
1422 pos = priv->position;
1423
1424 if ((orientation == GTK_ORIENTATION_HORIZONTAL && POS_IS_VERTICAL (pos)) ||
1425 (orientation == GTK_ORIENTATION_VERTICAL && !POS_IS_VERTICAL (pos)))
1426 minimal_size += tail_gap_width;
1427
1428 gtk_widget_get_size_request (GTK_WIDGET (popover), width: &min_width, height: &min_height);
1429
1430 if (orientation == GTK_ORIENTATION_HORIZONTAL)
1431 minimal_size = MAX (minimal_size, min_width);
1432 else
1433 minimal_size = MAX (minimal_size, min_height);
1434
1435 return minimal_size;
1436}
1437
1438static void
1439gtk_popover_measure (GtkWidget *widget,
1440 GtkOrientation orientation,
1441 int for_size,
1442 int *minimum,
1443 int *natural,
1444 int *minimum_baseline,
1445 int *natural_baseline)
1446{
1447 GtkPopover *popover = GTK_POPOVER (widget);
1448 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1449 int minimal_size;
1450 int tail_height = priv->has_arrow ? TAIL_HEIGHT : 0;
1451 GtkCssStyle *style;
1452 GtkBorder shadow_width;
1453
1454 if (for_size >= 0)
1455 for_size -= tail_height;
1456
1457 style = gtk_css_node_get_style (cssnode: gtk_widget_get_css_node (GTK_WIDGET (priv->contents_widget)));
1458 gtk_css_shadow_value_get_extents (shadow: style->background->box_shadow, border: &shadow_width);
1459
1460 gtk_widget_measure (widget: priv->contents_widget,
1461 orientation, for_size,
1462 minimum, natural,
1463 minimum_baseline, natural_baseline);
1464
1465 minimal_size = get_minimal_size (popover, orientation);
1466 *minimum = MAX (*minimum, minimal_size);
1467 *natural = MAX (*natural, minimal_size);
1468
1469 if (orientation == GTK_ORIENTATION_HORIZONTAL)
1470 {
1471 *minimum += shadow_width.left + shadow_width.right;
1472 *natural += shadow_width.left + shadow_width.right;
1473 }
1474 else
1475 {
1476 *minimum += shadow_width.top + shadow_width.bottom;
1477 *natural += shadow_width.top + shadow_width.bottom;
1478 }
1479
1480 if (POS_IS_VERTICAL (priv->position) == (orientation == GTK_ORIENTATION_VERTICAL))
1481 {
1482 *minimum += tail_height;
1483 *natural += tail_height;
1484 }
1485}
1486
1487static void
1488gtk_popover_size_allocate (GtkWidget *widget,
1489 int width,
1490 int height,
1491 int baseline)
1492{
1493 GtkPopover *popover = GTK_POPOVER (widget);
1494 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1495 GtkAllocation child_alloc;
1496 int tail_height = priv->has_arrow ? TAIL_HEIGHT : 0;
1497 GtkCssStyle *style;
1498 GtkBorder shadow_width;
1499
1500 style = gtk_css_node_get_style (cssnode: gtk_widget_get_css_node (GTK_WIDGET (priv->contents_widget)));
1501 gtk_css_shadow_value_get_extents (shadow: style->background->box_shadow, border: &shadow_width);
1502
1503 switch (priv->final_position)
1504 {
1505 case GTK_POS_TOP:
1506 child_alloc.x = shadow_width.left;
1507 child_alloc.y = shadow_width.top;
1508 child_alloc.width = width - (shadow_width.left + shadow_width.right);
1509 child_alloc.height = height - (shadow_width.top + shadow_width.bottom + tail_height);
1510 break;
1511 case GTK_POS_BOTTOM:
1512 child_alloc.x = shadow_width.left;
1513 child_alloc.y = shadow_width.top + tail_height;
1514 child_alloc.width = width - (shadow_width.left + shadow_width.right);
1515 child_alloc.height = height - (shadow_width.top + shadow_width.bottom + tail_height);
1516 break;
1517 case GTK_POS_LEFT:
1518 child_alloc.x = shadow_width.left;
1519 child_alloc.y = shadow_width.top;
1520 child_alloc.width = width - (shadow_width.left + shadow_width.right + tail_height);
1521 child_alloc.height = height - (shadow_width.top + shadow_width.bottom);
1522 break;
1523 case GTK_POS_RIGHT:
1524 child_alloc.x = shadow_width.left + tail_height;
1525 child_alloc.y = shadow_width.top;
1526 child_alloc.width = width - (shadow_width.left + shadow_width.right + tail_height);
1527 child_alloc.height = height - (shadow_width.top + shadow_width.bottom);
1528 break;
1529 default:
1530 break;
1531 }
1532
1533 gtk_widget_size_allocate (widget: priv->contents_widget, allocation: &child_alloc, baseline);
1534
1535 if (priv->surface)
1536 {
1537 gtk_popover_update_shape (popover);
1538 g_clear_pointer (&priv->arrow_render_node, gsk_render_node_unref);
1539 }
1540
1541 gtk_tooltip_maybe_allocate (native: GTK_NATIVE (ptr: popover));
1542}
1543
1544static void
1545create_arrow_render_node (GtkPopover *popover)
1546{
1547 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1548 GtkWidget *widget = GTK_WIDGET (popover);
1549 GtkStyleContext *context;
1550 GtkBorder border;
1551 cairo_t *cr;
1552 GtkSnapshot *snapshot;
1553
1554 snapshot = gtk_snapshot_new ();
1555
1556 cr = gtk_snapshot_append_cairo (snapshot,
1557 bounds: &GRAPHENE_RECT_INIT (
1558 0, 0,
1559 gtk_widget_get_width (widget),
1560 gtk_widget_get_height (widget)
1561 ));
1562
1563 /* Clip to the arrow shape */
1564 cairo_save (cr);
1565 gtk_popover_apply_tail_path (popover, cr);
1566 cairo_clip (cr);
1567
1568 get_border (node: priv->arrow_node, border: &border);
1569
1570 context = gtk_widget_get_style_context (widget);
1571 gtk_style_context_save_to_node (context, node: priv->arrow_node);
1572
1573 /* Render the arrow background */
1574 gtk_render_background (context, cr,
1575 x: 0, y: 0,
1576 width: gtk_widget_get_width (widget),
1577 height: gtk_widget_get_height (widget));
1578
1579 /* Render the border of the arrow tip */
1580 if (border.bottom > 0)
1581 {
1582 GtkCssStyle *style;
1583 const GdkRGBA *border_color;
1584
1585 style = gtk_css_node_get_style (cssnode: priv->arrow_node);
1586 border_color = gtk_css_color_value_get_rgba (color: style->border->border_left_color ? style->border->border_left_color : style->core->color);
1587
1588 gtk_popover_apply_tail_path (popover, cr);
1589 gdk_cairo_set_source_rgba (cr, rgba: border_color);
1590
1591 cairo_set_line_width (cr, width: border.bottom + 1);
1592 cairo_stroke (cr);
1593 }
1594
1595 cairo_restore (cr);
1596 cairo_destroy (cr);
1597
1598 gtk_style_context_restore (context);
1599
1600 priv->arrow_render_node = gtk_snapshot_free_to_node (snapshot);
1601}
1602
1603static void
1604gtk_popover_snapshot (GtkWidget *widget,
1605 GtkSnapshot *snapshot)
1606{
1607 GtkPopover *popover = GTK_POPOVER (widget);
1608 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1609
1610 gtk_widget_snapshot_child (widget, child: priv->contents_widget, snapshot);
1611
1612 if (priv->has_arrow)
1613 {
1614 if (!priv->arrow_render_node)
1615 create_arrow_render_node (popover);
1616
1617 gtk_snapshot_append_node (snapshot, node: priv->arrow_render_node);
1618 }
1619}
1620
1621static void
1622gtk_popover_set_property (GObject *object,
1623 guint prop_id,
1624 const GValue *value,
1625 GParamSpec *pspec)
1626{
1627 GtkPopover *popover = GTK_POPOVER (object);
1628
1629 switch (prop_id)
1630 {
1631 case PROP_POINTING_TO:
1632 gtk_popover_set_pointing_to (popover, rect: g_value_get_boxed (value));
1633 break;
1634
1635 case PROP_POSITION:
1636 gtk_popover_set_position (popover, position: g_value_get_enum (value));
1637 break;
1638
1639 case PROP_AUTOHIDE:
1640 gtk_popover_set_autohide (popover, autohide: g_value_get_boolean (value));
1641 break;
1642
1643 case PROP_DEFAULT_WIDGET:
1644 gtk_popover_set_default_widget (popover, widget: g_value_get_object (value));
1645 break;
1646
1647 case PROP_HAS_ARROW:
1648 gtk_popover_set_has_arrow (popover, has_arrow: g_value_get_boolean (value));
1649 break;
1650
1651 case PROP_MNEMONICS_VISIBLE:
1652 gtk_popover_set_mnemonics_visible (popover, mnemonics_visible: g_value_get_boolean (value));
1653 break;
1654
1655 case PROP_CHILD:
1656 gtk_popover_set_child (popover, child: g_value_get_object (value));
1657 break;
1658
1659 case PROP_CASCADE_POPDOWN:
1660 gtk_popover_set_cascade_popdown (popover, cascade_popdown: g_value_get_boolean (value));
1661 break;
1662
1663 default:
1664 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1665 break;
1666 }
1667}
1668
1669static void
1670gtk_popover_get_property (GObject *object,
1671 guint prop_id,
1672 GValue *value,
1673 GParamSpec *pspec)
1674{
1675 GtkPopover *popover = GTK_POPOVER (object);
1676 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1677
1678 switch (prop_id)
1679 {
1680 case PROP_POINTING_TO:
1681 g_value_set_boxed (value, v_boxed: &priv->pointing_to);
1682 break;
1683
1684 case PROP_POSITION:
1685 g_value_set_enum (value, v_enum: priv->position);
1686 break;
1687
1688 case PROP_AUTOHIDE:
1689 g_value_set_boolean (value, v_boolean: priv->autohide);
1690 break;
1691
1692 case PROP_DEFAULT_WIDGET:
1693 g_value_set_object (value, v_object: priv->default_widget);
1694 break;
1695
1696 case PROP_HAS_ARROW:
1697 g_value_set_boolean (value, v_boolean: priv->has_arrow);
1698 break;
1699
1700 case PROP_MNEMONICS_VISIBLE:
1701 g_value_set_boolean (value, v_boolean: priv->mnemonics_visible);
1702 break;
1703
1704 case PROP_CHILD:
1705 g_value_set_object (value, v_object: gtk_popover_get_child (popover));
1706 break;
1707
1708 case PROP_CASCADE_POPDOWN:
1709 g_value_set_boolean (value, v_boolean: priv->cascade_popdown);
1710 break;
1711
1712 default:
1713 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1714 break;
1715 }
1716}
1717
1718static void
1719add_tab_bindings (GtkWidgetClass *widget_class,
1720 GdkModifierType modifiers,
1721 GtkDirectionType direction)
1722{
1723 gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_Tab, mods: modifiers,
1724 signal: "move-focus",
1725 format_string: "(i)", direction);
1726 gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_KP_Tab, mods: modifiers,
1727 signal: "move-focus",
1728 format_string: "(i)", direction);
1729}
1730
1731static void
1732add_arrow_bindings (GtkWidgetClass *widget_class,
1733 guint keysym,
1734 GtkDirectionType direction)
1735{
1736 guint keypad_keysym = keysym - GDK_KEY_Left + GDK_KEY_KP_Left;
1737
1738 gtk_widget_class_add_binding_signal (widget_class, keyval: keysym, mods: 0,
1739 signal: "move-focus",
1740 format_string: "(i)",
1741 direction);
1742 gtk_widget_class_add_binding_signal (widget_class, keyval: keysym, mods: GDK_CONTROL_MASK,
1743 signal: "move-focus",
1744 format_string: "(i)",
1745 direction);
1746 gtk_widget_class_add_binding_signal (widget_class, keyval: keypad_keysym, mods: 0,
1747 signal: "move-focus",
1748 format_string: "(i)",
1749 direction);
1750 gtk_widget_class_add_binding_signal (widget_class, keyval: keypad_keysym, mods: GDK_CONTROL_MASK,
1751 signal: "move-focus",
1752 format_string: "(i)",
1753 direction);
1754}
1755
1756static void
1757gtk_popover_compute_expand (GtkWidget *widget,
1758 gboolean *hexpand,
1759 gboolean *vexpand)
1760{
1761 GtkPopover *popover = GTK_POPOVER (widget);
1762 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1763
1764 if (priv->child)
1765 {
1766 *hexpand = gtk_widget_compute_expand (widget: priv->child, orientation: GTK_ORIENTATION_HORIZONTAL);
1767 *vexpand = gtk_widget_compute_expand (widget: priv->child, orientation: GTK_ORIENTATION_VERTICAL);
1768 }
1769 else
1770 {
1771 *hexpand = FALSE;
1772 *vexpand = FALSE;
1773 }
1774}
1775
1776static GtkSizeRequestMode
1777gtk_popover_get_request_mode (GtkWidget *widget)
1778{
1779 GtkPopover *popover = GTK_POPOVER (widget);
1780 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1781
1782 if (priv->child)
1783 return gtk_widget_get_request_mode (widget: priv->child);
1784 else
1785 return GTK_SIZE_REQUEST_CONSTANT_SIZE;
1786}
1787
1788static void
1789gtk_popover_class_init (GtkPopoverClass *klass)
1790{
1791 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1792 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1793
1794 object_class->dispose = gtk_popover_dispose;
1795 object_class->finalize = gtk_popover_finalize;
1796 object_class->set_property = gtk_popover_set_property;
1797 object_class->get_property = gtk_popover_get_property;
1798
1799 widget_class->realize = gtk_popover_realize;
1800 widget_class->unrealize = gtk_popover_unrealize;
1801 widget_class->map = gtk_popover_map;
1802 widget_class->unmap = gtk_popover_unmap;
1803 widget_class->focus = gtk_popover_focus;
1804 widget_class->show = gtk_popover_show;
1805 widget_class->hide = gtk_popover_hide;
1806 widget_class->measure = gtk_popover_measure;
1807 widget_class->size_allocate = gtk_popover_size_allocate;
1808 widget_class->snapshot = gtk_popover_snapshot;
1809 widget_class->compute_expand = gtk_popover_compute_expand;
1810 widget_class->get_request_mode = gtk_popover_get_request_mode;
1811
1812 klass->activate_default = gtk_popover_activate_default;
1813
1814 /**
1815 * GtkPopover:pointing-to: (attributes org.gtk.Property.get=gtk_popover_get_pointing_to org.gtk.Property.set=gtk_popover_set_pointing_to)
1816 *
1817 * Rectangle in the parent widget that the popover points to.
1818 */
1819 properties[PROP_POINTING_TO] =
1820 g_param_spec_boxed (name: "pointing-to",
1821 P_("Pointing to"),
1822 P_("Rectangle the bubble window points to"),
1823 GDK_TYPE_RECTANGLE,
1824 GTK_PARAM_READWRITE);
1825
1826 /**
1827 * GtkPopover:position: (attributes org.gtk.Property.get=gtk_popover_get_position org.gtk.Property.set=gtk_popover_set_position)
1828 *
1829 * How to place the popover, relative to its parent.
1830 */
1831 properties[PROP_POSITION] =
1832 g_param_spec_enum (name: "position",
1833 P_("Position"),
1834 P_("Position to place the bubble window"),
1835 enum_type: GTK_TYPE_POSITION_TYPE, default_value: GTK_POS_BOTTOM,
1836 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1837
1838 /**
1839 * GtkPopover:autohide: (attributes org.gtk.Property.get=gtk_popover_get_autohide org.gtk.Property.set=gtk_popover_set_autohide)
1840 *
1841 * Whether to dismiss the popover on outside clicks.
1842 */
1843 properties[PROP_AUTOHIDE] =
1844 g_param_spec_boolean (name: "autohide",
1845 P_("Autohide"),
1846 P_("Whether to dismiss the popover on outside clicks"),
1847 TRUE,
1848 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1849
1850 /**
1851 * GtkPopover:default-widget: (attributes org.gtk.Popover.set=gtk_popover_set_default_widget)
1852 *
1853 * The default widget inside the popover.
1854 */
1855 properties[PROP_DEFAULT_WIDGET] =
1856 g_param_spec_object (name: "default-widget",
1857 P_("Default widget"),
1858 P_("The default widget"),
1859 GTK_TYPE_WIDGET,
1860 GTK_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY);
1861
1862 /**
1863 * GtkPopover:has-arrow: (attributes org.gtk.Popover.get=gtk_popover_get_has_arrow org.gtk.Property.set=gtk_popover_set_has_arrow)
1864 *
1865 * Whether to draw an arrow.
1866 */
1867 properties[PROP_HAS_ARROW] =
1868 g_param_spec_boolean (name: "has-arrow",
1869 P_("Has Arrow"),
1870 P_("Whether to draw an arrow"),
1871 TRUE,
1872 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1873
1874 /**
1875 * GtkPopover:mnemonics-visible: (attributes org.gtk.Property.get=gtk_popover_get_mnemonics_visible org.gtk.Property.set=gtk_popover_set_mnemonics_visible)
1876 *
1877 * Whether mnemonics are currently visible in this popover.
1878 */
1879 properties[PROP_MNEMONICS_VISIBLE] =
1880 g_param_spec_boolean (name: "mnemonics-visible",
1881 P_("Mnemonics visible"),
1882 P_("Whether mnemonics are currently visible in this popover"),
1883 FALSE,
1884 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1885
1886 /**
1887 * GtkPopover:child: (attributes org.gtk.Property.get=gtk_popover_get_child org.gtk.Property.set=gtk_popover_set_child)
1888 *
1889 * The child widget.
1890 */
1891 properties[PROP_CHILD] =
1892 g_param_spec_object (name: "child",
1893 P_("Child"),
1894 P_("The child widget"),
1895 GTK_TYPE_WIDGET,
1896 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1897
1898 /**
1899 * GtkPopover:cascade-popdown: (attributes org.gtk.Property.get=gtk_popover_get_cascade_popdown org.gtk.Property.set=gtk_popover_set_cascade_popdown)
1900 *
1901 * Whether the popover pops down after a child popover.
1902 *
1903 * This is used to implement the expected behavior of submenus.
1904 */
1905 properties[PROP_CASCADE_POPDOWN] =
1906 g_param_spec_boolean (name: "cascade-popdown",
1907 P_("Cascade popdown"),
1908 P_("Whether the popover pops down after a child popover"),
1909 FALSE,
1910 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1911
1912 g_object_class_install_properties (oclass: object_class, n_pspecs: NUM_PROPERTIES, pspecs: properties);
1913
1914 /**
1915 * GtkPopover::closed:
1916 * @self: the `GtkPopover` which received the signal
1917 *
1918 * Emitted when the popover is closed.
1919 */
1920 signals[CLOSED] =
1921 g_signal_new (I_("closed"),
1922 G_TYPE_FROM_CLASS (object_class),
1923 signal_flags: G_SIGNAL_RUN_LAST,
1924 G_STRUCT_OFFSET (GtkPopoverClass, closed),
1925 NULL, NULL,
1926 NULL,
1927 G_TYPE_NONE,
1928 n_params: 0);
1929
1930 /**
1931 * GtkPopover::activate-default:
1932 * @self: the `GtkPopover` which received the signal
1933 *
1934 * Emitted whend the user activates the default widget.
1935 *
1936 * This is a [keybinding signal](class.SignalAction.html).
1937 */
1938 signals[ACTIVATE_DEFAULT] =
1939 g_signal_new (I_("activate-default"),
1940 G_TYPE_FROM_CLASS (object_class),
1941 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1942 G_STRUCT_OFFSET (GtkPopoverClass, activate_default),
1943 NULL, NULL,
1944 NULL,
1945 G_TYPE_NONE,
1946 n_params: 0);
1947
1948 add_arrow_bindings (widget_class, GDK_KEY_Up, direction: GTK_DIR_UP);
1949 add_arrow_bindings (widget_class, GDK_KEY_Down, direction: GTK_DIR_DOWN);
1950 add_arrow_bindings (widget_class, GDK_KEY_Left, direction: GTK_DIR_LEFT);
1951 add_arrow_bindings (widget_class, GDK_KEY_Right, direction: GTK_DIR_RIGHT);
1952
1953 add_tab_bindings (widget_class, modifiers: 0, direction: GTK_DIR_TAB_FORWARD);
1954 add_tab_bindings (widget_class, modifiers: GDK_CONTROL_MASK, direction: GTK_DIR_TAB_FORWARD);
1955 add_tab_bindings (widget_class, modifiers: GDK_SHIFT_MASK, direction: GTK_DIR_TAB_BACKWARD);
1956 add_tab_bindings (widget_class, modifiers: GDK_CONTROL_MASK | GDK_SHIFT_MASK, direction: GTK_DIR_TAB_BACKWARD);
1957
1958 gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_Return, mods: 0,
1959 signal: "activate-default", NULL);
1960 gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_ISO_Enter, mods: 0,
1961 signal: "activate-default", NULL);
1962 gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_KP_Enter, mods: 0,
1963 signal: "activate-default", NULL);
1964
1965 gtk_widget_class_set_css_name (widget_class, name: "popover");
1966}
1967
1968/**
1969 * gtk_popover_new:
1970 *
1971 * Creates a new `GtkPopover`.
1972 *
1973 * Returns: the new `GtkPopover`
1974 */
1975GtkWidget *
1976gtk_popover_new (void)
1977{
1978 return g_object_new (GTK_TYPE_POPOVER, NULL);
1979}
1980
1981/**
1982 * gtk_popover_set_child: (attributes org.gtk.Method.set_property=child)
1983 * @popover: a `GtkPopover`
1984 * @child: (nullable): the child widget
1985 *
1986 * Sets the child widget of @popover.
1987 */
1988void
1989gtk_popover_set_child (GtkPopover *popover,
1990 GtkWidget *child)
1991{
1992 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
1993
1994 g_return_if_fail (GTK_IS_POPOVER (popover));
1995 g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
1996
1997 if (priv->child == child)
1998 return;
1999
2000 g_clear_pointer (&priv->child, gtk_widget_unparent);
2001
2002 if (child)
2003 {
2004 priv->child = child;
2005 gtk_widget_set_parent (widget: child, parent: priv->contents_widget);
2006 }
2007
2008 g_object_notify_by_pspec (G_OBJECT (popover), pspec: properties[PROP_CHILD]);
2009}
2010
2011/**
2012 * gtk_popover_get_child: (attributes org.gtk.Method.get_property=child)
2013 * @popover: a `GtkPopover`
2014 *
2015 * Gets the child widget of @popover.
2016 *
2017 * Returns: (nullable) (transfer none): the child widget of @popover
2018 */
2019GtkWidget *
2020gtk_popover_get_child (GtkPopover *popover)
2021{
2022 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2023
2024 g_return_val_if_fail (GTK_IS_POPOVER (popover), NULL);
2025
2026 return priv->child;
2027}
2028
2029
2030/**
2031 * gtk_popover_set_default_widget: (attributes org.gtk.Method.set_property=default-widget)
2032 * @popover: a `GtkPopover`
2033 * @widget: (nullable): a child widget of @popover to set as
2034 * the default, or %NULL to unset the default widget for the popover
2035 *
2036 * Sets the default widget of a `GtkPopover`.
2037 *
2038 * The default widget is the widget that’s activated when the user
2039 * presses Enter in a dialog (for example). This function sets or
2040 * unsets the default widget for a `GtkPopover`.
2041 */
2042void
2043gtk_popover_set_default_widget (GtkPopover *popover,
2044 GtkWidget *widget)
2045{
2046 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2047
2048 g_return_if_fail (GTK_IS_POPOVER (popover));
2049
2050 if (priv->default_widget == widget)
2051 return;
2052
2053 if (priv->default_widget)
2054 {
2055 _gtk_widget_set_has_default (widget: priv->default_widget, FALSE);
2056 gtk_widget_queue_draw (widget: priv->default_widget);
2057 g_object_notify (G_OBJECT (priv->default_widget), property_name: "has-default");
2058 }
2059
2060 g_set_object (&priv->default_widget, widget);
2061
2062 if (priv->default_widget)
2063 {
2064 _gtk_widget_set_has_default (widget: priv->default_widget, TRUE);
2065 gtk_widget_queue_draw (widget: priv->default_widget);
2066 g_object_notify (G_OBJECT (priv->default_widget), property_name: "has-default");
2067 }
2068
2069 g_object_notify_by_pspec (G_OBJECT (popover), pspec: properties[PROP_DEFAULT_WIDGET]);
2070}
2071
2072static void
2073gtk_popover_shortcut_manager_interface_init (GtkShortcutManagerInterface *iface)
2074{
2075}
2076
2077static void
2078gtk_popover_native_interface_init (GtkNativeInterface *iface)
2079{
2080 iface->get_surface = gtk_popover_native_get_surface;
2081 iface->get_renderer = gtk_popover_native_get_renderer;
2082 iface->get_surface_transform = gtk_popover_native_get_surface_transform;
2083 iface->layout = gtk_popover_native_layout;
2084}
2085
2086static GtkBuildableIface *parent_buildable_iface;
2087
2088static void
2089gtk_popover_buildable_add_child (GtkBuildable *buildable,
2090 GtkBuilder *builder,
2091 GObject *child,
2092 const char *type)
2093{
2094 if (GTK_IS_WIDGET (child))
2095 gtk_popover_set_child (GTK_POPOVER (buildable), GTK_WIDGET (child));
2096 else
2097 parent_buildable_iface->add_child (buildable, builder, child, type);
2098}
2099
2100static void
2101gtk_popover_buildable_init (GtkBuildableIface *iface)
2102{
2103 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
2104
2105 iface->add_child = gtk_popover_buildable_add_child;
2106}
2107
2108/**
2109 * gtk_popover_set_pointing_to: (attributes org.gtk.Method.set_property=pointing-to)
2110 * @popover: a `GtkPopover`
2111 * @rect: (nullable): rectangle to point to
2112 *
2113 * Sets the rectangle that @popover points to.
2114 *
2115 * This is in the coordinate space of the @popover parent.
2116 */
2117void
2118gtk_popover_set_pointing_to (GtkPopover *popover,
2119 const GdkRectangle *rect)
2120{
2121 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2122
2123 g_return_if_fail (GTK_IS_POPOVER (popover));
2124
2125 if (rect)
2126 {
2127 priv->pointing_to = *rect;
2128 priv->has_pointing_to = TRUE;
2129 priv->pointing_to.width = MAX (priv->pointing_to.width, 1);
2130 priv->pointing_to.height = MAX (priv->pointing_to.height, 1);
2131 }
2132 else
2133 priv->has_pointing_to = FALSE;
2134
2135 g_object_notify_by_pspec (G_OBJECT (popover), pspec: properties[PROP_POINTING_TO]);
2136
2137 if (gtk_widget_is_visible (GTK_WIDGET (popover)))
2138 present_popup (popover);
2139}
2140
2141/**
2142 * gtk_popover_get_pointing_to: (attributes org.gtk.Method.get_property=pointing-to)
2143 * @popover: a `GtkPopover`
2144 * @rect: (out): location to store the rectangle
2145 *
2146 * Gets the rectangle that the popover points to.
2147 *
2148 * If a rectangle to point to has been set, this function will
2149 * return %TRUE and fill in @rect with such rectangle, otherwise
2150 * it will return %FALSE and fill in @rect with the parent
2151 * widget coordinates.
2152 *
2153 * Returns: %TRUE if a rectangle to point to was set.
2154 */
2155gboolean
2156gtk_popover_get_pointing_to (GtkPopover *popover,
2157 GdkRectangle *rect)
2158{
2159 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2160
2161 g_return_val_if_fail (GTK_IS_POPOVER (popover), FALSE);
2162 g_return_val_if_fail (rect != NULL, FALSE);
2163
2164 if (priv->has_pointing_to)
2165 *rect = priv->pointing_to;
2166 else
2167 {
2168 graphene_rect_t r;
2169 GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (popover));
2170
2171 if (!gtk_widget_compute_bounds (widget: parent, target: parent, out_bounds: &r))
2172 return FALSE;
2173
2174 rect->x = floorf (x: r.origin.x);
2175 rect->y = floorf (x: r.origin.y);
2176 rect->width = ceilf (x: r.size.width);
2177 rect->height = ceilf (x: r.size.height);
2178 }
2179
2180 return priv->has_pointing_to;
2181}
2182
2183/**
2184 * gtk_popover_set_position: (attributes org.gtk.Method.set_property=position)
2185 * @popover: a `GtkPopover`
2186 * @position: preferred popover position
2187 *
2188 * Sets the preferred position for @popover to appear.
2189 *
2190 * If the @popover is currently visible, it will be immediately
2191 * updated.
2192 *
2193 * This preference will be respected where possible, although
2194 * on lack of space (eg. if close to the window edges), the
2195 * `GtkPopover` may choose to appear on the opposite side.
2196 */
2197void
2198gtk_popover_set_position (GtkPopover *popover,
2199 GtkPositionType position)
2200{
2201 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2202
2203 g_return_if_fail (GTK_IS_POPOVER (popover));
2204
2205 if (priv->position == position)
2206 return;
2207
2208 priv->position = position;
2209 priv->final_position = position;
2210
2211 g_object_notify_by_pspec (G_OBJECT (popover), pspec: properties[PROP_POSITION]);
2212
2213 gtk_widget_queue_resize (GTK_WIDGET (popover));
2214
2215 if (gtk_widget_is_visible (GTK_WIDGET (popover)))
2216 present_popup (popover);
2217}
2218
2219/**
2220 * gtk_popover_get_position: (attributes org.gtk.Method.get_property=position)
2221 * @popover: a `GtkPopover`
2222 *
2223 * Returns the preferred position of @popover.
2224 *
2225 * Returns: The preferred position.
2226 */
2227GtkPositionType
2228gtk_popover_get_position (GtkPopover *popover)
2229{
2230 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2231
2232 g_return_val_if_fail (GTK_IS_POPOVER (popover), GTK_POS_TOP);
2233
2234 return priv->position;
2235}
2236
2237/**
2238 * gtk_popover_set_autohide: (attributes org.gtk.Method.set_property=autohide)
2239 * @popover: a `GtkPopover`
2240 * @autohide: %TRUE to dismiss the popover on outside clicks
2241 *
2242 * Sets whether @popover is modal.
2243 *
2244 * A modal popover will grab the keyboard focus on it when being
2245 * displayed. Focus will wrap around within the popover. Clicking
2246 * outside the popover area or pressing Esc will dismiss the popover.
2247 *
2248 * Called this function on an already showing popup with a new
2249 * autohide value different from the current one, will cause the
2250 * popup to be hidden.
2251 */
2252void
2253gtk_popover_set_autohide (GtkPopover *popover,
2254 gboolean autohide)
2255{
2256 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2257
2258 g_return_if_fail (GTK_IS_POPOVER (popover));
2259
2260 autohide = autohide != FALSE;
2261
2262 if (priv->autohide == autohide)
2263 return;
2264
2265 priv->autohide = autohide;
2266
2267 gtk_widget_unrealize (GTK_WIDGET (popover));
2268
2269 g_object_notify_by_pspec (G_OBJECT (popover), pspec: properties[PROP_AUTOHIDE]);
2270}
2271
2272/**
2273 * gtk_popover_get_autohide: (attributes org.gtk.Method.get_property=autohide)
2274 * @popover: a `GtkPopover`
2275 *
2276 * Returns whether the popover is modal.
2277 *
2278 * See [method@Gtk.Popover.set_autohide] for the
2279 * implications of this.
2280 *
2281 * Returns: %TRUE if @popover is modal
2282 */
2283gboolean
2284gtk_popover_get_autohide (GtkPopover *popover)
2285{
2286 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2287
2288 g_return_val_if_fail (GTK_IS_POPOVER (popover), FALSE);
2289
2290 return priv->autohide;
2291}
2292
2293/**
2294 * gtk_popover_popup:
2295 * @popover: a `GtkPopover`
2296 *
2297 * Pops @popover up.
2298 */
2299void
2300gtk_popover_popup (GtkPopover *popover)
2301{
2302 g_return_if_fail (GTK_IS_POPOVER (popover));
2303
2304 gtk_widget_show (GTK_WIDGET (popover));
2305}
2306
2307static void
2308cascade_popdown (GtkPopover *popover)
2309{
2310 GtkWidget *parent;
2311
2312 /* Do not trigger cascade close from non-modal popovers */
2313 if (!gtk_popover_get_autohide (popover))
2314 return;
2315
2316 parent = gtk_widget_get_parent (GTK_WIDGET (popover));
2317
2318 while (parent)
2319 {
2320 if (GTK_IS_POPOVER (parent))
2321 {
2322 if (gtk_popover_get_cascade_popdown (GTK_POPOVER (parent)))
2323 gtk_widget_hide (widget: parent);
2324 else
2325 break;
2326 }
2327
2328 parent = gtk_widget_get_parent (widget: parent);
2329 }
2330}
2331
2332/**
2333 * gtk_popover_popdown:
2334 * @popover: a `GtkPopover`
2335 *
2336 * Pops @popover down.
2337 *
2338 * This may have the side-effect of closing a parent popover
2339 * as well. See [property@Gtk.Popover:cascade-popdown].
2340 */
2341void
2342gtk_popover_popdown (GtkPopover *popover)
2343{
2344 g_return_if_fail (GTK_IS_POPOVER (popover));
2345
2346 gtk_widget_hide (GTK_WIDGET (popover));
2347
2348 cascade_popdown (popover);
2349}
2350
2351GtkWidget *
2352gtk_popover_get_contents_widget (GtkPopover *popover)
2353{
2354 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2355
2356 return priv->contents_widget;
2357}
2358
2359/**
2360 * gtk_popover_set_has_arrow: (attributes org.gtk.Method.set_property=has-arrow)
2361 * @popover: a `GtkPopover`
2362 * @has_arrow: %TRUE to draw an arrow
2363 *
2364 * Sets whether this popover should draw an arrow
2365 * pointing at the widget it is relative to.
2366 */
2367void
2368gtk_popover_set_has_arrow (GtkPopover *popover,
2369 gboolean has_arrow)
2370{
2371 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2372
2373 g_return_if_fail (GTK_IS_POPOVER (popover));
2374
2375 if (priv->has_arrow == has_arrow)
2376 return;
2377
2378 priv->has_arrow = has_arrow;
2379
2380 g_object_notify_by_pspec (G_OBJECT (popover), pspec: properties[PROP_HAS_ARROW]);
2381 gtk_widget_queue_resize (GTK_WIDGET (popover));
2382}
2383
2384/**
2385 * gtk_popover_get_has_arrow: (attributes org.gtk.Method.get_property=has-arrow)
2386 * @popover: a `GtkPopover`
2387 *
2388 * Gets whether this popover is showing an arrow
2389 * pointing at the widget that it is relative to.
2390 *
2391 * Returns: whether the popover has an arrow
2392 */
2393gboolean
2394gtk_popover_get_has_arrow (GtkPopover *popover)
2395{
2396 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2397
2398 g_return_val_if_fail (GTK_IS_POPOVER (popover), TRUE);
2399
2400 return priv->has_arrow;
2401}
2402
2403/**
2404 * gtk_popover_set_mnemonics_visible: (attributes org.gtk.Method.set_property=mnemonics-visible)
2405 * @popover: a `GtkPopover`
2406 * @mnemonics_visible: the new value
2407 *
2408 * Sets whether mnemonics should be visible.
2409 */
2410void
2411gtk_popover_set_mnemonics_visible (GtkPopover *popover,
2412 gboolean mnemonics_visible)
2413{
2414 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2415
2416 g_return_if_fail (GTK_IS_POPOVER (popover));
2417
2418 if (priv->mnemonics_visible == mnemonics_visible)
2419 return;
2420
2421 priv->mnemonics_visible = mnemonics_visible;
2422
2423 g_object_notify_by_pspec (G_OBJECT (popover), pspec: properties[PROP_MNEMONICS_VISIBLE]);
2424 gtk_widget_queue_resize (GTK_WIDGET (popover));
2425
2426 if (priv->mnemonics_display_timeout_id)
2427 {
2428 g_source_remove (tag: priv->mnemonics_display_timeout_id);
2429 priv->mnemonics_display_timeout_id = 0;
2430 }
2431}
2432
2433/**
2434 * gtk_popover_get_mnemonics_visible: (attributes org.gtk.Method.get_property=mnemonics-visible)
2435 * @popover: a `GtkPopover`
2436 *
2437 * Gets whether mnemonics are visible.
2438 *
2439 * Returns: %TRUE if mnemonics are supposed to be visible
2440 * in this popover
2441 */
2442gboolean
2443gtk_popover_get_mnemonics_visible (GtkPopover *popover)
2444{
2445 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2446
2447 g_return_val_if_fail (GTK_IS_POPOVER (popover), FALSE);
2448
2449 return priv->mnemonics_visible;
2450}
2451
2452void
2453gtk_popover_disable_auto_mnemonics (GtkPopover *popover)
2454{
2455 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2456
2457 priv->disable_auto_mnemonics = TRUE;
2458}
2459
2460/**
2461 * gtk_popover_set_offset:
2462 * @popover: a `GtkPopover`
2463 * @x_offset: the x offset to adjust the position by
2464 * @y_offset: the y offset to adjust the position by
2465 *
2466 * Sets the offset to use when calculating the position
2467 * of the popover.
2468 *
2469 * These values are used when preparing the [struct@Gdk.PopupLayout]
2470 * for positioning the popover.
2471 */
2472void
2473gtk_popover_set_offset (GtkPopover *popover,
2474 int x_offset,
2475 int y_offset)
2476{
2477 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2478
2479 g_return_if_fail (GTK_IS_POPOVER (popover));
2480
2481 if (priv->x_offset != x_offset || priv->y_offset != y_offset)
2482 {
2483 priv->x_offset = x_offset;
2484 priv->y_offset = y_offset;
2485
2486 gtk_widget_queue_resize (GTK_WIDGET (popover));
2487 }
2488}
2489
2490/**
2491 * gtk_popover_get_offset:
2492 * @popover: a `GtkPopover`
2493 * @x_offset: (out) (nullable): a location for the x_offset
2494 * @y_offset: (out) (nullable): a location for the y_offset
2495 *
2496 * Gets the offset previous set with gtk_popover_set_offset().
2497 */
2498void
2499gtk_popover_get_offset (GtkPopover *popover,
2500 int *x_offset,
2501 int *y_offset)
2502{
2503 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2504
2505 g_return_if_fail (GTK_IS_POPOVER (popover));
2506
2507 if (x_offset)
2508 *x_offset = priv->x_offset;
2509
2510 if (y_offset)
2511 *y_offset = priv->y_offset;
2512}
2513
2514/**
2515 * gtk_popover_set_cascade_popdown: (attributes org.gtk.Method.set_property=cascade-popdown)
2516 * @popover: A `GtkPopover`
2517 * @cascade_popdown: %TRUE if the popover should follow a child closing
2518 *
2519 * If @cascade_popdown is %TRUE, the popover will be
2520 * closed when a child modal popover is closed.
2521 *
2522 * If %FALSE, @popover will stay visible.
2523 */
2524void
2525gtk_popover_set_cascade_popdown (GtkPopover *popover,
2526 gboolean cascade_popdown)
2527{
2528 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2529
2530 if (priv->cascade_popdown != !!cascade_popdown)
2531 {
2532 priv->cascade_popdown = !!cascade_popdown;
2533 g_object_notify (G_OBJECT (popover), property_name: "cascade-popdown");
2534 }
2535}
2536
2537/**
2538 * gtk_popover_get_cascade_popdown: (attributes org.gtk.Method.get_property=cascade-popdown)
2539 * @popover: a `GtkPopover`
2540 *
2541 * Returns whether the popover will close after a modal child is closed.
2542 *
2543 * Returns: %TRUE if @popover will close after a modal child.
2544 */
2545gboolean
2546gtk_popover_get_cascade_popdown (GtkPopover *popover)
2547{
2548 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (self: popover);
2549
2550 return priv->cascade_popdown;
2551}
2552

source code of gtk/gtk/gtkpopover.c