22.3. Métodos de Arrastrar y Soltar

22.3.1. Configuración del Control Origen

El método drag_source_set() especifica un conjunto de tipos objetivo para una operación de arrastrar en un control.

  widget.drag_source_set(start_button_mask, targets, actions)

Los parámetros significan lo siguiente:

  • widget especifica el control fuente

  • start_button_mask especifica una máscara de bits de los botones que pueden empezar a arrastrar (por ejemplo BUTTON1_MASK).

  • targets especifica una lista de los tipos de datos objetivos que se manejarán.

  • actions especifica un máscara de bits de las acciones posibles para arrastrar desde esta ventana.

El parámetro targets es una lista de tuplas similar a:

  (target, flags, info)

target especifica una cadena de caracteres que representa el tipo de arrastre.

flags restringe la aplicación del arrastre. flags puede ser 0 (sin limitación del ámbito) o las siguientes constantes:

  gtk.TARGET_SAME_APP    # El objetivo sólo se puede seleccionar para arrastres
dentro de una única aplicación.

  gtk.TARGET_SAME_WIDGET # El objetivo sólo se puede seleccionar para arrastres
dentro del mismo control.

info es un identificador entero asignado por la aplicación.

Si no es necesario que un control siga siendo el orígen de operaciones arrastrar-y-soltar, el método drag_source_unset() se puede usar para eliminar un conjunto de tipos de objetivos arrastrar-y-soltar.

  widget.drag_source_unset()

22.3.2. Señales en el Control Fuente

Las siguientes señales se envian al control fuente durante una operación de arrastrar-y-soltar.

Tabla 22.1. Señales del Control Fuente

drag_begin (comienzo de arrastre)def drag_begin_cb(widget, drag_context, data):
drag_data_get (obtención de datos de arrastre)def drag_data_get_cb(widget, drag_context, selection_data, info, time, data):
drag_data_delete (eliminación de datos de arrastre)def drag_data_delete_cb(widget, drag_context, data):
drag_end (fin de arrastre)def drag_end_cb(widget, drag_context, data):

El manejador de señal "drag-begin" se puede usar para configurar algunas condiciones iniciales tales como el icono de arrastre usando para ello uno de los siguientes métodos del la clase Widget: drag_source_set_icon(), drag_source_set_icon_pixbuf(), drag_source_set_icon_stock(). El manejador de señal "drag-end" puede usarse para deshacer las acciones del manejador de señal "drag-begin".

El manejador de señal "drag-data-get" debería devolver los datos de arrastre que coincidan con el objetivo especificado por info. Rellena gtk.gdk.SelectionData con los datos de arrastre.

El manejador de señal "drag-delete" se usa para borrar los datos de arrastre de una acción gtk.gdk.ACTION_MOVE tras haberlos copiado.

22.3.3. Configuración de un Control Destino

drag_dest_set() especifica que este control puede ser destino de operaciones arrastrar-y-soltar y define los tipos que admite.

drag_dest_unset() especifica que el control no puede recibir más operaciones arrastrar-y-soltar.

  widget.drag_dest_set(flags, targets, actions)

  widget.drag_dest_unset()
    

flags especifica qué acciones debe realizar GTK+ por parte del control cuando se suelte algo en él. Los valores posibles son:

gtk.DEST_DEFAULT_MOTION

Si está activado para un control, GTK+ comprobará si el arrastre se corresponde con algún objetivo y acción del control cuando se arrastre por encima de él. Entonces GTK+ llamará a drag_status() según corresponda.

gtk.DEST_DEFAULT_HIGHLIGHT

Si está activado para un control, GTK+ resaltará el control siempre que el arrastre esté encima del mismo y el formato y la acción sean aceptables.

gtk.DEST_DEFAULT_DROP

Si está activado para un control, GTK+ comprobará si el arrastre se corresponde con algún objetivo y acción de dicho control. Si es así, GTK+ llamará a drag_data_get() de parte del control. No importa si el soltar tiene éxito o no, GTK+ llamará a drag_finish(). Si la acción fue mover y el arrastre tuvo éxito, entonces se pasará TRUE como argumento delete (borrar) a drag_finish().

gtk.DEST_DEFAULT_ALL

Si está activo, especifica que todas las acciones anteriores deben ejecutarse.

targets es una lista de tuplas de información de objetivos tal y como se describe más arriba.

actions es una máscara de bits de las posibles acciones que realizar cuando se arrastre sobre este control. Los valores posibles se pueden componer con la operación OR y son los siguientes:

  gtk.gdk.ACTION_DEFAULT # acción predeterminada
  gtk.gdk.ACTION_COPY    # acción copiar
  gtk.gdk.ACTION_MOVE    # acción mover
  gtk.gdk.ACTION_LINK    # acción enlazar
  gtk.gdk.ACTION_PRIVATE # acción privada
  gtk.gdk.ACTION_ASK     # acción preguntar
    

targets y actions son ignoradas si flags no contiene gtk.DEST_DEFAULT_MOTION o gtk.DEST_DEFAULT_DROP. En este caso la aplicación debe manejar las señales "drag-motion" y "drag-drop".

El manejador de señal "drag-motion" debe determinar si los datos de arrastre son apropiados comparando para ello los objetivos del destino con los objetivos gtk.gdk.DragContext y opcionalmente examinando los datos de arrastre llamando el método drag_get_data() method. Se debe llamar al método gtk.gdk.DragContext. drag_status() para actualizar el estado de drag_context.

El manejador de señal "drag-drop" debe determinar el objetivo que corresponda usando el método del control drag_dest_find_target() y después solicitar los datos de arrastre mediante el método del control drag_get_data(). Los datos estarán disponibles en el manejador "drag-data-received".

El programa dragtargets.py muestra todos los posibles objetivos de una operación de arrastre en un control de tipo etiqueta (label):

    1    #!/usr/local/env python
    2
    3    import pygtk
    4    pygtk.require('2.0')
    5    import gtk
    6
    7    def motion_cb(wid, context, x, y, time):
    8        context.drag_status(gtk.gdk.ACTION_COPY, time)
    9        return True
   10
   11    def drop_cb(wid, context, x, y, time):
   12        l.set_text('\n'.join([str(t) for t in context.targets]))
   13        return True
   14
   15    w = gtk.Window()
   16    w.set_size_request(200, 150)
   17    w.drag_dest_set(0, [], 0)
   18    w.connect('drag_motion', motion_cb)
   19    w.connect('drag_drop', drop_cb)
   20    w.connect('destroy', lambda w: gtk.main_quit())
   21    l = gtk.Label()
   22    w.add(l)
   23    w.show_all()
   24
   25    gtk.main()

El programa crea una ventana que se configura como destino de arrastre para ningún objetivo y ninguna acción poniendo las banderas (flags) a cero. Se conectan los manejadores motion_cb() y drop_cb() a las señales "drag-motion" y "drag-drop" respectivamente. El manejador motion_cb() configura el estado de arrastre para que se permita soltar. drop_cb() modifica el texto de la etiqueta a una cadena de caracteres que contenga los objetivos de arrastre e ignora los datos de tal modo que la acción de soltar no se completa en ningún caso..

22.3.4. Señales en el Control Destino

Durante una operación arrastrar-y-soltar se envian las siguientes señales al control destino.

Tabla 22.2. Señales del Control Destino

drag_motion (movimiento de arrastre)def drag_motion_cb(widget, drag_context, x, y, time, data):
drag_drop (arrastre soltado)def drag_drop_cb(widget, drag_context, x, y, time, data):
drag_data_received (datos recibidos)def drag_data_received_cb(widget, drag_context, x, y, selection_data, info, time, data):

El programa de ejemplo dragndrop.py muestra el uso de arrastrar y soltar en una aplicación. Un botón con un icono xpm (en gtkxpm.py) es el origen del arrastre y proporciona tanto texto como datos xpm. Un control de disposición es el destino para soltar el xpm mientras que un botón es el destino para soltar el texto. La figura Figura 22.1, “Ejemplo de Arrastrar y Soltar” ilustra la ventana del programa tras soltar el xpm en el control de disposición y el texto en el botón:

Figura 22.1. Ejemplo de Arrastrar y Soltar

Ejemplo de Arrastrar y Soltar

El código fuente de dragndrop.py es:

    1    #!/usr/bin/env python
    2
    3    # ejemplo dragndrop.py
    4
    5	import pygtk
    6	pygtk.require('2.0')
    7	import gtk
    8	import string, time
    9
   10    import gtkxpm
   11
   12    class DragNDropExample:
   13        HEIGHT = 600
   14        WIDTH = 600
   15        TARGET_TYPE_TEXT = 80
   16        TARGET_TYPE_PIXMAP = 81
   17        fromImage = [ ( "text/plain", 0, TARGET_TYPE_TEXT ),
   18                  ( "image/x-xpixmap", 0, TARGET_TYPE_PIXMAP ) ]
   19        toButton = [ ( "text/plain", 0, TARGET_TYPE_TEXT ) ]
   20        toCanvas = [ ( "image/x-xpixmap", 0, TARGET_TYPE_PIXMAP ) ]
   21
   22        def layout_resize(self, widget, event):
   23            x, y, width, height = widget.get_allocation()
   24            if width > self.lwidth or height > self.lheight:
   25                self.lwidth = max(width, self.lwidth)
   26                self.lheight = max(height, self.lheight)
   27                widget.set_size(self.lwidth, self.lheight)
   28
   29        def makeLayout(self):
   30            self.lwidth = self.WIDTH
   31            self.lheight = self.HEIGHT
   32            box = gtk.VBox(gtk.FALSE,0)
   33            box.show()
   34            table = gtk.Table(2, 2, gtk.FALSE)
   35            table.show()
   36            box.pack_start(table, gtk.TRUE, gtk.TRUE, 0)
   37            layout = gtk.Layout()
   38            self.layout = layout
   39            layout.set_size(self.lwidth, self.lheight)
   40            layout.connect("size-allocate", self.layout_resize)
   41            layout.show()
   42            table.attach(layout, 0, 1, 0, 1, gtk.FILL|gtk.EXPAND,
   43                         gtk.FILL|gtk.EXPAND, 0, 0)
   44            # se crean las barras de desplazamiento que se empaquetan en la tabla
   45            vScrollbar = gtk.VScrollbar(None)
   46            vScrollbar.show()
   47            table.attach(vScrollbar, 1, 2, 0, 1, gtk.FILL|gtk.SHRINK,
   48                         gtk.FILL|gtk.SHRINK, 0, 0)
   49            hScrollbar = gtk.HScrollbar(None)
   50            hScrollbar.show()
   51            table.attach(hScrollbar, 0, 1, 1, 2, gtk.FILL|gtk.SHRINK,
   52                         gtk.FILL|gtk.SHRINK,
   53                         0, 0)
   54            # indicamos que las barras de desplazamiento usen los ajustes del control disposición
   55            vAdjust = layout.get_vadjustment()
   56            vScrollbar.set_adjustment(vAdjust)
   57            hAdjust = layout.get_hadjustment()
   58            hScrollbar.set_adjustment(hAdjust)
   59            layout.connect("drag_data_received", self.receiveCallback)
   60            layout.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
   61                                      gtk.DEST_DEFAULT_HIGHLIGHT |
   62                                      gtk.DEST_DEFAULT_DROP,
   63                                      self.toCanvas, gtk.gdk.ACTION_COPY)
   64            self.addImage(gtkxpm.gtk_xpm, 0, 0)
   65            button = gtk.Button("Text Target")
   66            button.show()
   67            button.connect("drag_data_received", self.receiveCallback)
   68            button.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
   69                                 gtk.DEST_DEFAULT_HIGHLIGHT |
   70                                 gtk.DEST_DEFAULT_DROP,
   71                                 self.toButton, gtk.gdk.ACTION_COPY)
   72            box.pack_start(button, gtk.FALSE, gtk.FALSE, 0)
   73            return box
   74
   75        def addImage(self, xpm, xd, yd):
   76            hadj = self.layout.get_hadjustment()
   77            vadj = self.layout.get_vadjustment()
   78            style = self.window.get_style()
   79            pixmap, mask = gtk.gdk.pixmap_create_from_xpm_d(
   80                self.window.window, style.bg[gtk.STATE_NORMAL], xpm)
   81            image = gtk.Image()
   82            image.set_from_pixmap(pixmap, mask)
   83            button = gtk.Button()
   84            button.add(image)
   85            button.connect("drag_data_get", self.sendCallback)
   86            button.drag_source_set(gtk.gdk.BUTTON1_MASK, self.fromImage,
   87                                   gtk.gdk.ACTION_COPY)
   88            button.show_all()
   89            # ajustamos según el desplazamientos de la disposición - la posición del evento
   90            # es relativo a la zona visible, no al tamaño del control disposición
   91            self.layout.put(button, int(xd+hadj.value), int(yd+vadj.value))
   92            return
   93
   94        def sendCallback(self, widget, context, selection, targetType, eventTime):
   95            if targetType == self.TARGET_TYPE_TEXT:
   96                now = time.time()
   97                str = time.ctime(now)
   98                selection.set(selection.target, 8, str)
   99            elif targetType == self.TARGET_TYPE_PIXMAP:
  100                selection.set(selection.target, 8,
  101                              string.join(gtkxpm.gtk_xpm, '\n'))
  102
  103        def receiveCallback(self, widget, context, x, y, selection, targetType,
  104                            time):
  105            if targetType == self.TARGET_TYPE_TEXT:
  106                label = widget.get_children()[0]
  107                label.set_text(selection.data)
  108            elif targetType == self.TARGET_TYPE_PIXMAP:
  109                self.addImage(string.split(selection.data, '\n'), x, y)
  110
  111        def __init__(self):
  112            self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
  113            self.window.set_default_size(300, 300)
  114            self.window.connect("destroy", lambda w: gtk.main_quit())
  115            self.window.show()
  116            layout = self.makeLayout()
  117            self.window.add(layout)
  118
  119    def main():
  120        gtk.main()
  121
  122    if __name__ == "__main__":
  123        DragNDropExample()
  124        main()