gtkscrolledwindow.c 164 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
Elliot Lee's avatar
Elliot Lee committed
2 3 4
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
Elliot Lee's avatar
Elliot Lee committed
6 7 8 9 10 11
 * 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
12
 * Lesser General Public License for more details.
Elliot Lee's avatar
Elliot Lee committed
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
Elliot Lee's avatar
Elliot Lee committed
16
 */
17 18

/*
19
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
20 21
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
22
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23 24
 */

25
#include "config.h"
26

27
#include "gtkscrolledwindow.h"
28

29
#include "gtkadjustment.h"
30
#include "gtkadjustmentprivate.h"
31
#include "gtkbindings.h"
32
#include "gtkcsscustomgadgetprivate.h"
33
#include "gtkdnd.h"
34
#include "gtkintl.h"
35
#include "gtkmain.h"
36
#include "gtkmarshalers.h"
37
#include "gtkprivate.h"
38 39
#include "gtkscrollable.h"
#include "gtkscrollbar.h"
40
#include "gtkrangeprivate.h"
41
#include "gtktypebuiltins.h"
42
#include "gtkviewport.h"
43
#include "gtkwidgetprivate.h"
44
#include "gtkwindow.h"
45
#include "gtkkineticscrolling.h"
46
#include "a11y/gtkscrolledwindowaccessible.h"
47
#include "gtkstylecontextprivate.h"
48
#include "gtkprogresstrackerprivate.h"
49
#include "gtksettingsprivate.h"
50

51 52
#include <math.h>

53 54 55 56
/**
 * SECTION:gtkscrolledwindow
 * @Short_description: Adds scrollbars to its child widget
 * @Title: GtkScrolledWindow
57
 * @See_also: #GtkScrollable, #GtkViewport, #GtkAdjustment
58
 *
59 60 61
 * GtkScrolledWindow is a container that accepts a single child widget, makes
 * that child scrollable using either internally added scrollbars or externally
 * associated adjustments, and optionally draws a frame around the child.
62
 *
63 64 65 66 67 68 69
 * Widgets with native scrolling support, i.e. those whose classes implement the
 * #GtkScrollable interface, are added directly. For other types of widget, the
 * class #GtkViewport acts as an adaptor, giving scrollability to other widgets.
 * GtkScrolledWindow’s implementation of gtk_container_add() intelligently
 * accounts for whether or not the added child is a #GtkScrollable. If it isn’t,
 * #GtkScrolledWindow wraps the child in a #GtkViewport and adds that for you.
 * Therefore, you can just add any child widget and not worry about the details.
70
 *
71
 * If gtk_container_add() has added a #GtkViewport for you, you can remove
72
 * both your added child widget from the #GtkViewport, and the #GtkViewport
73
 * from the GtkScrolledWindow, like this:
74
 *
75
 * |[<!-- language="C" -->
76 77 78 79 80
 * GtkWidget *scrolled_window = gtk_scrolled_window_new (NULL, NULL);
 * GtkWidget *child_widget = gtk_button_new ();
 *
 * // GtkButton is not a GtkScrollable, so GtkScrolledWindow will automatically
 * // add a GtkViewport.
81 82 83 84
 * gtk_container_add (GTK_CONTAINER (scrolled_window),
 *                    child_widget);
 *
 * // Either of these will result in child_widget being unparented:
85 86 87 88 89 90 91
 * gtk_container_remove (GTK_CONTAINER (scrolled_window),
 *                       child_widget);
 * // or
 * gtk_container_remove (GTK_CONTAINER (scrolled_window),
 *                       gtk_bin_get_child (GTK_BIN (scrolled_window)));
 * ]|
 *
92 93 94 95 96 97 98
 * Unless #GtkScrolledWindow:policy is GTK_POLICY_NEVER or GTK_POLICY_EXTERNAL,
 * GtkScrolledWindow adds internal #GtkScrollbar widgets around its child. The
 * scroll position of the child, and if applicable the scrollbars, is controlled
 * by the #GtkScrolledWindow:hadjustment and #GtkScrolledWindow:vadjustment
 * that are associated with the GtkScrolledWindow. See the docs on #GtkScrollbar
 * for the details, but note that the “step_increment” and “page_increment”
 * fields are only effective if the policy causes scrollbars to be present.
99
 *
100
 * If a GtkScrolledWindow doesn’t behave quite as you would like, or
101
 * doesn’t have exactly the right layout, it’s very possible to set up
102
 * your own scrolling with #GtkScrollbar and for example a #GtkGrid.
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
 *
 * # Touch support
 *
 * GtkScrolledWindow has built-in support for touch devices. When a
 * touchscreen is used, swiping will move the scrolled window, and will
 * expose 'kinetic' behavior. This can be turned off with the
 * #GtkScrolledWindow:kinetic-scrolling property if it is undesired.
 *
 * GtkScrolledWindow also displays visual 'overshoot' indication when
 * the content is pulled beyond the end, and this situation can be
 * captured with the #GtkScrolledWindow::edge-overshot signal.
 *
 * If no mouse device is present, the scrollbars will overlayed as
 * narrow, auto-hiding indicators over the content. If traditional
 * scrollbars are desired although no mouse is present, this behaviour
 * can be turned off with the #GtkScrolledWindow:overlay-scrolling
 * property.
120 121 122 123
 *
 * # CSS nodes
 *
 * GtkScrolledWindow has a main CSS node with name scrolledwindow.
124 125
 *
 * It uses subnodes with names overshoot and undershoot to
126 127
 * draw the overflow and underflow indications. These nodes get
 * the .left, .right, .top or .bottom style class added depending
128 129 130
 * on where the indication is drawn.
 *
 * GtkScrolledWindow also sets the positional style classes (.left,
131 132
 * .right, .top, .bottom) and style classes related to overlay
 * scrolling (.overlay-indicator, .dragging, .hovering) on its scrollbars.
133 134 135
 *
 * If both scrollbars are visible, the area where they meet is drawn
 * with a subnode named junction.
136 137 138
 */


139 140 141 142 143 144 145
/* scrolled window policy and size requisition handling:
 *
 * gtk size requisition works as follows:
 *   a widget upon size-request reports the width and height that it finds
 *   to be best suited to display its contents, including children.
 *   the width and/or height reported from a widget upon size requisition
 *   may be overidden by the user by specifying a width and/or height
146
 *   other than 0 through gtk_widget_set_size_request().
147
 *
148
 * a scrolled window needs (for implementing all three policy types) to
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
 * request its width and height based on two different rationales.
 * 1)   the user wants the scrolled window to just fit into the space
 *      that it gets allocated for a specifc dimension.
 * 1.1) this does not apply if the user specified a concrete value
 *      value for that specific dimension by either specifying usize for the
 *      scrolled window or for its child.
 * 2)   the user wants the scrolled window to take as much space up as
 *      is desired by the child for a specifc dimension (i.e. POLICY_NEVER).
 *
 * also, kinda obvious:
 * 3)   a user would certainly not have choosen a scrolled window as a container
 *      for the child, if the resulting allocation takes up more space than the
 *      child would have allocated without the scrolled window.
 *
 * conclusions:
164
 * A) from 1) follows: the scrolled window shouldn’t request more space for a
165 166 167 168
 *    specifc dimension than is required at minimum.
 * B) from 1.1) follows: the requisition may be overidden by usize of the scrolled
 *    window (done automatically) or by usize of the child (needs to be checked).
 * C) from 2) follows: for POLICY_NEVER, the scrolled window simply reports the
169 170
 *    child’s dimension.
 * D) from 3) follows: the scrolled window child’s minimum width and minimum height
171 172 173
 *    under A) at least correspond to the space taken up by its scrollbars.
 */

174
#define DEFAULT_SCROLLBAR_SPACING  3
175 176 177
#define TOUCH_BYPASS_CAPTURED_THRESHOLD 30

/* Kinetic scrolling */
178
#define MAX_OVERSHOOT_DISTANCE 100
179 180
#define DECELERATION_FRICTION 4
#define OVERSHOOT_FRICTION 20
181
#define SCROLL_CAPTURE_THRESHOLD_MS 150
Elliot Lee's avatar
Elliot Lee committed
182

183 184 185
/* Animated scrolling */
#define ANIMATION_DURATION 200

186
/* Overlay scrollbars */
187
#define INDICATOR_FADE_OUT_DELAY 2000
188 189
#define INDICATOR_FADE_OUT_DURATION 1000
#define INDICATOR_FADE_OUT_TIME 500
190 191
#define INDICATOR_CLOSE_DISTANCE 5
#define INDICATOR_FAR_DISTANCE 10
192

193
/* Scrolled off indication */
194
#define UNDERSHOOT_SIZE 40
195

196 197 198 199
typedef struct
{
  GtkWidget *scrollbar;
  GdkWindow *window;
200
  gboolean   over; /* either mouse over, or while dragging */
201 202 203 204 205 206
  gint64     last_scroll_time;
  guint      conceil_timer;

  gdouble    current_pos;
  gdouble    source_pos;
  gdouble    target_pos;
207
  GtkProgressTracker tracker;
208
  guint      tick_id;
209
  guint      over_timeout_id;
210 211
} Indicator;

212 213 214 215 216 217 218
typedef struct
{
  gdouble dx;
  gdouble dy;
  guint32 evtime;
} ScrollHistoryElem;

219 220 221 222 223
struct _GtkScrolledWindowPrivate
{
  GtkWidget     *hscrollbar;
  GtkWidget     *vscrollbar;

224
  GtkCssGadget  *gadget;
225 226 227
  GtkCssNode    *overshoot_node[4];
  GtkCssNode    *undershoot_node[4];

228 229 230
  Indicator hindicator;
  Indicator vindicator;

231
  GtkCornerType  window_placement;
232 233
  guint16  shadow_type;

234 235 236 237 238 239 240 241 242 243
  guint    hscrollbar_policy        : 2;
  guint    vscrollbar_policy        : 2;
  guint    hscrollbar_visible       : 1;
  guint    vscrollbar_visible       : 1;
  guint    focus_out                : 1; /* used by ::move-focus-out implementation */
  guint    overlay_scrolling        : 1;
  guint    use_indicators           : 1;
  guint    auto_added_viewport      : 1;
  guint    propagate_natural_width  : 1;
  guint    propagate_natural_height : 1;
244 245 246

  gint     min_content_width;
  gint     min_content_height;
247 248
  gint     max_content_width;
  gint     max_content_height;
249

250 251
  guint scroll_events_overshoot_id;

252
  /* Kinetic scrolling */
253
  GtkGesture *long_press_gesture;
254 255
  GtkGesture *swipe_gesture;

256 257
  GArray *scroll_history;
  GdkDevice *scroll_device;
258 259
  GdkWindow *scroll_window;
  GdkCursor *scroll_cursor;
260

261 262 263 264
  /* These two gestures are mutually exclusive */
  GtkGesture *drag_gesture;
  GtkGesture *pan_gesture;

265 266 267
  gdouble drag_start_x;
  gdouble drag_start_y;

268 269 270
  GdkDevice             *drag_device;
  guint                  kinetic_scrolling         : 1;
  guint                  capture_button_press      : 1;
271
  guint                  in_drag                   : 1;
272 273 274 275 276 277 278 279

  guint                  deceleration_id;

  gdouble                x_velocity;
  gdouble                y_velocity;

  gdouble                unclamped_hadj_value;
  gdouble                unclamped_vadj_value;
280
};
281

282 283 284 285 286
typedef struct
{
  GtkScrolledWindow     *scrolled_window;
  gint64                 last_deceleration_time;

287 288
  GtkKineticScrolling   *hscrolling;
  GtkKineticScrolling   *vscrolling;
289
} KineticScrollData;
290

291
enum {
292 293 294 295 296 297
  PROP_0,
  PROP_HADJUSTMENT,
  PROP_VADJUSTMENT,
  PROP_HSCROLLBAR_POLICY,
  PROP_VSCROLLBAR_POLICY,
  PROP_WINDOW_PLACEMENT,
298
  PROP_WINDOW_PLACEMENT_SET,
299 300
  PROP_SHADOW_TYPE,
  PROP_MIN_CONTENT_WIDTH,
301
  PROP_MIN_CONTENT_HEIGHT,
302
  PROP_KINETIC_SCROLLING,
303
  PROP_OVERLAY_SCROLLING,
304 305
  PROP_MAX_CONTENT_WIDTH,
  PROP_MAX_CONTENT_HEIGHT,
306 307
  PROP_PROPAGATE_NATURAL_WIDTH,
  PROP_PROPAGATE_NATURAL_HEIGHT,
308
  NUM_PROPERTIES
309 310
};

311 312 313 314 315
/* Signals */
enum
{
  SCROLL_CHILD,
  MOVE_FOCUS_OUT,
316
  EDGE_OVERSHOT,
317
  EDGE_REACHED,
318 319 320
  LAST_SIGNAL
};

321 322 323 324 325 326 327 328
static void     gtk_scrolled_window_set_property       (GObject           *object,
                                                        guint              prop_id,
                                                        const GValue      *value,
                                                        GParamSpec        *pspec);
static void     gtk_scrolled_window_get_property       (GObject           *object,
                                                        guint              prop_id,
                                                        GValue            *value,
                                                        GParamSpec        *pspec);
329
static void     gtk_scrolled_window_finalize           (GObject           *object);
330

331
static void     gtk_scrolled_window_destroy            (GtkWidget         *widget);
332 333
static gboolean gtk_scrolled_window_draw               (GtkWidget         *widget,
                                                        cairo_t           *cr);
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
static void     gtk_scrolled_window_size_allocate      (GtkWidget         *widget,
                                                        GtkAllocation     *allocation);
static gboolean gtk_scrolled_window_scroll_event       (GtkWidget         *widget,
                                                        GdkEventScroll    *event);
static gboolean gtk_scrolled_window_focus              (GtkWidget         *widget,
                                                        GtkDirectionType   direction);
static void     gtk_scrolled_window_add                (GtkContainer      *container,
                                                        GtkWidget         *widget);
static void     gtk_scrolled_window_remove             (GtkContainer      *container,
                                                        GtkWidget         *widget);
static void     gtk_scrolled_window_forall             (GtkContainer      *container,
                                                        gboolean           include_internals,
                                                        GtkCallback        callback,
                                                        gpointer           callback_data);
static gboolean gtk_scrolled_window_scroll_child       (GtkScrolledWindow *scrolled_window,
                                                        GtkScrollType      scroll,
                                                        gboolean           horizontal);
static void     gtk_scrolled_window_move_focus_out     (GtkScrolledWindow *scrolled_window,
                                                        GtkDirectionType   direction_type);

static void     gtk_scrolled_window_relative_allocation(GtkWidget         *widget,
                                                        GtkAllocation     *allocation);
356 357 358 359 360 361 362
static void     gtk_scrolled_window_inner_allocation   (GtkWidget         *widget,
                                                        GtkAllocation     *rect);
static void     gtk_scrolled_window_allocate_scrollbar (GtkScrolledWindow *scrolled_window,
                                                        GtkWidget         *scrollbar,
                                                        GtkAllocation     *allocation);
static void     gtk_scrolled_window_allocate_child     (GtkScrolledWindow *swindow,
                                                        GtkAllocation     *relative_allocation);
363 364
static void     gtk_scrolled_window_adjustment_changed (GtkAdjustment     *adjustment,
                                                        gpointer           data);
365 366
static void     gtk_scrolled_window_adjustment_value_changed (GtkAdjustment     *adjustment,
                                                              gpointer           data);
367
static gboolean gtk_widget_should_animate              (GtkWidget           *widget);
368

369
static void  gtk_scrolled_window_get_preferred_width   (GtkWidget           *widget,
370 371
							gint                *minimum_size,
							gint                *natural_size);
372
static void  gtk_scrolled_window_get_preferred_height  (GtkWidget           *widget,
373 374
							gint                *minimum_size,
							gint                *natural_size);
375
static void  gtk_scrolled_window_get_preferred_height_for_width  (GtkWidget           *layout,
376 377 378
							gint                 width,
							gint                *minimum_height,
							gint                *natural_height);
379
static void  gtk_scrolled_window_get_preferred_width_for_height  (GtkWidget           *layout,
380 381 382
							gint                 width,
							gint                *minimum_height,
							gint                *natural_height);
383

384 385
static void  gtk_scrolled_window_map                   (GtkWidget           *widget);
static void  gtk_scrolled_window_unmap                 (GtkWidget           *widget);
386 387
static void  gtk_scrolled_window_realize               (GtkWidget           *widget);
static void  gtk_scrolled_window_unrealize             (GtkWidget           *widget);
388

389 390 391
static void  gtk_scrolled_window_grab_notify           (GtkWidget           *widget,
                                                        gboolean             was_grabbed);

392 393 394
static void _gtk_scrolled_window_set_adjustment_value  (GtkScrolledWindow *scrolled_window,
                                                        GtkAdjustment     *adjustment,
                                                        gdouble            value);
395

396 397
static void gtk_scrolled_window_cancel_deceleration (GtkScrolledWindow *scrolled_window);

398 399 400 401 402
static gboolean _gtk_scrolled_window_get_overshoot (GtkScrolledWindow *scrolled_window,
                                                    gint              *overshoot_x,
                                                    gint              *overshoot_y);

static void     gtk_scrolled_window_start_deceleration (GtkScrolledWindow *scrolled_window);
403
static gint     _gtk_scrolled_window_get_scrollbar_spacing (GtkScrolledWindow *scrolled_window);
404

405
static void     gtk_scrolled_window_update_use_indicators (GtkScrolledWindow *scrolled_window);
406 407 408 409 410
static void     remove_indicator     (GtkScrolledWindow *sw,
                                      Indicator         *indicator);
static void     indicator_stop_fade  (Indicator         *indicator);
static gboolean maybe_hide_indicator (gpointer data);

411 412
static void     indicator_start_fade (Indicator *indicator,
                                      gdouble    pos);
413 414 415
static void     indicator_set_over   (Indicator *indicator,
                                      gboolean   over);

416

417
static guint signals[LAST_SIGNAL] = {0};
418
static GParamSpec *properties[NUM_PROPERTIES];
Elliot Lee's avatar
Elliot Lee committed
419

420
G_DEFINE_TYPE_WITH_PRIVATE (GtkScrolledWindow, gtk_scrolled_window, GTK_TYPE_BIN)
Elliot Lee's avatar
Elliot Lee committed
421

422 423 424 425 426 427 428
static void
add_scroll_binding (GtkBindingSet  *binding_set,
		    guint           keyval,
		    GdkModifierType mask,
		    GtkScrollType   scroll,
		    gboolean        horizontal)
{
429
  guint keypad_keyval = keyval - GDK_KEY_Left + GDK_KEY_KP_Left;
430 431
  
  gtk_binding_entry_add_signal (binding_set, keyval, mask,
432
                                "scroll-child", 2,
433 434 435
                                GTK_TYPE_SCROLL_TYPE, scroll,
				G_TYPE_BOOLEAN, horizontal);
  gtk_binding_entry_add_signal (binding_set, keypad_keyval, mask,
436
                                "scroll-child", 2,
437 438 439 440 441 442 443 444 445
                                GTK_TYPE_SCROLL_TYPE, scroll,
				G_TYPE_BOOLEAN, horizontal);
}

static void
add_tab_bindings (GtkBindingSet    *binding_set,
		  GdkModifierType   modifiers,
		  GtkDirectionType  direction)
{
446
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_Tab, modifiers,
447
                                "move-focus-out", 1,
448
                                GTK_TYPE_DIRECTION_TYPE, direction);
449
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Tab, modifiers,
450
                                "move-focus-out", 1,
451 452 453
                                GTK_TYPE_DIRECTION_TYPE, direction);
}

454
static gboolean
455
gtk_scrolled_window_leave_notify (GtkWidget        *widget,
456 457 458 459
                                  GdkEventCrossing *event)
{
  GtkScrolledWindowPrivate *priv = GTK_SCROLLED_WINDOW (widget)->priv;

460
  if (priv->use_indicators && event->detail != GDK_NOTIFY_INFERIOR)
461 462 463 464
    {
      indicator_set_over (&priv->hindicator, FALSE);
      indicator_set_over (&priv->vindicator, FALSE);
    }
465 466 467 468

  return GDK_EVENT_PROPAGATE;
}

469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
static void
update_scrollbar_positions (GtkScrolledWindow *scrolled_window)
{
  GtkScrolledWindowPrivate *priv = scrolled_window->priv;
  GtkStyleContext *context;
  gboolean is_rtl;

  if (priv->hscrollbar != NULL)
    {
      context = gtk_widget_get_style_context (priv->hscrollbar);
      if (priv->window_placement == GTK_CORNER_TOP_LEFT ||
          priv->window_placement == GTK_CORNER_TOP_RIGHT)
        {
          gtk_style_context_add_class (context, GTK_STYLE_CLASS_BOTTOM);
          gtk_style_context_remove_class (context, GTK_STYLE_CLASS_TOP);
        }
      else
        {
          gtk_style_context_remove_class (context, GTK_STYLE_CLASS_BOTTOM);
          gtk_style_context_add_class (context, GTK_STYLE_CLASS_TOP);
        }
    }

  if (priv->vscrollbar != NULL)
    {
      context = gtk_widget_get_style_context (priv->vscrollbar);
      is_rtl = gtk_widget_get_direction (GTK_WIDGET (scrolled_window)) == GTK_TEXT_DIR_RTL;
      if ((is_rtl &&
          (priv->window_placement == GTK_CORNER_TOP_RIGHT ||
           priv->window_placement == GTK_CORNER_BOTTOM_RIGHT)) ||
         (!is_rtl &&
          (priv->window_placement == GTK_CORNER_TOP_LEFT ||
           priv->window_placement == GTK_CORNER_BOTTOM_LEFT)))
        {
503 504
          gtk_style_context_add_class (context, GTK_STYLE_CLASS_RIGHT);
          gtk_style_context_remove_class (context, GTK_STYLE_CLASS_LEFT);
505 506 507
        }
      else
        {
508 509
          gtk_style_context_remove_class (context, GTK_STYLE_CLASS_RIGHT);
          gtk_style_context_add_class (context, GTK_STYLE_CLASS_LEFT);
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
        }
    }
}

static void
gtk_scrolled_window_direction_changed (GtkWidget        *widget,
                                       GtkTextDirection  previous_dir)
{
  GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);

  update_scrollbar_positions (scrolled_window);

  GTK_WIDGET_CLASS (gtk_scrolled_window_parent_class)->direction_changed (widget, previous_dir);
}

Elliot Lee's avatar
Elliot Lee committed
525 526 527
static void
gtk_scrolled_window_class_init (GtkScrolledWindowClass *class)
{
528
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
Elliot Lee's avatar
Elliot Lee committed
529 530
  GtkWidgetClass *widget_class;
  GtkContainerClass *container_class;
531
  GtkBindingSet *binding_set;
Elliot Lee's avatar
Elliot Lee committed
532 533 534

  widget_class = (GtkWidgetClass*) class;
  container_class = (GtkContainerClass*) class;
Manish Singh's avatar
Manish Singh committed
535

536 537
  gobject_class->set_property = gtk_scrolled_window_set_property;
  gobject_class->get_property = gtk_scrolled_window_get_property;
538
  gobject_class->finalize = gtk_scrolled_window_finalize;
539

540
  widget_class->destroy = gtk_scrolled_window_destroy;
541
  widget_class->draw = gtk_scrolled_window_draw;
542 543
  widget_class->size_allocate = gtk_scrolled_window_size_allocate;
  widget_class->scroll_event = gtk_scrolled_window_scroll_event;
544
  widget_class->focus = gtk_scrolled_window_focus;
545 546 547 548
  widget_class->get_preferred_width = gtk_scrolled_window_get_preferred_width;
  widget_class->get_preferred_height = gtk_scrolled_window_get_preferred_height;
  widget_class->get_preferred_height_for_width = gtk_scrolled_window_get_preferred_height_for_width;
  widget_class->get_preferred_width_for_height = gtk_scrolled_window_get_preferred_width_for_height;
549 550 551
  widget_class->map = gtk_scrolled_window_map;
  widget_class->unmap = gtk_scrolled_window_unmap;
  widget_class->grab_notify = gtk_scrolled_window_grab_notify;
552 553
  widget_class->realize = gtk_scrolled_window_realize;
  widget_class->unrealize = gtk_scrolled_window_unrealize;
554
  widget_class->leave_notify_event = gtk_scrolled_window_leave_notify;
555
  widget_class->direction_changed = gtk_scrolled_window_direction_changed;
556 557 558 559

  container_class->add = gtk_scrolled_window_add;
  container_class->remove = gtk_scrolled_window_remove;
  container_class->forall = gtk_scrolled_window_forall;
560
  gtk_container_class_handle_border_width (container_class);
561

562
  class->scrollbar_spacing = -1;
563

564 565
  class->scroll_child = gtk_scrolled_window_scroll_child;
  class->move_focus_out = gtk_scrolled_window_move_focus_out;
566

567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604
  properties[PROP_HADJUSTMENT] =
      g_param_spec_object ("hadjustment",
                           P_("Horizontal Adjustment"),
                           P_("The GtkAdjustment for the horizontal position"),
                           GTK_TYPE_ADJUSTMENT,
                           GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT);

  properties[PROP_VADJUSTMENT] =
      g_param_spec_object ("vadjustment",
                           P_("Vertical Adjustment"),
                           P_("The GtkAdjustment for the vertical position"),
                           GTK_TYPE_ADJUSTMENT,
                           GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT);

  properties[PROP_HSCROLLBAR_POLICY] =
      g_param_spec_enum ("hscrollbar-policy",
                         P_("Horizontal Scrollbar Policy"),
                         P_("When the horizontal scrollbar is displayed"),
                         GTK_TYPE_POLICY_TYPE,
                         GTK_POLICY_AUTOMATIC,
                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

  properties[PROP_VSCROLLBAR_POLICY] =
      g_param_spec_enum ("vscrollbar-policy",
                         P_("Vertical Scrollbar Policy"),
                         P_("When the vertical scrollbar is displayed"),
			GTK_TYPE_POLICY_TYPE,
			GTK_POLICY_AUTOMATIC,
                        GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

  properties[PROP_WINDOW_PLACEMENT] =
      g_param_spec_enum ("window-placement",
                         P_("Window Placement"),
                         P_("Where the contents are located with respect to the scrollbars."),
			GTK_TYPE_CORNER_TYPE,
			GTK_CORNER_TOP_LEFT,
                        GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

605
  /**
606
   * GtkScrolledWindow:window-placement-set:
607
   *
608
   * Whether "window-placement" should be used to determine the location
609
   * of the contents with respect to the scrollbars.
610 611
   *
   * Since: 2.10
612 613 614
   *
   * Deprecated: 3.10: This value is ignored and
   * #GtkScrolledWindow:window-placement value is always honored.
615
   */
616 617 618 619 620 621 622 623 624 625 626 627 628 629
  properties[PROP_WINDOW_PLACEMENT_SET] =
      g_param_spec_boolean ("window-placement-set",
                            P_("Window Placement Set"),
                            P_("Whether \"window-placement\" should be used to determine the location of the contents with respect to the scrollbars."),
                            TRUE,
                            GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

  properties[PROP_SHADOW_TYPE] =
      g_param_spec_enum ("shadow-type",
                         P_("Shadow Type"),
                         P_("Style of bevel around the contents"),
			GTK_TYPE_SHADOW_TYPE,
			GTK_SHADOW_NONE,
                        GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
630

631 632 633 634 635 636
  /**
   * GtkScrolledWindow:scrollbars-within-bevel:
   *
   * Whether to place scrollbars within the scrolled window's bevel.
   *
   * Since: 2.12
637 638
   *
   * Deprecated: 3.20: the value of this style property is ignored.
639
   */
640 641 642 643 644
  gtk_widget_class_install_style_property (widget_class,
					   g_param_spec_boolean ("scrollbars-within-bevel",
							         P_("Scrollbars within bevel"),
							         P_("Place scrollbars within the scrolled window's bevel"),
							         FALSE,
645
							         GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
646

647
  gtk_widget_class_install_style_property (widget_class,
Matthias Clasen's avatar
x  
Matthias Clasen committed
648
					   g_param_spec_int ("scrollbar-spacing",
649 650
							     P_("Scrollbar spacing"),
							     P_("Number of pixels between the scrollbars and the scrolled window"),
651 652 653
							     0,
							     G_MAXINT,
							     DEFAULT_SCROLLBAR_SPACING,
654
							     GTK_PARAM_READABLE));
655

656 657 658 659 660 661 662
  /**
   * GtkScrolledWindow:min-content-width:
   *
   * The minimum content width of @scrolled_window, or -1 if not set.
   *
   * Since: 3.0
   */
663 664 665 666 667 668
  properties[PROP_MIN_CONTENT_WIDTH] =
      g_param_spec_int ("min-content-width",
                        P_("Minimum Content Width"),
                        P_("The minimum width that the scrolled window will allocate to its content"),
                        -1, G_MAXINT, -1,
                        GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
669 670 671 672 673 674 675 676

  /**
   * GtkScrolledWindow:min-content-height:
   *
   * The minimum content height of @scrolled_window, or -1 if not set.
   *
   * Since: 3.0
   */
677 678 679 680 681 682
  properties[PROP_MIN_CONTENT_HEIGHT] =
      g_param_spec_int ("min-content-height",
                        P_("Minimum Content Height"),
                        P_("The minimum height that the scrolled window will allocate to its content"),
                        -1, G_MAXINT, -1,
                        GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
683 684 685 686

  /**
   * GtkScrolledWindow:kinetic-scrolling:
   *
687 688
   * Whether kinetic scrolling is enabled or not. Kinetic scrolling
   * only applies to devices with source %GDK_SOURCE_TOUCHSCREEN.
689 690 691
   *
   * Since: 3.4
   */
692 693 694 695 696 697
  properties[PROP_KINETIC_SCROLLING] =
      g_param_spec_boolean ("kinetic-scrolling",
                            P_("Kinetic Scrolling"),
                            P_("Kinetic scrolling mode."),
                            TRUE,
                            GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
698

699 700 701 702 703 704 705 706 707 708
  /**
   * GtkScrolledWindow:overlay-scrolling:
   *
   * Whether overlay scrolling is enabled or not. If it is, the
   * scrollbars are only added as traditional widgets when a mouse
   * is present. Otherwise, they are overlayed on top of the content,
   * as narrow indicators.
   *
   * Since: 3.16
   */
709 710 711 712 713 714 715
  properties[PROP_OVERLAY_SCROLLING] =
      g_param_spec_boolean ("overlay-scrolling",
                            P_("Overlay Scrolling"),
                            P_("Overlay scrolling mode"),
                            TRUE,
                            GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
  /**
   * GtkScrolledWindow:max-content-width:
   *
   * The maximum content width of @scrolled_window, or -1 if not set.
   *
   * Since: 3.22
   */
  properties[PROP_MAX_CONTENT_WIDTH] =
      g_param_spec_int ("max-content-width",
                        P_("Maximum Content Width"),
                        P_("The maximum width that the scrolled window will allocate to its content"),
                        -1, G_MAXINT, -1,
                        GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

  /**
   * GtkScrolledWindow:max-content-height:
   *
   * The maximum content height of @scrolled_window, or -1 if not set.
   *
   * Since: 3.22
   */
  properties[PROP_MAX_CONTENT_HEIGHT] =
      g_param_spec_int ("max-content-height",
                        P_("Maximum Content Height"),
                        P_("The maximum height that the scrolled window will allocate to its content"),
                        -1, G_MAXINT, -1,
                        GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779
  /**
   * GtkScrolledWindow:propagate-natural-width:
   *
   * Whether the natural width of the child should be calculated and propagated
   * through the scrolled windows requested natural width.
   *
   * This is useful in cases where an attempt should be made to allocate exactly
   * enough space for the natural size of the child.
   *
   * Since: 3.22
   */
  properties[PROP_PROPAGATE_NATURAL_WIDTH] =
      g_param_spec_boolean ("propagate-natural-width",
                            P_("Propagate Natural Width"),
                            P_("Propagate Natural Width"),
                            FALSE,
                            GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

  /**
   * GtkScrolledWindow:propagate-natural-height:
   *
   * Whether the natural height of the child should be calculated and propagated
   * through the scrolled windows requested natural height.
   *
   * This is useful in cases where an attempt should be made to allocate exactly
   * enough space for the natural size of the child.
   *
   * Since: 3.22
   */
  properties[PROP_PROPAGATE_NATURAL_HEIGHT] =
      g_param_spec_boolean ("propagate-natural-height",
                            P_("Propagate Natural Height"),
                            P_("Propagate Natural Height"),
                            FALSE,
                            GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

780
  g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
781

782 783 784 785 786 787 788 789
  /**
   * GtkScrolledWindow::scroll-child:
   * @scrolled_window: a #GtkScrolledWindow
   * @scroll: a #GtkScrollType describing how much to scroll
   * @horizontal: whether the keybinding scrolls the child
   *   horizontally or not
   *
   * The ::scroll-child signal is a
790
   * [keybinding signal][GtkBindingSignal]
791 792 793 794
   * which gets emitted when a keybinding that scrolls is pressed.
   * The horizontal or vertical adjustment is updated which triggers a
   * signal that the scrolled windows child may listen to and scroll itself.
   */
795
  signals[SCROLL_CHILD] =
796
    g_signal_new (I_("scroll-child"),
797
                  G_TYPE_FROM_CLASS (gobject_class),
798 799 800
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkScrolledWindowClass, scroll_child),
                  NULL, NULL,
801 802
                  _gtk_marshal_BOOLEAN__ENUM_BOOLEAN,
                  G_TYPE_BOOLEAN, 2,
803 804
                  GTK_TYPE_SCROLL_TYPE,
		  G_TYPE_BOOLEAN);
805 806 807 808 809 810 811

  /**
   * GtkScrolledWindow::move-focus-out:
   * @scrolled_window: a #GtkScrolledWindow
   * @direction_type: either %GTK_DIR_TAB_FORWARD or
   *   %GTK_DIR_TAB_BACKWARD
   *
812
   * The ::move-focus-out signal is a
813
   * [keybinding signal][GtkBindingSignal] which gets
814
   * emitted when focus is moved away from the scrolled window by a
815
   * keybinding. The #GtkWidget::move-focus signal is emitted with
816
   * @direction_type on this scrolled windows toplevel parent in the
817
   * container hierarchy. The default bindings for this signal are
818
   * `Tab + Ctrl` and `Tab + Ctrl + Shift`.
819
   */
820
  signals[MOVE_FOCUS_OUT] =
821
    g_signal_new (I_("move-focus-out"),
822
                  G_TYPE_FROM_CLASS (gobject_class),
823 824 825
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkScrolledWindowClass, move_focus_out),
                  NULL, NULL,
826
                  NULL,
827 828
                  G_TYPE_NONE, 1,
                  GTK_TYPE_DIRECTION_TYPE);
829 830 831 832 833 834 835 836 837 838

  /**
   * GtkScrolledWindow::edge-overshot:
   * @scrolled_window: a #GtkScrolledWindow
   * @pos: edge side that was hit
   *
   * The ::edge-overshot signal is emitted whenever user initiated scrolling
   * makes the scrolledwindow firmly surpass (ie. with some edge resistance)
   * the lower or upper limits defined by the adjustment in that orientation.
   *
839 840
   * A similar behavior without edge resistance is provided by the
   * #GtkScrolledWindow::edge-reached signal.
841 842 843 844 845 846 847 848 849 850 851 852 853 854
   *
   * Note: The @pos argument is LTR/RTL aware, so callers should be aware too
   * if intending to provide behavior on horizontal edges.
   *
   * Since: 3.16
   */
  signals[EDGE_OVERSHOT] =
    g_signal_new (I_("edge-overshot"),
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_LAST, 0,
                  NULL, NULL,
                  g_cclosure_marshal_generic,
                  G_TYPE_NONE, 1, GTK_TYPE_POSITION_TYPE);

855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
  /**
   * GtkScrolledWindow::edge-reached:
   * @scrolled_window: a #GtkScrolledWindow
   * @pos: edge side that was reached
   *
   * The ::edge-reached signal is emitted whenever user-initiated scrolling
   * makes the scrolledwindow exactly reaches the lower or upper limits
   * defined by the adjustment in that orientation.
   *
   * A similar behavior with edge resistance is provided by the
   * #GtkScrolledWindow::edge-overshot signal.
   *
   * Note: The @pos argument is LTR/RTL aware, so callers should be aware too
   * if intending to provide behavior on horizontal edges.
   *
   * Since: 3.16
   */
  signals[EDGE_REACHED] =
    g_signal_new (I_("edge-reached"),
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_LAST, 0,
                  NULL, NULL,
                  g_cclosure_marshal_generic,
                  G_TYPE_NONE, 1, GTK_TYPE_POSITION_TYPE);

880 881
  binding_set = gtk_binding_set_by_class (class);

882 883 884 885
  add_scroll_binding (binding_set, GDK_KEY_Left,  GDK_CONTROL_MASK, GTK_SCROLL_STEP_BACKWARD, TRUE);
  add_scroll_binding (binding_set, GDK_KEY_Right, GDK_CONTROL_MASK, GTK_SCROLL_STEP_FORWARD,  TRUE);
  add_scroll_binding (binding_set, GDK_KEY_Up,    GDK_CONTROL_MASK, GTK_SCROLL_STEP_BACKWARD, FALSE);
  add_scroll_binding (binding_set, GDK_KEY_Down,  GDK_CONTROL_MASK, GTK_SCROLL_STEP_FORWARD,  FALSE);
886

887 888 889 890
  add_scroll_binding (binding_set, GDK_KEY_Page_Up,   GDK_CONTROL_MASK, GTK_SCROLL_PAGE_BACKWARD, TRUE);
  add_scroll_binding (binding_set, GDK_KEY_Page_Down, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_FORWARD,  TRUE);
  add_scroll_binding (binding_set, GDK_KEY_Page_Up,   0,                GTK_SCROLL_PAGE_BACKWARD, FALSE);
  add_scroll_binding (binding_set, GDK_KEY_Page_Down, 0,                GTK_SCROLL_PAGE_FORWARD,  FALSE);
891

892 893 894 895
  add_scroll_binding (binding_set, GDK_KEY_Home, GDK_CONTROL_MASK, GTK_SCROLL_START, TRUE);
  add_scroll_binding (binding_set, GDK_KEY_End,  GDK_CONTROL_MASK, GTK_SCROLL_END,   TRUE);
  add_scroll_binding (binding_set, GDK_KEY_Home, 0,                GTK_SCROLL_START, FALSE);
  add_scroll_binding (binding_set, GDK_KEY_End,  0,                GTK_SCROLL_END,   FALSE);
896 897 898

  add_tab_bindings (binding_set, GDK_CONTROL_MASK, GTK_DIR_TAB_FORWARD);
  add_tab_bindings (binding_set, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_DIR_TAB_BACKWARD);
899

900
  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SCROLLED_WINDOW_ACCESSIBLE);
901
  gtk_widget_class_set_css_name (widget_class, "scrolledwindow");
902 903
}

904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919
static gboolean
may_hscroll (GtkScrolledWindow *scrolled_window)
{
  GtkScrolledWindowPrivate *priv = scrolled_window->priv;

  return priv->hscrollbar_visible || priv->hscrollbar_policy == GTK_POLICY_EXTERNAL;
}

static gboolean
may_vscroll (GtkScrolledWindow *scrolled_window)
{
  GtkScrolledWindowPrivate *priv = scrolled_window->priv;

  return priv->vscrollbar_visible || priv->vscrollbar_policy == GTK_POLICY_EXTERNAL;
}

920 921 922 923 924 925
static inline gboolean
policy_may_be_visible (GtkPolicyType policy)
{
  return policy == GTK_POLICY_ALWAYS || policy == GTK_POLICY_AUTOMATIC;
}

926 927 928 929 930 931 932
static void
scrolled_window_drag_begin_cb (GtkScrolledWindow *scrolled_window,
                               gdouble            start_x,
                               gdouble            start_y,
                               GtkGesture        *gesture)
{
  GtkScrolledWindowPrivate *priv = scrolled_window->priv;
933
  GtkEventSequenceState state;
934
  GdkEventSequence *sequence;
935 936
  GtkWidget *event_widget;
  const GdkEvent *event;
937

938
  priv->in_drag = FALSE;
939 940 941
  priv->drag_start_x = priv->unclamped_hadj_value;
  priv->drag_start_y = priv->unclamped_vadj_value;
  gtk_scrolled_window_cancel_deceleration (scrolled_window);
942 943 944
  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
  event = gtk_gesture_get_last_event (gesture, sequence);
  event_widget = gtk_get_event_widget ((GdkEvent *) event);
945

946 947
  if (event_widget == priv->vscrollbar || event_widget == priv->hscrollbar ||
      (!may_hscroll (scrolled_window) && !may_vscroll (scrolled_window)))
948 949 950 951 952 953
    state = GTK_EVENT_SEQUENCE_DENIED;
  else if (priv->capture_button_press)
    state = GTK_EVENT_SEQUENCE_CLAIMED;
  else
    return;

954
  gtk_gesture_set_sequence_state (gesture, sequence, state);
955 956
}

957 958 959
static void
gtk_scrolled_window_invalidate_overshoot (GtkScrolledWindow *scrolled_window)
{
960
  GtkAllocation child_allocation;
961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999
  gint overshoot_x, overshoot_y;
  GdkRectangle rect;

  if (!_gtk_scrolled_window_get_overshoot (scrolled_window, &overshoot_x, &overshoot_y))
    return;

  gtk_scrolled_window_relative_allocation (GTK_WIDGET (scrolled_window),
                                           &child_allocation);
  if (overshoot_x != 0)
    {
      if (overshoot_x < 0)
        rect.x = child_allocation.x;
      else
        rect.x = child_allocation.x + child_allocation.width - MAX_OVERSHOOT_DISTANCE;

      rect.y = child_allocation.y;
      rect.width = MAX_OVERSHOOT_DISTANCE;
      rect.height = child_allocation.height;

      gdk_window_invalidate_rect (gtk_widget_get_window (GTK_WIDGET (scrolled_window)),
                                  &rect, TRUE);
    }

  if (overshoot_y != 0)
    {
      if (overshoot_y < 0)
        rect.y = child_allocation.y;
      else
        rect.y = child_allocation.y + child_allocation.height - MAX_OVERSHOOT_DISTANCE;

      rect.x = child_allocation.x;
      rect.width = child_allocation.width;
      rect.height = MAX_OVERSHOOT_DISTANCE;

      gdk_window_invalidate_rect (gtk_widget_get_window (GTK_WIDGET (scrolled_window)),
                                  &rect, TRUE);
    }
}

1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010
static void
scrolled_window_drag_update_cb (GtkScrolledWindow *scrolled_window,
                                gdouble            offset_x,
                                gdouble            offset_y,
                                GtkGesture        *gesture)
{
  GtkScrolledWindowPrivate *priv = scrolled_window->priv;
  GtkAdjustment *hadjustment;
  GtkAdjustment *vadjustment;
  gdouble dx, dy;

1011 1012
  gtk_scrolled_window_invalidate_overshoot (scrolled_window);

1013 1014 1015 1016
  if (!priv->capture_button_press)
    {
      GdkEventSequence *sequence;

1017 1018 1019
      sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
      gtk_gesture_set_sequence_state (gesture, sequence,
                                      GTK_EVENT_SEQUENCE_CLAIMED);
1020
    }
1021 1022

  hadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar));
1023
  if (hadjustment && may_hscroll (scrolled_window))
1024
    {
1025
      dx = priv->drag_start_x - offset_x;
1026 1027
      _gtk_scrolled_window_set_adjustment_value (scrolled_window,
                                                 hadjustment, dx);
1028 1029 1030
    }

  vadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar));
1031
  if (vadjustment && may_vscroll (scrolled_window))
1032
    {
1033
      dy = priv->drag_start_y - offset_y;
1034 1035
      _gtk_scrolled_window_set_adjustment_value (scrolled_window,
                                                 vadjustment, dy);
1036 1037
    }

1038
  gtk_scrolled_window_invalidate_overshoot (scrolled_window);
1039 1040
}

1041 1042
static void
scrolled_window_drag_end_cb (GtkScrolledWindow *scrolled_window,
1043
                             GdkEventSequence  *sequence,
1044 1045 1046 1047
                             GtkGesture        *gesture)
{
  GtkScrolledWindowPrivate *priv = scrolled_window->priv;

1048 1049
  if (!priv->in_drag || !gtk_gesture_handles_sequence (gesture, sequence))
    gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
1050 1051
}

1052
static void
1053 1054 1055
gtk_scrolled_window_decelerate (GtkScrolledWindow *scrolled_window,
                                gdouble            x_velocity,
                                gdouble            y_velocity)
1056 1057 1058 1059 1060
{
  GtkScrolledWindowPrivate *priv = scrolled_window->priv;
  gboolean overshoot;

  overshoot = _gtk_scrolled_window_get_overshoot (scrolled_window, NULL, NULL);
1061 1062
  priv->x_velocity = x_velocity;
  priv->y_velocity = y_velocity;
1063

1064 1065
  /* Zero out vector components for which we don't scroll */
  if (!may_hscroll (scrolled_window))
1066
    priv->x_velocity = 0;
1067
  if (!may_vscroll (scrolled_window))
1068 1069 1070 1071 1072 1073 1074 1075 1076
    priv->y_velocity = 0;

  if (priv->x_velocity != 0 || priv->y_velocity != 0 || overshoot)
    {
      gtk_scrolled_window_start_deceleration (scrolled_window);
      priv->x_velocity = priv->y_velocity = 0;
    }
}

1077 1078 1079 1080 1081 1082 1083 1084
static void
scrolled_window_swipe_cb (GtkScrolledWindow *scrolled_window,
                          gdouble            x_velocity,
                          gdouble            y_velocity)
{
  gtk_scrolled_window_decelerate (scrolled_window, -x_velocity, -y_velocity);
}

1085 1086 1087 1088 1089 1090 1091 1092
static void
scrolled_window_long_press_cb (GtkScrolledWindow *scrolled_window,
                               gdouble            x,
                               gdouble            y,
                               GtkGesture        *gesture)
{
  GdkEventSequence *sequence;

1093 1094 1095
  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
  gtk_gesture_set_sequence_state (gesture, sequence,
                                  GTK_EVENT_SEQUENCE_DENIED);
1096 1097
}

1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108
static void
scrolled_window_long_press_cancelled_cb (GtkScrolledWindow *scrolled_window,
                                         GtkGesture        *gesture)
{
  GtkScrolledWindowPrivate *priv = scrolled_window->priv;
  GdkEventSequence *sequence;
  const GdkEvent *event;

  sequence = gtk_gesture_get_last_updated_sequence (gesture);
  event = gtk_gesture_get_last_event (gesture, sequence);

1109 1110 1111 1112
  if (event->type == GDK_TOUCH_BEGIN ||
      event->type == GDK_BUTTON_PRESS)
    gtk_gesture_set_sequence_state (gesture, sequence,
                                    GTK_EVENT_SEQUENCE_DENIED);
1113 1114
  else if (event->type != GDK_TOUCH_END &&
           event->type != GDK_BUTTON_RELEASE)
1115 1116 1117
    priv->in_drag = TRUE;
}

1118 1119 1120
static void
gtk_scrolled_window_check_attach_pan_gesture (GtkScrolledWindow *sw)
{
1121
  GtkPropagationPhase phase = GTK_PHASE_NONE;
1122 1123 1124
  GtkScrolledWindowPrivate *priv = sw->priv;

  if (priv->kinetic_scrolling &&
1125 1126
      ((may_hscroll (sw) && !may_vscroll (sw)) ||
       (!may_hscroll (sw) && may_vscroll (sw))))
1127
    {
1128
      GtkOrientation orientation;
1129

1130
      if (may_hscroll (sw))
1131
        orientation = GTK_ORIENTATION_HORIZONTAL;
1132
      else
1133
        orientation = GTK_ORIENTATION_VERTICAL;
1134 1135 1136

      gtk_gesture_pan_set_orientation (GTK_GESTURE_PAN (priv->pan_gesture),
                                       orientation);
1137
      phase = GTK_PHASE_CAPTURE;
1138
    }
1139 1140

  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->pan_gesture), phase);
1141 1142
}

1143 1144 1145 1146 1147 1148
static void
indicator_set_over (Indicator *indicator,
                    gboolean   over)
{
  GtkStyleContext *context;

1149 1150 1151 1152 1153 1154
  if (indicator->over_timeout_id)
    {
      g_source_remove (indicator->over_timeout_id);
      indicator->over_timeout_id = 0;
    }

1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168
  if (indicator->over == over)
    return;

  context = gtk_widget_get_style_context (indicator->scrollbar);
  indicator->over = over;

  if (indicator->over)
    gtk_style_context_add_class (context, "hovering");
  else
    gtk_style_context_remove_class (context, "hovering");

  gtk_widget_queue_resize (indicator->scrollbar);
}

1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205
static void
translate_to_widget (GtkWidget *widget,
                     GdkEvent  *event,
                     gint      *x,
                     gint      *y)
{
  GtkWidget *event_widget;
  GdkWindow *event_widget_window;
  GdkWindow *window;
  gdouble event_x, event_y;
  gint wx, wy;
  GtkAllocation allocation;

  event_widget = gtk_get_event_widget (event);
  event_widget_window = gtk_widget_get_window (event_widget);
  gdk_event_get_coords (event, &event_x, &event_y);
  window = event->any.window;
  while (window && window != event_widget_window)
    {
      gdk_window_get_position (window, &wx, &wy);
      event_x += wx;
      event_y += wy;
      window = gdk_window_get_effective_parent (window);
    }

  if (!gtk_widget_get_has_window (event_widget))
    {
      gtk_widget_get_allocation (event_widget, &allocation);
      event_x -= allocation.x;
      event_y -= allocation.y;
    }

  gtk_widget_translate_coordinates (event_widget, widget,
                                    (gint)event_x, (gint)event_y,
                                    x, y);
}

1206 1207 1208 1209 1210
static gboolean
event_close_to_indicator (GtkScrolledWindow *sw,
                          Indicator         *indicator,
                          GdkEvent          *event)
{
1211
  GtkScrolledWindowPrivate *priv;
1212 1213
  GtkAllocation indicator_alloc;
  gint x, y;
1214
  gint distance;
1215
  gint win_x, win_y;
1216

1217
  priv = sw->priv;
1218 1219 1220

  gtk_widget_get_allocation (indicator->scrollbar, &indicator_alloc);
  gdk_window_get_position (indicator->window, &win_x, &win_y);
1221
  translate_to_widget (GTK_WIDGET (sw), event, &x, &y);
1222

1223 1224 1225 1226 1227
  if (indicator->over)
    distance = INDICATOR_FAR_DISTANCE;
  else
    distance = INDICATOR_CLOSE_DISTANCE;

1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239
  if (indicator == &priv->hindicator)
    {
       if (y >= win_y - distance &&
           y < win_y + indicator_alloc.height + distance)
         return TRUE;
    }
  else if (indicator == &priv->vindicator)
    {
      if (x >= win_x - distance &&
          x < win_x + indicator_alloc.width + distance)
        return TRUE;
    }
1240 1241 1242 1243

  return FALSE;
}

1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257
static gboolean
enable_over_timeout_cb (gpointer user_data)
{
  Indicator *indicator = user_data;

  indicator_set_over (indicator, TRUE);
  return G_SOURCE_REMOVE;
}

static gboolean
check_update_scrollbar_proximity (GtkScrolledWindow *sw,
                                  Indicator         *indicator,
                                  GdkEvent          *event)
{
1258 1259
  GtkScrolledWindowPrivate *priv = sw->priv;
  gboolean indicator_close, on_scrollbar, on_other_scrollbar;
1260 1261 1262
  GtkWidget *event_widget;

  event_widget = gtk_get_event_widget (event);
1263 1264

  indicator_close = event_close_to_indicator (sw, indicator, event);
1265 1266
  on_scrollbar = (event_widget == indicator->scrollbar &&
                  event->type != GDK_LEAVE_NOTIFY);
1267 1268 1269 1270
  on_other_scrollbar = (!on_scrollbar &&
                        event->type != GDK_LEAVE_NOTIFY &&
                        (event_widget == priv->hindicator.scrollbar ||
                         event_widget == priv->vindicator.scrollbar));
1271 1272 1273 1274 1275 1276 1277

  if (indicator->over_timeout_id)
    {
      g_source_remove (indicator->over_timeout_id);
      indicator->over_timeout_id = 0;
    }

1278 1279
  if (on_scrollbar)
    indicator_set_over (indicator, TRUE);
1280
  else if (indicator_close && !on_other_scrollbar)
1281 1282 1283 1284 1285 1286 1287
    indicator->over_timeout_id = gdk_threads_add_timeout (30, enable_over_timeout_cb, indicator);
  else
    indicator_set_over (indicator, FALSE);

  return indicator_close;
}

1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317
static gdouble
get_scroll_unit (GtkScrolledWindow *sw,
                 GtkOrientation     orientation)
{
  gdouble scroll_unit;

#ifndef GDK_WINDOWING_QUARTZ
  GtkScrolledWindowPrivate *priv = sw->priv;
  GtkRange *scrollbar;
  GtkAdjustment *adj;
  gdouble page_size;

  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    scrollbar = GTK_RANGE (priv->hscrollbar);
  else
    scrollbar = GTK_RANGE (priv->vscrollbar);

  if (!scrollbar)
    return 0;

  adj = gtk_range_get_adjustment (scrollbar);
  page_size = gtk_adjustment_get_page_size (adj);
  scroll_unit = pow (page_size, 2.0 / 3.0);
#else
  scroll_unit = 1;
#endif

  return scroll_unit;
}

1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 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 1384 1385 1386 1387
static void
scroll_history_push (GtkScrolledWindow *sw,
                     GdkEventScroll    *event)
{
  GtkScrolledWindowPrivate *priv = sw->priv;
  ScrollHistoryElem new_item;
  guint i;

  if (event->direction != GDK_SCROLL_SMOOTH)
    return;

  for (i = 0; i < priv->scroll_history->len; i++)
    {
      ScrollHistoryElem *elem;

      elem = &g_array_index (priv->scroll_history, ScrollHistoryElem, i);

      if (elem->evtime >= event->time - SCROLL_CAPTURE_THRESHOLD_MS)
        break;
    }

  if (i > 0)
    g_array_remove_range (priv->scroll_history, 0, i);

  new_item.dx = event->delta_x;
  new_item.dy = event->delta_y;
  new_item.evtime = event->time;
  g_array_append_val (priv->scroll_history, new_item);
}

static void
scroll_history_reset (GtkScrolledWindow *sw)
{
  GtkScrolledWindowPrivate *priv = sw->priv;

  if (priv->scroll_history->len == 0)
    return;

  g_array_remove_range (priv->scroll_history, 0,
                        priv->scroll_history->len);
}

static gboolean
scroll_history_finish (GtkScrolledWindow *sw,
                       gdouble           *velocity_x,
                       gdouble           *velocity_y)
{
  GtkScrolledWindowPrivate *priv = sw->priv;
  gdouble accum_dx = 0, accum_dy = 0;
  guint32 first = 0, last = 0;
  gdouble xunit, yunit;
  guint i;

  if (priv->scroll_history->len == 0)
    return FALSE;

  for (i = 0; i < priv->scroll_history->len; i++)
    {
      ScrollHistoryElem *elem;

      elem = &g_array_index (priv->scroll_history, ScrollHistoryElem, i);
      accum_dx += elem->dx;
      accum_dy += elem->dy;
      last = elem->evtime;

      if (i == 0)
        first = elem->evtime;
    }

  if (last == first)
1388 1389 1390 1391
    {
      scroll_history_reset (sw);
      return FALSE;
    }
1392 1393 1394 1395 1396 1397 1398 1399 1400 1401

  xunit = get_scroll_unit (sw, GTK_ORIENTATION_HORIZONTAL);
  yunit = get_scroll_unit (sw, GTK_ORIENTATION_VERTICAL);
  *velocity_x = (accum_dx * 1000 * xunit) / (last - first);
  *velocity_y = (accum_dy * 1000 * yunit) / (last - first);
  scroll_history_reset (sw);

  return TRUE;
}

1402 1403 1404 1405 1406 1407 1408 1409
static gboolean
captured_event_cb (GtkWidget *widget,
                   GdkEvent  *event)
{
  GtkScrolledWindowPrivate *priv;
  GtkScrolledWindow *sw;
  GdkInputSource input_source;
  GdkDevice *source_device;
1410 1411
  GtkWidget *event_widget;
  gboolean on_scrollbar;
1412

1413 1414
  sw = GTK_SCROLLED_WINDOW (widget);
  priv = sw->priv;
1415
  source_device = gdk_event_get_source_device (event);
1416

1417 1418 1419 1420 1421 1422
  if (event->type == GDK_SCROLL)
    {
      gtk_scrolled_window_cancel_deceleration (sw);
      return GDK_EVENT_PROPAGATE;
    }

1423
  if (!priv->use_indicators)
1424 1425
    return GDK_EVENT_PROPAGATE;

1426 1427 1428 1429 1430 1431
  if (event->type != GDK_MOTION_NOTIFY &&
      event->type != GDK_LEAVE_NOTIFY)
    return GDK_EVENT_PROPAGATE;

  input_source = gdk_device_get_source (source_device);

1432 1433
  if (input_source == GDK_SOURCE_KEYBOARD ||
      input_source == GDK_SOURCE_TOUCHSCREEN)
1434 1435
    return GDK_EVENT_PROPAGATE;

1436 1437 1438 1439
  event_widget = gtk_get_event_widget (event);
  on_scrollbar = (event_widget == priv->hindicator.scrollbar ||
                  event_widget == priv->vindicator.scrollbar);

1440
  if (event->type == GDK_MOTION_NOTIFY)
1441
    {
1442 1443 1444 1445
      if (priv->hscrollbar_visible)
        indicator_start_fade (&priv->hindicator, 1.0);
      if (priv->vscrollbar_visible)
        indicator_start_fade (&priv->vindicator, 1.0);
1446

1447 1448 1449
      if (!on_scrollbar &&
           (event->motion.state &
            (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK)) != 0)
1450 1451 1452 1453
        {
          indicator_set_over (&priv->hindicator, FALSE);
          indicator_set_over (&priv->vindicator, FALSE);
        }
1454 1455
      else if (input_source == GDK_SOURCE_PEN ||
               input_source == GDK_SOURCE_ERASER ||
1456
               input_source == GDK_SOURCE_TRACKPOINT)
1457
        {
1458 1459
          indicator_set_over (&priv->hindicator, TRUE);
          indicator_set_over (&priv->vindicator, TRUE);
1460 1461
        }
      else
1462 1463 1464 1465 1466 1467
        {
          if (!check_update_scrollbar_proximity (sw, &priv->vindicator, event))
            check_update_scrollbar_proximity (sw, &priv->hindicator, event);
          else
            indicator_set_over (&priv->hindicator, FALSE);
        }
1468
    }
1469
  else if (event->type == GDK_LEAVE_NOTIFY && on_scrollbar &&
1470 1471
           event->crossing.mode == GDK_CROSSING_UNGRAB)
    {
1472 1473
      check_update_scrollbar_proximity (sw, &priv->vindicator, event);
      check_update_scrollbar_proximity (sw, &priv->hindicator, event);
1474 1475 1476 1477 1478
    }

  return GDK_EVENT_PROPAGATE;
}

1479 1480 1481
/*
 * _gtk_scrolled_window_get_spacing:
 * @scrolled_window: a scrolled window
1482
 *
1483 1484
 * Gets the spacing between the scrolled window’s scrollbars and
 * the scrolled widget. Used by GtkCombo
Matthias Clasen's avatar
Matthias Clasen committed
1485
 *
1486
 * Returns: the spacing, in pixels.
Matthias Clasen's avatar
Matthias Clasen committed
1487
 */
1488 1489
static gint
_gtk_scrolled_window_get_scrollbar_spacing (GtkScrolledWindow *scrolled_window)
Elliot Lee's avatar
Elliot Lee committed
1490
{
1491
  GtkScrolledWindowClass *class;
Elliot Lee's avatar
Elliot Lee committed
1492

1493
  g_return_val_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window), 0);
1494

1495
  class = GTK_SCROLLED_WINDOW_GET_CLASS (scrolled_window);
Elliot Lee's avatar
Elliot Lee committed
1496

1497 1498 1499 1500 1501
  if (class->scrollbar_spacing >= 0)
    return class->scrollbar_spacing;
  else
    {
      gint scrollbar_spacing;
1502

1503 1504 1505 1506 1507 1508
      gtk_widget_style_get (GTK_WIDGET (scrolled_window),
			    "scrollbar-spacing", &scrollbar_spacing,
			    NULL);

      return scrollbar_spacing;
    }
1509 1510
}

1511 1512 1513 1514 1515 1516
static void
gtk_scrolled_window_allocate (GtkCssGadget        *gadget,
                              const GtkAllocation *allocation,
                              int                  baseline,
                              GtkAllocation       *out_clip,
                              gpointer             data)
1517
{
1518 1519 1520
  GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
  GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
  GtkScrolledWindowPrivate *priv = scrolled_window->priv;
1521
  GtkBin *bin;
1522 1523
  GtkAllocation relative_allocation;
  GtkAllocation child_allocation;
Javier Jardón's avatar
Javier Jardón committed
1524
  GtkWidget *child;
1525 1526 1527
  gint sb_spacing;
  gint sb_width;
  gint sb_height;
1528

1529
  bin = GTK_BIN (scrolled_window);
1530

1531 1532 1533 1534
  /* Get possible scrollbar dimensions */
  sb_spacing = _gtk_scrolled_window_get_scrollbar_spacing (scrolled_window);
  gtk_widget_get_preferred_height (priv->hscrollbar, &sb_height, NULL);
  gtk_widget_get_preferred_width (priv->vscrollbar, &sb_width, NULL);
1535

1536 1537 1538 1539 1540
  if (priv->hscrollbar_policy == GTK_POLICY_ALWAYS)
    priv->hscrollbar_visible = TRUE;
  else if (priv->hscrollbar_policy == GTK_POLICY_NEVER ||
           priv->hscrollbar_policy == GTK_POLICY_EXTERNAL)
    priv->hscrollbar_visible = FALSE;
1541