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