diff options
author | Søren Sandmann <sandmann@redhat.com> | 2009-03-26 18:18:39 -0400 |
---|---|---|
committer | Søren Sandmann <sandmann@redhat.com> | 2009-03-26 18:18:39 -0400 |
commit | 7f4f44e95a55356e0ebff3a2d410b9308f44b60f (patch) | |
tree | 4b2508437af381f03c6bc855bdb0e96b7929e9b5 | |
parent | c2520b87ac06bdd92fe785d5907bc0d707df377f (diff) |
Update scrollarea.[ch]
-rw-r--r-- | backingstore.c | 833 | ||||
-rw-r--r-- | backingstore.h | 42 | ||||
-rw-r--r-- | scrollarea.c | 1069 | ||||
-rw-r--r-- | scrollarea.h | 5 | ||||
-rw-r--r-- | toolbar.c | 18 |
5 files changed, 1226 insertions, 741 deletions
diff --git a/backingstore.c b/backingstore.c index e52377b..a9ea014 100644 --- a/backingstore.c +++ b/backingstore.c @@ -1,28 +1,54 @@ +#include <gdk/gdkx.h> +#include <X11/Xlib.h> #include "backingstore.h" +typedef struct InputPath InputPath; +typedef struct InputRegion InputRegion; + struct BackingStore { - GdkPixmap *pixmap; - GdkRegion *update_region; - int width; - int height; + GdkPixmap * pixmap; + GdkRegion * update_region; + int width; + int height; + + /* Input */ + InputRegion * current_input; + GPtrArray * input_regions; + int current_x; + int current_y; + BackingStoreEventFunc last_func; + gpointer last_data; + int last_x; + int last_y; + gboolean last_was_motion; + gboolean implicit_grab; + gboolean grabbed; + BackingStoreEventFunc grab_func; + gpointer grab_data; + + guint32 motion_notify_time; + gboolean need_fake_motion; + gboolean in_process_updates; }; -BackingStore * -backing_store_new (GdkWindow *window, - int width, - int height) +struct InputPath { - BackingStore *store = g_new0 (BackingStore, 1); - GdkRectangle rect = { 0, 0, width, height }; + cairo_fill_rule_t fill_rule; + double line_width; + cairo_path_t *path; + gboolean is_stroke; - store->pixmap = gdk_pixmap_new (window, width, height, -1); - store->update_region = gdk_region_rectangle (&rect); - store->width = width; - store->height = height; + BackingStoreEventFunc func; + gpointer data; +}; - return store; -} +/* InputRegions are mutually disjoint */ +struct InputRegion +{ + GdkRegion *region; + GArray *paths; +}; void backing_store_free (BackingStore *store) @@ -33,55 +59,82 @@ backing_store_free (BackingStore *store) } void -backing_store_scroll (BackingStore *store, - int dx, - int dy) +backing_store_move_area (BackingStore *store, + int x, + int y, + int width, + int height, + int dx, + int dy) { - GdkGC *gc = gdk_gc_new (store->pixmap); + GdkRegion *moving_invalid; + GdkRegion *region; GdkRectangle rect; + GdkGC *gc; + gc = gdk_gc_new (store->pixmap); gdk_draw_drawable (store->pixmap, gc, store->pixmap, - 0, 0, dx, dy, - store->width, store->height); + x, y, x + dx, y + dy, + width, height); + g_object_unref (gc); - gdk_region_offset (store->update_region, dx, dy); - - /* Invalidate vertically */ - rect.x = 0; - rect.width = store->width; + rect.x = x; + rect.y = y; + rect.width = width; + rect.height = height; - if (dy > 0) - { - rect.y = 0; - rect.height = dy; - } - else - { - rect.y = store->height + dy; - rect.height = -dy; - } + region = gdk_region_rectangle (&rect); - gdk_region_union_with_rect (store->update_region, &rect); - - /* Invalidate horizontally */ + /* Compute the invalid part of the region that moves */ + moving_invalid = gdk_region_copy (region); + gdk_region_intersect (moving_invalid, store->update_region); + + /* Invalidate the source rectangle */ + gdk_region_union (store->update_region, region); + + /* Uninvalidate the destination rectangle */ + gdk_region_offset (region, dx, dy); + gdk_region_subtract (store->update_region, region); + + /* Invalidate the invalid parts that moved */ + gdk_region_union (store->update_region, moving_invalid); + + gdk_region_destroy (moving_invalid); + gdk_region_destroy (region); + + /* Intersect invalid area with store extents */ + rect.x = 0; rect.y = 0; + rect.width = store->width; rect.height = store->height; - if (dx > 0) - { - rect.x = 0; - rect.width = dx; - } - else - { - rect.x = store->width + dx; - rect.width = -dx; - } + region = gdk_region_rectangle (&rect); + gdk_region_intersect (store->update_region, region); + gdk_region_destroy (region); + + /* FIXME: move the input areas instead */ + backing_store_invalidate_all (store); - gdk_region_union_with_rect (store->update_region, &rect); + if (!gdk_region_empty (store->update_region)) + store->need_fake_motion = TRUE; +} + +static void +process_event (BackingStore *store, + BackingStoreEventType input_type, + int x, + int y, + int button); + + +void +backing_store_scroll (BackingStore *store, + int dx, + int dy) +{ + backing_store_move_area (store, 0, 0, store->width, store->height, dx, dy); } -#if 0 static void print_region (const char *header, GdkRegion *region) { @@ -101,7 +154,465 @@ print_region (const char *header, GdkRegion *region) g_free (rects); } + +static void +begin_grab_internal (BackingStore *store, + gboolean implicit, + BackingStoreEventFunc func, + gpointer data) +{ +#if 0 + g_print ("begin%s grab\n", implicit? " implicit" : ""); +#endif + + /* FIXME: we should probably take a server grab */ + /* Also, there should be support for setting the grab cursor */ + + /* Grabs can be overridden if desired - FIXME: make sure this right */ + /* We may need a grab counter here if there are both implicit and + * explicit grabs in play at the same time + */ + store->implicit_grab = implicit; + store->grabbed = TRUE; + store->grab_func = func; + store->grab_data = data; +} + +static void +end_grab_internal (BackingStore *store, + gboolean only_implicit) +{ + if (only_implicit && !store->implicit_grab) + return; + +#if 0 + g_print ("end grab\n"); +#endif + + store->implicit_grab = FALSE; + store->grabbed = FALSE; + store->grab_func = NULL; + store->grab_data = NULL; +} + +static void +emit_input (BackingStore *store, + BackingStoreEventType type, + int x, + int y, + int button, + BackingStoreEventFunc func, + gpointer data) +{ + BackingStoreEvent event; + gboolean finished_grab = FALSE; + + event.x = x; + event.y = y; + event.button = button; + +#if 0 + if (store->grabbed) + g_print ("grabbed\n"); #endif + + /* Handle crossing events */ + if (store->last_func != func || store->last_data != data) + { + if (!store->grabbed || + (store->last_func == store->grab_func && + store->last_data == store->grab_data)) + { + event.type = BACKING_STORE_LEAVE; + + if (store->last_func) + { +#if 0 + g_print ("leave: %d\n", ec); + g_print ("leave %p -> %p\n", store->last_func, func); +#endif + + store->last_func (store, &event, store->last_data); + } + } + + if (!store->grabbed || + (func == store->grab_func && + data == store->grab_data)) + { + event.type = BACKING_STORE_ENTER; + + if (func) + { +#if 0 + g_print ("enter %d\n", ec); +#endif + func (store, &event, data); + } + } + } + + event.type = type; + + if (event.type != BACKING_STORE_NONE) + { + BackingStoreEventFunc emit_func; + gpointer emit_data; + + if (store->grabbed) + { + emit_func = store->grab_func; + emit_data = store->grab_data; + } + else + { + emit_func = func; + emit_data = data; + } + + if (emit_func) + { + if (event.type == BACKING_STORE_BUTTON_PRESS) + begin_grab_internal (store, TRUE, emit_func, emit_data); + +#if 0 + g_print ("other %d\n", ec); +#endif + + emit_func (store, &event, emit_data); + + if (event.type == BACKING_STORE_BUTTON_RELEASE) + { + end_grab_internal (store, TRUE); + + finished_grab = TRUE; + } + } + } + + store->last_func = func; + store->last_data = data; + store->last_x = x; + store->last_y = y; + store->last_was_motion = (event.type == BACKING_STORE_MOTION); + + if (finished_grab) + { + /* When a grab has finished, fake a motion to where + * the last grabbed event happened. + */ + process_event (store, BACKING_STORE_MOTION, x, y, 0); + } +} + +static void +process_event (BackingStore *store, + BackingStoreEventType input_type, + int x, + int y, + int button) +{ + int i; + + for (i = 0; i < store->input_regions->len; ++i) + { + InputRegion *region = store->input_regions->pdata[i]; + + if (gdk_region_point_in (region->region, x, y)) + { + int j; + + /* Iterate backwards since the topmost paths are at + * the back. + */ + for (j = region->paths->len - 1; j >= 0; --j) + { + InputPath *path = &g_array_index (region->paths, InputPath, j); + cairo_t *cr; + gboolean inside; + + if (store->grabbed && + (path->func != store->grab_func || + path->data != store->grab_data)) + { + /* When there is a grab in place we only care + * about input regions for the purposes of + * generating enter events for the grab + * function. + */ + continue; + } + + cr = gdk_cairo_create (store->pixmap); + cairo_set_fill_rule (cr, path->fill_rule); + cairo_set_line_width (cr, path->line_width); + cairo_append_path (cr, path->path); + + if (path->is_stroke) + inside = cairo_in_stroke (cr, x, y); + else + inside = cairo_in_fill (cr, x, y); + + cairo_destroy (cr); + + if (inside) + { + emit_input (store, input_type, + x, y, button, + path->func, + path->data); + + return; + } + } + + /* Since the regions are all disjoint, no other region + * can match. Of course we could be clever and try and + * sort the regions, but so far I have been unable to + * make this loop show up on a profile. + */ + break; + } + } + + /* If we got here, no event was emitted, so we emit + * an event with a NULL event function. This will cause some + * internal bookkeeping to be updated, and an event to be + * emitted if a grab happens to be active. + */ + emit_input (store, input_type, x, y, button, NULL, NULL); +} + +typedef struct +{ + Window window; + guint32 last_motion_time; + gboolean stop_compressing; +} EventScannerData; + +static Bool +get_motion_time (Display *dpy, + XEvent *xevent, + XPointer arg) +{ + EventScannerData *esd = (EventScannerData *)arg; + + if (esd->stop_compressing) + return FALSE; + + if (esd->window != xevent->xany.window) + { + esd->stop_compressing = TRUE; + return FALSE; + } + + if (xevent->type == MotionNotify) + { + esd->last_motion_time = xevent->xmotion.time; + return FALSE; + } + + if (xevent->type == EnterNotify || + xevent->type == LeaveNotify) + { + esd->last_motion_time = xevent->xcrossing.time; + return FALSE; + } + + return FALSE; +} + +static gboolean +use_this_motion_notify (GdkWindow *window, + guint32 event_time, + guint32 *motion_notify_time) +{ + if (*motion_notify_time) + { + if (event_time == *motion_notify_time) + { + *motion_notify_time = 0; + + return TRUE; + } + else + { + /* There are more motion events already queued up */ + + return FALSE; + } + } + else + { + Display *dpy = gdk_x11_get_default_xdisplay(); + EventScannerData esd = { GDK_WINDOW_XWINDOW (window), 0, FALSE }; + XEvent dummy; + + XCheckIfEvent (dpy, &dummy, get_motion_time, (XPointer)&esd); + + if (esd.last_motion_time == 0) + { + return TRUE; + } + else + { + *motion_notify_time = esd.last_motion_time; + return FALSE; + } + } +} + +/* Input */ +void +backing_store_process_event (BackingStore *store, + GdkEvent *event) +{ + BackingStoreEventType input_type = BACKING_STORE_NONE; + int button = 0; + int x, y; + + if (event->type == GDK_BUTTON_PRESS) + { + input_type = BACKING_STORE_BUTTON_PRESS; + button = event->button.button; + x = event->button.x; + y = event->button.y; + } + else if (event->type == GDK_BUTTON_RELEASE) + { + input_type = BACKING_STORE_BUTTON_RELEASE; + button = event->button.button; + x = event->button.x; + y = event->button.y; + } + else if (event->type == GDK_MOTION_NOTIFY || + event->type == GDK_LEAVE_NOTIFY || + event->type == GDK_ENTER_NOTIFY) + { + GdkWindow *window; + guint32 time; + + input_type = BACKING_STORE_MOTION; + button = 0; + + if (event->type == GDK_MOTION_NOTIFY) + { + x = event->motion.x; + y = event->motion.y; + window = event->motion.window; + time = event->motion.time; + } + else + { + x = event->crossing.x; + y = event->crossing.y; + window = event->crossing.window; + time = event->crossing.time; + } + + if (!use_this_motion_notify (window, time, &store->motion_notify_time)) + return; + } + else + { + return; + } + + process_event (store, input_type, x, y, button); + + store->current_x = x; + store->current_y = y; +} + +static void +user_to_device (double *x, double *y, + gpointer data) +{ + cairo_surface_t *surface = cairo_get_target (data); + double dev_x; + double dev_y; + + cairo_surface_get_device_offset (surface, &dev_x, &dev_y); + + cairo_user_to_device (data, x, y); + *x += dev_x; + *y += dev_y; + + +} + +typedef void (* PathForeachFunc) (double *x, + double *y, + gpointer data); + +static void +path_foreach_point (cairo_path_t *path, + PathForeachFunc func, + gpointer user_data) +{ + int i; + + for (i = 0; i < path->num_data; i += path->data[i].header.length) + { + cairo_path_data_t *data = &(path->data[i]); + + switch (data->header.type) + { + case CAIRO_PATH_MOVE_TO: + case CAIRO_PATH_LINE_TO: + func (&(data[1].point.x), &(data[1].point.y), user_data); + break; + + case CAIRO_PATH_CURVE_TO: + func (&(data[1].point.x), &(data[1].point.y), user_data); + func (&(data[2].point.x), &(data[2].point.y), user_data); + func (&(data[3].point.x), &(data[3].point.y), user_data); + break; + + case CAIRO_PATH_CLOSE_PATH: + break; + } + } +} + +void +backing_store_add_input (BackingStore *store, + cairo_t *cr, + gboolean is_stroke, + BackingStoreEventFunc func, + gpointer data) +{ + InputPath path; + + path.is_stroke = is_stroke; + path.fill_rule = cairo_get_fill_rule (cr); + path.line_width = cairo_get_line_width (cr); + path.path = cairo_copy_path (cr); + path_foreach_point (path.path, user_to_device, cr); + path.func = func; + path.data = data; + + g_array_append_val (store->current_input->paths, path); +} + +BackingStore * +backing_store_new (GdkWindow *window, + int width, + int height) +{ + BackingStore *store = g_new0 (BackingStore, 1); + GdkRectangle rect = { 0, 0, width, height }; + + store->pixmap = gdk_pixmap_new (window, width, height, -1); + store->update_region = gdk_region_rectangle (&rect); + store->width = width; + store->height = height; + + store->current_input = NULL; + store->input_regions = g_ptr_array_new (); + + return store; +} void backing_store_invalidate_rect (BackingStore *store, @@ -140,23 +651,6 @@ backing_store_invalidate_all (BackingStore *store) store->update_region = gdk_region_rectangle (&rect); } -static void -simple_draw_drawable (GdkDrawable *dst, - GdkDrawable *src, - int src_x, - int src_y, - int dst_x, - int dst_y, - int width, - int height) -{ - GdkGC *gc = gdk_gc_new (dst); - - gdk_draw_drawable (dst, gc, src, src_x, src_y, dst_x, dst_y, width, height); - - g_object_unref (gc); -} - void backing_store_resize (BackingStore *store, int width, @@ -165,7 +659,8 @@ backing_store_resize (BackingStore *store, GdkPixmap *pixmap; GdkRegion *old, *invalid; GdkRectangle r; - + GdkGC *gc; + width = MAX (width, 1); height = MAX (height, 1); @@ -178,8 +673,10 @@ backing_store_resize (BackingStore *store, * That might just work, actually. We need to make sure metacity uses * static gravity for the window before this will be useful. */ - simple_draw_drawable (pixmap, store->pixmap, 0, 0, 0, 0, -1, -1); - + gc = gdk_gc_new (pixmap); + gdk_draw_drawable (pixmap, gc, store->pixmap, 0, 0, 0, 0, -1, -1); + g_object_unref (gc); + g_object_unref (store->pixmap); store->pixmap = pixmap; @@ -206,9 +703,93 @@ backing_store_resize (BackingStore *store, gdk_region_destroy (invalid); } +void +backing_store_draw (BackingStore *store, + GdkDrawable *dest, + GdkRegion *clip, + int dest_x, + int dest_y) +{ + GdkGC *gc; + + if (store->in_process_updates) + { + g_critical ("backing_store_draw() cannot be called when " + "backing_store_process_updates() is running\n"); + + return; + } + + gc = gdk_gc_new (dest); + + gdk_gc_set_clip_region (gc, clip); + + gdk_draw_drawable (dest, gc, store->pixmap, + 0, 0, dest_x, dest_y, store->width, store->height); + + g_object_unref (gc); +} + +static GdkRegion * +make_store_region (BackingStore *store) +{ + GdkRectangle store_rect; + + store_rect.x = 0; + store_rect.y = 0; + store_rect.width = store->width; + store_rect.height = store->height; + + return gdk_region_rectangle (&store_rect); +} static void -cclip_to_region (cairo_t *cr, GdkRegion *region) +input_region_free (InputRegion *region) +{ + int i; + + for (i = 0; i < region->paths->len; ++i) + { + InputPath *path = &g_array_index (region->paths, InputPath, i); + + cairo_path_destroy (path->path); + } + + g_array_free (region->paths, TRUE); + + gdk_region_destroy (region->region); + + g_free (region); +} + +static void +clear_exposed_input_region (BackingStore *store, + GdkRegion *region) +{ + int i; + GdkRegion *store_reg = make_store_region (store); + + gdk_region_subtract (store_reg, region); + + for (i = 0; i < store->input_regions->len; ++i) + { + InputRegion *region = store->input_regions->pdata[i]; + + gdk_region_intersect (region->region, store_reg); + + if (gdk_region_empty (region->region)) + { + input_region_free (region); + + g_ptr_array_remove_index_fast (store->input_regions, i--); + } + } + + gdk_region_destroy (store_reg); +} + +static void +clip_to_region (cairo_t *cr, GdkRegion *region) { int n_rects; GdkRectangle *rects; @@ -228,38 +809,88 @@ cclip_to_region (cairo_t *cr, GdkRegion *region) } void -backing_store_draw (BackingStore *store, - GdkDrawable *dest, - GdkRegion *clip, - int dest_x, - int dest_y) -{ - GdkGC *gc = gdk_gc_new (dest); - - gdk_gc_set_clip_region (gc, clip); - - gdk_draw_drawable (dest, gc, store->pixmap, - 0, 0, dest_x, dest_y, store->width, store->height); - g_object_unref (gc); -} - -void backing_store_process_updates (BackingStore *store, BackingPaintFunc func, gpointer data) { - cairo_t *cr = gdk_cairo_create (store->pixmap); - GdkRegion *region = store->update_region; - store->update_region = gdk_region_new (); - - cclip_to_region (cr, region); - - cairo_set_source_rgb (cr, g_random_double(), 0, 0); - - cairo_paint (cr); + store->in_process_updates = TRUE; - func (cr, region, data); + /* FIXME: + * + * The expose process itself can generate invalidations, + * so we need to continue to process updates until + * the update region is empty + * + * Yes, this can lead to infinite loops. Something should be + * done about that, but it's not completely clear what exactly. + * + * Consider the case where hovering an object causes the object to + * move out of the way, which causes non-hovering, which causes + * the object to move back - and so on. If the application is + * actually in this situation, then an infnitely loop is + * in some sense the logical result. + * + * A possibility is to only loop, say, five times, and just let + * applications sort it out by itself. (If they don't do anything, + * they will get flickering back and forth, which is probably the + * correct default behavior). + */ + while (!gdk_region_empty (store->update_region)) + { + cairo_t *cr = gdk_cairo_create (store->pixmap); + GdkRegion *region = store->update_region; + GdkRegion *store_reg = make_store_region (store); + + gdk_region_intersect (region, store_reg); + + gdk_region_destroy (store_reg); + + store->update_region = gdk_region_new (); + + clip_to_region (cr, region); + + { + /* To make sure all of the clip region is painted */ + cairo_set_source_rgb (cr, g_random_double(), 0, 0); + cairo_paint (cr); + } + + clear_exposed_input_region (store, region); + + store->current_input = g_new0 (InputRegion, 1); + store->current_input->region = region; + store->current_input->paths = g_array_new ( + FALSE, FALSE, sizeof (InputPath)); + + func (cr, region, data); + + g_ptr_array_add (store->input_regions, store->current_input); + + store->current_input = NULL; + + cairo_destroy (cr); + + if (store->need_fake_motion) + { + /* The backing store was scrolled, so generate a fake + * motion event so that scrolling while dragging produces + * the correct result. + */ + process_event (store, BACKING_STORE_MOTION, + store->current_x, store->current_y, 0); + + store->need_fake_motion = FALSE; + } + else + { + /* Make sure any crossing events generated during the + * expose process are emitted here + */ + + process_event (store, BACKING_STORE_NONE, + store->current_x, store->current_y, 0); + } + } - gdk_region_destroy (region); - cairo_destroy (cr); + store->in_process_updates = FALSE; } diff --git a/backingstore.h b/backingstore.h index 2d39214..3237885 100644 --- a/backingstore.h +++ b/backingstore.h @@ -3,12 +3,32 @@ #include <cairo.h> typedef struct BackingStore BackingStore; +typedef struct BackingStoreEvent BackingStoreEvent; + +typedef enum +{ + BACKING_STORE_NONE, + BACKING_STORE_BUTTON_PRESS, + BACKING_STORE_BUTTON_RELEASE, + BACKING_STORE_ENTER, + BACKING_STORE_LEAVE, + BACKING_STORE_MOTION, +} BackingStoreEventType; + +struct BackingStoreEvent +{ + BackingStoreEventType type; + int x; + int y; + int button; +}; typedef void (* BackingPaintFunc) (cairo_t *cr, GdkRegion *region, gpointer data); -typedef gboolean (* BackingInputFunc) (gpointer input_data, - gpointer user_data); +typedef gboolean (* BackingStoreEventFunc) (BackingStore *store, + BackingStoreEvent *event, + gpointer *data); BackingStore *backing_store_new (GdkWindow *window, int width, @@ -19,6 +39,14 @@ void backing_store_draw (BackingStore *store, GdkRegion *clip, int dest_x, int dest_y); + +void backing_store_move_area (BackingStore *store, + int x, + int y, + int width, + int height, + int dx, + int dy); void backing_store_scroll (BackingStore *store, int dx, int dy); @@ -33,3 +61,13 @@ void backing_store_resize (BackingStore *store, void backing_store_process_updates (BackingStore *store, BackingPaintFunc func, gpointer data); + +/* Input */ +void backing_store_process_event (BackingStore *store, + GdkEvent *event); +void backing_store_add_input (BackingStore *store, + cairo_t *cr, + gboolean is_stroke, + BackingStoreEventFunc func, + gpointer data); + diff --git a/scrollarea.c b/scrollarea.c index 622a402..dde63d8 100644 --- a/scrollarea.c +++ b/scrollarea.c @@ -5,38 +5,12 @@ #include "foo-marshal.h" #include "backingstore.h" - - G_DEFINE_TYPE (FooScrollArea, foo_scroll_area, GTK_TYPE_CONTAINER); static GtkWidgetClass *parent_class; -typedef struct InputPath InputPath; -typedef struct InputRegion InputRegion; typedef struct AutoScrollInfo AutoScrollInfo; -struct InputPath -{ - gboolean is_stroke; - cairo_fill_rule_t fill_rule; - double line_width; - cairo_path_t *path; /* In canvas coordinates */ - - FooScrollAreaEventFunc func; - gpointer data; - - InputPath *next; -}; - -/* InputRegions are mutually disjoint */ -struct InputRegion -{ - GdkRegion *region; /* the boundary of this area in - * canvas coordinates - */ - InputPath *paths; -}; - struct AutoScrollInfo { int dx; @@ -71,8 +45,6 @@ struct FooScrollAreaPrivate * * It is used for clipping of input areas */ - InputRegion *current_input; - gboolean implicit_grab; gboolean grabbed; FooScrollAreaEventFunc grab_func; @@ -80,13 +52,20 @@ struct FooScrollAreaPrivate BackingStore *store; - guint32 motion_notify_time; - gboolean in_size_allocate; + gboolean paint_updates; /* Last emitted event went here - used to generate enters and leaves */ FooScrollAreaEventFunc last_func; gpointer last_data; + int last_x; + int last_y; + gboolean last_was_motion; + + int current_x; + int current_y; + + gboolean need_flush; }; enum @@ -106,7 +85,7 @@ static gboolean foo_scroll_area_expose (GtkWidget *widget GdkEventExpose *expose); static void foo_scroll_area_size_allocate (GtkWidget *widget, GtkAllocation *allocation); -static void foo_scroll_area_set_scroll_adjustments (FooScrollArea *scroll_area, +static void foo_scroll_area_set_scroll_adjustments (FooScrollArea *area, GtkAdjustment *hadj, GtkAdjustment *vadj); static void foo_scroll_area_realize (GtkWidget *widget); @@ -122,11 +101,18 @@ static gboolean foo_scroll_area_motion (GtkWidget *widget static gboolean foo_scroll_area_crossing (GtkWidget *widget, GdkEventCrossing *event); - static void foo_scroll_area_map (GtkWidget *widget) { + FooScrollArea *area = FOO_SCROLL_AREA (widget); + FooScrollAreaPrivate *priv = area->priv; + GTK_WIDGET_CLASS (parent_class)->map (widget); + + gdk_window_get_pointer (widget->window, + &priv->current_x, + &priv->current_y, + NULL); } static void @@ -138,14 +124,15 @@ foo_scroll_area_unmap (GtkWidget *widget) static void foo_scroll_area_finalize (GObject *object) { - FooScrollArea *scroll_area = FOO_SCROLL_AREA (object); + FooScrollArea *area = FOO_SCROLL_AREA (object); + FooScrollAreaPrivate *priv = area->priv; - g_object_unref (scroll_area->priv->hadj); - g_object_unref (scroll_area->priv->vadj); + g_object_unref (priv->hadj); + g_object_unref (priv->vadj); - g_ptr_array_free (scroll_area->priv->input_regions, TRUE); + g_ptr_array_free (priv->input_regions, TRUE); - g_free (scroll_area->priv); + g_free (priv); G_OBJECT_CLASS (foo_scroll_area_parent_class)->finalize (object); } @@ -233,60 +220,27 @@ new_adjustment (void) } static void -foo_scroll_area_init (FooScrollArea *scroll_area) +foo_scroll_area_init (FooScrollArea *area) { -#if 0 - GTK_WIDGET_SET_FLAGS (scroll_area, GTK_NO_WINDOW); -#endif - - gtk_widget_set_redraw_on_allocate (GTK_WIDGET (scroll_area), FALSE); + FooScrollAreaPrivate *priv; - scroll_area->priv = g_new0 (FooScrollAreaPrivate, 1); - scroll_area->priv->width = 0; - scroll_area->priv->height = 0; - scroll_area->priv->hadj = g_object_ref_sink (new_adjustment()); - scroll_area->priv->vadj = g_object_ref_sink (new_adjustment()); - scroll_area->priv->x_offset = 0.0; - scroll_area->priv->y_offset = 0.0; - scroll_area->priv->min_width = -1; - scroll_area->priv->min_height = -1; - scroll_area->priv->auto_scroll_info = NULL; - scroll_area->priv->input_regions = g_ptr_array_new (); + gtk_widget_set_redraw_on_allocate (GTK_WIDGET (area), FALSE); - gtk_widget_set_double_buffered (GTK_WIDGET (scroll_area), FALSE); -} + priv = g_new0 (FooScrollAreaPrivate, 1); + priv->width = 0; + priv->height = 0; + priv->hadj = g_object_ref_sink (new_adjustment()); + priv->vadj = g_object_ref_sink (new_adjustment()); + priv->x_offset = 0.0; + priv->y_offset = 0.0; + priv->min_width = -1; + priv->min_height = -1; + priv->auto_scroll_info = NULL; + priv->input_regions = g_ptr_array_new (); -static void -translate_cairo_device (cairo_t *cr, - int x_offset, - int y_offset) -{ - cairo_surface_t *surface = cairo_get_target (cr); - double dev_x; - double dev_y; + area->priv = priv; - cairo_surface_get_device_offset (surface, &dev_x, &dev_y); - dev_x += x_offset; - dev_y += y_offset; - cairo_surface_set_device_offset (surface, dev_x, dev_y); -} - -static void -print_region (const char *header, GdkRegion *region) -{ - GdkRectangle *rects; - int n_rects; - int i; - - g_print ("%s\n", header); - - gdk_region_get_rectangles (region, &rects, &n_rects); - for (i = 0; i < n_rects; ++i) - { - GdkRectangle *rect = &(rects[i]); - g_print (" %d %d %d %d\n", - rect->x, rect->y, rect->width, rect->height); - } + gtk_widget_set_double_buffered (GTK_WIDGET (area), FALSE); } typedef void (* PathForeachFunc) (double *x, @@ -368,76 +322,19 @@ path_compute_extents (cairo_path_t *path, #endif static void -input_path_free_list (InputPath *paths) -{ - if (!paths) - return; - - input_path_free_list (paths->next); - cairo_path_destroy (paths->path); - g_free (paths); -} - -static void -input_region_free (InputRegion *region) -{ - input_path_free_list (region->paths); - gdk_region_destroy (region->region); - - g_free (region); -} - -static void -get_viewport (FooScrollArea *scroll_area, +get_viewport (FooScrollArea *area, GdkRectangle *viewport) { - GtkWidget *widget = GTK_WIDGET (scroll_area); + FooScrollAreaPrivate *priv = area->priv; + GtkWidget *widget = GTK_WIDGET (area); - viewport->x = scroll_area->priv->x_offset; - viewport->y = scroll_area->priv->y_offset; + viewport->x = priv->x_offset; + viewport->y = priv->y_offset; viewport->width = widget->allocation.width; viewport->height = widget->allocation.height; } static void -allocation_to_canvas (FooScrollArea *area, - int *x, - int *y) -{ - *x += area->priv->x_offset; - *y += area->priv->y_offset; -} - -static void -clear_exposed_input_region (FooScrollArea *area, - GdkRegion *exposed) /* in canvas coordinates */ -{ - int i; - GdkRegion *viewport_reg; - GdkRectangle viewport; - - get_viewport (area, &viewport); - - viewport_reg = gdk_region_rectangle (&viewport); - gdk_region_subtract (viewport_reg, exposed); - - for (i = 0; i < area->priv->input_regions->len; ++i) - { - InputRegion *region = area->priv->input_regions->pdata[i]; - - gdk_region_intersect (region->region, viewport_reg); - - if (gdk_region_empty (region->region)) - { - input_region_free (region); - g_ptr_array_remove_index_fast (area->priv->input_regions, i--); - } - } - - gdk_region_destroy (viewport_reg); -} - -static void setup_background_cr (GdkWindow *window, cairo_t *cr, int x_offset, @@ -465,39 +362,51 @@ setup_background_cr (GdkWindow *window, } static void +allocation_to_canvas (FooScrollArea *area, + int *x, + int *y) +{ + FooScrollAreaPrivate *priv = area->priv; + + *x += priv->x_offset; + *y += priv->y_offset; +} + +static void canvas_to_window (FooScrollArea *area, GdkRegion *region) { - gdk_region_offset (region, - -area->priv->x_offset, - -area->priv->y_offset); + FooScrollAreaPrivate *priv = area->priv; + + gdk_region_offset (region, - priv->x_offset, - priv->y_offset); } static void window_to_canvas (FooScrollArea *area, GdkRegion *region) { - gdk_region_offset (region, - area->priv->x_offset, - area->priv->y_offset); + FooScrollAreaPrivate *priv = area->priv; + + gdk_region_offset (region, priv->x_offset, priv->y_offset); } static void canvas_to_store (FooScrollArea *area, GdkRegion *region) { - gdk_region_offset (region, -area->priv->x_offset, -area->priv->y_offset); - + FooScrollAreaPrivate *priv = area->priv; + + gdk_region_offset (region, - priv->x_offset, - priv->y_offset); } static void store_to_canvas (FooScrollArea *area, GdkRegion *region) { - gdk_region_offset (region, area->priv->x_offset, area->priv->y_offset); -} + FooScrollAreaPrivate *priv = area->priv; -static gboolean paint_updates = FALSE; + gdk_region_offset (region, priv->x_offset, priv->y_offset); +} static void paint_region (FooScrollArea *area, GdkRegion *region, double r, double g, double b, double a) @@ -558,6 +467,9 @@ clip_to_32bit_rect (cairo_t *cr, GdkRectangle *rect) #if 0 /* This is the code we'll use when cairo is fixed */ + /* It is fixed in 1.8.0, so we can turn it on when 1.8.0 is more + * common + */ cairo_rectangle (cr, rect->x, rect->y, rect->width, rect->height); cairo_clip (cr); #endif @@ -568,17 +480,18 @@ do_exposes (cairo_t *cr, GdkRegion *store_region, gpointer data) { - FooScrollArea *scroll_area = data; + FooScrollArea *area = data; + FooScrollAreaPrivate *priv = area->priv; GdkRegion *region; GdkRectangle rect; GdkRegion *r; /* Paint background */ - setup_background_cr (GTK_WIDGET (scroll_area)->window, cr, 0, 0); + setup_background_cr (GTK_WIDGET (area)->window, cr, 0, 0); cairo_paint (cr); - if (paint_updates) - paint_region (scroll_area, store_region, 0.2, 0.2, 0.8, 0.4); + if (priv->paint_updates) + paint_region (area, store_region, 1.0, 0.0, 0.0, 0.8); /* Note that this function can be called at a time * where the adj->value is different from x_offset. @@ -591,31 +504,19 @@ do_exposes (cairo_t *cr, */ region = gdk_region_copy (store_region); - store_to_canvas (scroll_area, region); - - /* Set up input areas */ - clear_exposed_input_region (scroll_area, region); - - scroll_area->priv->current_input = g_new0 (InputRegion, 1); - scroll_area->priv->current_input->region = gdk_region_copy (region); - scroll_area->priv->current_input->paths = NULL; - g_ptr_array_add (scroll_area->priv->input_regions, - scroll_area->priv->current_input); - + store_to_canvas (area, region); + /* Set up cairo context and emit the signal */ #if 0 - g_print ("translating %d %d\n", -scroll_area->priv->x_offset, - -scroll_area->priv->y_offset); + g_print ("translating %d %d\n", -priv->x_offset, -priv->y_offset); #endif - translate_cairo_device (cr, - -scroll_area->priv->x_offset, - -scroll_area->priv->y_offset); + cairo_translate (cr, -priv->x_offset, -priv->y_offset); /* Restrict to the actual canvas */ rect.x = 0; rect.y = 0; - rect.width = scroll_area->priv->width; - rect.height = scroll_area->priv->height; + rect.width = priv->width; + rect.height = priv->height; r = gdk_region_rectangle (&rect); gdk_region_intersect (region, r); @@ -624,8 +525,8 @@ do_exposes (cairo_t *cr, clip_to_32bit_rect (cr, &rect); #if 0 - g_print ("clipping to %d %d %d %d\n", 0, 0, scroll_area->priv->width, - scroll_area->priv->height); + g_print ("clipping to %d %d %d %d\n", 0, 0, priv->width, + area->priv->height); #endif #if 0 @@ -638,56 +539,80 @@ do_exposes (cairo_t *cr, { gdk_region_get_clipbox (region, &rect); - g_signal_emit (scroll_area, signals[PAINT], 0, cr, &rect, region); + g_signal_emit (area, signals[PAINT], 0, cr, &rect, region); } - scroll_area->priv->current_input = NULL; gdk_region_destroy (region); } +static void +print_region (const char *header, GdkRegion *region) +{ + GdkRectangle *rects; + int n_rects; + int i; + + g_print ("%s\n", header); + + gdk_region_get_rectangles (region, &rects, &n_rects); + for (i = 0; i < n_rects; ++i) + { + GdkRectangle *rect = &(rects[i]); + g_print (" %d %d %d %d\n", + rect->x, rect->y, rect->width, rect->height); + } + + g_free (rects); +} + static gboolean foo_scroll_area_expose (GtkWidget *widget, GdkEventExpose *expose) { FooScrollArea *area = FOO_SCROLL_AREA (widget); + FooScrollAreaPrivate *priv = area->priv; + + backing_store_process_updates (priv->store, do_exposes, area); - backing_store_process_updates (area->priv->store, do_exposes, area); - - if (paint_updates) + if (priv->paint_updates) { - paint_region (area, expose->region, 0.8, 0.2, 0.2, 0.4); - + paint_region (area, expose->region, 0, 0, 1, 0.3); + gdk_flush (); - g_usleep (70000); + g_usleep (100000); } - - backing_store_draw (area->priv->store, widget->window, - expose->region, 0, 0); + + if (!gdk_region_empty (expose->region)) + { + backing_store_draw (area->priv->store, widget->window, + expose->region, 0, 0); + + XImage *image; + + image = XGetImage (GDK_WINDOW_XDISPLAY (widget->window), + GDK_WINDOW_XID (gdk_get_default_root_window()), + 0, 0, 1, 1, + AllPlanes, ZPixmap); + } + return TRUE; } void -foo_scroll_area_get_viewport (FooScrollArea *scroll_area, +foo_scroll_area_get_viewport (FooScrollArea *area, GdkRectangle *viewport) { - g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area)); + g_return_if_fail (FOO_IS_SCROLL_AREA (area)); if (!viewport) return; - get_viewport (scroll_area, viewport); + get_viewport (area, viewport); } static void -process_event (FooScrollArea *scroll_area, - FooScrollAreaEventType input_type, - int x, - int y, - int button); - -static void -emit_viewport_signals (FooScrollArea *scroll_area, +emit_viewport_signals (FooScrollArea *area, GdkRectangle *new_viewport, GdkRectangle *old_viewport) { @@ -702,24 +627,14 @@ emit_viewport_signals (FooScrollArea *scroll_area, if (size_changed || position_changed) { - int px, py; - - g_signal_emit (scroll_area, signals[VIEWPORT_CHANGED], 0, + g_signal_emit (area, signals[VIEWPORT_CHANGED], 0, new_viewport, old_viewport); if (size_changed) { - g_signal_emit (scroll_area, signals[VIEWPORT_SIZE_CHANGED], 0, + g_signal_emit (area, signals[VIEWPORT_SIZE_CHANGED], 0, new_viewport, old_viewport); } - - /* Fake an event when the viewport changes - * FIXME: keep track of the last position in the window - */ - gdk_window_get_pointer (GTK_WIDGET (scroll_area)->window, - &px, &py, NULL); - - process_event (scroll_area, FOO_MOTION, px, py, 0); } } @@ -740,26 +655,27 @@ clamp_adjustment (GtkAdjustment *adj) } static gboolean -set_adjustment_values (FooScrollArea *scroll_area) +set_adjustment_values (FooScrollArea *area) { - GtkAllocation *allocation = >K_WIDGET (scroll_area)->allocation; + FooScrollAreaPrivate *priv = area->priv; + GtkAllocation *allocation = >K_WIDGET (area)->allocation; - GtkAdjustment *hadj = scroll_area->priv->hadj; - GtkAdjustment *vadj = scroll_area->priv->vadj; + GtkAdjustment *hadj = priv->hadj; + GtkAdjustment *vadj = priv->vadj; /* Horizontal */ hadj->page_size = allocation->width; hadj->step_increment = 0.1 * allocation->width; hadj->page_increment = 0.9 * allocation->width; hadj->lower = 0.0; - hadj->upper = scroll_area->priv->width; + hadj->upper = area->priv->width; /* Vertical */ vadj->page_size = allocation->height; vadj->step_increment = 0.1 * allocation->height; vadj->page_increment = 0.9 * allocation->height; vadj->lower = 0.0; - vadj->upper = scroll_area->priv->height; + vadj->upper = priv->height; clamp_adjustment (hadj); clamp_adjustment (vadj); @@ -771,6 +687,7 @@ static void foo_scroll_area_realize (GtkWidget *widget) { FooScrollArea *area = FOO_SCROLL_AREA (widget); + FooScrollAreaPrivate *priv = area->priv; GdkWindowAttr attributes; gint attributes_mask; @@ -799,10 +716,12 @@ foo_scroll_area_realize (GtkWidget *widget) widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask); + gdk_window_set_user_data (widget->window, area); - area->priv->store = backing_store_new (widget->window, widget->allocation.width, - widget->allocation.height); + priv->store = backing_store_new (widget->window, + widget->allocation.width, + widget->allocation.height); widget->style = gtk_style_attach (widget->style, widget->window); @@ -813,17 +732,9 @@ static void foo_scroll_area_unrealize (GtkWidget *widget) { FooScrollArea *area = FOO_SCROLL_AREA (widget); + FooScrollAreaPrivate *priv = area->priv; -#if 0 - if (area->priv->input_window) - { - gdk_window_set_user_data (area->priv->input_window, NULL); - gdk_window_destroy (area->priv->input_window); - area->priv->input_window = NULL; - } -#endif - - backing_store_free (area->priv->store); + backing_store_free (priv->store); GTK_WIDGET_CLASS (parent_class)->unrealize (widget); } @@ -833,7 +744,9 @@ static void allocation_to_canvas_region (FooScrollArea *area, GdkRegion *region) { - gdk_region_offset (region, area->priv->x_offset, area->priv->y_offset); + FooScrollAreaPrivate *priv = area->priv; + + gdk_region_offset (region, priv->x_offset, priv->y_offset); } #endif @@ -841,13 +754,14 @@ static void foo_scroll_area_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { - FooScrollArea *scroll_area = FOO_SCROLL_AREA (widget); + FooScrollArea *area = FOO_SCROLL_AREA (widget); + FooScrollAreaPrivate *priv = area->priv; GdkRectangle new_viewport; GdkRectangle old_viewport; - scroll_area->priv->in_size_allocate = TRUE; + priv->in_size_allocate = TRUE; - get_viewport (scroll_area, &old_viewport); + get_viewport (area, &old_viewport); widget->allocation = *allocation; @@ -863,32 +777,36 @@ foo_scroll_area_size_allocate (GtkWidget *widget, * we still need to paint the background of the exposed * areas outside the area. */ - backing_store_resize (scroll_area->priv->store, + backing_store_resize (priv->store, widget->allocation.width, widget->allocation.height); } - set_adjustment_values (scroll_area); + set_adjustment_values (area); - get_viewport (scroll_area, &new_viewport); + get_viewport (area, &new_viewport); - emit_viewport_signals (scroll_area, &new_viewport, &old_viewport); + emit_viewport_signals (area, &new_viewport, &old_viewport); - scroll_area->priv->in_size_allocate = FALSE; + priv->in_size_allocate = FALSE; } gboolean foo_scroll_area_is_resizing (FooScrollArea *area) { - return area->priv->in_size_allocate; + FooScrollAreaPrivate *priv = area->priv; + + return priv->in_size_allocate; } static void -begin_grab_internal (FooScrollArea *scroll_area, +begin_grab_internal (FooScrollArea *area, gboolean implicit, FooScrollAreaEventFunc func, gpointer data) { + FooScrollAreaPrivate *priv = area->priv; + #if 0 g_print ("begin%s grab\n", implicit? " implicit" : ""); #endif @@ -897,76 +815,32 @@ begin_grab_internal (FooScrollArea *scroll_area, /* Also, there should be support for setting the grab cursor */ /* Grabs can be overridden if desired - FIXME: make sure this right */ - scroll_area->priv->implicit_grab = implicit; - scroll_area->priv->grabbed = TRUE; - scroll_area->priv->grab_func = func; - scroll_area->priv->grab_data = data; + /* We may need a grab counter here if there are both implicit and + * explicit grabs in play at the same time + */ + priv->implicit_grab = implicit; + priv->grabbed = TRUE; + priv->grab_func = func; + priv->grab_data = data; } static void -end_grab_internal (FooScrollArea *scroll_area, +end_grab_internal (FooScrollArea *area, gboolean only_implicit) { - if (only_implicit && !scroll_area->priv->implicit_grab) + FooScrollAreaPrivate *priv = area->priv; + + if (only_implicit && !priv->implicit_grab) return; #if 0 g_print ("end grab\n"); #endif - scroll_area->priv->implicit_grab = FALSE; - scroll_area->priv->grabbed = FALSE; - scroll_area->priv->grab_func = NULL; - scroll_area->priv->grab_data = NULL; -} - -static void -emit_input (FooScrollArea *scroll_area, - FooScrollAreaEventType type, - int x, - int y, - int button, - FooScrollAreaEventFunc func, - gpointer data) -{ - FooScrollAreaEvent event; - - event.x = x; - event.y = y; - event.button = button; - - if (scroll_area->priv->last_func != func || - scroll_area->priv->last_data != data) - { - if (scroll_area->priv->last_func) - { - event.type = FOO_LEAVE; - scroll_area->priv->last_func (scroll_area, &event, - scroll_area->priv->last_data); - } - - if (func) - { - event.type = FOO_ENTER; - func (scroll_area, &event, data); - } - } - - event.type = type; - - if (func) - { - if (event.type == FOO_BUTTON_PRESS) - begin_grab_internal (scroll_area, TRUE, func, data); - - func (scroll_area, &event, data); - - if (event.type == FOO_BUTTON_RELEASE) - end_grab_internal (scroll_area, TRUE); - } - - scroll_area->priv->last_func = func; - scroll_area->priv->last_data = data; + priv->implicit_grab = FALSE; + priv->grabbed = FALSE; + priv->grab_func = NULL; + priv->grab_data = NULL; } #if 0 @@ -1005,121 +879,15 @@ print_path (const char *header, } #endif -static void -process_event (FooScrollArea *scroll_area, - FooScrollAreaEventType input_type, - int x, - int y, - int button) -{ - GtkWidget *widget = GTK_WIDGET (scroll_area); - int i; - - allocation_to_canvas (scroll_area, &x, &y); - - if (scroll_area->priv->grabbed) - { - emit_input (scroll_area, input_type, x, y, button, - scroll_area->priv->grab_func, - scroll_area->priv->grab_data); - return; - } - - for (i = 0; i < scroll_area->priv->input_regions->len; ++i) - { - InputRegion *region = scroll_area->priv->input_regions->pdata[i]; - - if (gdk_region_point_in (region->region, x, y)) - { - InputPath *path; - int n_paths = 0; - - path = region->paths; - while (path) - { - cairo_t *cr; - gboolean inside; - - cr = gdk_cairo_create (widget->window); - cairo_set_fill_rule (cr, path->fill_rule); - cairo_set_line_width (cr, path->line_width); - cairo_append_path (cr, path->path); - - if (path->is_stroke) - inside = cairo_in_stroke (cr, x, y); - else - inside = cairo_in_fill (cr, x, y); - - cairo_destroy (cr); - - if (inside) - { - emit_input (scroll_area, input_type, - x, y, button, - path->func, - path->data); - return; - } - - path = path->next; - n_paths++; - } - - /* Since the regions are all disjoint, no other region - * can match. Of course we could be clever and try and - * sort the regions, but so far I have been unable to - * make this loop show up on a profile. - */ - break; - } - } - - /* If we got here, no event was emitted, so we emit - * a "nothing" event, that will just cause some internal bookkeeping - * be updated - */ - emit_input (scroll_area, FOO_NONE, 0, 0, 0, NULL, NULL); -} - -static void -process_gdk_event (FooScrollArea *scroll_area, - int x, - int y, - GdkEvent *event) -{ - FooScrollAreaEventType input_type = FOO_NONE; - int button = 0; - - if (event->type == GDK_BUTTON_PRESS) - { - input_type = FOO_BUTTON_PRESS; - button = event->button.button; - } - else if (event->type == GDK_BUTTON_RELEASE) - { - input_type = FOO_BUTTON_RELEASE; - button = event->button.button; - } - else if (event->type == GDK_MOTION_NOTIFY || - event->type == GDK_LEAVE_NOTIFY || - event->type == GDK_ENTER_NOTIFY) - { - input_type = FOO_MOTION; - button = 0; - } - - if (input_type != FOO_NONE) - process_event (scroll_area, input_type, x, y, button); -} - static gboolean foo_scroll_area_button_press (GtkWidget *widget, GdkEventButton *event) { FooScrollArea *area = FOO_SCROLL_AREA (widget); + FooScrollAreaPrivate *priv = area->priv; - process_gdk_event (area, event->x, event->y, (GdkEvent *)event); - + backing_store_process_event (priv->store, (GdkEvent *)event); + return TRUE; } @@ -1128,103 +896,21 @@ foo_scroll_area_button_release (GtkWidget *widget, GdkEventButton *event) { FooScrollArea *area = FOO_SCROLL_AREA (widget); + FooScrollAreaPrivate *priv = area->priv; - process_gdk_event (area, event->x, event->y, (GdkEvent *)event); - - return FALSE; -} - -typedef struct -{ - Window window; - guint32 last_motion_time; - gboolean stop_compressing; -} EventScannerData; - -static Bool -get_motion_time (Display *dpy, - XEvent *xevent, - XPointer arg) -{ - EventScannerData *esd = (EventScannerData *)arg; - - if (esd->stop_compressing) - return FALSE; - - if (esd->window != xevent->xany.window) - { - esd->stop_compressing = TRUE; - return FALSE; - } - - if (xevent->type == MotionNotify) - { - esd->last_motion_time = xevent->xmotion.time; - return FALSE; - } - - if (xevent->type == EnterNotify || - xevent->type == LeaveNotify) - { - esd->last_motion_time = xevent->xcrossing.time; - return FALSE; - } + backing_store_process_event (priv->store, (GdkEvent *)event); return FALSE; } static gboolean -use_this_motion_notify (GdkWindow *window, - guint32 event_time, - guint32 *motion_notify_time) -{ - if (*motion_notify_time) - { - if (event_time == *motion_notify_time) - { - *motion_notify_time = 0; - - return TRUE; - } - else - { - /* There are more motion events already queued up */ - - return FALSE; - } - } - else - { - Display *dpy = gdk_x11_get_default_xdisplay(); - EventScannerData esd = { GDK_WINDOW_XWINDOW (window), 0, FALSE }; - XEvent dummy; - - XCheckIfEvent (dpy, &dummy, get_motion_time, (XPointer)&esd); - - if (esd.last_motion_time == 0) - { - return TRUE; - } - else - { - *motion_notify_time = esd.last_motion_time; - return FALSE; - } - } -} - -static gboolean foo_scroll_area_motion (GtkWidget *widget, GdkEventMotion *event) { FooScrollArea *area = FOO_SCROLL_AREA (widget); - - if (use_this_motion_notify (event->window, - event->time, - &(area->priv->motion_notify_time))) - { - process_gdk_event (area, event->x, event->y, (GdkEvent *)event); - } + FooScrollAreaPrivate *priv = area->priv; + + backing_store_process_event (priv->store, (GdkEvent *)event); return TRUE; } @@ -1234,43 +920,42 @@ foo_scroll_area_crossing (GtkWidget *widget, GdkEventCrossing *event) { FooScrollArea *area = FOO_SCROLL_AREA (widget); + FooScrollAreaPrivate *priv = area->priv; - if (use_this_motion_notify (event->window, - event->time, - &(area->priv->motion_notify_time))) - { - process_gdk_event (area, event->x, event->y, (GdkEvent *)event); - } + backing_store_process_event (priv->store, (GdkEvent *)event); return TRUE; } void -foo_scroll_area_set_size_fixed_y (FooScrollArea *scroll_area, +foo_scroll_area_set_size_fixed_y (FooScrollArea *area, int width, int height, int old_y, int new_y) { + FooScrollAreaPrivate *priv = area->priv; int dy = new_y - old_y; - scroll_area->priv->width = width; - scroll_area->priv->height = height; + priv->width = width; + priv->height = height; - scroll_area->priv->vadj->value += dy; + priv->vadj->value += dy; - set_adjustment_values (scroll_area); + set_adjustment_values (area); if (dy != 0) - gtk_adjustment_value_changed (scroll_area->priv->vadj); + gtk_adjustment_value_changed (priv->vadj); } void -foo_scroll_area_set_size (FooScrollArea *scroll_area, +foo_scroll_area_set_size (FooScrollArea *area, int width, int height) { - g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area)); + FooScrollAreaPrivate *priv = area->priv; + + g_return_if_fail (FOO_IS_SCROLL_AREA (area)); /* FIXME: Default scroll algorithm should probably be to * keep the same *area* outside the screen as before. @@ -1287,20 +972,21 @@ foo_scroll_area_set_size (FooScrollArea *scroll_area, * fixed points? */ - scroll_area->priv->width = width; - scroll_area->priv->height = height; + priv->width = width; + priv->height = height; - set_adjustment_values (scroll_area); + set_adjustment_values (area); } static void foo_scroll_area_size_request (GtkWidget *widget, GtkRequisition *requisition) { - FooScrollArea *scroll_area = FOO_SCROLL_AREA (widget); + FooScrollArea *area = FOO_SCROLL_AREA (widget); + FooScrollAreaPrivate *priv = area->priv; - requisition->width = scroll_area->priv->min_width; - requisition->height = scroll_area->priv->min_height; + requisition->width = priv->min_width; + requisition->height = priv->min_height; #if 0 g_print ("request %d %d\n", requisition->width, requisition->height); @@ -1309,9 +995,10 @@ foo_scroll_area_size_request (GtkWidget *widget, static void foo_scrollbar_adjustment_changed (GtkAdjustment *adj, - FooScrollArea *scroll_area) + FooScrollArea *area) { - GtkWidget *widget = GTK_WIDGET (scroll_area); + FooScrollAreaPrivate *priv = area->priv; + GtkWidget *widget = GTK_WIDGET (area); gint dx = 0; gint dy = 0; GdkRectangle old_viewport, new_viewport; @@ -1322,20 +1009,20 @@ foo_scrollbar_adjustment_changed (GtkAdjustment *adj, * On the other hand with the constant time scrolling, it shouldn't * really matter. */ - get_viewport (scroll_area, &old_viewport); + get_viewport (area, &old_viewport); - if (adj == scroll_area->priv->hadj) + if (adj == priv->hadj) { /* FIXME: do we treat the offset as int or double, and, * if int, how do we round? */ - dx = (int)adj->value - scroll_area->priv->x_offset; - scroll_area->priv->x_offset = adj->value; + dx = (int)adj->value - priv->x_offset; + priv->x_offset = adj->value; } - else if (adj == scroll_area->priv->vadj) + else if (adj == priv->vadj) { - dy = (int)adj->value - scroll_area->priv->y_offset; - scroll_area->priv->y_offset = adj->value; + dy = (int)adj->value - priv->y_offset; + priv->y_offset = adj->value; } else { @@ -1344,18 +1031,20 @@ foo_scrollbar_adjustment_changed (GtkAdjustment *adj, if (GTK_WIDGET_REALIZED (widget) && (dx || dy)) { - backing_store_scroll (scroll_area->priv->store, -dx, -dy); + backing_store_scroll (priv->store, -dx, -dy); - gtk_widget_queue_draw (GTK_WIDGET (scroll_area)); + gtk_widget_queue_draw (GTK_WIDGET (area)); } - get_viewport (scroll_area, &new_viewport); + get_viewport (area, &new_viewport); - emit_viewport_signals (scroll_area, &new_viewport, &old_viewport); + emit_viewport_signals (area, &new_viewport, &old_viewport); + + gdk_window_process_all_updates(); } static void -set_one_adjustment (FooScrollArea *scroll_area, +set_one_adjustment (FooScrollArea *area, GtkAdjustment *adjustment, GtkAdjustment **location) { @@ -1372,7 +1061,7 @@ set_one_adjustment (FooScrollArea *scroll_area, if (*location) { g_signal_handlers_disconnect_by_func ( - *location, foo_scrollbar_adjustment_changed, scroll_area); + *location, foo_scrollbar_adjustment_changed, area); g_object_unref (*location); } @@ -1383,18 +1072,20 @@ set_one_adjustment (FooScrollArea *scroll_area, g_signal_connect (*location, "value_changed", G_CALLBACK (foo_scrollbar_adjustment_changed), - scroll_area); + area); } static void -foo_scroll_area_set_scroll_adjustments (FooScrollArea *scroll_area, +foo_scroll_area_set_scroll_adjustments (FooScrollArea *area, GtkAdjustment *hadjustment, GtkAdjustment *vadjustment) { - set_one_adjustment (scroll_area, hadjustment, &scroll_area->priv->hadj); - set_one_adjustment (scroll_area, vadjustment, &scroll_area->priv->vadj); + FooScrollAreaPrivate *priv = area->priv; - set_adjustment_values (scroll_area); + set_one_adjustment (area, hadjustment, &priv->hadj); + set_one_adjustment (area, vadjustment, &priv->vadj); + + set_adjustment_values (area); } FooScrollArea * @@ -1404,12 +1095,14 @@ foo_scroll_area_new (void) } void -foo_scroll_area_set_min_size (FooScrollArea *scroll_area, - int min_width, +foo_scroll_area_set_min_size (FooScrollArea *area, + int min_width, int min_height) { - scroll_area->priv->min_width = min_width; - scroll_area->priv->min_height = min_height; + FooScrollAreaPrivate *priv = area->priv; + + priv->min_width = min_width; + priv->min_height = min_height; /* FIXME: think through invalidation. * @@ -1417,7 +1110,7 @@ foo_scroll_area_set_min_size (FooScrollArea *scroll_area, * - make sure input boxes are invalidated when * needed */ - gtk_widget_queue_resize (GTK_WIDGET (scroll_area)); + gtk_widget_queue_resize (GTK_WIDGET (area)); } #if 0 @@ -1445,6 +1138,63 @@ user_to_device (double *x, double *y, cairo_user_to_device (cr, x, y); } +typedef struct +{ + FooScrollArea *area; + FooScrollAreaEventFunc func; + gpointer data; +} Info; + +static void +backing_store_event (BackingStore *store, + BackingStoreEvent *event, + gpointer data) +{ + Info *info = data; + BackingStoreEvent e = *event; + const char *type; + + e.x = event->x + info->area->priv->x_offset; + e.y = event->y + info->area->priv->y_offset; + + if (info->func) + info->func (info->area, &e, info->data); + +#if 0 + if (event->type == BACKING_STORE_BUTTON_PRESS) + type = "press"; + else if (event->type == BACKING_STORE_BUTTON_RELEASE) + type = "reelase"; + else if (event->type == BACKING_STORE_LEAVE) + type = "leave"; + else if (event->type == BACKING_STORE_ENTER) + type = "enter"; + else if (event->type == BACKING_STORE_MOTION) + type = "motion"; + else + type = "unknown"; + + g_print ("backing: %s %d %d %p %p\n", type, + + e.x, e.y, info->func, info->data); +#endif +} + +static guint +hash_info (gpointer a) +{ + Info *info = a; + + return (gsize)info->func ^ (gsize)info->data; +} + +static gboolean +equal_info (gconstpointer a, gconstpointer b) +{ + const Info *ia = a, *ib = b; + return ia->func == ib->func && ia->data == ib->data; +} + static void add_path (FooScrollArea *area, cairo_t *cr, @@ -1452,10 +1202,21 @@ add_path (FooScrollArea *area, FooScrollAreaEventFunc func, gpointer data) { - InputPath *path = g_new0 (InputPath, 1); + FooScrollAreaPrivate *priv = area->priv; #if 0 double x1, y1, x2, y2; GdkRectangle r; + static GHashTable *hash; + + /* FIXME: This doesn't work because the rectangle we + * get from _extents() is in user coordinates, so + * if we just transform it back to device coordinates + * we may get the wrong bounding box + * + * Ironically, the extent that cairo produces is not + * actually tight - because cairo first converted + * from device coordinates ... + */ if (is_stroke) cairo_stroke_extents (cr, &x1, &y1, &x2, &y2); @@ -1464,24 +1225,41 @@ add_path (FooScrollArea *area, cairo_user_to_device (cr, &x1, &y1); cairo_user_to_device (cr, &x2, &y2); + + r.x = MIN (x1, x2) - 1; + r.y = MIN (y1, y2) - 1; + r.width = MAX (x1, x2) - r.x + 2; + r.width = MAX (y1, y2) - r.y + 2; + + path->extents = r; +#endif + + static GHashTable *hash; + if (!hash) + hash = g_hash_table_new (hash_info, equal_info); - r.x = x1 - 1; - r.y = y1 - 1; - r.width = x2 - x1 + 1; - r.height = y2 - y1 + 1; + Info *info = g_new0 (Info, 1); - if (gdk_region_rect_in (area->priv->current_input->region, &r) != GDK_OVERLAP_RECTANGLE_OUT) + info->area = area; + info->func = func; + info->data = data; + + if (g_hash_table_lookup (hash, info)) + { + Info *old = info; + info = g_hash_table_lookup (hash, info); + g_free (old); + } + else + g_hash_table_insert (hash, info, info); + + backing_store_add_input (priv->store, cr, is_stroke, + backing_store_event, info); + +#if 0 + if (gdk_region_rect_in (priv->current_input->region, &r) != GDK_OVERLAP_RECTANGLE_OUT) { #endif - path->is_stroke = is_stroke; - path->fill_rule = cairo_get_fill_rule (cr); - path->line_width = cairo_get_line_width (cr); - path->path = cairo_copy_path (cr); - path_foreach_point (path->path, user_to_device, cr); - path->func = func; - path->data = data; - path->next = area->priv->current_input->paths; - area->priv->current_input->paths = path; #if 0 } #endif @@ -1495,41 +1273,45 @@ add_path (FooScrollArea *area, * as well. */ void -foo_scroll_area_add_input_from_fill (FooScrollArea *scroll_area, +foo_scroll_area_add_input_from_fill (FooScrollArea *area, cairo_t *cr, FooScrollAreaEventFunc func, gpointer data) { - g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area)); + FooScrollAreaPrivate *priv = area->priv; + + g_return_if_fail (FOO_IS_SCROLL_AREA (area)); g_return_if_fail (cr != NULL); - g_return_if_fail (scroll_area->priv->current_input); - add_path (scroll_area, cr, FALSE, func, data); + add_path (area, cr, FALSE, func, data); } void -foo_scroll_area_add_input_from_stroke (FooScrollArea *scroll_area, +foo_scroll_area_add_input_from_stroke (FooScrollArea *area, cairo_t *cr, FooScrollAreaEventFunc func, gpointer data) { - g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area)); + FooScrollAreaPrivate *priv = area->priv; + + g_return_if_fail (FOO_IS_SCROLL_AREA (area)); g_return_if_fail (cr != NULL); - g_return_if_fail (scroll_area->priv->current_input); - add_path (scroll_area, cr, TRUE, func, data); + add_path (area, cr, TRUE, func, data); } void foo_scroll_area_invalidate_region (FooScrollArea *area, GdkRegion *region) { + FooScrollAreaPrivate *priv = area->priv; + g_return_if_fail (FOO_IS_SCROLL_AREA (area)); if (GTK_WIDGET_REALIZED (area)) { canvas_to_store (area, region); - backing_store_invalidate_region (area->priv->store, region); + backing_store_invalidate_region (priv->store, region); store_to_canvas (area, region); canvas_to_window (area, region); @@ -1539,7 +1321,7 @@ foo_scroll_area_invalidate_region (FooScrollArea *area, } void -foo_scroll_area_invalidate_rect (FooScrollArea *scroll_area, +foo_scroll_area_invalidate_rect (FooScrollArea *area, int x, int y, int width, @@ -1548,61 +1330,65 @@ foo_scroll_area_invalidate_rect (FooScrollArea *scroll_area, GdkRectangle rect = { x, y, width, height }; GdkRegion *region; - g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area)); + g_return_if_fail (FOO_IS_SCROLL_AREA (area)); region = gdk_region_rectangle (&rect); - foo_scroll_area_invalidate_region (scroll_area, region); + foo_scroll_area_invalidate_region (area, region); gdk_region_destroy (region); } void -foo_scroll_area_invalidate (FooScrollArea *scroll_area) +foo_scroll_area_invalidate (FooScrollArea *area) { - GtkWidget *widget = GTK_WIDGET (scroll_area); + FooScrollAreaPrivate *priv = area->priv; + GtkWidget *widget = GTK_WIDGET (area); - foo_scroll_area_invalidate_rect (scroll_area, - scroll_area->priv->x_offset, scroll_area->priv->y_offset, + foo_scroll_area_invalidate_rect (area, + priv->x_offset, priv->y_offset, widget->allocation.width, widget->allocation.height); } void -foo_scroll_area_begin_grab (FooScrollArea *scroll_area, +foo_scroll_area_begin_grab (FooScrollArea *area, FooScrollAreaEventFunc func, gpointer input_data) { - g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area)); + g_return_if_fail (FOO_IS_SCROLL_AREA (area)); - begin_grab_internal (scroll_area, FALSE, func, input_data); + begin_grab_internal (area, FALSE, func, input_data); } void -foo_scroll_area_end_grab (FooScrollArea *scroll_area) +foo_scroll_area_end_grab (FooScrollArea *area) { - g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area)); + g_return_if_fail (FOO_IS_SCROLL_AREA (area)); - end_grab_internal (scroll_area, FALSE); + end_grab_internal (area, FALSE); } gboolean -foo_scroll_area_is_grabbed (FooScrollArea *scroll_area) +foo_scroll_area_is_grabbed (FooScrollArea *area) { - return scroll_area->priv->grabbed; + FooScrollAreaPrivate *priv = area->priv; + + return priv->grabbed; } void -foo_scroll_area_set_viewport_pos (FooScrollArea *scroll_area, +foo_scroll_area_set_viewport_pos (FooScrollArea *area, int x, int y) { + FooScrollAreaPrivate *priv = area->priv; int x_changed; int y_changed; GtkAdjustment *hadj, *vadj; - hadj = scroll_area->priv->hadj; - vadj = scroll_area->priv->vadj; + hadj = priv->hadj; + vadj = priv->vadj; x_changed = (int)hadj->value != x; y_changed = (int)vadj->value != y; @@ -1610,7 +1396,7 @@ foo_scroll_area_set_viewport_pos (FooScrollArea *scroll_area, hadj->value = x; vadj->value = y; - set_adjustment_values (scroll_area); + set_adjustment_values (area); if (x_changed && hadj->upper >= hadj->page_size) { @@ -1641,16 +1427,20 @@ rect_contains (const GdkRectangle *rect, int x, int y) static void stop_scrolling (FooScrollArea *area) { + FooScrollAreaPrivate *priv = area->priv; #if 0 g_print ("stop scrolling\n"); #endif - if (area->priv->auto_scroll_info) + if (priv->auto_scroll_info) { - g_source_remove (area->priv->auto_scroll_info->timeout_id); - g_timer_destroy (area->priv->auto_scroll_info->timer); - g_free (area->priv->auto_scroll_info); + g_source_remove (priv->auto_scroll_info->timeout_id); +#if 0 + g_print ("removed idle\n"); +#endif + g_timer_destroy (priv->auto_scroll_info->timer); + g_free (priv->auto_scroll_info); - area->priv->auto_scroll_info = NULL; + priv->auto_scroll_info = NULL; } } @@ -1659,12 +1449,17 @@ scroll_idle (gpointer data) { GdkRectangle viewport, new_viewport; FooScrollArea *area = data; - AutoScrollInfo *info = area->priv->auto_scroll_info; + FooScrollAreaPrivate *priv = area->priv; + AutoScrollInfo *info = priv->auto_scroll_info; #if 0 int dx, dy; #endif int new_x, new_y; double elapsed; + +#if 0 + g_print ("idle\n"); +#endif get_viewport (area, &viewport); @@ -1682,6 +1477,12 @@ scroll_idle (gpointer data) #if 0 g_print ("new info %d %d\n", info->dx, info->dy); #endif + + if (priv->need_flush) + { + gdk_flush (); + priv->need_flush = FALSE; + } elapsed = g_timer_elapsed (info->timer, NULL); @@ -1689,7 +1490,7 @@ scroll_idle (gpointer data) info->res_y = elapsed * info->dy / 0.2; #if 0 - g_print ("%f %f\n", info->res_x, info->res_y); + g_print ("%f: %f %f\n", elapsed, info->res_x, info->res_y); #endif new_x = viewport.x + info->res_x; @@ -1702,29 +1503,25 @@ scroll_idle (gpointer data) #if 0 g_print ("new_x, new_y\n: %d %d\n", new_x, new_y); #endif - - foo_scroll_area_set_viewport_pos (area, new_x, new_y); -#if 0 - viewport.x + info->dx, - viewport.y + info->dy); -#endif -get_viewport (area, &new_viewport); + foo_scroll_area_set_viewport_pos (area, new_x, new_y); -if (viewport.x == new_viewport.x && - viewport.y == new_viewport.y && - (info->res_x > 1.0 || - info->res_y > 1.0 || - info->res_x < -1.0 || - info->res_y < -1.0)) -{ - stop_scrolling (area); + get_viewport (area, &new_viewport); - /* stop scrolling if it didn't have an effect */ - return FALSE; -} - -return TRUE; + if (viewport.x == new_viewport.x && + viewport.y == new_viewport.y && + (info->res_x > 1.0 || + info->res_y > 1.0 || + info->res_x < -1.0 || + info->res_y < -1.0)) + { + stop_scrolling (area); + + /* stop scrolling if it didn't have an effect */ + return FALSE; + } + + return TRUE; } static void @@ -1732,15 +1529,20 @@ ensure_scrolling (FooScrollArea *area, int dx, int dy) { - if (!area->priv->auto_scroll_info) + FooScrollAreaPrivate *priv = area->priv; + + if (!priv->auto_scroll_info) { #if 0 g_print ("start scrolling\n"); #endif - area->priv->auto_scroll_info = g_new0 (AutoScrollInfo, 1); - area->priv->auto_scroll_info->timeout_id = + priv->auto_scroll_info = g_new0 (AutoScrollInfo, 1); + priv->auto_scroll_info->timeout_id = g_idle_add (scroll_idle, area); - area->priv->auto_scroll_info->timer = g_timer_new (); +#if 0 + g_print ("added idle\n"); +#endif + priv->auto_scroll_info->timer = g_timer_new (); } #if 0 @@ -1751,19 +1553,19 @@ ensure_scrolling (FooScrollArea *area, g_print ("dx, dy: %d %d\n", dx, dy); #endif - area->priv->auto_scroll_info->dx = dx; - area->priv->auto_scroll_info->dy = dy; + priv->auto_scroll_info->dx = dx; + priv->auto_scroll_info->dy = dy; } void -foo_scroll_area_auto_scroll (FooScrollArea *scroll_area, +foo_scroll_area_auto_scroll (FooScrollArea *area, FooScrollAreaEvent *event) { #define SCROLL_MARGIN 4 GdkRectangle viewport; - get_viewport (scroll_area, &viewport); + get_viewport (area, &viewport); viewport.x += SCROLL_MARGIN; viewport.y += SCROLL_MARGIN; @@ -1776,7 +1578,7 @@ foo_scroll_area_auto_scroll (FooScrollArea *scroll_area, g_print ("in viewport\n"); #endif - stop_scrolling (scroll_area); + stop_scrolling (area); } else { @@ -1815,18 +1617,27 @@ foo_scroll_area_auto_scroll (FooScrollArea *scroll_area, g_print ("dx, dy: %d %d\n", dx, dy); #endif - ensure_scrolling (scroll_area, dx, dy); + ensure_scrolling (area, dx, dy); } } void -foo_scroll_area_begin_auto_scroll (FooScrollArea *scroll_area) +foo_scroll_area_begin_auto_scroll (FooScrollArea *area) { /* noop for now */ } void -foo_scroll_area_end_auto_scroll (FooScrollArea *scroll_area) +foo_scroll_area_end_auto_scroll (FooScrollArea *area) +{ + stop_scrolling (area); +} + +void +foo_scroll_area_set_paint_updates (FooScrollArea *area, + gboolean paint_upd) { - stop_scrolling (scroll_area); + FooScrollAreaPrivate *priv = area->priv; + + priv->paint_updates = paint_upd; } diff --git a/scrollarea.h b/scrollarea.h index 57b0eaf..205a667 100644 --- a/scrollarea.h +++ b/scrollarea.h @@ -138,4 +138,9 @@ void foo_scroll_area_auto_scroll (FooScrollArea *sc void foo_scroll_area_end_auto_scroll (FooScrollArea *scroll_area); +/* Debug */ +void foo_scroll_area_set_paint_updates (FooScrollArea *scroll_area, + gboolean paint_upd); + + #endif @@ -16,15 +16,15 @@ typedef struct static const ToolInfo tools[] = { - { "T", NULL }, - { "R", NULL }, - { "E", NULL }, - { "P", NULL }, - { "L", NULL }, - { "I", NULL }, - { "H", NULL }, - { "V", NULL }, - { "N", NULL }, + { "T", NULL }, /* text */ + { "R", NULL }, /* rectangle */ + { "E", NULL }, /* ellipse */ + { "P", NULL }, /* polygon */ + { "L", NULL }, /* line */ + { "I", NULL }, /* image */ + { "H", NULL }, /* horizontal helper line */ + { "V", NULL }, /* vertical helper line */ + { "N", NULL }, /* post-it note */ }; struct Toolbar |