I took the bait and coded my solution that I (in lack of anything better
called) dovtk-lasso . I'm including it below including a test program.

I think this solution is even simpler than your old xor solution. All you
need to do is to create the draw_callback fuction that uses whatever cairo
painting you want to draw your overlay. Note that this function is called
twice, once to extract where you are drawing, and once to do the drawing.
During the first time the do_mask variable is on. In this case you should
choose a thicker pen and draw in black, to make sure that everything is
included.

Note that there is one constraint that I have not been able to solve. The
expose-event callback that is doing the actual drawing of your graphics have
to return FALSE. I would be happy to receive a solution for it.

I promise to put this code into git-hub as soon as possible.

//======================================================================

//  test-gtk-lasso.c - This example is in the public domain

//

//  Dov Grobgeld <dov.grobg...@gmail.com>

//  Mon Aug 16 09:09:56 2010

//----------------------------------------------------------------------

 #include <stdlib.h>

#include <gtk/gtk.h>

#include <math.h>

#include "dovtk-lasso.h"

 DovtkLasso *lasso = NULL;

int start_x, start_y, end_x, end_y;

 int cb_expose(GtkWidget      *widget,

              GdkEventExpose *event,

              gpointer        user_data)

{

    cairo_t *cr;

    cr = gdk_cairo_create(widget->window);

    cairo_rectangle(cr, event->area.x, event->area.y,

                    event->area.width, event->area.height);

    cairo_clip(cr);

     // Just draw anything in the widget

    double x, y;

    x = widget->allocation.x + widget->allocation.width / 2;

    y = widget->allocation.y + widget->allocation.height / 2;

    double radius;

    radius = MIN (widget->allocation.width / 2,

                  widget->allocation.height / 2) - 5;

     cairo_set_source_rgb(cr, 0,0,0);

    cairo_arc (cr, x, y, radius, 0, 2 * M_PI);

    cairo_stroke(cr);

    cairo_destroy(cr);

     return FALSE;

}

 /**

 * Draw  whatever overlay you want on the image. If the do_mask

 * is on, then you should paint in black and with a pen that

 * is thicker than the drawing.

 */

void my_lasso_draw(cairo_t *cr,

                   gboolean do_mask,

                   // output

                   DovtkLassoRectangleList **rect_list)

{

    int min_x = MIN(start_x, end_x);

    int min_y = MIN(start_y, end_y);

     if (!do_mask) {

        cairo_set_source_rgb(cr, 1,0,0);

        cairo_set_line_width(cr,1);

    }

    else

        cairo_set_line_width(cr, 5);

     cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);

     // Draw a rectangle

    cairo_rectangle(cr, min_x, min_y, abs(end_x-start_x), abs(end_y-start_y));

     cairo_stroke(cr);

}

 int cb_button_press(GtkWidget      *widget,

                    GdkEventButton *event,

                    gpointer        user_data)

{

    lasso = dovtk_lasso_create(widget,

                               &my_lasso_draw,

                               TRUE);

    start_x = event->x;

    start_y = event->y;

     return FALSE;

}

 int cb_button_release(GtkWidget      *widget,

                      GdkEventButton *event,

                      gpointer        user_data)

{

    dovtk_lasso_destroy(lasso);

    lasso = NULL;

    return FALSE;

}

 int cb_motion_notify(GtkWidget      *widget,

                     GdkEventMotion *event,

                     gpointer        user_data)

{

    //    printf("button motion\n");

    end_x = event->x;

    end_y = event->y;

     dovtk_lasso_update(lasso);

     return FALSE;

}

 int main(int argc, char *argv[])

{

    gtk_init(&argc, &argv);

    GtkWidget *w_top = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    g_signal_connect(G_OBJECT(w_top), "delete-event",

                     G_CALLBACK(gtk_main_quit), NULL);

     GtkWidget *w_draw = gtk_drawing_area_new();

    gtk_container_add(GTK_CONTAINER(w_top),

                      w_draw);

    gtk_widget_set_size_request(w_draw, 500,500);

    g_signal_connect(G_OBJECT(w_draw), "expose-event",

                     G_CALLBACK(cb_expose), NULL);

     // TBD - set up events for lasso

    gtk_widget_add_events(w_draw,

                          GDK_BUTTON_MOTION_MASK

                          | GDK_BUTTON_PRESS_MASK

                          | GDK_BUTTON_RELEASE_MASK);

    g_signal_connect(G_OBJECT(w_draw), "button-press-event",

                     G_CALLBACK(cb_button_press), NULL);

    g_signal_connect(G_OBJECT(w_draw), "button-release-event",

                     G_CALLBACK(cb_button_release), NULL);

    g_signal_connect(G_OBJECT(w_draw), "motion-notify-event",

                     G_CALLBACK(cb_motion_notify), NULL);

     gtk_widget_show_all(w_top);

    gtk_main();

     return 0;

}

--------------------

/**

 * dovtk-lasso.h

 *

 * A solution for drawing overlays on a gtk widget.

 *

 * This code is relased under the LGPL v2.0.

 *

 * Copyright Dov Grobgeld <dov.grobg...@gmail.com> 2010

 *

 */

#ifndef DOVTK_H

#define DOVTK_H

 #include <gtk/gtk.h>

 typedef struct {

} DovtkLasso;

 typedef struct {

    int num_rectangles;

    cairo_rectangle_t *rectangles;

} DovtkLassoRectangleList;

 typedef void (*DovtkLassoDrawing)(cairo_t *cr,

                                  gboolean do_mask,

                                  // output

                                  DovtkLassoRectangleList **rect_list);

 DovtkLasso *dovtk_lasso_create(GtkWidget *widget,

                               DovtkLassoDrawing drawing_cb,

                               gboolean do_calc_expose_from_cairo);

 /**

 * Called when the coordinates of the lasso were changed.

 *

 * @param lasso

 */

void dovtk_lasso_update(DovtkLasso *lasso);

 void dovtk_lasso_destroy(DovtkLasso *lasso);

 DovtkLassoRectangleList *dovtk_lasso_rectangle_list_new(int num_rectangles);

void dovtk_lasso_rectangle_list_destroy(DovtkLassoRectangleList
*rectangcle_list);

#endif /* DOVTK */

 /**

 * dovtk-lasso.c

 *

 * A solution for drawing overlays on a gtk widget.

 *

 * This code is relased under the LGPL v2.0.

 *

 * Copyright Dov Grobgeld <dov.grobg...@gmail.com> 2010

 *

 */

#include "dovtk-lasso.h"

 typedef struct {

    DovtkLasso parent;

    gulong expose_handler_id;

    GtkWidget *widget;

    DovtkLassoDrawing drawing_cb;

    gboolean do_calc_expose_from_cairo;

    DovtkLassoRectangleList *old_rect_list;

} DovtkLassoPrivate ;

 static int lasso_cb_expose(GtkWidget      *widget,

                           GdkEventExpose *event,

                           gpointer        user_data);

 DovtkLasso *dovtk_lasso_create(GtkWidget *widget,

                               DovtkLassoDrawing drawing_cb,

                               gboolean do_calc_expose_from_cairo)

{

    DovtkLassoPrivate *selfp = g_new0(DovtkLassoPrivate, 1);

     // This binding doesn't work if the default expose handler

    // returns TRUE!

    selfp->expose_handler_id

        = g_signal_connect(widget,

                           "expose-event",

                           G_CALLBACK(lasso_cb_expose),

                           selfp);

    selfp->widget = widget;

    selfp->drawing_cb = drawing_cb;

    selfp->do_calc_expose_from_cairo = do_calc_expose_from_cairo;

    // Create an empty list so that we can free it

    selfp->old_rect_list = dovtk_lasso_rectangle_list_new(0);

    return (DovtkLasso*)selfp;

}

 void dovtk_lasso_destroy(DovtkLasso *lasso)

{

    DovtkLassoPrivate *selfp = (DovtkLassoPrivate*)lasso;

    g_signal_handler_disconnect(selfp->widget,

                                selfp->expose_handler_id);

    // This gets rid of the overlay. Is this always needed?

    dovtk_lasso_update(lasso);

     dovtk_lasso_rectangle_list_destroy(selfp->old_rect_list);

     g_free(lasso);

}

 static int lasso_cb_expose(GtkWidget      *widget,

                           GdkEventExpose *event,

                           gpointer        user_data)

{

    DovtkLassoPrivate *selfp = (DovtkLassoPrivate*)user_data;

    //    printf("dovtk-lasso.c: expose\n");

     g_signal_handler_block(widget, selfp->expose_handler_id);

    int retval;

    g_signal_emit_by_name (widget, "expose-event", event, &retval);

    g_signal_handler_unblock(widget, selfp->expose_handler_id);

     cairo_t *cr;

    cr = gdk_cairo_create(widget->window);

    cairo_rectangle(cr, event->area.x, event->area.y,

                    event->area.width, event->area.height);

    cairo_clip(cr);

     DovtkLassoRectangleList *rect_list = NULL;

    selfp->drawing_cb(cr, FALSE, &rect_list);

     cairo_destroy(cr);

     return TRUE;

}

 int a8_idx=0;

 void dovtk_lasso_update(DovtkLasso *lasso)

{

    DovtkLassoPrivate *selfp = (DovtkLassoPrivate*)lasso;

     // Call drawing_cb to and use it to generate the rectangle list

    DovtkLassoRectangleList *rect_list = NULL;

    int scale_factor = 32;

    int low_res_width =
(selfp->widget->allocation.width+scale_factor-1) / scale_factor;

    int low_res_height =
(selfp->widget->allocation.height+scale_factor-1) / scale_factor;

     int i;

     // This should be created in the creation of DovtkLasso

    cairo_t *cr = NULL;

    cairo_surface_t *surf = NULL;

     if (selfp->do_calc_expose_from_cairo) {

        surf=cairo_image_surface_create(CAIRO_FORMAT_ARGB32,

                                        low_res_width,

                                        low_res_height);

        cr = cairo_create(surf);

        cairo_set_source_rgba(cr,0,0,0,0);

        cairo_rectangle(cr, 0,0,low_res_height,low_res_width);

        cairo_fill(cr);

        cairo_set_source_rgba(cr,0,0,0,1);

    }

    cairo_scale(cr,1.0/scale_factor,1.0/scale_factor);

    selfp->drawing_cb(cr, TRUE, &rect_list);

#if 0

    char filename[64];

    sprintf(filename, "/tmp/a8-%04d.png", a8_idx++);

    cairo_surface_write_to_png(surf, filename);

#endif

     // TBD - Turn surf into a list of rectangles

    if (selfp->do_calc_expose_from_cairo) {

        int row_idx, col_idx;

         // Allocate a lot of space

        rect_list =
dovtk_lasso_rectangle_list_new(low_res_width*low_res_height);

         guint8 *buf = cairo_image_surface_get_data(surf);

        int rect_idx = 0;

        int row_stride = cairo_image_surface_get_stride(surf);

        for (row_idx=0; row_idx<low_res_height; row_idx++) {

            for (col_idx=0; col_idx<low_res_width; col_idx++) {

                if (*(buf + row_stride * row_idx + col_idx * 4+3) > 0) {

                    cairo_rectangle_t *rect =
&rect_list->rectangles[rect_idx++];

                    rect->x = col_idx*scale_factor;

                    rect->y = row_idx*scale_factor;

                    rect->width = scale_factor;

                    rect->height = scale_factor;

                }

            }

        }

        rect_list->num_rectangles = rect_idx;

         cairo_destroy(cr);

        cairo_surface_destroy(surf);

    }

    //    printf("num_rectangles = %d\n", rect_list->num_rectangles);

     // Build a list of expose rectangles from the old and the new lists.

    // Better done as a linked list.

    DovtkLassoRectangleList *expose_rect_list

        = dovtk_lasso_rectangle_list_new(selfp->old_rect_list->num_rectangles

                                         + rect_list->num_rectangles);

    int num_old_rects = selfp->old_rect_list->num_rectangles;

    for (i=0; i<num_old_rects; i++)

        expose_rect_list->rectangles[i] = selfp->old_rect_list->rectangles[i];

    for (i=0; i<rect_list->num_rectangles; i++)

        expose_rect_list->rectangles[num_old_rects + i] =
rect_list->rectangles[i];

     // Expose the old and the new list of rectangles!

    for (i=0; i<expose_rect_list->num_rectangles; i++) {

        // Shortcut

        cairo_rectangle_t *lasso_rect = &expose_rect_list->rectangles[i];

         GdkRectangle rect;

        rect.x = lasso_rect->x;

        rect.y = lasso_rect->y;

        rect.width = lasso_rect->width;

        rect.height = lasso_rect->height;


        gdk_window_invalidate_rect(selfp->widget->window,

                                   &rect,

                                   TRUE);

    }

    dovtk_lasso_rectangle_list_destroy(expose_rect_list);

     dovtk_lasso_rectangle_list_destroy(selfp->old_rect_list);

    selfp->old_rect_list = rect_list;

}

 DovtkLassoRectangleList *dovtk_lasso_rectangle_list_new(int num_rectangles)

{

    DovtkLassoRectangleList *rectangle_list =
g_new0(DovtkLassoRectangleList, 1);

    rectangle_list->num_rectangles = num_rectangles;

    rectangle_list->rectangles = g_new0(cairo_rectangle_t, num_rectangles);

    return rectangle_list;

}

 void dovtk_lasso_rectangle_list_destroy(DovtkLassoRectangleList
*rectangle_list)

{

    g_free(rectangle_list->rectangles);

    g_free(rectangle_list);

}



On Wed, Aug 11, 2010 at 20:12, Allin Cottrell <cottr...@wfu.edu> wrote:

> On Wed, 11 Aug 2010 jcup...@gmail.com wrote:
>
> >
> > On 11 August 2010 02:14, Allin Cottrell <cottr...@wfu.edu> wrote:
> > > rid of GdkGC.  I'd imagine that the effect I'm after is something
> > > that many GTP apps have need of, and it's trivial to achieve with
> > > the GDK API.
> >
> > I've found that XOR rubber banding is quite hard to do reliably in
> > gdk.  The problem I think is that you are using the screen pixels to
> > store part of the state, and that gets hard to coordinate between the
> > various things that can affect the display.
> >
> > I had mouse movements triggering rect moves, mouse moves with a button
> > held down causing background scrolling of the canvas, and mouse moves
> > over other screen objects triggering highlight effects. With all these
> > things going on at once it became very difficult to get XOR rubber
> > bands to not leave annoying trails as they moved.
> >
> > I switched to an update-model / invalidate-widget / redraw-on-idle
> > scheme as Dov suggests and I got prettier updates with less
> > complication. Since input and output are decoupled, it'll scale more
> > gracefully between slower and faster machines as well, which is nice.
>
> My drawing case may be simpler than yours -- there's nothing
> scrollable in the vicinity -- but I've found that rubber-banding
> using GDK_INVERT to "undraw" the last box works flawlessly at
> low programming cost.
>
> But can you suggest a good example to look at for the alternative
> approach? Thanks.
>
> Allin Cottrell
> _______________________________________________
> gtk-app-devel-list mailing list
> gtk-app-devel-list@gnome.org
> http://mail.gnome.org/mailman/listinfo/gtk-app-devel-list
>
_______________________________________________
gtk-app-devel-list mailing list
gtk-app-devel-list@gnome.org
http://mail.gnome.org/mailman/listinfo/gtk-app-devel-list

Reply via email to