24.2. Manejo de Eventos

Las señales GTK+ que hemos explicado sirven para acciones de alto nivel, tal como la selección de un elemento de menú. Sin embargo, a veces es útil tener información sobre eventos de más bajo nivel, como cuándo se mueve un ratón o se pulsa una tecla. También hay señales para estos eventos de bajo nivel. Los manejadores de estas señales tienen un parámetro adicional, que es un objeto gtk.gdk.Event, y contiene información sobre el evento. Por ejemplo, a los manejadores de eventos de movimiento se les pasa un objeto gtk.gdk.Event que contiene información de EventMotion, que (parcialmente) contiene atributos como:

  type       # tipo
  window     # ventana
  time       # tiempo
  x
  y
    ...
  state      # estado
    ...
    

window es la ventana en la que ha ocurrido el evento.

x e y proporcionan las coordenadas del evento.

type informa del tipo de evento, en este caso MOTION_NOTIFY. Estos tipos (incluidos en el módulo gtk.gdk) son:

NOTHING                código especial para indicar un evento nulo.

DELETE                 el manejador de ventanas ha pedido que se oculte o destruya la ventana de más alto nivel, normalmente cuando el usuario hace clic en un icono especial de la barra de título.

DESTROY                la ventana ha sido destruida.

EXPOSE                 toda o parte de la ventana se ha hecho visible y necesita redibujarse.

MOTION_NOTIFY          el puntero (normalmente el ratón) se ha movido.

BUTTON_PRESS           se ha presionado un botón del ratón.

_2BUTTON_PRESS         se ha hecho doble clic en un botón del ratón (2 veces dentro de un corto periodo de tiempo). Nótese que cada clic genera también un evento BUTTON_PRESS.

_3BUTTON_PRESS         se ha hecho clic 3 veces seguidas dentro de un corto periodo de tiempo en un botón del ratón. Nótese que cada clic genera también un evento BUTTON_PRESS.

BUTTON_RELEASE         se ha soltado un botón del ratón.

KEY_PRESS              se ha pulsado una tecla.

KEY_RELEASE            se ha soltado una tecla.

ENTER_NOTIFY           el puntero ha entrado en la ventana.

LEAVE_NOTIFY           el puntero ha salido de la ventana.

FOCUS_CHANGE           el foco del teclado ha entrado o dejado la ventana.

CONFIGURE              el tamaño, posición u orden de apilamiento de la ventana ha cambiado. Obsérvese que GTK+ no usa estos eventos en ventanas hijas (GDK_WINDOW_CHILD).

MAP                    se han reservado recursos para la ventana.

UNMAP                  se han liberado recursos para la ventana.

PROPERTY_NOTIFY        se ha borrado o cambiado una propiedad de la ventana.

SELECTION_CLEAR        la aplicación ha perdido la propiedad de una selección.

SELECTION_REQUEST      otra aplicación ha solicitado una selección.

SELECTION_NOTIFY       se ha recibido una selección.

PROXIMITY_IN           se ha hecho contacto en una superficie sensible de un dispositivo de entrada (por ejemplo, una tableta gráfica o pantalla sensible al tacto).

PROXIMITY_OUT          se ha perdido el contacto de una superfice sensible.

DRAG_ENTER             el ratón ha entrado en la ventana mientras se estaba arrastrando algo.

DRAG_LEAVE             el ratón ha salido de la ventana mientras se estaba arrastrando algo.

DRAG_MOTION            el ratón se ha movido por la ventana mientras se estaba arrastrando algo.

DRAG_STATUS            el estado de la operación de arrastre iniciada por la ventana ha cambiado.

DROP_START             se ha iniciado una operación de soltar en la ventana.

DROP_FINISHED          la operación de soltar iniciada por la ventana ha terminado.

CLIENT_EVENT           se ha recibido un mensaje desde otra aplicación.

VISIBILITY_NOTIFY      la visibilidad de la ventana ha cambiado.

NO_EXPOSE              indica que la región fuente estaba disponible completamente cuando partes de un dibujable fueron copiadas. No es muy útil.

SCROLL                 ?

WINDOW_STATE           ?

SETTING                ?
    

state especifica el modificador de estado cuando ocurre el evento (es decir, especifica que teclas auxiliares y botones del ratón estaban presionados). Es una combinación con el operador OR de algunas de las siguientes constantes (incluidas en el módulo gtk.gdk):

  SHIFT_MASK     # máscara de mayúsculas
  LOCK_MASK      # máscara de bloqueo
  CONTROL_MASK   # máscara de control
  MOD1_MASK      # máscara del modificador 1
  MOD2_MASK      # máscara del modificador 2
  MOD3_MASK      # máscara del modificador 3
  MOD4_MASK      # máscara del modificador 4
  MOD5_MASK      # máscara del modificador 5
  BUTTON1_MASK   # máscara del botón 1
  BUTTON2_MASK   # máscara del botón 2
  BUTTON3_MASK   # máscara del botón 3
  BUTTON4_MASK   # máscara del botón 4
  BUTTON5_MASK   # máscara del botón 5
    

Como con las demás señales, para determinar qué sucede cuando ocurre un evento se llama al método connect(). Pero también es necesario indicar a GTK+ qué eventos queremos tratar. Para ello llamamos al método:

  widget.set_events(events)
    

El argumento events especifica los eventos en los que se está interesado. Es una combinación con el operador OR de constantes que especifican los distintos tipos de eventos. Los tipos de eventos (del módulo gtk.gdk) son:

  EXPOSURE_MASK
  POINTER_MOTION_MASK
  POINTER_MOTION_HINT_MASK
  BUTTON_MOTION_MASK
  BUTTON1_MOTION_MASK
  BUTTON2_MOTION_MASK
  BUTTON3_MOTION_MASK
  BUTTON_PRESS_MASK
  BUTTON_RELEASE_MASK
  KEY_PRESS_MASK
  KEY_RELEASE_MASK
  ENTER_NOTIFY_MASK
  LEAVE_NOTIFY_MASK
  FOCUS_CHANGE_MASK
  STRUCTURE_MASK
  PROPERTY_CHANGE_MASK
  VISIBILITY_NOTIFY_MASK
  PROXIMITY_IN_MASK
  PROXIMITY_OUT_MASK
  SUBSTRUCTURE_MASK
    

Hay un par de cuestiones que es necesario tener en cuenta al llamar al método set_events() . En primer lugar, que debe ser llamado antes de que se cree la ventana X del control PyGTK (en la práctica esto significa que es preciso llamarlo inmediatamente después de crear el control). En segundo lugar, que el control debe tener una ventana X asociada. Por eficiencia, la mayoria de los controles no tienen su propia ventana sino que se dibujan en la ventana del control padre. Entre estos controles se incluyen:

  gtk.Alignment
  gtk.Arrow
  gtk.Bin
  gtk.Box
  gtk.Image
  gtk.Item
  gtk.Label
  gtk.Layout
  gtk.Pixmap
  gtk.ScrolledWindow
  gtk.Separator
  gtk.Table
  gtk.AspectFrame
  gtk.Frame
  gtk.VBox
  gtk.HBox
  gtk.VSeparator
  gtk.HSeparator
    

Para capturar eventos en estos controles se debe usar un control EventBox. Véase la sección del control EventBox para más detalles.

Los atributos de los eventos que PyGTK usa para cada tipo de evento son:

todos los eventos       type               # tipo
                        window             # ventana
                        send_event         # evento enviado

NOTHING
DELETE
DESTROY                                    # sin atributos adicionales

EXPOSE                 area                # área
                       count               # cuenta

MOTION_NOTIFY          time                # tiempo
                       x                   # x
                       y                   # y
                       pressure            # presión
                       xtilt               # inclinación x
                       ytilt               # inclinación y
                       state               # estado
                       is_hint             # es pista
                       source              # fuente
                       deviceid            # identificador de dispositivo
                       x_root              # x raíz
                       y_root              # y raíz

BUTTON_PRESS
_2BUTTON_PRESS
_3BUTTON_PRESS
BUTTON_RELEASE         time                # tiempo
                       x                   # x
                       y                   # y
                       pressure            # presión
                       xtilt               # inclinación x
                       ytilt               # inclinación y
                       state               # estado
                       button              # botón
                       source              # fuente
                       deviceid            # identificador de dispositivo
                       x_root              # x raíz
                       y_root              # y raíz

KEY_PRESS
KEY_RELEASE            time                # tiempo
                       state               # estado
                       keyval              # valor de tecla
                       string              # cadena de caracteres

ENTER_NOTIFY
LEAVE_NOTIFY           subwindow           # subventana
                       time                # tiempo
                       x                   # x
                       y                   # y
                       x_root              # x raíz
                       y_root              # y raíz
                       mode                # modo
                       detail              # detalle
                       focus               # foco
                       state               # estado

FOCUS_CHANGE           _in                 # dentro

CONFIGURE              x                   # x
                       y                   # y
                       width               # ancho
                       height              # alto

MAP
UNMAP                                      # sin atributos adicionales

PROPERTY_NOTIFY        atom                # átomo
                       time                # tiempo
                       state               # estado

SELECTION_CLEAR
SELECTION_REQUEST
SELECTION_NOTIFY       selection           # selección
                       target              # objetivo
                       property            # propiedad
                       requestor           # solicitante
                       time                # tiempo

PROXIMITY_IN
PROXIMITY_OUT          time                # tiempo
                       source              # fuente
                       deviceid            # identificador de dispositivo

DRAG_ENTER
DRAG_LEAVE
DRAG_MOTION
DRAG_STATUS
DROP_START
DROP_FINISHED          context             # contexto
                       time                # tiempo
                       x_root              # x raíz
                       y_root              # y raíz

CLIENT_EVENT           message_type        # tipo de mensaje
                       data_format         # formato de los datos
                       data                # datos

VISIBILTY_NOTIFY       state               # estado

NO_EXPOSE                                  # sin atributos adicionales
    

24.2.1. Scribble - Manejo de Eventos

En nuestro programa de dibujo querremos saber en qué momento se pulsa el botón del ratón y cuándo se mueve. Por tanto especificamos POINTER_MOTION_MASK y BUTTON_PRESS_MASK. También querremos saber cuándo hay que redibujar la ventana, y por ello especificamos EXPOSURE_MASK. Aunque deseamos que se nos notifique con un evento de configuración cuando el tamaño de la ventana cambie, no es necesario especificar el correspondiente STRUCTURE_MASK, puesto que se hace automáticamente para todas las ventanas.

Sin embargo, resulta problemático especificar solamente POINTER_MOTION_MASK, puesto que haría que el servidor añadiese un nuevo evento de moviento a la cola de eventos cada vez que el usuario mueve el ratón. Imaginemos que se tardan 0.1 segundos en tratar un evento de movimiento, pero el servidor X encola un nuevo evento de movimiento cada 0.05 segundos. Pronto nos encontraremos muy por detrás de lo que el usuario está dibujando. Si el usuario está dibujando durante 5 segundos, ¡se tardarían otros 5 segundos en recuperarse después de que suelte el botón del ratón!. Lo que se puede hacer es tratar un evento de movimiento por cada evento que procesemos. Para hacer esto debemos especificar POINTER_MOTION_HINT_MASK.

Cuando se especifica POINTER_MOTION_HINT_MASK, el servidor manda un evento de movimiento la primera vez que el puntero se mueve tras entrar en la ventana o después de que se pulse o suelte el botón. Los movimientos posteriores se suprimen hasta que se solicite explícitamente la posición del puntero con el siguiente método de gtk.gdk.Window:

  x, y, mask = window.get_pointer()
    

window es un objeto gtk.gdk.Window. x e y son las coordenadas del puntero y mask es la máscara de modificación para detectar qué teclas están pulsadas. (Hay un método de gtk.Widget, get_pointer(), que devuelve la misma información que el método gtk.gdk.Window.get_pointer() pero sin la información de máscara).

El programa de ejemplo scribblesimple.py demuestra el uso básico de eventos y manejadores de eventos. La figura Figura 24.2, “Ejemplo sencillo - Scribble” muestra el programa en acción:

Figura 24.2. Ejemplo sencillo - Scribble

Ejemplo sencillo - Scribble

Los manejadores de eventos se conectan a la drawing_area (área de dibujo) en las siguientes líneas:

   92       # Señales para gestionar el mapa de píxeles de respaldo
   93       drawing_area.connect("expose_event", expose_event)
   94       drawing_area.connect("configure_event", configure_event)
   95
   96       # señales de evento
   97       drawing_area.connect("motion_notify_event", motion_notify_event)
   98       drawing_area.connect("button_press_event", button_press_event)
   99
  100       drawing_area.set_events(gtk.gdk.EXPOSURE_MASK
  101                               | gtk.gdk.LEAVE_NOTIFY_MASK
  102                               | gtk.gdk.BUTTON_PRESS_MASK
  103                               | gtk.gdk.POINTER_MOTION_MASK
  104                               | gtk.gdk.POINTER_MOTION_HINT_MASK)
    

Los manejadores de eventos button_press_event() y motion_notify_event() en scribblesimple.py son:

   57   def button_press_event(widget, event):
   58       if event.button == 1 and pixmap != None:
   59           draw_brush(widget, event.x, event.y)
   60       return gtk.TRUE
   61
   62   def motion_notify_event(widget, event):
   63       if event.is_hint:
   64           x, y, state = event.window.get_pointer()
   65       else:
   66           x = event.x
   67           y = event.y
   68           state = event.state
   69
   70       if state & gtk.gdk.BUTTON1_MASK and pixmap != None:
   71           draw_brush(widget, x, y)
   72
   73       return gtk.TRUE
    

Los manejadores expose_event() y configure_event() se describirán más adelante.