Home
  Software
    HowTo
      GTK+
        Intro

Idioma:
  English

Temas
  GIOChannel

Drag and drop en Python (pyGTK)

Introduccion

Uno de las cosas que me costó solucionar fue la implementación de Drag and drop (llevar datos de un widget a otro con un movimiento del cursor). El proceso detrás de esta operación no es sencilla, debido a que los widgets frecuentemente esperan datos en diferentes formatos.

El mecanismo implementado en GTK+ es tán versatil que DnD puede realizarse no solo en el mismo programa, sino también entre diferentes aplicaciones. Esto añada aún mas problemas, ya que diferentes programas pueden utilizar hasta diferentes encodificaciones.

Para complicar la vida aún mas, los ejemplos encontrados en tutoriales generalmente simplifican tanto, que realmente no son indicativos de casos reales.

En lo que sigue, se documento el caso de un práctico que implementé con los alumnos: La premisa era de fabricar una suerte de \'organizador\' para los horarios de clases, donde un widget contiene las clases a organizar, y otro widget contiene la grilla de destino.

Implementacion

Los widgets utilizados son: un gtk.TreeView/gtk.ListStore de origen, donde se encuentra la lista de clases a organizar, y un gtk.Layout que representará las clases. (En realidad, hay una serie de Layout widgets, organizado por escuela, y por año, pero para simplificar el tutorial no tomaremos en cuenta este detalle)

Cada operación del Drag and Dtop genera eventos, los cuales tenemos que atender en diferentes rutinas para lograr nuestro objetivo. Nuevamente, para simplificar, consideraremos únicamente el flujo de datos en un solo sentido, i.e. de la lista (treeview) a la grilla (layout).

Eventos

El estado antes de iniciar el movimiento de datos. A la izquierda el gtk.TreeView que muestra el contenido de un gtk.ListStore y que será la fuente de los datos a mover. A la derecha un gtk.Notebook, que contiene un gtk.Layout, el cual será el destino del movimiento.

 

Se inicio el proceso de Drag and Srop, apretando el botón izquierdo sobre la primera línea del treeview. Para que tenga efecto, tenemos que habilitar el widget de origen (el TreeView) para que reaccione, determinar la operaciones permisibles:

    mi_tv.drag_source_set(gtk.gdk.BUTTON1_MASK, \
                         [('text/plain', gtk.TARGET_SAME_APP, 1)],
                         gtk.gdk.ACTION_COPY)
El primer parámetro indica cual boton será el activador del evento.

El segundo parámetro, es una lista: Contiene 3 elementos de información sobre los datos que estan disponibles para el intercambio. En este caso, 'text/plain' (un designador estandarizado MIME) indica que solo se trata de textos comunos (ASCII). gtk.TARGET_SAME_APP indica que el intercambio solo es permitido dentro de la misma aplicación. '1' es un identificador del widget.

El tercer parámetro, finalmente, dice cual será la operación a realizar, ej. en este caso, se copiarán los datos.

Con solo esta declaración en el TreeView, se habilitará el icono nuevo que indica que se inició el movimiento.

Este evento no necesita atención del programa, entonces no es necesario de conectar una rutina de atención al evento.

 

Para que el destino pueda recibir (y reaccionar), una función similar a la mencionada arriba es necesaria en preparación:

    mi_layout.drag_dest_set(gtk.DEST_DEFAULT_MOTION,
                       [('text/plain', gtk.TARGET_SAME_APP, 1)], 
                       gtk.gdk.ACTION_COPY)
Este agregado aún no genera ningun efecto visible! Solo hará que, cuando el cursor ingresa al área del Layout, se empezarán a generar eventos que tenemos que sí atender con rutinas nuestras. Son unas cuantas:

mi_layout.connect("drag_motion", on_drag_motion)
se llama al mover el cursor sobre el área de destino (el Layout). Ya que está función recibiró continuamente las coordenadas, podemos utilizar la misma para determinar si el 'aterrizaje' de los datos es permisible o no.

mi_layout.connect("drag_drop", on_drag_drop
es llamado en el momento de \'liberar\' la cargar (soltar el botón). Ojo, es importante darse cuenta que el movimiento del botón no está llevando los datos! Es nuestra responsabilidad de buscar el dato en el origen! La rutina on_drag_drop es el momento para iniciar el intercambio de datos:
def on_drag_drop(self, wid, context, x, y, time):
    wid.drag_get_data(context, context.targets[-1], time)
    return True
'wid' es una referencia el widget de origen, lo que nos permite de hacer un pedido (drag_get_data) a este widget para que nos envie la información.

Claro, esto genera ahora un evento en el widget fuente! Significa que en el TreeView, también tenemos que conectar una rutina de atención:

mi_tv.connect("drag_data_get", on_drag_data_get)
. El código correspondiente a la función:
def on_drag_data_get(widget, context, selection, info, time):
    # Aquí accedemos al ListStore, para extraer la información y combinarla
    # en una sola cadena para el envio.
    selection.set(selection.target, 10, message)
'10' es el largo del mensaje a enviar. Aunque Python maneja al largo del string en forma automática, potencialmente estamos enviando 'message' a una aplicacóon escrita en otro idioma de programación!

Finalmente los datos están en camino! Cuando llegan al destino, generarán otro evento mas que tenemos que atender:

mi_layout.connect("drag_data_received", on_drag_data_received)
y la rutina que procesa la recepción:
def on_drag_data_received(wid, context, x, y, data, info, time):
    message = data.get_text()
    # Aquí procesamos el texto recibido...
    # y finalmente señalamos que el proceso de intercambio se terminó
    context.finish(True, False, time)

El resultado final (después de la transferencia, ya conociendo la duración de la clases, se ajusta el tamaño del bloque, se muestran los datos mas importantes, y además se agrega un cuadro de ayuda (tooltip) con toda la información).

2622
(c) John Coppens ON6JC/LW3HAZ correo