2 * This file is a part of hildon
4 * Copyright (C) 2008 Nokia Corporation, all rights reserved.
6 * Contact: Karl Lattimer <karl.lattimer@nokia.com>
8 * This widget is based on MokoFingerScroll from libmokoui
9 * OpenMoko Application Framework UI Library
10 * Authored by Chris Lord <chris@openedhand.com>
11 * Copyright (C) 2006-2007 OpenMoko Inc.
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU Lesser Public License as published by
15 * the Free Software Foundation; version 2 of the license.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU Lesser Public License for more details.
25 * SECTION: hildon-pannable-area
26 * @short_description: A scrolling widget designed for touch screens
27 * @see_also: #GtkScrolledWindow
29 * #HildonPannableArea implements a scrolled window designed to be used with a
30 * touch screen interface. The user scrolls the child widget by activating the
31 * pointing device and dragging it over the widget.
36 * todo items should be marked with a number and a relative position in the code where they are relevant.
38 * 1 - Make OVERSHOOT a property, the default of this property is half of the pannable-area height
39 * 2 - Add method of scrolling to the selected element of a treeview and have it align to half way point
40 * 3 - Add a property for initial scrolling of the widget, so it scrolls on realize
41 * 4 - Add a method of removing children from the alignment, gtk-container-remove override?
42 * 5 - Remove elasticity if it is no longer required?
43 * 6 x Reduce calls to queue_resize to improve performance
44 * 7 - Make 'fast' factor a property
45 * 8 - Strip out g_print/g_debug calls
47 * 10 - Delay click mode (only send synthetic clicks on mouse-up, as in previous
49 * 11 - 'Physical' mode for acceleration scrolling
52 #define SMOOTH_FACTOR 0.85
54 #define ELASTICITY 0.2
58 #include "hildon-pannable-area.h"
60 G_DEFINE_TYPE (HildonPannableArea, hildon_pannable_area, GTK_TYPE_BIN)
61 #define PANNABLE_AREA_PRIVATE(o) \
62 (G_TYPE_INSTANCE_GET_PRIVATE ((o), HILDON_TYPE_PANNABLE_AREA, \
63 HildonPannableAreaPrivate))
64 typedef struct _HildonPannableAreaPrivate HildonPannableAreaPrivate;
66 struct _HildonPannableAreaPrivate {
67 HildonPannableAreaMode mode;
68 GdkWindow *event_window;
69 gdouble x; /* Used to store mouse co-ordinates of the first or */
70 gdouble y; /* previous events in a press-motion pair */
71 gdouble ex; /* Used to store mouse co-ordinates of the last */
72 gdouble ey; /* motion event in acceleration mode */
75 guint32 last_time; /* Last event time, to stop infinite loops */
85 gint ix; /* Initial click mouse co-ordinates */
87 gint cx; /* Initial click child window mouse co-ordinates */
98 GdkRectangle hscroll_rect;
99 GdkRectangle vscroll_rect;
102 GtkAdjustment *hadjust;
103 GtkAdjustment *vadjust;
110 HildonPannableAreaIndicatorMode vindicator_mode;
111 HildonPannableAreaIndicatorMode hindicator_mode;
127 static GdkWindow *hildon_pannable_area_get_topmost (GdkWindow * window,
129 gint * tx, gint * ty)
131 /* Find the GdkWindow at the given point, by recursing from a given
132 * parent GdkWindow. Optionally return the co-ordinates transformed
133 * relative to the child window.
137 gdk_drawable_get_size (GDK_DRAWABLE (window), &width, &height);
138 if ((x < 0) || (x >= width) || (y < 0) || (y >= height))
141 /*g_debug ("Finding window at (%d, %d) in %p", x, y, window); */
144 gint child_x = 0, child_y = 0;
145 GList *c, *children = gdk_window_get_children (window);
146 GdkWindow *old_window = window;
148 for (c = children; c; c = c->next) {
149 GdkWindow *child = (GdkWindow *) c->data;
152 gdk_window_get_geometry (child, &wx, &wy, &width, &height, NULL);
153 /*g_debug ("Child: %p, (%dx%d+%d,%d)", child,
154 width, height, wx, wy); */
156 if ((x >= wx) && (x < (wx + width)) && (y >= wy)
157 && (y < (wy + height))) {
164 g_list_free (children);
166 /*g_debug ("\\|/"); */
167 if (window == old_window)
179 /*g_debug ("Returning: %p", window); */
185 synth_crossing (GdkWindow * child,
187 gint x_root, gint y_root, guint32 time, gboolean in)
189 GdkEventCrossing *crossing_event;
190 GdkEventType type = in ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY;
192 /* Send synthetic enter event */
193 crossing_event = (GdkEventCrossing *) gdk_event_new (type);
194 ((GdkEventAny *) crossing_event)->type = type;
195 ((GdkEventAny *) crossing_event)->window = g_object_ref (child);
196 ((GdkEventAny *) crossing_event)->send_event = FALSE;
197 crossing_event->subwindow = g_object_ref (child);
198 crossing_event->time = time;
199 crossing_event->x = x;
200 crossing_event->y = y;
201 crossing_event->x_root = x_root;
202 crossing_event->y_root = y_root;
203 crossing_event->mode = GDK_CROSSING_NORMAL;
204 crossing_event->detail = GDK_NOTIFY_UNKNOWN;
205 crossing_event->focus = FALSE;
206 crossing_event->state = 0;
207 gdk_event_put ((GdkEvent *) crossing_event);
208 gdk_event_free ((GdkEvent *) crossing_event);
212 hildon_pannable_area_button_press_cb (GtkWidget * widget,
213 GdkEventButton * event)
216 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
218 if ((!priv->enabled) || (event->button != 1) ||
219 ((event->time == priv->last_time) &&
220 (priv->last_type == 1)) || (priv->overshot_dist_y))
223 priv->last_time = event->time;
226 priv->click_x = event->x;
227 priv->click_y = event->y;
229 if (priv->clicked && priv->child) {
230 /* Widget stole focus on last click, send crossing-out event */
231 synth_crossing (priv->child, 0, 0, event->x_root, event->y_root,
239 /* Don't allow a click if we're still moving fast, where fast is
240 * defined as a quarter of our top possible speed.
241 * TODO 7: Make 'fast' a property
243 if ((ABS (priv->vel_x) < (priv->vmax * 0.25)) &&
244 (ABS (priv->vel_y) < (priv->vmax * 0.25)))
246 hildon_pannable_area_get_topmost (GTK_BIN (priv->align)->child->window,
247 event->x, event->y, &x, &y);
251 priv->clicked = TRUE;
252 /* Stop scrolling on mouse-down (so you can flick, then hold to stop) */
256 if ((priv->child) && (priv->child != GTK_BIN (priv->align)->child->window)) {
258 g_object_add_weak_pointer ((GObject *) priv->child,
259 (gpointer *) & priv->child);
261 event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
267 synth_crossing (priv->child, x, y, event->x_root,
268 event->y_root, event->time, TRUE);
270 /* Send synthetic click (button press/release) event */
271 ((GdkEventAny *) event)->window = g_object_ref (priv->child);
272 gdk_event_put ((GdkEvent *) event);
273 gdk_event_free ((GdkEvent *) event);
281 hildon_pannable_area_redraw (HildonPannableArea * area)
283 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
285 /* Redraw scroll indicators */
287 if (GTK_WIDGET (area)->window) {
288 gdk_window_invalidate_rect (GTK_WIDGET (area)->window,
289 &priv->hscroll_rect, FALSE);
293 if (GTK_WIDGET (area)->window) {
294 gdk_window_invalidate_rect (GTK_WIDGET (area)->window,
295 &priv->vscroll_rect, FALSE);
301 hildon_pannable_area_refresh (HildonPannableArea * area)
303 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
304 GtkWidget *widget = GTK_BIN (priv->align)->child;
305 gboolean vscroll, hscroll;
310 /* Calculate if we need scroll indicators */
311 gtk_widget_size_request (widget, NULL);
313 switch (priv->hindicator_mode) {
314 case HILDON_PANNABLE_AREA_INDICATOR_MODE_SHOW:
317 case HILDON_PANNABLE_AREA_INDICATOR_MODE_HIDE:
321 hscroll = (priv->hadjust->upper - priv->hadjust->lower >
322 priv->hadjust->page_size) ? TRUE : FALSE;
325 switch (priv->vindicator_mode) {
326 case HILDON_PANNABLE_AREA_INDICATOR_MODE_SHOW:
329 case HILDON_PANNABLE_AREA_INDICATOR_MODE_HIDE:
333 vscroll = (priv->vadjust->upper - priv->vadjust->lower >
334 priv->vadjust->page_size) ? TRUE : FALSE;
337 /* TODO: Read ltr settings to decide which corner gets scroll
340 if ((priv->vscroll != vscroll) || (priv->hscroll != hscroll)) {
341 gtk_alignment_set_padding (GTK_ALIGNMENT (priv->align), 0,
342 hscroll ? priv->area_width : 0, 0,
343 vscroll ? priv->area_width : 0);
346 /* Store the vscroll/hscroll areas for redrawing */
348 GtkAllocation *allocation = >K_WIDGET (area)->allocation;
349 priv->vscroll_rect.x = allocation->x + allocation->width -
351 priv->vscroll_rect.y = allocation->y;
352 priv->vscroll_rect.width = priv->area_width;
353 priv->vscroll_rect.height = allocation->height -
354 (hscroll ? priv->area_width : 0);
357 GtkAllocation *allocation = >K_WIDGET (area)->allocation;
358 priv->hscroll_rect.y = allocation->y + allocation->height -
360 priv->hscroll_rect.x = allocation->x;
361 priv->hscroll_rect.height = priv->area_width;
362 priv->hscroll_rect.width = allocation->width -
363 (vscroll ? priv->area_width : 0);
366 priv->vscroll = vscroll;
367 priv->hscroll = hscroll;
369 hildon_pannable_area_redraw (area);
373 hildon_pannable_area_scroll (HildonPannableArea * area, gdouble x, gdouble y,
374 gboolean * sx, gboolean * sy)
376 /* Scroll by a particular amount (in pixels). Optionally, return if
377 * the scroll on a particular axis was successful.
380 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
382 if (!GTK_BIN (priv->align)->child) return;
385 h = gtk_adjustment_get_value (priv->hadjust) - x;
386 if (h > priv->hadjust->upper - priv->hadjust->page_size) {
388 h = priv->hadjust->upper - priv->hadjust->page_size;
389 } else if (h < priv->hadjust->lower) {
391 h = priv->hadjust->lower;
394 gtk_adjustment_set_value (priv->hadjust, h);
398 * We use overshot_dist_* to define the distance of the current overshoot,
399 * and overshooting_* to define the direction/whether or not we are overshot
403 v = gtk_adjustment_get_value (priv->vadjust) - y;
404 if (!priv->overshooting_y) {
406 /* Initiation of the overshoot happens when the finger is released
407 * and the current position of the pannable contents are out of range
409 if (v < priv->vadjust->lower) {
412 v = priv->vadjust->lower;
414 if ((!priv->clicked) && (priv->vel_y > 0) &&
415 (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO)) {
416 priv->overshooting_y = 1;
417 priv->overshot_dist_y = MIN (priv->overshot_dist_y + priv->vel_y, OVERSHOOT);
418 gtk_widget_queue_resize (GTK_WIDGET (area));
420 } else if (v > priv->vadjust->upper - priv->vadjust->page_size) {
423 v = priv->vadjust->upper - priv->vadjust->page_size;
425 if ((!priv->clicked) && (priv->vel_y < 0) &&
426 (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO)) {
427 priv->overshooting_y = 1;
428 priv->overshot_dist_y = MAX (priv->overshot_dist_y + priv->vel_y, -1*OVERSHOOT);
429 gtk_widget_queue_resize (GTK_WIDGET (area));
434 gtk_adjustment_set_value (priv->vadjust, v);
436 if (!priv->clicked) {
439 /* When the overshoot has started we continue for 3 more steps into the overshoot
440 * before we reverse direction. The deceleration factor is calculated based on
441 * the percentage distance from the first item with each iteration, therefore always
442 * returning us to the top/bottom most element
444 if (priv->overshot_dist_y > 0) {
445 if ((priv->overshooting_y < 3) && (priv->vel_y > 0)) {
446 priv->overshooting_y++;
447 priv->vel_y = ((gdouble)priv->overshot_dist_y/OVERSHOOT) * priv->vel_y;
449 } else if ((priv->overshooting_y >= 3) && (priv->vel_y > 0)) {
451 priv->overshooting_y--;
452 } else if ((priv->overshooting_y > 1) && (priv->vel_y < 0)) {
453 priv->overshooting_y--;
454 priv->vel_y = ((gdouble)priv->overshot_dist_y/OVERSHOOT) * priv->vel_y;
456 priv->overshot_dist_y = MIN (priv->overshot_dist_y + priv->vel_y, OVERSHOOT);
457 if (priv->overshot_dist_y < 0) priv->overshot_dist_y = 0;
458 gtk_widget_queue_resize (GTK_WIDGET (area));
460 } else if (priv->overshot_dist_y < 0) {
461 if ((priv->overshooting_y < 3) && (priv->vel_y < 0)) {
462 priv->overshooting_y++;
463 priv->vel_y = ((gdouble)priv->overshot_dist_y/OVERSHOOT) * priv->vel_y * -1;
465 } else if ((priv->overshooting_y >= 3) && (priv->vel_y < 0)) {
467 priv->overshooting_y--;
468 } else if ((priv->overshooting_y > 1) && (priv->vel_y > 0)) {
469 priv->overshooting_y--;
470 priv->vel_y = ((gdouble)priv->overshot_dist_y/OVERSHOOT) * priv->vel_y * -1;
472 priv->overshot_dist_y = MAX (priv->overshot_dist_y + priv->vel_y, -1*OVERSHOOT);
473 if (priv->overshot_dist_y > 0) priv->overshot_dist_y = 0;
474 gtk_widget_queue_resize (GTK_WIDGET (area));
478 priv->overshooting_y = 0;
481 gtk_adjustment_set_value (priv->vadjust, v);
485 //g_print("Overshoot %d\n", priv->overshot_dist_y);
486 hildon_pannable_area_redraw (area);
490 hildon_pannable_area_timeout (HildonPannableArea * area)
493 HildonPannableAreaPrivate *priv;
495 GDK_THREADS_ENTER ();
497 priv = PANNABLE_AREA_PRIVATE (area);
499 if ((!priv->enabled) || (priv->mode == HILDON_PANNABLE_AREA_MODE_PUSH)) {
504 if (!priv->clicked) {
505 /* Decelerate gradually when pointer is raised */
506 if (!priv->overshot_dist_y) {
507 priv->vel_x *= priv->decel;
508 priv->vel_y *= priv->decel;
509 if ((ABS (priv->vel_x) < 1.0) && (ABS (priv->vel_y) < 1.0)) {
514 } else if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO) {
519 hildon_pannable_area_scroll (area, priv->vel_x, priv->vel_y, &sx, &sy);
520 /* If the scroll on a particular axis wasn't succesful, reset the
521 * initial scroll position to the new mouse co-ordinate. This means
522 * when you get to the top of the page, dragging down works immediately.
526 priv->vel_x *= -1 * priv->decel * ELASTICITY;
531 priv->vel_y *= -1 * priv->decel * ELASTICITY;
534 GDK_THREADS_LEAVE ();
540 hildon_pannable_area_motion_notify_cb (GtkWidget * widget,
541 GdkEventMotion * event)
543 HildonPannableArea *area = HILDON_PANNABLE_AREA (widget);
544 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (area);
547 gdouble delta, rawvel_x, rawvel_y;
549 gint direction_x, direction_y;
551 if ((!priv->enabled) || (!priv->clicked) ||
552 ((event->time == priv->last_time) && (priv->last_type == 2))) {
553 gdk_window_get_pointer (widget->window, NULL, NULL, 0);
557 /* Only start the scroll if the mouse cursor passes beyond the
558 * DnD threshold for dragging.
560 g_object_get (G_OBJECT (gtk_settings_get_default ()),
561 "gtk-dnd-drag-threshold", &dnd_threshold, NULL);
562 x = event->x - priv->x;
563 y = event->y - priv->y;
565 if ((!priv->moved) && ((ABS (x) > dnd_threshold)
566 || (ABS (y) > dnd_threshold))) {
568 if ((priv->mode != HILDON_PANNABLE_AREA_MODE_PUSH) &&
569 (priv->mode != HILDON_PANNABLE_AREA_MODE_AUTO)) {
572 g_source_remove (priv->idle_id);
574 priv->idle_id = g_timeout_add ((gint)
575 (1000.0 / (gdouble) priv->sps),
577 hildon_pannable_area_timeout, area);
582 switch (priv->mode) {
583 case HILDON_PANNABLE_AREA_MODE_PUSH:
584 /* Scroll by the amount of pixels the cursor has moved
585 * since the last motion event.
587 hildon_pannable_area_scroll (area, x, y, NULL, NULL);
591 case HILDON_PANNABLE_AREA_MODE_ACCEL:
592 /* Set acceleration relative to the initial click */
595 priv->vel_x = ((x > 0) ? 1 : -1) *
597 (gdouble) widget->allocation.width) *
598 (priv->vmax - priv->vmin)) + priv->vmin);
599 priv->vel_y = ((y > 0) ? 1 : -1) *
601 (gdouble) widget->allocation.height) *
602 (priv->vmax - priv->vmin)) + priv->vmin);
604 case HILDON_PANNABLE_AREA_MODE_AUTO:
606 delta = event->time - priv->last_time;
608 rawvel_x = (((event->x - priv->x) / ABS (delta)) *
609 (gdouble) priv->sps) * FORCE;
610 rawvel_y = (((event->y - priv->y) / ABS (delta)) *
611 (gdouble) priv->sps) * FORCE;
613 /* we store the direction and after the calculation we
614 change it, this reduces the ifs for the calculation */
615 direction_x = rawvel_x < 0 ? -1 : 1;
616 direction_y = rawvel_y < 0 ? -1 : 1;
618 rawvel_y = ABS (rawvel_y);
619 rawvel_x = ABS (rawvel_x);
621 priv->vel_x = priv->vel_x * (1 - SMOOTH_FACTOR) +
622 direction_x * rawvel_x * SMOOTH_FACTOR;
623 priv->vel_y = priv->vel_y * (1 - SMOOTH_FACTOR) +
624 direction_y * rawvel_y * SMOOTH_FACTOR;
626 priv->vel_x = priv->vel_x > 0 ? MIN (priv->vel_x, priv->vmax)
627 : MAX (priv->vel_x, -1 * priv->vmax);
628 priv->vel_y = priv->vel_y > 0 ? MIN (priv->vel_y, priv->vmax)
629 : MAX (priv->vel_y, -1 * priv->vmax);
631 /* Overshooting while the finger is down */
632 if (priv->overshot_dist_y > 0) {
633 priv->overshot_dist_y = CLAMP (priv->overshot_dist_y + y, 0, OVERSHOOT);
634 gtk_widget_queue_resize (GTK_WIDGET (area));
635 } else if (priv->overshot_dist_y < 0) {
636 priv->overshot_dist_y = CLAMP (priv->overshot_dist_y + y, -1 * OVERSHOOT, 0);
637 gtk_widget_queue_resize (GTK_WIDGET (area));
639 v = priv->vadjust->value - y;
641 if (v < priv->vadjust->lower) {
642 priv->overshooting_y = 1;
643 priv->overshot_dist_y = MIN (priv->overshot_dist_y + y, OVERSHOOT);
644 gtk_widget_queue_resize (GTK_WIDGET (area));
645 } else if (v > priv->vadjust->upper - priv->vadjust->page_size) {
646 priv->overshooting_y = 1;
647 priv->overshot_dist_y = MAX (priv->overshot_dist_y + y, -1 * OVERSHOOT);
648 gtk_widget_queue_resize (GTK_WIDGET (area));
650 hildon_pannable_area_scroll (area, x, y, NULL, NULL);
664 /* Send motion notify to child */
665 priv->last_time = event->time;
667 event = (GdkEventMotion *) gdk_event_copy ((GdkEvent *) event);
668 event->x = priv->cx + (event->x - priv->ix);
669 event->y = priv->cy + (event->y - priv->iy);
670 event->window = g_object_ref (priv->child);
671 gdk_event_put ((GdkEvent *) event);
672 gdk_event_free ((GdkEvent *) event);
675 gdk_window_get_pointer (widget->window, NULL, NULL, 0);
681 hildon_pannable_area_button_release_cb (GtkWidget * widget,
682 GdkEventButton * event)
684 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
688 if ((!priv->clicked) || (!priv->enabled) || (event->button != 1) ||
689 ((event->time == priv->last_time) && (priv->last_type == 3)))
691 priv->clicked = FALSE;
693 if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO) {
695 g_source_remove (priv->idle_id);
697 /* If overshoot has been initiated with a finger down, on release set max speed */
698 if (priv->overshot_dist_y != 0) {
699 priv->vel_y = priv->vmax;
700 priv->overshooting_y = 3; // Hack to stop a bounce in the finger down case
702 priv->idle_id = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
704 hildon_pannable_area_timeout, widget);
707 priv->last_time = event->time;
716 hildon_pannable_area_get_topmost (GTK_BIN (priv->align)->child->window,
717 event->x, event->y, &x, &y);
719 event = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
723 /* Leave the widget if we've moved - This doesn't break selection,
724 * but stops buttons from being clicked.
726 if ((child != priv->child) || (priv->moved)) {
727 /* Send synthetic leave event */
728 synth_crossing (priv->child, x, y, event->x_root,
729 event->y_root, event->time, FALSE);
730 /* Send synthetic button release event */
731 ((GdkEventAny *) event)->window = g_object_ref (priv->child);
732 gdk_event_put ((GdkEvent *) event);
734 /* Send synthetic button release event */
735 ((GdkEventAny *) event)->window = g_object_ref (child);
736 gdk_event_put ((GdkEvent *) event);
737 /* Send synthetic leave event */
738 synth_crossing (priv->child, x, y, event->x_root,
739 event->y_root, event->time, FALSE);
741 g_object_remove_weak_pointer ((GObject *) priv->child,
742 (gpointer *) & priv->child);
745 gdk_event_free ((GdkEvent *) event);
751 hildon_pannable_area_expose_event (GtkWidget * widget, GdkEventExpose * event)
753 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
755 if (GTK_BIN (priv->align)->child) {
758 gdk_draw_rectangle (widget->window,
759 widget->style->fg_gc[GTK_STATE_INSENSITIVE],
761 priv->vscroll_rect.x, priv->vscroll_rect.y,
762 priv->vscroll_rect.width,
763 priv->vscroll_rect.height);
765 y = widget->allocation.y +
766 ((priv->vadjust->value / priv->vadjust->upper) *
767 (widget->allocation.height -
768 (priv->hscroll ? priv->area_width : 0)));
769 height = (widget->allocation.y +
770 (((priv->vadjust->value +
771 priv->vadjust->page_size) /
772 priv->vadjust->upper) *
773 (widget->allocation.height -
774 (priv->hscroll ? priv->area_width : 0)))) - y;
776 gdk_draw_rectangle (widget->window,
777 widget->style->base_gc[GTK_STATE_SELECTED],
778 TRUE, priv->vscroll_rect.x, y,
779 priv->vscroll_rect.width, height);
784 gdk_draw_rectangle (widget->window,
785 widget->style->fg_gc[GTK_STATE_INSENSITIVE],
787 priv->hscroll_rect.x, priv->hscroll_rect.y,
788 priv->hscroll_rect.width,
789 priv->hscroll_rect.height);
791 x = widget->allocation.x +
792 ((priv->hadjust->value / priv->hadjust->upper) *
793 (widget->allocation.width - (priv->vscroll ? priv->area_width : 0)));
795 (widget->allocation.x +
796 (((priv->hadjust->value +
797 priv->hadjust->page_size) / priv->hadjust->upper) *
798 (widget->allocation.width -
799 (priv->vscroll ? priv->area_width : 0)))) - x;
801 gdk_draw_rectangle (widget->window,
802 widget->style->base_gc[GTK_STATE_SELECTED],
803 TRUE, x, priv->hscroll_rect.y, width,
804 priv->hscroll_rect.height);
807 if (priv->overshot_dist_y > 0) {
808 gdk_draw_rectangle (widget->window,
809 widget->style->bg_gc[GTK_STATE_NORMAL],
811 widget->allocation.x,
812 widget->allocation.y,
813 widget->allocation.width -
814 priv->vscroll_rect.width, priv->overshot_dist_y);
815 } else if (priv->overshot_dist_y < 0) {
816 gdk_draw_rectangle (widget->window,
817 widget->style->bg_gc[GTK_STATE_NORMAL],
819 widget->allocation.x,
820 widget->allocation.y +
821 widget->allocation.height +
822 priv->overshot_dist_y,
823 widget->allocation.width -
824 priv->vscroll_rect.width,
825 -1 * priv->overshot_dist_y);
829 return GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->expose_event (widget, event);
833 hildon_pannable_area_destroy (GtkObject * object)
835 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
838 g_object_unref (G_OBJECT (priv->hadjust));
839 priv->hadjust = NULL;
843 g_object_unref (G_OBJECT (priv->vadjust));
844 priv->vadjust = NULL;
847 GTK_OBJECT_CLASS (hildon_pannable_area_parent_class)->destroy (object);
851 parent_set_cb (GtkWidget * widget, GtkObject * parent,
852 HildonPannableArea * area)
855 g_signal_handlers_disconnect_by_func (widget,
856 hildon_pannable_area_refresh, area);
857 g_signal_handlers_disconnect_by_func (widget, gtk_widget_queue_resize,
859 g_signal_handlers_disconnect_by_func (widget, parent_set_cb, area);
860 gtk_widget_set_scroll_adjustments (widget, NULL, NULL);
865 hildon_pannable_area_add (GtkContainer * container, GtkWidget * child)
867 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (container);
869 gtk_container_add (GTK_CONTAINER (priv->align), child);
870 g_signal_connect_swapped (child, "size-allocate",
871 G_CALLBACK (hildon_pannable_area_refresh),
873 g_signal_connect_swapped (child, "size-request",
874 G_CALLBACK (gtk_widget_queue_resize), container);
875 g_signal_connect (child, "parent-set", G_CALLBACK (parent_set_cb),
878 if (!gtk_widget_set_scroll_adjustments
879 (child, priv->hadjust, priv->vadjust))
880 g_warning ("%s: cannot add non scrollable widget, "
881 "wrap it in a viewport", __FUNCTION__);
885 hildon_pannable_area_get_property (GObject * object, guint property_id,
886 GValue * value, GParamSpec * pspec)
888 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
890 switch (property_id) {
892 g_value_set_boolean (value, priv->enabled);
895 g_value_set_enum (value, priv->mode);
897 case PROP_VELOCITY_MIN:
898 g_value_set_double (value, priv->vmin);
900 case PROP_VELOCITY_MAX:
901 g_value_set_double (value, priv->vmax);
903 case PROP_DECELERATION:
904 g_value_set_double (value, priv->decel);
907 g_value_set_uint (value, priv->sps);
909 case PROP_VINDICATOR:
910 g_value_set_enum (value, priv->vindicator_mode);
912 case PROP_HINDICATOR:
913 g_value_set_enum (value, priv->hindicator_mode);
917 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
922 hildon_pannable_area_set_property (GObject * object, guint property_id,
923 const GValue * value, GParamSpec * pspec)
925 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
928 switch (property_id) {
930 enabled = g_value_get_boolean (value);
932 if ((priv->enabled != enabled) && (GTK_WIDGET_REALIZED (object))) {
934 gdk_window_raise (priv->event_window);
936 gdk_window_lower (priv->event_window);
939 priv->enabled = enabled;
942 priv->mode = g_value_get_enum (value);
944 case PROP_VELOCITY_MIN:
945 priv->vmin = g_value_get_double (value);
947 case PROP_VELOCITY_MAX:
948 priv->vmax = g_value_get_double (value);
950 case PROP_DECELERATION:
951 priv->decel = g_value_get_double (value);
954 priv->sps = g_value_get_uint (value);
956 case PROP_VINDICATOR:
957 priv->vindicator_mode = g_value_get_enum (value);
959 case PROP_HINDICATOR:
960 priv->hindicator_mode = g_value_get_enum (value);
964 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
969 hildon_pannable_area_dispose (GObject * object)
971 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (object);
974 g_source_remove (priv->idle_id);
979 g_object_unref (priv->hadjust);
980 priv->hadjust = NULL;
983 g_object_unref (priv->vadjust);
984 priv->vadjust = NULL;
987 if (G_OBJECT_CLASS (hildon_pannable_area_parent_class)->dispose)
988 G_OBJECT_CLASS (hildon_pannable_area_parent_class)->dispose (object);
992 hildon_pannable_area_finalize (GObject * object)
994 G_OBJECT_CLASS (hildon_pannable_area_parent_class)->finalize (object);
998 hildon_pannable_area_realize (GtkWidget * widget)
1000 GdkWindowAttr attributes;
1001 gint attributes_mask;
1003 HildonPannableAreaPrivate *priv;
1005 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
1007 border_width = GTK_CONTAINER (widget)->border_width;
1009 attributes.x = widget->allocation.x + border_width;
1010 attributes.y = widget->allocation.y + border_width;
1011 attributes.width = widget->allocation.width - 2 * border_width;
1012 attributes.height = widget->allocation.height - 2 * border_width;
1013 attributes.window_type = GDK_WINDOW_CHILD;
1014 attributes.event_mask = gtk_widget_get_events (widget)
1015 | GDK_BUTTON_MOTION_MASK
1016 | GDK_BUTTON_PRESS_MASK
1017 | GDK_BUTTON_RELEASE_MASK
1018 | GDK_EXPOSURE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK;
1020 priv = PANNABLE_AREA_PRIVATE (widget);
1022 widget->window = gtk_widget_get_parent_window (widget);
1023 g_object_ref (widget->window);
1025 attributes.wclass = GDK_INPUT_ONLY;
1026 attributes_mask = GDK_WA_X | GDK_WA_Y;
1028 priv->event_window = gdk_window_new (widget->window,
1029 &attributes, attributes_mask);
1030 gdk_window_set_user_data (priv->event_window, widget);
1032 widget->style = gtk_style_attach (widget->style, widget->window);
1036 hildon_pannable_area_unrealize (GtkWidget * widget)
1038 HildonPannableAreaPrivate *priv;
1040 priv = PANNABLE_AREA_PRIVATE (widget);
1042 if (priv->event_window != NULL) {
1043 gdk_window_set_user_data (priv->event_window, NULL);
1044 gdk_window_destroy (priv->event_window);
1045 priv->event_window = NULL;
1048 if (GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unrealize)
1049 (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->
1050 unrealize) (widget);
1054 hildon_pannable_area_map (GtkWidget * widget)
1056 HildonPannableAreaPrivate *priv;
1058 priv = PANNABLE_AREA_PRIVATE (widget);
1060 if (priv->event_window != NULL && !priv->enabled)
1061 gdk_window_show (priv->event_window);
1063 (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->map) (widget);
1065 if (priv->event_window != NULL && priv->enabled)
1066 gdk_window_show (priv->event_window);
1068 if (priv->mode == HILDON_PANNABLE_AREA_MODE_AUTO) {
1069 // TODO 3: This should only be active if this hint has been set
1070 //priv->overshot_dist_y = OVERSHOOT;
1072 //priv->vel_y = priv->vmax * 0.1;
1074 priv->idle_id = g_timeout_add ((gint) (1000.0 / (gdouble) priv->sps),
1076 hildon_pannable_area_timeout, widget);
1082 hildon_pannable_area_unmap (GtkWidget * widget)
1084 HildonPannableAreaPrivate *priv;
1086 priv = PANNABLE_AREA_PRIVATE (widget);
1088 if (priv->event_window != NULL)
1089 gdk_window_hide (priv->event_window);
1091 (*GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->unmap) (widget);
1095 hildon_pannable_area_size_request (GtkWidget * widget,
1096 GtkRequisition * requisition)
1098 /* Request tiny size, seeing as we have no decoration of our own. */
1099 requisition->width = 32;
1100 requisition->height = 32;
1104 hildon_pannable_area_size_allocate (GtkWidget * widget,
1105 GtkAllocation * allocation)
1108 GtkAllocation child_allocation;
1109 HildonPannableAreaPrivate *priv;
1111 widget->allocation = *allocation;
1112 bin = GTK_BIN (widget);
1114 priv = PANNABLE_AREA_PRIVATE (widget);
1116 child_allocation.x = allocation->x + GTK_CONTAINER (widget)->border_width;
1117 child_allocation.y = allocation->y + GTK_CONTAINER (widget)->border_width;
1118 child_allocation.width = MAX (allocation->width -
1119 GTK_CONTAINER (widget)->border_width * 2, 0);
1120 child_allocation.height = MAX (allocation->height -
1121 GTK_CONTAINER (widget)->border_width * 2, 0);
1123 if (GTK_WIDGET_REALIZED (widget)) {
1124 if (priv->event_window != NULL)
1125 gdk_window_move_resize (priv->event_window,
1128 child_allocation.width,
1129 child_allocation.height);
1132 /* negative overshooting? fix it */
1133 if (priv->overshot_dist_y > 0) {
1134 child_allocation.x = child_allocation.x;
1135 child_allocation.y += priv->overshot_dist_y;
1136 child_allocation.width = child_allocation.width;
1137 child_allocation.height -= priv->overshot_dist_y;
1139 gtk_adjustment_set_value (priv->vadjust, priv->vadjust->lower);
1141 } else if (priv->overshot_dist_y < 0) {
1142 /* change this assignations */
1143 child_allocation.x = child_allocation.x;
1144 child_allocation.y = child_allocation.y;
1145 child_allocation.width = child_allocation.width;
1146 child_allocation.height += priv->overshot_dist_y;
1148 gtk_adjustment_set_value (priv->vadjust, priv->vadjust->upper -
1149 priv->vadjust->page_size);
1153 gtk_widget_size_allocate (bin->child, &child_allocation);
1157 hildon_pannable_area_style_set (GtkWidget * widget, GtkStyle * previous_style)
1159 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (widget);
1161 GTK_WIDGET_CLASS (hildon_pannable_area_parent_class)->
1162 style_set (widget, previous_style);
1164 gtk_widget_style_get (widget, "indicator-width", &priv->area_width, NULL);
1168 hildon_pannable_area_class_init (HildonPannableAreaClass * klass)
1170 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1171 GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass);
1172 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1173 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
1175 g_type_class_add_private (klass, sizeof (HildonPannableAreaPrivate));
1177 object_class->get_property = hildon_pannable_area_get_property;
1178 object_class->set_property = hildon_pannable_area_set_property;
1179 object_class->dispose = hildon_pannable_area_dispose;
1180 object_class->finalize = hildon_pannable_area_finalize;
1182 gtkobject_class->destroy = hildon_pannable_area_destroy;
1184 widget_class->realize = hildon_pannable_area_realize;
1185 widget_class->unrealize = hildon_pannable_area_unrealize;
1186 widget_class->map = hildon_pannable_area_map;
1187 widget_class->unmap = hildon_pannable_area_unmap;
1188 widget_class->size_request = hildon_pannable_area_size_request;
1189 widget_class->size_allocate = hildon_pannable_area_size_allocate;
1190 widget_class->expose_event = hildon_pannable_area_expose_event;
1191 widget_class->style_set = hildon_pannable_area_style_set;
1192 widget_class->button_press_event = hildon_pannable_area_button_press_cb;
1193 widget_class->button_release_event = hildon_pannable_area_button_release_cb;
1194 widget_class->motion_notify_event = hildon_pannable_area_motion_notify_cb;
1196 container_class->add = hildon_pannable_area_add;
1198 g_object_class_install_property (object_class,
1200 g_param_spec_boolean ("enabled",
1202 "Enable or disable finger-scroll.",
1205 G_PARAM_CONSTRUCT));
1207 g_object_class_install_property (object_class,
1209 g_param_spec_enum ("vindicator_mode",
1211 "Mode of the vertical scrolling indicator",
1212 HILDON_TYPE_PANNABLE_AREA_INDICATOR_MODE,
1213 HILDON_PANNABLE_AREA_INDICATOR_MODE_AUTO,
1215 G_PARAM_CONSTRUCT));
1217 g_object_class_install_property (object_class,
1219 g_param_spec_enum ("hindicator_mode",
1221 "Mode of the horizontal scrolling indicator",
1222 HILDON_TYPE_PANNABLE_AREA_INDICATOR_MODE,
1223 HILDON_PANNABLE_AREA_INDICATOR_MODE_AUTO,
1225 G_PARAM_CONSTRUCT));
1227 g_object_class_install_property (object_class,
1229 g_param_spec_enum ("mode",
1231 "Change the finger-scrolling mode.",
1232 HILDON_TYPE_PANNABLE_AREA_MODE,
1233 HILDON_PANNABLE_AREA_MODE_AUTO,
1235 G_PARAM_CONSTRUCT));
1237 g_object_class_install_property (object_class,
1239 g_param_spec_double ("velocity_min",
1240 "Minimum scroll velocity",
1241 "Minimum distance the child widget should scroll "
1242 "per 'frame', in pixels.",
1245 G_PARAM_CONSTRUCT));
1247 g_object_class_install_property (object_class,
1249 g_param_spec_double ("velocity_max",
1250 "Maximum scroll velocity",
1251 "Maximum distance the child widget should scroll "
1252 "per 'frame', in pixels.",
1255 G_PARAM_CONSTRUCT));
1257 g_object_class_install_property (object_class,
1259 g_param_spec_double ("deceleration",
1260 "Deceleration multiplier",
1261 "The multiplier used when decelerating when in "
1262 "acceleration scrolling mode.",
1265 G_PARAM_CONSTRUCT));
1267 g_object_class_install_property (object_class,
1269 g_param_spec_uint ("sps",
1270 "Scrolls per second",
1271 "Amount of scroll events to generate per second.",
1274 G_PARAM_CONSTRUCT));
1276 gtk_widget_class_install_style_property (widget_class,
1279 "Width of the scroll indicators",
1280 "Pixel width used to draw the scroll indicators.",
1282 G_PARAM_READWRITE));
1286 hildon_pannable_area_init (HildonPannableArea * self)
1288 HildonPannableAreaPrivate *priv = PANNABLE_AREA_PRIVATE (self);
1290 priv->moved = FALSE;
1291 priv->clicked = FALSE;
1292 priv->last_time = 0;
1293 priv->last_type = 0;
1294 priv->vscroll = TRUE;
1295 priv->hscroll = TRUE;
1296 priv->area_width = 6;
1297 priv->overshot_dist_x = 0;
1298 priv->overshot_dist_y = 0;
1299 priv->overshooting_y = 0;
1300 priv->overshooting_x = 0;
1303 priv->align = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
1304 GTK_CONTAINER_CLASS (hildon_pannable_area_parent_class)->
1305 add (GTK_CONTAINER (self), priv->align);
1306 gtk_alignment_set_padding (GTK_ALIGNMENT (priv->align), 0, priv->area_width,
1307 0, priv->area_width);
1308 gtk_widget_show (priv->align);
1310 gtk_widget_add_events (GTK_WIDGET (self), GDK_POINTER_MOTION_HINT_MASK);
1313 GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1315 GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1317 g_object_ref_sink (G_OBJECT (priv->hadjust));
1318 g_object_ref_sink (G_OBJECT (priv->vadjust));
1320 g_signal_connect_swapped (G_OBJECT (priv->hadjust), "changed",
1321 G_CALLBACK (hildon_pannable_area_refresh), self);
1322 g_signal_connect_swapped (G_OBJECT (priv->vadjust), "changed",
1323 G_CALLBACK (hildon_pannable_area_refresh), self);
1324 g_signal_connect_swapped (G_OBJECT (priv->hadjust), "value-changed",
1325 G_CALLBACK (hildon_pannable_area_redraw), self);
1326 g_signal_connect_swapped (G_OBJECT (priv->vadjust), "value-changed",
1327 G_CALLBACK (hildon_pannable_area_redraw), self);
1331 * hildon_pannable_area_new:
1333 * Create a new pannable area widget
1335 * Returns: the newly created #HildonPannableArea
1339 hildon_pannable_area_new (void)
1341 return g_object_new (HILDON_TYPE_PANNABLE_AREA, NULL);
1345 * hildon_pannable_area_new_full:
1346 * @mode: #HildonPannableAreaMode
1347 * @enabled: Value for the enabled property
1348 * @vel_min: Value for the velocity-min property
1349 * @vel_max: Value for the velocity-max property
1350 * @decel: Value for the deceleration property
1351 * @sps: Value for the sps property
1353 * Create a new #HildonPannableArea widget and set various properties
1355 * returns: the newly create #HildonPannableArea
1359 hildon_pannable_area_new_full (gint mode, gboolean enabled,
1360 gdouble vel_min, gdouble vel_max,
1361 gdouble decel, guint sps)
1363 return g_object_new (HILDON_TYPE_PANNABLE_AREA,
1366 "velocity_min", vel_min,
1367 "velocity_max", vel_max,
1368 "deceleration", decel, "sps", sps, NULL);
1372 * hildon_pannable_area_add_with_viewport:
1373 * @area: A #HildonPannableArea
1374 * @child: Child widget to add to the viewport
1376 * Convenience function used to add a child to a #GtkViewport, and add the
1377 * viewport to the scrolled window.
1381 hildon_pannable_area_add_with_viewport (HildonPannableArea * area,
1384 GtkWidget *viewport = gtk_viewport_new (NULL, NULL);
1385 gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
1386 gtk_container_add (GTK_CONTAINER (viewport), child);
1387 gtk_widget_show (viewport);
1388 gtk_container_add (GTK_CONTAINER (area), viewport);