gtkpopover.c 69.8 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
 * Since: 3.12
Carlos Garnacho's avatar
Carlos Garnacho committed
75 76 77 78
 */

#include "config.h"
#include <gdk/gdk.h>
Carlos Garnacho's avatar
Carlos Garnacho committed
79
#include "gtkpopover.h"
80
#include "gtkpopoverprivate.h"
Matthias Clasen's avatar
Matthias Clasen committed
81 82
#include "gtktypebuiltins.h"
#include "gtkmain.h"
83
#include "gtkwindowprivate.h"
84 85
#include "gtkscrollable.h"
#include "gtkadjustment.h"
Carlos Garnacho's avatar
Carlos Garnacho committed
86 87
#include "gtkprivate.h"
#include "gtkintl.h"
88 89 90 91 92 93 94 95 96 97
#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"
98
#include "a11y/gtkpopoveraccessible.h"
99
#include "gtkmenusectionbox.h"
Carlos Garnacho's avatar
Carlos Garnacho committed
100

101 102 103 104
#ifdef GDK_WINDOWING_WAYLAND
#include "wayland/gdkwayland.h"
#endif

105 106 107 108
#define TAIL_GAP_WIDTH  24
#define TAIL_HEIGHT     12
#define TRANSITION_DIFF 20
#define TRANSITION_DURATION 330 * 1000
Carlos Garnacho's avatar
Carlos Garnacho committed
109 110 111 112 113 114

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

enum {
  PROP_RELATIVE_TO = 1,
  PROP_POINTING_TO,
115
  PROP_POSITION,
116 117
  PROP_MODAL,
  PROP_TRANSITIONS_ENABLED
Carlos Garnacho's avatar
Carlos Garnacho committed
118 119
};

120 121 122 123 124
enum {
  CLOSED,
  N_SIGNALS
};

125 126 127 128 129 130 131
enum {
  STATE_SHOWING,
  STATE_SHOWN,
  STATE_HIDING,
  STATE_HIDDEN
};

Carlos Garnacho's avatar
Carlos Garnacho committed
132
struct _GtkPopoverPrivate
Carlos Garnacho's avatar
Carlos Garnacho committed
133
{
134 135
  GtkWidget *widget;
  GtkWindow *window;
136
  GtkWidget *prev_focus_widget;
137 138
  GtkWidget *default_widget;
  GtkWidget *prev_default;
139 140 141
  GtkScrollable *parent_scrollable;
  GtkAdjustment *vadj;
  GtkAdjustment *hadj;
142
  GdkRectangle pointing_to;
143
  guint prev_focus_unmap_id;
144 145 146
  guint hierarchy_changed_id;
  guint size_allocate_id;
  guint unmap_id;
147
  guint scrollable_notify_id;
148
  guint grab_notify_id;
149
  guint state_changed_id;
Carlos Garnacho's avatar
Carlos Garnacho committed
150 151 152
  guint has_pointing_to    : 1;
  guint preferred_position : 2;
  guint final_position     : 2;
Carlos Garnacho's avatar
Carlos Garnacho committed
153
  guint current_position   : 2;
154
  guint modal              : 1;
155
  guint button_pressed     : 1;
156
  guint apply_shape        : 1;
157
  guint grab_notify_blocked : 1;
158 159 160 161 162 163
  guint transitions_enabled : 1;
  guint state               : 2;
  guint visible             : 1;
  gint64 start_time;
  gint transition_diff;
  guint tick_id;
Carlos Garnacho's avatar
Carlos Garnacho committed
164 165
};

166
static GQuark quark_widget_popovers = 0;
167
static guint signals[N_SIGNALS] = { 0 };
168

169 170
static void gtk_popover_update_relative_to (GtkPopover *popover,
                                            GtkWidget  *relative_to);
171 172
static void gtk_popover_set_state          (GtkPopover *popover,
                                            guint       state);
Carlos Garnacho's avatar
Carlos Garnacho committed
173 174

G_DEFINE_TYPE_WITH_PRIVATE (GtkPopover, gtk_popover, GTK_TYPE_BIN)
Carlos Garnacho's avatar
Carlos Garnacho committed
175 176

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
177
gtk_popover_init (GtkPopover *popover)
Carlos Garnacho's avatar
Carlos Garnacho committed
178 179
{
  GtkWidget *widget;
180
  GtkStyleContext *context;
Carlos Garnacho's avatar
Carlos Garnacho committed
181

Carlos Garnacho's avatar
Carlos Garnacho committed
182
  widget = GTK_WIDGET (popover);
183
  gtk_widget_set_has_window (widget, TRUE);
184 185
  popover->priv = gtk_popover_get_instance_private (popover);
  popover->priv->modal = TRUE;
186
  popover->priv->apply_shape = TRUE;
187 188
  popover->priv->tick_id = 0;
  popover->priv->transitions_enabled = TRUE;
189 190 191 192

  context = gtk_widget_get_style_context (widget);
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_BACKGROUND);
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_POPOVER);
Carlos Garnacho's avatar
Carlos Garnacho committed
193 194 195
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
196 197 198 199
gtk_popover_set_property (GObject      *object,
                          guint         prop_id,
                          const GValue *value,
                          GParamSpec   *pspec)
Carlos Garnacho's avatar
Carlos Garnacho committed
200 201 202 203
{
  switch (prop_id)
    {
    case PROP_RELATIVE_TO:
Carlos Garnacho's avatar
Carlos Garnacho committed
204 205
      gtk_popover_set_relative_to (GTK_POPOVER (object),
                                   g_value_get_object (value));
Carlos Garnacho's avatar
Carlos Garnacho committed
206 207
      break;
    case PROP_POINTING_TO:
Carlos Garnacho's avatar
Carlos Garnacho committed
208 209
      gtk_popover_set_pointing_to (GTK_POPOVER (object),
                                   g_value_get_boxed (value));
Carlos Garnacho's avatar
Carlos Garnacho committed
210 211
      break;
    case PROP_POSITION:
Carlos Garnacho's avatar
Carlos Garnacho committed
212 213
      gtk_popover_set_position (GTK_POPOVER (object),
                                g_value_get_enum (value));
Carlos Garnacho's avatar
Carlos Garnacho committed
214
      break;
215 216 217 218
    case PROP_MODAL:
      gtk_popover_set_modal (GTK_POPOVER (object),
                             g_value_get_boolean (value));
      break;
219 220 221 222
    case PROP_TRANSITIONS_ENABLED:
      gtk_popover_set_transitions_enabled (GTK_POPOVER (object),
                                           g_value_get_boolean (value));
      break;
Carlos Garnacho's avatar
Carlos Garnacho committed
223 224 225 226 227 228
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
229 230 231 232
gtk_popover_get_property (GObject    *object,
                          guint       prop_id,
                          GValue     *value,
                          GParamSpec *pspec)
Carlos Garnacho's avatar
Carlos Garnacho committed
233
{
Carlos Garnacho's avatar
Carlos Garnacho committed
234
  GtkPopoverPrivate *priv = GTK_POPOVER (object)->priv;
Carlos Garnacho's avatar
Carlos Garnacho committed
235 236 237 238

  switch (prop_id)
    {
    case PROP_RELATIVE_TO:
239
      g_value_set_object (value, priv->widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
240 241 242 243 244 245 246
      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;
247 248 249
    case PROP_MODAL:
      g_value_set_boolean (value, priv->modal);
      break;
250 251 252
    case PROP_TRANSITIONS_ENABLED:
      g_value_set_boolean (value, priv->transitions_enabled);
      break;
Carlos Garnacho's avatar
Carlos Garnacho committed
253 254 255 256 257
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

258 259 260 261 262 263 264 265 266 267 268 269 270
static gboolean
transitions_enabled (GtkPopover *popover)
{
  GtkPopoverPrivate *priv = popover->priv;
  gboolean animations_enabled;

  g_object_get (gtk_widget_get_settings (GTK_WIDGET (popover)),
                "gtk-enable-animations", &animations_enabled,
                NULL);

  return animations_enabled && priv->transitions_enabled;
}

Carlos Garnacho's avatar
Carlos Garnacho committed
271
static void
Carlos Garnacho's avatar
Carlos Garnacho committed
272
gtk_popover_finalize (GObject *object)
Carlos Garnacho's avatar
Carlos Garnacho committed
273
{
Carlos Garnacho's avatar
Carlos Garnacho committed
274
  GtkPopover *popover = GTK_POPOVER (object);
275 276 277 278
  GtkPopoverPrivate *priv = popover->priv;

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

Carlos Garnacho's avatar
Carlos Garnacho committed
280
  G_OBJECT_CLASS (gtk_popover_parent_class)->finalize (object);
Carlos Garnacho's avatar
Carlos Garnacho committed
281 282
}

283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
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);
}

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

  if (priv->window)
308 309 310 311
    {
      g_signal_handlers_disconnect_by_data (priv->window, popover);
      _gtk_window_remove_popover (priv->window, GTK_WIDGET (object));
    }
312 313

  priv->window = NULL;
314 315 316

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

318
  popover_unset_prev_focus (popover);
319

320 321
  g_clear_object (&priv->default_widget);

Carlos Garnacho's avatar
Carlos Garnacho committed
322
  G_OBJECT_CLASS (gtk_popover_parent_class)->dispose (object);
323 324 325
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
326
gtk_popover_realize (GtkWidget *widget)
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
{
  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) |
344
    GDK_POINTER_MOTION_MASK |
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
    GDK_BUTTON_MOTION_MASK |
    GDK_BUTTON_PRESS_MASK |
    GDK_BUTTON_RELEASE_MASK |
    GDK_EXPOSURE_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);
}

360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
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
377
      if (focus == NULL || !gtk_widget_is_ancestor (focus, GTK_WIDGET (popover)))
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
        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;
}

406 407 408 409 410 411 412
static void
window_set_focus (GtkWindow  *window,
                  GtkWidget  *widget,
                  GtkPopover *popover)
{
  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);

413
  if (priv->modal && widget &&
414
      gtk_widget_is_drawable (GTK_WIDGET (popover)) &&
415
      !gtk_widget_is_ancestor (widget, GTK_WIDGET (popover)))
416 417 418 419 420 421 422 423
    {
      GtkWidget *grab_widget;

      grab_widget = gtk_grab_get_current ();

      if (!grab_widget || !GTK_IS_POPOVER (grab_widget))
        gtk_widget_hide (GTK_WIDGET (popover));
    }
424 425
}

426 427 428 429 430 431 432
static void
prev_focus_unmap_cb (GtkWidget  *widget,
                     GtkPopover *popover)
{
  popover_unset_prev_focus (popover);
}

433 434 435 436
static void
gtk_popover_apply_modality (GtkPopover *popover,
                            gboolean    modal)
{
437
  GtkPopoverPrivate *priv = popover->priv;
438

439 440 441
  if (!priv->window)
    return;

442 443 444 445 446
  if (modal)
    {
      GtkWidget *prev_focus;

      prev_focus = gtk_window_get_focus (priv->window);
447 448
      priv->prev_focus_widget = prev_focus;
      if (priv->prev_focus_widget)
449 450 451 452 453 454
        {
          priv->prev_focus_unmap_id =
            g_signal_connect (prev_focus, "unmap",
                              G_CALLBACK (prev_focus_unmap_cb), popover);
          g_object_ref (prev_focus);
        }
455
      gtk_grab_add (GTK_WIDGET (popover));
456
      gtk_window_set_focus (priv->window, NULL);
457
      gtk_widget_grab_focus (GTK_WIDGET (popover));
458 459 460 461 462

      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);
463 464
      g_signal_connect (priv->window, "set-focus",
                        G_CALLBACK (window_set_focus), popover);
465 466 467
    }
  else
    {
468
      g_signal_handlers_disconnect_by_data (priv->window, popover);
469 470
      gtk_grab_remove (GTK_WIDGET (popover));

471 472 473 474
      /* 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);
475
      else if (priv->window)
476
        gtk_widget_grab_focus (GTK_WIDGET (priv->window));
477

478
      popover_unset_prev_focus (popover);
479 480 481
    }
}

482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
/* From clutter-easing.c, based on Robert Penner's
 * infamous easing equations, MIT license.
 */
static double
ease_out_cubic (double t)
{
  double p = t - 1;

  return p * p * p + 1;
}

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);
  gint64 now = gdk_frame_clock_get_frame_time (frame_clock);
  gdouble t;

  if (now < (priv->start_time + TRANSITION_DURATION))
    t = (now - priv->start_time) / (gdouble) (TRANSITION_DURATION);
  else
    t = 1.0;

  t = ease_out_cubic (t);

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

521 522
  gtk_widget_queue_resize (GTK_WIDGET (popover));

523 524 525 526 527 528 529 530 531 532 533 534
  if (t >= 1.0)
    {
      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);

535
      return FALSE;
536 537
    }
  else
538
    return TRUE;
539 540
}

541
static void
542
gtk_popover_start_transition (GtkPopover *popover)
543
{
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
  GtkPopoverPrivate *priv = popover->priv;
  GdkFrameClock *clock;

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

  clock = gtk_widget_get_frame_clock (GTK_WIDGET (popover));
  priv->start_time = gdk_frame_clock_get_frame_time (clock);
  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;
573

574 575 576 577
  if (state == STATE_SHOWING || state == STATE_HIDING)
    gtk_popover_start_transition (popover);
  else
    {
578
      if (priv->tick_id)
579 580 581 582 583 584 585 586 587 588 589 590
        {
          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)
{
591 592 593
  GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;

  priv->prev_default = gtk_window_get_default_widget (priv->window);
594 595
  if (priv->prev_default)
    g_object_ref (priv->prev_default);
596

Carlos Garnacho's avatar
Carlos Garnacho committed
597
  GTK_WIDGET_CLASS (gtk_popover_parent_class)->map (widget);
598

599
  gdk_window_show (gtk_widget_get_window (widget));
Carlos Garnacho's avatar
Carlos Garnacho committed
600
  gtk_popover_update_position (GTK_POPOVER (widget));
601 602

  gtk_window_set_default (priv->window, priv->default_widget);
603 604 605
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
606
gtk_popover_unmap (GtkWidget *widget)
607
{
608
  GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
609

610
  priv->button_pressed = FALSE;
611

612
  gdk_window_hide (gtk_widget_get_window (widget));
Carlos Garnacho's avatar
Carlos Garnacho committed
613
  GTK_WIDGET_CLASS (gtk_popover_parent_class)->unmap (widget);
614

615 616
  if (gtk_window_get_default_widget (priv->window) == priv->default_widget)
    gtk_window_set_default (priv->window, priv->prev_default);
617
  g_clear_object (&priv->prev_default);
618 619
}

Carlos Garnacho's avatar
Carlos Garnacho committed
620
static void
621 622
gtk_popover_get_pointed_to_coords (GtkPopover   *popover,
                                   GdkRectangle *rect_out)
Carlos Garnacho's avatar
Carlos Garnacho committed
623
{
Carlos Garnacho's avatar
Carlos Garnacho committed
624
  GtkPopoverPrivate *priv = popover->priv;
625
  GdkRectangle rect;
Carlos Garnacho's avatar
Carlos Garnacho committed
626

Carlos Garnacho's avatar
Carlos Garnacho committed
627 628 629 630
  if (!rect_out)
    return;

  gtk_popover_get_pointing_to (popover, &rect);
631 632
  gtk_widget_translate_coordinates (priv->widget, GTK_WIDGET (priv->window),
                                    rect.x, rect.y, &rect.x, &rect.y);
Carlos Garnacho's avatar
Carlos Garnacho committed
633
  *rect_out = rect;
Carlos Garnacho's avatar
Carlos Garnacho committed
634 635
}

636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
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;
}

651 652 653 654 655 656 657 658 659 660 661 662
static void
get_margin (GtkWidget *widget,
            GtkBorder *border)
{
  GtkStyleContext *context;
  GtkStateFlags state;

  context = gtk_widget_get_style_context (widget);
  state = gtk_widget_get_state_flags (widget);
  gtk_style_context_get_margin (context, state, border);
}

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

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

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

707
  get_margin (widget, &margin);
708

Carlos Garnacho's avatar
Carlos Garnacho committed
709 710 711 712 713 714 715
  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);

716 717
  context = gtk_widget_get_style_context (widget);
  state = gtk_widget_get_state_flags (widget);
718 719 720

  gtk_style_context_get_border (context, state, &border);
  gtk_style_context_get (context, state,
721 722
                         GTK_STYLE_PROPERTY_BORDER_RADIUS, &border_radius,
                         NULL);
723
  pos = get_effective_position (popover, priv->final_position);
724

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

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

Carlos Garnacho's avatar
Carlos Garnacho committed
754
      tip_x = CLAMP (tip_pos, 0, allocation.width);
755 756
      tip_y = tip;

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

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

      tip_x = tip;
Carlos Garnacho's avatar
Carlos Garnacho committed
772
      tip_y = CLAMP (tip_pos, 0, allocation.height);
773 774

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

  if (initial_x_out)
    *initial_x_out = initial_x;
  if (initial_y_out)
783
    *initial_y_out = initial_y;
784 785 786 787

  if (tip_x_out)
    *tip_x_out = tip_x;
  if (tip_y_out)
788
    *tip_y_out = tip_y;
789 790 791 792

  if (final_x_out)
    *final_x_out = final_x;
  if (final_y_out)
793
    *final_y_out = final_y;
794 795 796 797 798 799

  if (gap_side_out)
    *gap_side_out = gap_side;
}

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

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

  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;
824

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

  if (x1_out)
    *x1_out = x1;
  if (y1_out)
833
    *y1_out = y1;
834 835 836
  if (x2_out)
    *x2_out = x2;
  if (y2_out)
837
    *y2_out = y2;
838 839 840
}

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

848 849 850
  if (!popover->priv->widget)
    return;

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

  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
861 862 863
}

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

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

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

  gtk_popover_get_rect_coords (popover, &x1, &y1, &x2, &y2);
  gtk_render_frame (context, cr, x1, y1, x2 - x1, y2 - y1);
  gtk_render_background (context, cr, x1, y1, x2 - x1, y2 - y1);
Carlos Garnacho's avatar
Carlos Garnacho committed
882 883 884
}

static void
Carlos Garnacho's avatar
Carlos Garnacho committed
885
gtk_popover_update_shape (GtkPopover *popover)
Carlos Garnacho's avatar
Carlos Garnacho committed
886
{
887
  GtkWidget *widget = GTK_WIDGET (popover);
Carlos Garnacho's avatar
Carlos Garnacho committed
888 889 890 891 892
  cairo_surface_t *surface;
  cairo_region_t *region;
  GdkWindow *win;
  cairo_t *cr;

893
  win = gtk_widget_get_window (widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
894 895 896 897 898 899 900
  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);
901
  gtk_popover_fill_border_path (popover, cr);
Carlos Garnacho's avatar
Carlos Garnacho committed
902 903 904 905 906
  cairo_destroy (cr);

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

907
  gtk_widget_shape_combine_region (widget, region);
Carlos Garnacho's avatar
Carlos Garnacho committed
908
  cairo_region_destroy (region);
909

910
  gdk_window_set_child_shapes (gtk_widget_get_parent_window (widget));
Carlos Garnacho's avatar
Carlos Garnacho committed
911 912
}

913 914 915
static void
_gtk_popover_update_child_visible (GtkPopover *popover)
{
916 917
  GtkPopoverPrivate *priv = popover->priv;
  GtkWidget *widget = GTK_WIDGET (popover);
918
  GdkRectangle rect;
919 920 921 922 923
  GtkAllocation allocation;
  GtkWidget *parent;

  if (!priv->parent_scrollable)
    {
924
      gtk_widget_set_child_visible (widget, TRUE);
925 926 927 928 929 930 931 932 933 934 935
      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);

936 937
  if (rect.x + rect.width < 0 || rect.x > allocation.width ||
      rect.y + rect.height < 0 || rect.y > allocation.height)
938
    gtk_widget_set_child_visible (widget, FALSE);
939
  else
940
    gtk_widget_set_child_visible (widget, TRUE);
941 942
}

943 944 945 946 947 948 949 950 951 952 953 954 955
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;
    }
}

956
void
Carlos Garnacho's avatar
Carlos Garnacho committed
957
gtk_popover_update_position (GtkPopover *popover)
Carlos Garnacho's avatar
Carlos Garnacho committed
958
{
959 960
  GtkPopoverPrivate *priv = popover->priv;
  GtkWidget *widget = GTK_WIDGET (popover);
Carlos Garnacho's avatar
Carlos Garnacho committed
961
  GtkAllocation window_alloc;
962
  GdkRectangle rect;
Carlos Garnacho's avatar
Carlos Garnacho committed
963
  GtkRequisition req;
964 965
  GtkPositionType pos;
  gint overshoot[4];
966
  gint i, j;
967
  gint best;
Carlos Garnacho's avatar
Carlos Garnacho committed
968

Carlos Garnacho's avatar
Carlos Garnacho committed
969 970 971
  if (!priv->window)
    return;

972
  gtk_widget_get_preferred_size (widget, NULL, &req);
973
  gtk_widget_get_allocation (GTK_WIDGET (priv->window), &window_alloc);
Carlos Garnacho's avatar
Carlos Garnacho committed
974 975
  priv->final_position = priv->preferred_position;

Carlos Garnacho's avatar
Carlos Garnacho committed
976
  gtk_popover_get_pointed_to_coords (popover, &rect);
977
  pos = get_effective_position (popover, priv->preferred_position);
Carlos Garnacho's avatar
Carlos Garnacho committed
978

979 980 981 982 983
  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;

984
#ifdef GDK_WINDOWING_WAYLAND
985 986 987 988
  if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (widget)))
    {
      priv->final_position = priv->preferred_position;
    }
989 990 991
  else
#endif
  if (overshoot[pos] <= 0)
992
    {
993
      priv->final_position = priv->preferred_position;
994 995 996
    }
  else if (overshoot[opposite_position (pos)] <= 0)
    {
997
      priv->final_position = opposite_position (priv->preferred_position);
998 999 1000 1001 1002 1003 1004
    }
  else
    {
      best = G_MAXINT;
      pos = 0;
      for (i = 0; i < 4; i++)
        {
1005 1006
          j = get_effective_position (popover, i);
          if (overshoot[j] < best)
1007 1008
            {
              pos = i;
1009
              best = overshoot[j];
1010 1011 1012 1013
            }
        }
      priv->final_position = pos;
    }
Carlos Garnacho's avatar
Carlos Garnacho committed
1014

1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
  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;
    }

1031
  _gtk_window_set_popover_position (priv->window, widget,
1032
                                    priv->final_position, &rect);
Carlos Garnacho's avatar
Carlos Garnacho committed
1033

Carlos Garnacho's avatar
Carlos Garnacho committed
1034 1035
  if (priv->final_position != priv->current_position)
    {
1036
      if (priv->apply_shape && gtk_widget_is_drawable (widget))
Carlos Garnacho's avatar
Carlos Garnacho committed
1037
        gtk_popover_update_shape (popover);
1038

Carlos Garnacho's avatar
Carlos Garnacho committed
1039 1040
      priv->current_position = priv->final_position;
    }
1041 1042

  _gtk_popover_update_child_visible (popover);
Carlos Garnacho's avatar
Carlos Garnacho committed
1043 1044 1045
}

static gboolean
Carlos Garnacho's avatar
Carlos Garnacho committed
1046 1047
gtk_popover_draw (GtkWidget *widget,
                  cairo_t   *cr)
Carlos Garnacho's avatar
Carlos Garnacho committed
1048
{
1049
  GtkPopover *popover = GTK_POPOVER (widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
1050 1051 1052
  GtkStyleContext *context;
  GtkAllocation allocation;
  GtkWidget *child;
1053 1054 1055 1056 1057 1058 1059
  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
1060 1061

  context = gtk_widget_get_style_context (widget);
1062 1063
  gtk_style_context_save (context);

1064
  state = gtk_widget_get_state_flags (widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
1065 1066
  gtk_widget_get_allocation (widget, &allocation);

1067
  gtk_style_context_get_border (context, state, &border);
1068
  gtk_popover_get_rect_coords (popover,
Carlos Garnacho's avatar
Carlos Garnacho committed
1069 1070
                               &rect_x1, &rect_y1,
                               &rect_x2, &rect_y2);
1071 1072 1073

  /* Render the rect background */
  gtk_render_background (context, cr,
1074 1075 1076
                         rect_x1, rect_y1,
                         rect_x2 - rect_x1,
                         rect_y2 - rect_y1);
1077

1078
  if (popover->priv->widget)
1079
    {
1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102
      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);
1103 1104 1105
    }
  else
    {
1106
      gtk_render_frame (context, cr,
1107
                        rect_x1, rect_y1,
1108 1109
                        rect_x2 - rect_x1, rect_y2 - rect_y1);
    }
1110 1111 1112 1113

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

1114
  gtk_popover_apply_tail_path (popover, cr);
1115 1116 1117 1118
  cairo_clip (cr);

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

1122 1123 1124
  /* Render the border of the arrow tip */
  if (border.bottom > 0)
    {
1125
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1126
      gtk_style_context_get_border_color (context, state, &border_color);
1127
G_GNUC_END_IGNORE_DEPRECATIONS
1128
      gtk_popover_apply_tail_path (popover, cr);
1129 1130
      gdk_cairo_set_source_rgba (cr, &border_color);

1131
      cairo_set_line_width (cr, border.bottom + 1);
1132 1133 1134 1135 1136
      cairo_stroke (cr);
    }

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

Carlos Garnacho's avatar
Carlos Garnacho committed
1138 1139 1140 1141 1142
  child = gtk_bin_get_child (GTK_BIN (widget));

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

1143 1144
  gtk_style_context_restore (context);

Carlos Garnacho's avatar
Carlos Garnacho committed
1145 1146 1147
  return TRUE;
}

1148 1149 1150 1151 1152 1153
static void
get_padding_and_border (GtkWidget *widget,
                        GtkBorder *border)
{
  GtkStyleContext *context;
  GtkStateFlags state;
1154
  gint border_width;
1155 1156 1157 1158 1159
  GtkBorder tmp;

  context = gtk_widget_get_style_context (widget);
  state = gtk_widget_get_state_flags (widget);

1160 1161
  border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));

1162 1163
  gtk_style_context_get_padding (context, state, border);
  gtk_style_context_get_border (context, state, &tmp);
1164 1165 1166 1167
  border->top += tmp.top + border_width;
  border->right += tmp.right + border_width;
  border->bottom += tmp.bottom + border_width;
  border->left += tmp.left + border_width;
1168 1169
}

1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188
static gint
get_border_radius (GtkWidget *widget)
{
  GtkStyleContext *context;
  GtkStateFlags state;
  gint border_radius;

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

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

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

1219
  get_padding_and_border (widget, &border);
1220
  get_margin (widget, &margin);
1221
  minimal_size = get_minimal_size (popover, GTK_ORIENTATION_HORIZONTAL);
1222

1223 1224
  min = MAX (min, minimal_size) + border.left + border.right;
  nat = MAX (nat, minimal_size) + border.left + border.right;
1225
  extra = MAX (TAIL_HEIGHT, margin.left) + MAX (TAIL_HEIGHT, margin.right);
1226 1227 1228

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

1230 1231
  *minimum_width = min;
  *natural_width = nat;
Carlos Garnacho's avatar
Carlos Garnacho committed
1232 1233
}

1234 1235 1236 1237 1238 1239
static void
gtk_popover_get_preferred_width_for_height (GtkWidget *widget,
                                            gint       height,
                                            gint      *minimum_width,
                                            gint      *natural_width)
{
1240 1241
  GtkPopover *popover = GTK_POPOVER (widget);
  GtkPopoverPrivate *priv = popover->priv;
1242
  GtkWidget *child;
1243
  gint min, nat, extra, minimal_size;
1244
  gint child_height;
1245
  GtkBorder border, margin;
1246 1247 1248 1249 1250 1251 1252 1253 1254 1255

  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);
1256
  get_margin (widget, &margin);
1257
  child_height -= border.top + border.bottom;
1258
  minimal_size = get_minimal_size (popover, GTK_ORIENTATION_HORIZONTAL);
1259 1260 1261 1262

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

1263 1264
  min = MAX (min, minimal_size) + border.left + border.right;
  nat = MAX (nat, minimal_size) + border.left + border.right;
1265
  extra = MAX (TAIL_HEIGHT, margin.left) + MAX (TAIL_HEIGHT, margin.right);
1266 1267 1268

  min += extra;
  nat += extra;
1269

1270 1271
  *minimum_width = min;
  *natural_width = nat;
1272 1273
}

Carlos Garnacho's avatar
Carlos Garnacho committed
1274
static void
Carlos Garnacho's avatar
Carlos Garnacho committed
1275 1276 1277
gtk_popover_get_preferred_height (GtkWidget *widget,
                                  gint      *minimum_height,
                                  gint      *natural_height)
Carlos Garnacho's avatar
Carlos Garnacho committed
1278
{
1279
  GtkPopover *popover = GTK_POPOVER (widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
1280
  GtkWidget *child;
1281
  gint min, nat, extra, minimal_size;
1282
  GtkBorder border, margin;
Carlos Garnacho's avatar
Carlos Garnacho committed
1283 1284 1285 1286 1287

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

  if (child)
1288 1289 1290
    gtk_widget_get_preferred_height (child, &min, &nat);

  get_padding_and_border (widget, &border);
1291
  get_margin (widget, &margin);
1292
  minimal_size = get_minimal_size (popover, GTK_ORIENTATION_VERTICAL);
1293 1294 1295

  min = MAX (min, minimal_size) + border.top + border.bottom;
  nat = MAX (nat, minimal_size) + border.top + border.bottom;
1296
  extra = MAX (TAIL_HEIGHT, margin.top) + MAX (TAIL_HEIGHT, margin.bottom);
1297 1298 1299

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

1301 1302
  *minimum_height = min;
  *natural_height = nat;
Carlos Garnacho's avatar
Carlos Garnacho committed
1303 1304
}

1305 1306 1307 1308 1309 1310
static void
gtk_popover_get_preferred_height_for_width (GtkWidget *widget,
                                            gint       width,
                                            gint      *minimum_height,
                                            gint      *natural_height)
{
1311 1312
  GtkPopover *popover = GTK_POPOVER (widget);
  GtkPopoverPrivate *priv = popover->priv;
1313
  GtkWidget *child;
1314
  gint min, nat, extra, minimal_size;
1315
  gint child_width;
1316
  GtkBorder border, margin;
1317 1318 1319 1320 1321 1322 1323 1324 1325 1326

  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);
1327
  get_margin (widget, &margin);
1328
  child_width -= border.left + border.right;
1329
  minimal_size = get_minimal_size (popover, GTK_ORIENTATION_VERTICAL);
1330 1331 1332
  if (child)
    gtk_widget_get_preferred_height_for_width (child, child_width, &min, &nat);

1333 1334
  min = MAX (min, minimal_size) + border.top + border.bottom;
  nat = MAX (nat, minimal_size) + border.top + border.bottom;
1335
  extra = MAX (TAIL_HEIGHT, margin.top) + MAX (TAIL_HEIGHT, margin.bottom);
1336 1337 1338

  min += extra;
  nat += extra;
1339 1340

  if (minimum_height)
1341
    *minimum_height = min;
1342 1343

  if (natural_height)
1344
    *natural_height = nat;
1345 1346
}

Carlos Garnacho's avatar
Carlos Garnacho committed
1347
static void
Carlos Garnacho's avatar
Carlos Garnacho committed
1348 1349
gtk_popover_size_allocate (GtkWidget     *widget,
                           GtkAllocation *allocation)
Carlos Garnacho's avatar
Carlos Garnacho committed
1350
{
1351 1352
  GtkPopover *popover = GTK_POPOVER (widget);
  GtkPopoverPrivate *priv = popover->priv;
Carlos Garnacho's avatar
Carlos Garnacho committed
1353 1354 1355 1356 1357 1358 1359
  GtkWidget *child;

  gtk_widget_set_allocation (widget, allocation);
  child = gtk_bin_get_child (GTK_BIN (widget));
  if (child)
    {
      GtkAllocation child_alloc;
Carlos Garnacho's avatar
Carlos Garnacho committed
1360
      gint x1, y1, x2, y2;
1361 1362
      GtkBorder border;

1363
      gtk_popover_get_rect_coords (popover, &x1, &y1, &x2, &y2);
1364
      get_padding_and_border (widget, &border);
Carlos Garnacho's avatar
Carlos Garnacho committed
1365

Carlos Garnacho's avatar
Carlos Garnacho committed
1366 1367 1368 1369
      child_alloc.x = x1 + border.left;
      child_alloc.y = y1 + border.top;
      child_alloc.width = (x2 - x1) - border.left - border.right;
      child_alloc.height = (y2 - y1) - border.top - border.bottom;
Carlos Garnacho's avatar
Carlos Garnacho committed
1370 1371 1372 1373
      gtk_widget_size_allocate (child, &child_alloc);
    }

  if (gtk_widget_get_realized (widget))
1374 1375 1376
    {
      gdk_window_move_resize (gtk_widget_get_window (widget),
                              0, 0, allocation->width, allocation->height);
1377
      if (priv->apply_shape)
1378
        gtk_popover_update_shape (popover);
1379
    }
Carlos Garnacho's avatar
Carlos Garnacho committed
1380 1381 1382
}

static gboolean
Carlos Garnacho's avatar
Carlos Garnacho committed
1383 1384
gtk_popover_button_press (GtkWidget      *widget,
                          GdkEventButton *event)
Carlos Garnacho's avatar
Carlos Garnacho committed
1385
{
1386
  GtkPopover *popover = GTK_POPOVER (widget);
1387 1388 1389 1390

  if (event->type != GDK_BUTTON_PRESS)
    return GDK_EVENT_PROPAGATE;

1391
  popover->priv->button_pressed = TRUE;
1392 1393 1394 1395 1396 1397 1398 1399

  return GDK_EVENT_PROPAGATE;
}

static gboolean
gtk_popover_button_release (GtkWidget      *widget,
			    GdkEventButton *event)
{
1400
  GtkPopover *popover = GTK_POPOVER (widget);
1401
  GtkWidget *child, *event_widget;
Carlos Garnacho's avatar
Carlos Garnacho committed
1402 1403 1404

  child = gtk_bin_get_child (GTK_BIN (widget));

1405
  if (!popover->priv->button_pressed)
1406 1407
    return GDK_EVENT_PROPAGATE;

1408 1409
  event_widget = gtk_get_event_widget ((GdkEvent *) event);

Carlos Garnacho's avatar
Carlos Garnacho committed
1410 1411 1412 1413 1414 1415 1416 1417 1418 1419
  if (child && event->window == gtk_widget_get_window (widget))
    {
      GtkAllocation child_alloc;

      gtk_widget_get_allocation (child, &child_alloc);

      if (event->x < child_alloc.x ||
          event->x > child_alloc.x + child_alloc.width ||
          event->y < child_alloc.y ||
          event->y > child_alloc.y + child_alloc.height)
1420
        gtk_widget_hide (widget);
Carlos Garnacho's avatar
Carlos Garnacho committed
1421
    }