1
/* Copyright 2006, 2007, 2008, Soren Sandmann <sandmann@daimi.au.dk>
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.
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.
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., 59 Temple Place - Suite 330,
16
* Boston, MA 02111-1307, USA.
19
#include <gdk/gdkprivate.h> /* For GDK_PARENT_RELATIVE_BG */
20
#include "scrollarea.h"
21
#include "foo-marshal.h"
23
G_DEFINE_TYPE (FooScrollArea, foo_scroll_area, GTK_TYPE_CONTAINER);
25
static GtkWidgetClass *parent_class;
27
typedef struct BackingStore BackingStore;
29
typedef void (* ExposeFunc) (cairo_t *cr, GdkRegion *region, gpointer data);
32
static void backing_store_draw (BackingStore *store,
37
static void backing_store_scroll (BackingStore *store,
39
static void backing_store_invalidate_rect (BackingStore *store,
41
static void backing_store_invalidate_region (BackingStore *store,
43
static void backing_store_invalidate_all (BackingStore *store);
44
static BackingStore *backing_store_new (GdkWindow *window,
45
int width, int height);
46
static void backing_store_resize (BackingStore *store,
47
int width, int height);
48
static void backing_store_process_updates (BackingStore *store,
51
static void backing_store_free (BackingStore *store);
54
typedef struct InputPath InputPath;
55
typedef struct InputRegion InputRegion;
56
typedef struct AutoScrollInfo AutoScrollInfo;
61
cairo_fill_rule_t fill_rule;
63
cairo_path_t *path; /* In canvas coordinates */
65
FooScrollAreaEventFunc func;
71
/* InputRegions are mutually disjoint */
74
GdkRegion *region; /* the boundary of this area in canvas coordinates */
90
struct FooScrollAreaPrivate
92
GdkWindow *input_window;
105
GPtrArray *input_regions;
107
AutoScrollInfo *auto_scroll_info;
109
/* During expose, this region is set to the region
110
* being exposed. At other times, it is NULL
112
* It is used for clipping of input areas
114
GdkRegion *expose_region;
115
InputRegion *current_input;
118
FooScrollAreaEventFunc grab_func;
122
GdkRegion *update_region; /* In canvas coordinates */
133
static guint signals [LAST_SIGNAL] = { 0 };
135
static void foo_scroll_area_size_request (GtkWidget *widget,
136
GtkRequisition *requisition);
137
static gboolean foo_scroll_area_expose (GtkWidget *widget,
138
GdkEventExpose *expose);
139
static void foo_scroll_area_size_allocate (GtkWidget *widget,
140
GtkAllocation *allocation);
141
static void foo_scroll_area_set_scroll_adjustments (FooScrollArea *scroll_area,
142
GtkAdjustment *hadjustment,
143
GtkAdjustment *vadjustment);
144
static void foo_scroll_area_realize (GtkWidget *widget);
145
static void foo_scroll_area_unrealize (GtkWidget *widget);
146
static void foo_scroll_area_map (GtkWidget *widget);
147
static void foo_scroll_area_unmap (GtkWidget *widget);
148
static gboolean foo_scroll_area_button_press (GtkWidget *widget,
149
GdkEventButton *event);
150
static gboolean foo_scroll_area_button_release (GtkWidget *widget,
151
GdkEventButton *event);
152
static gboolean foo_scroll_area_motion (GtkWidget *widget,
153
GdkEventMotion *event);
156
foo_scroll_area_map (GtkWidget *widget)
158
FooScrollArea *area = FOO_SCROLL_AREA (widget);
160
GTK_WIDGET_CLASS (parent_class)->map (widget);
162
if (area->priv->input_window)
163
gdk_window_show (area->priv->input_window);
167
foo_scroll_area_unmap (GtkWidget *widget)
169
FooScrollArea *area = FOO_SCROLL_AREA (widget);
171
if (area->priv->input_window)
172
gdk_window_hide (area->priv->input_window);
174
GTK_WIDGET_CLASS (parent_class)->unmap (widget);
178
foo_scroll_area_finalize (GObject *object)
180
FooScrollArea *scroll_area = FOO_SCROLL_AREA (object);
182
g_object_unref (scroll_area->priv->hadj);
183
g_object_unref (scroll_area->priv->vadj);
185
g_ptr_array_free (scroll_area->priv->input_regions, TRUE);
187
g_free (scroll_area->priv);
189
G_OBJECT_CLASS (foo_scroll_area_parent_class)->finalize (object);
193
foo_scroll_area_class_init (FooScrollAreaClass *class)
195
GObjectClass *object_class = G_OBJECT_CLASS (class);
196
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
198
object_class->finalize = foo_scroll_area_finalize;
199
widget_class->size_request = foo_scroll_area_size_request;
200
widget_class->expose_event = foo_scroll_area_expose;
201
widget_class->size_allocate = foo_scroll_area_size_allocate;
202
widget_class->realize = foo_scroll_area_realize;
203
widget_class->unrealize = foo_scroll_area_unrealize;
204
widget_class->button_press_event = foo_scroll_area_button_press;
205
widget_class->button_release_event = foo_scroll_area_button_release;
206
widget_class->motion_notify_event = foo_scroll_area_motion;
207
widget_class->map = foo_scroll_area_map;
208
widget_class->unmap = foo_scroll_area_unmap;
210
class->set_scroll_adjustments = foo_scroll_area_set_scroll_adjustments;
212
parent_class = g_type_class_peek_parent (class);
214
signals[VIEWPORT_CHANGED] =
215
g_signal_new ("viewport_changed",
216
G_OBJECT_CLASS_TYPE (object_class),
217
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
218
G_STRUCT_OFFSET (FooScrollAreaClass,
221
foo_marshal_VOID__BOXED_BOXED,
227
g_signal_new ("paint",
228
G_OBJECT_CLASS_TYPE (object_class),
229
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
230
G_STRUCT_OFFSET (FooScrollAreaClass,
233
foo_marshal_VOID__POINTER_BOXED_POINTER,
239
widget_class->set_scroll_adjustments_signal =
240
g_signal_new ("set_scroll_adjustments",
241
G_OBJECT_CLASS_TYPE (object_class),
242
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
243
G_STRUCT_OFFSET (FooScrollAreaClass,
244
set_scroll_adjustments),
246
foo_marshal_VOID__OBJECT_OBJECT,
249
GTK_TYPE_ADJUSTMENT);
252
static GtkAdjustment *
253
new_adjustment (void)
255
return GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
259
foo_scroll_area_init (FooScrollArea *scroll_area)
263
widget = GTK_WIDGET (scroll_area);
265
gtk_widget_set_has_window (widget, FALSE);
266
gtk_widget_set_redraw_on_allocate (widget, FALSE);
268
scroll_area->priv = g_new0 (FooScrollAreaPrivate, 1);
269
scroll_area->priv->width = 0;
270
scroll_area->priv->height = 0;
271
scroll_area->priv->hadj = g_object_ref_sink (new_adjustment());
272
scroll_area->priv->vadj = g_object_ref_sink (new_adjustment());
273
scroll_area->priv->x_offset = 0.0;
274
scroll_area->priv->y_offset = 0.0;
275
scroll_area->priv->min_width = -1;
276
scroll_area->priv->min_height = -1;
277
scroll_area->priv->auto_scroll_info = NULL;
278
scroll_area->priv->input_regions = g_ptr_array_new ();
279
scroll_area->priv->pixmap = NULL;
280
scroll_area->priv->update_region = gdk_region_new ();
282
gtk_widget_set_double_buffered (widget, FALSE);
286
translate_cairo_device (cairo_t *cr,
290
cairo_surface_t *surface = cairo_get_target (cr);
294
cairo_surface_get_device_offset (surface, &dev_x, &dev_y);
297
cairo_surface_set_device_offset (surface, dev_x, dev_y);
302
print_region (const char *header, GdkRegion *region)
308
g_print ("%s\n", header);
310
gdk_region_get_rectangles (region, &rects, &n_rects);
311
for (i = 0; i < n_rects; ++i)
313
GdkRectangle *rect = &(rects[i]);
314
g_print (" %d %d %d %d\n",
315
rect->x, rect->y, rect->width, rect->height);
320
typedef void (* PathForeachFunc) (double *x,
325
path_foreach_point (cairo_path_t *path,
326
PathForeachFunc func,
331
for (i = 0; i < path->num_data; i += path->data[i].header.length)
333
cairo_path_data_t *data = &(path->data[i]);
335
switch (data->header.type)
337
case CAIRO_PATH_MOVE_TO:
338
case CAIRO_PATH_LINE_TO:
339
func (&(data[1].point.x), &(data[1].point.y), user_data);
342
case CAIRO_PATH_CURVE_TO:
343
func (&(data[1].point.x), &(data[1].point.y), user_data);
344
func (&(data[2].point.x), &(data[2].point.y), user_data);
345
func (&(data[3].point.x), &(data[3].point.y), user_data);
348
case CAIRO_PATH_CLOSE_PATH:
356
double x1, y1, x2, y2;
361
update_box (double *x, double *y, gpointer data)
381
path_compute_extents (cairo_path_t *path,
386
Box box = { G_MAXDOUBLE, G_MAXDOUBLE, G_MINDOUBLE, G_MINDOUBLE };
388
path_foreach_point (path, update_box, &box);
392
rect->width = box.x2 - box.x1;
393
rect->height = box.y2 - box.y1;
399
input_path_free_list (InputPath *paths)
404
input_path_free_list (paths->next);
405
cairo_path_destroy (paths->path);
410
input_region_free (InputRegion *region)
412
input_path_free_list (region->paths);
413
gdk_region_destroy (region->region);
419
get_viewport (FooScrollArea *scroll_area,
420
GdkRectangle *viewport)
422
GtkAllocation allocation;
423
GtkWidget *widget = GTK_WIDGET (scroll_area);
425
gtk_widget_get_allocation (widget, &allocation);
427
viewport->x = scroll_area->priv->x_offset;
428
viewport->y = scroll_area->priv->y_offset;
429
viewport->width = allocation.width;
430
viewport->height = allocation.height;
434
allocation_to_canvas (FooScrollArea *area,
438
*x += area->priv->x_offset;
439
*y += area->priv->y_offset;
443
clear_exposed_input_region (FooScrollArea *area,
444
GdkRegion *exposed) /* in canvas coordinates */
448
GdkRectangle allocation;
450
gtk_widget_get_allocation (GTK_WIDGET (area), &allocation);
453
allocation_to_canvas (area, &allocation.x, &allocation.y);
454
viewport = gdk_region_rectangle (&allocation);
455
gdk_region_subtract (viewport, exposed);
457
for (i = 0; i < area->priv->input_regions->len; ++i)
459
InputRegion *region = area->priv->input_regions->pdata[i];
461
gdk_region_intersect (region->region, viewport);
463
if (gdk_region_empty (region->region))
465
input_region_free (region);
466
g_ptr_array_remove_index_fast (area->priv->input_regions, i--);
470
gdk_region_destroy (viewport);
473
path = region->paths;
478
path_compute_extents (path->path, &rect);
480
if (gdk_region_rect_in (area->priv->expose_region, &rect) == GDK_OVERLAP_RECTANGLE_IN)
481
g_print ("we would have deleted it\n");
484
g_print ("nope (%d %d %d %d)\n", );
490
/* FIXME: we should also delete paths (and path segments)
491
* completely contained in the expose_region
498
setup_background_cr (GdkWindow *window,
503
GdkWindowObject *private = (GdkWindowObject *)window;
505
if (private->bg_pixmap == GDK_PARENT_RELATIVE_BG && private->parent)
507
x_offset += private->x;
508
y_offset += private->y;
510
setup_background_cr (GDK_WINDOW (private->parent), cr, x_offset, y_offset);
512
else if (private->bg_pixmap &&
513
private->bg_pixmap != GDK_PARENT_RELATIVE_BG &&
514
private->bg_pixmap != GDK_NO_BG)
516
gdk_cairo_set_source_pixmap (cr, private->bg_pixmap, -x_offset, -y_offset);
520
gdk_cairo_set_source_color (cr, &private->bg_color);
525
initialize_background (GtkWidget *widget,
528
setup_background_cr (gtk_widget_get_window (widget), cr, 0, 0);
534
clip_to_region (cairo_t *cr, GdkRegion *region)
539
gdk_region_get_rectangles (region, &rects, &n_rects);
544
GdkRectangle *rect = &(rects[n_rects]);
546
cairo_rectangle (cr, rect->x, rect->y, rect->width, rect->height);
554
simple_draw_drawable (GdkDrawable *dst,
563
GdkGC *gc = gdk_gc_new (dst);
565
gdk_draw_drawable (dst, gc, src, src_x, src_y, dst_x, dst_y, width, height);
571
foo_scroll_area_expose (GtkWidget *widget,
572
GdkEventExpose *expose)
574
FooScrollArea *scroll_area = FOO_SCROLL_AREA (widget);
576
GdkRectangle extents;
578
int x_offset, y_offset;
580
GtkAllocation widget_allocation;
581
GdkWindow *window = gtk_widget_get_window (widget);
583
/* I don't think expose can ever recurse for the same area */
584
g_assert (!scroll_area->priv->expose_region);
586
/* Note that this function can be called at a time
587
* where the adj->value is different from x_offset.
588
* Ie., the GtkScrolledWindow changed the adj->value
589
* without emitting the value_changed signal.
591
* Hence we must always use the value we got
592
* the last time the signal was emitted, ie.,
593
* priv->{x,y}_offset.
596
x_offset = scroll_area->priv->x_offset;
597
y_offset = scroll_area->priv->y_offset;
599
scroll_area->priv->expose_region = expose->region;
601
/* Setup input areas */
602
clear_exposed_input_region (scroll_area, scroll_area->priv->update_region);
604
scroll_area->priv->current_input = g_new0 (InputRegion, 1);
605
scroll_area->priv->current_input->region = gdk_region_copy (scroll_area->priv->update_region);
606
scroll_area->priv->current_input->paths = NULL;
607
g_ptr_array_add (scroll_area->priv->input_regions,
608
scroll_area->priv->current_input);
610
region = scroll_area->priv->update_region;
611
scroll_area->priv->update_region = gdk_region_new ();
613
/* Create cairo context */
614
cr = gdk_cairo_create (scroll_area->priv->pixmap);
615
translate_cairo_device (cr, -x_offset, -y_offset);
616
clip_to_region (cr, region);
617
initialize_background (widget, cr);
620
gdk_region_get_clipbox (region, &extents);
622
g_signal_emit (widget, signals[PAINT], 0, cr, &extents, region);
627
scroll_area->priv->expose_region = NULL;
628
scroll_area->priv->current_input = NULL;
630
/* Finally draw the backing pixmap */
631
gc = gdk_gc_new (window);
633
gdk_gc_set_clip_region (gc, expose->region);
635
gtk_widget_get_allocation (widget, &widget_allocation);
636
gdk_draw_drawable (window, gc, scroll_area->priv->pixmap,
637
0, 0, widget_allocation.x, widget_allocation.y,
638
widget_allocation.width, widget_allocation.height);
641
gdk_region_destroy (region);
647
foo_scroll_area_get_viewport (FooScrollArea *scroll_area,
648
GdkRectangle *viewport)
650
g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
655
get_viewport (scroll_area, viewport);
659
process_event (FooScrollArea *scroll_area,
660
FooScrollAreaEventType input_type,
665
emit_viewport_changed (FooScrollArea *scroll_area,
666
GdkRectangle *new_viewport,
667
GdkRectangle *old_viewport)
670
g_signal_emit (scroll_area, signals[VIEWPORT_CHANGED], 0,
671
new_viewport, old_viewport);
673
gdk_window_get_pointer (scroll_area->priv->input_window, &px, &py, NULL);
679
process_event (scroll_area, FOO_MOTION, px, py);
683
clamp_adjustment (GtkAdjustment *adj)
685
if (gtk_adjustment_get_upper (adj) >= gtk_adjustment_get_page_size (adj))
686
gtk_adjustment_set_value (adj, CLAMP (gtk_adjustment_get_value (adj), 0.0,
687
gtk_adjustment_get_upper (adj)
688
- gtk_adjustment_get_page_size (adj)));
690
gtk_adjustment_set_value (adj, 0.0);
692
gtk_adjustment_changed (adj);
696
set_adjustment_values (FooScrollArea *scroll_area)
698
GtkAllocation allocation;
700
GtkAdjustment *hadj = scroll_area->priv->hadj;
701
GtkAdjustment *vadj = scroll_area->priv->vadj;
704
gtk_widget_get_allocation (GTK_WIDGET (scroll_area), &allocation);
705
g_object_freeze_notify (G_OBJECT (hadj));
706
gtk_adjustment_set_page_size (hadj, allocation.width);
707
gtk_adjustment_set_step_increment (hadj, 0.1 * allocation.width);
708
gtk_adjustment_set_page_increment (hadj, 0.9 * allocation.width);
709
gtk_adjustment_set_lower (hadj, 0.0);
710
gtk_adjustment_set_upper (hadj, scroll_area->priv->width);
711
g_object_thaw_notify (G_OBJECT (hadj));
714
g_object_freeze_notify (G_OBJECT (vadj));
715
gtk_adjustment_set_page_size (vadj, allocation.height);
716
gtk_adjustment_set_step_increment (vadj, 0.1 * allocation.height);
717
gtk_adjustment_set_page_increment (vadj, 0.9 * allocation.height);
718
gtk_adjustment_set_lower (vadj, 0.0);
719
gtk_adjustment_set_upper (vadj, scroll_area->priv->height);
720
g_object_thaw_notify (G_OBJECT (vadj));
722
clamp_adjustment (hadj);
723
clamp_adjustment (vadj);
729
foo_scroll_area_realize (GtkWidget *widget)
731
FooScrollArea *area = FOO_SCROLL_AREA (widget);
732
GdkWindowAttr attributes;
733
GtkAllocation widget_allocation;
735
gint attributes_mask;
737
gtk_widget_get_allocation (widget, &widget_allocation);
738
gtk_widget_set_realized (widget, TRUE);
740
attributes.window_type = GDK_WINDOW_CHILD;
741
attributes.x = widget_allocation.x;
742
attributes.y = widget_allocation.y;
743
attributes.width = widget_allocation.width;
744
attributes.height = widget_allocation.height;
745
attributes.wclass = GDK_INPUT_ONLY;
746
attributes.event_mask = gtk_widget_get_events (widget);
747
attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
748
GDK_BUTTON_RELEASE_MASK |
749
GDK_BUTTON1_MOTION_MASK |
750
GDK_BUTTON2_MOTION_MASK |
751
GDK_BUTTON3_MOTION_MASK |
752
GDK_POINTER_MOTION_MASK |
753
GDK_ENTER_NOTIFY_MASK |
754
GDK_LEAVE_NOTIFY_MASK);
756
attributes_mask = GDK_WA_X | GDK_WA_Y;
758
window = gtk_widget_get_parent_window (widget);
759
gtk_widget_set_window (widget, window);
760
g_object_ref (window);
762
area->priv->input_window = gdk_window_new (window,
763
&attributes, attributes_mask);
764
area->priv->pixmap = gdk_pixmap_new (window,
765
widget_allocation.width,
766
widget_allocation.height,
768
gdk_window_set_user_data (area->priv->input_window, area);
770
gtk_widget_style_attach (widget);
774
foo_scroll_area_unrealize (GtkWidget *widget)
776
FooScrollArea *area = FOO_SCROLL_AREA (widget);
778
if (area->priv->input_window)
780
gdk_window_set_user_data (area->priv->input_window, NULL);
781
gdk_window_destroy (area->priv->input_window);
782
area->priv->input_window = NULL;
785
GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
789
create_new_pixmap (GtkWidget *widget,
792
GtkAllocation widget_allocation;
795
gtk_widget_get_allocation (widget, &widget_allocation);
796
new = gdk_pixmap_new (gtk_widget_get_window (widget),
797
widget_allocation.width,
798
widget_allocation.height,
801
/* Unfortunately we don't know in which direction we were resized,
802
* so we just assume we were dragged from the south-east corner.
804
* Although, maybe we could get the root coordinates of the input-window?
805
* That might just work, actually. We need to make sure metacity uses
806
* static gravity for the window before this will be useful.
808
simple_draw_drawable (new, old, 0, 0, 0, 0, -1, -1);
814
allocation_to_canvas_region (FooScrollArea *area,
817
gdk_region_offset (region, area->priv->x_offset, area->priv->y_offset);
822
foo_scroll_area_size_allocate (GtkWidget *widget,
823
GtkAllocation *allocation)
825
FooScrollArea *scroll_area = FOO_SCROLL_AREA (widget);
826
GdkRectangle new_viewport;
827
GdkRectangle old_viewport;
828
GdkRegion *old_allocation;
830
GtkAllocation widget_allocation;
832
get_viewport (scroll_area, &old_viewport);
834
gtk_widget_get_allocation (widget, &widget_allocation);
835
old_allocation = gdk_region_rectangle (&widget_allocation);
836
gdk_region_offset (old_allocation,
837
-widget_allocation.x, -widget_allocation.y);
838
invalid = gdk_region_rectangle (allocation);
839
gdk_region_offset (invalid, -allocation->x, -allocation->y);
840
gdk_region_xor (invalid, old_allocation);
841
allocation_to_canvas_region (scroll_area, invalid);
842
foo_scroll_area_invalidate_region (scroll_area, invalid);
843
gdk_region_destroy (old_allocation);
844
gdk_region_destroy (invalid);
846
gtk_widget_set_allocation (widget, allocation);
848
if (scroll_area->priv->input_window)
850
GdkPixmap *new_pixmap;
852
gdk_window_move_resize (scroll_area->priv->input_window,
853
allocation->x, allocation->y,
854
allocation->width, allocation->height);
856
new_pixmap = create_new_pixmap (widget, scroll_area->priv->pixmap);
858
g_object_unref (scroll_area->priv->pixmap);
860
scroll_area->priv->pixmap = new_pixmap;
863
get_viewport (scroll_area, &new_viewport);
865
emit_viewport_changed (scroll_area, &new_viewport, &old_viewport);
869
emit_input (FooScrollArea *scroll_area,
870
FooScrollAreaEventType type,
873
FooScrollAreaEventFunc func,
876
FooScrollAreaEvent event;
881
if (type != FOO_MOTION)
882
emit_input (scroll_area, FOO_MOTION, x, y, func, data);
885
x += scroll_area->priv->x_offset;
886
y += scroll_area->priv->y_offset;
893
func (scroll_area, &event, data);
898
print_path (const char *header,
903
g_print ("%s\n", header);
905
for (i=0; i < path->num_data; i += path->data[i].header.length)
907
cairo_path_data_t *data = &(path->data[i]);
909
switch (data->header.type)
911
case CAIRO_PATH_MOVE_TO:
912
g_print ("move to: %f, %f\n", data[1].point.x, data[1].point.y);
915
case CAIRO_PATH_LINE_TO:
916
g_print ("line to: %f, %f\n", data[1].point.x, data[1].point.y);
919
case CAIRO_PATH_CURVE_TO:
920
g_print ("curve to: %f, %f\n", data[1].point.x, data[1].point.y);
921
g_print (" %f, %f\n", data[1].point.x, data[1].point.y);
922
g_print (" %f, %f\n", data[1].point.x, data[1].point.y);
925
case CAIRO_PATH_CLOSE_PATH:
933
process_event (FooScrollArea *scroll_area,
934
FooScrollAreaEventType input_type,
938
GtkWidget *widget = GTK_WIDGET (scroll_area);
941
allocation_to_canvas (scroll_area, &x, &y);
943
if (scroll_area->priv->grabbed)
945
emit_input (scroll_area, input_type, x, y,
946
scroll_area->priv->grab_func,
947
scroll_area->priv->grab_data);
953
x += widget->allocation.x;
954
y += widget->allocation.y;
958
g_print ("number of input regions: %d\n", scroll_area->priv->input_regions->len);
961
for (i = 0; i < scroll_area->priv->input_regions->len; ++i)
963
InputRegion *region = scroll_area->priv->input_regions->pdata[i];
967
print_region ("region:", region->region);
970
if (gdk_region_point_in (region->region, x, y))
974
path = region->paths;
980
cr = gdk_cairo_create (gtk_widget_get_window (widget));
981
cairo_set_fill_rule (cr, path->fill_rule);
982
cairo_set_line_width (cr, path->line_width);
983
cairo_append_path (cr, path->path);
986
inside = cairo_in_stroke (cr, x, y);
988
inside = cairo_in_fill (cr, x, y);
994
emit_input (scroll_area, input_type,
1004
/* Since the regions are all disjoint, no other region
1005
* can match. Of course we could be clever and try and
1006
* sort the regions, but so far I have been unable to
1007
* make this loop show up on a profile.
1015
process_gdk_event (FooScrollArea *scroll_area,
1020
FooScrollAreaEventType input_type;
1022
if (event->type == GDK_BUTTON_PRESS)
1023
input_type = FOO_BUTTON_PRESS;
1024
else if (event->type == GDK_BUTTON_RELEASE)
1025
input_type = FOO_BUTTON_RELEASE;
1026
else if (event->type == GDK_MOTION_NOTIFY)
1027
input_type = FOO_MOTION;
1031
process_event (scroll_area, input_type, x, y);
1035
foo_scroll_area_button_press (GtkWidget *widget,
1036
GdkEventButton *event)
1038
FooScrollArea *area = FOO_SCROLL_AREA (widget);
1040
process_gdk_event (area, event->x, event->y, (GdkEvent *)event);
1046
foo_scroll_area_button_release (GtkWidget *widget,
1047
GdkEventButton *event)
1049
FooScrollArea *area = FOO_SCROLL_AREA (widget);
1051
process_gdk_event (area, event->x, event->y, (GdkEvent *)event);
1057
foo_scroll_area_motion (GtkWidget *widget,
1058
GdkEventMotion *event)
1060
FooScrollArea *area = FOO_SCROLL_AREA (widget);
1062
process_gdk_event (area, event->x, event->y, (GdkEvent *)event);
1067
foo_scroll_area_set_size_fixed_y (FooScrollArea *scroll_area,
1073
scroll_area->priv->width = width;
1074
scroll_area->priv->height = height;
1077
g_print ("diff: %d\n", new_y - old_y);
1079
g_object_thaw_notify (G_OBJECT (scroll_area->priv->vadj));
1080
gtk_adjustment_set_value (scroll_area->priv->vadj, new_y);
1082
set_adjustment_values (scroll_area);
1083
g_object_thaw_notify (G_OBJECT (scroll_area->priv->vadj));
1087
foo_scroll_area_set_size (FooScrollArea *scroll_area,
1091
g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
1093
/* FIXME: Default scroll algorithm should probably be to
1094
* keep the same *area* outside the screen as before.
1096
* For wrapper widgets that will do something roughly
1097
* right. For widgets that don't change size, it
1098
* will do the right thing. Except for idle-layouting
1101
* Maybe there should be some generic support for those
1102
* widgets. Can that even be done?
1104
* Should we have a version of this function using
1108
scroll_area->priv->width = width;
1109
scroll_area->priv->height = height;
1111
set_adjustment_values (scroll_area);
1115
foo_scroll_area_size_request (GtkWidget *widget,
1116
GtkRequisition *requisition)
1118
FooScrollArea *scroll_area = FOO_SCROLL_AREA (widget);
1120
requisition->width = scroll_area->priv->min_width;
1121
requisition->height = scroll_area->priv->min_height;
1124
g_print ("request %d %d\n", requisition->width, requisition->height);
1130
translate_point (double *x, double *y, gpointer data)
1132
int *translation = data;
1134
*x += translation[0];
1135
*y += translation[1];
1141
path_translate (cairo_path_t *path,
1145
int translation[2] = {dx, dy};
1147
path_foreach_point (path, translate_point, translation);
1152
translate_input_regions (FooScrollArea *scroll_area,
1159
for (i = 0; i < scroll_area->priv->input_regions->len; ++i)
1161
InputRegion *region = scroll_area->priv->input_regions->pdata[i];
1164
gdk_region_offset (region->region, dx, dy);
1166
path = region->paths;
1167
while (path != NULL)
1169
path_translate (path->path, dx, dy);
1178
paint_region (FooScrollArea *area, GdkRegion *region)
1181
GdkRectangle *rects;
1182
region = gdk_region_copy (region);
1184
gdk_region_get_rectangles (region, &rects, &n_rects);
1186
gdk_region_offset (region,
1187
GTK_WIDGET (area)->allocation.x,
1188
GTK_WIDGET (area)->allocation.y);
1190
GdkGC *gc = gdk_gc_new (GTK_WIDGET (area)->window);
1191
gdk_gc_set_clip_region (gc, region);
1192
gdk_draw_rectangle (GTK_WIDGET (area)->window, gc, TRUE, 0, 0, -1, -1);
1193
g_object_unref (gc);
1199
foo_scroll_area_scroll (FooScrollArea *area,
1203
GdkRectangle allocation;
1204
GdkRectangle src_area;
1205
GdkRectangle move_area;
1206
GdkRegion *invalid_region;
1208
gtk_widget_get_allocation (GTK_WIDGET (area), &allocation);
1212
src_area = allocation;
1216
invalid_region = gdk_region_rectangle (&allocation);
1218
if (gdk_rectangle_intersect (&allocation, &src_area, &move_area))
1220
GdkRegion *move_region;
1223
g_print ("scrolling %d %d %d %d (%d %d)\n",
1224
move_area.x, move_area.y,
1225
move_area.width, move_area.height,
1229
simple_draw_drawable (area->priv->pixmap, area->priv->pixmap,
1230
move_area.x, move_area.y,
1231
move_area.x + dx, move_area.y + dy,
1232
move_area.width, move_area.height);
1233
gtk_widget_queue_draw (GTK_WIDGET (area));
1235
move_region = gdk_region_rectangle (&move_area);
1236
gdk_region_offset (move_region, dx, dy);
1237
gdk_region_subtract (invalid_region, move_region);
1238
gdk_region_destroy (move_region);
1242
paint_region (area, invalid_region);
1245
allocation_to_canvas_region (area, invalid_region);
1247
foo_scroll_area_invalidate_region (area, invalid_region);
1249
gdk_region_destroy (invalid_region);
1253
foo_scrollbar_adjustment_changed (GtkAdjustment *adj,
1254
FooScrollArea *scroll_area)
1256
GtkWidget *widget = GTK_WIDGET (scroll_area);
1259
GdkRectangle old_viewport, new_viewport;
1261
get_viewport (scroll_area, &old_viewport);
1263
if (adj == scroll_area->priv->hadj)
1265
/* FIXME: do we treat the offset as int or double, and,
1266
* if int, how do we round?
1268
dx = (int)gtk_adjustment_get_value (adj) - scroll_area->priv->x_offset;
1269
scroll_area->priv->x_offset = gtk_adjustment_get_value (adj);
1271
else if (adj == scroll_area->priv->vadj)
1273
dy = (int)gtk_adjustment_get_value (adj) - scroll_area->priv->y_offset;
1274
scroll_area->priv->y_offset = gtk_adjustment_get_value (adj);
1278
g_assert_not_reached ();
1281
if (gtk_widget_get_realized (widget))
1283
foo_scroll_area_scroll (scroll_area, -dx, -dy);
1286
window_scroll_area (widget->window, &widget->allocation, -dx, -dy);
1288
translate_input_regions (scroll_area, -dx, -dy);
1291
gdk_window_process_updates (widget->window, TRUE);
1295
get_viewport (scroll_area, &new_viewport);
1297
emit_viewport_changed (scroll_area, &new_viewport, &old_viewport);
1301
set_one_adjustment (FooScrollArea *scroll_area,
1302
GtkAdjustment *adjustment,
1303
GtkAdjustment **location)
1305
g_return_if_fail (location != NULL);
1307
if (adjustment == *location)
1311
adjustment = new_adjustment ();
1313
g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
1317
g_signal_handlers_disconnect_by_func (
1318
*location, foo_scrollbar_adjustment_changed, scroll_area);
1320
g_object_unref (*location);
1323
*location = adjustment;
1325
g_object_ref_sink (*location);
1327
g_signal_connect (*location, "value_changed",
1328
G_CALLBACK (foo_scrollbar_adjustment_changed),
1333
foo_scroll_area_set_scroll_adjustments (FooScrollArea *scroll_area,
1334
GtkAdjustment *hadjustment,
1335
GtkAdjustment *vadjustment)
1337
set_one_adjustment (scroll_area, hadjustment, &scroll_area->priv->hadj);
1338
set_one_adjustment (scroll_area, vadjustment, &scroll_area->priv->vadj);
1340
set_adjustment_values (scroll_area);
1344
foo_scroll_area_new (void)
1346
return g_object_new (FOO_TYPE_SCROLL_AREA, NULL);
1350
foo_scroll_area_set_min_size (FooScrollArea *scroll_area,
1354
scroll_area->priv->min_width = min_width;
1355
scroll_area->priv->min_height = min_height;
1357
/* FIXME: think through invalidation.
1359
* Goals: - no repainting everything on size_allocate(),
1360
* - make sure input boxes are invalidated when
1363
gtk_widget_queue_resize (GTK_WIDGET (scroll_area));
1368
warn_about_adding_input_outside_expose (const char *func)
1370
static gboolean warned = FALSE;
1374
g_warning ("%s() can only be called "
1375
"from the paint handler for the FooScrollArea\n", func);
1383
user_to_device (double *x, double *y,
1388
cairo_user_to_device (cr, x, y);
1392
make_path (FooScrollArea *area,
1395
FooScrollAreaEventFunc func,
1398
InputPath *path = g_new0 (InputPath, 1);
1400
path->is_stroke = is_stroke;
1401
path->fill_rule = cairo_get_fill_rule (cr);
1402
path->line_width = cairo_get_line_width (cr);
1403
path->path = cairo_copy_path (cr);
1404
path_foreach_point (path->path, user_to_device, cr);
1407
path->next = area->priv->current_input->paths;
1408
area->priv->current_input->paths = path;
1412
/* FIXME: we probably really want a
1414
* foo_scroll_area_add_input_from_fill (area, cr, ...);
1416
* foo_scroll_area_add_input_from_stroke (area, cr, ...);
1420
foo_scroll_area_add_input_from_fill (FooScrollArea *scroll_area,
1422
FooScrollAreaEventFunc func,
1425
g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
1426
g_return_if_fail (cr != NULL);
1427
g_return_if_fail (scroll_area->priv->current_input);
1429
make_path (scroll_area, cr, FALSE, func, data);
1433
foo_scroll_area_add_input_from_stroke (FooScrollArea *scroll_area,
1435
FooScrollAreaEventFunc func,
1438
g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
1439
g_return_if_fail (cr != NULL);
1440
g_return_if_fail (scroll_area->priv->current_input);
1442
make_path (scroll_area, cr, TRUE, func, data);
1446
foo_scroll_area_invalidate (FooScrollArea *scroll_area)
1448
GtkAllocation allocation;
1449
GtkWidget *widget = GTK_WIDGET (scroll_area);
1451
gtk_widget_get_allocation (widget, &allocation);
1452
foo_scroll_area_invalidate_rect (scroll_area,
1453
scroll_area->priv->x_offset, scroll_area->priv->y_offset,
1459
canvas_to_window (FooScrollArea *area,
1462
GtkAllocation allocation;
1463
GtkWidget *widget = GTK_WIDGET (area);
1465
gtk_widget_get_allocation (widget, &allocation);
1466
gdk_region_offset (region,
1467
-area->priv->x_offset + allocation.x,
1468
-area->priv->y_offset + allocation.y);
1472
window_to_canvas (FooScrollArea *area,
1475
GtkAllocation allocation;
1476
GtkWidget *widget = GTK_WIDGET (area);
1478
gtk_widget_get_allocation (widget, &allocation);
1479
gdk_region_offset (region,
1480
area->priv->x_offset - allocation.x,
1481
area->priv->y_offset - allocation.y);
1485
foo_scroll_area_invalidate_region (FooScrollArea *area,
1490
g_return_if_fail (FOO_IS_SCROLL_AREA (area));
1492
widget = GTK_WIDGET (area);
1494
gdk_region_union (area->priv->update_region, region);
1496
if (gtk_widget_get_realized (widget))
1498
canvas_to_window (area, region);
1500
gdk_window_invalidate_region (gtk_widget_get_window (widget),
1503
window_to_canvas (area, region);
1508
foo_scroll_area_invalidate_rect (FooScrollArea *scroll_area,
1514
GdkRectangle rect = { x, y, width, height };
1517
g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
1519
region = gdk_region_rectangle (&rect);
1521
foo_scroll_area_invalidate_region (scroll_area, region);
1523
gdk_region_destroy (region);
1527
foo_scroll_area_begin_grab (FooScrollArea *scroll_area,
1528
FooScrollAreaEventFunc func,
1529
gpointer input_data)
1531
g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
1532
g_return_if_fail (!scroll_area->priv->grabbed);
1534
scroll_area->priv->grabbed = TRUE;
1535
scroll_area->priv->grab_func = func;
1536
scroll_area->priv->grab_data = input_data;
1538
/* FIXME: we should probably take a server grab */
1539
/* Also, maybe there should be support for setting the grab cursor */
1543
foo_scroll_area_end_grab (FooScrollArea *scroll_area)
1545
g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
1547
scroll_area->priv->grabbed = FALSE;
1548
scroll_area->priv->grab_func = NULL;
1549
scroll_area->priv->grab_data = NULL;
1553
foo_scroll_area_is_grabbed (FooScrollArea *scroll_area)
1555
return scroll_area->priv->grabbed;
1559
foo_scroll_area_set_viewport_pos (FooScrollArea *scroll_area,
1563
g_object_freeze_notify (G_OBJECT (scroll_area->priv->hadj));
1564
g_object_freeze_notify (G_OBJECT (scroll_area->priv->vadj));
1565
gtk_adjustment_set_value (scroll_area->priv->hadj, x);
1566
gtk_adjustment_set_value (scroll_area->priv->vadj, y);
1568
set_adjustment_values (scroll_area);
1569
g_object_thaw_notify (G_OBJECT (scroll_area->priv->hadj));
1570
g_object_thaw_notify (G_OBJECT (scroll_area->priv->vadj));
1574
rect_contains (const GdkRectangle *rect, int x, int y)
1576
return (x >= rect->x &&
1578
x < rect->x + rect->width &&
1579
y < rect->y + rect->height);
1583
stop_scrolling (FooScrollArea *area)
1586
g_print ("stop scrolling\n");
1588
if (area->priv->auto_scroll_info)
1590
g_source_remove (area->priv->auto_scroll_info->timeout_id);
1591
g_timer_destroy (area->priv->auto_scroll_info->timer);
1592
g_free (area->priv->auto_scroll_info);
1594
area->priv->auto_scroll_info = NULL;
1599
scroll_idle (gpointer data)
1601
GdkRectangle viewport, new_viewport;
1602
FooScrollArea *area = data;
1603
AutoScrollInfo *info = area->priv->auto_scroll_info;
1610
get_viewport (area, &viewport);
1613
g_print ("old info: %d %d\n", info->dx, info->dy);
1615
g_print ("timeout (%d %d)\n", dx, dy);
1619
viewport.x += info->dx;
1620
viewport.y += info->dy;
1624
g_print ("new info %d %d\n", info->dx, info->dy);
1627
elapsed = g_timer_elapsed (info->timer, NULL);
1629
info->res_x = elapsed * info->dx / 0.2;
1630
info->res_y = elapsed * info->dy / 0.2;
1633
g_print ("%f %f\n", info->res_x, info->res_y);
1636
new_x = viewport.x + info->res_x;
1637
new_y = viewport.y + info->res_y;
1640
g_print ("%f\n", elapsed * (info->dx / 0.2));
1644
g_print ("new_x, new_y\n: %d %d\n", new_x, new_y);
1647
foo_scroll_area_set_viewport_pos (area, new_x, new_y);
1649
viewport.x + info->dx,
1650
viewport.y + info->dy);
1653
get_viewport (area, &new_viewport);
1655
if (viewport.x == new_viewport.x &&
1656
viewport.y == new_viewport.y &&
1657
(info->res_x > 1.0 ||
1658
info->res_y > 1.0 ||
1659
info->res_x < -1.0 ||
1660
info->res_y < -1.0))
1662
stop_scrolling (area);
1664
/* stop scrolling if it didn't have an effect */
1672
ensure_scrolling (FooScrollArea *area,
1676
if (!area->priv->auto_scroll_info)
1679
g_print ("start scrolling\n");
1681
area->priv->auto_scroll_info = g_new0 (AutoScrollInfo, 1);
1682
area->priv->auto_scroll_info->timeout_id =
1683
g_idle_add (scroll_idle, area);
1684
area->priv->auto_scroll_info->timer = g_timer_new ();
1688
g_print ("setting scrolling to %d %d\n", dx, dy);
1692
g_print ("dx, dy: %d %d\n", dx, dy);
1695
area->priv->auto_scroll_info->dx = dx;
1696
area->priv->auto_scroll_info->dy = dy;
1700
foo_scroll_area_auto_scroll (FooScrollArea *scroll_area,
1701
FooScrollAreaEvent *event)
1703
GdkRectangle viewport;
1705
get_viewport (scroll_area, &viewport);
1707
if (rect_contains (&viewport, event->x, event->y))
1709
stop_scrolling (scroll_area);
1717
if (event->y < viewport.y)
1719
dy = event->y - viewport.y;
1720
dy = MIN (dy + 2, 0);
1722
else if (event->y >= viewport.y + viewport.height)
1724
dy = event->y - (viewport.y + viewport.height - 1);
1725
dy = MAX (dy - 2, 0);
1728
if (event->x < viewport.x)
1730
dx = event->x - viewport.x;
1731
dx = MIN (dx + 2, 0);
1733
else if (event->x >= viewport.x + viewport.width)
1735
dx = event->x - (viewport.x + viewport.width - 1);
1736
dx = MAX (dx - 2, 0);
1740
g_print ("dx, dy: %d %d\n", dx, dy);
1743
ensure_scrolling (scroll_area, dx, dy);
1748
foo_scroll_area_begin_auto_scroll (FooScrollArea *scroll_area)
1754
foo_scroll_area_end_auto_scroll (FooScrollArea *scroll_area)
1756
stop_scrolling (scroll_area);
1768
GdkRegion *update_region;
1773
static BackingStore *
1774
backing_store_new (GdkWindow *window,
1775
int width, int height)
1777
BackingStore *store = g_new0 (BackingStore, 1);
1778
GdkRectangle rect = { 0, 0, width, height };
1780
store->pixmap = gdk_pixmap_new (window, width, height, -1);
1781
store->update_region = gdk_region_rectangle (&rect);
1782
store->width = width;
1783
store->height = height;
1789
backing_store_free (BackingStore *store)
1791
g_object_unref (store->pixmap);
1792
gdk_region_destroy (store->update_region);
1797
backing_store_draw (BackingStore *store,
1803
GdkGC *gc = gdk_gc_new (dest);
1805
gdk_gc_set_clip_region (gc, clip);
1807
gdk_draw_drawable (dest, gc, store->pixmap,
1808
0, 0, x, y, store->width, store->height);
1810
g_object_unref (gc);
1814
backing_store_scroll (BackingStore *store,
1818
GdkGC *gc = gdk_gc_new (store->pixmap);
1821
gdk_draw_drawable (store->pixmap, gc, store->pixmap,
1823
store->width, store->height);
1825
/* Invalidate vertically */
1827
rect.width = store->width;
1836
rect.y = store->height + dy;
1840
gdk_region_union_with_rect (store->update_region, &rect);
1842
/* Invalidate horizontally */
1844
rect.height = store->height;
1853
rect.x = store->width + dx;
1857
gdk_region_union_with_rect (store->update_region, &rect);
1861
backing_store_invalidate_rect (BackingStore *store,
1864
gdk_region_union_with_rect (store->update_region, rect);
1868
backing_store_invalidate_region (BackingStore *store,
1871
gdk_region_union (store->update_region, region);
1875
backing_store_invalidate_all (BackingStore *store)
1877
GdkRectangle rect = { 0, 0, store->width, store->height };
1878
gdk_region_destroy (store->update_region);
1879
store->update_region = gdk_region_rectangle (&rect);
1883
backing_store_resize (BackingStore *store,
1887
GdkPixmap *pixmap = gdk_pixmap_new (store->pixmap, width, height, -1);
1889
/* Unfortunately we don't know in which direction we were resized,
1890
* so we just assume we were dragged from the south-east corner.
1892
* Although, maybe we could get the root coordinates of the input-window?
1893
* That might just work, actually. We need to make sure metacity uses
1894
* static gravity for the window before this will be useful.
1896
simple_draw_drawable (pixmap, store->pixmap, 0, 0, 0, 0, -1, -1);
1898
g_object_unref (store->pixmap);
1900
store->pixmap = pixmap;
1902
/* FIXME: invalidate uncovered strip only */
1904
backing_store_invalidate_all (store);
1908
cclip_to_region (cairo_t *cr, GdkRegion *region)
1911
GdkRectangle *rects;
1913
gdk_region_get_rectangles (region, &rects, &n_rects);
1915
cairo_new_path (cr);
1918
GdkRectangle *rect = &(rects[n_rects]);
1920
cairo_rectangle (cr, rect->x, rect->y, rect->width, rect->height);
1928
backing_store_process_updates (BackingStore *store,
1932
cairo_t *cr = gdk_cairo_create (store->pixmap);
1933
GdkRegion *region = store->update_region;
1934
store->update_region = gdk_region_new ();
1936
cclip_to_region (cr, store->update_region);
1938
func (cr, store->update_region, data);
1940
gdk_region_destroy (region);