gtkpopover.c 73.2 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 179 180
  guint transitions_enabled : 1;
  guint state               : 2;
  guint visible             : 1;
  gint transition_diff;
  guint tick_id;
181 182 183

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

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

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

G_DEFINE_TYPE_WITH_PRIVATE (GtkPopover, gtk_popover, GTK_TYPE_BIN)
Carlos Garnacho's avatar
Carlos Garnacho committed
199 200

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
201
gtk_popover_init (GtkPopover *popover)
Carlos Garnacho's avatar
Carlos Garnacho committed
202 203
{
  GtkWidget *widget;
204
  GtkStyleContext *context;
Carlos Garnacho's avatar
Carlos Garnacho committed
205

Carlos Garnacho's avatar
Carlos Garnacho committed
206
  widget = GTK_WIDGET (popover);
207
  gtk_widget_set_has_window (widget, TRUE);
208 209
  popover->priv = gtk_popover_get_instance_private (popover);
  popover->priv->modal = TRUE;
210 211
  popover->priv->tick_id = 0;
  popover->priv->transitions_enabled = TRUE;
212
  popover->priv->preferred_position = GTK_POS_TOP;
213
  popover->priv->constraint = GTK_POPOVER_CONSTRAINT_WINDOW;
214 215 216

  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
217 218 219
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
220 221 222 223
gtk_popover_set_property (GObject      *object,
                          guint         prop_id,
                          const GValue *value,
                          GParamSpec   *pspec)
Carlos Garnacho's avatar
Carlos Garnacho committed
224 225 226 227
{
  switch (prop_id)
    {
    case PROP_RELATIVE_TO:
Carlos Garnacho's avatar
Carlos Garnacho committed
228 229
      gtk_popover_set_relative_to (GTK_POPOVER (object),
                                   g_value_get_object (value));
Carlos Garnacho's avatar
Carlos Garnacho committed
230 231
      break;
    case PROP_POINTING_TO:
Carlos Garnacho's avatar
Carlos Garnacho committed
232 233
      gtk_popover_set_pointing_to (GTK_POPOVER (object),
                                   g_value_get_boxed (value));
Carlos Garnacho's avatar
Carlos Garnacho committed
234 235
      break;
    case PROP_POSITION:
Carlos Garnacho's avatar
Carlos Garnacho committed
236 237
      gtk_popover_set_position (GTK_POPOVER (object),
                                g_value_get_enum (value));
Carlos Garnacho's avatar
Carlos Garnacho committed
238
      break;
239 240 241 242
    case PROP_MODAL:
      gtk_popover_set_modal (GTK_POPOVER (object),
                             g_value_get_boolean (value));
      break;
243 244 245 246
    case PROP_TRANSITIONS_ENABLED:
      gtk_popover_set_transitions_enabled (GTK_POPOVER (object),
                                           g_value_get_boolean (value));
      break;
247 248 249 250
    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
251 252 253 254 255 256
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
257 258 259 260
gtk_popover_get_property (GObject    *object,
                          guint       prop_id,
                          GValue     *value,
                          GParamSpec *pspec)
Carlos Garnacho's avatar
Carlos Garnacho committed
261
{
Carlos Garnacho's avatar
Carlos Garnacho committed
262
  GtkPopoverPrivate *priv = GTK_POPOVER (object)->priv;
Carlos Garnacho's avatar
Carlos Garnacho committed
263 264 265 266

  switch (prop_id)
    {
    case PROP_RELATIVE_TO:
267
      g_value_set_object (value, priv->widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
268 269 270 271 272 273 274
      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;
275 276 277
    case PROP_MODAL:
      g_value_set_boolean (value, priv->modal);
      break;
278 279 280
    case PROP_TRANSITIONS_ENABLED:
      g_value_set_boolean (value, priv->transitions_enabled);
      break;
281 282 283
    case PROP_CONSTRAIN_TO:
      g_value_set_enum (value, priv->constraint);
      break;
Carlos Garnacho's avatar
Carlos Garnacho committed
284 285 286 287 288
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

289 290 291 292 293
static gboolean
transitions_enabled (GtkPopover *popover)
{
  GtkPopoverPrivate *priv = popover->priv;

294 295
  return gtk_settings_get_enable_animations (gtk_widget_get_settings (GTK_WIDGET (popover))) &&
         priv->transitions_enabled;
296 297
}

Carlos Garnacho's avatar
Carlos Garnacho committed
298
static void
Carlos Garnacho's avatar
Carlos Garnacho committed
299
gtk_popover_finalize (GObject *object)
Carlos Garnacho's avatar
Carlos Garnacho committed
300
{
Carlos Garnacho's avatar
Carlos Garnacho committed
301
  GtkPopover *popover = GTK_POPOVER (object);
302 303 304 305
  GtkPopoverPrivate *priv = popover->priv;

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

Carlos Garnacho's avatar
Carlos Garnacho committed
307
  G_OBJECT_CLASS (gtk_popover_parent_class)->finalize (object);
Carlos Garnacho's avatar
Carlos Garnacho committed
308 309
}

310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
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);
}

328
static void
Carlos Garnacho's avatar
Carlos Garnacho committed
329
gtk_popover_dispose (GObject *object)
330
{
Carlos Garnacho's avatar
Carlos Garnacho committed
331 332
  GtkPopover *popover = GTK_POPOVER (object);
  GtkPopoverPrivate *priv = popover->priv;
333

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

337
  if (priv->window)
338 339 340 341
    {
      g_signal_handlers_disconnect_by_data (priv->window, popover);
      _gtk_window_remove_popover (priv->window, GTK_WIDGET (object));
    }
342 343

  priv->window = NULL;
344 345 346

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

348
  popover_unset_prev_focus (popover);
349

350 351
  g_clear_object (&priv->default_widget);

Carlos Garnacho's avatar
Carlos Garnacho committed
352
  G_OBJECT_CLASS (gtk_popover_parent_class)->dispose (object);
353 354 355
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
356
gtk_popover_realize (GtkWidget *widget)
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
{
  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) |
374
    GDK_POINTER_MOTION_MASK |
375 376 377 378 379 380 381 382 383 384 385 386 387 388
    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);
}

389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
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
406
      if (focus == NULL || !gtk_widget_is_ancestor (focus, GTK_WIDGET (popover)))
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
        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;
}

435 436 437 438 439 440 441
static void
window_set_focus (GtkWindow  *window,
                  GtkWidget  *widget,
                  GtkPopover *popover)
{
  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);

442
  if (priv->modal && widget &&
443
      gtk_widget_is_drawable (GTK_WIDGET (popover)) &&
444
      !gtk_widget_is_ancestor (widget, GTK_WIDGET (popover)))
445 446 447 448 449 450 451 452
    {
      GtkWidget *grab_widget;

      grab_widget = gtk_grab_get_current ();

      if (!grab_widget || !GTK_IS_POPOVER (grab_widget))
        gtk_widget_hide (GTK_WIDGET (popover));
    }
453 454
}

455 456 457 458 459 460 461
static void
prev_focus_unmap_cb (GtkWidget  *widget,
                     GtkPopover *popover)
{
  popover_unset_prev_focus (popover);
}

462 463 464 465
static void
gtk_popover_apply_modality (GtkPopover *popover,
                            gboolean    modal)
{
466
  GtkPopoverPrivate *priv = popover->priv;
467

468 469 470
  if (!priv->window)
    return;

471 472 473 474 475
  if (modal)
    {
      GtkWidget *prev_focus;

      prev_focus = gtk_window_get_focus (priv->window);
476 477
      priv->prev_focus_widget = prev_focus;
      if (priv->prev_focus_widget)
478 479 480 481 482 483
        {
          priv->prev_focus_unmap_id =
            g_signal_connect (prev_focus, "unmap",
                              G_CALLBACK (prev_focus_unmap_cb), popover);
          g_object_ref (prev_focus);
        }
484
      gtk_grab_add (GTK_WIDGET (popover));
485
      gtk_window_set_focus (priv->window, NULL);
486
      gtk_widget_grab_focus (GTK_WIDGET (popover));
487 488 489 490 491

      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);
492 493
      g_signal_connect (priv->window, "set-focus",
                        G_CALLBACK (window_set_focus), popover);
494 495 496
    }
  else
    {
497
      g_signal_handlers_disconnect_by_data (priv->window, popover);
498 499
      gtk_grab_remove (GTK_WIDGET (popover));

500 501 502 503
      /* 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);
504
      else if (priv->window)
505
        gtk_widget_grab_focus (GTK_WIDGET (priv->window));
506

507
      popover_unset_prev_focus (popover);
508 509 510
    }
}

511 512 513 514 515 516 517 518 519
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;

520 521 522
  gtk_progress_tracker_advance_frame (&priv->tracker,
                                      gdk_frame_clock_get_frame_time (frame_clock));
  t = gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE);
523 524 525 526 527 528 529 530 531 532 533 534

  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);
    }

535
  gtk_popover_update_position (popover);
536

537
  if (gtk_progress_tracker_get_state (&priv->tracker) == GTK_PROGRESS_STATE_AFTER)
538 539 540 541 542 543 544 545 546 547 548
    {
      if (priv->state == STATE_SHOWING)
        {
          gtk_popover_set_state (popover, STATE_SHOWN);

          if (!priv->visible)
            gtk_popover_set_state (popover, STATE_HIDING);
        }
      else
        gtk_popover_set_state (popover, STATE_HIDDEN);

549
      return FALSE;
550 551
    }
  else
552
    return TRUE;
553 554
}

555
static void
556
gtk_popover_start_transition (GtkPopover *popover)
557
{
558 559 560 561 562
  GtkPopoverPrivate *priv = popover->priv;

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

563
  gtk_progress_tracker_start (&priv->tracker, TRANSITION_DURATION, 0, 1.0);
564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584
  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;
585

586 587 588 589
  if (state == STATE_SHOWING || state == STATE_HIDING)
    gtk_popover_start_transition (popover);
  else
    {
590
      if (priv->tick_id)
591 592 593 594 595 596 597 598 599 600 601 602
        {
          gtk_widget_remove_tick_callback (GTK_WIDGET (popover), priv->tick_id);
          priv->tick_id = 0;
        }

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

static void
gtk_popover_map (GtkWidget *widget)
{
603 604 605
  GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;

  priv->prev_default = gtk_window_get_default_widget (priv->window);
606 607
  if (priv->prev_default)
    g_object_ref (priv->prev_default);
608

Carlos Garnacho's avatar
Carlos Garnacho committed
609
  GTK_WIDGET_CLASS (gtk_popover_parent_class)->map (widget);
610

611
  gdk_window_show (gtk_widget_get_window (widget));
Carlos Garnacho's avatar
Carlos Garnacho committed
612
  gtk_popover_update_position (GTK_POPOVER (widget));
613 614

  gtk_window_set_default (priv->window, priv->default_widget);
615 616 617
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
618
gtk_popover_unmap (GtkWidget *widget)
619
{
620
  GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
621

622
  priv->button_pressed = FALSE;
623

624
  gdk_window_hide (gtk_widget_get_window (widget));
Carlos Garnacho's avatar
Carlos Garnacho committed
625
  GTK_WIDGET_CLASS (gtk_popover_parent_class)->unmap (widget);
626

627 628
  if (gtk_window_get_default_widget (priv->window) == priv->default_widget)
    gtk_window_set_default (priv->window, priv->prev_default);
629
  g_clear_object (&priv->prev_default);
630 631
}

632 633 634 635 636 637 638 639 640 641 642 643 644 645 646
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;
}

647 648 649 650 651 652 653
static void
get_margin (GtkWidget *widget,
            GtkBorder *border)
{
  GtkStyleContext *context;

  context = gtk_widget_get_style_context (widget);
654 655 656
  gtk_style_context_get_margin (context,
                                gtk_style_context_get_state (context),
                                border);
657 658
}

Carlos Garnacho's avatar
Carlos Garnacho committed
659
static void
Carlos Garnacho's avatar
Carlos Garnacho committed
660 661 662 663 664 665 666 667
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
668
{
Carlos Garnacho's avatar
Carlos Garnacho committed
669 670
  GtkWidget *widget = GTK_WIDGET (popover);
  GtkPopoverPrivate *priv = popover->priv;
671
  GdkRectangle rect;
Carlos Garnacho's avatar
Carlos Garnacho committed
672
  gint base, tip, tip_pos;
673 674 675
  gint initial_x, initial_y;
  gint tip_x, tip_y;
  gint final_x, final_y;
676
  GtkPositionType gap_side, pos;
677
  GtkAllocation allocation;
678
  gint border_radius;
679 680
  GtkStyleContext *context;
  GtkBorder margin, border;
Timm Bäder's avatar
Timm Bäder committed
681
  GtkStateFlags state;
Carlos Garnacho's avatar
Carlos Garnacho committed
682

Carlos Garnacho's avatar
Carlos Garnacho committed
683 684
  gtk_popover_get_pointing_to (popover, &rect);
  gtk_widget_get_allocation (widget, &allocation);
685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702

#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);

703
  get_margin (widget, &margin);
704

Carlos Garnacho's avatar
Carlos Garnacho committed
705 706 707 708 709 710 711
  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
    rect.x += gtk_widget_get_margin_start (widget);
  else
    rect.x += gtk_widget_get_margin_end (widget);

  rect.y += gtk_widget_get_margin_top (widget);

712
  context = gtk_widget_get_style_context (widget);
Timm Bäder's avatar
Timm Bäder committed
713
  state = gtk_style_context_get_state (context);
714

Timm Bäder's avatar
Timm Bäder committed
715
  gtk_style_context_get_border (context, state, &border);
716
  gtk_style_context_get (context,
Timm Bäder's avatar
Timm Bäder committed
717
                         state,
718 719
                         GTK_STYLE_PROPERTY_BORDER_RADIUS, &border_radius,
                         NULL);
720
  pos = get_effective_position (popover, priv->final_position);
721

722
  if (pos == GTK_POS_BOTTOM || pos == GTK_POS_RIGHT)
Carlos Garnacho's avatar
Carlos Garnacho committed
723
    {
724
      base = TAIL_HEIGHT + ((pos == GTK_POS_BOTTOM) ? border.top : border.left);
Carlos Garnacho's avatar
Carlos Garnacho committed
725
      tip = 0;
726
      gap_side = (priv->final_position == GTK_POS_BOTTOM) ? GTK_POS_TOP : GTK_POS_LEFT;
Carlos Garnacho's avatar
Carlos Garnacho committed
727
    }
728
  else if (pos == GTK_POS_TOP)
Carlos Garnacho's avatar
Carlos Garnacho committed
729
    {
730
      base = allocation.height - TAIL_HEIGHT - border.bottom;
731 732
      tip = allocation.height;
      gap_side = GTK_POS_BOTTOM;
Carlos Garnacho's avatar
Carlos Garnacho committed
733
    }
734
  else if (pos == GTK_POS_LEFT)
Carlos Garnacho's avatar
Carlos Garnacho committed
735
    {
736
      base = allocation.width - TAIL_HEIGHT - border.right;
737 738
      tip = allocation.width;
      gap_side = GTK_POS_RIGHT;
Carlos Garnacho's avatar
Carlos Garnacho committed
739
    }
Carlos Garnacho's avatar
Carlos Garnacho committed
740 741
  else
    g_assert_not_reached ();
Carlos Garnacho's avatar
Carlos Garnacho committed
742

743
  if (POS_IS_VERTICAL (pos))
Carlos Garnacho's avatar
Carlos Garnacho committed
744
    {
Carlos Garnacho's avatar
Carlos Garnacho committed
745 746
      tip_pos = rect.x + (rect.width / 2);
      initial_x = CLAMP (tip_pos - TAIL_GAP_WIDTH / 2,
747 748
                         border_radius + margin.left + TAIL_HEIGHT,
                         allocation.width - TAIL_GAP_WIDTH - margin.right - border_radius - TAIL_HEIGHT);
749 750
      initial_y = base;

Carlos Garnacho's avatar
Carlos Garnacho committed
751
      tip_x = CLAMP (tip_pos, 0, allocation.width);
752 753
      tip_y = tip;

Carlos Garnacho's avatar
Carlos Garnacho committed
754
      final_x = CLAMP (tip_pos + TAIL_GAP_WIDTH / 2,
755 756
                       border_radius + margin.left + TAIL_GAP_WIDTH + TAIL_HEIGHT,
                       allocation.width - margin.right - border_radius - TAIL_HEIGHT);
757
      final_y = base;
Carlos Garnacho's avatar
Carlos Garnacho committed
758 759 760
    }
  else
    {
Carlos Garnacho's avatar
Carlos Garnacho committed
761 762
      tip_pos = rect.y + (rect.height / 2);

763
      initial_x = base;
Carlos Garnacho's avatar
Carlos Garnacho committed
764
      initial_y = CLAMP (tip_pos - TAIL_GAP_WIDTH / 2,
765 766
                         border_radius + margin.top + TAIL_HEIGHT,
                         allocation.height - TAIL_GAP_WIDTH - margin.bottom - border_radius - TAIL_HEIGHT);
767 768

      tip_x = tip;
Carlos Garnacho's avatar
Carlos Garnacho committed
769
      tip_y = CLAMP (tip_pos, 0, allocation.height);
770 771

      final_x = base;
Carlos Garnacho's avatar
Carlos Garnacho committed
772
      final_y = CLAMP (tip_pos + TAIL_GAP_WIDTH / 2,
773 774
                       border_radius + margin.top + TAIL_GAP_WIDTH + TAIL_HEIGHT,
                       allocation.height - margin.right - border_radius - TAIL_HEIGHT);
775 776 777 778 779
    }

  if (initial_x_out)
    *initial_x_out = initial_x;
  if (initial_y_out)
780
    *initial_y_out = initial_y;
781 782 783 784

  if (tip_x_out)
    *tip_x_out = tip_x;
  if (tip_y_out)
785
    *tip_y_out = tip_y;
786 787 788 789

  if (final_x_out)
    *final_x_out = final_x;
  if (final_y_out)
790
    *final_y_out = final_y;
791 792 793 794 795 796

  if (gap_side_out)
    *gap_side_out = gap_side;
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
797 798 799 800 801
gtk_popover_get_rect_coords (GtkPopover *popover,
                             gint       *x1_out,
                             gint       *y1_out,
                             gint       *x2_out,
                             gint       *y2_out)
802
{
Carlos Garnacho's avatar
Carlos Garnacho committed
803
  GtkWidget *widget = GTK_WIDGET (popover);
804
  GtkAllocation allocation;
Carlos Garnacho's avatar
Carlos Garnacho committed
805
  gint x1, x2, y1, y2;
806
  GtkBorder margin;
807

Carlos Garnacho's avatar
Carlos Garnacho committed
808
  gtk_widget_get_allocation (widget, &allocation);
809
  get_margin (widget, &margin);
Carlos Garnacho's avatar
Carlos Garnacho committed
810 811 812 813 814 815 816 817 818 819 820

  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
    x1 = gtk_widget_get_margin_start (widget);
  else
    x1 = gtk_widget_get_margin_end (widget);

  y1 = gtk_widget_get_margin_top (widget);
  x2 = allocation.width -
    gtk_widget_get_margin_end (widget) + x1;
  y2 = allocation.height -
    gtk_widget_get_margin_bottom (widget) + y1;
821

822 823 824 825
  x1 += MAX (TAIL_HEIGHT, margin.left);
  y1 += MAX (TAIL_HEIGHT, margin.top);
  x2 -= MAX (TAIL_HEIGHT, margin.right);
  y2 -= MAX (TAIL_HEIGHT, margin.bottom);
826 827 828 829

  if (x1_out)
    *x1_out = x1;
  if (y1_out)
830
    *y1_out = y1;
831 832 833
  if (x2_out)
    *x2_out = x2;
  if (y2_out)
834
    *y2_out = y2;
835 836 837
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
838 839
gtk_popover_apply_tail_path (GtkPopover *popover,
                             cairo_t    *cr)
840 841 842 843 844
{
  gint initial_x, initial_y;
  gint tip_x, tip_y;
  gint final_x, final_y;

845 846 847
  if (!popover->priv->widget)
    return;

848
  cairo_set_line_width (cr, 1);
Carlos Garnacho's avatar
Carlos Garnacho committed
849 850 851 852 853
  gtk_popover_get_gap_coords (popover,
                              &initial_x, &initial_y,
                              &tip_x, &tip_y,
                              &final_x, &final_y,
                              NULL);
854 855 856 857

  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
858 859 860
}

static void
861 862
gtk_popover_fill_border_path (GtkPopover *popover,
                              cairo_t    *cr)
Carlos Garnacho's avatar
Carlos Garnacho committed
863
{
864
  GtkWidget *widget = GTK_WIDGET (popover);
Carlos Garnacho's avatar
Carlos Garnacho committed
865
  GtkAllocation allocation;
866
  GtkStyleContext *context;
867
  gint x1, y1, x2, y2;
868
  GtkRoundedBox box;
Carlos Garnacho's avatar
Carlos Garnacho committed
869

870 871
  context = gtk_widget_get_style_context (widget);
  gtk_widget_get_allocation (widget, &allocation);
Carlos Garnacho's avatar
Carlos Garnacho committed
872

873 874
  cairo_set_source_rgba (cr, 0, 0, 0, 1);

Carlos Garnacho's avatar
Carlos Garnacho committed
875
  gtk_popover_apply_tail_path (popover, cr);
876
  cairo_close_path (cr);
877 878 879
  cairo_fill (cr);

  gtk_popover_get_rect_coords (popover, &x1, &y1, &x2, &y2);
880 881 882 883 884 885 886

  _gtk_rounded_box_init_rect (&box, x1, y1, x2 - x1, y2 - y1);
  _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
887 888 889
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
890
gtk_popover_update_shape (GtkPopover *popover)
Carlos Garnacho's avatar
Carlos Garnacho committed
891
{
892
  GtkWidget *widget = GTK_WIDGET (popover);
Carlos Garnacho's avatar
Carlos Garnacho committed
893 894 895 896 897
  cairo_surface_t *surface;
  cairo_region_t *region;
  GdkWindow *win;
  cairo_t *cr;

898 899 900 901 902
#ifdef GDK_WINDOWING_WAYLAND
  if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (widget)))
    return;
#endif

903
  win = gtk_widget_get_window (widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
904 905 906 907 908 909 910
  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);
911
  gtk_popover_fill_border_path (popover, cr);
Carlos Garnacho's avatar
Carlos Garnacho committed
912 913 914 915 916
  cairo_destroy (cr);

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

917
  gtk_widget_shape_combine_region (widget, region);
Carlos Garnacho's avatar
Carlos Garnacho committed
918
  cairo_region_destroy (region);
919

920
  gdk_window_set_child_shapes (gtk_widget_get_parent_window (widget));
Carlos Garnacho's avatar
Carlos Garnacho committed
921 922
}

923 924 925
static void
_gtk_popover_update_child_visible (GtkPopover *popover)
{
926 927
  GtkPopoverPrivate *priv = popover->priv;
  GtkWidget *widget = GTK_WIDGET (popover);
928
  GdkRectangle rect;
929 930 931 932 933
  GtkAllocation allocation;
  GtkWidget *parent;

  if (!priv->parent_scrollable)
    {
934
      gtk_widget_set_child_visible (widget, TRUE);
935 936 937 938 939 940 941 942 943 944 945
      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);

946 947
  if (rect.x + rect.width < 0 || rect.x > allocation.width ||
      rect.y + rect.height < 0 || rect.y > allocation.height)
948
    gtk_widget_set_child_visible (widget, FALSE);
949
  else
950
    gtk_widget_set_child_visible (widget, TRUE);
951 952
}

953 954 955 956 957 958 959 960 961 962 963 964 965
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;
    }
}

966
void
Carlos Garnacho's avatar
Carlos Garnacho committed
967
gtk_popover_update_position (GtkPopover *popover)
Carlos Garnacho's avatar
Carlos Garnacho committed
968
{
969 970
  GtkPopoverPrivate *priv = popover->priv;
  GtkWidget *widget = GTK_WIDGET (popover);
Carlos Garnacho's avatar
Carlos Garnacho committed
971
  GtkAllocation window_alloc;
972
  GdkRectangle rect;
Carlos Garnacho's avatar
Carlos Garnacho committed
973
  GtkRequisition req;
974 975
  GtkPositionType pos;
  gint overshoot[4];
976
  gint i, j;
977
  gint best;
Carlos Garnacho's avatar
Carlos Garnacho committed
978

Carlos Garnacho's avatar
Carlos Garnacho committed
979 980 981
  if (!priv->window)
    return;

982
  gtk_widget_get_preferred_size (widget, NULL, &req);
983
  gtk_widget_get_allocation (GTK_WIDGET (priv->window), &window_alloc);
Carlos Garnacho's avatar
Carlos Garnacho committed
984 985
  priv->final_position = priv->preferred_position;

986 987 988 989
  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);

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

992 993 994 995 996
  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;

997
#ifdef GDK_WINDOWING_WAYLAND
998 999
  if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (widget)) &&
      priv->constraint == GTK_POPOVER_CONSTRAINT_NONE)
1000 1001 1002
    {
      priv->final_position = priv->preferred_position;
    }
1003 1004 1005
  else
#endif
  if (overshoot[pos] <= 0)
1006
    {
1007
      priv->final_position = priv->preferred_position;
1008 1009 1010
    }
  else if (overshoot[opposite_position (pos)] <= 0)
    {
1011
      priv->final_position = opposite_position (priv->preferred_position);
1012 1013 1014 1015 1016 1017 1018
    }
  else
    {
      best = G_MAXINT;
      pos = 0;
      for (i = 0; i < 4; i++)
        {
1019 1020
          j = get_effective_position (popover, i);
          if (overshoot[j] < best)
1021 1022
            {
              pos = i;
1023
              best = overshoot[j];
1024 1025 1026 1027
            }
        }
      priv->final_position = pos;
    }
Carlos Garnacho's avatar
Carlos Garnacho committed
1028

1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044
  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;
    }

1045
  _gtk_window_set_popover_position (priv->window, widget,
1046
                                    priv->final_position, &rect);
Carlos Garnacho's avatar
Carlos Garnacho committed
1047

Carlos Garnacho's avatar
Carlos Garnacho committed
1048 1049
  if (priv->final_position != priv->current_position)
    {
1050
      if (gtk_widget_is_drawable (widget))
Carlos Garnacho's avatar
Carlos Garnacho committed
1051
        gtk_popover_update_shape (popover);
1052

Carlos Garnacho's avatar
Carlos Garnacho committed
1053
      priv->current_position = priv->final_position;
1054
      gtk_popover_invalidate_borders (popover);
Carlos Garnacho's avatar
Carlos Garnacho committed
1055
    }
1056 1057

  _gtk_popover_update_child_visible (popover);
Carlos Garnacho's avatar
Carlos Garnacho committed
1058 1059 1060
}

static gboolean
Carlos Garnacho's avatar
Carlos Garnacho committed
1061 1062
gtk_popover_draw (GtkWidget *widget,
                  cairo_t   *cr)
Carlos Garnacho's avatar
Carlos Garnacho committed
1063
{
1064
  GtkPopover *popover = GTK_POPOVER (widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
1065 1066 1067
  GtkStyleContext *context;
  GtkAllocation allocation;
  GtkWidget *child;
1068 1069 1070 1071 1072 1073 1074
  GtkBorder border;
  GdkRGBA border_color;
  gint rect_x1, rect_x2, rect_y1, rect_y2;
  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
1075 1076

  context = gtk_widget_get_style_context (widget);
1077

1078
  state = gtk_style_context_get_state (context);
Carlos Garnacho's avatar
Carlos Garnacho committed
1079 1080
  gtk_widget_get_allocation (widget, &allocation);

1081
  gtk_style_context_get_border (context, state, &border);
1082
  gtk_popover_get_rect_coords (popover,
Carlos Garnacho's avatar
Carlos Garnacho committed
1083 1084
                               &rect_x1, &rect_y1,
                               &rect_x2, &rect_y2);
1085 1086 1087

  /* Render the rect background */
  gtk_render_background (context, cr,
1088 1089 1090
                         rect_x1, rect_y1,
                         rect_x2 - rect_x1,
                         rect_y2 - rect_y1);
1091

1092
  if (popover->priv->widget)
1093
    {
1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116
      gtk_popover_get_gap_coords (popover,
                                  &initial_x, &initial_y,
                                  NULL, NULL,
                                  &final_x, &final_y,
                                  &gap_side);

      if (POS_IS_VERTICAL (gap_side))
        {
          gap_start = initial_x - rect_x1;
          gap_end = final_x - rect_x1;
        }
      else
        {
          gap_start = initial_y - rect_y1;
          gap_end = final_y - rect_y1;
        }

      /* Now render the frame, without the gap for the arrow tip */
      gtk_render_frame_gap (context, cr,
                            rect_x1, rect_y1,
                            rect_x2 - rect_x1, rect_y2 - rect_y1,
                            gap_side,
                            gap_start, gap_end);
1117 1118 1119
    }
  else
    {
1120
      gtk_render_frame (context, cr,
1121
                        rect_x1, rect_y1,
1122 1123
                        rect_x2 - rect_x1, rect_y2 - rect_y1);
    }
1124 1125 1126 1127

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

1128
  gtk_popover_apply_tail_path (popover, cr);
1129 1130 1131 1132
  cairo_clip (cr);

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

1136 1137 1138
  /* Render the border of the arrow tip */
  if (border.bottom > 0)
    {
1139
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1140
      gtk_style_context_get_border_color (context, state, &border_color);
1141
G_GNUC_END_IGNORE_DEPRECATIONS
1142

1143
      gtk_popover_apply_tail_path (popover, cr);
1144 1145
      gdk_cairo_set_source_rgba (cr, &border_color);

1146
      cairo_set_line_width (cr, border.bottom + 1);
1147 1148 1149 1150 1151
      cairo_stroke (cr);
    }

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

Carlos Garnacho's avatar
Carlos Garnacho committed
1153 1154 1155 1156 1157 1158 1159 1160
  child = gtk_bin_get_child (GTK_BIN (widget));

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

  return TRUE;
}

1161 1162 1163 1164 1165 1166
static void
get_padding_and_border (GtkWidget *widget,
                        GtkBorder *border)
{
  GtkStyleContext *context;
  GtkStateFlags state;
1167
  gint border_width;
1168 1169 1170
  GtkBorder tmp;

  context = gtk_widget_get_style_context (widget);
1171
  state = gtk_style_context_get_state (context);
1172

1173 1174
  border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));

1175 1176
  gtk_style_context_get_padding (context, state, border);
  gtk_style_context_get_border (context, state, &tmp);
1177 1178 1179 1180
  border->top += tmp.top + border_width;
  border->right += tmp.right + border_width;
  border->bottom += tmp.bottom + border_width;
  border->left += tmp.left + border_width;
1181 1182
}

1183 1184 1185 1186 1187 1188 1189 1190
static gint
get_border_radius (GtkWidget *widget)
{
  GtkStyleContext *context;
  GtkStateFlags state;
  gint border_radius;

  context = gtk_widget_get_style_context (widget);
1191
  state = gtk_style_context_get_state (context);
1192 1193 1194 1195 1196 1197 1198 1199 1200 1201
  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)
{
1202
  GtkPopoverPrivate *priv = popover->priv;
1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215
  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
1216
static void
Carlos Garnacho's avatar
Carlos Garnacho committed
1217 1218 1219
gtk_popover_get_preferred_width (GtkWidget *widget,
                                 gint      *minimum_width,
                                 gint      *natural_width)
Carlos Garnacho's avatar
Carlos Garnacho committed
1220
{
1221
  GtkPopover *popover = GTK_POPOVER (widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
1222
  GtkWidget *child;
1223
  gint min, nat, extra, minimal_size;
1224
  GtkBorder border, margin;
Carlos Garnacho's avatar
Carlos Garnacho committed
1225 1226 1227 1228 1229 1230 1231

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

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

1232
  get_padding_and_border (widget, &border);
1233
  get_margin (widget, &margin);
1234
  minimal_size = get_minimal_size (popover, GTK_ORIENTATION_HORIZONTAL);
1235

1236 1237
  min = MAX (min, minimal_size) + border.left + border.right;
  nat = MAX (nat, minimal_size) + border.left + border.right;
1238
  extra = MAX (TAIL_HEIGHT, margin.left) + MAX (TAIL_HEIGHT, margin.right);
1239 1240 1241

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

1243 1244
  *minimum_width = min;
  *natural_width = nat;
Carlos Garnacho's avatar
Carlos Garnacho committed
1245 1246
}

1247 1248 1249 1250 1251 1252
static void
gtk_popover_get_preferred_width_for_height (GtkWidget *widget,
                                            gint       height,
                                            gint      *minimum_width,
                                            gint      *natural_width)
{
1253 1254
  GtkPopover *popover = GTK_POPOVER (widget);
  GtkPopoverPrivate *priv = popover->priv;
1255
  GtkWidget *child;
1256
  gint min, nat, extra, minimal_size;
1257
  gint child_height;
1258
  GtkBorder border, margin;
1259 1260 1261 1262 1263 1264 1265 1266 1267 1268

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

  child_height = height;

  if (POS_IS_VERTICAL (priv->preferred_position))
    child_height -= TAIL_HEIGHT;

  get_padding_and_border (widget, &border);
1269
  get_margin (widget, &margin);
1270
  child_height -= border.top + border.bottom;
1271
  minimal_size = get_minimal_size (popover, GTK_ORIENTATION_HORIZONTAL);
1272 1273 1274 1275

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

1276 1277
  min = MAX (min, minimal_size) + border.left + border.right;
  nat = MAX (nat, minimal_size) + border.left + border.right;
1278
  extra = MAX (TAIL_HEIGHT, margin.left) + MAX (TAIL_HEIGHT, margin.right);
1279 1280 1281

  min += extra;
  nat += extra;
1282

1283 1284
  *minimum_width = min;
  *natural_width = nat;
1285 1286
}

Carlos Garnacho's avatar
Carlos Garnacho committed
1287
static void
Carlos Garnacho's avatar
Carlos Garnacho committed
1288 1289 1290
gtk_popover_get_preferred_height (GtkWidget *widget,
                                  gint      *minimum_height,
                                  gint      *natural_height)
Carlos Garnacho's avatar
Carlos Garnacho committed
1291
{
1292
  GtkPopover *popover = GTK_POPOVER (widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
1293
  GtkWidget *child;
1294
  gint min, nat, extra, minimal_size;
1295
  GtkBorder border, margin;
Carlos Garnacho's avatar
Carlos Garnacho committed
1296 1297 1298 1299 1300

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

  if (child)
1301 1302 1303
    gtk_widget_get_preferred_height (child, &min, &nat);

  get_padding_and_border (widget, &border);
1304
  get_margin (widget, &margin);
1305
  minimal_size = get_minimal_size (popover, GTK_ORIENTATION_VERTICAL);
1306 1307 1308

  min = MAX (min, minimal_size) + border.top + border.bottom;
  nat = MAX (nat, minimal_size) + border.top + border.bottom;
1309
  extra = MAX (TAIL_HEIGHT, margin.top) + MAX (TAIL_HEIGHT, margin.bottom);
1310 1311 1312

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

1314 1315
  *minimum_height = min;
  *natural_height = nat;
Carlos Garnacho's avatar
Carlos Garnacho committed
1316 1317
}

1318 1319 1320 1321 1322 1323
static void
gtk_popover_get_preferred_height_for_width (GtkWidget *widget,
                                            gint       width,
                                            gint      *minimum_height,
                                            gint      *natural_height)
{
1324 1325
  GtkPopover *popover = GTK_POPOVER (widget);
  GtkPopoverPrivate *priv = popover->priv;
1326
  GtkWidget *child;
1327
  gint min, nat, extra, minimal_size;
1328
  gint child_width;
1329
  GtkBorder border, margin;
1330 1331 1332 1333 1334 1335 1336 1337 1338 1339

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

  child_width = width;

  if (!POS_IS_VERTICAL (priv->preferred_position))
    child_width -= TAIL_HEIGHT;

  get_padding_and_border (widget, &border);
1340
  get_margin (widget, &margin);
1341
  child_width -= border.left + border.right;
1342
  minimal_size = get_minimal_size (popover, GTK_ORIENTATION_VERTICAL);
1343 1344 1345
  if (child)
    gtk_widget_get_preferred_height_for_width (child, child_width, &min, &nat);

1346 1347
  min = MAX (min, minimal_size) + border.top + border.bottom;
  nat = MAX (nat, minimal_size) + border.top + border.bottom;
1348
  extra = MAX (TAIL_HEIGHT, margin.top) + MAX (TAIL_HEIGHT, margin.bottom);
1349 1350 1351

  min += extra;
  nat += extra;
1352 1353

  if (minimum_height)
1354
    *minimum_height = min;
1355 1356

  if (natural_height)
1357
    *natural_height = nat;
1358 1359
}

1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383
static void
gtk_popover_invalidate_borders (GtkPopover *popover)
{
  GtkAllocation allocation;
  GtkBorder border;

  gtk_widget_get_allocation (GTK_WIDGET (popover), &allocation);
  get_padding_and_border (GTK_WIDGET (popover), &border);

  gtk_widget_queue_draw_area (GTK_WIDGET (popover), 0, 0, border.left + TAIL_HEIGHT, allocation.height);
  gtk_widget_queue_draw_area (GTK_WIDGET (popover), 0, 0, allocation.width, border.top + TAIL_HEIGHT);
  gtk_widget_queue_draw_area (GTK_WIDGET (popover), 0, allocation.height - border.bottom - TAIL_HEIGHT,
                              allocation.width, border.bottom + TAIL_HEIGHT);
  gtk_widget_queue_draw_area (GTK_WIDGET (popover), allocation.width - border.right - TAIL_HEIGHT,
                              0, border.right + TAIL_HEIGHT, allocation.height);
}

static void
gtk_popover_check_invalidate_borders (GtkPopover *popover)
{
  GtkPopoverPrivate *priv = popover->priv;
  GtkPositionType gap_side;
  gint tip_x, tip_y;

1384 1385 1386
  if (!priv->widget)
    return;

1387 1388 1389 1390 1391 1392 1393 1394