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