gtkscrolledwindow.c 150 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"
Matthias Clasen's avatar
Matthias Clasen committed
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

49 50
#include <math.h>

51 52 53 54
/**
 * SECTION:gtkscrolledwindow
 * @Short_description: Adds scrollbars to its child widget
 * @Title: GtkScrolledWindow
55
 * @See_also: #GtkScrollable, #GtkViewport, #GtkAdjustment
56
 *
57 58
 * GtkScrolledWindow is a #GtkBin subclass: it’s a container
 * the accepts a single child widget. GtkScrolledWindow adds scrollbars
59 60 61 62
 * to the child widget and optionally draws a beveled frame around the
 * child widget.
 *
 * The scrolled window can work in two ways. Some widgets have native
63
 * scrolling support; these widgets implement the #GtkScrollable interface.
64 65 66 67 68 69
 * Widgets with native scroll support include #GtkTreeView, #GtkTextView,
 * and #GtkLayout.
 *
 * For widgets that lack native scrolling support, the #GtkViewport
 * widget acts as an adaptor class, implementing scrollability for child
 * widgets that lack their own scrolling capabilities. Use #GtkViewport
70
 * to scroll child widgets such as #GtkGrid, #GtkBox, and so on.
71 72
 *
 * If a widget has native scrolling abilities, it can be added to the
73
 * GtkScrolledWindow with gtk_container_add(). If a widget does not, you
74
 * must first add the widget to a #GtkViewport, then add the #GtkViewport
75
 * to the scrolled window. gtk_container_add() will do this for you for
76
 * widgets that don’t implement #GtkScrollable natively, so you can
77 78
 * ignore the presence of the viewport.
 *
79 80 81
 * The position of the scrollbars is controlled by the scroll adjustments.
 * See #GtkAdjustment for the fields in an adjustment — for
 * #GtkScrollbar, used by GtkScrolledWindow, the “value” field
82
 * represents the position of the scrollbar, which must be between the
William Jon McCann's avatar
William Jon McCann committed
83
 * “lower” field and “upper - page_size.” The “page_size” field
84
 * represents the size of the visible scrollable area. The
William Jon McCann's avatar
William Jon McCann committed
85
 * “step_increment” and “page_increment” fields are used when the user
86 87 88
 * asks to step down (using the small stepper arrows) or page down (using
 * for example the PageDown key).
 *
89
 * If a GtkScrolledWindow doesn’t behave quite as you would like, or
90
 * doesn’t have exactly the right layout, it’s very possible to set up
91
 * your own scrolling with #GtkScrollbar and for example a #GtkGrid.
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
 *
 * # 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.
109 110 111 112
 *
 * # CSS nodes
 *
 * GtkScrolledWindow has a main CSS node with name scrolledwindow.
113 114
 *
 * It uses subnodes with names overshoot and undershoot to
115 116
 * draw the overflow and underflow indications. These nodes get
 * the .left, .right, .top or .bottom style class added depending
117 118 119
 * on where the indication is drawn.
 *
 * GtkScrolledWindow also sets the positional style classes (.left,
120 121
 * .right, .top, .bottom) and style classes related to overlay
 * scrolling (.overlay-indicator, .dragging, .hovering) on its scrollbars.
122 123 124
 *
 * If both scrollbars are visible, the area where they meet is drawn
 * with a subnode named junction.
125 126 127
 */


128 129 130 131 132 133 134
/* 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
135
 *   other than 0 through gtk_widget_set_size_request().
136
 *
137
 * a scrolled window needs (for implementing all three policy types) to
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
 * 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:
153
 * A) from 1) follows: the scrolled window shouldn’t request more space for a
154 155 156 157
 *    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
158 159
 *    child’s dimension.
 * D) from 3) follows: the scrolled window child’s minimum width and minimum height
160 161 162
 *    under A) at least correspond to the space taken up by its scrollbars.
 */

163
#define DEFAULT_SCROLLBAR_SPACING  3
164 165 166
#define TOUCH_BYPASS_CAPTURED_THRESHOLD 30

/* Kinetic scrolling */
167
#define MAX_OVERSHOOT_DISTANCE 100
168 169
#define DECELERATION_FRICTION 4
#define OVERSHOOT_FRICTION 20
170
#define SCROLL_CAPTURE_THRESHOLD_MS 150
Elliot Lee's avatar
Elliot Lee committed
171

172 173 174
/* Animated scrolling */
#define ANIMATION_DURATION 200

175
/* Overlay scrollbars */
176
#define INDICATOR_FADE_OUT_DELAY 2000
177 178
#define INDICATOR_FADE_OUT_DURATION 1000
#define INDICATOR_FADE_OUT_TIME 500
179 180
#define INDICATOR_CLOSE_DISTANCE 5
#define INDICATOR_FAR_DISTANCE 10
181

182
/* Scrolled off indication */
183
#define UNDERSHOOT_SIZE 40
184

185 186 187 188
typedef struct
{
  GtkWidget *scrollbar;
  GdkWindow *window;
189
  gboolean   over; /* either mouse over, or while dragging */
190 191 192 193 194 195 196 197 198
  gint64     last_scroll_time;
  guint      conceil_timer;

  gdouble    current_pos;
  gdouble    source_pos;
  gdouble    target_pos;
  gint64     start_time;
  gint64     end_time;
  guint      tick_id;
199
  guint      over_timeout_id;
200 201
} Indicator;

202 203 204 205 206 207 208
typedef struct
{
  gdouble dx;
  gdouble dy;
  guint32 evtime;
} ScrollHistoryElem;

209 210 211 212 213
struct _GtkScrolledWindowPrivate
{
  GtkWidget     *hscrollbar;
  GtkWidget     *vscrollbar;

214
  GtkCssGadget  *gadget;
215 216 217
  GtkCssNode    *overshoot_node[4];
  GtkCssNode    *undershoot_node[4];

218 219 220
  Indicator hindicator;
  Indicator vindicator;

221
  GtkCornerType  window_placement;
222 223 224 225 226 227
  guint16  shadow_type;

  guint    hscrollbar_policy      : 2;
  guint    vscrollbar_policy      : 2;
  guint    hscrollbar_visible     : 1;
  guint    vscrollbar_visible     : 1;
228
  guint    focus_out              : 1; /* used by ::move-focus-out implementation */
229
  guint    overlay_scrolling      : 1;
230
  guint    use_indicators         : 1;
231 232 233

  gint     min_content_width;
  gint     min_content_height;
234

235 236
  guint scroll_events_overshoot_id;

237
  /* Kinetic scrolling */
238
  GtkGesture *long_press_gesture;
239 240
  GtkGesture *swipe_gesture;

241 242 243
  GArray *scroll_history;
  GdkDevice *scroll_device;

244 245 246 247
  /* These two gestures are mutually exclusive */
  GtkGesture *drag_gesture;
  GtkGesture *pan_gesture;

248 249 250
  gdouble drag_start_x;
  gdouble drag_start_y;

251 252 253
  GdkDevice             *drag_device;
  guint                  kinetic_scrolling         : 1;
  guint                  capture_button_press      : 1;
254
  guint                  in_drag                   : 1;
255 256 257 258 259 260 261 262

  guint                  deceleration_id;

  gdouble                x_velocity;
  gdouble                y_velocity;

  gdouble                unclamped_hadj_value;
  gdouble                unclamped_vadj_value;
263
};
264

265 266 267 268 269
typedef struct
{
  GtkScrolledWindow     *scrolled_window;
  gint64                 last_deceleration_time;

270 271
  GtkKineticScrolling   *hscrolling;
  GtkKineticScrolling   *vscrolling;
272
} KineticScrollData;
273

274
enum {
Owen Taylor's avatar
Owen Taylor committed
275 276 277 278 279 280
  PROP_0,
  PROP_HADJUSTMENT,
  PROP_VADJUSTMENT,
  PROP_HSCROLLBAR_POLICY,
  PROP_VSCROLLBAR_POLICY,
  PROP_WINDOW_PLACEMENT,
281
  PROP_WINDOW_PLACEMENT_SET,
282 283
  PROP_SHADOW_TYPE,
  PROP_MIN_CONTENT_WIDTH,
284
  PROP_MIN_CONTENT_HEIGHT,
285
  PROP_KINETIC_SCROLLING,
286 287
  PROP_OVERLAY_SCROLLING,
  NUM_PROPERTIES
288 289
};

290 291 292 293 294
/* Signals */
enum
{
  SCROLL_CHILD,
  MOVE_FOCUS_OUT,
295
  EDGE_OVERSHOT,
296
  EDGE_REACHED,
297 298 299
  LAST_SIGNAL
};

300 301 302 303 304 305 306 307 308
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);

309
static void     gtk_scrolled_window_destroy            (GtkWidget         *widget);
310 311
static gboolean gtk_scrolled_window_draw               (GtkWidget         *widget,
                                                        cairo_t           *cr);
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
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);
334 335 336 337 338 339 340
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);
341 342
static void     gtk_scrolled_window_adjustment_changed (GtkAdjustment     *adjustment,
                                                        gpointer           data);
343 344
static void     gtk_scrolled_window_adjustment_value_changed (GtkAdjustment     *adjustment,
                                                              gpointer           data);
345
static gboolean gtk_scrolled_window_should_animate     (GtkScrolledWindow   *sw);
346

347
static void  gtk_scrolled_window_get_preferred_width   (GtkWidget           *widget,
348 349
							gint                *minimum_size,
							gint                *natural_size);
350
static void  gtk_scrolled_window_get_preferred_height  (GtkWidget           *widget,
351 352
							gint                *minimum_size,
							gint                *natural_size);
353
static void  gtk_scrolled_window_get_preferred_height_for_width  (GtkWidget           *layout,
354 355 356
							gint                 width,
							gint                *minimum_height,
							gint                *natural_height);
357
static void  gtk_scrolled_window_get_preferred_width_for_height  (GtkWidget           *layout,
358 359 360
							gint                 width,
							gint                *minimum_height,
							gint                *natural_height);
361

362 363
static void  gtk_scrolled_window_map                   (GtkWidget           *widget);
static void  gtk_scrolled_window_unmap                 (GtkWidget           *widget);
364 365
static void  gtk_scrolled_window_realize               (GtkWidget           *widget);
static void  gtk_scrolled_window_unrealize             (GtkWidget           *widget);
366

367 368 369
static void  gtk_scrolled_window_grab_notify           (GtkWidget           *widget,
                                                        gboolean             was_grabbed);

370 371 372
static void _gtk_scrolled_window_set_adjustment_value  (GtkScrolledWindow *scrolled_window,
                                                        GtkAdjustment     *adjustment,
                                                        gdouble            value);
373

374 375
static void gtk_scrolled_window_cancel_deceleration (GtkScrolledWindow *scrolled_window);

376 377 378 379 380
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);
381
static gint     _gtk_scrolled_window_get_scrollbar_spacing (GtkScrolledWindow *scrolled_window);
382

383 384 385 386 387
static void     remove_indicator     (GtkScrolledWindow *sw,
                                      Indicator         *indicator);
static void     indicator_stop_fade  (Indicator         *indicator);
static gboolean maybe_hide_indicator (gpointer data);

388 389
static void     indicator_start_fade (Indicator *indicator,
                                      gdouble    pos);
390 391 392
static void     indicator_set_over   (Indicator *indicator,
                                      gboolean   over);

393

394
static guint signals[LAST_SIGNAL] = {0};
395
static GParamSpec *properties[NUM_PROPERTIES];
Elliot Lee's avatar
Elliot Lee committed
396

397
G_DEFINE_TYPE_WITH_PRIVATE (GtkScrolledWindow, gtk_scrolled_window, GTK_TYPE_BIN)
Elliot Lee's avatar
Elliot Lee committed
398

399 400 401 402 403 404 405
static void
add_scroll_binding (GtkBindingSet  *binding_set,
		    guint           keyval,
		    GdkModifierType mask,
		    GtkScrollType   scroll,
		    gboolean        horizontal)
{
406
  guint keypad_keyval = keyval - GDK_KEY_Left + GDK_KEY_KP_Left;
407 408
  
  gtk_binding_entry_add_signal (binding_set, keyval, mask,
409
                                "scroll-child", 2,
410 411 412
                                GTK_TYPE_SCROLL_TYPE, scroll,
				G_TYPE_BOOLEAN, horizontal);
  gtk_binding_entry_add_signal (binding_set, keypad_keyval, mask,
413
                                "scroll-child", 2,
414 415 416 417 418 419 420 421 422
                                GTK_TYPE_SCROLL_TYPE, scroll,
				G_TYPE_BOOLEAN, horizontal);
}

static void
add_tab_bindings (GtkBindingSet    *binding_set,
		  GdkModifierType   modifiers,
		  GtkDirectionType  direction)
{
423
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_Tab, modifiers,
424
                                "move-focus-out", 1,
425
                                GTK_TYPE_DIRECTION_TYPE, direction);
426
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Tab, modifiers,
427
                                "move-focus-out", 1,
428 429 430
                                GTK_TYPE_DIRECTION_TYPE, direction);
}

431
static gboolean
432
gtk_scrolled_window_leave_notify (GtkWidget        *widget,
433 434 435 436
                                  GdkEventCrossing *event)
{
  GtkScrolledWindowPrivate *priv = GTK_SCROLLED_WINDOW (widget)->priv;

437
  if (priv->use_indicators && event->detail != GDK_NOTIFY_INFERIOR)
438 439 440 441
    {
      indicator_set_over (&priv->hindicator, FALSE);
      indicator_set_over (&priv->vindicator, FALSE);
    }
442 443 444 445

  return GDK_EVENT_PROPAGATE;
}

446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 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
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)))
        {
          gtk_style_context_add_class (context, GTK_STYLE_CLASS_LEFT);
          gtk_style_context_remove_class (context, GTK_STYLE_CLASS_RIGHT);
        }
      else
        {
          gtk_style_context_remove_class (context, GTK_STYLE_CLASS_LEFT);
          gtk_style_context_add_class (context, GTK_STYLE_CLASS_RIGHT);
        }
    }
}

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
502 503 504
static void
gtk_scrolled_window_class_init (GtkScrolledWindowClass *class)
{
505
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
Elliot Lee's avatar
Elliot Lee committed
506 507
  GtkWidgetClass *widget_class;
  GtkContainerClass *container_class;
508
  GtkBindingSet *binding_set;
Elliot Lee's avatar
Elliot Lee committed
509 510 511

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

Owen Taylor's avatar
Owen Taylor committed
513 514
  gobject_class->set_property = gtk_scrolled_window_set_property;
  gobject_class->get_property = gtk_scrolled_window_get_property;
515

516
  widget_class->destroy = gtk_scrolled_window_destroy;
517
  widget_class->draw = gtk_scrolled_window_draw;
518 519
  widget_class->size_allocate = gtk_scrolled_window_size_allocate;
  widget_class->scroll_event = gtk_scrolled_window_scroll_event;
520
  widget_class->focus = gtk_scrolled_window_focus;
521 522 523 524
  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;
525 526 527
  widget_class->map = gtk_scrolled_window_map;
  widget_class->unmap = gtk_scrolled_window_unmap;
  widget_class->grab_notify = gtk_scrolled_window_grab_notify;
528 529
  widget_class->realize = gtk_scrolled_window_realize;
  widget_class->unrealize = gtk_scrolled_window_unrealize;
530
  widget_class->leave_notify_event = gtk_scrolled_window_leave_notify;
531
  widget_class->direction_changed = gtk_scrolled_window_direction_changed;
532 533 534 535

  container_class->add = gtk_scrolled_window_add;
  container_class->remove = gtk_scrolled_window_remove;
  container_class->forall = gtk_scrolled_window_forall;
536
  gtk_container_class_handle_border_width (container_class);
537

538
  class->scrollbar_spacing = -1;
539

540 541
  class->scroll_child = gtk_scrolled_window_scroll_child;
  class->move_focus_out = gtk_scrolled_window_move_focus_out;
542

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 573 574 575 576 577 578 579 580
  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);

581
  /**
582
   * GtkScrolledWindow:window-placement-set:
583
   *
584
   * Whether "window-placement" should be used to determine the location
585
   * of the contents with respect to the scrollbars.
586 587
   *
   * Since: 2.10
588 589 590
   *
   * Deprecated: 3.10: This value is ignored and
   * #GtkScrolledWindow:window-placement value is always honored.
591
   */
592 593 594 595 596 597 598 599 600 601 602 603 604 605
  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);
606

607 608 609 610 611 612
  /**
   * GtkScrolledWindow:scrollbars-within-bevel:
   *
   * Whether to place scrollbars within the scrolled window's bevel.
   *
   * Since: 2.12
613 614
   *
   * Deprecated: 3.20: the value of this style property is ignored.
615
   */
616 617 618 619 620
  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,
621
							         GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
622

623
  gtk_widget_class_install_style_property (widget_class,
Matthias Clasen's avatar
x  
Matthias Clasen committed
624
					   g_param_spec_int ("scrollbar-spacing",
625 626
							     P_("Scrollbar spacing"),
							     P_("Number of pixels between the scrollbars and the scrolled window"),
627 628 629
							     0,
							     G_MAXINT,
							     DEFAULT_SCROLLBAR_SPACING,
630
							     GTK_PARAM_READABLE));
631

632 633 634 635 636 637 638
  /**
   * GtkScrolledWindow:min-content-width:
   *
   * The minimum content width of @scrolled_window, or -1 if not set.
   *
   * Since: 3.0
   */
639 640 641 642 643 644
  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);
645 646 647 648 649 650 651 652

  /**
   * GtkScrolledWindow:min-content-height:
   *
   * The minimum content height of @scrolled_window, or -1 if not set.
   *
   * Since: 3.0
   */
653 654 655 656 657 658
  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);
659 660 661 662

  /**
   * GtkScrolledWindow:kinetic-scrolling:
   *
663 664
   * Whether kinetic scrolling is enabled or not. Kinetic scrolling
   * only applies to devices with source %GDK_SOURCE_TOUCHSCREEN.
665 666 667
   *
   * Since: 3.4
   */
668 669 670 671 672 673
  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);
674

675 676 677 678 679 680 681 682 683 684
  /**
   * 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
   */
685 686 687 688 689 690 691 692
  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);

  g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
693

694 695 696 697 698 699 700 701
  /**
   * 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
702
   * [keybinding signal][GtkBindingSignal]
703 704 705 706
   * 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.
   */
707
  signals[SCROLL_CHILD] =
708
    g_signal_new (I_("scroll-child"),
709
                  G_TYPE_FROM_CLASS (gobject_class),
710 711 712
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkScrolledWindowClass, scroll_child),
                  NULL, NULL,
713 714
                  _gtk_marshal_BOOLEAN__ENUM_BOOLEAN,
                  G_TYPE_BOOLEAN, 2,
715 716
                  GTK_TYPE_SCROLL_TYPE,
		  G_TYPE_BOOLEAN);
717 718 719 720 721 722 723

  /**
   * GtkScrolledWindow::move-focus-out:
   * @scrolled_window: a #GtkScrolledWindow
   * @direction_type: either %GTK_DIR_TAB_FORWARD or
   *   %GTK_DIR_TAB_BACKWARD
   *
724
   * The ::move-focus-out signal is a
725
   * [keybinding signal][GtkBindingSignal] which gets
726
   * emitted when focus is moved away from the scrolled window by a
727
   * keybinding. The #GtkWidget::move-focus signal is emitted with
728
   * @direction_type on this scrolled windows toplevel parent in the
729
   * container hierarchy. The default bindings for this signal are
730
   * `Tab + Ctrl` and `Tab + Ctrl + Shift`.
731
   */
732
  signals[MOVE_FOCUS_OUT] =
733
    g_signal_new (I_("move-focus-out"),
734
                  G_TYPE_FROM_CLASS (gobject_class),
735 736 737 738 739 740
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkScrolledWindowClass, move_focus_out),
                  NULL, NULL,
                  _gtk_marshal_VOID__ENUM,
                  G_TYPE_NONE, 1,
                  GTK_TYPE_DIRECTION_TYPE);
741 742 743 744 745 746 747 748 749 750

  /**
   * 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.
   *
751 752
   * A similar behavior without edge resistance is provided by the
   * #GtkScrolledWindow::edge-reached signal.
753 754 755 756 757 758 759 760 761 762 763 764 765 766
   *
   * 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);

767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791
  /**
   * 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);

792 793
  binding_set = gtk_binding_set_by_class (class);

794 795 796 797
  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);
798

799 800 801 802
  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);
803

804 805 806 807
  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);
808 809 810

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

812
  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SCROLLED_WINDOW_ACCESSIBLE);
813
  gtk_widget_class_set_css_name (widget_class, "scrolledwindow");
814 815
}

816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831
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;
}

832 833 834 835 836 837
static inline gboolean
policy_may_be_visible (GtkPolicyType policy)
{
  return policy == GTK_POLICY_ALWAYS || policy == GTK_POLICY_AUTOMATIC;
}

838 839 840 841 842 843 844
static void
scrolled_window_drag_begin_cb (GtkScrolledWindow *scrolled_window,
                               gdouble            start_x,
                               gdouble            start_y,
                               GtkGesture        *gesture)
{
  GtkScrolledWindowPrivate *priv = scrolled_window->priv;
845
  GtkEventSequenceState state;
846
  GdkEventSequence *sequence;
847 848
  GtkWidget *event_widget;
  const GdkEvent *event;
849

850
  priv->in_drag = FALSE;
851 852 853
  priv->drag_start_x = priv->unclamped_hadj_value;
  priv->drag_start_y = priv->unclamped_vadj_value;
  gtk_scrolled_window_cancel_deceleration (scrolled_window);
854 855 856
  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);
857

858 859
  if (event_widget == priv->vscrollbar || event_widget == priv->hscrollbar ||
      (!may_hscroll (scrolled_window) && !may_vscroll (scrolled_window)))
860 861 862 863 864 865
    state = GTK_EVENT_SEQUENCE_DENIED;
  else if (priv->capture_button_press)
    state = GTK_EVENT_SEQUENCE_CLAIMED;
  else
    return;

866
  gtk_gesture_set_sequence_state (gesture, sequence, state);
867 868
}

869 870 871
static void
gtk_scrolled_window_invalidate_overshoot (GtkScrolledWindow *scrolled_window)
{
872
  GtkAllocation child_allocation;
873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911
  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);
    }
}

912 913 914 915 916 917 918 919 920 921 922
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;

923 924
  gtk_scrolled_window_invalidate_overshoot (scrolled_window);

925 926 927 928
  if (!priv->capture_button_press)
    {
      GdkEventSequence *sequence;

929 930 931
      sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
      gtk_gesture_set_sequence_state (gesture, sequence,
                                      GTK_EVENT_SEQUENCE_CLAIMED);
932
    }
933 934

  hadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar));
935
  if (hadjustment && may_hscroll (scrolled_window))
936
    {
937
      dx = priv->drag_start_x - offset_x;
938 939
      _gtk_scrolled_window_set_adjustment_value (scrolled_window,
                                                 hadjustment, dx);
940 941 942
    }

  vadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar));
943
  if (vadjustment && may_vscroll (scrolled_window))
944
    {
945
      dy = priv->drag_start_y - offset_y;
946 947
      _gtk_scrolled_window_set_adjustment_value (scrolled_window,
                                                 vadjustment, dy);
948 949
    }

950
  gtk_scrolled_window_invalidate_overshoot (scrolled_window);
951 952
}

953 954
static void
scrolled_window_drag_end_cb (GtkScrolledWindow *scrolled_window,
955
                             GdkEventSequence  *sequence,
956 957 958 959
                             GtkGesture        *gesture)
{
  GtkScrolledWindowPrivate *priv = scrolled_window->priv;

960 961
  if (!priv->in_drag || !gtk_gesture_handles_sequence (gesture, sequence))
    gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
962 963
}

964
static void
965 966 967
gtk_scrolled_window_decelerate (GtkScrolledWindow *scrolled_window,
                                gdouble            x_velocity,
                                gdouble            y_velocity)
968 969 970 971 972
{
  GtkScrolledWindowPrivate *priv = scrolled_window->priv;
  gboolean overshoot;

  overshoot = _gtk_scrolled_window_get_overshoot (scrolled_window, NULL, NULL);
973 974
  priv->x_velocity = x_velocity;
  priv->y_velocity = y_velocity;
975

976 977
  /* Zero out vector components for which we don't scroll */
  if (!may_hscroll (scrolled_window))
978
    priv->x_velocity = 0;
979
  if (!may_vscroll (scrolled_window))
980 981 982 983 984 985 986 987 988
    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;
    }
}

989 990 991 992 993 994 995 996
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);
}

997 998 999 1000 1001 1002 1003 1004
static void
scrolled_window_long_press_cb (GtkScrolledWindow *scrolled_window,
                               gdouble            x,
                               gdouble            y,
                               GtkGesture        *gesture)
{
  GdkEventSequence *sequence;

1005 1006 1007
  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
  gtk_gesture_set_sequence_state (gesture, sequence,
                                  GTK_EVENT_SEQUENCE_DENIED);
1008 1009
}

1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
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);

1021 1022 1023 1024
  if (event->type == GDK_TOUCH_BEGIN ||
      event->type == GDK_BUTTON_PRESS)
    gtk_gesture_set_sequence_state (gesture, sequence,
                                    GTK_EVENT_SEQUENCE_DENIED);
1025 1026
  else if (event->type != GDK_TOUCH_END &&
           event->type != GDK_BUTTON_RELEASE)
1027 1028 1029
    priv->in_drag = TRUE;
}

1030 1031 1032
static void
gtk_scrolled_window_check_attach_pan_gesture (GtkScrolledWindow *sw)
{
1033
  GtkPropagationPhase phase = GTK_PHASE_NONE;
1034 1035 1036
  GtkScrolledWindowPrivate *priv = sw->priv;

  if (priv->kinetic_scrolling &&
1037 1038
      ((may_hscroll (sw) && !may_vscroll (sw)) ||
       (!may_hscroll (sw) && may_vscroll (sw))))
1039
    {
1040
      GtkOrientation orientation;
1041

1042
      if (may_hscroll (sw))
1043
        orientation = GTK_ORIENTATION_HORIZONTAL;
1044
      else
1045
        orientation = GTK_ORIENTATION_VERTICAL;
1046 1047 1048

      gtk_gesture_pan_set_orientation (GTK_GESTURE_PAN (priv->pan_gesture),
                                       orientation);
1049
      phase = GTK_PHASE_CAPTURE;
1050
    }
1051 1052

  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->pan_gesture), phase);
1053 1054
}

1055 1056 1057 1058 1059 1060
static void
indicator_set_over (Indicator *indicator,
                    gboolean   over)
{
  GtkStyleContext *context;

1061 1062 1063 1064 1065 1066
  if (indicator->over_timeout_id)
    {
      g_source_remove (indicator->over_timeout_id);
      indicator->over_timeout_id = 0;
    }

1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080
  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);
}

1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117
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);
}

1118 1119 1120 1121 1122
static gboolean
event_close_to_indicator (GtkScrolledWindow *sw,
                          Indicator         *indicator,
                          GdkEvent          *event)
{
1123
  GtkScrolledWindowPrivate *priv;
1124 1125
  GtkAllocation indicator_alloc;
  gint x, y;
1126
  gint distance;
1127
  gint win_x, win_y;
1128

1129
  priv = sw->priv;
1130 1131 1132

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

1135 1136 1137 1138 1139
  if (indicator->over)
    distance = INDICATOR_FAR_DISTANCE;
  else
    distance = INDICATOR_CLOSE_DISTANCE;

1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151
  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;
    }
1152 1153 1154 1155

  return FALSE;
}

1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169
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)
{
1170 1171 1172 1173
  gboolean indicator_close, on_scrollbar;
  GtkWidget *event_widget;

  event_widget = gtk_get_event_widget (event);
1174 1175

  indicator_close = event_close_to_indicator (sw, indicator, event);
1176 1177
  on_scrollbar = (event_widget == indicator->scrollbar &&
                  event->type != GDK_LEAVE_NOTIFY);
1178 1179 1180 1181 1182 1183 1184

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

1185 1186 1187
  if (on_scrollbar)
    indicator_set_over (indicator, TRUE);
  else if (indicator_close)
1188 1189 1190 1191 1192 1193 1194
    indicator->over_timeout_id = gdk_threads_add_timeout (30, enable_over_timeout_cb, indicator);
  else
    indicator_set_over (indicator, FALSE);

  return indicator_close;
}

1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224
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;
}

1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305
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)
    return FALSE;

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

1306 1307 1308 1309 1310 1311 1312 1313
static gboolean
captured_event_cb (GtkWidget *widget,
                   GdkEvent  *event)
{
  GtkScrolledWindowPrivate *priv;
  GtkScrolledWindow *sw;
  GdkInputSource input_source;
  GdkDevice *source_device;
1314 1315
  GtkWidget *event_widget;
  gboolean on_scrollbar;
1316
  const gchar *device_name;
1317

1318 1319
  sw = GTK_SCROLLED_WINDOW (widget);
  priv = sw->priv;
1320
  source_device = gdk_event_get_source_device (event);
1321

1322 1323 1324 1325 1326 1327
  if (event->type == GDK_SCROLL)
    {
      gtk_scrolled_window_cancel_deceleration (sw);
      return GDK_EVENT_PROPAGATE;
    }

1328
  if (!priv->use_indicators)
1329 1330
    return GDK_EVENT_PROPAGATE;

1331 1332 1333 1334 1335
  if (event->type != GDK_MOTION_NOTIFY &&
      event->type != GDK_LEAVE_NOTIFY)
    return GDK_EVENT_PROPAGATE;

  input_source = gdk_device_get_source (source_device);
1336
  device_name = gdk_device_get_name (source_device);
1337

1338 1339
  if (input_source == GDK_SOURCE_KEYBOARD ||
      input_source == GDK_SOURCE_TOUCHSCREEN)
1340 1341
    return GDK_EVENT_PROPAGATE;

1342 1343 1344 1345
  event_widget = gtk_get_event_widget (event);
  on_scrollbar = (event_widget == priv->hindicator.scrollbar ||
                  event_widget == priv->vindicator.scrollbar);

1346
  if (event->type == GDK_MOTION_NOTIFY)
1347
    {
1348 1349 1350 1351
      if (priv->hscrollbar_visible)
        indicator_start_fade (&priv->hindicator, 1.0);
      if (priv->vscrollbar_visible)
        indicator_start_fade (&priv->vindicator, 1.0);
1352

1353 1354 1355
      if (!on_scrollbar &&
           (event->motion.state &
            (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK)) != 0)
1356 1357 1358 1359
        {
          indicator_set_over (&priv->hindicator, FALSE);
          indicator_set_over (&priv->vindicator, FALSE);
        }
1360 1361
      else if (input_source == GDK_SOURCE_PEN ||
               input_source == GDK_SOURCE_ERASER ||
1362 1363
               (device_name != NULL && strstr (device_name, "TrackPoint")) ||
               (device_name != NULL && strstr (device_name, "DualPoint Stick")))
1364
        {
1365 1366
          indicator_set_over (&priv->hindicator, TRUE);
          indicator_set_over (&priv->vindicator, T