Crear objetos propios

Lo más interesante del GnomeCanvas es poder crear objetos propios, ya que con ello se puede ajustar el canvas a las neceidades de cada aplicación.

Todos los objetos que se creen deben derivar de GnomeCanvasItem o de alguna clase que derive de GnomeCanvasItem.

Ejemplo 7. Objeto propio

typedef struct _MyItem MyItem;

struct _MyItem
{
        GnomeCanvasItem item_class;

        /* datos propios */
};
  

Métodos

Todos los objetos deben dar una serie de métodos para que el canvas pueda interactuar con ellos, bien para que realicen alguna acción (como dibujarse) o para que den información (como su posición en el canvas).

Para dar tal funcionalidad a los objetos se deben sobrescribir los valores de los miembros de GnomeCanvasItem, los cuales se explican a continuación

point

El método point se usa para calcular qué objeto está en un determinado punto. Este método devolverá la distancia que hay entre el objeto y el punto y, si el punto está dentro del objeto, devolverá cero. El valor exacto no es muy importante, ya que el principal uso de este método es ver si un punto está dentro del objeto (devuelve cero) o no (devuelve un valor distinto de cero).

struct _GnomeCanvasItemClass {
        GtkObjectClass parent_class;

/* ... */

        double (* point) (GnomeCanvasItem *item, double x, double y,
                int cx, int cy, GnomeCanvasItem **actual_item);
};

  

Los argumentos x e y corresponden al punto en unidades del canvas, mientas que cx y cy corresponden al mismo punto, pero en pixels del canvas.

El último argumento, actual_item, debe rellenarse siempre.

Ejemplo 8. Crear un evento point

  
  

bounds

Este no lo usa internamente el canvas, y sólo es requerido por gnome_canvas_item_get_bounds. Este evento calcula el rectángulo que encuadra al objeto.


struct _GnomeCanvasItemClass {
        GtkObjectClass parent_class;

/* ... */

        double (* point) (GnomeCanvasItem *item, double x, double y,
                int cx, int cy, GnomeCanvasItem **actual_item);

        void (* bounds) (GnomeCanvasItem *item, double *x1, double *y1,
                double *x2, double *y2);
};
  
void gnome_canvas_item_get_bounds(item,  
 x1,  
 y1,  
 x2,  
 y2); 
GnomeCanvasItem*  item;
double*  x1;
double*  y1;
double*  x2;
double*  y2;

Puesto que no es usado internamente por el canvas, no es necesario implementar este evento.

event

Este evento hace lo mismo que la señal event. La única diferencia es que con este evento no es necesario conectar ninguna señal

struct _GnomeCanvasItemClass
{
        GtkObjectClass parent_class;

/* ... */

        gboolean (* event)  (GnomeCanvasItem *item, GdkEvent *event);
};
  

En el apartado Recibiendo eventos y los siguientes hay una descripción de event.

realizing y mapping

Los métodos realize, unrealize, map y unmap tienen el mismo significado que las señales equivalentes en los widgets.

realize se usa para crear todos los recursos GDK que el objeto del canvas quiera usar, y unrealize es llamado cuando esos recursos deben ser liberados.

map y unmap son usados sólo por objetos que tengan una GdkWindow asociada con ellos. Esto es poco habitual, aunque hay excepciones como GnomeCanvasWidget. map es invocado cuando su GdkWindow es mostrada, y unmap cuando se oculta.

Ejemplo 9. realize y unrealize

GnomeCanvasItemClass * my_item_parent_class;

typedef struct {
        GnomeCanvasItem item;

        GdkDC *bg_gc;
} MyItem;

typedef struct {
        GnomeCanvasItemClass parent_class;

} MyItemClass;

/* ... */

void
my_item_realize(GnomeCanvasItem* item)
{
        MyItem* my_item = MY_ITEM(item);

        /* Llamar a la función de la clase GnomeCanvasItemClass.
         * Para obtener la dirección de la clase se llama a g_type_class_peek_parent
         * durante la inicialización de la clase MyItemClass;
         */
        if (my_item_parent_class->realize)
                (* my_item_parent_class->realize)(item);

        /* Crear el GC cuando el canvas no esté en modo RGB */
        if(!item->canvas->aa)
                my_item->bg_gc = gdk_gc_new (item->canvas->layout.bin_window);
}

void
my_item_unrealize(GnomeCanvasItem* item)
{
        MyItem* my_item = MY_ITEM(item);

        if (my_item_parent_class->unrealize)
                (* my_item_parent_class->unrealize)(item);

        /* Crear el GC cuando el canvas no esté en modo RGB */
        if(!item->canvas->aa)
                gdk_gc_unref (my_item->bg_gc);
}

  

Dibujar en el canvas

Para poner contenido en el canvas hay que implementar al menos dos métodos. Uno de ellos es update, y el segundo puede ser draw o render, aunque es posible tener los dos.

render y draw

La diferencia entre draw y render es que draw es llamado cuando el canvas está en modo GDK, mientras que render es llamado cuando el canvas está en modo RGB.

struct _GnomeCanvasItemClass {
        GtkObjectClass parent_class;

/* ... */

        double (* draw) (GnomeCanvasItem *item, GdkDrawable *drawable,
                       int x, int y, int width, int height);

        void (* render) (GnomeCanvasItem *item, GnomeCanvasBuf *buf);
}

  

En draw, el argumento item indica qué objeto se solicita dibujar en el GdkDrawable indicado por el argumento drawable. Los cuatro últimos argumentos indican la zona que se pidea actualizar.

En render, la información se pasa a través de una estructura de tipo GnomeCanvasBuf.

/* Data for rendering in antialiased mode */
typedef struct {
        /* 24-bit RGB buffer for rendering */
        guchar *buf;

        /* Rectangle describing the rendering area */
        ArtIRect rect;

        /* Rowstride for the buffer */
        int buf_rowstride;

        /* Background color, given as 0xrrggbb */
        guint32 bg_color;

        /* Invariant: at least one of the following flags is true. */

        /* Set when the render rectangle area is the solid color bg_color */
        unsigned int is_bg : 1;

        /* Set when the render rectangle area is represented by the buf */
        unsigned int is_buf : 1;
} GnomeCanvasBuf;

  
  • buf es un puntero al búfer RGB donde se debe dibujar.

  • rect indica la zona donde se quiere dibujar.

  • buf_rowstride es el número de bytes que tiene cada fila de pixels

  • bg_color es el color que debe tener el fondo del canvas.

  • is_bg e is_buf indican cuándo se ha dibujado en el búfer RGB y cuándo se debe usar el color del fondo.

Siempre hay que comprobar el valor de los miembros is_buf e is_bg.

  • Si queremos poner contenido en el búfer RGB, is_buf debe ser TRUE y is_bg FALSE

  • Si is_bg es TRUE, el canvas usará el valor del miembro bg_color para dibujar un rectángulo sólido.

  • Cuando se quiere dibujar sobre el búfer RGB es preferible llamar a gnome_canvas_buf_ensure_buf para evitar que en el búfer hayan residuos.

void gnome_canvas_buf_ensure_buf(buf); 
GnomeCanvasBuf*  buf;

Ejemplo 10. Verificar is_buf y is_bg

void my_item_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf)
{
        gnome_canvas_buf_ensure_buf(buf)
        buf->is_bg = 0;

        /* Dibujar en el búfer */

}
  

update

Los métodos render y draw son llamados cada vez que el canvas necesita actualizar el widget.

El canvas puede llamar a estos eventos varias veces en una sola actualización, por lo que es necesario que los eventos tengan la menor carga posible, ya que, de lo contrario, actualizar el contenido del canvas puede ser muy lento.

Para evitar cargar los métodos render o draw se usa el método update. update es llamado sólo cuando los datos del objeto cambian, y hace falta actualizarse. Casi siempre, después de update, el canvas llamará a draw o render.

struct _GnomeCanvasItemClass
{
        GtkObjectClass parent_class;

/* ... */

        void (* update) (GnomeCanvasItem *item, double *affine,
                ArtSVP *clip_path, int flags);
}

  

El canvas usa este método para indicarle al objeto que se actualice él mismo y guarde su estado, para que cuando sea necesario pintar se haga más rápidamente, sin tener que calcular repetidamente los datos del objeto.

Es muy importante usar este evento para hacer los cálculos que se necesiten para dibujar en el canvas, y usar render o draw únicamente para mostrar los datos sin tener que calcular nada (o lo menos posible).

El objeto genérico GnomeCanvasShape

El canvas tiene un objeto propio que de por sí no hace nada, pero sirve de base para otros como GnomeCanvasRect, GnomeCanvasPolygon o GnomeCanvasEllipse. Este objeto es GnomeCanvasShape

Este objeto implementa los eventos necesarios para dibujar cualquier figura sobre el canvas. Para definir la figura que contiene el objeto se usan rutas.

Rutas

Las rutas son similares a las instrucciones PostScript de dibujo: definen figuras con funciones como LINETO, MOVETO o CURVETO.

Para enviar una ruta a GnomeCanvasShape hay que crearla antes con gnome_canvas_path_def_new. Esta función devolverá un puntero a la estructura GnomeCanvasPathDef, que será usada por funciones como gnome_canvas_path_def_moveto, gnome_canvas_path_def_lineto o gnome_canvas_path_def_curveto.

Cuando las rutas no son más necesarias hay que liberarlas con gnome_canvas_path_def_unref.

GnomeCanvasPathDef* gnome_canvas_path_def_new(); 
 ;
void gnome_canvas_path_def_moveto(path,  
 x,  
 y); 
GnomeCanvasPathDef*  path;
gdouble  x;
gdouble  y;
void gnome_canvas_path_def_lineto(path,  
 x,  
 y); 
GnomeCanvasPathDef*  path;
gdouble  x;
gdouble  y;
void gnome_canvas_path_def_curveto(path,  
 x0,  
 y0,  
 x1,  
 y1,  
 x2,  
 y2); 
GnomeCanvasPathDef*  path;
gdouble  x0;
gdouble  y0;
gdouble  x1;
gdouble  y1;
gdouble  x2;
gdouble  y2;
void gnome_canvas_path_def_unref(path); 
GnomeCanvasPathDef*  path;

Para asegurar que una figura está cerrada es posible llamar a gnome_canvas_path_def_closepath o gnome_canvas_path_def_closepath_current. Con gnome_canvas_path_def_closepath se añadirá una línea entre el último punto creado y el primero (si están separados), y con gnome_canvas_path_def_closepath_current se moverán las coordenadas del último punto para que esté encima del primero. Con ambas funciones se garantiza que la figura definida por la ruta está cerrada.

Pasar una ruta a GnomeCanvasShape

Una vez que la ruta esté creada es necesario mandarla a GnomeCanvasShape. Esto se hace con gnome_canvas_shape_set_path_def. Una vez que la ruta se haya mandado a GnomeCanvasShape hay que liberarla con gnome_canvas_path_def_unref, a menos que se vaya a volver a emplear en otra llamada a gnome_canvas_shape_set_path_def.

void gnome_canvas_shape_set_path_def(shape,  
 path); 
GnomeCanvasShape*  shape;
GnomeCanvasPathDef*  path;

Uso

Para usar GnomeCanvasShape hay que crear un nuevo objeto que derive de GnomeCanvasShape en lugar de GnomeCanvasItem.

El único evento que se debe implementar es el update, ya que GnomeCanvasShape implementa los demás métodos necesarios.

Ejemplo 11. Cabecera para usar GnomeCanvasShape

typedef struct _MyItem MyItem;
typedef struct _MyItemClass MyItemClass;

struct _MyItem {
        GnomeCanvasShape shape_item;

        /* datos propios de este objeto */
};

struct _MyItemClass {
        GnomeCanvasShapeClass parent_class;
};
  

Ejemplo 12. Funciones para usar GnomeCanvasShape

static GnomeCanvasShapeClass* my_item_parent_class;

void
my_item_class_init(MyItemClass* class)
{
        GnomeCanvasItemClass *item_class;

        item_class = (GnomeCanvasItemClass *) class;

        item_class->update = my_item_update;
        my_item_parent_class = g_type_class_peek_parent (class);
}

void
my_item_update (GnomeCanvasItem *item, gdouble *affine,
        ArtSVP *clip_path, gint flags)
{
        GnomeCanvasPathDef* path_def;

        /* crear una ruta */
        path_def = gnome_canvas_path_def_new();

        /*
         * llamadas a gnome_canvas_path_def_* para crear una figura
         */

        /* meter la ruta en el GnomeCanvasShape */
        gnome_canvas_path_def_closepath_current(path_def);
        gnome_canvas_shape_set_path_def (GNOME_CANVAS_SHAPE (item), path_def);
        gnome_canvas_path_def_unref(path_def);

        /* Llamar al update de GnomeCanvasShape para terminar */
        if (my_item_parent_class->update)
                (* my_item_parent_class->update) (item, affine, clip_path, flags);
}

  

Al final de la función de update es necesario llamar al update de GnomeCanvasShape. Éste creará a partir de la ruta que hemos definido con gnome_canvas_shape_set_path_def los datos que necesite para responder a los demás evento del canvas.