1 /* Copyright 2006, 2007, 2008, Soren Sandmann <sandmann@daimi.au.dk>
2 *
3 * This library is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 2 of the License, or (at your option) any later version.
7 *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the
15 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 */
18
19 #include <gdk/gdkprivate.h> /* For GDK_PARENT_RELATIVE_BG */
20 #include "scrollarea.h"
21 #include "foo-marshal.h"
22
23 G_DEFINE_TYPE_WITH_CODE (FooScrollArea, foo_scroll_area, GTK_TYPE_CONTAINER, G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL));
24
25 static GtkWidgetClass *parent_class;
26
27 typedef struct BackingStore BackingStore;
28
29 typedef void (* ExposeFunc) (cairo_t *cr, cairo_region_t *region, gpointer data);
30
31 typedef struct InputPath InputPath;
32 typedef struct InputRegion InputRegion;
33 typedef struct AutoScrollInfo AutoScrollInfo;
34
35 struct InputPath
36 {
37 gboolean is_stroke;
38 cairo_fill_rule_t fill_rule;
39 double line_width;
40 cairo_path_t *path; /* In canvas coordinates */
41
42 FooScrollAreaEventFunc func;
43 gpointer data;
44
45 InputPath *next;
46 };
47
48 /* InputRegions are mutually disjoint */
49 struct InputRegion
50 {
51 cairo_region_t *region; /* the boundary of this area in canvas coordinates */
52 InputPath *paths;
53 };
54
55 struct AutoScrollInfo
56 {
57 int dx;
58 int dy;
59 guint timeout_id;
60 int begin_x;
61 int begin_y;
62 double res_x;
63 double res_y;
64 GTimer *timer;
65 };
66
67 struct FooScrollAreaPrivate
68 {
69 GdkWindow *input_window;
70
71 int width;
72 int height;
73
74 GtkAdjustment *hadj;
75 GtkAdjustment *vadj;
76
77 GtkScrollablePolicy hscroll_policy;
78 GtkScrollablePolicy vscroll_policy;
79
80 int x_offset;
81 int y_offset;
82
83 int min_width;
84 int min_height;
85
86 GPtrArray *input_regions;
87
88 AutoScrollInfo *auto_scroll_info;
89
90 InputRegion *current_input;
91
92 gboolean grabbed;
93 FooScrollAreaEventFunc grab_func;
94 gpointer grab_data;
95
96 cairo_surface_t *surface;
97 cairo_region_t *update_region; /* In canvas coordinates */
98 };
99
100 enum
101 {
102 VIEWPORT_CHANGED,
103 PAINT,
104 INPUT,
105 LAST_SIGNAL,
106 };
107
108 enum {
109 PROP_0,
110 PROP_VADJUSTMENT,
111 PROP_HADJUSTMENT,
112 PROP_HSCROLL_POLICY,
113 PROP_VSCROLL_POLICY
114 };
115
116 static guint signals [LAST_SIGNAL] = { 0 };
117
118 static void foo_scroll_area_get_preferred_width (GtkWidget *widget,
119 gint *minimum,
120 gint *natural);
121 static void foo_scroll_area_get_preferred_height (GtkWidget *widget,
122 gint *minimum,
123 gint *natural);
124 static gboolean foo_scroll_area_draw (GtkWidget *widget,
125 cairo_t *cr);
126 static void foo_scroll_area_size_allocate (GtkWidget *widget,
127 GtkAllocation *allocation);
128 static void foo_scroll_area_set_hadjustment (FooScrollArea *scroll_area,
129 GtkAdjustment *hadjustment);
130 static void foo_scroll_area_set_vadjustment (FooScrollArea *scroll_area,
131 GtkAdjustment *vadjustment);
132 static void foo_scroll_area_realize (GtkWidget *widget);
133 static void foo_scroll_area_unrealize (GtkWidget *widget);
134 static void foo_scroll_area_map (GtkWidget *widget);
135 static void foo_scroll_area_unmap (GtkWidget *widget);
136 static gboolean foo_scroll_area_button_press (GtkWidget *widget,
137 GdkEventButton *event);
138 static gboolean foo_scroll_area_button_release (GtkWidget *widget,
139 GdkEventButton *event);
140 static gboolean foo_scroll_area_motion (GtkWidget *widget,
141 GdkEventMotion *event);
142
143 static void
foo_scroll_area_map(GtkWidget * widget)144 foo_scroll_area_map (GtkWidget *widget)
145 {
146 FooScrollArea *area = FOO_SCROLL_AREA (widget);
147
148 GTK_WIDGET_CLASS (parent_class)->map (widget);
149
150 if (area->priv->input_window)
151 gdk_window_show (area->priv->input_window);
152 }
153
154 static void
foo_scroll_area_unmap(GtkWidget * widget)155 foo_scroll_area_unmap (GtkWidget *widget)
156 {
157 FooScrollArea *area = FOO_SCROLL_AREA (widget);
158
159 if (area->priv->input_window)
160 gdk_window_hide (area->priv->input_window);
161
162 GTK_WIDGET_CLASS (parent_class)->unmap (widget);
163 }
164
165 static void
foo_scroll_area_finalize(GObject * object)166 foo_scroll_area_finalize (GObject *object)
167 {
168 FooScrollArea *scroll_area = FOO_SCROLL_AREA (object);
169
170 g_object_unref (scroll_area->priv->hadj);
171 g_object_unref (scroll_area->priv->vadj);
172
173 g_ptr_array_free (scroll_area->priv->input_regions, TRUE);
174
175 g_free (scroll_area->priv);
176
177 G_OBJECT_CLASS (foo_scroll_area_parent_class)->finalize (object);
178 }
179
180 static void
foo_scroll_area_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)181 foo_scroll_area_get_property (GObject *object,
182 guint property_id,
183 GValue *value,
184 GParamSpec *pspec)
185 {
186 FooScrollArea *scroll_area = FOO_SCROLL_AREA (object);
187
188 switch (property_id)
189 {
190 case PROP_VADJUSTMENT:
191 g_value_set_object (value, &scroll_area->priv->vadj);
192 break;
193 case PROP_HADJUSTMENT:
194 g_value_set_object (value, &scroll_area->priv->hadj);
195 break;
196 case PROP_HSCROLL_POLICY:
197 g_value_set_enum (value, scroll_area->priv->hscroll_policy);
198 break;
199 case PROP_VSCROLL_POLICY:
200 g_value_set_enum (value, scroll_area->priv->vscroll_policy);
201 break;
202 default:
203 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
204 }
205 }
206
207 static void
foo_scroll_area_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)208 foo_scroll_area_set_property (GObject *object,
209 guint property_id,
210 const GValue *value,
211 GParamSpec *pspec)
212 {
213 FooScrollArea *scroll_area = FOO_SCROLL_AREA (object);
214 switch (property_id) {
215 case PROP_VADJUSTMENT:
216 foo_scroll_area_set_vadjustment (FOO_SCROLL_AREA (object), g_value_get_object (value));
217 break;
218 case PROP_HADJUSTMENT:
219 foo_scroll_area_set_hadjustment (FOO_SCROLL_AREA (object), g_value_get_object (value));
220 break;
221 case PROP_HSCROLL_POLICY:
222 scroll_area->priv->hscroll_policy = g_value_get_enum (value);
223 break;
224 case PROP_VSCROLL_POLICY:
225 scroll_area->priv->vscroll_policy = g_value_get_enum (value);
226 break;
227 default:
228 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
229 }
230 }
231
232 static void
foo_scroll_area_class_init(FooScrollAreaClass * class)233 foo_scroll_area_class_init (FooScrollAreaClass *class)
234 {
235 GObjectClass *object_class = G_OBJECT_CLASS (class);
236 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
237
238 object_class->finalize = foo_scroll_area_finalize;
239 object_class->set_property = foo_scroll_area_set_property;
240 object_class->get_property = foo_scroll_area_get_property;
241 widget_class->draw = foo_scroll_area_draw;
242 widget_class->get_preferred_width = foo_scroll_area_get_preferred_width;
243 widget_class->get_preferred_height = foo_scroll_area_get_preferred_height;
244 widget_class->size_allocate = foo_scroll_area_size_allocate;
245 widget_class->realize = foo_scroll_area_realize;
246 widget_class->unrealize = foo_scroll_area_unrealize;
247 widget_class->button_press_event = foo_scroll_area_button_press;
248 widget_class->button_release_event = foo_scroll_area_button_release;
249 widget_class->motion_notify_event = foo_scroll_area_motion;
250 widget_class->map = foo_scroll_area_map;
251 widget_class->unmap = foo_scroll_area_unmap;
252
253 gtk_widget_class_set_css_name (widget_class, "foo-scroll-area");
254
255 parent_class = g_type_class_peek_parent (class);
256
257 /* Scrollable interface properties */
258 g_object_class_override_property (object_class, PROP_HADJUSTMENT, "hadjustment");
259 g_object_class_override_property (object_class, PROP_VADJUSTMENT, "vadjustment");
260 g_object_class_override_property (object_class, PROP_HSCROLL_POLICY, "hscroll-policy");
261 g_object_class_override_property (object_class, PROP_VSCROLL_POLICY, "vscroll-policy");
262
263 signals[VIEWPORT_CHANGED] =
264 g_signal_new ("viewport_changed",
265 G_OBJECT_CLASS_TYPE (object_class),
266 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
267 G_STRUCT_OFFSET (FooScrollAreaClass,
268 viewport_changed),
269 NULL, NULL,
270 foo_marshal_VOID__BOXED_BOXED,
271 G_TYPE_NONE, 2,
272 GDK_TYPE_RECTANGLE,
273 GDK_TYPE_RECTANGLE);
274
275 signals[PAINT] =
276 g_signal_new ("paint",
277 G_OBJECT_CLASS_TYPE (object_class),
278 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
279 G_STRUCT_OFFSET (FooScrollAreaClass,
280 paint),
281 NULL, NULL,
282 g_cclosure_marshal_VOID__POINTER,
283 G_TYPE_NONE,
284 1,
285 G_TYPE_POINTER);
286 }
287
288 static GtkAdjustment *
new_adjustment(void)289 new_adjustment (void)
290 {
291 return GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
292 }
293
294 static void
foo_scroll_area_init(FooScrollArea * scroll_area)295 foo_scroll_area_init (FooScrollArea *scroll_area)
296 {
297 GtkWidget *widget;
298
299 widget = GTK_WIDGET (scroll_area);
300
301 gtk_widget_set_has_window (widget, FALSE);
302 gtk_widget_set_redraw_on_allocate (widget, FALSE);
303
304 scroll_area->priv = g_new0 (FooScrollAreaPrivate, 1);
305 scroll_area->priv->width = 0;
306 scroll_area->priv->height = 0;
307 scroll_area->priv->hadj = g_object_ref_sink (new_adjustment());
308 scroll_area->priv->vadj = g_object_ref_sink (new_adjustment());
309 scroll_area->priv->x_offset = 0.0;
310 scroll_area->priv->y_offset = 0.0;
311 scroll_area->priv->min_width = 0;
312 scroll_area->priv->min_height = 0;
313 scroll_area->priv->auto_scroll_info = NULL;
314 scroll_area->priv->input_regions = g_ptr_array_new ();
315 scroll_area->priv->surface = NULL;
316 scroll_area->priv->update_region = cairo_region_create ();
317 }
318
319 typedef void (* PathForeachFunc) (double *x,
320 double *y,
321 gpointer data);
322
323 static void
path_foreach_point(cairo_path_t * path,PathForeachFunc func,gpointer user_data)324 path_foreach_point (cairo_path_t *path,
325 PathForeachFunc func,
326 gpointer user_data)
327 {
328 int i;
329
330 for (i = 0; i < path->num_data; i += path->data[i].header.length)
331 {
332 cairo_path_data_t *data = &(path->data[i]);
333
334 switch (data->header.type)
335 {
336 case CAIRO_PATH_MOVE_TO:
337 case CAIRO_PATH_LINE_TO:
338 func (&(data[1].point.x), &(data[1].point.y), user_data);
339 break;
340
341 case CAIRO_PATH_CURVE_TO:
342 func (&(data[1].point.x), &(data[1].point.y), user_data);
343 func (&(data[2].point.x), &(data[2].point.y), user_data);
344 func (&(data[3].point.x), &(data[3].point.y), user_data);
345 break;
346
347 case CAIRO_PATH_CLOSE_PATH:
348 break;
349 }
350 }
351 }
352
353 typedef struct
354 {
355 double x1, y1, x2, y2;
356 } Box;
357
358 static void
input_path_free_list(InputPath * paths)359 input_path_free_list (InputPath *paths)
360 {
361 if (!paths)
362 return;
363
364 input_path_free_list (paths->next);
365 cairo_path_destroy (paths->path);
366 g_free (paths);
367 }
368
369 static void
input_region_free(InputRegion * region)370 input_region_free (InputRegion *region)
371 {
372 input_path_free_list (region->paths);
373 cairo_region_destroy (region->region);
374
375 g_free (region);
376 }
377
378 static void
get_viewport(FooScrollArea * scroll_area,GdkRectangle * viewport)379 get_viewport (FooScrollArea *scroll_area,
380 GdkRectangle *viewport)
381 {
382 GtkAllocation allocation;
383 GtkWidget *widget = GTK_WIDGET (scroll_area);
384
385 gtk_widget_get_allocation (widget, &allocation);
386
387 viewport->x = scroll_area->priv->x_offset;
388 viewport->y = scroll_area->priv->y_offset;
389 viewport->width = allocation.width;
390 viewport->height = allocation.height;
391 }
392
393 static void
allocation_to_canvas(FooScrollArea * area,int * x,int * y)394 allocation_to_canvas (FooScrollArea *area,
395 int *x,
396 int *y)
397 {
398 *x += area->priv->x_offset;
399 *y += area->priv->y_offset;
400 }
401
402 static void
clear_exposed_input_region(FooScrollArea * area,cairo_region_t * exposed)403 clear_exposed_input_region (FooScrollArea *area,
404 cairo_region_t *exposed) /* in canvas coordinates */
405 {
406 guint i;
407 cairo_region_t *viewport;
408 GdkRectangle allocation;
409
410 gtk_widget_get_allocation (GTK_WIDGET (area), &allocation);
411 allocation.x = 0;
412 allocation.y = 0;
413 allocation_to_canvas (area, &allocation.x, &allocation.y);
414 viewport = cairo_region_create_rectangle (&allocation);
415 cairo_region_subtract (viewport, exposed);
416
417 for (i = 0; i < area->priv->input_regions->len; ++i)
418 {
419 InputRegion *region = area->priv->input_regions->pdata[i];
420
421 cairo_region_intersect (region->region, viewport);
422
423 if (cairo_region_is_empty (region->region))
424 {
425 input_region_free (region);
426 g_ptr_array_remove_index_fast (area->priv->input_regions, i--);
427 }
428 }
429
430 cairo_region_destroy (viewport);
431 }
432
433 static void
setup_background_cr(GdkWindow * window,cairo_t * cr,int x_offset,int y_offset)434 setup_background_cr (GdkWindow *window,
435 cairo_t *cr,
436 int x_offset,
437 int y_offset)
438 {
439 GdkWindow *parent = gdk_window_get_parent (window);
440 cairo_pattern_t *bg_pattern;
441
442 bg_pattern = gdk_window_get_background_pattern (window);
443 if (bg_pattern == NULL && parent)
444 {
445 gint window_x, window_y;
446
447 gdk_window_get_position (window, &window_x, &window_y);
448 setup_background_cr (parent, cr, x_offset + window_x, y_offset + window_y);
449 }
450 else if (bg_pattern)
451 {
452 cairo_translate (cr, - x_offset, - y_offset);
453 cairo_set_source (cr, bg_pattern);
454 cairo_translate (cr, x_offset, y_offset);
455 }
456 }
457
458 static void
initialize_background(GtkWidget * widget,cairo_t * cr)459 initialize_background (GtkWidget *widget,
460 cairo_t *cr)
461 {
462 setup_background_cr (gtk_widget_get_window (widget), cr, 0, 0);
463
464 cairo_paint (cr);
465 }
466
467 static gboolean
foo_scroll_area_draw(GtkWidget * widget,cairo_t * widget_cr)468 foo_scroll_area_draw (GtkWidget *widget,
469 cairo_t *widget_cr)
470 {
471 FooScrollArea *scroll_area = FOO_SCROLL_AREA (widget);
472 cairo_t *cr;
473 cairo_region_t *region;
474 GtkAllocation widget_allocation;
475
476 /* Setup input areas */
477 clear_exposed_input_region (scroll_area, scroll_area->priv->update_region);
478
479 scroll_area->priv->current_input = g_new0 (InputRegion, 1);
480 scroll_area->priv->current_input->region = cairo_region_copy (scroll_area->priv->update_region);
481 scroll_area->priv->current_input->paths = NULL;
482 g_ptr_array_add (scroll_area->priv->input_regions,
483 scroll_area->priv->current_input);
484
485 region = scroll_area->priv->update_region;
486 scroll_area->priv->update_region = cairo_region_create ();
487
488 /* Create cairo context */
489 cr = cairo_create (scroll_area->priv->surface);
490 initialize_background (widget, cr);
491
492 g_signal_emit (widget, signals[PAINT], 0, cr);
493
494 /* Destroy stuff */
495 cairo_destroy (cr);
496
497 scroll_area->priv->current_input = NULL;
498
499 /* Finally draw the backing pixmap */
500 gtk_widget_get_allocation (widget, &widget_allocation);
501 cairo_set_source_surface (widget_cr, scroll_area->priv->surface, widget_allocation.x, widget_allocation.y);
502 cairo_paint (widget_cr);
503
504 cairo_region_destroy (region);
505
506 return TRUE;
507 }
508
509 void
foo_scroll_area_get_viewport(FooScrollArea * scroll_area,GdkRectangle * viewport)510 foo_scroll_area_get_viewport (FooScrollArea *scroll_area,
511 GdkRectangle *viewport)
512 {
513 g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
514
515 if (!viewport)
516 return;
517
518 get_viewport (scroll_area, viewport);
519 }
520
521 static void
522 process_event (FooScrollArea *scroll_area,
523 FooScrollAreaEventType input_type,
524 int x,
525 int y);
526
527 static void
emit_viewport_changed(FooScrollArea * scroll_area,GdkRectangle * new_viewport,GdkRectangle * old_viewport)528 emit_viewport_changed (FooScrollArea *scroll_area,
529 GdkRectangle *new_viewport,
530 GdkRectangle *old_viewport)
531 {
532 GdkDisplay *display;
533 GdkSeat *seat;
534 GdkDevice *pointer;
535
536 int px, py;
537 g_signal_emit (scroll_area, signals[VIEWPORT_CHANGED], 0,
538 new_viewport, old_viewport);
539
540 if (scroll_area->priv->input_window == NULL)
541 return;
542
543 display = gdk_window_get_display (scroll_area->priv->input_window);
544 seat = gdk_display_get_default_seat (display);
545 pointer = gdk_seat_get_pointer (seat);
546 gdk_window_get_device_position (scroll_area->priv->input_window,
547 pointer,
548 &px,
549 &py,
550 NULL);
551
552 process_event (scroll_area, FOO_MOTION, px, py);
553 }
554
555 static void
clamp_adjustment(GtkAdjustment * adj)556 clamp_adjustment (GtkAdjustment *adj)
557 {
558 if (gtk_adjustment_get_upper (adj) >= gtk_adjustment_get_page_size (adj))
559 gtk_adjustment_set_value (adj, CLAMP (gtk_adjustment_get_value (adj), 0.0,
560 gtk_adjustment_get_upper (adj)
561 - gtk_adjustment_get_page_size (adj)));
562 else
563 gtk_adjustment_set_value (adj, 0.0);
564 }
565
566 static gboolean
set_adjustment_values(FooScrollArea * scroll_area)567 set_adjustment_values (FooScrollArea *scroll_area)
568 {
569 GtkAllocation allocation;
570
571 GtkAdjustment *hadj = scroll_area->priv->hadj;
572 GtkAdjustment *vadj = scroll_area->priv->vadj;
573
574 /* Horizontal */
575 gtk_widget_get_allocation (GTK_WIDGET (scroll_area), &allocation);
576 g_object_freeze_notify (G_OBJECT (hadj));
577 gtk_adjustment_set_page_size (hadj, allocation.width);
578 gtk_adjustment_set_step_increment (hadj, 0.1 * allocation.width);
579 gtk_adjustment_set_page_increment (hadj, 0.9 * allocation.width);
580 gtk_adjustment_set_lower (hadj, 0.0);
581 gtk_adjustment_set_upper (hadj, scroll_area->priv->width);
582 g_object_thaw_notify (G_OBJECT (hadj));
583
584 /* Vertical */
585 g_object_freeze_notify (G_OBJECT (vadj));
586 gtk_adjustment_set_page_size (vadj, allocation.height);
587 gtk_adjustment_set_step_increment (vadj, 0.1 * allocation.height);
588 gtk_adjustment_set_page_increment (vadj, 0.9 * allocation.height);
589 gtk_adjustment_set_lower (vadj, 0.0);
590 gtk_adjustment_set_upper (vadj, scroll_area->priv->height);
591 g_object_thaw_notify (G_OBJECT (vadj));
592
593 clamp_adjustment (hadj);
594 clamp_adjustment (vadj);
595
596 return TRUE;
597 }
598
599 static void
foo_scroll_area_realize(GtkWidget * widget)600 foo_scroll_area_realize (GtkWidget *widget)
601 {
602 FooScrollArea *area = FOO_SCROLL_AREA (widget);
603 GdkWindowAttr attributes;
604 GtkAllocation widget_allocation;
605 GdkWindow *window;
606 gint attributes_mask;
607 cairo_t *cr;
608
609 gtk_widget_get_allocation (widget, &widget_allocation);
610 gtk_widget_set_realized (widget, TRUE);
611
612 attributes.window_type = GDK_WINDOW_CHILD;
613 attributes.x = widget_allocation.x;
614 attributes.y = widget_allocation.y;
615 attributes.width = widget_allocation.width;
616 attributes.height = widget_allocation.height;
617 attributes.wclass = GDK_INPUT_ONLY;
618 attributes.event_mask = gtk_widget_get_events (widget);
619 attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
620 GDK_BUTTON_RELEASE_MASK |
621 GDK_BUTTON1_MOTION_MASK |
622 GDK_BUTTON2_MOTION_MASK |
623 GDK_BUTTON3_MOTION_MASK |
624 GDK_POINTER_MOTION_MASK |
625 GDK_ENTER_NOTIFY_MASK |
626 GDK_LEAVE_NOTIFY_MASK);
627
628 attributes_mask = GDK_WA_X | GDK_WA_Y;
629
630 window = gtk_widget_get_parent_window (widget);
631 gtk_widget_set_window (widget, window);
632 g_object_ref (window);
633
634 area->priv->input_window = gdk_window_new (window,
635 &attributes, attributes_mask);
636 cr = gdk_cairo_create (gtk_widget_get_window (widget));
637 area->priv->surface = cairo_surface_create_similar (cairo_get_target (cr), CAIRO_CONTENT_COLOR,
638 widget_allocation.width, widget_allocation.height);
639 cairo_destroy (cr);
640
641 gdk_window_set_user_data (area->priv->input_window, area);
642
643 gtk_widget_style_attach (widget);
644 }
645
646 static void
foo_scroll_area_unrealize(GtkWidget * widget)647 foo_scroll_area_unrealize (GtkWidget *widget)
648 {
649 FooScrollArea *area = FOO_SCROLL_AREA (widget);
650
651 if (area->priv->input_window)
652 {
653 gdk_window_set_user_data (area->priv->input_window, NULL);
654 gdk_window_destroy (area->priv->input_window);
655 area->priv->input_window = NULL;
656 }
657
658 GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
659 }
660
661 static cairo_surface_t *
create_new_surface(GtkWidget * widget,cairo_surface_t * old)662 create_new_surface (GtkWidget *widget,
663 cairo_surface_t *old)
664 {
665 GtkAllocation widget_allocation;
666 cairo_t *cr;
667 cairo_surface_t *new;
668
669 gtk_widget_get_allocation (widget, &widget_allocation);
670
671 cr = gdk_cairo_create (gtk_widget_get_window (widget));
672 new = cairo_surface_create_similar (cairo_get_target (cr),
673 CAIRO_CONTENT_COLOR,
674 widget_allocation.width,
675 widget_allocation.height);
676 cairo_destroy (cr);
677
678 /* Unfortunately we don't know in which direction we were resized,
679 * so we just assume we were dragged from the south-east corner.
680 *
681 * Although, maybe we could get the root coordinates of the input-window?
682 * That might just work, actually. We need to make sure marco uses
683 * static gravity for the window before this will be useful.
684 */
685
686 cr = cairo_create (new);
687 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
688 cairo_set_source_surface (cr, old, 0, 0);
689
690 cairo_paint (cr);
691 cairo_destroy (cr);
692
693 return new;
694 }
695
696 static void
allocation_to_canvas_region(FooScrollArea * area,cairo_region_t * region)697 allocation_to_canvas_region (FooScrollArea *area,
698 cairo_region_t *region)
699 {
700 cairo_region_translate (region, area->priv->x_offset, area->priv->y_offset);
701 }
702
703 static void
_cairo_region_xor(cairo_region_t * dst,const cairo_region_t * src)704 _cairo_region_xor (cairo_region_t *dst, const cairo_region_t *src)
705 {
706 cairo_region_t *trb;
707
708 trb = cairo_region_copy (src);
709
710 cairo_region_subtract (trb, dst);
711 cairo_region_subtract (dst, src);
712 cairo_region_union (dst, trb);
713 cairo_region_destroy (trb);
714 }
715
716 static void
foo_scroll_area_size_allocate(GtkWidget * widget,GtkAllocation * allocation)717 foo_scroll_area_size_allocate (GtkWidget *widget,
718 GtkAllocation *allocation)
719 {
720 FooScrollArea *scroll_area = FOO_SCROLL_AREA (widget);
721 GdkRectangle new_viewport;
722 GdkRectangle old_viewport;
723 cairo_region_t *old_allocation;
724 cairo_region_t *invalid;
725 GtkAllocation widget_allocation;
726
727 get_viewport (scroll_area, &old_viewport);
728
729 gtk_widget_get_allocation (widget, &widget_allocation);
730 old_allocation = cairo_region_create_rectangle (&widget_allocation);
731 cairo_region_translate (old_allocation,
732 -widget_allocation.x, -widget_allocation.y);
733 invalid = cairo_region_create_rectangle (allocation);
734 cairo_region_translate (invalid, -allocation->x, -allocation->y);
735 _cairo_region_xor (invalid, old_allocation);
736 allocation_to_canvas_region (scroll_area, invalid);
737 foo_scroll_area_invalidate_region (scroll_area, invalid);
738 cairo_region_destroy (old_allocation);
739 cairo_region_destroy (invalid);
740
741 gtk_widget_set_allocation (widget, allocation);
742
743 if (scroll_area->priv->input_window)
744 {
745 cairo_surface_t *new_surface;
746
747 gdk_window_move_resize (scroll_area->priv->input_window,
748 allocation->x, allocation->y,
749 allocation->width, allocation->height);
750
751 new_surface = create_new_surface (widget, scroll_area->priv->surface);
752 cairo_surface_destroy (scroll_area->priv->surface);
753 scroll_area->priv->surface = new_surface;
754 }
755
756 get_viewport (scroll_area, &new_viewport);
757
758 emit_viewport_changed (scroll_area, &new_viewport, &old_viewport);
759 }
760
761 static void
emit_input(FooScrollArea * scroll_area,FooScrollAreaEventType type,int x,int y,FooScrollAreaEventFunc func,gpointer data)762 emit_input (FooScrollArea *scroll_area,
763 FooScrollAreaEventType type,
764 int x,
765 int y,
766 FooScrollAreaEventFunc func,
767 gpointer data)
768 {
769 FooScrollAreaEvent event;
770
771 if (!func)
772 return;
773
774 if (type != FOO_MOTION)
775 emit_input (scroll_area, FOO_MOTION, x, y, func, data);
776
777 event.type = type;
778 event.x = x;
779 event.y = y;
780
781 func (scroll_area, &event, data);
782 }
783
784 static void
process_event(FooScrollArea * scroll_area,FooScrollAreaEventType input_type,int x,int y)785 process_event (FooScrollArea *scroll_area,
786 FooScrollAreaEventType input_type,
787 int x,
788 int y)
789 {
790 GtkWidget *widget = GTK_WIDGET (scroll_area);
791 guint i;
792
793 allocation_to_canvas (scroll_area, &x, &y);
794
795 if (scroll_area->priv->grabbed)
796 {
797 emit_input (scroll_area, input_type, x, y,
798 scroll_area->priv->grab_func,
799 scroll_area->priv->grab_data);
800 return;
801 }
802
803 for (i = 0; i < scroll_area->priv->input_regions->len; ++i)
804 {
805 InputRegion *region = scroll_area->priv->input_regions->pdata[i];
806
807 if (cairo_region_contains_point (region->region, x, y))
808 {
809 InputPath *path;
810
811 path = region->paths;
812 while (path)
813 {
814 cairo_t *cr;
815 gboolean inside;
816
817 cr = gdk_cairo_create (gtk_widget_get_window (widget));
818 cairo_set_fill_rule (cr, path->fill_rule);
819 cairo_set_line_width (cr, path->line_width);
820 cairo_append_path (cr, path->path);
821
822 if (path->is_stroke)
823 inside = cairo_in_stroke (cr, x, y);
824 else
825 inside = cairo_in_fill (cr, x, y);
826
827 cairo_destroy (cr);
828
829 if (inside)
830 {
831 emit_input (scroll_area, input_type,
832 x, y,
833 path->func,
834 path->data);
835 return;
836 }
837
838 path = path->next;
839 }
840
841 /* Since the regions are all disjoint, no other region
842 * can match. Of course we could be clever and try and
843 * sort the regions, but so far I have been unable to
844 * make this loop show up on a profile.
845 */
846 return;
847 }
848 }
849 }
850
851 static void
process_gdk_event(FooScrollArea * scroll_area,int x,int y,GdkEvent * event)852 process_gdk_event (FooScrollArea *scroll_area,
853 int x,
854 int y,
855 GdkEvent *event)
856 {
857 FooScrollAreaEventType input_type;
858
859 if (event->type == GDK_BUTTON_PRESS)
860 input_type = FOO_BUTTON_PRESS;
861 else if (event->type == GDK_BUTTON_RELEASE)
862 input_type = FOO_BUTTON_RELEASE;
863 else if (event->type == GDK_MOTION_NOTIFY)
864 input_type = FOO_MOTION;
865 else
866 return;
867
868 process_event (scroll_area, input_type, x, y);
869 }
870
871 static gboolean
foo_scroll_area_button_press(GtkWidget * widget,GdkEventButton * event)872 foo_scroll_area_button_press (GtkWidget *widget,
873 GdkEventButton *event)
874 {
875 FooScrollArea *area = FOO_SCROLL_AREA (widget);
876
877 process_gdk_event (area, (int) event->x, (int) event->y, (GdkEvent *)event);
878
879 return TRUE;
880 }
881
882 static gboolean
foo_scroll_area_button_release(GtkWidget * widget,GdkEventButton * event)883 foo_scroll_area_button_release (GtkWidget *widget,
884 GdkEventButton *event)
885 {
886 FooScrollArea *area = FOO_SCROLL_AREA (widget);
887
888 process_gdk_event (area, (int) event->x, (int) event->y, (GdkEvent *)event);
889
890 return FALSE;
891 }
892
893 static gboolean
foo_scroll_area_motion(GtkWidget * widget,GdkEventMotion * event)894 foo_scroll_area_motion (GtkWidget *widget,
895 GdkEventMotion *event)
896 {
897 FooScrollArea *area = FOO_SCROLL_AREA (widget);
898
899 process_gdk_event (area, (int) event->x, (int) event->y, (GdkEvent *)event);
900 return TRUE;
901 }
902
903 void
foo_scroll_area_set_size_fixed_y(FooScrollArea * scroll_area,int width,int height,int old_y,int new_y)904 foo_scroll_area_set_size_fixed_y (FooScrollArea *scroll_area,
905 int width,
906 int height,
907 int old_y,
908 int new_y)
909 {
910 scroll_area->priv->width = width;
911 scroll_area->priv->height = height;
912
913 g_object_thaw_notify (G_OBJECT (scroll_area->priv->vadj));
914 gtk_adjustment_set_value (scroll_area->priv->vadj, new_y);
915
916 set_adjustment_values (scroll_area);
917 g_object_thaw_notify (G_OBJECT (scroll_area->priv->vadj));
918 }
919
920 void
foo_scroll_area_set_size(FooScrollArea * scroll_area,int width,int height)921 foo_scroll_area_set_size (FooScrollArea *scroll_area,
922 int width,
923 int height)
924 {
925 g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
926
927 /* FIXME: Default scroll algorithm should probably be to
928 * keep the same *area* outside the screen as before.
929 *
930 * For wrapper widgets that will do something roughly
931 * right. For widgets that don't change size, it
932 * will do the right thing. Except for idle-layouting
933 * widgets.
934 *
935 * Maybe there should be some generic support for those
936 * widgets. Can that even be done?
937 *
938 * Should we have a version of this function using
939 * fixed points?
940 */
941
942 scroll_area->priv->width = width;
943 scroll_area->priv->height = height;
944
945 set_adjustment_values (scroll_area);
946 }
947
948 static void
foo_scroll_area_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)949 foo_scroll_area_get_preferred_width (GtkWidget *widget,
950 gint *minimum,
951 gint *natural)
952 {
953 FooScrollArea *scroll_area = FOO_SCROLL_AREA (widget);
954
955 if (minimum != NULL) {
956 *minimum = scroll_area->priv->min_width;
957 }
958 if (natural != NULL) {
959 *natural = scroll_area->priv->min_width;
960 }
961 }
962
963 static void
foo_scroll_area_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)964 foo_scroll_area_get_preferred_height (GtkWidget *widget,
965 gint *minimum,
966 gint *natural)
967 {
968 FooScrollArea *scroll_area = FOO_SCROLL_AREA (widget);
969
970 if (minimum != NULL) {
971 *minimum = scroll_area->priv->min_height;
972 }
973 if (natural != NULL) {
974 *natural = scroll_area->priv->min_height;
975 }
976 }
977
978 static void
foo_scroll_area_scroll(FooScrollArea * area,gint dx,gint dy)979 foo_scroll_area_scroll (FooScrollArea *area,
980 gint dx,
981 gint dy)
982 {
983 GdkRectangle allocation;
984 GdkRectangle src_area;
985 GdkRectangle move_area;
986 cairo_region_t *invalid_region;
987
988 gtk_widget_get_allocation (GTK_WIDGET (area), &allocation);
989 allocation.x = 0;
990 allocation.y = 0;
991
992 src_area = allocation;
993 src_area.x -= dx;
994 src_area.y -= dy;
995
996 invalid_region = cairo_region_create_rectangle (&allocation);
997
998 if (gdk_rectangle_intersect (&allocation, &src_area, &move_area))
999 {
1000 cairo_region_t *move_region;
1001 cairo_t *cr;
1002
1003 cr = cairo_create (area->priv->surface);
1004
1005 /* Cairo doesn't allow self-copies, so we do this little trick instead:
1006 * 1) Clip so the group size is small.
1007 * 2) Call push_group() which creates a temporary pixmap as a workaround
1008 */
1009 gdk_cairo_rectangle (cr, &move_area);
1010 cairo_clip (cr);
1011 cairo_push_group (cr);
1012
1013 cairo_set_source_surface (cr, area->priv->surface, dx, dy);
1014 gdk_cairo_rectangle (cr, &move_area);
1015 cairo_fill (cr);
1016
1017 cairo_pop_group_to_source (cr);
1018 cairo_paint (cr);
1019
1020 cairo_destroy (cr);
1021
1022 gtk_widget_queue_draw (GTK_WIDGET (area));
1023
1024 move_region = cairo_region_create_rectangle (&move_area);
1025 cairo_region_translate (move_region, dx, dy);
1026 cairo_region_subtract (invalid_region, move_region);
1027 cairo_region_destroy (move_region);
1028 }
1029
1030 allocation_to_canvas_region (area, invalid_region);
1031
1032 foo_scroll_area_invalidate_region (area, invalid_region);
1033
1034 cairo_region_destroy (invalid_region);
1035 }
1036
1037 static void
foo_scrollbar_adjustment_changed(GtkAdjustment * adj,FooScrollArea * scroll_area)1038 foo_scrollbar_adjustment_changed (GtkAdjustment *adj,
1039 FooScrollArea *scroll_area)
1040 {
1041 GtkWidget *widget = GTK_WIDGET (scroll_area);
1042 gint dx = 0;
1043 gint dy = 0;
1044 GdkRectangle old_viewport, new_viewport;
1045 gdouble aux;
1046 int offset;
1047
1048 get_viewport (scroll_area, &old_viewport);
1049
1050 aux = gtk_adjustment_get_value (adj);
1051 offset = (int) aux;
1052 if (adj == scroll_area->priv->hadj)
1053 {
1054 dx = offset - scroll_area->priv->x_offset;
1055 scroll_area->priv->x_offset = offset;
1056 }
1057 else if (adj == scroll_area->priv->vadj)
1058 {
1059 dy = offset - scroll_area->priv->y_offset;
1060 scroll_area->priv->y_offset = offset;
1061 }
1062 else
1063 {
1064 g_assert_not_reached ();
1065 }
1066
1067 if (gtk_widget_get_realized (widget))
1068 {
1069 foo_scroll_area_scroll (scroll_area, -dx, -dy);
1070 }
1071
1072 get_viewport (scroll_area, &new_viewport);
1073
1074 emit_viewport_changed (scroll_area, &new_viewport, &old_viewport);
1075 }
1076
1077 static void
set_one_adjustment(FooScrollArea * scroll_area,GtkAdjustment * adjustment,GtkAdjustment ** location)1078 set_one_adjustment (FooScrollArea *scroll_area,
1079 GtkAdjustment *adjustment,
1080 GtkAdjustment **location)
1081 {
1082 g_return_if_fail (location != NULL);
1083
1084 if (adjustment == *location)
1085 return;
1086
1087 if (!adjustment)
1088 adjustment = new_adjustment ();
1089
1090 g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
1091
1092 if (*location)
1093 {
1094 g_signal_handlers_disconnect_by_func (
1095 *location, foo_scrollbar_adjustment_changed, scroll_area);
1096
1097 g_object_unref (*location);
1098 }
1099
1100 *location = adjustment;
1101
1102 g_object_ref_sink (*location);
1103
1104 g_signal_connect (*location, "value_changed",
1105 G_CALLBACK (foo_scrollbar_adjustment_changed),
1106 scroll_area);
1107 }
1108
1109 static void
foo_scroll_area_set_hadjustment(FooScrollArea * scroll_area,GtkAdjustment * hadjustment)1110 foo_scroll_area_set_hadjustment (FooScrollArea *scroll_area,
1111 GtkAdjustment *hadjustment)
1112 {
1113 set_one_adjustment (scroll_area, hadjustment, &scroll_area->priv->hadj);
1114
1115 set_adjustment_values (scroll_area);
1116 }
1117
1118 static void
foo_scroll_area_set_vadjustment(FooScrollArea * scroll_area,GtkAdjustment * vadjustment)1119 foo_scroll_area_set_vadjustment (FooScrollArea *scroll_area,
1120 GtkAdjustment *vadjustment)
1121 {
1122 set_one_adjustment (scroll_area, vadjustment, &scroll_area->priv->vadj);
1123
1124 set_adjustment_values (scroll_area);
1125 }
1126
1127 FooScrollArea *
foo_scroll_area_new(void)1128 foo_scroll_area_new (void)
1129 {
1130 return g_object_new (FOO_TYPE_SCROLL_AREA, NULL);
1131 }
1132
1133 void
foo_scroll_area_set_min_size(FooScrollArea * scroll_area,int min_width,int min_height)1134 foo_scroll_area_set_min_size (FooScrollArea *scroll_area,
1135 int min_width,
1136 int min_height)
1137 {
1138 scroll_area->priv->min_width = min_width;
1139 scroll_area->priv->min_height = min_height;
1140
1141 /* FIXME: think through invalidation.
1142 *
1143 * Goals: - no repainting everything on size_allocate(),
1144 * - make sure input boxes are invalidated when
1145 * needed
1146 */
1147 gtk_widget_queue_resize (GTK_WIDGET (scroll_area));
1148 }
1149
1150 static void
user_to_device(double * x,double * y,gpointer data)1151 user_to_device (double *x, double *y,
1152 gpointer data)
1153 {
1154 cairo_t *cr = data;
1155
1156 cairo_user_to_device (cr, x, y);
1157 }
1158
1159 static InputPath *
make_path(FooScrollArea * area,cairo_t * cr,gboolean is_stroke,FooScrollAreaEventFunc func,gpointer data)1160 make_path (FooScrollArea *area,
1161 cairo_t *cr,
1162 gboolean is_stroke,
1163 FooScrollAreaEventFunc func,
1164 gpointer data)
1165 {
1166 InputPath *path = g_new0 (InputPath, 1);
1167
1168 path->is_stroke = is_stroke;
1169 path->fill_rule = cairo_get_fill_rule (cr);
1170 path->line_width = cairo_get_line_width (cr);
1171 path->path = cairo_copy_path (cr);
1172 path_foreach_point (path->path, user_to_device, cr);
1173 path->func = func;
1174 path->data = data;
1175 path->next = area->priv->current_input->paths;
1176 area->priv->current_input->paths = path;
1177 return path;
1178 }
1179
1180 /* FIXME: we probably really want a
1181 *
1182 * foo_scroll_area_add_input_from_fill (area, cr, ...);
1183 * and
1184 * foo_scroll_area_add_input_from_stroke (area, cr, ...);
1185 * as well.
1186 */
1187 void
foo_scroll_area_add_input_from_fill(FooScrollArea * scroll_area,cairo_t * cr,FooScrollAreaEventFunc func,gpointer data)1188 foo_scroll_area_add_input_from_fill (FooScrollArea *scroll_area,
1189 cairo_t *cr,
1190 FooScrollAreaEventFunc func,
1191 gpointer data)
1192 {
1193 g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
1194 g_return_if_fail (cr != NULL);
1195 g_return_if_fail (scroll_area->priv->current_input);
1196
1197 make_path (scroll_area, cr, FALSE, func, data);
1198 }
1199
1200 void
foo_scroll_area_add_input_from_stroke(FooScrollArea * scroll_area,cairo_t * cr,FooScrollAreaEventFunc func,gpointer data)1201 foo_scroll_area_add_input_from_stroke (FooScrollArea *scroll_area,
1202 cairo_t *cr,
1203 FooScrollAreaEventFunc func,
1204 gpointer data)
1205 {
1206 g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
1207 g_return_if_fail (cr != NULL);
1208 g_return_if_fail (scroll_area->priv->current_input);
1209
1210 make_path (scroll_area, cr, TRUE, func, data);
1211 }
1212
1213 void
foo_scroll_area_invalidate(FooScrollArea * scroll_area)1214 foo_scroll_area_invalidate (FooScrollArea *scroll_area)
1215 {
1216 GtkAllocation allocation;
1217 GtkWidget *widget = GTK_WIDGET (scroll_area);
1218
1219 gtk_widget_get_allocation (widget, &allocation);
1220 foo_scroll_area_invalidate_rect (scroll_area,
1221 scroll_area->priv->x_offset, scroll_area->priv->y_offset,
1222 allocation.width,
1223 allocation.height);
1224 }
1225
1226 static void
canvas_to_window(FooScrollArea * area,cairo_region_t * region)1227 canvas_to_window (FooScrollArea *area,
1228 cairo_region_t *region)
1229 {
1230 GtkAllocation allocation;
1231 GtkWidget *widget = GTK_WIDGET (area);
1232
1233 gtk_widget_get_allocation (widget, &allocation);
1234 cairo_region_translate (region,
1235 -area->priv->x_offset + allocation.x,
1236 -area->priv->y_offset + allocation.y);
1237 }
1238
1239 static void
window_to_canvas(FooScrollArea * area,cairo_region_t * region)1240 window_to_canvas (FooScrollArea *area,
1241 cairo_region_t *region)
1242 {
1243 GtkAllocation allocation;
1244 GtkWidget *widget = GTK_WIDGET (area);
1245
1246 gtk_widget_get_allocation (widget, &allocation);
1247 cairo_region_translate (region,
1248 area->priv->x_offset - allocation.x,
1249 area->priv->y_offset - allocation.y);
1250 }
1251
1252 void
foo_scroll_area_invalidate_region(FooScrollArea * area,cairo_region_t * region)1253 foo_scroll_area_invalidate_region (FooScrollArea *area,
1254 cairo_region_t *region)
1255 {
1256 GtkWidget *widget;
1257
1258 g_return_if_fail (FOO_IS_SCROLL_AREA (area));
1259
1260 widget = GTK_WIDGET (area);
1261
1262 cairo_region_union (area->priv->update_region, region);
1263
1264 if (gtk_widget_get_realized (widget))
1265 {
1266 canvas_to_window (area, region);
1267
1268 gdk_window_invalidate_region (gtk_widget_get_window (widget),
1269 region, TRUE);
1270
1271 window_to_canvas (area, region);
1272 }
1273 }
1274
1275 void
foo_scroll_area_invalidate_rect(FooScrollArea * scroll_area,int x,int y,int width,int height)1276 foo_scroll_area_invalidate_rect (FooScrollArea *scroll_area,
1277 int x,
1278 int y,
1279 int width,
1280 int height)
1281 {
1282 GdkRectangle rect = { x, y, width, height };
1283 cairo_region_t *region;
1284
1285 g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
1286
1287 region = cairo_region_create_rectangle (&rect);
1288
1289 foo_scroll_area_invalidate_region (scroll_area, region);
1290
1291 cairo_region_destroy (region);
1292 }
1293
1294 void
foo_scroll_area_begin_grab(FooScrollArea * scroll_area,FooScrollAreaEventFunc func,gpointer input_data)1295 foo_scroll_area_begin_grab (FooScrollArea *scroll_area,
1296 FooScrollAreaEventFunc func,
1297 gpointer input_data)
1298 {
1299 g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
1300 g_return_if_fail (!scroll_area->priv->grabbed);
1301
1302 scroll_area->priv->grabbed = TRUE;
1303 scroll_area->priv->grab_func = func;
1304 scroll_area->priv->grab_data = input_data;
1305
1306 /* FIXME: we should probably take a server grab */
1307 /* Also, maybe there should be support for setting the grab cursor */
1308 }
1309
1310 void
foo_scroll_area_end_grab(FooScrollArea * scroll_area)1311 foo_scroll_area_end_grab (FooScrollArea *scroll_area)
1312 {
1313 g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
1314
1315 scroll_area->priv->grabbed = FALSE;
1316 scroll_area->priv->grab_func = NULL;
1317 scroll_area->priv->grab_data = NULL;
1318 }
1319
1320 gboolean
foo_scroll_area_is_grabbed(FooScrollArea * scroll_area)1321 foo_scroll_area_is_grabbed (FooScrollArea *scroll_area)
1322 {
1323 return scroll_area->priv->grabbed;
1324 }
1325
1326 void
foo_scroll_area_set_viewport_pos(FooScrollArea * scroll_area,int x,int y)1327 foo_scroll_area_set_viewport_pos (FooScrollArea *scroll_area,
1328 int x,
1329 int y)
1330 {
1331 g_object_freeze_notify (G_OBJECT (scroll_area->priv->hadj));
1332 g_object_freeze_notify (G_OBJECT (scroll_area->priv->vadj));
1333 gtk_adjustment_set_value (scroll_area->priv->hadj, x);
1334 gtk_adjustment_set_value (scroll_area->priv->vadj, y);
1335
1336 set_adjustment_values (scroll_area);
1337 g_object_thaw_notify (G_OBJECT (scroll_area->priv->hadj));
1338 g_object_thaw_notify (G_OBJECT (scroll_area->priv->vadj));
1339 }
1340
1341 static gboolean
rect_contains(const GdkRectangle * rect,int x,int y)1342 rect_contains (const GdkRectangle *rect, int x, int y)
1343 {
1344 return (x >= rect->x &&
1345 y >= rect->y &&
1346 x < rect->x + rect->width &&
1347 y < rect->y + rect->height);
1348 }
1349
1350 static void
stop_scrolling(FooScrollArea * area)1351 stop_scrolling (FooScrollArea *area)
1352 {
1353 if (area->priv->auto_scroll_info)
1354 {
1355 g_source_remove (area->priv->auto_scroll_info->timeout_id);
1356 g_timer_destroy (area->priv->auto_scroll_info->timer);
1357 g_free (area->priv->auto_scroll_info);
1358
1359 area->priv->auto_scroll_info = NULL;
1360 }
1361 }
1362
1363 static gboolean
scroll_idle(gpointer data)1364 scroll_idle (gpointer data)
1365 {
1366 GdkRectangle viewport, new_viewport;
1367 FooScrollArea *area = data;
1368 AutoScrollInfo *info = area->priv->auto_scroll_info;
1369 int new_x, new_y;
1370 double elapsed;
1371
1372 get_viewport (area, &viewport);
1373
1374 elapsed = g_timer_elapsed (info->timer, NULL);
1375
1376 info->res_x = elapsed * info->dx / 0.2;
1377 info->res_y = elapsed * info->dy / 0.2;
1378
1379 new_x = viewport.x + (int) info->res_x;
1380 new_y = viewport.y + (int) info->res_y;
1381
1382 foo_scroll_area_set_viewport_pos (area, new_x, new_y);
1383
1384 get_viewport (area, &new_viewport);
1385
1386 if (viewport.x == new_viewport.x &&
1387 viewport.y == new_viewport.y &&
1388 (info->res_x > 1.0 ||
1389 info->res_y > 1.0 ||
1390 info->res_x < -1.0 ||
1391 info->res_y < -1.0))
1392 {
1393 stop_scrolling (area);
1394
1395 /* stop scrolling if it didn't have an effect */
1396 return FALSE;
1397 }
1398
1399 return TRUE;
1400 }
1401
1402 static void
ensure_scrolling(FooScrollArea * area,int dx,int dy)1403 ensure_scrolling (FooScrollArea *area,
1404 int dx,
1405 int dy)
1406 {
1407 if (!area->priv->auto_scroll_info)
1408 {
1409 area->priv->auto_scroll_info = g_new0 (AutoScrollInfo, 1);
1410 area->priv->auto_scroll_info->timeout_id =
1411 g_idle_add (scroll_idle, area);
1412 area->priv->auto_scroll_info->timer = g_timer_new ();
1413 }
1414
1415 area->priv->auto_scroll_info->dx = dx;
1416 area->priv->auto_scroll_info->dy = dy;
1417 }
1418
1419 void
foo_scroll_area_auto_scroll(FooScrollArea * scroll_area,FooScrollAreaEvent * event)1420 foo_scroll_area_auto_scroll (FooScrollArea *scroll_area,
1421 FooScrollAreaEvent *event)
1422 {
1423 GdkRectangle viewport;
1424
1425 get_viewport (scroll_area, &viewport);
1426
1427 if (rect_contains (&viewport, event->x, event->y))
1428 {
1429 stop_scrolling (scroll_area);
1430 }
1431 else
1432 {
1433 int dx, dy;
1434
1435 dx = dy = 0;
1436
1437 if (event->y < viewport.y)
1438 {
1439 dy = event->y - viewport.y;
1440 dy = MIN (dy + 2, 0);
1441 }
1442 else if (event->y >= viewport.y + viewport.height)
1443 {
1444 dy = event->y - (viewport.y + viewport.height - 1);
1445 dy = MAX (dy - 2, 0);
1446 }
1447
1448 if (event->x < viewport.x)
1449 {
1450 dx = event->x - viewport.x;
1451 dx = MIN (dx + 2, 0);
1452 }
1453 else if (event->x >= viewport.x + viewport.width)
1454 {
1455 dx = event->x - (viewport.x + viewport.width - 1);
1456 dx = MAX (dx - 2, 0);
1457 }
1458
1459 ensure_scrolling (scroll_area, dx, dy);
1460 }
1461 }
1462
1463 void
foo_scroll_area_begin_auto_scroll(FooScrollArea * scroll_area)1464 foo_scroll_area_begin_auto_scroll (FooScrollArea *scroll_area)
1465 {
1466 /* noop for now */
1467 }
1468
1469 void
foo_scroll_area_end_auto_scroll(FooScrollArea * scroll_area)1470 foo_scroll_area_end_auto_scroll (FooScrollArea *scroll_area)
1471 {
1472 stop_scrolling (scroll_area);
1473 }
1474