Re: GdkDrawable from GtkDrawingArea and it's cairo surface



hi,

i do this same sort of thing in my stuff, but written before cairo was.  i plan on converting all my drawing to cairo one day, thus must eventually solve this problem myself.  so i took your original sample/example and expanded on it to demonstrate pixmap backing stores and how to use/reference them when wanting to "undo" things relating to drawing areas.

attached is the same sample, but with the following mods/additions:
but i deliver this with a huge caveat: i do not like the solution, something about the design is wrong (i can "feel" it).  so, this reference is intended for demonstration purposes only!  i will eventually come back to this and find the proper architecture, but don't have time at the moment.

it does, however, "work", so it's not completely worthless; the best answer is in there somewhere.

and just for the record, there really is many, many ways to solve this problem; you must strive to find the most appropriate solution that satisfies all the specific requirements of your particular problem, and sometimes this simply comes down to solving it over and over until the best solution becomes obvious.

cheers,

richard


2011/7/24 Carlos López Camey <c lopez kmels net>
Hello again,

Let's say I'd like to save a GtkDrawingArea 'screenshot' to a file.
I'm able to do

cairo_t *cr = gtkDrawingAreaWidget->window;
cairo_surface_t *screenshot = cairo_get_target(cr);
cairo_surface_write_to_png(screenshot,"screenshot.png");

but what this code does is save a screenshot for the *entire window*,
including other widgets in it (e.g. toolbar). I can't get a cairo
context for a GtkDrawingArea since it's not GdkDrawable, and it's not
possible to have child GtkWindow. I've seen
gtk_widget_set_has_window() but it didn't work, I think because it
says "This function should only be called by widget implementations".
Is there a work-around?

What I'm really trying to do: Save my drawing area surface everytime
it's modified (drawn on it), so I can later do "undos" and set the
surface source to a previous one.

Thanks,
Carlos
_______________________________________________
gtk-list mailing list
gtk-list gnome org
http://mail.gnome.org/mailman/listinfo/gtk-list

#include <string.h>
#include <gtk/gtk.h>

enum {
  DA_CFG,
  DA_EXP,
  MOUSE_CLICK,
  MOUSE_DRAG,
  TTL_DA_EVENTS
};

enum {
  B_DRAW,
  B_UNDO,
  TTL_BUTTONS
};

GtkWidget  *daG;
GdkPixmap *pmap;      // which pixmap expose event should draw
GSList  *pmaps;       // list of pixmaps, data = GdkPixmap*
static gint w, h;     // drawing area size
static gboolean allowed;  // canvas can be drawn on?

static inline void delPmaps()
{ // free entire list of pixmap backing stores
  GSList  *iter;
  for (iter = pmaps;
       iter;
       iter = g_slist_next(iter))
  {
    GdkPixmap *pmap = iter->data;
    g_object_unref(pmap);
  }
  g_slist_free(pmaps);
  pmaps = NULL;
}

static gboolean da_cfgexp(GtkWidget *da, void *e, gpointer t)
{  // callback for drawing areas - configure and expose event
  gint  type = GPOINTER_TO_INT(t);

  switch(type)
  {
    case DA_CFG:
    { // configure event
//      GdkEventConfigure *event = (GdkEventConfigure *) e;
      GdkPixmap *pmap;
      w = da->allocation.width, h = da->allocation.height;

      delPmaps();
      pmap = gdk_pixmap_new(da->window, w, h, -1);
      gdk_draw_rectangle(pmap, da->style->bg_gc[GTK_WIDGET_STATE(da)], TRUE, 0, 0, w, h);
      pmaps = g_slist_prepend(pmaps, pmap);
      daG = da;
    }
    break;

    case DA_EXP:
    { // expose event
      GdkEventExpose *event = (GdkEventExpose *) e;
      GdkPixmap *p = pmap ? pmap : (GdkPixmap *) pmaps->data;
      gdk_draw_drawable(da->window,
              da->style->fg_gc[GTK_WIDGET_STATE(da)],
              p,
              event->area.x, event->area.y,
              event->area.x, event->area.y,
              event->area.width, event->area.height);
    }
    break;
  }
  return TRUE;
}

static inline void brush(cairo_t *cr, double x1, double y1, double x2, double y2)
{
  cairo_move_to(cr, x1, y1);
  cairo_line_to(cr, x2, y2);
  cairo_stroke(cr);
}

static gboolean handle_mouse(GtkWidget *da, void *e, gpointer t)
{
  gint  type = GPOINTER_TO_INT(t);
  static cairo_surface_t *cst;

  static struct {
    gboolean isdragging;
    cairo_t *cr;
    double x, y;
  } mouseState;
  
  switch(type)
  {
    case MOUSE_CLICK:
    {
      GdkEventButton *event = (GdkEventButton*) e;
      if (!allowed)
        break;

      switch(event->type)
      {
        case GDK_BUTTON_PRESS:
        {
          cairo_t *cr;
          mouseState.isdragging = TRUE;
          mouseState.x = event->x;    mouseState.y = event->y;

          cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
          cr = cairo_create(cst);
          cairo_set_source_rgb(cr, 0, 0, 0);
          cairo_set_line_width(cr, 2);

          mouseState.cr = cr;
        }
        break;

        case GDK_BUTTON_RELEASE:
        {
          allowed = FALSE;
          cairo_destroy(mouseState.cr);
          cairo_surface_destroy(cst);
          memset(&mouseState, 0, sizeof(mouseState));
        }
        break;
        default:
        break;
      }
    }
    break;
  
    case MOUSE_DRAG:
    {
      GdkEventMotion *event = (GdkEventMotion*) e;
      static  GdkWindow *gdkWindow;
      GdkPixmap *pmap;

      if (!gdkWindow)
      {  // initialize
        gdkWindow = gdk_get_default_root_window();
        memset(&mouseState, 0, sizeof(mouseState));
        break;
      }

      if (!mouseState.isdragging ||
        (mouseState.x == event->x && mouseState.y == event->y))
        break;

      // draw the line
      brush(mouseState.cr, mouseState.x, mouseState.y, event->x, event->y);

      // copy to pixmap
      pmap = gdk_pixmap_new(da->window, w, h, -1);
      gdk_draw_drawable(pmap, da->style->fg_gc[GTK_STATE_NORMAL], pmaps->data, 0, 0, 0, 0, -1, -1);

      cairo_t *cr_pixmap = gdk_cairo_create(pmap);
      cairo_set_source_surface(cr_pixmap, cst, 0, 0);
      cairo_paint(cr_pixmap);
      cairo_destroy(cr_pixmap);
      // save to our list of pixmaps
      pmaps = g_slist_prepend(pmaps, pmap);

      // save last coordinates
      mouseState.x = event->x;    mouseState.y = event->y;

      gtk_widget_queue_draw_area(da, 0, 0, w, h);    // force the expose event

      {
        // even though we don't use the resulting information from this call, 
        // calling it is an indication to the main_loop() 
        // that we are ready to receive the next mouse motion notify event
        gint x, y;
        GdkModifierType state;
        gdk_window_get_pointer(gdkWindow, &x, &y, &state);
      }
    }
    break;
  }

  return TRUE;
}

static gboolean undo(gpointer n)
{
  gboolean ret;
  static GSList *iter;

  // set the return (TRUE = call again, FALSE = stop timer)
  // TRUE = next pixmap to draw exists
  // FALSE = no more pixmaps, we're done
  ret = (iter = iter ? g_slist_next(iter) : pmaps) ? TRUE : FALSE;

  // set the (global) next pixmap to be drawn
  pmap = ret ? iter->data : NULL;

  gtk_widget_queue_draw_area(daG, 0, 0, w, h);    // force the expose event

  return (ret);
}

static void buttonCback(GtkButton *b, gpointer t)
{
  gint  type = GPOINTER_TO_INT(t);
  switch (type)
  {
    case B_DRAW:
      allowed = TRUE;
    break;
    case B_UNDO:
				g_timeout_add(50, (GSourceFunc) undo, NULL);
    break;
  }
}

int main( int argc,
          char *argv[] )
{
  GtkWidget *window, *frame;
  GtkWidget *main_vbox, *da, *hbox, *button;

  gtk_init (&argc, &argv);
  
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_main_quit), "WM destroy");
  gtk_widget_set_usize (GTK_WIDGET(window), 300, 200);
  
  main_vbox = gtk_vbox_new (FALSE, 1);
  gtk_container_border_width (GTK_CONTAINER (main_vbox), 1);
  gtk_container_add (GTK_CONTAINER (window), main_vbox);

  frame = gtk_frame_new("Drawing Sample");
  gtk_box_pack_start(GTK_BOX (main_vbox), frame, TRUE, TRUE, 3);

  da = gtk_drawing_area_new();
  
  gtk_widget_add_events(da, GDK_ALL_EVENTS_MASK);
  g_signal_connect(da, "configure-event", G_CALLBACK(da_cfgexp), GINT_TO_POINTER(DA_CFG));
  g_signal_connect(da, "expose-event", G_CALLBACK(da_cfgexp), GINT_TO_POINTER(DA_EXP));
  g_signal_connect(da, "button-press-event", G_CALLBACK(handle_mouse), GINT_TO_POINTER(MOUSE_CLICK));
  g_signal_connect(da, "button-release-event", G_CALLBACK(handle_mouse), GINT_TO_POINTER(MOUSE_CLICK));
  g_signal_connect(da, "motion-notify-event",G_CALLBACK(handle_mouse), GINT_TO_POINTER(MOUSE_DRAG));
  gtk_container_add (GTK_CONTAINER (frame), da);

  hbox = gtk_hbox_new(FALSE, 1);
  gtk_box_pack_start(GTK_BOX (main_vbox), hbox, FALSE, FALSE, 3);
  gtk_widget_set_size_request(hbox, -1, 30);
  button = gtk_button_new_with_label("Draw");
  g_signal_connect(button, "clicked", G_CALLBACK(buttonCback), GINT_TO_POINTER(B_DRAW));
  gtk_container_add (GTK_CONTAINER (hbox), button);
  button = gtk_button_new_with_label("UNDO");
  g_signal_connect(button, "clicked", G_CALLBACK(buttonCback), GINT_TO_POINTER(B_UNDO));
  gtk_container_add (GTK_CONTAINER (hbox), button);
  
  //ends gtkdrawable + cairo example    
  gtk_widget_show_all (window);
  gtk_main ();
  
  return(0);
}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]