1 /* GTK - The GIMP Toolkit
2 * Copyright © 2013 Carlos Garnacho <carlosg@gnome.org>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 /**
19 * SECTION:gtkpopover
20 * @Short_description: Context dependent bubbles
21 * @Title: GtkPopover
22 *
23 * GtkPopover is a bubble-like context window, primarily meant to
24 * provide context-dependent information or options. Popovers are
25 * attached to a widget, passed at construction time on gtk_popover_new(),
26 * or updated afterwards through gtk_popover_set_relative_to(), by
27 * default they will point to the whole widget area, although this
28 * behavior can be changed through gtk_popover_set_pointing_to().
29 *
30 * The position of a popover relative to the widget it is attached to
31 * can also be changed through gtk_popover_set_position().
32 *
33 * By default, #GtkPopover performs a GTK+ grab, in order to ensure
34 * input events get redirected to it while it is shown, and also so
35 * the popover is dismissed in the expected situations (clicks outside
36 * the popover, or the Esc key being pressed). If no such modal behavior
37 * is desired on a popover, gtk_popover_set_modal() may be called on it
38 * to tweak its behavior.
39 *
40 * ## GtkPopover as menu replacement
41 *
42 * GtkPopover is often used to replace menus. To facilitate this, it
43 * supports being populated from a #GMenuModel, using
44 * gtk_popover_new_from_model(). In addition to all the regular menu
45 * model features, this function supports rendering sections in the
46 * model in a more compact form, as a row of icon buttons instead of
47 * menu items.
48 *
49 * To use this rendering, set the ”display-hint” attribute of the
50 * section to ”horizontal-buttons” and set the icons of your items
51 * with the ”verb-icon” attribute.
52 *
53 * |[
54 * <section>
55 * <attribute name="display-hint">horizontal-buttons</attribute>
56 * <item>
57 * <attribute name="label">Cut</attribute>
58 * <attribute name="action">app.cut</attribute>
59 * <attribute name="verb-icon">edit-cut-symbolic</attribute>
60 * </item>
61 * <item>
62 * <attribute name="label">Copy</attribute>
63 * <attribute name="action">app.copy</attribute>
64 * <attribute name="verb-icon">edit-copy-symbolic</attribute>
65 * </item>
66 * <item>
67 * <attribute name="label">Paste</attribute>
68 * <attribute name="action">app.paste</attribute>
69 * <attribute name="verb-icon">edit-paste-symbolic</attribute>
70 * </item>
71 * </section>
72 * ]|
73 *
74 * # CSS nodes
75 *
76 * GtkPopover has a single css node called popover. It always gets the
77 * .background style class and it gets the .menu style class if it is
78 * menu-like (e.g. #GtkPopoverMenu or created using gtk_popover_new_from_model().
79 *
80 * Particular uses of GtkPopover, such as touch selection popups
81 * or magnifiers in #GtkEntry or #GtkTextView get style classes
82 * like .touch-selection or .magnifier to differentiate from
83 * plain popovers.
84 *
85 * Since: 3.12
86 */
87
88 #include "config.h"
89 #include <gdk/gdk.h>
90 #include "gtkpopover.h"
91 #include "gtkpopoverprivate.h"
92 #include "gtktypebuiltins.h"
93 #include "gtkmain.h"
94 #include "gtkwindowprivate.h"
95 #include "gtkscrollable.h"
96 #include "gtkadjustment.h"
97 #include "gtkprivate.h"
98 #include "gtkintl.h"
99 #include "gtklabel.h"
100 #include "gtkbox.h"
101 #include "gtkbutton.h"
102 #include "gtkseparator.h"
103 #include "gtkmodelbutton.h"
104 #include "gtkwidgetprivate.h"
105 #include "gtkactionmuxer.h"
106 #include "gtkmenutracker.h"
107 #include "gtkstack.h"
108 #include "gtksizegroup.h"
109 #include "a11y/gtkpopoveraccessible.h"
110 #include "gtkmenusectionbox.h"
111 #include "gtkroundedboxprivate.h"
112 #include "gtkstylecontextprivate.h"
113 #include "gtkprogresstrackerprivate.h"
114 #include "gtksettingsprivate.h"
115
116 #ifdef GDK_WINDOWING_WAYLAND
117 #include "wayland/gdkwayland.h"
118 #endif
119
120 #define TAIL_GAP_WIDTH 24
121 #define TAIL_HEIGHT 12
122 #define TRANSITION_DIFF 20
123 #define TRANSITION_DURATION 150 * 1000
124
125 #define POS_IS_VERTICAL(p) ((p) == GTK_POS_TOP || (p) == GTK_POS_BOTTOM)
126
127 enum {
128 PROP_RELATIVE_TO = 1,
129 PROP_POINTING_TO,
130 PROP_POSITION,
131 PROP_MODAL,
132 PROP_TRANSITIONS_ENABLED,
133 PROP_CONSTRAIN_TO,
134 NUM_PROPERTIES
135 };
136
137 enum {
138 CLOSED,
139 N_SIGNALS
140 };
141
142 enum {
143 STATE_SHOWING,
144 STATE_SHOWN,
145 STATE_HIDING,
146 STATE_HIDDEN
147 };
148
149 struct _GtkPopoverPrivate
150 {
151 GtkWidget *widget;
152 GtkWindow *window;
153 GtkWidget *prev_focus_widget;
154 GtkWidget *default_widget;
155 GtkWidget *prev_default;
156 GtkScrollable *parent_scrollable;
157 GtkAdjustment *vadj;
158 GtkAdjustment *hadj;
159 GdkRectangle pointing_to;
160 GtkPopoverConstraint constraint;
161 GtkProgressTracker tracker;
162 GtkGesture *multipress_gesture;
163 guint prev_focus_unmap_id;
164 guint hierarchy_changed_id;
165 guint size_allocate_id;
166 guint unmap_id;
167 guint scrollable_notify_id;
168 guint grab_notify_id;
169 guint state_changed_id;
170 guint has_pointing_to : 1;
171 guint preferred_position : 2;
172 guint final_position : 2;
173 guint current_position : 2;
174 guint modal : 1;
175 guint button_pressed : 1;
176 guint grab_notify_blocked : 1;
177 guint transitions_enabled : 1;
178 guint state : 2;
179 guint visible : 1;
180 guint first_frame_skipped : 1;
181 gint transition_diff;
182 guint tick_id;
183
184 gint tip_x;
185 gint tip_y;
186 };
187
188 static GParamSpec *properties[NUM_PROPERTIES];
189 static GQuark quark_widget_popovers = 0;
190 static guint signals[N_SIGNALS] = { 0 };
191
192 static void gtk_popover_update_relative_to (GtkPopover *popover,
193 GtkWidget *relative_to);
194 static void gtk_popover_set_state (GtkPopover *popover,
195 guint state);
196 static void gtk_popover_invalidate_borders (GtkPopover *popover);
197 static void gtk_popover_apply_modality (GtkPopover *popover,
198 gboolean modal);
199
200 static void gtk_popover_set_scrollable_full (GtkPopover *popover,
201 GtkScrollable *scrollable);
202
203 static void gtk_popover_multipress_gesture_pressed (GtkGestureMultiPress *gesture,
204 gint n_press,
205 gdouble widget_x,
206 gdouble widget_y,
207 GtkPopover *popover);
208
G_DEFINE_TYPE_WITH_PRIVATE(GtkPopover,gtk_popover,GTK_TYPE_BIN)209 G_DEFINE_TYPE_WITH_PRIVATE (GtkPopover, gtk_popover, GTK_TYPE_BIN)
210
211 static void
212 gtk_popover_init (GtkPopover *popover)
213 {
214 GtkWidget *widget;
215 GtkStyleContext *context;
216 GtkPopoverPrivate *priv;
217
218 widget = GTK_WIDGET (popover);
219 gtk_widget_set_has_window (widget, TRUE);
220 priv = popover->priv = gtk_popover_get_instance_private (popover);
221 priv->modal = TRUE;
222 priv->tick_id = 0;
223 priv->state = STATE_HIDDEN;
224 priv->visible = FALSE;
225 priv->transitions_enabled = TRUE;
226 priv->preferred_position = GTK_POS_TOP;
227 priv->constraint = GTK_POPOVER_CONSTRAINT_WINDOW;
228
229 priv->multipress_gesture = gtk_gesture_multi_press_new (widget);
230 g_signal_connect (priv->multipress_gesture, "pressed",
231 G_CALLBACK (gtk_popover_multipress_gesture_pressed), popover);
232 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->multipress_gesture), 0);
233 gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (priv->multipress_gesture), TRUE);
234 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->multipress_gesture),
235 GTK_PHASE_CAPTURE);
236
237 context = gtk_widget_get_style_context (GTK_WIDGET (popover));
238 gtk_style_context_add_class (context, GTK_STYLE_CLASS_BACKGROUND);
239 }
240
241 static void
gtk_popover_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)242 gtk_popover_set_property (GObject *object,
243 guint prop_id,
244 const GValue *value,
245 GParamSpec *pspec)
246 {
247 switch (prop_id)
248 {
249 case PROP_RELATIVE_TO:
250 gtk_popover_set_relative_to (GTK_POPOVER (object),
251 g_value_get_object (value));
252 break;
253 case PROP_POINTING_TO:
254 gtk_popover_set_pointing_to (GTK_POPOVER (object),
255 g_value_get_boxed (value));
256 break;
257 case PROP_POSITION:
258 gtk_popover_set_position (GTK_POPOVER (object),
259 g_value_get_enum (value));
260 break;
261 case PROP_MODAL:
262 gtk_popover_set_modal (GTK_POPOVER (object),
263 g_value_get_boolean (value));
264 break;
265 case PROP_TRANSITIONS_ENABLED:
266 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
267 gtk_popover_set_transitions_enabled (GTK_POPOVER (object),
268 g_value_get_boolean (value));
269 G_GNUC_END_IGNORE_DEPRECATIONS;
270 break;
271 case PROP_CONSTRAIN_TO:
272 gtk_popover_set_constrain_to (GTK_POPOVER (object),
273 g_value_get_enum (value));
274 break;
275 default:
276 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
277 }
278 }
279
280 static void
gtk_popover_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)281 gtk_popover_get_property (GObject *object,
282 guint prop_id,
283 GValue *value,
284 GParamSpec *pspec)
285 {
286 GtkPopoverPrivate *priv = GTK_POPOVER (object)->priv;
287
288 switch (prop_id)
289 {
290 case PROP_RELATIVE_TO:
291 g_value_set_object (value, priv->widget);
292 break;
293 case PROP_POINTING_TO:
294 g_value_set_boxed (value, &priv->pointing_to);
295 break;
296 case PROP_POSITION:
297 g_value_set_enum (value, priv->preferred_position);
298 break;
299 case PROP_MODAL:
300 g_value_set_boolean (value, priv->modal);
301 break;
302 case PROP_TRANSITIONS_ENABLED:
303 g_value_set_boolean (value, priv->transitions_enabled);
304 break;
305 case PROP_CONSTRAIN_TO:
306 g_value_set_enum (value, priv->constraint);
307 break;
308 default:
309 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
310 }
311 }
312
313 static gboolean
transitions_enabled(GtkPopover * popover)314 transitions_enabled (GtkPopover *popover)
315 {
316 GtkPopoverPrivate *priv = popover->priv;
317
318 return gtk_settings_get_enable_animations (gtk_widget_get_settings (GTK_WIDGET (popover))) &&
319 priv->transitions_enabled;
320 }
321
322 static void
gtk_popover_hide_internal(GtkPopover * popover)323 gtk_popover_hide_internal (GtkPopover *popover)
324 {
325 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
326 GtkWidget *widget = GTK_WIDGET (popover);
327
328 if (!priv->visible)
329 return;
330
331 priv->visible = FALSE;
332 g_signal_emit (widget, signals[CLOSED], 0);
333
334 if (priv->modal)
335 gtk_popover_apply_modality (popover, FALSE);
336
337 if (gtk_widget_get_realized (widget))
338 {
339 cairo_region_t *region = cairo_region_create ();
340 gdk_window_input_shape_combine_region (gtk_widget_get_parent_window (widget),
341 region, 0, 0);
342 cairo_region_destroy (region);
343 }
344 }
345
346 static void
gtk_popover_finalize(GObject * object)347 gtk_popover_finalize (GObject *object)
348 {
349 GtkPopover *popover = GTK_POPOVER (object);
350 GtkPopoverPrivate *priv = popover->priv;
351
352 if (priv->widget)
353 gtk_popover_update_relative_to (popover, NULL);
354
355 g_clear_object (&priv->multipress_gesture);
356
357 G_OBJECT_CLASS (gtk_popover_parent_class)->finalize (object);
358 }
359
360 static void
popover_unset_prev_focus(GtkPopover * popover)361 popover_unset_prev_focus (GtkPopover *popover)
362 {
363 GtkPopoverPrivate *priv = popover->priv;
364
365 if (!priv->prev_focus_widget)
366 return;
367
368 if (priv->prev_focus_unmap_id)
369 {
370 g_signal_handler_disconnect (priv->prev_focus_widget,
371 priv->prev_focus_unmap_id);
372 priv->prev_focus_unmap_id = 0;
373 }
374
375 g_clear_object (&priv->prev_focus_widget);
376 }
377
378 static void
gtk_popover_dispose(GObject * object)379 gtk_popover_dispose (GObject *object)
380 {
381 GtkPopover *popover = GTK_POPOVER (object);
382 GtkPopoverPrivate *priv = popover->priv;
383
384 if (priv->modal)
385 gtk_popover_apply_modality (popover, FALSE);
386
387 if (priv->window)
388 {
389 g_signal_handlers_disconnect_by_data (priv->window, popover);
390 _gtk_window_remove_popover (priv->window, GTK_WIDGET (object));
391 }
392
393 priv->window = NULL;
394
395 if (priv->widget)
396 gtk_popover_update_relative_to (popover, NULL);
397
398 popover_unset_prev_focus (popover);
399
400 g_clear_object (&priv->default_widget);
401
402 G_OBJECT_CLASS (gtk_popover_parent_class)->dispose (object);
403 }
404
405 static void
gtk_popover_realize(GtkWidget * widget)406 gtk_popover_realize (GtkWidget *widget)
407 {
408 GtkAllocation allocation;
409 GdkWindowAttr attributes;
410 gint attributes_mask;
411 GdkWindow *window;
412
413 gtk_widget_get_allocation (widget, &allocation);
414
415 attributes.x = 0;
416 attributes.y = 0;
417 attributes.width = allocation.width;
418 attributes.height = allocation.height;
419 attributes.window_type = GDK_WINDOW_CHILD;
420 attributes.visual = gtk_widget_get_visual (widget);
421 attributes.wclass = GDK_INPUT_OUTPUT;
422 attributes.event_mask =
423 gtk_widget_get_events (widget) |
424 GDK_POINTER_MOTION_MASK |
425 GDK_BUTTON_MOTION_MASK |
426 GDK_BUTTON_PRESS_MASK |
427 GDK_BUTTON_RELEASE_MASK |
428 GDK_ENTER_NOTIFY_MASK |
429 GDK_LEAVE_NOTIFY_MASK;
430
431 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
432 window = gdk_window_new (gtk_widget_get_parent_window (widget),
433 &attributes, attributes_mask);
434 gtk_widget_set_window (widget, window);
435 gtk_widget_register_window (widget, window);
436 gtk_widget_set_realized (widget, TRUE);
437 }
438
439 static gboolean
window_focus_in(GtkWidget * widget,GdkEvent * event,GtkPopover * popover)440 window_focus_in (GtkWidget *widget,
441 GdkEvent *event,
442 GtkPopover *popover)
443 {
444 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
445
446 /* Regain the grab when the window is focused */
447 if (priv->modal &&
448 gtk_widget_is_drawable (GTK_WIDGET (popover)))
449 {
450 GtkWidget *focus;
451
452 gtk_grab_add (GTK_WIDGET (popover));
453
454 focus = gtk_window_get_focus (GTK_WINDOW (widget));
455
456 if (focus == NULL || !gtk_widget_is_ancestor (focus, GTK_WIDGET (popover)))
457 gtk_widget_grab_focus (GTK_WIDGET (popover));
458
459 if (priv->grab_notify_blocked)
460 g_signal_handler_unblock (priv->widget, priv->grab_notify_id);
461
462 priv->grab_notify_blocked = FALSE;
463 }
464 return FALSE;
465 }
466
467 static gboolean
window_focus_out(GtkWidget * widget,GdkEvent * event,GtkPopover * popover)468 window_focus_out (GtkWidget *widget,
469 GdkEvent *event,
470 GtkPopover *popover)
471 {
472 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
473
474 /* Temporarily remove the grab when unfocused */
475 if (priv->modal &&
476 gtk_widget_is_drawable (GTK_WIDGET (popover)))
477 {
478 g_signal_handler_block (priv->widget, priv->grab_notify_id);
479 gtk_grab_remove (GTK_WIDGET (popover));
480 priv->grab_notify_blocked = TRUE;
481 }
482 return FALSE;
483 }
484
485 static void
window_set_focus(GtkWindow * window,GtkWidget * widget,GtkPopover * popover)486 window_set_focus (GtkWindow *window,
487 GtkWidget *widget,
488 GtkPopover *popover)
489 {
490 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
491
492 if (!priv->modal || !widget || !gtk_widget_is_drawable (GTK_WIDGET (popover)))
493 return;
494
495 widget = gtk_widget_get_ancestor (widget, GTK_TYPE_POPOVER);
496 while (widget != NULL)
497 {
498 if (widget == GTK_WIDGET (popover))
499 return;
500
501 widget = gtk_popover_get_relative_to (GTK_POPOVER (widget));
502 if (widget == NULL)
503 break;
504 widget = gtk_widget_get_ancestor (widget, GTK_TYPE_POPOVER);
505 }
506
507 popover_unset_prev_focus (popover);
508 gtk_widget_hide (GTK_WIDGET (popover));
509 }
510
511 static void
prev_focus_unmap_cb(GtkWidget * widget,GtkPopover * popover)512 prev_focus_unmap_cb (GtkWidget *widget,
513 GtkPopover *popover)
514 {
515 popover_unset_prev_focus (popover);
516 }
517
518 static void
gtk_popover_apply_modality(GtkPopover * popover,gboolean modal)519 gtk_popover_apply_modality (GtkPopover *popover,
520 gboolean modal)
521 {
522 GtkPopoverPrivate *priv = popover->priv;
523
524 if (!priv->window)
525 return;
526
527 if (modal)
528 {
529 GtkWidget *prev_focus;
530
531 prev_focus = gtk_window_get_focus (priv->window);
532 priv->prev_focus_widget = prev_focus;
533 if (priv->prev_focus_widget)
534 {
535 priv->prev_focus_unmap_id =
536 g_signal_connect (prev_focus, "unmap",
537 G_CALLBACK (prev_focus_unmap_cb), popover);
538 g_object_ref (prev_focus);
539 }
540 gtk_grab_add (GTK_WIDGET (popover));
541 gtk_window_set_focus (priv->window, NULL);
542 gtk_widget_grab_focus (GTK_WIDGET (popover));
543
544 g_signal_connect (priv->window, "focus-in-event",
545 G_CALLBACK (window_focus_in), popover);
546 g_signal_connect (priv->window, "focus-out-event",
547 G_CALLBACK (window_focus_out), popover);
548 g_signal_connect (priv->window, "set-focus",
549 G_CALLBACK (window_set_focus), popover);
550 }
551 else
552 {
553 g_signal_handlers_disconnect_by_data (priv->window, popover);
554 gtk_grab_remove (GTK_WIDGET (popover));
555
556 /* Let prev_focus_widget regain focus */
557 if (priv->prev_focus_widget &&
558 gtk_widget_is_drawable (priv->prev_focus_widget))
559 {
560 if (GTK_IS_ENTRY (priv->prev_focus_widget))
561 gtk_entry_grab_focus_without_selecting (GTK_ENTRY (priv->prev_focus_widget));
562 else
563 gtk_widget_grab_focus (priv->prev_focus_widget);
564 }
565 else if (priv->window)
566 gtk_widget_grab_focus (GTK_WIDGET (priv->window));
567
568 popover_unset_prev_focus (popover);
569 }
570 }
571
572 static gboolean
show_animate_cb(GtkWidget * widget,GdkFrameClock * frame_clock,gpointer user_data)573 show_animate_cb (GtkWidget *widget,
574 GdkFrameClock *frame_clock,
575 gpointer user_data)
576 {
577 GtkPopover *popover = GTK_POPOVER (widget);
578 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
579 gdouble t;
580
581 if (priv->first_frame_skipped)
582 gtk_progress_tracker_advance_frame (&priv->tracker,
583 gdk_frame_clock_get_frame_time (frame_clock));
584 else
585 priv->first_frame_skipped = TRUE;
586
587 t = gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE);
588
589 if (priv->state == STATE_SHOWING)
590 {
591 priv->transition_diff = TRANSITION_DIFF - (TRANSITION_DIFF * t);
592 gtk_widget_set_opacity (widget, t);
593 }
594 else if (priv->state == STATE_HIDING)
595 {
596 priv->transition_diff = -TRANSITION_DIFF * t;
597 gtk_widget_set_opacity (widget, 1.0 - t);
598 }
599
600 gtk_popover_update_position (popover);
601
602 if (gtk_progress_tracker_get_state (&priv->tracker) == GTK_PROGRESS_STATE_AFTER)
603 {
604 if (priv->state == STATE_SHOWING)
605 {
606 gtk_popover_set_state (popover, STATE_SHOWN);
607
608 if (!priv->visible)
609 gtk_popover_set_state (popover, STATE_HIDING);
610 }
611 else
612 {
613 gtk_widget_hide (widget);
614 }
615
616 priv->tick_id = 0;
617 return G_SOURCE_REMOVE;
618 }
619 else
620 return G_SOURCE_CONTINUE;
621 }
622
623 static void
gtk_popover_stop_transition(GtkPopover * popover)624 gtk_popover_stop_transition (GtkPopover *popover)
625 {
626 GtkPopoverPrivate *priv = popover->priv;
627
628 if (priv->tick_id != 0)
629 {
630 gtk_widget_remove_tick_callback (GTK_WIDGET (popover), priv->tick_id);
631 priv->tick_id = 0;
632 }
633 }
634
635 static void
gtk_popover_start_transition(GtkPopover * popover)636 gtk_popover_start_transition (GtkPopover *popover)
637 {
638 GtkPopoverPrivate *priv = popover->priv;
639
640 if (priv->tick_id != 0)
641 return;
642
643 priv->first_frame_skipped = FALSE;
644 gtk_progress_tracker_start (&priv->tracker, TRANSITION_DURATION, 0, 1.0);
645 priv->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (popover),
646 show_animate_cb,
647 popover, NULL);
648 }
649
650 static void
gtk_popover_set_state(GtkPopover * popover,guint state)651 gtk_popover_set_state (GtkPopover *popover,
652 guint state)
653 {
654 GtkPopoverPrivate *priv = popover->priv;
655
656 if (!transitions_enabled (popover) ||
657 !gtk_widget_get_realized (GTK_WIDGET (popover)))
658 {
659 if (state == STATE_SHOWING)
660 state = STATE_SHOWN;
661 else if (state == STATE_HIDING)
662 state = STATE_HIDDEN;
663 }
664
665 priv->state = state;
666
667 if (state == STATE_SHOWING || state == STATE_HIDING)
668 gtk_popover_start_transition (popover);
669 else
670 {
671 gtk_popover_stop_transition (popover);
672
673 gtk_widget_set_visible (GTK_WIDGET (popover), state == STATE_SHOWN);
674 }
675 }
676
677 GtkWidget *
gtk_popover_get_prev_default(GtkPopover * popover)678 gtk_popover_get_prev_default (GtkPopover *popover)
679 {
680 g_return_val_if_fail (GTK_IS_POPOVER (popover), NULL);
681
682 return popover->priv->prev_default;
683 }
684
685
686 static void
gtk_popover_map(GtkWidget * widget)687 gtk_popover_map (GtkWidget *widget)
688 {
689 GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
690
691 priv->prev_default = gtk_window_get_default_widget (priv->window);
692 if (priv->prev_default)
693 g_object_ref (priv->prev_default);
694
695 GTK_WIDGET_CLASS (gtk_popover_parent_class)->map (widget);
696
697 gdk_window_show (gtk_widget_get_window (widget));
698 gtk_popover_update_position (GTK_POPOVER (widget));
699
700 gtk_window_set_default (priv->window, priv->default_widget);
701 }
702
703 static void
gtk_popover_unmap(GtkWidget * widget)704 gtk_popover_unmap (GtkWidget *widget)
705 {
706 GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
707
708 priv->button_pressed = FALSE;
709
710 gdk_window_hide (gtk_widget_get_window (widget));
711 GTK_WIDGET_CLASS (gtk_popover_parent_class)->unmap (widget);
712
713 if (gtk_window_get_default_widget (priv->window) == priv->default_widget)
714 gtk_window_set_default (priv->window, priv->prev_default);
715 g_clear_object (&priv->prev_default);
716 }
717
718 static GtkPositionType
get_effective_position(GtkPopover * popover,GtkPositionType pos)719 get_effective_position (GtkPopover *popover,
720 GtkPositionType pos)
721 {
722 if (gtk_widget_get_direction (GTK_WIDGET (popover)) == GTK_TEXT_DIR_RTL)
723 {
724 if (pos == GTK_POS_LEFT)
725 pos = GTK_POS_RIGHT;
726 else if (pos == GTK_POS_RIGHT)
727 pos = GTK_POS_LEFT;
728 }
729
730 return pos;
731 }
732
733 static void
get_margin(GtkWidget * widget,GtkBorder * border)734 get_margin (GtkWidget *widget,
735 GtkBorder *border)
736 {
737 GtkStyleContext *context;
738
739 context = gtk_widget_get_style_context (widget);
740 gtk_style_context_get_margin (context,
741 gtk_style_context_get_state (context),
742 border);
743 }
744
745 static void
gtk_popover_get_gap_coords(GtkPopover * popover,gint * initial_x_out,gint * initial_y_out,gint * tip_x_out,gint * tip_y_out,gint * final_x_out,gint * final_y_out,GtkPositionType * gap_side_out)746 gtk_popover_get_gap_coords (GtkPopover *popover,
747 gint *initial_x_out,
748 gint *initial_y_out,
749 gint *tip_x_out,
750 gint *tip_y_out,
751 gint *final_x_out,
752 gint *final_y_out,
753 GtkPositionType *gap_side_out)
754 {
755 GtkWidget *widget = GTK_WIDGET (popover);
756 GtkPopoverPrivate *priv = popover->priv;
757 GdkRectangle rect;
758 gint base, tip, tip_pos;
759 gint initial_x, initial_y;
760 gint tip_x, tip_y;
761 gint final_x, final_y;
762 GtkPositionType gap_side, pos;
763 GtkAllocation allocation;
764 gint border_radius;
765 GtkStyleContext *context;
766 GtkBorder margin, border, widget_margin;
767 GtkStateFlags state;
768
769 gtk_popover_get_pointing_to (popover, &rect);
770 gtk_widget_get_allocation (widget, &allocation);
771
772 #ifdef GDK_WINDOWING_WAYLAND
773 if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (widget)))
774 {
775 gint win_x, win_y;
776
777 gtk_widget_translate_coordinates (priv->widget, GTK_WIDGET (priv->window),
778 rect.x, rect.y, &rect.x, &rect.y);
779 gdk_window_get_origin (gtk_widget_get_window (GTK_WIDGET (popover)),
780 &win_x, &win_y);
781 rect.x -= win_x;
782 rect.y -= win_y;
783 }
784 else
785 #endif
786 gtk_widget_translate_coordinates (priv->widget, widget,
787 rect.x, rect.y, &rect.x, &rect.y);
788
789 get_margin (widget, &margin);
790
791 if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
792 {
793 widget_margin.left = gtk_widget_get_margin_start (widget);
794 widget_margin.right = gtk_widget_get_margin_end (widget);
795 }
796 else
797 {
798 widget_margin.left = gtk_widget_get_margin_end (widget);
799 widget_margin.right = gtk_widget_get_margin_start (widget);
800 }
801
802 widget_margin.top = gtk_widget_get_margin_top (widget);
803 widget_margin.bottom = gtk_widget_get_margin_bottom (widget);
804
805 context = gtk_widget_get_style_context (widget);
806 state = gtk_style_context_get_state (context);
807
808 gtk_style_context_get_border (context, state, &border);
809 gtk_style_context_get (context,
810 state,
811 GTK_STYLE_PROPERTY_BORDER_RADIUS, &border_radius,
812 NULL);
813 pos = get_effective_position (popover, priv->final_position);
814
815 if (pos == GTK_POS_BOTTOM || pos == GTK_POS_RIGHT)
816 {
817 tip = ((pos == GTK_POS_BOTTOM) ? border.top + widget_margin.top : border.left + widget_margin.left);
818 base = tip + TAIL_HEIGHT;
819 gap_side = (priv->final_position == GTK_POS_BOTTOM) ? GTK_POS_TOP : GTK_POS_LEFT;
820 }
821 else if (pos == GTK_POS_TOP)
822 {
823 base = allocation.height - TAIL_HEIGHT - border.bottom - widget_margin.bottom;
824 tip = base + TAIL_HEIGHT;
825 gap_side = GTK_POS_BOTTOM;
826 }
827 else if (pos == GTK_POS_LEFT)
828 {
829 base = allocation.width - TAIL_HEIGHT - border.right - widget_margin.right;
830 tip = base + TAIL_HEIGHT;
831 gap_side = GTK_POS_RIGHT;
832 }
833 else
834 g_assert_not_reached ();
835
836 if (POS_IS_VERTICAL (pos))
837 {
838 tip_pos = rect.x + (rect.width / 2) + widget_margin.left;
839 initial_x = CLAMP (tip_pos - TAIL_GAP_WIDTH / 2,
840 border_radius + margin.left + TAIL_HEIGHT,
841 allocation.width - TAIL_GAP_WIDTH - margin.right - border_radius - TAIL_HEIGHT);
842 initial_y = base;
843
844 tip_x = CLAMP (tip_pos, 0, allocation.width);
845 tip_y = tip;
846
847 final_x = CLAMP (tip_pos + TAIL_GAP_WIDTH / 2,
848 border_radius + margin.left + TAIL_GAP_WIDTH + TAIL_HEIGHT,
849 allocation.width - margin.right - border_radius - TAIL_HEIGHT);
850 final_y = base;
851 }
852 else
853 {
854 tip_pos = rect.y + (rect.height / 2) + widget_margin.top;
855
856 initial_x = base;
857 initial_y = CLAMP (tip_pos - TAIL_GAP_WIDTH / 2,
858 border_radius + margin.top + TAIL_HEIGHT,
859 allocation.height - TAIL_GAP_WIDTH - margin.bottom - border_radius - TAIL_HEIGHT);
860
861 tip_x = tip;
862 tip_y = CLAMP (tip_pos, 0, allocation.height);
863
864 final_x = base;
865 final_y = CLAMP (tip_pos + TAIL_GAP_WIDTH / 2,
866 border_radius + margin.top + TAIL_GAP_WIDTH + TAIL_HEIGHT,
867 allocation.height - margin.right - border_radius - TAIL_HEIGHT);
868 }
869
870 if (initial_x_out)
871 *initial_x_out = initial_x;
872 if (initial_y_out)
873 *initial_y_out = initial_y;
874
875 if (tip_x_out)
876 *tip_x_out = tip_x;
877 if (tip_y_out)
878 *tip_y_out = tip_y;
879
880 if (final_x_out)
881 *final_x_out = final_x;
882 if (final_y_out)
883 *final_y_out = final_y;
884
885 if (gap_side_out)
886 *gap_side_out = gap_side;
887 }
888
889 static void
gtk_popover_get_rect_for_size(GtkPopover * popover,int popover_width,int popover_height,GdkRectangle * rect)890 gtk_popover_get_rect_for_size (GtkPopover *popover,
891 int popover_width,
892 int popover_height,
893 GdkRectangle *rect)
894 {
895 GtkWidget *widget = GTK_WIDGET (popover);
896 int x, y, w, h;
897 GtkBorder margin;
898
899 get_margin (widget, &margin);
900
901 x = 0;
902 y = 0;
903 w = popover_width;
904 h = popover_height;
905
906 x += MAX (TAIL_HEIGHT, margin.left);
907 y += MAX (TAIL_HEIGHT, margin.top);
908 w -= x + MAX (TAIL_HEIGHT, margin.right);
909 h -= y + MAX (TAIL_HEIGHT, margin.bottom);
910
911 rect->x = x;
912 rect->y = y;
913 rect->width = w;
914 rect->height = h;
915 }
916
917 static void
gtk_popover_get_rect_coords(GtkPopover * popover,int * x_out,int * y_out,int * w_out,int * h_out)918 gtk_popover_get_rect_coords (GtkPopover *popover,
919 int *x_out,
920 int *y_out,
921 int *w_out,
922 int *h_out)
923 {
924 GtkWidget *widget = GTK_WIDGET (popover);
925 GdkRectangle rect;
926 GtkAllocation allocation;
927
928 gtk_widget_get_allocation (widget, &allocation);
929 gtk_popover_get_rect_for_size (popover, allocation.width, allocation.height, &rect);
930
931 *x_out = rect.x;
932 *y_out = rect.y;
933 *w_out = rect.width;
934 *h_out = rect.height;
935 }
936
937 static void
gtk_popover_apply_tail_path(GtkPopover * popover,cairo_t * cr)938 gtk_popover_apply_tail_path (GtkPopover *popover,
939 cairo_t *cr)
940 {
941 gint initial_x, initial_y;
942 gint tip_x, tip_y;
943 gint final_x, final_y;
944
945 if (!popover->priv->widget)
946 return;
947
948 cairo_set_line_width (cr, 1);
949 gtk_popover_get_gap_coords (popover,
950 &initial_x, &initial_y,
951 &tip_x, &tip_y,
952 &final_x, &final_y,
953 NULL);
954
955 cairo_move_to (cr, initial_x, initial_y);
956 cairo_line_to (cr, tip_x, tip_y);
957 cairo_line_to (cr, final_x, final_y);
958 }
959
960 static void
gtk_popover_fill_border_path(GtkPopover * popover,cairo_t * cr)961 gtk_popover_fill_border_path (GtkPopover *popover,
962 cairo_t *cr)
963 {
964 GtkWidget *widget = GTK_WIDGET (popover);
965 GtkAllocation allocation;
966 GtkStyleContext *context;
967 int x, y, w, h;
968 GtkRoundedBox box;
969
970 context = gtk_widget_get_style_context (widget);
971 gtk_widget_get_allocation (widget, &allocation);
972
973 cairo_set_source_rgba (cr, 0, 0, 0, 1);
974
975 gtk_popover_apply_tail_path (popover, cr);
976 cairo_close_path (cr);
977 cairo_fill (cr);
978
979 gtk_popover_get_rect_coords (popover, &x, &y, &w, &h);
980
981 _gtk_rounded_box_init_rect (&box, x, y, w, h);
982 _gtk_rounded_box_apply_border_radius_for_style (&box,
983 gtk_style_context_lookup_style (context),
984 0);
985 _gtk_rounded_box_path (&box, cr);
986 cairo_fill (cr);
987 }
988
989 static void
gtk_popover_update_shape(GtkPopover * popover)990 gtk_popover_update_shape (GtkPopover *popover)
991 {
992 GtkWidget *widget = GTK_WIDGET (popover);
993 cairo_surface_t *surface;
994 cairo_region_t *region;
995 GdkWindow *win;
996 cairo_t *cr;
997
998 #ifdef GDK_WINDOWING_WAYLAND
999 if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (widget)))
1000 return;
1001 #endif
1002
1003 win = gtk_widget_get_window (widget);
1004 surface =
1005 gdk_window_create_similar_surface (win,
1006 CAIRO_CONTENT_COLOR_ALPHA,
1007 gdk_window_get_width (win),
1008 gdk_window_get_height (win));
1009
1010 cr = cairo_create (surface);
1011 gtk_popover_fill_border_path (popover, cr);
1012 cairo_destroy (cr);
1013
1014 region = gdk_cairo_region_create_from_surface (surface);
1015 cairo_surface_destroy (surface);
1016
1017 gtk_widget_shape_combine_region (widget, region);
1018 cairo_region_destroy (region);
1019
1020 gdk_window_set_child_shapes (gtk_widget_get_parent_window (widget));
1021 }
1022
1023 static void
_gtk_popover_update_child_visible(GtkPopover * popover)1024 _gtk_popover_update_child_visible (GtkPopover *popover)
1025 {
1026 GtkPopoverPrivate *priv = popover->priv;
1027 GtkWidget *widget = GTK_WIDGET (popover);
1028 GdkRectangle rect;
1029 GtkAllocation allocation;
1030 GtkWidget *parent;
1031
1032 if (!priv->parent_scrollable)
1033 {
1034 gtk_widget_set_child_visible (widget, TRUE);
1035 return;
1036 }
1037
1038 parent = gtk_widget_get_parent (GTK_WIDGET (priv->parent_scrollable));
1039 gtk_popover_get_pointing_to (popover, &rect);
1040
1041 gtk_widget_translate_coordinates (priv->widget, parent,
1042 rect.x, rect.y, &rect.x, &rect.y);
1043
1044 gtk_widget_get_allocation (GTK_WIDGET (parent), &allocation);
1045
1046 if (rect.x + rect.width < 0 || rect.x > allocation.width ||
1047 rect.y + rect.height < 0 || rect.y > allocation.height)
1048 gtk_widget_set_child_visible (widget, FALSE);
1049 else
1050 gtk_widget_set_child_visible (widget, TRUE);
1051 }
1052
1053 static GtkPositionType
opposite_position(GtkPositionType pos)1054 opposite_position (GtkPositionType pos)
1055 {
1056 switch (pos)
1057 {
1058 default:
1059 case GTK_POS_LEFT: return GTK_POS_RIGHT;
1060 case GTK_POS_RIGHT: return GTK_POS_LEFT;
1061 case GTK_POS_TOP: return GTK_POS_BOTTOM;
1062 case GTK_POS_BOTTOM: return GTK_POS_TOP;
1063 }
1064 }
1065
1066 void
gtk_popover_update_position(GtkPopover * popover)1067 gtk_popover_update_position (GtkPopover *popover)
1068 {
1069 GtkPopoverPrivate *priv = popover->priv;
1070 GtkWidget *widget = GTK_WIDGET (popover);
1071 GtkAllocation window_alloc;
1072 GtkBorder window_shadow;
1073 GdkRectangle rect;
1074 GtkRequisition req;
1075 GtkPositionType pos;
1076 gint overshoot[4];
1077 gint i, j;
1078 gint best;
1079
1080 if (!priv->window)
1081 return;
1082
1083 gtk_widget_get_preferred_size (widget, NULL, &req);
1084 gtk_widget_get_allocation (GTK_WIDGET (priv->window), &window_alloc);
1085 _gtk_window_get_shadow_width (priv->window, &window_shadow);
1086 priv->final_position = priv->preferred_position;
1087
1088 gtk_popover_get_pointing_to (popover, &rect);
1089 gtk_widget_translate_coordinates (priv->widget, GTK_WIDGET (priv->window),
1090 rect.x, rect.y, &rect.x, &rect.y);
1091
1092 pos = get_effective_position (popover, priv->preferred_position);
1093
1094 overshoot[GTK_POS_TOP] = req.height - rect.y + window_shadow.top;
1095 overshoot[GTK_POS_BOTTOM] = rect.y + rect.height + req.height - window_alloc.height
1096 + window_shadow.bottom;
1097 overshoot[GTK_POS_LEFT] = req.width - rect.x + window_shadow.left;
1098 overshoot[GTK_POS_RIGHT] = rect.x + rect.width + req.width - window_alloc.width
1099 + window_shadow.right;
1100
1101 #ifdef GDK_WINDOWING_WAYLAND
1102 if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (widget)) &&
1103 priv->constraint == GTK_POPOVER_CONSTRAINT_NONE)
1104 {
1105 priv->final_position = priv->preferred_position;
1106 }
1107 else
1108 #endif
1109 if (overshoot[pos] <= 0)
1110 {
1111 priv->final_position = priv->preferred_position;
1112 }
1113 else if (overshoot[opposite_position (pos)] <= 0)
1114 {
1115 priv->final_position = opposite_position (priv->preferred_position);
1116 }
1117 else
1118 {
1119 best = G_MAXINT;
1120 pos = 0;
1121 for (i = 0; i < 4; i++)
1122 {
1123 j = get_effective_position (popover, i);
1124 if (overshoot[j] < best)
1125 {
1126 pos = i;
1127 best = overshoot[j];
1128 }
1129 }
1130 priv->final_position = pos;
1131 }
1132
1133 switch (priv->final_position)
1134 {
1135 case GTK_POS_TOP:
1136 rect.y += priv->transition_diff;
1137 break;
1138 case GTK_POS_BOTTOM:
1139 rect.y -= priv->transition_diff;
1140 break;
1141 case GTK_POS_LEFT:
1142 rect.x += priv->transition_diff;
1143 break;
1144 case GTK_POS_RIGHT:
1145 rect.x -= priv->transition_diff;
1146 break;
1147 }
1148
1149 _gtk_window_set_popover_position (priv->window, widget,
1150 priv->final_position, &rect);
1151
1152 if (priv->final_position != priv->current_position)
1153 {
1154 if (gtk_widget_is_drawable (widget))
1155 gtk_popover_update_shape (popover);
1156
1157 priv->current_position = priv->final_position;
1158 gtk_popover_invalidate_borders (popover);
1159 }
1160
1161 _gtk_popover_update_child_visible (popover);
1162 }
1163
1164 static gboolean
gtk_popover_draw(GtkWidget * widget,cairo_t * cr)1165 gtk_popover_draw (GtkWidget *widget,
1166 cairo_t *cr)
1167 {
1168 GtkPopover *popover = GTK_POPOVER (widget);
1169 GtkStyleContext *context;
1170 GtkAllocation allocation;
1171 GtkWidget *child;
1172 GtkBorder border;
1173 GdkRGBA border_color;
1174 int rect_x, rect_y, rect_w, rect_h;
1175 gint initial_x, initial_y, final_x, final_y;
1176 gint gap_start, gap_end;
1177 GtkPositionType gap_side;
1178 GtkStateFlags state;
1179
1180 context = gtk_widget_get_style_context (widget);
1181
1182 state = gtk_style_context_get_state (context);
1183 gtk_widget_get_allocation (widget, &allocation);
1184
1185 gtk_style_context_get_border (context, state, &border);
1186 gtk_popover_get_rect_coords (popover,
1187 &rect_x, &rect_y,
1188 &rect_w, &rect_h);
1189
1190 /* Render the rect background */
1191 gtk_render_background (context, cr,
1192 rect_x, rect_y,
1193 rect_w, rect_h);
1194
1195 if (popover->priv->widget)
1196 {
1197 gtk_popover_get_gap_coords (popover,
1198 &initial_x, &initial_y,
1199 NULL, NULL,
1200 &final_x, &final_y,
1201 &gap_side);
1202
1203 if (POS_IS_VERTICAL (gap_side))
1204 {
1205 gap_start = initial_x - rect_x;
1206 gap_end = final_x - rect_x;
1207 }
1208 else
1209 {
1210 gap_start = initial_y - rect_y;
1211 gap_end = final_y - rect_y;
1212 }
1213
1214 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1215 /* Now render the frame, without the gap for the arrow tip */
1216 gtk_render_frame_gap (context, cr,
1217 rect_x, rect_y,
1218 rect_w, rect_h,
1219 gap_side,
1220 gap_start, gap_end);
1221 G_GNUC_END_IGNORE_DEPRECATIONS
1222 }
1223 else
1224 {
1225 gtk_render_frame (context, cr,
1226 rect_x, rect_y,
1227 rect_w, rect_h);
1228 }
1229
1230 /* Clip to the arrow shape */
1231 cairo_save (cr);
1232
1233 gtk_popover_apply_tail_path (popover, cr);
1234 cairo_clip (cr);
1235
1236 /* Render the arrow background */
1237 gtk_render_background (context, cr,
1238 0, 0,
1239 allocation.width, allocation.height);
1240
1241 /* Render the border of the arrow tip */
1242 if (border.bottom > 0)
1243 {
1244 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1245 gtk_style_context_get_border_color (context, state, &border_color);
1246 G_GNUC_END_IGNORE_DEPRECATIONS
1247
1248 gtk_popover_apply_tail_path (popover, cr);
1249 gdk_cairo_set_source_rgba (cr, &border_color);
1250
1251 cairo_set_line_width (cr, border.bottom + 1);
1252 cairo_stroke (cr);
1253 }
1254
1255 /* We're done */
1256 cairo_restore (cr);
1257
1258 child = gtk_bin_get_child (GTK_BIN (widget));
1259
1260 if (child)
1261 gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr);
1262
1263 return GDK_EVENT_PROPAGATE;
1264 }
1265
1266 static void
get_padding_and_border(GtkWidget * widget,GtkBorder * border)1267 get_padding_and_border (GtkWidget *widget,
1268 GtkBorder *border)
1269 {
1270 GtkStyleContext *context;
1271 GtkStateFlags state;
1272 gint border_width;
1273 GtkBorder tmp;
1274
1275 context = gtk_widget_get_style_context (widget);
1276 state = gtk_style_context_get_state (context);
1277
1278 border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
1279
1280 gtk_style_context_get_padding (context, state, border);
1281 gtk_style_context_get_border (context, state, &tmp);
1282 border->top += tmp.top + border_width;
1283 border->right += tmp.right + border_width;
1284 border->bottom += tmp.bottom + border_width;
1285 border->left += tmp.left + border_width;
1286 }
1287
1288 static gint
get_border_radius(GtkWidget * widget)1289 get_border_radius (GtkWidget *widget)
1290 {
1291 GtkStyleContext *context;
1292 GtkStateFlags state;
1293 gint border_radius;
1294
1295 context = gtk_widget_get_style_context (widget);
1296 state = gtk_style_context_get_state (context);
1297 gtk_style_context_get (context, state,
1298 GTK_STYLE_PROPERTY_BORDER_RADIUS, &border_radius,
1299 NULL);
1300 return border_radius;
1301 }
1302
1303 static gint
get_minimal_size(GtkPopover * popover,GtkOrientation orientation)1304 get_minimal_size (GtkPopover *popover,
1305 GtkOrientation orientation)
1306 {
1307 GtkPopoverPrivate *priv = popover->priv;
1308 GtkPositionType pos;
1309 gint minimal_size;
1310
1311 minimal_size = 2 * get_border_radius (GTK_WIDGET (popover));
1312 pos = get_effective_position (popover, priv->preferred_position);
1313
1314 if ((orientation == GTK_ORIENTATION_HORIZONTAL && POS_IS_VERTICAL (pos)) ||
1315 (orientation == GTK_ORIENTATION_VERTICAL && !POS_IS_VERTICAL (pos)))
1316 minimal_size += TAIL_GAP_WIDTH;
1317
1318 return minimal_size;
1319 }
1320
1321 static void
gtk_popover_get_preferred_width(GtkWidget * widget,gint * minimum_width,gint * natural_width)1322 gtk_popover_get_preferred_width (GtkWidget *widget,
1323 gint *minimum_width,
1324 gint *natural_width)
1325 {
1326 GtkPopover *popover = GTK_POPOVER (widget);
1327 GtkWidget *child;
1328 gint min, nat, extra, minimal_size;
1329 GtkBorder border, margin;
1330
1331 child = gtk_bin_get_child (GTK_BIN (widget));
1332 min = nat = 0;
1333
1334 if (child)
1335 gtk_widget_get_preferred_width (child, &min, &nat);
1336
1337 get_padding_and_border (widget, &border);
1338 get_margin (widget, &margin);
1339 minimal_size = get_minimal_size (popover, GTK_ORIENTATION_HORIZONTAL);
1340
1341 min = MAX (min, minimal_size) + border.left + border.right;
1342 nat = MAX (nat, minimal_size) + border.left + border.right;
1343 extra = MAX (TAIL_HEIGHT, margin.left) + MAX (TAIL_HEIGHT, margin.right);
1344
1345 min += extra;
1346 nat += extra;
1347
1348 *minimum_width = min;
1349 *natural_width = nat;
1350 }
1351
1352 static void
gtk_popover_get_preferred_width_for_height(GtkWidget * widget,gint height,gint * minimum_width,gint * natural_width)1353 gtk_popover_get_preferred_width_for_height (GtkWidget *widget,
1354 gint height,
1355 gint *minimum_width,
1356 gint *natural_width)
1357 {
1358 GtkPopover *popover = GTK_POPOVER (widget);
1359 GtkWidget *child;
1360 GdkRectangle child_rect;
1361 gint min, nat, extra, minimal_size;
1362 gint child_height;
1363 GtkBorder border, margin;
1364
1365 child = gtk_bin_get_child (GTK_BIN (widget));
1366 min = nat = 0;
1367
1368 gtk_popover_get_rect_for_size (popover, 0, height, &child_rect);
1369 child_height = child_rect.height;
1370
1371
1372 get_padding_and_border (widget, &border);
1373 get_margin (widget, &margin);
1374 child_height -= border.top + border.bottom;
1375 minimal_size = get_minimal_size (popover, GTK_ORIENTATION_HORIZONTAL);
1376
1377 if (child)
1378 gtk_widget_get_preferred_width_for_height (child, child_height, &min, &nat);
1379
1380 min = MAX (min, minimal_size) + border.left + border.right;
1381 nat = MAX (nat, minimal_size) + border.left + border.right;
1382 extra = MAX (TAIL_HEIGHT, margin.left) + MAX (TAIL_HEIGHT, margin.right);
1383
1384 min += extra;
1385 nat += extra;
1386
1387 *minimum_width = min;
1388 *natural_width = nat;
1389 }
1390
1391 static void
gtk_popover_get_preferred_height(GtkWidget * widget,gint * minimum_height,gint * natural_height)1392 gtk_popover_get_preferred_height (GtkWidget *widget,
1393 gint *minimum_height,
1394 gint *natural_height)
1395 {
1396 GtkPopover *popover = GTK_POPOVER (widget);
1397 GtkWidget *child;
1398 gint min, nat, extra, minimal_size;
1399 GtkBorder border, margin;
1400
1401 child = gtk_bin_get_child (GTK_BIN (widget));
1402 min = nat = 0;
1403
1404 if (child)
1405 gtk_widget_get_preferred_height (child, &min, &nat);
1406
1407 get_padding_and_border (widget, &border);
1408 get_margin (widget, &margin);
1409 minimal_size = get_minimal_size (popover, GTK_ORIENTATION_VERTICAL);
1410
1411 min = MAX (min, minimal_size) + border.top + border.bottom;
1412 nat = MAX (nat, minimal_size) + border.top + border.bottom;
1413 extra = MAX (TAIL_HEIGHT, margin.top) + MAX (TAIL_HEIGHT, margin.bottom);
1414
1415 min += extra;
1416 nat += extra;
1417
1418 *minimum_height = min;
1419 *natural_height = nat;
1420 }
1421
1422 static void
gtk_popover_get_preferred_height_for_width(GtkWidget * widget,gint width,gint * minimum_height,gint * natural_height)1423 gtk_popover_get_preferred_height_for_width (GtkWidget *widget,
1424 gint width,
1425 gint *minimum_height,
1426 gint *natural_height)
1427 {
1428 GtkPopover *popover = GTK_POPOVER (widget);
1429 GtkWidget *child;
1430 GdkRectangle child_rect;
1431 gint min, nat, extra, minimal_size;
1432 gint child_width;
1433 GtkBorder border, margin;
1434
1435 child = gtk_bin_get_child (GTK_BIN (widget));
1436 min = nat = 0;
1437
1438 get_padding_and_border (widget, &border);
1439 get_margin (widget, &margin);
1440
1441 gtk_popover_get_rect_for_size (popover, width, 0, &child_rect);
1442 child_width = child_rect.width;
1443
1444 child_width -= border.left + border.right;
1445 minimal_size = get_minimal_size (popover, GTK_ORIENTATION_VERTICAL);
1446 if (child)
1447 gtk_widget_get_preferred_height_for_width (child, child_width, &min, &nat);
1448
1449 min = MAX (min, minimal_size) + border.top + border.bottom;
1450 nat = MAX (nat, minimal_size) + border.top + border.bottom;
1451 extra = MAX (TAIL_HEIGHT, margin.top) + MAX (TAIL_HEIGHT, margin.bottom);
1452
1453 min += extra;
1454 nat += extra;
1455
1456 if (minimum_height)
1457 *minimum_height = min;
1458
1459 if (natural_height)
1460 *natural_height = nat;
1461 }
1462
1463 static void
gtk_popover_invalidate_borders(GtkPopover * popover)1464 gtk_popover_invalidate_borders (GtkPopover *popover)
1465 {
1466 GtkAllocation allocation;
1467 GtkBorder border;
1468
1469 gtk_widget_get_allocation (GTK_WIDGET (popover), &allocation);
1470 get_padding_and_border (GTK_WIDGET (popover), &border);
1471
1472 gtk_widget_queue_draw_area (GTK_WIDGET (popover), 0, 0, border.left + TAIL_HEIGHT, allocation.height);
1473 gtk_widget_queue_draw_area (GTK_WIDGET (popover), 0, 0, allocation.width, border.top + TAIL_HEIGHT);
1474 gtk_widget_queue_draw_area (GTK_WIDGET (popover), 0, allocation.height - border.bottom - TAIL_HEIGHT,
1475 allocation.width, border.bottom + TAIL_HEIGHT);
1476 gtk_widget_queue_draw_area (GTK_WIDGET (popover), allocation.width - border.right - TAIL_HEIGHT,
1477 0, border.right + TAIL_HEIGHT, allocation.height);
1478 }
1479
1480 static void
gtk_popover_check_invalidate_borders(GtkPopover * popover)1481 gtk_popover_check_invalidate_borders (GtkPopover *popover)
1482 {
1483 GtkPopoverPrivate *priv = popover->priv;
1484 GtkPositionType gap_side;
1485 gint tip_x, tip_y;
1486
1487 if (!priv->widget)
1488 return;
1489
1490 gtk_popover_get_gap_coords (popover, NULL, NULL,
1491 &tip_x, &tip_y, NULL, NULL,
1492 &gap_side);
1493
1494 if (tip_x != priv->tip_x || tip_y != priv->tip_y)
1495 {
1496 priv->tip_x = tip_x;
1497 priv->tip_y = tip_y;
1498 gtk_popover_invalidate_borders (popover);
1499 }
1500 }
1501
1502 static void
gtk_popover_size_allocate(GtkWidget * widget,GtkAllocation * allocation)1503 gtk_popover_size_allocate (GtkWidget *widget,
1504 GtkAllocation *allocation)
1505 {
1506 GtkPopover *popover = GTK_POPOVER (widget);
1507 GtkWidget *child;
1508
1509 gtk_widget_set_allocation (widget, allocation);
1510 child = gtk_bin_get_child (GTK_BIN (widget));
1511 if (child)
1512 {
1513 GtkAllocation child_alloc;
1514 int x, y, w, h;
1515 GtkBorder border;
1516
1517 gtk_popover_get_rect_coords (popover, &x, &y, &w, &h);
1518 get_padding_and_border (widget, &border);
1519
1520 child_alloc.x = x + border.left;
1521 child_alloc.y = y + border.top;
1522 child_alloc.width = w - border.left - border.right;
1523 child_alloc.height = h - border.top - border.bottom;
1524 gtk_widget_size_allocate (child, &child_alloc);
1525 }
1526
1527 if (gtk_widget_get_realized (widget))
1528 {
1529 gdk_window_move_resize (gtk_widget_get_window (widget),
1530 0, 0, allocation->width, allocation->height);
1531 gtk_popover_update_shape (popover);
1532 }
1533
1534 if (gtk_widget_is_drawable (widget))
1535 gtk_popover_check_invalidate_borders (popover);
1536 }
1537
1538 static gboolean
gtk_popover_button_press(GtkWidget * widget,GdkEventButton * event)1539 gtk_popover_button_press (GtkWidget *widget,
1540 GdkEventButton *event)
1541 {
1542 GtkPopover *popover = GTK_POPOVER (widget);
1543
1544 if (event->type != GDK_BUTTON_PRESS)
1545 return GDK_EVENT_PROPAGATE;
1546
1547 popover->priv->button_pressed = TRUE;
1548
1549 return GDK_EVENT_PROPAGATE;
1550 }
1551
1552 static gboolean
gtk_popover_button_release(GtkWidget * widget,GdkEventButton * event)1553 gtk_popover_button_release (GtkWidget *widget,
1554 GdkEventButton *event)
1555 {
1556 GtkPopover *popover = GTK_POPOVER (widget);
1557 GtkWidget *child, *event_widget;
1558
1559 child = gtk_bin_get_child (GTK_BIN (widget));
1560
1561 if (!popover->priv->button_pressed)
1562 return GDK_EVENT_PROPAGATE;
1563
1564 event_widget = gtk_get_event_widget ((GdkEvent *) event);
1565
1566 if (child && event->window == gtk_widget_get_window (widget))
1567 {
1568 GtkAllocation child_alloc;
1569
1570 gtk_widget_get_allocation (child, &child_alloc);
1571
1572 if (event->x < child_alloc.x ||
1573 event->x > child_alloc.x + child_alloc.width ||
1574 event->y < child_alloc.y ||
1575 event->y > child_alloc.y + child_alloc.height)
1576 gtk_popover_popdown (popover);
1577 }
1578 else if (!event_widget || !gtk_widget_is_ancestor (event_widget, widget))
1579 {
1580 gtk_popover_popdown (popover);
1581 }
1582
1583 return GDK_EVENT_PROPAGATE;
1584 }
1585
1586 static gboolean
gtk_popover_key_press(GtkWidget * widget,GdkEventKey * event)1587 gtk_popover_key_press (GtkWidget *widget,
1588 GdkEventKey *event)
1589 {
1590 GtkWidget *toplevel, *focus;
1591
1592 if (event->keyval == GDK_KEY_Escape)
1593 {
1594 gtk_popover_popdown (GTK_POPOVER (widget));
1595 return GDK_EVENT_STOP;
1596 }
1597
1598 if (!GTK_POPOVER (widget)->priv->modal)
1599 return GDK_EVENT_PROPAGATE;
1600
1601 toplevel = gtk_widget_get_toplevel (widget);
1602
1603 if (GTK_IS_WINDOW (toplevel))
1604 {
1605 focus = gtk_window_get_focus (GTK_WINDOW (toplevel));
1606
1607 if (focus && gtk_widget_is_ancestor (focus, widget))
1608 return gtk_widget_event (focus, (GdkEvent*) event);
1609 }
1610
1611 return GDK_EVENT_PROPAGATE;
1612 }
1613
1614 static void
gtk_popover_grab_focus(GtkWidget * widget)1615 gtk_popover_grab_focus (GtkWidget *widget)
1616 {
1617 GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
1618 GtkWidget *child;
1619
1620 if (!priv->visible)
1621 return;
1622
1623 /* Focus the first natural child */
1624 child = gtk_bin_get_child (GTK_BIN (widget));
1625
1626 if (child)
1627 gtk_widget_child_focus (child, GTK_DIR_TAB_FORWARD);
1628 }
1629
1630 static gboolean
gtk_popover_focus(GtkWidget * widget,GtkDirectionType direction)1631 gtk_popover_focus (GtkWidget *widget,
1632 GtkDirectionType direction)
1633 {
1634 GtkPopover *popover = GTK_POPOVER (widget);
1635 GtkPopoverPrivate *priv = popover->priv;
1636
1637 if (!priv->visible)
1638 return FALSE;
1639
1640 if (!GTK_WIDGET_CLASS (gtk_popover_parent_class)->focus (widget, direction))
1641 {
1642 GtkWidget *focus;
1643
1644 focus = gtk_window_get_focus (popover->priv->window);
1645 focus = gtk_widget_get_parent (focus);
1646
1647 /* Unset focus child through children, so it is next stepped from
1648 * scratch.
1649 */
1650 while (focus && focus != widget)
1651 {
1652 gtk_container_set_focus_child (GTK_CONTAINER (focus), NULL);
1653 focus = gtk_widget_get_parent (focus);
1654 }
1655
1656 return gtk_widget_child_focus (gtk_bin_get_child (GTK_BIN (widget)),
1657 direction);
1658 }
1659
1660 return TRUE;
1661 }
1662
1663 static void
gtk_popover_show(GtkWidget * widget)1664 gtk_popover_show (GtkWidget *widget)
1665 {
1666 GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
1667
1668 if (priv->window)
1669 _gtk_window_raise_popover (priv->window, widget);
1670
1671 priv->visible = TRUE;
1672
1673 GTK_WIDGET_CLASS (gtk_popover_parent_class)->show (widget);
1674
1675 if (priv->modal)
1676 gtk_popover_apply_modality (GTK_POPOVER (widget), TRUE);
1677
1678 priv->state = STATE_SHOWN;
1679
1680 if (gtk_widget_get_realized (widget))
1681 gdk_window_input_shape_combine_region (gtk_widget_get_parent_window (widget),
1682 NULL, 0, 0);
1683 }
1684
1685 static void
gtk_popover_hide(GtkWidget * widget)1686 gtk_popover_hide (GtkWidget *widget)
1687 {
1688 GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
1689
1690 gtk_popover_hide_internal (GTK_POPOVER (widget));
1691
1692 gtk_popover_stop_transition (GTK_POPOVER (widget));
1693 priv->state = STATE_HIDDEN;
1694 priv->transition_diff = 0;
1695 gtk_progress_tracker_finish (&priv->tracker);
1696 gtk_widget_set_opacity (widget, 1.0);
1697
1698
1699 GTK_WIDGET_CLASS (gtk_popover_parent_class)->hide (widget);
1700 }
1701
1702 static void
gtk_popover_class_init(GtkPopoverClass * klass)1703 gtk_popover_class_init (GtkPopoverClass *klass)
1704 {
1705 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1706 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1707
1708 object_class->set_property = gtk_popover_set_property;
1709 object_class->get_property = gtk_popover_get_property;
1710 object_class->finalize = gtk_popover_finalize;
1711 object_class->dispose = gtk_popover_dispose;
1712
1713 widget_class->realize = gtk_popover_realize;
1714 widget_class->map = gtk_popover_map;
1715 widget_class->unmap = gtk_popover_unmap;
1716 widget_class->get_preferred_width = gtk_popover_get_preferred_width;
1717 widget_class->get_preferred_height = gtk_popover_get_preferred_height;
1718 widget_class->get_preferred_width_for_height = gtk_popover_get_preferred_width_for_height;
1719 widget_class->get_preferred_height_for_width = gtk_popover_get_preferred_height_for_width;
1720 widget_class->size_allocate = gtk_popover_size_allocate;
1721 widget_class->draw = gtk_popover_draw;
1722 widget_class->button_press_event = gtk_popover_button_press;
1723 widget_class->button_release_event = gtk_popover_button_release;
1724 widget_class->key_press_event = gtk_popover_key_press;
1725 widget_class->grab_focus = gtk_popover_grab_focus;
1726 widget_class->focus = gtk_popover_focus;
1727 widget_class->show = gtk_popover_show;
1728 widget_class->hide = gtk_popover_hide;
1729
1730 /**
1731 * GtkPopover:relative-to:
1732 *
1733 * Sets the attached widget.
1734 *
1735 * Since: 3.12
1736 */
1737 properties[PROP_RELATIVE_TO] =
1738 g_param_spec_object ("relative-to",
1739 P_("Relative to"),
1740 P_("Widget the bubble window points to"),
1741 GTK_TYPE_WIDGET,
1742 GTK_PARAM_READWRITE);
1743
1744 /**
1745 * GtkPopover:pointing-to:
1746 *
1747 * Marks a specific rectangle to be pointed.
1748 *
1749 * Since: 3.12
1750 */
1751 properties[PROP_POINTING_TO] =
1752 g_param_spec_boxed ("pointing-to",
1753 P_("Pointing to"),
1754 P_("Rectangle the bubble window points to"),
1755 GDK_TYPE_RECTANGLE,
1756 GTK_PARAM_READWRITE);
1757
1758 /**
1759 * GtkPopover:position
1760 *
1761 * Sets the preferred position of the popover.
1762 *
1763 * Since: 3.12
1764 */
1765 properties[PROP_POSITION] =
1766 g_param_spec_enum ("position",
1767 P_("Position"),
1768 P_("Position to place the bubble window"),
1769 GTK_TYPE_POSITION_TYPE, GTK_POS_TOP,
1770 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1771
1772 /**
1773 * GtkPopover:modal
1774 *
1775 * Sets whether the popover is modal (so other elements in the window do not
1776 * receive input while the popover is visible).
1777 *
1778 * Since: 3.12
1779 */
1780 properties[PROP_MODAL] =
1781 g_param_spec_boolean ("modal",
1782 P_("Modal"),
1783 P_("Whether the popover is modal"),
1784 TRUE,
1785 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1786
1787 /**
1788 * GtkPopover:transitions-enabled
1789 *
1790 * Whether show/hide transitions are enabled for this popover.
1791 *
1792 * Since: 3.16
1793 *
1794 * Deprecated: 3.22: You can show or hide the popover without transitions
1795 * using gtk_widget_show() and gtk_widget_hide() while gtk_popover_popup()
1796 * and gtk_popover_popdown() will use transitions.
1797 */
1798 properties[PROP_TRANSITIONS_ENABLED] =
1799 g_param_spec_boolean ("transitions-enabled",
1800 P_("Transitions enabled"),
1801 P_("Whether show/hide transitions are enabled or not"),
1802 TRUE,
1803 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_DEPRECATED);
1804
1805 /**
1806 * GtkPopover:constrain-to:
1807 *
1808 * Sets a constraint for the popover position.
1809 *
1810 * Since: 3.20
1811 */
1812 properties[PROP_CONSTRAIN_TO] =
1813 g_param_spec_enum ("constrain-to",
1814 P_("Constraint"),
1815 P_("Constraint for the popover position"),
1816 GTK_TYPE_POPOVER_CONSTRAINT, GTK_POPOVER_CONSTRAINT_WINDOW,
1817 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1818
1819 g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
1820
1821 /**
1822 * GtkPopover::closed:
1823 *
1824 * This signal is emitted when the popover is dismissed either through
1825 * API or user interaction.
1826 *
1827 * Since: 3.12
1828 */
1829 signals[CLOSED] =
1830 g_signal_new (I_("closed"),
1831 G_TYPE_FROM_CLASS (object_class),
1832 G_SIGNAL_RUN_LAST,
1833 G_STRUCT_OFFSET (GtkPopoverClass, closed),
1834 NULL, NULL, NULL,
1835 G_TYPE_NONE, 0);
1836
1837 quark_widget_popovers = g_quark_from_static_string ("gtk-quark-widget-popovers");
1838 gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_POPOVER_ACCESSIBLE);
1839 gtk_widget_class_set_css_name (widget_class, "popover");
1840 }
1841
1842 static void
gtk_popover_update_scrollable(GtkPopover * popover)1843 gtk_popover_update_scrollable (GtkPopover *popover)
1844 {
1845 GtkPopoverPrivate *priv = popover->priv;
1846 GtkScrollable *scrollable;
1847
1848 scrollable = GTK_SCROLLABLE (gtk_widget_get_ancestor (priv->widget,
1849 GTK_TYPE_SCROLLABLE));
1850 gtk_popover_set_scrollable_full (popover, scrollable);
1851 }
1852
1853 static void
_gtk_popover_parent_hierarchy_changed(GtkWidget * widget,GtkWidget * previous_toplevel,GtkPopover * popover)1854 _gtk_popover_parent_hierarchy_changed (GtkWidget *widget,
1855 GtkWidget *previous_toplevel,
1856 GtkPopover *popover)
1857 {
1858 GtkPopoverPrivate *priv = popover->priv;
1859 GtkWindow *new_window;
1860
1861 new_window = GTK_WINDOW (gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW));
1862
1863 if (priv->window == new_window)
1864 return;
1865
1866 g_object_ref (popover);
1867
1868 if (gtk_widget_has_grab (GTK_WIDGET (popover)))
1869 gtk_popover_apply_modality (popover, FALSE);
1870
1871 if (priv->window)
1872 _gtk_window_remove_popover (priv->window, GTK_WIDGET (popover));
1873
1874 if (priv->parent_scrollable)
1875 gtk_popover_set_scrollable_full (popover, NULL);
1876
1877 priv->window = new_window;
1878
1879 if (new_window)
1880 {
1881 _gtk_window_add_popover (new_window, GTK_WIDGET (popover), priv->widget, TRUE);
1882 gtk_popover_update_scrollable (popover);
1883 gtk_popover_update_position (popover);
1884 }
1885
1886 if (gtk_widget_is_visible (GTK_WIDGET (popover)))
1887 gtk_widget_queue_resize (GTK_WIDGET (popover));
1888
1889 g_object_unref (popover);
1890 }
1891
1892 static void
_popover_propagate_state(GtkPopover * popover,GtkStateFlags state,GtkStateFlags old_state,GtkStateFlags flag)1893 _popover_propagate_state (GtkPopover *popover,
1894 GtkStateFlags state,
1895 GtkStateFlags old_state,
1896 GtkStateFlags flag)
1897 {
1898 if ((state & flag) != (old_state & flag))
1899 {
1900 if ((state & flag) == flag)
1901 gtk_widget_set_state_flags (GTK_WIDGET (popover), flag, FALSE);
1902 else
1903 gtk_widget_unset_state_flags (GTK_WIDGET (popover), flag);
1904 }
1905 }
1906
1907 static void
_gtk_popover_parent_state_changed(GtkWidget * widget,GtkStateFlags old_state,GtkPopover * popover)1908 _gtk_popover_parent_state_changed (GtkWidget *widget,
1909 GtkStateFlags old_state,
1910 GtkPopover *popover)
1911 {
1912 guint state;
1913
1914 state = gtk_widget_get_state_flags (widget);
1915 _popover_propagate_state (popover, state, old_state,
1916 GTK_STATE_FLAG_INSENSITIVE);
1917 _popover_propagate_state (popover, state, old_state,
1918 GTK_STATE_FLAG_BACKDROP);
1919 }
1920
1921 static void
_gtk_popover_parent_grab_notify(GtkWidget * widget,gboolean was_shadowed,GtkPopover * popover)1922 _gtk_popover_parent_grab_notify (GtkWidget *widget,
1923 gboolean was_shadowed,
1924 GtkPopover *popover)
1925 {
1926 GtkPopoverPrivate *priv = popover->priv;
1927
1928 if (priv->modal &&
1929 gtk_widget_is_visible (GTK_WIDGET (popover)) &&
1930 !gtk_widget_has_grab (GTK_WIDGET (popover)))
1931 {
1932 GtkWidget *grab_widget;
1933
1934 grab_widget = gtk_grab_get_current ();
1935
1936 if (!grab_widget || !GTK_IS_POPOVER (grab_widget))
1937 gtk_popover_popdown (popover);
1938 }
1939 }
1940
1941 static void
_gtk_popover_parent_unmap(GtkWidget * widget,GtkPopover * popover)1942 _gtk_popover_parent_unmap (GtkWidget *widget,
1943 GtkPopover *popover)
1944 {
1945 GtkPopoverPrivate *priv = popover->priv;
1946
1947 if (priv->state == STATE_SHOWING)
1948 priv->visible = FALSE;
1949 else if (priv->state == STATE_SHOWN)
1950 gtk_popover_set_state (popover, STATE_HIDING);
1951 }
1952
1953 static void
_gtk_popover_parent_size_allocate(GtkWidget * widget,GtkAllocation * allocation,GtkPopover * popover)1954 _gtk_popover_parent_size_allocate (GtkWidget *widget,
1955 GtkAllocation *allocation,
1956 GtkPopover *popover)
1957 {
1958 gtk_popover_update_position (popover);
1959 }
1960
1961 static void
_unmanage_popover(GObject * object)1962 _unmanage_popover (GObject *object)
1963 {
1964 gtk_popover_update_relative_to (GTK_POPOVER (object), NULL);
1965 g_object_unref (object);
1966 }
1967
1968 static void
widget_manage_popover(GtkWidget * widget,GtkPopover * popover)1969 widget_manage_popover (GtkWidget *widget,
1970 GtkPopover *popover)
1971 {
1972 GHashTable *popovers;
1973
1974 popovers = g_object_get_qdata (G_OBJECT (widget), quark_widget_popovers);
1975
1976 if (G_UNLIKELY (!popovers))
1977 {
1978 popovers = g_hash_table_new_full (NULL, NULL,
1979 (GDestroyNotify) _unmanage_popover, NULL);
1980 g_object_set_qdata_full (G_OBJECT (widget),
1981 quark_widget_popovers, popovers,
1982 (GDestroyNotify) g_hash_table_unref);
1983 }
1984
1985 g_hash_table_add (popovers, g_object_ref_sink (popover));
1986 }
1987
1988 static void
widget_unmanage_popover(GtkWidget * widget,GtkPopover * popover)1989 widget_unmanage_popover (GtkWidget *widget,
1990 GtkPopover *popover)
1991 {
1992 GHashTable *popovers;
1993
1994 popovers = g_object_get_qdata (G_OBJECT (widget), quark_widget_popovers);
1995
1996 if (G_UNLIKELY (!popovers))
1997 return;
1998
1999 g_hash_table_remove (popovers, popover);
2000 }
2001
2002 static void
adjustment_changed_cb(GtkAdjustment * adjustment,GtkPopover * popover)2003 adjustment_changed_cb (GtkAdjustment *adjustment,
2004 GtkPopover *popover)
2005 {
2006 gtk_popover_update_position (popover);
2007 }
2008
2009 static void
_gtk_popover_set_scrollable(GtkPopover * popover,GtkScrollable * scrollable)2010 _gtk_popover_set_scrollable (GtkPopover *popover,
2011 GtkScrollable *scrollable)
2012 {
2013 GtkPopoverPrivate *priv = popover->priv;
2014
2015 if (priv->parent_scrollable)
2016 {
2017 if (priv->vadj)
2018 {
2019 g_signal_handlers_disconnect_by_data (priv->vadj, popover);
2020 g_object_unref (priv->vadj);
2021 priv->vadj = NULL;
2022 }
2023
2024 if (priv->hadj)
2025 {
2026 g_signal_handlers_disconnect_by_data (priv->hadj, popover);
2027 g_object_unref (priv->hadj);
2028 priv->hadj = NULL;
2029 }
2030
2031 g_object_unref (priv->parent_scrollable);
2032 }
2033
2034 priv->parent_scrollable = scrollable;
2035
2036 if (scrollable)
2037 {
2038 g_object_ref (scrollable);
2039 priv->vadj = gtk_scrollable_get_vadjustment (scrollable);
2040 priv->hadj = gtk_scrollable_get_hadjustment (scrollable);
2041
2042 if (priv->vadj)
2043 {
2044 g_object_ref (priv->vadj);
2045 g_signal_connect (priv->vadj, "changed",
2046 G_CALLBACK (adjustment_changed_cb), popover);
2047 g_signal_connect (priv->vadj, "value-changed",
2048 G_CALLBACK (adjustment_changed_cb), popover);
2049 }
2050
2051 if (priv->hadj)
2052 {
2053 g_object_ref (priv->hadj);
2054 g_signal_connect (priv->hadj, "changed",
2055 G_CALLBACK (adjustment_changed_cb), popover);
2056 g_signal_connect (priv->hadj, "value-changed",
2057 G_CALLBACK (adjustment_changed_cb), popover);
2058 }
2059 }
2060 }
2061
2062 static void
scrollable_notify_cb(GObject * object,GParamSpec * pspec,GtkPopover * popover)2063 scrollable_notify_cb (GObject *object,
2064 GParamSpec *pspec,
2065 GtkPopover *popover)
2066 {
2067 if (pspec->value_type == GTK_TYPE_ADJUSTMENT)
2068 _gtk_popover_set_scrollable (popover, GTK_SCROLLABLE (object));
2069 }
2070
2071 static void
gtk_popover_set_scrollable_full(GtkPopover * popover,GtkScrollable * scrollable)2072 gtk_popover_set_scrollable_full (GtkPopover *popover,
2073 GtkScrollable *scrollable)
2074 {
2075 GtkPopoverPrivate *priv = popover->priv;
2076
2077 if (priv->scrollable_notify_id != 0 &&
2078 g_signal_handler_is_connected (priv->parent_scrollable, priv->scrollable_notify_id))
2079 {
2080 g_signal_handler_disconnect (priv->parent_scrollable, priv->scrollable_notify_id);
2081 priv->scrollable_notify_id = 0;
2082 }
2083
2084 _gtk_popover_set_scrollable (popover, scrollable);
2085
2086 if (scrollable)
2087 {
2088 priv->scrollable_notify_id =
2089 g_signal_connect (priv->parent_scrollable, "notify",
2090 G_CALLBACK (scrollable_notify_cb), popover);
2091 }
2092 }
2093
2094 static void
gtk_popover_update_relative_to(GtkPopover * popover,GtkWidget * relative_to)2095 gtk_popover_update_relative_to (GtkPopover *popover,
2096 GtkWidget *relative_to)
2097 {
2098 GtkPopoverPrivate *priv = popover->priv;
2099 GtkStateFlags old_state = 0;
2100
2101 if (priv->widget == relative_to)
2102 return;
2103
2104 g_object_ref (popover);
2105
2106 if (priv->window)
2107 {
2108 _gtk_window_remove_popover (priv->window, GTK_WIDGET (popover));
2109 priv->window = NULL;
2110 }
2111
2112 popover_unset_prev_focus (popover);
2113
2114 if (priv->widget)
2115 {
2116 old_state = gtk_widget_get_state_flags (priv->widget);
2117 if (g_signal_handler_is_connected (priv->widget, priv->hierarchy_changed_id))
2118 g_signal_handler_disconnect (priv->widget, priv->hierarchy_changed_id);
2119 if (g_signal_handler_is_connected (priv->widget, priv->size_allocate_id))
2120 g_signal_handler_disconnect (priv->widget, priv->size_allocate_id);
2121 if (g_signal_handler_is_connected (priv->widget, priv->unmap_id))
2122 g_signal_handler_disconnect (priv->widget, priv->unmap_id);
2123 if (g_signal_handler_is_connected (priv->widget, priv->state_changed_id))
2124 g_signal_handler_disconnect (priv->widget, priv->state_changed_id);
2125 if (g_signal_handler_is_connected (priv->widget, priv->grab_notify_id))
2126 g_signal_handler_disconnect (priv->widget, priv->grab_notify_id);
2127
2128 widget_unmanage_popover (priv->widget, popover);
2129 }
2130
2131 if (priv->parent_scrollable)
2132 gtk_popover_set_scrollable_full (popover, NULL);
2133
2134 priv->widget = relative_to;
2135 g_object_notify_by_pspec (G_OBJECT (popover), properties[PROP_RELATIVE_TO]);
2136
2137 if (priv->widget)
2138 {
2139 priv->window =
2140 GTK_WINDOW (gtk_widget_get_ancestor (priv->widget, GTK_TYPE_WINDOW));
2141
2142 priv->hierarchy_changed_id =
2143 g_signal_connect (priv->widget, "hierarchy-changed",
2144 G_CALLBACK (_gtk_popover_parent_hierarchy_changed),
2145 popover);
2146 priv->size_allocate_id =
2147 g_signal_connect (priv->widget, "size-allocate",
2148 G_CALLBACK (_gtk_popover_parent_size_allocate),
2149 popover);
2150 priv->unmap_id =
2151 g_signal_connect (priv->widget, "unmap",
2152 G_CALLBACK (_gtk_popover_parent_unmap),
2153 popover);
2154 priv->state_changed_id =
2155 g_signal_connect (priv->widget, "state-flags-changed",
2156 G_CALLBACK (_gtk_popover_parent_state_changed),
2157 popover);
2158 priv->grab_notify_id =
2159 g_signal_connect (priv->widget, "grab-notify",
2160 G_CALLBACK (_gtk_popover_parent_grab_notify),
2161 popover);
2162
2163 /* Give ownership of the popover to widget */
2164 widget_manage_popover (priv->widget, popover);
2165 }
2166
2167 if (priv->window)
2168 _gtk_window_add_popover (priv->window, GTK_WIDGET (popover), priv->widget, TRUE);
2169
2170 if (priv->widget)
2171 gtk_popover_update_scrollable (popover);
2172
2173 if (priv->widget)
2174 _gtk_popover_parent_state_changed (priv->widget, old_state, popover);
2175
2176 _gtk_widget_update_parent_muxer (GTK_WIDGET (popover));
2177 g_object_unref (popover);
2178 }
2179
2180 static void
gtk_popover_update_pointing_to(GtkPopover * popover,const GdkRectangle * pointing_to)2181 gtk_popover_update_pointing_to (GtkPopover *popover,
2182 const GdkRectangle *pointing_to)
2183 {
2184 GtkPopoverPrivate *priv = popover->priv;
2185
2186 if (pointing_to)
2187 {
2188 priv->pointing_to = *pointing_to;
2189 priv->has_pointing_to = TRUE;
2190 }
2191 else
2192 priv->has_pointing_to = FALSE;
2193
2194 g_object_notify_by_pspec (G_OBJECT (popover), properties[PROP_POINTING_TO]);
2195 }
2196
2197 static void
gtk_popover_update_preferred_position(GtkPopover * popover,GtkPositionType position)2198 gtk_popover_update_preferred_position (GtkPopover *popover,
2199 GtkPositionType position)
2200 {
2201 if (popover->priv->preferred_position == position)
2202 return;
2203
2204 popover->priv->preferred_position = position;
2205 g_object_notify_by_pspec (G_OBJECT (popover), properties[PROP_POSITION]);
2206 }
2207
2208 static void
gtk_popover_multipress_gesture_pressed(GtkGestureMultiPress * gesture,gint n_press,gdouble widget_x,gdouble widget_y,GtkPopover * popover)2209 gtk_popover_multipress_gesture_pressed (GtkGestureMultiPress *gesture,
2210 gint n_press,
2211 gdouble widget_x,
2212 gdouble widget_y,
2213 GtkPopover *popover)
2214 {
2215 GtkPopoverPrivate *priv = popover->priv;
2216
2217 if (!gtk_window_is_active (priv->window) && gtk_widget_is_drawable (GTK_WIDGET (popover)))
2218 gtk_window_present_with_time (priv->window, gtk_get_current_event_time ());
2219 }
2220
2221 /**
2222 * gtk_popover_new:
2223 * @relative_to: (allow-none): #GtkWidget the popover is related to
2224 *
2225 * Creates a new popover to point to @relative_to
2226 *
2227 * Returns: a new #GtkPopover
2228 *
2229 * Since: 3.12
2230 **/
2231 GtkWidget *
gtk_popover_new(GtkWidget * relative_to)2232 gtk_popover_new (GtkWidget *relative_to)
2233 {
2234 g_return_val_if_fail (relative_to == NULL || GTK_IS_WIDGET (relative_to), NULL);
2235
2236 return g_object_new (GTK_TYPE_POPOVER,
2237 "relative-to", relative_to,
2238 NULL);
2239 }
2240
2241 /**
2242 * gtk_popover_set_relative_to:
2243 * @popover: a #GtkPopover
2244 * @relative_to: (allow-none): a #GtkWidget
2245 *
2246 * Sets a new widget to be attached to @popover. If @popover is
2247 * visible, the position will be updated.
2248 *
2249 * Note: the ownership of popovers is always given to their @relative_to
2250 * widget, so if @relative_to is set to %NULL on an attached @popover, it
2251 * will be detached from its previous widget, and consequently destroyed
2252 * unless extra references are kept.
2253 *
2254 * Since: 3.12
2255 **/
2256 void
gtk_popover_set_relative_to(GtkPopover * popover,GtkWidget * relative_to)2257 gtk_popover_set_relative_to (GtkPopover *popover,
2258 GtkWidget *relative_to)
2259 {
2260 g_return_if_fail (GTK_IS_POPOVER (popover));
2261 g_return_if_fail (relative_to == NULL || GTK_IS_WIDGET (relative_to));
2262
2263 gtk_popover_update_relative_to (popover, relative_to);
2264
2265 if (relative_to)
2266 gtk_popover_update_position (popover);
2267 }
2268
2269 /**
2270 * gtk_popover_get_relative_to:
2271 * @popover: a #GtkPopover
2272 *
2273 * Returns the widget @popover is currently attached to
2274 *
2275 * Returns: (transfer none): a #GtkWidget
2276 *
2277 * Since: 3.12
2278 **/
2279 GtkWidget *
gtk_popover_get_relative_to(GtkPopover * popover)2280 gtk_popover_get_relative_to (GtkPopover *popover)
2281 {
2282 g_return_val_if_fail (GTK_IS_POPOVER (popover), NULL);
2283
2284 return popover->priv->widget;
2285 }
2286
2287 /**
2288 * gtk_popover_set_pointing_to:
2289 * @popover: a #GtkPopover
2290 * @rect: rectangle to point to
2291 *
2292 * Sets the rectangle that @popover will point to, in the
2293 * coordinate space of the widget @popover is attached to,
2294 * see gtk_popover_set_relative_to().
2295 *
2296 * Since: 3.12
2297 **/
2298 void
gtk_popover_set_pointing_to(GtkPopover * popover,const GdkRectangle * rect)2299 gtk_popover_set_pointing_to (GtkPopover *popover,
2300 const GdkRectangle *rect)
2301 {
2302 g_return_if_fail (GTK_IS_POPOVER (popover));
2303 g_return_if_fail (rect != NULL);
2304
2305 gtk_popover_update_pointing_to (popover, rect);
2306 gtk_popover_update_position (popover);
2307 }
2308
2309 /**
2310 * gtk_popover_get_pointing_to:
2311 * @popover: a #GtkPopover
2312 * @rect: (out): location to store the rectangle
2313 *
2314 * If a rectangle to point to has been set, this function will
2315 * return %TRUE and fill in @rect with such rectangle, otherwise
2316 * it will return %FALSE and fill in @rect with the attached
2317 * widget coordinates.
2318 *
2319 * Returns: %TRUE if a rectangle to point to was set.
2320 **/
2321 gboolean
gtk_popover_get_pointing_to(GtkPopover * popover,GdkRectangle * rect)2322 gtk_popover_get_pointing_to (GtkPopover *popover,
2323 GdkRectangle *rect)
2324 {
2325 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
2326
2327 g_return_val_if_fail (GTK_IS_POPOVER (popover), FALSE);
2328
2329 if (rect)
2330 {
2331 if (priv->has_pointing_to)
2332 *rect = priv->pointing_to;
2333 else if (priv->widget)
2334 {
2335 gtk_widget_get_allocation (priv->widget, rect);
2336 rect->x = rect->y = 0;
2337 }
2338 }
2339
2340 return priv->has_pointing_to;
2341 }
2342
2343 /**
2344 * gtk_popover_set_position:
2345 * @popover: a #GtkPopover
2346 * @position: preferred popover position
2347 *
2348 * Sets the preferred position for @popover to appear. If the @popover
2349 * is currently visible, it will be immediately updated.
2350 *
2351 * This preference will be respected where possible, although
2352 * on lack of space (eg. if close to the window edges), the
2353 * #GtkPopover may choose to appear on the opposite side
2354 *
2355 * Since: 3.12
2356 **/
2357 void
gtk_popover_set_position(GtkPopover * popover,GtkPositionType position)2358 gtk_popover_set_position (GtkPopover *popover,
2359 GtkPositionType position)
2360 {
2361 g_return_if_fail (GTK_IS_POPOVER (popover));
2362 g_return_if_fail (position >= GTK_POS_LEFT && position <= GTK_POS_BOTTOM);
2363
2364 gtk_popover_update_preferred_position (popover, position);
2365 gtk_popover_update_position (popover);
2366 }
2367
2368 /**
2369 * gtk_popover_get_position:
2370 * @popover: a #GtkPopover
2371 *
2372 * Returns the preferred position of @popover.
2373 *
2374 * Returns: The preferred position.
2375 **/
2376 GtkPositionType
gtk_popover_get_position(GtkPopover * popover)2377 gtk_popover_get_position (GtkPopover *popover)
2378 {
2379 g_return_val_if_fail (GTK_IS_POPOVER (popover), GTK_POS_TOP);
2380
2381 return popover->priv->preferred_position;
2382 }
2383
2384 /**
2385 * gtk_popover_set_modal:
2386 * @popover: a #GtkPopover
2387 * @modal: #TRUE to make popover claim all input within the toplevel
2388 *
2389 * Sets whether @popover is modal, a modal popover will grab all input
2390 * within the toplevel and grab the keyboard focus on it when being
2391 * displayed. Clicking outside the popover area or pressing Esc will
2392 * dismiss the popover and ungrab input.
2393 *
2394 * Since: 3.12
2395 **/
2396 void
gtk_popover_set_modal(GtkPopover * popover,gboolean modal)2397 gtk_popover_set_modal (GtkPopover *popover,
2398 gboolean modal)
2399 {
2400 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
2401
2402 g_return_if_fail (GTK_IS_POPOVER (popover));
2403
2404 modal = modal != FALSE;
2405
2406 if (priv->modal == modal)
2407 return;
2408
2409 priv->modal = modal;
2410
2411 if (gtk_widget_is_visible (GTK_WIDGET (popover)))
2412 gtk_popover_apply_modality (popover, priv->modal);
2413
2414 g_object_notify_by_pspec (G_OBJECT (popover), properties[PROP_MODAL]);
2415 }
2416
2417 /**
2418 * gtk_popover_get_modal:
2419 * @popover: a #GtkPopover
2420 *
2421 * Returns whether the popover is modal, see gtk_popover_set_modal to
2422 * see the implications of this.
2423 *
2424 * Returns: #TRUE if @popover is modal
2425 *
2426 * Since: 3.12
2427 **/
2428 gboolean
gtk_popover_get_modal(GtkPopover * popover)2429 gtk_popover_get_modal (GtkPopover *popover)
2430 {
2431 g_return_val_if_fail (GTK_IS_POPOVER (popover), FALSE);
2432
2433 return popover->priv->modal;
2434 }
2435
2436 /**
2437 * gtk_popover_set_transitions_enabled:
2438 * @popover: a #GtkPopover
2439 * @transitions_enabled: Whether transitions are enabled
2440 *
2441 * Sets whether show/hide transitions are enabled on this popover
2442 *
2443 * Since: 3.16
2444 *
2445 * Deprecated: 3.22: You can show or hide the popover without transitions
2446 * using gtk_widget_show() and gtk_widget_hide() while gtk_popover_popup()
2447 * and gtk_popover_popdown() will use transitions.
2448 */
2449 void
gtk_popover_set_transitions_enabled(GtkPopover * popover,gboolean transitions_enabled)2450 gtk_popover_set_transitions_enabled (GtkPopover *popover,
2451 gboolean transitions_enabled)
2452 {
2453 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
2454
2455 g_return_if_fail (GTK_IS_POPOVER (popover));
2456
2457 transitions_enabled = !!transitions_enabled;
2458
2459 if (priv->transitions_enabled == transitions_enabled)
2460 return;
2461
2462 priv->transitions_enabled = transitions_enabled;
2463 g_object_notify_by_pspec (G_OBJECT (popover), properties[PROP_TRANSITIONS_ENABLED]);
2464 }
2465
2466 /**
2467 * gtk_popover_get_transitions_enabled:
2468 * @popover: a #GtkPopover
2469 *
2470 * Returns whether show/hide transitions are enabled on this popover.
2471 *
2472 * Returns: #TRUE if the show and hide transitions of the given
2473 * popover are enabled, #FALSE otherwise.
2474 *
2475 * Since: 3.16
2476 *
2477 * Deprecated: 3.22: You can show or hide the popover without transitions
2478 * using gtk_widget_show() and gtk_widget_hide() while gtk_popover_popup()
2479 * and gtk_popover_popdown() will use transitions.
2480 */
2481 gboolean
gtk_popover_get_transitions_enabled(GtkPopover * popover)2482 gtk_popover_get_transitions_enabled (GtkPopover *popover)
2483 {
2484 g_return_val_if_fail (GTK_IS_POPOVER (popover), FALSE);
2485
2486 return popover->priv->transitions_enabled;
2487 }
2488
2489
2490 static void
back_to_main(GtkWidget * popover)2491 back_to_main (GtkWidget *popover)
2492 {
2493 GtkWidget *stack;
2494
2495 stack = gtk_bin_get_child (GTK_BIN (popover));
2496 gtk_stack_set_visible_child_name (GTK_STACK (stack), "main");
2497 }
2498
2499 /**
2500 * gtk_popover_bind_model:
2501 * @popover: a #GtkPopover
2502 * @model: (allow-none): the #GMenuModel to bind to or %NULL to remove
2503 * binding
2504 * @action_namespace: (allow-none): the namespace for actions in @model
2505 *
2506 * Establishes a binding between a #GtkPopover and a #GMenuModel.
2507 *
2508 * The contents of @popover are removed and then refilled with menu items
2509 * according to @model. When @model changes, @popover is updated.
2510 * Calling this function twice on @popover with different @model will
2511 * cause the first binding to be replaced with a binding to the new
2512 * model. If @model is %NULL then any previous binding is undone and
2513 * all children are removed.
2514 *
2515 * If @action_namespace is non-%NULL then the effect is as if all
2516 * actions mentioned in the @model have their names prefixed with the
2517 * namespace, plus a dot. For example, if the action “quit” is
2518 * mentioned and @action_namespace is “app” then the effective action
2519 * name is “app.quit”.
2520 *
2521 * This function uses #GtkActionable to define the action name and
2522 * target values on the created menu items. If you want to use an
2523 * action group other than “app” and “win”, or if you want to use a
2524 * #GtkMenuShell outside of a #GtkApplicationWindow, then you will need
2525 * to attach your own action group to the widget hierarchy using
2526 * gtk_widget_insert_action_group(). As an example, if you created a
2527 * group with a “quit” action and inserted it with the name “mygroup”
2528 * then you would use the action name “mygroup.quit” in your
2529 * #GMenuModel.
2530 *
2531 * Since: 3.12
2532 */
2533 void
gtk_popover_bind_model(GtkPopover * popover,GMenuModel * model,const gchar * action_namespace)2534 gtk_popover_bind_model (GtkPopover *popover,
2535 GMenuModel *model,
2536 const gchar *action_namespace)
2537 {
2538 GtkWidget *child;
2539 GtkWidget *stack;
2540 GtkStyleContext *style_context;
2541
2542 g_return_if_fail (GTK_IS_POPOVER (popover));
2543 g_return_if_fail (model == NULL || G_IS_MENU_MODEL (model));
2544
2545 child = gtk_bin_get_child (GTK_BIN (popover));
2546 if (child)
2547 gtk_widget_destroy (child);
2548
2549 style_context = gtk_widget_get_style_context (GTK_WIDGET (popover));
2550
2551 if (model)
2552 {
2553 stack = gtk_stack_new ();
2554 gtk_stack_set_vhomogeneous (GTK_STACK (stack), FALSE);
2555 gtk_stack_set_transition_type (GTK_STACK (stack), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
2556 gtk_stack_set_interpolate_size (GTK_STACK (stack), TRUE);
2557 gtk_widget_show (stack);
2558 gtk_container_add (GTK_CONTAINER (popover), stack);
2559
2560 gtk_menu_section_box_new_toplevel (GTK_STACK (stack),
2561 model,
2562 action_namespace,
2563 popover);
2564 gtk_stack_set_visible_child_name (GTK_STACK (stack), "main");
2565
2566 g_signal_connect (popover, "unmap", G_CALLBACK (back_to_main), NULL);
2567 g_signal_connect (popover, "map", G_CALLBACK (back_to_main), NULL);
2568
2569 gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_MENU);
2570 }
2571 else
2572 {
2573 gtk_style_context_remove_class (style_context, GTK_STYLE_CLASS_MENU);
2574 }
2575 }
2576
2577 /**
2578 * gtk_popover_new_from_model:
2579 * @relative_to: (allow-none): #GtkWidget the popover is related to
2580 * @model: a #GMenuModel
2581 *
2582 * Creates a #GtkPopover and populates it according to
2583 * @model. The popover is pointed to the @relative_to widget.
2584 *
2585 * The created buttons are connected to actions found in the
2586 * #GtkApplicationWindow to which the popover belongs - typically
2587 * by means of being attached to a widget that is contained within
2588 * the #GtkApplicationWindows widget hierarchy.
2589 *
2590 * Actions can also be added using gtk_widget_insert_action_group()
2591 * on the menus attach widget or on any of its parent widgets.
2592 *
2593 * Returns: the new #GtkPopover
2594 *
2595 * Since: 3.12
2596 */
2597 GtkWidget *
gtk_popover_new_from_model(GtkWidget * relative_to,GMenuModel * model)2598 gtk_popover_new_from_model (GtkWidget *relative_to,
2599 GMenuModel *model)
2600 {
2601 GtkWidget *popover;
2602
2603 g_return_val_if_fail (relative_to == NULL || GTK_IS_WIDGET (relative_to), NULL);
2604 g_return_val_if_fail (G_IS_MENU_MODEL (model), NULL);
2605
2606 popover = gtk_popover_new (relative_to);
2607 gtk_popover_bind_model (GTK_POPOVER (popover), model, NULL);
2608
2609 return popover;
2610 }
2611
2612 /**
2613 * gtk_popover_set_default_widget:
2614 * @popover: a #GtkPopover
2615 * @widget: (allow-none): the new default widget, or %NULL
2616 *
2617 * Sets the widget that should be set as default widget while
2618 * the popover is shown (see gtk_window_set_default()). #GtkPopover
2619 * remembers the previous default widget and reestablishes it
2620 * when the popover is dismissed.
2621 *
2622 * Since: 3.18
2623 */
2624 void
gtk_popover_set_default_widget(GtkPopover * popover,GtkWidget * widget)2625 gtk_popover_set_default_widget (GtkPopover *popover,
2626 GtkWidget *widget)
2627 {
2628 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
2629
2630 g_return_if_fail (GTK_IS_POPOVER (popover));
2631 g_return_if_fail (widget == NULL || gtk_widget_get_can_default (widget));
2632
2633 if (priv->default_widget == widget)
2634 return;
2635
2636 if (priv->default_widget)
2637 g_object_unref (priv->default_widget);
2638
2639 priv->default_widget = widget;
2640
2641 if (priv->default_widget)
2642 g_object_ref (priv->default_widget);
2643
2644 if (gtk_widget_get_mapped (GTK_WIDGET (popover)))
2645 gtk_window_set_default (priv->window, priv->default_widget);
2646 }
2647
2648 /**
2649 * gtk_popover_get_default_widget:
2650 * @popover: a #GtkPopover
2651 *
2652 * Gets the widget that should be set as the default while
2653 * the popover is shown.
2654 *
2655 * Returns: (nullable) (transfer none): the default widget,
2656 * or %NULL if there is none
2657 *
2658 * Since: 3.18
2659 */
2660 GtkWidget *
gtk_popover_get_default_widget(GtkPopover * popover)2661 gtk_popover_get_default_widget (GtkPopover *popover)
2662 {
2663 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
2664
2665 g_return_val_if_fail (GTK_IS_POPOVER (popover), NULL);
2666
2667 return priv->default_widget;
2668 }
2669
2670 /**
2671 * gtk_popover_set_constrain_to:
2672 * @popover: a #GtkPopover
2673 * @constraint: the new constraint
2674 *
2675 * Sets a constraint for positioning this popover.
2676 *
2677 * Note that not all platforms support placing popovers freely,
2678 * and may already impose constraints.
2679 *
2680 * Since: 3.20
2681 */
2682 void
gtk_popover_set_constrain_to(GtkPopover * popover,GtkPopoverConstraint constraint)2683 gtk_popover_set_constrain_to (GtkPopover *popover,
2684 GtkPopoverConstraint constraint)
2685 {
2686 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
2687
2688 g_return_if_fail (GTK_IS_POPOVER (popover));
2689
2690 if (priv->constraint == constraint)
2691 return;
2692
2693 priv->constraint = constraint;
2694 gtk_popover_update_position (popover);
2695
2696 g_object_notify_by_pspec (G_OBJECT (popover), properties[PROP_CONSTRAIN_TO]);
2697 }
2698
2699 /**
2700 * gtk_popover_get_constrain_to:
2701 * @popover: a #GtkPopover
2702 *
2703 * Returns the constraint for placing this popover.
2704 * See gtk_popover_set_constrain_to().
2705 *
2706 * Returns: the constraint for placing this popover.
2707 *
2708 * Since: 3.20
2709 */
2710 GtkPopoverConstraint
gtk_popover_get_constrain_to(GtkPopover * popover)2711 gtk_popover_get_constrain_to (GtkPopover *popover)
2712 {
2713 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
2714
2715 g_return_val_if_fail (GTK_IS_POPOVER (popover), GTK_POPOVER_CONSTRAINT_WINDOW);
2716
2717 return priv->constraint;
2718 }
2719
2720 /**
2721 * gtk_popover_popup:
2722 * @popover: a #GtkPopover
2723 *
2724 * Pops @popover up. This is different than a gtk_widget_show() call
2725 * in that it shows the popover with a transition. If you want to show
2726 * the popover without a transition, use gtk_widget_show().
2727 *
2728 * Since: 3.22
2729 */
2730 void
gtk_popover_popup(GtkPopover * popover)2731 gtk_popover_popup (GtkPopover *popover)
2732 {
2733 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
2734
2735 g_return_if_fail (GTK_IS_POPOVER (popover));
2736
2737 if (priv->state == STATE_SHOWING ||
2738 priv->state == STATE_SHOWN)
2739 return;
2740
2741 gtk_widget_show (GTK_WIDGET (popover));
2742
2743 if (transitions_enabled (popover))
2744 gtk_popover_set_state (popover, STATE_SHOWING);
2745 }
2746
2747 /**
2748 * gtk_popover_popdown:
2749 * @popover: a #GtkPopover
2750 *
2751 * Pops @popover down.This is different than a gtk_widget_hide() call
2752 * in that it shows the popover with a transition. If you want to hide
2753 * the popover without a transition, use gtk_widget_hide().
2754 *
2755 * Since: 3.22
2756 */
2757 void
gtk_popover_popdown(GtkPopover * popover)2758 gtk_popover_popdown (GtkPopover *popover)
2759 {
2760 GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
2761
2762 g_return_if_fail (GTK_IS_POPOVER (popover));
2763
2764 if (priv->state == STATE_HIDING ||
2765 priv->state == STATE_HIDDEN)
2766 return;
2767
2768
2769 if (!transitions_enabled (popover))
2770 gtk_widget_hide (GTK_WIDGET (popover));
2771 else
2772 gtk_popover_set_state (popover, STATE_HIDING);
2773
2774 gtk_popover_hide_internal (popover);
2775 }
2776