24.3. El Control del Área de Dibujo, y Dibujar

Ahora toca dibujar en la pantalla. El control que se usa para éso es la DrawingArea (Área de dibujo). Un control de área de dibujo es básicamente una ventana X; sin más. Es un lienzo en blanco en el que se puede pintar lo que se desee. Un área de dibujo se crea usando la llamada:

  darea = gtk.DrawingArea()
    

Se puede especificar un tamaño predeterminado para el control usando:

  darea.set_size_request(width, height)
    

Este tamaño predeterminado se puede cambiar, como en todos los controles, llamando al método set_size_request(), y también se puede modificar si el usuario cambia manualmente el tamaño de la ventana que contiene el área de dibujo.

Hay que aclarar que, cuando creamos un control DrawingArea, se adquiere toda la responsabilidad de dibujar su contenido. Si la ventana se tapa y luego se muestra, se recibe un evento de exposición y se debe redibujar lo que antes se ocultó.

Tener que recordar lo que había dibujado en la pantalla para que podamos redibujarlo es, cuanto menos, una inconveniencia. Además, puede resultar molesto visualmente el que partes de la ventana se limpien para luego dibujarse paso a paso. La solución a este problema es el uso de un pixmap oculto. En vez de dibujar directamente en la pantalla se dibuja en la imagen almacenada en la memoria del servidor (que no se muestra), y luego se copian las partes relevantes a la pantalla.

Para crear un pixmap fuera de pantalla, se usa esta función:

  pixmap = gtk.gdk.Pixmap(window, width, height, depth=-1)
    

El parámetro window especifica una ventana gtk.gdk.Window de la que este pixmap tomará algunas propiedades. width (ancho) y height (alto) especifican el tamaño del pixmap. depth especifica la profundidad de color, es decir, el número de bits por píxel de la nueva ventana. Si depth es -1 o se omite, coincidirá con la profundidad de la ventana.

Creamos el pixmap en nuestro manejador del evento "configure_event". Este evento se genera cada vez que la ventana cambia de tamaño, incluso cuando se crea por primera vez.

   32   # Creamos un nuevo mapa de píxeles de respaldo con el tamaño adecuado
   33   def configure_event(widget, event):
   34       global pixmap
   35
   36       x, y, width, height = widget.get_allocation()
   37       pixmap = gtk.gdk.Pixmap(widget.get_window(), width, height)
   38       gtk.draw_rectangle(pixmap, widget.get_style().white_gc,
   39                          gtk.TRUE, 0, 0, width, height)
   40
   41       return gtk.TRUE
    

La llamada a draw_rectangle() deja inicialmente el pixmap en blanco. Se comentará esto un poco más detenidamente en breve.

El manejador del evento de exposición simplemente copia las partes correspondientes del pixmap en el área de dibujo usando el método draw_pixmap(). (Se determina el área que se debe redibujar mediante el atributo event.area del evento de exposición):

   43   # Redibujamos la pantalla a partir del pixmap de respaldo
   44   def expose_event(widget, event):
   45       x , y, width, height = event.area
   46       widget.window.draw_drawable(widget.get_style().fg_gc[gtk.STATE_NORMAL],
   47                                   pixmap, x, y, x, y, width, height)
   48       return gtk.FALSE
    

Ya se ha visto cómo manetener la pantalla sincronizada con el pixmap de respaldo, pero ¿cómo se pintan cosas interesantes en el pixmap? Existe un gran número de llamadas de PyGTK para dibujar en objetos dibujables. Un objeto dibujable es sencillamente algo en lo que se puede dibujar. Puede ser una ventana, un pixmap, o un bitmap (imagen en blanco y negro). Ya se han visto dos de esas llamadas con anterioridad, draw_rectangle() y draw_pixmap(). La lista completa es:

  # dibuja punto
  drawable.draw_point(gc, x, y)

  # dibuja línea
  drawable.draw_line(gc, x1, y1, x2, y2)

  # dibuja rectángulo
  drawable.draw_rectangle(gc, fill, x, y, width, height)

  # dibuja arco
  drawable.draw_arc(gc, fill, x, y, width, height, angle1, angle2)

  # dibuja polígono
  drawable.draw_polygon(gc, fill, points)

  # dibuja dibujable
  drawable.draw_drawable(gc, src, xsrc, ysrc, xdest, ydest, width, height)

  # dibuja puntos
  drawable.draw_points(gc, points)

  # dibuja líneas
  drawable.draw_lines(gc, points)

  # dibuja segmentos
  drawable.draw_segments(gc, segments)

  # dibuja imagen rgb
  drawable.draw_rgb_image(gc, x, y, width, height, dither, buffer, rowstride)

  # dibuja imagen rgb 32 bits
  drawable.draw_rgb_32_image(gc, x, y, width, height, dither, buffer, rowstride)

  # dibuja imagen escala grises
  drawable.draw_gray_image(gc, x, y, width, height, dither, buffer, rowstride)
    

Los métodos del área de dibujo son los mismos que los métodos de dibujo de los objetos dibujables por lo que se puede consultar la sección Métodos de Dibujo para más detalles sobre estas funciones. Todas estas funciones comparten los primeros argumentos. El primer argumento es un contexto gráfico (gc).

Un contexto gráfico encapsula información sobre cuestiones como los colores de fondo y frente o el ancho de línea. PyGTK posee un conjunto completo de funciones para crear y modificar contextos gráficos, pero para mantener las cosas fáciles aquí simplemente se usarán contextos gráficos predefinidos. Consúltese la sección Contexto Gráfico del Área de Dibujo para obtener más información sobre los contextos gráficos. Cada control tiene un estilo asociado (que se puede modificar en un fichero gtkrc, según se indica en la sección Ficheros rc de GTK+.) Éste, entre otras cosas, almacena diversos contextos gráficos. Algunos ejemplos del acceso a estos contextos gráficos son:

  widget.get_style().white_gc

  widget.get_style().black_gc

  widget.get_style().fg_gc[STATE_NORMAL]

  widget.get_style().bg_gc[STATE_PRELIGHT]
    

Los campos fg_gc, bg_gc, dark_gc, y light_gc se indexan con un parámetro que puede tomar los siguientes valores:

  STATE_NORMAL,      # El estado durante la operación normal
  STATE_ACTIVE,      # El control está activado, como cuando se pulsa un botón
  STATE_PRELIGHT,    # El puntero del ratón está sobre el control
  STATE_SELECTED,    # El control está seleccionado
  STATE_INSENSITIVE  # El control está desactivado
    

Por ejemplo, para STATE_SELECTED el color de frente predeterminado es el blanco y el color de fondo predeterminado es azul oscuro.

La función draw_brush() (pinta trazo) del ejemplo, que es la que realmente dibuja en el pixmap, es la siguiente:

   50   # Dibujamos un rectángulo en la pantalla
   51   def draw_brush(widget, x, y):
   52       rect = (int(x - 5), int(y - 5), 10, 10)
   53       pixmap.draw_rectangle(widget.get_style().black_gc, gtk.TRUE,
   54                             rect[0], rect[1], rect[2], rect[3])
   55       apply(widget.queue_draw_area, rect)
    

Tras dibujar el rectángulo que representa el trazo en el pixmap llamamos a la función:

  widget.queue_draw_area(x, y, width, height)
    

que notifica a X que ese área ha de ser actualizada. En algún momento X generará un evento de exposición (posiblemente combinando las áreas que se le pasan en varias llamadas a draw()) que hará que se llame al manejador del evento de exposición que se creó anteriormente que copiará las partes relevantes en la pantalla.

Y... ya se ha tratado todo el programa de dibujo, exceptuando algunos detalles mundanos como la creación de la ventana principal.