gtkpopover.c 77.1 KB
Newer Older
Carlos Garnacho's avatar
Carlos Garnacho committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/* GTK - The GIMP Toolkit
 * Copyright © 2013 Carlos Garnacho <carlosg@gnome.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

18 19 20 21
/**
 * SECTION:gtkpopover
 * @Short_description: Context dependent bubbles
 * @Title: GtkPopover
Carlos Garnacho's avatar
Carlos Garnacho committed
22
 *
23 24 25 26 27 28
 * GtkPopover is a bubble-like context window, primarily meant to
 * provide context-dependent information or options. Popovers are
 * attached to a widget, passed at construction time on gtk_popover_new(),
 * or updated afterwards through gtk_popover_set_relative_to(), by
 * default they will point to the whole widget area, although this
 * behavior can be changed through gtk_popover_set_pointing_to().
Carlos Garnacho's avatar
Carlos Garnacho committed
29
 *
30 31 32
 * The position of a popover relative to the widget it is attached to
 * can also be changed through gtk_popover_set_position().
 *
33 34
 * By default, #GtkPopover performs a GTK+ grab, in order to ensure
 * 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 37 38 39
 * the popover, or the Esc key being pressed). If no such modal behavior
 * is desired on a popover, gtk_popover_set_modal() may be called on it
 * to tweak its behavior.
 *
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
 * ## GtkPopover as menu replacement
 *
 * GtkPopover is often used to replace menus. To facilitate this, it
 * supports being populated from a #GMenuModel, using
 * gtk_popover_new_from_model(). In addition to all the regular menu
 * model features, this function supports rendering sections in the
 * model in a more compact form, as a row of icon buttons instead of
 * menu items.
 *
 * To use this rendering, set the ”display-hint” attribute of the
 * section to ”horizontal-buttons” and set the icons of your items
 * with the ”verb-icon” attribute.
 *
 * |[
 * <section>
 *   <attribute name="display-hint">horizontal-buttons</attribute>
 *   <item>
 *     <attribute name="label">Cut</attribute>
 *     <attribute name="action">app.cut</attribute>
 *     <attribute name="verb-icon">edit-cut-symbolic</attribute>
 *   </item>
 *   <item>
 *     <attribute name="label">Copy</attribute>
 *     <attribute name="action">app.copy</attribute>
 *     <attribute name="verb-icon">edit-copy-symbolic</attribute>
 *   </item>
 *   <item>
 *     <attribute name="label">Paste</attribute>
 *     <attribute name="action">app.paste</attribute>
 *     <attribute name="verb-icon">edit-paste-symbolic</attribute>
 *   </item>
 * </section>
 * ]|
 *
74 75
 * # CSS nodes
 *
Michael Catanzaro's avatar
Michael Catanzaro committed
76
 * GtkPopover has a single css node called popover. It always gets the
77 78
 * .background style class and it gets the .menu style class if it is
 * menu-like (e.g. #GtkPopoverMenu or created using gtk_popover_new_from_model().
79 80 81 82 83
 *
 * Particular uses of GtkPopover, such as touch selection popups
 * or magnifiers in #GtkEntry or #GtkTextView get style classes
 * like .touch-selection or .magnifier to differentiate from
 * plain popovers.
Timm Bäder's avatar
Timm Bäder committed
84
 *
85
 * Since: 3.12
Carlos Garnacho's avatar
Carlos Garnacho committed
86 87 88 89
 */

#include "config.h"
#include <gdk/gdk.h>
Carlos Garnacho's avatar
Carlos Garnacho committed
90
#include "gtkpopover.h"
91
#include "gtkpopoverprivate.h"
Matthias Clasen's avatar
Matthias Clasen committed
92 93
#include "gtktypebuiltins.h"
#include "gtkmain.h"
94
#include "gtkwindowprivate.h"
95 96
#include "gtkscrollable.h"
#include "gtkadjustment.h"
Carlos Garnacho's avatar
Carlos Garnacho committed
97 98
#include "gtkprivate.h"
#include "gtkintl.h"
99 100 101 102 103 104 105 106 107 108
#include "gtklabel.h"
#include "gtkbox.h"
#include "gtkbutton.h"
#include "gtkseparator.h"
#include "gtkmodelbutton.h"
#include "gtkwidgetprivate.h"
#include "gtkactionmuxer.h"
#include "gtkmenutracker.h"
#include "gtkstack.h"
#include "gtksizegroup.h"
109
#include "a11y/gtkpopoveraccessible.h"
110
#include "gtkmenusectionbox.h"
111 112
#include "gtkroundedboxprivate.h"
#include "gtkstylecontextprivate.h"
113
#include "gtkprogresstrackerprivate.h"
114
#include "gtksettingsprivate.h"
Carlos Garnacho's avatar
Carlos Garnacho committed
115

116 117 118 119
#ifdef GDK_WINDOWING_WAYLAND
#include "wayland/gdkwayland.h"
#endif

120 121 122
#define TAIL_GAP_WIDTH  24
#define TAIL_HEIGHT     12
#define TRANSITION_DIFF 20
123
#define TRANSITION_DURATION 150 * 1000
Carlos Garnacho's avatar
Carlos Garnacho committed
124 125 126 127 128 129

#define POS_IS_VERTICAL(p) ((p) == GTK_POS_TOP || (p) == GTK_POS_BOTTOM)

enum {
  PROP_RELATIVE_TO = 1,
  PROP_POINTING_TO,
130
  PROP_POSITION,
131
  PROP_MODAL,
132
  PROP_TRANSITIONS_ENABLED,
133 134
  PROP_CONSTRAIN_TO,
  NUM_PROPERTIES
Carlos Garnacho's avatar
Carlos Garnacho committed
135 136
};

137 138 139 140 141
enum {
  CLOSED,
  N_SIGNALS
};

142 143 144 145 146 147 148
enum {
  STATE_SHOWING,
  STATE_SHOWN,
  STATE_HIDING,
  STATE_HIDDEN
};

Carlos Garnacho's avatar
Carlos Garnacho committed
149
struct _GtkPopoverPrivate
Carlos Garnacho's avatar
Carlos Garnacho committed
150
{
151 152
  GtkWidget *widget;
  GtkWindow *window;
153
  GtkWidget *prev_focus_widget;
154 155
  GtkWidget *default_widget;
  GtkWidget *prev_default;
156 157 158
  GtkScrollable *parent_scrollable;
  GtkAdjustment *vadj;
  GtkAdjustment *hadj;
159
  GdkRectangle pointing_to;
160
  GtkPopoverConstraint constraint;
161
  GtkProgressTracker tracker;
162
  guint prev_focus_unmap_id;
163 164 165
  guint hierarchy_changed_id;
  guint size_allocate_id;
  guint unmap_id;
166
  guint scrollable_notify_id;
167
  guint grab_notify_id;
168
  guint state_changed_id;
Carlos Garnacho's avatar
Carlos Garnacho committed
169 170 171
  guint has_pointing_to    : 1;
  guint preferred_position : 2;
  guint final_position     : 2;
Carlos Garnacho's avatar
Carlos Garnacho committed
172
  guint current_position   : 2;
173
  guint modal              : 1;
174
  guint button_pressed     : 1;
175
  guint grab_notify_blocked : 1;
176 177 178
  guint transitions_enabled : 1;
  guint state               : 2;
  guint visible             : 1;
179
  guint first_frame_skipped : 1;
180 181
  gint transition_diff;
  guint tick_id;
182 183 184

  gint tip_x;
  gint tip_y;
Carlos Garnacho's avatar
Carlos Garnacho committed
185 186
};

187
static GParamSpec *properties[NUM_PROPERTIES];
188
static GQuark quark_widget_popovers = 0;
189
static guint signals[N_SIGNALS] = { 0 };
190

191 192
static void gtk_popover_update_relative_to (GtkPopover *popover,
                                            GtkWidget  *relative_to);
193 194
static void gtk_popover_set_state          (GtkPopover *popover,
                                            guint       state);
195
static void gtk_popover_invalidate_borders (GtkPopover *popover);
196 197
static void gtk_popover_apply_modality     (GtkPopover *popover,
                                            gboolean    modal);
Carlos Garnacho's avatar
Carlos Garnacho committed
198

199 200 201
static void gtk_popover_set_scrollable_full (GtkPopover    *popover,
                                             GtkScrollable *scrollable);

Carlos Garnacho's avatar
Carlos Garnacho committed
202
G_DEFINE_TYPE_WITH_PRIVATE (GtkPopover, gtk_popover, GTK_TYPE_BIN)
Carlos Garnacho's avatar
Carlos Garnacho committed
203 204

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
205
gtk_popover_init (GtkPopover *popover)
Carlos Garnacho's avatar
Carlos Garnacho committed
206 207
{
  GtkWidget *widget;
208
  GtkStyleContext *context;
Carlos Garnacho's avatar
Carlos Garnacho committed
209

Carlos Garnacho's avatar
Carlos Garnacho committed
210
  widget = GTK_WIDGET (popover);
211
  gtk_widget_set_has_window (widget, TRUE);
212 213
  popover->priv = gtk_popover_get_instance_private (popover);
  popover->priv->modal = TRUE;
214
  popover->priv->tick_id = 0;
215 216
  popover->priv->state = STATE_HIDDEN;
  popover->priv->visible = FALSE;
217
  popover->priv->transitions_enabled = TRUE;
218
  popover->priv->preferred_position = GTK_POS_TOP;
219
  popover->priv->constraint = GTK_POPOVER_CONSTRAINT_WINDOW;
220 221 222

  context = gtk_widget_get_style_context (GTK_WIDGET (popover));
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_BACKGROUND);
Carlos Garnacho's avatar
Carlos Garnacho committed
223 224 225
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
226 227 228 229
gtk_popover_set_property (GObject      *object,
                          guint         prop_id,
                          const GValue *value,
                          GParamSpec   *pspec)
Carlos Garnacho's avatar
Carlos Garnacho committed
230 231 232 233
{
  switch (prop_id)
    {
    case PROP_RELATIVE_TO:
Carlos Garnacho's avatar
Carlos Garnacho committed
234 235
      gtk_popover_set_relative_to (GTK_POPOVER (object),
                                   g_value_get_object (value));
Carlos Garnacho's avatar
Carlos Garnacho committed
236 237
      break;
    case PROP_POINTING_TO:
Carlos Garnacho's avatar
Carlos Garnacho committed
238 239
      gtk_popover_set_pointing_to (GTK_POPOVER (object),
                                   g_value_get_boxed (value));
Carlos Garnacho's avatar
Carlos Garnacho committed
240 241
      break;
    case PROP_POSITION:
Carlos Garnacho's avatar
Carlos Garnacho committed
242 243
      gtk_popover_set_position (GTK_POPOVER (object),
                                g_value_get_enum (value));
Carlos Garnacho's avatar
Carlos Garnacho committed
244
      break;
245 246 247 248
    case PROP_MODAL:
      gtk_popover_set_modal (GTK_POPOVER (object),
                             g_value_get_boolean (value));
      break;
249
    case PROP_TRANSITIONS_ENABLED:
250
      G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
251 252
      gtk_popover_set_transitions_enabled (GTK_POPOVER (object),
                                           g_value_get_boolean (value));
253
      G_GNUC_END_IGNORE_DEPRECATIONS;
254
      break;
255 256 257 258
    case PROP_CONSTRAIN_TO:
      gtk_popover_set_constrain_to (GTK_POPOVER (object),
                                    g_value_get_enum (value));
      break;
Carlos Garnacho's avatar
Carlos Garnacho committed
259 260 261 262 263 264
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
265 266 267 268
gtk_popover_get_property (GObject    *object,
                          guint       prop_id,
                          GValue     *value,
                          GParamSpec *pspec)
Carlos Garnacho's avatar
Carlos Garnacho committed
269
{
Carlos Garnacho's avatar
Carlos Garnacho committed
270
  GtkPopoverPrivate *priv = GTK_POPOVER (object)->priv;
Carlos Garnacho's avatar
Carlos Garnacho committed
271 272 273 274

  switch (prop_id)
    {
    case PROP_RELATIVE_TO:
275
      g_value_set_object (value, priv->widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
276 277 278 279 280 281 282
      break;
    case PROP_POINTING_TO:
      g_value_set_boxed (value, &priv->pointing_to);
      break;
    case PROP_POSITION:
      g_value_set_enum (value, priv->preferred_position);
      break;
283 284 285
    case PROP_MODAL:
      g_value_set_boolean (value, priv->modal);
      break;
286 287 288
    case PROP_TRANSITIONS_ENABLED:
      g_value_set_boolean (value, priv->transitions_enabled);
      break;
289 290 291
    case PROP_CONSTRAIN_TO:
      g_value_set_enum (value, priv->constraint);
      break;
Carlos Garnacho's avatar
Carlos Garnacho committed
292 293 294 295 296
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

297 298 299 300 301
static gboolean
transitions_enabled (GtkPopover *popover)
{
  GtkPopoverPrivate *priv = popover->priv;

302 303
  return gtk_settings_get_enable_animations (gtk_widget_get_settings (GTK_WIDGET (popover))) &&
         priv->transitions_enabled;
304 305
}

306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
static void
gtk_popover_hide_internal (GtkPopover *popover)
{
  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
  GtkWidget *widget = GTK_WIDGET (popover);

  if (!priv->visible)
    return;

  priv->visible = FALSE;
  g_signal_emit (widget, signals[CLOSED], 0);

  if (priv->modal)
    gtk_popover_apply_modality (popover, FALSE);

  if (gtk_widget_get_realized (widget))
    {
      cairo_region_t *region = cairo_region_create ();
      gdk_window_input_shape_combine_region (gtk_widget_get_parent_window (widget),
                                             region, 0, 0);
      cairo_region_destroy (region);
    }
}

Carlos Garnacho's avatar
Carlos Garnacho committed
330
static void
Carlos Garnacho's avatar
Carlos Garnacho committed
331
gtk_popover_finalize (GObject *object)
Carlos Garnacho's avatar
Carlos Garnacho committed
332
{
Carlos Garnacho's avatar
Carlos Garnacho committed
333
  GtkPopover *popover = GTK_POPOVER (object);
334 335 336 337
  GtkPopoverPrivate *priv = popover->priv;

  if (priv->widget)
    gtk_popover_update_relative_to (popover, NULL);
Carlos Garnacho's avatar
Carlos Garnacho committed
338

Carlos Garnacho's avatar
Carlos Garnacho committed
339
  G_OBJECT_CLASS (gtk_popover_parent_class)->finalize (object);
Carlos Garnacho's avatar
Carlos Garnacho committed
340 341
}

342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
static void
popover_unset_prev_focus (GtkPopover *popover)
{
  GtkPopoverPrivate *priv = popover->priv;

  if (!priv->prev_focus_widget)
    return;

  if (priv->prev_focus_unmap_id)
    {
      g_signal_handler_disconnect (priv->prev_focus_widget,
                                   priv->prev_focus_unmap_id);
      priv->prev_focus_unmap_id = 0;
    }

  g_clear_object (&priv->prev_focus_widget);
}

360
static void
Carlos Garnacho's avatar
Carlos Garnacho committed
361
gtk_popover_dispose (GObject *object)
362
{
Carlos Garnacho's avatar
Carlos Garnacho committed
363 364
  GtkPopover *popover = GTK_POPOVER (object);
  GtkPopoverPrivate *priv = popover->priv;
365

366 367 368
  if (priv->modal)
    gtk_popover_apply_modality (popover, FALSE);

369
  if (priv->window)
370 371 372 373
    {
      g_signal_handlers_disconnect_by_data (priv->window, popover);
      _gtk_window_remove_popover (priv->window, GTK_WIDGET (object));
    }
374 375

  priv->window = NULL;
376 377 378

  if (priv->widget)
    gtk_popover_update_relative_to (popover, NULL);
379

380
  popover_unset_prev_focus (popover);
381

382 383
  g_clear_object (&priv->default_widget);

Carlos Garnacho's avatar
Carlos Garnacho committed
384
  G_OBJECT_CLASS (gtk_popover_parent_class)->dispose (object);
385 386 387
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
388
gtk_popover_realize (GtkWidget *widget)
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
{
  GtkAllocation allocation;
  GdkWindowAttr attributes;
  gint attributes_mask;
  GdkWindow *window;

  gtk_widget_get_allocation (widget, &allocation);

  attributes.x = 0;
  attributes.y = 0;
  attributes.width = allocation.width;
  attributes.height = allocation.height;
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.event_mask =
    gtk_widget_get_events (widget) |
406
    GDK_POINTER_MOTION_MASK |
407 408 409 410 411 412 413 414 415 416 417 418 419 420
    GDK_BUTTON_MOTION_MASK |
    GDK_BUTTON_PRESS_MASK |
    GDK_BUTTON_RELEASE_MASK |
    GDK_ENTER_NOTIFY_MASK |
    GDK_LEAVE_NOTIFY_MASK;

  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
  window = gdk_window_new (gtk_widget_get_parent_window (widget),
                           &attributes, attributes_mask);
  gtk_widget_set_window (widget, window);
  gtk_widget_register_window (widget, window);
  gtk_widget_set_realized (widget, TRUE);
}

421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
static gboolean
window_focus_in (GtkWidget  *widget,
                 GdkEvent   *event,
                 GtkPopover *popover)
{
  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);

  /* Regain the grab when the window is focused */
  if (priv->modal &&
      gtk_widget_is_drawable (GTK_WIDGET (popover)))
    {
      GtkWidget *focus;

      gtk_grab_add (GTK_WIDGET (popover));

      focus = gtk_window_get_focus (GTK_WINDOW (widget));

Matthias Clasen's avatar
Matthias Clasen committed
438
      if (focus == NULL || !gtk_widget_is_ancestor (focus, GTK_WIDGET (popover)))
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
        gtk_widget_grab_focus (GTK_WIDGET (popover));

      if (priv->grab_notify_blocked)
        g_signal_handler_unblock (priv->widget, priv->grab_notify_id);

      priv->grab_notify_blocked = FALSE;
    }
  return FALSE;
}

static gboolean
window_focus_out (GtkWidget  *widget,
                  GdkEvent   *event,
                  GtkPopover *popover)
{
  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);

  /* Temporarily remove the grab when unfocused */
  if (priv->modal &&
      gtk_widget_is_drawable (GTK_WIDGET (popover)))
    {
      g_signal_handler_block (priv->widget, priv->grab_notify_id);
      gtk_grab_remove (GTK_WIDGET (popover));
      priv->grab_notify_blocked = TRUE;
    }
  return FALSE;
}

467 468 469 470 471 472 473
static void
window_set_focus (GtkWindow  *window,
                  GtkWidget  *widget,
                  GtkPopover *popover)
{
  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);

474 475
  if (!priv->modal || !widget || !gtk_widget_is_drawable (GTK_WIDGET (popover)))
    return;
476

477 478 479 480 481
  widget = gtk_widget_get_ancestor (widget, GTK_TYPE_POPOVER);
  while (widget != NULL)
    {
      if (widget == GTK_WIDGET (popover))
        return;
482

483 484 485 486
      widget = gtk_popover_get_relative_to (GTK_POPOVER (widget));
      if (widget == NULL)
        break;
      widget = gtk_widget_get_ancestor (widget, GTK_TYPE_POPOVER);
487
    }
488 489 490

  popover_unset_prev_focus (popover);
  gtk_widget_hide (GTK_WIDGET (popover));
491 492
}

493 494 495 496 497 498 499
static void
prev_focus_unmap_cb (GtkWidget  *widget,
                     GtkPopover *popover)
{
  popover_unset_prev_focus (popover);
}

500 501 502 503
static void
gtk_popover_apply_modality (GtkPopover *popover,
                            gboolean    modal)
{
504
  GtkPopoverPrivate *priv = popover->priv;
505

506 507 508
  if (!priv->window)
    return;

509 510 511 512 513
  if (modal)
    {
      GtkWidget *prev_focus;

      prev_focus = gtk_window_get_focus (priv->window);
514 515
      priv->prev_focus_widget = prev_focus;
      if (priv->prev_focus_widget)
516 517 518 519 520 521
        {
          priv->prev_focus_unmap_id =
            g_signal_connect (prev_focus, "unmap",
                              G_CALLBACK (prev_focus_unmap_cb), popover);
          g_object_ref (prev_focus);
        }
522
      gtk_grab_add (GTK_WIDGET (popover));
523
      gtk_window_set_focus (priv->window, NULL);
524
      gtk_widget_grab_focus (GTK_WIDGET (popover));
525 526 527 528 529

      g_signal_connect (priv->window, "focus-in-event",
                        G_CALLBACK (window_focus_in), popover);
      g_signal_connect (priv->window, "focus-out-event",
                        G_CALLBACK (window_focus_out), popover);
530 531
      g_signal_connect (priv->window, "set-focus",
                        G_CALLBACK (window_set_focus), popover);
532 533 534
    }
  else
    {
535
      g_signal_handlers_disconnect_by_data (priv->window, popover);
536 537
      gtk_grab_remove (GTK_WIDGET (popover));

538 539 540 541
      /* Let prev_focus_widget regain focus */
      if (priv->prev_focus_widget &&
          gtk_widget_is_drawable (priv->prev_focus_widget))
        gtk_widget_grab_focus (priv->prev_focus_widget);
542
      else if (priv->window)
543
        gtk_widget_grab_focus (GTK_WIDGET (priv->window));
544

545
      popover_unset_prev_focus (popover);
546 547 548
    }
}

549 550 551 552 553 554 555 556 557
static gboolean
show_animate_cb (GtkWidget     *widget,
                 GdkFrameClock *frame_clock,
                 gpointer       user_data)
{
  GtkPopover *popover = GTK_POPOVER (widget);
  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
  gdouble t;

558 559 560 561 562 563
  if (priv->first_frame_skipped)
    gtk_progress_tracker_advance_frame (&priv->tracker,
                                        gdk_frame_clock_get_frame_time (frame_clock));
  else
    priv->first_frame_skipped = TRUE;

564
  t = gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE);
565 566 567 568 569 570 571 572 573 574 575 576

  if (priv->state == STATE_SHOWING)
    {
      priv->transition_diff = TRANSITION_DIFF - (TRANSITION_DIFF * t);
      gtk_widget_set_opacity (widget, t);
    }
  else if (priv->state == STATE_HIDING)
    {
      priv->transition_diff = -TRANSITION_DIFF * t;
      gtk_widget_set_opacity (widget, 1.0 - t);
    }

577
  gtk_popover_update_position (popover);
578

579
  if (gtk_progress_tracker_get_state (&priv->tracker) == GTK_PROGRESS_STATE_AFTER)
580 581 582 583 584 585 586 587 588
    {
      if (priv->state == STATE_SHOWING)
        {
          gtk_popover_set_state (popover, STATE_SHOWN);

          if (!priv->visible)
            gtk_popover_set_state (popover, STATE_HIDING);
        }
      else
589 590 591
        {
          gtk_widget_hide (widget);
        }
592

593 594
      priv->tick_id = 0;
      return G_SOURCE_REMOVE;
595 596
    }
  else
597 598 599 600 601 602 603 604 605 606 607 608 609
    return G_SOURCE_CONTINUE;
}

static void
gtk_popover_stop_transition (GtkPopover *popover)
{
  GtkPopoverPrivate *priv = popover->priv;

  if (priv->tick_id != 0)
    {
      gtk_widget_remove_tick_callback (GTK_WIDGET (popover), priv->tick_id);
      priv->tick_id = 0;
    }
610 611
}

612
static void
613
gtk_popover_start_transition (GtkPopover *popover)
614
{
615 616 617 618 619
  GtkPopoverPrivate *priv = popover->priv;

  if (priv->tick_id != 0)
    return;

620
  priv->first_frame_skipped = FALSE;
621
  gtk_progress_tracker_start (&priv->tracker, TRANSITION_DURATION, 0, 1.0);
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
  priv->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (popover),
                                                show_animate_cb,
                                                popover, NULL);
}

static void
gtk_popover_set_state (GtkPopover *popover,
                       guint       state)
{
  GtkPopoverPrivate *priv = popover->priv;

  if (!transitions_enabled (popover) ||
      !gtk_widget_get_realized (GTK_WIDGET (popover)))
    {
      if (state == STATE_SHOWING)
        state = STATE_SHOWN;
      else if (state == STATE_HIDING)
        state = STATE_HIDDEN;
    }

  priv->state = state;
643

644 645 646 647
  if (state == STATE_SHOWING || state == STATE_HIDING)
    gtk_popover_start_transition (popover);
  else
    {
648
      gtk_popover_stop_transition (popover);
649 650 651 652 653 654 655 656

      gtk_widget_set_visible (GTK_WIDGET (popover), state == STATE_SHOWN);
    }
}

static void
gtk_popover_map (GtkWidget *widget)
{
657 658 659
  GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;

  priv->prev_default = gtk_window_get_default_widget (priv->window);
660 661
  if (priv->prev_default)
    g_object_ref (priv->prev_default);
662

Carlos Garnacho's avatar
Carlos Garnacho committed
663
  GTK_WIDGET_CLASS (gtk_popover_parent_class)->map (widget);
664

665
  gdk_window_show (gtk_widget_get_window (widget));
Carlos Garnacho's avatar
Carlos Garnacho committed
666
  gtk_popover_update_position (GTK_POPOVER (widget));
667 668

  gtk_window_set_default (priv->window, priv->default_widget);
669 670 671
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
672
gtk_popover_unmap (GtkWidget *widget)
673
{
674
  GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
675

676
  priv->button_pressed = FALSE;
677

678
  gdk_window_hide (gtk_widget_get_window (widget));
Carlos Garnacho's avatar
Carlos Garnacho committed
679
  GTK_WIDGET_CLASS (gtk_popover_parent_class)->unmap (widget);
680

681 682
  if (gtk_window_get_default_widget (priv->window) == priv->default_widget)
    gtk_window_set_default (priv->window, priv->prev_default);
683
  g_clear_object (&priv->prev_default);
684 685
}

686 687 688 689 690 691 692 693 694 695 696 697 698 699 700
static GtkPositionType
get_effective_position (GtkPopover      *popover,
                        GtkPositionType  pos)
{
  if (gtk_widget_get_direction (GTK_WIDGET (popover)) == GTK_TEXT_DIR_RTL)
    {
      if (pos == GTK_POS_LEFT)
        pos = GTK_POS_RIGHT;
      else if (pos == GTK_POS_RIGHT)
        pos = GTK_POS_LEFT;
    }

  return pos;
}

701 702 703 704 705 706 707
static void
get_margin (GtkWidget *widget,
            GtkBorder *border)
{
  GtkStyleContext *context;

  context = gtk_widget_get_style_context (widget);
708 709 710
  gtk_style_context_get_margin (context,
                                gtk_style_context_get_state (context),
                                border);
711 712
}

Carlos Garnacho's avatar
Carlos Garnacho committed
713
static void
Carlos Garnacho's avatar
Carlos Garnacho committed
714 715 716 717 718 719 720 721
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)
Carlos Garnacho's avatar
Carlos Garnacho committed
722
{
Carlos Garnacho's avatar
Carlos Garnacho committed
723 724
  GtkWidget *widget = GTK_WIDGET (popover);
  GtkPopoverPrivate *priv = popover->priv;
725
  GdkRectangle rect;
Carlos Garnacho's avatar
Carlos Garnacho committed
726
  gint base, tip, tip_pos;
727 728 729
  gint initial_x, initial_y;
  gint tip_x, tip_y;
  gint final_x, final_y;
730
  GtkPositionType gap_side, pos;
731
  GtkAllocation allocation;
732
  gint border_radius;
733
  GtkStyleContext *context;
734
  GtkBorder margin, border, widget_margin;
Timm Bäder's avatar
Timm Bäder committed
735
  GtkStateFlags state;
Carlos Garnacho's avatar
Carlos Garnacho committed
736

Carlos Garnacho's avatar
Carlos Garnacho committed
737 738
  gtk_popover_get_pointing_to (popover, &rect);
  gtk_widget_get_allocation (widget, &allocation);
739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756

#ifdef GDK_WINDOWING_WAYLAND
  if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (widget)))
    {
      gint win_x, win_y;

      gtk_widget_translate_coordinates (priv->widget, GTK_WIDGET (priv->window),
                                        rect.x, rect.y, &rect.x, &rect.y);
      gdk_window_get_origin (gtk_widget_get_window (GTK_WIDGET (popover)),
                             &win_x, &win_y);
      rect.x -= win_x;
      rect.y -= win_y;
    }
  else
#endif
    gtk_widget_translate_coordinates (priv->widget, widget,
                                      rect.x, rect.y, &rect.x, &rect.y);

757
  get_margin (widget, &margin);
758

Carlos Garnacho's avatar
Carlos Garnacho committed
759
  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
760 761 762 763
    {
      widget_margin.left = gtk_widget_get_margin_start (widget);
      widget_margin.right = gtk_widget_get_margin_end (widget);
    }
Carlos Garnacho's avatar
Carlos Garnacho committed
764
  else
765 766 767 768
    {
      widget_margin.left = gtk_widget_get_margin_end (widget);
      widget_margin.right = gtk_widget_get_margin_start (widget);
    }
Carlos Garnacho's avatar
Carlos Garnacho committed
769

770 771
  widget_margin.top = gtk_widget_get_margin_top (widget);
  widget_margin.bottom = gtk_widget_get_margin_bottom (widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
772

773
  context = gtk_widget_get_style_context (widget);
Timm Bäder's avatar
Timm Bäder committed
774
  state = gtk_style_context_get_state (context);
775

Timm Bäder's avatar
Timm Bäder committed
776
  gtk_style_context_get_border (context, state, &border);
777
  gtk_style_context_get (context,
Timm Bäder's avatar
Timm Bäder committed
778
                         state,
779 780
                         GTK_STYLE_PROPERTY_BORDER_RADIUS, &border_radius,
                         NULL);
781
  pos = get_effective_position (popover, priv->final_position);
782

783
  if (pos == GTK_POS_BOTTOM || pos == GTK_POS_RIGHT)
Carlos Garnacho's avatar
Carlos Garnacho committed
784
    {
785 786
      tip = ((pos == GTK_POS_BOTTOM) ? border.top + widget_margin.top : border.left + widget_margin.left);
      base = tip + TAIL_HEIGHT;
787
      gap_side = (priv->final_position == GTK_POS_BOTTOM) ? GTK_POS_TOP : GTK_POS_LEFT;
Carlos Garnacho's avatar
Carlos Garnacho committed
788
    }
789
  else if (pos == GTK_POS_TOP)
Carlos Garnacho's avatar
Carlos Garnacho committed
790
    {
791 792
      base = allocation.height - TAIL_HEIGHT - border.bottom - widget_margin.bottom;
      tip = base + TAIL_HEIGHT;
793
      gap_side = GTK_POS_BOTTOM;
Carlos Garnacho's avatar
Carlos Garnacho committed
794
    }
795
  else if (pos == GTK_POS_LEFT)
Carlos Garnacho's avatar
Carlos Garnacho committed
796
    {
797 798
      base = allocation.width - TAIL_HEIGHT - border.right - widget_margin.right;
      tip = base + TAIL_HEIGHT;
799
      gap_side = GTK_POS_RIGHT;
Carlos Garnacho's avatar
Carlos Garnacho committed
800
    }
Carlos Garnacho's avatar
Carlos Garnacho committed
801 802
  else
    g_assert_not_reached ();
Carlos Garnacho's avatar
Carlos Garnacho committed
803

804
  if (POS_IS_VERTICAL (pos))
Carlos Garnacho's avatar
Carlos Garnacho committed
805
    {
806
      tip_pos = rect.x + (rect.width / 2) + widget_margin.left;
Carlos Garnacho's avatar
Carlos Garnacho committed
807
      initial_x = CLAMP (tip_pos - TAIL_GAP_WIDTH / 2,
808 809
                         border_radius + margin.left + TAIL_HEIGHT,
                         allocation.width - TAIL_GAP_WIDTH - margin.right - border_radius - TAIL_HEIGHT);
810 811
      initial_y = base;

Carlos Garnacho's avatar
Carlos Garnacho committed
812
      tip_x = CLAMP (tip_pos, 0, allocation.width);
813 814
      tip_y = tip;

Carlos Garnacho's avatar
Carlos Garnacho committed
815
      final_x = CLAMP (tip_pos + TAIL_GAP_WIDTH / 2,
816 817
                       border_radius + margin.left + TAIL_GAP_WIDTH + TAIL_HEIGHT,
                       allocation.width - margin.right - border_radius - TAIL_HEIGHT);
818
      final_y = base;
Carlos Garnacho's avatar
Carlos Garnacho committed
819 820 821
    }
  else
    {
822
      tip_pos = rect.y + (rect.height / 2) + widget_margin.top;
Carlos Garnacho's avatar
Carlos Garnacho committed
823

824
      initial_x = base;
Carlos Garnacho's avatar
Carlos Garnacho committed
825
      initial_y = CLAMP (tip_pos - TAIL_GAP_WIDTH / 2,
826 827
                         border_radius + margin.top + TAIL_HEIGHT,
                         allocation.height - TAIL_GAP_WIDTH - margin.bottom - border_radius - TAIL_HEIGHT);
828 829

      tip_x = tip;
Carlos Garnacho's avatar
Carlos Garnacho committed
830
      tip_y = CLAMP (tip_pos, 0, allocation.height);
831 832

      final_x = base;
Carlos Garnacho's avatar
Carlos Garnacho committed
833
      final_y = CLAMP (tip_pos + TAIL_GAP_WIDTH / 2,
834 835
                       border_radius + margin.top + TAIL_GAP_WIDTH + TAIL_HEIGHT,
                       allocation.height - margin.right - border_radius - TAIL_HEIGHT);
836 837 838 839 840
    }

  if (initial_x_out)
    *initial_x_out = initial_x;
  if (initial_y_out)
841
    *initial_y_out = initial_y;
842 843 844 845

  if (tip_x_out)
    *tip_x_out = tip_x;
  if (tip_y_out)
846
    *tip_y_out = tip_y;
847 848 849 850

  if (final_x_out)
    *final_x_out = final_x;
  if (final_y_out)
851
    *final_y_out = final_y;
852 853 854 855 856

  if (gap_side_out)
    *gap_side_out = gap_side;
}

857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884
static void
gtk_popover_get_rect_for_size (GtkPopover   *popover,
                               int           popover_width,
                               int           popover_height,
                               GdkRectangle *rect)
{
  GtkWidget *widget = GTK_WIDGET (popover);
  int x, y, w, h;
  GtkBorder margin;

  get_margin (widget, &margin);

  x = 0;
  y = 0;
  w = popover_width;
  h = popover_height;

  x += MAX (TAIL_HEIGHT, margin.left);
  y += MAX (TAIL_HEIGHT, margin.top);
  w -= x + MAX (TAIL_HEIGHT, margin.right);
  h -= y + MAX (TAIL_HEIGHT, margin.bottom);

  rect->x = x;
  rect->y = y;
  rect->width = w;
  rect->height = h;
}

885
static void
Carlos Garnacho's avatar
Carlos Garnacho committed
886
gtk_popover_get_rect_coords (GtkPopover *popover,
887 888 889 890
                             int        *x_out,
                             int        *y_out,
                             int        *w_out,
                             int        *h_out)
891
{
Carlos Garnacho's avatar
Carlos Garnacho committed
892
  GtkWidget *widget = GTK_WIDGET (popover);
893
  GdkRectangle rect;
894 895
  GtkAllocation allocation;

Carlos Garnacho's avatar
Carlos Garnacho committed
896
  gtk_widget_get_allocation (widget, &allocation);
897
  gtk_popover_get_rect_for_size (popover, allocation.width, allocation.height, &rect);
Carlos Garnacho's avatar
Carlos Garnacho committed
898

899 900 901 902
  *x_out = rect.x;
  *y_out = rect.y;
  *w_out = rect.width;
  *h_out = rect.height;
903 904 905
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
906 907
gtk_popover_apply_tail_path (GtkPopover *popover,
                             cairo_t    *cr)
908 909 910 911 912
{
  gint initial_x, initial_y;
  gint tip_x, tip_y;
  gint final_x, final_y;

913 914 915
  if (!popover->priv->widget)
    return;

916
  cairo_set_line_width (cr, 1);
Carlos Garnacho's avatar
Carlos Garnacho committed
917 918 919 920 921
  gtk_popover_get_gap_coords (popover,
                              &initial_x, &initial_y,
                              &tip_x, &tip_y,
                              &final_x, &final_y,
                              NULL);
922 923 924 925

  cairo_move_to (cr, initial_x, initial_y);
  cairo_line_to (cr, tip_x, tip_y);
  cairo_line_to (cr, final_x, final_y);
Carlos Garnacho's avatar
Carlos Garnacho committed
926 927 928
}

static void
929 930
gtk_popover_fill_border_path (GtkPopover *popover,
                              cairo_t    *cr)
Carlos Garnacho's avatar
Carlos Garnacho committed
931
{
932
  GtkWidget *widget = GTK_WIDGET (popover);
Carlos Garnacho's avatar
Carlos Garnacho committed
933
  GtkAllocation allocation;
934
  GtkStyleContext *context;
935
  int x, y, w, h;
936
  GtkRoundedBox box;
Carlos Garnacho's avatar
Carlos Garnacho committed
937

938 939
  context = gtk_widget_get_style_context (widget);
  gtk_widget_get_allocation (widget, &allocation);
Carlos Garnacho's avatar
Carlos Garnacho committed
940

941 942
  cairo_set_source_rgba (cr, 0, 0, 0, 1);

Carlos Garnacho's avatar
Carlos Garnacho committed
943
  gtk_popover_apply_tail_path (popover, cr);
944
  cairo_close_path (cr);
945 946
  cairo_fill (cr);

947
  gtk_popover_get_rect_coords (popover, &x, &y, &w, &h);
948

949
  _gtk_rounded_box_init_rect (&box, x, y, w, h);
950 951 952 953 954
  _gtk_rounded_box_apply_border_radius_for_style (&box,
                                                  gtk_style_context_lookup_style (context),
                                                  0);
  _gtk_rounded_box_path (&box, cr);
  cairo_fill (cr);
Carlos Garnacho's avatar
Carlos Garnacho committed
955 956 957
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
958
gtk_popover_update_shape (GtkPopover *popover)
Carlos Garnacho's avatar
Carlos Garnacho committed
959
{
960
  GtkWidget *widget = GTK_WIDGET (popover);
Carlos Garnacho's avatar
Carlos Garnacho committed
961 962 963 964 965
  cairo_surface_t *surface;
  cairo_region_t *region;
  GdkWindow *win;
  cairo_t *cr;

966 967 968 969 970
#ifdef GDK_WINDOWING_WAYLAND
  if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (widget)))
    return;
#endif

971
  win = gtk_widget_get_window (widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
972 973 974 975 976 977 978
  surface =
    gdk_window_create_similar_surface (win,
                                       CAIRO_CONTENT_COLOR_ALPHA,
                                       gdk_window_get_width (win),
                                       gdk_window_get_height (win));

  cr = cairo_create (surface);
979
  gtk_popover_fill_border_path (popover, cr);
Carlos Garnacho's avatar
Carlos Garnacho committed
980 981 982 983 984
  cairo_destroy (cr);

  region = gdk_cairo_region_create_from_surface (surface);
  cairo_surface_destroy (surface);

985
  gtk_widget_shape_combine_region (widget, region);
Carlos Garnacho's avatar
Carlos Garnacho committed
986
  cairo_region_destroy (region);
987

988
  gdk_window_set_child_shapes (gtk_widget_get_parent_window (widget));
Carlos Garnacho's avatar
Carlos Garnacho committed
989 990
}

991 992 993
static void
_gtk_popover_update_child_visible (GtkPopover *popover)
{
994 995
  GtkPopoverPrivate *priv = popover->priv;
  GtkWidget *widget = GTK_WIDGET (popover);
996
  GdkRectangle rect;
997 998 999 1000 1001
  GtkAllocation allocation;
  GtkWidget *parent;

  if (!priv->parent_scrollable)
    {
1002
      gtk_widget_set_child_visible (widget, TRUE);
1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
      return;
    }

  parent = gtk_widget_get_parent (GTK_WIDGET (priv->parent_scrollable));
  rect = priv->pointing_to;

  gtk_widget_translate_coordinates (priv->widget, parent,
                                    rect.x, rect.y, &rect.x, &rect.y);

  gtk_widget_get_allocation (GTK_WIDGET (parent), &allocation);

1014 1015
  if (rect.x + rect.width < 0 || rect.x > allocation.width ||
      rect.y + rect.height < 0 || rect.y > allocation.height)
1016
    gtk_widget_set_child_visible (widget, FALSE);
1017
  else
1018
    gtk_widget_set_child_visible (widget, TRUE);
1019 1020
}

1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
static GtkPositionType
opposite_position (GtkPositionType pos)
{
  switch (pos)
    {
    default:
    case GTK_POS_LEFT: return GTK_POS_RIGHT;
    case GTK_POS_RIGHT: return GTK_POS_LEFT;
    case GTK_POS_TOP: return GTK_POS_BOTTOM;
    case GTK_POS_BOTTOM: return GTK_POS_TOP;
    }
}

1034
void
Carlos Garnacho's avatar
Carlos Garnacho committed
1035
gtk_popover_update_position (GtkPopover *popover)
Carlos Garnacho's avatar
Carlos Garnacho committed
1036
{
1037 1038
  GtkPopoverPrivate *priv = popover->priv;
  GtkWidget *widget = GTK_WIDGET (popover);
Carlos Garnacho's avatar
Carlos Garnacho committed
1039
  GtkAllocation window_alloc;
1040
  GdkRectangle rect;
Carlos Garnacho's avatar
Carlos Garnacho committed
1041
  GtkRequisition req;
1042 1043
  GtkPositionType pos;
  gint overshoot[4];
1044
  gint i, j;
1045
  gint best;
Carlos Garnacho's avatar
Carlos Garnacho committed
1046

Carlos Garnacho's avatar
Carlos Garnacho committed
1047 1048 1049
  if (!priv->window)
    return;

1050
  gtk_widget_get_preferred_size (widget, NULL, &req);
1051
  gtk_widget_get_allocation (GTK_WIDGET (priv->window), &window_alloc);
Carlos Garnacho's avatar
Carlos Garnacho committed
1052 1053
  priv->final_position = priv->preferred_position;

1054 1055 1056 1057
  gtk_popover_get_pointing_to (popover, &rect);
  gtk_widget_translate_coordinates (priv->widget, GTK_WIDGET (priv->window),
                                    rect.x, rect.y, &rect.x, &rect.y);

1058
  pos = get_effective_position (popover, priv->preferred_position);
Carlos Garnacho's avatar
Carlos Garnacho committed
1059

1060 1061 1062 1063 1064
  overshoot[GTK_POS_TOP] = req.height - rect.y;
  overshoot[GTK_POS_BOTTOM] = rect.y + rect.height + req.height - window_alloc.height;
  overshoot[GTK_POS_LEFT] = req.width - rect.x;
  overshoot[GTK_POS_RIGHT] = rect.x + rect.width + req.width - window_alloc.width;

1065
#ifdef GDK_WINDOWING_WAYLAND
1066 1067
  if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (widget)) &&
      priv->constraint == GTK_POPOVER_CONSTRAINT_NONE)
1068 1069 1070
    {
      priv->final_position = priv->preferred_position;
    }
1071 1072 1073
  else
#endif
  if (overshoot[pos] <= 0)
1074
    {
1075
      priv->final_position = priv->preferred_position;
1076 1077 1078
    }
  else if (overshoot[opposite_position (pos)] <= 0)
    {
1079
      priv->final_position = opposite_position (priv->preferred_position);
1080 1081 1082 1083 1084 1085 1086
    }
  else
    {
      best = G_MAXINT;
      pos = 0;
      for (i = 0; i < 4; i++)
        {
1087 1088
          j = get_effective_position (popover, i);
          if (overshoot[j] < best)
1089 1090
            {
              pos = i;
1091
              best = overshoot[j];
1092 1093 1094 1095
            }
        }
      priv->final_position = pos;
    }
Carlos Garnacho's avatar
Carlos Garnacho committed
1096

1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112
  switch (priv->final_position)
    {
    case GTK_POS_TOP:
      rect.y += priv->transition_diff;
      break;
    case GTK_POS_BOTTOM:
      rect.y -= priv->transition_diff;
      break;
    case GTK_POS_LEFT:
      rect.x += priv->transition_diff;
      break;
    case GTK_POS_RIGHT:
      rect.x -= priv->transition_diff;
      break;
    }

1113
  _gtk_window_set_popover_position (priv->window, widget,
1114
                                    priv->final_position, &rect);
Carlos Garnacho's avatar
Carlos Garnacho committed
1115

Carlos Garnacho's avatar
Carlos Garnacho committed
1116 1117
  if (priv->final_position != priv->current_position)
    {
1118
      if (gtk_widget_is_drawable (widget))
Carlos Garnacho's avatar
Carlos Garnacho committed
1119
        gtk_popover_update_shape (popover);
1120

Carlos Garnacho's avatar
Carlos Garnacho committed
1121
      priv->current_position = priv->final_position;
1122
      gtk_popover_invalidate_borders (popover);
Carlos Garnacho's avatar
Carlos Garnacho committed
1123
    }
1124 1125

  _gtk_popover_update_child_visible (popover);
Carlos Garnacho's avatar
Carlos Garnacho committed
1126 1127 1128
}

static gboolean
Carlos Garnacho's avatar
Carlos Garnacho committed
1129 1130
gtk_popover_draw (GtkWidget *widget,
                  cairo_t   *cr)
Carlos Garnacho's avatar
Carlos Garnacho committed
1131
{
1132
  GtkPopover *popover = GTK_POPOVER (widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
1133 1134 1135
  GtkStyleContext *context;
  GtkAllocation allocation;
  GtkWidget *child;
1136 1137
  GtkBorder border;
  GdkRGBA border_color;
1138
  int rect_x, rect_y, rect_w, rect_h;
1139 1140 1141 1142
  gint initial_x, initial_y, final_x, final_y;
  gint gap_start, gap_end;
  GtkPositionType gap_side;
  GtkStateFlags state;
Carlos Garnacho's avatar
Carlos Garnacho committed
1143 1144

  context = gtk_widget_get_style_context (widget);
1145

1146
  state = gtk_style_context_get_state (context);
Carlos Garnacho's avatar
Carlos Garnacho committed
1147 1148
  gtk_widget_get_allocation (widget, &allocation);

1149
  gtk_style_context_get_border (context, state, &border);
1150
  gtk_popover_get_rect_coords (popover,
1151 1152
                               &rect_x, &rect_y,
                               &rect_w, &rect_h);
1153 1154 1155

  /* Render the rect background */
  gtk_render_background (context, cr,
1156 1157
                         rect_x, rect_y,
                         rect_w, rect_h);
1158

1159
  if (popover->priv->widget)
1160
    {
1161 1162 1163 1164 1165 1166 1167 1168
      gtk_popover_get_gap_coords (popover,
                                  &initial_x, &initial_y,
                                  NULL, NULL,
                                  &final_x, &final_y,
                                  &gap_side);

      if (POS_IS_VERTICAL (gap_side))
        {
1169 1170
          gap_start = initial_x - rect_x;
          gap_end = final_x - rect_x;
1171 1172 1173
        }
      else
        {
1174 1175
          gap_start = initial_y - rect_y;
          gap_end = final_y - rect_y;
1176 1177 1178 1179
        }

      /* Now render the frame, without the gap for the arrow tip */
      gtk_render_frame_gap (context, cr,
1180 1181
                            rect_x, rect_y,
                            rect_w, rect_h,
1182 1183
                            gap_side,
                            gap_start, gap_end);
1184 1185 1186
    }
  else
    {
1187
      gtk_render_frame (context, cr,
1188 1189
                        rect_x, rect_y,
                        rect_w, rect_h);
1190
    }
1191 1192 1193 1194

  /* Clip to the arrow shape */
  cairo_save (cr);

1195
  gtk_popover_apply_tail_path (popover, cr);
1196 1197 1198 1199
  cairo_clip (cr);

  /* Render the arrow background */
  gtk_render_background (context, cr,
1200
                         0, 0,
Carlos Garnacho's avatar
Carlos Garnacho committed
1201
                         allocation.width, allocation.height);
1202

1203 1204 1205
  /* Render the border of the arrow tip */
  if (border.bottom > 0)
    {
1206
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1207
      gtk_style_context_get_border_color (context, state, &border_color);
1208
G_GNUC_END_IGNORE_DEPRECATIONS
1209

1210
      gtk_popover_apply_tail_path (popover, cr);
1211 1212
      gdk_cairo_set_source_rgba (cr, &border_color);

1213
      cairo_set_line_width (cr, border.bottom + 1);
1214 1215 1216 1217 1218
      cairo_stroke (cr);
    }

  /* We're done */
  cairo_restore (cr);
1219

Carlos Garnacho's avatar
Carlos Garnacho committed
1220 1221 1222 1223 1224
  child = gtk_bin_get_child (GTK_BIN (widget));

  if (child)
    gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr);

Timm Bäder's avatar
Timm Bäder committed
1225
  return GDK_EVENT_PROPAGATE;
Carlos Garnacho's avatar
Carlos Garnacho committed
1226 1227
}

1228 1229 1230 1231 1232 1233
static void
get_padding_and_border (GtkWidget *widget,
                        GtkBorder *border)
{
  GtkStyleContext *context;
  GtkStateFlags state;
1234
  gint border_width;
1235 1236 1237
  GtkBorder tmp;

  context = gtk_widget_get_style_context (widget);
1238
  state = gtk_style_context_get_state (context);
1239

1240 1241
  border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));

1242 1243
  gtk_style_context_get_padding (context, state, border);
  gtk_style_context_get_border (context, state, &tmp);
1244 1245 1246 1247
  border->top += tmp.top + border_width;
  border->right += tmp.right + border_width;
  border->bottom += tmp.bottom + border_width;
  border->left += tmp.left + border_width;
1248 1249
}

1250 1251 1252 1253 1254 1255 1256 1257
static gint
get_border_radius (GtkWidget *widget)
{
  GtkStyleContext *context;
  GtkStateFlags state;
  gint border_radius;

  context = gtk_widget_get_style_context (widget);
1258
  state = gtk_style_context_get_state (context);
1259 1260 1261 1262 1263 1264 1265 1266 1267 1268
  gtk_style_context_get (context, state,
                         GTK_STYLE_PROPERTY_BORDER_RADIUS, &border_radius,
                         NULL);
  return border_radius;
}

static gint
get_minimal_size (GtkPopover     *popover,
                  GtkOrientation  orientation)
{
1269
  GtkPopoverPrivate *priv = popover->priv;
1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282
  GtkPositionType pos;
  gint minimal_size;

  minimal_size = 2 * get_border_radius (GTK_WIDGET (popover));
  pos = get_effective_position (popover, priv->preferred_position);

  if ((orientation == GTK_ORIENTATION_HORIZONTAL && POS_IS_VERTICAL (pos)) ||
      (orientation == GTK_ORIENTATION_VERTICAL && !POS_IS_VERTICAL (pos)))
    minimal_size += TAIL_GAP_WIDTH;

  return minimal_size;
}

Carlos Garnacho's avatar
Carlos Garnacho committed
1283
static void
Carlos Garnacho's avatar
Carlos Garnacho committed
1284 1285 1286
gtk_popover_get_preferred_width (GtkWidget *widget,
                                 gint      *minimum_width,
                                 gint      *natural_width)
Carlos Garnacho's avatar
Carlos Garnacho committed
1287
{
1288
  GtkPopover *popover = GTK_POPOVER (widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
1289
  GtkWidget *child;
1290
  gint min, nat, extra, minimal_size;
1291
  GtkBorder border, margin;
Carlos Garnacho's avatar
Carlos Garnacho committed
1292 1293 1294 1295 1296 1297 1298

  child = gtk_bin_get_child (GTK_BIN (widget));
  min = nat = 0;

  if (child)
    gtk_widget_get_preferred_width (child, &min, &nat);

1299
  get_padding_and_border (widget, &border);
1300
  get_margin (widget, &margin);
1301
  minimal_size = get_minimal_size (popover, GTK_ORIENTATION_HORIZONTAL);
1302

1303 1304
  min = MAX (min, minimal_size) + border.left + border.right;
  nat = MAX (nat, minimal_size) + border.left + border.right;
1305
  extra = MAX (TAIL_HEIGHT, margin.left) + MAX (TAIL_HEIGHT, margin.right);
1306 1307 1308

  min += extra;
  nat += extra;
Carlos Garnacho's avatar
Carlos Garnacho committed
1309

1310 1311
  *minimum_width = min;
  *natural_width = nat;
Carlos Garnacho's avatar
Carlos Garnacho committed
1312 1313
}

1314 1315 1316 1317 1318 1319
static void
gtk_popover_get_preferred_width_for_height (GtkWidget *widget,
                                            gint       height,
                                            gint      *minimum_width,
                                            gint      *natural_width)
{
1320
  GtkPopover *popover = GTK_POPOVER (widget);
1321
  GtkWidget *child;
1322
  GdkRectangle child_rect;
1323
  gint min, nat, extra, minimal_size;
1324
  gint child_height;
1325
  GtkBorder border, margin;
1326 1327 1328 1329

  child = gtk_bin_get_child (GTK_BIN (widget));
  min = nat = 0;

1330 1331
  gtk_popover_get_rect_for_size (popover, 0, height, &child_rect);
  child_height = child_rect.height;
1332 1333 1334


  get_padding_and_border (widget, &border);
1335
  get_margin (widget, &margin);
1336
  child_height -= border.top + border.bottom;
1337
  minimal_size = get_minimal_size (popover, GTK_ORIENTATION_HORIZONTAL);
1338 1339 1340 1341

  if (child)
    gtk_widget_get_preferred_width_for_height (child, child_height, &min, &nat);

1342 1343
  min = MAX (min, minimal_size) + border.left + border.right;
  nat = MAX (nat, minimal_size) + border.left + border.right;
1344
  extra = MAX (TAIL_HEIGHT, margin.left) + MAX (TAIL_HEIGHT, margin.right);
1345 1346 1347

  min += extra;
  nat += extra;
1348

1349 1350
  *minimum_width = min;
  *natural_width = nat;
1351 1352
}

Carlos Garnacho's avatar
Carlos Garnacho committed
1353
static void
Carlos Garnacho's avatar
Carlos Garnacho committed
1354 1355 1356
gtk_popover_get_preferred_height (GtkWidget *widget,
                                  gint      *minimum_height,
                                  gint      *natural_height)
Carlos Garnacho's avatar
Carlos Garnacho committed
1357
{
1358
  GtkPopover *popover = GTK_POPOVER (widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
1359
  GtkWidget *child;
1360
  gint min, nat, extra, minimal_size;
1361
  GtkBorder border, margin;
Carlos Garnacho's avatar
Carlos Garnacho committed
1362 1363 1364 1365 1366

  child = gtk_bin_get_child (GTK_BIN (widget));
  min = nat = 0;

  if (child)
1367 1368 1369
    gtk_widget_get_preferred_height (child, &min, &nat);

  get_padding_and_border (widget, &