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 | |
144 | typedef 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 | |
174 | enum { |
175 | CLOSED, |
176 | ACTIVATE_DEFAULT, |
177 | LAST_SIGNAL |
178 | }; |
179 | |
180 | static guint signals[LAST_SIGNAL] = { 0 }; |
181 | |
182 | enum { |
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 | |
194 | static GParamSpec *properties[NUM_PROPERTIES] = { NULL }; |
195 | |
196 | static void gtk_popover_buildable_init (GtkBuildableIface *iface); |
197 | |
198 | static void gtk_popover_shortcut_manager_interface_init (GtkShortcutManagerInterface *iface); |
199 | static void gtk_popover_native_interface_init (GtkNativeInterface *iface); |
200 | |
201 | G_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 | |
211 | static GdkSurface * |
212 | gtk_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 | |
220 | static GskRenderer * |
221 | gtk_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 | |
229 | static void |
230 | gtk_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 | |
244 | static gboolean |
245 | is_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 | |
266 | static gboolean |
267 | is_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 | |
288 | static gboolean |
289 | is_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 | |
310 | static gboolean |
311 | is_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 | |
332 | static gboolean |
333 | did_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 | |
350 | static gboolean |
351 | did_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 | |
368 | static void |
369 | update_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 * = 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 | |
436 | static GdkPopupLayout * |
437 | (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 | |
580 | static gboolean |
581 | (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 | */ |
605 | void |
606 | gtk_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 | |
616 | static void |
617 | maybe_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 | |
644 | static void |
645 | gtk_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 | |
671 | static gboolean |
672 | gtk_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 | |
697 | static gboolean |
698 | schedule_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 | |
710 | static void |
711 | gtk_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 | |
723 | static void |
724 | gtk_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 | |
739 | static void |
740 | gtk_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 | |
751 | static void |
752 | update_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 | |
772 | static gboolean |
773 | gtk_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 | |
794 | static gboolean |
795 | gtk_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 | |
810 | static void |
811 | surface_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 | |
819 | static gboolean |
820 | surface_render (GdkSurface *surface, |
821 | cairo_region_t *region, |
822 | GtkWidget *widget) |
823 | { |
824 | gtk_widget_render (widget, surface, region); |
825 | return TRUE; |
826 | } |
827 | |
828 | static gboolean |
829 | surface_event (GdkSurface *surface, |
830 | GdkEvent *event, |
831 | GtkWidget *widget) |
832 | { |
833 | gtk_main_do_event (event); |
834 | return TRUE; |
835 | } |
836 | |
837 | static void |
838 | gtk_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 | |
855 | static void |
856 | activate_default_cb (GSimpleAction *action, |
857 | GVariant *parameter, |
858 | gpointer data) |
859 | { |
860 | gtk_popover_activate_default (GTK_POPOVER (data)); |
861 | } |
862 | |
863 | static void |
864 | add_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 | |
880 | static void |
881 | node_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 | |
894 | static void |
895 | gtk_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 | |
937 | static void |
938 | gtk_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 | |
964 | static void |
965 | gtk_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 | |
985 | static gboolean |
986 | gtk_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 | |
1038 | static void |
1039 | gtk_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 | |
1060 | static void |
1061 | gtk_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 | |
1075 | static void |
1076 | unset_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 | |
1084 | static gboolean |
1085 | surface_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 | |
1098 | static void |
1099 | gtk_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 | |
1117 | static void |
1118 | gtk_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 | |
1133 | static void |
1134 | gtk_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 | |
1146 | static void |
1147 | gtk_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 | |
1163 | static void |
1164 | gtk_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 | |
1280 | static void |
1281 | get_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 | |
1294 | static void |
1295 | gtk_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 | |
1323 | static void |
1324 | gtk_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 | |
1402 | static int |
1403 | get_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 | |
1411 | static int |
1412 | get_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 | |
1438 | static void |
1439 | gtk_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 | |
1487 | static void |
1488 | gtk_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 | |
1544 | static void |
1545 | create_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 | |
1603 | static void |
1604 | gtk_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 | |
1621 | static void |
1622 | gtk_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 | |
1669 | static void |
1670 | gtk_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 | |
1718 | static void |
1719 | add_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 | |
1731 | static void |
1732 | add_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 | |
1756 | static void |
1757 | gtk_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 | |
1776 | static GtkSizeRequestMode |
1777 | gtk_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 | |
1788 | static void |
1789 | gtk_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 | */ |
1975 | GtkWidget * |
1976 | gtk_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 | */ |
1988 | void |
1989 | gtk_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 | */ |
2019 | GtkWidget * |
2020 | gtk_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 | */ |
2042 | void |
2043 | gtk_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 | |
2072 | static void |
2073 | gtk_popover_shortcut_manager_interface_init (GtkShortcutManagerInterface *iface) |
2074 | { |
2075 | } |
2076 | |
2077 | static void |
2078 | gtk_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 | |
2086 | static GtkBuildableIface *parent_buildable_iface; |
2087 | |
2088 | static void |
2089 | gtk_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 | |
2100 | static void |
2101 | gtk_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 | */ |
2117 | void |
2118 | gtk_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 | */ |
2155 | gboolean |
2156 | gtk_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 | */ |
2197 | void |
2198 | gtk_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 | */ |
2227 | GtkPositionType |
2228 | gtk_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 | */ |
2252 | void |
2253 | gtk_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 | */ |
2283 | gboolean |
2284 | gtk_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 | */ |
2299 | void |
2300 | (GtkPopover *popover) |
2301 | { |
2302 | g_return_if_fail (GTK_IS_POPOVER (popover)); |
2303 | |
2304 | gtk_widget_show (GTK_WIDGET (popover)); |
2305 | } |
2306 | |
2307 | static void |
2308 | cascade_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 | */ |
2341 | void |
2342 | gtk_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 | |
2351 | GtkWidget * |
2352 | gtk_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 | */ |
2367 | void |
2368 | gtk_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 | */ |
2393 | gboolean |
2394 | gtk_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 | */ |
2410 | void |
2411 | gtk_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 | */ |
2442 | gboolean |
2443 | gtk_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 | |
2452 | void |
2453 | gtk_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 | */ |
2472 | void |
2473 | gtk_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 | */ |
2498 | void |
2499 | gtk_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 | */ |
2524 | void |
2525 | gtk_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 | */ |
2545 | gboolean |
2546 | gtk_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 | |