El portapapeles, JavaScript y Gjs

Aplicaciones nativas en JavaScript con 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.

Si arrastrar y soltar es una operación típica en los entornos gráficos, que tengo que decir del uso del portapapeles. Esto de copiar y pegar es algo tan habitual, que no tenerlo disponible, es casi una aberración. Así, en este capítulo, encontrarás como relacionarte con el portapapeles. Conocer el tipo de contenido que hay en el portapapeles, reemplazarlo o modificarlo.

El portapapeles

El portapapeles, JavaScript y Gjs

Tengo que confesarte que el capítulo anterior, el referente a la operación de arrastrar y soltar no me resulta nada intuitivo. Existen algunos pasos, que me parecen algo extraños. Sin embargo, el caso de la clase portapapeles, Gtk.Clipboard, es, al menos para mi, realmente intuitivo. El comportamiento es el que, al menos yo espero, y el funcionamiento es realmente sencillo.

Indicarte, que de nuevo, como en el caso de la operación de arrastrar y soltar, se distingue entre, al menos, dos operaciones. Es decir, se distingue entre copiar y pegar texto, y copiar y pegar imágenes. Aunque, si profundizas, verás que todavía hay un caso mas, para el texto enriquecido. Sin embargo, este caso, no lo voy a abordar, al menos en este capítulo del tutorial.

Un ejemplo vale mas que mil palabras

De nuevo, siguiendo con la metodología de capítulos anteriores me voy a apoyar en un ejemplo, muy sencillo. En este ejemplo, encuentras un objeto de la clase Gtk.Entry, donde copiar y pegar texto, y un objeto de la clase Gtk.Image.

Para que las operaciones de copiar y pegar sean sencillas y visuales, encontrarás dos botones al lado de cada uno de los objetos anteriores. Uno para copiar y otro para pegar.

Así, el ejemplo es el que puedes ver a continuación,

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 entry_text = new Gtk.Entry();
            grid.attach(entry_text, 0, 0, 1, 1);

            let buttonCopyText = new Gtk.Button({label: "Copiar texto"});
            grid.attach(buttonCopyText, 2, 0, 1, 1);

            let buttonPasteText = new Gtk.Button({label: "Pegar texto"});
            grid.attach(buttonPasteText, 3, 0, 1, 1);

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

            let buttonCopyImage = new Gtk.Button({label: "Copiar imagen"});
            grid.attach(buttonCopyImage, 2, 1, 1, 1);

            let buttonPasteImage = new Gtk.Button({label: "Pegar imagen"});
            grid.attach(buttonPasteImage, 3, 1, 1, 1);
        }
    }
); 

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

Gtk.main();

En este ejemplo, si accionas los botones, verás que no se produce ninguna acción. Esto es lo que vas a hacer a continuación. Además, falta la clase Gtk.Clipboard, que es la que se encarga de gestionar este objeto.

Así, para añadir el clipboard, tienes que hacerlo de la siguiente forma,

let defaultDisplay = Gdk.Display.get_default();
this.clipboard = Gtk.Clipboard.get_default(defaultDisplay);

Trabajando con texto

Copiar texto

El primer paso va a consistir en copiar el texto que se encuentra en el objeto de la clase Gtk.Entry, en el portapapeles. Para ello, tienes que hacerlo de la siguiente forma,

buttonCopyText.connect("clicked", (widget)=>{
    let text = entry_text.get_text();
    this.clipboard.set_text(text, text.length);
});

La verdad es que podías mejorarlo ligeramente comprobando el contenido del objeto de la clase Gtk.Entry, y si no tiene nada no lo ponemos en el portapapeles. Para hacer esto, simplemente tienes que modificar el código de la siguiente forma,

buttonCopyText.connect("clicked", (widget)=>{
    let text = entry_text.get_text();
    if(text){
        this.clipboard.set_text(text, text.length);
    }
});

Pegar texto

El siguiente paso, es conseguir pegar lo que hay en el portapapeles en el objeto de la clase Gtk.Entry. De nuevo, en este caso, es algo relativamente sencillo, aunque no tanto como en el caso anterior. Tienes al menos dos opciones para pegar el texto. La primera es esperar a que el texto esté disponible,

buttonPasteText.connect("clicked", ()=>{
    let text = wait_for_text();
    if(text){
        entry_text.set_text(text);
    }
});

La segunda es mucho mas elegante, en tanto en cuanto el texto se mostrará cuando esté disponible, y mientras tanto podrás hacer otras acciones.

buttonPasteText.connect("clicked", ()=>{
    this.clipboard.request_text((clipboard, data)=>{
        if(data){
            entry_text.set_text(data);
        }
    });
});

Trabajando con imágenes

Copiando imágenes

Al igual que has hecho para el texto, el procedimiento para las imágenes es relativamente similar. Simplemente tienes que poner en el portapapeles el contenido de la imagen. Esto lo puedes hacer de la siguiente forma,

buttonCopyImage.connect("clicked", (widget)=>{
    this.clipboard.set_image(entry_image.get_pixbuf());
});

Al igual que en el caso del texto, lo puedes mejorar respecto de esta solución, realizando una comprobación previa de que la imagen tiene algún contenido. Aunque no tengo claro, que esto sea lo que espera el usuario. A lo mejor espera, que se machaque el contenido, para no llevarse la desagradable sorpresa de darse cuenta que está pegando algo que no quiere, o que simplemente espera otra imagen. Sea como fuere, la solución en este caso sería,

buttonCopyImage.connect("clicked", (widget)=>{
    let pixbuf = entry_image.get_pixbug();
    if(pixbuf){
        this.clipboard.set_image(pixbuf);
    }
});

Pegando imágenes

Y por último te queda pegar imágenes, y de nuevo la operativa es muy similar a la que has visto anteriormente para el texto. Fíjate en el código siguiente,

buttonPasteImage.connect("clicked", (widget)=>{
    this.clipboard.request_image((clipboard, pixbuf)=>{
        if(pixbuf){
            entry_image.set_from_pixbuf(pixbuf);
        }
    });
});

Como viste en el caso del texto, este es el método elegante, en tanto en cuanto no esperas en el bucle principal a que se cargue la imagen, sino que la imagen se mostrará cuando esté disponible, digamos que la carga en segundo plano. Como siempre, si lo que quieres es que el usuario espere hasta que la imagen esté cargada, puedes hacerlo de la siguiente forma,

buttonPasteImage.connect("clicked", (widget)=>{
    let pixbuf = this.clipboard.wait_for_image();
    if(pixbuf){
        entry_image.set_from_pixbuf(pixbuf);
    }
});

Conclusión

Estas son las operaciones básicas que puedes realizar con el portapapeles del sistema y con texto e imágenes. Existen también operaciones con texto enriquecido y con uris. Pero el procedimiento es muy similar al que acabo de mostrar en los dos ejemplos anteriores, y no creo que vayan a aportar mucho mas de lo que te he mostrado hasta el momento.


Imagen de portada Luba Ertel en Unsplash

Deja una respuesta

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