Arrastrar y soltar con JavaScript y GJS

Este es uno de los capítulos del tutorial Aplicaciones nativas en JavaScript con GJS. Encontrarás los enlaces a todos los de capítulos, al final de este artículo.

Probablemente una de las operaciones mas características de las aplicaciones de escritorio con interfaz gráfica es la de arrastrar y soltar. Seguro que en mas de una ocasión te has encontrado realizando este gesto de arrastrar y soltar. Incluso, en alguna ocasión, te habrás quedado con dos palmos de narices haciendo ese gesto, para darte cuenta de que esa operación no está soportada por un determinado objeto. Y es que, es necesario dotar a determinados objetos de los mecanismos para que arrastrar y soltar sea posible, y esto es lo que te voy a mostrar en este capítulo del tutorial de aplicaciones nativas en JavaScript y GJS sobre arrastrar y soltar con JavaScript y GJS.

En este capítulo voy a dotar a un par de clases, que por defecto no te permiten arrastrar y soltar con JavaScript, de esta posibilidad. De esta forma, podrás convertir cualquier objeto en susceptible de ser origen y destino de una operación de arrastrar y soltar.

Arrastrar y soltar con JavaScript y GJS

Arrastrar y soltar con JavaScript y GJS

Un ejemplo práctico

Con el fin de que este capítulo del tutorial sea la mas visible posible de nuevo lo acompaño de un ejemplo, donde irás dotando poco a poco, a cada uno de los elementos de la posibilidad de ser origen o destino de una operación de arrastrar y soltar con JavaScript. Así, el ejemplo básico es el siguiente,

imports.gi.versions.Gtk = '3.0'
const {Gtk, GObject, GdkPixbuf, Gdk, Gio} = imports.gi;

Gtk.init(null);

var Ventana = GObject.registerClass(
    class Ventana extends Gtk.Window{
        _init(){
            super._init({
                defaultWidth: 200,
                defaultHeight: 200,
                title: "Demo de Drag and Drop"
            });
            this.connect('destroy', ()=>{
                Gtk.main_quit();
            });
            let grid = new Gtk.Grid({
                margin: 10,
                "baseline-row": Gtk.BaselinePosition.CENTER,
                "column-homogeneous": true,
                "column-spacing": 10,
                "row-homogeneous": true,
                "row-spacing": 10
            });
            this.add(grid);

            let label_origen = new Gtk.Label({label: "origen"});
            grid.attach(label_origen, 0, 0, 1, 1);

            let label_destino = new Gtk.Label({label: "Destino"});
            grid.attach(label_destino, 1, 0, 1, 1);

            let file = '/usr/share/icons/gnome/256x256/categories/applications-development.png'
            let pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(file, 256, 256);
            let image_origen = Gtk.Image.new_from_pixbuf(pixbuf);
            grid.attach(image_origen, 0, 1, 1, 1);

            let image_destino = new Gtk.Image();
            grid.attach(image_destino, 1, 1, 1, 1);
        }
    }
); 

let ventana = new Ventana();
ventana.show_all();

Este código, directamente lo puedes copiar en el archivo ejemplo.js y ejecutarlo con el comando gjs ejemplo.js. Incluso, si le das permisos de ejecución, simplemente tendrás que ejecutar ./ejemplo.js.

Cuando lo pruebes, si intentas arrastrar desde la etiqueta origen hasta la etiqueta destino, verás que no sucede absolutamente nada. Esto es así, porque no está preparado para soportar la operación de arrastrar y soltar con JavaScript. Realmente no solo con JavaScript, sino con ningún lenguaje. Esto es una característica propia de este objeto Gtk.Label.

Lo mismo sucederá cuando lo pruebes con la Gtk.Image, que tienes en la parte inferior. Si la intentas arrastrar, tampoco sucederá nada. Esto es por la misma razón que en el caso anterior.

Arrastra y soltar texto

Para facilitar comprender como funciona esto de arrastrar y soltar, lo tienes aquí divido en dos partes. Esta primera en la que encontrarás como hacerlo con texto, y en el siguiente apartado, hacerlo con imágenes.

Preparando el origen del arrastrar y soltar texto

Lo primero es indicarte que la clase Gtk.Label, no soporta directamente el evento drag-data-get, con lo que, lo tienes que envolver de una clase adicional, que será la encargada de recoger ese evento. Esta clase es Gtk.EventBox. Así, para envolver la clase, tienes que hacerlo de la siguiente forma,

let wrap_label_origen = new Gtk.EventBox();
wrap_label_origen.add(label_origen);

El siguiente paso, es indicar que esa envoltura, el Gtk.EventBox, será origen de un arrastrar y soltar y además especificar como. Para ello, tienes que utilizar las siguientes líneas de código,

wrap_label_origen.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY);
wrap_label_origen.drag_source_add_text_targets();

Con la primera línea indicas que es origen de una operación de arrastrar y soltar y que será sensible al botón derecho del ratón. Mientras que con la segunda línea indicarás que lo que copiará será texto.

Y por último, le tienes que decir como se va a comportar cuando realices una operación de arrastrar y soltar, porque realmente no sabe lo que va a copiar. Por ejemplo si tuvieras un objeto de la clase Gtk.Entry, la cuestión es sencilla, porque copias el texto contenido y lo arrastras, pero aquí es complejo.

Para indicar que es lo que vas a copiar, lo que tienes que hacer es conectar el evento de iniciar el movimiento de arrastrar, drag-data-get, con un método. En este caso, lo he definido de la siguiente forma,

wrap_label_origen.connect("drag-data-get", (widget, context, data, info , time)=>{
    let inner_label = widget.get_children()[0]
    let text = inner_label.get_text();
    data.set_text(text, text.length);
});

Como he envuelto el objeto inner_label de la clase Gtk.Label, con el objeto wrap_label_origen de la clase Gtk.Event, es necesario, sacarlo de su envoltura, para obtener el contenido. Esto lo puedes hacer con wiget.get_children()[0], atendiendo a que solo tienes un elemento.

Preparando el destino de arrastrar y soltar texto

De la misma forma que has preparado el origen, ahora tienes que preparar el destino, pero, en este caso es mas sencillo, en el sentido de que no necesitas envolver al objeto de la clase Gtk.Label, de un envoltorio. Básicamente tienes que indicar que será destino de una operación de arrastrar y soltar y que tipo de fuentes admite, que en este caso será texto. Para esto, simplemente tienes que utilizar dos líneas de código similares a las que viste anteriormente,

label_destino.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY);
label_destino.drag_dest_add_text_targets();

Y, al igual, que hiciste en el paso anterior, también tienes que indicar que vas a hacer con ese texto que has arrastrado. Ahora es sencillo, porque le vas a indicar que sustituya el del Gtk.Label por el que estás arrastrando,

label_destino.connect("drag-data-received", (widget, context, x, y, data, info, timestamp)=>{
    widget.set_text(data.get_text());
});

Arrastrar y soltar imágenes

Preparando el origen del arrastrar y soltar imágenes

De la misma forma que has visto como arrastrar y soltar texto, ahora toca arrastrar y soltar imágenes. Y de la misma forma que has envuelto a tu Gtk.Label de un Gtk.EventBox para iniciar el proceso de arrastrar, aquí hay que hacer exactamente lo mimo pero para el caso de Gtk.Image. Así,

let wrap_image_origen = new Gtk.EventBox();
wrap_image_origen.add(image_origen);

Ahora hay que indicarle que es origen de arrastrar y soltar de imágenes. Para esto,

wrap_image_origen.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY);
wrap_image_origen.drag_source_add_image_targets();

Pero además, si quieres que durante la operación de arrastrar y soltar una imagen, se muestre un icono que indique lo que se está arrastrando, en lugar de un icono genérico, tienes que añadir la siguiente línea,

wrap_image_origen.drag_source_set_icon_pixbuf(pixbuf);

Y ahora seleccionaremos aquello que queremos arrastrar,

wrap_image_origen.connect("drag-data-get", (widget, context, data, info, time)=>{
    let image = widget.get_children()[0];
    data.set_pixbuf(image.get_pixbuf());
});

Preparando el destino del arrastrar y soltar imágenes

Como verás a continuación la forma de preparar el destino del arrastrar y soltar imágenes es completamente equivalente a la que viste anteriormente con el texto, pero sustituyendo, texto por imágenes, evidentemente,

image_destino.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY);
image_destino.drag_dest_add_image_targets();
image_destino.connect("drag-data-received", (widget, context, x, y, data, info, timestamp)=>{
    widget.set_from_pixbuf(data.get_pixbuf());
});

Conclusión

Con esto has visto como realizar las operaciones de arrastrar y soltar tanto texto como imágenes de forma sencilla. Esto en general funciona así. Pero no siempre, en el sentido de que hay determinadas clases que tienen un comportamiento peculiar, y es necesario trabajarlo de forma diferente para completar todo el proceso de arrastrar y soltar. Incluso, es posible, tener objetos que admitan tanto texto como imagen como destino de una operación de arrastrar y soltar.


Imagen de portada de Levi XU en Unsplash

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *